diff options
Diffstat (limited to '')
882 files changed, 287043 insertions, 0 deletions
diff --git a/src/VBox/ValidationKit/.scm-settings b/src/VBox/ValidationKit/.scm-settings new file mode 100644 index 00000000..83ad77a1 --- /dev/null +++ b/src/VBox/ValidationKit/.scm-settings @@ -0,0 +1,79 @@ +# $Id: .scm-settings $ +## @file +# Source code massager settings for the Validation Kit. +# + +# +# Copyright (C) 2017-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + + +# The validation kit is dual licensed. +--license-ose-dual + +*.c16: --treat-as .c +*.c32: --treat-as .c +*.c64: --treat-as .c + +*.css: --treat-as .h --no-fix-header-guards +*.js: --treat-as .h --no-fix-header-guards + +/testdriver/*.ps1: --treat-as Makefile +/testmanager/apache-template-*.conf: --treat-as Makefile + +# Skip stuff without licenses and such. +--filter-out-files *.txt +--filter-out-files *.html +--filter-out-files *.svg +--filter-out-files /testmanager/misc/htpasswd-logout +--filter-out-files /testmanager/misc/htpasswd-sample + +# Skip the XML for database diagrams. +--filter-out-files /testmanager/db/TestManagerDatabase/*.xml +--filter-out-files /testmanager/db/TestManagerDatabase.dmd + +# Skip ova test data +--filter-out-files *.ova +--filter-out-files *.pem + +# Skip some plain config files +--filter-out-files /utils/TestExecServ/linux/vboxtxs.service +--filter-out-files /utils/TestExecServ/win/*.reg +--filter-out-files /utils/usb/linux/usbtest.service + +# misc +/bootsectors/bs3kit/bs3kit-mangling-code.h: --no-fix-header-guards +/bootsectors/bs3kit/bs3kit-mangling-code-define.h: --no-fix-header-guards +/bootsectors/bs3kit/bs3kit-mangling-code-undef.h: --no-fix-header-guards +/bootsectors/bs3kit/bs3kit-template-header.h: --no-fix-header-guards +/bootsectors/bs3kit/bs3kit-template-footer.h: --no-fix-header-guards +/bootsectors/bs3kit/bs3-cmn-instantiate-common.h: --no-fix-header-guards +/bootsectors/bs3kit/*.h: --guard-relative-to-dir bootsectors/bs3kit/ --guard-prefix BS3KIT_INCLUDED_ + diff --git a/src/VBox/ValidationKit/Config.kmk b/src/VBox/ValidationKit/Config.kmk new file mode 100644 index 00000000..66ea42ca --- /dev/null +++ b/src/VBox/ValidationKit/Config.kmk @@ -0,0 +1,281 @@ +# $Id: Config.kmk $ +## @file +# kBuild Configuration file for the VirtualBox Validation Kit. +# + +# +# Copyright (C) 2010-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +VBOX_VALIDATIONKIT_CONFIG_KMK_INCLUDED = 1 + +# Include the top-level configure file. +ifndef VBOX_ROOT_CONFIG_KMK_INCLUDED + include $(PATH_ROOT)/Config.kmk +endif + +# +# Globals +# +VBOX_PATH_VALIDATIONKIT_SRC := $(PATH_ROOT)/src/VBox/ValidationKit + + +# +# Base template for Validation Kit R3 programs that drops the -static flag since we only want to use the +# static version of our own libraries and not the system libs. +# +# +TEMPLATE_VBOXVALIDATIONKITR3BASE = VBox Validation Kit ring-3 program base, both guest and host. +TEMPLATE_VBOXVALIDATIONKITR3BASE_EXTENDS = VBoxR3Static +ifeq ($(KBUILD_TARGET),win) + TEMPLATE_VBOXVALIDATIONKITR3BASE_LDFLAGS = $(filter-out -IntegrityCheck, $(TEMPLATE_VBoxR3Static_LDFLAGS)) +else ifn1of ($(KBUILD_TARGET), darwin solaris win) + TEMPLATE_VBOXVALIDATIONKITR3BASE_CFLAGS = $(filter-out -static, $(TEMPLATE_VBoxR3Static_CFLAGS)) + TEMPLATE_VBOXVALIDATIONKITR3BASE_CXXFLAGS = $(filter-out -static, $(TEMPLATE_VBoxR3Static_CXXFLAGS)) + TEMPLATE_VBOXVALIDATIONKITR3BASE_OBJCFLAGS = $(filter-out -static, $(TEMPLATE_VBoxR3Static_OBJCFLAGS)) + TEMPLATE_VBOXVALIDATIONKITR3BASE_LDFLAGS = $(filter-out -static, $(TEMPLATE_VBoxR3Static_LDFLAGS)) +endif +TEMPLATE_VBOXVALIDATIONKITR3BASE_DEFS = $(filter-out VBOX_WITH_DTRACE,$(TEMPLATE_VBoxR3Static_DEFS)) +TEMPLATE_VBOXVALIDATIONKITR3BASE_LIBS = $(TEMPLATE_VBoxR3Static_LIBS) +if1of ($(KBUILD_TARGET), linux) + if $(VBOX_GCC_VERSION_CXX) < 40800 + TEMPLATE_VBOXVALIDATIONKITR3BASE_LIBS += supc++ + TEMPLATE_VBOXVALIDATIONKITR3BASE_LDTOOL = $(subst GXX,GCC,$(TEMPLATE_VBoxR3Static_TOOL)) + endif +endif +TEMPLATE_VBOXVALIDATIONKITR3BASE_LDFLAGS.darwin = $(TEMPLATE_VBoxR3Static_LDFLAGS.darwin) -framework IOKit + + +# +# Template for building ring-3 programs for the Validation Kit. +# These programs can run on any host or guest. +# +TEMPLATE_VBoxValidationKitR3 = VBox Validation Kit ring-3 program, both guest and host. +TEMPLATE_VBoxValidationKitR3_EXTENDS = VBOXVALIDATIONKITR3BASE +TEMPLATE_VBoxValidationKitR3_EXTENDS_BY = appending +TEMPLATE_VBoxValidationKitR3_INST = $(INST_VALIDATIONKIT)$(KBUILD_TARGET)/$(KBUILD_TARGET_ARCH)/ +TEMPLATE_VBoxValidationKitR3_SDKS.win = ReorderCompilerIncs $(VBOX_WINPSDK) $(VBOX_WINDDK) VBOX_NTDLL +TEMPLATE_VBoxValidationKitR3_DEFS = IN_RT_R3 +TEMPLATE_VBoxValidationKitR3_LIBS.darwin = iconv +TEMPLATE_VBoxValidationKitR3_LIBS.freebsd = iconv +TEMPLATE_VBoxValidationKitR3_LIBS = \ + $(PATH_STAGE_LIB)/RuntimeR3$(VBOX_SUFF_LIB) +ifeq ($(KBUILD_TARGET),solaris) + TEMPLATE_VBoxValidationKitR3_LIBS += \ + kstat \ + nsl \ + contract + if1of ($(KBUILD_TARGET_ARCH), amd64 x86) + TEMPLATE_VBoxValidationKitR3_LIBS += \ + smbios + endif +endif +ifneq ($(KBUILD_TARGET),win) + TEMPLATE_VBoxValidationKitR3_LIBS += \ + $(SDK_VBOX_ZLIB_LIBS) +endif + +# Make VCC100 output work on NT3.x, NT4, W2K, XP and W2K3. +ifndef VBOX_WITH_NOCRT_STATIC +TEMPLATE_VBoxValidationKitR3_LIBS.win.x86 = \ + $(PATH_TOOL_$(TEMPLATE_VBoxValidationKitR3_TOOL.win.x86)_LIB)/oldnames.lib \ + $(PATH_TOOL_$(TEMPLATE_VBoxValidationKitR3_TOOL.win.x86)_LIB)/libcmt$(VBOX_VCC_CRT_TYPE).lib \ + $(PATH_TOOL_$(TEMPLATE_VBoxValidationKitR3_TOOL.win.x86)_LIB)/libcpmt$(VBOX_VCC_CRT_TYPE).lib \ + $(PATH_STAGE_LIB)/RuntimeR3VccTricks$(VBOX_SUFF_LIB) +TEMPLATE_VBoxValidationKitR3_LDFLAGS.win.x86 = \ + -Include:_vcc100_shell32_fakes_cpp \ + -Include:_vcc100_shell32_fakes_asm \ + -Section:.bss,RW!K +endif +TEMPLATE_VBoxValidationKitR3_LDFLAGS.win.x86 += -NoOptIData +TEMPLATE_VBoxValidationKitR3_POST_CMDS.win.x86 = $(if $(eq $(tool_do),LINK_LIBRARY),,$(VBOX_PE_SET_VERSION) $(out)$$(NLTAB))$(TEMPLATE_VBOXVALIDATIONKITR3BASE_POST_CMDS.win.x86)$$(NLTAB) +TEMPLATE_VBoxValidationKitR3_POST_CMDS.win.amd64 = $(if $(eq $(tool_do),LINK_LIBRARY),,$(VBOX_PE_SET_VERSION) $(out)$$(NLTAB))$(TEMPLATE_VBOXVALIDATIONKITR3BASE_POST_CMDS.win.amd64)$$(NLTAB) +TEMPLATE_VBoxValidationKitR3_LNK_DEPS.win.x86 = $(if $(eq $(tool_do),LINK_LIBRARY),,$(VBOX_PE_SET_VERSION)) $(TEMPLATE_VBOXVALIDATIONKITR3BASE_LNK_DEPS.win.x86) +TEMPLATE_VBoxValidationKitR3_LNK_DEPS.win.amd64 = $(if $(eq $(tool_do),LINK_LIBRARY),,$(VBOX_PE_SET_VERSION)) $(TEMPLATE_VBOXVALIDATIONKITR3BASE_LNK_DEPS.win.amd64) + +#TODO: TEMPLATE_VBoxValidationKitR3_EXTENDS = VBOXGUESTR3EXE + +TEMPLATE_VBoxValidationKitR3_USES.win += vboximportchecker +TEMPLATE_VBoxValidationKitR3_VBOX_IMPORT_CHECKER.win.x86 = nt31 +TEMPLATE_VBoxValidationKitR3_VBOX_IMPORT_CHECKER.win.amd64 = xp64 + + +# +# Template for ring-3 testcases to be included on the Validation Kit .ISO. +# +# Needed for running the ring-3 testcases on older guests (like NT4 / XP). +# Those testcases then run as part of the Validation Kit and are included on the Validation Kit .ISO. +# See @bugref:10195. +# +TEMPLATE_VBoxValidationKitR3TstExe = VBox Ring 3 Testcase Exe for Validation Kit .ISO +TEMPLATE_VBoxValidationKitR3TstExe_EXTENDS = VBoxValidationKitR3 +TEMPLATE_VBoxValidationKitR3TstExe_INST = $(INST_VALIDATIONKIT)/testcase/$(KBUILD_TARGET)/$(KBUILD_TARGET_ARCH)/testcase/ + + +# +# Template for building ring-3 programs for the Validation Kit. +# When these programs run on the host they may take advantage of the +# support driver if installed. +# +TEMPLATE_VBoxValidationKitR3SupDrv = VBox Validation Kit ring-3 program, mainly host. +TEMPLATE_VBoxValidationKitR3SupDrv_EXTENDS = VBoxValidationKitR3 +TEMPLATE_VBoxValidationKitR3SupDrv_EXTENDS_BY = appending +TEMPLATE_VBoxValidationKitR3SupDrv_DEFS = IN_SUP_R3 +TEMPLATE_VBoxValidationKitR3SupDrv_LIBS = \ + $(PATH_STAGE_LIB)/SUPR3Static$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/RuntimeR3$(VBOX_SUFF_LIB) +ifndef VBOX_WITH_NOCRT_STATIC +TEMPLATE_VBoxValidationKitR3SupDrv_LDFLAGS.win.x86 = \ + -Include:_vcc100_ntdll_fakes_cpp \ + -Include:_vcc100_ntdll_fakes_asm +endif + +# +# Template for building agnostic ring-0 host modules for the Validation Kit. +# +TEMPLATE_VBoxValidationKitR0 = VBox Validation Kit agnostic ring-0 host module. +TEMPLATE_VBoxValidationKitR0_EXTENDS = VBoxR0 +TEMPLATE_VBoxValidationKitR0_EXTENDS_BY = appending +TEMPLATE_VBoxValidationKitR0_INST = $(INST_VALIDATIONKIT)$(KBUILD_TARGET)/$(KBUILD_TARGET_ARCH)/ +TEMPLATE_VBoxValidationKitR0_DEFS = IN_RT_R0 +TEMPLATE_VBoxValidationKitR0_LIBS = \ + $(PATH_STAGE_LIB)/RuntimeR0$(VBOX_SUFF_LIB) \ + $(VBOX_LIB_SUPR0) + +# +# List of python sources that should be linted and unittested. +# +VBOX_VALIDATIONKIT_PYTHON_SOURCES := +VBOX_VALIDATIONKIT_PYLINT_TARGETS := +VBOX_VALIDATIONKIT_PYUNITTEST_EXCLUDE := + +ifdef VBOX_WITH_PYLINT + TESTING += +endif + +# +# Process python sources. +# +if1of ($(KBUILD_TARGET), win os2) + VBOX_PYTHONPATH_VALIDATIONKIT = $(PYTHONPATH);$(VBOX_PATH_VALIDATIONKIT_SRC);$(VBOX_PATH_VALIDATIONKIT_SRC)/testboxscript;$(VBOX_PATH_VALIDATIONKIT_SRC)/testmanager;$(VBOX_PATH_VALIDATIONKIT_SRC)/tests/additions;$(VBOX_PATH_VALIDATIONKIT_SRC)/../VMM/VMMAll +else + VBOX_PYTHONPATH_VALIDATIONKIT = $(PYTHONPATH):$(VBOX_PATH_VALIDATIONKIT_SRC):$(VBOX_PATH_VALIDATIONKIT_SRC)/testboxscript:$(VBOX_PATH_VALIDATIONKIT_SRC)/testmanager:$(VBOX_PATH_VALIDATIONKIT_SRC)/tests/additions:$(VBOX_PATH_VALIDATIONKIT_SRC)/../VMM/VMMAll +endif +BLDDIRS += $(PATH_TARGET)/pylint $(PATH_TARGET)/pyunittest + +define def_vbox_validationkit_py_check +$(eval name:=$(basename $(notdir $(py)))) + +pylint: $(name)-py-phony.o +$(name).o: $(name)-py-phony.o +$(PATH_TARGET)/pylint/$(name).o $(name)-py-phony.o:: $(py) | $(PATH_TARGET)/pylint/ +ifdef VBOX_WITH_PYLINT + $(QUIET2)$(call MSG_L1,Subjecting $(py) to pylint...) + $(QUIET)$(REDIRECT) -C "$(dir $(py))" -E LC_ALL=C -E PYTHONPATH="$(VBOX_PYTHONPATH_VALIDATIONKIT)" -- \ + $(VBOX_PYLINT) --rcfile=$(VBOX_PATH_VALIDATIONKIT_SRC)/pylintrc $$(VBOX_PYLINT_FLAGS) $$($(py)_VBOX_PYLINT_FLAGS) ./$(notdir $(py)) +endif + $(QUIET)$(APPEND) -t "$(PATH_TARGET)/pylint/$(name).o" + +ifn1of ($(py),$(VBOX_VALIDATIONKIT_PYUNITTEST_EXCLUDE)) +pyunittest: $(name)-pyunittest.o +$(PATH_TARGET)/pyunittest/$(name).o $(name)-pyunittest.o:: $(py) | $(PATH_TARGET)/pyunittest/ + $(QUIET2)$(call MSG_L1,Unittesting Python source $(py)...) + $(QUIET)$(REDIRECT) -E LC_ALL=C -E PYTHONPATH="$(VBOX_PYTHONPATH_VALIDATIONKIT)" -C $(dir $(py)) \ + -- $(VBOX_UNITTEST_PYTHON) -m unittest -v $(notdir $(basename $(py))) + $(QUIET)$(APPEND) -t "$(PATH_TARGET)/pyunittest/$(name).o" +VBOX_VALIDATIONKIT_PYUNITTEST_TARGETS += $(PATH_TARGET)/pyunittest/$(name).o + +TESTING += $(name)-pyunittest.o +endif +TESTING += $(name)-py-phony.o +VBOX_VALIDATIONKIT_PYLINT_TARGETS += $(PATH_TARGET)/pylint/$(name).o +endef # def_vbox_validationkit_py_check + + +define def_vbox_validationkit_process_python_sources +$(if-expr $(words $(_SUB_MAKEFILE_STACK)) <= 0 || "$1" == "FORCE", \ + $(foreach py, $(VBOX_VALIDATIONKIT_PYTHON_SOURCES), $(eval $(def_vbox_validationkit_py_check))),) +endef + + + +# +# http://www.jshint.com +# +VBOX_JSHINT ?= jshint +VBOX_JSHINT_FLAGS := --config $(VBOX_PATH_VALIDATIONKIT_SRC)/jshintrc.js --verbose +ifndef VBOX_WITH_JSHINT + VBOX_WITH_JSHINT := $(which $(VBOX_JSHINT)) +endif + +# +# List of javascript sources that should be checked and linted. +# +VBOX_VALIDATIONKIT_JS_SOURCES := + +define def_vbox_validationkit_js_check +$(eval name:=$(basename $(notdir $(js)))) +$(name).o $(name).obj: # $(PATH_SUB_CURRENT)/$(js) + -$(REDIRECT) -E LC_ALL=C -C $(dir $(js)) -- $$(VBOX_JSHINT) ./$(notdir $(js)) $$(VBOX_JSHINT_FLAGS) +jslint: $(name).o +endef + +ifdef VBOX_WITH_JSHINT +define def_vbox_validationkit_process_js_sources +$(if-expr $(words $(_SUB_MAKEFILE_STACK)) <= 0, \ + $(foreach js, $(VBOX_VALIDATIONKIT_JS_SOURCES), $(eval $(def_vbox_validationkit_js_check))),) +endef +endif + + +# +# List of IPRT testcases that will be included in the ValKit. +# +ifdef VBOX_WITH_VALIDATIONKIT_UNITTESTS_PACKING + VALKIT_UNITTESTS_WHITELIST_IPRT := \ + tstFile \ + tstFileLock \ + tstRTPathQueryInfo \ + tstRTPipe \ + tstRTProcCreateEx \ + tstRTProcCreatePrf \ + tstRTProcQueryUsername \ + tstThread-1 \ + tstUtf8 + + VALKIT_UNITTESTS_WHITELIST_IPRT.linux += \ + tstRTProcWait \ + tstRTProcIsRunningByName + + VALKIT_UNITTESTS_WHITELIST_IPRT.win += \ + tstRTProcWait + +endif # VBOX_WITH_VALIDATIONKIT_UNITTESTS_PACKING + diff --git a/src/VBox/ValidationKit/Makefile.kmk b/src/VBox/ValidationKit/Makefile.kmk new file mode 100644 index 00000000..42d4d791 --- /dev/null +++ b/src/VBox/ValidationKit/Makefile.kmk @@ -0,0 +1,400 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Make sure our Config.kmk is included. +ifndef VBOX_VALIDATIONKIT_CONFIG_KMK_INCLUDED + include $(PATH_SUB_CURRENT)/Config.kmk +endif + +# Collect install targets +VBOX_VALIDATIONKIT_INSTALLS := $(INSTALLS) + +# +# Include sub-makefiles. +# +include $(PATH_SUB_CURRENT)/tests/Makefile.kmk +ifneq ($(KBUILD_HOST),os2) # needs yasm + if1of ($(KBUILD_TARGET_ARCH), amd64 x86) # needs yasm + include $(PATH_SUB_CURRENT)/bootsectors/Makefile.kmk + endif +endif +include $(PATH_SUB_CURRENT)/utils/Makefile.kmk +include $(PATH_SUB_CURRENT)/common/Makefile.kmk +include $(PATH_SUB_CURRENT)/testboxscript/Makefile.kmk +include $(PATH_SUB_CURRENT)/testdriver/Makefile.kmk +include $(PATH_SUB_CURRENT)/testmanager/Makefile.kmk + +# +# Globals. +# + +# The current target is enabled by default. +VBOX_WITH_VALIDATIONKIT_PACKING.$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH) = 1 +ifdef VBOX_WITH_VALIDATIONKIT_UNITTESTS_PACKING + VBOX_WITH_VALIDATIONKIT_UNITTESTS_PACKING.$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH) = 1 +endif + +# +# Install the test driver framework. +# +INSTALLS += ValidationKit-testdriver +ValidationKit-testdriver_TEMPLATE = VBoxValidationKitR3 +ValidationKit-testdriver_INST = $(INST_VALIDATIONKIT)testdriver/ +ValidationKit-testdriver_MODE = a+r,u+w +ValidationKit-testdriver_SOURCES := \ + $(PATH_SUB_CURRENT)/testdriver/__init__.py \ + $(PATH_SUB_CURRENT)/testdriver/base.py \ + $(PATH_SUB_CURRENT)/testdriver/btresolver.py \ + $(PATH_SUB_CURRENT)/testdriver/reporter.py \ + $(PATH_SUB_CURRENT)/testdriver/testfileset.py \ + $(PATH_SUB_CURRENT)/testdriver/tst-txsclient.py \ + $(PATH_SUB_CURRENT)/testdriver/txsclient.py \ + $(PATH_SUB_CURRENT)/testdriver/vbox.py \ + $(PATH_SUB_CURRENT)/testdriver/vboxcon.py \ + $(PATH_SUB_CURRENT)/testdriver/vboxtestfileset.py \ + $(PATH_SUB_CURRENT)/testdriver/vboxtestvms.py \ + $(PATH_SUB_CURRENT)/testdriver/vboxwrappers.py \ + $(PATH_SUB_CURRENT)/testdriver/winbase.py \ + $(PATH_SUB_CURRENT)/testdriver/win-vbox-net-uninstall.ps1 +ValidationKit-testdriver_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/testdriver/vboxinstaller.py +$(PATH_SUB_CURRENT)/testdriver/txsclient.py_VBOX_PYCHECKER_FLAGS = --no-reimport + + +# +# Packing target. +# +ifndef VBOX_WITHOUT_VALIDATIONKIT_ZIP + + VBOX_VALIDATIONKIT_INSTALLS := $(filter-out $(VBOX_VALIDATIONKIT_INSTALLS), $(INSTALLS)) + + # + # VBoxValidationKit.zip. + # TODO: Don't pack the python stuff multiple times? Lazy works for now. + # + PACKING += $(PATH_OUT)/VBoxValidationKit.zip + $(PATH_OUT)/VBoxValidationKit.zip: \ + $(VBOX_PATH_VALIDATIONKIT)/VBoxValidationKit.iso \ + $(VBOX_PATH_VALIDATIONKIT)/ \ + $$(foreach inst, $$(VBOX_VALIDATIONKIT_INSTALLS), \ + $$(filter $(PATH_STAGE)/$(INST_VALIDATIONKIT)%, $$($$(inst)_2_STAGE_TARGETS))) + $(call MSG_L1,Packing the Test Suite $@) + $(QUIET)$(RM) -f $@ + # Note: Exclude packing the testcase directory into .zip, as that directory only needs to be + # included in VBoxValidationKit.iso. + $(foreach os, darwin freebsd linux os2 solaris win,$(foreach arch, x86 amd64 \ + ,$(if-expr defined(VBOX_WITH_VALIDATIONKIT_PACKING.$(os).$(arch))\ + ,$(NLTAB)$(QUIET)$(REDIRECT) -C $(PATH_OUT_BASE)/$(os).$(arch)/$(KBUILD_TYPE)/ \ + -- $(VBOX_ZIP) -r9 $@ $(INST_VALIDATIONKIT) -x '$(INST_VALIDATIONKIT)testcase/*' -x '*.pyc',))) + + # + # VBoxTestBoxScript.zip - For setting up the testboxes. + # + PACKING += $(PATH_OUT)/VBoxTestBoxScript.zip + $(PATH_OUT)/VBoxTestBoxScript.zip: \ + $$(testboxscript_2_STAGE_TARGETS) \ + $$(testboxscript-common_2_STAGE_TARGETS) \ + $$(TestBoxHelper_2_STAGE_TARGETS) + $(call MSG_L1,Packing the TestBox Script files to $@) + $(QUIET)$(RM) -f $@ + $(foreach os, darwin freebsd linux os2 solaris win,$(foreach arch, x86 amd64 \ + ,$(if-expr defined(VBOX_WITH_VALIDATIONKIT_PACKING.$(os).$(arch))\ + ,$(NLTAB)$(QUIET)$(REDIRECT) -C $(PATH_OUT_BASE)/$(os).$(arch)/$(KBUILD_TYPE)/ \ + -- $(VBOX_ZIP) -r9X $@ $(INST_TESTBOXSCRIPT) -x '*.pyc',))) + +endif # !VBOX_WITHOUT_VALIDATIONKIT_ZIP + + +# +# Automatically lint python code and python unit tests during build. +# +if defined(VBOX_WITH_PYLINT) && !defined(VBOX_WITHOUT_AUTO_PYLINT) + OTHERS += $(PATH_TARGET)/pylint.run + OTHER_CLEAN += $(PATH_TARGET)/pylint.run + $(PATH_TARGET)/pylint.run: $$(filter-out %/testboxscript.o, $$(VBOX_VALIDATIONKIT_PYLINT_TARGETS)) + $(QUIET)$(APPEND) -t "$@" +endif +if defined(VBOX_WITH_PYLINT) && !defined(VBOX_WITHOUT_AUTO_PYUNITTEST) # Tied to pylint for hysterical raisins. + OTHERS += $(PATH_TARGET)/pyunittest.run + OTHER_CLEAN += $(PATH_TARGET)/pyunittest.run + $(PATH_TARGET)/pyunittest.run: $$(VBOX_VALIDATIONKIT_PYUNITTEST_TARGETS) + $(QUIET)$(APPEND) -t "$@" +endif + +$(evalcall def_vbox_validationkit_process_python_sources,FORCE) +$(evalcall def_vbox_validationkit_process_js_sources,FORCE) +include $(FILE_KBUILD_SUB_FOOTER) + + +VBOX_VALIDATIONKIT_ISO_RSP = $(PATH_TARGET)/VBoxValidationKitISO.rsp +OTHERS_CLEAN += $(VBOX_VALIDATIONKIT_ISO_RSP) + +# +# Construct the file spec for creating the Validation Kit guest iso. +# +VBOX_VALIDATIONKIT_FILESPEC = \ + valkit.txt=$(VBOX_PATH_VALIDATIONKIT_SRC)/docs/valkit.txt \ + $(VBOX_PATH_VALIDATIONKIT)/vboxtxs-readme.txt +ifneq ($(KBUILD_HOST),os2) + if1of ($(KBUILD_TARGET_ARCH), amd64 x86) + VBOX_VALIDATIONKIT_FILESPEC += \ + $(VBOX_PATH_VALIDATIONKIT)/bootsectors/bootsector-pae.img \ + $(VBOX_PATH_VALIDATIONKIT)/bootsectors/bootsector-shutdown.img + endif +endif + +VBOX_VALIDATIONKIT_FILESPEC_UNITTESTS = + +# Generate VBOX_PATH_VALIDATIONKIT.os.arch variables. +$(foreach os, darwin freebsd linux os2 solaris win,$(foreach arch, x86 amd64, \ + $(eval VBOX_PATH_VALIDATIONKIT.$(os).$(arch) = $(PATH_OUT_BASE)/$(os).$(arch)/$(KBUILD_TYPE)/$(INST_VALIDATIONKIT)$(os)/$(arch)) \ + $(eval VBOX_PATH_VALIDATIONKIT_UNITTESTS.$(os).$(arch) = $(abspath $(VBOX_PATH_VALIDATIONKIT.$(os).$(arch))/../../testcase/$(os)/$(arch)/testcase)) \ +)) + +# Common files first. +define def_vbox_validationkit_common_files + ifdef VBOX_WITH_VALIDATIONKIT_PACKING.$(os).$(arch) + VBOX_VALIDATIONKIT_FILESPEC += \ + $$(VBOX_PATH_VALIDATIONKIT.$(os).$(arch))/exceptionsR3$(TMP_SUFF_EXE) \ + $$(VBOX_PATH_VALIDATIONKIT.$(os).$(arch))/cpu-alloc-all-mem$(TMP_SUFF_EXE) \ + $$(VBOX_PATH_VALIDATIONKIT.$(os).$(arch))/cpu-numa$(TMP_SUFF_EXE) \ + $$(VBOX_PATH_VALIDATIONKIT.$(os).$(arch))/FsPerf$(TMP_SUFF_EXE) \ + $$(VBOX_PATH_VALIDATIONKIT.$(os).$(arch))/IoPerf$(TMP_SUFF_EXE) \ + $$(VBOX_PATH_VALIDATIONKIT.$(os).$(arch))/NetPerf$(TMP_SUFF_EXE) \ + $$(VBOX_PATH_VALIDATIONKIT.$(os).$(arch))/LoadGenerator$(TMP_SUFF_EXE) \ + $$(VBOX_PATH_VALIDATIONKIT.$(os).$(arch))/SerialTest$(TMP_SUFF_EXE) \ + $$(VBOX_PATH_VALIDATIONKIT.$(os).$(arch))/TestExecService$(TMP_SUFF_EXE) \ + $$(VBOX_PATH_VALIDATIONKIT.$(os).$(arch))/vts_rm$(TMP_SUFF_EXE) \ + $$(VBOX_PATH_VALIDATIONKIT.$(os).$(arch))/vts_shutdown$(TMP_SUFF_EXE) \ + $$(VBOX_PATH_VALIDATIONKIT.$(os).$(arch))/vts_tar$(TMP_SUFF_EXE) \ + $$(VBOX_PATH_VALIDATIONKIT.$(os).$(arch))/xmmsaving$(TMP_SUFF_EXE) + if1of ($(arch), amd64) ## HACK ALERT! This mirrors VBOX_WITH_R0_MODULES logic in /Config.kmk. + VBOX_VALIDATIONKIT_FILESPEC += \ + $$(VBOX_PATH_VALIDATIONKIT.$(os).$(arch))/loadgeneratorR0.r0 + endif + ifn1of ($(os), os2) ## not compiling bootsectors, no yasm. could fix this better. + VBOX_VALIDATIONKIT_FILESPEC += \ + $$(VBOX_PATH_VALIDATIONKIT.$(os).$(arch))/bs3-timing-1$(TMP_SUFF_EXE) + endif + ifn1of ($(os), os2 freebsd netbsd openbsd) ## must match utils/audio/Makefile.kmk + VBOX_VALIDATIONKIT_FILESPEC += \ + $$(VBOX_PATH_VALIDATIONKIT.$(os).$(arch))/vkat$(TMP_SUFF_EXE) + endif + endif +endef + +TMP_SUFF_EXE=.exe +$(foreach os, os2 win,$(foreach arch, x86 amd64, \ + $(eval $(def_vbox_validationkit_common_files)) \ +)) +TMP_SUFF_EXE= +$(foreach os, darwin freebsd linux solaris,$(foreach arch, x86 amd64, \ + $(eval $(def_vbox_validationkit_common_files)) \ +)) + +# OS specific files - Linux +ifdef VBOX_WITH_VALIDATIONKIT_PACKING.linux.x86 + VBOX_VALIDATIONKIT_FILESPEC += \ + $(VBOX_PATH_VALIDATIONKIT.linux.x86)/UsbTest +endif +ifdef VBOX_WITH_VALIDATIONKIT_PACKING.linux.amd64 + VBOX_VALIDATIONKIT_FILESPEC += \ + $(VBOX_PATH_VALIDATIONKIT.linux.amd64)/UsbTest +endif +ifdef VBOX_WITH_VALIDATIONKIT_PACKING.linux.x86 + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.linux.x86)/../vboxtxs) + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.linux.x86)/../vboxtxs-nat) + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.linux.x86)/../vboxtxs.service) + +else ifdef VBOX_WITH_VALIDATIONKIT_PACKING.linux.amd64 + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.linux.amd64)/../vboxtxs) + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.linux.amd64)/../vboxtxs-nat) + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.linux.amd64)/../vboxtxs.service) +endif + +# OS specific files - OS/2 +ifdef VBOX_WITH_VALIDATIONKIT_PACKING.os2.x86 + VBOX_VALIDATIONKIT_FILESPEC += \ + $(VBOX_PATH_VALIDATIONKIT.os2.x86)/libc06.dll \ + $(VBOX_PATH_VALIDATIONKIT.os2.x86)/libc061.dll \ + $(VBOX_PATH_VALIDATIONKIT.os2.x86)/libc062.dll \ + $(VBOX_PATH_VALIDATIONKIT.os2.x86)/libc063.dll \ + $(VBOX_PATH_VALIDATIONKIT.os2.x86)/libc064.dll \ + $(VBOX_PATH_VALIDATIONKIT.os2.x86)/libc065.dll \ + $(VBOX_PATH_VALIDATIONKIT.os2.x86)/libc066.dll +endif + +# OS specific files - Solaris +ifdef VBOX_WITH_VALIDATIONKIT_PACKING.solaris.x86 + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.solaris.x86)/../vboxtxs.sh) + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.solaris.x86)/../vboxtxs.xml) + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.solaris.x86)/../vboxtxs-sol10.xml) +else ifdef VBOX_WITH_VALIDATIONKIT_PACKING.solaris.amd64 + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.solaris.amd64)/../vboxtxs.sh) + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.solaris.amd64)/../vboxtxs.xml) + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.solaris.amd64)/../vboxtxs-sol10.xml) +endif + +# OS specific files - Windows +ifdef VBOX_WITH_VALIDATIONKIT_PACKING.win.x86 + VBOX_VALIDATIONKIT_FILESPEC += \ + $(VBOX_PATH_VALIDATIONKIT.win.x86)/ntSetFreq.exe \ + $(VBOX_PATH_VALIDATIONKIT.win.x86)/ntTimeSources.exe + # Disabled for now; does not work without WinMM.dll export verification files. + #ifdef VBOX_WITH_AUDIO_VALIDATIONKIT + # VBOX_VALIDATIONKIT_FILESPEC += \ + # $(VBOX_PATH_VALIDATIONKIT.win.x86)/ntPlayToneWaveX.exe + #endif +endif +ifdef VBOX_WITH_VALIDATIONKIT_PACKING.win.amd64 + VBOX_VALIDATIONKIT_FILESPEC += \ + $(VBOX_PATH_VALIDATIONKIT.win.amd64)/ntSetFreq.exe \ + $(VBOX_PATH_VALIDATIONKIT.win.amd64)/ntTimeSources.exe + # Disabled for now; does not work without WinMM.dll export verification files. + #ifdef VBOX_WITH_AUDIO_VALIDATIONKIT + # VBOX_VALIDATIONKIT_FILESPEC += \ + # $(VBOX_PATH_VALIDATIONKIT.win.amd64)/ntPlayToneWaveX.exe + #endif +endif +ifdef VBOX_WITH_VALIDATIONKIT_PACKING.win.x86 + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.win.x86)/../vboxtxs.cmd) + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.win.x86)/../vboxtxs.reg) + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.win.x86)/../vboxtxs-nat.cmd) + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.win.x86)/../vboxtxs-nat.reg) +else ifdef VBOX_WITH_VALIDATIONKIT_PACKING.win.amd64 + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.win.amd64)/../vboxtxs.cmd) + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.win.amd64)/../vboxtxs.reg) + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.win.amd64)/../vboxtxs-nat.cmd) + VBOX_VALIDATIONKIT_FILESPEC += $(abspath $(VBOX_PATH_VALIDATIONKIT.win.amd64)/../vboxtxs-nat.reg) +endif + +# +# If enabled, this includes specially built unit tests (statically linked, for guests) located +# in the $(VBOX_PATH_VALIDATIONKIT)/../../testcase/ directory. +# +# This is useful if we want to run those on platforms where we don't have / support +# any host support anymore (like Windows XP, for instance). +# +# Note that executing the tests require an additional component (tdUnitTest test driver) +# which runs as part of the Validation Kit. +# +# See @bugref{10195} +# +ifdef VBOX_WITH_VALIDATIONKIT_UNITTESTS_PACKING + define def_vbox_validationkit_unittests + # IPRT unit tests. + VBOX_VALIDATIONKIT_FILESPEC_UNITTESTS += \ + $(foreach whitelisted, $(VALKIT_UNITTESTS_WHITELIST_IPRT) $(VALKIT_UNITTESTS_WHITELIST_IPRT.$(os)), \ + $$(wildcard $(VBOX_PATH_VALIDATIONKIT_UNITTESTS.$(os).$(arch))/$(whitelisted)$(TMP_SUFF_EXE))) + # Unit tests which utilize parts of the Guest Additions. + VBOX_VALIDATIONKIT_FILESPEC_UNITTESTS += \ + $(foreach whitelisted, $(VALKIT_UNITTESTS_WHITELIST_GUEST_ADDITIONS) $(VALKIT_UNITTESTS_WHITELIST_GUEST_ADDITIONS.$(os)), \ + $$(wildcard $(VBOX_PATH_VALIDATIONKIT_UNITTESTS.$(os).$(arch))/$(whitelisted)$(TMP_SUFF_EXE))) + endef + + TMP_SUFF_EXE=.exe + $(foreach os, win,$(foreach arch, x86 amd64, \ + $(eval $(def_vbox_validationkit_unittests)) \ + )) + TMP_SUFF_EXE= + $(foreach os, darwin freebsd linux solaris,$(foreach arch, x86 amd64, \ + $(eval $(def_vbox_validationkit_unittests)) \ + )) + +endif # VBOX_WITH_VALIDATIONKIT_UNITTESTS_PACKING + +# +# Build the Validation Kit guest ISO response file for RTIsoMaker. +# +# We need this response file because passing all those arguments to RTIsoMaker +# will blow up the maximum command line length on some OSes. +# +$(VBOX_VALIDATIONKIT_ISO_RSP): | $$(dir $$@) + $(call MSG_L1,Creating Validation Kit guest ISO response file $@ ) + kmk_builtin_append -nt "$@" \ + '--iso-level=3' \ + '--rock-ridge' \ + '--joliet' \ + '--rational-attribs' \ + '--random-order-verification=2048' \ + $(foreach spec,$(VBOX_VALIDATIONKIT_FILESPEC) \ + ,$(if $(findstring =,$(spec)), $(spec), /$(lastword $(subst /$(INST_VALIDATIONKIT), ,$(spec))=$(spec))) ) \ + $(foreach spec,$(VBOX_VALIDATIONKIT_FILESPEC_UNITTESTS) \ + ,$(if $(findstring =,$(spec)), $(spec), /$(lastword $(subst /$(INST_VALIDATIONKIT), ,$(spec))=$(spec))) ) \ + $(foreach spec,$(filter-out %.txt %.dll %.xml %.reg %.img, $(VBOX_VALIDATIONKIT_FILESPEC)) \ + ,--chmod=a+x:/$(lastword $(if $(findstring =,$(spec)), \ + $(subst =, $(SP), $(spec)), \ + $(subst /$(INST_VALIDATIONKIT), ,$(spec))))) \ + $(foreach spec,$(filter-out %.txt %.dll %.xml %.reg %.img, $(VBOX_VALIDATIONKIT_FILESPEC_UNITTESTS)) \ + ,--chmod=a+x:/$(lastword $(if $(findstring =,$(spec)), \ + $(subst =, $(SP), $(spec)), \ + $(subst $(INST_VALIDATIONKIT), ,$(spec))))) \ + '--volume-id="VBOXVALK_$(VBOX_SVN_REV)_$(VBOX_VERSION_STRING_RAW)"' \ + '--name-setup=joliet' \ + '--volume-id="VBoxValK $(VBOX_SVN_REV)"' + +# +# Build the Validation Kit guest ISO. +# +$(VBOX_PATH_VALIDATIONKIT)/VBoxValidationKit.iso: \ + $(filter-out %=deleteme=, $(subst =,=deleteme= , $(VBOX_VALIDATIONKIT_FILESPEC))) \ + $(VBOX_SVN_REV_KMK) \ + $(VBOX_PATH_VALIDATIONKIT_SRC)/Makefile.kmk \ + $(VBOX_VALIDATIONKIT_ISO_RSP) \ + | $(if-expr defined(VBOX_USE_RTISOMAKER),$(VBOX_RTISOMAKER),) + $(call MSG_TOOL,RTIsoMaker,,$@) + $(QUIET)$(MKDIR) -p $(@D) +ifneq ($(KBUILD_TARGET),os2) + $(QUIET)$(ECHO) VBOX_VALIDATIONKIT_FILESPEC $(VBOX_VALIDATIONKIT_FILESPEC) +endif +ifdef VBOX_WITH_VALIDATIONKIT_UNITTESTS_PACKING + $(foreach os, darwin linux solaris win,$(foreach arch, x86 amd64, \ + $(call MSG_L1, VBOX_PATH_VALIDATIONKIT_UNITTESTS.$(os).$(arch): $(VBOX_PATH_VALIDATIONKIT_UNITTESTS.$(os).$(arch))) \ + )) + $(call MSG_L1, VBOX_VALIDATIONKIT_FILESPEC_UNITTESTS $(VBOX_VALIDATIONKIT_FILESPEC_UNITTESTS)) +endif + $(VBOX_RTISOMAKER) \ + @$(VBOX_VALIDATIONKIT_ISO_RSP) \ + --output $@ + + + +# Alias for creating the iso. +.PHONY: validationkit-iso +validationkit-iso: $(VBOX_PATH_VALIDATIONKIT)/VBoxValidationKit.iso diff --git a/src/VBox/ValidationKit/ValidationKitCodingGuidelines.cpp b/src/VBox/ValidationKit/ValidationKitCodingGuidelines.cpp new file mode 100644 index 00000000..7b35708f --- /dev/null +++ b/src/VBox/ValidationKit/ValidationKitCodingGuidelines.cpp @@ -0,0 +1,87 @@ +/* $Id: ValidationKitCodingGuidelines.cpp $ */ +/** @file + * VirtualBox Validation Kit - Coding Guidelines. + */ + +/* + * Copyright (C) 2010-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/** @page pg_validationkit_guideline Validation Kit Coding Guidelines + * + * The guidelines extends the VBox coding guidelines (@ref pg_vbox_guideline) + * and currently only defines python prefixes and linting. + * + * + * @section sec_validationkit_guideline_python Python + * + * Python is a typeless language so using prefixes to indicate the intended + * type of a variable or attribute can be very helpful. + * + * Type prefixes: + * - 'b' for byte (octect). + * - 'ch' for a single character. + * - 'f' for boolean and flags. + * - 'fn' for function or method references. + * - 'fp' for floating point values. + * - 'i' for integers. + * - 'l' for long integers. + * - 'o' for objects, structures and anything with attributes that doesn't + * match any of the other type prefixes. + * - 'r' for a range or xrange. + * - 's' for a string (can be unicode). + * - 'su' for a unicode string when the distinction is important. + * + * Collection qualifiers: + * - 'a' for a list or an array. + * - 'd' for a dictionary. + * - 'h' for a set (hashed). + * - 't' for a tuple. + * + * Other qualifiers: + * - 'c' for a count. Implies integer or long integer type. Higest + * priority. + * - 'sec' for a second value. Implies long integer type. + * - 'ms' for a millisecond value. Implies long integer type. + * - 'us' for a microsecond value. Implies long integer type. + * - 'ns' for a nanosecond value. Implies long integer type. + * + * The 'ms', 'us', 'ns' and 'se' qualifiers can be capitalized when prefixed by + * 'c', e.g. cMsElapsed. While this technically means they are no longer a + * prefix, it's easier to read and everyone understands what it means. + * + * The type collection qualifiers comes first, then the other qualifiers and + * finally the type qualifier. + * + * Python statements are terminated by semicolons (';') as a convention. + * + */ + diff --git a/src/VBox/ValidationKit/analysis/Makefile.kmk b/src/VBox/ValidationKit/analysis/Makefile.kmk new file mode 100644 index 00000000..b277d965 --- /dev/null +++ b/src/VBox/ValidationKit/analysis/Makefile.kmk @@ -0,0 +1,45 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Python Test Driver. +# + +# +# Copyright (C) 2010-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(wildcard $(PATH_SUB_CURRENT)/*.py) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/analysis/__init__.py b/src/VBox/ValidationKit/analysis/__init__.py new file mode 100644 index 00000000..6fb1017f --- /dev/null +++ b/src/VBox/ValidationKit/analysis/__init__.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# $Id: __init__.py $ + +""" +Test analysis package +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154259 $" + diff --git a/src/VBox/ValidationKit/analysis/analyze.py b/src/VBox/ValidationKit/analysis/analyze.py new file mode 100755 index 00000000..3b92597a --- /dev/null +++ b/src/VBox/ValidationKit/analysis/analyze.py @@ -0,0 +1,447 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: analyze.py $ + +""" +Analyzer CLI. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154267 $" + +# Standard python imports. +import re; +import os; +import textwrap; +import sys; + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from analysis import reader +from analysis import reporting + + +def usage(): + """ + Display usage. + """ + # Set up the output wrapper. + try: cCols = os.get_terminal_size()[0] # since 3.3 + except: cCols = 79; + oWrapper = textwrap.TextWrapper(width = cCols); + + # Do the outputting. + print('Tool for comparing test results.'); + print(''); + oWrapper.subsequent_indent = ' ' * (len('usage: ') + 4); + print(oWrapper.fill('usage: analyze.py [options] [collection-1] -- [collection-2] [-- [collection3] [..]])')) + oWrapper.subsequent_indent = ''; + print(''); + print(oWrapper.fill('This tool compares two or more result collections, using one as a baseline (first by default) ' + 'and showing how the results in others differs from it.')); + print(''); + print(oWrapper.fill('The results (XML file) from one or more test runs makes up a collection. A collection can be ' + 'named using the --name <name> option, or will get a sequential name automatically. The baseline ' + 'collection will have "(baseline)" appended to its name.')); + print(''); + print(oWrapper.fill('A test run produces one XML file, either via the testdriver/reporter.py machinery or via the IPRT ' + 'test.cpp code. In the latter case it can be enabled and controlled via IPRT_TEST_FILE. A collection ' + 'consists of one or more of test runs (i.e. XML result files). These are combined (aka distilled) ' + 'into a single set of results before comparing them with the others. The --best and --avg options ' + 'controls how this combining is done. The need for this is mainly to try counteract some of the ' + 'instability typically found in the restuls. Just because one test run produces a better result ' + 'after a change does not necessarily mean this will always be the case and that the change was to ' + 'the better, it might just have been regular fluctuations in the test results.')); + + oWrapper.initial_indent = ' '; + oWrapper.subsequent_indent = ' '; + print(''); + print('Options governing combining (distillation):'); + print(' --avg, --average'); + print(oWrapper.fill('Picks the best result by calculating the average values across all the runs.')); + print(''); + print(' --best'); + print(oWrapper.fill('Picks the best result from all the runs. For values, this means making guessing what result is ' + 'better based on the unit. This may not always lead to the right choices.')); + print(oWrapper.initial_indent + 'Default: --best'); + + print(''); + print('Options relating to collections:'); + print(' --name <name>'); + print(oWrapper.fill('Sets the name of the current collection. By default a collection gets a sequential number.')); + print(''); + print(' --baseline <num>'); + print(oWrapper.fill('Sets collection given by <num> (0-based) as the baseline collection.')); + print(oWrapper.initial_indent + 'Default: --baseline 0') + + print(''); + print('Filtering options:'); + print(' --filter-test <substring>'); + print(oWrapper.fill('Exclude tests not containing any of the substrings given via the --filter-test option. The ' + 'matching is done with full test name, i.e. all parent names are prepended with ", " as separator ' + '(for example "tstIOInstr, CPUID EAX=1").')); + print(''); + print(' --filter-test-out <substring>'); + print(oWrapper.fill('Exclude tests containing the given substring. As with --filter-test, the matching is done against ' + 'the full test name.')); + print(''); + print(' --filter-value <substring>'); + print(oWrapper.fill('Exclude values not containing any of the substrings given via the --filter-value option. The ' + 'matching is done against the value name prefixed by the full test name and ": " ' + '(for example "tstIOInstr, CPUID EAX=1: real mode, CPUID").')); + print(''); + print(' --filter-value-out <substring>'); + print(oWrapper.fill('Exclude value containing the given substring. As with --filter-value, the matching is done against ' + 'the value name prefixed by the full test name.')); + + print(''); + print(' --regex-test <expr>'); + print(oWrapper.fill('Same as --filter-test except the substring matching is done via a regular expression.')); + print(''); + print(' --regex-test-out <expr>'); + print(oWrapper.fill('Same as --filter-test-out except the substring matching is done via a regular expression.')); + print(''); + print(' --regex-value <expr>'); + print(oWrapper.fill('Same as --filter-value except the substring matching is done via a regular expression.')); + print(''); + print(' --regex-value-out <expr>'); + print(oWrapper.fill('Same as --filter-value-out except the substring matching is done via a regular expression.')); + print(''); + print(' --filter-out-empty-leaf-tests'); + print(oWrapper.fill('Removes any leaf tests that are without any values or sub-tests. This is useful when ' + 'only considering values, especially when doing additional value filtering.')); + + print(''); + print('Analysis options:'); + print(' --pct-same-value <float>'); + print(oWrapper.fill('The threshold at which the percent difference between two values are considered the same ' + 'during analysis.')); + print(oWrapper.initial_indent + 'Default: --pct-same-value 0.10'); + + print(''); + print('Output options:'); + print(' --brief, --verbose'); + print(oWrapper.fill('Whether to omit (--brief) the value for non-baseline runs and just get along with the difference.')); + print(oWrapper.initial_indent + 'Default: --brief'); + print(''); + print(' --pct <num>, --pct-precision <num>'); + print(oWrapper.fill('Specifies the number of decimal place to use when formatting the difference as percent.')); + print(oWrapper.initial_indent + 'Default: --pct 2'); + return 1; + + +class ResultCollection(object): + """ + One or more test runs that should be merged before comparison. + """ + + def __init__(self, sName): + self.sName = sName; + self.aoTestTrees = [] # type: [Test] + self.asTestFiles = [] # type: [str] - runs parallel to aoTestTrees + self.oDistilled = None # type: Test + + def append(self, sFilename): + """ + Loads sFilename and appends the result. + Returns True on success, False on failure. + """ + oTestTree = reader.parseTestResult(sFilename); + if oTestTree: + self.aoTestTrees.append(oTestTree); + self.asTestFiles.append(sFilename); + return True; + return False; + + def isEmpty(self): + """ Checks if the result is empty. """ + return len(self.aoTestTrees) == 0; + + def filterTests(self, asFilters): + """ + Keeps all the tests in the test trees sub-string matching asFilters (str or re). + """ + for oTestTree in self.aoTestTrees: + oTestTree.filterTests(asFilters); + return self; + + def filterOutTests(self, asFilters): + """ + Removes all the tests in the test trees sub-string matching asFilters (str or re). + """ + for oTestTree in self.aoTestTrees: + oTestTree.filterOutTests(asFilters); + return self; + + def filterValues(self, asFilters): + """ + Keeps all the tests in the test trees sub-string matching asFilters (str or re). + """ + for oTestTree in self.aoTestTrees: + oTestTree.filterValues(asFilters); + return self; + + def filterOutValues(self, asFilters): + """ + Removes all the tests in the test trees sub-string matching asFilters (str or re). + """ + for oTestTree in self.aoTestTrees: + oTestTree.filterOutValues(asFilters); + return self; + + def filterOutEmptyLeafTests(self): + """ + Removes all the tests in the test trees that have neither child tests nor values. + """ + for oTestTree in self.aoTestTrees: + oTestTree.filterOutEmptyLeafTests(); + return self; + + def distill(self, sMethod, fDropLoners = False): + """ + Distills the set of test results into a single one by the given method. + + Valid sMethod values: + - 'best': Pick the best result for each test and value among all the test runs. + - 'avg': Calculate the average value among all the test runs. + + When fDropLoners is True, tests and values that only appear in a single test run + will be discarded. When False (the default), the lone result will be used. + """ + assert sMethod in ['best', 'avg']; + assert not self.oDistilled; + + # If empty, nothing to do. + if self.isEmpty(): + return None; + + # If there is only a single tree, make a deep copy of it. + if len(self.aoTestTrees) == 1: + oDistilled = self.aoTestTrees[0].clone(); + else: + + # Since we don't know if the test runs are all from the same test, we create + # dummy root tests for each run and use these are the start for the distillation. + aoDummyInputTests = []; + for oRun in self.aoTestTrees: + oDummy = reader.Test(); + oDummy.aoChildren = [oRun,]; + aoDummyInputTests.append(oDummy); + + # Similarly, we end up with a "dummy" root test for the result. + oDistilled = reader.Test(); + oDistilled.distill(aoDummyInputTests, sMethod, fDropLoners); + + # We can drop this if there is only a single child, i.e. if all runs are for + # the same test. + if len(oDistilled.aoChildren) == 1: + oDistilled = oDistilled.aoChildren[0]; + + self.oDistilled = oDistilled; + return oDistilled; + + + +# matchWithValue hacks. +g_asOptions = []; +g_iOptInd = 1; +g_sOptArg = ''; + +def matchWithValue(sOption): + """ Matches an option with a value, placing the value in g_sOptArg if it matches. """ + global g_asOptions, g_iOptInd, g_sOptArg; + sArg = g_asOptions[g_iOptInd]; + if sArg.startswith(sOption): + if len(sArg) == len(sOption): + if g_iOptInd + 1 < len(g_asOptions): + g_iOptInd += 1; + g_sOptArg = g_asOptions[g_iOptInd]; + return True; + + print('syntax error: Option %s takes a value!' % (sOption,)); + raise Exception('syntax error: Option %s takes a value!' % (sOption,)); + + if sArg[len(sOption)] in ('=', ':'): + g_sOptArg = sArg[len(sOption) + 1:]; + return True; + return False; + + +def main(asArgs): + """ C style main(). """ + # + # Parse arguments + # + oCurCollection = ResultCollection('#0'); + aoCollections = [ oCurCollection, ]; + iBaseline = 0; + sDistillationMethod = 'best'; + fBrief = True; + cPctPrecision = 2; + rdPctSameValue = 0.1; + asTestFilters = []; + asTestOutFilters = []; + asValueFilters = []; + asValueOutFilters = []; + fFilterOutEmptyLeafTest = True; + + global g_asOptions, g_iOptInd, g_sOptArg; + g_asOptions = asArgs; + g_iOptInd = 1; + while g_iOptInd < len(asArgs): + sArg = asArgs[g_iOptInd]; + g_sOptArg = ''; + #print("dbg: g_iOptInd=%s '%s'" % (g_iOptInd, sArg,)); + + if sArg.startswith('--help'): + return usage(); + + if matchWithValue('--filter-test'): + asTestFilters.append(g_sOptArg); + elif matchWithValue('--filter-test-out'): + asTestOutFilters.append(g_sOptArg); + elif matchWithValue('--filter-value'): + asValueFilters.append(g_sOptArg); + elif matchWithValue('--filter-value-out'): + asValueOutFilters.append(g_sOptArg); + + elif matchWithValue('--regex-test'): + asTestFilters.append(re.compile(g_sOptArg)); + elif matchWithValue('--regex-test-out'): + asTestOutFilters.append(re.compile(g_sOptArg)); + elif matchWithValue('--regex-value'): + asValueFilters.append(re.compile(g_sOptArg)); + elif matchWithValue('--regex-value-out'): + asValueOutFilters.append(re.compile(g_sOptArg)); + + elif sArg == '--filter-out-empty-leaf-tests': + fFilterOutEmptyLeafTest = True; + elif sArg == '--no-filter-out-empty-leaf-tests': + fFilterOutEmptyLeafTest = False; + + elif sArg == '--best': + sDistillationMethod = 'best'; + elif sArg in ('--avg', '--average'): + sDistillationMethod = 'avg'; + + elif sArg == '--brief': + fBrief = True; + elif sArg == '--verbose': + fBrief = False; + + elif matchWithValue('--pct') or matchWithValue('--pct-precision'): + cPctPrecision = int(g_sOptArg); + elif matchWithValue('--base') or matchWithValue('--baseline'): + iBaseline = int(g_sOptArg); + + elif matchWithValue('--pct-same-value'): + rdPctSameValue = float(g_sOptArg); + + # '--' starts a new collection. If current one is empty, drop it. + elif sArg == '--': + print("dbg: new collection"); + #if oCurCollection.isEmpty(): + # del aoCollections[-1]; + oCurCollection = ResultCollection("#%s" % (len(aoCollections),)); + aoCollections.append(oCurCollection); + + # Name the current result collection. + elif matchWithValue('--name'): + oCurCollection.sName = g_sOptArg; + + # Read in a file and add it to the current data set. + else: + if not oCurCollection.append(sArg): + return 1; + g_iOptInd += 1; + + # + # Post argument parsing processing. + # + + # Drop the last collection if empty. + if oCurCollection.isEmpty(): + del aoCollections[-1]; + if not aoCollections: + print("error: No input files given!"); + return 1; + + # Check the baseline value and mark the column as such. + if iBaseline < 0 or iBaseline > len(aoCollections): + print("error: specified baseline is out of range: %s, valid range 0 <= baseline < %s" + % (iBaseline, len(aoCollections),)); + return 1; + aoCollections[iBaseline].sName += ' (baseline)'; + + # + # Apply filtering before distilling each collection into a single result tree. + # + if asTestFilters: + for oCollection in aoCollections: + oCollection.filterTests(asTestFilters); + if asTestOutFilters: + for oCollection in aoCollections: + oCollection.filterOutTests(asTestOutFilters); + + if asValueFilters: + for oCollection in aoCollections: + oCollection.filterValues(asValueFilters); + if asValueOutFilters: + for oCollection in aoCollections: + oCollection.filterOutValues(asValueOutFilters); + + if fFilterOutEmptyLeafTest: + for oCollection in aoCollections: + oCollection.filterOutEmptyLeafTests(); + + # Distillation. + for oCollection in aoCollections: + oCollection.distill(sDistillationMethod); + + # + # Produce the report. + # + oTable = reporting.RunTable(iBaseline, fBrief, cPctPrecision, rdPctSameValue); + oTable.populateFromRuns([oCollection.oDistilled for oCollection in aoCollections], + [oCollection.sName for oCollection in aoCollections]); + print('\n'.join(oTable.formatAsText())); + return 0; + +if __name__ == '__main__': + sys.exit(main(sys.argv)); + diff --git a/src/VBox/ValidationKit/analysis/reader.py b/src/VBox/ValidationKit/analysis/reader.py new file mode 100755 index 00000000..3190d2f6 --- /dev/null +++ b/src/VBox/ValidationKit/analysis/reader.py @@ -0,0 +1,762 @@ +# -*- coding: utf-8 -*- +# $Id: reader.py $ + +""" +XML reader module. + +This produces a test result tree that can be processed and passed to +reporting. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154267 $" +__all__ = [ 'parseTestResult', ] + +# Standard python imports. +import datetime; +import os; +import re; +import sys; +import traceback; + +# Only the main script needs to modify the path. +try: __file__; +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))); +sys.path.append(g_ksValidationKitDir); + +# ValidationKit imports. +from common import utils; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + +# pylint: disable=missing-docstring + + +class Value(object): + """ + Represents a value. Usually this is benchmark result or parameter. + """ + + kdBestByUnit = { + "%": +1, # Difficult to say what's best really. + "bytes": +1, # Difficult to say what's best really. + "bytes/s": +2, + "KB": +1, + "KB/s": +2, + "MB": +1, + "MB/s": +2, + "packets": +2, + "packets/s": +2, + "frames": +2, + "frames/s": +2, + "occurrences": +1, # Difficult to say what's best really. + "occurrences/s": +2, + "roundtrips": +2, + "calls": +1, # Difficult to say what's best really. + "calls/s": +2, + "s": -2, + "ms": -2, + "ns": -2, + "ns/call": -2, + "ns/frame": -2, + "ns/occurrence": -2, + "ns/packet": -2, + "ns/roundtrip": -2, + "ins": +2, + "ins/sec": +2, + "": +1, # Difficult to say what's best really. + "pp1k": -2, + "pp10k": -2, + "ppm": -2, + "ppb": -2, + "ticks": -1, # Difficult to say what's best really. + "ticks/call": -2, + "ticks/occ": -2, + "pages": +1, # Difficult to say what's best really. + "pages/s": +2, + "ticks/page": -2, + "ns/page": -2, + "ps": -1, # Difficult to say what's best really. + "ps/call": -2, + "ps/frame": -2, + "ps/occurrence": -2, + "ps/packet": -2, + "ps/roundtrip": -2, + "ps/page": -2, + }; + + def __init__(self, oTest, sName = None, sUnit = None, sTimestamp = None, lValue = None): + self.oTest = oTest; + self.sName = sName; + self.sUnit = sUnit; + self.sTimestamp = sTimestamp; + self.lValue = self.valueToInteger(lValue); + assert self.lValue is None or isinstance(self.lValue, (int, long)), "lValue=%s %s" % (self.lValue, type(self.lValue),); + + def clone(self, oParentTest): + """ + Clones the value. + """ + return Value(oParentTest, self.sName, self.sUnit, self.sTimestamp, self.lValue); + + def matchFilters(self, sPrefix, aoFilters): + """ + Checks for any substring match between aoFilters (str or re.Pattern) + and the value name prefixed by sPrefix. + + Returns True if any of the filters matches. + Returns False if none of the filters matches. + """ + sFullName = sPrefix + self.sName; + for oFilter in aoFilters: + if oFilter.search(sFullName) is not None if isinstance(oFilter, re.Pattern) else sFullName.find(oFilter) >= 0: + return True; + return False; + + def canDoBetterCompare(self): + """ + Checks whether we can do a confident better-than comparsion of the value. + """ + return self.sUnit is not None and self.kdBestByUnit[self.sUnit] not in (-1, 0, 1); + + def getBetterRelation(self): + """ + Returns +2 if larger values are definintely better. + Returns +1 if larger values are likely to be better. + Returns 0 if we have no clue. + Returns -1 if smaller values are likey to better. + Returns -2 if smaller values are definitely better. + """ + if self.sUnit is None: + return 0; + return self.kdBestByUnit[self.sUnit]; + + @staticmethod + def valueToInteger(sValue): + """ + Returns integer (long) represention of lValue. + Returns None if it cannot be converted to integer. + + Raises an exception if sValue isn't an integer. + """ + if sValue is None or isinstance(sValue, (int, long)): + return sValue; + sValue = sValue.strip(); + if not sValue: + return None; + return long(sValue); + + # Manipluation + + def distill(self, aoValues, sMethod): + """ + Distills the value of the object from values from multiple test runs. + """ + if not aoValues: + return self; + + # Everything except the value comes from the first run. + self.sName = aoValues[0].sName; + self.sTimestamp = aoValues[0].sTimestamp; + self.sUnit = aoValues[0].sUnit; + + # Find the value to use according to sMethod. + if len(aoValues) == 1: + self.lValue = aoValues[0].lValue; + else: + alValuesXcptInvalid = [oValue.lValue for oValue in aoValues if oValue.lValue is not None]; + if not alValuesXcptInvalid: + # No integer result, so just pick the first value whatever it is. + self.lValue = aoValues[0].lValue; + + elif sMethod == 'best': + # Pick the best result out of the whole bunch. + if self.kdBestByUnit[self.sUnit] >= 0: + self.lValue = max(alValuesXcptInvalid); + else: + self.lValue = min(alValuesXcptInvalid); + + elif sMethod == 'avg': + # Calculate the average. + self.lValue = (sum(alValuesXcptInvalid) + len(alValuesXcptInvalid) // 2) // len(alValuesXcptInvalid); + + else: + assert False; + self.lValue = aoValues[0].lValue; + + return self; + + + # debug + + def printValue(self, cIndent): + print('%sValue: name=%s timestamp=%s unit=%s value=%s' + % (''.ljust(cIndent*2), self.sName, self.sTimestamp, self.sUnit, self.lValue)); + + +class Test(object): + """ + Nested test result. + """ + def __init__(self, oParent = None, hsAttrs = None): + self.aoChildren = [] # type: list(Test) + self.aoValues = []; + self.oParent = oParent; + self.sName = hsAttrs['name'] if hsAttrs else None; + self.sStartTS = hsAttrs['timestamp'] if hsAttrs else None; + self.sEndTS = None; + self.sStatus = None; + self.cErrors = -1; + + def clone(self, oParent = None): + """ + Returns a deep copy. + """ + oClone = Test(oParent, {'name': self.sName, 'timestamp': self.sStartTS}); + + for oChild in self.aoChildren: + oClone.aoChildren.append(oChild.clone(oClone)); + + for oValue in self.aoValues: + oClone.aoValues.append(oValue.clone(oClone)); + + oClone.sEndTS = self.sEndTS; + oClone.sStatus = self.sStatus; + oClone.cErrors = self.cErrors; + return oClone; + + # parsing + + def addChild(self, oChild): + self.aoChildren.append(oChild); + return oChild; + + def addValue(self, oValue): + self.aoValues.append(oValue); + return oValue; + + def __markCompleted(self, sTimestamp): + """ Sets sEndTS if not already done. """ + if not self.sEndTS: + self.sEndTS = sTimestamp; + + def markPassed(self, sTimestamp): + self.__markCompleted(sTimestamp); + self.sStatus = 'passed'; + self.cErrors = 0; + + def markSkipped(self, sTimestamp): + self.__markCompleted(sTimestamp); + self.sStatus = 'skipped'; + self.cErrors = 0; + + def markFailed(self, sTimestamp, cErrors): + self.__markCompleted(sTimestamp); + self.sStatus = 'failed'; + self.cErrors = cErrors; + + def markEnd(self, sTimestamp, cErrors): + self.__markCompleted(sTimestamp); + if self.sStatus is None: + self.sStatus = 'failed' if cErrors != 0 else 'end'; + self.cErrors = 0; + + def mergeInIncludedTest(self, oTest): + """ oTest will be robbed. """ + if oTest is not None: + for oChild in oTest.aoChildren: + oChild.oParent = self; + self.aoChildren.append(oChild); + for oValue in oTest.aoValues: + oValue.oTest = self; + self.aoValues.append(oValue); + oTest.aoChildren = []; + oTest.aoValues = []; + + # debug + + def printTree(self, iLevel = 0): + print('%sTest: name=%s start=%s end=%s' % (''.ljust(iLevel*2), self.sName, self.sStartTS, self.sEndTS)); + for oChild in self.aoChildren: + oChild.printTree(iLevel + 1); + for oValue in self.aoValues: + oValue.printValue(iLevel + 1); + + # getters / queries + + def getFullNameWorker(self, cSkipUpper): + if self.oParent is None: + return (self.sName, 0); + sName, iLevel = self.oParent.getFullNameWorker(cSkipUpper); + if iLevel < cSkipUpper: + sName = self.sName; + else: + sName += ', ' + self.sName; + return (sName, iLevel + 1); + + def getFullName(self, cSkipUpper = 2): + return self.getFullNameWorker(cSkipUpper)[0]; + + def matchFilters(self, aoFilters): + """ + Checks for any substring match between aoFilters (str or re.Pattern) + and the full test name. + + Returns True if any of the filters matches. + Returns False if none of the filters matches. + """ + sFullName = self.getFullName(); + for oFilter in aoFilters: + if oFilter.search(sFullName) is not None if isinstance(oFilter, re.Pattern) else sFullName.find(oFilter) >= 0: + return True; + return False; + + # manipulation + + def filterTestsWorker(self, asFilters, fReturnOnMatch): + # depth first + i = 0; + while i < len(self.aoChildren): + if self.aoChildren[i].filterTestsWorker(asFilters, fReturnOnMatch): + i += 1; + else: + self.aoChildren[i].oParent = None; + del self.aoChildren[i]; + + # If we have children, they must've matched up. + if self.aoChildren: + return True; + if self.matchFilters(asFilters): + return fReturnOnMatch; + return not fReturnOnMatch; + + def filterTests(self, asFilters): + """ Keep tests matching asFilters. """ + if asFilters: + self.filterTestsWorker(asFilters, True); + return self; + + def filterOutTests(self, asFilters): + """ Removes tests matching asFilters. """ + if asFilters: + self.filterTestsWorker(asFilters, False); + return self; + + def filterValuesWorker(self, asFilters, fKeepWhen): + # Process children recursively. + for oChild in self.aoChildren: + oChild.filterValuesWorker(asFilters, fKeepWhen); + + # Filter our values. + iValue = len(self.aoValues); + if iValue > 0: + sFullname = self.getFullName() + ': '; + while iValue > 0: + iValue -= 1; + if self.aoValues[iValue].matchFilters(sFullname, asFilters) != fKeepWhen: + del self.aoValues[iValue]; + return None; + + def filterValues(self, asFilters): + """ Keep values matching asFilters. """ + if asFilters: + self.filterValuesWorker(asFilters, True); + return self; + + def filterOutValues(self, asFilters): + """ Removes values matching asFilters. """ + if asFilters: + self.filterValuesWorker(asFilters, False); + return self; + + def filterOutEmptyLeafTests(self): + """ + Removes any child tests that has neither values nor sub-tests. + Returns True if leaf, False if not. + """ + iChild = len(self.aoChildren); + while iChild > 0: + iChild -= 1; + if self.aoChildren[iChild].filterOutEmptyLeafTests(): + del self.aoChildren[iChild]; + return not self.aoChildren and not self.aoValues; + + @staticmethod + def calcDurationStatic(sStartTS, sEndTS): + """ + Returns None the start timestamp is absent or invalid. + Returns datetime.timedelta otherwise. + """ + if not sStartTS: + return None; + try: + oStart = utils.parseIsoTimestamp(sStartTS); + except: + return None; + + if not sEndTS: + return datetime.timedelta.max; + try: + oEnd = utils.parseIsoTimestamp(sEndTS); + except: + return datetime.timedelta.max; + + return oEnd - oStart; + + def calcDuration(self): + """ + Returns the duration as a datetime.timedelta object or None if not available. + """ + return self.calcDurationStatic(self.sStartTS, self.sEndTS); + + def calcDurationAsMicroseconds(self): + """ + Returns the duration as microseconds or None if not available. + """ + oDuration = self.calcDuration(); + if not oDuration: + return None; + return (oDuration.days * 86400 + oDuration.seconds) * 1000000 + oDuration.microseconds; + + @staticmethod + def distillTimes(aoTestRuns, sMethod, sStatus): + """ + Destills the error counts of the tests. + Returns a (sStartTS, sEndTS) pair. + """ + + # + # Start by assembling two list of start and end times for all runs that have a start timestamp. + # Then sort out the special cases where no run has a start timestamp and only a single one has. + # + asStartTS = [oRun.sStartTS for oRun in aoTestRuns if oRun.sStartTS]; + if not asStartTS: + return (None, None); + asEndTS = [oRun.sEndTS for oRun in aoTestRuns if oRun.sStartTS]; # parallel to asStartTS, so we don't check sEndTS. + if len(asStartTS) == 1: + return (asStartTS[0], asEndTS[0]); + + # + # Calculate durations for all runs. + # + if sMethod == 'best': + aoDurations = [Test.calcDurationStatic(oRun.sStartTS, oRun.sEndTS) for oRun in aoTestRuns if oRun.sStatus == sStatus]; + if not aoDurations or aoDurations.count(None) == len(aoDurations): + aoDurations = [Test.calcDurationStatic(oRun.sStartTS, oRun.sEndTS) for oRun in aoTestRuns]; + if aoDurations.count(None) == len(aoDurations): + return (asStartTS[0], None); + oDuration = min([oDuration for oDuration in aoDurations if oDuration is not None]); + + elif sMethod == 'avg': + print("dbg: 0: sStatus=%s []=%s" + % (sStatus, [(Test.calcDurationStatic(oRun.sStartTS, oRun.sEndTS),oRun.sStatus) for oRun in aoTestRuns],)); + aoDurations = [Test.calcDurationStatic(oRun.sStartTS, oRun.sEndTS) for oRun in aoTestRuns if oRun.sStatus == sStatus]; + print("dbg: 1: aoDurations=%s" % (aoDurations,)) + aoDurations = [oDuration for oDuration in aoDurations if oDuration]; + print("dbg: 2: aoDurations=%s" % (aoDurations,)) + if not aoDurations: + return (asStartTS[0], None); + aoDurations = [oDuration for oDuration in aoDurations if oDuration < datetime.timedelta.max]; + print("dbg: 3: aoDurations=%s" % (aoDurations,)) + if not aoDurations: + return (asStartTS[0], None); + # sum doesn't work on timedelta, so do it manually. + oDuration = aoDurations[0]; + for i in range(1, len(aoDurations)): + oDuration += aoDurations[i]; + print("dbg: 5: oDuration=%s" % (aoDurations,)) + oDuration = oDuration / len(aoDurations); + print("dbg: 6: oDuration=%s" % (aoDurations,)) + + else: + assert False; + return (asStartTS[0], asEndTS[0]); + + # Check unfinished test. + if oDuration >= datetime.timedelta.max: + return (asStartTS[0], None); + + # Calculate and format the end timestamp string. + oStartTS = utils.parseIsoTimestamp(asStartTS[0]); + oEndTS = oStartTS + oDuration; + return (asStartTS[0], utils.formatIsoTimestamp(oEndTS)); + + @staticmethod + def distillStatus(aoTestRuns, sMethod): + """ + Destills the status of the tests. + Returns the status. + """ + asStatuses = [oRun.sStatus for oRun in aoTestRuns]; + + if sMethod == 'best': + for sStatus in ('passed', 'failed', 'skipped'): + if sStatus in asStatuses: + return sStatus; + return asStatuses[0]; + + if sMethod == 'avg': + cPassed = asStatuses.count('passed'); + cFailed = asStatuses.count('failed'); + cSkipped = asStatuses.count('skipped'); + cEnd = asStatuses.count('end'); + cNone = asStatuses.count(None); + if cPassed >= cFailed and cPassed >= cSkipped and cPassed >= cNone and cPassed >= cEnd: + return 'passed'; + if cFailed >= cPassed and cFailed >= cSkipped and cFailed >= cNone and cFailed >= cEnd: + return 'failed'; + if cSkipped >= cPassed and cSkipped >= cFailed and cSkipped >= cNone and cSkipped >= cEnd: + return 'skipped'; + if cEnd >= cPassed and cEnd >= cFailed and cEnd >= cNone and cEnd >= cSkipped: + return 'end'; + return None; + + assert False; + return asStatuses[0]; + + @staticmethod + def distillErrors(aoTestRuns, sMethod): + """ + Destills the error counts of the tests. + Returns the status. + """ + acErrorsXcptNeg = [oRun.cErrors for oRun in aoTestRuns if oRun.cErrors >= 0]; + + if sMethod == 'best': + if acErrorsXcptNeg: + return min(acErrorsXcptNeg); + elif sMethod == 'avg': + if acErrorsXcptNeg: + return sum(acErrorsXcptNeg) // len(acErrorsXcptNeg); + else: + assert False; + return -1; + + def distill(self, aoTestRuns, sMethod, fDropLoners): + """ + Distills the test runs into this test. + """ + # + # Recurse first (before we create too much state in the stack + # frame) and do child tests. + # + # We copy the child lists of each test run so we can remove tests we've + # processed from each run and thus make sure we include tests in + # + # + aaoChildren = [list(oRun.aoChildren) for oRun in aoTestRuns]; + + # Process the tests for each run. + for i, _ in enumerate(aaoChildren): + # Process all tests for the current run. + while len(aaoChildren[i]) > 0: + oFirst = aaoChildren[i].pop(0); + + # Build a list of sub-test runs by searching remaining runs by test name. + aoSameSubTests = [oFirst,]; + for j in range(i + 1, len(aaoChildren)): + aoThis = aaoChildren[j]; + for iThis, oThis in enumerate(aoThis): + if oThis.sName == oFirst.sName: + del aoThis[iThis]; + aoSameSubTests.append(oThis); + break; + + # Apply fDropLoners. + if not fDropLoners or len(aoSameSubTests) > 1 or len(aaoChildren) == 1: + # Create an empty test and call distill on it with the subtest array, unless + # of course that the array only has one member and we can simply clone it. + if len(aoSameSubTests) == 1: + self.addChild(oFirst.clone(self)); + else: + oSubTest = Test(self); + oSubTest.sName = oFirst.sName; + oSubTest.distill(aoSameSubTests, sMethod, fDropLoners); + self.addChild(oSubTest); + del aaoChildren; + + # + # Do values. Similar approch as for the sub-tests. + # + aaoValues = [list(oRun.aoValues) for oRun in aoTestRuns]; + + # Process the values for each run. + for i,_ in enumerate(aaoValues): + # Process all values for the current run. + while len(aaoValues[i]) > 0: + oFirst = aaoValues[i].pop(0); + + # Build a list of values runs by searching remaining runs by value name and unit. + aoSameValues = [oFirst,]; + for j in range(i + 1, len(aaoValues)): + aoThis = aaoValues[j]; + for iThis, oThis in enumerate(aoThis): + if oThis.sName == oFirst.sName and oThis.sUnit == oFirst.sUnit: + del aoThis[iThis]; + aoSameValues.append(oThis); + break; + + # Apply fDropLoners. + if not fDropLoners or len(aoSameValues) > 1 or len(aaoValues) == 1: + # Create an empty test and call distill on it with the subtest array, unless + # of course that the array only has one member and we can simply clone it. + if len(aoSameValues) == 1: + self.aoValues.append(oFirst.clone(self)); + else: + oValue = Value(self); + oValue.distill(aoSameValues, sMethod); + self.aoValues.append(oValue); + del aaoValues; + + # + # Distill test properties. + # + self.sStatus = self.distillStatus(aoTestRuns, sMethod); + self.cErrors = self.distillErrors(aoTestRuns, sMethod); + (self.sStartTS, self.sEndTS) = self.distillTimes(aoTestRuns, sMethod, self.sStatus); + print("dbg: %s: sStartTS=%s, sEndTS=%s" % (self.sName, self.sStartTS, self.sEndTS)); + + return self; + + +class XmlLogReader(object): + """ + XML log reader class. + """ + + def __init__(self, sXmlFile): + self.sXmlFile = sXmlFile; + self.oRoot = Test(None, {'name': 'root', 'timestamp': ''}); + self.oTest = self.oRoot; + self.iLevel = 0; + self.oValue = None; + + def parse(self): + try: + oFile = open(self.sXmlFile, 'rb'); # pylint: disable=consider-using-with + except: + traceback.print_exc(); + return False; + + from xml.parsers.expat import ParserCreate + oParser = ParserCreate(); + oParser.StartElementHandler = self.handleElementStart; + oParser.CharacterDataHandler = self.handleElementData; + oParser.EndElementHandler = self.handleElementEnd; + try: + oParser.ParseFile(oFile); + except: + traceback.print_exc(); + oFile.close(); + return False; + oFile.close(); + return True; + + def handleElementStart(self, sName, hsAttrs): + #print('%s%s: %s' % (''.ljust(self.iLevel * 2), sName, str(hsAttrs))); + if sName in ('Test', 'SubTest',): + self.iLevel += 1; + self.oTest = self.oTest.addChild(Test(self.oTest, hsAttrs)); + elif sName == 'Value': + self.oValue = self.oTest.addValue(Value(self.oTest, hsAttrs.get('name'), hsAttrs.get('unit'), + hsAttrs.get('timestamp'), hsAttrs.get('value'))); + elif sName == 'End': + self.oTest.markEnd(hsAttrs.get('timestamp'), int(hsAttrs.get('errors', '0'))); + elif sName == 'Passed': + self.oTest.markPassed(hsAttrs.get('timestamp')); + elif sName == 'Skipped': + self.oTest.markSkipped(hsAttrs.get('timestamp')); + elif sName == 'Failed': + self.oTest.markFailed(hsAttrs.get('timestamp'), int(hsAttrs['errors'])); + elif sName == 'Include': + self.handleInclude(hsAttrs); + else: + print('Unknown element "%s"' % (sName,)); + + def handleElementData(self, sData): + if self.oValue is not None: + self.oValue.addData(sData); + elif sData.strip() != '': + print('Unexpected data "%s"' % (sData,)); + return True; + + def handleElementEnd(self, sName): + if sName in ('Test', 'Subtest',): + self.iLevel -= 1; + self.oTest = self.oTest.oParent; + elif sName == 'Value': + self.oValue = None; + return True; + + def handleInclude(self, hsAttrs): + # relative or absolute path. + sXmlFile = hsAttrs['filename']; + if not os.path.isabs(sXmlFile): + sXmlFile = os.path.join(os.path.dirname(self.sXmlFile), sXmlFile); + + # Try parse it. + oSub = parseTestResult(sXmlFile); + if oSub is None: + print('error: failed to parse include "%s"' % (sXmlFile,)); + else: + # Skip the root and the next level before merging it the subtest and + # values in to the current test. The reason for this is that the + # include is the output of some sub-program we've run and we don't need + # the extra test level it automatically adds. + # + # More benchmark heuristics: Walk down until we find more than one + # test or values. + oSub2 = oSub; + while len(oSub2.aoChildren) == 1 and not oSub2.aoValues: + oSub2 = oSub2.aoChildren[0]; + if not oSub2.aoValues: + oSub2 = oSub; + self.oTest.mergeInIncludedTest(oSub2); + return True; + +def parseTestResult(sXmlFile): + """ + Parses the test results in the XML. + Returns result tree. + Returns None on failure. + """ + oXlr = XmlLogReader(sXmlFile); + if oXlr.parse(): + if len(oXlr.oRoot.aoChildren) == 1 and not oXlr.oRoot.aoValues: + return oXlr.oRoot.aoChildren[0]; + return oXlr.oRoot; + return None; + diff --git a/src/VBox/ValidationKit/analysis/reporting.py b/src/VBox/ValidationKit/analysis/reporting.py new file mode 100755 index 00000000..ee1830e4 --- /dev/null +++ b/src/VBox/ValidationKit/analysis/reporting.py @@ -0,0 +1,746 @@ +# -*- coding: utf-8 -*- +# $Id: reporting.py $ + +""" +Test Result Report Writer. + +This takes a processed test result tree and creates a HTML, re-structured text, +or normal text report from it. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" + +__version__ = "$Revision: 154268 $" + +# Standard python imports. +import os; +import sys; + +# Only the main script needs to modify the path. +try: __file__; +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))); +sys.path.append(g_ksValidationKitDir); + +# ValidationKit imports. +from common import utils; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + + +################################################################################################################################## +# Run Table # +################################################################################################################################## + +def alignTextLeft(sText, cchWidth): + """ Left aligns text and pads it to cchWidth characters length. """ + return sText + ' ' * (cchWidth - min(len(sText), cchWidth)); + + +def alignTextRight(sText, cchWidth): + """ Right aligns text and pads it to cchWidth characters length. """ + return ' ' * (cchWidth - min(len(sText), cchWidth)) + sText; + + +def alignTextCenter(sText, cchWidth): + """ Pads the text equally on both sides to cchWidth characters length. """ + return alignTextLeft(' ' * ((cchWidth - min(len(sText), cchWidth)) // 2) + sText, cchWidth); + + +g_kiAlignLeft = -1; +g_kiAlignRight = 1; +g_kiAlignCenter = 0; +def alignText(sText, cchWidth, iAlignType): + """ + General alignment method. + + Negative iAlignType for left aligning, zero for entered, and positive for + right aligning the text. + """ + if iAlignType < 0: + return alignTextLeft(sText, cchWidth); + if iAlignType > 0: + return alignTextRight(sText, cchWidth); + return alignTextCenter(sText, cchWidth); + + +class TextColumnWidth(object): + """ + Tracking the width of a column, dealing with sub-columns and such. + """ + + def __init__(self): + self.cch = 0; + self.dacchSub = {}; + + def update(self, oWidth, cchSubColSpacing = 1): + """ + Updates the column width tracking with oWidth, which is either + an int or an array of ints (sub columns). + """ + if isinstance(oWidth, int): + self.cch = max(self.cch, oWidth); + else: + cSubCols = len(oWidth); + if cSubCols not in self.dacchSub: + self.dacchSub[cSubCols] = list(oWidth); + self.cch = max(self.cch, sum(oWidth) + cchSubColSpacing * (cSubCols - 1)); + else: + acchSubCols = self.dacchSub[cSubCols]; + for iSub in range(cSubCols): + acchSubCols[iSub] = max(acchSubCols[iSub], oWidth[iSub]); + self.cch = max(self.cch, sum(acchSubCols) + cchSubColSpacing * (cSubCols - 1)); + + def finalize(self): + """ Finalizes sub-column sizes. """ + ## @todo maybe do something here, maybe not... + return self; + + def hasSubColumns(self): + """ Checks if there are sub-columns for this column. """ + return not self.dacchSub; + +class TextWidths(object): + """ + Tracks the column widths for text rending of the table. + """ + def __init__(self, cchSubColSpacing = 1, ): + self.cchName = 1; + self.aoColumns = [] # type: TextColumnWidth + self.cchSubColSpacing = cchSubColSpacing; + self.fFinalized = False; + + def update(self, aoWidths): + """ Updates the tracker with the returns of calcColumnWidthsForText. """ + if not aoWidths[0]: + self.cchName = max(self.cchName, aoWidths[1]); + + for iCol, oWidth in enumerate(aoWidths[2]): + if iCol >= len(self.aoColumns): + self.aoColumns.append(TextColumnWidth()); + self.aoColumns[iCol].update(oWidth, self.cchSubColSpacing); + + return self; + + def finalize(self): + """ Finalizes sub-column sizes. """ + for oColumnWidth in self.aoColumns: + oColumnWidth.finalize(); + self.fFinalized = True; + return self; + + def getColumnWidth(self, iColumn, cSubs = None, iSub = None): + """ Returns the width of the specified column. """ + if not self.fFinalized: + return 0; + assert iColumn < len(self.aoColumns), "iColumn=%s vs %s" % (iColumn, len(self.aoColumns),); + oColumn = self.aoColumns[iColumn]; + if cSubs is not None: + assert iSub < cSubs; + if cSubs != 1: + assert cSubs in oColumn.dacchSub, \ + "iColumn=%s cSubs=%s iSub=%s; dacchSub=%s" % (iColumn, cSubs, iSub, oColumn.dacchSub); + return oColumn.dacchSub[cSubs][iSub]; + return oColumn.cch; + + +class TextElement(object): + """ + A text element (cell/sub-cell in a table). + """ + + def __init__(self, sText = '', iAlign = g_kiAlignRight): # type: (str, int) -> None + self.sText = sText; + self.iAlign = iAlign; + + def asText(self, cchWidth): # type: (int) -> str + """ Pads the text to width of cchWidth characters. """ + return alignText(self.sText, cchWidth, self.iAlign); + + +class RunRow(object): + """ + Run table row. + """ + + def __init__(self, iLevel, sName, iRun = 0): # type: (int, str, int) -> None + self.iLevel = iLevel; + self.sName = sName; + self.iFirstRun = iRun; + + # Fields used while formatting (set during construction or calcColumnWidthsForText/Html). + self.cColumns = 0; ##< Number of columns. + self.fSkip = False ##< Whether or not to skip this row in the output. + + # Format as Text: + + def formatNameAsText(self, cchWidth): # (int) -> TextElement + """ Format the row as text. """ + _ = cchWidth; + return TextElement(' ' * (self.iLevel * 2) + self.sName, g_kiAlignLeft); + + def getColumnCountAsText(self, oTable): + """ + Called by calcColumnWidthsForText for getting an up-to-date self.cColumns value. + Override this to update cColumns after construction. + """ + _ = oTable; + return self.cColumns; + + def formatColumnAsText(self, iColumn, oTable): # type: (int, RunTable) -> [TextElement] + """ Returns an array of TextElements for the given column in this row. """ + _ = iColumn; _ = oTable; + return [ TextElement(),]; + + def calcColumnWidthsForText(self, oTable): # type: (RunTable) -> (bool, int, []) + """ + Calculates the column widths for text rendering. + + Returns a tuple consisting of the fSkip, the formatted name width, and an + array of column widths. The entries in the latter are either integer + widths or arrays of subcolumn integer widths. + """ + aoRetCols = []; + cColumns = self.getColumnCountAsText(oTable); + for iColumn in range(cColumns): + aoSubColumns = self.formatColumnAsText(iColumn, oTable); + if len(aoSubColumns) == 1: + aoRetCols.append(len(aoSubColumns[0].sText)); + else: + aoRetCols.append([len(oSubColumn.sText) for oSubColumn in aoSubColumns]); + return (False, len(self.formatNameAsText(0).sText), aoRetCols); + + def renderAsText(self, oWidths, oTable): # type: (TextWidths, RunTable) -> str + """ + Renders the row as text. + + Returns string. + """ + sRow = self.formatNameAsText(oWidths.cchName).asText(oWidths.cchName); + sRow = sRow + ' ' * (oWidths.cchName - min(len(sRow), oWidths.cchName)) + ' : '; + + for iColumn in range(self.cColumns): + aoSubCols = self.formatColumnAsText(iColumn, oTable); + sCell = ''; + for iSub, oText in enumerate(aoSubCols): + cchWidth = oWidths.getColumnWidth(iColumn, len(aoSubCols), iSub); + if iSub > 0: + sCell += ' ' * oWidths.cchSubColSpacing; + sCell += oText.asText(cchWidth); + cchWidth = oWidths.getColumnWidth(iColumn); + sRow += (' | ' if iColumn > 0 else '') + ' ' * (cchWidth - min(cchWidth, len(sCell))) + sCell; + + return sRow; + + @staticmethod + def formatDiffAsText(lNumber, lBaseline): + """ Formats the difference between lNumber and lBaseline as text. """ + if lNumber is not None: + if lBaseline is not None: + if lNumber < lBaseline: + return '-' + utils.formatNumber(lBaseline - lNumber); ## @todo formatter is busted for negative nums. + if lNumber > lBaseline: + return '+' + utils.formatNumber(lNumber - lBaseline); + return '0'; + return ''; + + @staticmethod + def formatPctAsText(chSign, rdPct, cPctPrecision): + """ Formats percentage value as text. """ + if rdPct >= 100: + return '%s%s%%' % (chSign, utils.formatNumber(int(rdPct + 0.5)),); + if round(rdPct, cPctPrecision) != 0: + return '%s%.*f%%' % (chSign, cPctPrecision, rdPct,); # %.*f rounds. + return '~' + chSign + '0.' + '0' * cPctPrecision + '%'; + + @staticmethod + def formatDiffInPctAsText(lNumber, lBaseline, cPctPrecision): + """ Formats the difference between lNumber and lBaseline in precent as text. """ + if lNumber is not None: + if lBaseline is not None: + ## @todo implement cPctPrecision + if lNumber == lBaseline: + return '0.' + '0'*cPctPrecision + '%'; + + lDiff = lNumber - lBaseline; + chSign = '+'; + if lDiff < 0: + lDiff = -lDiff; + chSign = '-'; + return RunRow.formatPctAsText(chSign, lDiff / float(lBaseline) * 100, cPctPrecision); + return ''; + + +class RunHeaderRow(RunRow): + """ + Run table header row. + """ + def __init__(self, sName, asColumns): # type: (str, [str]) -> None + RunRow.__init__(self, 0, sName); + self.asColumns = asColumns + self.cColumns = len(asColumns); + + def formatColumnAsText(self, iColumn, oTable): # type: (int, RunTable) -> [TextElement] + return [TextElement(self.asColumns[iColumn], g_kiAlignCenter),]; + + +class RunFooterRow(RunHeaderRow): + """ + Run table footer row. + """ + def __init__(self, sName, asColumns): + RunHeaderRow.__init__(self, sName, asColumns); + + +class RunSeparatorRow(RunRow): + """ + Base class for separator rows. + """ + def __init__(self): + RunRow.__init__(self, 0, ''); + + def calcTableWidthAsText(self, oWidths): + """ Returns the table width for when rendered as text. """ + cchWidth = oWidths.cchName; + for oCol in oWidths.aoColumns: + cchWidth += 3 + oCol.cch; + return cchWidth; + + +class RunHeaderSeparatorRow(RunSeparatorRow): + """ + Run table header separator row. + """ + def __init__(self): + RunSeparatorRow.__init__(self); + + def renderAsText(self, oWidths, oTable): + _ = oTable; + return '=' * self.calcTableWidthAsText(oWidths); + + +class RunFooterSeparatorRow(RunHeaderSeparatorRow): + """ + Run table footer separator row. + """ + def __init__(self): + RunHeaderSeparatorRow.__init__(self); + + +class RunTestRow(RunRow): + """ + Run table test row. + """ + + def __init__(self, iLevel, oTest, iRun, aoTests = None): # type: (int, reader.Test, int, [reader.Test]) -> None + RunRow.__init__(self, iLevel, oTest.sName, iRun); + assert oTest; + self.oTest = oTest; + if aoTests is None: + aoTests = [None for i in range(iRun)]; + aoTests.append(oTest); + else: + aoTests= list(aoTests); + self.aoTests = aoTests + + def isSameTest(self, oTest): + """ Checks if oTest belongs to this row or not. """ + return oTest.sName == self.oTest.sName; + + def getBaseTest(self, oTable): + """ Returns the baseline test. """ + oBaseTest = self.aoTests[oTable.iBaseline]; + if not oBaseTest: + oBaseTest = self.aoTests[self.iFirstRun]; + return oBaseTest; + + +class RunTestStartRow(RunTestRow): + """ + Run table start of test row. + """ + + def __init__(self, iLevel, oTest, iRun): # type: (int, reader.Test, int) -> None + RunTestRow.__init__(self, iLevel, oTest, iRun); + + def renderAsText(self, oWidths, oTable): + _ = oTable; + sRet = self.formatNameAsText(oWidths.cchName).asText(oWidths.cchName); + sRet += ' : '; + sRet += ' | '.join(['-' * oCol.cch for oCol in oWidths.aoColumns]); + return sRet; + + +class RunTestEndRow(RunTestRow): + """ + Run table end of test row. + """ + + def __init__(self, oStartRow): # type: (RunTestStartRow) -> None + RunTestRow.__init__(self, oStartRow.iLevel, oStartRow.oTest, oStartRow.iFirstRun, oStartRow.aoTests); + self.oStartRow = oStartRow # type: RunTestStartRow + + def getColumnCountAsText(self, oTable): + self.cColumns = len(self.aoTests); + return self.cColumns; + + def formatColumnAsText(self, iColumn, oTable): + oTest = self.aoTests[iColumn]; + if oTest and oTest.sStatus: + if oTest.cErrors > 0: + return [ TextElement(oTest.sStatus, g_kiAlignCenter), + TextElement(utils.formatNumber(oTest.cErrors) + 'errors') ]; + return [ TextElement(oTest.sStatus, g_kiAlignCenter) ]; + return [ TextElement(), ]; + + +class RunTestEndRow2(RunTestRow): + """ + Run table 2nd end of test row, this shows the times. + """ + + def __init__(self, oStartRow): # type: (RunTestStartRow) -> None + RunTestRow.__init__(self, oStartRow.iLevel, oStartRow.oTest, oStartRow.iFirstRun, oStartRow.aoTests); + self.oStartRow = oStartRow # type: RunTestStartRow + + def formatNameAsText(self, cchWidth): + _ = cchWidth; + return TextElement('runtime', g_kiAlignRight); + + def getColumnCountAsText(self, oTable): + self.cColumns = len(self.aoTests); + return self.cColumns; + + def formatColumnAsText(self, iColumn, oTable): + oTest = self.aoTests[iColumn]; + if oTest: + cUsElapsed = oTest.calcDurationAsMicroseconds(); + if cUsElapsed: + oBaseTest = self.getBaseTest(oTable); + if oTest is oBaseTest: + return [ TextElement(utils.formatNumber(cUsElapsed)), TextElement('us', g_kiAlignLeft), ]; + cUsElapsedBase = oBaseTest.calcDurationAsMicroseconds(); + aoRet = [ + TextElement(utils.formatNumber(cUsElapsed)), + TextElement(self.formatDiffAsText(cUsElapsed, cUsElapsedBase)), + TextElement(self.formatDiffInPctAsText(cUsElapsed, cUsElapsedBase, oTable.cPctPrecision)), + ]; + return aoRet[1:] if oTable.fBrief else aoRet; + return [ TextElement(), ]; + + +class RunTestValueAnalysisRow(RunTestRow): + """ + Run table row with value analysis for a test, see if we have an improvement or not. + """ + def __init__(self, oStartRow): # type: (RunTestStartRow) -> None + RunTestRow.__init__(self, oStartRow.iLevel, oStartRow.oTest, oStartRow.iFirstRun, oStartRow.aoTests); + self.oStartRow = oStartRow # type: RunTestStartRow + self.cColumns = len(self.aoTests); + + def formatNameAsText(self, cchWidth): + _ = cchWidth; + return TextElement('value analysis', g_kiAlignRight); + + def formatColumnAsText(self, iColumn, oTable): + oBaseline = self.getBaseTest(oTable); + oTest = self.aoTests[iColumn]; + if not oTest or oTest is oBaseline: + return [TextElement(),]; + + # + # This is a bit ugly, but it means we don't have to re-merge the values. + # + cTotal = 0; + cBetter = 0; + cWorse = 0; + cSame = 0; + cUncertain = 0; + rdPctTotal = 0.0; + + iRow = oTable.aoRows.index(self.oStartRow); # ugly + while iRow < len(oTable.aoRows): + oRow = oTable.aoRows[iRow]; + if oRow is self: + break; + if isinstance(oRow, RunValueRow): + oValue = oRow.aoValues[iColumn]; + oBaseValue = oRow.getBaseValue(oTable); + if oValue is not None and oValue is not oBaseValue: + iBetter = oValue.getBetterRelation(); + if iBetter != 0: + lDiff = oValue.lValue - oBaseValue.lValue; + rdPct = abs(lDiff / float(oBaseValue.lValue) * 100); + if rdPct < oTable.rdPctSameValue: + cSame += 1; + else: + if lDiff > 0 if iBetter > 0 else lDiff < 0: + cBetter += 1; + rdPctTotal += rdPct; + else: + cWorse += 1; + rdPctTotal += -rdPct; + cUncertain += 1 if iBetter in (1, -1) else 0; + cTotal += 1; + iRow += 1; + + # + # Format the result. + # + aoRet = []; + if not oTable.fBrief: + sText = u' \u2193%u' % (cWorse,); + sText = u' \u2248%u' % (cSame,) + alignTextRight(sText, 4); + sText = u'\u2191%u' % (cBetter,) + alignTextRight(sText, 8); + aoRet = [TextElement(sText),]; + + if cSame >= cWorse and cSame >= cBetter: + sVerdict = 'same'; + elif cWorse >= cSame and cWorse >= cBetter: + sVerdict = 'worse'; + else: + sVerdict = 'better'; + if cUncertain > 0: + sVerdict = 'probably ' + sVerdict; + aoRet.append(TextElement(sVerdict)); + + rdPctAvg = abs(rdPctTotal / cTotal); # Yes, average of the percentages! + aoRet.append(TextElement(self.formatPctAsText('+' if rdPctTotal >= 0 else '-', rdPctAvg, oTable.cPctPrecision))); + + return aoRet; + + +class RunValueRow(RunRow): + """ + Run table value row. + """ + + def __init__(self, iLevel, oValue, iRun): # type: (int, reader.Value, int) -> None + RunRow.__init__(self, iLevel, oValue.sName, iRun); + self.oValue = oValue; + self.aoValues = [None for i in range(iRun)]; + self.aoValues.append(oValue); + + def isSameValue(self, oValue): + """ Checks if oValue belongs to this row or not. """ + return oValue.sName == self.oValue.sName and oValue.sUnit == self.oValue.sUnit; + + # Formatting as Text. + + @staticmethod + def formatOneValueAsText(oValue): # type: (reader.Value) -> str + """ Formats a value. """ + if not oValue: + return "N/A"; + return utils.formatNumber(oValue.lValue); + + def getBaseValue(self, oTable): + """ Returns the base value instance. """ + oBaseValue = self.aoValues[oTable.iBaseline]; + if not oBaseValue: + oBaseValue = self.aoValues[self.iFirstRun]; + return oBaseValue; + + def getColumnCountAsText(self, oTable): + self.cColumns = len(self.aoValues); + return self.cColumns; + + def formatColumnAsText(self, iColumn, oTable): + oValue = self.aoValues[iColumn]; + oBaseValue = self.getBaseValue(oTable); + if oValue is oBaseValue: + return [ TextElement(self.formatOneValueAsText(oValue)), + TextElement(oValue.sUnit, g_kiAlignLeft), ]; + aoRet = [ + TextElement(self.formatOneValueAsText(oValue)), + TextElement(self.formatDiffAsText(oValue.lValue if oValue else None, oBaseValue.lValue)), + TextElement(self.formatDiffInPctAsText(oValue.lValue if oValue else None, oBaseValue.lValue, oTable.cPctPrecision)) + ]; + return aoRet[1:] if oTable.fBrief else aoRet; + + +class RunTable(object): + """ + Result table. + + This contains one or more test runs as columns. + """ + + def __init__(self, iBaseline = 0, fBrief = True, cPctPrecision = 2, rdPctSameValue = 0.10): # (int, bool, int, float) -> None + self.asColumns = [] # type: [str] ##< Column names. + self.aoRows = [] # type: [RunRow] ##< The table rows. + self.iBaseline = iBaseline # type: int ##< Which column is the baseline when diffing things. + self.fBrief = fBrief # type: bool ##< Whether to exclude the numerical values of non-baseline runs. + self.cPctPrecision = cPctPrecision # type: int ##< Number of decimal points in diff percentage value. + self.rdPctSameValue = rdPctSameValue # type: float ##< The percent value at which a value difference is considered + ## to be the same during value analysis. + def __populateFromValues(self, aaoValueRuns, iLevel): # type: ([reader.Value]) -> None + """ + Internal worker for __populateFromRuns() + + This will modify the sub-lists inside aaoValueRuns, returning with them all empty. + + Returns True if an value analysis row should be added, False if not. + """ + # Same as for __populateFromRuns, only no recursion. + fAnalysisRow = False; + for iValueRun, aoValuesForRun in enumerate(aaoValueRuns): + while aoValuesForRun: + oRow = RunValueRow(iLevel, aoValuesForRun.pop(0), iValueRun); + self.aoRows.append(oRow); + + # Pop matching values from the other runs of this test. + for iOtherRun in range(iValueRun + 1, len(aaoValueRuns)): + aoValuesForOtherRun = aaoValueRuns[iOtherRun]; + for iValueToPop, oOtherValue in enumerate(aoValuesForOtherRun): + if oRow.isSameValue(oOtherValue): + oRow.aoValues.append(aoValuesForOtherRun.pop(iValueToPop)); + break; + if len(oRow.aoValues) <= iOtherRun: + oRow.aoValues.append(None); + + fAnalysisRow = fAnalysisRow or oRow.oValue.canDoBetterCompare(); + return fAnalysisRow; + + def __populateFromRuns(self, aaoTestRuns, iLevel): # type: ([reader.Test]) -> None + """ + Internal worker for populateFromRuns() + + This will modify the sub-lists inside aaoTestRuns, returning with them all empty. + """ + + # + # Currently doing depth first, so values are always at the end. + # Nominally, we should inject values according to the timestamp. + # However, that's too much work right now and can be done later if needed. + # + for iRun, aoTestForRun in enumerate(aaoTestRuns): + while aoTestForRun: + # Pop the next test and create a start-test row for it. + oStartRow = RunTestStartRow(iLevel, aoTestForRun.pop(0), iRun); + self.aoRows.append(oStartRow); + + # Pop matching tests from the other runs. + for iOtherRun in range(iRun + 1, len(aaoTestRuns)): + aoOtherTestRun = aaoTestRuns[iOtherRun]; + for iTestToPop, oOtherTest in enumerate(aoOtherTestRun): + if oStartRow.isSameTest(oOtherTest): + oStartRow.aoTests.append(aoOtherTestRun.pop(iTestToPop)); + break; + if len(oStartRow.aoTests) <= iOtherRun: + oStartRow.aoTests.append(None); + + # Now recursively do the subtests for it and then do the values. + self.__populateFromRuns( [list(oTest.aoChildren) if oTest else list() for oTest in oStartRow.aoTests], iLevel+1); + fValueAnalysisRow = self.__populateFromValues([list(oTest.aoValues) + if oTest else list() for oTest in oStartRow.aoTests], iLevel+1); + + # Add the end-test row for it. + self.aoRows.append(RunTestEndRow(oStartRow)); + self.aoRows.append(RunTestEndRow2(oStartRow)); + if fValueAnalysisRow: + self.aoRows.append(RunTestValueAnalysisRow(oStartRow)); + + return self; + + def populateFromRuns(self, aoTestRuns, asRunNames = None): # type: ([reader.Test], [str]) -> RunTable + """ + Populates the table from the series of runs. + + The aoTestRuns and asRunNames run in parallel. If the latter isn't + given, the names will just be ordinals starting with #0 for the + first column. + + Returns self. + """ + # + # Deal with the column names first. + # + if asRunNames: + self.asColumns = list(asRunNames); + else: + self.asColumns = []; + iCol = len(self.asColumns); + while iCol < len(aoTestRuns): + self.asColumns.append('#%u%s' % (iCol, ' (baseline)' if iCol == self.iBaseline else '',)); + + self.aoRows = [ + RunHeaderSeparatorRow(), + RunHeaderRow('Test / Value', self.asColumns), + RunHeaderSeparatorRow(), + ]; + + # + # Now flatten the test trees into a table. + # + self.__populateFromRuns([[oTestRun,] for oTestRun in aoTestRuns], 0); + + # + # Add a footer if there are a lot of rows. + # + if len(self.aoRows) - 2 > 40: + self.aoRows.extend([RunFooterSeparatorRow(), RunFooterRow('', self.asColumns),]); + + return self; + + # + # Text formatting. + # + + def formatAsText(self): + """ + Formats the table as text. + + Returns a string array of the output lines. + """ + + # + # Pass 1: Calculate column widths. + # + oWidths = TextWidths(1); + for oRow in self.aoRows: + oWidths.update(oRow.calcColumnWidthsForText(self)); + oWidths.finalize(); + + # + # Pass 2: Generate the output strings. + # + asRet = []; + for oRow in self.aoRows: + if not oRow.fSkip: + asRet.append(oRow.renderAsText(oWidths, self)); + + return asRet; + diff --git a/src/VBox/ValidationKit/bootsectors/Config.kmk b/src/VBox/ValidationKit/bootsectors/Config.kmk new file mode 100644 index 00000000..3937275e --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/Config.kmk @@ -0,0 +1,951 @@ +# $Id: Config.kmk $ +## @file +# kBuild Configuration file for VirtualBox Boot Sector Kit 3. +# + +# +# Copyright (C) 2010-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +VBOX_BOOTSECTORS_CONFIG_KMK_INCLUDED = 1 + +# Include the parent configure file. +ifndef VBOX_VALIDATIONKIT_CONFIG_KMK_INCLUDED + include $(PATH_ROOT)/src/VBox/ValidationKit/Config.kmk +endif + +# Add our 32-bit and 64-bit C properties. +KBUILD_COMPILE_CATEGTORIES += C32 C64 +PROPS_TOOLS += C32TOOL C64TOOL +PROPS_SINGLE += C32TOOL C64TOOL C32OBJSUFF C64OBJSUFF +PROPS_ACCUMULATE_R += C32FLAGS C64FLAGS C32DEFS C64DEFS +PROPS_ACCUMULATE_L += C32INCS C64INCS + +if 0 # Adding as few as possible new properties. +KBUILD_COMPILE_CATEGTORIES += C16 +PROPS_TOOLS += C16TOOL +PROPS_SINGLE += C16TOOL C16OBJSUFF +PROPS_ACCUMULATE_R += C16FLAGS C16DEFS +PROPS_ACCUMULATE_L += C16INCS +endif + +# Add noarch to the architectures list (will be there by default in a new kBuild). +KBUILD_ARCHES += noarch + + +# The bootsector directory. +VBOX_PATH_BOOTSECTORS_SRC = $(VBOX_PATH_VALIDATIONKIT_SRC)/bootsectors + +# The bs3kit source directory. +VBOX_PATH_BS3KIT_SRC = $(VBOX_PATH_BOOTSECTORS_SRC)/bs3kit + + +# The 16-bit code & data segment classes. +if 1 +BS3KIT_CLASS_CODE16 = CODE +BS3KIT_SEGNM_DATA16 = +BS3KIT_CLASS_DATA16 = DATA +BS3KIT_GRPNM_DATA16 = DGROUP +BS3KIT_CLASS_BSS16 = BSS +else +BS3KIT_CLASS_CODE16 = BS3CLASS16CODE +BS3KIT_SEGNM_DATA16 = BS3DATA16 +BS3KIT_CLASS_DATA16 = FAR_DATA +BS3KIT_GRPNM_DATA16 = BS3DATA16_GROUP +BS3KIT_CLASS_BSS16 = ??? +endif + + +## +# Macro for generating near-call aliases for one 16-bit C function. +# @param 1 The target name. +# @param 2 The common function. +BS3KIT_FN_GEN_CMN_NEARSTUB = $(evalcall2 def_Bs3KitGenNearStubSource,$1,_$2_c16,_$2_f16) + +## +# Macro for generating near-call aliases for one 16-bit C mode function. +# @param 1 The target name. +# @param 2 The mode function. +BS3KIT_FN_GEN_MODE_NEARSTUB = $(foreach suff, \ + _rm \ + _pe16 \ + _pe16_v86 \ + _pe32_16 \ + _pev86 \ + _pp16 \ + _pp16_v86 \ + _pp32_16 \ + _ppv86 \ + _pae16 \ + _pae16_v86 \ + _pae32_16 \ + _paev86 \ + _lm16 \ + ,$(evalcall2 def_Bs3KitGenNearStubSource,$1,_$2$(suff),_$2$(suff)_far)) + +# @param 1 The target name. +# @param 2 The near function name. +# @param 3 The far function name. +define def_Bs3KitGenNearStubSource +$1_SOURCES += $$($1_0_OUTDIR)/stub$2.asm +$1_CLEAN += $$($1_0_OUTDIR)/stub$2.asm +$$$$($1_0_OUTDIR)/stub$2.asm: $$(VBOX_PATH_BOOTSECTORS_SRC)/Config.kmk | $$$$(dir $$$$@) + $(QUIET)$(APPEND) -tn $$@ \ + '%include "bs3kit.mac"' \ + 'BS3_BEGIN_TEXT16' \ + ' extern $3' \ + 'BS3_BEGIN_TEXT16_NEARSTUBS' \ + 'BS3_GLOBAL_NAME_EX $2, function, 6' \ + ' pop ax' \ + ' push cs' \ + ' push ax' \ + ' jmp $3 wrt CGROUP16' +endef + + +## +# Macro for generating far-call aliases for zero or more 16-bit C or assembly functions. +# @param 1 The target name. +# @param 2 The common function. +# @param 3 The parameter size in bytes. +BS3KIT_FN_GEN_CMN_FARSTUB = $(evalcall2 def_Bs3KitGenFarStubSource,$1,$2,_f16,_c16,$3) + +## +# Macro for generating far-call aliases for zero or more 16-bit C mode functions. +# @param 1 The target name. +# @param 2 The mode function. +# @param 3 The parameter size in bytes. +BS3KIT_FN_GEN_MODE_FARSTUB = $(foreach suff, \ + _rm \ + _pe16 \ + _pe16_v86 \ + _pe32_16 \ + _pev86 \ + _pp16 \ + _pp16_v86 \ + _pp32_16 \ + _ppv86 \ + _pae16 \ + _pae16_v86 \ + _pae32_16 \ + _paev86 \ + _lm16 \ + ,$(evalcall2 def_Bs3KitGenFarStubSource,$1,$2,$(suff)_far,$(suff),$3)) + +# @param 1 The target name. +# @param 2 The function name. +# @param 3 The far function suffix. +# @param 4 The near function suffix. +# @param 5 The parameter size in bytes. +define def_Bs3KitGenFarStubSource +$1_SOURCES += $$($1_0_OUTDIR)/stub_$2$3.asm +$1_CLEAN += $$($1_0_OUTDIR)/stub_$2$3.asm +$$$$($1_0_OUTDIR)/stub_$2$3.asm: $$(VBOX_PATH_BOOTSECTORS_SRC)/Config.kmk | $$$$(dir $$$$@) + $(QUIET)$(APPEND) -tn $$@ \ + '%include "bs3kit.mac"' \ + 'BS3_BEGIN_TEXT16' \ + ' extern _$2$4' \ + 'BS3_BEGIN_TEXT16_FARSTUBS' \ + 'BS3_PROC_BEGIN _$2$3' \ + ' CPU 8086' \ + ' inc bp' \ + ' push bp' \ + ' mov bp, sp' \ + '%assign offParam $5' \ + '%rep $5 / 2' \ + ' push word [bp + 2 + 4 + offParam - 2]' \ + '%assign offParam offParam - 2' \ + '%endrep' \ + ' call _$2$4' \ + ' add sp, $5' \ + ' pop bp' \ + ' dec bp' \ + ' retf' \ + 'BS3_PROC_END _$2$3' \ + '' +endef + + +# +# Tools Tools Tools +# Tools Tools Tools +# Tools Tools Tools +# + +if defined(VBOX_USE_KSUBMIT) && "$(KBUILD_HOST)" == "win" + VBOX_BS3KIT_KSUBMIT_OBJ_CONV := kmk_builtin_kSubmit -- +else + VBOX_BS3KIT_KSUBMIT_OBJ_CONV := +endif + +# Dummy CP "linker" tool. +TOOL_VBoxBsCpLd = Dummy copy linker. +TOOL_VBoxBsCpLd_LINK_MISCBIN_OUTPUT = +TOOL_VBoxBsCpLd_LINK_MISCBIN_DEPEND = +TOOL_VBoxBsCpLd_LINK_MISCBIN_DEPORD = +define TOOL_VBoxBsCpLd_LINK_MISCBIN_CMDS + $(CP) -- $(objs) $(othersrc) "$(out)" +endef + +# Dummy exit 1 "linker" tool. +TOOL_VBoxBsUnusedLd = Dummy unused linker. +TOOL_VBoxBsUnusedLd_LINK_MISCBIN_OUTPUT = +TOOL_VBoxBsUnusedLd_LINK_MISCBIN_DEPEND = +TOOL_VBoxBsUnusedLd_LINK_MISCBIN_DEPORD = +define TOOL_VBoxBsUnusedLd_LINK_MISCBIN_CMDS + echo "cannot use this template for linking" + exit 1 +endef + +# NASM tool with dependency workarounds (change dir to force consistent results; add -MP). +# Requires http://permalink.gmane.org/gmane.comp.lang.nasm.devel/3704 to work. +include $(KBUILD_PATH)/tools/NASM.kmk +TOOL_VBoxNasm = Our version of the NASM tool +ifndef TOOL_VBoxNasm_PATH + TOOL_VBoxNasm_PATH := $(firstword $(rsort $(wildcard $(KBUILD_DEVTOOLS_HST)/nasm/v*.*))) + if "$(TOOL_VBoxNasm_PATH)" == "" && "$(KBUILD_DEVTOOLS_HST_ALT)" != "" + TOOL_VBoxNasm_PATH := $(firstword $(rsort $(wildcard $(KBUILD_DEVTOOLS_HST_ALT)/nasm/v*.*))) + endif +endif +ifneq ($(TOOL_VBoxNasm_PATH),) + TOOL_VBoxNasm_AS ?= $(TOOL_VBoxNasm_PATH)/nasm$(HOSTSUFF_EXE) +else + TOOL_VBoxNasm_AS ?= nasm$(HOSTSUFF_EXE) +endif +TOOL_VBoxNasm_ASFLAGS ?= $(TOOL_NASM_ASFLAGS) +TOOL_VBoxNasm_COMPILE_AS_OUTPUT = $(outbase).lst +TOOL_VBoxNasm_COMPILE_AS_OUTPUT_MAYBE = $(obj).original +TOOL_VBoxNasm_COMPILE_AS_DEPEND = $(VBoxBs3ObjConverter_1_TARGET) +TOOL_VBoxNasm_COMPILE_AS_DEPORD = +define TOOL_VBoxNasm_COMPILE_AS_CMDS +ifdef TOOL_VBoxNasm_USE_KSUBMIT + $(QUIET)kmk_builtin_kSubmit -C $(PATH_OUT_BASE) -- $(TOOL_VBoxNasm_AS)\ + $(flags) $(addsuffix /,$(addprefix -i, $(incs))) $(addprefix -D, $(defs))\ + -l $(outbase).lst\ + -o $(obj)\ + -MD "$(dep)" -MP\ + $(abspath $(source)) +else + $(QUIET)$(REDIRECT) -C $(PATH_OUT_BASE) -- $(TOOL_VBoxNasm_AS)\ + $(flags) $(addsuffix /,$(addprefix -i, $(incs))) $(addprefix -D, $(defs))\ + -l $(outbase).lst\ + -o $(obj)\ + -MD "$(dep)" -MP\ + $(abspath $(source)) +endif + $(QUIET)$(VBOX_BS3KIT_KSUBMIT_OBJ_CONV) $(VBoxBs3ObjConverter_1_TARGET) "$(obj)" +endef + +# +# ELF 64-bit compiler tool with object conversion. +# +# Mac needs cross compiler: sudo port install x86_64-elf-gcc +# +TOOL_Bs3Gcc64Elf64 := AMD64/ELF64 gcc/g++ (cross) compiler. +ifeq ($(KBUILD_HOST),darwin) + TOOL_Bs3Gcc64Elf64_CC ?= x86_64-elf-gcc$(HOSTSUFF_EXE) -m64 + TOOL_Bs3Gcc64Elf64_CXX ?= x86_64-elf-g++$(HOSTSUFF_EXE) -m64 +else + TOOL_Bs3Gcc64Elf64_CC ?= gcc$(HOSTSUFF_EXE) -m64 + TOOL_Bs3Gcc64Elf64_CXX ?= g++$(HOSTSUFF_EXE) -m64 +endif +ifdef SLKRUNS + TOOL_Bs3Gcc64Elf64_CC += -fmessage-length=0 + TOOL_Bs3Gcc64Elf64_CXX += -fmessage-length=0 +endif +TOOL_Bs3Gcc64Elf64_COBJSUFF = .o64 +TOOL_Bs3Gcc64Elf64_CFLAGS = -fno-pie -x c $(VBOX_GCC_Wa_cma_nocompress_debug_sections) -ffreestanding +TOOL_Bs3Gcc64Elf64_CFLAGS.debug = -g +TOOL_Bs3Gcc64Elf64_CFLAGS.profile = -O2 #-g -pg +TOOL_Bs3Gcc64Elf64_CFLAGS.release = -O2 +TOOL_Bs3Gcc64Elf64_CINCS = +TOOL_Bs3Gcc64Elf64_CDEFS = +TOOL_Bs3Gcc64Elf64_COMPILE_C_DEPEND = $(VBoxBs3ObjConverter_1_TARGET) +TOOL_Bs3Gcc64Elf64_COMPILE_C_DEPORD = +TOOL_Bs3Gcc64Elf64_COMPILE_C_OUTPUT = +TOOL_Bs3Gcc64Elf64_COMPILE_C_OUTPUT_MAYBE = $(obj).original +define TOOL_Bs3Gcc64Elf64_COMPILE_C_CMDS + $(QUIET)$(TOOL_Bs3Gcc64Elf64_CC) -c\ + $(flags) $(addprefix -I, $(incs)) $(addprefix -D, $(defs))\ + -Wp,-MD,$(dep) -Wp,-MT,$(obj) -Wp,-MP\ + -o $(obj)\ + $(abspath $(source)) + $(QUIET)$(VBOX_BS3KIT_KSUBMIT_OBJ_CONV) $(VBoxBs3ObjConverter_1_TARGET) "$(obj)" + $(QUIET)$(APPEND) -n "$(dep)" "" "$(source):" "" +endef + +TOOL_Bs3Gcc64Elf64_C64OBJSUFF = $(TOOL_Bs3Gcc64Elf64_COBJSUFF) +TOOL_Bs3Gcc64Elf64_C64FLAGS = $(TOOL_Bs3Gcc64Elf64_CFLAGS) +TOOL_Bs3Gcc64Elf64_C64FLAGS.debug = $(TOOL_Bs3Gcc64Elf64_CFLAGS.debug) +TOOL_Bs3Gcc64Elf64_C64FLAGS.profile = $(TOOL_Bs3Gcc64Elf64_CFLAGS.profile) +TOOL_Bs3Gcc64Elf64_C64FLAGS.release = $(TOOL_Bs3Gcc64Elf64_CFLAGS.release) +TOOL_Bs3Gcc64Elf64_C64INCS = $(TOOL_Bs3Gcc64Elf64_CINCS) +TOOL_Bs3Gcc64Elf64_C64DEFS = $(TOOL_Bs3Gcc64Elf64_CDEFS) +TOOL_Bs3Gcc64Elf64_COMPILE_C64_DEPEND = $(TOOL_Bs3Gcc64Elf64_COMPILE_C_DEPEND) +TOOL_Bs3Gcc64Elf64_COMPILE_C64_DEPORD = $(TOOL_Bs3Gcc64Elf64_COMPILE_C_DEPORD) +TOOL_Bs3Gcc64Elf64_COMPILE_C64_OUTPUT = $(TOOL_Bs3Gcc64Elf64_COMPILE_C_OUTPUT) +TOOL_Bs3Gcc64Elf64_COMPILE_C64_OUTPUT_MAYBE = $(TOOL_Bs3Gcc64Elf64_COMPILE_C_OUTPUT_MAYBE) +define TOOL_Bs3Gcc64Elf64_COMPILE_C64_CMDS +$(TOOL_Bs3Gcc64Elf64_COMPILE_C_CMDS) +endef + +TOOL_Bs3Gcc64Elf64_CXXOBJSUFF ?= .o +TOOL_Bs3Gcc64Elf64_CXXFLAGS ?= -fno-pie $(VBOX_GCC_Wa_cma_nocompress_debug_sections) -ffreestanding +TOOL_Bs3Gcc64Elf64_CXXFLAGS.debug ?= -g0 # no debug info, thank you +TOOL_Bs3Gcc64Elf64_CXXFLAGS.profile ?= -O2 #-g -pg +TOOL_Bs3Gcc64Elf64_CXXFLAGS.release ?= -O2 +TOOL_Bs3Gcc64Elf64_CXXINCS ?= +TOOL_Bs3Gcc64Elf64_CXXDEFS ?= +TOOL_Bs3Gcc64Elf64_COMPILE_CXX_DEPEND = $(VBoxBs3ObjConverter_1_TARGET) +TOOL_Bs3Gcc64Elf64_COMPILE_CXX_DEPORD = +TOOL_Bs3Gcc64Elf64_COMPILE_CXX_OUTPUT = +TOOL_Bs3Gcc64Elf64_COMPILE_CXX_OUTPUT_MAYBE = $(obj).original +define TOOL_Bs3Gcc64Elf64_COMPILE_CXX_CMDS + $(QUIET)$(TOOL_Bs3Gcc64Elf64_CXX) -c\ + $(flags) $(addprefix -I, $(incs)) $(addprefix -D, $(defs))\ + -Wp,-MD,$(dep) -Wp,-MT,$(obj) -Wp,-MP\ + -o $(obj)\ + $(abspath $(source)) + $(QUIET)$(VBOX_BS3KIT_KSUBMIT_OBJ_CONV) $(VBoxBs3ObjConverter_1_TARGET) "$(obj)" + $(QUIET)$(APPEND) -n "$(dep)" "" "$(source):" "" +endef + +# +# Visual C++ tool variant that runs the object converter afterwards. +# +TOOL_Bs3Vcc64 := Visual C++ 64-bit +TOOL_Bs3Vcc64_CC = $(TOOL_$(VBOX_VCC_TOOL_STEM)AMD64_CC) +TOOL_Bs3Vcc64_CXX = $(TOOL_$(VBOX_VCC_TOOL_STEM)AMD64_CXX) +TOOL_Bs3Vcc64_COBJSUFF = .o64 +TOOL_Bs3Vcc64_CFLAGS = $(filter-out -TC -Zi,$(TOOL_$(VBOX_VCC_TOOL_STEM)AMD64_CFLAGS)) -TC -Z7 +TOOL_Bs3Vcc64_CFLAGS.debug = +TOOL_Bs3Vcc64_CFLAGS.dbgopt = -O1 +TOOL_Bs3Vcc64_CFLAGS.profile = -O1 +TOOL_Bs3Vcc64_CFLAGS.release = -O1 +TOOL_Bs3Vcc64_CINCS = $(PATH_TOOL_$(VBOX_VCC_TOOL_STEM)AMD64_INC) +TOOL_Bs3Vcc64_CDEFS = +TOOL_Bs3Vcc64_COMPILE_C_DEPEND = $(TOOL_$(VBOX_VCC_TOOL_STEM)AMD64_COMPILE_C_DEPEND) $(VBoxBs3ObjConverter_1_TARGET) +TOOL_Bs3Vcc64_COMPILE_C_DEPORD = $(TOOL_$(VBOX_VCC_TOOL_STEM)AMD64_COMPILE_C_DEPORD) +TOOL_Bs3Vcc64_COMPILE_C_OUTPUT = $(TOOL_$(VBOX_VCC_TOOL_STEM)AMD64_COMPILE_C_OUTPUT) +TOOL_Bs3Vcc64_COMPILE_C_OUTPUT_MAYBE = $(TOOL_$(VBOX_VCC_TOOL_STEM)AMD64_COMPILE_C_OUTPUT_MAYBE) $(obj).original +define TOOL_Bs3Vcc64_COMPILE_C_CMDS +$(TOOL_$(VBOX_VCC_TOOL_STEM)AMD64_COMPILE_C_CMDS) + $(QUIET)$(VBOX_BS3KIT_KSUBMIT_OBJ_CONV) $(VBoxBs3ObjConverter_1_TARGET) "$(obj)" +endef + +TOOL_Bs3Vcc64_C64OBJSUFF = $(TOOL_Bs3Vcc64_COBJSUFF) +TOOL_Bs3Vcc64_C64FLAGS = $(TOOL_Bs3Vcc64_CFLAGS) +TOOL_Bs3Vcc64_C64FLAGS.debug = $(TOOL_Bs3Vcc64_CFLAGS.debug) +TOOL_Bs3Vcc64_C64FLAGS.dbgopt = $(TOOL_Bs3Vcc64_CFLAGS.dbgopt) +TOOL_Bs3Vcc64_C64FLAGS.profile = $(TOOL_Bs3Vcc64_CFLAGS.profile) +TOOL_Bs3Vcc64_C64FLAGS.release = $(TOOL_Bs3Vcc64_CFLAGS.release) +TOOL_Bs3Vcc64_C64INCS = $(TOOL_Bs3Vcc64_CINCS) +TOOL_Bs3Vcc64_C64DEFS = $(TOOL_Bs3Vcc64_CDEFS) +TOOL_Bs3Vcc64_COMPILE_C64_DEPEND = $(TOOL_Bs3Vcc64_COMPILE_C_DEPEND) +TOOL_Bs3Vcc64_COMPILE_C64_DEPORD = $(TOOL_Bs3Vcc64_COMPILE_C_DEPORD) +TOOL_Bs3Vcc64_COMPILE_C64_OUTPUT = $(TOOL_Bs3Vcc64_COMPILE_C_OUTPUT) +TOOL_Bs3Vcc64_COMPILE_C64_OUTPUT_MAYBE = $(TOOL_Bs3Vcc64_COMPILE_C_OUTPUT_MAYBE) +define TOOL_Bs3Vcc64_COMPILE_C64_CMDS +$(TOOL_Bs3Vcc64_COMPILE_C_CMDS) +endef + +TOOL_Bs3Vcc64_CXXOBJSUFF = $(TOOL_$(VBOX_VCC_TOOL_STEM)AMD64_CXXOBJSUFF) +TOOL_Bs3Vcc64_CXXFLAGS = $(filter-out -Zi,$(TOOL_$(VBOX_VCC_TOOL_STEM)AMD64_CXXFLAGS)) -TP -Z7 +TOOL_Bs3Vcc64_CXXFLAGS.debug = +TOOL_Bs3Vcc64_CXXFLAGS.dbgopt = -O1 +TOOL_Bs3Vcc64_CXXFLAGS.profile = -O1 +TOOL_Bs3Vcc64_CXXFLAGS.release = -O1 +TOOL_Bs3Vcc64_CXXINCS = $(PATH_TOOL_$(VBOX_VCC_TOOL_STEM)AMD64_INC) +TOOL_Bs3Vcc64_CXXDEFS = +TOOL_Bs3Vcc64_COMPILE_CXX_DEPEND = $(TOOL_$(VBOX_VCC_TOOL_STEM)AMD64_COMPILE_CXX_DEPEND) $(VBoxBs3ObjConverter_1_TARGET) +TOOL_Bs3Vcc64_COMPILE_CXX_DEPORD = $(TOOL_$(VBOX_VCC_TOOL_STEM)AMD64_COMPILE_CXX_DEPORD) +TOOL_Bs3Vcc64_COMPILE_CXX_OUTPUT = $(TOOL_$(VBOX_VCC_TOOL_STEM)AMD64_COMPILE_CXX_OUTPUT) +TOOL_Bs3Vcc64_COMPILE_CXX_OUTPUT_MAYBE = $(TOOL_$(VBOX_VCC_TOOL_STEM)AMD64_COMPILE_CXX_OUTPUT_MAYBE) $(obj).original +define TOOL_Bs3Vcc64_COMPILE_CXX_CMDS +$(TOOL_$(VBOX_VCC_TOOL_STEM)AMD64_COMPILE_CXX_CMDS) + $(QUIET)$(VBOX_BS3KIT_KSUBMIT_OBJ_CONV) $(VBoxBs3ObjConverter_1_TARGET) "$(obj)" +endef + +# +# 32-bit OpenWatcom C/C++ tool variant that runs the object converter afterwards +# to rename intrinsic functions so they don't clash with the 16-bit compiler. +# +TOOL_Bs3Ow32 := OpenWatcom C/C++ 32-bit with object convertsion +TOOL_Bs3Ow32_CC = $(TOOL_OPENWATCOM_CC) +TOOL_Bs3Ow32_CXX = $(TOOL_OPENWATCOM_CXX) +TOOL_Bs3Ow32_COBJSUFF = .o32 +TOOL_Bs3Ow32_CFLAGS = $(TOOL_OPENWATCOM_CFLAGS) +# -adfs \ - This is too complicated and it doesn't support stubbing files (svn rename fun.h pain.h). Use kDepObj instead. +# -ad=$(call TOOL_OPENWATCOM_FIX_SLASHES,$(dep)) \ +# -adt=$(call TOOL_OPENWATCOM_FIX_SLASHES,$(obj)) \ +# -add=$(call TOOL_OPENWATCOM_FIX_SLASHES,$(abspath $(source))) \ +# -adhp=$(call TOOL_OPENWATCOM_FIX_SLASHES,$(dir $(abspath $(source)))) +TOOL_Bs3Ow32_CFLAGS.debug = $(TOOL_OPENWATCOM_CFLAGS.debug) +TOOL_Bs3Ow32_CFLAGS.dbgopt = $(TOOL_OPENWATCOM_CFLAGS.dbgopt) +TOOL_Bs3Ow32_CFLAGS.profile = $(TOOL_OPENWATCOM_CFLAGS.profile) +TOOL_Bs3Ow32_CFLAGS.release = $(TOOL_OPENWATCOM_CFLAGS.release) +TOOL_Bs3Ow32_CINCS = $(TOOL_OPENWATCOM_CINCS) +TOOL_Bs3Ow32_CDEFS = +TOOL_Bs3Ow32_COMPILE_C_DEPEND = $(TOOL_OPENWATCOM_COMPILE_C_DEPEND) $(VBoxBs3ObjConverter_1_TARGET) +TOOL_Bs3Ow32_COMPILE_C_DEPORD = $(TOOL_OPENWATCOM_COMPILE_C_DEPORD) +TOOL_Bs3Ow32_COMPILE_C_OUTPUT = $(TOOL_OPENWATCOM_COMPILE_C_OUTPUT) +TOOL_Bs3Ow32_COMPILE_C_OUTPUT_MAYBE = $(TOOL_OPENWATCOM_COMPILE_C_OUTPUT_MAYBE) $(obj).original +define TOOL_Bs3Ow32_COMPILE_C_CMDS +$(TOOL_OPENWATCOM_COMPILE_C_CMDS) + $(QUIET)$(VBOX_BS3KIT_KSUBMIT_OBJ_CONV) $(VBoxBs3ObjConverter_1_TARGET) "$(obj)" +endef + +TOOL_Bs3Ow32_C32OBJSUFF = $(TOOL_Bs3Ow32_COBJSUFF) +TOOL_Bs3Ow32_C32FLAGS = $(TOOL_Bs3Ow32_CFLAGS) +TOOL_Bs3Ow32_C32FLAGS.debug = $(TOOL_Bs3Ow32_CFLAGS.debug) +TOOL_Bs3Ow32_C32FLAGS.dbgopt = $(TOOL_Bs3Ow32_CFLAGS.dbgopt) +TOOL_Bs3Ow32_C32FLAGS.profile = $(TOOL_Bs3Ow32_CFLAGS.profile) +TOOL_Bs3Ow32_C32FLAGS.release = $(TOOL_Bs3Ow32_CFLAGS.release) +TOOL_Bs3Ow32_C32INCS = $(TOOL_Bs3Ow32_CINCS) +TOOL_Bs3Ow32_C32DEFS = +TOOL_Bs3Ow32_COMPILE_C32_DEPEND = $(TOOL_Bs3Ow32_COMPILE_C_DEPEND) +TOOL_Bs3Ow32_COMPILE_C32_DEPORD = $(TOOL_Bs3Ow32_COMPILE_C_DEPORD) +TOOL_Bs3Ow32_COMPILE_C32_OUTPUT = $(TOOL_Bs3Ow32_COMPILE_C_OUTPUT) +TOOL_Bs3Ow32_COMPILE_C32_OUTPUT_MAYBE = $(TOOL_Bs3Ow32_COMPILE_C_OUTPUT_MAYBE) +define TOOL_Bs3Ow32_COMPILE_C32_CMDS +$(TOOL_Bs3Ow32_COMPILE_C_CMDS) +endef + +TOOL_Bs3Ow32_CXXOBJSUFF = $(TOOL_OPENWATCOM_CXXOBJSUFF) +TOOL_Bs3Ow32_CXXFLAGS = $(TOOL_OPENWATCOM_CXXFLAGS) -ad=$(call TOOL_OPENWATCOM_FIX_SLASHES,$(dep)) -adfs +TOOL_Bs3Ow32_CXXFLAGS.debug = $(TOOL_OPENWATCOM_CXXFLAGS.debug) +TOOL_Bs3Ow32_CXXFLAGS.dbgopt = $(TOOL_OPENWATCOM_CXXFLAGS.dbgopt) +TOOL_Bs3Ow32_CXXFLAGS.profile = $(TOOL_OPENWATCOM_CXXFLAGS.profile) +TOOL_Bs3Ow32_CXXFLAGS.release = $(TOOL_OPENWATCOM_CXXFLAGS.release) +TOOL_Bs3Ow32_CXXINCS = $(TOOL_OPENWATCOM_CXXINCS) +TOOL_Bs3Ow32_CXXDEFS = +TOOL_Bs3Ow32_COMPILE_CXX_DEPEND = $(TOOL_OPENWATCOM_COMPILE_CXX_DEPEND) $(VBoxBs3ObjConverter_1_TARGET) +TOOL_Bs3Ow32_COMPILE_CXX_DEPORD = $(TOOL_OPENWATCOM_COMPILE_CXX_DEPORD) +TOOL_Bs3Ow32_COMPILE_CXX_OUTPUT = $(TOOL_OPENWATCOM_COMPILE_CXX_OUTPUT) +TOOL_Bs3Ow32_COMPILE_CXX_OUTPUT_MAYBE = $(TOOL_OPENWATCOM_COMPILE_CXX_OUTPUT_MAYBE) $(obj).original +define TOOL_Bs3Ow32_COMPILE_CXX_CMDS +$(TOOL_OPENWATCOM_COMPILE_CXX_CMDS) + $(QUIET)$(VBOX_BS3KIT_KSUBMIT_OBJ_CONV) $(VBoxBs3ObjConverter_1_TARGET) "$(obj)" +endef + + +# +# 16-bit OpenWatcom C/C++ tool variant that runs the object converter afterwards +# to rename intrinsic functions so they don't clash with the 16-bit compiler. +# +TOOL_Bs3Ow16 := OpenWatcom C/C++ 16-bit with object convertsion +TOOL_Bs3Ow16_CC = $(TOOL_OPENWATCOM-16_CC) +TOOL_Bs3Ow16_CXX = $(TOOL_OPENWATCOM-16_CXX) +TOOL_Bs3Ow16_COBJSUFF = .o16 +TOOL_Bs3Ow16_CFLAGS = $(TOOL_OPENWATCOM-16_CFLAGS) +TOOL_Bs3Ow16_CFLAGS.debug = $(TOOL_OPENWATCOM-16_CFLAGS.debug) +TOOL_Bs3Ow16_CFLAGS.dbgopt = $(TOOL_OPENWATCOM-16_CFLAGS.dbgopt) +TOOL_Bs3Ow16_CFLAGS.profile = $(TOOL_OPENWATCOM-16_CFLAGS.profile) +TOOL_Bs3Ow16_CFLAGS.release = $(TOOL_OPENWATCOM-16_CFLAGS.release) +TOOL_Bs3Ow16_CINCS = $(TOOL_OPENWATCOM-16_CINCS) +TOOL_Bs3Ow16_CDEFS = +TOOL_Bs3Ow16_COMPILE_C_DEPEND = $(TOOL_OPENWATCOM-16_COMPILE_C_DEPEND) $(VBoxBs3ObjConverter_1_TARGET) +TOOL_Bs3Ow16_COMPILE_C_DEPORD = $(TOOL_OPENWATCOM-16_COMPILE_C_DEPORD) +TOOL_Bs3Ow16_COMPILE_C_OUTPUT = $(TOOL_OPENWATCOM-16_COMPILE_C_OUTPUT) +TOOL_Bs3Ow16_COMPILE_C_OUTPUT_MAYBE = $(TOOL_OPENWATCOM-16_COMPILE_C_OUTPUT_MAYBE) $(obj).original +define TOOL_Bs3Ow16_COMPILE_C_CMDS +$(TOOL_OPENWATCOM-16_COMPILE_C_CMDS) + $(QUIET)$(VBOX_BS3KIT_KSUBMIT_OBJ_CONV) $(VBoxBs3ObjConverter_1_TARGET) "$(obj)" +endef + +TOOL_Bs3Ow16_C16OBJSUFF = $(TOOL_Bs3Ow16_C16OBJSUFF) +TOOL_Bs3Ow16_C16FLAGS = $(TOOL_Bs3Ow16_C16FLAGS) +TOOL_Bs3Ow16_C16FLAGS.debug = $(TOOL_Bs3Ow16_C16FLAGS.debug) +TOOL_Bs3Ow16_C16FLAGS.dbgopt = $(TOOL_Bs3Ow16_C16FLAGS.dbgopt) +TOOL_Bs3Ow16_C16FLAGS.profile = $(TOOL_Bs3Ow16_C16FLAGS.profile) +TOOL_Bs3Ow16_C16FLAGS.release = $(TOOL_Bs3Ow16_C16FLAGS.release) +TOOL_Bs3Ow16_C16INCS = $(TOOL_Bs3Ow16_C16INCS) +TOOL_Bs3Ow16_C16DEFS = $(TOOL_Bs3Ow16_C16DEFS) +TOOL_Bs3Ow16_COMPILE_C16_DEPEND = $(TOOL_Bs3Ow16_COMPILE_C16_DEPEND) +TOOL_Bs3Ow16_COMPILE_C16_DEPORD = $(TOOL_Bs3Ow16_COMPILE_C16_DEPORD) +TOOL_Bs3Ow16_COMPILE_C16_OUTPUT = $(TOOL_Bs3Ow16_COMPILE_C16_OUTPUT) +TOOL_Bs3Ow16_COMPILE_C16_OUTPUT_MAYBE = $(TOOL_Bs3Ow16_COMPILE_C16_OUTPUT_MAYBE) +define TOOL_Bs3Ow16_COMPILE_C16_CMDS +$(TOOL_Bs3Ow16_COMPILE_C_CMDS) +endef + +TOOL_Bs3Ow16_CXXOBJSUFF = $(TOOL_OPENWATCOM-16_CXXOBJSUFF) +TOOL_Bs3Ow16_CXXFLAGS = $(TOOL_OPENWATCOM-16_CXXFLAGS) +TOOL_Bs3Ow16_CXXFLAGS.debug = $(TOOL_OPENWATCOM-16_CXXFLAGS.debug) +TOOL_Bs3Ow16_CXXFLAGS.dbgopt = $(TOOL_OPENWATCOM-16_CXXFLAGS.dbgopt) +TOOL_Bs3Ow16_CXXFLAGS.profile = $(TOOL_OPENWATCOM-16_CXXFLAGS.profile) +TOOL_Bs3Ow16_CXXFLAGS.release = $(TOOL_OPENWATCOM-16_CXXFLAGS.release) +TOOL_Bs3Ow16_CXXINCS = $(TOOL_OPENWATCOM-16_CXXINCS) +TOOL_Bs3Ow16_CXXDEFS = +TOOL_Bs3Ow16_COMPILE_CXX_DEPEND = $(TOOL_OPENWATCOM-16_COMPILE_CXX_DEPEND) $(VBoxBs3ObjConverter_1_TARGET) +TOOL_Bs3Ow16_COMPILE_CXX_DEPORD = $(TOOL_OPENWATCOM-16_COMPILE_CXX_DEPORD) +TOOL_Bs3Ow16_COMPILE_CXX_OUTPUT = $(TOOL_OPENWATCOM-16_COMPILE_CXX_OUTPUT) +TOOL_Bs3Ow16_COMPILE_CXX_OUTPUT_MAYBE = $(TOOL_OPENWATCOM-16_COMPILE_CXX_OUTPUT_MAYBE) $(obj).original +define TOOL_Bs3Ow16_COMPILE_CXX_CMDS +$(TOOL_OPENWATCOM-16_COMPILE_CXX_CMDS) + $(QUIET)$(VBOX_BS3KIT_KSUBMIT_OBJ_CONV) $(VBoxBs3ObjConverter_1_TARGET) "$(obj)" +endef + +# Debug info format depends on what we use for 64-bit. +if 1 #1of ($(KBUILD_HOST), win) - wlink dwarf .sym files are useless for binary blobs + BS3_OW_DBG_OPT = -hc -d1+ + #BS3_OW_DBG_OPT = -hd -d1+ + BS3_OW_DBG_LDOPT = codeview +else + BS3_OW_DBG_OPT = -hd -d1+ + BS3_OW_DBG_LDOPT = dwarf +endif + +# +# Source handlers for .c16, .c32 and .c64 +# +define VBoxBs3KitImgSrcHandler_16bit_c +local type := C + $(kb-src-one 2) +endef + +C32TOOL = Bs3Ow32 +define VBoxBs3KitImgSrcHandler_32bit_c +local type := C32 + $(kb-src-one 2) +endef + +define VBoxBs3KitImgSrcHandler_64bit_c +local type := C64 + $(kb-src-one 2) +endef + + +# +# BS3Kit template for assembly and 16-bit code. +# +# Note! Using -d1 as -d1+ and -d2 causes suboptimal code to be generated (strlen +# reloading string pointer argument all the time). +# Update! -d1+ is required for line number information in code living in include +# files and any DWARF stuff at all. So, we'll ignore poor code quality. +# Note! Optimization options should come after debug stuff as -d2 for instance +# disables all optimziations. +# Note! We use BS3CLASS16CODE because of wdis code detection heuristics requires the class +# of a code segment to be exactly 'CODE', or ending with 'CODE' or 'TEXT' (more +# recent wdis have a -c=<clsnm> option, but not the one we currently use ). +# +# +# Compiler options explained: +# -nt=xxxx Sets the text segment name. +# -nc=xxxx Sets the text segment class name. +# -nd=xxxx Sets the data segment name. +# -ecc Sets the default calling convension to __cdecl +# Update: We don't use this in 16-bit code as it causes unfavorable reloading of DS before calling +# inlined functions (e.g. iprt/asm.h). Instead we use -ecw and __cdecl where needed. +# Update: With -zdp the DS reloading is gone. Code is slightly larger, but seems to cure stability +# issues in bs3CpuBasic2_RaiseXcpt1 (workers ending up with default calling convention). +# -ecw Sets the default calling convension to __watcall () +# -q Quiet, no logos or stuff. +# -0 Use 8086 instruction set (16-bit only). +# -3 Use 386 instruction set (16-bit only). +# -e<num> Stop after <num> errors. +# -wx Maxium warning level. +# -zl Don't emit default library information. +# -zdp DS pegged to BS3DATA16_GROUP/DGROUP. +# -zu Assume SS != DS. +# -mc Compact memory model, far data, small code. +# -ml Large memory model, far data, far code. +# -mf Flat memory model (32-bit). +# -d+ Enabled better /dVAR=XXX parsing, using space as delimiter instead of alpha-numerical/whatever. +# -d1 Debug info: Globals and line numbers. +# -s No stack overflow checks. +# -oa Relaxed aliasing constraints. +# -ob Branch prediction. +# -of Generate stack frames when needed. +# -oi Inline instrinsics functions. +# -ol Loop optimizations. +# -oh Expensive optimizations. (saves a byte or two) +# -or Reorder for best pipeline. +# -os Favor size over speed. +# +TEMPLATE_VBoxBS3KitImg = Template for building BS3Kit test images. +TEMPLATE_VBoxBS3KitImg_BLD_TRG = os-agnostic +TEMPLATE_VBoxBS3KitImg_BLD_TRG_ARCH = noarch +TEMPLATE_VBoxBS3KitImg_INST = $(INST_VALIDATIONKIT)bootsectors/ +TEMPLATE_VBoxBS3KitImg_BINSUFF = .img +TEMPLATE_VBoxBS3KitImg_MODE = 0644 +TEMPLATE_VBoxBS3KitImg_SRC_HANDLERS = \ + .c16:VBoxBs3KitImgSrcHandler_16bit_c \ + .c32:VBoxBs3KitImgSrcHandler_32bit_c \ + .c64:VBoxBs3KitImgSrcHandler_64bit_c +TEMPLATE_VBoxBS3KitImg_ASOBJSUFF = .o16 +TEMPLATE_VBoxBS3KitImg_ASTOOL = VBoxNasm +TEMPLATE_VBoxBS3KitImg_ASFLAGS = -f obj -g $(BS3KIT_NASM_allow_64_bit) -w+orphan-labels +TEMPLATE_VBoxBS3KitImg_ASDEFS = ASM_FORMAT_OMF RT_NOINC_SEGMENTS __NASM__ ARCH_BITS=16 RT_ARCH_X86 ASM_MODEL_FAR_CODE \ + BS3CLASS16CODE=$(BS3KIT_CLASS_CODE16) BS3KIT_CLASS_DATA16=$(BS3KIT_CLASS_DATA16) \ + BS3KIT_GRPNM_DATA16=$(BS3KIT_GRPNM_DATA16) BS3KIT_CLASS_BSS16=$(BS3KIT_CLASS_BSS16) +TEMPLATE_VBoxBS3KitImg_DEFS = IN_BS3KIT +TEMPLATE_VBoxBS3KitImg_DEFS.debug = BS3_STRICT + +TEMPLATE_VBoxBS3KitImg_ARTOOL = OPENWATCOM-16 + +TEMPLATE_VBoxBS3KitImg_CTOOL = Bs3Ow16 +TEMPLATE_VBoxBS3KitImg_CXXTOOL = Bs3Ow16 +TEMPLATE_VBoxBS3KitImg_CFLAGS = $(if $(BS3KIT_SEGNM_DATA16),-nd=$(BS3KIT_SEGNM_DATA16),) \ + -nt=BS3TEXT16 -nc=$(BS3KIT_CLASS_CODE16) -ecc -q -0 -e125 -wx -zl -zdp -zu -ml $(BS3_OW_DBG_OPT) -s -oa -ob -of -oi -ol -or -os -oh -d+ +TEMPLATE_VBoxBS3KitImg_CXXFLAGS = $(if $(BS3KIT_SEGNM_DATA16),-nd=$(BS3KIT_SEGNM_DATA16),) \ + -nt=BS3TEXT16 -nc=$(BS3KIT_CLASS_CODE16) -ecc -q -0 -e125 -wx -zl -zdp -zu -ml $(BS3_OW_DBG_OPT) -s -oa -ob -of -oi -ol -or -os -oh -d+ +TEMPLATE_VBoxBS3KitImg_CDEFS = ARCH_BITS=16 RT_ARCH_X86 + +TEMPLATE_VBoxBS3KitImg_TOOL = $(NO_SUCH_VARIABLE) +TEMPLATE_VBoxBS3KitImg_C16TOOL = $(TEMPLATE_VBoxBS3KitImg_CTOOL) +TEMPLATE_VBoxBS3KitImg_C16FLAGS = $(TEMPLATE_VBoxBS3KitImg_CFLAGS) +TEMPLATE_VBoxBS3KitImg_C16DEFS = $(TEMPLATE_VBoxBS3KitImg_CDEFS) +TEMPLATE_VBoxBS3KitImg_C32TOOL := Bs3Ow32 +TEMPLATE_VBoxBS3KitImg_C32FLAGS = $(TEMPLATE_VBoxBS3KitImg32_CFLAGS) +TEMPLATE_VBoxBS3KitImg_C32DEFS = ARCH_BITS=32 RT_ARCH_X86 +TEMPLATE_VBoxBS3KitImg_C64TOOL = $(TEMPLATE_VBoxBS3KitImg64_CTOOL) +TEMPLATE_VBoxBS3KitImg_C64FLAGS = $(TEMPLATE_VBoxBS3KitImg64_CFLAGS) +TEMPLATE_VBoxBS3KitImg_C64DEFS = ARCH_BITS=64 RT_ARCH_AMD64 + +TEMPLATE_VBoxBS3KitImg_INCS = $(VBOX_PATH_BS3KIT_SRC) . +TEMPLATE_VBoxBS3KitImg_LDTOOL = OPENWATCOM-WL + +# linker options: +# system dos: Link a 16-bit DOS binary. +# output raw ...: Produce a raw DOS binary for loading at flat address 10000h. +# The following is for ordering segments. +# option start=_start: The start symbol in bs3-first-xxx.asm. +# debug codeview/dwarf all: Full debug information either in codeview or dwarf. +# option symfile: Produce a separate symbol file with the debug info. +# option map: Produce a map file. +# option farcalls: Change intrasegment far calls into 'push cs; seg ds; call symbol' where possible. +# option statics: ? +# option verbose: Verbose map file? +# option disable 1014: Disable warning about 'stack segment not found'. +# option disable 1080: Disable warning about '%1 is a 32-bit object file'. +# +# Note! We're pushing DATA16 to 0x20000 because it's impossible to force wlink +# to give us a real-mode + GDT compatible alignment (0ffffff80h), i.e. +# real-mode address on the form 0fff8:0000. +TEMPLATE_VBoxBS3KitImg_LDFLAGS = system dos \ + debug $(BS3_OW_DBG_LDOPT) all \ + option quiet, map, statics, verbose, symfile, start=_start, farcalls \ + disable 1014, 1080 \ + \ + output raw offset=0x10000 \ + order \ + clname BS3FLAT segaddr=0x0000 \ + segment BS3FLAT segaddr=0x0000 \ + clname $(BS3KIT_CLASS_CODE16) segaddr=0x1000 \ + segment BS3TEXT16 \ + segment BS3TEXT16_NEARSTUBS \ + segment BS3TEXT16_FARSTUBS \ + segment BS3TEXT16_END \ + clname BS3SYSTEM16 segaddr=0x2000 \ + segment BS3SYSTEM16 \ +$(if-expr "$(BS3KIT_SEGNM_DATA16)" == "", \ + clname DATA \ + segment BS3DATA16 segaddr=0x2900 \ + segment BS3DATA16_DATA \ + segment DATA \ + segment _DATA \ + segment BS3DATA16CONST \ + segment CONST \ + segment BS3DATA16CONST2 \ + segment CONST2 \ + segment STRINGS \ + segment BS3DATA16_END \ + clname BSS \ + segment BSS \ + segment _BSS \ + segment BS3DATA16_END \ + clname FAR_DATA \ + segment FAR_DATA \ +, \ + clname FAR_DATA \ + segment BS3DATA16 segaddr=0x2900 \ + segment FAR_DATA \ + segment BS3DATA16CONST \ + segment BS3DATA16CONST2 \ + segment BS3DATA16_DATA \ + segment BS3DATA16_END \ +) \ + segment BS3DATA32 \ + segment BS3DATA32CONST \ + segment BS3DATA32CONST2 \ + segment BS3DATA32_DATA \ + segment BS3DATA32_BSS \ + segment BS3DATA32_END \ + \ + segment BS3DATA64 \ + segment BS3DATA64CONST \ + segment BS3DATA64_BSS \ + segment BS3DATA64_END \ + clname BS3CLASS16RMCODE \ + segment BS3RMCODE16_START \ + segment BS3RMCODE16 \ + segment BS3RMCODE16_END \ + clname BS3CLASS16X0CODE \ + segment BS3X0CODE16_START \ + segment BS3X0CODE16 \ + segment BS3X0CODE16_END \ + clname BS3CLASS16X1CODE \ + segment BS3X1CODE16_START \ + segment BS3X1CODE16 \ + segment BS3X1CODE16_END \ + clname BS3CLASS32CODE \ + segment BS3TEXT32_START \ + segment BS3TEXT32 \ + segment BS3TEXT32_END \ + clname BS3CLASSSEPARATE32AND64BITCODE \ + segment BS3SEPARATE32AND64BITCODE \ + segment BS3SEPARATE32AND64BITCODE_END \ + clname BS3CLASS64CODE \ + segment BS3TEXT64_START \ + segment BS3TEXT64 \ + segment BS3TEXT64_END + +TEMPLATE_VBoxBS3KitImg_LNK_DEPS = \ + $(bs3-bootsector_1_TARGET) \ + $(VBoxBs3Linker_1_TARGET) +TEMPLATE_VBoxBS3KitImg_POST_CMDS = $(if $(eq $(tool_do),LINK_LIBRARY)\ + ,,$(QUIET)$(MV_EXT) -f -- "$(out)" "$(out).tmp" \ + $$(NLTAB)$(QUIET)$(VBoxBs3Linker_1_TARGET) -o $(out) $(bs3-bootsector_1_TARGET) $(out).tmp \ + $$(NLTAB)$(QUIET)$(RM_EXT) -f -- "$(out).tmp") \ + $(eval .PRECIOUS: $(outbase).map) # ugly hack! + + +TEMPLATE_VBoxBS3KitImg_LIBS = \ + $(PATH_OBJ)/bs3kit-common-16/bs3kit-common-16.lib \ + $(PATH_OBJ)/bs3kit-common-32/bs3kit-common-32.lib \ + $(PATH_OBJ)/bs3kit-common-64/bs3kit-common-64.lib \ + \ + $(PATH_OBJ)/bs3kit-rm/bs3kit-rm.lib \ + $(PATH_OBJ)/bs3kit-pe16/bs3kit-pe16.lib \ + $(PATH_OBJ)/bs3kit-pe16_32/bs3kit-pe16_32.lib \ + $(PATH_OBJ)/bs3kit-pe16_v86/bs3kit-pe16_v86.lib \ + $(PATH_OBJ)/bs3kit-pe32/bs3kit-pe32.lib \ + $(PATH_OBJ)/bs3kit-pe32_16/bs3kit-pe32_16.lib \ + $(PATH_OBJ)/bs3kit-pev86/bs3kit-pev86.lib \ + $(PATH_OBJ)/bs3kit-pp16/bs3kit-pp16.lib \ + $(PATH_OBJ)/bs3kit-pp16_32/bs3kit-pp16_32.lib \ + $(PATH_OBJ)/bs3kit-pp16_v86/bs3kit-pp16_v86.lib \ + $(PATH_OBJ)/bs3kit-pp32/bs3kit-pp32.lib \ + $(PATH_OBJ)/bs3kit-pp32_16/bs3kit-pp32_16.lib \ + $(PATH_OBJ)/bs3kit-ppv86/bs3kit-ppv86.lib \ + $(PATH_OBJ)/bs3kit-pae16/bs3kit-pae16.lib \ + $(PATH_OBJ)/bs3kit-pae16_32/bs3kit-pae16_32.lib \ + $(PATH_OBJ)/bs3kit-pae16_v86/bs3kit-pae16_v86.lib \ + $(PATH_OBJ)/bs3kit-pae32/bs3kit-pae32.lib \ + $(PATH_OBJ)/bs3kit-pae32_16/bs3kit-pae32_16.lib \ + $(PATH_OBJ)/bs3kit-paev86/bs3kit-paev86.lib \ + $(PATH_OBJ)/bs3kit-lm16/bs3kit-lm16.lib \ + $(PATH_OBJ)/bs3kit-lm32/bs3kit-lm32.lib \ + $(PATH_OBJ)/bs3kit-lm64/bs3kit-lm64.lib + +# BS3Kit template for 32-bit code. +TEMPLATE_VBoxBS3KitImg32 = Template for building BS3Kit test images. +TEMPLATE_VBoxBS3KitImg32_BLD_TRG = os-agnostic +TEMPLATE_VBoxBS3KitImg32_BLD_TRG_ARCH = x86 +TEMPLATE_VBoxBS3KitImg32_INSTTYPE = none +TEMPLATE_VBoxBS3KitImg32_ASTOOL = VBoxNasm +TEMPLATE_VBoxBS3KitImg32_ASOBJSUFF = .o32 +TEMPLATE_VBoxBS3KitImg32_ASFLAGS = -f obj -g $(BS3KIT_NASM_allow_64_bit) -w+orphan-labels +TEMPLATE_VBoxBS3KitImg32_ASDEFS = ASM_FORMAT_OMF RT_NOINC_SEGMENTS __NASM__ \ + BS3CLASS16CODE=$(BS3KIT_CLASS_CODE16) BS3KIT_CLASS_DATA16=$(BS3KIT_CLASS_DATA16) \ + BS3KIT_GRPNM_DATA16=$(BS3KIT_GRPNM_DATA16) BS3KIT_CLASS_BSS16=$(BS3KIT_CLASS_BSS16) +TEMPLATE_VBoxBS3KitImg32_DEFS = ARCH_BITS=32 IN_BS3KIT +TEMPLATE_VBoxBS3KitImg32_DEFS.debug = BS3_STRICT +TEMPLATE_VBoxBS3KitImg32_ARTOOL = OPENWATCOM +TEMPLATE_VBoxBS3KitImg32_CTOOL = Bs3Ow32 +TEMPLATE_VBoxBS3KitImg32_CXXTOOL = Bs3Ow32 +TEMPLATE_VBoxBS3KitImg32_CFLAGS = \ + -nt=BS3TEXT32 -nd=BS3DATA32 -nc=BS3CLASS32CODE -ecc -q -e125 -wx -zl -mf $(BS3_OW_DBG_OPT) -s -oa -ob -of -oi -ol -or -os -d+ +TEMPLATE_VBoxBS3KitImg32_CXXFLAGS = \ + -nt=BS3TEXT32 -nd=BS3DATA32 -nc=BS3CLASS32CODE -ecc -q -e125 -wx -zl -mf $(BS3_OW_DBG_OPT) -s -oa -ob -of -oi -ol -or -os -d+ +TEMPLATE_VBoxBS3KitImg32_INCS = $(VBOX_PATH_BS3KIT_SRC) . +TEMPLATE_VBoxBS3KitImg32_LDTOOL = VBoxBsUnusedLd + +# BS3Kit template for 64-bit code. +TEMPLATE_VBoxBS3KitImg64 = Template for building BS3Kit test images. +TEMPLATE_VBoxBS3KitImg64_BLD_TRG = os-agnostic +TEMPLATE_VBoxBS3KitImg64_BLD_TRG_ARCH = amd64 +TEMPLATE_VBoxBS3KitImg64_INSTTYPE = none +TEMPLATE_VBoxBS3KitImg64_ASTOOL = VBoxNasm +TEMPLATE_VBoxBS3KitImg64_ASOBJSUFF = .o64 +TEMPLATE_VBoxBS3KitImg64_ASFLAGS = -f obj -g $(BS3KIT_NASM_allow_64_bit) -w+orphan-labels +TEMPLATE_VBoxBS3KitImg64_ASDEFS = ASM_FORMAT_OMF ASM_CALL64_MSC RT_NOINC_SEGMENTS __NASM__ \ + BS3CLASS16CODE=$(BS3KIT_CLASS_CODE16) BS3KIT_CLASS_DATA16=$(BS3KIT_CLASS_DATA16) \ + BS3KIT_GRPNM_DATA16=$(BS3KIT_GRPNM_DATA16) BS3KIT_CLASS_BSS16=$(BS3KIT_CLASS_BSS16) +TEMPLATE_VBoxBS3KitImg64_DEFS = IN_BS3KIT ARCH_BITS=64 +TEMPLATE_VBoxBS3KitImg64_DEFS.debug = BS3_STRICT +TEMPLATE_VBoxBS3KitImg64_ARTOOL = OPENWATCOM +TEMPLATE_VBoxBS3KitImg64_INCS = $(VBOX_PATH_BS3KIT_SRC) . +if1of ($(KBUILD_HOST), win) + ifndef TOOL_VCC100AMD64 # For win.x86 builds. + include $(KBUILD_PATH)/tools/$(VBOX_VCC_TOOL_STEM)AMD64.kmk + endif + TEMPLATE_VBoxBS3KitImg64_CTOOL := Bs3Vcc64 + TEMPLATE_VBoxBS3KitImg64_CXXTOOL := Bs3Vcc64 + TEMPLATE_VBoxBS3KitImg64_CFLAGS = -Z7 -O1 -Oi -GF -GS- -Gy- -Gs65536 + TEMPLATE_VBoxBS3KitImg64_CXXFLAGS = -Z7 -O1 -Oi -GF -GS- -Gy- -Gs65536 +else + TEMPLATE_VBoxBS3KitImg64_CTOOL := Bs3Gcc64Elf64 + TEMPLATE_VBoxBS3KitImg64_CXXTOOL := Bs3Gcc64Elf64 + # Note! -mx32 would be exactly what we needed here, however it causes internal compiler errors with 4.8.4 on gentoo. + TEMPLATE_VBoxBS3KitImg64_CFLAGS = -m64 -maccumulate-outgoing-args -g -Os -fno-omit-frame-pointer $(VBOX_GCC_fno-stack-protector) $(VBOX_GCC_WARN_PEDANTIC_C) \ + -msoft-float -fno-exceptions -mno-sse -mno-mmx -mno-sse2 -mno-3dnow $(VBOX_GCC_fno-stack-protector) + TEMPLATE_VBoxBS3KitImg64_CXXFLAGS = -m64 -maccumulate-outgoing-args -g -Os -fno-omit-frame-pointer $(VBOX_GCC_fno-stack-protector) $(VBOX_GCC_WARN_PEDANTIC_CXX) \ + -msoft-float -fno-exceptions -mno-sse -mno-mmx -mno-sse2 -mno-3dnow $(VBOX_GCC_fno-stack-protector) +endif +TEMPLATE_VBoxBS3KitImg64_LDTOOL = VBoxBsUnusedLd + +# BS3Kit template for the bootsector. +TEMPLATE_VBoxBS3KitBS = Template for building BS3Kit test images. +TEMPLATE_VBoxBS3KitBS_BLD_TRG = os-agnostic +TEMPLATE_VBoxBS3KitBS_BLD_TRG_ARCH = x86 +TEMPLATE_VBoxBS3KitBS_INST = $(INST_VALIDATIONKIT)bootsectors/ +TEMPLATE_VBoxBS3KitBS_INSTTYPE = none +TEMPLATE_VBoxBS3KitBS_BINSUFF = .img +TEMPLATE_VBoxBS3KitBS_MODE = 0644 +TEMPLATE_VBoxBS3KitBS_ASTOOL = YASM +TEMPLATE_VBoxBS3KitBS_ASFLAGS = -f bin --mapfile +TEMPLATE_VBoxBS3KitBS_ASDEFS = ASM_FORMAT_BIN RT_NOINC_SEGMENTS ARCH_BITS=16 __YASM__ \ + BS3CLASS16CODE=$(BS3KIT_CLASS_CODE16) BS3KIT_CLASS_DATA16=$(BS3KIT_CLASS_DATA16) \ + BS3KIT_GRPNM_DATA16=$(BS3KIT_GRPNM_DATA16) BS3KIT_CLASS_BSS16=$(BS3KIT_CLASS_BSS16) +TEMPLATE_VBoxBS3KitBS_INCS = $(VBOX_PATH_BS3KIT_SRC) . +TEMPLATE_VBoxBS3KitBS_LDTOOL = VBoxBsCpLd + + + +# +# Extends VBoxBS3KitImg +# User must starts SOURCES with: $(VBOX_PATH_BS3KIT_SRC)/bs3-first-dosexe.asm +## disable 1014, 1080, 1150 +# +TEMPLATE_VBoxBS3KitUtil = Utility using bs3kit code. +TEMPLATE_VBoxBS3KitUtil_EXTENDS = VBoxBS3KitImg +TEMPLATE_VBoxBS3KitUtil_BINSUFF = .exe +TEMPLATE_VBoxBS3KitUtil_DEFS = $(TEMPLATE_VBoxBS3KitImg_DEFS) BS3_IS_DOS_EXE +TEMPLATE_VBoxBS3KitUtil_CFLAGS = $(filter-out -zl,$(TEMPLATE_VBoxBS3KitImg_CFLAGS)) +TEMPLATE_VBoxBS3KitUtil_CXXFLAGS = $(filter-out -zl,$(TEMPLATE_VBoxBS3KitImg_CXXFLAGS)) +TEMPLATE_VBoxBS3KitUtil_LDFLAGS = system dos \ + debug $(BS3_OW_DBG_LDOPT) all \ + option quiet, map, statics, verbose, symfile \ + disable 1080 \ + order \ + clname $(BS3KIT_CLASS_CODE16) \ + segment BEGTEXT \ + segment BS3TEXT16 \ + segment _TEXT \ + segment BS3TEXT16_NEARSTUBS \ + segment BS3TEXT16_FARSTUBS \ + segment BS3TEXT16_END \ + clname BS3SYSTEM16 \ + segment BS3SYSTEM16 \ + \ + clname BEGDATA \ + segment _NULL \ + segment _AFTERNULL \ + clname DATA \ +$(if-expr "$(BS3KIT_SEGNM_DATA16)" == "", \ + segment BS3DATA16 \ + segment BS3DATA16CONST \ + segment CONST \ + segment BS3DATA16CONST2 \ + segment CONST2 \ +,\ + segment CONST \ + segment CONST2 \ +) \ + segment _DATA \ + segment XIB \ + segment XI \ + segment XIE \ + segment YIB \ + segment YI \ + segment YIE \ + segment STRINGS \ +$(if-expr "$(BS3KIT_SEGNM_DATA16)" == "", \ + segment BS3DATA16_DATA \ +,) \ + segment DATA \ + clname BSS \ + segment _BSS \ + segment BSS \ + segment BS3DATA16_END \ + clname STACK \ + segment STACK \ + \ + clname FAR_DATA \ +$(if-expr "$(BS3KIT_SEGNM_DATA16)" != "", \ + segment BS3DATA16 \ + segment BS3DATA16_DATA \ + segment BS3DATA16CONST \ + segment BS3DATA16CONST2 \ + segment FAR_DATA \ + segment BS3DATA16_END \ +,\ + segment FAR_DATA \ +)\ + segment BS3DATA32 \ + segment BS3DATA32CONST \ + segment BS3DATA32CONST2 \ + segment BS3DATA32_DATA \ + segment BS3DATA32_BSS \ + segment BS3DATA32_END \ + \ + segment BS3DATA64 \ + segment BS3DATA64CONST \ + segment BS3DATA64_BSS \ + segment BS3DATA64_END \ + clname BS3CLASS16RMCODE \ + segment BS3RMCODE16_START \ + segment BS3RMCODE16 \ + segment BS3RMCODE16_END \ + clname BS3CLASS16X0CODE \ + segment BS3X0CODE16_START \ + segment BS3X0CODE16 \ + segment BS3X0CODE16_END \ + clname BS3CLASS16X1CODE \ + segment BS3X1CODE16_START \ + segment BS3X1CODE16 \ + segment BS3X1CODE16_END \ + clname BS3CLASS32CODE \ + segment BS3TEXT32 \ + segment BS3TEXT32_END \ + clname BS3CLASSSEPARATE32AND64BITCODE \ + segment BS3SEPARATE32AND64BITCODE \ + segment BS3SEPARATE32AND64BITCODE_END \ + clname BS3CLASS64CODE \ + segment BS3TEXT64 \ + segment BS3TEXT64_END +# clname BS3FLAT segaddr=0x0000 \ +# segment BS3FLAT segaddr=0x0000 + +TEMPLATE_VBoxBS3KitUtil_LNK_DEPS = $(NO_SUCH_VARIABLE) +TEMPLATE_VBoxBS3KitUtil_POST_CMDS = $(NO_SUCH_VARIABLE) + diff --git a/src/VBox/ValidationKit/bootsectors/Makefile.kmk b/src/VBox/ValidationKit/bootsectors/Makefile.kmk new file mode 100644 index 00000000..a9fd5214 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/Makefile.kmk @@ -0,0 +1,472 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Bootsector Tests for Test Drivers or standalone testing. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +# +# Make sure our Config.kmk gets included when kmk is running from a parent directory. +# +ifndef VBOX_BOOTSECTORS_CONFIG_KMK_INCLUDED + include $(PATH_SUB_CURRENT)/Config.kmk +endif + + +# +# Include sub-makefile. +# +# The VBOX_WITH_BS3KIT feature requires NASM 2.12 and either MSVC or gcc +# with ms_abi function attribute (gcc v4.4+, MSVC default). +# Some 32-bit gcc compilers come without 64-bit support (e.g. EL5). +# +if defined(VBOX_WITH_OPEN_WATCOM) + if1of ($(KBUILD_TARGET), win) + VBOX_WITH_BS3KIT = 1 + else if $(VBOX_GCC_VERSION_CC) >= 40400 # ms_abi was added in 4.4 + if1of ($(KBUILD_TARGET), linux) + ifneq ($(VBOX_GCC_m64),) + VBOX_WITH_BS3KIT = 1 + endif + endif + endif + ifdef VBOX_WITH_BS3KIT + include $(PATH_SUB_CURRENT)/bs3kit/Makefile.kmk + endif +endif + + +# +# Boot Sector "Linker" tool. +# +TOOL_VBoxBootSectorLd = Joins one or more BS2 object files into a floppy img. +TOOL_VBoxBootSectorLd_LINK_MISCBIN_OUTPUT = +TOOL_VBoxBootSectorLd_LINK_MISCBIN_DEPEND = +TOOL_VBoxBootSectorLd_LINK_MISCBIN_DEPORD = $(VBoxBs2Linker_1_TARGET) +define TOOL_VBoxBootSectorLd_LINK_MISCBIN_CMDS + $(VBoxBs2Linker_1_TARGET) -o $(out) $(objs) $(othersrc) +endef + +BLDPROGS += VBoxBs2Linker +VBoxBs2Linker_TEMPLATE = VBoxBldProg +VBoxBs2Linker_SOURCES = VBoxBs2Linker.cpp + + +# +# Makes a boot sector test image. +# +TEMPLATE_VBoxBsTestImg = kBuild tool config for building boot sector stuff. +TEMPLATE_VBoxBsTestImg_INST = $(INST_VALIDATIONKIT)bootsectors/ +TEMPLATE_VBoxBsTestImg_BINSUFF = .img +TEMPLATE_VBoxBsTestImg_MODE = 0644 +TEMPLATE_VBoxBsTestImg_ASTOOL = YASM +TEMPLATE_VBoxBsTestImg_ASFLAGS = -f bin -P $(VBOX_PATH_BOOTSECTORS_SRC)/bootsector2-first.mac $(VBOX_YASM_Wno-segreg-in-64bit) --mapfile +TEMPLATE_VBoxBsTestImg_ASDEFS = ASM_FORMAT_BIN +TEMPLATE_VBoxBsTestImg_INCS = \ + . \ + ../../VMM/testcase/Instructions +TEMPLATE_VBoxBsTestImg_LDTOOL = VBoxBootSectorLd + + +# +# The boot sector tests. +# +MISCBINS += bootsector-shutdown +bootsector-shutdown_TEMPLATE = VBoxBsTestImg +bootsector-shutdown_SOURCES = bootsector-shutdown.asm + +MISCBINS += bootsector-pae +bootsector-pae_TEMPLATE = VBoxBsTestImg +bootsector-pae_SOURCES = bootsector-pae.asm + +MISCBINS += bootsector-empty +bootsector-empty_TEMPLATE = VBoxBsTestImg +bootsector-empty_SOURCES = bootsector-empty.asm + +MISCBINS += bootsector2-test1 +bootsector2-test1_TEMPLATE = VBoxBsTestImg +bootsector2-test1_SOURCES = bootsector2-test1.asm + +MISCBINS += bootsector2-cpu-hidden-regs-1 +bootsector2-cpu-hidden-regs-1_TEMPLATE = VBoxBsTestImg +bootsector2-cpu-hidden-regs-1_SOURCES = bootsector2-cpu-hidden-regs-1.asm + +MISCBINS += bootsector2-cpu-instr-1 +bootsector2-cpu-instr-1_TEMPLATE = VBoxBsTestImg +bootsector2-cpu-instr-1_SOURCES = bootsector2-cpu-instr-1.asm + +MISCBINS += bootsector2-cpu-pf-1 +bootsector2-cpu-pf-1_TEMPLATE = VBoxBsTestImg +bootsector2-cpu-pf-1_SOURCES = bootsector2-cpu-pf-1.asm + +MISCBINS += bootsector2-cpu-xcpt-1 +bootsector2-cpu-xcpt-1_TEMPLATE = VBoxBsTestImg +bootsector2-cpu-xcpt-1_SOURCES = bootsector2-cpu-xcpt-1.asm + +MISCBINS += bootsector2-cpu-xcpt-2 +bootsector2-cpu-xcpt-2_TEMPLATE = VBoxBsTestImg +bootsector2-cpu-xcpt-2_SOURCES = bootsector2-cpu-xcpt-2.asm + +MISCBINS += bootsector2-cpu-a20-1 +bootsector2-cpu-a20-1_TEMPLATE = VBoxBsTestImg +bootsector2-cpu-a20-1_SOURCES = bootsector2-cpu-a20-1.asm + +MISCBINS += bootsector2-cpu-basic-1 +bootsector2-cpu-basic-1_TEMPLATE = VBoxBsTestImg +bootsector2-cpu-basic-1_SOURCES = bootsector2-cpu-basic-1.asm + +MISCBINS += bootsector2-cpu-ac-loop +bootsector2-cpu-ac-loop_TEMPLATE = VBoxBsTestImg +bootsector2-cpu-ac-loop_SOURCES = bootsector2-cpu-ac-loop.asm + +MISCBINS += bootsector2-cpu-db-loop +bootsector2-cpu-db-loop_TEMPLATE = VBoxBsTestImg +bootsector2-cpu-db-loop_SOURCES = bootsector2-cpu-db-loop.asm + +MISCBINS += bootsector2-boot-registers-1 +bootsector2-boot-registers-1_TEMPLATE = VBoxBsTestImg +bootsector2-boot-registers-1_SOURCES = bootsector2-boot-registers-1.asm + +MISCBINS += bootsector2-triple-fault-1 +bootsector2-triple-fault-1_TEMPLATE = VBoxBsTestImg +bootsector2-triple-fault-1_SOURCES = bootsector2-triple-fault-1.asm + + +ifeq ($(USERNAME),birdxx) + if1of ($(KBUILD_HOST).$(KBUILD_HOST_ARCH),win.amd64) +# +# Generated instruction tests (work in progress). +# + +VBOX_PATH_VBINSTST = $(PATH_ROOT)/src/VBox/VMM/testcase/Instructions +VBOX_VBINSTST_GEN = $(VBOX_PATH_VBINSTST)/InstructionTestGen.py +VBOX_BOOTSECTOR2_VBINSTST_AMD64_GEN = $(VBOX_BLD_PYTHON) $(VBOX_VBINSTST_GEN) \ + --split 3 --target bs2-r0-64 --output-base $(bootsectors_0_OUTDIR)/VBInsTst-64 --test-size tiny +VBOX_BOOTSECTOR2_VBINSTST_AMD64_FILES = $(shell $(VBOX_BOOTSECTOR2_VBINSTST_AMD64_GEN) --makefile-mode) + +#$$(bootsectors_0_OUTDIR)/VBInsTst.ts + $$(VBOX_BOOTSECTOR2_VBINSTST_AMD64_FILES): $(VBOX_VBINSTST_GEN) | $$(dir $$@) +# $(VBOX_BOOTSECTOR2_VBINSTST_AMD64_GEN) +# $(APPEND) -t $@ +# +#bootsectors_SOURCES += $(bootsectors_0_OUTDIR)/bootsector2-vbinstst-1.img +#bootsectors_CLEAN += $(VBOX_BOOTSECTOR2_VBINSTST_AMD64_FILES) +# +#$$(bootsectors_0_OUTDIR)/bootsector2-vbinstst-1.img: \ +# $(PATH_SUB_CURRENT)/bootsector2-vbinstst-64-1.asm \ +# $$(bootsectors_0_OUTDIR)/VBInsTst-64.asm +# $(TOOL_$(VBOX_ASTOOL)_AS) -f bin -D ASM_FORMAT_BIN -I $(dir $@) -I $(PATH_ROOT)/include -I $(VBOX_PATH_VBINSTST) -o $@ -L nasm -l $@.lst $< + +MISCBINS += bootsector2-vbinstst-kernel +bootsector2-vbinstst-kernel_TEMPLATE = VBoxBsTestImg +bootsector2-vbinstst-kernel_SOURCES = \ + bootsector2-vbinstst-kernel.asm + + +MISCBINS += bootsector2-vbinstst-64-1 +bootsector2-vbinstst-64-1_TEMPLATE = VBoxBsTestImg +bootsector2-vbinstst-64-1_DEFS = \ + BS2_BIG_IMAGE_LM64 \ + BS2_BIG_IMAGE_GEN_SOURCE_FILE=bs2-vbinstst-64-1.asm \ + BS2_BIG_IMAGE_GEN_TEST_NAME=\"bs2-vbinstst-64-1\" +bootsector2-vbinstst-64-1_INCS = $(bootsector2-vbinstst-64-1_0_OUTDIR)/ +bootsector2-vbinstst-64-1_SOURCES = \ + bootsector2-vbinstst-kernel.asm \ + bootsector2-vbinstst-big-template.asm +bootsector2-vbinstst-64-1_INTERMEDIATES = \ + $(bootsector2-vbinstst-64-1_0_OUTDIR)/bs2-vbinstst-64-1.asm +bootsector2-vbinstst-64-1_CLEAN = \ + $(bootsector2-vbinstst-64-1_0_OUTDIR)/bs2-vbinstst-64-1.asm + +$$(bootsector2-vbinstst-64-1_0_OUTDIR)/bs2-vbinstst-64-1.asm: $(VBOX_VBINSTST_GEN) | $$(dir $$@) + $(REDIRECT) -0 /dev/null -- $(VBOX_BLD_PYTHON) $(VBOX_VBINSTST_GEN) --target bs2-r0-64-big --output-base $(basename $@) --test-size medium + +MISCBINS += bootsector2-vbinstst-32-1 +bootsector2-vbinstst-32-1_TEMPLATE = VBoxBsTestImg +bootsector2-vbinstst-32-1_DEFS = \ + BS2_BIG_IMAGE_PP32 \ + BS2_BIG_IMAGE_GEN_SOURCE_FILE=bs2-vbinstst-32-1.asm \ + BS2_BIG_IMAGE_GEN_TEST_NAME=\"bs2-vbinstst-32-1\" +bootsector2-vbinstst-32-1_INCS = $(bootsector2-vbinstst-32-1_0_OUTDIR)/ +bootsector2-vbinstst-32-1_SOURCES = \ + bootsector2-vbinstst-kernel.asm \ + bootsector2-vbinstst-big-template.asm +bootsector2-vbinstst-32-1_INTERMEDIATES = \ + $(bootsector2-vbinstst-32-1_0_OUTDIR)/bs2-vbinstst-32-1.asm +bootsector2-vbinstst-32-1_CLEAN = \ + $(bootsector2-vbinstst-32-1_0_OUTDIR)/bs2-vbinstst-32-1.asm + +$$(bootsector2-vbinstst-32-1_0_OUTDIR)/bs2-vbinstst-32-1.asm: $(VBOX_VBINSTST_GEN) | $$(dir $$@) + $(REDIRECT) -0 /dev/null -- $(VBOX_BLD_PYTHON) $(VBOX_VBINSTST_GEN) --target bs2-r0-32-big --output-base $(basename $@) --test-size medium + + endif +endif # bird-only + + +ifdef VBOX_WITH_BS3KIT +# +# Bs3kit +# + +# +# APIC +# +MISCBINS += bs3-apic-1 +bs3-apic-1_TEMPLATE = VBoxBS3KitImg +bs3-apic-1_SOURCES = \ + bs3kit/bs3-first-rm.asm \ + bs3-apic-1.c \ + bs3-apic-1-32.c32 + + +# CPU basics #2 (first being bootsector2-cpu-basic-1). +MISCBINS += bs3-cpu-basic-2 +bs3-cpu-basic-2_TEMPLATE = VBoxBS3KitImg +bs3-cpu-basic-2_INCS = . +bs3-cpu-basic-2_DEFS = BS3_CMN_INSTANTIATE_FILE1=bs3-cpu-basic-2-template.c +bs3-cpu-basic-2_DEFS += BS3_MODE_INSTANTIATE_FILE1=bs3-cpu-basic-2-template.c +bs3-cpu-basic-2_SOURCES = \ + bs3kit/bs3-first-rm.asm \ + bs3-cpu-basic-2.c \ + bs3-cpu-basic-2-x0.c \ + bs3-cpu-basic-2-32.c32 \ + bs3-cpu-basic-2-pf.c32 \ + bs3-cpu-basic-2-asm.asm \ + bs3kit/bs3-cmn-instantiate-x0.c16 \ + bs3kit/bs3-cmn-instantiate.c32 \ + bs3kit/bs3-cmn-instantiate.c64 +bs3-cpu-basic-2-template.o:: \ + $$(bs3-cpu-basic-2_0_OUTDIR)/bs3kit/bs3-cmn-instantiate-x0.o16 \ + $$(bs3-cpu-basic-2_0_OUTDIR)/bs3kit/bs3-cmn-instantiate.o32 \ + $$(bs3-cpu-basic-2_0_OUTDIR)/bs3kit/bs3-cmn-instantiate.o64 \ + $$(bs3-cpu-basic-2_0_OUTDIR)/bs3-cpu-basic-2-asm.o16 + +# +# CPU weird stuff #1. +# +MISCBINS += bs3-cpu-weird-1 +bs3-cpu-weird-1_TEMPLATE = VBoxBS3KitImg +bs3-cpu-weird-1_INCS = . +bs3-cpu-weird-1_SOURCES = \ + bs3kit/bs3-first-rm.asm \ + bs3-cpu-weird-1.c \ + bs3-cpu-weird-1-x0.c \ + bs3-cpu-weird-1-asm.asm + +# +# 64-bit CPU state #1. +# +MISCBINS += bs3-cpu-state64-1 +bs3-cpu-state64-1_TEMPLATE = VBoxBS3KitImg +bs3-cpu-state64-1_INCS = . +bs3-cpu-state64-1_SOURCES = \ + bs3kit/bs3-first-init-all-lm64.asm \ + bs3-cpu-state64-1.c64 \ + bs3-cpu-state64-1-asm.asm + +# +# FPU state corruption checker. +# +MISCBINS += bs3-fpustate-1 +bs3-fpustate-1_TEMPLATE = VBoxBS3KitImg +bs3-fpustate-1_INCS = . +bs3-fpustate-1_DEFS = BS3_CMN_INSTANTIATE_FILE1=bs3-fpustate-1-template.c +bs3-fpustate-1_DEFS += BS3_MODE_INSTANTIATE_FILE1=bs3-fpustate-1-template.c +bs3-fpustate-1_SOURCES = \ + bs3kit/bs3-first-rm.asm \ + bs3-fpustate-1.c \ + bs3kit/bs3-cmn-instantiate.c16 \ + bs3kit/bs3-cmn-instantiate.c32 \ + bs3kit/bs3-cmn-instantiate.c64 \ + bs3-fpustate-1-asm.asm +bs3-fpustate-1-template.o:: \ + $$(bs3-fpustate-1_0_OUTDIR)/bs3kit/bs3-cmn-instantiate.o16 \ + $$(bs3-fpustate-1_0_OUTDIR)/bs3kit/bs3-cmn-instantiate.o32 \ + $$(bs3-fpustate-1_0_OUTDIR)/bs3kit/bs3-cmn-instantiate.o64 \ + $$(bs3-fpustate-1_0_OUTDIR)/bs3-fpustate-1-asm.o16 + +# +# CPU instruction decoding experiments. +# +MISCBINS += bs3-cpu-decoding-1 +bs3-cpu-decoding-1_TEMPLATE = VBoxBS3KitImg +bs3-cpu-decoding-1_INCS = . +bs3-cpu-decoding-1_DEFS = BS3_CMN_INSTANTIATE_FILE1=bs3-cpu-decoding-1-template.c +bs3-cpu-decoding-1_DEFS += BS3_MODE_INSTANTIATE_FILE1=bs3-cpu-decoding-1-template.c +bs3-cpu-decoding-1_SOURCES = \ + bs3kit/bs3-first-init-all-pp32.asm \ + bs3-cpu-decoding-1.c32 \ + bs3-cpu-decoding-1-asm.asm +# bs3kit/bs3-cmn-instantiate.c16 \ +# bs3kit/bs3-cmn-instantiate.c32 \ +# bs3kit/bs3-cmn-instantiate.c64 +bs3-cpu-decoding-1-template.o:: \ + $$(bs3-cpu-decoding-1_0_OUTDIR)/bs3-cpu-decoding-1-asm.o16 +# $$(bs3-cpu-decoding-1_0_OUTDIR)/bs3kit/bs3-cmn-instantiate.o16 \ +# $$(bs3-cpu-decoding-1_0_OUTDIR)/bs3kit/bs3-cmn-instantiate.o32 \ +# $$(bs3-cpu-decoding-1_0_OUTDIR)/bs3kit/bs3-cmn-instantiate.o64 \ + + +# +# CPU instructions #2 (first being bootsector2-cpu-instr-1). +# +MISCBINS += bs3-cpu-instr-2 +bs3-cpu-instr-2_TEMPLATE = VBoxBS3KitImg +bs3-cpu-instr-2_INCS = . +bs3-cpu-instr-2_DEFS = BS3_CMN_INSTANTIATE_FILE1=bs3-cpu-instr-2-template.c +bs3-cpu-instr-2_DEFS += BS3_MODE_INSTANTIATE_FILE1=bs3-cpu-instr-2-template.c +bs3-cpu-instr-2_SOURCES = \ + bs3kit/bs3-first-rm.asm \ + bs3-cpu-instr-2.c \ + bs3-cpu-instr-2-asm.asm \ + bs3kit/bs3-cmn-instantiate-x0.c16 \ + bs3kit/bs3-cmn-instantiate.c32 \ + bs3kit/bs3-cmn-instantiate.c64 +bs3-cpu-instr-2-template.o:: \ + $$(bs3-cpu-instr-2_0_OUTDIR)/bs3kit/bs3-cmn-instantiate-x0.o16 \ + $$(bs3-cpu-instr-2_0_OUTDIR)/bs3kit/bs3-cmn-instantiate.o32 \ + $$(bs3-cpu-instr-2_0_OUTDIR)/bs3kit/bs3-cmn-instantiate.o64 \ + $$(bs3-cpu-instr-2_0_OUTDIR)/bs3-cpu-instr-2-asm.o16 + +# +# CPU instructions #3 - SSE, ++. +# +MISCBINS += bs3-cpu-instr-3 +bs3-cpu-instr-3_TEMPLATE = VBoxBS3KitImg +bs3-cpu-instr-3_INCS = . +bs3-cpu-instr-3_SOURCES = \ + bs3kit/bs3-first-init-all-pe32.asm \ + bs3-cpu-instr-3.c32 \ + bs3-cpu-instr-3-asm.asm +bs3-cpu-instr-3-template.o:: \ + $$(bs3-cpu-instr-3_0_OUTDIR)/bs3-cpu-instr-3-asm.o16 + +# +# CPU generated instruction tests #1 +# +MISCBINS += bs3-cpu-generated-1 +bs3-cpu-generated-1_TEMPLATE = VBoxBS3KitImg +bs3-cpu-generated-1_INCS = . +bs3-cpu-generated-1_DEFS = BS3_CMN_INSTANTIATE_FILE1=bs3-cpu-generated-1-template.c +bs3-cpu-generated-1_SOURCES = \ + bs3kit/bs3-first-rm.asm \ + bs3-cpu-generated-1.c \ + bs3-cpu-generated-1-asm.asm \ + bs3kit/bs3-cmn-instantiate-x0.c16 \ + bs3kit/bs3-cmn-instantiate.c32 \ + bs3kit/bs3-cmn-instantiate.c64 \ + $(bs3-cpu-generated-1_0_OUTDIR)/bs3-cpu-generated-1-data.c16 +bs3-cpu-generated-1_CLEAN = $(bs3-cpu-generated-1_0_OUTDIR)/bs3-cpu-generated-1-data.c16 + +bs3-cpu-generated-1-template.o:: \ + $$(bs3-cpu-generated-1_0_OUTDIR)/bs3kit/bs3-cmn-instantiate-x0.o16 \ + $$(bs3-cpu-generated-1_0_OUTDIR)/bs3kit/bs3-cmn-instantiate.o32 \ + $$(bs3-cpu-generated-1_0_OUTDIR)/bs3kit/bs3-cmn-instantiate.o64 \ + $$(bs3-cpu-generated-1_0_OUTDIR)/bs3-cpu-generated-1-asm.o16 + +$$(bs3-cpu-generated-1_0_OUTDIR)/bs3-cpu-generated-1-data.c16: \ + $(PATH_SUB_CURRENT)/bs3-cpu-generated-1-data.py \ + $(PATH_SUB_CURRENT)/../../VMM/VMMAll/IEMAllInstructionsPython.py \ + $(PATH_SUB_CURRENT)/../../VMM/VMMAll/IEMAllInstructions*.cpp.h \ + | $$(dir $$@) + $(REDIRECT) -0 /dev/null -- $(VBOX_BLD_PYTHON) $< $@ + +# +# Memory allocation. +# +MISCBINS += bs3-memalloc-1 +bs3-memalloc-1_TEMPLATE = VBoxBS3KitImg +bs3-memalloc-1_INCS = . +bs3-memalloc-1_SOURCES = \ + bs3kit/bs3-first-init-all-lm64.asm \ + bs3-memalloc-1.c64 + + +# +# Timer Interrupts +# +MISCBINS += bs3-timers-1 +bs3-timers-1_TEMPLATE = VBoxBS3KitImg +bs3-timers-1_INCS = . +bs3-timers-1_DEFS = BS3_CMN_INSTANTIATE_FILE1=bs3-timers-1-template.c +bs3-timers-1_DEFS += BS3_MODE_INSTANTIATE_FILE1=bs3-timers-1-template.c +bs3-timers-1_SOURCES = \ + bs3kit/bs3-first-rm.asm \ + bs3-timers-1.c \ + bs3-timers-1-x0.c + +# +# Timing +# +MISCBINS += bs3-timing-1 +bs3-timing-1_TEMPLATE = VBoxBS3KitImg +bs3-timing-1_INCS = . +bs3-timing-1_DEFS = BS3_CMN_INSTANTIATE_FILE1=bs3-timing-1-template.c +bs3-timing-1_DEFS += BS3_MODE_INSTANTIATE_FILE1=bs3-timing-1-template.c +bs3-timing-1_SOURCES = \ + bs3kit/bs3-first-rm.asm \ + bs3-timing-1.c \ + bs3-timing-1-32.c32 + +# +# Lock contention and interruption. +# +MISCBINS += bs3-locking-1 +bs3-locking-1_TEMPLATE = VBoxBS3KitImg +bs3-locking-1_INCS = . +bs3-locking-1_SOURCES = \ + bs3kit/bs3-first-rm.asm \ + bs3-locking-1.c + +endif # VBOX_WITH_BS3KIT + + +# +# Executable version of the bs3-timing-1 bootsector. +# +PROGRAMS += bs3-timing-1-exe +bs3-timing-1-exe_TEMPLATE = VBoxValidationKitR3 +bs3-timing-1-exe_NAME = bs3-timing-1 +bs3-timing-1-exe_SOURCES = bs3-timing-1-exe.c + + +# +# pylint +# +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(wildcard $(PATH_SUB_CURRENT)/*.py) +$(evalcall def_vbox_validationkit_process_python_sources) + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/bootsectors/VBoxBs2Linker.cpp b/src/VBox/ValidationKit/bootsectors/VBoxBs2Linker.cpp new file mode 100644 index 00000000..ed54cede --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/VBoxBs2Linker.cpp @@ -0,0 +1,229 @@ +/* $Id: VBoxBs2Linker.cpp $ */ +/** @file + * VirtualBox Validation Kit - Boot Sector 2 "linker". + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <iprt/types.h> + + +int main(int argc, char **argv) +{ + const char *pszOutput = NULL; + const char **papszInputs = (const char **)calloc(argc, sizeof(const char *)); + unsigned cInputs = 0; + + /* + * Scan the arguments. + */ + for (int i = 1; i < argc; i++) + { + if (argv[i][0] == '-') + { + const char *pszOpt = &argv[i][1]; + if (*pszOpt == '-') + { + /* Convert long options to short ones. */ + pszOpt--; + if (!strcmp(pszOpt, "--output")) + pszOpt = "o"; + else if (!strcmp(pszOpt, "--version")) + pszOpt = "V"; + else if (!strcmp(pszOpt, "--help")) + pszOpt = "h"; + else + { + fprintf(stderr, "syntax errro: Unknown options '%s'\n", pszOpt); + free(papszInputs); + return 2; + } + } + + /* Process the list of short options. */ + while (*pszOpt) + { + switch (*pszOpt++) + { + case 'o': + { + const char *pszValue = pszOpt; + pszOpt = strchr(pszOpt, '\0'); + if (*pszValue == '=') + pszValue++; + else if (!*pszValue) + { + if (i + 1 >= argc) + { + fprintf(stderr, "syntax error: The --output option expects a filename.\n"); + free(papszInputs); + return 12; + } + pszValue = argv[++i]; + } + if (pszOutput) + { + fprintf(stderr, "Only one output file is allowed. You've specified '%s' and '%s'\n", + pszOutput, pszValue); + free(papszInputs); + return 2; + } + pszOutput = pszValue; + pszOpt = ""; + break; + } + + case 'V': + printf("%s\n", "$Revision: 153224 $"); + free(papszInputs); + return 0; + + case '?': + case 'h': + printf("usage: %s [options] -o <output> <input1> [input2 ... [inputN]]\n", argv[0]); + free(papszInputs); + return 0; + } + } + } + else + papszInputs[cInputs++] = argv[i]; + } + + if (!pszOutput) + { + fprintf(stderr, "syntax error: No output file was specified (-o or --output).\n"); + free(papszInputs); + return 2; + } + if (cInputs == 0) + { + fprintf(stderr, "syntax error: No input files was specified.\n"); + free(papszInputs); + return 2; + } + + + /* + * Do the job. + */ + /* Open the output file. */ +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) + FILE *pOutput = fopen(pszOutput, "wb"); +#else + FILE *pOutput = fopen(pszOutput, "w"); +#endif + if (!pOutput) + { + fprintf(stderr, "error: Failed to open output file '%s' for writing\n", pszOutput); + free(papszInputs); + return 1; + } + + /* Copy the input files to the output file, with sector padding applied. */ + int rcExit = 0; + size_t off = 0; + for (unsigned i = 0; i < cInputs && rcExit == 0; i++) + { +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) + FILE *pInput = fopen(papszInputs[i], "rb"); +#else + FILE *pInput = fopen(papszInputs[i], "r"); +#endif + if (pInput) + { + for (;;) + { + /* Read a block from the input file. */ + uint8_t abBuf[4096]; + size_t cbRead = fread(abBuf, sizeof(uint8_t), 4096, pInput); + if (!cbRead || ferror(pInput)) + break; + + /* Padd the end of the file if necessary. */ + if (cbRead != 4096 && !feof(pInput)) + { + fprintf(stderr, "error: fread returned %u bytes, but we're not at the end of the file yet...\n", + (unsigned)cbRead); + rcExit = 1; + break; + } + if ((cbRead & 0x1ff) != 0) + { + memset(&abBuf[cbRead], 0, 4096 - cbRead); + cbRead = (cbRead + 0x1ff) & ~0x1ffU; + } + + /* Write the block to the output file. */ + if (fwrite(abBuf, sizeof(uint8_t), cbRead, pOutput) == cbRead) + off += cbRead; + else + { + fprintf(stderr, "error: fwrite failed\n"); + rcExit = 1; + break; + } + } + + if (ferror(pInput)) + { + fprintf(stderr, "error: Error reading '%s'.\n", papszInputs[i]); + rcExit = 1; + } + fclose(pInput); + } + else + { + fprintf(stderr, "error: Failed to open '%s' for reading.\n", papszInputs[i]); + rcExit = 1; + } + } + + /* Finally, close the output file (can fail because of buffered data). */ + if (fclose(stderr) != 0) + { + fprintf(stderr, "error: Error closing '%s'.\n", pszOutput); + rcExit = 1; + } + + fclose(pOutput); + free(papszInputs); + return rcExit; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector-empty.asm b/src/VBox/ValidationKit/bootsectors/bootsector-empty.asm new file mode 100644 index 00000000..be650efc --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector-empty.asm @@ -0,0 +1,70 @@ +; $Id: bootsector-empty.asm $ +;; @file +; Empty bootsector can be used as example +; + +; +; Copyright (C) 2012-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" + + +;; The boot sector load address. +%define BS_ADDR 0x7c00 +%define PDP_ADDR 0x9000 +%define PD_ADDR 0xa000 + + +BITS 16 +start: + ; Start with a jump just to follow the convention. + jmp short the_code + nop +times 3ah db 0 + +the_code: + ; put the code here + + + +hlt_again: + hlt + cli + jmp hlt_again + + ; + ; The GDT. + ; +padding: +times 510 - (padding - start) db 0 + db 055h, 0aah + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector-pae.asm b/src/VBox/ValidationKit/bootsectors/bootsector-pae.asm new file mode 100644 index 00000000..ae342ba5 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector-pae.asm @@ -0,0 +1,175 @@ +; $Id: bootsector-pae.asm $ +;; @file +; Bootsector that switches the CPU info PAE mode. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" +%include "VBox/bios.mac" + + +;; The boot sector load address. +%define BS_ADDR 0x7c00 +%define PDP_ADDR 0x9000 +%define PD_ADDR 0xa000 + + +BITS 16 +start: + ; Start with a jump just to follow the convention. + jmp short the_code + nop +times 3ah db 0 + +the_code: + cli + xor edx, edx + mov ds, dx ; Use 0 based addresses + + ; + ; Create a paging hierarchy + ; + mov cx, 4 + xor esi, esi ; physical address + mov ebx, PDP_ADDR + mov edi, PD_ADDR +pdptr_loop: + ; The page directory pointer entry. + mov dword [ebx], edi + or word [bx], X86_PDPE_P + mov dword [ebx + 4], edx + + ; The page directory. +pd_loop: + mov dword [edi], esi + or word [di], X86_PDE4M_P | X86_PDE4M_RW | X86_PDE4M_PS + mov dword [edi + 4], 0 + add esi, 0x00200000 ; 2MB + add di, 8 + test di, 0fffh + jnz pd_loop + + add bx, 8 + loop pdptr_loop + + ; + ; Switch to protected mode. + ; + lgdt [(gdtr - start) + BS_ADDR] + lidt [(idtr_null - start) + BS_ADDR] + + mov eax, PDP_ADDR + mov cr3, eax + + mov eax, cr4 + or eax, X86_CR4_PAE | X86_CR4_PSE + mov cr4, eax + + mov eax, cr0 + or eax, X86_CR0_PE | X86_CR0_PG + mov cr0, eax + jmp far 0x0008:((code32_start - start) + BS_ADDR) ; 8=32-bit CS + +BITS 32 +code32_start: + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ax, 0x18 + mov es, ax + mov esp, 0x80000 + + ; eye catchers + mov eax, 0xCafeBabe + mov ebx, eax + mov ecx, eax + mov edx, eax + mov edi, eax + mov esi, eax + mov ebp, eax + + ; + ; Boch shutdown request. + ; + mov bl, 64 + mov dx, VBOX_BIOS_SHUTDOWN_PORT + mov ax, VBOX_BIOS_OLD_SHUTDOWN_PORT +retry: + mov ecx, 8 + mov esi, (szShutdown - start) + BS_ADDR + rep outsb + xchg dx, ax ; alternate between the new (VBox) and old (Bochs) ports. + dec bl + jnz retry + ; Shutdown failed! +hlt_again: + hlt + cli + jmp hlt_again + + ; + ; The GDT. + ; +align 8, db 0 +gdt: + dw 0, 0, 0, 0 ; null selector + dw 0xffff, 0, 0x9b00, 0x00cf ; 32 bit flat code segment (0x08) + dw 0xffff, 0, 0x9300, 0x00cf ; 32 bit flat data segment (0x10) + dw 0xffff, 0, 0x9300, 0x00cf ; 32 bit flat stack segment (0x18) + +gdtr: + dw 8*4-1 ; limit 15:00 + dw (gdt - start) + BS_ADDR ; base 15:00 + db 0 ; base 23:16 + db 0 ; unused + +idtr_null: + dw 0 ; limit 15:00 + dw (gdt - start) + BS_ADDR ; base 15:00 + db 0 ; base 23:16 + db 0 ; unused + +szShutdown: + db 'Shutdown', 0 + + ; + ; Padd the remainder of the sector with zeros and + ; end it with the dos signature. + ; +padding: +times 510 - (padding - start) db 0 + db 055h, 0aah + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector-shutdown.asm b/src/VBox/ValidationKit/bootsectors/bootsector-shutdown.asm new file mode 100644 index 00000000..63094698 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector-shutdown.asm @@ -0,0 +1,90 @@ +; $Id: bootsector-shutdown.asm $ +;; @file +; Bootsector for grub chainloading that shutdowns the VM. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "VBox/bios.mac" + + +BITS 16 +start: + ; Start with a jump just to follow the convention. + jmp short the_code + nop +times 3ah db 0 + +the_code: + cli + + ; + ; VBox/Bochs shutdown request - write "Shutdown" byte by byte to shutdown port. + ; + mov cx, 64 + mov dx, VBOX_BIOS_SHUTDOWN_PORT + mov bx, VBOX_BIOS_OLD_SHUTDOWN_PORT +retry: + mov al, 'S' + out dx, al + mov al, 'h' + out dx, al + mov al, 'u' + out dx, al + mov al, 't' + out dx, al + mov al, 'd' + out dx, al + mov al, 'o' + out dx, al + mov al, 'w' + out dx, al + mov al, 'n' + out dx, al + xchg dx, bx ; alternate between the new (VBox) and old (Bochs) ports. + loop retry + + ; + ; Shutdown failed! + ; + + ;; @todo print some message before halting. + hlt + + ; + ; Padd the remainder of the sector with zeros and + ; end it with the dos signature. + ; +padding: +times 510 - (padding - start) db 0 + db 055h, 0aah + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-api.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-api.mac new file mode 100644 index 00000000..4bf26881 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-api.mac @@ -0,0 +1,162 @@ +; $Id: bootsector2-api.mac $ +;; @file +; Bootsector2 API definition for use by split images (kernel < 1MB < image). +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%ifndef ___bootsector2_api_mac +%define ___bootsector2_api_mac + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" +%include "bootsector2-structures.mac" + +;; +; The load address for big images. +%define BS2_BIG_LOAD_ADDR 0x120000 + + +;; +; API Template for lazy birds. +; +%macro BS2_API_TEMPLATE 0, + + BS2_API_TEMPLATE_ACTION(Bs2DisableA20) + BS2_API_TEMPLATE_ACTION(Bs2DisableA20ViaKbd) + BS2_API_TEMPLATE_ACTION(Bs2DisableA20ViaPortA) + BS2_API_TEMPLATE_ACTION(Bs2DisableNX) + BS2_API_TEMPLATE_ACTION(Bs2EnableA20) + BS2_API_TEMPLATE_ACTION(Bs2EnableA20ViaKbd) + BS2_API_TEMPLATE_ACTION(Bs2EnableA20ViaPortA) + BS2_API_TEMPLATE_ACTION(Bs2EnableNX) + BS2_API_TEMPLATE_ACTION(Bs2IsNXSupported) + BS2_API_TEMPLATE_ACTION(Bs2KbdRead) + BS2_API_TEMPLATE_ACTION(Bs2KbdWait) + BS2_API_TEMPLATE_ACTION(Bs2KbdWrite) + BS2_API_TEMPLATE_ACTION(Bs2PanicIfVMMDevTestingIsMissing) + BS2_API_TEMPLATE_ACTION(Bs2SetupNX) + BS2_API_TEMPLATE_ACTION(CalcBenchmarkIterations) + BS2_API_TEMPLATE_ACTION(CalcTestPerSecond) + BS2_API_TEMPLATE_ACTION(GetElapsedNanoTS) + BS2_API_TEMPLATE_ACTION(GetNanoTS) + BS2_API_TEMPLATE_ACTION(PrintChr) + BS2_API_TEMPLATE_ACTION(PrintF) + BS2_API_TEMPLATE_ACTION(PrintStr) + BS2_API_TEMPLATE_ACTION(PrintStrColonSpaces) + BS2_API_TEMPLATE_ACTION(PrintStrSpacesColonSpace) + BS2_API_TEMPLATE_ACTION(PrintU32) + BS2_API_TEMPLATE_ACTION(ReportResult) + BS2_API_TEMPLATE_ACTION(Shutdown) + BS2_API_TEMPLATE_ACTION(StrFormatF) + BS2_API_TEMPLATE_ACTION(StrFormatV) + BS2_API_TEMPLATE_ACTION(StrLen) + BS2_API_TEMPLATE_ACTION(TestCheckTrap) + BS2_API_TEMPLATE_ACTION(TestDumpCurrentRegisters) + BS2_API_TEMPLATE_ACTION(TestDumpRegisters) + BS2_API_TEMPLATE_ACTION(TestFailed) + BS2_API_TEMPLATE_ACTION(TestFailedF) + BS2_API_TEMPLATE_ACTION(TestInit) + BS2_API_TEMPLATE_ACTION(TestRestoreRegisters) + BS2_API_TEMPLATE_ACTION(TestSaveRegisters) + BS2_API_TEMPLATE_ACTION(testSendStrCmd) + BS2_API_TEMPLATE_ACTION(TestSkipped) + BS2_API_TEMPLATE_ACTION(TestSub) + BS2_API_TEMPLATE_ACTION(testSubCleanup) + BS2_API_TEMPLATE_ACTION(TestSubDone) + BS2_API_TEMPLATE_ACTION(TestSubErrorCount) + BS2_API_TEMPLATE_ACTION(TestTerm) + BS2_API_TEMPLATE_ACTION(TestValueReg) + BS2_API_TEMPLATE_ACTION(TestValueU32) + BS2_API_TEMPLATE_ACTION(TestInstallTrapRecs) + +%endmacro + + +;; +; This defines the API pointers. +; +ABSOLUTE 0x500 +;; Start the structure with a magic number. +NAME(g_u32Bs2ApiMagic): resd 1 +;; And a version number. +NAME(g_u32Bs2ApiVersion): resd 1 + +; The real mode and v8086 mode entry points (far pointers). +%undef BS2_API_TEMPLATE_ACTION +%define BS2_API_TEMPLATE_ACTION(a_Name) NAME(g_pfn %+ a_Name %+ _r86): resd 1 +BS2_API_TEMPLATE + +; The 16-bit protected mode entry points (far pointers). +%undef BS2_API_TEMPLATE_ACTION +%define BS2_API_TEMPLATE_ACTION(a_Name) NAME(g_pfn %+ a_Name %+ _p16): resd 1 +BS2_API_TEMPLATE + +; The 32-bit protected mode entry points (flat pointers). +%undef BS2_API_TEMPLATE_ACTION +%define BS2_API_TEMPLATE_ACTION(a_Name) NAME(g_pfn %+ a_Name %+ _p32): resd 1 +BS2_API_TEMPLATE + +; The 64-bit protected (long) mode entry points. +%undef BS2_API_TEMPLATE_ACTION +%define BS2_API_TEMPLATE_ACTION(a_Name) NAME(g_pfn %+ a_Name %+ _p64): resq 1 +BS2_API_TEMPLATE + +;; End the structure with a magic so it's integrity can be verified. +NAME(g_u32Bs2ApiEndMagic) resd 1 + +;; +; The current API magic value (Douglas Carl Engelbart). +%define BS2_API_MAGIC 0x19250130 + +;; +; The current API version. +%define BS2_API_VERSION 0x00010000 + +BEGINCODE + + +;; @name Service trap vector interface. +; @{ +%define BS2_SYSCALL_TO_RING0 0 +%define BS2_SYSCALL_TO_RING1 1 +%define BS2_SYSCALL_TO_RING2 2 +%define BS2_SYSCALL_TO_RING3 3 + +;; The service vector. +%define BS2_TRAP_SYSCALL 20h +;; @} + +%endif diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-boot-registers-1.asm b/src/VBox/ValidationKit/bootsectors/bootsector2-boot-registers-1.asm new file mode 100644 index 00000000..35d14d96 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-boot-registers-1.asm @@ -0,0 +1,86 @@ +; $Id: bootsector2-boot-registers-1.asm $ +;; @file +; Bootsector that prints the register status at boot. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" +%include "VBox/VMMDevTesting.mac" + +; +; Initialize in real mode, preserving the registers. +; +%define BS2_INIT_RM +%define BS2_INIT_SAVE_REGS +%include "bootsector2-common-init-code.mac" + +main: + mov ax, BS2_REG_SAVE_ADDR + call NAME(TestDumpRegisters_r86) + + xor ax, ax + mov al, [es:di] + push ax + mov al, [es:di + 1] + push ax + mov al, [es:di + 2] + push ax + mov al, [es:di + 3] + push ax + push ds + push .s_szPnpFmt1 + call NAME(PrintF_r86) + pop ax + push .s_szPnpFmt2 + call NAME(PrintF_r86) + pop ax + push .s_szPnpFmt3 + call NAME(PrintF_r86) + + call NAME(Bs2Panic) + +.s_szPnpFmt1: + db 'es:di -> %RX8 %RX8 %RX8 %RX8 ',0 +.s_szPnpFmt2: + db '%c%c%c%c', 0 +.s_szPnpFmt3: + db 13, 10, 0 + +; +; Pad the image so it loads cleanly. +; +BS2_PAD_IMAGE +the_end: + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-common-end.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-common-end.mac new file mode 100644 index 00000000..934fb2a2 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-common-end.mac @@ -0,0 +1,52 @@ +; $Id: bootsector2-common-end.mac $ +;; @file +; Boot sector 2 - End of code. +; +; @note Don't forget to cinldue bootsector2-common-traprec-end.mac! +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +; +; Terminate the trap records if opened. +; +%ifdef BS2_WITH_TRAPRECS + BS2_TRAP_RECS_END +%endif + +; +; Pad the image so it loads cleanly. +; +BEGINEND +the_end: + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-common-init-code.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-common-init-code.mac new file mode 100644 index 00000000..1e090648 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-common-init-code.mac @@ -0,0 +1,2313 @@ +; $Id: bootsector2-common-init-code.mac $ +;; @file +; Common bootsector code init. +; +; In addition to initialize the stack at %7bf0 it loads the first 512KB of the +; floppy image at %7c00. The control is handed over with interrupts disabled +; to a 'main' function defined by the includer. +; +; The following defines controls the mode we call main in: +; - BS2_INIT_RM (default) +; - BS2_INIT_PE32 +; - BS2_INIT_PP32 +; - BS2_INIT_PAE32 +; - BS2_INIT_LM64 +; +; The following defines controls code inclusion: +; - BS2_INC_RM +; - BS2_INC_PE +; - BS2_INC_PE16 +; - BS2_INC_PE32 +; - BS2_INC_PEV86 +; - BS2_INC_PP +; - BS2_INC_PP16 +; - BS2_INC_PP32 +; - BS2_INC_PPV86 +; - BS2_INC_PAE +; - BS2_INC_PAE16 +; - BS2_INC_PAE32 +; - BS2_INC_PAEV86 +; - BS2_INC_LM +; - BS2_INC_LM16 +; - BS2_INC_LM32 +; - BS2_INC_LM64 +; - BS2_INC_CMN_R86 +; - BS2_INC_CMN_V86 +; - BS2_INC_CMN_P16 +; - BS2_INC_CMN_P32 +; - BS2_INC_CMN_P64 +; - BS2_WITH_TRAPS +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +; map files should include symbols and everything. +[map all] + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "bootsector2-structures.mac" +%include "bootsector2-common-macros-1.mac" +%include "VBox/bios.mac" + + +;******************************************************************************* +;* Defined Constants And Macros * +;******************************************************************************* +;; @name Static Memory Allocation +; @{ +;; The boot sector load address. +%define BS2_ADDR 07c00h +;; The stack is located before the code (and will overflow into the interrupt +; table and other essential system data). +%define STACK_ADDR (BS2_ADDR - 256) + +%ifdef BS2_WITH_TRAPS +;; The address of the ring-0 stack in bs2Tss32BitDf. +%define BS2_DF_R0_STACK_ADDR 06800h +;; The address of the ring-0 stack in TSSxx. +%define BS2_R0_STACK_ADDR 06000h +;; The address of the ring-1 stack in TSSxx. +%define BS2_R1_STACK_ADDR 05000h +;; The address of the ring-2 stack in TSSxx. +%define BS2_R2_STACK_ADDR 04800h +%endif ; BS2_WITH_TRAPS + +;; +; Where we save the boot registers during init. +%define BS2_REG_SAVE_ADDR 06000h + +;; The start of the memory area used for paging, stacks and so forth. +%define BS2_PXX_BASE 080000h + +;; The page map level 4 address (all entries point to BS2_LM_PDP_ADDR). +%define BS2_LM_PML4_ADDR 080000h +;; The long mode page directory pointer table address. +%define BS2_LM_PDP_ADDR 081000h +;; The PAE page directory pointer table address. +%define BS2_PAE_PDP_ADDR 082000h +;; The address of the 4 PAE page directories. Also used by long mode. +%define BS2_PAE_PD_ADDR 083000h +;; The address of the 32-bit page directory. +%define BS2_32B_PD_ADDR 087000h +;; User page table #0. +%define BS2_USER_PX_0_ADDR 088000h +;; User page table #1. +%define BS2_USER_PX_1_ADDR 089000h +;; User page table #2. +%define BS2_USER_PX_2_ADDR 08a000h +;; User page table #3. +%define BS2_USER_PX_3_ADDR 08b000h +;; User page table #4. +%define BS2_USER_PX_4_ADDR 08c000h +;; User page table #5. +%define BS2_USER_PX_5_ADDR 08d000h +;; User page table #6. +%define BS2_USER_PX_6_ADDR 08e000h +;; User page table #7. +%define BS2_USER_PX_7_ADDR 08f000h +;; The selector to use when accessing the PDP and PD from real mode. +%define BS2_PXX_SEL 08000h +;; Converts a BS2_P*_ADDR into a BS2_PXX_SEL selector offset. +%define BS2_PXX_OFF(Addr) ( (Addr) - (BS2_PXX_SEL * 16) ) + +;; The base address in the default address spaces of the range where we are +; free to muck about as much as we like. (This is a virtual address.) +%define BS2_MUCK_ABOUT_BASE 000400000h + +; We have some free space here 090000h...09a000h (stacks moved) + +;; The address of the LDT. +%define BS2_LDT_BASE 09b000h +;; The size of the LDT in bytes. +%define BS2_LDT_SIZE 001fffh + +;; The start of the memory area used for paging, stacks and so forth. +%define BS2_PXX_LAST 09ffffh +;; @} + + +;; +; @name Group of 32-bit, 16-bit and 64-bit selectors for one ring. +; @{ +%define BS2_SEL_GRP_CS32 00h +%define BS2_SEL_GRP_DS32 08h +%define BS2_SEL_GRP_SS32 10h +%define BS2_SEL_GRP_CS16 18h +%define BS2_SEL_GRP_DS16 20h +%define BS2_SEL_GRP_SS16 28h +%define BS2_SEL_GRP_CS64 30h +%define BS2_SEL_GRP_DS64 38h +%define BS2_SEL_GRP_SS64 38h +%define BS2_SEL_GRP_SIZE 40h +;; @} + + +;; Move to program. +%ifndef BS2_WITHOUT_RAW_MODE + %define BS2_WITH_RAW_MODE +%endif + +; Implicit code inclusion based on the init mode. +%ifdef BS2_INIT_PE32 + %define BS2_INC_PE32 +%elifdef BS2_INIT_PP32 + %define BS2_INC_PP32 +%elifdef BS2_INIT_PAE32 + %define BS2_INC_PAE32 +%elifdef BS2_INIT_LM64 + %define BS2_INC_LM64 +%else + %define BS2_INIT_RM ; the default + %define BS2_INC_RM +%endif + +; Aliases. +%ifdef BS2_INC_PE + %define BS2_INC_PE16 + %define BS2_INC_PE32 + %define BS2_INC_PEV86 +%endif +%ifdef BS2_INC_PP + %define BS2_INC_PP16 + %define BS2_INC_PP32 + %define BS2_INC_PPV86 +%endif +%ifdef BS2_INC_PAE + %define BS2_INC_PAE16 + %define BS2_INC_PAE32 + %define BS2_INC_PAEV86 +%endif +%ifdef BS2_INC_LM + %define BS2_INC_LM16 + %define BS2_INC_LM32 + %define BS2_INC_LM64 +%endif + +; Common code. +%ifdef BS2_INC_RM + %define BS2_INC_CMN_R86 +%endif +%ifdef BS2_INC_PE16 + %define BS2_INC_CMN_P16 + %define BS2_INC_CMN_PE + %define BS2_INC_CMN_PM +%endif +%ifdef BS2_INC_PE32 + %define BS2_INC_CMN_P32 + %define BS2_INC_CMN_PE + %define BS2_INC_CMN_PM +%endif +%ifdef BS2_INC_PEV86 + %define BS2_INC_CMN_R86 + %define BS2_INC_CMN_V86 + %define BS2_INC_CMN_PE + %define BS2_INC_CMN_PM +%endif +%ifdef BS2_INC_PP16 + %define BS2_INC_CMN_P16 + %define BS2_INC_CMN_PP + %define BS2_INC_CMN_PM +%endif +%ifdef BS2_INC_PP32 + %define BS2_INC_CMN_P32 + %define BS2_INC_CMN_PP + %define BS2_INC_CMN_PM +%endif +%ifdef BS2_INC_PPV86 + %define BS2_INC_CMN_R86 + %define BS2_INC_CMN_V86 + %define BS2_INC_CMN_PP + %define BS2_INC_CMN_PM +%endif +%ifdef BS2_INC_PAE16 + %define BS2_INC_CMN_P16 + %define BS2_INC_CMN_PAE + %define BS2_INC_CMN_PM + %define BS2_INC_CMN_PAE_LM +%endif +%ifdef BS2_INC_PAE32 + %define BS2_INC_CMN_P32 + %define BS2_INC_CMN_PAE + %define BS2_INC_CMN_PM + %define BS2_INC_CMN_PAE_LM +%endif +%ifdef BS2_INC_PAEV86 + %define BS2_INC_CMN_R86 + %define BS2_INC_CMN_V86 + %define BS2_INC_CMN_PAE + %define BS2_INC_CMN_PM + %define BS2_INC_CMN_PAE_LM +%endif +%ifdef BS2_INC_LM16 + %define BS2_INC_CMN_P16 + %define BS2_INC_CMN_LM + %define BS2_INC_CMN_PAE_LM +%endif +%ifdef BS2_INC_LM32 + %define BS2_INC_CMN_P32 + %define BS2_INC_CMN_LM + %define BS2_INC_CMN_PAE_LM +%endif +%ifdef BS2_INC_LM64 + %define BS2_INC_CMN_LM64 + %define BS2_INC_CMN_LM + %define BS2_INC_CMN_PAE_LM +%endif + + +; +; Misc defines. +; +;; The offset of the TSS32.CR3 field. +%define BS2_TSS32_CR3_OFF 01ch + + +;******************************************************************************* +;* Structures and Typedefs * +;******************************************************************************* + + +; +; Start with a jump just to follow the convention. +; Also declare all segments/sections to establish them and their order. +; + ORG BS2_ADDR + +section .text valign=16 align=16 progbits +section .data vfollows=.text follows=.text valign=16 align=16 progbits +section .texthigh vfollows=.data follows=.data valign=16 align=16 progbits +section .traprecs vfollows=.texthigh follows=.texthigh valign=8 align=8 progbits +section .end vfollows=.traprecs follows=.traprecs valign=512 align=512 progbits + +%define BEGINCODELOW section .text ;;< For 16-bit code. +%define BEGINCODEHIGH section .texthigh ;;< For 32-bit and 64-bit code. +%define BEGINEND section .end ;;< For aligning image to 512 bytes. + +BEGINCODELOW +BITS 16 +start: + jmp short bs2InitCode + nop + nop ; alignment + +; +; Abuse the bios parameter block area for data storage. +; +gdtr: + dw bs2GdtEnd - bs2Gdt - 1 ; limit 15:00 + dw bs2Gdt ; base 15:00 + db 0 ; base 23:16 + db 0 ; unused + +idtr_null: + dw 0 ; limit 15:00 + dw bs2Gdt ; base 15:00 + db 0 ; base 23:16 + db 0 ; unused + +%ifdef BS2_WITH_TRAPS + %ifdef BS2_INC_CMN_PM +idtr_32bit: + dw bs2Idt32bitEnd - bs2Idt32bit -1 ; limit 15:00 + dw bs2Idt32bit ; base 15:00 + db 0 ; base 23:16 + db 0 ; unused + %endif + + %ifdef BS2_INC_CMN_LM +idtr_64bit: + dw bs2Idt64bitEnd - bs2Idt64bit -1 ; limit 15:00 + dw bs2Idt64bit ; base 15:00 + db 0 ; base 23:16 + db 0 ; unused + %endif + +%elifdef BS2_WITH_RAW_MODE +idtr_dummy_32bit: + dw bs2DummyIdt32bitEnd - bs2DummyIdt32bit -1 ; limit 15:00 + dw bs2DummyIdt32bit ; base 15:00 + db 0 ; base 23:16 + db 0 ; unused +%endif + +idtr_real_mode: + dw 01ffh ; limit 15:00 + dw 0 ; base 15:00 + db 0 ; base 23:16 + db 0 ; unused + +g_achHex: + db '0123456789abcdef', 0 + +g_bBootDrv: + db 80h ; Not in the official BPB location, but whatever. +g_fCpuIntel: + db 0 +g_fCpuAmd: + db 0 + +bs2BpbPadding: + times 3dh - (bs2BpbPadding - start) db 0 + + +; +; Where to real init code starts. +; +bs2InitCode: + cli + +%ifdef BS2_INIT_SAVE_REGS + ; save the registers if we've been asked to do so. + mov [cs:BS2_REG_SAVE_ADDR + BS2REGS.rax], eax + mov [cs:BS2_REG_SAVE_ADDR + BS2REGS.rsp], esp + mov [cs:BS2_REG_SAVE_ADDR + BS2REGS.rbp], ebp + mov ax, ss + mov [cs:BS2_REG_SAVE_ADDR + BS2REGS.ss], ax + mov ax, ds + mov [cs:BS2_REG_SAVE_ADDR + BS2REGS.ds], ax + mov ax, es + mov [cs:BS2_REG_SAVE_ADDR + BS2REGS.es], ax + mov ax, fs + mov [cs:BS2_REG_SAVE_ADDR + BS2REGS.fs], ax + mov ax, gs +%endif + + ; set up the segment reisters and stack. + xor eax, eax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + mov esp, STACK_ADDR + mov [esp], eax ; clear the first 16 bytes + mov [esp + 04h], eax + mov [esp + 08h], eax ; fake rbp. + mov [esp + 0ch], eax ; fake ebp and bp + mov ebp, esp + +%ifdef BS2_INIT_SAVE_REGS + ; Save more registers now that ds is known and the stack is usable. + pushfd + pop eax + mov [BS2_REG_SAVE_ADDR + BS2REGS.rflags], eax + mov [BS2_REG_SAVE_ADDR + BS2REGS.rbx], ebx + mov [BS2_REG_SAVE_ADDR + BS2REGS.rcx], ecx + mov [BS2_REG_SAVE_ADDR + BS2REGS.rdx], edx + mov [BS2_REG_SAVE_ADDR + BS2REGS.rsi], esi + mov [BS2_REG_SAVE_ADDR + BS2REGS.rdi], edi +%endif + + ; Make sure caching is enabled and alignment is off. + mov eax, cr0 +%ifdef BS2_INIT_SAVE_REGS + mov [BS2_REG_SAVE_ADDR + BS2REGS.cr0], eax +%endif + and eax, ~(X86_CR0_NW | X86_CR0_CD | X86_CR0_AM) + mov cr0, eax + + ; Load all the code. + call bs2InitLoadImage + mov [g_bBootDrv], dl + + ; Initialize the data structures for the included modes requiring this. +%ifdef BS2_INC_CMN_PP + call bs2InitPagedProtMode +%endif +%ifdef BS2_INC_CMN_PAE + call bs2InitPaeProtMode +%endif +%ifdef BS2_INC_CMN_LM + call bs2InitLongMode +%endif + + ; Entered the desired mode. +%ifdef BS2_INIT_PE32 + call Bs2EnterMode_rm_pe32 +BITS 32 +%endif +%ifdef BS2_INIT_PP32 + call Bs2EnterMode_rm_pp32 +BITS 32 +%endif +%ifdef BS2_INIT_PAE32 + call Bs2EnterMode_rm_pae32 +BITS 32 +%endif +%ifdef BS2_INIT_LM64 + call Bs2EnterMode_rm_lm64 +BITS 64 +%endif +%ifdef BS2_INIT_RM + call SetCpuModeGlobals_rm +%endif + +%ifdef BS2_WITH_RAW_MODE + ; + ; Mask interrupts and then set IF. + ; + mov al, 0ffh + out 021h, al + out 0a1h, al + sti +%endif + + jmp bs2DoneInit + + +;; +; Loads the image off the floppy. +; +; This uses the the_end label to figure out the length. For this to work +; cleanly the label must be aligned on a sector boundrary. Use BS2_PAD_IMAGE +; to make sure this is the case. +; +; Clobbers everything except ebp and esp. Panics on failure. +; +; @param dl The boot drive number (from BIOS). +; @uses ax, cx, bx, esi, di +; +BEGINCODELOW +BITS 16 +BEGINPROC bs2InitLoadImage + push bp + mov bp, sp + push es +%define bSavedDiskNo byte [bp - 04h] + push dx +%define bMaxSector byte [bp - 06h] + push 0 +%define bMaxHead byte [bp - 08h] + push 0 +%define bMaxCylinder byte [bp - 0ah] + push 0 + + ; + ; Try figure the geometry. + ; + mov ah, 08h + int 13h + jc .failure + mov bMaxSector, cl + mov bMaxHead, dh + mov bMaxCylinder, ch + mov dl, bSavedDiskNo + + ; + ; Reload all the sectors one at a time (avoids problems). + ; + lea esi, [dword the_end] + sub esi, start + shr esi, 9 ; si = number of sectors to load. + mov di, BS2_ADDR / 16 ; The current load segment. + mov cx, 0001h ; ch/cylinder=0 (0-based); cl/sector=1 (1-based) + xor dh, dh ; dh/head=0 +.the_load_loop: + xor bx, bx + mov es, di ; es:bx -> buffer + mov ax, 0201h ; al=1 sector; ah=read function + int 13h + jc .failure + + ; advance to the next sector/head/cylinder. + inc cl + cmp cl, bMaxSector + jbe .adv_addr + + mov cl, 1 + inc dh + cmp dh, bMaxHead + jbe .adv_addr + + mov dh, 0 + inc ch + +.adv_addr: + add di, 512 / 16 + dec si + jnz .the_load_loop + + add sp, 3*2 + pop dx + pop es + leave + ret + + ; + ; Something went wrong, display a message. + ; +.failure: + pusha + + ; print message + mov si, .s_szErrMsg + mov ah, 0eh + xor bx, bx +.failure_next_char: + lodsb + int 10h + cmp si, .s_szErrMsgEnd + jb .failure_next_char + + ; format the error number. + movzx bx, byte [bp - 2 - 1] ; read the ah of the pusha frame + shr bl, 4 + mov al, [bx + g_achHex] + int 10h + + movzx bx, byte [bp - 2 - 1] ; read the ah of the pusha frame + and bl, 0fh + mov al, [bx + g_achHex] + int 10h + + ; panic + popa + call Bs2Panic +.s_szErrMsg: + db 13, 10, 'read error: ' +.s_szErrMsgEnd: +ENDPROC bs2InitLoadImage + +;; Pads the image so bs2InitLoadImage can load it without trouble. +%macro BS2_PAD_IMAGE 0 +bs2PadImageLabel: +; times ( (512*18*2) - ( (bs2PadImageLabel - start) % (512*18*2) ) ) db 0 ; track aligned size. + times ( 512 - ( (bs2PadImageLabel - start) % 512 ) ) db 0 ; sector aligned size. +; times ( 10000h - BS2_ADDR - (bs2PadImageLabel - start) ) db 0 ; full segment 0 size. +%endmacro + + +;; +; Shutdown routine that will work in real and protected mode, providing +; that SS is valid that we can load it into DS. +; +; Does not return. +; +BEGINCODELOW +BITS 16 +BEGINPROC Bs2Shutdown + cli + mov bl, 64 + mov ax, ss + mov ds, ax + mov dx, VBOX_BIOS_SHUTDOWN_PORT + mov ax, VBOX_BIOS_OLD_SHUTDOWN_PORT +.retry: + mov ecx, 8 + mov esi, .s_szShutdown + rep outsb + xchg dx, ax ; alternate between the new (VBox) and old (Bochs) ports. + dec bl + jnz .retry + ; Shutdown failed! + jmp Bs2Panic +.s_szShutdown: + db 'Shutdown', 0 +ENDPROC Bs2Shutdown + + +;; +; Panic routine for real mode. +; +; Does not return. +; +BEGINCODELOW +BITS 16 +BEGINPROC Bs2Panic + cli +.hlt_again: + hlt + jmp .hlt_again +ENDPROC Bs2Panic + + +; +; Padd the remainder of the sector with zeros and +; end it with the dos signature. +; +bs2Padding: + times 510 - (bs2Padding - start) db 0 + db 055h, 0aah + + +; +; The GDT (X86DESCGENERIC). +; +align 8, db 0 +bs2Gdt: + dw 00000h, 00000h, 00000h, 00000h ; null selector +%define BS2_SEL_R0_BASE 08h +%define BS2_SEL_CS32 08h + dw 0ffffh, 00000h, 09b00h, 000cfh ; 32-bit flat code segment. +%define BS2_SEL_DS32 10h + dw 0ffffh, 00000h, 09300h, 000cfh ; 32-bit flat data segment. +%define BS2_SEL_SS32 18h + dw 0ffffh, 00000h, 09300h, 000cfh ; 32-bit flat stack segment. +%define BS2_SEL_CS16 20h + dw 0ffffh, 00000h, 09b00h, 00000h ; 16-bit code segment with base 0. +%define BS2_SEL_DS16 28h + dw 0ffffh, 00000h, 09300h, 00000h ; 16-bit data segment with base 0. +%define BS2_SEL_SS16 30h + dw 0ffffh, 00000h, 09300h, 00000h ; 16-bit stack segment with base 0. +%define BS2_SEL_CS64 38h + dw 0ffffh, 00000h, 09a00h, 000afh ; 64-bit code segment. +%define BS2_SEL_DS64 40h +%define BS2_SEL_SS64 40h + dw 0ffffh, 00000h, 09300h, 000afh ; 64-bit stack and data segment. + dw 00000h, 00000h, 00000h, 00000h ; Unused +%define BS2_SEL_MMIO16 50h +%define BS2_SEL_MMIO16_BASE 00df000h + dw 0ffffh, 0f000h, 0930dh, 00000h ; 16-bit VMMDev MMIO segment with base 0df000h. + dw 00000h, 00000h, 00000h, 00000h ; Unused +%define BS2_SEL_LDT 60h ; LDT usage requires manual LLDT and setting up. + dw BS2_LDT_SIZE, BS2_LDT_BASE & 0xffff, 08200h | ((BS2_LDT_BASE >> 16) & 0xff), 00000h + dw 00000h, 00000h, 00000h, 00000h ; zero for 64-bit mode. +%define BS2_SEL_CS16_EO 70h + dw 0fffeh, 00000h, 09800h, 00000h ; 16-bit code segment with base 0, not accessed, execute only, short limit. + dw 00000h, 00000h, 00000h, 00000h ; unused. +%ifdef BS2_WITH_TRAPS + %ifdef BS2_INC_CMN_PM + %define BS2_SEL_TSS32 80h + dw (bs2Tss32BitEnd - bs2Tss32Bit) - 1 ; 32-bit TSS. + dw bs2Tss32Bit + db 0 + db X86_SEL_TYPE_SYS_386_TSS_AVAIL | 0x80 + dw 0 + %define BS2_SEL_TSS32_DF 88h + dw (bs2Tss32BitDfEnd - bs2Tss32BitDf) - 1; 32-bit TSS, double fault. + dw bs2Tss32BitDf + db 0 + db X86_SEL_TYPE_SYS_386_TSS_AVAIL | 0x80 + dw 0 + %else + dw 00000h, 00000h, 00000h, 00000h + dw 00000h, 00000h, 00000h, 00000h + %endif + %ifdef BS2_INC_CMN_LM + %define BS2_SEL_TSS64 90h + dw (bs2Tss64BitEnd - bs2Tss64Bit) - 1 ; 32-bit TSS. + dw bs2Tss64Bit + db 0 + db AMD64_SEL_TYPE_SYS_TSS_AVAIL | 0x80 + dw 0 + dw 00000h, 00000h, 00000h, 00000h ; 2nd half of the 64-bit selector (not necessary). + %else + dw 00000h, 00000h, 00000h, 00000h + dw 00000h, 00000h, 00000h, 00000h + %endif +%endif + ; Ring-1 selectors. +%define BS2_SEL_R1_BASE 0a0h +%define BS2_SEL_R1_CS32 0a0h + dw 0ffffh, 00000h, 0bb00h, 000cfh ; Ring-1 32-bit flat code segment. +%define BS2_SEL_R1_DS32 0a8h + dw 0ffffh, 00000h, 0b300h, 000cfh ; Ring-1 32-bit flat data segment. +%define BS2_SEL_R1_SS32 0b0h + dw 0ffffh, 00000h, 0b300h, 000cfh ; Ring-1 32-bit flat stack segment. +%define BS2_SEL_R1_CS16 0b8h + dw 0ffffh, 00000h, 0bb00h, 00000h ; Ring-1 16-bit code segment with base 0. +%define BS2_SEL_R1_DS16 0c0h + dw 0ffffh, 00000h, 0b300h, 00000h ; Ring-1 16-bit data segment with base 0. +%define BS2_SEL_R1_SS16 0c8h + dw 0ffffh, 00000h, 0b300h, 00000h ; Ring-1 16-bit stack segment with base 0. +%define BS2_SEL_R1_CS64 0d0h + dw 0ffffh, 00000h, 0ba00h, 000afh ; Ring-1 64-bit code segment. +%define BS2_SEL_R1_DS64 0d8h +%define BS2_SEL_R1_SS64 0d8h + dw 0ffffh, 00000h, 0b300h, 000afh ; Ring-1 64-bit stack and data segment. + + ; Ring-2 selectors. +%define BS2_SEL_R2_BASE 0e0h +%define BS2_SEL_R2_CS32 0e0h + dw 0ffffh, 00000h, 0db00h, 000cfh ; Ring-2 32-bit flat code segment. +%define BS2_SEL_R2_DS32 0e8h + dw 0ffffh, 00000h, 0d300h, 000cfh ; Ring-2 32-bit flat data segment. +%define BS2_SEL_R2_SS32 0f0h + dw 0ffffh, 00000h, 0d300h, 000cfh ; Ring-2 32-bit flat stack segment. +%define BS2_SEL_R2_CS16 0f8h + dw 0ffffh, 00000h, 0db00h, 00000h ; Ring-2 16-bit code segment with base 0. +%define BS2_SEL_R2_DS16 0f0h + dw 0ffffh, 00000h, 0d300h, 00000h ; Ring-2 16-bit data segment with base 0. +%define BS2_SEL_R2_SS16 108h + dw 0ffffh, 00000h, 0d300h, 00000h ; Ring-2 16-bit stack segment with base 0. +%define BS2_SEL_R2_CS64 110h + dw 0ffffh, 00000h, 0da00h, 000afh ; Ring-2 64-bit code segment. +%define BS2_SEL_R2_DS64 118h +%define BS2_SEL_R2_SS64 118h + dw 0ffffh, 00000h, 0d300h, 000afh ; Ring-2 64-bit stack and data segment. + + ; Ring-3 selectors. +%define BS2_SEL_R3_BASE 120h +%define BS2_SEL_R3_CS32 120h + dw 0ffffh, 00000h, 0fb00h, 000cfh ; Ring-3 32-bit flat code segment. +%define BS2_SEL_R3_DS32 128h + dw 0ffffh, 00000h, 0f300h, 000cfh ; Ring-3 32-bit flat data segment. +%define BS2_SEL_R3_SS32 130h + dw 0ffffh, 00000h, 0f300h, 000cfh ; Ring-3 32-bit flat stack segment. +%define BS2_SEL_R3_CS16 138h + dw 0ffffh, 00000h, 0fb00h, 00000h ; Ring-3 16-bit code segment with base 0. +%define BS2_SEL_R3_DS16 140h + dw 0ffffh, 00000h, 0f300h, 00000h ; Ring-3 16-bit data segment with base 0. +%define BS2_SEL_R3_SS16 148h + dw 0ffffh, 00000h, 0f300h, 00000h ; Ring-3 16-bit stack segment with base 0. +%define BS2_SEL_R3_CS64 150h + dw 0ffffh, 00000h, 0fa00h, 000afh ; Ring-1 64-bit code segment. +%define BS2_SEL_R3_DS64 158h +%define BS2_SEL_R3_SS64 158h + dw 0ffffh, 00000h, 0f300h, 000afh ; Ring-1 64-bit stack and data segment. + + ; Here follows a bunch of spare GDT entries for (ab)use in testing. +%define BS2_SEL_SPARE0 160h +bs2GdtSpare0: + dq 0 +%define BS2_SEL_SPARE1 (BS2_SEL_SPARE0 + 08h) +bs2GdtSpare1: + dq 0 +%define BS2_SEL_SPARE2 (BS2_SEL_SPARE0 + 10h) +bs2GdtSpare2: + dq 0 +%define BS2_SEL_SPARE3 (BS2_SEL_SPARE0 + 18h) +bs2GdtSpare3: + dq 0 +bs2GdtEnd: + + +%ifndef BS2_WITH_TRAPS + %ifdef BS2_WITH_RAW_MODE +; +; Dummy 32-bit IDT for making CSAM happy. +; +align 16, db 0 +bs2DummyIdt32bit: + dw 0, 0, 0, 0 + dw 0, 0, 0, 0 + dw 0, 0, 0, 0 + dw 0, 0, 0, 0 +bs2DummyIdt32bitEnd + %endif +%endif + + +; +; Mode initialization routines. +; + +%ifdef BS2_INC_CMN_PP +;; +; Initializes the paged protected mode structures during init. +; +; @uses ebx, esi +; +BEGINCODELOW +BITS 16 +BEGINPROC bs2InitPagedProtMode + push ds + + ; + ; Create a paging hierarchy + ; + mov bx, BS2_PXX_SEL + mov ds, bx + mov ebx, BS2_PXX_OFF(BS2_32B_PD_ADDR) + xor esi, esi ; physical address + + ; The page directory. +.pd_loop: + mov dword [bx], esi + or word [bx], X86_PDE4M_P | X86_PDE4M_RW | X86_PDE4M_PS | X86_PDE4M_US + add esi, _4M + add bx, 4 + test bx, 0fffh + jnz .pd_loop + +%ifdef BS2_WITH_RAW_MODE + ; + ; Make sure there is some free space for the hypervisor near the top + ; of the address space (last 4MB is mapped). + ; + and byte [bx - 08h], 0feh + and byte [bx - 0ch], 0feh + and byte [bx - 10h], 0feh + and byte [bx - 14h], 0feh +%endif + + pop ds + ret +ENDPROC bs2InitPagedProtMode +%endif ; BS2_INC_CMN_PP + + +%ifdef BS2_INC_CMN_PAE_LM +;; +; Initializes the PAE page directories. +; +; Assumes ds is set to BS2_PXX_SEL already and that edx is zero. +; +; @uses ebx, esi +; @internal +; +BEGINPROC bs2InitPaePageDirs + mov esi, X86_PDE4M_P | X86_PDE4M_RW | X86_PDE4M_PS | X86_PDE4M_US + mov ebx, BS2_PXX_OFF(BS2_PAE_PD_ADDR) +.pd_loop: + mov [bx], esi + mov [bx + 4], edx + add esi, _2M + add bx, 8 + test bx, 0fffh + jnz .pd_loop + cmp bx, BS2_PXX_OFF(BS2_PAE_PD_ADDR + 4*_4K) + jne .pd_loop + +%ifdef BS2_WITH_RAW_MODE + ; + ; Make sure there is some free space for the hypervisor near the top + ; of the address space (last 4MB is mapped). + ; + and byte [bx - 10h], 0feh + and byte [bx - 18h], 0feh + and byte [bx - 20h], 0feh + and byte [bx - 28h], 0feh +%endif + + ret +ENDPROC bs2InitPaePageDirs +%endif ; BS2_INC_CMN_PAE_LM + + +%ifdef BS2_INC_CMN_PAE +;; +; Initializes the PAE protected mode structures during init. +; +; @uses edx, ebx, esi. +; +BEGINCODELOW +BITS 16 +BEGINPROC bs2InitPaeProtMode + push ds + + mov dx, BS2_PXX_SEL + mov ds, dx + xor edx, edx + + ; + ; Join paths with long mode. + ; + call bs2InitPaePageDirs + + ; + ; Create the page directory pointer table. + ; + mov ebx, BS2_PXX_OFF(BS2_PAE_PDP_ADDR) + mov dword [bx], (BS2_PAE_PD_ADDR ) | X86_PDPE_P + mov dword [bx + 04h], edx + mov dword [bx + 08h], (BS2_PAE_PD_ADDR + 1000h) | X86_PDPE_P + mov dword [bx + 0ch], edx + mov dword [bx + 10h], (BS2_PAE_PD_ADDR + 2000h) | X86_PDPE_P + mov dword [bx + 14h], edx + mov dword [bx + 18h], (BS2_PAE_PD_ADDR + 3000h) | X86_PDPE_P + mov dword [bx + 1ch], edx + + pop ds + ret +ENDPROC bs2InitPaeProtMode +%endif ; BS2_INC_CMN_PAE + + +%ifdef BS2_INC_CMN_LM +;; +; Initializes the long mode structures during init. +; +; @uses edx, ebx, esi +; +BEGINCODELOW +BITS 16 +BEGINPROC bs2InitLongMode + push ds + + mov dx, BS2_PXX_SEL + mov ds, dx + xor edx, edx + + ; + ; Join paths with the PAE code. + ; + call bs2InitPaePageDirs + + ; + ; Create the long mode page directory pointer table. + ; + mov ebx, BS2_PXX_OFF(BS2_LM_PDP_ADDR) +.pdptr_loop: + mov dword [bx], (BS2_PAE_PD_ADDR ) | X86_PDPE_P | X86_PDPE_RW | X86_PDPE_US + mov dword [bx + 04h], edx + mov dword [bx + 08h], (BS2_PAE_PD_ADDR + 1000h) | X86_PDPE_P | X86_PDPE_RW | X86_PDPE_US + mov dword [bx + 0ch], edx + mov dword [bx + 10h], (BS2_PAE_PD_ADDR + 2000h) | X86_PDPE_P | X86_PDPE_RW | X86_PDPE_US + mov dword [bx + 14h], edx + mov dword [bx + 18h], (BS2_PAE_PD_ADDR + 3000h) | X86_PDPE_P | X86_PDPE_RW | X86_PDPE_US + mov dword [bx + 1ch], edx + add bx, 20h + test bx, 0fffh + jnz .pdptr_loop + + ; + ; Set up the page map level 4 table, all entries mapping the same PDPTR. + ; + mov ebx, BS2_PXX_OFF(BS2_LM_PML4_ADDR) +.pml4_loop: + mov dword [bx], BS2_LM_PDP_ADDR | X86_PML4E_P | X86_PML4E_RW | X86_PML4E_US + mov dword [bx + 4], edx + add bx, 8 + test bx, 0fffh + jnz .pml4_loop + + pop ds + ret +ENDPROC bs2InitLongMode +%endif ; BS2_INC_CMN_LM + + + +; +; Routines for entering the different modes. +; + +%ifdef BS2_INC_RM +;; +; Dummy. +BEGINCODELOW +BITS 16 +BEGINPROC Bs2EnterMode_rm_rm + ret +ENDPROC Bs2EnterMode_rm_rm +%endif ; BS2_INC_RM + + +%ifdef BS2_INC_PE16 +;; +; Enters unpaged protected mode from real mode (cs = 0). +; +; @returns cs,ds,ss,es,gs,fs loaded with 16-bit selectors. +; ebp and esp converted to 32/16-bit. +; All other registers are preserved. +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC Bs2EnterMode_rm_pe16 + push eax + pushfd + cli +%ifndef BS2_NOINC_COMMON + push word SetCpuModeGlobals_pe16 +%endif + + ; + ; Switch to protected mode. + ; + xor ax, ax + mov ds, ax + lgdt [gdtr] +%ifdef BS2_WITH_TRAPS + lidt [idtr_32bit] +%elifdef BS2_WITH_RAW_MODE + lidt [idtr_dummy_32bit] +%else + lidt [idtr_null] +%endif + + mov eax, cr0 + or eax, X86_CR0_PE + and eax, 0ffffffffh - X86_CR0_PG + mov cr0, eax + jmp far BS2_SEL_CS16:bs2ProtModeCode16Start_p16 +ENDPROC Bs2EnterMode_rm_pe16 +%endif ; BS2_INC_PE16 + + +%ifdef BS2_INC_PE32 +;; +; Enters unpaged protected mode from real mode (cs = 0). +; +; @returns cs,ds,ss,es,gs,fs loaded with 32-bit selectors. +; ebp and esp converted to 32-bit. +; All other registers are preserved. +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC Bs2EnterMode_rm_pe32 + push word 0 + push eax + pushfd + cli +%ifndef BS2_NOINC_COMMON + push dword SetCpuModeGlobals_pe32 +%endif + + ; Do the mode switch. + xor ax, ax + mov ds, ax + lgdt [gdtr] +%ifdef BS2_WITH_TRAPS + lidt [idtr_32bit] +%elifdef BS2_WITH_RAW_MODE + lidt [idtr_dummy_32bit] +%else + lidt [idtr_null] +%endif + + mov eax, cr0 + or eax, X86_CR0_PE + and eax, 0ffffffffh - X86_CR0_PG + mov cr0, eax + jmp far BS2_SEL_CS32:bs2ProtModeCode32Start_p32 +ENDPROC Bs2EnterMode_rm_pe32 +%endif ; BS2_INC_PE32 + + +;; @todo BS2_INC_PEV86 + + +%ifdef BS2_INC_PP16 +;; +; Enters paged protected mode from real mode (cs = 0). +; +; @returns cs,ds,ss,es,gs,fs loaded with 16-bit selectors. +; ebp and esp converted to 16/32-bit. +; All other registers are preserved. +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC Bs2EnterMode_rm_pp16 + push eax + pushfd + cli +%ifndef BS2_NOINC_COMMON + push word SetCpuModeGlobals_pp16 +%endif + + ; Do the mode switch. + xor ax, ax + mov ds, ax + lgdt [gdtr] +%ifdef BS2_WITH_TRAPS + lidt [idtr_32bit] +%elifdef BS2_WITH_RAW_MODE + lidt [idtr_dummy_32bit] +%else + lidt [idtr_null] +%endif + + mov eax, BS2_32B_PD_ADDR + mov cr3, eax +%ifdef BS2_WITH_TRAPS + mov [bs2Tss32BitDf + BS2_TSS32_CR3_OFF], eax +%endif + + mov eax, cr4 + or eax, X86_CR4_PSE + and eax, ~X86_CR4_PAE + mov cr4, eax + + mov eax, cr0 + or eax, X86_CR0_PE | X86_CR0_PG | X86_CR0_WP + mov cr0, eax + jmp far BS2_SEL_CS16:bs2ProtModeCode16Start_p16 +ENDPROC Bs2EnterMode_rm_pp16 +%endif ; BS2_INC_PP16 + + +%ifdef BS2_INC_PP32 +;; +; Enters paged protected mode from real mode (cs = 0). +; +; @returns cs,ds,ss,es,gs,fs loaded with 32-bit selectors. +; ebp and esp converted to 32-bit. +; All other registers are preserved. +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC Bs2EnterMode_rm_pp32 + push word 0 + push eax + pushfd + cli +%ifndef BS2_NOINC_COMMON + push dword SetCpuModeGlobals_pp32 +%endif + + ; Do the mode switch. + xor ax, ax + mov ds, ax + lgdt [gdtr] +%ifdef BS2_WITH_TRAPS + lidt [idtr_32bit] +%elifdef BS2_WITH_RAW_MODE + lidt [idtr_dummy_32bit] +%else + lidt [idtr_null] +%endif + + mov eax, BS2_32B_PD_ADDR + mov cr3, eax +%ifdef BS2_WITH_TRAPS + mov [bs2Tss32BitDf + BS2_TSS32_CR3_OFF], eax +%endif + + mov eax, cr4 + or eax, X86_CR4_PSE + and eax, ~X86_CR4_PAE + mov cr4, eax + + mov eax, cr0 + or eax, X86_CR0_PE | X86_CR0_PG | X86_CR0_WP + mov cr0, eax + jmp far BS2_SEL_CS32:bs2ProtModeCode32Start_p32 +ENDPROC Bs2EnterMode_rm_pp32 +%endif ; BS2_INC_PP16 + + +;; @todo BS2_INC_PPV86 + + +%ifdef BS2_INC_PAE16 +;; +; Enters PAE protected mode from real mode (cs = 0). +; +; @returns cs,ds,ss,es,gs,fs loaded with 16-bit selectors. +; ebp and esp converted to 16/32-bit. +; All other registers are preserved. +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC Bs2EnterMode_rm_pae16 + push eax + pushfd + cli +%ifndef BS2_NOINC_COMMON + push word SetCpuModeGlobals_pae16 +%endif + + ; Do the mode switch. + xor ax, ax + mov ds, ax + lgdt [gdtr] +%ifdef BS2_WITH_TRAPS + lidt [idtr_32bit] +%elifdef BS2_WITH_RAW_MODE + lidt [idtr_dummy_32bit] +%else + lidt [idtr_null] +%endif + + mov eax, BS2_PAE_PDP_ADDR + mov cr3, eax +%ifdef BS2_WITH_TRAPS + mov [bs2Tss32BitDf + BS2_TSS32_CR3_OFF], eax +%endif + + mov eax, cr4 + or eax, X86_CR4_PAE | X86_CR4_PSE + mov cr4, eax + + mov eax, cr0 + or eax, X86_CR0_PE | X86_CR0_PG | X86_CR0_WP + mov cr0, eax + jmp far BS2_SEL_CS16:bs2ProtModeCode16Start_p16 +ENDPROC Bs2EnterMode_rm_pae16 +%endif ; BS2_INC_PAE16 + + +%ifdef BS2_INC_PAE32 +;; +; Enters PAE protected mode from real mode (cs = 0). +; +; @returns cs,ds,ss,es,gs,fs loaded with 32-bit selectors. +; ebp and esp converted to 32-bit. +; All other registers are preserved. +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC Bs2EnterMode_rm_pae32 + push word 0 + push eax + pushfd + cli +%ifndef BS2_NOINC_COMMON + push dword SetCpuModeGlobals_pae32 +%endif + + ; Do the mode switch. + xor ax, ax + mov ds, ax + lgdt [gdtr] +%ifdef BS2_WITH_TRAPS + lidt [idtr_32bit] +%elifdef BS2_WITH_RAW_MODE + lidt [idtr_dummy_32bit] +%else + lidt [idtr_null] +%endif + + mov eax, BS2_PAE_PDP_ADDR + mov cr3, eax +%ifdef BS2_WITH_TRAPS + mov [bs2Tss32BitDf + BS2_TSS32_CR3_OFF], eax +%endif + + mov eax, cr4 + or eax, X86_CR4_PAE | X86_CR4_PSE + mov cr4, eax + + mov eax, cr0 + or eax, X86_CR0_PE | X86_CR0_PG | X86_CR0_WP + mov cr0, eax + jmp far BS2_SEL_CS32:bs2ProtModeCode32Start_p32 +ENDPROC Bs2EnterMode_rm_pae32 +%endif ; BS2_INC_PAE32 + + +;; @todo BS2_INC_PAEV86 + + +%ifdef BS2_INC_LM16 +;; +; Enters long mode from real mode (cs = 0). +; +; @returns cs,ds,ss,es,gs,fs loaded with 16-bit selectors. +; rbp and rsp converted to 16/32/64-bit. +; All other registers are preserved. +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC Bs2EnterMode_rm_lm16 + call Bs2EnterMode_rm_lm64 +BITS 64 + call Bs2Thunk_lm64_lm16 +BITS 16 + ret +ENDPROC Bs2EnterMode_rm_lm16 +%endif ; BS2_INC_LM16 + + +%ifdef BS2_INC_LM32 +;; +; Enters long mode from real mode (cs = 0). +; +; @returns cs,ds,ss,es,gs,fs loaded with 32-bit selectors. +; rbp and rsp converted to 16/32/64-bit. +; All other registers are preserved. +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC Bs2EnterMode_rm_lm32 + ; Change the return address into a 32-bit one. + push word 0 ; Reserved 2 extra bytes for 32-bit return address. + push eax ; Save eax. + movzx eax, word [esp + 6h] ; Read narrow return address. + mov [esp + 4h], eax ; Store wide return address. + pop eax ; Restore eax. + + ; Do the mode switch and thunking. + call Bs2EnterMode_rm_lm64 +BITS 64 + call Bs2Thunk_lm64_lm32 +BITS 32 + ret +ENDPROC Bs2EnterMode_rm_lm32 +%endif ; BS2_INC_LM32 + + +%ifdef BS2_INC_LM64 +;; +; Enters long mode from real mode (cs = 0). +; +; @returns cs,ds,ss,es,gs,fs loaded with 64-bit selectors. +; rbp and rsp converted to 64-bit. +; All other registers are preserved. +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC Bs2EnterMode_rm_lm64 + push word 0 ; reserve bytes for 64-bit return address. + push dword 0 + push dword 0 ; rax + push eax + push dword 0 ; rcx + push ecx + push dword 0 ; rdx + push edx + push dword 0 ; rflags + pushfd + cli + + ; Fix the return address. + mov ax, [esp + 20h + 6h] + mov word [esp + 20h + 6h], 0 + mov [esp + 20h], ax + + ; + ; Switch to long mode. + ; + xor ax, ax + mov ds, ax + lgdt [gdtr] +%ifdef BS2_WITH_TRAPS + lidt [idtr_64bit] +%else + lidt [idtr_null] +%endif + + mov eax, BS2_LM_PML4_ADDR + mov cr3, eax + + mov eax, cr4 + or eax, X86_CR4_PAE | X86_CR4_PSE + mov cr4, eax + + mov ecx, MSR_K6_EFER + rdmsr + or eax, MSR_K6_EFER_LME + wrmsr + + mov eax, cr0 + or eax, X86_CR0_PE | X86_CR0_PG | X86_CR0_WP + mov cr0, eax + jmp far BS2_SEL_CS64:.code64_start + +BITS 64 +.code64_start: + mov eax, BS2_SEL_DS64 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ax, BS2_SEL_SS64 + mov ss, ax +%ifdef BS2_WITH_TRAPS + btr dword [bs2Gdt + BS2_SEL_TSS64 + 32/8], 1+8 ; busy -> avail + %ifndef BS2_WITH_MANUAL_LTR + mov ax, BS2_SEL_TSS64 + ltr ax + %endif +%endif + + and ebp, 0ffffh + and esp, 0ffffh + and edi, 0ffffffffh + and esi, 0ffffffffh + and ebx, 0ffffffffh + +%ifndef BS2_NOINC_COMMON + call SetCpuModeGlobals_lm64 +%endif + + popf + pop rdx + pop rcx + pop rax + ret +ENDPROC Bs2EnterMode_rm_lm64 +%endif ; BS2_INC_PAE32 + + + +%ifdef BS2_INC_CMN_P16 +;; +; Code shared by the three 16-bit protected mode switchers. +; +; @internal +; +BEGINCODELOW +BITS 16 +BEGINPROC bs2ProtModeCode16Start_p16 + ; Initialize the registers. + mov ax, BS2_SEL_DS16 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ax, BS2_SEL_SS16 + mov ss, ax + and esp, 0ffffh + and ebp, 0ffffh +%ifdef BS2_WITH_TRAPS + btr word [bs2Gdt + BS2_SEL_TSS32 + 32/8], 1+8 ; busy -> avail + btr word [bs2Gdt + BS2_SEL_TSS32_DF + 32/8], 1+8 ; busy -> avail + %ifndef BS2_WITH_MANUAL_LTR + mov ax, BS2_SEL_TSS32 + ltr ax + %endif +%endif + +%ifndef BS2_NOINC_COMMON + ; Set up mode specific global variables. + pop ax + call ax +%endif + + popfd + pop eax + ret +ENDPROC bs2ProtModeCode16Start_p16 +%endif ; BS2_INC_CMN_P16 + + +%ifdef BS2_INC_CMN_P32 +;; +; Code shared by the three 32-bit protected mode switchers. +; +; @internal +; +BEGINCODELOW +BITS 32 +BEGINPROC bs2ProtModeCode32Start_p32 + ; Initialize the registers. + mov ax, BS2_SEL_DS32 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ax, BS2_SEL_SS32 + mov ss, ax + and esp, 0ffffh + and ebp, 0ffffh +%ifdef BS2_WITH_TRAPS + btr dword [bs2Gdt + BS2_SEL_TSS32 + 32/8], 1+8 ; busy -> avail + btr dword [bs2Gdt + BS2_SEL_TSS32_DF + 32/8], 1+8 ; busy -> avail + %ifndef BS2_WITH_MANUAL_LTR + mov ax, BS2_SEL_TSS32 + ltr ax + %endif +%endif + +%ifndef BS2_NOINC_COMMON + ; Set up mode specific global variables. + pop eax + call eax +%endif + + ; Make the return address 32-bit and then return. + movzx eax, word [esp + 0ah] + mov [esp + 8h], eax + popfd + pop eax + ret +ENDPROC bs2ProtModeCode32Start_p32 +%endif ; BS2_INC_CMN_P32 + + + +; +; Routines for exitting the different modes. +; + + +%ifdef BS2_INC_RM +;; +; Dummy. +BEGINCODELOW +BITS 16 +BEGINPROC Bs2ExitMode_rm + ret +ENDPROC Bs2ExitMode_rm +%endif ; BS2_INC_RM + + +%ifdef BS2_INC_PE16 +;; +; See bs2ExitMode_p16. +BEGINCODELOW +BITS 16 +BEGINPROC Bs2ExitMode_pe16 + push bs2ExitModeCleanupNop_rm + jmp bs2ExitMode_p16 +ENDPROC Bs2ExitMode_pe16 +%endif ; BS2_INC_PE16 + + +%ifdef BS2_INC_PE32 +;; +; See bs2ExitMode_p32. +BEGINCODEHIGH +BITS 32 +BEGINPROC Bs2ExitMode_pe32 + push bs2ExitModeCleanupNop_rm + jmp bs2ExitMode_p32 +ENDPROC Bs2ExitMode_pe32 +%endif ; BS2_INC_PE32 + + +%ifdef BS2_INC_PEV86 +;; +; See Bs2ExitMode_v86. +BEGINCODELOW +BITS 16 +BEGINPROC Bs2ExitMode_pev86 + push bs2ExitModeCleanupNop_rm + jmp Bs2ExitMode_pv86 +ENDPROC Bs2ExitMode_pev86 +%endif ; BS2_INC_PEV86 + + +%ifdef BS2_INC_PP16 +;; +; See bs2ExitMode_p16. +BEGINCODELOW +BITS 16 +BEGINPROC Bs2ExitMode_pp16 + push bs2ExitModeCleanupNop_rm + jmp bs2ExitMode_p16 +ENDPROC Bs2ExitMode_pp16 +%endif ; BS2_INC_PP16 + + +%ifdef BS2_INC_PP32 +;; +; See bs2ExitMode_p32. +BEGINCODEHIGH +BITS 32 +BEGINPROC Bs2ExitMode_pp32 + push bs2ExitModeCleanupNop_rm + jmp bs2ExitMode_p32 +ENDPROC Bs2ExitMode_pp32 +%endif ; BS2_INC_PP32 + + +%ifdef BS2_INC_PPV86 +;; +; See Bs2ExitMode_v86. +BEGINCODELOW +BITS 16 +BEGINPROC Bs2ExitMode_ppv86 + push bs2ExitModeCleanupNop_rm + jmp Bs2ExitMode_pv86 +ENDPROC Bs2ExitMode_ppv86 +%endif ; BS2_INC_PPV86 + + + +%ifdef BS2_INC_PAE16 +;; +; See bs2ExitMode_p16. +BEGINCODELOW +BITS 16 +BEGINPROC Bs2ExitMode_pae16 + push bs2ExitModeCleanupPae_rm + jmp bs2ExitMode_p16 +ENDPROC Bs2ExitMode_pae16 +%endif ; BS2_INC_pae16 + + +%ifdef BS2_INC_PAE32 +;; +; See bs2ExitMode_p32. +BEGINCODEHIGH +BITS 32 +BEGINPROC Bs2ExitMode_pae32 + push bs2ExitModeCleanupPae_rm + jmp bs2ExitMode_p32 +ENDPROC Bs2ExitMode_pae32 +%endif ; BS2_INC_PAE32 + + +%ifdef BS2_INC_PAEV86 +;; +; See Bs2ExitMode_v86. +BEGINCODELOW +BITS 16 +BEGINPROC Bs2ExitMode_paev86 + push bs2ExitModeCleanupPae_rm + jmp Bs2ExitMode_pv86 +ENDPROC Bs2ExitMode_paev86 +%endif ; BS2_INC_PAEV86 + + +%ifdef BS2_INC_LM16 +;; +; See bs2ExitMode_p16. +BEGINCODELOW +BITS 16 +BEGINPROC Bs2ExitMode_lm16 + push bs2ExitModeCleanupLm_rm + jmp bs2ExitMode_p16 +ENDPROC Bs2ExitMode_lm16 +%endif ; BS2_INC_lm16 + + +%ifdef BS2_INC_LM32 +;; +; See bs2ExitMode_p32. +BEGINCODEHIGH +BITS 32 +BEGINPROC Bs2ExitMode_lm32 + push bs2ExitModeCleanupLm_rm + jmp bs2ExitMode_p32 +ENDPROC Bs2ExitMode_lm32 +%endif ; BS2_INC_LM32 + + +%ifdef BS2_INC_LM64 +;; +; See Bs2ExitMode_v86. +BEGINCODELOW +BITS 64 +BEGINPROC Bs2ExitMode_lm64 + call Bs2Thunk_lm64_lm16 +BITS 16 + pop dword [esp] + pop word [esp] + push bs2ExitModeCleanupLm_rm + jmp bs2ExitMode_p16 +ENDPROC Bs2ExitMode_lm64 +%endif ; BS2_INC_LM64 + + +; Isn't there a better way to do this in yasm? +%ifdef BS2_INC_CMN_P16 + %define BS2_INC_BS2EXITMODE_P16 +%elifdef BS2_INC_CMN_LM + %define BS2_INC_BS2EXITMODE_P16 +%elifdef BS2_INC_CMN_P32 + %define BS2_INC_BS2EXITMODE_P16 +%endif + +%ifdef BS2_INC_BS2EXITMODE_P16 +;; +; Exit 16-bit protected or long mode (cs = CS16, ss = SS16). +; +; @returns cs,ds,ss,es,gs,fs loaded with the 0 selector. +; All other registers are preserved. +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC bs2ExitMode_p16 + push eax + pushfd + cli + + ; Make sure we've got the right SS and CS values (paranoia). + mov ax, BS2_SEL_SS16 + mov ss, ax + jmp far BS2_SEL_CS16:.code16_start + +.code16_start: + ; Turn off paging and protected mode. + mov eax, cr0 + and eax, 0ffffffffh - X86_CR0_PE - X86_CR0_PG + mov cr0, eax + jmp far 0000:.real_mode_start + +.real_mode_start: + ; Load the correct real mode segment registers. + xor ax, ax + mov ss, ax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + ; Load the real mode idtr. + lidt [idtr_real_mode] + + ; Cleanup. + mov ax, [esp + 8h] + call ax + +%ifndef BS2_NOINC_COMMON + ; Set globals. + call SetCpuModeGlobals_rm +%endif + + popfd + pop eax + add sp, 2h ; cleanup routine address + ret +ENDPROC bs2ExitMode_p16 +%endif ; BS2_INC_CMN_P16 + + +%ifdef BS2_INC_CMN_P32 +;; +; Exit 32-bit protected or long mode (cs = CS32, ss = SS32). +; +; The return address as well as the stack registers must be somewhere within +; the first 64KB of the address space. +; +; @returns cs,ds,ss,es,gs,fs loaded with the 0 selector. +; All other registers are preserved. +; @uses nothing +; +BEGINCODELOW +BITS 32 +BEGINPROC bs2ExitMode_p32 + push eax + mov eax, [esp + 8h] ; return address + mov [esp + 0ah], ax + mov eax, [esp + 4h] ; cleanup routine address + mov [esp + 08h], ax + pop eax + add esp, 4h + + call Bs2Thunk_p32_p16 +BITS 16 + jmp bs2ExitMode_p16 +ENDPROC bs2ExitMode_p32 +%endif ; BS2_INC_CMN_P32 + + +;; +; Dummy cleanup routine. +BEGINCODELOW +BITS 16 +BEGINPROC bs2ExitModeCleanupNop_rm + ret +ENDPROC bs2ExitModeCleanupNop_rm + + +%ifdef BS2_INC_CMN_PAE +;; +; Cleans up after leaving PAE mode. +; @uses nothing +BEGINCODELOW +BITS 16 +BEGINPROC bs2ExitModeCleanupPae_rm + push eax + mov eax, cr4 + and eax, ~X86_CR4_PAE + mov cr4, eax + pop eax + ret +ENDPROC bs2ExitModeCleanupPae_rm +%endif + + +%ifdef BS2_INC_CMN_LM +;; +; Cleans up after leaving long mode. +; @uses nothing +BEGINCODELOW +BITS 16 +BEGINPROC bs2ExitModeCleanupLm_rm + push eax + push edx + push ecx + + mov ecx, MSR_K6_EFER + rdmsr + and eax, ~MSR_K6_EFER_LME + wrmsr + + pop ecx + pop edx + pop eax + ret +ENDPROC bs2ExitModeCleanupLm_rm +%endif + + + +; +; Thunking routines for switching between 16/32/64-bit code. +; + + +%ifdef BS2_INC_CMN_PM +;; +; Switches from 32-bit to 16-bit mode ({eip,esp,ebp} < 64KB). +; +; @returns cs,ds,ss,es,gs,fs loaded with 16-bit selectors. +; All other registers are preserved. +; @uses nothing +; +BEGINCODELOW +BITS 32 +BEGINPROC Bs2Thunk_p32_p16 + push eax + pushf + cli + mov ax, BS2_SEL_SS16 + jmp far BS2_SEL_CS16:.code16_start +BITS 16 +.code16_start: + mov ss, ax + mov ax, BS2_SEL_DS16 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + ; Fix the return address and then return. + mov ax, [esp + 08h] + mov [esp + 0ah], ax + popfd + pop eax + add sp, 2h + ret +ENDPROC Bs2Thunk_p32_p16 + %define Bs2Thunk_p32_pe16 Bs2Thunk_p32_p16 ; Alternative name for TMPL_NM + %define Bs2Thunk_p32_pp16 Bs2Thunk_p32_p16 ; Alternative name for TMPL_NM + %define Bs2Thunk_p32_pae16 Bs2Thunk_p32_p16 ; Alternative name for TMPL_NM +%endif ; BS2_INC_CMN_PM + + +%ifdef BS2_INC_CMN_PM +;; +; Switches from 16-bit to 32-bit mode (cs = CS16, ds = DS16). +; +; @returns cs,ds,ss,es,gs,fs loaded with 32-bit selectors. +; ebp and esp converted to 32bit. +; All other registers are preserved. +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC Bs2Thunk_p16_p32 + push word 0 + push eax + pushfd + cli + jmp far BS2_SEL_CS32:.code32_start +BITS 32 +.code32_start: + mov eax, BS2_SEL_SS32 + mov ss, ax + mov ax, BS2_SEL_DS32 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + and ebp, 0ffffh + and esp, 0ffffh + + ; Fix the return address and then return. + movzx eax, word [esp + 0ah] + mov [esp + 08h], eax + popfd + pop eax + ret +ENDPROC Bs2Thunk_p16_p32 + %define Bs2Thunk_p16_pe32 Bs2Thunk_p16_p32 ; Alternative name for TMPL_NM + %define Bs2Thunk_p16_pp32 Bs2Thunk_p16_p32 ; Alternative name for TMPL_NM + %define Bs2Thunk_p16_pae32 Bs2Thunk_p16_p32 ; Alternative name for TMPL_NM +%endif ; BS2_INC_CMN_PM + + +%ifdef BS2_INC_CMN_LM +;; +; Switches from 64-bit to 16-bit mode ({rip,rsp,rbp} < 64KB). +; +; @returns cs,ds,ss,es,gs,fs loaded with 16-bit selectors. +; All other registers are preserved. +; @uses nothing +; +BEGINCODELOW +BITS 64 +BEGINPROC Bs2Thunk_lm64_lm16 + push rax + pushf + cli + mov ax, BS2_SEL_SS16 + push BS2_SEL_CS16 + push .code16_start + retf +BITS 16 +.code16_start: + mov ss, ax + mov ax, BS2_SEL_DS16 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + ; Fix the return address and then return. + mov ax, [esp + 10h] + mov [esp + 16h], ax + popfd + add sp, 4h + pop eax + add sp, 4h + 6h + ret +ENDPROC Bs2Thunk_lm64_lm16 + %define Bs2Thunk_p64_lm16 Bs2Thunk_lm64_lm16 ; Alternative name for TMPL_NM +%endif ; BS2_INC_CMN_LM + + +%ifdef BS2_INC_LM16 +;; +; Switches from 16-bit to 64-bit mode (cs = CS16, ss = SS16). +; +; @returns cs,ds,ss,es,gs,fs loaded with 64-bit selectors. +; rbp and rsp converted to 16/32/64-bit. +; All other registers are preserved. +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC Bs2Thunk_lm16_lm64 + push word 0 + push dword 0 + push dword 0 + push eax + push dword 0 + pushfd + cli + mov eax, BS2_SEL_SS64 + jmp far BS2_SEL_CS64:.code64_start +BITS 64 +.code64_start: + mov ss, ax + mov ax, BS2_SEL_DS64 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + and ebp, 0ffffh + and esp, 0ffffh + and edi, 0ffffffffh + and esi, 0ffffffffh + and ebx, 0ffffffffh + + ; Fix the return address and then return. + movzx rax, word [rsp + 16h] + mov [rsp + 10h], rax + popf + pop rax + ret +ENDPROC Bs2Thunk_lm16_lm64 + %define Bs2Thunk_p16_lm64 Bs2Thunk_lm16_lm64 ; Alternative name for TMPL_NM +%endif ; BS2_INC_LM16 + + +%ifdef BS2_INC_LM32 +;; +; Switches from 64-bit to 32-bit mode ({rip,rsp,rbp} < 4GB). +; +; @returns cs,ds,ss,es,gs,fs loaded with 32-bit selectors. +; All other registers are preserved. +; @uses nothing +; +BEGINCODEHIGH +BITS 64 +BEGINPROC Bs2Thunk_lm64_lm32 + push rax + pushf + cli + mov ax, BS2_SEL_SS32 + push BS2_SEL_CS32 + push .code32_start + retf +BITS 32 +.code32_start: + mov ss, ax + mov ax, BS2_SEL_DS32 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + ; Fix the return address and then return. + mov eax, [esp + 10h] + mov [esp + 14h], eax + popfd + mov eax, [esp + 4h] + lea esp, [esp + 10h] + ret +ENDPROC Bs2Thunk_lm64_lm32 + %define Bs2Thunk_p64_lm32 Bs2Thunk_lm64_lm32 ; Alternative name for TMPL_NM +%endif ; BS2_INC_LM32 + + +%ifdef BS2_INC_LM32 +;; +; Switches from 32-bit to 64-bit mode (cs = CS32, ss = SS32). +; +; @returns cs,ds,ss,es,gs,fs loaded with 64-bit selectors. +; rbp and rsp converted to 32/64-bit. +; All other registers are preserved. +; @uses nothing +; +BEGINCODEHIGH +BITS 32 +BEGINPROC Bs2Thunk_lm32_lm64 + push dword 0 + push dword 0 + push eax + push dword 0 + pushfd + cli + mov eax, BS2_SEL_SS64 + jmp far BS2_SEL_CS64:.code64_start +BITS 64 +.code64_start: + mov ss, ax + mov ax, BS2_SEL_DS64 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + and ebp, 0ffffffffh + and esp, 0ffffffffh + and edi, 0ffffffffh + and esi, 0ffffffffh + and ebx, 0ffffffffh + + ; Fix the return address and then return. + mov eax, [rsp + 14h] + mov [rsp + 10h], rax + popf + pop rax + ret +ENDPROC Bs2Thunk_lm32_lm64 + %define Bs2Thunk_p32_lm64 Bs2Thunk_lm32_lm64 ; Alternative name for TMPL_NM +%endif ; BS2_INC_LM32 + + + + +; +; Routines for checking if mode is supported or not. +; +; @returns al=1 & ZF=0 if supported, al=0 & ZF=1 if not. +; @uses nothing. +; + +; These are easy. +BEGINCODELOW +BITS 16 +BEGINPROC bs2IsModeSupportedYes_rm +GLOBALNAME Bs2IsModeSupported_rm_rm +GLOBALNAME Bs2IsModeSupported_rm_pe16 +GLOBALNAME Bs2IsModeSupported_rm_pe32 +GLOBALNAME Bs2IsModeSupported_rm_pev86 +GLOBALNAME Bs2IsModeSupported_rm_pp16 +GLOBALNAME Bs2IsModeSupported_rm_pp32 +GLOBALNAME Bs2IsModeSupported_rm_ppv86 + mov al, 1 + test al, al + ret +ENDPROC bs2IsModeSupportedYes_rm + + +%ifdef BS2_INC_CMN_PAE +BEGINCODELOW +BITS 16 +BEGINPROC Bs2IsPaeSupported_16 +GLOBALNAME Bs2IsModeSupported_rm_pae16 +GLOBALNAME Bs2IsModeSupported_rm_pae32 +GLOBALNAME Bs2IsModeSupported_rm_paev86 + push eax + push ebx + push ecx + push edx + + mov eax, 0 + cpuid + cmp eax, 1 + jb .no + cmp eax, 1000h + ja .no + + mov eax, 1 + cpuid + test edx, X86_CPUID_FEATURE_EDX_PAE + + pop edx + pop ecx + pop ebx + pop eax + + mov al, 0 + jz .no + mov al, 1 +.no: + ret +ENDPROC Bs2IsPaeSupported_16 +%endif ; BS2_INC_CMN_PAE + + +%ifdef BS2_INC_CMN_LM +BEGINCODELOW +BITS 16 +BEGINPROC Bs2IsLongModeSupported_16 +GLOBALNAME Bs2IsModeSupported_rm_lm16 +GLOBALNAME Bs2IsModeSupported_rm_lm32 +GLOBALNAME Bs2IsModeSupported_rm_lm64 + push eax + push ebx + push ecx + push edx + + mov eax, 080000000h + cpuid + cmp eax, 080000001h + jb .no + cmp eax, 080001000h + ja .no + + mov eax, 080000001h + cpuid + test edx, X86_CPUID_EXT_FEATURE_EDX_LONG_MODE + + pop edx + pop ecx + pop ebx + pop eax + + mov al, 0 + jz .no + mov al, 1 +.no: + ret +ENDPROC Bs2IsLongModeSupported_16 +%endif ; BS2_INC_CMN_LM + + +; +; Include addition init/base code. +; +%ifdef BS2_WITH_TRAPS + %include "bootsector2-common-init-traps.mac" +%endif + + +; +; Include common code. +; +%ifndef BS2_NOINC_COMMON + %include "bootsector2-common-routines.mac" +%endif + +; +; Include trap records if requested. +; +%ifdef BS2_WITH_TRAPRECS + %include "bootsector2-common-traprec.mac" +%endif + +; +; Map stuff for the initial environment. +; +%ifdef BS2_INIT_RM + %define TMPL_RM +%endif +%ifdef BS2_INIT_PE32 + %define TMPL_PE32 +%endif +%ifdef BS2_INIT_PP32 + %define TMPL_PP32 +%endif +%ifdef BS2_INIT_PAE32 + %define TMPL_PAE32 +%endif +%ifdef BS2_INIT_LM64 + %define TMPL_LM64 +%endif +%include "bootsector2-template-header.mac" + + +; +; Where we jump after initialization. +; +TMPL_BEGINCODE +BITS TMPL_BITS +bs2DoneInit: +%ifdef BS2_INIT_SAVE_REGS + mov eax, cr2 + mov [BS2_REG_SAVE_ADDR + BS2REGS.cr2], eax + mov eax, cr3 + mov [BS2_REG_SAVE_ADDR + BS2REGS.cr3], eax + mov eax, cr4 + mov [BS2_REG_SAVE_ADDR + BS2REGS.cr4], eax + mov byte [BS2_REG_SAVE_ADDR + BS2REGS.cBits], 16 + xor eax, eax + mov [cs:BS2_REG_SAVE_ADDR + BS2REGS.cs], ax + mov ax, start + mov [cs:BS2_REG_SAVE_ADDR + BS2REGS.rip], eax +%endif + +%ifdef BS2_WITH_TRAPRECS + ; + ; Install the trap records. + ; + BS2_TRAP_RECS_INSTALL +%endif + + ; + ; Detect the CPU. + ; + xor eax, eax + mov [g_fCpuIntel], al + mov [g_fCpuAmd], al + cpuid + cmp ecx, 0x444d4163 + jne .not_amd + cmp edx, 0x69746e65 + jne .not_amd + cmp ebx, 0x68747541 + jne .not_amd + mov byte [g_fCpuAmd], 1 + jmp .not_intel + +.not_amd: + cmp ecx, 0x6c65746e + jne .not_intel + cmp edx, 0x49656e69 + jne .not_intel + cmp ebx, 0x756e6547 + jne .not_intel + mov byte [g_fCpuIntel], 1 +.not_intel: + + ; + ; Call the user 'main' procedure (shouldn't return). + ; + call main +.panic_again + call Bs2Panic + jmp .panic_again + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-common-init-traps.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-common-init-traps.mac new file mode 100644 index 00000000..16603143 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-common-init-traps.mac @@ -0,0 +1,1619 @@ +; $Id: bootsector2-common-init-traps.mac $ +;; @file +; Common bootsector code init, traps. +; +; This is included from bootsector2-common-init-code.mac and was split out of +; that file to keep the size manageable. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%ifndef BS2_WITH_TRAPS + %error "huh? BS2_WITH_TRAPS is not defined!" +%endif + + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "bootsector2-structures.mac" +%include "bootsector2-api.mac" + + +;******************************************************************************* +;* Global Variables * +;******************************************************************************* +BEGINCODELOW +ALIGNDATA(8) +;; Where to resume execution after a trap (if g_fTrapPrepared is set). +; @internal +g_TrapResumeRIP: + dq 0 +;; Set if we've prepared for a trap. +; @internal +g_fTrapPrepared: + db 0 +;; Benchmark indicator. +; This is set to the expected trap number when we're benchmarking and 0ffh when +; we aren't benchmarking. +; @internal +g_u8TrapBenchmarkNo: + db 0ffh + db 0 ; alignment padding. +;; The last trap number. +GLOBALNAME g_u8LastTrapNo + db 0 +;; The number of traps since last call to Bs2TrapReset. +GLOBALNAME g_u32cTraps + dd 0 +;; The last trap error code. +GLOBALNAME g_u64LastTrapErr + dq 0 +;; The register frame of the last trap (BS2REGS). +GLOBALNAME g_LastTrapRegs + times (BS2REGS_size) db 0 + +;; The RFLAGS/EFLAGS inside last invoked trap handler. +GLOBALNAME g_u64LastTrapHandlerRFlags + dq 0 +;; The CS inside last invoked trap handler. +GLOBALNAME g_u16LastTrapHandlerCS + dw 0 +;; The SS inside last invoked trap handler. +GLOBALNAME g_u16LastTrapHandlerSS + dw 0 + dw 0,0 ; alignment +;; The RSP inside the last invoked trap handler, i.e. when bs2Trap_XX_32bit is +; entered, so including fake error code and vector number. +GLOBALNAME g_u64LastTrapHandlerRSP + dq 0 + +;; +; Pointer to an array of BS2TRAPREC1 records. +GLOBALNAME g_paTrapRecs + dq 0 +;; Number of entries in the array g_paTrapRecs points to. +GLOBALNAME g_cTrapRecs + dd 0 +;; The index of the last BS2TRAPREC1 we hit. +GLOBALNAME g_iTrapRecLast + dd 0 +;; The base address the BS2TRAPREC.offWhere values are relative to. +GLOBALNAME g_pTrapRecBase + dq 0 + + +;; +; Reset all the trap globals to default. +; +; This undos the effect of any previous Bs2TrapPrepare call. +; +; @uses nothing. +; +BEGINCODELOW +BITS 16 +BEGINPROC Bs2TrapReset_rm16 + push xBP + mov xBP, xSP + push ds + push word 0 + pop ds + + mov dword [g_u32cTraps], 0 + mov byte [g_u8LastTrapNo], 0ffh + mov dword [g_u64LastTrapErr], 0 + mov dword [g_u64LastTrapErr + 4], 0 + mov dword [g_TrapResumeRIP], 0 + mov dword [g_TrapResumeRIP + 4], 0 + mov byte [g_u8TrapBenchmarkNo], 0ffh + mov byte [g_fTrapPrepared], 0 + + pop ds + leave + ret +ENDPROC Bs2TrapReset_rm16 + + +;; +; Reset all the trap globals to default. +; +; This undos the effect of any previous Bs2TrapPrepare call. +; +; @uses nothing. +; +BEGINCODELOW +BITS 16 +BEGINPROC Bs2TrapReset_p16 + push ds + push BS2_SEL_DS16 + pop ds + + mov dword [g_u32cTraps], 0 + mov byte [g_u8LastTrapNo], 0ffh + mov dword [g_u64LastTrapErr], 0 + mov dword [g_u64LastTrapErr + 4], 0 + mov dword [g_TrapResumeRIP], 0 + mov dword [g_TrapResumeRIP + 4], 0 + mov byte [g_u8TrapBenchmarkNo], 0ffh + mov byte [g_fTrapPrepared], 0 + + pop ds + ret +ENDPROC Bs2TrapReset_p16 + + + +;; +; Reset all the trap globals to default. +; +; This undos the effect of any previous Bs2TrapPrepare call. +; +; @uses nothing. +; +BEGINCODEHIGH +BITS 32 +BEGINPROC Bs2TrapReset_p32 + push ds + push BS2_SEL_DS32 + pop ds + + mov dword [g_u32cTraps], 0 + mov byte [g_u8LastTrapNo], 0ffh + mov dword [g_u64LastTrapErr], 0 + mov dword [g_u64LastTrapErr + 4], 0 + mov dword [g_TrapResumeRIP], 0 + mov dword [g_TrapResumeRIP + 4], 0 + mov byte [g_u8TrapBenchmarkNo], 0ffh + mov byte [g_fTrapPrepared], 0 + + pop ds + ret +ENDPROC Bs2TrapReset_p32 + + +;; +; Reset all the trap globals to default. +; +; This undos the effect of any previous Bs2TrapPrepare call. +; +; @uses nothing. +; +BEGINCODEHIGH +BITS 64 +BEGINPROC Bs2TrapReset_p64 + mov dword [g_u32cTraps], 0 + mov byte [g_u8LastTrapNo], 0ffh + mov qword [g_u64LastTrapErr], 0 + mov qword [g_TrapResumeRIP], 0 + mov byte [g_u8TrapBenchmarkNo], 0ffh + mov byte [g_fTrapPrepared], 0 + ret +ENDPROC Bs2TrapReset_p64 + + + +;; +; Prepare for a test that will trap. +; +; @param xAX Where to resume after the trap. +; @param dl Set to 0ffh for tests and the expected trap number when +; preparing a benchmark. +; @uses nothing. +; +BEGINCODELOW +BITS 16 +BEGINPROC Bs2TrapPrepare_rm16 + push xBP + mov xBP, xSP + push ds + push word 0 + pop ds + + mov dword [g_u32cTraps], 0 + mov byte [g_u8LastTrapNo], 0ffh + mov dword [g_u64LastTrapErr], 0 + mov dword [g_u64LastTrapErr + 4], 0 + mov word [g_TrapResumeRIP], ax + mov word [g_TrapResumeRIP + 2], 0 + mov dword [g_TrapResumeRIP + 4], 0 + mov byte [g_u8TrapBenchmarkNo], dl + mov byte [g_fTrapPrepared], 1 + + pop ds + leave + ret +ENDPROC Bs2TrapPrepare_rm16 + + +;; +; Prepare for a test that will trap. +; +; @param ax Where to resume after the trap. +; @param dl Set to 0ffh for tests and the expected trap number when +; preparing a benchmark. +; @uses nothing. +; +BEGINCODELOW +BITS 16 +BEGINPROC Bs2TrapPrepare_p16 + push ds + push BS2_SEL_DS16 + pop ds + + mov dword [g_u32cTraps], 0 + mov byte [g_u8LastTrapNo], 0ffh + mov dword [g_u64LastTrapErr], 0 + mov dword [g_u64LastTrapErr + 4], 0 + mov word [g_TrapResumeRIP], ax + mov word [g_TrapResumeRIP + 2], 0 + mov dword [g_TrapResumeRIP + 4], 0 + mov byte [g_u8TrapBenchmarkNo], dl + mov byte [g_fTrapPrepared], 1 + + pop ds + ret +ENDPROC Bs2TrapPrepare_p16 + + +;; +; Prepare for a test that will trap. +; +; @param eax Where to resume after the trap. +; @param dl Set to 0ffh for tests and the expected trap number when +; preparing a benchmark. +; @uses nothing. +; +BEGINCODEHIGH +BITS 32 +BEGINPROC Bs2TrapPrepare_p32 + push ds + push BS2_SEL_DS32 + pop ds + + mov dword [g_u32cTraps], 0 + mov byte [g_u8LastTrapNo], 0ffh + mov dword [g_u64LastTrapErr], 0 + mov dword [g_u64LastTrapErr + 4], 0 + mov dword [g_TrapResumeRIP], eax + mov dword [g_TrapResumeRIP + 4], 0 + mov byte [g_u8TrapBenchmarkNo], dl + mov byte [g_fTrapPrepared], 1 + + pop ds + ret +ENDPROC Bs2TrapPrepare_p32 + + +;; +; Prepare for a test that will trap. +; +; @param rax Where to resume after the trap. +; @param dl Set to 0ffh for tests and the expected trap number when +; preparing a benchmark. +; @uses nothing. +; +BEGINCODEHIGH +BITS 64 +BEGINPROC Bs2TrapPrepare_p64 + mov dword [g_u32cTraps], 0 + mov byte [g_u8LastTrapNo], 0ffh + mov qword [g_u64LastTrapErr], 0 + mov qword [g_TrapResumeRIP], rax + mov byte [g_u8TrapBenchmarkNo], dl + mov byte [g_fTrapPrepared], 1 + ret +ENDPROC Bs2TrapPrepare_p64 + + +BEGINCODELOW ; The TSSes, IDTs and handlers must be 16-bit addressable. + +%ifdef BS2_INC_CMN_PM +; +; 32-bit TSS (X86TSS32). +; +ALIGNDATA(16) +bs2Tss32Bit: + dw 07fffh ; selPrev - Back link to previous task. (static) + dw 0h ; padding1; + dd BS2_R0_STACK_ADDR ; esp0 - Ring-0 stack pointer. (static) + dw BS2_SEL_SS32 ; ss0 + dw 0 ; padding + dd BS2_R1_STACK_ADDR ; esp1 - Ring-1 stack pointer. (static) + dw 0 ; ss1 + dw 0 ; padding + dd BS2_R2_STACK_ADDR ; esp2 - Ring-1 stack pointer. (static) + dw 0 ; ss2 + dw 0 ; padding + dd 0ffffffffh ; cr3 - Page directory for the task. (static) + dd 0 ; eip - EIP before task switch. + dd 0 ; eflags - EFLAGS before task switch. + dd 0 ; eax - EAX before task switch. + dd 0 ; ecx - ECX before task switch. + dd 0 ; edx - EDX before task switch. + dd 0 ; ebx - EBX before task switch. + dd 0 ; esp - ESP before task switch. + dd 0 ; ebp - EBP before task switch. + dd 0 ; esi - ESI before task switch. + dd 0 ; edi - EDI before task switch. + dw 0, 0 ; es,pad - ES before task switch. + dw 0, 0 ; cs,pad - CS before task switch. + dw 0, 0 ; ss,pad - SS before task switch. + dw 0, 0 ; ds,pad - DS before task switch. + dw 0, 0 ; fs,pad - FS before task switch. + dw 0, 0 ; gs,pad - GS before task switch. + dw 0, 0 ; ldt,pad - LDTR before task switch. + dw 0 ; fDebugTrap - Debug trap flag. + dw 7fffh ; offIoBitmap - Offset relative to the TSS of the + ; start of the I/O Bitmap and the end of the + ; interrupt redirection bitmap. + ; IntRedirBitmap - 32 bytes for the virtual interrupt redirection bitmap. (VME) +bs2Tss32BitEnd: +times (68h - (bs2Tss32BitEnd - bs2Tss32Bit)) db 0 +times ((bs2Tss32BitEnd - bs2Tss32Bit) - 68h) db 0 + + +; +; 32-bit TSS for #DF (X86TSS32). +; +ALIGNDATA(16) +bs2Tss32BitDf: + dw 07fffh ; selPrev - Back link to previous task. (static) + dw 0h ; padding1; + dd BS2_DF_R0_STACK_ADDR ; esp0 - Ring-0 stack pointer. (static) + dw BS2_SEL_SS32 ; ss0 + dw 0 ; padding + dd 0 ; esp1 - Ring-1 stack pointer. (static) + dw 0 ; ss1 + dw 0 ; padding + dd 0 ; esp2 - Ring-1 stack pointer. (static) + dw 0 ; ss2 + dw 0 ; padding + dd 0ffffffffh ; cr3 - Page directory for the task. (static) + dd bs2Trap_08h_32bit ; eip - EIP before task switch. */ + dd 0 ; eflags - EFLAGS before task switch. */ + dd 0 ; eax - EAX before task switch. */ + dd 0 ; ecx - ECX before task switch. */ + dd 0 ; edx - EDX before task switch. */ + dd 0 ; ebx - EBX before task switch. */ + dd BS2_DF_R0_STACK_ADDR ; esp - ESP before task switch. */ + dd 0 ; ebp - EBP before task switch. */ + dd 0 ; esi - ESI before task switch. */ + dd 0 ; edi - EDI before task switch. */ + dw BS2_SEL_DS32, 0 ; es,pad - ES before task switch. */ + dw BS2_SEL_CS32, 0 ; cs,pad - CS before task switch. */ + dw BS2_SEL_SS32, 0 ; ss,pad - SS before task switch. */ + dw BS2_SEL_DS32, 0 ; ds,pad - DS before task switch. */ + dw BS2_SEL_DS32, 0 ; fs,pad - FS before task switch. */ + dw BS2_SEL_DS32, 0 ; gs,pad - GS before task switch. */ + dw 0, 0 ; ldt,pad- LDTR before task switch. */ + dw 0 ; fDebugTrap - Debug trap flag. + dw 7fffh ; offIoBitmap - Offset relative to the TSS of the + ; start of the I/O Bitmap and the end of the + ; interrupt redirection bitmap. + ; IntRedirBitmap - 32 bytes for the virtual interrupt redirection bitmap. (VME) +bs2Tss32BitDfEnd: +times (68h - (bs2Tss32BitDfEnd - bs2Tss32BitDf)) db 0 +times ((bs2Tss32BitDfEnd - bs2Tss32BitDf) - 68h) db 0 + + +; +; 32-bit IDT (X86DESCGATE). +; +ALIGNDATA(16) +bs2Idt32bit: + dw bs2Trap_00h_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_01h_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_02h_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate +bs2Idt32bit_BP: + dw bs2Trap_03h_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_04h_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_05h_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_06h_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_07h_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw 0, BS2_SEL_TSS32_DF, 08500h, 00000h ; p=1 dpl=0 type=taskgate + dw bs2Trap_09h_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_0ah_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_0bh_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_0ch_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_0dh_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_0eh_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_0fh_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_10h_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_11h_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_12h_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_13h_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_14h_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_15h_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_16h_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_17h_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_18h_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_19h_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_1ah_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_1bh_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_1ch_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_1dh_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_1eh_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2Trap_1fh_32bit, BS2_SEL_CS32, 08e00h, 00000h ; p=1 dpl=0 type=int32gate + dw bs2TrapService32bit,BS2_SEL_CS32,0ee00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=3 type=int32gate +%define BS2_TRAP_SERVICE_NO 30h +;; @todo +bs2Idt32bitEnd + +; +; 32-bit trap handlers. +; +BITS 32 +%macro bs2Trap_XX_32bit_macro 1 +bs2Trap_ %+ %1 %+ _32bit: + push %1 + jmp bs2Trap_XX_32bit +%endmacro +%macro bs2Trap_XX_32bit_macro_no_err 1 +bs2Trap_ %+ %1 %+ _32bit: + push 0 + push %1 + jmp bs2Trap_XX_32bit +%endmacro + bs2Trap_XX_32bit_macro_no_err 00h + bs2Trap_XX_32bit_macro_no_err 01h + bs2Trap_XX_32bit_macro_no_err 02h + bs2Trap_XX_32bit_macro_no_err 03h + bs2Trap_XX_32bit_macro_no_err 04h + bs2Trap_XX_32bit_macro_no_err 05h + bs2Trap_XX_32bit_macro_no_err 06h + bs2Trap_XX_32bit_macro_no_err 07h + bs2Trap_XX_32bit_macro 08h + bs2Trap_XX_32bit_macro_no_err 09h + bs2Trap_XX_32bit_macro 0ah + bs2Trap_XX_32bit_macro 0bh + bs2Trap_XX_32bit_macro 0ch + bs2Trap_XX_32bit_macro 0dh + bs2Trap_XX_32bit_macro 0eh + bs2Trap_XX_32bit_macro_no_err 0fh + bs2Trap_XX_32bit_macro_no_err 10h + bs2Trap_XX_32bit_macro 11h + bs2Trap_XX_32bit_macro_no_err 12h + bs2Trap_XX_32bit_macro_no_err 13h + bs2Trap_XX_32bit_macro_no_err 14h + bs2Trap_XX_32bit_macro_no_err 15h + bs2Trap_XX_32bit_macro_no_err 16h + bs2Trap_XX_32bit_macro_no_err 17h + bs2Trap_XX_32bit_macro_no_err 18h + bs2Trap_XX_32bit_macro_no_err 19h + bs2Trap_XX_32bit_macro_no_err 1ah + bs2Trap_XX_32bit_macro_no_err 1bh + bs2Trap_XX_32bit_macro_no_err 1ch + bs2Trap_XX_32bit_macro_no_err 1dh + bs2Trap_XX_32bit_macro_no_err 1eh + bs2Trap_XX_32bit_macro_no_err 1fh + +;; +; Common 32-bit trap handler. +; +; return GS ebp + 2ch - v86 +; return FS ebp + 28h - v86 +; return DS ebp + 24h - v86 +; return ES ebp + 20h - v86 +; return SS ebp + 1ch - higher privilege +; return ESP ebp + 18h - higher privilege +; return EFLAGS ebp + 14h +; return CS ebp + 10h +; return EIP ebp + 0ch +; error code ebp + 08h +; vector # ebp + 04h +BITS 32 +BEGINCODEHIGH +BEGINPROC bs2Trap_XX_32bit + push ebp ; ebp + 00h + mov ebp, esp + pushfd ; ebp - 04h + push eax ; ebp - 08h + push ebx ; ebp - 0ch + push ecx ; ebp - 10h + push ds ; ebp - 14h + + mov eax, ss ; load flat DS. Using SS here because of conforming IDTE.CS tests. + mov ds, ax + + pushfd ; Clear the AC flag. + and dword [esp], ~X86_EFL_AC + popfd + + ; + ; Benchmark mode? Then resume the action right away! + ; + mov eax, [ebp + 04h] + cmp [g_u8TrapBenchmarkNo], al + jne .test_mode + cmp byte [g_fTrapPrepared], 0 + je .test_mode + mov eax, [g_TrapResumeRIP] + mov [ebp + 0ch], eax + + pop ds + pop ecx + pop ebx + ;pop eax + ;popfd + leave + add esp, 08h ; Skip the vector # and error code. + xor eax, eax + iret + + + ; + ; Update the globals. + ; +.test_mode: + xor ecx, ecx ; zero register + inc dword [g_u32cTraps] + mov eax, [ebp + 04h] + mov [g_u8LastTrapNo], al + mov eax, [ebp + 08h] + mov [g_u64LastTrapErr], eax + mov [g_u64LastTrapErr + 4], ecx + mov eax, [ebp - 04h] + mov [g_u64LastTrapHandlerRFlags], eax + mov dword [g_u64LastTrapHandlerRFlags + 4], ecx + mov ax, cs + mov [g_u16LastTrapHandlerCS], ax + mov ax, ss + mov [g_u16LastTrapHandlerSS], ax + lea eax, [ebp + 4] + mov [g_u64LastTrapHandlerRSP], eax + mov [g_u64LastTrapHandlerRSP + 4], ecx + + ; + ; Save the registers. + ; + lea ebx, [g_LastTrapRegs] + mov eax, [ebp - 08h] + mov [ebx + BS2REGS.rax], eax + mov [ebx + BS2REGS.rax + 4], ecx + mov eax, [ebp - 0ch] + mov [ebx + BS2REGS.rbx], eax + mov [ebx + BS2REGS.rbx + 4], ecx + mov eax, [ebp - 10h] + mov [ebx + BS2REGS.rcx], eax + mov [ebx + BS2REGS.rcx + 4], ecx + mov [ebx + BS2REGS.rdx], edx + mov [ebx + BS2REGS.rdx + 4], ecx + mov [ebx + BS2REGS.rdi], edi + mov [ebx + BS2REGS.rdi + 4], ecx + mov [ebx + BS2REGS.rsi], esi + mov [ebx + BS2REGS.rsi + 4], ecx + mov eax, [ebp] + mov [ebx + BS2REGS.rbp], eax + mov [ebx + BS2REGS.rbp + 4], ecx + mov eax, [ebp + 0ch] + mov [ebx + BS2REGS.rip], eax + mov [ebx + BS2REGS.rip + 4], ecx + mov [ebx + BS2REGS.r8], ecx + mov [ebx + BS2REGS.r8 + 4], ecx + mov [ebx + BS2REGS.r9], ecx + mov [ebx + BS2REGS.r9 + 4], ecx + mov [ebx + BS2REGS.r10], ecx + mov [ebx + BS2REGS.r10 + 4], ecx + mov [ebx + BS2REGS.r11], ecx + mov [ebx + BS2REGS.r11 + 4], ecx + mov [ebx + BS2REGS.r12], ecx + mov [ebx + BS2REGS.r12 + 4], ecx + mov [ebx + BS2REGS.r13], ecx + mov [ebx + BS2REGS.r13 + 4], ecx + mov [ebx + BS2REGS.r14], ecx + mov [ebx + BS2REGS.r14 + 4], ecx + mov [ebx + BS2REGS.r15], ecx + mov [ebx + BS2REGS.r15 + 4], ecx + mov eax, [ebp + 14h] + mov [ebx + BS2REGS.rflags], eax + mov [ebx + BS2REGS.rflags+4],ecx + mov eax, [ebp + 10h] + mov [ebx + BS2REGS.cs], ax + mov [ebx + BS2REGS.cBits], byte 32 + + ; Part of the stack varies depending on the trap context. + test dword [ebx + BS2REGS.rflags], X86_EFL_VM + jnz .v86 + test ax, 7h + jz .ring0 + +.ring0: + lea eax, [ebp + 18h] + mov [ebx + BS2REGS.rsp], eax + mov [ebx + BS2REGS.rsp + 4], ecx + mov [ebx + BS2REGS.ss], ss + mov eax, [ebp - 14h] + mov [ebx + BS2REGS.ds], ax + mov [ebx + BS2REGS.es], es + mov [ebx + BS2REGS.fs], fs + mov [ebx + BS2REGS.gs], gs + jmp .do_crX + +.higher_privilege: + mov eax, [ebp + 18h] + mov [ebx + BS2REGS.rsp], eax + mov [ebx + BS2REGS.rsp + 4], ecx + mov eax, [ebp + 20h] + mov [ebx + BS2REGS.ss], ax + mov eax, [ebp - 14h] + mov [ebx + BS2REGS.ds], ax + mov [ebx + BS2REGS.es], es + mov [ebx + BS2REGS.fs], fs + mov [ebx + BS2REGS.gs], gs + jmp .do_crX + +.v86: + mov eax, [ebp + 18h] + mov [ebx + BS2REGS.rsp], eax + mov [ebx + BS2REGS.rsp + 4], ecx + mov eax, [ebp + 1ch] + mov [ebx + BS2REGS.ss], ax + mov eax, [ebp + 24h] + mov [ebx + BS2REGS.ds], ax + mov eax, [ebp + 20h] + mov [ebx + BS2REGS.es], ax + mov eax, [ebp + 28h] + mov [ebx + BS2REGS.fs], ax + mov eax, [ebp + 2ch] + mov [ebx + BS2REGS.gs], ax + ;jmp .do_crX + +.do_crX: + ; The CRx registers are only accessible from ring-0 (CS=conforming, CPL < 0) + test byte [ebx + BS2REGS.ss], 3 + jnz .skip_crX + mov eax, cr0 + mov [ebx + BS2REGS.cr0], eax + mov [ebx + BS2REGS.cr0 + 4], ecx + mov eax, cr2 + mov [ebx + BS2REGS.cr2], eax + mov [ebx + BS2REGS.cr2 + 4], ecx + mov eax, cr3 + mov [ebx + BS2REGS.cr3], eax + mov [ebx + BS2REGS.cr3 + 4], ecx + mov eax, cr4 + mov [ebx + BS2REGS.cr4], eax + mov [ebx + BS2REGS.cr4 + 4], ecx + mov [ebx + BS2REGS.cr8], ecx + mov [ebx + BS2REGS.cr8 + 4], ecx +.skip_crX: + + ; + ; Advance to a prepared resume position or panic. + ; + cmp byte [g_fTrapPrepared], 0 + je .no_resume_pos + mov byte [g_fTrapPrepared], 0 + mov eax, [g_TrapResumeRIP] + mov [ebp + 0ch], eax + +.resume: +%ifdef BS2_WITH_XCPT_DB_CLEARING_TF + cmp byte [ebp + 04h], X86_XCPT_DB ; make sure we won't trap again due to a TF. + jne .resume_no_clear_trap_flags + and word [ebp + 14h], ~X86_EFL_TF +.resume_no_clear_trap_flags: +%endif + pop ds + pop ecx + pop ebx + pop eax + ;popfd + leave + add esp, 8h + iret + + +.no_resume_pos: + ; + ; Look for a trap record. + ; + mov ecx, [g_cTrapRecs] ; the number of records. + test ecx, ecx + jz .panic + mov eax, [g_LastTrapRegs + BS2REGS.rip] + sub eax, [g_pTrapRecBase] ; the offWhere we're looking for. + jb .panic + + ; Look starting at the previous record first. + mov ebx, [g_iTrapRecLast] + sub ecx, ebx + jbe .traprec_loop2 ; g_iTrapRecLast is out of range. + shl ebx, BS2TRAPREC_SIZE_SHIFT + add ebx, [g_paTrapRecs] ; ebx points to the record we hit last time. +.traprec_loop1_next: + cmp [ebx + BS2TRAPREC.offWhere], eax + je .traprec_found + add ebx, BS2TRAPREC_size + dec ecx + jnz .traprec_loop1_next + + ; Start searching from the start, stopping at the previous record. +.traprec_loop2: + mov ecx, [g_iTrapRecLast] + or ecx, ecx + jz .panic ; not found. + mov ebx, [g_paTrapRecs] +.traprec_loop2_next: + cmp [ebx + BS2TRAPREC.offWhere], eax + je .traprec_found + add ebx, BS2TRAPREC_size + dec ecx + jnz .traprec_loop2_next + jmp .panic ; not found + +.traprec_found: + ; Remember the hit for the next trap. + mov eax, ebx + sub eax, [g_paTrapRecs] + shr eax, BS2TRAPREC_SIZE_SHIFT + mov [g_iTrapRecLast], eax + + ; + ; Fail the test if we got the wrong trap or error code. + ; + mov al, [g_u8LastTrapNo] + cmp al, [ebx + BS2TRAPREC.u8TrapNo] + je .traprec_ok_trap + push eax + movzx eax, byte [ebx + BS2TRAPREC.u8TrapNo] + push eax + push .s_szWrongTrap + call NAME(TestFailedF_p32) + add esp, 12 + +.traprec_ok_trap: + mov ax, [g_u64LastTrapErr] + cmp ax, [ebx + BS2TRAPREC.u16ErrCd] + je .traprec_ok_err_cd + push eax + movzx eax, word [ebx + BS2TRAPREC.u16ErrCd] + push eax + push .s_szWrongErrCd + call NAME(TestFailedF_p32) + add esp, 12 + +.traprec_ok_err_cd: + ; + ; Advance the EIP and resume execution. + ; + movzx eax, byte [ebx + BS2TRAPREC.offResumeAddend] + add eax, [g_LastTrapRegs + BS2REGS.rip] + mov [ebp + 0ch], eax + jmp .resume + + + ; + ; Write panic message and then halt. + ; +.panic: + push dword [g_LastTrapRegs + BS2REGS.rflags] + push dword [g_LastTrapRegs + BS2REGS.ss] + push dword [g_LastTrapRegs + BS2REGS.gs] + push dword [g_LastTrapRegs + BS2REGS.fs] + push dword [g_LastTrapRegs + BS2REGS.es] + push dword [g_LastTrapRegs + BS2REGS.ds] + push dword [g_LastTrapRegs + BS2REGS.cs] + ; line break + push dword [g_LastTrapRegs + BS2REGS.cr4] + push dword [g_LastTrapRegs + BS2REGS.cr3] + push dword [g_LastTrapRegs + BS2REGS.cr0] + push dword [g_LastTrapRegs + BS2REGS.rbp] + push dword [g_LastTrapRegs + BS2REGS.rsp] + push dword [g_LastTrapRegs + BS2REGS.rip] + ; line break + push dword [g_LastTrapRegs + BS2REGS.rdi] + push dword [g_LastTrapRegs + BS2REGS.rsi] + push dword [g_LastTrapRegs + BS2REGS.rdx] + push dword [g_LastTrapRegs + BS2REGS.rcx] + push dword [g_LastTrapRegs + BS2REGS.rbx] + push dword [g_LastTrapRegs + BS2REGS.rax] + ; line break + mov eax, [ebp + 08h] + push eax + mov eax, cr2 + push eax + mov eax, [ebp + 0ch] + push eax + movzx eax, word [ebp + 10h] + push eax + movzx eax, byte [ebp + 04h] + push eax + push .s_szPanicMsg + call NAME(TestFailedF_p32) + + call Bs2Panic + jmp .panic ; paranoia + +.s_szPanicMsg: + db 'trap #%RX8 at %RX16:%RX32 cr2=%RX32 err=%RX32', 13, 10 + db 'eax=%RX32 ebx=%RX32 ecx=%RX32 edx=%RX32 esi=%RX32 edi=%RX32', 13, 10 + db 'eip=%RX32 esp=%RX32 ebp=%RX32 cr0=%RX32 cr3=%RX32 cr4=%RX32', 13, 10 + db 'cs=%RX16 ds=%RX16 es=%RX16 fs=%RX16 gs=%RX16 ss=%RX16 eflags=%RX32', 13, 10 + db 0 +.s_szWrongTrap: + db 'Expected trap %RX8 got %RX8', 13, 10, 0 +.s_szWrongErrCd: + db 'Expected errcd %RX16 got %RX16', 13, 10, 0 +ENDPROC bs2Trap_XX_32bit + +;; +; Service IRQ handler, 32-bit version. +; +; Takes requests in eax and later maybe parameters in other registers. +; +; return GS ebp + 24h - v86 +; return FS ebp + 20h - v86 +; return DS ebp + 1ch - v86 +; return ES ebp + 18h - v86 +; return SS ebp + 14h - higher privilege +; return ESP ebp + 10h - higher privilege +; return EFLAGS ebp + 0ch +; return CS ebp + 08h +; return EIP ebp + 04h +BEGINCODELOW +BEGINPROC bs2TrapService32bit + jmp .highsegment +BEGINCODEHIGH +.highsegment: + push ebp ; ebp + mov ebp, esp + push eax ; ebp - 04h + push edx ; ebp - 08h + push ecx ; ebp - 0ch + push ebx ; ebp - 10h + push ds ; ebp - 14h + + mov dx, ss + mov ds, dx + + ; + ; Classify the caller context in cl. + ;; @todo What if CS on the stack is conforming? + ; +%define BS2_TRP_SRV_CALLER_SAME_RING 0 +%define BS2_TRP_SRV_CALLER_OTHER_RING 1 +%define BS2_TRP_SRV_CALLER_VM 2 + test dword [ebp + 0ch], X86_EFL_VM + jnz .vm_ctx + + mov cx, ss + mov ch, [ebp + 08h] ; cs + and cx, 00303h + cmp ch, cl + jz .same_ctx + mov cl, BS2_TRP_SRV_CALLER_OTHER_RING + jmp .done_ctx +.vm_ctx: + mov cl, BS2_TRP_SRV_CALLER_VM + jmp .done_ctx +.same_ctx: + mov cl, BS2_TRP_SRV_CALLER_SAME_RING +.done_ctx: + + ; + ; Switch (eax). + ; + cmp eax, BS2_SYSCALL_TO_RING3 + jbe .to_ringX + + ; Unknown request. +.failure: + mov eax, -1 +.return: ; careful with ebp here! + pop ds + pop ebx + pop ecx + pop edx + ;pop eax + leave + iretd + + ; + ; Switching to the ring specified by eax. + ; Annoying that ss:esp isn't always restored. + ; +.to_ringX: + cmp cl, BS2_TRP_SRV_CALLER_VM + je .failure + sub al, BS2_SYSCALL_TO_RING0 + + ; Fake missing stack registers if necessary. + cmp cl, BS2_TRP_SRV_CALLER_SAME_RING + jnz .have_stack_regs + + sub esp, 8h + sub ebp, 8h + xor ebx, ebx +.move_more: + mov edx, [esp + 8 + ebx] + mov [esp + ebx], edx + add ebx, 4 + cmp ebx, 9*4 + jb .move_more + + mov dx, ss + mov [ebp + 14h], edx + lea edx, [ebp + 18h] + mov [ebp + 10h], edx + +.have_stack_regs: + ; Translate the selector registers + mov dx, [ebp - 14h] + call bs2SRegToRing + mov [ebp - 14h], dx + + mov dx, es + call bs2SRegToRing + mov es, dx + + mov dx, fs + call bs2SRegToRing + mov fs, dx + + mov dx, gs + call bs2SRegToRing + mov gs, dx + + mov dx, [ebp + 08h] ; cs + call bs2SRegToRing + mov [ebp + 08h], dx + + mov dx, [ebp + 14h] ; ss + call bs2SRegToRing + mov [ebp + 14h], dx + + or dword [ebp + 0ch], X86_EFL_IOPL ; set IOPL=3 + + ; If the desired target is ring-0 we cannot use iret. + cmp al, 0 + je .iret_to_ring_with_stack + +.done_success: + xor eax, eax + jmp .return + +.iret_to_ring_with_stack: + ; + ; Move the iret-to-same-ring to the desired return position. By also + ; moving the saved ebp we make the leave instruction do stack + ; adjusting/switching for us. + ; + cli ; paranoia, it's disable already. + mov eax, [ebp + 10h] + lea edx, [ebp + 18h] + cmp eax, edx + lea ecx, [ebp + 08h] ; same stack, just shifted 8 bytes + je .move_iret_and_ebp + mov ecx, [ebp + 10h] ; different stack. + sub ecx, 10h +.move_iret_and_ebp: + mov edx, [ebp + 0ch] + mov eax, [ebp + 08h] + mov [ecx + 0ch], edx + mov [ecx + 08h], eax + mov edx, [ebp + 04h] + mov eax, [ebp + 00h] + mov [ecx + 04h], edx + mov [ecx + 00h], eax + mov ebp, ecx + xor eax, eax + jmp .return + +ENDPROC bs2TrapService32bit + +%endif ; BS2_INC_CMN_PM + + +%ifdef BS2_INC_CMN_LM +; +; 64-bit TSS (X86TSS64). +; +BEGINCODELOW +ALIGNDATA(16) +bs2Tss64Bit: + dd 0 ; 00h - u32Reserved - Reserved. + dq BS2_R0_STACK_ADDR ; 04h - rsp0 - Ring-0 stack pointer. (static) + dq BS2_R1_STACK_ADDR ; 1ch - rsp1 - Ring-1 stack pointer. (static) + dq BS2_R2_STACK_ADDR ; 14h - rsp2 - Ring-2 stack pointer. (static) + dq 0 ; 2ch - reserved + dq BS2_DF_R0_STACK_ADDR ; 24h - ist1; + dq BS2_R0_STACK_ADDR ; 3ch - ist2; + dq BS2_R0_STACK_ADDR ; 34h - ist3; + dq BS2_R0_STACK_ADDR ; 4ch - ist4; + dq BS2_R0_STACK_ADDR ; 44h - ist5; + dq BS2_R0_STACK_ADDR ; 5ch - ist6; + dq BS2_R0_STACK_ADDR ; 54h - ist7; + dw 0,0,0,0,0 ; 6ch - reserved + dw 0 ; 76h - offIoBitmap - Offset relative to the TSS of the + ; 00h - start of the I/O Bitmap and the end of the + ; 00h - interrupt redirection bitmap. +bs2Tss64BitEnd: +times (68h - (bs2Tss64BitEnd - bs2Tss64Bit)) db 0 +times ((bs2Tss64BitEnd - bs2Tss64Bit) - 68h) db 0 + +; +; 64-bit IDT (X86DESC64GATE). +; +BEGINCODELOW +ALIGNDATA(16) +bs2Idt64bit: + dw bs2Trap_00h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_01h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_02h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate +bs2Idt64bit_BP: + dw bs2Trap_03h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_04h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_05h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_06h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_07h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_08h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_09h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_0ah_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_0bh_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_0ch_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_0dh_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_0eh_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_0fh_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_10h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_11h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_12h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_13h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_14h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_15h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_16h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_17h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_18h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_19h_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_1ah_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_1bh_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_1ch_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_1dh_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_1eh_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2Trap_1fh_64bit, BS2_SEL_CS64, 08e00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=0 type=int64gate + dw bs2TrapService64bit,BS2_SEL_CS64,0ee00h, 00000h, 0, 0, 0, 0 ; p=1 dpl=3 type=int64gate +bs2Idt64bitEnd + + +; +; 64-bit trap handlers. +; +BITS 64 +%macro bs2Trap_XX_64bit_macro 1 +BEGINCODELOW +bs2Trap_ %+ %1 %+ _64bit: + push %1 + jmp bs2Trap_XX_64bit +%endmacro +%macro bs2Trap_XX_64bit_macro_no_err 1 +bs2Trap_ %+ %1 %+ _64bit: + push 0 + push %1 + jmp bs2Trap_XX_64bit +%endmacro + bs2Trap_XX_64bit_macro_no_err 00h + bs2Trap_XX_64bit_macro_no_err 01h + bs2Trap_XX_64bit_macro_no_err 02h + bs2Trap_XX_64bit_macro_no_err 03h + bs2Trap_XX_64bit_macro_no_err 04h + bs2Trap_XX_64bit_macro_no_err 05h + bs2Trap_XX_64bit_macro_no_err 06h + bs2Trap_XX_64bit_macro_no_err 07h + bs2Trap_XX_64bit_macro 08h + bs2Trap_XX_64bit_macro_no_err 09h + bs2Trap_XX_64bit_macro 0ah + bs2Trap_XX_64bit_macro 0bh + bs2Trap_XX_64bit_macro 0ch + bs2Trap_XX_64bit_macro 0dh + bs2Trap_XX_64bit_macro 0eh + bs2Trap_XX_64bit_macro_no_err 0fh + bs2Trap_XX_64bit_macro_no_err 10h + bs2Trap_XX_64bit_macro 11h + bs2Trap_XX_64bit_macro_no_err 12h + bs2Trap_XX_64bit_macro_no_err 13h + bs2Trap_XX_64bit_macro_no_err 14h + bs2Trap_XX_64bit_macro_no_err 15h + bs2Trap_XX_64bit_macro_no_err 16h + bs2Trap_XX_64bit_macro_no_err 17h + bs2Trap_XX_64bit_macro_no_err 18h + bs2Trap_XX_64bit_macro_no_err 19h + bs2Trap_XX_64bit_macro_no_err 1ah + bs2Trap_XX_64bit_macro_no_err 1bh + bs2Trap_XX_64bit_macro_no_err 1ch + bs2Trap_XX_64bit_macro_no_err 1dh + bs2Trap_XX_64bit_macro_no_err 1eh + bs2Trap_XX_64bit_macro_no_err 1fh + +;; +; Common 64-bit trap handler. +; +; return SS rbp + 38h +; return RSP rbp + 30h +; return RFLAGS rbp + 28h +; return CS rbp + 20h +; return RIP rbp + 18h +; error code rbp + 10h +; vector # rbp + 08h +BEGINCODEHIGH +BEGINPROC bs2Trap_XX_64bit + push rbp ; rbp + 00h + mov rbp, rsp + pushfq ; rbp - 08h + push rax ; rbp - 10h + push rbx ; rbp - 18h + + ; + ; Benchmark mode? Then resume the action right away! + ; + mov rax, [rbp + 08h] + cmp [g_u8TrapBenchmarkNo], al + jne .test_mode + cmp byte [g_fTrapPrepared], 0 + je .test_mode + mov rax, [g_TrapResumeRIP] + mov [rbp + 18h], rax + + pop rbx + ;pop rax + ;popfq + leave + add rsp, 10h ; Skip the vector # and error code. + xor rax, rax + iretq + + ; + ; Save the trap information + ; +.test_mode: + inc dword [g_u32cTraps] + mov rax, [rbp + 08h] + mov [g_u8LastTrapNo], al + mov rax, [rbp + 10h] + mov [g_u64LastTrapErr], rax + mov rax, [rbp - 08h] + mov [g_u64LastTrapHandlerRFlags], rax + mov ax, cs + mov [g_u16LastTrapHandlerCS], ax + mov ax, ss + mov [g_u16LastTrapHandlerSS], ax + lea rax, [rbp + 8] + mov [g_u64LastTrapHandlerRSP], rax + + ; Save the registers. + lea rbx, [g_LastTrapRegs] + mov rax, [rbp - 10h] + mov [rbx + BS2REGS.rax], rax + mov rax, [rbp - 18h] + mov [rbx + BS2REGS.rbx], rax + mov [rbx + BS2REGS.rcx], rcx + mov [rbx + BS2REGS.rdx], rdx + mov [rbx + BS2REGS.rdi], rdi + mov [rbx + BS2REGS.rsi], rsi + mov rax, [rbp] + mov [rbx + BS2REGS.rbp], rax + mov rax, [rbp + 30h] + mov [rbx + BS2REGS.rsp], rax + mov rax, [rbp + 18h] + mov [rbx + BS2REGS.rip], rax + mov [rbx + BS2REGS.r8], r8 + mov [rbx + BS2REGS.r9], r9 + mov [rbx + BS2REGS.r10], r10 + mov [rbx + BS2REGS.r11], r11 + mov [rbx + BS2REGS.r12], r12 + mov [rbx + BS2REGS.r13], r13 + mov [rbx + BS2REGS.r14], r14 + mov [rbx + BS2REGS.r15], r15 + mov rax, [rbp + 28h] + mov [rbx + BS2REGS.rflags], rax + mov rax, [rbp + 20h] + mov [rbx + BS2REGS.cs], ax + mov [rbx + BS2REGS.ds], ds + mov [rbx + BS2REGS.es], es + mov [rbx + BS2REGS.fs], fs + mov [rbx + BS2REGS.gs], gs + mov rax, [rbp + 38h] + mov [rbx + BS2REGS.ss], ax + mov [rbx + BS2REGS.cBits], byte 64 + + ; The CRx registers are only accessible from ring-0 (CS=conforming, CPL < 0) + test byte [rbx + BS2REGS.ss], 3 + jnz .skip_crX + mov rax, cr0 + mov [rbx + BS2REGS.cr0], rax + mov rax, cr2 + mov [rbx + BS2REGS.cr2], rax + mov rax, cr3 + mov [rbx + BS2REGS.cr3], rax + mov rax, cr4 + mov [rbx + BS2REGS.cr4], rax + mov rax, cr8 + mov [rbx + BS2REGS.cr8], rax +.skip_crX: + + ; + ; Advance to a prepared resume position or panic. + ; + cmp byte [g_fTrapPrepared], 0 + je .no_resume_pos + mov byte [g_fTrapPrepared], 0 + mov rax, [g_TrapResumeRIP] + mov [rbp + 18h], rax + jmp .resume + +.resume: +%ifdef BS2_WITH_XCPT_DB_CLEARING_TF + cmp byte [rbp + 08h], X86_XCPT_DB ; make sure we won't trap again due to a TF. + jne .resume_no_clear_trap_flags + and word [rbp + 28h], ~X86_EFL_TF +.resume_no_clear_trap_flags: +%endif + pop rbx + pop rax + ;popfq + leave + add rsp, 10h + iretq + + +.no_resume_pos: + ; + ; Look for a trap record. + ; + push rcx + + mov ecx, [g_cTrapRecs] ; the number of records. + test ecx, ecx + jz .panic + mov rax, [g_LastTrapRegs + BS2REGS.rip] + sub rax, [g_pTrapRecBase] ; the offWhere we're looking for. + jb .panic + mov rbx, _4G + cmp rax, rbx + jae .panic ; out of range. + + ; Look starting at the previous record first. + mov ebx, [g_iTrapRecLast] + sub ecx, ebx + jbe .traprec_loop2 ; g_iTrapRecLast is out of range. + shl rbx, BS2TRAPREC_SIZE_SHIFT + add rbx, [g_paTrapRecs] ; ebx points to the record we hit last time. +.traprec_loop1_next: + cmp [rbx + BS2TRAPREC.offWhere], eax + je .traprec_found + add rbx, BS2TRAPREC_size + dec ecx + jnz .traprec_loop1_next + + ; Start searching from the start, stopping at the previous record. +.traprec_loop2: + mov ecx, [g_iTrapRecLast] + or ecx, ecx + jz .panic ; not found. + mov rbx, [g_paTrapRecs] +.traprec_loop2_next: + cmp [rbx + BS2TRAPREC.offWhere], eax + je .traprec_found + add rbx, BS2TRAPREC_size + dec ecx + jnz .traprec_loop2_next + jmp .panic ; not found + +.traprec_found: + ; Remember the hit for the next trap. + mov rax, rbx + sub rax, [g_paTrapRecs] + shr rax, BS2TRAPREC_SIZE_SHIFT + mov [g_iTrapRecLast], eax + + ; + ; Fail the test if we got the wrong trap or error code. + ; + mov al, [g_u8LastTrapNo wrt rip] + cmp al, [rbx + BS2TRAPREC.u8TrapNo] + je .traprec_ok_trap + push rax + movzx rax, byte [rbx + BS2TRAPREC.u8TrapNo] + push rax + push .s_szWrongTrap + call NAME(TestFailedF_p64) + add rsp, 24 + +.traprec_ok_trap: + mov ax, [g_u64LastTrapErr wrt rip] + cmp ax, [rbx + BS2TRAPREC.u16ErrCd] + je .traprec_ok_err_cd + push rax + movzx rax, word [rbx + BS2TRAPREC.u16ErrCd] + push rax + push .s_szWrongErrCd + call NAME(TestFailedF_p64) + add rsp, 24 + +.traprec_ok_err_cd: + ; + ; Advance the EIP and resume execution. + ; + movzx rax, byte [rbx + BS2TRAPREC.offResumeAddend] + add rax, [g_LastTrapRegs + BS2REGS.rip] + mov [rbp + 18h], rax + + pop rcx + jmp .resume + + + ; + ; Format a panic message and halt. + ; +.panic: + lea rbx, [g_LastTrapRegs] + ; line break + movzx eax, word [rbx + BS2REGS.ss] + push rax + movzx eax, word [rbx + BS2REGS.gs] + push rax + movzx eax, word [rbx + BS2REGS.fs] + push rax + movzx eax, word [rbx + BS2REGS.es] + push rax + movzx eax, word [rbx + BS2REGS.ds] + push rax + movzx eax, word [rbx + BS2REGS.cs] + push rax + ; line break + push qword [rbx + BS2REGS.rbp] + push qword [rbx + BS2REGS.rsp] + push qword [rbx + BS2REGS.rip] + ; line break + push qword [rbx + BS2REGS.rflags] + push qword [rbx + BS2REGS.r15] + push qword [rbx + BS2REGS.r14] + ; line break + push qword [rbx + BS2REGS.r13] + push qword [rbx + BS2REGS.r12] + push qword [rbx + BS2REGS.r11] + ; line break + push qword [rbx + BS2REGS.r10] + push qword [rbx + BS2REGS.r9] + push qword [rbx + BS2REGS.r8] + ; line break + push qword [rbx + BS2REGS.rdi] + push qword [rbx + BS2REGS.rsi] + push qword [rbx + BS2REGS.rdx] + ; line break + push qword [rbx + BS2REGS.rcx] + push qword [rbx + BS2REGS.rbx] + push qword [rbx + BS2REGS.rax] + ; line break + mov eax, [rbx + BS2REGS.cr8] + push rax + mov eax, [rbx + BS2REGS.cr4] + push rax + mov eax, [rbx + BS2REGS.cr3] + push rax + mov eax, [rbx + BS2REGS.cr0] + push rax + ; line break + push qword [rbp + 10h] + push qword [rbx + BS2REGS.cr2] + push qword [rbx + BS2REGS.rip] + movzx eax, word [rbp + BS2REGS.ss] + push rax + movzx eax, byte [rbp + 08h] + push rax + push .s_szPanicMsg + call NAME(TestFailedF_p64) + + call Bs2Panic + jmp .panic ; paranoia + +.s_szPanicMsg: + db 'trap #%RX8 at %RX16:%RX64 cr2=%RX64 err=%RX64', 13, 10 + db 'cr0=%RX64 cr3=%RX64 cr4=%RX64 cr8=%RX16', 13, 10 + db 'rax=%RX64 rbx=%RX64 rcx=%RX64', 13, 10 + db 'rdx=%RX64 rsi=%RX64 rdi=%RX64', 13, 10 + db 'r8 =%RX64 r9 =%RX64 r10=%RX64', 13, 10 + db 'r11=%RX64 r12=%RX64 r13=%RX64', 13, 10 + db 'r14=%RX64 r15=%RX64 rfl=%RX64', 13, 10 + db 'rip=%RX64 rsp=%RX64 rbp=%RX64 ', 13, 10 + db 'cs=%RX16 ds=%RX16 es=%RX16 fs=%RX16 gs=%RX16 ss=%RX16', 13, 10 + db 0 +.s_szWrongTrap: + db 'Expected trap %RX8 got %RX8', 13, 10, 0 +.s_szWrongErrCd: + db 'Expected errcd %RX16 got %RX16', 13, 10, 0 +ENDPROC bs2Trap_XX_64bit + + +;; +; Service IRQ handler. +; +; Takes requests in eax and later maybe parameters in other registers. +; +; return SS rbp + 28h +; return RSP rbp + 20h +; return RFLAGS rbp + 18h +; return CS rbp + 10h +; return RIP rbp + 08h +BEGINCODELOW +BEGINPROC bs2TrapService64bit + jmp .highsegment +BEGINCODEHIGH +.highsegment: + push rbp + mov rbp, rsp + push rax + push rdx + push rcx + + + ; + ; Switch (eax). + ; + cmp eax, BS2_SYSCALL_TO_RING3 + jbe .to_ringX + + ; Unknown request. + mov rax, -1 +.return: + pop rcx + pop rdx + ;pop rax + leave + iretq + + ; + ; Switching to the ring specified by eax. + ; +.to_ringX: + sub eax, BS2_SYSCALL_TO_RING0 ; al = new ring number. + + mov dx, ds + call bs2SRegToRing + mov ds, dx + + mov dx, es + call bs2SRegToRing + mov es, dx + + mov dx, fs + call bs2SRegToRing + mov fs, dx + + mov dx, gs + call bs2SRegToRing + mov gs, dx + + mov dx, [rbp + 10h] ; cs + call bs2SRegToRing + mov [rbp + 10h], dx + + mov dx, [rbp + 28h] ; ss + call bs2SRegToRing + mov [rbp + 28h], dx + + or dword [ebp + 18h], X86_EFL_IOPL ; set IOPL=3 + + jmp .done_success + +.done_success: + xor eax, eax + jmp .return + +ENDPROC bs2TrapService64bit + +%endif ; BS2_INC_CMN_LM + + +;; +; Converts a segment value (dx) to the ring specified by al. +; +; If the selector isn't a known CS, DS or SS selector it will be set to null. +; +; @returns dx +; @param al The desired ring. +; @param dx The segment to convert. +; +; @remarks WARNING! This has to work exactly the same both in 32-bit and 64-bit mode. +; +BEGINCODEHIGH +BITS 32 +BEGINPROC bs2SRegToRing + ; + ; Classify the incoming selector. + ; + cmp dx, BS2_SEL_R0_BASE + jb .null + cmp dx, BS2_SEL_R0_BASE + BS2_SEL_GRP_SIZE + jb .ring0 + + cmp dx, BS2_SEL_R1_BASE + jb .miss + cmp dx, BS2_SEL_R1_BASE + BS2_SEL_GRP_SIZE + jb .ring1 + + cmp dx, BS2_SEL_R2_BASE + jb .miss + cmp dx, BS2_SEL_R2_BASE + BS2_SEL_GRP_SIZE + jb .ring2 + + cmp dx, BS2_SEL_R3_BASE + jb .miss + cmp dx, BS2_SEL_R3_BASE + BS2_SEL_GRP_SIZE + jb .ring3 + jmp .miss + + ; + ; Convert the incoming selector to ring-0 and then from ring-0 to the + ; desired one. + ; +.ring0: + cmp al, 0 + je .done + + add dx, BS2_SEL_R1_BASE - BS2_SEL_R0_BASE + cmp al, 1 + je .done + + add dx, BS2_SEL_R2_BASE - BS2_SEL_R1_BASE + cmp al, 2 + je .done + + add dx, BS2_SEL_R3_BASE - BS2_SEL_R2_BASE + cmp al, 3 + je .done +.panic: + hlt + jmp .panic + +.ring1: + sub dx, BS2_SEL_R1_BASE - BS2_SEL_R0_BASE + jmp .ring0 +.ring2: + sub dx, BS2_SEL_R2_BASE - BS2_SEL_R0_BASE + jmp .ring0 +.ring3: + sub dx, BS2_SEL_R3_BASE - BS2_SEL_R0_BASE + jmp .ring0 + +.done: + and dl, ~3h + or dl, al ; set the RPL + ret + +.miss: +.null: + xor dx, dx + ret +ENDPROC bs2SRegToRing + +BEGINCODELOW + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-common-macros-1.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-common-macros-1.mac new file mode 100644 index 00000000..e9dc9476 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-common-macros-1.mac @@ -0,0 +1,60 @@ +; $Id: bootsector2-common-macros-1.mac $ +;; @file +; Common bootsector macros. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;; +; Asserts a test. +; +; @param %1 First cmp operand. +; @param %2 First cmp operand. +; @param %3 Which kind of conditional jump to make +; @param %4 The message to print (format string, no arguments please). +; +%macro TEST_ASSERT_SIMPLE 4 + cmp %1, %2 + %3 %%.ok + push dword __LINE__ + %ifdef TMPL_16BIT + push ds + %endif + push %%.s_szMsg + call TMPL_NM_CMN(TestFailedF) + add xSP, sCB*2 + jmp %%.ok +%%.s_szMsg: db %4, " (0x%RX32)", 0 +%%.ok: +%endmacro + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-common-routines-template-1.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-common-routines-template-1.mac new file mode 100644 index 00000000..79cb339a --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-common-routines-template-1.mac @@ -0,0 +1,2655 @@ +; $Id: bootsector2-common-routines-template-1.mac $ +;; @file +; bootsector2 common routines - template containing code common to related modes. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bootsector2-template-header.mac" +%include "VBox/bios.mac" + +ALIGNCODE(32) +GLOBALNAME TMPL_NM_CMN(g_szMode) + db TMPL_MODE_STR, 0 + + +;; +; Shutdown routine. +; +; Does not return. +; +; @uses N/A +; +BEGINPROC TMPL_NM_CMN(Shutdown) +%ifdef TMPL_16BIT + jmp Bs2Shutdown +%else + cli + mov bl, 64 + mov ax, ss + mov ds, ax + mov dx, VBOX_BIOS_SHUTDOWN_PORT + mov ax, VBOX_BIOS_OLD_SHUTDOWN_PORT +.retry: + mov ecx, 8 + mov esi, .s_szShutdown + rep outsb + xchg dx, ax ; alternate between the new (VBox) and old (Bochs) ports. + dec bl + jnz .retry + ; Shutdown failed! + jmp Bs2Panic +.s_szShutdown: + db 'Shutdown', 0 +%endif +ENDPROC TMPL_NM_CMN(Shutdown) + + +;; +; Prints a 32-bit unsigned integer on the screen. +; +; @param eax The value to print. +; +; @uses nothing +; +BEGINPROC TMPL_NM_CMN(PrintU32) + push xBP + mov xBP, xSP + push sAX + push sDX + push sCX + push sBX +%ifdef TMPL_16BIT + push ds + + mov cx, ss + mov ds, cx +%endif + + ; Allocate a stack buffer and terminate it. ds:bx points ot the end. + sub xSP, 30h + mov xBX, xSP + add xBX, 2fh + mov byte [xBX], 0 + + mov ecx, 10 ; what to divide by +.next: + xor edx, edx + div ecx ; edx:eax / ecx -> eax and rest in edx. + add dl, '0' + dec xBX + mov [xBX], dl + cmp eax, 0 + jnz .next + + ; Print the string. + mov xAX, xBX + call TMPL_NM_CMN(PrintStr) + + add xSP, 30h +%ifdef TMPL_16BIT + pop ds +%endif + pop sBX + pop sCX + pop sDX + pop sAX + leave + ret +ENDPROC TMPL_NM_CMN(PrintU32) + + +;; +; Equivalent to RTPrintf, but a max output length of 1KB. +; +; @remarks This uses an entirely STACK BASED CALLING CONVENTION where the +; caller does the cleanup (cdecl sans volatile regs). +; +; @param fpszFormat The format string (far pointer on 16-bit +; systems). See StrFormatV for format details. +; @param ... Zero or more format string arguments. +; @uses nothing +; +BEGINPROC TMPL_NM_CMN(PrintF) + push xBP + mov xBP, xSP + push sAX + push xDX + push xCX + push xBX +%ifdef TMPL_16BIT + push ds + push es + push fs +%endif + sub xSP, 0400h ; string buffer (1024 bytes) + + ; + ; Format the failure string and call TestFailed. + ; +%ifdef TMPL_16BIT + mov ax, ss ; buffer address. + mov ds, ax + mov ax, sp + mov xDX, 0400h ; buffer size. + les cx, [bp + 4] ; format string + mov bx, ss ; argument list + mov fs, bx + mov bx, bp + add bx, 8 +%else + mov xAX, xSP ; buffer address + mov xDX, 0400h ; buffer size + mov xCX, [xBP + xCB * 2] ; format string + lea xBX, [xBP + xCB * 3] ; argument list. +%endif + call TMPL_NM_CMN(StrFormatV) + call TMPL_NM_CMN(PrintStr) + + add xSP, 0400h +%ifdef TMPL_16BIT + pop fs + pop es + pop ds +%endif + pop xBX + pop xCX + pop xDX + pop sAX + leave + ret +ENDPROC TMPL_NM_CMN(PrintF) + + +;; +; Print a string followed by a semicolon and at least one space. +; +; @param ds:ax The string to print. +; @param dx The desired minimum length of the output. That is +; string + colon + spaces. +; @uses nothing. +; +BEGINPROC TMPL_NM_CMN(PrintStrColonSpaces) + push xBP + mov xBP, xSP + push xAX + push xCX + + call TMPL_NM_CMN(PrintStr) + call TMPL_NM_CMN(StrLen) + mov cx, ax + mov al, ':' + call TMPL_NM_CMN(PrintChr) + inc cx + mov al, ' ' +.next_space: + call TMPL_NM_CMN(PrintChr) + inc cx + cmp cx, dx + jb .next_space + + pop xCX + pop xAX + leave + ret +ENDPROC TMPL_NM_CMN(PrintStrColonSpaces) + + +;; +; Print a string followed by a 0+ spaces, a semicolon and a space. +; +; @param ds:ax The string to print. +; @param dx The desired minimum length of the output. That is +; string + spaces + colon + space. +; @uses nothing. +; +BEGINPROC TMPL_NM_CMN(PrintStrSpacesColonSpace) + push xBP + mov xBP, xSP + push xAX + push xCX + + call TMPL_NM_CMN(PrintStr) + call TMPL_NM_CMN(StrLen) + mov cx, ax + inc cx + mov al, ' ' +.next_space: + inc cx + cmp cx, dx + jae .done_spaces + call TMPL_NM_CMN(PrintChr) + jmp .next_space +.done_spaces: + mov al, ':' + call TMPL_NM_CMN(PrintChr) + mov al, ' ' + call TMPL_NM_CMN(PrintChr) + + pop xCX + pop xAX + leave + ret +ENDPROC TMPL_NM_CMN(PrintStrSpacesColonSpace) + + +;; +; Store the current nanosecond timestamp in [ax] (qword). +; +; @param ds:ax Where to store the 64-bit timestamp. +; +; @uses nothing +; +BEGINPROC TMPL_NM_CMN(GetNanoTS) + push sAX + push sCX + push xDX +%ifdef TMPL_16BIT + movzx ecx, ax +%else + mov xCX, xAX +%endif + + mov dx, VMMDEV_TESTING_IOPORT_TS_LOW + in eax, dx + mov [sCX], eax + + mov dx, VMMDEV_TESTING_IOPORT_TS_HIGH + in eax, dx + mov [sCX + 4], eax + + pop xDX + pop sCX + pop sAX + ret +ENDPROC TMPL_NM_CMN(GetNanoTS) + + + +;; +; Calculates the time elapsed since [ax] (qword), storing it at [ax] (qword). +; +; @param ds:ax Where to get the start timestamp (input) and where +; to store the time elapsed (output). qword +; +; @uses nothing +; +BEGINPROC TMPL_NM_CMN(GetElapsedNanoTS) + push sAX + push sCX + push xDX +%ifdef TMPL_16BIT + movzx ecx, ax +%else + mov xCX, xAX +%endif + + mov dx, VMMDEV_TESTING_IOPORT_TS_LOW + in eax, dx + sub eax, [sCX] + mov [sCX], eax + + mov dx, VMMDEV_TESTING_IOPORT_TS_HIGH + in eax, dx + sbb eax, [sCX + 4] + mov [sCX + 4], eax + + pop xDX + pop sCX + pop sAX + ret +ENDPROC TMPL_NM_CMN(GetElapsedNanoTS) + + +;; +; Sends a command to VMMDev followed by a single string. +; +; If the VMMDev is not present or is not being used, this function will +; do nothing. +; +; @param eax The command. +; @param ds:dx The string (zero terminated). +; @uses nothing +; @internal +; +BEGINPROC TMPL_NM_CMN(testSendStrCmd) + push xBP + mov xBP, xSP + push sAX + push xBX + push xDX + + cmp byte [g_fbBs2VMMDevTesting], 0 + je .no_vmmdev + + mov dx, VMMDEV_TESTING_IOPORT_CMD + out dx, eax + + mov dx, VMMDEV_TESTING_IOPORT_DATA + pop xBX + push xBX + dec xBX +.next_char: + inc xBX + mov al, [xBX] + out dx, al + test al, al + jnz .next_char + +.no_vmmdev: + pop xDX + pop xBX + pop sAX + leave + ret +ENDPROC TMPL_NM_CMN(testSendStrCmd) + + +;; +; Equivalent to RTTestCreate + RTTestBanner +; +; @param DS16:xAX Pointer to a zero terminated string naming the +; test. Must be a global constant. +; @uses nothing +; +BEGINPROC TMPL_NM_CMN(TestInit) + push xBP + mov xBP, xSP + push sAX + push xDX + + ; Initialize the globals. + mov [g_npszBs2Test], xAX + xor eax, eax + mov [g_uscBs2TestErrors], ax + mov [g_npszBs2SubTest], eax + mov [g_uscBs2SubTestAtErrors], ax + mov byte [g_fbBs2SubTestReported], 1 + mov [g_uscBs2SubTests], ax + mov [g_uscBs2SubTestsFailed], ax + + ; Print the name. RTTestBanner + mov xAX, [g_npszBs2Test] + call TMPL_NM_CMN(PrintStr) + mov xAX, .s_szTesting + call TMPL_NM_CMN(PrintStr) + + ; Report it to the VMMDev. + mov eax, VMMDEV_TESTING_CMD_INIT + mov xDX, [g_npszBs2Test] + call TMPL_NM_CMN(testSendStrCmd) + + pop xDX + pop sAX + leave + ret +.s_szTesting: + db ': TESTING...', 13, 10, 0 +ENDPROC TMPL_NM_CMN(TestInit) + + +;; +; rtTestSubTestReport +; @uses nothing +; @internal +BEGINPROC TMPL_NM_CMN(testSubTestReport) + push xBP + mov xBP, xSP + push sAX + push sCX + push xDX + + ; Check if there is anything to do. + cmp byte [g_fbBs2SubTestReported], 0 + jne .done + xor xAX, xAX ; load the sub test name pointer for later + mov xAX, [g_npszBs2SubTest] + test xAX, xAX + jz .done + + ; Start the printing. + mov dx, 48 + call TMPL_NM_CMN(PrintStrSpacesColonSpace) + + mov byte [g_fbBs2SubTestReported], 1 + mov cx, [g_uscBs2TestErrors] + sub cx, [g_uscBs2SubTestAtErrors] + and ecx, 0ffffh + jnz .failed + + ; passed + mov xAX, .s_szPassed + call TMPL_NM_CMN(PrintStr) + jmp .vmmdev + + ; failed +.failed: + mov xAX, .s_szFailure + call TMPL_NM_CMN(PrintStr) + mov eax, ecx + call TMPL_NM_CMN(PrintU32) + mov xAX, .s_szFailureEnd + call TMPL_NM_CMN(PrintStr) + + ; report to VMMDev +.vmmdev: + cmp byte [g_fbBs2VMMDevTesting], 0 + je .no_vmmdev + + mov dx, VMMDEV_TESTING_IOPORT_CMD + mov eax, VMMDEV_TESTING_CMD_SUB_DONE + out dx, eax + + mov dx, VMMDEV_TESTING_IOPORT_DATA + mov eax, ecx + out dx, eax + +.no_vmmdev: +.done: + pop xDX + pop sCX + pop sAX + leave + ret +.s_szPassed: + db 'PASSED', 13, 10, 0 +.s_szFailure: + db 'FAILED (', 0 +.s_szFailureEnd: + db ' errors)', 13, 10, 0 +ENDPROC TMPL_NM_CMN(testSubTestReport) + + +;; +; rtTestSubCleanup +; @uses nothing +; @internal +BEGINPROC TMPL_NM_CMN(testSubCleanup) + push xBP + mov xBP, xSP + + cmp dword [g_npszBs2SubTest], 0 + je .cleaned_up + + call TMPL_NM_CMN(testSubTestReport) + mov dword [g_npszBs2SubTest], 0 + mov byte [g_fbBs2SubTestReported], 0 + +.cleaned_up: + leave + ret +ENDPROC TMPL_NM_CMN(testSubCleanup) + + +;; +; Equivalent to RTTestSub. +; +; @param ds:xAX Pointer to a zero terminated string naming the sub test. +; @uses nothing +; +BEGINPROC TMPL_NM_CMN(TestSub) + push xBP + mov xBP, xSP + push sAX + push xDX + + ; Complete and cleanup any current sub test. + call TMPL_NM_CMN(testSubCleanup) + + ; Start a new sub test. + inc word [g_uscBs2SubTests] + mov dx, [g_uscBs2TestErrors] + mov [g_uscBs2SubTestAtErrors], dx + mov [g_npszBs2SubTest], xAX + mov byte [g_fbBs2SubTestReported], 0 + + ; Report it to the VMMDev. + mov xDX, xAX + mov eax, VMMDEV_TESTING_CMD_SUB_NEW + call TMPL_NM_CMN(testSendStrCmd) + + pop xDX + pop sAX + leave + ret +ENDPROC TMPL_NM_CMN(TestSub) + + +;; +; Calculates the error count for the current sub test. +; +; @returns ax Error count for the current sub test. +; @uses ax +; +BEGINPROC TMPL_NM_CMN(TestSubErrorCount) + pushf + + mov ax, [g_uscBs2TestErrors] + sub ax, [g_uscBs2SubTestAtErrors] + + popf + ret +ENDPROC TMPL_NM_CMN(TestSubErrorCount) + + + +;; +; Equivalent to RTTestValue. +; +; @param ds:ax The value name. +; @param edx The 32-bit value to report. +; @param cl The unit (VMMDEV_TESTING_UNIT_XXX). +; @uses nothing +; +BEGINPROC TMPL_NM_CMN(TestValueU32) + push xBP + mov xBP, xSP + push sDX + push sCX + push sAX + push sSI + pushf + cld + + mov xSI, xAX ; xSI = name + + ; Print it. + mov dx, 48 + call TMPL_NM_CMN(PrintStrSpacesColonSpace) + mov eax, [xBP - sCB] + call TMPL_NM_CMN(PrintU32) + mov al, ' ' + call TMPL_NM_CMN(PrintChr) + movzx sAX, cl ; ASSUMES correct input. + mov edx, eax ; edx = unit + shl xAX, 4 ; * 16 + add xAX, g_aszBs2TestUnitNames + call TMPL_NM_CMN(PrintStr) + mov al, 13 + call TMPL_NM_CMN(PrintChr) + mov al, 10 + call TMPL_NM_CMN(PrintChr) + + ; Report it to the host. + cmp byte [g_fbBs2VMMDevTesting], 0 + je .no_vmmdev + mov ecx, edx ; ecx = unit + + mov dx, VMMDEV_TESTING_IOPORT_CMD + mov eax, VMMDEV_TESTING_CMD_VALUE + out dx, eax + + mov dx, VMMDEV_TESTING_IOPORT_DATA + mov eax, [xBP - sCB] + out dx, eax ; value - low dword + xor eax, eax + out dx, eax ; value - high dword + mov eax, ecx + out dx, eax ; unit +.next_char: + lodsb + out dx, al + test al, al + jnz .next_char + +.no_vmmdev: + popf + pop sSI + pop sAX + pop sCX + pop sDX + leave + ret +ENDPROC TMPL_NM_CMN(TestValueU32) + + +;; +; RTTestValue + DBGFR3RegNmQueryU64. +; +; @param ds:ax The value name and register name separated by a colon. +; @uses nothing +; +BEGINPROC TMPL_NM_CMN(TestValueReg) + push xBP + mov xBP, xSP + push sDX + push sAX + push sSI + pushf + cld + + mov xSI, xAX ; xSI = name + + ; Report it to the host. + cmp byte [g_fbBs2VMMDevTesting], 0 + je .no_vmmdev + + mov dx, VMMDEV_TESTING_IOPORT_CMD + mov eax, VMMDEV_TESTING_CMD_VALUE_REG + out dx, eax + + mov dx, VMMDEV_TESTING_IOPORT_DATA +.next_char: + lodsb + out dx, al + test al, al + jnz .next_char + +.no_vmmdev: + popf + pop sSI + pop sAX + pop sDX + leave + ret +ENDPROC TMPL_NM_CMN(TestValueReg) + + +;; +; Equivalent to RTTestFailed("%s", ds:xAX). +; +; @param ds:xAX Failure explanation. +; @uses nothing +; +BEGINPROC TMPL_NM_CMN(TestFailed) + push xBP + mov xBP, xSP + push sAX + push xDX + + ; Increment the error count. + inc word [g_uscBs2TestErrors] + + ; Print failure message. + call TMPL_NM_CMN(PrintStr) + + ; Report it to the VMMDev. + mov xDX, xAX + mov eax, VMMDEV_TESTING_CMD_FAILED + call TMPL_NM_CMN(testSendStrCmd) + + pop xDX + pop sAX + leave + ret +ENDPROC TMPL_NM_CMN(TestFailed) + + +;; +; Equivalent to RTTestFailed. +; +; @remarks This uses an entirely STACK BASED CALLING CONVENTION where the +; caller does the cleanup (cdecl sans volatile regs). +; +; @param fpszFormat The format string (far pointer on 16-bit +; systems). See StrFormatV for format details. +; @param ... Zero or more format string arguments. +; @uses nothing +; +BEGINPROC TMPL_NM_CMN(TestFailedF) + push xBP + mov xBP, xSP + push sAX + push xDX + push xCX + push xBX +%ifdef TMPL_16BIT + push ds + push es + push fs +%endif + sub xSP, 0400h ; string buffer (1024 bytes) + + ; + ; Format the failure string and call TestFailed. + ; +%ifdef TMPL_16BIT + mov ax, ss ; buffer address. + mov ds, ax + mov ax, sp + mov xDX, 0400h ; buffer size. + les cx, [bp + 4] ; format string + mov bx, ss ; argument list + mov fs, bx + mov bx, bp + add bx, 8 +%else + mov xAX, xSP ; buffer address + mov xDX, 0400h ; buffer size + mov xCX, [xBP + xCB * 2] ; format string + lea xBX, [xBP + xCB * 3] ; argument list. +%endif + call TMPL_NM_CMN(StrFormatV) + call TMPL_NM_CMN(TestFailed) + + add xSP, 0400h +%ifdef TMPL_16BIT + pop fs + pop es + pop ds +%endif + pop xBX + pop xCX + pop xDX + pop sAX + leave + ret +ENDPROC TMPL_NM_CMN(TestFailedF) + + +;; +; Equivalent to RTTestSkipped("%s", ds:xAX). +; +; @param ds:xAX Explanation. +; @uses nothing +; +BEGINPROC TMPL_NM_CMN(TestSkipped) + push xBP + mov xBP, xSP + push sAX + push xDX + + ; Print reason. + call TMPL_NM_CMN(PrintStr) + + ; Report it to the VMMDev. + mov xDX, xAX + mov eax, VMMDEV_TESTING_CMD_SKIPPED + call TMPL_NM_CMN(testSendStrCmd) + + pop xDX + pop sAX + leave + ret +ENDPROC TMPL_NM_CMN(TestSkipped) + + + +;; +; Equivalent to RTTestSubDone. +; +; @uses nothing +; +BEGINPROC TMPL_NM_CMN(TestSubDone) + jmp TMPL_NM_CMN(testSubCleanup) +ENDPROC TMPL_NM_CMN(TestSubDone) + + +;; +; Equivalent to RTTestSummaryAndDestroy, does not return. +; +BEGINPROC TMPL_NM_CMN(TestTerm) + push xBP + mov xBP, xSP + push sAX + push sCX + push xDX + + ; Complete and cleanup any current sub test. + call TMPL_NM_CMN(testSubCleanup) + + ; Print test summary. + mov xAX, [g_npszBs2Test] + call TMPL_NM_CMN(PrintStr) + + mov cx, [g_uscBs2TestErrors] + and ecx, 0ffffh + jnz .failure + + ; success + mov xAX, .s_szSuccess + call TMPL_NM_CMN(PrintStr) + jmp .vmmdev + + ; failure +.failure: + mov xAX, .s_szFailure + call TMPL_NM_CMN(PrintStr) + mov eax, ecx + call TMPL_NM_CMN(PrintU32) + mov xAX, .s_szFailureEnd + call TMPL_NM_CMN(PrintStr) + + ; report to VMMDev +.vmmdev: + cmp byte [g_fbBs2VMMDevTesting], 0 + je .no_vmmdev + + mov dx, VMMDEV_TESTING_IOPORT_CMD + mov eax, VMMDEV_TESTING_CMD_TERM + out dx, eax + + mov dx, VMMDEV_TESTING_IOPORT_DATA + mov eax, ecx + out dx, eax +.no_vmmdev: + + ; Shut down the VM by default. + call TMPL_NM_CMN(Shutdown) + + pop xDX + pop sCX + pop sAX + leave + ret +.s_szSuccess: + db ': SUCCESS', 13, 10, 0 +.s_szFailure: + db ': FAILURE - ', 0 +.s_szFailureEnd: + db ' errors', 13, 10, 0 +ENDPROC TMPL_NM_CMN(TestTerm) + + + + +;; +; Report a result (elapsed time). +; +; @param ds:ax Pointer to the elapsed time. +; @param edx The number of tests executed. +; @param ds:cx The test description. +; +; @users nothing +; +BEGINPROC TMPL_NM_CMN(ReportResult) + push xBP + mov xBP, xSP + push sAX + push sDX + push xCX + +%if 0 + push sDX + push xCX + push sDX + mov edx, [sAX] + push sDX + mov edx, [sAX + 4] + push sDX + push .szDbg + call TMPL_NM_CMN(PrintF) + add xSP, 4 * sCB + xCB + pop sDX + jmp .end_debug +.szDbg: + db 'ReportResult(%RX32.%RX32, %RX32, %s)', 13, 10, 0 +.end_debug: +%endif + + call TMPL_NM_CMN(CalcTestPerSecond) + mov edx, eax + mov xAX, xCX + mov cl, VMMDEV_TESTING_UNIT_INSTRS_PER_SEC + call TMPL_NM_CMN(TestValueU32) + + pop xCX + pop sDX + pop sAX + leave + ret +ENDPROC TMPL_NM_CMN(ReportResult) + + +%ifdef BS2_WITH_TRAPS +;; +; Checks a trap, complains if not the expected one. +; +; @param al The expected trap number. +; @param sDX The expected trap error code. +; @param sCX The expected fault eip. +; @param sBX The expected fault address. +; @returns al=1 and ZF=0 on success. +; @returns al=0 and ZF=1 on failure +; @uses Nothing. +; +BEGINPROC TMPL_NM_CMN(TestCheckTrap) + push xBP + mov xBP, xSP +%define a_u8ExpectedTrapNo byte [xBP - xCB] + push xAX +%define a_uExpectedErr sPRE [xBP - xCB - sCB*1] + push sDX +%define a_uExpectedFaultPC sPRE [xBP - xCB - sCB*2] + push sCX +%define a_uExpectedFaultAddr sPRE [xBP - xCB - sCB*3] + push sBX + + ; Any traps at all? + cmp dword [g_u32cTraps], 0 + jne .trapped + mov xAX, .s_szNoTrap + jmp .failed +.trapped: + + ; Exactly one trap. + cmp dword [g_u32cTraps], 1 + je .one_trap + mov xAX, .s_szToManyTraps + jmp .failed +.one_trap: + + ; The right trap. + cmp byte [g_u8LastTrapNo], al + je .right_trap_no + mov xAX, .s_szWrongTrapNo + jmp .failed +.right_trap_no: + + ; The right error code. + cmp [g_u64LastTrapErr], sDX +%ifndef TMPL_64BIT + jne .bad_err_cd + cmp dword [g_u64LastTrapErr + 4], 0 +%endif + je .right_err_cd +.bad_err_cd: + mov xAX, .s_szWrongErrCd + jmp .failed +.right_err_cd: + + ; The fault PC. + cmp [g_LastTrapRegs + BS2REGS.rip], sCX +%ifndef TMPL_64BIT + jne .bad_pc + cmp dword [g_LastTrapRegs + BS2REGS.rip + 4], 0 +%endif + je .right_pc +.bad_pc: + mov xAX, .s_szWrongPc + jmp .failed +.right_pc: + + + ; The fault address (PF only). + cmp al, 0eh + jne .right_fault_address + cmp [g_LastTrapRegs + BS2REGS.cr2], sBX +%ifndef TMPL_64BIT + jne .bad_fault_address + cmp dword [g_LastTrapRegs + BS2REGS.cr2 + 4], 0 +%endif + je .right_fault_address +.bad_fault_address: + mov xAX, .s_szWrongFaultAddress + jmp .failed +.right_fault_address: + + pop sBX + pop sCX + pop sDX + pop xAX + leave + mov al, 1 + test al, al + ret + + ; + ; Reportfailure + ; +.failed: + mov xDX, xSP ; save xSP - lazy bird. + cmp a_u8ExpectedTrapNo, 0eh + jne .not_pf2 +%ifndef TMPL_64BIT + push dword 0 +%endif + push a_uExpectedFaultAddr +.not_pf1: +%ifndef TMPL_64BIT + push dword 0 +%endif + push a_uExpectedErr +%ifndef TMPL_64BIT + push dword 0 +%endif + push a_uExpectedFaultPC + movzx xBX, a_u8ExpectedTrapNo + push xBX + ; line break + cmp a_u8ExpectedTrapNo, 0eh + jne .not_pf2 +%ifndef TMPL_64BIT + push dword [g_LastTrapRegs + BS2REGS.cr2 + 4] +%endif + push sPRE [g_LastTrapRegs + BS2REGS.cr2] +.not_pf2: +%ifndef TMPL_64BIT + push dword [g_u64LastTrapErr + 4] +%endif + push sPRE [g_u64LastTrapErr] +%ifndef TMPL_64BIT + push dword [g_LastTrapRegs + BS2REGS.rip + 4] +%endif + push sPRE [g_LastTrapRegs + BS2REGS.rip] + movzx xBX, byte [g_u8LastTrapNo] + push xBX + push xAX ; msg + mov xAX, .s_szFailureMsg + cmp a_u8ExpectedTrapNo, 0eh + jne .not_pf3 + mov xAX, .s_szFailurePfMsg +.not_pf3: + push xAX ; format string + call TMPL_NM_CMN(TestFailedF) + mov xSP, xDX ; clean up call frame + +.done: + pop sBX + pop sCX + pop sDX + pop xAX + leave + xor al, al + ret + +.s_szFailureMsg: + db '%s', 13, 10 + db ' got trap %RX8 pc=%RX64 err=%RX64', 13, 10 + db ' expected %RX8 pc=%RX64 err=%RX64', 13, 10, 0 +.s_szFailurePfMsg: + db '%s', 13, 10 + db ' got trap %RX8 pc=%RX64 err=%RX64 cr2=%RX64', 13, 10, + db ' expected %RX8 pc=%RX64 err=%RX64 cr2=%RX64', 13, 10, 0 +.s_szNoTrap: + db 'no traps', 0 +.s_szToManyTraps: + db 'too many traps', 0 +.s_szWrongTrapNo: + db 'wrong trap number', 0 +.s_szWrongErrCd: + db 'wrong error code', 0 +.s_szWrongPc: + db 'wrong xIP', 0 +.s_szWrongFaultAddress: + db 'wrong fault address', 0 +%undef a_u8ExpectedTrapNo +%undef a_u32ExpectedErr +%undef a_u32ExpectedFaultPC +%undef a_u32ExpectedFaultAddr +ENDPROC TMPL_NM_CMN(TestCheckTrap) +%endif ; BS2_WITH_TRAPS + + +%ifdef BS2_WITH_TRAPS +;; +; Installs the active list of trap records (BS2TRAPREC). +; +; @param sAX Flat address of the trap records (BS2TRAPREC). +; Assumes that DS is FLAT. +; @param edx The number of trap records. +; @param sCX Flat image base address, i.e. what BS2TRAPREC.offWhere +; is relative to. +; @returns al=1 and ZF=0 on success. +; @returns al=0 and ZF=1 on failure +; +; @uses sAX (return value) +; +BEGINPROC TMPL_NM_CMN(TestInstallTrapRecs) + push xBP + mov xBP, xSP + push sDX + push sBX + push sCX + push sDI + push sSI + + ; Make sure the record array is within limits. + cmp edx, _4M + jae .nok + + ; Scan the trap records. + mov sDI, sAX + mov esi, edx + or esi, esi + jnz .ok +.next: + cmp dword [sDI + BS2TRAPREC.offWhere], _2G + jae .nok + + cmp dword [sDI + BS2TRAPREC.offResumeAddend], 0 + je .nok + cmp dword [sDI + BS2TRAPREC.offResumeAddend], 0xff + je .nok + + cmp dword [sDI + BS2TRAPREC.u8TrapNo], X86_XCPT_LAST + ja .nok + + ; next. + add sDI, BS2TRAPREC_size + dec esi + jnz .next + + ; Set the global variables. +.ok: + xor esi, esi + mov [g_paTrapRecs + 4], esi + mov [g_paTrapRecs], sAX + mov [g_cTrapRecs], edx + mov [g_iTrapRecLast], esi + mov [g_pTrapRecBase + 4], esi + mov [g_pTrapRecBase], sCX + mov eax, 1 + or eax, eax + +.done: + pop sSI + pop sDI + pop sBX + pop sCX + pop sDX + leave + ret + +.nok: + xor eax, eax + jmp .done +ENDPROC TMPL_NM_CMN(TestInstallTrapRecs) +%endif ; BS2_WITH_TRAPS + + +;; +; Calculate the number of tests executed per second. +; +; @param ds:ax Pointer to the elapsed time. +; @param edx The number of tests executed. +; @returns The tests per second in eax. +; +; @uses eax (return value) +; +BEGINPROC TMPL_NM_CMN(CalcTestPerSecond) + push xBP + mov xBP, xSP + push sDX + push sCX +%ifdef TMPL_16BIT + movzx eax, ax +%endif + + ; Calc NS per test. + mov ecx, edx + cmp ecx, 0 + jz .div_zero + movzx eax, ax + mov edx, [sAX + 4] + cmp edx, ecx + jae .div_overflow + mov eax, [sAX] + shld edx, eax, 10 + shl eax,10 + div ecx ; eax = NS per test + + ; Calc tests per second. + mov ecx, eax + cmp ecx, 0 + jz .div_zero + mov edx, 0xee + mov eax, 0x6b280000 ; 1024G + div ecx ; eax = tests per second + +.done: + pop sCX + pop sDX + leave + ret + +.div_zero: + mov eax, 0 + jmp .done + +.div_overflow: + mov eax, 4242424242 + jmp .done +ENDPROC TMPL_NM_CMN(CalcTestPerSecond) + + +;; +; Calculate the number of iterations for a benchmark based on the time a short +; calibration run too. +; +; @param ds:xAX Pointer to the elapsed time. +; @param edx The number of tests iterations executed. +; @param ecx The desired test length, in seconds. +; @returns The tests iterations in eax. +; +; @uses eax (return value) +; +BEGINPROC TMPL_NM_CMN(CalcBenchmarkIterations) + push xBP + mov xBP, xSP + push sDX + push sCX +%ifdef TMPL_16BIT + movzx eax, ax +%endif + + ; Calc NS per test. + mov ecx, edx + cmp ecx, 0 + jz .div_zero + movzx eax, ax + mov edx, [sAX + 4] + cmp edx, ecx + jae .div_overflow + mov eax, [sAX] + div ecx ; eax = NS per test + + ; Calc tests per second. + mov ecx, eax + cmp ecx, 0 + jz .div_zero + xor edx, edx + mov eax, 1000000000 ; 1G + div ecx ; eax = tests per second + + ; Multiply this up to the desired number of seconds. + mov edx, [xBP - xCB*2] + mul edx + test edx, edx + jnz .mult_32bit_overflow + cmp eax, _64K + jle .too_small + +.done: + pop sCX + pop sDX + leave + ret + +.too_small: + mov eax, _64K + jmp .done + +.mult_32bit_overflow: + mov eax, 0ffff0000h + jmp .done + +.div_zero: + mov eax, [xBP - xCB] + shl eax, 8 + jmp .fudge + +.div_overflow: + mov eax, [xBP - xCB] + shr eax, 4 +.fudge: + add eax, 10 + jmp .done +ENDPROC TMPL_NM_CMN(CalcBenchmarkIterations) + + +;; +; Prints a string on the screen. +; +; @param ds:ax The string to find the length of. +; @returns The string length in ax. +; +; @uses ax (return value) +; +BEGINPROC TMPL_NM_CMN(StrLen) + push xBP + mov xBP, xSP + push xBX + + mov xBX, xAX + dec xBX +.next: + inc xBX + cmp byte [xBX], byte 0 + jnz .next + + xchg xAX, xBX + sub xAX, xBX + + pop xBX + leave + ret +ENDPROC TMPL_NM_CMN(StrLen) + + + +;; +; Simple string format, taking an argument list. +; +; Implemented: +; - %RX8 +; - %RX16 +; - %RX32 +; - %RX64 +; - %s +; +; Planned: +; - %RU8 +; - %RU16 +; - %RU32 +; - %RU64 +; - %RI8 +; - %RI16 +; - %RI32 +; - %RI64 +; +; @param ds:xAX The buffer address. +; @param xDX The buffer size. +; @param es:xCX The format string. +; @param fs:xBX The argument list. +; @uses nothing +; +BEGINPROC TMPL_NM_CMN(StrFormatV) + push xBP + mov xBP, xSP + push sAX + push sDX + push sCX + push sBX + push sDI + push sSI + pushf + cld +%ifdef TMPL_16BIT + push ds + push es + push fs + push gs + + mov si, ds + mov di, es + mov ds, di + mov es, si + mov di, ax ; es:di -> output buffer. + mov si, cx ; ds:si -> format string. +%define a_pArgs [fs:bx] +%define a_pu32ArgsHighDW dword [fs:bx + 4] +%else + mov xDI, xAX ; (es:)xDI -> output buffer. + mov xSI, xCX ; (ds:)xSI -> format string. +%define a_pArgs [xBX] +%define a_pu32ArgsHighDW dword [xBX + 4] +%endif + xchg xCX, xDX ; xCX=buffer size. + + ; + ; Make sure we've got space for a terminator char in the output buffer. + ; + test xCX, xCX + jz .return + dec xCX + jz .done + + ; + ; In this loop we're free to use sDX and (with some caution) sAX. + ; +.format_loop: + lodsb + test al, al + jz .done + cmp al, '%' + je .switch + + ; Emit the character in al if there is room for it. +.emit_al: + test xCX, xCX + jz .done + stosb + dec xCX + jmp .format_loop + + ; Try recognize the format specifier. +.switch: + lodsb + cmp al, 's' + je .switch_case_string + cmp al, 'c' + je .switch_case_char + cmp al, 'R' + jne .switch_default + lodsb + cmp al, 'X' + jne .switch_case_number + cmp al, 'U' + jne .switch_case_number + cmp al, 'I' + jne .switch_case_number + +.switch_default: + test al, al + jz .done + mov al, '!' + jmp .emit_al + + ; parse the number part. +.switch_case_number: + mov ah, al ; ah = {X,U,I} + lodsb + cmp al, '8' + je .switch_case_number_8bit + cmp al, '1' + je .switch_case_number_16bit + cmp al, '3' + je .switch_case_number_32bit + cmp al, '6' + je .switch_case_number_64bit + jmp .switch_default + + + ; + ; Common code for 8-bit, 16-bit and 32-bit. + ; + ; The first part load the value into edx, ah={X,U,I}, + ; al=(max-hex, max-unsigned-dec). + ; +.switch_case_number_8bit: + mov al, (2 << 4) | 2 + movzx edx, byte a_pArgs + add xBX, xCB + cmp ah, 'I' + jne .switch_case_number_common_32bit_hex_or_unsigned + movsx edx, dl + jmp .switch_case_number_common_32bit_signed + +.switch_case_number_16bit: + lodsb + cmp al, '6' + jne .switch_default + mov al, (4 << 4) | 5 + movzx edx, word a_pArgs + add xBX, xCB + cmp ah, 'I' + jne .switch_case_number_common_32bit_hex_or_unsigned + movsx edx, dx + jmp .switch_case_number_common_32bit_signed + +.switch_case_number_32bit: + lodsb + cmp al, '2' + jne .switch_default + mov al, (8 << 4) | 10 + mov edx, dword a_pArgs + add xBX, sCB + cmp ah, 'I' + je .switch_case_number_common_32bit_signed + +.switch_case_number_common_32bit_hex_or_unsigned: + cmp ah, 'X' + jne .switch_case_number_common_32bit_unsigned + shr al, 4 + and xAX, 0fh + cmp xCX, xAX + jb .switch_case_number_bad_buf + call .format_32bit_hex_subroutine + jmp .format_loop + +.switch_case_number_common_32bit_unsigned: + and xAX, 0fh + cmp xCX, xAX + jb .switch_case_number_bad_buf + call .format_32bit_dec_subroutine + jmp .format_loop + +.switch_case_number_common_32bit_signed: + cmp edx, 0 + jge .switch_case_number_common_32bit_unsigned + and xAX, 0fh + inc xAX ; sign char + cmp xCX, xAX + jb .switch_case_number_bad_buf + ; Emit the minus sign, invert the value and join the unsigned formatting. + push xAX + mov al, '-' + stosb + dec xCX + pop xAX + neg edx + call .format_32bit_dec_subroutine + jmp .format_loop + + + ; + ; 64-bit is special, to simplify we treat all formats as hex... + ; +.switch_case_number_64bit: + lodsb + cmp al, '4' + jne .switch_default + cmp ah, 'X' + je .switch_case_number_64bit_hex + cmp ah, 'I' + je .switch_case_number_64bit_signed + jmp .switch_case_number_64bit_unsigned + +.switch_case_number_64bit_hex: + mov eax, dword a_pArgs + mov edx, a_pu32ArgsHighDW + add xBX, 8 + cmp xCX, 8+1+8 + jb .switch_case_number_bad_buf + ; Simple, format it as two 32-bit hex values. + push sAX + mov eax, 8 + call .format_32bit_hex_subroutine + mov al, 27h ; '\'' - how do we escape this with yasm? + stosb + dec xCX + pop sDX + mov eax, 8 + call .format_32bit_hex_subroutine + jmp .format_loop + +.switch_case_number_64bit_unsigned: + cmp xCX, 19 + jb .switch_case_number_bad_buf +.switch_case_number_64bit_unsigned_format_it: + ;; @todo implement me + jmp .switch_case_number_64bit_hex + +.switch_case_number_64bit_signed: + mov eax, dword a_pArgs + mov edx, a_pu32ArgsHighDW + add xBX, 8 + cmp xCX, 20 + jb .switch_case_number_bad_buf + test edx, 080000000h + jz .switch_case_number_64bit_unsigned_format_it + ; Emit the minus sign, invert the value and join the unsigned formatting. + push xAX + mov al, '-' + stosb + dec xCX + pop xAX + neg eax + neg edx + jmp .switch_case_number_64bit_unsigned_format_it + + + ; The remaining buffer is too small to hold the number. +.switch_case_number_bad_buf: + mov al, '^' + jmp .emit_al + + + ; + ; Emit a string. + ; +.switch_case_string: +%ifdef TMPL_16BIT + lgs dx, a_pArgs + add xBX, 4 +%else + mov xDX, a_pArgs + add xBX, xCB +%endif + test xCX, xCX +.switch_case_string_loop: + jz .done +%ifdef TMPL_16BIT + mov al, [gs:edx] +%else + mov al, [xDX] +%endif + test al, al + jz .format_loop + inc xDX + stosb + dec xCX + jmp .switch_case_string_loop + + ; + ; Emit a char. + ; +.switch_case_char: + mov al, byte a_pArgs + add xBX, xCB + jmp .emit_al + + + ; + ; Done, just emit the terminator char. + ; +.done: + xor al, al + stosb + +.return: +%ifdef TMPL_16BIT + pop gs + pop fs + pop es + pop ds +%endif + popf + pop sSI + pop sDI + pop sBX + pop sCX + pop sDX + pop sAX + leave + ret +%undef a_pArgs +%undef a_pu32ArgsHighDW + +;; +; Internal subroutine for formatting a hex number into the buffer. +; @param al The precision (2, 4, 8). +; @param edx The value. +; +; @uses ecx, edi +; +.format_32bit_hex_subroutine: + push xAX + push sDX + push xBX + + ; Rotate edx into position. + mov ebx, 8 + sub bl, al + shl bl, 2 + xchg cl, bl + rol edx, cl + xchg bl, cl + + mov bl, al ; Width counter +.format_32bit_hex_subroutine_next: + rol edx, 4 + mov eax, edx + and eax, 0fh + add sAX, g_achHex + mov al, [cs:sAX] + stosb + dec xCX + dec bl + jnz .format_32bit_hex_subroutine_next + + pop xBX + pop sDX + pop xAX + ret + + +;; +; Internal subroutine for formatting a hex number into the buffer. +; @param al The max precision (2, 5, 10). +; @param edx The value. +; @param xCX Counter register to decrement as characters are emited. +; @param es:xDI Where to write the output, xDI is updated. +; +; @uses xCX, xDI +; +.format_32bit_dec_subroutine: +%if 0 ;; @todo implement this + sub xSP, 20h + ; Format in reverse order into a stack buffer. + + ; Append the stack buffer to the string, reversing it in the process. + + add xSP, 20h +%else + call .format_32bit_hex_subroutine +%endif + ret + +ENDPROC TMPL_NM_CMN(StrFormatV) + + +;; +; Very limited RTStrPrintf version. +; +; @remarks This uses an entirely STACK BASED CALLING CONVENTION where the +; caller does the cleanup (cdecl sans volatile regs). +; +; @param fpszBuf The output buffer. +; @param cbBuf The output buffer size (natural size). +; @param fpszFormat The format string (far pointer in 16-bit). +; See StrFormatV for format. +; @param ... Zero or more format string arguments. +; @uses nothing +; +BEGINPROC TMPL_NM_CMN(StrFormatF) + push xBP + mov xBP, xSP + push xAX + push xDX + push xCX + push xBX +%ifdef TMPL_16BIT + push ds + push es + push fs + + lds xAX, [bp + 04h] + mov dx, [bp + 08h] + les xCX, [bp + 0ah] + mov bx, ss + mov fs, bx + mov bx, bp + add bx, 0eh +%else + mov xAX, [xBP + xCB * 2] + mov xDX, [xBP + xCB * 3] + mov xCX, [xBP + xCB * 4] + lea xBX, [xBP + xCB * 5] +%endif + call TMPL_NM_CMN(StrFormatV) + +%ifdef TMPL_16BIT + pop fs + pop es + pop ds +%endif + pop xBX + pop xCX + pop xDX + pop xAX + leave + ret +ENDPROC TMPL_NM_CMN(StrFormatF) + + +;; +; Dumps the CPU registers. +; +; @uses Nothing. +; +BEGINPROC TMPL_NM_CMN(TestDumpCurrentRegisters) +%ifndef TMPL_64BIT + push xBP + mov xBP, xSP + push eax + pushfd + + push dword [xBP - sCB - 4] ; eflags + mov eax, cr2 + push eax + xor eax, eax + mov eax, gs + push eax + mov eax, fs + push eax + mov eax, es + push eax + mov eax, ds + push eax + mov eax, cs + push eax + + mov eax, cr4 + push eax + mov eax, cr3 + push eax + mov eax, cr0 + push eax + mov eax, ebp ; return EBP + mov xAX, [xBP] + push eax + + mov eax, ebp ; return ESP + add xAX, xCB + push eax + + mov xAX, [xBP + xCB] ; return EIP + %ifdef TMPL_16BIT + movzx eax, ax + %endif + push eax + + push edi + push esi + push edx + push ecx + push ebx + push dword [xBP - sCB] ; eax + + %ifdef TMPL_16BIT + push cs + %endif + push .s_szRegFmt + call TMPL_NM_CMN(PrintF) + + popfd + pop eax + leave + ret + +.s_szRegFmt: + db 'eax=%RX32 ebx=%RX32 ecx=%RX32 edx=%RX32 esi=%RX32 edi=%RX32', 13, 10 + db 'eip=%RX32 esp=%RX32 ebp=%RX32 cr0=%RX32 cr3=%RX32 cr4=%RX32', 13, 10 + db 'cs=%RX16 ds=%RX16 es=%RX16 fs=%RX16 gs=%RX16 ss=%RX16 cr2=%RX32 eflags=%RX32', 13, 10, 0 + +%else ; 64-bit + push .s_szRegFmt + call TMPL_NM_CMN(PrintF) + ret + +.s_szRegFmt: + db 'TestDumpCurrentRegisters not implemented', 13, 10, 0 + +%endif ; 64-bit +ENDPROC TMPL_NM_CMN(TestDumpCurrentRegisters) + + + +;; +; Dumps the CPU registers. +; +; @param ds:xAX Pointer to the register frame to dump. +; @uses Nothing. +; +BEGINPROC TMPL_NM_CMN(TestDumpRegisters) + push xBP + mov xBP, xSP + pushf + push sDX + push sBX + mov xBX, xAX + + cmp byte [xBX + BS2REGS.cBits], 64 + je .dump_64bit_regs + + push -1 ; sanity + mov edx, [xBX + BS2REGS.rflags] + push sDX + mov edx, [xBX + BS2REGS.cr2] + push sDX + xor edx, edx + mov dx, [xBX + BS2REGS.ss] + push xDX + mov dx, [xBX + BS2REGS.gs] + push xDX + mov dx, [xBX + BS2REGS.fs] + push xDX + mov dx, [xBX + BS2REGS.es] + push xDX + mov dx, [xBX + BS2REGS.ds] + push xDX + mov dx, [xBX + BS2REGS.cs] + push xDX + + mov edx, [xBX + BS2REGS.cr4] + push sDX + mov edx, [xBX + BS2REGS.cr3] + push sDX + mov edx, [xBX + BS2REGS.cr0] + push sDX + mov edx, [xBX + BS2REGS.rbp] + push sDX + mov edx, [xBX + BS2REGS.rsp] + push sDX + mov edx, [xBX + BS2REGS.rip] + push sDX + + mov edx, [xBX + BS2REGS.rdi] + push sDX + mov edx, [xBX + BS2REGS.rsi] + push sDX + mov edx, [xBX + BS2REGS.rdx] + push sDX + mov edx, [xBX + BS2REGS.rcx] + push sDX + mov edx, [xBX + BS2REGS.rbx] + push sDX + mov edx, [xBX + BS2REGS.rax] + push sDX + +%ifdef TMPL_16BIT + push cs +%endif + push .s_szReg32Fmt + call TMPL_NM_CMN(PrintF) + jmp .return + +.dump_64bit_regs: +%ifdef TMPL_16BIT + push cs +%endif + push .s_szReg64Fmt + call TMPL_NM_CMN(PrintF) + +.return: + lea xSP, [xBP - sCB*2 - xCB] + pop sBX + pop sDX + popf + leave + ret + +.s_szReg32Fmt: + db 'eax=%RX32 ebx=%RX32 ecx=%RX32 edx=%RX32 esi=%RX32 edi=%RX32', 13, 10 + db 'eip=%RX32 esp=%RX32 ebp=%RX32 cr0=%RX32 cr3=%RX32 cr4=%RX32', 13, 10 + db 'cs=%RX16 ds=%RX16 es=%RX16 fs=%RX16 gs=%RX16 ss=%RX16 cr2=%RX32 eflags=%RX32', 13, 10 + db 0 + +.s_szReg64Fmt: + db 'TestDumpCurrentRegisters not implemented', 13, 10, 0 +ENDPROC TMPL_NM_CMN(TestDumpRegisters) + + +;; +; Saves the CPU registers. +; +; @param ds:xAX Pointer to the register frame to dump. +; @uses Nothing. +; +BEGINPROC TMPL_NM_CMN(TestSaveRegisters) + push xBP + mov xBP, xSP +%ifdef TMPL_16BIT + pushfd +%else + pushf ; - 1*sCB +%endif + push sBX ; - 2*sCB + push sAX ; - 3*sCB + push sDX ; - 4*sCB + + xor edx, edx ; zero register. + mov xBX, xAX ; xBX for addressing, xAX for scratch. + +%ifdef TMPL_64BIT + mov rax, [xSP + sCB*1] + mov [xBX + BS2REGS.rax], rax + mov rax, [xSP + sCB*2] + mov [xBX + BS2REGS.rbx], rax + mov [xBX + BS2REGS.rcx], rcx + mov rax, [xSP] + mov [xBX + BS2REGS.rdx], rax + mov [xBX + BS2REGS.rdi], rdi + mov [xBX + BS2REGS.rsi], rsi + mov rax, [xBP] + mov [xBX + BS2REGS.rbp], rax + lea rax, [xBP + 16] + mov [xBX + BS2REGS.rsp], rax + mov rax, [xBP + 8] + mov [xBX + BS2REGS.rip], rax + mov [xBX + BS2REGS.r8], r8 + mov [xBX + BS2REGS.r9], r9 + mov [xBX + BS2REGS.r10], r10 + mov [xBX + BS2REGS.r11], r11 + mov [xBX + BS2REGS.r12], r12 + mov [xBX + BS2REGS.r13], r13 + mov [xBX + BS2REGS.r14], r14 + mov [xBX + BS2REGS.r15], r15 + mov rax, [xBP - sCB] + mov [xBX + BS2REGS.rflags], rax + mov ax, cs + mov [xBX + BS2REGS.cs], ax + mov ax, ds + mov [xBX + BS2REGS.ds], ax + mov ax, es + mov [xBX + BS2REGS.es], ax + mov ax, fs + mov [xBX + BS2REGS.fs], ax + mov ax, gs + mov [xBX + BS2REGS.gs], ax + mov ax, ss + mov [xBX + BS2REGS.ss], ax + mov byte [xBX + BS2REGS.cBits], 64 + mov [xBX + BS2REGS.pad ], dl + mov [xBX + BS2REGS.pad + 1], dx + mov rax, cr0 + mov [xBX + BS2REGS.cr0], rax + mov rax, cr2 + mov [xBX + BS2REGS.cr2], rax + mov rax, cr3 + mov [xBX + BS2REGS.cr3], rax + mov rax, cr4 + mov [xBX + BS2REGS.cr4], rax + mov rax, cr8 + mov [xBX + BS2REGS.cr8], rax +%else ; !TMPL_64 + mov eax, [xBP - sCB*3] + mov dword [xBX + BS2REGS.rax], eax + mov dword [xBX + BS2REGS.rax + 4], edx + mov eax, [xBP - sCB*2] + mov dword [xBX + BS2REGS.rbx], eax + mov dword [xBX + BS2REGS.rbx + 4], edx + mov dword [xBX + BS2REGS.rcx], ecx + mov dword [xBX + BS2REGS.rcx + 4], edx + mov eax, [xBP - sCB*4] + mov dword [xBX + BS2REGS.rdx], eax + mov dword [xBX + BS2REGS.rdx + 4], edx + mov dword [xBX + BS2REGS.rdi], edi + mov dword [xBX + BS2REGS.rdi + 4], edx + mov dword [xBX + BS2REGS.rsi], esi + mov dword [xBX + BS2REGS.rsi + 4], edx + mov eax, ebp + mov ax, [xBP] + mov dword [xBX + BS2REGS.rbp], eax + mov dword [xBX + BS2REGS.rbp + 4], edx + %ifdef TMPL_16BIT + mov eax, esp + mov ax, bp + sub ax, 4 + %else + lea eax, [ebp + 8] + %endif + mov dword [xBX + BS2REGS.rsp], eax + mov dword [xBX + BS2REGS.rsp + 4], edx + %ifdef TMPL_16BIT + movzx eax, word [xBP + 2] + %else + mov eax, [xBP + 4] + %endif + mov dword [xBX + BS2REGS.rip], eax + mov dword [xBX + BS2REGS.rip + 4], edx + mov dword [xBX + BS2REGS.r8 ], edx + mov dword [xBX + BS2REGS.r8 + 4], edx + mov dword [xBX + BS2REGS.r9 ], edx + mov dword [xBX + BS2REGS.r9 + 4], edx + mov dword [xBX + BS2REGS.r10 ], edx + mov dword [xBX + BS2REGS.r10 + 4], edx + mov dword [xBX + BS2REGS.r11 ], edx + mov dword [xBX + BS2REGS.r11 + 4], edx + mov dword [xBX + BS2REGS.r12 ], edx + mov dword [xBX + BS2REGS.r12 + 4], edx + mov dword [xBX + BS2REGS.r13 ], edx + mov dword [xBX + BS2REGS.r13 + 4], edx + mov dword [xBX + BS2REGS.r14 ], edx + mov dword [xBX + BS2REGS.r14 + 4], edx + mov dword [xBX + BS2REGS.r15 ], edx + mov dword [xBX + BS2REGS.r15 + 4], edx + mov eax, [xBP - sCB] + mov dword [xBX + BS2REGS.rflags], eax + mov dword [xBX + BS2REGS.rflags + 4], edx + mov ax, cs + mov [xBX + BS2REGS.cs], ax + mov ax, ds + mov [xBX + BS2REGS.ds], ax + mov ax, es + mov [xBX + BS2REGS.es], ax + mov ax, fs + mov [xBX + BS2REGS.fs], ax + mov ax, gs + mov [xBX + BS2REGS.gs], ax + mov ax, ss + mov [xBX + BS2REGS.ss], ax + %ifdef TMPL_16BIT + mov byte [xBX + BS2REGS.cBits], 16 + %else + mov byte [xBX + BS2REGS.cBits], 32 + %endif + mov [xBX + BS2REGS.pad ], dl + mov [xBX + BS2REGS.pad + 1], dx + mov eax, cr0 + mov dword [xBX + BS2REGS.cr0], eax + mov dword [xBX + BS2REGS.cr0 + 4], edx + mov eax, cr2 + mov dword [xBX + BS2REGS.cr2], eax + mov dword [xBX + BS2REGS.cr2 + 4], edx + mov eax, cr3 + mov dword [xBX + BS2REGS.cr3], eax + mov dword [xBX + BS2REGS.cr3 + 4], edx + mov eax, cr4 + mov dword [xBX + BS2REGS.cr4], eax + mov dword [xBX + BS2REGS.cr4 + 4], edx + mov dword [xBX + BS2REGS.cr8], edx + mov dword [xBX + BS2REGS.cr8 + 4], edx +%endif ; !TMPL_64 + +.return: + pop sDX + pop sAX + pop sBX +%ifdef TMPL_16BIT + popfd +%else + popf +%endif + leave + ret +ENDPROC TMPL_NM_CMN(TestSaveRegisters) + + +;; +; Restores the CPU registers, except for rsp, rip, cs, ss and ds. +; +; @param ds:xAX Pointer to the register frame to dump. +; @uses All, but RSP, RIP, CS, SS and DS. +; +BEGINPROC TMPL_NM_CMN(TestRestoreRegisters) + push xBP + mov xBP, xSP +%ifdef TMPL_16BIT + pushfd +%else + pushf ; - 1*sCB +%endif + push sBX ; - 2*sCB + push sAX ; - 3*sCB + + mov xBX, xAX ; xBX for addressing, xAX for scratch. + + mov sAX, [xBX + BS2REGS.rax] + mov [xBP - 3*sCB], sAX + mov sAX, [xBX + BS2REGS.rbx] + mov [xBP - 2*sCB], sAX + mov sCX, [xBX + BS2REGS.rcx] + mov sDX, [xBX + BS2REGS.rdx] + mov sDI, [xBX + BS2REGS.rdi] + mov sSI, [xBX + BS2REGS.rsi] + ; skip rsp, rbp or rip. +%ifdef TMPL_64BIT + mov r8, [xBX + BS2REGS.r8] + mov r9, [xBX + BS2REGS.r9] + mov r10, [xBX + BS2REGS.r10] + mov r11, [xBX + BS2REGS.r11] + mov r12, [xBX + BS2REGS.r12] + mov r13, [xBX + BS2REGS.r13] + mov r14, [xBX + BS2REGS.r14] + mov r15, [xBX + BS2REGS.r15] +%endif + mov sAX, [xBX + BS2REGS.rflags] + mov [xBP - sCB], sAX + ; skip cs & ds. + mov ax, [xBX + BS2REGS.es] + mov es, ax + mov ax, [xBX + BS2REGS.fs] + mov fs, ax + mov ax, [xBX + BS2REGS.gs] + mov gs, ax + ; skip ss + mov sAX, [xBX + BS2REGS.cr0] + mov cr0, sAX + mov sAX, [xBX + BS2REGS.cr2] + mov cr2, sAX + mov sAX, [xBX + BS2REGS.cr3] + mov cr3, sAX + mov sAX, [xBX + BS2REGS.cr4] + mov cr4, sAX + +.return: + pop sAX + pop sBX +%ifdef TMPL_16BIT + popfd +%else + popf +%endif + leave + ret +ENDPROC TMPL_NM_CMN(TestRestoreRegisters) + + +;; +; Enables the A20 gate. +; +; @uses Nothing. +; +BEGINPROC TMPL_NM_CMN(Bs2EnableA20) + call TMPL_NM_CMN(Bs2EnableA20ViaPortA) +; call TMPL_NM_CMN(Bs2EnableA20ViaKbd) + ret +ENDPROC TMPL_NM_CMN(Bs2EnableA20) + + +;; +; Disables the A20 gate. +; +; @uses Nothing. +; +BEGINPROC TMPL_NM_CMN(Bs2DisableA20) + ; Must call both because they may be ORed together on real HW. + call TMPL_NM_CMN(Bs2DisableA20ViaKbd) + call TMPL_NM_CMN(Bs2DisableA20ViaPortA) + ret +ENDPROC TMPL_NM_CMN(Bs2DisableA20) + + +;; +; Waits for the keyboard controller to become ready. +; +; @uses Nothing +; +BEGINPROC TMPL_NM_CMN(Bs2KbdWait) + push xAX + +.check_status: + in al, 64h + test al, 1 ; KBD_STAT_OBF + jnz .read_data_and_status + test al, 2 ; KBD_STAT_IBF + jnz .check_status + + pop xAX + ret + +.read_data_and_status: + in al, 60h + jmp .check_status +ENDPROC TMPL_NM_CMN(Bs2KbdWait) + + +;; +; Sends a read command to the keyboard controller and gets the result. +; +; The caller is responsible for making sure the keyboard controller is ready +; for a command (call Bs2KbdWait if unsure). +; +; @param al The read command. +; @returns The value read is returned. +; @uses al (obviously) +; +BEGINPROC TMPL_NM_CMN(Bs2KbdRead) + out 64h, al ; Write the command. + +.check_status: + in al, 64h + test al, 1 ; KBD_STAT_OBF + jz .check_status + + in al, 60h ; Read the data. + ret +ENDPROC TMPL_NM_CMN(Bs2KbdRead) + + +;; +; Sends a write command to the keyboard controller and then sends the data. +; +; The caller is responsible for making sure the keyboard controller is ready +; for a command (call Bs2KbdWait if unsure). +; +; @param al The write command. +; @param ah The data to write. +; @uses Nothing. +; +; @todo Return status? +; +BEGINPROC TMPL_NM_CMN(Bs2KbdWrite) + out 64h, al ; Write the command. + call TMPL_NM_CMN(Bs2KbdWait) + + xchg al, ah + out 60h, al ; Write the data. + call TMPL_NM_CMN(Bs2KbdWait) + xchg al, ah + + ret +ENDPROC TMPL_NM_CMN(Bs2KbdWrite) + + +;; +; Enables the A20 gate via the keyboard controller. +; +; @uses Nothing. +; +BEGINPROC TMPL_NM_CMN(Bs2EnableA20ViaKbd) + push xAX + pushf + cli + + call TMPL_NM_CMN(Bs2KbdWait) + mov al, 0d0h ; KBD_CCMD_READ_OUTPORT + call TMPL_NM_CMN(Bs2KbdRead) + + mov ah, 002h + or ah, al + mov al, 0d1h ; KBD_CCMD_WRITE_OUTPORT + call TMPL_NM_CMN(Bs2KbdWrite) + + mov al, 0ffh ; KBD_CMD_RESET + out 64h, al + call TMPL_NM_CMN(Bs2KbdWait) + + popf + pop xAX + ret +ENDPROC TMPL_NM_CMN(Bs2EnableA20ViaKbd) + + +;; +; Disables the A20 gate via the keyboard controller. +; +; @uses Nothing. +; +BEGINPROC TMPL_NM_CMN(Bs2DisableA20ViaKbd) + push xAX + pushf + cli + + call TMPL_NM_CMN(Bs2KbdWait) + mov al, 0d0h ; KBD_CCMD_READ_OUTPORT + call TMPL_NM_CMN(Bs2KbdRead) + + mov ah, 0fdh ; ~2 + and ah, al + mov al, 0d1h ; KBD_CCMD_WRITE_OUTPORT + call TMPL_NM_CMN(Bs2KbdWrite) + + mov al, 0ffh ; KBD_CMD_RESET + out 64h, al + call TMPL_NM_CMN(Bs2KbdWait) + + popf + pop xAX + ret +ENDPROC TMPL_NM_CMN(Bs2DisableA20ViaKbd) + + +;; +; Enables the A20 gate via control port A (PS/2 style). +; +; @uses Nothing. +; +BEGINPROC TMPL_NM_CMN(Bs2EnableA20ViaPortA) + push xAX + + ; Use Control port A, assuming a PS/2 style system. + in al, 092h + test al, 02h + jnz .done ; avoid trouble writing back the same value. + or al, 2 ; enable the A20 gate. + out 092h, al + +.done: + pop xAX + ret +ENDPROC TMPL_NM_CMN(Bs2EnableA20ViaPortA) + + +;; +; Disables the A20 gate via control port A (PS/2 style). +; +; @uses Nothing. +; +BEGINPROC TMPL_NM_CMN(Bs2DisableA20ViaPortA) + push xAX + + ; Use Control port A, assuming a PS/2 style system. + in al, 092h + test al, 02h + jz .done ; avoid trouble writing back the same value. + and al, 0fdh ; disable the A20 gate. + out 092h, al + +.done: + pop xAX + ret +ENDPROC TMPL_NM_CMN(Bs2DisableA20ViaPortA) + + +;; +; Checks if the no-execution bit is supported. +; +; @returns AL=1 and ZF=0 if supported. +; @returns AL=0 and ZF=1 if not. +; @uses al +; +BEGINPROC TMPL_NM_CMN(Bs2IsNXSupported) + push xBP + mov xBP, xSP + push sBX + push sDX + push sCX + push sAX + + mov eax, 080000000h + cpuid + cmp eax, 080000001h + jb .not_supported + cmp eax, 080001000h + jae .not_supported + + mov eax, 080000001h + cpuid + test edx, X86_CPUID_EXT_FEATURE_EDX_NX + jz .not_supported + + ; supported + pop sAX + mov al, 1 + +.return: + pop sCX + pop sDX + pop sBX + leave + ret +.not_supported: + pop sAX + xor al, al + jmp .return +ENDPROC TMPL_NM_CMN(Bs2IsNXSupported) + + +;; +; Sets EFER.NXE=al if NXE is supported. +; +; @param al 0 if NXE should be disabled, non-zero if it should +; be enabled. +; @uses nothing. +; +BEGINPROC TMPL_NM_CMN(Bs2SetupNX) + push xBP + mov xBP, xSP + push sAX + push sDX + push sCX + + call TMPL_NM_CMN(Bs2IsNXSupported) + jz .done + + mov ecx, MSR_K6_EFER + rdmsr + test byte [xBP - sCB], 0ffh + jz .disable_it + or eax, MSR_K6_EFER_NXE + jmp .set_it +.disable_it: + and eax, ~MSR_K6_EFER_NXE +.set_it: + wrmsr + +.done: + pop sCX + pop sDX + pop sAX + leave + ret +ENDPROC TMPL_NM_CMN(Bs2SetupNX) + + +;; +; Disables NX if supported. +; +; @uses nothing. +; +BEGINPROC TMPL_NM_CMN(Bs2DisableNX) + push xBP + mov xBP, xSP + push xAX + + xor al, al + call TMPL_NM_CMN(Bs2SetupNX) + + pop xAX + leave + ret +ENDPROC TMPL_NM_CMN(Bs2DisableNX) + + +;; +; Enables NX if supported. +; +; @uses nothing. +; +BEGINPROC TMPL_NM_CMN(Bs2EnableNX) + push xBP + mov xBP, xSP + push xAX + + mov al, 1 + call TMPL_NM_CMN(Bs2SetupNX) + + pop xAX + leave + ret +ENDPROC TMPL_NM_CMN(Bs2EnableNX) + + +;; +; Panics if the testing feature of the VMMDev is missing. +; +; A message will be printed. +; +; @uses Nothing. +; +BEGINPROC TMPL_NM_CMN(Bs2PanicIfVMMDevTestingIsMissing) + push xDX + push sAX + + xor eax, eax + mov dx, VMMDEV_TESTING_IOPORT_NOP + in eax, dx + cmp eax, VMMDEV_TESTING_NOP_RET + je .ok + + mov xAX, .s_szMissingVMMDevTesting + call TMPL_NM_CMN(PrintStr) + call Bs2Panic + +.ok: + pop sAX + pop xDX + ret + +.s_szMissingVMMDevTesting: + db 'fatal error: The testing feature of the VMMDevVMMDev is not present!', 13, 10, 0 +ENDPROC TMPL_NM_CMN(Bs2PanicIfVMMDevTestingIsMissing) + + + +%ifdef BS2_WITH_TRAPS + +;; +; Switches to ring-0 from whatever the current mode is. +; +; @uses cs, ss, ds, es, fs, gs +; +BEGINPROC TMPL_NM_CMN(Bs2ToRing0) + push sAX + mov sAX, BS2_SYSCALL_TO_RING0 + int BS2_TRAP_SYSCALL + pop sAX + ret +ENDPROC TMPL_NM_CMN(Bs2ToRing0) + +;; +; Switches to ring-1 from whatever the current mode is. +; +; @uses cs, ss, ds, es, fs, gs +; +BEGINPROC TMPL_NM_CMN(Bs2ToRing1) + push sAX + mov sAX, BS2_SYSCALL_TO_RING1 + int BS2_TRAP_SYSCALL + pop sAX + ret +ENDPROC TMPL_NM_CMN(Bs2ToRing1) + +;; +; Switches to ring-0 from whatever the current mode is. +; +; @uses cs, ss, ds, es, fs, gs +; +BEGINPROC TMPL_NM_CMN(Bs2ToRing2) + push sAX + mov sAX, BS2_SYSCALL_TO_RING2 + int BS2_TRAP_SYSCALL + pop sAX + ret +ENDPROC TMPL_NM_CMN(Bs2ToRing2) + +;; +; Switches to ring-3 from whatever the current mode is. +; +; @uses cs, ss, ds, es, fs, gs +; +BEGINPROC TMPL_NM_CMN(Bs2ToRing3) + push sAX + mov sAX, BS2_SYSCALL_TO_RING3 + int BS2_TRAP_SYSCALL + pop sAX + ret +ENDPROC TMPL_NM_CMN(Bs2ToRing3) + +;; +; Switches the given ring from whatever the current mode is. +; +; @param AL The desired ring. +; @uses cs, ss, ds, es, fs, gs +; +BEGINPROC TMPL_NM_CMN(Bs2ToRingN) + pushf + cmp al, 3 + je .ring3 + cmp al, 2 + je .ring2 + cmp al, 1 + je .ring1 +.ring0: + call TMPL_NM_CMN(Bs2ToRing0) +.done: + popf + ret + +.ring1: + call TMPL_NM_CMN(Bs2ToRing1) + jmp .done +.ring2: + call TMPL_NM_CMN(Bs2ToRing2) + jmp .done +.ring3: + call TMPL_NM_CMN(Bs2ToRing3) + jmp .done +ENDPROC TMPL_NM_CMN(Bs2ToRingN) + +%endif ; BS2_WITH_TRAPS + + + +; +; Wrapper for dynamically calling the right specific method. +; This avoid putting large portions of the code in the 2nd template. +; + +TMPL_NM_CMN(g_pfnPrintStrInternal): TMPL_PTR_DEF 0 +TMPL_NM_CMN(g_pfnPrintChrInternal): TMPL_PTR_DEF 0 + +BEGINPROC TMPL_NM_CMN(PrintStr) + jmp [TMPL_NM_CMN(g_pfnPrintStrInternal)] +ENDPROC TMPL_NM_CMN(PrintStr) + +BEGINPROC TMPL_NM_CMN(PrintChr) + jmp [TMPL_NM_CMN(g_pfnPrintChrInternal)] +ENDPROC TMPL_NM_CMN(PrintChr) + + +%include "bootsector2-template-footer.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-common-routines-template-2.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-common-routines-template-2.mac new file mode 100644 index 00000000..44c83ebf --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-common-routines-template-2.mac @@ -0,0 +1,224 @@ +; $Id: bootsector2-common-routines-template-2.mac $ +;; @file +; bootsector2 common routines - template containing code specific to each mode. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bootsector2-template-header.mac" +ALIGNCODE(32) +GLOBALNAME TMPL_NM(g_szMode) + db TMPL_MODE_STR, 0 + + +;; +; Prints a string on the screen. +; +; @param ds:ax The string to print (null terminated). +; +; @uses nothing +; +BEGINPROC TMPL_NM(PrintStr) + push xBP + mov xBP, xSP + push xAX + push xBX + push xSI + + mov xSI, xAX +.next: + lodsb + test al, al + jz .done +%ifdef TMPL_HAVE_BIOS + mov bx, 0ff00h + mov ah, 0eh + int 10h +%else + call TMPL_NM(PrintChr) +%endif + jmp .next + +.done: + pop xSI + pop xBX + pop xAX + leave + ret +ENDPROC TMPL_NM(PrintStr) + + +;; +; Prints a string on the screen. +; +; @param al The character to print. +; +; @uses nothing +; +BEGINCODELOW +BEGINPROC TMPL_NM(PrintChr) + push xBP + mov xBP, xSP + push xAX + push xBX + +%ifndef TMPL_HAVE_BIOS + %ifdef BS2_WITH_TRAPS + mov bx, cs + and xBX, 0x3 + push xBX + jz .ring_ok + call TMPL_NM_CMN(Bs2ToRing0) +.ring_ok: + %endif + + mov bl, al + call TMPL_NM(LeaveCpuMode) + mov al, bl +BITS 16 +%endif + + mov bx, 0ff00h + mov ah, 0eh + int 10h + +%ifndef TMPL_HAVE_BIOS + call TMPL_NM(EnterCpuMode) +BITS TMPL_BITS + %ifdef BS2_WITH_TRAPS + pop xAX + test al, al + jz .ring_restored + call TMPL_NM_CMN(Bs2ToRingN) +.ring_restored: + %endif +%endif + + pop xBX + pop xAX + leave + ret +ENDPROC TMPL_NM(PrintChr) +TMPL_BEGINCODE + + +%ifndef TMPL_HAVE_BIOS + +;; +; Leaves the current CPU mode and returns to real mode. +; +; @uses nothing +; +BEGINPROC TMPL_NM(LeaveCpuMode) + jmp TMPL_NM(Bs2ExitMode) +ENDPROC TMPL_NM(LeaveCpuMode) + + +;; +; Undo what LeaveCpuMode did. +; +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC TMPL_NM(EnterCpuMode) + jmp TMPL_NM(Bs2EnterMode_rm) +ENDPROC TMPL_NM(EnterCpuMode) +TMPL_BEGINCODE +BITS TMPL_BITS + +%endif ; TMPL_HAVE_BIOS + + +;; +; Sets the global variable for the current CPU mode. +; +; @uses nothing. +; +BEGINPROC TMPL_NM(SetCpuModeGlobals) +%ifdef TMPL_CMN_PE + %ifdef BS2_INC_PE16 + mov word [g_pfnPrintStrInternal_p16], PrintStr_pe16 + mov word [g_pfnPrintChrInternal_p16], PrintChr_pe16 + %endif + %ifdef BS2_INC_PE32 + mov dword [g_pfnPrintStrInternal_p32], PrintStr_pe32 + mov dword [g_pfnPrintChrInternal_p32], PrintChr_pe32 + %endif + +%elifdef TMPL_CMN_PP + %ifdef BS2_INC_PP16 + mov word [g_pfnPrintStrInternal_p16], PrintStr_pp16 + mov word [g_pfnPrintChrInternal_p16], PrintChr_pp16 + %endif + %ifdef BS2_INC_PP32 + mov dword [g_pfnPrintStrInternal_p32], PrintStr_pp32 + mov dword [g_pfnPrintChrInternal_p32], PrintChr_pp32 + %endif + +%elifdef TMPL_CMN_PAE + %ifdef BS2_INC_PAE16 + mov word [g_pfnPrintStrInternal_p16], PrintStr_pae16 + mov word [g_pfnPrintChrInternal_p16], PrintChr_pae16 + %endif + %ifdef BS2_INC_PAE32 + mov dword [g_pfnPrintStrInternal_p32], PrintStr_pae32 + mov dword [g_pfnPrintChrInternal_p32], PrintChr_pae32 + %endif + +%elifdef TMPL_CMN_LM + %ifdef BS2_INC_LM16 + mov word [g_pfnPrintStrInternal_p16], PrintStr_lm16 + mov word [g_pfnPrintChrInternal_p16], PrintChr_lm16 + %endif + %ifdef BS2_INC_LM32 + mov dword [g_pfnPrintStrInternal_p32], PrintStr_lm32 + mov dword [g_pfnPrintChrInternal_p32], PrintChr_lm32 + %endif + %ifdef BS2_INC_LM64 + mov dword [g_pfnPrintStrInternal_p64], PrintStr_lm64 + mov dword [g_pfnPrintChrInternal_p64], PrintChr_lm64 + %endif + +%elifdef TMPL_16BIT + mov word [TMPL_NM_CMN(g_pfnPrintStrInternal)], TMPL_NM(PrintStr) + mov word [TMPL_NM_CMN(g_pfnPrintChrInternal)], TMPL_NM(PrintChr) +%else + %error "missing case" +%endif + ret +ENDPROC TMPL_NM(SetCpuModeGlobals) + + + +%include "bootsector2-template-footer.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-common-routines.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-common-routines.mac new file mode 100644 index 00000000..36d644f5 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-common-routines.mac @@ -0,0 +1,246 @@ +; $Id: bootsector2-common-routines.mac $ +;; @file +; Common bootsector routines. +; +; This is just a bit file with common code that can be included at the end of +; a bootsector2-xxx.asm file. Conventions (used elsewhere as well): +; - _rm - real-mode function suffix. +; - _r86 - common real and virtual 8086 mode suffix. +; - _p16 - common 16-bit protected mode suffix. +; - _p32 - common 32-bit protected mode suffix. +; - _p64 - common 64-bit long mode suffix. +; - _pe16 - 16-bit unpaged protected mode suffix. +; - _pe32 - 32-bit unpaged protected mode suffix. +; - _pev86 - v8086 unpaged protected mode suffix. +; - _pp16 - 16-bit paged protected mode suffix. +; - _pp32 - 32-bit paged protected mode suffix. +; - _ppv86 - v8086 paged protected mode suffix. +; - _pae16 - 16-bit pae protected mode suffix. +; - _pae32 - 32-bit pae protected mode suffix. +; - _paev86- v8086 pae protected mode suffix. +; - _lm16 - 16-bit long mode suffix. +; - _lm32 - 32-bit long mode suffix. +; - _lm64 - 64-bit long mode suffix. +; +; The routines uses a custom register based calling convention for all cpu +; modes so that the users can template multi mode code. To make life easy for +; the programmer all registers are preserved with the exception of rflags and +; any return registers that may be used. Routines that does not return +; anything will only clobber eflags. +; +; The parameter register allocation order: +; rax, rdx, rcx, rbx, rsi, rdi(, r8, r9, r10, r11) +; +; When pointers are passed by 16-bit code, segments registers are allocated in +; the following order: +; ds, es, fs, gs. +; +; The return register allocations are: +; - edx:eax for 64-bit values in 16 and 32-bit mode, +; - eax for 32-bit, +; - ax for 16-bit, +; - al for 8-bit. +; +; Routines may use other calling convensions will be named differently. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;******************************************************************************* +;* Structures and Typedefs * +;******************************************************************************* + + +;******************************************************************************* +;* Global Variables * +;******************************************************************************* +BEGINCODELOW +ALIGNDATA(32) +;; Indicates whether the VMMDev is operational. +GLOBALNAME g_fbBs2VMMDevTesting + db 1 + db 0 ; padding + +;; The test name (DS16:xxx). +g_npszBs2Test: + dd 0 +;; The number of tests that have failed. +g_uscBs2TestErrors: + dw 0 +;; The subtest name (DS16:xxx). +g_npszBs2SubTest + dd 0 +;; The start error count of the current subtest. +g_uscBs2SubTestAtErrors: + dw 0 +;; Whether we've reported the sub-test result or not. +g_fbBs2SubTestReported: + db 0 + db 0 ; padding +;; The number of sub tests. +g_uscBs2SubTests: + dw 0 +;; The number of sub tests that failed. +g_uscBs2SubTestsFailed: + dw 0 + + +;; VMMDEV_TESTING_UNIT_XXX -> string +g_aszBs2TestUnitNames: + db 'i','n','v', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db '%', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'b','y','t','e','s', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'b','y','t','e','s','/','s', 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'K','B', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'K','B','/','s', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'M','B', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'M','B','/','s', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'p','a','c','k','e','t','s', 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'p','a','c','k','e','t','s','/','s', 0, 0, 0, 0, 0, 0, 0 + db 'f','r','a','m','e','s', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'f','r','a','m','e','s','/', 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'o','c','c', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'o','c','c','/','s', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'r','n','d','t','r','p', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'c','a','l','l','s', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'c','a','l','l','s','/','s', 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 's', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'm','s', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'n','s', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'n','s','/','c','a','l','l', 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'n','s','/','f','r','a','m','e', 0, 0, 0, 0, 0, 0, 0, 0 + db 'n','s','/','o','c','c', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'n','s','/','p','a','c','k','e','t', 0, 0, 0, 0, 0, 0, 0 + db 'n','s','/','r','n','d','t','r','p', 0, 0, 0, 0, 0, 0, 0 + db 'i','n','s', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'i','n','s','/','s', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ; none + db 'p','p','1','k', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'p','p','1','0','k', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'p','p','m', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'p','p','b', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 't','i','c','k','s', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 't','i','c','k','s','/','c','a','l','l', 0, 0, 0, 0, 0, 0 + db 't','i','c','k','s','/','o','c','c', 0, 0, 0, 0, 0, 0, 0 + db 'p','a','g','e','s', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'p','a','g','e','s','/','s', 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 't','i','c','k','s','/','p','a','g','e', 0, 0, 0, 0, 0, 0 + db 'n','s','/','p','a','g','e', 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'p','s', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'p','s','/','c','a','l','l', 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'p','s','/','f','r','a','m','e', 0, 0, 0, 0, 0, 0, 0, 0 + db 'p','s','/','o','c','c', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + db 'p','s','/','p','a','c','k','e','t', 0, 0, 0, 0, 0, 0, 0 + db 'p','s','/','r','n','d','t','r','p', 0, 0, 0, 0, 0, 0, 0 + db 'p','s','/','p','a','g','e', 0, 0, 0, 0, 0, 0, 0, 0, 0 + ; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f + + +; +; Instantiate the common template code. +; +%ifdef BS2_INC_CMN_R86 + %define TMPL_RM + %include "bootsector2-common-routines-template-1.mac" +%endif +%ifdef BS2_INC_CMN_P16 + %define TMPL_PE16 + %include "bootsector2-common-routines-template-1.mac" +%endif +%ifdef BS2_INC_CMN_P32 + %define TMPL_PE32 + %include "bootsector2-common-routines-template-1.mac" +%endif +%ifdef BS2_INC_LM64 + %define TMPL_LM64 + %include "bootsector2-common-routines-template-1.mac" +%endif + +; +; Instantiate the mode specific code. +; +%ifdef BS2_INC_RM + %define TMPL_RM + %include "bootsector2-common-routines-template-2.mac" +%endif +%ifdef BS2_INC_PE16 + %define TMPL_PE16 + %include "bootsector2-common-routines-template-2.mac" +%endif +%ifdef BS2_INC_PE32 + %define TMPL_PE32 + %include "bootsector2-common-routines-template-2.mac" +%endif +%ifdef BS2_INC_PEV86 + %define TMPL_PEV86 + %include "bootsector2-common-routines-template-2.mac" +%endif +%ifdef BS2_INC_PP16 + %define TMPL_PP16 + %include "bootsector2-common-routines-template-2.mac" +%endif +%ifdef BS2_INC_PP32 + %define TMPL_PP32 + %include "bootsector2-common-routines-template-2.mac" +%endif +%ifdef BS2_INC_PPV86 + %define TMPL_PPV86 + %include "bootsector2-common-routines-template-2.mac" +%endif +%ifdef BS2_INC_PAE16 + %define TMPL_PAE16 + %include "bootsector2-common-routines-template-2.mac" +%endif +%ifdef BS2_INC_PAE32 + %define TMPL_PAE32 + %include "bootsector2-common-routines-template-2.mac" +%endif +%ifdef BS2_INC_PAEV86 + %define TMPL_PAEV86 + %include "bootsector2-common-routines-template-2.mac" +%endif +%ifdef BS2_INC_LM16 + %define TMPL_LM16 + %include "bootsector2-common-routines-template-2.mac" +%endif +%ifdef BS2_INC_LM32 + %define TMPL_LM32 + %include "bootsector2-common-routines-template-2.mac" +%endif +%ifdef BS2_INC_LM64 + %define TMPL_LM64 + %include "bootsector2-common-routines-template-2.mac" +%endif + +BEGINCODELOW + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-common-traprec-template.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-common-traprec-template.mac new file mode 100644 index 00000000..3f2271ce --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-common-traprec-template.mac @@ -0,0 +1,115 @@ +; $Id: bootsector2-common-traprec-template.mac $ +;; @file +; Boot sector 2 - Trap Records, Code Template. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bootsector2-template-header.mac" + + +;; +; Internal worker for reporting a missing trap +; +; The callee cleans up the arguments on the stack. +; +; @param [xBP + xCB*2] bExpected Expected exception number. +; @param [xBP + xCB*2+1] szExpected The name of the exception (2 bytes + terminator). +; @uses None +; +BEGINPROC TMPL_NM_CMN(TestFailedMissingTrapInternal) + push xBP + mov xBP, xSP + pushf + push sAX + + movzx eax, byte [xBP + xCB*2] + push xAX + lea sAX, [sBP + xCB*2+1] +%ifdef TMPL_16BIT + push ss +%endif + push xAX +%ifdef TMPL_16BIT + push cs +%endif + push .szFmt + call TMPL_NM_CMN(TestFailedF) +%ifdef TMPL_16BIT + add xSP, xCB*5 +%else + add xSP, xCB*3 +%endif + + pop sAX + popf + leave + ret sCB +.szFmt: db 'Missing trap #%s (%RX8)', 13, 10, 0 +ENDPROC TMPL_NM_CMN(TestFailedMissingTrapInternal) + +%ifndef TestFailedMissingTrapTemplate_defined + ;; + ; Internal template. + %macro TestFailedMissingTrapTemplate 4 + BEGINPROC TMPL_NM_CMN(TestFailedMissingTrap_%1) + push dword RT_MAKE_U32_FROM_U8(%1, %2, %3, %4) + call TMPL_NM_CMN(TestFailedMissingTrapInternal) + ret + ENDPROC TMPL_NM_CMN(TestFailedMissingTrap_%1) + %endmacro + %define TestFailedMissingTrapTemplate_defined +%endif + +TestFailedMissingTrapTemplate X86_XCPT_DE, 'D', 'E', 0 +TestFailedMissingTrapTemplate X86_XCPT_DB, 'D', 'B', 0 +TestFailedMissingTrapTemplate X86_XCPT_NMI, 'N', 'M', 0 +TestFailedMissingTrapTemplate X86_XCPT_BP, 'B', 'P', 0 +TestFailedMissingTrapTemplate X86_XCPT_OF, 'O', 'F', 0 +TestFailedMissingTrapTemplate X86_XCPT_BR, 'B', 'R', 0 +TestFailedMissingTrapTemplate X86_XCPT_UD, 'U', 'D', 0 +TestFailedMissingTrapTemplate X86_XCPT_NM, 'N', 'M', 0 +;TestFailedMissingTrapTemplate X86_XCPT_DF, 'D', 'F', 0 +;TestFailedMissingTrapTemplate X86_XCPT_CO_SEG_OVERRUN, 'C', 'O', 0 +TestFailedMissingTrapTemplate X86_XCPT_TS, 'T', 'S', 0 +TestFailedMissingTrapTemplate X86_XCPT_NP, 'N', 'P', 0 +TestFailedMissingTrapTemplate X86_XCPT_SS, 'S', 'S', 0 +TestFailedMissingTrapTemplate X86_XCPT_GP, 'G', 'P', 0 +TestFailedMissingTrapTemplate X86_XCPT_PF, 'P', 'F', 0 +TestFailedMissingTrapTemplate X86_XCPT_MF, 'M', 'F', 0 +TestFailedMissingTrapTemplate X86_XCPT_AC, 'A', 'C', 0 +;TestFailedMissingTrapTemplate X86_XCPT_MC, 'M', 'C', 0 +TestFailedMissingTrapTemplate X86_XCPT_XF, 'X', 'F', 0 + + +%include "bootsector2-template-footer.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-common-traprec.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-common-traprec.mac new file mode 100644 index 00000000..26ef3119 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-common-traprec.mac @@ -0,0 +1,209 @@ +; $Id: bootsector2-common-traprec.mac $ +;; @file +; Boot sector 2 - Trap Records. +; +; @note Don't forget to cinldue bootsector2-common-traprec-end.mac! +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%ifndef ___bootsector2_common_traprec_mac___ +%define ___bootsector2_common_traprec_mac___ + + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "iprt/x86.mac" + + +;******************************************************************************* +;* Defined Constants And Macros * +;******************************************************************************* +;; +; The base address for the records (only important for 64-bit code +; loaded above 4GB). +; We use 0 by default so we don't create too complex expressions for YASM. +%ifndef BS2_TRAP_RECS_BASE + %define BS2_TRAP_RECS_BASE 0 +%endif + +;; +; Macro to emit an trapping instruction. +; +; @param 1 The trap number (X86_XCPT_XXX). +; @param 2 The error code, 0 if none. +; @param 3+ The instruction. +; +; @sa BS2_TRAP_BRANCH_INSTR +; +%macro BS2_TRAP_INSTR 3+, + [section .traprecs] + istruc BS2TRAPREC + at BS2TRAPREC.offWhere, dd (%%trapinstr - BS2_TRAP_RECS_BASE) + at BS2TRAPREC.offResumeAddend, db (%%resume - %%trapinstr) + at BS2TRAPREC.u8TrapNo, db %1 + at BS2TRAPREC.u16ErrCd, dw %2 + iend + __SECT__ + %if %1 != X86_XCPT_BP + %%trapinstr: + %3 + %else + %3 + %%trapinstr: + %endif + call TMPL_NM_CMN(TestFailedMissingTrap_ %+ %1) + %%resume: +%endmacro + +;; +; Macro to emit an trapping instruction. +; +; @param 1 The trap number (X86_XCPT_XXX). +; @param 2 The error code, 0 if none. +; @param 3 The name of the branch label. +; @param 4+ The instruction. +; +%macro BS2_TRAP_BRANCH_INSTR 4+, + [section .traprecs] + istruc BS2TRAPREC + at BS2TRAPREC.offWhere, dd (%%trapinstr - BS2_TRAP_RECS_BASE) + at BS2TRAPREC.offResumeAddend, db (%%resume - %%trapinstr) + at BS2TRAPREC.u8TrapNo, db %1 + at BS2TRAPREC.u16ErrCd, dw %2 + iend + __SECT__ + %%trapinstr: + %4 + %3: + call TMPL_NM_CMN(TestFailedMissingTrap_ %+ %1) + %%resume: +%endmacro + +;; +; Sets up the trap records section. +; @internal +%macro BS2_TRAP_RECS_BEGIN 0, + [section .traprecs] ; Declared in bootsector2-common-init-code.mac + dq 0ffffffffeeeeeeeeh +g_aTrapRecs: + __SECT__ +%endmacro + +;; +; Terminates the trap records section. +; @internal +%macro BS2_TRAP_RECS_END 0, + [section .traprecs] +g_aTrapRecsEnd: + dq 0ddddddddcccccccch + __SECT__ +%endmacro + + +;; +; Macro for installing the trap records. +; +; This must be invoked prior to the traps. +; +; @uses Stack +; +%macro BS2_TRAP_RECS_INSTALL 0, + push sAX + push sDX + push sCX + + mov sAX, NAME(g_aTrapRecs) + mov edx, NAME(g_aTrapRecsEnd) - NAME(g_aTrapRecs) + shr edx, BS2TRAPREC_SIZE_SHIFT + mov sCX, BS2_TRAP_RECS_BASE + call TMPL_NM_CMN(TestInstallTrapRecs) + + pop sAX + pop sDX + pop sCX +%endmacro + + +;; +; Macro for uninstalling the trap records. +; +; @uses Stack +; +%macro BS2_TRAP_RECS_UNINSTALL 0, + push sAX + push sDX + push sCX + + xor sAX, sAX + xor edx, edx + xor sCX, sCX + call TMPL_NM_CMN(TestInstallTrapRecs) + + pop sAX + pop sDX + pop sCX +%endmacro + + +; +; Setup the trap record segment. +; +BS2_TRAP_RECS_BEGIN +BEGINCODELOW + + +; +; Instantiate code templates. +; +%ifdef BS2_INC_CMN_R86 + %define TMPL_RM + %include "bootsector2-common-traprec-template.mac" +%endif +%ifdef BS2_INC_CMN_P16 + %define TMPL_PE16 + %include "bootsector2-common-traprec-template.mac" +%endif +%ifdef BS2_INC_CMN_P32 + %define TMPL_PE32 + %include "bootsector2-common-traprec-template.mac" +%endif +%ifdef BS2_INC_LM64 + %define TMPL_LM64 + %include "bootsector2-common-traprec-template.mac" +%endif + +BEGINCODELOW + +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-a20-1-template.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-a20-1-template.mac new file mode 100644 index 00000000..61742419 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-a20-1-template.mac @@ -0,0 +1,385 @@ +; $Id: bootsector2-cpu-a20-1-template.mac $ +;; @file +; bootsector2 A20 - multi mode template. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "bootsector2-template-header.mac" + + +;; +; Inner loop dealing with one 64KB segment. +; +BEGINPROC TMPL_NM(TestA20_EnabledInner) +.inner_loop: + mov ah, [es:xDI] + mov al, [ds:xSI] + cmp al, ah + jne .inner_next + + not al + mov [es:xDI], al + cmp al, [ds:xSI] + mov [es:xDI], ah + je .failed + +.inner_next: + inc xDI + inc xSI + dec ecx + jnz .inner_loop + + clc + ret + +.failed: + push sBX + push sAX + push sSI + push sDI +%ifdef TMPL_16BIT + push cs +%endif + push .s_szModifiyError + call TMPL_NM_CMN(TestFailedF) + add xSP, sCB * 5 + stc + ret +.s_szModifiyError: + db TMPL_MODE_STR, '- Same memory; EDI=%RX32 ESI=%RX32 EAX=%RX32 EBX=%RX32', 13, 10, 0 +ENDPROC TMPL_NM(TestA20_EnabledInner) + + +;; +; Inner loop dealing with one 64KB segment. +; +BEGINPROC TMPL_NM(TestA20_DisabledInner) +.inner_loop: + mov ah, [es:xDI] + mov al, [ds:xSI] + cmp al, ah + jne .failed1 + + not al + mov [es:xDI], al + cmp al, [ds:xSI] + mov [es:xDI], ah + jne .failed2 + + inc xDI + inc xSI + dec ecx + jnz .inner_loop + + clc + ret + +.failed1: + push sCX + push sBX + push sAX + push sSI + push sDI +%ifdef TMPL_16BIT + push cs +%endif + push .s_szNotEqual + call TMPL_NM_CMN(TestFailedF) + add xSP, sCB * 6 + stc + ret +.s_szNotEqual: + db TMPL_MODE_STR, ' - Not equal; EDI=%RX32 ESI=%RX32 EAX=%RX32 EBX=%RX32 ECX=%RX32', 13, 10, 0 + +.failed2: + push sCX + push sBX + push sAX + push sSI + push sDI +%ifdef TMPL_16BIT + push cs +%endif + push .s_szModifiyError + call TMPL_NM_CMN(TestFailedF) + add xSP, sCB * 6 + stc + ret + +.s_szModifiyError: + db TMPL_MODE_STR, ' - Modify error; EDI=%RX32 ESI=%RX32 EAX=%RX32 EBX=%RX32 ECX=%RX32', 13, 10, 0 +ENDPROC TMPL_NM(TestA20_DisabledInner) + + +;; +; Scans memory calling sDX for each segment. +; +BEGINPROC TMPL_NM(TestA20_ScanMemory) + push sAX + push sBX + push sCX + push sDX + push sSI + push sDI + pushf + cli +%ifdef TMPL_16BIT + push es + push ds +%endif + + ; + ; The outer loop walks a segment (64 KB) at a time. + ; + mov ebx, _1M +.outer_loop: + + ; Set up the registers. +%ifdef TMPL_CMN_R86 + mov ax, 0ffffh + mov edi, 00010h + mov es, ax + mov ax, 00000h + mov esi, 00000h + mov ds, ax + mov ecx, 01000h ; at 101000 there was a VMMDevTesting MMIO page. +%elifdef TMPL_16BIT + ;; @todo need a selector we can modify. + jmp .done + +%else + mov edi, ebx + mov esi, ebx + and esi, ~_1M + mov ecx, _64K +%endif +%ifndef TMPL_CMN_R86 + ; Should we skip this segment or only check parts of it? + cmp esi, edi ; affected by A20? + je .outer_next + +%if BS2_PXX_LAST != 09ffffh + %error BS2_PXX_LAST does not have the expected value. +%endif + cmp ebx, BS2_PXX_BASE + _1M ; don't mess with page tables, stacks, MMIO or ROMs. + jb .not_low_rom_mmio_region + cmp ebx, _1M + _1M + jb .outer_next +.not_low_rom_mmio_region: + + cmp ebx, BS2_ADDR + _1M + ja .not_bs2 + mov ecx, BS2_ADDR ; don't overwrite our own code. +.not_bs2: + cmp ebx, _1M + jne .not_VMMDevTestingMMIO + mov ecx, 1000h ; don't bother with the MMIO +.not_VMMDevTestingMMIO: +%endif ; TMPL_CMN_R86 + + ; Invoke the callback. + call xDX + jc .failure + +%ifndef TMPL_CMN_R86 +.outer_next: + add ebx, _64K + cmp ebx, 32*_1M + jbe .outer_loop +%endif + +.done: +%ifdef TMPL_16BIT + pop ds + pop es +%endif + popf + pop sDI + pop sSI + pop sDX + pop sCX + pop sBX + pop sAX + ret + +.failure: +%if 1 + cmp ebx, _1M + je .contine_at_next_MB + cmp ebx, _2M + je .contine_at_next_MB + cmp ebx, _1M + _2M + je .contine_at_next_MB + cmp ebx, _4M + je .contine_at_next_MB +%endif + jmp .done + +.contine_at_next_MB: + add ebx, _1M + jmp .outer_loop +ENDPROC TMPL_NM(TestA20_ScanMemory) + + +BEGINPROC TMPL_NM(TestA20_Enabled) + push sDX + mov xDX, TMPL_NM(TestA20_EnabledInner) + call TMPL_NM(TestA20_ScanMemory) + pop sDX + ret +ENDPROC TMPL_NM(TestA20_Enabled) + + +;; +; Checks that the first 64KB at 1MB wraps is the same physical memory as at +; address 0. +; +BEGINPROC TMPL_NM(TestA20_Disabled) + push sDX + mov xDX, TMPL_NM(TestA20_DisabledInner) + call TMPL_NM(TestA20_ScanMemory) + pop sDX + ret +ENDPROC TMPL_NM(TestA20_Disabled) + + +BEGINPROC TMPL_NM(TestA20_FlushAll) + push sAX + wbinvd + mov sAX, cr3 + mov cr3, sAX + wbinvd + pop sAX + ret +ENDPROC TMPL_NM(TestA20_FlushAll) + + + +;; +; Do the A20 tests for this mode. +; +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC TMPL_NM(TestA20_rm) + push eax + + mov ax, .s_szTestName + call TestSub_r86 + + call TMPL_NM(Bs2IsModeSupported_rm) + jz .skip_not_supported + + ; + ; Do tests with A20 enabled. + ; + call Bs2EnableA20_r86 + call TMPL_NM(Bs2EnterMode_rm) +BITS TMPL_BITS + call TMPL_NM(TestA20_FlushAll) + call TMPL_NM(TestA20_Enabled) + call TMPL_NM(Bs2ExitMode) +BITS 16 + call TestSubErrorCount_r86 + cmp ax, 0 + jne .done + + ; + ; Do tests with A20 disabled. + ; + call Bs2DisableA20_r86 + call TMPL_NM(Bs2EnterMode_rm) +BITS TMPL_BITS + call TMPL_NM(TestA20_FlushAll) + call TMPL_NM(TestA20_Disabled) + call TMPL_NM(Bs2ExitMode) +BITS 16 + call TestSubErrorCount_r86 + cmp ax, 0 + jne .done + +%ifndef TMPL_CMN_V86 + ; + ; Change A20 state without leaving and entering the CPU mode. + ; + call Bs2EnableA20_r86 + call TMPL_NM(Bs2EnterMode_rm) +BITS TMPL_BITS + call TMPL_NM(TestA20_Enabled) + + call TMPL_NM_CMN(Bs2DisableA20) + call TMPL_NM(TestA20_FlushAll) + call TMPL_NM(TestA20_Disabled) + + call TMPL_NM_CMN(Bs2EnableA20) + call TMPL_NM(TestA20_FlushAll) + call TMPL_NM(TestA20_Enabled) + + call TMPL_NM_CMN(Bs2DisableA20) + call TMPL_NM(TestA20_FlushAll) + call TMPL_NM(TestA20_Disabled) + + call TMPL_NM_CMN(Bs2EnableA20) + call TMPL_NM(TestA20_FlushAll) + call TMPL_NM(TestA20_Enabled) + + call TMPL_NM(Bs2ExitMode) +BITS 16 +%endif ; !TMPL_CMN_V86 + +.done: + call Bs2DisableA20_r86 +.done1: + call TestSubDone_r86 + + pop eax + ret + +.skip_not_supported: + mov eax, .s_szSkipNotSupported + call TestSkipped_r86 + jmp .done1 + +.s_szTestName: + db TMPL_MODE_STR, 0 +.s_szSkipNotSupported: + db TMPL_MODE_STR, ' is not supported by the CPU', 10, 13, 0 +ENDPROC TMPL_NM(TestA20_rm) +TMPL_BEGINCODE +BITS TMPL_BITS + + +%include "bootsector2-template-footer.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-a20-1.asm b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-a20-1.asm new file mode 100644 index 00000000..1e727519 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-a20-1.asm @@ -0,0 +1,386 @@ +; $Id: bootsector2-cpu-a20-1.asm $ +;; @file +; Bootsector that checks the A20 emulation. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" +%include "VBox/VMMDevTesting.mac" + +; +; Include and execute the init code. +; +%define BS2_WITH_TRAPS +%define BS2_INIT_RM +%define BS2_INC_PE16 +%define BS2_INC_PE32 +%define BS2_INC_PP32 +%define BS2_INC_PAE32 +%define BS2_INC_LM64 +%include "bootsector2-common-init-code.mac" + + +; +; The benchmark driver +; +BEGINPROC main + ; + ; Test prologue. + ; + mov ax, .s_szTstName + call TestInit_r86 + + ; + ; The actual tests. + ; + call TestA20_1 ; must come first + call TestA20_rm_rm + ;call TestA20_rm_pe16 + call TestA20_rm_pe32 + call TestA20_rm_pp32 + ;call TestA20_rm_pp16 + call TestA20_rm_pae32 + call TestA20_rm_lm64 + + ; + ; We're done. + ; + call TestTerm_r86 + call Bs2Panic + +.s_szTstName: + db 'tstA20-1', 0 +.s_szInitialA20Status: + db 'Initial A20 state', 0 +ENDPROC main + + +; +; Do some initial tests. +; +BEGINPROC TestA20_1 + push eax + push edx + push ecx + push ebx + push edi + + ; + ; Check that the A20 gate is disabled when we come from the BIOS. + ; + mov ax, .s_szInitialA20Status + call TestSub_r86 + + call IsA20GateEnabled_rm + mov di, ax ; save A20 state in AX for bios test. + cmp al, 0 + je .initial_state_done + mov ax, .s_szBadInitialA20Status + call TestFailed_r86 + jmp .initial_state_done +.s_szInitialA20Status: + db 'Initial A20 state', 0 +.s_szBadInitialA20Status: + db 'Initial A20 state is enabled, expected disabled', 10, 13, 0 +.initial_state_done: + call TestSubDone_r86 + + ; + ; Disable it via the BIOS interface and check. + ; + mov ax, .s_szBios + call TestSub_r86 + + ; query support + mov ax, 2403h + int 15h + jnc .bios_2403_ok + movzx edx, ax + mov ax, .s_szBios2403Error + mov cl, VMMDEV_TESTING_UNIT_NONE + call TestValueU32_r86 + jmp .bios_2403_done +.bios_2403_ok: + movzx edx, al + mov ax, .s_szBios2403Mask + mov cl, VMMDEV_TESTING_UNIT_NONE + call TestValueU32_r86 +.bios_2403_done: + + ; Check what the bios thinks the state is. + call BiosIsA20GateEnabled_rm + cmp ax, di + je .bios_2402_done + push di + push ax + push word ds + push word .s_szBios2402Error + call TestFailedF_r86 + add sp, 8 +.bios_2402_done: + + ; Loop to make sure we get all transitions and ends up with A20 disabled. + mov cx, 10h +.bios_loop: + ; enable it + mov ax, 2401h + push cx ; paranoia that seems necessary for at least one AMI bios. + int 15h + pop cx + jnc .bios_continue1 + mov ax, .s_szBiosFailed2401 + jmp .bios_failed +.bios_continue1: + + call IsA20GateEnabled_rm + cmp al, 1 + je .bios_continue2 + mov ax, .s_szBiosEnableFailed + jmp .bios_failed +.bios_continue2: + + ; disable + mov ax, 2400h + push cx ; paranoia that seems necessary for at least one AMI bios. + int 15h + pop cx + jnc .bios_continue3 + mov ax, .s_szBiosFailed2400 + jmp .bios_failed +.bios_continue3: + call IsA20GateEnabled_rm + cmp al, 0 + je .bios_continue4 + mov ax, .s_szBiosDisableFailed + jmp .bios_failed +.bios_continue4: + + loop .bios_loop + jmp .bios_done +.s_szBios: + db 'INT 15h AH=24 A20 Gate interface', 0 +.s_szBios2403Mask: + db 'AX=2403 return (AL)', 0 +.s_szBios2403Error: + db 'AX=2403 error (AX)', 10, 13, 0 +.s_szBios2402Error: + db '2402h -> AX=%RX16 expected %RX16', 10, 13, 0 +.s_szBiosFailed2400: + db '2400h interface failed', 10, 13, 0 +.s_szBiosFailed2401: + db '2401h interface failed', 10, 13, 0 +.s_szBiosDisableFailed: + db 'BIOS failed to disable A20 (or bad CPU)', 10, 13, 0 +.s_szBiosEnableFailed: + db 'BIOS failed to enable A20', 10, 13, 0 +.bios_failed: + call TestFailed_r86 +.bios_done: + call TestSubDone_r86 + call Bs2DisableA20ViaPortA_r86 + call Bs2DisableA20ViaKbd_r86 + + ; + ; Test the fast A20 gate interface. + ; + mov ax, .s_szFastA20 + call TestSub_r86 + + mov cx, 10h +.fast_loop: + call Bs2EnableA20ViaPortA_r86 + call IsA20GateEnabled_rm + cmp al, 1 + mov ax, .s_szFastEnableFailed + jne .fast_failed + + call Bs2DisableA20ViaPortA_r86 + call IsA20GateEnabled_rm + cmp al, 0 + mov ax, .s_szFastDisableFailed + jne .fast_failed + loop .fast_loop + + jmp .fast_done +.s_szFastA20: + db 'Fast A20 Gate Interface', 0 +.s_szFastDisableFailed: + db 'Fast A20 gate disabling failed', 10, 13, 0 +.s_szFastEnableFailed: + db 'Fast A20 gate enabling failed', 10, 13, 0 +.fast_failed: + call TestFailed_r86 +.fast_done: + call TestSubDone_r86 + call Bs2DisableA20ViaPortA_r86 + call Bs2DisableA20ViaKbd_r86 + + ; + ; Test the keyboard interface. + ; + mov ax, .s_szKeyboardA20 + call TestSub_r86 + + mov cx, 10h +.kbd_loop: + call Bs2EnableA20ViaKbd_r86 + call IsA20GateEnabled_rm + cmp al, 1 + mov ax, .s_szKbdEnableFailed + jne .kbd_failed + + call Bs2DisableA20ViaKbd_r86 + call IsA20GateEnabled_rm + cmp al, 0 + mov ax, .s_szKbdDisableFailed + jne .kbd_failed + loop .kbd_loop + + jmp .kbd_done +.s_szKeyboardA20: + db 'Keyboard A20 Gate Interface', 0 +.s_szKbdDisableFailed: + db 'Disabling the A20 gate via the keyboard controller failed', 10, 13, 0 +.s_szKbdEnableFailed: + db 'Enabling the A20 gate via the keyboard controller failed', 10, 13, 0 +.kbd_failed: + call TestFailed_r86 +.kbd_done: + call TestSubDone_r86 + call Bs2DisableA20ViaPortA_r86 + call Bs2DisableA20ViaKbd_r86 + + pop edi + pop ebx + pop ecx + pop edx + pop eax + ret +ENDPROC TestA20_1 + + +;; +; Checks if the A20 gate is enabled. +; +; This is do by temporarily changing a word at address 0000000h and see if this +; is reflected at address 0100000h (1 MB). The word written is +; ~*(word *)0x100000h to make sure it won't accidentally match. +; +; @returns ax 1 if enabled, 0 if disabled. +; +BEGINPROC IsA20GateEnabled_rm + push ds + push es + push dx + pushf + cli + +.once_again: + xor ax, ax + mov ds, ax + dec ax + mov es, ax + + mov ax, [es:0010h] ; 0ffff:0010 => 0100000h (1 MB) + mov dx, [ds:0000h] ; 00000:0000 => 0000000h - save it + not ax + mov [ds:0000h], ax ; 0000000h - write ~[0100000h] + cmp [es:0010h], ax ; 0100000h - same as 0000000h if A20 is disabled. + mov [ds:0000h], dx ; 0000000h - restore original value + setne al + movzx ax, al + + popf + pop dx + pop es + pop ds + ret +ENDPROC IsA20GateEnabled_rm + +;; +; Checks if the BIOS thinks the A20 gate is enabled. +; +; @returns ax 1 if enabled, 0 if disabled. +; +BEGINPROC BiosIsA20GateEnabled_rm + push ecx + push eax + + mov ax, 2402h + int 15h + jnc .ok + mov al, 080h +.ok: + mov cx, ax + pop eax + mov ax, cx + pop ecx + ret +ENDPROC BiosIsA20GateEnabled_rm + +; +; Instantiate the template code. +; +%include "bootsector2-template-footer.mac" ; reset the initial environemnt. + +%define TMPL_RM +%include "bootsector2-cpu-a20-1-template.mac" +;%define TMPL_CMN_V86 +;%include "bootsector2-cpu-a20-1-template.mac" +%define TMPL_PE16 +%include "bootsector2-cpu-a20-1-template.mac" +%define TMPL_PE32 +%include "bootsector2-cpu-a20-1-template.mac" +;%define TMPL_PP16 +;%include "bootsector2-cpu-a20-1-template.mac" +%define TMPL_PP32 +%include "bootsector2-cpu-a20-1-template.mac" +;%define TMPL_PAE16 +;%include "bootsector2-cpu-a20-1-template.mac" +%define TMPL_PAE32 +%include "bootsector2-cpu-a20-1-template.mac" +;%define TMPL_LM16 +;%include "bootsector2-cpu-a20-1-template.mac" +;%define TMPL_LM32 +;%include "bootsector2-cpu-a20-1-template.mac" +%define TMPL_LM64 +%include "bootsector2-cpu-a20-1-template.mac" + + +; +; End sections and image. +; +%include "bootsector2-common-end.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-ac-loop.asm b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-ac-loop.asm new file mode 100644 index 00000000..47873488 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-ac-loop.asm @@ -0,0 +1,125 @@ +; $Id: bootsector2-cpu-ac-loop.asm $ +;; @file +; Bootsector test for debug exceptions. +; +; Recommended (but not necessary): +; VBoxManage setextradata bs-cpu-xcpt-2 VBoxInternal/Devices/VMMDev/0/Config/TestingEnabled 1 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" +%include "VBox/VMMDevTesting.mac" + + +; +; Include and execute the init code. +; + %define BS2_INIT_PE32 + %define BS2_WITH_TRAPS + %define BS2_WITH_TRAPRECS + %define BS2_INC_PE32 + %define BS2_INC_RM ; for SetCpuModeGlobals_rm + %include "bootsector2-common-init-code.mac" + + +; +; The main() function. +; +BEGINPROC main + BITS 32 + ; + ; Test prologue. + ; + mov ax, .s_szTstName + call TestInit_p32 + call Bs2EnableA20_p32 + cli ; raw-mode hack + + ; + ; Execute the tests + ; + sub esp, 20h + + ; Get the address of the #AC IDT entry. + sidt [esp] + mov eax, [esp + 2] + add eax, 8 * X86_XCPT_AC + + ; Make it execute in ring-3. + mov word [eax + 2], BS2_SEL_R3_CS32 ; u16Sel + or byte [eax + 5], 3 << 5 ; u2Dpl = 3 + + ; Enable AC. + mov eax, cr0 + or eax, X86_CR0_AM + mov cr0, eax + + ; Switch to ring-3 + call Bs2ToRing3_p32 + + ; Enable AC. + pushfd + or dword [esp], X86_EFL_AC + popfd + + ;; Test it. - won't work as the handle touches CR2, which traps in ring-3. + ;BS2_TRAP_INSTR X86_XCPT_AC, 0, mov dword [esp + 3], 0 + + ; Misalign the stack and use it. + or esp, 3 + push esp ; this will loop forever on real intel hardware. + and esp, ~3h + + add esp, 20h + + ; + ; We're done. + ; + call TestTerm_p32 + ret + +.s_szTstName: + db 'tstCpuAcLoop', 0 +ENDPROC main + + +; +; End sections and image. +; +%include "bootsector2-common-end.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-basic-1-template.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-basic-1-template.mac new file mode 100644 index 00000000..b099fde3 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-basic-1-template.mac @@ -0,0 +1,84 @@ +; $Id: bootsector2-cpu-basic-1-template.mac $ +;; @file +; bootsector2 basic #1 - multi mode template. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "bootsector2-template-header.mac" + + +;; +; Do the tests for this mode. +; +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC TMPL_NM(TestBasic1_rm) + push eax + + mov ax, .s_szTestName + call TestSub_r86 + + call TMPL_NM(Bs2IsModeSupported_rm) + jz .skip_not_supported + + call TMPL_NM(Bs2EnterMode_rm) +BITS TMPL_BITS + ; Later, currently just getting thru the mode switch is good enough. + nop + call TMPL_NM(Bs2ExitMode) +BITS 16 +.done1: + call TestSubDone_r86 + + pop eax + ret + +.skip_not_supported: + mov eax, .s_szSkipNotSupported + call TestSkipped_r86 + jmp .done1 + +.s_szTestName: + db TMPL_MODE_STR, 0 +.s_szSkipNotSupported: + db TMPL_MODE_STR, ' is not supported by the CPU', 10, 13, 0 +ENDPROC TMPL_NM(TestBasic1_rm) +TMPL_BEGINCODE +BITS TMPL_BITS + + +%include "bootsector2-template-footer.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-basic-1.asm b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-basic-1.asm new file mode 100644 index 00000000..66e150fb --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-basic-1.asm @@ -0,0 +1,129 @@ +; $Id: bootsector2-cpu-basic-1.asm $ +;; @file +; Bootsector that checks the basic CPU operation. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" +%include "VBox/VMMDevTesting.mac" + +; +; Include and execute the init code. +; +%define BS2_WITH_TRAPS +%define BS2_INIT_RM +%define BS2_INC_PE16 +%define BS2_INC_PE32 +%define BS2_INC_PP16 +%define BS2_INC_PP32 +%define BS2_INC_PAE32 +%define BS2_INC_PAE16 +%define BS2_INC_LM16 +%define BS2_INC_LM32 +%define BS2_INC_LM64 +%include "bootsector2-common-init-code.mac" + + +; +; The benchmark driver +; +BEGINPROC main + ; + ; Test prologue. + ; + mov ax, .s_szTstName + call TestInit_r86 + + ; + ; The actual tests. + ; + call TestBasic1_rm_rm + call TestBasic1_rm_pe16 + call TestBasic1_rm_pe32 + call TestBasic1_rm_pp32 + call TestBasic1_rm_pp16 + call TestBasic1_rm_pae16 + call TestBasic1_rm_pae32 + call TestBasic1_rm_lm64 + call TestBasic1_rm_lm32 + call TestBasic1_rm_lm16 + + ; + ; We're done. + ; + call TestTerm_r86 + call Bs2Panic + +.s_szTstName: + db 'tstBasic1-1', 0 +.s_szInitialBasic1Status: + db 'Initial Basic1 state', 0 +ENDPROC main + + +; +; Instantiate the template code. +; +%include "bootsector2-template-footer.mac" ; reset the initial environemnt. + +%define TMPL_RM +%include "bootsector2-cpu-basic-1-template.mac" +;%define TMPL_CMN_V86 +;%include "bootsector2-cpu-basic-1-template.mac" +%define TMPL_PE16 +%include "bootsector2-cpu-basic-1-template.mac" +%define TMPL_PE32 +%include "bootsector2-cpu-basic-1-template.mac" +%define TMPL_PP16 +%include "bootsector2-cpu-basic-1-template.mac" +%define TMPL_PP32 +%include "bootsector2-cpu-basic-1-template.mac" +%define TMPL_PAE16 +%include "bootsector2-cpu-basic-1-template.mac" +%define TMPL_PAE32 +%include "bootsector2-cpu-basic-1-template.mac" +%define TMPL_LM16 +%include "bootsector2-cpu-basic-1-template.mac" +%define TMPL_LM32 +%include "bootsector2-cpu-basic-1-template.mac" +%define TMPL_LM64 +%include "bootsector2-cpu-basic-1-template.mac" + + +; +; End sections and image. +; +%include "bootsector2-common-end.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-db-loop.asm b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-db-loop.asm new file mode 100644 index 00000000..212906e2 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-db-loop.asm @@ -0,0 +1,161 @@ +; $Id: bootsector2-cpu-db-loop.asm $ +;; @file +; Bootsector test for debug exception loop. +; +; Recommended (but not necessary): +; VBoxManage setextradata bs-cpu-db-loop VBoxInternal/Devices/VMMDev/0/Config/TestingEnabled 1 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" +%include "VBox/VMMDevTesting.mac" + + +; +; Include and execute the init code. +; + %define BS2_INIT_PE32 + %define BS2_WITH_TRAPS + %define BS2_WITH_XCPT_DB_CLEARING_TF + %define BS2_INC_PE16 + %define BS2_INC_PE32 + %define BS2_INC_RM ; for SetCpuModeGlobals_rm + %include "bootsector2-common-init-code.mac" + + +; +; The main() function. +; +BEGINPROC main + BITS 32 + ; + ; Test prologue. + ; + mov ax, .s_szTstName + call TestInit_p32 + call Bs2EnableA20_p32 + cli ; raw-mode hack + sub esp, 20h + + call Bs2Thunk_p32_p16 + BITS 16 + + ; + ; We require a stack that can wrap around here. The default stack + ; doesn't allow us to do this, so we'll configure a custom one + ; where the page tables usually are. + ; + mov eax, [bs2Gdt + BS2_SEL_SS16] + mov ebx, [bs2Gdt + BS2_SEL_SS16 + 4] + + and eax, 0xffff + or eax, (BS2_PXX_BASE & 0xffff) << 16 + and ebx, 0x00ffff00 + or ebx, BS2_PXX_BASE & 0xff000000 + or ebx, (BS2_PXX_BASE & 0x00ff0000) >> 16 + mov [bs2GdtSpare0], eax + mov [bs2GdtSpare0 + 4], ebx + + + ; + ; Switch the stack. + ; + mov ax, ss + mov es, ax ; saved ss + mov edi, esp ; saved esp + + mov ax, BS2_SEL_SPARE0 + mov ss, ax + mov esp, 0xfff0 + + + ; + ; Arm the breakpoint. + ; + and dword [esp + 2], 0 + sidt [esp] + mov eax, [esp + 2] + add eax, 8 + mov dr0, eax + mov eax, X86_DR7_RA1_MASK | X86_DR7_GE \ + | X86_DR7_L0 | X86_DR7_G0 | X86_DR7_RW(0, X86_DR7_RW_RW) | X86_DR7_LEN(0, X86_DR7_LEN_DWORD) + mov dr7, eax + + ; + ; Trigger a single step exception. + ; + pushf + or word [xSP], X86_EFL_TF + popf + xchg eax, ebx + xchg edx, ecx ; should get a #DB here. + xchg eax, ebx + xchg edx, ecx + + ; + ; If we get thus far, we've failed. + ; + mov ax, es ; restore ss + mov ss, ax + mov esp, edi ; restore esp + + call Bs2Thunk_p16_p32 + BITS 32 + + mov eax, .s_szFailed + call TestFailed_p32 + + ; + ; We're done. + ; + call TestTerm_p32 + add esp, 20h + ret + +.s_szTstName: + db 'tstCpuDbLoop', 0 +.s_szFailed: + db 'no #DB loop detected',0 +ENDPROC main + + +; +; End sections and image. +; +%include "bootsector2-common-end.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-hidden-regs-1-template.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-hidden-regs-1-template.mac new file mode 100644 index 00000000..dbd52cc1 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-hidden-regs-1-template.mac @@ -0,0 +1,352 @@ +; $Id: bootsector2-cpu-hidden-regs-1-template.mac $ +;; @file +; bootsector2 hidden CPU registers - multi mode template. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "bootsector2-template-header.mac" + +;; +; Helper for reporting several register values at in a sequence. +; +BEGINPROC TMPL_NM(TestValueRegSZZ) + push sAX + push sBX + + mov xBX, xAX +.next: + mov xAX, xBX + call TMPL_NM_CMN(TestValueReg) +.inner_next: + inc xBX + cmp byte [xBX], 0 + jne .inner_next + + inc xBX + cmp byte [xBX], 0 + je .done + jmp .next + +.done + pop sBX + pop sAX + ret +ENDPROC TMPL_NM(TestValueRegSZZ) + +;; +; Tests various LDTR values +; +BEGINPROC TMPL_NM(doLdtrTests) + push sAX + + ; The inital LDT. + mov sAX, .szLdtrInitial + call TMPL_NM(TestValueRegSZZ) + + ; Load our LDT + mov eax, BS2_SEL_LDT + lldt ax + mov sAX, .szLdtrValid + call TMPL_NM(TestValueRegSZZ) + + ; NULL LDTR. + xor eax, eax + lldt ax + mov sAX, .szLdtr0 + call TMPL_NM(TestValueRegSZZ) + + ; NULL(1) LDTR. + mov eax, 1 + lldt ax + mov sAX, .szLdtr1 + call TMPL_NM(TestValueRegSZZ) + + ; NULL(2) LDTR. + mov eax, 2 + lldt ax + mov sAX, .szLdtr2 + call TMPL_NM(TestValueRegSZZ) + + ; NULL(3) LDTR. + mov eax, 3 + lldt ax + mov sAX, .szLdtr3 + call TMPL_NM(TestValueRegSZZ) + +.done + pop sAX + ret + +.szLdtrInitial: + db 'LDTR(Initial) sel:ldtr', 0 + db 'LDTR(Initial) base:ldtr_base', 0 + db 'LDTR(Initial) limit:ldtr_lim', 0 + db 'LDTR(Initial) attr:ldtr_attr', 0 + db 0 +.szLdtrValid: + db 'LDTR(Valid) sel:ldtr', 0 + db 'LDTR(Valid) base:ldtr_base', 0 + db 'LDTR(Valid) limit:ldtr_lim', 0 + db 'LDTR(Valid) attr:ldtr_attr', 0 + db 0 +.szLdtr0: + db 'LDTR(0) sel:ldtr', 0 + db 'LDTR(0) base:ldtr_base', 0 + db 'LDTR(0) limit:ldtr_lim', 0 + db 'LDTR(0) attr:ldtr_attr', 0 + db 0 +.szLdtr1: + db 'LDTR(1) sel:ldtr', 0 + db 'LDTR(1) base:ldtr_base', 0 + db 'LDTR(1) limit:ldtr_lim', 0 + db 'LDTR(1) attr:ldtr_attr', 0 + db 0 +.szLdtr2: + db 'LDTR(2) sel:ldtr', 0 + db 'LDTR(2) base:ldtr_base', 0 + db 'LDTR(2) limit:ldtr_lim', 0 + db 'LDTR(2) attr:ldtr_attr', 0 + db 0 +.szLdtr3: + db 'LDTR(3) sel:ldtr', 0 + db 'LDTR(3) base:ldtr_base', 0 + db 'LDTR(3) limit:ldtr_lim', 0 + db 'LDTR(3) attr:ldtr_attr', 0 + db 0 +ENDPROC TMPL_NM(doLdtrTests) + + +;; +; Tests various LDTR values +; +BEGINPROC TMPL_NM(doTrTests) + push sAX + + ; Initial TR values. + mov sAX, .szTrInitial + call TMPL_NM(TestValueRegSZZ) + jmp .next1 +.szTrInitial: + db 'TR(Initial) sel:tr', 0 + db 'TR(Initial) base:tr_base', 0 + db 'TR(Initial) limit:tr_lim', 0 + db 'TR(Initial) attr:tr_attr', 0 + db 0 +.next1: + + ; Our TR. +%ifdef TMPL_CMN_LM + mov ax, BS2_SEL_TSS64 + ltr ax + mov sAX, .szTrTss64 + call TMPL_NM(TestValueRegSZZ) + jmp .next2 +.szTrTss64: + db 'TR(64) sel:tr', 0 + db 'TR(64) base:tr_base', 0 + db 'TR(64) limit:tr_lim', 0 + db 'TR(64) attr:tr_attr', 0 + db 0 + +%elifdef TMPL_PP32 + mov ax, BS2_SEL_TSS32 + ltr ax + mov sAX, .szTrTss32 + call TMPL_NM(TestValueRegSZZ) + jmp .next2 +.szTrTss32: + db 'TR(32) sel:tr', 0 + db 'TR(32) base:tr_base', 0 + db 'TR(32) limit:tr_lim', 0 + db 'TR(32) attr:tr_attr', 0 + db 0 +;%elifdef TMPL_PP16 +; mov ax, BS2_SEL_TSS16 +; mov sAX, .szTrTss16 +; call TMPL_NM(TestValueRegSZZ) +%endif +.next2: + + ; Note! Loading 0 into TR is not possible, unlike with LDTR. + +.done + pop sAX + ret +ENDPROC TMPL_NM(doTrTests) + + +;; +; Test loading of NULL selectors. +; +BEGINPROC TMPL_NM(doNullSelTests) + push sAX + push xBX + push gs + + mov ax, ss + mov gs, ax + mov sAX, .szGsSs + call TMPL_NM(TestValueRegSZZ) + + xor eax, eax + mov gs, ax + mov sAX, .szGs0 + call TMPL_NM(TestValueRegSZZ) + + mov ax, 3 + mov gs, ax + mov sAX, .szGs3 + call TMPL_NM(TestValueRegSZZ) + +%ifdef TMPL_64BIT + pushf + cli + mov bx, ss + mov ax, 0 + mov ss, ax + mov sAX, .szSs0 + call TMPL_NM(TestValueRegSZZ) + mov ss, bx + popf + + call TMPL_NM_CMN(Bs2ToRing2) + mov bx, ss + mov ax, 2 + mov ss, ax + mov sAX, .szSs1Ring2 + call TMPL_NM(TestValueRegSZZ) + mov ss, bx + + test byte [g_fCpuAmd], 1 + jz .not_amd + mov ax, 3 + mov ss, ax + mov sAX, .szSs3Ring2 + call TMPL_NM(TestValueRegSZZ) + +.not_amd: + call TMPL_NM_CMN(Bs2ToRing0) + +%endif + + pop gs + pop xBX + pop sAX + ret + +.szGsSs: + db 'GS(ss) sel:gs', 0 + db 'GS(ss) base:gs_base', 0 + db 'GS(ss) limit:gs_lim', 0 + db 'GS(ss) attr:gs_attr', 0 + db 0 +.szGs0: + db 'GS(0) sel:gs', 0 + db 'GS(0) base:gs_base', 0 + db 'GS(0) limit:gs_lim', 0 + db 'GS(0) attr:gs_attr', 0 + db 0 +.szGs3: + db 'GS(3) sel:gs', 0 + db 'GS(3) base:gs_base', 0 + db 'GS(3) limit:gs_lim', 0 + db 'GS(3) attr:gs_attr', 0 + db 0 +%ifdef TMPL_64BIT +.szSs0: + db 'SS(0) sel:ss', 0 + db 'SS(0) base:ss_base', 0 + db 'SS(0) limit:ss_lim', 0 + db 'SS(0) attr:ss_attr', 0 + db 0 +.szSs1Ring2 + db 'ring-2 SS(2) sel:ss', 0 + db 'ring-2 SS(2) base:ss_base', 0 + db 'ring-2 SS(2) limit:ss_lim', 0 + db 'ring-2 SS(2) attr:ss_attr', 0 + db 0 +.szSs3Ring2 + db 'ring-2 SS(3) sel:ss', 0 + db 'ring-2 SS(3) base:ss_base', 0 + db 'ring-2 SS(3) limit:ss_lim', 0 + db 'ring-2 SS(3) attr:ss_attr', 0 + db 0 +%endif +ENDPROC TMPL_NM(doNullSelTests) + + +BEGINPROC TMPL_NM(doTestsWorker) + push sAX + + mov xAX, .s_szSubTest + call TMPL_NM_CMN(TestSub) + call TMPL_NM(doLdtrTests) + call TMPL_NM(doTrTests) + call TMPL_NM(doNullSelTests) + +.done + pop sAX + ret + +.s_szSubTest: + db TMPL_MODE_STR, 0 +ENDPROC TMPL_NM(doTestsWorker) + + +;; +; Do the tests for this mode. +; +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC TMPL_NM(doTests_rm) + call TMPL_NM(Bs2IsModeSupported_rm) + jz .done + call TMPL_NM(Bs2EnterMode_rm) +BITS TMPL_BITS + + call TMPL_NM(doTestsWorker) + + call TMPL_NM(Bs2ExitMode) +BITS 16 +.done: + ret +ENDPROC TMPL_NM(doTests_rm) +TMPL_BEGINCODE +BITS TMPL_BITS + + +%include "bootsector2-template-footer.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-hidden-regs-1.asm b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-hidden-regs-1.asm new file mode 100644 index 00000000..75bd87dc --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-hidden-regs-1.asm @@ -0,0 +1,285 @@ +; $Id: bootsector2-cpu-hidden-regs-1.asm $ +;; @file +; Bootsector that shows/tests the content of hidden CPU registers. +; +; Requires VMMDevTesting. Enable it via VBoxManage: +; VBoxManage setextradata bs-cpu-hidden-regs-1 VBoxInternal/Devices/VMMDev/0/Config/TestingEnabled 1 + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" +%include "VBox/VMMDevTesting.mac" + +; +; Include and execute the init code. +; + %define BS2_INIT_RM + %define BS2_INC_PE16 + %define BS2_INC_PE32 + %define BS2_INC_PP32 + %define BS2_INC_LM64 + %define BS2_WITH_TRAPS + %define BS2_WITH_MANUAL_LTR + %include "bootsector2-common-init-code.mac" + + +; +; The benchmark driver +; +BEGINPROC main + ; + ; Test prologue. + ; + mov ax, .s_szTstName + call TestInit_r86 + call Bs2EnableA20_r86 + call Bs2PanicIfVMMDevTestingIsMissing_r86 + + call reportPostBiosValues + call rmTests + call doTests_rm_pe32 + call doTests_rm_pp32 + call doTests_rm_lm64 + + ; + ; We're done. + ; + call TestTerm_r86 + call Bs2Panic + +.s_szTstName: + db 'tstCpuHidRegs', 0 +ENDPROC main + + +; +; Reports the values of interesting hidden registers as we start the test, i.e. +; right after the BIOS completed. +; +BEGINPROC reportPostBiosValues + push ax + push bx + mov ax, .s_szTstInitial + call TestSub_r86 + + mov ax, .s_szzStart + call TestValueRegSZZ_rm + +.done + pop bx + pop ax + ret + +.s_szzStart: + db 'BIOS - ldtr:ldtr', 0; + db 'BIOS - ldtr_base:ldtr_base', 0; + db 'BIOS - ldtr_limit:ldtr_lim', 0; + db 'BIOS - ldtr_attr:ldtr_attr', 0; + db 'BIOS - tr:tr', 0; + db 'BIOS - tr_base:tr_base', 0; + db 'BIOS - tr_limit:tr_lim', 0; + db 'BIOS - tr_attr:tr_attr', 0; + db 'BIOS - cs:cs', 0; + db 'BIOS - cs_base:cs_base', 0; + db 'BIOS - cs_limit:cs_lim', 0; + db 'BIOS - cs_attr:cs_attr', 0; + db 'BIOS - ss:ss', 0; + db 'BIOS - ss_base:ss_base', 0; + db 'BIOS - ss_limit:ss_lim', 0; + db 'BIOS - ss_attr:ss_attr', 0; + db 'BIOS - ds:ds', 0; + db 'BIOS - ds_base:ds_base', 0; + db 'BIOS - ds_limit:ds_lim', 0; + db 'BIOS - ds_attr:ds_attr', 0; + db 0,0,0,0 ; terminator +.s_szTstInitial: + db 'Post BIOS Values', 0 +ENDPROC reportPostBiosValues + + +; +; Reports the values of interesting hidden registers as we start the test, i.e. +; right after the BIOS completed. +; +BEGINPROC rmTests + push eax + push ebx + pushfd + cli + + mov ax, .s_szTstRM + call TestSub_r86 + + ; Check if CS changes when leaving protected mode. + mov ax, .s_szzRMPre + call TestValueRegSZZ_rm + mov byte [cs:.s_dwDummy], 1 + call Bs2EnterMode_rm_pe32 +BITS 32 + mov eax, .s_szzProt32 + call TestValueRegSZZ_pe32 + ; mov word [cs:.s_dwDummy], 2 - this shall GP(CS). + call Bs2ExitMode_pe32 +BITS 16 + mov ax, .s_szzRMPost + call TestValueRegSZZ_rm + mov dword [cs:.s_dwDummy], 3 + + ; + ; What happens if we make CS32 execute-only and return to real-mode. + ; + mov byte [cs:.s_dwDummy], 1 + call Bs2EnterMode_rm_pe16 + jmp BS2_SEL_CS16_EO:.loaded_cs16_eo +.loaded_cs16_eo: + mov eax, .s_szzProtEO + call TestValueRegSZZ_pe16 + ; mov ax, word [cs:.s_dwDummy] - this shall GP(CS). + ; mov word [cs:.s_dwDummy], 2 - this shall GP(CS). + + ; Leave real-mode ourselves. + mov eax, cr0 + and eax, ~X86_CR0_PE + mov cr0, eax + + ; All but cs gets reloaded. + xor ax, ax + mov ss, ax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + ; Display CS and do a test. + mov ax, .s_szzRMEO + call TestValueRegSZZ_rm + + mov ax, [cs:.s_dwDummy] ; works on intel + mov dword [cs:.s_dwDummy], 3 ; ditto + + jmp far 0000:.load_rm_cs +.load_rm_cs: + ; Display CS to check that it remained unchanged. + mov ax, .s_szzRMEO2 + call TestValueRegSZZ_rm + + ; Cleanup everything properly. + call Bs2EnterMode_rm_pe32 +BITS 32 + call Bs2ExitMode_pe32 +BITS 16 + + popfd + pop ebx + pop eax + ret + +.s_dwDummy: + dd 0 +.s_szzRMPre: + db 'RM Pre - cs:cs', 0; + db 'RM Pre - cs_base:cs_base', 0; + db 'RM Pre - cs_limit:cs_lim', 0; + db 'RM Pre - cs_attr:cs_attr', 0; + db 0,0,0,0 ; terminator +.s_szzProt32: + db 'Prot32 - cs:cs', 0; + db 'Prot32 - cs_base:cs_base', 0; + db 'Prot32 - cs_limit:cs_lim', 0; + db 'Prot32 - cs_attr:cs_attr', 0; + db 0,0,0,0 ; terminator +.s_szzRMPost: + db 'RM Post - cs:cs', 0; + db 'RM Post - cs_base:cs_base', 0; + db 'RM Post - cs_limit:cs_lim', 0; + db 'RM Post - cs_attr:cs_attr', 0; + db 0,0,0,0 ; terminator +.s_szzProtEO: + db 'Prot 16 EO,L-1,NA - cs:cs', 0; + db 'Prot 16 EO,L-1,NA - cs_base:cs_base', 0; + db 'Prot 16 EO,L-1,NA - cs_limit:cs_lim', 0; + db 'Prot 16 EO,L-1,NA - cs_attr:cs_attr', 0; + db 0,0,0,0 ; terminator +.s_szzRMEO: + db 'RM Post EO,L-1,NA - cs:cs', 0; + db 'RM Post EO,L-1,NA - cs_base:cs_base', 0; + db 'RM Post EO,L-1,NA - cs_limit:cs_lim', 0; + db 'RM Post EO,L-1,NA - cs_attr:cs_attr', 0; + db 0,0,0,0 ; terminator +.s_szzRMEO2: + db 'RM CS(0) EO,L-1 - cs:cs', 0; + db 'RM CS(0) EO,L-1 - cs_base:cs_base', 0; + db 'RM CS(0) EO,L-1 - cs_limit:cs_lim', 0; + db 'RM CS(0) EO,L-1 - cs_attr:cs_attr', 0; + db 0,0,0,0 ; terminator +.s_szTstRM: + db 'Real Mode Test', 0 +ENDPROC rmTests + + + +; +; Instantiate the template code. +; +%include "bootsector2-template-footer.mac" ; reset the initial environemnt. + +%define TMPL_RM +%include "bootsector2-cpu-hidden-regs-1-template.mac" +;%define TMPL_CMN_V86 +;%include "bootsector2-cpu-hidden-regs-1-template.mac" +%define TMPL_PE16 +%include "bootsector2-cpu-hidden-regs-1-template.mac" +%define TMPL_PE32 +%include "bootsector2-cpu-hidden-regs-1-template.mac" +;%define TMPL_PP16 +;%include "bootsector2-cpu-hidden-regs-1-template.mac" +%define TMPL_PP32 +%include "bootsector2-cpu-hidden-regs-1-template.mac" +;%define TMPL_PAE16 +;%include "bootsector2-cpu-hidden-regs-1-template.mac" +;%define TMPL_PAE32 +;%include "bootsector2-cpu-hidden-regs-1-template.mac" +;%define TMPL_LM16 +;%include "bootsector2-cpu-hidden-regs-1-template.mac" +;%define TMPL_LM32 +;%include "bootsector2-cpu-hidden-regs-1-template.mac" +%define TMPL_LM64 +%include "bootsector2-cpu-hidden-regs-1-template.mac" + + +; +; End sections and image. +; +%include "bootsector2-common-end.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-instr-1-template.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-instr-1-template.mac new file mode 100644 index 00000000..f7b192a8 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-instr-1-template.mac @@ -0,0 +1,567 @@ +; $Id: bootsector2-cpu-instr-1-template.mac $ +;; @file +; Bootsector test for misc instruction - multi mode template. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "bootsector2-template-header.mac" + + + +;; +; Memory fence instructions (SSE2). +; +; @uses No registers, but BS2_SEL_SPARE0 is trashed. +; +BEGINPROC TMPL_NM(TestMemFences) + push xBP + mov xBP, xSP + push sAX + push xBX + push xCX + push xDX + push xDI + push xSI + sub xSP, 80h ; iret stack frame space. + mov xSI, xSP ; Save the stack register. + + mov xAX, .s_szSubTestName + call TMPL_NM_CMN(TestSub) + + ; + ; SSE2 supported? + ; + mov eax, 1 + xor ecx, ecx + cpuid + test edx, X86_CPUID_FEATURE_EDX_SSE2 + jz .skip + + ; + ; Check that the standard instruction encodings work. + ; + mov xBX, [xSP + 10h] + mov [xSP], xAX + mfence + mov [xSP], xCX + mov xBX, [xSP + 08h] + sfence + mov [xSP], xDX + mov xBX, [xSP] + lfence + mov bx, [xSP + 04h] + + + ; + ; The instruction encodings in the intel manual may open the RM as well + ; as prefixes open to interpretation. AMD sets RM=0 in their docs. + ; + ; lfence = 0f,ea,e8 + ; mfence = 0f,ea,f0 + ; sfence = 0f,ea,f8 + ; (RM is the lower 3 bits of the last byte.) + +%assign MY_RM 0xe8 +%rep 18h + db 0fh, 0aeh, MY_RM + db X86_OP_PRF_CS, 0fh, 0aeh, MY_RM + db X86_OP_PRF_DS, 0fh, 0aeh, MY_RM + db X86_OP_PRF_ES, 0fh, 0aeh, MY_RM + db X86_OP_PRF_FS, 0fh, 0aeh, MY_RM + db X86_OP_PRF_GS, 0fh, 0aeh, MY_RM + db X86_OP_PRF_SS, 0fh, 0aeh, MY_RM + db X86_OP_PRF_SIZE_ADDR, 0fh, 0aeh, MY_RM + BS2_TRAP_INSTR X86_XCPT_UD, 0, db X86_OP_PRF_SIZE_OP, 0fh, 0aeh, MY_RM ; (used in group) + BS2_TRAP_INSTR X86_XCPT_UD, 0, db X86_OP_PRF_LOCK, 0fh, 0aeh, MY_RM ; (used in group) + BS2_TRAP_INSTR X86_XCPT_UD, 0, db X86_OP_PRF_REPNZ, 0fh, 0aeh, MY_RM ; (used in group) + BS2_TRAP_INSTR X86_XCPT_UD, 0, db X86_OP_PRF_REPZ, 0fh, 0aeh, MY_RM ; (used in group) +%ifdef TMPL_64BIT + %assign MY_REX 0x40 + %rep 10h + ; Rex prefixes doesn't change anything. + db MY_REX, 0fh, 0aeh, MY_RM + db X86_OP_PRF_CS, MY_REX, 0fh, 0aeh, MY_RM + db X86_OP_PRF_DS, MY_REX, 0fh, 0aeh, MY_RM + db X86_OP_PRF_ES, MY_REX, 0fh, 0aeh, MY_RM + db X86_OP_PRF_FS, MY_REX, 0fh, 0aeh, MY_RM + db X86_OP_PRF_GS, MY_REX, 0fh, 0aeh, MY_RM + db X86_OP_PRF_SS, MY_REX, 0fh, 0aeh, MY_RM + db X86_OP_PRF_SIZE_ADDR, MY_REX, 0fh, 0aeh, MY_RM + BS2_TRAP_INSTR X86_XCPT_UD, 0, db X86_OP_PRF_SIZE_OP, MY_REX, 0fh, 0aeh, MY_RM ; (used in group) + BS2_TRAP_INSTR X86_XCPT_UD, 0, db X86_OP_PRF_LOCK, MY_REX, 0fh, 0aeh, MY_RM ; (used in group) + BS2_TRAP_INSTR X86_XCPT_UD, 0, db X86_OP_PRF_REPNZ, MY_REX, 0fh, 0aeh, MY_RM ; (used in group) + BS2_TRAP_INSTR X86_XCPT_UD, 0, db X86_OP_PRF_REPZ, MY_REX, 0fh, 0aeh, MY_RM ; (used in group) + %assign MY_REX (MY_REX + 1) + %endrep +%endif + %assign MY_RM (MY_RM + 1) +%endrep + + ; + ; Done. + ; + call TMPL_NM_CMN(TestSubDone) +.done: + mov xSP, xSI + add xSP, 80h + pop xSI + pop xDI + pop xDX + pop xCX + pop xBX + pop sAX + leave + ret + +.skip: + mov xAX, .s_szSse2Missing + call TMPL_NM_CMN(TestSubDone) + jmp .done + +.s_szSubTestName: + db TMPL_MODE_STR, ', mfence et al.', 0 +.s_szSse2Missing: + db 'SSE2 is missing', 0 +ENDPROC TMPL_NM(TestMemFences) + + +;; +; Floating-point to integer conversion (SSE/SSE2). +; Neither Intel nor AMD explicitly document what happens for the 32-bit forms +; of CVTxx2SI in 64-bit mode with regard to the high dword of a 64-bit +; destination register. +; +; @uses XMM0, and BS2_SEL_SPARE0 is trashed. +; +BEGINPROC TMPL_NM(TestCvtSize) + push xBP + mov xBP, xSP + push sAX + push xBX + push xCX + push xDX + push xDI + push xSI + sub xSP, 80h ; iret stack frame space. + mov xSI, xSP ; Save the stack register. + + mov xAX, .s_szSubTestName + call TMPL_NM_CMN(TestSub) + + ; + ; SSE2 supported? + ; + mov eax, 1 + xor ecx, ecx + cpuid + test edx, X86_CPUID_FEATURE_EDX_SSE2 + jz .skip + +%ifdef TMPL_64BIT + + ; + ; Have to enable OSFXSR for SSE instructions to work. + ; + mov rcx,cr4 + mov rsi,rcx + or rcx,200h + mov cr4,rcx + + ; + ; Load 32-bit float -2.75 into XMM0 + ; + mov eax, 0C0300000h + movd xmm0, eax + mov rbx, -1 ; make sure high dword is not zero + cvtss2si ebx, xmm0 ; result is -3 + mov eax, -3 ; high dword of rax zeroed + TEST_ASSERT_SIMPLE rbx, rax, jz, "CVTSS2SI EBX" + + mov eax, 0C0300000h + movd xmm0, eax + mov rbx, -1 + cvttss2si ebx, xmm0 ; result is -2 + mov eax, -2 + TEST_ASSERT_SIMPLE rbx, rax, jz, "CVTTSS2SI EBX" + + ; + ; Load 64-bit double -2.75 into XMM0 + ; + mov rax, 0C006000000000000h + movd xmm0, rax + mov rbx, -1 + cvtsd2si ebx, xmm0 + mov eax, -3 + TEST_ASSERT_SIMPLE rbx, rax, jz, "CVTSD2SI EBX" + + mov rax, 0C006000000000000h + mov rbx, -1 + movd xmm0, rax + cvttsd2si ebx, xmm0 + mov eax,-2 + TEST_ASSERT_SIMPLE rbx, rax, jz, "CVTTSD2SI EBX" + + ; + ; Restore prior CR4 value + ; + mov cr4,rsi +%endif + + ; + ; Done. + ; + call TMPL_NM_CMN(TestSubDone) +.done: + mov xSP, xSI + add xSP, 80h + pop xSI + pop xDI + pop xDX + pop xCX + pop xBX + pop sAX + leave + ret + +.skip: + mov xAX, .s_szSse2Missing + call TMPL_NM_CMN(TestSubDone) + jmp .done + +.s_szSubTestName: + db TMPL_MODE_STR, ', cvtss2si et al.', 0 +.s_szSse2Missing: + db 'SSE2 is missing', 0 +ENDPROC TMPL_NM(TestCvtSize) + + +;; +; Test what CMPXCHG with 32-bit operand size does to 64-bit registers, +; as this is not particularly well documented by either Intel or AMD. +; +; @uses No registers, but BS2_SEL_SPARE0 is trashed. +; +BEGINPROC TMPL_NM(TestCmpxchg32) + push xBP + mov xBP, xSP + push sAX + push xBX + push xCX + push xDX + push xDI + push xSI + sub xSP, 80h ; iret stack frame space. + mov xSI, xSP ; Save the stack register. + + mov xAX, .s_szSubTestName + call TMPL_NM_CMN(TestSub) + +%ifdef TMPL_64BIT + + ; + ; CMPXCHG reg, reg - values not equal, eax written + ; + mov rax, -1 ; Load registers with 64-bit values + mov rbx, -2 + mov rcx, -3 + cmpxchg ebx, ecx ; Not equal, writes ebx to eax + mov edx, -2 ; Clears high dword + TEST_ASSERT_SIMPLE rax, rdx, jz, "CMPXCHG reg, unequal, rax set" + mov rdx, -2 ; All ones still in high dword + TEST_ASSERT_SIMPLE rbx, rdx, jz, "CMPXCHG reg, unequal, rbx not set" + + ; + ; CMPXCHG reg, reg - values equal, first operand written + ; + mov rax, -4 ; Load registers with 64-bit values + mov rbx, -4 + mov rcx, -5 + cmpxchg ebx, ecx ; Equal, writes ecx to ebx + mov edx, -5 ; Clears high dword + TEST_ASSERT_SIMPLE rbx, rdx, jz, "CMPXCHG reg, equal, rbx set" + mov rdx, -4 ; All ones still in high dword + TEST_ASSERT_SIMPLE rax, rdx, jz, "CMPXCHG reg, equal, rax not set" + + ; + ; CMPXCHG mem, reg - values not equal, eax written + ; + mov rax, -1 ; Load registers with 64-bit values + mov rbx, -2 + push rbx + mov rcx, -3 + cmpxchg [rsp], ecx ; Not equal, writes eax + mov edx, -2 ; Clears high dword + TEST_ASSERT_SIMPLE rax, rdx, jz, "CMPXCHG mem, unequal, rax set" + pop rbx + + ; + ; CMPXCHG mem, reg - values equal, first operand written + ; + mov rax, -4 ; Load registers with 64-bit values + mov rbx, -4 + push rbx + mov rcx, -5 + cmpxchg [rsp], ecx ; Equal, writes ecx to memory + mov rdx, -4 ; All ones in high dword + TEST_ASSERT_SIMPLE rax, rdx, jz, "CMPXCHG mem, equal, rax not set" + pop rbx + + ; + ; CMPXCHG8B mem, reg - values equal, memory written + ; compares edx:eax with mem64 + ; + mov rdx, -1 ; Load registers with 64-bit values + mov rax, -4 + mov rcx, -1 + mov rbx, -5 + mov rsi, -4 + push rsi + cmpxchg8b [rsp] ; Equal, writes ecx:ebx to memory + mov rsi, -4 ; All ones in high dword + TEST_ASSERT_SIMPLE rax, rsi, jz, "CMPXCHG8B mem, equal, rax not set" + mov rsi, -1 ; All ones in high dword + TEST_ASSERT_SIMPLE rdx, rsi, jz, "CMPXCHG8B mem, equal, rdx not set" + pop rsi + + ; + ; CMPXCHG8B mem, reg - values unequal, edx:eax written + ; compares edx:eax with mem64 + ; + mov rdx, -1 ; Load registers with 64-bit values + mov rax, -2 + mov rcx, -1 + mov rbx, -4 + mov rsi, -3 + push rsi + cmpxchg8b [rsp] ; Not equal, writes memory to edx:eax + mov esi, -3 ; Clears high dword + TEST_ASSERT_SIMPLE rax, rsi, jz, "CMPXCHG8B mem, unequal, rax set" + mov esi, -1 ; Clears high dword + TEST_ASSERT_SIMPLE rdx, rsi, jz, "CMPXCHG8B mem, unequal, rdx set" + pop rsi + +%endif + + ; + ; Done. + ; + call TMPL_NM_CMN(TestSubDone) +.done: + mov xSP, xSI + add xSP, 80h + pop xSI + pop xDI + pop xDX + pop xCX + pop xBX + pop sAX + leave + ret + +.s_szSubTestName: + db TMPL_MODE_STR, ', 32-bit CMPXCHG in 64-bit mode', 0 +ENDPROC TMPL_NM(TestCmpxchg32) + + +;; +; Proving intel manual wrong about using REX.X for BSWAP R8-R15 on 64-bit. +; Checking the 'undefined' 16-bit bswap behavior. +; +; @uses No registers, but BS2_SEL_SPARE0 is trashed. +; +BEGINPROC TMPL_NM(TestBSwap) + push xBP + mov xBP, xSP + push sAX + push xBX + push xCX + push xDX + push xDI + push xSI + sub xSP, 80h ; iret stack frame space. + mov xSI, xSP ; Save the stack register. + + mov xAX, .s_szSubTestName + call TMPL_NM_CMN(TestSub) + + ; + ; Assert sanity. + ; + mov eax, 11223344h + bswap eax + TEST_ASSERT_SIMPLE eax, 44332211h, jz, "32-bit BSWAP EAX" + + ; + ; Buggy manual (325383-041US, December 2011). + ; +%ifdef TMPL_64BIT + push r8 + + mov r8d, 55667788h + mov eax, 55667788h + db X86_OP_REX_X + bswap eax ; does it access r8 or eax? + TEST_ASSERT_SIMPLE eax, 88776655h, jz, "REX.X BSWAP EAX - Wrong EAX." + TEST_ASSERT_SIMPLE r8, 55667788h, jz, "REX.X BSWAP EAX - Wrong R8." + + mov r8d, 55667788h + mov eax, 55667788h + db X86_OP_REX_R + bswap eax ; does it access r8 or eax? + TEST_ASSERT_SIMPLE eax, 88776655h, jz, "REX.R BSWAP EAX - Wrong EAX." + TEST_ASSERT_SIMPLE r8, 55667788h, jz, "REX.R BSWAP EAX - Wrong R8." + + mov r8d, 55667788h + mov eax, 55667788h + db X86_OP_REX_B + bswap eax ; does it access r8 or eax? + TEST_ASSERT_SIMPLE rax, 55667788h, jz, "REX.B BSWAP R8D - Wrong RAX." + TEST_ASSERT_SIMPLE r8d, 88776655h, jz, "REX.B BSWAP R8D - Wrong R8D." + + pop r8 +%endif + + ; + ; 'Undefined' 16-bit behavior. + ; + ; Zeroing of the lower 16-bits has been observed on: + ; - Intel(R) Core(TM) i7-3960X CPU @ 3.30GHz + ; +%ifndef TestBSwap16_defined + %define TestBSwap16_defined + %macro TestBSwap16 3, + mov %3, %2 ; save the primary register. + %ifdef TMPL_64BIT + mov %2, 0ffffffff98765432h ; Set the upper bit as well. + %else + mov %2, 98765432h + %endif + %ifndef TMPL_16BIT + db X86_OP_PRF_SIZE_OP + %endif + bswap %1 + xchg %2, %3 ; Restore and save the result (xSP). + TEST_ASSERT_SIMPLE %3, 98760000h, jz, "Unexpected 16-bit BSWAP error." + %endmacro +%endif + + TestBSwap16 eax, sAX, sSI + TestBSwap16 ebx, sBX, sSI + TestBSwap16 ecx, sCX, sSI + TestBSwap16 edx, sDX, sSI + TestBSwap16 esp, sSP, sSI + TestBSwap16 ebp, sBP, sSI + TestBSwap16 edi, sDI, sSI + TestBSwap16 esi, sSI, sDI +%ifdef TMPL_64BIT + TestBSwap16 r8d, r8, rax + TestBSwap16 r9d, r9, rax + TestBSwap16 r10d, r10, rax + TestBSwap16 r11d, r11, rax + TestBSwap16 r12d, r12, rax + TestBSwap16 r13d, r13, rax + TestBSwap16 r14d, r14, rax + TestBSwap16 r15d, r15, rax +%endif + + ; + ; Done. + ; + call TMPL_NM_CMN(TestSubDone) +.done: + mov xSP, xSI + add xSP, 80h + pop xSI + pop xDI + pop xDX + pop xCX + pop xBX + pop sAX + leave + ret + +.s_szSubTestName: + db TMPL_MODE_STR, ', bswap', 0 +ENDPROC TMPL_NM(TestBSwap) + + +;; +; Do the tests for this mode. +; +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC TMPL_NM(DoTestsForMode_rm) + push bp + mov bp, sp + push ax + + ; + ; Check if the mode and NX is supported, do the switch. + ; + call TMPL_NM(Bs2IsModeSupported_rm) + jz .done + call TMPL_NM(Bs2EnterMode_rm) +BITS TMPL_BITS + + ; + ; Test exception handler basics using INT3 and #BP. + ; + + call TMPL_NM(TestMemFences) + call TMPL_NM(TestBSwap) +%ifdef TMPL_64BIT + ; Specifically tests 64-bit behavior. + call TMPL_NM(TestCvtSize) + call TMPL_NM(TestCmpxchg32) +%endif + + ; + ; Back to real mode. + ; + call TMPL_NM(Bs2ExitMode) +BITS 16 + call Bs2DisableNX_r86 + +.done: + pop ax + leave + ret +ENDPROC TMPL_NM(DoTestsForMode_rm) +TMPL_BEGINCODE +BITS TMPL_BITS + +%include "bootsector2-template-footer.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-instr-1.asm b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-instr-1.asm new file mode 100644 index 00000000..0411070a --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-instr-1.asm @@ -0,0 +1,120 @@ +; $Id: bootsector2-cpu-instr-1.asm $ +;; @file +; Bootsector test for misc instructions. +; +; Recommended (but not necessary): +; VBoxManage setextradata bs-cpu-instr-1 VBoxInternal/Devices/VMMDev/0/Config/TestingEnabled 1 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" +%include "VBox/VMMDevTesting.mac" + +; Include and execute the init code. +%define BS2_INIT_RM +%define BS2_WITH_TRAPS +%define BS2_INC_RM +%define BS2_INC_PE32 +%define BS2_INC_PP32 +%define BS2_INC_PAE32 +%define BS2_INC_LM32 +%define BS2_INC_LM64 +%define BS2_WITH_TRAPRECS +%include "bootsector2-common-init-code.mac" + + +; +; The main() function. +; +BEGINPROC main + BITS 16 + ; + ; Test prologue. + ; + mov ax, .s_szTstName + call TestInit_r86 + call Bs2EnableA20_r86 + + + ; + ; Execute the tests + ; +%if 1 + call NAME(DoTestsForMode_rm_pe32) +%endif +%if 1 + call NAME(DoTestsForMode_rm_pp32) +%endif +%if 1 + call NAME(DoTestsForMode_rm_pae32) +%endif +%if 1 + call NAME(DoTestsForMode_rm_lm64) +%endif + + ; + ; We're done. + ; + call TestTerm_r86 + ret + +.s_szTstName: + db 'tstCpuInstr1', 0 +ENDPROC main + + +; +; Instantiate the template code. +; +%include "bootsector2-template-footer.mac" ; reset the initial environemnt. + +%define TMPL_PE32 +%include "bootsector2-cpu-instr-1-template.mac" +%define TMPL_PP32 +%include "bootsector2-cpu-instr-1-template.mac" +%define TMPL_PAE32 +%include "bootsector2-cpu-instr-1-template.mac" +%define TMPL_LM64 +%include "bootsector2-cpu-instr-1-template.mac" + + +; +; End sections and image. +; +%include "bootsector2-common-end.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-pf-1-template.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-pf-1-template.mac new file mode 100644 index 00000000..8220af63 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-pf-1-template.mac @@ -0,0 +1,1061 @@ +; $Id: bootsector2-cpu-pf-1-template.mac $ +;; @file +; Bootsector test for various types of #PFs - multi mode template. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "bootsector2-template-header.mac" + + +;******************************************************************************* +;* Defined Constants And Macros * +;******************************************************************************* +%undef BIG_PAGE_SIZE +%undef PXE_SIZE +%ifdef TMPL_CMN_PP + %define BIG_PAGE_SIZE _4M + %define PXE_SIZE 4 +%else + %define BIG_PAGE_SIZE _2M + %define PXE_SIZE 8 +%endif + + +;; +; Do the tests for this mode. +; +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC TMPL_NM(DoTestsForMode_rm) + push bp + mov bp, sp + push ax + + ; + ; Check if the mode and NX is supported, do the switch. + ; + call TMPL_NM(Bs2IsModeSupported_rm) + jz .done + mov ax, [bp - 2] + test al, al + jz .nx_disabled + call Bs2IsNXSupported_r86 + jz .done + call Bs2EnableNX_r86 +.nx_disabled: + call TMPL_NM(Bs2EnterMode_rm) +BITS TMPL_BITS + + ; + ; Do the tests. + ; + call TMPL_NM(TestNotPresent) + ;; @todo call TMPL_NM(TestReadOnly) + ;; @todo call TMPL_NM(TestSupervisor) + ;; @todo call TMPL_NM(TestReservedBits) + + ; + ; Back to real mode. + ; + call TMPL_NM(Bs2ExitMode) +BITS 16 + call Bs2DisableNX_r86 + +.done: + pop ax + leave + ret +ENDPROC TMPL_NM(DoTestsForMode_rm) +TMPL_BEGINCODE +BITS TMPL_BITS + + +;; +; Do the tests for this mode. +; +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC TMPL_NM(DoBenchmarksForMode_rm) + push bp + mov bp, sp + push ax + + ; + ; Check if the mode and NX is supported, do the switch. + ; + call TMPL_NM(Bs2IsModeSupported_rm) + jz .done + call TMPL_NM(Bs2EnterMode_rm) +BITS TMPL_BITS + + ; + ; Do the tests. + ; + call TMPL_NM(BenchmarkNotPresent) + + ; + ; Back to real mode. + ; + call TMPL_NM(Bs2ExitMode) +BITS 16 +.done: + pop ax + leave + ret +ENDPROC TMPL_NM(DoBenchmarksForMode_rm) +TMPL_BEGINCODE +BITS TMPL_BITS + + +;; +; Does the page-not-present tests. +; +; @param al Set if NXE=1, clear if NXE=0. +; +; @uses nothing +; +BEGINPROC TMPL_NM(TestNotPresent) + push xBP + mov xBP, xSP + push sAX + push xBX + push xCX + push xDX + push xDI + push xSI + + ; + ; Setup sCX for all the following tests. + ; + xor sCX, sCX + test al, al + jz .no_nxe + mov sCX, X86_TRAP_PF_ID +.no_nxe: + + ; + ; First test, big page not present. + ; + mov xAX, .s_szBigPageNotPresent + test sCX, sCX + jz .test1_nxe + mov xAX, .s_szBigPageNotPresentNX +.test1_nxe: + call TMPL_NM_CMN(TestSub) + call TMPL_NM(TestFillTestAreaWithRet) + + mov sAX, TST_SCRATCH_PD_BASE + call TMPL_NM(TestGetPdeAddr) + and byte [sAX], ~X86_PTE_P + mov sAX, cr3 + mov cr3, sAX + + mov sAX, TST_SCRATCH_PD_BASE + mov sDX, 0 ; err code + call TMPL_NM(TestHammerPage) + jz .test1_cleanup + + mov sAX, TST_SCRATCH_PD_BASE + (BIG_PAGE_SIZE / 2 - _4K) + call TMPL_NM(TestHammerPage) + jz .test1_cleanup + + mov sAX, TST_SCRATCH_PD_BASE + (BIG_PAGE_SIZE - _4K) + call TMPL_NM(TestHammerPage) + jz .test1_cleanup + +.test1_cleanup: + mov sAX, TST_SCRATCH_PD_BASE + call TMPL_NM(TestGetPdeAddr) + or byte [sAX], X86_PTE_P + mov sAX, cr3 + mov cr3, sAX + + ; + ; The second test, normal page not present. + ; + mov xAX, .s_szPageNotPresent + test sCX, sCX + jz .test2_nxe + mov xAX, .s_szPageNotPresentNX +.test2_nxe: + call TMPL_NM_CMN(TestSub) + + mov sAX, TST_SCRATCH_PD_BASE + call TMPL_NM(TstPutPageTableAt) + + ; Make the first and last page not-present. + and byte [BS2_USER_PX_0_ADDR], ~X86_PTE_P + and byte [BS2_USER_PX_0_ADDR + 01000h - PXE_SIZE], ~X86_PTE_P + mov sAX, cr3 + mov cr3, sAX + + ; Do the tests. + mov sAX, TST_SCRATCH_PD_BASE + mov sDX, 0 ; err code + call TMPL_NM(TestHammerPage) + jz .test2_cleanup + + mov sAX, TST_SCRATCH_PD_BASE + (BIG_PAGE_SIZE - _4K) + call TMPL_NM(TestHammerPage) + jz .test2_cleanup + +.test2_cleanup: + mov sAX, TST_SCRATCH_PD_BASE + call TMPL_NM(TstRestoreBigPageAt) + + +%if PXE_SIZE == 8 ; PAE or LM + ; + ; The third test, mark a page directory pointer entry not present. + ; + mov xAX, .s_szPdpeNotPresent + test sCX, sCX + jz .test3_nxe + mov xAX, .s_szPdpeNotPresentNX +.test3_nxe: + call TMPL_NM_CMN(TestSub) + call TMPL_NM(TestFillTestAreaWithRet) + + mov sAX, TST_SCRATCH_PDPT_BASE + call TMPL_NM(TestGetPdpeAddr) + and byte [sAX], ~X86_PTE_P + mov sAX, cr3 + mov cr3, sAX + + mov sAX, TST_SCRATCH_PDPT_BASE + mov sDX, 0 ; err code + call TMPL_NM(TestHammerPage) + jz .test3_cleanup + + mov sAX, TST_SCRATCH_PDPT_BASE + (BIG_PAGE_SIZE / 2 - _4K) + call TMPL_NM(TestHammerPage) + jz .test3_cleanup + + mov sAX, TST_SCRATCH_PDPT_BASE + (BIG_PAGE_SIZE - _4K) + call TMPL_NM(TestHammerPage) + jz .test3_cleanup + +.test3_cleanup: + mov sAX, TST_SCRATCH_PDPT_BASE + call TMPL_NM(TestGetPdpeAddr) + or byte [sAX], X86_PTE_P + mov sAX, cr3 + mov cr3, sAX +%endif ; PAE || LM + + +%ifdef TMPL_LM64 + ; + ; The fourth test, mark a page map level 4 entry not present. + ; + mov xAX, .s_szPml4eNotPresent + test sCX, sCX + jz .test4_nxe + mov xAX, .s_szPml4eNotPresentNX +.test4_nxe: + call TMPL_NM_CMN(TestSub) + call TMPL_NM(TestFillTestAreaWithRet) + + mov sAX, TST_SCRATCH_PML4_BASE + call TMPL_NM(TestGetPml4eAddr) + and byte [sAX], ~X86_PTE_P + mov sAX, cr3 + mov cr3, sAX + + mov sAX, TST_SCRATCH_PML4_BASE + mov sDX, 0 ; err code + call TMPL_NM(TestHammerPage) + jz .test4_cleanup + + mov sAX, TST_SCRATCH_PML4_BASE + (BIG_PAGE_SIZE / 2 - _4K) + call TMPL_NM(TestHammerPage) + jz .test4_cleanup + + mov sAX, TST_SCRATCH_PML4_BASE + (BIG_PAGE_SIZE - _4K) + call TMPL_NM(TestHammerPage) + jz .test4_cleanup + +.test4_cleanup: + mov sAX, TST_SCRATCH_PML4_BASE + call TMPL_NM(TestGetPml4eAddr) + or byte [sAX], X86_PTE_P + mov sAX, cr3 + mov cr3, sAX +%endif + + ; + ; Done. + ; + call TMPL_NM_CMN(TestSubDone) + + pop xSI + pop xDI + pop xDX + pop xCX + pop xBX + pop sAX + leave + ret + +.s_szBigPageNotPresent: + db TMPL_MODE_STR, ', !NX, big page NP', 0 +.s_szBigPageNotPresentNX: + db TMPL_MODE_STR, ', NX, big page NP', 0 +.s_szPageNotPresent: + db TMPL_MODE_STR, ', !NX, page NP', 0 +.s_szPageNotPresentNX: + db TMPL_MODE_STR, ', NX, page NP', 0 +%if PXE_SIZE == 8 ; PAE or LM +.s_szPdpeNotPresent: + db TMPL_MODE_STR, ', !NX, PDPE NP', 0 +.s_szPdpeNotPresentNX: + db TMPL_MODE_STR, ', NX, PDPE NP', 0 +%endif +%ifdef TMPL_LM64 +.s_szPml4eNotPresent: + db TMPL_MODE_STR, ', !NX, PML4E NP', 0 +.s_szPml4eNotPresentNX: + db TMPL_MODE_STR, ', NX, PML4E NP', 0 +%endif +ENDPROC TMPL_NM(TestNotPresent) + + + +;; +; Does the page-not-present benchmark. +; +; @uses nothing +; +BEGINPROC TMPL_NM(BenchmarkNotPresent) + push xBP + mov xBP, xSP + push sAX + push xBX + push sCX + push sDX + push xDI + push xSI + sub xSP, 20h + + call TMPL_NM(TestFillTestAreaWithRet) + + ; + ; The First benchmark: Big page not present. + ; + + ; Mark the big test page not present. + mov sAX, TST_SCRATCH_PD_BASE + call TMPL_NM(TestGetPdeAddr) + and byte [sAX], ~X86_PTE_P + mov sAX, cr3 + mov cr3, sAX + + ; Benchmark. + mov sAX, TST_SCRATCH_PD_BASE + mov xDX, .s_szBigPageNotPresent + mov xCX, .s_szBigPageNotPresentFailed + call TMPL_NM(TstBenchmark32BitReads) + + ; Cleanup. + mov sAX, TST_SCRATCH_PD_BASE + call TMPL_NM(TestGetPdeAddr) + or byte [sAX], X86_PTE_P + mov sAX, cr3 + mov cr3, sAX + + ; + ; The second benchmark: Normal page not present. + ; + + ; Replace the big page with a page table and make the first and last + ; pages not-present. + mov sAX, TST_SCRATCH_PD_BASE + call TMPL_NM(TstPutPageTableAt) + + and byte [BS2_USER_PX_0_ADDR], ~X86_PTE_P + and byte [BS2_USER_PX_0_ADDR + 01000h - PXE_SIZE], ~X86_PTE_P + mov sAX, cr3 + mov cr3, sAX + + ; Benchmark. + mov sAX, TST_SCRATCH_PD_BASE + mov xDX, .s_szPageNotPresent + mov xCX, .s_szPageNotPresentFailed + call TMPL_NM(TstBenchmark32BitReads) + + ; Cleanup + mov sAX, TST_SCRATCH_PD_BASE + call TMPL_NM(TstRestoreBigPageAt) + + + ; + ; Done. + ; + add xSP, 20h + pop xSI + pop xDI + pop sDX + pop sCX + pop xBX + pop sAX + leave + ret + +.s_szBigPageNotPresent: + db TMPL_MODE_STR, ', read NP big page', 0 +.s_szBigPageNotPresentFailed: + db TMPL_MODE_STR, ', reading NP big page failed', 13, 10, 0 +.s_szPageNotPresent: + db TMPL_MODE_STR, ', read NP page', 0 +.s_szPageNotPresentFailed: + db TMPL_MODE_STR, ', reading NP page failed', 13, 10, 0 +ENDPROC TMPL_NM(BenchmarkNotPresent) + + +;; +; Benchmark 32-bit reads at a give location. +; +; Will report the result under the name given via xDX. Will report any test +; failure giving the string pointed to by xCX as explanation. +; +; @param sAX The location to do the reads. +; @param xDX The test value name. +; @param xCX The failure string +; @uses nothing +; +BEGINPROC TMPL_NM(TstBenchmark32BitReads) + push xBP + mov xBP, xSP +%define a_pu32Test [xBP - sCB] + push sAX +%define a_pszValue [xBP - sCB*2] + push sDX +%define a_pszFailure [xBP - sCB*3] + push sCX + push sSI + push sDI +%define u64NanoTS [xBP - sCB*5 - 8h] + sub xSP, 8h + + ; + ; Calibrate the test so it doesn't take forever. + ; + mov xAX, .calibrate_resume + mov dl, 0eh + call TMPL_NM_CMN(Bs2TrapPrepare) + mov ecx, TST_CALIBRATE_LOOP_COUNT + + lea xAX, u64NanoTS + call TMPL_NM_CMN(GetNanoTS) + +.calibrate_loop: + mov sAX, a_pu32Test + mov esi, [sAX] +.calibrate_resume: + test sAX, sAX + jnz .failure + dec ecx + jnz .calibrate_loop + + lea xAX, u64NanoTS + call TMPL_NM_CMN(GetElapsedNanoTS) + call TMPL_NM_CMN(Bs2TrapReset) + + ; Figure out how many iterations is required for the full benchmark. + mov ecx, TST_BENCHMARK_PERIOD_IN_SECS + mov edx, TST_CALIBRATE_LOOP_COUNT + mov xAX, xSP + call TMPL_NM_CMN(CalcBenchmarkIterations) + mov ecx, eax ; iteration count. + + ; + ; Do the full benchmark run. + ; + mov xAX, .bench_resume + mov dl, 0eh + call TMPL_NM_CMN(Bs2TrapPrepare) + mov edx, ecx ; save test count for ReportResult. + + lea xAX, u64NanoTS + call TMPL_NM_CMN(GetNanoTS) +.bench_loop: + mov xAX, a_pu32Test + mov esi, [eax] +.bench_resume: + test eax, eax + jnz .failure + dec ecx + jnz .bench_loop + + lea xAX, u64NanoTS + call TMPL_NM_CMN(GetElapsedNanoTS) + call TMPL_NM_CMN(Bs2TrapReset) + + mov xCX, a_pszValue + lea xAX, u64NanoTS + call TMPL_NM_CMN(ReportResult) + +.return: + pop sDI + pop sSI + pop sCX + pop sDX + pop sAX + leave + ret + +.failure: + call TMPL_NM_CMN(Bs2TrapReset) + mov xAX, a_pszFailure + call TMPL_NM_CMN(TestFailed) + jmp .return + +%undef a_pszFailure +%undef a_pu32Test +%undef a_pszValue +%undef a_pszFailure +%undef u64NanoTS +ENDPROC TMPL_NM(TstBenchmark32BitReads) + + +;; +; Fills the test area with return instructions. +; +; @uses nothing. +; +BEGINPROC TMPL_NM(TestFillTestAreaWithRet) + push xBP + mov xBP, xSP + push xDI + push xCX + push xAX + + mov xDI, TST_SCRATCH_PD_BASE + mov xCX, (_4M + _4M) / 4 + mov eax, 0c3c3c3c3h + rep stosd + + mov xDI, TST_SCRATCH_PDPT_BASE + mov xCX, (_4M + _4M) / 4 + mov eax, 0c3c3c3c3h + rep stosd + +%ifdef TMPL_LM64 + mov xDI, TST_SCRATCH_PML4_BASE + mov xCX, (_4M + _4M) / 8 + mov rax, 0c3c3c3c3c3c3c3c3h + rep stosq +%endif + + pop xAX + pop xCX + pop xDI + leave + ret +ENDPROC TMPL_NM(TestFillTestAreaWithRet) + + +;; +; Gets the page directory address. +; +; ASSUMES identity mapped page translation tables. +; +; @returns ds:xAX The page directory address. +; @param sAX The virtual address in question. +; @uses nothing +; +BEGINPROC TMPL_NM(TestGetPdeAddr) + push xBP + mov xBP, xSP + push sBX + push sCX + +%ifdef TMPL_CMN_PP + ; PDPE + shr sAX, X86_PD_SHIFT + and sAX, X86_PD_MASK + shl sAX, 2 + mov sBX, cr3 + and sBX, X86_CR3_PAGE_MASK + add sAX, sBX + +%else + %ifdef TMPL_CMN_LM + ; PML4E + mov sCX, sAX + shr sCX, X86_PML4_SHIFT + and sCX, X86_PML4_MASK + shl sCX, 3 + mov sBX, cr3 + and sBX, X86_CR3_AMD64_PAGE_MASK & 0ffffffffh + add sBX, sCX + mov sBX, [sBX] + and sBX, X86_PDPE_PG_MASK & 0ffffffffh + %else + mov sBX, cr3 + and sBX, X86_CR3_PAE_PAGE_MASK + %endif + + ; PDPE + mov sCX, sAX + shr sCX, X86_PDPT_SHIFT + %ifdef TMPL_CMN_LM + and sCX, X86_PDPT_MASK_AMD64 + %else + and sCX, X86_PDPT_MASK_PAE + %endif + shl sCX, 3 + add sBX, xCX + mov sBX, [sBX] + and sBX, X86_PDPE_PG_MASK & 0ffffffffh + + ; PDE + shr sAX, X86_PD_PAE_SHIFT + and sAX, X86_PD_PAE_MASK + shl sAX, 3 + add sAX, sBX +%endif + + pop sCX + pop sBX + leave + ret +ENDPROC TMPL_NM(TestGetPdeAddr) + + +%if PXE_SIZE == 8 ; PAE or LM +;; +; Gets the page directory pointer entry for an address. +; +; ASSUMES identity mapped page translation tables. +; +; @returns ds:xAX The pointer to the PDPE. +; @param sAX The virtual address in question. +; @uses nothing +; +BEGINPROC TMPL_NM(TestGetPdpeAddr) + push xBP + mov xBP, xSP + push sBX + push sCX + +%ifdef TMPL_CMN_PP + %error "misconfig" +%endif + +%ifdef TMPL_CMN_LM + ; PML4E + mov sCX, sAX + shr sCX, X86_PML4_SHIFT + and sCX, X86_PML4_MASK + shl sCX, 3 + mov sBX, cr3 + and sBX, X86_CR3_AMD64_PAGE_MASK & 0ffffffffh + add sBX, sCX + mov sBX, [sBX] + and sBX, X86_PDPE_PG_MASK & 0ffffffffh +%else + mov sBX, cr3 + and sBX, X86_CR3_PAE_PAGE_MASK +%endif + + ; PDPE + shr sAX, X86_PDPT_SHIFT +%ifdef TMPL_CMN_LM + and sAX, X86_PDPT_MASK_AMD64 +%else + and sAX, X86_PDPT_MASK_PAE +%endif + shl sAX, 3 + add sAX, sBX + + pop sCX + pop sBX + leave + ret +ENDPROC TMPL_NM(TestGetPdpeAddr) +%endif ; PAE or LM + + +%ifdef TMPL_CMN_LM +;; +; Gets the page map level 4 entry for an address. +; +; ASSUMES identity mapped page translation tables. +; +; @returns rax The pointer to the PML4E. +; @param rax The virtual address in question. +; @uses nothing +; +BEGINPROC TMPL_NM(TestGetPml4eAddr) + push xBP + mov xBP, xSP + push rbx + + ; PML4E + shr rax, X86_PML4_SHIFT + and rax, X86_PML4_MASK + shl rax, 3 + mov rbx, cr3 + and rbx, X86_CR3_AMD64_PAGE_MASK & 0ffffffffh + add rax, rbx + + pop rbx + leave + ret +ENDPROC TMPL_NM(TestGetPml4eAddr) +%endif ; TMPL_CMN_LM + + +;; +; Initialize page table #0 and hooks it up at the specified address. +; +; The page table will have identity mapped pages. The TLBs are flushed +; wholesale. The caller will have to reconstruct the PDE when finished. +; +; @param sAX The virtual address (big page -> page table). +; @uses nothing +; +BEGINPROC TMPL_NM(TstPutPageTableAt) + push xBP + mov xBP, xSP + push sAX + push sCX + push sDI + push sSI + + ; initialize a page table. + mov sDI, BS2_USER_PX_0_ADDR + mov sSI, sAX +.init_loop: +%if PXE_SIZE == 8 + mov [sDI + 4], dword 0 + mov [sDI], sSI +%else + mov [sDI], esi +%endif + or byte [sDI], X86_PTE_P | X86_PTE_RW + add sSI, _4K + add sDI, PXE_SIZE + test sDI, 0fffh + jnz .init_loop + + ; hook it up instead of the big page. + and sAX, ~(BIG_PAGE_SIZE - 1) + mov sDI, sAX + call TMPL_NM(TestGetPdeAddr) + mov dword [sAX], BS2_USER_PX_0_ADDR | X86_PDE_P | X86_PDE_RW | X86_PDE_RW +%if PXE_SIZE == 8 + mov dword [sAX + 4], 0 +%endif + mov sAX, cr3 + mov cr3, sAX + + ; Make sure it works. + mov eax, 0c3c3c3c3h + mov ecx, BIG_PAGE_SIZE / 4 + rep stosd + + pop sSI + pop sDI + pop sCX + pop sAX + leave + ret +ENDPROC TMPL_NM(TstPutPageTableAt) + + +;; +; Restores the big page for a virtual address, undoing harm done by a +; previous TstPutPageTableAt call. +; +; @param sAX The virtual address to restore to a big page. +; @uses nothing +; +BEGINPROC TMPL_NM(TstRestoreBigPageAt) + push xBP + mov xBP, xSP + push sAX + push sCX + push sDI + + ; Set it up, inheriting bits from the previous PDE. + and sAX, ~(BIG_PAGE_SIZE - 1) + mov sDI, sAX ; save it for later. + call TMPL_NM(TestGetPdeAddr) + mov sCX, [sAX - PXE_SIZE] + and sCX, X86_PDE4M_US | X86_PDE4M_RW | X86_PDE4M_G | X86_PDE4M_PAT | X86_PDE4M_AVL | X86_PDE4M_PCD | X86_PDE4M_PWT + or sCX, X86_PDE4M_P | X86_PDE4M_PS + or sCX, sDI +%if PXE_SIZE == 8 + mov dword [sAX + 4], 0 + mov [sAX], sCX +%else + mov [sAX], ecx +%endif + mov sAX, cr3 + mov cr3, sAX + + ; Make sure it works. + mov eax, 0c3c3c3c3h + mov ecx, BIG_PAGE_SIZE / 4 + rep stosd + + pop sDI + pop sCX + pop sAX + leave + ret +ENDPROC TMPL_NM(TstRestoreBigPageAt) + + + +;; +; Hammers a page. +; +; Accesses a page in a few different ways, expecting all of the accesses to +; cause some kind of page fault. The caller just makes sure the page causes +; a fault and points us to it. +; +; @returns al=1, ZF=0 on success. +; @returns al=0, ZF=1 on failure. +; @param sAX The page. +; @param sDX The base error code to expect. +; @param xCX X86_TRAP_PF_ID if NXE, otherwise 0. +; @uses al +; +BEGINPROC TMPL_NM(TestHammerPage) + push xBP + mov xBP, xSP + push sBX +%define a_uErrorExec sPRE [xBP - sCB*2] + push sCX +%define a_uErrorFixed sPRE [xBP - sCB*3] + push sDX + push sDI + push sSI +%define a_pPage sPRE [xBP - sCB*6] + push sAX + + ; + ; First reads of different sizes. + ; + mov sDI, a_pPage +.read_byte_loop: + mov dl, 0ffh + mov xAX, .read_byte_resume + call TMPL_NM_CMN(Bs2TrapPrepare) +.read_byte: + mov cl, byte [sDI] +.read_byte_resume: + mov eax, 0eh ; trap # + mov sDX, a_uErrorFixed ; err + mov sCX, .read_byte ; fault eip + mov sBX, sDI ; fault address. + call TMPL_NM_CMN(TestCheckTrap) + jz .failed + inc sDI + test sDI, 0fffh + jnz .read_byte_loop + + mov sDI, a_pPage +.read_word_loop: + mov dl, 0ffh + mov xAX, .read_word_resume + call TMPL_NM_CMN(Bs2TrapPrepare) +.read_word: + mov cx, word [sDI] +.read_word_resume: + mov eax, 0eh ; trap # + mov sDX, a_uErrorFixed ; err + mov sCX, .read_word ; fault eip + mov sBX, sDI ; fault address. + call TMPL_NM_CMN(TestCheckTrap) + jz .failed + inc sDI + test sDI, 0fffh + jnz .read_word_loop + + mov sDI, a_pPage +.read_dword_loop: + mov dl, 0ffh + mov xAX, .read_dword_resume + call TMPL_NM_CMN(Bs2TrapPrepare) +.read_dword: + mov ecx, dword [sDI] +.read_dword_resume: + mov eax, 0eh ; trap # + mov sDX, a_uErrorFixed ; err + mov sCX, .read_dword ; fault eip + mov sBX, sDI ; fault address. + call TMPL_NM_CMN(TestCheckTrap) + jz .failed + inc sDI + test sDI, 0fffh + jnz .read_dword_loop + + ; + ; Then writes of different sizes. + ; + mov sDI, a_pPage +.write_byte_loop: + mov dl, 0ffh + mov xAX, .write_byte_resume + call TMPL_NM_CMN(Bs2TrapPrepare) +.write_byte: + mov byte [sDI], 0c3h ; (ret instruction) +.write_byte_resume: + mov eax, 0eh ; trap # + mov sDX, a_uErrorFixed ; err + or sDX, X86_TRAP_PF_RW + mov sCX, .write_byte ; fault eip + mov sBX, sDI ; fault address. + call TMPL_NM_CMN(TestCheckTrap) + jz .failed + inc sDI + test sDI, 0fffh + jnz .write_byte_loop + + mov sDI, a_pPage +.write_word_loop: + mov dl, 0ffh + mov xAX, .write_word_resume + call TMPL_NM_CMN(Bs2TrapPrepare) +.write_word: + mov word [sDI], 0c3c3h ; (2 ret instructions) +.write_word_resume: + mov eax, 0eh ; trap # + mov sDX, a_uErrorFixed ; err + or sDX, X86_TRAP_PF_RW + mov sCX, .write_word ; fault eip + mov sBX, sDI ; fault address. + call TMPL_NM_CMN(TestCheckTrap) + jz .failed + inc sDI + test sDI, 0fffh + jnz .write_word_loop + + mov sDI, a_pPage +.write_dword_loop: + mov dl, 0ffh + mov xAX, .write_dword_resume + call TMPL_NM_CMN(Bs2TrapPrepare) +.write_dword: + mov dword [sDI], 0c3c3c3c3h ; (4 ret instructions) +.write_dword_resume: + mov eax, 0eh ; trap # + mov sDX, a_uErrorFixed ; err + or sDX, X86_TRAP_PF_RW + mov sCX, .write_dword ; fault eip + mov sBX, sDI ; fault address. + call TMPL_NM_CMN(TestCheckTrap) + jz .failed + inc sDI + test sDI, 0fffh + jnz .write_dword_loop + + ; + ; Execute access. + ; + mov sDI, a_pPage + mov xSI, xSP +.call_loop: + mov dl, 0ffh + mov xAX, .call_resume + call TMPL_NM_CMN(Bs2TrapPrepare) + call sDI +.call_resume: + mov xSP, xSI ; restore xSP since the call will change it before #PF'ing. + mov eax, 0eh ; trap # + mov sDX, a_uErrorFixed ; err + or sDX, a_uErrorExec + mov sCX, sDI ; fault eip + mov sBX, sDI ; fault address. + call TMPL_NM_CMN(TestCheckTrap) + jz .failed + inc sDI + test sDI, 0fffh + jnz .call_loop + + + mov sDI, a_pPage + mov xSI, xSP +.jmp_loop: + mov dl, 0ffh + mov xAX, .jmp_resume + call TMPL_NM_CMN(Bs2TrapPrepare) + push .jmp_resume ; push a return address in case of failure. + jmp sDI +.jmp_resume: + mov xSP, xSI ; restore xSP in case the jmp didn't trap. + mov eax, 0eh ; trap # + mov sDX, a_uErrorFixed ; err + or sDX, a_uErrorExec + mov sCX, sDI ; fault eip + mov sBX, sDI ; fault address. + call TMPL_NM_CMN(TestCheckTrap) + jz .failed + inc sDI + test sDI, 0fffh + jnz .jmp_loop + + ; successfull return. + pop sAX + xor al, al + inc al +.return: + pop sSI + pop sDI + pop sDX + pop sCX + pop sBX + leave + ret + +.failed: + pop sAX + xor al, al + jmp .return +%undef a_uErrorFixed +%undef a_uErrorExec +%undef a_pPage +ENDPROC TMPL_NM(TestHammerPage) + + +%include "bootsector2-template-footer.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-pf-1.asm b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-pf-1.asm new file mode 100644 index 00000000..a15c422b --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-pf-1.asm @@ -0,0 +1,164 @@ +; $Id: bootsector2-cpu-pf-1.asm $ +;; @file +; Bootsector test for various types of #PFs. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" +%include "VBox/VMMDevTesting.mac" + + +;******************************************************************************* +;* Defined Constants And Macros * +;******************************************************************************* +;; Base address at which we can start testing page tables and page directories. +%define TST_SCRATCH_PD_BASE BS2_MUCK_ABOUT_BASE +;; Base address at which we can start testing the page pointer table. +%define TST_SCRATCH_PDPT_BASE (1 << X86_PDPT_SHIFT) +;; Base address at which we can start testing the page map level 4. +%define TST_SCRATCH_PML4_BASE ((1 << X86_PML4_SHIFT) + TST_SCRATCH_PD_BASE) + +;; The number of loops done during calibration. +%define TST_CALIBRATE_LOOP_COUNT 10000 +;; The desired benchmark period (seconds). +%define TST_BENCHMARK_PERIOD_IN_SECS 2 + + + +; +; Include and execute the init code. +; + %define BS2_INIT_RM + %define BS2_WITH_TRAPS + %define BS2_INC_RM +; %define BS2_INC_PP16 + %define BS2_INC_PP32 +; %define BS2_INC_PAE16 + %define BS2_INC_PAE32 +; %define BS2_INC_LM16 +; %define BS2_INC_LM32 + %define BS2_INC_LM64 + %include "bootsector2-common-init-code.mac" + + +; +; The benchmark driver +; +BEGINPROC main + ; + ; Test prologue. + ; + mov ax, .s_szTstName + call TestInit_r86 + call Bs2EnableA20_r86 + + ; + ; Execute the tests + ; +%if 1 + %if 1 + xor eax, eax ; NXE=0 (N/A) + call NAME(DoTestsForMode_rm_pp32) + %endif + %if 1 + xor eax, eax ; NXE=0 + call NAME(DoTestsForMode_rm_pae32) + mov eax, 1 ; NXE=1 + call NAME(DoTestsForMode_rm_pae32) + %endif + %if 1 + xor eax, eax ; NXE=0 + call NAME(DoTestsForMode_rm_lm64) + mov eax, 1 ; NXE=1 + call NAME(DoTestsForMode_rm_lm64) + %endif +%endif + + ; + ; Execute benchmarks. + ; +%if 1 + mov ax, .s_szTstBenchmark + call NAME(TestSub_r86) + call NAME(DoBenchmarksForMode_rm_pp32) + call NAME(DoBenchmarksForMode_rm_pae32) + call NAME(DoBenchmarksForMode_rm_lm64) + call NAME(TestSubDone_r86) +%endif + + ; + ; We're done. + ; + call TestTerm_r86 + ret + +.s_szTstBenchmark: + db 'Benchmark', 0 +.s_szTstName: + db 'tstIOIntr', 0 +.s_szTstX: + db 'X', 0 +ENDPROC main + + +; +; Instantiate the template code. +; +%include "bootsector2-template-footer.mac" ; reset the initial environemnt. + +;%define TMPL_PP16 +;%include "bootsector2-cpu-pf-1-template.mac" +%define TMPL_PP32 +%include "bootsector2-cpu-pf-1-template.mac" +;%define TMPL_PAE16 +;%include "bootsector2-cpu-pf-1-template.mac" +%define TMPL_PAE32 +%include "bootsector2-cpu-pf-1-template.mac" +;%define TMPL_LM16 +;%include "bootsector2-cpu-pf-1-template.mac" +;%define TMPL_LM32 +;%include "bootsector2-cpu-pf-1-template.mac" +%define TMPL_LM64 +%include "bootsector2-cpu-pf-1-template.mac" + + +; +; End sections and image. +; +%include "bootsector2-common-end.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-xcpt-1-template.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-xcpt-1-template.mac new file mode 100644 index 00000000..e3281508 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-xcpt-1-template.mac @@ -0,0 +1,1973 @@ +; $Id: bootsector2-cpu-xcpt-1-template.mac $ +;; @file +; Bootsector test for basic exceptions - multi mode template. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "bootsector2-template-header.mac" + + +;******************************************************************************* +;* Defined Constants And Macros * +;******************************************************************************* +;; +; Some 32/64 macros. +; +%if TMPL_BITS == 32 + %define bs2Idt_BP bs2Idt32bit_BP + %define MY_R0_CS BS2_SEL_CS32 + %define MY_R1_CS BS2_SEL_R1_CS32 + %define MY_R2_CS BS2_SEL_R2_CS32 + %define MY_R3_CS BS2_SEL_R3_CS32 + + %define MY_R0_DS BS2_SEL_DS32 + %define MY_R1_DS BS2_SEL_R1_DS32 + %define MY_R2_DS BS2_SEL_R2_DS32 + %define MY_R3_DS BS2_SEL_R3_DS32 + + %define MY_R0_SS BS2_SEL_SS32 + %define MY_R1_SS BS2_SEL_R1_SS32 + %define MY_R2_SS BS2_SEL_R2_SS32 + %define MY_R3_SS BS2_SEL_R3_SS32 + +%else + %define bs2Idt_BP bs2Idt64bit_BP + %define MY_R0_CS BS2_SEL_CS64 + %define MY_R1_CS BS2_SEL_R1_CS64 + %define MY_R2_CS BS2_SEL_R2_CS64 + %define MY_R3_CS BS2_SEL_R3_CS64 + + %define MY_R0_DS BS2_SEL_DS64 + %define MY_R1_DS BS2_SEL_R1_DS64 + %define MY_R2_DS BS2_SEL_R2_DS64 + %define MY_R3_DS BS2_SEL_R3_DS64 + + %define MY_R0_SS BS2_SEL_SS64 + %define MY_R1_SS BS2_SEL_R1_SS64 + %define MY_R2_SS BS2_SEL_R2_SS64 + %define MY_R3_SS BS2_SEL_R3_SS64 +%endif + +%ifdef TMPL_64BIT + %assign MY_IS_64BIT 1 +%else + %assign MY_IS_64BIT 0 +%endif + + +;******************************************************************************* +;* Global Variables * +;******************************************************************************* +%ifndef CPU_XCPT_1_GLOBALS + %define CPU_XCPT_1_GLOBALS + g_szWrongIfStateFmt: + db 'Wrong IF state (0x%RX32) on line 0x%RX32', 0 + g_szWrongHandlerCsFmt: + db 'Wrong handler CS=%RX16, expected %RX16 (line 0x%RX32)', 0 + g_szWrongCurCsFmt: + db 'Wrong CS=%RX16, expected %RX16 (line 0x%RX32)', 0 + g_szWrongCurSRegFmt_fs: + db 'Wrong FS=%RX16, expected %RX16 (line 0x%RX32)', 0 + g_szWrongCurSRegFmt_ss: + db 'Wrong SS=%RX16, expected %RX16 (line 0x%RX32)', 0 + + +;; +; Asserts a test. +; +; @param %1 First cmp operand. +; @param %2 First cmp operand. +; @param %3 Which kind of conditional jump to make +; @param %4 The message to print (format string, no arguments please). +; +%macro ASSERT_SIMPLE 4 + cmp %1, %2 + %3 %%.ok + push dword __LINE__ + %ifdef TMPL_16BIT + push ds + %endif + push %%.s_szMsg + call TMPL_NM_CMN(TestFailedF) + add xSP, sCB*2 + jmp %%.ok +%%.s_szMsg: db %4, " (0x%RX32)", 0 +%%.ok: +%endmacro + + + ;; + ; Asserts that the IF flag is set or clear when the trap handler was called. + ; + ; @param 1 jnz or jz. + ; + ; @uses rax, flags, and stack. + ; + %macro ASSERT_TRAP_EFLAGS_IF 1 + test word [g_u64LastTrapHandlerRFlags xWrtRIP], X86_EFL_IF + %1 %%.ok + %ifdef TMPL_LM64 + push __LINE__ + push qword [g_u64LastTrapHandlerRFlags xWrtRIP] + lea rax, [g_szWrongIfStateFmt wrt RIP] + push rax + call TMPL_NM_CMN(TestFailedF) + add xSP, 24 + %elifdef TMPL_16 + push dword __LINE__ + push dword [g_u64LastTrapHandlerRFlags] + push cs + push g_szWrongIfStateFmt + call TMPL_NM_CMN(TestFailedF) + add xSP, 12 + %else + push __LINE__ + push dword [g_u64LastTrapHandlerRFlags] + push g_szWrongIfStateFmt + call TMPL_NM_CMN(TestFailedF) + add xSP, 12 + %endif + %%.ok: + %endmacro + + + ;; + ; Asserts that a certain CS value when the trap handler was called. + ; + ; @param 1 The CS value. + ; + ; @uses rax, flags, and stack. + ; + %macro ASSERT_TRAP_CS_VALUE 1 + cmp word [g_u16LastTrapHandlerCS xWrtRIP], (%1) + je %%.ok + %ifdef TMPL_LM64 + push __LINE__ + push (%1) + movzx eax, word [g_u16LastTrapHandlerCS xWrtRIP] + push rax + lea rax, [g_szWrongHandlerCsFmt wrt RIP] + push rax + call TMPL_NM_CMN(TestFailedF) + add xSP, 32 + %elifdef TMPL_16 + push dword __LINE__ + push word (%1) + push word [g_u16LastTrapHandlerCS] + push cs + push g_szWrongHandlerCsFmt + call TMPL_NM_CMN(TestFailedF) + add xSP, 12 + %else + push __LINE__ + push (%1) + movzx eax, word [g_u16LastTrapHandlerCS] + push eax + push g_szWrongHandlerCsFmt + call TMPL_NM_CMN(TestFailedF) + add xSP, 16 + %endif + %%.ok: + %endmacro + + ;; + ; Asserts that a certain CS value right now, CS being loaded in BX. + ; + ; @param bx The CS value. + ; @param 1 The expected CS value. + ; + ; @uses rax, flags, and stack. + ; + %macro ASSERT_CUR_CS_VALUE_IN_BX 1 + cmp bx, (%1) + je %%.ok + %ifdef TMPL_LM64 + push __LINE__ + push (%1) + push rbx + lea rax, [g_szWrongCurCsFmt wrt RIP] + push rax + call TMPL_NM_CMN(TestFailedF) + add xSP, 32 + %elifdef TMPL_16 + push dword __LINE__ + push word (%1) + push bx + push g_szWrongCurCsFmt + call TMPL_NM_CMN(TestFailedF) + add xSP, 12 + %else + push __LINE__ + push (%1) + push ebx + push g_szWrongCurCsFmt + call TMPL_NM_CMN(TestFailedF) + add xSP, 16 + %endif + %%.ok: + %endmacro + + ;; + ; Asserts that the given segment register has a certain value right now. + ; + ; @param 1 The segment register + ; @param 2 The value. + ; + ; @uses rax, flags, and stack. + ; + %macro ASSERT_CUR_SREG_VALUE 2 + mov ax, %1 + cmp ax, (%2) + je %%.ok + %ifdef TMPL_LM64 + push __LINE__ + push (%2) + push rax + lea rax, [g_szWrongCurSRegFmt_ %+ %1 wrt RIP] + push rax + call TMPL_NM_CMN(TestFailedF) + add xSP, 32 + %elifdef TMPL_16 + push dword __LINE__ + push word (%2) + push ax + push g_szWrongCurSRegFmt_ %+ %1 + call TMPL_NM_CMN(TestFailedF) + add xSP, 12 + %else + push __LINE__ + push (%2) + push eax + push g_szWrongCurSRegFmt_ %+ %1 + call TMPL_NM_CMN(TestFailedF) + add xSP, 16 + %endif + %%.ok: + %endmacro + + +%endif + + +;; +; Checks different gate types. +; +BEGINPROC TMPL_NM(TestGateType) + push xBP + mov xBP, xSP + push sAX + push xBX + push xCX + push xDX + push xDI + push xSI + + mov xAX, .s_szSubTestName + call TMPL_NM_CMN(TestSub) + + + ; + ; Check that int3 works and save the IDTE before making changes. + ; + ; We'll be changing X86DESCGATE.u4Type, which starts at bit 0x28 (that + ; is byte 5) and is 4-bit wide, and X86DESCGATE.u1DescType, which is + ; at bit 2c. + ; + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 ; check that int3 works before we start messing around... + +%ifdef TMPL_LM64 + push qword [bs2Idt_BP xWrtRIP] + push qword [bs2Idt_BP + 8 xWrtRIP] +%else + push dword [bs2Idt_BP xWrtRIP] + push dword [bs2Idt_BP + 4 xWrtRIP] +%endif + mov xDI, xSP ; for catching stack errors + + ; + ; Check all kinds of none system selectors first (they should all GP(3+IDT)) + ; +%assign u4Type 0 +%rep 16 + and byte [bs2Idt_BP + 5 xWrtRIP], 0e0h + or byte [bs2Idt_BP + 5 xWrtRIP], RT_BIT(4) | u4Type + BS2_TRAP_INSTR X86_XCPT_GP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 + %assign u4Type (u4Type + 1) +%endrep + + ; + ; Illegal system types. + ; +%ifdef TMPL_LM64 + %assign u4Type 0 + %rep 14 + and byte [bs2Idt_BP + 5 xWrtRIP], 0e0h + or byte [bs2Idt_BP + 5 xWrtRIP], u4Type + BS2_TRAP_INSTR X86_XCPT_GP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 + %assign u4Type (u4Type + 1) + %endrep +%else + and byte [bs2Idt_BP + 5 xWrtRIP], 0e0h + or byte [bs2Idt_BP + 5 xWrtRIP], X86_SEL_TYPE_SYS_UNDEFINED + BS2_TRAP_INSTR X86_XCPT_GP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 + + and byte [bs2Idt_BP + 5 xWrtRIP], 0e0h + or byte [bs2Idt_BP + 5 xWrtRIP], X86_SEL_TYPE_SYS_286_TSS_AVAIL + BS2_TRAP_INSTR X86_XCPT_GP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 + + and byte [bs2Idt_BP + 5 xWrtRIP], 0e0h + or byte [bs2Idt_BP + 5 xWrtRIP], X86_SEL_TYPE_SYS_LDT + BS2_TRAP_INSTR X86_XCPT_GP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 + + and byte [bs2Idt_BP + 5 xWrtRIP], 0e0h + or byte [bs2Idt_BP + 5 xWrtRIP], X86_SEL_TYPE_SYS_286_TSS_BUSY + BS2_TRAP_INSTR X86_XCPT_GP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 + + and byte [bs2Idt_BP + 5 xWrtRIP], 0e0h + or byte [bs2Idt_BP + 5 xWrtRIP], X86_SEL_TYPE_SYS_286_CALL_GATE + BS2_TRAP_INSTR X86_XCPT_GP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 + + and byte [bs2Idt_BP + 5 xWrtRIP], 0e0h + or byte [bs2Idt_BP + 5 xWrtRIP], X86_SEL_TYPE_SYS_UNDEFINED2 + BS2_TRAP_INSTR X86_XCPT_GP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 + + and byte [bs2Idt_BP + 5 xWrtRIP], 0e0h + or byte [bs2Idt_BP + 5 xWrtRIP], X86_SEL_TYPE_SYS_386_TSS_AVAIL + BS2_TRAP_INSTR X86_XCPT_GP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 + + and byte [bs2Idt_BP + 5 xWrtRIP], 0e0h + or byte [bs2Idt_BP + 5 xWrtRIP], X86_SEL_TYPE_SYS_UNDEFINED3 + BS2_TRAP_INSTR X86_XCPT_GP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 + + and byte [bs2Idt_BP + 5 xWrtRIP], 0e0h + or byte [bs2Idt_BP + 5 xWrtRIP], X86_SEL_TYPE_SYS_386_TSS_BUSY + BS2_TRAP_INSTR X86_XCPT_GP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 + + and byte [bs2Idt_BP + 5 xWrtRIP], 0e0h + or byte [bs2Idt_BP + 5 xWrtRIP], X86_SEL_TYPE_SYS_UNDEFINED4 + BS2_TRAP_INSTR X86_XCPT_GP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 + + and byte [bs2Idt_BP + 5 xWrtRIP], 0e0h + or byte [bs2Idt_BP + 5 xWrtRIP], X86_SEL_TYPE_SYS_386_CALL_GATE + BS2_TRAP_INSTR X86_XCPT_GP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 +%endif + + ; + ; Legal types. + ; + pushf + sti ; make sure interrupts are enabled. + +%ifdef TMPL_LM64 + and byte [bs2Idt_BP + 5 xWrtRIP], 0e0h + or byte [bs2Idt_BP + 5 xWrtRIP], AMD64_SEL_TYPE_SYS_INT_GATE + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + ASSERT_TRAP_EFLAGS_IF jz + + and byte [bs2Idt_BP + 5 xWrtRIP], 0e0h + or byte [bs2Idt_BP + 5 xWrtRIP], AMD64_SEL_TYPE_SYS_TRAP_GATE + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + ASSERT_TRAP_EFLAGS_IF jnz +%else + and byte [bs2Idt_BP + 5 xWrtRIP], 0e0h + or byte [bs2Idt_BP + 5 xWrtRIP], X86_SEL_TYPE_SYS_386_INT_GATE + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + ASSERT_TRAP_EFLAGS_IF jz + + and byte [bs2Idt_BP + 5 xWrtRIP], 0e0h + or byte [bs2Idt_BP + 5 xWrtRIP], X86_SEL_TYPE_SYS_386_TRAP_GATE + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + ASSERT_TRAP_EFLAGS_IF jnz + + ;; @todo X86_SEL_TYPE_SYS_TASK_GATE, X86_SEL_TYPE_SYS_286_INT_GATE, X86_SEL_TYPE_SYS_286_TRAP_GATE, X86_SEL_TYPE_SYS_386_CALL_GATE +%endif + + popf + + ; + ; Check that a not-present gate GPs. The not-present bit is 0x2f. + ; + and byte [bs2Idt_BP + 5 xWrtRIP], 0e0h +%ifdef TMPL_LM64 + or byte [bs2Idt_BP + 5 xWrtRIP], AMD64_SEL_TYPE_SYS_INT_GATE +%else + or byte [bs2Idt_BP + 5 xWrtRIP], X86_SEL_TYPE_SYS_386_TRAP_GATE +%endif + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + + and byte [bs2Idt_BP + 5 xWrtRIP], 07fh + BS2_TRAP_INSTR X86_XCPT_NP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 + + ; + ; Restore the descriptor and make sure it works. + ; + ASSERT_SIMPLE xDI, xSP, je, "Someone busted xSP during this test." +%ifdef TMPL_LM64 + pop qword [bs2Idt_BP + 8 xWrtRIP] + pop qword [bs2Idt_BP xWrtRIP] +%else + pop dword [bs2Idt_BP + 4 xWrtRIP] + pop dword [bs2Idt_BP xWrtRIP] +%endif + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + + ; + ; Done. + ; + call TMPL_NM_CMN(TestSubDone) + + pop xSI + pop xDI + pop xDX + pop xCX + pop xBX + pop sAX + leave + ret + +.s_szSubTestName: + db TMPL_MODE_STR, ', IDTE type checks', 0 +ENDPROC TMPL_NM(TestGateType) + + +;; +; Checks different code selector types. +; +; @uses No registers, but BS2_SEL_SPARE0 is trashed. +; +BEGINPROC TMPL_NM(TestCodeSelector) + push xBP + mov xBP, xSP + push sAX + push xBX + push xCX + push xDX + push xDI + push xSI + + mov xAX, .s_szSubTestName + call TMPL_NM_CMN(TestSub) + + + ; + ; Modify the first extra selector to be various kinds of invalid code + ; selectors. + ; + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 ; check that int3 works before we start messing around... + +%ifdef TMPL_LM64 + push qword [bs2Idt_BP xWrtRIP] + push qword [bs2Idt_BP + 8 xWrtRIP] +%else + push dword [bs2Idt_BP xWrtRIP] + push dword [bs2Idt_BP + 4 xWrtRIP] +%endif + + mov ecx, [bs2Gdt + MY_R0_CS xWrtRIP] + mov [bs2GdtSpare0 xWrtRIP], ecx + mov ecx, [bs2Gdt + MY_R0_CS + 4 xWrtRIP] + mov [bs2GdtSpare0 + 4 xWrtRIP], ecx ; GdtSpare0 is a copy of the CS descriptor now. + + mov word [bs2Idt_BP + 2 xWrtRIP], BS2_SEL_SPARE0 + + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 ; check again to make sure the CS copy is fine. + + + ; Data selector (u4Type starts at bit 0x28, that is byte 5) . + and byte [bs2GdtSpare0 + 5 xWrtRIP], 0f0h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_RO + BS2_TRAP_INSTR X86_XCPT_GP, BS2_SEL_SPARE0, int3 + + and byte [bs2GdtSpare0 + 5 xWrtRIP], 0f0h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_RO_ACC + BS2_TRAP_INSTR X86_XCPT_GP, BS2_SEL_SPARE0, int3 + + and byte [bs2GdtSpare0 + 5 xWrtRIP], 0f0h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_RW + BS2_TRAP_INSTR X86_XCPT_GP, BS2_SEL_SPARE0, int3 + + and byte [bs2GdtSpare0 + 5 xWrtRIP], 0f0h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_RW_ACC + BS2_TRAP_INSTR X86_XCPT_GP, BS2_SEL_SPARE0, int3 + + and byte [bs2GdtSpare0 + 5 xWrtRIP], 0f0h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_RO_DOWN + BS2_TRAP_INSTR X86_XCPT_GP, BS2_SEL_SPARE0, int3 + + and byte [bs2GdtSpare0 + 5 xWrtRIP], 0f0h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_RO_DOWN_ACC + BS2_TRAP_INSTR X86_XCPT_GP, BS2_SEL_SPARE0, int3 + + and byte [bs2GdtSpare0 + 5 xWrtRIP], 0f0h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_RW_DOWN + BS2_TRAP_INSTR X86_XCPT_GP, BS2_SEL_SPARE0, int3 + + and byte [bs2GdtSpare0 + 5 xWrtRIP], 0f0h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_RW_DOWN_ACC + BS2_TRAP_INSTR X86_XCPT_GP, BS2_SEL_SPARE0, int3 + + ; Executable selector types (works fine). + and byte [bs2GdtSpare0 + 5 xWrtRIP], 0f0h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_EO + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + + and byte [bs2GdtSpare0 + 5 xWrtRIP], 0f0h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_EO_ACC + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + + and byte [bs2GdtSpare0 + 5 xWrtRIP], 0f0h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_ER + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + + and byte [bs2GdtSpare0 + 5 xWrtRIP], 0f0h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_ER_ACC + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + + and byte [bs2GdtSpare0 + 5 xWrtRIP], 0f0h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_EO_CONF + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + + and byte [bs2GdtSpare0 + 5 xWrtRIP], 0f0h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_EO_CONF_ACC + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + + and byte [bs2GdtSpare0 + 5 xWrtRIP], 0f0h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_ER_CONF + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + + and byte [bs2GdtSpare0 + 5 xWrtRIP], 0f0h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_ER_CONF_ACC + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + + ; + ; Test with the code selector set to NULL. + ; + mov word [bs2Idt_BP + 2 xWrtRIP], 0 + BS2_TRAP_INSTR X86_XCPT_GP, 0, int3 + + mov word [bs2Idt_BP + 2 xWrtRIP], 1 + BS2_TRAP_INSTR X86_XCPT_GP, 0, int3 + + mov word [bs2Idt_BP + 2 xWrtRIP], 2 + BS2_TRAP_INSTR X86_XCPT_GP, 0, int3 + + mov word [bs2Idt_BP + 2 xWrtRIP], 3 + BS2_TRAP_INSTR X86_XCPT_GP, 0, int3 + + mov word [bs2Idt_BP + 2 xWrtRIP], BS2_SEL_SPARE0 ; restore our CS + + ; + ; Test with the code selector marked as not present but otherwise valid. + ; + and byte [bs2GdtSpare0 + 5 xWrtRIP], 0f0h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_ER_ACC + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + + and byte [bs2GdtSpare0 + 5 xWrtRIP], 07fh + BS2_TRAP_INSTR X86_XCPT_NP, BS2_SEL_SPARE0, int3 + + ; + ; Invalid CS selector and not present, we should get a GP. + ; Intel states that the present bit is checked after the type. + ; + and byte [bs2GdtSpare0 + 5 xWrtRIP], 070h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_RW_DOWN_ACC + BS2_TRAP_INSTR X86_XCPT_GP, BS2_SEL_SPARE0, int3 + +%ifdef TMPL_LM64 + ; Long mode variations on invalid (L and D bits) pitted against NP. + and byte [bs2GdtSpare0 + 5 xWrtRIP], 070h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_ER_ACC + and byte [bs2GdtSpare0 + 6 xWrtRIP], ~(RT_BIT(5) | RT_BIT(6)) ; (0x35=u1Long, 0x36=u1DefBig) = (0, 0) + BS2_TRAP_INSTR X86_XCPT_GP, BS2_SEL_SPARE0, int3 + + or byte [bs2GdtSpare0 + 6 xWrtRIP], RT_BIT(6) ; (0x35=u1Long, 0x36=u1DefBig) = (0, 1) + BS2_TRAP_INSTR X86_XCPT_GP, BS2_SEL_SPARE0, int3 + + or byte [bs2GdtSpare0 + 6 xWrtRIP], RT_BIT(5) ; (0x35=u1Long, 0x36=u1DefBig) = (1, 1) + BS2_TRAP_INSTR X86_XCPT_GP, BS2_SEL_SPARE0, int3 + + and byte [bs2GdtSpare0 + 6 xWrtRIP], ~(RT_BIT(5) | RT_BIT(6)) + or byte [bs2GdtSpare0 + 6 xWrtRIP], RT_BIT(5) ; restored +%endif + + and byte [bs2GdtSpare0 + 5 xWrtRIP], 070h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_ER_ACC | 080h ; restore CS to present & valid. + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 ; make sure this is so. + + ; + ; Check the CS DPL vs IDTE DPL. + ; X86DESCGENERIC.u2Dpl is at bit 0x2d (i.e. in byte 5). + ; + and byte [bs2GdtSpare0 + 5 xWrtRIP], ~(RT_BIT(5) | RT_BIT(6)) + or byte [bs2GdtSpare0 + 5 xWrtRIP], 0 ; CS.DPL == 0 == CPL + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + + and byte [bs2GdtSpare0 + 5 xWrtRIP], ~(RT_BIT(5) | RT_BIT(6)) + or byte [bs2GdtSpare0 + 5 xWrtRIP], 1 << 5 ; CS.DPL == 1 < CPL + BS2_TRAP_INSTR X86_XCPT_GP, BS2_SEL_SPARE0, int3 + + and byte [bs2GdtSpare0 + 5 xWrtRIP], ~(RT_BIT(5) | RT_BIT(6)) + or byte [bs2GdtSpare0 + 5 xWrtRIP], 2 << 5 ; CS.DPL == 2 < CPL + BS2_TRAP_INSTR X86_XCPT_GP, BS2_SEL_SPARE0, int3 + + and byte [bs2GdtSpare0 + 5 xWrtRIP], ~(RT_BIT(5) | RT_BIT(6)) + or byte [bs2GdtSpare0 + 5 xWrtRIP], 3 << 5 ; CS.DPL == 3 < CPL + BS2_TRAP_INSTR X86_XCPT_GP, BS2_SEL_SPARE0, int3 + + ; Restore. + and byte [bs2GdtSpare0 + 5 xWrtRIP], 010h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_ER_ACC | 080h ; restore CS to present, valid and DPL=0 + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 ; make sure it's restored. + + ; + ; Is RPL is ignored? Yes, it is. + ; + and word [bs2Idt_BP + 2 xWrtRIP], X86_SEL_MASK_OFF_RPL ; RPL = 0 + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + ASSERT_TRAP_CS_VALUE BS2_SEL_SPARE0 + + and word [bs2Idt_BP + 2 xWrtRIP], X86_SEL_MASK_OFF_RPL + or byte [bs2Idt_BP + 2 xWrtRIP], 1 ; RPL = 1 + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + ASSERT_TRAP_CS_VALUE BS2_SEL_SPARE0 + + and word [bs2Idt_BP + 2 xWrtRIP], X86_SEL_MASK_OFF_RPL + or byte [bs2Idt_BP + 2 xWrtRIP], 2 ; RPL = 2 + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + ASSERT_TRAP_CS_VALUE BS2_SEL_SPARE0 + + and word [bs2Idt_BP + 2 xWrtRIP], X86_SEL_MASK_OFF_RPL + or byte [bs2Idt_BP + 2 xWrtRIP], 3 ; RPL = 3 + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + ASSERT_TRAP_CS_VALUE BS2_SEL_SPARE0 + + ; + ; Conforming CS. + ; + or byte [bs2Idt_BP + 5 xWrtRIP], (3 << 5) ; IDTE.DPL = 3 + and byte [bs2GdtSpare0 + 5 xWrtRIP], 090h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_ER_CONF_ACC ; CS.DPL=0, code, read, conforming + + call TMPL_NM_CMN(Bs2ToRing1) + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + call TMPL_NM_CMN(Bs2ToRing0) + ASSERT_TRAP_CS_VALUE BS2_SEL_SPARE0 | 1 + + call TMPL_NM_CMN(Bs2ToRing2) + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + call TMPL_NM_CMN(Bs2ToRing0) + ASSERT_TRAP_CS_VALUE BS2_SEL_SPARE0 | 2 + + call TMPL_NM_CMN(Bs2ToRing3) + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + call TMPL_NM_CMN(Bs2ToRing0) + ASSERT_TRAP_CS_VALUE BS2_SEL_SPARE0 | 3 + + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + ASSERT_TRAP_CS_VALUE BS2_SEL_SPARE0 | 0 + + ; RPL is ignored. Only CPL matters. + or byte [bs2Idt_BP + 2 xWrtRIP], (3 << 5) ; IDTE.CS.RPL=3 + call TMPL_NM_CMN(Bs2ToRing2) + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + call TMPL_NM_CMN(Bs2ToRing0) + ASSERT_TRAP_CS_VALUE BS2_SEL_SPARE0 | 2 + + and word [bs2Idt_BP + 2 xWrtRIP], X86_SEL_MASK_OFF_RPL + or byte [bs2Idt_BP + 2 xWrtRIP], (1 << 5) ; IDTE.CS.RPL=1 + call TMPL_NM_CMN(Bs2ToRing2) + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + call TMPL_NM_CMN(Bs2ToRing0) + ASSERT_TRAP_CS_VALUE BS2_SEL_SPARE0 | 2 + + and word [bs2Idt_BP + 2 xWrtRIP], X86_SEL_MASK_OFF_RPL + or byte [bs2Idt_BP + 2 xWrtRIP], (2 << 5) ; IDTE.CS.RPL=2 + call TMPL_NM_CMN(Bs2ToRing2) + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + call TMPL_NM_CMN(Bs2ToRing0) + ASSERT_TRAP_CS_VALUE BS2_SEL_SPARE0 | 2 + + ; Change the CS.DPL to 1 and try it from ring-0. + and byte [bs2GdtSpare0 + 5 xWrtRIP], 09fh + or byte [bs2GdtSpare0 + 5 xWrtRIP], (1 << 5) ; CS.DPL=1 + BS2_TRAP_INSTR X86_XCPT_GP, BS2_SEL_SPARE0, int3 + + ; Restore. + and word [bs2Idt_BP + 2 xWrtRIP], X86_SEL_MASK_OFF_RPL + and byte [bs2Idt_BP + 5 xWrtRIP], 0x9f ; IDTE.DPL=0 + and byte [bs2GdtSpare0 + 5 xWrtRIP], 010h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_ER_ACC | 080h ; restore CS to present, valid and DPL=0 + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 ; make sure it's restored. + + ; + ; Limit / canonical checks. + ; + ; Messing with X86DESCGENERIC.u16LimitLow which is at bit 0, + ; X86DESCGENERIC.u4LimitHigh which is at bit 0x30, and + ; X86DESCGENERIC.u1Granularity which is at bit 0x37. + ; + mov word [bs2GdtSpare0 xWrtRIP], 0010h + and byte [bs2GdtSpare0 + 6 xWrtRIP], 070h ; setting limit to 0x10, ASSUMES IDTE.off > 0x10 +%ifdef TMPL_LM64 + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 +%else + BS2_TRAP_INSTR X86_XCPT_GP, 0, int3 +%endif + +%ifdef TMPL_LM64 + or dword [bs2Idt_BP + 8 xWrtRIP], 0x007f7f33 + BS2_TRAP_INSTR X86_XCPT_GP, 0, int3 +%endif + + ; Who takes precedence? CS NP or the above GP? NP does. + and byte [bs2GdtSpare0 + 5 xWrtRIP], 07fh + BS2_TRAP_INSTR X86_XCPT_NP, BS2_SEL_SPARE0, int3 + + +%ifdef TMPL_LM64 + ; Who takes precedence? IDTE NP or the not canoncial GP? NP does. + or byte [bs2GdtSpare0 + 5 xWrtRIP], 80h + and byte [bs2Idt_BP + 5 xWrtRIP], 07fh + BS2_TRAP_INSTR X86_XCPT_NP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 +%endif + + ; + ; Restore the descriptor and make sure it works. + ; +%ifdef TMPL_LM64 + pop qword [bs2Idt_BP + 8 xWrtRIP] + pop qword [bs2Idt_BP xWrtRIP] +%else + pop dword [bs2Idt_BP + 4 xWrtRIP] + pop dword [bs2Idt_BP xWrtRIP] +%endif + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + + ; + ; Done. + ; + call TMPL_NM_CMN(TestSubDone) + + pop xSI + pop xDI + pop xDX + pop xCX + pop xBX + pop sAX + leave + ret + +.s_szSubTestName: + db TMPL_MODE_STR, ', IDTE CS checks', 0 +ENDPROC TMPL_NM(TestCodeSelector) + + +;; +; Checks that the IDTE type is checked before the CS type. +; +; @uses No registers, but BS2_SEL_SPARE0 is trashed. +; +BEGINPROC TMPL_NM(TestCheckOrderCsTypeVsIdteType) + push xBP + mov xBP, xSP + push sAX + push xBX + push xCX + push xDX + push xDI + push xSI + + mov xAX, .s_szSubTestName + call TMPL_NM_CMN(TestSub) + + + ; + ; Check the int3 and save its IDTE. + ; + ; We'll be changing X86DESCGATE.u4Type, which starts at bit 0x28 (that + ; is byte 5) and is 4-bit wide, and X86DESCGATE.u1DescType, which is + ; at bit 2c. + ; + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 ; check that int3 works before we start messing around... + +%ifdef TMPL_LM64 + push qword [bs2Idt_BP xWrtRIP] + push qword [bs2Idt_BP + 8 xWrtRIP] +%else + push dword [bs2Idt_BP xWrtRIP] + push dword [bs2Idt_BP + 4 xWrtRIP] +%endif + + ; + ; Make a copy of our CS descriptor into spare one and make INT3 use it. + ; + mov ecx, [bs2Gdt + MY_R0_CS xWrtRIP] + mov [bs2GdtSpare0 xWrtRIP], ecx + mov ecx, [bs2Gdt + MY_R0_CS + 4 xWrtRIP] + mov [bs2GdtSpare0 + 4 xWrtRIP], ecx ; GdtSpare0 is a copy of the CS descriptor now. + + mov word [bs2Idt_BP + 2 xWrtRIP], BS2_SEL_SPARE0 + + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 ; check again to make sure the CS copy is fine. + + ; + ; Make both the IDTE type and CS invalid, we should end up with a IDT GP not the CS one. + ; CS = data selector and IDTE invalid 0 type. + ; + and byte [bs2GdtSpare0 + 5 xWrtRIP], 0f0h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_RO + + and byte [bs2Idt_BP + 5 xWrtRIP], 0e0h + or byte [bs2Idt_BP + 5 xWrtRIP], X86_SEL_TYPE_SYS_UNDEFINED + BS2_TRAP_INSTR X86_XCPT_GP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 + + ; + ; Make the IDTE not-present but otherwise fine, keeping CS invalid. + ; + and byte [bs2Idt_BP + 5 xWrtRIP], 070h +%ifdef TMPL_LM64 + or byte [bs2Idt_BP + 5 xWrtRIP], AMD64_SEL_TYPE_SYS_INT_GATE +%else + or byte [bs2Idt_BP + 5 xWrtRIP], X86_SEL_TYPE_SYS_386_TRAP_GATE +%endif + BS2_TRAP_INSTR X86_XCPT_NP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 + + ; + ; Make the CS not present as well. + ; + and byte [bs2GdtSpare0 + 5 xWrtRIP], 070h + or byte [bs2GdtSpare0 + 5 xWrtRIP], X86_SEL_TYPE_EO + BS2_TRAP_INSTR X86_XCPT_NP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 + + ; + ; CS not present, IDTE invalid but present. + ; + and byte [bs2Idt_BP + 5 xWrtRIP], 0f0h + or byte [bs2Idt_BP + 5 xWrtRIP], X86_SEL_TYPE_SYS_UNDEFINED | 0x80 + BS2_TRAP_INSTR X86_XCPT_GP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 + + ; + ; CS NULL, IDTE invalid but present. + ; + mov word [bs2Idt_BP + 2 xWrtRIP], 0 + BS2_TRAP_INSTR X86_XCPT_GP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 + + ; + ; CS NULL, IDTE valid but not present. + ; + and byte [bs2Idt_BP + 5 xWrtRIP], 070h +%ifdef TMPL_LM64 + or byte [bs2Idt_BP + 5 xWrtRIP], AMD64_SEL_TYPE_SYS_INT_GATE +%else + or byte [bs2Idt_BP + 5 xWrtRIP], X86_SEL_TYPE_SYS_386_TRAP_GATE +%endif + BS2_TRAP_INSTR X86_XCPT_NP, (3 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT, int3 + + ; + ; Restore the descriptor and make sure it works. + ; +%ifdef TMPL_LM64 + pop qword [bs2Idt_BP + 8 xWrtRIP] + pop qword [bs2Idt_BP xWrtRIP] +%else + pop dword [bs2Idt_BP + 4 xWrtRIP] + pop dword [bs2Idt_BP xWrtRIP] +%endif + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + + ; + ; Done. + ; + call TMPL_NM_CMN(TestSubDone) + + pop xSI + pop xDI + pop xDX + pop xCX + pop xBX + pop sAX + leave + ret + +.s_szSubTestName: + db TMPL_MODE_STR, ', IDTE.type before CS.type', 0 +ENDPROC TMPL_NM(TestCheckOrderCsTypeVsIdteType) + + +;; +; Checks stack switching behavior. +; +; @uses none +; +BEGINPROC TMPL_NM(TestStack) + push xBP + mov xBP, xSP + push sAX + push xBX + push xCX + push xDX + push xDI + push xSI + pushf + cli + + mov xAX, .s_szSubTestName + call TMPL_NM_CMN(TestSub) + + + ; + ; Check the int3, save its IDTE, then make it ring-3 accessible. + ; + ; X86DESCGENERIC.u2Dpl is at bit 0x2d (i.e. in byte 5). + ; + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 ; check that int3 works before we start messing around... + +%ifdef TMPL_LM64 + push qword [bs2Idt_BP xWrtRIP] + push qword [bs2Idt_BP + 8 xWrtRIP] +%else + push dword [bs2Idt_BP xWrtRIP] + push dword [bs2Idt_BP + 4 xWrtRIP] +%endif + + and byte [bs2Idt_BP + 5 xWrtRIP], ~(RT_BIT(5) | RT_BIT(6)) + or byte [bs2Idt_BP + 5 xWrtRIP], 3 << 5 ; DPL == 3 + + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + + + ; + ; In ring-0 no stack switching is performed. + ; + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + mov xBX, [g_u64LastTrapHandlerRSP] +%ifdef TMPL_64BIT + mov rax, rsp + and rax, ~15 + sub rax, 7*8 +%else + lea eax, [esp - 5*4] +%endif + ASSERT_SIMPLE sAX, xBX, je, "Wrong xSP value for ring-0 -> ring-0 int3." + mov bx, [g_u16LastTrapHandlerSS] + mov ax, ss + ASSERT_SIMPLE ax, bx, je, "Wrong SS value for ring-0 -> ring-0 int3." + + ; + ; Switch to ring-1 and watch stack switching take place. + ; + call TMPL_NM_CMN(Bs2ToRing1) + + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + mov xBX, [g_u64LastTrapHandlerRSP] + mov sAX, BS2_R0_STACK_ADDR +%ifdef TMPL_64BIT + and rax, ~15 + sub rax, 7*8 +%else + sub eax, 7*4 +%endif + ASSERT_SIMPLE sAX, xBX, je, "Wrong xSP value for ring-1 -> ring-0 int3." + mov bx, [g_u16LastTrapHandlerSS] +%ifdef TMPL_64BIT + mov ax, 0 +%else + mov ax, MY_R0_SS +%endif + ASSERT_SIMPLE ax, bx, je, "Wrong SS value for ring-1 -> ring-0 int3." + + call TMPL_NM_CMN(Bs2ToRing0) + + ; + ; Missaligned stack, ring-0 -> ring-0. + ; + mov xDI, xSP ; save the stack pointer. +%rep 15 + sub xSP, 1h + + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + mov xBX, [g_u64LastTrapHandlerRSP] +%ifdef TMPL_64BIT + mov rax, rsp + and rax, ~15 + sub rax, 7*8 +%else + lea eax, [esp - 5*4] +%endif + ASSERT_SIMPLE sAX, xBX, je, "Wrong xSP value for ring-0 -> ring-0 int3, w/ unaligned stack." + mov bx, [g_u16LastTrapHandlerSS] + mov ax, ss + ASSERT_SIMPLE ax, bx, je, "Wrong SS value for ring-0 -> ring-0 int3, w/ unaligned stack." + +%endrep + mov xSP, xDI ; restore the stack pointer. + + ; + ; Missaligned stack, ring-1 -> ring-0. + ; + call TMPL_NM_CMN(Bs2ToRing1) + + mov sSI, BS2_R0_STACK_ADDR - 16 +%rep 16 + add sSI, 1 +%ifdef TMPL_64BIT + mov [bs2Tss64Bit + 4], sSI +%else + mov [bs2Tss32Bit + 4], sSI +%endif + + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + mov xBX, [g_u64LastTrapHandlerRSP] + mov sAX, sSI +%ifdef TMPL_64BIT + and rax, ~15 + sub rax, 7*8 +%else + sub eax, 7*4 +%endif + ASSERT_SIMPLE sAX, xBX, je, "Wrong xSP value for ring-1 -> ring-0 int3, w/ unaligned ring-0 stack." + mov bx, [g_u16LastTrapHandlerSS] +%ifdef TMPL_64BIT + mov ax, 0 +%else + mov ax, MY_R0_SS +%endif + ASSERT_SIMPLE sAX, xBX, je, "Wrong SS value for ring-1 -> ring-0 int3, w/ unaligned ring-0 stack." + +%endrep + call TMPL_NM_CMN(Bs2ToRing0) + + +%ifdef TMPL_64BIT + ; + ; Stack table (AMD64 only), ring-0 -> ring-0. + ; + and byte [bs2Idt_BP + 4], ~7 + or byte [bs2Idt_BP + 4], 3 ; IDTE.IST=3 + + mov rdi, [bs2Tss64Bit + X86TSS64.ist3] + mov rsi, BS2_R0_STACK_ADDR - 128 + %rep 16 + sub rsi, 1h + mov [bs2Tss64Bit + X86TSS64.ist3], rsi + + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + mov rbx, [g_u64LastTrapHandlerRSP] + mov rax, rsi + and rax, ~15 + sub rax, 7*8 + ASSERT_SIMPLE rax, rbx, je, "Wrong xSP value for ring-0 -> ring-0 int3, w/ unaligned IST." + mov bx, [g_u16LastTrapHandlerSS] + mov ax, ss + ASSERT_SIMPLE ax, bx, je, "Wrong SS value for ring-0 -> ring-0 int3, w/ unaligned IST." + + %endrep + + ; Continue in ring-1,2,3. + %assign uCurRing 1 + %rep 3 + call TMPL_NM_CMN(Bs2ToRing %+ uCurRing) + %rep 16 + sub rsi, 1h + mov [bs2Tss64Bit + X86TSS64.ist3], rsi + + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + mov rbx, [g_u64LastTrapHandlerRSP] + mov rax, rsi + and rax, ~15 + sub rax, 7*8 + ASSERT_SIMPLE rax, rbx, je, "Wrong xSP value for ring-X -> ring-0 int3, w/ unaligned IST." + mov bx, [g_u16LastTrapHandlerSS] + mov ax, 0 + ASSERT_SIMPLE ax, bx, je, "Wrong SS value for ring-X -> ring-0 int3, w/ unaligned IST." + %endrep + call TMPL_NM_CMN(Bs2ToRing0) + %assign uCurRing (uCurRing + 1) + %endrep + + mov [bs2Tss64Bit + X86TSS64.ist3], rdi ; restore original value + and byte [bs2Idt_BP + 4], ~7 ; IDTE.IST=0 + + + ; + ; Check SS handling when interrupting 32-bit code with a 64-bit handler. + ; + call Bs2Thunk_lm64_lm32 + BITS 32 + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + mov bx, [g_u16LastTrapHandlerSS] + mov ax, ss + call Bs2Thunk_lm32_lm64 + BITS 64 + ASSERT_SIMPLE ax, bx, je, "Wrong SS value for ring-0-32 -> ring-0-64 int3, w/ 32-bit stack." + + call Bs2Thunk_lm64_lm32 + BITS 32 + mov cx, ss + mov ax, BS2_SEL_SS16 + mov ss, ax + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + mov bx, [g_u16LastTrapHandlerSS] + mov ss, cx + call Bs2Thunk_lm32_lm64 + BITS 64 + ASSERT_SIMPLE ax, bx, je, "Wrong SS value for ring-0-32 -> ring-0-64 int3, w/ 16-bit stack." + +%endif ; TMPL_64BIT + + + ; + ; Restore the descriptor and make sure it works. + ; +%ifdef TMPL_LM64 + pop qword [bs2Idt_BP + 8 xWrtRIP] + pop qword [bs2Idt_BP xWrtRIP] +%else + pop dword [bs2Idt_BP + 4 xWrtRIP] + pop dword [bs2Idt_BP xWrtRIP] +%endif + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + + ; + ; Done. + ; + call TMPL_NM_CMN(TestSubDone) + + popf + pop xSI + pop xDI + pop xDX + pop xCX + pop xBX + pop sAX + leave + ret + +.s_szSubTestName: + db TMPL_MODE_STR, ', Stack switching', 0 +ENDPROC TMPL_NM(TestStack) + + + +;; +; Loads MY_R0_CS into CS. +; +; @uses stack, cs, flags +; +BEGINPROC TMPL_NM(TestLoadMyCS) + push 0 + push xAX + + ; Make it a far return with MY_R0_CS + CPL. + mov xAX, [xSP + xCB*2] + mov [xSP + xCB*1], xAX + mov xAX, ss +%ifdef TMPL_64BIT + sub xAX, BS2_SEL_GRP_SS64 - BS2_SEL_GRP_CS64 +%elifdef TMPL_32BIT + sub xAX, BS2_SEL_GRP_SS32 - BS2_SEL_GRP_CS32 +%elifdef TMPL_16BIT + sub xAX, BS2_SEL_GRP_SS16 - BS2_SEL_GRP_CS16 +%else + TMPL_xxBIT is not defined +%endif + mov [xSP + xCB*2], xAX + + pop xAX + retf +ENDPROC TMPL_NM(TestLoadMyCS) + + +;; +; Checks our understanding of how conforming segments are handled. +; +; @uses No registers, but BS2_SEL_SPARE0 is trashed. +; +BEGINPROC TMPL_NM(TestConforming) + push xBP + mov xBP, xSP + push sAX + push xBX + push xCX + push xDX + push xDI + push xSI + + mov xAX, .s_szSubTestName + call TMPL_NM_CMN(TestSub) + + ; + ; Check the int3. + ; + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 ; check that int3 works before we start messing around... + + mov xDI, xSP ; save the stack pointer. + sub xSP, 20h + + ; + ; In this test we will do various experiments with code using a + ; conforming CS. The main purpose is to check that CS.RPL is always the + ; same as CPL, despite earlier beliefs to the contrary. Because if it + ; is different, iret cannot dermine the CPL to return to among other + ; interesting problems. + ; + mov ecx, [bs2Gdt + MY_R0_CS xWrtRIP] + mov [bs2GdtSpare0 xWrtRIP], ecx + mov ecx, [bs2Gdt + MY_R0_CS + 4 xWrtRIP] + mov [bs2GdtSpare0 + 4 xWrtRIP], ecx ; GdtSpare0 is a copy of the CS descriptor now. + and byte [bs2GdtSpare0 + 5], 0x90 ; DPL = 0 + or byte [bs2GdtSpare0 + 5], X86_SEL_TYPE_ER_CONF_ACC + +%assign uCurRing 0 +%rep 4 + ; Far jumps. + %assign uSpecifiedRpl 0 + %rep 4 + call TMPL_NM_CMN(Bs2ToRing %+ uCurRing) + lea xAX, [.far_jmp_target_ %+ uSpecifiedRpl %+ uCurRing] + %ifdef TMPL_64BIT ; AMD doesn't have an jmp far m16:m64 instruction, it ignores REX.W apparently. Intel does though. + ; Tested on: Bulldozer + mov dword [xSP + 4], BS2_SEL_SPARE0 | uSpecifiedRpl + mov [xSP], eax + jmp far dword [xSP] + %else + mov dword [xSP + xCB], BS2_SEL_SPARE0 | uSpecifiedRpl + mov [xSP], xAX + jmp far xPRE [xSP] + %endif +.far_jmp_target_ %+ uSpecifiedRpl %+ uCurRing: + mov bx, cs + call TMPL_NM(TestLoadMyCS) + call TMPL_NM_CMN(Bs2ToRing0) + ASSERT_CUR_CS_VALUE_IN_BX BS2_SEL_SPARE0 | uCurRing + %assign uSpecifiedRpl uSpecifiedRpl + 1 + %endrep + + ; Far calls. + %assign uSpecifiedRpl 0 + %rep 4 + call TMPL_NM_CMN(Bs2ToRing %+ uCurRing) + mov xSI, xSP + lea xAX, [.far_call_target_ %+ uSpecifiedRpl %+ uCurRing] + %ifdef TMPL_64BIT ; AMD doesn't have an call far m16:m64 instruction, it ignores REX.W apparently. Intel does though. + ; Tested on: Bulldozer + mov dword [xSP + 4], BS2_SEL_SPARE0 | uSpecifiedRpl + mov [xSP], eax + call far dword [xSP] + %else + mov dword [xSP + xCB], BS2_SEL_SPARE0 | uSpecifiedRpl + mov [xSP], xAX + call far xPRE [xSP] + %endif +.far_call_target_ %+ uSpecifiedRpl %+ uCurRing: + mov bx, cs + %ifdef TMPL_64BIT + add xSP, 4 * 2 + %else + add xSP, xCB * 2 + %endif + call TMPL_NM(TestLoadMyCS) + call TMPL_NM_CMN(Bs2ToRing0) + ASSERT_CUR_CS_VALUE_IN_BX BS2_SEL_SPARE0 | uCurRing + %assign uSpecifiedRpl uSpecifiedRpl + 1 + %endrep + + %assign uCurRing uCurRing + 1 +%endrep + + ; + ; While at it, lets check something about RPL and non-conforming + ; segments. The check when loading is supposed to be RPL >= DPL, + ; except for when loading SS, where RPL = DPL = CPL. + ; + + ; ring-0 + mov dx, MY_R0_DS | 0 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R0_DS | 0 + mov dx, MY_R0_DS | 1 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R0_DS, mov fs, dx + mov dx, MY_R0_DS | 2 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R0_DS, mov fs, dx + mov dx, MY_R0_DS | 3 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R0_DS, mov fs, dx + + ; ring-0 - Lower DPL isn't an issue, only RPL vs DPL. + mov dx, MY_R1_DS | 0 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R1_DS | 0 + mov dx, MY_R1_DS | 1 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R1_DS | 1 + mov dx, MY_R1_DS | 2 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R1_DS, mov fs, dx + + mov dx, MY_R2_DS | 0 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R2_DS | 0 + mov dx, MY_R2_DS | 2 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R2_DS | 2 + mov dx, MY_R2_DS | 3 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R2_DS, mov fs, dx + + mov dx, MY_R3_DS | 0 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R3_DS | 0 + mov dx, MY_R3_DS | 1 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R3_DS | 1 + mov dx, MY_R3_DS | 2 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R3_DS | 2 + mov dx, MY_R3_DS | 3 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R3_DS | 3 + + ; ring-0 - What works above doesn't work with ss. + mov dx, MY_R1_DS | 0 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R1_DS, mov ss, dx + mov dx, MY_R1_DS | 1 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R1_DS, mov ss, dx + mov dx, MY_R1_DS | 2 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R1_DS, mov ss, dx + mov dx, MY_R2_DS | 0 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R2_DS, mov ss, dx + mov dx, MY_R3_DS | 0 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R3_DS, mov ss, dx + mov dx, MY_R3_DS | 3 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R3_DS, mov ss, dx + + + ; ring-1 + call TMPL_NM_CMN(Bs2ToRing1) + + mov dx, MY_R1_DS | 0 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R1_DS | 0 + mov dx, MY_R1_DS | 1 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R1_DS | 1 + mov dx, MY_R1_DS | 2 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R1_DS, mov fs, dx + mov dx, MY_R1_DS | 3 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R1_DS, mov fs, dx + + mov dx, MY_R0_DS | 0 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R0_DS, mov fs, dx + mov dx, MY_R0_DS | 1 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R0_DS, mov fs, dx + mov dx, MY_R0_DS | 2 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R0_DS, mov fs, dx + mov dx, MY_R0_DS | 3 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R0_DS, mov fs, dx + + ; ring-1 - Lower DPL isn't an issue, only RPL vs DPL. + mov dx, MY_R2_DS | 0 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R2_DS | 0 + mov dx, MY_R2_DS | 1 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R2_DS | 1 + mov dx, MY_R2_DS | 2 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R2_DS | 2 + mov dx, MY_R2_DS | 3 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R2_DS, mov fs, dx + + mov dx, MY_R3_DS | 0 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R3_DS | 0 + mov dx, MY_R3_DS | 1 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R3_DS | 1 + mov dx, MY_R3_DS | 2 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R3_DS | 2 + mov dx, MY_R3_DS | 3 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R3_DS | 3 + + ; ring-1 - What works above doesn't work with ss. + mov dx, MY_R1_DS | 0 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R1_DS, mov ss, dx + mov dx, MY_R1_DS | 2 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R1_DS, mov ss, dx + mov dx, MY_R2_DS | 0 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R2_DS, mov ss, dx + mov dx, MY_R3_DS | 0 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R3_DS, mov ss, dx + mov dx, MY_R3_DS | 3 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R3_DS, mov ss, dx + + + ; ring-2 + call TMPL_NM_CMN(Bs2ToRing2) + + mov dx, MY_R2_DS | 0 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R2_DS | 0 + mov dx, MY_R2_DS | 1 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R2_DS | 1 + mov dx, MY_R2_DS | 2 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R2_DS | 2 + mov dx, MY_R2_DS | 3 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R2_DS, mov fs, dx + + mov dx, MY_R0_DS | 0 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R0_DS, mov fs, dx + mov dx, MY_R0_DS | 1 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R0_DS, mov fs, dx + mov dx, MY_R0_DS | 2 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R0_DS, mov fs, dx + mov dx, MY_R0_DS | 3 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R0_DS, mov fs, dx + mov dx, MY_R1_DS | 1 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R1_DS, mov fs, dx + mov dx, MY_R1_DS | 2 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R1_DS, mov fs, dx + + ; ring-2 - Lower DPL isn't an issue, only RPL vs DPL. + mov dx, MY_R3_DS | 0 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R3_DS | 0 + mov dx, MY_R3_DS | 1 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R3_DS | 1 + mov dx, MY_R3_DS | 2 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R3_DS | 2 + mov dx, MY_R3_DS | 3 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R3_DS | 3 + + ; ring-2 - What works above doesn't work with ss. + mov dx, MY_R2_DS | 1 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R2_DS, mov ss, dx + mov dx, MY_R2_DS | 3 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R2_DS, mov ss, dx + mov dx, MY_R3_DS | 0 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R3_DS, mov ss, dx + mov dx, MY_R3_DS | 1 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R3_DS, mov ss, dx + mov dx, MY_R3_DS | 2 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R3_DS, mov ss, dx + mov dx, MY_R3_DS | 3 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R3_DS, mov ss, dx + + + ; ring-3 + call TMPL_NM_CMN(Bs2ToRing3) + + mov dx, MY_R3_DS | 0 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R3_DS | 0 + mov dx, MY_R3_DS | 1 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R3_DS | 1 + mov dx, MY_R3_DS | 2 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R3_DS | 2 + mov dx, MY_R3_DS | 3 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, MY_R3_DS | 3 + + mov dx, MY_R0_DS | 0 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R0_DS, mov fs, dx + mov dx, MY_R0_DS | 1 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R0_DS, mov fs, dx + mov dx, MY_R0_DS | 2 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R0_DS, mov fs, dx + mov dx, MY_R0_DS | 3 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R0_DS, mov fs, dx + + mov dx, MY_R1_DS | 1 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R1_DS, mov fs, dx + mov dx, MY_R1_DS | 2 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R1_DS, mov fs, dx + + mov dx, MY_R2_DS | 0 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R2_DS, mov fs, dx + mov dx, MY_R2_DS | 1 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R2_DS, mov fs, dx + mov dx, MY_R2_DS | 2 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R2_DS, mov fs, dx + mov dx, MY_R2_DS | 3 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R2_DS, mov fs, dx + + ; ring-0 - What works above doesn't work with ss. + mov dx, MY_R3_DS | 0 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R3_DS, mov ss, dx + mov dx, MY_R3_DS | 1 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R3_DS, mov ss, dx + mov dx, MY_R3_DS | 2 + BS2_TRAP_INSTR X86_XCPT_GP, MY_R3_DS, mov ss, dx + + call TMPL_NM_CMN(Bs2ToRing0) + + + ; + ; One more odd thing, NULL selectors and RPL. + ; + pushf + cli + +%assign uCurRing 0 +%rep 4 + ; Null sectors. + call TMPL_NM_CMN(Bs2ToRing %+ uCurRing) + mov si, ss + + mov dx, 0 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, 0 + %if MY_IS_64BIT == 0 || uCurRing != 0 + %ifdef TMPL_64BIT ; AMD is doing something inconsistent. + %if uCurRing != 3 + test byte [g_fCpuAmd], 1 + jz .null_0_not_amd_ %+ uCurRing + mov ss, dx + ASSERT_CUR_SREG_VALUE ss, 0 + jmp .null_0_next_ %+ uCurRing +.null_0_not_amd_ %+ uCurRing: + %endif + %endif + BS2_TRAP_INSTR X86_XCPT_GP, 0, mov ss, dx +.null_0_next_ %+ uCurRing: + %else + mov ss, dx + ASSERT_CUR_SREG_VALUE ss, 0 + %endif + mov ss, si + + mov dx, 1 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, 1 + %if MY_IS_64BIT == 0 || uCurRing != 1 + %ifdef TMPL_64BIT ; AMD is doing something inconsistent. + %if uCurRing != 3 + test byte [g_fCpuAmd], 1 + jz .null_1_not_amd_ %+ uCurRing + mov ss, dx + ASSERT_CUR_SREG_VALUE ss, 1 + jmp .null_1_next_ %+ uCurRing +.null_1_not_amd_ %+ uCurRing: + %endif + %endif + BS2_TRAP_INSTR X86_XCPT_GP, 0, mov ss, dx +.null_1_next_ %+ uCurRing: + %else + mov ss, dx + ASSERT_CUR_SREG_VALUE ss, 1 + %endif + mov ss, si + + mov dx, 2 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, 2 + %if MY_IS_64BIT == 0 || uCurRing != 2 + %ifdef TMPL_64BIT ; AMD is doing something inconsistent. + %if uCurRing != 3 + test byte [g_fCpuAmd], 1 + jz .null_2_not_amd_ %+ uCurRing + mov ss, dx + ASSERT_CUR_SREG_VALUE ss, 2 + jmp .null_2_next_ %+ uCurRing +.null_2_not_amd_ %+ uCurRing: + %endif + %endif + BS2_TRAP_INSTR X86_XCPT_GP, 0, mov ss, dx +.null_2_next_ %+ uCurRing: + %else + mov ss, dx + ASSERT_CUR_SREG_VALUE ss, 2 + %endif + mov ss, si + + mov dx, 3 + mov fs, dx + ASSERT_CUR_SREG_VALUE fs, 3 + %ifdef TMPL_64BIT ; AMD is doing something inconsistent. + %if uCurRing != 3 + test byte [g_fCpuAmd], 1 + jz .null_3_not_amd_ %+ uCurRing + mov ss, dx + ASSERT_CUR_SREG_VALUE ss, 3 + jmp .null_3_next_ %+ uCurRing +.null_3_not_amd_ %+ uCurRing: + %endif + %endif + BS2_TRAP_INSTR X86_XCPT_GP, 0, mov ss, dx +.null_3_next_ %+ uCurRing: + mov ss, si + + %assign uCurRing uCurRing + 1 +%endrep + call TMPL_NM_CMN(Bs2ToRing0) + + ; Restore the selectors. + mov dx, MY_R0_DS + mov ds, dx + mov es, dx + mov fs, dx + mov gs, dx + popf + + + ; + ; Restore the descriptor and make sure it works. + ; + mov xSP, xDI ; restore the stack pointer. + BS2_TRAP_INSTR X86_XCPT_BP, 0, int3 + + ; + ; Done. + ; + call TMPL_NM_CMN(TestSubDone) + + pop xSI + pop xDI + pop xDX + pop xCX + pop xBX + pop sAX + leave + ret + +.s_szSubTestName: + db TMPL_MODE_STR, ', Conforming CS, ++', 0 +ENDPROC TMPL_NM(TestConforming) + + + +;; +; Returning from interrupt/trap/whatever handlers. +; +; @uses No registers, but BS2_SEL_SPARE0 is trashed. +; +BEGINPROC TMPL_NM(TestReturn) + push xBP + mov xBP, xSP + push sAX + push xBX + push xCX + push xDX + push xDI + push xSI + sub xSP, 80h ; iret stack frame space. + mov xSI, xSP ; Save the stack register. + + mov xAX, .s_szSubTestName + call TMPL_NM_CMN(TestSub) + +%ifdef TMPL_64BIT + pushfq + pop rdi ; rdi contains good flags register value. + + ; + ; 64-bit mode: IRETQ unconditional pop of SS:RSP. + ; + mov qword [rsp + 20h], MY_R0_SS + mov [rsp + 18h], rsp + mov [rsp + 10h], rdi + mov qword [rsp + 08h], MY_R0_CS + lea rax, [.resume1 wrt rip] + mov [rsp + 00h], rax + iretq + +.resume1: + pushfq + pop rbx + ASSERT_SIMPLE rsp, rsi, je, "Wrong RSP after IRETQ." + mov rsp, rsi + ASSERT_SIMPLE rbx, rdi, je, "Wrong flags after IRETQ." + mov ax, ss + ASSERT_SIMPLE ax, MY_R0_SS, je, "Wrong SS after IRETQ." + mov ax, cs + ASSERT_SIMPLE ax, MY_R0_CS, je, "Wrong CS after IRETQ." + + ; 64-bit mode: The NT flag causes #GP(0) + mov qword [rsp + 20h], MY_R0_SS + lea rax, [rsp - 100h] + mov [rsp + 18h], rax + mov [rsp + 10h], rdi + mov qword [rsp + 08h], MY_R0_CS + lea rax, [.resume2 wrt rip] + mov [rsp + 00h], rax + push rdi + or dword [rsp], X86_EFL_NT + popfq + BS2_TRAP_BRANCH_INSTR X86_XCPT_GP, 0, .resume2, iretq + pushfq + pop rbx + push rdi + popfq + ASSERT_SIMPLE rsp, rsi, je, "Wrong RSP after IRETQ." + mov rsp, rsi + mov rax, rdi + or rax, X86_EFL_NT + ASSERT_SIMPLE rbx, rax, je, "Wrong flags after IRETQ GP(0)-NT." + mov ax, ss + ASSERT_SIMPLE ax, MY_R0_SS, je, "Wrong SS after IRETQ." + mov ax, cs + ASSERT_SIMPLE ax, MY_R0_CS, je, "Wrong CS after IRETQ." + + ; 64-bit mode: The VM flag is disregarded. + mov qword [rsp + 20h], MY_R0_SS + lea rax, [rsp - 88h] + mov [rsp + 18h], rax + mov [rsp + 10h], rdi + or dword [rsp + 10h], X86_EFL_VM + mov qword [rsp + 08h], MY_R0_CS + lea rax, [.resume3 wrt rip] + mov [rsp + 00h], rax + iretq +.resume3: + pushfq + pop rbx + add rsp, 88h + ASSERT_SIMPLE rsp, rsi, je, "Wrong RSP after IRETQ." + mov rsp, rsi + mov rax, rdi + ASSERT_SIMPLE rbx, rax, je, "Wrong flags after IRETQ GP(0)-NT." + mov ax, ss + ASSERT_SIMPLE ax, MY_R0_SS, je, "Wrong SS after IRETQ." + mov ax, cs + ASSERT_SIMPLE ax, MY_R0_CS, je, "Wrong CS after IRETQ." + + ; + ; 64-bit mode: IRETD unconditionally pops SS:ESP as well. + ; + mov dword [rsp + 10h], MY_R0_SS + lea eax, [esp - 18h] + mov [rsp + 0ch], eax + mov [rsp + 08h], edi + mov dword [rsp + 04h], MY_R0_CS + lea eax, [.resume20 wrt rip] + mov [rsp + 00h], eax + iretd +.resume20: + pushfq + pop rbx + add rsp, 18h + ASSERT_SIMPLE rsp, rsi, je, "Wrong RSP after IRETD." + mov rsp, rsi + ASSERT_SIMPLE rbx, rdi, je, "Wrong flags after IRETD." + mov ax, ss + ASSERT_SIMPLE ax, MY_R0_SS, je, "Wrong SS after IRETD." + mov ax, cs + ASSERT_SIMPLE ax, MY_R0_CS, je, "Wrong CS after IRETD." + + ; + ; 64-bit mode: IRET unconditionally pops SS:SP as well. + ; + mov word [rsp + 08h], MY_R0_SS + lea eax, [esp - 1ah] + mov [rsp + 06h], ax + mov [rsp + 04h], di + mov word [rsp + 02h], MY_R0_CS + mov word [rsp + 00h], .resume30 + o16 iret +BEGINCODELOW +.resume30: + jmp .high1 +BEGINCODEHIGH +.high1: + pushfq + pop rbx + add rsp, 1ah + ASSERT_SIMPLE rsp, rsi, je, "Wrong RSP after IRET." + mov rsp, rsi + ASSERT_SIMPLE rbx, rdi, je, "Wrong flags after IRET." + mov ax, ss + ASSERT_SIMPLE ax, MY_R0_SS, je, "Wrong SS after IRET." + mov ax, cs + ASSERT_SIMPLE ax, MY_R0_CS, je, "Wrong CS after IRET." + + +%elifdef TMPL_32BIT + ; later... +%endif + + ; + ; Returning to 16-bit code, what happens to upper ESP bits? + ; + cli + mov xBX, xSP ; save the current stack address + + mov sAX, BS2_SEL_R3_SS16 | 3 + push sAX ; Return SS + movzx edi, bx + or edi, 0xdead0000 + push sDI ; Return sSP +%ifdef TMPL_64BIT + pushfq +%else + pushfd +%endif + mov sAX, BS2_SEL_R3_CS16 | 3 + push sAX ; Return CS + lea sAX, [.resume100 xWrtRIP] + push sAX ; Return sIP +%ifdef TMPL_64BIT + iretq +%else + iretd +%endif + +BEGINCODELOW +BITS 16 +.resume100: + xchg ebx, esp + call Bs2ToRing0_p16 + call TMPL_NM(Bs2Thunk_p16) +BITS TMPL_BITS + jmp .high100 +BEGINCODEHIGH +.high100: + and edi, 0ffffh + ASSERT_SIMPLE ebx, edi, je, "IRET to 16-bit didn't restore ESP as expected [#1]." + +%ifndef TMPL_16BIT + ; + ; Take two on 16-bit return, does the high word of ESP leak? + ; + cli + mov sBX, sSP ; save the current stack address + mov xSP, BS2_MUCK_ABOUT_BASE + 1000h + + mov sAX, BS2_SEL_R3_SS16 | 3 + push sAX ; Return SS + mov sDI, sBX + push sDI ; Return sSP + %ifdef TMPL_64BIT + pushfq + %else + pushfd + %endif + mov sAX, BS2_SEL_R3_CS16 | 3 + push sAX ; Return CS + lea sAX, [.resume101 xWrtRIP] + push sAX ; Return sIP + %ifdef TMPL_64BIT + iretq + %else + iretd + %endif + +BEGINCODELOW +BITS 16 +.resume101: + xchg ebx, esp + call Bs2ToRing0_p16 + call TMPL_NM(Bs2Thunk_p16) +BITS TMPL_BITS + jmp .high101 +BEGINCODEHIGH +.high101: + or edi, (BS2_MUCK_ABOUT_BASE + 1000h) & 0ffff0000h + ASSERT_SIMPLE ebx, edi, je, "IRET to 16-bit didn't restore ESP as expected [#2]." +%endif ; Not 16-bit. + + ; + ; Done. + ; + call TMPL_NM_CMN(TestSubDone) + + mov xSP, xSI + add xSP, 80h + pop xSI + pop xDI + pop xDX + pop xCX + pop xBX + pop sAX + leave + ret + +.s_szSubTestName: + db TMPL_MODE_STR, ', IRET', 0 +ENDPROC TMPL_NM(TestReturn) + +;; +; Do the tests for this mode. +; +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC TMPL_NM(DoTestsForMode_rm) + push bp + mov bp, sp + push ax + + ; + ; Check if the mode and NX is supported, do the switch. + ; + call TMPL_NM(Bs2IsModeSupported_rm) + jz .done + call TMPL_NM(Bs2EnterMode_rm) +BITS TMPL_BITS + + ; + ; Test exception handler basics using INT3 and #BP. + ; + + call TMPL_NM(TestGateType) + call TMPL_NM(TestCodeSelector) + call TMPL_NM(TestCheckOrderCsTypeVsIdteType) + call TMPL_NM(TestStack) + call TMPL_NM(TestConforming) + call TMPL_NM(TestReturn) + + ; + ; Back to real mode. + ; + call TMPL_NM(Bs2ExitMode) +BITS 16 + call Bs2DisableNX_r86 + +.done: + pop ax + leave + ret +ENDPROC TMPL_NM(DoTestsForMode_rm) +TMPL_BEGINCODE +BITS TMPL_BITS + +%include "bootsector2-template-footer.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-xcpt-1.asm b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-xcpt-1.asm new file mode 100644 index 00000000..f030e4f1 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-xcpt-1.asm @@ -0,0 +1,138 @@ +; $Id: bootsector2-cpu-xcpt-1.asm $ +;; @file +; Bootsector test for basic exception stuff. +; +; Recommended (but not necessary): +; VBoxManage setextradata bs-cpu-xcpt-1 VBoxInternal/Devices/VMMDev/0/Config/TestingEnabled 1 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" +%include "VBox/VMMDevTesting.mac" + + +;******************************************************************************* +;* Defined Constants And Macros * +;******************************************************************************* +;; Base address at which we can start testing page tables and page directories. +%define TST_SCRATCH_PD_BASE BS2_MUCK_ABOUT_BASE +;; Base address at which we can start testing the page pointer table. +%define TST_SCRATCH_PDPT_BASE (1 << X86_PDPT_SHIFT) +;; Base address at which we can start testing the page map level 4. +%define TST_SCRATCH_PML4_BASE ((1 << X86_PML4_SHIFT) + TST_SCRATCH_PD_BASE) + + +; +; Include and execute the init code. +; + %define BS2_INIT_RM + %define BS2_WITH_TRAPS + %define BS2_INC_RM + %define BS2_INC_PE16 + %define BS2_INC_PE32 + %define BS2_INC_PP16 + %define BS2_INC_PP32 + %define BS2_INC_PAE16 + %define BS2_INC_PAE32 + %define BS2_INC_LM16 + %define BS2_INC_LM32 + %define BS2_INC_LM64 + %define BS2_WITH_TRAPRECS + %include "bootsector2-common-init-code.mac" + + +; +; The main() function. +; +BEGINPROC main + BITS 16 + ; + ; Test prologue. + ; + mov ax, .s_szTstName + call TestInit_r86 + call Bs2EnableA20_r86 + + + ; + ; Execute the tests + ; +%if 1 + call NAME(DoTestsForMode_rm_pe32) +%endif +%if 1 + call NAME(DoTestsForMode_rm_pp32) +%endif +%if 1 + call NAME(DoTestsForMode_rm_pae32) +%endif +%if 1 + call NAME(DoTestsForMode_rm_lm64) +%endif + + ; + ; We're done. + ; + call TestTerm_r86 + ret + +.s_szTstName: + db 'tstCpuXcpt1', 0 +ENDPROC main + + +; +; Instantiate the template code. +; +%include "bootsector2-template-footer.mac" ; reset the initial environemnt. + +%define TMPL_PE32 +%include "bootsector2-cpu-xcpt-1-template.mac" +%define TMPL_PP32 +%include "bootsector2-cpu-xcpt-1-template.mac" +%define TMPL_PAE32 +%include "bootsector2-cpu-xcpt-1-template.mac" +%define TMPL_LM64 +%include "bootsector2-cpu-xcpt-1-template.mac" + + +; +; End sections and image. +; +%include "bootsector2-common-end.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-xcpt-2-template.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-xcpt-2-template.mac new file mode 100644 index 00000000..5ae476a4 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-xcpt-2-template.mac @@ -0,0 +1,511 @@ +; $Id: bootsector2-cpu-xcpt-2-template.mac $ +;; @file +; Bootsector test for debug exceptions - multi mode template. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "bootsector2-template-header.mac" + + +;******************************************************************************* +;* Defined Constants And Macros * +;******************************************************************************* +;; +; Some 32/64 macros. +; +%if TMPL_BITS == 32 + %define bs2Idt_BP bs2Idt32bit_BP + %define MY_R0_CS BS2_SEL_CS32 + %define MY_R1_CS BS2_SEL_R1_CS32 + %define MY_R2_CS BS2_SEL_R2_CS32 + %define MY_R3_CS BS2_SEL_R3_CS32 + + %define MY_R0_DS BS2_SEL_DS32 + %define MY_R1_DS BS2_SEL_R1_DS32 + %define MY_R2_DS BS2_SEL_R2_DS32 + %define MY_R3_DS BS2_SEL_R3_DS32 + + %define MY_R0_SS BS2_SEL_SS32 + %define MY_R1_SS BS2_SEL_R1_SS32 + %define MY_R2_SS BS2_SEL_R2_SS32 + %define MY_R3_SS BS2_SEL_R3_SS32 + +%else + %define bs2Idt_BP bs2Idt64bit_BP + %define MY_R0_CS BS2_SEL_CS64 + %define MY_R1_CS BS2_SEL_R1_CS64 + %define MY_R2_CS BS2_SEL_R2_CS64 + %define MY_R3_CS BS2_SEL_R3_CS64 + + %define MY_R0_DS BS2_SEL_DS64 + %define MY_R1_DS BS2_SEL_R1_DS64 + %define MY_R2_DS BS2_SEL_R2_DS64 + %define MY_R3_DS BS2_SEL_R3_DS64 + + %define MY_R0_SS BS2_SEL_SS64 + %define MY_R1_SS BS2_SEL_R1_SS64 + %define MY_R2_SS BS2_SEL_R2_SS64 + %define MY_R3_SS BS2_SEL_R3_SS64 +%endif + +%ifdef TMPL_64BIT + %assign MY_IS_64BIT 1 +%else + %assign MY_IS_64BIT 0 +%endif + +;; Uncomment this to do lots more iterations (takes time!). +%define QUICK_TEST + + +;******************************************************************************* +;* Global Variables * +;******************************************************************************* +%ifndef CPU_XCPT_1_GLOBALS + %define CPU_XCPT_1_GLOBALS + +;; +; Asserts a test. +; +; @param %1 First cmp operand. +; @param %2 First cmp operand. +; @param %3 Which kind of conditional jump to make +; @param %4 The message to print (format string, no arguments please). +; + %macro ASSERT_SIMPLE 4 + cmp %1, %2 + %3 %%.ok + cli ; raw-mode hack + push dword __LINE__ + %ifdef TMPL_16BIT + push ds + %endif + push %%.s_szMsg + call TMPL_NM_CMN(TestFailedF) + add xSP, sCB*2 + ;hlt + sti + jmp %%.ok + %%.s_szMsg: db %4, " (0x%RX32)", 0 + %%.ok: + %endmacro + +%endif + + +;; +; Disable the breakpoints as well as check RA1 bits. +; +; @changes DRx +; +BEGINPROC TMPL_NM(DisableBps) + push sAX + push sBX + sPUSHF + + xor eax, eax + mov dr7, sAX + mov dr6, sAX + mov dr0, sAX + mov dr1, sAX + mov dr2, sAX + mov dr3, sAX + + mov sAX, dr6 + mov ebx, X86_DR6_RA1_MASK + ASSERT_SIMPLE sAX, xBX, je, "Wrong DR6 value (RA1)." + mov sAX, dr7 + mov ebx, X86_DR7_RA1_MASK + ASSERT_SIMPLE sAX, sBX, je, "Wrong DR7 value (RA1)." + + sPOPF + pop sBX + pop sAX + ret +ENDPROC TMPL_NM(DisableBps) + + +;; +; Checks different gate types. +; +BEGINPROC TMPL_NM(TestStepping) + push xBP + mov xBP, xSP + push sAX + push xBX + push xCX + push xDX + push xDI + push xSI + + mov xAX, .s_szSubTestName + call TMPL_NM_CMN(TestSub) + + + ; + ; Step one instruction a lot of times to catch DR6 mismanagement. + ; +%ifdef QUICK_TEST + mov ecx, 0x1000 +%else + mov ecx, 0x80000 +%endif +.the_1st_loop: + + mov eax, X86_DR6_INIT_VAL + mov dr6, sAX + mov eax, 0x12345678 + mov ebx, 0xaabbccdd + sPUSHF + or word [xSP], X86_EFL_TF + sPOPF + xchg ebx, eax + BS2_TRAP_INSTR X86_XCPT_DB, 0, nop + ASSERT_SIMPLE eax, 0xaabbccdd, je, "xchg wasn't executed (eax)." + ASSERT_SIMPLE ebx, 0x12345678, je, "xchg wasn't executed (ebx)." + mov sAX, dr6 + ASSERT_SIMPLE eax, (X86_DR6_INIT_VAL | X86_DR6_BS), je, "Wrong DR6 value." + + dec ecx + jnz .the_1st_loop + + ; + ; Check that certain bits in DR6 is preserved and others not. + ; +%ifdef QUICK_TEST + mov ecx, 0x200 +%else + mov ecx, 0x20000 +%endif +.the_2nd_loop: + mov eax, X86_DR6_INIT_VAL | X86_DR6_B0 | X86_DR6_B1 | X86_DR6_B2 | X86_DR6_B3 | X86_DR6_BT | X86_DR6_BD + mov dr6, sAX + mov eax, 0x12345678 + mov ebx, 0xaabbccdd + sPUSHF + or word [xSP], X86_EFL_TF + sPOPF + xchg ebx, eax + BS2_TRAP_INSTR X86_XCPT_DB, 0, nop + ASSERT_SIMPLE eax, 0xaabbccdd, je, "xchg wasn't executed (eax)." + ASSERT_SIMPLE ebx, 0x12345678, je, "xchg wasn't executed (ebx)." + mov sAX, dr6 + ASSERT_SIMPLE eax, (X86_DR6_BS | X86_DR6_INIT_VAL | X86_DR6_BT | X86_DR6_BD), je, "Wrong DR6 value." + + dec ecx + jnz .the_2nd_loop + + ; + ; Done. + ; + cli ; raw-mode hack + call TMPL_NM_CMN(TestSubDone) + sti + + pop xSI + pop xDI + pop xDX + pop xCX + pop xBX + pop sAX + leave + ret + +.s_szSubTestName: + db TMPL_MODE_STR, ', EFLAGS.TF stepping', 0 +ENDPROC TMPL_NM(TestGateType) + + +;; +; Check execution breakpoint. +; +BEGINPROC TMPL_NM(TestBpExec) + push xBP + mov xBP, xSP + push sAX + push xBX + push xCX + push xDX + push xDI + push xSI + + mov xAX, .s_szSubTestName + call TMPL_NM_CMN(TestSub) + + + ; + ; Arm all 4 breakpoints and check DR6 management. + ; +%ifdef QUICK_TEST + mov ecx, 0x1000 +%else + mov ecx, 0x80000 +%endif + lea sAX, [.bp_dr0 xWrtRIP] + mov dr0, sAX + lea sAX, [.bp_dr1 xWrtRIP] + mov dr1, sAX + lea sAX, [.bp_dr2 xWrtRIP] + mov dr2, sAX + lea sAX, [.bp_dr3 xWrtRIP] + mov dr3, sAX + mov eax, X86_DR7_RA1_MASK | X86_DR7_G0 | X86_DR7_G1 | X86_DR7_G2 | X86_DR7_G3 | X86_DR7_GE + mov dr7, sAX + +.the_loop: + mov eax, X86_DR6_INIT_VAL | X86_DR6_BS | X86_DR6_BT | X86_DR6_BD + mov dr6, sAX + + mov eax, 0x12345678 + mov ebx, 0xaabbccdd +.bp_dr0: + BS2_TRAP_INSTR X86_XCPT_DB, 0, xchg ebx, eax + ASSERT_SIMPLE eax, 0x12345678, je, "xchg was executed (eax)." + ASSERT_SIMPLE ebx, 0xaabbccdd, je, "xchg was executed (ebx)." + mov sAX, dr6 + ASSERT_SIMPLE eax, (X86_DR6_INIT_VAL | X86_DR6_BS | X86_DR6_BT | X86_DR6_BD | X86_DR6_B0), je, "Wrong DR6 value (dr0)." + + mov eax, 0x12345678 + mov ebx, 0xaabbccdd +.bp_dr1: + BS2_TRAP_INSTR X86_XCPT_DB, 0, xchg ebx, eax + ASSERT_SIMPLE eax, 0x12345678, je, "xchg was executed (eax)." + ASSERT_SIMPLE ebx, 0xaabbccdd, je, "xchg was executed (ebx)." + mov sAX, dr6 + ASSERT_SIMPLE eax, (X86_DR6_INIT_VAL | X86_DR6_BS | X86_DR6_BT | X86_DR6_BD | X86_DR6_B1), je, "Wrong DR6 value (dr1)." + + mov eax, 0x12345678 + mov ebx, 0xaabbccdd +.bp_dr2: + BS2_TRAP_INSTR X86_XCPT_DB, 0, xchg ebx, eax + ASSERT_SIMPLE eax, 0x12345678, je, "xchg was executed (eax)." + ASSERT_SIMPLE ebx, 0xaabbccdd, je, "xchg was executed (ebx)." + mov sAX, dr6 + ASSERT_SIMPLE eax, (X86_DR6_INIT_VAL | X86_DR6_BS | X86_DR6_BT | X86_DR6_BD | X86_DR6_B2), je, "Wrong DR6 value (dr2)." + + mov eax, 0x12345678 + mov ebx, 0xaabbccdd +.bp_dr3: + BS2_TRAP_INSTR X86_XCPT_DB, 0, xchg ebx, eax + ASSERT_SIMPLE eax, 0x12345678, je, "xchg was executed (eax)." + ASSERT_SIMPLE ebx, 0xaabbccdd, je, "xchg was executed (ebx)." + mov sAX, dr6 + ASSERT_SIMPLE eax, (X86_DR6_INIT_VAL | X86_DR6_BS | X86_DR6_BT | X86_DR6_BD | X86_DR6_B3), je, "Wrong DR6 value (dr3)." + + dec ecx + jnz .the_loop + + ; + ; Touch the code, making sure the BPs don't trigger on data access. + ; + mov al, [.bp_dr0 xWrtRIP] + mov [.bp_dr0 xWrtRIP], al + mov al, [.bp_dr1 xWrtRIP] + mov [.bp_dr1 xWrtRIP], al + mov al, [.bp_dr2 xWrtRIP] + mov [.bp_dr2 xWrtRIP], al + mov al, [.bp_dr3 xWrtRIP] + mov [.bp_dr3 xWrtRIP], al + + ; + ; Done. + ; + call TMPL_NM(DisableBps) + cli ; raw-mode hack + call TMPL_NM_CMN(TestSubDone) + sti + + pop xSI + pop xDI + pop xDX + pop xCX + pop xBX + pop sAX + leave + ret + +.s_szSubTestName: + db TMPL_MODE_STR, ', Exec BP', 0 +ENDPROC TMPL_NM(TestBpExec) + + +;; +; Check I/O breakpoints. +; +BEGINPROC TMPL_NM(TestBpIo) + push xBP + mov xBP, xSP + push sAX + push xBX + push xCX + push xDX + push xDI + push xSI + + mov xAX, .s_szSubTestName + call TMPL_NM_CMN(TestSub) + + + ; + ; Arm all 4 breakpoints and check range handling and such. + ; + mov sAX, cr4 + or sAX, X86_CR4_DE + mov cr4, sAX + +%ifdef QUICK_TEST + mov ecx, 1000 +%else + mov ecx, 4096 +%endif + mov sAX, 84h + mov dr0, sAX + mov sAX, 85h + mov dr1, sAX + mov sAX, 86h + mov dr2, sAX + mov sAX, 8ch + mov dr3, sAX + mov eax, X86_DR7_RA1_MASK | X86_DR7_LE | X86_DR7_GE \ + | X86_DR7_L0 | X86_DR7_G0 | X86_DR7_RW(0, X86_DR7_RW_IO) | X86_DR7_LEN(0, X86_DR7_LEN_BYTE) \ + | X86_DR7_L1 | X86_DR7_G1 | X86_DR7_RW(1, X86_DR7_RW_IO) | X86_DR7_LEN(1, X86_DR7_LEN_WORD) \ + | X86_DR7_L2 | X86_DR7_G2 | X86_DR7_RW(2, X86_DR7_RW_IO) | X86_DR7_LEN(2, X86_DR7_LEN_DWORD) \ + | X86_DR7_L3 | X86_DR7_G3 | X86_DR7_RW(3, X86_DR7_RW_IO) | X86_DR7_LEN(3, X86_DR7_LEN_DWORD) + mov dr7, sAX + +.the_loop: + mov eax, X86_DR6_INIT_VAL + mov dr6, sAX + + mov eax, 0x12345678 + in eax, 84h + BS2_TRAP_INSTR X86_XCPT_DB, 0, nop + ASSERT_SIMPLE eax, 0x12345678, jne, "in was not executed." + mov sAX, dr6 + ASSERT_SIMPLE eax, (X86_DR6_INIT_VAL | X86_DR6_B0), je, "Wrong DR6 value (dr0)." + + mov ebx, 0x12345678 + in eax, 85h + BS2_TRAP_INSTR X86_XCPT_DB, 0, nop + ASSERT_SIMPLE eax, 0x12345678, jne, "in was not executed." + mov sAX, dr6 + ASSERT_SIMPLE eax, (X86_DR6_INIT_VAL | X86_DR6_B1), je, "Wrong DR6 value (dr1)." + + mov eax, 0x12345678 + in eax, 86h + BS2_TRAP_INSTR X86_XCPT_DB, 0, nop + ASSERT_SIMPLE eax, 0x12345678, jne, "in was not executed." + mov sAX, dr6 + ASSERT_SIMPLE eax, (X86_DR6_INIT_VAL | X86_DR6_B2), je, "Wrong DR6 value (dr2)." + + mov eax, 0x12345678 + in eax, 8ch + BS2_TRAP_INSTR X86_XCPT_DB, 0, nop + ASSERT_SIMPLE eax, 0x12345678, jne, "in was not executed." + mov sAX, dr6 + ASSERT_SIMPLE eax, (X86_DR6_INIT_VAL | X86_DR6_B3), je, "Wrong DR6 value (dr3)." + + dec ecx + jnz .the_loop + + ; + ; Done. + ; + call TMPL_NM(DisableBps) + cli ; raw-mode hack + call TMPL_NM_CMN(TestSubDone) + sti + + pop xSI + pop xDI + pop xDX + pop xCX + pop xBX + pop sAX + leave + ret + +.s_szSubTestName: + db TMPL_MODE_STR, ', I/O BP', 0 +ENDPROC TMPL_NM(TestBpIo) + + +;; +; Do the tests for this mode. +; +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC TMPL_NM(DoTestsForMode_rm) + push bp + mov bp, sp + push ax + + ; + ; Check if the mode and NX is supported, do the switch. + ; + call TMPL_NM(Bs2IsModeSupported_rm) + jz .done + call TMPL_NM(Bs2EnterMode_rm) +BITS TMPL_BITS + pushf + sti ; raw-mode hacks + + ; + ; Do the testing. + ; + + call TMPL_NM(TestStepping) + call TMPL_NM(TestBpExec) + call TMPL_NM(TestBpIo) + + ; + ; Back to real mode. + ; + popf + call TMPL_NM(Bs2ExitMode) +BITS 16 + call Bs2DisableNX_r86 + +.done: + pop ax + leave + ret +ENDPROC TMPL_NM(DoTestsForMode_rm) +TMPL_BEGINCODE +BITS TMPL_BITS + +%include "bootsector2-template-footer.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-xcpt-2.asm b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-xcpt-2.asm new file mode 100644 index 00000000..7c7859fb --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-cpu-xcpt-2.asm @@ -0,0 +1,129 @@ +; $Id: bootsector2-cpu-xcpt-2.asm $ +;; @file +; Bootsector test for debug exceptions. +; +; Recommended (but not necessary): +; VBoxManage setextradata bs-cpu-xcpt-2 VBoxInternal/Devices/VMMDev/0/Config/TestingEnabled 1 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" +%include "VBox/VMMDevTesting.mac" + + +; +; Include and execute the init code. +; + %define BS2_INIT_RM + %define BS2_WITH_TRAPS + %define BS2_INC_RM + %define BS2_INC_PE16 + %define BS2_INC_PE32 + %define BS2_INC_PP16 + %define BS2_INC_PP32 + %define BS2_INC_PAE16 + %define BS2_INC_PAE32 + %define BS2_INC_LM16 + %define BS2_INC_LM32 + %define BS2_INC_LM64 + %define BS2_WITH_TRAPRECS + %define BS2_WITH_XCPT_DB_CLEARING_TF + %include "bootsector2-common-init-code.mac" + + +; +; The main() function. +; +BEGINPROC main + BITS 16 + ; + ; Test prologue. + ; + mov ax, .s_szTstName + call TestInit_r86 + call Bs2EnableA20_r86 + cli ; raw-mode hack + + + ; + ; Execute the tests + ; +%if 1 + call NAME(DoTestsForMode_rm_pe32) +%endif +%if 1 + call NAME(DoTestsForMode_rm_pp32) +%endif +%if 1 + call NAME(DoTestsForMode_rm_pae32) +%endif +%if 1 + call NAME(DoTestsForMode_rm_lm64) +%endif + + ; + ; We're done. + ; + call TestTerm_r86 + ret + +.s_szTstName: + db 'tstCpuXcpt2', 0 +ENDPROC main + + +; +; Instantiate the template code. +; +%include "bootsector2-template-footer.mac" ; reset the initial environemnt. + +%define TMPL_PE32 +%include "bootsector2-cpu-xcpt-2-template.mac" +%define TMPL_PP32 +%include "bootsector2-cpu-xcpt-2-template.mac" +%define TMPL_PAE32 +%include "bootsector2-cpu-xcpt-2-template.mac" +%define TMPL_LM64 +%include "bootsector2-cpu-xcpt-2-template.mac" + + +; +; End sections and image. +; +%include "bootsector2-common-end.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-first.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-first.mac new file mode 100644 index 00000000..50f8b410 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-first.mac @@ -0,0 +1,84 @@ +; $Id: bootsector2-first.mac $ +;; @file +; bootsector2 first include file - works around YASM / kBuild issues. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%ifndef ___bootsector2_first_mac +%define ___bootsector2_first_mac + +; +; Undefine thing that shouldn't be defined if we're targeting the +; binary format directly. These macros comes from DEFS in Config.kmk. +; +%ifdef ASM_FORMAT_BIN + %undef RT_ARCH_AMD64 + %undef RT_ARCH_X86 + + %undef RT_OS_DARWIN + %undef RT_OS_FREEBSD + %undef RT_OS_HAIKU + %undef RT_OS_LINUX + %undef RT_OS_NETBSD + %undef RT_OS_OPENBSD + %undef RT_OS_OS2 + %undef RT_OS_WINDOWS + + %undef __AMD64__ + %undef __x86_64__ + %undef __i386__ + %undef __I386__ + %undef __x86__ + %undef __X86__ + + %undef __WIN__ + %undef __WIN32__ + %undef __WIN64__ +%endif + + +; +; Include standard includes. +; +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" +%include "VBox/VMMDevTesting.mac" + + +; +; Open the code segment. +; +BEGINCODE + +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-structures.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-structures.mac new file mode 100644 index 00000000..23b318f4 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-structures.mac @@ -0,0 +1,101 @@ +; $Id: bootsector2-structures.mac $ +;; @file +; Common structures. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%ifndef ___bootsector2_structures_mac___ +%define ___bootsector2_structures_mac___ + + +;; +; Registers. Used by traps and such. +; +struc BS2REGS + .rax resq 1 + .rbx resq 1 + .rcx resq 1 + .rdx resq 1 + .rdi resq 1 + .rsi resq 1 + .rbp resq 1 + .rsp resq 1 + .rip resq 1 + .r8 resq 1 + .r9 resq 1 + .r10 resq 1 + .r11 resq 1 + .r12 resq 1 + .r13 resq 1 + .r14 resq 1 + .r15 resq 1 + .rflags resq 1 + .cs resw 1 + .ds resw 1 + .es resw 1 + .fs resw 1 + .gs resw 1 + .ss resw 1 + .cBits resb 1 + .pad resb 3 + .cr0 resq 1 + .cr2 resq 1 + .cr3 resq 1 + .cr4 resq 1 + .cr8 resq 1 + ;; @todo Add floating point registers when they are active. +endstruc + + + +;; +; Trap record. +; +struc BS2TRAPREC + ;; The trap location relative to the base address given at + ; registration time. + .offWhere resd 1 + ;; What to add to .offWhere to calculate the resume address. + .offResumeAddend resb 1 + ;; The trap number. + .u8TrapNo resb 1 + ;; The error code if the trap takes one. + .u16ErrCd resw 1 +endstruc + +;; The size shift. +%define BS2TRAPREC_SIZE_SHIFT 3 + + +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-template-footer.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-template-footer.mac new file mode 100644 index 00000000..9d32fe71 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-template-footer.mac @@ -0,0 +1,116 @@ +; $Id: bootsector2-template-footer.mac $ +;; @file +; bootsector2 footer for multi-mode code templates. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +; +; Undefine macros defined by the header. +; +; Note! The following is useful for verifying that all macros are included here: +; +; for i in `grep "%define" bootsector2-template-header.mac \ +; | sed -e 's/^ *%define *//' -e 's/^\([^() ]*\).*$/\1/' \ +; | sort -u` +; do +; if ! grep -wF "%undef $i" bootsector2-template-footer.mac; then +; echo $i +; fi +; done +; +%undef TMPL_RM +%undef TMPL_PE16 +%undef TMPL_PE32 +%undef TMPL_PEV86 +%undef TMPL_PP16 +%undef TMPL_PP32 +%undef TMPL_PPV86 +%undef TMPL_PAE16 +%undef TMPL_PAE32 +%undef TMPL_PAEV86 +%undef TMPL_LM16 +%undef TMPL_LM32 +%undef TMPL_LM64 + +%undef TMPL_CMN_PE +%undef TMPL_CMN_PP +%undef TMPL_CMN_PAE +%undef TMPL_CMN_LM +%undef TMPL_CMN_V86 + +%undef TMPL_CMN_P16 +%undef TMPL_CMN_P32 +%undef TMPL_CMN_P64 +%undef TMPL_CMN_R16 +%undef TMPL_CMN_R86 + +%undef TMPL_NM +%undef TMPL_NM_CMN +%undef TMPL_MODE +%undef TMPL_MODE_STR +%undef TMPL_16BIT +%undef TMPL_32BIT +%undef TMPL_64BIT +%undef TMPL_BITS +%undef TMPL_PTR_DEF +%undef TMPL_HAVE_BIOS +%undef TMPL_BEGINCODE + +%undef xCB +%undef xDEF +%undef xRES +%undef xPRE +%undef xSP +%undef xBP +%undef xAX +%undef xBX +%undef xCX +%undef xDX +%undef xDI +%undef xSI +%undef xWrtRIP + +%undef sCB +%undef sDEF +%undef sRES +%undef sPRE +%undef sSP +%undef sBP +%undef sAX +%undef sBX +%undef sCX +%undef sDX +%undef sDI +%undef sSI + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-template-header.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-template-header.mac new file mode 100644 index 00000000..705a93df --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-template-header.mac @@ -0,0 +1,803 @@ +; $Id: bootsector2-template-header.mac $ +;; @file +; bootsector2 header for multi-mode code templates. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +; +; Check and expand the mode defines. +; One of the following must be defined: +; - TMPL_RM - real mode. +; - TMPL_PE16 - 16-bit protected mode, unpaged. +; - TMPL_PE32 - 32-bit protected mode, unpaged. +; - TMPL_PEV86 - virtual 8086 mode under protected mode, unpaged. +; - TMPL_PP16 - 16-bit protected mode, paged. +; - TMPL_PP32 - 32-bit protected mode, paged. +; - TMPL_PPV86 - virtual 8086 mode under protected mode, paged. +; - TMPL_PAE16 - 16-bit protected mode with PAE (paged). +; - TMPL_PAE32 - 16-bit protected mode with PAE (paged). +; - TMPL_PAEV86- virtual 8086 mode under protected mode with PAE (paged). +; - TMPL_LM16 - 16-bit long mode (paged). +; - TMPL_LM32 - 32-bit long mode (paged). +; - TMPL_LM64 - 64-bit long mode (paged). +; +; Derived indicators: +; - TMPL_CMN_PE = TMPL_PE16 | TMPL_PE32 | TMPL_PEV86 +; - TMPL_CMN_PP = TMPL_PP16 | TMPL_PP32 | TMPL_PPV86 +; - TMPL_CMN_PAE = TMPL_PAE16 | TMPL_PAE32 | TMPL_PAEV86 +; - TMPL_CMN_LM = TMPL_LM16 | TMPL_LM32 | TMPL_LM64 +; - TMPL_CMN_V86 = TMPL_PEV86 | TMPL_PPV86 | TMPL_PAEV86 +; - TMPL_CMN_R86 = TMPL_CMN_V86 | TMPL_RM +; +%ifdef TMPL_RM + %ifdef TMPL_PE16 + %error "Both 'TMPL_RM' and 'TMPL_PE16' are defined." + %endif + %ifdef TMPL_PE32 + %error "Both 'TMPL_RM' and 'TMPL_PE32' are defined." + %endif + %ifdef TMPL_PEV86 + %error "Both 'TMPL_RM' and 'TMPL_PEV86' are defined." + %endif + %ifdef TMPL_PP16 + %error "Both 'TMPL_RM' and 'TMPL_PP16' are defined." + %endif + %ifdef TMPL_PP32 + %error "Both 'TMPL_RM' and 'TMPL_PP32' are defined." + %endif + %ifdef TMPL_PPV86 + %error "Both 'TMPL_RM' and 'TMPL_PPV86' are defined." + %endif + %ifdef TMPL_PAE16 + %error "Both 'TMPL_RM' and 'TMPL_PAE16' are defined." + %endif + %ifdef TMPL_PAE32 + %error "Both 'TMPL_RM' and 'TMPL_PAE32' are defined." + %endif + %ifdef TMPL_PAEV86 + %error "Both 'TMPL_RM' and 'TMPL_PAEV86' are defined." + %endif + %ifdef TMPL_LM16 + %error "Both 'TMPL_RM' and 'TMPL_LM16' are defined." + %endif + %ifdef TMPL_LM32 + %error "Both 'TMPL_RM' and 'TMPL_LM32' are defined." + %endif + %ifdef TMPL_LM64 + %error "Both 'TMPL_RM' and 'TMPL_LM64' are defined." + %endif + %define TMPL_16BIT + %define TMPL_BITS 16 + %define TMPL_PTR_DEF dw + %define TMPL_NM(Name) Name %+ _rm + %define TMPL_NM_CMN(Name) Name %+ _r86 + %define TMPL_MODE_STR 'real mode' + %define TMPL_HAVE_BIOS + %define TMPL_CMN_R86 +%endif + +%ifdef TMPL_PE16 + %ifdef TMPL_RM + %error "Both 'TMPL_PE16' and 'TMPL_RM' are defined." + %endif + %ifdef TMPL_PE32 + %error "Both 'TMPL_PE16' and 'TMPL_PE32' are defined." + %endif + %ifdef TMPL_PEV86 + %error "Both 'TMPL_RM' and 'TMPL_PEV86' are defined." + %endif + %ifdef TMPL_PP16 + %error "Both 'TMPL_PE16' and 'TMPL_PP16' are defined." + %endif + %ifdef TMPL_PP32 + %error "Both 'TMPL_PE16' and 'TMPL_PP32' are defined." + %endif + %ifdef TMPL_PPV86 + %error "Both 'TMPL_PE16' and 'TMPL_PPV86' are defined." + %endif + %ifdef TMPL_PAE16 + %error "Both 'TMPL_PE16' and 'TMPL_PAE16' are defined." + %endif + %ifdef TMPL_PAE32 + %error "Both 'TMPL_PE16' and 'TMPL_PAE32' are defined." + %endif + %ifdef TMPL_PAEV86 + %error "Both 'TMPL_PE32' and 'TMPL_PAEV86' are defined." + %endif + %ifdef TMPL_LM16 + %error "Both 'TMPL_PE16' and 'TMPL_LM16' are defined." + %endif + %ifdef TMPL_LM32 + %error "Both 'TMPL_PE16' and 'TMPL_LM32' are defined." + %endif + %ifdef TMPL_LM64 + %error "Both 'TMPL_PE16' and 'TMPL_LM64' are defined." + %endif + %define TMPL_CMN_PE + %define TMPL_CMN_P16 + %define TMPL_16BIT + %define TMPL_BITS 16 + %define TMPL_PTR_DEF dw + %define TMPL_NM(Name) Name %+ _pe16 + %define TMPL_NM_CMN(Name) Name %+ _p16 + %define TMPL_MODE_STR '16-bit unpaged protected mode' +%endif + +%ifdef TMPL_PE32 + %ifdef TMPL_RM + %error "Both 'TMPL_PE32' and 'TMPL_RM' are defined." + %endif + %ifdef TMPL_PE16 + %error "Both 'TMPL_PE32' and 'TMPL_PE16' are defined." + %endif + %ifdef TMPL_PEV86 + %error "Both 'TMPL_PE32' and 'TMPL_PEV86' are defined." + %endif + %ifdef TMPL_PP16 + %error "Both 'TMPL_PE32' and 'TMPL_PP16' are defined." + %endif + %ifdef TMPL_PP32 + %error "Both 'TMPL_PE32' and 'TMPL_PP32' are defined." + %endif + %ifdef TMPL_PPV86 + %error "Both 'TMPL_PE32' and 'TMPL_PPV86' are defined." + %endif + %ifdef TMPL_PAE16 + %error "Both 'TMPL_PE32' and 'TMPL_PAE16' are defined." + %endif + %ifdef TMPL_PAE32 + %error "Both 'TMPL_PE32' and 'TMPL_PAE32' are defined." + %endif + %ifdef TMPL_PAE86 + %error "Both 'TMPL_PE32' and 'TMPL_PPV86' are defined." + %endif + %ifdef TMPL_LM16 + %error "Both 'TMPL_PE32' and 'TMPL_LM16' are defined." + %endif + %ifdef TMPL_LM32 + %error "Both 'TMPL_PE32' and 'TMPL_LM32' are defined." + %endif + %ifdef TMPL_LM64 + %error "Both 'TMPL_PE32' and 'TMPL_LM64' are defined." + %endif + %define TMPL_CMN_PE + %define TMPL_CMN_P32 + %define TMPL_32BIT + %define TMPL_BITS 32 + %define TMPL_PTR_DEF dd + %define TMPL_NM(Name) Name %+ _pe32 + %define TMPL_NM_CMN(Name) Name %+ _p32 + %define TMPL_MODE_STR '32-bit unpaged protected mode' +%endif + +%ifdef TMPL_PEV86 + %ifdef TMPL_RM + %error "Both 'TMPL_PEV86' and 'TMPL_RM' are defined." + %endif + %ifdef TMPL_PE16 + %error "Both 'TMPL_PEV86' and 'TMPL_PE16' are defined." + %endif + %ifdef TMPL_PP32 + %error "Both 'TMPL_PEV86' and 'TMPL_PP32' are defined." + %endif + %ifdef TMPL_PP16 + %error "Both 'TMPL_PEV86' and 'TMPL_PP16' are defined." + %endif + %ifdef TMPL_PP32 + %error "Both 'TMPL_PEV86' and 'TMPL_PP32' are defined." + %endif + %ifdef TMPL_PPV86 + %error "Both 'TMPL_PEV86' and 'TMPL_PPV86' are defined." + %endif + %ifdef TMPL_PAE16 + %error "Both 'TMPL_PEV86' and 'TMPL_PAE16' are defined." + %endif + %ifdef TMPL_PAE32 + %error "Both 'TMPL_PEV86' and 'TMPL_PAE32' are defined." + %endif + %ifdef TMPL_PAE86 + %error "Both 'TMPL_PEV86' and 'TMPL_PPV86' are defined." + %endif + %ifdef TMPL_LM16 + %error "Both 'TMPL_PEV86' and 'TMPL_LM16' are defined." + %endif + %ifdef TMPL_LM32 + %error "Both 'TMPL_PEV86' and 'TMPL_LM32' are defined." + %endif + %ifdef TMPL_LM64 + %error "Both 'TMPL_PEV86' and 'TMPL_LM64' are defined." + %endif + %define TMPL_CMN_PE + %define TMPL_CMN_V86 + %define TMPL_CMN_R86 + %define TMPL_16BIT + %define TMPL_BITS 16 + %define TMPL_PTR_DEF dw + %define TMPL_NM(Name) Name %+ _pev86 + %define TMPL_NM_CMN(Name) Name %+ _r86 + %define TMPL_MODE_STR 'v8086 unpaged protected mode' +%endif + +%ifdef TMPL_PP16 + %ifdef TMPL_RM + %error "Both 'TMPL_PP16' and 'TMPL_RM' are defined." + %endif + %ifdef TMPL_PE16 + %error "Both 'TMPL_PP16' and 'TMPL_PE16' are defined." + %endif + %ifdef TMPL_PE32 + %error "Both 'TMPL_PP16' and 'TMPL_PE32' are defined." + %endif + %ifdef TMPL_PEV86 + %error "Both 'TMPL_PP16' and 'TMPL_PEV86' are defined." + %endif + %ifdef TMPL_PP32 + %error "Both 'TMPL_PP16' and 'TMPL_PP32' are defined." + %endif + %ifdef TMPL_PPV86 + %error "Both 'TMPL_PP32' and 'TMPL_PPV86' are defined." + %endif + %ifdef TMPL_PAE16 + %error "Both 'TMPL_PP16' and 'TMPL_PAE16' are defined." + %endif + %ifdef TMPL_PAE32 + %error "Both 'TMPL_PP16' and 'TMPL_PAE32' are defined." + %endif + %ifdef TMPL_PAEV86 + %error "Both 'TMPL_PP16' and 'TMPL_PAEV86' are defined." + %endif + %ifdef TMPL_LM16 + %error "Both 'TMPL_PP16' and 'TMPL_LM16' are defined." + %endif + %ifdef TMPL_LM32 + %error "Both 'TMPL_PP16' and 'TMPL_LM32' are defined." + %endif + %ifdef TMPL_LM64 + %error "Both 'TMPL_PP16' and 'TMPL_LM64' are defined." + %endif + %define TMPL_CMN_PP + %define TMPL_CMN_P16 + %define TMPL_16BIT + %define TMPL_BITS 16 + %define TMPL_PTR_DEF dw + %define TMPL_NM(Name) Name %+ _pp16 + %define TMPL_NM_CMN(Name) Name %+ _p16 + %define TMPL_MODE_STR '16-bit paged protected mode' +%endif + +%ifdef TMPL_PP32 + %ifdef TMPL_RM + %error "Both 'TMPL_PP32' and 'TMPL_RM' are defined." + %endif + %ifdef TMPL_PE16 + %error "Both 'TMPL_PP32' and 'TMPL_PE16' are defined." + %endif + %ifdef TMPL_PE32 + %error "Both 'TMPL_PP32' and 'TMPL_PE32' are defined." + %endif + %ifdef TMPL_PEV86 + %error "Both 'TMPL_PP32' and 'TMPL_PEV86' are defined." + %endif + %ifdef TMPL_PP16 + %error "Both 'TMPL_PP32' and 'TMPL_PP16' are defined." + %endif + %ifdef TMPL_PPV86 + %error "Both 'TMPL_PP32' and 'TMPL_PPV86' are defined." + %endif + %ifdef TMPL_PAE16 + %error "Both 'TMPL_PP32' and 'TMPL_PAE16' are defined." + %endif + %ifdef TMPL_PAE32 + %error "Both 'TMPL_PP32' and 'TMPL_PAE32' are defined." + %endif + %ifdef TMPL_PAEV86 + %error "Both 'TMPL_PP32' and 'TMPL_PAEV86' are defined." + %endif + %ifdef TMPL_LM16 + %error "Both 'TMPL_PP32' and 'TMPL_LM16' are defined." + %endif + %ifdef TMPL_LM32 + %error "Both 'TMPL_PP32' and 'TMPL_LM32' are defined." + %endif + %ifdef TMPL_LM64 + %error "Both 'TMPL_PP32' and 'TMPL_LM64' are defined." + %endif + %define TMPL_CMN_PP + %define TMPL_CMN_P32 + %define TMPL_32BIT + %define TMPL_BITS 32 + %define TMPL_PTR_DEF dd + %define TMPL_NM(Name) Name %+ _pp32 + %define TMPL_NM_CMN(Name) Name %+ _p32 + %define TMPL_MODE_STR '32-bit paged protected mode' +%endif + +%ifdef TMPL_PPV86 + %ifdef TMPL_RM + %error "Both 'TMPL_PPV86' and 'TMPL_RM' are defined." + %endif + %ifdef TMPL_PE16 + %error "Both 'TMPL_PPV86' and 'TMPL_PE16' are defined." + %endif + %ifdef TMPL_PE32 + %error "Both 'TMPL_PPV86' and 'TMPL_PE32' are defined." + %endif + %ifdef TMPL_PEV86 + %error "Both 'TMPL_PPV86' and 'TMPL_PEV86' are defined." + %endif + %ifdef TMPL_PP16 + %error "Both 'TMPL_PPV86' and 'TMPL_PP16' are defined." + %endif + %ifdef TMPL_PP32 + %error "Both 'TMPL_PPV86' and 'TMPL_PP32' are defined." + %endif + %ifdef TMPL_PAE16 + %error "Both 'TMPL_PPV86' and 'TMPL_PAE16' are defined." + %endif + %ifdef TMPL_PAE32 + %error "Both 'TMPL_PPV86' and 'TMPL_PAE32' are defined." + %endif + %ifdef TMPL_PAEV86 + %error "Both 'TMPL_PPV86' and 'TMPL_PAEV86' are defined." + %endif + %ifdef TMPL_LM16 + %error "Both 'TMPL_PPV86' and 'TMPL_LM16' are defined." + %endif + %ifdef TMPL_LM32 + %error "Both 'TMPL_PPV86' and 'TMPL_LM32' are defined." + %endif + %ifdef TMPL_LM64 + %error "Both 'TMPL_PPV86' and 'TMPL_LM64' are defined." + %endif + %define TMPL_CMN_PP + %define TMPL_CMN_V86 + %define TMPL_CMN_R86 + %define TMPL_16BIT + %define TMPL_BITS 16 + %define TMPL_PTR_DEF dw + %define TMPL_NM(Name) Name %+ _ppv86 + %define TMPL_NM_CMN(Name) Name %+ _r86 + %define TMPL_MODE_STR 'v8086 paged protected mode' +%endif + +%ifdef TMPL_PAE16 + %ifdef TMPL_RM + %error "Both 'TMPL_PAE16' and 'TMPL_RM' are defined." + %endif + %ifdef TMPL_PE16 + %error "Both 'TMPL_PAE16' and 'TMPL_PE16' are defined." + %endif + %ifdef TMPL_PE32 + %error "Both 'TMPL_PAE16' and 'TMPL_PE32' are defined." + %endif + %ifdef TMPL_PEV86 + %error "Both 'TMPL_PAE16' and 'TMPL_PEV86' are defined." + %endif + %ifdef TMPL_PP16 + %error "Both 'TMPL_PAE16' and 'TMPL_PP16' are defined." + %endif + %ifdef TMPL_PP32 + %error "Both 'TMPL_PAE16' and 'TMPL_PP32' are defined." + %endif + %ifdef TMPL_PPV86 + %error "Both 'TMPL_PAE16' and 'TMPL_PPV86' are defined." + %endif + %ifdef TMPL_PAE32 + %error "Both 'TMPL_PAE16' and 'TMPL_PAE32' are defined." + %endif + %ifdef TMPL_LM16 + %error "Both 'TMPL_PAE16' and 'TMPL_LM16' are defined." + %endif + %ifdef TMPL_PAEV86 + %error "Both 'TMPL_PAE16' and 'TMPL_PAEV86' are defined." + %endif + %ifdef TMPL_LM32 + %error "Both 'TMPL_PAE16' and 'TMPL_LM32' are defined." + %endif + %ifdef TMPL_LM64 + %error "Both 'TMPL_PAE16' and 'TMPL_LM64' are defined." + %endif + %define TMPL_CMN_PAE + %define TMPL_16BIT + %define TMPL_CMN_P16 + %define TMPL_BITS 16 + %define TMPL_PTR_DEF dw + %define TMPL_NM(Name) Name %+ _pae16 + %define TMPL_NM_CMN(Name) Name %+ _p16 + %define TMPL_MODE_STR '16-bit pae protected mode' +%endif + +%ifdef TMPL_PAE32 + %ifdef TMPL_RM + %error "Both 'TMPL_PAE32' and 'TMPL_RM' are defined." + %endif + %ifdef TMPL_PE16 + %error "Both 'TMPL_PAE32' and 'TMPL_PE16' are defined." + %endif + %ifdef TMPL_PE32 + %error "Both 'TMPL_PAE32' and 'TMPL_PE32' are defined." + %endif + %ifdef TMPL_PEV86 + %error "Both 'TMPL_PAE32' and 'TMPL_PEV86' are defined." + %endif + %ifdef TMPL_PP16 + %error "Both 'TMPL_PAE32' and 'TMPL_PP16' are defined." + %endif + %ifdef TMPL_PP32 + %error "Both 'TMPL_PAE32' and 'TMPL_PP32' are defined." + %endif + %ifdef TMPL_PPV86 + %error "Both 'TMPL_PAE32' and 'TMPL_PPV86' are defined." + %endif + %ifdef TMPL_PAE16 + %error "Both 'TMPL_PAE32' and 'TMPL_PAE16' are defined." + %endif + %ifdef TMPL_PAEV86 + %error "Both 'TMPL_PAE32' and 'TMPL_PAEV86' are defined." + %endif + %ifdef TMPL_LM16 + %error "Both 'TMPL_PAE32' and 'TMPL_LM16' are defined." + %endif + %ifdef TMPL_LM32 + %error "Both 'TMPL_PAE32' and 'TMPL_LM32' are defined." + %endif + %ifdef TMPL_LM64 + %error "Both 'TMPL_PAE32' and 'TMPL_LM64' are defined." + %endif + %define TMPL_CMN_PAE + %define TMPL_CMN_P32 + %define TMPL_32BIT + %define TMPL_BITS 32 + %define TMPL_PTR_DEF dd + %define TMPL_NM(Name) Name %+ _pae32 + %define TMPL_NM_CMN(Name) Name %+ _p32 + %define TMPL_MODE_STR '32-bit pae protected mode' +%endif + +%ifdef TMPL_PAEV86 + %ifdef TMPL_RM + %error "Both 'TMPL_PAEV86' and 'TMPL_RM' are defined." + %endif + %ifdef TMPL_PE16 + %error "Both 'TMPL_PAEV86' and 'TMPL_PE16' are defined." + %endif + %ifdef TMPL_PE32 + %error "Both 'TMPL_PAEV86' and 'TMPL_PE32' are defined." + %endif + %ifdef TMPL_PEV86 + %error "Both 'TMPL_PAEV86' and 'TMPL_PEV86' are defined." + %endif + %ifdef TMPL_PP16 + %error "Both 'TMPL_PAEV86' and 'TMPL_PP16' are defined." + %endif + %ifdef TMPL_PP32 + %error "Both 'TMPL_PAEV86' and 'TMPL_PP32' are defined." + %endif + %ifdef TMPL_PPV86 + %error "Both 'TMPL_PAEV86' and 'TMPL_PPV86' are defined." + %endif + %ifdef TMPL_PAE16 + %error "Both 'TMPL_PAEV86' and 'TMPL_PAE16' are defined." + %endif + %ifdef TMPL_PAEV86 + %error "Both 'TMPL_PAEV86' and 'TMPL_PAEV86' are defined." + %endif + %ifdef TMPL_LM16 + %error "Both 'TMPL_PAEV86' and 'TMPL_LM16' are defined." + %endif + %ifdef TMPL_LM32 + %error "Both 'TMPL_PAEV86' and 'TMPL_LM32' are defined." + %endif + %ifdef TMPL_LM64 + %error "Both 'TMPL_PAEV86' and 'TMPL_LM64' are defined." + %endif + %define TMPL_CMN_PAE + %define TMPL_CMN_V86 + %define TMPL_CMN_R86 + %define TMPL_16BIT + %define TMPL_BITS 16 + %define TMPL_PTR_DEF dw + %define TMPL_NM(Name) Name %+ _paev86 + %define TMPL_NM_CMN(Name) Name %+ _r86 + %define TMPL_MODE_STR 'v8086 pae protected mode' +%endif + +%ifdef TMPL_LM16 + %ifdef TMPL_RM + %error "Both 'TMPL_LM16' and 'TMPL_RM' are defined." + %endif + %ifdef TMPL_PE16 + %error "Both 'TMPL_LM16' and 'TMPL_PE16' are defined." + %endif + %ifdef TMPL_PE32 + %error "Both 'TMPL_LM16' and 'TMPL_PE32' are defined." + %endif + %ifdef TMPL_PEV86 + %error "Both 'TMPL_LM16' and 'TMPL_PEV86' are defined." + %endif + %ifdef TMPL_PP16 + %error "Both 'TMPL_LM16' and 'TMPL_PP16' are defined." + %endif + %ifdef TMPL_PP32 + %error "Both 'TMPL_LM16' and 'TMPL_PP32' are defined." + %endif + %ifdef TMPL_PPV86 + %error "Both 'TMPL_LM16' and 'TMPL_PPV86' are defined." + %endif + %ifdef TMPL_PAE16 + %error "Both 'TMPL_LM16' and 'TMPL_PAE16' are defined." + %endif + %ifdef TMPL_PAE32 + %error "Both 'TMPL_LM16' and 'TMPL_PAE32' are defined." + %endif + %ifdef TMPL_PAEV86 + %error "Both 'TMPL_LM16' and 'TMPL_PAEV86' are defined." + %endif + %ifdef TMPL_LM32 + %error "Both 'TMPL_LM16' and 'TMPL_LM32' are defined." + %endif + %ifdef TMPL_LM64 + %error "Both 'TMPL_LM16' and 'TMPL_LM64' are defined." + %endif + %define TMPL_CMN_LM + %define TMPL_CMN_P16 + %define TMPL_16BIT + %define TMPL_BITS 16 + %define TMPL_PTR_DEF dw + %define TMPL_NM(Name) Name %+ _lm16 + %define TMPL_NM_CMN(Name) Name %+ _p16 + %define TMPL_MODE_STR '16-bit long mode' +%endif + +%ifdef TMPL_LM32 + %ifdef TMPL_RM + %error "Both 'TMPL_LM32' and 'TMPL_RM' are defined." + %endif + %ifdef TMPL_PE16 + %error "Both 'TMPL_LM32' and 'TMPL_PE16' are defined." + %endif + %ifdef TMPL_PE32 + %error "Both 'TMPL_LM32' and 'TMPL_PE32' are defined." + %endif + %ifdef TMPL_PEV86 + %error "Both 'TMPL_LM32' and 'TMPL_PEV86' are defined." + %endif + %ifdef TMPL_PP16 + %error "Both 'TMPL_LM32' and 'TMPL_PP16' are defined." + %endif + %ifdef TMPL_PP32 + %error "Both 'TMPL_LM32' and 'TMPL_PP32' are defined." + %endif + %ifdef TMPL_PPV86 + %error "Both 'TMPL_LM32' and 'TMPL_PPV86' are defined." + %endif + %ifdef TMPL_PAE16 + %error "Both 'TMPL_LM32' and 'TMPL_PAE16' are defined." + %endif + %ifdef TMPL_PAE32 + %error "Both 'TMPL_LM32' and 'TMPL_PAE32' are defined." + %endif + %ifdef TMPL_PAEV86 + %error "Both 'TMPL_LM32' and 'TMPL_PAEV86' are defined." + %endif + %ifdef TMPL_LM16 + %error "Both 'TMPL_LM32' and 'TMPL_LM16' are defined." + %endif + %ifdef TMPL_LM64 + %error "Both 'TMPL_LM32' and 'TMPL_LM64' are defined." + %endif + %define TMPL_CMN_LM + %define TMPL_CMN_P32 + %define TMPL_32BIT + %define TMPL_BITS 32 + %define TMPL_PTR_DEF dd + %define TMPL_NM(Name) Name %+ _lm32 + %define TMPL_NM_CMN(Name) Name %+ _p32 + %define TMPL_MODE_STR '32-bit long mode' +%endif + +%ifdef TMPL_LM64 + %ifdef TMPL_RM + %error ""Both 'TMPL_LM64' and 'TMPL_RM' are defined."" + %endif + %ifdef TMPL_PE16 + %error "Both 'TMPL_LM64' and 'TMPL_PE16' are defined." + %endif + %ifdef TMPL_PE32 + %error "Both 'TMPL_LM64' and 'TMPL_PE32' are defined." + %endif + %ifdef TMPL_PEV86 + %error "Both 'TMPL_LM64' and 'TMPL_PEV86' are defined." + %endif + %ifdef TMPL_PP16 + %error "Both 'TMPL_LM64' and 'TMPL_PP16' are defined." + %endif + %ifdef TMPL_PP32 + %error "Both 'TMPL_LM64' and 'TMPL_PP32' are defined." + %endif + %ifdef TMPL_PPV86 + %error "Both 'TMPL_LM64' and 'TMPL_PPV86' are defined." + %endif + %ifdef TMPL_PAE16 + %error "Both 'TMPL_LM64' and 'TMPL_PAE16' are defined." + %endif + %ifdef TMPL_PAE32 + %error "Both 'TMPL_LM64' and 'TMPL_PAE32' are defined." + %endif + %ifdef TMPL_PAEV86 + %error "Both 'TMPL_LM64' and 'TMPL_PAEV86' are defined." + %endif + %ifdef TMPL_LM16 + %error "Both 'TMPL_LM64' and 'TMPL_LM16' are defined." + %endif + %ifdef TMPL_LM32 + %error "Both 'TMPL_LM64' and 'TMPL_LM32' are defined." + %endif + %define TMPL_CMN_LM + %define TMPL_CMN_P64 + %define TMPL_64BIT + %define TMPL_BITS 64 + %define TMPL_PTR_DEF dq + %define TMPL_NM(Name) Name %+ _lm64 + %define TMPL_NM_CMN(Name) Name %+ _p64 + %define TMPL_MODE_STR '64-bit long mode' +%endif + +%ifndef TMPL_MODE_STR + %error "internal error" +%endif + + +; +; Register aliases. +; +%ifdef TMPL_64BIT + %define xCB 8 + %define xDEF dq + %define xRES resq + %define xPRE qword + %define xSP rsp + %define xBP rbp + %define xAX rax + %define xBX rbx + %define xCX rcx + %define xDX rdx + %define xDI rdi + %define xSI rsi + %define xWrtRIP wrt rip + %define xPUSHF pushq + %define xPOPF popfq +%else + %ifdef TMPL_32BIT + %define xCB 4 + %define xDEF dd + %define xRES resd + %define xPRE dword + %define xSP esp + %define xBP ebp + %define xAX eax + %define xBX ebx + %define xCX ecx + %define xDX edx + %define xDI edi + %define xSI esi + %define xWrtRIP + %define xPUSHF pushfd + %define xPOPF popfd + %else + %ifndef TMPL_16BIT + %error "TMPL_XXBIT is not defined." + %endif + %define xCB 2 + %define xDEF dw + %define xRES resw + %define xPRE word + %define xSP sp + %define xBP bp + %define xAX ax + %define xBX bx + %define xCX cx + %define xDX dx + %define xDI di + %define xSI si + %define xWrtRIP + %define xPUSHF pushf + %define xPOPF popf + %endif +%endif + +; +; Register names corresponding to the max size for pop/push <reg>. +; +; 16-bit can push both 32-bit and 16-bit registers. This 's' prefixed variant +; is used when 16-bit should use the 32-bit register. +; +%ifdef TMPL_64BIT + %define sCB 8 + %define sDEF dq + %define sRES resq + %define sPRE qword + %define sSP rsp + %define sBP rbp + %define sAX rax + %define sBX rbx + %define sCX rcx + %define sDX rdx + %define sDI rdi + %define sSI rsi + %define sPUSHF pushfq + %define sPOPF popfq +%else + %define sCB 4 + %define sDEF dd + %define sRES resd + %define sPRE dword + %define sSP esp + %define sBP ebp + %define sAX eax + %define sBX ebx + %define sCX ecx + %define sDX edx + %define sDI edi + %define sSI esi + %define sPUSHF pushfd + %define sPOPF popfd +%endif + +; +; Default code segment. +; +%ifdef TMPL_64BIT + %define TMPL_BEGINCODE BEGINCODEHIGH +%elifdef TMPL_32BIT + %define TMPL_BEGINCODE BEGINCODEHIGH +%elifdef TMPL_16BIT + %define TMPL_BEGINCODE BEGINCODELOW +%else + %error "Missing TMPL_xxBIT!" +%endif +TMPL_BEGINCODE + +; +; Change the bitness. +; +%ifdef TMPL_64BIT +BITS 64 +%else + %ifdef TMPL_32BIT +BITS 32 + %else +BITS 16 + %endif +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-test1-template.mac b/src/VBox/ValidationKit/bootsectors/bootsector2-test1-template.mac new file mode 100644 index 00000000..3c911c61 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-test1-template.mac @@ -0,0 +1,836 @@ +; $Id: bootsector2-test1-template.mac $ +;; @file +; bootsector2 test1 - multi mode template. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "bootsector2-template-header.mac" + +;; +; Run the CPUID benchmark for this mode. +; +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC TMPL_NM(BenchmarkFlushCmd_rm) + call TMPL_NM(Bs2IsModeSupported_rm) + jz .done + call TMPL_NM(Bs2EnterMode_rm) +BITS TMPL_BITS + push xBP + mov xBP, xSP + push sAX + push sBX + push sCX + push sDX + push sDI + sub sSP, 20h + + ; Get the current time. + mov xAX, xSP + call TMPL_NM_CMN(GetNanoTS) + + ; Do the test. + mov edi, TEST_INSTRUCTION_COUNT_IO / 4 +%define MSR_IA32_FLUSH_CMD 0x10b +%define MSR_IA32_FLUSH_CMD_F_L1D RT_BIT_32(0) + mov ecx, MSR_IA32_FLUSH_CMD + mov eax, MSR_IA32_FLUSH_CMD_F_L1D + xor edx, edx +.again: + wrmsr + wrmsr + wrmsr + wrmsr + dec edi + jnz .again + + ; Calc the elapsed time and report the result. + mov xAX, xSP + call TMPL_NM_CMN(GetElapsedNanoTS) + + mov xCX, .s_szTestName + mov edx, TEST_INSTRUCTION_COUNT_IO + mov xAX, xSP + call TMPL_NM_CMN(ReportResult) + + add sSP, 20h + pop sDI + pop sDX + pop sCX + pop sBX + pop sAX + leave + + call TMPL_NM(Bs2ExitMode) +BITS 16 +.done: + ret + +.s_szTestName: + db TMPL_MODE_STR, ', FLUSH_CMD', 0 +ENDPROC TMPL_NM(BenchmarkFlushCmd_rm) + +TMPL_BEGINCODE +BITS TMPL_BITS + + +;; +; Run the CPUID benchmark for this mode. +; +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC TMPL_NM(BenchmarkCpuId_rm) + call TMPL_NM(Bs2IsModeSupported_rm) + jz .done + call TMPL_NM(Bs2EnterMode_rm) +BITS TMPL_BITS + push xBP + mov xBP, xSP + push sAX + push sBX + push sCX + push sDX + push sDI + sub sSP, 20h + + ; Get the current time. + mov xAX, xSP + call TMPL_NM_CMN(GetNanoTS) + + ; Do the test. + mov edi, TEST_INSTRUCTION_COUNT_CPUID / 4 +.again: + mov eax, 1 + cpuid + mov eax, 1 + cpuid + mov eax, 1 + cpuid + mov eax, 1 + cpuid + dec edi + jnz .again + + ; Calc the elapsed time and report the result. + mov xAX, xSP + call TMPL_NM_CMN(GetElapsedNanoTS) + + mov xCX, .s_szTestName + mov edx, TEST_INSTRUCTION_COUNT_CPUID + mov xAX, xSP + call TMPL_NM_CMN(ReportResult) + + add sSP, 20h + pop sDI + pop sDX + pop sCX + pop sBX + pop sAX + leave + + call TMPL_NM(Bs2ExitMode) +BITS 16 +.done: + ret + +.s_szTestName: + db TMPL_MODE_STR, ', CPUID', 0 +ENDPROC TMPL_NM(BenchmarkCpuId_rm) + +TMPL_BEGINCODE +BITS TMPL_BITS + + +;; +; Run the RDTSC benchmark for this mode. +; +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC TMPL_NM(BenchmarkRdTsc_rm) + call TMPL_NM(Bs2IsModeSupported_rm) + jz .done + call TMPL_NM(Bs2EnterMode_rm) +BITS TMPL_BITS + push xBP + mov xBP, xSP + push sAX + push sBX + push sCX + push sDX + push sDI + sub sSP, 20h + + ; Get the current time. + mov xAX, xSP + call TMPL_NM_CMN(GetNanoTS) + + ; Do the test. + mov edi, TEST_INSTRUCTION_COUNT_RDTSC / 4 +.again: + rdtsc + rdtsc + rdtsc + rdtsc + dec edi + jnz .again + + ; Calc the elapsed time and report the result. + mov xAX, xSP + call TMPL_NM_CMN(GetElapsedNanoTS) + + mov xCX, .s_szTestName + mov edx, TEST_INSTRUCTION_COUNT_RDTSC + mov xAX, xSP + call TMPL_NM_CMN(ReportResult) + + add sSP, 20h + pop sDI + pop sDX + pop sCX + pop sBX + pop sAX + leave + + call TMPL_NM(Bs2ExitMode) +BITS 16 +.done: + ret + +.s_szTestName: + db TMPL_MODE_STR, ', RDTSC', 0 +ENDPROC TMPL_NM(BenchmarkRdTsc_rm) + +TMPL_BEGINCODE +BITS TMPL_BITS + + +;; +; Run the Read CR4 benchmark for this mode. +; +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC TMPL_NM(BenchmarkRdCr4_rm) + call TMPL_NM(Bs2IsModeSupported_rm) + jz .done + call TMPL_NM(Bs2EnterMode_rm) +BITS TMPL_BITS + push xBP + mov xBP, xSP + push sAX + push sBX + push sCX + push sDX + push sDI + sub sSP, 20h + + ; Get the current time. + mov xAX, xSP + call TMPL_NM_CMN(GetNanoTS) + + ; Do the test. + mov edi, TEST_INSTRUCTION_COUNT_READCR4 / 4 +.again: + mov sAX, cr4 + mov sAX, cr4 + mov sAX, cr4 + mov sAX, cr4 + dec edi + jnz .again + + ; Calc the elapsed time and report the result. + mov xAX, xSP + call TMPL_NM_CMN(GetElapsedNanoTS) + + mov xCX, .s_szTestName + mov edx, TEST_INSTRUCTION_COUNT_READCR4 + mov xAX, xSP + call TMPL_NM_CMN(ReportResult) + + add sSP, 20h + pop sDI + pop sDX + pop sCX + pop sBX + pop sAX + leave + + call TMPL_NM(Bs2ExitMode) +BITS 16 +.done: + ret + +.s_szTestName: + db TMPL_MODE_STR, ', Read CR4', 0 +ENDPROC TMPL_NM(BenchmarkRdCr4_rm) + +TMPL_BEGINCODE +BITS TMPL_BITS + + +;; +; Prologue for the I/O port tests. +%ifndef HaveIoPortPrologue +%define HaveIoPortPrologue +%macro IoPortPrologue 2 + push xBP + mov xBP, xSP + push sAX + push sDX + push sCX + sub xSP, 20h + + ; Get the current time. + mov xAX, xSP + call TMPL_NM_CMN(GetNanoTS) + + ; Do the test. + mov dx, %2 + mov ecx, (%1) / 5 +%endmacro +%endif + + +;; +; Epilogue for the I/O port tests. +%ifndef HaveIoPortEpilogue +%define HaveIoPortEpilogue +%macro IoPortEpilogue 1 + ; Calc the elapsed time and report the result. + mov xAX, xSP + call TMPL_NM_CMN(GetElapsedNanoTS) + + mov xCX, .s_szTestName + mov edx, (%1) + mov xAX, xSP + call TMPL_NM_CMN(ReportResult) + + add xSP, 20h + pop sCX + pop sDX + pop sAX + leave + ret +%endmacro +%endif + + +;; +; Benchmarks: IN eax, NOP +; +; @uses nothing +; +BEGINPROC TMPL_NM(BenchmarkIoPortNop32In) + IoPortPrologue TEST_INSTRUCTION_COUNT_IO, VMMDEV_TESTING_IOPORT_NOP +.again: + in eax, dx + in eax, dx + in eax, dx + in eax, dx + in eax, dx + dec ecx + jnz .again + IoPortEpilogue TEST_INSTRUCTION_COUNT_IO +.s_szTestName: + db TMPL_MODE_STR, ', 32-bit IN', 0 +ENDPROC TMPL_NM(BenchmarkIoPortNop32In) + + +;; +; Benchmarks: OUT NOP, eax +; +; @uses nothing +; +BEGINPROC TMPL_NM(BenchmarkIoPortNop32Out) + IoPortPrologue TEST_INSTRUCTION_COUNT_IO, VMMDEV_TESTING_IOPORT_NOP +.again: + out dx, eax + out dx, eax + out dx, eax + out dx, eax + out dx, eax + dec ecx + jnz .again + IoPortEpilogue TEST_INSTRUCTION_COUNT_IO +.s_szTestName: + db TMPL_MODE_STR, ', 32-bit OUT', 0 +ENDPROC TMPL_NM(BenchmarkIoPortNop32Out) + + +;; +; Benchmarks: IN ax, NOP +; +; @uses nothing +; +BEGINPROC TMPL_NM(BenchmarkIoPortNop16In) + IoPortPrologue TEST_INSTRUCTION_COUNT_IO, VMMDEV_TESTING_IOPORT_NOP +.again: + in ax, dx + in ax, dx + in ax, dx + in ax, dx + in ax, dx + dec ecx + jnz .again + IoPortEpilogue TEST_INSTRUCTION_COUNT_IO +.s_szTestName: + db TMPL_MODE_STR, ', 16-bit IN', 0 +ENDPROC TMPL_NM(BenchmarkIoPortNop16In) + + +;; +; Benchmarks: OUT NOP, ax +; +; @uses nothing +; +BEGINPROC TMPL_NM(BenchmarkIoPortNop16Out) + IoPortPrologue TEST_INSTRUCTION_COUNT_IO, VMMDEV_TESTING_IOPORT_NOP +.again: + out dx, ax + out dx, ax + out dx, ax + out dx, ax + out dx, ax + dec ecx + jnz .again + IoPortEpilogue TEST_INSTRUCTION_COUNT_IO +.s_szTestName: + db TMPL_MODE_STR, ', 16-bit OUT', 0 +ENDPROC TMPL_NM(BenchmarkIoPortNop16Out) + + +;; +; Benchmarks: IN al, NOP +; +; @uses nothing +; +BEGINPROC TMPL_NM(BenchmarkIoPortNop8In) + IoPortPrologue TEST_INSTRUCTION_COUNT_IO, VMMDEV_TESTING_IOPORT_NOP +.again: + in al, dx + in al, dx + in al, dx + in al, dx + in al, dx + dec ecx + jnz .again + IoPortEpilogue TEST_INSTRUCTION_COUNT_IO +.s_szTestName: + db TMPL_MODE_STR, ', 8-bit IN', 0 +ENDPROC TMPL_NM(BenchmarkIoPortNop8In) + + +;; +; Benchmarks: OUT NOP, al +; +; @uses nothing +; +BEGINPROC TMPL_NM(BenchmarkIoPortNop8Out) + IoPortPrologue TEST_INSTRUCTION_COUNT_IO, VMMDEV_TESTING_IOPORT_NOP +.again: + out dx, al + out dx, al + out dx, al + out dx, al + out dx, al + dec ecx + jnz .again + IoPortEpilogue TEST_INSTRUCTION_COUNT_IO +.s_szTestName: + db TMPL_MODE_STR, ', 8-bit OUT', 0 +ENDPROC TMPL_NM(BenchmarkIoPortNop8Out) + + +;; +; Benchmarks: IN eax, NOP_R3 +; +; @uses nothing +; +BEGINPROC TMPL_NM(BenchmarkIoPortRing3Nop32In) + IoPortPrologue TEST_INSTRUCTION_COUNT_IO, VMMDEV_TESTING_IOPORT_NOP_R3 +.again: + in eax, dx + in eax, dx + in eax, dx + in eax, dx + in eax, dx + dec ecx + jnz .again + IoPortEpilogue TEST_INSTRUCTION_COUNT_IO +.s_szTestName: + db TMPL_MODE_STR, ', 32-bit IN-to-ring-3', 0 +ENDPROC TMPL_NM(BenchmarkIoPortRing3Nop32In) + + +;; +; Benchmarks: OUT NOP_R3, eax +; +; @uses nothing +; +BEGINPROC TMPL_NM(BenchmarkIoPortRing3Nop32Out) + IoPortPrologue TEST_INSTRUCTION_COUNT_IO, VMMDEV_TESTING_IOPORT_NOP_R3 +.again: + out dx, eax + out dx, eax + out dx, eax + out dx, eax + out dx, eax + dec ecx + jnz .again + IoPortEpilogue TEST_INSTRUCTION_COUNT_IO +.s_szTestName: + db TMPL_MODE_STR, ', 32-bit OUT-to-ring-3', 0 +ENDPROC TMPL_NM(BenchmarkIoPortRing3Nop32Out) + + +%undef IoPortPrologue +%undef IoPortEpilogue + + +;; +; Run the I/O benchmarks for this mode. +; +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC TMPL_NM(BenchmarkIoPortNop_rm) + call TMPL_NM(Bs2IsModeSupported_rm) + jz .done + call TMPL_NM(Bs2EnterMode_rm) +BITS TMPL_BITS + + call TMPL_NM(BenchmarkIoPortNop32In) + call TMPL_NM(BenchmarkIoPortNop32Out) +%ifndef QUICK_TEST + call TMPL_NM(BenchmarkIoPortNop16In) + call TMPL_NM(BenchmarkIoPortNop16Out) + call TMPL_NM(BenchmarkIoPortNop8In) + call TMPL_NM(BenchmarkIoPortNop8Out) +%endif + call TMPL_NM(BenchmarkIoPortRing3Nop32In) + call TMPL_NM(BenchmarkIoPortRing3Nop32Out) + + call TMPL_NM(Bs2ExitMode) +BITS 16 +.done: + ret +ENDPROC TMPL_NM(BenchmarkIoPortNop_rm) + +TMPL_BEGINCODE +BITS TMPL_BITS + + + + +;; +; Prologue for the MMIO tests. +%ifndef HaveMmioPrologue +%define HaveMmioPrologue +%macro MmioPrologue 2 + push xBP + mov xBP, xSP + push sAX + push sDX + push sCX + push sBX + sub xSP, 20h + + ; Get the current time. + mov xAX, xSP + call TMPL_NM_CMN(GetNanoTS) + + ; Do the test - X million 32-bit IN instructions. +%ifdef TMPL_16BIT + mov dx, ds ; save ds + %ifdef TMPL_RM + mov bx, VMMDEV_TESTING_MMIO_RM_SEL + mov ds, bx + mov ebx, VMMDEV_TESTING_MMIO_RM_OFF(%2) + %else + mov bx, BS2_SEL_MMIO16 + mov ds, bx + mov ebx, %2 - BS2_SEL_MMIO16_BASE + %endif +%else + mov xBX, %2 +%endif + mov ecx, (%1) / 5 +%endmacro +%endif + +;; +; Epilogue for the MMIO tests. +%ifndef HaveMmioEpilogue +%define HaveMmioEpilogue +%macro MmioEpilogue 1 +%ifdef TMPL_16BIT + mov ds, dx ; restore ds +%endif + + ; Calc the elapsed time and report the result. + mov xAX, xSP + call TMPL_NM_CMN(GetElapsedNanoTS) + + mov xCX, .s_szTestName + mov edx, (%1) + mov xAX, xSP + call TMPL_NM_CMN(ReportResult) + + add xSP, 20h + pop sBX + pop sCX + pop sDX + pop sAX + leave + ret +%endmacro +%endif + + +;; +; Benchmarks: MOV eax, [NOP] +; +; @uses nothing +; +BEGINPROC TMPL_NM(BenchmarkMmioNop32Read) + MmioPrologue TEST_INSTRUCTION_COUNT_MMIO, VMMDEV_TESTING_MMIO_NOP +.again: + mov eax, [sBX] + mov eax, [sBX] + mov eax, [sBX] + mov eax, [sBX] + mov eax, [sBX] + dec ecx + jnz .again + MmioEpilogue TEST_INSTRUCTION_COUNT_MMIO +.s_szTestName: + db TMPL_MODE_STR, ', 32-bit read', 0 +ENDPROC TMPL_NM(BenchmarkMmioNop32Read) + + +;; +; Benchmarks: MOV [NOP], eax +; +; @uses nothing +; +BEGINPROC TMPL_NM(BenchmarkMmioNop32Write) + MmioPrologue TEST_INSTRUCTION_COUNT_MMIO, VMMDEV_TESTING_MMIO_NOP +.again: + mov [sBX], eax + mov [sBX], eax + mov [sBX], eax + mov [sBX], eax + mov [sBX], eax + dec ecx + jnz .again + MmioEpilogue TEST_INSTRUCTION_COUNT_MMIO +.s_szTestName: + db TMPL_MODE_STR, ', 32-bit write', 0 +ENDPROC TMPL_NM(BenchmarkMmioNop32Write) + + +;; +; Benchmarks: MOV ax, [NOP] +; +; @uses nothing +; +BEGINPROC TMPL_NM(BenchmarkMmioNop16Read) + MmioPrologue TEST_INSTRUCTION_COUNT_MMIO, VMMDEV_TESTING_MMIO_NOP +.again: + mov ax, [xBX] + mov ax, [xBX] + mov ax, [xBX] + mov ax, [xBX] + mov ax, [xBX] + dec ecx + jnz .again + MmioEpilogue TEST_INSTRUCTION_COUNT_MMIO +.s_szTestName: + db TMPL_MODE_STR, ', 16-bit read', 0 +ENDPROC TMPL_NM(BenchmarkMmioNop16Read) + + +;; +; Benchmarks: MOV [NOP], ax +; +; @uses nothing +; +BEGINPROC TMPL_NM(BenchmarkMmioNop16Write) + MmioPrologue TEST_INSTRUCTION_COUNT_MMIO, VMMDEV_TESTING_MMIO_NOP +.again: + mov [xBX], ax + mov [xBX], ax + mov [xBX], ax + mov [xBX], ax + mov [xBX], ax + dec ecx + jnz .again + MmioEpilogue TEST_INSTRUCTION_COUNT_MMIO +.s_szTestName: + db TMPL_MODE_STR, ', 16-bit write', 0 +ENDPROC TMPL_NM(BenchmarkMmioNop16Write) + + +;; +; Benchmarks: MOV al, [NOP] +; +; @uses nothing +; +BEGINPROC TMPL_NM(BenchmarkMmioNop8Read) + MmioPrologue TEST_INSTRUCTION_COUNT_MMIO, VMMDEV_TESTING_MMIO_NOP +.again: + mov al, [xBX] + mov al, [xBX] + mov al, [xBX] + mov al, [xBX] + mov al, [xBX] + dec ecx + jnz .again + MmioEpilogue TEST_INSTRUCTION_COUNT_MMIO +.s_szTestName: + db TMPL_MODE_STR, ', 8-bit read', 0 +ENDPROC TMPL_NM(BenchmarkMmioNop8Read) + + +;; +; Benchmarks: MOV [NOP], al +; +; @uses nothing +; +BEGINPROC TMPL_NM(BenchmarkMmioNop8Write) + MmioPrologue TEST_INSTRUCTION_COUNT_MMIO, VMMDEV_TESTING_MMIO_NOP +.again: + mov [xBX], al + mov [xBX], al + mov [xBX], al + mov [xBX], al + mov [xBX], al + dec ecx + jnz .again + MmioEpilogue TEST_INSTRUCTION_COUNT_MMIO +.s_szTestName: + db TMPL_MODE_STR, ', 8-bit write', 0 +ENDPROC TMPL_NM(BenchmarkMmioNop8Write) + + +;; +; Benchmarks: MOV eax, [NOP_R3] +; +; @uses nothing +; +BEGINPROC TMPL_NM(BenchmarkMmioRing3Nop32Read) + MmioPrologue TEST_INSTRUCTION_COUNT_MMIO, VMMDEV_TESTING_MMIO_NOP_R3 +.again: + mov eax, [sBX] + mov eax, [sBX] + mov eax, [sBX] + mov eax, [sBX] + mov eax, [sBX] + dec ecx + jnz .again + MmioEpilogue TEST_INSTRUCTION_COUNT_MMIO +.s_szTestName: + db TMPL_MODE_STR, ', 32-bit read-to-ring-3', 0 +ENDPROC TMPL_NM(BenchmarkMmioRing3Nop32Read) + + +;; +; Benchmarks: MOV [NOP_R3], eax +; +; @uses nothing +; +BEGINPROC TMPL_NM(BenchmarkMmioRing3Nop32Write) + MmioPrologue TEST_INSTRUCTION_COUNT_MMIO, VMMDEV_TESTING_MMIO_NOP_R3 +.again: + mov [sBX], eax + mov [sBX], eax + mov [sBX], eax + mov [sBX], eax + mov [sBX], eax + dec ecx + jnz .again + MmioEpilogue TEST_INSTRUCTION_COUNT_MMIO +.s_szTestName: + db TMPL_MODE_STR, ', 32-bit write-to-ring-3', 0 +ENDPROC TMPL_NM(BenchmarkMmioRing3Nop32Write) + + +%undef MmioPrologue +%undef MmioEpilogue + + +;; +; Do the MMIO tests for this mode. +; +; @uses nothing +; +BEGINCODELOW +BITS 16 +BEGINPROC TMPL_NM(BenchmarkMmioNop_rm) + call TMPL_NM(Bs2IsModeSupported_rm) + jz .done + call TMPL_NM(Bs2EnterMode_rm) +BITS TMPL_BITS + + call TMPL_NM(BenchmarkMmioNop32Read) + call TMPL_NM(BenchmarkMmioNop32Write) +%ifndef QUICK_TEST + call TMPL_NM(BenchmarkMmioNop16Read) + call TMPL_NM(BenchmarkMmioNop16Write) + call TMPL_NM(BenchmarkMmioNop8Read) + call TMPL_NM(BenchmarkMmioNop8Write) +%endif + call TMPL_NM(BenchmarkMmioRing3Nop32Read) + call TMPL_NM(BenchmarkMmioRing3Nop32Write) + + call TMPL_NM(Bs2ExitMode) +BITS 16 +.done: + ret +ENDPROC TMPL_NM(BenchmarkMmioNop_rm) + +TMPL_BEGINCODE +BITS TMPL_BITS + + +%include "bootsector2-template-footer.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-test1.asm b/src/VBox/ValidationKit/bootsectors/bootsector2-test1.asm new file mode 100644 index 00000000..107dfa4f --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-test1.asm @@ -0,0 +1,216 @@ +; $Id: bootsector2-test1.asm $ +;; @file +; Bootsector that benchmarks I/O and MMIO roundtrip time. +; VBoxManage setextradata bs-test1 VBoxInternal/Devices/VMMDev/0/Config/TestingEnabled 1 +; VBoxManage setextradata bs-test1 VBoxInternal/Devices/VMMDev/0/Config/TestingMMIO 1 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" +%include "VBox/VMMDevTesting.mac" + +;; The number of instructions to test. +%define TEST_INSTRUCTION_COUNT_IO 2000000 + +;; The number of CPUID instructions to test. +%define TEST_INSTRUCTION_COUNT_CPUID 8000000 + +;; The number of RDTSC instructions to test. +%define TEST_INSTRUCTION_COUNT_RDTSC 4000000 + +;; The number of RDTSC instructions to test. +%define TEST_INSTRUCTION_COUNT_READCR4 1000000 + +;; The number of instructions to test. +%define TEST_INSTRUCTION_COUNT_MMIO 750000 + +;; Define this to drop unnecessary test variations. +%define QUICK_TEST + +; +; Include and execute the init code. +; + %define BS2_INIT_RM + %define BS2_INC_PE16 + %define BS2_INC_PE32 + %define BS2_INC_PP32 + %define BS2_INC_PAE32 + %define BS2_INC_LM64 + %include "bootsector2-common-init-code.mac" + + +; +; The benchmark driver +; +BEGINPROC main + ; + ; Test prologue. + ; + mov ax, .s_szTstName + call TestInit_r86 + call Bs2EnableA20_r86 + call Bs2PanicIfVMMDevTestingIsMissing_r86 + +%if 0 + ; + ; IA32_FLUSH_CMD. + ; + mov ax, .s_szTstFlushCmd + call TestSub_r86 + call BenchmarkFlushCmd_rm_pp32 + call BenchmarkFlushCmd_rm_pae32 + call BenchmarkFlushCmd_rm_lm64 + call BenchmarkFlushCmd_rm_pe16 + call BenchmarkFlushCmd_rm_pe32 + call BenchmarkFlushCmd_rm_rm +%endif + + ; + ; CPUID. + ; + mov ax, .s_szTstCpuId + call TestSub_r86 + call BenchmarkCpuId_rm_pp32 + call BenchmarkCpuId_rm_pae32 + call BenchmarkCpuId_rm_lm64 + call BenchmarkCpuId_rm_pe16 + call BenchmarkCpuId_rm_pe32 + call BenchmarkCpuId_rm_rm + + ; + ; RDTSC. + ; + mov ax, .s_szTstRdTsc + call TestSub_r86 + call BenchmarkRdTsc_rm_pp32 + call BenchmarkRdTsc_rm_pae32 + call BenchmarkRdTsc_rm_lm64 + call BenchmarkRdTsc_rm_pe16 + call BenchmarkRdTsc_rm_pe32 + call BenchmarkRdTsc_rm_rm + + ; + ; Read CR4 + ; + mov ax, .s_szTstRdCr4 + call TestSub_r86 + call BenchmarkRdCr4_rm_pp32 + call BenchmarkRdCr4_rm_pae32 + call BenchmarkRdCr4_rm_lm64 + call BenchmarkRdCr4_rm_pe16 + call BenchmarkRdCr4_rm_pe32 + call BenchmarkRdCr4_rm_rm + + ; + ; I/O port access. + ; + mov ax, .s_szTstNopIoPort + call TestSub_r86 + call BenchmarkIoPortNop_rm_rm + call BenchmarkIoPortNop_rm_pe16 + call BenchmarkIoPortNop_rm_pe32 + call BenchmarkIoPortNop_rm_pp32 + call BenchmarkIoPortNop_rm_pae32 + call BenchmarkIoPortNop_rm_lm64 + + ; + ; MMIO access. + ; + mov ax, .s_szTstNopMmio + call TestSub_r86 + call BenchmarkMmioNop_rm_pp32 + call BenchmarkMmioNop_rm_pae32 + call BenchmarkMmioNop_rm_lm64 + call BenchmarkMmioNop_rm_pe16 + call BenchmarkMmioNop_rm_pe32 + call BenchmarkMmioNop_rm_rm + + ; + ; We're done. + ; + call TestTerm_r86 + call Bs2Panic + +.s_szTstName: + db 'tstIOIntr', 0 +.s_szTstCpuId: + db 'CPUID EAX=1', 0 +.s_szTstFlushCmd: + db 'IA32_FLUSH_CMD', 0 +.s_szTstRdTsc: + db 'RDTSC', 0 +.s_szTstRdCr4: + db 'Read CR4', 0 +.s_szTstNopIoPort: + db 'NOP I/O Port Access', 0 +.s_szTstNopMmio: + db 'NOP MMIO Access', 0 +ENDPROC main + + +; +; Instantiate the template code. +; +%include "bootsector2-template-footer.mac" ; reset the initial environemnt. + +%define TMPL_RM +%include "bootsector2-test1-template.mac" +;%define TMPL_CMN_V86 +;%include "bootsector2-test1-template.mac" +%define TMPL_PE16 +%include "bootsector2-test1-template.mac" +%define TMPL_PE32 +%include "bootsector2-test1-template.mac" +;%define TMPL_PP16 +;%include "bootsector2-test1-template.mac" +%define TMPL_PP32 +%include "bootsector2-test1-template.mac" +;%define TMPL_PAE16 +;%include "bootsector2-test1-template.mac" +%define TMPL_PAE32 +%include "bootsector2-test1-template.mac" +;%define TMPL_LM16 +;%include "bootsector2-test1-template.mac" +;%define TMPL_LM32 +;%include "bootsector2-test1-template.mac" +%define TMPL_LM64 +%include "bootsector2-test1-template.mac" + + +; +; End sections and image. +; +%include "bootsector2-common-end.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-triple-fault-1.asm b/src/VBox/ValidationKit/bootsectors/bootsector2-triple-fault-1.asm new file mode 100644 index 00000000..4be5a8de --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-triple-fault-1.asm @@ -0,0 +1,390 @@ +; $Id: bootsector2-triple-fault-1.asm $ +;; @file +; Bootsector for testing triple faults. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" +%include "VBox/VMMDevTesting.mac" + +;; The number of instructions to test. +%define TEST_INSTRUCTION_COUNT_IO 2000000 + +;; The number of instructions to test. +%define TEST_INSTRUCTION_COUNT_MMIO 750000 + +;; Define this to drop unnecessary test variations. +%define QUICK_TEST + +; +; Include and execute the init code. +; + %define BS2_INIT_RM + %define BS2_INC_PE16 + %define BS2_INC_PE32 + %define BS2_INC_PP32 + %define BS2_INC_PAE32 + %define BS2_INC_LM64 + %include "bootsector2-common-init-code.mac" + + +; +; The test driver +; +BEGINPROC main + ; + ; Test prologue. + ; + mov ax, .s_szTstName + call TestInit_r86 + call Bs2EnableA20_r86 + + ; + ; Did we get here from a reboot triggered below? + ; + push ds + mov ax, 2000h ; 128 KB is enough for the test program +.boot_check_loop: + mov ds, ax + cmp dword [0], 064726962h + jne .boot_check_next + cmp dword [4], 062697264h + je .warm_reset_broken + +.boot_check_next: + mov dword [0], 064726962h + mov dword [4], 062697264h + add ax, 1000h + cmp ax, 8000h + jbe .boot_check_loop + pop ds + jmp .fine + +.warm_reset_broken: + pop ds + mov ax, .s_szWarmResetBroken + call NAME(TestFailed_r86) + jmp .done +.s_szWarmResetBroken: + db 'Warm reset vector support is broken', 0dh, 0ah, 0 +.fine: + + ; + ; Test that the warm reset interface works. + ; + mov ax, .s_szPrecondTest5 + call NAME(TestSub_r86) + mov al, 05h + call NAME(SetWarmResetJmp) + cmp ax, 0 + jne .precond_test_A + call NAME(TestReboot_r86) + +.precond_test_A: + mov ax, .s_szPrecondTestA + call NAME(TestSub_r86) + mov al, 0Ah + call NAME(SetWarmResetJmp) + cmp ax, 0 + jne .precond_test_A_passed + call NAME(TestReboot_r86) +.precond_test_A_passed: + call NAME(TestSubDone_r86) + + ; + ; The real tests. + ; + + + ; + ; We're done. + ; +.done: + call NAME(TestTerm_r86) + call Bs2Panic + +.s_szTstName: + db 'tstTriple', 0 +.s_szPrecondTest5: + db 'Shutdown Action 5', 0 +.s_szPrecondTestA: + db 'Shutdown Action A', 0 +ENDPROC main + + + +;; +; Sets up the warm reset vector. +; +; @param ax Where to resume exeuction. +; @param dl Shutdown action command to use, 5h or Fh. +; +; @uses nothing +; +BEGINPROC SetUpWarmReset + push bp + mov bp, sp + push eax + push ebx + push ecx + push edx + push edi + push esi + push es + + ; + ; Set up the warm reboot vector. + ; + mov bx, 40h + mov es, bx + + mov ecx, [es:67h] ; debug + mov word [es:67h], ax + mov bx, cs + mov word [es:67h+2], bx + + mov bx, [es:72h] ; debug + mov word [es:72h], 1234h ; warm reboot + + wbinvd + + mov al, 0fh + out 70h, al ; set register index + in al, 71h + mov ah, al ; debug + mov al, dl ; shutdown action command + out 71h, al ; set cmos[f] = a - invoke testResume as early as possible. + in al, 71h ; debug / paranoia + movzx si, al + + ; Debug print. +%if 1 + mov di, sp ; save sp (lazy bird) + in al, 64h + push ax ; kbd status + push si ; cmos[f] after + mov al, ah ; cmos[f] before + push ax + push word [0472h] ; 40:72 word after + push bx ; 40:72 word before + push word [0467h] ; 40:67 far addr after + push word [0469h] + push cx ; 40:67 far addr before + shr ecx, 16 + push dx + push ds + push .s_szDebugFmt + call NAME(PrintF_r86) + mov sp, di ; restore sp. +;.forever: +; cli +; hlt +; jmp .forever +%endif + + pop es + pop esi + pop edi + pop edx + pop ecx + pop ebx + pop eax + leave + ret + +.s_szDebugFmt: + db 'dbg: 40:67=%RX16:%RX16 (%RX16:%RX16) 40:72=%RX16 (%RX16) cmos[f]=%RX8 (%RX8) kbdsts=%RX8', 0dh, 0ah, 0 +ENDPROC SetUpWarmReset + + +;; +; Sets up the warm reset vector. +; +; @returns ax = 0 on setup call, ax = 1 on resume return. +; @param al Shutdown action command to use, 5h or Fh. +; @uses ax +; +BEGINPROC SetWarmResetJmp + push bp + mov bp, sp + push dx + + mov dl, al + mov ax, .resume + call NAME(SetUpWarmReset) + +%ifdef DEBUG + push cs + push .s_szDbg1 + call NAME(PrintF_r86) + add sp, 4 +%endif + + mov ax, .s_ResumeRegs + call NAME(TestSaveRegisters_r86) + +%ifdef DEBUG + push cs + push .s_szDbg2 + call NAME(PrintF_r86) + add sp, 4 +%endif + + mov dx, [bp - 2] + mov [.s_ResumeRegs + BS2REGS.rdx], dx + mov ax, bp + add ax, 4 + mov [.s_ResumeRegs + BS2REGS.rsp], ax + mov ax, [bp] + mov [.s_ResumeRegs + BS2REGS.rbp], ax + mov ax, [bp + 2] + mov [.s_ResumeRegs + BS2REGS.rip], ax + mov word [.s_ResumeRegs + BS2REGS.rax], 1 + +%ifdef DEBUG + push cs + push .s_szDbg3 + call NAME(PrintF_r86) + add sp, 4 +%endif + + xor ax, ax +.done: + pop dx + leave + ret + +.resume: + cli + xor ax, ax + mov ds, ax + mov es, ax + mov ax, [.s_ResumeRegs + BS2REGS.ss] + mov ss, ax + mov esp, [.s_ResumeRegs + BS2REGS.rsp] + mov ebp, [.s_ResumeRegs + BS2REGS.rbp] + +%ifdef DEBUG + push ds + push .s_szDbg4 + call NAME(PrintF_r86) + add sp, 4 +%endif + + mov ax, .s_ResumeRegs + call NAME(TestRestoreRegisters_r86) + mov ax, [.s_ResumeRegs + BS2REGS.rip] + push ax + mov ax, 1 + ret + ;jmp word [.s_ResumeRegs + BS2REGS.rip] + +.s_ResumeRegs: + times (BS2REGS_size) db 0 +%ifdef DEBUG +.s_szDbg1: + db 'dbg 1', 0dh, 0ah, 0 +.s_szDbg2: + db 'dbg 2', 0dh, 0ah, 0 +.s_szDbg3: + db 'dbg 3', 0dh, 0ah, 0 +.s_szDbg4: + db 'dbg 4', 0dh, 0ah, 0 +%endif +ENDPROC SetWarmResetJmp + + +;; +; Reboot the machine. Will not return. +; +BEGINPROC TestReboot_r86 +%ifdef DEBUG + ; Debug + push ds + push .s_szDbg + call NAME(PrintF_r86) +%endif + ; Via port A + in al, 92h + and al, ~1 + out 92h, al + or al, 1 + out 92h, al + in al, 92h +.forever: + cli + hlt + jmp .forever +%ifdef DEBUG +.s_szDbg: + db 'Rebooting...', 0dh, 0ah, 0 +%endif +ENDPROC TestReboot_r86 + + +; +; Instantiate the template code. +; +%include "bootsector2-template-footer.mac" ; reset the initial environemnt. + +;%define TMPL_RM +;%include "bootsector2-test1-template.mac" +;%define TMPL_CMN_V86 +;%include "bootsector2-test1-template.mac" +;%define TMPL_PE16 +;%include "bootsector2-test1-template.mac" +;%define TMPL_PE32 +;%include "bootsector2-test1-template.mac" +;%define TMPL_PP16 +;%include "bootsector2-test1-template.mac" +;%define TMPL_PP32 +;%include "bootsector2-test1-template.mac" +;%define TMPL_PAE16 +;%include "bootsector2-test1-template.mac" +;%define TMPL_PAE32 +;%include "bootsector2-test1-template.mac" +;%define TMPL_LM16 +;%include "bootsector2-test1-template.mac" +;%define TMPL_LM32 +;%include "bootsector2-test1-template.mac" +;%define TMPL_LM64 +;%include "bootsector2-test1-template.mac" + + +; +; End sections and image. +; +%include "bootsector2-common-end.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-vbinstst-64-1.asm b/src/VBox/ValidationKit/bootsectors/bootsector2-vbinstst-64-1.asm new file mode 100644 index 00000000..faebef5e --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-vbinstst-64-1.asm @@ -0,0 +1,121 @@ +; $Id: bootsector2-vbinstst-64-1.asm $ +;; @file +; Bootsector tests instructions in 64-bit mode. +; VBoxManage setextradata bs-vbinstst-64-1 VBoxInternal/Devices/VMMDev/0/Config/TestingEnabled 1 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" +%include "VBox/VMMDevTesting.mac" + +; +; Include and execute the init code. +; + %define BS2_INIT_RM + %define BS2_INC_LM64 + %define BS2_WITH_TRAPS + %include "bootsector2-common-init-code.mac" + + +; +; The benchmark driver +; +BEGINPROC main + ; + ; Test prologue. + ; + cli + mov ax, .s_szTstName + call TestInit_r86 + call Bs2EnableA20_r86 + call Bs2PanicIfVMMDevTestingIsMissing_r86 + lea eax, [dword the_end] + cmp eax, dword 80000h + jae .size_nok + + + ; + ; Do the testing. + ; + call Bs2IsModeSupported_rm_lm64 + jz .done + call Bs2EnterMode_rm_lm64 +BITS 64 + + call TestInstrMain_lm64 + + call TestSubDone_p64 + call Bs2ExitMode_lm64 +BITS 16 +.done: + + ; + ; We're done. + ; + call TestTerm_r86 + call Bs2Panic + jmp .done + +.size_nok: + push eax + push word ds + push .s_szSizeNok + call TestFailedF_r86 + jmp .done + +.s_szSizeNok: + db 'Test is too big (%RX32)', 0 +.s_szTstName: + db 'VBInsTst-64-1', 0 +ENDPROC main + + +; +; Instantiate the template code. +; +%include "bootsector2-template-footer.mac" ; reset the initial environemnt. + +%define TMPL_LM64 +%include "bootsector2-template-header.mac" +BITS 64 +%include "VBInsTst-64.asm" +%include "bootsector2-template-footer.mac" + + +; +; End sections and image. +; +%include "bootsector2-common-end.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-vbinstst-big-template.asm b/src/VBox/ValidationKit/bootsectors/bootsector2-vbinstst-big-template.asm new file mode 100644 index 00000000..fa8bcf63 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-vbinstst-big-template.asm @@ -0,0 +1,91 @@ +; $Id: bootsector2-vbinstst-big-template.asm $ +;; @file +; Boot Sector 2 with big instruction test image template. For use with +; bootsector2-vbinstst-kernel.asm. Requires: +; VBoxManage setextradata bs-vbinstst-64-1 VBoxInternal/Devices/VMMDev/0/Config/TestingEnabled 1 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +; +; Set up the assembler environment. +; +%include "bootsector2-first.mac" +%include "bootsector2-api.mac" + +%include "bootsector2-template-footer.mac" +%ifdef BS2_BIG_IMAGE_LM64 + %define TMPL_LM64 + %include "bootsector2-template-header.mac" + BITS 64 + +%elifdef BS2_BIG_IMAGE_PAE32 + %define TMPL_PAE32 + %include "bootsector2-template-header.mac" + BITS 32 + +%elifdef BS2_BIG_IMAGE_PP32 + %define TMPL_PP32 + %include "bootsector2-template-header.mac" + BITS 32 + +%else + %error Do not know which mode to run in. + mov bad,instr +%endif + + + ORG BS2_BIG_LOAD_ADDR + +; +; The entry point is the first byte in the image. +; +bs2_big_image_start: +entrypoint: + mov xAX, .s_szTestName + call [TMPL_NM_CMN(g_pfnTestInit) xWrtRIP] + call TMPL_NM(TestInstrMain) + call [TMPL_NM_CMN(g_pfnTestTerm) xWrtRIP] +.hltloop: + hlt + jmp .hltloop + +.s_szTestName: + db BS2_BIG_IMAGE_GEN_TEST_NAME, 0 + + +; +; Instantiate the template code. +; +%include "BS2_BIG_IMAGE_GEN_SOURCE_FILE" + diff --git a/src/VBox/ValidationKit/bootsectors/bootsector2-vbinstst-kernel.asm b/src/VBox/ValidationKit/bootsectors/bootsector2-vbinstst-kernel.asm new file mode 100644 index 00000000..2df65b59 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bootsector2-vbinstst-kernel.asm @@ -0,0 +1,527 @@ +; $Id: bootsector2-vbinstst-kernel.asm $ +;; @file +; bootsector #2 kernel for big instruction testcases. +; VBoxManage setextradata bs-vbinstst-64-1 VBoxInternal/Devices/VMMDev/0/Config/TestingEnabled 1 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +; +; This is always the first include file. +; +%include "bootsector2-first.mac" + +; +; Include and execute the init code. +; + %define BS2_INIT_RM + %define BS2_WITH_TRAPS + %define BS2_WITHOUT_RAW_MODE ; causes troubles with PIC/floppy. + + %define BS2_INC_RM + %define BS2_INC_PE16 + %define BS2_INC_PE32 + %define BS2_INC_PP16 + %define BS2_INC_PP32 + %define BS2_INC_PAE16 + %define BS2_INC_PAE32 + %define BS2_INC_LM16 + %define BS2_INC_LM32 + %define BS2_INC_LM64 + %include "bootsector2-common-init-code.mac" + %include "bootsector2-api.mac" + %include "iprt/formats/pe.mac" + %include "iprt/formats/mz.mac" + + +BEGINCODE +BEGINPROC main +; +; Set up the runtime environment. +; + call Bs2EnableA20_r86 + + ; 16-bit real mode. +%undef BS2_API_TEMPLATE_ACTION +%define BS2_API_TEMPLATE_ACTION(a_Name) mov dword [NAME(g_pfn %+ a_Name %+ _r86)], dword NAME(a_Name %+ _r86) + BS2_API_TEMPLATE + + ; 16-bit protected mode. +%undef BS2_API_TEMPLATE_ACTION +%define BS2_API_TEMPLATE_ACTION(a_Name) mov word [NAME(g_pfn %+ a_Name %+ _p16)], word NAME(a_Name %+ _p16) + BS2_API_TEMPLATE + mov eax, BS2_SEL_CS16 +%undef BS2_API_TEMPLATE_ACTION +%define BS2_API_TEMPLATE_ACTION(a_Name) mov [NAME(g_pfn %+ a_Name %+ _p16) + 2], ax + BS2_API_TEMPLATE + + ; 32-bit +%undef BS2_API_TEMPLATE_ACTION +%define BS2_API_TEMPLATE_ACTION(a_Name) mov dword [NAME(g_pfn %+ a_Name %+ _p32)], dword NAME(a_Name %+ _p32) + BS2_API_TEMPLATE + + ; 64-bit +%undef BS2_API_TEMPLATE_ACTION +%define BS2_API_TEMPLATE_ACTION(a_Name) mov dword [NAME(g_pfn %+ a_Name %+ _p64)], dword NAME(a_Name %+ _p64) + BS2_API_TEMPLATE + xor eax, eax +%undef BS2_API_TEMPLATE_ACTION +%define BS2_API_TEMPLATE_ACTION(a_Name) mov dword [NAME(g_pfn %+ a_Name %+ _p64) + 4], eax + BS2_API_TEMPLATE + + ; The magic markers and version number. + mov dword [g_u32Bs2ApiMagic], BS2_API_MAGIC + mov dword [g_u32Bs2ApiEndMagic], BS2_API_MAGIC + mov dword [g_u32Bs2ApiVersion], BS2_API_VERSION + +; +; Load the extended image into high memory. +; + mov dl, [g_bBootDrv] + call NAME(bs2LoadBigImage) + +; +; Hand control over to the extended image. +; +%ifdef BS2_BIG_IMAGE_LM64 + call Bs2EnterMode_rm_lm64 +BITS 64 + mov eax, BS2_BIG_LOAD_ADDR + call rax + call Bs2ExitMode_lm64 +BITS 16 + +%elifdef BS2_BIG_IMAGE_PP32 + call Bs2EnterMode_rm_pp32 +BITS 32 + mov eax, BS2_BIG_LOAD_ADDR + call eax + call Bs2ExitMode_pp32 +BITS 16 + +%elifdef BS2_BIG_IMAGE_PAE32 + call Bs2EnterMode_rm_pae32 +BITS 32 + mov eax, BS2_BIG_LOAD_ADDR + call eax + call Bs2ExitMode_pae32 +BITS 16 + +%else + ; + ; Probe the image, looking for an executable format we can deal with. + ; Not doing a lot of checking here, but who cares right now... + ; + call Bs2EnterMode_rm_pp32 +BITS 32 + mov eax, BS2_BIG_LOAD_ADDR + cmp word [eax], IMAGE_DOS_SIGNATURE + jne .not_dos + add eax, [eax + IMAGE_DOS_HEADER.e_lfanew] +.not_dos: + cmp dword [eax], IMAGE_NT_SIGNATURE + je .is_pe + mov eax, BS2_BIG_LOAD_ADDR + jmp .start_32 + +.is_pe: + lea edx, [eax + IMAGE_NT_HEADERS32.FileHeader] + cmp word [edx + IMAGE_FILE_HEADER.Machine], IMAGE_FILE_MACHINE_I386 + je .is_pe32 + cmp word [edx + IMAGE_FILE_HEADER.Machine], IMAGE_FILE_MACHINE_AMD64 + je .is_pe64 + jmp .panic_32 + +.is_pe32: + add edx, IMAGE_FILE_HEADER_size + mov eax, [edx + IMAGE_OPTIONAL_HEADER32.AddressOfEntryPoint] + add eax, BS2_BIG_LOAD_ADDR + jmp .start_32 + +.is_pe64: + add edx, IMAGE_FILE_HEADER_size + mov eax, [edx + IMAGE_OPTIONAL_HEADER64.AddressOfEntryPoint] + add eax, BS2_BIG_LOAD_ADDR + jmp .start_64 + + ; Start executing at eax in 32-bit mode (current). +.start_32: + call eax +.panic_32: + call Bs2ExitMode_pp32 +BITS 16 + jmp .panic + + ; Start executing at eax in 64-bit mode. +BITS 32 +.start_64: + call Bs2ExitMode_pp32 +BITS 16 + call Bs2EnterMode_rm_lm64 +BITS 64 + mov eax, eax + call rax + call Bs2ExitMode_lm64 +BITS 16 + jmp .panic + +.panic: +%endif + call Bs2Panic +ENDPROC main + + + + +;; +; Loads the big image off the floppy. +; +; This uses the the_end label to figure out the starting offset. +; The length is assumed to be the whole floppy. +; +; Clobbers nothing, except for 68KB of memory beyond the_end. +; +; @param dl The boot drive number (from BIOS). +; +BITS 16 +BEGINPROC bs2LoadBigImage + push ebp + movzx ebp, sp + +%define bSavedDiskNo byte [bp - 02h] + push dx +%define bMaxSector byte [bp - 04h] + push 0 +%define bMaxHead byte [bp - 06h] + push 0 +%define bMaxCylinder byte [bp - 08h] + push 0 +%define pbHighDst dword [bp - 0ch] + push dword BS2_BIG_LOAD_ADDR +%define SegTemp word [bp - 0eh] + push 0 +%define fStatus byte [bp - 10h] + push 0 + + push es + push ds + push eax + push edx + push ecx + push ebx + push edi + push esi + push ebp + + ; Display message. + push cs + push .s_szLoadingBigImage + call PrintF_r86 + add sp, 4 + + + ; + ; Try figure the geometry. This defines how much we'll read. + ; + mov ah, 08h + xor di, di ; (es:di = 0000:0000 works around some buggy bioses, says wikipedia.) + mov es, di + int 13h + jc .param_error + mov bMaxSector, cl ; Do the cl[7:6]+ch stuff so we can address 255 sectors on the fake 63MB floppy. + mov bMaxHead, dh + mov bMaxCylinder, ch ; See above. + mov dl, bSavedDiskNo +%if 0 + movzx ax, bMaxCylinder + push ax + movzx cx, bMaxHead + push cx + movzx ax, bMaxSector + push ax + push ds + push .s_szDbgParam + call PrintF_r86 + jmp .dprintf_param_done +.s_szDbgParam: + db 13, 10, 'Floppy params max: sectors=%RX16 heads=%RX16 cylinders=%RX16', 13, 10, 0 +.dprintf_param_done: +%endif + + ; + ; Skip the kernel image (this could be done more efficiently, but this + ; also does the trick). + ; + lea eax, [dword the_end] + sub eax, start + shr eax, 9 ; sectors to skip + mov cx, 0001h ; sector (1-based), cylinder (0-based). + xor dh, dh ; head (0-based). +.skip_one_more: + inc cl + cmp cl, bMaxSector + jbe .decrement_sector_count + + mov cl, 1 + inc dh + cmp dh, bMaxHead ; ASSUMES bMaxHead < 255. + jbe .decrement_sector_count + + mov dh, 0 + inc ch + +.decrement_sector_count: + dec ax + jnz .skip_one_more + + + + ; + ; Load loop. We load and copy 64 KB at the time into the high location. + ; Fixed registers (above): dl=drive, cl[7:6]:ch=cylinder, dh=head, cl[5:0]=sector. + ; + lea eax, [dword the_end + 0ffffh] + and eax, 0ffff0000h + shr eax, 4 + mov SegTemp, ax ; the 64KB segment we use for temporary storage. + +.the_load_loop: + mov al, '.' + call PrintChr_r86 + + ; Fill the segment with int3s (in case we don't read a full 64KB). + mov eax, 0cccccccch + mov di, SegTemp + mov es, di + xor edi, edi + push ecx + cld + mov cx, 4000h + rep stosd + pop ecx + + ; + ; Load a bunch of sectors into the temp segment. + ; + xor ebx, ebx +.the_sector_load_loop: + ; Figure how many sectors we can read without switching track or side. + movzx ax, bMaxSector + sub al, cl + inc al ; al = sectors left to read in the current track on the current side. + mov di, bx + shr di, 9 ; bx/512 = current sector offset. + neg di + add di, 10000h / 512 ; di = sectors left to read in the 64KB buffer. + cmp ax, di ; ax = min(ax, di) + jbe .use_ax_sector_count1 + mov ax, di +.use_ax_sector_count1: + cmp ax, 64 ; ax = min(ax,64) - Our BIOS limitation is 72, play safe. + jbe .use_ax_sector_count2 + mov ax, 64 +.use_ax_sector_count2: + mov di, ax ; save the number of sectors we read + + ; Do the reading. +%if 0 + push bx + push ax + push dx + push cx + push cs + push .s_szDbgRead + call PrintF_r86 + jmp .after_read_dprintf +.s_szDbgRead: db 'Reading CX=%RX16 DX=%RX16 AX=%RX16 BX=%RX16', 13, 10, 0 +.after_read_dprintf: +%endif + push bx + mov ah, 02h ; ah=read function + int 13h + pop bx + jc .read_error + + ; advance to the next sector/head/cylinder and address (lazy impl). +.advance_another_sector: + cmp cl, bMaxSector + je .next_head + inc cl + jmp .adv_addr + +.next_head: + mov cl, 1 + cmp dh, bMaxHead + je .next_cylinder + inc dh + jmp .adv_addr + +.next_cylinder: + mov dh, 0 + cmp ch, bMaxCylinder ; No the cl[7:6]+ch stuff so we can address 255 sectors on the fake 63MB floppy. + jb .update_ch + mov fStatus, 1 + jmp .move_block +.update_ch: + inc ch + +.adv_addr: + add bx, 512 + dec di + jnz .advance_another_sector + + test bx, bx + jnz .the_sector_load_loop + +.move_block: + ; + ; Copy the memory into high mem. + ; +%if 0 + mov edi, pbHighDst + push edi + push cs + push .s_szDbgMove + call PrintF_r86 + jmp .after_move_dprintf +.s_szDbgMove: db 'Moving memory to EDI=%RX32', 13, 10, 0 +.after_move_dprintf: +%endif + + push ecx + push edx + push ds + push es + call Bs2EnterMode_rm_pp32 +BITS 32 + ; Copy + mov edi, pbHighDst + movzx esi, SegTemp + shl esi, 4 + mov ecx, 10000h / 4 + cld + rep movsd + + ; Verify + mov edi, pbHighDst + movzx esi, SegTemp + shl esi, 4 + mov ecx, 10000h / 4 + cld + repe cmpsd + je .mem_verified_ok + mov fStatus, 2 + +.mem_verified_ok: + mov pbHighDst, edi + + call Bs2ExitMode_pp32 +BITS 16 + pop es + pop ds + pop edx + pop ecx + + ; Continue reading and copying? + cmp fStatus, 0 + je .the_load_loop + + ; Do we quit the loop on a failure? + cmp fStatus, 2 + je .verify_failed_msg + + ; + ; Done, so end the current message line. + ; + mov al, 13 + call PrintChr_r86 + mov al, 10 + call PrintChr_r86 + + + pop esi + pop edi + pop ebx + pop ecx + pop edx + pop eax + pop ds + pop es + mov sp, bp + pop ebp + ret + + + ; + ; Something went wrong, display a message. + ; +.verify_failed_msg: + mov edi, pbHighDst + push edi + push cs + push .s_szVerifyFailed + jmp .print_message_and_panic + +.param_error: + push ax + push cs + push .s_szParamError + jmp .print_message_and_panic + +.read_error: + push ax + push cs + push .s_szReadError + jmp .print_message_and_panic + +.print_message_and_panic: + call PrintF_r86 + call Bs2Panic + jmp .print_message_and_panic + +.s_szReadError: + db 13, 10, 'Error reading: %RX8', 13, 10, 0 +.s_szParamError: + db 13, 10, 'Error getting params: %RX8', 13, 10, 0 +.s_szVerifyFailed: + db 13, 10, 'Failed to move block high... (%RX32) Got enough memory configured?', 13, 10, 0 +.s_szLoadingBigImage: + db 'Loading 2nd image.', 0 +ENDPROC bs2LoadBigImage + + +; +; End sections and image. +; +%include "bootsector2-common-end.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-apic-1-32.c32 b/src/VBox/ValidationKit/bootsectors/bs3-apic-1-32.c32 new file mode 100644 index 00000000..b6ac7e7c --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-apic-1-32.c32 @@ -0,0 +1,119 @@ +/* $Id: bs3-apic-1-32.c32 $ */ +/** @file + * BS3Kit - bs3-apic-1, 32-bit C code. + */ + +/* + * Copyright (C) 2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> +#include <iprt/asm-amd64-x86.h> +#include <iprt/x86.h> +#include <VBox/apic.h> + + +static void printBitmap(const char BS3_FAR *pszName, uint32_t BS3_FAR volatile *pau32Bitmap) +{ + unsigned off; + Bs3TestPrintf("%s:", pszName); + for (off = 0; off < 0x80; off += 0x10) + { + uint32_t uVal = pau32Bitmap[off / sizeof(uint32_t)]; + Bs3TestPrintf(off != 0 ? "'%08x" : "%08x", uVal); + } + Bs3TestPrintf("\n"); +} + + +BS3_DECL(void) ProtModeApicTests(void) +{ + uint64_t uApicBase2, uApicBase; + + Bs3TestSub("protected mode"); + uApicBase = ASMRdMsr(MSR_IA32_APICBASE); + + /* Disable the APIC (according to wiki.osdev.org/APIC, disabling the + APIC could require a CPU reset to re-enable it, but it works for us): */ + ASMWrMsr(MSR_IA32_APICBASE, uApicBase & ~(uint64_t)MSR_IA32_APICBASE_EN); + uApicBase2 = ASMRdMsr(MSR_IA32_APICBASE); + if (uApicBase2 == (uApicBase & ~(uint64_t)MSR_IA32_APICBASE_EN)) + Bs3TestPrintf("Disabling worked.\n"); + else + Bs3TestFailedF("Disabling the APIC did not work (%#RX64)", uApicBase2); + + /* Enabling the APIC: */ + ASMWrMsr(MSR_IA32_APICBASE, uApicBase | MSR_IA32_APICBASE_EN); + uApicBase2 = ASMRdMsr(MSR_IA32_APICBASE); + if (uApicBase2 == (uApicBase | MSR_IA32_APICBASE_EN)) + { + uint8_t BS3_FAR volatile * const pabApic = (uint8_t BS3_FAR volatile *)((uintptr_t)uApicBase & X86_PAGE_4K_BASE_MASK); + uint32_t BS3_FAR volatile * const pau32Apic = (uint32_t BS3_FAR volatile *)pabApic; + uint32_t i, uVal, uVal2; + Bs3TestPrintf("Enabling worked.\n"); + + /* + * Do some register reads and such. + */ + uVal = pau32Apic[XAPIC_OFF_VERSION / sizeof(uint32_t)]; + Bs3TestPrintf("APIC version: %#x\n", uVal); + if ( APIC_REG_VERSION_GET_VER(uVal) != XAPIC_HARDWARE_VERSION_P4 + && APIC_REG_VERSION_GET_VER(uVal) != XAPIC_HARDWARE_VERSION_P6) + Bs3TestFailedF("Unexpected APIC version: %#x (%#x)", APIC_REG_VERSION_GET_VER(uVal), uVal); + + Bs3TestPrintf("APIC ID: %#x\n", pau32Apic[XAPIC_OFF_ID / sizeof(uint32_t)]); + + Bs3TestPrintf("TPR: %#x\n", pau32Apic[XAPIC_OFF_TPR / sizeof(uint32_t)]); + for (i = 0; i < 5; i++) + { + Bs3TestPrintf("TPR write test iteration #%u\n", i + 1); + uVal = 256; + while (uVal-- > 0) + { + pau32Apic[XAPIC_OFF_TPR / sizeof(uint32_t)] = uVal; + uVal2 = pau32Apic[XAPIC_OFF_TPR / sizeof(uint32_t)]; + if (uVal2 != uVal) + Bs3TestFailedF("Setting TPR to %#x failed, read back %#x", uVal, uVal2); + } + } + Bs3TestPrintf("APR: %#x\n", pau32Apic[XAPIC_OFF_APR / sizeof(uint32_t)]); + Bs3TestPrintf("PPR: %#x\n", pau32Apic[XAPIC_OFF_PPR / sizeof(uint32_t)]); + printBitmap("ISR", &pau32Apic[XAPIC_OFF_ISR0 / sizeof(uint32_t)]); + printBitmap("TMR", &pau32Apic[XAPIC_OFF_TMR0 / sizeof(uint32_t)]); + printBitmap("IRR", &pau32Apic[XAPIC_OFF_IRR0 / sizeof(uint32_t)]); + } + else + Bs3TestFailedF("Enabling the APIC did not work (%#RX64)", uApicBase2); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-apic-1.c b/src/VBox/ValidationKit/bootsectors/bs3-apic-1.c new file mode 100644 index 00000000..11397e21 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-apic-1.c @@ -0,0 +1,108 @@ +/* $Id: bs3-apic-1.c $ */ +/** @file + * BS3Kit - bs3-apic-1, 16-bit C code. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> +#include <iprt/asm-amd64-x86.h> +#include <iprt/x86.h> + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +BS3_DECL_CALLBACK(void) ProtModeApicTests(void); + + +BS3_DECL(void) Main_rm() +{ + Bs3InitAll_rm(); + Bs3TestInit("bs3-apic-1"); + Bs3TestPrintf("g_uBs3CpuDetected=%#x\n", g_uBs3CpuDetected); + Bs3TestSub("real-mode"); + + /* + * Check that there is an APIC + */ + if (!(g_uBs3CpuDetected & BS3CPU_F_CPUID)) + Bs3TestFailed("CPUID not supported"); + else if (!(ASMCpuId_EDX(1) & X86_CPUID_FEATURE_EDX_MSR)) + Bs3TestFailed("No APIC: RDMSR/WRMSR not supported!"); + else if (!(ASMCpuId_EDX(1) & X86_CPUID_FEATURE_EDX_APIC)) + Bs3TestFailed("No APIC: CPUID(1) does not have EDX_APIC set!\n"); + else + { + uint64_t uApicBase2; + uint64_t uApicBase = ASMRdMsr(MSR_IA32_APICBASE); + Bs3TestPrintf("MSR_IA32_APICBASE=%#RX64 %s, %s cpu%s\n", + uApicBase, + uApicBase & MSR_IA32_APICBASE_EN ? "enabled" : "disabled", + uApicBase & MSR_IA32_APICBASE_BSP ? "bootstrap" : "slave", + uApicBase & MSR_IA32_APICBASE_EXTD ? ", x2apic" : "", + (uApicBase & X86_PAGE_4K_BASE_MASK) == MSR_IA32_APICBASE_ADDR ? ", !non-default address!" : ""); + + /* Disable the APIC (according to wiki.osdev.org/APIC, disabling the + APIC could require a CPU reset to re-enable it, but it works for us): */ + ASMWrMsr(MSR_IA32_APICBASE, uApicBase & ~(uint64_t)MSR_IA32_APICBASE_EN); + uApicBase2 = ASMRdMsr(MSR_IA32_APICBASE); + if (uApicBase2 == (uApicBase & ~(uint64_t)MSR_IA32_APICBASE_EN)) + Bs3TestPrintf("Disabling worked.\n"); + else + Bs3TestFailedF("Disabling the APIC did not work (%#RX64)", uApicBase2); + + /* Enabling the APIC: */ + ASMWrMsr(MSR_IA32_APICBASE, uApicBase | MSR_IA32_APICBASE_EN); + uApicBase2 = ASMRdMsr(MSR_IA32_APICBASE); + if (uApicBase2 == (uApicBase | MSR_IA32_APICBASE_EN)) + { + Bs3TestPrintf("Enabling worked.\n"); + + /* + * Do the rest of the testing in protected mode since we cannot + * (easily) access the APIC address from real mode. + */ + Bs3SwitchTo32BitAndCallC_rm(ProtModeApicTests, 0); + } + else + Bs3TestFailedF("Enabling the APIC did not work (%#RX64)", uApicBase2); + } + + Bs3TestTerm(); + Bs3Shutdown(); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2-32.c32 b/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2-32.c32 new file mode 100644 index 00000000..b06d0047 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2-32.c32 @@ -0,0 +1,66 @@ +/* $Id: bs3-cpu-basic-2-32.c32 $ */ +/** @file + * BS3Kit - bs3-cpu-basic-2, 32-bit C code. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> +#include <iprt/asm-amd64-x86.h> + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +FNBS3TESTDOMODE bs3CpuBasic2_RaiseXcpt0e_c32; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static const BS3TESTMODEBYONEENTRY g_aModeByOne32Tests[] = +{ + { "#PF", bs3CpuBasic2_RaiseXcpt0e_c32, BS3TESTMODEBYONEENTRY_F_ONLY_PAGING }, +}; + + +BS3_DECL(void) bs3CpuBasic2_Do32BitTests_pe32(void) +{ + Bs3TestPrintf("bs3CpuBasic2_Do32BitTests=%#x\n", g_uBs3CpuDetected); + + Bs3TestDoModesByOne_pe32(g_aModeByOne32Tests, RT_ELEMENTS(g_aModeByOne32Tests), 0); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2-asm.asm b/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2-asm.asm new file mode 100644 index 00000000..6c04b3cd --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2-asm.asm @@ -0,0 +1,283 @@ +; $Id: bs3-cpu-basic-2-asm.asm $ +;; @file +; BS3Kit - bs3-cpu-basic-2 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit.mac" + + +;********************************************************************************************************************************* +;* Global Variables * +;********************************************************************************************************************************* +BS3_BEGIN_DATA16 +BS3_GLOBAL_DATA g_bs3CpuBasic2_ud2_FlatAddr, 4 + dd _bs3CpuBasic2_ud2 wrt FLAT + + + +; +; CPU mode agnostic test code snippets. +; +BS3_BEGIN_TEXT16 + +BS3_PROC_BEGIN _bs3CpuBasic2_ud2 +.again: + ud2 + jmp .again +BS3_PROC_END _bs3CpuBasic2_ud2 + + +BS3_PROC_BEGIN _bs3CpuBasic2_salc_ud2 + salc ; #UD in 64-bit mode +.again: + ud2 + jmp .again +BS3_PROC_END _bs3CpuBasic2_salc_ud2 + +BS3_PROC_BEGIN _bs3CpuBasic2_swapgs +.again: + db 00fh, 001h, 0f8h ; swapgs - #UD when not in 64-bit mode. + jmp .again +BS3_PROC_END _bs3CpuBasic2_swapgs + + +BS3_PROC_BEGIN _bs3CpuBasic2_Int80 + int 80h +.again: ud2 + jmp .again +BS3_PROC_END _bs3CpuBasic2_Int80 + + +BS3_PROC_BEGIN _bs3CpuBasic2_Int81 + int 81h +.again: ud2 + jmp .again +BS3_PROC_END _bs3CpuBasic2_Int81 + + +BS3_PROC_BEGIN _bs3CpuBasic2_Int82 + int 82h +.again: ud2 + jmp .again +BS3_PROC_END _bs3CpuBasic2_Int82 + + +BS3_PROC_BEGIN _bs3CpuBasic2_Int83 + int 83h +.again: ud2 + jmp .again +BS3_PROC_END _bs3CpuBasic2_Int83 + + +BS3_PROC_BEGIN _bs3CpuBasic2_iret + iret +BS3_PROC_END _bs3CpuBasic2_iret +AssertCompile(_bs3CpuBasic2_iret_EndProc - _bs3CpuBasic2_iret == 1) + + +BS3_PROC_BEGIN _bs3CpuBasic2_iret_opsize + iretd +BS3_PROC_END _bs3CpuBasic2_iret_opsize +AssertCompile(_bs3CpuBasic2_iret_opsize_EndProc - _bs3CpuBasic2_iret_opsize == 2) + + +BS3_PROC_BEGIN _bs3CpuBasic2_iret_rexw + BS3_SET_BITS 64 + iretq + BS3_SET_BITS 16 +BS3_PROC_END _bs3CpuBasic2_iret_rexw +AssertCompile(_bs3CpuBasic2_iret_rexw_EndProc - _bs3CpuBasic2_iret_rexw == 2) + + +; +; CPU mode agnostic test code snippets. +; +BS3_BEGIN_TEXT32 + +;; +; @param [xBP + xCB*2] puDst +; @param [xBP + xCB*3] uNewValue +BS3_PROC_BEGIN_CMN bs3CpuBasic2_Store_mov, BS3_PBC_NEAR + push xBP + mov xBP, xSP + mov xCX, [xBP + xCB*2] + mov xAX, [xBP + xCB*3] + mov [xCX], xAX + leave + ret +BS3_PROC_END_CMN bs3CpuBasic2_Store_mov + +;; +; @param [xBP + xCB*2] puDst +; @param [xBP + xCB*3] uNewValue +BS3_PROC_BEGIN_CMN bs3CpuBasic2_Store_xchg, BS3_PBC_NEAR + push xBP + mov xBP, xSP + mov xCX, [xBP + xCB*2] + mov xAX, [xBP + xCB*3] + xchg [xCX], xAX + leave + ret +BS3_PROC_END_CMN bs3CpuBasic2_Store_xchg + +;; +; @param [xBP + xCB*2] puDst +; @param [xBP + xCB*3] uNewValue +; @param [xBP + xCB*4] uOldValue +BS3_PROC_BEGIN_CMN bs3CpuBasic2_Store_cmpxchg, BS3_PBC_NEAR + push xBP + mov xBP, xSP + mov xCX, [xBP + xCB*2] + mov xDX, [xBP + xCB*3] + mov xAX, [xBP + xCB*4] +.again: + cmpxchg [xCX], xDX + jnz .again + leave + ret +BS3_PROC_END_CMN bs3CpuBasic2_Store_cmpxchg + + +; +; Jump code segment 64KB. +; +; There is no ORG directive in OMF mode of course. :-( +; +section BS3JMPTEXT16 align=16 CLASS=BS3CLASS16JMPCODE PRIVATE USE16 + GROUP BS3GROUPJMPTEXT16 BS3JMPTEXT16 + BS3_SET_BITS 16 + +; 0000: Start with two int3 filler instructions. +BS3_GLOBAL_NAME_EX NAME(bs3CpuBasic2_jmptext16_start), function, 2 + int3 + int3 + +; 0002: This is the target for forward wrap around jumps, should they succeed. +BS3_GLOBAL_NAME_EX NAME(bs3CpuBasic2_jmp_target_wrap_forward), function, 2 + ud2 + align 8, int3 + +; 0008 +BS3_GLOBAL_NAME_EX NAME(bs3CpuBasic2_jmp_jb_wrap_backward__ud2), function, 2 + db 0ebh, -012h ; jmp (0x0008 + 2 - 0x12 = 0xFFFFFFF8 (-8)) + int3 + +; 000b +BS3_GLOBAL_NAME_EX NAME(bs3CpuBasic2_jmp_jb_opsize_wrap_backward__ud2), function, 3 + db 066h, 0ebh, -016h ; jmp (0x000b + 3 - 0x16 = 0xFFFFFFF8 (-8)) + int3 + + align 0x80, int3 +; 0080 +BS3_GLOBAL_NAME_EX NAME(bs3CpuBasic2_jmp_jv16_wrap_backward__ud2), function, 3 + db 0e9h ; jmp (0x0080 + 3 - 0x8b = 0xFFFFFFF8 (-8)) + dw -08bh + int3 + +; 0084 +BS3_GLOBAL_NAME_EX NAME(bs3CpuBasic2_jmp_jv16_opsize_wrap_backward__ud2), function, 6 + db 066h, 0e9h ; jmp (0x0084 + 6 - 0x92 = 0xFFFFFFF8 (-8)) + dd -092h + int3 + +; 008b +BS3_GLOBAL_NAME_EX NAME(bs3CpuBasic2_call_jv16_wrap_backward__ud2), function, 3 + db 0e8h ; call (0x008b + 3 - 0x96) + dw -096h + int3 + +; 008f +BS3_GLOBAL_NAME_EX NAME(bs3CpuBasic2_call_jv16_opsize_wrap_backward__ud2), function, 6 + db 066h, 0e8h ; call (0x008f + 6 - 0x9d = 0xFFFFFFF8 (-8)) + dd -09dh + int3 + + + align 0x100, int3 ; Note! Doesn't work correctly for higher values. + times (0xff6b - 0x100) int3 + +; ff6b +BS3_GLOBAL_NAME_EX NAME(bs3CpuBasic2_call_jv16_wrap_forward__ud2), function, 4 + db 0e8h ; call (0xff6b+3 + 0x94 = 0x10002 (65538)) + dw 094h + int3 + +; ff6f +BS3_GLOBAL_NAME_EX NAME(bs3CpuBasic2_call_jv16_opsize_wrap_forward__ud2), function, 7 + db 066h, 0e8h ; o32 call (0xff6f+6 + 0x8d = 0x10002 (65538)) + dd 08dh + int3 + +; ff76 +BS3_GLOBAL_NAME_EX NAME(bs3CpuBasic2_jmp_jv16_wrap_forward__ud2), function, 5 + db 0e9h ; jmp (0xff76+4 + 0x88 = 0x10002 (65538)) + dw 089h + int3 + +; ff7a +BS3_GLOBAL_NAME_EX NAME(bs3CpuBasic2_jmp_jv16_opsize_wrap_forward__ud2), function, 7 + db 066h, 0e9h ; o32 jmp (0xff7a+6 + 0x82 = 0x10002 (65538)) + dd 082h + int3 + +; ff81 +BS3_GLOBAL_NAME_EX NAME(bs3CpuBasic2_jmp_jb_wrap_forward__ud2), function, 2 + db 0ebh, 07fh ; jmp (0xff81+2 + 0x7f = 0x10002 (65538)) + int3 + +; ff84 +BS3_GLOBAL_NAME_EX NAME(bs3CpuBasic2_jmp_jb_opsize_wrap_forward__ud2), function, 3 + db 066h, 0ebh, 07bh ; o32 jmp (0xff84+3 + 0x7b = 0x10002 (65538)) +; ff87 + + times (0xfff8 - 0xff87) int3 + +; fff8: This is the target for backward wrap around jumps, should they succeed. +BS3_GLOBAL_NAME_EX NAME(bs3CpuBasic2_jmp_target_wrap_backward), function, 2 + ud2 + times 6 int3 +; End of segment. + +BS3_BEGIN_TEXT16 + +; +; Instantiate code templates. +; +BS3_INSTANTIATE_COMMON_TEMPLATE "bs3-cpu-basic-2-template.mac" +BS3_INSTANTIATE_TEMPLATE_WITH_WEIRD_ONES "bs3-cpu-basic-2-template.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2-pf.c32 b/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2-pf.c32 new file mode 100644 index 00000000..b5ed910f --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2-pf.c32 @@ -0,0 +1,1890 @@ +/* $Id: bs3-cpu-basic-2-pf.c32 $ */ +/** @file + * BS3Kit - bs3-cpu-basic-2, 32-bit C code for testing \#PF. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> +#include <iprt/asm-amd64-x86.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define CHECK_MEMBER(a_pszMode, a_szName, a_szFmt, a_Actual, a_Expected) \ + do { \ + if ((a_Actual) == (a_Expected)) { /* likely */ } \ + else Bs3TestFailedF("%u - %s: " a_szName "=" a_szFmt " expected " a_szFmt, \ + g_usBs3TestStep, (a_pszMode), (a_Actual), (a_Expected)); \ + } while (0) + +#define BS3CPUBASIC2PF_HALT(pThis) \ + do { \ + Bs3TestPrintf("Halting: pteworker=%s store=%s accessor=%s\n", \ + pThis->pszPteWorker, pThis->pszStore, pThis->pszAccessor); \ + ASMHalt(); \ + } while (0) + + +/** @def BS3CPUBASIC2PF_FASTER + * This is useful for IEM execution. */ +#define BS3CPUBASIC2PF_FASTER + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef void BS3_CALL FNBS3CPUBASIC2PFSNIPPET(void); + +typedef struct FNBS3CPUBASIC2PFTSTCODE +{ + FNBS3CPUBASIC2PFSNIPPET *pfn; + uint8_t offUd2; + +} FNBS3CPUBASIC2PFTSTCODE; +typedef FNBS3CPUBASIC2PFTSTCODE const *PCFNBS3CPUBASIC2PFTSTCODE; + +typedef struct BS3CPUBASIC2PFTTSTCMNMODE +{ + uint8_t bMode; + FNBS3CPUBASIC2PFTSTCODE MovLoad; + FNBS3CPUBASIC2PFTSTCODE MovStore; + FNBS3CPUBASIC2PFTSTCODE Xchg; + FNBS3CPUBASIC2PFTSTCODE CmpXchg; + FNBS3CPUBASIC2PFTSTCODE DivMem; +} BS3CPUBASIC2PFTTSTCMNMODE; +typedef BS3CPUBASIC2PFTTSTCMNMODE const *PCBS3CPUBASIC2PFTTSTCMNMODE; + + +typedef struct BS3CPUBASIC2PFSTATE +{ + /** The mode we're currently testing. */ + uint8_t bMode; + /** The size of a natural access. */ + uint8_t cbAccess; + /** The common mode functions. */ + PCBS3CPUBASIC2PFTTSTCMNMODE pCmnMode; + /** Address of the test area (alias). */ + union + { + uint64_t u; + uint32_t u32; + uint16_t u16; + } uTestAddr; + /** Pointer to the orignal test area mapping. */ + uint8_t *pbOrgTest; + /** The size of the test area (at least two pages). */ + uint32_t cbTest; + /** cbTest expressed as a page count. */ + uint16_t cTestPages; + /** The number of PTEs in the first PTE, i.e. what we can + * safely access via PgInfo.u.Pae.pPte/PgInfo.u.Legacy.pPte. */ + uint16_t cTest1stPtes; + /** The number of PDEs for cTestPages. */ + uint16_t cTestPdes; + /** 16-bit data selector for uTestAddr.u32. */ + uint16_t uSel16TestData; + /** 16-bit code selector for uTestAddr.u32. */ + uint16_t uSel16TestCode; + /** The size of the PDE backup. */ + uint16_t cbPdeBackup; + /** The size of the PTE backup. */ + uint16_t cbPteBackup; + /** Test paging information for uTestAddr.u. */ + BS3PAGINGINFO4ADDR PgInfo; + + /** Set if we can use the INVLPG instruction. */ + bool fUseInvlPg; + /** Physical addressing width. */ + uint8_t cBitsPhysWidth; + + /** Reflects CR0.WP. */ + bool fWp; + /** Reflects EFER.NXE & CR4.PAE. */ + bool fNxe; + + const char *pszAccessor; + const char *pszPteWorker; + const char *pszStore; + + /** Trap context frame. */ + BS3TRAPFRAME TrapCtx; + /** Expected result context. */ + BS3REGCTX ExpectCtx; + + /** The PML4E backup. */ + uint64_t u64Pml4eBackup; + /** The PDPTE backup. */ + uint64_t u64PdpteBackup; + /** The PDE backup. */ + uint64_t au64PdeBackup[16]; + /** The PTE backup. */ + union + { + uint32_t Legacy[X86_PG_ENTRIES]; + uint64_t Pae[X86_PG_PAE_ENTRIES]; + } PteBackup; + +} BS3CPUBASIC2PFSTATE; +/** Pointer to state for the \#PF test. */ +typedef BS3CPUBASIC2PFSTATE *PBS3CPUBASIC2PFSTATE; + + +/** + * Paging modification worker. + */ +typedef struct BS3CPUBASIC2PFMODPT +{ + const char *pszName; + uint32_t fPresent : 1; + uint32_t fUser : 1; + uint32_t fWriteable : 1; + uint32_t fNoExecute : 1; + uint32_t fReserved : 1; + uint32_t uModifyArg : 24; + void (*pfnModify)(PBS3CPUBASIC2PFSTATE pThis, unsigned iStore, struct BS3CPUBASIC2PFMODPT const *pEntry, + uint32_t fClearMask, uint32_t fSetMask); + bool (*pfnApplicable)(PBS3CPUBASIC2PFSTATE pThis, struct BS3CPUBASIC2PFMODPT const *pEntry); +} BS3CPUBASIC2PFMODPT; +typedef BS3CPUBASIC2PFMODPT const *PCBS3CPUBASIC2PFMODPT; + +/** Page level protection. Alternative is page directory or higher level. */ +#define BS3CB2PFACC_F_PAGE_LEVEL RT_BIT(0) +/** Directly access the boobytrapped page, no edging on or off it. */ +#define BS3CB2PFACC_F_DIRECT RT_BIT(1) + +/** + * Memory accessor. + */ +typedef struct BS3CPUBASIC2PFACCESSOR +{ + /** Accessor name. */ + const char *pszName; + /** The accessor. */ + void (*pfnAccessor)(PBS3CPUBASIC2PFSTATE pThis, PBS3REGCTX pCtx, uint32_t fFlags, uint8_t bXcpt, uint8_t uPfErrCd); + /** The X86_TRAP_PF_XXX access flags this access sets. */ + uint32_t fAccess; + /** The exception when things are fine. */ + uint8_t bOkayXcpt; +} BS3CPUBASIC2PFACCESSOR; +typedef const BS3CPUBASIC2PFACCESSOR *PCBS3CPUBASIC2PFACCESSOR; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +FNBS3TESTDOMODE bs3CpuBasic2_RaiseXcpt0e_c32; + +/* bs3-cpu-basic-2-asm.asm: */ +void BS3_CALL bs3CpuBasic2_Store_mov_c32(void *pvDst, uint32_t uValue, uint32_t uOld); +void BS3_CALL bs3CpuBasic2_Store_xchg_c32(void *pvDst, uint32_t uValue, uint32_t uOld); +void BS3_CALL bs3CpuBasic2_Store_cmpxchg_c32(void *pvDst, uint32_t uValue, uint32_t uOld); + + +/* bs3-cpu-basic-2-template.mac: */ +FNBS3CPUBASIC2PFSNIPPET bs3CpuBasic2_mov_ax_ds_bx__ud2_c16; +FNBS3CPUBASIC2PFSNIPPET bs3CpuBasic2_mov_ds_bx_ax__ud2_c16; +FNBS3CPUBASIC2PFSNIPPET bs3CpuBasic2_xchg_ds_bx_ax__ud2_c16; +FNBS3CPUBASIC2PFSNIPPET bs3CpuBasic2_cmpxchg_ds_bx_cx__ud2_c16; +FNBS3CPUBASIC2PFSNIPPET bs3CpuBasic2_div_ds_bx__ud2_c16; + +FNBS3CPUBASIC2PFSNIPPET bs3CpuBasic2_mov_ax_ds_bx__ud2_c32; +FNBS3CPUBASIC2PFSNIPPET bs3CpuBasic2_mov_ds_bx_ax__ud2_c32; +FNBS3CPUBASIC2PFSNIPPET bs3CpuBasic2_xchg_ds_bx_ax__ud2_c32; +FNBS3CPUBASIC2PFSNIPPET bs3CpuBasic2_cmpxchg_ds_bx_cx__ud2_c32; +FNBS3CPUBASIC2PFSNIPPET bs3CpuBasic2_div_ds_bx__ud2_c32; + +FNBS3CPUBASIC2PFSNIPPET bs3CpuBasic2_mov_ax_ds_bx__ud2_c64; +FNBS3CPUBASIC2PFSNIPPET bs3CpuBasic2_mov_ds_bx_ax__ud2_c64; +FNBS3CPUBASIC2PFSNIPPET bs3CpuBasic2_xchg_ds_bx_ax__ud2_c64; +FNBS3CPUBASIC2PFSNIPPET bs3CpuBasic2_cmpxchg_ds_bx_cx__ud2_c64; +FNBS3CPUBASIC2PFSNIPPET bs3CpuBasic2_div_ds_bx__ud2_c64; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Page table access functions. */ +static const struct +{ + const char *pszName; + void (BS3_CALL *pfnStore)(void *pvDst, uint32_t uValue, uint32_t uOld); +} g_aStoreMethods[] = +{ + { "mov", bs3CpuBasic2_Store_mov_c32 }, + { "xchg", bs3CpuBasic2_Store_xchg_c32 }, + { "cmpxchg", bs3CpuBasic2_Store_cmpxchg_c32 }, +}; + + +static const BS3CPUBASIC2PFTTSTCMNMODE g_aCmnModes[] = +{ + { + BS3_MODE_CODE_16, + { bs3CpuBasic2_mov_ax_ds_bx__ud2_c16, 2 }, + { bs3CpuBasic2_mov_ds_bx_ax__ud2_c16, 2 }, + { bs3CpuBasic2_xchg_ds_bx_ax__ud2_c16, 2 }, + { bs3CpuBasic2_cmpxchg_ds_bx_cx__ud2_c16, 3 }, + { bs3CpuBasic2_div_ds_bx__ud2_c16, 2 }, + }, + { + BS3_MODE_CODE_32, + { bs3CpuBasic2_mov_ax_ds_bx__ud2_c32, 2 }, + { bs3CpuBasic2_mov_ds_bx_ax__ud2_c32, 2 }, + { bs3CpuBasic2_xchg_ds_bx_ax__ud2_c32, 2 }, + { bs3CpuBasic2_cmpxchg_ds_bx_cx__ud2_c32, 3 }, + { bs3CpuBasic2_div_ds_bx__ud2_c32, 2 }, + }, + { + BS3_MODE_CODE_64, + { bs3CpuBasic2_mov_ax_ds_bx__ud2_c64, 2 + 1 }, + { bs3CpuBasic2_mov_ds_bx_ax__ud2_c64, 2 + 1 }, + { bs3CpuBasic2_xchg_ds_bx_ax__ud2_c64, 2 + 1 }, + { bs3CpuBasic2_cmpxchg_ds_bx_cx__ud2_c64, 3 + 1 }, + { bs3CpuBasic2_div_ds_bx__ud2_c64, 2 + 1 }, + }, + { + BS3_MODE_CODE_V86, + { bs3CpuBasic2_mov_ax_ds_bx__ud2_c16, 2 }, + { bs3CpuBasic2_mov_ds_bx_ax__ud2_c16, 2 }, + { bs3CpuBasic2_xchg_ds_bx_ax__ud2_c16, 2 }, + { bs3CpuBasic2_cmpxchg_ds_bx_cx__ud2_c16, 3 }, + { bs3CpuBasic2_div_ds_bx__ud2_c16, 2 }, + }, +}; + + +/** + * Compares a CPU trap. + */ +static void bs3CpuBasic2Pf_CompareCtx(PBS3CPUBASIC2PFSTATE pThis, PBS3REGCTX pExpectCtx, int cbPcAdjust, + uint8_t bXcpt, unsigned uErrCd) +{ + const char *pszHint = "xxxx"; + uint16_t const cErrorsBefore = Bs3TestSubErrorCount(); + uint32_t fExtraEfl; + + CHECK_MEMBER(pszHint, "bXcpt", "%#04x", pThis->TrapCtx.bXcpt, bXcpt); + CHECK_MEMBER(pszHint, "uErrCd", "%#06RX16", (uint16_t)pThis->TrapCtx.uErrCd, (uint16_t)uErrCd); /* 486 only writes a word */ + + fExtraEfl = X86_EFL_RF; + if (BS3_MODE_IS_16BIT_SYS(g_bBs3CurrentMode)) + fExtraEfl = 0; + else + fExtraEfl = X86_EFL_RF; + Bs3TestCheckRegCtxEx(&pThis->TrapCtx.Ctx, pExpectCtx, cbPcAdjust, 0 /*cbSpAdjust*/, fExtraEfl, pszHint, g_usBs3TestStep); + if (Bs3TestSubErrorCount() != cErrorsBefore) + { + Bs3TrapPrintFrame(&pThis->TrapCtx); +#if 1 + Bs3TestPrintf("Halting: g_uBs3CpuDetected=%#x\n", g_uBs3CpuDetected); + Bs3TestPrintf("Halting: bXcpt=%#x uErrCd=%#x\n", bXcpt, uErrCd); + BS3CPUBASIC2PF_HALT(pThis); +#endif + } +} + + +/** + * Compares a CPU trap. + */ +static void bs3CpuBasic2Pf_CompareSimpleCtx(PBS3CPUBASIC2PFSTATE pThis, PBS3REGCTX pStartCtx, int offAddPC, + uint8_t bXcpt, unsigned uErrCd, uint64_t uCr2) +{ + const char *pszHint = "xxxx"; + uint16_t const cErrorsBefore = Bs3TestSubErrorCount(); + uint64_t const uSavedCr2 = pStartCtx->cr2.u; + uint32_t fExtraEfl; + + CHECK_MEMBER(pszHint, "bXcpt", "%#04x", pThis->TrapCtx.bXcpt, bXcpt); + CHECK_MEMBER(pszHint, "uErrCd", "%#06RX16", (uint16_t)pThis->TrapCtx.uErrCd, (uint16_t)uErrCd); /* 486 only writes a word */ + + fExtraEfl = X86_EFL_RF; + if (BS3_MODE_IS_16BIT_SYS(g_bBs3CurrentMode)) + fExtraEfl = 0; + else + fExtraEfl = X86_EFL_RF; + pStartCtx->cr2.u = uCr2; + Bs3TestCheckRegCtxEx(&pThis->TrapCtx.Ctx, pStartCtx, offAddPC, 0 /*cbSpAdjust*/, fExtraEfl, pszHint, g_usBs3TestStep); + pStartCtx->cr2.u = uSavedCr2; + if (Bs3TestSubErrorCount() != cErrorsBefore) + { + Bs3TrapPrintFrame(&pThis->TrapCtx); +#if 1 + Bs3TestPrintf("Halting: g_uBs3CpuDetected=%#x\n", g_uBs3CpuDetected); + Bs3TestPrintf("Halting: bXcpt=%#x uErrCd=%#x\n", bXcpt, uErrCd); + BS3CPUBASIC2PF_HALT(pThis); +#endif + } +} + + +/** + * Checks the trap context for a simple \#PF trap. + */ +static void bs3CpuBasic2Pf_CompareSimplePf(PBS3CPUBASIC2PFSTATE pThis, PCBS3REGCTX pStartCtx, int offAddPC, + unsigned uErrCd, uint64_t uCr2) +{ + bs3CpuBasic2Pf_CompareSimpleCtx(pThis, (PBS3REGCTX)pStartCtx, offAddPC, X86_XCPT_PF, uErrCd, uCr2); +} + +/** + * Checks the trap context for a simple \#UD trap. + */ +static void bs3CpuBasic2Pf_CompareSimpleUd(PBS3CPUBASIC2PFSTATE pThis, PCBS3REGCTX pStartCtx, int offAddPC) +{ + bs3CpuBasic2Pf_CompareSimpleCtx(pThis, (PBS3REGCTX)pStartCtx, offAddPC, X86_XCPT_UD, 0, pStartCtx->cr2.u); +} + + +/** + * Restores all the paging entries from backup and flushes everything. + */ +static void bs3CpuBasic2Pf_FlushAll(void) +{ + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80486) + { + uint32_t uCr4 = ASMGetCR4(); + if (uCr4 & (X86_CR4_PGE | X86_CR4_PCIDE)) + { + ASMSetCR4(uCr4 & ~(X86_CR4_PGE | X86_CR4_PCIDE)); + ASMSetCR4(uCr4); + return; + } + } + + ASMReloadCR3(); +} + + +/** + * Restores all the paging entries from backup and flushes everything. + * + * @param pThis Test state data. + */ +static void bs3CpuBasic2Pf_RestoreFromBackups(PBS3CPUBASIC2PFSTATE pThis) +{ + Bs3MemCpy(pThis->PgInfo.u.Legacy.pPte, &pThis->PteBackup, pThis->cbPteBackup); + Bs3MemCpy(pThis->PgInfo.u.Legacy.pPde, pThis->au64PdeBackup, pThis->cbPdeBackup); + if (pThis->PgInfo.cEntries > 2) + pThis->PgInfo.u.Pae.pPdpe->u = pThis->u64PdpteBackup; + if (pThis->PgInfo.cEntries > 3) + pThis->PgInfo.u.Pae.pPml4e->u = pThis->u64Pml4eBackup; + bs3CpuBasic2Pf_FlushAll(); +} + + +/** @name BS3CPUBASIC2PFACCESSOR::pfnAccessor Implementations + * @{ */ + +static void bs3CpuBasic2Pf_DoExec(PBS3CPUBASIC2PFSTATE pThis, PBS3REGCTX pCtx, uint32_t fFlags, uint8_t bXcpt, uint8_t uPfErrCd) +{ + uint8_t *pbOrgTest = pThis->pbOrgTest; + unsigned offEnd = fFlags & BS3CB2PFACC_F_DIRECT ? X86_PAGE_SIZE + 1 : X86_PAGE_SIZE + 2; + unsigned off = fFlags & BS3CB2PFACC_F_DIRECT ? offEnd - 1 : X86_PAGE_SIZE - 5; + + for (; off < offEnd; off++) + { + /* Emit a little bit of code (using the original allocation mapping) and point pCtx to it. */ + pbOrgTest[off + 0] = X86_OP_PRF_SIZE_ADDR; + pbOrgTest[off + 1] = X86_OP_PRF_SIZE_OP; + pbOrgTest[off + 2] = 0x90; /* NOP */ + pbOrgTest[off + 3] = 0x0f; /* UD2 */ + pbOrgTest[off + 4] = 0x0b; + pbOrgTest[off + 5] = 0xeb; /* JMP $-4 */ + pbOrgTest[off + 6] = 0xfc; + switch (pThis->bMode & BS3_MODE_CODE_MASK) + { + default: + pCtx->rip.u = pThis->uTestAddr.u + off; + break; + case BS3_MODE_CODE_16: + Bs3SelSetup16BitCode(&Bs3GdteSpare01, pThis->uTestAddr.u32, pCtx->bCpl); + pCtx->rip.u = off; + pCtx->cs = BS3_SEL_SPARE_01 | pCtx->bCpl; + break; + case BS3_MODE_CODE_V86: + /** @todo fix me. */ + return; + } + //Bs3TestPrintf("cs:rip=%04x:%010RX64 iRing=%d\n", pCtx->cs, pCtx->rip.u, pCtx->bCpl); + + Bs3TrapSetJmpAndRestore(pCtx, &pThis->TrapCtx); + //Bs3TestPrintf("off=%#06x bXcpt=%#x uErrCd=%#RX64\n", off, pThis->TrapCtx.bXcpt, pThis->TrapCtx.uErrCd); + if ( bXcpt != X86_XCPT_PF + || ((fFlags & BS3CB2PFACC_F_PAGE_LEVEL) && off < X86_PAGE_SIZE - 4)) + bs3CpuBasic2Pf_CompareSimpleUd(pThis, pCtx, 3); + else if (!(fFlags & BS3CB2PFACC_F_PAGE_LEVEL) || off >= X86_PAGE_SIZE) + bs3CpuBasic2Pf_CompareSimplePf(pThis, pCtx, 0, uPfErrCd, pThis->uTestAddr.u + off); + else + bs3CpuBasic2Pf_CompareSimplePf(pThis, pCtx, + off + 3 == X86_PAGE_SIZE || off + 4 == X86_PAGE_SIZE + ? RT_MIN(X86_PAGE_SIZE, off + 3) - off : 0, + uPfErrCd, pThis->uTestAddr.u + RT_MIN(X86_PAGE_SIZE, off + 4)); + } +} + + +static void bs3CpuBasic2Pf_SetCsEip(PBS3CPUBASIC2PFSTATE pThis, PBS3REGCTX pCtx, PCFNBS3CPUBASIC2PFTSTCODE pCode) +{ + switch (pThis->bMode & BS3_MODE_CODE_MASK) + { + default: + pCtx->rip.u = (uintptr_t)pCode->pfn; + break; + + case BS3_MODE_CODE_16: + { + uint32_t uFar16 = Bs3SelFlatCodeToProtFar16((uintptr_t)pCode->pfn); + pCtx->rip.u = (uint16_t)uFar16; + pCtx->cs = (uint16_t)(uFar16 >> 16) | pCtx->bCpl; + pCtx->cs += (uint16_t)pCtx->bCpl << BS3_SEL_RING_SHIFT; + break; + } + + case BS3_MODE_CODE_V86: + { + uint32_t uFar16 = Bs3SelFlatCodeToRealMode((uintptr_t)pCode->pfn); + pCtx->rip.u = (uint16_t)uFar16; + pCtx->cs = (uint16_t)(uFar16 >> 16); + break; + } + } +} + + +/** + * Test a simple load instruction around the edges of page two. + * + * @param pThis The test stat data. + * @param pCtx The test context. + * @param fFlags BS3CB2PFACC_F_XXX. + * @param bXcpt X86_XCPT_PF if this can cause \#PFs, otherwise + * X86_XCPT_UD. + * @param uPfErrCd The error code for \#PFs. + */ +static void bs3CpuBasic2Pf_DoMovLoad(PBS3CPUBASIC2PFSTATE pThis, PBS3REGCTX pCtx, uint32_t fFlags, uint8_t bXcpt, uint8_t uPfErrCd) +{ + static uint64_t const s_uValue = UINT64_C(0x7c4d0114428d); + uint64_t uExpectRax; + unsigned i; + + /* + * Adjust the incoming context and calculate our expections. + */ + bs3CpuBasic2Pf_SetCsEip(pThis, pCtx, &pThis->pCmnMode->MovLoad); + Bs3MemCpy(&pThis->ExpectCtx, pCtx, sizeof(pThis->ExpectCtx)); + switch (pThis->bMode & BS3_MODE_CODE_MASK) + { + case BS3_MODE_CODE_16: + case BS3_MODE_CODE_V86: + uExpectRax = (uint16_t)s_uValue | (pCtx->rax.u & UINT64_C(0xffffffffffff0000)); + break; + case BS3_MODE_CODE_32: + uExpectRax = (uint32_t)s_uValue | (pCtx->rax.u & UINT64_C(0xffffffff00000000)); + break; + case BS3_MODE_CODE_64: + uExpectRax = s_uValue; + break; + } + if (uExpectRax == pCtx->rax.u) + pCtx->rax.u = ~pCtx->rax.u; + + /* + * Make two approaches to the test page (the 2nd one): + * - i=0: Start on the 1st page and edge into the 2nd. + * - i=1: Start at the end of the 2nd page and edge off it and into the 3rd. + */ + for (i = 0; i < 2; i++) + { + unsigned off = fFlags & BS3CB2PFACC_F_DIRECT ? X86_PAGE_SIZE : X86_PAGE_SIZE * (i + 1) - pThis->cbAccess; + unsigned offEnd = fFlags & BS3CB2PFACC_F_DIRECT ? off + 1 : X86_PAGE_SIZE * (i + 1) + (i == 0 ? 8 : 7); + + for (; off < offEnd; off++) + { + *(uint64_t *)&pThis->pbOrgTest[off] = s_uValue; + if (BS3_MODE_IS_16BIT_CODE(pThis->bMode)) + pThis->ExpectCtx.rbx.u = pCtx->rbx.u = off; + else + pThis->ExpectCtx.rbx.u = pCtx->rbx.u = pThis->uTestAddr.u + off; + + Bs3TrapSetJmpAndRestore(pCtx, &pThis->TrapCtx); + //Bs3TestPrintf("off=%#06x bXcpt=%#x uErrCd=%#RX64\n", off, pThis->TrapCtx.bXcpt, pThis->TrapCtx.uErrCd); + + if ( bXcpt != X86_XCPT_PF + || ((fFlags & BS3CB2PFACC_F_PAGE_LEVEL) && off >= X86_PAGE_SIZE * 2) + || ((fFlags & BS3CB2PFACC_F_PAGE_LEVEL) && off <= X86_PAGE_SIZE - pThis->cbAccess) ) + { + pThis->ExpectCtx.rax.u = uExpectRax; + bs3CpuBasic2Pf_CompareCtx(pThis, &pThis->ExpectCtx, pThis->pCmnMode->MovLoad.offUd2, X86_XCPT_UD, 0 /*uErrCd*/); + pThis->ExpectCtx.rax = pCtx->rax; + } + else + { + if (off < X86_PAGE_SIZE) + pThis->ExpectCtx.cr2.u = pThis->uTestAddr.u + X86_PAGE_SIZE; + else + pThis->ExpectCtx.cr2.u = pThis->uTestAddr.u + off; + bs3CpuBasic2Pf_CompareCtx(pThis, &pThis->ExpectCtx, 0 /*cbPcAdjust*/, bXcpt, uPfErrCd); + pThis->ExpectCtx.cr2 = pCtx->cr2; + } + } + + if (fFlags & BS3CB2PFACC_F_DIRECT) + break; + } +} + + +/** + * Test a simple store instruction around the edges of page two. + * + * @param pThis The test stat data. + * @param pCtx The test context. + * @param fFlags BS3CB2PFACC_F_XXX. + * @param bXcpt X86_XCPT_PF if this can cause \#PFs, otherwise + * X86_XCPT_UD. + * @param uPfErrCd The error code for \#PFs. + */ +static void bs3CpuBasic2Pf_DoMovStore(PBS3CPUBASIC2PFSTATE pThis, PBS3REGCTX pCtx, uint32_t fFlags, + uint8_t bXcpt, uint8_t uPfErrCd) +{ + static uint64_t const s_uValue = UINT64_C(0x3af45ead86a34a26); + static uint64_t const s_uValueFlipped = UINT64_C(0xc50ba152795cb5d9); + uint64_t const uRaxSaved = pCtx->rax.u; + uint64_t uExpectStored; + unsigned i; + + /* + * Adjust the incoming context and calculate our expections. + */ + bs3CpuBasic2Pf_SetCsEip(pThis, pCtx, &pThis->pCmnMode->MovStore); + if ((pThis->bMode & BS3_MODE_CODE_MASK) != BS3_MODE_CODE_64) + pCtx->rax.u = (uint32_t)s_uValue; /* leave the upper part zero */ + else + pCtx->rax.u = s_uValue; + + Bs3MemCpy(&pThis->ExpectCtx, pCtx, sizeof(pThis->ExpectCtx)); + switch (pThis->bMode & BS3_MODE_CODE_MASK) + { + case BS3_MODE_CODE_16: + case BS3_MODE_CODE_V86: + uExpectStored = (uint16_t)s_uValue | (s_uValueFlipped & UINT64_C(0xffffffffffff0000)); + break; + case BS3_MODE_CODE_32: + uExpectStored = (uint32_t)s_uValue | (s_uValueFlipped & UINT64_C(0xffffffff00000000)); + break; + case BS3_MODE_CODE_64: + uExpectStored = s_uValue; + break; + } + + /* + * Make two approaches to the test page (the 2nd one): + * - i=0: Start on the 1st page and edge into the 2nd. + * - i=1: Start at the end of the 2nd page and edge off it and into the 3rd. + */ + for (i = 0; i < 2; i++) + { + unsigned off = fFlags & BS3CB2PFACC_F_DIRECT ? X86_PAGE_SIZE : X86_PAGE_SIZE * (i + 1) - pThis->cbAccess; + unsigned offEnd = fFlags & BS3CB2PFACC_F_DIRECT ? off + 1 : X86_PAGE_SIZE * (i + 1) + (i == 0 ? 8 : 7); + for (; off < offEnd; off++) + { + *(uint64_t *)&pThis->pbOrgTest[off] = s_uValueFlipped; + if (BS3_MODE_IS_16BIT_CODE(pThis->bMode)) + pThis->ExpectCtx.rbx.u = pCtx->rbx.u = off; + else + pThis->ExpectCtx.rbx.u = pCtx->rbx.u = pThis->uTestAddr.u + off; + + Bs3TrapSetJmpAndRestore(pCtx, &pThis->TrapCtx); + //Bs3TestPrintf("off=%#06x bXcpt=%#x uErrCd=%#RX64\n", off, pThis->TrapCtx.bXcpt, pThis->TrapCtx.uErrCd); + + if ( bXcpt != X86_XCPT_PF + || ((fFlags & BS3CB2PFACC_F_PAGE_LEVEL) && off >= X86_PAGE_SIZE * 2) + || ((fFlags & BS3CB2PFACC_F_PAGE_LEVEL) && off <= X86_PAGE_SIZE - pThis->cbAccess) ) + { + bs3CpuBasic2Pf_CompareCtx(pThis, &pThis->ExpectCtx, pThis->pCmnMode->MovStore.offUd2, X86_XCPT_UD, 0 /*uErrCd*/); + if (*(uint64_t *)&pThis->pbOrgTest[off] != uExpectStored) + Bs3TestFailedF("%u - %s: Stored %#RX64, expected %#RX64", + g_usBs3TestStep, "xxxx", *(uint64_t *)&pThis->pbOrgTest[off], uExpectStored); + } + else + { + if (off < X86_PAGE_SIZE) + pThis->ExpectCtx.cr2.u = pThis->uTestAddr.u + X86_PAGE_SIZE; + else + pThis->ExpectCtx.cr2.u = pThis->uTestAddr.u + off; + bs3CpuBasic2Pf_CompareCtx(pThis, &pThis->ExpectCtx, 0 /*cbPcAdjust*/, bXcpt, uPfErrCd); + pThis->ExpectCtx.cr2 = pCtx->cr2; + if (*(uint64_t *)&pThis->pbOrgTest[off] != s_uValueFlipped) + Bs3TestFailedF("%u - %s: #PF'ed store modified memory: %#RX64, expected %#RX64", + g_usBs3TestStep, "xxxx", *(uint64_t *)&pThis->pbOrgTest[off], s_uValueFlipped); + + } + } + + if (fFlags & BS3CB2PFACC_F_DIRECT) + break; + } + + pCtx->rax.u = uRaxSaved; +} + + +/** + * Test a xchg instruction around the edges of page two. + * + * @param pThis The test stat data. + * @param pCtx The test context. + * @param fFlags BS3CB2PFACC_F_XXX. + * @param bXcpt X86_XCPT_PF if this can cause \#PFs, otherwise + * X86_XCPT_UD. + * @param uPfErrCd The error code for \#PFs. + */ +static void bs3CpuBasic2Pf_DoXchg(PBS3CPUBASIC2PFSTATE pThis, PBS3REGCTX pCtx, uint32_t fFlags, uint8_t bXcpt, uint8_t uPfErrCd) +{ + static uint64_t const s_uValue = UINT64_C(0xea58699648e2f32c); + static uint64_t const s_uValueFlipped = UINT64_C(0x15a79669b71d0cd3); + uint64_t const uRaxSaved = pCtx->rax.u; + uint64_t uRaxIn; + uint64_t uExpectedRax; + uint64_t uExpectStored; + unsigned i; + + /* + * Adjust the incoming context and calculate our expections. + */ + bs3CpuBasic2Pf_SetCsEip(pThis, pCtx, &pThis->pCmnMode->Xchg); + if ((pThis->bMode & BS3_MODE_CODE_MASK) != BS3_MODE_CODE_64) + uRaxIn = (uint32_t)s_uValue; /* leave the upper part zero */ + else + uRaxIn = s_uValue; + + Bs3MemCpy(&pThis->ExpectCtx, pCtx, sizeof(pThis->ExpectCtx)); + switch (pThis->bMode & BS3_MODE_CODE_MASK) + { + case BS3_MODE_CODE_16: + case BS3_MODE_CODE_V86: + uExpectedRax = (uint16_t)s_uValueFlipped | (uRaxIn & UINT64_C(0xffffffffffff0000)); + uExpectStored = (uint16_t)s_uValue | (s_uValueFlipped & UINT64_C(0xffffffffffff0000)); + break; + case BS3_MODE_CODE_32: + uExpectedRax = (uint32_t)s_uValueFlipped | (uRaxIn & UINT64_C(0xffffffff00000000)); + uExpectStored = (uint32_t)s_uValue | (s_uValueFlipped & UINT64_C(0xffffffff00000000)); + break; + case BS3_MODE_CODE_64: + uExpectedRax = s_uValueFlipped; + uExpectStored = s_uValue; + break; + } + + /* + * Make two approaches to the test page (the 2nd one): + * - i=0: Start on the 1st page and edge into the 2nd. + * - i=1: Start at the end of the 2nd page and edge off it and into the 3rd. + */ + for (i = 0; i < 2; i++) + { + unsigned off = fFlags & BS3CB2PFACC_F_DIRECT ? X86_PAGE_SIZE : X86_PAGE_SIZE * (i + 1) - pThis->cbAccess; + unsigned offEnd = fFlags & BS3CB2PFACC_F_DIRECT ? off + 1 : X86_PAGE_SIZE * (i + 1) + (i == 0 ? 8 : 7); + for (; off < offEnd; off++) + { + *(uint64_t *)&pThis->pbOrgTest[off] = s_uValueFlipped; + pCtx->rax.u = uRaxIn; + if (BS3_MODE_IS_16BIT_CODE(pThis->bMode)) + pThis->ExpectCtx.rbx.u = pCtx->rbx.u = off; + else + pThis->ExpectCtx.rbx.u = pCtx->rbx.u = pThis->uTestAddr.u + off; + + Bs3TrapSetJmpAndRestore(pCtx, &pThis->TrapCtx); + //Bs3TestPrintf("off=%#06x bXcpt=%#x uErrCd=%#RX64\n", off, pThis->TrapCtx.bXcpt, pThis->TrapCtx.uErrCd); + + if ( bXcpt != X86_XCPT_PF + || ((fFlags & BS3CB2PFACC_F_PAGE_LEVEL) && off >= X86_PAGE_SIZE * 2) + || ((fFlags & BS3CB2PFACC_F_PAGE_LEVEL) && off <= X86_PAGE_SIZE - pThis->cbAccess) ) + { + pThis->ExpectCtx.rax.u = uExpectedRax; + bs3CpuBasic2Pf_CompareCtx(pThis, &pThis->ExpectCtx, pThis->pCmnMode->Xchg.offUd2, X86_XCPT_UD, 0 /*uErrCd*/); + if (*(uint64_t *)&pThis->pbOrgTest[off] != uExpectStored) + Bs3TestFailedF("%u - %s: Stored %#RX64, expected %#RX64", + g_usBs3TestStep, "xxxx", *(uint64_t *)&pThis->pbOrgTest[off], uExpectStored); + } + else + { + pThis->ExpectCtx.rax.u = uRaxIn; + if (off < X86_PAGE_SIZE) + pThis->ExpectCtx.cr2.u = pThis->uTestAddr.u + X86_PAGE_SIZE; + else + pThis->ExpectCtx.cr2.u = pThis->uTestAddr.u + off; + bs3CpuBasic2Pf_CompareCtx(pThis, &pThis->ExpectCtx, 0 /*cbPcAdjust*/, bXcpt, uPfErrCd); + pThis->ExpectCtx.cr2 = pCtx->cr2; + if (*(uint64_t *)&pThis->pbOrgTest[off] != s_uValueFlipped) + Bs3TestFailedF("%u - %s: #PF'ed store modified memory: %#RX64, expected %#RX64", + g_usBs3TestStep, "xxxx", *(uint64_t *)&pThis->pbOrgTest[off], s_uValueFlipped); + } + } + + if (fFlags & BS3CB2PFACC_F_DIRECT) + break; + } + + pCtx->rax.u = uRaxSaved; +} + + +/** + * Test a cmpxchg instruction around the edges of page two. + * + * @param pThis The test stat data. + * @param pCtx The test context. + * @param fFlags BS3CB2PFACC_F_XXX. + * @param bXcpt X86_XCPT_PF if this can cause \#PFs, otherwise + * X86_XCPT_UD. + * @param uPfErrCd The error code for \#PFs. + * @param fMissmatch Whether to fail and not store (@c true), or succeed + * and do the store. + */ +static void bs3CpuBasic2Pf_DoCmpXchg(PBS3CPUBASIC2PFSTATE pThis, PBS3REGCTX pCtx, uint32_t fFlags, + uint8_t bXcpt, uint8_t uPfErrCd, bool fMissmatch) +{ + static uint64_t const s_uValue = UINT64_C(0xea58699648e2f32c); + static uint64_t const s_uValueFlipped = UINT64_C(0x15a79669b71d0cd3); + static uint64_t const s_uValueOther = UINT64_C(0x2171239bcb044c81); + uint64_t const uRaxSaved = pCtx->rax.u; + uint64_t const uRcxSaved = pCtx->rcx.u; + uint64_t uRaxIn; + uint64_t uExpectedRax; + uint32_t uExpectedFlags; + uint64_t uExpectStored; + unsigned i; + + /* + * Adjust the incoming context and calculate our expections. + * Hint: CMPXCHG [xBX],xCX ; xAX compare and update implicit, ZF set to !fMissmatch. + */ + bs3CpuBasic2Pf_SetCsEip(pThis, pCtx, &pThis->pCmnMode->CmpXchg); + if ((pThis->bMode & BS3_MODE_CODE_MASK) != BS3_MODE_CODE_64) + { + uRaxIn = (uint32_t)(fMissmatch ? s_uValueOther : s_uValueFlipped); /* leave the upper part zero */ + pCtx->rcx.u = (uint32_t)s_uValue; /* ditto */ + } + else + { + uRaxIn = fMissmatch ? s_uValueOther : s_uValueFlipped; + pCtx->rcx.u = s_uValue; + } + if (fMissmatch) + pCtx->rflags.u32 |= X86_EFL_ZF; + else + pCtx->rflags.u32 &= ~X86_EFL_ZF; + + Bs3MemCpy(&pThis->ExpectCtx, pCtx, sizeof(pThis->ExpectCtx)); + uExpectedFlags = pCtx->rflags.u32 & ~(X86_EFL_CF | X86_EFL_PF | X86_EFL_AF | X86_EFL_SF | X86_EFL_OF | X86_EFL_ZF); + switch (pThis->bMode & BS3_MODE_CODE_MASK) + { + case BS3_MODE_CODE_16: + case BS3_MODE_CODE_V86: + uExpectedRax = (uint16_t)s_uValueFlipped | (uRaxIn & UINT64_C(0xffffffffffff0000)); + uExpectStored = (uint16_t)s_uValue | (s_uValueFlipped & UINT64_C(0xffffffffffff0000)); + uExpectedFlags |= !fMissmatch ? X86_EFL_ZF | X86_EFL_PF : X86_EFL_AF; + break; + case BS3_MODE_CODE_32: + uExpectedRax = (uint32_t)s_uValueFlipped | (uRaxIn & UINT64_C(0xffffffff00000000)); + uExpectStored = (uint32_t)s_uValue | (s_uValueFlipped & UINT64_C(0xffffffff00000000)); + uExpectedFlags |= !fMissmatch ? X86_EFL_ZF | X86_EFL_PF : X86_EFL_AF; + break; + case BS3_MODE_CODE_64: + uExpectedRax = s_uValueFlipped; + uExpectStored = s_uValue; + uExpectedFlags |= !fMissmatch ? X86_EFL_ZF | X86_EFL_PF : X86_EFL_AF; + break; + } + if (fMissmatch) + uExpectStored = s_uValueFlipped; + + /* + * Make two approaches to the test page (the 2nd one): + * - i=0: Start on the 1st page and edge into the 2nd. + * - i=1: Start at the end of the 2nd page and edge off it and into the 3rd. + */ + for (i = 0; i < 2; i++) + { + unsigned off = fFlags & BS3CB2PFACC_F_DIRECT ? X86_PAGE_SIZE : X86_PAGE_SIZE * (i + 1) - pThis->cbAccess; + unsigned offEnd = fFlags & BS3CB2PFACC_F_DIRECT ? off + 1 : X86_PAGE_SIZE * (i + 1) + (i == 0 ? 8 : 7); + for (; off < offEnd; off++) + { + *(uint64_t *)&pThis->pbOrgTest[off] = s_uValueFlipped; + pCtx->rax.u = uRaxIn; + if (BS3_MODE_IS_16BIT_CODE(pThis->bMode)) + pThis->ExpectCtx.rbx.u = pCtx->rbx.u = off; + else + pThis->ExpectCtx.rbx.u = pCtx->rbx.u = pThis->uTestAddr.u + off; + + Bs3TrapSetJmpAndRestore(pCtx, &pThis->TrapCtx); + //Bs3TestPrintf("off=%#06x bXcpt=%#x uErrCd=%#RX64\n", off, pThis->TrapCtx.bXcpt, pThis->TrapCtx.uErrCd); + + if ( bXcpt != X86_XCPT_PF + || ((fFlags & BS3CB2PFACC_F_PAGE_LEVEL) && off >= X86_PAGE_SIZE * 2) + || ((fFlags & BS3CB2PFACC_F_PAGE_LEVEL) && off <= X86_PAGE_SIZE - pThis->cbAccess) ) + { + pThis->ExpectCtx.rax.u = uExpectedRax; + pThis->ExpectCtx.rflags.u32 = uExpectedFlags; + bs3CpuBasic2Pf_CompareCtx(pThis, &pThis->ExpectCtx, pThis->pCmnMode->CmpXchg.offUd2, X86_XCPT_UD, 0 /*uErrCd*/); + if (*(uint64_t *)&pThis->pbOrgTest[off] != uExpectStored) + Bs3TestFailedF("%u - %s: Stored %#RX64, expected %#RX64", + g_usBs3TestStep, "xxxx", *(uint64_t *)&pThis->pbOrgTest[off], uExpectStored); + } + else + { + pThis->ExpectCtx.rax.u = uRaxIn; + pThis->ExpectCtx.rflags = pCtx->rflags; + if (off < X86_PAGE_SIZE) + pThis->ExpectCtx.cr2.u = pThis->uTestAddr.u + X86_PAGE_SIZE; + else + pThis->ExpectCtx.cr2.u = pThis->uTestAddr.u + off; + bs3CpuBasic2Pf_CompareCtx(pThis, &pThis->ExpectCtx, 0 /*cbPcAdjust*/, bXcpt, uPfErrCd); + pThis->ExpectCtx.cr2 = pCtx->cr2; + if (*(uint64_t *)&pThis->pbOrgTest[off] != s_uValueFlipped) + Bs3TestFailedF("%u - %s: #PF'ed store modified memory: %#RX64, expected %#RX64", + g_usBs3TestStep, "xxxx", *(uint64_t *)&pThis->pbOrgTest[off], s_uValueFlipped); + } + } + + if (fFlags & BS3CB2PFACC_F_DIRECT) + break; + } + + pCtx->rax.u = uRaxSaved; + pCtx->rcx.u = uRcxSaved; +} + + +static void bs3CpuBasic2Pf_DoCmpXchgMiss(PBS3CPUBASIC2PFSTATE pThis, PBS3REGCTX pCtx, uint32_t fFlags, + uint8_t bXcpt, uint8_t uPfErrCd) +{ + bs3CpuBasic2Pf_DoCmpXchg(pThis, pCtx, fFlags, bXcpt, uPfErrCd, true /*fMissmatch*/ ); +} + + +static void bs3CpuBasic2Pf_DoCmpXchgMatch(PBS3CPUBASIC2PFSTATE pThis, PBS3REGCTX pCtx, uint32_t fFlags, + uint8_t bXcpt, uint8_t uPfErrCd) +{ + bs3CpuBasic2Pf_DoCmpXchg(pThis, pCtx, fFlags, bXcpt, uPfErrCd , false /*fMissmatch*/ ); +} + + +/** + * @interface_method_impl{BS3CPUBASIC2PFACCESSOR,pfnAccessor, + * DIV [MEM=0] for checking the accessed bit} + */ +static void bs3CpuBasic2Pf_DoDivByZero(PBS3CPUBASIC2PFSTATE pThis, PBS3REGCTX pCtx, uint32_t fFlags, + uint8_t bXcpt, uint8_t uPfErrCd) +{ + static uint64_t const s_uFiller = UINT64_C(0x9856703711f4069e); + uint64_t uZeroAndFill; + unsigned i; + + /* + * Adjust the incoming context and calculate our expections. + */ + bs3CpuBasic2Pf_SetCsEip(pThis, pCtx, &pThis->pCmnMode->DivMem); + + Bs3MemCpy(&pThis->ExpectCtx, pCtx, sizeof(pThis->ExpectCtx)); + switch (pThis->bMode & BS3_MODE_CODE_MASK) + { + case BS3_MODE_CODE_16: + case BS3_MODE_CODE_V86: + uZeroAndFill = s_uFiller & UINT64_C(0xffffffffffff0000); + break; + case BS3_MODE_CODE_32: + uZeroAndFill = s_uFiller & UINT64_C(0xffffffff00000000); + break; + case BS3_MODE_CODE_64: + uZeroAndFill = 0; + break; + } + + /* + * Make two approaches to the test page (the 2nd one): + * - i=0: Start on the 1st page and edge into the 2nd. + * - i=1: Start at the end of the 2nd page and edge off it and into the 3rd. + */ + for (i = 0; i < 2; i++) + { + unsigned off = fFlags & BS3CB2PFACC_F_DIRECT ? X86_PAGE_SIZE : X86_PAGE_SIZE * (i + 1) - pThis->cbAccess; + unsigned offEnd = fFlags & BS3CB2PFACC_F_DIRECT ? off + 1 : X86_PAGE_SIZE * (i + 1) + (i == 0 ? 8 : 7); + for (; off < offEnd; off++) + { + *(uint64_t *)&pThis->pbOrgTest[off] = uZeroAndFill; + if (BS3_MODE_IS_16BIT_CODE(pThis->bMode)) + pThis->ExpectCtx.rbx.u = pCtx->rbx.u = off; + else + pThis->ExpectCtx.rbx.u = pCtx->rbx.u = pThis->uTestAddr.u + off; + + Bs3TrapSetJmpAndRestore(pCtx, &pThis->TrapCtx); + //if (pThis->bMode == BS3_MODE_PP16_32) Bs3TestPrintf("off=%#06x bXcpt=%#x uErrCd=%#RX64\n", off, pThis->TrapCtx.bXcpt, pThis->TrapCtx.uErrCd); + + if ( bXcpt != X86_XCPT_PF + || ((fFlags & BS3CB2PFACC_F_PAGE_LEVEL) && off >= X86_PAGE_SIZE * 2) + || ((fFlags & BS3CB2PFACC_F_PAGE_LEVEL) && off <= X86_PAGE_SIZE - pThis->cbAccess) ) + { + bs3CpuBasic2Pf_CompareCtx(pThis, &pThis->ExpectCtx, 0 /*cbPcAdjust*/, X86_XCPT_DE, 0 /*uErrCd*/); + if (*(uint64_t *)&pThis->pbOrgTest[off] != uZeroAndFill) + Bs3TestFailedF("%u - %s: Modified source op: %#RX64, expected %#RX64", + g_usBs3TestStep, "xxxx", *(uint64_t *)&pThis->pbOrgTest[off], uZeroAndFill); + } + else + { + if (off < X86_PAGE_SIZE) + pThis->ExpectCtx.cr2.u = pThis->uTestAddr.u + X86_PAGE_SIZE; + else + pThis->ExpectCtx.cr2.u = pThis->uTestAddr.u + off; + bs3CpuBasic2Pf_CompareCtx(pThis, &pThis->ExpectCtx, 0 /*cbPcAdjust*/, bXcpt, uPfErrCd); + pThis->ExpectCtx.cr2 = pCtx->cr2; + if (*(uint64_t *)&pThis->pbOrgTest[off] != uZeroAndFill) + Bs3TestFailedF("%u - %s: Modified source op: %#RX64, expected %#RX64", + g_usBs3TestStep, "xxxx", *(uint64_t *)&pThis->pbOrgTest[off], uZeroAndFill); + } + } + + if (fFlags & BS3CB2PFACC_F_DIRECT) + break; + } +} + + +static BS3CPUBASIC2PFACCESSOR const g_aAccessors[] = +{ + { "DoExec", bs3CpuBasic2Pf_DoExec, X86_TRAP_PF_ID, X86_XCPT_UD }, + { "DoMovLoad", bs3CpuBasic2Pf_DoMovLoad, 0, X86_XCPT_UD }, + { "DoMovStore", bs3CpuBasic2Pf_DoMovStore, X86_TRAP_PF_RW, X86_XCPT_UD }, + { "DoXchg", bs3CpuBasic2Pf_DoXchg, X86_TRAP_PF_RW, X86_XCPT_UD }, + { "DoCmpXchgMiss", bs3CpuBasic2Pf_DoCmpXchgMiss, X86_TRAP_PF_RW, X86_XCPT_UD }, + { "DoCmpXhcgMatch", bs3CpuBasic2Pf_DoCmpXchgMatch, X86_TRAP_PF_RW, X86_XCPT_UD }, + { "DoDivByZero", bs3CpuBasic2Pf_DoDivByZero, 0, X86_XCPT_DE }, +}; + +/** @} */ + + +/** @name BS3CPUBASIC2PFMODPT::pfnModify implementations. + * @{ */ + + +static void bs3CpuBasic2Pf_ClearMask(PBS3CPUBASIC2PFSTATE pThis, unsigned iStore, PCBS3CPUBASIC2PFMODPT pEntry, + uint32_t fClearMask, uint32_t fSetMask) +{ + if (pThis->PgInfo.cbEntry == 4) + { + uint32_t const uOrg = pThis->PteBackup.Legacy[1]; + uint32_t uNew = ((uOrg & ~fClearMask) | fSetMask) & ~(uint32_t)pEntry->uModifyArg; + uint32_t const uOld = pThis->PgInfo.u.Legacy.pPte[1].u; + g_aStoreMethods[iStore].pfnStore(pThis->PgInfo.u.Legacy.pPte + 1, uNew, uOld); + } + else + { + uint64_t const uOrg = pThis->PteBackup.Pae[1]; + uint64_t uNew = ((uOrg & ~(uint64_t)fClearMask) | fSetMask) & ~(uint64_t)pEntry->uModifyArg; + uint64_t const uOld = pThis->PgInfo.u.Pae.pPte[1].u; + + g_aStoreMethods[iStore].pfnStore(&pThis->PgInfo.u.Pae.pPte[1].au32[0], (uint32_t)uNew, (uint32_t)uOld); + if ((uint32_t)(uNew >> 32) != (uint32_t)(uOld >> 32)) + g_aStoreMethods[iStore].pfnStore(&pThis->PgInfo.u.Pae.pPte[1].au32[1], + (uint32_t)(uNew >> 32), (uint32_t)(uOld >> 32)); + } +} + +static void bs3CpuBasic2Pf_SetBit(PBS3CPUBASIC2PFSTATE pThis, unsigned iStore, PCBS3CPUBASIC2PFMODPT pEntry, + uint32_t fClearMask, uint32_t fSetMask) +{ + if (pThis->PgInfo.cbEntry == 4) + { + uint32_t const uOrg = pThis->PteBackup.Legacy[1]; + uint32_t uNew = (uOrg & ~fClearMask) | fSetMask | RT_BIT_32(pEntry->uModifyArg); + uint32_t const uOld = pThis->PgInfo.u.Legacy.pPte[1].u; + g_aStoreMethods[iStore].pfnStore(pThis->PgInfo.u.Legacy.pPte + 1, uNew, uOld); + } + else + { + uint64_t const uOrg = pThis->PteBackup.Pae[1]; + uint64_t uNew = ((uOrg & ~(uint64_t)fClearMask) | fSetMask) | RT_BIT_64(pEntry->uModifyArg); + uint64_t const uOld = pThis->PgInfo.u.Pae.pPte[1].u; + + if (pEntry->uModifyArg < 32 || (uint32_t)uNew != (uint32_t)uOld) + g_aStoreMethods[iStore].pfnStore(&pThis->PgInfo.u.Pae.pPte[1].au32[0], (uint32_t)uNew, (uint32_t)uOld); + if (pEntry->uModifyArg >= 32 || (uint32_t)(uNew >> 32) != (uint32_t)(uOld >> 32)) + g_aStoreMethods[iStore].pfnStore(&pThis->PgInfo.u.Pae.pPte[1].au32[1], + (uint32_t)(uNew >> 32), (uint32_t)(uOld >> 32)); + } +} + +static void bs3CpuBasic2Pf_NoChange(PBS3CPUBASIC2PFSTATE pThis, unsigned iStore, PCBS3CPUBASIC2PFMODPT pEntry, + uint32_t fClearMask, uint32_t fSetMask) +{ + if (pThis->PgInfo.cbEntry == 4) + { + uint32_t const uOrg = pThis->PteBackup.Legacy[1]; + uint32_t uNew = (uOrg & ~fClearMask) | fSetMask; + uint32_t const uOld = pThis->PgInfo.u.Legacy.pPte[1].u; + if (uNew != uOld) + g_aStoreMethods[iStore].pfnStore(&pThis->PgInfo.u.Legacy.pPte[1], uNew, uOld); + } + else + { + uint64_t const uOrg = pThis->PteBackup.Pae[1]; + uint64_t uNew = (uOrg & ~(uint64_t)fClearMask) | fSetMask; + uint64_t const uOld = pThis->PgInfo.u.Pae.pPte[1].u; + if (uNew != uOld) + { + if ((uint32_t)uNew != (uint32_t)uOld) + g_aStoreMethods[iStore].pfnStore(&pThis->PgInfo.u.Pae.pPte[1].au32[0], (uint32_t)uNew, (uint32_t)uOld); + if ((uint32_t)(uNew >> 32) != (uint32_t)(uOld >> 32)) + g_aStoreMethods[iStore].pfnStore(&pThis->PgInfo.u.Pae.pPte[1].au32[1], + (uint32_t)(uNew >> 32), (uint32_t)(uOld >> 32)); + } + } +} + +/** @} */ + + +/** @name BS3CPUBASIC2PFMODPT::pfnApplicable implementations. + * @{ */ + +static bool bs3CpuBasic2Pf_IsPteBitReserved(PBS3CPUBASIC2PFSTATE pThis, PCBS3CPUBASIC2PFMODPT pEntry) +{ + if (pThis->PgInfo.cbEntry == 8) + { + /* Bits 52..63 or 62 (NXE=1). */ + if (pThis->PgInfo.cEntries == 3) + { + if ((uint32_t)(pEntry->uModifyArg - 52U) < (uint32_t)(12 - pThis->fNxe)) + return true; + } + else if (pEntry->uModifyArg == 63 && !pThis->fNxe) + return true; + + /* Reserved physical address bits. */ + if (pEntry->uModifyArg < 52) + { + if ((uint32_t)pEntry->uModifyArg >= (uint32_t)pThis->cBitsPhysWidth) + return true; + } + } + return false; +} + +static bool bs3CpuBasic2Pf_IsPteBitSoftwareUsable(PBS3CPUBASIC2PFSTATE pThis, PCBS3CPUBASIC2PFMODPT pEntry) +{ + if (pThis->PgInfo.cbEntry == 8) + { + if (pThis->PgInfo.cEntries != 3) + { + if ((uint32_t)(pEntry->uModifyArg - 52U) < (uint32_t)11) + return true; + } + } + return false; +} + + +static bool bs3CpuBasic2Pf_IsNxe(PBS3CPUBASIC2PFSTATE pThis, PCBS3CPUBASIC2PFMODPT pEntry) +{ + return pThis->fNxe && pThis->PgInfo.cbEntry == 8; +} + +/** @} */ + + +static const BS3CPUBASIC2PFMODPT g_aPteWorkers[] = +{ +/* { pszName, P U W NX RSV ModiyfArg pfnModify, pfnApplicable }, */ + { "org", 1, 1, 1, 0, 0, 0, bs3CpuBasic2Pf_NoChange, NULL }, + { "!US", 1, 0, 1, 0, 0, X86_PTE_US, bs3CpuBasic2Pf_ClearMask, NULL }, + { "!RW", 1, 1, 0, 0, 0, X86_PTE_RW, bs3CpuBasic2Pf_ClearMask, NULL }, + { "!RW+!US", 1, 0, 0, 0, 0, X86_PTE_RW | X86_PTE_US, bs3CpuBasic2Pf_ClearMask, NULL }, + { "!P", 0, 0, 0, 0, 0, X86_PTE_P, bs3CpuBasic2Pf_ClearMask, NULL }, + { "NX", 1, 1, 1, 1, 0, 63, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsNxe }, + { "RSVPH[32]", 0, 0, 0, 0, 1, 32, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSVPH[33]", 0, 0, 0, 0, 1, 33, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSVPH[34]", 0, 0, 0, 0, 1, 34, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSVPH[35]", 0, 0, 0, 0, 1, 35, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSVPH[36]", 0, 0, 0, 0, 1, 36, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSVPH[37]", 0, 0, 0, 0, 1, 37, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSVPH[38]", 0, 0, 0, 0, 1, 38, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSVPH[39]", 0, 0, 0, 0, 1, 39, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSVPH[40]", 0, 0, 0, 0, 1, 40, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSVPH[41]", 0, 0, 0, 0, 1, 41, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSVPH[42]", 0, 0, 0, 0, 1, 42, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSVPH[43]", 0, 0, 0, 0, 1, 43, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSVPH[44]", 0, 0, 0, 0, 1, 44, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSVPH[45]", 0, 0, 0, 0, 1, 45, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSVPH[46]", 0, 0, 0, 0, 1, 46, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSVPH[47]", 0, 0, 0, 0, 1, 47, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSVPH[48]", 0, 0, 0, 0, 1, 48, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSVPH[49]", 0, 0, 0, 0, 1, 49, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSVPH[50]", 0, 0, 0, 0, 1, 50, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSVPH[51]", 0, 0, 0, 0, 1, 51, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSV[52]", 0, 0, 0, 0, 1, 52, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSV[53]", 0, 0, 0, 0, 1, 53, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSV[54]", 0, 0, 0, 0, 1, 54, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSV[55]", 0, 0, 0, 0, 1, 55, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSV[56]", 0, 0, 0, 0, 1, 56, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSV[57]", 0, 0, 0, 0, 1, 57, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSV[58]", 0, 0, 0, 0, 1, 58, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSV[59]", 0, 0, 0, 0, 1, 59, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSV[60]", 0, 0, 0, 0, 1, 60, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSV[61]", 0, 0, 0, 0, 1, 61, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSV[62]", 0, 0, 0, 0, 1, 62, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSV[62]", 0, 0, 0, 0, 1, 62, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "RSV[63]", 0, 0, 0, 0, 1, 63, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitReserved }, + { "!RSV[52]", 1, 1, 1, 0, 0, 52, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitSoftwareUsable }, + { "!RSV[53]", 1, 1, 1, 0, 0, 53, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitSoftwareUsable }, + { "!RSV[54]", 1, 1, 1, 0, 0, 54, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitSoftwareUsable }, + { "!RSV[55]", 1, 1, 1, 0, 0, 55, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitSoftwareUsable }, + { "!RSV[56]", 1, 1, 1, 0, 0, 56, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitSoftwareUsable }, + { "!RSV[57]", 1, 1, 1, 0, 0, 57, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitSoftwareUsable }, + { "!RSV[58]", 1, 1, 1, 0, 0, 58, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitSoftwareUsable }, + { "!RSV[59]", 1, 1, 1, 0, 0, 59, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitSoftwareUsable }, + { "!RSV[60]", 1, 1, 1, 0, 0, 60, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitSoftwareUsable }, + { "!RSV[61]", 1, 1, 1, 0, 0, 61, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitSoftwareUsable }, + { "!RSV[62]", 1, 1, 1, 0, 0, 62, bs3CpuBasic2Pf_SetBit, bs3CpuBasic2Pf_IsPteBitSoftwareUsable }, + +}; + + +/** + * Worker for bs3CpuBasic2_RaiseXcpt0e_c32 that does the actual testing. + * + * Caller does all the cleaning up. + * + * @returns Error count. + * @param pThis Test state data. + * @param fNxe Whether NX is enabled. + */ +static uint8_t bs3CpuBasic2_RaiseXcpt0eWorker(PBS3CPUBASIC2PFSTATE register pThis, bool const fWp, bool const fNxe) +{ + unsigned iLevel; + unsigned iRing; + unsigned iStore; + unsigned iAccessor; + unsigned iOuter; + unsigned cPml4Tests; + unsigned cPdPtrTests; + uint32_t const fPfIdMask = fNxe ? UINT32_MAX : ~X86_TRAP_PF_ID; + BS3REGCTX aCtxts[4]; + + pThis->fWp = fWp; + pThis->fNxe = fNxe; + + /** @todo figure out V8086 testing. */ + if ((pThis->bMode & BS3_MODE_CODE_MASK) == BS3_MODE_CODE_V86) + return BS3TESTDOMODE_SKIPPED; + + + /* paranoia: Touch the various big stack structures to ensure the compiler has allocated stack for them. */ + for (iRing = 0; iRing < RT_ELEMENTS(aCtxts); iRing++) + Bs3MemZero(&aCtxts[iRing], sizeof(aCtxts[iRing])); + + /* + * Set up a few contexts for testing this stuff. + */ + Bs3RegCtxSaveEx(&aCtxts[0], pThis->bMode, 2048); + for (iRing = 1; iRing < 4; iRing++) + { + aCtxts[iRing] = aCtxts[0]; + Bs3RegCtxConvertToRingX(&aCtxts[iRing], iRing); + } + + if (!BS3_MODE_IS_16BIT_CODE(pThis->bMode)) + { + for (iRing = 0; iRing < 4; iRing++) + aCtxts[iRing].rbx.u = pThis->uTestAddr.u; + } + else + { + for (iRing = 0; iRing < 4; iRing++) + { + aCtxts[iRing].ds = pThis->uSel16TestData; + aCtxts[iRing].rbx.u = 0; + } + } + + /* + * Check basic operation: + */ + for (iRing = 0; iRing < 4; iRing++) + for (iAccessor = 0; iAccessor < RT_ELEMENTS(g_aAccessors); iAccessor++) + g_aAccessors[iAccessor].pfnAccessor(pThis, &aCtxts[iRing], BS3CB2PFACC_F_PAGE_LEVEL, X86_XCPT_UD, UINT8_MAX); + + /* + * Some PTE checks. We only mess with the 2nd page. + */ + for (iOuter = 0; iOuter < 2; iOuter++) + { + uint32_t const fAccessor = (iOuter == 0 ? BS3CB2PFACC_F_DIRECT : 0) | BS3CB2PFACC_F_PAGE_LEVEL; + unsigned iPteWrk; + + bs3CpuBasic2Pf_FlushAll(); + for (iPteWrk = 0; iPteWrk < RT_ELEMENTS(g_aPteWorkers); iPteWrk++) + { + BS3CPUBASIC2PFMODPT EffWrk; + const BS3CPUBASIC2PFMODPT *pPteWrk = &g_aPteWorkers[iPteWrk]; + if (pPteWrk->pfnApplicable && !pPteWrk->pfnApplicable(pThis, pPteWrk)) + continue; + + pThis->pszPteWorker = pPteWrk->pszName; + + EffWrk = *pPteWrk; + +#if 1 + /* + * Do the modification once, then test all different accesses + * without flushing the TLB or anything in-between. + */ + for (iStore = 0; iStore < RT_ELEMENTS(g_aStoreMethods); iStore++) + { + pThis->pszStore = g_aStoreMethods[iStore].pszName; + pPteWrk->pfnModify(pThis, iStore, pPteWrk, 0, 0); + + for (iRing = 0; iRing < 4; iRing++) + { + PBS3REGCTX const pCtx = &aCtxts[iRing]; + if ( EffWrk.fReserved + || !EffWrk.fPresent + || (!EffWrk.fUser && iRing == 3)) + { + uint32_t const fPfBase = ( EffWrk.fReserved ? X86_TRAP_PF_P | X86_TRAP_PF_RSVD + : EffWrk.fPresent ? X86_TRAP_PF_P : 0) + | (iRing == 3 ? X86_TRAP_PF_US : 0); + for (iAccessor = 0; iAccessor < RT_ELEMENTS(g_aAccessors); iAccessor++) + { + pThis->pszAccessor = g_aAccessors[iAccessor].pszName; + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, + fPfBase | (g_aAccessors[iAccessor].fAccess & fPfIdMask)); + } + } + else + { + uint32_t const fPfBase = X86_TRAP_PF_P | (iRing == 3 ? X86_TRAP_PF_US : 0); + for (iAccessor = 0; iAccessor < RT_ELEMENTS(g_aAccessors); iAccessor++) + { + pThis->pszAccessor = g_aAccessors[iAccessor].pszName; + if ( ( (g_aAccessors[iAccessor].fAccess & X86_TRAP_PF_ID) + && EffWrk.fNoExecute) + || ( (g_aAccessors[iAccessor].fAccess & X86_TRAP_PF_RW) + && !EffWrk.fWriteable + && (fWp || iRing == 3)) ) + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, + fPfBase | (g_aAccessors[iAccessor].fAccess & fPfIdMask)); + else + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_UD, UINT8_MAX); + } + } + } + + /* Reset the paging + full flush. */ + bs3CpuBasic2Pf_RestoreFromBackups(pThis); + } +#endif + +#define CHECK_AD_BITS(a_fExpectedAD) \ + do { \ + uint32_t fActualAD = ( pThis->PgInfo.cbEntry == 8 \ + ? pThis->PgInfo.u.Pae.pPte[1].au32[0] : pThis->PgInfo.u.Legacy.pPte[1].au32[0]) \ + & (X86_PTE_A | X86_PTE_D); \ + if (fActualAD != (a_fExpectedAD)) \ + { \ + Bs3TestFailedF("%u - %s/%u: unexpected A/D bits: %#x, expected %#x\n", \ + g_usBs3TestStep, "xxxx", __LINE__, fActualAD, a_fExpectedAD); \ + BS3CPUBASIC2PF_HALT(pThis); \ + } \ + } while (0) + + /* + * Again, but redoing everything for each accessor. + */ + for (iStore = 0; iStore < RT_ELEMENTS(g_aStoreMethods); iStore++) + { + pThis->pszStore = g_aStoreMethods[iStore].pszName; + + for (iRing = 0; iRing < 4; iRing++) + { + PBS3REGCTX const pCtx = &aCtxts[iRing]; + + if ( EffWrk.fReserved + || !EffWrk.fPresent + || (!EffWrk.fUser && iRing == 3)) + { + uint32_t const fPfBase = ( EffWrk.fReserved ? X86_TRAP_PF_P | X86_TRAP_PF_RSVD + : EffWrk.fPresent ? X86_TRAP_PF_P : 0) + | (iRing == 3 ? X86_TRAP_PF_US : 0); + for (iAccessor = 0; iAccessor < RT_ELEMENTS(g_aAccessors); iAccessor++) + { + pThis->pszAccessor = g_aAccessors[iAccessor].pszName; + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, 0, 0); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, + fPfBase | (g_aAccessors[iAccessor].fAccess & fPfIdMask)); + CHECK_AD_BITS(0); + bs3CpuBasic2Pf_RestoreFromBackups(pThis); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_A | X86_PTE_D, 0); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, + fPfBase | (g_aAccessors[iAccessor].fAccess & fPfIdMask)); + CHECK_AD_BITS(0); + bs3CpuBasic2Pf_RestoreFromBackups(pThis); + } + } + else + { + uint32_t const fPfBase = X86_TRAP_PF_P | (iRing == 3 ? X86_TRAP_PF_US : 0); + for (iAccessor = 0; iAccessor < RT_ELEMENTS(g_aAccessors); iAccessor++) + { + pThis->pszAccessor = g_aAccessors[iAccessor].pszName; + if ( ( (g_aAccessors[iAccessor].fAccess & X86_TRAP_PF_ID) + && EffWrk.fNoExecute) + || ( (g_aAccessors[iAccessor].fAccess & X86_TRAP_PF_RW) + && !EffWrk.fWriteable + && (fWp || iRing == 3)) ) + { + uint32_t const fErrCd = fPfBase | (g_aAccessors[iAccessor].fAccess & fPfIdMask); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_A | X86_PTE_D, 0); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, fErrCd); + CHECK_AD_BITS(0); + bs3CpuBasic2Pf_RestoreFromBackups(pThis); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, 0, X86_PTE_A | X86_PTE_D); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, fErrCd); + CHECK_AD_BITS(X86_PTE_A | X86_PTE_D); + bs3CpuBasic2Pf_RestoreFromBackups(pThis); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_A, X86_PTE_D); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, fErrCd); + CHECK_AD_BITS(X86_PTE_D); + bs3CpuBasic2Pf_RestoreFromBackups(pThis); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_D, X86_PTE_A); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, fErrCd); + CHECK_AD_BITS(X86_PTE_A); + bs3CpuBasic2Pf_RestoreFromBackups(pThis); + } + else + { + uint32_t const fExpectedAD = (g_aAccessors[iAccessor].fAccess & X86_TRAP_PF_RW) + ? X86_PTE_A | X86_PTE_D : X86_PTE_A; + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_A | X86_PTE_D, 0); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_UD, UINT8_MAX); + CHECK_AD_BITS(fExpectedAD); + bs3CpuBasic2Pf_RestoreFromBackups(pThis); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, 0, X86_PTE_A | X86_PTE_D); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_UD, UINT8_MAX); + CHECK_AD_BITS(X86_PTE_A | X86_PTE_D); + bs3CpuBasic2Pf_RestoreFromBackups(pThis); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_A, X86_PTE_D); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_UD, UINT8_MAX); + CHECK_AD_BITS(fExpectedAD | X86_PTE_D); + bs3CpuBasic2Pf_RestoreFromBackups(pThis); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_D, X86_PTE_A); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_UD, UINT8_MAX); + CHECK_AD_BITS(fExpectedAD | X86_PTE_A); + bs3CpuBasic2Pf_RestoreFromBackups(pThis); + } + } + } + } + } + + /* + * Again, but using invalidate page. + */ + if (pThis->fUseInvlPg) + { + bs3CpuBasic2Pf_RestoreFromBackups(pThis); + + for (iStore = 0; iStore < RT_ELEMENTS(g_aStoreMethods); iStore++) + { + pThis->pszStore = g_aStoreMethods[iStore].pszName; + + for (iRing = 0; iRing < 4; iRing++) + { + PBS3REGCTX const pCtx = &aCtxts[iRing]; + + if ( EffWrk.fReserved + || !EffWrk.fPresent + || (!EffWrk.fUser && iRing == 3)) + { + uint32_t const fPfBase = ( EffWrk.fReserved ? X86_TRAP_PF_P | X86_TRAP_PF_RSVD + : EffWrk.fPresent ? X86_TRAP_PF_P : 0) + | (iRing == 3 ? X86_TRAP_PF_US : 0); + for (iAccessor = 0; iAccessor < RT_ELEMENTS(g_aAccessors); iAccessor++) + { + pThis->pszAccessor = g_aAccessors[iAccessor].pszName; + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, 0, 0); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, + fPfBase | (g_aAccessors[iAccessor].fAccess & fPfIdMask)); + CHECK_AD_BITS(0); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_A | X86_PTE_D, 0); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, + fPfBase | (g_aAccessors[iAccessor].fAccess & fPfIdMask)); + CHECK_AD_BITS(0); + } + } + else + { + uint32_t const fPfBase = X86_TRAP_PF_P | (iRing == 3 ? X86_TRAP_PF_US : 0); + for (iAccessor = 0; iAccessor < RT_ELEMENTS(g_aAccessors); iAccessor++) + { + pThis->pszAccessor = g_aAccessors[iAccessor].pszName; + if ( ( (g_aAccessors[iAccessor].fAccess & X86_TRAP_PF_ID) + && EffWrk.fNoExecute) + || ( (g_aAccessors[iAccessor].fAccess & X86_TRAP_PF_RW) + && !EffWrk.fWriteable + && (fWp || iRing == 3)) ) + { + uint32_t const fErrCd = fPfBase | (g_aAccessors[iAccessor].fAccess & fPfIdMask); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_A | X86_PTE_D, 0); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, fErrCd); + CHECK_AD_BITS(0); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, 0, X86_PTE_A | X86_PTE_D); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, fErrCd); + CHECK_AD_BITS(X86_PTE_A | X86_PTE_D); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_A, X86_PTE_D); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, fErrCd); + CHECK_AD_BITS(X86_PTE_D); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_D, X86_PTE_A); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, fErrCd); + CHECK_AD_BITS(X86_PTE_A); + } + else + { + uint32_t const fExpectedAD = (g_aAccessors[iAccessor].fAccess & X86_TRAP_PF_RW) + ? X86_PTE_A | X86_PTE_D : X86_PTE_A; + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_A | X86_PTE_D, 0); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_UD, UINT8_MAX); + CHECK_AD_BITS(fExpectedAD); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, 0, X86_PTE_A | X86_PTE_D); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_UD, UINT8_MAX); + CHECK_AD_BITS(X86_PTE_A | X86_PTE_D); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_A, X86_PTE_D); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_UD, UINT8_MAX); + CHECK_AD_BITS(fExpectedAD | X86_PTE_D); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_D, X86_PTE_A); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_UD, UINT8_MAX); + CHECK_AD_BITS(fExpectedAD | X86_PTE_A); + } + } + } + } + } + + bs3CpuBasic2Pf_RestoreFromBackups(pThis); + } + } + } + + + /* + * Do all 4 paging levels. We start out with full access to the page and + * restrict it in various ways. + * + * (On the final level we only mess with the 2nd page for now.) + */ + cPdPtrTests = 1; + cPml4Tests = 1; + if (pThis->uTestAddr.u >= UINT64_C(0x8000000000)) + { + cPml4Tests = 2; + cPdPtrTests = 2; + } + else if (pThis->PgInfo.cEntries == 3) + cPdPtrTests = 2; + +#if 0 + /* Loop 1: Accessor flags. */ + for (iOuter = 0; iOuter < 2; iOuter++) + { + uint32_t const fAccessor = (iOuter == 0 ? BS3CB2PFACC_F_DIRECT : 0) | BS3CB2PFACC_F_PAGE_LEVEL; + + /* Loop 2: Paging store method. */ + for (iStore = 0; iStore < RT_ELEMENTS(g_aStoreMethods); iStore++) + { + unsigned iPml4Test; + int8_t cReserved = 0; + int8_t cNotPresent = 0; + int8_t cNotWrite = 0; + int8_t cNotUser = 0; + int8_t cExecute = 0; + + /* Loop 3: Page map level 4 */ + for (iPml4Test = 0; iPml4Test < cPml4Tests; iPml4Test++) + { + unsigned iPdPtrTest; + + /* Loop 4: Page directory pointer table. */ + for (iPdPtrTest = 0; iPdPtrTest < cPdPtrTests; iPdPtrTest++) + { + unsigned iPdTest; + + /* Loop 5: Page directory. */ + for (iPdTest = 0; iPdTest < 2; iPdTest++) + { + unsigned iPtTest; + + /* Loop 6: Page table. */ + for (iPtTest = 0; iPtTest < 2; iPtTest++) + { + /* Loop 7: Accessor ring. */ + for (iRing = 0; iRing < 4; iRing++) + { + PBS3REGCTX const pCtx = &aCtxts[iRing]; + + if ( EffWrk.fReserved + || !EffWrk.fPresent + || (!EffWrk.fUser && iRing == 3)) + { + uint32_t const fPfBase = ( EffWrk.fReserved ? X86_TRAP_PF_P | X86_TRAP_PF_RSVD + : EffWrk.fPresent ? X86_TRAP_PF_P : 0) + | (iRing == 3 ? X86_TRAP_PF_US : 0); + for (iAccessor = 0; iAccessor < RT_ELEMENTS(g_aAccessors); iAccessor++) + { + pThis->pszAccessor = g_aAccessors[iAccessor].pszName; + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, 0, 0); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, + fPfBase | (g_aAccessors[iAccessor].fAccess & fPfIdMask)); + CHECK_AD_BITS(0); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_A | X86_PTE_D, 0); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, + fPfBase | (g_aAccessors[iAccessor].fAccess & fPfIdMask)); + CHECK_AD_BITS(0); + } + } + else + { + uint32_t const fPfBase = X86_TRAP_PF_P | (iRing == 3 ? X86_TRAP_PF_US : 0); + for (iAccessor = 0; iAccessor < RT_ELEMENTS(g_aAccessors); iAccessor++) + { + pThis->pszAccessor = g_aAccessors[iAccessor].pszName; + if ( ( (g_aAccessors[iAccessor].fAccess & X86_TRAP_PF_ID) + && EffWrk.fNoExecute) + || ( (g_aAccessors[iAccessor].fAccess & X86_TRAP_PF_RW) + && !EffWrk.fWriteable + && (fWp || iRing == 3)) ) + { + uint32_t const fErrCd = fPfBase | (g_aAccessors[iAccessor].fAccess & fPfIdMask); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_A | X86_PTE_D, 0); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, fErrCd); + CHECK_AD_BITS(0); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, 0, X86_PTE_A | X86_PTE_D); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, fErrCd); + CHECK_AD_BITS(X86_PTE_A | X86_PTE_D); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_A, X86_PTE_D); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, fErrCd); + CHECK_AD_BITS(X86_PTE_D); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_D, X86_PTE_A); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_PF, fErrCd); + CHECK_AD_BITS(X86_PTE_A); + } + else + { + uint32_t const fExpectedAD = (g_aAccessors[iAccessor].fAccess & X86_TRAP_PF_RW) + ? X86_PTE_A | X86_PTE_D : X86_PTE_A; + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_A | X86_PTE_D, 0); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_UD, UINT8_MAX); + CHECK_AD_BITS(fExpectedAD); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, 0, X86_PTE_A | X86_PTE_D); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_UD, UINT8_MAX); + CHECK_AD_BITS(X86_PTE_A | X86_PTE_D); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_A, X86_PTE_D); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_UD, UINT8_MAX); + CHECK_AD_BITS(fExpectedAD | X86_PTE_D); + + pPteWrk->pfnModify(pThis, iStore, pPteWrk, X86_PTE_D, X86_PTE_A); + ASMInvalidatePage(pThis->uTestAddr.u + X86_PAGE_SIZE); + g_aAccessors[iAccessor].pfnAccessor(pThis, pCtx, fAccessor, X86_XCPT_UD, UINT8_MAX); + CHECK_AD_BITS(fExpectedAD | X86_PTE_A); + } + } + } + } + + } + } + } + } + + } + } +#endif + + /* + * Check reserved bits on each paging level. + */ + + /* Loop 1: Accessor flags (only direct for now). */ + for (iOuter = 0; iOuter < 1; iOuter++) + { + uint32_t const fAccessor = BS3CB2PFACC_F_DIRECT; + + /* Loop 2: Paging store method. */ + for (iStore = 0; iStore < RT_ELEMENTS(g_aStoreMethods); iStore++) + { + /* Loop 3: Accessor ring. */ + for (iRing = 0; iRing < 4; iRing++) + { + /* Loop 4: Which level we mess up. */ + for (iLevel = 0; iLevel < pThis->PgInfo.cEntries; iLevel++) + { +#if 0 + const BS3CPUBASIC2PFMODPT *pPteWrk = &g_aPteWorkers[iPteWrk]; + if (pThis->PgInfo.) + { + } +#endif + + + } + } + } + } + + + + return 0; +} + + +BS3_DECL_CALLBACK(uint8_t) bs3CpuBasic2_RaiseXcpt0e_c32(uint8_t bMode) +{ + void *pvTestUnaligned; + uint32_t cbTestUnaligned = _8M; + uint8_t bRet = 1; + int rc; + BS3CPUBASIC2PFSTATE State; + + /* + * Initalize the state data. + */ + Bs3MemZero(&State, sizeof(State)); + State.bMode = bMode; + switch (bMode & BS3_MODE_CODE_MASK) + { + case BS3_MODE_CODE_16: State.cbAccess = sizeof(uint16_t); break; + case BS3_MODE_CODE_V86: State.cbAccess = sizeof(uint16_t); break; + case BS3_MODE_CODE_32: State.cbAccess = sizeof(uint32_t); break; + case BS3_MODE_CODE_64: State.cbAccess = sizeof(uint64_t); break; + } + State.pCmnMode = &g_aCmnModes[0]; + while (State.pCmnMode->bMode != (bMode & BS3_MODE_CODE_MASK)) + State.pCmnMode++; + State.fUseInvlPg = (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80486; + + /* Figure physical addressing width. */ + State.cBitsPhysWidth = 32; + if ( (g_uBs3CpuDetected & BS3CPU_F_CPUID) + && (ASMCpuId_EDX(1) & (X86_CPUID_FEATURE_EDX_PSE36 | X86_CPUID_FEATURE_EDX_PAE)) ) + State.cBitsPhysWidth = 36; + + if ( (g_uBs3CpuDetected & BS3CPU_F_CPUID_EXT_LEAVES) + && ASMCpuId_EAX(0x80000000) >= 0x80000008) + { + uint8_t cBits = (uint8_t)ASMCpuId_EAX(0x80000008); + if (cBits >= 32 && cBits <= 52) + State.cBitsPhysWidth = cBits; + else + Bs3TestPrintf("CPUID 0x80000008: Physical bitcount out of range: %u\n", cBits); + } + //Bs3TestPrintf("Physical bitcount: %u\n", State.cBitsPhysWidth); + + /* + * Allocate a some memory we can play around with, then carve a size aligned + * chunk out of it so we might be able to maybe play with 2/4MB pages too. + */ + cbTestUnaligned = _8M * 2; + while ((pvTestUnaligned = Bs3MemAlloc(BS3MEMKIND_FLAT32, cbTestUnaligned)) == NULL) + { + cbTestUnaligned >>= 1; + if (cbTestUnaligned <= _16K) + { + Bs3TestFailed("Failed to allocate memory to play around with\n"); + return 1; + } + } + + /* align. */ + if ((uintptr_t)pvTestUnaligned & (cbTestUnaligned - 1)) + { + State.cbTest = cbTestUnaligned >> 1; + State.pbOrgTest = (uint8_t *)(((uintptr_t)pvTestUnaligned + State.cbTest - 1) & ~(State.cbTest - 1)); + } + else + { + State.pbOrgTest = pvTestUnaligned; + State.cbTest = cbTestUnaligned; + } + State.cTestPages = State.cbTest >> X86_PAGE_SHIFT; + + /* + * Alias this memory far away from where our code and data lives. + */ + if (bMode & BS3_MODE_CODE_64) + State.uTestAddr.u = UINT64_C(0x0000648680000000); + else + State.uTestAddr.u = UINT32_C(0x80000000); + rc = Bs3PagingAlias(State.uTestAddr.u, (uintptr_t)State.pbOrgTest, State.cbTest, X86_PTE_P | X86_PTE_RW | X86_PTE_US); + if (RT_SUCCESS(rc)) + { + rc = Bs3PagingQueryAddressInfo(State.uTestAddr.u, &State.PgInfo); + if (RT_SUCCESS(rc)) + { +//if (bMode & BS3_MODE_CODE_64) ASMHalt(); + /* Set values that derives from the test memory size and paging info. */ + if (State.PgInfo.cEntries == 2) + { + State.cTestPdes = (State.cTestPages + X86_PG_ENTRIES - 1) / X86_PG_ENTRIES; + State.cTest1stPtes = RT_MIN(State.cTestPages, X86_PG_ENTRIES); + State.cbPdeBackup = State.cTestPdes * (X86_PAGE_SIZE / X86_PG_ENTRIES); + State.cbPteBackup = State.cTest1stPtes * (X86_PAGE_SIZE / X86_PG_ENTRIES); + } + else + { + State.cTestPdes = (State.cTestPages + X86_PG_PAE_ENTRIES - 1) / X86_PG_PAE_ENTRIES; + State.cTest1stPtes = RT_MIN(State.cTestPages, X86_PG_PAE_ENTRIES); + State.cbPdeBackup = State.cTestPdes * (X86_PAGE_SIZE / X86_PG_PAE_ENTRIES); + State.cbPteBackup = State.cTest1stPtes * (X86_PAGE_SIZE / X86_PG_PAE_ENTRIES); + } +#ifdef BS3CPUBASIC2PF_FASTER + State.cbPteBackup = State.PgInfo.cbEntry * 4; +#endif + if (State.cTestPdes <= RT_ELEMENTS(State.au64PdeBackup)) + { + uint32_t cr0 = ASMGetCR0(); + + /* Back up the structures. */ + Bs3MemCpy(&State.PteBackup, State.PgInfo.u.Legacy.pPte, State.cbPteBackup); + Bs3MemCpy(State.au64PdeBackup, State.PgInfo.u.Legacy.pPde, State.cbPdeBackup); + if (State.PgInfo.cEntries > 2) + State.u64PdpteBackup = State.PgInfo.u.Pae.pPdpe->u; + if (State.PgInfo.cEntries > 3) + State.u64Pml4eBackup = State.PgInfo.u.Pae.pPml4e->u; + + /* + * Setup a 16-bit selector for accessing the alias. + */ + Bs3SelSetup16BitData(&Bs3GdteSpare00, State.uTestAddr.u32); + State.uSel16TestData = BS3_SEL_SPARE_00 | 3; + + /* + * Do the testing. + */ + ASMSetCR0(ASMGetCR0() & ~X86_CR0_WP); + bRet = bs3CpuBasic2_RaiseXcpt0eWorker(&State, false /*fWp*/, false /*fNxe*/); + if (bRet == 0 && (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80486) + { + ASMSetCR0(ASMGetCR0() | X86_CR0_WP); + bRet = bs3CpuBasic2_RaiseXcpt0eWorker(&State, true /*fWp*/, false /*fNxe*/); + } + + /* Do again with NX enabled. */ + if (bRet == 0 && (g_uBs3CpuDetected & BS3CPU_F_NX)) + { + ASMWrMsr(MSR_K6_EFER, ASMRdMsr(MSR_K6_EFER) | MSR_K6_EFER_NXE); + ASMSetCR0(ASMGetCR0() & ~X86_CR0_WP); + bRet = bs3CpuBasic2_RaiseXcpt0eWorker(&State, false /*fWp*/, State.PgInfo.cbEntry == 8 /*fNxe*/); + ASMSetCR0(ASMGetCR0() | X86_CR0_WP); + bRet = bs3CpuBasic2_RaiseXcpt0eWorker(&State, true /*fWp*/, State.PgInfo.cbEntry == 8 /*fNxe*/); + ASMWrMsr(MSR_K6_EFER, ASMRdMsr(MSR_K6_EFER) & ~MSR_K6_EFER_NXE); + } + bs3CpuBasic2Pf_RestoreFromBackups(&State); + ASMSetCR0((ASMGetCR0() & ~X86_CR0_WP) | (cr0 & X86_CR0_WP)); + } + else + Bs3TestFailedF("cTestPdes=%u!\n", State.cTestPdes); + } + else + Bs3TestFailedF("Bs3PagingQueryAddressInfo failed: %d\n", rc); + Bs3PagingUnalias(State.uTestAddr.u, State.cbTest); + } + else + Bs3TestFailedF("Bs3PagingAlias failed! rc=%d\n", rc); + Bs3MemFree(pvTestUnaligned, cbTestUnaligned); + return bRet; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2-template.c b/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2-template.c new file mode 100644 index 00000000..52bea00c --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2-template.c @@ -0,0 +1,1548 @@ +/* $Id: bs3-cpu-basic-2-template.c $ */ +/** @file + * BS3Kit - bs3-cpu-basic-2, C code template. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/asm-amd64-x86.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#undef CHECK_MEMBER +#define CHECK_MEMBER(a_szName, a_szFmt, a_Actual, a_Expected) \ + do \ + { \ + if ((a_Actual) == (a_Expected)) { /* likely */ } \ + else bs3CpuBasic2_FailedF(a_szName "=" a_szFmt " expected " a_szFmt, (a_Actual), (a_Expected)); \ + } while (0) + + +#ifdef BS3_INSTANTIATING_MODE +# undef MyBs3Idt +# undef MY_SYS_SEL_R0_CS +# undef MY_SYS_SEL_R0_CS_CNF +# undef MY_SYS_SEL_R0_DS +# undef MY_SYS_SEL_R0_SS +# if BS3_MODE_IS_16BIT_SYS(TMPL_MODE) +# define MyBs3Idt Bs3Idt16 +# define MY_SYS_SEL_R0_CS BS3_SEL_R0_CS16 +# define MY_SYS_SEL_R0_CS_CNF BS3_SEL_R0_CS16_CNF +# define MY_SYS_SEL_R0_DS BS3_SEL_R0_DS16 +# define MY_SYS_SEL_R0_SS BS3_SEL_R0_SS16 +# elif BS3_MODE_IS_32BIT_SYS(TMPL_MODE) +# define MyBs3Idt Bs3Idt32 +# define MY_SYS_SEL_R0_CS BS3_SEL_R0_CS32 +# define MY_SYS_SEL_R0_CS_CNF BS3_SEL_R0_CS32_CNF +# define MY_SYS_SEL_R0_DS BS3_SEL_R0_DS32 +# define MY_SYS_SEL_R0_SS BS3_SEL_R0_SS32 +# elif BS3_MODE_IS_64BIT_SYS(TMPL_MODE) +# define MyBs3Idt Bs3Idt64 +# define MY_SYS_SEL_R0_CS BS3_SEL_R0_CS64 +# define MY_SYS_SEL_R0_CS_CNF BS3_SEL_R0_CS64_CNF +# define MY_SYS_SEL_R0_DS BS3_SEL_R0_DS64 +# define MY_SYS_SEL_R0_SS BS3_SEL_R0_DS64 +# else +# error "TMPL_MODE" +# endif +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +#ifdef BS3_INSTANTIATING_CMN +typedef struct BS3CB2INVLDESCTYPE +{ + uint8_t u4Type; + uint8_t u1DescType; +} BS3CB2INVLDESCTYPE; +#endif + + +/********************************************************************************************************************************* +* External Symbols * +*********************************************************************************************************************************/ +#ifdef BS3_INSTANTIATING_CMN +extern FNBS3FAR bs3CpuBasic2_Int80; +extern FNBS3FAR bs3CpuBasic2_Int81; +extern FNBS3FAR bs3CpuBasic2_Int82; +extern FNBS3FAR bs3CpuBasic2_Int83; +extern FNBS3FAR bs3CpuBasic2_ud2; +# define g_bs3CpuBasic2_ud2_FlatAddr BS3_DATA_NM(g_bs3CpuBasic2_ud2_FlatAddr) +extern uint32_t g_bs3CpuBasic2_ud2_FlatAddr; +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#ifdef BS3_INSTANTIATING_CMN +# define g_pszTestMode BS3_CMN_NM(g_pszTestMode) +static const char BS3_FAR *g_pszTestMode = (const char *)1; +# define g_bTestMode BS3_CMN_NM(g_bTestMode) +static uint8_t g_bTestMode = 1; +# define g_f16BitSys BS3_CMN_NM(g_f16BitSys) +static bool g_f16BitSys = 1; + + +/** Table containing invalid CS selector types. */ +static const BS3CB2INVLDESCTYPE g_aInvalidCsTypes[] = +{ + { X86_SEL_TYPE_RO, 1 }, + { X86_SEL_TYPE_RO_ACC, 1 }, + { X86_SEL_TYPE_RW, 1 }, + { X86_SEL_TYPE_RW_ACC, 1 }, + { X86_SEL_TYPE_RO_DOWN, 1 }, + { X86_SEL_TYPE_RO_DOWN_ACC, 1 }, + { X86_SEL_TYPE_RW_DOWN, 1 }, + { X86_SEL_TYPE_RW_DOWN_ACC, 1 }, + { 0, 0 }, + { 1, 0 }, + { 2, 0 }, + { 3, 0 }, + { 4, 0 }, + { 5, 0 }, + { 6, 0 }, + { 7, 0 }, + { 8, 0 }, + { 9, 0 }, + { 10, 0 }, + { 11, 0 }, + { 12, 0 }, + { 13, 0 }, + { 14, 0 }, + { 15, 0 }, +}; + +/** Table containing invalid SS selector types. */ +static const BS3CB2INVLDESCTYPE g_aInvalidSsTypes[] = +{ + { X86_SEL_TYPE_EO, 1 }, + { X86_SEL_TYPE_EO_ACC, 1 }, + { X86_SEL_TYPE_ER, 1 }, + { X86_SEL_TYPE_ER_ACC, 1 }, + { X86_SEL_TYPE_EO_CONF, 1 }, + { X86_SEL_TYPE_EO_CONF_ACC, 1 }, + { X86_SEL_TYPE_ER_CONF, 1 }, + { X86_SEL_TYPE_ER_CONF_ACC, 1 }, + { 0, 0 }, + { 1, 0 }, + { 2, 0 }, + { 3, 0 }, + { 4, 0 }, + { 5, 0 }, + { 6, 0 }, + { 7, 0 }, + { 8, 0 }, + { 9, 0 }, + { 10, 0 }, + { 11, 0 }, + { 12, 0 }, + { 13, 0 }, + { 14, 0 }, + { 15, 0 }, +}; + +#endif /* BS3_INSTANTIATING_CMN - global */ + +#ifdef BS3_INSTANTIATING_CMN + +/** + * Wrapper around Bs3TestFailedF that prefixes the error with g_usBs3TestStep + * and g_pszTestMode. + */ +# define bs3CpuBasic2_FailedF BS3_CMN_NM(bs3CpuBasic2_FailedF) +BS3_DECL_NEAR(void) bs3CpuBasic2_FailedF(const char *pszFormat, ...) +{ + va_list va; + + char szTmp[168]; + va_start(va, pszFormat); + Bs3StrPrintfV(szTmp, sizeof(szTmp), pszFormat, va); + va_end(va); + + Bs3TestFailedF("%u - %s: %s", g_usBs3TestStep, g_pszTestMode, szTmp); +} + + +/** + * Compares trap stuff. + */ +# define bs3CpuBasic2_CompareIntCtx1 BS3_CMN_NM(bs3CpuBasic2_CompareIntCtx1) +BS3_DECL_NEAR(void) bs3CpuBasic2_CompareIntCtx1(PCBS3TRAPFRAME pTrapCtx, PCBS3REGCTX pStartCtx, uint8_t bXcpt) +{ + uint16_t const cErrorsBefore = Bs3TestSubErrorCount(); + CHECK_MEMBER("bXcpt", "%#04x", pTrapCtx->bXcpt, bXcpt); + CHECK_MEMBER("bErrCd", "%#06RX64", pTrapCtx->uErrCd, 0); + Bs3TestCheckRegCtxEx(&pTrapCtx->Ctx, pStartCtx, 2 /*int xx*/, 0 /*cbSpAdjust*/, 0 /*fExtraEfl*/, g_pszTestMode, g_usBs3TestStep); + if (Bs3TestSubErrorCount() != cErrorsBefore) + { + Bs3TrapPrintFrame(pTrapCtx); +#if 1 + Bs3TestPrintf("Halting: g_uBs3CpuDetected=%#x\n", g_uBs3CpuDetected); + Bs3TestPrintf("Halting in CompareTrapCtx1: bXcpt=%#x\n", bXcpt); + ASMHalt(); +#endif + } +} + + +/** + * Compares trap stuff. + */ +# define bs3CpuBasic2_CompareTrapCtx2 BS3_CMN_NM(bs3CpuBasic2_CompareTrapCtx2) +BS3_DECL_NEAR(void) bs3CpuBasic2_CompareTrapCtx2(PCBS3TRAPFRAME pTrapCtx, PCBS3REGCTX pStartCtx, uint16_t cbIpAdjust, + uint8_t bXcpt, uint16_t uHandlerCs) +{ + uint16_t const cErrorsBefore = Bs3TestSubErrorCount(); + CHECK_MEMBER("bXcpt", "%#04x", pTrapCtx->bXcpt, bXcpt); + CHECK_MEMBER("bErrCd", "%#06RX64", pTrapCtx->uErrCd, 0); + CHECK_MEMBER("uHandlerCs", "%#06x", pTrapCtx->uHandlerCs, uHandlerCs); + Bs3TestCheckRegCtxEx(&pTrapCtx->Ctx, pStartCtx, cbIpAdjust, 0 /*cbSpAdjust*/, 0 /*fExtraEfl*/, g_pszTestMode, g_usBs3TestStep); + if (Bs3TestSubErrorCount() != cErrorsBefore) + { + Bs3TrapPrintFrame(pTrapCtx); +#if 1 + Bs3TestPrintf("Halting: g_uBs3CpuDetected=%#x\n", g_uBs3CpuDetected); + Bs3TestPrintf("Halting in CompareTrapCtx2: bXcpt=%#x\n", bXcpt); + ASMHalt(); +#endif + } +} + +/** + * Compares a CPU trap. + */ +# define bs3CpuBasic2_CompareCpuTrapCtx BS3_CMN_NM(bs3CpuBasic2_CompareCpuTrapCtx) +BS3_DECL_NEAR(void) bs3CpuBasic2_CompareCpuTrapCtx(PCBS3TRAPFRAME pTrapCtx, PCBS3REGCTX pStartCtx, uint16_t uErrCd, + uint8_t bXcpt, bool f486ResumeFlagHint) +{ + uint16_t const cErrorsBefore = Bs3TestSubErrorCount(); + uint32_t fExtraEfl; + + CHECK_MEMBER("bXcpt", "%#04x", pTrapCtx->bXcpt, bXcpt); + CHECK_MEMBER("bErrCd", "%#06RX16", (uint16_t)pTrapCtx->uErrCd, (uint16_t)uErrCd); /* 486 only writes a word */ + + fExtraEfl = X86_EFL_RF; + if ( g_f16BitSys + || ( !f486ResumeFlagHint + && (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) <= BS3CPU_80486 ) ) + fExtraEfl = 0; + else + fExtraEfl = X86_EFL_RF; +#if 0 /** @todo Running on an AMD Phenom II X6 1100T under AMD-V I'm not getting good X86_EFL_RF results. Enable this to get on with other work. */ + fExtraEfl = pTrapCtx->Ctx.rflags.u32 & X86_EFL_RF; +#endif + Bs3TestCheckRegCtxEx(&pTrapCtx->Ctx, pStartCtx, 0 /*cbIpAdjust*/, 0 /*cbSpAdjust*/, fExtraEfl, g_pszTestMode, g_usBs3TestStep); + if (Bs3TestSubErrorCount() != cErrorsBefore) + { + Bs3TrapPrintFrame(pTrapCtx); +#if 1 + Bs3TestPrintf("Halting: g_uBs3CpuDetected=%#x\n", g_uBs3CpuDetected); + Bs3TestPrintf("Halting: bXcpt=%#x uErrCd=%#x\n", bXcpt, uErrCd); + ASMHalt(); +#endif + } +} + + +/** + * Compares \#GP trap. + */ +# define bs3CpuBasic2_CompareGpCtx BS3_CMN_NM(bs3CpuBasic2_CompareGpCtx) +BS3_DECL_NEAR(void) bs3CpuBasic2_CompareGpCtx(PCBS3TRAPFRAME pTrapCtx, PCBS3REGCTX pStartCtx, uint16_t uErrCd) +{ + bs3CpuBasic2_CompareCpuTrapCtx(pTrapCtx, pStartCtx, uErrCd, X86_XCPT_GP, true /*f486ResumeFlagHint*/); +} + +/** + * Compares \#NP trap. + */ +# define bs3CpuBasic2_CompareNpCtx BS3_CMN_NM(bs3CpuBasic2_CompareNpCtx) +BS3_DECL_NEAR(void) bs3CpuBasic2_CompareNpCtx(PCBS3TRAPFRAME pTrapCtx, PCBS3REGCTX pStartCtx, uint16_t uErrCd) +{ + bs3CpuBasic2_CompareCpuTrapCtx(pTrapCtx, pStartCtx, uErrCd, X86_XCPT_NP, true /*f486ResumeFlagHint*/); +} + +/** + * Compares \#SS trap. + */ +# define bs3CpuBasic2_CompareSsCtx BS3_CMN_NM(bs3CpuBasic2_CompareSsCtx) +BS3_DECL_NEAR(void) bs3CpuBasic2_CompareSsCtx(PCBS3TRAPFRAME pTrapCtx, PCBS3REGCTX pStartCtx, uint16_t uErrCd, bool f486ResumeFlagHint) +{ + bs3CpuBasic2_CompareCpuTrapCtx(pTrapCtx, pStartCtx, uErrCd, X86_XCPT_SS, f486ResumeFlagHint); +} + +/** + * Compares \#TS trap. + */ +# define bs3CpuBasic2_CompareTsCtx BS3_CMN_NM(bs3CpuBasic2_CompareTsCtx) +BS3_DECL_NEAR(void) bs3CpuBasic2_CompareTsCtx(PCBS3TRAPFRAME pTrapCtx, PCBS3REGCTX pStartCtx, uint16_t uErrCd) +{ + bs3CpuBasic2_CompareCpuTrapCtx(pTrapCtx, pStartCtx, uErrCd, X86_XCPT_TS, false /*f486ResumeFlagHint*/); +} + +/** + * Compares \#PF trap. + */ +# define bs3CpuBasic2_ComparePfCtx BS3_CMN_NM(bs3CpuBasic2_ComparePfCtx) +BS3_DECL_NEAR(void) bs3CpuBasic2_ComparePfCtx(PCBS3TRAPFRAME pTrapCtx, PBS3REGCTX pStartCtx, uint16_t uErrCd, uint64_t uCr2Expected) +{ + uint64_t const uCr2Saved = pStartCtx->cr2.u; + pStartCtx->cr2.u = uCr2Expected; + bs3CpuBasic2_CompareCpuTrapCtx(pTrapCtx, pStartCtx, uErrCd, X86_XCPT_PF, true /*f486ResumeFlagHint*/); + pStartCtx->cr2.u = uCr2Saved; +} + +/** + * Compares \#UD trap. + */ +# define bs3CpuBasic2_CompareUdCtx BS3_CMN_NM(bs3CpuBasic2_CompareUdCtx) +BS3_DECL_NEAR(void) bs3CpuBasic2_CompareUdCtx(PCBS3TRAPFRAME pTrapCtx, PCBS3REGCTX pStartCtx) +{ + bs3CpuBasic2_CompareCpuTrapCtx(pTrapCtx, pStartCtx, 0 /*no error code*/, X86_XCPT_UD, true /*f486ResumeFlagHint*/); +} + + +# define bs3CpuBasic2_RaiseXcpt1Common BS3_CMN_NM(bs3CpuBasic2_RaiseXcpt1Common) +BS3_DECL_NEAR(void) bs3CpuBasic2_RaiseXcpt1Common(uint16_t const uSysR0Cs, uint16_t const uSysR0CsConf, uint16_t const uSysR0Ss, + PX86DESC const paIdt, unsigned const cIdteShift) +{ + BS3TRAPFRAME TrapCtx; + BS3REGCTX Ctx80; + BS3REGCTX Ctx81; + BS3REGCTX Ctx82; + BS3REGCTX Ctx83; + BS3REGCTX CtxTmp; + BS3REGCTX CtxTmp2; + PBS3REGCTX apCtx8x[4]; + unsigned iCtx; + unsigned iRing; + unsigned iDpl; + unsigned iRpl; + unsigned i, j, k; + uint32_t uExpected; + bool const f486Plus = (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80486; +# if TMPL_BITS == 16 + bool const f386Plus = (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80386; + bool const f286 = (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) == BS3CPU_80286; +# else + bool const f286 = false; + bool const f386Plus = true; + int rc; + uint8_t *pbIdtCopyAlloc; + PX86DESC pIdtCopy; + const unsigned cbIdte = 1 << (3 + cIdteShift); + RTCCUINTXREG uCr0Saved = ASMGetCR0(); + RTGDTR GdtrSaved; +# endif + RTIDTR IdtrSaved; + RTIDTR Idtr; + + ASMGetIDTR(&IdtrSaved); +# if TMPL_BITS != 16 + ASMGetGDTR(&GdtrSaved); +# endif + + /* make sure they're allocated */ + Bs3MemZero(&TrapCtx, sizeof(TrapCtx)); + Bs3MemZero(&Ctx80, sizeof(Ctx80)); + Bs3MemZero(&Ctx81, sizeof(Ctx81)); + Bs3MemZero(&Ctx82, sizeof(Ctx82)); + Bs3MemZero(&Ctx83, sizeof(Ctx83)); + Bs3MemZero(&CtxTmp, sizeof(CtxTmp)); + Bs3MemZero(&CtxTmp2, sizeof(CtxTmp2)); + + /* Context array. */ + apCtx8x[0] = &Ctx80; + apCtx8x[1] = &Ctx81; + apCtx8x[2] = &Ctx82; + apCtx8x[3] = &Ctx83; + +# if TMPL_BITS != 16 + /* Allocate memory for playing around with the IDT. */ + pbIdtCopyAlloc = NULL; + if (BS3_MODE_IS_PAGED(g_bTestMode)) + pbIdtCopyAlloc = Bs3MemAlloc(BS3MEMKIND_FLAT32, 12*_1K); +# endif + + /* + * IDT entry 80 thru 83 are assigned DPLs according to the number. + * (We'll be useing more, but this'll do for now.) + */ + paIdt[0x80 << cIdteShift].Gate.u2Dpl = 0; + paIdt[0x81 << cIdteShift].Gate.u2Dpl = 1; + paIdt[0x82 << cIdteShift].Gate.u2Dpl = 2; + paIdt[0x83 << cIdteShift].Gate.u2Dpl = 3; + + Bs3RegCtxSave(&Ctx80); + Ctx80.rsp.u -= 0x300; + Ctx80.rip.u = (uintptr_t)BS3_FP_OFF(&bs3CpuBasic2_Int80); +# if TMPL_BITS == 16 + Ctx80.cs = BS3_MODE_IS_RM_OR_V86(g_bTestMode) ? BS3_SEL_TEXT16 : BS3_SEL_R0_CS16; +# elif TMPL_BITS == 32 + g_uBs3TrapEipHint = Ctx80.rip.u32; +# endif + Bs3MemCpy(&Ctx81, &Ctx80, sizeof(Ctx80)); + Ctx81.rip.u = (uintptr_t)BS3_FP_OFF(&bs3CpuBasic2_Int81); + Bs3MemCpy(&Ctx82, &Ctx80, sizeof(Ctx80)); + Ctx82.rip.u = (uintptr_t)BS3_FP_OFF(&bs3CpuBasic2_Int82); + Bs3MemCpy(&Ctx83, &Ctx80, sizeof(Ctx80)); + Ctx83.rip.u = (uintptr_t)BS3_FP_OFF(&bs3CpuBasic2_Int83); + + /* + * Check that all the above gates work from ring-0. + */ + for (iCtx = 0; iCtx < RT_ELEMENTS(apCtx8x); iCtx++) + { + g_usBs3TestStep = iCtx; +# if TMPL_BITS == 32 + g_uBs3TrapEipHint = apCtx8x[iCtx]->rip.u32; +# endif + Bs3TrapSetJmpAndRestore(apCtx8x[iCtx], &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, apCtx8x[iCtx], 0x80+iCtx /*bXcpt*/); + } + + /* + * Check that the gate DPL checks works. + */ + g_usBs3TestStep = 100; + for (iRing = 0; iRing <= 3; iRing++) + { + for (iCtx = 0; iCtx < RT_ELEMENTS(apCtx8x); iCtx++) + { + Bs3MemCpy(&CtxTmp, apCtx8x[iCtx], sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, iRing); +# if TMPL_BITS == 32 + g_uBs3TrapEipHint = CtxTmp.rip.u32; +# endif + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + if (iCtx < iRing) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, ((0x80 + iCtx) << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + else + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x80 + iCtx /*bXcpt*/); + g_usBs3TestStep++; + } + } + + /* + * Modify the gate CS value and run the handler at a different CPL. + * Throw RPL variations into the mix (completely ignored) together + * with gate presence. + * 1. CPL <= GATE.DPL + * 2. GATE.P + * 3. GATE.CS.DPL <= CPL (non-conforming segments) + */ + g_usBs3TestStep = 1000; + for (i = 0; i <= 3; i++) + { + for (iRing = 0; iRing <= 3; iRing++) + { + for (iCtx = 0; iCtx < RT_ELEMENTS(apCtx8x); iCtx++) + { +# if TMPL_BITS == 32 + g_uBs3TrapEipHint = apCtx8x[iCtx]->rip.u32; +# endif + Bs3MemCpy(&CtxTmp, apCtx8x[iCtx], sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, iRing); + + for (j = 0; j <= 3; j++) + { + uint16_t const uCs = (uSysR0Cs | j) + (i << BS3_SEL_RING_SHIFT); + for (k = 0; k < 2; k++) + { + g_usBs3TestStep++; + /*Bs3TestPrintf("g_usBs3TestStep=%u iCtx=%u iRing=%u i=%u uCs=%04x\n", g_usBs3TestStep, iCtx, iRing, i, uCs);*/ + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u16Sel = uCs; + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u1Present = k; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + /*Bs3TrapPrintFrame(&TrapCtx);*/ + if (iCtx < iRing) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, ((0x80 + iCtx) << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + else if (k == 0) + bs3CpuBasic2_CompareNpCtx(&TrapCtx, &CtxTmp, ((0x80 + iCtx) << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + else if (i > iRing) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, uCs & X86_SEL_MASK_OFF_RPL); + else + { + uint16_t uExpectedCs = uCs & X86_SEL_MASK_OFF_RPL; + if (i <= iCtx && i <= iRing) + uExpectedCs |= i; + bs3CpuBasic2_CompareTrapCtx2(&TrapCtx, &CtxTmp, 2 /*int 8xh*/, 0x80 + iCtx /*bXcpt*/, uExpectedCs); + } + } + } + + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u16Sel = uSysR0Cs; + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u1Present = 1; + } + } + } + BS3_ASSERT(g_usBs3TestStep < 1600); + + /* + * Various CS and SS related faults + * + * We temporarily reconfigure gate 80 and 83 with new CS selectors, the + * latter have a CS.DPL of 2 for testing ring transisions and SS loading + * without making it impossible to handle faults. + */ + g_usBs3TestStep = 1600; + Bs3GdteTestPage00 = Bs3Gdt[uSysR0Cs >> X86_SEL_SHIFT]; + Bs3GdteTestPage00.Gen.u1Present = 0; + Bs3GdteTestPage00.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + paIdt[0x80 << cIdteShift].Gate.u16Sel = BS3_SEL_TEST_PAGE_00; + + /* CS.PRESENT = 0 */ + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareNpCtx(&TrapCtx, &Ctx80, BS3_SEL_TEST_PAGE_00); + if (Bs3GdteTestPage00.Gen.u4Type & X86_SEL_TYPE_ACCESSED) + bs3CpuBasic2_FailedF("selector was accessed"); + g_usBs3TestStep++; + + /* Check that GATE.DPL is checked before CS.PRESENT. */ + for (iRing = 1; iRing < 4; iRing++) + { + Bs3MemCpy(&CtxTmp, &Ctx80, sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, iRing); + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, (0x80 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + if (Bs3GdteTestPage00.Gen.u4Type & X86_SEL_TYPE_ACCESSED) + bs3CpuBasic2_FailedF("selector was accessed"); + g_usBs3TestStep++; + } + + /* CS.DPL mismatch takes precedence over CS.PRESENT = 0. */ + Bs3GdteTestPage00.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareNpCtx(&TrapCtx, &Ctx80, BS3_SEL_TEST_PAGE_00); + if (Bs3GdteTestPage00.Gen.u4Type & X86_SEL_TYPE_ACCESSED) + bs3CpuBasic2_FailedF("CS selector was accessed"); + g_usBs3TestStep++; + for (iDpl = 1; iDpl < 4; iDpl++) + { + Bs3GdteTestPage00.Gen.u2Dpl = iDpl; + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx80, BS3_SEL_TEST_PAGE_00); + if (Bs3GdteTestPage00.Gen.u4Type & X86_SEL_TYPE_ACCESSED) + bs3CpuBasic2_FailedF("CS selector was accessed"); + g_usBs3TestStep++; + } + + /* 1608: Check all the invalid CS selector types alone. */ + Bs3GdteTestPage00 = Bs3Gdt[uSysR0Cs >> X86_SEL_SHIFT]; + for (i = 0; i < RT_ELEMENTS(g_aInvalidCsTypes); i++) + { + Bs3GdteTestPage00.Gen.u4Type = g_aInvalidCsTypes[i].u4Type; + Bs3GdteTestPage00.Gen.u1DescType = g_aInvalidCsTypes[i].u1DescType; + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx80, BS3_SEL_TEST_PAGE_00); + if (Bs3GdteTestPage00.Gen.u4Type != g_aInvalidCsTypes[i].u4Type) + bs3CpuBasic2_FailedF("Invalid CS type %#x/%u -> %#x/%u\n", + g_aInvalidCsTypes[i].u4Type, g_aInvalidCsTypes[i].u1DescType, + Bs3GdteTestPage00.Gen.u4Type, Bs3GdteTestPage00.Gen.u1DescType); + g_usBs3TestStep++; + + /* Incorrect CS.TYPE takes precedence over CS.PRESENT = 0. */ + Bs3GdteTestPage00.Gen.u1Present = 0; + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx80, BS3_SEL_TEST_PAGE_00); + Bs3GdteTestPage00.Gen.u1Present = 1; + g_usBs3TestStep++; + } + + /* Fix CS again. */ + Bs3GdteTestPage00 = Bs3Gdt[uSysR0Cs >> X86_SEL_SHIFT]; + + /* 1632: Test SS. */ + if (!BS3_MODE_IS_64BIT_SYS(g_bTestMode)) + { + uint16_t BS3_FAR *puTssSs2 = BS3_MODE_IS_16BIT_SYS(g_bTestMode) ? &Bs3Tss16.ss2 : &Bs3Tss32.ss2; + uint16_t const uSavedSs2 = *puTssSs2; + X86DESC const SavedGate83 = paIdt[0x83 << cIdteShift]; + + /* Make the handler execute in ring-2. */ + Bs3GdteTestPage02 = Bs3Gdt[(uSysR0Cs + (2 << BS3_SEL_RING_SHIFT)) >> X86_SEL_SHIFT]; + Bs3GdteTestPage02.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + paIdt[0x83 << cIdteShift].Gate.u16Sel = BS3_SEL_TEST_PAGE_02 | 2; + + Bs3MemCpy(&CtxTmp, &Ctx83, sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, 3); /* yeah, from 3 so SS:xSP is reloaded. */ + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x83); + if (!(Bs3GdteTestPage02.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + bs3CpuBasic2_FailedF("CS selector was not access"); + g_usBs3TestStep++; + + /* Create a SS.DPL=2 stack segment and check that SS2.RPL matters and + that we get #SS if the selector isn't present. */ + i = 0; /* used for cycling thru invalid CS types */ + for (k = 0; k < 10; k++) + { + /* k=0: present, + k=1: not-present, + k=2: present but very low limit, + k=3: not-present, low limit. + k=4: present, read-only. + k=5: not-present, read-only. + k=6: present, code-selector. + k=7: not-present, code-selector. + k=8: present, read-write / no access + system (=LDT). + k=9: not-present, read-write / no access + system (=LDT). + */ + Bs3GdteTestPage03 = Bs3Gdt[(uSysR0Ss + (2 << BS3_SEL_RING_SHIFT)) >> X86_SEL_SHIFT]; + Bs3GdteTestPage03.Gen.u1Present = !(k & 1); + if (k >= 8) + { + Bs3GdteTestPage03.Gen.u1DescType = 0; /* system */ + Bs3GdteTestPage03.Gen.u4Type = X86_SEL_TYPE_RW; /* = LDT */ + } + else if (k >= 6) + Bs3GdteTestPage03.Gen.u4Type = X86_SEL_TYPE_ER; + else if (k >= 4) + Bs3GdteTestPage03.Gen.u4Type = X86_SEL_TYPE_RO; + else if (k >= 2) + { + Bs3GdteTestPage03.Gen.u16LimitLow = 0x400; + Bs3GdteTestPage03.Gen.u4LimitHigh = 0; + Bs3GdteTestPage03.Gen.u1Granularity = 0; + } + + for (iDpl = 0; iDpl < 4; iDpl++) + { + Bs3GdteTestPage03.Gen.u2Dpl = iDpl; + + for (iRpl = 0; iRpl < 4; iRpl++) + { + *puTssSs2 = BS3_SEL_TEST_PAGE_03 | iRpl; + //Bs3TestPrintf("k=%u iDpl=%u iRpl=%u step=%u\n", k, iDpl, iRpl, g_usBs3TestStep); + Bs3GdteTestPage02.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + Bs3GdteTestPage03.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + if (iRpl != 2 || iRpl != iDpl || k >= 4) + bs3CpuBasic2_CompareTsCtx(&TrapCtx, &CtxTmp, BS3_SEL_TEST_PAGE_03); + else if (k != 0) + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &CtxTmp, BS3_SEL_TEST_PAGE_03, + k == 2 /*f486ResumeFlagHint*/); + else + { + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x83); + if (TrapCtx.uHandlerSs != (BS3_SEL_TEST_PAGE_03 | 2)) + bs3CpuBasic2_FailedF("uHandlerSs=%#x expected %#x\n", TrapCtx.uHandlerSs, BS3_SEL_TEST_PAGE_03 | 2); + } + if (!(Bs3GdteTestPage02.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + bs3CpuBasic2_FailedF("CS selector was not access"); + if ( TrapCtx.bXcpt == 0x83 + || (TrapCtx.bXcpt == X86_XCPT_SS && k == 2) ) + { + if (!(Bs3GdteTestPage03.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + bs3CpuBasic2_FailedF("SS selector was not accessed"); + } + else if (Bs3GdteTestPage03.Gen.u4Type & X86_SEL_TYPE_ACCESSED) + bs3CpuBasic2_FailedF("SS selector was accessed"); + g_usBs3TestStep++; + + /* +1: Modify the gate DPL to check that this is checked before SS.DPL and SS.PRESENT. */ + paIdt[0x83 << cIdteShift].Gate.u2Dpl = 2; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, (0x83 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + paIdt[0x83 << cIdteShift].Gate.u2Dpl = 3; + g_usBs3TestStep++; + + /* +2: Check the CS.DPL check is done before the SS ones. Restoring the + ring-0 INT 83 context triggers the CS.DPL < CPL check. */ + Bs3TrapSetJmpAndRestore(&Ctx83, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx83, BS3_SEL_TEST_PAGE_02); + g_usBs3TestStep++; + + /* +3: Now mark the CS selector not present and check that that also triggers before SS stuff. */ + Bs3GdteTestPage02.Gen.u1Present = 0; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareNpCtx(&TrapCtx, &CtxTmp, BS3_SEL_TEST_PAGE_02); + Bs3GdteTestPage02.Gen.u1Present = 1; + g_usBs3TestStep++; + + /* +4: Make the CS selector some invalid type and check it triggers before SS stuff. */ + Bs3GdteTestPage02.Gen.u4Type = g_aInvalidCsTypes[i].u4Type; + Bs3GdteTestPage02.Gen.u1DescType = g_aInvalidCsTypes[i].u1DescType; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, BS3_SEL_TEST_PAGE_02); + Bs3GdteTestPage02.Gen.u4Type = X86_SEL_TYPE_ER_ACC; + Bs3GdteTestPage02.Gen.u1DescType = 1; + g_usBs3TestStep++; + + /* +5: Now, make the CS selector limit too small and that it triggers after SS trouble. + The 286 had a simpler approach to these GP(0). */ + Bs3GdteTestPage02.Gen.u16LimitLow = 0; + Bs3GdteTestPage02.Gen.u4LimitHigh = 0; + Bs3GdteTestPage02.Gen.u1Granularity = 0; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + if (f286) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, 0 /*uErrCd*/); + else if (iRpl != 2 || iRpl != iDpl || k >= 4) + bs3CpuBasic2_CompareTsCtx(&TrapCtx, &CtxTmp, BS3_SEL_TEST_PAGE_03); + else if (k != 0) + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &CtxTmp, BS3_SEL_TEST_PAGE_03, k == 2 /*f486ResumeFlagHint*/); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, 0 /*uErrCd*/); + Bs3GdteTestPage02 = Bs3Gdt[(uSysR0Cs + (2 << BS3_SEL_RING_SHIFT)) >> X86_SEL_SHIFT]; + g_usBs3TestStep++; + } + } + } + + /* Check all the invalid SS selector types alone. */ + Bs3GdteTestPage02 = Bs3Gdt[(uSysR0Cs + (2 << BS3_SEL_RING_SHIFT)) >> X86_SEL_SHIFT]; + Bs3GdteTestPage03 = Bs3Gdt[(uSysR0Ss + (2 << BS3_SEL_RING_SHIFT)) >> X86_SEL_SHIFT]; + *puTssSs2 = BS3_SEL_TEST_PAGE_03 | 2; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x83); + g_usBs3TestStep++; + for (i = 0; i < RT_ELEMENTS(g_aInvalidSsTypes); i++) + { + Bs3GdteTestPage03.Gen.u4Type = g_aInvalidSsTypes[i].u4Type; + Bs3GdteTestPage03.Gen.u1DescType = g_aInvalidSsTypes[i].u1DescType; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareTsCtx(&TrapCtx, &CtxTmp, BS3_SEL_TEST_PAGE_03); + if (Bs3GdteTestPage03.Gen.u4Type != g_aInvalidSsTypes[i].u4Type) + bs3CpuBasic2_FailedF("Invalid SS type %#x/%u -> %#x/%u\n", + g_aInvalidSsTypes[i].u4Type, g_aInvalidSsTypes[i].u1DescType, + Bs3GdteTestPage03.Gen.u4Type, Bs3GdteTestPage03.Gen.u1DescType); + g_usBs3TestStep++; + } + + /* + * Continue the SS experiments with a expand down segment. We'll use + * the same setup as we already have with gate 83h being DPL and + * having CS.DPL=2. + * + * Expand down segments are weird. The valid area is practically speaking + * reversed. So, a 16-bit segment with a limit of 0x6000 will have valid + * addresses from 0xffff thru 0x6001. + * + * So, with expand down segments we can more easily cut partially into the + * pushing of the iret frame and trigger more interesting behavior than + * with regular "expand up" segments where the whole pushing area is either + * all fine or not not fine. + */ + Bs3GdteTestPage02 = Bs3Gdt[(uSysR0Cs + (2 << BS3_SEL_RING_SHIFT)) >> X86_SEL_SHIFT]; + Bs3GdteTestPage03 = Bs3Gdt[(uSysR0Ss + (2 << BS3_SEL_RING_SHIFT)) >> X86_SEL_SHIFT]; + Bs3GdteTestPage03.Gen.u2Dpl = 2; + Bs3GdteTestPage03.Gen.u4Type = X86_SEL_TYPE_RW_DOWN; + *puTssSs2 = BS3_SEL_TEST_PAGE_03 | 2; + + /* First test, limit = max --> no bytes accessible --> #GP */ + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &CtxTmp, BS3_SEL_TEST_PAGE_03, true /*f486ResumeFlagHint*/); + + /* Second test, limit = 0 --> all by zero byte accessible --> works */ + Bs3GdteTestPage03.Gen.u16LimitLow = 0; + Bs3GdteTestPage03.Gen.u4LimitHigh = 0; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x83); + + /* Modify the gate handler to be a dummy that immediately does UD2 + and triggers #UD, then advance the limit down till we get the #UD. */ + Bs3GdteTestPage03.Gen.u1Granularity = 0; + + Bs3MemCpy(&CtxTmp2, &CtxTmp, sizeof(CtxTmp2)); /* #UD result context */ + if (g_f16BitSys) + { + CtxTmp2.rip.u = g_bs3CpuBasic2_ud2_FlatAddr - BS3_ADDR_BS3TEXT16; + Bs3Trap16SetGate(0x83, X86_SEL_TYPE_SYS_286_INT_GATE, 3, BS3_SEL_TEST_PAGE_02, CtxTmp2.rip.u16, 0 /*cParams*/); + CtxTmp2.rsp.u = Bs3Tss16.sp2 - 2*5; + } + else + { + CtxTmp2.rip.u = g_bs3CpuBasic2_ud2_FlatAddr; + Bs3Trap32SetGate(0x83, X86_SEL_TYPE_SYS_386_INT_GATE, 3, BS3_SEL_TEST_PAGE_02, CtxTmp2.rip.u32, 0 /*cParams*/); + CtxTmp2.rsp.u = Bs3Tss32.esp2 - 4*5; + } + CtxTmp2.bMode = g_bTestMode; /* g_bBs3CurrentMode not changed by the UD2 handler. */ + CtxTmp2.cs = BS3_SEL_TEST_PAGE_02 | 2; + CtxTmp2.ss = BS3_SEL_TEST_PAGE_03 | 2; + CtxTmp2.bCpl = 2; + + /* test run. */ + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxTmp2); + g_usBs3TestStep++; + + /* Real run. */ + i = (g_f16BitSys ? 2 : 4) * 6 + 1; + while (i-- > 0) + { + Bs3GdteTestPage03.Gen.u16LimitLow = CtxTmp2.rsp.u16 + i - 1; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + if (i > 0) + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &CtxTmp, BS3_SEL_TEST_PAGE_03, true /*f486ResumeFlagHint*/); + else + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxTmp2); + g_usBs3TestStep++; + } + + /* Do a run where we do the same-ring kind of access. */ + Bs3RegCtxConvertToRingX(&CtxTmp, 2); + if (g_f16BitSys) + { + CtxTmp2.rsp.u32 = CtxTmp.rsp.u32 - 2*3; + i = 2*3 - 1; + } + else + { + CtxTmp2.rsp.u32 = CtxTmp.rsp.u32 - 4*3; + i = 4*3 - 1; + } + CtxTmp.ss = BS3_SEL_TEST_PAGE_03 | 2; + CtxTmp2.ds = CtxTmp.ds; + CtxTmp2.es = CtxTmp.es; + CtxTmp2.fs = CtxTmp.fs; + CtxTmp2.gs = CtxTmp.gs; + while (i-- > 0) + { + Bs3GdteTestPage03.Gen.u16LimitLow = CtxTmp2.rsp.u16 + i - 1; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + if (i > 0) + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &CtxTmp, 0 /*BS3_SEL_TEST_PAGE_03*/, true /*f486ResumeFlagHint*/); + else + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxTmp2); + g_usBs3TestStep++; + } + + *puTssSs2 = uSavedSs2; + paIdt[0x83 << cIdteShift] = SavedGate83; + } + paIdt[0x80 << cIdteShift].Gate.u16Sel = uSysR0Cs; + BS3_ASSERT(g_usBs3TestStep < 3000); + + /* + * Modify the gate CS value with a conforming segment. + */ + g_usBs3TestStep = 3000; + for (i = 0; i <= 3; i++) /* cs.dpl */ + { + for (iRing = 0; iRing <= 3; iRing++) + { + for (iCtx = 0; iCtx < RT_ELEMENTS(apCtx8x); iCtx++) + { + Bs3MemCpy(&CtxTmp, apCtx8x[iCtx], sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, iRing); +# if TMPL_BITS == 32 + g_uBs3TrapEipHint = CtxTmp.rip.u32; +# endif + + for (j = 0; j <= 3; j++) /* rpl */ + { + uint16_t const uCs = (uSysR0CsConf | j) + (i << BS3_SEL_RING_SHIFT); + /*Bs3TestPrintf("g_usBs3TestStep=%u iCtx=%u iRing=%u i=%u uCs=%04x\n", g_usBs3TestStep, iCtx, iRing, i, uCs);*/ + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u16Sel = uCs; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + //Bs3TestPrintf("%u/%u/%u/%u: cs=%04x hcs=%04x xcpt=%02x\n", i, iRing, iCtx, j, uCs, TrapCtx.uHandlerCs, TrapCtx.bXcpt); + /*Bs3TrapPrintFrame(&TrapCtx);*/ + g_usBs3TestStep++; + if (iCtx < iRing) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, ((0x80 + iCtx) << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + else if (i > iRing) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, uCs & X86_SEL_MASK_OFF_RPL); + else + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x80 + iCtx /*bXcpt*/); + } + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u16Sel = uSysR0Cs; + } + } + } + BS3_ASSERT(g_usBs3TestStep < 3500); + + /* + * The gates must be 64-bit in long mode. + */ + if (cIdteShift != 0) + { + g_usBs3TestStep = 3500; + for (i = 0; i <= 3; i++) + { + for (iRing = 0; iRing <= 3; iRing++) + { + for (iCtx = 0; iCtx < RT_ELEMENTS(apCtx8x); iCtx++) + { + Bs3MemCpy(&CtxTmp, apCtx8x[iCtx], sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, iRing); + + for (j = 0; j < 2; j++) + { + static const uint16_t s_auCSes[2] = { BS3_SEL_R0_CS16, BS3_SEL_R0_CS32 }; + uint16_t uCs = (s_auCSes[j] | i) + (i << BS3_SEL_RING_SHIFT); + g_usBs3TestStep++; + /*Bs3TestPrintf("g_usBs3TestStep=%u iCtx=%u iRing=%u i=%u uCs=%04x\n", g_usBs3TestStep, iCtx, iRing, i, uCs);*/ + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u16Sel = uCs; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + /*Bs3TrapPrintFrame(&TrapCtx);*/ + if (iCtx < iRing) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, ((0x80 + iCtx) << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, uCs & X86_SEL_MASK_OFF_RPL); + } + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u16Sel = uSysR0Cs; + } + } + } + BS3_ASSERT(g_usBs3TestStep < 4000); + } + + /* + * IDT limit check. The 286 does not access X86DESCGATE::u16OffsetHigh. + */ + g_usBs3TestStep = 5000; + i = (0x80 << (cIdteShift + 3)) - 1; + j = (0x82 << (cIdteShift + 3)) - (!f286 ? 1 : 3); + k = (0x83 << (cIdteShift + 3)) - 1; + for (; i <= k; i++, g_usBs3TestStep++) + { + Idtr = IdtrSaved; + Idtr.cbIdt = i; + ASMSetIDTR(&Idtr); + Bs3TrapSetJmpAndRestore(&Ctx81, &TrapCtx); + if (i < j) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx81, (0x81 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + else + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx81, 0x81 /*bXcpt*/); + } + ASMSetIDTR(&IdtrSaved); + BS3_ASSERT(g_usBs3TestStep < 5100); + +# if TMPL_BITS != 16 /* Only do the paging related stuff in 32-bit and 64-bit modes. */ + + /* + * IDT page not present. Placing the IDT copy such that 0x80 is on the + * first page and 0x81 is on the second page. We need proceed to move + * it down byte by byte to check that any inaccessible byte means #PF. + * + * Note! We must reload the alternative IDTR for each run as any kind of + * printing to the string (like error reporting) will cause a switch + * to real mode and back, reloading the default IDTR. + */ + g_usBs3TestStep = 5200; + if (BS3_MODE_IS_PAGED(g_bTestMode) && pbIdtCopyAlloc) + { + uint32_t const uCr2Expected = Bs3SelPtrToFlat(pbIdtCopyAlloc) + _4K; + for (j = 0; j < cbIdte; j++) + { + pIdtCopy = (PX86DESC)&pbIdtCopyAlloc[_4K - cbIdte * 0x81 - j]; + Bs3MemCpy(pIdtCopy, paIdt, cbIdte * 256); + + Idtr.cbIdt = IdtrSaved.cbIdt; + Idtr.pIdt = Bs3SelPtrToFlat(pIdtCopy); + + ASMSetIDTR(&Idtr); + Bs3TrapSetJmpAndRestore(&Ctx81, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx81, 0x81 /*bXcpt*/); + g_usBs3TestStep++; + + ASMSetIDTR(&Idtr); + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx80, 0x80 /*bXcpt*/); + g_usBs3TestStep++; + + rc = Bs3PagingProtect(uCr2Expected, _4K, 0 /*fSet*/, X86_PTE_P /*fClear*/); + if (RT_SUCCESS(rc)) + { + ASMSetIDTR(&Idtr); + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx80, 0x80 /*bXcpt*/); + g_usBs3TestStep++; + + ASMSetIDTR(&Idtr); + Bs3TrapSetJmpAndRestore(&Ctx81, &TrapCtx); + if (f486Plus) + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx81, 0 /*uErrCd*/, uCr2Expected); + else + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx81, X86_TRAP_PF_RW /*uErrCd*/, uCr2Expected + 4 - RT_MIN(j, 4)); + g_usBs3TestStep++; + + Bs3PagingProtect(uCr2Expected, _4K, X86_PTE_P /*fSet*/, 0 /*fClear*/); + + /* Check if that the entry type is checked after the whole IDTE has been cleared for #PF. */ + pIdtCopy[0x80 << cIdteShift].Gate.u4Type = 0; + rc = Bs3PagingProtect(uCr2Expected, _4K, 0 /*fSet*/, X86_PTE_P /*fClear*/); + if (RT_SUCCESS(rc)) + { + ASMSetIDTR(&Idtr); + Bs3TrapSetJmpAndRestore(&Ctx81, &TrapCtx); + if (f486Plus) + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx81, 0 /*uErrCd*/, uCr2Expected); + else + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx81, X86_TRAP_PF_RW /*uErrCd*/, uCr2Expected + 4 - RT_MIN(j, 4)); + g_usBs3TestStep++; + + Bs3PagingProtect(uCr2Expected, _4K, X86_PTE_P /*fSet*/, 0 /*fClear*/); + } + } + else + Bs3TestPrintf("Bs3PagingProtectPtr: %d\n", i); + + ASMSetIDTR(&IdtrSaved); + } + } + + /* + * The read/write and user/supervisor bits the IDT PTEs are irrelevant. + */ + g_usBs3TestStep = 5300; + if (BS3_MODE_IS_PAGED(g_bTestMode) && pbIdtCopyAlloc) + { + Bs3MemCpy(pbIdtCopyAlloc, paIdt, cbIdte * 256); + Idtr.cbIdt = IdtrSaved.cbIdt; + Idtr.pIdt = Bs3SelPtrToFlat(pbIdtCopyAlloc); + + ASMSetIDTR(&Idtr); + Bs3TrapSetJmpAndRestore(&Ctx81, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx81, 0x81 /*bXcpt*/); + g_usBs3TestStep++; + + rc = Bs3PagingProtect(Idtr.pIdt, _4K, 0 /*fSet*/, X86_PTE_RW | X86_PTE_US /*fClear*/); + if (RT_SUCCESS(rc)) + { + ASMSetIDTR(&Idtr); + Bs3TrapSetJmpAndRestore(&Ctx81, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx81, 0x81 /*bXcpt*/); + g_usBs3TestStep++; + + Bs3PagingProtect(Idtr.pIdt, _4K, X86_PTE_RW | X86_PTE_US /*fSet*/, 0 /*fClear*/); + } + ASMSetIDTR(&IdtrSaved); + } + + /* + * Check that CS.u1Accessed is set to 1. Use the test page selector #0 and #3 together + * with interrupt gates 80h and 83h, respectively. + */ +/** @todo Throw in SS.u1Accessed too. */ + g_usBs3TestStep = 5400; + if (BS3_MODE_IS_PAGED(g_bTestMode) && pbIdtCopyAlloc) + { + Bs3GdteTestPage00 = Bs3Gdt[uSysR0Cs >> X86_SEL_SHIFT]; + Bs3GdteTestPage00.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + paIdt[0x80 << cIdteShift].Gate.u16Sel = BS3_SEL_TEST_PAGE_00; + + Bs3GdteTestPage03 = Bs3Gdt[(uSysR0Cs + (3 << BS3_SEL_RING_SHIFT)) >> X86_SEL_SHIFT]; + Bs3GdteTestPage03.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + paIdt[0x83 << cIdteShift].Gate.u16Sel = BS3_SEL_TEST_PAGE_03; /* rpl is ignored, so leave it as zero. */ + + /* Check that the CS.A bit is being set on a general basis and that + the special CS values work with out generic handler code. */ + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx80, 0x80 /*bXcpt*/); + if (!(Bs3GdteTestPage00.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + bs3CpuBasic2_FailedF("u4Type=%#x, not accessed", Bs3GdteTestPage00.Gen.u4Type); + g_usBs3TestStep++; + + Bs3MemCpy(&CtxTmp, &Ctx83, sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, 3); + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x83 /*bXcpt*/); + if (!(Bs3GdteTestPage03.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + bs3CpuBasic2_FailedF("u4Type=%#x, not accessed!", Bs3GdteTestPage00.Gen.u4Type); + if (TrapCtx.uHandlerCs != (BS3_SEL_TEST_PAGE_03 | 3)) + bs3CpuBasic2_FailedF("uHandlerCs=%#x, expected %#x", TrapCtx.uHandlerCs, (BS3_SEL_TEST_PAGE_03 | 3)); + g_usBs3TestStep++; + + /* + * Now check that setting CS.u1Access to 1 does __NOT__ trigger a page + * fault due to the RW bit being zero. + * (We check both with with and without the WP bit if 80486.) + */ + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80486) + ASMSetCR0(uCr0Saved | X86_CR0_WP); + + Bs3GdteTestPage00.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + Bs3GdteTestPage03.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + rc = Bs3PagingProtect(GdtrSaved.pGdt + BS3_SEL_TEST_PAGE_00, 8, 0 /*fSet*/, X86_PTE_RW /*fClear*/); + if (RT_SUCCESS(rc)) + { + /* ring-0 handler */ + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx80, 0x80 /*bXcpt*/); + if (!(Bs3GdteTestPage00.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + bs3CpuBasic2_FailedF("u4Type=%#x, not accessed!", Bs3GdteTestPage00.Gen.u4Type); + g_usBs3TestStep++; + + /* ring-3 handler */ + Bs3MemCpy(&CtxTmp, &Ctx83, sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, 3); + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x83 /*bXcpt*/); + if (!(Bs3GdteTestPage03.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + bs3CpuBasic2_FailedF("u4Type=%#x, not accessed!", Bs3GdteTestPage00.Gen.u4Type); + g_usBs3TestStep++; + + /* clear WP and repeat the above. */ + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80486) + ASMSetCR0(uCr0Saved & ~X86_CR0_WP); + Bs3GdteTestPage00.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; /* (No need to RW the page - ring-0, WP=0.) */ + Bs3GdteTestPage03.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; /* (No need to RW the page - ring-0, WP=0.) */ + + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx80, 0x80 /*bXcpt*/); + if (!(Bs3GdteTestPage00.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + bs3CpuBasic2_FailedF("u4Type=%#x, not accessed!", Bs3GdteTestPage00.Gen.u4Type); + g_usBs3TestStep++; + + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x83 /*bXcpt*/); + if (!(Bs3GdteTestPage03.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + bs3CpuBasic2_FailedF("u4Type=%#x, not accessed!n", Bs3GdteTestPage03.Gen.u4Type); + g_usBs3TestStep++; + + Bs3PagingProtect(GdtrSaved.pGdt + BS3_SEL_TEST_PAGE_00, 8, X86_PTE_RW /*fSet*/, 0 /*fClear*/); + } + + ASMSetCR0(uCr0Saved); + + /* + * While we're here, check that if the CS GDT entry is a non-present + * page we do get a #PF with the rigth error code and CR2. + */ + Bs3GdteTestPage00.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; /* Just for fun, really a pointless gesture. */ + Bs3GdteTestPage03.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + rc = Bs3PagingProtect(GdtrSaved.pGdt + BS3_SEL_TEST_PAGE_00, 8, 0 /*fSet*/, X86_PTE_P /*fClear*/); + if (RT_SUCCESS(rc)) + { + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + if (f486Plus) + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx80, 0 /*uErrCd*/, GdtrSaved.pGdt + BS3_SEL_TEST_PAGE_00); + else + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx80, X86_TRAP_PF_RW, GdtrSaved.pGdt + BS3_SEL_TEST_PAGE_00 + 4); + g_usBs3TestStep++; + + /* Do it from ring-3 to check ErrCd, which doesn't set X86_TRAP_PF_US it turns out. */ + Bs3MemCpy(&CtxTmp, &Ctx83, sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, 3); + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + + if (f486Plus) + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &CtxTmp, 0 /*uErrCd*/, GdtrSaved.pGdt + BS3_SEL_TEST_PAGE_03); + else + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &CtxTmp, X86_TRAP_PF_RW, GdtrSaved.pGdt + BS3_SEL_TEST_PAGE_03 + 4); + g_usBs3TestStep++; + + Bs3PagingProtect(GdtrSaved.pGdt + BS3_SEL_TEST_PAGE_00, 8, X86_PTE_P /*fSet*/, 0 /*fClear*/); + if (Bs3GdteTestPage00.Gen.u4Type & X86_SEL_TYPE_ACCESSED) + bs3CpuBasic2_FailedF("u4Type=%#x, accessed! #1", Bs3GdteTestPage00.Gen.u4Type); + if (Bs3GdteTestPage03.Gen.u4Type & X86_SEL_TYPE_ACCESSED) + bs3CpuBasic2_FailedF("u4Type=%#x, accessed! #2", Bs3GdteTestPage03.Gen.u4Type); + } + + /* restore */ + paIdt[0x80 << cIdteShift].Gate.u16Sel = uSysR0Cs; + paIdt[0x83 << cIdteShift].Gate.u16Sel = uSysR0Cs;// + (3 << BS3_SEL_RING_SHIFT) + 3; + } + +# endif /* 32 || 64*/ + + /* + * Check broad EFLAGS effects. + */ + g_usBs3TestStep = 5600; + for (iCtx = 0; iCtx < RT_ELEMENTS(apCtx8x); iCtx++) + { + for (iRing = 0; iRing < 4; iRing++) + { + Bs3MemCpy(&CtxTmp, apCtx8x[iCtx], sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, iRing); + + /* all set */ + CtxTmp.rflags.u32 &= X86_EFL_VM | X86_EFL_1; + CtxTmp.rflags.u32 |= X86_EFL_CF | X86_EFL_PF | X86_EFL_AF | X86_EFL_ZF | X86_EFL_SF /* | X86_EFL_TF */ /*| X86_EFL_IF*/ + | X86_EFL_DF | X86_EFL_OF | X86_EFL_IOPL /* | X86_EFL_NT*/; + if (f486Plus) + CtxTmp.rflags.u32 |= X86_EFL_AC; + if (f486Plus && !g_f16BitSys) + CtxTmp.rflags.u32 |= X86_EFL_RF; + if (g_uBs3CpuDetected & BS3CPU_F_CPUID) + CtxTmp.rflags.u32 |= X86_EFL_VIF | X86_EFL_VIP; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + CtxTmp.rflags.u32 &= ~X86_EFL_RF; + + if (iCtx >= iRing) + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x80 + iCtx /*bXcpt*/); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, ((0x80 + iCtx) << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + uExpected = CtxTmp.rflags.u32 + & ( X86_EFL_1 | X86_EFL_CF | X86_EFL_PF | X86_EFL_AF | X86_EFL_ZF | X86_EFL_SF | X86_EFL_DF + | X86_EFL_OF | X86_EFL_IOPL | X86_EFL_NT | X86_EFL_VM | X86_EFL_AC | X86_EFL_VIF | X86_EFL_VIP + | X86_EFL_ID /*| X86_EFL_TF*/ /*| X86_EFL_IF*/ /*| X86_EFL_RF*/ ); + if (TrapCtx.fHandlerRfl != uExpected) + bs3CpuBasic2_FailedF("unexpected handler rflags value: %RX64 expected %RX32; CtxTmp.rflags=%RX64 Ctx.rflags=%RX64\n", + TrapCtx.fHandlerRfl, uExpected, CtxTmp.rflags.u, TrapCtx.Ctx.rflags.u); + g_usBs3TestStep++; + + /* all cleared */ + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) < BS3CPU_80286) + CtxTmp.rflags.u32 = apCtx8x[iCtx]->rflags.u32 & (X86_EFL_RA1_MASK | UINT16_C(0xf000)); + else + CtxTmp.rflags.u32 = apCtx8x[iCtx]->rflags.u32 & (X86_EFL_VM | X86_EFL_RA1_MASK); + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + if (iCtx >= iRing) + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x80 + iCtx /*bXcpt*/); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, ((0x80 + iCtx) << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + uExpected = CtxTmp.rflags.u32; + if (TrapCtx.fHandlerRfl != uExpected) + bs3CpuBasic2_FailedF("unexpected handler rflags value: %RX64 expected %RX32; CtxTmp.rflags=%RX64 Ctx.rflags=%RX64\n", + TrapCtx.fHandlerRfl, uExpected, CtxTmp.rflags.u, TrapCtx.Ctx.rflags.u); + g_usBs3TestStep++; + } + } + +/** @todo CS.LIMIT / canonical(CS) */ + + + /* + * Check invalid gate types. + */ + g_usBs3TestStep = 32000; + for (iRing = 0; iRing <= 3; iRing++) + { + static const uint16_t s_auCSes[] = { BS3_SEL_R0_CS16, BS3_SEL_R0_CS32, BS3_SEL_R0_CS64, + BS3_SEL_TSS16, BS3_SEL_TSS32, BS3_SEL_TSS64, 0, BS3_SEL_SPARE_1f }; + static uint16_t const s_auInvlTypes64[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f }; + static uint16_t const s_auInvlTypes32[] = { 0, 1, 2, 3, 8, 9, 10, 11, 13, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + /*286:*/ 12, 14, 15 }; + uint16_t const * const pauInvTypes = cIdteShift != 0 ? s_auInvlTypes64 : s_auInvlTypes32; + uint16_t const cInvTypes = cIdteShift != 0 ? RT_ELEMENTS(s_auInvlTypes64) + : f386Plus ? RT_ELEMENTS(s_auInvlTypes32) - 3 : RT_ELEMENTS(s_auInvlTypes32); + + + for (iCtx = 0; iCtx < RT_ELEMENTS(apCtx8x); iCtx++) + { + unsigned iType; + + Bs3MemCpy(&CtxTmp, apCtx8x[iCtx], sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, iRing); +# if TMPL_BITS == 32 + g_uBs3TrapEipHint = CtxTmp.rip.u32; +# endif + for (iType = 0; iType < cInvTypes; iType++) + { + uint8_t const bSavedType = paIdt[(0x80 + iCtx) << cIdteShift].Gate.u4Type; + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u1DescType = pauInvTypes[iType] >> 4; + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u4Type = pauInvTypes[iType] & 0xf; + + for (i = 0; i < 4; i++) + { + for (j = 0; j < RT_ELEMENTS(s_auCSes); j++) + { + uint16_t uCs = (unsigned)(s_auCSes[j] - BS3_SEL_R0_FIRST) < (unsigned)(4 << BS3_SEL_RING_SHIFT) + ? (s_auCSes[j] | i) + (i << BS3_SEL_RING_SHIFT) + : s_auCSes[j] | i; + /*Bs3TestPrintf("g_usBs3TestStep=%u iCtx=%u iRing=%u i=%u uCs=%04x type=%#x\n", g_usBs3TestStep, iCtx, iRing, i, uCs, pauInvTypes[iType]);*/ + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u16Sel = uCs; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + g_usBs3TestStep++; + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, ((0x80 + iCtx) << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + + /* Mark it not-present to check that invalid type takes precedence. */ + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u1Present = 0; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + g_usBs3TestStep++; + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, ((0x80 + iCtx) << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u1Present = 1; + } + } + + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u16Sel = uSysR0Cs; + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u4Type = bSavedType; + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u1DescType = 0; + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u1Present = 1; + } + } + } + BS3_ASSERT(g_usBs3TestStep < 62000U && g_usBs3TestStep > 32000U); + + + /** @todo + * - Run \#PF and \#GP (and others?) at CPLs other than zero. + * - Quickly generate all faults. + * - All the peculiarities v8086. + */ + +# if TMPL_BITS != 16 + Bs3MemFree(pbIdtCopyAlloc, 12*_1K); +# endif +} + +# if ARCH_BITS != 64 + +/** + * Worker for bs3CpuBasic2_TssGateEsp that tests the INT 80 from outer rings. + */ +# define bs3CpuBasic2_TssGateEsp_AltStackOuterRing BS3_CMN_NM(bs3CpuBasic2_TssGateEsp_AltStackOuterRing) +BS3_DECL_NEAR(void) bs3CpuBasic2_TssGateEsp_AltStackOuterRing(PCBS3REGCTX pCtx, uint8_t bRing, uint8_t *pbAltStack, + size_t cbAltStack, bool f16BitStack, bool f16BitTss, + bool f16BitHandler, unsigned uLine) +{ + uint8_t const cbIretFrame = f16BitHandler ? 5*2 : 5*4; + BS3REGCTX Ctx2; + BS3TRAPFRAME TrapCtx; + uint8_t *pbTmp; + g_usBs3TestStep = uLine; + + Bs3MemCpy(&Ctx2, pCtx, sizeof(Ctx2)); + Bs3RegCtxConvertToRingX(&Ctx2, bRing); + + if (pbAltStack) + { + Ctx2.rsp.u = Bs3SelPtrToFlat(pbAltStack + 0x1980); + Bs3MemZero(pbAltStack, cbAltStack); + } + + Bs3TrapSetJmpAndRestore(&Ctx2, &TrapCtx); + + if (!f16BitStack && f16BitTss) + Ctx2.rsp.u &= UINT16_MAX; + + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx2, 0x80 /*bXcpt*/); + CHECK_MEMBER("bCpl", "%u", TrapCtx.Ctx.bCpl, bRing); + CHECK_MEMBER("cbIretFrame", "%#x", TrapCtx.cbIretFrame, cbIretFrame); + + if (pbAltStack) + { + uint64_t uExpectedRsp = (f16BitTss ? Bs3Tss16.sp0 : Bs3Tss32.esp0) - cbIretFrame; + if (f16BitStack) + { + uExpectedRsp &= UINT16_MAX; + uExpectedRsp |= Ctx2.rsp.u & ~(uint64_t)UINT16_MAX; + } + if ( TrapCtx.uHandlerRsp != uExpectedRsp + || TrapCtx.uHandlerSs != (f16BitTss ? Bs3Tss16.ss0 : Bs3Tss32.ss0)) + bs3CpuBasic2_FailedF("handler SS:ESP=%04x:%08RX64, expected %04x:%08RX16", + TrapCtx.uHandlerSs, TrapCtx.uHandlerRsp, Bs3Tss16.ss0, uExpectedRsp); + + pbTmp = (uint8_t *)ASMMemFirstNonZero(pbAltStack, cbAltStack); + if ((f16BitStack || TrapCtx.uHandlerRsp <= UINT16_MAX) && pbTmp != NULL) + bs3CpuBasic2_FailedF("someone touched the alt stack (%p) with SS:ESP=%04x:%#RX32: %p=%02x", + pbAltStack, Ctx2.ss, Ctx2.rsp.u32, pbTmp, *pbTmp); + else if (!f16BitStack && TrapCtx.uHandlerRsp > UINT16_MAX && pbTmp == NULL) + bs3CpuBasic2_FailedF("the alt stack (%p) was not used SS:ESP=%04x:%#RX32\n", pbAltStack, Ctx2.ss, Ctx2.rsp.u32); + } +} + +# define bs3CpuBasic2_TssGateEspCommon BS3_CMN_NM(bs3CpuBasic2_TssGateEspCommon) +BS3_DECL_NEAR(void) bs3CpuBasic2_TssGateEspCommon(bool const g_f16BitSys, PX86DESC const paIdt, unsigned const cIdteShift) +{ + BS3TRAPFRAME TrapCtx; + BS3REGCTX Ctx; + BS3REGCTX Ctx2; +# if TMPL_BITS == 16 + uint8_t *pbTmp; +# endif + + /* make sure they're allocated */ + Bs3MemZero(&Ctx, sizeof(Ctx)); + Bs3MemZero(&Ctx2, sizeof(Ctx2)); + Bs3MemZero(&TrapCtx, sizeof(TrapCtx)); + + Bs3RegCtxSave(&Ctx); + Ctx.rsp.u -= 0x80; + + Bs3RegCtxSetRipCsFromLnkPtr(&Ctx, bs3CpuBasic2_Int80); +# if TMPL_BITS == 32 + g_uBs3TrapEipHint = Ctx.rip.u32; +# endif + + /* + * We'll be using IDT entry 80 and 81 here. The first one will be + * accessible from all DPLs, the latter not. So, start with setting + * the DPLs. + */ + paIdt[0x80 << cIdteShift].Gate.u2Dpl = 3; + paIdt[0x81 << cIdteShift].Gate.u2Dpl = 0; + + /* + * Check that the basic stuff works first. + */ + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + g_usBs3TestStep = __LINE__; + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx, 0x80 /*bXcpt*/); + + bs3CpuBasic2_TssGateEsp_AltStackOuterRing(&Ctx, 1, NULL, 0, g_f16BitSys, g_f16BitSys, g_f16BitSys, __LINE__); + bs3CpuBasic2_TssGateEsp_AltStackOuterRing(&Ctx, 2, NULL, 0, g_f16BitSys, g_f16BitSys, g_f16BitSys, __LINE__); + bs3CpuBasic2_TssGateEsp_AltStackOuterRing(&Ctx, 3, NULL, 0, g_f16BitSys, g_f16BitSys, g_f16BitSys, __LINE__); + + /* + * Check that the upper part of ESP is preserved when doing . + */ + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80386) + { + size_t const cbAltStack = _8K; + uint8_t *pbAltStack = Bs3MemAllocZ(BS3MEMKIND_TILED, cbAltStack); + if (pbAltStack) + { + /* same ring */ + g_usBs3TestStep = __LINE__; + Bs3MemCpy(&Ctx2, &Ctx, sizeof(Ctx2)); + Ctx2.rsp.u = Bs3SelPtrToFlat(pbAltStack + 0x1980); + if (Bs3TrapSetJmp(&TrapCtx)) + Bs3RegCtxRestore(&Ctx2, 0); /* (does not return) */ + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx2, 0x80 /*bXcpt*/); +# if TMPL_BITS == 16 + if ((pbTmp = (uint8_t *)ASMMemFirstNonZero(pbAltStack, cbAltStack)) != NULL) + bs3CpuBasic2_FailedF("someone touched the alt stack (%p) with SS:ESP=%04x:%#RX32: %p=%02x\n", + pbAltStack, Ctx2.ss, Ctx2.rsp.u32, pbTmp, *pbTmp); +# else + if (ASMMemIsZero(pbAltStack, cbAltStack)) + bs3CpuBasic2_FailedF("alt stack wasn't used despite SS:ESP=%04x:%#RX32\n", Ctx2.ss, Ctx2.rsp.u32); +# endif + + /* Different rings (load SS0:SP0 from TSS). */ + bs3CpuBasic2_TssGateEsp_AltStackOuterRing(&Ctx, 1, pbAltStack, cbAltStack, + g_f16BitSys, g_f16BitSys, g_f16BitSys, __LINE__); + bs3CpuBasic2_TssGateEsp_AltStackOuterRing(&Ctx, 2, pbAltStack, cbAltStack, + g_f16BitSys, g_f16BitSys, g_f16BitSys, __LINE__); + bs3CpuBasic2_TssGateEsp_AltStackOuterRing(&Ctx, 3, pbAltStack, cbAltStack, + g_f16BitSys, g_f16BitSys, g_f16BitSys, __LINE__); + + /* Different rings but switch the SS bitness in the TSS. */ + if (g_f16BitSys) + { + Bs3Tss16.ss0 = BS3_SEL_R0_SS32; + bs3CpuBasic2_TssGateEsp_AltStackOuterRing(&Ctx, 1, pbAltStack, cbAltStack, + false, g_f16BitSys, g_f16BitSys, __LINE__); + Bs3Tss16.ss0 = BS3_SEL_R0_SS16; + } + else + { + Bs3Tss32.ss0 = BS3_SEL_R0_SS16; + bs3CpuBasic2_TssGateEsp_AltStackOuterRing(&Ctx, 1, pbAltStack, cbAltStack, + true, g_f16BitSys, g_f16BitSys, __LINE__); + Bs3Tss32.ss0 = BS3_SEL_R0_SS32; + } + + Bs3MemFree(pbAltStack, cbAltStack); + } + else + Bs3TestPrintf("%s: Skipping ESP check, alloc failed\n", g_pszTestMode); + } + else + Bs3TestPrintf("%s: Skipping ESP check, CPU too old\n", g_pszTestMode); +} + +# endif /* ARCH_BITS != 64 */ +#endif /* BS3_INSTANTIATING_CMN */ + + +/* + * Mode specific code. + * Mode specific code. + * Mode specific code. + */ +#ifdef BS3_INSTANTIATING_MODE + +BS3_DECL_FAR(uint8_t) TMPL_NM(bs3CpuBasic2_TssGateEsp)(uint8_t bMode) +{ + uint8_t bRet = 0; + + g_pszTestMode = TMPL_NM(g_szBs3ModeName); + g_bTestMode = bMode; + g_f16BitSys = BS3_MODE_IS_16BIT_SYS(TMPL_MODE); + +# if TMPL_MODE == BS3_MODE_PE16 \ + || TMPL_MODE == BS3_MODE_PE16_32 \ + || TMPL_MODE == BS3_MODE_PP16 \ + || TMPL_MODE == BS3_MODE_PP16_32 \ + || TMPL_MODE == BS3_MODE_PAE16 \ + || TMPL_MODE == BS3_MODE_PAE16_32 \ + || TMPL_MODE == BS3_MODE_PE32 + bs3CpuBasic2_TssGateEspCommon(BS3_MODE_IS_16BIT_SYS(TMPL_MODE), + (PX86DESC)MyBs3Idt, + BS3_MODE_IS_64BIT_SYS(TMPL_MODE) ? 1 : 0); +# else + bRet = BS3TESTDOMODE_SKIPPED; +# endif + + /* + * Re-initialize the IDT. + */ + Bs3TrapInit(); + return bRet; +} + + +BS3_DECL_FAR(uint8_t) TMPL_NM(bs3CpuBasic2_RaiseXcpt1)(uint8_t bMode) +{ + g_pszTestMode = TMPL_NM(g_szBs3ModeName); + g_bTestMode = bMode; + g_f16BitSys = BS3_MODE_IS_16BIT_SYS(TMPL_MODE); + +# if !BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + + /* + * Pass to common worker which is only compiled once per mode. + */ + bs3CpuBasic2_RaiseXcpt1Common(MY_SYS_SEL_R0_CS, + MY_SYS_SEL_R0_CS_CNF, + MY_SYS_SEL_R0_SS, + (PX86DESC)MyBs3Idt, + BS3_MODE_IS_64BIT_SYS(TMPL_MODE) ? 1 : 0); + + /* + * Re-initialize the IDT. + */ + Bs3TrapInit(); + return 0; +# elif TMPL_MODE == BS3_MODE_RM + + /* + * Check + */ + /** @todo check */ + return BS3TESTDOMODE_SKIPPED; + +# else + return BS3TESTDOMODE_SKIPPED; +# endif +} + +#endif /* BS3_INSTANTIATING_MODE */ + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2-template.mac b/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2-template.mac new file mode 100644 index 00000000..3404add9 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2-template.mac @@ -0,0 +1,1879 @@ +; $Id: bs3-cpu-basic-2-template.mac $ +;; @file +; BS3Kit - bs3-cpu-basic-2 assembly template. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" ; setup environment + + +;********************************************************************************************************************************* +;* Defined Constants And Macros * +;********************************************************************************************************************************* +%ifnmacro BS3_CPUBAS2_UD_OFF +%macro BS3_CPUBAS2_UD_OFF 1 +BS3_GLOBAL_NAME_EX BS3_CMN_NM(%1) %+ _offUD, , 1 + db BS3_CMN_NM(%1).again - BS3_CMN_NM(%1) +%endmacro +%endif + +%undef BS3_CPUBAS2_REF_LABEL_VIA_CS +%if TMPL_BITS == 16 + %define BS3_CPUBAS2_REF_LABEL_VIA_CS(a_Label) cs:a_Label +%elif TMPL_BITS == 32 + %define BS3_CPUBAS2_REF_LABEL_VIA_CS(a_Label) cs:a_Label wrt FLAT +%elif TMPL_BITS == 64 + %define BS3_CPUBAS2_REF_LABEL_VIA_CS(a_Label) a_Label wrt FLAT +%else + %error TMPL_BITS +%endif + +;; +; Macro for generating far jmp instruction w/o nasm adding REX.W prefixes. +; +; @param 1 The label of the memory pointer. +; @param 2 Prefix: 0: none, 1: 066h, 2: REX.W, 3: 066h REX.W +%ifnmacro BS3_CPUBAS2_JMP_FAR_MEM_LABEL +%macro BS3_CPUBAS2_JMP_FAR_MEM_LABEL 2 + %if (%2) == 1 || (%2) == 3 + db 066h ; o16/o32 + %endif + %if TMPL_BITS != 64 + jmp far [BS3_CPUBAS2_REF_LABEL_VIA_CS(%1)] + %elif TMPL_BITS == 64 + ; 48FF2C25[040C0000] <3> jmp far [BS3_CPUBAS2_REF_LABLE_VIA_CS(.fpfn)] + %if (%2) == 2 || (%2) == 3 + db 048h ; REX.W + %endif + db 0ffh, 02ch, 025h + dd %1 wrt FLAT + %else + %error TMPL_BITS + %endif +%endmacro +%endif + +;; +; Macro for generating far call instruction w/o nasm adding REX.W prefixes. +; +; @param 1 The label of the memory pointer. +; @param 2 Prefix: 0: none, 1: 066h, 2: REX.W, 3: 066h REX.W +%ifnmacro BS3_CPUBAS2_CALL_FAR_MEM_LABEL +%macro BS3_CPUBAS2_CALL_FAR_MEM_LABEL 2 + %if (%2) == 1 || (%2) == 3 + db 066h ; o16/o32 + %endif + %if TMPL_BITS != 64 + call far [BS3_CPUBAS2_REF_LABEL_VIA_CS(%1)] + %elif TMPL_BITS == 64 + %if (%2) == 2 || (%2) == 3 + db 048h ; REX.W + %endif + db 0ffh, 01ch, 025h ; call far [mem] + dd %1 wrt FLAT + %else + %error TMPL_BITS + %endif +%endmacro +%endif + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +TMPL_BEGIN_TEXT + + + +; +; Test code snippets containing code which differs between 16-bit, 32-bit +; and 64-bit CPUs modes. +; +%ifdef BS3_INSTANTIATING_CMN + +; +; SIDT +; +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_sidt_bx_ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_sidt_bx_ud2, BS3_PBC_NEAR + sidt [xBX] +.again: ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_sidt_bx_ud2) == 3) +BS3_PROC_END_CMN bs3CpuBasic2_sidt_bx_ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_sidt_opsize_bx_ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_sidt_opsize_bx_ud2, BS3_PBC_NEAR + db X86_OP_PRF_SIZE_OP + sidt [xBX] +.again: ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_sidt_opsize_bx_ud2) == 4) +BS3_PROC_END_CMN bs3CpuBasic2_sidt_opsize_bx_ud2 + + %if TMPL_BITS == 64 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_sidt_rexw_bx_ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_sidt_rexw_bx_ud2, BS3_PBC_NEAR + db X86_OP_REX_W + sidt [xBX] +.again: ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_sidt_rexw_bx_ud2) == 4) +BS3_PROC_END_CMN bs3CpuBasic2_sidt_rexw_bx_ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_sidt_opsize_rexw_bx_ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_sidt_opsize_rexw_bx_ud2, BS3_PBC_NEAR + db X86_OP_PRF_SIZE_OP + db X86_OP_REX_W + sidt [xBX] +.again: ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_sidt_opsize_rexw_bx_ud2) == 5) +BS3_PROC_END_CMN bs3CpuBasic2_sidt_opsize_rexw_bx_ud2 + %endif + + %if TMPL_BITS != 64 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_sidt_ss_bx_ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_sidt_ss_bx_ud2, BS3_PBC_NEAR + sidt [ss:xBX] +.again: ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_sidt_ss_bx_ud2) == 4) +BS3_PROC_END_CMN bs3CpuBasic2_sidt_ss_bx_ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_sidt_opsize_ss_bx_ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_sidt_opsize_ss_bx_ud2, BS3_PBC_NEAR + db X86_OP_PRF_SIZE_OP + sidt [ss:xBX] +.again: ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_sidt_opsize_ss_bx_ud2) == 5) +BS3_PROC_END_CMN bs3CpuBasic2_sidt_opsize_ss_bx_ud2 + %endif + + +; +; SGDT +; +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_sgdt_bx_ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_sgdt_bx_ud2, BS3_PBC_NEAR + sgdt [xBX] +.again: ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_sgdt_bx_ud2) == 3) +BS3_PROC_END_CMN bs3CpuBasic2_sgdt_bx_ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_sgdt_opsize_bx_ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_sgdt_opsize_bx_ud2, BS3_PBC_NEAR + db X86_OP_PRF_SIZE_OP + sgdt [xBX] +.again: ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_sgdt_opsize_bx_ud2) == 4) +BS3_PROC_END_CMN bs3CpuBasic2_sgdt_opsize_bx_ud2 + + %if TMPL_BITS == 64 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_sgdt_rexw_bx_ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_sgdt_rexw_bx_ud2, BS3_PBC_NEAR + db X86_OP_REX_W + sgdt [xBX] +.again: ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_sgdt_rexw_bx_ud2) == 4) +BS3_PROC_END_CMN bs3CpuBasic2_sgdt_rexw_bx_ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_sgdt_opsize_rexw_bx_ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_sgdt_opsize_rexw_bx_ud2, BS3_PBC_NEAR + db X86_OP_PRF_SIZE_OP + db X86_OP_REX_W + sgdt [xBX] +.again: ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_sgdt_opsize_rexw_bx_ud2) == 5) +BS3_PROC_END_CMN bs3CpuBasic2_sgdt_opsize_rexw_bx_ud2 + %endif + + %if TMPL_BITS != 64 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_sgdt_ss_bx_ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_sgdt_ss_bx_ud2, BS3_PBC_NEAR + sgdt [ss:xBX] +.again: ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_sgdt_ss_bx_ud2) == 4) +BS3_PROC_END_CMN bs3CpuBasic2_sgdt_ss_bx_ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_sgdt_opsize_ss_bx_ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_sgdt_opsize_ss_bx_ud2, BS3_PBC_NEAR + db X86_OP_PRF_SIZE_OP + sgdt [ss:xBX] +.again: ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_sgdt_opsize_ss_bx_ud2) == 5) +BS3_PROC_END_CMN bs3CpuBasic2_sgdt_opsize_ss_bx_ud2 + %endif + + +; +; LIDT +; +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_lidt_bx__sidt_es_di__lidt_es_si__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_lidt_bx__sidt_es_di__lidt_es_si__ud2, BS3_PBC_NEAR + lidt [xBX] + sidt [BS3_NOT_64BIT(es:) xDI] + lidt [BS3_NOT_64BIT(es:) xSI] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_lidt_bx__sidt_es_di__lidt_es_si__ud2) == BS3_IF_64BIT_OTHERWISE(9,11)) +BS3_PROC_END_CMN bs3CpuBasic2_lidt_bx__sidt_es_di__lidt_es_si__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_lidt_opsize_bx__sidt_es_di__lidt_es_si__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_lidt_opsize_bx__sidt_es_di__lidt_es_si__ud2, BS3_PBC_NEAR + db X86_OP_PRF_SIZE_OP + lidt [xBX] + sidt [BS3_NOT_64BIT(es:) xDI] + lidt [BS3_NOT_64BIT(es:) xSI] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_lidt_opsize_bx__sidt_es_di__lidt_es_si__ud2) == BS3_IF_64BIT_OTHERWISE(10,12)) +BS3_PROC_END_CMN bs3CpuBasic2_lidt_opsize_bx__sidt_es_di__lidt_es_si__ud2 + +%if TMPL_BITS == 16 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_lidt_opsize_bx__sidt32_es_di__lidt_es_si__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_lidt_opsize_bx__sidt32_es_di__lidt_es_si__ud2, BS3_PBC_NEAR + db X86_OP_PRF_SIZE_OP + lidt [xBX] + jmp dword BS3_SEL_R0_CS32:.in_32bit wrt FLAT + BS3_SET_BITS 32 +.in_32bit: + sidt [es:edi] + lidt [es:esi] + jmp dword BS3_SEL_R0_CS16:.again wrt CGROUP16 + BS3_SET_BITS 16 +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_lidt_opsize_bx__sidt32_es_di__lidt_es_si__ud2) == 27) +BS3_PROC_END_CMN bs3CpuBasic2_lidt_opsize_bx__sidt32_es_di__lidt_es_si__ud2 +%endif + + %if TMPL_BITS == 64 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_lidt_rexw_bx__sidt_es_di__lidt_es_si__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_lidt_rexw_bx__sidt_es_di__lidt_es_si__ud2, BS3_PBC_NEAR + db X86_OP_REX_W + lidt [xBX] + sidt [xDI] + lidt [xSI] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_lidt_rexw_bx__sidt_es_di__lidt_es_si__ud2) == 10) +BS3_PROC_END_CMN bs3CpuBasic2_lidt_rexw_bx__sidt_es_di__lidt_es_si__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_lidt_opsize_rexw_bx__sidt_es_di__lidt_es_si__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_lidt_opsize_rexw_bx__sidt_es_di__lidt_es_si__ud2, BS3_PBC_NEAR + db X86_OP_PRF_SIZE_OP + db X86_OP_REX_W + lidt [xBX] + sidt [xDI] + lidt [xSI] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_lidt_opsize_rexw_bx__sidt_es_di__lidt_es_si__ud2) == 11) +BS3_PROC_END_CMN bs3CpuBasic2_lidt_opsize_rexw_bx__sidt_es_di__lidt_es_si__ud2 + %endif + + %if TMPL_BITS != 64 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_lidt_ss_bx__sidt_es_di__lidt_es_si__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_lidt_ss_bx__sidt_es_di__lidt_es_si__ud2, BS3_PBC_NEAR + lidt [ss:xBX] + sidt [BS3_NOT_64BIT(es:) xDI] + lidt [BS3_NOT_64BIT(es:) xSI] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_lidt_ss_bx__sidt_es_di__lidt_es_si__ud2) == 12) +BS3_PROC_END_CMN bs3CpuBasic2_lidt_ss_bx__sidt_es_di__lidt_es_si__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_lidt_opsize_ss_bx__sidt_es_di__lidt_es_si__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_lidt_opsize_ss_bx__sidt_es_di__lidt_es_si__ud2, BS3_PBC_NEAR + db X86_OP_PRF_SIZE_OP + lidt [ss:xBX] + sidt [BS3_NOT_64BIT(es:) xDI] + lidt [BS3_NOT_64BIT(es:) xSI] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_lidt_opsize_ss_bx__sidt_es_di__lidt_es_si__ud2) == 13) +BS3_PROC_END_CMN bs3CpuBasic2_lidt_opsize_ss_bx__sidt_es_di__lidt_es_si__ud2 + %endif + + +; +; LGDT +; +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_lgdt_bx__sgdt_es_di__lgdt_es_si__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_lgdt_bx__sgdt_es_di__lgdt_es_si__ud2, BS3_PBC_NEAR + lgdt [xBX] + sgdt [BS3_NOT_64BIT(es:) xDI] + lgdt [BS3_NOT_64BIT(es:) xSI] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_lgdt_bx__sgdt_es_di__lgdt_es_si__ud2) == BS3_IF_64BIT_OTHERWISE(9,11)) +BS3_PROC_END_CMN bs3CpuBasic2_lgdt_bx__sgdt_es_di__lgdt_es_si__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_lgdt_opsize_bx__sgdt_es_di__lgdt_es_si__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_lgdt_opsize_bx__sgdt_es_di__lgdt_es_si__ud2, BS3_PBC_NEAR + db X86_OP_PRF_SIZE_OP + lgdt [xBX] + sgdt [BS3_NOT_64BIT(es:) xDI] + lgdt [BS3_NOT_64BIT(es:) xSI] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_lgdt_opsize_bx__sgdt_es_di__lgdt_es_si__ud2) == BS3_IF_64BIT_OTHERWISE(10,12)) +BS3_PROC_END_CMN bs3CpuBasic2_lgdt_opsize_bx__sgdt_es_di__lgdt_es_si__ud2 + + %if TMPL_BITS == 64 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_lgdt_rexw_bx__sgdt_es_di__lgdt_es_si__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_lgdt_rexw_bx__sgdt_es_di__lgdt_es_si__ud2, BS3_PBC_NEAR + db X86_OP_REX_W + lgdt [xBX] + sgdt [xDI] + lgdt [xSI] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_lgdt_rexw_bx__sgdt_es_di__lgdt_es_si__ud2) == 10) +BS3_PROC_END_CMN bs3CpuBasic2_lgdt_rexw_bx__sgdt_es_di__lgdt_es_si__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_lgdt_opsize_rexw_bx__sgdt_es_di__lgdt_es_si__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_lgdt_opsize_rexw_bx__sgdt_es_di__lgdt_es_si__ud2, BS3_PBC_NEAR + db X86_OP_PRF_SIZE_OP + db X86_OP_REX_W + lgdt [xBX] + sgdt [xDI] + lgdt [xSI] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_lgdt_opsize_rexw_bx__sgdt_es_di__lgdt_es_si__ud2) == 11) +BS3_PROC_END_CMN bs3CpuBasic2_lgdt_opsize_rexw_bx__sgdt_es_di__lgdt_es_si__ud2 + %endif + + %if TMPL_BITS != 64 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_lgdt_ss_bx__sgdt_es_di__lgdt_es_si__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_lgdt_ss_bx__sgdt_es_di__lgdt_es_si__ud2, BS3_PBC_NEAR + lgdt [ss:xBX] + sgdt [BS3_NOT_64BIT(es:) xDI] + lgdt [BS3_NOT_64BIT(es:) xSI] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_lgdt_ss_bx__sgdt_es_di__lgdt_es_si__ud2) == 12) +BS3_PROC_END_CMN bs3CpuBasic2_lgdt_ss_bx__sgdt_es_di__lgdt_es_si__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_lgdt_opsize_ss_bx__sgdt_es_di__lgdt_es_si__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_lgdt_opsize_ss_bx__sgdt_es_di__lgdt_es_si__ud2, BS3_PBC_NEAR + db X86_OP_PRF_SIZE_OP + lgdt [ss:xBX] + sgdt [BS3_NOT_64BIT(es:) xDI] + lgdt [BS3_NOT_64BIT(es:) xSI] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_CMN_NM(bs3CpuBasic2_lgdt_opsize_ss_bx__sgdt_es_di__lgdt_es_si__ud2) == 13) +BS3_PROC_END_CMN bs3CpuBasic2_lgdt_opsize_ss_bx__sgdt_es_di__lgdt_es_si__ud2 + %endif ; TMPL_BITS != 64 + +; +; #PF & #AC +; + +; For testing read access. +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_mov_ax_ds_bx__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_mov_ax_ds_bx__ud2, BS3_PBC_NEAR + mov xAX, [xBX] +.again: ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 2 + (TMPL_BITS == 64)) +BS3_PROC_END_CMN bs3CpuBasic2_mov_ax_ds_bx__ud2 + + +; For testing write access. +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_mov_ds_bx_ax__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_mov_ds_bx_ax__ud2, BS3_PBC_NEAR + mov [xBX], xAX +.again: ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 2 + (TMPL_BITS == 64)) +BS3_PROC_END_CMN bs3CpuBasic2_mov_ds_bx_ax__ud2 + + +; For testing read+write access. +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_xchg_ds_bx_ax__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_xchg_ds_bx_ax__ud2, BS3_PBC_NEAR + xchg [xBX], xAX +.again: ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 2 + (TMPL_BITS == 64)) +BS3_PROC_END_CMN bs3CpuBasic2_xchg_ds_bx_ax__ud2 + + +; Another read+write access test. +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_cmpxchg_ds_bx_cx__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_cmpxchg_ds_bx_cx__ud2, BS3_PBC_NEAR + cmpxchg [xBX], xCX +.again: ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 3 + (TMPL_BITS == 64)) +BS3_PROC_END_CMN bs3CpuBasic2_cmpxchg_ds_bx_cx__ud2 + + +; For testing read access from an aborted instruction: DIV by zero +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_div_ds_bx__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_div_ds_bx__ud2, BS3_PBC_NEAR + div xPRE [xBX] +.again: ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 2 + (TMPL_BITS == 64)) +BS3_PROC_END_CMN bs3CpuBasic2_div_ds_bx__ud2 + +; For testing FLD m80 alignment (#AC). +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_fninit_fld_ds_bx__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_fninit_fld_ds_bx__ud2, BS3_PBC_NEAR + fninit ; make sure to not trigger a stack overflow. +.actual_test_instruction: + fld tword [xBX] +.again: ud2 + jmp .again +AssertCompile(.actual_test_instruction - BS3_LAST_LABEL == 2) +BS3_PROC_END_CMN bs3CpuBasic2_fninit_fld_ds_bx__ud2 + +; For testing FBLD m80 alignment (#AC). +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_fninit_fbld_ds_bx__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_fninit_fbld_ds_bx__ud2, BS3_PBC_NEAR + fninit ; make sure to not trigger a stack overflow. +.actual_test_instruction: + fbld tword [xBX] +.again: ud2 + jmp .again +AssertCompile(.actual_test_instruction - BS3_LAST_LABEL == 2) +BS3_PROC_END_CMN bs3CpuBasic2_fninit_fbld_ds_bx__ud2 + +; For testing FST m80 alignment (#AC). +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_fninit_fldz_fstp_ds_bx__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_fninit_fldz_fstp_ds_bx__ud2, BS3_PBC_NEAR + fninit ; make sure to not trigger a stack overflow. + fldz ; make sure we've got something to store +.actual_test_instruction: + fstp tword [xBX] +.again: ud2 + jmp .again +AssertCompile(.actual_test_instruction - BS3_LAST_LABEL == 4) +BS3_PROC_END_CMN bs3CpuBasic2_fninit_fldz_fstp_ds_bx__ud2 + +; For testing FXSAVE alignment (#AC/#GP). +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_fxsave_ds_bx__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_fxsave_ds_bx__ud2, BS3_PBC_NEAR + fxsave [xBX] +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_fxsave_ds_bx__ud2 + + +; Two memory operands: push [mem] +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_push_ds_bx__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_push_ds_bx__ud2, BS3_PBC_NEAR + push xPRE [xBX] +.again: ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 2) +BS3_PROC_END_CMN bs3CpuBasic2_push_ds_bx__ud2 + +; Two memory operands: pop [mem] +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_push_ax__pop_ds_bx__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_push_ax__pop_ds_bx__ud2, BS3_PBC_NEAR + push xAX + pop xPRE [xBX] +.again: ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 3) +BS3_PROC_END_CMN bs3CpuBasic2_push_ax__pop_ds_bx__ud2 + +; Two memory operands: call [mem] +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_call_ds_bx__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_call_ds_bx__ud2, BS3_PBC_NEAR + call xPRE [xBX] +.again: ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 2) +BS3_PROC_END_CMN bs3CpuBasic2_call_ds_bx__ud2 + +; For testing #GP vs #PF write +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_insb__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_insb__ud2, BS3_PBC_NEAR + insb +.again: ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 1) +BS3_PROC_END_CMN bs3CpuBasic2_insb__ud2 + + +;********************************************************************************************************************************* +;* Non-far JMP & CALL Tests (simple ones). * +;********************************************************************************************************************************* + +; jmp rel8 (forwards) +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmp_jb__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmp_jb__ud2, BS3_PBC_NEAR + jmp short .again +.post_jmp: + times 7 int3 +.again: ud2 + int3 + jmp .again +AssertCompile(.post_jmp - BS3_LAST_LABEL == 2) +BS3_PROC_END_CMN bs3CpuBasic2_jmp_jb__ud2 + + +; jmp rel8 (backwards) +BS3_GLOBAL_NAME_EX RT_CONCAT(BS3_CMN_NM(bs3CpuBasic2_jmp_jb_back__ud2),.again), function, 2 + ud2 + times 7 int3 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmp_jb_back__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmp_jb_back__ud2, BS3_PBC_NEAR + jmp short .again +.post_jmp: + int3 +AssertCompile(.post_jmp - BS3_LAST_LABEL == 2) +BS3_PROC_END_CMN bs3CpuBasic2_jmp_jb_back__ud2 + + +; jmp rel16 (forwards) +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmp_jv__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmp_jv__ud2, BS3_PBC_NEAR + jmp near .again +.post_jmp: + times 9 int3 +.again: ud2 + int3 + jmp .again + %if TMPL_BITS == 16 +AssertCompile(.post_jmp - BS3_LAST_LABEL == 3) + %else +AssertCompile(.post_jmp - BS3_LAST_LABEL == 5) + %endif +BS3_PROC_END_CMN bs3CpuBasic2_jmp_jv__ud2 + + +; jmp rel16 (backwards) +BS3_GLOBAL_NAME_EX RT_CONCAT(BS3_CMN_NM(bs3CpuBasic2_jmp_jv_back__ud2),.again), function, 2 + ud2 + times 6 int3 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmp_jv_back__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmp_jv_back__ud2, BS3_PBC_NEAR + jmp near .again +.post_jmp: + int3 + %if TMPL_BITS == 16 +AssertCompile(.post_jmp - BS3_LAST_LABEL == 3) + %else +AssertCompile(.post_jmp - BS3_LAST_LABEL == 5) + %endif +BS3_PROC_END_CMN bs3CpuBasic2_jmp_jv_back__ud2 + + +; jmp [indirect] +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmp_ind_mem__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmp_ind_mem__ud2, BS3_PBC_NEAR +%if TMPL_BITS == 16 + jmp [word cs:.npAgain] +%elif TMPL_BITS == 32 + jmp [dword cs:.npAgain] +%else + jmp [.npAgain] +%endif +.post_jmp: + times 9 int3 +.npAgain: + %if TMPL_BITS == 16 + dw BS3_TEXT16_WRT(.again) + %else + dd .again wrt FLAT + %if TMPL_BITS == 64 + dd 0 + %endif + %endif +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmp_ind_mem__ud2 + +; jmp [xAX] +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmp_ind_xAX__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmp_ind_xAX__ud2, BS3_PBC_NEAR + jmp xAX +.post_jmp: + times 17 int3 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmp_ind_xAX__ud2 + +; jmp [xDI] +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmp_ind_xDI__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmp_ind_xDI__ud2, BS3_PBC_NEAR + jmp xDI +.post_jmp: + times 17 int3 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmp_ind_xDI__ud2 + + %if TMPL_BITS == 64 +; jmp [xAX] +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmp_ind_r9__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmp_ind_r9__ud2, BS3_PBC_NEAR + jmp r9 +.post_jmp: + times 17 int3 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmp_ind_r9__ud2 + %endif + + +; call rel16/32 (forwards) +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_call_jv__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_call_jv__ud2, BS3_PBC_NEAR + call near .again +.post_call: + times 9 int3 +.again: ud2 + int3 + jmp .again + %if TMPL_BITS == 16 +AssertCompile(.post_call - BS3_LAST_LABEL == 3) + %else +AssertCompile(.post_call - BS3_LAST_LABEL == 5) + %endif +BS3_PROC_END_CMN bs3CpuBasic2_call_jv__ud2 + +; call rel16/32 (backwards) +BS3_GLOBAL_NAME_EX RT_CONCAT(BS3_CMN_NM(bs3CpuBasic2_call_jv_back__ud2),.again), function, 2 + ud2 + times 6 int3 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_call_jv_back__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_call_jv_back__ud2, BS3_PBC_NEAR + call near .again +.post_call: + int3 + %if TMPL_BITS == 16 +AssertCompile(.post_call - BS3_LAST_LABEL == 3) + %else +AssertCompile(.post_call - BS3_LAST_LABEL == 5) + %endif +BS3_PROC_END_CMN bs3CpuBasic2_call_jv_back__ud2 + +; call [indirect] +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_call_ind_mem__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_call_ind_mem__ud2, BS3_PBC_NEAR +%if TMPL_BITS == 16 + call [word cs:.npAgain] +%elif TMPL_BITS == 32 + call [dword cs:.npAgain] +%else + call [.npAgain] +%endif +.post_call: + times 9 int3 +.npAgain: + %if TMPL_BITS == 16 + dw BS3_TEXT16_WRT(.again) + %else + dd .again wrt FLAT + %if TMPL_BITS == 64 + dd 0 + %endif + %endif +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_call_ind_mem__ud2 + +; call [xAX] +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_call_ind_xAX__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_call_ind_xAX__ud2, BS3_PBC_NEAR + call xAX +.post_call: + times 17 int3 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_call_ind_xAX__ud2 + +; call [xDI] +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_call_ind_xDI__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_call_ind_xDI__ud2, BS3_PBC_NEAR + call xDI +.post_call: + times 17 int3 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_call_ind_xDI__ud2 + + %if TMPL_BITS == 64 +; call [xAX] +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_call_ind_r9__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_call_ind_r9__ud2, BS3_PBC_NEAR + call r9 +.post_call: + times 17 int3 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_call_ind_r9__ud2 + %endif + + +; +; When applying opsize, we need to put this in the 16-bit text segment to +; better control where we end up in 32-bit and 64-bit mode. +; +; Try keep the code out of the IVT and BIOS data area. We ASSUME that the +; BS3TEXT16 segment portion in this object file will be at the start of the +; image, so we won't waste much space padding up to offset 0x600. +; +BS3_BEGIN_TEXT16 TMPL_BITS +%if TMPL_BITS == 32 + %assign here ($ - $$) + %if here < 0x600 + times (0x600 - here) int3 + %endif +%endif +BS3_GLOBAL_NAME_EX BS3_CMN_NM(bs3CpuBasic2_jmp_opsize_begin), , 1 + + +; jmp rel8 (forwards) with opsize override. +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmp_jb_opsize__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmp_jb_opsize__ud2, BS3_PBC_NEAR + db 66h + jmp short .again +.post_jmp: + times 8 int3 +.again: ud2 + int3 + jmp .again +AssertCompile(.post_jmp - BS3_LAST_LABEL == 3) +BS3_PROC_END_CMN bs3CpuBasic2_jmp_jb_opsize__ud2 + + +; jmp rel8 (backwards) with opsize override. +BS3_GLOBAL_NAME_EX RT_CONCAT(BS3_CMN_NM(bs3CpuBasic2_jmp_jb_opsize_back__ud2),.again), function, 2 + ud2 + times 19 int3 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmp_jb_opsize_back__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmp_jb_opsize_back__ud2, BS3_PBC_NEAR + db 66h + jmp short .again +.post_jmp: + int3 +AssertCompile(.post_jmp - BS3_LAST_LABEL == 3) +BS3_PROC_END_CMN bs3CpuBasic2_jmp_jb_opsize_back__ud2 + + +; jmp rel16 (forwards) with opsize override. +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmp_jv_opsize__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmp_jv_opsize__ud2, BS3_PBC_NEAR + db 66h, 0e9h ; o32 jmp near .again + %if TMPL_BITS != 32 + dd 11 + %else + dw 11 + %endif +.post_jmp: + times 11 int3 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmp_jv_opsize__ud2 + + +; jmp rel16 (backwards) with opsize override. +BS3_GLOBAL_NAME_EX RT_CONCAT(BS3_CMN_NM(bs3CpuBasic2_jmp_jv_opsize_back__ud2),.again), function, 2 + ud2 + times 19 int3 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmp_jv_opsize_back__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmp_jv_opsize_back__ud2, BS3_PBC_NEAR + %if TMPL_BITS != 32 + db 66h, 0e9h ; o32 jmp near .again + dd RT_CONCAT(BS3_CMN_NM(bs3CpuBasic2_jmp_jv_opsize_back__ud2),.again) - .post_jmp + %else + db 66h, 0e9h ; o16 jmp near .again + dw RT_CONCAT(BS3_CMN_NM(bs3CpuBasic2_jmp_jv_opsize_back__ud2),.again) - .post_jmp + %endif +.post_jmp: + int3 +BS3_PROC_END_CMN bs3CpuBasic2_jmp_jv_opsize_back__ud2 + + +; jmp [indirect] +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmp_ind_mem_opsize__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmp_ind_mem_opsize__ud2, BS3_PBC_NEAR + db 66h + %if TMPL_BITS == 16 + jmp [word cs:.npAgain] + %elif TMPL_BITS == 32 + jmp [dword cs:.npAgain wrt FLAT] + %else + jmp [.npAgain wrt FLAT] + %endif +.post_jmp: + times 9 int3 +.npAgain: + %if TMPL_BITS == 16 + dw BS3_TEXT16_WRT(.again) + dw 0 + %else + dw .again wrt CGROUP16 + dw 0faceh, 0f00dh, 07777h ; non-canonical address + %endif +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmp_ind_mem_opsize__ud2 + + %if TMPL_BITS == 64 +; jmp [indirect] - 64-bit intel version +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmp_ind_mem_opsize__ud2__intel +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmp_ind_mem_opsize__ud2__intel, BS3_PBC_NEAR + db 66h + jmp [.npAgain wrt FLAT] +.post_jmp: + times 8 int3 +.npAgain: + dd .again wrt FLAT + dd 0 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmp_ind_mem_opsize__ud2__intel + %endif + +; jmp [xAX] +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmp_ind_xAX_opsize__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmp_ind_xAX_opsize__ud2, BS3_PBC_NEAR + db 66h + jmp xAX +.post_jmp: + times 8 int3 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmp_ind_xAX_opsize__ud2 + + +; call rel16/32 (forwards) with opsize override. +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_call_jv_opsize__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_call_jv_opsize__ud2, BS3_PBC_NEAR + db 66h, 0e8h ; o32 jmp near .again + %if TMPL_BITS != 32 + dd 12 + %else + dw 12 + %endif +.post_call: + times 12 int3 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_call_jv_opsize__ud2 + + +; call rel16/32 (backwards) with opsize override. +BS3_GLOBAL_NAME_EX RT_CONCAT(BS3_CMN_NM(bs3CpuBasic2_call_jv_opsize_back__ud2),.again), function, 2 + ud2 + times 19 int3 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_call_jv_opsize_back__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_call_jv_opsize_back__ud2, BS3_PBC_NEAR + %if TMPL_BITS != 32 + db 66h, 0e8h ; o32 call near .again + dd RT_CONCAT(BS3_CMN_NM(bs3CpuBasic2_call_jv_opsize_back__ud2),.again) - .post_call + %else + db 66h, 0e8h ; o16 call near .again + dw RT_CONCAT(BS3_CMN_NM(bs3CpuBasic2_call_jv_opsize_back__ud2),.again) - .post_call + %endif +.post_call: + int3 +BS3_PROC_END_CMN bs3CpuBasic2_call_jv_opsize_back__ud2 + +; call [indirect] +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_call_ind_mem_opsize__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_call_ind_mem_opsize__ud2, BS3_PBC_NEAR + db 66h + %if TMPL_BITS == 16 + call [word cs:.npAgain] + %elif TMPL_BITS == 32 + call [dword cs:.npAgain wrt FLAT] + %else + call [.npAgain wrt FLAT] + %endif +.post_call: + times 9 int3 +.npAgain: + %if TMPL_BITS == 16 + dw BS3_TEXT16_WRT(.again) + dw 0 + %else + dw .again wrt CGROUP16 + dw 0faceh, 0f00dh, 07777h ; non-canonical address + %endif +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_call_ind_mem_opsize__ud2 + + %if TMPL_BITS == 64 +; call [indirect] - 64-bit intel version +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_call_ind_mem_opsize__ud2__intel +BS3_PROC_BEGIN_CMN bs3CpuBasic2_call_ind_mem_opsize__ud2__intel, BS3_PBC_NEAR + db 66h + call [.npAgain wrt FLAT] +.post_call: + times 8 int3 +.npAgain: + dd .again wrt FLAT + dd 0 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_call_ind_mem_opsize__ud2__intel + %endif + +; call [xAX] +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_call_ind_xAX_opsize__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_call_ind_xAX_opsize__ud2, BS3_PBC_NEAR + db 66h + call xAX +.post_call: + times 8 int3 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_call_ind_xAX_opsize__ud2 + + +; +; Mark the end of the opsize jmp section. +; +BS3_GLOBAL_NAME_EX BS3_CMN_NM(bs3CpuBasic2_jmp_opsize_end), , 1 + int3 +TMPL_BEGIN_TEXT + + +;********************************************************************************************************************************* +;* FAR JMP ABS * +;********************************************************************************************************************************* + +; +; Mark the start of the opsize far jmp/call section. +; +; Here we also need to keep the 16-bit code out of the IVT and BIOS data area, +; not just the 32-bit and 64-bit code like for the above opsize cases. +; +BS3_BEGIN_TEXT16 TMPL_BITS + %assign here $-$$ +%if here < 0x600 + times (0x600 - here) int3 +%endif +BS3_GLOBAL_NAME_EX BS3_CMN_NM(bs3CpuBasic2_far_jmp_call_opsize_begin), , 1 + int3 +TMPL_BEGIN_TEXT + + %if TMPL_BITS == 16 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmpf_ptr_rm__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmpf_ptr_rm__ud2, BS3_PBC_NEAR + db 0eah + dw .again wrt CGROUP16 + dw BS3_SEL_TEXT16 +.post_jmp: + times 2 int3 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmpf_ptr_rm__ud2 + %endif + + %if TMPL_BITS != 64 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmpf_ptr_same_r0__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmpf_ptr_same_r0__ud2, BS3_PBC_NEAR + db 0eah + %if TMPL_BITS == 16 + dw .again wrt CGROUP16 + dw BS3_SEL_R0_CS16 + %else + dd .again wrt FLAT + dw BS3_SEL_R0_CS32 + %endif +.post_jmp: + times 7 int3 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmpf_ptr_same_r0__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmpf_ptr_same_r1__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmpf_ptr_same_r1__ud2, BS3_PBC_NEAR + db 0eah ; inter privilege jmp -> #GP(dst-cs) + %if TMPL_BITS == 16 + dw .again wrt CGROUP16 + dw BS3_SEL_R1_CS16 | 1 + %else + dd .again wrt FLAT + dw BS3_SEL_R1_CS32 | 1 + %endif +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmpf_ptr_same_r1__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmpf_ptr_same_r2__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmpf_ptr_same_r2__ud2, BS3_PBC_NEAR + db 0eah ; inter privilege jmp -> #GP(dst-cs) + %if TMPL_BITS == 16 + dw .again wrt CGROUP16 + dw BS3_SEL_R2_CS16 | 2 + %else + dd .again wrt FLAT + dw BS3_SEL_R2_CS32 | 2 + %endif +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmpf_ptr_same_r2__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmpf_ptr_same_r3__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmpf_ptr_same_r3__ud2, BS3_PBC_NEAR + db 0eah ; inter privilege jmp -> #GP(dst-cs) + %if TMPL_BITS == 16 + dw .again wrt CGROUP16 + dw BS3_SEL_R3_CS16 | 3 + %else + dd .again wrt FLAT + dw BS3_SEL_R3_CS32 | 3 + %endif +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmpf_ptr_same_r3__ud2 + +BS3_BEGIN_TEXT16 TMPL_BITS +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmpf_ptr_opsize_flipbit_r0__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmpf_ptr_opsize_flipbit_r0__ud2, BS3_PBC_NEAR + db 066h, 0eah + %if TMPL_BITS == 32 + dw .again wrt CGROUP16 + dw BS3_SEL_R0_CS16 + %else + dd .again wrt FLAT + dw BS3_SEL_R0_CS32 + %endif + times 4 int3 +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmpf_ptr_opsize_flipbit_r0__ud2 +TMPL_BEGIN_TEXT + +; Do a jmp to BS3_SEL_R0_CS64. Except for when we're in long mode, this will +; result in a 16-bit CS with zero base and 4G limit. +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmpf_ptr_r0_cs64__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmpf_ptr_r0_cs64__ud2, BS3_PBC_NEAR + %if TMPL_BITS == 16 + db 066h + %endif + db 0eah + dd .jmp_target wrt FLAT + dw BS3_SEL_R0_CS64 + times 8 int3 +.jmp_target: + salc ; #UD in 64-bit mode +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmpf_ptr_r0_cs64__ud2 + +BS3_BEGIN_TEXT16 TMPL_BITS +; Variation of the previous with a CS16 copy that has the L bit set, emulating +; pre-AMD64 software using the L bit for other stuff. (Don't run in long mode +; w/o copying the 3 bytes to the 0xxxxh memory range.) +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmpf_ptr_r0_cs16l__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmpf_ptr_r0_cs16l__ud2, BS3_PBC_NEAR + %if TMPL_BITS != 16 + db 066h + %endif + db 0eah + dw .jmp_target wrt CGROUP16 + dw BS3_SEL_SPARE_00 ; ASSUMES this is set up as CGROUP16 but with L=1. + times 3 int3 +.jmp_target: + salc ; #UD in 64-bit mode +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmpf_ptr_r0_cs16l__ud2 +TMPL_BEGIN_TEXT + + %endif ; TMPL_BITS != 64 + + + +;********************************************************************************************************************************* +;* FAR CALL ABS * +;********************************************************************************************************************************* + + %if TMPL_BITS == 16 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_callf_ptr_rm__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_callf_ptr_rm__ud2, BS3_PBC_NEAR + db 09ah + dw .again wrt CGROUP16 + dw BS3_SEL_TEXT16 +.post_call: + times 2 int3 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_callf_ptr_rm__ud2 + %endif + + %if TMPL_BITS != 64 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_callf_ptr_same_r0__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_callf_ptr_same_r0__ud2, BS3_PBC_NEAR + db 09ah + %if TMPL_BITS == 16 + dw .again wrt CGROUP16 + dw BS3_SEL_R0_CS16 + %else + dd .again wrt FLAT + dw BS3_SEL_R0_CS32 + %endif +.post_call: + times 7 int3 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_callf_ptr_same_r0__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_callf_ptr_same_r1__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_callf_ptr_same_r1__ud2, BS3_PBC_NEAR + db 09ah + %if TMPL_BITS == 16 + dw .again wrt CGROUP16 + dw BS3_SEL_R1_CS16 | 1 + %else + dd .again wrt FLAT + dw BS3_SEL_R1_CS32 | 1 + %endif +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_callf_ptr_same_r1__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_callf_ptr_same_r2__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_callf_ptr_same_r2__ud2, BS3_PBC_NEAR + db 09ah + %if TMPL_BITS == 16 + dw .again wrt CGROUP16 + dw BS3_SEL_R2_CS16 | 2 + %else + dd .again wrt FLAT + dw BS3_SEL_R2_CS32 | 2 + %endif +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_callf_ptr_same_r2__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_callf_ptr_same_r3__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_callf_ptr_same_r3__ud2, BS3_PBC_NEAR + db 09ah + %if TMPL_BITS == 16 + dw .again wrt CGROUP16 + dw BS3_SEL_R3_CS16 | 3 + %else + dd .again wrt FLAT + dw BS3_SEL_R3_CS32 | 3 + %endif +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_callf_ptr_same_r3__ud2 + +BS3_BEGIN_TEXT16 TMPL_BITS +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_callf_ptr_opsize_flipbit_r0__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_callf_ptr_opsize_flipbit_r0__ud2, BS3_PBC_NEAR + db 066h, 09ah + %if TMPL_BITS == 32 + dw .again wrt CGROUP16 + dw BS3_SEL_R0_CS16 + %else + dd .again wrt FLAT + dw BS3_SEL_R0_CS32 + %endif + times 4 int3 +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_callf_ptr_opsize_flipbit_r0__ud2 +TMPL_BEGIN_TEXT + +; Do a call to BS3_SEL_R0_CS64. Except for when we're in long mode, this will +; result in a 16-bit CS with zero base and 4G limit. +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_callf_ptr_r0_cs64__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_callf_ptr_r0_cs64__ud2, BS3_PBC_NEAR + %if TMPL_BITS == 16 + db 066h + %endif + db 09ah + dd .call_target wrt FLAT + dw BS3_SEL_R0_CS64 + times 8 int3 +.call_target: + salc ; #UD in 64-bit mode +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_callf_ptr_r0_cs64__ud2 + +BS3_BEGIN_TEXT16 TMPL_BITS +; Variation of the previous with a CS16 copy that has the L bit set, emulating +; pre-AMD64 software using the L bit for other stuff. (Don't run in long mode +; w/o copying the 3 bytes to the 0xxxxh memory range.) +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_callf_ptr_r0_cs16l__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_callf_ptr_r0_cs16l__ud2, BS3_PBC_NEAR + %if TMPL_BITS != 16 + db 066h + %endif + db 09ah + dw .call_target wrt CGROUP16 + dw BS3_SEL_SPARE_00 ; ASSUMES this is set up as CGROUP16 but with L=1. + times 3 int3 +.call_target: + salc ; #UD in 64-bit mode +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_callf_ptr_r0_cs16l__ud2 +TMPL_BEGIN_TEXT + + %endif ; TMPL_BITS != 64 + + +;********************************************************************************************************************************* +;* INDIRECT FAR JMP * +;********************************************************************************************************************************* + + %if TMPL_BITS == 16 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmpf_mem_rm__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmpf_mem_rm__ud2, BS3_PBC_NEAR + jmp far [BS3_CPUBAS2_REF_LABEL_VIA_CS(.fpfn)] + int3 +.fpfn: + dw .again wrt CGROUP16 + dw BS3_SEL_TEXT16 +.post_jmp: + times 2 int3 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmpf_mem_rm__ud2 + %endif + +;; +; Since AMD and Intel treat REX.W differently, we need two versions of the +; test functions here and use a macro to accomplish that. +; +; @param 1 Symbol suffix +; @param 2 0 for AMD, 1 for Intel. +; +%ifnmacro jmpf_macro +%macro jmpf_macro 2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmpf_mem_same_r0__ud2 %+ %1 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmpf_mem_same_r0__ud2 %+ %1, BS3_PBC_NEAR + BS3_CPUBAS2_JMP_FAR_MEM_LABEL .fpfn, 2 +.fpfn: + %if TMPL_BITS == 16 + dw .again wrt CGROUP16 + dw BS3_SEL_R0_CS16 + %elif TMPL_BITS == 32 + dd .again wrt FLAT + dw BS3_SEL_R0_CS32 + %else + dd .again wrt FLAT + %if %2 != 0 + dd 0fffff000h + %endif + dw BS3_SEL_R0_CS64 + %endif +.post_jmp: + times 7 int3 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmpf_mem_same_r0__ud2 %+ %1 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmpf_mem_same_r1__ud2 %+ %1 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmpf_mem_same_r1__ud2 %+ %1, BS3_PBC_NEAR + BS3_CPUBAS2_JMP_FAR_MEM_LABEL .fpfn, 2 +.fpfn: + %if TMPL_BITS == 16 + dw .again wrt CGROUP16 + dw BS3_SEL_R1_CS16 | 1 + %elif TMPL_BITS == 32 + dd .again wrt FLAT + dw BS3_SEL_R1_CS32 | 1 + %else + dd .again wrt FLAT + %if %2 != 0 + dd 0fffff000h + %endif + dw BS3_SEL_R1_CS64 | 1 + %endif +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmpf_mem_same_r1__ud2 %+ %1 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmpf_mem_same_r2__ud2 %+ %1 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmpf_mem_same_r2__ud2 %+ %1, BS3_PBC_NEAR + BS3_CPUBAS2_JMP_FAR_MEM_LABEL .fpfn, 0 +.fpfn: + %if TMPL_BITS == 16 + dw .again wrt CGROUP16 + dw BS3_SEL_R2_CS16 | 2 + %elif TMPL_BITS == 32 + dd .again wrt FLAT + dw BS3_SEL_R2_CS32 | 2 + %else + dd .again wrt FLAT + dw BS3_SEL_R2_CS64 | 2 + %endif +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmpf_mem_same_r2__ud2 %+ %1 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmpf_mem_same_r3__ud2 %+ %1 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmpf_mem_same_r3__ud2 %+ %1, BS3_PBC_NEAR + BS3_CPUBAS2_JMP_FAR_MEM_LABEL .fpfn, 2 +.fpfn: + %if TMPL_BITS == 16 + dw .again wrt CGROUP16 + dw BS3_SEL_R3_CS16 | 3 + %elif TMPL_BITS == 32 + dd .again wrt FLAT + dw BS3_SEL_R3_CS32 | 3 + %else + dd .again wrt FLAT + %if %2 != 0 + dd 0fffff000h + %endif + dw BS3_SEL_R3_CS64 | 3 + %endif +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmpf_mem_same_r3__ud2 %+ %1 + +BS3_BEGIN_TEXT16 TMPL_BITS +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmpf_mem_r0_cs16__ud2 %+ %1 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmpf_mem_r0_cs16__ud2 %+ %1, BS3_PBC_NEAR + BS3_CPUBAS2_JMP_FAR_MEM_LABEL .fpfn, (TMPL_BITS != 16) ; TMPL_BITS != 16 ? 1 : 0 +.fpfn: + dw .again wrt CGROUP16 + dw BS3_SEL_R0_CS16 + times 4 int3 +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmpf_mem_r0_cs16__ud2 %+ %1 +TMPL_BEGIN_TEXT + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmpf_mem_r0_cs32__ud2 %+ %1 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmpf_mem_r0_cs32__ud2 %+ %1, BS3_PBC_NEAR + BS3_CPUBAS2_JMP_FAR_MEM_LABEL .fpfn, (TMPL_BITS == 16) ; TMPL_BITS == 16 ? 1 : 0 +.fpfn: + dd .again wrt FLAT + dw BS3_SEL_R0_CS32 + times 4 int3 +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmpf_mem_r0_cs32__ud2 %+ %1 + +; Do a jmp to BS3_SEL_R0_CS64. Except for when we're in long mode, this will +; result in a 16-bit CS with zero base and 4G limit. +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmpf_mem_r0_cs64__ud2 %+ %1 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmpf_mem_r0_cs64__ud2 %+ %1, BS3_PBC_NEAR + BS3_CPUBAS2_JMP_FAR_MEM_LABEL .fpfn, (2 - (TMPL_BITS == 16)) ; TMPL_BITS == 16 ? 1 : 2 +.fpfn: + dd .jmp_target wrt FLAT + %if TMPL_BITS == 64 && %2 != 0 + dd 0fffff000h + %endif + dw BS3_SEL_R0_CS64 + times 8 int3 +.jmp_target: + %if TMPL_BITS != 64 + salc ; #UD in 64-bit mode + %endif +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmpf_mem_r0_cs64__ud2 %+ %1 + +BS3_BEGIN_TEXT16 TMPL_BITS +; Variation of the previous with a CS16 copy that has the L bit set, emulating +; pre-AMD64 software using the L bit for other stuff. (Don't run _c16/32 in +; long mode w/o copying the 3 bytes to the 0xxxxh memory range.) +; The _c64 version will test that the base is ignored. +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_jmpf_mem_r0_cs16l__ud2 %+ %1 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_jmpf_mem_r0_cs16l__ud2 %+ %1, BS3_PBC_NEAR + BS3_CPUBAS2_JMP_FAR_MEM_LABEL .fpfn, (TMPL_BITS == 32) ; TMPL_BITS == 32 ? 1 : 0 +.fpfn: + %if TMPL_BITS != 64 + dw .jmp_target wrt CGROUP16 + %else + dd .jmp_target wrt FLAT + %endif + dw BS3_SEL_SPARE_00 ; ASSUMES this is set up as CGROUP16 but with L=1. + times 3 int3 +.jmp_target: + %if TMPL_BITS != 64 + salc ; #UD in 64-bit mode + %endif +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_jmpf_mem_r0_cs16l__ud2 %+ %1 +TMPL_BEGIN_TEXT + +%endmacro +%endif + +; Instantiate the above code +jmpf_macro , 0 + %if TMPL_BITS == 64 +jmpf_macro _intel, 1 + %endif + + +;********************************************************************************************************************************* +;* INDIRECT FAR CALL * +;********************************************************************************************************************************* + + %if TMPL_BITS == 16 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_callf_mem_rm__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_callf_mem_rm__ud2, BS3_PBC_NEAR + call far [BS3_CPUBAS2_REF_LABEL_VIA_CS(.fpfn)] + int3 +.fpfn: + dw .again wrt CGROUP16 + dw BS3_SEL_TEXT16 +.post_jmp: + times 2 int3 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_callf_mem_rm__ud2 + %endif + + +;; +; Since AMD and Intel treat REX.W differently, we need two versions of the +; test functions here and use a macro to accomplish that. +; +; @param 1 Symbol suffix +; @param 2 0 for AMD, 1 for Intel. +; +%ifnmacro callf_macro +%macro callf_macro 2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_callf_mem_same_r0__ud2 %+ %1 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_callf_mem_same_r0__ud2 %+ %1, BS3_PBC_NEAR + BS3_CPUBAS2_CALL_FAR_MEM_LABEL .fpfn, 2 +.fpfn: + %if TMPL_BITS == 16 + dw .again wrt CGROUP16 + dw BS3_SEL_R0_CS16 + %elif TMPL_BITS == 32 + dd .again wrt FLAT + dw BS3_SEL_R0_CS32 + %else + dd .again wrt FLAT + %if %2 != 0 + dd 0fffff000h + %endif + dw BS3_SEL_R0_CS64 + %endif +.post_call: + times 7 int3 +.again: ud2 + int3 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_callf_mem_same_r0__ud2 %+ %1 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_callf_mem_same_r1__ud2 %+ %1 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_callf_mem_same_r1__ud2 %+ %1, BS3_PBC_NEAR + BS3_CPUBAS2_CALL_FAR_MEM_LABEL .fpfn, 2 +.fpfn: + %if TMPL_BITS == 16 + dw .again wrt CGROUP16 + dw BS3_SEL_R1_CS16 | 1 + %elif TMPL_BITS == 32 + dd .again wrt FLAT + dw BS3_SEL_R1_CS32 | 1 + %else + dd .again wrt FLAT + %if %2 != 0 + dd 0fffff000h + %endif + dw BS3_SEL_R1_CS64 | 1 + %endif +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_callf_mem_same_r1__ud2 %+ %1 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_callf_mem_same_r2__ud2 %+ %1 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_callf_mem_same_r2__ud2 %+ %1, BS3_PBC_NEAR + BS3_CPUBAS2_CALL_FAR_MEM_LABEL .fpfn, 0 +.fpfn: + %if TMPL_BITS == 16 + dw .again wrt CGROUP16 + dw BS3_SEL_R2_CS16 | 2 + %elif TMPL_BITS == 32 + dd .again wrt FLAT + dw BS3_SEL_R2_CS32 | 2 + %else + dd .again wrt FLAT + dw BS3_SEL_R2_CS64 | 2 + %endif +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_callf_mem_same_r2__ud2 %+ %1 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_callf_mem_same_r3__ud2 %+ %1 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_callf_mem_same_r3__ud2 %+ %1, BS3_PBC_NEAR + BS3_CPUBAS2_CALL_FAR_MEM_LABEL .fpfn, 2 +.fpfn: + %if TMPL_BITS == 16 + dw .again wrt CGROUP16 + dw BS3_SEL_R3_CS16 | 3 + %elif TMPL_BITS == 32 + dd .again wrt FLAT + dw BS3_SEL_R3_CS32 | 3 + %else + dd .again wrt FLAT + %if %2 != 0 + dd 0fffff000h + %endif + dw BS3_SEL_R3_CS64 | 3 + %endif +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_callf_mem_same_r3__ud2 %+ %1 + +BS3_BEGIN_TEXT16 TMPL_BITS +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_callf_mem_r0_cs16__ud2 %+ %1 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_callf_mem_r0_cs16__ud2 %+ %1, BS3_PBC_NEAR + BS3_CPUBAS2_CALL_FAR_MEM_LABEL .fpfn, (TMPL_BITS != 16) ; (TMPL_BITS == 16 ? 0 : 1) +.fpfn: + dw .again wrt CGROUP16 + dw BS3_SEL_R0_CS16 + times 4 int3 +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_callf_mem_r0_cs16__ud2 %+ %1 +TMPL_BEGIN_TEXT + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_callf_mem_r0_cs32__ud2 %+ %1 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_callf_mem_r0_cs32__ud2 %+ %1, BS3_PBC_NEAR + BS3_CPUBAS2_CALL_FAR_MEM_LABEL .fpfn, (TMPL_BITS == 16) ; (TMPL_BITS == 16 ? 1 : 0) +.fpfn: + dd .again wrt FLAT + dw BS3_SEL_R0_CS32 + times 4 int3 +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_callf_mem_r0_cs32__ud2 %+ %1 + +; Do a call to BS3_SEL_R0_CS64. Except for when we're in long mode, this will +; result in a 16-bit CS with zero base and 4G limit. +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_callf_mem_r0_cs64__ud2 %+ %1 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_callf_mem_r0_cs64__ud2 %+ %1, BS3_PBC_NEAR + BS3_CPUBAS2_CALL_FAR_MEM_LABEL .fpfn, (2 - (TMPL_BITS == 16)) ; (TMPL_BITS == 16 ? 1 : 2) +.fpfn: + dd .call_target wrt FLAT + %if TMPL_BITS == 64 && %2 != 0 + dd 0fffff000h + %endif + dw BS3_SEL_R0_CS64 + times 8 int3 +.call_target: + %if TMPL_BITS != 64 + salc ; #UD in 64-bit mode + %endif +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_callf_mem_r0_cs64__ud2 %+ %1 + +BS3_BEGIN_TEXT16 TMPL_BITS +; Variation of the previous with a CS16 copy that has the L bit set, emulating +; pre-AMD64 software using the L bit for other stuff. (Don't run _c16/32 in +; long mode w/o copying the 3 bytes to the 0xxxxh memory range.) +; The _c64 version will test that the base is ignored. +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_callf_mem_r0_cs16l__ud2 %+ %1 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_callf_mem_r0_cs16l__ud2 %+ %1, BS3_PBC_NEAR + BS3_CPUBAS2_CALL_FAR_MEM_LABEL .fpfn, (TMPL_BITS == 32) ; (TMPL_BITS == 32 ? 1 : 0) +.fpfn: + %if TMPL_BITS != 64 + dw .call_target wrt CGROUP16 + %else + dd .call_target wrt FLAT + %endif + dw BS3_SEL_SPARE_00 ; ASSUMES this is set up as CGROUP16 but with L=1. + times 3 int3 +.call_target: + %if TMPL_BITS != 64 + salc ; #UD in 64-bit mode + %endif +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_callf_mem_r0_cs16l__ud2 %+ %1 +TMPL_BEGIN_TEXT + +%endmacro ; callf_macro +%endif + +; Instantiate the above code +callf_macro , 0 + %if TMPL_BITS == 64 +callf_macro _intel, 1 + %endif + +; +; Mark the end of the opsize far jmp/call section. +; +BS3_BEGIN_TEXT16 TMPL_BITS +BS3_GLOBAL_NAME_EX BS3_CMN_NM(bs3CpuBasic2_far_jmp_call_opsize_end), , 1 + int3 +TMPL_BEGIN_TEXT + + +;********************************************************************************************************************************* +;* Near RET * +;********************************************************************************************************************************* +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_retn__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retn__ud2, BS3_PBC_NEAR + ret +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_retn__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_retn_i24__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retn_i24__ud2, BS3_PBC_NEAR + ret 24 +.again: ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 3) +BS3_PROC_END_CMN bs3CpuBasic2_retn_i24__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_retn_i0__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retn_i0__ud2, BS3_PBC_NEAR + ret 0 +.again: ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 3) +BS3_PROC_END_CMN bs3CpuBasic2_retn_i0__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_retn_i760__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retn_i760__ud2, BS3_PBC_NEAR + ret 760 +.again: ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 3) +BS3_PROC_END_CMN bs3CpuBasic2_retn_i760__ud2 + + %if TMPL_BITS == 64 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_retn_rexw__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retn_rexw__ud2, BS3_PBC_NEAR + db 048h ; REX.W + ret +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_retn_rexw__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_retn_i24_rexw__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retn_i24_rexw__ud2, BS3_PBC_NEAR + db 048h ; REX.W + ret 24 +.again: ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 4) +BS3_PROC_END_CMN bs3CpuBasic2_retn_i24_rexw__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_retn_opsize_rexw__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retn_opsize_rexw__ud2, BS3_PBC_NEAR + db 66h, 048h + ret +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_retn_opsize_rexw__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_retn_i24_opsize_rexw__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retn_i24_opsize_rexw__ud2, BS3_PBC_NEAR + db 66h, 048h + ret 24 +.again: ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 5) +BS3_PROC_END_CMN bs3CpuBasic2_retn_i24_opsize_rexw__ud2 + + %endif + +; Mark the start of opsize tests as we end up below 64K in 32-bit and 64-bit when used. +BS3_BEGIN_TEXT16 TMPL_BITS +BS3_GLOBAL_NAME_EX BS3_CMN_NM(bs3CpuBasic2_retn_opsize_begin), , 1 + int3 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_retn_opsize__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retn_opsize__ud2, BS3_PBC_NEAR + db 66h + ret +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_retn_opsize__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_retn_i24_opsize__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retn_i24_opsize__ud2, BS3_PBC_NEAR + db 66h + ret 24 +.again: ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 4) +BS3_PROC_END_CMN bs3CpuBasic2_retn_i24_opsize__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_retn_i0_opsize__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retn_i0_opsize__ud2, BS3_PBC_NEAR + db 66h + ret 0 +.again: ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 4) +BS3_PROC_END_CMN bs3CpuBasic2_retn_i0_opsize__ud2 + + %if TMPL_BITS == 64 +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_retn_rexw_opsize__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retn_rexw_opsize__ud2, BS3_PBC_NEAR + db 048h, 66h + ret +.again: ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuBasic2_retn_rexw_opsize__ud2 + +BS3_CPUBAS2_UD_OFF bs3CpuBasic2_retn_i24_rexw_opsize__ud2 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retn_i24_rexw_opsize__ud2, BS3_PBC_NEAR + db 048h, 66h + ret 24 +.again: ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 5) +BS3_PROC_END_CMN bs3CpuBasic2_retn_i24_rexw_opsize__ud2 + %endif + +; End of opsize tests. +BS3_GLOBAL_NAME_EX BS3_CMN_NM(bs3CpuBasic2_retn_opsize_end), , 1 + int3 +TMPL_BEGIN_TEXT + + +;********************************************************************************************************************************* +;* FAR RET * +;********************************************************************************************************************************* +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retf, BS3_PBC_NEAR + db 0cbh ; retf +BS3_PROC_END_CMN bs3CpuBasic2_retf + +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retf_opsize, BS3_PBC_NEAR + db 066h, 0cbh ; o32/o16 retf +BS3_PROC_END_CMN bs3CpuBasic2_retf_opsize + +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retf_i32, BS3_PBC_NEAR + db 0cah, 20h, 0 ; retf 32 +BS3_PROC_END_CMN bs3CpuBasic2_retf_i32 + +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retf_i32_opsize, BS3_PBC_NEAR + db 066h, 0cah, 20h, 0 ; o32/o16 retf 32 +BS3_PROC_END_CMN bs3CpuBasic2_retf_i32_opsize + +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retf_i888, BS3_PBC_NEAR + db 0cah, 78h, 03h ; retf 888 (0x378) +BS3_PROC_END_CMN bs3CpuBasic2_retf_i888 + + %if TMPL_BITS == 64 +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retf_rexw, BS3_PBC_NEAR + db 048h, 0cbh ; o64 retf +BS3_PROC_END_CMN bs3CpuBasic2_retf_rexw + +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retf_opsize_rexw, BS3_PBC_NEAR + db 066h, 048h, 0cbh ; o16 o64 retf +BS3_PROC_END_CMN bs3CpuBasic2_retf_opsize_rexw + +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retf_rexw_opsize, BS3_PBC_NEAR + db 048h, 066h, 0cbh ; o64 o16 retf +BS3_PROC_END_CMN bs3CpuBasic2_retf_rexw_opsize + +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retf_i24_rexw, BS3_PBC_NEAR + db 048h, 0cah, 24, 0 ; o64 retf 24 +BS3_PROC_END_CMN bs3CpuBasic2_retf_i24_rexw + +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retf_i24_opsize_rexw, BS3_PBC_NEAR + db 066h, 048h, 0cah, 24, 0 ; o16 o64 retf 24 +BS3_PROC_END_CMN bs3CpuBasic2_retf_i24_opsize_rexw + +BS3_PROC_BEGIN_CMN bs3CpuBasic2_retf_i24_rexw_opsize, BS3_PBC_NEAR + db 048h, 066h, 0cah, 24, 0 ; o64 o16 retf 24 +BS3_PROC_END_CMN bs3CpuBasic2_retf_i24_rexw_opsize + %endif + + +%endif ; BS3_INSTANTIATING_CMN + +%include "bs3kit-template-footer.mac" ; reset environment + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2-x0.c b/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2-x0.c new file mode 100644 index 00000000..db5574ee --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2-x0.c @@ -0,0 +1,6400 @@ +/* $Id: bs3-cpu-basic-2-x0.c $ */ +/** @file + * BS3Kit - bs3-cpu-basic-2, C test driver code (16-bit). + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define BS3_USE_X0_TEXT_SEG +#include <bs3kit.h> +#include <iprt/asm.h> +#include <iprt/asm-amd64-x86.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#undef CHECK_MEMBER +#define CHECK_MEMBER(a_szName, a_szFmt, a_Actual, a_Expected) \ + do \ + { \ + if ((a_Actual) == (a_Expected)) { /* likely */ } \ + else bs3CpuBasic2_FailedF(a_szName "=" a_szFmt " expected " a_szFmt, (a_Actual), (a_Expected)); \ + } while (0) + + +/** Indicating that we've got operand size prefix and that it matters. */ +#define BS3CB2SIDTSGDT_F_OPSIZE UINT8_C(0x01) +/** Worker requires 386 or later. */ +#define BS3CB2SIDTSGDT_F_386PLUS UINT8_C(0x02) + + +/** @name MYOP_XXX - Values for FNBS3CPUBASIC2ACTSTCODE::fOp. + * + * These are flags, though we've precombined a few shortening things down. + * + * @{ */ +#define MYOP_LD 0x1 /**< The instruction loads. */ +#define MYOP_ST 0x2 /**< The instruction stores */ +#define MYOP_EFL 0x4 /**< The instruction modifies EFLAGS. */ +#define MYOP_AC_GP 0x8 /**< The instruction may cause either \#AC or \#GP (FXSAVE). */ + +#define MYOP_LD_ST 0x3 /**< Convenience: The instruction both loads and stores. */ +#define MYOP_LD_DIV 0x5 /**< Convenience: DIV instruction - loading and modifying flags. */ +/** @} */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Near void pointer. */ +typedef void BS3_NEAR *NPVOID; + +typedef struct BS3CB2INVLDESCTYPE +{ + uint8_t u4Type; + uint8_t u1DescType; +} BS3CB2INVLDESCTYPE; + +typedef struct BS3CB2SIDTSGDT +{ + const char *pszDesc; + FPFNBS3FAR fpfnWorker; + uint8_t cbInstr; + bool fSs; + uint8_t bMode; + uint8_t fFlags; +} BS3CB2SIDTSGDT; + + +typedef void BS3_CALL FNBS3CPUBASIC2ACSNIPPET(void); + +typedef struct FNBS3CPUBASIC2ACTSTCODE +{ + FNBS3CPUBASIC2ACSNIPPET BS3_FAR *pfn; + uint8_t fOp; + uint16_t cbMem; + uint8_t cbAlign; + uint8_t offFaultInstr; /**< For skipping fninit with the fld test. */ +} FNBS3CPUBASIC2ACTSTCODE; +typedef FNBS3CPUBASIC2ACTSTCODE const *PCFNBS3CPUBASIC2ACTSTCODE; + +typedef struct BS3CPUBASIC2ACTTSTCMNMODE +{ + uint8_t bMode; + uint16_t cEntries; + PCFNBS3CPUBASIC2ACTSTCODE paEntries; +} BS3CPUBASIC2PFTTSTCMNMODE; +typedef BS3CPUBASIC2PFTTSTCMNMODE const *PCBS3CPUBASIC2PFTTSTCMNMODE; + + +/********************************************************************************************************************************* +* External Symbols * +*********************************************************************************************************************************/ +extern FNBS3FAR bs3CpuBasic2_Int80; +extern FNBS3FAR bs3CpuBasic2_Int81; +extern FNBS3FAR bs3CpuBasic2_Int82; +extern FNBS3FAR bs3CpuBasic2_Int83; + +extern FNBS3FAR bs3CpuBasic2_ud2; +#define g_bs3CpuBasic2_ud2_FlatAddr BS3_DATA_NM(g_bs3CpuBasic2_ud2_FlatAddr) +extern uint32_t g_bs3CpuBasic2_ud2_FlatAddr; + +extern FNBS3FAR bs3CpuBasic2_salc_ud2; +extern FNBS3FAR bs3CpuBasic2_swapgs; + +extern FNBS3FAR bs3CpuBasic2_iret; +extern FNBS3FAR bs3CpuBasic2_iret_opsize; +extern FNBS3FAR bs3CpuBasic2_iret_rexw; + +extern FNBS3FAR bs3CpuBasic2_sidt_bx_ud2_c16; +extern FNBS3FAR bs3CpuBasic2_sidt_bx_ud2_c32; +extern FNBS3FAR bs3CpuBasic2_sidt_bx_ud2_c64; +extern FNBS3FAR bs3CpuBasic2_sidt_ss_bx_ud2_c16; +extern FNBS3FAR bs3CpuBasic2_sidt_ss_bx_ud2_c32; +extern FNBS3FAR bs3CpuBasic2_sidt_rexw_bx_ud2_c64; +extern FNBS3FAR bs3CpuBasic2_sidt_opsize_bx_ud2_c16; +extern FNBS3FAR bs3CpuBasic2_sidt_opsize_bx_ud2_c32; +extern FNBS3FAR bs3CpuBasic2_sidt_opsize_bx_ud2_c64; +extern FNBS3FAR bs3CpuBasic2_sidt_opsize_ss_bx_ud2_c16; +extern FNBS3FAR bs3CpuBasic2_sidt_opsize_ss_bx_ud2_c32; +extern FNBS3FAR bs3CpuBasic2_sidt_opsize_rexw_bx_ud2_c64; + +extern FNBS3FAR bs3CpuBasic2_sgdt_bx_ud2_c16; +extern FNBS3FAR bs3CpuBasic2_sgdt_bx_ud2_c32; +extern FNBS3FAR bs3CpuBasic2_sgdt_bx_ud2_c64; +extern FNBS3FAR bs3CpuBasic2_sgdt_ss_bx_ud2_c16; +extern FNBS3FAR bs3CpuBasic2_sgdt_ss_bx_ud2_c32; +extern FNBS3FAR bs3CpuBasic2_sgdt_rexw_bx_ud2_c64; +extern FNBS3FAR bs3CpuBasic2_sgdt_opsize_bx_ud2_c16; +extern FNBS3FAR bs3CpuBasic2_sgdt_opsize_bx_ud2_c32; +extern FNBS3FAR bs3CpuBasic2_sgdt_opsize_bx_ud2_c64; +extern FNBS3FAR bs3CpuBasic2_sgdt_opsize_ss_bx_ud2_c16; +extern FNBS3FAR bs3CpuBasic2_sgdt_opsize_ss_bx_ud2_c32; +extern FNBS3FAR bs3CpuBasic2_sgdt_opsize_rexw_bx_ud2_c64; + +extern FNBS3FAR bs3CpuBasic2_lidt_bx__sidt_es_di__lidt_es_si__ud2_c16; +extern FNBS3FAR bs3CpuBasic2_lidt_bx__sidt_es_di__lidt_es_si__ud2_c32; +extern FNBS3FAR bs3CpuBasic2_lidt_bx__sidt_es_di__lidt_es_si__ud2_c64; +extern FNBS3FAR bs3CpuBasic2_lidt_ss_bx__sidt_es_di__lidt_es_si__ud2_c16; +extern FNBS3FAR bs3CpuBasic2_lidt_ss_bx__sidt_es_di__lidt_es_si__ud2_c32; +extern FNBS3FAR bs3CpuBasic2_lidt_rexw_bx__sidt_es_di__lidt_es_si__ud2_c64; +extern FNBS3FAR bs3CpuBasic2_lidt_opsize_bx__sidt_es_di__lidt_es_si__ud2_c16; +extern FNBS3FAR bs3CpuBasic2_lidt_opsize_bx__sidt32_es_di__lidt_es_si__ud2_c16; +extern FNBS3FAR bs3CpuBasic2_lidt_opsize_bx__sidt_es_di__lidt_es_si__ud2_c32; +extern FNBS3FAR bs3CpuBasic2_lidt_opsize_bx__sidt_es_di__lidt_es_si__ud2_c64; +extern FNBS3FAR bs3CpuBasic2_lidt_opsize_ss_bx__sidt_es_di__lidt_es_si__ud2_c16; +extern FNBS3FAR bs3CpuBasic2_lidt_opsize_ss_bx__sidt_es_di__lidt_es_si__ud2_c32; +extern FNBS3FAR bs3CpuBasic2_lidt_opsize_rexw_bx__sidt_es_di__lidt_es_si__ud2_c64; + +extern FNBS3FAR bs3CpuBasic2_lgdt_bx__sgdt_es_di__lgdt_es_si__ud2_c16; +extern FNBS3FAR bs3CpuBasic2_lgdt_bx__sgdt_es_di__lgdt_es_si__ud2_c32; +extern FNBS3FAR bs3CpuBasic2_lgdt_bx__sgdt_es_di__lgdt_es_si__ud2_c64; +extern FNBS3FAR bs3CpuBasic2_lgdt_ss_bx__sgdt_es_di__lgdt_es_si__ud2_c16; +extern FNBS3FAR bs3CpuBasic2_lgdt_ss_bx__sgdt_es_di__lgdt_es_si__ud2_c32; +extern FNBS3FAR bs3CpuBasic2_lgdt_rexw_bx__sgdt_es_di__lgdt_es_si__ud2_c64; +extern FNBS3FAR bs3CpuBasic2_lgdt_opsize_bx__sgdt_es_di__lgdt_es_si__ud2_c16; +extern FNBS3FAR bs3CpuBasic2_lgdt_opsize_bx__sgdt_es_di__lgdt_es_si__ud2_c32; +extern FNBS3FAR bs3CpuBasic2_lgdt_opsize_bx__sgdt_es_di__lgdt_es_si__ud2_c64; +extern FNBS3FAR bs3CpuBasic2_lgdt_opsize_ss_bx__sgdt_es_di__lgdt_es_si__ud2_c16; +extern FNBS3FAR bs3CpuBasic2_lgdt_opsize_ss_bx__sgdt_es_di__lgdt_es_si__ud2_c32; +extern FNBS3FAR bs3CpuBasic2_lgdt_opsize_rexw_bx__sgdt_es_di__lgdt_es_si__ud2_c64; + + +/* bs3-cpu-basic-2-template.mac: */ +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_mov_ax_ds_bx__ud2_c16; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_mov_ds_bx_ax__ud2_c16; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_xchg_ds_bx_ax__ud2_c16; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_cmpxchg_ds_bx_cx__ud2_c16; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_div_ds_bx__ud2_c16; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_fninit_fld_ds_bx__ud2_c16; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_fninit_fbld_ds_bx__ud2_c16; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_fninit_fldz_fstp_ds_bx__ud2_c16; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_fxsave_ds_bx__ud2_c16; + +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_mov_ax_ds_bx__ud2_c32; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_mov_ds_bx_ax__ud2_c32; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_xchg_ds_bx_ax__ud2_c32; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_cmpxchg_ds_bx_cx__ud2_c32; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_div_ds_bx__ud2_c32; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_fninit_fld_ds_bx__ud2_c32; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_fninit_fbld_ds_bx__ud2_c32; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_fninit_fldz_fstp_ds_bx__ud2_c32; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_fxsave_ds_bx__ud2_c32; + +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_mov_ax_ds_bx__ud2_c64; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_mov_ds_bx_ax__ud2_c64; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_xchg_ds_bx_ax__ud2_c64; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_cmpxchg_ds_bx_cx__ud2_c64; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_div_ds_bx__ud2_c64; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_fninit_fld_ds_bx__ud2_c64; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_fninit_fbld_ds_bx__ud2_c64; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_fninit_fldz_fstp_ds_bx__ud2_c64; +FNBS3CPUBASIC2ACSNIPPET bs3CpuBasic2_fxsave_ds_bx__ud2_c64; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static const char BS3_FAR *g_pszTestMode = (const char *)1; +static uint8_t g_bTestMode = 1; +static bool g_f16BitSys = 1; + + +/** SIDT test workers. */ +static BS3CB2SIDTSGDT const g_aSidtWorkers[] = +{ + { "sidt [bx]", bs3CpuBasic2_sidt_bx_ud2_c16, 3, false, BS3_MODE_CODE_16 | BS3_MODE_CODE_V86, 0 }, + { "sidt [ss:bx]", bs3CpuBasic2_sidt_ss_bx_ud2_c16, 4, true, BS3_MODE_CODE_16 | BS3_MODE_CODE_V86, 0 }, + { "o32 sidt [bx]", bs3CpuBasic2_sidt_opsize_bx_ud2_c16, 4, false, BS3_MODE_CODE_16 | BS3_MODE_CODE_V86, BS3CB2SIDTSGDT_F_386PLUS }, + { "o32 sidt [ss:bx]", bs3CpuBasic2_sidt_opsize_ss_bx_ud2_c16, 5, true, BS3_MODE_CODE_16 | BS3_MODE_CODE_V86, BS3CB2SIDTSGDT_F_386PLUS }, + { "sidt [ebx]", bs3CpuBasic2_sidt_bx_ud2_c32, 3, false, BS3_MODE_CODE_32, 0 }, + { "sidt [ss:ebx]", bs3CpuBasic2_sidt_ss_bx_ud2_c32, 4, true, BS3_MODE_CODE_32, 0 }, + { "o16 sidt [ebx]", bs3CpuBasic2_sidt_opsize_bx_ud2_c32, 4, false, BS3_MODE_CODE_32, 0 }, + { "o16 sidt [ss:ebx]", bs3CpuBasic2_sidt_opsize_ss_bx_ud2_c32, 5, true, BS3_MODE_CODE_32, 0 }, + { "sidt [rbx]", bs3CpuBasic2_sidt_bx_ud2_c64, 3, false, BS3_MODE_CODE_64, 0 }, + { "o64 sidt [rbx]", bs3CpuBasic2_sidt_rexw_bx_ud2_c64, 4, false, BS3_MODE_CODE_64, 0 }, + { "o32 sidt [rbx]", bs3CpuBasic2_sidt_opsize_bx_ud2_c64, 4, false, BS3_MODE_CODE_64, 0 }, + { "o32 o64 sidt [rbx]", bs3CpuBasic2_sidt_opsize_rexw_bx_ud2_c64, 5, false, BS3_MODE_CODE_64, 0 }, +}; + +/** SGDT test workers. */ +static BS3CB2SIDTSGDT const g_aSgdtWorkers[] = +{ + { "sgdt [bx]", bs3CpuBasic2_sgdt_bx_ud2_c16, 3, false, BS3_MODE_CODE_16 | BS3_MODE_CODE_V86, 0 }, + { "sgdt [ss:bx]", bs3CpuBasic2_sgdt_ss_bx_ud2_c16, 4, true, BS3_MODE_CODE_16 | BS3_MODE_CODE_V86, 0 }, + { "o32 sgdt [bx]", bs3CpuBasic2_sgdt_opsize_bx_ud2_c16, 4, false, BS3_MODE_CODE_16 | BS3_MODE_CODE_V86, BS3CB2SIDTSGDT_F_386PLUS }, + { "o32 sgdt [ss:bx]", bs3CpuBasic2_sgdt_opsize_ss_bx_ud2_c16, 5, true, BS3_MODE_CODE_16 | BS3_MODE_CODE_V86, BS3CB2SIDTSGDT_F_386PLUS }, + { "sgdt [ebx]", bs3CpuBasic2_sgdt_bx_ud2_c32, 3, false, BS3_MODE_CODE_32, 0 }, + { "sgdt [ss:ebx]", bs3CpuBasic2_sgdt_ss_bx_ud2_c32, 4, true, BS3_MODE_CODE_32, 0 }, + { "o16 sgdt [ebx]", bs3CpuBasic2_sgdt_opsize_bx_ud2_c32, 4, false, BS3_MODE_CODE_32, 0 }, + { "o16 sgdt [ss:ebx]", bs3CpuBasic2_sgdt_opsize_ss_bx_ud2_c32, 5, true, BS3_MODE_CODE_32, 0 }, + { "sgdt [rbx]", bs3CpuBasic2_sgdt_bx_ud2_c64, 3, false, BS3_MODE_CODE_64, 0 }, + { "o64 sgdt [rbx]", bs3CpuBasic2_sgdt_rexw_bx_ud2_c64, 4, false, BS3_MODE_CODE_64, 0 }, + { "o32 sgdt [rbx]", bs3CpuBasic2_sgdt_opsize_bx_ud2_c64, 4, false, BS3_MODE_CODE_64, 0 }, + { "o32 o64 sgdt [rbx]", bs3CpuBasic2_sgdt_opsize_rexw_bx_ud2_c64, 5, false, BS3_MODE_CODE_64, 0 }, +}; + +/** LIDT test workers. */ +static BS3CB2SIDTSGDT const g_aLidtWorkers[] = +{ + { "lidt [bx]", bs3CpuBasic2_lidt_bx__sidt_es_di__lidt_es_si__ud2_c16, 11, false, BS3_MODE_CODE_16 | BS3_MODE_CODE_V86, 0 }, + { "lidt [ss:bx]", bs3CpuBasic2_lidt_ss_bx__sidt_es_di__lidt_es_si__ud2_c16, 12, true, BS3_MODE_CODE_16 | BS3_MODE_CODE_V86, 0 }, + { "o32 lidt [bx]", bs3CpuBasic2_lidt_opsize_bx__sidt_es_di__lidt_es_si__ud2_c16, 12, false, BS3_MODE_CODE_16 | BS3_MODE_CODE_V86, BS3CB2SIDTSGDT_F_OPSIZE | BS3CB2SIDTSGDT_F_386PLUS }, + { "o32 lidt [bx]; sidt32", bs3CpuBasic2_lidt_opsize_bx__sidt32_es_di__lidt_es_si__ud2_c16, 27, false, BS3_MODE_CODE_16 | BS3_MODE_CODE_V86, BS3CB2SIDTSGDT_F_OPSIZE | BS3CB2SIDTSGDT_F_386PLUS }, + { "o32 lidt [ss:bx]", bs3CpuBasic2_lidt_opsize_ss_bx__sidt_es_di__lidt_es_si__ud2_c16, 13, true, BS3_MODE_CODE_16 | BS3_MODE_CODE_V86, BS3CB2SIDTSGDT_F_OPSIZE | BS3CB2SIDTSGDT_F_386PLUS }, + { "lidt [ebx]", bs3CpuBasic2_lidt_bx__sidt_es_di__lidt_es_si__ud2_c32, 11, false, BS3_MODE_CODE_32, 0 }, + { "lidt [ss:ebx]", bs3CpuBasic2_lidt_ss_bx__sidt_es_di__lidt_es_si__ud2_c32, 12, true, BS3_MODE_CODE_32, 0 }, + { "o16 lidt [ebx]", bs3CpuBasic2_lidt_opsize_bx__sidt_es_di__lidt_es_si__ud2_c32, 12, false, BS3_MODE_CODE_32, BS3CB2SIDTSGDT_F_OPSIZE }, + { "o16 lidt [ss:ebx]", bs3CpuBasic2_lidt_opsize_ss_bx__sidt_es_di__lidt_es_si__ud2_c32, 13, true, BS3_MODE_CODE_32, BS3CB2SIDTSGDT_F_OPSIZE }, + { "lidt [rbx]", bs3CpuBasic2_lidt_bx__sidt_es_di__lidt_es_si__ud2_c64, 9, false, BS3_MODE_CODE_64, 0 }, + { "o64 lidt [rbx]", bs3CpuBasic2_lidt_rexw_bx__sidt_es_di__lidt_es_si__ud2_c64, 10, false, BS3_MODE_CODE_64, 0 }, + { "o32 lidt [rbx]", bs3CpuBasic2_lidt_opsize_bx__sidt_es_di__lidt_es_si__ud2_c64, 10, false, BS3_MODE_CODE_64, 0 }, + { "o32 o64 lidt [rbx]", bs3CpuBasic2_lidt_opsize_rexw_bx__sidt_es_di__lidt_es_si__ud2_c64, 11, false, BS3_MODE_CODE_64, 0 }, +}; + +/** LGDT test workers. */ +static BS3CB2SIDTSGDT const g_aLgdtWorkers[] = +{ + { "lgdt [bx]", bs3CpuBasic2_lgdt_bx__sgdt_es_di__lgdt_es_si__ud2_c16, 11, false, BS3_MODE_CODE_16 | BS3_MODE_CODE_V86, 0 }, + { "lgdt [ss:bx]", bs3CpuBasic2_lgdt_ss_bx__sgdt_es_di__lgdt_es_si__ud2_c16, 12, true, BS3_MODE_CODE_16 | BS3_MODE_CODE_V86, 0 }, + { "o32 lgdt [bx]", bs3CpuBasic2_lgdt_opsize_bx__sgdt_es_di__lgdt_es_si__ud2_c16, 12, false, BS3_MODE_CODE_16 | BS3_MODE_CODE_V86, BS3CB2SIDTSGDT_F_OPSIZE | BS3CB2SIDTSGDT_F_386PLUS }, + { "o32 lgdt [ss:bx]", bs3CpuBasic2_lgdt_opsize_ss_bx__sgdt_es_di__lgdt_es_si__ud2_c16, 13, true, BS3_MODE_CODE_16 | BS3_MODE_CODE_V86, BS3CB2SIDTSGDT_F_OPSIZE | BS3CB2SIDTSGDT_F_386PLUS }, + { "lgdt [ebx]", bs3CpuBasic2_lgdt_bx__sgdt_es_di__lgdt_es_si__ud2_c32, 11, false, BS3_MODE_CODE_32, 0 }, + { "lgdt [ss:ebx]", bs3CpuBasic2_lgdt_ss_bx__sgdt_es_di__lgdt_es_si__ud2_c32, 12, true, BS3_MODE_CODE_32, 0 }, + { "o16 lgdt [ebx]", bs3CpuBasic2_lgdt_opsize_bx__sgdt_es_di__lgdt_es_si__ud2_c32, 12, false, BS3_MODE_CODE_32, BS3CB2SIDTSGDT_F_OPSIZE }, + { "o16 lgdt [ss:ebx]", bs3CpuBasic2_lgdt_opsize_ss_bx__sgdt_es_di__lgdt_es_si__ud2_c32, 13, true, BS3_MODE_CODE_32, BS3CB2SIDTSGDT_F_OPSIZE }, + { "lgdt [rbx]", bs3CpuBasic2_lgdt_bx__sgdt_es_di__lgdt_es_si__ud2_c64, 9, false, BS3_MODE_CODE_64, 0 }, + { "o64 lgdt [rbx]", bs3CpuBasic2_lgdt_rexw_bx__sgdt_es_di__lgdt_es_si__ud2_c64, 10, false, BS3_MODE_CODE_64, 0 }, + { "o32 lgdt [rbx]", bs3CpuBasic2_lgdt_opsize_bx__sgdt_es_di__lgdt_es_si__ud2_c64, 10, false, BS3_MODE_CODE_64, 0 }, + { "o32 o64 lgdt [rbx]", bs3CpuBasic2_lgdt_opsize_rexw_bx__sgdt_es_di__lgdt_es_si__ud2_c64, 11, false, BS3_MODE_CODE_64, 0 }, +}; + + + +#if 0 +/** Table containing invalid CS selector types. */ +static const BS3CB2INVLDESCTYPE g_aInvalidCsTypes[] = +{ + { X86_SEL_TYPE_RO, 1 }, + { X86_SEL_TYPE_RO_ACC, 1 }, + { X86_SEL_TYPE_RW, 1 }, + { X86_SEL_TYPE_RW_ACC, 1 }, + { X86_SEL_TYPE_RO_DOWN, 1 }, + { X86_SEL_TYPE_RO_DOWN_ACC, 1 }, + { X86_SEL_TYPE_RW_DOWN, 1 }, + { X86_SEL_TYPE_RW_DOWN_ACC, 1 }, + { 0, 0 }, + { 1, 0 }, + { 2, 0 }, + { 3, 0 }, + { 4, 0 }, + { 5, 0 }, + { 6, 0 }, + { 7, 0 }, + { 8, 0 }, + { 9, 0 }, + { 10, 0 }, + { 11, 0 }, + { 12, 0 }, + { 13, 0 }, + { 14, 0 }, + { 15, 0 }, +}; + +/** Table containing invalid SS selector types. */ +static const BS3CB2INVLDESCTYPE g_aInvalidSsTypes[] = +{ + { X86_SEL_TYPE_EO, 1 }, + { X86_SEL_TYPE_EO_ACC, 1 }, + { X86_SEL_TYPE_ER, 1 }, + { X86_SEL_TYPE_ER_ACC, 1 }, + { X86_SEL_TYPE_EO_CONF, 1 }, + { X86_SEL_TYPE_EO_CONF_ACC, 1 }, + { X86_SEL_TYPE_ER_CONF, 1 }, + { X86_SEL_TYPE_ER_CONF_ACC, 1 }, + { 0, 0 }, + { 1, 0 }, + { 2, 0 }, + { 3, 0 }, + { 4, 0 }, + { 5, 0 }, + { 6, 0 }, + { 7, 0 }, + { 8, 0 }, + { 9, 0 }, + { 10, 0 }, + { 11, 0 }, + { 12, 0 }, + { 13, 0 }, + { 14, 0 }, + { 15, 0 }, +}; +#endif + + +static const FNBS3CPUBASIC2ACTSTCODE g_aCmn16[] = +{ + { bs3CpuBasic2_mov_ax_ds_bx__ud2_c16, MYOP_LD, 2, 2 }, + { bs3CpuBasic2_mov_ds_bx_ax__ud2_c16, MYOP_ST, 2, 2 }, + { bs3CpuBasic2_xchg_ds_bx_ax__ud2_c16, MYOP_LD_ST, 2, 2 }, + { bs3CpuBasic2_cmpxchg_ds_bx_cx__ud2_c16, MYOP_LD_ST | MYOP_EFL, 2, 2 }, + { bs3CpuBasic2_div_ds_bx__ud2_c16, MYOP_LD_DIV, 2, 2 }, + { bs3CpuBasic2_fninit_fld_ds_bx__ud2_c16, MYOP_LD, 10, 8, 2 /*fninit*/ }, + { bs3CpuBasic2_fninit_fbld_ds_bx__ud2_c16, MYOP_LD, 10, 8, 2 /*fninit*/ }, + { bs3CpuBasic2_fninit_fldz_fstp_ds_bx__ud2_c16, MYOP_ST, 10, 8, 4 /*fninit+fldz*/ }, + { bs3CpuBasic2_fxsave_ds_bx__ud2_c16, MYOP_ST | MYOP_AC_GP, 512, 16 }, +}; + +static const FNBS3CPUBASIC2ACTSTCODE g_aCmn32[] = +{ + { bs3CpuBasic2_mov_ax_ds_bx__ud2_c32, MYOP_LD, 4, 4 }, + { bs3CpuBasic2_mov_ds_bx_ax__ud2_c32, MYOP_ST, 4, 4 }, + { bs3CpuBasic2_xchg_ds_bx_ax__ud2_c32, MYOP_LD_ST, 4, 4 }, + { bs3CpuBasic2_cmpxchg_ds_bx_cx__ud2_c32, MYOP_LD_ST | MYOP_EFL, 4, 4 }, + { bs3CpuBasic2_div_ds_bx__ud2_c32, MYOP_LD_DIV, 4, 4 }, + { bs3CpuBasic2_fninit_fld_ds_bx__ud2_c32, MYOP_LD, 10, 8, 2 /*fninit*/ }, + { bs3CpuBasic2_fninit_fbld_ds_bx__ud2_c32, MYOP_LD, 10, 8, 2 /*fninit*/ }, + { bs3CpuBasic2_fninit_fldz_fstp_ds_bx__ud2_c32, MYOP_ST, 10, 8, 4 /*fninit+fldz*/ }, + { bs3CpuBasic2_fxsave_ds_bx__ud2_c32, MYOP_ST | MYOP_AC_GP, 512, 16 }, +}; + +static const FNBS3CPUBASIC2ACTSTCODE g_aCmn64[] = +{ + { bs3CpuBasic2_mov_ax_ds_bx__ud2_c64, MYOP_LD, 8, 8 }, + { bs3CpuBasic2_mov_ds_bx_ax__ud2_c64, MYOP_ST, 8, 8 }, + { bs3CpuBasic2_xchg_ds_bx_ax__ud2_c64, MYOP_LD_ST, 8, 8 }, + { bs3CpuBasic2_cmpxchg_ds_bx_cx__ud2_c64, MYOP_LD_ST | MYOP_EFL, 8, 8 }, + { bs3CpuBasic2_div_ds_bx__ud2_c64, MYOP_LD_DIV, 8, 8 }, + { bs3CpuBasic2_fninit_fld_ds_bx__ud2_c64, MYOP_LD, 10, 8, 2 /*fninit*/ }, + { bs3CpuBasic2_fninit_fbld_ds_bx__ud2_c64, MYOP_LD, 10, 8, 2 /*fninit*/ }, + { bs3CpuBasic2_fninit_fldz_fstp_ds_bx__ud2_c64, MYOP_ST, 10, 8, 4 /*fninit+fldz*/ }, + { bs3CpuBasic2_fxsave_ds_bx__ud2_c64, MYOP_ST | MYOP_AC_GP, 512, 16 }, +}; + +static const BS3CPUBASIC2PFTTSTCMNMODE g_aCmnModes[] = +{ + { BS3_MODE_CODE_16, RT_ELEMENTS(g_aCmn16), g_aCmn16 }, + { BS3_MODE_CODE_V86, RT_ELEMENTS(g_aCmn16), g_aCmn16 }, + { BS3_MODE_CODE_32, RT_ELEMENTS(g_aCmn32), g_aCmn32 }, + { BS3_MODE_CODE_64, RT_ELEMENTS(g_aCmn64), g_aCmn64 }, +}; + + +/** + * Sets globals according to the mode. + * + * @param bTestMode The test mode. + */ +static void bs3CpuBasic2_SetGlobals(uint8_t bTestMode) +{ + g_bTestMode = bTestMode; + g_pszTestMode = Bs3GetModeName(bTestMode); + g_f16BitSys = BS3_MODE_IS_16BIT_SYS(bTestMode); + g_usBs3TestStep = 0; +} + + +uint32_t ASMGetESP(void); +#pragma aux ASMGetESP = \ + ".386" \ + "mov ax, sp" \ + "mov edx, esp" \ + "shr edx, 16" \ + value [ax dx] \ + modify exact [ax dx]; + + +/** + * Wrapper around Bs3TestFailedF that prefixes the error with g_usBs3TestStep + * and g_pszTestMode. + */ +static void bs3CpuBasic2_FailedF(const char *pszFormat, ...) +{ + va_list va; + + char szTmp[168]; + va_start(va, pszFormat); + Bs3StrPrintfV(szTmp, sizeof(szTmp), pszFormat, va); + va_end(va); + + Bs3TestFailedF("%u - %s: %s", g_usBs3TestStep, g_pszTestMode, szTmp); +} + + +#if 0 +/** + * Compares trap stuff. + */ +static void bs3CpuBasic2_CompareIntCtx1(PCBS3TRAPFRAME pTrapCtx, PCBS3REGCTX pStartCtx, uint8_t bXcpt) +{ + uint16_t const cErrorsBefore = Bs3TestSubErrorCount(); + CHECK_MEMBER("bXcpt", "%#04x", pTrapCtx->bXcpt, bXcpt); + CHECK_MEMBER("bErrCd", "%#06RX64", pTrapCtx->uErrCd, 0); + Bs3TestCheckRegCtxEx(&pTrapCtx->Ctx, pStartCtx, 2 /*int xx*/, 0 /*cbSpAdjust*/, 0 /*fExtraEfl*/, g_pszTestMode, g_usBs3TestStep); + if (Bs3TestSubErrorCount() != cErrorsBefore) + { + Bs3TrapPrintFrame(pTrapCtx); +#if 1 + Bs3TestPrintf("Halting: g_uBs3CpuDetected=%#x\n", g_uBs3CpuDetected); + Bs3TestPrintf("Halting in CompareTrapCtx1: bXcpt=%#x\n", bXcpt); + ASMHalt(); +#endif + } +} +#endif + + +#if 0 +/** + * Compares trap stuff. + */ +static void bs3CpuBasic2_CompareTrapCtx2(PCBS3TRAPFRAME pTrapCtx, PCBS3REGCTX pStartCtx, uint16_t cbIpAdjust, + uint8_t bXcpt, uint16_t uHandlerCs) +{ + uint16_t const cErrorsBefore = Bs3TestSubErrorCount(); + CHECK_MEMBER("bXcpt", "%#04x", pTrapCtx->bXcpt, bXcpt); + CHECK_MEMBER("bErrCd", "%#06RX64", pTrapCtx->uErrCd, 0); + CHECK_MEMBER("uHandlerCs", "%#06x", pTrapCtx->uHandlerCs, uHandlerCs); + Bs3TestCheckRegCtxEx(&pTrapCtx->Ctx, pStartCtx, cbIpAdjust, 0 /*cbSpAdjust*/, 0 /*fExtraEfl*/, g_pszTestMode, g_usBs3TestStep); + if (Bs3TestSubErrorCount() != cErrorsBefore) + { + Bs3TrapPrintFrame(pTrapCtx); +#if 1 + Bs3TestPrintf("Halting: g_uBs3CpuDetected=%#x\n", g_uBs3CpuDetected); + Bs3TestPrintf("Halting in CompareTrapCtx2: bXcpt=%#x\n", bXcpt); + ASMHalt(); +#endif + } +} +#endif + +/** + * Compares a CPU trap. + */ +static void bs3CpuBasic2_CompareCpuTrapCtx(PCBS3TRAPFRAME pTrapCtx, PCBS3REGCTX pStartCtx, uint16_t uErrCd, + uint8_t bXcpt, bool f486ResumeFlagHint, uint8_t cbIpAdjust) +{ + uint16_t const cErrorsBefore = Bs3TestSubErrorCount(); + uint32_t fExtraEfl; + + CHECK_MEMBER("bXcpt", "%#04x", pTrapCtx->bXcpt, bXcpt); + CHECK_MEMBER("bErrCd", "%#06RX16", (uint16_t)pTrapCtx->uErrCd, (uint16_t)uErrCd); /* 486 only writes a word */ + + if ( g_f16BitSys + || bXcpt == X86_XCPT_DB /* hack (10980xe)... */ + || ( !f486ResumeFlagHint + && (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) <= BS3CPU_80486 ) ) + fExtraEfl = 0; + else + fExtraEfl = X86_EFL_RF; +#if 0 /** @todo Running on an AMD Phenom II X6 1100T under AMD-V I'm not getting good X86_EFL_RF results. Enable this to get on with other work. */ + fExtraEfl = pTrapCtx->Ctx.rflags.u32 & X86_EFL_RF; +#endif + Bs3TestCheckRegCtxEx(&pTrapCtx->Ctx, pStartCtx, cbIpAdjust, 0 /*cbSpAdjust*/, fExtraEfl, g_pszTestMode, g_usBs3TestStep); + if (Bs3TestSubErrorCount() != cErrorsBefore) + { + Bs3TrapPrintFrame(pTrapCtx); +#if 1 + Bs3TestPrintf("Halting: g_uBs3CpuDetected=%#x\n", g_uBs3CpuDetected); + Bs3TestPrintf("Halting: bXcpt=%#x uErrCd=%#x\n", bXcpt, uErrCd); + ASMHalt(); +#endif + } +} + + +/** + * Compares \#GP trap. + */ +static void bs3CpuBasic2_CompareGpCtx(PCBS3TRAPFRAME pTrapCtx, PCBS3REGCTX pStartCtx, uint16_t uErrCd) +{ + bs3CpuBasic2_CompareCpuTrapCtx(pTrapCtx, pStartCtx, uErrCd, X86_XCPT_GP, true /*f486ResumeFlagHint*/, 0 /*cbIpAdjust*/); +} + +#if 0 +/** + * Compares \#NP trap. + */ +static void bs3CpuBasic2_CompareNpCtx(PCBS3TRAPFRAME pTrapCtx, PCBS3REGCTX pStartCtx, uint16_t uErrCd) +{ + bs3CpuBasic2_CompareCpuTrapCtx(pTrapCtx, pStartCtx, uErrCd, X86_XCPT_NP, true /*f486ResumeFlagHint*/, 0 /*cbIpAdjust*/); +} +#endif + +/** + * Compares \#SS trap. + */ +static void bs3CpuBasic2_CompareSsCtx(PCBS3TRAPFRAME pTrapCtx, PCBS3REGCTX pStartCtx, uint16_t uErrCd, bool f486ResumeFlagHint) +{ + bs3CpuBasic2_CompareCpuTrapCtx(pTrapCtx, pStartCtx, uErrCd, X86_XCPT_SS, f486ResumeFlagHint, 0 /*cbIpAdjust*/); +} + +#if 0 +/** + * Compares \#TS trap. + */ +static void bs3CpuBasic2_CompareTsCtx(PCBS3TRAPFRAME pTrapCtx, PCBS3REGCTX pStartCtx, uint16_t uErrCd) +{ + bs3CpuBasic2_CompareCpuTrapCtx(pTrapCtx, pStartCtx, uErrCd, X86_XCPT_TS, false /*f486ResumeFlagHint*/, 0 /*cbIpAdjust*/); +} +#endif + +/** + * Compares \#PF trap. + */ +static void bs3CpuBasic2_ComparePfCtx(PCBS3TRAPFRAME pTrapCtx, PBS3REGCTX pStartCtx, uint16_t uErrCd, + uint64_t uCr2Expected, uint8_t cbIpAdjust) +{ + uint64_t const uCr2Saved = pStartCtx->cr2.u; + pStartCtx->cr2.u = uCr2Expected; + bs3CpuBasic2_CompareCpuTrapCtx(pTrapCtx, pStartCtx, uErrCd, X86_XCPT_PF, true /*f486ResumeFlagHint*/, cbIpAdjust); + pStartCtx->cr2.u = uCr2Saved; +} + +/** + * Compares \#UD trap. + */ +static void bs3CpuBasic2_CompareUdCtx(PCBS3TRAPFRAME pTrapCtx, PCBS3REGCTX pStartCtx) +{ + bs3CpuBasic2_CompareCpuTrapCtx(pTrapCtx, pStartCtx, 0 /*no error code*/, X86_XCPT_UD, + true /*f486ResumeFlagHint*/, 0 /*cbIpAdjust*/); +} + +/** + * Compares \#AC trap. + */ +static void bs3CpuBasic2_CompareAcCtx(PCBS3TRAPFRAME pTrapCtx, PCBS3REGCTX pStartCtx, uint8_t cbIpAdjust) +{ + bs3CpuBasic2_CompareCpuTrapCtx(pTrapCtx, pStartCtx, 0 /*always zero*/, X86_XCPT_AC, true /*f486ResumeFlagHint*/, cbIpAdjust); +} + +/** + * Compares \#DB trap. + */ +static void bs3CpuBasic2_CompareDbCtx(PCBS3TRAPFRAME pTrapCtx, PCBS3REGCTX pStartCtx, uint32_t fDr6Expect) +{ + uint16_t const cErrorsBefore = Bs3TestSubErrorCount(); + uint32_t const fDr6 = Bs3RegGetDr6(); + fDr6Expect |= X86_DR6_RA1_MASK; + CHECK_MEMBER("dr6", "%#08RX32", fDr6, fDr6Expect); + + bs3CpuBasic2_CompareCpuTrapCtx(pTrapCtx, pStartCtx, 0 /*always zero*/, X86_XCPT_DB, false /*f486ResumeFlagHint?*/, 0 /*cbIpAdjust*/); + + if (Bs3TestSubErrorCount() > cErrorsBefore) + { +#if 0 + Bs3TestPrintf("Halting\n"); + ASMHalt(); +#endif + } +} + + +/** + * Checks that DR6 has the initial value, i.e. is unchanged when other exception + * was raised before a \#DB could occur. + */ +static void bs3CpuBasic2_CheckDr6InitVal(void) +{ + uint16_t const cErrorsBefore = Bs3TestSubErrorCount(); + uint32_t const fDr6 = Bs3RegGetDr6(); + uint32_t const fDr6Expect = X86_DR6_INIT_VAL; + CHECK_MEMBER("dr6", "%#08RX32", fDr6, fDr6Expect); + if (Bs3TestSubErrorCount() > cErrorsBefore) + { + Bs3TestPrintf("Halting\n"); + ASMHalt(); + } +} + +#if 0 /* convert me */ +static void bs3CpuBasic2_RaiseXcpt1Common(uint16_t const uSysR0Cs, uint16_t const uSysR0CsConf, uint16_t const uSysR0Ss, + PX86DESC const paIdt, unsigned const cIdteShift) +{ + BS3TRAPFRAME TrapCtx; + BS3REGCTX Ctx80; + BS3REGCTX Ctx81; + BS3REGCTX Ctx82; + BS3REGCTX Ctx83; + BS3REGCTX CtxTmp; + BS3REGCTX CtxTmp2; + PBS3REGCTX apCtx8x[4]; + unsigned iCtx; + unsigned iRing; + unsigned iDpl; + unsigned iRpl; + unsigned i, j, k; + uint32_t uExpected; + bool const f486Plus = (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80486; +# if TMPL_BITS == 16 + bool const f386Plus = (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80386; + bool const f286 = (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) == BS3CPU_80286; +# else + bool const f286 = false; + bool const f386Plus = true; + int rc; + uint8_t *pbIdtCopyAlloc; + PX86DESC pIdtCopy; + const unsigned cbIdte = 1 << (3 + cIdteShift); + RTCCUINTXREG uCr0Saved = ASMGetCR0(); + RTGDTR GdtrSaved; +# endif + RTIDTR IdtrSaved; + RTIDTR Idtr; + + ASMGetIDTR(&IdtrSaved); +# if TMPL_BITS != 16 + ASMGetGDTR(&GdtrSaved); +# endif + + /* make sure they're allocated */ + Bs3MemZero(&TrapCtx, sizeof(TrapCtx)); + Bs3MemZero(&Ctx80, sizeof(Ctx80)); + Bs3MemZero(&Ctx81, sizeof(Ctx81)); + Bs3MemZero(&Ctx82, sizeof(Ctx82)); + Bs3MemZero(&Ctx83, sizeof(Ctx83)); + Bs3MemZero(&CtxTmp, sizeof(CtxTmp)); + Bs3MemZero(&CtxTmp2, sizeof(CtxTmp2)); + + /* Context array. */ + apCtx8x[0] = &Ctx80; + apCtx8x[1] = &Ctx81; + apCtx8x[2] = &Ctx82; + apCtx8x[3] = &Ctx83; + +# if TMPL_BITS != 16 + /* Allocate memory for playing around with the IDT. */ + pbIdtCopyAlloc = NULL; + if (BS3_MODE_IS_PAGED(g_bTestMode)) + pbIdtCopyAlloc = Bs3MemAlloc(BS3MEMKIND_FLAT32, 12*_1K); +# endif + + /* + * IDT entry 80 thru 83 are assigned DPLs according to the number. + * (We'll be useing more, but this'll do for now.) + */ + paIdt[0x80 << cIdteShift].Gate.u2Dpl = 0; + paIdt[0x81 << cIdteShift].Gate.u2Dpl = 1; + paIdt[0x82 << cIdteShift].Gate.u2Dpl = 2; + paIdt[0x83 << cIdteShift].Gate.u2Dpl = 3; + + Bs3RegCtxSave(&Ctx80); + Ctx80.rsp.u -= 0x300; + Ctx80.rip.u = (uintptr_t)BS3_FP_OFF(&bs3CpuBasic2_Int80); +# if TMPL_BITS == 16 + Ctx80.cs = BS3_MODE_IS_RM_OR_V86(g_bTestMode) ? BS3_SEL_TEXT16 : BS3_SEL_R0_CS16; +# elif TMPL_BITS == 32 + g_uBs3TrapEipHint = Ctx80.rip.u32; +# endif + Bs3MemCpy(&Ctx81, &Ctx80, sizeof(Ctx80)); + Ctx81.rip.u = (uintptr_t)BS3_FP_OFF(&bs3CpuBasic2_Int81); + Bs3MemCpy(&Ctx82, &Ctx80, sizeof(Ctx80)); + Ctx82.rip.u = (uintptr_t)BS3_FP_OFF(&bs3CpuBasic2_Int82); + Bs3MemCpy(&Ctx83, &Ctx80, sizeof(Ctx80)); + Ctx83.rip.u = (uintptr_t)BS3_FP_OFF(&bs3CpuBasic2_Int83); + + /* + * Check that all the above gates work from ring-0. + */ + for (iCtx = 0; iCtx < RT_ELEMENTS(apCtx8x); iCtx++) + { + g_usBs3TestStep = iCtx; +# if TMPL_BITS == 32 + g_uBs3TrapEipHint = apCtx8x[iCtx]->rip.u32; +# endif + Bs3TrapSetJmpAndRestore(apCtx8x[iCtx], &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, apCtx8x[iCtx], 0x80+iCtx /*bXcpt*/); + } + + /* + * Check that the gate DPL checks works. + */ + g_usBs3TestStep = 100; + for (iRing = 0; iRing <= 3; iRing++) + { + for (iCtx = 0; iCtx < RT_ELEMENTS(apCtx8x); iCtx++) + { + Bs3MemCpy(&CtxTmp, apCtx8x[iCtx], sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, iRing); +# if TMPL_BITS == 32 + g_uBs3TrapEipHint = CtxTmp.rip.u32; +# endif + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + if (iCtx < iRing) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, ((0x80 + iCtx) << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + else + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x80 + iCtx /*bXcpt*/); + g_usBs3TestStep++; + } + } + + /* + * Modify the gate CS value and run the handler at a different CPL. + * Throw RPL variations into the mix (completely ignored) together + * with gate presence. + * 1. CPL <= GATE.DPL + * 2. GATE.P + * 3. GATE.CS.DPL <= CPL (non-conforming segments) + */ + g_usBs3TestStep = 1000; + for (i = 0; i <= 3; i++) + { + for (iRing = 0; iRing <= 3; iRing++) + { + for (iCtx = 0; iCtx < RT_ELEMENTS(apCtx8x); iCtx++) + { +# if TMPL_BITS == 32 + g_uBs3TrapEipHint = apCtx8x[iCtx]->rip.u32; +# endif + Bs3MemCpy(&CtxTmp, apCtx8x[iCtx], sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, iRing); + + for (j = 0; j <= 3; j++) + { + uint16_t const uCs = (uSysR0Cs | j) + (i << BS3_SEL_RING_SHIFT); + for (k = 0; k < 2; k++) + { + g_usBs3TestStep++; + /*Bs3TestPrintf("g_usBs3TestStep=%u iCtx=%u iRing=%u i=%u uCs=%04x\n", g_usBs3TestStep, iCtx, iRing, i, uCs);*/ + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u16Sel = uCs; + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u1Present = k; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + /*Bs3TrapPrintFrame(&TrapCtx);*/ + if (iCtx < iRing) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, ((0x80 + iCtx) << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + else if (k == 0) + bs3CpuBasic2_CompareNpCtx(&TrapCtx, &CtxTmp, ((0x80 + iCtx) << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + else if (i > iRing) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, uCs & X86_SEL_MASK_OFF_RPL); + else + { + uint16_t uExpectedCs = uCs & X86_SEL_MASK_OFF_RPL; + if (i <= iCtx && i <= iRing) + uExpectedCs |= i; + bs3CpuBasic2_CompareTrapCtx2(&TrapCtx, &CtxTmp, 2 /*int 8xh*/, 0x80 + iCtx /*bXcpt*/, uExpectedCs); + } + } + } + + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u16Sel = uSysR0Cs; + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u1Present = 1; + } + } + } + BS3_ASSERT(g_usBs3TestStep < 1600); + + /* + * Various CS and SS related faults + * + * We temporarily reconfigure gate 80 and 83 with new CS selectors, the + * latter have a CS.DPL of 2 for testing ring transisions and SS loading + * without making it impossible to handle faults. + */ + g_usBs3TestStep = 1600; + Bs3GdteTestPage00 = Bs3Gdt[uSysR0Cs >> X86_SEL_SHIFT]; + Bs3GdteTestPage00.Gen.u1Present = 0; + Bs3GdteTestPage00.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + paIdt[0x80 << cIdteShift].Gate.u16Sel = BS3_SEL_TEST_PAGE_00; + + /* CS.PRESENT = 0 */ + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareNpCtx(&TrapCtx, &Ctx80, BS3_SEL_TEST_PAGE_00); + if (Bs3GdteTestPage00.Gen.u4Type & X86_SEL_TYPE_ACCESSED) + bs3CpuBasic2_FailedF("selector was accessed"); + g_usBs3TestStep++; + + /* Check that GATE.DPL is checked before CS.PRESENT. */ + for (iRing = 1; iRing < 4; iRing++) + { + Bs3MemCpy(&CtxTmp, &Ctx80, sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, iRing); + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, (0x80 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + if (Bs3GdteTestPage00.Gen.u4Type & X86_SEL_TYPE_ACCESSED) + bs3CpuBasic2_FailedF("selector was accessed"); + g_usBs3TestStep++; + } + + /* CS.DPL mismatch takes precedence over CS.PRESENT = 0. */ + Bs3GdteTestPage00.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareNpCtx(&TrapCtx, &Ctx80, BS3_SEL_TEST_PAGE_00); + if (Bs3GdteTestPage00.Gen.u4Type & X86_SEL_TYPE_ACCESSED) + bs3CpuBasic2_FailedF("CS selector was accessed"); + g_usBs3TestStep++; + for (iDpl = 1; iDpl < 4; iDpl++) + { + Bs3GdteTestPage00.Gen.u2Dpl = iDpl; + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx80, BS3_SEL_TEST_PAGE_00); + if (Bs3GdteTestPage00.Gen.u4Type & X86_SEL_TYPE_ACCESSED) + bs3CpuBasic2_FailedF("CS selector was accessed"); + g_usBs3TestStep++; + } + + /* 1608: Check all the invalid CS selector types alone. */ + Bs3GdteTestPage00 = Bs3Gdt[uSysR0Cs >> X86_SEL_SHIFT]; + for (i = 0; i < RT_ELEMENTS(g_aInvalidCsTypes); i++) + { + Bs3GdteTestPage00.Gen.u4Type = g_aInvalidCsTypes[i].u4Type; + Bs3GdteTestPage00.Gen.u1DescType = g_aInvalidCsTypes[i].u1DescType; + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx80, BS3_SEL_TEST_PAGE_00); + if (Bs3GdteTestPage00.Gen.u4Type != g_aInvalidCsTypes[i].u4Type) + bs3CpuBasic2_FailedF("Invalid CS type %#x/%u -> %#x/%u\n", + g_aInvalidCsTypes[i].u4Type, g_aInvalidCsTypes[i].u1DescType, + Bs3GdteTestPage00.Gen.u4Type, Bs3GdteTestPage00.Gen.u1DescType); + g_usBs3TestStep++; + + /* Incorrect CS.TYPE takes precedence over CS.PRESENT = 0. */ + Bs3GdteTestPage00.Gen.u1Present = 0; + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx80, BS3_SEL_TEST_PAGE_00); + Bs3GdteTestPage00.Gen.u1Present = 1; + g_usBs3TestStep++; + } + + /* Fix CS again. */ + Bs3GdteTestPage00 = Bs3Gdt[uSysR0Cs >> X86_SEL_SHIFT]; + + /* 1632: Test SS. */ + if (!BS3_MODE_IS_64BIT_SYS(g_bTestMode)) + { + uint16_t BS3_FAR *puTssSs2 = BS3_MODE_IS_16BIT_SYS(g_bTestMode) ? &Bs3Tss16.ss2 : &Bs3Tss32.ss2; + uint16_t const uSavedSs2 = *puTssSs2; + X86DESC const SavedGate83 = paIdt[0x83 << cIdteShift]; + + /* Make the handler execute in ring-2. */ + Bs3GdteTestPage02 = Bs3Gdt[(uSysR0Cs + (2 << BS3_SEL_RING_SHIFT)) >> X86_SEL_SHIFT]; + Bs3GdteTestPage02.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + paIdt[0x83 << cIdteShift].Gate.u16Sel = BS3_SEL_TEST_PAGE_02 | 2; + + Bs3MemCpy(&CtxTmp, &Ctx83, sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, 3); /* yeah, from 3 so SS:xSP is reloaded. */ + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x83); + if (!(Bs3GdteTestPage02.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + bs3CpuBasic2_FailedF("CS selector was not access"); + g_usBs3TestStep++; + + /* Create a SS.DPL=2 stack segment and check that SS2.RPL matters and + that we get #SS if the selector isn't present. */ + i = 0; /* used for cycling thru invalid CS types */ + for (k = 0; k < 10; k++) + { + /* k=0: present, + k=1: not-present, + k=2: present but very low limit, + k=3: not-present, low limit. + k=4: present, read-only. + k=5: not-present, read-only. + k=6: present, code-selector. + k=7: not-present, code-selector. + k=8: present, read-write / no access + system (=LDT). + k=9: not-present, read-write / no access + system (=LDT). + */ + Bs3GdteTestPage03 = Bs3Gdt[(uSysR0Ss + (2 << BS3_SEL_RING_SHIFT)) >> X86_SEL_SHIFT]; + Bs3GdteTestPage03.Gen.u1Present = !(k & 1); + if (k >= 8) + { + Bs3GdteTestPage03.Gen.u1DescType = 0; /* system */ + Bs3GdteTestPage03.Gen.u4Type = X86_SEL_TYPE_RW; /* = LDT */ + } + else if (k >= 6) + Bs3GdteTestPage03.Gen.u4Type = X86_SEL_TYPE_ER; + else if (k >= 4) + Bs3GdteTestPage03.Gen.u4Type = X86_SEL_TYPE_RO; + else if (k >= 2) + { + Bs3GdteTestPage03.Gen.u16LimitLow = 0x400; + Bs3GdteTestPage03.Gen.u4LimitHigh = 0; + Bs3GdteTestPage03.Gen.u1Granularity = 0; + } + + for (iDpl = 0; iDpl < 4; iDpl++) + { + Bs3GdteTestPage03.Gen.u2Dpl = iDpl; + + for (iRpl = 0; iRpl < 4; iRpl++) + { + *puTssSs2 = BS3_SEL_TEST_PAGE_03 | iRpl; + //Bs3TestPrintf("k=%u iDpl=%u iRpl=%u step=%u\n", k, iDpl, iRpl, g_usBs3TestStep); + Bs3GdteTestPage02.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + Bs3GdteTestPage03.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + if (iRpl != 2 || iRpl != iDpl || k >= 4) + bs3CpuBasic2_CompareTsCtx(&TrapCtx, &CtxTmp, BS3_SEL_TEST_PAGE_03); + else if (k != 0) + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &CtxTmp, BS3_SEL_TEST_PAGE_03, + k == 2 /*f486ResumeFlagHint*/); + else + { + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x83); + if (TrapCtx.uHandlerSs != (BS3_SEL_TEST_PAGE_03 | 2)) + bs3CpuBasic2_FailedF("uHandlerSs=%#x expected %#x\n", TrapCtx.uHandlerSs, BS3_SEL_TEST_PAGE_03 | 2); + } + if (!(Bs3GdteTestPage02.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + bs3CpuBasic2_FailedF("CS selector was not access"); + if ( TrapCtx.bXcpt == 0x83 + || (TrapCtx.bXcpt == X86_XCPT_SS && k == 2) ) + { + if (!(Bs3GdteTestPage03.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + bs3CpuBasic2_FailedF("SS selector was not accessed"); + } + else if (Bs3GdteTestPage03.Gen.u4Type & X86_SEL_TYPE_ACCESSED) + bs3CpuBasic2_FailedF("SS selector was accessed"); + g_usBs3TestStep++; + + /* +1: Modify the gate DPL to check that this is checked before SS.DPL and SS.PRESENT. */ + paIdt[0x83 << cIdteShift].Gate.u2Dpl = 2; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, (0x83 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + paIdt[0x83 << cIdteShift].Gate.u2Dpl = 3; + g_usBs3TestStep++; + + /* +2: Check the CS.DPL check is done before the SS ones. Restoring the + ring-0 INT 83 context triggers the CS.DPL < CPL check. */ + Bs3TrapSetJmpAndRestore(&Ctx83, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx83, BS3_SEL_TEST_PAGE_02); + g_usBs3TestStep++; + + /* +3: Now mark the CS selector not present and check that that also triggers before SS stuff. */ + Bs3GdteTestPage02.Gen.u1Present = 0; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareNpCtx(&TrapCtx, &CtxTmp, BS3_SEL_TEST_PAGE_02); + Bs3GdteTestPage02.Gen.u1Present = 1; + g_usBs3TestStep++; + + /* +4: Make the CS selector some invalid type and check it triggers before SS stuff. */ + Bs3GdteTestPage02.Gen.u4Type = g_aInvalidCsTypes[i].u4Type; + Bs3GdteTestPage02.Gen.u1DescType = g_aInvalidCsTypes[i].u1DescType; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, BS3_SEL_TEST_PAGE_02); + Bs3GdteTestPage02.Gen.u4Type = X86_SEL_TYPE_ER_ACC; + Bs3GdteTestPage02.Gen.u1DescType = 1; + g_usBs3TestStep++; + + /* +5: Now, make the CS selector limit too small and that it triggers after SS trouble. + The 286 had a simpler approach to these GP(0). */ + Bs3GdteTestPage02.Gen.u16LimitLow = 0; + Bs3GdteTestPage02.Gen.u4LimitHigh = 0; + Bs3GdteTestPage02.Gen.u1Granularity = 0; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + if (f286) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, 0 /*uErrCd*/); + else if (iRpl != 2 || iRpl != iDpl || k >= 4) + bs3CpuBasic2_CompareTsCtx(&TrapCtx, &CtxTmp, BS3_SEL_TEST_PAGE_03); + else if (k != 0) + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &CtxTmp, BS3_SEL_TEST_PAGE_03, k == 2 /*f486ResumeFlagHint*/); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, 0 /*uErrCd*/); + Bs3GdteTestPage02 = Bs3Gdt[(uSysR0Cs + (2 << BS3_SEL_RING_SHIFT)) >> X86_SEL_SHIFT]; + g_usBs3TestStep++; + } + } + } + + /* Check all the invalid SS selector types alone. */ + Bs3GdteTestPage02 = Bs3Gdt[(uSysR0Cs + (2 << BS3_SEL_RING_SHIFT)) >> X86_SEL_SHIFT]; + Bs3GdteTestPage03 = Bs3Gdt[(uSysR0Ss + (2 << BS3_SEL_RING_SHIFT)) >> X86_SEL_SHIFT]; + *puTssSs2 = BS3_SEL_TEST_PAGE_03 | 2; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x83); + g_usBs3TestStep++; + for (i = 0; i < RT_ELEMENTS(g_aInvalidSsTypes); i++) + { + Bs3GdteTestPage03.Gen.u4Type = g_aInvalidSsTypes[i].u4Type; + Bs3GdteTestPage03.Gen.u1DescType = g_aInvalidSsTypes[i].u1DescType; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareTsCtx(&TrapCtx, &CtxTmp, BS3_SEL_TEST_PAGE_03); + if (Bs3GdteTestPage03.Gen.u4Type != g_aInvalidSsTypes[i].u4Type) + bs3CpuBasic2_FailedF("Invalid SS type %#x/%u -> %#x/%u\n", + g_aInvalidSsTypes[i].u4Type, g_aInvalidSsTypes[i].u1DescType, + Bs3GdteTestPage03.Gen.u4Type, Bs3GdteTestPage03.Gen.u1DescType); + g_usBs3TestStep++; + } + + /* + * Continue the SS experiments with a expand down segment. We'll use + * the same setup as we already have with gate 83h being DPL and + * having CS.DPL=2. + * + * Expand down segments are weird. The valid area is practically speaking + * reversed. So, a 16-bit segment with a limit of 0x6000 will have valid + * addresses from 0xffff thru 0x6001. + * + * So, with expand down segments we can more easily cut partially into the + * pushing of the iret frame and trigger more interesting behavior than + * with regular "expand up" segments where the whole pushing area is either + * all fine or not not fine. + */ + Bs3GdteTestPage02 = Bs3Gdt[(uSysR0Cs + (2 << BS3_SEL_RING_SHIFT)) >> X86_SEL_SHIFT]; + Bs3GdteTestPage03 = Bs3Gdt[(uSysR0Ss + (2 << BS3_SEL_RING_SHIFT)) >> X86_SEL_SHIFT]; + Bs3GdteTestPage03.Gen.u2Dpl = 2; + Bs3GdteTestPage03.Gen.u4Type = X86_SEL_TYPE_RW_DOWN; + *puTssSs2 = BS3_SEL_TEST_PAGE_03 | 2; + + /* First test, limit = max --> no bytes accessible --> #GP */ + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &CtxTmp, BS3_SEL_TEST_PAGE_03, true /*f486ResumeFlagHint*/); + + /* Second test, limit = 0 --> all by zero byte accessible --> works */ + Bs3GdteTestPage03.Gen.u16LimitLow = 0; + Bs3GdteTestPage03.Gen.u4LimitHigh = 0; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x83); + + /* Modify the gate handler to be a dummy that immediately does UD2 + and triggers #UD, then advance the limit down till we get the #UD. */ + Bs3GdteTestPage03.Gen.u1Granularity = 0; + + Bs3MemCpy(&CtxTmp2, &CtxTmp, sizeof(CtxTmp2)); /* #UD result context */ + if (g_f16BitSys) + { + CtxTmp2.rip.u = g_bs3CpuBasic2_ud2_FlatAddr - BS3_ADDR_BS3TEXT16; + Bs3Trap16SetGate(0x83, X86_SEL_TYPE_SYS_286_INT_GATE, 3, BS3_SEL_TEST_PAGE_02, CtxTmp2.rip.u16, 0 /*cParams*/); + CtxTmp2.rsp.u = Bs3Tss16.sp2 - 2*5; + } + else + { + CtxTmp2.rip.u = g_bs3CpuBasic2_ud2_FlatAddr; + Bs3Trap32SetGate(0x83, X86_SEL_TYPE_SYS_386_INT_GATE, 3, BS3_SEL_TEST_PAGE_02, CtxTmp2.rip.u32, 0 /*cParams*/); + CtxTmp2.rsp.u = Bs3Tss32.esp2 - 4*5; + } + CtxTmp2.bMode = g_bTestMode; /* g_bBs3CurrentMode not changed by the UD2 handler. */ + CtxTmp2.cs = BS3_SEL_TEST_PAGE_02 | 2; + CtxTmp2.ss = BS3_SEL_TEST_PAGE_03 | 2; + CtxTmp2.bCpl = 2; + + /* test run. */ + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxTmp2); + g_usBs3TestStep++; + + /* Real run. */ + i = (g_f16BitSys ? 2 : 4) * 6 + 1; + while (i-- > 0) + { + Bs3GdteTestPage03.Gen.u16LimitLow = CtxTmp2.rsp.u16 + i - 1; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + if (i > 0) + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &CtxTmp, BS3_SEL_TEST_PAGE_03, true /*f486ResumeFlagHint*/); + else + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxTmp2); + g_usBs3TestStep++; + } + + /* Do a run where we do the same-ring kind of access. */ + Bs3RegCtxConvertToRingX(&CtxTmp, 2); + if (g_f16BitSys) + { + CtxTmp2.rsp.u32 = CtxTmp.rsp.u32 - 2*3; + i = 2*3 - 1; + } + else + { + CtxTmp2.rsp.u32 = CtxTmp.rsp.u32 - 4*3; + i = 4*3 - 1; + } + CtxTmp.ss = BS3_SEL_TEST_PAGE_03 | 2; + CtxTmp2.ds = CtxTmp.ds; + CtxTmp2.es = CtxTmp.es; + CtxTmp2.fs = CtxTmp.fs; + CtxTmp2.gs = CtxTmp.gs; + while (i-- > 0) + { + Bs3GdteTestPage03.Gen.u16LimitLow = CtxTmp2.rsp.u16 + i - 1; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + if (i > 0) + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &CtxTmp, 0 /*BS3_SEL_TEST_PAGE_03*/, true /*f486ResumeFlagHint*/); + else + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxTmp2); + g_usBs3TestStep++; + } + + *puTssSs2 = uSavedSs2; + paIdt[0x83 << cIdteShift] = SavedGate83; + } + paIdt[0x80 << cIdteShift].Gate.u16Sel = uSysR0Cs; + BS3_ASSERT(g_usBs3TestStep < 3000); + + /* + * Modify the gate CS value with a conforming segment. + */ + g_usBs3TestStep = 3000; + for (i = 0; i <= 3; i++) /* cs.dpl */ + { + for (iRing = 0; iRing <= 3; iRing++) + { + for (iCtx = 0; iCtx < RT_ELEMENTS(apCtx8x); iCtx++) + { + Bs3MemCpy(&CtxTmp, apCtx8x[iCtx], sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, iRing); +# if TMPL_BITS == 32 + g_uBs3TrapEipHint = CtxTmp.rip.u32; +# endif + + for (j = 0; j <= 3; j++) /* rpl */ + { + uint16_t const uCs = (uSysR0CsConf | j) + (i << BS3_SEL_RING_SHIFT); + /*Bs3TestPrintf("g_usBs3TestStep=%u iCtx=%u iRing=%u i=%u uCs=%04x\n", g_usBs3TestStep, iCtx, iRing, i, uCs);*/ + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u16Sel = uCs; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + //Bs3TestPrintf("%u/%u/%u/%u: cs=%04x hcs=%04x xcpt=%02x\n", i, iRing, iCtx, j, uCs, TrapCtx.uHandlerCs, TrapCtx.bXcpt); + /*Bs3TrapPrintFrame(&TrapCtx);*/ + g_usBs3TestStep++; + if (iCtx < iRing) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, ((0x80 + iCtx) << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + else if (i > iRing) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, uCs & X86_SEL_MASK_OFF_RPL); + else + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x80 + iCtx /*bXcpt*/); + } + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u16Sel = uSysR0Cs; + } + } + } + BS3_ASSERT(g_usBs3TestStep < 3500); + + /* + * The gates must be 64-bit in long mode. + */ + if (cIdteShift != 0) + { + g_usBs3TestStep = 3500; + for (i = 0; i <= 3; i++) + { + for (iRing = 0; iRing <= 3; iRing++) + { + for (iCtx = 0; iCtx < RT_ELEMENTS(apCtx8x); iCtx++) + { + Bs3MemCpy(&CtxTmp, apCtx8x[iCtx], sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, iRing); + + for (j = 0; j < 2; j++) + { + static const uint16_t s_auCSes[2] = { BS3_SEL_R0_CS16, BS3_SEL_R0_CS32 }; + uint16_t uCs = (s_auCSes[j] | i) + (i << BS3_SEL_RING_SHIFT); + g_usBs3TestStep++; + /*Bs3TestPrintf("g_usBs3TestStep=%u iCtx=%u iRing=%u i=%u uCs=%04x\n", g_usBs3TestStep, iCtx, iRing, i, uCs);*/ + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u16Sel = uCs; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + /*Bs3TrapPrintFrame(&TrapCtx);*/ + if (iCtx < iRing) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, ((0x80 + iCtx) << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, uCs & X86_SEL_MASK_OFF_RPL); + } + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u16Sel = uSysR0Cs; + } + } + } + BS3_ASSERT(g_usBs3TestStep < 4000); + } + + /* + * IDT limit check. The 286 does not access X86DESCGATE::u16OffsetHigh. + */ + g_usBs3TestStep = 5000; + i = (0x80 << (cIdteShift + 3)) - 1; + j = (0x82 << (cIdteShift + 3)) - (!f286 ? 1 : 3); + k = (0x83 << (cIdteShift + 3)) - 1; + for (; i <= k; i++, g_usBs3TestStep++) + { + Idtr = IdtrSaved; + Idtr.cbIdt = i; + ASMSetIDTR(&Idtr); + Bs3TrapSetJmpAndRestore(&Ctx81, &TrapCtx); + if (i < j) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx81, (0x81 << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + else + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx81, 0x81 /*bXcpt*/); + } + ASMSetIDTR(&IdtrSaved); + BS3_ASSERT(g_usBs3TestStep < 5100); + +# if TMPL_BITS != 16 /* Only do the paging related stuff in 32-bit and 64-bit modes. */ + + /* + * IDT page not present. Placing the IDT copy such that 0x80 is on the + * first page and 0x81 is on the second page. We need proceed to move + * it down byte by byte to check that any inaccessible byte means #PF. + * + * Note! We must reload the alternative IDTR for each run as any kind of + * printing to the string (like error reporting) will cause a switch + * to real mode and back, reloading the default IDTR. + */ + g_usBs3TestStep = 5200; + if (BS3_MODE_IS_PAGED(g_bTestMode) && pbIdtCopyAlloc) + { + uint32_t const uCr2Expected = Bs3SelPtrToFlat(pbIdtCopyAlloc) + _4K; + for (j = 0; j < cbIdte; j++) + { + pIdtCopy = (PX86DESC)&pbIdtCopyAlloc[_4K - cbIdte * 0x81 - j]; + Bs3MemCpy(pIdtCopy, paIdt, cbIdte * 256); + + Idtr.cbIdt = IdtrSaved.cbIdt; + Idtr.pIdt = Bs3SelPtrToFlat(pIdtCopy); + + ASMSetIDTR(&Idtr); + Bs3TrapSetJmpAndRestore(&Ctx81, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx81, 0x81 /*bXcpt*/); + g_usBs3TestStep++; + + ASMSetIDTR(&Idtr); + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx80, 0x80 /*bXcpt*/); + g_usBs3TestStep++; + + rc = Bs3PagingProtect(uCr2Expected, _4K, 0 /*fSet*/, X86_PTE_P /*fClear*/); + if (RT_SUCCESS(rc)) + { + ASMSetIDTR(&Idtr); + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx80, 0x80 /*bXcpt*/); + g_usBs3TestStep++; + + ASMSetIDTR(&Idtr); + Bs3TrapSetJmpAndRestore(&Ctx81, &TrapCtx); + if (f486Plus) + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx81, 0 /*uErrCd*/, uCr2Expected); + else + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx81, X86_TRAP_PF_RW /*uErrCd*/, uCr2Expected + 4 - RT_MIN(j, 4)); + g_usBs3TestStep++; + + Bs3PagingProtect(uCr2Expected, _4K, X86_PTE_P /*fSet*/, 0 /*fClear*/); + + /* Check if that the entry type is checked after the whole IDTE has been cleared for #PF. */ + pIdtCopy[0x80 << cIdteShift].Gate.u4Type = 0; + rc = Bs3PagingProtect(uCr2Expected, _4K, 0 /*fSet*/, X86_PTE_P /*fClear*/); + if (RT_SUCCESS(rc)) + { + ASMSetIDTR(&Idtr); + Bs3TrapSetJmpAndRestore(&Ctx81, &TrapCtx); + if (f486Plus) + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx81, 0 /*uErrCd*/, uCr2Expected); + else + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx81, X86_TRAP_PF_RW /*uErrCd*/, uCr2Expected + 4 - RT_MIN(j, 4)); + g_usBs3TestStep++; + + Bs3PagingProtect(uCr2Expected, _4K, X86_PTE_P /*fSet*/, 0 /*fClear*/); + } + } + else + Bs3TestPrintf("Bs3PagingProtectPtr: %d\n", i); + + ASMSetIDTR(&IdtrSaved); + } + } + + /* + * The read/write and user/supervisor bits the IDT PTEs are irrelevant. + */ + g_usBs3TestStep = 5300; + if (BS3_MODE_IS_PAGED(g_bTestMode) && pbIdtCopyAlloc) + { + Bs3MemCpy(pbIdtCopyAlloc, paIdt, cbIdte * 256); + Idtr.cbIdt = IdtrSaved.cbIdt; + Idtr.pIdt = Bs3SelPtrToFlat(pbIdtCopyAlloc); + + ASMSetIDTR(&Idtr); + Bs3TrapSetJmpAndRestore(&Ctx81, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx81, 0x81 /*bXcpt*/); + g_usBs3TestStep++; + + rc = Bs3PagingProtect(Idtr.pIdt, _4K, 0 /*fSet*/, X86_PTE_RW | X86_PTE_US /*fClear*/); + if (RT_SUCCESS(rc)) + { + ASMSetIDTR(&Idtr); + Bs3TrapSetJmpAndRestore(&Ctx81, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx81, 0x81 /*bXcpt*/); + g_usBs3TestStep++; + + Bs3PagingProtect(Idtr.pIdt, _4K, X86_PTE_RW | X86_PTE_US /*fSet*/, 0 /*fClear*/); + } + ASMSetIDTR(&IdtrSaved); + } + + /* + * Check that CS.u1Accessed is set to 1. Use the test page selector #0 and #3 together + * with interrupt gates 80h and 83h, respectively. + */ +/** @todo Throw in SS.u1Accessed too. */ + g_usBs3TestStep = 5400; + if (BS3_MODE_IS_PAGED(g_bTestMode) && pbIdtCopyAlloc) + { + Bs3GdteTestPage00 = Bs3Gdt[uSysR0Cs >> X86_SEL_SHIFT]; + Bs3GdteTestPage00.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + paIdt[0x80 << cIdteShift].Gate.u16Sel = BS3_SEL_TEST_PAGE_00; + + Bs3GdteTestPage03 = Bs3Gdt[(uSysR0Cs + (3 << BS3_SEL_RING_SHIFT)) >> X86_SEL_SHIFT]; + Bs3GdteTestPage03.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + paIdt[0x83 << cIdteShift].Gate.u16Sel = BS3_SEL_TEST_PAGE_03; /* rpl is ignored, so leave it as zero. */ + + /* Check that the CS.A bit is being set on a general basis and that + the special CS values work with out generic handler code. */ + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx80, 0x80 /*bXcpt*/); + if (!(Bs3GdteTestPage00.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + bs3CpuBasic2_FailedF("u4Type=%#x, not accessed", Bs3GdteTestPage00.Gen.u4Type); + g_usBs3TestStep++; + + Bs3MemCpy(&CtxTmp, &Ctx83, sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, 3); + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x83 /*bXcpt*/); + if (!(Bs3GdteTestPage03.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + bs3CpuBasic2_FailedF("u4Type=%#x, not accessed!", Bs3GdteTestPage00.Gen.u4Type); + if (TrapCtx.uHandlerCs != (BS3_SEL_TEST_PAGE_03 | 3)) + bs3CpuBasic2_FailedF("uHandlerCs=%#x, expected %#x", TrapCtx.uHandlerCs, (BS3_SEL_TEST_PAGE_03 | 3)); + g_usBs3TestStep++; + + /* + * Now check that setting CS.u1Access to 1 does __NOT__ trigger a page + * fault due to the RW bit being zero. + * (We check both with with and without the WP bit if 80486.) + */ + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80486) + ASMSetCR0(uCr0Saved | X86_CR0_WP); + + Bs3GdteTestPage00.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + Bs3GdteTestPage03.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + rc = Bs3PagingProtect(GdtrSaved.pGdt + BS3_SEL_TEST_PAGE_00, 8, 0 /*fSet*/, X86_PTE_RW /*fClear*/); + if (RT_SUCCESS(rc)) + { + /* ring-0 handler */ + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx80, 0x80 /*bXcpt*/); + if (!(Bs3GdteTestPage00.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + bs3CpuBasic2_FailedF("u4Type=%#x, not accessed!", Bs3GdteTestPage00.Gen.u4Type); + g_usBs3TestStep++; + + /* ring-3 handler */ + Bs3MemCpy(&CtxTmp, &Ctx83, sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, 3); + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x83 /*bXcpt*/); + if (!(Bs3GdteTestPage03.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + bs3CpuBasic2_FailedF("u4Type=%#x, not accessed!", Bs3GdteTestPage00.Gen.u4Type); + g_usBs3TestStep++; + + /* clear WP and repeat the above. */ + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80486) + ASMSetCR0(uCr0Saved & ~X86_CR0_WP); + Bs3GdteTestPage00.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; /* (No need to RW the page - ring-0, WP=0.) */ + Bs3GdteTestPage03.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; /* (No need to RW the page - ring-0, WP=0.) */ + + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &Ctx80, 0x80 /*bXcpt*/); + if (!(Bs3GdteTestPage00.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + bs3CpuBasic2_FailedF("u4Type=%#x, not accessed!", Bs3GdteTestPage00.Gen.u4Type); + g_usBs3TestStep++; + + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x83 /*bXcpt*/); + if (!(Bs3GdteTestPage03.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + bs3CpuBasic2_FailedF("u4Type=%#x, not accessed!n", Bs3GdteTestPage03.Gen.u4Type); + g_usBs3TestStep++; + + Bs3PagingProtect(GdtrSaved.pGdt + BS3_SEL_TEST_PAGE_00, 8, X86_PTE_RW /*fSet*/, 0 /*fClear*/); + } + + ASMSetCR0(uCr0Saved); + + /* + * While we're here, check that if the CS GDT entry is a non-present + * page we do get a #PF with the rigth error code and CR2. + */ + Bs3GdteTestPage00.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; /* Just for fun, really a pointless gesture. */ + Bs3GdteTestPage03.Gen.u4Type &= ~X86_SEL_TYPE_ACCESSED; + rc = Bs3PagingProtect(GdtrSaved.pGdt + BS3_SEL_TEST_PAGE_00, 8, 0 /*fSet*/, X86_PTE_P /*fClear*/); + if (RT_SUCCESS(rc)) + { + Bs3TrapSetJmpAndRestore(&Ctx80, &TrapCtx); + if (f486Plus) + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx80, 0 /*uErrCd*/, GdtrSaved.pGdt + BS3_SEL_TEST_PAGE_00); + else + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx80, X86_TRAP_PF_RW, GdtrSaved.pGdt + BS3_SEL_TEST_PAGE_00 + 4); + g_usBs3TestStep++; + + /* Do it from ring-3 to check ErrCd, which doesn't set X86_TRAP_PF_US it turns out. */ + Bs3MemCpy(&CtxTmp, &Ctx83, sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, 3); + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + + if (f486Plus) + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &CtxTmp, 0 /*uErrCd*/, GdtrSaved.pGdt + BS3_SEL_TEST_PAGE_03); + else + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &CtxTmp, X86_TRAP_PF_RW, GdtrSaved.pGdt + BS3_SEL_TEST_PAGE_03 + 4); + g_usBs3TestStep++; + + Bs3PagingProtect(GdtrSaved.pGdt + BS3_SEL_TEST_PAGE_00, 8, X86_PTE_P /*fSet*/, 0 /*fClear*/); + if (Bs3GdteTestPage00.Gen.u4Type & X86_SEL_TYPE_ACCESSED) + bs3CpuBasic2_FailedF("u4Type=%#x, accessed! #1", Bs3GdteTestPage00.Gen.u4Type); + if (Bs3GdteTestPage03.Gen.u4Type & X86_SEL_TYPE_ACCESSED) + bs3CpuBasic2_FailedF("u4Type=%#x, accessed! #2", Bs3GdteTestPage03.Gen.u4Type); + } + + /* restore */ + paIdt[0x80 << cIdteShift].Gate.u16Sel = uSysR0Cs; + paIdt[0x83 << cIdteShift].Gate.u16Sel = uSysR0Cs;// + (3 << BS3_SEL_RING_SHIFT) + 3; + } + +# endif /* 32 || 64*/ + + /* + * Check broad EFLAGS effects. + */ + g_usBs3TestStep = 5600; + for (iCtx = 0; iCtx < RT_ELEMENTS(apCtx8x); iCtx++) + { + for (iRing = 0; iRing < 4; iRing++) + { + Bs3MemCpy(&CtxTmp, apCtx8x[iCtx], sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, iRing); + + /* all set */ + CtxTmp.rflags.u32 &= X86_EFL_VM | X86_EFL_1; + CtxTmp.rflags.u32 |= X86_EFL_CF | X86_EFL_PF | X86_EFL_AF | X86_EFL_ZF | X86_EFL_SF /* | X86_EFL_TF */ /*| X86_EFL_IF*/ + | X86_EFL_DF | X86_EFL_OF | X86_EFL_IOPL /* | X86_EFL_NT*/; + if (f486Plus) + CtxTmp.rflags.u32 |= X86_EFL_AC; + if (f486Plus && !g_f16BitSys) + CtxTmp.rflags.u32 |= X86_EFL_RF; + if (g_uBs3CpuDetected & BS3CPU_F_CPUID) + CtxTmp.rflags.u32 |= X86_EFL_VIF | X86_EFL_VIP; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + CtxTmp.rflags.u32 &= ~X86_EFL_RF; + + if (iCtx >= iRing) + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x80 + iCtx /*bXcpt*/); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, ((0x80 + iCtx) << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + uExpected = CtxTmp.rflags.u32 + & ( X86_EFL_1 | X86_EFL_CF | X86_EFL_PF | X86_EFL_AF | X86_EFL_ZF | X86_EFL_SF | X86_EFL_DF + | X86_EFL_OF | X86_EFL_IOPL | X86_EFL_NT | X86_EFL_VM | X86_EFL_AC | X86_EFL_VIF | X86_EFL_VIP + | X86_EFL_ID /*| X86_EFL_TF*/ /*| X86_EFL_IF*/ /*| X86_EFL_RF*/ ); + if (TrapCtx.fHandlerRfl != uExpected) + bs3CpuBasic2_FailedF("unexpected handler rflags value: %RX64 expected %RX32; CtxTmp.rflags=%RX64 Ctx.rflags=%RX64\n", + TrapCtx.fHandlerRfl, uExpected, CtxTmp.rflags.u, TrapCtx.Ctx.rflags.u); + g_usBs3TestStep++; + + /* all cleared */ + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) < BS3CPU_80286) + CtxTmp.rflags.u32 = apCtx8x[iCtx]->rflags.u32 & (X86_EFL_RA1_MASK | UINT16_C(0xf000)); + else + CtxTmp.rflags.u32 = apCtx8x[iCtx]->rflags.u32 & (X86_EFL_VM | X86_EFL_RA1_MASK); + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + if (iCtx >= iRing) + bs3CpuBasic2_CompareIntCtx1(&TrapCtx, &CtxTmp, 0x80 + iCtx /*bXcpt*/); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, ((0x80 + iCtx) << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + uExpected = CtxTmp.rflags.u32; + if (TrapCtx.fHandlerRfl != uExpected) + bs3CpuBasic2_FailedF("unexpected handler rflags value: %RX64 expected %RX32; CtxTmp.rflags=%RX64 Ctx.rflags=%RX64\n", + TrapCtx.fHandlerRfl, uExpected, CtxTmp.rflags.u, TrapCtx.Ctx.rflags.u); + g_usBs3TestStep++; + } + } + +/** @todo CS.LIMIT / canonical(CS) */ + + + /* + * Check invalid gate types. + */ + g_usBs3TestStep = 32000; + for (iRing = 0; iRing <= 3; iRing++) + { + static const uint16_t s_auCSes[] = { BS3_SEL_R0_CS16, BS3_SEL_R0_CS32, BS3_SEL_R0_CS64, + BS3_SEL_TSS16, BS3_SEL_TSS32, BS3_SEL_TSS64, 0, BS3_SEL_SPARE_1f }; + static uint16_t const s_auInvlTypes64[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f }; + static uint16_t const s_auInvlTypes32[] = { 0, 1, 2, 3, 8, 9, 10, 11, 13, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + /*286:*/ 12, 14, 15 }; + uint16_t const * const pauInvTypes = cIdteShift != 0 ? s_auInvlTypes64 : s_auInvlTypes32; + uint16_t const cInvTypes = cIdteShift != 0 ? RT_ELEMENTS(s_auInvlTypes64) + : f386Plus ? RT_ELEMENTS(s_auInvlTypes32) - 3 : RT_ELEMENTS(s_auInvlTypes32); + + + for (iCtx = 0; iCtx < RT_ELEMENTS(apCtx8x); iCtx++) + { + unsigned iType; + + Bs3MemCpy(&CtxTmp, apCtx8x[iCtx], sizeof(CtxTmp)); + Bs3RegCtxConvertToRingX(&CtxTmp, iRing); +# if TMPL_BITS == 32 + g_uBs3TrapEipHint = CtxTmp.rip.u32; +# endif + for (iType = 0; iType < cInvTypes; iType++) + { + uint8_t const bSavedType = paIdt[(0x80 + iCtx) << cIdteShift].Gate.u4Type; + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u1DescType = pauInvTypes[iType] >> 4; + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u4Type = pauInvTypes[iType] & 0xf; + + for (i = 0; i < 4; i++) + { + for (j = 0; j < RT_ELEMENTS(s_auCSes); j++) + { + uint16_t uCs = (unsigned)(s_auCSes[j] - BS3_SEL_R0_FIRST) < (unsigned)(4 << BS3_SEL_RING_SHIFT) + ? (s_auCSes[j] | i) + (i << BS3_SEL_RING_SHIFT) + : s_auCSes[j] | i; + /*Bs3TestPrintf("g_usBs3TestStep=%u iCtx=%u iRing=%u i=%u uCs=%04x type=%#x\n", g_usBs3TestStep, iCtx, iRing, i, uCs, pauInvTypes[iType]);*/ + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u16Sel = uCs; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + g_usBs3TestStep++; + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, ((0x80 + iCtx) << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + + /* Mark it not-present to check that invalid type takes precedence. */ + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u1Present = 0; + Bs3TrapSetJmpAndRestore(&CtxTmp, &TrapCtx); + g_usBs3TestStep++; + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxTmp, ((0x80 + iCtx) << X86_TRAP_ERR_SEL_SHIFT) | X86_TRAP_ERR_IDT); + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u1Present = 1; + } + } + + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u16Sel = uSysR0Cs; + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u4Type = bSavedType; + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u1DescType = 0; + paIdt[(0x80 + iCtx) << cIdteShift].Gate.u1Present = 1; + } + } + } + BS3_ASSERT(g_usBs3TestStep < 62000U && g_usBs3TestStep > 32000U); + + + /** @todo + * - Run \#PF and \#GP (and others?) at CPLs other than zero. + * - Quickly generate all faults. + * - All the peculiarities v8086. + */ + +# if TMPL_BITS != 16 + Bs3MemFree(pbIdtCopyAlloc, 12*_1K); +# endif +} +#endif /* convert me */ + + +static void bs3CpuBasic2_RaiseXcpt11Worker(uint8_t bMode, uint8_t *pbBuf, unsigned cbCacheLine, bool fAm, bool fPf, + RTCCUINTXREG uFlatBufPtr, BS3CPUBASIC2PFTTSTCMNMODE const BS3_FAR *pCmn) +{ + BS3TRAPFRAME TrapCtx; + BS3REGCTX Ctx; + BS3REGCTX CtxUdExpected; + uint8_t const cRings = bMode == BS3_MODE_RM ? 1 : 4; + uint8_t iRing; + uint16_t iTest; + + /* make sure they're allocated */ + Bs3MemZero(&TrapCtx, sizeof(TrapCtx)); + Bs3MemZero(&Ctx, sizeof(Ctx)); + Bs3MemZero(&CtxUdExpected, sizeof(CtxUdExpected)); + + /* + * Test all relevant rings. + * + * The memory operand is ds:xBX, so point it to pbBuf. + * The test snippets mostly use xAX as operand, with the div + * one also using xDX, so make sure they make some sense. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 512); + + Ctx.cr0.u32 &= ~(X86_CR0_MP | X86_CR0_EM | X86_CR0_TS); /* so fninit + fld works */ + + for (iRing = BS3_MODE_IS_V86(bMode) ? 3 : 0; iRing < cRings; iRing++) + { + uint32_t uEbx; + uint8_t fAc; + + if (!BS3_MODE_IS_RM_OR_V86(bMode)) + Bs3RegCtxConvertToRingX(&Ctx, iRing); + + if (!fPf || BS3_MODE_IS_32BIT_CODE(bMode) || BS3_MODE_IS_64BIT_CODE(bMode)) + Bs3RegCtxSetGrpDsFromCurPtr(&Ctx, &Ctx.rbx, pbBuf); + else + { + /* Bs3RegCtxSetGrpDsFromCurPtr barfs when trying to output a sel:off address for the aliased buffer. */ + Ctx.ds = BS3_FP_SEG(pbBuf); + Ctx.rbx.u32 = BS3_FP_OFF(pbBuf); + } + uEbx = Ctx.rbx.u32; + + Ctx.rax.u = (bMode & BS3_MODE_CODE_MASK) == BS3_MODE_CODE_64 + ? UINT64_C(0x80868028680386fe) : UINT32_C(0x65020686); + Ctx.rdx.u = UINT32_C(0x00100100); /* careful with range due to div */ + + Bs3MemCpy(&CtxUdExpected, &Ctx, sizeof(Ctx)); + + /* + * AC flag loop. + */ + for (fAc = 0; fAc < 2; fAc++) + { + if (fAc) + Ctx.rflags.u32 |= X86_EFL_AC; + else + Ctx.rflags.u32 &= ~X86_EFL_AC; + + /* + * Loop over the test snippets. + */ + for (iTest = 0; iTest < pCmn->cEntries; iTest++) + { + uint8_t const fOp = pCmn->paEntries[iTest].fOp; + uint16_t const cbMem = pCmn->paEntries[iTest].cbMem; + uint8_t const cbAlign = pCmn->paEntries[iTest].cbAlign; + uint16_t const cbMax = cbCacheLine + cbMem; + uint16_t offMem; + uint8_t BS3_FAR *poffUd = (uint8_t BS3_FAR *)Bs3SelLnkPtrToCurPtr(pCmn->paEntries[iTest].pfn); + Bs3RegCtxSetRipCsFromLnkPtr(&Ctx, pCmn->paEntries[iTest].pfn); + CtxUdExpected.rip = Ctx.rip; + CtxUdExpected.rip.u = Ctx.rip.u + poffUd[-1]; + CtxUdExpected.cs = Ctx.cs; + CtxUdExpected.rflags = Ctx.rflags; + if (bMode == BS3_MODE_RM) + CtxUdExpected.rflags.u32 &= ~X86_EFL_AC; /** @todo investigate. automatically cleared, or is it just our code? Observed with bs3-cpu-instr-3 too (10980xe), seems to be the CPU doing it. */ + CtxUdExpected.rdx = Ctx.rdx; + CtxUdExpected.rax = Ctx.rax; + if (fOp & MYOP_LD) + { + switch (cbMem) + { + case 2: + CtxUdExpected.rax.u16 = 0x0101; + break; + case 4: + CtxUdExpected.rax.u32 = UINT32_C(0x01010101); + break; + case 8: + CtxUdExpected.rax.u64 = UINT64_C(0x0101010101010101); + break; + } + } + + /* + * Buffer misalignment loop. + * Note! We must make sure to cross a cache line here to make sure + * to cover the split-lock scenario. (The buffer is cache + * line aligned.) + */ + for (offMem = 0; offMem < cbMax; offMem++) + { + bool const fMisaligned = (offMem & (cbAlign - 1)) != 0; + unsigned offBuf = cbMax + cbMem * 2; + while (offBuf-- > 0) + pbBuf[offBuf] = 1; /* byte-by-byte to make sure it doesn't trigger AC. */ + + CtxUdExpected.rbx.u32 = Ctx.rbx.u32 = uEbx + offMem; /* ASSUMES memory in first 4GB. */ + if (BS3_MODE_IS_16BIT_SYS(bMode)) + g_uBs3TrapEipHint = Ctx.rip.u32; + + //Bs3TestPrintf("iRing=%d iTest=%d cs:rip=%04RX16:%08RX32 ds:rbx=%04RX16:%08RX32 ss:esp=%04RX16:%08RX32 bXcpt=%#x errcd=%#x fAm=%d fAc=%d ESP=%#RX32\n", + // iRing, iTest, Ctx.cs, Ctx.rip.u32, Ctx.ds, Ctx.rbx.u32, Ctx.ss, Ctx.rsp.u32, TrapCtx.bXcpt, (unsigned)TrapCtx.uErrCd, fAm, fAc, ASMGetESP()); + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + + if ( (pCmn->paEntries[iTest].fOp & MYOP_AC_GP) + && fMisaligned + && (!fAm || iRing != 3 || !fAc || (offMem & 3 /* 10980XE */) == 0) ) + { + if (fAc && bMode == BS3_MODE_RM) + TrapCtx.Ctx.rflags.u32 |= X86_EFL_AC; + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + } + else if (fPf && iRing == 3 && (!fAm || !fAc || !fMisaligned)) /* #AC beats #PF */ + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx, + X86_TRAP_PF_P | X86_TRAP_PF_US + | (pCmn->paEntries[iTest].fOp & MYOP_ST ? X86_TRAP_PF_RW : 0), + uFlatBufPtr + offMem + (cbMem > 64 ? cbMem - 1 /*FXSAVE*/ : 0), + pCmn->paEntries[iTest].offFaultInstr); + else if (!fAm || iRing != 3 || !fAc || !fMisaligned) + { + if (fOp & MYOP_EFL) + { + CtxUdExpected.rflags.u16 &= ~X86_EFL_STATUS_BITS; + CtxUdExpected.rflags.u16 |= TrapCtx.Ctx.rflags.u16 & X86_EFL_STATUS_BITS; + } + if (fOp == MYOP_LD_DIV) + { + CtxUdExpected.rax = TrapCtx.Ctx.rax; + CtxUdExpected.rdx = TrapCtx.Ctx.rdx; + } + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + } + else + bs3CpuBasic2_CompareAcCtx(&TrapCtx, &Ctx, pCmn->paEntries[iTest].offFaultInstr); + + g_usBs3TestStep++; + } + } + } + } +} + + +/** + * Entrypoint for \#AC tests. + * + * @returns 0 or BS3TESTDOMODE_SKIPPED. + * @param bMode The CPU mode we're testing. + * + * @note When testing v8086 code, we'll be running in v8086 mode. So, careful + * with control registers and such. + */ +BS3_DECL_FAR(uint8_t) BS3_CMN_FAR_NM(bs3CpuBasic2_RaiseXcpt11)(uint8_t bMode) +{ + unsigned cbCacheLine = 128; /** @todo detect */ + uint8_t BS3_FAR *pbBufAlloc; + uint8_t BS3_FAR *pbBuf; + unsigned idxCmnModes; + uint32_t fCr0; + + /* + * Skip if 386 or older. + */ + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) < BS3CPU_80486) + { + Bs3TestSkipped("#AC test requires 486 or later"); + return BS3TESTDOMODE_SKIPPED; + } + + bs3CpuBasic2_SetGlobals(bMode); + + /* Get us a 64-byte aligned buffer. */ + pbBufAlloc = pbBuf = Bs3MemAllocZ(BS3_MODE_IS_RM_OR_V86(bMode) ? BS3MEMKIND_REAL : BS3MEMKIND_TILED, X86_PAGE_SIZE * 2); + if (!pbBufAlloc) + return Bs3TestFailed("Failed to allocate 2 pages of real-mode memory"); + if (BS3_FP_OFF(pbBuf) & (X86_PAGE_SIZE - 1)) + pbBuf = &pbBufAlloc[X86_PAGE_SIZE - (BS3_FP_OFF(pbBuf) & X86_PAGE_OFFSET_MASK)]; + BS3_ASSERT(pbBuf - pbBufAlloc <= X86_PAGE_SIZE); + //Bs3TestPrintf("pbBuf=%p\n", pbBuf); + + /* Find the g_aCmnModes entry. */ + idxCmnModes = 0; + while (g_aCmnModes[idxCmnModes].bMode != (bMode & BS3_MODE_CODE_MASK)) + idxCmnModes++; + //Bs3TestPrintf("idxCmnModes=%d bMode=%#x\n", idxCmnModes, bMode); + + /* First round is w/o alignment checks enabled. */ + //Bs3TestPrintf("round 1\n"); + fCr0 = Bs3RegGetCr0(); + BS3_ASSERT(!(fCr0 & X86_CR0_AM)); + Bs3RegSetCr0(fCr0 & ~X86_CR0_AM); +#if 1 + bs3CpuBasic2_RaiseXcpt11Worker(bMode, pbBuf, cbCacheLine, false /*fAm*/, false /*fPf*/, 0, &g_aCmnModes[idxCmnModes]); +#endif + + /* The second round is with aligment checks enabled. */ +#if 1 + //Bs3TestPrintf("round 2\n"); + Bs3RegSetCr0(Bs3RegGetCr0() | X86_CR0_AM); + bs3CpuBasic2_RaiseXcpt11Worker(bMode, pbBuf, cbCacheLine, true /*fAm*/, false /*fPf*/, 0, &g_aCmnModes[idxCmnModes]); +#endif + +#if 1 + /* The third and fourth round access the buffer via a page alias that's not + accessible from ring-3. The third round has ACs disabled and the fourth + has them enabled. */ + if (BS3_MODE_IS_PAGED(bMode) && !BS3_MODE_IS_V86(bMode)) + { + /* Alias the buffer as system memory so ring-3 access with AC+AM will cause #PF: */ + /** @todo the aliasing is not necessary any more... */ + int rc; + RTCCUINTXREG uFlatBufPtr = Bs3SelPtrToFlat(pbBuf); + uint64_t const uAliasPgPtr = bMode & BS3_MODE_CODE_64 ? UINT64_C(0x0000648680000000) : UINT32_C(0x80000000); + rc = Bs3PagingAlias(uAliasPgPtr, uFlatBufPtr & ~(uint64_t)X86_PAGE_OFFSET_MASK, X86_PAGE_SIZE * 2, + X86_PTE_P | X86_PTE_RW); + if (RT_SUCCESS(rc)) + { + /* We 'misalign' the segment base here to make sure it's the final + address that gets alignment checked and not just the operand value. */ + RTCCUINTXREG uAliasBufPtr = (RTCCUINTXREG)uAliasPgPtr + (uFlatBufPtr & X86_PAGE_OFFSET_MASK); + uint8_t BS3_FAR *pbBufAlias = BS3_FP_MAKE(BS3_SEL_SPARE_00 | 3, (uFlatBufPtr & X86_PAGE_OFFSET_MASK) + 1); + Bs3SelSetup16BitData(&Bs3GdteSpare00, uAliasPgPtr - 1); + + //Bs3TestPrintf("round 3 pbBufAlias=%p\n", pbBufAlias); + Bs3RegSetCr0(Bs3RegGetCr0() & ~X86_CR0_AM); + bs3CpuBasic2_RaiseXcpt11Worker(bMode, pbBufAlias, cbCacheLine, false /*fAm*/, + true /*fPf*/, uAliasBufPtr, &g_aCmnModes[idxCmnModes]); + + //Bs3TestPrintf("round 4\n"); + Bs3RegSetCr0(Bs3RegGetCr0() | X86_CR0_AM); + bs3CpuBasic2_RaiseXcpt11Worker(bMode, pbBufAlias, cbCacheLine, true /*fAm*/, + true /*fPf*/, uAliasBufPtr, &g_aCmnModes[idxCmnModes]); + + Bs3PagingUnalias(uAliasPgPtr, X86_PAGE_SIZE * 2); + } + else + Bs3TestFailedF("Bs3PagingAlias failed with %Rrc", rc); + } +#endif + + Bs3MemFree(pbBufAlloc, X86_PAGE_SIZE * 2); + Bs3RegSetCr0(fCr0); + return 0; +} + + +/** + * Executes one round of SIDT and SGDT tests using one assembly worker. + * + * This is written with driving everything from the 16-bit or 32-bit worker in + * mind, i.e. not assuming the test bitcount is the same as the current. + */ +static void bs3CpuBasic2_sidt_sgdt_One(BS3CB2SIDTSGDT const BS3_FAR *pWorker, uint8_t bTestMode, uint8_t bRing, + uint8_t const *pbExpected) +{ + BS3TRAPFRAME TrapCtx; + BS3REGCTX Ctx; + BS3REGCTX CtxUdExpected; + BS3REGCTX TmpCtx; + uint8_t const cbBuf = 8*2; /* test buffer area */ + uint8_t abBuf[8*2 + 8 + 8]; /* test buffer w/ misalignment test space and some extra guard. */ + uint8_t BS3_FAR *pbBuf = abBuf; + uint8_t const cbIdtr = BS3_MODE_IS_64BIT_CODE(bTestMode) ? 2+8 : 2+4; + bool const f286 = (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) == BS3CPU_80286; + uint8_t bFiller; + int off; + int off2; + unsigned cb; + uint8_t BS3_FAR *pbTest; + + /* make sure they're allocated */ + Bs3MemZero(&Ctx, sizeof(Ctx)); + Bs3MemZero(&CtxUdExpected, sizeof(CtxUdExpected)); + Bs3MemZero(&TmpCtx, sizeof(TmpCtx)); + Bs3MemZero(&TrapCtx, sizeof(TrapCtx)); + Bs3MemZero(&abBuf, sizeof(abBuf)); + + /* Create a context, give this routine some more stack space, point the context + at our SIDT [xBX] + UD2 combo, and point DS:xBX at abBuf. */ + Bs3RegCtxSaveEx(&Ctx, bTestMode, 256 /*cbExtraStack*/); + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, pWorker->fSs ? &Ctx.ss : &Ctx.ds, abBuf); + Bs3RegCtxSetRipCsFromLnkPtr(&Ctx, pWorker->fpfnWorker); + if (BS3_MODE_IS_16BIT_SYS(bTestMode)) + g_uBs3TrapEipHint = Ctx.rip.u32; + if (!BS3_MODE_IS_RM_OR_V86(bTestMode)) + Bs3RegCtxConvertToRingX(&Ctx, bRing); + + /* For successful SIDT attempts, we'll stop at the UD2. */ + Bs3MemCpy(&CtxUdExpected, &Ctx, sizeof(Ctx)); + CtxUdExpected.rip.u += pWorker->cbInstr; + + /* + * Check that it works at all and that only bytes we expect gets written to. + */ + /* First with zero buffer. */ + Bs3MemZero(abBuf, sizeof(abBuf)); + if (!ASMMemIsAllU8(abBuf, sizeof(abBuf), 0)) + Bs3TestFailedF("ASMMemIsAllU8 or Bs3MemZero is busted: abBuf=%.*Rhxs\n", sizeof(abBuf), pbBuf); + if (!ASMMemIsZero(abBuf, sizeof(abBuf))) + Bs3TestFailedF("ASMMemIsZero or Bs3MemZero is busted: abBuf=%.*Rhxs\n", sizeof(abBuf), pbBuf); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (f286 && abBuf[cbIdtr - 1] != 0xff) + Bs3TestFailedF("286: Top base byte isn't 0xff (#1): %#x\n", abBuf[cbIdtr - 1]); + if (!ASMMemIsZero(&abBuf[cbIdtr], cbBuf - cbIdtr)) + Bs3TestFailedF("Unexpected buffer bytes set (#1): cbIdtr=%u abBuf=%.*Rhxs\n", cbIdtr, cbBuf, pbBuf); + if (Bs3MemCmp(abBuf, pbExpected, cbIdtr) != 0) + Bs3TestFailedF("Mismatch (%s,#1): expected %.*Rhxs, got %.*Rhxs\n", pWorker->pszDesc, cbIdtr, pbExpected, cbIdtr, abBuf); + g_usBs3TestStep++; + + /* Again with a buffer filled with a byte not occuring in the previous result. */ + bFiller = 0x55; + while (Bs3MemChr(abBuf, bFiller, cbBuf) != NULL) + bFiller++; + Bs3MemSet(abBuf, bFiller, sizeof(abBuf)); + if (!ASMMemIsAllU8(abBuf, sizeof(abBuf), bFiller)) + Bs3TestFailedF("ASMMemIsAllU8 or Bs3MemSet is busted: bFiller=%#x abBuf=%.*Rhxs\n", bFiller, sizeof(abBuf), pbBuf); + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (f286 && abBuf[cbIdtr - 1] != 0xff) + Bs3TestFailedF("286: Top base byte isn't 0xff (#2): %#x\n", abBuf[cbIdtr - 1]); + if (!ASMMemIsAllU8(&abBuf[cbIdtr], cbBuf - cbIdtr, bFiller)) + Bs3TestFailedF("Unexpected buffer bytes set (#2): cbIdtr=%u bFiller=%#x abBuf=%.*Rhxs\n", cbIdtr, bFiller, cbBuf, pbBuf); + if (Bs3MemChr(abBuf, bFiller, cbIdtr) != NULL) + Bs3TestFailedF("Not all bytes touched: cbIdtr=%u bFiller=%#x abBuf=%.*Rhxs\n", cbIdtr, bFiller, cbBuf, pbBuf); + if (Bs3MemCmp(abBuf, pbExpected, cbIdtr) != 0) + Bs3TestFailedF("Mismatch (%s,#2): expected %.*Rhxs, got %.*Rhxs\n", pWorker->pszDesc, cbIdtr, pbExpected, cbIdtr, abBuf); + g_usBs3TestStep++; + + /* + * Slide the buffer along 8 bytes to cover misalignment. + */ + for (off = 0; off < 8; off++) + { + pbBuf = &abBuf[off]; + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, pWorker->fSs ? &Ctx.ss : &Ctx.ds, &abBuf[off]); + CtxUdExpected.rbx.u = Ctx.rbx.u; + + /* First with zero buffer. */ + Bs3MemZero(abBuf, sizeof(abBuf)); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (off > 0 && !ASMMemIsZero(abBuf, off)) + Bs3TestFailedF("Unexpected buffer bytes set before (#3): cbIdtr=%u off=%u abBuf=%.*Rhxs\n", + cbIdtr, off, off + cbBuf, abBuf); + if (!ASMMemIsZero(&abBuf[off + cbIdtr], sizeof(abBuf) - cbIdtr - off)) + Bs3TestFailedF("Unexpected buffer bytes set after (#3): cbIdtr=%u off=%u abBuf=%.*Rhxs\n", + cbIdtr, off, off + cbBuf, abBuf); + if (f286 && abBuf[off + cbIdtr - 1] != 0xff) + Bs3TestFailedF("286: Top base byte isn't 0xff (#3): %#x\n", abBuf[off + cbIdtr - 1]); + if (Bs3MemCmp(&abBuf[off], pbExpected, cbIdtr) != 0) + Bs3TestFailedF("Mismatch (#3): expected %.*Rhxs, got %.*Rhxs\n", cbIdtr, pbExpected, cbIdtr, &abBuf[off]); + g_usBs3TestStep++; + + /* Again with a buffer filled with a byte not occuring in the previous result. */ + Bs3MemSet(abBuf, bFiller, sizeof(abBuf)); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (off > 0 && !ASMMemIsAllU8(abBuf, off, bFiller)) + Bs3TestFailedF("Unexpected buffer bytes set before (#4): cbIdtr=%u off=%u bFiller=%#x abBuf=%.*Rhxs\n", + cbIdtr, off, bFiller, off + cbBuf, abBuf); + if (!ASMMemIsAllU8(&abBuf[off + cbIdtr], sizeof(abBuf) - cbIdtr - off, bFiller)) + Bs3TestFailedF("Unexpected buffer bytes set after (#4): cbIdtr=%u off=%u bFiller=%#x abBuf=%.*Rhxs\n", + cbIdtr, off, bFiller, off + cbBuf, abBuf); + if (Bs3MemChr(&abBuf[off], bFiller, cbIdtr) != NULL) + Bs3TestFailedF("Not all bytes touched (#4): cbIdtr=%u off=%u bFiller=%#x abBuf=%.*Rhxs\n", + cbIdtr, off, bFiller, off + cbBuf, abBuf); + if (f286 && abBuf[off + cbIdtr - 1] != 0xff) + Bs3TestFailedF("286: Top base byte isn't 0xff (#4): %#x\n", abBuf[off + cbIdtr - 1]); + if (Bs3MemCmp(&abBuf[off], pbExpected, cbIdtr) != 0) + Bs3TestFailedF("Mismatch (#4): expected %.*Rhxs, got %.*Rhxs\n", cbIdtr, pbExpected, cbIdtr, &abBuf[off]); + g_usBs3TestStep++; + } + pbBuf = abBuf; + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, pWorker->fSs ? &Ctx.ss : &Ctx.ds, abBuf); + CtxUdExpected.rbx.u = Ctx.rbx.u; + + /* + * Play with the selector limit if the target mode supports limit checking + * We use BS3_SEL_TEST_PAGE_00 for this + */ + if ( !BS3_MODE_IS_RM_OR_V86(bTestMode) + && !BS3_MODE_IS_64BIT_CODE(bTestMode)) + { + uint16_t cbLimit; + uint32_t uFlatBuf = Bs3SelPtrToFlat(abBuf); + Bs3GdteTestPage00 = Bs3Gdte_DATA16; + Bs3GdteTestPage00.Gen.u2Dpl = bRing; + Bs3GdteTestPage00.Gen.u16BaseLow = (uint16_t)uFlatBuf; + Bs3GdteTestPage00.Gen.u8BaseHigh1 = (uint8_t)(uFlatBuf >> 16); + Bs3GdteTestPage00.Gen.u8BaseHigh2 = (uint8_t)(uFlatBuf >> 24); + + if (pWorker->fSs) + CtxUdExpected.ss = Ctx.ss = BS3_SEL_TEST_PAGE_00 | bRing; + else + CtxUdExpected.ds = Ctx.ds = BS3_SEL_TEST_PAGE_00 | bRing; + + /* Expand up (normal). */ + for (off = 0; off < 8; off++) + { + CtxUdExpected.rbx.u = Ctx.rbx.u = off; + for (cbLimit = 0; cbLimit < cbIdtr*2; cbLimit++) + { + Bs3GdteTestPage00.Gen.u16LimitLow = cbLimit; + Bs3MemSet(abBuf, bFiller, sizeof(abBuf)); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (off + cbIdtr <= cbLimit + 1) + { + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (Bs3MemChr(&abBuf[off], bFiller, cbIdtr) != NULL) + Bs3TestFailedF("Not all bytes touched (#5): cbIdtr=%u off=%u cbLimit=%u bFiller=%#x abBuf=%.*Rhxs\n", + cbIdtr, off, cbLimit, bFiller, off + cbBuf, abBuf); + if (Bs3MemCmp(&abBuf[off], pbExpected, cbIdtr) != 0) + Bs3TestFailedF("Mismatch (#5): expected %.*Rhxs, got %.*Rhxs\n", cbIdtr, pbExpected, cbIdtr, &abBuf[off]); + if (f286 && abBuf[off + cbIdtr - 1] != 0xff) + Bs3TestFailedF("286: Top base byte isn't 0xff (#5): %#x\n", abBuf[off + cbIdtr - 1]); + } + else + { + if (pWorker->fSs) + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &Ctx, 0, false /*f486ResumeFlagHint*/); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + if (off + 2 <= cbLimit + 1) + { + if (Bs3MemChr(&abBuf[off], bFiller, 2) != NULL) + Bs3TestFailedF("Limit bytes not touched (#6): cbIdtr=%u off=%u cbLimit=%u bFiller=%#x abBuf=%.*Rhxs\n", + cbIdtr, off, cbLimit, bFiller, off + cbBuf, abBuf); + if (Bs3MemCmp(&abBuf[off], pbExpected, 2) != 0) + Bs3TestFailedF("Mismatch (#6): expected %.2Rhxs, got %.2Rhxs\n", pbExpected, &abBuf[off]); + if (!ASMMemIsAllU8(&abBuf[off + 2], cbIdtr - 2, bFiller)) + Bs3TestFailedF("Base bytes touched on #GP (#6): cbIdtr=%u off=%u cbLimit=%u bFiller=%#x abBuf=%.*Rhxs\n", + cbIdtr, off, cbLimit, bFiller, off + cbBuf, abBuf); + } + else if (!ASMMemIsAllU8(abBuf, sizeof(abBuf), bFiller)) + Bs3TestFailedF("Bytes touched on #GP: cbIdtr=%u off=%u cbLimit=%u bFiller=%#x abBuf=%.*Rhxs\n", + cbIdtr, off, cbLimit, bFiller, off + cbBuf, abBuf); + } + + if (off > 0 && !ASMMemIsAllU8(abBuf, off, bFiller)) + Bs3TestFailedF("Leading bytes touched (#7): cbIdtr=%u off=%u cbLimit=%u bFiller=%#x abBuf=%.*Rhxs\n", + cbIdtr, off, cbLimit, bFiller, off + cbBuf, abBuf); + if (!ASMMemIsAllU8(&abBuf[off + cbIdtr], sizeof(abBuf) - off - cbIdtr, bFiller)) + Bs3TestFailedF("Trailing bytes touched (#7): cbIdtr=%u off=%u cbLimit=%u bFiller=%#x abBuf=%.*Rhxs\n", + cbIdtr, off, cbLimit, bFiller, off + cbBuf, abBuf); + + g_usBs3TestStep++; + } + } + + /* Expand down (weird). Inverted valid area compared to expand up, + so a limit of zero give us a valid range for 0001..0ffffh (instead of + a segment with one valid byte at 0000h). Whereas a limit of 0fffeh + means one valid byte at 0ffffh, and a limit of 0ffffh means none + (because in a normal expand up the 0ffffh means all 64KB are + accessible). */ + Bs3GdteTestPage00.Gen.u4Type = X86_SEL_TYPE_RW_DOWN_ACC; + for (off = 0; off < 8; off++) + { + CtxUdExpected.rbx.u = Ctx.rbx.u = off; + for (cbLimit = 0; cbLimit < cbIdtr*2; cbLimit++) + { + Bs3GdteTestPage00.Gen.u16LimitLow = cbLimit; + Bs3MemSet(abBuf, bFiller, sizeof(abBuf)); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + + if (off > cbLimit) + { + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (Bs3MemChr(&abBuf[off], bFiller, cbIdtr) != NULL) + Bs3TestFailedF("Not all bytes touched (#8): cbIdtr=%u off=%u cbLimit=%u bFiller=%#x abBuf=%.*Rhxs\n", + cbIdtr, off, cbLimit, bFiller, off + cbBuf, abBuf); + if (Bs3MemCmp(&abBuf[off], pbExpected, cbIdtr) != 0) + Bs3TestFailedF("Mismatch (#8): expected %.*Rhxs, got %.*Rhxs\n", cbIdtr, pbExpected, cbIdtr, &abBuf[off]); + if (f286 && abBuf[off + cbIdtr - 1] != 0xff) + Bs3TestFailedF("286: Top base byte isn't 0xff (#8): %#x\n", abBuf[off + cbIdtr - 1]); + } + else + { + if (pWorker->fSs) + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &Ctx, 0, false /*f486ResumeFlagHint*/); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + if (!ASMMemIsAllU8(abBuf, sizeof(abBuf), bFiller)) + Bs3TestFailedF("Bytes touched on #GP: cbIdtr=%u off=%u cbLimit=%u bFiller=%#x abBuf=%.*Rhxs\n", + cbIdtr, off, cbLimit, bFiller, off + cbBuf, abBuf); + } + + if (off > 0 && !ASMMemIsAllU8(abBuf, off, bFiller)) + Bs3TestFailedF("Leading bytes touched (#9): cbIdtr=%u off=%u cbLimit=%u bFiller=%#x abBuf=%.*Rhxs\n", + cbIdtr, off, cbLimit, bFiller, off + cbBuf, abBuf); + if (!ASMMemIsAllU8(&abBuf[off + cbIdtr], sizeof(abBuf) - off - cbIdtr, bFiller)) + Bs3TestFailedF("Trailing bytes touched (#9): cbIdtr=%u off=%u cbLimit=%u bFiller=%#x abBuf=%.*Rhxs\n", + cbIdtr, off, cbLimit, bFiller, off + cbBuf, abBuf); + + g_usBs3TestStep++; + } + } + + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, pWorker->fSs ? &Ctx.ss : &Ctx.ds, abBuf); + CtxUdExpected.rbx.u = Ctx.rbx.u; + CtxUdExpected.ss = Ctx.ss; + CtxUdExpected.ds = Ctx.ds; + } + + /* + * Play with the paging. + */ + if ( BS3_MODE_IS_PAGED(bTestMode) + && (!pWorker->fSs || bRing == 3) /* SS.DPL == CPL, we'll get some tiled ring-3 selector here. */ + && (pbTest = (uint8_t BS3_FAR *)Bs3MemGuardedTestPageAlloc(BS3MEMKIND_TILED)) != NULL) + { + RTCCUINTXREG uFlatTest = Bs3SelPtrToFlat(pbTest); + + /* + * Slide the buffer towards the trailing guard page. We'll observe the + * first word being written entirely separately from the 2nd dword/qword. + */ + for (off = X86_PAGE_SIZE - cbIdtr - 4; off < X86_PAGE_SIZE + 4; off++) + { + Bs3MemSet(&pbTest[X86_PAGE_SIZE - cbIdtr * 2], bFiller, cbIdtr * 2); + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, pWorker->fSs ? &Ctx.ss : &Ctx.ds, &pbTest[off]); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (off + cbIdtr <= X86_PAGE_SIZE) + { + CtxUdExpected.rbx = Ctx.rbx; + CtxUdExpected.ss = Ctx.ss; + CtxUdExpected.ds = Ctx.ds; + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (Bs3MemCmp(&pbTest[off], pbExpected, cbIdtr) != 0) + Bs3TestFailedF("Mismatch (#9): expected %.*Rhxs, got %.*Rhxs\n", cbIdtr, pbExpected, cbIdtr, &pbTest[off]); + } + else + { + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx, X86_TRAP_PF_RW | (Ctx.bCpl == 3 ? X86_TRAP_PF_US : 0), + uFlatTest + RT_MAX(off, X86_PAGE_SIZE), 0 /*cbIpAdjust*/); + if ( off <= X86_PAGE_SIZE - 2 + && Bs3MemCmp(&pbTest[off], pbExpected, 2) != 0) + Bs3TestFailedF("Mismatch (#10): Expected limit %.2Rhxs, got %.2Rhxs; off=%#x\n", + pbExpected, &pbTest[off], off); + if ( off < X86_PAGE_SIZE - 2 + && !ASMMemIsAllU8(&pbTest[off + 2], X86_PAGE_SIZE - off - 2, bFiller)) + Bs3TestFailedF("Wrote partial base on #PF (#10): bFiller=%#x, got %.*Rhxs; off=%#x\n", + bFiller, X86_PAGE_SIZE - off - 2, &pbTest[off + 2], off); + if (off == X86_PAGE_SIZE - 1 && pbTest[off] != bFiller) + Bs3TestFailedF("Wrote partial limit on #PF (#10): Expected %02x, got %02x\n", bFiller, pbTest[off]); + } + g_usBs3TestStep++; + } + + /* + * Now, do it the other way around. It should look normal now since writing + * the limit will #PF first and nothing should be written. + */ + for (off = cbIdtr + 4; off >= -cbIdtr - 4; off--) + { + Bs3MemSet(pbTest, bFiller, 48); + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, pWorker->fSs ? &Ctx.ss : &Ctx.ds, &pbTest[off]); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (off >= 0) + { + CtxUdExpected.rbx = Ctx.rbx; + CtxUdExpected.ss = Ctx.ss; + CtxUdExpected.ds = Ctx.ds; + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (Bs3MemCmp(&pbTest[off], pbExpected, cbIdtr) != 0) + Bs3TestFailedF("Mismatch (#11): expected %.*Rhxs, got %.*Rhxs\n", cbIdtr, pbExpected, cbIdtr, &pbTest[off]); + } + else + { + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx, X86_TRAP_PF_RW | (Ctx.bCpl == 3 ? X86_TRAP_PF_US : 0), + uFlatTest + off, 0 /*cbIpAdjust*/); + if ( -off < cbIdtr + && !ASMMemIsAllU8(pbTest, cbIdtr + off, bFiller)) + Bs3TestFailedF("Wrote partial content on #PF (#12): bFiller=%#x, found %.*Rhxs; off=%d\n", + bFiller, cbIdtr + off, pbTest, off); + } + if (!ASMMemIsAllU8(&pbTest[RT_MAX(cbIdtr + off, 0)], 16, bFiller)) + Bs3TestFailedF("Wrote beyond expected area (#13): bFiller=%#x, found %.16Rhxs; off=%d\n", + bFiller, &pbTest[RT_MAX(cbIdtr + off, 0)], off); + g_usBs3TestStep++; + } + + /* + * Combine paging and segment limit and check ordering. + * This is kind of interesting here since it the instruction seems to + * be doing two separate writes. + */ + if ( !BS3_MODE_IS_RM_OR_V86(bTestMode) + && !BS3_MODE_IS_64BIT_CODE(bTestMode)) + { + uint16_t cbLimit; + + Bs3GdteTestPage00 = Bs3Gdte_DATA16; + Bs3GdteTestPage00.Gen.u2Dpl = bRing; + Bs3GdteTestPage00.Gen.u16BaseLow = (uint16_t)uFlatTest; + Bs3GdteTestPage00.Gen.u8BaseHigh1 = (uint8_t)(uFlatTest >> 16); + Bs3GdteTestPage00.Gen.u8BaseHigh2 = (uint8_t)(uFlatTest >> 24); + + if (pWorker->fSs) + CtxUdExpected.ss = Ctx.ss = BS3_SEL_TEST_PAGE_00 | bRing; + else + CtxUdExpected.ds = Ctx.ds = BS3_SEL_TEST_PAGE_00 | bRing; + + /* Expand up (normal), approaching tail guard page. */ + for (off = X86_PAGE_SIZE - cbIdtr - 4; off < X86_PAGE_SIZE + 4; off++) + { + CtxUdExpected.rbx.u = Ctx.rbx.u = off; + for (cbLimit = X86_PAGE_SIZE - cbIdtr*2; cbLimit < X86_PAGE_SIZE + cbIdtr*2; cbLimit++) + { + Bs3GdteTestPage00.Gen.u16LimitLow = cbLimit; + Bs3MemSet(&pbTest[X86_PAGE_SIZE - cbIdtr * 2], bFiller, cbIdtr * 2); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (off + cbIdtr <= cbLimit + 1) + { + /* No #GP, but maybe #PF. */ + if (off + cbIdtr <= X86_PAGE_SIZE) + { + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (Bs3MemCmp(&pbTest[off], pbExpected, cbIdtr) != 0) + Bs3TestFailedF("Mismatch (#14): expected %.*Rhxs, got %.*Rhxs\n", + cbIdtr, pbExpected, cbIdtr, &pbTest[off]); + } + else + { + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx, X86_TRAP_PF_RW | (Ctx.bCpl == 3 ? X86_TRAP_PF_US : 0), + uFlatTest + RT_MAX(off, X86_PAGE_SIZE), 0 /*cbIpAdjust*/); + if ( off <= X86_PAGE_SIZE - 2 + && Bs3MemCmp(&pbTest[off], pbExpected, 2) != 0) + Bs3TestFailedF("Mismatch (#15): Expected limit %.2Rhxs, got %.2Rhxs; off=%#x\n", + pbExpected, &pbTest[off], off); + cb = X86_PAGE_SIZE - off - 2; + if ( off < X86_PAGE_SIZE - 2 + && !ASMMemIsAllU8(&pbTest[off + 2], cb, bFiller)) + Bs3TestFailedF("Wrote partial base on #PF (#15): bFiller=%#x, got %.*Rhxs; off=%#x\n", + bFiller, cb, &pbTest[off + 2], off); + if (off == X86_PAGE_SIZE - 1 && pbTest[off] != bFiller) + Bs3TestFailedF("Wrote partial limit on #PF (#15): Expected %02x, got %02x\n", bFiller, pbTest[off]); + } + } + else if (off + 2 <= cbLimit + 1) + { + /* [ig]tr.limit writing does not cause #GP, but may cause #PG, if not writing the base causes #GP. */ + if (off <= X86_PAGE_SIZE - 2) + { + if (pWorker->fSs) + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &Ctx, 0, false /*f486ResumeFlagHint*/); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + if (Bs3MemCmp(&pbTest[off], pbExpected, 2) != 0) + Bs3TestFailedF("Mismatch (#16): Expected limit %.2Rhxs, got %.2Rhxs; off=%#x\n", + pbExpected, &pbTest[off], off); + cb = X86_PAGE_SIZE - off - 2; + if ( off < X86_PAGE_SIZE - 2 + && !ASMMemIsAllU8(&pbTest[off + 2], cb, bFiller)) + Bs3TestFailedF("Wrote partial base with limit (#16): bFiller=%#x, got %.*Rhxs; off=%#x\n", + bFiller, cb, &pbTest[off + 2], off); + } + else + { + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx, X86_TRAP_PF_RW | (Ctx.bCpl == 3 ? X86_TRAP_PF_US : 0), + uFlatTest + RT_MAX(off, X86_PAGE_SIZE), 0 /*cbIpAdjust*/); + if ( off < X86_PAGE_SIZE + && !ASMMemIsAllU8(&pbTest[off], X86_PAGE_SIZE - off, bFiller)) + Bs3TestFailedF("Mismatch (#16): Partial limit write on #PF: bFiller=%#x, got %.*Rhxs\n", + bFiller, X86_PAGE_SIZE - off, &pbTest[off]); + } + } + else + { + /* #GP/#SS on limit. */ + if (pWorker->fSs) + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &Ctx, 0, false /*f486ResumeFlagHint*/); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + if ( off < X86_PAGE_SIZE + && !ASMMemIsAllU8(&pbTest[off], X86_PAGE_SIZE - off, bFiller)) + Bs3TestFailedF("Mismatch (#17): Partial write on #GP: bFiller=%#x, got %.*Rhxs\n", + bFiller, X86_PAGE_SIZE - off, &pbTest[off]); + } + + cb = RT_MIN(cbIdtr * 2, off - (X86_PAGE_SIZE - cbIdtr*2)); + if (!ASMMemIsAllU8(&pbTest[X86_PAGE_SIZE - cbIdtr * 2], cb, bFiller)) + Bs3TestFailedF("Leading bytes touched (#18): cbIdtr=%u off=%u cbLimit=%u bFiller=%#x pbTest=%.*Rhxs\n", + cbIdtr, off, cbLimit, bFiller, cb, pbTest[X86_PAGE_SIZE - cbIdtr * 2]); + + g_usBs3TestStep++; + + /* Set DS to 0 and check that we get #GP(0). */ + if (!pWorker->fSs) + { + Ctx.ds = 0; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + Ctx.ds = BS3_SEL_TEST_PAGE_00 | bRing; + g_usBs3TestStep++; + } + } + } + + /* Expand down. */ + pbTest -= X86_PAGE_SIZE; /* Note! we're backing up a page to simplify things */ + uFlatTest -= X86_PAGE_SIZE; + + Bs3GdteTestPage00.Gen.u4Type = X86_SEL_TYPE_RW_DOWN_ACC; + Bs3GdteTestPage00.Gen.u16BaseLow = (uint16_t)uFlatTest; + Bs3GdteTestPage00.Gen.u8BaseHigh1 = (uint8_t)(uFlatTest >> 16); + Bs3GdteTestPage00.Gen.u8BaseHigh2 = (uint8_t)(uFlatTest >> 24); + + for (off = X86_PAGE_SIZE - cbIdtr - 4; off < X86_PAGE_SIZE + 4; off++) + { + CtxUdExpected.rbx.u = Ctx.rbx.u = off; + for (cbLimit = X86_PAGE_SIZE - cbIdtr*2; cbLimit < X86_PAGE_SIZE + cbIdtr*2; cbLimit++) + { + Bs3GdteTestPage00.Gen.u16LimitLow = cbLimit; + Bs3MemSet(&pbTest[X86_PAGE_SIZE], bFiller, cbIdtr * 2); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (cbLimit < off && off >= X86_PAGE_SIZE) + { + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (Bs3MemCmp(&pbTest[off], pbExpected, cbIdtr) != 0) + Bs3TestFailedF("Mismatch (#19): expected %.*Rhxs, got %.*Rhxs\n", + cbIdtr, pbExpected, cbIdtr, &pbTest[off]); + cb = X86_PAGE_SIZE + cbIdtr*2 - off; + if (!ASMMemIsAllU8(&pbTest[off + cbIdtr], cb, bFiller)) + Bs3TestFailedF("Trailing bytes touched (#20): cbIdtr=%u off=%u cbLimit=%u bFiller=%#x pbTest=%.*Rhxs\n", + cbIdtr, off, cbLimit, bFiller, cb, pbTest[off + cbIdtr]); + } + else + { + if (cbLimit < off && off < X86_PAGE_SIZE) + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx, X86_TRAP_PF_RW | (Ctx.bCpl == 3 ? X86_TRAP_PF_US : 0), + uFlatTest + off, 0 /*cbIpAdjust*/); + else if (pWorker->fSs) + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &Ctx, 0, false /*f486ResumeFlagHint*/); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + cb = cbIdtr*2; + if (!ASMMemIsAllU8(&pbTest[X86_PAGE_SIZE], cb, bFiller)) + Bs3TestFailedF("Trailing bytes touched (#20): cbIdtr=%u off=%u cbLimit=%u bFiller=%#x pbTest=%.*Rhxs\n", + cbIdtr, off, cbLimit, bFiller, cb, pbTest[X86_PAGE_SIZE]); + } + g_usBs3TestStep++; + } + } + + pbTest += X86_PAGE_SIZE; + uFlatTest += X86_PAGE_SIZE; + } + + Bs3MemGuardedTestPageFree(pbTest); + } + + /* + * Check non-canonical 64-bit space. + */ + if ( BS3_MODE_IS_64BIT_CODE(bTestMode) + && (pbTest = (uint8_t BS3_FAR *)Bs3PagingSetupCanonicalTraps()) != NULL) + { + /* Make our references relative to the gap. */ + pbTest += g_cbBs3PagingOneCanonicalTrap; + + /* Hit it from below. */ + for (off = -cbIdtr - 8; off < cbIdtr + 8; off++) + { + Ctx.rbx.u = CtxUdExpected.rbx.u = UINT64_C(0x0000800000000000) + off; + Bs3MemSet(&pbTest[-64], bFiller, 64*2); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (off + cbIdtr <= 0) + { + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (Bs3MemCmp(&pbTest[off], pbExpected, cbIdtr) != 0) + Bs3TestFailedF("Mismatch (#21): expected %.*Rhxs, got %.*Rhxs\n", cbIdtr, pbExpected, cbIdtr, &pbTest[off]); + } + else + { + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + if (off <= -2 && Bs3MemCmp(&pbTest[off], pbExpected, 2) != 0) + Bs3TestFailedF("Mismatch (#21): expected limit %.2Rhxs, got %.2Rhxs\n", pbExpected, &pbTest[off]); + off2 = off <= -2 ? 2 : 0; + cb = cbIdtr - off2; + if (!ASMMemIsAllU8(&pbTest[off + off2], cb, bFiller)) + Bs3TestFailedF("Mismatch (#21): touched base %.*Rhxs, got %.*Rhxs\n", + cb, &pbExpected[off], cb, &pbTest[off + off2]); + } + if (!ASMMemIsAllU8(&pbTest[off - 16], 16, bFiller)) + Bs3TestFailedF("Leading bytes touched (#21): bFiller=%#x, got %.16Rhxs\n", bFiller, &pbTest[off]); + if (!ASMMemIsAllU8(&pbTest[off + cbIdtr], 16, bFiller)) + Bs3TestFailedF("Trailing bytes touched (#21): bFiller=%#x, got %.16Rhxs\n", bFiller, &pbTest[off + cbIdtr]); + } + + /* Hit it from above. */ + for (off = -cbIdtr - 8; off < cbIdtr + 8; off++) + { + Ctx.rbx.u = CtxUdExpected.rbx.u = UINT64_C(0xffff800000000000) + off; + Bs3MemSet(&pbTest[-64], bFiller, 64*2); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (off >= 0) + { + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (Bs3MemCmp(&pbTest[off], pbExpected, cbIdtr) != 0) + Bs3TestFailedF("Mismatch (#22): expected %.*Rhxs, got %.*Rhxs\n", cbIdtr, pbExpected, cbIdtr, &pbTest[off]); + } + else + { + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + if (!ASMMemIsAllU8(&pbTest[off], cbIdtr, bFiller)) + Bs3TestFailedF("Mismatch (#22): touched base %.*Rhxs, got %.*Rhxs\n", + cbIdtr, &pbExpected[off], cbIdtr, &pbTest[off]); + } + if (!ASMMemIsAllU8(&pbTest[off - 16], 16, bFiller)) + Bs3TestFailedF("Leading bytes touched (#22): bFiller=%#x, got %.16Rhxs\n", bFiller, &pbTest[off]); + if (!ASMMemIsAllU8(&pbTest[off + cbIdtr], 16, bFiller)) + Bs3TestFailedF("Trailing bytes touched (#22): bFiller=%#x, got %.16Rhxs\n", bFiller, &pbTest[off + cbIdtr]); + } + + } +} + + +static void bs3CpuBasic2_sidt_sgdt_Common(uint8_t bTestMode, BS3CB2SIDTSGDT const BS3_FAR *paWorkers, unsigned cWorkers, + uint8_t const *pbExpected) +{ + unsigned idx; + unsigned bRing; + unsigned iStep = 0; + + /* Note! We skip the SS checks for ring-0 since we badly mess up SS in the + test and don't want to bother with double faults. */ + for (bRing = 0; bRing <= 3; bRing++) + { + for (idx = 0; idx < cWorkers; idx++) + if ( (paWorkers[idx].bMode & (bTestMode & BS3_MODE_CODE_MASK)) + && (!paWorkers[idx].fSs || bRing != 0 /** @todo || BS3_MODE_IS_64BIT_SYS(bTestMode)*/ )) + { + g_usBs3TestStep = iStep; + bs3CpuBasic2_sidt_sgdt_One(&paWorkers[idx], bTestMode, bRing, pbExpected); + iStep += 1000; + } + if (BS3_MODE_IS_RM_OR_V86(bTestMode)) + break; + } +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_FAR_NM(bs3CpuBasic2_sidt)(uint8_t bMode) +{ + union + { + RTIDTR Idtr; + uint8_t ab[16]; + } Expected; + + //if (bMode != BS3_MODE_LM64) return BS3TESTDOMODE_SKIPPED; + bs3CpuBasic2_SetGlobals(bMode); + + /* + * Pass to common worker which is only compiled once per mode. + */ + Bs3MemZero(&Expected, sizeof(Expected)); + ASMGetIDTR(&Expected.Idtr); + bs3CpuBasic2_sidt_sgdt_Common(bMode, g_aSidtWorkers, RT_ELEMENTS(g_aSidtWorkers), Expected.ab); + + /* + * Re-initialize the IDT. + */ + Bs3TrapReInit(); + return 0; +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_FAR_NM(bs3CpuBasic2_sgdt)(uint8_t bMode) +{ + uint64_t const uOrgAddr = Bs3Lgdt_Gdt.uAddr; + uint64_t uNew = 0; + union + { + RTGDTR Gdtr; + uint8_t ab[16]; + } Expected; + + //if (bMode != BS3_MODE_LM64) return BS3TESTDOMODE_SKIPPED; + bs3CpuBasic2_SetGlobals(bMode); + + /* + * If paged mode, try push the GDT way up. + */ + Bs3MemZero(&Expected, sizeof(Expected)); + ASMGetGDTR(&Expected.Gdtr); + if (BS3_MODE_IS_PAGED(bMode)) + { +/** @todo loading non-canonical base addresses. */ + int rc; + uNew = BS3_MODE_IS_64BIT_SYS(bMode) ? UINT64_C(0xffff80fedcb70000) : UINT64_C(0xc2d28000); + uNew |= uOrgAddr & X86_PAGE_OFFSET_MASK; + rc = Bs3PagingAlias(uNew, uOrgAddr, Bs3Lgdt_Gdt.cb, X86_PTE_P | X86_PTE_RW | X86_PTE_US | X86_PTE_D | X86_PTE_A); + if (RT_SUCCESS(rc)) + { + Bs3Lgdt_Gdt.uAddr = uNew; + Bs3UtilSetFullGdtr(Bs3Lgdt_Gdt.cb, uNew); + ASMGetGDTR(&Expected.Gdtr); + if (BS3_MODE_IS_64BIT_SYS(bMode) && ARCH_BITS != 64) + *(uint32_t *)&Expected.ab[6] = (uint32_t)(uNew >> 32); + } + } + + /* + * Pass to common worker which is only compiled once per mode. + */ + bs3CpuBasic2_sidt_sgdt_Common(bMode, g_aSgdtWorkers, RT_ELEMENTS(g_aSgdtWorkers), Expected.ab); + + /* + * Unalias the GDT. + */ + if (uNew != 0) + { + Bs3Lgdt_Gdt.uAddr = uOrgAddr; + Bs3UtilSetFullGdtr(Bs3Lgdt_Gdt.cb, uOrgAddr); + Bs3PagingUnalias(uNew, Bs3Lgdt_Gdt.cb); + } + + /* + * Re-initialize the IDT. + */ + Bs3TrapReInit(); + return 0; +} + + + +/* + * LIDT & LGDT + */ + +/** + * Executes one round of LIDT and LGDT tests using one assembly worker. + * + * This is written with driving everything from the 16-bit or 32-bit worker in + * mind, i.e. not assuming the test bitcount is the same as the current. + */ +static void bs3CpuBasic2_lidt_lgdt_One(BS3CB2SIDTSGDT const BS3_FAR *pWorker, uint8_t bTestMode, uint8_t bRing, + uint8_t const *pbRestore, size_t cbRestore, uint8_t const *pbExpected) +{ + static const struct + { + bool fGP; + uint16_t cbLimit; + uint64_t u64Base; + } s_aValues64[] = + { + { false, 0x0000, UINT64_C(0x0000000000000000) }, + { false, 0x0001, UINT64_C(0x0000000000000001) }, + { false, 0x0002, UINT64_C(0x0000000000000010) }, + { false, 0x0003, UINT64_C(0x0000000000000123) }, + { false, 0x0004, UINT64_C(0x0000000000001234) }, + { false, 0x0005, UINT64_C(0x0000000000012345) }, + { false, 0x0006, UINT64_C(0x0000000000123456) }, + { false, 0x0007, UINT64_C(0x0000000001234567) }, + { false, 0x0008, UINT64_C(0x0000000012345678) }, + { false, 0x0009, UINT64_C(0x0000000123456789) }, + { false, 0x000a, UINT64_C(0x000000123456789a) }, + { false, 0x000b, UINT64_C(0x00000123456789ab) }, + { false, 0x000c, UINT64_C(0x0000123456789abc) }, + { false, 0x001c, UINT64_C(0x00007ffffeefefef) }, + { false, 0xffff, UINT64_C(0x00007fffffffffff) }, + { true, 0xf3f1, UINT64_C(0x0000800000000000) }, + { true, 0x0000, UINT64_C(0x0000800000000000) }, + { true, 0x0000, UINT64_C(0x0000800000000333) }, + { true, 0x00f0, UINT64_C(0x0001000000000000) }, + { true, 0x0ff0, UINT64_C(0x0012000000000000) }, + { true, 0x0eff, UINT64_C(0x0123000000000000) }, + { true, 0xe0fe, UINT64_C(0x1234000000000000) }, + { true, 0x00ad, UINT64_C(0xffff300000000000) }, + { true, 0x0000, UINT64_C(0xffff7fffffffffff) }, + { true, 0x00f0, UINT64_C(0xffff7fffffffffff) }, + { false, 0x5678, UINT64_C(0xffff800000000000) }, + { false, 0x2969, UINT64_C(0xffffffffffeefefe) }, + { false, 0x1221, UINT64_C(0xffffffffffffffff) }, + { false, 0x1221, UINT64_C(0xffffffffffffffff) }, + }; + static const struct + { + uint16_t cbLimit; + uint32_t u32Base; + } s_aValues32[] = + { + { 0xdfdf, UINT32_C(0xefefefef) }, + { 0x0000, UINT32_C(0x00000000) }, + { 0x0001, UINT32_C(0x00000001) }, + { 0x0002, UINT32_C(0x00000012) }, + { 0x0003, UINT32_C(0x00000123) }, + { 0x0004, UINT32_C(0x00001234) }, + { 0x0005, UINT32_C(0x00012345) }, + { 0x0006, UINT32_C(0x00123456) }, + { 0x0007, UINT32_C(0x01234567) }, + { 0x0008, UINT32_C(0x12345678) }, + { 0x0009, UINT32_C(0x80204060) }, + { 0x000a, UINT32_C(0xddeeffaa) }, + { 0x000b, UINT32_C(0xfdecdbca) }, + { 0x000c, UINT32_C(0x6098456b) }, + { 0x000d, UINT32_C(0x98506099) }, + { 0x000e, UINT32_C(0x206950bc) }, + { 0x000f, UINT32_C(0x9740395d) }, + { 0x0334, UINT32_C(0x64a9455e) }, + { 0xb423, UINT32_C(0xd20b6eff) }, + { 0x4955, UINT32_C(0x85296d46) }, + { 0xffff, UINT32_C(0x07000039) }, + { 0xefe1, UINT32_C(0x0007fe00) }, + }; + + BS3TRAPFRAME TrapCtx; + BS3REGCTX Ctx; + BS3REGCTX CtxUdExpected; + BS3REGCTX TmpCtx; + uint8_t abBufLoad[40]; /* Test buffer w/ misalignment test space and some (cbIdtr) extra guard. */ + uint8_t abBufSave[32]; /* For saving the result after loading. */ + uint8_t abBufRestore[24]; /* For restoring sane value (same seg as abBufSave!). */ + uint8_t abExpectedFilled[32]; /* Same as pbExpected, except it's filled with bFiller2 instead of zeros. */ + uint8_t BS3_FAR *pbBufSave; /* Correctly aligned pointer into abBufSave. */ + uint8_t BS3_FAR *pbBufRestore; /* Correctly aligned pointer into abBufRestore. */ + uint8_t const cbIdtr = BS3_MODE_IS_64BIT_CODE(bTestMode) ? 2+8 : 2+4; + uint8_t const cbBaseLoaded = BS3_MODE_IS_64BIT_CODE(bTestMode) ? 8 + : BS3_MODE_IS_16BIT_CODE(bTestMode) == !(pWorker->fFlags & BS3CB2SIDTSGDT_F_OPSIZE) + ? 3 : 4; + bool const f286 = (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) == BS3CPU_80286; + uint8_t const bTop16BitBase = f286 ? 0xff : 0x00; + uint8_t bFiller1; /* For filling abBufLoad. */ + uint8_t bFiller2; /* For filling abBufSave and expectations. */ + int off; + uint8_t BS3_FAR *pbTest; + unsigned i; + + /* make sure they're allocated */ + Bs3MemZero(&Ctx, sizeof(Ctx)); + Bs3MemZero(&CtxUdExpected, sizeof(CtxUdExpected)); + Bs3MemZero(&TmpCtx, sizeof(TmpCtx)); + Bs3MemZero(&TrapCtx, sizeof(TrapCtx)); + Bs3MemZero(abBufSave, sizeof(abBufSave)); + Bs3MemZero(abBufLoad, sizeof(abBufLoad)); + Bs3MemZero(abBufRestore, sizeof(abBufRestore)); + + /* + * Create a context, giving this routine some more stack space. + * - Point the context at our LIDT [xBX] + SIDT [xDI] + LIDT [xSI] + UD2 combo. + * - Point DS/SS:xBX at abBufLoad. + * - Point ES:xDI at abBufSave. + * - Point ES:xSI at abBufRestore. + */ + Bs3RegCtxSaveEx(&Ctx, bTestMode, 256 /*cbExtraStack*/); + Bs3RegCtxSetRipCsFromLnkPtr(&Ctx, pWorker->fpfnWorker); + if (BS3_MODE_IS_16BIT_SYS(bTestMode)) + g_uBs3TrapEipHint = Ctx.rip.u32; + Ctx.rflags.u16 &= ~X86_EFL_IF; + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, pWorker->fSs ? &Ctx.ss : &Ctx.ds, abBufLoad); + + pbBufSave = abBufSave; + if ((BS3_FP_OFF(pbBufSave) + 2) & 7) + pbBufSave += 8 - ((BS3_FP_OFF(pbBufSave) + 2) & 7); + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rdi, &Ctx.es, pbBufSave); + + pbBufRestore = abBufRestore; + if ((BS3_FP_OFF(pbBufRestore) + 2) & 7) + pbBufRestore += 8 - ((BS3_FP_OFF(pbBufRestore) + 2) & 7); + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rsi, &Ctx.es, pbBufRestore); + Bs3MemCpy(pbBufRestore, pbRestore, cbRestore); + + if (!BS3_MODE_IS_RM_OR_V86(bTestMode)) + Bs3RegCtxConvertToRingX(&Ctx, bRing); + + /* For successful SIDT attempts, we'll stop at the UD2. */ + Bs3MemCpy(&CtxUdExpected, &Ctx, sizeof(Ctx)); + CtxUdExpected.rip.u += pWorker->cbInstr; + + /* + * Check that it works at all. + */ + Bs3MemZero(abBufLoad, sizeof(abBufLoad)); + Bs3MemCpy(abBufLoad, pbBufRestore, cbIdtr); + Bs3MemZero(abBufSave, sizeof(abBufSave)); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bRing != 0) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + else + { + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (Bs3MemCmp(pbBufSave, pbExpected, cbIdtr * 2) != 0) + Bs3TestFailedF("Mismatch (%s, #1): expected %.*Rhxs, got %.*Rhxs\n", + pWorker->pszDesc, cbIdtr*2, pbExpected, cbIdtr*2, pbBufSave); + } + g_usBs3TestStep++; + + /* Determine two filler bytes that doesn't appear in the previous result or our expectations. */ + bFiller1 = ~0x55; + while ( Bs3MemChr(pbBufSave, bFiller1, cbIdtr) != NULL + || Bs3MemChr(pbRestore, bFiller1, cbRestore) != NULL + || bFiller1 == 0xff) + bFiller1++; + bFiller2 = 0x33; + while ( Bs3MemChr(pbBufSave, bFiller2, cbIdtr) != NULL + || Bs3MemChr(pbRestore, bFiller2, cbRestore) != NULL + || bFiller2 == 0xff + || bFiller2 == bFiller1) + bFiller2++; + Bs3MemSet(abExpectedFilled, bFiller2, sizeof(abExpectedFilled)); + Bs3MemCpy(abExpectedFilled, pbExpected, cbIdtr); + + /* Again with a buffer filled with a byte not occuring in the previous result. */ + Bs3MemSet(abBufLoad, bFiller1, sizeof(abBufLoad)); + Bs3MemCpy(abBufLoad, pbBufRestore, cbIdtr); + Bs3MemSet(abBufSave, bFiller2, sizeof(abBufSave)); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bRing != 0) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + else + { + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (Bs3MemCmp(pbBufSave, abExpectedFilled, cbIdtr * 2) != 0) + Bs3TestFailedF("Mismatch (%s, #2): expected %.*Rhxs, got %.*Rhxs\n", + pWorker->pszDesc, cbIdtr*2, abExpectedFilled, cbIdtr*2, pbBufSave); + } + g_usBs3TestStep++; + + /* + * Try loading a bunch of different limit+base value to check what happens, + * especially what happens wrt the top part of the base in 16-bit mode. + */ + if (BS3_MODE_IS_64BIT_CODE(bTestMode)) + { + for (i = 0; i < RT_ELEMENTS(s_aValues64); i++) + { + Bs3MemSet(abBufLoad, bFiller1, sizeof(abBufLoad)); + Bs3MemCpy(&abBufLoad[0], &s_aValues64[i].cbLimit, 2); + Bs3MemCpy(&abBufLoad[2], &s_aValues64[i].u64Base, 8); + Bs3MemSet(abBufSave, bFiller2, sizeof(abBufSave)); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bRing != 0 || s_aValues64[i].fGP) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + else + { + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if ( Bs3MemCmp(&pbBufSave[0], &s_aValues64[i].cbLimit, 2) != 0 + || Bs3MemCmp(&pbBufSave[2], &s_aValues64[i].u64Base, 8) != 0 + || !ASMMemIsAllU8(&pbBufSave[10], cbIdtr, bFiller2)) + Bs3TestFailedF("Mismatch (%s, #2): expected %04RX16:%016RX64, fillers %#x %#x, got %.*Rhxs\n", + pWorker->pszDesc, s_aValues64[i].cbLimit, s_aValues64[i].u64Base, + bFiller1, bFiller2, cbIdtr*2, pbBufSave); + } + g_usBs3TestStep++; + } + } + else + { + for (i = 0; i < RT_ELEMENTS(s_aValues32); i++) + { + Bs3MemSet(abBufLoad, bFiller1, sizeof(abBufLoad)); + Bs3MemCpy(&abBufLoad[0], &s_aValues32[i].cbLimit, 2); + Bs3MemCpy(&abBufLoad[2], &s_aValues32[i].u32Base, cbBaseLoaded); + Bs3MemSet(abBufSave, bFiller2, sizeof(abBufSave)); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bRing != 0) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + else + { + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if ( Bs3MemCmp(&pbBufSave[0], &s_aValues32[i].cbLimit, 2) != 0 + || Bs3MemCmp(&pbBufSave[2], &s_aValues32[i].u32Base, cbBaseLoaded) != 0 + || ( cbBaseLoaded != 4 + && pbBufSave[2+3] != bTop16BitBase) + || !ASMMemIsAllU8(&pbBufSave[8], cbIdtr, bFiller2)) + Bs3TestFailedF("Mismatch (%s,#3): loaded %04RX16:%08RX32, fillers %#x %#x%s, got %.*Rhxs\n", + pWorker->pszDesc, s_aValues32[i].cbLimit, s_aValues32[i].u32Base, bFiller1, bFiller2, + f286 ? ", 286" : "", cbIdtr*2, pbBufSave); + } + g_usBs3TestStep++; + } + } + + /* + * Slide the buffer along 8 bytes to cover misalignment. + */ + for (off = 0; off < 8; off++) + { + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, pWorker->fSs ? &Ctx.ss : &Ctx.ds, &abBufLoad[off]); + CtxUdExpected.rbx.u = Ctx.rbx.u; + + Bs3MemSet(abBufLoad, bFiller1, sizeof(abBufLoad)); + Bs3MemCpy(&abBufLoad[off], pbBufRestore, cbIdtr); + Bs3MemSet(abBufSave, bFiller2, sizeof(abBufSave)); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bRing != 0) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + else + { + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (Bs3MemCmp(pbBufSave, abExpectedFilled, cbIdtr * 2) != 0) + Bs3TestFailedF("Mismatch (%s, #4): expected %.*Rhxs, got %.*Rhxs\n", + pWorker->pszDesc, cbIdtr*2, abExpectedFilled, cbIdtr*2, pbBufSave); + } + g_usBs3TestStep++; + } + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, pWorker->fSs ? &Ctx.ss : &Ctx.ds, abBufLoad); + CtxUdExpected.rbx.u = Ctx.rbx.u; + + /* + * Play with the selector limit if the target mode supports limit checking + * We use BS3_SEL_TEST_PAGE_00 for this + */ + if ( !BS3_MODE_IS_RM_OR_V86(bTestMode) + && !BS3_MODE_IS_64BIT_CODE(bTestMode)) + { + uint16_t cbLimit; + uint32_t uFlatBuf = Bs3SelPtrToFlat(abBufLoad); + Bs3GdteTestPage00 = Bs3Gdte_DATA16; + Bs3GdteTestPage00.Gen.u2Dpl = bRing; + Bs3GdteTestPage00.Gen.u16BaseLow = (uint16_t)uFlatBuf; + Bs3GdteTestPage00.Gen.u8BaseHigh1 = (uint8_t)(uFlatBuf >> 16); + Bs3GdteTestPage00.Gen.u8BaseHigh2 = (uint8_t)(uFlatBuf >> 24); + + if (pWorker->fSs) + CtxUdExpected.ss = Ctx.ss = BS3_SEL_TEST_PAGE_00 | bRing; + else + CtxUdExpected.ds = Ctx.ds = BS3_SEL_TEST_PAGE_00 | bRing; + + /* Expand up (normal). */ + for (off = 0; off < 8; off++) + { + CtxUdExpected.rbx.u = Ctx.rbx.u = off; + for (cbLimit = 0; cbLimit < cbIdtr*2; cbLimit++) + { + Bs3GdteTestPage00.Gen.u16LimitLow = cbLimit; + + Bs3MemSet(abBufLoad, bFiller1, sizeof(abBufLoad)); + Bs3MemCpy(&abBufLoad[off], pbBufRestore, cbIdtr); + Bs3MemSet(abBufSave, bFiller2, sizeof(abBufSave)); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bRing != 0) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + else if (off + cbIdtr <= cbLimit + 1) + { + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (Bs3MemCmp(pbBufSave, abExpectedFilled, cbIdtr * 2) != 0) + Bs3TestFailedF("Mismatch (%s, #5): expected %.*Rhxs, got %.*Rhxs\n", + pWorker->pszDesc, cbIdtr*2, abExpectedFilled, cbIdtr*2, pbBufSave); + } + else if (pWorker->fSs) + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &Ctx, 0, false /*f486ResumeFlagHint*/); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + g_usBs3TestStep++; + + /* Again with zero limit and messed up base (should trigger tripple fault if partially loaded). */ + abBufLoad[off] = abBufLoad[off + 1] = 0; + abBufLoad[off + 2] |= 1; + abBufLoad[off + cbIdtr - 2] ^= 0x5a; + abBufLoad[off + cbIdtr - 1] ^= 0xa5; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bRing != 0) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + else if (off + cbIdtr <= cbLimit + 1) + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + else if (pWorker->fSs) + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &Ctx, 0, false /*f486ResumeFlagHint*/); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + } + } + + /* Expand down (weird). Inverted valid area compared to expand up, + so a limit of zero give us a valid range for 0001..0ffffh (instead of + a segment with one valid byte at 0000h). Whereas a limit of 0fffeh + means one valid byte at 0ffffh, and a limit of 0ffffh means none + (because in a normal expand up the 0ffffh means all 64KB are + accessible). */ + Bs3GdteTestPage00.Gen.u4Type = X86_SEL_TYPE_RW_DOWN_ACC; + for (off = 0; off < 8; off++) + { + CtxUdExpected.rbx.u = Ctx.rbx.u = off; + for (cbLimit = 0; cbLimit < cbIdtr*2; cbLimit++) + { + Bs3GdteTestPage00.Gen.u16LimitLow = cbLimit; + + Bs3MemSet(abBufLoad, bFiller1, sizeof(abBufLoad)); + Bs3MemCpy(&abBufLoad[off], pbBufRestore, cbIdtr); + Bs3MemSet(abBufSave, bFiller2, sizeof(abBufSave)); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bRing != 0) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + else if (off > cbLimit) + { + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (Bs3MemCmp(pbBufSave, abExpectedFilled, cbIdtr * 2) != 0) + Bs3TestFailedF("Mismatch (%s, #6): expected %.*Rhxs, got %.*Rhxs\n", + pWorker->pszDesc, cbIdtr*2, abExpectedFilled, cbIdtr*2, pbBufSave); + } + else if (pWorker->fSs) + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &Ctx, 0, false /*f486ResumeFlagHint*/); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + g_usBs3TestStep++; + + /* Again with zero limit and messed up base (should trigger triple fault if partially loaded). */ + abBufLoad[off] = abBufLoad[off + 1] = 0; + abBufLoad[off + 2] |= 3; + abBufLoad[off + cbIdtr - 2] ^= 0x55; + abBufLoad[off + cbIdtr - 1] ^= 0xaa; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bRing != 0) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + else if (off > cbLimit) + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + else if (pWorker->fSs) + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &Ctx, 0, false /*f486ResumeFlagHint*/); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + } + } + + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, pWorker->fSs ? &Ctx.ss : &Ctx.ds, abBufLoad); + CtxUdExpected.rbx.u = Ctx.rbx.u; + CtxUdExpected.ss = Ctx.ss; + CtxUdExpected.ds = Ctx.ds; + } + + /* + * Play with the paging. + */ + if ( BS3_MODE_IS_PAGED(bTestMode) + && (!pWorker->fSs || bRing == 3) /* SS.DPL == CPL, we'll get some tiled ring-3 selector here. */ + && (pbTest = (uint8_t BS3_FAR *)Bs3MemGuardedTestPageAlloc(BS3MEMKIND_TILED)) != NULL) + { + RTCCUINTXREG uFlatTest = Bs3SelPtrToFlat(pbTest); + + /* + * Slide the load buffer towards the trailing guard page. + */ + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, pWorker->fSs ? &Ctx.ss : &Ctx.ds, &pbTest[X86_PAGE_SIZE]); + CtxUdExpected.ss = Ctx.ss; + CtxUdExpected.ds = Ctx.ds; + for (off = X86_PAGE_SIZE - cbIdtr - 4; off < X86_PAGE_SIZE + 4; off++) + { + Bs3MemSet(&pbTest[X86_PAGE_SIZE - cbIdtr * 2], bFiller1, cbIdtr*2); + if (off < X86_PAGE_SIZE) + Bs3MemCpy(&pbTest[off], pbBufRestore, RT_MIN(X86_PAGE_SIZE - off, cbIdtr)); + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, pWorker->fSs ? &Ctx.ss : &Ctx.ds, &pbTest[off]); + Bs3MemSet(abBufSave, bFiller2, sizeof(abBufSave)); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bRing != 0) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + else if (off + cbIdtr <= X86_PAGE_SIZE) + { + CtxUdExpected.rbx = Ctx.rbx; + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (Bs3MemCmp(pbBufSave, abExpectedFilled, cbIdtr*2) != 0) + Bs3TestFailedF("Mismatch (%s, #7): expected %.*Rhxs, got %.*Rhxs\n", + pWorker->pszDesc, cbIdtr*2, abExpectedFilled, cbIdtr*2, pbBufSave); + } + else + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx, 0, uFlatTest + RT_MAX(off, X86_PAGE_SIZE), 0 /*cbIpAdjust*/); + g_usBs3TestStep++; + + /* Again with zero limit and maybe messed up base as well (triple fault if buggy). + The 386DX-40 here triple faults (or something) with off == 0xffe, nothing else. */ + if ( off < X86_PAGE_SIZE && off + cbIdtr > X86_PAGE_SIZE + && ( off != X86_PAGE_SIZE - 2 + || (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) != BS3CPU_80386) + ) + { + pbTest[off] = 0; + if (off + 1 < X86_PAGE_SIZE) + pbTest[off + 1] = 0; + if (off + 2 < X86_PAGE_SIZE) + pbTest[off + 2] |= 7; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bRing != 0) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + else + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx, 0, uFlatTest + RT_MAX(off, X86_PAGE_SIZE), 0 /*cbIpAdjust*/); + g_usBs3TestStep++; + } + } + + /* + * Now, do it the other way around. It should look normal now since writing + * the limit will #PF first and nothing should be written. + */ + for (off = cbIdtr + 4; off >= -cbIdtr - 4; off--) + { + Bs3MemSet(pbTest, bFiller1, 48); + if (off >= 0) + Bs3MemCpy(&pbTest[off], pbBufRestore, cbIdtr); + else if (off + cbIdtr > 0) + Bs3MemCpy(pbTest, &pbBufRestore[-off], cbIdtr + off); + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, pWorker->fSs ? &Ctx.ss : &Ctx.ds, &pbTest[off]); + Bs3MemSet(abBufSave, bFiller2, sizeof(abBufSave)); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bRing != 0) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + else if (off >= 0) + { + CtxUdExpected.rbx = Ctx.rbx; + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (Bs3MemCmp(pbBufSave, abExpectedFilled, cbIdtr*2) != 0) + Bs3TestFailedF("Mismatch (%s, #8): expected %.*Rhxs, got %.*Rhxs\n", + pWorker->pszDesc, cbIdtr*2, abExpectedFilled, cbIdtr*2, pbBufSave); + } + else + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx, 0, uFlatTest + off, 0 /*cbIpAdjust*/); + g_usBs3TestStep++; + + /* Again with messed up base as well (triple fault if buggy). */ + if (off < 0 && off > -cbIdtr) + { + if (off + 2 >= 0) + pbTest[off + 2] |= 15; + pbTest[off + cbIdtr - 1] ^= 0xaa; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bRing != 0) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + else + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx, 0, uFlatTest + off, 0 /*cbIpAdjust*/); + g_usBs3TestStep++; + } + } + + /* + * Combine paging and segment limit and check ordering. + * This is kind of interesting here since it the instruction seems to + * actually be doing two separate read, just like it's S[IG]DT counterpart. + * + * Note! My 486DX4 does a DWORD limit read when the operand size is 32-bit, + * that's what f486Weirdness deals with. + */ + if ( !BS3_MODE_IS_RM_OR_V86(bTestMode) + && !BS3_MODE_IS_64BIT_CODE(bTestMode)) + { + bool const f486Weirdness = (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) == BS3CPU_80486 + && BS3_MODE_IS_32BIT_CODE(bTestMode) == !(pWorker->fFlags & BS3CB2SIDTSGDT_F_OPSIZE); + uint16_t cbLimit; + + Bs3GdteTestPage00 = Bs3Gdte_DATA16; + Bs3GdteTestPage00.Gen.u2Dpl = bRing; + Bs3GdteTestPage00.Gen.u16BaseLow = (uint16_t)uFlatTest; + Bs3GdteTestPage00.Gen.u8BaseHigh1 = (uint8_t)(uFlatTest >> 16); + Bs3GdteTestPage00.Gen.u8BaseHigh2 = (uint8_t)(uFlatTest >> 24); + + if (pWorker->fSs) + CtxUdExpected.ss = Ctx.ss = BS3_SEL_TEST_PAGE_00 | bRing; + else + CtxUdExpected.ds = Ctx.ds = BS3_SEL_TEST_PAGE_00 | bRing; + + /* Expand up (normal), approaching tail guard page. */ + for (off = X86_PAGE_SIZE - cbIdtr - 4; off < X86_PAGE_SIZE + 4; off++) + { + CtxUdExpected.rbx.u = Ctx.rbx.u = off; + for (cbLimit = X86_PAGE_SIZE - cbIdtr*2; cbLimit < X86_PAGE_SIZE + cbIdtr*2; cbLimit++) + { + Bs3GdteTestPage00.Gen.u16LimitLow = cbLimit; + Bs3MemSet(&pbTest[X86_PAGE_SIZE - cbIdtr * 2], bFiller1, cbIdtr * 2); + if (off < X86_PAGE_SIZE) + Bs3MemCpy(&pbTest[off], pbBufRestore, RT_MIN(cbIdtr, X86_PAGE_SIZE - off)); + Bs3MemSet(abBufSave, bFiller2, sizeof(abBufSave)); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bRing != 0) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + else if (off + cbIdtr <= cbLimit + 1) + { + /* No #GP, but maybe #PF. */ + if (off + cbIdtr <= X86_PAGE_SIZE) + { + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (Bs3MemCmp(pbBufSave, abExpectedFilled, cbIdtr * 2) != 0) + Bs3TestFailedF("Mismatch (%s, #9): expected %.*Rhxs, got %.*Rhxs\n", + pWorker->pszDesc, cbIdtr*2, abExpectedFilled, cbIdtr*2, pbBufSave); + } + else + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx, 0, uFlatTest + RT_MAX(off, X86_PAGE_SIZE), 0 /*cbIpAdjust*/); + } + /* No #GP/#SS on limit, but instead #PF? */ + else if ( !f486Weirdness + ? off < cbLimit && off >= 0xfff + : off + 2 < cbLimit && off >= 0xffd) + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx, 0, uFlatTest + RT_MAX(off, X86_PAGE_SIZE), 0 /*cbIpAdjust*/); + /* #GP/#SS on limit or base. */ + else if (pWorker->fSs) + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &Ctx, 0, false /*f486ResumeFlagHint*/); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + + g_usBs3TestStep++; + + /* Set DS to 0 and check that we get #GP(0). */ + if (!pWorker->fSs) + { + Ctx.ds = 0; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + Ctx.ds = BS3_SEL_TEST_PAGE_00 | bRing; + g_usBs3TestStep++; + } + } + } + + /* Expand down. */ + pbTest -= X86_PAGE_SIZE; /* Note! we're backing up a page to simplify things */ + uFlatTest -= X86_PAGE_SIZE; + + Bs3GdteTestPage00.Gen.u4Type = X86_SEL_TYPE_RW_DOWN_ACC; + Bs3GdteTestPage00.Gen.u16BaseLow = (uint16_t)uFlatTest; + Bs3GdteTestPage00.Gen.u8BaseHigh1 = (uint8_t)(uFlatTest >> 16); + Bs3GdteTestPage00.Gen.u8BaseHigh2 = (uint8_t)(uFlatTest >> 24); + + for (off = X86_PAGE_SIZE - cbIdtr - 4; off < X86_PAGE_SIZE + 4; off++) + { + CtxUdExpected.rbx.u = Ctx.rbx.u = off; + for (cbLimit = X86_PAGE_SIZE - cbIdtr*2; cbLimit < X86_PAGE_SIZE + cbIdtr*2; cbLimit++) + { + Bs3GdteTestPage00.Gen.u16LimitLow = cbLimit; + Bs3MemSet(&pbTest[X86_PAGE_SIZE], bFiller1, cbIdtr * 2); + if (off >= X86_PAGE_SIZE) + Bs3MemCpy(&pbTest[off], pbBufRestore, cbIdtr); + else if (off > X86_PAGE_SIZE - cbIdtr) + Bs3MemCpy(&pbTest[X86_PAGE_SIZE], &pbBufRestore[X86_PAGE_SIZE - off], cbIdtr - (X86_PAGE_SIZE - off)); + Bs3MemSet(abBufSave, bFiller2, sizeof(abBufSave)); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bRing != 0) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + else if (cbLimit < off && off >= X86_PAGE_SIZE) + { + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (Bs3MemCmp(pbBufSave, abExpectedFilled, cbIdtr * 2) != 0) + Bs3TestFailedF("Mismatch (%s, #10): expected %.*Rhxs, got %.*Rhxs\n", + pWorker->pszDesc, cbIdtr*2, abExpectedFilled, cbIdtr*2, pbBufSave); + } + else if (cbLimit < off && off < X86_PAGE_SIZE) + bs3CpuBasic2_ComparePfCtx(&TrapCtx, &Ctx, 0, uFlatTest + off, 0 /*cbIpAdjust*/); + else if (pWorker->fSs) + bs3CpuBasic2_CompareSsCtx(&TrapCtx, &Ctx, 0, false /*f486ResumeFlagHint*/); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + g_usBs3TestStep++; + } + } + + pbTest += X86_PAGE_SIZE; + uFlatTest += X86_PAGE_SIZE; + } + + Bs3MemGuardedTestPageFree(pbTest); + } + + /* + * Check non-canonical 64-bit space. + */ + if ( BS3_MODE_IS_64BIT_CODE(bTestMode) + && (pbTest = (uint8_t BS3_FAR *)Bs3PagingSetupCanonicalTraps()) != NULL) + { + /* Make our references relative to the gap. */ + pbTest += g_cbBs3PagingOneCanonicalTrap; + + /* Hit it from below. */ + for (off = -cbIdtr - 8; off < cbIdtr + 8; off++) + { + Ctx.rbx.u = CtxUdExpected.rbx.u = UINT64_C(0x0000800000000000) + off; + Bs3MemSet(&pbTest[-64], bFiller1, 64*2); + Bs3MemCpy(&pbTest[off], pbBufRestore, cbIdtr); + Bs3MemSet(abBufSave, bFiller2, sizeof(abBufSave)); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (off + cbIdtr > 0 || bRing != 0) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + else + { + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (Bs3MemCmp(pbBufSave, abExpectedFilled, cbIdtr * 2) != 0) + Bs3TestFailedF("Mismatch (%s, #11): expected %.*Rhxs, got %.*Rhxs\n", + pWorker->pszDesc, cbIdtr*2, abExpectedFilled, cbIdtr*2, pbBufSave); + } + } + + /* Hit it from above. */ + for (off = -cbIdtr - 8; off < cbIdtr + 8; off++) + { + Ctx.rbx.u = CtxUdExpected.rbx.u = UINT64_C(0xffff800000000000) + off; + Bs3MemSet(&pbTest[-64], bFiller1, 64*2); + Bs3MemCpy(&pbTest[off], pbBufRestore, cbIdtr); + Bs3MemSet(abBufSave, bFiller2, sizeof(abBufSave)); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (off < 0 || bRing != 0) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + else + { + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + if (Bs3MemCmp(pbBufSave, abExpectedFilled, cbIdtr * 2) != 0) + Bs3TestFailedF("Mismatch (%s, #19): expected %.*Rhxs, got %.*Rhxs\n", + pWorker->pszDesc, cbIdtr*2, abExpectedFilled, cbIdtr*2, pbBufSave); + } + } + + } +} + + +static void bs3CpuBasic2_lidt_lgdt_Common(uint8_t bTestMode, BS3CB2SIDTSGDT const BS3_FAR *paWorkers, unsigned cWorkers, + void const *pvRestore, size_t cbRestore, uint8_t const *pbExpected) +{ + unsigned idx; + unsigned bRing; + unsigned iStep = 0; + + /* Note! We skip the SS checks for ring-0 since we badly mess up SS in the + test and don't want to bother with double faults. */ + for (bRing = BS3_MODE_IS_V86(bTestMode) ? 3 : 0; bRing <= 3; bRing++) + { + for (idx = 0; idx < cWorkers; idx++) + if ( (paWorkers[idx].bMode & (bTestMode & BS3_MODE_CODE_MASK)) + && (!paWorkers[idx].fSs || bRing != 0 /** @todo || BS3_MODE_IS_64BIT_SYS(bTestMode)*/ ) + && ( !(paWorkers[idx].fFlags & BS3CB2SIDTSGDT_F_386PLUS) + || ( bTestMode > BS3_MODE_PE16 + || ( bTestMode == BS3_MODE_PE16 + && (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80386)) ) ) + { + //Bs3TestPrintf("idx=%-2d fpfnWorker=%p fSs=%d cbInstr=%d\n", + // idx, paWorkers[idx].fpfnWorker, paWorkers[idx].fSs, paWorkers[idx].cbInstr); + g_usBs3TestStep = iStep; + bs3CpuBasic2_lidt_lgdt_One(&paWorkers[idx], bTestMode, bRing, pvRestore, cbRestore, pbExpected); + iStep += 1000; + } + if (BS3_MODE_IS_RM_SYS(bTestMode)) + break; + } +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_FAR_NM(bs3CpuBasic2_lidt)(uint8_t bMode) +{ + union + { + RTIDTR Idtr; + uint8_t ab[32]; /* At least cbIdtr*2! */ + } Expected; + + //if (bMode != BS3_MODE_LM64) return 0; + bs3CpuBasic2_SetGlobals(bMode); + + /* + * Pass to common worker which is only compiled once per mode. + */ + Bs3MemZero(&Expected, sizeof(Expected)); + ASMGetIDTR(&Expected.Idtr); + + if (BS3_MODE_IS_RM_SYS(bMode)) + bs3CpuBasic2_lidt_lgdt_Common(bMode, g_aLidtWorkers, RT_ELEMENTS(g_aLidtWorkers), + &Bs3Lidt_Ivt, sizeof(Bs3Lidt_Ivt), Expected.ab); + else if (BS3_MODE_IS_16BIT_SYS(bMode)) + bs3CpuBasic2_lidt_lgdt_Common(bMode, g_aLidtWorkers, RT_ELEMENTS(g_aLidtWorkers), + &Bs3Lidt_Idt16, sizeof(Bs3Lidt_Idt16), Expected.ab); + else if (BS3_MODE_IS_32BIT_SYS(bMode)) + bs3CpuBasic2_lidt_lgdt_Common(bMode, g_aLidtWorkers, RT_ELEMENTS(g_aLidtWorkers), + &Bs3Lidt_Idt32, sizeof(Bs3Lidt_Idt32), Expected.ab); + else + bs3CpuBasic2_lidt_lgdt_Common(bMode, g_aLidtWorkers, RT_ELEMENTS(g_aLidtWorkers), + &Bs3Lidt_Idt64, sizeof(Bs3Lidt_Idt64), Expected.ab); + + /* + * Re-initialize the IDT. + */ + Bs3TrapReInit(); + return 0; +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_FAR_NM(bs3CpuBasic2_lgdt)(uint8_t bMode) +{ + union + { + RTGDTR Gdtr; + uint8_t ab[32]; /* At least cbIdtr*2! */ + } Expected; + + //if (!BS3_MODE_IS_64BIT_SYS(bMode)) return 0; + bs3CpuBasic2_SetGlobals(bMode); + + /* + * Pass to common worker which is only compiled once per mode. + */ + if (BS3_MODE_IS_RM_SYS(bMode)) + ASMSetGDTR((PRTGDTR)&Bs3LgdtDef_Gdt); + Bs3MemZero(&Expected, sizeof(Expected)); + ASMGetGDTR(&Expected.Gdtr); + + bs3CpuBasic2_lidt_lgdt_Common(bMode, g_aLgdtWorkers, RT_ELEMENTS(g_aLgdtWorkers), + &Bs3LgdtDef_Gdt, sizeof(Bs3LgdtDef_Gdt), Expected.ab); + + /* + * Re-initialize the IDT. + */ + Bs3TrapReInit(); + return 0; +} + +typedef union IRETBUF +{ + uint64_t au64[6]; /* max req is 5 */ + uint32_t au32[12]; /* max req is 9 */ + uint16_t au16[24]; /* max req is 5 */ + uint8_t ab[48]; +} IRETBUF; +typedef IRETBUF BS3_FAR *PIRETBUF; + + +static void iretbuf_SetupFrame(PIRETBUF pIretBuf, unsigned const cbPop, + uint16_t uCS, uint64_t uPC, uint32_t fEfl, uint16_t uSS, uint64_t uSP) +{ + if (cbPop == 2) + { + pIretBuf->au16[0] = (uint16_t)uPC; + pIretBuf->au16[1] = uCS; + pIretBuf->au16[2] = (uint16_t)fEfl; + pIretBuf->au16[3] = (uint16_t)uSP; + pIretBuf->au16[4] = uSS; + } + else if (cbPop != 8) + { + pIretBuf->au32[0] = (uint32_t)uPC; + pIretBuf->au16[1*2] = uCS; + pIretBuf->au32[2] = (uint32_t)fEfl; + pIretBuf->au32[3] = (uint32_t)uSP; + pIretBuf->au16[4*2] = uSS; + } + else + { + pIretBuf->au64[0] = uPC; + pIretBuf->au16[1*4] = uCS; + pIretBuf->au64[2] = fEfl; + pIretBuf->au64[3] = uSP; + pIretBuf->au16[4*4] = uSS; + } +} + + +static void bs3CpuBasic2_iret_Worker(uint8_t bTestMode, FPFNBS3FAR pfnIret, unsigned const cbPop, + PIRETBUF pIretBuf, const char BS3_FAR *pszDesc) +{ + BS3TRAPFRAME TrapCtx; + BS3REGCTX Ctx; + BS3REGCTX CtxUdExpected; + BS3REGCTX TmpCtx; + BS3REGCTX TmpCtxExpected; + uint8_t abLowUd[8]; + uint8_t abLowIret[8]; + FPFNBS3FAR pfnUdLow = (FPFNBS3FAR)abLowUd; + FPFNBS3FAR pfnIretLow = (FPFNBS3FAR)abLowIret; + unsigned const cbSameCplFrame = BS3_MODE_IS_64BIT_CODE(bTestMode) ? 5*cbPop : 3*cbPop; + bool const fUseLowCode = cbPop == 2 && !BS3_MODE_IS_16BIT_CODE(bTestMode); + int iRingDst; + int iRingSrc; + uint16_t uDplSs; + uint16_t uRplCs; + uint16_t uRplSs; +// int i; + uint8_t BS3_FAR *pbTest; + + NOREF(abLowUd); +#define IRETBUF_SET_SEL(a_idx, a_uValue) \ + do { *(uint16_t)&pIretBuf->ab[a_idx * cbPop] = (a_uValue); } while (0) +#define IRETBUF_SET_REG(a_idx, a_uValue) \ + do { uint8_t const BS3_FAR *pbTmp = &pIretBuf->ab[a_idx * cbPop]; \ + if (cbPop == 2) *(uint16_t)pbTmp = (uint16_t)(a_uValue); \ + else if (cbPop != 8) *(uint32_t)pbTmp = (uint32_t)(a_uValue); \ + else *(uint64_t)pbTmp = (a_uValue); \ + } while (0) + + /* make sure they're allocated */ + Bs3MemZero(&Ctx, sizeof(Ctx)); + Bs3MemZero(&CtxUdExpected, sizeof(CtxUdExpected)); + Bs3MemZero(&TmpCtx, sizeof(TmpCtx)); + Bs3MemZero(&TmpCtxExpected, sizeof(TmpCtxExpected)); + Bs3MemZero(&TrapCtx, sizeof(TrapCtx)); + + /* + * When dealing with 16-bit irets in 32-bit or 64-bit mode, we must have + * copies of both iret and ud in the first 64KB of memory. The stack is + * below 64KB, so we'll just copy the instructions onto the stack. + */ + Bs3MemCpy(abLowUd, bs3CpuBasic2_ud2, 4); + Bs3MemCpy(abLowIret, pfnIret, 4); + + /* + * Create a context (stack is irrelevant, we'll mainly be using pIretBuf). + * - Point the context at our iret instruction. + * - Point SS:xSP at pIretBuf. + */ + Bs3RegCtxSaveEx(&Ctx, bTestMode, 0); + if (!fUseLowCode) + Bs3RegCtxSetRipCsFromLnkPtr(&Ctx, pfnIret); + else + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, pfnIretLow); + if (BS3_MODE_IS_16BIT_SYS(bTestMode)) + g_uBs3TrapEipHint = Ctx.rip.u32; + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rsp, &Ctx.ss, pIretBuf); + + /* + * The first success (UD) context keeps the same code bit-count as the iret. + */ + Bs3MemCpy(&CtxUdExpected, &Ctx, sizeof(Ctx)); + if (!fUseLowCode) + Bs3RegCtxSetRipCsFromLnkPtr(&CtxUdExpected, bs3CpuBasic2_ud2); + else + Bs3RegCtxSetRipCsFromCurPtr(&CtxUdExpected, pfnUdLow); + CtxUdExpected.rsp.u += cbSameCplFrame; + + /* + * Check that it works at all. + */ + iretbuf_SetupFrame(pIretBuf, cbPop, CtxUdExpected.cs, CtxUdExpected.rip.u, + CtxUdExpected.rflags.u32, CtxUdExpected.ss, CtxUdExpected.rsp.u); + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + g_usBs3TestStep++; + + if (!BS3_MODE_IS_RM_OR_V86(bTestMode)) + { + /* Selectors are modified when switching rings, so we need to know + what we're dealing with there. */ + if ( !BS3_SEL_IS_IN_R0_RANGE(Ctx.cs) || !BS3_SEL_IS_IN_R0_RANGE(Ctx.ss) + || !BS3_SEL_IS_IN_R0_RANGE(Ctx.ds) || !BS3_SEL_IS_IN_R0_RANGE(Ctx.es)) + Bs3TestFailedF("Expected R0 CS, SS, DS and ES; not %#x, %#x, %#x and %#x\n", Ctx.cs, Ctx.ss, Ctx.ds, Ctx.es); + if (Ctx.fs || Ctx.gs) + Bs3TestFailed("Expected R0 FS and GS to be 0!\n"); + + /* + * Test returning to outer rings if protected mode. + */ + Bs3MemCpy(&TmpCtx, &Ctx, sizeof(TmpCtx)); + Bs3MemCpy(&TmpCtxExpected, &CtxUdExpected, sizeof(TmpCtxExpected)); + for (iRingDst = 3; iRingDst >= 0; iRingDst--) + { + Bs3RegCtxConvertToRingX(&TmpCtxExpected, iRingDst); + TmpCtxExpected.ds = iRingDst ? 0 : TmpCtx.ds; + TmpCtx.es = TmpCtxExpected.es; + iretbuf_SetupFrame(pIretBuf, cbPop, TmpCtxExpected.cs, TmpCtxExpected.rip.u, + TmpCtxExpected.rflags.u32, TmpCtxExpected.ss, TmpCtxExpected.rsp.u); + Bs3TrapSetJmpAndRestore(&TmpCtx, &TrapCtx); + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &TmpCtxExpected); + g_usBs3TestStep++; + } + + /* + * Check CS.RPL and SS.RPL. + */ + for (iRingDst = 3; iRingDst >= 0; iRingDst--) + { + uint16_t const uDstSsR0 = (CtxUdExpected.ss & BS3_SEL_RING_SUB_MASK) + BS3_SEL_R0_FIRST; + Bs3MemCpy(&TmpCtxExpected, &CtxUdExpected, sizeof(TmpCtxExpected)); + Bs3RegCtxConvertToRingX(&TmpCtxExpected, iRingDst); + for (iRingSrc = 3; iRingSrc >= 0; iRingSrc--) + { + Bs3MemCpy(&TmpCtx, &Ctx, sizeof(TmpCtx)); + Bs3RegCtxConvertToRingX(&TmpCtx, iRingSrc); + TmpCtx.es = TmpCtxExpected.es; + TmpCtxExpected.ds = iRingDst != iRingSrc ? 0 : TmpCtx.ds; + for (uRplCs = 0; uRplCs <= 3; uRplCs++) + { + uint16_t const uSrcEs = TmpCtx.es; + uint16_t const uDstCs = (TmpCtxExpected.cs & X86_SEL_MASK_OFF_RPL) | uRplCs; + //Bs3TestPrintf("dst=%d src=%d rplCS=%d\n", iRingDst, iRingSrc, uRplCs); + + /* CS.RPL */ + iretbuf_SetupFrame(pIretBuf, cbPop, uDstCs, TmpCtxExpected.rip.u, TmpCtxExpected.rflags.u32, + TmpCtxExpected.ss, TmpCtxExpected.rsp.u); + Bs3TrapSetJmpAndRestore(&TmpCtx, &TrapCtx); + if (uRplCs == iRingDst && iRingDst >= iRingSrc) + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &TmpCtxExpected); + else + { + if (iRingDst < iRingSrc) + TmpCtx.es = 0; + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &TmpCtx, uDstCs & X86_SEL_MASK_OFF_RPL); + TmpCtx.es = uSrcEs; + } + g_usBs3TestStep++; + + /* SS.RPL */ + if (iRingDst != iRingSrc || BS3_MODE_IS_64BIT_CODE(bTestMode)) + { + uint16_t uSavedDstSs = TmpCtxExpected.ss; + for (uRplSs = 0; uRplSs <= 3; uRplSs++) + { + /* SS.DPL (iRingDst == CS.DPL) */ + for (uDplSs = 0; uDplSs <= 3; uDplSs++) + { + uint16_t const uDstSs = ((uDplSs << BS3_SEL_RING_SHIFT) | uRplSs) + uDstSsR0; + //Bs3TestPrintf("dst=%d src=%d rplCS=%d rplSS=%d dplSS=%d dst %04x:%08RX64 %08RX32 %04x:%08RX64\n", + // iRingDst, iRingSrc, uRplCs, uRplSs, uDplSs, uDstCs, TmpCtxExpected.rip.u, + // TmpCtxExpected.rflags.u32, uDstSs, TmpCtxExpected.rsp.u); + + iretbuf_SetupFrame(pIretBuf, cbPop, uDstCs, TmpCtxExpected.rip.u, + TmpCtxExpected.rflags.u32, uDstSs, TmpCtxExpected.rsp.u); + Bs3TrapSetJmpAndRestore(&TmpCtx, &TrapCtx); + if (uRplCs != iRingDst || iRingDst < iRingSrc) + { + if (iRingDst < iRingSrc) + TmpCtx.es = 0; + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &TmpCtx, uDstCs & X86_SEL_MASK_OFF_RPL); + } + else if (uRplSs != iRingDst || uDplSs != iRingDst) + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &TmpCtx, uDstSs & X86_SEL_MASK_OFF_RPL); + else + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &TmpCtxExpected); + TmpCtx.es = uSrcEs; + g_usBs3TestStep++; + } + } + + TmpCtxExpected.ss = uSavedDstSs; + } + } + } + } + } + + /* + * Special 64-bit checks. + */ + if (BS3_MODE_IS_64BIT_CODE(bTestMode)) + { + /* The VM flag is completely ignored. */ + iretbuf_SetupFrame(pIretBuf, cbPop, CtxUdExpected.cs, CtxUdExpected.rip.u, + CtxUdExpected.rflags.u32 | X86_EFL_VM, CtxUdExpected.ss, CtxUdExpected.rsp.u); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + g_usBs3TestStep++; + + /* The NT flag can be loaded just fine. */ + CtxUdExpected.rflags.u32 |= X86_EFL_NT; + iretbuf_SetupFrame(pIretBuf, cbPop, CtxUdExpected.cs, CtxUdExpected.rip.u, + CtxUdExpected.rflags.u32, CtxUdExpected.ss, CtxUdExpected.rsp.u); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxUdExpected); + CtxUdExpected.rflags.u32 &= ~X86_EFL_NT; + g_usBs3TestStep++; + + /* However, we'll #GP(0) if it's already set (in RFLAGS) when executing IRET. */ + Ctx.rflags.u32 |= X86_EFL_NT; + iretbuf_SetupFrame(pIretBuf, cbPop, CtxUdExpected.cs, CtxUdExpected.rip.u, + CtxUdExpected.rflags.u32, CtxUdExpected.ss, CtxUdExpected.rsp.u); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + g_usBs3TestStep++; + + /* The NT flag #GP(0) should trump all other exceptions - pit it against #PF. */ + pbTest = (uint8_t BS3_FAR *)Bs3MemGuardedTestPageAlloc(BS3MEMKIND_TILED); + if (pbTest != NULL) + { + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rsp, &Ctx.ss, &pbTest[X86_PAGE_SIZE]); + iretbuf_SetupFrame(pIretBuf, cbPop, CtxUdExpected.cs, CtxUdExpected.rip.u, + CtxUdExpected.rflags.u32, CtxUdExpected.ss, CtxUdExpected.rsp.u); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &Ctx, 0); + g_usBs3TestStep++; + + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rsp, &Ctx.ss, pIretBuf); + Bs3MemGuardedTestPageFree(pbTest); + } + Ctx.rflags.u32 &= ~X86_EFL_NT; + } +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_FAR_NM(bs3CpuBasic2_iret)(uint8_t bMode) +{ + struct + { + uint8_t abExtraStack[4096]; /**< we've got ~30KB of stack, so 4KB for the trap handlers++ is not a problem. */ + IRETBUF IRetBuf; + uint8_t abGuard[32]; + } uBuf; + size_t cbUnused; + + //if (bMode != BS3_MODE_LM64) return BS3TESTDOMODE_SKIPPED; + bs3CpuBasic2_SetGlobals(bMode); + + /* + * Primary instruction form. + */ + Bs3MemSet(&uBuf, 0xaa, sizeof(uBuf)); + Bs3MemSet(uBuf.abGuard, 0x88, sizeof(uBuf.abGuard)); + if (BS3_MODE_IS_16BIT_CODE(bMode)) + bs3CpuBasic2_iret_Worker(bMode, bs3CpuBasic2_iret, 2, &uBuf.IRetBuf, "iret"); + else if (BS3_MODE_IS_32BIT_CODE(bMode)) + bs3CpuBasic2_iret_Worker(bMode, bs3CpuBasic2_iret, 4, &uBuf.IRetBuf, "iretd"); + else + bs3CpuBasic2_iret_Worker(bMode, bs3CpuBasic2_iret_rexw, 8, &uBuf.IRetBuf, "o64 iret"); + + BS3_ASSERT(ASMMemIsAllU8(uBuf.abGuard, sizeof(uBuf.abGuard), 0x88)); + cbUnused = (uintptr_t)ASMMemFirstMismatchingU8(uBuf.abExtraStack, sizeof(uBuf.abExtraStack) + sizeof(uBuf.IRetBuf), 0xaa) + - (uintptr_t)uBuf.abExtraStack; + if (cbUnused < 2048) + Bs3TestFailedF("cbUnused=%u #%u\n", cbUnused, 1); + + /* + * Secondary variation: opsize prefixed. + */ + Bs3MemSet(&uBuf, 0xaa, sizeof(uBuf)); + Bs3MemSet(uBuf.abGuard, 0x88, sizeof(uBuf.abGuard)); + if (BS3_MODE_IS_16BIT_CODE(bMode) && (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80386) + bs3CpuBasic2_iret_Worker(bMode, bs3CpuBasic2_iret_opsize, 4, &uBuf.IRetBuf, "o32 iret"); + else if (BS3_MODE_IS_32BIT_CODE(bMode)) + bs3CpuBasic2_iret_Worker(bMode, bs3CpuBasic2_iret_opsize, 2, &uBuf.IRetBuf, "o16 iret"); + else if (BS3_MODE_IS_64BIT_CODE(bMode)) + bs3CpuBasic2_iret_Worker(bMode, bs3CpuBasic2_iret, 4, &uBuf.IRetBuf, "iretd"); + BS3_ASSERT(ASMMemIsAllU8(uBuf.abGuard, sizeof(uBuf.abGuard), 0x88)); + cbUnused = (uintptr_t)ASMMemFirstMismatchingU8(uBuf.abExtraStack, sizeof(uBuf.abExtraStack) + sizeof(uBuf.IRetBuf), 0xaa) + - (uintptr_t)uBuf.abExtraStack; + if (cbUnused < 2048) + Bs3TestFailedF("cbUnused=%u #%u\n", cbUnused, 2); + + /* + * Third variation: 16-bit in 64-bit mode (truly unlikely) + */ + if (BS3_MODE_IS_64BIT_CODE(bMode)) + { + Bs3MemSet(&uBuf, 0xaa, sizeof(uBuf)); + Bs3MemSet(uBuf.abGuard, 0x88, sizeof(uBuf.abGuard)); + bs3CpuBasic2_iret_Worker(bMode, bs3CpuBasic2_iret_opsize, 2, &uBuf.IRetBuf, "o16 iret"); + BS3_ASSERT(ASMMemIsAllU8(uBuf.abGuard, sizeof(uBuf.abGuard), 0x88)); + cbUnused = (uintptr_t)ASMMemFirstMismatchingU8(uBuf.abExtraStack, sizeof(uBuf.abExtraStack) + sizeof(uBuf.IRetBuf), 0xaa) + - (uintptr_t)uBuf.abExtraStack; + if (cbUnused < 2048) + Bs3TestFailedF("cbUnused=%u #%u\n", cbUnused, 3); + } + + return 0; +} + + + +/********************************************************************************************************************************* +* Non-far JMP & CALL Tests * +*********************************************************************************************************************************/ +#define PROTO_ALL(a_Template) \ + FNBS3FAR a_Template ## _c16, \ + a_Template ## _c32, \ + a_Template ## _c64 +PROTO_ALL(bs3CpuBasic2_jmp_jb__ud2); +PROTO_ALL(bs3CpuBasic2_jmp_jb_back__ud2); +PROTO_ALL(bs3CpuBasic2_jmp_jv__ud2); +PROTO_ALL(bs3CpuBasic2_jmp_jv_back__ud2); +PROTO_ALL(bs3CpuBasic2_jmp_ind_mem__ud2); +PROTO_ALL(bs3CpuBasic2_jmp_ind_xAX__ud2); +PROTO_ALL(bs3CpuBasic2_jmp_ind_xDI__ud2); +FNBS3FAR bs3CpuBasic2_jmp_ind_r9__ud2_c64; +PROTO_ALL(bs3CpuBasic2_call_jv__ud2); +PROTO_ALL(bs3CpuBasic2_call_jv_back__ud2); +PROTO_ALL(bs3CpuBasic2_call_ind_mem__ud2); +PROTO_ALL(bs3CpuBasic2_call_ind_xAX__ud2); +PROTO_ALL(bs3CpuBasic2_call_ind_xDI__ud2); +FNBS3FAR bs3CpuBasic2_call_ind_r9__ud2_c64; + +PROTO_ALL(bs3CpuBasic2_jmp_opsize_begin); +PROTO_ALL(bs3CpuBasic2_jmp_jb_opsize__ud2); +PROTO_ALL(bs3CpuBasic2_jmp_jb_opsize_back__ud2); +PROTO_ALL(bs3CpuBasic2_jmp_jv_opsize__ud2); +PROTO_ALL(bs3CpuBasic2_jmp_jv_opsize_back__ud2); +PROTO_ALL(bs3CpuBasic2_jmp_ind_mem_opsize__ud2); +FNBS3FAR bs3CpuBasic2_jmp_ind_mem_opsize__ud2__intel_c64; +PROTO_ALL(bs3CpuBasic2_jmp_ind_xAX_opsize__ud2); +PROTO_ALL(bs3CpuBasic2_call_jv_opsize__ud2); +PROTO_ALL(bs3CpuBasic2_call_jv_opsize_back__ud2); +PROTO_ALL(bs3CpuBasic2_call_ind_mem_opsize__ud2); +FNBS3FAR bs3CpuBasic2_call_ind_mem_opsize__ud2__intel_c64; +PROTO_ALL(bs3CpuBasic2_call_ind_xAX_opsize__ud2); +PROTO_ALL(bs3CpuBasic2_jmp_opsize_end); +#undef PROTO_ALL + +FNBS3FAR bs3CpuBasic2_jmptext16_start; + +FNBS3FAR bs3CpuBasic2_jmp_target_wrap_forward; +FNBS3FAR bs3CpuBasic2_jmp_jb_wrap_forward__ud2; +FNBS3FAR bs3CpuBasic2_jmp_jb_opsize_wrap_forward__ud2; +FNBS3FAR bs3CpuBasic2_jmp_jv16_wrap_forward__ud2; +FNBS3FAR bs3CpuBasic2_jmp_jv16_opsize_wrap_forward__ud2; +FNBS3FAR bs3CpuBasic2_call_jv16_wrap_forward__ud2; +FNBS3FAR bs3CpuBasic2_call_jv16_opsize_wrap_forward__ud2; + +FNBS3FAR bs3CpuBasic2_jmp_target_wrap_backward; +FNBS3FAR bs3CpuBasic2_jmp_jb_wrap_backward__ud2; +FNBS3FAR bs3CpuBasic2_jmp_jb_opsize_wrap_backward__ud2; +FNBS3FAR bs3CpuBasic2_jmp_jv16_wrap_backward__ud2; +FNBS3FAR bs3CpuBasic2_jmp_jv16_opsize_wrap_backward__ud2; +FNBS3FAR bs3CpuBasic2_call_jv16_wrap_backward__ud2; +FNBS3FAR bs3CpuBasic2_call_jv16_opsize_wrap_backward__ud2; + + + +/** + * Entrypoint for non-far JMP & CALL tests. + * + * @returns 0 or BS3TESTDOMODE_SKIPPED. + * @param bMode The CPU mode we're testing. + * + * @note When testing v8086 code, we'll be running in v8086 mode. So, careful + * with control registers and such. + */ +BS3_DECL_FAR(uint8_t) BS3_CMN_FAR_NM(bs3CpuBasic2_jmp_call)(uint8_t bMode) +{ + BS3TRAPFRAME TrapCtx; + BS3REGCTX Ctx; + BS3REGCTX CtxExpected; + unsigned iTest; + + /* make sure they're allocated */ + Bs3MemZero(&Ctx, sizeof(Ctx)); + Bs3MemZero(&CtxExpected, sizeof(Ctx)); + Bs3MemZero(&TrapCtx, sizeof(TrapCtx)); + + bs3CpuBasic2_SetGlobals(bMode); + + /* + * Create a context. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 768); + Bs3MemCpy(&CtxExpected, &Ctx, sizeof(CtxExpected)); + + /* + * 16-bit tests. + * + * When opsize is 16-bit relative jumps will do 16-bit calculations and + * modify IP. This means that it is not possible to trigger a segment + * limit #GP(0) when the limit is set to 0xffff. + */ + if (BS3_MODE_IS_16BIT_CODE(bMode)) + { + static struct + { + int8_t iWrap; + bool fOpSizePfx; + int8_t iGprIndirect; + bool fCall; + FPFNBS3FAR pfnTest; + } + const s_aTests[] = + { + { 0, false, -1, false, bs3CpuBasic2_jmp_jb__ud2_c16, }, + { 0, false, -1, false, bs3CpuBasic2_jmp_jb_back__ud2_c16, }, + { 0, true, -1, false, bs3CpuBasic2_jmp_jb_opsize__ud2_c16, }, + { 0, true, -1, false, bs3CpuBasic2_jmp_jb_opsize_back__ud2_c16, }, + { 0, false, -1, false, bs3CpuBasic2_jmp_jv__ud2_c16, }, + { 0, false, -1, false, bs3CpuBasic2_jmp_jv_back__ud2_c16, }, + { 0, true, -1, false, bs3CpuBasic2_jmp_jv_opsize__ud2_c16, }, + { 0, true, -1, false, bs3CpuBasic2_jmp_jv_opsize_back__ud2_c16, }, + { 0, false, -1, false, bs3CpuBasic2_jmp_ind_mem__ud2_c16, }, + { 0, true, -1, false, bs3CpuBasic2_jmp_ind_mem_opsize__ud2_c16, }, + { 0, false, X86_GREG_xAX, false, bs3CpuBasic2_jmp_ind_xAX__ud2_c16, }, + { 0, false, X86_GREG_xDI, false, bs3CpuBasic2_jmp_ind_xDI__ud2_c16, }, + { 0, true, X86_GREG_xAX, false, bs3CpuBasic2_jmp_ind_xAX_opsize__ud2_c16, }, + { 0, false, -1, true, bs3CpuBasic2_call_jv__ud2_c16, }, + { 0, false, -1, true, bs3CpuBasic2_call_jv_back__ud2_c16, }, + { 0, true, -1, true, bs3CpuBasic2_call_jv_opsize__ud2_c16, }, + { 0, true, -1, true, bs3CpuBasic2_call_jv_opsize_back__ud2_c16, }, + { 0, false, -1, true, bs3CpuBasic2_call_ind_mem__ud2_c16, }, + { 0, true, -1, true, bs3CpuBasic2_call_ind_mem_opsize__ud2_c16, }, + { 0, false, X86_GREG_xAX, true, bs3CpuBasic2_call_ind_xAX__ud2_c16, }, + { 0, false, X86_GREG_xDI, true, bs3CpuBasic2_call_ind_xDI__ud2_c16, }, + { 0, true, X86_GREG_xAX, true, bs3CpuBasic2_call_ind_xAX_opsize__ud2_c16, }, + + { -1, false, -1, false, bs3CpuBasic2_jmp_jb_wrap_backward__ud2, }, + { +1, false, -1, false, bs3CpuBasic2_jmp_jb_wrap_forward__ud2, }, + { -1, true, -1, false, bs3CpuBasic2_jmp_jb_opsize_wrap_backward__ud2, }, + { +1, true, -1, false, bs3CpuBasic2_jmp_jb_opsize_wrap_forward__ud2, }, + + { -1, false, -1, false, bs3CpuBasic2_jmp_jv16_wrap_backward__ud2, }, + { +1, false, -1, false, bs3CpuBasic2_jmp_jv16_wrap_forward__ud2, }, + { -1, true, -1, false, bs3CpuBasic2_jmp_jv16_opsize_wrap_backward__ud2, }, + { +1, true, -1, false, bs3CpuBasic2_jmp_jv16_opsize_wrap_forward__ud2, }, + { -1, false, -1, true, bs3CpuBasic2_call_jv16_wrap_backward__ud2, }, + { +1, false, -1, true, bs3CpuBasic2_call_jv16_wrap_forward__ud2, }, + { -1, true, -1, true, bs3CpuBasic2_call_jv16_opsize_wrap_backward__ud2, }, + { +1, true, -1, true, bs3CpuBasic2_call_jv16_opsize_wrap_forward__ud2, }, + }; + + if (!BS3_MODE_IS_RM_OR_V86(bMode)) + Bs3SelSetup16BitCode(&Bs3GdteSpare03, Bs3SelLnkPtrToFlat(bs3CpuBasic2_jmptext16_start), 0); + + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + { + uint64_t uGprSaved; + if (s_aTests[iTest].iWrap == 0) + { + uint8_t const BS3_FAR *fpbCode; + Bs3RegCtxSetRipCsFromLnkPtr(&Ctx, s_aTests[iTest].pfnTest); + fpbCode = (uint8_t const BS3_FAR *)BS3_FP_MAKE(Ctx.cs, Ctx.rip.u16); + CtxExpected.rip.u = Ctx.rip.u + (int64_t)(int8_t)fpbCode[-1]; + } + else + { + if (BS3_MODE_IS_RM_OR_V86(bMode)) + Ctx.cs = BS3_FP_SEG(s_aTests[iTest].pfnTest); + else + Ctx.cs = BS3_SEL_SPARE_03; + Ctx.rip.u = BS3_FP_OFF(s_aTests[iTest].pfnTest); + if (s_aTests[iTest].fOpSizePfx) + CtxExpected.rip.u = Ctx.rip.u; + else if (s_aTests[iTest].iWrap < 0) + CtxExpected.rip.u = BS3_FP_OFF(bs3CpuBasic2_jmp_target_wrap_backward); + else + CtxExpected.rip.u = BS3_FP_OFF(bs3CpuBasic2_jmp_target_wrap_forward); + } + CtxExpected.cs = Ctx.cs; + if (s_aTests[iTest].iGprIndirect >= 0) + { + uGprSaved = (&Ctx.rax)[s_aTests[iTest].iGprIndirect].u; + (&Ctx.rax)[s_aTests[iTest].iGprIndirect].u + = (&CtxExpected.rax)[s_aTests[iTest].iGprIndirect].u = CtxExpected.rip.u; + } + CtxExpected.rsp.u = Ctx.rsp.u; + if (s_aTests[iTest].fCall && (s_aTests[iTest].iWrap == 0 || !s_aTests[iTest].fOpSizePfx)) + CtxExpected.rsp.u -= s_aTests[iTest].fOpSizePfx ? 4 : 2; + //Bs3TestPrintf("cs:rip=%04RX16:%04RX64\n", Ctx.cs, Ctx.rip.u); + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (s_aTests[iTest].iWrap == 0 || !s_aTests[iTest].fOpSizePfx) + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxExpected); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, 0); + g_usBs3TestStep++; + + /* Again single stepping: */ + //Bs3TestPrintf("stepping...\n"); + Bs3RegSetDr6(0); + Ctx.rflags.u16 |= X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (s_aTests[iTest].iWrap == 0 || !s_aTests[iTest].fOpSizePfx) + bs3CpuBasic2_CompareDbCtx(&TrapCtx, &CtxExpected, X86_DR6_BS); + else + { + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, 0); + bs3CpuBasic2_CheckDr6InitVal(); + } + Ctx.rflags.u16 &= ~X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + g_usBs3TestStep++; + + if (s_aTests[iTest].iGprIndirect >= 0) + (&Ctx.rax)[s_aTests[iTest].iGprIndirect].u = (&CtxExpected.rax)[s_aTests[iTest].iGprIndirect].u = uGprSaved; + } + + /* Limit the wraparound CS segment to exclude bs3CpuBasic2_jmp_target_wrap_backward + and run the backward wrapping tests. */ + if (!BS3_MODE_IS_RM_OR_V86(bMode)) + { + Bs3GdteSpare03.Gen.u16LimitLow = BS3_FP_OFF(bs3CpuBasic2_jmp_target_wrap_backward) - 1; + CtxExpected.cs = Ctx.cs = BS3_SEL_SPARE_03; + CtxExpected.rsp.u = Ctx.rsp.u; + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + if (s_aTests[iTest].iWrap < 0) + { + CtxExpected.rip.u = Ctx.rip.u = BS3_FP_OFF(s_aTests[iTest].pfnTest); + //Bs3TestPrintf("cs:rip=%04RX16:%04RX64 v1\n", Ctx.cs, Ctx.rip.u); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, 0); + g_usBs3TestStep++; + } + + /* Do another round where we put the limit in the middle of the UD2 + instruction we're jumping to: */ + Bs3GdteSpare03.Gen.u16LimitLow = BS3_FP_OFF(bs3CpuBasic2_jmp_target_wrap_backward); + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + if (s_aTests[iTest].iWrap < 0) + { + Ctx.rip.u = BS3_FP_OFF(s_aTests[iTest].pfnTest); + if (s_aTests[iTest].fOpSizePfx) + CtxExpected.rip.u = Ctx.rip.u; + else + CtxExpected.rip.u = BS3_FP_OFF(bs3CpuBasic2_jmp_target_wrap_backward); + CtxExpected.rsp.u = Ctx.rsp.u; + if (s_aTests[iTest].fCall && (s_aTests[iTest].iWrap == 0 || !s_aTests[iTest].fOpSizePfx)) + CtxExpected.rsp.u -= s_aTests[iTest].fOpSizePfx ? 4 : 2; + //Bs3TestPrintf("cs:rip=%04RX16:%04RX64 v2\n", Ctx.cs, Ctx.rip.u); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, 0); + g_usBs3TestStep++; + } + } + + } + /* + * 32-bit & 64-bit tests. + * + * When the opsize prefix is applied here, IP is updated and bits 63:16 + * cleared. However in 64-bit mode, Intel ignores the opsize prefix + * whereas AMD doesn't and it works like you expect. + */ + else + { + static struct + { + uint8_t cBits; + bool fOpSizePfx; + bool fIgnPfx; + int8_t iGprIndirect; + bool fCall; + FPFNBS3FAR pfnTest; + } + const s_aTests[] = + { + { 32, false, false, -1, false, bs3CpuBasic2_jmp_jb__ud2_c32, }, + { 32, false, false, -1, false, bs3CpuBasic2_jmp_jb_back__ud2_c32, }, + { 32, true, false, -1, false, bs3CpuBasic2_jmp_jb_opsize__ud2_c32, }, + { 32, true, false, -1, false, bs3CpuBasic2_jmp_jb_opsize_back__ud2_c32, }, + { 32, false, false, -1, false, bs3CpuBasic2_jmp_jv__ud2_c32, }, + { 32, false, false, -1, false, bs3CpuBasic2_jmp_jv_back__ud2_c32, }, + { 32, true, false, -1, false, bs3CpuBasic2_jmp_jv_opsize__ud2_c32, }, + { 32, true, false, -1, false, bs3CpuBasic2_jmp_jv_opsize_back__ud2_c32, }, + { 32, false, false, -1, false, bs3CpuBasic2_jmp_ind_mem__ud2_c32, }, + { 32, true, false, -1, false, bs3CpuBasic2_jmp_ind_mem_opsize__ud2_c32, }, + { 32, false, false, X86_GREG_xAX, false, bs3CpuBasic2_jmp_ind_xAX__ud2_c32, }, + { 32, false, false, X86_GREG_xDI, false, bs3CpuBasic2_jmp_ind_xDI__ud2_c32, }, + { 32, true, false, X86_GREG_xAX, false, bs3CpuBasic2_jmp_ind_xAX_opsize__ud2_c32, }, + { 32, false, false, -1, true, bs3CpuBasic2_call_jv__ud2_c32, }, + { 32, false, false, -1, true, bs3CpuBasic2_call_jv_back__ud2_c32, }, + { 32, true, false, -1, true, bs3CpuBasic2_call_jv_opsize__ud2_c32, }, + { 32, true, false, -1, true, bs3CpuBasic2_call_jv_opsize_back__ud2_c32, }, + { 32, false, false, -1, true, bs3CpuBasic2_call_ind_mem__ud2_c32, }, + { 32, true, false, -1, true, bs3CpuBasic2_call_ind_mem_opsize__ud2_c32, }, + { 32, false, false, X86_GREG_xAX, true, bs3CpuBasic2_call_ind_xAX__ud2_c32, }, + { 32, false, false, X86_GREG_xDI, true, bs3CpuBasic2_call_ind_xDI__ud2_c32, }, + { 32, true, false, X86_GREG_xAX, true, bs3CpuBasic2_call_ind_xAX_opsize__ud2_c32, }, + /* 64bit/Intel: Use the _c64 tests, which are written to ignore the o16 prefix. */ + { 64, false, true, -1, false, bs3CpuBasic2_jmp_jb__ud2_c64, }, + { 64, false, true, -1, false, bs3CpuBasic2_jmp_jb_back__ud2_c64, }, + { 64, true, true, -1, false, bs3CpuBasic2_jmp_jb_opsize__ud2_c64, }, + { 64, true, true, -1, false, bs3CpuBasic2_jmp_jb_opsize_back__ud2_c64, }, + { 64, false, true, -1, false, bs3CpuBasic2_jmp_jv__ud2_c64, }, + { 64, false, true, -1, false, bs3CpuBasic2_jmp_jv_back__ud2_c64, }, + { 64, true, true, -1, false, bs3CpuBasic2_jmp_jv_opsize__ud2_c64, }, + { 64, true, true, -1, false, bs3CpuBasic2_jmp_jv_opsize_back__ud2_c64, }, + { 64, false, true, -1, false, bs3CpuBasic2_jmp_ind_mem__ud2_c64, }, + { 64, true, true, -1, false, bs3CpuBasic2_jmp_ind_mem_opsize__ud2__intel_c64, }, + { 64, false, true, X86_GREG_xAX, false, bs3CpuBasic2_jmp_ind_xAX__ud2_c64, }, + { 64, false, true, X86_GREG_xDI, false, bs3CpuBasic2_jmp_ind_xDI__ud2_c64, }, + { 64, false, true, X86_GREG_x9, false, bs3CpuBasic2_jmp_ind_r9__ud2_c64, }, + { 64, true, true, X86_GREG_xAX, false, bs3CpuBasic2_jmp_ind_xAX_opsize__ud2_c64, }, /* no intel version needed */ + { 64, false, true, -1, true, bs3CpuBasic2_call_jv__ud2_c64, }, + { 64, false, true, -1, true, bs3CpuBasic2_call_jv_back__ud2_c64, }, + { 64, true, true, -1, true, bs3CpuBasic2_call_jv_opsize__ud2_c64, }, + { 64, true, true, -1, true, bs3CpuBasic2_call_jv_opsize_back__ud2_c64, }, + { 64, false, true, -1, true, bs3CpuBasic2_call_ind_mem__ud2_c64, }, + { 64, true, true, -1, true, bs3CpuBasic2_call_ind_mem_opsize__ud2__intel_c64,}, + { 64, false, true, X86_GREG_xAX, true, bs3CpuBasic2_call_ind_xAX__ud2_c64, }, + { 64, false, true, X86_GREG_xDI, true, bs3CpuBasic2_call_ind_xDI__ud2_c64, }, + { 64, false, true, X86_GREG_x9, true, bs3CpuBasic2_call_ind_r9__ud2_c64, }, + { 64, true, true, X86_GREG_xAX, true, bs3CpuBasic2_call_ind_xAX_opsize__ud2_c64, }, /* no intel version needed */ + /* 64bit/AMD: Use the _c32 tests. */ + { 64, false, false, -1, false, bs3CpuBasic2_jmp_jb__ud2_c32, }, + { 64, false, false, -1, false, bs3CpuBasic2_jmp_jb_back__ud2_c32, }, + { 64, true, false, -1, false, bs3CpuBasic2_jmp_jb_opsize__ud2_c32, }, + { 64, true, false, -1, false, bs3CpuBasic2_jmp_jb_opsize_back__ud2_c32, }, + { 64, false, false, -1, false, bs3CpuBasic2_jmp_jv__ud2_c32, }, + { 64, false, false, -1, false, bs3CpuBasic2_jmp_jv_back__ud2_c32, }, + { 64, true, false, -1, false, bs3CpuBasic2_jmp_jv_opsize__ud2_c32, }, + { 64, true, false, -1, false, bs3CpuBasic2_jmp_jv_opsize_back__ud2_c32, }, + { 64, false, false, -1, false, bs3CpuBasic2_jmp_ind_mem__ud2_c64, }, /* using c64 here */ + { 64, true, false, -1, false, bs3CpuBasic2_jmp_ind_mem_opsize__ud2_c64, }, /* ditto */ + { 64, false, false, X86_GREG_xAX, false, bs3CpuBasic2_jmp_ind_xAX__ud2_c64, }, /* ditto */ + { 64, false, false, X86_GREG_xDI, false, bs3CpuBasic2_jmp_ind_xDI__ud2_c64, }, /* ditto */ + { 64, false, false, X86_GREG_x9, false, bs3CpuBasic2_jmp_ind_r9__ud2_c64, }, /* ditto */ + { 64, true, false, X86_GREG_xAX, false, bs3CpuBasic2_jmp_ind_xAX_opsize__ud2_c64, }, /* ditto */ + { 64, false, false, -1, true, bs3CpuBasic2_call_jv__ud2_c32, }, /* using c32 again */ + { 64, false, false, -1, true, bs3CpuBasic2_call_jv_back__ud2_c32, }, + { 64, true, false, -1, true, bs3CpuBasic2_call_jv_opsize__ud2_c32, }, + { 64, true, false, -1, true, bs3CpuBasic2_call_jv_opsize_back__ud2_c32, }, + { 64, false, false, -1, true, bs3CpuBasic2_call_ind_mem__ud2_c64, }, /* using c64 here */ + { 64, true, false, -1, true, bs3CpuBasic2_call_ind_mem_opsize__ud2_c64, }, /* ditto */ + { 64, false, false, X86_GREG_xAX, true, bs3CpuBasic2_call_ind_xAX__ud2_c64, }, /* ditto */ + { 64, false, false, X86_GREG_xDI, true, bs3CpuBasic2_call_ind_xDI__ud2_c64, }, /* ditto */ + { 64, false, false, X86_GREG_x9, true, bs3CpuBasic2_call_ind_r9__ud2_c64, }, /* ditto */ + { 64, true, false, X86_GREG_xAX, true, bs3CpuBasic2_call_ind_xAX_opsize__ud2_c64, }, /* ditto */ + }; + uint8_t const cBits = BS3_MODE_IS_64BIT_CODE(bMode) ? 64 : 32; + BS3CPUVENDOR const enmCpuVendor = Bs3GetCpuVendor(); + bool const fIgnPfx = cBits == 64 && enmCpuVendor == BS3CPUVENDOR_INTEL; /** @todo what does VIA do? */ + + /* Prepare a copy of the UD2 instructions in low memory for opsize prefixed tests. */ + uint16_t const offLow = BS3_FP_OFF(bs3CpuBasic2_jmp_opsize_begin_c32); + uint16_t const cbLow = BS3_FP_OFF(bs3CpuBasic2_jmp_opsize_end_c64) - offLow; + uint8_t BS3_FAR * const pbCode16 = BS3_MAKE_PROT_R0PTR_FROM_FLAT(BS3_ADDR_BS3TEXT16); + uint8_t BS3_FAR * const pbLow = BS3_FP_MAKE(BS3_SEL_TILED_R0, 0); + if (offLow < 0x600 || offLow + cbLow >= BS3_ADDR_STACK_R2) + Bs3TestFailedF("Opsize overriden jumps are out of place: %#x LB %#x\n", offLow, cbLow); + Bs3MemSet(&pbLow[offLow], 0xcc /*int3*/, cbLow); + if (!fIgnPfx) + { + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + if (s_aTests[iTest].fOpSizePfx && s_aTests[iTest].cBits == cBits && s_aTests[iTest].fIgnPfx == fIgnPfx) + { + uint16_t const offFn = BS3_FP_OFF(s_aTests[iTest].pfnTest); + uint16_t const offUd = offFn + (int16_t)(int8_t)pbCode16[offFn - 1]; + BS3_ASSERT(offUd - offLow + 1 < cbLow); + pbCode16[offUd] = 0xf1; /* replace original ud2 with icebp */ + pbCode16[offUd + 1] = 0xf1; + pbLow[offUd] = 0x0f; /* plant ud2 in low memory */ + pbLow[offUd + 1] = 0x0b; + } + } + + /* Run the tests. */ + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + { + if (s_aTests[iTest].cBits == cBits && s_aTests[iTest].fIgnPfx == fIgnPfx) + { + uint64_t uGprSaved; + uint8_t const BS3_FAR *fpbCode = Bs3SelLnkPtrToCurPtr(s_aTests[iTest].pfnTest); + Ctx.rip.u = Bs3SelLnkPtrToFlat(s_aTests[iTest].pfnTest); + CtxExpected.rip.u = Ctx.rip.u + (int64_t)(int8_t)fpbCode[-1]; + if (s_aTests[iTest].iGprIndirect >= 0) + { + uGprSaved = (&Ctx.rax)[s_aTests[iTest].iGprIndirect].u; + (&Ctx.rax)[s_aTests[iTest].iGprIndirect].u + = (&CtxExpected.rax)[s_aTests[iTest].iGprIndirect].u = CtxExpected.rip.u; + } + if (s_aTests[iTest].fOpSizePfx && !fIgnPfx) + CtxExpected.rip.u &= UINT16_MAX; + CtxExpected.rsp.u = Ctx.rsp.u; + if (s_aTests[iTest].fCall) + CtxExpected.rsp.u -= s_aTests[iTest].cBits == 64 ? 8 + : !s_aTests[iTest].fOpSizePfx ? 4 : 2; + + //Bs3TestPrintf("cs:rip=%04RX16:%08RX64\n", Ctx.cs, Ctx.rip.u); + + if (BS3_MODE_IS_16BIT_SYS(bMode)) + g_uBs3TrapEipHint = s_aTests[iTest].fOpSizePfx ? 0 : Ctx.rip.u32; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxExpected); + g_usBs3TestStep++; + + /* Again single stepping: */ + //Bs3TestPrintf("stepping...\n"); + Bs3RegSetDr6(0); + Ctx.rflags.u16 |= X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareDbCtx(&TrapCtx, &CtxExpected, X86_DR6_BS); + Ctx.rflags.u16 &= ~X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + g_usBs3TestStep++; + + if (s_aTests[iTest].iGprIndirect >= 0) + (&Ctx.rax)[s_aTests[iTest].iGprIndirect].u = (&CtxExpected.rax)[s_aTests[iTest].iGprIndirect].u = uGprSaved; + } + } + + Bs3MemSet(&pbLow[offLow], 0xcc /*int3*/, cbLow); + } + + return 0; +} + + +/********************************************************************************************************************************* +* FAR JMP & FAR CALL Tests * +*********************************************************************************************************************************/ +#define PROTO_ALL(a_Template) \ + FNBS3FAR a_Template ## _c16, \ + a_Template ## _c32, \ + a_Template ## _c64 +PROTO_ALL(bs3CpuBasic2_far_jmp_call_opsize_begin); + +FNBS3FAR bs3CpuBasic2_jmpf_ptr_rm__ud2_c16; +PROTO_ALL(bs3CpuBasic2_jmpf_ptr_same_r0__ud2); +PROTO_ALL(bs3CpuBasic2_jmpf_ptr_same_r1__ud2); +PROTO_ALL(bs3CpuBasic2_jmpf_ptr_same_r2__ud2); +PROTO_ALL(bs3CpuBasic2_jmpf_ptr_same_r3__ud2); +PROTO_ALL(bs3CpuBasic2_jmpf_ptr_opsize_flipbit_r0__ud2); +PROTO_ALL(bs3CpuBasic2_jmpf_ptr_r0_cs64__ud2); +PROTO_ALL(bs3CpuBasic2_jmpf_ptr_r0_cs16l__ud2); + +FNBS3FAR bs3CpuBasic2_callf_ptr_rm__ud2_c16; +PROTO_ALL(bs3CpuBasic2_callf_ptr_same_r0__ud2); +PROTO_ALL(bs3CpuBasic2_callf_ptr_same_r1__ud2); +PROTO_ALL(bs3CpuBasic2_callf_ptr_same_r2__ud2); +PROTO_ALL(bs3CpuBasic2_callf_ptr_same_r3__ud2); +PROTO_ALL(bs3CpuBasic2_callf_ptr_opsize_flipbit_r0__ud2); +PROTO_ALL(bs3CpuBasic2_callf_ptr_r0_cs64__ud2); +PROTO_ALL(bs3CpuBasic2_callf_ptr_r0_cs16l__ud2); + +FNBS3FAR bs3CpuBasic2_jmpf_mem_rm__ud2_c16; +PROTO_ALL(bs3CpuBasic2_jmpf_mem_same_r0__ud2); +PROTO_ALL(bs3CpuBasic2_jmpf_mem_same_r1__ud2); +PROTO_ALL(bs3CpuBasic2_jmpf_mem_same_r2__ud2); +PROTO_ALL(bs3CpuBasic2_jmpf_mem_same_r3__ud2); +PROTO_ALL(bs3CpuBasic2_jmpf_mem_r0_cs16__ud2); +PROTO_ALL(bs3CpuBasic2_jmpf_mem_r0_cs32__ud2); +PROTO_ALL(bs3CpuBasic2_jmpf_mem_r0_cs64__ud2); +PROTO_ALL(bs3CpuBasic2_jmpf_mem_r0_cs16l__ud2); + +FNBS3FAR bs3CpuBasic2_jmpf_mem_same_r0__ud2_intel_c64; +FNBS3FAR bs3CpuBasic2_jmpf_mem_same_r1__ud2_intel_c64; +FNBS3FAR bs3CpuBasic2_jmpf_mem_same_r2__ud2_intel_c64; +FNBS3FAR bs3CpuBasic2_jmpf_mem_same_r3__ud2_intel_c64; +FNBS3FAR bs3CpuBasic2_jmpf_mem_r0_cs16__ud2_intel_c64; +FNBS3FAR bs3CpuBasic2_jmpf_mem_r0_cs32__ud2_intel_c64; +FNBS3FAR bs3CpuBasic2_jmpf_mem_r0_cs64__ud2_intel_c64; +FNBS3FAR bs3CpuBasic2_jmpf_mem_r0_cs16l__ud2_intel_c64; + +FNBS3FAR bs3CpuBasic2_callf_mem_rm__ud2_c16; +PROTO_ALL(bs3CpuBasic2_callf_mem_same_r0__ud2); +PROTO_ALL(bs3CpuBasic2_callf_mem_same_r1__ud2); +PROTO_ALL(bs3CpuBasic2_callf_mem_same_r2__ud2); +PROTO_ALL(bs3CpuBasic2_callf_mem_same_r3__ud2); +PROTO_ALL(bs3CpuBasic2_callf_mem_r0_cs16__ud2); +PROTO_ALL(bs3CpuBasic2_callf_mem_r0_cs32__ud2); +PROTO_ALL(bs3CpuBasic2_callf_mem_r0_cs64__ud2); +PROTO_ALL(bs3CpuBasic2_callf_mem_r0_cs16l__ud2); + +FNBS3FAR bs3CpuBasic2_callf_mem_same_r0__ud2_intel_c64; +FNBS3FAR bs3CpuBasic2_callf_mem_same_r1__ud2_intel_c64; +FNBS3FAR bs3CpuBasic2_callf_mem_same_r2__ud2_intel_c64; +FNBS3FAR bs3CpuBasic2_callf_mem_same_r3__ud2_intel_c64; +FNBS3FAR bs3CpuBasic2_callf_mem_r0_cs16__ud2_intel_c64; +FNBS3FAR bs3CpuBasic2_callf_mem_r0_cs32__ud2_intel_c64; +FNBS3FAR bs3CpuBasic2_callf_mem_r0_cs64__ud2_intel_c64; +FNBS3FAR bs3CpuBasic2_callf_mem_r0_cs16l__ud2_intel_c64; + +PROTO_ALL(bs3CpuBasic2_far_jmp_call_opsize_end); +#undef PROTO_ALL + + + +/** + * Entrypoint for FAR JMP & FAR CALL tests. + * + * @returns 0 or BS3TESTDOMODE_SKIPPED. + * @param bMode The CPU mode we're testing. + * + * @note When testing v8086 code, we'll be running in v8086 mode. So, careful + * with control registers and such. + */ +BS3_DECL_FAR(uint8_t) BS3_CMN_FAR_NM(bs3CpuBasic2_far_jmp_call)(uint8_t bMode) +{ + BS3TRAPFRAME TrapCtx; + BS3REGCTX Ctx; + BS3REGCTX CtxExpected; + unsigned iTest; + + /* make sure they're allocated */ + Bs3MemZero(&Ctx, sizeof(Ctx)); + Bs3MemZero(&CtxExpected, sizeof(Ctx)); + Bs3MemZero(&TrapCtx, sizeof(TrapCtx)); + + bs3CpuBasic2_SetGlobals(bMode); + + /* + * Create a context. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 768); + Bs3MemCpy(&CtxExpected, &Ctx, sizeof(CtxExpected)); + + if (Ctx.rax.u8 == 0 || Ctx.rax.u8 == 0xff) /* for salc & the 64-bit detection */ + CtxExpected.rax.u8 = Ctx.rax.u8 = 0x42; + + /* + * Set up spare selectors. + */ + Bs3GdteSpare00 = Bs3Gdte_CODE16; + Bs3GdteSpare00.Gen.u1Long = 1; + + /* + * 16-bit tests. + */ + if (BS3_MODE_IS_16BIT_CODE(bMode)) + { + static struct + { + bool fRmOrV86; + bool fCall; + uint16_t uDstSel; + uint8_t uDstBits; + bool fOpSizePfx; + FPFNBS3FAR pfnTest; + } + const s_aTests[] = + { + { true, false, BS3_SEL_TEXT16, 16, false, bs3CpuBasic2_jmpf_ptr_rm__ud2_c16, }, + { false, false, BS3_SEL_R0_CS16, 16, false, bs3CpuBasic2_jmpf_ptr_same_r0__ud2_c16, }, + { false, false, BS3_SEL_R1_CS16 | 1, 16, false, bs3CpuBasic2_jmpf_ptr_same_r1__ud2_c16, }, + { false, false, BS3_SEL_R2_CS16 | 2, 16, false, bs3CpuBasic2_jmpf_ptr_same_r2__ud2_c16, }, + { false, false, BS3_SEL_R3_CS16 | 3, 16, false, bs3CpuBasic2_jmpf_ptr_same_r3__ud2_c16, }, + { false, false, BS3_SEL_R0_CS32, 32, true, bs3CpuBasic2_jmpf_ptr_opsize_flipbit_r0__ud2_c16, }, + { false, false, BS3_SEL_R0_CS64, 64, true, bs3CpuBasic2_jmpf_ptr_r0_cs64__ud2_c16, }, /* 16-bit CS, except in LM. */ + { false, false, BS3_SEL_SPARE_00, 64, false, bs3CpuBasic2_jmpf_ptr_r0_cs16l__ud2_c16, }, /* 16-bit CS, except in LM. */ + + { true, true, BS3_SEL_TEXT16, 16, false, bs3CpuBasic2_callf_ptr_rm__ud2_c16, }, + { false, true, BS3_SEL_R0_CS16, 16, false, bs3CpuBasic2_callf_ptr_same_r0__ud2_c16, }, + { false, true, BS3_SEL_R1_CS16 | 1, 16, false, bs3CpuBasic2_callf_ptr_same_r1__ud2_c16, }, + { false, true, BS3_SEL_R2_CS16 | 2, 16, false, bs3CpuBasic2_callf_ptr_same_r2__ud2_c16, }, + { false, true, BS3_SEL_R3_CS16 | 3, 16, false, bs3CpuBasic2_callf_ptr_same_r3__ud2_c16, }, + { false, true, BS3_SEL_R0_CS32, 32, true, bs3CpuBasic2_callf_ptr_opsize_flipbit_r0__ud2_c16, }, + { false, true, BS3_SEL_R0_CS64, 64, true, bs3CpuBasic2_callf_ptr_r0_cs64__ud2_c16, }, /* 16-bit CS, except in LM. */ + { false, true, BS3_SEL_SPARE_00, 64, false, bs3CpuBasic2_callf_ptr_r0_cs16l__ud2_c16, }, /* 16-bit CS, except in LM. */ + + { true, false, BS3_SEL_TEXT16, 16, false, bs3CpuBasic2_jmpf_mem_rm__ud2_c16, }, + { false, false, BS3_SEL_R0_CS16, 16, false, bs3CpuBasic2_jmpf_mem_same_r0__ud2_c16, }, + { false, false, BS3_SEL_R1_CS16 | 1, 16, false, bs3CpuBasic2_jmpf_mem_same_r1__ud2_c16, }, + { false, false, BS3_SEL_R2_CS16 | 2, 16, false, bs3CpuBasic2_jmpf_mem_same_r2__ud2_c16, }, + { false, false, BS3_SEL_R3_CS16 | 3, 16, false, bs3CpuBasic2_jmpf_mem_same_r3__ud2_c16, }, + { false, false, BS3_SEL_R0_CS16, 16, false, bs3CpuBasic2_jmpf_mem_r0_cs16__ud2_c16, }, + { false, false, BS3_SEL_R0_CS32, 32, true, bs3CpuBasic2_jmpf_mem_r0_cs32__ud2_c16, }, + { false, false, BS3_SEL_R0_CS64, 64, true, bs3CpuBasic2_jmpf_mem_r0_cs64__ud2_c16, }, /* 16-bit CS, except in LM. */ + { false, false, BS3_SEL_SPARE_00, 64, false, bs3CpuBasic2_jmpf_mem_r0_cs16l__ud2_c16, }, /* 16-bit CS, except in LM. */ + + { true, true, BS3_SEL_TEXT16, 16, false, bs3CpuBasic2_callf_mem_rm__ud2_c16, }, + { false, true, BS3_SEL_R0_CS16, 16, false, bs3CpuBasic2_callf_mem_same_r0__ud2_c16, }, + { false, true, BS3_SEL_R1_CS16 | 1, 16, false, bs3CpuBasic2_callf_mem_same_r1__ud2_c16, }, + { false, true, BS3_SEL_R2_CS16 | 2, 16, false, bs3CpuBasic2_callf_mem_same_r2__ud2_c16, }, + { false, true, BS3_SEL_R3_CS16 | 3, 16, false, bs3CpuBasic2_callf_mem_same_r3__ud2_c16, }, + { false, true, BS3_SEL_R0_CS16, 16, false, bs3CpuBasic2_callf_mem_r0_cs16__ud2_c16, }, + { false, true, BS3_SEL_R0_CS32, 32, true, bs3CpuBasic2_callf_mem_r0_cs32__ud2_c16, }, + { false, true, BS3_SEL_R0_CS64, 64, true, bs3CpuBasic2_callf_mem_r0_cs64__ud2_c16, }, /* 16-bit CS, except in LM. */ + { false, true, BS3_SEL_SPARE_00, 64, false, bs3CpuBasic2_callf_mem_r0_cs16l__ud2_c16, }, /* 16-bit CS, except in LM. */ + }; + bool const fRmOrV86 = BS3_MODE_IS_RM_OR_V86(bMode); + + /* Prepare a copy of the SALC & UD2 instructions in low memory for opsize + prefixed tests jumping to BS3_SEL_SPARE_00 when in 64-bit mode, because + it'll be a 64-bit CS then with base=0 instead of a CS16 with base=0x10000. */ + if (BS3_MODE_IS_64BIT_SYS(bMode)) + { + uint16_t const offLow = BS3_FP_OFF(bs3CpuBasic2_far_jmp_call_opsize_begin_c16); + uint16_t const cbLow = BS3_FP_OFF(bs3CpuBasic2_far_jmp_call_opsize_end_c16) - offLow; + uint8_t BS3_FAR * const pbLow = BS3_FP_MAKE(BS3_SEL_TILED_R0, 0); + uint8_t BS3_FAR * const pbCode16 = BS3_MAKE_PROT_R0PTR_FROM_FLAT(BS3_ADDR_BS3TEXT16); + if (offLow < 0x600 || offLow + cbLow >= BS3_ADDR_STACK_R2) + Bs3TestFailedF("Opsize overriden jumps/calls are out of place: %#x LB %#x\n", offLow, cbLow); + Bs3MemSet(&pbLow[offLow], 0xcc /*int3*/, cbLow); + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + if (s_aTests[iTest].uDstSel == BS3_SEL_SPARE_00 && s_aTests[iTest].uDstBits == 64) + { + uint16_t const offFn = BS3_FP_OFF(s_aTests[iTest].pfnTest); + uint16_t const offUd = offFn + (int16_t)(int8_t)pbCode16[offFn - 1]; + BS3_ASSERT(offUd - offLow + 1 < cbLow); + pbLow[offUd - 1] = 0xd6; /* plant salc + ud2 in low memory */ + pbLow[offUd] = 0x0f; + pbLow[offUd + 1] = 0x0b; + } + } + + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + if (s_aTests[iTest].fRmOrV86 == fRmOrV86) + { + uint64_t const uSavedRsp = Ctx.rsp.u; + bool const fGp = (s_aTests[iTest].uDstSel & X86_SEL_RPL) != 0; + uint8_t const BS3_FAR *fpbCode; + + Bs3RegCtxSetRipCsFromLnkPtr(&Ctx, s_aTests[iTest].pfnTest); + fpbCode = (uint8_t const BS3_FAR *)BS3_FP_MAKE(Ctx.cs, Ctx.rip.u16); + CtxExpected.rip.u = Ctx.rip.u + (int64_t)(int8_t)fpbCode[-1]; + if ( s_aTests[iTest].uDstBits == 32 + || ( s_aTests[iTest].uDstBits == 64 + && !BS3_MODE_IS_16BIT_SYS(bMode) + && s_aTests[iTest].uDstSel != BS3_SEL_SPARE_00)) + CtxExpected.rip.u += BS3_ADDR_BS3TEXT16; + if (s_aTests[iTest].uDstSel == BS3_SEL_SPARE_00 && s_aTests[iTest].uDstBits == 64 && BS3_MODE_IS_64BIT_SYS(bMode)) + CtxExpected.rip.u &= UINT16_MAX; + CtxExpected.cs = s_aTests[iTest].uDstSel; + if (fGp) + { + CtxExpected.rip.u = Ctx.rip.u; + CtxExpected.cs = Ctx.cs; + } + g_uBs3TrapEipHint = CtxExpected.rip.u32; + CtxExpected.rsp.u = Ctx.rsp.u; + if (s_aTests[iTest].fCall && !fGp) + CtxExpected.rsp.u -= s_aTests[iTest].fOpSizePfx ? 8 : 4; + if (s_aTests[iTest].uDstBits == 64 && !fGp) + { + if (BS3_MODE_IS_64BIT_SYS(bMode)) + CtxExpected.rip.u -= 1; + else + CtxExpected.rax.u8 = CtxExpected.rflags.u & X86_EFL_CF ? 0xff : 0x00; + } + //Bs3TestPrintf("cs:rip=%04RX16:%04RX64 -> %04RX16:%04RX64\n", Ctx.cs, Ctx.rip.u, CtxExpected.cs, CtxExpected.rip.u); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (!fGp) + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxExpected); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, s_aTests[iTest].uDstSel & X86_TRAP_ERR_SEL_MASK); + Ctx.rsp.u = uSavedRsp; + g_usBs3TestStep++; + + /* Again single stepping: */ + //Bs3TestPrintf("stepping...\n"); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + Ctx.rflags.u16 |= X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + CtxExpected.rax.u = Ctx.rax.u; + if (s_aTests[iTest].uDstBits == 64 && !fGp && !BS3_MODE_IS_64BIT_SYS(bMode)) + CtxExpected.rip.u -= 1; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (!fGp) + bs3CpuBasic2_CompareDbCtx(&TrapCtx, &CtxExpected, X86_DR6_BS); + else + { + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, s_aTests[iTest].uDstSel & X86_TRAP_ERR_SEL_MASK); + bs3CpuBasic2_CheckDr6InitVal(); + } + Ctx.rflags.u16 &= ~X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + Ctx.rsp.u = uSavedRsp; + g_usBs3TestStep++; + } + } + /* + * 32-bit tests. + */ + else if (BS3_MODE_IS_32BIT_CODE(bMode)) + { + static struct + { + bool fCall; + uint16_t uDstSel; + uint8_t uDstBits; + bool fOpSizePfx; + FPFNBS3FAR pfnTest; + } + const s_aTests[] = + { + { false, BS3_SEL_R0_CS32, 32, false, bs3CpuBasic2_jmpf_ptr_same_r0__ud2_c32, }, + { false, BS3_SEL_R1_CS32 | 1, 32, false, bs3CpuBasic2_jmpf_ptr_same_r1__ud2_c32, }, + { false, BS3_SEL_R2_CS32 | 2, 32, false, bs3CpuBasic2_jmpf_ptr_same_r2__ud2_c32, }, + { false, BS3_SEL_R3_CS32 | 3, 32, false, bs3CpuBasic2_jmpf_ptr_same_r3__ud2_c32, }, + { false, BS3_SEL_R0_CS16, 16, true, bs3CpuBasic2_jmpf_ptr_opsize_flipbit_r0__ud2_c32, }, + { false, BS3_SEL_R0_CS64, 64, false, bs3CpuBasic2_jmpf_ptr_r0_cs64__ud2_c32, }, /* 16-bit CS, except in LM. */ + { false, BS3_SEL_SPARE_00, 64, true, bs3CpuBasic2_jmpf_ptr_r0_cs16l__ud2_c32, }, /* 16-bit CS, except in LM. */ + + { true, BS3_SEL_R0_CS32, 32, false, bs3CpuBasic2_callf_ptr_same_r0__ud2_c32, }, + { true, BS3_SEL_R1_CS32 | 1, 32, false, bs3CpuBasic2_callf_ptr_same_r1__ud2_c32, }, + { true, BS3_SEL_R2_CS32 | 2, 32, false, bs3CpuBasic2_callf_ptr_same_r2__ud2_c32, }, + { true, BS3_SEL_R3_CS32 | 3, 32, false, bs3CpuBasic2_callf_ptr_same_r3__ud2_c32, }, + { true, BS3_SEL_R0_CS16, 16, true, bs3CpuBasic2_callf_ptr_opsize_flipbit_r0__ud2_c32, }, + { true, BS3_SEL_R0_CS64, 64, false, bs3CpuBasic2_callf_ptr_r0_cs64__ud2_c32, }, /* 16-bit CS, except in LM. */ + { true, BS3_SEL_SPARE_00, 64, true, bs3CpuBasic2_callf_ptr_r0_cs16l__ud2_c32, }, /* 16-bit CS, except in LM. */ + + { false, BS3_SEL_R0_CS32, 32, false, bs3CpuBasic2_jmpf_mem_same_r0__ud2_c32, }, + { false, BS3_SEL_R1_CS32 | 1, 32, false, bs3CpuBasic2_jmpf_mem_same_r1__ud2_c32, }, + { false, BS3_SEL_R2_CS32 | 2, 32, false, bs3CpuBasic2_jmpf_mem_same_r2__ud2_c32, }, + { false, BS3_SEL_R3_CS32 | 3, 32, false, bs3CpuBasic2_jmpf_mem_same_r3__ud2_c32, }, + { false, BS3_SEL_R0_CS16, 16, true, bs3CpuBasic2_jmpf_mem_r0_cs16__ud2_c32, }, + { false, BS3_SEL_R0_CS32, 32, false, bs3CpuBasic2_jmpf_mem_r0_cs32__ud2_c32, }, + { false, BS3_SEL_R0_CS64, 64, false, bs3CpuBasic2_jmpf_mem_r0_cs64__ud2_c32, }, /* 16-bit CS, except in LM. */ + { false, BS3_SEL_SPARE_00, 64, true, bs3CpuBasic2_jmpf_mem_r0_cs16l__ud2_c32, }, /* 16-bit CS, except in LM. */ + + { true, BS3_SEL_R0_CS32, 32, false, bs3CpuBasic2_callf_mem_same_r0__ud2_c32, }, + { true, BS3_SEL_R1_CS32 | 1, 32, false, bs3CpuBasic2_callf_mem_same_r1__ud2_c32, }, + { true, BS3_SEL_R2_CS32 | 2, 32, false, bs3CpuBasic2_callf_mem_same_r2__ud2_c32, }, + { true, BS3_SEL_R3_CS32 | 3, 32, false, bs3CpuBasic2_callf_mem_same_r3__ud2_c32, }, + { true, BS3_SEL_R0_CS16, 16, true, bs3CpuBasic2_callf_mem_r0_cs16__ud2_c32, }, + { true, BS3_SEL_R0_CS32, 32, false, bs3CpuBasic2_callf_mem_r0_cs32__ud2_c32, }, + { true, BS3_SEL_R0_CS64, 64, false, bs3CpuBasic2_callf_mem_r0_cs64__ud2_c32, }, /* 16-bit CS, except in LM. */ + { true, BS3_SEL_SPARE_00, 64, true, bs3CpuBasic2_callf_mem_r0_cs16l__ud2_c32, }, /* 16-bit CS, except in LM. */ + }; + + /* Prepare a copy of the SALC & UD2 instructions in low memory for opsize + prefixed tests jumping to BS3_SEL_SPARE_00 when in 64-bit mode, because + it'll be a 64-bit CS then with base=0 instead of a CS16 with base=0x10000. */ + if (BS3_MODE_IS_64BIT_SYS(bMode)) + { + uint16_t const offLow = BS3_FP_OFF(bs3CpuBasic2_far_jmp_call_opsize_begin_c32); + uint16_t const cbLow = BS3_FP_OFF(bs3CpuBasic2_far_jmp_call_opsize_end_c32) - offLow; + uint8_t BS3_FAR * const pbLow = BS3_FP_MAKE(BS3_SEL_TILED_R0, 0); + uint8_t BS3_FAR * const pbCode16 = BS3_MAKE_PROT_R0PTR_FROM_FLAT(BS3_ADDR_BS3TEXT16); + if (offLow < 0x600 || offLow + cbLow >= BS3_ADDR_STACK_R2) + Bs3TestFailedF("Opsize overriden jumps/calls are out of place: %#x LB %#x\n", offLow, cbLow); + Bs3MemSet(&pbLow[offLow], 0xcc /*int3*/, cbLow); + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + if (s_aTests[iTest].uDstSel == BS3_SEL_SPARE_00 && s_aTests[iTest].uDstBits == 64) + { + uint16_t const offFn = BS3_FP_OFF(s_aTests[iTest].pfnTest); + uint16_t const offUd = offFn + (int16_t)(int8_t)pbCode16[offFn - 1]; + BS3_ASSERT(offUd - offLow + 1 < cbLow); + pbLow[offUd - 1] = 0xd6; /* plant salc + ud2 in low memory */ + pbLow[offUd] = 0x0f; + pbLow[offUd + 1] = 0x0b; + } + } + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + { + uint64_t const uSavedRsp = Ctx.rsp.u; + bool const fGp = (s_aTests[iTest].uDstSel & X86_SEL_RPL) != 0; + uint8_t const BS3_FAR *fpbCode = Bs3SelLnkPtrToCurPtr(s_aTests[iTest].pfnTest); + + Ctx.rip.u = Bs3SelLnkPtrToFlat(s_aTests[iTest].pfnTest); + CtxExpected.rip.u = Ctx.rip.u + (int64_t)(int8_t)fpbCode[-1]; + if ( s_aTests[iTest].uDstBits == 16 + || ( s_aTests[iTest].uDstBits == 64 + && ( BS3_MODE_IS_16BIT_SYS(bMode)) + || s_aTests[iTest].uDstSel == BS3_SEL_SPARE_00)) + CtxExpected.rip.u &= UINT16_MAX; + CtxExpected.cs = s_aTests[iTest].uDstSel; + if (fGp) + { + CtxExpected.rip.u = Ctx.rip.u; + CtxExpected.cs = Ctx.cs; + } + g_uBs3TrapEipHint = CtxExpected.rip.u32; + CtxExpected.rsp.u = Ctx.rsp.u; + if (s_aTests[iTest].fCall && !fGp) + CtxExpected.rsp.u -= s_aTests[iTest].fOpSizePfx ? 4 : 8; + if (s_aTests[iTest].uDstBits == 64 && !fGp) + { + if (BS3_MODE_IS_64BIT_SYS(bMode)) + CtxExpected.rip.u -= 1; + else + CtxExpected.rax.u8 = CtxExpected.rflags.u & X86_EFL_CF ? 0xff : 0x00; + } + //Bs3TestPrintf("cs:rip=%04RX16:%04RX64 -> %04RX16:%04RX64\n", Ctx.cs, Ctx.rip.u, CtxExpected.cs, CtxExpected.rip.u); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (!fGp) + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxExpected); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, s_aTests[iTest].uDstSel & X86_TRAP_ERR_SEL_MASK); + Ctx.rsp.u = uSavedRsp; + g_usBs3TestStep++; + + /* Again single stepping: */ + //Bs3TestPrintf("stepping...\n"); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + Ctx.rflags.u16 |= X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + CtxExpected.rax.u = Ctx.rax.u; + if (s_aTests[iTest].uDstBits == 64 && !fGp && !BS3_MODE_IS_64BIT_SYS(bMode)) + CtxExpected.rip.u -= 1; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (!fGp) + bs3CpuBasic2_CompareDbCtx(&TrapCtx, &CtxExpected, X86_DR6_BS); + else + { + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, s_aTests[iTest].uDstSel & X86_TRAP_ERR_SEL_MASK); + bs3CpuBasic2_CheckDr6InitVal(); + } + Ctx.rflags.u16 &= ~X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + Ctx.rsp.u = uSavedRsp; + g_usBs3TestStep++; + } + } + /* + * 64-bit tests. + */ + else if (BS3_MODE_IS_64BIT_CODE(bMode)) + { + static struct + { + bool fInvalid; + bool fCall; + uint16_t uDstSel; + uint8_t uDstBits; + uint8_t fOpSizePfx; /**< 0: none, 1: 066h, 2: REX.W, 3: 066h REX.W */ + int8_t fFix64OpSize; + FPFNBS3FAR pfnTest; + } + const s_aTests[] = + { + /* invalid opcodes: */ + { true, false, BS3_SEL_R0_CS32, 64, 0, -1, bs3CpuBasic2_jmpf_ptr_same_r0__ud2_c32, }, + { true, false, BS3_SEL_R1_CS32 | 1, 64, 0, -1, bs3CpuBasic2_jmpf_ptr_same_r1__ud2_c32, }, + { true, false, BS3_SEL_R2_CS32 | 2, 64, 0, -1, bs3CpuBasic2_jmpf_ptr_same_r2__ud2_c32, }, + { true, false, BS3_SEL_R3_CS32 | 3, 64, 0, -1, bs3CpuBasic2_jmpf_ptr_same_r3__ud2_c32, }, + { true, false, BS3_SEL_R0_CS16, 64, 0, -1, bs3CpuBasic2_jmpf_ptr_opsize_flipbit_r0__ud2_c32, }, + { true, false, BS3_SEL_R0_CS64, 64, 0, -1, bs3CpuBasic2_jmpf_ptr_r0_cs64__ud2_c32, }, + { true, false, BS3_SEL_SPARE_00, 64, 0, -1, bs3CpuBasic2_jmpf_ptr_r0_cs16l__ud2_c32, }, + + { true, true, BS3_SEL_R0_CS32, 64, 0, -1, bs3CpuBasic2_callf_ptr_same_r0__ud2_c32, }, + { true, true, BS3_SEL_R1_CS32 | 1, 64, 0, -1, bs3CpuBasic2_callf_ptr_same_r1__ud2_c32, }, + { true, true, BS3_SEL_R2_CS32 | 2, 64, 0, -1, bs3CpuBasic2_callf_ptr_same_r2__ud2_c32, }, + { true, true, BS3_SEL_R3_CS32 | 3, 64, 0, -1, bs3CpuBasic2_callf_ptr_same_r3__ud2_c32, }, + { true, true, BS3_SEL_R0_CS16, 64, 0, -1, bs3CpuBasic2_callf_ptr_opsize_flipbit_r0__ud2_c32, }, + { true, true, BS3_SEL_R0_CS64, 64, 0, -1, bs3CpuBasic2_callf_ptr_r0_cs64__ud2_c32, }, + { true, true, BS3_SEL_SPARE_00, 64, 0, -1, bs3CpuBasic2_callf_ptr_r0_cs16l__ud2_c32, }, + + { false, false, BS3_SEL_R0_CS64, 64, 0, false, bs3CpuBasic2_jmpf_mem_same_r0__ud2_c64, }, + { false, false, BS3_SEL_R1_CS64 | 1, 64, 0, false, bs3CpuBasic2_jmpf_mem_same_r1__ud2_c64, }, + { false, false, BS3_SEL_R2_CS64 | 2, 64, 0, false, bs3CpuBasic2_jmpf_mem_same_r2__ud2_c64, }, + { false, false, BS3_SEL_R3_CS64 | 3, 64, 0, false, bs3CpuBasic2_jmpf_mem_same_r3__ud2_c64, }, + { false, false, BS3_SEL_R0_CS16, 16, 1, false, bs3CpuBasic2_jmpf_mem_r0_cs16__ud2_c64, }, + { false, false, BS3_SEL_R0_CS32, 32, 0, false, bs3CpuBasic2_jmpf_mem_r0_cs32__ud2_c64, }, + { false, false, BS3_SEL_R0_CS64, 64, 0, false, bs3CpuBasic2_jmpf_mem_r0_cs64__ud2_c64, }, /* 16-bit CS, except in LM. */ + { false, false, BS3_SEL_SPARE_00, 64, 0, false, bs3CpuBasic2_jmpf_mem_r0_cs16l__ud2_c64, }, /* 16-bit CS, except in LM. */ + + { false, false, BS3_SEL_R0_CS64, 64, 2, true, bs3CpuBasic2_jmpf_mem_same_r0__ud2_intel_c64, }, + { false, false, BS3_SEL_R1_CS64 | 1, 64, 2, true, bs3CpuBasic2_jmpf_mem_same_r1__ud2_intel_c64, }, + { false, false, BS3_SEL_R2_CS64 | 2, 64, 0, true, bs3CpuBasic2_jmpf_mem_same_r2__ud2_intel_c64, }, + { false, false, BS3_SEL_R3_CS64 | 3, 64, 2, true, bs3CpuBasic2_jmpf_mem_same_r3__ud2_intel_c64, }, + { false, false, BS3_SEL_R0_CS16, 16, 1, true, bs3CpuBasic2_jmpf_mem_r0_cs16__ud2_intel_c64, }, + { false, false, BS3_SEL_R0_CS32, 32, 0, true, bs3CpuBasic2_jmpf_mem_r0_cs32__ud2_intel_c64, }, + { false, false, BS3_SEL_R0_CS64, 64, 2, true, bs3CpuBasic2_jmpf_mem_r0_cs64__ud2_intel_c64, }, /* 16-bit CS, except in LM. */ + { false, false, BS3_SEL_SPARE_00, 64, 0, true, bs3CpuBasic2_jmpf_mem_r0_cs16l__ud2_intel_c64, }, /* 16-bit CS, except in LM. */ + + { false, true, BS3_SEL_R0_CS64, 64, 2, false, bs3CpuBasic2_callf_mem_same_r0__ud2_c64, }, + { false, true, BS3_SEL_R1_CS64 | 1, 64, 2, false, bs3CpuBasic2_callf_mem_same_r1__ud2_c64, }, + { false, true, BS3_SEL_R2_CS64 | 2, 64, 0, false, bs3CpuBasic2_callf_mem_same_r2__ud2_c64, }, + { false, true, BS3_SEL_R3_CS64 | 3, 64, 2, false, bs3CpuBasic2_callf_mem_same_r3__ud2_c64, }, + { false, true, BS3_SEL_R0_CS16, 16, 1, false, bs3CpuBasic2_callf_mem_r0_cs16__ud2_c64, }, + { false, true, BS3_SEL_R0_CS32, 32, 2, false, bs3CpuBasic2_callf_mem_r0_cs32__ud2_c64, }, + { false, true, BS3_SEL_R0_CS64, 64, 0, false, bs3CpuBasic2_callf_mem_r0_cs64__ud2_c64, }, /* 16-bit CS, except in LM. */ + { false, true, BS3_SEL_SPARE_00, 64, 0, false, bs3CpuBasic2_callf_mem_r0_cs16l__ud2_c64, }, /* 16-bit CS, except in LM. */ + + { false, true, BS3_SEL_R0_CS64, 64, 2, true, bs3CpuBasic2_callf_mem_same_r0__ud2_intel_c64, }, + { false, true, BS3_SEL_R1_CS64 | 1, 64, 2, true, bs3CpuBasic2_callf_mem_same_r1__ud2_intel_c64, }, + { false, true, BS3_SEL_R2_CS64 | 2, 64, 0, true, bs3CpuBasic2_callf_mem_same_r2__ud2_intel_c64, }, + { false, true, BS3_SEL_R3_CS64 | 3, 64, 2, true, bs3CpuBasic2_callf_mem_same_r3__ud2_intel_c64, }, + { false, true, BS3_SEL_R0_CS16, 16, 1, true, bs3CpuBasic2_callf_mem_r0_cs16__ud2_intel_c64, }, + { false, true, BS3_SEL_R0_CS32, 32, 0, true, bs3CpuBasic2_callf_mem_r0_cs32__ud2_intel_c64, }, + { false, true, BS3_SEL_R0_CS64, 64, 2, true, bs3CpuBasic2_callf_mem_r0_cs64__ud2_intel_c64, }, /* 16-bit CS, except in LM. */ + { false, true, BS3_SEL_SPARE_00, 64, 0, true, bs3CpuBasic2_callf_mem_r0_cs16l__ud2_intel_c64, }, /* 16-bit CS, except in LM. */ + }; + BS3CPUVENDOR const enmCpuVendor = Bs3GetCpuVendor(); + bool const fFix64OpSize = enmCpuVendor == BS3CPUVENDOR_INTEL; /** @todo what does VIA do? */ + + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + { + uint64_t const uSavedRsp = Ctx.rsp.u; + bool const fUd = s_aTests[iTest].fInvalid; + bool const fGp = (s_aTests[iTest].uDstSel & X86_SEL_RPL) != 0; + uint8_t const BS3_FAR *fpbCode = Bs3SelLnkPtrToCurPtr(s_aTests[iTest].pfnTest); + + if (s_aTests[iTest].fFix64OpSize != fFix64OpSize && s_aTests[iTest].fFix64OpSize >= 0) + continue; + + Ctx.rip.u = Bs3SelLnkPtrToFlat(s_aTests[iTest].pfnTest); + CtxExpected.rip.u = Ctx.rip.u + (int64_t)(int8_t)fpbCode[-1]; + CtxExpected.cs = s_aTests[iTest].uDstSel; + if (s_aTests[iTest].uDstBits == 16) + CtxExpected.rip.u &= UINT16_MAX; + else if (s_aTests[iTest].uDstBits == 64 && fFix64OpSize && s_aTests[iTest].uDstSel != BS3_SEL_SPARE_00) + CtxExpected.rip.u |= UINT64_C(0xfffff00000000000); + + if (fGp || fUd) + { + CtxExpected.rip.u = Ctx.rip.u; + CtxExpected.cs = Ctx.cs; + } + CtxExpected.rsp.u = Ctx.rsp.u; + if (s_aTests[iTest].fCall && !fGp && !fUd) + { + CtxExpected.rsp.u -= s_aTests[iTest].fOpSizePfx == 0 ? 8 + : s_aTests[iTest].fOpSizePfx == 1 ? 4 : 16; + //Bs3TestPrintf("cs:rsp=%04RX16:%04RX64 -> %04RX64 (fOpSizePfx=%d)\n", Ctx.ss, Ctx.rsp.u, CtxExpected.rsp.u, s_aTests[iTest].fOpSizePfx); + } + //Bs3TestPrintf("cs:rip=%04RX16:%04RX64 -> %04RX16:%04RX64\n", Ctx.cs, Ctx.rip.u, CtxExpected.cs, CtxExpected.rip.u); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (!fGp || fUd) + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxExpected); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, s_aTests[iTest].uDstSel & X86_TRAP_ERR_SEL_MASK); + Ctx.rsp.u = uSavedRsp; + g_usBs3TestStep++; + + /* Again single stepping: */ + //Bs3TestPrintf("stepping...\n"); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + Ctx.rflags.u16 |= X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + CtxExpected.rax.u = Ctx.rax.u; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (fUd) + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxExpected); + else if (!fGp) + bs3CpuBasic2_CompareDbCtx(&TrapCtx, &CtxExpected, X86_DR6_BS); + else + { + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, s_aTests[iTest].uDstSel & X86_TRAP_ERR_SEL_MASK); + bs3CpuBasic2_CheckDr6InitVal(); + } + Ctx.rflags.u16 &= ~X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + Ctx.rsp.u = uSavedRsp; + g_usBs3TestStep++; + } + } + else + Bs3TestFailed("wtf?"); + + return 0; +} + + +/********************************************************************************************************************************* +* Near RET * +*********************************************************************************************************************************/ +#define PROTO_ALL(a_Template) \ + FNBS3FAR a_Template ## _c16, \ + a_Template ## _c32, \ + a_Template ## _c64 +PROTO_ALL(bs3CpuBasic2_retn_opsize_begin); +PROTO_ALL(bs3CpuBasic2_retn__ud2); +PROTO_ALL(bs3CpuBasic2_retn_opsize__ud2); +PROTO_ALL(bs3CpuBasic2_retn_i24__ud2); +PROTO_ALL(bs3CpuBasic2_retn_i24_opsize__ud2); +PROTO_ALL(bs3CpuBasic2_retn_i760__ud2); +PROTO_ALL(bs3CpuBasic2_retn_i0__ud2); +PROTO_ALL(bs3CpuBasic2_retn_i0_opsize__ud2); +FNBS3FAR bs3CpuBasic2_retn_rexw__ud2_c64; +FNBS3FAR bs3CpuBasic2_retn_i24_rexw__ud2_c64; +FNBS3FAR bs3CpuBasic2_retn_opsize_rexw__ud2_c64; +FNBS3FAR bs3CpuBasic2_retn_rexw_opsize__ud2_c64; +FNBS3FAR bs3CpuBasic2_retn_i24_opsize_rexw__ud2_c64; +FNBS3FAR bs3CpuBasic2_retn_i24_rexw_opsize__ud2_c64; +PROTO_ALL(bs3CpuBasic2_retn_opsize_end); +#undef PROTO_ALL + + +static void bs3CpuBasic2_retn_PrepStack(BS3PTRUNION StkPtr, PCBS3REGCTX pCtxExpected, uint8_t cbAddr) +{ + StkPtr.pu32[3] = UINT32_MAX; + StkPtr.pu32[2] = UINT32_MAX; + StkPtr.pu32[1] = UINT32_MAX; + StkPtr.pu32[0] = UINT32_MAX; + StkPtr.pu32[-1] = UINT32_MAX; + StkPtr.pu32[-2] = UINT32_MAX; + StkPtr.pu32[-3] = UINT32_MAX; + StkPtr.pu32[-4] = UINT32_MAX; + if (cbAddr == 2) + StkPtr.pu16[0] = pCtxExpected->rip.u16; + else if (cbAddr == 4) + StkPtr.pu32[0] = pCtxExpected->rip.u32; + else + StkPtr.pu64[0] = pCtxExpected->rip.u64; +} + + +/** + * Entrypoint for NEAR RET tests. + * + * @returns 0 or BS3TESTDOMODE_SKIPPED. + * @param bMode The CPU mode we're testing. + */ +BS3_DECL_FAR(uint8_t) BS3_CMN_FAR_NM(bs3CpuBasic2_near_ret)(uint8_t bMode) +{ + BS3TRAPFRAME TrapCtx; + BS3REGCTX Ctx; + BS3REGCTX CtxExpected; + unsigned iTest; + BS3PTRUNION StkPtr; + + /* make sure they're allocated */ + Bs3MemZero(&Ctx, sizeof(Ctx)); + Bs3MemZero(&CtxExpected, sizeof(Ctx)); + Bs3MemZero(&TrapCtx, sizeof(TrapCtx)); + + bs3CpuBasic2_SetGlobals(bMode); + + /* + * Create a context. + * + * ASSUMES we're in on the ring-0 stack in ring-0 and using less than 16KB. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 1664); + Ctx.rsp.u = BS3_ADDR_STACK - _16K; + Bs3MemCpy(&CtxExpected, &Ctx, sizeof(CtxExpected)); + + StkPtr.pv = Bs3RegCtxGetRspSsAsCurPtr(&Ctx); + //Bs3TestPrintf("Stack=%p rsp=%RX64\n", StkPtr.pv, Ctx.rsp.u); + + /* + * 16-bit tests. + */ + if (BS3_MODE_IS_16BIT_CODE(bMode)) + { + static struct + { + bool fOpSizePfx; + uint16_t cbImm; + FPFNBS3FAR pfnTest; + } + const s_aTests[] = + { + { false, 0, bs3CpuBasic2_retn__ud2_c16, }, + { true, 0, bs3CpuBasic2_retn_opsize__ud2_c16, }, + { false, 24, bs3CpuBasic2_retn_i24__ud2_c16, }, + { true, 24, bs3CpuBasic2_retn_i24_opsize__ud2_c16, }, + { false, 0, bs3CpuBasic2_retn_i0__ud2_c16, }, + { true, 0, bs3CpuBasic2_retn_i0_opsize__ud2_c16, }, + { false,760, bs3CpuBasic2_retn_i760__ud2_c16, }, + }; + + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + { + uint8_t const BS3_FAR *fpbCode; + + Bs3RegCtxSetRipCsFromLnkPtr(&Ctx, s_aTests[iTest].pfnTest); + fpbCode = (uint8_t const BS3_FAR *)BS3_FP_MAKE(Ctx.cs, Ctx.rip.u16); + CtxExpected.rip.u = Ctx.rip.u + (int64_t)(int8_t)fpbCode[-1]; + g_uBs3TrapEipHint = CtxExpected.rip.u32; + CtxExpected.cs = Ctx.cs; + if (!s_aTests[iTest].fOpSizePfx) + CtxExpected.rsp.u = Ctx.rsp.u + s_aTests[iTest].cbImm + 2; + else + CtxExpected.rsp.u = Ctx.rsp.u + s_aTests[iTest].cbImm + 4; + //Bs3TestPrintf("cs:rip=%04RX16:%04RX64 -> %04RX16:%04RX64\n", Ctx.cs, Ctx.rip.u, CtxExpected.cs, CtxExpected.rip.u); + //Bs3TestPrintf("ss:rsp=%04RX16:%04RX64\n", Ctx.ss, Ctx.rsp.u); + bs3CpuBasic2_retn_PrepStack(StkPtr, &CtxExpected, s_aTests[iTest].fOpSizePfx ? 4 : 2); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxExpected); + g_usBs3TestStep++; + + /* Again single stepping: */ + //Bs3TestPrintf("stepping...\n"); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + Ctx.rflags.u16 |= X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + bs3CpuBasic2_retn_PrepStack(StkPtr, &CtxExpected, s_aTests[iTest].fOpSizePfx ? 4 : 2); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareDbCtx(&TrapCtx, &CtxExpected, X86_DR6_BS); + Ctx.rflags.u16 &= ~X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + g_usBs3TestStep++; + } + } + /* + * 32-bit tests. + */ + else if (BS3_MODE_IS_32BIT_CODE(bMode)) + { + static struct + { + uint8_t cBits; + bool fOpSizePfx; + uint16_t cbImm; + FPFNBS3FAR pfnTest; + } + const s_aTests[] = + { + { 32, false, 0, bs3CpuBasic2_retn__ud2_c32, }, + { 32, true, 0, bs3CpuBasic2_retn_opsize__ud2_c32, }, + { 32, false, 24, bs3CpuBasic2_retn_i24__ud2_c32, }, + { 32, true, 24, bs3CpuBasic2_retn_i24_opsize__ud2_c32, }, + { 32, false, 0, bs3CpuBasic2_retn_i0__ud2_c32, }, + { 32, true, 0, bs3CpuBasic2_retn_i0_opsize__ud2_c32, }, + { 32, false,760, bs3CpuBasic2_retn_i760__ud2_c32, }, + }; + + /* Prepare a copy of the UD2 instructions in low memory for opsize prefixed tests. */ + uint16_t const offLow = BS3_FP_OFF(bs3CpuBasic2_retn_opsize_begin_c32); + uint16_t const cbLow = BS3_FP_OFF(bs3CpuBasic2_retn_opsize_end_c32) - offLow; + uint8_t BS3_FAR * const pbLow = BS3_FP_MAKE(BS3_SEL_TILED_R0, 0); + uint8_t BS3_FAR * const pbCode16 = BS3_MAKE_PROT_R0PTR_FROM_FLAT(BS3_ADDR_BS3TEXT16); + if (offLow < 0x600 || offLow + cbLow >= BS3_ADDR_STACK_R2) + Bs3TestFailedF("Opsize overriden jumps/calls are out of place: %#x LB %#x\n", offLow, cbLow); + Bs3MemSet(&pbLow[offLow], 0xcc /*int3*/, cbLow); + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + if (s_aTests[iTest].fOpSizePfx) + { + uint16_t const offFn = BS3_FP_OFF(s_aTests[iTest].pfnTest); + uint16_t const offUd = offFn + (int16_t)(int8_t)pbCode16[offFn - 1]; + BS3_ASSERT(offUd - offLow + 1 < cbLow); + pbCode16[offUd] = 0xf1; /* replace original ud2 with icebp */ + pbCode16[offUd + 1] = 0xf1; + pbLow[offUd] = 0x0f; /* plant ud2 in low memory */ + pbLow[offUd + 1] = 0x0b; + } + + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + { + uint8_t const BS3_FAR *fpbCode = Bs3SelLnkPtrToCurPtr(s_aTests[iTest].pfnTest); + + Ctx.rip.u = Bs3SelLnkPtrToFlat(s_aTests[iTest].pfnTest); + CtxExpected.rip.u = Ctx.rip.u + (int64_t)(int8_t)fpbCode[-1]; + CtxExpected.cs = Ctx.cs; + if (!s_aTests[iTest].fOpSizePfx) + CtxExpected.rsp.u = Ctx.rsp.u + s_aTests[iTest].cbImm + 4; + else + { + CtxExpected.rsp.u = Ctx.rsp.u + s_aTests[iTest].cbImm + 2; + CtxExpected.rip.u &= UINT16_MAX; + } + g_uBs3TrapEipHint = CtxExpected.rip.u32; + //Bs3TestPrintf("cs:rip=%04RX16:%04RX64 -> %04RX16:%04RX64\n", Ctx.cs, Ctx.rip.u, CtxExpected.cs, CtxExpected.rip.u); + //Bs3TestPrintf("ss:rsp=%04RX16:%04RX64\n", Ctx.ss, Ctx.rsp.u); + bs3CpuBasic2_retn_PrepStack(StkPtr, &CtxExpected, s_aTests[iTest].fOpSizePfx ? 2 : 4); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxExpected); + g_usBs3TestStep++; + + /* Again single stepping: */ + //Bs3TestPrintf("stepping...\n"); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + Ctx.rflags.u16 |= X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + bs3CpuBasic2_retn_PrepStack(StkPtr, &CtxExpected, s_aTests[iTest].fOpSizePfx ? 2 : 4); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareDbCtx(&TrapCtx, &CtxExpected, X86_DR6_BS); + Ctx.rflags.u16 &= ~X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + g_usBs3TestStep++; + } + } + /* + * 64-bit tests. + */ + else if (BS3_MODE_IS_64BIT_CODE(bMode)) + { + static struct + { + uint8_t cBits; + bool fOpSizePfx; + uint16_t cbImm; + FPFNBS3FAR pfnTest; + } + const s_aTests[] = + { + { 32, false, 0, bs3CpuBasic2_retn__ud2_c64, }, + { 32, false, 0, bs3CpuBasic2_retn_rexw__ud2_c64, }, + { 32, true, 0, bs3CpuBasic2_retn_opsize__ud2_c64, }, + { 32, false, 0, bs3CpuBasic2_retn_opsize_rexw__ud2_c64, }, + { 32, true, 0, bs3CpuBasic2_retn_rexw_opsize__ud2_c64, }, + { 32, false, 24, bs3CpuBasic2_retn_i24__ud2_c64, }, + { 32, false, 24, bs3CpuBasic2_retn_i24_rexw__ud2_c64, }, + { 32, true, 24, bs3CpuBasic2_retn_i24_opsize__ud2_c64, }, + { 32, false, 24, bs3CpuBasic2_retn_i24_opsize_rexw__ud2_c64, }, + { 32, true, 24, bs3CpuBasic2_retn_i24_rexw_opsize__ud2_c64, }, + { 32, false, 0, bs3CpuBasic2_retn_i0__ud2_c64, }, + { 32, true, 0, bs3CpuBasic2_retn_i0_opsize__ud2_c64, }, + { 32, false,760, bs3CpuBasic2_retn_i760__ud2_c64, }, + }; + BS3CPUVENDOR const enmCpuVendor = Bs3GetCpuVendor(); + bool const fFix64OpSize = enmCpuVendor == BS3CPUVENDOR_INTEL; /** @todo what does VIA do? */ + + /* Prepare a copy of the UD2 instructions in low memory for opsize prefixed + tests, unless we're on intel where the opsize prefix is ignored. Here we + just fill low memory with int3's so we can detect non-intel behaviour. */ + uint16_t const offLow = BS3_FP_OFF(bs3CpuBasic2_retn_opsize_begin_c64); + uint16_t const cbLow = BS3_FP_OFF(bs3CpuBasic2_retn_opsize_end_c64) - offLow; + uint8_t BS3_FAR * const pbLow = BS3_FP_MAKE(BS3_SEL_TILED_R0, 0); + uint8_t BS3_FAR * const pbCode16 = BS3_MAKE_PROT_R0PTR_FROM_FLAT(BS3_ADDR_BS3TEXT16); + if (offLow < 0x600 || offLow + cbLow >= BS3_ADDR_STACK_R2) + Bs3TestFailedF("Opsize overriden jumps/calls are out of place: %#x LB %#x\n", offLow, cbLow); + Bs3MemSet(&pbLow[offLow], 0xcc /*int3*/, cbLow); + if (!fFix64OpSize) + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + if (s_aTests[iTest].fOpSizePfx) + { + uint16_t const offFn = BS3_FP_OFF(s_aTests[iTest].pfnTest); + uint16_t const offUd = offFn + (int16_t)(int8_t)pbCode16[offFn - 1]; + BS3_ASSERT(offUd - offLow + 1 < cbLow); + pbCode16[offUd] = 0xf1; /* replace original ud2 with icebp */ + pbCode16[offUd + 1] = 0xf1; + pbLow[offUd] = 0x0f; /* plant ud2 in low memory */ + pbLow[offUd + 1] = 0x0b; + } + + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + { + uint8_t const BS3_FAR *fpbCode = Bs3SelLnkPtrToCurPtr(s_aTests[iTest].pfnTest); + + Ctx.rip.u = Bs3SelLnkPtrToFlat(s_aTests[iTest].pfnTest); + CtxExpected.rip.u = Ctx.rip.u + (int64_t)(int8_t)fpbCode[-1]; + CtxExpected.cs = Ctx.cs; + if (!s_aTests[iTest].fOpSizePfx || fFix64OpSize) + CtxExpected.rsp.u = Ctx.rsp.u + s_aTests[iTest].cbImm + 8; + else + { + CtxExpected.rsp.u = Ctx.rsp.u + s_aTests[iTest].cbImm + 2; + CtxExpected.rip.u &= UINT16_MAX; + } + g_uBs3TrapEipHint = CtxExpected.rip.u32; + //Bs3TestPrintf("cs:rip=%04RX16:%04RX64 -> %04RX16:%04RX64\n", Ctx.cs, Ctx.rip.u, CtxExpected.cs, CtxExpected.rip.u); + //Bs3TestPrintf("ss:rsp=%04RX16:%04RX64\n", Ctx.ss, Ctx.rsp.u); + bs3CpuBasic2_retn_PrepStack(StkPtr, &CtxExpected, s_aTests[iTest].fOpSizePfx && !fFix64OpSize ? 2 : 8); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxExpected); + g_usBs3TestStep++; + + /* Again single stepping: */ + //Bs3TestPrintf("stepping...\n"); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + Ctx.rflags.u16 |= X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + bs3CpuBasic2_retn_PrepStack(StkPtr, &CtxExpected, s_aTests[iTest].fOpSizePfx && !fFix64OpSize ? 2 : 8); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuBasic2_CompareDbCtx(&TrapCtx, &CtxExpected, X86_DR6_BS); + Ctx.rflags.u16 &= ~X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + g_usBs3TestStep++; + } + } + else + Bs3TestFailed("wtf?"); + + return 0; +} + + +/********************************************************************************************************************************* +* Far RET * +*********************************************************************************************************************************/ +#define PROTO_ALL(a_Template) \ + FNBS3FAR a_Template ## _c16, \ + a_Template ## _c32, \ + a_Template ## _c64 +PROTO_ALL(bs3CpuBasic2_retf); +PROTO_ALL(bs3CpuBasic2_retf_opsize); +FNBS3FAR bs3CpuBasic2_retf_rexw_c64; +FNBS3FAR bs3CpuBasic2_retf_rexw_opsize_c64; +FNBS3FAR bs3CpuBasic2_retf_opsize_rexw_c64; +PROTO_ALL(bs3CpuBasic2_retf_i32); +PROTO_ALL(bs3CpuBasic2_retf_i32_opsize); +FNBS3FAR bs3CpuBasic2_retf_i24_rexw_c64; +FNBS3FAR bs3CpuBasic2_retf_i24_rexw_opsize_c64; +FNBS3FAR bs3CpuBasic2_retf_i24_opsize_rexw_c64; +PROTO_ALL(bs3CpuBasic2_retf_i888); +#undef PROTO_ALL + + +static void bs3CpuBasic2_retf_PrepStack(BS3PTRUNION StkPtr, uint8_t cbStkItem, RTSEL uRetCs, uint64_t uRetRip, + bool fWithStack, uint16_t cbImm, RTSEL uRetSs, uint64_t uRetRsp) +{ + Bs3MemSet(&StkPtr.pu32[-4], 0xff, 96); + if (cbStkItem == 2) + { + StkPtr.pu16[0] = (uint16_t)uRetRip; + StkPtr.pu16[1] = uRetCs; + if (fWithStack) + { + StkPtr.pb += cbImm; + StkPtr.pu16[2] = (uint16_t)uRetRsp; + StkPtr.pu16[3] = uRetSs; + } + } + else if (cbStkItem == 4) + { + StkPtr.pu32[0] = (uint32_t)uRetRip; + StkPtr.pu16[2] = uRetCs; + if (fWithStack) + { + StkPtr.pb += cbImm; + StkPtr.pu32[2] = (uint32_t)uRetRsp; + StkPtr.pu16[6] = uRetSs; + } + } + else + { + StkPtr.pu64[0] = uRetRip; + StkPtr.pu16[4] = uRetCs; + if (fWithStack) + { + StkPtr.pb += cbImm; + StkPtr.pu64[2] = uRetRsp; + StkPtr.pu16[12] = uRetSs; + } + } +} + + +/** + * Entrypoint for FAR RET tests. + * + * @returns 0 or BS3TESTDOMODE_SKIPPED. + * @param bMode The CPU mode we're testing. + */ +BS3_DECL_FAR(uint8_t) BS3_CMN_FAR_NM(bs3CpuBasic2_far_ret)(uint8_t bMode) +{ + BS3TRAPFRAME TrapCtx; + BS3REGCTX Ctx; + BS3REGCTX Ctx2; + BS3REGCTX CtxExpected; + unsigned iTest; + unsigned iSubTest; + BS3PTRUNION StkPtr; + +#define LOW_UD_ADDR 0x0609 + uint8_t BS3_FAR * const pbLowUd = BS3_FP_MAKE(BS3_FP_SEG(&StkPtr), LOW_UD_ADDR); +#define LOW_SALC_UD_ADDR 0x0611 + uint8_t BS3_FAR * const pbLowSalcUd = BS3_FP_MAKE(BS3_FP_SEG(&StkPtr), LOW_SALC_UD_ADDR); +#define LOW_SWAPGS_ADDR 0x061d + uint8_t BS3_FAR * const pbLowSwapGs = BS3_FP_MAKE(BS3_FP_SEG(&StkPtr), LOW_SWAPGS_ADDR); +#define BS3TEXT16_ADDR_HI (BS3_ADDR_BS3TEXT16 >> 16) + + /* make sure they're allocated */ + Bs3MemZero(&Ctx, sizeof(Ctx)); + Bs3MemZero(&Ctx2, sizeof(Ctx2)); + Bs3MemZero(&CtxExpected, sizeof(CtxExpected)); + Bs3MemZero(&TrapCtx, sizeof(TrapCtx)); + + bs3CpuBasic2_SetGlobals(bMode); + + //if (!BS3_MODE_IS_64BIT_SYS(bMode) && bMode != BS3_MODE_PP32_16) return 0xff; + //if (bMode != BS3_MODE_PE32_16) return 0xff; + + /* + * When dealing retf with 16-bit effective operand size to 32-bit or 64-bit + * code, we're restricted to a 16-bit address. So, we plant a UD + * instruction below 64KB that we can target with flat 32/64 code segments. + * (Putting it on the stack would be possible too, but we'd have to create + * the sub-test tables dynamically, which isn't necessary.) + */ + Bs3MemSet(&pbLowUd[-9], 0xcc, 32); + Bs3MemSet(&pbLowSalcUd[-9], 0xcc, 32); + Bs3MemSet(&pbLowSwapGs[-9], 0xcc, 32); + + pbLowUd[0] = 0x0f; /* ud2 */ + pbLowUd[1] = 0x0b; + + /* A variation to detect whether we're in 64-bit or 16-bit mode when + executing the code. */ + pbLowSalcUd[0] = 0xd6; /* salc */ + pbLowSalcUd[1] = 0x0f; /* ud2 */ + pbLowSalcUd[2] = 0x0b; + + /* A variation to check that we're not in 64-bit mode. */ + pbLowSwapGs[0] = 0x0f; /* swapgs */ + pbLowSwapGs[1] = 0x01; + pbLowSwapGs[2] = 0xf8; + + /* + * Use separate stacks for all relevant CPU exceptions so we can put + * garbage in unused RSP bits w/o needing to care about where a long mode + * handler will end up when accessing the whole RSP. (Not an issue with + * 16-bit and 32-bit protected mode kernels, as here the weird SS based + * stack pointer handling is in effect and the exception handler code + * will just continue using the same SS and same portion of RSP.) + * + * See r154660. + */ + if (BS3_MODE_IS_64BIT_SYS(bMode)) + Bs3Trap64InitEx(true); + + /* + * Create some call gates and whatnot for the UD2 code using the spare selectors. + */ + if (BS3_MODE_IS_64BIT_SYS(bMode)) + for (iTest = 0; iTest < 16; iTest++) + Bs3SelSetupGate64(&Bs3GdteSpare00 + iTest * 2, iTest /*bType*/, 3 /*bDpl*/, + BS3_SEL_R0_CS64, BS3_FP_OFF(bs3CpuBasic2_ud2) + BS3_ADDR_BS3TEXT16); + else + { + for (iTest = 0; iTest < 16; iTest++) + { + Bs3SelSetupGate(&Bs3GdteSpare00 + iTest, iTest /*bType*/, 3 /*bDpl*/, + BS3_SEL_R0_CS16, BS3_FP_OFF(bs3CpuBasic2_ud2), 0); + Bs3SelSetupGate(&Bs3GdteSpare00 + iTest + 16, iTest /*bType*/, 3 /*bDpl*/, + BS3_SEL_R0_CS32, BS3_FP_OFF(bs3CpuBasic2_ud2) + BS3_ADDR_BS3TEXT16, 0); + } + } + + /* + * Create a context. + * + * ASSUMES we're in on the ring-0 stack in ring-0 and using less than 16KB. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 1728); + Ctx.rsp.u = BS3_ADDR_STACK - _16K; + Bs3MemCpy(&CtxExpected, &Ctx, sizeof(CtxExpected)); + + StkPtr.pv = Bs3RegCtxGetRspSsAsCurPtr(&Ctx); + //Bs3TestPrintf("Stack=%p rsp=%RX64\n", StkPtr.pv, Ctx.rsp.u); + + /* + * 16-bit tests. + */ + if (BS3_MODE_IS_16BIT_CODE(bMode)) + { + static struct + { + bool fOpSizePfx; + uint16_t cbImm; + FPFNBS3FAR pfnTest; + } const s_aTests[] = + { + { false, 0, bs3CpuBasic2_retf_c16, }, + { true, 0, bs3CpuBasic2_retf_opsize_c16, }, + { false, 32, bs3CpuBasic2_retf_i32_c16, }, + { true, 32, bs3CpuBasic2_retf_i32_opsize_c16, }, + { false,888, bs3CpuBasic2_retf_i888_c16, }, + }; + + static struct + { + bool fRmOrV86; + bool fInterPriv; + int8_t iXcpt; + RTSEL uStartSs; + uint8_t cDstBits; + RTSEL uDstCs; + union /* must use a union here as the compiler won't compile if uint16_t and will mess up fixups for uint32_t. */ + { + uint32_t offDst; + struct + { + NPVOID pv; + uint16_t uHigh; + } s; + }; + RTSEL uDstSs; + uint16_t uErrCd; + } const s_aSubTests[] = + { /* rm/v86, PriChg, Xcpt, uStartSs, => bits uDstCs offDst/pv uDstSs uErrCd */ + { true, false, -1, 0, 16, BS3_SEL_TEXT16, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, 0, 0 }, + { false, false, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_TEXT16 | 0, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R0_SS16 | 0, 0 }, + { false, false, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R0_CS16 | 0, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R0_SS16 | 0, 0 }, + { false, false, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R0_CS16 | 0, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R0_SS16 | 0, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R1_CS16 | 1, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R1_SS16 | 1, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16 | 1, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R1_SS16 | 1, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R1_CS16 | 1, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R1_SS32 | 1, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16 | 1, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R1_SS32 | 1, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R2_CS16 | 2, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R2_SS16 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R2_CS16 | 2, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R2_SS16 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R2_CS16 | 2, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R2_SS32 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R2_CS16 | 2, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R2_SS32 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R3_CS16 | 3, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R3_CS16 | 3, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R3_CS16 | 3, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R3_SS32 | 3, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R3_CS16 | 3, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R3_SS32 | 3, 0 }, + /* conforming stuff */ + { false, false, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R0_CS16_CNF | 0, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R0_SS16 | 0, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R0_CS16_CNF | 1, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R1_SS16 | 1, 0 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R0_CS16_CNF | 1, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R0_SS16 | 1, BS3_SEL_R0_SS16 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R0_CS16_CNF | 2, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R2_SS16 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R0_CS16_CNF | 3, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, false, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R1_CS16_CNF | 0, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R0_SS16 | 0, BS3_SEL_R1_CS16_CNF }, + { false, false, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R1_CS16_CNF | 0, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R1_SS16 | 1, BS3_SEL_R1_CS16_CNF }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R1_CS16_CNF | 1, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R1_SS16 | 1, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R1_CS16_CNF | 2, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R2_SS16 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R1_CS16_CNF | 3, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, false, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R2_CS16_CNF | 0, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R0_SS16 | 0, BS3_SEL_R2_CS16_CNF }, + { false, false, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R2_CS16_CNF | 0, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R1_SS16 | 1, BS3_SEL_R2_CS16_CNF }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R2_CS16_CNF | 1, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R1_SS16 | 1, BS3_SEL_R2_CS16_CNF }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R2_CS16_CNF | 1, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R0_SS16 | 0, BS3_SEL_R2_CS16_CNF }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R2_CS16_CNF | 2, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R2_SS16 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R2_CS16_CNF | 3, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, false, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R3_CS16_CNF | 0, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R0_SS16 | 0, BS3_SEL_R3_CS16_CNF }, + { false, false, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R3_CS16_CNF | 0, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R1_SS16 | 1, BS3_SEL_R3_CS16_CNF }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R3_CS16_CNF | 1, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R1_SS16 | 1, BS3_SEL_R3_CS16_CNF }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R3_CS16_CNF | 1, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R0_SS16 | 0, BS3_SEL_R3_CS16_CNF }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R3_CS16_CNF | 2, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R2_SS16 | 2, BS3_SEL_R3_CS16_CNF }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R3_CS16_CNF | 2, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R3_SS16 | 2, BS3_SEL_R3_CS16_CNF }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R3_CS16_CNF | 3, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R3_SS16 | 3, 0 }, + /* returning to 32-bit code: */ + { false, false, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R0_CS32 | 0, { .offDst = LOW_UD_ADDR }, BS3_SEL_R0_SS16 | 0, 0 }, + { false, false, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R0_CS32 | 0, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R0_SS16 | 0, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32 | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R1_SS16 | 1, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32 | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS16 | 1, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32 | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R1_SS32 | 1, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32 | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS32 | 1, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R2_CS32 | 2, { .offDst = LOW_UD_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R2_CS32 | 2, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R2_SS16 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R2_CS32 | 2, { .offDst = LOW_UD_ADDR }, BS3_SEL_R2_SS32 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R2_CS32 | 2, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R2_SS32 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R3_CS32 | 3, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R3_CS32 | 3, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R3_CS32 | 3, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS32 | 3, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R3_CS32 | 3, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS32 | 3, 0 }, + { false, false, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R0_CS32 | 0, { .offDst = LOW_UD_ADDR }, BS3_SEL_R0_SS32 | 0, 0 }, + { false, false, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R0_CS32 | 0, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R0_SS32 | 0, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32 | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R1_SS32 | 1, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32 | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS32 | 1, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32 | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R1_SS16 | 1, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32 | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS16 | 1, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32 | 2, { .offDst = LOW_UD_ADDR }, BS3_SEL_R2_SS32 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32 | 2, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R2_SS32 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32 | 2, { .offDst = LOW_UD_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32 | 2, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R2_SS16 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32 | 3, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS32 | 3, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32 | 3, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS32 | 3, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32 | 3, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32 | 3, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS16 | 3, 0 }, + /* returning to 32-bit conforming code: */ + { false, false, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R0_CS32_CNF | 0, { .offDst = LOW_UD_ADDR }, BS3_SEL_R0_SS16 | 0, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R0_CS32_CNF | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R1_SS16 | 1, 0 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R0_CS32_CNF | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R0_SS16 | 1, BS3_SEL_R0_SS16 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R0_CS32_CNF | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R0_SS16 | 0, BS3_SEL_R0_SS16 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R0_CS32_CNF | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS16 | 1, BS3_SEL_R3_SS16 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R0_CS32_CNF | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS16 | 3, BS3_SEL_R3_SS16 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R0_CS32_CNF | 2, { .offDst = LOW_UD_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R0_CS32_CNF | 3, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, false, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32_CNF | 0, { .offDst = LOW_UD_ADDR }, BS3_SEL_R0_SS16 | 0, BS3_SEL_R1_CS32_CNF }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32_CNF | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R1_SS16 | 1, 0 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32_CNF | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R0_SS16 | 1, BS3_SEL_R0_SS16 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32_CNF | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R0_SS16 | 0, BS3_SEL_R0_SS16 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32_CNF | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS16 | 1, BS3_SEL_R3_SS16 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32_CNF | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS16 | 3, BS3_SEL_R3_SS16 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32_CNF | 2, { .offDst = LOW_UD_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32_CNF | 3, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, false, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R2_CS32_CNF | 0, { .offDst = LOW_UD_ADDR }, BS3_SEL_R0_SS16 | 0, BS3_SEL_R2_CS32_CNF }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R2_CS32_CNF | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R1_SS16 | 1, BS3_SEL_R2_CS32_CNF }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R2_CS32_CNF | 2, { .offDst = LOW_UD_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R2_CS32_CNF | 3, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, false, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R3_CS32_CNF | 0, { .offDst = LOW_UD_ADDR }, BS3_SEL_R0_SS16 | 0, BS3_SEL_R3_CS32_CNF }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R3_CS32_CNF | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R1_SS16 | 1, BS3_SEL_R3_CS32_CNF }, + { false, true, 42, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R3_CS32_CNF | 2, { .offDst = LOW_UD_ADDR }, BS3_SEL_R2_SS16 | 2, BS3_SEL_R3_CS32_CNF }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R3_CS32_CNF | 3, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + /* returning to 64-bit code or 16-bit when not in long mode: */ + { false, false, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R0_CS64 | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS16 | 0, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R1_CS64 | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS16 | 1, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R2_CS64 | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R3_CS64 | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R1_CS64 | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_DS64 | 1, BS3_SEL_R0_DS64 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R1_CS64 | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_DS64 | 1, 0 }, + { false, false, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R0_CS64 | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS32 | 0, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64 | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS16 | 1, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64 | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS32 | 1, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R2_CS64 | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R2_CS64 | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS32 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64 | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64 | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS32 | 3, 0 }, + { false, true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R2_CS64 | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS32 | 3, BS3_SEL_R2_CS64 }, + { false, true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R2_CS64 | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS32 | 3, BS3_SEL_R2_CS64 }, + { false, true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64 | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS32 | 3, BS3_SEL_R1_SS32 }, + { false, true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64 | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS32 | 2, BS3_SEL_R3_SS32 }, + /* returning to 64-bit code or 16-bit when not in long mode, conforming code variant: */ + { false, false, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R0_CS64_CNF | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS16 | 0, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R0_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS16 | 1, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R0_CS64_CNF | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R0_CS64_CNF | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + + { false, false, 14, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R1_CS64_CNF | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS16 | 0, BS3_SEL_R1_CS64_CNF }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R1_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS16 | 1, 0 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R1_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS16 | 2, BS3_SEL_R1_SS16 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R1_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS16 | 1, BS3_SEL_R2_SS16 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R1_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS16 | 2, BS3_SEL_R2_SS16 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R1_CS64_CNF | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R1_CS64_CNF | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + + { false, false, 14, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R2_CS64_CNF | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS16 | 0, BS3_SEL_R2_CS64_CNF }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R2_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS16 | 1, BS3_SEL_R2_CS64_CNF }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R2_CS64_CNF | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R2_CS64_CNF | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + + { false, false, 14, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R3_CS64_CNF | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS16 | 0, BS3_SEL_R3_CS64_CNF }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R3_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS16 | 1, BS3_SEL_R3_CS64_CNF }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R3_CS64_CNF | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS16 | 2, BS3_SEL_R3_CS64_CNF }, + { false, true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R3_CS64_CNF | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + + /* some additional #GP variations */ /** @todo test all possible exceptions! */ + { false, true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R3_CS16 | 2, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R2_SS16 | 2, BS3_SEL_R3_CS16 }, + { false, true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_TSS32_DF | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_TSS32_DF }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_SPARE_00 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_00 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_SPARE_01 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_01 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_SPARE_02 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_02 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_SPARE_03 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_03 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_SPARE_04 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_04 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_SPARE_05 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_05 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_SPARE_06 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_06 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_SPARE_07 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_07 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_SPARE_08 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_08 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_SPARE_09 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_09 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_SPARE_0a | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_0a }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_SPARE_0b | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_0b }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_SPARE_0c | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_0c }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_SPARE_0d | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_0d }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_SPARE_0e | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_0e }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_SPARE_0f | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_0f }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_SPARE_10 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_10 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_SPARE_11 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_11 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_SPARE_12 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_12 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_SPARE_13 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_13 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_SPARE_14 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_14 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_SPARE_15 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_15 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_SPARE_16 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_16 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_SPARE_17 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_17 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_SPARE_18 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_18 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_SPARE_19 | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_19 }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_SPARE_1a | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_1a }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_SPARE_1b | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_1b }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_SPARE_1c | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_1c }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_SPARE_1d | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_1d }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_SPARE_1e | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_1e }, + { false, true, 14, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_SPARE_1f | 0, { .offDst = 0 }, BS3_SEL_R0_SS16 | 0, BS3_SEL_SPARE_1f }, + }; + + bool const fRmOrV86 = BS3_MODE_IS_RM_OR_V86(bMode); + BS3CPUVENDOR const enmCpuVendor = Bs3GetCpuVendor(); + + Bs3RegSetDr7(X86_DR7_INIT_VAL); + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + { + Bs3RegCtxSetRipCsFromLnkPtr(&Ctx, s_aTests[iTest].pfnTest); + + for (iSubTest = 0; iSubTest < RT_ELEMENTS(s_aSubTests); iSubTest++) + { + g_usBs3TestStep = (iTest << 12) | (iSubTest << 4); + if ( s_aSubTests[iSubTest].fRmOrV86 == fRmOrV86 + && (s_aSubTests[iSubTest].offDst <= UINT16_MAX || s_aTests[iTest].fOpSizePfx)) + { + uint16_t const cbFrmDisp = s_aSubTests[iSubTest].fInterPriv ? iSubTest % 7 : 0; + uint16_t const cbStkItem = s_aTests[iTest].fOpSizePfx ? 4 : 2; + uint16_t const cbFrame = (s_aSubTests[iSubTest].fInterPriv ? 4 : 2) * cbStkItem; + uint32_t const uFlatDst = Bs3SelFar32ToFlat32(s_aSubTests[iSubTest].offDst, s_aSubTests[iSubTest].uDstCs) + + (s_aSubTests[iSubTest].cDstBits == 64 && !BS3_MODE_IS_64BIT_SYS(bMode)); + RTSEL const uDstSs = s_aSubTests[iSubTest].uDstSs; + uint64_t uDstRspExpect, uDstRspPush; + uint16_t cErrors; + + Ctx.ss = s_aSubTests[iSubTest].uStartSs; + if (Ctx.ss != BS3_SEL_R0_SS32) + Ctx.rsp.u32 |= UINT32_C(0xfffe0000); + else + Ctx.rsp.u32 &= UINT16_MAX; + uDstRspExpect = uDstRspPush = Ctx.rsp.u + s_aTests[iTest].cbImm + cbFrame + cbFrmDisp; + if (s_aSubTests[iSubTest].fInterPriv) + { + if (s_aTests[iTest].fOpSizePfx) + uDstRspPush = (uDstRspPush & UINT16_MAX) | UINT32_C(0xacdc0000); + if ( uDstSs == (BS3_SEL_R1_SS32 | 1) + || uDstSs == (BS3_SEL_R2_SS32 | 2) + || uDstSs == (BS3_SEL_R3_SS32 | 3) + || (s_aSubTests[iSubTest].cDstBits == 64 && BS3_MODE_IS_64BIT_SYS(bMode))) + { + if (s_aTests[iTest].fOpSizePfx) + uDstRspExpect = uDstRspPush; + else + uDstRspExpect &= UINT16_MAX; + } + } + + CtxExpected.bCpl = Ctx.bCpl; + CtxExpected.cs = Ctx.cs; + CtxExpected.ss = Ctx.ss; + CtxExpected.ds = Ctx.ds; + CtxExpected.es = Ctx.es; + CtxExpected.fs = Ctx.fs; + CtxExpected.gs = Ctx.gs; + CtxExpected.rip.u = Ctx.rip.u; + CtxExpected.rsp.u = Ctx.rsp.u; + CtxExpected.rax.u = Ctx.rax.u; + if (s_aSubTests[iSubTest].iXcpt < 0) + { + CtxExpected.cs = s_aSubTests[iSubTest].uDstCs; + CtxExpected.rip.u = s_aSubTests[iSubTest].offDst; + if (s_aSubTests[iSubTest].cDstBits == 64 && !BS3_MODE_IS_64BIT_SYS(bMode)) + { + CtxExpected.rip.u += 1; + CtxExpected.rax.au8[0] = CtxExpected.rflags.u16 & X86_EFL_CF ? 0xff : 0; + } + CtxExpected.ss = uDstSs; + CtxExpected.rsp.u = uDstRspExpect; + if (s_aSubTests[iSubTest].fInterPriv) + { + uint16_t BS3_FAR *puSel = &CtxExpected.ds; /* ASSUME member order! */ + unsigned cSels = 4; + CtxExpected.bCpl = CtxExpected.ss & X86_SEL_RPL; + while (cSels-- > 0) + { + uint16_t uSel = *puSel; + if ( (uSel & X86_SEL_MASK_OFF_RPL) + && Bs3Gdt[uSel >> X86_SEL_SHIFT].Gen.u2Dpl < CtxExpected.bCpl + && (Bs3Gdt[uSel >> X86_SEL_SHIFT].Gen.u4Type & (X86_SEL_TYPE_CODE | X86_SEL_TYPE_CONF)) + != (X86_SEL_TYPE_CODE | X86_SEL_TYPE_CONF)) + *puSel = 0; + puSel++; + } + CtxExpected.rsp.u += s_aTests[iTest].cbImm; /* arguments are dropped from both stacks. */ + } + } + g_uBs3TrapEipHint = CtxExpected.rip.u32; + //Bs3TestPrintf("cs:rip=%04RX16:%04RX64 -> %04RX16:%04RX64\n", Ctx.cs, Ctx.rip.u, CtxExpected.cs, CtxExpected.rip.u); + //Bs3TestPrintf("ss:rsp=%04RX16:%04RX64 -> %04RX16:%04RX64 [pushed %#RX64]\n", Ctx.ss, Ctx.rsp.u, CtxExpected.ss, CtxExpected.rsp.u, uDstRspPush); + bs3CpuBasic2_retf_PrepStack(StkPtr, cbStkItem, s_aSubTests[iSubTest].uDstCs, s_aSubTests[iSubTest].offDst, + s_aSubTests[iSubTest].fInterPriv, s_aTests[iTest].cbImm, + s_aSubTests[iSubTest].uDstSs, uDstRspPush); + //Bs3TestPrintf("%p: %04RX16 %04RX16 %04RX16 %04RX16\n", StkPtr.pu16, StkPtr.pu16[0], StkPtr.pu16[1], StkPtr.pu16[2], StkPtr.pu16[3]); + //Bs3TestPrintf("%.48Rhxd\n", StkPtr.pu16); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (s_aSubTests[iSubTest].iXcpt < 0) + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxExpected); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, s_aSubTests[iSubTest].uErrCd); + g_usBs3TestStep++; /* 1 */ + + /* Bad hw bp: Setup DR0-3 but use invalid length encodings (non-byte) */ + //Bs3TestPrintf("hw bp: bad len\n"); + Bs3RegSetDr0(uFlatDst); + Bs3RegSetDr1(uFlatDst); + Bs3RegSetDr2(uFlatDst); + Bs3RegSetDr3(uFlatDst); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + Bs3RegSetDr7(X86_DR7_INIT_VAL + | X86_DR7_RW(0, X86_DR7_RW_EO) | X86_DR7_LEN(1, X86_DR7_LEN_WORD) | X86_DR7_L_G(1) + | X86_DR7_RW(2, X86_DR7_RW_EO) | X86_DR7_LEN(2, X86_DR7_LEN_DWORD) | X86_DR7_L_G(2) + | ( BS3_MODE_IS_64BIT_SYS(bMode) + ? X86_DR7_RW(3, X86_DR7_RW_EO) | X86_DR7_LEN(3, X86_DR7_LEN_QWORD) | X86_DR7_L_G(3) : 0) ); + bs3CpuBasic2_retf_PrepStack(StkPtr, cbStkItem, s_aSubTests[iSubTest].uDstCs, s_aSubTests[iSubTest].offDst, + s_aSubTests[iSubTest].fInterPriv, s_aTests[iTest].cbImm, + s_aSubTests[iSubTest].uDstSs, uDstRspPush); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + Bs3RegSetDr7(X86_DR7_INIT_VAL); + if (s_aSubTests[iSubTest].iXcpt < 0) + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxExpected); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, s_aSubTests[iSubTest].uErrCd); + bs3CpuBasic2_CheckDr6InitVal(); + g_usBs3TestStep++; /* 2 */ + + /* Bad hw bp: setup DR0-3 but don't enable them */ + //Bs3TestPrintf("hw bp: disabled\n"); + //Bs3RegSetDr0(uFlatDst); + //Bs3RegSetDr1(uFlatDst); + //Bs3RegSetDr2(uFlatDst); + //Bs3RegSetDr3(uFlatDst); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + Bs3RegSetDr7(X86_DR7_INIT_VAL); + bs3CpuBasic2_retf_PrepStack(StkPtr, cbStkItem, s_aSubTests[iSubTest].uDstCs, s_aSubTests[iSubTest].offDst, + s_aSubTests[iSubTest].fInterPriv, s_aTests[iTest].cbImm, + s_aSubTests[iSubTest].uDstSs, uDstRspPush); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + Bs3RegSetDr7(X86_DR7_INIT_VAL); + if (s_aSubTests[iSubTest].iXcpt < 0) + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxExpected); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, s_aSubTests[iSubTest].uErrCd); + bs3CpuBasic2_CheckDr6InitVal(); + g_usBs3TestStep++; /* 3 */ + + /* Bad hw bp: Points at 2nd byte in the UD2. Docs says it only works when pointing at first byte. */ + //Bs3TestPrintf("hw bp: byte 2\n"); + Bs3RegSetDr0(uFlatDst + 1); + Bs3RegSetDr1(uFlatDst + 1); + //Bs3RegSetDr2(uFlatDst); + //Bs3RegSetDr3(uFlatDst); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + Bs3RegSetDr7(X86_DR7_INIT_VAL + | X86_DR7_RW(0, X86_DR7_RW_EO) | X86_DR7_LEN(0, X86_DR7_LEN_BYTE) | X86_DR7_L_G(0) + | X86_DR7_RW(1, X86_DR7_RW_EO) | X86_DR7_LEN(1, X86_DR7_LEN_BYTE) | X86_DR7_L_G(1)); + bs3CpuBasic2_retf_PrepStack(StkPtr, cbStkItem, s_aSubTests[iSubTest].uDstCs, s_aSubTests[iSubTest].offDst, + s_aSubTests[iSubTest].fInterPriv, s_aTests[iTest].cbImm, + s_aSubTests[iSubTest].uDstSs, uDstRspPush); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + Bs3RegSetDr7(X86_DR7_INIT_VAL); + if (s_aSubTests[iSubTest].iXcpt < 0) + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxExpected); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, s_aSubTests[iSubTest].uErrCd); + bs3CpuBasic2_CheckDr6InitVal(); + g_usBs3TestStep++; /* 4 */ + + /* Again with two correctly hardware breakpoints and a disabled one that just matches the address: */ + //Bs3TestPrintf("bp 1 + 3...\n"); + Bs3RegSetDr0(uFlatDst); + Bs3RegSetDr1(uFlatDst); + Bs3RegSetDr2(0); + Bs3RegSetDr3(uFlatDst); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + Bs3RegSetDr7(X86_DR7_INIT_VAL + | X86_DR7_RW(1, X86_DR7_RW_EO) | X86_DR7_LEN(1, X86_DR7_LEN_BYTE) | X86_DR7_L_G(1) + | X86_DR7_RW(3, X86_DR7_RW_EO) | X86_DR7_LEN(3, X86_DR7_LEN_BYTE) | X86_DR7_L_G(3) ); + bs3CpuBasic2_retf_PrepStack(StkPtr, cbStkItem, s_aSubTests[iSubTest].uDstCs, s_aSubTests[iSubTest].offDst, + s_aSubTests[iSubTest].fInterPriv, s_aTests[iTest].cbImm, + s_aSubTests[iSubTest].uDstSs, uDstRspPush); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + Bs3RegSetDr7(X86_DR7_INIT_VAL); + if (s_aSubTests[iSubTest].iXcpt < 0) + bs3CpuBasic2_CompareDbCtx(&TrapCtx, &CtxExpected, + enmCpuVendor == BS3CPUVENDOR_AMD ? X86_DR6_B1 | X86_DR6_B3 /* 3990x */ + : X86_DR6_B0 | X86_DR6_B1 | X86_DR6_B3); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, s_aSubTests[iSubTest].uErrCd); + g_usBs3TestStep++; /* 5 */ + + /* Again with a single locally enabled breakpoint. */ + //Bs3TestPrintf("bp 0/l...\n"); + Bs3RegSetDr0(uFlatDst); + Bs3RegSetDr1(0); + Bs3RegSetDr2(0); + Bs3RegSetDr3(0); + Bs3RegSetDr6(X86_DR6_INIT_VAL | X86_DR6_B1 | X86_DR6_B2 | X86_DR6_B3 | X86_DR6_BS); + Bs3RegSetDr7(X86_DR7_INIT_VAL + | X86_DR7_RW(0, X86_DR7_RW_EO) | X86_DR7_LEN(0, X86_DR7_LEN_BYTE) | X86_DR7_L(0)); + bs3CpuBasic2_retf_PrepStack(StkPtr, cbStkItem, s_aSubTests[iSubTest].uDstCs, s_aSubTests[iSubTest].offDst, + s_aSubTests[iSubTest].fInterPriv, s_aTests[iTest].cbImm, + s_aSubTests[iSubTest].uDstSs, uDstRspPush); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + Bs3RegSetDr7(X86_DR7_INIT_VAL); + if (s_aSubTests[iSubTest].iXcpt < 0) + bs3CpuBasic2_CompareDbCtx(&TrapCtx, &CtxExpected, X86_DR6_B0 | X86_DR6_BS); /* B0-B3 set, BS preserved */ + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, s_aSubTests[iSubTest].uErrCd); + g_usBs3TestStep++; /* 6 */ + + /* Again with a single globally enabled breakpoint and serveral other types of breakpoints + configured but not enabled. */ + //Bs3TestPrintf("bp 2/g+...\n"); + cErrors = Bs3TestSubErrorCount(); + Bs3RegSetDr0(uFlatDst); + Bs3RegSetDr1(uFlatDst); + Bs3RegSetDr2(uFlatDst); + Bs3RegSetDr3(uFlatDst); + Bs3RegSetDr6(X86_DR6_INIT_VAL | X86_DR6_BS | X86_DR6_BD | X86_DR6_BT | X86_DR6_B2); + Bs3RegSetDr7(X86_DR7_INIT_VAL + | X86_DR7_RW(0, X86_DR7_RW_RW) | X86_DR7_LEN(0, X86_DR7_LEN_BYTE) + | X86_DR7_RW(1, X86_DR7_RW_RW) | X86_DR7_LEN(1, X86_DR7_LEN_BYTE) | X86_DR7_L_G(1) + | X86_DR7_RW(2, X86_DR7_RW_EO) | X86_DR7_LEN(2, X86_DR7_LEN_BYTE) | X86_DR7_G(2) + | X86_DR7_RW(3, X86_DR7_RW_WO) | X86_DR7_LEN(3, X86_DR7_LEN_BYTE) | X86_DR7_G(3) + ); + bs3CpuBasic2_retf_PrepStack(StkPtr, cbStkItem, s_aSubTests[iSubTest].uDstCs, s_aSubTests[iSubTest].offDst, + s_aSubTests[iSubTest].fInterPriv, s_aTests[iTest].cbImm, + s_aSubTests[iSubTest].uDstSs, uDstRspPush); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + Bs3RegSetDr7(X86_DR7_INIT_VAL); + if (s_aSubTests[iSubTest].iXcpt < 0) + bs3CpuBasic2_CompareDbCtx(&TrapCtx, &CtxExpected, X86_DR6_B2 | X86_DR6_BS | X86_DR6_BD | X86_DR6_BT); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, s_aSubTests[iSubTest].uErrCd); + g_usBs3TestStep++; /* 7 */ + + /* Now resume it with lots of execution breakpoints configured. */ + if (s_aSubTests[iSubTest].iXcpt < 0 && Bs3TestSubErrorCount() == cErrors) + { + Bs3MemCpy(&Ctx2, &TrapCtx.Ctx, sizeof(Ctx2)); + Ctx2.rflags.u32 |= X86_EFL_RF; + //Bs3TestPrintf("bp 3/g+rf %04RX16:%04RX64 efl=%RX32 ds=%04RX16...\n", Ctx2.cs, Ctx2.rip.u, Ctx2.rflags.u32, Ctx2.ds); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + Bs3RegSetDr7(X86_DR7_INIT_VAL + | X86_DR7_RW(0, X86_DR7_RW_EO) | X86_DR7_LEN(0, X86_DR7_LEN_BYTE) + | X86_DR7_RW(1, X86_DR7_RW_EO) | X86_DR7_LEN(1, X86_DR7_LEN_BYTE) | X86_DR7_L_G(1) + | X86_DR7_RW(2, X86_DR7_RW_EO) | X86_DR7_LEN(2, X86_DR7_LEN_BYTE) | X86_DR7_G(2) + | X86_DR7_RW(3, X86_DR7_RW_EO) | X86_DR7_LEN(3, X86_DR7_LEN_BYTE) | X86_DR7_G(3) + ); + Bs3TrapSetJmpAndRestore(&Ctx2, &TrapCtx); + Bs3RegSetDr7(X86_DR7_INIT_VAL); + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxExpected); + bs3CpuBasic2_CheckDr6InitVal(); + } + g_usBs3TestStep++; /* 8 */ + + /* Now do single stepping: */ + //Bs3TestPrintf("stepping...\n"); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + Ctx.rflags.u16 |= X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + if (s_aSubTests[iSubTest].iXcpt < 0 && s_aSubTests[iSubTest].cDstBits == 64 && !BS3_MODE_IS_64BIT_SYS(bMode)) + { + CtxExpected.rip.u -= 1; + CtxExpected.rax.u = Ctx.rax.u; + } + bs3CpuBasic2_retf_PrepStack(StkPtr, cbStkItem, s_aSubTests[iSubTest].uDstCs, s_aSubTests[iSubTest].offDst, + s_aSubTests[iSubTest].fInterPriv, s_aTests[iTest].cbImm, + s_aSubTests[iSubTest].uDstSs, uDstRspPush); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (s_aSubTests[iSubTest].iXcpt < 0) + bs3CpuBasic2_CompareDbCtx(&TrapCtx, &CtxExpected, X86_DR6_BS); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, s_aSubTests[iSubTest].uErrCd); + Ctx.rflags.u16 &= ~X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + g_usBs3TestStep++; /* 9 */ + + /* Single step with B0-B3 set to check that they're not preserved + and with BD & BT to check that they are (checked on Intel 6700K): */ + //Bs3TestPrintf("stepping b0-b3+bd+bt=1...\n"); + Bs3RegSetDr6(X86_DR6_INIT_VAL | X86_DR6_B_MASK | X86_DR6_BD | X86_DR6_BT); + Ctx.rflags.u16 |= X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + bs3CpuBasic2_retf_PrepStack(StkPtr, cbStkItem, s_aSubTests[iSubTest].uDstCs, s_aSubTests[iSubTest].offDst, + s_aSubTests[iSubTest].fInterPriv, s_aTests[iTest].cbImm, + s_aSubTests[iSubTest].uDstSs, uDstRspPush); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (s_aSubTests[iSubTest].iXcpt < 0) + bs3CpuBasic2_CompareDbCtx(&TrapCtx, &CtxExpected, X86_DR6_BS | X86_DR6_BD | X86_DR6_BT); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, s_aSubTests[iSubTest].uErrCd); + Ctx.rflags.u16 &= ~X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + g_usBs3TestStep++; /* 10 */ + + } + } + } + } + /* + * 32-bit tests. + */ + else if (BS3_MODE_IS_32BIT_CODE(bMode)) + { + static struct + { + bool fOpSizePfx; + uint16_t cbImm; + FPFNBS3FAR pfnTest; + } const s_aTests[] = + { + { false, 0, bs3CpuBasic2_retf_c32, }, + { true, 0, bs3CpuBasic2_retf_opsize_c32, }, + { false, 32, bs3CpuBasic2_retf_i32_c32, }, + { true, 32, bs3CpuBasic2_retf_i32_opsize_c32, }, + { false,888, bs3CpuBasic2_retf_i888_c32, }, + }; + + static struct + { + bool fInterPriv; + int8_t iXcpt; + RTSEL uStartSs; + uint8_t cDstBits; + RTSEL uDstCs; + union /* must use a union here as the compiler won't compile if uint16_t and will mess up fixups for uint32_t. */ + { + uint32_t offDst; + struct + { + NPVOID pv; + uint16_t uHigh; + } s; + }; + RTSEL uDstSs; + uint16_t uErrCd; + } const s_aSubTests[] = + { /* PriChg, Xcpt, uStartSs, => bits uDstCs offDst/pv uDstSs uErrCd */ + { false, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R0_CS32 | 0, { .offDst = LOW_UD_ADDR }, BS3_SEL_R0_SS32 | 0, 0 }, + { false, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R0_CS32 | 0, { .offDst = LOW_UD_ADDR }, BS3_SEL_R0_SS32 | 0, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32 | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32 | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32 | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32 | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32 | 2, { .offDst = LOW_UD_ADDR }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R2_CS32 | 2, { .offDst = LOW_UD_ADDR }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32 | 2, { .offDst = LOW_UD_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R2_CS32 | 2, { .offDst = LOW_UD_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32 | 3, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS32 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R3_CS32 | 3, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS32 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32 | 3, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R3_CS32 | 3, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + /* same with 32-bit wide target addresses: */ + { false, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R0_CS32 | 0, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R0_SS32 | 0, 0 }, + { false, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R0_CS32 | 0, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R0_SS32 | 0, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32 | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32 | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32 | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32 | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32 | 2, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R2_CS32 | 2, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32 | 2, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R2_CS32 | 2, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32 | 3, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS32 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R3_CS32 | 3, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS32 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32 | 3, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS16 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R3_CS32 | 3, { .s = {(NPVOID)bs3CpuBasic2_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS16 | 3, 0 }, + /* conforming stuff */ + { false, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R0_CS32_CNF | 0, { .offDst = LOW_UD_ADDR }, BS3_SEL_R0_SS32 | 0, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R0_CS32_CNF | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R0_CS32_CNF | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R0_SS32 | 1, BS3_SEL_R0_SS32 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R0_CS32_CNF | 2, { .offDst = LOW_UD_ADDR }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R0_CS32_CNF | 3, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS32 | 3, 0 }, + { false, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32_CNF | 0, { .offDst = LOW_UD_ADDR }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R1_CS32_CNF }, + { false, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32_CNF | 0, { .offDst = LOW_UD_ADDR }, BS3_SEL_R1_SS32 | 1, BS3_SEL_R1_CS32_CNF }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32_CNF | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32_CNF | 2, { .offDst = LOW_UD_ADDR }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32_CNF | 3, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS32 | 3, 0 }, + { false, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32_CNF | 0, { .offDst = LOW_UD_ADDR }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R2_CS32_CNF }, + { false, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32_CNF | 0, { .offDst = LOW_UD_ADDR }, BS3_SEL_R1_SS32 | 1, BS3_SEL_R2_CS32_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32_CNF | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R1_SS32 | 1, BS3_SEL_R2_CS32_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32_CNF | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R2_CS32_CNF }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32_CNF | 2, { .offDst = LOW_UD_ADDR }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32_CNF | 3, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS32 | 3, 0 }, + { false, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32_CNF | 0, { .offDst = LOW_UD_ADDR }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R3_CS32_CNF }, + { false, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32_CNF | 0, { .offDst = LOW_UD_ADDR }, BS3_SEL_R1_SS32 | 1, BS3_SEL_R3_CS32_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32_CNF | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R1_SS32 | 1, BS3_SEL_R3_CS32_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32_CNF | 1, { .offDst = LOW_UD_ADDR }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R3_CS32_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32_CNF | 2, { .offDst = LOW_UD_ADDR }, BS3_SEL_R2_SS32 | 2, BS3_SEL_R3_CS32_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32_CNF | 2, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS32 | 2, BS3_SEL_R3_CS32_CNF }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32_CNF | 3, { .offDst = LOW_UD_ADDR }, BS3_SEL_R3_SS32 | 3, 0 }, + /* returning to 16-bit code: */ + { false, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R0_CS16 | 0, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R0_SS32 | 0, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16 | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16 | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R2_CS16 | 2, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R2_CS16 | 2, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R3_CS16 | 3, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R3_SS32 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R3_CS16 | 3, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R0_CS16 | 0, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R0_SS16 | 0, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R1_CS16 | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R1_CS16 | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R2_CS16 | 2, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R2_CS16 | 2, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R3_CS16 | 3, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R3_SS16 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R3_CS16 | 3, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R3_SS32 | 3, 0 }, + /* returning to 16-bit conforming code: */ + { false, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R0_CS16_CNF | 0, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R0_SS32 | 0, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R0_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R0_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R0_SS32 | 1, BS3_SEL_R0_SS32 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R0_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R0_SS32 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R0_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R3_SS32 | 1, BS3_SEL_R3_SS32 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R0_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R3_SS32 | 3, BS3_SEL_R3_SS32 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R0_CS16_CNF | 2, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R0_CS16_CNF | 3, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R3_SS32 | 3, 0 }, + { false, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16_CNF | 0, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R1_CS16_CNF }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R0_SS32 | 1, BS3_SEL_R0_SS32 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R0_SS32 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R3_SS32 | 1, BS3_SEL_R3_SS32 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R3_SS32 | 3, BS3_SEL_R3_SS32 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16_CNF | 2, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16_CNF | 3, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R3_SS32 | 3, 0 }, + { false, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R2_CS16_CNF | 0, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R2_CS16_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R2_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R1_SS32 | 1, BS3_SEL_R2_CS16_CNF }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R2_CS16_CNF | 2, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R2_CS16_CNF | 3, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R3_CS16_CNF | 0, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R3_CS16_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R3_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R1_SS32 | 1, BS3_SEL_R3_CS16_CNF }, + { true, 42, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R3_CS16_CNF | 2, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R2_SS32 | 2, BS3_SEL_R3_CS16_CNF }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R3_CS16_CNF | 3, { .s = {(NPVOID)bs3CpuBasic2_ud2, 0 } }, BS3_SEL_R3_SS32 | 3, 0 }, + /* returning to 64-bit code or 16-bit when not in long mode: */ + { false, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R0_CS64 | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS16 | 0, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64 | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R2_CS64 | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64 | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64 | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_DS64 | 1, BS3_SEL_R0_DS64 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64 | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_DS64 | 1, 0 }, + { false, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R0_CS64 | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS32 | 0, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R1_CS64 | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R1_CS64 | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R2_CS64 | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R2_CS64 | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R3_CS64 | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R3_CS64 | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS32 | 3, 0 }, + { true, 14, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R2_CS64 | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS32 | 3, BS3_SEL_R2_CS64 }, + { true, 14, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R2_CS64 | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS32 | 3, BS3_SEL_R2_CS64 }, + { true, 14, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R3_CS64 | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS32 | 3, BS3_SEL_R1_SS32 }, + { true, 14, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R3_CS64 | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS32 | 2, BS3_SEL_R3_SS32 }, + /* returning to 64-bit code or 16-bit when not in long mode, conforming code variant: */ + { false, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R0_CS64_CNF | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS16 | 0, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R0_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R0_CS64_CNF | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R0_CS64_CNF | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + + { false, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64_CNF | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS16 | 0, BS3_SEL_R1_CS64_CNF }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS16 | 2, BS3_SEL_R1_SS16 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS16 | 1, BS3_SEL_R2_SS16 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS16 | 2, BS3_SEL_R2_SS16 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64_CNF | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64_CNF | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + + { false, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R2_CS64_CNF | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS16 | 0, BS3_SEL_R2_CS64_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R2_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS16 | 1, BS3_SEL_R2_CS64_CNF }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R2_CS64_CNF | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R2_CS64_CNF | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + + { false, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64_CNF | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS16 | 0, BS3_SEL_R3_CS64_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS16 | 1, BS3_SEL_R3_CS64_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64_CNF | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS16 | 2, BS3_SEL_R3_CS64_CNF }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64_CNF | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + + /* some additional #GP variations */ /** @todo test all possible exceptions! */ + { true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R3_CS16 | 2, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R2_SS16 | 2, BS3_SEL_R3_CS16 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_SPARE_00 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_00 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_SPARE_01 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_01 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_SPARE_02 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_02 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_SPARE_03 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_03 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_SPARE_04 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_04 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_SPARE_05 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_05 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_SPARE_06 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_06 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_SPARE_07 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_07 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_SPARE_08 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_08 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_SPARE_09 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_09 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_SPARE_0a | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_0a }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_SPARE_0b | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_0b }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_SPARE_0c | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_0c }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_SPARE_0d | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_0d }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_SPARE_0e | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_0e }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_SPARE_0f | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_0f }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_SPARE_10 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_10 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_SPARE_11 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_11 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_SPARE_12 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_12 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_SPARE_13 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_13 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_SPARE_14 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_14 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_SPARE_15 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_15 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_SPARE_16 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_16 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_SPARE_17 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_17 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_SPARE_18 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_18 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_SPARE_19 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_19 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_SPARE_1a | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_1a }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_SPARE_1b | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_1b }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_SPARE_1c | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_1c }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_SPARE_1d | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_1d }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_SPARE_1e | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_1e }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_SPARE_1f | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_1f }, + }; + + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + { + Bs3RegCtxSetRipCsFromLnkPtr(&Ctx, s_aTests[iTest].pfnTest); + //Bs3TestPrintf("-------------- #%u: cs:eip=%04RX16:%08RX64 imm=%u%s\n", + // iTest, Ctx.cs, Ctx.rip.u, s_aTests[iTest].cbImm, s_aTests[iTest].fOpSizePfx ? " o16" : ""); + + for (iSubTest = 0; iSubTest < RT_ELEMENTS(s_aSubTests); iSubTest++) + { + g_usBs3TestStep = (iTest << 12) | (iSubTest << 1); + if (!s_aTests[iTest].fOpSizePfx || s_aSubTests[iSubTest].offDst <= UINT16_MAX) + { + uint16_t const cbFrmDisp = s_aSubTests[iSubTest].fInterPriv ? iSubTest % 7 : 0; + uint16_t const cbStkItem = s_aTests[iTest].fOpSizePfx ? 2 : 4; + uint16_t const cbFrame = (s_aSubTests[iSubTest].fInterPriv ? 4 : 2) * cbStkItem; + RTSEL const uDstSs = s_aSubTests[iSubTest].uDstSs; + uint64_t uDstRspExpect, uDstRspPush; + //Bs3TestPrintf(" #%u: %s %d %#04RX16 -> %u %#04RX16:%#04RX32 %#04RX16 %#RX16\n", iSubTest, s_aSubTests[iSubTest].fInterPriv ? "priv" : "same", s_aSubTests[iSubTest].iXcpt, s_aSubTests[iSubTest].uStartSs, + // s_aSubTests[iSubTest].cDstBits, s_aSubTests[iSubTest].uDstCs, s_aSubTests[iSubTest].offDst, s_aSubTests[iSubTest].uDstSs, s_aSubTests[iSubTest].uErrCd); + + Ctx.ss = s_aSubTests[iSubTest].uStartSs; + if (Ctx.ss != BS3_SEL_R0_SS32) + Ctx.rsp.u32 |= UINT32_C(0xfffe0000); + else + Ctx.rsp.u32 &= UINT16_MAX; + uDstRspExpect = uDstRspPush = Ctx.rsp.u + s_aTests[iTest].cbImm + cbFrame + cbFrmDisp; + if (s_aSubTests[iSubTest].fInterPriv) + { + if (!s_aTests[iTest].fOpSizePfx) + uDstRspPush = (uDstRspPush & UINT16_MAX) | UINT32_C(0xacdc0000); + if ( uDstSs == (BS3_SEL_R1_SS32 | 1) + || uDstSs == (BS3_SEL_R2_SS32 | 2) + || uDstSs == (BS3_SEL_R3_SS32 | 3) + || (s_aSubTests[iSubTest].cDstBits == 64 && BS3_MODE_IS_64BIT_SYS(bMode))) + { + if (!s_aTests[iTest].fOpSizePfx) + uDstRspExpect = uDstRspPush; + else + uDstRspExpect &= UINT16_MAX; + } + } + + CtxExpected.bCpl = Ctx.bCpl; + CtxExpected.cs = Ctx.cs; + CtxExpected.ss = Ctx.ss; + CtxExpected.ds = Ctx.ds; + CtxExpected.es = Ctx.es; + CtxExpected.fs = Ctx.fs; + CtxExpected.gs = Ctx.gs; + CtxExpected.rip.u = Ctx.rip.u; + CtxExpected.rsp.u = Ctx.rsp.u; + CtxExpected.rax.u = Ctx.rax.u; + if (s_aSubTests[iSubTest].iXcpt < 0) + { + CtxExpected.cs = s_aSubTests[iSubTest].uDstCs; + CtxExpected.rip.u = s_aSubTests[iSubTest].offDst; + if (s_aSubTests[iSubTest].cDstBits == 64 && !BS3_MODE_IS_64BIT_SYS(bMode)) + { + CtxExpected.rip.u += 1; + CtxExpected.rax.au8[0] = CtxExpected.rflags.u16 & X86_EFL_CF ? 0xff : 0; + } + CtxExpected.ss = uDstSs; + CtxExpected.rsp.u = uDstRspExpect; + if (s_aSubTests[iSubTest].fInterPriv) + { + uint16_t BS3_FAR *puSel = &CtxExpected.ds; /* ASSUME member order! */ + unsigned cSels = 4; + CtxExpected.bCpl = CtxExpected.ss & X86_SEL_RPL; + while (cSels-- > 0) + { + uint16_t uSel = *puSel; + if ( (uSel & X86_SEL_MASK_OFF_RPL) + && Bs3Gdt[uSel >> X86_SEL_SHIFT].Gen.u2Dpl < CtxExpected.bCpl + && (Bs3Gdt[uSel >> X86_SEL_SHIFT].Gen.u4Type & (X86_SEL_TYPE_CODE | X86_SEL_TYPE_CONF)) + != (X86_SEL_TYPE_CODE | X86_SEL_TYPE_CONF)) + *puSel = 0; + puSel++; + } + CtxExpected.rsp.u += s_aTests[iTest].cbImm; /* arguments are dropped from both stacks. */ + } + } + g_uBs3TrapEipHint = CtxExpected.rip.u32; + //Bs3TestPrintf("ss:rsp=%04RX16:%04RX64 -> %04RX16:%04RX64 [pushed %#RX64]; %04RX16:%04RX64\n",Ctx.ss, Ctx.rsp.u, + // CtxExpected.ss, CtxExpected.rsp.u, uDstRspPush, CtxExpected.cs, CtxExpected.rip.u); + bs3CpuBasic2_retf_PrepStack(StkPtr, cbStkItem, s_aSubTests[iSubTest].uDstCs, s_aSubTests[iSubTest].offDst, + s_aSubTests[iSubTest].fInterPriv, s_aTests[iTest].cbImm, + s_aSubTests[iSubTest].uDstSs, uDstRspPush); + //Bs3TestPrintf("%p: %04RX16 %04RX16 %04RX16 %04RX16\n", StkPtr.pu16, StkPtr.pu16[0], StkPtr.pu16[1], StkPtr.pu16[2], StkPtr.pu16[3]); + //Bs3TestPrintf("%.48Rhxd\n", StkPtr.pu16); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (s_aSubTests[iSubTest].iXcpt < 0) + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxExpected); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, s_aSubTests[iSubTest].uErrCd); + g_usBs3TestStep++; + + /* Again single stepping: */ + //Bs3TestPrintf("stepping...\n"); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + Ctx.rflags.u16 |= X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + if (s_aSubTests[iSubTest].iXcpt < 0 && s_aSubTests[iSubTest].cDstBits == 64 && !BS3_MODE_IS_64BIT_SYS(bMode)) + { + CtxExpected.rip.u -= 1; + CtxExpected.rax.u = Ctx.rax.u; + } + bs3CpuBasic2_retf_PrepStack(StkPtr, cbStkItem, s_aSubTests[iSubTest].uDstCs, s_aSubTests[iSubTest].offDst, + s_aSubTests[iSubTest].fInterPriv, s_aTests[iTest].cbImm, + s_aSubTests[iSubTest].uDstSs, uDstRspPush); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (s_aSubTests[iSubTest].iXcpt < 0) + bs3CpuBasic2_CompareDbCtx(&TrapCtx, &CtxExpected, X86_DR6_BS); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, s_aSubTests[iSubTest].uErrCd); + Ctx.rflags.u16 &= ~X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + g_usBs3TestStep++; + } + } + } + } + /* + * 64-bit tests. + */ + else if (BS3_MODE_IS_64BIT_CODE(bMode)) + { + static struct + { + uint8_t fOpSizePfx; /**< 0: none, 1: 066h, 2: REX.W; Effective op size prefix. */ + uint16_t cbImm; + FPFNBS3FAR pfnTest; + } const s_aTests[] = + { + { 0, 0, bs3CpuBasic2_retf_c64, }, + { 1, 0, bs3CpuBasic2_retf_opsize_c64, }, + { 0, 32, bs3CpuBasic2_retf_i32_c64, }, + { 1, 32, bs3CpuBasic2_retf_i32_opsize_c64, }, + { 2, 0, bs3CpuBasic2_retf_rexw_c64, }, + { 2, 0, bs3CpuBasic2_retf_opsize_rexw_c64, }, + { 1, 0, bs3CpuBasic2_retf_rexw_opsize_c64, }, + { 2, 24, bs3CpuBasic2_retf_i24_rexw_c64, }, + { 2, 24, bs3CpuBasic2_retf_i24_opsize_rexw_c64, }, + { 1, 24, bs3CpuBasic2_retf_i24_rexw_opsize_c64, }, + { 0,888, bs3CpuBasic2_retf_i888_c64, }, + }; + + static struct + { + bool fInterPriv; + int8_t iXcpt; + RTSEL uStartSs; + uint8_t cDstBits; + RTSEL uDstCs; + union /* must use a union here as the compiler won't compile if uint16_t and will mess up fixups for uint32_t. */ + { + uint32_t offDst; + struct + { + NPVOID pv; + uint16_t uHigh; + } s; + }; + RTSEL uDstSs; + uint16_t uErrCd; + } const s_aSubTests[] = + { /* PriChg, Xcpt, uStartSs, => bits uDstCs offDst/pv uDstSs uErrCd */ + { false, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R0_CS64 | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS32 | 0, 0 }, + { false, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R0_CS64 | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS32 | 0, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64 | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R1_CS64 | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64 | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R1_CS64 | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R2_CS64 | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R2_CS64 | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R2_CS64 | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R2_CS64 | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64 | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS32 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R3_CS64 | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS32 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64 | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R3_CS64 | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + /* same with 32-bit wide target addresses: */ + { false, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R0_CS64 | 0, { .s = {(NPVOID)bs3CpuBasic2_salc_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R0_SS32 | 0, 0 }, + { false, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R0_CS64 | 0, { .s = {(NPVOID)bs3CpuBasic2_salc_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R0_SS32 | 0, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64 | 1, { .s = {(NPVOID)bs3CpuBasic2_salc_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R1_CS64 | 1, { .s = {(NPVOID)bs3CpuBasic2_salc_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64 | 1, { .s = {(NPVOID)bs3CpuBasic2_salc_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R1_CS64 | 1, { .s = {(NPVOID)bs3CpuBasic2_salc_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R2_CS64 | 2, { .s = {(NPVOID)bs3CpuBasic2_salc_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R2_CS64 | 2, { .s = {(NPVOID)bs3CpuBasic2_salc_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R2_CS64 | 2, { .s = {(NPVOID)bs3CpuBasic2_salc_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R2_CS64 | 2, { .s = {(NPVOID)bs3CpuBasic2_salc_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64 | 3, { .s = {(NPVOID)bs3CpuBasic2_salc_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS32 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R3_CS64 | 3, { .s = {(NPVOID)bs3CpuBasic2_salc_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS32 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64 | 3, { .s = {(NPVOID)bs3CpuBasic2_salc_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS16 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 64, BS3_SEL_R3_CS64 | 3, { .s = {(NPVOID)bs3CpuBasic2_salc_ud2, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS16 | 3, 0 }, + /* conforming stuff */ + { false, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R0_CS64_CNF | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS32 | 0, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R0_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R0_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS32 | 1, BS3_SEL_R0_SS32 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R0_CS64_CNF | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R0_CS64_CNF | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS32 | 3, 0 }, + { false, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64_CNF | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R1_CS64_CNF }, + { false, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64_CNF | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS32 | 1, BS3_SEL_R1_CS64_CNF }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64_CNF | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R1_CS64_CNF | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS32 | 3, 0 }, + { false, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R2_CS64_CNF | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R2_CS64_CNF }, + { false, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R2_CS64_CNF | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS32 | 1, BS3_SEL_R2_CS64_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R2_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS32 | 1, BS3_SEL_R2_CS64_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R2_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R2_CS64_CNF }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R2_CS64_CNF | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R2_CS64_CNF | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS32 | 3, 0 }, + { false, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64_CNF | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R3_CS64_CNF }, + { false, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64_CNF | 0, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS32 | 1, BS3_SEL_R3_CS64_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R1_SS32 | 1, BS3_SEL_R3_CS64_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64_CNF | 1, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R3_CS64_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64_CNF | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R2_SS32 | 2, BS3_SEL_R3_CS64_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64_CNF | 2, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS32 | 2, BS3_SEL_R3_CS64_CNF }, + { true, -1, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_R3_CS64_CNF | 3, { .offDst = LOW_SALC_UD_ADDR }, BS3_SEL_R3_SS32 | 3, 0 }, + /* returning to 16-bit code: */ + { false, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R0_CS16 | 0, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R0_SS32 | 0, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16 | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16 | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R2_CS16 | 2, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R2_CS16 | 2, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R3_CS16 | 3, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R3_SS32 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R3_CS16 | 3, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R0_CS16 | 0, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R0_SS16 | 0, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R1_CS16 | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R1_CS16 | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R2_CS16 | 2, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R2_CS16 | 2, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R3_CS16 | 3, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R3_SS16 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R3_CS16 | 3, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R3_SS32 | 3, 0 }, + /* returning to 16-bit conforming code: */ + { false, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R0_CS16_CNF | 0, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R0_SS32 | 0, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R0_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R0_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R0_SS32 | 1, BS3_SEL_R0_SS32 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R0_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R0_SS32 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R0_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R3_SS32 | 1, BS3_SEL_R3_SS32 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R0_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R3_SS32 | 3, BS3_SEL_R3_SS32 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R0_CS16_CNF | 2, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R0_CS16_CNF | 3, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R3_SS32 | 3, 0 }, + { false, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16_CNF | 0, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R1_CS16_CNF }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R0_SS32 | 1, BS3_SEL_R0_SS32 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R0_SS32 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R3_SS32 | 1, BS3_SEL_R3_SS32 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R3_SS32 | 3, BS3_SEL_R3_SS32 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16_CNF | 2, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R1_CS16_CNF | 3, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R3_SS32 | 3, 0 }, + { false, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R2_CS16_CNF | 0, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R2_CS16_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R2_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R1_SS32 | 1, BS3_SEL_R2_CS16_CNF }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R2_CS16_CNF | 2, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R2_CS16_CNF | 3, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R3_CS16_CNF | 0, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R3_CS16_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R3_CS16_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R1_SS32 | 1, BS3_SEL_R3_CS16_CNF }, + { true, 42, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R3_CS16_CNF | 2, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R2_SS32 | 2, BS3_SEL_R3_CS16_CNF }, + { true, -1, BS3_SEL_R0_SS32 | 0, 16, BS3_SEL_R3_CS16_CNF | 3, { .s = {(NPVOID)bs3CpuBasic2_swapgs, 0 } }, BS3_SEL_R3_SS32 | 3, 0 }, + /* returning to 32-bit code - narrow 16-bit target address: */ + { false, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R0_CS32 | 0, { .offDst = LOW_SWAPGS_ADDR }, BS3_SEL_R0_SS32 | 0, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32 | 1, { .offDst = LOW_SWAPGS_ADDR }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32 | 1, { .offDst = LOW_SWAPGS_ADDR }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32 | 2, { .offDst = LOW_SWAPGS_ADDR }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32 | 2, { .offDst = LOW_SWAPGS_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32 | 3, { .offDst = LOW_SWAPGS_ADDR }, BS3_SEL_R3_SS32 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32 | 3, { .offDst = LOW_SWAPGS_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R0_CS32 | 0, { .offDst = LOW_SWAPGS_ADDR }, BS3_SEL_R0_SS16 | 0, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32 | 1, { .offDst = LOW_SWAPGS_ADDR }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32 | 1, { .offDst = LOW_SWAPGS_ADDR }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R2_CS32 | 2, { .offDst = LOW_SWAPGS_ADDR }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R2_CS32 | 2, { .offDst = LOW_SWAPGS_ADDR }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R3_CS32 | 3, { .offDst = LOW_SWAPGS_ADDR }, BS3_SEL_R3_SS16 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R3_CS32 | 3, { .offDst = LOW_SWAPGS_ADDR }, BS3_SEL_R3_SS32 | 3, 0 }, + /* returning to 32-bit code - wider 32-bit target address: */ + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32 | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32 | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32 | 2, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32 | 2, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32 | 3, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS32 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32 | 3, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R0_CS32 | 0, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R0_SS16 | 0, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32 | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R1_CS32 | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R2_CS32 | 2, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R2_CS32 | 2, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R3_CS32 | 3, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS16 | 3, 0 }, + { true, -1, BS3_SEL_R0_SS16 | 0, 32, BS3_SEL_R3_CS32 | 3, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS32 | 3, 0 }, + /* returning to 32-bit conforming code: */ + { false, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R0_CS32_CNF | 0, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R0_SS32 | 0, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R0_CS32_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS32 | 1, 0 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R0_CS32_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R0_SS32 | 1, BS3_SEL_R0_SS32 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R0_CS32_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R0_SS32 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R0_CS32_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS32 | 1, BS3_SEL_R3_SS32 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R0_CS32_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS32 | 3, BS3_SEL_R3_SS32 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R0_CS32_CNF | 2, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R2_SS16 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R0_CS32_CNF | 3, { .offDst = LOW_SWAPGS_ADDR }, BS3_SEL_R3_SS32 | 3, 0 }, + { false, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32_CNF | 0, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R1_CS32_CNF }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS16 | 1, 0 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R0_SS32 | 1, BS3_SEL_R0_SS32 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R0_SS32 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS32 | 1, BS3_SEL_R3_SS32 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS32 | 3, BS3_SEL_R3_SS32 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32_CNF | 2, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R1_CS32_CNF | 3, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS32 | 3, 0 }, + { false, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32_CNF | 0, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R2_CS32_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS32 | 1, BS3_SEL_R2_CS32_CNF }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32_CNF | 2, { .offDst = LOW_SWAPGS_ADDR }, BS3_SEL_R2_SS32 | 2, 0 }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R2_CS32_CNF | 3, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS16 | 3, 0 }, + { false, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32_CNF | 0, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R0_SS32 | 0, BS3_SEL_R3_CS32_CNF }, + { true, 14, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32_CNF | 1, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R1_SS32 | 1, BS3_SEL_R3_CS32_CNF }, + { true, 42, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32_CNF | 2, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R2_SS32 | 2, BS3_SEL_R3_CS32_CNF }, + { true, -1, BS3_SEL_R0_SS32 | 0, 32, BS3_SEL_R3_CS32_CNF | 3, { .s = {(NPVOID)bs3CpuBasic2_swapgs, BS3TEXT16_ADDR_HI } }, BS3_SEL_R3_SS32 | 3, 0 }, + + /* some additional #GP variations */ /** @todo test all possible exceptions! */ + { true, 14, BS3_SEL_R0_SS16 | 0, 16, BS3_SEL_R3_CS16 | 2, { .s = { (NPVOID)bs3CpuBasic2_ud2 } }, BS3_SEL_R2_SS16 | 2, BS3_SEL_R3_CS16 }, + + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_SPARE_00 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_00 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_SPARE_02 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_02 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_SPARE_04 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_04 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_SPARE_06 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_06 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_SPARE_08 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_08 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_SPARE_0a | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_0a }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_SPARE_0c | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_0c }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_SPARE_0e | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_0e }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_SPARE_10 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_10 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_SPARE_12 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_12 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_SPARE_14 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_14 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_SPARE_16 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_16 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_SPARE_18 | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_18 }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_SPARE_1a | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_1a }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_SPARE_1c | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_1c }, + { true, 14, BS3_SEL_R0_SS32 | 0, 64, BS3_SEL_SPARE_1e | 0, { .offDst = 0 }, BS3_SEL_R0_SS32 | 0, BS3_SEL_SPARE_1e }, + }; + + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + { + Bs3RegCtxSetRipCsFromLnkPtr(&Ctx, s_aTests[iTest].pfnTest); + //Bs3TestPrintf("-------------- #%u: cs:eip=%04RX16:%08RX64 imm=%u%s\n", iTest, Ctx.cs, Ctx.rip.u, s_aTests[iTest].cbImm, + // s_aTests[iTest].fOpSizePfx == 1 ? " o16" : s_aTests[iTest].fOpSizePfx == 2 ? " o64" : ""); + + for (iSubTest = 0; iSubTest < RT_ELEMENTS(s_aSubTests); iSubTest++) + { + g_usBs3TestStep = (iTest << 12) | (iSubTest << 1); + if (s_aTests[iTest].fOpSizePfx != 1 || s_aSubTests[iSubTest].offDst <= UINT16_MAX) + { + uint16_t const cbFrmDisp = s_aSubTests[iSubTest].fInterPriv ? iSubTest % 7 : 0; + uint16_t const cbStkItem = s_aTests[iTest].fOpSizePfx == 2 ? 8 : s_aTests[iTest].fOpSizePfx == 0 ? 4 : 2; + uint16_t const cbFrame = (s_aSubTests[iSubTest].fInterPriv ? 4 : 2) * cbStkItem; + RTSEL const uDstSs = s_aSubTests[iSubTest].uDstSs; + uint64_t uDstRspExpect, uDstRspPush; + //Bs3TestPrintf(" #%u: %s %d %#04RX16 -> %u %#04RX16:%#04RX32 %#04RX16 %#RX16\n", iSubTest, s_aSubTests[iSubTest].fInterPriv ? "priv" : "same", s_aSubTests[iSubTest].iXcpt, s_aSubTests[iSubTest].uStartSs, + // s_aSubTests[iSubTest].cDstBits, s_aSubTests[iSubTest].uDstCs, s_aSubTests[iSubTest].offDst, s_aSubTests[iSubTest].uDstSs, s_aSubTests[iSubTest].uErrCd); + + Ctx.ss = s_aSubTests[iSubTest].uStartSs; + uDstRspExpect = uDstRspPush = Ctx.rsp.u + s_aTests[iTest].cbImm + cbFrame + cbFrmDisp; + if (s_aSubTests[iSubTest].fInterPriv) + { + if (s_aTests[iTest].fOpSizePfx != 1) + { + if (s_aTests[iTest].fOpSizePfx == 2) + uDstRspPush |= UINT64_C(0xf00dfaceacdc0000); + else + uDstRspPush |= UINT32_C(0xacdc0000); + if (s_aSubTests[iSubTest].cDstBits == 64) + uDstRspExpect = uDstRspPush; + else if (!BS3_SEL_IS_SS16(uDstSs)) + uDstRspExpect = (uint32_t)uDstRspPush; + } + } + + CtxExpected.bCpl = Ctx.bCpl; + CtxExpected.cs = Ctx.cs; + CtxExpected.ss = Ctx.ss; + CtxExpected.ds = Ctx.ds; + CtxExpected.es = Ctx.es; + CtxExpected.fs = Ctx.fs; + CtxExpected.gs = Ctx.gs; + CtxExpected.rip.u = Ctx.rip.u; + CtxExpected.rsp.u = Ctx.rsp.u; + CtxExpected.rax.u = Ctx.rax.u; + if (s_aSubTests[iSubTest].iXcpt < 0) + { + CtxExpected.cs = s_aSubTests[iSubTest].uDstCs; + CtxExpected.rip.u = s_aSubTests[iSubTest].offDst; + if (s_aSubTests[iSubTest].cDstBits == 64 && !BS3_MODE_IS_64BIT_SYS(bMode)) + { + CtxExpected.rip.u += 1; + CtxExpected.rax.au8[0] = CtxExpected.rflags.u16 & X86_EFL_CF ? 0xff : 0; + } + CtxExpected.ss = uDstSs; + CtxExpected.rsp.u = uDstRspExpect; + if (s_aSubTests[iSubTest].fInterPriv) + { + uint16_t BS3_FAR *puSel = &CtxExpected.ds; /* ASSUME member order! */ + unsigned cSels = 4; + CtxExpected.bCpl = CtxExpected.ss & X86_SEL_RPL; + while (cSels-- > 0) + { + uint16_t uSel = *puSel; + if ( (uSel & X86_SEL_MASK_OFF_RPL) + && Bs3Gdt[uSel >> X86_SEL_SHIFT].Gen.u2Dpl < CtxExpected.bCpl + && (Bs3Gdt[uSel >> X86_SEL_SHIFT].Gen.u4Type & (X86_SEL_TYPE_CODE | X86_SEL_TYPE_CONF)) + != (X86_SEL_TYPE_CODE | X86_SEL_TYPE_CONF)) + *puSel = 0; + puSel++; + } + CtxExpected.rsp.u += s_aTests[iTest].cbImm; /* arguments are dropped from both stacks. */ + } + } + g_uBs3TrapEipHint = CtxExpected.rip.u32; + //Bs3TestPrintf("ss:rsp=%04RX16:%04RX64 -> %04RX16:%04RX64 [pushed %#RX64]; %04RX16:%04RX64\n",Ctx.ss, Ctx.rsp.u, + // CtxExpected.ss, CtxExpected.rsp.u, uDstRspPush, CtxExpected.cs, CtxExpected.rip.u); + bs3CpuBasic2_retf_PrepStack(StkPtr, cbStkItem, s_aSubTests[iSubTest].uDstCs, s_aSubTests[iSubTest].offDst, + s_aSubTests[iSubTest].fInterPriv, s_aTests[iTest].cbImm, + s_aSubTests[iSubTest].uDstSs, uDstRspPush); + //Bs3TestPrintf("%p: %04RX16 %04RX16 %04RX16 %04RX16\n", StkPtr.pu16, StkPtr.pu16[0], StkPtr.pu16[1], StkPtr.pu16[2], StkPtr.pu16[3]); + //Bs3TestPrintf("%.48Rhxd\n", StkPtr.pu16); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (s_aSubTests[iSubTest].iXcpt < 0) + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxExpected); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, s_aSubTests[iSubTest].uErrCd); + g_usBs3TestStep++; + + /* Again single stepping: */ + //Bs3TestPrintf("stepping...\n"); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + Ctx.rflags.u16 |= X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + if (s_aSubTests[iSubTest].iXcpt < 0 && s_aSubTests[iSubTest].cDstBits == 64 && !BS3_MODE_IS_64BIT_SYS(bMode)) + { + CtxExpected.rip.u -= 1; + CtxExpected.rax.u = Ctx.rax.u; + } + bs3CpuBasic2_retf_PrepStack(StkPtr, cbStkItem, s_aSubTests[iSubTest].uDstCs, s_aSubTests[iSubTest].offDst, + s_aSubTests[iSubTest].fInterPriv, s_aTests[iTest].cbImm, + s_aSubTests[iSubTest].uDstSs, uDstRspPush); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (s_aSubTests[iSubTest].iXcpt < 0) + bs3CpuBasic2_CompareDbCtx(&TrapCtx, &CtxExpected, X86_DR6_BS); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, s_aSubTests[iSubTest].uErrCd); + Ctx.rflags.u16 &= ~X86_EFL_TF; + CtxExpected.rflags.u16 = Ctx.rflags.u16; + g_usBs3TestStep++; + } + } + } + } + else + Bs3TestFailed("wtf?"); + + if (BS3_MODE_IS_64BIT_SYS(bMode)) + Bs3TrapReInit(); + return 0; +} + + + +/********************************************************************************************************************************* +* Instruction Length * +*********************************************************************************************************************************/ + + +static uint8_t bs3CpuBasic2_instr_len_Worker(uint8_t bMode, uint8_t BS3_FAR *pbCodeBuf) +{ + BS3TRAPFRAME TrapCtx; + BS3REGCTX Ctx; + BS3REGCTX CtxExpected; + uint32_t uEipBase; + unsigned cbInstr; + unsigned off; + + /* Make sure they're allocated and all zeroed. */ + Bs3MemZero(&Ctx, sizeof(Ctx)); + Bs3MemZero(&CtxExpected, sizeof(Ctx)); + Bs3MemZero(&TrapCtx, sizeof(TrapCtx)); + + /* + * Create a context. + * + * ASSUMES we're in on the ring-0 stack in ring-0 and using less than 16KB. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 768); + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, (FPFNBS3FAR)pbCodeBuf); + uEipBase = Ctx.rip.u32; + + Bs3MemCpy(&CtxExpected, &Ctx, sizeof(CtxExpected)); + + /* + * Simple stuff crossing the page. + */ + for (off = X86_PAGE_SIZE - 32; off <= X86_PAGE_SIZE + 16; off++) + { + Ctx.rip.u32 = uEipBase + off; + for (cbInstr = 0; cbInstr < 24; cbInstr++) + { + /* + * Generate the instructions: + * [es] nop + * ud2 + */ + if (cbInstr > 0) + { + Bs3MemSet(&pbCodeBuf[off], 0x26 /* es */, cbInstr); + pbCodeBuf[off + cbInstr - 1] = 0x90; /* nop */ + } + pbCodeBuf[off + cbInstr + 0] = 0x0f; /* ud2 */ + pbCodeBuf[off + cbInstr + 1] = 0x0b; + + /* + * Test it. + */ + if (cbInstr < 16) + CtxExpected.rip.u32 = Ctx.rip.u32 + cbInstr; + else + CtxExpected.rip.u32 = Ctx.rip.u32; + g_uBs3TrapEipHint = CtxExpected.rip.u32; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (cbInstr < 16) + bs3CpuBasic2_CompareUdCtx(&TrapCtx, &CtxExpected); + else + bs3CpuBasic2_CompareGpCtx(&TrapCtx, &CtxExpected, 0); + } + pbCodeBuf[off] = 0xf1; /* icebp */ + } + + /* + * Pit instruction length violations against the segment limit (#GP). + */ + if (!BS3_MODE_IS_RM_OR_V86(bMode) && bMode != BS3_MODE_LM64) + { + /** @todo */ + } + + /* + * Pit instruction length violations against an invalid page (#PF). + */ + if (BS3_MODE_IS_PAGED(bMode)) + { + /** @todo */ + } + + return 0; +} + + +/** + * Entrypoint for FAR RET tests. + * + * @returns 0 or BS3TESTDOMODE_SKIPPED. + * @param bMode The CPU mode we're testing. + */ +BS3_DECL_FAR(uint8_t) BS3_CMN_FAR_NM(bs3CpuBasic2_instr_len)(uint8_t bMode) +{ + /* + * Allocate three pages so we can straddle an instruction across the + * boundrary for testing special IEM cases, with the last page being + * made in accessible and useful for pitting #PF against #GP. + */ + uint8_t BS3_FAR * const pbCodeBuf = (uint8_t BS3_FAR *)Bs3MemAlloc(BS3MEMKIND_REAL, X86_PAGE_SIZE * 3); + //Bs3TestPrintf("pbCodeBuf=%p\n", pbCodeBuf); + if (pbCodeBuf) + { + Bs3MemSet(pbCodeBuf, 0xf1, X86_PAGE_SIZE * 3); + bs3CpuBasic2_SetGlobals(bMode); + + if (!BS3_MODE_IS_PAGED(bMode)) + bs3CpuBasic2_instr_len_Worker(bMode, pbCodeBuf); + else + { + uint32_t const uFlatLastPg = Bs3SelPtrToFlat(pbCodeBuf) + X86_PAGE_SIZE * 2; + int rc = Bs3PagingProtect(uFlatLastPg, X86_PAGE_SIZE, 0, X86_PTE_P); + if (RT_SUCCESS(rc)) + { + bs3CpuBasic2_instr_len_Worker(bMode, pbCodeBuf); + Bs3PagingProtect(uFlatLastPg, X86_PAGE_SIZE, X86_PTE_P, 0); + } + else + Bs3TestFailed("Failed to allocate 3 code pages"); + } + + Bs3MemFree(pbCodeBuf, X86_PAGE_SIZE * 3); + } + else + Bs3TestFailed("Failed to allocate 3 code pages"); + return 0; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2.c b/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2.c new file mode 100644 index 00000000..ba430552 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-basic-2.c @@ -0,0 +1,127 @@ +/* $Id: bs3-cpu-basic-2.c $ */ +/** @file + * BS3Kit - bs3-cpu-basic-2, 16-bit C code. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> +#include <iprt/asm-amd64-x86.h> + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +BS3TESTMODE_PROTOTYPES_MODE(bs3CpuBasic2_TssGateEsp); +BS3TESTMODE_PROTOTYPES_MODE(bs3CpuBasic2_RaiseXcpt1); + +FNBS3TESTDOMODE bs3CpuBasic2_RaiseXcpt11_f16; +FNBS3TESTDOMODE bs3CpuBasic2_sidt_f16; +FNBS3TESTDOMODE bs3CpuBasic2_sgdt_f16; +FNBS3TESTDOMODE bs3CpuBasic2_lidt_f16; +FNBS3TESTDOMODE bs3CpuBasic2_lgdt_f16; +FNBS3TESTDOMODE bs3CpuBasic2_iret_f16; +FNBS3TESTDOMODE bs3CpuBasic2_jmp_call_f16; +FNBS3TESTDOMODE bs3CpuBasic2_far_jmp_call_f16; +FNBS3TESTDOMODE bs3CpuBasic2_near_ret_f16; +FNBS3TESTDOMODE bs3CpuBasic2_far_ret_f16; +FNBS3TESTDOMODE bs3CpuBasic2_instr_len_f16; + +BS3_DECL_CALLBACK(void) bs3CpuBasic2_Do32BitTests_pe32(); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static const BS3TESTMODEENTRY g_aModeTest[] = +{ + BS3TESTMODEENTRY_MODE("tss / gate / esp", bs3CpuBasic2_TssGateEsp), +#if 0 /** @todo The 'raise xcpt \#1' test doesn't work in IEM! */ + BS3TESTMODEENTRY_MODE("raise xcpt #1", bs3CpuBasic2_RaiseXcpt1), +#endif +}; + +static const BS3TESTMODEBYONEENTRY g_aModeByOneTests[] = +{ +#if 1 + { "#ac", bs3CpuBasic2_RaiseXcpt11_f16, 0 }, +#endif +#if 1 + { "iret", bs3CpuBasic2_iret_f16, 0 }, + { "near jmp+call jb / jv / ind", bs3CpuBasic2_jmp_call_f16, 0 }, + { "far jmp+call", bs3CpuBasic2_far_jmp_call_f16, 0 }, + { "near ret", bs3CpuBasic2_near_ret_f16, 0 }, + { "far ret", bs3CpuBasic2_far_ret_f16, 0 }, +#endif +#if 1 + { "sidt", bs3CpuBasic2_sidt_f16, 0 }, + { "sgdt", bs3CpuBasic2_sgdt_f16, 0 }, + { "lidt", bs3CpuBasic2_lidt_f16, 0 }, + { "lgdt", bs3CpuBasic2_lgdt_f16, 0 }, +#endif +#if 1 + { "instr length", bs3CpuBasic2_instr_len_f16, 0 }, +#endif +}; + + +BS3_DECL(void) Main_rm() +{ + Bs3InitAll_rm(); + Bs3TestInit("bs3-cpu-basic-2"); + Bs3TestPrintf("g_uBs3CpuDetected=%#x\n", g_uBs3CpuDetected); + + /* + * Do tests driven from 16-bit code. + */ + NOREF(g_aModeTest); NOREF(g_aModeByOneTests); /* for when commenting out bits */ +#if 1 + Bs3TestDoModes_rm(g_aModeTest, RT_ELEMENTS(g_aModeTest)); +#endif + Bs3TestDoModesByOne_rm(g_aModeByOneTests, RT_ELEMENTS(g_aModeByOneTests), 0); + +#if 0 /** @todo The '\#PF' test doesn't work right in IEM! */ + /* + * Do tests driven from 32-bit code (bs3-cpu-basic-2-32.c32 via assembly). + */ + Bs3SwitchTo32BitAndCallC_rm(bs3CpuBasic2_Do32BitTests_pe32, 0); +#endif + + Bs3TestTerm(); + Bs3Shutdown(); +for (;;) { ASMHalt(); } +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-decoding-1-asm.asm b/src/VBox/ValidationKit/bootsectors/bs3-cpu-decoding-1-asm.asm new file mode 100644 index 00000000..ff762316 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-decoding-1-asm.asm @@ -0,0 +1,49 @@ +; $Id: bs3-cpu-decoding-1-asm.asm $ +;; @file +; BS3Kit - bs3-cpu-decoding-1, assembly helpers and template instantiation. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit.mac" + + +; +; Instantiate code templates. +; +BS3_INSTANTIATE_TEMPLATE_ESSENTIALS "bs3-cpu-decoding-1-template.mac" +BS3_INSTANTIATE_COMMON_TEMPLATE "bs3-cpu-decoding-1-template.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-decoding-1-template.c b/src/VBox/ValidationKit/bootsectors/bs3-cpu-decoding-1-template.c new file mode 100644 index 00000000..90bf7e2d --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-decoding-1-template.c @@ -0,0 +1,68 @@ +/* $Id: bs3-cpu-decoding-1-template.c $ */ +/** @file + * BS3Kit - bs3-cpu-decoding-1, C code template. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/asm-amd64-x86.h> +#include <VBox/VMMDevTesting.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/* + * Common code. + * Common code. + * Common code. + */ +#ifdef BS3_INSTANTIATING_CMN + +#endif /* BS3_INSTANTIATING_CMN */ + + +/* + * Mode specific code. + * Mode specific code. + * Mode specific code. + */ +#ifdef BS3_INSTANTIATING_MODE + +#endif /* BS3_INSTANTIATING_MODE */ + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-decoding-1-template.mac b/src/VBox/ValidationKit/bootsectors/bs3-cpu-decoding-1-template.mac new file mode 100644 index 00000000..9ba6de87 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-decoding-1-template.mac @@ -0,0 +1,124 @@ +; $Id: bs3-cpu-decoding-1-template.mac $ +;; @file +; BS3Kit - bs3-cpu-decoding-1, assembly template. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" ; setup environment + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +TMPL_BEGIN_TEXT + + +%ifdef BS3_INSTANTIATING_CMN + +BS3_PROC_BEGIN_CMN bs3CpuDecoding1_LoadXmm0, BS3_PBC_NEAR + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + +%if TMPL_BITS == 16 + push es + push bx + les bx, [xBP + xCB + cbCurRetAddr] + movupd xmm0, [es:bx] + pop bx + pop es +%else + mov xAX, [xBP + xCB + cbCurRetAddr] + movupd xmm0, [xAX] +%endif + + leave + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN bs3CpuDecoding1_LoadXmm0 + + +BS3_PROC_BEGIN_CMN bs3CpuDecoding1_LoadXmm1, BS3_PBC_NEAR + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + +%if TMPL_BITS == 16 + push es + push bx + les bx, [xBP + xCB + cbCurRetAddr] + movupd xmm1, [es:bx] + pop bx + pop es +%else + mov xAX, [xBP + xCB + cbCurRetAddr] + movupd xmm1, [xAX] +%endif + + leave + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN bs3CpuDecoding1_LoadXmm1 + + +BS3_PROC_BEGIN_CMN bs3CpuDecoding1_SaveXmm0, BS3_PBC_NEAR + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + +%if TMPL_BITS == 16 + push es + push bx + les bx, [xBP + xCB + cbCurRetAddr] + movupd [es:bx], xmm0 + pop bx + pop es +%else + mov xAX, [xBP + xCB + cbCurRetAddr] + movupd [xAX], xmm0 +%endif + + leave + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN bs3CpuDecoding1_SaveXmm0 + + +%endif + +%include "bs3kit-template-footer.mac" ; reset environment + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-decoding-1.c32 b/src/VBox/ValidationKit/bootsectors/bs3-cpu-decoding-1.c32 new file mode 100644 index 00000000..efbe730b --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-decoding-1.c32 @@ -0,0 +1,1736 @@ +/* $Id: bs3-cpu-decoding-1.c32 $ */ +/** @file + * BS3Kit - bs3-cpu-decoding-1, 32-bit C code. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> +#include <iprt/asm-amd64-x86.h> + + +/* bs3-cpu-decoding-1-template.mac: */ +BS3_DECL_NEAR(void) BS3_CMN_NM(bs3CpuDecoding1_LoadXmm0)(PCRTUINT128U); +BS3_DECL_NEAR(void) BS3_CMN_NM(bs3CpuDecoding1_LoadXmm1)(PCRTUINT128U); +BS3_DECL_NEAR(void) BS3_CMN_NM(bs3CpuDecoding1_SaveXmm0)(PRTUINT128U); + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Simple test. + */ +typedef struct CPUDECODE1TST +{ + uint16_t fFlags; + uint8_t cbOpcodes; + uint8_t abOpcodes[20]; + uint8_t cbUd; +} CPUDECODE1TST; +typedef CPUDECODE1TST BS3_FAR *PCPUDECODE1TST; + +#define P_CS X86_OP_PRF_CS +#define P_SS X86_OP_PRF_SS +#define P_DS X86_OP_PRF_DS +#define P_ES X86_OP_PRF_ES +#define P_FS X86_OP_PRF_FS +#define P_GS X86_OP_PRF_GS +#define P_OZ X86_OP_PRF_SIZE_OP +#define P_AZ X86_OP_PRF_SIZE_ADDR +#define P_LK X86_OP_PRF_LOCK +#define P_RN X86_OP_PRF_REPNZ +#define P_RZ X86_OP_PRF_REPZ + +#define RM_EAX_EAX ((3 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xAX << X86_MODRM_REG_SHIFT) | (X86_GREG_xAX)) +#define RM_ECX_EAX ((3 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xCX << X86_MODRM_REG_SHIFT) | (X86_GREG_xAX)) +#define RM_EDX_EAX ((3 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xDX << X86_MODRM_REG_SHIFT) | (X86_GREG_xAX)) +#define RM_EBX_EAX ((3 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xBX << X86_MODRM_REG_SHIFT) | (X86_GREG_xAX)) +#define RM_ESP_EAX ((3 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xSP << X86_MODRM_REG_SHIFT) | (X86_GREG_xAX)) +#define RM_EBP_EAX ((3 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xBP << X86_MODRM_REG_SHIFT) | (X86_GREG_xAX)) +#define RM_ESI_EAX ((3 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xSI << X86_MODRM_REG_SHIFT) | (X86_GREG_xAX)) +#define RM_EDI_EAX ((3 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xDI << X86_MODRM_REG_SHIFT) | (X86_GREG_xAX)) + +#define RM_EAX_DEREF_EBX ((0 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xAX << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_ECX_DEREF_EBX ((0 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xCX << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_EDX_DEREF_EBX ((0 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xDX << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_EBX_DEREF_EBX ((0 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xBX << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_ESP_DEREF_EBX ((0 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xSP << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_EBP_DEREF_EBX ((0 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xBP << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_ESI_DEREF_EBX ((0 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xSI << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_EDI_DEREF_EBX ((0 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xDI << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) + +#define RM_EAX_DEREF_EBX_DISP8 ((1 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xAX << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_ECX_DEREF_EBX_DISP8 ((1 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xCX << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_EDX_DEREF_EBX_DISP8 ((1 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xDX << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_EBX_DEREF_EBX_DISP8 ((1 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xBX << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_ESP_DEREF_EBX_DISP8 ((1 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xSP << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_EBP_DEREF_EBX_DISP8 ((1 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xBP << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_ESI_DEREF_EBX_DISP8 ((1 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xSI << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_EDI_DEREF_EBX_DISP8 ((1 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xDI << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) + +#define RM_EAX_DEREF_EBX_DISP32 ((2 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xAX << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_ECX_DEREF_EBX_DISP32 ((2 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xCX << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_EDX_DEREF_EBX_DISP32 ((2 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xDX << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_EBX_DEREF_EBX_DISP32 ((2 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xBX << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_ESP_DEREF_EBX_DISP32 ((2 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xSP << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_EBP_DEREF_EBX_DISP32 ((2 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xBP << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_ESI_DEREF_EBX_DISP32 ((2 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xSI << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) +#define RM_EDI_DEREF_EBX_DISP32 ((2 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xDI << X86_MODRM_REG_SHIFT) | (X86_GREG_xBX)) + +#define RM_EAX_SIB ((0 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xAX << X86_MODRM_REG_SHIFT) | 4) +#define RM_ECX_SIB ((0 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xCX << X86_MODRM_REG_SHIFT) | 4) +#define RM_EDX_SIB ((0 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xDX << X86_MODRM_REG_SHIFT) | 4) +#define RM_EBX_SIB ((0 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xBX << X86_MODRM_REG_SHIFT) | 4) +#define RM_ESP_SIB ((0 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xSP << X86_MODRM_REG_SHIFT) | 4) +#define RM_EBP_SIB ((0 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xBP << X86_MODRM_REG_SHIFT) | 4) +#define RM_ESI_SIB ((0 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xSI << X86_MODRM_REG_SHIFT) | 4) +#define RM_EDI_SIB ((0 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xDI << X86_MODRM_REG_SHIFT) | 4) + +#define RM_EAX_SIB_DISP8 ((1 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xAX << X86_MODRM_REG_SHIFT) | 4) +#define RM_ECX_SIB_DISP8 ((1 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xCX << X86_MODRM_REG_SHIFT) | 4) +#define RM_EDX_SIB_DISP8 ((1 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xDX << X86_MODRM_REG_SHIFT) | 4) +#define RM_EBX_SIB_DISP8 ((1 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xBX << X86_MODRM_REG_SHIFT) | 4) +#define RM_ESP_SIB_DISP8 ((1 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xSP << X86_MODRM_REG_SHIFT) | 4) +#define RM_EBP_SIB_DISP8 ((1 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xBP << X86_MODRM_REG_SHIFT) | 4) +#define RM_ESI_SIB_DISP8 ((1 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xSI << X86_MODRM_REG_SHIFT) | 4) +#define RM_EDI_SIB_DISP8 ((1 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xDI << X86_MODRM_REG_SHIFT) | 4) + +#define RM_EAX_SIB_DISP32 ((2 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xAX << X86_MODRM_REG_SHIFT) | 4) +#define RM_ECX_SIB_DISP32 ((2 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xCX << X86_MODRM_REG_SHIFT) | 4) +#define RM_EDX_SIB_DISP32 ((2 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xDX << X86_MODRM_REG_SHIFT) | 4) +#define RM_EBX_SIB_DISP32 ((2 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xBX << X86_MODRM_REG_SHIFT) | 4) +#define RM_ESP_SIB_DISP32 ((2 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xSP << X86_MODRM_REG_SHIFT) | 4) +#define RM_EBP_SIB_DISP32 ((2 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xBP << X86_MODRM_REG_SHIFT) | 4) +#define RM_ESI_SIB_DISP32 ((2 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xSI << X86_MODRM_REG_SHIFT) | 4) +#define RM_EDI_SIB_DISP32 ((2 << X86_MODRM_MOD_SHIFT) | (X86_GREG_xDI << X86_MODRM_REG_SHIFT) | 4) + +#define RM_XMM0_XMM1 ((3 << X86_MODRM_MOD_SHIFT) | (0 << X86_MODRM_REG_SHIFT) | 1) + +#define SIB_EBX_X1_NONE ((0 << X86_SIB_SCALE_SHIFT) | (4 << X86_SIB_INDEX_SHIFT) | (X86_GREG_xBX)) +#define SIB_EBX_X2_NONE ((1 << X86_SIB_SCALE_SHIFT) | (4 << X86_SIB_INDEX_SHIFT) | (X86_GREG_xBX)) +#define SIB_EBX_X4_NONE ((2 << X86_SIB_SCALE_SHIFT) | (4 << X86_SIB_INDEX_SHIFT) | (X86_GREG_xBX)) +#define SIB_EBX_X8_NONE ((3 << X86_SIB_SCALE_SHIFT) | (4 << X86_SIB_INDEX_SHIFT) | (X86_GREG_xBX)) + +#define F_486 UINT16_C(0x0000) +#define F_SSE2 UINT16_C(0x0001) +#define F_SSE3 UINT16_C(0x0002) +#define F_SSE42 UINT16_C(0x0004) +#define F_MOVBE UINT16_C(0x0080) +#define F_CBUD UINT16_C(0x4000) +#define F_UD UINT16_C(0x8000) +#define F_OK UINT16_C(0x0000) + + +/** + * This is an exploratory testcase. It tries to figure out how exactly the + * different Intel and AMD CPUs implements SSE and similar instructions that + * uses the size, repz, repnz and lock prefixes in the encoding. + */ +CPUDECODE1TST const g_aSimpleTests[] = +{ + /* + * fFlags, cbUd, cbOpcodes, abOpcodes + */ +#if 0 + /* Using currently undefined 0x0f 0x7a sequences. */ + { F_UD, 3, { 0x0f, 0x7a, RM_EAX_EAX, } }, + { F_UD, 3+1, { P_LK, 0x0f, 0x7a, RM_EAX_EAX, } }, + { F_UD, 3+1, { P_RZ, 0x0f, 0x7a, RM_EAX_EAX, } }, + { F_UD, 3+1, { P_RN, 0x0f, 0x7a, RM_EAX_EAX, } }, + { F_UD, 3+2, { P_LK, P_LK, 0x0f, 0x7a, RM_EAX_EAX, } }, + { F_UD, 4, { 0x0f, 0x7a, RM_EAX_DEREF_EBX_DISP8, 0 } }, + { F_UD, 4+1, { P_LK, 0x0f, 0x7a, RM_EAX_DEREF_EBX_DISP8, 0 } }, + { F_UD, 4+1, { P_RZ, 0x0f, 0x7a, RM_EAX_DEREF_EBX_DISP8, 0 } }, + { F_UD, 4+1, { P_RN, 0x0f, 0x7a, RM_EAX_DEREF_EBX_DISP8, 0 } }, + { F_UD, 4+2, { P_LK, P_LK, 0x0f, 0x7a, RM_EAX_DEREF_EBX_DISP8, 0 } }, + { F_UD, 7, { 0x0f, 0x7a, RM_EAX_DEREF_EBX_DISP32, 0, 0, 0, 0 } }, + { F_UD, 7+1, { P_LK, 0x0f, 0x7a, RM_EAX_DEREF_EBX_DISP32, 0, 0, 0, 0 } }, + { F_UD, 7+1, { P_RZ, 0x0f, 0x7a, RM_EAX_DEREF_EBX_DISP32, 0, 0, 0, 0 } }, + { F_UD, 7+1, { P_RN, 0x0f, 0x7a, RM_EAX_DEREF_EBX_DISP32, 0, 0, 0, 0 } }, + { F_UD, 7+2, { P_LK, P_LK, 0x0f, 0x7a, RM_EAX_DEREF_EBX_DISP32, 0, 0, 0, 0 } }, +#endif +#if 0 + /* Ditto for currently undefined sequence: 0x0f 0x7b */ + { F_UD, 3, { 0x0f, 0x7b, RM_EAX_EAX, } }, + { F_UD, 3+1, { P_LK, 0x0f, 0x7b, RM_EAX_EAX, } }, + { F_UD, 3+1, { P_RZ, 0x0f, 0x7b, RM_EAX_EAX, } }, + { F_UD, 3+1, { P_RN, 0x0f, 0x7b, RM_EAX_EAX, } }, + { F_UD, 3+2, { P_LK, P_LK, 0x0f, 0x7b, RM_EAX_EAX, } }, +#endif +#if 1 + /* Ditto for currently undefined sequence: 0x0f 0x24 */ + { F_UD, 3, { 0x0f, 0x24, RM_EAX_EAX, } }, + { F_UD, 3+1, { P_LK, 0x0f, 0x24, RM_EAX_EAX, } }, + { F_UD, 3+1, { P_RZ, 0x0f, 0x24, RM_EAX_EAX, } }, + { F_UD, 3+1, { P_RN, 0x0f, 0x24, RM_EAX_EAX, } }, + { F_UD, 3+2, { P_LK, P_LK, 0x0f, 0x24, RM_EAX_EAX, } }, +#endif +#if 0 + /* The XADD instruction has empty lines for 66, f3 and f2 prefixes. + AMD doesn't do anything special for XADD Ev,Gv as the intel table would indicate. */ + { F_486 | F_OK, 3, { 0x0f, 0xc1, RM_EAX_EAX, } }, + { F_486 | F_OK, 4, { P_OZ, 0x0f, 0xc1, RM_EAX_EAX, } }, + { F_486 | F_OK, 4, { P_RZ, 0x0f, 0xc1, RM_EAX_EAX, } }, + { F_486 | F_OK, 5, { P_OZ, P_RZ, 0x0f, 0xc1, RM_EAX_EAX, } }, + { F_486 | F_OK, 5, { P_RZ, P_OZ, 0x0f, 0xc1, RM_EAX_EAX, } }, + { F_486 | F_OK, 4, { P_RN, 0x0f, 0xc1, RM_EAX_EAX, } }, + { F_486 | F_OK, 5, { P_OZ, P_RN, 0x0f, 0xc1, RM_EAX_EAX, } }, + { F_486 | F_OK, 5, { P_RN, P_OZ, 0x0f, 0xc1, RM_EAX_EAX, } }, +#endif +#if 0 + /* The movnti instruction is confined to the unprefixed lined in the intel manuals. Check how the other lines work. */ + { F_SSE2 | F_UD, 3, { 0x0f, 0xc3, RM_EAX_EAX, } }, /* invalid - reg,reg */ + { F_SSE2 | F_OK, 3, { 0x0f, 0xc3, RM_EAX_DEREF_EBX, } }, + { F_SSE2 | F_UD, 4, { P_OZ, 0x0f, 0xc3, RM_EAX_DEREF_EBX, } }, /* invalid */ + { F_SSE2 | F_UD, 4, { P_RZ, 0x0f, 0xc3, RM_EAX_DEREF_EBX, } }, /* invalid */ + { F_SSE2 | F_UD, 4, { P_RN, 0x0f, 0xc3, RM_EAX_DEREF_EBX, } }, /* invalid */ + { F_SSE2 | F_UD, 4, { P_LK, 0x0f, 0xc3, RM_EAX_DEREF_EBX, } }, /* invalid */ + { F_SSE2 | F_UD, 5, { P_RN, P_LK, 0x0f, 0xc3, RM_EAX_DEREF_EBX, } }, /* invalid */ +#endif +#if 0 + /* The lddqu instruction requires a 0xf2 prefix, intel only lists 0x66 and empty + prefix for it. Check what they really mean by that*/ + { F_SSE3 | F_UD, 4, { P_RN, 0x0f, 0xf0, RM_EAX_EAX, } }, /* invalid - reg, reg */ + { F_SSE3 | F_OK, 4, { P_RN, 0x0f, 0xf0, RM_EAX_DEREF_EBX, } }, + { F_SSE3 | F_OK, 5, { P_RN, P_RN, 0x0f, 0xf0, RM_EAX_DEREF_EBX, } }, + { F_SSE3 | F_UD, 3, { 0x0f, 0xf0, RM_EAX_DEREF_EBX, } }, + { F_SSE3 | F_UD, 4, { P_RZ, 0x0f, 0xf0, RM_EAX_DEREF_EBX, } }, + { F_SSE3 | F_UD, 4, { P_OZ, 0x0f, 0xf0, RM_EAX_DEREF_EBX, } }, + { F_SSE3 | F_UD, 4, { P_LK, 0x0f, 0xf0, RM_EAX_DEREF_EBX, } }, + { F_SSE3 | F_UD, 5, { P_RN, P_RZ, 0x0f, 0xf0, RM_EAX_DEREF_EBX, } }, + { F_SSE3 | F_OK, 5, { P_RN, P_OZ, 0x0f, 0xf0, RM_EAX_DEREF_EBX, } }, // AMD,why? + { F_SSE3 | F_UD, 5, { P_RN, P_LK, 0x0f, 0xf0, RM_EAX_DEREF_EBX, } }, + { F_SSE3 | F_OK, 5, { P_RZ, P_RN, 0x0f, 0xf0, RM_EAX_DEREF_EBX, } }, + { F_SSE3 | F_OK, 5, { P_OZ, P_RN, 0x0f, 0xf0, RM_EAX_DEREF_EBX, } }, + { F_SSE3 | F_UD, 5, { P_LK, P_RN, 0x0f, 0xf0, RM_EAX_DEREF_EBX, } }, + { F_SSE3 | F_OK, 5, { P_OZ, P_RN, 0x0f, 0xf0, RM_EAX_DEREF_EBX, } }, + { F_SSE3 | F_OK, 6,{ P_OZ, P_RZ, P_RN, 0x0f, 0xf0, RM_EAX_DEREF_EBX, } }, +#endif +#if 0 + { F_SSE2 | F_OK, 3, { 0x0f, 0x7e, RM_EAX_EAX, } }, + { F_SSE2 | F_OK, 4, { P_OZ, 0x0f, 0x7e, RM_EAX_EAX, } }, + { F_SSE2 | F_UD, 5,{ P_RN, P_OZ, 0x0f, 0x7e, RM_EAX_EAX, } }, // WTF? + { F_SSE2 | F_UD, 5,{ P_OZ, P_RN, 0x0f, 0x7e, RM_EAX_EAX, } }, + { F_SSE2 | F_OK, 5,{ P_RZ, P_OZ, 0x0f, 0x7e, RM_EAX_EAX, } }, + { F_SSE2 | F_OK, 4, { P_RZ, 0x0f, 0x7e, RM_EAX_EAX, } }, + { F_SSE2 | F_UD, 4, { P_RN, 0x0f, 0x7e, RM_EAX_EAX, } }, +#endif +/** @todo crc32 / movbe */ +}; + +void DecodeEdgeTest(void) +{ + /* + * Allocate and initialize a page pair + */ + uint8_t BS3_FAR *pbPages; + pbPages = Bs3MemGuardedTestPageAlloc(BS3MEMKIND_FLAT32); + if (pbPages) + { + unsigned i; + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + + Bs3MemZero(&Ctx, sizeof(Ctx)); + Bs3MemZero(&TrapFrame, sizeof(TrapFrame)); + + ASMSetCR0((ASMGetCR0() & ~(X86_CR0_EM | X86_CR0_TS)) | X86_CR0_MP); + ASMSetCR4(ASMGetCR4() | X86_CR4_OSFXSR); + + Bs3RegCtxSaveEx(&Ctx, BS3_MODE_CODE_32, 512); + Ctx.rbx.u64 = (uintptr_t)pbPages; + + for (i = 0; i < RT_ELEMENTS(g_aSimpleTests); i++) + { + unsigned const cbOpcodes = g_aSimpleTests[i].cbOpcodes; + uint16_t const fFlags = g_aSimpleTests[i].fFlags; + unsigned cb; + /** @todo check if supported. */ + + /* + * Place the instruction exactly at the page boundrary and proceed to + * move it across it and check that we get #PFs then. + */ + cb = cbOpcodes; + while (cb >= 1) + { + unsigned const cErrorsBefore = Bs3TestSubErrorCount(); + uint8_t BS3_FAR *pbRip = &pbPages[X86_PAGE_SIZE - cb]; + Bs3MemCpy(pbRip, &g_aSimpleTests[i].abOpcodes[0], cb); + Bs3RegCtxSetRipCsFromFlat(&Ctx, (uintptr_t)pbRip); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); +#if 1 + Bs3TestPrintf("\ni=%d cb=%#x (cbOpcodes=%#x fFlags=%#x)\n", i, cb, cbOpcodes, fFlags); +// Bs3TrapPrintFrame(&TrapFrame); +#endif + if (cb >= cbOpcodes && (g_aSimpleTests[i].fFlags & F_UD)) + { + if (TrapFrame.bXcpt != X86_XCPT_UD) + Bs3TestFailedF("i=%d cb=%d cbOp=%d fFlags=%#x: expected #UD got %#x at %RX32\n", + i, cb, cbOpcodes, fFlags, TrapFrame.bXcpt, TrapFrame.Ctx.rip.u32); + } + else if (cb < cbOpcodes) + { + if (TrapFrame.bXcpt != X86_XCPT_PF) + Bs3TestFailedF("i=%d cb=%d cbOp=%d fFlags=%#x: expected #PF (on) got %#x at %RX32\n", + i, cb, cbOpcodes, fFlags, TrapFrame.bXcpt, TrapFrame.Ctx.rip.u32); + else if (TrapFrame.Ctx.rip.u32 != (uintptr_t)pbRip) + Bs3TestFailedF("i=%d cb=%d cbOp=%d fFlags=%#x: expected #PF rip of %p (on) got %#RX32\n", + i, cb, cbOpcodes, fFlags, pbRip, TrapFrame.Ctx.rip.u32); + } + else + { + if (TrapFrame.bXcpt != X86_XCPT_PF) + Bs3TestFailedF("i=%d cb=%d cbOp=%d fFlags=%#x: expected #PF (after) got %#x at %RX32\n", + i, cb, cbOpcodes, fFlags, TrapFrame.bXcpt, TrapFrame.Ctx.rip.u32); + else if (TrapFrame.Ctx.rip.u32 != (uintptr_t)&pbPages[X86_PAGE_SIZE]) + Bs3TestFailedF("i=%d cb=%d cbOp=%d fFlags=%#x: expected #PF rip of %p (after) got %#RX32\n", + i, cb, cbOpcodes, fFlags, &pbPages[X86_PAGE_SIZE], TrapFrame.Ctx.rip.u32); + } + if (Bs3TestSubErrorCount() != cErrorsBefore) + { + Bs3TestPrintf(" %.*Rhxs", cb, &g_aSimpleTests[i].abOpcodes[0]); + if (cb < cbOpcodes) + Bs3TestPrintf("[%.*Rhxs]", cbOpcodes - cb, &g_aSimpleTests[i].abOpcodes[cb]); + Bs3TestPrintf("\n"); + } + + /* next */ + cb--; + } + } + + Bs3MemGuardedTestPageFree(pbPages); + } + else + Bs3TestFailed("Failed to allocate two pages!\n"); + + /* + * Test instruction sequences. + */ + + +} + + +/** + * Undefined opcode test. + */ +typedef struct CPUDECODE1UDTST +{ + /** Type of undefined opcode decoding logic - UD_T_XXX. */ + uint8_t enmType; + /** Core opcodes length. */ + uint8_t cbOpcodes; + /** Core opcodes. */ + uint8_t abOpcodes[5]; + /** UD_F_XXX. */ + uint8_t fFlags; +} CPUDECODE1UDTST; +typedef CPUDECODE1UDTST const BS3_FAR *PCCPUDECODE1UDTST; + +#define UD_T_EXACT 0 +#define UD_T_NOAMD 0x80 /**< AMD does not decode unnecessary bytes, Intel does. */ +#define UD_T_MODRM 1 +#define UD_T_MODRM_I8 2 +#define UD_T_MODRM_M 3 +#define UD_T_MODRM_M_I8 4 +#define UD_T_MODRM_RR0 0x10 +#define UD_T_MODRM_RR1 0x11 +#define UD_T_MODRM_RR2 0x12 +#define UD_T_MODRM_RR3 0x13 +#define UD_T_MODRM_RR4 0x14 +#define UD_T_MODRM_RR5 0x15 +#define UD_T_MODRM_RR6 0x16 +#define UD_T_MODRM_RR7 0x17 +#define UD_T_MODRM_RR0_I8 0x18 +#define UD_T_MODRM_RR1_I8 0x19 +#define UD_T_MODRM_RR2_I8 0x1a +#define UD_T_MODRM_RR3_I8 0x1b +#define UD_T_MODRM_RR4_I8 0x1c +#define UD_T_MODRM_RR5_I8 0x1d +#define UD_T_MODRM_RR6_I8 0x1e +#define UD_T_MODRM_RR7_I8 0x1f +#define UD_T_MODRM_MR0 0x20 +#define UD_T_MODRM_MR1 0x21 +#define UD_T_MODRM_MR2 0x22 +#define UD_T_MODRM_MR3 0x23 +#define UD_T_MODRM_MR4 0x24 +#define UD_T_MODRM_MR5 0x25 +#define UD_T_MODRM_MR6 0x26 +#define UD_T_MODRM_MR7 0x27 +#define UD_T_MODRM_MR0_I8 0x28 +#define UD_T_MODRM_MR1_I8 0x29 +#define UD_T_MODRM_MR2_I8 0x2a +#define UD_T_MODRM_MR3_I8 0x2b +#define UD_T_MODRM_MR4_I8 0x2c +#define UD_T_MODRM_MR5_I8 0x2d +#define UD_T_MODRM_MR6_I8 0x2e +#define UD_T_MODRM_MR7_I8 0x2f + +#define UD_F_ANY_PFX 0 +#define UD_F_NOT_NO_PFX UINT8_C(0x01) /**< Must have some kind of prefix to be \#UD. */ +#define UD_F_NOT_OZ_PFX UINT8_C(0x02) /**< Skip the size prefix. */ +#define UD_F_NOT_RZ_PFX UINT8_C(0x04) /**< Skip the REPZ prefix. */ +#define UD_F_NOT_RN_PFX UINT8_C(0x08) /**< Skip the REPNZ prefix. */ +#define UD_F_NOT_LK_PFX UINT8_C(0x10) /**< Skip the LOCK prefix. */ +#define UD_F_3BYTE_ESC UINT8_C(0x20) /**< Unused 3 byte escape table. Test all 256 entries */ + +/** + * Two byte opcodes. + */ +CPUDECODE1UDTST const g_aUdTest2Byte_0f[] = +{ +#if 0 + { UD_T_EXACT, 2, { 0x0f, 0x04 }, UD_F_ANY_PFX }, + { UD_T_EXACT, 2, { 0x0f, 0x0a }, UD_F_ANY_PFX }, + { UD_T_EXACT, 2, { 0x0f, 0x0c }, UD_F_ANY_PFX }, + { UD_T_EXACT, 2, { 0x0f, 0x0e }, UD_F_ANY_PFX }, + { UD_T_EXACT, 2, { 0x0f, 0x0f }, UD_F_ANY_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x13 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x14 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x15 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x16 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX | UD_F_NOT_RZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x17 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + /** @todo figure when 0f 019 and 0f 0c-0f were made into NOPs. */ + { UD_T_EXACT, 2, { 0x0f, 0x24 }, UD_F_ANY_PFX }, + { UD_T_EXACT, 2, { 0x0f, 0x25 }, UD_F_ANY_PFX }, + { UD_T_EXACT, 2, { 0x0f, 0x26 }, UD_F_ANY_PFX }, + { UD_T_EXACT, 2, { 0x0f, 0x27 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x28 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x29 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x2b }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x2e }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x2f }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_EXACT, 2, { 0x0f, 0x36 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x39, 0x00 }, UD_F_3BYTE_ESC | UD_F_ANY_PFX }, /* Three byte escape table, just unused. */ + { UD_T_MODRM_I8, 3, { 0x0f, 0x3b, 0x00 }, UD_F_3BYTE_ESC | UD_F_ANY_PFX }, /* Three byte escape table, just unused. */ + { UD_T_MODRM, 3, { 0x0f, 0x3c, 0x00 }, UD_F_3BYTE_ESC | UD_F_ANY_PFX }, /* Three byte escape table, just unused. */ + { UD_T_MODRM, 3, { 0x0f, 0x3d, 0x00 }, UD_F_3BYTE_ESC | UD_F_ANY_PFX }, /* Three byte escape table, just unused. */ + { UD_T_MODRM_I8, 3, { 0x0f, 0x3e, 0x00 }, UD_F_3BYTE_ESC | UD_F_ANY_PFX }, /* Three byte escape table, just unused. */ + { UD_T_MODRM_I8, 3, { 0x0f, 0x3f, 0x00 }, UD_F_3BYTE_ESC | UD_F_ANY_PFX }, /* Three byte escape table, just unused. */ + { UD_T_MODRM, 2, { 0x0f, 0x50 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x52 }, UD_F_NOT_NO_PFX | UD_F_NOT_RZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x53 }, UD_F_NOT_NO_PFX | UD_F_NOT_RZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x54 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x55 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x56 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x57 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x5b }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX | UD_F_NOT_RZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x60 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x61 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x62 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x63 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x64 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x65 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x66 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x67 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x68 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x69 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x6a }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x6b }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x6c }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x6d }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x6e }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x6f }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX | UD_F_NOT_RZ_PFX }, + { UD_T_MODRM_M_I8, 2, { 0x0f, 0x71 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR0_I8, 2, { 0x0f, 0x71 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR1_I8, 2, { 0x0f, 0x71 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR2_I8, 2, { 0x0f, 0x71 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM_RR3_I8, 2, { 0x0f, 0x71 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR4_I8, 2, { 0x0f, 0x71 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM_RR5_I8, 2, { 0x0f, 0x71 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR6_I8, 2, { 0x0f, 0x71 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM_RR7_I8, 2, { 0x0f, 0x71 }, UD_F_ANY_PFX }, + { UD_T_MODRM_M_I8, 2, { 0x0f, 0x72 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR0_I8, 2, { 0x0f, 0x72 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR1_I8, 2, { 0x0f, 0x72 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR2_I8, 2, { 0x0f, 0x72 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM_RR3_I8, 2, { 0x0f, 0x72 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR4_I8, 2, { 0x0f, 0x72 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM_RR5_I8, 2, { 0x0f, 0x72 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR6_I8, 2, { 0x0f, 0x72 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM_RR7_I8, 2, { 0x0f, 0x72 }, UD_F_ANY_PFX }, + { UD_T_MODRM_M_I8, 2, { 0x0f, 0x73 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR0_I8, 2, { 0x0f, 0x73 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR1_I8, 2, { 0x0f, 0x73 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR2_I8, 2, { 0x0f, 0x73 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM_RR3_I8, 2, { 0x0f, 0x73 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM_RR4_I8, 2, { 0x0f, 0x73 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR5_I8, 2, { 0x0f, 0x73 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR6_I8, 2, { 0x0f, 0x73 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM_RR7_I8, 2, { 0x0f, 0x73 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x74 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x75 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x76 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + /* 0f 77: WTF? OZ, RZ and RN are all empty in the intel tables and LK isn't metnioned at all: */ + { UD_T_MODRM, 2, { 0x0f, 0x77 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX | UD_F_NOT_RN_PFX | UD_F_NOT_RZ_PFX | UD_F_NOT_LK_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x78 }, UD_F_NOT_NO_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x79 }, UD_F_NOT_NO_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x7a }, UD_F_ANY_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x7b }, UD_F_ANY_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x7c }, UD_F_NOT_OZ_PFX | UD_F_NOT_RN_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x7d }, UD_F_NOT_OZ_PFX | UD_F_NOT_RN_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x7e }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX | UD_F_NOT_RZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0x7f }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX | UD_F_NOT_RZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xa6 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xa7 }, UD_F_ANY_PFX }, + { UD_T_MODRM_MR0, 2, { 0x0f, 0xae }, UD_F_NOT_NO_PFX }, /* fxsave only checks REX.W */ + { UD_T_MODRM_MR1, 2, { 0x0f, 0xae }, UD_F_NOT_NO_PFX }, /* frstor ditto */ + { UD_T_MODRM_MR2, 2, { 0x0f, 0xae }, UD_F_NOT_NO_PFX }, /* ldmxcsr */ + { UD_T_MODRM_MR3, 2, { 0x0f, 0xae }, UD_F_NOT_NO_PFX }, /* stmxcsr */ + { UD_T_MODRM_MR4, 2, { 0x0f, 0xae }, UD_F_NOT_NO_PFX }, /* xsave */ + { UD_T_MODRM_MR5, 2, { 0x0f, 0xae }, UD_F_NOT_NO_PFX }, /* xrstor */ + { UD_T_MODRM_MR6, 2, { 0x0f, 0xae }, UD_F_NOT_NO_PFX }, /* xsaveopt */ + { UD_T_MODRM_MR7, 2, { 0x0f, 0xae }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, /* clflush (none) and clflushopt (66) */ + { UD_T_MODRM_RR0, 2, { 0x0f, 0xae }, UD_F_ANY_PFX }, /* f3=rdfsbase is 64-bit */ + { UD_T_MODRM_RR1, 2, { 0x0f, 0xae }, UD_F_ANY_PFX }, /* f3=rdfsbase is 64-bit */ + { UD_T_MODRM_RR2, 2, { 0x0f, 0xae }, UD_F_ANY_PFX }, /* f3=rdfsbase is 64-bit */ + { UD_T_MODRM_RR3, 2, { 0x0f, 0xae }, UD_F_ANY_PFX }, /* f3=rdfsbase is 64-bit */ + { UD_T_MODRM_RR4, 2, { 0x0f, 0xae }, UD_F_ANY_PFX }, /* unused */ + { UD_T_MODRM_RR5, 2, { 0x0f, 0xae }, UD_F_NOT_NO_PFX }, /* 00=lfence */ + { UD_T_MODRM_RR6, 2, { 0x0f, 0xae }, UD_F_NOT_NO_PFX }, /* 00=mfence */ + { UD_T_MODRM_RR7, 2, { 0x0f, 0xae }, UD_F_NOT_NO_PFX }, /* 00=sfence */ + { UD_T_MODRM, 2, { 0x0f, 0xb8 }, UD_F_NOT_RZ_PFX }, + { UD_T_MODRM | UD_T_NOAMD, 2, { 0x0f, 0xb9 }, UD_F_ANY_PFX }, /* UD1 */ + { UD_T_MODRM_MR0_I8, 2, { 0x0f, 0xba }, UD_F_ANY_PFX }, /* grp8 */ + { UD_T_MODRM_MR1_I8, 2, { 0x0f, 0xba }, UD_F_ANY_PFX }, /* grp8 */ + { UD_T_MODRM_MR2_I8, 2, { 0x0f, 0xba }, UD_F_ANY_PFX }, /* grp8 */ + { UD_T_MODRM_MR3_I8, 2, { 0x0f, 0xba }, UD_F_ANY_PFX }, /* grp8 */ + /** @todo f3 0f bb rm and f2 0f bb rm does stuff on skylake even if their are blank in intel and AMD tables! */ + //{ UD_T_MODRM, 2, { 0x0f, 0xbb }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + /** @todo AMD tables indicates that f2 0f bc rm is invalid, but on skylake it works differently (BSF?) */ + { UD_T_MODRM, 2, { 0x0f, 0xbc }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX | UD_F_NOT_RZ_PFX /* figure: */ | UD_F_NOT_RN_PFX }, + /** @todo AMD tables indicates that f3 0f bc rm is invalid, but on skylake it works differently (BSR?) */ + { UD_T_MODRM, 2, { 0x0f, 0xbd }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX | UD_F_NOT_RZ_PFX /* figure: */ | UD_F_NOT_RN_PFX }, + /* Note! Intel incorrectly states that XADD (0f c0 and 0f c1) are sensitive to OZ, RN and RZ. AMD and skylake hw disagrees. */ + { UD_T_MODRM, 2, { 0x0f, 0xc3 }, UD_F_NOT_NO_PFX }, + { UD_T_MODRM_I8, 2, { 0x0f, 0xc4 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM_I8, 2, { 0x0f, 0xc5 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM_I8, 2, { 0x0f, 0xc6 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, +#endif + { UD_T_MODRM_MR0, 2, { 0x0f, 0xc7 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR0, 2, { 0x0f, 0xc7 }, UD_F_ANY_PFX }, + //{ UD_T_MODRM_MR1, 2, { 0x0f, 0xc7 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX | UD_F_NOT_RN_PFX | UD_F_NOT_RZ_PFX | UD_F_NOT_LK_PFX }, - cmpxchg8b ignores everything. @ + { UD_T_MODRM_RR1, 2, { 0x0f, 0xc7 }, UD_F_ANY_PFX }, + { UD_T_MODRM_MR2, 2, { 0x0f, 0xc7 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR2, 2, { 0x0f, 0xc7 }, UD_F_ANY_PFX }, + { UD_T_MODRM_MR3, 2, { 0x0f, 0xc7 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR3, 2, { 0x0f, 0xc7 }, UD_F_ANY_PFX }, + { UD_T_MODRM_MR4, 2, { 0x0f, 0xc7 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR4, 2, { 0x0f, 0xc7 }, UD_F_ANY_PFX }, + { UD_T_MODRM_MR5, 2, { 0x0f, 0xc7 }, UD_F_ANY_PFX }, + { UD_T_MODRM_RR5, 2, { 0x0f, 0xc7 }, UD_F_ANY_PFX }, + { UD_T_MODRM_MR6, 2, { 0x0f, 0xc7 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX | UD_F_NOT_RZ_PFX }, /* f2? */ + { UD_T_MODRM_RR6, 2, { 0x0f, 0xc7 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, /* (rdrand Rv) */ + { UD_T_MODRM_MR7, 2, { 0x0f, 0xc7 }, UD_F_NOT_NO_PFX }, /* vmptrst Mq (f2?); */ + { UD_T_MODRM_RR7, 2, { 0x0f, 0xc7 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX | UD_F_NOT_RZ_PFX }, /* rdrand Rv; rdpid Rd/q (f2,66??); */ +#if 0 + { UD_T_MODRM, 2, { 0x0f, 0xd0 }, UD_F_NOT_OZ_PFX | UD_F_NOT_RN_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xd1 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xd2 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xd3 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xd4 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xd5 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xd6 }, UD_F_NOT_OZ_PFX | UD_F_NOT_RN_PFX | UD_F_NOT_RZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xd7 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xd8 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xd9 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xda }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xdb }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xdc }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xdd }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xde }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xdf }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xe0 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xe1 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xe2 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xe3 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xe4 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xe5 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xe6 }, UD_F_NOT_OZ_PFX | UD_F_NOT_RN_PFX | UD_F_NOT_RZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xe7 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xe8 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xe9 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xea }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xeb }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xec }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xed }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xee }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xef }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xf0 }, UD_F_NOT_RN_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xf1 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xf2 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xf3 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xf4 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xf5 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xf6 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xf7 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xf8 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xf9 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xfa }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xfb }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xfc }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xfd }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xfe }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 2, { 0x0f, 0xff }, UD_F_ANY_PFX }, +#endif +}; + + +/** + * Three byte opcodes. + */ +CPUDECODE1UDTST const g_aUdTest3Byte_0f_38[] = +{ + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x00 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x01 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x02 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x03 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x04 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x05 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x06 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x07 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x08 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x09 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x0a }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x0b }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x0c }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x0d }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x0e }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x0f }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x10 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x11 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x12 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x13 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x14 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x15 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x16 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x17 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x18 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x19 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x1a }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x1b }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x1c }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x1d }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x1e }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x1f }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x20 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x21 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x22 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x23 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x24 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x25 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x26 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x27 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x28 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x29 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x2a }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x2b }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x2c }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x2d }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x2e }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x2f }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x30 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x31 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x32 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x33 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x34 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x35 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x36 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x37 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x38 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x39 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x3a }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x3b }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x3c }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x3d }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x3e }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x3f }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x40 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x41 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x42 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x43 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x44 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x45 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x46 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x47 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x48 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x49 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x4a }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x4b }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x4c }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x4d }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x4e }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x4f }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x50 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x51 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x52 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x53 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x54 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x55 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x56 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x57 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x58 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x59 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x5a }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x5b }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x5c }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x5e }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x5d }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x5f }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x60 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x61 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x62 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x63 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x64 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x65 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x66 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x67 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x68 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x69 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x6a }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x6b }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x6c }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x6d }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x6e }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x6f }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x70 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x71 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x72 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x73 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x74 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x75 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x76 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x77 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x78 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x79 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x7a }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x7b }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x7c }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x7d }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x7e }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x7f }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x80 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x81 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x82 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x83 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x84 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x85 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x86 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x87 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x88 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x89 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x8a }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x8b }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x8c }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x8d }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x8e }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x8f }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x90 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x91 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x92 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x93 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x94 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x95 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x96 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x97 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x98 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x99 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x9a }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x9b }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x9c }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x9d }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x9e }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0x9f }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xa0 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xa1 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xa2 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xa3 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xa4 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xa5 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xa6 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xa7 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xa8 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xa9 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xaa }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xab }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xac }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xad }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xae }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xaf }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xb0 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xb1 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xb2 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xb3 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xb4 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xb5 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xb6 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xb7 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xb8 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xb9 }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xba }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xbb }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xbc }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xbd }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xbe }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xbf }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xc0 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xc1 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xc2 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xc3 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xc4 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xc5 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xc6 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xc7 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xc8 }, UD_F_NOT_NO_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xc9 }, UD_F_NOT_NO_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xca }, UD_F_NOT_NO_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xcb }, UD_F_NOT_NO_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xcc }, UD_F_NOT_NO_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xcd }, UD_F_NOT_NO_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xce }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xcf }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xd0 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xd1 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xd2 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xd3 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xd4 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xd5 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xd6 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xd7 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xd8 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xd9 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xda }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xdb }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xdc }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xdd }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xde }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xdf }, UD_F_NOT_OZ_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xe0 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xe1 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xe2 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xe3 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xe4 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xe5 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xe6 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xe7 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xe8 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xe9 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xea }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xeb }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xec }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xed }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xee }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xef }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xf0 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX | UD_F_NOT_RN_PFX }, /// @todo crc32 weirdness + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xf1 }, UD_F_NOT_NO_PFX | UD_F_NOT_OZ_PFX | UD_F_NOT_RN_PFX }, /// @todo crc32 weirdness + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xf2 }, UD_F_NOT_NO_PFX }, + + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xf4 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xf5 }, UD_F_NOT_NO_PFX | UD_F_NOT_RZ_PFX | UD_F_NOT_RN_PFX }, + + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xf7 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xf8 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xf9 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xfa }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xfb }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xfc }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xfd }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xfe }, UD_F_ANY_PFX }, + { UD_T_MODRM, 3, { 0x0f, 0x38, 0xff }, UD_F_ANY_PFX }, + + /* This is going to be interesting: */ + { UD_T_MODRM, 5, { 0x66, 0xf2, 0x0f, 0x38, 0xf5 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 5, { 0x66, 0xf3, 0x0f, 0x38, 0xf5 }, UD_F_ANY_PFX }, + { UD_T_MODRM, 5, { 0x66, 0xf2, 0x0f, 0x38, 0xf6 }, UD_F_ANY_PFX }, + //{ UD_T_MODRM, 5, { 0x66, 0xf3, 0x0f, 0x38, 0xf6 }, UD_F_ANY_PFX }, - not this one. +}; + + +void DecodeUdEdgeTest(PCCPUDECODE1UDTST paTests, unsigned cTests) +{ + uint8_t BS3_FAR *pbPages; + + /* + * Detect AMD. + */ + bool fIsAmd = false; + if (g_uBs3CpuDetected & BS3CPU_F_CPUID) + fIsAmd = ASMIsAmdCpu() || ASMIsHygonCpu(); + Bs3TestPrintf("fIsAmd=%d\n", fIsAmd); + + /* + * Allocate and initialize a page pair + */ + pbPages = Bs3MemGuardedTestPageAlloc(BS3MEMKIND_FLAT32); + if (pbPages) + { + unsigned iTest; + BS3REGCTX Ctx; + BS3REGCTX ExpectCtx; + BS3TRAPFRAME TrapFrame; + uint32_t iStep; + + Bs3MemZero(&Ctx, sizeof(Ctx)); + Bs3MemZero(&ExpectCtx, sizeof(ExpectCtx)); + Bs3MemZero(&TrapFrame, sizeof(TrapFrame)); + + /* Enable SSE. */ + ASMSetCR0((ASMGetCR0() & ~(X86_CR0_EM | X86_CR0_TS)) | X86_CR0_MP); + ASMSetCR4(ASMGetCR4() | X86_CR4_OSFXSR); + + /* Create a test context. */ + Bs3RegCtxSaveEx(&Ctx, BS3_MODE_CODE_32, 512); + Ctx.rbx.u = (uintptr_t)pbPages; + Ctx.rcx.u = (uintptr_t)pbPages; + Ctx.rdx.u = (uintptr_t)pbPages; + Ctx.rax.u = (uintptr_t)pbPages; + Ctx.rbp.u = (uintptr_t)pbPages; + Ctx.rsi.u = (uintptr_t)pbPages; + Ctx.rdi.u = (uintptr_t)pbPages; + + Bs3MemCpy(&ExpectCtx, &Ctx, sizeof(ExpectCtx)); + ExpectCtx.rflags.u32 |= X86_EFL_RF; + + /* Loop thru the tests. */ + iStep = g_usBs3TestStep = 0; + for (iTest = 0; iTest < cTests; iTest++) + { + typedef struct CPUDECODE1UDSEQ + { + uint8_t cb; + uint8_t ab[10]; + uint8_t fIncompatible; + } CPUDECODE1UDSEQ; + typedef CPUDECODE1UDSEQ const BS3_FAR *PCCPUDECODE1UDSEQ; + + static CPUDECODE1UDSEQ const s_aPrefixes[] = + { + { 0, { 0 }, UD_F_NOT_NO_PFX }, + { 1, { P_OZ }, UD_F_NOT_OZ_PFX }, + { 1, { P_RN }, UD_F_NOT_RN_PFX }, + { 1, { P_RZ }, UD_F_NOT_RZ_PFX }, + { 1, { P_LK }, UD_F_NOT_LK_PFX }, + { 2, { P_OZ, P_OZ }, UD_F_NOT_OZ_PFX | UD_F_NOT_OZ_PFX }, + { 2, { P_RN, P_OZ }, UD_F_NOT_RN_PFX | UD_F_NOT_OZ_PFX }, + { 2, { P_RZ, P_OZ }, UD_F_NOT_RZ_PFX | UD_F_NOT_OZ_PFX }, + { 2, { P_LK, P_OZ }, UD_F_NOT_LK_PFX | UD_F_NOT_OZ_PFX }, + { 2, { P_OZ, P_RN }, UD_F_NOT_OZ_PFX | UD_F_NOT_RN_PFX }, + { 2, { P_RN, P_RN }, UD_F_NOT_RN_PFX | UD_F_NOT_RN_PFX }, + { 2, { P_RZ, P_RN }, UD_F_NOT_RZ_PFX | UD_F_NOT_RN_PFX }, + { 2, { P_LK, P_RN }, UD_F_NOT_LK_PFX | UD_F_NOT_RN_PFX }, + { 2, { P_OZ, P_RZ }, UD_F_NOT_OZ_PFX | UD_F_NOT_RZ_PFX }, + { 2, { P_RN, P_RZ }, UD_F_NOT_RN_PFX | UD_F_NOT_RZ_PFX }, + { 2, { P_RZ, P_RZ }, UD_F_NOT_RZ_PFX | UD_F_NOT_RZ_PFX }, + { 2, { P_LK, P_RZ }, UD_F_NOT_LK_PFX | UD_F_NOT_RZ_PFX }, + { 2, { P_OZ, P_LK }, UD_F_NOT_OZ_PFX | UD_F_NOT_LK_PFX }, + { 2, { P_RN, P_LK }, UD_F_NOT_RN_PFX | UD_F_NOT_LK_PFX }, + { 2, { P_RZ, P_LK }, UD_F_NOT_RZ_PFX | UD_F_NOT_LK_PFX }, + { 2, { P_LK, P_LK }, UD_F_NOT_LK_PFX | UD_F_NOT_LK_PFX }, + }; + + static CPUDECODE1UDSEQ const s_aExact[] = { { 0, { 0 }, 0 } }; + static CPUDECODE1UDSEQ const s_aModRm[] = + { + { 1, { RM_EAX_EAX, }, 0 }, + /* Mem forms (hardcoded indexed later): */ + { 2, { RM_EAX_DEREF_EBX_DISP8, 0 }, 0 }, + { 5, { RM_EAX_DEREF_EBX_DISP32, 0, 0, 0, 0 }, 0 }, + { 2, { RM_EAX_SIB, SIB_EBX_X1_NONE, }, 0 }, + { 3, { RM_EAX_SIB_DISP8, SIB_EBX_X1_NONE, 0 }, 0 }, + { 6, { RM_EAX_SIB_DISP32, SIB_EBX_X1_NONE, 0, 0, 0, 0 }, 0 }, + }; + static CPUDECODE1UDSEQ const s_aModRmImm8[] = + { + { 1 + 1, { RM_EAX_EAX, 0x11 }, 0 }, + /* Mem forms (hardcoded indexed later): */ + { 2 + 1, { RM_EAX_DEREF_EBX_DISP8, 0, 0x11 }, 0 }, + { 5 + 1, { RM_EAX_DEREF_EBX_DISP32, 0, 0, 0, 0, 0x11 }, 0 }, + { 2 + 1, { RM_EAX_SIB, SIB_EBX_X1_NONE, 0x11 }, 0 }, + { 3 + 1, { RM_EAX_SIB_DISP8, SIB_EBX_X1_NONE, 0, 0x11 }, 0 }, + { 6 + 1, { RM_EAX_SIB_DISP32, SIB_EBX_X1_NONE, 0, 0, 0, 0, 0x11 }, 0 }, + }; + static CPUDECODE1UDSEQ const s_aModRmRRx[] = + { + { 1, { RM_EAX_EAX, }, 0 }, + { 1, { RM_ECX_EAX, }, 0 }, + { 1, { RM_EDX_EAX, }, 0 }, + { 1, { RM_EBX_EAX, }, 0 }, + { 1, { RM_ESP_EAX, }, 0 }, + { 1, { RM_EBP_EAX, }, 0 }, + { 1, { RM_ESI_EAX, }, 0 }, + { 1, { RM_EDI_EAX, }, 0 }, + }; + static CPUDECODE1UDSEQ const s_aModRmRRxImm8[] = + { + { 2, { RM_EAX_EAX, 0x11 }, 0 }, + { 2, { RM_ECX_EAX, 0x11 }, 0 }, + { 2, { RM_EDX_EAX, 0x11 }, 0 }, + { 2, { RM_EBX_EAX, 0x11 }, 0 }, + { 2, { RM_ESP_EAX, 0x11 }, 0 }, + { 2, { RM_EBP_EAX, 0x11 }, 0 }, + { 2, { RM_ESI_EAX, 0x11 }, 0 }, + { 2, { RM_EDI_EAX, 0x11 }, 0 }, + }; + static CPUDECODE1UDSEQ const s_aModRmMRx[] = /* index*5 */ + { + { 2, { RM_EAX_DEREF_EBX_DISP8, 0 }, 0 }, + { 5, { RM_EAX_DEREF_EBX_DISP32, 0, 0, 0, 0 }, 0 }, + { 2, { RM_EAX_SIB, SIB_EBX_X1_NONE, }, 0 }, + { 3, { RM_EAX_SIB_DISP8, SIB_EBX_X1_NONE, 0 }, 0 }, + { 6, { RM_EAX_SIB_DISP32, SIB_EBX_X1_NONE, 0, 0, 0, 0 }, 0 }, + + { 2, { RM_ECX_DEREF_EBX_DISP8, 0 }, 0 }, + { 5, { RM_ECX_DEREF_EBX_DISP32, 0, 0, 0, 0 }, 0 }, + { 2, { RM_ECX_SIB, SIB_EBX_X1_NONE, }, 0 }, + { 3, { RM_ECX_SIB_DISP8, SIB_EBX_X1_NONE, 0 }, 0 }, + { 6, { RM_ECX_SIB_DISP32, SIB_EBX_X1_NONE, 0, 0, 0, 0 }, 0 }, + + { 2, { RM_EDX_DEREF_EBX_DISP8, 0 }, 0 }, + { 5, { RM_EDX_DEREF_EBX_DISP32, 0, 0, 0, 0 }, 0 }, + { 2, { RM_EDX_SIB, SIB_EBX_X1_NONE, }, 0 }, + { 3, { RM_EDX_SIB_DISP8, SIB_EBX_X1_NONE, 0 }, 0 }, + { 6, { RM_EDX_SIB_DISP32, SIB_EBX_X1_NONE, 0, 0, 0, 0 }, 0 }, + + { 2, { RM_EBX_DEREF_EBX_DISP8, 0 }, 0 }, + { 5, { RM_EBX_DEREF_EBX_DISP32, 0, 0, 0, 0 }, 0 }, + { 2, { RM_EBX_SIB, SIB_EBX_X1_NONE, }, 0 }, + { 3, { RM_EBX_SIB_DISP8, SIB_EBX_X1_NONE, 0 }, 0 }, + { 6, { RM_EBX_SIB_DISP32, SIB_EBX_X1_NONE, 0, 0, 0, 0 }, 0 }, + + { 2, { RM_ESP_DEREF_EBX_DISP8, 0 }, 0 }, + { 5, { RM_ESP_DEREF_EBX_DISP32, 0, 0, 0, 0 }, 0 }, + { 2, { RM_ESP_SIB, SIB_EBX_X1_NONE, }, 0 }, + { 3, { RM_ESP_SIB_DISP8, SIB_EBX_X1_NONE, 0 }, 0 }, + { 6, { RM_ESP_SIB_DISP32, SIB_EBX_X1_NONE, 0, 0, 0, 0 }, 0 }, + + { 2, { RM_EBP_DEREF_EBX_DISP8, 0 }, 0 }, + { 5, { RM_EBP_DEREF_EBX_DISP32, 0, 0, 0, 0 }, 0 }, + { 2, { RM_EBP_SIB, SIB_EBX_X1_NONE, }, 0 }, + { 3, { RM_EBP_SIB_DISP8, SIB_EBX_X1_NONE, 0 }, 0 }, + { 6, { RM_EBP_SIB_DISP32, SIB_EBX_X1_NONE, 0, 0, 0, 0 }, 0 }, + + { 2, { RM_ESI_DEREF_EBX_DISP8, 0 }, 0 }, + { 5, { RM_ESI_DEREF_EBX_DISP32, 0, 0, 0, 0 }, 0 }, + { 2, { RM_ESI_SIB, SIB_EBX_X1_NONE, }, 0 }, + { 3, { RM_ESI_SIB_DISP8, SIB_EBX_X1_NONE, 0 }, 0 }, + { 6, { RM_ESI_SIB_DISP32, SIB_EBX_X1_NONE, 0, 0, 0, 0 }, 0 }, + + { 2, { RM_EDI_DEREF_EBX_DISP8, 0 }, 0 }, + { 5, { RM_EDI_DEREF_EBX_DISP32, 0, 0, 0, 0 }, 0 }, + { 2, { RM_EDI_SIB, SIB_EBX_X1_NONE, }, 0 }, + { 3, { RM_EDI_SIB_DISP8, SIB_EBX_X1_NONE, 0 }, 0 }, + { 6, { RM_EDI_SIB_DISP32, SIB_EBX_X1_NONE, 0, 0, 0, 0 }, 0 }, + }; + static CPUDECODE1UDSEQ const s_aModRmMRxImm8[] = /* index*5 */ + { + { 2+1, { RM_EAX_DEREF_EBX_DISP8, 0, 0x11 }, 0 }, + { 5+1, { RM_EAX_DEREF_EBX_DISP32, 0, 0, 0, 0, 0x11 }, 0 }, + { 2+1, { RM_EAX_SIB, SIB_EBX_X1_NONE, 0x11 }, 0 }, + { 3+1, { RM_EAX_SIB_DISP8, SIB_EBX_X1_NONE, 0, 0x11 }, 0 }, + { 6+1, { RM_EAX_SIB_DISP32, SIB_EBX_X1_NONE, 0, 0, 0, 0, 0x11 }, 0 }, + + { 2+1, { RM_ECX_DEREF_EBX_DISP8, 0, 0x11 }, 0 }, + { 5+1, { RM_ECX_DEREF_EBX_DISP32, 0, 0, 0, 0, 0x11 }, 0 }, + { 2+1, { RM_ECX_SIB, SIB_EBX_X1_NONE, 0x11 }, 0 }, + { 3+1, { RM_ECX_SIB_DISP8, SIB_EBX_X1_NONE, 0, 0x11 }, 0 }, + { 6+1, { RM_ECX_SIB_DISP32, SIB_EBX_X1_NONE, 0, 0, 0, 0, 0x11 }, 0 }, + + { 2+1, { RM_EDX_DEREF_EBX_DISP8, 0, 0x11 }, 0 }, + { 5+1, { RM_EDX_DEREF_EBX_DISP32, 0, 0, 0, 0, 0x11 }, 0 }, + { 2+1, { RM_EDX_SIB, SIB_EBX_X1_NONE, 0x11 }, 0 }, + { 3+1, { RM_EDX_SIB_DISP8, SIB_EBX_X1_NONE, 0, 0x11 }, 0 }, + { 6+1, { RM_EDX_SIB_DISP32, SIB_EBX_X1_NONE, 0, 0, 0, 0, 0x11 }, 0 }, + + { 2+1, { RM_EBX_DEREF_EBX_DISP8, 0, 0x11 }, 0 }, + { 5+1, { RM_EBX_DEREF_EBX_DISP32, 0, 0, 0, 0, 0x11 }, 0 }, + { 2+1, { RM_EBX_SIB, SIB_EBX_X1_NONE, 0x11 }, 0 }, + { 3+1, { RM_EBX_SIB_DISP8, SIB_EBX_X1_NONE, 0, 0x11 }, 0 }, + { 6+1, { RM_EBX_SIB_DISP32, SIB_EBX_X1_NONE, 0, 0, 0, 0, 0x11 }, 0 }, + + { 2+1, { RM_ESP_DEREF_EBX_DISP8, 0, 0x11 }, 0 }, + { 5+1, { RM_ESP_DEREF_EBX_DISP32, 0, 0, 0, 0, 0x11 }, 0 }, + { 2+1, { RM_ESP_SIB, SIB_EBX_X1_NONE, 0x11 }, 0 }, + { 3+1, { RM_ESP_SIB_DISP8, SIB_EBX_X1_NONE, 0, 0x11 }, 0 }, + { 6+1, { RM_ESP_SIB_DISP32, SIB_EBX_X1_NONE, 0, 0, 0, 0, 0x11 }, 0 }, + + { 2+1, { RM_EBP_DEREF_EBX_DISP8, 0, 0x11 }, 0 }, + { 5+1, { RM_EBP_DEREF_EBX_DISP32, 0, 0, 0, 0, 0x11 }, 0 }, + { 2+1, { RM_EBP_SIB, SIB_EBX_X1_NONE, 0x11 }, 0 }, + { 3+1, { RM_EBP_SIB_DISP8, SIB_EBX_X1_NONE, 0, 0x11 }, 0 }, + { 6+1, { RM_EBP_SIB_DISP32, SIB_EBX_X1_NONE, 0, 0, 0, 0, 0x11 }, 0 }, + + { 2+1, { RM_ESI_DEREF_EBX_DISP8, 0, 0x11 }, 0 }, + { 5+1, { RM_ESI_DEREF_EBX_DISP32, 0, 0, 0, 0, 0x11 }, 0 }, + { 2+1, { RM_ESI_SIB, SIB_EBX_X1_NONE, 0x11 }, 0 }, + { 3+1, { RM_ESI_SIB_DISP8, SIB_EBX_X1_NONE, 0, 0x11 }, 0 }, + { 6+1, { RM_ESI_SIB_DISP32, SIB_EBX_X1_NONE, 0, 0, 0, 0, 0x11 }, 0 }, + + { 2+1, { RM_EDI_DEREF_EBX_DISP8, 0, 0x11 }, 0 }, + { 5+1, { RM_EDI_DEREF_EBX_DISP32, 0, 0, 0, 0, 0x11 }, 0 }, + { 2+1, { RM_EDI_SIB, SIB_EBX_X1_NONE, 0x11 }, 0 }, + { 3+1, { RM_EDI_SIB_DISP8, SIB_EBX_X1_NONE, 0, 0x11 }, 0 }, + { 6+1, { RM_EDI_SIB_DISP32, SIB_EBX_X1_NONE, 0, 0, 0, 0, 0x11 }, 0 }, + }; + unsigned iPrefix; + unsigned cSuffixes; + PCCPUDECODE1UDSEQ paSuffixes; + unsigned const cSubTabEntries = paTests[iTest].fFlags & UD_F_3BYTE_ESC ? 256 : 1; + unsigned cImmEntries = 1; + + /* + * Skip if implemented. + */ + + /* + * Produce a number of opcode sequences by varying the prefixes and + * ModR/M parts. Each opcode sequence is then treated to the edge test. + */ + switch (paTests[iTest].enmType) + { + case UD_T_EXACT: + l_case_exact: + cSuffixes = RT_ELEMENTS(s_aExact); + paSuffixes = s_aExact; + break; + case UD_T_MODRM | UD_T_NOAMD: + if (fIsAmd) + goto l_case_exact; + case UD_T_MODRM: + cSuffixes = RT_ELEMENTS(s_aModRm); + paSuffixes = s_aModRm; + break; + case UD_T_MODRM_I8: + cSuffixes = RT_ELEMENTS(s_aModRmImm8); + paSuffixes = s_aModRmImm8; + cImmEntries = 256; + break; + case UD_T_MODRM_M: + cSuffixes = RT_ELEMENTS(s_aModRm) - 1; + paSuffixes = &s_aModRm[1]; + break; + case UD_T_MODRM_M_I8: + cSuffixes = RT_ELEMENTS(s_aModRmImm8) - 1; + paSuffixes = &s_aModRmImm8[1]; + break; + case UD_T_MODRM_RR0: + case UD_T_MODRM_RR1: + case UD_T_MODRM_RR2: + case UD_T_MODRM_RR3: + case UD_T_MODRM_RR4: + case UD_T_MODRM_RR5: + case UD_T_MODRM_RR6: + case UD_T_MODRM_RR7: + cSuffixes = 1; + paSuffixes = &s_aModRmRRx[paTests[iTest].enmType - UD_T_MODRM_RR0]; + break; + case UD_T_MODRM_RR0_I8: + case UD_T_MODRM_RR1_I8: + case UD_T_MODRM_RR2_I8: + case UD_T_MODRM_RR3_I8: + case UD_T_MODRM_RR4_I8: + case UD_T_MODRM_RR5_I8: + case UD_T_MODRM_RR6_I8: + case UD_T_MODRM_RR7_I8: + cSuffixes = 1; + paSuffixes = &s_aModRmRRxImm8[paTests[iTest].enmType - UD_T_MODRM_RR0_I8]; + break; + case UD_T_MODRM_MR0: + case UD_T_MODRM_MR1: + case UD_T_MODRM_MR2: + case UD_T_MODRM_MR3: + case UD_T_MODRM_MR4: + case UD_T_MODRM_MR5: + case UD_T_MODRM_MR6: + case UD_T_MODRM_MR7: + cSuffixes = 5; + paSuffixes = &s_aModRmMRx[(paTests[iTest].enmType - UD_T_MODRM_MR0) * 5]; + break; + case UD_T_MODRM_MR0_I8: + case UD_T_MODRM_MR1_I8: + case UD_T_MODRM_MR2_I8: + case UD_T_MODRM_MR3_I8: + case UD_T_MODRM_MR4_I8: + case UD_T_MODRM_MR5_I8: + case UD_T_MODRM_MR6_I8: + case UD_T_MODRM_MR7_I8: + cSuffixes = 5; + paSuffixes = &s_aModRmMRxImm8[(paTests[iTest].enmType - UD_T_MODRM_MR0_I8) * 5]; + break; + default: + Bs3TestPrintf("#%u: enmType=%d\n", paTests[iTest].enmType); + continue; + } + + for (iPrefix = 0; iPrefix < RT_ELEMENTS(s_aPrefixes); iPrefix++) + if (!(s_aPrefixes[iPrefix].fIncompatible & paTests[iTest].fFlags)) + { + unsigned iSubTab; + unsigned cbOpcodesLead; + uint8_t abOpcodes[32]; + + Bs3MemCpy(&abOpcodes[0], &s_aPrefixes[iPrefix].ab[0], s_aPrefixes[iPrefix].cb); + cbOpcodesLead = s_aPrefixes[iPrefix].cb; + Bs3MemCpy(&abOpcodes[cbOpcodesLead], &paTests[iTest].abOpcodes[0], paTests[iTest].cbOpcodes); + cbOpcodesLead += paTests[iTest].cbOpcodes; + + for (iSubTab = 0; iSubTab < cSubTabEntries; iSubTab++) + { + unsigned iSuffix; + + if (cSubTabEntries > 1) + abOpcodes[cbOpcodesLead - 1] = iSubTab; + + for (iSuffix = 0; iSuffix < cSuffixes; iSuffix++) + if (!(paSuffixes[iSuffix].fIncompatible & paTests[iTest].fFlags)) + { + unsigned const cbOpcodes = cbOpcodesLead + paSuffixes[iSuffix].cb; + unsigned cbOpcodesMin = 1; + unsigned iImm; + Bs3MemCpy(&abOpcodes[cbOpcodesLead], paSuffixes[iSuffix].ab, paSuffixes[iSuffix].cb); + + for (iImm = 0; iImm < cImmEntries; iImm++) + { + unsigned cb; + + if (cImmEntries > 1) + abOpcodes[cbOpcodes - 1] = iImm; + + /* + * Do the edge thing. + */ + cb = cbOpcodes; + while (cb >= cbOpcodesMin) + { + uint8_t BS3_FAR *pbRip = &pbPages[X86_PAGE_SIZE - cb]; + uint8_t bXcptExpected; + + Bs3RegCtxSetRipCsFromFlat(&Ctx, (uintptr_t)pbRip); + ExpectCtx.rip = Ctx.rip; + ExpectCtx.cs = Ctx.cs; + if (cb >= cbOpcodes) + { + ExpectCtx.cr2 = Ctx.cr2; + bXcptExpected = X86_XCPT_UD; + } + else + { + ExpectCtx.cr2.u = (uintptr_t)&pbPages[X86_PAGE_SIZE]; + bXcptExpected = X86_XCPT_PF; + } + + Bs3MemCpy(pbRip, &abOpcodes[0], cb); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); +#if 0 + Bs3TestPrintf("iTest=%d iPrefix=%d (%d/%#x) iSubTab=%d iSuffix=%d (%d/%#x) iImm=%d cb=%d cbOp=%d: %.*Rhxs\n", + iTest, iPrefix, s_aPrefixes[iPrefix].cb, s_aPrefixes[iPrefix].fIncompatible, + iSubTab, iSuffix, paSuffixes[iSuffix].cb, paSuffixes[iSuffix].fIncompatible, iImm, + cb, cbOpcodes, + cbOpcodes, abOpcodes); +#endif + + if ( !Bs3TestCheckRegCtxEx(&TrapFrame.Ctx, &ExpectCtx, 0 /*cbPcAdjust*/, + 0 /*cbSpAdjust*/, 0 /*fExtraEfl*/, "mode", 0) + || TrapFrame.bXcpt != bXcptExpected) + { + Bs3TestFailedF("iTest=%d iPrefix=%d (%d/%#x) iSubTab=%u iSuffix=%d (%d/%#x) cb=%d cbOp=%d: %.*Rhxs\n", + iTest, iPrefix, s_aPrefixes[iPrefix].cb, s_aPrefixes[iPrefix].fIncompatible, + iSubTab, iSuffix, paSuffixes[iSuffix].cb, paSuffixes[iSuffix].fIncompatible, + cb, cbOpcodes, + cbOpcodes, abOpcodes); + if (TrapFrame.bXcpt != bXcptExpected) + Bs3TestFailedF("Expected bXcpt=%#x got %#x\n", bXcptExpected, TrapFrame.bXcpt); + Bs3TrapPrintFrame(&TrapFrame); + Bs3Shutdown(); + } + + /* next */ + g_usBs3TestStep++; + iStep++; + cb--; + } + + /* For iImm > 0 only test cb == cbOpcode since the byte isn't included when cb < cbOpcode. */ + cbOpcodesMin = cbOpcodes; + } + } + } + } + } + Bs3TestPrintf("%RI32 (%#RX32) test steps\n", iStep, iStep); + + Bs3MemGuardedTestPageFree(pbPages); + } + else + Bs3TestFailed("Failed to allocate two pages!\n"); +} + + +#if 0 +/** + * Checks how prefixes affects cmpxchg8b and cmpxchg16b + * + * The thing here is that the intel opcode tables indicates that the 66 and f3 + * prefixes encodings are reserved and causes \#UD, where AMD doesn't. Seems + * though that the f2, f3 and 66 prefixes are ignored on skylake intel. Need to + * make sure this is the case, also in 64-bit mode and for the 16b version. + */ +static void DecodeCmpXchg8bVs16b(void) +{ + uint8_t BS3_FAR *pbPages; + + /* Check that the instructions are supported. */ + if ( !(g_uBs3CpuDetected & BS3CPU_F_CPUID) + || !(ASMCpuId_EDX(1) & X86_CPUID_FEATURE_EDX_CX8)) + { + Bs3TestSkipped("not supported"); + return; + } + + /* Setup a guarded page. */ + pbPages = Bs3MemGuardedTestPageAlloc(BS3MEMKIND_FLAT32); + if (pbPages) + { + + Bs3MemGuardedTestPageFree(pbPages); + } + else + Bs3TestFailed("Failed to allocate two pages!\n"); +} +#endif + + +/** + * Checks various prefix encodings with the MOVBE and CRC32 instructions to try + * figure out how they are decoded. + * + * The issue here is that both MOVBE and CRC32 are sensitive to the operand size + * prefix, which helps us identify whether the F2h and F3h prefixes takes + * precedence over 66h in this case. (As it turned out they do and it order + * doesn't matter.) + */ +static void DecodeMovbeVsCrc32(void) +{ + uint8_t BS3_FAR *pbPages; + + /* Check that the instructions are supported. */ + if ( !(g_uBs3CpuDetected & BS3CPU_F_CPUID) + || (ASMCpuId_ECX(1) & (X86_CPUID_FEATURE_ECX_MOVBE | X86_CPUID_FEATURE_ECX_SSE4_2)) + != (X86_CPUID_FEATURE_ECX_MOVBE | X86_CPUID_FEATURE_ECX_SSE4_2) ) + { + Bs3TestSkipped("not supported"); + return; + } + + /* Setup a guarded page. */ + pbPages = Bs3MemGuardedTestPageAlloc(BS3MEMKIND_FLAT32); + if (pbPages) + { + unsigned iTest; + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + BS3REGCTX ExpectCtxMovbe_m32_eax; /* 0f 38 f1 /r */ + BS3REGCTX ExpectCtxMovbe_m16_ax; /* 66 0f 38 f1 /r */ + BS3REGCTX ExpectCtxCrc32_eax_m32; /* f2 0f 38 f1 /r */ + BS3REGCTX ExpectCtxCrc32_eax_m16; /* 66 f2 0f 38 f1 /r */ + BS3REGCTX ExpectCtxUd; + PBS3REGCTX apExpectCtxs[5]; + static const struct + { + uint32_t u32Stored; + uint8_t iExpectCtx; + uint8_t bXcpt; + uint8_t cbOpcodes; + uint8_t abOpcodes[18]; + } s_aTests[] = + { +#define BECRC_EAX UINT32_C(0x11223344) +#define BECRC_MEM_ORG UINT32_C(0x55667788) +#define BECRC_MEM_BE16 UINT32_C(0x55664433) +#define BECRC_MEM_BE32 UINT32_C(0x44332211) + + /* base forms. */ + { BECRC_MEM_BE32, 0, X86_XCPT_PF, 4, { 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + { BECRC_MEM_BE16, 1, X86_XCPT_PF, 5, { P_OZ, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + { BECRC_MEM_ORG, 2, X86_XCPT_PF, 5, { P_RN, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + { BECRC_MEM_ORG, 3, X86_XCPT_PF, 6, { P_OZ, P_RN, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + { BECRC_MEM_ORG, 4, X86_XCPT_UD, 5, { P_RZ, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, /* undefined F3 (P_RZ) */ + { BECRC_MEM_ORG, 4, X86_XCPT_UD, 6, { P_OZ, P_RZ, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, /* undefined F3 (P_RZ) */ + + /* CRC32 eax, [word ebx]: Simple variations showing it doesn't matter where the prefixes are placed. */ + { BECRC_MEM_ORG, 3, X86_XCPT_PF, 6, { P_RN, P_OZ, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + { BECRC_MEM_ORG, 3, X86_XCPT_PF, 7, { P_RN, P_OZ, P_ES, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + { BECRC_MEM_ORG, 3, X86_XCPT_PF, 8, { P_RN, P_SS, P_OZ, P_ES, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + { BECRC_MEM_ORG, 3, X86_XCPT_PF, 8, { P_RN, P_SS, P_ES, P_OZ, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + { BECRC_MEM_ORG, 3, X86_XCPT_PF, 8, { P_SS, P_RN, P_ES, P_OZ, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + { BECRC_MEM_ORG, 3, X86_XCPT_PF, 8, { P_SS, P_ES, P_RN, P_OZ, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + { BECRC_MEM_ORG, 3, X86_XCPT_PF, 8, { P_SS, P_ES, P_OZ, P_RN, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + { BECRC_MEM_ORG, 3, X86_XCPT_PF, 8, { P_SS, P_OZ, P_ES, P_RN, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + { BECRC_MEM_ORG, 3, X86_XCPT_PF, 8, { P_OZ, P_SS, P_ES, P_RN, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + + /* CRC32 eax, [word ebx]: Throw the F3h prefix into the mix. The last of F3 and F2 wins on skylake+jaguar. */ + { BECRC_MEM_ORG, 3, X86_XCPT_PF, 7, { P_RZ, P_OZ, P_RN, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + { BECRC_MEM_ORG, 3, X86_XCPT_PF, 7, { P_OZ, P_RZ, P_RN, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + { BECRC_MEM_ORG, 4, X86_XCPT_UD, 7, { P_OZ, P_RN, P_RZ, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + { BECRC_MEM_ORG, 3, X86_XCPT_PF, 8, { P_OZ, P_RN, P_RZ, P_RN, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + { BECRC_MEM_ORG, 3, X86_XCPT_PF, 8, { P_RN, P_RZ, P_OZ, P_RN, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + { BECRC_MEM_ORG, 3, X86_XCPT_PF, 8, { P_RN, P_RZ, P_RN, P_OZ, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + + { BECRC_MEM_ORG, 4, X86_XCPT_UD, 7, { P_OZ, P_RN, P_RZ, 0x0f, 0x38, 0xf1, RM_EAX_DEREF_EBX } }, + }; + + apExpectCtxs[0] = &ExpectCtxMovbe_m32_eax; + apExpectCtxs[1] = &ExpectCtxMovbe_m16_ax; + apExpectCtxs[2] = &ExpectCtxCrc32_eax_m32; + apExpectCtxs[3] = &ExpectCtxCrc32_eax_m16; + apExpectCtxs[4] = &ExpectCtxUd; + + Bs3MemZero(&Ctx, sizeof(Ctx)); + Bs3MemZero(&ExpectCtxMovbe_m32_eax, sizeof(ExpectCtxMovbe_m32_eax)); + Bs3MemZero(&ExpectCtxMovbe_m16_ax, sizeof(ExpectCtxMovbe_m16_ax)); + Bs3MemZero(&ExpectCtxCrc32_eax_m32, sizeof(ExpectCtxCrc32_eax_m32)); + Bs3MemZero(&ExpectCtxCrc32_eax_m16, sizeof(ExpectCtxCrc32_eax_m16)); + Bs3MemZero(&ExpectCtxUd, sizeof(ExpectCtxUd)); + Bs3MemZero(&TrapFrame, sizeof(TrapFrame)); + + /* Create a test context. */ + Bs3RegCtxSaveEx(&Ctx, BS3_MODE_CODE_32, 512); + Ctx.rax.u = BECRC_EAX; + Ctx.rbx.u = (uintptr_t)pbPages; + + /* Create expected result contexts. */ + Bs3MemCpy(&ExpectCtxMovbe_m32_eax, &Ctx, sizeof(ExpectCtxMovbe_m32_eax)); + ExpectCtxMovbe_m32_eax.rflags.u32 |= X86_EFL_RF; + ExpectCtxMovbe_m32_eax.rip.u = (uintptr_t)&pbPages[X86_PAGE_SIZE]; + ExpectCtxMovbe_m32_eax.cr2.u = (uintptr_t)&pbPages[X86_PAGE_SIZE]; + + Bs3MemCpy(&ExpectCtxMovbe_m16_ax, &ExpectCtxMovbe_m32_eax, sizeof(ExpectCtxMovbe_m16_ax)); + + Bs3MemCpy(&ExpectCtxCrc32_eax_m32, &Ctx, sizeof(ExpectCtxCrc32_eax_m32)); + ExpectCtxCrc32_eax_m32.rflags.u32 |= X86_EFL_RF; + ExpectCtxCrc32_eax_m32.rip.u = (uintptr_t)&pbPages[X86_PAGE_SIZE]; + ExpectCtxCrc32_eax_m32.cr2.u = (uintptr_t)&pbPages[X86_PAGE_SIZE]; + ExpectCtxCrc32_eax_m32.rax.u32 = 0x1aa7cd75; + Bs3MemCpy(&ExpectCtxCrc32_eax_m16, &ExpectCtxCrc32_eax_m32, sizeof(ExpectCtxCrc32_eax_m16)); + ExpectCtxCrc32_eax_m16.rax.u32 = 0x51ab0518; + + Bs3MemCpy(&ExpectCtxUd, &Ctx, sizeof(ExpectCtxUd)); + ExpectCtxUd.rflags.u32 |= X86_EFL_RF; + + /* Loop thru the tests. */ + g_usBs3TestStep = 0; + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + { + unsigned const cbOpcodes = s_aTests[iTest].cbOpcodes; + uint8_t BS3_FAR *pbRip = &pbPages[X86_PAGE_SIZE - cbOpcodes]; + + Bs3MemCpy(pbRip, s_aTests[iTest].abOpcodes, cbOpcodes); + Bs3RegCtxSetRipCsFromFlat(&Ctx, (uintptr_t)pbRip); + *(uint32_t *)pbPages = BECRC_MEM_ORG; + +#if 0 + Bs3TestPrintf("iTest=%d pbRip=%p cbOpcodes=%d: %.*Rhxs\n", + iTest, pbRip, cbOpcodes, cbOpcodes, s_aTests[iTest].abOpcodes); + //Bs3RegCtxPrint(&Ctx); +#endif + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); + if (s_aTests[iTest].bXcpt == X86_XCPT_UD) + ExpectCtxUd.rip = Ctx.rip; + if ( !Bs3TestCheckRegCtxEx(&TrapFrame.Ctx, apExpectCtxs[s_aTests[iTest].iExpectCtx], + 0 /*cbPcAdjust*/, 0 /*cbSpAdjust*/, 0 /*fExtraEfl*/, "mode", iTest) + || TrapFrame.bXcpt != s_aTests[iTest].bXcpt + || *(uint32_t *)pbPages != s_aTests[iTest].u32Stored) + { + Bs3TestFailedF("iTest=%d cbOpcodes=%d: %.*Rhxs\n", iTest, cbOpcodes, cbOpcodes, s_aTests[iTest].abOpcodes); + if (TrapFrame.bXcpt != s_aTests[iTest].bXcpt) + Bs3TestFailedF("Expected bXcpt=%#x, got %#x\n", s_aTests[iTest].bXcpt, TrapFrame.bXcpt); + if (*(uint32_t *)pbPages != s_aTests[iTest].u32Stored) + Bs3TestFailedF("Expected %#RX32 stored at %p, found: %RX32\n", + s_aTests[iTest].u32Stored, pbPages, *(uint32_t *)pbPages); + } + } + + Bs3MemGuardedTestPageFree(pbPages); + } + else + Bs3TestFailed("Failed to allocate two pages!\n"); +} + + + +/** + * Checks various prefix encodings with the CMPPS, CMPPD, CMPSS and CMPSD + * instructions to try figure out how they are decoded. + * + * The important thing to check here is that unlike CRC32/MOVBE the operand size + * prefix (66h) is ignored when the F2h and F3h prefixes are used. We also + * check that the prefix ordering is irrelevant and that the last one of F2h and + * F3h wins. + */ +static void DecodeCmppsCmppdCmpssCmpsd(void) +{ + uint8_t BS3_FAR *pbPages; + + /* Check that the instructions are supported. */ + if ( !(g_uBs3CpuDetected & BS3CPU_F_CPUID) + || (ASMCpuId_EDX(1) & (X86_CPUID_FEATURE_EDX_SSE | X86_CPUID_FEATURE_EDX_SSE2)) + != (X86_CPUID_FEATURE_EDX_SSE | X86_CPUID_FEATURE_EDX_SSE2) ) + { + Bs3TestSkipped("SSE and/or SSE2 are not supported"); + return; + } + + /* Setup a guarded page. */ + pbPages = Bs3MemGuardedTestPageAlloc(BS3MEMKIND_FLAT32); + if (pbPages) + { + unsigned iTest; + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + BS3REGCTX ExpectCtxPf; + BS3REGCTX ExpectCtxUd; + static const struct + { + RTUINT128U Xmm0Expect; + uint8_t bXcpt; + uint8_t cbOpcodes; + uint8_t abOpcodes[18]; + } s_aTests[] = + { +#define BECRC_IN_XMM1 RTUINT128_INIT_C(0x76547654bbaa9988, 0x7766554433221100) +#define BECRC_IN_XMM0 RTUINT128_INIT_C(0x765476549988bbaa, 0x7766554400112233) +#define BECRC_OUT_PS RTUINT128_INIT_C(0xffffffff00000000, 0xffffffff00000000) /* No prefix. */ +#define BECRC_OUT_PD RTUINT128_INIT_C(0x0000000000000000, 0x0000000000000000) /* P_OZ (66h) */ +#define BECRC_OUT_SS RTUINT128_INIT_C(0x765476549988bbaa, 0x7766554400000000) /* P_RZ (f3h) */ +#define BECRC_OUT_SD RTUINT128_INIT_C(0x765476549988bbaa, 0x0000000000000000) /* P_RN (f2h) */ + + /* We use imm8=0 which checks for equality, with the subvalue result being all + F's if equal and all zeros if not equal. The input values are choosen such + that the 4 variants produces different results in xmm0. */ + /* CMPPS xmm0, xmm1, 0: 0f c2 /r ib ; Compares four 32-bit subvalues. */ + /* CMPPD xmm0, xmm1, 0: 66 0f c2 /r ib ; Compares two 64-bit subvalues. */ + /* CMPSS xmm0, xmm1, 0: f3 0f c2 /r ib ; Compares two 32-bit subvalues, top 64-bit remains unchanged. */ + /* CMPSD xmm0, xmm1, 0: f2 0f c2 /r ib ; Compares one 64-bit subvalue, top 64-bit remains unchanged. */ + + /* base forms. */ + { BECRC_OUT_PS, X86_XCPT_PF, 4, { 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_PD, X86_XCPT_PF, 5, { P_OZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 5, { P_RZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 5, { P_RN, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + + /* Skylake+jaguar ignores the 66h prefix with both f3h (P_RZ) and f2h (P_RN). */ + { BECRC_OUT_SS, X86_XCPT_PF, 6, { P_OZ, P_RZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 6, { P_RZ, P_OZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 6, { P_OZ, P_RN, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 6, { P_RN, P_OZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + + /* Throw in segment prefixes and address size prefixes. */ + { BECRC_OUT_PS, X86_XCPT_PF, 5, { P_ES, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_PS, X86_XCPT_PF, 6, { P_ES, P_SS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_PS, X86_XCPT_PF, 5, { P_AZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_PS, X86_XCPT_PF, 6, { P_AZ, P_CS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + + { BECRC_OUT_PD, X86_XCPT_PF, 6, { P_ES, P_OZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_PD, X86_XCPT_PF, 6, { P_OZ, P_ES, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_PD, X86_XCPT_PF, 7, { P_ES, P_SS, P_OZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_PD, X86_XCPT_PF, 7, { P_ES, P_OZ, P_SS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_PD, X86_XCPT_PF, 7, { P_OZ, P_ES, P_SS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_PD, X86_XCPT_PF, 6, { P_AZ, P_OZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_PD, X86_XCPT_PF, 6, { P_OZ, P_AZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_PD, X86_XCPT_PF, 7, { P_AZ, P_CS, P_OZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_PD, X86_XCPT_PF, 7, { P_AZ, P_OZ, P_CS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_PD, X86_XCPT_PF, 7, { P_OZ, P_AZ, P_CS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + + { BECRC_OUT_SS, X86_XCPT_PF, 6, { P_ES, P_RZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 6, { P_RZ, P_ES, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 7, { P_ES, P_SS, P_RZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 7, { P_ES, P_RZ, P_SS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 7, { P_RZ, P_ES, P_SS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 6, { P_AZ, P_RZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 6, { P_RZ, P_AZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 7, { P_AZ, P_CS, P_RZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 7, { P_AZ, P_RZ, P_CS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 7, { P_RZ, P_AZ, P_CS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 8, { P_OZ, P_RZ, P_AZ, P_CS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 8, { P_RZ, P_OZ, P_AZ, P_CS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 8, { P_RZ, P_AZ, P_OZ, P_CS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 8, { P_RZ, P_AZ, P_CS, P_OZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + + { BECRC_OUT_SD, X86_XCPT_PF, 6, { P_ES, P_RN, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 6, { P_RN, P_ES, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 7, { P_ES, P_SS, P_RN, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 7, { P_ES, P_RN, P_SS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 7, { P_RN, P_ES, P_SS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 6, { P_AZ, P_RN, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 6, { P_RN, P_AZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 7, { P_AZ, P_CS, P_RN, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 7, { P_AZ, P_RN, P_CS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 7, { P_RN, P_AZ, P_CS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 8, { P_OZ, P_RN, P_AZ, P_CS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 8, { P_RN, P_OZ, P_AZ, P_CS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 8, { P_RN, P_AZ, P_OZ, P_CS, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 8, { P_RN, P_AZ, P_CS, P_OZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + + /* Pit f2h against f3h, on skylake+jaguar the last prefix wins. */ + { BECRC_OUT_SS, X86_XCPT_PF, 6, { P_RN, P_RZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 7, { P_RN, P_RN, P_RZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 7, { P_RZ, P_RN, P_RZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 7, { P_RN, P_RZ, P_RZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 8, { P_RN, P_RN, P_RN, P_RZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 8, { P_RN, P_RN, P_RZ, P_RZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 8, { P_RN, P_RZ, P_RN, P_RZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 8, { P_RZ, P_RN, P_RN, P_RZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 8, { P_RZ, P_RZ, P_RN, P_RZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SS, X86_XCPT_PF, 8, { P_RN, P_RZ, P_RZ, P_RZ, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + + { BECRC_OUT_SD, X86_XCPT_PF, 6, { P_RZ, P_RN, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 7, { P_RZ, P_RZ, P_RN, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 7, { P_RN, P_RZ, P_RN, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 7, { P_RZ, P_RN, P_RN, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 8, { P_RZ, P_RZ, P_RZ, P_RN, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 8, { P_RZ, P_RZ, P_RN, P_RN, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 8, { P_RZ, P_RN, P_RZ, P_RN, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 8, { P_RN, P_RZ, P_RZ, P_RN, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 8, { P_RN, P_RN, P_RZ, P_RN, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + { BECRC_OUT_SD, X86_XCPT_PF, 8, { P_RZ, P_RN, P_RN, P_RN, 0x0f, 0xc2, RM_XMM0_XMM1, 0 } }, + }; + RTUINT128U InXmm0 = BECRC_IN_XMM0; + RTUINT128U InXmm1 = BECRC_IN_XMM1; + RTUINT128U OutXmm0 = RTUINT128_INIT_C(0xeeeeeeeeeeeeeeee, 0xcccccccccccccccc); + + Bs3MemZero(&Ctx, sizeof(Ctx)); + Bs3MemZero(&ExpectCtxPf, sizeof(ExpectCtxPf)); + Bs3MemZero(&ExpectCtxUd, sizeof(ExpectCtxUd)); + Bs3MemZero(&TrapFrame, sizeof(TrapFrame)); + + /* Enable SSE. */ + ASMSetCR0((ASMGetCR0() & ~(X86_CR0_EM | X86_CR0_TS)) | X86_CR0_MP); + ASMSetCR4(ASMGetCR4() | X86_CR4_OSFXSR); + + /* Create a test context. */ + Bs3RegCtxSaveEx(&Ctx, BS3_MODE_CODE_32, 512); + Ctx.rax.u = BECRC_EAX; + Ctx.rbx.u = (uintptr_t)pbPages; + + /* Create expected result contexts. */ + Bs3MemCpy(&ExpectCtxPf, &Ctx, sizeof(ExpectCtxPf)); + ExpectCtxPf.rflags.u32 |= X86_EFL_RF; + ExpectCtxPf.rip.u = (uintptr_t)&pbPages[X86_PAGE_SIZE]; + ExpectCtxPf.cr2.u = (uintptr_t)&pbPages[X86_PAGE_SIZE]; + + Bs3MemCpy(&ExpectCtxUd, &Ctx, sizeof(ExpectCtxUd)); + ExpectCtxUd.rflags.u32 |= X86_EFL_RF; + + /* Loop thru the tests. */ + g_usBs3TestStep = 0; + for (iTest = 0; iTest < RT_ELEMENTS(s_aTests); iTest++) + { + unsigned const cbOpcodes = s_aTests[iTest].cbOpcodes; + uint8_t BS3_FAR *pbRip = &pbPages[X86_PAGE_SIZE - cbOpcodes]; + + Bs3MemCpy(pbRip, s_aTests[iTest].abOpcodes, cbOpcodes); + Bs3RegCtxSetRipCsFromFlat(&Ctx, (uintptr_t)pbRip); + ExpectCtxUd.rip = Ctx.rip; +#if 0 + Bs3TestPrintf("iTest=%d pbRip=%p cbOpcodes=%d: %.*Rhxs\n", + iTest, pbRip, cbOpcodes, cbOpcodes, s_aTests[iTest].abOpcodes); + //Bs3RegCtxPrint(&Ctx); +#endif + BS3_CMN_NM(bs3CpuDecoding1_LoadXmm0)(&InXmm0); + BS3_CMN_NM(bs3CpuDecoding1_LoadXmm1)(&InXmm1); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); + BS3_CMN_NM(bs3CpuDecoding1_SaveXmm0)(&OutXmm0); + + if ( !Bs3TestCheckRegCtxEx(&TrapFrame.Ctx, s_aTests[iTest].bXcpt == X86_XCPT_UD ? &ExpectCtxUd : &ExpectCtxPf, + 0 /*cbPcAdjust*/, 0 /*cbSpAdjust*/, 0 /*fExtraEfl*/, "mode", iTest) + || TrapFrame.bXcpt != s_aTests[iTest].bXcpt + || OutXmm0.s.Lo != s_aTests[iTest].Xmm0Expect.s.Lo + || OutXmm0.s.Hi != s_aTests[iTest].Xmm0Expect.s.Hi) + { + Bs3TestFailedF("iTest=%d cbOpcodes=%d: %.*Rhxs\n", iTest, cbOpcodes, cbOpcodes, s_aTests[iTest].abOpcodes); + if (TrapFrame.bXcpt != s_aTests[iTest].bXcpt) + Bs3TestFailedF("Expected bXcpt=%#x, got %#x\n", s_aTests[iTest].bXcpt, TrapFrame.bXcpt); + if ( OutXmm0.s.Lo != s_aTests[iTest].Xmm0Expect.s.Lo + || OutXmm0.s.Hi != s_aTests[iTest].Xmm0Expect.s.Hi) + Bs3TestFailedF("Expected XMM0=%08RX32:%08RX32:%08RX32:%08RX32, not %08RX32:%08RX32:%08RX32:%08RX32\n", + s_aTests[iTest].Xmm0Expect.DWords.dw3, s_aTests[iTest].Xmm0Expect.DWords.dw2, + s_aTests[iTest].Xmm0Expect.DWords.dw1, s_aTests[iTest].Xmm0Expect.DWords.dw0, + OutXmm0.DWords.dw3, OutXmm0.DWords.dw2, OutXmm0.DWords.dw1, OutXmm0.DWords.dw0); + } + } + + Bs3MemGuardedTestPageFree(pbPages); + } + else + Bs3TestFailed("Failed to allocate two pages!\n"); +} + + +BS3_DECL(void) Main_pp32() +{ + Bs3TestInit("bs3-cpu-decoding-1"); + Bs3TestPrintf("g_uBs3CpuDetected=%#x\n", g_uBs3CpuDetected); + +#if 0 + Bs3TestSub("CMPPS, CMPPD, CMPSS, CMPSD"); + DecodeCmppsCmppdCmpssCmpsd(); + + Bs3TestSub("MOVBE vs CRC32"); + DecodeMovbeVsCrc32(); +#endif + + //Bs3TestSub("CMPXCHG8B/16B"); + //DecodeCmpXchg8bVs16b(); + +#if 1 + Bs3TestSub("2 byte undefined opcodes 0f"); + DecodeUdEdgeTest(g_aUdTest2Byte_0f, RT_ELEMENTS(g_aUdTest2Byte_0f)); +#endif +#if 0 + Bs3TestSub("3 byte undefined opcodes 0f 38"); + DecodeUdEdgeTest(g_aUdTest3Byte_0f_38, RT_ELEMENTS(g_aUdTest3Byte_0f_38)); +#endif + +#if 0 + Bs3TestSub("misc"); + DecodeEdgeTest(); +#endif + + Bs3TestTerm(); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-generated-1-asm.asm b/src/VBox/ValidationKit/bootsectors/bs3-cpu-generated-1-asm.asm new file mode 100644 index 00000000..78132fe8 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-generated-1-asm.asm @@ -0,0 +1,44 @@ +; $Id: bs3-cpu-generated-1-asm.asm $ +;; @file +; BS3Kit - bs3-generated-1, assembly helpers and template instantiation. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit.mac" + +; later maybe. + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-generated-1-data.py b/src/VBox/ValidationKit/bootsectors/bs3-cpu-generated-1-data.py new file mode 100755 index 00000000..42a8234d --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-generated-1-data.py @@ -0,0 +1,664 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: bs3-cpu-generated-1-data.py $ +# pylint: disable=invalid-name + +""" +Generates testcases from @optest specifications in IEM. +""" + +from __future__ import print_function; + +__copyright__ = \ +""" +Copyright (C) 2017-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + +# Standard python imports. +import os; +import sys; + +# Only the main script needs to modify the path. +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))); +g_ksVmmAllDir = os.path.join(os.path.dirname(g_ksValidationKitDir), 'VMM', 'VMMAll') +sys.path.append(g_ksVmmAllDir); + +import IEMAllInstructionsPython as iai; # pylint: disable=import-error + + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + + +class Bs3Cg1TestEncoder(object): + """ + Does the encoding of a single test. + """ + + def __init__(self, fLast): + self.fLast = fLast; + # Each list member (in all lists) are C expression of a byte. + self.asHdr = []; + self.asSelectors = []; + self.asInputs = []; + self.asOutputs = []; + + @staticmethod + def _compileSelectors(aoSelectors): # (list(iai.TestSelector)) -> list(str) + """ + Compiles a list of iai.TestSelector predicate checks. + Returns C byte expression strings. + """ + asRet = []; + for oSelector in aoSelectors: + sConstant = oSelector.kdVariables[oSelector.sVariable][oSelector.sValue]; + sConstant = sConstant.upper().replace('.', '_'); + if oSelector.sOp == '==': + sByte = '(BS3CG1PRED_%s << BS3CG1SEL_OP_PRED_SHIFT) | BS3CG1SEL_OP_IS_TRUE' % (sConstant,); + elif oSelector.sOp == '!=': + sByte = '(BS3CG1PRED_%s << BS3CG1SEL_OP_PRED_SHIFT) | BS3CG1SEL_OP_IS_FALSE' % (sConstant,); + else: + raise Exception('Unknown selector operator: %s' % (oSelector.sOp,)); + asRet.append(sByte); + return asRet; + + kdSmallFields = { + 'op1': 'BS3CG1_CTXOP_OP1', + 'op2': 'BS3CG1_CTXOP_OP2', + 'efl': 'BS3CG1_CTXOP_EFL', + }; + kdOperators = { + '=': 'BS3CG1_CTXOP_ASSIGN', + '|=': 'BS3CG1_CTXOP_OR', + '&=': 'BS3CG1_CTXOP_AND', + '&~=': 'BS3CG1_CTXOP_AND_INV', + }; + kdSmallSizes = { + 1: 'BS3CG1_CTXOP_1_BYTE', + 2: 'BS3CG1_CTXOP_2_BYTES', + 4: 'BS3CG1_CTXOP_4_BYTES', + 8: 'BS3CG1_CTXOP_8_BYTES', + 16: 'BS3CG1_CTXOP_16_BYTES', + 32: 'BS3CG1_CTXOP_32_BYTES', + 12: 'BS3CG1_CTXOP_12_BYTES', + }; + + @staticmethod + def _amendOutputs(aoOutputs, oInstr): # type: (list(iai.TestInOut), iai.Instruction) -> list(iai.TestInOut) + """ + Amends aoOutputs for instructions with special flag behaviour (undefined, + always set, always clear). + + Undefined flags are copied from the result context as the very first + operation so they can be set to CPU vendor specific values later if + desired. + + Always set or cleared flags are applied at the very end of the + modification operations so that we spot incorrect specifications. + """ + if oInstr.asFlUndefined or oInstr.asFlClear or oInstr.asFlSet: + aoOutputs = list(aoOutputs); + + if oInstr.asFlUndefined: + fFlags = oInstr.getUndefinedFlagsMask(); + assert fFlags != 0; + aoOutputs.insert(0, iai.TestInOut('efl_undef', '=', str(fFlags), 'uint')); + + if oInstr.asFlClear: + fFlags = oInstr.getClearedFlagsMask(); + assert fFlags != 0; + aoOutputs.append(iai.TestInOut('efl', '&~=', str(fFlags), 'uint')); + + if oInstr.asFlSet: + fFlags = oInstr.getSetFlagsMask(); + assert fFlags != 0; + aoOutputs.append(iai.TestInOut('efl', '|=', str(fFlags), 'uint')); + + return aoOutputs; + + @staticmethod + def _compileContextModifers(aoOperations): # (list(iai.TestInOut)) + """ + Compile a list of iai.TestInOut context modifiers. + """ + asRet = []; + for oOperation in aoOperations: + oType = iai.TestInOut.kdTypes[oOperation.sType]; + aaoValues = oType.get(oOperation.sValue); + assert len(aaoValues) == 1 or len(aaoValues) == 2; + + sOp = oOperation.sOp; + if sOp == '&|=': + sOp = '|=' if len(aaoValues) == 1 else '&~='; + + for fSignExtend, abValue in aaoValues: + cbValue = len(abValue); + + # The opcode byte. + sOpcode = Bs3Cg1TestEncoder.kdOperators[sOp]; + sOpcode += ' | '; + if oOperation.sField in Bs3Cg1TestEncoder.kdSmallFields: + sOpcode += Bs3Cg1TestEncoder.kdSmallFields[oOperation.sField]; + else: + sOpcode += 'BS3CG1_CTXOP_DST_ESC'; + sOpcode += ' | '; + if cbValue in Bs3Cg1TestEncoder.kdSmallSizes: + sOpcode += Bs3Cg1TestEncoder.kdSmallSizes[cbValue]; + else: + sOpcode += 'BS3CG1_CTXOP_SIZE_ESC'; + if fSignExtend: + sOpcode += ' | BS3CG1_CTXOP_SIGN_EXT'; + asRet.append(sOpcode); + + # Escaped field identifier. + if oOperation.sField not in Bs3Cg1TestEncoder.kdSmallFields: + asRet.append('BS3CG1DST_%s' % (oOperation.sField.upper().replace('.', '_'),)); + + # Escaped size byte? + if cbValue not in Bs3Cg1TestEncoder.kdSmallSizes: + if cbValue >= 256 or cbValue not in [ 1, 2, 4, 6, 8, 12, 16, 32, 64, 128, ]: + raise Exception('Invalid value size: %s' % (cbValue,)); + asRet.append('0x%02x' % (cbValue,)); + + # The value bytes. + for b in abValue: + asRet.append('0x%02x' % (b,)); + + sOp = '|='; + + return asRet; + + def _constructHeader(self): + """ + Returns C byte expression strings for BS3CG1TESTHDR. + """ + cbSelectors = len(self.asSelectors); + if cbSelectors >= 256: + raise Exception('Too many selectors: %s bytes, max 255 bytes' % (cbSelectors,)) + + cbInputs = len(self.asInputs); + if cbInputs >= 4096: + raise Exception('Too many input context modifiers: %s bytes, max 4095 bytes' % (cbInputs,)) + + cbOutputs = len(self.asOutputs); + if cbOutputs >= 2048: + raise Exception('Too many output context modifiers: %s bytes, max 2047 bytes' % (cbOutputs,)) + + return [ + '%#04x' % (cbSelectors,), # 8-bit + '%#05x & 0xff' % (cbInputs,), # first 8 bits of cbInputs + '(%#05x >> 8) | ((%#05x & 0xf) << 4)' % (cbInputs, cbOutputs,), # last 4 bits of cbInputs, lower 4 bits of cbOutputs. + '(%#05x >> 4) | (%#05x << 7)' % (cbOutputs, self.fLast), # last 7 bits of cbOutputs and 1 bit fLast. + ]; + + def encodeTest(self, oTest): # type: (iai.InstructionTest) + """ + Does the encoding. + """ + self.asSelectors = self._compileSelectors(oTest.aoSelectors); + self.asInputs = self._compileContextModifers(oTest.aoInputs); + self.asOutputs = self._compileContextModifers(self._amendOutputs(oTest.aoOutputs, oTest.oInstr)); + self.asHdr = self._constructHeader(); + + +class Bs3Cg1EncodedTests(object): + """ + Encodes the tests for an instruction. + """ + + def __init__(self, oInstr): + self.offTests = -1; + self.cbTests = 0; + self.asLines = [] # type: list(str) + self.aoInstructions = [] # type: list(iai.Instruction) + + # Encode the tests. + for iTest, oTest in enumerate(oInstr.aoTests): + oEncodedTest = Bs3Cg1TestEncoder(iTest + 1 == len(oInstr.aoTests)); + oEncodedTest.encodeTest(oTest); + + self.cbTests += len(oEncodedTest.asHdr) + len(oEncodedTest.asSelectors) \ + + len(oEncodedTest.asInputs) + len(oEncodedTest.asOutputs); + + self.asLines.append(' /* test #%s: %s */' % (iTest, oTest,)); + self.asLines += self.bytesToLines(' ', oEncodedTest.asHdr); + if oEncodedTest.asSelectors: + self.asLines += self.bytesToLines(' /*sel:*/ ', oEncodedTest.asSelectors); + if oEncodedTest.asInputs: + self.asLines += self.bytesToLines(' /* in:*/ ', oEncodedTest.asInputs); + if oEncodedTest.asOutputs: + self.asLines += self.bytesToLines(' /*out:*/ ', oEncodedTest.asOutputs); + + @staticmethod + def bytesToLines(sPrefix, asBytes): + """ + Formats a series of bytes into one or more lines. + A byte ending with a newline indicates that we should start a new line, + and prefix it by len(sPrefix) spaces. + + Returns list of lines. + """ + asRet = []; + sLine = sPrefix; + for sByte in asBytes: + if sByte[-1] == '\n': + sLine += sByte[:-1] + ','; + asRet.append(sLine); + sLine = ' ' * len(sPrefix); + else: + if len(sLine) + 2 + len(sByte) > 132 and len(sLine) > len(sPrefix): + asRet.append(sLine[:-1]); + sLine = ' ' * len(sPrefix); + sLine += sByte + ', '; + + + if len(sLine) > len(sPrefix): + asRet.append(sLine); + return asRet; + + + def isEqual(self, oOther): + """ Compares two encoded tests. """ + if self.cbTests != oOther.cbTests: + return False; + if len(self.asLines) != len(oOther.asLines): + return False; + for iLine, sLines in enumerate(self.asLines): + if sLines != oOther.asLines[iLine]: + return False; + return True; + + + +class Bs3Cg1Instruction(object): + """ + An instruction with tests. + """ + + def __init__(self, oMap, oInstr, oTests): + self.oMap = oMap # type: iai.InstructionMap + self.oInstr = oInstr # type: iai.Instruction + self.oTests = oTests # type: Bs3Cg1EncodedTests + + self.asOpcodes = oMap.asLeadOpcodes + [ '0x%02x' % (oInstr.getOpcodeByte(),) ]; + self.sEncoding = iai.g_kdEncodings[oInstr.sEncoding][0]; + + for oOp in oInstr.aoOperands: + self.sEncoding += '_' + oOp.sType; + if oInstr.sSubOpcode and iai.g_kdSubOpcodes[oInstr.sSubOpcode][1]: + self.sEncoding += '_' + iai.g_kdSubOpcodes[oInstr.sSubOpcode][1]; + + if oInstr.fUnused: + if oInstr.sInvalidStyle == 'immediate' and oInstr.sSubOpcode: + self.sEncoding += '_MOD_EQ_3' if oInstr.sSubOpcode == '11 mr/reg' else '_MOD_NE_3'; + elif oInstr.sInvalidStyle == 'intel-modrm': + if oInstr.sSubOpcode is None: + self.sEncoding = 'BS3CG1ENC_MODRM_Gv_Ev'; + elif oInstr.sSubOpcode == '11 mr/reg': + self.sEncoding = 'BS3CG1ENC_MODRM_MOD_EQ_3'; + elif oInstr.sSubOpcode == '!11 mr/reg': + self.sEncoding = 'BS3CG1ENC_MODRM_MOD_NE_3'; + else: + raise Exception('Unhandled sSubOpcode=%s for sInvalidStyle=%s' % (oInstr.sSubOpcode, oInstr.sInvalidStyle)); + elif oInstr.sInvalidStyle == 'vex.modrm': + self.sEncoding = 'BS3CG1ENC_VEX_MODRM'; + + self.asFlags = []; + if 'invalid_64' in oInstr.dHints: + self.asFlags.append('BS3CG1INSTR_F_INVALID_64BIT'); + if oInstr.fUnused: + self.asFlags.append('BS3CG1INSTR_F_UNUSED'); + elif oInstr.fInvalid: + self.asFlags.append('BS3CG1INSTR_F_INVALID'); + if oInstr.sInvalidStyle and oInstr.sInvalidStyle.startswith('intel-'): + self.asFlags.append('BS3CG1INSTR_F_INTEL_DECODES_INVALID'); + if 'vex_l_zero' in oInstr.dHints: + self.asFlags.append('BS3CG1INSTR_F_VEX_L_ZERO'); + if 'vex_l_ignored' in oInstr.dHints: + self.asFlags.append('BS3CG1INSTR_F_VEX_L_IGNORED'); + + self.fAdvanceMnemonic = True; ##< Set by the caller. + if oInstr.sPrefix: + if oInstr.sPrefix == 'none': + self.sPfxKind = 'BS3CG1PFXKIND_NO_F2_F3_66'; + else: + self.sPfxKind = 'BS3CG1PFXKIND_REQ_' + oInstr.sPrefix[-2:].upper(); + elif oInstr.sEncoding == 'ModR/M': + if 'ignores_op_size' not in oInstr.dHints: + self.sPfxKind = 'BS3CG1PFXKIND_MODRM'; + else: + self.sPfxKind = 'BS3CG1PFXKIND_MODRM_NO_OP_SIZES'; + else: + self.sPfxKind = '0'; + + self.sCpu = 'BS3CG1CPU_'; + assert len(oInstr.asCpuIds) in [0, 1], str(oInstr); + if oInstr.asCpuIds: + self.sCpu += oInstr.asCpuIds[0].upper().replace('.', '_'); + elif oInstr.sMinCpu: + self.sCpu += 'GE_' + oInstr.sMinCpu; + else: + self.sCpu += 'ANY'; + + if oInstr.sXcptType: + self.sXcptType = 'BS3CG1XCPTTYPE_' + oInstr.sXcptType.upper(); + else: + self.sXcptType = 'BS3CG1XCPTTYPE_NONE'; + + def getOperands(self): + """ Returns comma separated string of operand values for g_abBs3Cg1Operands. """ + return ', '.join(['(uint8_t)BS3CG1OP_%s' % (oOp.sType,) for oOp in self.oInstr.aoOperands]); + + def getOpcodeMap(self): + """ Returns the opcode map number for the BS3CG1INSTR structure. """ + sEncoding = self.oInstr.aoMaps[0].sEncoding; + if sEncoding == 'legacy': return 0; + if sEncoding == 'vex1': return 1; + if sEncoding == 'vex2': return 2; + if sEncoding == 'vex3': return 3; + if sEncoding == 'xop8': return 8; + if sEncoding == 'xop9': return 9; + if sEncoding == 'xop10': return 10; + assert False, sEncoding; + return 3; + + def getInstructionEntry(self): + """ Returns an array of BS3CG1INSTR member initializers. """ + assert len(self.oInstr.sMnemonic) < 16; + sOperands = ', '.join([oOp.sType for oOp in self.oInstr.aoOperands]); + if sOperands: + sOperands = ' /* ' + sOperands + ' */'; + return [ + ' /* cbOpcodes = */ %s, /* %s */' % (len(self.asOpcodes), ' '.join(self.asOpcodes),), + ' /* cOperands = */ %s,%s' % (len(self.oInstr.aoOperands), sOperands,), + ' /* cchMnemonic = */ %s, /* %s */' % (len(self.oInstr.sMnemonic), self.oInstr.sMnemonic,), + ' /* fAdvanceMnemonic = */ %s,' % ('true' if self.fAdvanceMnemonic else 'false',), + ' /* offTests = */ %s,' % (self.oTests.offTests,), + ' /* enmEncoding = */ (unsigned)%s,' % (self.sEncoding,), + ' /* uOpcodeMap = */ (unsigned)%s,' % (self.getOpcodeMap(),), + ' /* enmPrefixKind = */ (unsigned)%s,' % (self.sPfxKind,), + ' /* enmCpuTest = */ (unsigned)%s,' % (self.sCpu,), + ' /* enmXcptType = */ (unsigned)%s,' % (self.sXcptType,), + ' /* uUnused = */ 0,', + ' /* fFlags = */ %s' % (' | '.join(self.asFlags) if self.asFlags else '0'), + ]; + + +class Bs3CpuGenerated1Generator(object): + """ + The generator code for bs3-cpu-generated-1. + """ + + def __init__(self): + self.aoInstructions = [] # type: Bs3Cg1Instruction + self.aoTests = [] # type: Bs3Cg1EncodedTests + self.cbTests = 0; + + def addTests(self, oTests, oInstr): # type: (Bs3Cg1EncodedTests, iai.Instruction) -> Bs3Cg1EncodedTests + """ + Adds oTests to self.aoTests, setting the oTests.offTests member. + Checks for and eliminates duplicates. + Returns the tests to use. + """ + # Check for duplicates. + for oExisting in self.aoTests: + if oTests.isEqual(oExisting): + oExisting.aoInstructions.append(oInstr); + return oExisting; + + # New test, so add it. + oTests.offTests = self.cbTests; + self.aoTests.append(oTests); + self.cbTests += oTests.cbTests; + + assert not oTests.aoInstructions; + oTests.aoInstructions.append(oInstr); + + return oTests; + + def processInstruction(self): + """ + Processes the IEM specified instructions. + Returns success indicator. + """ + + # + # Group instructions by mnemonic to reduce the number of sub-tests. + # + for oInstr in sorted(iai.g_aoAllInstructions, + key = lambda oInstr: oInstr.sMnemonic + ''.join([oOp.sType for oOp in oInstr.aoOperands]) + + (oInstr.sOpcode if oInstr.sOpcode else 'zz')): + if oInstr.aoTests: + oTests = Bs3Cg1EncodedTests(oInstr); + oTests = self.addTests(oTests, oInstr); + + for oMap in oInstr.aoMaps: + self.aoInstructions.append(Bs3Cg1Instruction(oMap, oInstr, oTests)); + + # Set fAdvanceMnemonic. + for iInstr, oInstr in enumerate(self.aoInstructions): + oInstr.fAdvanceMnemonic = iInstr + 1 >= len(self.aoInstructions) \ + or oInstr.oInstr.sMnemonic != self.aoInstructions[iInstr + 1].oInstr.sMnemonic; + + return True; + + def generateCode(self, oOut): + """ + Generates the C code. + Returns success indicator. + """ + + # First, a file header. + asLines = [ + '/*', + ' * Autogenerated by $Id: bs3-cpu-generated-1-data.py $ ', + ' * Do not edit!', + ' */', + '', + '/*', + ' * Copyright (C) 2017-2022 Oracle and/or its affiliates.', + ' *', + ' * This file is part of VirtualBox base platform packages, as', + ' * available from https://www.virtualbox.org.', + ' *', + ' * This program is free software; you can redistribute it and/or', + ' * modify it under the terms of the GNU General Public License', + ' * as published by the Free Software Foundation, in version 3 of the', + ' * License.', + ' *', + ' * This program is distributed in the hope that it will be useful, but', + ' * WITHOUT ANY WARRANTY; without even the implied warranty of', + ' * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU', + ' * General Public License for more details.', + ' *', + ' * You should have received a copy of the GNU General Public License', + ' * along with this program; if not, see <https://www.gnu.org/licenses>.', + ' *', + ' * The contents of this file may alternatively be used under the terms', + ' * of the Common Development and Distribution License Version 1.0', + ' * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included', + ' * in the VirtualBox distribution, in which case the provisions of the', + ' * CDDL are applicable instead of those of the GPL.', + ' *', + ' * You may elect to license modified versions of this file under the', + ' * terms and conditions of either the GPL or the CDDL or both.', + ' *', + ' * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0', + ' */', + '', + '', + '#include "bs3-cpu-generated-1.h"', + '', + '', + '#pragma data_seg ("BS3DATA16")', + ]; + + # Generate the g_achBs3Cg1Mnemonics array. + asLines += [ + 'const char BS3_FAR_DATA g_achBs3Cg1Mnemonics[] = ', + '{', + ]; + fAdvanceMnemonic = True; + for oInstr in self.aoInstructions: + if fAdvanceMnemonic: + asLines.append(' \"%s\"' % (oInstr.oInstr.sMnemonic,)); + fAdvanceMnemonic = oInstr.fAdvanceMnemonic; + asLines += [ + '};', + '', + '', + ]; + + # Generate the g_abBs3Cg1Opcodes array. + asLines += [ + 'const uint8_t BS3_FAR_DATA g_abBs3Cg1Opcodes[] = ', + '{', + ]; + for oInstr in self.aoInstructions: + asLines.append(' ' + ', '.join(oInstr.asOpcodes) + ','); + asLines += [ + '};', + '', + '', + ]; + + # Generate the g_abBs3Cg1Opcodes array. + asLines += [ + 'const uint8_t BS3_FAR_DATA g_abBs3Cg1Operands[] = ', + '{', + ]; + cOperands = 0; + for oInstr in self.aoInstructions: + if oInstr.oInstr.aoOperands: + cOperands += len(oInstr.oInstr.aoOperands); + asLines.append(' ' + oInstr.getOperands() + ', /* %s */' % (oInstr.oInstr.sStats,)); + else: + asLines.append(' /* none */'); + if not cOperands: + asLines.append(' 0 /* dummy */'); + asLines += [ + '};', + '', + '', + ]; + + # Generate the g_abBs3Cg1Operands array. + asLines += [ + 'const BS3CG1INSTR BS3_FAR_DATA g_aBs3Cg1Instructions[] = ', + '{', + ]; + for oInstr in self.aoInstructions: + asLines.append(' {'); + asLines += oInstr.getInstructionEntry(); + asLines.append(' },'); + asLines += [ + '};', + 'const uint16_t BS3_FAR_DATA g_cBs3Cg1Instructions = RT_ELEMENTS(g_aBs3Cg1Instructions);', + '', + '', + ]; + + # Generate the g_abBs3Cg1Tests array. + asLines += [ + 'const uint8_t BS3_FAR_DATA g_abBs3Cg1Tests[] = ', + '{', + ]; + for oTests in self.aoTests: + asLines.append(' /*'); + asLines.append(' * offTests=%s' % (oTests.offTests,)); + asLines.append(' * Instructions: %s' % (', '.join([oInstr.sStats for oInstr in oTests.aoInstructions]),)); + asLines.append(' */'); + asLines += oTests.asLines; + asLines += [ + '};', + '', + ]; + + + #/** The test data that BS3CG1INSTR. + # * In order to simplify generating these, we use a byte array. */ + #extern const uint8_t BS3_FAR_DATA g_abBs3Cg1Tests[]; + + + oOut.write('\n'.join(asLines)); + return True; + + + def usage(self): + """ Prints usage. """ + print('usage: bs3-cpu-generated-1-data.py [output file|-]'); + return 0; + + def main(self, asArgs): + """ + C-like main function. + Returns exit code. + """ + + # + # Quick argument parsing. + # + if len(asArgs) == 1: + sOutFile = '-'; + elif len(asArgs) != 2: + print('syntax error! Expected exactly one argument.'); + return 2; + elif asArgs[1] in [ '-h', '-?', '--help' ]: + return self.usage(); + else: + sOutFile = asArgs[1]; + + # + # Process the instructions specified in the IEM sources. + # + if self.processInstruction(): + + # + # Open the output file and generate the code. + # + if sOutFile == '-': + oOut = sys.stdout; + else: + try: + oOut = open(sOutFile, 'w'); # pylint: disable=consider-using-with,unspecified-encoding + except Exception as oXcpt: + print('error! Failed open "%s" for writing: %s' % (sOutFile, oXcpt,)); + return 1; + if self.generateCode(oOut): + return 0; + + return 1; + + +if __name__ == '__main__': + sys.exit(Bs3CpuGenerated1Generator().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-generated-1-template.c b/src/VBox/ValidationKit/bootsectors/bs3-cpu-generated-1-template.c new file mode 100644 index 00000000..e217a6a5 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-generated-1-template.c @@ -0,0 +1,6152 @@ +/* $Id: bs3-cpu-generated-1-template.c $ */ +/** @file + * BS3Kit - bs3-cpu-generated-1, C code template. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef BS3_INSTANTIATING_CMN +# error "BS3_INSTANTIATING_CMN not defined" +#endif + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/asm-amd64-x86.h> + +#include "bs3-cpu-generated-1.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define BS3CG1_WITH_VEX + +#define P_CS X86_OP_PRF_CS +#define P_SS X86_OP_PRF_SS +#define P_DS X86_OP_PRF_DS +#define P_ES X86_OP_PRF_ES +#define P_FS X86_OP_PRF_FS +#define P_GS X86_OP_PRF_GS +#define P_OZ X86_OP_PRF_SIZE_OP +#define P_AZ X86_OP_PRF_SIZE_ADDR +#define P_LK X86_OP_PRF_LOCK +#define P_RN X86_OP_PRF_REPNZ +#define P_RZ X86_OP_PRF_REPZ + +#define REX_WRBX (X86_OP_REX_W | X86_OP_REX_R | X86_OP_REX_B | X86_OP_REX_X) +#define REX_W___ (X86_OP_REX_W) +#define REX_WR__ (X86_OP_REX_W | X86_OP_REX_R) +#define REX_W_B_ (X86_OP_REX_W | X86_OP_REX_B) +#define REX_W__X (X86_OP_REX_W | X86_OP_REX_X) +#define REX_WRB_ (X86_OP_REX_W | X86_OP_REX_R | X86_OP_REX_B) +#define REX_WR_X (X86_OP_REX_W | X86_OP_REX_R | X86_OP_REX_X) +#define REX_W_BX (X86_OP_REX_W | X86_OP_REX_B | X86_OP_REX_X) +#define REX__R__ (X86_OP_REX_R) +#define REX__RB_ (X86_OP_REX_R | X86_OP_REX_B) +#define REX__R_X (X86_OP_REX_R | X86_OP_REX_X) +#define REX__RBX (X86_OP_REX_R | X86_OP_REX_B | X86_OP_REX_X) +#define REX___B_ (X86_OP_REX_B) +#define REX___BX (X86_OP_REX_B | X86_OP_REX_X) +#define REX____X (X86_OP_REX_X) +#define REX_____ (0x40) + + +/** @def BS3CG1_DPRINTF + * Debug print macro. + */ +#if 0 +# define BS3CG1_DPRINTF(a_ArgList) Bs3TestPrintf a_ArgList +# define BS3CG1_DEBUG_CTX_MOD +#else +# define BS3CG1_DPRINTF(a_ArgList) do { } while (0) +#endif + +/** + * Checks if this is a 64-bit test target or not. + * Helps avoid ifdefs or code bloat. + */ +#if ARCH_BITS == 64 +# define BS3CG1_IS_64BIT_TARGET(a_pThis) BS3_MODE_IS_64BIT_CODE((a_pThis)->bMode) +#else +# define BS3CG1_IS_64BIT_TARGET(a_pThis) (false) +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Operand value location. */ +typedef enum BS3CG1OPLOC +{ + BS3CG1OPLOC_INVALID = 0, + BS3CG1OPLOC_CTX, + BS3CG1OPLOC_CTX_ZX_VLMAX, + BS3CG1OPLOC_IMM, + BS3CG1OPLOC_MEM, + BS3CG1OPLOC_MEM_RW, + BS3CG1OPLOC_MEM_WO, + BS3CG1OPLOC_END +} BS3CG1OPLOC; +AssertCompile(BS3CG1OPLOC_END <= 16); + + +/** Pointer to the generated test state. */ +typedef struct BS3CG1STATE *PBS3CG1STATE; + +/** + * Encoder callback. + * @returns Next encoding. If equal or less to @a iEncoding, no + * further encodings are available for testing. + * @param pThis The state. + * @param iEncoding The encoding. + */ +typedef unsigned BS3_NEAR_CODE FNBS3CG1ENCODER(PBS3CG1STATE pThis, unsigned iEncoding); +/** Pointer to a encoder callback. */ +typedef FNBS3CG1ENCODER *PFNBS3CG1ENCODER; + + +/** + * The state. + */ +typedef struct BS3CG1STATE +{ + /** @name Instruction details (expanded from BS3CG1INSTR). + * @{ */ + /** Pointer to the mnemonic string (not terminated) (g_achBs3Cg1Mnemonics). */ + const char BS3_FAR *pchMnemonic; + /** Pointer to the test header. */ + PCBS3CG1TESTHDR pTestHdr; + /** Pointer to the per operand flags (g_abBs3Cg1Operands). */ + const uint8_t BS3_FAR *pabOperands; + /** Opcode bytes (g_abBs3Cg1Opcodes). */ + const uint8_t BS3_FAR *pabOpcodes; + /** The current instruction number in the input array (for error reporting). */ + uint32_t iInstr; + + /** The instruction flags. */ + uint32_t fFlags; + /** The encoding. */ + BS3CG1ENC enmEncoding; + /** The non-invalid encoding. This may differ from enmEncoding when + * Bs3Cg1CalcNoneIntelInvalidEncoding has been called. */ + BS3CG1ENC enmEncodingNonInvalid; + /** The CPU test / CPU ID. */ + BS3CG1CPU enmCpuTest; + /** Prefix sensitivity and requirements. */ + BS3CG1PFXKIND enmPrefixKind; + /** Exception type (SSE, AVX). */ + BS3CG1XCPTTYPE enmXcptType; + /** Per operand flags. */ + BS3CG1OP aenmOperands[4]; + /** Opcode bytes. */ + uint8_t abOpcodes[4]; + /** The instruction encoder. */ + PFNBS3CG1ENCODER pfnEncoder; + + /** The length of the mnemonic. */ + uint8_t cchMnemonic; + /** Whether to advance the mnemonic pointer or not. */ + uint8_t fAdvanceMnemonic; + /** The opcode map number. */ + uint8_t uOpcodeMap; + /** The number of opcode bytes. */ + uint8_t cbOpcodes; + /** Number of operands. */ + uint8_t cOperands; + /** @} */ + + /** Default operand size. */ + uint8_t cbOpDefault; + /** Operand size when overridden by 066h. */ + uint8_t cbOpOvrd66; + /** Operand size when overridden by REX.W. */ + uint8_t cbOpOvrdRexW; + + /** Operand size in bytes (0 if not applicable). */ + uint8_t cbOperand; + /** Current VEX.L value (UINT8_MAX if not applicable). */ + uint8_t uVexL; + /** Current target ring (0..3). */ + uint8_t uCpl; + + /** The current test number. */ + uint8_t iTest; + + /** Target mode (g_bBs3CurrentMode). */ + uint8_t bMode; + /** The CPU vendor (BS3CPUVENDOR). */ + uint8_t bCpuVendor; + /** First ring being tested. */ + uint8_t iFirstRing; + /** End of rings being tested. */ + uint8_t iEndRing; + + /** @name Current encoded instruction. + * @{ */ + /** The size of the current instruction that we're testing. */ + uint8_t cbCurInstr; + /** The size the prefixes. */ + uint8_t cbCurPrefix; + /** The offset into abCurInstr of the immediate. */ + uint8_t offCurImm; + /** Buffer for assembling the current instruction. */ + uint8_t abCurInstr[23]; + + /** Set if the encoding can't be tested in the same ring as this test code. + * This is used to deal with encodings modifying SP/ESP/RSP. */ + bool fSameRingNotOkay; + /** Whether to work the extended context too. */ + bool fWorkExtCtx; + /** The aOperands index of the modrm.reg operand (if applicable). */ + uint8_t iRegOp; + /** The aOperands index of the modrm.rm operand (if applicable). */ + uint8_t iRmOp; + + /** Operands details. */ + struct + { + uint8_t cbOp; + /** BS3CG1OPLOC_XXX. */ + uint8_t enmLocation; + /** BS3CG1OPLOC_XXX for memory encodings (MODRM.rm field). */ + uint8_t enmLocationMem : 4; + /** BS3CG1OPLOC_XXX for register encodings (MODRM.rm field). */ + uint8_t enmLocationReg : 4; + /** The BS3CG1DST value for this field. + * Set to BS3CG1DST_INVALID if memory or immediate. */ + uint8_t idxField; + /** The base BS3CG1DST value for this field. + * Used only by some generalized encoders when dealing with registers. */ + uint8_t idxFieldBase; + /** Depends on enmLocation. + * - BS3CG1OPLOC_IMM: offset relative to start of the instruction. + * - BS3CG1OPLOC_MEM: offset should be subtracted from &pbDataPg[_4K]. + * - BS3CG1OPLOC_MEM_RW: offset should be subtracted from &pbDataPg[_4K]. + * - BS3CG1OPLOC_MEM_RO: offset should be subtracted from &pbDataPg[_4K]. + * - BS3CG1OPLOC_CTX: not used (use idxField instead). + */ + uint8_t off; + } aOperands[4]; + /** @} */ + + /** Page to put code in. When paging is enabled, the page before and after + * are marked not-present. */ + uint8_t BS3_FAR *pbCodePg; + /** The flat address corresponding to pbCodePg. */ + uintptr_t uCodePgFlat; + /** The 16-bit address corresponding to pbCodePg if relevant for bMode. */ + RTFAR16 CodePgFar; + /** The IP/EIP/RIP value for pbCodePg[0] relative to CS (bMode). */ + uintptr_t CodePgRip; + + /** Page for placing data operands in. When paging is enabled, the page before + * and after are marked not-present. */ + uint8_t BS3_FAR *pbDataPg; + /** The flat address corresponding to pbDataPg. */ + uintptr_t uDataPgFlat; + /** The 16-bit address corresponding to pbDataPg. */ + RTFAR16 DataPgFar; + + /** The name corresponding to bMode. */ + const char BS3_FAR *pszMode; + /** The short name corresponding to bMode. */ + const char BS3_FAR *pszModeShort; + + /** @name Expected result (modifiable by output program). + * @{ */ + /** The expected exception based on operand values or result. + * UINT8_MAX if no special exception expected. */ + uint8_t bValueXcpt; + /** @} */ + /** Alignment exception expected by the encoder. + * UINT8_MAX if no special exception expected. */ + uint8_t bAlignmentXcpt; + /** Set by the encoding method to indicating invalid encoding. */ + bool fInvalidEncoding; + /** The result of Bs3Cg1CpuSetupFirst(). */ + bool fCpuSetupFirstResult; + + /** The context we're working on. */ + BS3REGCTX Ctx; + /** The trap context and frame. */ + BS3TRAPFRAME TrapFrame; + /** Initial contexts, one for each ring. */ + BS3REGCTX aInitialCtxs[4]; + + /** The extended context we're working on (input, expected output). */ + PBS3EXTCTX pExtCtx; + /** The extended result context (analoguous to TrapFrame). */ + PBS3EXTCTX pResultExtCtx; + /** The initial extended context. */ + PBS3EXTCTX pInitialExtCtx; + + /** Memory operand scratch space. */ + union + { + uint8_t ab[128]; + uint16_t au16[128 / sizeof(uint16_t)]; + uint32_t au32[128 / sizeof(uint32_t)]; + uint64_t au64[128 / sizeof(uint64_t)]; + } MemOp; + + /** Array parallel to aInitialCtxs for saving segment registers. */ + struct + { + RTSEL ds; + } aSavedSegRegs[4]; + +} BS3CG1STATE; + + +#define BS3CG1_PF_OZ UINT16_C(0x0001) +#define BS3CG1_PF_AZ UINT16_C(0x0002) +#define BS3CG1_PF_CS UINT16_C(0x0004) +#define BS3CG1_PF_DS UINT16_C(0x0008) +#define BS3CG1_PF_ES UINT16_C(0x0010) +#define BS3CG1_PF_FS UINT16_C(0x0020) +#define BS3CG1_PF_GS UINT16_C(0x0040) +#define BS3CG1_PF_SS UINT16_C(0x0080) +#define BS3CG1_PF_SEGS (BS3CG1_PF_CS | BS3CG1_PF_DS | BS3CG1_PF_ES | BS3CG1_PF_FS | BS3CG1_PF_GS | BS3CG1_PF_SS) +#define BS3CG1_PF_MEM (BS3CG1_PF_SEGS | BS3CG1_PF_AZ) +#define BS3CG1_PF_LK UINT16_C(0x0100) +#define BS3CG1_PF_RN UINT16_C(0x0200) +#define BS3CG1_PF_RZ UINT16_C(0x0400) +#define BS3CG1_PF_W UINT16_C(0x0800) /**< REX.W */ +#define BS3CG1_PF_R UINT16_C(0x1000) /**< REX.R */ +#define BS3CG1_PF_B UINT16_C(0x2000) /**< REX.B */ +#define BS3CG1_PF_X UINT16_C(0x4000) /**< REX.X */ + + +/** Used in g_cbBs3Cg1DstFields to indicate that it's one of the 4 operands. */ +#define BS3CG1DSTSIZE_OPERAND UINT8_C(255) +/** Used in g_cbBs3Cg1DstFields to indicate that the operand size determins + * the field size (2, 4, or 8). */ +#define BS3CG1DSTSIZE_OPERAND_SIZE_GRP UINT8_C(254) + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Destination field sizes indexed by bBS3CG1DST. + * Zero means operand size sized. */ +static const uint8_t g_acbBs3Cg1DstFields[] = +{ + /* [BS3CG1DST_INVALID] = */ BS3CG1DSTSIZE_OPERAND, + + /* [BS3CG1DST_OP1] = */ BS3CG1DSTSIZE_OPERAND, + /* [BS3CG1DST_OP2] = */ BS3CG1DSTSIZE_OPERAND, + /* [BS3CG1DST_OP3] = */ BS3CG1DSTSIZE_OPERAND, + /* [BS3CG1DST_OP4] = */ BS3CG1DSTSIZE_OPERAND, + /* [BS3CG1DST_EFL] = */ 4, + /* [BS3CG1DST_EFL_UNDEF]=*/ 4, + + /* [BS3CG1DST_AL] = */ 1, + /* [BS3CG1DST_CL] = */ 1, + /* [BS3CG1DST_DL] = */ 1, + /* [BS3CG1DST_BL] = */ 1, + /* [BS3CG1DST_AH] = */ 1, + /* [BS3CG1DST_CH] = */ 1, + /* [BS3CG1DST_DH] = */ 1, + /* [BS3CG1DST_BH] = */ 1, + /* [BS3CG1DST_SPL] = */ 1, + /* [BS3CG1DST_BPL] = */ 1, + /* [BS3CG1DST_SIL] = */ 1, + /* [BS3CG1DST_DIL] = */ 1, + /* [BS3CG1DST_R8L] = */ 1, + /* [BS3CG1DST_R9L] = */ 1, + /* [BS3CG1DST_R10L] = */ 1, + /* [BS3CG1DST_R11L] = */ 1, + /* [BS3CG1DST_R12L] = */ 1, + /* [BS3CG1DST_R13L] = */ 1, + /* [BS3CG1DST_R14L] = */ 1, + /* [BS3CG1DST_R15L] = */ 1, + + /* [BS3CG1DST_AX] = */ 2, + /* [BS3CG1DST_CX] = */ 2, + /* [BS3CG1DST_DX] = */ 2, + /* [BS3CG1DST_BX] = */ 2, + /* [BS3CG1DST_SP] = */ 2, + /* [BS3CG1DST_BP] = */ 2, + /* [BS3CG1DST_SI] = */ 2, + /* [BS3CG1DST_DI] = */ 2, + /* [BS3CG1DST_R8W] = */ 2, + /* [BS3CG1DST_R9W] = */ 2, + /* [BS3CG1DST_R10W] = */ 2, + /* [BS3CG1DST_R11W] = */ 2, + /* [BS3CG1DST_R12W] = */ 2, + /* [BS3CG1DST_R13W] = */ 2, + /* [BS3CG1DST_R14W] = */ 2, + /* [BS3CG1DST_R15W] = */ 2, + + /* [BS3CG1DST_EAX] = */ 4, + /* [BS3CG1DST_ECX] = */ 4, + /* [BS3CG1DST_EDX] = */ 4, + /* [BS3CG1DST_EBX] = */ 4, + /* [BS3CG1DST_ESP] = */ 4, + /* [BS3CG1DST_EBP] = */ 4, + /* [BS3CG1DST_ESI] = */ 4, + /* [BS3CG1DST_EDI] = */ 4, + /* [BS3CG1DST_R8D] = */ 4, + /* [BS3CG1DST_R9D] = */ 4, + /* [BS3CG1DST_R10D] = */ 4, + /* [BS3CG1DST_R11D] = */ 4, + /* [BS3CG1DST_R12D] = */ 4, + /* [BS3CG1DST_R13D] = */ 4, + /* [BS3CG1DST_R14D] = */ 4, + /* [BS3CG1DST_R15D] = */ 4, + + /* [BS3CG1DST_RAX] = */ 8, + /* [BS3CG1DST_RCX] = */ 8, + /* [BS3CG1DST_RDX] = */ 8, + /* [BS3CG1DST_RBX] = */ 8, + /* [BS3CG1DST_RSP] = */ 8, + /* [BS3CG1DST_RBP] = */ 8, + /* [BS3CG1DST_RSI] = */ 8, + /* [BS3CG1DST_RDI] = */ 8, + /* [BS3CG1DST_R8] = */ 8, + /* [BS3CG1DST_R9] = */ 8, + /* [BS3CG1DST_R10] = */ 8, + /* [BS3CG1DST_R11] = */ 8, + /* [BS3CG1DST_R12] = */ 8, + /* [BS3CG1DST_R13] = */ 8, + /* [BS3CG1DST_R14] = */ 8, + /* [BS3CG1DST_R15] = */ 8, + + /* [BS3CG1DST_OZ_RAX] = */ BS3CG1DSTSIZE_OPERAND_SIZE_GRP, + /* [BS3CG1DST_OZ_RCX] = */ BS3CG1DSTSIZE_OPERAND_SIZE_GRP, + /* [BS3CG1DST_OZ_RDX] = */ BS3CG1DSTSIZE_OPERAND_SIZE_GRP, + /* [BS3CG1DST_OZ_RBX] = */ BS3CG1DSTSIZE_OPERAND_SIZE_GRP, + /* [BS3CG1DST_OZ_RSP] = */ BS3CG1DSTSIZE_OPERAND_SIZE_GRP, + /* [BS3CG1DST_OZ_RBP] = */ BS3CG1DSTSIZE_OPERAND_SIZE_GRP, + /* [BS3CG1DST_OZ_RSI] = */ BS3CG1DSTSIZE_OPERAND_SIZE_GRP, + /* [BS3CG1DST_OZ_RDI] = */ BS3CG1DSTSIZE_OPERAND_SIZE_GRP, + /* [BS3CG1DST_OZ_R8] = */ BS3CG1DSTSIZE_OPERAND_SIZE_GRP, + /* [BS3CG1DST_OZ_R9] = */ BS3CG1DSTSIZE_OPERAND_SIZE_GRP, + /* [BS3CG1DST_OZ_R10] = */ BS3CG1DSTSIZE_OPERAND_SIZE_GRP, + /* [BS3CG1DST_OZ_R11] = */ BS3CG1DSTSIZE_OPERAND_SIZE_GRP, + /* [BS3CG1DST_OZ_R12] = */ BS3CG1DSTSIZE_OPERAND_SIZE_GRP, + /* [BS3CG1DST_OZ_R13] = */ BS3CG1DSTSIZE_OPERAND_SIZE_GRP, + /* [BS3CG1DST_OZ_R14] = */ BS3CG1DSTSIZE_OPERAND_SIZE_GRP, + /* [BS3CG1DST_OZ_R15] = */ BS3CG1DSTSIZE_OPERAND_SIZE_GRP, + + /* [BS3CG1DST_CR0] = */ 4, + /* [BS3CG1DST_CR4] = */ 4, + /* [BS3CG1DST_XCR0] = */ 8, + + /* [BS3CG1DST_FCW] = */ 2, + /* [BS3CG1DST_FSW] = */ 2, + /* [BS3CG1DST_FTW] = */ 2, + /* [BS3CG1DST_FOP] = */ 2, + /* [BS3CG1DST_FPUIP] = */ 2, + /* [BS3CG1DST_FPUCS] = */ 2, + /* [BS3CG1DST_FPUDP] = */ 2, + /* [BS3CG1DST_FPUDS] = */ 2, + /* [BS3CG1DST_MXCSR] = */ 4, + /* [BS3CG1DST_ST0] = */ 12, + /* [BS3CG1DST_ST1] = */ 12, + /* [BS3CG1DST_ST2] = */ 12, + /* [BS3CG1DST_ST3] = */ 12, + /* [BS3CG1DST_ST4] = */ 12, + /* [BS3CG1DST_ST5] = */ 12, + /* [BS3CG1DST_ST6] = */ 12, + /* [BS3CG1DST_ST7] = */ 12, + /* [BS3CG1DST_MM0] = */ 8, + /* [BS3CG1DST_MM1] = */ 8, + /* [BS3CG1DST_MM2] = */ 8, + /* [BS3CG1DST_MM3] = */ 8, + /* [BS3CG1DST_MM4] = */ 8, + /* [BS3CG1DST_MM5] = */ 8, + /* [BS3CG1DST_MM6] = */ 8, + /* [BS3CG1DST_MM7] = */ 8, + /* [BS3CG1DST_MM0_LO_ZX] = */ 4, + /* [BS3CG1DST_MM1_LO_ZX] = */ 4, + /* [BS3CG1DST_MM2_LO_ZX] = */ 4, + /* [BS3CG1DST_MM3_LO_ZX] = */ 4, + /* [BS3CG1DST_MM4_LO_ZX] = */ 4, + /* [BS3CG1DST_MM5_LO_ZX] = */ 4, + /* [BS3CG1DST_MM6_LO_ZX] = */ 4, + /* [BS3CG1DST_MM7_LO_ZX] = */ 4, + /* [BS3CG1DST_XMM0] = */ 16, + /* [BS3CG1DST_XMM1] = */ 16, + /* [BS3CG1DST_XMM2] = */ 16, + /* [BS3CG1DST_XMM3] = */ 16, + /* [BS3CG1DST_XMM4] = */ 16, + /* [BS3CG1DST_XMM5] = */ 16, + /* [BS3CG1DST_XMM6] = */ 16, + /* [BS3CG1DST_XMM7] = */ 16, + /* [BS3CG1DST_XMM8] = */ 16, + /* [BS3CG1DST_XMM9] = */ 16, + /* [BS3CG1DST_XMM10] = */ 16, + /* [BS3CG1DST_XMM11] = */ 16, + /* [BS3CG1DST_XMM12] = */ 16, + /* [BS3CG1DST_XMM13] = */ 16, + /* [BS3CG1DST_XMM14] = */ 16, + /* [BS3CG1DST_XMM15] = */ 16, + /* [BS3CG1DST_XMM0_LO] = */ 8, + /* [BS3CG1DST_XMM1_LO] = */ 8, + /* [BS3CG1DST_XMM2_LO] = */ 8, + /* [BS3CG1DST_XMM3_LO] = */ 8, + /* [BS3CG1DST_XMM4_LO] = */ 8, + /* [BS3CG1DST_XMM5_LO] = */ 8, + /* [BS3CG1DST_XMM6_LO] = */ 8, + /* [BS3CG1DST_XMM7_LO] = */ 8, + /* [BS3CG1DST_XMM8_LO] = */ 8, + /* [BS3CG1DST_XMM9_LO] = */ 8, + /* [BS3CG1DST_XMM10_LO] = */ 8, + /* [BS3CG1DST_XMM11_LO] = */ 8, + /* [BS3CG1DST_XMM12_LO] = */ 8, + /* [BS3CG1DST_XMM13_LO] = */ 8, + /* [BS3CG1DST_XMM14_LO] = */ 8, + /* [BS3CG1DST_XMM15_LO] = */ 8, + /* [BS3CG1DST_XMM0_HI] = */ 8, + /* [BS3CG1DST_XMM1_HI] = */ 8, + /* [BS3CG1DST_XMM2_HI] = */ 8, + /* [BS3CG1DST_XMM3_HI] = */ 8, + /* [BS3CG1DST_XMM4_HI] = */ 8, + /* [BS3CG1DST_XMM5_HI] = */ 8, + /* [BS3CG1DST_XMM6_HI] = */ 8, + /* [BS3CG1DST_XMM7_HI] = */ 8, + /* [BS3CG1DST_XMM8_HI] = */ 8, + /* [BS3CG1DST_XMM9_HI] = */ 8, + /* [BS3CG1DST_XMM10_HI] = */ 8, + /* [BS3CG1DST_XMM11_HI] = */ 8, + /* [BS3CG1DST_XMM12_HI] = */ 8, + /* [BS3CG1DST_XMM13_HI] = */ 8, + /* [BS3CG1DST_XMM14_HI] = */ 8, + /* [BS3CG1DST_XMM15_HI] = */ 8, + /* [BS3CG1DST_XMM0_LO_ZX] = */ 8, + /* [BS3CG1DST_XMM1_LO_ZX] = */ 8, + /* [BS3CG1DST_XMM2_LO_ZX] = */ 8, + /* [BS3CG1DST_XMM3_LO_ZX] = */ 8, + /* [BS3CG1DST_XMM4_LO_ZX] = */ 8, + /* [BS3CG1DST_XMM5_LO_ZX] = */ 8, + /* [BS3CG1DST_XMM6_LO_ZX] = */ 8, + /* [BS3CG1DST_XMM7_LO_ZX] = */ 8, + /* [BS3CG1DST_XMM8_LO_ZX] = */ 8, + /* [BS3CG1DST_XMM9_LO_ZX] = */ 8, + /* [BS3CG1DST_XMM10_LO_ZX] = */ 8, + /* [BS3CG1DST_XMM11_LO_ZX] = */ 8, + /* [BS3CG1DST_XMM12_LO_ZX] = */ 8, + /* [BS3CG1DST_XMM13_LO_ZX] = */ 8, + /* [BS3CG1DST_XMM14_LO_ZX] = */ 8, + /* [BS3CG1DST_XMM15_LO_ZX] = */ 8, + /* [BS3CG1DST_XMM0_DW0] = */ 4, + /* [BS3CG1DST_XMM1_DW0] = */ 4, + /* [BS3CG1DST_XMM2_DW0] = */ 4, + /* [BS3CG1DST_XMM3_DW0] = */ 4, + /* [BS3CG1DST_XMM4_DW0] = */ 4, + /* [BS3CG1DST_XMM5_DW0] = */ 4, + /* [BS3CG1DST_XMM6_DW0] = */ 4, + /* [BS3CG1DST_XMM7_DW0] = */ 4, + /* [BS3CG1DST_XMM8_DW0] = */ 4, + /* [BS3CG1DST_XMM9_DW0] = */ 4, + /* [BS3CG1DST_XMM10_DW0] = */ 4, + /* [BS3CG1DST_XMM11_DW0] = */ 4, + /* [BS3CG1DST_XMM12_DW0] = */ 4, + /* [BS3CG1DST_XMM13_DW0] = */ 4, + /* [BS3CG1DST_XMM14_DW0] = */ 4, + /* [BS3CG1DST_XMM15_DW0] = */ 4, + /* [BS3CG1DST_XMM0_DW0_ZX] = */ 4, + /* [BS3CG1DST_XMM1_DW0_ZX] = */ 4, + /* [BS3CG1DST_XMM2_DW0_ZX] = */ 4, + /* [BS3CG1DST_XMM3_DW0_ZX] = */ 4, + /* [BS3CG1DST_XMM4_DW0_ZX] = */ 4, + /* [BS3CG1DST_XMM5_DW0_ZX] = */ 4, + /* [BS3CG1DST_XMM6_DW0_ZX] = */ 4, + /* [BS3CG1DST_XMM7_DW0_ZX] = */ 4, + /* [BS3CG1DST_XMM8_DW0_ZX] = */ 4, + /* [BS3CG1DST_XMM9_DW0_ZX] = */ 4, + /* [BS3CG1DST_XMM10_DW0_ZX] =*/ 4, + /* [BS3CG1DST_XMM11_DW0_ZX] =*/ 4, + /* [BS3CG1DST_XMM12_DW0_ZX] =*/ 4, + /* [BS3CG1DST_XMM13_DW0_ZX] =*/ 4, + /* [BS3CG1DST_XMM14_DW0_ZX] =*/ 4, + /* [BS3CG1DST_XMM15_DW0_ZX] =*/ 4, + /* [BS3CG1DST_XMM0_HI96] = */ 12, + /* [BS3CG1DST_XMM1_HI96] = */ 12, + /* [BS3CG1DST_XMM2_HI96] = */ 12, + /* [BS3CG1DST_XMM3_HI96] = */ 12, + /* [BS3CG1DST_XMM4_HI96] = */ 12, + /* [BS3CG1DST_XMM5_HI96] = */ 12, + /* [BS3CG1DST_XMM6_HI96] = */ 12, + /* [BS3CG1DST_XMM7_HI96] = */ 12, + /* [BS3CG1DST_XMM8_HI96] = */ 12, + /* [BS3CG1DST_XMM9_HI96] = */ 12, + /* [BS3CG1DST_XMM10_HI96] =*/ 12, + /* [BS3CG1DST_XMM11_HI96] =*/ 12, + /* [BS3CG1DST_XMM12_HI96] =*/ 12, + /* [BS3CG1DST_XMM13_HI96] =*/ 12, + /* [BS3CG1DST_XMM14_HI96] =*/ 12, + /* [BS3CG1DST_XMM15_HI96] =*/ 12, + /* [BS3CG1DST_YMM0] = */ 32, + /* [BS3CG1DST_YMM1] = */ 32, + /* [BS3CG1DST_YMM2] = */ 32, + /* [BS3CG1DST_YMM3] = */ 32, + /* [BS3CG1DST_YMM4] = */ 32, + /* [BS3CG1DST_YMM5] = */ 32, + /* [BS3CG1DST_YMM6] = */ 32, + /* [BS3CG1DST_YMM7] = */ 32, + /* [BS3CG1DST_YMM8] = */ 32, + /* [BS3CG1DST_YMM9] = */ 32, + /* [BS3CG1DST_YMM10] = */ 32, + /* [BS3CG1DST_YMM11] = */ 32, + /* [BS3CG1DST_YMM12] = */ 32, + /* [BS3CG1DST_YMM13] = */ 32, + /* [BS3CG1DST_YMM14] = */ 32, + /* [BS3CG1DST_YMM15] = */ 32, + + /* [BS3CG1DST_VALUE_XCPT] = */ 1, +}; +AssertCompile(RT_ELEMENTS(g_acbBs3Cg1DstFields) == BS3CG1DST_END); + +/** Destination field offset indexed by bBS3CG1DST. + * Zero means operand size sized. */ +static const unsigned g_aoffBs3Cg1DstFields[] = +{ + /* [BS3CG1DST_INVALID] = */ ~0U, + /* [BS3CG1DST_OP1] = */ ~0U, + /* [BS3CG1DST_OP2] = */ ~0U, + /* [BS3CG1DST_OP3] = */ ~0U, + /* [BS3CG1DST_OP4] = */ ~0U, + /* [BS3CG1DST_EFL] = */ RT_OFFSETOF(BS3REGCTX, rflags), + /* [BS3CG1DST_EFL_UNDEF]=*/ ~0, /* special field */ + + /* [BS3CG1DST_AL] = */ RT_OFFSETOF(BS3REGCTX, rax.u8), + /* [BS3CG1DST_CL] = */ RT_OFFSETOF(BS3REGCTX, rcx.u8), + /* [BS3CG1DST_DL] = */ RT_OFFSETOF(BS3REGCTX, rdx.u8), + /* [BS3CG1DST_BL] = */ RT_OFFSETOF(BS3REGCTX, rbx.u8), + /* [BS3CG1DST_AH] = */ RT_OFFSETOF(BS3REGCTX, rax.b.bHi), + /* [BS3CG1DST_CH] = */ RT_OFFSETOF(BS3REGCTX, rcx.b.bHi), + /* [BS3CG1DST_DH] = */ RT_OFFSETOF(BS3REGCTX, rdx.b.bHi), + /* [BS3CG1DST_BH] = */ RT_OFFSETOF(BS3REGCTX, rbx.b.bHi), + /* [BS3CG1DST_SPL] = */ RT_OFFSETOF(BS3REGCTX, rsp.u8), + /* [BS3CG1DST_BPL] = */ RT_OFFSETOF(BS3REGCTX, rbp.u8), + /* [BS3CG1DST_SIL] = */ RT_OFFSETOF(BS3REGCTX, rsi.u8), + /* [BS3CG1DST_DIL] = */ RT_OFFSETOF(BS3REGCTX, rdi.u8), + /* [BS3CG1DST_R8L] = */ RT_OFFSETOF(BS3REGCTX, r8.u8), + /* [BS3CG1DST_R9L] = */ RT_OFFSETOF(BS3REGCTX, r9.u8), + /* [BS3CG1DST_R10L] = */ RT_OFFSETOF(BS3REGCTX, r10.u8), + /* [BS3CG1DST_R11L] = */ RT_OFFSETOF(BS3REGCTX, r11.u8), + /* [BS3CG1DST_R12L] = */ RT_OFFSETOF(BS3REGCTX, r12.u8), + /* [BS3CG1DST_R13L] = */ RT_OFFSETOF(BS3REGCTX, r13.u8), + /* [BS3CG1DST_R14L] = */ RT_OFFSETOF(BS3REGCTX, r14.u8), + /* [BS3CG1DST_R15L] = */ RT_OFFSETOF(BS3REGCTX, r15.u8), + + /* [BS3CG1DST_AX] = */ RT_OFFSETOF(BS3REGCTX, rax.u16), + /* [BS3CG1DST_CX] = */ RT_OFFSETOF(BS3REGCTX, rcx.u16), + /* [BS3CG1DST_DX] = */ RT_OFFSETOF(BS3REGCTX, rdx.u16), + /* [BS3CG1DST_BX] = */ RT_OFFSETOF(BS3REGCTX, rbx.u16), + /* [BS3CG1DST_SP] = */ RT_OFFSETOF(BS3REGCTX, rsp.u16), + /* [BS3CG1DST_BP] = */ RT_OFFSETOF(BS3REGCTX, rbp.u16), + /* [BS3CG1DST_SI] = */ RT_OFFSETOF(BS3REGCTX, rsi.u16), + /* [BS3CG1DST_DI] = */ RT_OFFSETOF(BS3REGCTX, rdi.u16), + /* [BS3CG1DST_R8W] = */ RT_OFFSETOF(BS3REGCTX, r8.u16), + /* [BS3CG1DST_R9W] = */ RT_OFFSETOF(BS3REGCTX, r9.u16), + /* [BS3CG1DST_R10W] = */ RT_OFFSETOF(BS3REGCTX, r10.u16), + /* [BS3CG1DST_R11W] = */ RT_OFFSETOF(BS3REGCTX, r11.u16), + /* [BS3CG1DST_R12W] = */ RT_OFFSETOF(BS3REGCTX, r12.u16), + /* [BS3CG1DST_R13W] = */ RT_OFFSETOF(BS3REGCTX, r13.u16), + /* [BS3CG1DST_R14W] = */ RT_OFFSETOF(BS3REGCTX, r14.u16), + /* [BS3CG1DST_R15W] = */ RT_OFFSETOF(BS3REGCTX, r15.u16), + + /* [BS3CG1DST_EAX] = */ RT_OFFSETOF(BS3REGCTX, rax.u32), + /* [BS3CG1DST_ECX] = */ RT_OFFSETOF(BS3REGCTX, rcx.u32), + /* [BS3CG1DST_EDX] = */ RT_OFFSETOF(BS3REGCTX, rdx.u32), + /* [BS3CG1DST_EBX] = */ RT_OFFSETOF(BS3REGCTX, rbx.u32), + /* [BS3CG1DST_ESP] = */ RT_OFFSETOF(BS3REGCTX, rsp.u32), + /* [BS3CG1DST_EBP] = */ RT_OFFSETOF(BS3REGCTX, rbp.u32), + /* [BS3CG1DST_ESI] = */ RT_OFFSETOF(BS3REGCTX, rsi.u32), + /* [BS3CG1DST_EDI] = */ RT_OFFSETOF(BS3REGCTX, rdi.u32), + /* [BS3CG1DST_R8D] = */ RT_OFFSETOF(BS3REGCTX, r8.u32), + /* [BS3CG1DST_R9D] = */ RT_OFFSETOF(BS3REGCTX, r9.u32), + /* [BS3CG1DST_R10D] = */ RT_OFFSETOF(BS3REGCTX, r10.u32), + /* [BS3CG1DST_R11D] = */ RT_OFFSETOF(BS3REGCTX, r11.u32), + /* [BS3CG1DST_R12D] = */ RT_OFFSETOF(BS3REGCTX, r12.u32), + /* [BS3CG1DST_R13D] = */ RT_OFFSETOF(BS3REGCTX, r13.u32), + /* [BS3CG1DST_R14D] = */ RT_OFFSETOF(BS3REGCTX, r14.u32), + /* [BS3CG1DST_R15D] = */ RT_OFFSETOF(BS3REGCTX, r15.u32), + + /* [BS3CG1DST_RAX] = */ RT_OFFSETOF(BS3REGCTX, rax.u64), + /* [BS3CG1DST_RCX] = */ RT_OFFSETOF(BS3REGCTX, rcx.u64), + /* [BS3CG1DST_RDX] = */ RT_OFFSETOF(BS3REGCTX, rdx.u64), + /* [BS3CG1DST_RBX] = */ RT_OFFSETOF(BS3REGCTX, rbx.u64), + /* [BS3CG1DST_RSP] = */ RT_OFFSETOF(BS3REGCTX, rsp.u64), + /* [BS3CG1DST_RBP] = */ RT_OFFSETOF(BS3REGCTX, rbp.u64), + /* [BS3CG1DST_RSI] = */ RT_OFFSETOF(BS3REGCTX, rsi.u64), + /* [BS3CG1DST_RDI] = */ RT_OFFSETOF(BS3REGCTX, rdi.u64), + /* [BS3CG1DST_R8] = */ RT_OFFSETOF(BS3REGCTX, r8.u64), + /* [BS3CG1DST_R9] = */ RT_OFFSETOF(BS3REGCTX, r9.u64), + /* [BS3CG1DST_R10] = */ RT_OFFSETOF(BS3REGCTX, r10.u64), + /* [BS3CG1DST_R11] = */ RT_OFFSETOF(BS3REGCTX, r11.u64), + /* [BS3CG1DST_R12] = */ RT_OFFSETOF(BS3REGCTX, r12.u64), + /* [BS3CG1DST_R13] = */ RT_OFFSETOF(BS3REGCTX, r13.u64), + /* [BS3CG1DST_R14] = */ RT_OFFSETOF(BS3REGCTX, r14.u64), + /* [BS3CG1DST_R15] = */ RT_OFFSETOF(BS3REGCTX, r15.u64), + + /* [BS3CG1DST_OZ_RAX] = */ RT_OFFSETOF(BS3REGCTX, rax), + /* [BS3CG1DST_OZ_RCX] = */ RT_OFFSETOF(BS3REGCTX, rcx), + /* [BS3CG1DST_OZ_RDX] = */ RT_OFFSETOF(BS3REGCTX, rdx), + /* [BS3CG1DST_OZ_RBX] = */ RT_OFFSETOF(BS3REGCTX, rbx), + /* [BS3CG1DST_OZ_RSP] = */ RT_OFFSETOF(BS3REGCTX, rsp), + /* [BS3CG1DST_OZ_RBP] = */ RT_OFFSETOF(BS3REGCTX, rbp), + /* [BS3CG1DST_OZ_RSI] = */ RT_OFFSETOF(BS3REGCTX, rsi), + /* [BS3CG1DST_OZ_RDI] = */ RT_OFFSETOF(BS3REGCTX, rdi), + /* [BS3CG1DST_OZ_R8] = */ RT_OFFSETOF(BS3REGCTX, r8), + /* [BS3CG1DST_OZ_R9] = */ RT_OFFSETOF(BS3REGCTX, r9), + /* [BS3CG1DST_OZ_R10] = */ RT_OFFSETOF(BS3REGCTX, r10), + /* [BS3CG1DST_OZ_R11] = */ RT_OFFSETOF(BS3REGCTX, r11), + /* [BS3CG1DST_OZ_R12] = */ RT_OFFSETOF(BS3REGCTX, r12), + /* [BS3CG1DST_OZ_R13] = */ RT_OFFSETOF(BS3REGCTX, r13), + /* [BS3CG1DST_OZ_R14] = */ RT_OFFSETOF(BS3REGCTX, r14), + /* [BS3CG1DST_OZ_R15] = */ RT_OFFSETOF(BS3REGCTX, r15), + + /* [BS3CG1DST_CR0] = */ RT_OFFSETOF(BS3REGCTX, cr0), + /* [BS3CG1DST_CR4] = */ RT_OFFSETOF(BS3REGCTX, cr4), + /* [BS3CG1DST_XCR0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, fXcr0Saved), + + /* [BS3CG1DST_FCW] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.FCW), + /* [BS3CG1DST_FSW] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.FSW), + /* [BS3CG1DST_FTW] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.FTW), + /* [BS3CG1DST_FOP] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.FOP), + /* [BS3CG1DST_FPUIP] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.FPUIP), + /* [BS3CG1DST_FPUCS] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.CS), + /* [BS3CG1DST_FPUDP] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.FPUDP), + /* [BS3CG1DST_FPUDS] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.DS), + /* [BS3CG1DST_MXCSR] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.MXCSR), + /* [BS3CG1DST_ST0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[0]), + /* [BS3CG1DST_ST1] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[1]), + /* [BS3CG1DST_ST2] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[2]), + /* [BS3CG1DST_ST3] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[3]), + /* [BS3CG1DST_ST4] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[4]), + /* [BS3CG1DST_ST5] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[5]), + /* [BS3CG1DST_ST6] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[6]), + /* [BS3CG1DST_ST7] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[7]), + /* [BS3CG1DST_MM0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[0]), + /* [BS3CG1DST_MM1] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[1]), + /* [BS3CG1DST_MM2] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[2]), + /* [BS3CG1DST_MM3] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[3]), + /* [BS3CG1DST_MM4] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[4]), + /* [BS3CG1DST_MM5] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[5]), + /* [BS3CG1DST_MM6] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[6]), + /* [BS3CG1DST_MM7] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[7]), + /* [BS3CG1DST_MM0_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[0]), + /* [BS3CG1DST_MM1_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[1]), + /* [BS3CG1DST_MM2_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[2]), + /* [BS3CG1DST_MM3_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[3]), + /* [BS3CG1DST_MM4_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[4]), + /* [BS3CG1DST_MM5_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[5]), + /* [BS3CG1DST_MM6_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[6]), + /* [BS3CG1DST_MM7_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aRegs[7]), + + /* [BS3CG1DST_XMM0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[0]), + /* [BS3CG1DST_XMM1] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[1]), + /* [BS3CG1DST_XMM2] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[2]), + /* [BS3CG1DST_XMM3] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[3]), + /* [BS3CG1DST_XMM4] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[4]), + /* [BS3CG1DST_XMM5] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[5]), + /* [BS3CG1DST_XMM6] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[6]), + /* [BS3CG1DST_XMM7] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[7]), + /* [BS3CG1DST_XMM8] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[8]), + /* [BS3CG1DST_XMM9] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[9]), + /* [BS3CG1DST_XMM10] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[10]), + /* [BS3CG1DST_XMM11] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[11]), + /* [BS3CG1DST_XMM12] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[12]), + /* [BS3CG1DST_XMM13] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[13]), + /* [BS3CG1DST_XMM14] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[14]), + /* [BS3CG1DST_XMM15] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[15]), + /* [BS3CG1DST_XMM0_LO] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[0]), + /* [BS3CG1DST_XMM1_LO] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[1]), + /* [BS3CG1DST_XMM2_LO] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[2]), + /* [BS3CG1DST_XMM3_LO] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[3]), + /* [BS3CG1DST_XMM4_LO] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[4]), + /* [BS3CG1DST_XMM5_LO] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[5]), + /* [BS3CG1DST_XMM6_LO] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[6]), + /* [BS3CG1DST_XMM7_LO] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[7]), + /* [BS3CG1DST_XMM8_LO] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[8]), + /* [BS3CG1DST_XMM9_LO] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[9]), + /* [BS3CG1DST_XMM10_LO] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[10]), + /* [BS3CG1DST_XMM11_LO] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[11]), + /* [BS3CG1DST_XMM12_LO] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[12]), + /* [BS3CG1DST_XMM13_LO] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[13]), + /* [BS3CG1DST_XMM14_LO] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[14]), + /* [BS3CG1DST_XMM15_LO] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[15]), + /* [BS3CG1DST_XMM0_HI] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[0]) + sizeof(uint64_t), + /* [BS3CG1DST_XMM1_HI] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[1]) + sizeof(uint64_t), + /* [BS3CG1DST_XMM2_HI] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[2]) + sizeof(uint64_t), + /* [BS3CG1DST_XMM3_HI] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[3]) + sizeof(uint64_t), + /* [BS3CG1DST_XMM4_HI] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[4]) + sizeof(uint64_t), + /* [BS3CG1DST_XMM5_HI] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[5]) + sizeof(uint64_t), + /* [BS3CG1DST_XMM6_HI] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[6]) + sizeof(uint64_t), + /* [BS3CG1DST_XMM7_HI] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[7]) + sizeof(uint64_t), + /* [BS3CG1DST_XMM8_HI] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[8]) + sizeof(uint64_t), + /* [BS3CG1DST_XMM9_HI] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[9]) + sizeof(uint64_t), + /* [BS3CG1DST_XMM10_HI] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[10]) + sizeof(uint64_t), + /* [BS3CG1DST_XMM11_HI] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[11]) + sizeof(uint64_t), + /* [BS3CG1DST_XMM12_HI] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[12]) + sizeof(uint64_t), + /* [BS3CG1DST_XMM13_HI] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[13]) + sizeof(uint64_t), + /* [BS3CG1DST_XMM14_HI] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[14]) + sizeof(uint64_t), + /* [BS3CG1DST_XMM15_HI] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[15]) + sizeof(uint64_t), + /* [BS3CG1DST_XMM0_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[0]), + /* [BS3CG1DST_XMM1_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[1]), + /* [BS3CG1DST_XMM2_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[2]), + /* [BS3CG1DST_XMM3_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[3]), + /* [BS3CG1DST_XMM4_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[4]), + /* [BS3CG1DST_XMM5_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[5]), + /* [BS3CG1DST_XMM6_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[6]), + /* [BS3CG1DST_XMM7_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[7]), + /* [BS3CG1DST_XMM8_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[8]), + /* [BS3CG1DST_XMM9_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[9]), + /* [BS3CG1DST_XMM10_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[10]), + /* [BS3CG1DST_XMM11_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[11]), + /* [BS3CG1DST_XMM12_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[12]), + /* [BS3CG1DST_XMM13_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[13]), + /* [BS3CG1DST_XMM14_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[14]), + /* [BS3CG1DST_XMM15_LO_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[15]), + /* [BS3CG1DST_XMM0_DW0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[0]), + /* [BS3CG1DST_XMM1_DW0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[1]), + /* [BS3CG1DST_XMM2_DW0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[2]), + /* [BS3CG1DST_XMM3_DW0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[3]), + /* [BS3CG1DST_XMM4_DW0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[4]), + /* [BS3CG1DST_XMM5_DW0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[5]), + /* [BS3CG1DST_XMM6_DW0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[6]), + /* [BS3CG1DST_XMM7_DW0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[7]), + /* [BS3CG1DST_XMM8_DW0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[8]), + /* [BS3CG1DST_XMM9_DW0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[9]), + /* [BS3CG1DST_XMM10_DW0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[10]), + /* [BS3CG1DST_XMM11_DW0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[11]), + /* [BS3CG1DST_XMM12_DW0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[12]), + /* [BS3CG1DST_XMM13_DW0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[13]), + /* [BS3CG1DST_XMM14_DW0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[14]), + /* [BS3CG1DST_XMM15_DW0] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[15]), + /* [BS3CG1DST_XMM0_DW0_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[0]), + /* [BS3CG1DST_XMM1_DW0_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[1]), + /* [BS3CG1DST_XMM2_DW0_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[2]), + /* [BS3CG1DST_XMM3_DW0_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[3]), + /* [BS3CG1DST_XMM4_DW0_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[4]), + /* [BS3CG1DST_XMM5_DW0_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[5]), + /* [BS3CG1DST_XMM6_DW0_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[6]), + /* [BS3CG1DST_XMM7_DW0_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[7]), + /* [BS3CG1DST_XMM8_DW0_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[8]), + /* [BS3CG1DST_XMM9_DW0_ZX] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[9]), + /* [BS3CG1DST_XMM10_DW0_ZX] =*/ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[10]), + /* [BS3CG1DST_XMM11_DW0_ZX] =*/ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[11]), + /* [BS3CG1DST_XMM12_DW0_ZX] =*/ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[12]), + /* [BS3CG1DST_XMM13_DW0_ZX] =*/ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[13]), + /* [BS3CG1DST_XMM14_DW0_ZX] =*/ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[14]), + /* [BS3CG1DST_XMM15_DW0_ZX] =*/ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[15]), + /* [BS3CG1DST_XMM0_HI96] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[0].au32[1]), + /* [BS3CG1DST_XMM1_HI96] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[1].au32[1]), + /* [BS3CG1DST_XMM2_HI96] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[2].au32[1]), + /* [BS3CG1DST_XMM3_HI96] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[3].au32[1]), + /* [BS3CG1DST_XMM4_HI96] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[4].au32[1]), + /* [BS3CG1DST_XMM5_HI96] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[5].au32[1]), + /* [BS3CG1DST_XMM6_HI96] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[6].au32[1]), + /* [BS3CG1DST_XMM7_HI96] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[7].au32[1]), + /* [BS3CG1DST_XMM8_HI96] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[8].au32[1]), + /* [BS3CG1DST_XMM9_HI96] = */ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[9].au32[1]), + /* [BS3CG1DST_XMM10_HI96] =*/ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[10].au32[1]), + /* [BS3CG1DST_XMM11_HI96] =*/ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[11].au32[1]), + /* [BS3CG1DST_XMM12_HI96] =*/ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[12].au32[1]), + /* [BS3CG1DST_XMM13_HI96] =*/ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[13].au32[1]), + /* [BS3CG1DST_XMM14_HI96] =*/ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[14].au32[1]), + /* [BS3CG1DST_XMM15_HI96] =*/ sizeof(BS3REGCTX) + RT_OFFSETOF(BS3EXTCTX, Ctx.x87.aXMM[15].au32[1]), + + /* [BS3CG1DST_YMM0] = */ ~0U, + /* [BS3CG1DST_YMM1] = */ ~0U, + /* [BS3CG1DST_YMM2] = */ ~0U, + /* [BS3CG1DST_YMM3] = */ ~0U, + /* [BS3CG1DST_YMM4] = */ ~0U, + /* [BS3CG1DST_YMM5] = */ ~0U, + /* [BS3CG1DST_YMM6] = */ ~0U, + /* [BS3CG1DST_YMM7] = */ ~0U, + /* [BS3CG1DST_YMM8] = */ ~0U, + /* [BS3CG1DST_YMM9] = */ ~0U, + /* [BS3CG1DST_YMM10] = */ ~0U, + /* [BS3CG1DST_YMM11] = */ ~0U, + /* [BS3CG1DST_YMM12] = */ ~0U, + /* [BS3CG1DST_YMM13] = */ ~0U, + /* [BS3CG1DST_YMM14] = */ ~0U, + /* [BS3CG1DST_YMM15] = */ ~0U, + + /* [BS3CG1DST_VALUE_XCPT] = */ ~0U, +}; +AssertCompile(RT_ELEMENTS(g_aoffBs3Cg1DstFields) == BS3CG1DST_END); + +/** Destination field names. */ +static const struct { char sz[12]; } g_aszBs3Cg1DstFields[] = +{ + { "INVALID" }, + { "OP1" }, + { "OP2" }, + { "OP3" }, + { "OP4" }, + { "EFL" }, + { "EFL_UND" }, + + { "AL" }, + { "CL" }, + { "DL" }, + { "BL" }, + { "AH" }, + { "CH" }, + { "DH" }, + { "BH" }, + { "SPL" }, + { "BPL" }, + { "SIL" }, + { "DIL" }, + { "R8L" }, + { "R9L" }, + { "R10L" }, + { "R11L" }, + { "R12L" }, + { "R13L" }, + { "R14L" }, + { "R15L" }, + + { "AX" }, + { "CX" }, + { "DX" }, + { "BX" }, + { "SP" }, + { "BP" }, + { "SI" }, + { "DI" }, + { "R8W" }, + { "R9W" }, + { "R10W" }, + { "R11W" }, + { "R12W" }, + { "R13W" }, + { "R14W" }, + { "R15W" }, + + { "EAX" }, + { "ECX" }, + { "EDX" }, + { "EBX" }, + { "ESP" }, + { "EBP" }, + { "ESI" }, + { "EDI" }, + { "R8D" }, + { "R9D" }, + { "R10D" }, + { "R11D" }, + { "R12D" }, + { "R13D" }, + { "R14D" }, + { "R15D" }, + + { "RAX" }, + { "RCX" }, + { "RDX" }, + { "RBX" }, + { "RSP" }, + { "RBP" }, + { "RSI" }, + { "RDI" }, + { "R8" }, + { "R9" }, + { "R10" }, + { "R11" }, + { "R12" }, + { "R13" }, + { "R14" }, + { "R15" }, + + { "OZ_RAX" }, + { "OZ_RCX" }, + { "OZ_RDX" }, + { "OZ_RBX" }, + { "OZ_RSP" }, + { "OZ_RBP" }, + { "OZ_RSI" }, + { "OZ_RDI" }, + { "OZ_R8" }, + { "OZ_R9" }, + { "OZ_R10" }, + { "OZ_R11" }, + { "OZ_R12" }, + { "OZ_R13" }, + { "OZ_R14" }, + { "OZ_R15" }, + + { "CR0" }, + { "CR4" }, + { "XCR0" }, + + { "FCW" }, + { "FSW" }, + { "FTW" }, + { "FOP" }, + { "FPUIP" }, + { "FPUCS" }, + { "FPUDP" }, + { "FPUDS" }, + { "MXCSR" }, + { "ST0" }, + { "ST1" }, + { "ST2" }, + { "ST3" }, + { "ST4" }, + { "ST5" }, + { "ST6" }, + { "ST7" }, + { "MM0" }, + { "MM1" }, + { "MM2" }, + { "MM3" }, + { "MM4" }, + { "MM5" }, + { "MM6" }, + { "MM7" }, + { "MM0_LO_ZX" }, + { "MM1_LO_ZX" }, + { "MM2_LO_ZX" }, + { "MM3_LO_ZX" }, + { "MM4_LO_ZX" }, + { "MM5_LO_ZX" }, + { "MM6_LO_ZX" }, + { "MM7_LO_ZX" }, + { "XMM0" }, + { "XMM1" }, + { "XMM2" }, + { "XMM3" }, + { "XMM4" }, + { "XMM5" }, + { "XMM6" }, + { "XMM7" }, + { "XMM8" }, + { "XMM9" }, + { "XMM10" }, + { "XMM11" }, + { "XMM12" }, + { "XMM13" }, + { "XMM14" }, + { "XMM15" }, + { "XMM0_LO" }, + { "XMM1_LO" }, + { "XMM2_LO" }, + { "XMM3_LO" }, + { "XMM4_LO" }, + { "XMM5_LO" }, + { "XMM6_LO" }, + { "XMM7_LO" }, + { "XMM8_LO" }, + { "XMM9_LO" }, + { "XMM10_LO" }, + { "XMM11_LO" }, + { "XMM12_LO" }, + { "XMM13_LO" }, + { "XMM14_LO" }, + { "XMM15_LO" }, + { "XMM0_HI" }, + { "XMM1_HI" }, + { "XMM2_HI" }, + { "XMM3_HI" }, + { "XMM4_HI" }, + { "XMM5_HI" }, + { "XMM6_HI" }, + { "XMM7_HI" }, + { "XMM8_HI" }, + { "XMM9_HI" }, + { "XMM10_HI" }, + { "XMM11_HI" }, + { "XMM12_HI" }, + { "XMM13_HI" }, + { "XMM14_HI" }, + { "XMM15_HI" }, + { "XMM0_LO_ZX" }, + { "XMM1_LO_ZX" }, + { "XMM2_LO_ZX" }, + { "XMM3_LO_ZX" }, + { "XMM4_LO_ZX" }, + { "XMM5_LO_ZX" }, + { "XMM6_LO_ZX" }, + { "XMM7_LO_ZX" }, + { "XMM8_LO_ZX" }, + { "XMM9_LO_ZX" }, + { "XMM10_LO_ZX" }, + { "XMM11_LO_ZX" }, + { "XMM12_LO_ZX" }, + { "XMM13_LO_ZX" }, + { "XMM14_LO_ZX" }, + { "XMM15_LO_ZX" }, + { "XMM0_DW0" }, + { "XMM1_DW0" }, + { "XMM2_DW0" }, + { "XMM3_DW0" }, + { "XMM4_DW0" }, + { "XMM5_DW0" }, + { "XMM6_DW0" }, + { "XMM7_DW0" }, + { "XMM8_DW0" }, + { "XMM9_DW0" }, + { "XMM10_DW0" }, + { "XMM11_DW0" }, + { "XMM12_DW0" }, + { "XMM13_DW0" }, + { "XMM14_DW0" }, + { "XMM15_DW0" }, + { "XMM0_DW0_ZX" }, + { "XMM1_DW0_ZX" }, + { "XMM2_DW0_ZX" }, + { "XMM3_DW0_ZX" }, + { "XMM4_DW0_ZX" }, + { "XMM5_DW0_ZX" }, + { "XMM6_DW0_ZX" }, + { "XMM7_DW0_ZX" }, + { "XMM8_DW0_ZX" }, + { "XMM9_DW0_ZX" }, + { "XMM10_DW0_ZX" }, + { "XMM11_DW0_ZX" }, + { "XMM12_DW0_ZX" }, + { "XMM13_DW0_ZX" }, + { "XMM14_DW0_ZX" }, + { "XMM15_DW0_ZX" }, + { "XMM0_HI96" }, + { "XMM1_HI96" }, + { "XMM2_HI96" }, + { "XMM3_HI96" }, + { "XMM4_HI96" }, + { "XMM5_HI96" }, + { "XMM6_HI96" }, + { "XMM7_HI96" }, + { "XMM8_HI96" }, + { "XMM9_HI96" }, + { "XMM10_HI96" }, + { "XMM11_HI96" }, + { "XMM12_HI96" }, + { "XMM13_HI96" }, + { "XMM14_HI96" }, + { "XMM15_HI96" }, + { "YMM0" }, + { "YMM1" }, + { "YMM2" }, + { "YMM3" }, + { "YMM4" }, + { "YMM5" }, + { "YMM6" }, + { "YMM7" }, + { "YMM8" }, + { "YMM9" }, + { "YMM10" }, + { "YMM11" }, + { "YMM12" }, + { "YMM13" }, + { "YMM14" }, + { "YMM15" }, + + { "VALXCPT" }, +}; +AssertCompile(RT_ELEMENTS(g_aszBs3Cg1DstFields) >= BS3CG1DST_END); +AssertCompile(RT_ELEMENTS(g_aszBs3Cg1DstFields) == BS3CG1DST_END); + + +#if 0 +static const struct +{ + uint8_t cbPrefixes; + uint8_t abPrefixes[14]; + uint16_t fEffective; +} g_aPrefixVariations[] = +{ + { 0, { 0x00 }, BS3CG1_PF_NONE }, + + { 1, { P_OZ }, BS3CG1_PF_OZ }, + { 1, { P_CS }, BS3CG1_PF_CS }, + { 1, { P_DS }, BS3CG1_PF_DS }, + { 1, { P_ES }, BS3CG1_PF_ES }, + { 1, { P_FS }, BS3CG1_PF_FS }, + { 1, { P_GS }, BS3CG1_PF_GS }, + { 1, { P_SS }, BS3CG1_PF_SS }, + { 1, { P_LK }, BS3CG1_PF_LK }, + + { 2, { P_CS, P_OZ, }, BS3CG1_PF_CS | BS3CFG1_PF_OZ }, + { 2, { P_DS, P_OZ, }, BS3CG1_PF_DS | BS3CFG1_PF_OZ }, + { 2, { P_ES, P_OZ, }, BS3CG1_PF_ES | BS3CFG1_PF_OZ }, + { 2, { P_FS, P_OZ, }, BS3CG1_PF_FS | BS3CFG1_PF_OZ }, + { 2, { P_GS, P_OZ, }, BS3CG1_PF_GS | BS3CFG1_PF_OZ }, + { 2, { P_GS, P_OZ, }, BS3CG1_PF_SS | BS3CFG1_PF_OZ }, + { 2, { P_SS, P_OZ, }, BS3CG1_PF_SS | BS3CFG1_PF_OZ }, + + { 2, { P_OZ, P_CS, }, BS3CG1_PF_CS | BS3CFG1_PF_OZ }, + { 2, { P_OZ, P_DS, }, BS3CG1_PF_DS | BS3CFG1_PF_OZ }, + { 2, { P_OZ, P_ES, }, BS3CG1_PF_ES | BS3CFG1_PF_OZ }, + { 2, { P_OZ, P_FS, }, BS3CG1_PF_FS | BS3CFG1_PF_OZ }, + { 2, { P_OZ, P_GS, }, BS3CG1_PF_GS | BS3CFG1_PF_OZ }, + { 2, { P_OZ, P_GS, }, BS3CG1_PF_SS | BS3CFG1_PF_OZ }, + { 2, { P_OZ, P_SS, }, BS3CG1_PF_SS | BS3CFG1_PF_OZ }, +}; + +static const uint16_t g_afPfxKindToIgnoredFlags[BS3CG1PFXKIND_END] = +{ + /* [BS3CG1PFXKIND_INVALID] = */ UINT16_MAX, + /* [BS3CG1PFXKIND_MODRM] = */ 0, + /* [BS3CG1PFXKIND_MODRM_NO_OP_SIZES] = */ BS3CG1_PF_OZ | BS3CG1_PF_W, +}; + +#endif + + +/** + * Checks if >= 16 byte SSE alignment are exempted for the exception type. + * + * @returns true / false. + * @param enmXcptType The type to check. + */ +static bool BS3_NEAR_CODE Bs3Cg1XcptTypeIsUnaligned(BS3CG1XCPTTYPE enmXcptType) +{ + switch (enmXcptType) + { + case BS3CG1XCPTTYPE_1: + case BS3CG1XCPTTYPE_2: + case BS3CG1XCPTTYPE_4: + return false; + case BS3CG1XCPTTYPE_NONE: + case BS3CG1XCPTTYPE_3: + case BS3CG1XCPTTYPE_4UA: + case BS3CG1XCPTTYPE_5: + return true; + default: + return false; + } +} + + +/** + * Checks if >= 16 byte AVX alignment are exempted for the exception type. + * + * @returns true / false. + * @param enmXcptType The type to check. + */ +static bool BS3_NEAR_CODE Bs3Cg1XcptTypeIsVexUnaligned(BS3CG1XCPTTYPE enmXcptType) +{ + switch (enmXcptType) + { + case BS3CG1XCPTTYPE_1: + return false; + + case BS3CG1XCPTTYPE_NONE: + case BS3CG1XCPTTYPE_2: + case BS3CG1XCPTTYPE_3: + case BS3CG1XCPTTYPE_4: + case BS3CG1XCPTTYPE_4UA: + case BS3CG1XCPTTYPE_5: + case BS3CG1XCPTTYPE_6: + case BS3CG1XCPTTYPE_11: + case BS3CG1XCPTTYPE_12: + return true; + + default: + return false; + } +} + + +DECLINLINE(unsigned) BS3_NEAR_CODE Bs3Cg1InsertReqPrefix(PBS3CG1STATE pThis, unsigned offDst) +{ + switch (pThis->enmPrefixKind) + { + case BS3CG1PFXKIND_REQ_66: + pThis->abCurInstr[offDst] = 0x66; + break; + case BS3CG1PFXKIND_REQ_F2: + pThis->abCurInstr[offDst] = 0xf2; + break; + case BS3CG1PFXKIND_REQ_F3: + pThis->abCurInstr[offDst] = 0xf3; + break; + default: + return offDst; + } + return offDst + 1; +} + + +DECLINLINE(unsigned) BS3_NEAR_CODE Bs3Cg1InsertOpcodes(PBS3CG1STATE pThis, unsigned offDst) +{ + switch (pThis->cbOpcodes) + { + case 4: pThis->abCurInstr[offDst + 3] = pThis->abOpcodes[3]; + case 3: pThis->abCurInstr[offDst + 2] = pThis->abOpcodes[2]; + case 2: pThis->abCurInstr[offDst + 1] = pThis->abOpcodes[1]; + case 1: pThis->abCurInstr[offDst] = pThis->abOpcodes[0]; + return offDst + pThis->cbOpcodes; + + default: + BS3_ASSERT(0); + return 0; + } +} + + +/** + * Inserts a ModR/M byte with mod=3 and set the two idxFields members. + * + * @returns off + 1. + * @param pThis The state. + * @param off Current instruction offset. + * @param uReg Register index for ModR/M.reg. + * @param uRegMem Register index for ModR/M.rm. + */ +static unsigned Bs3Cg1InsertModRmWithRegFields(PBS3CG1STATE pThis, unsigned off, uint8_t uReg, uint8_t uRegMem) +{ + pThis->abCurInstr[off++] = X86_MODRM_MAKE(3, uReg & 7, uRegMem & 7); + pThis->aOperands[pThis->iRegOp].idxField = pThis->aOperands[pThis->iRegOp].idxFieldBase + uReg; + pThis->aOperands[pThis->iRmOp ].idxField = pThis->aOperands[pThis->iRmOp ].idxFieldBase + uRegMem; + return off; +} + + + +/** + * Cleans up state and context changes made by the encoder. + * + * @param pThis The state. + */ +static void BS3_NEAR_CODE Bs3Cg1EncodeCleanup(PBS3CG1STATE pThis) +{ + /* Restore the DS registers in the contexts. */ + unsigned iRing = 4; + while (iRing-- > 0) + pThis->aInitialCtxs[iRing].ds = pThis->aSavedSegRegs[iRing].ds; + + switch (pThis->enmEncoding) + { + /* Most encodings currently doesn't need any special cleaning up. */ + default: + return; + } +} + + +static unsigned BS3_NEAR_CODE Bs3Cfg1EncodeMemMod0Disp(PBS3CG1STATE pThis, bool fAddrOverride, unsigned off, uint8_t iReg, + uint8_t cbOp, uint8_t cbMisalign, BS3CG1OPLOC enmLocation) +{ + pThis->aOperands[pThis->iRmOp].idxField = BS3CG1DST_INVALID; + pThis->aOperands[pThis->iRmOp].enmLocation = enmLocation; + pThis->aOperands[pThis->iRmOp].cbOp = cbOp; + pThis->aOperands[pThis->iRmOp].off = cbOp + cbMisalign; + + if ( BS3_MODE_IS_16BIT_CODE(pThis->bMode) + || (fAddrOverride && BS3_MODE_IS_32BIT_CODE(pThis->bMode)) ) + { + /* + * 16-bit code doing 16-bit or 32-bit addressing, + * or 32-bit code doing 16-bit addressing. + */ + unsigned iRing = 4; + if (BS3_MODE_IS_RM_OR_V86(pThis->bMode)) + while (iRing-- > 0) + pThis->aInitialCtxs[iRing].ds = pThis->DataPgFar.sel; + else + while (iRing-- > 0) + pThis->aInitialCtxs[iRing].ds = pThis->DataPgFar.sel | iRing; + if (!fAddrOverride || BS3_MODE_IS_32BIT_CODE(pThis->bMode)) + { + pThis->abCurInstr[off++] = X86_MODRM_MAKE(0, iReg, 6 /*disp16*/); + *(uint16_t *)&pThis->abCurInstr[off] = pThis->DataPgFar.off + X86_PAGE_SIZE - cbOp - cbMisalign; + off += 2; + } + else + { + pThis->abCurInstr[off++] = X86_MODRM_MAKE(0, iReg, 5 /*disp32*/); + *(uint32_t *)&pThis->abCurInstr[off] = pThis->DataPgFar.off + X86_PAGE_SIZE - cbOp - cbMisalign; + off += 4; + } + } + else + { + /* + * 32-bit code doing 32-bit addressing, + * or 64-bit code doing either 64-bit or 32-bit addressing. + */ + pThis->abCurInstr[off++] = X86_MODRM_MAKE(0, iReg, 5 /*disp32*/); + *(uint32_t *)&pThis->abCurInstr[off] = BS3_FP_OFF(pThis->pbDataPg) + X86_PAGE_SIZE - cbOp - cbMisalign; + +#if ARCH_BITS == 64 + /* In 64-bit mode we always have a rip relative encoding regardless of fAddrOverride. */ + if (BS3CG1_IS_64BIT_TARGET(pThis)) + *(uint32_t *)&pThis->abCurInstr[off] -= BS3_FP_OFF(&pThis->pbCodePg[X86_PAGE_SIZE]); +#endif + off += 4; + } + + /* + * Fill the memory with 0xcc. + */ + switch (cbOp + cbMisalign) + { + case 8: pThis->pbDataPg[X86_PAGE_SIZE - 8] = 0xcc; RT_FALL_THRU(); + case 7: pThis->pbDataPg[X86_PAGE_SIZE - 7] = 0xcc; RT_FALL_THRU(); + case 6: pThis->pbDataPg[X86_PAGE_SIZE - 6] = 0xcc; RT_FALL_THRU(); + case 5: pThis->pbDataPg[X86_PAGE_SIZE - 5] = 0xcc; RT_FALL_THRU(); + case 4: pThis->pbDataPg[X86_PAGE_SIZE - 4] = 0xcc; RT_FALL_THRU(); + case 3: pThis->pbDataPg[X86_PAGE_SIZE - 3] = 0xcc; RT_FALL_THRU(); + case 2: pThis->pbDataPg[X86_PAGE_SIZE - 2] = 0xcc; RT_FALL_THRU(); + case 1: pThis->pbDataPg[X86_PAGE_SIZE - 1] = 0xcc; RT_FALL_THRU(); + case 0: break; + default: + { + BS3CG1_DPRINTF(("Bs3MemSet(%p,%#x,%#x)\n", &pThis->pbDataPg[X86_PAGE_SIZE - cbOp - cbMisalign], 0xcc, cbOp - cbMisalign)); + Bs3MemSet(&pThis->pbDataPg[X86_PAGE_SIZE - cbOp - cbMisalign], 0xcc, cbOp - cbMisalign); + break; + } + } + + return off; +} + + +#if 0 /* unused */ +/** Also encodes idxField of the register operand using idxFieldBase. */ +static unsigned BS3_NEAR_CODE +Bs3Cfg1EncodeMemMod0DispWithRegField(PBS3CG1STATE pThis, bool fAddrOverride, unsigned off, uint8_t iReg, + uint8_t cbOp, uint8_t cbMisalign, BS3CG1OPLOC enmLocation) +{ + pThis->aOperands[pThis->iRegOp].idxField = pThis->aOperands[pThis->iRegOp].idxFieldBase + iReg; + return Bs3Cfg1EncodeMemMod0Disp(pThis, fAddrOverride, off, iReg & 7, cbOp, cbMisalign, enmLocation); +} +#endif + +/** Also encodes idxField of the register operand using idxFieldBase. */ +static unsigned BS3_NEAR_CODE +Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(PBS3CG1STATE pThis, unsigned off, uint8_t iReg) +{ + pThis->aOperands[pThis->iRegOp].idxField = pThis->aOperands[pThis->iRegOp].idxFieldBase + iReg; + return Bs3Cfg1EncodeMemMod0Disp(pThis, false /*fAddrOverride*/, off, iReg & 7, + pThis->aOperands[pThis->iRmOp].cbOp, + 0 /*cbMisalign*/, + pThis->aOperands[pThis->iRmOp].enmLocation); +} + +/** Also encodes idxField of the register operand using idxFieldBase. */ +static unsigned BS3_NEAR_CODE +Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaultsAddrOverride(PBS3CG1STATE pThis, unsigned off, uint8_t iReg) +{ + pThis->aOperands[pThis->iRegOp].idxField = pThis->aOperands[pThis->iRegOp].idxFieldBase + iReg; + return Bs3Cfg1EncodeMemMod0Disp(pThis, true /*fAddrOverride*/, off, iReg & 7, + pThis->aOperands[pThis->iRmOp].cbOp, + 0 /*cbMisalign*/, + pThis->aOperands[pThis->iRmOp].enmLocation); +} + + +/** Also encodes idxField of the register operand using idxFieldBase. */ +static unsigned BS3_NEAR_CODE +Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaultsMisaligned(PBS3CG1STATE pThis, unsigned off, uint8_t iReg, uint8_t cbMisalign) +{ + pThis->aOperands[pThis->iRegOp].idxField = pThis->aOperands[pThis->iRegOp].idxFieldBase + iReg; + return Bs3Cfg1EncodeMemMod0Disp(pThis, false /*fAddrOverride*/, off, iReg & 7, + pThis->aOperands[pThis->iRmOp].cbOp, + cbMisalign, + pThis->aOperands[pThis->iRmOp].enmLocation); +} + + +/** Also encodes idxField of the register operand using idxFieldBase. */ +static unsigned BS3_NEAR_CODE +Bs3Cfg1EncodeMemMod0DispWithRegFieldAndSizeAndDefaults(PBS3CG1STATE pThis, unsigned off, uint8_t iReg, uint8_t cbOp) +{ + pThis->aOperands[pThis->iRegOp].idxField = pThis->aOperands[pThis->iRegOp].idxFieldBase + iReg; + return Bs3Cfg1EncodeMemMod0Disp(pThis, false /*fAddrOverride*/, off, iReg & 7, cbOp, 0 /*cbMisalign*/, + pThis->aOperands[pThis->iRmOp].enmLocation); +} + +/** Also encodes idxField of the register operand using idxFieldBase. */ +static unsigned BS3_NEAR_CODE +Bs3Cfg1EncodeMemMod0DispWithRegFieldAndSizeAndDefaultsAddrOverride(PBS3CG1STATE pThis, unsigned off, uint8_t iReg, uint8_t cbOp) +{ + pThis->aOperands[pThis->iRegOp].idxField = pThis->aOperands[pThis->iRegOp].idxFieldBase + iReg; + return Bs3Cfg1EncodeMemMod0Disp(pThis, true /*fAddrOverride*/, off, iReg & 7, cbOp, 0 /*cbMisalign*/, + pThis->aOperands[pThis->iRmOp].enmLocation); +} + + +/** The modrm.reg value is taken from the instruction byte at @a off. */ +static unsigned BS3_NEAR_CODE +Bs3Cfg1EncodeMemMod0DispWithDefaultsAndNoReg(PBS3CG1STATE pThis, unsigned off) +{ + return Bs3Cfg1EncodeMemMod0Disp(pThis, false /*fAddrOverride*/, off, + (pThis->abCurInstr[off] & X86_MODRM_REG_MASK) >> X86_MODRM_REG_SHIFT, + pThis->aOperands[pThis->iRmOp].cbOp, + 0 /*cbMisalign*/, + pThis->aOperands[pThis->iRmOp].enmLocation); +} + + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_MODRM_Eb_Gb_OR_ViceVersa(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + /* Start by reg,reg encoding. */ + case 0: + pThis->aOperands[pThis->iRmOp].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationReg; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, X86_GREG_xAX, X86_GREG_xCX); + break; + case 1: + pThis->aOperands[pThis->iRmOp].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationMem; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 5 /*CH*/); + break; + case 2: + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) < BS3CPU_80386) + return 0; + pThis->aOperands[pThis->iRmOp].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationMem; + pThis->abCurInstr[0] = P_OZ; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 6 /*DH*/); + break; + /* Tests with address overrides go last! */ + case 3: + pThis->abCurInstr[0] = P_AZ; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 1)); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaultsAddrOverride(pThis, off, 7 /*BH*/); + break; + + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_MODRM_Gv_Ev__OR__MODRM_Ev_Gv(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + unsigned cbOp; + switch (iEncoding) + { + case 0: + pThis->aOperands[pThis->iRmOp].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationReg; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, X86_GREG_xBX, X86_GREG_xDX); + cbOp = pThis->cbOpDefault; + break; + case 1: + pThis->aOperands[pThis->iRmOp].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationMem; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + cbOp = pThis->cbOpDefault; + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndSizeAndDefaults(pThis, off, X86_GREG_xBP, cbOp); + break; + case 2: + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) < BS3CPU_80386) + return 0; + pThis->aOperands[pThis->iRmOp].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationReg; + pThis->abCurInstr[0] = P_OZ; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 1)); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, X86_GREG_xAX, X86_GREG_xCX); + cbOp = pThis->cbOpOvrd66; + break; + case 3: + pThis->aOperands[pThis->iRmOp].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationMem; + pThis->abCurInstr[0] = P_OZ; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 1)); + cbOp = pThis->cbOpOvrd66; + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndSizeAndDefaults(pThis, off, X86_GREG_xSI, cbOp); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 2 : 0; + break; + case 4: + pThis->aOperands[pThis->iRmOp].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationReg; + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX_W___; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, X86_GREG_xBX, X86_GREG_xDX); + cbOp = pThis->cbOpOvrdRexW; + break; + case 5: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX__RB_; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, X86_GREG_x14, X86_GREG_x12); + cbOp = pThis->cbOpDefault; + break; + /* Tests with address overrides go last!*/ + case 6: + pThis->aOperands[pThis->iRmOp].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationMem; + pThis->abCurInstr[0] = P_AZ; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 1)); + cbOp = pThis->cbOpDefault; + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndSizeAndDefaultsAddrOverride(pThis, off, X86_GREG_xDI, cbOp); + break; + case 7: + pThis->abCurInstr[0] = P_OZ; + pThis->abCurInstr[1] = P_AZ; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 2)); + cbOp = pThis->cbOpOvrd66; + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndSizeAndDefaultsAddrOverride(pThis, off, X86_GREG_xDI, cbOp); + break; + default: + return 0; + } + pThis->aOperands[0].cbOp = cbOp; + pThis->aOperands[1].cbOp = cbOp; + pThis->cbOperand = cbOp; + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_MODRM_Pq_WO_Qq(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 0: + pThis->aOperands[pThis->iRmOp ].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationReg; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 1, 0); + break; + case 1: + pThis->aOperands[pThis->iRmOp ].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationReg; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 4, 7); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; +#if ARCH_BITS == 64 + case 2: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX__RBX; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6 /*no +8*/, 2 /*no +8*/); + break; +#endif + case 3: + pThis->aOperands[pThis->iRmOp ].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationMem; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 4 /*iReg*/); + break; + case 4: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaultsMisaligned(pThis, off, 7 /*iReg*/, 1 /*cbMisalign*/); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; +#if ARCH_BITS == 64 + case 5: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX__RBX; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 7 /*iReg - no +8*/); + break; +#endif + + default: + return 0; + } + + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_MODRM_Pq_WO_Uq(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 0: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 1, 0); + break; + case 1: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6, 2); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; + case 2: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX__RBX; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6 /*no+8*/, 2 + 8); + break; + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_MODRM_PdZx_WO_Ed_WZ(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 0: + pThis->aOperands[pThis->iRmOp ].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationReg; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 1, 0); + break; + case 1: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6, 2); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; +#if ARCH_BITS == 64 + case 2: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX__RBX; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6 /*no +8*/, 2+8); + break; +#endif + case 3: + pThis->aOperands[pThis->iRmOp ].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationMem; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 4 /*iReg*/); + break; + case 4: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaultsMisaligned(pThis, off, 7 /*iReg*/, 1 /*cbMisalign*/); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; +#if ARCH_BITS == 64 + case 5: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX__RBX; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 7 /*iReg*/); + break; +#endif + + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_MODRM_Pq_WO_Eq_WNZ(PBS3CG1STATE pThis, unsigned iEncoding) +{ +#if ARCH_BITS == 64 + if (BS3CG1_IS_64BIT_TARGET(pThis)) + { + unsigned off; + switch (iEncoding) + { + case 0: + pThis->aOperands[pThis->iRmOp ].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationReg; + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX_W___; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 1, 0); + break; + case 1: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX_W___; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6, 2); + break; + case 2: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX_WRBX; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6 /*no +8*/, 2+8); + break; + case 3: + pThis->aOperands[pThis->iRmOp ].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationMem; + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX_W___; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 4 /*iReg*/); + break; + case 4: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX_W___; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaultsMisaligned(pThis, off, 7 /*iReg*/, 1 /*cbMisalign*/); + break; + case 5: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX_WRBX; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 7 /*iReg*/); + break; + + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; + } +#endif + return 0; +} + + +/* Differs from Bs3Cg1EncodeNext_MODRM_PdZx_WO_Ed_WZ in that REX.R isn't ignored. */ +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_MODRM_Vd_WO_Ed_WZ(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 0: + pThis->aOperands[pThis->iRmOp ].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationReg; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 1, 0); + break; + case 1: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6, 2); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; +#if ARCH_BITS == 64 + case 2: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX__RBX; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6+8, 2+8); + break; +#endif + case 3: + pThis->aOperands[pThis->iRmOp ].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationMem; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 4 /*iReg*/); + break; + case 4: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaultsMisaligned(pThis, off, 7 /*iReg*/, 1 /*cbMisalign*/); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; +#if ARCH_BITS == 64 + case 5: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX__RBX; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 7+8 /*iReg*/); + break; +#endif + + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +/* Differs from Bs3Cg1EncodeNext_MODRM_Pq_WO_Eq_WNZ in that REX.R isn't ignored. */ +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_MODRM_Vq_WO_Eq_WNZ(PBS3CG1STATE pThis, unsigned iEncoding) +{ +#if ARCH_BITS == 64 + if (BS3CG1_IS_64BIT_TARGET(pThis)) + { + unsigned off; + switch (iEncoding) + { + case 0: + pThis->aOperands[pThis->iRmOp ].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationReg; + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX_W___; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 1, 0); + break; + case 1: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX_W___; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6, 2); + break; + case 2: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX_WRBX; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6+8, 2+8); + break; + case 4: + pThis->aOperands[pThis->iRmOp ].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationMem; + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX_W___; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 4 /*iReg*/); + break; + case 5: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX_W___; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaultsMisaligned(pThis, off, 7 /*iReg*/, 1 /*cbMisalign*/); + break; + case 6: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX_WRBX; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 7+8 /*iReg*/); + break; + + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; + } +#endif + return 0; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_MODRM_Vsomething_Usomething_OR_ViceVersa(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 0: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 1, 0); + break; + case 1: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 2, 2); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; + case 2: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX__RBX; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 3+8, 7+8); + break; + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_MODRM_Vsomething_Wsomething_OR_ViceVersa(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 0: + pThis->aOperands[pThis->iRmOp].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationReg; + off = Bs3Cg1InsertModRmWithRegFields(pThis, Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)), 1, 0); + break; + case 1: + pThis->aOperands[pThis->iRmOp].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationMem; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 2 /*iReg*/); + break; + case 2: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaultsMisaligned(pThis, off, 3 /*iReg*/, 1 /*cbMisalign*/); + if (!Bs3Cg1XcptTypeIsUnaligned(pThis->enmXcptType)) + pThis->bAlignmentXcpt = X86_XCPT_GP; + break; + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_MODRM_Vsomething_Nsomething(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 0: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 1, 0); + break; + case 1: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6, 7); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; + case 2: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX_WRBX; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6 + 8, 7 /*no +8*/); + break; + + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_MODRM_Gv_RO_Ma(PBS3CG1STATE pThis, unsigned iEncoding) /* bound instr */ +{ + unsigned off; + unsigned cbOp = BS3_MODE_IS_16BIT_CODE(pThis->bMode) ? 2 : 4; + switch (iEncoding) + { + case 0: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndSizeAndDefaults(pThis, off, X86_GREG_xBP, cbOp * 2); + break; + case 1: + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) < BS3CPU_80386) + return 0; + cbOp = cbOp == 2 ? 4 : 2; + pThis->abCurInstr[0] = P_OZ; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 1)); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndSizeAndDefaults(pThis, off, X86_GREG_xBP, cbOp * 2); + break; + case 2: + pThis->abCurInstr[0] = P_AZ; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 1)); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndSizeAndDefaultsAddrOverride(pThis, off, X86_GREG_xBP, cbOp * 2); + break; + case 3: + cbOp = cbOp == 2 ? 4 : 2; + pThis->abCurInstr[0] = P_AZ; + pThis->abCurInstr[1] = P_OZ; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 2)); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndSizeAndDefaultsAddrOverride(pThis, off, X86_GREG_xBP, cbOp * 2); + break; + default: + return 0; + } + pThis->aOperands[pThis->iRegOp].cbOp = cbOp; + pThis->cbOperand = cbOp; + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_MODRM_Msomething(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 0: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)) - 1; + off = Bs3Cfg1EncodeMemMod0DispWithDefaultsAndNoReg(pThis, off); + break; + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_MODRM_Msomething_Psomething(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 0: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 4 /*iReg*/); + break; + case 1: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaultsMisaligned(pThis, off, 7 /*iReg*/, 1 /*cbMisalign*/); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; +#if ARCH_BITS == 64 + case 2: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX__RBX; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 7 /*iReg - no +8*/); + break; +#endif + + default: + return 0; + } + + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_MODRM_Msomething_Vsomething_OR_ViceVersa(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 0: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 2 /*iReg*/); + break; + case 1: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaultsMisaligned(pThis, off, 2 /*iReg*/, 1 /*cbMisalign*/ ); + if (!Bs3Cg1XcptTypeIsUnaligned(pThis->enmXcptType)) + pThis->bAlignmentXcpt = X86_XCPT_GP; + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; + case 2: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX__R__; + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 2+8 /*iReg*/); + break; + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_FIXED(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 0: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + pThis->cbCurInstr = off; + break; + default: + return 0; + } + return iEncoding + 1; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_FIXED_AL_Ib(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 0: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + pThis->aOperands[1].off = (uint8_t)off; + pThis->abCurInstr[off++] = 0xff; + pThis->cbCurInstr = off; + break; + default: + return 0; + } + return iEncoding + 1; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_FIXED_rAX_Iz(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + unsigned cbOp; + switch (iEncoding) + { + case 0: + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 0)); + pThis->aOperands[1].off = (uint8_t)off; + cbOp = pThis->cbOpDefault; + if (cbOp == 2) + *(uint16_t *)&pThis->abCurInstr[off] = UINT16_MAX; + else + *(uint32_t *)&pThis->abCurInstr[off] = UINT32_MAX; + off += cbOp; + pThis->aOperands[0].cbOp = cbOp; + pThis->aOperands[1].cbOp = cbOp; + pThis->cbOperand = cbOp; + break; + case 1: + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) < BS3CPU_80386) + return 0; + pThis->abCurInstr[0] = P_OZ; + off = Bs3Cg1InsertOpcodes(pThis, Bs3Cg1InsertReqPrefix(pThis, 1)); + pThis->aOperands[1].off = (uint8_t)off; + cbOp = pThis->cbOpOvrd66; + if (cbOp == 2) + *(uint16_t *)&pThis->abCurInstr[off] = UINT16_MAX; + else + *(uint32_t *)&pThis->abCurInstr[off] = UINT32_MAX; + off += cbOp; + pThis->aOperands[0].cbOp = cbOp; + pThis->aOperands[1].cbOp = cbOp; + pThis->cbOperand = cbOp; + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; + case 2: + off = Bs3Cg1InsertReqPrefix(pThis, 0); + pThis->abCurInstr[off++] = REX_W___; + off = Bs3Cg1InsertOpcodes(pThis, off); + pThis->aOperands[1].off = (uint8_t)off; + *(uint32_t *)&pThis->abCurInstr[off] = UINT32_MAX; + off += 4; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 4; + pThis->cbOperand = 8; + break; + default: + return 0; + + /* IMAGE PADDING - workaround for "rd err" - remove later! */ + case 4: + ASMHalt(); + ASMHalt(); + ASMHalt(); + return 0; + + } + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_MODRM_MOD_EQ_3(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + if (iEncoding < 8) + { + off = Bs3Cg1InsertReqPrefix(pThis, 0); + off = Bs3Cg1InsertOpcodes(pThis, off); + pThis->abCurInstr[off++] = X86_MODRM_MAKE(3, iEncoding, 1); + } + else if (iEncoding < 16) + { + off = Bs3Cg1InsertReqPrefix(pThis, 0); + off = Bs3Cg1InsertOpcodes(pThis, off); + pThis->abCurInstr[off++] = X86_MODRM_MAKE(3, 0, iEncoding & 7); + } + else + return 0; + pThis->cbCurInstr = off; + + return iEncoding + 1; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_MODRM_MOD_NE_3(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + if (iEncoding < 3) + { + off = Bs3Cg1InsertReqPrefix(pThis, 0); + off = Bs3Cg1InsertOpcodes(pThis, off); + pThis->abCurInstr[off++] = X86_MODRM_MAKE(iEncoding, 0, 1); + if (iEncoding >= 1) + pThis->abCurInstr[off++] = 0x7f; + if (iEncoding == 2) + { + pThis->abCurInstr[off++] = 0x5f; + if (!BS3_MODE_IS_16BIT_CODE(pThis->bMode)) + { + pThis->abCurInstr[off++] = 0x3f; + pThis->abCurInstr[off++] = 0x1f; + } + } + } + else + return 0; + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +/* + * + * VEX + * VEX + * VEX + * + */ +#ifdef BS3CG1_WITH_VEX + +/** + * Inserts a 3-byte VEX prefix. + * + * @returns New offDst value. + * @param pThis The state. + * @param offDst The current instruction offset. + * @param uVexL The VEX.L value. + * @param uVexV The VEX.V value (caller inverted it already). + * @param uVexR The VEX.R value (caller inverted it already). + * @param uVexX The VEX.X value (caller inverted it already). + * @param uVexB The VEX.B value (caller inverted it already). + * @param uVexW The VEX.W value (straight). + */ +DECLINLINE(unsigned) BS3_NEAR_CODE Bs3Cg1InsertVex3bPrefix(PBS3CG1STATE pThis, unsigned offDst, uint8_t uVexV, uint8_t uVexL, + uint8_t uVexR, uint8_t uVexX, uint8_t uVexB, uint8_t uVexW) +{ + uint8_t b1; + uint8_t b2; + b1 = uVexR << 7; + b1 |= uVexX << 6; + b1 |= uVexB << 5; + b1 |= pThis->uOpcodeMap; + b2 = uVexV << 3; + b2 |= uVexW << 7; + b2 |= uVexL << 2; + switch (pThis->enmPrefixKind) + { + case BS3CG1PFXKIND_NO_F2_F3_66: b2 |= 0; break; + case BS3CG1PFXKIND_REQ_66: b2 |= 1; break; + case BS3CG1PFXKIND_REQ_F3: b2 |= 2; break; + case BS3CG1PFXKIND_REQ_F2: b2 |= 3; break; + default: + Bs3TestFailedF("enmPrefixKind=%d not supported for VEX!\n", pThis->enmPrefixKind); + break; + } + + pThis->abCurInstr[offDst] = 0xc4; /* vex3 */ + pThis->abCurInstr[offDst + 1] = b1; + pThis->abCurInstr[offDst + 2] = b2; + pThis->uVexL = uVexL; + return offDst + 3; +} + + +/** + * Inserts a 2-byte VEX prefix. + * + * @note Will switch to 3-byte VEX prefix if uOpcodeMap isn't one. + * + * @returns New offDst value. + * @param pThis The state. + * @param offDst The current instruction offset. + * @param uVexL The VEX.L value. + * @param uVexV The VEX.V value (caller inverted it already). + * @param uVexR The VEX.R value (caller inverted it already). + */ +DECLINLINE(unsigned) BS3_NEAR_CODE Bs3Cg1InsertVex2bPrefix(PBS3CG1STATE pThis, unsigned offDst, + uint8_t uVexV, uint8_t uVexL, uint8_t uVexR) +{ + if (pThis->uOpcodeMap == 1) + { + uint8_t b = uVexR << 7; + b |= uVexV << 3; + b |= uVexL << 2; + switch (pThis->enmPrefixKind) + { + case BS3CG1PFXKIND_NO_F2_F3_66: b |= 0; break; + case BS3CG1PFXKIND_REQ_66: b |= 1; break; + case BS3CG1PFXKIND_REQ_F3: b |= 2; break; + case BS3CG1PFXKIND_REQ_F2: b |= 3; break; + default: + Bs3TestFailedF("enmPrefixKind=%d not supported for VEX!\n"); + break; + } + + pThis->abCurInstr[offDst] = 0xc5; /* vex2 */ + pThis->abCurInstr[offDst + 1] = b; + pThis->uVexL = uVexL; + return offDst + 2; + } + return Bs3Cg1InsertVex3bPrefix(pThis, offDst, uVexV, uVexL, uVexR, 1 /*uVexX*/, 1 /*uVexB*/, 0/*uVexW*/); +} + + +/** + * Inserts a ModR/M byte with mod=3 and set the two idxFields members. + * + * @returns off + 1. + * @param pThis The state. + * @param off Current instruction offset. + * @param uReg Register index for ModR/M.reg. + * @param uRegMem Register index for ModR/M.rm. + * @param uVexVvvv The VEX.vvvv register. + */ +static unsigned Bs3Cg1InsertModRmWithRegFieldsAndVvvv(PBS3CG1STATE pThis, unsigned off, + uint8_t uReg, uint8_t uRegMem, uint8_t uVexVvvv) +{ + pThis->abCurInstr[off++] = X86_MODRM_MAKE(3, uReg & 7, uRegMem & 7); + pThis->aOperands[pThis->iRegOp].idxField = pThis->aOperands[pThis->iRegOp].idxFieldBase + uReg; + pThis->aOperands[1 ].idxField = pThis->aOperands[1 ].idxFieldBase + uVexVvvv; + pThis->aOperands[pThis->iRmOp ].idxField = pThis->aOperands[pThis->iRmOp ].idxFieldBase + uRegMem; + return off; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_VEX_MODRM_Vd_WO_Ed_WZ(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 0: + pThis->aOperands[pThis->iRmOp ].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationReg; + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 1, 0); + break; + case 1: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6, 2); + break; + case 2: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 1 /*L-invalid*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6, 2); + pThis->fInvalidEncoding = true; + break; + case 3: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xe /*~V-invalid*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6, 2); + pThis->fInvalidEncoding = true; + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; +#if ARCH_BITS == 64 + case 4: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 0 /*~R*/, 1 /*~X*/, 0 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6+8, 2+8); + break; +#endif + case 5: + pThis->aOperands[pThis->iRmOp ].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationMem; + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 4 /*iReg*/); + break; + case 6: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 4 /*iReg*/); + break; + case 7: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaultsMisaligned(pThis, off, 4 /*iReg*/, 1 /*cbMisalign*/); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 2 : 0; + break; +#if ARCH_BITS == 64 + case 8: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 0 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 4+8 /*iReg*/); + break; + case 9: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 0 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 5+8 /*iReg*/); + iEncoding += 2; + break; +#endif + case 10: /* VEX.W is ignored in 32-bit mode. flag? */ + BS3_ASSERT(!BS3CG1_IS_64BIT_TARGET(pThis)); + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 4 /*iReg*/); + break; + + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +/* Differs from Bs3Cg1EncodeNext_MODRM_Pq_WO_Eq_WNZ in that REX.R isn't ignored. */ +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_VEX_MODRM_Vq_WO_Eq_WNZ(PBS3CG1STATE pThis, unsigned iEncoding) +{ +#if ARCH_BITS == 64 + if (BS3CG1_IS_64BIT_TARGET(pThis)) + { + unsigned off; + switch (iEncoding) + { + case 0: + pThis->aOperands[pThis->iRmOp].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationReg; + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6, 2); + break; + case 1: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 1 /*L-invalid*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6, 2); + pThis->fInvalidEncoding = true; + break; + case 2: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xe /*~V-invalid*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6, 2); + pThis->fInvalidEncoding = true; + break; + case 3: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 0 /*~R*/, 1 /*~X*/, 0 /*~B*/, 1 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 6+8, 2+8); + break; + case 4: + pThis->aOperands[pThis->iRmOp ].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationMem; + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 4 /*iReg*/); + break; + case 5: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaultsMisaligned(pThis, off, 4 /*iReg*/, 1 /*cbMisalign*/); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 2 : 0; + break; + case 6: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 0 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 4+8 /*iReg*/); + break; + + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; + } +#endif + return 0; +} + + +/** + * Wip - VEX.W ignored. + * Lig - VEX.L ignored. + */ +static unsigned BS3_NEAR_CODE +Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Hsomething_Usomething_Lip_Wip_OR_ViceVersa(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 0: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFieldsAndVvvv(pThis, off, 2, 1, 0); + break; + case 1: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0x8 /*~V*/, 1 /*L-ignored*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFieldsAndVvvv(pThis, off, 3, 1, 7); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; + case 2: +#if ARCH_BITS == 64 + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0 /*~V*/, 0 /*L*/, 0 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFieldsAndVvvv(pThis, off, 3+8, 2, 15); + break; +#endif + case 3: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFieldsAndVvvv(pThis, off, 2, 1, 0); + break; + case 4: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 1 /*L - ignored*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFieldsAndVvvv(pThis, off, 2, 1, 0); + break; + case 5: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xc /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W-ignored*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFieldsAndVvvv(pThis, off, 2, 1, 3); + break; + case 6: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0 /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W-ignored*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFieldsAndVvvv(pThis, off, 2, 1, BS3CG1_IS_64BIT_TARGET(pThis) ? 15 : 7); + break; + case 7: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0 /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFieldsAndVvvv(pThis, off, 2, 1, BS3CG1_IS_64BIT_TARGET(pThis) ? 15 : 7); + break; + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +/** + * Wip - VEX.W ignored. + */ +static unsigned BS3_NEAR_CODE +Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_HdqCsomething_Usomething_Wip_OR_ViceVersa(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 0: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFieldsAndVvvv(pThis, off, 2, 1, 0); + break; + case 1: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0x8 /*~V*/, 1 /*L-ignored*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFieldsAndVvvv(pThis, off, 3, 1, 7); + pThis->fInvalidEncoding = true; + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; + case 2: +#if ARCH_BITS == 64 + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0 /*~V*/, 0 /*L*/, 0 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFieldsAndVvvv(pThis, off, 3+8, 2, 15); + break; +#endif + case 3: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFieldsAndVvvv(pThis, off, 2, 1, 0); + break; + case 4: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 1 /*L - ignored*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFieldsAndVvvv(pThis, off, 2, 1, 0); + pThis->fInvalidEncoding = true; + break; + case 5: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xc /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W-ignored*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFieldsAndVvvv(pThis, off, 2, 1, 3); + break; + case 6: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0 /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W-ignored*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFieldsAndVvvv(pThis, off, 2, 1, BS3CG1_IS_64BIT_TARGET(pThis) ? 15 : 7); + break; + case 7: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0 /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFieldsAndVvvv(pThis, off, 2, 1, BS3CG1_IS_64BIT_TARGET(pThis) ? 15 : 7); + break; + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +/** + * Wip - VEX.W ignored. + */ +static unsigned BS3_NEAR_CODE +Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Msomething_Wip_OR_ViceVersa(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 20: /* Switch to 256-bit operands. */ + pThis->aOperands[pThis->iRegOp].idxFieldBase = BS3CG1DST_YMM0; + pThis->aOperands[pThis->iRegOp].cbOp = 32; + pThis->aOperands[pThis->iRmOp ].cbOp = 32; + RT_FALL_THRU(); + case 0: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 0); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; +#if ARCH_BITS == 64 + case 1: + case 21: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 0 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 7 + 8); + break; +#endif + case 2: + case 22: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xe /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 0); + pThis->fInvalidEncoding = true; + break; + case 3: + case 23: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 1); + break; + case 4: + case 24: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W-ignored*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 5); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 3 : 0; + break; +#if ARCH_BITS == 64 + case 5: + case 25: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 0 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 5+8); + break; + case 6: + case 26: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/, 1 /*~X*/, 0 /*~B-ignored*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 1); + break; + case 7: + case 27: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/, 0 /*~X-ignored*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 2); + break; +#endif + case 8: + case 28: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0 /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 5); + pThis->fInvalidEncoding = true; + break; + case 9: + case 29: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 7 /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 2); + pThis->fInvalidEncoding = true; + iEncoding += 10; + break; + + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + + +/** + * Wip - VEX.W ignored. + * Lig - VEX.L ignored. + */ +static unsigned BS3_NEAR_CODE +Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Msomething_Wip_Lig_OR_ViceVersa(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 0: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 0); + break; + case 1: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 1 /*L - ignored*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 7); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; +#if ARCH_BITS == 64 + case 2: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 1 /*L - ignored*/, 0 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 7 + 8); + break; +#endif + case 3: + iEncoding = 3; + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xe /*~V*/, 0 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 0); + pThis->fInvalidEncoding = true; + break; + case 4: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 1); + break; + case 5: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 1 /*L-ignored*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 1); + break; + case 6: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W-ignored*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 5); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 3 : 0; + break; +#if ARCH_BITS == 64 + case 7: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 0 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 5+8); + break; + case 8: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 0 /*~B-ignored*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 1); + break; + case 9: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 0 /*~X-ignored*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 2); + break; +#endif + case 10: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0 /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 5); + pThis->fInvalidEncoding = true; + break; + case 11: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 7 /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 2); + pThis->fInvalidEncoding = true; + break; + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +/** + * Wip - VEX.W ignored. + * L0 - VEX.L must be zero. + */ +static unsigned BS3_NEAR_CODE +Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Msomething_Wip_Lmbz_OR_ViceVersa(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 0: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 0); + break; + case 1: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 1 /*L - invalid*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 7); + pThis->fInvalidEncoding = true; + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 2 : 0; + break; +#if ARCH_BITS == 64 + case 2: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 0 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 7 + 8); + break; + case 3: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 1 /*L - invalid*/, 0 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 5 + 8); + pThis->fInvalidEncoding = true; + break; +#endif + case 4: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xe /*~V*/, 0 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 0); + pThis->fInvalidEncoding = true; + break; + case 5: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 1); + break; + case 6: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 1 /*L - invalid*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 1); + pThis->fInvalidEncoding = true; + break; + case 7: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W-ignored*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 5); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 3 : 0; + break; +#if ARCH_BITS == 64 + case 8: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 0 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 5+8); + break; + case 9: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 0 /*~B-ignored*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 1); + break; + case 10: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 0 /*~X-ignored*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 2); + break; +#endif + case 11: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0 /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 5); + pThis->fInvalidEncoding = true; + break; + case 12: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 7 /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 2); + pThis->fInvalidEncoding = true; + break; + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +/** + * Wip - VEX.W ignored. + */ +static unsigned BS3_NEAR_CODE +Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Msomething_Wip_Lxx_OR_ViceVersa(PBS3CG1STATE pThis, unsigned iEncoding, uint8_t uVexL) +{ + unsigned off; + switch (iEncoding) + { + case 0: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, uVexL, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 0); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; +#if ARCH_BITS == 64 + case 1: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, uVexL, 0 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 7 + 8); + break; +#endif + case 2: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xe /*~V*/, uVexL, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 0); + pThis->fInvalidEncoding = true; + break; + case 3: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, uVexL, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 1); + break; + case 4: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, uVexL, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W-ignored*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 5); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 3 : 0; + break; +#if ARCH_BITS == 64 + case 5: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, uVexL, 0 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 5+8); + break; + case 6: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, uVexL, 1 /*~R*/, 1 /*~X*/, 0 /*~B-ignored*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 1); + break; + case 7: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, uVexL, 1 /*~R*/, 0 /*~X-ignored*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 2); + break; +#endif + case 8: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0 /*~V*/, uVexL, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 5); + pThis->fInvalidEncoding = true; + break; + case 9: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 7 /*~V*/, uVexL, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 2); + pThis->fInvalidEncoding = true; + break; + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +/** + * Wip - VEX.W ignored. + * L0 - VEX.L is zero (encoding may exist where it isn't). + */ +static unsigned BS3_NEAR_CODE +Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Msomething_Wip_L0_OR_ViceVersa(PBS3CG1STATE pThis, unsigned iEncoding) +{ + return Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Msomething_Wip_Lxx_OR_ViceVersa(pThis, iEncoding, 0 /*uVexL*/); +} + + +/** + * Wip - VEX.W ignored. + * L1 - VEX.L is one (encoding may exist where it isn't). + */ +static unsigned BS3_NEAR_CODE +Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Msomething_Wip_L1_OR_ViceVersa(PBS3CG1STATE pThis, unsigned iEncoding) +{ + return Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Msomething_Wip_Lxx_OR_ViceVersa(pThis, iEncoding, 1 /*uVexL*/); +} + + + +/** + * Wip - VEX.W ignored. + */ +static unsigned BS3_NEAR_CODE +Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Hsomething_Msomething_Wip_OR_ViceVersa(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 0: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xc /*~V*/, 0 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 0); + pThis->aOperands[1].idxField = pThis->aOperands[1].idxFieldBase + 3; + break; + case 1: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 1 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 7); + pThis->aOperands[1].idxField = pThis->aOperands[1].idxFieldBase + 0; + pThis->fInvalidEncoding = true; + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; +#if ARCH_BITS == 64 + case 2: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0x1 /*~V*/, 0 /*L*/, 0 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 7 + 8); + pThis->aOperands[1].idxField = pThis->aOperands[1].idxFieldBase + 14; + break; +#endif + case 3: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xe /*~V*/, 0 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 0); + pThis->aOperands[1].idxField = pThis->aOperands[1].idxFieldBase + 1; + break; + case 4: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 1); + pThis->aOperands[1].idxField = pThis->aOperands[1].idxFieldBase + 0; + break; + case 5: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 1 /*L-ignored*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 1); + pThis->aOperands[1].idxField = pThis->aOperands[1].idxFieldBase + 0; + pThis->fInvalidEncoding = true; + break; + case 6: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W-ignored*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 5); + pThis->aOperands[1].idxField = pThis->aOperands[1].idxFieldBase + 0; + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 3 : 0; + break; +#if ARCH_BITS == 64 + case 7: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 0 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 5+8); + pThis->aOperands[1].idxField = pThis->aOperands[1].idxFieldBase + 0; + break; + case 8: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 0 /*~B-ignored*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 1); + pThis->aOperands[1].idxField = pThis->aOperands[1].idxFieldBase + 0; + break; + case 9: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 0 /*~X-ignored*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 2); + pThis->aOperands[1].idxField = pThis->aOperands[1].idxFieldBase + 0; + break; +#endif + case 10: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0 /*~V*/, 1 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 5); + pThis->aOperands[1].idxField = pThis->aOperands[1].idxFieldBase + (BS3CG1_IS_64BIT_TARGET(pThis) ? 15 : 7); + pThis->fInvalidEncoding = true; + break; + default: + return 0; + } + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_VEX_MODRM_Md_WO(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + case 0: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off) - 1; + off = Bs3Cfg1EncodeMemMod0DispWithDefaultsAndNoReg(pThis, off); + break; + case 1: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off) - 1; + off = Bs3Cfg1EncodeMemMod0DispWithDefaultsAndNoReg(pThis, off); + break; + case 2: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0x7 /*~V-invalid*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off) - 1; + off = Bs3Cfg1EncodeMemMod0DispWithDefaultsAndNoReg(pThis, off); + pThis->fInvalidEncoding = true; + break; + case 3: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 1 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off) - 1; + off = Bs3Cfg1EncodeMemMod0DispWithDefaultsAndNoReg(pThis, off); + pThis->fInvalidEncoding = true; + break; + case 4: + pThis->abCurInstr[0] = P_OZ; + off = Bs3Cg1InsertVex3bPrefix(pThis, 1 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off) - 1; + off = Bs3Cfg1EncodeMemMod0DispWithDefaultsAndNoReg(pThis, off); + pThis->fInvalidEncoding = true; + break; + case 5: + pThis->abCurInstr[0] = P_RZ; + off = Bs3Cg1InsertVex3bPrefix(pThis, 1 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off) - 1; + off = Bs3Cfg1EncodeMemMod0DispWithDefaultsAndNoReg(pThis, off); + pThis->fInvalidEncoding = true; + break; + case 6: + pThis->abCurInstr[0] = P_RN; + off = Bs3Cg1InsertVex3bPrefix(pThis, 1 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off) - 1; + off = Bs3Cfg1EncodeMemMod0DispWithDefaultsAndNoReg(pThis, off); + pThis->fInvalidEncoding = true; + break; + case 7: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off) - 1; + off = Bs3Cfg1EncodeMemMod0DispWithDefaultsAndNoReg(pThis, off); + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 1 : 0; + break; +#if ARCH_BITS == 64 + case 8: + pThis->abCurInstr[0] = REX_____; + off = Bs3Cg1InsertVex3bPrefix(pThis, 1 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off) - 1; + off = Bs3Cfg1EncodeMemMod0DispWithDefaultsAndNoReg(pThis, off); + pThis->fInvalidEncoding = true; + break; +#endif + default: + return 0; + } + + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +/** + * Wip = VEX.W ignored. + * Lmbz = VEX.L must be zero. + */ +static unsigned BS3_NEAR_CODE +Bs3Cg1EncodeNext_VEX_MODRM_WsomethingWO_Vsomething_Wip_Lmbz_OR_ViceVersa(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + switch (iEncoding) + { + /* 128-bit wide stuff goes first, then we'll update the operand widths afterwards. */ + case 0: + pThis->aOperands[pThis->iRmOp ].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationReg; + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 1, 0); + break; + + case 1: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 4, 5); + break; + case 2: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W - ignored*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 5, 4); + break; + case 3: + pThis->aOperands[pThis->iRmOp].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationMem; + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 2 /*iReg*/); + break; + case 4: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 3 /*iReg*/); + break; + case 5: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W - ignored */); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 3 /*iReg*/); + break; + case 6: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaultsMisaligned(pThis, off, 3 /*iReg*/, 1 /*cbMisalign*/); + if (!Bs3Cg1XcptTypeIsVexUnaligned(pThis->enmXcptType)) + pThis->bAlignmentXcpt = X86_XCPT_GP; + break; + case 7: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaultsMisaligned(pThis, off, 3 /*iReg*/, 1 /*cbMisalign*/); + if (!Bs3Cg1XcptTypeIsVexUnaligned(pThis->enmXcptType)) + pThis->bAlignmentXcpt = X86_XCPT_GP; + break; + /* 128-bit invalid encodings: */ + case 8: + pThis->aOperands[pThis->iRmOp ].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationReg; + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xe /*~V*/, 0 /*L*/, 1 /*~R*/); /* Bad V value */ + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 1, 0); + pThis->fInvalidEncoding = true; + break; + case 9: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0 /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 4, 5); + pThis->fInvalidEncoding = true; + iEncoding = 20-1; + break; + + default: + return 0; + } + + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +/** + * Wip = VEX.W ignored. + */ +static unsigned BS3_NEAR_CODE +Bs3Cg1EncodeNext_VEX_MODRM_WsomethingWO_Vsomething_Wip_OR_ViceVersa(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + + switch (iEncoding) + { + case 20: /* switch to 256-bit */ + pThis->aOperands[pThis->iRmOp ].cbOp = 32; + pThis->aOperands[pThis->iRmOp ].idxFieldBase = BS3CG1DST_YMM0; + pThis->aOperands[pThis->iRegOp].cbOp = 32; + pThis->aOperands[pThis->iRegOp].idxFieldBase = BS3CG1DST_YMM0; + RT_FALL_THRU(); + case 0: + pThis->aOperands[pThis->iRmOp ].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationReg; + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 1, 0); + break; + + case 1: + case 21: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 4, 5); + break; + case 2: + case 22: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W - ignored*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 5, 4); + break; + case 3: + case 23: + pThis->aOperands[pThis->iRmOp].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationMem; + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 2 /*iReg*/); + break; + case 4: + case 24: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 3 /*iReg*/); + break; + case 5: + case 25: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 1 /*W - ignored */); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaults(pThis, off, 3 /*iReg*/); + break; + case 6: + case 26: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaultsMisaligned(pThis, off, 3 /*iReg*/, 1 /*cbMisalign*/); + if (!Bs3Cg1XcptTypeIsVexUnaligned(pThis->enmXcptType)) + pThis->bAlignmentXcpt = X86_XCPT_GP; + break; + case 7: + case 27: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cfg1EncodeMemMod0DispWithRegFieldAndDefaultsMisaligned(pThis, off, 3 /*iReg*/, 1 /*cbMisalign*/); + if (!Bs3Cg1XcptTypeIsVexUnaligned(pThis->enmXcptType)) + pThis->bAlignmentXcpt = X86_XCPT_GP; + break; + /* invalid encodings: */ + case 8: + case 28: + pThis->aOperands[pThis->iRmOp ].enmLocation = pThis->aOperands[pThis->iRmOp].enmLocationReg; + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xe /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/); /* Bad V value */ + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 1, 0); + pThis->fInvalidEncoding = true; + break; + case 9: + case 29: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0 /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 4, 5); + pThis->fInvalidEncoding = true; + break; + + case 10: + case 30: + pThis->abCurInstr[0] = P_RN; + off = Bs3Cg1InsertVex3bPrefix(pThis, 1 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 4, 5); + pThis->fInvalidEncoding = true; + break; + case 11: + case 31: + pThis->abCurInstr[0] = P_RZ; + off = Bs3Cg1InsertVex3bPrefix(pThis, 1 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 4, 5); + pThis->fInvalidEncoding = true; + break; + case 12: + case 32: + pThis->abCurInstr[0] = P_OZ; + off = Bs3Cg1InsertVex3bPrefix(pThis, 1 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 4, 5); + pThis->fInvalidEncoding = true; + break; + case 13: + case 33: + pThis->abCurInstr[0] = P_LK; + off = Bs3Cg1InsertVex3bPrefix(pThis, 1 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 4, 5); + pThis->fInvalidEncoding = true; + iEncoding += !BS3CG1_IS_64BIT_TARGET(pThis) ? 2 + 4 : 0; + break; + +#if ARCH_BITS == 64 + /* 64-bit mode registers */ + case 14: + case 34: + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 0 /*~R*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 3+8, 4); + break; + case 15: + case 35: + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, iEncoding >= 20 /*L*/, 0 /*~R*/, 1 /*~X*/, 0 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + off = Bs3Cg1InsertModRmWithRegFields(pThis, off, 1+8, 4+8); + iEncoding += 4; + break; +#endif + default: + return 0; + } + + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +//static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_VEX_FIXED(PBS3CG1STATE pThis, unsigned iEncoding) +//{ +// unsigned off; +// if (iEncoding == 0) +// off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/); +// else if (iEncoding == 0) +// off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); +// else +// return 0; +// pThis->cbCurInstr = off; +// return iEncoding + 1; +//} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_VEX_MODRM_MOD_EQ_3(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + if (iEncoding < 8) + { + if (iEncoding & 1) + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/); + else + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + pThis->abCurInstr[off++] = X86_MODRM_MAKE(3, iEncoding, 1); + } + else if (iEncoding < 16) + { + if (iEncoding & 1) + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 1 /*L*/, 1 /*~R*/); + else + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 1 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + pThis->abCurInstr[off++] = X86_MODRM_MAKE(3, iEncoding & 7, 1); + } + else if (iEncoding < 24) + { + if (iEncoding & 1) + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/); + else + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, 0 /*L*/, 1 /*~R*/, 1 /*~X*/, 1 /*~B*/, 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + pThis->abCurInstr[off++] = X86_MODRM_MAKE(3, 0, iEncoding & 7); + } + else if (iEncoding < 32) + { + if (iEncoding & 1) + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, (iEncoding & 3) != 0 /*L*/, 1 /*~R*/); + else + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, (iEncoding & 2) != 0 /*L*/, 1 /*~R*/, 1 /*~X*/, + 1 /*~B*/, (iEncoding & 4) != 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + pThis->abCurInstr[off++] = X86_MODRM_MAKE(3, 0, iEncoding & 7); + } + else + return 0; + pThis->cbCurInstr = off; + + return iEncoding + 1; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_VEX_MODRM_MOD_NE_3(PBS3CG1STATE pThis, unsigned iEncoding) +{ + unsigned off; + if (iEncoding < 8) + { + unsigned iMod = iEncoding % 3; + if (iEncoding & 1) + off = Bs3Cg1InsertVex2bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, (iEncoding & 2) != 0 /*L*/, 1 /*~R*/); + else + off = Bs3Cg1InsertVex3bPrefix(pThis, 0 /*offDst*/, 0xf /*~V*/, (iEncoding & 2) != 0 /*L*/, 1 /*~R*/, + 1 /*~X*/, 1 /*~B*/, (iEncoding & 4) != 0 /*W*/); + off = Bs3Cg1InsertOpcodes(pThis, off); + pThis->abCurInstr[off++] = X86_MODRM_MAKE(iMod, 0, 1); + if (iMod >= 1) + pThis->abCurInstr[off++] = 0x7f; + if (iMod == 2) + { + pThis->abCurInstr[off++] = 0x5f; + if (!BS3_MODE_IS_16BIT_CODE(pThis->bMode)) + { + pThis->abCurInstr[off++] = 0x3f; + pThis->abCurInstr[off++] = 0x1f; + } + } + } + else + return 0; + pThis->cbCurInstr = off; + return iEncoding + 1; +} + + +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext_VEX_MODRM(PBS3CG1STATE pThis, unsigned iEncoding) +{ + const unsigned cFirstEncodings = 32; + if (iEncoding < cFirstEncodings) + { + unsigned iRet = Bs3Cg1EncodeNext_VEX_MODRM_MOD_EQ_3(pThis, iEncoding); + BS3_ASSERT(iRet > iEncoding); + return iRet; + } + return Bs3Cg1EncodeNext_VEX_MODRM_MOD_NE_3(pThis, iEncoding - cFirstEncodings) + cFirstEncodings; +} + +#endif /* BS3CG1_WITH_VEX */ + + +/** + * Encodes the next instruction. + * + * @returns Next iEncoding value. Returns @a iEncoding unchanged to indicate + * that there are no more encodings to test. + * @param pThis The state. + * @param iEncoding The encoding to produce. Meaning is specific to + * each BS3CG1ENC_XXX value and should be considered + * internal. + */ +static unsigned BS3_NEAR_CODE Bs3Cg1EncodeNext(PBS3CG1STATE pThis, unsigned iEncoding) +{ + pThis->bAlignmentXcpt = UINT8_MAX; + pThis->uVexL = UINT8_MAX; + if (pThis->pfnEncoder) + return pThis->pfnEncoder(pThis, iEncoding); + + Bs3TestFailedF("Internal error! BS3CG1ENC_XXX = %u not implemented", pThis->enmEncoding); + return iEncoding; +} + + +/** + * Prepares doing instruction encodings. + * + * This is in part specific to how the instruction is encoded, but generally it + * sets up basic operand values that doesn't change (much) when Bs3Cg1EncodeNext + * is called from within the loop. + * + * @returns Success indicator (true/false). + * @param pThis The state. + */ +#define Bs3Cg1EncodePrep BS3_CMN_NM(Bs3Cg1EncodePrep) +bool BS3_NEAR_CODE Bs3Cg1EncodePrep(PBS3CG1STATE pThis) +{ + unsigned i = 4; + while (i-- > 0) + pThis->aSavedSegRegs[i].ds = pThis->aInitialCtxs[i].ds; + + i = RT_ELEMENTS(pThis->aOperands); + while (i-- > 0) + { + pThis->aOperands[i].enmLocationReg = BS3CG1OPLOC_INVALID; + pThis->aOperands[i].enmLocationMem = BS3CG1OPLOC_INVALID; + pThis->aOperands[i].idxFieldBase = BS3CG1DST_INVALID; + } + + pThis->iRmOp = RT_ELEMENTS(pThis->aOperands) - 1; + pThis->iRegOp = RT_ELEMENTS(pThis->aOperands) - 1; + pThis->fSameRingNotOkay = false; + pThis->cbOperand = 0; + pThis->pfnEncoder = NULL; + + switch (pThis->enmEncoding) + { + case BS3CG1ENC_MODRM_Eb_Gb: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Eb_Gb_OR_ViceVersa; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 1; + pThis->aOperands[1].cbOp = 1; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_AL; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_AL; + pThis->aOperands[0].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM_RW; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + break; + + case BS3CG1ENC_MODRM_Ev_Gv: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Gv_Ev__OR__MODRM_Ev_Gv; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->cbOperand = 2; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_OZ_RAX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_OZ_RAX; + pThis->aOperands[0].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM_RW; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + break; + + case BS3CG1ENC_MODRM_Ed_WO_Pd_WZ: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_PdZx_WO_Ed_WZ; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 4; + pThis->aOperands[1].cbOp = 4; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_EAX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_MM0; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[0].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + break; + + case BS3CG1ENC_MODRM_Eq_WO_Pq_WNZ: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Pq_WO_Eq_WNZ; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_RAX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_MM0; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[0].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + break; + + case BS3CG1ENC_MODRM_Ed_WO_Vd_WZ: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Vd_WO_Ed_WZ; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 4; + pThis->aOperands[1].cbOp = 4; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_EAX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[0].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + break; + + case BS3CG1ENC_MODRM_Eq_WO_Vq_WNZ: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Vq_WO_Eq_WNZ; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_RAX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[0].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + break; + + case BS3CG1ENC_MODRM_Gb_Eb: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Eb_Gb_OR_ViceVersa; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 1; + pThis->aOperands[1].cbOp = 1; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_AL; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_AL; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationMem = BS3CG1OPLOC_MEM; + break; + + case BS3CG1ENC_MODRM_Gv_Ev: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Gv_Ev__OR__MODRM_Ev_Gv; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->cbOperand = 2; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_OZ_RAX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_OZ_RAX; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationMem = BS3CG1OPLOC_MEM; + break; + + case BS3CG1ENC_MODRM_Gv_RO_Ma: /* bound instr */ + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Gv_RO_Ma; + pThis->iRmOp = 1; + pThis->iRegOp = 0; + pThis->cbOperand = 2; + pThis->aOperands[0].cbOp = 2; + pThis->aOperands[1].cbOp = 4; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_MEM; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_OZ_RAX; + break; + + case BS3CG1ENC_MODRM_Wss_WO_Vss: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Vsomething_Wsomething_OR_ViceVersa; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 4; + pThis->aOperands[1].cbOp = 4; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0_DW0; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_DW0; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[0].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + break; + + case BS3CG1ENC_MODRM_Wsd_WO_Vsd: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Vsomething_Wsomething_OR_ViceVersa; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0_LO; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_LO; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[0].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + break; + + case BS3CG1ENC_MODRM_WqZxReg_WO_Vq: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Vsomething_Wsomething_OR_ViceVersa; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0_LO_ZX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_LO; + pThis->aOperands[0].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + break; + + case BS3CG1ENC_MODRM_Wps_WO_Vps: + case BS3CG1ENC_MODRM_Wpd_WO_Vpd: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Vsomething_Wsomething_OR_ViceVersa; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 16; + pThis->aOperands[1].cbOp = 16; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0; + pThis->aOperands[0].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + break; + + case BS3CG1ENC_MODRM_Vdq_WO_Mdq: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Msomething_Vsomething_OR_ViceVersa; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 16; + pThis->aOperands[1].cbOp = 16; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_MEM; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0; + break; + + case BS3CG1ENC_MODRM_Vdq_WO_Wdq: + case BS3CG1ENC_MODRM_Vpd_WO_Wpd: + case BS3CG1ENC_MODRM_Vps_WO_Wps: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Vsomething_Wsomething_OR_ViceVersa; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 16; + pThis->aOperands[1].cbOp = 16; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationMem = BS3CG1OPLOC_MEM; + break; + + case BS3CG1ENC_MODRM_Pq_WO_Qq: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Pq_WO_Qq; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_MM0; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_MM0; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationMem = BS3CG1OPLOC_MEM; + break; + + case BS3CG1ENC_MODRM_Pq_WO_Uq: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Pq_WO_Uq; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_MM0; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_LO; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; /* reg only */ + break; + + case BS3CG1ENC_MODRM_PdZx_WO_Ed_WZ: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_PdZx_WO_Ed_WZ; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 4; + pThis->aOperands[1].cbOp = 4; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_MM0_LO_ZX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_EAX; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationMem = BS3CG1OPLOC_MEM; + break; + + case BS3CG1ENC_MODRM_Pq_WO_Eq_WNZ: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Pq_WO_Eq_WNZ; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_MM0; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_RAX; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationMem = BS3CG1OPLOC_MEM; + break; + + case BS3CG1ENC_MODRM_VdZx_WO_Ed_WZ: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Vd_WO_Ed_WZ; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 4; + pThis->aOperands[1].cbOp = 4; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0_DW0_ZX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_EAX; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationMem = BS3CG1OPLOC_MEM; + break; + + case BS3CG1ENC_MODRM_VqZx_WO_Eq_WNZ: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Vq_WO_Eq_WNZ; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0_LO_ZX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_RAX; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationMem = BS3CG1OPLOC_MEM; + break; + + case BS3CG1ENC_MODRM_Vq_WO_UqHi: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Vsomething_Usomething_OR_ViceVersa; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0_LO; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_HI; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + break; + + case BS3CG1ENC_MODRM_VqHi_WO_Uq: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Vsomething_Usomething_OR_ViceVersa; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0_HI; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_LO; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + break; + + case BS3CG1ENC_MODRM_VqHi_WO_Mq: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Msomething_Vsomething_OR_ViceVersa; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0_HI; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_MEM; + break; + + case BS3CG1ENC_MODRM_Vq_WO_Mq: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Msomething_Vsomething_OR_ViceVersa; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0_LO; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_MEM; + break; + + case BS3CG1ENC_MODRM_VssZx_WO_Wss: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Vsomething_Wsomething_OR_ViceVersa; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 4; + pThis->aOperands[1].cbOp = 4; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationMem = BS3CG1OPLOC_MEM; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0_DW0_ZX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_LO; + break; + + case BS3CG1ENC_MODRM_VqZx_WO_Nq: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Vsomething_Nsomething; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0_LO_ZX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_MM0; + break; + + case BS3CG1ENC_MODRM_VsdZx_WO_Wsd: + case BS3CG1ENC_MODRM_VqZx_WO_Wq: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Vsomething_Wsomething_OR_ViceVersa; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationMem = BS3CG1OPLOC_MEM; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0_LO_ZX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_LO; + break; + + case BS3CG1ENC_MODRM_Mb_RO: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Msomething; + pThis->iRmOp = 0; + pThis->aOperands[0].cbOp = 1; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_MEM; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM; + break; + + case BS3CG1ENC_MODRM_Md_RO: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Msomething; + pThis->iRmOp = 0; + pThis->aOperands[0].cbOp = 4; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_MEM; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM; + break; + + case BS3CG1ENC_MODRM_Md_WO: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Msomething; + pThis->iRmOp = 0; + pThis->aOperands[0].cbOp = 4; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM_WO; + break; + + case BS3CG1ENC_MODRM_Mdq_WO_Vdq: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Msomething_Vsomething_OR_ViceVersa; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 16; + pThis->aOperands[1].cbOp = 16; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0; + break; + + case BS3CG1ENC_MODRM_Mq_WO_Pq: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Msomething_Psomething; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_MM0; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + break; + + case BS3CG1ENC_MODRM_Mq_WO_Vq: + case BS3CG1ENC_MODRM_Mq_WO_VqHi: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Msomething_Vsomething_OR_ViceVersa; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].idxFieldBase = pThis->enmEncoding == BS3CG1ENC_MODRM_Mq_WO_Vq + ? BS3CG1DST_XMM0_LO : BS3CG1DST_XMM0_HI; + break; + + case BS3CG1ENC_MODRM_Mps_WO_Vps: + case BS3CG1ENC_MODRM_Mpd_WO_Vpd: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_Msomething_Vsomething_OR_ViceVersa; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 16; + pThis->aOperands[1].cbOp = 16; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0; + break; + + case BS3CG1ENC_FIXED: + pThis->pfnEncoder = Bs3Cg1EncodeNext_FIXED; + break; + + case BS3CG1ENC_FIXED_AL_Ib: + pThis->pfnEncoder = Bs3Cg1EncodeNext_FIXED_AL_Ib; + pThis->aOperands[0].cbOp = 1; + pThis->aOperands[1].cbOp = 1; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_IMM; + pThis->aOperands[0].idxField = BS3CG1DST_AL; + pThis->aOperands[1].idxField = BS3CG1DST_INVALID; + break; + + case BS3CG1ENC_FIXED_rAX_Iz: + pThis->pfnEncoder = Bs3Cg1EncodeNext_FIXED_rAX_Iz; + pThis->aOperands[0].cbOp = 2; + pThis->aOperands[1].cbOp = 2; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_IMM; + pThis->aOperands[0].idxField = BS3CG1DST_OZ_RAX; + pThis->aOperands[1].idxField = BS3CG1DST_INVALID; + break; + + /* Unused or invalid instructions mostly. */ + case BS3CG1ENC_MODRM_MOD_EQ_3: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_MOD_EQ_3; + break; + case BS3CG1ENC_MODRM_MOD_NE_3: + pThis->pfnEncoder = Bs3Cg1EncodeNext_MODRM_MOD_NE_3; + break; + +#ifdef BS3CG1_WITH_VEX + + case BS3CG1ENC_VEX_MODRM_Vd_WO_Ed_WZ: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_Vd_WO_Ed_WZ; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 4; + pThis->aOperands[1].cbOp = 4; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0_DW0_ZX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_EAX; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationMem = BS3CG1OPLOC_MEM; + break; + + case BS3CG1ENC_VEX_MODRM_Vq_WO_Eq_WNZ: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_Vq_WO_Eq_WNZ; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0_LO_ZX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_RAX; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationMem = BS3CG1OPLOC_MEM; + break; + + case BS3CG1ENC_VEX_MODRM_Vps_WO_Wps: + case BS3CG1ENC_VEX_MODRM_Vpd_WO_Wpd: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_WsomethingWO_Vsomething_Wip_OR_ViceVersa; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 16; + pThis->aOperands[1].cbOp = 16; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationMem = BS3CG1OPLOC_MEM; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0; + break; + + case BS3CG1ENC_VEX_MODRM_VssZx_WO_Md: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Msomething_Wip_Lig_OR_ViceVersa; + pThis->iRmOp = 1; + pThis->iRegOp = 0; + pThis->aOperands[0].cbOp = 4; + pThis->aOperands[1].cbOp = 4; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_MEM; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0_DW0; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_INVALID; + break; + + case BS3CG1ENC_VEX_MODRM_Vss_WO_HssHi_Uss: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Hsomething_Usomething_Lip_Wip_OR_ViceVersa; + pThis->iRegOp = 0; + pThis->iRmOp = 2; + pThis->aOperands[0].cbOp = 16; + pThis->aOperands[1].cbOp = 12; + pThis->aOperands[2].cbOp = 4; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[2].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_HI96; + pThis->aOperands[2].idxFieldBase = BS3CG1DST_XMM0_DW0; + break; + + case BS3CG1ENC_VEX_MODRM_VsdZx_WO_Mq: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Msomething_Wip_Lig_OR_ViceVersa; + pThis->iRmOp = 1; + pThis->iRegOp = 0; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_MEM; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0_LO; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_INVALID; + break; + + case BS3CG1ENC_VEX_MODRM_Vx_WO_Mx_L0: + BS3_ASSERT(!(pThis->fFlags & BS3CG1INSTR_F_VEX_L_ZERO)); + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Msomething_Wip_L0_OR_ViceVersa; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 16; + pThis->aOperands[1].cbOp = 16; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_MEM; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0; + break; + + case BS3CG1ENC_VEX_MODRM_Vx_WO_Mx_L1: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Msomething_Wip_L1_OR_ViceVersa; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 32; + pThis->aOperands[1].cbOp = 32; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_MEM; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_YMM0; + break; + + case BS3CG1ENC_VEX_MODRM_Vsd_WO_HsdHi_Usd: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Hsomething_Usomething_Lip_Wip_OR_ViceVersa; + pThis->iRegOp = 0; + pThis->iRmOp = 2; + pThis->aOperands[0].cbOp = 16; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[2].cbOp = 8; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[2].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_HI; + pThis->aOperands[2].idxFieldBase = BS3CG1DST_XMM0_LO; + break; + + case BS3CG1ENC_VEX_MODRM_Vq_WO_HqHi_UqHi: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_HdqCsomething_Usomething_Wip_OR_ViceVersa; + pThis->iRegOp = 0; + pThis->iRmOp = 2; + pThis->aOperands[0].cbOp = 16; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[2].cbOp = 8; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[2].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_HI; + pThis->aOperands[2].idxFieldBase = BS3CG1DST_XMM0_HI; + break; + + case BS3CG1ENC_VEX_MODRM_Vq_WO_HqHi_Mq: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Hsomething_Msomething_Wip_OR_ViceVersa; + pThis->iRegOp = 0; + pThis->iRmOp = 2; + pThis->aOperands[0].cbOp = 16; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[2].cbOp = 8; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[2].enmLocation = BS3CG1OPLOC_MEM; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_HI; + pThis->aOperands[2].idxFieldBase = BS3CG1DST_INVALID; + break; + + case BS3CG1ENC_VEX_MODRM_Vq_WO_Wq: + BS3_ASSERT(pThis->fFlags & BS3CG1INSTR_F_VEX_L_ZERO); + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_WsomethingWO_Vsomething_Wip_Lmbz_OR_ViceVersa; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationMem = BS3CG1OPLOC_MEM; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0_LO; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_LO; + break; + + case BS3CG1ENC_VEX_MODRM_Vx_WO_Wx: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_WsomethingWO_Vsomething_Wip_OR_ViceVersa; + pThis->iRegOp = 0; + pThis->iRmOp = 1; + pThis->aOperands[0].cbOp = 16; + pThis->aOperands[1].cbOp = 16; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[1].enmLocationMem = BS3CG1OPLOC_MEM; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0; + break; + + case BS3CG1ENC_VEX_MODRM_Ed_WO_Vd_WZ: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_Vd_WO_Ed_WZ; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 4; + pThis->aOperands[1].cbOp = 4; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_EAX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_DW0_ZX; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[0].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + break; + + case BS3CG1ENC_VEX_MODRM_Eq_WO_Vq_WNZ: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_Vq_WO_Eq_WNZ; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_RAX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_LO_ZX; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[0].enmLocationReg = BS3CG1OPLOC_CTX; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + break; + + case BS3CG1ENC_VEX_MODRM_Md_WO: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_Md_WO; + pThis->iRmOp = 0; + pThis->aOperands[0].cbOp = 4; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM_WO; + break; + + case BS3CG1ENC_VEX_MODRM_Md_WO_Vss: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Msomething_Wip_Lig_OR_ViceVersa; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 4; + pThis->aOperands[1].cbOp = 4; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_INVALID; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_DW0; + break; + + case BS3CG1ENC_VEX_MODRM_Mq_WO_Vq: + BS3_ASSERT(pThis->fFlags & (BS3CG1INSTR_F_VEX_L_ZERO | BS3CG1INSTR_F_VEX_L_IGNORED)); + pThis->pfnEncoder = pThis->fFlags & BS3CG1INSTR_F_VEX_L_ZERO + ? Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Msomething_Wip_Lmbz_OR_ViceVersa + : Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Msomething_Wip_Lig_OR_ViceVersa; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_LO; + break; + + case BS3CG1ENC_VEX_MODRM_Mq_WO_Vsd: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Msomething_Wip_Lig_OR_ViceVersa; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_INVALID; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_LO; + break; + + case BS3CG1ENC_VEX_MODRM_Mps_WO_Vps: + case BS3CG1ENC_VEX_MODRM_Mpd_WO_Vpd: + case BS3CG1ENC_VEX_MODRM_Mx_WO_Vx: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Msomething_Wip_OR_ViceVersa; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 16; + pThis->aOperands[1].cbOp = 16; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0; + break; + + case BS3CG1ENC_VEX_MODRM_Uss_WO_HssHi_Vss: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Hsomething_Usomething_Lip_Wip_OR_ViceVersa; + pThis->iRegOp = 2; + pThis->iRmOp = 0; + pThis->aOperands[0].cbOp = 16; + pThis->aOperands[1].cbOp = 96; + pThis->aOperands[2].cbOp = 4; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[2].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_HI96; + pThis->aOperands[2].idxFieldBase = BS3CG1DST_XMM0_DW0; + break; + + case BS3CG1ENC_VEX_MODRM_Usd_WO_HsdHi_Vsd: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_VsomethingWO_Hsomething_Usomething_Lip_Wip_OR_ViceVersa; + pThis->iRegOp = 2; + pThis->iRmOp = 0; + pThis->aOperands[0].cbOp = 16; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[2].cbOp = 8; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[2].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_HI; + pThis->aOperands[2].idxFieldBase = BS3CG1DST_XMM0_LO; + break; + + case BS3CG1ENC_VEX_MODRM_Wps_WO_Vps: + case BS3CG1ENC_VEX_MODRM_Wpd_WO_Vpd: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_WsomethingWO_Vsomething_Wip_OR_ViceVersa; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 16; + pThis->aOperands[1].cbOp = 16; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[0].enmLocationReg = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0; + break; + + case BS3CG1ENC_VEX_MODRM_Wq_WO_Vq: + BS3_ASSERT(pThis->fFlags & BS3CG1INSTR_F_VEX_L_ZERO); + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_WsomethingWO_Vsomething_Wip_Lmbz_OR_ViceVersa; + pThis->iRegOp = 1; + pThis->iRmOp = 0; + pThis->aOperands[0].cbOp = 8; + pThis->aOperands[1].cbOp = 8; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[0].enmLocationReg = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0_LO; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0_LO; + break; + + case BS3CG1ENC_VEX_MODRM_Wx_WO_Vx: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_WsomethingWO_Vsomething_Wip_OR_ViceVersa; + pThis->iRmOp = 0; + pThis->iRegOp = 1; + pThis->aOperands[0].cbOp = 16; + pThis->aOperands[1].cbOp = 16; + pThis->aOperands[0].enmLocation = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[0].enmLocationReg = BS3CG1OPLOC_CTX_ZX_VLMAX; + pThis->aOperands[0].enmLocationMem = BS3CG1OPLOC_MEM_WO; + pThis->aOperands[1].enmLocation = BS3CG1OPLOC_CTX; + pThis->aOperands[0].idxFieldBase = BS3CG1DST_XMM0; + pThis->aOperands[1].idxFieldBase = BS3CG1DST_XMM0; + break; + + + /* Unused or invalid instructions mostly. */ + //case BS3CG1ENC_VEX_FIXED: + // pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_FIXED; + // break; + case BS3CG1ENC_VEX_MODRM_MOD_EQ_3: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_MOD_EQ_3; + break; + case BS3CG1ENC_VEX_MODRM_MOD_NE_3: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM_MOD_NE_3; + break; + case BS3CG1ENC_VEX_MODRM: + pThis->pfnEncoder = Bs3Cg1EncodeNext_VEX_MODRM; + break; + +#endif /* BS3CG1_WITH_VEX */ + + default: + Bs3TestFailedF("Invalid/unimplemented enmEncoding for instruction #%RU32 (%.*s): %d", + pThis->iInstr, pThis->cchMnemonic, pThis->pchMnemonic, pThis->enmEncoding); + return false; + } + return true; +} + + +/** + * Calculates the appropriate non-intel invalid instruction encoding. + * + * @returns the encoding to use instead. + * @param enmEncoding The intel invalid instruction encoding. + */ +static BS3CG1ENC Bs3Cg1CalcNoneIntelInvalidEncoding(BS3CG1ENC enmEncoding) +{ + switch (enmEncoding) + { + case BS3CG1ENC_MODRM_Gb_Eb: + case BS3CG1ENC_MODRM_Gv_RO_Ma: + case BS3CG1ENC_FIXED: + return BS3CG1ENC_FIXED; + default: + Bs3TestFailedF("Bs3Cg1CalcNoneIntelInvalidEncoding: Unsupported encoding: %d\n", enmEncoding); + return BS3CG1ENC_FIXED; + } +} + + +/** + * Sets cbOpDefault, cbOpOvrd66 and cbOpOvrdRexW. + * + * @param pThis The state. + * @param bMode The mode (only code part is used). + */ +static void Bs3Cg1SetOpSizes(PBS3CG1STATE pThis, uint8_t bMode) +{ + if (BS3_MODE_IS_16BIT_CODE(bMode)) + { + pThis->cbOpDefault = 2; + pThis->cbOpOvrd66 = 4; + pThis->cbOpOvrdRexW = 0; + } + else if (BS3_MODE_IS_32BIT_CODE(bMode)) + { + pThis->cbOpDefault = 4; + pThis->cbOpOvrd66 = 2; + pThis->cbOpOvrdRexW = 0; + } + else + { + pThis->cbOpDefault = 4; + pThis->cbOpOvrd66 = 2; + pThis->cbOpOvrdRexW = 8; + } +} + + +/** + * Sets up SSE and maybe AVX. + * + * @returns true (if successful, false if not and the SSE instructions ends up + * being invalid). + * @param pThis The state. + */ +static bool BS3_NEAR_CODE Bs3Cg3SetupSseAndAvx(PBS3CG1STATE pThis) +{ + if (!pThis->fWorkExtCtx) + { + unsigned i; + uint32_t cr0 = ASMGetCR0(); + uint32_t cr4 = ASMGetCR4(); + + cr0 &= ~(X86_CR0_TS | X86_CR0_MP | X86_CR0_EM); + cr0 |= X86_CR0_NE; + ASMSetCR0(cr0); + if (pThis->pExtCtx->enmMethod == BS3EXTCTXMETHOD_XSAVE) + { + cr4 |= X86_CR4_OSFXSR | X86_CR4_OSXMMEEXCPT | X86_CR4_OSXSAVE; + ASMSetCR4(cr4); + ASMSetXcr0(pThis->pExtCtx->fXcr0Nominal); + } + else + { + cr4 |= X86_CR4_OSFXSR | X86_CR4_OSXMMEEXCPT; + ASMSetCR4(cr4); + } + + for (i = 0; i < RT_ELEMENTS(pThis->aInitialCtxs); i++) + { + pThis->aInitialCtxs[i].cr0.u32 = cr0; + pThis->aInitialCtxs[i].cr4.u32 = cr4; + } + pThis->fWorkExtCtx = true; + } + + return true; +} + + +/** + * Next CPU configuration to test the current instruction in. + * + * This is for testing FPU, SSE and AVX instructions with the various lazy state + * load and enable bits in different configurations to ensure we're getting the + * right response. + * + * This also cleans up the CPU and test driver state. + * + * @returns true if we're to do another round, false if we're done. + * @param pThis The state. + * @param iCpuSetup The current CPU setup number. + * @param pfInvalidInstr Where to indicate whether the setup causes an + * invalid instruction or not. This is also used as + * input to avoid unnecessary CPUID work. + */ +static bool BS3_NEAR_CODE Bs3Cg1CpuSetupNext(PBS3CG1STATE pThis, unsigned iCpuSetup, bool BS3_FAR *pfInvalidInstr) +{ + if ( (pThis->fFlags & BS3CG1INSTR_F_INVALID_64BIT) + && BS3CG1_IS_64BIT_TARGET(pThis)) + return false; + + switch (pThis->enmCpuTest) + { + case BS3CG1CPU_ANY: + case BS3CG1CPU_GE_80186: + case BS3CG1CPU_GE_80286: + case BS3CG1CPU_GE_80386: + case BS3CG1CPU_GE_80486: + case BS3CG1CPU_GE_Pentium: + case BS3CG1CPU_CLFSH: + case BS3CG1CPU_CLFLUSHOPT: + return false; + + case BS3CG1CPU_MMX: + return false; + + case BS3CG1CPU_SSE: + case BS3CG1CPU_SSE2: + case BS3CG1CPU_SSE3: + case BS3CG1CPU_SSE4_1: + case BS3CG1CPU_AVX: + case BS3CG1CPU_AVX2: + if (iCpuSetup > 0 || *pfInvalidInstr) + { + /** @todo do more configs here. */ + pThis->fWorkExtCtx = false; + ASMSetCR0(ASMGetCR0() | X86_CR0_EM | X86_CR0_MP); + ASMSetCR4(ASMGetCR4() & ~(X86_CR4_OSFXSR | X86_CR4_OSXMMEEXCPT | X86_CR4_OSXSAVE)); + return false; + } + return false; + + default: + Bs3TestFailedF("Invalid enmCpuTest value: %d", pThis->enmCpuTest); + return false; + } +} + + +/** + * Check if the instruction is supported by the CPU, possibly making state + * adjustments to enable support for it. + * + * @returns true if supported, false if not. + * @param pThis The state. + */ +static bool BS3_NEAR_CODE Bs3Cg1CpuSetupFirst(PBS3CG1STATE pThis) +{ + uint32_t fEax; + uint32_t fEbx; + uint32_t fEcx; + uint32_t fEdx; + + if ( (pThis->fFlags & BS3CG1INSTR_F_INVALID_64BIT) + && BS3CG1_IS_64BIT_TARGET(pThis)) + return false; + + switch (pThis->enmCpuTest) + { + case BS3CG1CPU_ANY: + return true; + + case BS3CG1CPU_GE_80186: + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80186) + return true; + return false; + + case BS3CG1CPU_GE_80286: + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80286) + return true; + return false; + + case BS3CG1CPU_GE_80386: + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80386) + return true; + return false; + + case BS3CG1CPU_GE_80486: + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80486) + return true; + return false; + + case BS3CG1CPU_GE_Pentium: + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_Pentium) + return true; + return false; + + case BS3CG1CPU_MMX: + if (g_uBs3CpuDetected & BS3CPU_F_CPUID) + { + ASMCpuIdExSlow(1, 0, 0, 0, NULL, NULL, NULL, &fEdx); + if (fEdx & X86_CPUID_FEATURE_EDX_MMX) + return Bs3Cg3SetupSseAndAvx(pThis); /** @todo only do FNSAVE/FXSAVE here? */ + } + return false; + + case BS3CG1CPU_SSE: + case BS3CG1CPU_SSE2: + case BS3CG1CPU_SSE3: + case BS3CG1CPU_SSE4_1: + case BS3CG1CPU_AVX: + if (g_uBs3CpuDetected & BS3CPU_F_CPUID) + { + ASMCpuIdExSlow(1, 0, 0, 0, NULL, NULL, &fEcx, &fEdx); + switch (pThis->enmCpuTest) + { + case BS3CG1CPU_SSE: + if (fEdx & X86_CPUID_FEATURE_EDX_SSE) + return Bs3Cg3SetupSseAndAvx(pThis); + return false; + case BS3CG1CPU_SSE2: + if (fEdx & X86_CPUID_FEATURE_EDX_SSE2) + return Bs3Cg3SetupSseAndAvx(pThis); + return false; + case BS3CG1CPU_SSE3: + if (fEcx & X86_CPUID_FEATURE_ECX_SSE3) + return Bs3Cg3SetupSseAndAvx(pThis); + return false; + case BS3CG1CPU_SSE4_1: + if (fEcx & X86_CPUID_FEATURE_ECX_SSE4_1) + return Bs3Cg3SetupSseAndAvx(pThis); + return false; + case BS3CG1CPU_AVX: + if (fEcx & X86_CPUID_FEATURE_ECX_AVX) + return Bs3Cg3SetupSseAndAvx(pThis) && !BS3_MODE_IS_RM_OR_V86(pThis->bMode); + return false; + default: BS3_ASSERT(0); /* impossible */ + } + } + return false; + + case BS3CG1CPU_AVX2: + if (g_uBs3CpuDetected & BS3CPU_F_CPUID) + { + ASMCpuIdExSlow(7, 0, 0/*leaf*/, 0, &fEax, &fEbx, &fEcx, &fEdx); + switch (pThis->enmCpuTest) + { + case BS3CG1CPU_AVX2: + if (fEbx & X86_CPUID_STEXT_FEATURE_EBX_AVX2) + return Bs3Cg3SetupSseAndAvx(pThis) && !BS3_MODE_IS_RM_OR_V86(pThis->bMode); + return false; + default: BS3_ASSERT(0); return false; /* impossible */ + } + } + return false; + + case BS3CG1CPU_CLFSH: + if (g_uBs3CpuDetected & BS3CPU_F_CPUID) + { + ASMCpuIdExSlow(1, 0, 0, 0, NULL, NULL, NULL, &fEdx); + if (fEdx & X86_CPUID_FEATURE_EDX_CLFSH) + return true; + } + return false; + + case BS3CG1CPU_CLFLUSHOPT: + if (g_uBs3CpuDetected & BS3CPU_F_CPUID) + { + ASMCpuIdExSlow(7, 0, 0/*leaf*/, 0, NULL, &fEbx, NULL, NULL); + if (fEbx & X86_CPUID_STEXT_FEATURE_EBX_CLFLUSHOPT) + return true; + } + return false; + + default: + Bs3TestFailedF("Invalid enmCpuTest value: %d", pThis->enmCpuTest); + return false; + } +} + + + +/** + * Checks the preconditions for a test. + * + * @returns true if the test be executed, false if not. + * @param pThis The state. + * @param pHdr The test header. + */ +static bool BS3_NEAR_CODE Bs3Cg1RunSelector(PBS3CG1STATE pThis, PCBS3CG1TESTHDR pHdr) +{ + + uint8_t const BS3_FAR *pbCode = (uint8_t const BS3_FAR *)(pHdr + 1); + unsigned cbLeft = pHdr->cbSelector; + while (cbLeft-- > 0) + { + switch (*pbCode++) + { +#define CASE_PRED(a_Pred, a_Expr) \ + case ((a_Pred) << BS3CG1SEL_OP_KIND_MASK) | BS3CG1SEL_OP_IS_TRUE: \ + if (!(a_Expr)) return false; \ + break; \ + case ((a_Pred) << BS3CG1SEL_OP_KIND_MASK) | BS3CG1SEL_OP_IS_FALSE: \ + if (a_Expr) return false; \ + break + CASE_PRED(BS3CG1PRED_SIZE_O16, pThis->cbOperand == 2); + CASE_PRED(BS3CG1PRED_SIZE_O32, pThis->cbOperand == 4); + CASE_PRED(BS3CG1PRED_SIZE_O64, pThis->cbOperand == 8); + CASE_PRED(BS3CG1PRED_VEXL_0, pThis->uVexL == 0); + CASE_PRED(BS3CG1PRED_VEXL_1, pThis->uVexL == 1); + CASE_PRED(BS3CG1PRED_RING_0, pThis->uCpl == 0); + CASE_PRED(BS3CG1PRED_RING_1, pThis->uCpl == 1); + CASE_PRED(BS3CG1PRED_RING_2, pThis->uCpl == 2); + CASE_PRED(BS3CG1PRED_RING_3, pThis->uCpl == 3); + CASE_PRED(BS3CG1PRED_RING_0_THRU_2, pThis->uCpl <= 2); + CASE_PRED(BS3CG1PRED_RING_1_THRU_3, pThis->uCpl >= 1); + CASE_PRED(BS3CG1PRED_CODE_64BIT, BS3CG1_IS_64BIT_TARGET(pThis)); + CASE_PRED(BS3CG1PRED_CODE_32BIT, BS3_MODE_IS_32BIT_CODE(pThis->bMode)); + CASE_PRED(BS3CG1PRED_CODE_16BIT, BS3_MODE_IS_16BIT_CODE(pThis->bMode)); + CASE_PRED(BS3CG1PRED_MODE_REAL, BS3_MODE_IS_RM_SYS(pThis->bMode)); + CASE_PRED(BS3CG1PRED_MODE_PROT, BS3_MODE_IS_PM_SYS(pThis->bMode)); + CASE_PRED(BS3CG1PRED_MODE_LONG, BS3_MODE_IS_64BIT_SYS(pThis->bMode)); + CASE_PRED(BS3CG1PRED_MODE_SMM, false); + CASE_PRED(BS3CG1PRED_MODE_VMX, false); + CASE_PRED(BS3CG1PRED_MODE_SVM, false); + CASE_PRED(BS3CG1PRED_PAGING_ON, BS3_MODE_IS_PAGED(pThis->bMode)); + CASE_PRED(BS3CG1PRED_PAGING_OFF, !BS3_MODE_IS_PAGED(pThis->bMode)); + CASE_PRED(BS3CG1PRED_VENDOR_AMD, pThis->bCpuVendor == BS3CPUVENDOR_AMD); + CASE_PRED(BS3CG1PRED_VENDOR_INTEL, pThis->bCpuVendor == BS3CPUVENDOR_INTEL); + CASE_PRED(BS3CG1PRED_VENDOR_VIA, pThis->bCpuVendor == BS3CPUVENDOR_VIA); + CASE_PRED(BS3CG1PRED_VENDOR_SHANGHAI, pThis->bCpuVendor == BS3CPUVENDOR_SHANGHAI); + CASE_PRED(BS3CG1PRED_VENDOR_HYGON, pThis->bCpuVendor == BS3CPUVENDOR_HYGON); + +#undef CASE_PRED + default: + return Bs3TestFailedF("Invalid selector opcode %#x!", pbCode[-1]); + } + } + + return true; +} + + +#ifdef BS3CG1_DEBUG_CTX_MOD +/** + * Translates the operator into a string. + * + * @returns Read-only string pointer. + * @param bOpcode The context modifier program opcode. + */ +static const char BS3_FAR * BS3_NEAR_CODE Bs3Cg1CtxOpToString(uint8_t bOpcode) +{ + switch (bOpcode & BS3CG1_CTXOP_OPERATOR_MASK) + { + case BS3CG1_CTXOP_ASSIGN: return "="; + case BS3CG1_CTXOP_OR: return "|="; + case BS3CG1_CTXOP_AND: return "&="; + case BS3CG1_CTXOP_AND_INV: return "&~="; + default: return "?WTF?"; + } +} +#endif + + +/** + * Runs a context modifier program. + * + * @returns Success indicator (true/false). + * @param pThis The state. + * @param pCtx The context. + * @param pHdr The program header. + * @param off The program offset relative to the end of the header. + * @param cb The program size. + * @param pEflCtx The context to take undefined EFLAGS from. (This is NULL + * if we're processing a input context modifier program.) + * @param pbInstr Points to the first instruction byte. For storing + * immediate operands during input context modification. + * NULL for output contexts. + */ +static bool BS3_NEAR_CODE Bs3Cg1RunContextModifier(PBS3CG1STATE pThis, PBS3REGCTX pCtx, PCBS3CG1TESTHDR pHdr, + unsigned off, unsigned cb, + PCBS3REGCTX pEflCtx, uint8_t BS3_FAR *pbInstr) +{ + uint8_t const BS3_FAR *pbCode = (uint8_t const BS3_FAR *)(pHdr + 1) + off; + int cbLeft = cb; + while (cbLeft-- > 0) + { + /* + * Decode the instruction. + */ + uint8_t const bOpcode = *pbCode++; + unsigned cbValue; + unsigned cbDst; + BS3CG1DST idxField; + BS3PTRUNION PtrField; + uint8_t BS3_FAR *pbMemCopy = NULL; + bool fZxVlMax; + + /* Expand the destiation field (can be escaped). Set fZxVlMax. */ + switch (bOpcode & BS3CG1_CTXOP_DST_MASK) + { + case BS3CG1_CTXOP_OP1: + idxField = pThis->aOperands[0].idxField; + if (idxField == BS3CG1DST_INVALID) + idxField = BS3CG1DST_OP1; + fZxVlMax = pEflCtx != NULL && pThis->aOperands[0].enmLocation == BS3CG1OPLOC_CTX_ZX_VLMAX; + break; + + case BS3CG1_CTXOP_OP2: + idxField = pThis->aOperands[1].idxField; + if (idxField == BS3CG1DST_INVALID) + idxField = BS3CG1DST_OP2; + fZxVlMax = pEflCtx != NULL && pThis->aOperands[1].enmLocation == BS3CG1OPLOC_CTX_ZX_VLMAX; + break; + + case BS3CG1_CTXOP_EFL: + idxField = BS3CG1DST_EFL; + fZxVlMax = false; + break; + + case BS3CG1_CTXOP_DST_ESC: + if (cbLeft-- > 0) + { + idxField = (BS3CG1DST)*pbCode++; + if (idxField <= BS3CG1DST_OP4) + { + if (idxField > BS3CG1DST_INVALID) + { + unsigned idxOp = idxField - BS3CG1DST_OP1; + uint8_t idxField2 = pThis->aOperands[idxOp].idxField; + if (idxField2 != BS3CG1DST_INVALID) + idxField = idxField2; + fZxVlMax = pEflCtx != NULL && pThis->aOperands[idxOp].enmLocation == BS3CG1OPLOC_CTX_ZX_VLMAX; + break; + } + } + else if (idxField < BS3CG1DST_END) + { + fZxVlMax = false; + break; + } + return Bs3TestFailedF("Malformed context instruction: idxField=%d", idxField); + } + RT_FALL_THRU(); + default: + return Bs3TestFailed("Malformed context instruction: Destination"); + } + + /* Expand value size (can be escaped). */ + switch (bOpcode & BS3CG1_CTXOP_SIZE_MASK) + { + case BS3CG1_CTXOP_1_BYTE: cbValue = 1; break; + case BS3CG1_CTXOP_2_BYTES: cbValue = 2; break; + case BS3CG1_CTXOP_4_BYTES: cbValue = 4; break; + case BS3CG1_CTXOP_8_BYTES: cbValue = 8; break; + case BS3CG1_CTXOP_16_BYTES: cbValue = 16; break; + case BS3CG1_CTXOP_32_BYTES: cbValue = 32; break; + case BS3CG1_CTXOP_12_BYTES: cbValue = 12; break; + case BS3CG1_CTXOP_SIZE_ESC: + if (cbLeft-- > 0) + { + cbValue = *pbCode++; + if (cbValue) + break; + } + RT_FALL_THRU(); + default: + return Bs3TestFailed("Malformed context instruction: size"); + } + + /* Make sure there is enough instruction bytes for the value. */ + if (cbValue <= cbLeft) + { /* likely */ } + else + return Bs3TestFailedF("Malformed context instruction: %u bytes value, %u bytes left", cbValue, cbLeft); + + /* + * Do value processing specific to the target field size. + */ + cbDst = g_acbBs3Cg1DstFields[idxField]; + if (cbDst == BS3CG1DSTSIZE_OPERAND) + cbDst = pThis->aOperands[idxField - BS3CG1DST_OP1].cbOp; + else if (cbDst == BS3CG1DSTSIZE_OPERAND_SIZE_GRP) + cbDst = pThis->cbOperand; + if (cbDst <= 8) + { + unsigned const offField = g_aoffBs3Cg1DstFields[idxField]; + + /* + * Deal with fields up to 8-byte wide. + */ + + /* Get the value. */ + uint64_t uValue; + if ((bOpcode & BS3CG1_CTXOP_SIGN_EXT)) + switch (cbValue) + { + case 1: uValue = *(int8_t const BS3_FAR *)pbCode; break; + case 2: uValue = *(int16_t const BS3_FAR *)pbCode; break; + case 4: uValue = *(int32_t const BS3_FAR *)pbCode; break; + default: + if (cbValue >= 8) + { + uValue = *(uint64_t const BS3_FAR *)pbCode; + break; + } + return Bs3TestFailedF("Malformed context instruction: %u bytes value (%u dst)", cbValue, cbDst); + } + else + switch (cbValue) + { + case 1: uValue = *(uint8_t const BS3_FAR *)pbCode; break; + case 2: uValue = *(uint16_t const BS3_FAR *)pbCode; break; + case 4: uValue = *(uint32_t const BS3_FAR *)pbCode; break; + default: + if (cbValue >= 8) + { + uValue = *(uint64_t const BS3_FAR *)pbCode; + break; + } + return Bs3TestFailedF("Malformed context instruction: %u bytes value (%u dst)", cbValue, cbDst); + } + + /* Find the field. */ + if (offField < sizeof(BS3REGCTX)) + PtrField.pu8 = (uint8_t BS3_FAR *)pCtx + offField; + /* Non-register operands: */ + else if ((unsigned)(idxField - BS3CG1DST_OP1) < 4U) + { + unsigned const idxOp = idxField - BS3CG1DST_OP1; + + switch (pThis->aOperands[idxOp].enmLocation) + { + case BS3CG1OPLOC_IMM: + if (pbInstr) + PtrField.pu8 = &pbInstr[pThis->aOperands[idxOp].off]; + else + return Bs3TestFailedF("Immediate operand referenced in output context!"); + break; + + case BS3CG1OPLOC_MEM: + if (!pbInstr) + return Bs3TestFailedF("Read only operand specified in output!"); + PtrField.pu8 = &pThis->pbDataPg[X86_PAGE_SIZE - pThis->aOperands[idxOp].off]; + break; + + case BS3CG1OPLOC_MEM_RW: + case BS3CG1OPLOC_MEM_WO: + if (pbInstr) + { + PtrField.pu8 = &pThis->pbDataPg[X86_PAGE_SIZE - pThis->aOperands[idxOp].off]; + pbMemCopy = pThis->MemOp.ab; + } + else + PtrField.pu8 = pThis->MemOp.ab; + break; + + default: + if (pThis->enmEncoding != pThis->enmEncodingNonInvalid) + goto l_advance_to_next; + return Bs3TestFailedF("Internal error: cbDst=%u idxField=%d (%d) offField=%#x: enmLocation=%u off=%#x idxField=%u", + cbDst, idxField, idxOp, offField, pThis->aOperands[idxOp].enmLocation, + pThis->aOperands[idxOp].off, pThis->aOperands[idxOp].idxField); + } + } + /* Special field: Copying in undefined EFLAGS from the result context. */ + else if (idxField == BS3CG1DST_EFL_UNDEF) + { + if (!pEflCtx || (bOpcode & BS3CG1_CTXOP_OPERATOR_MASK) != BS3CG1_CTXOP_ASSIGN) + return Bs3TestFailed("Invalid BS3CG1DST_EFL_UNDEF usage"); + PtrField.pu32 = &pCtx->rflags.u32; + uValue = (*PtrField.pu32 & ~(uint32_t)uValue) | (pEflCtx->rflags.u32 & (uint32_t)uValue); + } + /* Special field: Expected value (in/result) exception. */ + else if (idxField == BS3CG1DST_VALUE_XCPT) + { + if (!pEflCtx || (bOpcode & BS3CG1_CTXOP_OPERATOR_MASK) != BS3CG1_CTXOP_ASSIGN || cbDst != 1) + return Bs3TestFailed("Invalid BS3CG1DST_VALUE_XCPT usage"); + PtrField.pu8 = &pThis->bValueXcpt; + } + /* FPU and FXSAVE format. */ + else if ( pThis->pExtCtx->enmMethod != BS3EXTCTXMETHOD_ANCIENT + && offField - sizeof(BS3REGCTX) < RT_UOFFSET_AFTER(BS3EXTCTX, Ctx.x87.aXMM[15])) + { + if (pThis->fWorkExtCtx) + PtrField.pb = (uint8_t *)pThis->pExtCtx + offField - sizeof(BS3REGCTX); + else if (!pThis->fCpuSetupFirstResult) + { + BS3CG1_DPRINTF(("dbg: Extended context disabled: skipping modification (<=8)\n")); + goto l_advance_to_next; + } + else + return Bs3TestFailedF("Extended context disabled: Field %d (%s) @ %#x LB %u\n", + idxField, g_aszBs3Cg1DstFields[idxField].sz, offField, cbDst); + } + /** @todo other FPU fields and FPU state formats. */ + else + return Bs3TestFailedF("Todo implement me: cbDst=%u idxField=%d %s offField=%#x (<= 8)", + cbDst, idxField, g_aszBs3Cg1DstFields[idxField].sz, offField); + +#ifdef BS3CG1_DEBUG_CTX_MOD + switch (cbDst) + { + case 1: + BS3CG1_DPRINTF(("dbg: modify %s: %#04RX8 (LB %u) %s %#RX64 (LB %u)\n", g_aszBs3Cg1DstFields[idxField].sz, + *PtrField.pu8, cbDst, Bs3Cg1CtxOpToString(bOpcode), uValue, cbValue)); + break; + case 2: + BS3CG1_DPRINTF(("dbg: modify %s: %#06RX16 (LB %u) %s %#RX64 (LB %u)\n", g_aszBs3Cg1DstFields[idxField].sz, + *PtrField.pu16, cbDst, Bs3Cg1CtxOpToString(bOpcode), uValue, cbValue)); + break; + case 4: + BS3CG1_DPRINTF(("dbg: modify %s: %#010RX32 (LB %u) %s %#RX64 (LB %u)\n", g_aszBs3Cg1DstFields[idxField].sz, + *PtrField.pu32, cbDst, Bs3Cg1CtxOpToString(bOpcode), uValue, cbValue)); + break; + default: + BS3CG1_DPRINTF(("dbg: modify %s: %#018RX64 (LB %u) %s %#RX64 (LB %u)\n", g_aszBs3Cg1DstFields[idxField].sz, + *PtrField.pu64, cbDst, Bs3Cg1CtxOpToString(bOpcode), uValue, cbValue)); + break; + } +#endif + + /* Modify the field. */ + switch (cbDst) + { + case 1: + switch (bOpcode & BS3CG1_CTXOP_OPERATOR_MASK) + { + case BS3CG1_CTXOP_ASSIGN: *PtrField.pu8 = (uint8_t)uValue; break; + case BS3CG1_CTXOP_OR: *PtrField.pu8 |= (uint8_t)uValue; break; + case BS3CG1_CTXOP_AND: *PtrField.pu8 &= (uint8_t)uValue; break; + case BS3CG1_CTXOP_AND_INV: *PtrField.pu8 &= ~(uint8_t)uValue; break; + } + break; + + case 2: + switch (bOpcode & BS3CG1_CTXOP_OPERATOR_MASK) + { + case BS3CG1_CTXOP_ASSIGN: *PtrField.pu16 = (uint16_t)uValue; break; + case BS3CG1_CTXOP_OR: *PtrField.pu16 |= (uint16_t)uValue; break; + case BS3CG1_CTXOP_AND: *PtrField.pu16 &= (uint16_t)uValue; break; + case BS3CG1_CTXOP_AND_INV: *PtrField.pu16 &= ~(uint16_t)uValue; break; + } + break; + + case 4: + if ( (unsigned)(idxField - BS3CG1DST_XMM0_DW0_ZX) <= (unsigned)(BS3CG1DST_XMM15_DW0_ZX - BS3CG1DST_XMM0_DW0_ZX) + || fZxVlMax) + { + PtrField.pu32[1] = 0; + PtrField.pu64[1] = 0; + } + else if (offField <= RT_UOFFSETOF(BS3REGCTX, r15) /* Clear the top dword. */) + PtrField.pu32[1] = 0; + else if ((unsigned)(idxField - BS3CG1DST_MM0_LO_ZX) <= (unsigned)(BS3CG1DST_MM7_LO_ZX - BS3CG1DST_MM0_LO_ZX)) + { + PtrField.pu32[1] = 0; + PtrField.pu32[2] = 0xffff; /* observed on skylake */ + } + switch (bOpcode & BS3CG1_CTXOP_OPERATOR_MASK) + { + case BS3CG1_CTXOP_ASSIGN: *PtrField.pu32 = (uint32_t)uValue; break; + case BS3CG1_CTXOP_OR: *PtrField.pu32 |= (uint32_t)uValue; break; + case BS3CG1_CTXOP_AND: *PtrField.pu32 &= (uint32_t)uValue; break; + case BS3CG1_CTXOP_AND_INV: *PtrField.pu32 &= ~(uint32_t)uValue; break; + } + break; + + case 8: + if ( (unsigned)(idxField - BS3CG1DST_XMM0_LO_ZX) <= (unsigned)(BS3CG1DST_XMM15_LO_ZX - BS3CG1DST_XMM0_LO_ZX) + || fZxVlMax) + PtrField.pu64[1] = 0; + else if ((unsigned)(idxField - BS3CG1DST_MM0) <= (unsigned)(BS3CG1DST_MM7 - BS3CG1DST_MM0)) + PtrField.pu32[2] = 0xffff; /* observed on skylake */ + + switch (bOpcode & BS3CG1_CTXOP_OPERATOR_MASK) + { + case BS3CG1_CTXOP_ASSIGN: *PtrField.pu64 = (uint64_t)uValue; break; + case BS3CG1_CTXOP_OR: *PtrField.pu64 |= (uint64_t)uValue; break; + case BS3CG1_CTXOP_AND: *PtrField.pu64 &= (uint64_t)uValue; break; + case BS3CG1_CTXOP_AND_INV: *PtrField.pu64 &= ~(uint64_t)uValue; break; + } + break; + + default: + return Bs3TestFailedF("Malformed context instruction: cbDst=%u, expected 1, 2, 4, or 8", cbDst); + } + +#ifdef BS3CG1_DEBUG_CTX_MOD + switch (cbDst) + { + case 1: BS3CG1_DPRINTF(("dbg: --> %s: %#04RX8\n", g_aszBs3Cg1DstFields[idxField].sz, *PtrField.pu8)); break; + case 2: BS3CG1_DPRINTF(("dbg: --> %s: %#06RX16\n", g_aszBs3Cg1DstFields[idxField].sz, *PtrField.pu16)); break; + case 4: BS3CG1_DPRINTF(("dbg: --> %s: %#010RX32\n", g_aszBs3Cg1DstFields[idxField].sz, *PtrField.pu32)); break; + default: BS3CG1_DPRINTF(("dbg: --> %s: %#018RX64\n", g_aszBs3Cg1DstFields[idxField].sz, *PtrField.pu64)); break; + } +#endif + if (fZxVlMax) + { + uintptr_t iReg = ((uintptr_t)PtrField.pu8 - (uintptr_t)&pThis->pExtCtx->Ctx.x87.aXMM[0]) + / sizeof(pThis->pExtCtx->Ctx.x87.aXMM[0]); + pThis->pExtCtx->Ctx.x.u.YmmHi.aYmmHi[iReg].au64[0] = 0; + pThis->pExtCtx->Ctx.x.u.YmmHi.aYmmHi[iReg].au64[1] = 0; +#ifdef BS3CG1_DEBUG_CTX_MOD + BS3CG1_DPRINTF(("dbg: --> cleared YMM%u_HI\n", iReg)); +#endif + } + } + /* + * Deal with larger field (FPU, SSE, AVX, ...). + */ + else if (pThis->fWorkExtCtx) + { + union + { + X86FPUREG FpuReg; + X86XMMREG XmmReg; + X86YMMREG YmmReg; + X86ZMMREG ZmmReg; + uint8_t ab[sizeof(X86ZMMREG)]; + uint32_t au32[sizeof(X86ZMMREG) / sizeof(uint32_t)]; + uint64_t au64[sizeof(X86ZMMREG) / sizeof(uint64_t)]; + } Value; + unsigned const offField = g_aoffBs3Cg1DstFields[idxField]; + unsigned iReg; + + /* Copy the value into the union, doing the zero padding / extending. */ + Bs3MemCpy(&Value, pbCode, cbValue); + if (cbValue < sizeof(Value)) + { + if ((bOpcode & BS3CG1_CTXOP_SIGN_EXT) && (Value.ab[cbValue - 1] & 0x80)) + Bs3MemSet(&Value.ab[cbValue], 0xff, sizeof(Value) - cbValue); + else + Bs3MemSet(&Value.ab[cbValue], 0x00, sizeof(Value) - cbValue); + } + + /* Optimized access to XMM and STx registers. */ + if ( pThis->pExtCtx->enmMethod != BS3EXTCTXMETHOD_ANCIENT + && offField - sizeof(BS3REGCTX) < RT_UOFFSET_AFTER(BS3EXTCTX, Ctx.x87.aXMM[15]) ) + PtrField.pb = (uint8_t *)pThis->pExtCtx + offField - sizeof(BS3REGCTX); + /* Non-register operands: */ + else if ((unsigned)(idxField - BS3CG1DST_OP1) < 4U) + { + unsigned const idxOp = idxField - BS3CG1DST_OP1; + switch (pThis->aOperands[idxOp].enmLocation) + { + case BS3CG1OPLOC_MEM: + if (!pbInstr) + return Bs3TestFailedF("Read only operand specified in output!"); + PtrField.pu8 = &pThis->pbDataPg[X86_PAGE_SIZE - pThis->aOperands[idxOp].off]; + break; + + case BS3CG1OPLOC_MEM_RW: + case BS3CG1OPLOC_MEM_WO: + if (pbInstr) + { + PtrField.pu8 = &pThis->pbDataPg[X86_PAGE_SIZE - pThis->aOperands[idxOp].off]; + pbMemCopy = pThis->MemOp.ab; + } + else + PtrField.pu8 = pThis->MemOp.ab; + break; + + default: + return Bs3TestFailedF("Internal error: Field %d (%d) @ %#x LB %u: enmLocation=%u off=%#x idxField=%u", + idxField, idxOp, offField, cbDst, pThis->aOperands[idxOp].enmLocation, + pThis->aOperands[idxOp].off, pThis->aOperands[idxOp].idxField); + } + } + /* The YMM (AVX) registers have split storage in the state, so they need special handling. */ + else if ((iReg = idxField - BS3CG1DST_YMM0) < 16U) + { + /* The first 128-bits in XMM land. */ + PtrField.pu64 = &pThis->pExtCtx->Ctx.x87.aXMM[iReg].au64[0]; + switch (bOpcode & BS3CG1_CTXOP_OPERATOR_MASK) + { + case BS3CG1_CTXOP_ASSIGN: + PtrField.pu64[0] = Value.au64[0]; + PtrField.pu64[1] = Value.au64[1]; + break; + case BS3CG1_CTXOP_OR: + PtrField.pu64[0] |= Value.au64[0]; + PtrField.pu64[1] |= Value.au64[1]; + break; + case BS3CG1_CTXOP_AND: + PtrField.pu64[0] &= Value.au64[0]; + PtrField.pu64[1] &= Value.au64[1]; + break; + case BS3CG1_CTXOP_AND_INV: + PtrField.pu64[0] &= ~Value.au64[0]; + PtrField.pu64[1] &= ~Value.au64[1]; + break; + } + + /* The second 128-bit in YMM_HI land. */ + PtrField.pu64 = &pThis->pExtCtx->Ctx.x.u.YmmHi.aYmmHi[iReg].au64[0]; + switch (bOpcode & BS3CG1_CTXOP_OPERATOR_MASK) + { + case BS3CG1_CTXOP_ASSIGN: + PtrField.pu64[0] = Value.au64[2]; + PtrField.pu64[1] = Value.au64[3]; + break; + case BS3CG1_CTXOP_OR: + PtrField.pu64[0] |= Value.au64[2]; + PtrField.pu64[1] |= Value.au64[3]; + break; + case BS3CG1_CTXOP_AND: + PtrField.pu64[0] &= Value.au64[2]; + PtrField.pu64[1] &= Value.au64[3]; + break; + case BS3CG1_CTXOP_AND_INV: + PtrField.pu64[0] &= ~Value.au64[2]; + PtrField.pu64[1] &= ~Value.au64[3]; + break; + } + PtrField.pb = NULL; + } + /* AVX512 needs handling like above, but more complicated. */ + else + return Bs3TestFailedF("TODO: implement me: cbDst=%d idxField=%d (AVX and other weird state)", cbDst, idxField); + + if (PtrField.pb) + { + /* Modify the field / memory. */ + unsigned i; + if (cbDst & 3) + return Bs3TestFailedF("Malformed context instruction: cbDst=%u, multiple of 4", cbDst); + +#ifdef BS3CG1_DEBUG_CTX_MOD + BS3CG1_DPRINTF(("dbg: modify %s: %.*Rhxs (LB %u) %s %.*Rhxs (LB %u)\n", g_aszBs3Cg1DstFields[idxField].sz, + cbDst, PtrField.pb, cbDst, Bs3Cg1CtxOpToString(bOpcode), cbValue, Value.ab, cbValue)); +#endif + + i = cbDst / 4; + while (i-- > 0) + { + switch (bOpcode & BS3CG1_CTXOP_OPERATOR_MASK) + { + case BS3CG1_CTXOP_ASSIGN: PtrField.pu32[i] = Value.au32[i]; break; + case BS3CG1_CTXOP_OR: PtrField.pu32[i] |= Value.au32[i]; break; + case BS3CG1_CTXOP_AND: PtrField.pu32[i] &= Value.au32[i]; break; + case BS3CG1_CTXOP_AND_INV: PtrField.pu32[i] &= ~Value.au32[i]; break; + } + } + +#ifdef BS3CG1_DEBUG_CTX_MOD + BS3CG1_DPRINTF(("dbg: --> %s: %.*Rhxs\n", g_aszBs3Cg1DstFields[idxField].sz, cbDst, PtrField.pb)); +#endif + + if (fZxVlMax) + { + uintptr_t iReg = ((uintptr_t)PtrField.pu8 - (uintptr_t)&pThis->pExtCtx->Ctx.x87.aXMM[0]) + / sizeof(pThis->pExtCtx->Ctx.x87.aXMM[0]); + if (cbDst < 16) + { + for (i = cbDst / 4; i < 4; i++) + PtrField.pu32[i++] = 0; +#ifdef BS3CG1_DEBUG_CTX_MOD + BS3CG1_DPRINTF(("dbg: --> cleared high %u bytes of XMM%u\n", 16 - cbDst, iReg)); +#endif + } + pThis->pExtCtx->Ctx.x.u.YmmHi.aYmmHi[iReg].au64[0] = 0; + pThis->pExtCtx->Ctx.x.u.YmmHi.aYmmHi[iReg].au64[1] = 0; +#ifdef BS3CG1_DEBUG_CTX_MOD + BS3CG1_DPRINTF(("dbg: --> cleared YMM%u_HI\n", iReg)); +#endif + } + } + + /* + * Hack! Update pThis->MemOp when setting up the inputs so we can + * correctly validate value and alignment exceptions. + */ + if (pbMemCopy && PtrField.pv) + Bs3MemCpy(pbMemCopy, PtrField.pv, cbDst); + } + /* !pThis->fWorkExtCtx: */ + else if (pThis->fCpuSetupFirstResult) + return Bs3TestFailedF("Extended context disabled: Field %d (%s) @ %#x LB %u\n", + idxField, g_aszBs3Cg1DstFields[idxField].sz, g_aoffBs3Cg1DstFields[idxField], cbDst); + else + BS3CG1_DPRINTF(("dbg: Extended context disabled: skipping modification [> 8]\n")); + + /* + * Advance to the next instruction. + */ +l_advance_to_next: + pbCode += cbValue; + cbLeft -= cbValue; + } + + return true; +} + + +/** + * Checks the result of a run. + * + * @returns true if successful, false if not. + * @param pThis The state. + * @param bTestXcptExpected The exception causing the test code to stop + * executing. + * @param fInvalidEncodingPgFault Set if we've cut the instruction a byte + * short and is expecting a \#PF on the page + * boundrary rather than a \#UD. Only set if + * fInvalidEncoding is also set. + * @param iEncoding For error reporting. + */ +static bool BS3_NEAR_CODE Bs3Cg1CheckResult(PBS3CG1STATE pThis, uint8_t bTestXcptExpected, + bool fInvalidEncodingPgFault, unsigned iEncoding) +{ + unsigned iOperand; + + /* + * Check the exception state first. + */ + uint8_t bExpectedXcpt; + uint8_t cbAdjustPc; + if (!pThis->fInvalidEncoding) + { + bExpectedXcpt = pThis->bAlignmentXcpt; + if (bExpectedXcpt == UINT8_MAX) + bExpectedXcpt = pThis->bValueXcpt; + if (bExpectedXcpt == UINT8_MAX) + { + cbAdjustPc = pThis->cbCurInstr; + bExpectedXcpt = bTestXcptExpected; + if (bTestXcptExpected == X86_XCPT_PF) + pThis->Ctx.cr2.u = pThis->uCodePgFlat + X86_PAGE_SIZE; + } + else + cbAdjustPc = 0; + } + else + { + cbAdjustPc = 0; + if (!fInvalidEncodingPgFault) + bExpectedXcpt = X86_XCPT_UD; + else + { + bExpectedXcpt = X86_XCPT_PF; + pThis->Ctx.cr2.u = pThis->uCodePgFlat + X86_PAGE_SIZE; + } + } + if (RT_LIKELY( pThis->TrapFrame.bXcpt == bExpectedXcpt + && pThis->TrapFrame.Ctx.rip.u == pThis->Ctx.rip.u + cbAdjustPc)) + { + /* + * Check the register content. + */ + bool fOkay = Bs3TestCheckRegCtxEx(&pThis->TrapFrame.Ctx, &pThis->Ctx, + cbAdjustPc, 0 /*cbSpAdjust*/, 0 /*fExtraEfl*/, + pThis->pszMode, iEncoding); + + /* + * Check memory output operands. + */ + if (!pThis->fInvalidEncoding) + { + iOperand = pThis->cOperands; + while (iOperand-- > 0) + if ( pThis->aOperands[iOperand].enmLocation == BS3CG1OPLOC_MEM_RW + || pThis->aOperands[iOperand].enmLocation == BS3CG1OPLOC_MEM_WO) + { + if (pThis->aOperands[iOperand].off) + { + BS3PTRUNION PtrUnion; + PtrUnion.pb = &pThis->pbDataPg[X86_PAGE_SIZE - pThis->aOperands[iOperand].off]; + switch (pThis->aOperands[iOperand].cbOp) + { + case 1: + if (*PtrUnion.pu8 == pThis->MemOp.ab[0]) + continue; + Bs3TestFailedF("op%u: Wrote %#04RX8, expected %#04RX8", + iOperand, *PtrUnion.pu8, pThis->MemOp.ab[0]); + break; + case 2: + if (*PtrUnion.pu16 == pThis->MemOp.au16[0]) + continue; + Bs3TestFailedF("op%u: Wrote %#06RX16, expected %#06RX16", + iOperand, *PtrUnion.pu16, pThis->MemOp.au16[0]); + break; + case 4: + if (*PtrUnion.pu32 == pThis->MemOp.au32[0]) + continue; + Bs3TestFailedF("op%u: Wrote %#010RX32, expected %#010RX32", + iOperand, *PtrUnion.pu32, pThis->MemOp.au32[0]); + break; + case 8: + if (*PtrUnion.pu64 == pThis->MemOp.au64[0]) + continue; + Bs3TestFailedF("op%u: Wrote %#018RX64, expected %#018RX64", + iOperand, *PtrUnion.pu64, pThis->MemOp.au64[0]); + break; + default: + if (Bs3MemCmp(PtrUnion.pb, pThis->MemOp.ab, pThis->aOperands[iOperand].cbOp) == 0) + continue; + Bs3TestFailedF("op%u: Wrote %.*Rhxs, expected %.*Rhxs", + iOperand, + pThis->aOperands[iOperand].cbOp, PtrUnion.pb, + pThis->aOperands[iOperand].cbOp, pThis->MemOp.ab); + break; + } + } + else + Bs3TestFailedF("op%u: off is zero\n", iOperand); + fOkay = false; + } + } + + /* + * Check extended context if enabled. + */ + if (pThis->fWorkExtCtx) + { + PBS3EXTCTX pExpect = pThis->pExtCtx; + PBS3EXTCTX pResult = pThis->pResultExtCtx; + unsigned i; + if ( pExpect->enmMethod == BS3EXTCTXMETHOD_XSAVE + || pExpect->enmMethod == BS3EXTCTXMETHOD_FXSAVE) + { + /* Compare the x87 state, ASSUMING XCR0 bit 1 is set. */ +#define CHECK_FIELD(a_Field, a_szFmt) \ + if (pResult->Ctx.a_Field != pExpect->Ctx.a_Field) fOkay = Bs3TestFailedF(a_szFmt, pResult->Ctx.a_Field, pExpect->Ctx.a_Field) + CHECK_FIELD(x87.FCW, "FCW: %#06x, expected %#06x"); + CHECK_FIELD(x87.FSW, "FSW: %#06x, expected %#06x"); + CHECK_FIELD(x87.FTW, "FTW: %#06x, expected %#06x"); + //CHECK_FIELD(x87.FOP, "FOP: %#06x, expected %#06x"); + //CHECK_FIELD(x87.FPUIP, "FPUIP: %#010RX32, expected %#010RX32"); + //CHECK_FIELD(x87.CS, "FPUCS: %#06x, expected %#06x"); + //CHECK_FIELD(x87.Rsrvd1, "Rsrvd1: %#06x, expected %#06x"); + //CHECK_FIELD(x87.DP, "FPUDP: %#010RX32, expected %#010RX32"); + //CHECK_FIELD(x87.DS, "FPUDS: %#06x, expected %#06x"); + //CHECK_FIELD(x87.Rsrvd2, "Rsrvd2: %#06x, expected %#06x"); + CHECK_FIELD(x87.MXCSR, "MXCSR: %#010RX32, expected %#010RX32"); +#undef CHECK_FIELD + for (i = 0; i < RT_ELEMENTS(pExpect->Ctx.x87.aRegs); i++) + if ( pResult->Ctx.x87.aRegs[i].au64[0] != pExpect->Ctx.x87.aRegs[i].au64[0] + || pResult->Ctx.x87.aRegs[i].au16[4] != pExpect->Ctx.x87.aRegs[i].au16[4]) + fOkay = Bs3TestFailedF("ST[%u]: %c m=%#RX64 e=%d, expected %c m=%#RX64 e=%d", i, + pResult->Ctx.x87.aRegs[i].r80Ex.s.fSign ? '-' : '+', + pResult->Ctx.x87.aRegs[i].r80Ex.s.uMantissa, + pResult->Ctx.x87.aRegs[i].r80Ex.s.uExponent, + pExpect->Ctx.x87.aRegs[i].r80Ex.s.fSign ? '-' : '+', + pExpect->Ctx.x87.aRegs[i].r80Ex.s.uMantissa, + pExpect->Ctx.x87.aRegs[i].r80Ex.s.uExponent); + for (i = 0; i < (ARCH_BITS == 64 ? 16 : 8); i++) + if ( pResult->Ctx.x87.aXMM[i].au64[0] != pExpect->Ctx.x87.aXMM[i].au64[0] + || pResult->Ctx.x87.aXMM[i].au64[1] != pExpect->Ctx.x87.aXMM[i].au64[1]) + fOkay = Bs3TestFailedF("XMM%u: %#010RX64'%016RX64, expected %#010RX64'%08RX64", i, + pResult->Ctx.x87.aXMM[i].au64[1], + pResult->Ctx.x87.aXMM[i].au64[0], + pExpect->Ctx.x87.aXMM[i].au64[1], + pExpect->Ctx.x87.aXMM[i].au64[0]); + if (pExpect->fXcr0Saved & XSAVE_C_YMM) + for (i = 0; i < (ARCH_BITS == 64 ? 16 : 8); i++) + if ( pResult->Ctx.x.u.YmmHi.aYmmHi[i].au64[0] != pExpect->Ctx.x.u.YmmHi.aYmmHi[i].au64[0] + || pResult->Ctx.x.u.YmmHi.aYmmHi[i].au64[1] != pExpect->Ctx.x.u.YmmHi.aYmmHi[i].au64[1]) + fOkay = Bs3TestFailedF("YMM%u_HI: %#010RX64'%016RX64, expected %#010RX64'%08RX64", i, + pResult->Ctx.x.u.YmmHi.aYmmHi[i].au64[1], + pResult->Ctx.x.u.YmmHi.aYmmHi[i].au64[0], + pExpect->Ctx.x.u.YmmHi.aYmmHi[i].au64[1], + pExpect->Ctx.x.u.YmmHi.aYmmHi[i].au64[0]); + } + else + fOkay = Bs3TestFailedF("Unsupported extended CPU context method: %d", pExpect->enmMethod); + } + + /* + * Done. + */ + if (fOkay) + return true; + + /* + * Report failure. + */ + Bs3TestFailedF("ins#%RU32/test#%u: encoding #%u: %.*Rhxs%s", + pThis->iInstr, pThis->iTest, iEncoding, pThis->cbCurInstr, pThis->abCurInstr, + fInvalidEncodingPgFault ? " (cut short)" : ""); + } + else + Bs3TestFailedF("ins#%RU32/test#%u: bXcpt=%#x expected %#x; rip=%RX64 expected %RX64; encoding#%u: %.*Rhxs%s", + pThis->iInstr, pThis->iTest, + pThis->TrapFrame.bXcpt, bExpectedXcpt, + pThis->TrapFrame.Ctx.rip.u, pThis->Ctx.rip.u + cbAdjustPc, + iEncoding, pThis->cbCurInstr, pThis->abCurInstr, fInvalidEncodingPgFault ? " (cut short)" : ""); + Bs3TestPrintf("cpl=%u cbOperands=%u\n", pThis->uCpl, pThis->cbOperand); + + /* + * Display memory operands. + */ + for (iOperand = 0; iOperand < pThis->cOperands; iOperand++) + { + BS3PTRUNION PtrUnion; + switch (pThis->aOperands[iOperand].enmLocation) + { + case BS3CG1OPLOC_CTX: + { + uint8_t idxField = pThis->aOperands[iOperand].idxField; + unsigned offField = g_aoffBs3Cg1DstFields[idxField]; + if (offField <= sizeof(BS3REGCTX)) + PtrUnion.pb = (uint8_t BS3_FAR *)&pThis->Ctx + offField; + else + { + Bs3TestPrintf("op%u: ctx%u: xxxx\n", iOperand, pThis->aOperands[iOperand].cbOp * 8); + break; + } + switch (pThis->aOperands[iOperand].cbOp) + { + case 1: Bs3TestPrintf("op%u: ctx08: %#04RX8\n", iOperand, *PtrUnion.pu8); break; + case 2: Bs3TestPrintf("op%u: ctx16: %#06RX16\n", iOperand, *PtrUnion.pu16); break; + case 4: Bs3TestPrintf("op%u: ctx32: %#010RX32\n", iOperand, *PtrUnion.pu32); break; + case 8: Bs3TestPrintf("op%u: ctx64: %#018RX64\n", iOperand, *PtrUnion.pu64); break; + default: + Bs3TestPrintf("op%u: ctx%u: %.*Rhxs\n", iOperand, pThis->aOperands[iOperand].cbOp * 8, + pThis->aOperands[iOperand].cbOp, PtrUnion.pb); + break; + } + break; + } + + case BS3CG1OPLOC_IMM: + PtrUnion.pb = &pThis->pbCodePg[pThis->aOperands[iOperand].off]; + switch (pThis->aOperands[iOperand].cbOp) + { + case 1: Bs3TestPrintf("op%u: imm08: %#04RX8\n", iOperand, *PtrUnion.pu8); break; + case 2: Bs3TestPrintf("op%u: imm16: %#06RX16\n", iOperand, *PtrUnion.pu16); break; + case 4: Bs3TestPrintf("op%u: imm32: %#010RX32\n", iOperand, *PtrUnion.pu32); break; + case 8: Bs3TestPrintf("op%u: imm64: %#018RX64\n", iOperand, *PtrUnion.pu64); break; + default: + Bs3TestPrintf("op%u: imm%u: %.*Rhxs\n", iOperand, pThis->aOperands[iOperand].cbOp * 8, + pThis->aOperands[iOperand].cbOp, PtrUnion.pb); + break; + } + break; + + case BS3CG1OPLOC_MEM: + case BS3CG1OPLOC_MEM_RW: + case BS3CG1OPLOC_MEM_WO: + if (pThis->aOperands[iOperand].off) + { + PtrUnion.pb = &pThis->pbDataPg[X86_PAGE_SIZE - pThis->aOperands[iOperand].off]; + switch (pThis->aOperands[iOperand].cbOp) + { + case 1: Bs3TestPrintf("op%u: result mem08: %#04RX8\n", iOperand, *PtrUnion.pu8); break; + case 2: Bs3TestPrintf("op%u: result mem16: %#06RX16\n", iOperand, *PtrUnion.pu16); break; + case 4: Bs3TestPrintf("op%u: result mem32: %#010RX32\n", iOperand, *PtrUnion.pu32); break; + case 8: Bs3TestPrintf("op%u: result mem64: %#018RX64\n", iOperand, *PtrUnion.pu64); break; + default: + Bs3TestPrintf("op%u: result mem%u: %.*Rhxs\n", iOperand, pThis->aOperands[iOperand].cbOp * 8, + pThis->aOperands[iOperand].cbOp, PtrUnion.pb); + break; + } + if ( pThis->aOperands[iOperand].enmLocation == BS3CG1OPLOC_MEM_WO + || pThis->aOperands[iOperand].enmLocation == BS3CG1OPLOC_MEM_RW) + { + PtrUnion.pb = pThis->MemOp.ab; + switch (pThis->aOperands[iOperand].cbOp) + { + case 1: Bs3TestPrintf("op%u: expect mem08: %#04RX8\n", iOperand, *PtrUnion.pu8); break; + case 2: Bs3TestPrintf("op%u: expect mem16: %#06RX16\n", iOperand, *PtrUnion.pu16); break; + case 4: Bs3TestPrintf("op%u: expect mem32: %#010RX32\n", iOperand, *PtrUnion.pu32); break; + case 8: Bs3TestPrintf("op%u: expect mem64: %#018RX64\n", iOperand, *PtrUnion.pu64); break; + default: + Bs3TestPrintf("op%u: expect mem%u: %.*Rhxs\n", iOperand, pThis->aOperands[iOperand].cbOp * 8, + pThis->aOperands[iOperand].cbOp, PtrUnion.pb); + break; + } + } + } + else + Bs3TestPrintf("op%u: mem%u: zero off value!!\n", iOperand, pThis->aOperands[iOperand].cbOp * 8); + break; + } + } + + /* + * Display contexts. + */ + Bs3TestPrintf("-- Expected context:\n"); + Bs3RegCtxPrint(&pThis->Ctx); + if (pThis->fWorkExtCtx) + Bs3TestPrintf("xcr0=%RX64\n", pThis->pExtCtx->fXcr0Saved); + Bs3TestPrintf("-- Actual context:\n"); + Bs3TrapPrintFrame(&pThis->TrapFrame); + if (pThis->fWorkExtCtx) + Bs3TestPrintf("xcr0=%RX64\n", pThis->pResultExtCtx->fXcr0Saved); + Bs3TestPrintf("\n"); +ASMHalt(); + return false; +} + + +/** + * Destroys the state, freeing all allocations and such. + * + * @param pThis The state. + */ +static void BS3_NEAR_CODE Bs3Cg1Destroy(PBS3CG1STATE pThis) +{ + if (BS3_MODE_IS_PAGED(pThis->bMode)) + { +#if ARCH_BITS != 16 + Bs3MemGuardedTestPageFree(pThis->pbCodePg); + Bs3MemGuardedTestPageFree(pThis->pbDataPg); +#endif + } + else + { + Bs3MemFree(pThis->pbCodePg, X86_PAGE_SIZE); + Bs3MemFree(pThis->pbDataPg, X86_PAGE_SIZE); + } + + if (pThis->pExtCtx) + Bs3MemFree(pThis->pExtCtx, pThis->pExtCtx->cb * 3); + + pThis->pbCodePg = NULL; + pThis->pbDataPg = NULL; + pThis->pExtCtx = NULL; + pThis->pResultExtCtx = NULL; + pThis->pInitialExtCtx = NULL; +} + + +/** + * Initializes the state. + * + * @returns Success indicator (true/false) + * @param pThis The state. + * @param bMode The mode being tested. + */ +bool BS3_NEAR_CODE BS3_CMN_NM(Bs3Cg1Init)(PBS3CG1STATE pThis, uint8_t bMode) +{ + BS3MEMKIND const enmMemKind = BS3_MODE_IS_RM_OR_V86(bMode) ? BS3MEMKIND_REAL + : !BS3_MODE_IS_64BIT_CODE(bMode) ? BS3MEMKIND_TILED : BS3MEMKIND_FLAT32; + unsigned iRing; + unsigned cb; + unsigned i; + uint64_t fFlags; + PBS3EXTCTX pExtCtx; + + Bs3MemSet(pThis, 0, sizeof(*pThis)); + + pThis->iFirstRing = BS3_MODE_IS_V86(bMode) ? 3 : 0; + pThis->iEndRing = BS3_MODE_IS_RM_SYS(bMode) ? 1 : 4; + pThis->bMode = bMode; + pThis->pszMode = Bs3GetModeName(bMode); + pThis->pszModeShort = Bs3GetModeNameShortLower(bMode); + pThis->bCpuVendor = Bs3GetCpuVendor(); + pThis->pchMnemonic = g_achBs3Cg1Mnemonics; + pThis->pabOperands = g_abBs3Cg1Operands; + pThis->pabOpcodes = g_abBs3Cg1Opcodes; + pThis->fAdvanceMnemonic = 1; + + /* Allocate extended context structures. */ + cb = Bs3ExtCtxGetSize(&fFlags); + pExtCtx = Bs3MemAlloc(BS3MEMKIND_TILED, cb * 3); + if (!pExtCtx) + return Bs3TestFailedF("Bs3MemAlloc(tiled,%#x)", cb * 3); + pThis->pExtCtx = pExtCtx; + pThis->pResultExtCtx = (PBS3EXTCTX)((uint8_t BS3_FAR *)pExtCtx + cb); + pThis->pInitialExtCtx = (PBS3EXTCTX)((uint8_t BS3_FAR *)pExtCtx + cb + cb); + + Bs3ExtCtxInit(pThis->pExtCtx, cb, fFlags); + Bs3ExtCtxInit(pThis->pResultExtCtx, cb, fFlags); + Bs3ExtCtxInit(pThis->pInitialExtCtx, cb, fFlags); + //Bs3TestPrintf("fCR0=%RX64 cbExtCtx=%#x method=%d\n", fFlags, cb, pExtCtx->enmMethod); + + /* Allocate guarded exectuable and data memory. */ + if (BS3_MODE_IS_PAGED(bMode)) + { +#if ARCH_BITS != 16 + pThis->pbCodePg = Bs3MemGuardedTestPageAlloc(enmMemKind); + pThis->pbDataPg = Bs3MemGuardedTestPageAlloc(enmMemKind); + if (!pThis->pbCodePg || !pThis->pbDataPg) + { + Bs3TestFailedF("Bs3MemGuardedTestPageAlloc(%d) failed", enmMemKind); + Bs3MemPrintInfo(); + Bs3Shutdown(); + return Bs3TestFailedF("Bs3MemGuardedTestPageAlloc(%d) failed", enmMemKind); + } + if ( BS3_MODE_IS_64BIT_CODE(bMode) + && (uintptr_t)pThis->pbDataPg >= _2G) + return Bs3TestFailedF("pbDataPg=%p is above 2GB and not simple to address from 64-bit code", pThis->pbDataPg); +#else + return Bs3TestFailed("WTF?! #1"); +#endif + } + else + { + pThis->pbCodePg = Bs3MemAlloc(enmMemKind, X86_PAGE_SIZE); + pThis->pbDataPg = Bs3MemAlloc(enmMemKind, X86_PAGE_SIZE); + if (!pThis->pbCodePg || !pThis->pbDataPg) + { + Bs3MemPrintInfo(); + return Bs3TestFailedF("Bs3MemAlloc(%d,Pg) failed", enmMemKind); + } + } + pThis->uCodePgFlat = Bs3SelPtrToFlat(pThis->pbCodePg); + pThis->uDataPgFlat = Bs3SelPtrToFlat(pThis->pbDataPg); +#if ARCH_BITS == 16 + pThis->CodePgFar.sel = BS3_FP_SEG(pThis->pbCodePg); + pThis->CodePgFar.off = BS3_FP_OFF(pThis->pbCodePg); + pThis->CodePgRip = BS3_FP_OFF(pThis->pbCodePg); + pThis->DataPgFar.sel = BS3_FP_SEG(pThis->pbDataPg); + pThis->DataPgFar.off = BS3_FP_OFF(pThis->pbDataPg); +#else + if (BS3_MODE_IS_RM_OR_V86(bMode)) + { + *(uint32_t *)&pThis->DataPgFar = Bs3SelFlatDataToRealMode(pThis->uDataPgFlat); + ASMCompilerBarrier(); + pThis->CodePgFar.off = 0; + pThis->CodePgFar.sel = pThis->uCodePgFlat >> 4; + pThis->CodePgRip = pThis->CodePgFar.off; + } + else if (BS3_MODE_IS_16BIT_CODE(bMode)) + { + *(uint32_t *)&pThis->DataPgFar = Bs3SelFlatDataToProtFar16(pThis->uDataPgFlat); + ASMCompilerBarrier(); + pThis->CodePgFar.sel = BS3_SEL_SPARE_00; + pThis->CodePgFar.off = 0; + pThis->CodePgRip = 0; + } + else if (BS3_MODE_IS_32BIT_CODE(bMode)) + { + *(uint32_t *)&pThis->DataPgFar = Bs3SelFlatDataToProtFar16(pThis->uDataPgFlat); + ASMCompilerBarrier(); + pThis->CodePgFar.sel = 0; + pThis->CodePgFar.off = 0; + pThis->CodePgRip = (uintptr_t)pThis->pbCodePg; + } + else + { + pThis->DataPgFar.off = 0; + pThis->DataPgFar.sel = 0; + pThis->CodePgFar.off = 0; + pThis->CodePgFar.sel = 0; + pThis->CodePgRip = (uintptr_t)pThis->pbCodePg; + } +#endif + BS3CG1_DPRINTF(("pbDataPg=%p %04x:%04x pbCodePg=%p %04x:%04x\n", + pThis->pbDataPg, pThis->DataPgFar.sel, pThis->DataPgFar.off, + pThis->pbCodePg, pThis->CodePgFar.sel, pThis->CodePgFar.off)); + + /* + * Create basic context for each target ring. + * + * In protected 16-bit code we need set up code selectors that can access + * pbCodePg. + * + * In long mode we make sure the high 32-bits of GPRs (sans RSP) have some + * bits set so we can check that the implicit clearing is tested. + */ + Bs3RegCtxSaveEx(&pThis->aInitialCtxs[pThis->iFirstRing], bMode, 1024 * 3); +#if ARCH_BITS == 64 + pThis->aInitialCtxs[pThis->iFirstRing].rax.u |= UINT64_C(0x0101010100000000); + pThis->aInitialCtxs[pThis->iFirstRing].rbx.u |= UINT64_C(0x0202020200000000); + pThis->aInitialCtxs[pThis->iFirstRing].rcx.u |= UINT64_C(0x0303030300000000); + pThis->aInitialCtxs[pThis->iFirstRing].rdx.u |= UINT64_C(0x0404040400000000); + pThis->aInitialCtxs[pThis->iFirstRing].rbp.u |= UINT64_C(0x0505050500000000); + pThis->aInitialCtxs[pThis->iFirstRing].rdi.u |= UINT64_C(0x0606060600000000); + pThis->aInitialCtxs[pThis->iFirstRing].rsi.u |= UINT64_C(0x0707070700000000); + pThis->aInitialCtxs[pThis->iFirstRing].r8.u |= UINT64_C(0x0808080800000000); + pThis->aInitialCtxs[pThis->iFirstRing].r9.u |= UINT64_C(0x0909090900000000); + pThis->aInitialCtxs[pThis->iFirstRing].r10.u |= UINT64_C(0x1010101000000000); + pThis->aInitialCtxs[pThis->iFirstRing].r11.u |= UINT64_C(0x1111111100000000); + pThis->aInitialCtxs[pThis->iFirstRing].r12.u |= UINT64_C(0x1212121200000000); + pThis->aInitialCtxs[pThis->iFirstRing].r13.u |= UINT64_C(0x1313131300000000); + pThis->aInitialCtxs[pThis->iFirstRing].r14.u |= UINT64_C(0x1414141400000000); + pThis->aInitialCtxs[pThis->iFirstRing].r15.u |= UINT64_C(0x1515151500000000); +#endif + + if (BS3_MODE_IS_RM_OR_V86(bMode)) + { + pThis->aInitialCtxs[pThis->iFirstRing].cs = pThis->CodePgFar.sel; + BS3_ASSERT(pThis->iFirstRing + 1 == pThis->iEndRing); + } + else if (BS3_MODE_IS_16BIT_CODE(bMode)) + { +#if ARCH_BITS == 16 + uintptr_t const uFlatCodePgSeg = Bs3SelPtrToFlat(BS3_FP_MAKE(BS3_FP_SEG(pThis->pbCodePg), 0)); +#else + uintptr_t const uFlatCodePgSeg = (uintptr_t)pThis->pbCodePg; +#endif + for (iRing = pThis->iFirstRing + 1; iRing < pThis->iEndRing; iRing++) + { + Bs3MemCpy(&pThis->aInitialCtxs[iRing], &pThis->aInitialCtxs[pThis->iFirstRing], sizeof(pThis->aInitialCtxs[iRing])); + Bs3RegCtxConvertToRingX(&pThis->aInitialCtxs[iRing], iRing); + } + for (iRing = pThis->iFirstRing; iRing < pThis->iEndRing; iRing++) + { + pThis->aInitialCtxs[iRing].cs = BS3_SEL_SPARE_00 + iRing * 8 + iRing; + Bs3SelSetup16BitCode(&Bs3GdteSpare00 + iRing, uFlatCodePgSeg, iRing); + } + } + else + { + Bs3RegCtxSetRipCsFromCurPtr(&pThis->aInitialCtxs[pThis->iFirstRing], (FPFNBS3FAR)pThis->pbCodePg); + for (iRing = pThis->iFirstRing + 1; iRing < pThis->iEndRing; iRing++) + { + Bs3MemCpy(&pThis->aInitialCtxs[iRing], &pThis->aInitialCtxs[pThis->iFirstRing], sizeof(pThis->aInitialCtxs[iRing])); + Bs3RegCtxConvertToRingX(&pThis->aInitialCtxs[iRing], iRing); + } + } + + /* + * Create an initial extended CPU context. + */ + pExtCtx = pThis->pInitialExtCtx; + if ( pExtCtx->enmMethod == BS3EXTCTXMETHOD_FXSAVE + || pExtCtx->enmMethod == BS3EXTCTXMETHOD_XSAVE) + { + pExtCtx->Ctx.x87.FCW = X86_FCW_MASK_ALL | X86_FCW_PC_64 | X86_FCW_RC_NEAREST; + pExtCtx->Ctx.x87.FSW = 0; + pExtCtx->Ctx.x87.MXCSR = X86_MXCSR_IM | X86_MXCSR_DM | X86_MXCSR_RC_NEAREST; + pExtCtx->Ctx.x87.MXCSR_MASK = 0; + for (i = 0; i < RT_ELEMENTS(pExtCtx->Ctx.x87.aRegs); i++) + { + pExtCtx->Ctx.x87.aRegs[i].au16[0] = i << 4; + pExtCtx->Ctx.x87.aRegs[i].au16[1] = i << 4; + pExtCtx->Ctx.x87.aRegs[i].au16[2] = i << 4; + pExtCtx->Ctx.x87.aRegs[i].au16[3] = i << 4; + } + for (i = 0; i < RT_ELEMENTS(pExtCtx->Ctx.x87.aXMM); i++) + { + pExtCtx->Ctx.x87.aXMM[i].au16[0] = i | UINT16_C(0x8f00); + pExtCtx->Ctx.x87.aXMM[i].au16[1] = i | UINT16_C(0x8e00); + pExtCtx->Ctx.x87.aXMM[i].au16[2] = i | UINT16_C(0x8d00); + pExtCtx->Ctx.x87.aXMM[i].au16[3] = i | UINT16_C(0x8c00); + pExtCtx->Ctx.x87.aXMM[i].au16[4] = i | UINT16_C(0x8b00); + pExtCtx->Ctx.x87.aXMM[i].au16[5] = i | UINT16_C(0x8a00); + pExtCtx->Ctx.x87.aXMM[i].au16[6] = i | UINT16_C(0x8900); + pExtCtx->Ctx.x87.aXMM[i].au16[7] = i | UINT16_C(0x8800); + } + if (pExtCtx->fXcr0Nominal & XSAVE_C_YMM) + for (i = 0; i < RT_ELEMENTS(pExtCtx->Ctx.x.u.YmmHi.aYmmHi); i++) + { + pExtCtx->Ctx.x.u.YmmHi.aYmmHi[i].au16[0] = (i << 8) | (i << 12) | 0xff; + pExtCtx->Ctx.x.u.YmmHi.aYmmHi[i].au16[1] = (i << 8) | (i << 12) | 0xfe; + pExtCtx->Ctx.x.u.YmmHi.aYmmHi[i].au16[2] = (i << 8) | (i << 12) | 0xfd; + pExtCtx->Ctx.x.u.YmmHi.aYmmHi[i].au16[3] = (i << 8) | (i << 12) | 0xfc; + pExtCtx->Ctx.x.u.YmmHi.aYmmHi[i].au16[4] = (i << 8) | (i << 12) | 0xfb; + pExtCtx->Ctx.x.u.YmmHi.aYmmHi[i].au16[5] = (i << 8) | (i << 12) | 0xfa; + pExtCtx->Ctx.x.u.YmmHi.aYmmHi[i].au16[6] = (i << 8) | (i << 12) | 0xf9; + pExtCtx->Ctx.x.u.YmmHi.aYmmHi[i].au16[7] = (i << 8) | (i << 12) | 0xf8; + } + + } + //else if (pExtCtx->enmMethod == BS3EXTCTXMETHOD_ANCIENT) + else + return Bs3TestFailedF("Unsupported extended CPU context method: %d", pExtCtx->enmMethod); + + return true; +} + + +static uint8_t BS3_NEAR_CODE BS3_CMN_NM(Bs3Cg1WorkerInner)(PBS3CG1STATE pThis) +{ + uint8_t iRing; + unsigned iInstr; + + /* + * Test the instructions. + */ + for (iInstr = 0; iInstr < g_cBs3Cg1Instructions; + iInstr++, + pThis->pchMnemonic += pThis->fAdvanceMnemonic * pThis->cchMnemonic, + pThis->pabOperands += pThis->cOperands, + pThis->pabOpcodes += pThis->cbOpcodes) + { + uint8_t const bTestXcptExpected = BS3_MODE_IS_PAGED(pThis->bMode) ? X86_XCPT_PF : X86_XCPT_UD; + bool fOuterInvalidInstr = false; + unsigned iCpuSetup; + + /* + * Expand the instruction information into the state. + * Note! 16-bit will switch to a two level test header lookup once we exceed 64KB. + */ + PCBS3CG1INSTR pInstr = &g_aBs3Cg1Instructions[iInstr]; + pThis->iInstr = iInstr; + pThis->pTestHdr = (PCBS3CG1TESTHDR)&g_abBs3Cg1Tests[pInstr->offTests]; + pThis->fFlags = pInstr->fFlags; + pThis->enmEncoding = (BS3CG1ENC)pInstr->enmEncoding; + pThis->enmEncodingNonInvalid = (BS3CG1ENC)pInstr->enmEncoding; + pThis->enmCpuTest = (BS3CG1CPU)pInstr->enmCpuTest; + pThis->enmPrefixKind = (BS3CG1PFXKIND)pInstr->enmPrefixKind; + pThis->enmXcptType = (BS3CG1XCPTTYPE)pInstr->enmXcptType; + pThis->cchMnemonic = pInstr->cchMnemonic; + if (pThis->fAdvanceMnemonic) + Bs3TestSubF("%s / %.*s", pThis->pszModeShort, pThis->cchMnemonic, pThis->pchMnemonic); + pThis->fAdvanceMnemonic = pInstr->fAdvanceMnemonic; + pThis->uOpcodeMap = pInstr->uOpcodeMap; + pThis->cOperands = pInstr->cOperands; + pThis->cbOpcodes = pInstr->cbOpcodes; + switch (pThis->cOperands) + { + case 4: pThis->aenmOperands[3] = (BS3CG1OP)pThis->pabOperands[3]; + case 3: pThis->aenmOperands[2] = (BS3CG1OP)pThis->pabOperands[2]; + case 2: pThis->aenmOperands[1] = (BS3CG1OP)pThis->pabOperands[1]; + case 1: pThis->aenmOperands[0] = (BS3CG1OP)pThis->pabOperands[0]; + } + switch (pThis->cbOpcodes) + { + case 4: pThis->abOpcodes[3] = pThis->pabOpcodes[3]; + case 3: pThis->abOpcodes[2] = pThis->pabOpcodes[2]; + case 2: pThis->abOpcodes[1] = pThis->pabOpcodes[1]; + case 1: pThis->abOpcodes[0] = pThis->pabOpcodes[0]; + } + + /* + * Check if the CPU supports the instruction. + */ + pThis->fCpuSetupFirstResult = Bs3Cg1CpuSetupFirst(pThis); + if ( !pThis->fCpuSetupFirstResult + || (pThis->fFlags & (BS3CG1INSTR_F_UNUSED | BS3CG1INSTR_F_INVALID))) + fOuterInvalidInstr = true; + + /* Switch the encoder for some of the invalid instructions on non-intel CPUs. */ + if ( (pThis->fFlags & BS3CG1INSTR_F_INTEL_DECODES_INVALID) + && pThis->bCpuVendor != BS3CPUVENDOR_INTEL + && ( (pThis->fFlags & (BS3CG1INSTR_F_UNUSED | BS3CG1INSTR_F_INVALID)) + || (BS3CG1_IS_64BIT_TARGET(pThis) && (pThis->fFlags & BS3CG1INSTR_F_INVALID_64BIT)) + || fOuterInvalidInstr ) ) + pThis->enmEncoding = Bs3Cg1CalcNoneIntelInvalidEncoding(pThis->enmEncoding); + + for (iCpuSetup = 0;; iCpuSetup++) + { + unsigned iEncoding; + unsigned iEncodingNext; + + /* + * Prep the operands and encoding handling. + */ + Bs3Cg1SetOpSizes(pThis, pThis->bMode); + if (!Bs3Cg1EncodePrep(pThis)) + break; + + /* + * Encode the instruction in various ways and check out the test values. + */ + for (iEncoding = 0;; iEncoding = iEncodingNext) + { + /* + * Encode the next instruction variation. + */ + pThis->fInvalidEncoding = fOuterInvalidInstr; + iEncodingNext = Bs3Cg1EncodeNext(pThis, iEncoding); + if (iEncodingNext <= iEncoding) + break; + BS3CG1_DPRINTF(("\ndbg: Encoding #%u: cbCurInst=%u: %.*Rhxs fInvalidEncoding=%d\n", + iEncoding, pThis->cbCurInstr, pThis->cbCurInstr, pThis->abCurInstr, pThis->fInvalidEncoding)); + + /* + * Do the rings. + */ + for (iRing = pThis->iFirstRing + pThis->fSameRingNotOkay; iRing < pThis->iEndRing; iRing++) + { + PCBS3CG1TESTHDR pHdr; + + pThis->uCpl = iRing; + BS3CG1_DPRINTF(("dbg: Ring %u\n", iRing)); + + /* + * Do the tests one by one. + */ + pHdr = pThis->pTestHdr; + for (pThis->iTest = 0;; pThis->iTest++) + { + if (Bs3Cg1RunSelector(pThis, pHdr)) + { + /* Okay, set up the execution context. */ + unsigned offCode; + uint8_t BS3_FAR *pbCode; + + Bs3MemCpy(&pThis->Ctx, &pThis->aInitialCtxs[iRing], sizeof(pThis->Ctx)); + if (pThis->fWorkExtCtx) + Bs3ExtCtxCopy(pThis->pExtCtx, pThis->pInitialExtCtx); + if (BS3_MODE_IS_PAGED(pThis->bMode)) + { + offCode = X86_PAGE_SIZE - pThis->cbCurInstr; + pbCode = &pThis->pbCodePg[offCode]; + //if (iEncoding > 0) { pbCode[-1] = 0xf4; offCode--; } + } + else + { + pbCode = pThis->pbCodePg; + pbCode[pThis->cbCurInstr] = 0x0f; /* UD2 */ + pbCode[pThis->cbCurInstr + 1] = 0x0b; + offCode = 0; + } + pThis->Ctx.rip.u = pThis->CodePgRip + offCode; + Bs3MemCpy(pbCode, pThis->abCurInstr, pThis->cbCurInstr); + + if (Bs3Cg1RunContextModifier(pThis, &pThis->Ctx, pHdr, pHdr->cbSelector, pHdr->cbInput, NULL, pbCode)) + { + /* Run the instruction. */ + BS3CG1_DPRINTF(("dbg: Running test #%u\n", pThis->iTest)); + //Bs3RegCtxPrint(&pThis->Ctx); + if (pThis->fWorkExtCtx) + Bs3ExtCtxRestore(pThis->pExtCtx); + Bs3TrapSetJmpAndRestore(&pThis->Ctx, &pThis->TrapFrame); + if (pThis->fWorkExtCtx) + Bs3ExtCtxSave(pThis->pResultExtCtx); + BS3CG1_DPRINTF(("dbg: bXcpt=%#x rip=%RX64 -> %RX64\n", + pThis->TrapFrame.bXcpt, pThis->Ctx.rip.u, pThis->TrapFrame.Ctx.rip.u)); + + /* + * Apply the output modification program to the context. + */ + pThis->Ctx.rflags.u32 &= ~X86_EFL_RF; + pThis->Ctx.rflags.u32 |= pThis->TrapFrame.Ctx.rflags.u32 & X86_EFL_RF; + pThis->bValueXcpt = UINT8_MAX; //??? + if ( pThis->fInvalidEncoding + || pThis->bAlignmentXcpt != UINT8_MAX + || pThis->bValueXcpt != UINT8_MAX + || Bs3Cg1RunContextModifier(pThis, &pThis->Ctx, pHdr, + pHdr->cbSelector + pHdr->cbInput, pHdr->cbOutput, + &pThis->TrapFrame.Ctx, NULL /*pbCode*/)) + Bs3Cg1CheckResult(pThis, bTestXcptExpected, false /*fInvalidEncodingPgFault*/, iEncoding); + else + { + Bs3TestPrintf("Bs3Cg1RunContextModifier(out): iEncoding=%u iTest=%RU32 iInstr=%u %.*s\n", + iEncoding, pThis->iTest, pThis->iInstr, pThis->cchMnemonic, pThis->pchMnemonic); + ASMHalt(); + } + + /* + * If this is an invalid encoding or instruction, check that we + * get a page fault when shortening it by one byte. + * (Since we didn't execute the output context modifier, we don't + * need to re-initialize the start context.) + */ + if ( pThis->fInvalidEncoding + && BS3_MODE_IS_PAGED(pThis->bMode) + && pThis->cbCurInstr) + { + pbCode += 1; + offCode += 1; + pThis->Ctx.rip.u = pThis->CodePgRip + offCode; + Bs3MemCpy(pbCode, pThis->abCurInstr, pThis->cbCurInstr - 1); + + /* Run the instruction. */ + BS3CG1_DPRINTF(("dbg: Running test #%u (cut short #PF)\n", pThis->iTest)); + //Bs3RegCtxPrint(&pThis->Ctx); + if (pThis->fWorkExtCtx) + Bs3ExtCtxRestore(pThis->pExtCtx); + Bs3TrapSetJmpAndRestore(&pThis->Ctx, &pThis->TrapFrame); + if (pThis->fWorkExtCtx) + Bs3ExtCtxSave(pThis->pResultExtCtx); + BS3CG1_DPRINTF(("dbg: bXcpt=%#x rip=%RX64 -> %RX64 (cut short #PF)\n", + pThis->TrapFrame.bXcpt, pThis->Ctx.rip.u, pThis->TrapFrame.Ctx.rip.u)); + + /* Check it */ + pThis->Ctx.rflags.u32 &= ~X86_EFL_RF; + pThis->Ctx.rflags.u32 |= pThis->TrapFrame.Ctx.rflags.u32 & X86_EFL_RF; + Bs3Cg1CheckResult(pThis, X86_XCPT_PF, true /*fInvalidEncodingPgFault*/, iEncoding); + } + } + else + { + Bs3TestPrintf("Bs3Cg1RunContextModifier(in): iEncoding=%u iTest=%u iInstr=%RU32 %.*s\n", + iEncoding, pThis->iTest, pThis->iInstr, pThis->cchMnemonic, pThis->pchMnemonic); + ASMHalt(); + } + } + else + BS3CG1_DPRINTF(("dbg: Skipping #%u\n", pThis->iTest)); + + /* advance */ + if (pHdr->fLast) + { + BS3CG1_DPRINTF(("dbg: Last\n\n")); + break; + } + pHdr = (PCBS3CG1TESTHDR)((uint8_t BS3_FAR *)(pHdr + 1) + pHdr->cbInput + pHdr->cbOutput + pHdr->cbSelector); + } + } + } + + /* + * Clean up (segment registers, etc) and get the next CPU config. + */ + Bs3Cg1EncodeCleanup(pThis); + if (!Bs3Cg1CpuSetupNext(pThis, iCpuSetup, &fOuterInvalidInstr)) + break; + if (pThis->fFlags & (BS3CG1INSTR_F_UNUSED | BS3CG1INSTR_F_INVALID)) + fOuterInvalidInstr = true; + } + } + + return 0; +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(Bs3Cg1Worker)(uint8_t bMode) +{ + uint8_t bRet = 1; + BS3CG1STATE This; + +#if 0 + /* (for debugging) */ + if (bMode != BS3_MODE_LM64) + return BS3TESTDOMODE_SKIPPED; +#endif + + if (BS3_CMN_NM(Bs3Cg1Init)(&This, bMode)) + { + bRet = BS3_CMN_NM(Bs3Cg1WorkerInner)(&This); + Bs3TestSubDone(); + } + Bs3Cg1Destroy(&This); + +#if 0 + /* (for debugging) */ + if (bMode == BS3_MODE_PPV86) + { + Bs3TestTerm(); + Bs3Shutdown(); + } +#endif + return bRet; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-generated-1.c b/src/VBox/ValidationKit/bootsectors/bs3-cpu-generated-1.c new file mode 100644 index 00000000..2235f9ac --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-generated-1.c @@ -0,0 +1,69 @@ +/* $Id: bs3-cpu-generated-1.c $ */ +/** @file + * BS3Kit - bs3-cpu-generated-1, 16-bit C code. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> +#include "bs3-cpu-generated-1.h" + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +BS3TESTMODEBYMAX_PROTOTYPES_CMN(Bs3Cg1Worker); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static const BS3TESTMODEBYMAXENTRY g_aModeTest[] = +{ + BS3TESTMODEBYMAXENTRY_CMN(NULL, Bs3Cg1Worker), +}; + + +BS3_DECL(void) Main_rm() +{ + Bs3InitAll_rm(); + Bs3TestInit("bs3-cpu-generated-1"); + + Bs3TestDoModesByMax_rm(g_aModeTest, RT_ELEMENTS(g_aModeTest)); + + Bs3TestTerm(); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-generated-1.h b/src/VBox/ValidationKit/bootsectors/bs3-cpu-generated-1.h new file mode 100644 index 00000000..adb55c91 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-generated-1.h @@ -0,0 +1,817 @@ +/* $Id: bs3-cpu-generated-1.h $ */ +/** @file + * BS3Kit - bs3-cpu-generated-1, common header file. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_bootsectors_bs3_cpu_generated_1_h +#define VBOX_INCLUDED_SRC_bootsectors_bs3_cpu_generated_1_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <bs3kit.h> +#include <iprt/assert.h> + + +/** + * Operand details. + * + * Currently simply using the encoding from the reference manuals. + */ +typedef enum BS3CG1OP +{ + BS3CG1OP_INVALID = 0, + + BS3CG1OP_Eb, + BS3CG1OP_Ed, + BS3CG1OP_Ed_WO, + BS3CG1OP_Eq, + BS3CG1OP_Eq_WO, + BS3CG1OP_Ev, + BS3CG1OP_Qq, + BS3CG1OP_Qq_WO, + BS3CG1OP_Wss, + BS3CG1OP_Wss_WO, + BS3CG1OP_Wsd, + BS3CG1OP_Wsd_WO, + BS3CG1OP_Wps, + BS3CG1OP_Wps_WO, + BS3CG1OP_Wpd, + BS3CG1OP_Wpd_WO, + BS3CG1OP_Wdq, + BS3CG1OP_Wdq_WO, + BS3CG1OP_Wq, + BS3CG1OP_Wq_WO, + BS3CG1OP_WqZxReg_WO, + BS3CG1OP_Wx, + BS3CG1OP_Wx_WO, + + BS3CG1OP_Gb, + BS3CG1OP_Gv, + BS3CG1OP_Gv_RO, + BS3CG1OP_HssHi, + BS3CG1OP_HsdHi, + BS3CG1OP_HqHi, + BS3CG1OP_Nq, + BS3CG1OP_Pd, + BS3CG1OP_PdZx_WO, + BS3CG1OP_Pq, + BS3CG1OP_Pq_WO, + BS3CG1OP_Uq, + BS3CG1OP_UqHi, + BS3CG1OP_Uss, + BS3CG1OP_Uss_WO, + BS3CG1OP_Usd, + BS3CG1OP_Usd_WO, + BS3CG1OP_Vd, + BS3CG1OP_Vd_WO, + BS3CG1OP_VdZx_WO, + BS3CG1OP_Vss, + BS3CG1OP_Vss_WO, + BS3CG1OP_VssZx_WO, + BS3CG1OP_Vsd, + BS3CG1OP_Vsd_WO, + BS3CG1OP_VsdZx_WO, + BS3CG1OP_Vps, + BS3CG1OP_Vps_WO, + BS3CG1OP_Vpd, + BS3CG1OP_Vpd_WO, + BS3CG1OP_Vq, + BS3CG1OP_Vq_WO, + BS3CG1OP_Vdq, + BS3CG1OP_Vdq_WO, + BS3CG1OP_VqHi, + BS3CG1OP_VqHi_WO, + BS3CG1OP_VqZx_WO, + BS3CG1OP_Vx, + BS3CG1OP_Vx_WO, + + BS3CG1OP_Ib, + BS3CG1OP_Iz, + + BS3CG1OP_AL, + BS3CG1OP_rAX, + + BS3CG1OP_Ma, + BS3CG1OP_Mb_RO, + BS3CG1OP_Md, + BS3CG1OP_Md_RO, + BS3CG1OP_Md_WO, + BS3CG1OP_Mdq, + BS3CG1OP_Mdq_WO, + BS3CG1OP_Mq, + BS3CG1OP_Mq_WO, + BS3CG1OP_Mps_WO, + BS3CG1OP_Mpd_WO, + BS3CG1OP_Mx, + BS3CG1OP_Mx_WO, + + BS3CG1OP_END +} BS3CG1OP; +/** Pointer to a const operand enum. */ +typedef const BS3CG1OP BS3_FAR *PCBS3CG1OP; + + +/** + * Instruction encoding format. + * + * This duplicates some of the info in the operand array, however it makes it + * easier to figure out encoding variations. + */ +typedef enum BS3CG1ENC +{ + BS3CG1ENC_INVALID = 0, + + BS3CG1ENC_MODRM_Eb_Gb, + BS3CG1ENC_MODRM_Ev_Gv, + BS3CG1ENC_MODRM_Ed_WO_Pd_WZ, + BS3CG1ENC_MODRM_Eq_WO_Pq_WNZ, + BS3CG1ENC_MODRM_Ed_WO_Vd_WZ, + BS3CG1ENC_MODRM_Eq_WO_Vq_WNZ, + BS3CG1ENC_MODRM_Pq_WO_Qq, + BS3CG1ENC_MODRM_Wss_WO_Vss, + BS3CG1ENC_MODRM_Wsd_WO_Vsd, + BS3CG1ENC_MODRM_Wps_WO_Vps, + BS3CG1ENC_MODRM_Wpd_WO_Vpd, + BS3CG1ENC_MODRM_WqZxReg_WO_Vq, + + BS3CG1ENC_MODRM_Gb_Eb, + BS3CG1ENC_MODRM_Gv_Ev, + BS3CG1ENC_MODRM_Gv_RO_Ma, /**< bound instruction */ + BS3CG1ENC_MODRM_Pq_WO_Uq, + BS3CG1ENC_MODRM_PdZx_WO_Ed_WZ, + BS3CG1ENC_MODRM_Pq_WO_Eq_WNZ, + BS3CG1ENC_MODRM_VdZx_WO_Ed_WZ, + BS3CG1ENC_MODRM_Vq_Mq, + BS3CG1ENC_MODRM_Vq_WO_UqHi, + BS3CG1ENC_MODRM_Vq_WO_Mq, + BS3CG1ENC_MODRM_VqHi_WO_Uq, + BS3CG1ENC_MODRM_VqHi_WO_Mq, + BS3CG1ENC_MODRM_VqZx_WO_Eq_WNZ, + BS3CG1ENC_MODRM_Vdq_WO_Mdq, + BS3CG1ENC_MODRM_Vdq_WO_Wdq, + BS3CG1ENC_MODRM_Vpd_WO_Wpd, + BS3CG1ENC_MODRM_Vps_WO_Wps, + BS3CG1ENC_MODRM_VssZx_WO_Wss, + BS3CG1ENC_MODRM_VsdZx_WO_Wsd, + BS3CG1ENC_MODRM_VqZx_WO_Wq, + BS3CG1ENC_MODRM_VqZx_WO_Nq, + BS3CG1ENC_MODRM_Mb_RO, + BS3CG1ENC_MODRM_Md_RO, + BS3CG1ENC_MODRM_Md_WO, + BS3CG1ENC_MODRM_Mdq_WO_Vdq, + BS3CG1ENC_MODRM_Mq_WO_Pq, + BS3CG1ENC_MODRM_Mq_WO_Vq, + BS3CG1ENC_MODRM_Mq_WO_VqHi, + BS3CG1ENC_MODRM_Mps_WO_Vps, + BS3CG1ENC_MODRM_Mpd_WO_Vpd, + + BS3CG1ENC_VEX_MODRM_Vd_WO_Ed_WZ, + BS3CG1ENC_VEX_MODRM_Vps_WO_Wps, + BS3CG1ENC_VEX_MODRM_Vpd_WO_Wpd, + BS3CG1ENC_VEX_MODRM_Vss_WO_HssHi_Uss, + BS3CG1ENC_VEX_MODRM_Vsd_WO_HsdHi_Usd, + BS3CG1ENC_VEX_MODRM_Vq_WO_Eq_WNZ, + BS3CG1ENC_VEX_MODRM_Vq_WO_HqHi_UqHi, + BS3CG1ENC_VEX_MODRM_Vq_WO_HqHi_Mq, + BS3CG1ENC_VEX_MODRM_Vq_WO_Wq, + BS3CG1ENC_VEX_MODRM_VssZx_WO_Md, + BS3CG1ENC_VEX_MODRM_VsdZx_WO_Mq, + BS3CG1ENC_VEX_MODRM_Vx_WO_Mx_L0, + BS3CG1ENC_VEX_MODRM_Vx_WO_Mx_L1, + BS3CG1ENC_VEX_MODRM_Vx_WO_Wx, + BS3CG1ENC_VEX_MODRM_Ed_WO_Vd_WZ, + BS3CG1ENC_VEX_MODRM_Eq_WO_Vq_WNZ, + BS3CG1ENC_VEX_MODRM_Md_WO, + BS3CG1ENC_VEX_MODRM_Mq_WO_Vq, + BS3CG1ENC_VEX_MODRM_Md_WO_Vss, + BS3CG1ENC_VEX_MODRM_Mq_WO_Vsd, + BS3CG1ENC_VEX_MODRM_Mps_WO_Vps, + BS3CG1ENC_VEX_MODRM_Mpd_WO_Vpd, + BS3CG1ENC_VEX_MODRM_Mx_WO_Vx, + BS3CG1ENC_VEX_MODRM_Uss_WO_HssHi_Vss, + BS3CG1ENC_VEX_MODRM_Usd_WO_HsdHi_Vsd, + BS3CG1ENC_VEX_MODRM_Wps_WO_Vps, + BS3CG1ENC_VEX_MODRM_Wpd_WO_Vpd, + BS3CG1ENC_VEX_MODRM_Wq_WO_Vq, + BS3CG1ENC_VEX_MODRM_Wx_WO_Vx, + + BS3CG1ENC_FIXED, + BS3CG1ENC_FIXED_AL_Ib, + BS3CG1ENC_FIXED_rAX_Iz, + + + BS3CG1ENC_MODRM_MOD_EQ_3, /**< Unused or invalid instruction. */ + BS3CG1ENC_MODRM_MOD_NE_3, /**< Unused or invalid instruction. */ + //BS3CG1ENC_VEX_FIXED, /**< Unused or invalid instruction. */ + BS3CG1ENC_VEX_MODRM_MOD_EQ_3, /**< Unused or invalid instruction. */ + BS3CG1ENC_VEX_MODRM_MOD_NE_3, /**< Unused or invalid instruction. */ + BS3CG1ENC_VEX_MODRM, /**< Unused or invalid instruction. */ + + BS3CG1ENC_END +} BS3CG1ENC; + + +/** + * Prefix sensitivitiy kind. + */ +typedef enum BS3CG1PFXKIND +{ + BS3CG1PFXKIND_INVALID = 0, + + BS3CG1PFXKIND_NO_F2_F3_66, /**< No 66, F2 or F3 prefixes allowed as that would alter the meaning. */ + BS3CG1PFXKIND_REQ_F2, /**< Requires F2 (REPNE) prefix as part of the instr encoding. */ + BS3CG1PFXKIND_REQ_F3, /**< Requires F3 (REPE) prefix as part of the instr encoding. */ + BS3CG1PFXKIND_REQ_66, /**< Requires 66 (OP SIZE) prefix as part of the instr encoding. */ + + /** @todo more work to be done here... */ + BS3CG1PFXKIND_MODRM, + BS3CG1PFXKIND_MODRM_NO_OP_SIZES, + + BS3CG1PFXKIND_END +} BS3CG1PFXKIND; + +/** + * CPU selection or CPU ID. + */ +typedef enum BS3CG1CPU +{ + /** Works with an CPU. */ + BS3CG1CPU_ANY = 0, + BS3CG1CPU_GE_80186, + BS3CG1CPU_GE_80286, + BS3CG1CPU_GE_80386, + BS3CG1CPU_GE_80486, + BS3CG1CPU_GE_Pentium, + + BS3CG1CPU_MMX, + BS3CG1CPU_SSE, + BS3CG1CPU_SSE2, + BS3CG1CPU_SSE3, + BS3CG1CPU_SSE4_1, + BS3CG1CPU_AVX, + BS3CG1CPU_AVX2, + BS3CG1CPU_CLFSH, + BS3CG1CPU_CLFLUSHOPT, + + BS3CG1CPU_END +} BS3CG1CPU; + + +/** + * SSE & AVX exception types. + */ +typedef enum BS3CG1XCPTTYPE +{ + BS3CG1XCPTTYPE_NONE = 0, + /* SSE: */ + BS3CG1XCPTTYPE_1, + BS3CG1XCPTTYPE_2, + BS3CG1XCPTTYPE_3, + BS3CG1XCPTTYPE_4, + BS3CG1XCPTTYPE_4UA, + BS3CG1XCPTTYPE_5, + BS3CG1XCPTTYPE_5LZ, + BS3CG1XCPTTYPE_6, + BS3CG1XCPTTYPE_7, + BS3CG1XCPTTYPE_7LZ, + BS3CG1XCPTTYPE_8, + BS3CG1XCPTTYPE_11, + BS3CG1XCPTTYPE_12, + /* EVEX: */ + BS3CG1XCPTTYPE_E1, + BS3CG1XCPTTYPE_E1NF, + BS3CG1XCPTTYPE_E2, + BS3CG1XCPTTYPE_E3, + BS3CG1XCPTTYPE_E3NF, + BS3CG1XCPTTYPE_E4, + BS3CG1XCPTTYPE_E4NF, + BS3CG1XCPTTYPE_E5, + BS3CG1XCPTTYPE_E5NF, + BS3CG1XCPTTYPE_E6, + BS3CG1XCPTTYPE_E6NF, + BS3CG1XCPTTYPE_E7NF, + BS3CG1XCPTTYPE_E9, + BS3CG1XCPTTYPE_E9NF, + BS3CG1XCPTTYPE_E10, + BS3CG1XCPTTYPE_E11, + BS3CG1XCPTTYPE_E12, + BS3CG1XCPTTYPE_E12NF, + BS3CG1XCPTTYPE_END +} BS3CG1XCPTTYPE; +AssertCompile(BS3CG1XCPTTYPE_END <= 32); + + +/** + * Generated instruction info. + */ +typedef struct BS3CG1INSTR +{ + /** The opcode size. */ + uint32_t cbOpcodes : 2; + /** The number of operands. */ + uint32_t cOperands : 2; + /** The length of the mnemonic. */ + uint32_t cchMnemonic : 4; + /** Whether to advance the mnemonic array pointer. */ + uint32_t fAdvanceMnemonic : 1; + /** Offset into g_abBs3Cg1Tests of the first test. */ + uint32_t offTests : 23; + /** BS3CG1ENC values. */ + uint32_t enmEncoding : 10; + /** The VEX, EVEX or XOP opcode map number (VEX.mmmm). */ + uint32_t uOpcodeMap : 4; + /** BS3CG1PFXKIND values. */ + uint32_t enmPrefixKind : 4; + /** CPU test / CPU ID bit test (BS3CG1CPU). */ + uint32_t enmCpuTest : 6; + /** Exception type (BS3CG1XCPTTYPE) */ + uint32_t enmXcptType : 5; + /** Currently unused bits. */ + uint32_t uUnused : 3; + /** BS3CG1INSTR_F_XXX. */ + uint32_t fFlags; +} BS3CG1INSTR; +AssertCompileSize(BS3CG1INSTR, 12); +/** Pointer to a const instruction. */ +typedef BS3CG1INSTR const BS3_FAR *PCBS3CG1INSTR; + + +/** @name BS3CG1INSTR_F_XXX + * @{ */ +/** Defaults to SS rather than DS. */ +#define BS3CG1INSTR_F_DEF_SS UINT32_C(0x00000001) +/** Invalid instruction in 64-bit mode. */ +#define BS3CG1INSTR_F_INVALID_64BIT UINT32_C(0x00000002) +/** Unused instruction. */ +#define BS3CG1INSTR_F_UNUSED UINT32_C(0x00000004) +/** Invalid instruction. */ +#define BS3CG1INSTR_F_INVALID UINT32_C(0x00000008) +/** Only intel does full ModR/M(, ++) decoding for invalid instruction. + * Always used with BS3CG1INSTR_F_INVALID or BS3CG1INSTR_F_UNUSED. */ +#define BS3CG1INSTR_F_INTEL_DECODES_INVALID UINT32_C(0x00000010) +/** VEX.L must be zero (IEMOPHINT_VEX_L_ZERO). */ +#define BS3CG1INSTR_F_VEX_L_ZERO UINT32_C(0x00000020) +/** VEX.L is ignored (IEMOPHINT_VEX_L_IGNORED). */ +#define BS3CG1INSTR_F_VEX_L_IGNORED UINT32_C(0x00000040) +/** @} */ + + +/** + * Test header. + */ +typedef struct BS3CG1TESTHDR +{ + /** The size of the selector program in bytes. + * This is also the offset of the input context modification program. */ + uint32_t cbSelector : 8; + /** The size of the input context modification program in bytes. + * This immediately follows the selector program. */ + uint32_t cbInput : 12; + /** The size of the output context modification program in bytes. + * This immediately follows the input context modification program. The + * program takes the result of the input program as starting point. */ + uint32_t cbOutput : 11; + /** Indicates whether this is the last test or not. */ + uint32_t fLast : 1; +} BS3CG1TESTHDR; +AssertCompileSize(BS3CG1TESTHDR, 4); +/** Pointer to a const test header. */ +typedef BS3CG1TESTHDR const BS3_FAR *PCBS3CG1TESTHDR; + +/** @name Opcode format for the BS3CG1 context modifier. + * + * Used by both the input and output context programs. + * + * The most common operations are encoded as a single byte opcode followed by + * one or more immediate bytes with data. + * + * @{ */ +#define BS3CG1_CTXOP_SIZE_MASK UINT8_C(0x07) +#define BS3CG1_CTXOP_1_BYTE UINT8_C(0x00) +#define BS3CG1_CTXOP_2_BYTES UINT8_C(0x01) +#define BS3CG1_CTXOP_4_BYTES UINT8_C(0x02) +#define BS3CG1_CTXOP_8_BYTES UINT8_C(0x03) +#define BS3CG1_CTXOP_16_BYTES UINT8_C(0x04) +#define BS3CG1_CTXOP_32_BYTES UINT8_C(0x05) +#define BS3CG1_CTXOP_12_BYTES UINT8_C(0x06) +#define BS3CG1_CTXOP_SIZE_ESC UINT8_C(0x07) /**< Separate byte encoding the value size following any destination escape byte. */ + +#define BS3CG1_CTXOP_DST_MASK UINT8_C(0x18) +#define BS3CG1_CTXOP_OP1 UINT8_C(0x00) +#define BS3CG1_CTXOP_OP2 UINT8_C(0x08) +#define BS3CG1_CTXOP_EFL UINT8_C(0x10) +#define BS3CG1_CTXOP_DST_ESC UINT8_C(0x18) /**< Separate byte giving the destination follows immediately. */ + +#define BS3CG1_CTXOP_SIGN_EXT UINT8_C(0x20) /**< Whether to sign-extend (set) the immediate value. */ + +#define BS3CG1_CTXOP_OPERATOR_MASK UINT8_C(0xc0) +#define BS3CG1_CTXOP_ASSIGN UINT8_C(0x00) /**< Simple assignment operator (=) */ +#define BS3CG1_CTXOP_OR UINT8_C(0x40) /**< OR assignment operator (|=). */ +#define BS3CG1_CTXOP_AND UINT8_C(0x80) /**< AND assignment operator (&=). */ +#define BS3CG1_CTXOP_AND_INV UINT8_C(0xc0) /**< AND assignment operator of the inverted value (&~=). */ +/** @} */ + +/** + * Escaped destination values + * + * These are just uppercased versions of TestInOut.kdFields, where dots are + * replaced by underscores. + */ +typedef enum BS3CG1DST +{ + BS3CG1DST_INVALID = 0, + /* Operands. */ + BS3CG1DST_OP1, + BS3CG1DST_OP2, + BS3CG1DST_OP3, + BS3CG1DST_OP4, + /* Flags. */ + BS3CG1DST_EFL, + BS3CG1DST_EFL_UNDEF, /**< Special field only valid in output context modifiers: EFLAGS |= Value & Ouput.EFLAGS; */ + /* 8-bit GPRs. */ + BS3CG1DST_AL, + BS3CG1DST_CL, + BS3CG1DST_DL, + BS3CG1DST_BL, + BS3CG1DST_AH, + BS3CG1DST_CH, + BS3CG1DST_DH, + BS3CG1DST_BH, + BS3CG1DST_SPL, + BS3CG1DST_BPL, + BS3CG1DST_SIL, + BS3CG1DST_DIL, + BS3CG1DST_R8L, + BS3CG1DST_R9L, + BS3CG1DST_R10L, + BS3CG1DST_R11L, + BS3CG1DST_R12L, + BS3CG1DST_R13L, + BS3CG1DST_R14L, + BS3CG1DST_R15L, + /* 16-bit GPRs. */ + BS3CG1DST_AX, + BS3CG1DST_CX, + BS3CG1DST_DX, + BS3CG1DST_BX, + BS3CG1DST_SP, + BS3CG1DST_BP, + BS3CG1DST_SI, + BS3CG1DST_DI, + BS3CG1DST_R8W, + BS3CG1DST_R9W, + BS3CG1DST_R10W, + BS3CG1DST_R11W, + BS3CG1DST_R12W, + BS3CG1DST_R13W, + BS3CG1DST_R14W, + BS3CG1DST_R15W, + /* 32-bit GPRs. */ + BS3CG1DST_EAX, + BS3CG1DST_ECX, + BS3CG1DST_EDX, + BS3CG1DST_EBX, + BS3CG1DST_ESP, + BS3CG1DST_EBP, + BS3CG1DST_ESI, + BS3CG1DST_EDI, + BS3CG1DST_R8D, + BS3CG1DST_R9D, + BS3CG1DST_R10D, + BS3CG1DST_R11D, + BS3CG1DST_R12D, + BS3CG1DST_R13D, + BS3CG1DST_R14D, + BS3CG1DST_R15D, + /* 64-bit GPRs. */ + BS3CG1DST_RAX, + BS3CG1DST_RCX, + BS3CG1DST_RDX, + BS3CG1DST_RBX, + BS3CG1DST_RSP, + BS3CG1DST_RBP, + BS3CG1DST_RSI, + BS3CG1DST_RDI, + BS3CG1DST_R8, + BS3CG1DST_R9, + BS3CG1DST_R10, + BS3CG1DST_R11, + BS3CG1DST_R12, + BS3CG1DST_R13, + BS3CG1DST_R14, + BS3CG1DST_R15, + /* 16-bit, 32-bit or 64-bit registers according to operand size. */ + BS3CG1DST_OZ_RAX, + BS3CG1DST_OZ_RCX, + BS3CG1DST_OZ_RDX, + BS3CG1DST_OZ_RBX, + BS3CG1DST_OZ_RSP, + BS3CG1DST_OZ_RBP, + BS3CG1DST_OZ_RSI, + BS3CG1DST_OZ_RDI, + BS3CG1DST_OZ_R8, + BS3CG1DST_OZ_R9, + BS3CG1DST_OZ_R10, + BS3CG1DST_OZ_R11, + BS3CG1DST_OZ_R12, + BS3CG1DST_OZ_R13, + BS3CG1DST_OZ_R14, + BS3CG1DST_OZ_R15, + + /* Control registers.*/ + BS3CG1DST_CR0, + BS3CG1DST_CR4, + BS3CG1DST_XCR0, + + /* FPU registers. */ + BS3CG1DST_FPU_FIRST, + BS3CG1DST_FCW = BS3CG1DST_FPU_FIRST, + BS3CG1DST_FSW, + BS3CG1DST_FTW, + BS3CG1DST_FOP, + BS3CG1DST_FPUIP, + BS3CG1DST_FPUCS, + BS3CG1DST_FPUDP, + BS3CG1DST_FPUDS, + BS3CG1DST_MXCSR, + BS3CG1DST_ST0, + BS3CG1DST_ST1, + BS3CG1DST_ST2, + BS3CG1DST_ST3, + BS3CG1DST_ST4, + BS3CG1DST_ST5, + BS3CG1DST_ST6, + BS3CG1DST_ST7, + /* MMX registers. */ + BS3CG1DST_MM0, + BS3CG1DST_MM1, + BS3CG1DST_MM2, + BS3CG1DST_MM3, + BS3CG1DST_MM4, + BS3CG1DST_MM5, + BS3CG1DST_MM6, + BS3CG1DST_MM7, + BS3CG1DST_MM0_LO_ZX, + BS3CG1DST_MM1_LO_ZX, + BS3CG1DST_MM2_LO_ZX, + BS3CG1DST_MM3_LO_ZX, + BS3CG1DST_MM4_LO_ZX, + BS3CG1DST_MM5_LO_ZX, + BS3CG1DST_MM6_LO_ZX, + BS3CG1DST_MM7_LO_ZX, + /* SSE registers. */ + BS3CG1DST_XMM0, + BS3CG1DST_XMM1, + BS3CG1DST_XMM2, + BS3CG1DST_XMM3, + BS3CG1DST_XMM4, + BS3CG1DST_XMM5, + BS3CG1DST_XMM6, + BS3CG1DST_XMM7, + BS3CG1DST_XMM8, + BS3CG1DST_XMM9, + BS3CG1DST_XMM10, + BS3CG1DST_XMM11, + BS3CG1DST_XMM12, + BS3CG1DST_XMM13, + BS3CG1DST_XMM14, + BS3CG1DST_XMM15, + BS3CG1DST_XMM0_LO, + BS3CG1DST_XMM1_LO, + BS3CG1DST_XMM2_LO, + BS3CG1DST_XMM3_LO, + BS3CG1DST_XMM4_LO, + BS3CG1DST_XMM5_LO, + BS3CG1DST_XMM6_LO, + BS3CG1DST_XMM7_LO, + BS3CG1DST_XMM8_LO, + BS3CG1DST_XMM9_LO, + BS3CG1DST_XMM10_LO, + BS3CG1DST_XMM11_LO, + BS3CG1DST_XMM12_LO, + BS3CG1DST_XMM13_LO, + BS3CG1DST_XMM14_LO, + BS3CG1DST_XMM15_LO, + BS3CG1DST_XMM0_HI, + BS3CG1DST_XMM1_HI, + BS3CG1DST_XMM2_HI, + BS3CG1DST_XMM3_HI, + BS3CG1DST_XMM4_HI, + BS3CG1DST_XMM5_HI, + BS3CG1DST_XMM6_HI, + BS3CG1DST_XMM7_HI, + BS3CG1DST_XMM8_HI, + BS3CG1DST_XMM9_HI, + BS3CG1DST_XMM10_HI, + BS3CG1DST_XMM11_HI, + BS3CG1DST_XMM12_HI, + BS3CG1DST_XMM13_HI, + BS3CG1DST_XMM14_HI, + BS3CG1DST_XMM15_HI, + BS3CG1DST_XMM0_LO_ZX, + BS3CG1DST_XMM1_LO_ZX, + BS3CG1DST_XMM2_LO_ZX, + BS3CG1DST_XMM3_LO_ZX, + BS3CG1DST_XMM4_LO_ZX, + BS3CG1DST_XMM5_LO_ZX, + BS3CG1DST_XMM6_LO_ZX, + BS3CG1DST_XMM7_LO_ZX, + BS3CG1DST_XMM8_LO_ZX, + BS3CG1DST_XMM9_LO_ZX, + BS3CG1DST_XMM10_LO_ZX, + BS3CG1DST_XMM11_LO_ZX, + BS3CG1DST_XMM12_LO_ZX, + BS3CG1DST_XMM13_LO_ZX, + BS3CG1DST_XMM14_LO_ZX, + BS3CG1DST_XMM15_LO_ZX, + BS3CG1DST_XMM0_DW0, + BS3CG1DST_XMM1_DW0, + BS3CG1DST_XMM2_DW0, + BS3CG1DST_XMM3_DW0, + BS3CG1DST_XMM4_DW0, + BS3CG1DST_XMM5_DW0, + BS3CG1DST_XMM6_DW0, + BS3CG1DST_XMM7_DW0, + BS3CG1DST_XMM8_DW0, + BS3CG1DST_XMM9_DW0, + BS3CG1DST_XMM10_DW0, + BS3CG1DST_XMM11_DW0, + BS3CG1DST_XMM12_DW0, + BS3CG1DST_XMM13_DW0, + BS3CG1DST_XMM14_DW0, + BS3CG1DST_XMM15_DW0, + BS3CG1DST_XMM0_DW0_ZX, + BS3CG1DST_XMM1_DW0_ZX, + BS3CG1DST_XMM2_DW0_ZX, + BS3CG1DST_XMM3_DW0_ZX, + BS3CG1DST_XMM4_DW0_ZX, + BS3CG1DST_XMM5_DW0_ZX, + BS3CG1DST_XMM6_DW0_ZX, + BS3CG1DST_XMM7_DW0_ZX, + BS3CG1DST_XMM8_DW0_ZX, + BS3CG1DST_XMM9_DW0_ZX, + BS3CG1DST_XMM10_DW0_ZX, + BS3CG1DST_XMM11_DW0_ZX, + BS3CG1DST_XMM12_DW0_ZX, + BS3CG1DST_XMM13_DW0_ZX, + BS3CG1DST_XMM14_DW0_ZX, + BS3CG1DST_XMM15_DW0_ZX, + BS3CG1DST_XMM0_HI96, + BS3CG1DST_XMM1_HI96, + BS3CG1DST_XMM2_HI96, + BS3CG1DST_XMM3_HI96, + BS3CG1DST_XMM4_HI96, + BS3CG1DST_XMM5_HI96, + BS3CG1DST_XMM6_HI96, + BS3CG1DST_XMM7_HI96, + BS3CG1DST_XMM8_HI96, + BS3CG1DST_XMM9_HI96, + BS3CG1DST_XMM10_HI96, + BS3CG1DST_XMM11_HI96, + BS3CG1DST_XMM12_HI96, + BS3CG1DST_XMM13_HI96, + BS3CG1DST_XMM14_HI96, + BS3CG1DST_XMM15_HI96, + /* AVX registers. */ + BS3CG1DST_YMM0, + BS3CG1DST_YMM1, + BS3CG1DST_YMM2, + BS3CG1DST_YMM3, + BS3CG1DST_YMM4, + BS3CG1DST_YMM5, + BS3CG1DST_YMM6, + BS3CG1DST_YMM7, + BS3CG1DST_YMM8, + BS3CG1DST_YMM9, + BS3CG1DST_YMM10, + BS3CG1DST_YMM11, + BS3CG1DST_YMM12, + BS3CG1DST_YMM13, + BS3CG1DST_YMM14, + BS3CG1DST_YMM15, + + /* Special fields: */ + BS3CG1DST_SPECIAL_START, + BS3CG1DST_VALUE_XCPT = BS3CG1DST_SPECIAL_START, /**< Expected exception based on input or result. */ + + BS3CG1DST_END +} BS3CG1DST; +AssertCompile(BS3CG1DST_END <= 256); + +/** @name Selector opcode definitions. + * + * Selector programs are very simple, they are zero or more predicate tests + * that are ANDed together. If a predicate test fails, the test is skipped. + * + * One instruction is encoded as byte, where the first bit indicates what kind + * of test and the 7 remaining bits indicates which predicate to check. + * + * @{ */ +#define BS3CG1SEL_OP_KIND_MASK UINT8_C(0x01) /**< The operator part (put in lower bit to reduce switch value range). */ +#define BS3CG1SEL_OP_IS_TRUE UINT8_C(0x00) /**< Check that the predicate is true. */ +#define BS3CG1SEL_OP_IS_FALSE UINT8_C(0x01) /**< Check that the predicate is false. */ +#define BS3CG1SEL_OP_PRED_SHIFT 1 /**< Shift factor for getting/putting a BS3CG1PRED value into/from a byte. */ +/** @} */ + +/** + * Test selector predicates (values are shifted by BS3CG1SEL_OP_PRED_SHIFT). + */ +typedef enum BS3CG1PRED +{ + BS3CG1PRED_INVALID = 0, + + /* Operand size. */ + BS3CG1PRED_SIZE_O16, + BS3CG1PRED_SIZE_O32, + BS3CG1PRED_SIZE_O64, + /* VEX.L values. */ + BS3CG1PRED_VEXL_0, + BS3CG1PRED_VEXL_1, + /* Execution ring. */ + BS3CG1PRED_RING_0, + BS3CG1PRED_RING_1, + BS3CG1PRED_RING_2, + BS3CG1PRED_RING_3, + BS3CG1PRED_RING_0_THRU_2, + BS3CG1PRED_RING_1_THRU_3, + /* Basic code mode. */ + BS3CG1PRED_CODE_64BIT, + BS3CG1PRED_CODE_32BIT, + BS3CG1PRED_CODE_16BIT, + /* CPU modes. */ + BS3CG1PRED_MODE_REAL, + BS3CG1PRED_MODE_PROT, + BS3CG1PRED_MODE_LONG, + BS3CG1PRED_MODE_V86, + BS3CG1PRED_MODE_SMM, + BS3CG1PRED_MODE_VMX, + BS3CG1PRED_MODE_SVM, + /* Paging on/off */ + BS3CG1PRED_PAGING_ON, + BS3CG1PRED_PAGING_OFF, + /* CPU Vendors. */ + BS3CG1PRED_VENDOR_AMD, + BS3CG1PRED_VENDOR_INTEL, + BS3CG1PRED_VENDOR_VIA, + BS3CG1PRED_VENDOR_SHANGHAI, + BS3CG1PRED_VENDOR_HYGON, + + BS3CG1PRED_END +} BS3CG1PRED; + + +/** The test instructions (generated). */ +extern const BS3CG1INSTR BS3_FAR_DATA g_aBs3Cg1Instructions[]; +/** The number of test instructions (generated). */ +extern const uint16_t BS3_FAR_DATA g_cBs3Cg1Instructions; +/** The mnemonics (generated). + * Variable length sequence of mnemonics that runs in parallel to + * g_aBs3Cg1Instructions. */ +extern const char BS3_FAR_DATA g_achBs3Cg1Mnemonics[]; +/** The opcodes (generated). + * Variable length sequence of opcode bytes that runs in parallel to + * g_aBs3Cg1Instructions, advancing by BS3CG1INSTR::cbOpcodes each time. */ +extern const uint8_t BS3_FAR_DATA g_abBs3Cg1Opcodes[]; +/** The operands (generated). + * Variable length sequence of opcode values (BS3CG1OP) that runs in + * parallel to g_aBs3Cg1Instructions, advancing by BS3CG1INSTR::cOperands. */ +extern const uint8_t BS3_FAR_DATA g_abBs3Cg1Operands[]; +/** The test data that BS3CG1INSTR. + * In order to simplify generating these, we use a byte array. */ +extern const uint8_t BS3_FAR_DATA g_abBs3Cg1Tests[]; + + +#endif /* !VBOX_INCLUDED_SRC_bootsectors_bs3_cpu_generated_1_h */ + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-2-asm.asm b/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-2-asm.asm new file mode 100644 index 00000000..1624f345 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-2-asm.asm @@ -0,0 +1,72 @@ +; $Id: bs3-cpu-instr-2-asm.asm $ +;; @file +; BS3Kit - bs3-cpu-instr-2 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit.mac" + + +;********************************************************************************************************************************* +;* Global Variables * +;********************************************************************************************************************************* +;BS3_BEGIN_DATA16 +;BS3_GLOBAL_DATA g_bs3CpuBasic2_ud2_FlatAddr, 4 +; dd _bs3CpuBasic2_ud2 wrt FLAT + + + +; +; CPU mode agnostic test code snippets. +; +BS3_BEGIN_TEXT16 + +BS3_PROC_BEGIN _bs3CpuInstr2_imul_bl_ud2 + imul bl +.again: + ud2 + jmp .again +BS3_PROC_END _bs3CpuInstr2_imul_bl_ud2 + + + +; +; Instantiate code templates. +; +BS3_INSTANTIATE_COMMON_TEMPLATE "bs3-cpu-instr-2-template.mac" +BS3_INSTANTIATE_TEMPLATE_WITH_WEIRD_ONES "bs3-cpu-instr-2-template.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-2-template.c b/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-2-template.c new file mode 100644 index 00000000..8a27b3d9 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-2-template.c @@ -0,0 +1,3196 @@ +/* $Id: bs3-cpu-instr-2-template.c $ */ +/** @file + * BS3Kit - bs3-cpu-instr-2, C code template. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/asm-amd64-x86.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +#ifdef BS3_INSTANTIATING_CMN +# if ARCH_BITS == 64 +typedef struct BS3CI2FSGSBASE +{ + const char *pszDesc; + bool f64BitOperand; + FPFNBS3FAR pfnWorker; + uint8_t offWorkerUd2; + FPFNBS3FAR pfnVerifyWorker; + uint8_t offVerifyWorkerUd2; +} BS3CI2FSGSBASE; +# endif +#endif + + +/********************************************************************************************************************************* +* External Symbols * +*********************************************************************************************************************************/ +#ifdef BS3_INSTANTIATING_CMN +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_mul_xBX_ud2); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_imul_xBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_imul_xCX_xBX_ud2); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_div_xBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_idiv_xBX_ud2); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bsf_AX_BX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bsf_EAX_EBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bsf_RAX_RBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bsf_AX_FSxBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bsf_EAX_FSxBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bsf_RAX_FSxBX_ud2); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_bsf_AX_BX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_bsf_EAX_EBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_bsf_RAX_RBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_bsf_AX_FSxBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_bsf_EAX_FSxBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_bsf_RAX_FSxBX_ud2); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_tzcnt_AX_BX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_tzcnt_EAX_EBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_tzcnt_RAX_RBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_tzcnt_AX_FSxBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_tzcnt_EAX_FSxBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_tzcnt_RAX_FSxBX_ud2); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_tzcnt_AX_BX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_tzcnt_EAX_EBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_tzcnt_RAX_RBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_tzcnt_AX_FSxBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_tzcnt_EAX_FSxBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_tzcnt_RAX_FSxBX_ud2); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bsr_AX_BX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bsr_EAX_EBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bsr_RAX_RBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bsr_AX_FSxBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bsr_EAX_FSxBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bsr_RAX_FSxBX_ud2); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_bsr_AX_BX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_bsr_EAX_EBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_bsr_RAX_RBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_bsr_AX_FSxBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_bsr_EAX_FSxBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_bsr_RAX_FSxBX_ud2); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_lzcnt_AX_BX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_lzcnt_EAX_EBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_lzcnt_RAX_RBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_lzcnt_AX_FSxBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_lzcnt_EAX_FSxBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_lzcnt_RAX_FSxBX_ud2); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_lzcnt_AX_BX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_lzcnt_EAX_EBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_lzcnt_RAX_RBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_lzcnt_AX_FSxBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_lzcnt_EAX_FSxBX_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_f2_lzcnt_RAX_FSxBX_ud2); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_rorx_RBX_RDX_2_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_rorx_EBX_EDX_2_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_rorx_EBX_EDX_2_icebp_L1); +# if ARCH_BITS == 64 +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_rorx_EBX_EDX_2_icebp_X1); +# endif +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_rorx_EBX_EDX_2_icebp_V1); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_rorx_EBX_EDX_2_icebp_V15); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_rorx_RBX_DSxDI_68_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_rorx_EBX_DSxDI_36_icebp); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_andn_RAX_RCX_RBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_andn_RAX_RCX_FSxBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_andn_EAX_ECX_EBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_andn_EAX_ECX_FSxBX_icebp); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bextr_RAX_RBX_RCX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bextr_RAX_FSxBX_RCX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bextr_EAX_EBX_ECX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bextr_EAX_FSxBX_ECX_icebp); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bzhi_RAX_RBX_RCX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bzhi_RAX_FSxBX_RCX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bzhi_EAX_EBX_ECX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_bzhi_EAX_FSxBX_ECX_icebp); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_pdep_RAX_RCX_RBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_pdep_RAX_RCX_FSxBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_pdep_EAX_ECX_EBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_pdep_EAX_ECX_FSxBX_icebp); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_pext_RAX_RCX_RBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_pext_RAX_RCX_FSxBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_pext_EAX_ECX_EBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_pext_EAX_ECX_FSxBX_icebp); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_shlx_RAX_RBX_RCX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_shlx_RAX_FSxBX_RCX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_shlx_EAX_EBX_ECX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_shlx_EAX_FSxBX_ECX_icebp); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_sarx_RAX_RBX_RCX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_sarx_RAX_FSxBX_RCX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_sarx_EAX_EBX_ECX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_sarx_EAX_FSxBX_ECX_icebp); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_shrx_RAX_RBX_RCX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_shrx_RAX_FSxBX_RCX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_shrx_EAX_EBX_ECX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_shrx_EAX_FSxBX_ECX_icebp); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_blsr_RAX_RBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_blsr_RAX_FSxBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_blsr_EAX_EBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_blsr_EAX_FSxBX_icebp); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_blsmsk_RAX_RBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_blsmsk_RAX_FSxBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_blsmsk_EAX_EBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_blsmsk_EAX_FSxBX_icebp); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_blsi_RAX_RBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_blsi_RAX_FSxBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_blsi_EAX_EBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_blsi_EAX_FSxBX_icebp); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_mulx_RAX_RCX_RBX_RDX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_mulx_RCX_RCX_RBX_RDX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_mulx_RAX_RCX_FSxBX_RDX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_mulx_EAX_ECX_EBX_EDX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_mulx_ECX_ECX_EBX_EDX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_mulx_EAX_ECX_FSxBX_EDX_icebp); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_popcnt_AX_BX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_popcnt_EAX_EBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_popcnt_RAX_RBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_popcnt_AX_FSxBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_popcnt_EAX_FSxBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_popcnt_RAX_FSxBX_icebp); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_crc32_EAX_BL_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_crc32_EAX_byte_FSxBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_crc32_EAX_BX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_crc32_EAX_word_FSxBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_crc32_EAX_EBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_crc32_EAX_dword_FSxBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_crc32_EAX_RBX_icebp); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_crc32_EAX_qword_FSxBX_icebp); + +# if ARCH_BITS == 64 +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_cmpxchg16b_rdi_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_lock_cmpxchg16b_rdi_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_o16_cmpxchg16b_rdi_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_lock_o16_cmpxchg16b_rdi_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_repz_cmpxchg16b_rdi_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_lock_repz_cmpxchg16b_rdi_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_repnz_cmpxchg16b_rdi_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_lock_repnz_cmpxchg16b_rdi_ud2); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_wrfsbase_rbx_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_wrfsbase_ebx_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_wrfsbase_rbx_rdfsbase_rcx_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_wrfsbase_ebx_rdfsbase_ecx_ud2); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_wrgsbase_rbx_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_wrgsbase_ebx_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_wrgsbase_rbx_rdgsbase_rcx_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_wrgsbase_ebx_rdgsbase_ecx_ud2); + +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_rdfsbase_rbx_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_rdfsbase_ebx_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_rdgsbase_rbx_ud2); +extern FNBS3FAR BS3_CMN_NM(bs3CpuInstr2_rdgsbase_ebx_ud2); +# endif +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#ifdef BS3_INSTANTIATING_CMN +# if ARCH_BITS == 64 +static BS3CI2FSGSBASE const s_aWrFsBaseWorkers[] = +{ + { "wrfsbase rbx", true, BS3_CMN_NM(bs3CpuInstr2_wrfsbase_rbx_ud2), 5, BS3_CMN_NM(bs3CpuInstr2_wrfsbase_rbx_rdfsbase_rcx_ud2), 15 }, + { "wrfsbase ebx", false, BS3_CMN_NM(bs3CpuInstr2_wrfsbase_ebx_ud2), 4, BS3_CMN_NM(bs3CpuInstr2_wrfsbase_ebx_rdfsbase_ecx_ud2), 13 }, +}; + +static BS3CI2FSGSBASE const s_aWrGsBaseWorkers[] = +{ + { "wrgsbase rbx", true, BS3_CMN_NM(bs3CpuInstr2_wrgsbase_rbx_ud2), 5, BS3_CMN_NM(bs3CpuInstr2_wrgsbase_rbx_rdgsbase_rcx_ud2), 15 }, + { "wrgsbase ebx", false, BS3_CMN_NM(bs3CpuInstr2_wrgsbase_ebx_ud2), 4, BS3_CMN_NM(bs3CpuInstr2_wrgsbase_ebx_rdgsbase_ecx_ud2), 13 }, +}; + +static BS3CI2FSGSBASE const s_aRdFsBaseWorkers[] = +{ + { "rdfsbase rbx", true, BS3_CMN_NM(bs3CpuInstr2_rdfsbase_rbx_ud2), 5, BS3_CMN_NM(bs3CpuInstr2_wrfsbase_rbx_rdfsbase_rcx_ud2), 15 }, + { "rdfsbase ebx", false, BS3_CMN_NM(bs3CpuInstr2_rdfsbase_ebx_ud2), 4, BS3_CMN_NM(bs3CpuInstr2_wrfsbase_ebx_rdfsbase_ecx_ud2), 13 }, +}; + +static BS3CI2FSGSBASE const s_aRdGsBaseWorkers[] = +{ + { "rdgsbase rbx", true, BS3_CMN_NM(bs3CpuInstr2_rdgsbase_rbx_ud2), 5, BS3_CMN_NM(bs3CpuInstr2_wrgsbase_rbx_rdgsbase_rcx_ud2), 15 }, + { "rdgsbase ebx", false, BS3_CMN_NM(bs3CpuInstr2_rdgsbase_ebx_ud2), 4, BS3_CMN_NM(bs3CpuInstr2_wrgsbase_ebx_rdgsbase_ecx_ud2), 13 }, +}; +# endif +#endif /* BS3_INSTANTIATING_CMN - global */ + + +/* + * Common code. + * Common code. + * Common code. + */ +#ifdef BS3_INSTANTIATING_CMN + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_mul)(uint8_t bMode) +{ +#define MUL_CHECK_EFLAGS_ZERO (uint16_t)(X86_EFL_AF | X86_EFL_ZF) +#define MUL_CHECK_EFLAGS (uint16_t)(X86_EFL_CF | X86_EFL_OF | X86_EFL_SF | X86_EFL_PF) + + static const struct + { + RTCCUINTREG uInAX; + RTCCUINTREG uInBX; + RTCCUINTREG uOutDX; + RTCCUINTREG uOutAX; + uint16_t fFlags; + } s_aTests[] = + { + { 1, 1, + 0, 1, 0 }, + { 2, 2, + 0, 4, 0 }, + { RTCCUINTREG_MAX, RTCCUINTREG_MAX, + RTCCUINTREG_MAX-1, 1, X86_EFL_CF | X86_EFL_OF }, + { RTCCINTREG_MAX, RTCCINTREG_MAX, + RTCCINTREG_MAX / 2, 1, X86_EFL_CF | X86_EFL_OF }, + { 1, RTCCUINTREG_MAX, + 0, RTCCUINTREG_MAX, X86_EFL_PF | X86_EFL_SF }, + { 1, RTCCINTREG_MAX, + 0, RTCCINTREG_MAX, X86_EFL_PF }, + { 2, RTCCINTREG_MAX, + 0, RTCCUINTREG_MAX - 1, X86_EFL_SF }, + { (RTCCUINTREG)RTCCINTREG_MAX + 1, 2, + 1, 0, X86_EFL_PF | X86_EFL_CF | X86_EFL_OF }, + { (RTCCUINTREG)RTCCINTREG_MAX / 2 + 1, 3, + 0, ((RTCCUINTREG)RTCCINTREG_MAX / 2 + 1) * 3, X86_EFL_PF | X86_EFL_SF }, + }; + + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + unsigned i, j, k; + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 512); + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, BS3_CMN_NM(bs3CpuInstr2_mul_xBX_ud2)); + for (k = 0; k < 2; k++) + { + Ctx.rflags.u16 |= MUL_CHECK_EFLAGS | MUL_CHECK_EFLAGS_ZERO; + for (j = 0; j < 2; j++) + { + for (i = 0; i < RT_ELEMENTS(s_aTests); i++) + { + if (k == 0) + { + Ctx.rax.RT_CONCAT(u,ARCH_BITS) = s_aTests[i].uInAX; + Ctx.rbx.RT_CONCAT(u,ARCH_BITS) = s_aTests[i].uInBX; + } + else + { + Ctx.rax.RT_CONCAT(u,ARCH_BITS) = s_aTests[i].uInBX; + Ctx.rbx.RT_CONCAT(u,ARCH_BITS) = s_aTests[i].uInAX; + } + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); + if (TrapFrame.bXcpt != X86_XCPT_UD) + Bs3TestFailedF("Expected #UD got %#x", TrapFrame.bXcpt); + else if ( TrapFrame.Ctx.rax.RT_CONCAT(u,ARCH_BITS) != s_aTests[i].uOutAX + || TrapFrame.Ctx.rdx.RT_CONCAT(u,ARCH_BITS) != s_aTests[i].uOutDX + || (TrapFrame.Ctx.rflags.u16 & (MUL_CHECK_EFLAGS | MUL_CHECK_EFLAGS_ZERO)) + != (s_aTests[i].fFlags & MUL_CHECK_EFLAGS) ) + { + Bs3TestFailedF("test #%i failed: input %#" RTCCUINTREG_XFMT " * %#" RTCCUINTREG_XFMT, + i, s_aTests[i].uInAX, s_aTests[i].uInBX); + + if (TrapFrame.Ctx.rax.RT_CONCAT(u,ARCH_BITS) != s_aTests[i].uOutAX) + Bs3TestFailedF("Expected xAX = %#RX" RT_XSTR(ARCH_BITS) " got %#RX" RT_XSTR(ARCH_BITS), + s_aTests[i].uOutAX, TrapFrame.Ctx.rax.RT_CONCAT(u,ARCH_BITS)); + if (TrapFrame.Ctx.rdx.RT_CONCAT(u,ARCH_BITS) != s_aTests[i].uOutDX) + Bs3TestFailedF("Expected xDX = %#RX" RT_XSTR(ARCH_BITS) " got %#RX" RT_XSTR(ARCH_BITS), + s_aTests[i].uOutDX, TrapFrame.Ctx.rdx.RT_CONCAT(u,ARCH_BITS)); + if ( (TrapFrame.Ctx.rflags.u16 & (MUL_CHECK_EFLAGS | MUL_CHECK_EFLAGS_ZERO)) + != (s_aTests[i].fFlags & MUL_CHECK_EFLAGS) ) + Bs3TestFailedF("Expected EFLAGS = %#06RX16, got %#06RX16", s_aTests[i].fFlags & MUL_CHECK_EFLAGS, + TrapFrame.Ctx.rflags.u16 & (MUL_CHECK_EFLAGS | MUL_CHECK_EFLAGS_ZERO)); + } + } + Ctx.rflags.u16 &= ~(MUL_CHECK_EFLAGS | MUL_CHECK_EFLAGS_ZERO); + } + } + + return 0; +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_imul)(uint8_t bMode) +{ +#define IMUL_CHECK_EFLAGS_ZERO (uint16_t)(X86_EFL_AF | X86_EFL_ZF) +#define IMUL_CHECK_EFLAGS (uint16_t)(X86_EFL_CF | X86_EFL_OF | X86_EFL_SF | X86_EFL_PF) + static const struct + { + RTCCUINTREG uInAX; + RTCCUINTREG uInBX; + RTCCUINTREG uOutDX; + RTCCUINTREG uOutAX; + uint16_t fFlags; + } s_aTests[] = + { + /* two positive values. */ + { 1, 1, + 0, 1, 0 }, + { 2, 2, + 0, 4, 0 }, + { RTCCINTREG_MAX, RTCCINTREG_MAX, + RTCCINTREG_MAX/2, 1, X86_EFL_CF | X86_EFL_OF }, + { 1, RTCCINTREG_MAX, + 0, RTCCINTREG_MAX, X86_EFL_PF }, + { 2, RTCCINTREG_MAX, + 0, RTCCUINTREG_MAX - 1U, X86_EFL_CF | X86_EFL_OF | X86_EFL_SF }, + { 2, RTCCINTREG_MAX / 2, + 0, RTCCINTREG_MAX - 1U, 0 }, + { 2, (RTCCINTREG_MAX / 2 + 1), + 0, (RTCCUINTREG)RTCCINTREG_MAX + 1U, X86_EFL_CF | X86_EFL_OF | X86_EFL_SF | X86_EFL_PF }, + { 4, (RTCCINTREG_MAX / 2 + 1), + 1, 0, X86_EFL_CF | X86_EFL_OF | X86_EFL_PF }, + + /* negative and positive */ + { -4, 3, + -1, -12, X86_EFL_SF }, + { 32, -127, + -1, -4064, X86_EFL_SF }, + { RTCCINTREG_MIN, 1, + -1, RTCCINTREG_MIN, X86_EFL_SF | X86_EFL_PF }, + { RTCCINTREG_MIN, 2, + -1, 0, X86_EFL_CF | X86_EFL_OF | X86_EFL_PF }, + { RTCCINTREG_MIN, 3, + -2, RTCCINTREG_MIN, X86_EFL_CF | X86_EFL_OF | X86_EFL_SF | X86_EFL_PF }, + { RTCCINTREG_MIN, 4, + -2, 0, X86_EFL_CF | X86_EFL_OF | X86_EFL_PF }, + { RTCCINTREG_MIN, RTCCINTREG_MAX, + RTCCINTREG_MIN / 2, RTCCINTREG_MIN, X86_EFL_CF | X86_EFL_OF | X86_EFL_SF | X86_EFL_PF }, + { RTCCINTREG_MIN, RTCCINTREG_MAX - 1, + RTCCINTREG_MIN / 2 + 1, 0, X86_EFL_CF | X86_EFL_OF | X86_EFL_PF }, + + /* two negative values. */ + { -4, -63, + 0, 252, X86_EFL_PF }, + { RTCCINTREG_MIN, RTCCINTREG_MIN, + RTCCUINTREG_MAX / 4 + 1, 0, X86_EFL_CF | X86_EFL_OF | X86_EFL_PF }, + { RTCCINTREG_MIN, RTCCINTREG_MIN + 1, + RTCCUINTREG_MAX / 4, RTCCINTREG_MIN, X86_EFL_CF | X86_EFL_OF | X86_EFL_SF | X86_EFL_PF}, + { RTCCINTREG_MIN + 1, RTCCINTREG_MIN + 1, + RTCCUINTREG_MAX / 4, 1, X86_EFL_CF | X86_EFL_OF }, + + }; + + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + unsigned i, j, k; + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 512); + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, BS3_CMN_NM(bs3CpuInstr2_imul_xBX_ud2)); + + for (k = 0; k < 2; k++) + { + Ctx.rflags.u16 |= MUL_CHECK_EFLAGS | MUL_CHECK_EFLAGS_ZERO; + for (j = 0; j < 2; j++) + { + for (i = 0; i < RT_ELEMENTS(s_aTests); i++) + { + if (k == 0) + { + Ctx.rax.RT_CONCAT(u,ARCH_BITS) = s_aTests[i].uInAX; + Ctx.rbx.RT_CONCAT(u,ARCH_BITS) = s_aTests[i].uInBX; + } + else + { + Ctx.rax.RT_CONCAT(u,ARCH_BITS) = s_aTests[i].uInBX; + Ctx.rbx.RT_CONCAT(u,ARCH_BITS) = s_aTests[i].uInAX; + } + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); + if (TrapFrame.bXcpt != X86_XCPT_UD) + Bs3TestFailedF("Expected #UD got %#x", TrapFrame.bXcpt); + else if ( TrapFrame.Ctx.rax.RT_CONCAT(u,ARCH_BITS) != s_aTests[i].uOutAX + || TrapFrame.Ctx.rdx.RT_CONCAT(u,ARCH_BITS) != s_aTests[i].uOutDX + || (TrapFrame.Ctx.rflags.u16 & (IMUL_CHECK_EFLAGS | IMUL_CHECK_EFLAGS_ZERO)) + != (s_aTests[i].fFlags & IMUL_CHECK_EFLAGS) ) + { + Bs3TestFailedF("test #%i failed: input %#" RTCCUINTREG_XFMT " * %#" RTCCUINTREG_XFMT, + i, s_aTests[i].uInAX, s_aTests[i].uInBX); + + if (TrapFrame.Ctx.rax.RT_CONCAT(u,ARCH_BITS) != s_aTests[i].uOutAX) + Bs3TestFailedF("Expected xAX = %#RX" RT_XSTR(ARCH_BITS) " got %#RX" RT_XSTR(ARCH_BITS), + s_aTests[i].uOutAX, TrapFrame.Ctx.rax.RT_CONCAT(u,ARCH_BITS)); + if (TrapFrame.Ctx.rdx.RT_CONCAT(u,ARCH_BITS) != s_aTests[i].uOutDX) + Bs3TestFailedF("Expected xDX = %#RX" RT_XSTR(ARCH_BITS) " got %#RX" RT_XSTR(ARCH_BITS), + s_aTests[i].uOutDX, TrapFrame.Ctx.rdx.RT_CONCAT(u,ARCH_BITS)); + if ( (TrapFrame.Ctx.rflags.u16 & (IMUL_CHECK_EFLAGS | IMUL_CHECK_EFLAGS_ZERO)) + != (s_aTests[i].fFlags & IMUL_CHECK_EFLAGS) ) + Bs3TestFailedF("Expected EFLAGS = %#06RX16, got %#06RX16", s_aTests[i].fFlags & IMUL_CHECK_EFLAGS, + TrapFrame.Ctx.rflags.u16 & (IMUL_CHECK_EFLAGS | IMUL_CHECK_EFLAGS_ZERO)); + } + } + } + } + + /* + * Repeat for the truncating two operand version. + */ + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, BS3_CMN_NM(bs3CpuInstr2_imul_xCX_xBX_ud2)); + + for (k = 0; k < 2; k++) + { + Ctx.rflags.u16 |= MUL_CHECK_EFLAGS | MUL_CHECK_EFLAGS_ZERO; + for (j = 0; j < 2; j++) + { + for (i = 0; i < RT_ELEMENTS(s_aTests); i++) + { + if (k == 0) + { + Ctx.rcx.RT_CONCAT(u,ARCH_BITS) = s_aTests[i].uInAX; + Ctx.rbx.RT_CONCAT(u,ARCH_BITS) = s_aTests[i].uInBX; + } + else + { + Ctx.rcx.RT_CONCAT(u,ARCH_BITS) = s_aTests[i].uInBX; + Ctx.rbx.RT_CONCAT(u,ARCH_BITS) = s_aTests[i].uInAX; + } + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); + if (TrapFrame.bXcpt != X86_XCPT_UD) + Bs3TestFailedF("Expected #UD got %#x", TrapFrame.bXcpt); + else if ( TrapFrame.Ctx.rcx.RT_CONCAT(u,ARCH_BITS) != s_aTests[i].uOutAX + || TrapFrame.Ctx.rdx.u != Ctx.rdx.u + || TrapFrame.Ctx.rbx.u != Ctx.rbx.u + || (TrapFrame.Ctx.rflags.u16 & (IMUL_CHECK_EFLAGS | IMUL_CHECK_EFLAGS_ZERO)) + != (s_aTests[i].fFlags & IMUL_CHECK_EFLAGS) ) + { + Bs3TestFailedF("test #%i failed: input %#" RTCCUINTREG_XFMT " * %#" RTCCUINTREG_XFMT, + i, s_aTests[i].uInAX, s_aTests[i].uInBX); + + if (TrapFrame.Ctx.rcx.RT_CONCAT(u,ARCH_BITS) != s_aTests[i].uOutAX) + Bs3TestFailedF("Expected xAX = %#RX" RT_XSTR(ARCH_BITS) " got %#RX" RT_XSTR(ARCH_BITS), + s_aTests[i].uOutAX, TrapFrame.Ctx.rcx.RT_CONCAT(u,ARCH_BITS)); + if ( (TrapFrame.Ctx.rflags.u16 & (IMUL_CHECK_EFLAGS | IMUL_CHECK_EFLAGS_ZERO)) + != (s_aTests[i].fFlags & IMUL_CHECK_EFLAGS) ) + Bs3TestFailedF("Expected EFLAGS = %#06RX16, got %#06RX16", s_aTests[i].fFlags & IMUL_CHECK_EFLAGS, + TrapFrame.Ctx.rflags.u16 & (IMUL_CHECK_EFLAGS | IMUL_CHECK_EFLAGS_ZERO)); + } + } + } + } + + return 0; +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_div)(uint8_t bMode) +{ +#define DIV_CHECK_EFLAGS (uint16_t)(X86_EFL_CF | X86_EFL_OF | X86_EFL_SF | X86_EFL_ZF | X86_EFL_AF | X86_EFL_PF) + static const struct + { + RTCCUINTREG uInDX; + RTCCUINTREG uInAX; + RTCCUINTREG uInBX; + RTCCUINTREG uOutAX; + RTCCUINTREG uOutDX; + uint8_t bXcpt; + } s_aTests[] = + { + { 0, 1, 1, + 1, 0, X86_XCPT_UD }, + { 0, 5, 2, + 2, 1, X86_XCPT_UD }, + { 0, 0, 0, + 0, 0, X86_XCPT_DE }, + { RTCCUINTREG_MAX, RTCCUINTREG_MAX, 0, + 0, 0, X86_XCPT_DE }, + { RTCCUINTREG_MAX, RTCCUINTREG_MAX, 1, + 0, 0, X86_XCPT_DE }, + { RTCCUINTREG_MAX, RTCCUINTREG_MAX, RTCCUINTREG_MAX, + 0, 0, X86_XCPT_DE }, + { RTCCUINTREG_MAX - 1, RTCCUINTREG_MAX, RTCCUINTREG_MAX, + RTCCUINTREG_MAX, RTCCUINTREG_MAX - 1, X86_XCPT_UD }, + }; + + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + unsigned i, j; + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 512); + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, BS3_CMN_NM(bs3CpuInstr2_div_xBX_ud2)); + + /* + * Do the tests twice, first with all flags set, then once again with + * flags cleared. The flags are not touched by my intel skylake CPU. + */ + Ctx.rflags.u16 |= DIV_CHECK_EFLAGS; + for (j = 0; j < 2; j++) + { + for (i = 0; i < RT_ELEMENTS(s_aTests); i++) + { + Ctx.rax.RT_CONCAT(u,ARCH_BITS) = s_aTests[i].uInAX; + Ctx.rdx.RT_CONCAT(u,ARCH_BITS) = s_aTests[i].uInDX; + Ctx.rbx.RT_CONCAT(u,ARCH_BITS) = s_aTests[i].uInBX; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); + + if ( TrapFrame.bXcpt != s_aTests[i].bXcpt + || ( s_aTests[i].bXcpt == X86_XCPT_UD + ? TrapFrame.Ctx.rax.RT_CONCAT(u,ARCH_BITS) != s_aTests[i].uOutAX + || TrapFrame.Ctx.rdx.RT_CONCAT(u,ARCH_BITS) != s_aTests[i].uOutDX + || (TrapFrame.Ctx.rflags.u16 & DIV_CHECK_EFLAGS) != (Ctx.rflags.u16 & DIV_CHECK_EFLAGS) + : TrapFrame.Ctx.rax.u != Ctx.rax.u + || TrapFrame.Ctx.rdx.u != Ctx.rdx.u + || (TrapFrame.Ctx.rflags.u16 & DIV_CHECK_EFLAGS) != (Ctx.rflags.u16 & DIV_CHECK_EFLAGS) ) ) + { + Bs3TestFailedF("test #%i failed: input %#" RTCCUINTREG_XFMT ":%" RTCCUINTREG_XFMT " / %#" RTCCUINTREG_XFMT, + i, s_aTests[i].uInDX, s_aTests[i].uInAX, s_aTests[i].uInBX); + if (TrapFrame.bXcpt != s_aTests[i].bXcpt) + Bs3TestFailedF("Expected bXcpt = %#x, got %#x", s_aTests[i].bXcpt, TrapFrame.bXcpt); + if (s_aTests[i].bXcpt == X86_XCPT_UD) + { + if (TrapFrame.Ctx.rax.RT_CONCAT(u, ARCH_BITS) != s_aTests[i].uOutAX) + Bs3TestFailedF("Expected xAX = %#" RTCCUINTREG_XFMT ", got %#" RTCCUINTREG_XFMT, + s_aTests[i].uOutAX, TrapFrame.Ctx.rax.RT_CONCAT(u,ARCH_BITS)); + if (TrapFrame.Ctx.rdx.RT_CONCAT(u,ARCH_BITS) != s_aTests[i].uOutDX) + Bs3TestFailedF("Expected xDX = %#" RTCCUINTREG_XFMT ", got %#" RTCCUINTREG_XFMT, + s_aTests[i].uOutDX, TrapFrame.Ctx.rdx.RT_CONCAT(u,ARCH_BITS)); + if ((TrapFrame.Ctx.rflags.u16 & DIV_CHECK_EFLAGS) != (Ctx.rflags.u16 & DIV_CHECK_EFLAGS)) + Bs3TestFailedF("Expected EFLAGS = %#06RX16, got %#06RX16", + Ctx.rflags.u16 & DIV_CHECK_EFLAGS, TrapFrame.Ctx.rflags.u16 & DIV_CHECK_EFLAGS); + } + } + } + Ctx.rflags.u16 &= ~DIV_CHECK_EFLAGS; + } + + return 0; +} + + + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_idiv)(uint8_t bMode) +{ +#define IDIV_CHECK_EFLAGS (uint16_t)(X86_EFL_CF | X86_EFL_OF | X86_EFL_SF | X86_EFL_ZF | X86_EFL_AF | X86_EFL_PF) + static const struct + { + RTCCUINTREG uInDX; + RTCCUINTREG uInAX; + RTCCUINTREG uInBX; + RTCCUINTREG uOutAX; + RTCCUINTREG uOutDX; + uint8_t bXcpt; + } s_aTests[] = + { + { 0, 0, 0, + 0, 0, X86_XCPT_DE }, + { RTCCINTREG_MAX, RTCCINTREG_MAX, 0, + 0, 0, X86_XCPT_DE }, + /* two positive values. */ + { 0, 1, 1, + 1, 0, X86_XCPT_UD }, + { 0, 5, 2, + 2, 1, X86_XCPT_UD }, + { RTCCINTREG_MAX / 2, RTCCUINTREG_MAX / 2, RTCCINTREG_MAX, + RTCCINTREG_MAX, RTCCINTREG_MAX - 1, X86_XCPT_UD }, + { RTCCINTREG_MAX / 2, RTCCUINTREG_MAX / 2 + 1, RTCCINTREG_MAX, + RTCCINTREG_MAX, RTCCINTREG_MAX - 1, X86_XCPT_DE }, + /* negative dividend, positive divisor. */ + { -1, -7, 2, + -3, -1, X86_XCPT_UD }, + { RTCCINTREG_MIN / 2 + 1, 0, RTCCINTREG_MAX, + RTCCINTREG_MIN + 2, RTCCINTREG_MIN + 2, X86_XCPT_UD }, + { RTCCINTREG_MIN / 2, 0, RTCCINTREG_MAX, + 0, 0, X86_XCPT_DE }, + /* positive dividend, negative divisor. */ + { 0, 7, -2, + -3, 1, X86_XCPT_UD }, + { RTCCINTREG_MAX / 2 + 1, RTCCINTREG_MAX, RTCCINTREG_MIN, + RTCCINTREG_MIN, RTCCINTREG_MAX, X86_XCPT_UD }, + { RTCCINTREG_MAX / 2 + 1, (RTCCUINTREG)RTCCINTREG_MAX+1, RTCCINTREG_MIN, + 0, 0, X86_XCPT_DE }, + /* negative dividend, negative divisor. */ + { -1, -7, -2, + 3, -1, X86_XCPT_UD }, + { RTCCINTREG_MIN / 2, 1, RTCCINTREG_MIN, + RTCCINTREG_MAX, RTCCINTREG_MIN + 1, X86_XCPT_UD }, + { RTCCINTREG_MIN / 2, 2, RTCCINTREG_MIN, + RTCCINTREG_MAX, RTCCINTREG_MIN + 2, X86_XCPT_UD }, + { RTCCINTREG_MIN / 2, 0, RTCCINTREG_MIN, + 0, 0, X86_XCPT_DE }, + }; + + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + unsigned i, j; + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 512); + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, BS3_CMN_NM(bs3CpuInstr2_idiv_xBX_ud2)); + + /* + * Do the tests twice, first with all flags set, then once again with + * flags cleared. The flags are not touched by my intel skylake CPU. + */ + Ctx.rflags.u16 |= IDIV_CHECK_EFLAGS; + for (j = 0; j < 2; j++) + { + for (i = 0; i < RT_ELEMENTS(s_aTests); i++) + { + Ctx.rax.RT_CONCAT(u,ARCH_BITS) = s_aTests[i].uInAX; + Ctx.rdx.RT_CONCAT(u,ARCH_BITS) = s_aTests[i].uInDX; + Ctx.rbx.RT_CONCAT(u,ARCH_BITS) = s_aTests[i].uInBX; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); + + if ( TrapFrame.bXcpt != s_aTests[i].bXcpt + || ( s_aTests[i].bXcpt == X86_XCPT_UD + ? TrapFrame.Ctx.rax.RT_CONCAT(u,ARCH_BITS) != s_aTests[i].uOutAX + || TrapFrame.Ctx.rdx.RT_CONCAT(u,ARCH_BITS) != s_aTests[i].uOutDX + || (TrapFrame.Ctx.rflags.u16 & IDIV_CHECK_EFLAGS) != (Ctx.rflags.u16 & IDIV_CHECK_EFLAGS) + : TrapFrame.Ctx.rax.u != Ctx.rax.u + || TrapFrame.Ctx.rdx.u != Ctx.rdx.u + || (TrapFrame.Ctx.rflags.u16 & IDIV_CHECK_EFLAGS) != (Ctx.rflags.u16 & IDIV_CHECK_EFLAGS) ) ) + { + Bs3TestFailedF("test #%i failed: input %#" RTCCUINTREG_XFMT ":%" RTCCUINTREG_XFMT " / %#" RTCCUINTREG_XFMT, + i, s_aTests[i].uInDX, s_aTests[i].uInAX, s_aTests[i].uInBX); + if (TrapFrame.bXcpt != s_aTests[i].bXcpt) + Bs3TestFailedF("Expected bXcpt = %#x, got %#x", s_aTests[i].bXcpt, TrapFrame.bXcpt); + if (s_aTests[i].bXcpt == X86_XCPT_UD) + { + if (TrapFrame.Ctx.rax.RT_CONCAT(u, ARCH_BITS) != s_aTests[i].uOutAX) + Bs3TestFailedF("Expected xAX = %#" RTCCUINTREG_XFMT ", got %#" RTCCUINTREG_XFMT, + s_aTests[i].uOutAX, TrapFrame.Ctx.rax.RT_CONCAT(u,ARCH_BITS)); + if (TrapFrame.Ctx.rdx.RT_CONCAT(u,ARCH_BITS) != s_aTests[i].uOutDX) + Bs3TestFailedF("Expected xDX = %#" RTCCUINTREG_XFMT ", got %#" RTCCUINTREG_XFMT, + s_aTests[i].uOutDX, TrapFrame.Ctx.rdx.RT_CONCAT(u,ARCH_BITS)); + if ((TrapFrame.Ctx.rflags.u16 & IDIV_CHECK_EFLAGS) != (Ctx.rflags.u16 & IDIV_CHECK_EFLAGS)) + Bs3TestFailedF("Expected EFLAGS = %#06RX16, got %#06RX16", + Ctx.rflags.u16 & IDIV_CHECK_EFLAGS, TrapFrame.Ctx.rflags.u16 & IDIV_CHECK_EFLAGS); + } + } + } + Ctx.rflags.u16 &= ~IDIV_CHECK_EFLAGS; + } + + return 0; +} + + +/* + * BSF/BSR (386+) & TZCNT/LZCNT (BMI1,ABM) + */ + +typedef struct BS3CPUINSTR2_SUBTEST_BITSCAN_T +{ + RTCCUINTXREG uSrc; + RTCCUINTXREG uOut; + bool fOutNotSet; + uint16_t fEflOut; +} BS3CPUINSTR2_SUBTEST_BITSCAN_T; + +typedef struct BS3CPUINSTR2_TEST_BITSCAN_T +{ + FPFNBS3FAR pfnWorker; + bool fMemSrc; + uint8_t cbInstr; + uint8_t cOpBits; + uint16_t fEflCheck; + uint8_t cSubTests; + BS3CPUINSTR2_SUBTEST_BITSCAN_T const *paSubTests; +} BS3CPUINSTR2_TEST_BITSCAN_T; + +static uint8_t bs3CpuInstr2_BitScan(uint8_t bMode, BS3CPUINSTR2_TEST_BITSCAN_T const *paTests, unsigned cTests) +{ + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + unsigned i, j, k; + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 512); + + /* + * Do the tests twice, first with all flags set, then once again with + * flags cleared. The flags are not supposed to be touched at all. + */ + Ctx.rflags.u16 |= X86_EFL_STATUS_BITS; + for (j = 0; j < 2; j++) + { + for (i = 0; i < cTests; i++) + { + for (k = 0; k < paTests[i].cSubTests; k++) + { + uint64_t uExpectRax, uExpectRip; + RTCCUINTXREG uMemSrc, uMemSrcExpect; + + Ctx.rax.uCcXReg = RTCCUINTXREG_MAX * 1019; + if (!paTests[i].fMemSrc) + { + Ctx.rbx.uCcXReg = paTests[i].paSubTests[k].uSrc; + uMemSrcExpect = uMemSrc = ~paTests[i].paSubTests[k].uSrc; + } + else + { + uMemSrcExpect = uMemSrc = paTests[i].paSubTests[k].uSrc; + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, &Ctx.fs, &uMemSrc); + } + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, paTests[i].pfnWorker); + if (paTests[i].paSubTests[k].fOutNotSet) + uExpectRax = Ctx.rax.u; + else if (paTests[i].cOpBits != 16) + uExpectRax = paTests[i].paSubTests[k].uOut; + else + uExpectRax = paTests[i].paSubTests[k].uOut | (Ctx.rax.u & UINT64_C(0xffffffffffff0000)); + uExpectRip = Ctx.rip.u + paTests[i].cbInstr; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); + + if ( TrapFrame.bXcpt != X86_XCPT_UD + || TrapFrame.Ctx.rip.u != uExpectRip + || TrapFrame.Ctx.rbx.u != Ctx.rbx.u + || TrapFrame.Ctx.rax.u != uExpectRax + || (TrapFrame.Ctx.rflags.u16 & paTests[i].fEflCheck) + != (paTests[i].paSubTests[k].fEflOut & paTests[i].fEflCheck) + /* check that nothing else really changed: */ + || TrapFrame.Ctx.rcx.u != Ctx.rcx.u + || TrapFrame.Ctx.rdx.u != Ctx.rdx.u + || TrapFrame.Ctx.rsp.u != Ctx.rsp.u + || TrapFrame.Ctx.rbp.u != Ctx.rbp.u + || TrapFrame.Ctx.rsi.u != Ctx.rsi.u + || TrapFrame.Ctx.rdi.u != Ctx.rdi.u + || uMemSrc != uMemSrcExpect + ) + { + Bs3TestFailedF("test #%i/%i failed: input %#" RTCCUINTXREG_XFMT, + i, k, paTests[i].paSubTests[k].uSrc); + if (TrapFrame.bXcpt != X86_XCPT_UD) + Bs3TestFailedF("Expected bXcpt = %#x, got %#x", X86_XCPT_UD, TrapFrame.bXcpt); + if (TrapFrame.Ctx.rip.u != uExpectRip) + Bs3TestFailedF("Expected RIP = %#06RX64, got %#06RX64", uExpectRip, TrapFrame.Ctx.rip.u); + if (TrapFrame.Ctx.rax.u != uExpectRax) + Bs3TestFailedF("Expected RAX = %#06RX64, got %#06RX64", uExpectRax, TrapFrame.Ctx.rax.u); + if (TrapFrame.Ctx.rcx.u != Ctx.rcx.u) + Bs3TestFailedF("Expected RCX = %#06RX64, got %#06RX64", Ctx.rcx.u, TrapFrame.Ctx.rcx.u); + if (TrapFrame.Ctx.rbx.u != Ctx.rbx.u) + Bs3TestFailedF("Expected RBX = %#06RX64, got %#06RX64 (dst)", Ctx.rbx.u, TrapFrame.Ctx.rbx.u); + if ( (TrapFrame.Ctx.rflags.u16 & paTests[i].fEflCheck) + != (paTests[i].paSubTests[k].fEflOut & paTests[i].fEflCheck)) + Bs3TestFailedF("Expected EFLAGS = %#06RX32, got %#06RX32 (output)", + paTests[i].paSubTests[k].fEflOut & paTests[i].fEflCheck, + TrapFrame.Ctx.rflags.u16 & paTests[i].fEflCheck); + + if (TrapFrame.Ctx.rdx.u != Ctx.rdx.u) + Bs3TestFailedF("Expected RDX = %#06RX64, got %#06RX64 (src)", Ctx.rdx.u, TrapFrame.Ctx.rdx.u); + if (TrapFrame.Ctx.rsp.u != Ctx.rsp.u) + Bs3TestFailedF("Expected RSP = %#06RX64, got %#06RX64", Ctx.rsp.u, TrapFrame.Ctx.rsp.u); + if (TrapFrame.Ctx.rbp.u != Ctx.rbp.u) + Bs3TestFailedF("Expected RBP = %#06RX64, got %#06RX64", Ctx.rbp.u, TrapFrame.Ctx.rbp.u); + if (TrapFrame.Ctx.rsi.u != Ctx.rsi.u) + Bs3TestFailedF("Expected RSI = %#06RX64, got %#06RX64", Ctx.rsi.u, TrapFrame.Ctx.rsi.u); + if (TrapFrame.Ctx.rdi.u != Ctx.rdi.u) + Bs3TestFailedF("Expected RDI = %#06RX64, got %#06RX64", Ctx.rdi.u, TrapFrame.Ctx.rdi.u); + if (uMemSrc != uMemSrcExpect) + Bs3TestFailedF("Expected uMemSrc = %#06RX64, got %#06RX64", (uint64_t)uMemSrcExpect, (uint64_t)uMemSrc); + } + } + } + Ctx.rflags.u16 &= ~X86_EFL_STATUS_BITS; + } + + return 0; +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_bsf_tzcnt)(uint8_t bMode) +{ + static BS3CPUINSTR2_SUBTEST_BITSCAN_T const s_aSubTestsBsf16[] = + { + { 0, /* -> */ 0, true, X86_EFL_ZF }, + { ~(RTCCUINTXREG)UINT16_MAX, /* -> */ 0, true, X86_EFL_ZF }, + { ~(RTCCUINTXREG)0, /* -> */ 0, false, 0 }, + { ~(RTCCUINTXREG)1, /* -> */ 1, false, 0 }, + { UINT16_C(0x8000), /* -> */ 15, false, 0 }, + { UINT16_C(0x4560), /* -> */ 5, false, 0 }, + }; + static BS3CPUINSTR2_SUBTEST_BITSCAN_T const s_aSubTestsTzCnt16[] = + { + { 0, /* -> */ 16, false, X86_EFL_CF }, + { ~(RTCCUINTXREG)UINT16_MAX, /* -> */ 16, false, X86_EFL_CF }, + { ~(RTCCUINTXREG)0, /* -> */ 0, false, X86_EFL_ZF }, + { ~(RTCCUINTXREG)1, /* -> */ 1, false, 0 }, + { UINT16_C(0x8000), /* -> */ 15, false, 0 }, + { UINT16_C(0x4560), /* -> */ 5, false, 0 }, + }; + static BS3CPUINSTR2_SUBTEST_BITSCAN_T const s_aSubTestsBsf32[] = + { + { 0, /* -> */ 0, true, X86_EFL_ZF }, +#if ARCH_BITS == 64 + { ~(RTCCUINTXREG)UINT32_MAX, /* -> */ 0, true, X86_EFL_ZF }, +#endif + { ~(RTCCUINTXREG)0, /* -> */ 0, false, 0 }, + { ~(RTCCUINTXREG)1, /* -> */ 1, false, 0 }, + { UINT16_C(0x8000), /* -> */ 15, false, 0 }, + { UINT16_C(0x4560), /* -> */ 5, false, 0 }, + { UINT32_C(0x80000000), /* -> */ 31, false, 0 }, + { UINT32_C(0x45600000), /* -> */ 21, false, 0 }, + }; + static BS3CPUINSTR2_SUBTEST_BITSCAN_T const s_aSubTestsTzCnt32[] = + { + { 0, /* -> */ 32, false, X86_EFL_CF }, +#if ARCH_BITS == 64 + { ~(RTCCUINTXREG)UINT32_MAX, /* -> */ 32, false, X86_EFL_CF }, +#endif + { ~(RTCCUINTXREG)0, /* -> */ 0, false, X86_EFL_ZF }, + { ~(RTCCUINTXREG)1, /* -> */ 1, false, 0 }, + { UINT16_C(0x8000), /* -> */ 15, false, 0 }, + { UINT16_C(0x4560), /* -> */ 5, false, 0 }, + { UINT32_C(0x80000000), /* -> */ 31, false, 0 }, + { UINT32_C(0x45600000), /* -> */ 21, false, 0 }, + }; +#if ARCH_BITS == 64 + static BS3CPUINSTR2_SUBTEST_BITSCAN_T const s_aSubTestsBsf64[] = + { + { 0, /* -> */ 0, true, X86_EFL_ZF }, + { ~(RTCCUINTXREG)0, /* -> */ 0, false, 0 }, + { ~(RTCCUINTXREG)1, /* -> */ 1, false, 0 }, + { UINT16_C(0x8000), /* -> */ 15, false, 0 }, + { UINT16_C(0x4560), /* -> */ 5, false, 0 }, + { UINT32_C(0x80000000), /* -> */ 31, false, 0 }, + { UINT32_C(0x45600000), /* -> */ 21, false, 0 }, + { UINT64_C(0x8000000000000000), /* -> */ 63, false, 0 }, + { UINT64_C(0x4560000000000000), /* -> */ 53, false, 0 }, + }; + static BS3CPUINSTR2_SUBTEST_BITSCAN_T const s_aSubTestsTzCnt64[] = + { + { 0, /* -> */ 64, false, X86_EFL_CF }, + { ~(RTCCUINTXREG)0, /* -> */ 0, false, X86_EFL_ZF }, + { ~(RTCCUINTXREG)1, /* -> */ 1, false, 0 }, + { UINT16_C(0x8000), /* -> */ 15, false, 0 }, + { UINT16_C(0x4560), /* -> */ 5, false, 0 }, + { UINT32_C(0x80000000), /* -> */ 31, false, 0 }, + { UINT32_C(0x45600000), /* -> */ 21, false, 0 }, + { UINT64_C(0x8000000000000000), /* -> */ 63, false, 0 }, + { UINT64_C(0x4560000000000000), /* -> */ 53, false, 0 }, + }; +#endif + static BS3CPUINSTR2_TEST_BITSCAN_T s_aTests[] = + { + { BS3_CMN_NM(bs3CpuInstr2_bsf_AX_BX_ud2), false, 3 + (ARCH_BITS != 16), 16, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsf16), s_aSubTestsBsf16 }, + { BS3_CMN_NM(bs3CpuInstr2_bsf_AX_FSxBX_ud2), true, 4 + (ARCH_BITS != 16), 16, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsf16), s_aSubTestsBsf16 }, + { BS3_CMN_NM(bs3CpuInstr2_bsf_EAX_EBX_ud2), false, 3 + (ARCH_BITS == 16), 32, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsf32), s_aSubTestsBsf32 }, + { BS3_CMN_NM(bs3CpuInstr2_bsf_EAX_FSxBX_ud2), true, 4 + (ARCH_BITS == 16), 32, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsf32), s_aSubTestsBsf32 }, +#if ARCH_BITS == 64 + { BS3_CMN_NM(bs3CpuInstr2_bsf_RAX_RBX_ud2), false, 4, 64, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsf64), s_aSubTestsBsf64 }, + { BS3_CMN_NM(bs3CpuInstr2_bsf_RAX_FSxBX_ud2), true, 5, 64, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsf64), s_aSubTestsBsf64 }, +#endif + /* f2 prefixed variant: */ + { BS3_CMN_NM(bs3CpuInstr2_f2_bsf_AX_BX_ud2), false, 4 + (ARCH_BITS != 16), 16, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsf16), s_aSubTestsBsf16 }, + { BS3_CMN_NM(bs3CpuInstr2_f2_bsf_AX_FSxBX_ud2), true, 5 + (ARCH_BITS != 16), 16, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsf16), s_aSubTestsBsf16 }, + { BS3_CMN_NM(bs3CpuInstr2_f2_bsf_EAX_EBX_ud2), false, 4 + (ARCH_BITS == 16), 32, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsf32), s_aSubTestsBsf32 }, + { BS3_CMN_NM(bs3CpuInstr2_f2_bsf_EAX_FSxBX_ud2), true, 5 + (ARCH_BITS == 16), 32, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsf32), s_aSubTestsBsf32 }, +#if ARCH_BITS == 64 + { BS3_CMN_NM(bs3CpuInstr2_f2_bsf_RAX_RBX_ud2), false, 5, 64, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsf64), s_aSubTestsBsf64 }, + { BS3_CMN_NM(bs3CpuInstr2_f2_bsf_RAX_FSxBX_ud2), true, 6, 64, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsf64), s_aSubTestsBsf64 }, +#endif + + /* tzcnt: */ + { BS3_CMN_NM(bs3CpuInstr2_tzcnt_AX_BX_ud2), false, 4 + (ARCH_BITS != 16), 16, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsTzCnt16), s_aSubTestsTzCnt16 }, + { BS3_CMN_NM(bs3CpuInstr2_tzcnt_AX_FSxBX_ud2), true, 5 + (ARCH_BITS != 16), 16, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsTzCnt16), s_aSubTestsTzCnt16 }, + { BS3_CMN_NM(bs3CpuInstr2_tzcnt_EAX_EBX_ud2), false, 4 + (ARCH_BITS == 16), 32, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsTzCnt32), s_aSubTestsTzCnt32 }, + { BS3_CMN_NM(bs3CpuInstr2_tzcnt_EAX_FSxBX_ud2), true, 5 + (ARCH_BITS == 16), 32, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsTzCnt32), s_aSubTestsTzCnt32 }, +#if ARCH_BITS == 64 + { BS3_CMN_NM(bs3CpuInstr2_tzcnt_RAX_RBX_ud2), false, 5, 64, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsTzCnt64), s_aSubTestsTzCnt64 }, + { BS3_CMN_NM(bs3CpuInstr2_tzcnt_RAX_FSxBX_ud2), true, 6, 64, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsTzCnt64), s_aSubTestsTzCnt64 }, +#endif + /* f2 prefixed tzcnt variant (last prefix (f3) should prevail): */ + { BS3_CMN_NM(bs3CpuInstr2_f2_tzcnt_AX_BX_ud2), false, 5 + (ARCH_BITS != 16), 16, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsTzCnt16), s_aSubTestsTzCnt16 }, + { BS3_CMN_NM(bs3CpuInstr2_f2_tzcnt_AX_FSxBX_ud2), true, 6 + (ARCH_BITS != 16), 16, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsTzCnt16), s_aSubTestsTzCnt16 }, + { BS3_CMN_NM(bs3CpuInstr2_f2_tzcnt_EAX_EBX_ud2), false, 5 + (ARCH_BITS == 16), 32, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsTzCnt32), s_aSubTestsTzCnt32 }, + { BS3_CMN_NM(bs3CpuInstr2_f2_tzcnt_EAX_FSxBX_ud2),true, 6 + (ARCH_BITS == 16), 32, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsTzCnt32), s_aSubTestsTzCnt32 }, +#if ARCH_BITS == 64 + { BS3_CMN_NM(bs3CpuInstr2_f2_tzcnt_RAX_RBX_ud2), false, 6, 64, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsTzCnt64), s_aSubTestsTzCnt64 }, + { BS3_CMN_NM(bs3CpuInstr2_f2_tzcnt_RAX_FSxBX_ud2),true, 7, 64, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsTzCnt64), s_aSubTestsTzCnt64 }, +#endif + }; + + uint32_t uStdExtFeatEbx = 0; + if (g_uBs3CpuDetected & BS3CPU_F_CPUID) + ASMCpuIdExSlow(7, 0, 0, 0, NULL, &uStdExtFeatEbx, NULL, NULL); + if (!(uStdExtFeatEbx & X86_CPUID_STEXT_FEATURE_EBX_BMI1)) + { + unsigned i = RT_ELEMENTS(s_aTests); + while (i-- > 0) + if (s_aTests[i].fEflCheck & X86_EFL_CF) + { + s_aTests[i].fEflCheck = X86_EFL_ZF; + switch (s_aTests[i].cOpBits) + { + case 16: + s_aTests[i].cSubTests = RT_ELEMENTS(s_aSubTestsBsf16); + s_aTests[i].paSubTests = s_aSubTestsBsf16; + break; + case 32: + s_aTests[i].cSubTests = RT_ELEMENTS(s_aSubTestsBsf32); + s_aTests[i].paSubTests = s_aSubTestsBsf32; + break; +#if ARCH_BITS == 64 + case 64: + s_aTests[i].cSubTests = RT_ELEMENTS(s_aSubTestsBsf64); + s_aTests[i].paSubTests = s_aSubTestsBsf64; + break; +#endif + } + } + Bs3TestPrintf("tzcnt not supported\n"); + } + + return bs3CpuInstr2_BitScan(bMode, s_aTests, RT_ELEMENTS(s_aTests)); +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_bsr_lzcnt)(uint8_t bMode) +{ + static BS3CPUINSTR2_SUBTEST_BITSCAN_T const s_aSubTestsBsr16[] = + { + { 0, /* -> */ 0, true, X86_EFL_ZF }, + { ~(RTCCUINTXREG)UINT16_MAX, /* -> */ 0, true, X86_EFL_ZF }, + { ~(RTCCUINTXREG)0, /* -> */ 15, false, 0 }, + { ~(RTCCUINTXREG)1, /* -> */ 15, false, 0 }, + { UINT16_C(0x0001), /* -> */ 0, false, 0 }, + { UINT16_C(0x0002), /* -> */ 1, false, 0 }, + { UINT16_C(0x4560), /* -> */ 14, false, 0 }, + }; + static BS3CPUINSTR2_SUBTEST_BITSCAN_T const s_aSubTestsLzCnt16[] = + { + { 0, /* -> */ 16, false, X86_EFL_CF }, + { ~(RTCCUINTXREG)UINT16_MAX, /* -> */ 16, false, X86_EFL_CF }, + { ~(RTCCUINTXREG)0, /* -> */ 0, false, X86_EFL_ZF }, + { ~(RTCCUINTXREG)1, /* -> */ 0, false, X86_EFL_ZF }, + { UINT16_C(0x8000), /* -> */ 0, false, X86_EFL_ZF }, + { UINT16_C(0x4560), /* -> */ 1, false, 0 }, + { UINT16_C(0x003f), /* -> */ 10, false, 0 }, + { UINT16_C(0x0001), /* -> */ 15, false, 0 }, + }; + static BS3CPUINSTR2_SUBTEST_BITSCAN_T const s_aSubTestsBsr32[] = + { + { 0, /* -> */ 0, true, X86_EFL_ZF }, +#if ARCH_BITS == 64 + { ~(RTCCUINTXREG)UINT32_MAX, /* -> */ 0, true, X86_EFL_ZF }, +#endif + { ~(RTCCUINTXREG)0, /* -> */ 31, false, 0 }, + { ~(RTCCUINTXREG)1, /* -> */ 31, false, 0 }, + { 1, /* -> */ 0, false, 0 }, + { 2, /* -> */ 1, false, 0 }, + { UINT16_C(0x8000), /* -> */ 15, false, 0 }, + { UINT16_C(0x4560), /* -> */ 14, false, 0 }, + { UINT32_C(0x80000000), /* -> */ 31, false, 0 }, + { UINT32_C(0x45600000), /* -> */ 30, false, 0 }, + }; + static BS3CPUINSTR2_SUBTEST_BITSCAN_T const s_aSubTestsLzCnt32[] = + { + { 0, /* -> */ 32, false, X86_EFL_CF }, +#if ARCH_BITS == 64 + { ~(RTCCUINTXREG)UINT32_MAX, /* -> */ 32, false, X86_EFL_CF }, +#endif + { ~(RTCCUINTXREG)0, /* -> */ 0, false, X86_EFL_ZF }, + { ~(RTCCUINTXREG)1, /* -> */ 0, false, X86_EFL_ZF }, + { 1, /* -> */ 31, false, 0 }, + { 2, /* -> */ 30, false, 0}, + { UINT16_C(0x8000), /* -> */ 16, false, 0 }, + { UINT16_C(0x4560), /* -> */ 17, false, 0 }, + { UINT32_C(0x80000000), /* -> */ 0, false, X86_EFL_ZF }, + { UINT32_C(0x45600000), /* -> */ 1, false, 0 }, + { UINT32_C(0x0000ffff), /* -> */ 16, false, 0 }, + }; +#if ARCH_BITS == 64 + static BS3CPUINSTR2_SUBTEST_BITSCAN_T const s_aSubTestsBsr64[] = + { + { 0, /* -> */ 0, true, X86_EFL_ZF }, + { ~(RTCCUINTXREG)0, /* -> */ 63, false, 0 }, + { ~(RTCCUINTXREG)1, /* -> */ 63, false, 0 }, + { 1, /* -> */ 0, false, 0 }, + { 2, /* -> */ 1, false, 0 }, + { UINT16_C(0x8000), /* -> */ 15, false, 0 }, + { UINT16_C(0x4560), /* -> */ 14, false, 0 }, + { UINT32_C(0x80000000), /* -> */ 31, false, 0 }, + { UINT32_C(0x45600000), /* -> */ 30, false, 0 }, + { UINT64_C(0x8000000000000000), /* -> */ 63, false, 0 }, + { UINT64_C(0x0045600000000000), /* -> */ 54, false, 0 }, + }; + static BS3CPUINSTR2_SUBTEST_BITSCAN_T const s_aSubTestsLzCnt64[] = + { + { 0, /* -> */ 64, false, X86_EFL_CF }, + { ~(RTCCUINTXREG)0, /* -> */ 0, false, X86_EFL_ZF }, + { ~(RTCCUINTXREG)1, /* -> */ 0, false, X86_EFL_ZF }, + { 1, /* -> */ 63, false, 0 }, + { 2, /* -> */ 62, false, 0 }, + { UINT16_C(0x8000), /* -> */ 48, false, 0 }, + { UINT16_C(0x4560), /* -> */ 49, false, 0 }, + { UINT32_C(0x80000000), /* -> */ 32, false, 0 }, + { UINT32_C(0x45600000), /* -> */ 33, false, 0 }, + { UINT64_C(0x8000000000000000), /* -> */ 0, false, X86_EFL_ZF }, + { UINT64_C(0x4560000000000000), /* -> */ 1, false, 0 }, + { UINT64_C(0x0045600000000000), /* -> */ 9, false, 0 }, + }; +#endif + static BS3CPUINSTR2_TEST_BITSCAN_T s_aTests[] = + { + { BS3_CMN_NM(bs3CpuInstr2_bsr_AX_BX_ud2), false, 3 + (ARCH_BITS != 16), 16, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsr16), s_aSubTestsBsr16 }, + { BS3_CMN_NM(bs3CpuInstr2_bsr_AX_FSxBX_ud2), true, 4 + (ARCH_BITS != 16), 16, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsr16), s_aSubTestsBsr16 }, + { BS3_CMN_NM(bs3CpuInstr2_bsr_EAX_EBX_ud2), false, 3 + (ARCH_BITS == 16), 32, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsr32), s_aSubTestsBsr32 }, + { BS3_CMN_NM(bs3CpuInstr2_bsr_EAX_FSxBX_ud2), true, 4 + (ARCH_BITS == 16), 32, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsr32), s_aSubTestsBsr32 }, +#if ARCH_BITS == 64 + { BS3_CMN_NM(bs3CpuInstr2_bsr_RAX_RBX_ud2), false, 4, 64, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsr64), s_aSubTestsBsr64 }, + { BS3_CMN_NM(bs3CpuInstr2_bsr_RAX_FSxBX_ud2), true, 5, 64, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsr64), s_aSubTestsBsr64 }, +#endif + /* f2 prefixed variant: */ + { BS3_CMN_NM(bs3CpuInstr2_f2_bsr_AX_BX_ud2), false, 4 + (ARCH_BITS != 16), 16, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsr16), s_aSubTestsBsr16 }, + { BS3_CMN_NM(bs3CpuInstr2_f2_bsr_AX_FSxBX_ud2), true, 5 + (ARCH_BITS != 16), 16, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsr16), s_aSubTestsBsr16 }, + { BS3_CMN_NM(bs3CpuInstr2_f2_bsr_EAX_EBX_ud2), false, 4 + (ARCH_BITS == 16), 32, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsr32), s_aSubTestsBsr32 }, + { BS3_CMN_NM(bs3CpuInstr2_f2_bsr_EAX_FSxBX_ud2), true, 5 + (ARCH_BITS == 16), 32, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsr32), s_aSubTestsBsr32 }, +#if ARCH_BITS == 64 + { BS3_CMN_NM(bs3CpuInstr2_f2_bsr_RAX_RBX_ud2), false, 5, 64, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsr64), s_aSubTestsBsr64 }, + { BS3_CMN_NM(bs3CpuInstr2_f2_bsr_RAX_FSxBX_ud2), true, 6, 64, X86_EFL_ZF, + RT_ELEMENTS(s_aSubTestsBsr64), s_aSubTestsBsr64 }, +#endif + + /* lzcnt: */ + { BS3_CMN_NM(bs3CpuInstr2_lzcnt_AX_BX_ud2), false, 4 + (ARCH_BITS != 16), 16, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsLzCnt16), s_aSubTestsLzCnt16 }, + { BS3_CMN_NM(bs3CpuInstr2_lzcnt_AX_FSxBX_ud2), true, 5 + (ARCH_BITS != 16), 16, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsLzCnt16), s_aSubTestsLzCnt16 }, + { BS3_CMN_NM(bs3CpuInstr2_lzcnt_EAX_EBX_ud2), false, 4 + (ARCH_BITS == 16), 32, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsLzCnt32), s_aSubTestsLzCnt32 }, + { BS3_CMN_NM(bs3CpuInstr2_lzcnt_EAX_FSxBX_ud2), true, 5 + (ARCH_BITS == 16), 32, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsLzCnt32), s_aSubTestsLzCnt32 }, +#if ARCH_BITS == 64 + { BS3_CMN_NM(bs3CpuInstr2_lzcnt_RAX_RBX_ud2), false, 5, 64, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsLzCnt64), s_aSubTestsLzCnt64 }, + { BS3_CMN_NM(bs3CpuInstr2_lzcnt_RAX_FSxBX_ud2), true, 6, 64, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsLzCnt64), s_aSubTestsLzCnt64 }, +#endif + /* f2 prefixed lzcnt variant (last prefix (f3) should prevail): */ + { BS3_CMN_NM(bs3CpuInstr2_f2_lzcnt_AX_BX_ud2), false, 5 + (ARCH_BITS != 16), 16, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsLzCnt16), s_aSubTestsLzCnt16 }, + { BS3_CMN_NM(bs3CpuInstr2_f2_lzcnt_AX_FSxBX_ud2), true, 6 + (ARCH_BITS != 16), 16, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsLzCnt16), s_aSubTestsLzCnt16 }, + { BS3_CMN_NM(bs3CpuInstr2_f2_lzcnt_EAX_EBX_ud2), false, 5 + (ARCH_BITS == 16), 32, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsLzCnt32), s_aSubTestsLzCnt32 }, + { BS3_CMN_NM(bs3CpuInstr2_f2_lzcnt_EAX_FSxBX_ud2),true, 6 + (ARCH_BITS == 16), 32, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsLzCnt32), s_aSubTestsLzCnt32 }, +#if ARCH_BITS == 64 + { BS3_CMN_NM(bs3CpuInstr2_f2_lzcnt_RAX_RBX_ud2), false, 6, 64, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsLzCnt64), s_aSubTestsLzCnt64 }, + { BS3_CMN_NM(bs3CpuInstr2_f2_lzcnt_RAX_FSxBX_ud2),true, 7, 64, X86_EFL_ZF | X86_EFL_CF, + RT_ELEMENTS(s_aSubTestsLzCnt64), s_aSubTestsLzCnt64 }, +#endif + }; + + uint32_t uExtFeatEcx = 0; + if (g_uBs3CpuDetected & BS3CPU_F_CPUID_EXT_LEAVES) + ASMCpuIdExSlow(UINT32_C(0x80000001), 0, 0, 0, NULL, NULL, &uExtFeatEcx, NULL); + if (!(uExtFeatEcx & X86_CPUID_AMD_FEATURE_ECX_ABM)) + { + unsigned i = RT_ELEMENTS(s_aTests); + while (i-- > 0) + if (s_aTests[i].fEflCheck & X86_EFL_CF) + { + s_aTests[i].fEflCheck = X86_EFL_ZF; + switch (s_aTests[i].cOpBits) + { + case 16: + s_aTests[i].cSubTests = RT_ELEMENTS(s_aSubTestsBsr16); + s_aTests[i].paSubTests = s_aSubTestsBsr16; + break; + case 32: + s_aTests[i].cSubTests = RT_ELEMENTS(s_aSubTestsBsr32); + s_aTests[i].paSubTests = s_aSubTestsBsr32; + break; +#if ARCH_BITS == 64 + case 64: + s_aTests[i].cSubTests = RT_ELEMENTS(s_aSubTestsBsr64); + s_aTests[i].paSubTests = s_aSubTestsBsr64; + break; +#endif + } + } + Bs3TestPrintf("lzcnt not supported\n"); + } + + return bs3CpuInstr2_BitScan(bMode, s_aTests, RT_ELEMENTS(s_aTests)); +} + + +/** + * RORX + */ +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_rorx)(uint8_t bMode) +{ + static const struct + { + FPFNBS3FAR pfnWorker; + bool fMemSrc; + bool fOkay; + RTCCUINTXREG uIn; + RTCCUINTXREG uOut; + } s_aTests[] = + { + /* 64 bits register width (32 bits in 32- and 16-bit modes): */ + { BS3_CMN_NM(bs3CpuInstr2_rorx_RBX_RDX_2_icebp), false, true, // #0 + 0, /* -> */ 0 }, + { BS3_CMN_NM(bs3CpuInstr2_rorx_RBX_RDX_2_icebp), false, true, // #1 + ~(RTCCUINTXREG)2, /* -> */ ~(RTCCUINTXREG)0 >> 1 }, + { BS3_CMN_NM(bs3CpuInstr2_rorx_RBX_DSxDI_68_icebp), true, true, // #2 + 0, /* -> */ 0 }, + { BS3_CMN_NM(bs3CpuInstr2_rorx_RBX_DSxDI_68_icebp), true, true, // #3 + ~(RTCCUINTXREG)2, /* -> */ (RTCCUINTXREG_MAX >> 4) | (~(RTCCUINTXREG)2 << (sizeof(RTCCUINTXREG) * 8 - 4)) }, + + /* 32 bits register width: */ + { BS3_CMN_NM(bs3CpuInstr2_rorx_EBX_EDX_2_icebp), false, true, // #4 + 0, /* -> */ 0 }, + { BS3_CMN_NM(bs3CpuInstr2_rorx_EBX_EDX_2_icebp), false, true, // #5 + ~(RTCCUINTXREG)2, /* -> */ (RTCCUINTXREG)(~(uint32_t)0 >> 1) }, + { BS3_CMN_NM(bs3CpuInstr2_rorx_EBX_DSxDI_36_icebp), true, true, // #6 + 0, /* -> */ 0 }, + { BS3_CMN_NM(bs3CpuInstr2_rorx_EBX_DSxDI_36_icebp), true, true, // #7 + ~(RTCCUINTXREG)2, /* -> */ (RTCCUINTXREG)UINT32_C(0xdfffffff) }, + + /* encoding tests: */ + { BS3_CMN_NM(bs3CpuInstr2_rorx_EBX_EDX_2_icebp_L1), false, false, // #8 + RTCCUINTXREG_MAX, /* -> */ 0 }, + { BS3_CMN_NM(bs3CpuInstr2_rorx_EBX_EDX_2_icebp_V1), false, false, // #9 + RTCCUINTXREG_MAX, /* -> */ 0 }, + { BS3_CMN_NM(bs3CpuInstr2_rorx_EBX_EDX_2_icebp_V15), false, false, // #10 + RTCCUINTXREG_MAX, /* -> */ 0 }, +# if ARCH_BITS == 64 /* The VEX.X=0 encoding mean LES instruction in 32-bit and 16-bit mode. */ + { BS3_CMN_NM(bs3CpuInstr2_rorx_EBX_EDX_2_icebp_X1), false, true, // #11 + UINT32_C(0xf1e2d3c5), /* -> */ (RTCCUINTXREG)UINT32_C(0x7c78b4f1) }, +# endif + }; + + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + unsigned i, j; + uint32_t uStdExtFeatEbx = 0; + bool fSupportsRorX; + + if (g_uBs3CpuDetected & BS3CPU_F_CPUID) + ASMCpuIdExSlow(7, 0, 0, 0, NULL, &uStdExtFeatEbx, NULL, NULL); + fSupportsRorX = RT_BOOL(uStdExtFeatEbx & X86_CPUID_STEXT_FEATURE_EBX_BMI2); + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 512); + + /* + * Do the tests twice, first with all flags set, then once again with + * flags cleared. The flags are not supposed to be touched at all. + */ + Ctx.rflags.u16 |= X86_EFL_STATUS_BITS; + for (j = 0; j < 2; j++) + { + for (i = 0; i < RT_ELEMENTS(s_aTests); i++) + { + bool const fOkay = !BS3_MODE_IS_RM_OR_V86(bMode) && s_aTests[i].fOkay && fSupportsRorX; + uint8_t const bExpectXcpt = fOkay ? X86_XCPT_DB : X86_XCPT_UD; + uint64_t uExpectRbx, uExpectRip; + RTCCUINTXREG uMemSrc, uMemSrcExpect; + Ctx.rbx.uCcXReg = RTCCUINTXREG_MAX * 1019; + if (!s_aTests[i].fMemSrc) + { + Ctx.rdx.uCcXReg = s_aTests[i].uIn; + uMemSrcExpect = uMemSrc = ~s_aTests[i].uIn; + } + else + { + Ctx.rdx.uCcXReg = ~s_aTests[i].uIn; + uMemSrcExpect = uMemSrc = s_aTests[i].uIn; + Bs3RegCtxSetGrpDsFromCurPtr(&Ctx, &Ctx.rdi, &uMemSrc); + } + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, s_aTests[i].pfnWorker); + uExpectRbx = fOkay ? s_aTests[i].uOut : Ctx.rbx.u; + uExpectRip = Ctx.rip.u + (fOkay ? 6 + 1 : 0); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); + + if ( TrapFrame.bXcpt != bExpectXcpt + || TrapFrame.Ctx.rip.u != uExpectRip + || TrapFrame.Ctx.rdx.u != Ctx.rdx.u + || TrapFrame.Ctx.rbx.u != uExpectRbx + /* check that nothing else really changed: */ + || (TrapFrame.Ctx.rflags.u16 & X86_EFL_STATUS_BITS) != (Ctx.rflags.u16 & X86_EFL_STATUS_BITS) + || TrapFrame.Ctx.rax.u != Ctx.rax.u + || TrapFrame.Ctx.rcx.u != Ctx.rcx.u + || TrapFrame.Ctx.rsp.u != Ctx.rsp.u + || TrapFrame.Ctx.rbp.u != Ctx.rbp.u + || TrapFrame.Ctx.rsi.u != Ctx.rsi.u + || TrapFrame.Ctx.rdi.u != Ctx.rdi.u + || uMemSrc != uMemSrcExpect + ) + { + Bs3TestFailedF("test #%i failed: input %#" RTCCUINTXREG_XFMT, i, s_aTests[i].uIn); + if (TrapFrame.bXcpt != bExpectXcpt) + Bs3TestFailedF("Expected bXcpt = %#x, got %#x", bExpectXcpt, TrapFrame.bXcpt); + if (TrapFrame.Ctx.rip.u != uExpectRip) + Bs3TestFailedF("Expected RIP = %#06RX64, got %#06RX64", uExpectRip, TrapFrame.Ctx.rip.u); + if (TrapFrame.Ctx.rdx.u != Ctx.rdx.u) + Bs3TestFailedF("Expected RDX = %#06RX64, got %#06RX64 (src)", Ctx.rdx.u, TrapFrame.Ctx.rdx.u); + if (TrapFrame.Ctx.rbx.u != uExpectRbx) + Bs3TestFailedF("Expected RBX = %#06RX64, got %#06RX64 (dst)", uExpectRbx, TrapFrame.Ctx.rbx.u); + + if ((TrapFrame.Ctx.rflags.u16 & X86_EFL_STATUS_BITS) != (Ctx.rflags.u16 & X86_EFL_STATUS_BITS)) + Bs3TestFailedF("Expected EFLAGS = %#06RX64, got %#06RX64", + Ctx.rflags.u16 & X86_EFL_STATUS_BITS, TrapFrame.Ctx.rflags.u16 & X86_EFL_STATUS_BITS); + if (TrapFrame.Ctx.rax.u != Ctx.rax.u) + Bs3TestFailedF("Expected RAX = %#06RX64, got %#06RX64", Ctx.rax.u, TrapFrame.Ctx.rax.u); + if (TrapFrame.Ctx.rcx.u != Ctx.rcx.u) + Bs3TestFailedF("Expected RCX = %#06RX64, got %#06RX64", Ctx.rcx.u, TrapFrame.Ctx.rcx.u); + if (TrapFrame.Ctx.rsp.u != Ctx.rsp.u) + Bs3TestFailedF("Expected RSP = %#06RX64, got %#06RX64", Ctx.rsp.u, TrapFrame.Ctx.rsp.u); + if (TrapFrame.Ctx.rbp.u != Ctx.rbp.u) + Bs3TestFailedF("Expected RBP = %#06RX64, got %#06RX64", Ctx.rbp.u, TrapFrame.Ctx.rbp.u); + if (TrapFrame.Ctx.rsi.u != Ctx.rsi.u) + Bs3TestFailedF("Expected RSI = %#06RX64, got %#06RX64", Ctx.rsi.u, TrapFrame.Ctx.rsi.u); + if (TrapFrame.Ctx.rdi.u != Ctx.rdi.u) + Bs3TestFailedF("Expected RDI = %#06RX64, got %#06RX64", Ctx.rdi.u, TrapFrame.Ctx.rdi.u); + if (uMemSrc != uMemSrcExpect) + Bs3TestFailedF("Expected uMemSrc = %#06RX64, got %#06RX64", (uint64_t)uMemSrcExpect, (uint64_t)uMemSrc); + } + } + Ctx.rflags.u16 &= ~X86_EFL_STATUS_BITS; + } + + return 0; +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_andn)(uint8_t bMode) +{ +#define ANDN_CHECK_EFLAGS (uint16_t)(X86_EFL_CF | X86_EFL_ZF | X86_EFL_OF | X86_EFL_SF) +#define ANDN_IGNORE_EFLAGS (uint16_t)(X86_EFL_AF | X86_EFL_PF) /* undefined, ignoring for now */ + static const struct + { + FPFNBS3FAR pfnWorker; + bool fMemSrc; + uint8_t cbInstr; + RTCCUINTXREG uSrc1; + RTCCUINTXREG uSrc2; + RTCCUINTXREG uOut; + uint16_t fEFlags; + } s_aTests[] = + { + /* 64 bits register width (32 bits in 32- and 16-bit modes): */ + { BS3_CMN_NM(bs3CpuInstr2_andn_RAX_RCX_RBX_icebp), false, 5, // #0 + 0, 0, /* -> */ 0, X86_EFL_ZF }, + { BS3_CMN_NM(bs3CpuInstr2_andn_RAX_RCX_RBX_icebp), false, 5, // #1 + 2, ~(RTCCUINTXREG)3, /* -> */ ~(RTCCUINTXREG)3, X86_EFL_SF }, + { BS3_CMN_NM(bs3CpuInstr2_andn_RAX_RCX_FSxBX_icebp), true, 6, // #2 + 0, 0, /* -> */ 0, X86_EFL_ZF }, + { BS3_CMN_NM(bs3CpuInstr2_andn_RAX_RCX_FSxBX_icebp), true, 6, // #3 + 2, ~(RTCCUINTXREG)3, /* -> */ ~(RTCCUINTXREG)3, X86_EFL_SF }, + + /* 32-bit register width */ + { BS3_CMN_NM(bs3CpuInstr2_andn_EAX_ECX_EBX_icebp), false, 5, // #4 + 0, 0, /* -> */ 0, X86_EFL_ZF }, + { BS3_CMN_NM(bs3CpuInstr2_andn_EAX_ECX_EBX_icebp), false, 5, // #5 + 2, ~(RTCCUINTXREG)7, /* -> */ ~(uint32_t)7, X86_EFL_SF }, + { BS3_CMN_NM(bs3CpuInstr2_andn_EAX_ECX_FSxBX_icebp), true, 6, // #6 + 0, 0, /* -> */ 0, X86_EFL_ZF }, + { BS3_CMN_NM(bs3CpuInstr2_andn_EAX_ECX_FSxBX_icebp), true, 6, // #7 + 2, ~(RTCCUINTXREG)7, /* -> */ ~(uint32_t)7, X86_EFL_SF }, + + }; + + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + unsigned i, j; + uint32_t uStdExtFeatEbx = 0; + bool fSupportsAndN; + + if (g_uBs3CpuDetected & BS3CPU_F_CPUID) + ASMCpuIdExSlow(7, 0, 0, 0, NULL, &uStdExtFeatEbx, NULL, NULL); + fSupportsAndN = RT_BOOL(uStdExtFeatEbx & X86_CPUID_STEXT_FEATURE_EBX_BMI1); + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 512); + + /* + * Do the tests twice, first with all flags set, then once again with + * flags cleared. The flags are not supposed to be touched at all. + */ + Ctx.rflags.u16 |= X86_EFL_STATUS_BITS; + for (j = 0; j < 2; j++) + { + for (i = 0; i < RT_ELEMENTS(s_aTests); i++) + { + bool const fOkay = !BS3_MODE_IS_RM_OR_V86(bMode) && fSupportsAndN; + uint8_t const bExpectXcpt = fOkay ? X86_XCPT_DB : X86_XCPT_UD; + uint64_t uExpectRax, uExpectRip; + RTCCUINTXREG uMemSrc2, uMemSrc2Expect; + + Ctx.rax.uCcXReg = RTCCUINTXREG_MAX * 1019; + Ctx.rcx.uCcXReg = s_aTests[i].uSrc1; + if (!s_aTests[i].fMemSrc) + { + Ctx.rbx.uCcXReg = s_aTests[i].uSrc2; + uMemSrc2Expect = uMemSrc2 = ~s_aTests[i].uSrc2; + } + else + { + uMemSrc2Expect = uMemSrc2 = s_aTests[i].uSrc2; + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, &Ctx.fs, &uMemSrc2); + } + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, s_aTests[i].pfnWorker); + uExpectRax = fOkay ? s_aTests[i].uOut : Ctx.rax.u; + uExpectRip = Ctx.rip.u + (fOkay ? s_aTests[i].cbInstr + 1 : 0); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); + + if ( TrapFrame.bXcpt != bExpectXcpt + || TrapFrame.Ctx.rip.u != uExpectRip + || TrapFrame.Ctx.rcx.u != Ctx.rcx.u + || TrapFrame.Ctx.rbx.u != Ctx.rbx.u + || TrapFrame.Ctx.rax.u != uExpectRax + /* check that nothing else really changed: */ + || (TrapFrame.Ctx.rflags.u16 & ANDN_CHECK_EFLAGS) + != ((fOkay ? s_aTests[i].fEFlags : Ctx.rflags.u16) & ANDN_CHECK_EFLAGS) + || (TrapFrame.Ctx.rflags.u16 & ~(ANDN_CHECK_EFLAGS | ANDN_IGNORE_EFLAGS) & X86_EFL_STATUS_BITS) + != (Ctx.rflags.u16 & ~(ANDN_CHECK_EFLAGS | ANDN_IGNORE_EFLAGS) & X86_EFL_STATUS_BITS) + || TrapFrame.Ctx.rdx.u != Ctx.rdx.u + || TrapFrame.Ctx.rsp.u != Ctx.rsp.u + || TrapFrame.Ctx.rbp.u != Ctx.rbp.u + || TrapFrame.Ctx.rsi.u != Ctx.rsi.u + || TrapFrame.Ctx.rdi.u != Ctx.rdi.u + || uMemSrc2 != uMemSrc2Expect + ) + { + Bs3TestFailedF("test #%i failed: input %#" RTCCUINTXREG_XFMT ", %#" RTCCUINTXREG_XFMT, i, s_aTests[i].uSrc1, s_aTests[i].uSrc2); + if (TrapFrame.bXcpt != bExpectXcpt) + Bs3TestFailedF("Expected bXcpt = %#x, got %#x", bExpectXcpt, TrapFrame.bXcpt); + if (TrapFrame.Ctx.rip.u != uExpectRip) + Bs3TestFailedF("Expected RIP = %#06RX64, got %#06RX64", uExpectRip, TrapFrame.Ctx.rip.u); + if (TrapFrame.Ctx.rax.u != uExpectRax) + Bs3TestFailedF("Expected RAX = %#06RX64, got %#06RX64", uExpectRax, TrapFrame.Ctx.rax.u); + if (TrapFrame.Ctx.rcx.u != Ctx.rcx.u) + Bs3TestFailedF("Expected RCX = %#06RX64, got %#06RX64", Ctx.rcx.u, TrapFrame.Ctx.rcx.u); + if (TrapFrame.Ctx.rbx.u != Ctx.rbx.u) + Bs3TestFailedF("Expected RBX = %#06RX64, got %#06RX64 (dst)", Ctx.rbx.u, TrapFrame.Ctx.rbx.u); + if ( (TrapFrame.Ctx.rflags.u16 & ANDN_CHECK_EFLAGS) + != ((fOkay ? s_aTests[i].fEFlags : Ctx.rflags.u16) & ANDN_CHECK_EFLAGS)) + Bs3TestFailedF("Expected EFLAGS = %#06RX32, got %#06RX32 (output)", + (fOkay ? s_aTests[i].fEFlags : Ctx.rflags.u16) & ANDN_CHECK_EFLAGS, TrapFrame.Ctx.rflags.u16 & ANDN_CHECK_EFLAGS); + if ( (TrapFrame.Ctx.rflags.u16 & ~(ANDN_CHECK_EFLAGS | ANDN_IGNORE_EFLAGS) & X86_EFL_STATUS_BITS) + != (Ctx.rflags.u16 & ~(ANDN_CHECK_EFLAGS | ANDN_IGNORE_EFLAGS) & X86_EFL_STATUS_BITS)) + Bs3TestFailedF("Expected EFLAGS = %#06RX32, got %#06RX32 (immutable)", + Ctx.rflags.u16 & ~(ANDN_CHECK_EFLAGS | ANDN_IGNORE_EFLAGS) & X86_EFL_STATUS_BITS, + TrapFrame.Ctx.rflags.u16 & ~(ANDN_CHECK_EFLAGS | ANDN_IGNORE_EFLAGS) & X86_EFL_STATUS_BITS); + + if (TrapFrame.Ctx.rdx.u != Ctx.rdx.u) + Bs3TestFailedF("Expected RDX = %#06RX64, got %#06RX64 (src)", Ctx.rdx.u, TrapFrame.Ctx.rdx.u); + if (TrapFrame.Ctx.rsp.u != Ctx.rsp.u) + Bs3TestFailedF("Expected RSP = %#06RX64, got %#06RX64", Ctx.rsp.u, TrapFrame.Ctx.rsp.u); + if (TrapFrame.Ctx.rbp.u != Ctx.rbp.u) + Bs3TestFailedF("Expected RBP = %#06RX64, got %#06RX64", Ctx.rbp.u, TrapFrame.Ctx.rbp.u); + if (TrapFrame.Ctx.rsi.u != Ctx.rsi.u) + Bs3TestFailedF("Expected RSI = %#06RX64, got %#06RX64", Ctx.rsi.u, TrapFrame.Ctx.rsi.u); + if (TrapFrame.Ctx.rdi.u != Ctx.rdi.u) + Bs3TestFailedF("Expected RDI = %#06RX64, got %#06RX64", Ctx.rdi.u, TrapFrame.Ctx.rdi.u); + if (uMemSrc2 != uMemSrc2Expect) + Bs3TestFailedF("Expected uMemSrc2 = %#06RX64, got %#06RX64", (uint64_t)uMemSrc2Expect, (uint64_t)uMemSrc2); + } + } + Ctx.rflags.u16 &= ~X86_EFL_STATUS_BITS; + } + + return 0; +} + +/* + * For testing BEXTR, SHLX SARX & SHRX. + */ +typedef struct BS3CPUINSTR2_SUBTEST_Gy_Ey_By_T +{ + RTCCUINTXREG uSrc1; + RTCCUINTXREG uSrc2; + RTCCUINTXREG uOut; + uint16_t fEflOut; +} BS3CPUINSTR2_SUBTEST_Gy_Ey_By_T; + +typedef struct BS3CPUINSTR2_TEST_Gy_Ey_By_T +{ + FPFNBS3FAR pfnWorker; + bool fMemSrc; + uint8_t cbInstr; + uint8_t cSubTests; + BS3CPUINSTR2_SUBTEST_Gy_Ey_By_T const *paSubTests; +} BS3CPUINSTR2_TEST_Gy_Ey_By_T; + +static uint8_t bs3CpuInstr2_Common_Gy_Ey_By(uint8_t bMode, BS3CPUINSTR2_TEST_Gy_Ey_By_T const *paTests, unsigned cTests, + uint32_t fStdExtFeatEbx, uint16_t fEflCheck, uint16_t fEflIgnore) +{ + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + unsigned i, j, k; + uint32_t uStdExtFeatEbx = 0; + bool fSupportsInstr; + + fEflCheck &= ~fEflIgnore; + + if (g_uBs3CpuDetected & BS3CPU_F_CPUID) + ASMCpuIdExSlow(7, 0, 0, 0, NULL, &uStdExtFeatEbx, NULL, NULL); + fSupportsInstr = RT_BOOL(uStdExtFeatEbx & fStdExtFeatEbx); + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 512); + + /* + * Do the tests twice, first with all flags set, then once again with + * flags cleared. The flags are not supposed to be touched at all. + */ + Ctx.rflags.u16 |= X86_EFL_STATUS_BITS; + for (j = 0; j < 2; j++) + { + for (i = 0; i < cTests; i++) + { + for (k = 0; k < paTests[i].cSubTests; k++) + { + bool const fOkay = !BS3_MODE_IS_RM_OR_V86(bMode) && fSupportsInstr; + uint8_t const bExpectXcpt = fOkay ? X86_XCPT_DB : X86_XCPT_UD; + uint64_t uExpectRax, uExpectRip; + RTCCUINTXREG uMemSrc1, uMemSrc1Expect; + + Ctx.rax.uCcXReg = RTCCUINTXREG_MAX * 1019; + Ctx.rcx.uCcXReg = paTests[i].paSubTests[k].uSrc2; + if (!paTests[i].fMemSrc) + { + Ctx.rbx.uCcXReg = paTests[i].paSubTests[k].uSrc1; + uMemSrc1Expect = uMemSrc1 = ~paTests[i].paSubTests[k].uSrc1; + } + else + { + uMemSrc1Expect = uMemSrc1 = paTests[i].paSubTests[k].uSrc1; + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, &Ctx.fs, &uMemSrc1); + } + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, paTests[i].pfnWorker); + uExpectRax = fOkay ? paTests[i].paSubTests[k].uOut : Ctx.rax.u; + uExpectRip = Ctx.rip.u + (fOkay ? paTests[i].cbInstr + 1 : 0); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); + + if ( TrapFrame.bXcpt != bExpectXcpt + || TrapFrame.Ctx.rip.u != uExpectRip + || TrapFrame.Ctx.rcx.u != Ctx.rcx.u + || TrapFrame.Ctx.rbx.u != Ctx.rbx.u + || TrapFrame.Ctx.rax.u != uExpectRax + /* check that nothing else really changed: */ + || (TrapFrame.Ctx.rflags.u16 & fEflCheck) + != ((fOkay ? paTests[i].paSubTests[k].fEflOut : Ctx.rflags.u16) & fEflCheck) + || (TrapFrame.Ctx.rflags.u16 & ~(fEflCheck | fEflIgnore) & X86_EFL_STATUS_BITS) + != (Ctx.rflags.u16 & ~(fEflCheck | fEflIgnore) & X86_EFL_STATUS_BITS) + || TrapFrame.Ctx.rdx.u != Ctx.rdx.u + || TrapFrame.Ctx.rsp.u != Ctx.rsp.u + || TrapFrame.Ctx.rbp.u != Ctx.rbp.u + || TrapFrame.Ctx.rsi.u != Ctx.rsi.u + || TrapFrame.Ctx.rdi.u != Ctx.rdi.u + || uMemSrc1 != uMemSrc1Expect + ) + { + Bs3TestFailedF("test #%i/%i failed: input %#" RTCCUINTXREG_XFMT ", %#" RTCCUINTXREG_XFMT, + i, k, paTests[i].paSubTests[k].uSrc1, paTests[i].paSubTests[k].uSrc2); + if (TrapFrame.bXcpt != bExpectXcpt) + Bs3TestFailedF("Expected bXcpt = %#x, got %#x", bExpectXcpt, TrapFrame.bXcpt); + if (TrapFrame.Ctx.rip.u != uExpectRip) + Bs3TestFailedF("Expected RIP = %#06RX64, got %#06RX64", uExpectRip, TrapFrame.Ctx.rip.u); + if (TrapFrame.Ctx.rax.u != uExpectRax) + Bs3TestFailedF("Expected RAX = %#06RX64, got %#06RX64", uExpectRax, TrapFrame.Ctx.rax.u); + if (TrapFrame.Ctx.rcx.u != Ctx.rcx.u) + Bs3TestFailedF("Expected RCX = %#06RX64, got %#06RX64", Ctx.rcx.u, TrapFrame.Ctx.rcx.u); + if (TrapFrame.Ctx.rbx.u != Ctx.rbx.u) + Bs3TestFailedF("Expected RBX = %#06RX64, got %#06RX64", Ctx.rbx.u, TrapFrame.Ctx.rbx.u); + if ( (TrapFrame.Ctx.rflags.u16 & fEflCheck) + != ((fOkay ? paTests[i].paSubTests[k].fEflOut : Ctx.rflags.u16) & fEflCheck)) + Bs3TestFailedF("Expected EFLAGS = %#06RX32, got %#06RX32 (output)", + (fOkay ? paTests[i].paSubTests[k].fEflOut : Ctx.rflags.u16) & fEflCheck, + TrapFrame.Ctx.rflags.u16 & fEflCheck); + if ( (TrapFrame.Ctx.rflags.u16 & ~(fEflCheck | fEflIgnore) & X86_EFL_STATUS_BITS) + != (Ctx.rflags.u16 & ~(fEflCheck | fEflIgnore) & X86_EFL_STATUS_BITS)) + Bs3TestFailedF("Expected EFLAGS = %#06RX32, got %#06RX32 (immutable)", + Ctx.rflags.u16 & ~(fEflCheck | fEflIgnore) & X86_EFL_STATUS_BITS, + TrapFrame.Ctx.rflags.u16 & ~(fEflCheck | fEflIgnore) & X86_EFL_STATUS_BITS); + + if (TrapFrame.Ctx.rdx.u != Ctx.rdx.u) + Bs3TestFailedF("Expected RDX = %#06RX64, got %#06RX64", Ctx.rdx.u, TrapFrame.Ctx.rdx.u); + if (TrapFrame.Ctx.rsp.u != Ctx.rsp.u) + Bs3TestFailedF("Expected RSP = %#06RX64, got %#06RX64", Ctx.rsp.u, TrapFrame.Ctx.rsp.u); + if (TrapFrame.Ctx.rbp.u != Ctx.rbp.u) + Bs3TestFailedF("Expected RBP = %#06RX64, got %#06RX64", Ctx.rbp.u, TrapFrame.Ctx.rbp.u); + if (TrapFrame.Ctx.rsi.u != Ctx.rsi.u) + Bs3TestFailedF("Expected RSI = %#06RX64, got %#06RX64", Ctx.rsi.u, TrapFrame.Ctx.rsi.u); + if (TrapFrame.Ctx.rdi.u != Ctx.rdi.u) + Bs3TestFailedF("Expected RDI = %#06RX64, got %#06RX64", Ctx.rdi.u, TrapFrame.Ctx.rdi.u); + if (uMemSrc1 != uMemSrc1Expect) + Bs3TestFailedF("Expected uMemSrc1 = %#06RX64, got %#06RX64", (uint64_t)uMemSrc1Expect, (uint64_t)uMemSrc1); + } + } + } + Ctx.rflags.u16 &= ~X86_EFL_STATUS_BITS; + } + + return 0; +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_bextr)(uint8_t bMode) +{ + /* 64 bits register width (32 bits in 32- and 16-bit modes): */ + static BS3CPUINSTR2_SUBTEST_Gy_Ey_By_T const s_aSubTests64[] = + { + { 0, RT_MAKE_U16(0, 0), /* -> */ 0, X86_EFL_ZF }, + { 0, RT_MAKE_U16(16, 33), /* -> */ 0, X86_EFL_ZF }, + { ~(RTCCUINTXREG)7, RT_MAKE_U16(2, 4), /* -> */ 0xe, 0}, + { ~(RTCCUINTXREG)7, RT_MAKE_U16(40, 8), /* -> */ ARCH_BITS == 64 ? 0xff : 0x00, ARCH_BITS == 64 ? 0 : X86_EFL_ZF }, + }; + + /* 32-bit register width */ + static BS3CPUINSTR2_SUBTEST_Gy_Ey_By_T const s_aSubTests32[] = + { + { 0, RT_MAKE_U16(0, 0), /* -> */ 0, X86_EFL_ZF }, + { 0, RT_MAKE_U16(16, 18), /* -> */ 0, X86_EFL_ZF }, + { ~(RTCCUINTXREG)7, RT_MAKE_U16(2, 4), /* -> */ 0xe, 0 }, + { ~(RTCCUINTXREG)7, RT_MAKE_U16(24, 8), /* -> */ 0xff, 0 }, + { ~(RTCCUINTXREG)7, RT_MAKE_U16(31, 9), /* -> */ 1, 0 }, + { ~(RTCCUINTXREG)7, RT_MAKE_U16(42, 8), /* -> */ 0, X86_EFL_ZF }, + }; + + static BS3CPUINSTR2_TEST_Gy_Ey_By_T const s_aTests[] = + { + { BS3_CMN_NM(bs3CpuInstr2_bextr_RAX_RBX_RCX_icebp), false, 5, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_bextr_RAX_FSxBX_RCX_icebp), true, 6, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_bextr_EAX_EBX_ECX_icebp), false, 5, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + { BS3_CMN_NM(bs3CpuInstr2_bextr_EAX_FSxBX_ECX_icebp), true, 6, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + }; + return bs3CpuInstr2_Common_Gy_Ey_By(bMode, s_aTests, RT_ELEMENTS(s_aTests), X86_CPUID_STEXT_FEATURE_EBX_BMI1, + X86_EFL_STATUS_BITS, X86_EFL_AF | X86_EFL_SF | X86_EFL_PF); +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_bzhi)(uint8_t bMode) +{ + /* 64 bits register width (32 bits in 32- and 16-bit modes): */ + static BS3CPUINSTR2_SUBTEST_Gy_Ey_By_T const s_aSubTests64[] = + { + { 0, 0, /* -> */ 0, X86_EFL_ZF }, + { 0, ~(RTCCUINTXREG)255, /* -> */ 0, X86_EFL_ZF }, + { 0, 64, /* -> */ 0, X86_EFL_ZF | X86_EFL_CF }, + { ~(RTCCUINTXREG)0, 64, /* -> */ ~(RTCCUINTXREG)0, X86_EFL_CF | X86_EFL_SF }, + { ~(RTCCUINTXREG)0, 63, + /* -> */ ARCH_BITS >= 64 ? ~(RTCCUINTXREG)0 >> 1 : ~(RTCCUINTXREG)0, ARCH_BITS >= 64 ? 0 : X86_EFL_CF | X86_EFL_SF }, + { ~(RTCCUINTXREG)0 << 31 | UINT32_C(0x63849607), 24, /* -> */ UINT32_C(0x00849607), 0 }, + { ~(RTCCUINTXREG)0 << 31 | UINT32_C(0x63849607), 33, + /* -> */ ARCH_BITS >= 64 ? UINT64_C(0x1e3849607) : UINT32_C(0xe3849607), ARCH_BITS >= 64 ? 0 : X86_EFL_CF | X86_EFL_SF }, + }; + + /* 32-bit register width */ + static BS3CPUINSTR2_SUBTEST_Gy_Ey_By_T const s_aSubTests32[] = + { + { 0, 0, /* -> */ 0, X86_EFL_ZF }, + { 0, ~(RTCCUINTXREG)255, /* -> */ 0, X86_EFL_ZF }, + { 0, 32, /* -> */ 0, X86_EFL_ZF | X86_EFL_CF }, + { ~(RTCCUINTXREG)0, 32, /* -> */ UINT32_MAX, X86_EFL_CF | X86_EFL_SF }, + { ~(RTCCUINTXREG)0, 31, /* -> */ UINT32_MAX >> 1, 0 }, + { UINT32_C(0x1230fd34), 15, /* -> */ UINT32_C(0x00007d34), 0 }, + }; + + static BS3CPUINSTR2_TEST_Gy_Ey_By_T const s_aTests[] = + { + { BS3_CMN_NM(bs3CpuInstr2_bzhi_RAX_RBX_RCX_icebp), false, 5, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_bzhi_RAX_FSxBX_RCX_icebp), true, 6, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_bzhi_EAX_EBX_ECX_icebp), false, 5, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + { BS3_CMN_NM(bs3CpuInstr2_bzhi_EAX_FSxBX_ECX_icebp), true, 6, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + }; + return bs3CpuInstr2_Common_Gy_Ey_By(bMode, s_aTests, RT_ELEMENTS(s_aTests), X86_CPUID_STEXT_FEATURE_EBX_BMI2, + X86_EFL_STATUS_BITS, 0); +} + + +/** @note This is a Gy_By_Ey format instruction, so we're switching the two + * source registers around when calling bs3CpuInstr2_Common_Gy_Ey_By. + * Sorry for the confusion, but it saves some unnecessary code dup. */ +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_pdep)(uint8_t bMode) +{ + /* 64 bits register width (32 bits in 32- and 16-bit modes): */ + static BS3CPUINSTR2_SUBTEST_Gy_Ey_By_T const s_aSubTests64[] = + { /* Mask (RBX/[FS:xBX]), source=RCX */ + { 0, 0, /* -> */ 0, 0 }, + { 0, ~(RTCCUINTXREG)0, /* -> */ 0, 0 }, + { ~(RTCCUINTXREG)0, 0, /* -> */ 0, 0 }, + { ~(RTCCUINTXREG)0, ~(RTCCUINTXREG)0, /* -> */ ~(RTCCUINTXREG)0, 0 }, +#if ARCH_BITS >= 64 + { UINT64_C(0x3586049947589201), ~(RTCCUINTXREG)0, /* -> */ UINT64_C(0x3586049947589201), 0 }, + { UINT64_C(0x3586049947589201), ~(RTCCUINTXREG)7, /* -> */ UINT64_C(0x3586049947588000), 0 }, +#endif + { UINT32_C(0x47589201), ~(RTCCUINTXREG)0, /* -> */ UINT32_C(0x47589201), 0 }, + { UINT32_C(0x47589201), ~(RTCCUINTXREG)7, /* -> */ UINT32_C(0x47588000), 0 }, + }; + + /* 32-bit register width */ + static BS3CPUINSTR2_SUBTEST_Gy_Ey_By_T const s_aSubTests32[] = + { /* Mask (EBX/[FS:xBX]), source=ECX */ + { 0, 0, /* -> */ 0, 0 }, + { 0, ~(RTCCUINTXREG)0, /* -> */ 0, 0 }, + { ~(RTCCUINTXREG)0, 0, /* -> */ 0, 0 }, + { ~(RTCCUINTXREG)0, ~(RTCCUINTXREG)0, /* -> */ UINT32_MAX, 0 }, + { UINT32_C(0x01010101), ~(RTCCUINTXREG)0, /* -> */ UINT32_C(0x01010101), 0 }, + { UINT32_C(0x01010101), ~(RTCCUINTXREG)3, /* -> */ UINT32_C(0x01010000), 0 }, + { UINT32_C(0x47589201), ~(RTCCUINTXREG)0, /* -> */ UINT32_C(0x47589201), 0 }, + }; + + static BS3CPUINSTR2_TEST_Gy_Ey_By_T const s_aTests[] = + { + { BS3_CMN_NM(bs3CpuInstr2_pdep_RAX_RCX_RBX_icebp), false, 5, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_pdep_RAX_RCX_FSxBX_icebp), true, 6, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_pdep_EAX_ECX_EBX_icebp), false, 5, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + { BS3_CMN_NM(bs3CpuInstr2_pdep_EAX_ECX_FSxBX_icebp), true, 6, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + }; + return bs3CpuInstr2_Common_Gy_Ey_By(bMode, s_aTests, RT_ELEMENTS(s_aTests), X86_CPUID_STEXT_FEATURE_EBX_BMI2, 0, 0); +} + + +/** @note Same note as for bs3CpuInstr2_pdep */ +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_pext)(uint8_t bMode) +{ + /* 64 bits register width (32 bits in 32- and 16-bit modes): */ + static BS3CPUINSTR2_SUBTEST_Gy_Ey_By_T const s_aSubTests64[] = + { /* Mask (RBX/[FS:xBX]), source=RCX */ + { 0, 0, /* -> */ 0, 0 }, + { 0, ~(RTCCUINTXREG)0, /* -> */ 0, 0 }, + { ~(RTCCUINTXREG)0, 0, /* -> */ 0, 0 }, + { ~(RTCCUINTXREG)0, ~(RTCCUINTXREG)0, /* -> */ ~(RTCCUINTXREG)0, 0 }, +#if ARCH_BITS >= 64 + { UINT64_C(0x3586049947589201), ~(RTCCUINTXREG)0, /* -> */ UINT64_C(0x00000000007fffff), 0 }, + { UINT64_C(0x3586049947589201), ~(RTCCUINTXREG)7, /* -> */ UINT64_C(0x00000000007ffffe), 0 }, +#endif + { UINT32_C(0x47589201), ~(RTCCUINTXREG)0, /* -> */ UINT32_C(0x000007ff), 0 }, + { UINT32_C(0x47589201), ~(RTCCUINTXREG)7, /* -> */ UINT32_C(0x000007fe), 0 }, + }; + + /* 32-bit register width */ + static BS3CPUINSTR2_SUBTEST_Gy_Ey_By_T const s_aSubTests32[] = + { /* Mask (EBX/[FS:xBX]), source=ECX */ + { 0, 0, /* -> */ 0, 0 }, + { 0, ~(RTCCUINTXREG)0, /* -> */ 0, 0 }, + { ~(RTCCUINTXREG)0, 0, /* -> */ 0, 0 }, + { ~(RTCCUINTXREG)0, ~(RTCCUINTXREG)0, /* -> */ UINT32_MAX, 0 }, + { UINT32_C(0x01010101), ~(RTCCUINTXREG)0, /* -> */ UINT32_C(0x0000000f), 0 }, + { UINT32_C(0x01010101), ~(RTCCUINTXREG)3, /* -> */ UINT32_C(0x0000000e), 0 }, + { UINT32_C(0x47589201), ~(RTCCUINTXREG)0, /* -> */ UINT32_C(0x000007ff), 0 }, + { UINT32_C(0x47589201), ~(RTCCUINTXREG)7, /* -> */ UINT32_C(0x000007fe), 0 }, + }; + + static BS3CPUINSTR2_TEST_Gy_Ey_By_T const s_aTests[] = + { + { BS3_CMN_NM(bs3CpuInstr2_pext_RAX_RCX_RBX_icebp), false, 5, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_pext_RAX_RCX_FSxBX_icebp), true, 6, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_pext_EAX_ECX_EBX_icebp), false, 5, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + { BS3_CMN_NM(bs3CpuInstr2_pext_EAX_ECX_FSxBX_icebp), true, 6, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + }; + return bs3CpuInstr2_Common_Gy_Ey_By(bMode, s_aTests, RT_ELEMENTS(s_aTests), X86_CPUID_STEXT_FEATURE_EBX_BMI2, 0, 0); +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_shlx)(uint8_t bMode) +{ + /* 64 bits register width (32 bits in 32- and 16-bit modes): */ + static BS3CPUINSTR2_SUBTEST_Gy_Ey_By_T const s_aSubTests64[] = + { + { 0, 0, /* -> */ 0, 0 }, + { 0, ~(RTCCUINTXREG)3, /* -> */ 0, 0 }, + { ~(RTCCUINTXREG)7, 8, /* -> */ ~(RTCCUINTXREG)0x7ff, 0}, + { ~(RTCCUINTXREG)7, 40, /* -> */ ~(RTCCUINTXREG)7 << (ARCH_BITS == 64 ? 40 : 8), 0 }, + { ~(RTCCUINTXREG)7, 72, /* -> */ ~(RTCCUINTXREG)7 << 8, 0 }, + }; + + /* 32-bit register width */ + static BS3CPUINSTR2_SUBTEST_Gy_Ey_By_T const s_aSubTests32[] = + { + { 0, 0, /* -> */ 0, 0 }, + { 0, ~(RTCCUINTXREG)9, /* -> */ 0, 0 }, + { ~(RTCCUINTXREG)7, 8, /* -> */ UINT32_C(0xfffff800), 0 }, + { ~(RTCCUINTXREG)7, 8, /* -> */ UINT32_C(0xfffff800), 0 }, + }; + + static BS3CPUINSTR2_TEST_Gy_Ey_By_T const s_aTests[] = + { + { BS3_CMN_NM(bs3CpuInstr2_shlx_RAX_RBX_RCX_icebp), false, 5, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_shlx_RAX_FSxBX_RCX_icebp), true, 6, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_shlx_EAX_EBX_ECX_icebp), false, 5, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + { BS3_CMN_NM(bs3CpuInstr2_shlx_EAX_FSxBX_ECX_icebp), true, 6, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + }; + return bs3CpuInstr2_Common_Gy_Ey_By(bMode, s_aTests, RT_ELEMENTS(s_aTests), X86_CPUID_STEXT_FEATURE_EBX_BMI1, + 0, 0); +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_sarx)(uint8_t bMode) +{ + /* 64 bits register width (32 bits in 32- and 16-bit modes): */ + static BS3CPUINSTR2_SUBTEST_Gy_Ey_By_T const s_aSubTests64[] = + { + { 0, 0, /* -> */ 0, 0 }, + { 0, ~(RTCCUINTXREG)3, /* -> */ 0, 0 }, + { (RTCCUINTXREG)1 << (RTCCINTXREG_BITS - 1), RTCCINTXREG_BITS - 1, /* -> */ ~(RTCCUINTXREG)0, 0 }, + { (RTCCUINTXREG)1 << (RTCCINTXREG_BITS - 1), RTCCINTXREG_BITS - 1 + 64, /* -> */ ~(RTCCUINTXREG)0, 0 }, + { (RTCCUINTXREG)1 << (RTCCINTXREG_BITS - 2), RTCCINTXREG_BITS - 3, /* -> */ 2, 0 }, + { (RTCCUINTXREG)1 << (RTCCINTXREG_BITS - 2), RTCCINTXREG_BITS - 3 + 64, /* -> */ 2, 0 }, + }; + + /* 32-bit register width */ + static BS3CPUINSTR2_SUBTEST_Gy_Ey_By_T const s_aSubTests32[] = + { + { 0, 0, /* -> */ 0, 0 }, + { 0, ~(RTCCUINTXREG)9, /* -> */ 0, 0 }, + { ~(RTCCUINTXREG)UINT32_C(0x7fffffff), 24, /* -> */ UINT32_C(0xffffff80), 0 }, + { ~(RTCCUINTXREG)UINT32_C(0x7fffffff), 24+32, /* -> */ UINT32_C(0xffffff80), 0 }, + { ~(RTCCUINTXREG)UINT32_C(0xbfffffff), 24, /* -> */ UINT32_C(0x40), 0 }, + { ~(RTCCUINTXREG)UINT32_C(0xbfffffff), 24+32, /* -> */ UINT32_C(0x40), 0 }, + }; + + static BS3CPUINSTR2_TEST_Gy_Ey_By_T const s_aTests[] = + { + { BS3_CMN_NM(bs3CpuInstr2_sarx_RAX_RBX_RCX_icebp), false, 5, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_sarx_RAX_FSxBX_RCX_icebp), true, 6, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_sarx_EAX_EBX_ECX_icebp), false, 5, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + { BS3_CMN_NM(bs3CpuInstr2_sarx_EAX_FSxBX_ECX_icebp), true, 6, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + }; + return bs3CpuInstr2_Common_Gy_Ey_By(bMode, s_aTests, RT_ELEMENTS(s_aTests), X86_CPUID_STEXT_FEATURE_EBX_BMI1, + 0, 0); +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_shrx)(uint8_t bMode) +{ + /* 64 bits register width (32 bits in 32- and 16-bit modes): */ + static BS3CPUINSTR2_SUBTEST_Gy_Ey_By_T const s_aSubTests64[] = + { + { 0, 0, /* -> */ 0, 0 }, + { 0, ~(RTCCUINTXREG)3, /* -> */ 0, 0 }, + { (RTCCUINTXREG)1 << (RTCCINTXREG_BITS - 1), RTCCINTXREG_BITS - 1, /* -> */ 1, 0 }, + { (RTCCUINTXREG)1 << (RTCCINTXREG_BITS - 1), RTCCINTXREG_BITS - 1 + 64, /* -> */ 1, 0 }, + { (RTCCUINTXREG)1 << (RTCCINTXREG_BITS - 2), RTCCINTXREG_BITS - 3, /* -> */ 2, 0 }, + { (RTCCUINTXREG)1 << (RTCCINTXREG_BITS - 2), RTCCINTXREG_BITS - 3 + 64, /* -> */ 2, 0 }, + }; + + /* 32-bit register width */ + static BS3CPUINSTR2_SUBTEST_Gy_Ey_By_T const s_aSubTests32[] = + { + { 0, 0, /* -> */ 0, 0 }, + { 0, ~(RTCCUINTXREG)9, /* -> */ 0, 0 }, + { ~(RTCCUINTXREG)UINT32_C(0x7fffffff), 24, /* -> */ UINT32_C(0x80), 0 }, + { ~(RTCCUINTXREG)UINT32_C(0x7fffffff), 24+32, /* -> */ UINT32_C(0x80), 0 }, + { ~(RTCCUINTXREG)UINT32_C(0xbfffffff), 24, /* -> */ UINT32_C(0x40), 0 }, + { ~(RTCCUINTXREG)UINT32_C(0xbfffffff), 24+32, /* -> */ UINT32_C(0x40), 0 }, + }; + + static BS3CPUINSTR2_TEST_Gy_Ey_By_T const s_aTests[] = + { + { BS3_CMN_NM(bs3CpuInstr2_shrx_RAX_RBX_RCX_icebp), false, 5, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_shrx_RAX_FSxBX_RCX_icebp), true, 6, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_shrx_EAX_EBX_ECX_icebp), false, 5, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + { BS3_CMN_NM(bs3CpuInstr2_shrx_EAX_FSxBX_ECX_icebp), true, 6, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + }; + return bs3CpuInstr2_Common_Gy_Ey_By(bMode, s_aTests, RT_ELEMENTS(s_aTests), X86_CPUID_STEXT_FEATURE_EBX_BMI1, + 0, 0); +} + + +/* + * For testing BLSR, BLSMSK, and BLSI. + */ +typedef struct BS3CPUINSTR2_SUBTEST_By_Ey_T +{ + RTCCUINTXREG uSrc; + RTCCUINTXREG uDst; + uint16_t fEflOut; +} BS3CPUINSTR2_SUBTEST_By_Ey_T; + +typedef struct BS3CPUINSTR2_TEST_By_Ey_T +{ + FPFNBS3FAR pfnWorker; + bool fMemSrc; + uint8_t cbInstr; + uint8_t cSubTests; + BS3CPUINSTR2_SUBTEST_By_Ey_T const *paSubTests; +} BS3CPUINSTR2_TEST_By_Ey_T; + +static uint8_t bs3CpuInstr2_Common_By_Ey(uint8_t bMode, BS3CPUINSTR2_TEST_By_Ey_T const *paTests, unsigned cTests, + uint32_t fStdExtFeatEbx, uint16_t fEflCheck, uint16_t fEflIgnore) +{ + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + unsigned i, j, k; + uint32_t uStdExtFeatEbx = 0; + bool fSupportsInstr; + + fEflCheck &= ~fEflIgnore; + + if (g_uBs3CpuDetected & BS3CPU_F_CPUID) + ASMCpuIdExSlow(7, 0, 0, 0, NULL, &uStdExtFeatEbx, NULL, NULL); + fSupportsInstr = RT_BOOL(uStdExtFeatEbx & fStdExtFeatEbx); + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 512); + + /* + * Do the tests twice, first with all flags set, then once again with + * flags cleared. The flags are not supposed to be touched at all. + */ + Ctx.rflags.u16 |= X86_EFL_STATUS_BITS; + for (j = 0; j < 2; j++) + { + for (i = 0; i < cTests; i++) + { + for (k = 0; k < paTests[i].cSubTests; k++) + { + bool const fOkay = !BS3_MODE_IS_RM_OR_V86(bMode) && fSupportsInstr; + uint8_t const bExpectXcpt = fOkay ? X86_XCPT_DB : X86_XCPT_UD; + uint64_t uExpectRax, uExpectRip; + RTCCUINTXREG uMemSrc, uMemSrcExpect; + + Ctx.rax.uCcXReg = ~paTests[i].paSubTests[k].uSrc ^ 0x593e7591; + if (!paTests[i].fMemSrc) + { + Ctx.rbx.uCcXReg = paTests[i].paSubTests[k].uSrc; + uMemSrcExpect = uMemSrc = ~paTests[i].paSubTests[k].uSrc; + } + else + { + uMemSrcExpect = uMemSrc = paTests[i].paSubTests[k].uSrc; + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, &Ctx.fs, &uMemSrc); + } + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, paTests[i].pfnWorker); + uExpectRax = fOkay ? paTests[i].paSubTests[k].uDst : Ctx.rax.u; + uExpectRip = Ctx.rip.u + (fOkay ? paTests[i].cbInstr + 1 : 0); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); + + if ( TrapFrame.bXcpt != bExpectXcpt + || TrapFrame.Ctx.rip.u != uExpectRip + || TrapFrame.Ctx.rbx.u != Ctx.rbx.u + || TrapFrame.Ctx.rax.u != uExpectRax + /* check that nothing else really changed: */ + || (TrapFrame.Ctx.rflags.u16 & fEflCheck) + != ((fOkay ? paTests[i].paSubTests[k].fEflOut : Ctx.rflags.u16) & fEflCheck) + || (TrapFrame.Ctx.rflags.u16 & ~(fEflCheck | fEflIgnore) & X86_EFL_STATUS_BITS) + != (Ctx.rflags.u16 & ~(fEflCheck | fEflIgnore) & X86_EFL_STATUS_BITS) + || TrapFrame.Ctx.rcx.u != Ctx.rcx.u + || TrapFrame.Ctx.rdx.u != Ctx.rdx.u + || TrapFrame.Ctx.rsp.u != Ctx.rsp.u + || TrapFrame.Ctx.rbp.u != Ctx.rbp.u + || TrapFrame.Ctx.rsi.u != Ctx.rsi.u + || TrapFrame.Ctx.rdi.u != Ctx.rdi.u + || uMemSrc != uMemSrcExpect + ) + { + Bs3TestFailedF("test #%i/%i failed: input %#" RTCCUINTXREG_XFMT, + i, k, paTests[i].paSubTests[k].uSrc); + if (TrapFrame.bXcpt != bExpectXcpt) + Bs3TestFailedF("Expected bXcpt = %#x, got %#x", bExpectXcpt, TrapFrame.bXcpt); + if (TrapFrame.Ctx.rip.u != uExpectRip) + Bs3TestFailedF("Expected RIP = %#06RX64, got %#06RX64", uExpectRip, TrapFrame.Ctx.rip.u); + if (TrapFrame.Ctx.rax.u != uExpectRax) + Bs3TestFailedF("Expected RAX = %#06RX64, got %#06RX64", uExpectRax, TrapFrame.Ctx.rax.u); + if (TrapFrame.Ctx.rbx.u != Ctx.rbx.u) + Bs3TestFailedF("Expected RBX = %#06RX64, got %#06RX64 (dst)", Ctx.rbx.u, TrapFrame.Ctx.rbx.u); + if ( (TrapFrame.Ctx.rflags.u16 & fEflCheck) + != ((fOkay ? paTests[i].paSubTests[k].fEflOut : Ctx.rflags.u16) & fEflCheck)) + Bs3TestFailedF("Expected EFLAGS = %#06RX32, got %#06RX32 (output)", + (fOkay ? paTests[i].paSubTests[k].fEflOut : Ctx.rflags.u16) & fEflCheck, + TrapFrame.Ctx.rflags.u16 & fEflCheck); + if ( (TrapFrame.Ctx.rflags.u16 & ~(fEflCheck | fEflIgnore) & X86_EFL_STATUS_BITS) + != (Ctx.rflags.u16 & ~(fEflCheck | fEflIgnore) & X86_EFL_STATUS_BITS)) + Bs3TestFailedF("Expected EFLAGS = %#06RX32, got %#06RX32 (immutable)", + Ctx.rflags.u16 & ~(fEflCheck | fEflIgnore) & X86_EFL_STATUS_BITS, + TrapFrame.Ctx.rflags.u16 & ~(fEflCheck | fEflIgnore) & X86_EFL_STATUS_BITS); + + if (TrapFrame.Ctx.rcx.u != Ctx.rcx.u) + Bs3TestFailedF("Expected RCX = %#06RX64, got %#06RX64", Ctx.rcx.u, TrapFrame.Ctx.rcx.u); + if (TrapFrame.Ctx.rdx.u != Ctx.rdx.u) + Bs3TestFailedF("Expected RDX = %#06RX64, got %#06RX64", Ctx.rdx.u, TrapFrame.Ctx.rdx.u); + if (TrapFrame.Ctx.rsp.u != Ctx.rsp.u) + Bs3TestFailedF("Expected RSP = %#06RX64, got %#06RX64", Ctx.rsp.u, TrapFrame.Ctx.rsp.u); + if (TrapFrame.Ctx.rbp.u != Ctx.rbp.u) + Bs3TestFailedF("Expected RBP = %#06RX64, got %#06RX64", Ctx.rbp.u, TrapFrame.Ctx.rbp.u); + if (TrapFrame.Ctx.rsi.u != Ctx.rsi.u) + Bs3TestFailedF("Expected RSI = %#06RX64, got %#06RX64", Ctx.rsi.u, TrapFrame.Ctx.rsi.u); + if (TrapFrame.Ctx.rdi.u != Ctx.rdi.u) + Bs3TestFailedF("Expected RDI = %#06RX64, got %#06RX64", Ctx.rdi.u, TrapFrame.Ctx.rdi.u); + if (uMemSrc != uMemSrcExpect) + Bs3TestFailedF("Expected uMemSrc = %#06RX64, got %#06RX64", (uint64_t)uMemSrcExpect, (uint64_t)uMemSrc); + } + } + } + Ctx.rflags.u16 &= ~X86_EFL_STATUS_BITS; + } + + return 0; +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_blsr)(uint8_t bMode) +{ + /* 64 bits register width (32 bits in 32- and 16-bit modes): */ + static BS3CPUINSTR2_SUBTEST_By_Ey_T const s_aSubTests64[] = + { + { 0, /* -> */ 0, X86_EFL_ZF | X86_EFL_CF }, + { 1, /* -> */ 0, X86_EFL_ZF }, + { 2, /* -> */ 0, X86_EFL_ZF }, + { 3, /* -> */ 2, 0 }, + { 5, /* -> */ 4, 0 }, + { 6, /* -> */ 4, 0 }, + { 7, /* -> */ 6, 0 }, + { 9, /* -> */ 8, 0 }, + { 10, /* -> */ 8, 0 }, + { ~(RTCCUINTXREG)1, /* -> */ ~(RTCCUINTXREG)3, X86_EFL_SF }, + { (RTCCUINTXREG)3 << (RTCCINTXREG_BITS - 2), /* -> */ (RTCCUINTXREG)2 << (RTCCINTXREG_BITS - 2), X86_EFL_SF }, + }; + + /* 32-bit register width */ + static BS3CPUINSTR2_SUBTEST_By_Ey_T const s_aSubTests32[] = + { + { 0, /* -> */ 0, X86_EFL_ZF | X86_EFL_CF }, + { 1, /* -> */ 0, X86_EFL_ZF }, + { ~(RTCCUINTXREG)1, /* -> */ UINT32_C(0xfffffffc), X86_EFL_SF }, + { ~(RTCCUINTXREG)0 << 30, /* -> */ UINT32_C(0x80000000), X86_EFL_SF }, + }; + + static BS3CPUINSTR2_TEST_By_Ey_T const s_aTests[] = + { + { BS3_CMN_NM(bs3CpuInstr2_blsr_RAX_RBX_icebp), false, 5, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_blsr_RAX_FSxBX_icebp), true, 6, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_blsr_EAX_EBX_icebp), false, 5, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + { BS3_CMN_NM(bs3CpuInstr2_blsr_EAX_FSxBX_icebp), true, 6, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + }; + return bs3CpuInstr2_Common_By_Ey(bMode, s_aTests, RT_ELEMENTS(s_aTests), X86_CPUID_STEXT_FEATURE_EBX_BMI1, + X86_EFL_STATUS_BITS, 0); +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_blsmsk)(uint8_t bMode) +{ + /* 64 bits register width (32 bits in 32- and 16-bit modes): */ + static BS3CPUINSTR2_SUBTEST_By_Ey_T const s_aSubTests64[] = + { + { 0, /* -> */ ~(RTCCUINTXREG)0, X86_EFL_CF | X86_EFL_SF }, + { 1, /* -> */ 1, 0 }, + { ~(RTCCUINTXREG)1, /* -> */ 3, 0 }, + { (RTCCUINTXREG)3 << (RTCCINTXREG_BITS - 2), /* -> */ ~((RTCCUINTXREG)2 << (RTCCINTXREG_BITS - 2)), 0 }, + }; + + /* 32-bit register width */ + static BS3CPUINSTR2_SUBTEST_By_Ey_T const s_aSubTests32[] = + { + { 0, /* -> */ UINT32_MAX, X86_EFL_CF | X86_EFL_SF }, + { 1, /* -> */ 1, 0 }, + { ~(RTCCUINTXREG)1, /* -> */ 3, 0 }, + { ~(RTCCUINTXREG)0 << 30, /* -> */ UINT32_C(0x7fffffff), 0}, + }; + + static BS3CPUINSTR2_TEST_By_Ey_T const s_aTests[] = + { + { BS3_CMN_NM(bs3CpuInstr2_blsmsk_RAX_RBX_icebp), false, 5, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_blsmsk_RAX_FSxBX_icebp), true, 6, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_blsmsk_EAX_EBX_icebp), false, 5, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + { BS3_CMN_NM(bs3CpuInstr2_blsmsk_EAX_FSxBX_icebp), true, 6, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + }; + return bs3CpuInstr2_Common_By_Ey(bMode, s_aTests, RT_ELEMENTS(s_aTests), X86_CPUID_STEXT_FEATURE_EBX_BMI1, + X86_EFL_STATUS_BITS, 0); +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_blsi)(uint8_t bMode) +{ + /* 64 bits register width (32 bits in 32- and 16-bit modes): */ + static BS3CPUINSTR2_SUBTEST_By_Ey_T const s_aSubTests64[] = + { + { 0, /* -> */ 0, X86_EFL_ZF }, + { 1, /* -> */ 1, X86_EFL_CF }, + { ~(RTCCUINTXREG)1, /* -> */ 2, X86_EFL_CF }, + { (RTCCUINTXREG)3 << (RTCCINTXREG_BITS - 2), /* -> */ (RTCCUINTXREG)1 << (RTCCINTXREG_BITS - 2), X86_EFL_CF }, + }; + + /* 32-bit register width */ + static BS3CPUINSTR2_SUBTEST_By_Ey_T const s_aSubTests32[] = + { + { 0, /* -> */ 0, X86_EFL_ZF }, + { 1, /* -> */ 1, X86_EFL_CF }, + { ~(RTCCUINTXREG)1, /* -> */ 2, X86_EFL_CF }, + { ~(RTCCUINTXREG)0 << 30, /* -> */ UINT32_C(0x40000000), X86_EFL_CF }, + }; + + static BS3CPUINSTR2_TEST_By_Ey_T const s_aTests[] = + { + { BS3_CMN_NM(bs3CpuInstr2_blsi_RAX_RBX_icebp), false, 5, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_blsi_RAX_FSxBX_icebp), true, 6, RT_ELEMENTS(s_aSubTests64), s_aSubTests64 }, + { BS3_CMN_NM(bs3CpuInstr2_blsi_EAX_EBX_icebp), false, 5, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + { BS3_CMN_NM(bs3CpuInstr2_blsi_EAX_FSxBX_icebp), true, 6, RT_ELEMENTS(s_aSubTests32), s_aSubTests32 }, + }; + return bs3CpuInstr2_Common_By_Ey(bMode, s_aTests, RT_ELEMENTS(s_aTests), X86_CPUID_STEXT_FEATURE_EBX_BMI1, + X86_EFL_STATUS_BITS, 0); +} + + +/* + * MULX (BMI2) - destination registers (/r & vvvv) = r/m * rDX + */ +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_mulx)(uint8_t bMode) +{ + static const struct + { + FPFNBS3FAR pfnWorker; + bool fMemSrc; + bool fSameDst; + uint8_t cbInstr; + RTCCUINTXREG uSrc1; + RTCCUINTXREG uSrc2; + RTCCUINTXREG uDst1; + RTCCUINTXREG uDst2; + } s_aTests[] = + { + /* 64 bits register width (32 bits in 32- and 16-bit modes): */ + { BS3_CMN_NM(bs3CpuInstr2_mulx_RAX_RCX_RBX_RDX_icebp), false, false, 5, // #0 + 0, 0, /* -> */ 0, 0 }, + { BS3_CMN_NM(bs3CpuInstr2_mulx_RAX_RCX_RBX_RDX_icebp), false, false, 5, // #1 + ~(RTCCUINTXREG)0, ~(RTCCUINTXREG)0, /* -> */ ~(RTCCUINTXREG)1, 1 }, + { BS3_CMN_NM(bs3CpuInstr2_mulx_RCX_RCX_RBX_RDX_icebp), false, true, 5, // #2 + ~(RTCCUINTXREG)0, ~(RTCCUINTXREG)0, /* -> */ ~(RTCCUINTXREG)1, ~(RTCCUINTXREG)1 }, + { BS3_CMN_NM(bs3CpuInstr2_mulx_RAX_RCX_RBX_RDX_icebp), false, false, 5, // #3 + 2, 2, /* -> */ 0, 4 }, + { BS3_CMN_NM(bs3CpuInstr2_mulx_RAX_RCX_RBX_RDX_icebp), false, false, 5, // #4 + ~(RTCCUINTXREG)0, 42, /* -> */ 0x29, ~(RTCCUINTXREG)41 }, + + { BS3_CMN_NM(bs3CpuInstr2_mulx_RAX_RCX_FSxBX_RDX_icebp), true, false, 6, // #5 + 0, 0, /* -> */ 0, 0 }, + { BS3_CMN_NM(bs3CpuInstr2_mulx_RAX_RCX_FSxBX_RDX_icebp), true, false, 6, // #6 + ~(RTCCUINTXREG)0, ~(RTCCUINTXREG)0, /* -> */ ~(RTCCUINTXREG)1, 1 }, + { BS3_CMN_NM(bs3CpuInstr2_mulx_RAX_RCX_FSxBX_RDX_icebp), true, false, 6, // #7 + ~(RTCCUINTXREG)0, 42, /* -> */ 0x29, ~(RTCCUINTXREG)41 }, + + /* 32-bit register width */ + { BS3_CMN_NM(bs3CpuInstr2_mulx_EAX_ECX_EBX_EDX_icebp), false, false, 5, // #8 + 0, 0, /* -> */ 0, 0 }, + { BS3_CMN_NM(bs3CpuInstr2_mulx_EAX_ECX_EBX_EDX_icebp), false, false, 5, // #9 + ~(RTCCUINTXREG)0, ~(RTCCUINTXREG)0, /* -> */ ~(uint32_t)1, 1 }, + { BS3_CMN_NM(bs3CpuInstr2_mulx_ECX_ECX_EBX_EDX_icebp), false, true, 5, // #10 + ~(RTCCUINTXREG)0, ~(RTCCUINTXREG)0, /* -> */ ~(uint32_t)1, ~(uint32_t)1 }, + { BS3_CMN_NM(bs3CpuInstr2_mulx_EAX_ECX_EBX_EDX_icebp), false, false, 5, // #11 + 2, 2, /* -> */ 0, 4 }, + { BS3_CMN_NM(bs3CpuInstr2_mulx_EAX_ECX_EBX_EDX_icebp), false, false, 5, // #12 + ~(RTCCUINTXREG)0, 42, /* -> */ 0x29, ~(uint32_t)41 }, + + { BS3_CMN_NM(bs3CpuInstr2_mulx_EAX_ECX_FSxBX_EDX_icebp), true, false, 6, // #13 + 0, 0, /* -> */ 0, 0 }, + { BS3_CMN_NM(bs3CpuInstr2_mulx_EAX_ECX_FSxBX_EDX_icebp), true, false, 6, // #14 + ~(RTCCUINTXREG)0, ~(RTCCUINTXREG)0, /* -> */ ~(uint32_t)1, 1 }, + { BS3_CMN_NM(bs3CpuInstr2_mulx_EAX_ECX_FSxBX_EDX_icebp), true, false, 6, // #15 + ~(RTCCUINTXREG)0, 42, /* -> */ 0x29, ~(uint32_t)41 }, + }; + + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + unsigned i, j; + uint32_t uStdExtFeatEbx = 0; + bool fSupportsAndN; + + if (g_uBs3CpuDetected & BS3CPU_F_CPUID) + ASMCpuIdExSlow(7, 0, 0, 0, NULL, &uStdExtFeatEbx, NULL, NULL); + fSupportsAndN = RT_BOOL(uStdExtFeatEbx & X86_CPUID_STEXT_FEATURE_EBX_BMI2); + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 512); + + /* + * Do the tests twice, first with all flags set, then once again with + * flags cleared. The flags are not supposed to be touched at all. + */ + Ctx.rflags.u16 |= X86_EFL_STATUS_BITS; + for (j = 0; j < 2; j++) + { + for (i = 0; i < RT_ELEMENTS(s_aTests); i++) + { + bool const fOkay = !BS3_MODE_IS_RM_OR_V86(bMode) && fSupportsAndN; + uint8_t const bExpectXcpt = fOkay ? X86_XCPT_DB : X86_XCPT_UD; + uint64_t uExpectRax, uExpectRcx, uExpectRip; + RTCCUINTXREG uMemSrc1, uMemSrc1Expect; + + Ctx.rax.uCcXReg = RTCCUINTXREG_MAX * 1019; + Ctx.rcx.uCcXReg = RTCCUINTXREG_MAX * 4095; + Ctx.rdx.uCcXReg = s_aTests[i].uSrc2; + if (!s_aTests[i].fMemSrc) + { + Ctx.rbx.uCcXReg = s_aTests[i].uSrc1; + uMemSrc1Expect = uMemSrc1 = ~s_aTests[i].uSrc1; + } + else + { + uMemSrc1Expect = uMemSrc1 = s_aTests[i].uSrc1; + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, &Ctx.fs, &uMemSrc1); + } + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, s_aTests[i].pfnWorker); + uExpectRax = fOkay && !s_aTests[i].fSameDst ? s_aTests[i].uDst1 : Ctx.rax.u; + uExpectRcx = fOkay ? s_aTests[i].uDst2 : Ctx.rcx.u; + uExpectRip = Ctx.rip.u + (fOkay ? s_aTests[i].cbInstr + 1 : 0); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); + + if ( TrapFrame.bXcpt != bExpectXcpt + || TrapFrame.Ctx.rip.u != uExpectRip + || TrapFrame.Ctx.rbx.u != Ctx.rbx.u + || TrapFrame.Ctx.rdx.u != Ctx.rdx.u + || TrapFrame.Ctx.rax.u != uExpectRax + || TrapFrame.Ctx.rcx.u != uExpectRcx + /* check that nothing else really changed: */ + || (TrapFrame.Ctx.rflags.u16 & X86_EFL_STATUS_BITS) != (Ctx.rflags.u16 & X86_EFL_STATUS_BITS) + || TrapFrame.Ctx.rsp.u != Ctx.rsp.u + || TrapFrame.Ctx.rbp.u != Ctx.rbp.u + || TrapFrame.Ctx.rsi.u != Ctx.rsi.u + || TrapFrame.Ctx.rdi.u != Ctx.rdi.u + || uMemSrc1 != uMemSrc1Expect + ) + { + Bs3TestFailedF("test #%i failed: input %#" RTCCUINTXREG_XFMT ", %#" RTCCUINTXREG_XFMT, i, s_aTests[i].uSrc1, s_aTests[i].uSrc2); + if (TrapFrame.bXcpt != bExpectXcpt) + Bs3TestFailedF("Expected bXcpt = %#x, got %#x", bExpectXcpt, TrapFrame.bXcpt); + if (TrapFrame.Ctx.rip.u != uExpectRip) + Bs3TestFailedF("Expected RIP = %#06RX64, got %#06RX64", uExpectRip, TrapFrame.Ctx.rip.u); + if (TrapFrame.Ctx.rax.u != uExpectRax) + Bs3TestFailedF("Expected RAX = %#06RX64, got %#06RX64", uExpectRax, TrapFrame.Ctx.rax.u); + if (TrapFrame.Ctx.rcx.u != uExpectRcx) + Bs3TestFailedF("Expected RCX = %#06RX64, got %#06RX64", uExpectRcx, TrapFrame.Ctx.rcx.u); + if (TrapFrame.Ctx.rbx.u != Ctx.rbx.u) + Bs3TestFailedF("Expected RBX = %#06RX64, got %#06RX64 (dst)", Ctx.rbx.u, TrapFrame.Ctx.rbx.u); + if (TrapFrame.Ctx.rdx.u != Ctx.rdx.u) + Bs3TestFailedF("Expected RDX = %#06RX64, got %#06RX64 (src)", Ctx.rdx.u, TrapFrame.Ctx.rdx.u); + + if ( (TrapFrame.Ctx.rflags.u16 & X86_EFL_STATUS_BITS) != (Ctx.rflags.u16 & X86_EFL_STATUS_BITS)) + Bs3TestFailedF("Expected EFLAGS = %#06RX32, got %#06RX32 (immutable)", + Ctx.rflags.u16 & X86_EFL_STATUS_BITS, TrapFrame.Ctx.rflags.u16 & X86_EFL_STATUS_BITS); + if (TrapFrame.Ctx.rsp.u != Ctx.rsp.u) + Bs3TestFailedF("Expected RSP = %#06RX64, got %#06RX64", Ctx.rsp.u, TrapFrame.Ctx.rsp.u); + if (TrapFrame.Ctx.rbp.u != Ctx.rbp.u) + Bs3TestFailedF("Expected RBP = %#06RX64, got %#06RX64", Ctx.rbp.u, TrapFrame.Ctx.rbp.u); + if (TrapFrame.Ctx.rsi.u != Ctx.rsi.u) + Bs3TestFailedF("Expected RSI = %#06RX64, got %#06RX64", Ctx.rsi.u, TrapFrame.Ctx.rsi.u); + if (TrapFrame.Ctx.rdi.u != Ctx.rdi.u) + Bs3TestFailedF("Expected RDI = %#06RX64, got %#06RX64", Ctx.rdi.u, TrapFrame.Ctx.rdi.u); + if (uMemSrc1 != uMemSrc1Expect) + Bs3TestFailedF("Expected uMemSrc1 = %#06RX64, got %#06RX64", (uint64_t)uMemSrc1Expect, (uint64_t)uMemSrc1); + } + } + Ctx.rflags.u16 &= ~X86_EFL_STATUS_BITS; + } + + return 0; +} + + +/* + * POPCNT - Intel: POPCNT; AMD: ABM. + */ +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_popcnt)(uint8_t bMode) +{ + static const struct + { + FPFNBS3FAR pfnWorker; + bool fMemSrc; + uint8_t cWidth; + uint8_t cbInstr; + RTCCUINTXREG uSrc; + RTCCUINTXREG uDst; + uint16_t fEFlags; + } s_aTests[] = + { + /* 16-bit register width */ + { BS3_CMN_NM(bs3CpuInstr2_popcnt_AX_BX_icebp), false, 16, 4 + (ARCH_BITS != 16), // #0 + 0, /* -> */ 0, X86_EFL_ZF }, + { BS3_CMN_NM(bs3CpuInstr2_popcnt_AX_BX_icebp), false, 16, 4 + (ARCH_BITS != 16), // #1 + ~(RTCCUINTXREG)0, /* -> */ 16, 0 }, + { BS3_CMN_NM(bs3CpuInstr2_popcnt_AX_BX_icebp), false, 16, 4 + (ARCH_BITS != 16), // #2 + UINT16_C(0xffff), /* -> */ 16, 0 }, + { BS3_CMN_NM(bs3CpuInstr2_popcnt_AX_BX_icebp), false, 16, 4 + (ARCH_BITS != 16), // #3 + UINT16_C(0x0304), /* -> */ 3, 0 }, + { BS3_CMN_NM(bs3CpuInstr2_popcnt_AX_FSxBX_icebp), true, 16, 5 + (ARCH_BITS != 16), // #4 + UINT16_C(0xd569), /* -> */ 9, 0}, + { BS3_CMN_NM(bs3CpuInstr2_popcnt_AX_FSxBX_icebp), true, 16, 5 + (ARCH_BITS != 16), // #5 + 0, /* -> */ 0, X86_EFL_ZF }, + + /* 32-bit register width */ + { BS3_CMN_NM(bs3CpuInstr2_popcnt_EAX_EBX_icebp), false, 32, 4 + (ARCH_BITS == 16), // #6 + 0, /* -> */ 0, X86_EFL_ZF }, + { BS3_CMN_NM(bs3CpuInstr2_popcnt_EAX_EBX_icebp), false, 32, 4 + (ARCH_BITS == 16), // #7 + ~(RTCCUINTXREG)0, /* -> */ 32, 0}, + { BS3_CMN_NM(bs3CpuInstr2_popcnt_EAX_EBX_icebp), false, 32, 4 + (ARCH_BITS == 16), // #8 + UINT32_C(0x01020304), /* -> */ 5, 0}, + { BS3_CMN_NM(bs3CpuInstr2_popcnt_EAX_FSxBX_icebp), true, 32, 5 + (ARCH_BITS == 16), // #9 + 0, /* -> */ 0, X86_EFL_ZF }, + { BS3_CMN_NM(bs3CpuInstr2_popcnt_EAX_FSxBX_icebp), true, 32, 5 + (ARCH_BITS == 16), // #10 + UINT32_C(0x49760948), /* -> */ 12, 0 }, + +#if ARCH_BITS == 64 + /* 64-bit register width */ + { BS3_CMN_NM(bs3CpuInstr2_popcnt_RAX_RBX_icebp), false, 64, 5, // #11 + 0, /* -> */ 0, X86_EFL_ZF }, + { BS3_CMN_NM(bs3CpuInstr2_popcnt_RAX_RBX_icebp), false, 64, 5, // #12 + ~(RTCCUINTXREG)0, /* -> */ 64, 0 }, + { BS3_CMN_NM(bs3CpuInstr2_popcnt_RAX_RBX_icebp), false, 64, 5, // #13 + UINT64_C(0x1234123412341234), /* -> */ 5*4, 0 }, + { BS3_CMN_NM(bs3CpuInstr2_popcnt_RAX_FSxBX_icebp), true, 64, 6, // #14 + 0, /* -> */ 0, X86_EFL_ZF }, + { BS3_CMN_NM(bs3CpuInstr2_popcnt_RAX_FSxBX_icebp), true, 64, 6, // #15 + ~(RTCCUINTXREG)0, /* -> */ 64, 0 }, + { BS3_CMN_NM(bs3CpuInstr2_popcnt_RAX_FSxBX_icebp), true, 64, 6, // #16 + UINT64_C(0x5908760293769087), /* -> */ 26, 0 }, +#endif + }; + + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + unsigned i, j; + bool const fSupportsPopCnt = (g_uBs3CpuDetected & BS3CPU_F_CPUID) + && (ASMCpuId_ECX(1) & X86_CPUID_FEATURE_ECX_POPCNT); + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 512); + + /* + * Do the tests twice, first with all flags set, then once again with + * flags cleared. The flags are not supposed to be touched at all. + */ + Ctx.rflags.u16 |= X86_EFL_STATUS_BITS; + for (j = 0; j < 2; j++) + { + for (i = 0; i < RT_ELEMENTS(s_aTests); i++) + { + bool const fOkay = fSupportsPopCnt; + uint8_t const bExpectXcpt = fOkay ? X86_XCPT_DB : X86_XCPT_UD; + uint64_t uExpectRax, uExpectRip; + RTCCUINTXREG uMemSrc, uMemSrcExpect; + + Ctx.rax.uCcXReg = RTCCUINTXREG_MAX * 1019; + if (!s_aTests[i].fMemSrc) + { + Ctx.rbx.uCcXReg = s_aTests[i].uSrc; + uMemSrcExpect = uMemSrc = ~s_aTests[i].uSrc; + } + else + { + uMemSrcExpect = uMemSrc = s_aTests[i].uSrc; + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, &Ctx.fs, &uMemSrc); + } + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, s_aTests[i].pfnWorker); + uExpectRax = fOkay ? s_aTests[i].uDst : Ctx.rax.u; + if (s_aTests[i].cWidth == 16) + uExpectRax = (uExpectRax & UINT16_MAX) | (Ctx.rax.u & ~(uint64_t)UINT16_MAX); + + uExpectRip = Ctx.rip.u + (fOkay ? s_aTests[i].cbInstr + 1 : 0); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); + + if ( TrapFrame.bXcpt != bExpectXcpt + || TrapFrame.Ctx.rip.u != uExpectRip + || TrapFrame.Ctx.rbx.u != Ctx.rbx.u + || TrapFrame.Ctx.rax.u != uExpectRax + || (TrapFrame.Ctx.rflags.u16 & X86_EFL_STATUS_BITS) != (fOkay ? s_aTests[i].fEFlags : Ctx.rflags.u16) + /* check that nothing else really changed: */ + || TrapFrame.Ctx.rcx.u != Ctx.rcx.u + || TrapFrame.Ctx.rdx.u != Ctx.rdx.u + || TrapFrame.Ctx.rsp.u != Ctx.rsp.u + || TrapFrame.Ctx.rbp.u != Ctx.rbp.u + || TrapFrame.Ctx.rsi.u != Ctx.rsi.u + || TrapFrame.Ctx.rdi.u != Ctx.rdi.u + || uMemSrc != uMemSrcExpect + ) + { + Bs3TestFailedF("test #%i failed: input %#" RTCCUINTXREG_XFMT, i, s_aTests[i].uSrc); + if (TrapFrame.bXcpt != bExpectXcpt) + Bs3TestFailedF("Expected bXcpt = %#x, got %#x", bExpectXcpt, TrapFrame.bXcpt); + if (TrapFrame.Ctx.rip.u != uExpectRip) + Bs3TestFailedF("Expected RIP = %#06RX64, got %#06RX64", uExpectRip, TrapFrame.Ctx.rip.u); + if (TrapFrame.Ctx.rax.u != uExpectRax) + Bs3TestFailedF("Expected RAX = %#06RX64, got %#06RX64", uExpectRax, TrapFrame.Ctx.rax.u); + if (TrapFrame.Ctx.rbx.u != Ctx.rbx.u) + Bs3TestFailedF("Expected RBX = %#06RX64, got %#06RX64 (dst)", Ctx.rbx.u, TrapFrame.Ctx.rbx.u); + if ((TrapFrame.Ctx.rflags.u16 & X86_EFL_STATUS_BITS) != (fOkay ? s_aTests[i].fEFlags : Ctx.rflags.u16)) + Bs3TestFailedF("Expected EFLAGS = %#06RX32, got %#06RX32", + fOkay ? s_aTests[i].fEFlags : Ctx.rflags.u16, TrapFrame.Ctx.rflags.u16 & X86_EFL_STATUS_BITS); + + if (TrapFrame.Ctx.rcx.u != Ctx.rcx.u) + Bs3TestFailedF("Expected RCX = %#06RX64, got %#06RX64", Ctx.rcx.u, TrapFrame.Ctx.rcx.u); + if (TrapFrame.Ctx.rdx.u != Ctx.rdx.u) + Bs3TestFailedF("Expected RDX = %#06RX64, got %#06RX64 (src)", Ctx.rdx.u, TrapFrame.Ctx.rdx.u); + if (TrapFrame.Ctx.rsp.u != Ctx.rsp.u) + Bs3TestFailedF("Expected RSP = %#06RX64, got %#06RX64", Ctx.rsp.u, TrapFrame.Ctx.rsp.u); + if (TrapFrame.Ctx.rbp.u != Ctx.rbp.u) + Bs3TestFailedF("Expected RBP = %#06RX64, got %#06RX64", Ctx.rbp.u, TrapFrame.Ctx.rbp.u); + if (TrapFrame.Ctx.rsi.u != Ctx.rsi.u) + Bs3TestFailedF("Expected RSI = %#06RX64, got %#06RX64", Ctx.rsi.u, TrapFrame.Ctx.rsi.u); + if (TrapFrame.Ctx.rdi.u != Ctx.rdi.u) + Bs3TestFailedF("Expected RDI = %#06RX64, got %#06RX64", Ctx.rdi.u, TrapFrame.Ctx.rdi.u); + if (uMemSrc != uMemSrcExpect) + Bs3TestFailedF("Expected uMemSrc = %#06RX64, got %#06RX64", (uint64_t)uMemSrcExpect, (uint64_t)uMemSrc); + } + } + Ctx.rflags.u16 &= ~X86_EFL_STATUS_BITS; + } + + return 0; +} + +/* + * CRC32 - SSE4.2 + */ +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_crc32)(uint8_t bMode) +{ + typedef struct BS3CPUINSTR2_CRC32_VALUES_T + { + uint32_t uDstIn; + uint32_t uDstOut; + uint64_t uSrc; + } BS3CPUINSTR2_CRC32_VALUES_T; + static const BS3CPUINSTR2_CRC32_VALUES_T s_aValues1[] = + { + { UINT32_C(0000000000), UINT32_C(0000000000), UINT8_C(0000) }, + { UINT32_C(0xffffffff), UINT32_C(0x25502c8c), UINT8_C(0xea) }, + { UINT32_C(0x25502c8c), UINT32_C(0x474224a6), UINT8_C(0xea) }, + { UINT32_C(0x474224a6), UINT32_C(0x0c7f9048), UINT8_C(0xea) }, + { UINT32_C(0x0c7f9048), UINT32_C(0x39c5b9e0), UINT8_C(0x01) }, + { UINT32_C(0x39c5b9e0), UINT32_C(0x2493fabc), UINT8_C(0x04) }, + { UINT32_C(0x2493fabc), UINT32_C(0x0b05c4d6), UINT8_C(0x27) }, + { UINT32_C(0x0b05c4d6), UINT32_C(0xbe26a561), UINT8_C(0x2a) }, + { UINT32_C(0xbe26a561), UINT32_C(0xe1855652), UINT8_C(0x63) }, + { UINT32_C(0xe1855652), UINT32_C(0xc67efe3f), UINT8_C(0xa7) }, + { UINT32_C(0xc67efe3f), UINT32_C(0x227028cd), UINT8_C(0xfd) }, + { UINT32_C(0x227028cd), UINT32_C(0xf4559a1d), UINT8_C(0xea) }, + }; + static const BS3CPUINSTR2_CRC32_VALUES_T s_aValues2[] = + { + { UINT32_C(0000000000), UINT32_C(0000000000), UINT16_C(000000) }, + { UINT32_C(0xffffffff), UINT32_C(0xd550e2a0), UINT16_C(0x04d2) }, + { UINT32_C(0xd550e2a0), UINT32_C(0x38e07a0a), UINT16_C(0xe8cc) }, + { UINT32_C(0x38e07a0a), UINT32_C(0x60ebd519), UINT16_C(0x82a2) }, + { UINT32_C(0x60ebd519), UINT32_C(0xaaa127b5), UINT16_C(0x0fff) }, + { UINT32_C(0xaaa127b5), UINT32_C(0xb13175c6), UINT16_C(0x00ff) }, + { UINT32_C(0xb13175c6), UINT32_C(0x3a226f1b), UINT16_C(0x0300) }, + { UINT32_C(0x3a226f1b), UINT32_C(0xbaedef0c), UINT16_C(0x270f) }, + { UINT32_C(0xbaedef0c), UINT32_C(0x2d18866e), UINT16_C(0x3ff6) }, + { UINT32_C(0x2d18866e), UINT32_C(0x07e2e954), UINT16_C(0x9316) }, + { UINT32_C(0x07e2e954), UINT32_C(0x95f82acb), UINT16_C(0xa59c) }, + }; + static const BS3CPUINSTR2_CRC32_VALUES_T s_aValues4[] = + { + { UINT32_C(0000000000), UINT32_C(0000000000), UINT32_C(0000000000) }, + { UINT32_C(0xffffffff), UINT32_C(0xc9a7250e), UINT32_C(0x0270fa68) }, + { UINT32_C(0xc9a7250e), UINT32_C(0x7340d175), UINT32_C(0x23729736) }, + { UINT32_C(0x7340d175), UINT32_C(0x7e17b67d), UINT32_C(0x8bc75d35) }, + { UINT32_C(0x7e17b67d), UINT32_C(0x5028eb71), UINT32_C(0x0e9bebf2) }, + { UINT32_C(0x5028eb71), UINT32_C(0xc0a7f45a), UINT32_C(0x000001bc) }, + { UINT32_C(0xc0a7f45a), UINT32_C(0xa96f4012), UINT32_C(0x0034ba02) }, + { UINT32_C(0xa96f4012), UINT32_C(0xb27c0718), UINT32_C(0x0000002a) }, + { UINT32_C(0xb27c0718), UINT32_C(0x79fb2d35), UINT32_C(0x0153158e) }, + { UINT32_C(0x79fb2d35), UINT32_C(0x23434fc9), UINT32_C(0x02594882) }, + { UINT32_C(0x23434fc9), UINT32_C(0x354bf3b6), UINT32_C(0xb230b8f3) }, + }; +#if ARCH_BITS >= 64 + static const BS3CPUINSTR2_CRC32_VALUES_T s_aValues8[] = + { + { UINT32_C(0000000000), UINT32_C(0000000000), UINT64_C(000000000000000000) }, + { UINT32_C(0xffffffff), UINT32_C(0xadc36834), UINT64_C(0x02b0b5e2a975c1cc) }, + { UINT32_C(0xadc36834), UINT32_C(0xf0e893c9), UINT64_C(0x823d386bf7517583) }, + { UINT32_C(0xf0e893c9), UINT32_C(0x1a22a837), UINT64_C(0x0481f5311fa061d0) }, + { UINT32_C(0x1a22a837), UINT32_C(0xcf8b6d61), UINT64_C(0x13fa70f64d52a92d) }, + { UINT32_C(0xcf8b6d61), UINT32_C(0xc7dde203), UINT64_C(0x3ccc8b035903d3e1) }, + { UINT32_C(0xc7dde203), UINT32_C(0xd42b5823), UINT64_C(0x0000011850ec2fac) }, + { UINT32_C(0xd42b5823), UINT32_C(0x8b1ce49e), UINT64_C(0x0000000000001364) }, + { UINT32_C(0x8b1ce49e), UINT32_C(0x1af31710), UINT64_C(0x000000057840205a) }, + { UINT32_C(0x1af31710), UINT32_C(0xdea35e8b), UINT64_C(0x2e5d93688d9a0bfa) }, + { UINT32_C(0xdea35e8b), UINT32_C(0x594c013a), UINT64_C(0x8ac7230489e7ffff) }, + { UINT32_C(0x594c013a), UINT32_C(0x27b061e5), UINT64_C(0x6bf037ae325f1c71) }, + { UINT32_C(0x27b061e5), UINT32_C(0x3120b5f7), UINT64_C(0x0fffffff34503556) }, + }; +#endif + static const struct + { + FPFNBS3FAR pfnWorker; + bool fMemSrc; + uint8_t cbOp; + uint8_t cValues; + BS3CPUINSTR2_CRC32_VALUES_T const BS3_FAR *paValues; + } s_aTests[] = + { + /* 8-bit register width */ + { BS3_CMN_NM(bs3CpuInstr2_crc32_EAX_BL_icebp), false, 1, RT_ELEMENTS(s_aValues1), s_aValues1 }, + { BS3_CMN_NM(bs3CpuInstr2_crc32_EAX_byte_FSxBX_icebp), true, 1, RT_ELEMENTS(s_aValues1), s_aValues1 }, + + /* 16-bit register width */ + { BS3_CMN_NM(bs3CpuInstr2_crc32_EAX_BX_icebp), false, 2, RT_ELEMENTS(s_aValues2), s_aValues2 }, + { BS3_CMN_NM(bs3CpuInstr2_crc32_EAX_word_FSxBX_icebp), true, 2, RT_ELEMENTS(s_aValues2), s_aValues2 }, + + /* 32-bit register width */ + { BS3_CMN_NM(bs3CpuInstr2_crc32_EAX_EBX_icebp), false, 4, RT_ELEMENTS(s_aValues4), s_aValues4 }, + { BS3_CMN_NM(bs3CpuInstr2_crc32_EAX_dword_FSxBX_icebp), true, 4, RT_ELEMENTS(s_aValues4), s_aValues4 }, +#if ARCH_BITS >= 64 + /* 32-bit register width */ + { BS3_CMN_NM(bs3CpuInstr2_crc32_EAX_RBX_icebp), false, 8, RT_ELEMENTS(s_aValues8), s_aValues8 }, + { BS3_CMN_NM(bs3CpuInstr2_crc32_EAX_qword_FSxBX_icebp), true, 8, RT_ELEMENTS(s_aValues8), s_aValues8 }, +#endif + }; + + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + unsigned i, j; + bool const fSupportsCrc32 = (g_uBs3CpuDetected & BS3CPU_F_CPUID) + && (ASMCpuId_ECX(1) & X86_CPUID_FEATURE_ECX_SSE4_2); + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 512); + + /* + * Do the tests twice, first with all flags set, then once again with + * flags cleared. The flags are not supposed to be touched at all. + */ + Ctx.rflags.u16 |= X86_EFL_STATUS_BITS; + for (j = 0; j < 2; j++) + { + for (i = 0; i < RT_ELEMENTS(s_aTests); i++) + { + uint8_t const cbOp = s_aTests[i].cbOp; + unsigned const cValues = s_aTests[i].cValues; + BS3CPUINSTR2_CRC32_VALUES_T const BS3_FAR *paValues = s_aTests[i].paValues; + unsigned iValue; + bool const fOkay = fSupportsCrc32; + uint8_t const bExpectXcpt = fOkay ? X86_XCPT_DB : X86_XCPT_UD; + uint64_t const uSrcGarbage = ( cbOp == 1 ? UINT64_C(0x03948314d0f03400) + : cbOp == 2 ? UINT64_C(0x03948314d0f00000) + : cbOp == 4 ? UINT64_C(0x0394831000000000) : 0) + & (ARCH_BITS >= 64 ? UINT64_MAX : UINT32_MAX); + uint64_t uExpectRip; + + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, s_aTests[i].pfnWorker); + uExpectRip = Ctx.rip.u + (fOkay ? ((uint8_t const BS3_FAR *)s_aTests[i].pfnWorker)[-1] + 1 : 0); + + for (iValue = 0; iValue < cValues; iValue++) + { + uint64_t const uExpectRax = fOkay ? paValues[iValue].uDstOut : paValues[iValue].uDstIn; + uint64_t uMemSrc, uMemSrcExpect; + + Ctx.rax.uCcXReg = paValues[iValue].uDstIn; + if (!s_aTests[i].fMemSrc) + { + Ctx.rbx.u64 = paValues[iValue].uSrc | uSrcGarbage; + uMemSrcExpect = uMemSrc = ~(paValues[iValue].uSrc | uSrcGarbage); + } + else + { + uMemSrcExpect = uMemSrc = paValues[iValue].uSrc | uSrcGarbage; + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, &Ctx.fs, &uMemSrc); + } + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); + + if ( TrapFrame.bXcpt != bExpectXcpt + || TrapFrame.Ctx.rip.u != uExpectRip + || TrapFrame.Ctx.rbx.u != Ctx.rbx.u + || TrapFrame.Ctx.rax.u != uExpectRax + /* check that nothing else really changed: */ + || TrapFrame.Ctx.rflags.u16 != Ctx.rflags.u16 + || TrapFrame.Ctx.rcx.u != Ctx.rcx.u + || TrapFrame.Ctx.rdx.u != Ctx.rdx.u + || TrapFrame.Ctx.rsp.u != Ctx.rsp.u + || TrapFrame.Ctx.rbp.u != Ctx.rbp.u + || TrapFrame.Ctx.rsi.u != Ctx.rsi.u + || TrapFrame.Ctx.rdi.u != Ctx.rdi.u + || uMemSrc != uMemSrcExpect + ) + { + Bs3TestFailedF("test #%i value #%i failed: input %#RX32, %#RX64", + i, iValue, paValues[iValue].uDstIn, paValues[iValue].uSrc); + if (TrapFrame.bXcpt != bExpectXcpt) + Bs3TestFailedF("Expected bXcpt = %#x, got %#x", bExpectXcpt, TrapFrame.bXcpt); + if (TrapFrame.Ctx.rip.u != uExpectRip) + Bs3TestFailedF("Expected RIP = %#06RX64, got %#06RX64", uExpectRip, TrapFrame.Ctx.rip.u); + if (TrapFrame.Ctx.rax.u != uExpectRax) + Bs3TestFailedF("Expected RAX = %#010RX64, got %#010RX64", uExpectRax, TrapFrame.Ctx.rax.u); + if (TrapFrame.Ctx.rbx.u != Ctx.rbx.u) + Bs3TestFailedF("Expected RBX = %#06RX64, got %#06RX64 (dst)", Ctx.rbx.u, TrapFrame.Ctx.rbx.u); + + if (TrapFrame.Ctx.rflags.u16 != Ctx.rflags.u16) + Bs3TestFailedF("Expected EFLAGS = %#06RX32, got %#06RX32", Ctx.rflags.u16, TrapFrame.Ctx.rflags.u16); + if (TrapFrame.Ctx.rcx.u != Ctx.rcx.u) + Bs3TestFailedF("Expected RCX = %#06RX64, got %#06RX64", Ctx.rcx.u, TrapFrame.Ctx.rcx.u); + if (TrapFrame.Ctx.rdx.u != Ctx.rdx.u) + Bs3TestFailedF("Expected RDX = %#06RX64, got %#06RX64 (src)", Ctx.rdx.u, TrapFrame.Ctx.rdx.u); + if (TrapFrame.Ctx.rsp.u != Ctx.rsp.u) + Bs3TestFailedF("Expected RSP = %#06RX64, got %#06RX64", Ctx.rsp.u, TrapFrame.Ctx.rsp.u); + if (TrapFrame.Ctx.rbp.u != Ctx.rbp.u) + Bs3TestFailedF("Expected RBP = %#06RX64, got %#06RX64", Ctx.rbp.u, TrapFrame.Ctx.rbp.u); + if (TrapFrame.Ctx.rsi.u != Ctx.rsi.u) + Bs3TestFailedF("Expected RSI = %#06RX64, got %#06RX64", Ctx.rsi.u, TrapFrame.Ctx.rsi.u); + if (TrapFrame.Ctx.rdi.u != Ctx.rdi.u) + Bs3TestFailedF("Expected RDI = %#06RX64, got %#06RX64", Ctx.rdi.u, TrapFrame.Ctx.rdi.u); + if (uMemSrc != uMemSrcExpect) + Bs3TestFailedF("Expected uMemSrc = %#06RX64, got %#06RX64", (uint64_t)uMemSrcExpect, (uint64_t)uMemSrc); + } + } + } + Ctx.rflags.u16 &= ~X86_EFL_STATUS_BITS; + } + + return 0; +} + +#if 0 /* Program for generating CRC32 value sets: */ +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> + +int main(int argc, char **argv) +{ + int cbOp = atoi(argv[1]); + uint32_t uBefore = atoi(argv[2]); + int i = 3; + while (i < argc) + { + unsigned long long uValue = strtoull(argv[i], NULL, 0); + uint32_t uAfter = uBefore; + switch (cbOp) + { + case 1: + __asm__ __volatile__("crc32b %2, %0" : "=r" (uAfter) : "0" (uAfter), "r" ((uint8_t)uValue)); + printf(" { UINT32_C(%#010x), UINT32_C(%#010x), UINT8_C(%#04x) },\n", + uBefore, uAfter, (unsigned)(uint8_t)uValue); + break; + case 2: + __asm__ __volatile__("crc32w %2, %0" : "=r" (uAfter) : "0" (uAfter), "r" ((uint16_t)uValue)); + printf(" { UINT32_C(%#010x), UINT32_C(%#010x), UINT16_C(%#06x) },\n", + uBefore, uAfter, (unsigned)(uint16_t)uValue); + break; + case 4: + __asm__ __volatile__("crc32l %2, %0" : "=r" (uAfter) : "0" (uAfter), "r" ((uint32_t)uValue)); + printf(" { UINT32_C(%#010x), UINT32_C(%#010x), UINT32_C(%#010x) },\n", + uBefore, uAfter, (uint32_t)uValue); + break; + case 8: + { + uint64_t u64After = uBefore; + __asm__ __volatile__("crc32q %2, %0" : "=r" (u64After) : "0" (u64After), "r" (uValue)); + uAfter = (uint32_t)u64After; + printf(" { UINT32_C(%#010x), UINT32_C(%#010x), UINT64_C(%#018llx) },\n", uBefore, uAfter, uValue); + break; + } + } + + /* next */ + uBefore = uAfter; + i++; + } + return 0; +} +#endif + + +/* + * + */ +# if ARCH_BITS == 64 + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_cmpxchg16b)(uint8_t bMode) +{ + BS3REGCTX Ctx; + BS3REGCTX ExpectCtx; + BS3TRAPFRAME TrapFrame; + RTUINT128U au128[3]; + PRTUINT128U pau128 = RT_ALIGN_PT(&au128[0], sizeof(RTUINT128U), PRTUINT128U); + bool const fSupportCX16 = RT_BOOL(ASMCpuId_ECX(1) & X86_CPUID_FEATURE_ECX_CX16); + unsigned iFlags; + unsigned offBuf; + unsigned iMatch; + unsigned iWorker; + static struct + { + bool fLocked; + uint8_t offUd2; + FNBS3FAR *pfnWorker; + } const s_aWorkers[] = + { + { false, 4, BS3_CMN_NM(bs3CpuInstr2_cmpxchg16b_rdi_ud2) }, + { false, 5, BS3_CMN_NM(bs3CpuInstr2_o16_cmpxchg16b_rdi_ud2) }, + { false, 5, BS3_CMN_NM(bs3CpuInstr2_repz_cmpxchg16b_rdi_ud2) }, + { false, 5, BS3_CMN_NM(bs3CpuInstr2_repnz_cmpxchg16b_rdi_ud2) }, + { true, 1+4, BS3_CMN_NM(bs3CpuInstr2_lock_cmpxchg16b_rdi_ud2) }, + { true, 1+5, BS3_CMN_NM(bs3CpuInstr2_lock_o16_cmpxchg16b_rdi_ud2) }, + { true, 1+5, BS3_CMN_NM(bs3CpuInstr2_lock_repz_cmpxchg16b_rdi_ud2) }, + { true, 1+5, BS3_CMN_NM(bs3CpuInstr2_lock_repnz_cmpxchg16b_rdi_ud2) }, + }; + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&ExpectCtx, 0, sizeof(ExpectCtx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + Bs3MemSet(pau128, 0, sizeof(pau128[0]) * 2); + + /* + * Create test context. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 512); + if (!fSupportCX16) + Bs3TestPrintf("Note! CMPXCHG16B is not supported by the CPU!\n"); + + /* + * One loop with the normal variant and one with the locked one + */ + g_usBs3TestStep = 0; + for (iWorker = 0; iWorker < RT_ELEMENTS(s_aWorkers); iWorker++) + { + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, s_aWorkers[iWorker].pfnWorker); + + /* + * One loop with all status flags set, and one with them clear. + */ + Ctx.rflags.u16 |= X86_EFL_STATUS_BITS; + for (iFlags = 0; iFlags < 2; iFlags++) + { + Bs3MemCpy(&ExpectCtx, &Ctx, sizeof(ExpectCtx)); + + for (offBuf = 0; offBuf < sizeof(RTUINT128U); offBuf++) + { +# define CX16_OLD_LO UINT64_C(0xabb6345dcc9c4bbd) +# define CX16_OLD_HI UINT64_C(0x7b06ea35749549ab) +# define CX16_MISMATCH_LO UINT64_C(0xbace3e3590f18981) +# define CX16_MISMATCH_HI UINT64_C(0x9b385e8bfd5b4000) +# define CX16_STORE_LO UINT64_C(0x5cbd27d251f6559b) +# define CX16_STORE_HI UINT64_C(0x17ff434ed1b54963) + + PRTUINT128U pBuf = (PRTUINT128U)&pau128->au8[offBuf]; + + ExpectCtx.rax.u = Ctx.rax.u = CX16_MISMATCH_LO; + ExpectCtx.rdx.u = Ctx.rdx.u = CX16_MISMATCH_HI; + for (iMatch = 0; iMatch < 2; iMatch++) + { + uint8_t bExpectXcpt; + pBuf->s.Lo = CX16_OLD_LO; + pBuf->s.Hi = CX16_OLD_HI; + ExpectCtx.rdi.u = Ctx.rdi.u = (uintptr_t)pBuf; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); + g_usBs3TestStep++; + //Bs3TestPrintf("Test: iFlags=%d offBuf=%d iMatch=%u iWorker=%u\n", iFlags, offBuf, iMatch, iWorker); + bExpectXcpt = X86_XCPT_UD; + if (fSupportCX16) + { + if (offBuf & 15) + { + bExpectXcpt = X86_XCPT_GP; + ExpectCtx.rip.u = Ctx.rip.u; + ExpectCtx.rflags.u32 = Ctx.rflags.u32; + } + else + { + ExpectCtx.rax.u = CX16_OLD_LO; + ExpectCtx.rdx.u = CX16_OLD_HI; + if (iMatch & 1) + ExpectCtx.rflags.u32 = Ctx.rflags.u32 | X86_EFL_ZF; + else + ExpectCtx.rflags.u32 = Ctx.rflags.u32 & ~X86_EFL_ZF; + ExpectCtx.rip.u = Ctx.rip.u + s_aWorkers[iWorker].offUd2; + } + ExpectCtx.rflags.u32 |= X86_EFL_RF; + } + if ( !Bs3TestCheckRegCtxEx(&TrapFrame.Ctx, &ExpectCtx, 0 /*cbPcAdjust*/, 0 /*cbSpAdjust*/, + 0 /*fExtraEfl*/, "lm64", 0 /*idTestStep*/) + || TrapFrame.bXcpt != bExpectXcpt) + { + if (TrapFrame.bXcpt != bExpectXcpt) + Bs3TestFailedF("Expected bXcpt=#%x, got %#x (%#x)", bExpectXcpt, TrapFrame.bXcpt, TrapFrame.uErrCd); + Bs3TestFailedF("^^^ iWorker=%d iFlags=%d offBuf=%d iMatch=%u\n", iWorker, iFlags, offBuf, iMatch); + ASMHalt(); + } + + ExpectCtx.rax.u = Ctx.rax.u = CX16_OLD_LO; + ExpectCtx.rdx.u = Ctx.rdx.u = CX16_OLD_HI; + } + } + Ctx.rflags.u16 &= ~X86_EFL_STATUS_BITS; + } + } + + return 0; +} + + +static void bs3CpuInstr2_fsgsbase_ExpectUD(uint8_t bMode, PBS3REGCTX pCtx, PBS3REGCTX pExpectCtx, PBS3TRAPFRAME pTrapFrame) +{ + pCtx->rbx.u = 0; + Bs3MemCpy(pExpectCtx, pCtx, sizeof(*pExpectCtx)); + Bs3TrapSetJmpAndRestore(pCtx, pTrapFrame); + pExpectCtx->rip.u = pCtx->rip.u; + pExpectCtx->rflags.u32 |= X86_EFL_RF; + if ( !Bs3TestCheckRegCtxEx(&pTrapFrame->Ctx, pExpectCtx, 0 /*cbPcAdjust*/, 0 /*cbSpAdjust*/, 0 /*fExtraEfl*/, "lm64", + 0 /*idTestStep*/) + || pTrapFrame->bXcpt != X86_XCPT_UD) + { + Bs3TestFailedF("Expected #UD, got %#x (%#x)", pTrapFrame->bXcpt, pTrapFrame->uErrCd); + ASMHalt(); + } +} + + +static bool bs3CpuInstr2_fsgsbase_VerifyWorker(uint8_t bMode, PBS3REGCTX pCtx, PBS3REGCTX pExpectCtx, PBS3TRAPFRAME pTrapFrame, + BS3CI2FSGSBASE const *pFsGsBaseWorker, unsigned *puIter) +{ + bool fPassed = true; + unsigned iValue = 0; + static const struct + { + bool fGP; + uint64_t u64Base; + } s_aValues64[] = + { + { false, UINT64_C(0x0000000000000000) }, + { false, UINT64_C(0x0000000000000001) }, + { false, UINT64_C(0x0000000000000010) }, + { false, UINT64_C(0x0000000000000123) }, + { false, UINT64_C(0x0000000000001234) }, + { false, UINT64_C(0x0000000000012345) }, + { false, UINT64_C(0x0000000000123456) }, + { false, UINT64_C(0x0000000001234567) }, + { false, UINT64_C(0x0000000012345678) }, + { false, UINT64_C(0x0000000123456789) }, + { false, UINT64_C(0x000000123456789a) }, + { false, UINT64_C(0x00000123456789ab) }, + { false, UINT64_C(0x0000123456789abc) }, + { false, UINT64_C(0x00007ffffeefefef) }, + { false, UINT64_C(0x00007fffffffffff) }, + { true, UINT64_C(0x0000800000000000) }, + { true, UINT64_C(0x0000800000000000) }, + { true, UINT64_C(0x0000800000000333) }, + { true, UINT64_C(0x0001000000000000) }, + { true, UINT64_C(0x0012000000000000) }, + { true, UINT64_C(0x0123000000000000) }, + { true, UINT64_C(0x1234000000000000) }, + { true, UINT64_C(0xffff300000000000) }, + { true, UINT64_C(0xffff7fffffffffff) }, + { true, UINT64_C(0xffff7fffffffffff) }, + { false, UINT64_C(0xffff800000000000) }, + { false, UINT64_C(0xffffffffffeefefe) }, + { false, UINT64_C(0xffffffffffffffff) }, + { false, UINT64_C(0xffffffffffffffff) }, + { false, UINT64_C(0x00000000efefefef) }, + { false, UINT64_C(0x0000000080204060) }, + { false, UINT64_C(0x00000000ddeeffaa) }, + { false, UINT64_C(0x00000000fdecdbca) }, + { false, UINT64_C(0x000000006098456b) }, + { false, UINT64_C(0x0000000098506099) }, + { false, UINT64_C(0x00000000206950bc) }, + { false, UINT64_C(0x000000009740395d) }, + { false, UINT64_C(0x0000000064a9455e) }, + { false, UINT64_C(0x00000000d20b6eff) }, + { false, UINT64_C(0x0000000085296d46) }, + { false, UINT64_C(0x0000000007000039) }, + { false, UINT64_C(0x000000000007fe00) }, + }; + + Bs3RegCtxSetRipCsFromCurPtr(pCtx, pFsGsBaseWorker->pfnVerifyWorker); + if (pFsGsBaseWorker->f64BitOperand) + { + for (iValue = 0; iValue < RT_ELEMENTS(s_aValues64); iValue++) + { + bool const fGP = s_aValues64[iValue].fGP; + + pCtx->rbx.u = s_aValues64[iValue].u64Base; + pCtx->rcx.u = 0; + pCtx->cr4.u |= X86_CR4_FSGSBASE; + Bs3MemCpy(pExpectCtx, pCtx, sizeof(*pExpectCtx)); + Bs3TrapSetJmpAndRestore(pCtx, pTrapFrame); + pExpectCtx->rip.u = pCtx->rip.u + (!fGP ? pFsGsBaseWorker->offVerifyWorkerUd2 : 0); + pExpectCtx->rbx.u = !fGP ? 0 : s_aValues64[iValue].u64Base; + pExpectCtx->rcx.u = !fGP ? s_aValues64[iValue].u64Base : 0; + pExpectCtx->rflags.u32 |= X86_EFL_RF; + if ( !Bs3TestCheckRegCtxEx(&pTrapFrame->Ctx, pExpectCtx, 0 /*cbPcAdjust*/, 0 /*cbSpAdjust*/, + 0 /*fExtraEfl*/, "lm64", 0 /*idTestStep*/) + || (fGP && pTrapFrame->bXcpt != X86_XCPT_GP)) + { + if (fGP && pTrapFrame->bXcpt != X86_XCPT_GP) + Bs3TestFailedF("Expected #GP, got %#x (%#x)", pTrapFrame->bXcpt, pTrapFrame->uErrCd); + else + Bs3TestFailedF("iValue=%u\n", iValue); + fPassed = false; + break; + } + } + } + else + { + for (iValue = 0; iValue < RT_ELEMENTS(s_aValues64); iValue++) + { + pCtx->rbx.u = s_aValues64[iValue].u64Base; + pCtx->rcx.u = ~s_aValues64[iValue].u64Base; + pCtx->cr4.u |= X86_CR4_FSGSBASE; + Bs3MemCpy(pExpectCtx, pCtx, sizeof(*pExpectCtx)); + Bs3TrapSetJmpAndRestore(pCtx, pTrapFrame); + pExpectCtx->rip.u = pCtx->rip.u + pFsGsBaseWorker->offVerifyWorkerUd2; + pExpectCtx->rbx.u = 0; + pExpectCtx->rcx.u = s_aValues64[iValue].u64Base & UINT64_C(0x00000000ffffffff); + pExpectCtx->rflags.u32 |= X86_EFL_RF; + if (!Bs3TestCheckRegCtxEx(&pTrapFrame->Ctx, pExpectCtx, 0 /*cbPcAdjust*/, 0 /*cbSpAdjust*/, + 0 /*fExtraEfl*/, "lm64", 0 /*idTestStep*/)) + { + Bs3TestFailedF("iValue=%u\n", iValue); + fPassed = false; + break; + } + } + } + + *puIter = iValue; + return fPassed; +} + + +static void bs3CpuInstr2_rdfsbase_rdgsbase_Common(uint8_t bMode, BS3CI2FSGSBASE const *paFsGsBaseWorkers, + unsigned cFsGsBaseWorkers, uint32_t idxFsGsBaseMsr) +{ + BS3REGCTX Ctx; + BS3REGCTX ExpectCtx; + BS3TRAPFRAME TrapFrame; + unsigned iWorker; + unsigned iIter; + uint32_t uDummy; + uint32_t uStdExtFeatEbx; + bool fSupportsFsGsBase; + + ASMCpuId_Idx_ECX(7, 0, &uDummy, &uStdExtFeatEbx, &uDummy, &uDummy); + fSupportsFsGsBase = RT_BOOL(uStdExtFeatEbx & X86_CPUID_STEXT_FEATURE_EBX_FSGSBASE); + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&ExpectCtx, 0, sizeof(ExpectCtx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 512); + + for (iWorker = 0; iWorker < cFsGsBaseWorkers; iWorker++) + { + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, paFsGsBaseWorkers[iWorker].pfnWorker); + if (fSupportsFsGsBase) + { + uint64_t const uBaseAddr = ASMRdMsr(idxFsGsBaseMsr); + + /* CR4.FSGSBASE disabled -> #UD. */ + Ctx.cr4.u &= ~X86_CR4_FSGSBASE; + bs3CpuInstr2_fsgsbase_ExpectUD(bMode, &Ctx, &ExpectCtx, &TrapFrame); + + /* Read and verify existing base address. */ + Ctx.rbx.u = 0; + Ctx.cr4.u |= X86_CR4_FSGSBASE; + Bs3MemCpy(&ExpectCtx, &Ctx, sizeof(ExpectCtx)); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); + ExpectCtx.rip.u = Ctx.rip.u + paFsGsBaseWorkers[iWorker].offWorkerUd2; + ExpectCtx.rbx.u = uBaseAddr; + ExpectCtx.rflags.u32 |= X86_EFL_RF; + if (!Bs3TestCheckRegCtxEx(&TrapFrame.Ctx, &ExpectCtx, 0 /*cbPcAdjust*/, 0 /*cbSpAdjust*/, 0 /*fExtraEfl*/, "lm64", + 0 /*idTestStep*/)) + { + ASMHalt(); + } + + /* Write, read and verify series of base addresses. */ + if (!bs3CpuInstr2_fsgsbase_VerifyWorker(bMode, &Ctx, &ExpectCtx, &TrapFrame, &paFsGsBaseWorkers[iWorker], &iIter)) + { + Bs3TestFailedF("^^^ %s: iWorker=%u iIter=%u\n", paFsGsBaseWorkers[iWorker].pszDesc, iWorker, iIter); + ASMHalt(); + } + + /* Restore original base address. */ + ASMWrMsr(idxFsGsBaseMsr, uBaseAddr); + + /* Clean used GPRs. */ + Ctx.rbx.u = 0; + Ctx.rcx.u = 0; + } + else + { + /* Unsupported by CPUID -> #UD. */ + Bs3TestPrintf("Note! FSGSBASE is not supported by the CPU!\n"); + bs3CpuInstr2_fsgsbase_ExpectUD(bMode, &Ctx, &ExpectCtx, &TrapFrame); + } + } +} + + +static void bs3CpuInstr2_wrfsbase_wrgsbase_Common(uint8_t bMode, BS3CI2FSGSBASE const *paFsGsBaseWorkers, + unsigned cFsGsBaseWorkers, uint32_t idxFsGsBaseMsr) +{ + BS3REGCTX Ctx; + BS3REGCTX ExpectCtx; + BS3TRAPFRAME TrapFrame; + unsigned iWorker; + unsigned iIter; + uint32_t uDummy; + uint32_t uStdExtFeatEbx; + bool fSupportsFsGsBase; + + ASMCpuId_Idx_ECX(7, 0, &uDummy, &uStdExtFeatEbx, &uDummy, &uDummy); + fSupportsFsGsBase = RT_BOOL(uStdExtFeatEbx & X86_CPUID_STEXT_FEATURE_EBX_FSGSBASE); + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&ExpectCtx, 0, sizeof(ExpectCtx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + Bs3RegCtxSaveEx(&Ctx, bMode, 512); + + for (iWorker = 0; iWorker < cFsGsBaseWorkers; iWorker++) + { + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, paFsGsBaseWorkers[iWorker].pfnWorker); + if (fSupportsFsGsBase) + { + uint64_t const uBaseAddr = ASMRdMsr(idxFsGsBaseMsr); + + /* CR4.FSGSBASE disabled -> #UD. */ + Ctx.cr4.u &= ~X86_CR4_FSGSBASE; + bs3CpuInstr2_fsgsbase_ExpectUD(bMode, &Ctx, &ExpectCtx, &TrapFrame); + + /* Write a base address. */ + Ctx.rbx.u = 0xa0000; + Ctx.cr4.u |= X86_CR4_FSGSBASE; + Bs3MemCpy(&ExpectCtx, &Ctx, sizeof(ExpectCtx)); + Bs3TrapSetJmpAndRestore(&Ctx, &TrapFrame); + ExpectCtx.rip.u = Ctx.rip.u + paFsGsBaseWorkers[iWorker].offWorkerUd2; + ExpectCtx.rflags.u32 |= X86_EFL_RF; + if (!Bs3TestCheckRegCtxEx(&TrapFrame.Ctx, &ExpectCtx, 0 /*cbPcAdjust*/, 0 /*cbSpAdjust*/, 0 /*fExtraEfl*/, "lm64", + 0 /*idTestStep*/)) + { + ASMHalt(); + } + + /* Write and read back series of base addresses. */ + if (!bs3CpuInstr2_fsgsbase_VerifyWorker(bMode, &Ctx, &ExpectCtx, &TrapFrame, &paFsGsBaseWorkers[iWorker], &iIter)) + { + Bs3TestFailedF("^^^ %s: iWorker=%u iIter=%u\n", paFsGsBaseWorkers[iWorker].pszDesc, iWorker, iIter); + ASMHalt(); + } + + /* Restore original base address. */ + ASMWrMsr(idxFsGsBaseMsr, uBaseAddr); + + /* Clean used GPRs. */ + Ctx.rbx.u = 0; + Ctx.rcx.u = 0; + } + else + { + /* Unsupported by CPUID -> #UD. */ + Bs3TestPrintf("Note! FSGSBASE is not supported by the CPU!\n"); + bs3CpuInstr2_fsgsbase_ExpectUD(bMode, &Ctx, &ExpectCtx, &TrapFrame); + } + } +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_wrfsbase)(uint8_t bMode) +{ + bs3CpuInstr2_wrfsbase_wrgsbase_Common(bMode, s_aWrFsBaseWorkers, RT_ELEMENTS(s_aWrFsBaseWorkers), MSR_K8_FS_BASE); + return 0; +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_wrgsbase)(uint8_t bMode) +{ + bs3CpuInstr2_wrfsbase_wrgsbase_Common(bMode, s_aWrGsBaseWorkers, RT_ELEMENTS(s_aWrGsBaseWorkers), MSR_K8_GS_BASE); + return 0; +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_rdfsbase)(uint8_t bMode) +{ + bs3CpuInstr2_rdfsbase_rdgsbase_Common(bMode, s_aRdFsBaseWorkers, RT_ELEMENTS(s_aRdFsBaseWorkers), MSR_K8_FS_BASE); + return 0; +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_NM(bs3CpuInstr2_rdgsbase)(uint8_t bMode) +{ + bs3CpuInstr2_rdfsbase_rdgsbase_Common(bMode, s_aRdGsBaseWorkers, RT_ELEMENTS(s_aRdGsBaseWorkers), MSR_K8_GS_BASE); + return 0; +} + +# endif /* ARCH_BITS == 64 */ + +#endif /* BS3_INSTANTIATING_CMN */ + + + +/* + * Mode specific code. + * Mode specific code. + * Mode specific code. + */ +#ifdef BS3_INSTANTIATING_MODE + + +#endif /* BS3_INSTANTIATING_MODE */ + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-2-template.mac b/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-2-template.mac new file mode 100644 index 00000000..c9204861 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-2-template.mac @@ -0,0 +1,845 @@ +; $Id: bs3-cpu-instr-2-template.mac $ +;; @file +; BS3Kit - bs3-cpu-instr-2 assembly template. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" ; setup environment + + +;********************************************************************************************************************************* +;* Defined Constants And Macros * +;********************************************************************************************************************************* +;; +; Variant on BS3_PROC_BEGIN_CMN w/ BS3_PBC_NEAR that prefixes the function +; with an instruction length byte. +; +; ASSUMES the length is between the start of the function and the .again label. +; +%ifndef BS3CPUINSTR2_PROC_BEGIN_CMN_DEFINED + %define BS3CPUINSTR2_PROC_BEGIN_CMN_DEFINED + %macro BS3CPUINSTR2_PROC_BEGIN_CMN 1 + align 8, db 0cch + db BS3_CMN_NM(%1).again - BS3_CMN_NM(%1) +BS3_PROC_BEGIN_CMN %1, BS3_PBC_NEAR + %endmacro +%endif + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +TMPL_BEGIN_TEXT + + +; +; Test code snippets containing code which differs between 16-bit, 32-bit +; and 64-bit CPUs modes. +; +%ifdef BS3_INSTANTIATING_CMN + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_mul_xBX_ud2, BS3_PBC_NEAR + mul xBX +.again: + ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_mul_xBX_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_imul_xBX_ud2, BS3_PBC_NEAR + imul xBX +.again: + ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_imul_xBX_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_imul_xCX_xBX_ud2, BS3_PBC_NEAR + imul xCX, xBX +.again: + ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_imul_xCX_xBX_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_div_xBX_ud2, BS3_PBC_NEAR + div xBX +.again: + ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_div_xBX_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_idiv_xBX_ud2, BS3_PBC_NEAR + idiv xBX +.again: + ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_idiv_xBX_ud2 + + +; +; BSF / BSR / TZCNT / LZCNT +; +%ifndef EMIT_BITSCAN_DEFINED +%define EMIT_BITSCAN_DEFINED +%macro EMIT_BITSCAN 3 +BS3_PROC_BEGIN_CMN bs3CpuInstr2_ %+ %3 %+ _AX_BX_ud2, BS3_PBC_NEAR + %2 + %1 ax, bx +.again: + ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_ %+ %3 %+ _AX_BX_ud2 + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_ %+ %3 %+ _AX_FSxBX_ud2, BS3_PBC_NEAR + %2 + %1 ax, [fs:xBX] +.again: + ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_ %+ %3 %+ _AX_FSxBX_ud2 + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_ %+ %3 %+ _EAX_EBX_ud2, BS3_PBC_NEAR + %2 + %1 eax, ebx +.again: + ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_ %+ %3 %+ _EAX_EBX_ud2 + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_ %+ %3 %+ _EAX_FSxBX_ud2, BS3_PBC_NEAR + %2 + %1 eax, [fs:xBX] +.again: + ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_ %+ %3 %+ _EAX_FSxBX_ud2 + + %if TMPL_BITS == 64 +BS3_PROC_BEGIN_CMN bs3CpuInstr2_ %+ %3 %+ _RAX_RBX_ud2, BS3_PBC_NEAR + %2 + %1 rax, rbx +.again: + ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_ %+ %3 %+ _RAX_RBX_ud2 + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_ %+ %3 %+ _RAX_FSxBX_ud2, BS3_PBC_NEAR + %2 + %1 rax, [fs:xBX] +.again: + ud2 + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_ %+ %3 %+ _RAX_FSxBX_ud2 + %endif +%endmacro +%endif + +EMIT_BITSCAN bsf, .ignored:, bsf +EMIT_BITSCAN bsr, .ignored:, bsr +EMIT_BITSCAN tzcnt, .ignored:, tzcnt +EMIT_BITSCAN lzcnt, .ignored:, lzcnt +EMIT_BITSCAN bsf, db 0f2h, f2_bsf +EMIT_BITSCAN bsr, db 0f2h, f2_bsr +EMIT_BITSCAN tzcnt, db 0f2h, f2_tzcnt +EMIT_BITSCAN lzcnt, db 0f2h, f2_lzcnt + + +; +; RORX - VEX instruction with a couple of questions about non-standard encodings. +; +;;%define icebp ud2 +BS3_PROC_BEGIN_CMN bs3CpuInstr2_rorx_EBX_EDX_2_icebp, BS3_PBC_NEAR + rorx ebx, edx, 2 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_rorx_EBX_EDX_2_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_rorx_RBX_RDX_2_icebp, BS3_PBC_NEAR +%if TMPL_BITS == 64 + rorx rbx, rdx, 2 +%else + db 0C4h,0E3h,0FBh,0F0h,0DAh,002h ; 32-bit ignores VEX.W=1 (10980xe) +%endif +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_rorx_RBX_RDX_2_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_rorx_EBX_EDX_2_icebp_L1, BS3_PBC_NEAR + db 0C4h, 0E3h, 07Bh | 4h, 0F0h, 0DAh, 002h ; VEX.L=1 should #UD according to the docs +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_rorx_EBX_EDX_2_icebp_L1 + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_rorx_EBX_EDX_2_icebp_V1, BS3_PBC_NEAR + db 0C4h, 0E3h, 003h | ~(1 << 3), 0F0h, 0DAh, 002h ; VEX.VVVV=1 - behaviour is undocumented - 10980xe #UD +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_rorx_EBX_EDX_2_icebp_V1 + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_rorx_EBX_EDX_2_icebp_V15, BS3_PBC_NEAR + db 0C4h, 0E3h, 003h | ~(15 << 3), 0F0h, 0DAh, 002h ; VEX.VVVV=15 - behaviour is not documented - 10980xe #UD +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_rorx_EBX_EDX_2_icebp_V15 + + %if TMPL_BITS == 64 +BS3_PROC_BEGIN_CMN bs3CpuInstr2_rorx_EBX_EDX_2_icebp_X1, BS3_PBC_NEAR + db 0C4h, 0E3h & ~40h, 07Bh, 0F0h, 0DAh, 002h ; VEX.X=0 - behaviour is not documented - ignored by 10980xe +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_rorx_EBX_EDX_2_icebp_X1 + %endif + +; A couple of memory variants +BS3_PROC_BEGIN_CMN bs3CpuInstr2_rorx_EBX_DSxDI_36_icebp, BS3_PBC_NEAR + rorx ebx, [xDI], 36 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_rorx_EBX_DSxDI_36_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_rorx_RBX_DSxDI_68_icebp, BS3_PBC_NEAR + %if TMPL_BITS == 64 + rorx rbx, [xDI], 68 + %elif TMPL_BITS == 32 + db 0C4h,0E3h,07Bh,0F0h,01Fh,044h ; 16-bit ignores VEX.W=1 (10980xe) + %else + db 0C4h,0E3h,0FBh,0F0h,01Dh,044h ; 16-bit ignores VEX.W=1 (10980xe) + %endif +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_rorx_RBX_DSxDI_68_icebp + +; +; ANDN (BMI1) +; +BS3_PROC_BEGIN_CMN bs3CpuInstr2_andn_RAX_RCX_RBX_icebp, BS3_PBC_NEAR +%if TMPL_BITS == 64 + andn rax, rcx, rbx +%else + db 0C4h,0E2h,0F0h,0F2h,0C3h ; 32-bit & 16-bit ignores VEX.W=1 (10980xe) +%endif +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_andn_RAX_RCX_RBX_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_andn_EAX_ECX_EBX_icebp, BS3_PBC_NEAR + andn eax, ecx, ebx +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_andn_EAX_ECX_EBX_icebp + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_andn_RAX_RCX_FSxBX_icebp, BS3_PBC_NEAR +%if TMPL_BITS == 64 + andn rax, rcx, [fs:rbx] +%elif TMPL_BITS == 32 + db 064h,0C4h,0E2h,0F0h,0F2h,003h ; andn rax, rcx, [fs:ebx] +%else + db 064h,0C4h,0E2h,0F0h,0F2h,007h ; andn rax, rcx, [fs:bx] +%endif +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_andn_RAX_RCX_FSxBX_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_andn_EAX_ECX_FSxBX_icebp, BS3_PBC_NEAR + andn eax, ecx, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_andn_EAX_ECX_FSxBX_icebp + + +; +; BEXTR / SHLX / SARX / SHRX - BMI1 (opcode f7h) +; BZHI - BMI2 (opcode f5h) +; +; @param %1 instruction +; @param %2 opcode +; @param %3 prefix +; +%ifndef SHLX_SARX_SHRX_DEFINED +%define SHLX_SARX_SHRX_DEFINED +%macro SHLX_SARX_SHRX 3 + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_ %+ %1 %+ _RAX_RBX_RCX_icebp, BS3_PBC_NEAR + %if TMPL_BITS == 64 + %1 rax, rbx, rcx ; SHLX=C4E2F1F7C3 + %else + db 0C4h,0E2h,0F0h|%3,%2,0C3h ; 32-bit & 16-bit ignores VEX.W=1 (10980xe) + %endif +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_ %+ %1 %+ _RAX_RBX_RCX_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_ %+ %1 %+ _EAX_EBX_ECX_icebp, BS3_PBC_NEAR + %1 eax, ebx, ecx +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_ %+ %1 %+ _EAX_EBX_ECX_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_ %+ %1 %+ _RAX_FSxBX_RCX_icebp, BS3_PBC_NEAR + %if TMPL_BITS == 64 + %1 rax, [fs:rbx], rcx ; SHLX=64C4E2F1F703 + %elif TMPL_BITS == 32 + db 064h,0C4h,0E2h,0F0h|%3,%2,003h + %else + db 064h,0C4h,0E2h,0F0h|%3,%2,007h + %endif +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_ %+ %1 %+ _RAX_FSxBX_RCX_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_ %+ %1 %+ _EAX_FSxBX_ECX_icebp, BS3_PBC_NEAR + %1 eax, [fs:xBX], ecx +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_ %+ %1 %+ _EAX_FSxBX_ECX_icebp + +%endmacro +%endif + +SHLX_SARX_SHRX bextr, 0f7h, 0 ; none +SHLX_SARX_SHRX shlx, 0f7h, 1 ; 66h +SHLX_SARX_SHRX sarx, 0f7h, 2 ; f3h +SHLX_SARX_SHRX shrx, 0f7h, 3 ; f2h +SHLX_SARX_SHRX bzhi, 0f5h, 0 ; none + +; +; PPEP / PEXT - BMI2 (opcode f5h) +; +; @param %1 instruction +; @param %2 opcode +; @param %3 prefix +; +%ifndef PDEP_PEXT_DEFINED +%define PDEP_PEXT_DEFINED +%macro PDEP_PEXT_ 3 + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_ %+ %1 %+ _RAX_RCX_RBX_icebp, BS3_PBC_NEAR + %if TMPL_BITS == 64 + %1 rax, rcx, rbx + %else + db 0C4h,0E2h,0F0h|%3,%2,0C3h ; 32-bit & 16-bit ignores VEX.W=1 (10980xe) + %endif +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_ %+ %1 %+ _RAX_RCX_RBX_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_ %+ %1 %+ _EAX_ECX_EBX_icebp, BS3_PBC_NEAR + %1 eax, ecx, ebx +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_ %+ %1 %+ _EAX_ECX_EBX_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_ %+ %1 %+ _RAX_RCX_FSxBX_icebp, BS3_PBC_NEAR + %if TMPL_BITS == 64 + %1 rax, rcx, [fs:rbx] + %elif TMPL_BITS == 32 + db 064h,0C4h,0E2h,0F0h|%3,%2,003h + %else + db 064h,0C4h,0E2h,0F0h|%3,%2,007h + %endif +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_ %+ %1 %+ _RAX_RCX_FSxBX_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_ %+ %1 %+ _EAX_ECX_FSxBX_icebp, BS3_PBC_NEAR + %1 eax, ecx, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_ %+ %1 %+ _EAX_ECX_FSxBX_icebp + +%endmacro +%endif + +PDEP_PEXT_ pext, 0f5h, 2 ; f3h +PDEP_PEXT_ pdep, 0f5h, 3 ; f2h + +; +; BLSR / BLSMSK / BLSI +; These are encoded in the exact same way, only the /r differs (%2). +; +%ifndef BLSR_BLSMSK_BLSI_DEFINED +%define BLSR_BLSMSK_BLSI_DEFINED +%macro BLSR_BLSMSK_BLSI 2 + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_ %+ %1 %+ _RAX_RBX_icebp, BS3_PBC_NEAR + %if TMPL_BITS == 64 + %1 rax, rbx ; BLSR=C4E2F8F3CB + %else + db 0C4h,0E2h,0F8h,0F3h,0C3h | (%2 << 3) ; 32-bit & 16-bit ignores VEX.W=1 (10980xe) + %endif +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_ %+ %1 %+ _RAX_RBX_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_ %+ %1 %+ _EAX_EBX_icebp, BS3_PBC_NEAR + %1 eax, ebx +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_ %+ %1 %+ _EAX_EBX_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_ %+ %1 %+ _RAX_FSxBX_icebp, BS3_PBC_NEAR + %if TMPL_BITS == 64 + %1 rax, [fs:rbx] ; BSLR=64C4E2F8F30B + %elif TMPL_BITS == 32 + db 064h,0C4h,0E2h,0F8h,0F3h,003h | (%2 << 3) + %else + db 064h,0C4h,0E2h,0F8h,0F3h,007h | (%2 << 3) + %endif +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_ %+ %1 %+ _RAX_FSxBX_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_ %+ %1 %+ _EAX_FSxBX_icebp, BS3_PBC_NEAR + %1 eax, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_ %+ %1 %+ _EAX_FSxBX_icebp + +%endmacro +%endif + +BLSR_BLSMSK_BLSI blsr, 1 +BLSR_BLSMSK_BLSI blsmsk, 2 +BLSR_BLSMSK_BLSI blsi, 3 + +; +; MULX +; +BS3_PROC_BEGIN_CMN bs3CpuInstr2_mulx_RAX_RCX_RBX_RDX_icebp, BS3_PBC_NEAR + %if TMPL_BITS == 64 + mulx rax, rcx, rbx ; C4E2F3F6C3 + %else + db 0C4h,0E2h,0F3h,0F6h,0C3h ; 32-bit & 16-bit ignores VEX.W=1 (10980xe) + %endif +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_mulx_RAX_RCX_RBX_RDX_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_mulx_RCX_RCX_RBX_RDX_icebp, BS3_PBC_NEAR + %if TMPL_BITS == 64 + mulx rcx, rcx, rbx ; C4E2F3F6CB + %else + db 0C4h,0E2h,0F3h,0F6h,0CBh ; 32-bit & 16-bit ignores VEX.W=1 (10980xe) + %endif +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_mulx_RCX_RCX_RBX_RDX_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_mulx_RAX_RCX_FSxBX_RDX_icebp, BS3_PBC_NEAR + %if TMPL_BITS == 64 + mulx rax, rcx, [fs:rbx] ; 64C4E2F3F603 + %elif TMPL_BITS == 32 + db 064h,0C4h,0E2h,0F3h,0F6h,003h ; 32-bit & 16-bit ignores VEX.W=1 (10980xe) + %else + db 064h,0C4h,0E2h,0F3h,0F6h,007h ; 32-bit & 16-bit ignores VEX.W=1 (10980xe) + %endif +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_mulx_RAX_RCX_FSxBX_RDX_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_mulx_EAX_ECX_EBX_EDX_icebp, BS3_PBC_NEAR + mulx eax, ecx, ebx +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_mulx_EAX_ECX_EBX_EDX_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_mulx_ECX_ECX_EBX_EDX_icebp, BS3_PBC_NEAR + mulx ecx, ecx, ebx +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_mulx_ECX_ECX_EBX_EDX_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_mulx_EAX_ECX_FSxBX_EDX_icebp, BS3_PBC_NEAR + mulx eax, ecx, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_mulx_EAX_ECX_FSxBX_EDX_icebp + + +; +; POPCNT +; +BS3_PROC_BEGIN_CMN bs3CpuInstr2_popcnt_AX_BX_icebp, BS3_PBC_NEAR + popcnt ax, bx +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_popcnt_AX_BX_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_popcnt_EAX_EBX_icebp, BS3_PBC_NEAR + popcnt eax, ebx +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_popcnt_EAX_EBX_icebp + + %if TMPL_BITS == 64 +BS3_PROC_BEGIN_CMN bs3CpuInstr2_popcnt_RAX_RBX_icebp, BS3_PBC_NEAR + popcnt rax, rbx +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_popcnt_RAX_RBX_icebp + %endif + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_popcnt_AX_FSxBX_icebp, BS3_PBC_NEAR + popcnt ax, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_popcnt_AX_FSxBX_icebp + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_popcnt_EAX_FSxBX_icebp, BS3_PBC_NEAR + popcnt eax, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_popcnt_EAX_FSxBX_icebp + + %if TMPL_BITS == 64 +BS3_PROC_BEGIN_CMN bs3CpuInstr2_popcnt_RAX_FSxBX_icebp, BS3_PBC_NEAR + popcnt rax, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_popcnt_RAX_FSxBX_icebp + %endif + + +; +; CRC32 +; +BS3CPUINSTR2_PROC_BEGIN_CMN bs3CpuInstr2_crc32_EAX_BL_icebp + crc32 eax, bl +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_crc32_EAX_BL_icebp + +BS3CPUINSTR2_PROC_BEGIN_CMN bs3CpuInstr2_crc32_EAX_BX_icebp + crc32 eax, bx +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_crc32_EAX_BX_icebp + +BS3CPUINSTR2_PROC_BEGIN_CMN bs3CpuInstr2_crc32_EAX_EBX_icebp + crc32 eax, ebx +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_crc32_EAX_EBX_icebp + + %if TMPL_BITS == 64 +BS3CPUINSTR2_PROC_BEGIN_CMN bs3CpuInstr2_crc32_EAX_RBX_icebp + crc32 rax, rbx +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_crc32_EAX_RBX_icebp + %endif + + +BS3CPUINSTR2_PROC_BEGIN_CMN bs3CpuInstr2_crc32_EAX_byte_FSxBX_icebp + crc32 eax, byte [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_crc32_EAX_byte_FSxBX_icebp + +BS3CPUINSTR2_PROC_BEGIN_CMN bs3CpuInstr2_crc32_EAX_word_FSxBX_icebp + crc32 eax, word [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_crc32_EAX_word_FSxBX_icebp + +BS3CPUINSTR2_PROC_BEGIN_CMN bs3CpuInstr2_crc32_EAX_dword_FSxBX_icebp + crc32 eax, dword [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_crc32_EAX_dword_FSxBX_icebp + + %if TMPL_BITS == 64 +BS3CPUINSTR2_PROC_BEGIN_CMN bs3CpuInstr2_crc32_EAX_qword_FSxBX_icebp + crc32 rax, qword [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr2_crc32_EAX_qword_FSxBX_icebp + %endif + + +; +; CMPXCHG16B +; + %if TMPL_BITS == 64 +BS3_PROC_BEGIN_CMN bs3CpuInstr2_cmpxchg16b_rdi_ud2, BS3_PBC_NEAR + cmpxchg16b [rdi] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 4) +BS3_PROC_END_CMN bs3CpuInstr2_cmpxchg16b_rdi_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_lock_cmpxchg16b_rdi_ud2, BS3_PBC_NEAR + lock cmpxchg16b [rdi] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 5) +BS3_PROC_END_CMN bs3CpuInstr2_lock_cmpxchg16b_rdi_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_o16_cmpxchg16b_rdi_ud2, BS3_PBC_NEAR + o16 cmpxchg16b [rdi] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 5) +BS3_PROC_END_CMN bs3CpuInstr2_o16_cmpxchg16b_rdi_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_lock_o16_cmpxchg16b_rdi_ud2, BS3_PBC_NEAR + db 0f0h, 066h + cmpxchg16b [rdi] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 6) +BS3_PROC_END_CMN bs3CpuInstr2_lock_o16_cmpxchg16b_rdi_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_repz_cmpxchg16b_rdi_ud2, BS3_PBC_NEAR + repz cmpxchg16b [rdi] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 5) +BS3_PROC_END_CMN bs3CpuInstr2_repz_cmpxchg16b_rdi_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_lock_repz_cmpxchg16b_rdi_ud2, BS3_PBC_NEAR + db 0f0h, 0f3h + cmpxchg16b [rdi] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 6) +BS3_PROC_END_CMN bs3CpuInstr2_lock_repz_cmpxchg16b_rdi_ud2 + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_repnz_cmpxchg16b_rdi_ud2, BS3_PBC_NEAR + repnz cmpxchg16b [rdi] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 5) +BS3_PROC_END_CMN bs3CpuInstr2_repnz_cmpxchg16b_rdi_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_lock_repnz_cmpxchg16b_rdi_ud2, BS3_PBC_NEAR + db 0f0h, 0f2h + cmpxchg16b [rdi] +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 6) +BS3_PROC_END_CMN bs3CpuInstr2_lock_repnz_cmpxchg16b_rdi_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_wrfsbase_rbx_ud2, BS3_PBC_NEAR + wrfsbase rbx +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 5) +BS3_PROC_END_CMN bs3CpuInstr2_wrfsbase_rbx_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_wrfsbase_ebx_ud2, BS3_PBC_NEAR + wrfsbase ebx +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 4) +BS3_PROC_END_CMN bs3CpuInstr2_wrfsbase_ebx_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_wrgsbase_rbx_ud2, BS3_PBC_NEAR + wrgsbase rbx +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 5) +BS3_PROC_END_CMN bs3CpuInstr2_wrgsbase_rbx_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_wrgsbase_ebx_ud2, BS3_PBC_NEAR + wrgsbase ebx +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 4) +BS3_PROC_END_CMN bs3CpuInstr2_wrgsbase_ebx_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_wrfsbase_rbx_rdfsbase_rcx_ud2, BS3_PBC_NEAR + wrfsbase rbx + mov ebx, 0 + rdfsbase rcx +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 15) +BS3_PROC_END_CMN bs3CpuInstr2_wrfsbase_rbx_rdfsbase_rcx_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_wrfsbase_ebx_rdfsbase_ecx_ud2, BS3_PBC_NEAR + wrfsbase ebx + mov ebx, 0 + rdfsbase ecx +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 13) +BS3_PROC_END_CMN bs3CpuInstr2_wrfsbase_ebx_rdfsbase_ecx_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_wrgsbase_rbx_rdgsbase_rcx_ud2, BS3_PBC_NEAR + wrgsbase rbx + mov ebx, 0 + rdgsbase rcx +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 15) +BS3_PROC_END_CMN bs3CpuInstr2_wrgsbase_rbx_rdgsbase_rcx_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_wrgsbase_ebx_rdgsbase_ecx_ud2, BS3_PBC_NEAR + wrgsbase ebx + mov ebx, 0 + rdgsbase ecx +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 13) +BS3_PROC_END_CMN bs3CpuInstr2_wrfgbase_ebx_rdgsbase_ecx_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_rdfsbase_rbx_ud2, BS3_PBC_NEAR + rdfsbase rbx +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 5) +BS3_PROC_END_CMN bs3CpuInstr2_rdfsbase_rbx_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_rdfsbase_ebx_ud2, BS3_PBC_NEAR + rdfsbase ebx +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 4) +BS3_PROC_END_CMN bs3CpuInstr2_rdfsbase_ebx_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_rdgsbase_rbx_ud2, BS3_PBC_NEAR + rdgsbase rbx +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 5) +BS3_PROC_END_CMN bs3CpuInstr2_rdgsbase_rbx_ud2 + + +BS3_PROC_BEGIN_CMN bs3CpuInstr2_rdgsbase_ebx_ud2, BS3_PBC_NEAR + rdgsbase ebx +.again: + ud2 + jmp .again +AssertCompile(.again - BS3_LAST_LABEL == 4) +BS3_PROC_END_CMN bs3CpuInstr2_rdgsbase_ebx_ud2 + + +;; @todo figure out this fudge. sigh. +times (348) db 0cch ; fudge to avoid 'rderr' during boot. + + %endif ; TMPL_BITS == 64 + + +%endif ; BS3_INSTANTIATING_CMN + +%include "bs3kit-template-footer.mac" ; reset environment + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-2.c b/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-2.c new file mode 100644 index 00000000..92100473 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-2.c @@ -0,0 +1,130 @@ +/* $Id: bs3-cpu-instr-2.c $ */ +/** @file + * BS3Kit - bs3-cpu-instr-2, 16-bit C code. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_mul); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_imul); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_div); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_idiv); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_bsf_tzcnt); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_bsr_lzcnt); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_andn); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_bextr); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_blsr); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_blsmsk); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_blsi); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_bzhi); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_pdep); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_pext); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_rorx); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_shlx); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_sarx); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_shrx); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_mulx); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_popcnt); +BS3TESTMODE_PROTOTYPES_CMN(bs3CpuInstr2_crc32); +BS3TESTMODE_PROTOTYPES_CMN_64(bs3CpuInstr2_cmpxchg16b); +BS3TESTMODE_PROTOTYPES_CMN_64(bs3CpuInstr2_wrfsbase); +BS3TESTMODE_PROTOTYPES_CMN_64(bs3CpuInstr2_wrgsbase); +BS3TESTMODE_PROTOTYPES_CMN_64(bs3CpuInstr2_rdfsbase); +BS3TESTMODE_PROTOTYPES_CMN_64(bs3CpuInstr2_rdgsbase); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static const BS3TESTMODEENTRY g_aModeTests[] = +{ +#if 1 + BS3TESTMODEENTRY_CMN("mul", bs3CpuInstr2_mul), + BS3TESTMODEENTRY_CMN("imul", bs3CpuInstr2_imul), + BS3TESTMODEENTRY_CMN("div", bs3CpuInstr2_div), + BS3TESTMODEENTRY_CMN("idiv", bs3CpuInstr2_idiv), +#endif +#if 1 /* BSF/BSR (386+) & TZCNT/LZCNT (BMI1,ABM) */ + BS3TESTMODEENTRY_CMN("bsf/tzcnt", bs3CpuInstr2_bsf_tzcnt), + BS3TESTMODEENTRY_CMN("bsr/lzcnt", bs3CpuInstr2_bsr_lzcnt), +#endif +#if 1 /* BMI1 */ + BS3TESTMODEENTRY_CMN("andn", bs3CpuInstr2_andn), + BS3TESTMODEENTRY_CMN("bextr", bs3CpuInstr2_bextr), + BS3TESTMODEENTRY_CMN("blsr", bs3CpuInstr2_blsr), + BS3TESTMODEENTRY_CMN("blsmsk", bs3CpuInstr2_blsmsk), + BS3TESTMODEENTRY_CMN("blsi", bs3CpuInstr2_blsi), +#endif +#if 1 /* BMI2 */ + BS3TESTMODEENTRY_CMN("bzhi", bs3CpuInstr2_bzhi), + BS3TESTMODEENTRY_CMN("pdep", bs3CpuInstr2_pdep), + BS3TESTMODEENTRY_CMN("pext", bs3CpuInstr2_pext), + BS3TESTMODEENTRY_CMN("rorx", bs3CpuInstr2_rorx), + BS3TESTMODEENTRY_CMN("shlx", bs3CpuInstr2_shlx), + BS3TESTMODEENTRY_CMN("sarx", bs3CpuInstr2_sarx), + BS3TESTMODEENTRY_CMN("shrx", bs3CpuInstr2_shrx), + BS3TESTMODEENTRY_CMN("mulx", bs3CpuInstr2_mulx), +#endif +#if 1 + BS3TESTMODEENTRY_CMN("popcnt", bs3CpuInstr2_popcnt), /* Intel: POPCNT; AMD: ABM */ + BS3TESTMODEENTRY_CMN("crc32", bs3CpuInstr2_crc32), /* SSE4.2 */ +#endif +#if 1 + BS3TESTMODEENTRY_CMN_64("cmpxchg16b", bs3CpuInstr2_cmpxchg16b), + BS3TESTMODEENTRY_CMN_64("wrfsbase", bs3CpuInstr2_wrfsbase), + BS3TESTMODEENTRY_CMN_64("wrgsbase", bs3CpuInstr2_wrgsbase), + BS3TESTMODEENTRY_CMN_64("rdfsbase", bs3CpuInstr2_rdfsbase), + BS3TESTMODEENTRY_CMN_64("rdgsbase", bs3CpuInstr2_rdgsbase), +#endif +}; + + +BS3_DECL(void) Main_rm() +{ + Bs3InitAll_rm(); + Bs3TestInit("bs3-cpu-instr-2"); + + Bs3TestDoModes_rm(g_aModeTests, RT_ELEMENTS(g_aModeTests)); + + Bs3TestTerm(); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-3-asm.asm b/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-3-asm.asm new file mode 100644 index 00000000..8f0f7c89 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-3-asm.asm @@ -0,0 +1,48 @@ +; $Id: bs3-cpu-instr-3-asm.asm $ +;; @file +; BS3Kit - bs3-cpu-instr-3 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit.mac" + +; +; Instantiate code templates. +; +BS3_INSTANTIATE_COMMON_TEMPLATE "bs3-cpu-instr-3-template.mac" +BS3_INSTANTIATE_TEMPLATE_WITH_WEIRD_ONES "bs3-cpu-instr-3-template.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-3-template.mac b/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-3-template.mac new file mode 100644 index 00000000..1828e412 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-3-template.mac @@ -0,0 +1,2963 @@ +; $Id: bs3-cpu-instr-3-template.mac $ +;; @file +; BS3Kit - bs3-cpu-instr-3 - MMX, SSE and AVX instructions, assembly template. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" ; setup environment + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +TMPL_BEGIN_TEXT + + +; +; Test code snippets containing code which differs between 16-bit, 32-bit +; and 64-bit CPUs modes. +; +%ifdef BS3_INSTANTIATING_CMN + + +;; +; Variant on BS3_PROC_BEGIN_CMN w/ BS3_PBC_NEAR that prefixes the function +; with an instruction length byte. +; +; ASSUMES the length is between the start of the function and the .again label. +; + %ifndef BS3CPUINSTR3_PROC_BEGIN_CMN_DEFINED + %define BS3CPUINSTR3_PROC_BEGIN_CMN_DEFINED + %macro BS3CPUINSTR3_PROC_BEGIN_CMN 1 + align 8, db 0cch + db BS3_CMN_NM(%1).again - BS3_CMN_NM(%1) +BS3_PROC_BEGIN_CMN %1, BS3_PBC_NEAR + %endmacro + %endif + +;; +; The EMIT_INSTR_PLUS_ICEBP macros is for creating a common function for and +; named after a single instruction, followed by a looping ICEBP. +; +; This works like a prefix to the instruction invocation, only exception is that +; instead of [fs:xBX] you write FSxBS as that's what is wanted in the name. +; + %ifndef EMIT_INSTR_PLUS_ICEBP_DEFINED + %define EMIT_INSTR_PLUS_ICEBP_DEFINED + + %macro EMIT_INSTR_PLUS_ICEBP 2 +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %1 %+ _ %+ %2 %+ _icebp + %define FSxBX [fs:xBX] + %1 %2 + %undef FSxBX +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %1 %+ _ %+ %2 %+ _icebp + %endmacro + + %macro EMIT_INSTR_PLUS_ICEBP 3 +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %1 %+ _ %+ %2 %+ _ %+ %3 %+ _icebp + %define FSxBX [fs:xBX] + %1 %2, %3 + %undef FSxBX +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %1 %+ _ %+ %2 %+ _ %+ %3 %+ _icebp + %endmacro + + %macro EMIT_INSTR_PLUS_ICEBP 4 +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %1 %+ _ %+ %2 %+ _ %+ %3 %+ _ %+ %4 %+ _icebp + %define FSxBX [fs:xBX] + %1 %2, %3, %4 + %undef FSxBX +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %1 %+ _ %+ %2 %+ _ %+ %3 %+ _ %+ %4 %+ _icebp + %endmacro + + %macro EMIT_INSTR_PLUS_ICEBP 5 +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %1 %+ _ %+ %2 %+ _ %+ %3 %+ _ %+ %4 %+ _ %+ %5 %+ _icebp + %define FSxBX [fs:xBX] + %1 %2, %3, %4, %5 + %undef FSxBX +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %1 %+ _ %+ %2 %+ _ %+ %3 %+ _ %+ %4 %+ _ %+ %5 %+ _icebp + %endmacro + + %endif + +;; +; Companion to EMIT_INSTR_PLUS_ICEBP for dealing stuff that the assmbler does +; not want to emit. +; +; @param 1 The function name (omitting bs3CpuInstr3_ and _icebp). +; @param 2+ The opcode bytes. FSxBX_PFX and FSxBX_MODRM are defined locally. +; + %ifndef EMIT_INSTR_PLUS_ICEBP_BYTES_DEFINED + %define EMIT_INSTR_PLUS_ICEBP_BYTES_DEFINED + + %macro EMIT_INSTR_PLUS_ICEBP_BYTES 2+ +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %1 %+ _icebp + %define FSxBX_PFX 64h + %if TMPL_BITS == 16 + %define FSxBX_MODRM 07h + %else + %define FSxBX_MODRM 03h + %endif + db %2 + %undef FSxBX_MODRM + %undef FSxBX_PFX +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %1 %+ _icebp + %endmacro + %endif + + + +%ifndef EMIT_TYPE1_INSTR_DEFINED + %define EMIT_TYPE1_INSTR_DEFINED + ;; @param 7 Indicates whether the 2nd and 3rd pair has MMX variants. + %macro EMIT_TYPE1_INSTR 7 +; +; PXOR (SSE2) & VPXOR (AVX2) +; +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %1 %+ _MM1_MM2_icebp + %1 mm1, mm2 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %1 %+ _MM1_MM2_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %1 %+ _MM1_FSxBX_icebp + %1 mm1, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %1 %+ _MM1_FSxBX_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %1 %+ _XMM1_XMM2_icebp + %1 xmm1, xmm2 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %1 %+ _XMM1_XMM2_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %1 %+ _XMM1_FSxBX_icebp + %1 xmm1, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %1 %+ _XMM1_FSxBX_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %2 %+ _XMM1_XMM1_XMM2_icebp + %2 xmm1, xmm1, xmm2 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %2 %+ _XMM1_XMM1_XMM2_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %2 %+ _XMM1_XMM1_FSxBX_icebp + %2 xmm1, xmm1, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %2 %+ _XMM1_XMM1_FSxBX_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %2 %+ _YMM7_YMM2_YMM3_icebp + %2 ymm7, ymm2, ymm3 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %2 %+ _YMM7_YMM2_YMM3_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %2 %+ _YMM7_YMM2_FSxBX_icebp + %2 ymm7, ymm2, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %2 %+ _YMM7_YMM2_FSxBX_icebp + + +; +; XORPS (SSE2) & VXORPS (AVX) +; + %if %7 != 0 +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %3 %+ _MM1_MM2_icebp + %3 mm1, mm2 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %3 %+ _MM1_MM2_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %3 %+ _MM1_FSxBX_icebp + %3 mm1, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %3 %+ _MM1_FSxBX_icebp + %endif + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %3 %+ _XMM1_XMM2_icebp + %3 xmm1, xmm2 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %3 %+ _XMM1_XMM2_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %3 %+ _XMM1_FSxBX_icebp + %3 xmm1, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %3 %+ _XMM1_FSxBX_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %4 %+ _XMM1_XMM1_XMM2_icebp + %4 xmm1, xmm1, xmm2 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %4 %+ _XMM1_XMM1_XMM2_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %4 %+ _XMM1_XMM1_FSxBX_icebp + %4 xmm1, xmm1, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %4 %+ _XMM1_XMM1_FSxBX_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %4 %+ _YMM1_YMM1_YMM2_icebp + %4 ymm1, ymm1, ymm2 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %4 %+ _YMM1_YMM1_YMM2_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %4 %+ _YMM1_YMM1_FSxBX_icebp + %4 ymm1, ymm1, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %4 %+ _YMM1_YMM1_FSxBX_icebp + + + +; +; XORPD (SSE2) & VXORPD (AVX) +; + %if %7 != 0 +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %5 %+ _MM1_MM2_icebp + %5 mm1, mm2 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %5 %+ _MM1_MM2_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %5 %+ _MM1_FSxBX_icebp + %5 mm1, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %5 %+ _MM1_FSxBX_icebp + %endif + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %5 %+ _XMM1_XMM2_icebp + %5 xmm1, xmm2 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %5 %+ _XMM1_XMM2_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %5 %+ _XMM1_FSxBX_icebp + %5 xmm1, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %5 %+ _XMM1_FSxBX_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %6 %+ _XMM2_XMM1_XMM0_icebp + %6 xmm2, xmm1, xmm0 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %6 %+ _XMM2_XMM1_XMM0_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %6 %+ _XMM2_XMM1_FSxBX_icebp + %6 xmm2, xmm1, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %6 %+ _XMM2_XMM1_FSxBX_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %6 %+ _YMM2_YMM1_YMM0_icebp + %6 ymm2, ymm1, ymm0 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %6 %+ _YMM2_YMM1_YMM0_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %6 %+ _YMM2_YMM1_FSxBX_icebp + %6 ymm2, ymm1, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %6 %+ _YMM2_YMM1_FSxBX_icebp + + %if TMPL_BITS == 64 +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %6 %+ _YMM10_YMM8_YMM15_icebp + %6 ymm10, ymm8, ymm15 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %6 %+ _YMM10_YMM8_YMM15_icebp + %endif + + %endmacro ; EMIT_TYPE1_INSTR + + %macro EMIT_TYPE1_ONE_INSTR 3 + %if %3 != 0 +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %1 %+ _MM1_MM2_icebp + %1 mm1, mm2 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %1 %+ _MM1_MM2_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %1 %+ _MM1_FSxBX_icebp + %1 mm1, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %1 %+ _MM1_FSxBX_icebp + %endif + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %1 %+ _XMM1_XMM2_icebp + %1 xmm1, xmm2 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %1 %+ _XMM1_XMM2_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %1 %+ _XMM1_FSxBX_icebp + %1 xmm1, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %1 %+ _XMM1_FSxBX_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %2 %+ _XMM2_XMM1_XMM0_icebp + %2 xmm2, xmm1, xmm0 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %2 %+ _XMM2_XMM1_XMM0_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %2 %+ _XMM2_XMM1_FSxBX_icebp + %2 xmm2, xmm1, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %2 %+ _XMM2_XMM1_FSxBX_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %2 %+ _YMM2_YMM1_YMM0_icebp + %2 ymm2, ymm1, ymm0 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %2 %+ _YMM2_YMM1_YMM0_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %2 %+ _YMM2_YMM1_FSxBX_icebp + %2 ymm2, ymm1, [fs:xBX] +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %2 %+ _YMM2_YMM1_FSxBX_icebp + + %if TMPL_BITS == 64 +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %2 %+ _YMM10_YMM8_YMM15_icebp + %2 ymm10, ymm8, ymm15 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %2 %+ _YMM10_YMM8_YMM15_icebp + %endif + %endmacro ; EMIT_TYPE1_ONE_INSTR + +%endif + +EMIT_TYPE1_INSTR pand, vpand, andps, vandps, andpd, vandpd, 0 +EMIT_TYPE1_INSTR pandn, vpandn, andnps, vandnps, andnpd, vandnpd, 0 +EMIT_TYPE1_INSTR por, vpor, orps, vorps, orpd, vorpd, 0 +EMIT_TYPE1_INSTR pxor, vpxor, xorps, vxorps, xorpd, vxorpd, 0 + +EMIT_TYPE1_INSTR pcmpgtb, vpcmpgtb, pcmpgtw, vpcmpgtw, pcmpgtd, vpcmpgtd, 1 +EMIT_TYPE1_ONE_INSTR pcmpgtq, vpcmpgtq, 0 +EMIT_TYPE1_INSTR pcmpeqb, vpcmpeqb, pcmpeqw, vpcmpeqw, pcmpeqd, vpcmpeqd, 1 +EMIT_TYPE1_ONE_INSTR pcmpeqq, vpcmpeqq, 0 + +EMIT_TYPE1_INSTR paddb, vpaddb, paddw, vpaddw, paddd, vpaddd, 1 +EMIT_TYPE1_ONE_INSTR paddq, vpaddq, 1 + +EMIT_TYPE1_INSTR psubb, vpsubb, psubw, vpsubw, psubd, vpsubd, 1 +EMIT_TYPE1_ONE_INSTR psubq, vpsubq, 1 + + +; +; Type 2 instructions. On the form: pxxxx sAX, [zy]mm0 +; +%ifndef EMIT_TYPE2_ONE_INSTR_DEFINED + %define EMIT_TYPE2_ONE_INSTR_DEFINED + ;; @param 1 MMX/SSE instruction name + ;; @param 2 AVX instruction name + ;; @param 3 Whether to emit MMX function + ;; @param 4 The opcode byte. (assuming two byte / vex map 1) + %macro EMIT_TYPE2_ONE_INSTR 4 + %if %3 != 0 +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %1 %+ _EAX_MM2_icebp + %1 eax, mm2 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %1 %+ _EAX_MM2_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %1 %+ _EAX_qword_FSxBX_icebp + %if TMPL_BITS == 16 + db 64h, 0fh, %4, 7 ; %1 eax, qword [fs:xBX] + %else + db 64h, 0fh, %4, 3 ; %1 eax, qword [fs:xBX] + %endif +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %1 %+ _EAX_qword_FSxBX_icebp + %endif + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %1 %+ _EAX_XMM2_icebp + %1 eax, xmm2 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %1 %+ _EAX_XMM2_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %1 %+ _EAX_dqword_FSxBX_icebp + %if TMPL_BITS == 16 + db 64h, 66h, 0fh, %4, 7 ; %1 eax, dqword [fs:xBX] + %else + db 64h, 66h, 0fh, %4, 3 ; %1 eax, dqword [fs:xBX] + %endif +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %1 %+ _EAX_dqword_FSxBX_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %2 %+ _EAX_XMM2_icebp + %2 eax, xmm2 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %2 %+ _EAX_XMM2_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %2 %+ _EAX_dqword_FSxBX_icebp + %if TMPL_BITS == 16 + db 64h, 0c4h, 0e0h, 071h, %4, 7 ; %2 eax, dqword [fs:xBX] + %else + db 64h, 0c4h, 0e0h, 071h, %4, 3 ; %2 eax, dqword [fs:xBX] + %endif +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %2 %+ _EAX_dqword_FSxBX_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %2 %+ _EAX_YMM2_icebp + %2 eax, ymm2 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %2 %+ _EAX_YMM2_icebp + +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %2 %+ _EAX_qqword_FSxBX_icebp + %if TMPL_BITS == 16 + db 64h, 0c4h, 0e0h, 075h, %4, 7 ; %2 eax, qqword [fs:xBX] + %else + db 64h, 0c4h, 0e0h, 075h, %4, 3 ; %2 eax, qqword [fs:xBX] + %endif +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %2 %+ _EAX_qqword_FSxBX_icebp + + %if TMPL_BITS == 64 +BS3CPUINSTR3_PROC_BEGIN_CMN bs3CpuInstr3_ %+ %2 %+ _RAX_YMM9_icebp + %2 rax, ymm9 +.again: + icebp + jmp .again +BS3_PROC_END_CMN bs3CpuInstr3_ %+ %2 %+ _RAX_YMM9_icebp + %endif + %endmacro ; EMIT_TYPE2_ONE_INSTR +%endif + +EMIT_TYPE2_ONE_INSTR pmovmskb, vpmovmskb, 1, 0d7h + +; +; [V]PMULLW +; +EMIT_INSTR_PLUS_ICEBP pmullw, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP pmullw, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP pmullw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmullw, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmullw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pmullw, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmullw, XMM1, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmullw, XMM1, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmullw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpmullw, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmullw, YMM1, YMM1, YMM2 +EMIT_INSTR_PLUS_ICEBP vpmullw, YMM1, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmullw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpmullw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PMULLD +; +EMIT_INSTR_PLUS_ICEBP pmulld, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmulld, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmulld, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pmulld, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmulld, XMM2, XMM1, XMM0 +EMIT_INSTR_PLUS_ICEBP vpmulld, XMM2, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmulld, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpmulld, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmulld, YMM2, YMM1, YMM0 +EMIT_INSTR_PLUS_ICEBP vpmulld, YMM2, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmulld, YMM10, YMM8, YMM15 +EMIT_INSTR_PLUS_ICEBP vpmulld, YMM10, YMM8, FSxBX + %endif + +; +; [V]PMULHW +; +EMIT_INSTR_PLUS_ICEBP pmulhw, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP pmulhw, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP pmulhw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmulhw, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmulhw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pmulhw, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmulhw, XMM1, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmulhw, XMM1, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmulhw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpmulhw, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmulhw, YMM1, YMM1, YMM2 +EMIT_INSTR_PLUS_ICEBP vpmulhw, YMM1, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmulhw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpmulhw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PMULHUW +; +EMIT_INSTR_PLUS_ICEBP pmulhuw, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP pmulhuw, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP pmulhuw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmulhuw, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmulhuw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pmulhuw, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmulhuw, XMM1, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmulhuw, XMM1, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmulhuw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpmulhuw, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmulhuw, YMM1, YMM1, YMM2 +EMIT_INSTR_PLUS_ICEBP vpmulhuw, YMM1, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmulhuw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpmulhuw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PSHUFB +; +EMIT_INSTR_PLUS_ICEBP pshufb, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP pshufb, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP pshufb, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pshufb, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pshufb, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pshufb, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpshufb, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpshufb, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpshufb, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpshufb, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpshufb, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpshufb, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpshufb, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpshufb, YMM8, YMM9, FSxBX + %endif + +; +; PSHUFW +; +EMIT_INSTR_PLUS_ICEBP pshufw, MM1, MM2, 0FFh ; FF = top src word in all destination words +EMIT_INSTR_PLUS_ICEBP pshufw, MM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP pshufw, MM1, MM2, 01Bh ; 1B = word swap (like bswap but for words) +EMIT_INSTR_PLUS_ICEBP pshufw, MM1, FSxBX, 01Bh + +; +; [V]PSHUFHW +; +EMIT_INSTR_PLUS_ICEBP pshufhw, XMM1, XMM2, 0FFh +EMIT_INSTR_PLUS_ICEBP pshufhw, XMM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP pshufhw, XMM1, XMM2, 01Bh +EMIT_INSTR_PLUS_ICEBP pshufhw, XMM1, FSxBX, 01Bh + +EMIT_INSTR_PLUS_ICEBP vpshufhw, XMM1, XMM2, 0FFh +EMIT_INSTR_PLUS_ICEBP vpshufhw, XMM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vpshufhw, XMM1, XMM2, 01Bh +EMIT_INSTR_PLUS_ICEBP vpshufhw, XMM1, FSxBX, 01Bh + +EMIT_INSTR_PLUS_ICEBP vpshufhw, YMM1, YMM2, 0FFh +EMIT_INSTR_PLUS_ICEBP vpshufhw, YMM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vpshufhw, YMM1, YMM2, 01Bh +EMIT_INSTR_PLUS_ICEBP vpshufhw, YMM1, FSxBX, 01Bh + + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpshufhw, YMM12, YMM7, 0FFh +EMIT_INSTR_PLUS_ICEBP vpshufhw, YMM9, YMM12, 01Bh + %endif + +; +; [V]PSHUFLW +; +EMIT_INSTR_PLUS_ICEBP pshuflw, XMM1, XMM2, 0FFh +EMIT_INSTR_PLUS_ICEBP pshuflw, XMM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP pshuflw, XMM1, XMM2, 01Bh +EMIT_INSTR_PLUS_ICEBP pshuflw, XMM1, FSxBX, 01Bh + +EMIT_INSTR_PLUS_ICEBP vpshuflw, XMM1, XMM2, 0FFh +EMIT_INSTR_PLUS_ICEBP vpshuflw, XMM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vpshuflw, XMM1, XMM2, 01Bh +EMIT_INSTR_PLUS_ICEBP vpshuflw, XMM1, FSxBX, 01Bh + +EMIT_INSTR_PLUS_ICEBP vpshuflw, YMM1, YMM2, 0FFh +EMIT_INSTR_PLUS_ICEBP vpshuflw, YMM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vpshuflw, YMM1, YMM2, 01Bh +EMIT_INSTR_PLUS_ICEBP vpshuflw, YMM1, FSxBX, 01Bh + + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpshuflw, YMM12, YMM7, 0FFh +EMIT_INSTR_PLUS_ICEBP vpshuflw, YMM9, YMM12, 01Bh + %endif + +; +; [V]PSHUFD +; +EMIT_INSTR_PLUS_ICEBP pshufd, XMM1, XMM2, 0FFh +EMIT_INSTR_PLUS_ICEBP pshufd, XMM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP pshufd, XMM1, XMM2, 01Bh +EMIT_INSTR_PLUS_ICEBP pshufd, XMM1, FSxBX, 01Bh + +EMIT_INSTR_PLUS_ICEBP vpshufd, XMM1, XMM2, 0FFh +EMIT_INSTR_PLUS_ICEBP vpshufd, XMM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vpshufd, XMM1, XMM2, 01Bh +EMIT_INSTR_PLUS_ICEBP vpshufd, XMM1, FSxBX, 01Bh + +EMIT_INSTR_PLUS_ICEBP vpshufd, YMM1, YMM2, 0FFh +EMIT_INSTR_PLUS_ICEBP vpshufd, YMM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vpshufd, YMM1, YMM2, 01Bh +EMIT_INSTR_PLUS_ICEBP vpshufd, YMM1, FSxBX, 01Bh + + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpshufd, YMM12, YMM7, 0FFh +EMIT_INSTR_PLUS_ICEBP vpshufd, YMM9, YMM12, 01Bh + %endif + +; +; [V]PUNPCKHBW +; +EMIT_INSTR_PLUS_ICEBP punpckhbw, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP punpckhbw, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP punpckhbw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP punpckhbw, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP punpckhbw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP punpckhbw, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpunpckhbw, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpunpckhbw, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpunpckhbw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpunpckhbw, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpunpckhbw, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpunpckhbw, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpunpckhbw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpunpckhbw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PUNPCKHWD +; +EMIT_INSTR_PLUS_ICEBP punpckhwd, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP punpckhwd, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP punpckhwd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP punpckhwd, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP punpckhwd, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP punpckhwd, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpunpckhwd, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpunpckhwd, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpunpckhwd, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpunpckhwd, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpunpckhwd, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpunpckhwd, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpunpckhwd, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpunpckhwd, YMM8, YMM9, FSxBX + %endif + +; +; [V]PUNPCKHDQ +; +EMIT_INSTR_PLUS_ICEBP punpckhdq, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP punpckhdq, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP punpckhdq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP punpckhdq, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP punpckhdq, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP punpckhdq, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpunpckhdq, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpunpckhdq, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpunpckhdq, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpunpckhdq, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpunpckhdq, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpunpckhdq, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpunpckhdq, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpunpckhdq, YMM8, YMM9, FSxBX + %endif + +; +; [V]PUNPCKHQDQ (no MMX) +; +EMIT_INSTR_PLUS_ICEBP punpckhqdq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP punpckhqdq, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP punpckhqdq, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP punpckhqdq, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpunpckhqdq, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpunpckhqdq, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpunpckhqdq, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpunpckhqdq, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpunpckhqdq, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpunpckhqdq, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpunpckhqdq, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpunpckhqdq, YMM8, YMM9, FSxBX + %endif + +; +; [V]PUNPCKLBW +; +EMIT_INSTR_PLUS_ICEBP punpcklbw, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP punpcklbw, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP punpcklbw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP punpcklbw, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP punpcklbw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP punpcklbw, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpunpcklbw, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpunpcklbw, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpunpcklbw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpunpcklbw, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpunpcklbw, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpunpcklbw, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpunpcklbw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpunpcklbw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PUNPCKLWD +; +EMIT_INSTR_PLUS_ICEBP punpcklwd, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP punpcklwd, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP punpcklwd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP punpcklwd, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP punpcklwd, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP punpcklwd, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpunpcklwd, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpunpcklwd, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpunpcklwd, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpunpcklwd, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpunpcklwd, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpunpcklwd, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpunpcklwd, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpunpcklwd, YMM8, YMM9, FSxBX + %endif + +; +; [V]PUNPCKLDQ +; +EMIT_INSTR_PLUS_ICEBP punpckldq, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP punpckldq, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP punpckldq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP punpckldq, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP punpckldq, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP punpckldq, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpunpckldq, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpunpckldq, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpunpckldq, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpunpckldq, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpunpckldq, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpunpckldq, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpunpckldq, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpunpckldq, YMM8, YMM9, FSxBX + %endif + +; +; [V]PUNPCKLQDQ (no MMX) +; +EMIT_INSTR_PLUS_ICEBP punpcklqdq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP punpcklqdq, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP punpcklqdq, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP punpcklqdq, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpunpcklqdq, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpunpcklqdq, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpunpcklqdq, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpunpcklqdq, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpunpcklqdq, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpunpcklqdq, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpunpcklqdq, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpunpcklqdq, YMM8, YMM9, FSxBX + %endif + +; +; [V]PACKSSWB +; +EMIT_INSTR_PLUS_ICEBP packsswb, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP packsswb, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP packsswb, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP packsswb, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP packsswb, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP packsswb, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpacksswb, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpacksswb, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpacksswb, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpacksswb, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpacksswb, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpacksswb, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpacksswb, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpacksswb, YMM8, YMM9, FSxBX + %endif + +; +; [V]PACKSSWD +; +EMIT_INSTR_PLUS_ICEBP packssdw, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP packssdw, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP packssdw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP packssdw, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP packssdw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP packssdw, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpackssdw, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpackssdw, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpackssdw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpackssdw, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpackssdw, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpackssdw, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpackssdw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpackssdw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PACKUSWB +; +EMIT_INSTR_PLUS_ICEBP packuswb, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP packuswb, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP packuswb, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP packuswb, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP packuswb, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP packuswb, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpackuswb, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpackuswb, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpackuswb, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpackuswb, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpackuswb, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpackuswb, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpackuswb, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpackuswb, YMM8, YMM9, FSxBX + %endif + +; +; [V]PACKUSWD (no MMX) +; +EMIT_INSTR_PLUS_ICEBP packusdw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP packusdw, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP packusdw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP packusdw, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpackusdw, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpackusdw, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpackusdw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpackusdw, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpackusdw, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpackusdw, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpackusdw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpackusdw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PMAXUB +; +EMIT_INSTR_PLUS_ICEBP pmaxub, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP pmaxub, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP pmaxub, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmaxub, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmaxub, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pmaxub, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmaxub, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpmaxub, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmaxub, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpmaxub, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmaxub, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpmaxub, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmaxub, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpmaxub, YMM8, YMM9, FSxBX + %endif + +; +; [V]PMAXUW +; +EMIT_INSTR_PLUS_ICEBP pmaxuw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmaxuw, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmaxuw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pmaxuw, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmaxuw, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpmaxuw, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmaxuw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpmaxuw, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmaxuw, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpmaxuw, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmaxuw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpmaxuw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PMAXUD +; +EMIT_INSTR_PLUS_ICEBP pmaxud, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmaxud, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmaxud, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pmaxud, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmaxud, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpmaxud, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmaxud, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpmaxud, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmaxud, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpmaxud, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmaxud, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpmaxud, YMM8, YMM9, FSxBX + %endif + +; +; [V]PMAXSB +; +EMIT_INSTR_PLUS_ICEBP pmaxsb, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmaxsb, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmaxsb, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pmaxsb, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmaxsb, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpmaxsb, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmaxsb, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpmaxsb, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmaxsb, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpmaxsb, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmaxsb, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpmaxsb, YMM8, YMM9, FSxBX + %endif + +; +; [V]PMAXSW +; +EMIT_INSTR_PLUS_ICEBP pmaxsw, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP pmaxsw, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP pmaxsw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmaxsw, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmaxsw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pmaxsw, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmaxsw, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpmaxsw, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmaxsw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpmaxsw, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmaxsw, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpmaxsw, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmaxsw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpmaxsw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PMAXSD +; +EMIT_INSTR_PLUS_ICEBP pmaxsd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmaxsd, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmaxsd, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pmaxsd, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmaxsd, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpmaxsd, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmaxsd, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpmaxsd, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpmaxsd, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpmaxsd, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpmaxsd, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpmaxsd, YMM8, YMM9, FSxBX + %endif + +; +; [V]PMINUB +; +EMIT_INSTR_PLUS_ICEBP pminub, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP pminub, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP pminub, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pminub, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pminub, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pminub, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpminub, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpminub, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpminub, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpminub, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpminub, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpminub, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpminub, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpminub, YMM8, YMM9, FSxBX + %endif + +; +; [V]PMINUW +; +EMIT_INSTR_PLUS_ICEBP pminuw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pminuw, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pminuw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pminuw, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpminuw, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpminuw, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpminuw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpminuw, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpminuw, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpminuw, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpminuw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpminuw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PMINUD +; +EMIT_INSTR_PLUS_ICEBP pminud, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pminud, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pminud, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pminud, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpminud, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpminud, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpminud, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpminud, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpminud, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpminud, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpminud, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpminud, YMM8, YMM9, FSxBX + %endif + +; +; [V]PMINSB +; +EMIT_INSTR_PLUS_ICEBP pminsb, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pminsb, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pminsb, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pminsb, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpminsb, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpminsb, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpminsb, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpminsb, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpminsb, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpminsb, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpminsb, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpminsb, YMM8, YMM9, FSxBX + %endif + +; +; [V]PMINSW +; +EMIT_INSTR_PLUS_ICEBP pminsw, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP pminsw, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP pminsw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pminsw, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pminsw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pminsw, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpminsw, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpminsw, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpminsw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpminsw, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpminsw, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpminsw, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpminsw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpminsw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PMINSD +; +EMIT_INSTR_PLUS_ICEBP pminsd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pminsd, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pminsd, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pminsd, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpminsd, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpminsd, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpminsd, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpminsd, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpminsd, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpminsd, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpminsd, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpminsd, YMM8, YMM9, FSxBX + %endif + +; +; [V]MOVNTDQA +; +EMIT_INSTR_PLUS_ICEBP movntdqa, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovntdqa, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovntdqa, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movntdqa, XMM10, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovntdqa, XMM11, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovntdqa, YMM12, FSxBX + %endif + +; +; [V]MOVNTDQ +; +EMIT_INSTR_PLUS_ICEBP movntdq, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovntdq, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovntdq, FSxBX, YMM1 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movntdq, FSxBX, XMM10 +EMIT_INSTR_PLUS_ICEBP vmovntdq, FSxBX, XMM10 +EMIT_INSTR_PLUS_ICEBP vmovntdq, FSxBX, YMM10 + %endif + + +; +; [V]MOVNTPS +; +EMIT_INSTR_PLUS_ICEBP movntps, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovntps, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovntps, FSxBX, YMM1 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movntps, FSxBX, XMM10 +EMIT_INSTR_PLUS_ICEBP vmovntps, FSxBX, XMM11 +EMIT_INSTR_PLUS_ICEBP vmovntps, FSxBX, YMM12 + %endif + +; +; [V]MOVNTPD +; +EMIT_INSTR_PLUS_ICEBP movntpd, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovntpd, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovntpd, FSxBX, YMM1 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movntpd, FSxBX, XMM10 +EMIT_INSTR_PLUS_ICEBP vmovntpd, FSxBX, XMM11 +EMIT_INSTR_PLUS_ICEBP vmovntpd, FSxBX, YMM12 + %endif + +; +; [V]MOVUPS - not testing the 2nd register variant. +; +EMIT_INSTR_PLUS_ICEBP movups, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP movups, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP movups, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovups, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vmovups, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovups, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovups, YMM1, YMM2 +EMIT_INSTR_PLUS_ICEBP vmovups, YMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovups, FSxBX, YMM1 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movups, XMM8, XMM12 +EMIT_INSTR_PLUS_ICEBP movups, XMM10, FSxBX +EMIT_INSTR_PLUS_ICEBP movups, FSxBX, XMM10 +EMIT_INSTR_PLUS_ICEBP vmovups, XMM7, XMM14 +EMIT_INSTR_PLUS_ICEBP vmovups, XMM11, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovups, FSxBX, XMM11 +EMIT_INSTR_PLUS_ICEBP vmovups, YMM12, YMM8 +EMIT_INSTR_PLUS_ICEBP vmovups, YMM12, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovups, FSxBX, YMM12 + %endif + +; +; [V]MOVUPD - not testing the 2nd register variant. +; +EMIT_INSTR_PLUS_ICEBP movupd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP movupd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP movupd, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovupd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vmovupd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovupd, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovupd, YMM1, YMM2 +EMIT_INSTR_PLUS_ICEBP vmovupd, YMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovupd, FSxBX, YMM1 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movupd, XMM8, XMM12 +EMIT_INSTR_PLUS_ICEBP movupd, XMM10, FSxBX +EMIT_INSTR_PLUS_ICEBP movupd, FSxBX, XMM10 +EMIT_INSTR_PLUS_ICEBP vmovupd, XMM7, XMM14 +EMIT_INSTR_PLUS_ICEBP vmovupd, XMM11, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovupd, FSxBX, XMM11 +EMIT_INSTR_PLUS_ICEBP vmovupd, YMM12, YMM8 +EMIT_INSTR_PLUS_ICEBP vmovupd, YMM12, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovupd, FSxBX, YMM12 + %endif + +; +; [V]MOVSS - not testing the 2nd register variant. +; +EMIT_INSTR_PLUS_ICEBP movss, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP movss, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP movss, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovss, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vmovss, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovss, FSxBX, XMM1 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movss, XMM11, XMM8 +EMIT_INSTR_PLUS_ICEBP movss, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP movss, FSxBX, XMM11 +EMIT_INSTR_PLUS_ICEBP vmovss, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vmovss, XMM10, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovss, FSxBX, XMM9 + %endif + +; +; [V]MOVSD - not testing the 2nd register variant. +; +EMIT_INSTR_PLUS_ICEBP movsd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP movsd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP movsd, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovsd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vmovsd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovsd, FSxBX, XMM1 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movsd, XMM11, XMM8 +EMIT_INSTR_PLUS_ICEBP movsd, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP movsd, FSxBX, XMM11 +EMIT_INSTR_PLUS_ICEBP vmovsd, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vmovsd, XMM10, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovsd, FSxBX, XMM9 + %endif + +; +; [V]MOVLPS +; +EMIT_INSTR_PLUS_ICEBP movlps, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP movlps, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovlps, XMM1, XMM2, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovlps, FSxBX, XMM1 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movlps, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP movlps, FSxBX, XMM11 +EMIT_INSTR_PLUS_ICEBP vmovlps, XMM10, XMM14, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovlps, FSxBX, XMM9 + %endif + +; +; [V]MOVLPD +; +EMIT_INSTR_PLUS_ICEBP movlpd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP movlpd, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovlpd, XMM1, XMM2, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovlpd, FSxBX, XMM1 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movlpd, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP movlpd, FSxBX, XMM11 +EMIT_INSTR_PLUS_ICEBP vmovlpd, XMM10, XMM14, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovlpd, FSxBX, XMM9 + %endif + +; +; [V]MOVHPS +; +EMIT_INSTR_PLUS_ICEBP movhps, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP movhps, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovhps, XMM1, XMM2, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovhps, FSxBX, XMM1 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movhps, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP movhps, FSxBX, XMM11 +EMIT_INSTR_PLUS_ICEBP vmovhps, XMM10, XMM14, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovhps, FSxBX, XMM9 + %endif + +; +; [V]MOVHPD +; +EMIT_INSTR_PLUS_ICEBP movhpd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP movhpd, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovhpd, XMM1, XMM2, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovhpd, FSxBX, XMM1 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movhpd, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP movhpd, FSxBX, XMM11 +EMIT_INSTR_PLUS_ICEBP vmovhpd, XMM10, XMM14, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovhpd, FSxBX, XMM9 + %endif + +; +; [V]MOVHLPS +; +EMIT_INSTR_PLUS_ICEBP movhlps, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vmovhlps, XMM1, XMM2, XMM3 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movhlps, XMM8, XMM12 +EMIT_INSTR_PLUS_ICEBP vmovhlps, XMM10, XMM14, XMM12 + %endif + +; +; [V]MOVSLDUP +; +EMIT_INSTR_PLUS_ICEBP movsldup, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP movsldup, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovsldup, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vmovsldup, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovsldup, YMM1, YMM2 +EMIT_INSTR_PLUS_ICEBP vmovsldup, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movsldup, XMM8, XMM12 +EMIT_INSTR_PLUS_ICEBP movsldup, XMM10, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovsldup, XMM7, XMM14 +EMIT_INSTR_PLUS_ICEBP vmovsldup, XMM11, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovsldup, YMM12, YMM8 +EMIT_INSTR_PLUS_ICEBP vmovsldup, YMM12, FSxBX + %endif + +; +; [V]MOVSHDUP +; +EMIT_INSTR_PLUS_ICEBP movshdup, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP movshdup, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovshdup, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vmovshdup, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovshdup, YMM1, YMM2 +EMIT_INSTR_PLUS_ICEBP vmovshdup, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movshdup, XMM8, XMM12 +EMIT_INSTR_PLUS_ICEBP movshdup, XMM10, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovshdup, XMM7, XMM14 +EMIT_INSTR_PLUS_ICEBP vmovshdup, XMM11, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovshdup, YMM12, YMM8 +EMIT_INSTR_PLUS_ICEBP vmovshdup, YMM12, FSxBX + %endif + +; +; [V]MOVDDUP +; +EMIT_INSTR_PLUS_ICEBP movddup, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP movddup, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovddup, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vmovddup, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovddup, YMM1, YMM2 +EMIT_INSTR_PLUS_ICEBP vmovddup, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movddup, XMM8, XMM12 +EMIT_INSTR_PLUS_ICEBP movddup, XMM10, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovddup, XMM7, XMM14 +EMIT_INSTR_PLUS_ICEBP vmovddup, XMM11, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovddup, YMM12, YMM8 +EMIT_INSTR_PLUS_ICEBP vmovddup, YMM12, FSxBX + %endif + +; +; [V]MOVAPS +; +EMIT_INSTR_PLUS_ICEBP movaps, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP movaps, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovaps, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vmovaps, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovaps, YMM1, YMM2 +EMIT_INSTR_PLUS_ICEBP vmovaps, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movaps, XMM8, XMM12 +EMIT_INSTR_PLUS_ICEBP movaps, XMM10, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovaps, XMM7, XMM14 +EMIT_INSTR_PLUS_ICEBP vmovaps, XMM11, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovaps, YMM12, YMM8 +EMIT_INSTR_PLUS_ICEBP vmovaps, YMM12, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP movapd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP movapd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovapd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vmovapd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovapd, YMM1, YMM2 +EMIT_INSTR_PLUS_ICEBP vmovapd, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movapd, XMM8, XMM12 +EMIT_INSTR_PLUS_ICEBP movapd, XMM10, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovapd, XMM7, XMM14 +EMIT_INSTR_PLUS_ICEBP vmovapd, XMM11, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovapd, YMM12, YMM8 +EMIT_INSTR_PLUS_ICEBP vmovapd, YMM12, FSxBX + %endif + +; +; [V]MOVD +; +EMIT_INSTR_PLUS_ICEBP movd, MM1, EDX +EMIT_INSTR_PLUS_ICEBP movd, MM1, FSxBX +EMIT_INSTR_PLUS_ICEBP movd, EAX, MM1 +EMIT_INSTR_PLUS_ICEBP movd, FSxBX, MM1 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movd, MM1, R9D +EMIT_INSTR_PLUS_ICEBP movd, R10D, MM0 + %endif + +EMIT_INSTR_PLUS_ICEBP movd, XMM1, EAX +EMIT_INSTR_PLUS_ICEBP movd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP movd, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP movd, EAX, XMM1 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movd, XMM9, R8D +EMIT_INSTR_PLUS_ICEBP movd, R8D, XMM9 +EMIT_INSTR_PLUS_ICEBP movd, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP movd, FSxBX, XMM9 + %endif + +EMIT_INSTR_PLUS_ICEBP vmovd, XMM1, EAX +EMIT_INSTR_PLUS_ICEBP vmovd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovd, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovd, EDX, XMM1 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vmovd, XMM9, R9D +EMIT_INSTR_PLUS_ICEBP vmovd, R8D, XMM9 +EMIT_INSTR_PLUS_ICEBP vmovd, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovd, FSxBX, XMM9 + %endif + +; +; [V]MOVQ - some hand coded stuff here as the assembler prefers the 7f/6f variants. +; +EMIT_INSTR_PLUS_ICEBP movq, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP movq, MM1, FSxBX +EMIT_INSTR_PLUS_ICEBP movq, FSxBX, MM1 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movq, R9, MM1 +EMIT_INSTR_PLUS_ICEBP movq, MM1, R9 +EMIT_INSTR_PLUS_ICEBP_BYTES 06e_movq_MM1_FSxBX, FSxBX_PFX, 48h, 0fh, 06eh, FSxBX_MODRM | (1 << X86_MODRM_REG_SHIFT) +EMIT_INSTR_PLUS_ICEBP_BYTES 07e_movq_FSxBX_MM1, FSxBX_PFX, 48h, 0fh, 07eh, FSxBX_MODRM | (1 << X86_MODRM_REG_SHIFT) + %endif + +EMIT_INSTR_PLUS_ICEBP movq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP movq, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP movq, FSxBX, XMM1 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movq, XMM9, R8 +EMIT_INSTR_PLUS_ICEBP movq, R8, XMM9 +EMIT_INSTR_PLUS_ICEBP movq, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP movq, FSxBX, XMM9 +EMIT_INSTR_PLUS_ICEBP_BYTES 06e_movq_XMM1_FSxBX, FSxBX_PFX, 66h, 48h, 0fh, 06eh, FSxBX_MODRM | (1 << X86_MODRM_REG_SHIFT) +EMIT_INSTR_PLUS_ICEBP_BYTES 06e_movq_XMM9_FSxBX, FSxBX_PFX, 66h, 4ch, 0fh, 06eh, FSxBX_MODRM | (1 << X86_MODRM_REG_SHIFT) +EMIT_INSTR_PLUS_ICEBP_BYTES 07e_movq_FSxBX_XMM1, FSxBX_PFX, 66h, 48h, 0fh, 07eh, FSxBX_MODRM | (1 << X86_MODRM_REG_SHIFT) +EMIT_INSTR_PLUS_ICEBP_BYTES 07e_movq_FSxBX_XMM9, FSxBX_PFX, 66h, 4ch, 0fh, 07eh, FSxBX_MODRM | (1 << X86_MODRM_REG_SHIFT) + %endif + +EMIT_INSTR_PLUS_ICEBP vmovq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vmovq, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP_BYTES 06e_vmovq_XMM1_FSxBX, FSxBX_PFX, 0c4h, 0e1h, 0f9h, 06eh, FSxBX_MODRM | (1 << X86_MODRM_REG_SHIFT) +EMIT_INSTR_PLUS_ICEBP vmovq, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP_BYTES 07e_vmovq_FSxBX_XMM1, FSxBX_PFX, 0c4h, 0e1h, 0f9h, 07eh, FSxBX_MODRM | (1 << X86_MODRM_REG_SHIFT) + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vmovq, XMM9, R8 +EMIT_INSTR_PLUS_ICEBP vmovq, R8, XMM9 +EMIT_INSTR_PLUS_ICEBP vmovq, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovq, FSxBX, XMM9 +EMIT_INSTR_PLUS_ICEBP_BYTES 06e_vmovq_XMM9_FSxBX, FSxBX_PFX, 0c4h, 061h, 0f9h, 06eh, FSxBX_MODRM | (1 << X86_MODRM_REG_SHIFT) +EMIT_INSTR_PLUS_ICEBP_BYTES 07e_vmovq_FSxBX_XMM9, FSxBX_PFX, 0c4h, 061h, 0f9h, 07eh, FSxBX_MODRM | (1 << X86_MODRM_REG_SHIFT) + %endif + +; +; [V]MOVDQU - not testing the 2nd register variant. +; +EMIT_INSTR_PLUS_ICEBP movdqu, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP_BYTES 07f_movdqu_XMM1_XMM2, 0f3h, 00fh, 07fh, X86_MODRM_MAKE(3, 2, 1) +EMIT_INSTR_PLUS_ICEBP movdqu, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP movdqu, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovdqu, XMM1, XMM2 ; C5 FA 6F CA +EMIT_INSTR_PLUS_ICEBP_BYTES 07f_vmovdqu_XMM1_XMM2, 0c5h, 0fah, 07fh, X86_MODRM_MAKE(3, 2, 1) +EMIT_INSTR_PLUS_ICEBP vmovdqu, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovdqu, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovdqu, YMM1, YMM2 ; C5 FE 6F CA +EMIT_INSTR_PLUS_ICEBP_BYTES 07f_vmovdqu_YMM1_YMM2, 0c5h, 0feh, 07fh, X86_MODRM_MAKE(3, 2, 1) +EMIT_INSTR_PLUS_ICEBP vmovdqu, YMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovdqu, FSxBX, YMM1 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movdqu, XMM8, XMM12 ; F3 45 0F 6F C4 +EMIT_INSTR_PLUS_ICEBP_BYTES 07f_movdqu_XMM8_XMM12, 0f3h, 045h, 00fh, 07fh, X86_MODRM_MAKE(3, 4, 0) +EMIT_INSTR_PLUS_ICEBP movdqu, XMM10, FSxBX +EMIT_INSTR_PLUS_ICEBP movdqu, FSxBX, XMM10 +EMIT_INSTR_PLUS_ICEBP vmovdqu, XMM7, XMM14 +EMIT_INSTR_PLUS_ICEBP vmovdqu, XMM11, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovdqu, FSxBX, XMM11 +EMIT_INSTR_PLUS_ICEBP vmovdqu, YMM12, YMM8 +EMIT_INSTR_PLUS_ICEBP vmovdqu, YMM12, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovdqu, FSxBX, YMM12 + %endif + +; +; [V]MOVDQA - not testing the 2nd register variant. +; +EMIT_INSTR_PLUS_ICEBP movdqa, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP_BYTES 07f_movdqa_XMM1_XMM2, 066h, 00fh, 07fh, X86_MODRM_MAKE(3, 2, 1) +EMIT_INSTR_PLUS_ICEBP movdqa, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP movdqa, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovdqa, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP_BYTES 07f_vmovdqa_XMM1_XMM2, 0c5h, 0f9h, 07fh, X86_MODRM_MAKE(3, 2, 1) +EMIT_INSTR_PLUS_ICEBP vmovdqa, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovdqa, FSxBX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovdqa, YMM1, YMM2 +EMIT_INSTR_PLUS_ICEBP_BYTES 07f_vmovdqa_YMM1_YMM2, 0c5h, 0fdh, 07fh, X86_MODRM_MAKE(3, 2, 1) +EMIT_INSTR_PLUS_ICEBP vmovdqa, YMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovdqa, FSxBX, YMM1 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movdqa, XMM8, XMM12 ; 66 45 0F 6F C4 +EMIT_INSTR_PLUS_ICEBP_BYTES 07f_movdqa_XMM8_XMM12, 066h, 045h, 00fh, 07fh, X86_MODRM_MAKE(3, 4, 0) +EMIT_INSTR_PLUS_ICEBP movdqa, XMM10, FSxBX +EMIT_INSTR_PLUS_ICEBP movdqa, FSxBX, XMM10 +EMIT_INSTR_PLUS_ICEBP vmovdqa, XMM8, XMM14 ; C4 C1 79 6F FE +EMIT_INSTR_PLUS_ICEBP_BYTES 07f_vmovdqa_XMM8_XMM14, 0c4h, 041h, 79h, 07fh, X86_MODRM_MAKE(3, 6, 0) +EMIT_INSTR_PLUS_ICEBP vmovdqa, XMM11, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovdqa, FSxBX, XMM11 +EMIT_INSTR_PLUS_ICEBP vmovdqa, YMM12, YMM8 +EMIT_INSTR_PLUS_ICEBP_BYTES 07f_vmovdqa_YMM12_YMM8, 0c4h, 041h, 7dh, 07fh, X86_MODRM_MAKE(3, 0, 4) +EMIT_INSTR_PLUS_ICEBP vmovdqa, YMM12, FSxBX +EMIT_INSTR_PLUS_ICEBP vmovdqa, FSxBX, YMM12 + %endif + +; +; [V]PTEST +; +EMIT_INSTR_PLUS_ICEBP ptest, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP ptest, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vptest, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vptest, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vptest, YMM1, YMM2 +EMIT_INSTR_PLUS_ICEBP vptest, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP ptest, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP ptest, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vptest, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vptest, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vptest, YMM9, YMM8 +EMIT_INSTR_PLUS_ICEBP vptest, YMM9, FSxBX + %endif + +; +; [V]PAVGB +; +EMIT_INSTR_PLUS_ICEBP pavgb, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP pavgb, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP pavgb, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pavgb, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pavgb, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pavgb, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpavgb, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpavgb, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpavgb, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpavgb, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpavgb, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpavgb, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpavgb, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpavgb, YMM8, YMM9, FSxBX + %endif + +; +; [V]PAVGW +; +EMIT_INSTR_PLUS_ICEBP pavgw, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP pavgw, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP pavgw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pavgw, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pavgw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pavgw, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpavgw, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpavgw, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpavgw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpavgw, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpavgw, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpavgw, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpavgw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpavgw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PSIGNB +; +EMIT_INSTR_PLUS_ICEBP psignb, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP psignb, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP psignb, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP psignb, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP psignb, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP psignb, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpsignb, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpsignb, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpsignb, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpsignb, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpsignb, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpsignb, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpsignb, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpsignb, YMM8, YMM9, FSxBX + %endif + +; +; [V]PSIGNW +; +EMIT_INSTR_PLUS_ICEBP psignw, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP psignw, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP psignw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP psignw, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP psignw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP psignw, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpsignw, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpsignw, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpsignw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpsignw, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpsignw, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpsignw, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpsignw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpsignw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PSIGND +; +EMIT_INSTR_PLUS_ICEBP psignd, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP psignd, MM1, FSxBX + +EMIT_INSTR_PLUS_ICEBP psignd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP psignd, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP psignd, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP psignd, XMM8, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpsignd, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpsignd, XMM1, XMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpsignd, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpsignd, XMM8, XMM9, FSxBX + %endif + +EMIT_INSTR_PLUS_ICEBP vpsignd, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpsignd, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpsignd, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpsignd, YMM8, YMM9, FSxBX + %endif + +; +; [V]ABSB +; +EMIT_INSTR_PLUS_ICEBP pabsb, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP pabsb, MM1, FSxBX +EMIT_INSTR_PLUS_ICEBP pabsb, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pabsb, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpabsb, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpabsb, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpabsb, YMM1, YMM2 +EMIT_INSTR_PLUS_ICEBP vpabsb, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pabsb, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP pabsb, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpabsb, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpabsb, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpabsb, YMM9, YMM8 +EMIT_INSTR_PLUS_ICEBP vpabsb, YMM9, FSxBX + %endif + +; +; [V]ABSW +; +EMIT_INSTR_PLUS_ICEBP pabsw, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP pabsw, MM1, FSxBX +EMIT_INSTR_PLUS_ICEBP pabsw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pabsw, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpabsw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpabsw, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpabsw, YMM1, YMM2 +EMIT_INSTR_PLUS_ICEBP vpabsw, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pabsw, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP pabsw, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpabsw, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpabsw, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpabsw, YMM9, YMM8 +EMIT_INSTR_PLUS_ICEBP vpabsw, YMM9, FSxBX + %endif + +; +; [V]ABSD +; +EMIT_INSTR_PLUS_ICEBP pabsd, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP pabsd, MM1, FSxBX +EMIT_INSTR_PLUS_ICEBP pabsd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pabsd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpabsd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpabsd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpabsd, YMM1, YMM2 +EMIT_INSTR_PLUS_ICEBP vpabsd, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pabsd, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP pabsd, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpabsd, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpabsd, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpabsd, YMM9, YMM8 +EMIT_INSTR_PLUS_ICEBP vpabsd, YMM9, FSxBX + %endif + +; +; [V]PHADDW +; +EMIT_INSTR_PLUS_ICEBP phaddw, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP phaddw, MM1, FSxBX +EMIT_INSTR_PLUS_ICEBP phaddw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP phaddw, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vphaddw, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vphaddw, XMM1, XMM2, FSxBX +EMIT_INSTR_PLUS_ICEBP vphaddw, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vphaddw, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP phaddw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP phaddw, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP vphaddw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vphaddw, XMM8, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vphaddw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vphaddw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PHADDD +; +EMIT_INSTR_PLUS_ICEBP phaddd, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP phaddd, MM1, FSxBX +EMIT_INSTR_PLUS_ICEBP phaddd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP phaddd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vphaddd, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vphaddd, XMM1, XMM2, FSxBX +EMIT_INSTR_PLUS_ICEBP vphaddd, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vphaddd, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP phaddd, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP phaddd, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP vphaddd, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vphaddd, XMM8, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vphaddd, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vphaddd, YMM8, YMM9, FSxBX + %endif + + +; +; [V]PHSUBW +; +EMIT_INSTR_PLUS_ICEBP phsubw, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP phsubw, MM1, FSxBX +EMIT_INSTR_PLUS_ICEBP phsubw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP phsubw, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vphsubw, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vphsubw, XMM1, XMM2, FSxBX +EMIT_INSTR_PLUS_ICEBP vphsubw, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vphsubw, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP phsubw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP phsubw, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP vphsubw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vphsubw, XMM8, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vphsubw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vphsubw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PHSUBD +; +EMIT_INSTR_PLUS_ICEBP phsubd, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP phsubd, MM1, FSxBX +EMIT_INSTR_PLUS_ICEBP phsubd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP phsubd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vphsubd, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vphsubd, XMM1, XMM2, FSxBX +EMIT_INSTR_PLUS_ICEBP vphsubd, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vphsubd, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP phsubd, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP phsubd, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP vphsubd, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vphsubd, XMM8, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vphsubd, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vphsubd, YMM8, YMM9, FSxBX + %endif + +; +; [V]PHADDSW +; +EMIT_INSTR_PLUS_ICEBP phaddsw, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP phaddsw, MM1, FSxBX +EMIT_INSTR_PLUS_ICEBP phaddsw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP phaddsw, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vphaddsw, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vphaddsw, XMM1, XMM2, FSxBX +EMIT_INSTR_PLUS_ICEBP vphaddsw, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vphaddsw, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP phaddsw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP phaddsw, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP vphaddsw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vphaddsw, XMM8, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vphaddsw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vphaddsw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PHSUBSW +; +EMIT_INSTR_PLUS_ICEBP phsubsw, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP phsubsw, MM1, FSxBX +EMIT_INSTR_PLUS_ICEBP phsubsw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP phsubsw, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vphsubsw, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vphsubsw, XMM1, XMM2, FSxBX +EMIT_INSTR_PLUS_ICEBP vphsubsw, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vphsubsw, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP phsubsw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP phsubsw, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP vphsubsw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vphsubsw, XMM8, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vphsubsw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vphsubsw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PMADDUBSW +; +EMIT_INSTR_PLUS_ICEBP pmaddubsw, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP pmaddubsw, MM1, FSxBX +EMIT_INSTR_PLUS_ICEBP pmaddubsw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmaddubsw, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmaddubsw, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpmaddubsw, XMM1, XMM2, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmaddubsw, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpmaddubsw, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmaddubsw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pmaddubsw, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmaddubsw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpmaddubsw, XMM8, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmaddubsw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpmaddubsw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PMULHRSW +; +EMIT_INSTR_PLUS_ICEBP pmulhrsw, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP pmulhrsw, MM1, FSxBX +EMIT_INSTR_PLUS_ICEBP pmulhrsw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmulhrsw, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmulhrsw, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpmulhrsw, XMM1, XMM2, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmulhrsw, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpmulhrsw, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmulhrsw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pmulhrsw, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmulhrsw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpmulhrsw, XMM8, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmulhrsw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpmulhrsw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PSADBW +; +EMIT_INSTR_PLUS_ICEBP psadbw, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP psadbw, MM1, FSxBX +EMIT_INSTR_PLUS_ICEBP psadbw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP psadbw, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpsadbw, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpsadbw, XMM1, XMM2, FSxBX +EMIT_INSTR_PLUS_ICEBP vpsadbw, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpsadbw, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP psadbw, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP psadbw, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP vpsadbw, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpsadbw, XMM8, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpsadbw, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpsadbw, YMM8, YMM9, FSxBX + %endif + +; +; [V]PMULDQ +; +EMIT_INSTR_PLUS_ICEBP pmuldq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmuldq, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmuldq, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpmuldq, XMM1, XMM2, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmuldq, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpmuldq, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmuldq, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pmuldq, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmuldq, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpmuldq, XMM8, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmuldq, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpmuldq, YMM8, YMM9, FSxBX + %endif + +; +; [V]PMULUDQ +; +EMIT_INSTR_PLUS_ICEBP pmuludq, MM1, MM2 +EMIT_INSTR_PLUS_ICEBP pmuludq, MM1, FSxBX +EMIT_INSTR_PLUS_ICEBP pmuludq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmuludq, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmuludq, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vpmuludq, XMM1, XMM2, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmuludq, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vpmuludq, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmuludq, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pmuludq, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmuludq, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vpmuludq, XMM8, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmuludq, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vpmuludq, YMM8, YMM9, FSxBX + %endif + +; +; [V]PUNPCKLPS +; +EMIT_INSTR_PLUS_ICEBP unpcklps, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP unpcklps, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vunpcklps, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vunpcklps, XMM1, XMM2, FSxBX +EMIT_INSTR_PLUS_ICEBP vunpcklps, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vunpcklps, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP unpcklps, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP unpcklps, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP vunpcklps, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vunpcklps, XMM8, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vunpcklps, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vunpcklps, YMM8, YMM9, FSxBX + %endif + +; +; [V]PUNPCKLPD +; +EMIT_INSTR_PLUS_ICEBP unpcklpd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP unpcklpd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vunpcklpd, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vunpcklpd, XMM1, XMM2, FSxBX +EMIT_INSTR_PLUS_ICEBP vunpcklpd, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vunpcklpd, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP unpcklpd, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP unpcklpd, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP vunpcklpd, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vunpcklpd, XMM8, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vunpcklpd, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vunpcklpd, YMM8, YMM9, FSxBX + %endif + +; +; [V]PUNPCKHPS +; +EMIT_INSTR_PLUS_ICEBP unpckhps, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP unpckhps, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vunpckhps, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vunpckhps, XMM1, XMM2, FSxBX +EMIT_INSTR_PLUS_ICEBP vunpckhps, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vunpckhps, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP unpckhps, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP unpckhps, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP vunpckhps, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vunpckhps, XMM8, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vunpckhps, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vunpckhps, YMM8, YMM9, FSxBX + %endif + +; +; [V]PUNPCKHPD +; +EMIT_INSTR_PLUS_ICEBP unpckhpd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP unpckhpd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vunpckhpd, XMM1, XMM2, XMM3 +EMIT_INSTR_PLUS_ICEBP vunpckhpd, XMM1, XMM2, FSxBX +EMIT_INSTR_PLUS_ICEBP vunpckhpd, YMM1, YMM2, YMM3 +EMIT_INSTR_PLUS_ICEBP vunpckhpd, YMM1, YMM2, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP unpckhpd, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP unpckhpd, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP vunpckhpd, XMM8, XMM9, XMM10 +EMIT_INSTR_PLUS_ICEBP vunpckhpd, XMM8, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vunpckhpd, YMM8, YMM9, YMM10 +EMIT_INSTR_PLUS_ICEBP vunpckhpd, YMM8, YMM9, FSxBX + %endif + +; +; [V]PMOVSXBW +; +EMIT_INSTR_PLUS_ICEBP pmovsxbw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmovsxbw, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxbw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovsxbw, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxbw, YMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovsxbw, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmovsxbw, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP pmovsxbw, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxbw, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovsxbw, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxbw, YMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovsxbw, YMM9, FSxBX + %endif + +; +; [V]PMOVSXBD +; +EMIT_INSTR_PLUS_ICEBP pmovsxbd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmovsxbd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxbd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovsxbd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxbd, YMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovsxbd, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmovsxbd, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP pmovsxbd, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxbd, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovsxbd, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxbd, YMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovsxbd, YMM9, FSxBX + %endif + +; +; [V]PMOVSXBQ +; +EMIT_INSTR_PLUS_ICEBP pmovsxbq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmovsxbq, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxbq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovsxbq, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxbq, YMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovsxbq, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmovsxbq, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP pmovsxbq, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxbq, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovsxbq, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxbq, YMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovsxbq, YMM9, FSxBX + %endif + +; +; [V]PMOVSXWD +; +EMIT_INSTR_PLUS_ICEBP pmovsxwd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmovsxwd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxwd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovsxwd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxwd, YMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovsxwd, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmovsxwd, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP pmovsxwd, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxwd, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovsxwd, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxwd, YMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovsxwd, YMM9, FSxBX + %endif + +; +; [V]PMOVSXWQ +; +EMIT_INSTR_PLUS_ICEBP pmovsxwq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmovsxwq, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxwq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovsxwq, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxwq, YMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovsxwq, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmovsxwq, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP pmovsxwq, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxwq, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovsxwq, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxwq, YMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovsxwq, YMM9, FSxBX + %endif + +; +; [V]PMOVSXDQ +; +EMIT_INSTR_PLUS_ICEBP pmovsxdq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmovsxdq, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxdq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovsxdq, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxdq, YMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovsxdq, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmovsxdq, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP pmovsxdq, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxdq, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovsxdq, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovsxdq, YMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovsxdq, YMM9, FSxBX + %endif + +; +; [V]PMOVZXBW +; +EMIT_INSTR_PLUS_ICEBP pmovzxbw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmovzxbw, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxbw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovzxbw, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxbw, YMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovzxbw, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmovzxbw, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP pmovzxbw, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxbw, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovzxbw, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxbw, YMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovzxbw, YMM9, FSxBX + %endif + +; +; [V]PMOVZXBD +; +EMIT_INSTR_PLUS_ICEBP pmovzxbd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmovzxbd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxbd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovzxbd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxbd, YMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovzxbd, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmovzxbd, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP pmovzxbd, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxbd, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovzxbd, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxbd, YMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovzxbd, YMM9, FSxBX + %endif + +; +; [V]PMOVZXBQ +; +EMIT_INSTR_PLUS_ICEBP pmovzxbq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmovzxbq, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxbq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovzxbq, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxbq, YMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovzxbq, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmovzxbq, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP pmovzxbq, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxbq, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovzxbq, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxbq, YMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovzxbq, YMM9, FSxBX + %endif + +; +; [V]PMOVZXWD +; +EMIT_INSTR_PLUS_ICEBP pmovzxwd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmovzxwd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxwd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovzxwd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxwd, YMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovzxwd, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmovzxwd, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP pmovzxwd, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxwd, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovzxwd, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxwd, YMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovzxwd, YMM9, FSxBX + %endif + +; +; [V]PMOVZXWQ +; +EMIT_INSTR_PLUS_ICEBP pmovzxwq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmovzxwq, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxwq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovzxwq, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxwq, YMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovzxwq, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmovzxwq, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP pmovzxwq, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxwq, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovzxwq, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxwq, YMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovzxwq, YMM9, FSxBX + %endif + +; +; [V]PMOVZXDQ +; +EMIT_INSTR_PLUS_ICEBP pmovzxdq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pmovzxdq, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxdq, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovzxdq, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxdq, YMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vpmovzxdq, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pmovzxdq, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP pmovzxdq, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxdq, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovzxdq, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vpmovzxdq, YMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vpmovzxdq, YMM9, FSxBX + %endif + +; +; [V]SHUFPS +; +EMIT_INSTR_PLUS_ICEBP shufps, XMM1, XMM2, 0FFh +EMIT_INSTR_PLUS_ICEBP shufps, XMM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP shufps, XMM1, XMM2, 000h +EMIT_INSTR_PLUS_ICEBP shufps, XMM1, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vshufps, XMM1, XMM2, XMM3, 0FFh +EMIT_INSTR_PLUS_ICEBP vshufps, XMM1, XMM2, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vshufps, XMM1, XMM2, XMM3, 000h +EMIT_INSTR_PLUS_ICEBP vshufps, XMM1, XMM2, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vshufps, YMM1, YMM2, YMM3, 0FFh +EMIT_INSTR_PLUS_ICEBP vshufps, YMM1, YMM2, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vshufps, YMM1, YMM2, YMM3, 000h +EMIT_INSTR_PLUS_ICEBP vshufps, YMM1, YMM2, FSxBX, 000h + + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP shufps, XMM8, XMM9, 0FFh +EMIT_INSTR_PLUS_ICEBP shufps, XMM8, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP shufps, XMM8, XMM9, 000h +EMIT_INSTR_PLUS_ICEBP shufps, XMM8, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vshufps, XMM8, XMM9, XMM10, 0FFh +EMIT_INSTR_PLUS_ICEBP vshufps, XMM8, XMM9, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vshufps, XMM8, XMM9, XMM10, 000h +EMIT_INSTR_PLUS_ICEBP vshufps, XMM8, XMM9, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vshufps, YMM8, YMM9, YMM10, 0FFh +EMIT_INSTR_PLUS_ICEBP vshufps, YMM8, YMM9, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vshufps, YMM8, YMM9, YMM10, 000h +EMIT_INSTR_PLUS_ICEBP vshufps, YMM8, YMM9, FSxBX, 000h + %endif + +; +; [V]SHUFPD +; +EMIT_INSTR_PLUS_ICEBP shufpd, XMM1, XMM2, 0FFh +EMIT_INSTR_PLUS_ICEBP shufpd, XMM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP shufpd, XMM1, XMM2, 000h +EMIT_INSTR_PLUS_ICEBP shufpd, XMM1, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vshufpd, XMM1, XMM2, XMM3, 0FFh +EMIT_INSTR_PLUS_ICEBP vshufpd, XMM1, XMM2, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vshufpd, XMM1, XMM2, XMM3, 000h +EMIT_INSTR_PLUS_ICEBP vshufpd, XMM1, XMM2, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vshufpd, YMM1, YMM2, YMM3, 0FFh +EMIT_INSTR_PLUS_ICEBP vshufpd, YMM1, YMM2, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vshufpd, YMM1, YMM2, YMM3, 000h +EMIT_INSTR_PLUS_ICEBP vshufpd, YMM1, YMM2, FSxBX, 000h + + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP shufpd, XMM8, XMM9, 0FFh +EMIT_INSTR_PLUS_ICEBP shufpd, XMM8, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP shufpd, XMM8, XMM9, 000h +EMIT_INSTR_PLUS_ICEBP shufpd, XMM8, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vshufpd, XMM8, XMM9, XMM10, 0FFh +EMIT_INSTR_PLUS_ICEBP vshufpd, XMM8, XMM9, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vshufpd, XMM8, XMM9, XMM10, 000h +EMIT_INSTR_PLUS_ICEBP vshufpd, XMM8, XMM9, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vshufpd, YMM8, YMM9, YMM10, 0FFh +EMIT_INSTR_PLUS_ICEBP vshufpd, YMM8, YMM9, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vshufpd, YMM8, YMM9, YMM10, 000h +EMIT_INSTR_PLUS_ICEBP vshufpd, YMM8, YMM9, FSxBX, 000h + %endif + +; +; [V]LDDQU +; +EMIT_INSTR_PLUS_ICEBP lddqu, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vlddqu, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vlddqu, YMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP lddqu, XMM10, FSxBX +EMIT_INSTR_PLUS_ICEBP vlddqu, XMM11, FSxBX +EMIT_INSTR_PLUS_ICEBP vlddqu, YMM12, FSxBX + %endif + +; +; [V]PHMINPOSUW +; +EMIT_INSTR_PLUS_ICEBP phminposuw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP phminposuw, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vphminposuw, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP vphminposuw, XMM1, FSxBX + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP phminposuw, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP phminposuw, XMM9, FSxBX +EMIT_INSTR_PLUS_ICEBP vphminposuw, XMM9, XMM8 +EMIT_INSTR_PLUS_ICEBP vphminposuw, XMM9, FSxBX + %endif + +; +; [V]PBLENDVB +; +EMIT_INSTR_PLUS_ICEBP pblendvb, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP pblendvb, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vpblendvb, XMM1, XMM2, XMM3, XMM4 +EMIT_INSTR_PLUS_ICEBP vpblendvb, XMM1, XMM2, FSxBX, XMM4 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pblendvb, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP pblendvb, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP vpblendvb, XMM8, XMM9, XMM10, XMM11 +EMIT_INSTR_PLUS_ICEBP vpblendvb, XMM8, XMM9, FSxBX, XMM11 + %endif + +EMIT_INSTR_PLUS_ICEBP vpblendvb, YMM1, YMM2, YMM3, YMM4 +EMIT_INSTR_PLUS_ICEBP vpblendvb, YMM1, YMM2, FSxBX, YMM4 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vpblendvb, YMM8, YMM9, YMM10, YMM11 +EMIT_INSTR_PLUS_ICEBP vpblendvb, YMM8, YMM9, FSxBX, YMM11 + %endif + +; +; [V]BLENDVPS +; +EMIT_INSTR_PLUS_ICEBP blendvps, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP blendvps, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vblendvps, XMM1, XMM2, XMM3, XMM4 +EMIT_INSTR_PLUS_ICEBP vblendvps, XMM1, XMM2, FSxBX, XMM4 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP blendvps, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP blendvps, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP vblendvps, XMM8, XMM9, XMM10, XMM11 +EMIT_INSTR_PLUS_ICEBP vblendvps, XMM8, XMM9, FSxBX, XMM11 + %endif + +EMIT_INSTR_PLUS_ICEBP vblendvps, YMM1, YMM2, YMM3, YMM4 +EMIT_INSTR_PLUS_ICEBP vblendvps, YMM1, YMM2, FSxBX, YMM4 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vblendvps, YMM8, YMM9, YMM10, YMM11 +EMIT_INSTR_PLUS_ICEBP vblendvps, YMM8, YMM9, FSxBX, YMM11 + %endif + +; +; [V]BLENDVPD +; +EMIT_INSTR_PLUS_ICEBP blendvpd, XMM1, XMM2 +EMIT_INSTR_PLUS_ICEBP blendvpd, XMM1, FSxBX +EMIT_INSTR_PLUS_ICEBP vblendvpd, XMM1, XMM2, XMM3, XMM4 +EMIT_INSTR_PLUS_ICEBP vblendvpd, XMM1, XMM2, FSxBX, XMM4 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP blendvpd, XMM8, XMM9 +EMIT_INSTR_PLUS_ICEBP blendvpd, XMM8, FSxBX +EMIT_INSTR_PLUS_ICEBP vblendvpd, XMM8, XMM9, XMM10, XMM11 +EMIT_INSTR_PLUS_ICEBP vblendvpd, XMM8, XMM9, FSxBX, XMM11 + %endif + +EMIT_INSTR_PLUS_ICEBP vblendvpd, YMM1, YMM2, YMM3, YMM4 +EMIT_INSTR_PLUS_ICEBP vblendvpd, YMM1, YMM2, FSxBX, YMM4 + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP vblendvpd, YMM8, YMM9, YMM10, YMM11 +EMIT_INSTR_PLUS_ICEBP vblendvpd, YMM8, YMM9, FSxBX, YMM11 + %endif + +; +; [V]PALIGNR +; +EMIT_INSTR_PLUS_ICEBP palignr, MM1, MM2, 0FFh +EMIT_INSTR_PLUS_ICEBP palignr, MM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP palignr, MM1, MM2, 000h +EMIT_INSTR_PLUS_ICEBP palignr, MM1, FSxBX, 000h +EMIT_INSTR_PLUS_ICEBP palignr, MM1, MM2, 003h +EMIT_INSTR_PLUS_ICEBP palignr, MM1, FSxBX, 003h +EMIT_INSTR_PLUS_ICEBP palignr, MM1, MM2, 009h +EMIT_INSTR_PLUS_ICEBP palignr, MM1, FSxBX, 009h + +EMIT_INSTR_PLUS_ICEBP palignr, XMM1, XMM2, 0FFh +EMIT_INSTR_PLUS_ICEBP palignr, XMM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP palignr, XMM1, XMM2, 000h +EMIT_INSTR_PLUS_ICEBP palignr, XMM1, FSxBX, 000h +EMIT_INSTR_PLUS_ICEBP palignr, XMM1, XMM2, 003h +EMIT_INSTR_PLUS_ICEBP palignr, XMM1, FSxBX, 003h +EMIT_INSTR_PLUS_ICEBP palignr, XMM1, XMM2, 013h +EMIT_INSTR_PLUS_ICEBP palignr, XMM1, FSxBX, 013h + +EMIT_INSTR_PLUS_ICEBP vpalignr, XMM1, XMM2, XMM3, 0FFh +EMIT_INSTR_PLUS_ICEBP vpalignr, XMM1, XMM2, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vpalignr, XMM1, XMM2, XMM3, 000h +EMIT_INSTR_PLUS_ICEBP vpalignr, XMM1, XMM2, FSxBX, 000h +EMIT_INSTR_PLUS_ICEBP vpalignr, XMM1, XMM2, XMM3, 003h +EMIT_INSTR_PLUS_ICEBP vpalignr, XMM1, XMM2, FSxBX, 003h +EMIT_INSTR_PLUS_ICEBP vpalignr, XMM1, XMM2, XMM3, 013h +EMIT_INSTR_PLUS_ICEBP vpalignr, XMM1, XMM2, FSxBX, 013h + +EMIT_INSTR_PLUS_ICEBP vpalignr, YMM1, YMM2, YMM3, 0FFh +EMIT_INSTR_PLUS_ICEBP vpalignr, YMM1, YMM2, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vpalignr, YMM1, YMM2, YMM3, 000h +EMIT_INSTR_PLUS_ICEBP vpalignr, YMM1, YMM2, FSxBX, 000h +EMIT_INSTR_PLUS_ICEBP vpalignr, YMM1, YMM2, YMM3, 003h +EMIT_INSTR_PLUS_ICEBP vpalignr, YMM1, YMM2, FSxBX, 003h +EMIT_INSTR_PLUS_ICEBP vpalignr, YMM1, YMM2, YMM3, 013h +EMIT_INSTR_PLUS_ICEBP vpalignr, YMM1, YMM2, FSxBX, 013h + + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP palignr, XMM8, XMM9, 0FFh +EMIT_INSTR_PLUS_ICEBP palignr, XMM8, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP palignr, XMM8, XMM9, 000h +EMIT_INSTR_PLUS_ICEBP palignr, XMM8, FSxBX, 000h +EMIT_INSTR_PLUS_ICEBP palignr, XMM8, XMM9, 003h +EMIT_INSTR_PLUS_ICEBP palignr, XMM8, FSxBX, 003h +EMIT_INSTR_PLUS_ICEBP palignr, XMM8, XMM9, 013h +EMIT_INSTR_PLUS_ICEBP palignr, XMM8, FSxBX, 013h + +EMIT_INSTR_PLUS_ICEBP vpalignr, XMM8, XMM9, XMM10, 0FFh +EMIT_INSTR_PLUS_ICEBP vpalignr, XMM8, XMM9, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vpalignr, XMM8, XMM9, XMM10, 000h +EMIT_INSTR_PLUS_ICEBP vpalignr, XMM8, XMM9, FSxBX, 000h +EMIT_INSTR_PLUS_ICEBP vpalignr, XMM8, XMM9, XMM10, 003h +EMIT_INSTR_PLUS_ICEBP vpalignr, XMM8, XMM9, FSxBX, 003h +EMIT_INSTR_PLUS_ICEBP vpalignr, XMM8, XMM9, XMM10, 013h +EMIT_INSTR_PLUS_ICEBP vpalignr, XMM8, XMM9, FSxBX, 013h + +EMIT_INSTR_PLUS_ICEBP vpalignr, YMM8, YMM9, YMM10, 0FFh +EMIT_INSTR_PLUS_ICEBP vpalignr, YMM8, YMM9, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vpalignr, YMM8, YMM9, YMM10, 000h +EMIT_INSTR_PLUS_ICEBP vpalignr, YMM8, YMM9, FSxBX, 000h +EMIT_INSTR_PLUS_ICEBP vpalignr, YMM8, YMM9, YMM10, 003h +EMIT_INSTR_PLUS_ICEBP vpalignr, YMM8, YMM9, FSxBX, 003h +EMIT_INSTR_PLUS_ICEBP vpalignr, YMM8, YMM9, YMM10, 013h +EMIT_INSTR_PLUS_ICEBP vpalignr, YMM8, YMM9, FSxBX, 013h + %endif + +; +; [V]PBLENDW +; +EMIT_INSTR_PLUS_ICEBP pblendw, XMM1, XMM2, 0FFh +EMIT_INSTR_PLUS_ICEBP pblendw, XMM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP pblendw, XMM1, XMM2, 000h +EMIT_INSTR_PLUS_ICEBP pblendw, XMM1, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vpblendw, XMM1, XMM2, XMM3, 0FFh +EMIT_INSTR_PLUS_ICEBP vpblendw, XMM1, XMM2, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vpblendw, XMM1, XMM2, XMM3, 000h +EMIT_INSTR_PLUS_ICEBP vpblendw, XMM1, XMM2, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vpblendw, YMM1, YMM2, YMM3, 0FFh +EMIT_INSTR_PLUS_ICEBP vpblendw, YMM1, YMM2, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vpblendw, YMM1, YMM2, YMM3, 000h +EMIT_INSTR_PLUS_ICEBP vpblendw, YMM1, YMM2, FSxBX, 000h + + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pblendw, XMM8, XMM9, 0FFh +EMIT_INSTR_PLUS_ICEBP pblendw, XMM8, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP pblendw, XMM8, XMM9, 000h +EMIT_INSTR_PLUS_ICEBP pblendw, XMM8, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vpblendw, XMM8, XMM9, XMM10, 0FFh +EMIT_INSTR_PLUS_ICEBP vpblendw, XMM8, XMM9, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vpblendw, XMM8, XMM9, XMM10, 000h +EMIT_INSTR_PLUS_ICEBP vpblendw, XMM8, XMM9, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vpblendw, YMM8, YMM9, YMM10, 0FFh +EMIT_INSTR_PLUS_ICEBP vpblendw, YMM8, YMM9, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vpblendw, YMM8, YMM9, YMM10, 000h +EMIT_INSTR_PLUS_ICEBP vpblendw, YMM8, YMM9, FSxBX, 000h + %endif + +; +; [V]BLENDPS +; +EMIT_INSTR_PLUS_ICEBP blendps, XMM1, XMM2, 0FFh +EMIT_INSTR_PLUS_ICEBP blendps, XMM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP blendps, XMM1, XMM2, 000h +EMIT_INSTR_PLUS_ICEBP blendps, XMM1, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vblendps, XMM1, XMM2, XMM3, 0FFh +EMIT_INSTR_PLUS_ICEBP vblendps, XMM1, XMM2, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vblendps, XMM1, XMM2, XMM3, 000h +EMIT_INSTR_PLUS_ICEBP vblendps, XMM1, XMM2, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vblendps, YMM1, YMM2, YMM3, 0FFh +EMIT_INSTR_PLUS_ICEBP vblendps, YMM1, YMM2, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vblendps, YMM1, YMM2, YMM3, 000h +EMIT_INSTR_PLUS_ICEBP vblendps, YMM1, YMM2, FSxBX, 000h + + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP blendps, XMM8, XMM9, 0FFh +EMIT_INSTR_PLUS_ICEBP blendps, XMM8, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP blendps, XMM8, XMM9, 000h +EMIT_INSTR_PLUS_ICEBP blendps, XMM8, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vblendps, XMM8, XMM9, XMM10, 0FFh +EMIT_INSTR_PLUS_ICEBP vblendps, XMM8, XMM9, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vblendps, XMM8, XMM9, XMM10, 000h +EMIT_INSTR_PLUS_ICEBP vblendps, XMM8, XMM9, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vblendps, YMM8, YMM9, YMM10, 0FFh +EMIT_INSTR_PLUS_ICEBP vblendps, YMM8, YMM9, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vblendps, YMM8, YMM9, YMM10, 000h +EMIT_INSTR_PLUS_ICEBP vblendps, YMM8, YMM9, FSxBX, 000h + %endif + +; +; [V]BLENDPD +; +EMIT_INSTR_PLUS_ICEBP blendpd, XMM1, XMM2, 0FFh +EMIT_INSTR_PLUS_ICEBP blendpd, XMM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP blendpd, XMM1, XMM2, 000h +EMIT_INSTR_PLUS_ICEBP blendpd, XMM1, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vblendpd, XMM1, XMM2, XMM3, 0FFh +EMIT_INSTR_PLUS_ICEBP vblendpd, XMM1, XMM2, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vblendpd, XMM1, XMM2, XMM3, 000h +EMIT_INSTR_PLUS_ICEBP vblendpd, XMM1, XMM2, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vblendpd, YMM1, YMM2, YMM3, 0FFh +EMIT_INSTR_PLUS_ICEBP vblendpd, YMM1, YMM2, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vblendpd, YMM1, YMM2, YMM3, 000h +EMIT_INSTR_PLUS_ICEBP vblendpd, YMM1, YMM2, FSxBX, 000h + + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP blendpd, XMM8, XMM9, 0FFh +EMIT_INSTR_PLUS_ICEBP blendpd, XMM8, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP blendpd, XMM8, XMM9, 000h +EMIT_INSTR_PLUS_ICEBP blendpd, XMM8, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vblendpd, XMM8, XMM9, XMM10, 0FFh +EMIT_INSTR_PLUS_ICEBP vblendpd, XMM8, XMM9, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vblendpd, XMM8, XMM9, XMM10, 000h +EMIT_INSTR_PLUS_ICEBP vblendpd, XMM8, XMM9, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vblendpd, YMM8, YMM9, YMM10, 0FFh +EMIT_INSTR_PLUS_ICEBP vblendpd, YMM8, YMM9, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vblendpd, YMM8, YMM9, YMM10, 000h +EMIT_INSTR_PLUS_ICEBP vblendpd, YMM8, YMM9, FSxBX, 000h + %endif + +; +; [V]PCLMULQDQ +; +EMIT_INSTR_PLUS_ICEBP pclmulqdq, XMM1, XMM2, 0FFh +EMIT_INSTR_PLUS_ICEBP pclmulqdq, XMM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP pclmulqdq, XMM1, XMM2, 000h +EMIT_INSTR_PLUS_ICEBP pclmulqdq, XMM1, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vpclmulqdq, XMM1, XMM2, XMM3, 0FFh +EMIT_INSTR_PLUS_ICEBP vpclmulqdq, XMM1, XMM2, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vpclmulqdq, XMM1, XMM2, XMM3, 000h +EMIT_INSTR_PLUS_ICEBP vpclmulqdq, XMM1, XMM2, FSxBX, 000h + + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pclmulqdq, XMM8, XMM9, 0FFh +EMIT_INSTR_PLUS_ICEBP pclmulqdq, XMM8, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP pclmulqdq, XMM8, XMM9, 000h +EMIT_INSTR_PLUS_ICEBP pclmulqdq, XMM8, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vpclmulqdq, XMM8, XMM9, XMM10, 0FFh +EMIT_INSTR_PLUS_ICEBP vpclmulqdq, XMM8, XMM9, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vpclmulqdq, XMM8, XMM9, XMM10, 000h +EMIT_INSTR_PLUS_ICEBP vpclmulqdq, XMM8, XMM9, FSxBX, 000h + %endif + +; +; [V]PINSRW +; +EMIT_INSTR_PLUS_ICEBP pinsrw, MM1, EDX, 0FFh +EMIT_INSTR_PLUS_ICEBP pinsrw, MM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP pinsrw, MM1, EDX, 000h +EMIT_INSTR_PLUS_ICEBP pinsrw, MM1, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP pinsrw, XMM1, EDX, 0FFh +EMIT_INSTR_PLUS_ICEBP pinsrw, XMM1, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP pinsrw, XMM1, EDX, 000h +EMIT_INSTR_PLUS_ICEBP pinsrw, XMM1, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vpinsrw, XMM1, XMM2, EDX, 0FFh +EMIT_INSTR_PLUS_ICEBP vpinsrw, XMM1, XMM2, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vpinsrw, XMM1, XMM2, EDX, 000h +EMIT_INSTR_PLUS_ICEBP vpinsrw, XMM1, XMM2, FSxBX, 000h + + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pinsrw, MM1, R9D, 0FFh +EMIT_INSTR_PLUS_ICEBP pinsrw, MM1, R9D, 000h + +EMIT_INSTR_PLUS_ICEBP pinsrw, XMM8, R9D, 0FFh +EMIT_INSTR_PLUS_ICEBP pinsrw, XMM8, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP pinsrw, XMM8, R9D, 000h +EMIT_INSTR_PLUS_ICEBP pinsrw, XMM8, FSxBX, 000h + +EMIT_INSTR_PLUS_ICEBP vpinsrw, XMM8, XMM9, R9D, 0FFh +EMIT_INSTR_PLUS_ICEBP vpinsrw, XMM8, XMM9, FSxBX, 0FFh +EMIT_INSTR_PLUS_ICEBP vpinsrw, XMM8, XMM9, R9D, 000h +EMIT_INSTR_PLUS_ICEBP vpinsrw, XMM8, XMM9, FSxBX, 000h + %endif + +; +; [V]PEXTRW +; +EMIT_INSTR_PLUS_ICEBP pextrw, EDX, MM1, 0FFh +EMIT_INSTR_PLUS_ICEBP pextrw, EDX, MM1, 000h + +EMIT_INSTR_PLUS_ICEBP pextrw, EDX, XMM1, 0FFh +EMIT_INSTR_PLUS_ICEBP pextrw, EDX, XMM1, 000h + +EMIT_INSTR_PLUS_ICEBP vpextrw, EDX, XMM1, 0FFh +EMIT_INSTR_PLUS_ICEBP vpextrw, EDX, XMM1, 000h + + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP pextrw, R9D, MM1, 0FFh +EMIT_INSTR_PLUS_ICEBP pextrw, R9D, MM1, 000h + +; @todo Emits the SSE4.1 0f3a variant EMIT_INSTR_PLUS_ICEBP pextrw, RDX, XMM1, 0FFh +; @todo Emits the SSE4.1 0f3a variant EMIT_INSTR_PLUS_ICEBP pextrw, RDX, XMM1, 000h + +EMIT_INSTR_PLUS_ICEBP pextrw, R9D, XMM8, 0FFh +EMIT_INSTR_PLUS_ICEBP pextrw, R9D, XMM8, 000h + +EMIT_INSTR_PLUS_ICEBP vpextrw, R9D, XMM8, 0FFh +EMIT_INSTR_PLUS_ICEBP vpextrw, R9D, XMM8, 000h + +EMIT_INSTR_PLUS_ICEBP vpextrw, RDX, XMM1, 0FFh +EMIT_INSTR_PLUS_ICEBP vpextrw, RDX, XMM1, 000h + %endif + +; +; [V]MOVMSKPS +; +EMIT_INSTR_PLUS_ICEBP movmskps, EDX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovmskps, EDX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovmskps, EDX, YMM1 + + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movmskps, R9D, XMM8 +EMIT_INSTR_PLUS_ICEBP movmskps, RDX, XMM1 + +EMIT_INSTR_PLUS_ICEBP vmovmskps, R9D, XMM8 +EMIT_INSTR_PLUS_ICEBP vmovmskps, RDX, XMM1 + +EMIT_INSTR_PLUS_ICEBP vmovmskps, R9D, YMM8 +EMIT_INSTR_PLUS_ICEBP vmovmskps, RDX, YMM1 + %endif + +; +; [V]MOVMSKPD +; +EMIT_INSTR_PLUS_ICEBP movmskpd, EDX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovmskpd, EDX, XMM1 +EMIT_INSTR_PLUS_ICEBP vmovmskpd, EDX, YMM1 + + %if TMPL_BITS == 64 +EMIT_INSTR_PLUS_ICEBP movmskpd, R9D, XMM8 +EMIT_INSTR_PLUS_ICEBP movmskpd, RDX, XMM1 + +EMIT_INSTR_PLUS_ICEBP vmovmskpd, R9D, XMM8 +EMIT_INSTR_PLUS_ICEBP vmovmskpd, RDX, XMM1 + +EMIT_INSTR_PLUS_ICEBP vmovmskpd, R9D, YMM8 +EMIT_INSTR_PLUS_ICEBP vmovmskpd, RDX, YMM1 + %endif + + +%endif ; BS3_INSTANTIATING_CMN + +%include "bs3kit-template-footer.mac" ; reset environment diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-3.c32 b/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-3.c32 new file mode 100644 index 00000000..2ddc9d41 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-instr-3.c32 @@ -0,0 +1,12210 @@ +/* $Id: bs3-cpu-instr-3.c32 $ */ +/** @file + * BS3Kit - bs3-cpu-instr-3 - MMX, SSE and AVX instructions, C code template. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> + +#include <iprt/asm.h> +#include <iprt/asm-amd64-x86.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define BS3_FNBS3FAR_PROTOTYPES_CMN(a_BaseNm) \ + extern FNBS3FAR RT_CONCAT(a_BaseNm, _c16); \ + extern FNBS3FAR RT_CONCAT(a_BaseNm, _c32); \ + extern FNBS3FAR RT_CONCAT(a_BaseNm, _c64) + + +/** Converts an execution mode (BS3_MODE_XXX) into an index into an array + * initialized by BS3CPUINSTR3_TEST1_MODES_INIT, + * BS3CPUINSTR3_TEST2_MODES_INIT, BS3CPUINSTR3_TEST3_MODES_INIT, ... . */ +#define BS3CPUINSTR3_TEST_MODES_INDEX(a_bMode) (BS3_MODE_IS_16BIT_CODE(bMode) ? 0 : BS3_MODE_IS_32BIT_CODE(bMode) ? 1 : 2) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Instruction set type and operand width. */ +typedef enum +{ + T_INVALID, + T_MMX, + T_MMX_SSE, /**< MMX instruction, but require the SSE CPUID to work. */ + T_MMX_SSE2, /**< MMX instruction, but require the SSE2 CPUID to work. */ + T_MMX_SSSE3, /**< MMX instruction, but require the SSSE3 CPUID to work. */ + T_AXMMX, + T_AXMMX_OR_SSE, + T_SSE, + T_128BITS = T_SSE, + T_SSE2, + T_SSE3, + T_SSSE3, + T_SSE4_1, + T_SSE4_2, + T_SSE4A, + T_PCLMUL, + T_AVX_128, + T_AVX2_128, + T_AVX_PCLMUL, + T_AVX_256, + T_256BITS = T_AVX_256, + T_AVX2_256, + T_MAX +} INPUT_TYPE_T; + +/** Memory or register rm variant. */ +enum { + RM_REG = 0, + RM_MEM, + RM_MEM32, /**< Memory operand is 32 bits. Hack for movss and similar. */ + RM_MEM64 /**< Memory operand is 64 bits. Hack for movss and similar. */ +}; + +/** + * Execution environment configuration. + */ +typedef struct BS3CPUINSTR3_CONFIG_T +{ + uint16_t fCr0Mp : 1; + uint16_t fCr0Em : 1; + uint16_t fCr0Ts : 1; + uint16_t fCr4OsFxSR : 1; + uint16_t fCr4OsXSave : 1; + uint16_t fXcr0Sse : 1; + uint16_t fXcr0Avx : 1; + /** x87 exception pending (IE + something unmasked). */ + uint16_t fX87XcptPending : 1; + /** Aligned memory operands. If zero, they will be misaligned and tests w/o memory ops skipped. */ + uint16_t fAligned : 1; + uint16_t fAlignCheck : 1; + uint16_t fMxCsrMM : 1; /**< AMD only */ + uint8_t bXcptMmx; + uint8_t bXcptSse; + uint8_t bXcptAvx; +} BS3CPUINSTR3_CONFIG_T; +/** Pointer to an execution environment configuration. */ +typedef BS3CPUINSTR3_CONFIG_T const BS3_FAR *PCBS3CPUINSTR3_CONFIG_T; + +/** State saved by bs3CpuInstr3ConfigReconfigure. */ +typedef struct BS3CPUINSTR3_CONFIG_SAVED_T +{ + uint32_t uCr0; + uint32_t uCr4; + uint32_t uEfl; + uint16_t uFcw; + uint16_t uFsw; + uint32_t uMxCsr; +} BS3CPUINSTR3_CONFIG_SAVED_T; +typedef BS3CPUINSTR3_CONFIG_SAVED_T BS3_FAR *PBS3CPUINSTR3_CONFIG_SAVED_T; +typedef BS3CPUINSTR3_CONFIG_SAVED_T const BS3_FAR *PCBS3CPUINSTR3_CONFIG_SAVED_T; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static bool g_afTypeSupports[T_MAX] = { false, false, false, false, false, false, false, false, false }; +static bool g_fAmdMisalignedSse = false; + +/** Size of g_pbBuf - at least three pages. */ +static uint32_t g_cbBuf; +/** Buffer of g_cbBuf size. */ +static uint8_t BS3_FAR *g_pbBuf; + +/** Exception type \#1 test configurations, 16 & 32 bytes strictly aligned. */ +static const BS3CPUINSTR3_CONFIG_T g_aXcptConfig1[] = +{ +/* + * X87 SSE SSE SSE AVX AVX AVX MMX MMX+SSE MMX+AVX AMD/SSE <-- applies to + * +AVX +AMD/SSE + * CR0 CR0 CR0 CR4 CR4 XCR0 XCR0 FCW MXCSR + * MP, EM, TS, OSFXSR, OSXSAVE, SSE, AVX, ES+, fAligned, AC/AM, MM, bXcptMmx, bXcptSse, bXcptAvx */ + { 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_DB }, /* #0 */ + { 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_DB }, /* #1 */ + { 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_UD, X86_XCPT_UD, X86_XCPT_DB }, /* #2 */ + { 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_NM, X86_XCPT_NM, X86_XCPT_NM }, /* #3 */ + { 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_UD, X86_XCPT_UD, X86_XCPT_NM }, /* #4 */ + { 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_UD, X86_XCPT_DB }, /* #5 */ + { 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_UD }, /* #6 */ + { 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_UD }, /* #7 */ + { 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_UD }, /* #8 */ + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, X86_XCPT_MF, X86_XCPT_DB, X86_XCPT_DB }, /* #9 - pending x87 exception */ + /* Memory misalignment and alignment checks: */ + { 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, X86_XCPT_DB, X86_XCPT_GP, X86_XCPT_GP }, /* #10 */ + { 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, X86_XCPT_AC, X86_XCPT_GP, X86_XCPT_GP }, /* #11 */ + { 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_DB }, /* #12 */ + /* AMD only: */ + { 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_GP }, /* #13 */ + { 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, X86_XCPT_AC, X86_XCPT_AC, X86_XCPT_GP }, /* #14 */ +}; + +/** Exception type \#4 test configurations, 16 & 32 byte not strictly aligned. */ +static const BS3CPUINSTR3_CONFIG_T g_aXcptConfig4[] = +{ +/* + * X87 SSE SSE SSE AVX AVX AVX MMX MMX+SSE MMX+AVX AMD/SSE <-- applies to + * +AVX +AMD/SSE + * CR0 CR0 CR0 CR4 CR4 XCR0 XCR0 FCW MXCSR + * MP, EM, TS, OSFXSR, OSXSAVE, SSE, AVX, ES+, fAligned, AC/AM, MM, bXcptMmx, bXcptSse, bXcptAvx */ + { 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_DB }, /* #0 */ + { 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_DB }, /* #1 */ + { 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_UD, X86_XCPT_UD, X86_XCPT_DB }, /* #2 */ + { 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_NM, X86_XCPT_NM, X86_XCPT_NM }, /* #3 */ + { 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_UD, X86_XCPT_UD, X86_XCPT_NM }, /* #4 */ + { 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_UD, X86_XCPT_DB }, /* #5 */ + { 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_UD }, /* #6 */ + { 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_UD }, /* #7 */ + { 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_UD }, /* #8 */ + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, X86_XCPT_MF, X86_XCPT_DB, X86_XCPT_DB }, /* #9 - pending x87 exception */ + /* Memory misalignment and alignment checks: */ + { 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, X86_XCPT_DB, X86_XCPT_GP, X86_XCPT_DB }, /* #10 */ + { 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, X86_XCPT_AC, X86_XCPT_GP, X86_XCPT_AC }, /* #11 */ + { 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_DB }, /* #12 */ + /* AMD only: */ + { 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_DB }, /* #13 */ + { 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, X86_XCPT_AC, X86_XCPT_AC, X86_XCPT_AC }, /* #14 */ +}; + +/** Exception type \#4 test configurations, for the SSE version of movups. */ +static const BS3CPUINSTR3_CONFIG_T g_aXcptConfig4Unaligned[] = +{ +/* + * X87 SSE SSE SSE AVX AVX AVX MMX MMX+SSE MMX+AVX AMD/SSE <-- applies to + * +AVX +AMD/SSE + * CR0 CR0 CR0 CR4 CR4 XCR0 XCR0 FCW MXCSR + * MP, EM, TS, OSFXSR, OSXSAVE, SSE, AVX, ES+, fAligned, AC/AM, MM, bXcptMmx, bXcptSse, bXcptAvx */ + { 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_DB }, /* #0 */ + { 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_DB }, /* #1 */ + { 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_UD, X86_XCPT_UD, X86_XCPT_DB }, /* #2 */ + { 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_NM, X86_XCPT_NM, X86_XCPT_NM }, /* #3 */ + { 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_UD, X86_XCPT_UD, X86_XCPT_NM }, /* #4 */ + { 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_UD, X86_XCPT_DB }, /* #5 */ + { 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_UD }, /* #6 */ + { 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_UD }, /* #7 */ + { 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_UD }, /* #8 */ + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, X86_XCPT_MF, X86_XCPT_DB, X86_XCPT_DB }, /* #9 - pending x87 exception */ + /* Memory misalignment and alignment checks: */ + { 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_DB }, /* #10 */ + { 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, X86_XCPT_AC, X86_XCPT_DB, X86_XCPT_AC }, /* #11 */ + { 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_DB }, /* #12 */ + /* AMD only: */ + { 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_DB }, /* #13 */ + { 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, X86_XCPT_AC, X86_XCPT_AC, X86_XCPT_AC }, /* #14 */ +}; + +/** Exception type \#5 test configurations, less than 16 byte operands. */ +static const BS3CPUINSTR3_CONFIG_T g_aXcptConfig5[] = +{ +/* + * X87 SSE SSE SSE AVX AVX AVX MMX MMX+SSE MMX+AVX AMD/SSE <-- applies to + * +AVX +AMD/SSE + * CR0 CR0 CR0 CR4 CR4 XCR0 XCR0 FCW MXCSR + * MP, EM, TS, OSFXSR, OSXSAVE, SSE, AVX, ES+, fAligned, AC/AM, MM, bXcptMmx, bXcptSse, bXcptAvx */ + { 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_DB }, /* #0 */ + { 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_DB }, /* #1 */ + { 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_UD, X86_XCPT_UD, X86_XCPT_DB }, /* #2 */ + { 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_NM, X86_XCPT_NM, X86_XCPT_NM }, /* #3 */ + { 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_UD, X86_XCPT_UD, X86_XCPT_NM }, /* #4 */ + { 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_UD, X86_XCPT_DB }, /* #5 */ + { 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_UD }, /* #6 */ + { 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_UD }, /* #7 */ + { 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_UD }, /* #8 */ + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, X86_XCPT_MF, X86_XCPT_DB, X86_XCPT_DB }, /* #9 - pending x87 exception */ + /* Memory misalignment and alignment checks: */ + { 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_DB }, /* #10 */ + { 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, X86_XCPT_AC, X86_XCPT_AC, X86_XCPT_AC }, /* #11 */ + { 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_DB }, /* #12 */ + /* AMD only: */ + { 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, X86_XCPT_DB, X86_XCPT_DB, X86_XCPT_DB }, /* #13 */ + { 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, X86_XCPT_AC, X86_XCPT_AC, X86_XCPT_AC }, /* #14 */ +}; + + + +/** + * Reconfigures the execution environment according to @a pConfig. + * + * Call bs3CpuInstr3ConfigRestore to undo the changes. + * + * @returns true on success, false if the configuration cannot be applied. In + * the latter case, no context changes are made. + * @param pSavedCfg Where to save state we modify. + * @param pCtx The register context to modify. + * @param pExtCtx The extended register context to modify. + * @param pConfig The configuration to apply. + * @param bMode The target mode. + */ +static bool bs3CpuInstr3ConfigReconfigure(PBS3CPUINSTR3_CONFIG_SAVED_T pSavedCfg, PBS3REGCTX pCtx, PBS3EXTCTX pExtCtx, + PCBS3CPUINSTR3_CONFIG_T pConfig, uint8_t bMode) +{ + /* + * Save context bits we may change here + */ + pSavedCfg->uCr0 = pCtx->cr0.u32; + pSavedCfg->uCr4 = pCtx->cr4.u32; + pSavedCfg->uEfl = pCtx->rflags.u32; + pSavedCfg->uFcw = Bs3ExtCtxGetFcw(pExtCtx); + pSavedCfg->uFsw = Bs3ExtCtxGetFsw(pExtCtx); + pSavedCfg->uMxCsr = Bs3ExtCtxGetMxCsr(pExtCtx); + + /* + * Can we make these changes? + */ + if (pConfig->fMxCsrMM && !g_fAmdMisalignedSse) + return false; + + /* Currently we skip pending x87 exceptions in real mode as they cannot be + caught, given that we preserve the bios int10h. */ + if (pConfig->fX87XcptPending && BS3_MODE_IS_RM_OR_V86(bMode)) + return false; + + /* + * Modify the test context. + */ + if (pConfig->fCr0Mp) + pCtx->cr0.u32 |= X86_CR0_MP; + else + pCtx->cr0.u32 &= ~X86_CR0_MP; + if (pConfig->fCr0Em) + pCtx->cr0.u32 |= X86_CR0_EM; + else + pCtx->cr0.u32 &= ~X86_CR0_EM; + if (pConfig->fCr0Ts) + pCtx->cr0.u32 |= X86_CR0_TS; + else + pCtx->cr0.u32 &= ~X86_CR0_TS; + + if (pConfig->fCr4OsFxSR) + pCtx->cr4.u32 |= X86_CR4_OSFXSR; + else + pCtx->cr4.u32 &= ~X86_CR4_OSFXSR; + /** @todo X86_CR4_OSXMMEEXCPT? */ + if (pConfig->fCr4OsXSave) + pCtx->cr4.u32 |= X86_CR4_OSXSAVE; + else + pCtx->cr4.u32 &= ~X86_CR4_OSXSAVE; + + if (pConfig->fXcr0Sse) + pExtCtx->fXcr0Saved |= XSAVE_C_SSE; + else + pExtCtx->fXcr0Saved &= ~XSAVE_C_SSE; + if (pConfig->fXcr0Avx) + pExtCtx->fXcr0Saved |= XSAVE_C_YMM; + else + pExtCtx->fXcr0Saved &= ~XSAVE_C_YMM; + + if (pConfig->fAlignCheck) + { + pCtx->rflags.u32 |= X86_EFL_AC; + pCtx->cr0.u32 |= X86_CR0_AM; + } + else + { + pCtx->rflags.u32 &= ~X86_EFL_AC; + pCtx->cr0.u32 &= ~X86_CR0_AM; + } + + if (!pConfig->fX87XcptPending) + Bs3ExtCtxSetFsw(pExtCtx, pSavedCfg->uFsw & ~(X86_FSW_ES | X86_FSW_B)); + else + { + Bs3ExtCtxSetFcw(pExtCtx, pSavedCfg->uFcw & ~X86_FCW_ZM); + Bs3ExtCtxSetFsw(pExtCtx, pSavedCfg->uFsw | X86_FSW_ZE | X86_FSW_ES | X86_FSW_B); + pCtx->cr0.u32 |= X86_CR0_NE; + } + + if (pConfig->fMxCsrMM) + Bs3ExtCtxSetMxCsr(pExtCtx, pSavedCfg->uMxCsr | X86_MXCSR_MM); + else + Bs3ExtCtxSetMxCsr(pExtCtx, pSavedCfg->uMxCsr & ~X86_MXCSR_MM); + return true; +} + + +/** + * Undoes changes made by bs3CpuInstr3ConfigReconfigure. + */ +static void bs3CpuInstr3ConfigRestore(PCBS3CPUINSTR3_CONFIG_SAVED_T pSavedCfg, PBS3REGCTX pCtx, PBS3EXTCTX pExtCtx) +{ + pCtx->cr0.u32 = pSavedCfg->uCr0; + pCtx->cr4.u32 = pSavedCfg->uCr4; + pCtx->rflags.u32 = pSavedCfg->uEfl; + pExtCtx->fXcr0Saved = pExtCtx->fXcr0Nominal; + Bs3ExtCtxSetFcw(pExtCtx, pSavedCfg->uFcw); + Bs3ExtCtxSetFsw(pExtCtx, pSavedCfg->uFsw); + Bs3ExtCtxSetMxCsr(pExtCtx, pSavedCfg->uMxCsr); +} + + +/** + * Allocates two extended CPU contexts and initializes the first one + * with random data. + * @returns First extended context, initialized with randomish data. NULL on + * failure (complained). + * @param ppExtCtx2 Where to return the 2nd context. + */ +static PBS3EXTCTX bs3CpuInstr3AllocExtCtxs(PBS3EXTCTX BS3_FAR *ppExtCtx2) +{ + /* Allocate extended context structures. */ + uint64_t fFlags; + uint16_t cb = Bs3ExtCtxGetSize(&fFlags); + PBS3EXTCTX pExtCtx1 = Bs3MemAlloc(BS3MEMKIND_TILED, cb * 2); + PBS3EXTCTX pExtCtx2 = (PBS3EXTCTX)((uint8_t BS3_FAR *)pExtCtx1 + cb); + if (pExtCtx1) + { + Bs3ExtCtxInit(pExtCtx1, cb, fFlags); + /** @todo populate with semi-random stuff. */ + + Bs3ExtCtxInit(pExtCtx2, cb, fFlags); + *ppExtCtx2 = pExtCtx2; + return pExtCtx1; + } + Bs3TestFailedF("Bs3MemAlloc(tiled,%#x)", cb * 2); + *ppExtCtx2 = NULL; + return NULL; +} + +static void bs3CpuInstr3FreeExtCtxs(PBS3EXTCTX pExtCtx1, PBS3EXTCTX BS3_FAR pExtCtx2) +{ + RT_NOREF_PV(pExtCtx2); + Bs3MemFree(pExtCtx1, pExtCtx1->cb * 2); +} + +/** + * Sets up SSE and maybe AVX. + */ +static void bs3CpuInstr3SetupSseAndAvx(PBS3REGCTX pCtx, PCBS3EXTCTX pExtCtx) +{ + /* CR0: */ + uint32_t cr0 = Bs3RegGetCr0(); + cr0 &= ~(X86_CR0_TS | X86_CR0_MP | X86_CR0_EM); + cr0 |= X86_CR0_NE; + Bs3RegSetCr0(cr0); + + /* If real mode context, the cr0 value will differ from the current one (we're in PE32 mode). */ + pCtx->cr0.u32 &= ~(X86_CR0_TS | X86_CR0_MP | X86_CR0_EM); + pCtx->cr0.u32 |= X86_CR0_NE; + + /* CR4: */ + if (pExtCtx->enmMethod != BS3EXTCTXMETHOD_ANCIENT) + { + uint32_t cr4 = Bs3RegGetCr4(); + if (pExtCtx->enmMethod == BS3EXTCTXMETHOD_XSAVE) + { + cr4 |= X86_CR4_OSFXSR | X86_CR4_OSXMMEEXCPT | X86_CR4_OSXSAVE; + Bs3RegSetCr4(cr4); + Bs3RegSetXcr0(pExtCtx->fXcr0Nominal); + } + else if (pExtCtx->enmMethod == BS3EXTCTXMETHOD_FXSAVE) + { + cr4 |= X86_CR4_OSFXSR | X86_CR4_OSXMMEEXCPT; + Bs3RegSetCr4(cr4); + } + pCtx->cr4.u32 = cr4; + } +} + + + +/** + * Configures the buffer with electrict fences in paged modes. + * + * @returns Adjusted buffer pointer. + * @param pbBuf The buffer pointer. + * @param pcbBuf Pointer to the buffer size (input & output). + * @param bMode The testing target mode. + */ +DECLINLINE(uint8_t BS3_FAR *) bs3CpuInstr3BufSetup(uint8_t BS3_FAR *pbBuf, uint32_t *pcbBuf, uint8_t bMode) +{ + if (BS3_MODE_IS_PAGED(bMode)) + { + uint32_t cbBuf = *pcbBuf; + Bs3PagingProtectPtr(&pbBuf[0], X86_PAGE_SIZE, 0, X86_PTE_P); + Bs3PagingProtectPtr(&pbBuf[cbBuf - X86_PAGE_SIZE], X86_PAGE_SIZE, 0, X86_PTE_P); + pbBuf += X86_PAGE_SIZE; + cbBuf -= X86_PAGE_SIZE * 2; + *pcbBuf = cbBuf; + } + return pbBuf; +} + + +/** + * Undoes what bs3CpuInstr3BufSetup did. + * + * @param pbBuf The buffer pointer. + * @param cbBuf The buffer size. + * @param bMode The testing target mode. + */ +DECLINLINE(void) bs3CpuInstr3BufCleanup(uint8_t BS3_FAR *pbBuf, uint32_t cbBuf, uint8_t bMode) +{ + if (BS3_MODE_IS_PAGED(bMode)) + { + Bs3PagingProtectPtr(&pbBuf[-X86_PAGE_SIZE], X86_PAGE_SIZE, X86_PTE_P, 0); + Bs3PagingProtectPtr(&pbBuf[cbBuf], X86_PAGE_SIZE, X86_PTE_P, 0); + } +} + + +/** + * Gets a buffer of a @a cbMemOp sized operand according to the given + * configuration and alignment restrictions. + * + * @returns Pointer to the buffer. + * @param pbBuf The buffer pointer. + * @param cbBuf The buffer size. + * @param cbMemOp The operand size. + * @param cbAlign The operand alignment restriction. + * @param pConfig The configuration. + */ +DECLINLINE(PRTUINT256U) bs3CpuInstr3BufForOperand(uint8_t BS3_FAR *pbBuf, uint32_t cbBuf, uint8_t cbMemOp, uint8_t cbAlign, + PCBS3CPUINSTR3_CONFIG_T pConfig) +{ + if (pConfig->fAligned) + { + if (!pConfig->fAlignCheck) + return (PRTUINT256U)&pbBuf[cbBuf - cbMemOp]; + return (PRTUINT256U)&pbBuf[cbBuf - cbMemOp - cbAlign]; + } + return (PRTUINT256U)&pbBuf[cbBuf - cbMemOp - 1]; +} + + +/** + * Determins the size of memory operands. + */ +DECLINLINE(uint8_t) bs3CpuInstr3MemOpSize(uint8_t cbOperand, uint8_t enmRm) +{ + if (enmRm <= RM_MEM) + return cbOperand; + if (enmRm == RM_MEM32) + return sizeof(uint32_t); + if (enmRm == RM_MEM64) + return sizeof(uint64_t); + BS3_ASSERT(0); + return cbOperand; +} + + +/* + * Test type #1. + */ + +typedef struct BS3CPUINSTR3_TEST1_VALUES_T +{ + RTUINT256U uSrc2; + RTUINT256U uSrc1; /**< uDstIn for MMX & SSE */ + RTUINT256U uDstOut; +} BS3CPUINSTR3_TEST1_VALUES_T; + +typedef struct BS3CPUINSTR3_TEST1_T +{ + FPFNBS3FAR pfnWorker; + uint8_t bAvxMisalignXcpt; + uint8_t enmRm; + uint8_t enmType; + uint8_t iRegDst; + uint8_t iRegSrc1; + uint8_t iRegSrc2; + uint8_t cValues; + BS3CPUINSTR3_TEST1_VALUES_T const BS3_FAR *paValues; +} BS3CPUINSTR3_TEST1_T; + +typedef struct BS3CPUINSTR3_TEST1_MODE_T +{ + BS3CPUINSTR3_TEST1_T const BS3_FAR *paTests; + unsigned cTests; +} BS3CPUINSTR3_TEST1_MODE_T; + +/** Initializer for a BS3CPUINSTR3_TEST1_MODE_T array (three entries). */ +#define BS3CPUINSTR3_TEST1_MODES_INIT(a_aTests16, a_aTests32, a_aTests64) \ + { { a_aTests16, RT_ELEMENTS(a_aTests16) }, { a_aTests32, RT_ELEMENTS(a_aTests32) }, { a_aTests64, RT_ELEMENTS(a_aTests64) } } + + +/** + * Test type #1 worker. + */ +static uint8_t bs3CpuInstr3_WorkerTestType1(uint8_t bMode, BS3CPUINSTR3_TEST1_T const BS3_FAR *paTests, unsigned cTests, + PCBS3CPUINSTR3_CONFIG_T paConfigs, unsigned cConfigs) +{ + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + const char BS3_FAR * const pszMode = Bs3GetModeName(bMode); + uint8_t bRing = BS3_MODE_IS_V86(bMode) ? 3 : 0; + uint8_t BS3_FAR *pbBuf = g_pbBuf; + uint32_t cbBuf = g_cbBuf; + PBS3EXTCTX pExtCtxOut; + PBS3EXTCTX pExtCtx = bs3CpuInstr3AllocExtCtxs(&pExtCtxOut); + if (!pExtCtx) + return 0; + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + pbBuf = bs3CpuInstr3BufSetup(pbBuf, &cbBuf, bMode); + Bs3RegCtxSaveForMode(&Ctx, bMode, 1024); + bs3CpuInstr3SetupSseAndAvx(&Ctx, pExtCtx); + //Bs3TestPrintf("FTW=%#x mm1/st1=%.16Rhxs\n", pExtCtx->Ctx.x87.FTW, &pExtCtx->Ctx.x87.aRegs[1]); + + /* + * Run the tests in all rings since alignment issues may behave + * differently in ring-3 compared to ring-0. + */ + for (;;) + { + unsigned iCfg; + for (iCfg = 0; iCfg < cConfigs; iCfg++) + { + unsigned iTest; + BS3CPUINSTR3_CONFIG_SAVED_T SavedCfg; + if (!bs3CpuInstr3ConfigReconfigure(&SavedCfg, &Ctx, pExtCtx, &paConfigs[iCfg], bMode)) + continue; /* unsupported config */ + + /* + * Iterate the tests. + */ + for (iTest = 0; iTest < cTests; iTest++) + { + BS3CPUINSTR3_TEST1_VALUES_T const BS3_FAR *paValues = paTests[iTest].paValues; + uint8_t const cbInstr = ((uint8_t const BS3_FAR *)(uintptr_t)paTests[iTest].pfnWorker)[-1]; + unsigned const cValues = paTests[iTest].cValues; + bool const fMmxInstr = paTests[iTest].enmType < T_SSE; + bool const fSseInstr = paTests[iTest].enmType >= T_SSE && paTests[iTest].enmType < T_AVX_128; + bool const fAvxInstr = paTests[iTest].enmType >= T_AVX_128; + uint8_t const cbOperand = paTests[iTest].enmType < T_128BITS ? 64/8 + : paTests[iTest].enmType < T_256BITS ? 128/8 : 256/8; + uint8_t const cbMemOp = bs3CpuInstr3MemOpSize(cbOperand, paTests[iTest].enmRm); + uint8_t const cbAlign = cbMemOp; + PRTUINT256U puMemOp = bs3CpuInstr3BufForOperand(pbBuf, cbBuf, cbMemOp, cbAlign, &paConfigs[iCfg]); + uint8_t bXcptExpect = !g_afTypeSupports[paTests[iTest].enmType] ? X86_XCPT_UD + : fMmxInstr ? paConfigs[iCfg].bXcptMmx + : fSseInstr ? paConfigs[iCfg].bXcptSse + : BS3_MODE_IS_RM_OR_V86(bMode) ? X86_XCPT_UD : paConfigs[iCfg].bXcptAvx; + uint16_t idTestStep = bRing * 10000 + iCfg * 100 + iTest * 10; + unsigned iVal; + + /* If testing unaligned memory accesses, skip register-only tests. This allows + setting bXcptMmx, bXcptSse and bXcptAvx to reflect the misaligned exceptions. */ + if (paTests[iTest].enmRm == RM_REG && (!paConfigs[iCfg].fAligned || paConfigs[iCfg].fAlignCheck)) + continue; + + /* #AC is only raised in ring-3.: */ + if (bXcptExpect == X86_XCPT_AC) + { + if (bRing != 3) + bXcptExpect = X86_XCPT_DB; + else if (fAvxInstr) + bXcptExpect = paTests[iTest].bAvxMisalignXcpt; /* they generally don't raise #AC */ + } + + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, paTests[iTest].pfnWorker); + + /* + * Iterate the test values and do the actual testing. + */ + for (iVal = 0; iVal < cValues; iVal++, idTestStep++) + { + uint16_t cErrors; + uint16_t uSavedFtw = 0xff; + RTUINT256U uMemOpExpect; + + /* + * Set up the context and some expectations. + */ + /* dest */ + if (paTests[iTest].iRegDst == UINT8_MAX) + { + BS3_ASSERT(paTests[iTest].enmRm >= RM_MEM); + Bs3MemSet(puMemOp, 0xcc, cbMemOp); + if (bXcptExpect == X86_XCPT_DB) + uMemOpExpect = paValues[iVal].uDstOut; + else + Bs3MemSet(&uMemOpExpect, 0xcc, sizeof(uMemOpExpect)); + } + else if (fMmxInstr) + Bs3ExtCtxSetMm(pExtCtx, paTests[iTest].iRegSrc2, ~paValues[iVal].uDstOut.QWords.qw0, BS3EXTCTXTOPMM_ZERO); + + /* source #1 (/ destination for MMX and SSE) */ + if (paTests[iTest].iRegSrc1 == UINT8_MAX) + { + BS3_ASSERT(paTests[iTest].enmRm >= RM_MEM); + Bs3MemCpy(puMemOp, &paValues[iVal].uSrc1, cbMemOp); + if (paTests[iTest].iRegDst == UINT8_MAX) + BS3_ASSERT(fSseInstr); + else + uMemOpExpect = paValues[iVal].uSrc1; + } + else if (fMmxInstr) + Bs3ExtCtxSetMm(pExtCtx, paTests[iTest].iRegSrc1, paValues[iVal].uSrc1.QWords.qw0, BS3EXTCTXTOPMM_ZERO); + else if (fSseInstr) + Bs3ExtCtxSetXmm(pExtCtx, paTests[iTest].iRegSrc1, &paValues[iVal].uSrc1.DQWords.dqw0); + else + Bs3ExtCtxSetYmm(pExtCtx, paTests[iTest].iRegSrc1, &paValues[iVal].uSrc1, 32); + + /* source #2 */ + if (paTests[iTest].iRegSrc2 == UINT8_MAX) + { + BS3_ASSERT(paTests[iTest].enmRm >= RM_MEM); + BS3_ASSERT(paTests[iTest].iRegDst != UINT8_MAX && paTests[iTest].iRegSrc1 != UINT8_MAX); + Bs3MemCpy(puMemOp, &paValues[iVal].uSrc2, cbMemOp); + uMemOpExpect = paValues[iVal].uSrc2; + } + else if (fMmxInstr) + Bs3ExtCtxSetMm(pExtCtx, paTests[iTest].iRegSrc2, paValues[iVal].uSrc2.QWords.qw0, BS3EXTCTXTOPMM_ZERO); + else if (fSseInstr) + Bs3ExtCtxSetXmm(pExtCtx, paTests[iTest].iRegSrc2, &paValues[iVal].uSrc2.DQWords.dqw0); + else + Bs3ExtCtxSetYmm(pExtCtx, paTests[iTest].iRegSrc2, &paValues[iVal].uSrc2, 32); + + /* Memory pointer. */ + if (paTests[iTest].enmRm >= RM_MEM) + { + BS3_ASSERT( paTests[iTest].iRegDst == UINT8_MAX + || paTests[iTest].iRegSrc1 == UINT8_MAX + || paTests[iTest].iRegSrc2 == UINT8_MAX); + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, &Ctx.fs, puMemOp); + } + + /* + * Execute. + */ + Bs3TrapSetJmpAndRestoreWithExtCtxAndRm(&Ctx, pExtCtx, &TrapFrame, pExtCtxOut); + + /* + * Check the result: + */ + cErrors = Bs3TestSubErrorCount(); + + if (fMmxInstr && bXcptExpect == X86_XCPT_DB) + { + uSavedFtw = Bs3ExtCtxGetAbridgedFtw(pExtCtx); + Bs3ExtCtxSetAbridgedFtw(pExtCtx, 0xff); /* Observed on 10980xe after pxor mm1, mm2. */ + } + if (bXcptExpect == X86_XCPT_DB && paTests[iTest].iRegDst != UINT8_MAX) + { + if (fMmxInstr) + Bs3ExtCtxSetMm(pExtCtx, paTests[iTest].iRegDst, paValues[iVal].uDstOut.QWords.qw0, BS3EXTCTXTOPMM_SET); + else if (fSseInstr) + Bs3ExtCtxSetXmm(pExtCtx, paTests[iTest].iRegDst, &paValues[iVal].uDstOut.DQWords.dqw0); + else + Bs3ExtCtxSetYmm(pExtCtx, paTests[iTest].iRegDst, &paValues[iVal].uDstOut, cbOperand); + } + Bs3TestCheckExtCtx(pExtCtxOut, pExtCtx, 0 /*fFlags*/, pszMode, idTestStep); + + if (TrapFrame.bXcpt != bXcptExpect) + Bs3TestFailedF("Expected bXcpt = %#x, got %#x", bXcptExpect, TrapFrame.bXcpt); + + /* Kludge! Looks like EFLAGS.AC is cleared when raising #GP in real mode on the 10980XE. WEIRD! */ + if (bMode == BS3_MODE_RM && (Ctx.rflags.u32 & X86_EFL_AC)) + { + if (TrapFrame.Ctx.rflags.u32 & X86_EFL_AC) + Bs3TestFailedF("Expected EFLAGS.AC to be cleared (bXcpt=%d)", TrapFrame.bXcpt); + TrapFrame.Ctx.rflags.u32 |= X86_EFL_AC; + } + Bs3TestCheckRegCtxEx(&TrapFrame.Ctx, &Ctx, bXcptExpect == X86_XCPT_DB ? cbInstr + 1 : 0, 0, + bXcptExpect == X86_XCPT_DB || BS3_MODE_IS_16BIT_SYS(bMode) ? 0 : X86_EFL_RF, + pszMode, idTestStep); + + if ( paTests[iTest].enmRm >= RM_MEM + && Bs3MemCmp(puMemOp, &uMemOpExpect, cbMemOp) != 0) + Bs3TestFailedF("Expected uMemOp %.*Rhxs, got %.*Rhxs", cbMemOp, &uMemOpExpect, cbMemOp, puMemOp); + + if (cErrors != Bs3TestSubErrorCount()) + { + if (paConfigs[iCfg].fAligned) + Bs3TestFailedF("ring-%d/cfg#%u/test#%u/value#%u failed (bXcptExpect=%#x)", + bRing, iCfg, iTest, iVal, bXcptExpect); + else + Bs3TestFailedF("ring-%d/cfg#%u/test#%u/value#%u failed (bXcptExpect=%#x, puMemOp=%p, EFLAGS=%#RX32, CR0=%#RX32)", + bRing, iCfg, iTest, iVal, bXcptExpect, puMemOp, TrapFrame.Ctx.rflags.u32, TrapFrame.Ctx.cr0); + Bs3TestPrintf("\n"); + } + + if (uSavedFtw != 0xff) + Bs3ExtCtxSetAbridgedFtw(pExtCtx, uSavedFtw); + } + } + + bs3CpuInstr3ConfigRestore(&SavedCfg, &Ctx, pExtCtx); + } + + /* + * Next ring. + */ + bRing++; + if (bRing > 3 || bMode == BS3_MODE_RM) + break; + Bs3RegCtxConvertToRingX(&Ctx, bRing); + } + + /* + * Cleanup. + */ + bs3CpuInstr3BufCleanup(pbBuf, cbBuf, bMode); + bs3CpuInstr3FreeExtCtxs(pExtCtx, pExtCtxOut); + return 0; +} + + +/* + * PAND, VPAND, ANDPS, VANDPS, ANDPD, VANDPD. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pand_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pand_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pand_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pand_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpand_XMM1_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpand_XMM1_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpand_YMM7_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpand_YMM7_YMM2_FSxBX_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_andps_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_andps_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vandps_XMM1_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vandps_XMM1_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vandps_YMM1_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vandps_YMM1_YMM1_FSxBX_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_andpd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_andpd_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vandpd_XMM2_XMM1_XMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vandpd_XMM2_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vandpd_YMM2_YMM1_YMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vandpd_YMM2_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vandpd_YMM10_YMM8_YMM15_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_andps_andpd_pand(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* ^ */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* ^ */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x5555666677770000, 0x1111222233334444, 0x1111222233334444, 0x5555666677770000) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* ^ */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* = */ RTUINT256_INIT_C(0x0c09d02808403294, 0x385406c840621622, 0x8000290816080282, 0x0050c020030090b9) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pand_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pand_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pand_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pand_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpand_XMM1_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpand_XMM1_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpand_YMM7_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpand_YMM7_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_andps_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_andps_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandps_XMM1_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandps_XMM1_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandps_YMM1_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandps_YMM1_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_andpd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_andpd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandpd_XMM2_XMM1_XMM0_icebp_c16, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandpd_XMM2_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandpd_YMM2_YMM1_YMM0_icebp_c16, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandpd_YMM2_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pand_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pand_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pand_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pand_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpand_XMM1_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpand_XMM1_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpand_YMM7_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpand_YMM7_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_andps_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_andps_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandps_XMM1_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandps_XMM1_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandps_YMM1_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandps_YMM1_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_andpd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_andpd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandpd_XMM2_XMM1_XMM0_icebp_c32, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandpd_XMM2_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandpd_YMM2_YMM1_YMM0_icebp_c32, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandpd_YMM2_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pand_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pand_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pand_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pand_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpand_XMM1_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpand_XMM1_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpand_YMM7_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpand_YMM7_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_andps_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_andps_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandps_XMM1_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandps_XMM1_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandps_YMM1_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandps_YMM1_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_andpd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_andpd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandpd_XMM2_XMM1_XMM0_icebp_c64, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandpd_XMM2_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandpd_YMM2_YMM1_YMM0_icebp_c64, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandpd_YMM2_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandpd_YMM10_YMM8_YMM15_icebp_c64, 255, RM_REG, T_AVX_256, 10, 8, 15, RT_ELEMENTS(s_aValues), s_aValues }, + }; + + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * PANDN, VPANDN, ANDNPS, VANDNPS, ANDNPD, VANDNPD. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pandn_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pandn_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pandn_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pandn_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpandn_XMM1_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpandn_XMM1_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpandn_YMM7_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpandn_YMM7_YMM2_FSxBX_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_andnps_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_andnps_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vandnps_XMM1_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vandnps_XMM1_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vandnps_YMM1_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vandnps_YMM1_YMM1_FSxBX_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_andnpd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_andnpd_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vandnpd_XMM2_XMM1_XMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vandnpd_XMM2_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vandnpd_YMM2_YMM1_YMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vandnpd_YMM2_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vandnpd_YMM10_YMM8_YMM15_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_andnps_andnpd_pandn(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* ^ */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* ^ */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x0000000000008888, 0x0000000000000000, 0x0000000000000000, 0x0000000000008888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* ^ */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* = */ RTUINT256_INIT_C(0x41002002649c4141, 0x06a01100260929c4, 0x342106a040449920, 0x9c0c205390090602) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pandn_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pandn_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pandn_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pandn_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpandn_XMM1_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpandn_XMM1_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpandn_YMM7_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpandn_YMM7_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_andnps_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_andnps_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnps_XMM1_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnps_XMM1_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnps_YMM1_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnps_YMM1_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_andnpd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_andnpd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnpd_XMM2_XMM1_XMM0_icebp_c16, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnpd_XMM2_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnpd_YMM2_YMM1_YMM0_icebp_c16, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnpd_YMM2_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pandn_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pandn_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pandn_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pandn_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpandn_XMM1_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpandn_XMM1_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpandn_YMM7_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpandn_YMM7_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_andnps_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_andnps_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnps_XMM1_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnps_XMM1_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnps_YMM1_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnps_YMM1_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_andnpd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_andnpd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnpd_XMM2_XMM1_XMM0_icebp_c32, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnpd_XMM2_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnpd_YMM2_YMM1_YMM0_icebp_c32, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnpd_YMM2_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pandn_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pandn_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pandn_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pandn_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpandn_XMM1_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpandn_XMM1_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpandn_YMM7_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpandn_YMM7_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_andnps_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_andnps_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnps_XMM1_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnps_XMM1_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnps_YMM1_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnps_YMM1_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_andnpd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_andnpd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnpd_XMM2_XMM1_XMM0_icebp_c64, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnpd_XMM2_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnpd_YMM2_YMM1_YMM0_icebp_c64, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnpd_YMM2_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vandnpd_YMM10_YMM8_YMM15_icebp_c64, 255, RM_REG, T_AVX_256, 10, 8, 15, RT_ELEMENTS(s_aValues), s_aValues }, + }; + + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + + +/* + * POR, VPOR, PORPS, VORPS, PORPD, VPORPD. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_por_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_por_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_por_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_por_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpor_XMM1_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpor_XMM1_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpor_YMM7_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpor_YMM7_YMM2_FSxBX_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_orps_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_orps_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vorps_XMM1_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vorps_XMM1_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vorps_YMM1_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vorps_YMM1_YMM1_FSxBX_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_orpd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_orpd_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vorpd_XMM2_XMM1_XMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vorpd_XMM2_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vorpd_YMM2_YMM1_YMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vorpd_YMM2_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vorpd_YMM10_YMM8_YMM15_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_orps_orpd_por(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* ^ */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* ^ */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0xddddeeeeffff8888, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff8888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* ^ */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* = */ RTUINT256_INIT_C(0x5fddfdae6dff73d5, 0xfffc9fec667b7ff7, 0xbc21effbffddfbe3, 0xdfdfedf3b38d9fff) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_por_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_por_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_por_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_por_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpor_XMM1_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpor_XMM1_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpor_YMM7_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpor_YMM7_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_orps_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_orps_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorps_XMM1_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorps_XMM1_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorps_YMM1_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorps_YMM1_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_orpd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_orpd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorpd_XMM2_XMM1_XMM0_icebp_c16, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorpd_XMM2_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorpd_YMM2_YMM1_YMM0_icebp_c16, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorpd_YMM2_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_por_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_por_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_por_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_por_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpor_XMM1_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpor_XMM1_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpor_YMM7_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpor_YMM7_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_orps_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_orps_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorps_XMM1_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorps_XMM1_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorps_YMM1_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorps_YMM1_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_orpd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_orpd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorpd_XMM2_XMM1_XMM0_icebp_c32, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorpd_XMM2_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorpd_YMM2_YMM1_YMM0_icebp_c32, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorpd_YMM2_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_por_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_por_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_por_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_por_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpor_XMM1_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpor_XMM1_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpor_YMM7_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpor_YMM7_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_orps_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_orps_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorps_XMM1_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorps_XMM1_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorps_YMM1_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorps_YMM1_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_orpd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_orpd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorpd_XMM2_XMM1_XMM0_icebp_c64, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorpd_XMM2_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorpd_YMM2_YMM1_YMM0_icebp_c64, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorpd_YMM2_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vorpd_YMM10_YMM8_YMM15_icebp_c64, 255, RM_REG, T_AVX_256, 10, 8, 15, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * PXOR, VPXOR, XORPS, VXORPS, XORPD, VXORPD. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pxor_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pxor_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pxor_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pxor_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpxor_XMM1_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpxor_XMM1_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpxor_YMM7_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpxor_YMM7_YMM2_FSxBX_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_xorps_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_xorps_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vxorps_XMM1_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vxorps_XMM1_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vxorps_YMM1_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vxorps_YMM1_YMM1_FSxBX_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_xorpd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_xorpd_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vxorpd_XMM2_XMM1_XMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vxorpd_XMM2_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vxorpd_YMM2_YMM1_YMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vxorpd_YMM2_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vxorpd_YMM10_YMM8_YMM15_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_xorps_xorpd_pxor(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* ^ */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* ^ */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x8888888888888888, 0x8888888888888888, 0x8888888888888888, 0x8888888888888888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* ^ */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* = */ RTUINT256_INIT_C(0x53d42d8665bf4141, 0xc7a89924261969d5, 0x3c21c6f3e9d5f961, 0xdf8f2dd3b08d0f46) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pxor_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pxor_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pxor_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pxor_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpxor_XMM1_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpxor_XMM1_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpxor_YMM7_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpxor_YMM7_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_xorps_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_xorps_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorps_XMM1_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorps_XMM1_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorps_YMM1_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorps_YMM1_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_xorpd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_xorpd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorpd_XMM2_XMM1_XMM0_icebp_c16, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorpd_XMM2_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorpd_YMM2_YMM1_YMM0_icebp_c16, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorpd_YMM2_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pxor_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pxor_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pxor_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pxor_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpxor_XMM1_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpxor_XMM1_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpxor_YMM7_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpxor_YMM7_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_xorps_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_xorps_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorps_XMM1_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorps_XMM1_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorps_YMM1_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorps_YMM1_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_xorpd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_xorpd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorpd_XMM2_XMM1_XMM0_icebp_c32, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorpd_XMM2_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorpd_YMM2_YMM1_YMM0_icebp_c32, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorpd_YMM2_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pxor_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pxor_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pxor_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pxor_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpxor_XMM1_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpxor_XMM1_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpxor_YMM7_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpxor_YMM7_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_xorps_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_xorps_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorps_XMM1_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorps_XMM1_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorps_YMM1_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorps_YMM1_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_xorpd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_xorpd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorpd_XMM2_XMM1_XMM0_icebp_c64, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorpd_XMM2_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorpd_YMM2_YMM1_YMM0_icebp_c64, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorpd_YMM2_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vxorpd_YMM10_YMM8_YMM15_icebp_c64, 255, RM_REG, T_AVX_256, 10, 8, 15, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + + +/* + * PCMPGTB, VPCMPGTB, PCMPGTW, VPCMPGTW, PCMPGTD, VPCMPGTD, PCMPGTQ, VPCMPGTQ. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpgtb_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpgtb_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpgtb_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpgtb_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpgtb_XMM1_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpgtb_XMM1_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpgtb_YMM7_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpgtb_YMM7_YMM2_FSxBX_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpgtw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpgtw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpgtw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpgtw_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpgtw_XMM1_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpgtw_XMM1_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpgtw_YMM1_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpgtw_YMM1_YMM1_FSxBX_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpgtd_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpgtd_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpgtd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpgtd_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpgtd_XMM2_XMM1_XMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpgtd_XMM2_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpgtd_YMM2_YMM1_YMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpgtd_YMM2_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpcmpgtd_YMM10_YMM8_YMM15_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpgtq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpgtq_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpgtq_XMM2_XMM1_XMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpgtq_XMM2_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpgtq_YMM2_YMM1_YMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpgtq_YMM2_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpcmpgtq_YMM10_YMM8_YMM15_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pcmpgtb_pcmpgtw_pcmpgtd_pcmpgtq(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesB[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* < */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* < */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x000000000000ffff, 0x0000000000000000, 0x0000000000000000, 0x000000000000ffff) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* < */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* = */ RTUINT256_INIT_C(0x0000000000ff0000, 0x00ff00ff00ffffff, 0x000000ff0000ffff, 0xff000000ff00ffff) }, + }; + + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesW[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* < */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* < */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x000000000000ffff, 0x0000000000000000, 0x0000000000000000, 0x000000000000ffff) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* ^ */ RTUINT256_INIT_C(0x1eddddac77733294, 0xf95c8eec40725633, 0x3333e95bbf9962c3, 0x43d3cda0238499fd), /* modified 1st and 3rd value */ + /* = */ RTUINT256_INIT_C(0x00000000ffff0000, 0x000000000000ffff, 0xffff00000000ffff, 0xffff0000ffffffff) }, + }; + + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesD[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* < */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* < */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* < */ RTUINT256_INIT_C(0x555dddac09633294, 0xf95c8eec77725633, 0x7770e95bbf9962c3, 0x43d3cda0238499fd), /* modified 1st, 2nd and 3rd value */ + /* = */ RTUINT256_INIT_C(0xffffffff00000000, 0x00000000ffffffff, 0xffffffff00000000, 0xffffffffffffffff) }, + }; + + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesQ[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* < */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* < */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* < */ RTUINT256_INIT_C(0x77ddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), /* modified 1st value */ + /* = */ RTUINT256_INIT_C(0xffffffffffffffff, 0x0000000000000000, 0x0000000000000000, 0xffffffffffffffff) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pcmpgtb_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pcmpgtb_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pcmpgtb_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pcmpgtb_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpgtb_XMM1_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpgtb_XMM1_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpgtb_YMM7_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpgtb_YMM7_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pcmpgtw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pcmpgtw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pcmpgtw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pcmpgtw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpgtw_XMM1_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpgtw_XMM1_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpgtw_YMM1_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpgtw_YMM1_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pcmpgtd_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pcmpgtd_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pcmpgtd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pcmpgtd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpgtd_XMM2_XMM1_XMM0_icebp_c16, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpgtd_XMM2_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpgtd_YMM2_YMM1_YMM0_icebp_c16, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpgtd_YMM2_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + + { bs3CpuInstr3_pcmpgtq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_2, 1, 1, 2, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_pcmpgtq_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_2, 1, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpgtq_XMM2_XMM1_XMM0_icebp_c16, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpgtq_XMM2_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpgtq_YMM2_YMM1_YMM0_icebp_c16, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpgtq_YMM2_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pcmpgtb_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pcmpgtb_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pcmpgtb_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pcmpgtb_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpgtb_XMM1_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpgtb_XMM1_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpgtb_YMM7_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpgtb_YMM7_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pcmpgtw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pcmpgtw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pcmpgtw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pcmpgtw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpgtw_XMM1_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpgtw_XMM1_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpgtw_YMM1_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpgtw_YMM1_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pcmpgtd_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pcmpgtd_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pcmpgtd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pcmpgtd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpgtd_XMM2_XMM1_XMM0_icebp_c32, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpgtd_XMM2_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpgtd_YMM2_YMM1_YMM0_icebp_c32, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpgtd_YMM2_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + + { bs3CpuInstr3_pcmpgtq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_2, 1, 1, 2, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_pcmpgtq_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_2, 1, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpgtq_XMM2_XMM1_XMM0_icebp_c32, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpgtq_XMM2_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpgtq_YMM2_YMM1_YMM0_icebp_c32, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpgtq_YMM2_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pcmpgtb_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pcmpgtb_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pcmpgtb_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pcmpgtb_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpgtb_XMM1_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpgtb_XMM1_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpgtb_YMM7_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpgtb_YMM7_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pcmpgtw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pcmpgtw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pcmpgtw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pcmpgtw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpgtw_XMM1_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpgtw_XMM1_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpgtw_YMM1_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpgtw_YMM1_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pcmpgtd_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pcmpgtd_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pcmpgtd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pcmpgtd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpgtd_XMM2_XMM1_XMM0_icebp_c64, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpgtd_XMM2_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpgtd_YMM2_YMM1_YMM0_icebp_c64, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpgtd_YMM2_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpgtd_YMM10_YMM8_YMM15_icebp_c64, 255, RM_REG, T_AVX_256, 10, 8, 15, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + + { bs3CpuInstr3_pcmpgtq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_2, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesQ }, + { bs3CpuInstr3_pcmpgtq_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_2, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesQ }, + { bs3CpuInstr3_vpcmpgtq_XMM2_XMM1_XMM0_icebp_c64, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesQ }, + { bs3CpuInstr3_vpcmpgtq_XMM2_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesQ }, + { bs3CpuInstr3_vpcmpgtq_YMM2_YMM1_YMM0_icebp_c64, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesQ }, + { bs3CpuInstr3_vpcmpgtq_YMM2_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesQ }, + { bs3CpuInstr3_vpcmpgtq_YMM10_YMM8_YMM15_icebp_c64, 255, RM_REG, T_AVX_256, 10, 8, 15, RT_ELEMENTS(s_aValuesD), s_aValuesQ }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * PCMPEQB, VPCMPEQB, PCMPEQW, VPCMPEQW, PCMPEQD, VPCMPEQD, PCMPEQQ, VPCMPEQQ. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpeqb_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpeqb_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpeqb_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpeqb_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpeqb_XMM1_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpeqb_XMM1_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpeqb_YMM7_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpeqb_YMM7_YMM2_FSxBX_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpeqw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpeqw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpeqw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpeqw_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpeqw_XMM1_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpeqw_XMM1_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpeqw_YMM1_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpeqw_YMM1_YMM1_FSxBX_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpeqd_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpeqd_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpeqd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpeqd_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpeqd_XMM2_XMM1_XMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpeqd_XMM2_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpeqd_YMM2_YMM1_YMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpeqd_YMM2_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpcmpeqd_YMM10_YMM8_YMM15_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpeqq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pcmpeqq_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpeqq_XMM2_XMM1_XMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpeqq_XMM2_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpeqq_YMM2_YMM1_YMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpcmpeqq_YMM2_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpcmpeqq_YMM10_YMM8_YMM15_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pcmpeqb_pcmpeqw_pcmpeqd_pcmpeqq(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesB[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* ==*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* ==*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000) }, + { RTUINT256_INIT_C(0x4dddf02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* ==*/ RTUINT256_INIT_C(0x1eddddac09dc3294, 0xf95c17ec667256e6, 0xb400e95bbf999bc3, 0x9cd3cda0230999fd), /* modified all to get some matches */ + /* = */ RTUINT256_INIT_C(0x00ff000000ff0000, 0x0000ff00ff0000ff, 0xff0000000000ff00, 0xff00000000ff0000) }, + }; + + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesW[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* ==*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* ==*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* ==*/ RTUINT256_INIT_C(0x1eddf02a6cdc3294, 0x3ef48eec666b5633, 0x88002fa8bf999ba2, 0x9c5ccda0238496bb), /* modified all to get some matches */ + /* = */ RTUINT256_INIT_C(0x0000ffffffff0000, 0xffff0000ffff0000, 0x0000ffff0000ffff, 0xffff00000000ffff) }, + }; + + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesD[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* ==*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* ==*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* ==*/ RTUINT256_INIT_C(0x4d09f02a09633294, 0x3ef417c8666b3fe6, 0x8800e95b564c9ba2, 0x9c5ce073238499fd), /* modified all to get some matches */ + /* = */ RTUINT256_INIT_C(0xffffffff00000000, 0xffffffffffffffff, 0x00000000ffffffff, 0xffffffff00000000) }, + }; + + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesQ[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* ==*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* ==*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* ==*/ RTUINT256_INIT_C(0x1eddddac09633294, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x43d3cda0238499fd), /* modified 2nd and 3rd to get some matches */ + /* = */ RTUINT256_INIT_C(0x0000000000000000, 0xffffffffffffffff, 0xffffffffffffffff, 0x0000000000000000) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pcmpeqb_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pcmpeqb_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pcmpeqb_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pcmpeqb_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpeqb_XMM1_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpeqb_XMM1_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpeqb_YMM7_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpeqb_YMM7_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pcmpeqw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pcmpeqw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pcmpeqw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pcmpeqw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpeqw_XMM1_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpeqw_XMM1_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpeqw_YMM1_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpeqw_YMM1_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pcmpeqd_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pcmpeqd_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pcmpeqd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pcmpeqd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpeqd_XMM2_XMM1_XMM0_icebp_c16, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpeqd_XMM2_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpeqd_YMM2_YMM1_YMM0_icebp_c16, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpeqd_YMM2_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + + { bs3CpuInstr3_pcmpeqq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_pcmpeqq_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpeqq_XMM2_XMM1_XMM0_icebp_c16, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpeqq_XMM2_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpeqq_YMM2_YMM1_YMM0_icebp_c16, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpeqq_YMM2_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pcmpeqb_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pcmpeqb_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pcmpeqb_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pcmpeqb_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpeqb_XMM1_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpeqb_XMM1_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpeqb_YMM7_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpeqb_YMM7_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pcmpeqw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pcmpeqw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pcmpeqw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pcmpeqw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpeqw_XMM1_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpeqw_XMM1_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpeqw_YMM1_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpeqw_YMM1_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pcmpeqd_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pcmpeqd_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pcmpeqd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pcmpeqd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpeqd_XMM2_XMM1_XMM0_icebp_c32, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpeqd_XMM2_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpeqd_YMM2_YMM1_YMM0_icebp_c32, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpeqd_YMM2_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + + { bs3CpuInstr3_pcmpeqq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_2, 1, 1, 2, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_pcmpeqq_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_2, 1, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpeqq_XMM2_XMM1_XMM0_icebp_c32, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpeqq_XMM2_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpeqq_YMM2_YMM1_YMM0_icebp_c32, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpeqq_YMM2_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pcmpeqb_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pcmpeqb_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pcmpeqb_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pcmpeqb_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpeqb_XMM1_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpeqb_XMM1_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpeqb_YMM7_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpcmpeqb_YMM7_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pcmpeqw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pcmpeqw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pcmpeqw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pcmpeqw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpeqw_XMM1_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpeqw_XMM1_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpeqw_YMM1_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpcmpeqw_YMM1_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pcmpeqd_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pcmpeqd_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pcmpeqd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pcmpeqd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpeqd_XMM2_XMM1_XMM0_icebp_c64, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpeqd_XMM2_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpeqd_YMM2_YMM1_YMM0_icebp_c64, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpeqd_YMM2_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpcmpeqd_YMM10_YMM8_YMM15_icebp_c64, 255, RM_REG, T_AVX_256, 10, 8, 15, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + + { bs3CpuInstr3_pcmpeqq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_2, 1, 1, 2, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_pcmpeqq_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_2, 1, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpeqq_XMM2_XMM1_XMM0_icebp_c64, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpeqq_XMM2_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpeqq_YMM2_YMM1_YMM0_icebp_c64, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpeqq_YMM2_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpcmpeqq_YMM10_YMM8_YMM15_icebp_c64, 255, RM_REG, T_AVX_256, 10, 8, 15, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * PADDB, VPADDB, PADDW, VPADDW, PADDD, VPADDD, PADDQ, VPADDQ. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_paddb_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_paddb_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_paddb_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_paddb_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpaddb_XMM1_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpaddb_XMM1_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpaddb_YMM7_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpaddb_YMM7_YMM2_FSxBX_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_paddw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_paddw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_paddw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_paddw_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpaddw_XMM1_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpaddw_XMM1_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpaddw_YMM1_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpaddw_YMM1_YMM1_FSxBX_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_paddd_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_paddd_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_paddd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_paddd_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpaddd_XMM2_XMM1_XMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpaddd_XMM2_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpaddd_YMM2_YMM1_YMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpaddd_YMM2_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpaddd_YMM10_YMM8_YMM15_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_paddq_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_paddq_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_paddq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_paddq_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpaddq_XMM2_XMM1_XMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpaddq_XMM2_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpaddq_YMM2_YMM1_YMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpaddq_YMM2_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpaddq_YMM10_YMM8_YMM15_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_paddb_paddw_paddd_paddq(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesB[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* + */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* + */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x3232545476768888, 0xaaaacccceeee1010, 0xaaaacccceeee1010, 0x3232545476768888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* + */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* = */ RTUINT256_INIT_C(0x6be6cdd6753fa569, 0x3750a5b4a6dd9519, 0x3c21180315e5fd65, 0xdf2fad13b68d2fb8) }, + }; + + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesW[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* + */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* + */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x3332555477768888, 0xaaaacccceeee1110, 0xaaaacccceeee1110, 0x3332555477768888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* + */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* = */ RTUINT256_INIT_C(0x6be6cdd6763fA669, 0x3850A6B4A6DD9619, 0x3C21190315E5FE65, 0xE02FAE13B68D30B8) }, + }; + + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesD[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* + */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* + */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x3333555477768888, 0xAAAACCCCEEEF1110, 0xAAAACCCCEEEF1110, 0x3333555477768888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* + */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* = */ RTUINT256_INIT_C(0x6BE7CDD6763FA669, 0x3850A6B4A6DD9619, 0x3C22190315E5FE65, 0xE030AE13B68E30B8) }, + }; + + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesQ[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* + */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* + */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x3333555577768888, 0xAAAACCCCEEEF1110, 0xAAAACCCCEEEF1110, 0x3333555577768888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* + */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* = */ RTUINT256_INIT_C(0x6BE7CDD6763FA669, 0x3850A6B4A6DD9619, 0x3C22190415E5FE65, 0xE030AE13B68E30B8) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_paddb_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_paddb_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_paddb_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_paddb_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpaddb_XMM1_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpaddb_XMM1_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpaddb_YMM7_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpaddb_YMM7_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_paddw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_paddw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_paddw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_paddw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpaddw_XMM1_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpaddw_XMM1_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpaddw_YMM1_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpaddw_YMM1_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_paddd_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_paddd_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_paddd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_paddd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpaddd_XMM2_XMM1_XMM0_icebp_c16, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpaddd_XMM2_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpaddd_YMM2_YMM1_YMM0_icebp_c16, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpaddd_YMM2_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + + { bs3CpuInstr3_paddq_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_paddq_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_paddq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_paddq_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpaddq_XMM2_XMM1_XMM0_icebp_c16, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpaddq_XMM2_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpaddq_YMM2_YMM1_YMM0_icebp_c16, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpaddq_YMM2_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_paddb_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_paddb_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_paddb_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_paddb_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpaddb_XMM1_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpaddb_XMM1_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpaddb_YMM7_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpaddb_YMM7_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_paddw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_paddw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_paddw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_paddw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpaddw_XMM1_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpaddw_XMM1_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpaddw_YMM1_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpaddw_YMM1_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_paddd_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_paddd_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_paddd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_paddd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpaddd_XMM2_XMM1_XMM0_icebp_c32, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpaddd_XMM2_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpaddd_YMM2_YMM1_YMM0_icebp_c32, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpaddd_YMM2_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + + { bs3CpuInstr3_paddq_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_paddq_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_paddq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_paddq_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpaddq_XMM2_XMM1_XMM0_icebp_c32, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpaddq_XMM2_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpaddq_YMM2_YMM1_YMM0_icebp_c32, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpaddq_YMM2_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_paddb_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_paddb_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_paddb_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_paddb_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpaddb_XMM1_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpaddb_XMM1_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpaddb_YMM7_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpaddb_YMM7_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_paddw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_paddw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_paddw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_paddw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpaddw_XMM1_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpaddw_XMM1_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpaddw_YMM1_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpaddw_YMM1_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_paddd_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_paddd_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_paddd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_paddd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpaddd_XMM2_XMM1_XMM0_icebp_c64, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpaddd_XMM2_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpaddd_YMM2_YMM1_YMM0_icebp_c64, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpaddd_YMM2_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpaddd_YMM10_YMM8_YMM15_icebp_c64, 255, RM_REG, T_AVX_256, 10, 8, 15, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + + { bs3CpuInstr3_paddq_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_paddq_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_paddq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_paddq_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpaddq_XMM2_XMM1_XMM0_icebp_c64, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpaddq_XMM2_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpaddq_YMM2_YMM1_YMM0_icebp_c64, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpaddq_YMM2_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpaddq_YMM10_YMM8_YMM15_icebp_c64, 255, RM_REG, T_AVX_256, 10, 8, 15, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * PSUBB, VPSUBB, PSUBW, VPSUBW, PSUBD, VPSUBD, PSUBQ, VPSUBQ. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psubb_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psubb_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psubb_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psubb_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsubb_XMM1_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsubb_XMM1_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsubb_YMM7_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsubb_YMM7_YMM2_FSxBX_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psubw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psubw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psubw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psubw_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsubw_XMM1_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsubw_XMM1_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsubw_YMM1_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsubw_YMM1_YMM1_FSxBX_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psubd_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psubd_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psubd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psubd_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsubd_XMM2_XMM1_XMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsubd_XMM2_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsubd_YMM2_YMM1_YMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsubd_YMM2_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpsubd_YMM10_YMM8_YMM15_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psubq_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psubq_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psubq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psubq_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsubq_XMM2_XMM1_XMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsubq_XMM2_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsubq_YMM2_YMM1_YMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsubq_YMM2_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpsubq_YMM10_YMM8_YMM15_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_psubb_psubw_psubd_psubq(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesB[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* + */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* + */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x8888888888887878, 0x8888888888888888, 0x8888888888888888, 0x8888888888887878) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* + */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* = */ RTUINT256_INIT_C(0xd1d4ed829d87bfbf, 0xbb687724da07174d, 0xd4dfbab3694dc721, 0xa777ed2d907b0342) }, + }; + + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesW[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* + */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* + */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x8888888888887778, 0x8888888888888888, 0x8888888888888888, 0x8888888888887778) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* + */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* = */ RTUINT256_INIT_C(0xd1d4ed829c87bebf, 0xba687724da07164d, 0xd3dfb9b3694dc721, 0xa777ed2d907b0342) }, + }; + + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesD[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* + */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* + */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x8888888888877778, 0x8888888888888888, 0x8888888888888888, 0x8888888888877778) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* + */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* = */ RTUINT256_INIT_C(0xd1d3ed829c86bebf, 0xba687724da07164d, 0xd3dfb9b3694cc721, 0xa776ed2d907b0342) }, + }; + + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesQ[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* + */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* + */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x8888888888877778, 0x8888888888888888, 0x8888888888888888, 0x8888888888877778) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* + */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* = */ RTUINT256_INIT_C(0xd1d3ed819c86bebf, 0xba687723da07164d, 0xd3dfb9b3694cc721, 0xa776ed2c907b0342) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_psubb_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psubb_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psubb_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psubb_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsubb_XMM1_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsubb_XMM1_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsubb_YMM7_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsubb_YMM7_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_psubw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psubw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psubw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psubw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsubw_XMM1_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsubw_XMM1_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsubw_YMM1_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsubw_YMM1_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_psubd_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psubd_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psubd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psubd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsubd_XMM2_XMM1_XMM0_icebp_c16, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsubd_XMM2_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsubd_YMM2_YMM1_YMM0_icebp_c16, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsubd_YMM2_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + + { bs3CpuInstr3_psubq_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_psubq_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_psubq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_psubq_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpsubq_XMM2_XMM1_XMM0_icebp_c16, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpsubq_XMM2_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpsubq_YMM2_YMM1_YMM0_icebp_c16, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpsubq_YMM2_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_psubb_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psubb_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psubb_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psubb_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsubb_XMM1_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsubb_XMM1_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsubb_YMM7_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsubb_YMM7_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_psubw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psubw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psubw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psubw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsubw_XMM1_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsubw_XMM1_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsubw_YMM1_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsubw_YMM1_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_psubd_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psubd_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psubd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psubd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsubd_XMM2_XMM1_XMM0_icebp_c32, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsubd_XMM2_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsubd_YMM2_YMM1_YMM0_icebp_c32, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsubd_YMM2_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + + { bs3CpuInstr3_psubq_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_psubq_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_psubq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_psubq_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpsubq_XMM2_XMM1_XMM0_icebp_c32, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpsubq_XMM2_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpsubq_YMM2_YMM1_YMM0_icebp_c32, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpsubq_YMM2_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_psubb_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psubb_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psubb_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psubb_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsubb_XMM1_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsubb_XMM1_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsubb_YMM7_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX_256, 7, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsubb_YMM7_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 7, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_psubw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psubw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psubw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psubw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsubw_XMM1_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsubw_XMM1_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsubw_YMM1_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsubw_YMM1_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_psubd_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psubd_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psubd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psubd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsubd_XMM2_XMM1_XMM0_icebp_c64, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsubd_XMM2_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsubd_YMM2_YMM1_YMM0_icebp_c64, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsubd_YMM2_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsubd_YMM10_YMM8_YMM15_icebp_c64, 255, RM_REG, T_AVX_256, 10, 8, 15, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + + { bs3CpuInstr3_psubq_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_psubq_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_psubq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_psubq_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpsubq_XMM2_XMM1_XMM0_icebp_c64, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpsubq_XMM2_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpsubq_YMM2_YMM1_YMM0_icebp_c64, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpsubq_YMM2_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + { bs3CpuInstr3_vpsubq_YMM10_YMM8_YMM15_icebp_c64, 255, RM_REG, T_AVX_256, 10, 8, 15, RT_ELEMENTS(s_aValuesQ), s_aValuesQ }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * PMULLW, VPMULLW, PMULLD, VPMULLD. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmullw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmullw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmullw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmullw_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmullw_XMM1_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmullw_XMM1_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmullw_YMM1_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmullw_YMM1_YMM1_FSxBX_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmulld_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmulld_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmulld_XMM2_XMM1_XMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmulld_XMM2_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmulld_YMM2_YMM1_YMM0_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmulld_YMM2_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmulld_YMM10_YMM8_YMM15_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmulld_YMM10_YMM8_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pmullw_pmulld(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesW[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* * */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* * */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x0b6106d488890000, 0x5c293e94a7419630, 0x5c293e94a7419630, 0x0b6106d488890000) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* * */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* = */ RTUINT256_INIT_C(0x8ec59e38d5149124, 0xf3b0dc605ba6fed2, 0x8800d8b8476c9066, 0xf3d45ee00ba4b9cf) }, + }; + + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesD[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* * */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* * */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x2ea606d477780000, 0x6e5d3e9430ec9630, 0x6e5d3e9430ec9630, 0x2ea606d477780000) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* * */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* = */ RTUINT256_INIT_C(0x97439e3846719124, 0x8216dc606340fed2, 0x7c2bd8b8f1c09066, 0x31915ee054fbb9cf) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pmullw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmullw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmullw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmullw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmullw_XMM1_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmullw_XMM1_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmullw_YMM1_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmullw_YMM1_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pmulld_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pmulld_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmulld_XMM2_XMM1_XMM0_icebp_c16, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmulld_XMM2_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmulld_YMM2_YMM1_YMM0_icebp_c16, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmulld_YMM2_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pmullw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmullw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmullw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmullw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmullw_XMM1_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmullw_XMM1_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmullw_YMM1_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmullw_YMM1_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pmulld_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pmulld_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmulld_XMM2_XMM1_XMM0_icebp_c32, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmulld_XMM2_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmulld_YMM2_YMM1_YMM0_icebp_c32, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmulld_YMM2_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pmullw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmullw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmullw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmullw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmullw_XMM1_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmullw_XMM1_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmullw_YMM1_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmullw_YMM1_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pmulld_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pmulld_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmulld_XMM2_XMM1_XMM0_icebp_c64, 255, RM_REG, T_AVX_128, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmulld_XMM2_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmulld_YMM2_YMM1_YMM0_icebp_c64, 255, RM_REG, T_AVX_256, 2, 1, 0, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmulld_YMM2_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 2, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmulld_YMM10_YMM8_YMM15_icebp_c64, 255, RM_REG, T_AVX_256, 10, 8, 15, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmulld_YMM10_YMM8_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 10, 8, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * PMULHW, VPMULHW. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmulhw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmulhw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmulhw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmulhw_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmulhw_XMM1_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmulhw_XMM1_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmulhw_YMM1_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmulhw_YMM1_YMM1_FSxBX_icebp); + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pmulhw(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesW[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* * */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* * */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0xf49ff92cffff0000, 0xf92cf49ff258f258, 0xf92cf49ff258f258, 0xf49ff92cffff0000) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* * */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* = */ RTUINT256_INIT_C(0x0949021f03fd16e2, 0xfe5df57e19c81583, 0x2390fbc8ea4ad947, 0xe5990635f0e229f2) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pmulhw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmulhw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmulhw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmulhw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhw_XMM1_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhw_XMM1_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhw_YMM1_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhw_YMM1_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pmulhw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmulhw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmulhw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmulhw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhw_XMM1_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhw_XMM1_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhw_YMM1_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhw_YMM1_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pmulhw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmulhw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmulhw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmulhw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhw_XMM1_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhw_XMM1_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhw_YMM1_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhw_YMM1_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * PMULHUW, VPMULHUW. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmulhuw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmulhuw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmulhuw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmulhuw_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmulhuw_XMM1_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmulhuw_XMM1_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmulhuw_YMM1_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmulhuw_YMM1_YMM1_FSxBX_icebp); + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pmulhuw(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesW[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* * */ RTUINT256_INIT_C(0, 0, 0, 0), + /* = */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* * */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* = */ RTUINT256_INIT_C(0x49f45f9277760000, 0x0a3d16c1258b369c, 0x0a3d16c1258b369c, 0x49f45f9277760000) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* * */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* = */ RTUINT256_INIT_C(0x0949cff503fd16e2, 0x3d510d4619c81583, 0x5fb12b7040963c0a, 0x296cb44814665aaa) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pmulhuw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmulhuw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmulhuw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmulhuw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhuw_XMM1_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhuw_XMM1_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhuw_YMM1_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhuw_YMM1_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pmulhuw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmulhuw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmulhuw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmulhuw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhuw_XMM1_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhuw_XMM1_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhuw_YMM1_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhuw_YMM1_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pmulhuw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmulhuw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmulhuw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmulhuw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhuw_XMM1_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhuw_XMM1_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhuw_YMM1_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmulhuw_YMM1_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * PSHUFB + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshufb_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshufb_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshufb_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshufb_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pshufb_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pshufb_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufb_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufb_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpshufb_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpshufb_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufb_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufb_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpshufb_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpshufb_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_pshufb(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues64[] = + { + { /*mask*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /*val*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /* => */ RTUINT256_INIT_C( 0, 0, 0, 0) }, + { /*mask*/ RTUINT256_INIT_C( 1, 2, 3, 0xffffffffffffffff), + /*val*/ RTUINT256_INIT_C( 1, 2, 3, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0x0000000000000000) }, + { /*mask*/ RTUINT256_INIT_C( 1, 2, 3, 0x7f7f7f7f7f7f7f7f), + /*val*/ RTUINT256_INIT_C( 1, 2, 3, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0xffffffffffffffff) }, + { /*mask*/ RTUINT256_INIT_C( 4, 5, 7, 0x5555666677778888), + /*val*/ RTUINT256_INIT_C( 4, 6, 7, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C( 8, 10, 11, 0xeeeedddddddd0000) }, + { /*mask*/ RTUINT256_INIT_C( 8, 10, 11, 0x9c5ce073930996bb), + /*val*/ RTUINT256_INIT_C(12, 13, 14, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0x00a0002300990000) }, + }; + + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesOthers[] = + { + { /*mask*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*val*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*mask*/ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /*val*/ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000) }, + { /*mask*/ RTUINT256_INIT_C(0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f), + /*val*/ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { /*mask*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*val*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0xaaaa999999990000, 0xccccbbbbbbbbaaaa, 0x0000ffffffffeeee, 0xeeeedddddddd0000) }, + { /*mask*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*val*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0xdd320063ac004000, 0xdd00f9005c091e00, 0x00998800d35b0000, 0x005b002300620000) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pshufb_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_pshufb_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_pshufb_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_pshufb_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpshufb_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpshufb_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpshufb_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpshufb_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pshufb_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_pshufb_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_pshufb_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_pshufb_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpshufb_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpshufb_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpshufb_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpshufb_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pshufb_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_pshufb_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_pshufb_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_pshufb_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_pshufb_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_pshufb_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpshufb_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpshufb_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpshufb_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpshufb_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpshufb_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpshufb_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpshufb_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpshufb_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PUNPCKHBW + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpckhbw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpckhbw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpckhbw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpckhbw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_punpckhbw_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_punpckhbw_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckhbw_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckhbw_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpunpckhbw_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpunpckhbw_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckhbw_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckhbw_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpunpckhbw_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpunpckhbw_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_punpckhbw(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues64[] = + { + { /*src2*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /* => */ RTUINT256_INIT_C( 0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C( 1, 2, 3, 0xf1f2f3f4f5f6f7f8), + /*src1*/ RTUINT256_INIT_C( 1, 2, 3, 0xe1e2e3e4e5e6e7e8), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0xf1e1f2e2f3e3f4e4) }, + { /*src2*/ RTUINT256_INIT_C( 4, 5, 7, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C( 4, 6, 7, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C( 8, 10, 11, 0x55dd55dd66ee66ee) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0x9c435cd3e0cd73a0) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesOthers[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1b1f2b2f3b3f4b4, 0xf5b5f6b6f7b7f8b8, 0xd191d292d393d494, 0xd595d696d797d898) }, + { /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0x55dd55dd66ee66ee, 0x77ff77ff88008800, 0x1199119922aa22aa, 0x33bb33bb44cc44cc) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x4d1e09ddf0dd2aac, 0x6c09dc637332d594, 0xb48821002fe9a85b, 0x56bf4c999b62a2c3) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_punpckhbw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckhbw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckhbw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhbw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhbw_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhbw_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhbw_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhbw_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_punpckhbw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckhbw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckhbw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhbw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhbw_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhbw_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhbw_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhbw_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_punpckhbw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckhbw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckhbw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhbw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhbw_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhbw_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhbw_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhbw_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhbw_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhbw_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhbw_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhbw_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhbw_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhbw_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PUNPCKHWD + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpckhwd_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpckhwd_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpckhwd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpckhwd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_punpckhwd_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_punpckhwd_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckhwd_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckhwd_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpunpckhwd_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpunpckhwd_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckhwd_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckhwd_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpunpckhwd_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpunpckhwd_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_punpckhwd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues64[] = + { + { /*src2*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /* => */ RTUINT256_INIT_C( 0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C( 1, 2, 3, 0xf1f2f3f4f5f6f7f8), + /*src1*/ RTUINT256_INIT_C( 1, 2, 3, 0xe1e2e3e4e5e6e7e8), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0xf1f2e1e2f3f4e3e4) }, + { /*src2*/ RTUINT256_INIT_C( 4, 5, 7, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C( 4, 6, 7, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C( 8, 10, 11, 0x5555dddd6666eeee) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0x9c5c43d3e073cda0) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesOthers[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2b1b2f3f4b3b4, 0xf5f6b5b6f7f8b7b8, 0xd1d29192d3d49394, 0xd5d69596d7d89798) }, + { /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0x5555dddd6666eeee, 0x7777ffff88880000, 0x111199992222aaaa, 0x3333bbbb4444cccc) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x4d091eddf02addac, 0x6cdc096373d53294, 0xb42188002fa8e95b, 0x564cbf999ba262c3) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_punpckhwd_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckhwd_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckhwd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhwd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhwd_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhwd_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhwd_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhwd_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_punpckhwd_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckhwd_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckhwd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhwd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhwd_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhwd_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhwd_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhwd_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_punpckhwd_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckhwd_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckhwd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhwd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhwd_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhwd_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhwd_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhwd_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhwd_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhwd_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhwd_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhwd_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhwd_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhwd_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PUNPCKHDQ + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpckhdq_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpckhdq_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpckhdq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpckhdq_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_punpckhdq_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_punpckhdq_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckhdq_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckhdq_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpunpckhdq_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpunpckhdq_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckhdq_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckhdq_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpunpckhdq_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpunpckhdq_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_punpckhdq(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues64[] = + { + { /*src2*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /* => */ RTUINT256_INIT_C( 0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C( 1, 2, 3, 0xf1f2f3f4f5f6f7f8), + /*src1*/ RTUINT256_INIT_C( 1, 2, 3, 0xe1e2e3e4e5e6e7e8), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0xf1f2f3f4e1e2e3e4) }, + { /*src2*/ RTUINT256_INIT_C( 4, 5, 7, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C( 4, 6, 7, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C( 8, 10, 11, 0x55556666ddddeeee) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0x9c5ce07343d3cda0) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesOthers[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2f3f4b1b2b3b4, 0xf5f6f7f8b5b6b7b8, 0xd1d2d3d491929394, 0xd5d6d7d895969798) }, + { /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0x55556666ddddeeee, 0x77778888ffff0000, 0x111122229999aaaa, 0x33334444bbbbcccc) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x4d09f02a1eddddac, 0x6cdc73d509633294, 0xb4212fa88800e95b, 0x564c9ba2bf9962c3) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_punpckhdq_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckhdq_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckhdq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhdq_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhdq_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhdq_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhdq_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhdq_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_punpckhdq_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckhdq_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckhdq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhdq_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhdq_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhdq_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhdq_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhdq_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_punpckhdq_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckhdq_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckhdq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhdq_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhdq_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhdq_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhdq_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhdq_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhdq_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhdq_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhdq_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhdq_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhdq_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhdq_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PUNPCKHQDQ + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpckhqdq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpckhqdq_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_punpckhqdq_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_punpckhqdq_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckhqdq_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckhqdq_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpunpckhqdq_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpunpckhqdq_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckhqdq_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckhqdq_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpunpckhqdq_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpunpckhqdq_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_punpckhqdq(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesOthers[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xb1b2b3b4b5b6b7b8, 0xd1d2d3d4d5d6d7d8, 0x9192939495969798) }, + { /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0x5555666677778888, 0xddddeeeeffff0000, 0x1111222233334444, 0x9999aaaabbbbcccc) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x1eddddac09633294, 0xb4212fa8564c9ba2, 0x8800e95bbf9962c3) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_punpckhqdq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhqdq_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhqdq_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhqdq_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhqdq_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhqdq_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_punpckhqdq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhqdq_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhqdq_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhqdq_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhqdq_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhqdq_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_punpckhqdq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhqdq_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhqdq_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckhqdq_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhqdq_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhqdq_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhqdq_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhqdq_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhqdq_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhqdq_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhqdq_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckhqdq_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PUNPCKLBW + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpcklbw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpcklbw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpcklbw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpcklbw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_punpcklbw_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_punpcklbw_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpcklbw_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpcklbw_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpunpcklbw_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpunpcklbw_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpcklbw_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpcklbw_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpunpcklbw_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpunpcklbw_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_punpcklbw(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues64[] = + { + { /*src2*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /* => */ RTUINT256_INIT_C( 0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C( 1, 2, 3, 0xf1f2f3f4f5f6f7f8), + /*src1*/ RTUINT256_INIT_C( 1, 2, 3, 0xe1e2e3e4e5e6e7e8), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0xf5e5f6e6f7e7f8e8) }, + { /*src2*/ RTUINT256_INIT_C( 4, 5, 7, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C( 4, 6, 7, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C( 8, 10, 11, 0x77ff77ff88008800) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0x932309849699bbfd) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesOthers[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xe1a1e2a2e3a3e4a4, 0xe5a5e6a6e7a7e8a8, 0xc181c282c383c484, 0xc585c686c787c888) }, + { /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0x1199119922aa22aa, 0x33bb33bb44cc44cc, 0x55dd55dd66ee66ee, 0x77ff77ff88008800) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x3ef9f45c178ec8ec, 0x66406b723f56e633, 0x9c435cd3e0cd73a0, 0x932309849699bbfd) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_punpcklbw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpcklbw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpcklbw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpcklbw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklbw_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklbw_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklbw_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklbw_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_punpcklbw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpcklbw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpcklbw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpcklbw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklbw_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklbw_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklbw_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklbw_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_punpcklbw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpcklbw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpcklbw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpcklbw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpcklbw_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpcklbw_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklbw_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklbw_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklbw_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklbw_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklbw_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklbw_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklbw_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklbw_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PUNPCKLWD + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpcklwd_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpcklwd_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpcklwd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpcklwd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_punpcklwd_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_punpcklwd_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpcklwd_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpcklwd_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpunpcklwd_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpunpcklwd_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpcklwd_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpcklwd_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpunpcklwd_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpunpcklwd_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_punpcklwd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues64[] = + { + { /*src2*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /* => */ RTUINT256_INIT_C( 0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C( 1, 2, 3, 0xf1f2f3f4f5f6f7f8), + /*src1*/ RTUINT256_INIT_C( 1, 2, 3, 0xe1e2e3e4e5e6e7e8), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0xf5f6e5e6f7f8e7e8) }, + { /*src2*/ RTUINT256_INIT_C( 4, 5, 7, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C( 4, 6, 7, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C( 8, 10, 11, 0x7777ffff88880000) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0x9309238496bb99fd) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesOthers[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xe1e2a1a2e3e4a3a4, 0xe5e6a5a6e7e8a7a8, 0xc1c28182c3c48384, 0xc5c68586c7c88788) }, + { /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0x111199992222aaaa, 0x3333bbbb4444cccc, 0x5555dddd6666eeee, 0x7777ffff88880000) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x3ef4f95c17c88eec, 0x666b40723fe65633, 0x9c5c43d3e073cda0, 0x9309238496bb99fd) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_punpcklwd_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpcklwd_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpcklwd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpcklwd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklwd_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklwd_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklwd_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklwd_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_punpcklwd_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpcklwd_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpcklwd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpcklwd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklwd_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklwd_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklwd_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklwd_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_punpcklwd_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpcklwd_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpcklwd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpcklwd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpcklwd_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpcklwd_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklwd_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklwd_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklwd_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklwd_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklwd_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklwd_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklwd_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklwd_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PUNPCKLDQ + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpckldq_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpckldq_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpckldq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpckldq_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_punpckldq_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_punpckldq_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckldq_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckldq_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpunpckldq_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpunpckldq_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckldq_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpckldq_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpunpckldq_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpunpckldq_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_punpckldq(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues64[] = + { + { /*src2*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /* => */ RTUINT256_INIT_C( 0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C( 1, 2, 3, 0xf1f2f3f4f5f6f7f8), + /*src1*/ RTUINT256_INIT_C( 1, 2, 3, 0xe1e2e3e4e5e6e7e8), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0xf5f6f7f8e5e6e7e8) }, + { /*src2*/ RTUINT256_INIT_C( 4, 5, 7, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C( 4, 6, 7, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C( 8, 10, 11, 0x77778888ffff0000) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0x930996bb238499fd) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesOthers[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xe1e2e3e4a1a2a3a4, 0xe5e6e7e8a5a6a7a8, 0xc1c2c3c481828384, 0xc5c6c7c885868788) }, + { /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0x111122229999aaaa, 0x33334444bbbbcccc, 0x55556666ddddeeee, 0x77778888ffff0000) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x3ef417c8f95c8eec, 0x666b3fe640725633, 0x9c5ce07343d3cda0, 0x930996bb238499fd) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_punpckldq_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckldq_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckldq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckldq_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckldq_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckldq_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckldq_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckldq_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_punpckldq_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckldq_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckldq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckldq_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckldq_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckldq_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckldq_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckldq_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_punpckldq_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckldq_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_punpckldq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckldq_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckldq_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpckldq_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckldq_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckldq_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckldq_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckldq_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckldq_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckldq_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckldq_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpckldq_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PUNPCKLQDQ + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpcklqdq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_punpcklqdq_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_punpcklqdq_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_punpcklqdq_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpcklqdq_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpcklqdq_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpunpcklqdq_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpunpcklqdq_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpcklqdq_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpunpcklqdq_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpunpcklqdq_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpunpcklqdq_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_punpcklqdq(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesOthers[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xe1e2e3e4e5e6e7e8, 0xa1a2a3a4a5a6a7a8, 0xc1c2c3c4c5c6c7c8, 0x8182838485868788) }, + { /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0x1111222233334444, 0x9999aaaabbbbcccc, 0x5555666677778888, 0xddddeeeeffff0000) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x3ef417c8666b3fe6, 0xf95c8eec40725633, 0x9c5ce073930996bb, 0x43d3cda0238499fd) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_punpcklqdq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpcklqdq_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklqdq_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklqdq_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklqdq_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklqdq_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_punpcklqdq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpcklqdq_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklqdq_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklqdq_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklqdq_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklqdq_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_punpcklqdq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpcklqdq_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpcklqdq_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_punpcklqdq_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklqdq_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklqdq_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklqdq_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklqdq_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklqdq_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklqdq_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklqdq_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpunpcklqdq_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PACKSSWB + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_packsswb_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_packsswb_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_packsswb_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_packsswb_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_packsswb_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_packsswb_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpacksswb_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpacksswb_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpacksswb_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpacksswb_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpacksswb_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpacksswb_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpacksswb_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpacksswb_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_packsswb(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues64[] = + { + { /*src2*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /* => */ RTUINT256_INIT_C( 0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C( 1, 2, 3, 0xf1f2f3f4f5f6f7f8), + /*src1*/ RTUINT256_INIT_C( 1, 2, 3, 0xe1e2e3e4e5e6e7e8), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0x8080808080808080) }, + { /*src2*/ RTUINT256_INIT_C( 4, 5, 7, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C( 4, 6, 7, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C( 8, 10, 11, 0x7f7f7f808080ff00) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0x808080807f807f80) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0xFF820064fffe0042), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x0022fe00ff80ff81), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0x8264fe4222808081) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesOthers[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x8080808080808080, 0x8080808080808080, 0x8080808080808080, 0x8080808080808080) }, + { /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0x7f7f7f807f7f7f7f, 0x8080ff0080808080, 0x7f7f7f7f7f7f7f80, 0x808080808080ff00) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x7f807f7f7f7f7f7f, 0x7f807f7f80807f7f, 0x807f7f8080808080, 0x8080807f7f807f80) }, + { /*src2*/ RTUINT256_INIT_C(0x002200250079007e, 0xfffffffeff88ff7f, 0x0064003200160008, 0x0042004600880080), + /*src1*/ RTUINT256_INIT_C(0x0001000200030005, 0x0007000b000d0011, 0x00130017001d0025, 0x0029002b002f0035), + /* => */ RTUINT256_INIT_C(0x2225797efffe8880, 0x01020305070b0d11, 0x6432160842467f7f, 0x13171d25292b2f35) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_packsswb_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_packsswb_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_packsswb_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packsswb_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpacksswb_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpacksswb_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpacksswb_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpacksswb_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_packsswb_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_packsswb_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_packsswb_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packsswb_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpacksswb_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpacksswb_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpacksswb_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpacksswb_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_packsswb_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_packsswb_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_packsswb_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packsswb_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packsswb_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE2, 8, 8, 9, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packsswb_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 8, 8, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpacksswb_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpacksswb_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpacksswb_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpacksswb_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpacksswb_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpacksswb_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpacksswb_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpacksswb_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PACKSSDW + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_packssdw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_packssdw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_packssdw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_packssdw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_packssdw_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_packssdw_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpackssdw_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpackssdw_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpackssdw_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpackssdw_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpackssdw_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpackssdw_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpackssdw_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpackssdw_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_packssdw(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues64[] = + { + { /*src2*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /* => */ RTUINT256_INIT_C( 0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C( 1, 2, 3, 0xf1f2f3f4f5f6f7f8), + /*src1*/ RTUINT256_INIT_C( 1, 2, 3, 0xe1e2e3e4e5e6e7e8), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0x8000800080008000) }, + { /*src2*/ RTUINT256_INIT_C( 4, 5, 7, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C( 4, 6, 7, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C( 8, 10, 11, 0x7fff7fff80008000) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0x800080007fff7fff) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0xffff898400007495), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x00002222ffff9485), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0x8984749522229485) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesOthers[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x8000800080008000, 0x8000800080008000, 0x8000800080008000, 0x8000800080008000) }, + { /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0x7fff7fff7fff7fff, 0x8000800080008000, 0x7fff7fff7fff7fff, 0x8000800080008000) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x7fff7fff7fff7fff, 0x7fff7fff80007fff, 0x80007fff80008000, 0x800080007fff7fff) }, + { /*src2*/ RTUINT256_INIT_C(0x0000349000002349, 0xffffa230ffffe384, 0xffff348300007ffe, 0x00008000ffff7fff), + /*src1*/ RTUINT256_INIT_C(0xffff800100007ffe, 0xffffcbaffffffffe, 0x0000643200001608, 0xffffffe0ffffffc0), + /* => */ RTUINT256_INIT_C(0x34902349a230e384, 0x80017ffecbaffffe, 0x80007ffe7fff8000, 0x64321608ffe0ffc0) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_packssdw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_packssdw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_packssdw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packssdw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackssdw_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackssdw_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackssdw_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackssdw_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_packssdw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_packssdw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_packssdw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packssdw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackssdw_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackssdw_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackssdw_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackssdw_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_packssdw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_packssdw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_packssdw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packssdw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packssdw_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE2, 8, 8, 9, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packssdw_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 8, 8, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackssdw_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackssdw_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackssdw_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackssdw_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackssdw_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackssdw_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackssdw_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackssdw_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PACKUSWB + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_packuswb_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_packuswb_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_packuswb_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_packuswb_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_packuswb_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_packuswb_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpackuswb_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpackuswb_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpackuswb_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpackuswb_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpackuswb_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpackuswb_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpackuswb_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpackuswb_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_packuswb(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues64[] = + { + { /*src2*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /* => */ RTUINT256_INIT_C( 0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C( 1, 2, 3, 0xf1f2f3f4f5f6f7f8), + /*src1*/ RTUINT256_INIT_C( 1, 2, 3, 0xe1e2e3e4e5e6e7e8), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0x0000000000000000) }, + { /*src2*/ RTUINT256_INIT_C( 4, 5, 7, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C( 4, 6, 7, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C( 8, 10, 11, 0xffffff0000000000) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0x00000000ff00ff00) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0xFF820064fffe0042), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x0022fe00ff80ff81), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0x0064004222000000) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesOthers[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000) }, + { /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0xffffff00ffffffff, 0x0000000000000000, 0xffffffffffffff00, 0x000000000000000) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0xff00ffffffffffff, 0xff00ffff0000ffff, 0x00ffff0000000000, 0x000000ffff00ff00) }, + { /*src2*/ RTUINT256_INIT_C(0x002200250079007e, 0xfffffffeff88ff7f, 0x0064003200160008, 0x0042004600880080), + /*src1*/ RTUINT256_INIT_C(0x0001000200030005, 0x0007000b000d0011, 0x00130017001d0025, 0x0029002b002f0035), + /* => */ RTUINT256_INIT_C(0x2225797e00000000, 0x01020305070b0d11, 0x6432160842468880, 0x13171d25292b2f35) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_packuswb_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_packuswb_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_packuswb_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packuswb_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackuswb_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackuswb_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackuswb_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackuswb_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_packuswb_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_packuswb_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_packuswb_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packuswb_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackuswb_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackuswb_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackuswb_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackuswb_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_packuswb_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX, 1, 1, 2, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_packuswb_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX, 1, 1, 255, RT_ELEMENTS(s_aValues64), s_aValues64 }, + { bs3CpuInstr3_packuswb_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packuswb_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packuswb_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE2, 8, 8, 9, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packuswb_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 8, 8, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackuswb_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackuswb_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackuswb_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackuswb_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackuswb_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackuswb_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackuswb_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackuswb_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PACKUSDW + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_packusdw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_packusdw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_packusdw_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_packusdw_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpackusdw_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpackusdw_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpackusdw_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpackusdw_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpackusdw_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpackusdw_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpackusdw_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpackusdw_YMM8_YMM9_FSxBX_icebp_c64; +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_packusdw(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesOthers[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000) }, + { /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0x0000000000000000, 0xffffffffffffffff, 0x0000000000000000) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffff0000ffff, 0x0000ffff00000000, 0x00000000ffffffff) }, + { /*src2*/ RTUINT256_INIT_C(0x0000349000002349, 0xffffa230ffffe384, 0xffff348300007ffe, 0x00008000ffff7fff), + /*src1*/ RTUINT256_INIT_C(0xffff800100007ffe, 0xffffcbaffffffffe, 0x0000643200001608, 0xffffffe0ffffffc0), + /* => */ RTUINT256_INIT_C(0x3490234900000000, 0x00007ffe00000000, 0x00007ffe80000000, 0x6432160800000000) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_packusdw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packusdw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackusdw_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackusdw_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackusdw_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackusdw_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_packusdw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packusdw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackusdw_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackusdw_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackusdw_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackusdw_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_packusdw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packusdw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packusdw_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE4_1, 8, 8, 9, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_packusdw_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 8, 8, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackusdw_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackusdw_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackusdw_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackusdw_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackusdw_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackusdw_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackusdw_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + { bs3CpuInstr3_vpackusdw_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesOthers), s_aValuesOthers }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PMAXUB - Compare unsigned byte integers and returns maximum values. + * [V]PMAXUW - Compare unsigned word integers and returns maximum values. + * [V]PMAXUD - Compare unsigned double word integers and returns maximum values. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaxub_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaxub_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaxub_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaxub_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmaxub_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmaxub_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxub_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxub_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmaxub_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmaxub_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxub_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxub_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmaxub_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmaxub_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaxuw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaxuw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmaxuw_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmaxuw_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxuw_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxuw_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmaxuw_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmaxuw_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxuw_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxuw_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmaxuw_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmaxuw_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaxud_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaxud_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmaxud_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmaxud_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxud_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxud_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmaxud_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmaxud_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxud_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxud_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmaxud_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmaxud_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pmaxub_pmaxuw_pmaxud(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesB64[] = + { + { /*src2*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /* => */ RTUINT256_INIT_C( 0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C( 1, 2, 3, 0xf1f2f3f4f5f6f7f8), + /*src1*/ RTUINT256_INIT_C( 1, 2, 3, 0xe1e2e3e4e5e6e7e8), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0xf1f2f3f4f5f6f7f8) }, + { /*src2*/ RTUINT256_INIT_C( 4, 5, 7, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C( 4, 6, 7, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C( 4, 6, 7, 0xddddeeeeffff8888) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0x9cd3e0a0938499fd) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0xff820064fffe0042), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x0022fe00ff80ff81), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0xff82fe64fffeff81) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesB[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x4dddf0ac6cdc73d5, 0xf9f48eec667256e6, 0xb421e9a8bf999bc3, 0x9cd3e0a0938499fd) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesW[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0xf95c8eec666b5633, 0xb421e95bbf999ba2, 0x9c5ce073930999fd) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesD[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0xf95c8eec666b3fe6, 0xb4212fa8bf9962c3, 0x9c5ce073930996bb) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pmaxub_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pmaxub_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pmaxub_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pmaxub_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxub_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxub_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxub_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxub_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pmaxuw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmaxuw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxuw_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxuw_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxuw_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxuw_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pmaxud_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pmaxud_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxud_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxud_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxud_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxud_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pmaxub_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pmaxub_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pmaxub_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pmaxub_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxub_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxub_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxub_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxub_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pmaxuw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmaxuw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxuw_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxuw_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxuw_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxuw_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pmaxud_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pmaxud_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxud_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxud_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxud_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxud_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pmaxub_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pmaxub_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pmaxub_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pmaxub_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pmaxub_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE2, 8, 8, 9, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pmaxub_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 8, 8, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxub_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxub_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxub_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxub_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxub_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxub_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxub_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxub_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pmaxuw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmaxuw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmaxuw_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE4_1, 8, 8, 9, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmaxuw_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 8, 8, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxuw_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxuw_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxuw_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxuw_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxuw_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxuw_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxuw_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxuw_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pmaxud_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pmaxud_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pmaxud_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE4_1, 8, 8, 9, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pmaxud_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 8, 8, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxud_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxud_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxud_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxud_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxud_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxud_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxud_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxud_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PMAXSB - Compare signed byte integers and returns maximum values. + * [V]PMAXSW - Compare signed word integers and returns maximum values. + * [V]PMAXSD - Compare signed double word integers and returns maximum values. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaxsb_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaxsb_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmaxsb_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmaxsb_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxsb_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxsb_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmaxsb_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmaxsb_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxsb_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxsb_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmaxsb_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmaxsb_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaxsw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaxsw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaxsw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaxsw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmaxsw_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmaxsw_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxsw_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxsw_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmaxsw_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmaxsw_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxsw_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxsw_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmaxsw_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmaxsw_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaxsd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaxsd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmaxsd_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmaxsd_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxsd_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxsd_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmaxsd_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmaxsd_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxsd_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaxsd_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmaxsd_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmaxsd_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pmaxsb_pmaxsw_pmaxsd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesB64[] = + { + { /*src2*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /* => */ RTUINT256_INIT_C( 0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C( 1, 2, 3, 0xf1f2f3f4f5f6f7f8), + /*src1*/ RTUINT256_INIT_C( 1, 2, 3, 0xe1e2e3e4e5e6e7e8), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0xf1f2f3f4f5f6f7f8) }, + { /*src2*/ RTUINT256_INIT_C( 4, 5, 7, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C( 4, 6, 7, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C( 4, 6, 7, 0x5555666677770000) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0x43d3e073238499fd) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0xff820064fffe0042), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x0022fe00ff80ff81), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0x00220064fffe0042) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesB[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x4d09f02a6c6373d5, 0x3e5c17ec66725633, 0xb4212f5b564c62c3, 0x435ce073230999fd) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesW[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b5633, 0xb4212fa8564c62c3, 0x43d3e073238499fd) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesD[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x43d3cda0238499fd) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pmaxsb_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pmaxsb_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxsb_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxsb_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxsb_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxsb_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pmaxsw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pmaxsw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pmaxsw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmaxsw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxsw_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxsw_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxsw_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxsw_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pmaxsd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pmaxsd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxsd_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxsd_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxsd_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxsd_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pmaxsb_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pmaxsb_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxsb_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxsb_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxsb_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxsb_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pmaxsw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pmaxsw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pmaxsw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmaxsw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxsw_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxsw_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxsw_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxsw_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pmaxsd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pmaxsd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxsd_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxsd_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxsd_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxsd_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pmaxsb_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pmaxsb_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pmaxsb_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE4_1, 8, 8, 9, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pmaxsb_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 8, 8, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxsb_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxsb_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxsb_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxsb_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxsb_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxsb_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxsb_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpmaxsb_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pmaxsw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pmaxsw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pmaxsw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmaxsw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmaxsw_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE2, 8, 8, 9, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pmaxsw_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 8, 8, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxsw_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxsw_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxsw_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxsw_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxsw_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxsw_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxsw_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpmaxsw_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pmaxsd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pmaxsd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pmaxsd_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE4_1, 8, 8, 9, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pmaxsd_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 8, 8, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxsd_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxsd_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxsd_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxsd_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxsd_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxsd_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxsd_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpmaxsd_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PMINUB - Compare unsigned byte integers and returns minimum values. + * [V]PMINUW - Compare unsigned word integers and returns minimum values. + * [V]PMINUD - Compare unsigned double word integers and returns minimum values. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pminub_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pminub_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pminub_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pminub_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pminub_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pminub_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminub_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminub_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpminub_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpminub_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminub_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminub_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpminub_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpminub_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pminuw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pminuw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pminuw_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pminuw_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminuw_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminuw_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpminuw_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpminuw_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminuw_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminuw_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpminuw_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpminuw_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pminud_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pminud_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pminud_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pminud_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminud_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminud_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpminud_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpminud_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminud_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminud_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpminud_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpminud_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pminub_pminuw_pminud(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesB64[] = + { + { /*src2*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /* => */ RTUINT256_INIT_C( 0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C( 1, 2, 3, 0xf1f2f3f4f5f6f7f8), + /*src1*/ RTUINT256_INIT_C( 1, 2, 3, 0xe1e2e3e4e5e6e7e8), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0xe1e2e3e4e5e6e7e8) }, + { /*src2*/ RTUINT256_INIT_C( 4, 5, 7, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C( 4, 6, 7, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C( 4, 6, 7, 0x5555666677770000) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0x435ccd73230996bb) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0xff820064fffe0042), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x0022fe00ff80ff81), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0x00220000ff800042) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesB[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x1e09dd2a09633294, 0x3e5c17c8406b3f33, 0x88002f5b564c62a2, 0x435ccd73230996bb) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesW[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x1eddddac09633294, 0x3ef417c840723fe6, 0x88002fa8564c62c3, 0x43d3cda0238496bb) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesD[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x1eddddac09633294, 0x3ef417c840725633, 0x8800e95b564c9ba2, 0x43d3cda0238499fd) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pminub_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pminub_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pminub_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pminub_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminub_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminub_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminub_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminub_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pminuw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pminuw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminuw_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminuw_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminuw_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminuw_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pminud_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pminud_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminud_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminud_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminud_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminud_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pminub_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pminub_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pminub_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pminub_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminub_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminub_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminub_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminub_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pminuw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pminuw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminuw_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminuw_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminuw_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminuw_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pminud_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pminud_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminud_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminud_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminud_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminud_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pminub_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pminub_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pminub_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pminub_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pminub_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE2, 8, 8, 9, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pminub_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 8, 8, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminub_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminub_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminub_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminub_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminub_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminub_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminub_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminub_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pminuw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pminuw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pminuw_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE4_1, 8, 8, 9, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pminuw_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 8, 8, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminuw_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminuw_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminuw_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminuw_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminuw_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminuw_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminuw_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminuw_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pminud_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pminud_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pminud_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE4_1, 8, 8, 9, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pminud_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 8, 8, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminud_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminud_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminud_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminud_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminud_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminud_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminud_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminud_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PMINSB - Compare signed byte integers and returns minimum values. + * [V]PMINSW - Compare signed word integers and returns minimum values. + * [V]PMINSD - Compare signed double word integers and returns minimum values. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pminsb_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pminsb_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pminsb_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pminsb_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminsb_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminsb_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpminsb_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpminsb_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminsb_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminsb_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpminsb_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpminsb_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pminsw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pminsw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pminsw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pminsw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pminsw_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pminsw_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminsw_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminsw_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpminsw_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpminsw_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminsw_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminsw_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpminsw_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpminsw_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pminsd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pminsd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pminsd_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pminsd_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminsd_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminsd_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpminsd_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpminsd_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminsd_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpminsd_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpminsd_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpminsd_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pminsb_pminsw_pminsd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesB64[] = + { + { /*src2*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C( 0, 0, 0, 0), + /* => */ RTUINT256_INIT_C( 0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C( 1, 2, 3, 0xf1f2f3f4f5f6f7f8), + /*src1*/ RTUINT256_INIT_C( 1, 2, 3, 0xe1e2e3e4e5e6e7e8), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0xe1e2e3e4e5e6e7e8) }, + { /*src2*/ RTUINT256_INIT_C( 4, 5, 7, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C( 4, 6, 7, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C( 4, 6, 7, 0xddddeeeeffff8888) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0x9c5ccda0930996bb) }, + { /*src2*/ RTUINT256_INIT_C( 8, 10, 11, 0xff820064fffe0042), + /*src1*/ RTUINT256_INIT_C(12, 13, 14, 0x0022fe00ff80ff81), + /* => */ RTUINT256_INIT_C(12, 13, 14, 0xff82fe00ff80ff81) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesB[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x1eddddac09dc3294, 0xf9f48ec8406b3fe6, 0x8800e9a8bf999ba2, 0x9cd3cda0938496bb) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesW[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40723fe6, 0x8800e95bbf999ba2, 0x9c5ccda0930996bb) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesD[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x9c5ce073930996bb) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pminsb_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pminsb_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminsb_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminsb_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminsb_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminsb_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pminsw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pminsw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pminsw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pminsw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminsw_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminsw_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminsw_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminsw_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pminsd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pminsd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminsd_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminsd_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminsd_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminsd_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pminsb_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pminsb_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminsb_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminsb_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminsb_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminsb_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pminsw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pminsw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pminsw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pminsw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminsw_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminsw_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminsw_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminsw_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pminsd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pminsd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminsd_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminsd_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminsd_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminsd_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pminsb_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pminsb_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pminsb_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE4_1, 8, 8, 9, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pminsb_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 8, 8, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminsb_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminsb_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminsb_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminsb_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminsb_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminsb_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminsb_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpminsb_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pminsw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pminsw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesB64), s_aValuesB64 }, + { bs3CpuInstr3_pminsw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pminsw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pminsw_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE2, 8, 8, 9, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pminsw_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 8, 8, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminsw_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminsw_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminsw_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminsw_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminsw_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminsw_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminsw_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpminsw_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pminsd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pminsd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pminsd_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE4_1, 8, 8, 9, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pminsd_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 8, 8, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminsd_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminsd_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminsd_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminsd_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminsd_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminsd_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminsd_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpminsd_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]MOVSS - move (mem) or merge (reg) scalar single-precision floating-point value. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movss_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movss_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movss_FSxBX_XMM1_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovss_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovss_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovss_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_movss_XMM11_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movss_XMM8_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movss_FSxBX_XMM11_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovss_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovss_XMM10_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovss_FSxBX_XMM9_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_movss(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesR[] = + { + { /*src*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*dst-in*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*dst-in*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x81828384c5c6c7c8) }, + { /*src*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*dst-in*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeee77778888) }, + { /*src*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*dst-in*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0930996bb) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesM[] = + { + { /*src*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*dst-in*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*dst-in*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x00000000c5c6c7c8) }, + { /*src*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*dst-in*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000077778888) }, + { /*src*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*dst-in*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x00000000930996bb) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_movss_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesR), s_aValuesR }, + { bs3CpuInstr3_movss_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM32, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_movss_FSxBX_XMM1_icebp_c16, X86_XCPT_AC, RM_MEM32, T_SSE, 255, 128, 1, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + + { bs3CpuInstr3_vmovss_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesR), s_aValuesR }, + { bs3CpuInstr3_vmovss_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM32, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_vmovss_FSxBX_XMM1_icebp_c16, X86_XCPT_AC, RM_MEM32, T_AVX_128, 255, 128, 1, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_movss_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesR), s_aValuesR }, + { bs3CpuInstr3_movss_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM32, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_movss_FSxBX_XMM1_icebp_c32, X86_XCPT_AC, RM_MEM32, T_SSE, 255, 128, 1, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + + { bs3CpuInstr3_vmovss_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesR), s_aValuesR }, + { bs3CpuInstr3_vmovss_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM32, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_vmovss_FSxBX_XMM1_icebp_c32, X86_XCPT_AC, RM_MEM32, T_AVX_128, 255, 128, 1, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_movss_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesR), s_aValuesR }, + { bs3CpuInstr3_movss_XMM11_XMM8_icebp_c64, 255, RM_REG, T_SSE, 11, 11, 8, RT_ELEMENTS(s_aValuesR), s_aValuesR }, + { bs3CpuInstr3_movss_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM32, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_movss_XMM8_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM32, T_SSE, 8, 8, 255, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_movss_FSxBX_XMM1_icebp_c64, X86_XCPT_AC, RM_MEM32, T_SSE, 255, 128, 1, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_movss_FSxBX_XMM11_icebp_c64, X86_XCPT_AC, RM_MEM32, T_SSE, 255, 128, 11, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + + { bs3CpuInstr3_vmovss_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesR), s_aValuesR }, + { bs3CpuInstr3_vmovss_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 9, 9, 10, RT_ELEMENTS(s_aValuesR), s_aValuesR }, + { bs3CpuInstr3_vmovss_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM32, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_vmovss_XMM10_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM32, T_AVX_128, 10, 10, 255, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_vmovss_FSxBX_XMM1_icebp_c64, X86_XCPT_AC, RM_MEM32, T_AVX_128, 255, 128, 1, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_vmovss_FSxBX_XMM9_icebp_c64, X86_XCPT_AC, RM_MEM32, T_AVX_128, 255, 128, 9, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig5, RT_ELEMENTS(g_aXcptConfig5)); +} + + +/* + * [V]MOVSD - move (mem) or merge (reg) scalar single-precision floating-point value. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movsd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movsd_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movsd_FSxBX_XMM1_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovsd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovsd_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovsd_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_movsd_XMM11_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movsd_XMM8_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movsd_FSxBX_XMM11_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovsd_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovsd_XMM10_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovsd_FSxBX_XMM9_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_movsd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesR[] = + { + { /*src*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*dst-in*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*dst-in*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0xc1c2c3c4c5c6c7c8) }, + { /*src*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x81828384c5c6c7c8), + /*dst-in*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0x81828384c5c6c7c8) }, + { /*src*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*dst-in*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x9c5ce073930996bb) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesM[] = + { + { /*src*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*dst-in*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*dst-in*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0xc1c2c3c4c5c6c7c8) }, + { /*src*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*dst-in*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x5555666677778888) }, + { /*src*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*dst-in*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x9c5ce073930996bb) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_movsd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesR), s_aValuesR }, + { bs3CpuInstr3_movsd_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM64, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_movsd_FSxBX_XMM1_icebp_c16, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 1, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + + { bs3CpuInstr3_vmovsd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesR), s_aValuesR }, + { bs3CpuInstr3_vmovsd_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM64, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_vmovsd_FSxBX_XMM1_icebp_c16, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 1, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_movsd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesR), s_aValuesR }, + { bs3CpuInstr3_movsd_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM64, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_movsd_FSxBX_XMM1_icebp_c32, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 1, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + + { bs3CpuInstr3_vmovsd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesR), s_aValuesR }, + { bs3CpuInstr3_vmovsd_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM64, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_vmovsd_FSxBX_XMM1_icebp_c32, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 1, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_movsd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesR), s_aValuesR }, + { bs3CpuInstr3_movsd_XMM11_XMM8_icebp_c64, 255, RM_REG, T_SSE, 11, 11, 8, RT_ELEMENTS(s_aValuesR), s_aValuesR }, + { bs3CpuInstr3_movsd_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_movsd_XMM8_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 8, 8, 255, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_movsd_FSxBX_XMM1_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 1, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_movsd_FSxBX_XMM11_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 11, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + + { bs3CpuInstr3_vmovsd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 1, 2, RT_ELEMENTS(s_aValuesR), s_aValuesR }, + { bs3CpuInstr3_vmovsd_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 9, 9, 10, RT_ELEMENTS(s_aValuesR), s_aValuesR }, + { bs3CpuInstr3_vmovsd_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 1, 1, 255, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_vmovsd_XMM10_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 10, 10, 255, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_vmovsd_FSxBX_XMM1_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 1, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + { bs3CpuInstr3_vmovsd_FSxBX_XMM9_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 9, RT_ELEMENTS(s_aValuesM), s_aValuesM }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig5, RT_ELEMENTS(g_aXcptConfig5)); +} + + +/* + * [V]MOVLPS - Merge a low qword (two single precision floating-point values) + * from memory with the high qword from a register (SSE destination + * or VEX 2nd source). + * The store variant just stores the high qword. + * [V]MOVLPD - Same, just using double precision floating-point unit. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movlps_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movlps_FSxBX_XMM1_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovlps_XMM1_XMM2_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovlps_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_movlps_XMM8_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movlps_FSxBX_XMM11_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovlps_XMM10_XMM14_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovlps_FSxBX_XMM9_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movlpd_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movlpd_FSxBX_XMM1_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovlpd_XMM1_XMM2_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovlpd_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_movlpd_XMM8_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movlpd_FSxBX_XMM11_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovlpd_XMM10_XMM14_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovlpd_FSxBX_XMM9_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_movlps_movlpd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesLd[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x9192939495969798, 0xc1c2c3c4c5c6c7c8) }, + { /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x81828384c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x9999aaaabbbbcccc, 0x81828384c5c6c7c8) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x8800e95bbf9962c3, 0x9c5ce073930996bb) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesSt[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x9192939495969798, 0xc1c2c3c4c5c6c7c8) }, + { /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x81828384c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x9999aaaabbbbcccc, 0x81828384c5c6c7c8) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x8800e95bbf9962c3, 0x9c5ce073930996bb) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_movlps_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM64, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_movlps_FSxBX_XMM1_icebp_c16, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt }, + { bs3CpuInstr3_movlpd_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM64, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_movlpd_FSxBX_XMM1_icebp_c16, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt }, + + { bs3CpuInstr3_vmovlps_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM64, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_vmovlps_FSxBX_XMM1_icebp_c16, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt}, + { bs3CpuInstr3_vmovlpd_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM64, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_vmovlpd_FSxBX_XMM1_icebp_c16, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt}, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_movlps_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM64, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_movlps_FSxBX_XMM1_icebp_c32, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt }, + { bs3CpuInstr3_movlpd_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM64, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_movlpd_FSxBX_XMM1_icebp_c32, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt }, + + { bs3CpuInstr3_vmovlps_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM64, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_vmovlps_FSxBX_XMM1_icebp_c32, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt}, + { bs3CpuInstr3_vmovlpd_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM64, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_vmovlpd_FSxBX_XMM1_icebp_c32, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt}, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_movlps_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_movlps_XMM8_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 8, 8, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_movlps_FSxBX_XMM1_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt }, + { bs3CpuInstr3_movlps_FSxBX_XMM11_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 11, RT_ELEMENTS(s_aValuesSt), s_aValuesSt }, + { bs3CpuInstr3_movlpd_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_movlpd_XMM8_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 8, 8, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_movlpd_FSxBX_XMM1_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt }, + { bs3CpuInstr3_movlpd_FSxBX_XMM11_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 11, RT_ELEMENTS(s_aValuesSt), s_aValuesSt }, + + { bs3CpuInstr3_vmovlps_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_vmovlps_XMM10_XMM14_FSxBX_icebp_c64,X86_XCPT_AC, RM_MEM64, T_AVX_128, 10, 14, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_vmovlps_FSxBX_XMM1_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt}, + { bs3CpuInstr3_vmovlps_FSxBX_XMM9_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 9, RT_ELEMENTS(s_aValuesSt), s_aValuesSt}, + { bs3CpuInstr3_vmovlpd_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_vmovlpd_XMM10_XMM14_FSxBX_icebp_c64,X86_XCPT_AC, RM_MEM64, T_AVX_128, 10, 14, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_vmovlpd_FSxBX_XMM1_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt}, + { bs3CpuInstr3_vmovlpd_FSxBX_XMM9_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 9, RT_ELEMENTS(s_aValuesSt), s_aValuesSt}, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig5, RT_ELEMENTS(g_aXcptConfig5)); +} + + +/* + * [V]MOVHPS - Merge a high qword (two single precision floating-point values) + * from memory with the low qword from a register (SSE destination + * or VEX 2nd source). + * The store variant just stores the high qword. + * [V]MOVHPD - Same, just using double precision floating-point unit. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movhps_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movhps_FSxBX_XMM1_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovhps_XMM1_XMM2_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovhps_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_movhps_XMM8_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movhps_FSxBX_XMM11_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovhps_XMM10_XMM14_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovhps_FSxBX_XMM9_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movhpd_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movhpd_FSxBX_XMM1_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovhpd_XMM1_XMM2_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovhpd_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_movhpd_XMM8_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movhpd_FSxBX_XMM11_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovhpd_XMM10_XMM14_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovhpd_FSxBX_XMM9_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_movhps_movhpd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesLd[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0xc1c2c3c4c5c6c7c8, 0x8182838485868788) }, + { /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x5555666677778888, 0xddddeeeeffff0000) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x9c5ce073930996bb, 0x43d3cda0238499fd) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesSt[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*ign*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*ign*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0xd1d2d3d4d5d6d7d8) }, + { /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x81828384c5c6c7c8), + /*ign*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x1111222233334444) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*ign*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0xb4212fa8564c9ba2) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_movhps_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM64, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_movhps_FSxBX_XMM1_icebp_c16, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt }, + { bs3CpuInstr3_movhpd_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM64, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_movhpd_FSxBX_XMM1_icebp_c16, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt }, + + { bs3CpuInstr3_vmovhps_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM64, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_vmovhps_FSxBX_XMM1_icebp_c16, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt}, + { bs3CpuInstr3_vmovhpd_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM64, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_vmovhpd_FSxBX_XMM1_icebp_c16, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt}, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_movhps_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM64, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_movhps_FSxBX_XMM1_icebp_c32, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt }, + { bs3CpuInstr3_movhpd_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM64, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_movhpd_FSxBX_XMM1_icebp_c32, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt }, + + { bs3CpuInstr3_vmovhps_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM64, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_vmovhps_FSxBX_XMM1_icebp_c32, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt}, + { bs3CpuInstr3_vmovhpd_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM64, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_vmovhpd_FSxBX_XMM1_icebp_c32, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt}, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_movhps_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_movhps_XMM8_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 8, 8, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_movhps_FSxBX_XMM1_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt }, + { bs3CpuInstr3_movhps_FSxBX_XMM11_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 11, RT_ELEMENTS(s_aValuesSt), s_aValuesSt }, + { bs3CpuInstr3_movhpd_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_movhpd_XMM8_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 8, 8, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_movhpd_FSxBX_XMM1_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt }, + { bs3CpuInstr3_movhpd_FSxBX_XMM11_icebp_c64, X86_XCPT_AC, RM_MEM64, T_SSE, 255, 128, 11, RT_ELEMENTS(s_aValuesSt), s_aValuesSt }, + + { bs3CpuInstr3_vmovhps_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_vmovhps_XMM10_XMM14_FSxBX_icebp_c64,X86_XCPT_AC, RM_MEM64, T_AVX_128, 10, 14, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_vmovhps_FSxBX_XMM1_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt}, + { bs3CpuInstr3_vmovhps_FSxBX_XMM9_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 9, RT_ELEMENTS(s_aValuesSt), s_aValuesSt}, + { bs3CpuInstr3_vmovhpd_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_vmovhpd_XMM10_XMM14_FSxBX_icebp_c64,X86_XCPT_AC, RM_MEM64, T_AVX_128, 10, 14, 255, RT_ELEMENTS(s_aValuesLd), s_aValuesLd }, + { bs3CpuInstr3_vmovhpd_FSxBX_XMM1_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 1, RT_ELEMENTS(s_aValuesSt), s_aValuesSt}, + { bs3CpuInstr3_vmovhpd_FSxBX_XMM9_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 255, 128, 9, RT_ELEMENTS(s_aValuesSt), s_aValuesSt}, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig5, RT_ELEMENTS(g_aXcptConfig5)); +} + + +/* + * [V]MOVHLPS - Move high qword in source (2) to low qword in destination, leaving + * the high qword in the destination as it was. The VEX variant + * takes the high qword from the first source operand. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movhlps_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovhlps_XMM1_XMM2_XMM3_icebp); +extern FNBS3FAR bs3CpuInstr3_movhlps_XMM8_XMM12_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovhlps_XMM10_XMM14_XMM12_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_movhlps(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x9192939495969798, 0xd1d2d3d4d5d6d7d8) }, + { /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x81828384c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x9999aaaabbbbcccc, 0x1111222233334444) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x8800e95bbf9962c3, 0xb4212fa8564c9ba2) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_movhlps_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovhlps_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_movhlps_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovhlps_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_movhlps_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_movhlps_XMM8_XMM12_icebp_c64, 255, RM_REG, T_SSE, 8, 8, 12, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovhlps_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovhlps_XMM10_XMM14_XMM12_icebp_c64, 255, RM_REG, T_AVX_128, 10, 14, 12, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig5, RT_ELEMENTS(g_aXcptConfig5)); +} + + +/* + * [V]PAVGB - Average unsigned packed byte integers with rounding. + * [V]PAVGW - Average unsigned packed word integers with rounding. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pavgb_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pavgb_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pavgb_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pavgb_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pavgb_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pavgb_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpavgb_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpavgb_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpavgb_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpavgb_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpavgb_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpavgb_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpavgb_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpavgb_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pavgw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pavgw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pavgw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pavgw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pavgw_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pavgw_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpavgw_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpavgw_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpavgw_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpavgw_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpavgw_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpavgw_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpavgw_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpavgw_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pavgb_pavgw(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesB[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8, 0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x3673e76b3ba053b5, 0x9ca853da536f4b8d, 0x9e118c828b737fb3, 0x7098d78a5b4798dc) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesW[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8, 0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x35f3e6eb3b205335, 0x9c28535a536f4b0d, 0x9e118c828af37f33, 0x7018d70a5b47985c) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pavgb_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pavgb_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pavgb_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pavgb_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpavgb_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpavgb_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpavgb_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpavgb_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pavgw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pavgw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pavgw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pavgw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpavgw_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpavgw_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpavgw_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpavgw_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pavgb_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pavgb_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pavgb_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pavgb_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpavgb_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpavgb_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpavgb_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpavgb_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pavgw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pavgw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pavgw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pavgw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpavgw_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpavgw_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpavgw_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpavgw_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pavgb_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pavgb_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pavgb_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pavgb_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pavgb_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE2, 8, 8, 9, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pavgb_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 8, 8, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpavgb_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpavgb_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpavgb_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpavgb_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpavgb_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpavgb_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpavgb_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpavgb_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pavgw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pavgw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pavgw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pavgw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pavgw_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE2, 8, 8, 9, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pavgw_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 8, 8, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpavgw_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpavgw_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpavgw_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpavgw_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpavgw_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpavgw_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpavgw_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpavgw_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PSIGNB - Negate/Zero/Keep the destination packed byte integers based on the sign of the corresponding source operand. + * [V]PSIGNW - Negate/Zero/Keep the destination packed word integers based on the sign of the corresponding source operand. + * [V]PSIGND - Negate/Zero/Keep the destination packed doubleword integers based on the sign of the corresponding source operand. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psignb_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psignb_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psignb_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psignb_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_psignb_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_psignb_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsignb_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsignb_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpsignb_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpsignb_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsignb_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsignb_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpsignb_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpsignb_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psignw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psignw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psignw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psignw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_psignw_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_psignw_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsignw_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsignw_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpsignw_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpsignw_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsignw_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsignw_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpsignw_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpsignw_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psignd_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psignd_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psignd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psignd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_psignd_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_psignd_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsignd_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsignd_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpsignd_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpsignd_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsignd_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsignd_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpsignd_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpsignd_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_psignb_psignw_psignd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesB[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x4f4e4d4c4b4a4948, 0x5f5e5d5c5b5a5958, 0x6f6e6d6c6b6a6968, 0x7f7e7d7c7b7a7978) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x1edd23ac099d326c, 0xf9a48e14407256cd, 0x7800e9a5bf999e3d, 0xbdd333a0dd846703) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesW[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x4e4e4c4c4a4a4848, 0x5e5e5c5c5a5a5858, 0x6e6e6c6c6a6a6868, 0x7e7e7c7c7a7a7878) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x1edd225409633294, 0xf95c8eec40725633, 0x7800e95bbf999d3d, 0xbc2d3260dc7c6603) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesD[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x4e4d4c4c4a494848, 0x5e5d5c5c5a595858, 0x6e6d6c6c6a696868, 0x7e7d7c7c7a797878) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x77ff16a5bf9962c3, 0xbc2c3260dc7b6603) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_psignb_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psignb_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psignb_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psignb_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsignb_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsignb_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsignb_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsignb_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_psignw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psignw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psignw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psignw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsignw_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsignw_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsignw_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsignw_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_psignd_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psignd_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psignd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psignd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsignd_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsignd_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsignd_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsignd_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_psignb_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psignb_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psignb_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psignb_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsignb_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsignb_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsignb_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsignb_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_psignw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psignw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psignw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psignw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsignw_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsignw_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsignw_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsignw_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_psignd_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psignd_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psignd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psignd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsignd_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsignd_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsignd_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsignd_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_psignb_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psignb_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psignb_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psignb_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psignb_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_psignb_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsignb_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsignb_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsignb_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsignb_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsignb_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsignb_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsignb_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpsignb_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_psignw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psignw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psignw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psignw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psignw_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_psignw_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsignw_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsignw_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsignw_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsignw_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsignw_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsignw_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsignw_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpsignw_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_psignd_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psignd_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psignd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psignd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psignd_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_psignd_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsignd_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsignd_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsignd_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsignd_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsignd_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsignd_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsignd_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpsignd_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PHADDW - Horizontally add word sized signed integers. + * [V]PHADDD - Horizontally add doubleword sized signed integers. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phaddw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phaddw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phaddw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phaddw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_phaddw_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_phaddw_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphaddw_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphaddw_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vphaddw_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vphaddw_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphaddw_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphaddw_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vphaddw_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vphaddw_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phaddd_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phaddd_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phaddd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phaddd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_phaddd_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_phaddd_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphaddd_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphaddd_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vphaddd_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vphaddd_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphaddd_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphaddd_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vphaddd_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vphaddd_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_phaddw_phaddd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues64W[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0x85868d8e05060d0e) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 5, 6, 7, 0x7ccf29c41173bd81) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues128W[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 9, 10, 0xa5a6adae85868d8e, 0x25262d2e05060d0e) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 13, 14, 0xe3c9f1ee7ccf29c4, 0x715b225c1173bd81) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues256W[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xe5e6edeec5c6cdce, 0x65666d6e45464d4e, 0xa5a6adae85868d8e, 0x25262d2e05060d0e) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x3d33e0b156bca651, 0xfc893bf7884896a5, 0xe3c9f1ee7ccf29c4, 0x715b225c1173bd81) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues64D[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0x87898b8c07090b0c) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 5, 6, 7, 0x2f66772e6758679d) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues128D[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 9, 10, 0xa7a9abac87898b8c, 0x27292b2c07090b0c) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 13, 14, 0x0a6dcb4a2f66772e, 0x479a4c1e6758679d) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues256D[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xe7e9ebecc7c9cbcc, 0x67696b6c47494b4c, 0xa7a9abac87898b8c, 0x27292b2c07090b0c) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0xb9e663ffa55f57ae, 0x2841104039cee51f, 0x0a6dcb4a2f66772e, 0x479a4c1e6758679d) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_phaddw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phaddw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phaddw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phaddw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddw_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddw_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddw_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphaddw_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + + { bs3CpuInstr3_phaddd_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64D), s_aValues64D }, + { bs3CpuInstr3_phaddd_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64D), s_aValues64D }, + { bs3CpuInstr3_phaddd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_phaddd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphaddd_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphaddd_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphaddd_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256D), s_aValues256D }, + { bs3CpuInstr3_vphaddd_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256D), s_aValues256D }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_phaddw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phaddw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phaddw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phaddw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddw_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddw_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddw_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphaddw_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + + { bs3CpuInstr3_phaddd_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64D), s_aValues64D }, + { bs3CpuInstr3_phaddd_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64D), s_aValues64D }, + { bs3CpuInstr3_phaddd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_phaddd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphaddd_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphaddd_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphaddd_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256D), s_aValues256D }, + { bs3CpuInstr3_vphaddd_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256D), s_aValues256D }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_phaddw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phaddw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phaddw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phaddw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phaddw_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phaddw_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddw_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddw_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddw_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddw_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddw_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphaddw_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphaddw_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphaddw_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + + { bs3CpuInstr3_phaddd_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64D), s_aValues64D }, + { bs3CpuInstr3_phaddd_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64D), s_aValues64D }, + { bs3CpuInstr3_phaddd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_phaddd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_phaddd_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_phaddd_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphaddd_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphaddd_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphaddd_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphaddd_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphaddd_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256D), s_aValues256D }, + { bs3CpuInstr3_vphaddd_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256D), s_aValues256D }, + { bs3CpuInstr3_vphaddd_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValues256D), s_aValues256D }, + { bs3CpuInstr3_vphaddd_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValues256D), s_aValues256D }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PHSUBW - Horizontally subtract word sized signed integers. + * [V]PHSUBD - Horizontally subtract doubleword sized signed integers. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phsubw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phsubw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phsubw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phsubw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_phsubw_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_phsubw_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphsubw_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphsubw_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vphsubw_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vphsubw_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphsubw_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphsubw_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vphsubw_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vphsubw_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phsubd_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phsubd_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phsubd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phsubd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_phsubd_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_phsubd_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphsubd_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphsubd_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vphsubd_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vphsubd_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphsubd_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphsubd_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vphsubd_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vphsubd_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_phsubw_phsubd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues64W[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0x0202020202020202) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 5, 6, 7, 0x441703b289cd7679) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues128W[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 9, 10, 0x0202020202020202, 0x0202020202020202) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 13, 14, 0x7b874556441703b2, 0x615ba32a89cd7679) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues256W[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x0202020202020202, 0x0202020202020202, 0x0202020202020202, 0x0202020202020202) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0xa32106f9d8d4d97b, 0xbecf2931959015c1, 0x7b874556441703b2, 0x615ba32a89cd7679) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues64D[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0x0404040404040404) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 5, 6, 7, 0xf6acb648dfb0cc5d) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues128D[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 9, 10, 0x0404040404040404, 0x0404040404040404) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 13, 14, 0xa22b6bfaf6acb648, 0x37987968dfb0cc5d) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues256D[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x0404040404040404, 0x0404040404040404, 0x0404040404040404, 0x0404040404040404) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x1fd283ab2777281e, 0xea8554e84715c747, 0xa22b6bfaf6acb648, 0x37987968dfb0cc5d) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_phsubw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phsubw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phsubw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phsubw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubw_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubw_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubw_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphsubw_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + + { bs3CpuInstr3_phsubd_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64D), s_aValues64D }, + { bs3CpuInstr3_phsubd_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64D), s_aValues64D }, + { bs3CpuInstr3_phsubd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_phsubd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphsubd_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphsubd_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphsubd_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256D), s_aValues256D }, + { bs3CpuInstr3_vphsubd_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256D), s_aValues256D }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_phsubw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phsubw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phsubw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phsubw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubw_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubw_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubw_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphsubw_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + + { bs3CpuInstr3_phsubd_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64D), s_aValues64D }, + { bs3CpuInstr3_phsubd_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64D), s_aValues64D }, + { bs3CpuInstr3_phsubd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_phsubd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphsubd_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphsubd_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphsubd_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256D), s_aValues256D }, + { bs3CpuInstr3_vphsubd_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256D), s_aValues256D }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_phsubw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phsubw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phsubw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phsubw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phsubw_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phsubw_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubw_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubw_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubw_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubw_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubw_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphsubw_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphsubw_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphsubw_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + + { bs3CpuInstr3_phsubd_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64D), s_aValues64D }, + { bs3CpuInstr3_phsubd_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64D), s_aValues64D }, + { bs3CpuInstr3_phsubd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_phsubd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_phsubd_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_phsubd_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphsubd_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphsubd_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphsubd_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphsubd_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValues128D), s_aValues128D }, + { bs3CpuInstr3_vphsubd_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256D), s_aValues256D }, + { bs3CpuInstr3_vphsubd_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256D), s_aValues256D }, + { bs3CpuInstr3_vphsubd_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValues256D), s_aValues256D }, + { bs3CpuInstr3_vphsubd_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValues256D), s_aValues256D }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PHADDSW - Horizontally add and saturate word sized signed integers. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phaddsw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phaddsw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phaddsw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phaddsw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_phaddsw_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_phaddsw_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphaddsw_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphaddsw_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vphaddsw_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vphaddsw_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphaddsw_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphaddsw_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vphaddsw_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vphaddsw_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_phaddsw(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues64W[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0x85868d8e80008000) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 5, 6, 7, 0x800080001173bd81) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues128W[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 9, 10, 0xa5a6adae85868d8e, 0x8000800080008000) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 13, 14, 0xe3c9f1ee80008000, 0x8000225c1173bd81) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues256W[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xe5e6edeec5c6cdce, 0x8000800080008000, 0xa5a6adae85868d8e, 0x8000800080008000) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x3d337fff56bc7fff, 0xfc893bf788487fff, 0xe3c9f1ee80008000, 0x8000225c1173bd81) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_phaddsw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phaddsw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phaddsw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phaddsw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddsw_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddsw_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddsw_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphaddsw_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_phaddsw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phaddsw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phaddsw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phaddsw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddsw_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddsw_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddsw_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphaddsw_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_phaddsw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phaddsw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phaddsw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phaddsw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phaddsw_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phaddsw_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddsw_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddsw_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddsw_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddsw_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphaddsw_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphaddsw_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphaddsw_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphaddsw_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PHSUBSW - Horizontally subtract and saturate word sized signed integers. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phsubsw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phsubsw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phsubsw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phsubsw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_phsubsw_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_phsubsw_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphsubsw_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphsubsw_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vphsubsw_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vphsubsw_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphsubsw_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphsubsw_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vphsubsw_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vphsubsw_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_phsubsw(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues64W[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0x0202020202020202) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 5, 6, 7, 0x441703b289cd8000) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues128W[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 9, 10, 0x0202020202020202, 0x0202020202020202) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 13, 14, 0x7b878000441703b2, 0x615b7fff89cd8000) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues256W[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x0202020202020202, 0x0202020202020202, 0x0202020202020202, 0x0202020202020202) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0xa32106f9d8d4d97b, 0xbecf2931959015c1, 0x7b878000441703b2, 0x615b7fff89cd8000) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_phsubsw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phsubsw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phsubsw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phsubsw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubsw_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubsw_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubsw_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphsubsw_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_phsubsw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phsubsw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phsubsw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phsubsw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubsw_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubsw_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubsw_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphsubsw_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_phsubsw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phsubsw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_phsubsw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phsubsw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phsubsw_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_phsubsw_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubsw_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubsw_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubsw_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubsw_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vphsubsw_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphsubsw_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphsubsw_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vphsubsw_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PMADDUBSW - Horizontally subtract and saturate word sized signed integers. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaddubsw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaddubsw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaddubsw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmaddubsw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmaddubsw_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmaddubsw_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaddubsw_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaddubsw_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmaddubsw_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmaddubsw_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaddubsw_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmaddubsw_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmaddubsw_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmaddubsw_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pmaddubsw(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues64W[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 1, 2, 3, 0xc0c5c1d9c2fdc431) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 5, 6, 7, 0x31a82e40f5bd8000) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues128W[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 9, 10, 0xcb25ccb9ce5dd011, 0xc0c5c1d9c2fdc431) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 13, 14, 0xd7a00b7f6d9691bc, 0x31a82e40f5bd8000) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues256W[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xebe5ee79f11df3d1, 0xd985db99ddbddff1, 0xcb25ccb9ce5dd011, 0xc0c5c1d9c2fdc431) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x10cb0e68f5e0fd9a, 0x37fed92249260ffc, 0xd7a00b7f6d9691bc, 0x31a82e40f5bd8000) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pmaddubsw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_pmaddubsw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_pmaddubsw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_pmaddubsw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vpmaddubsw_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vpmaddubsw_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vpmaddubsw_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vpmaddubsw_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pmaddubsw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_pmaddubsw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_pmaddubsw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_pmaddubsw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vpmaddubsw_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vpmaddubsw_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vpmaddubsw_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vpmaddubsw_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pmaddubsw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_pmaddubsw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64W), s_aValues64W }, + { bs3CpuInstr3_pmaddubsw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_pmaddubsw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_pmaddubsw_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_pmaddubsw_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vpmaddubsw_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vpmaddubsw_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vpmaddubsw_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vpmaddubsw_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValues128W), s_aValues128W }, + { bs3CpuInstr3_vpmaddubsw_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vpmaddubsw_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vpmaddubsw_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + { bs3CpuInstr3_vpmaddubsw_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValues256W), s_aValues256W }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PMULHRSW - Vertically multiply, round and scale word sized signed integers and extract the high 16-bits. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmulhrsw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmulhrsw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmulhrsw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmulhrsw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmulhrsw_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmulhrsw_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmulhrsw_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmulhrsw_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmulhrsw_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmulhrsw_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmulhrsw_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmulhrsw_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmulhrsw_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmulhrsw_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pmulhrsw(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x0899072e05d40489, 0x16341448126d10a1, 0x27d7256a230e20c1, 0x3d823a9437b734e9) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x1293043f07fc2dc5, 0xfcbceafe33912b08, 0x4721f792d495b28f, 0xcb340c6be1c453e5) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pmulhrsw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmulhrsw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmulhrsw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmulhrsw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmulhrsw_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmulhrsw_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmulhrsw_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmulhrsw_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pmulhrsw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmulhrsw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmulhrsw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmulhrsw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmulhrsw_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmulhrsw_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmulhrsw_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmulhrsw_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pmulhrsw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmulhrsw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmulhrsw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmulhrsw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmulhrsw_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmulhrsw_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmulhrsw_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmulhrsw_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmulhrsw_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmulhrsw_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmulhrsw_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmulhrsw_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmulhrsw_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmulhrsw_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PSADBW - Compute sum of absolute differences of packed unsigned byte integers. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psadbw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psadbw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psadbw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_psadbw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_psadbw_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_psadbw_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsadbw_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsadbw_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpsadbw_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpsadbw_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsadbw_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpsadbw_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpsadbw_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpsadbw_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_psadbw(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x0000000000000200, 0x0000000000000200, 0x0000000000000200, 0x0000000000000200) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x00000000000002f6, 0x00000000000002e5, 0x0000000000000264, 0x0000000000000240) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_psadbw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_psadbw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_psadbw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_psadbw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpsadbw_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpsadbw_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpsadbw_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpsadbw_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_psadbw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_psadbw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_psadbw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_psadbw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpsadbw_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpsadbw_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpsadbw_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpsadbw_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_psadbw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSE, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_psadbw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSE, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_psadbw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_psadbw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_psadbw_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE2, 8, 8, 9, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_psadbw_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 8, 8, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpsadbw_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpsadbw_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpsadbw_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpsadbw_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpsadbw_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpsadbw_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpsadbw_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpsadbw_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PMULDQ - Multiply packed signed double word integers. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmuldq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmuldq_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmuldq_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmuldq_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmuldq_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmuldq_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmuldq_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmuldq_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmuldq_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmuldq_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmuldq_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmuldq_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pmuldq(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x02e97bbaf7148240, 0x0935ee3369408840, 0x118666b3e1709040, 0x1bdae53c5fa49a40) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x03fdeed546719124, 0x19c88e386340fed2, 0xea4a418ff1c09066, 0xf0e1df0254fbb9cf) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pmuldq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmuldq_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuldq_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuldq_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuldq_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuldq_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pmuldq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmuldq_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuldq_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuldq_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuldq_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuldq_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pmuldq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmuldq_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmuldq_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE4_1, 8, 8, 9, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmuldq_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 8, 8, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuldq_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuldq_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuldq_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuldq_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuldq_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuldq_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuldq_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuldq_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PMULUDQ - Multiply packed unsigned double word integers. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmuludq_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmuludq_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmuludq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmuludq_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmuludq_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmuludq_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmuludq_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmuludq_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmuludq_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmuludq_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmuludq_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmuludq_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmuludq_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmuludq_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pmuludq(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xae972b6af7148240, 0x94c37dc369408840, 0x7cf3d623e1709040, 0x6728348c5fa49a40) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x03fdeed546719124, 0x19c88e386340fed2, 0x4096dd31f1c09066, 0x146678ff54fbb9cf) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pmuludq_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmuludq_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmuludq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmuludq_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuludq_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuludq_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuludq_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuludq_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pmuludq_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmuludq_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmuludq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmuludq_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuludq_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuludq_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuludq_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuludq_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pmuludq_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmuludq_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmuludq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmuludq_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmuludq_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE2, 8, 8, 9, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmuludq_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 8, 8, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuludq_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuludq_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuludq_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuludq_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuludq_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuludq_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuludq_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmuludq_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PUNPCKLPS - Unpack and interleave low packed single precision FP values. + * [V]PUNPCKLPD - Unpack and interleave low packed double precision FP values. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_unpcklps_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_unpcklps_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_unpcklps_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_unpcklps_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vunpcklps_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vunpcklps_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vunpcklps_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vunpcklps_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vunpcklps_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vunpcklps_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vunpcklps_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vunpcklps_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_unpcklpd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_unpcklpd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_unpcklpd_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_unpcklpd_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vunpcklpd_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vunpcklpd_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vunpcklpd_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vunpcklpd_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vunpcklpd_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vunpcklpd_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vunpcklpd_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vunpcklpd_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_punpcklps_punpcklpd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesS[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xe1e2e3e4a1a2a3a4, 0xe5e6e7e8a5a6a7a8, 0xc1c2c3c481828384, 0xc5c6c7c885868788) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x3ef417c8f95c8eec, 0x666b3fe640725633, 0x9c5ce07343d3cda0, 0x930996bb238499fd) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesD[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xe1e2e3e4e5e6e7e8, 0xa1a2a3a4a5a6a7a8, 0xc1c2c3c4c5c6c7c8, 0x8182838485868788) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x3ef417c8666b3fe6, 0xf95c8eec40725633, 0x9c5ce073930996bb, 0x43d3cda0238499fd) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_unpcklps_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_unpcklps_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpcklps_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpcklps_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpcklps_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpcklps_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + + { bs3CpuInstr3_unpcklpd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_unpcklpd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpcklpd_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpcklpd_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpcklpd_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpcklpd_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_unpcklps_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_unpcklps_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpcklps_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpcklps_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpcklps_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpcklps_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + + { bs3CpuInstr3_unpcklpd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_unpcklpd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpcklpd_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpcklpd_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpcklpd_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpcklpd_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_unpcklps_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_unpcklps_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_unpcklps_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE, 8, 8, 9, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_unpcklps_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE, 8, 8, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpcklps_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpcklps_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpcklps_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpcklps_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpcklps_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpcklps_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpcklps_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpcklps_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + + { bs3CpuInstr3_unpcklpd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_unpcklpd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_unpcklpd_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE, 8, 8, 9, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_unpcklpd_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE, 8, 8, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpcklpd_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpcklpd_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpcklpd_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpcklpd_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpcklpd_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpcklpd_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpcklpd_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpcklpd_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PUNPCKHPS - Unpack and interleave low packed single precision FP values. + * [V]PUNPCKHPD - Unpack and interleave low packed double precision FP values. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_unpckhps_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_unpckhps_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_unpckhps_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_unpckhps_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vunpckhps_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vunpckhps_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vunpckhps_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vunpckhps_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vunpckhps_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vunpckhps_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vunpckhps_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vunpckhps_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_unpckhpd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_unpckhpd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_unpckhpd_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_unpckhpd_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vunpckhpd_XMM1_XMM2_XMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vunpckhpd_XMM1_XMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vunpckhpd_XMM8_XMM9_XMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vunpckhpd_XMM8_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vunpckhpd_YMM1_YMM2_YMM3_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vunpckhpd_YMM1_YMM2_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vunpckhpd_YMM8_YMM9_YMM10_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vunpckhpd_YMM8_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_punpckhps_punpckhpd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesS[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2f3f4b1b2b3b4, 0xf5f6f7f8b5b6b7b8, 0xd1d2d3d491929394, 0xd5d6d7d895969798) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x4d09f02a1eddddac, 0x6cdc73d509633294, 0xb4212fa88800e95b, 0x564c9ba2bf9962c3) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesD[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xb1b2b3b4b5b6b7b8, 0xd1d2d3d4d5d6d7d8, 0x9192939495969798) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x1eddddac09633294, 0xb4212fa8564c9ba2, 0x8800e95bbf9962c3) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_unpckhps_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_unpckhps_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpckhps_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpckhps_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpckhps_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpckhps_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + + { bs3CpuInstr3_unpckhpd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_unpckhpd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpckhpd_XMM1_XMM2_XMM3_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpckhpd_XMM1_XMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpckhpd_YMM1_YMM2_YMM3_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpckhpd_YMM1_YMM2_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_unpckhps_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_unpckhps_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpckhps_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpckhps_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpckhps_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpckhps_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + + { bs3CpuInstr3_unpckhpd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_unpckhpd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpckhpd_XMM1_XMM2_XMM3_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpckhpd_XMM1_XMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpckhpd_YMM1_YMM2_YMM3_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpckhpd_YMM1_YMM2_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_unpckhps_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_unpckhps_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_unpckhps_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE, 8, 8, 9, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_unpckhps_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE, 8, 8, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpckhps_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpckhps_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpckhps_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpckhps_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpckhps_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpckhps_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpckhps_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + { bs3CpuInstr3_vunpckhps_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesS), s_aValuesS }, + + { bs3CpuInstr3_unpckhpd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE, 1, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_unpckhpd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE, 1, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_unpckhpd_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE, 8, 8, 9, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_unpckhpd_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE, 8, 8, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpckhpd_XMM1_XMM2_XMM3_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpckhpd_XMM1_XMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpckhpd_XMM8_XMM9_XMM10_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpckhpd_XMM8_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpckhpd_YMM1_YMM2_YMM3_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpckhpd_YMM1_YMM2_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpckhpd_YMM8_YMM9_YMM10_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vunpckhpd_YMM8_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]SHUFPS - Shuffle two pairs of single precision floating point values. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_shufps_XMM1_XMM2_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_shufps_XMM1_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_shufps_XMM1_XMM2_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_shufps_XMM1_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_shufps_XMM8_XMM9_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_shufps_XMM8_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_shufps_XMM8_XMM9_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_shufps_XMM8_FSxBX_000h_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vshufps_XMM1_XMM2_XMM3_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vshufps_XMM1_XMM2_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vshufps_XMM1_XMM2_XMM3_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vshufps_XMM1_XMM2_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_vshufps_XMM8_XMM9_XMM10_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vshufps_XMM8_XMM9_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vshufps_XMM8_XMM9_XMM10_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vshufps_XMM8_XMM9_FSxBX_000h_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vshufps_YMM1_YMM2_YMM3_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vshufps_YMM1_YMM2_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vshufps_YMM1_YMM2_YMM3_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vshufps_YMM1_YMM2_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_vshufps_YMM8_YMM9_YMM10_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vshufps_YMM8_YMM9_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vshufps_YMM8_YMM9_YMM10_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vshufps_YMM8_YMM9_FSxBX_000h_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_shufps(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesFF[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2f3f4f1f2f3f4, 0xb1b2b3b4b1b2b3b4, 0xd1d2d3d4d1d2d3d4, 0x9192939491929394) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x4d09f02a4d09f02a, 0x1eddddac1eddddac, 0xb4212fa8b4212fa8, 0x8800e95b8800e95b) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues00[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xe5e6e7e8e5e6e7e8, 0xa5a6a7a8a5a6a7a8, 0xc5c6c7c8c5c6c7c8, 0x8586878885868788) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x666b3fe6666b3fe6, 0x4072563340725633, 0x930996bb930996bb, 0x238499fd238499fd) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_shufps_XMM1_XMM2_0FFh_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_shufps_XMM1_FSxBX_0FFh_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_shufps_XMM1_XMM2_000h_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_shufps_XMM1_FSxBX_000h_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vshufps_XMM1_XMM2_XMM3_0FFh_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufps_XMM1_XMM2_FSxBX_0FFh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufps_YMM1_YMM2_YMM3_0FFh_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufps_YMM1_YMM2_FSxBX_0FFh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufps_XMM1_XMM2_XMM3_000h_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufps_XMM1_XMM2_FSxBX_000h_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufps_YMM1_YMM2_YMM3_000h_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufps_YMM1_YMM2_FSxBX_000h_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_shufps_XMM1_XMM2_0FFh_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_shufps_XMM1_FSxBX_0FFh_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_shufps_XMM1_XMM2_000h_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_shufps_XMM1_FSxBX_000h_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vshufps_XMM1_XMM2_XMM3_0FFh_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufps_XMM1_XMM2_FSxBX_0FFh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufps_YMM1_YMM2_YMM3_0FFh_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufps_YMM1_YMM2_FSxBX_0FFh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufps_XMM1_XMM2_XMM3_000h_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufps_XMM1_XMM2_FSxBX_000h_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufps_YMM1_YMM2_YMM3_000h_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufps_YMM1_YMM2_FSxBX_000h_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_shufps_XMM1_XMM2_0FFh_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_shufps_XMM1_FSxBX_0FFh_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_shufps_XMM8_XMM9_0FFh_icebp_c64, 255, RM_REG, T_SSE2, 8, 8, 9, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_shufps_XMM8_FSxBX_0FFh_icebp_c64, 255, RM_MEM, T_SSE2, 8, 8, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_shufps_XMM1_XMM2_000h_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_shufps_XMM1_FSxBX_000h_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_shufps_XMM8_XMM9_000h_icebp_c64, 255, RM_REG, T_SSE2, 8, 8, 9, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_shufps_XMM8_FSxBX_000h_icebp_c64, 255, RM_MEM, T_SSE2, 8, 8, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vshufps_XMM1_XMM2_XMM3_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufps_XMM1_XMM2_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufps_XMM8_XMM9_XMM10_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufps_XMM8_XMM9_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufps_YMM1_YMM2_YMM3_0FFh_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufps_YMM1_YMM2_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufps_YMM8_YMM9_YMM10_0FFh_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufps_YMM8_YMM9_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufps_XMM1_XMM2_XMM3_000h_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufps_XMM1_XMM2_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufps_XMM8_XMM9_XMM10_000h_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufps_XMM8_XMM9_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufps_YMM1_YMM2_YMM3_000h_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufps_YMM1_YMM2_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufps_YMM8_YMM9_YMM10_000h_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufps_YMM8_YMM9_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]SHUFPD - Shuffle two pairs of double precision floating point values. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_shufpd_XMM1_XMM2_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_shufpd_XMM1_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_shufpd_XMM1_XMM2_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_shufpd_XMM1_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_shufpd_XMM8_XMM9_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_shufpd_XMM8_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_shufpd_XMM8_XMM9_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_shufpd_XMM8_FSxBX_000h_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vshufpd_XMM1_XMM2_XMM3_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vshufpd_XMM1_XMM2_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vshufpd_XMM1_XMM2_XMM3_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vshufpd_XMM1_XMM2_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_vshufpd_XMM8_XMM9_XMM10_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vshufpd_XMM8_XMM9_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vshufpd_XMM8_XMM9_XMM10_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vshufpd_XMM8_XMM9_FSxBX_000h_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vshufpd_YMM1_YMM2_YMM3_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vshufpd_YMM1_YMM2_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vshufpd_YMM1_YMM2_YMM3_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vshufpd_YMM1_YMM2_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_vshufpd_YMM8_YMM9_YMM10_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vshufpd_YMM8_YMM9_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vshufpd_YMM8_YMM9_YMM10_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vshufpd_YMM8_YMM9_FSxBX_000h_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_shufpd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesFF[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xb1b2b3b4b5b6b7b8, 0xd1d2d3d4d5d6d7d8, 0x9192939495969798) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x1eddddac09633294, 0xb4212fa8564c9ba2, 0x8800e95bbf9962c3) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues00[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xe1e2e3e4e5e6e7e8, 0xa1a2a3a4a5a6a7a8, 0xc1c2c3c4c5c6c7c8, 0x8182838485868788) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x3ef417c8666b3fe6, 0xf95c8eec40725633, 0x9c5ce073930996bb, 0x43d3cda0238499fd) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_shufpd_XMM1_XMM2_0FFh_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_shufpd_XMM1_FSxBX_0FFh_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_shufpd_XMM1_XMM2_000h_icebp_c16, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_shufpd_XMM1_FSxBX_000h_icebp_c16, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vshufpd_XMM1_XMM2_XMM3_0FFh_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufpd_XMM1_XMM2_FSxBX_0FFh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufpd_YMM1_YMM2_YMM3_0FFh_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufpd_YMM1_YMM2_FSxBX_0FFh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufpd_XMM1_XMM2_XMM3_000h_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufpd_XMM1_XMM2_FSxBX_000h_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufpd_YMM1_YMM2_YMM3_000h_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufpd_YMM1_YMM2_FSxBX_000h_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_shufpd_XMM1_XMM2_0FFh_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_shufpd_XMM1_FSxBX_0FFh_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_shufpd_XMM1_XMM2_000h_icebp_c32, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_shufpd_XMM1_FSxBX_000h_icebp_c32, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vshufpd_XMM1_XMM2_XMM3_0FFh_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufpd_XMM1_XMM2_FSxBX_0FFh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufpd_YMM1_YMM2_YMM3_0FFh_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufpd_YMM1_YMM2_FSxBX_0FFh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufpd_XMM1_XMM2_XMM3_000h_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufpd_XMM1_XMM2_FSxBX_000h_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufpd_YMM1_YMM2_YMM3_000h_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufpd_YMM1_YMM2_FSxBX_000h_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_shufpd_XMM1_XMM2_0FFh_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_shufpd_XMM1_FSxBX_0FFh_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_shufpd_XMM8_XMM9_0FFh_icebp_c64, 255, RM_REG, T_SSE2, 8, 8, 9, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_shufpd_XMM8_FSxBX_0FFh_icebp_c64, 255, RM_MEM, T_SSE2, 8, 8, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_shufpd_XMM1_XMM2_000h_icebp_c64, 255, RM_REG, T_SSE2, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_shufpd_XMM1_FSxBX_000h_icebp_c64, 255, RM_MEM, T_SSE2, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_shufpd_XMM8_XMM9_000h_icebp_c64, 255, RM_REG, T_SSE2, 8, 8, 9, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_shufpd_XMM8_FSxBX_000h_icebp_c64, 255, RM_MEM, T_SSE2, 8, 8, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vshufpd_XMM1_XMM2_XMM3_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufpd_XMM1_XMM2_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufpd_XMM8_XMM9_XMM10_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufpd_XMM8_XMM9_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufpd_YMM1_YMM2_YMM3_0FFh_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufpd_YMM1_YMM2_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufpd_YMM8_YMM9_YMM10_0FFh_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufpd_YMM8_YMM9_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vshufpd_XMM1_XMM2_XMM3_000h_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufpd_XMM1_XMM2_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufpd_XMM8_XMM9_XMM10_000h_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufpd_XMM8_XMM9_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufpd_YMM1_YMM2_YMM3_000h_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufpd_YMM1_YMM2_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufpd_YMM8_YMM9_YMM10_000h_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vshufpd_YMM8_YMM9_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PALIGNR - Concatenate and align source operands to the right. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_palignr_MM1_MM2_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_palignr_MM1_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_palignr_MM1_MM2_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_palignr_MM1_FSxBX_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_palignr_MM1_MM2_003h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_palignr_MM1_FSxBX_003h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_palignr_MM1_MM2_009h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_palignr_MM1_FSxBX_009h_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_palignr_XMM1_XMM2_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_palignr_XMM1_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_palignr_XMM1_XMM2_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_palignr_XMM1_FSxBX_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_palignr_XMM1_XMM2_003h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_palignr_XMM1_FSxBX_003h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_palignr_XMM1_XMM2_013h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_palignr_XMM1_FSxBX_013h_icebp); +extern FNBS3FAR bs3CpuInstr3_palignr_XMM8_XMM9_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_palignr_XMM8_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_palignr_XMM8_XMM9_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_palignr_XMM8_FSxBX_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_palignr_XMM8_XMM9_003h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_palignr_XMM8_FSxBX_003h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_palignr_XMM8_XMM9_013h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_palignr_XMM8_FSxBX_013h_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpalignr_XMM1_XMM2_XMM3_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpalignr_XMM1_XMM2_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpalignr_XMM1_XMM2_XMM3_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpalignr_XMM1_XMM2_FSxBX_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpalignr_XMM1_XMM2_XMM3_003h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpalignr_XMM1_XMM2_FSxBX_003h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpalignr_XMM1_XMM2_XMM3_013h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpalignr_XMM1_XMM2_FSxBX_013h_icebp); +extern FNBS3FAR bs3CpuInstr3_vpalignr_XMM8_XMM9_XMM10_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpalignr_XMM8_XMM9_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpalignr_XMM8_XMM9_XMM10_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpalignr_XMM8_XMM9_FSxBX_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpalignr_XMM8_XMM9_XMM10_003h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpalignr_XMM8_XMM9_FSxBX_003h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpalignr_XMM8_XMM9_XMM10_013h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpalignr_XMM8_XMM9_FSxBX_013h_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpalignr_YMM1_YMM2_YMM3_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpalignr_YMM1_YMM2_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpalignr_YMM1_YMM2_YMM3_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpalignr_YMM1_YMM2_FSxBX_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpalignr_YMM1_YMM2_YMM3_003h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpalignr_YMM1_YMM2_FSxBX_003h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpalignr_YMM1_YMM2_YMM3_013h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpalignr_YMM1_YMM2_FSxBX_013h_icebp); +extern FNBS3FAR bs3CpuInstr3_vpalignr_YMM8_YMM9_YMM10_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpalignr_YMM8_YMM9_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpalignr_YMM8_YMM9_YMM10_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpalignr_YMM8_YMM9_FSxBX_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpalignr_YMM8_YMM9_YMM10_003h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpalignr_YMM8_YMM9_FSxBX_003h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpalignr_YMM8_YMM9_YMM10_013h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpalignr_YMM8_YMM9_FSxBX_013h_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_palignr(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesFF[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues00[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues64B_03[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 17, 18, 19, 0x868788c1c2c3c4c5) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 21, 22, 23, 0x8499fd9c5ce07393) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues64B_09[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 17, 18, 19, 0x0081828384858687) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 21, 22, 23, 0x0043d3cda0238499) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues128B_03[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 17, 18, 0x868788d1d2d3d4d5, 0xd6d7d8c1c2c3c4c5) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 21, 22, 0x8499fdb4212fa856, 0x4c9ba29c5ce07393) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues128B_13[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 17, 18, 0x0000009192939495, 0x9697988182838485) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 21, 22, 0x0000008800e95bbf, 0x9962c343d3cda023) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues256B_03[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xa6a7a8f1f2f3f4f5, 0xf6f7f8e1e2e3e4e5, 0x868788d1d2d3d4d5, 0xd6d7d8c1c2c3c4c5) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x7256334d09f02a6c, 0xdc73d53ef417c866, 0x8499fdb4212fa856, 0x4c9ba29c5ce07393) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues256B_13[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0x000000b1b2b3b4b5, 0xb6b7b8a1a2a3a4a5, 0x0000009192939495, 0x9697988182838485) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x0000001eddddac09, 0x633294f95c8eec40, 0x0000008800e95bbf, 0x9962c343d3cda023) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_palignr_MM1_MM2_0FFh_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_palignr_MM1_FSxBX_0FFh_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_palignr_MM1_MM2_000h_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_palignr_MM1_FSxBX_000h_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_palignr_MM1_MM2_003h_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64B_03), s_aValues64B_03 }, + { bs3CpuInstr3_palignr_MM1_FSxBX_003h_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64B_03), s_aValues64B_03 }, + { bs3CpuInstr3_palignr_MM1_MM2_009h_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64B_09), s_aValues64B_09 }, + { bs3CpuInstr3_palignr_MM1_FSxBX_009h_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64B_09), s_aValues64B_09 }, + + { bs3CpuInstr3_palignr_XMM1_XMM2_0FFh_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_palignr_XMM1_FSxBX_0FFh_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_palignr_XMM1_XMM2_000h_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_palignr_XMM1_FSxBX_000h_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_palignr_XMM1_XMM2_003h_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128B_03), s_aValues128B_03 }, + { bs3CpuInstr3_palignr_XMM1_FSxBX_003h_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128B_03), s_aValues128B_03 }, + { bs3CpuInstr3_palignr_XMM1_XMM2_013h_icebp_c16, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128B_13), s_aValues128B_13 }, + { bs3CpuInstr3_palignr_XMM1_FSxBX_013h_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128B_13), s_aValues128B_13 }, + + { bs3CpuInstr3_vpalignr_XMM1_XMM2_XMM3_0FFh_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpalignr_XMM1_XMM2_FSxBX_0FFh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_YMM3_0FFh_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_FSxBX_0FFh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_vpalignr_XMM1_XMM2_XMM3_000h_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpalignr_XMM1_XMM2_FSxBX_000h_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_YMM3_000h_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_FSxBX_000h_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vpalignr_XMM1_XMM2_XMM3_003h_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128B_03), s_aValues128B_03 }, + { bs3CpuInstr3_vpalignr_XMM1_XMM2_FSxBX_003h_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128B_03), s_aValues128B_03 }, + { bs3CpuInstr3_vpalignr_XMM1_XMM2_XMM3_013h_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128B_13), s_aValues128B_13 }, + { bs3CpuInstr3_vpalignr_XMM1_XMM2_FSxBX_013h_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128B_13), s_aValues128B_13 }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_YMM3_003h_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256B_03), s_aValues256B_03 }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_FSxBX_003h_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256B_03), s_aValues256B_03 }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_YMM3_013h_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256B_13), s_aValues256B_13 }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_FSxBX_013h_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256B_13), s_aValues256B_13 }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_palignr_MM1_MM2_0FFh_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_palignr_MM1_FSxBX_0FFh_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_palignr_MM1_MM2_000h_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_palignr_MM1_FSxBX_000h_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_palignr_MM1_MM2_003h_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64B_03), s_aValues64B_03 }, + { bs3CpuInstr3_palignr_MM1_FSxBX_003h_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64B_03), s_aValues64B_03 }, + { bs3CpuInstr3_palignr_MM1_MM2_009h_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64B_09), s_aValues64B_09 }, + { bs3CpuInstr3_palignr_MM1_FSxBX_009h_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64B_09), s_aValues64B_09 }, + + { bs3CpuInstr3_palignr_XMM1_XMM2_0FFh_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_palignr_XMM1_FSxBX_0FFh_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_palignr_XMM1_XMM2_000h_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_palignr_XMM1_FSxBX_000h_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_palignr_XMM1_XMM2_003h_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128B_03), s_aValues128B_03 }, + { bs3CpuInstr3_palignr_XMM1_FSxBX_003h_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128B_03), s_aValues128B_03 }, + { bs3CpuInstr3_palignr_XMM1_XMM2_013h_icebp_c32, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128B_13), s_aValues128B_13 }, + { bs3CpuInstr3_palignr_XMM1_FSxBX_013h_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128B_13), s_aValues128B_13 }, + + { bs3CpuInstr3_vpalignr_XMM1_XMM2_XMM3_0FFh_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpalignr_XMM1_XMM2_FSxBX_0FFh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_YMM3_0FFh_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_FSxBX_0FFh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_vpalignr_XMM1_XMM2_XMM3_000h_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpalignr_XMM1_XMM2_FSxBX_000h_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_YMM3_000h_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_FSxBX_000h_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vpalignr_XMM1_XMM2_XMM3_003h_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128B_03), s_aValues128B_03 }, + { bs3CpuInstr3_vpalignr_XMM1_XMM2_FSxBX_003h_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128B_03), s_aValues128B_03 }, + { bs3CpuInstr3_vpalignr_XMM1_XMM2_XMM3_013h_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128B_13), s_aValues128B_13 }, + { bs3CpuInstr3_vpalignr_XMM1_XMM2_FSxBX_013h_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128B_13), s_aValues128B_13 }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_YMM3_003h_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256B_03), s_aValues256B_03 }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_FSxBX_003h_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256B_03), s_aValues256B_03 }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_YMM3_013h_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256B_13), s_aValues256B_13 }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_FSxBX_013h_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256B_13), s_aValues256B_13 }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_palignr_MM1_MM2_0FFh_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_palignr_MM1_FSxBX_0FFh_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_palignr_MM1_MM2_000h_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_palignr_MM1_FSxBX_000h_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_palignr_MM1_MM2_003h_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64B_03), s_aValues64B_03 }, + { bs3CpuInstr3_palignr_MM1_FSxBX_003h_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64B_03), s_aValues64B_03 }, + { bs3CpuInstr3_palignr_MM1_MM2_009h_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues64B_09), s_aValues64B_09 }, + { bs3CpuInstr3_palignr_MM1_FSxBX_009h_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues64B_09), s_aValues64B_09 }, + + { bs3CpuInstr3_palignr_XMM1_XMM2_0FFh_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_palignr_XMM1_FSxBX_0FFh_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_palignr_XMM8_XMM9_0FFh_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_palignr_XMM8_FSxBX_0FFh_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_palignr_XMM1_XMM2_000h_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_palignr_XMM1_FSxBX_000h_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_palignr_XMM8_XMM9_000h_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_palignr_XMM8_FSxBX_000h_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_palignr_XMM1_XMM2_003h_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128B_03), s_aValues128B_03 }, + { bs3CpuInstr3_palignr_XMM1_FSxBX_003h_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128B_03), s_aValues128B_03 }, + { bs3CpuInstr3_palignr_XMM8_XMM9_003h_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValues128B_03), s_aValues128B_03 }, + { bs3CpuInstr3_palignr_XMM8_FSxBX_003h_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValues128B_03), s_aValues128B_03 }, + + { bs3CpuInstr3_palignr_XMM1_XMM2_013h_icebp_c64, 255, RM_REG, T_SSSE3, 1, 1, 2, RT_ELEMENTS(s_aValues128B_13), s_aValues128B_13 }, + { bs3CpuInstr3_palignr_XMM1_FSxBX_013h_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 1, 255, RT_ELEMENTS(s_aValues128B_13), s_aValues128B_13 }, + { bs3CpuInstr3_palignr_XMM8_XMM9_013h_icebp_c64, 255, RM_REG, T_SSSE3, 8, 8, 9, RT_ELEMENTS(s_aValues128B_13), s_aValues128B_13 }, + { bs3CpuInstr3_palignr_XMM8_FSxBX_013h_icebp_c64, 255, RM_MEM, T_SSSE3, 8, 8, 255, RT_ELEMENTS(s_aValues128B_13), s_aValues128B_13 }, + + { bs3CpuInstr3_vpalignr_XMM1_XMM2_XMM3_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpalignr_XMM1_XMM2_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpalignr_XMM8_XMM9_XMM10_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpalignr_XMM8_XMM9_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_YMM3_0FFh_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpalignr_YMM8_YMM9_YMM10_0FFh_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpalignr_YMM8_YMM9_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_vpalignr_XMM1_XMM2_XMM3_000h_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpalignr_XMM1_XMM2_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpalignr_XMM8_XMM9_XMM10_000h_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpalignr_XMM8_XMM9_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_YMM3_000h_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpalignr_YMM8_YMM9_YMM10_000h_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpalignr_YMM8_YMM9_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vpalignr_XMM1_XMM2_XMM3_003h_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128B_03), s_aValues128B_03 }, + { bs3CpuInstr3_vpalignr_XMM1_XMM2_FSxBX_003h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128B_03), s_aValues128B_03 }, + { bs3CpuInstr3_vpalignr_XMM8_XMM9_XMM10_003h_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValues128B_03), s_aValues128B_03 }, + { bs3CpuInstr3_vpalignr_XMM8_XMM9_FSxBX_003h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValues128B_03), s_aValues128B_03 }, + { bs3CpuInstr3_vpalignr_XMM1_XMM2_XMM3_013h_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues128B_13), s_aValues128B_13 }, + { bs3CpuInstr3_vpalignr_XMM1_XMM2_FSxBX_013h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues128B_13), s_aValues128B_13 }, + { bs3CpuInstr3_vpalignr_XMM8_XMM9_XMM10_013h_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValues128B_13), s_aValues128B_13 }, + { bs3CpuInstr3_vpalignr_XMM8_XMM9_FSxBX_013h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValues128B_13), s_aValues128B_13 }, + + { bs3CpuInstr3_vpalignr_YMM1_YMM2_YMM3_003h_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256B_03), s_aValues256B_03 }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_FSxBX_003h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256B_03), s_aValues256B_03 }, + { bs3CpuInstr3_vpalignr_YMM8_YMM9_YMM10_003h_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValues256B_03), s_aValues256B_03 }, + { bs3CpuInstr3_vpalignr_YMM8_YMM9_FSxBX_003h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValues256B_03), s_aValues256B_03 }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_YMM3_013h_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues256B_13), s_aValues256B_13 }, + { bs3CpuInstr3_vpalignr_YMM1_YMM2_FSxBX_013h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues256B_13), s_aValues256B_13 }, + { bs3CpuInstr3_vpalignr_YMM8_YMM9_YMM10_013h_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValues256B_13), s_aValues256B_13 }, + { bs3CpuInstr3_vpalignr_YMM8_YMM9_FSxBX_013h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValues256B_13), s_aValues256B_13 }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PBLENDW - Blend packed words based on an 8-bit immediate. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pblendw_XMM1_XMM2_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pblendw_XMM1_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pblendw_XMM1_XMM2_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pblendw_XMM1_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_pblendw_XMM8_XMM9_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pblendw_XMM8_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pblendw_XMM8_XMM9_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pblendw_XMM8_FSxBX_000h_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpblendw_XMM1_XMM2_XMM3_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpblendw_XMM1_XMM2_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpblendw_XMM1_XMM2_XMM3_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpblendw_XMM1_XMM2_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_vpblendw_XMM8_XMM9_XMM10_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpblendw_XMM8_XMM9_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpblendw_XMM8_XMM9_XMM10_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpblendw_XMM8_XMM9_FSxBX_000h_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpblendw_YMM1_YMM2_YMM3_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpblendw_YMM1_YMM2_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpblendw_YMM1_YMM2_YMM3_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpblendw_YMM1_YMM2_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_vpblendw_YMM8_YMM9_YMM10_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpblendw_YMM8_YMM9_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpblendw_YMM8_YMM9_YMM10_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpblendw_YMM8_YMM9_FSxBX_000h_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pblendw(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesFF[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues00[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pblendw_XMM1_XMM2_0FFh_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pblendw_XMM1_FSxBX_0FFh_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pblendw_XMM1_XMM2_000h_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pblendw_XMM1_FSxBX_000h_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vpblendw_XMM1_XMM2_XMM3_0FFh_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpblendw_XMM1_XMM2_FSxBX_0FFh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpblendw_YMM1_YMM2_YMM3_0FFh_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpblendw_YMM1_YMM2_FSxBX_0FFh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_vpblendw_XMM1_XMM2_XMM3_000h_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpblendw_XMM1_XMM2_FSxBX_000h_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpblendw_YMM1_YMM2_YMM3_000h_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpblendw_YMM1_YMM2_FSxBX_000h_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pblendw_XMM1_XMM2_0FFh_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pblendw_XMM1_FSxBX_0FFh_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pblendw_XMM1_XMM2_000h_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pblendw_XMM1_FSxBX_000h_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vpblendw_XMM1_XMM2_XMM3_0FFh_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpblendw_XMM1_XMM2_FSxBX_0FFh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpblendw_YMM1_YMM2_YMM3_0FFh_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpblendw_YMM1_YMM2_FSxBX_0FFh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_vpblendw_XMM1_XMM2_XMM3_000h_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpblendw_XMM1_XMM2_FSxBX_000h_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpblendw_YMM1_YMM2_YMM3_000h_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpblendw_YMM1_YMM2_FSxBX_000h_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pblendw_XMM1_XMM2_0FFh_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pblendw_XMM1_FSxBX_0FFh_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pblendw_XMM8_XMM9_0FFh_icebp_c64, 255, RM_REG, T_SSE4_1, 8, 8, 9, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pblendw_XMM8_FSxBX_0FFh_icebp_c64, 255, RM_MEM, T_SSE4_1, 8, 8, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_pblendw_XMM1_XMM2_000h_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pblendw_XMM1_FSxBX_000h_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pblendw_XMM8_XMM9_000h_icebp_c64, 255, RM_REG, T_SSE4_1, 8, 8, 9, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pblendw_XMM8_FSxBX_000h_icebp_c64, 255, RM_MEM, T_SSE4_1, 8, 8, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vpblendw_XMM1_XMM2_XMM3_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpblendw_XMM1_XMM2_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpblendw_XMM8_XMM9_XMM10_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpblendw_XMM8_XMM9_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpblendw_YMM1_YMM2_YMM3_0FFh_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpblendw_YMM1_YMM2_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpblendw_YMM8_YMM9_YMM10_0FFh_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpblendw_YMM8_YMM9_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_vpblendw_XMM1_XMM2_XMM3_000h_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpblendw_XMM1_XMM2_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpblendw_XMM8_XMM9_XMM10_000h_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpblendw_XMM8_XMM9_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpblendw_YMM1_YMM2_YMM3_000h_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpblendw_YMM1_YMM2_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpblendw_YMM8_YMM9_YMM10_000h_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpblendw_YMM8_YMM9_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]BLENDPS - Blend packed single precision floating point values based on an 8-bit immediate. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_blendps_XMM1_XMM2_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_blendps_XMM1_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_blendps_XMM1_XMM2_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_blendps_XMM1_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_blendps_XMM8_XMM9_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_blendps_XMM8_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_blendps_XMM8_XMM9_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_blendps_XMM8_FSxBX_000h_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendps_XMM1_XMM2_XMM3_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendps_XMM1_XMM2_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendps_XMM1_XMM2_XMM3_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendps_XMM1_XMM2_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_vblendps_XMM8_XMM9_XMM10_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vblendps_XMM8_XMM9_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vblendps_XMM8_XMM9_XMM10_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vblendps_XMM8_XMM9_FSxBX_000h_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendps_YMM1_YMM2_YMM3_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendps_YMM1_YMM2_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendps_YMM1_YMM2_YMM3_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendps_YMM1_YMM2_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_vblendps_YMM8_YMM9_YMM10_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vblendps_YMM8_YMM9_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vblendps_YMM8_YMM9_YMM10_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vblendps_YMM8_YMM9_FSxBX_000h_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_blendps(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesFF[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues00[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_blendps_XMM1_XMM2_0FFh_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_blendps_XMM1_FSxBX_0FFh_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_blendps_XMM1_XMM2_000h_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_blendps_XMM1_FSxBX_000h_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vblendps_XMM1_XMM2_XMM3_0FFh_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendps_XMM1_XMM2_FSxBX_0FFh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendps_YMM1_YMM2_YMM3_0FFh_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendps_YMM1_YMM2_FSxBX_0FFh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_vblendps_XMM1_XMM2_XMM3_000h_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendps_XMM1_XMM2_FSxBX_000h_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendps_YMM1_YMM2_YMM3_000h_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendps_YMM1_YMM2_FSxBX_000h_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_blendps_XMM1_XMM2_0FFh_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_blendps_XMM1_FSxBX_0FFh_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_blendps_XMM1_XMM2_000h_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_blendps_XMM1_FSxBX_000h_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vblendps_XMM1_XMM2_XMM3_0FFh_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendps_XMM1_XMM2_FSxBX_0FFh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendps_YMM1_YMM2_YMM3_0FFh_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendps_YMM1_YMM2_FSxBX_0FFh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_vblendps_XMM1_XMM2_XMM3_000h_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendps_XMM1_XMM2_FSxBX_000h_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendps_YMM1_YMM2_YMM3_000h_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendps_YMM1_YMM2_FSxBX_000h_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_blendps_XMM1_XMM2_0FFh_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_blendps_XMM1_FSxBX_0FFh_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_blendps_XMM8_XMM9_0FFh_icebp_c64, 255, RM_REG, T_SSE4_1, 8, 8, 9, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_blendps_XMM8_FSxBX_0FFh_icebp_c64, 255, RM_MEM, T_SSE4_1, 8, 8, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_blendps_XMM1_XMM2_000h_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_blendps_XMM1_FSxBX_000h_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_blendps_XMM8_XMM9_000h_icebp_c64, 255, RM_REG, T_SSE4_1, 8, 8, 9, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_blendps_XMM8_FSxBX_000h_icebp_c64, 255, RM_MEM, T_SSE4_1, 8, 8, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vblendps_XMM1_XMM2_XMM3_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendps_XMM1_XMM2_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendps_XMM8_XMM9_XMM10_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendps_XMM8_XMM9_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendps_YMM1_YMM2_YMM3_0FFh_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendps_YMM1_YMM2_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendps_YMM8_YMM9_YMM10_0FFh_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendps_YMM8_YMM9_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_vblendps_XMM1_XMM2_XMM3_000h_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendps_XMM1_XMM2_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendps_XMM8_XMM9_XMM10_000h_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendps_XMM8_XMM9_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendps_YMM1_YMM2_YMM3_000h_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendps_YMM1_YMM2_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendps_YMM8_YMM9_YMM10_000h_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendps_YMM8_YMM9_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]BLENDPD - Blend packed double precision floating point values based on an 8-bit immediate. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_blendpd_XMM1_XMM2_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_blendpd_XMM1_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_blendpd_XMM1_XMM2_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_blendpd_XMM1_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_blendpd_XMM8_XMM9_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_blendpd_XMM8_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_blendpd_XMM8_XMM9_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_blendpd_XMM8_FSxBX_000h_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendpd_XMM1_XMM2_XMM3_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendpd_XMM1_XMM2_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendpd_XMM1_XMM2_XMM3_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendpd_XMM1_XMM2_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_vblendpd_XMM8_XMM9_XMM10_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vblendpd_XMM8_XMM9_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vblendpd_XMM8_XMM9_XMM10_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vblendpd_XMM8_XMM9_FSxBX_000h_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendpd_YMM1_YMM2_YMM3_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendpd_YMM1_YMM2_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendpd_YMM1_YMM2_YMM3_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendpd_YMM1_YMM2_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_vblendpd_YMM8_YMM9_YMM10_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vblendpd_YMM8_YMM9_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vblendpd_YMM8_YMM9_YMM10_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vblendpd_YMM8_YMM9_FSxBX_000h_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_blendpd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesFF[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues00[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_blendpd_XMM1_XMM2_0FFh_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_blendpd_XMM1_FSxBX_0FFh_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_blendpd_XMM1_XMM2_000h_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_blendpd_XMM1_FSxBX_000h_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vblendpd_XMM1_XMM2_XMM3_0FFh_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendpd_XMM1_XMM2_FSxBX_0FFh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendpd_YMM1_YMM2_YMM3_0FFh_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendpd_YMM1_YMM2_FSxBX_0FFh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_vblendpd_XMM1_XMM2_XMM3_000h_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendpd_XMM1_XMM2_FSxBX_000h_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendpd_YMM1_YMM2_YMM3_000h_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendpd_YMM1_YMM2_FSxBX_000h_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_blendpd_XMM1_XMM2_0FFh_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_blendpd_XMM1_FSxBX_0FFh_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_blendpd_XMM1_XMM2_000h_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_blendpd_XMM1_FSxBX_000h_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vblendpd_XMM1_XMM2_XMM3_0FFh_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendpd_XMM1_XMM2_FSxBX_0FFh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendpd_YMM1_YMM2_YMM3_0FFh_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendpd_YMM1_YMM2_FSxBX_0FFh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_vblendpd_XMM1_XMM2_XMM3_000h_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendpd_XMM1_XMM2_FSxBX_000h_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendpd_YMM1_YMM2_YMM3_000h_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendpd_YMM1_YMM2_FSxBX_000h_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_blendpd_XMM1_XMM2_0FFh_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_blendpd_XMM1_FSxBX_0FFh_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_blendpd_XMM8_XMM9_0FFh_icebp_c64, 255, RM_REG, T_SSE4_1, 8, 8, 9, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_blendpd_XMM8_FSxBX_0FFh_icebp_c64, 255, RM_MEM, T_SSE4_1, 8, 8, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_blendpd_XMM1_XMM2_000h_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_blendpd_XMM1_FSxBX_000h_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_blendpd_XMM8_XMM9_000h_icebp_c64, 255, RM_REG, T_SSE4_1, 8, 8, 9, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_blendpd_XMM8_FSxBX_000h_icebp_c64, 255, RM_MEM, T_SSE4_1, 8, 8, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vblendpd_XMM1_XMM2_XMM3_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendpd_XMM1_XMM2_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendpd_XMM8_XMM9_XMM10_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendpd_XMM8_XMM9_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendpd_YMM1_YMM2_YMM3_0FFh_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendpd_YMM1_YMM2_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendpd_YMM8_YMM9_YMM10_0FFh_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vblendpd_YMM8_YMM9_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_vblendpd_XMM1_XMM2_XMM3_000h_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendpd_XMM1_XMM2_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendpd_XMM8_XMM9_XMM10_000h_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendpd_XMM8_XMM9_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendpd_YMM1_YMM2_YMM3_000h_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendpd_YMM1_YMM2_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendpd_YMM8_YMM9_YMM10_000h_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 9, 10, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vblendpd_YMM8_YMM9_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 8, 9, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]PCLMULQDQ - Carry-less multiplication of a quadword. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pclmulqdq_XMM1_XMM2_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pclmulqdq_XMM1_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pclmulqdq_XMM1_XMM2_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pclmulqdq_XMM1_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_pclmulqdq_XMM8_XMM9_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pclmulqdq_XMM8_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pclmulqdq_XMM8_XMM9_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pclmulqdq_XMM8_FSxBX_000h_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpclmulqdq_XMM1_XMM2_XMM3_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpclmulqdq_XMM1_XMM2_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpclmulqdq_XMM1_XMM2_XMM3_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpclmulqdq_XMM1_XMM2_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_vpclmulqdq_XMM8_XMM9_XMM10_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpclmulqdq_XMM8_XMM9_FSxBX_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpclmulqdq_XMM8_XMM9_XMM10_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpclmulqdq_XMM8_XMM9_FSxBX_000h_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pclmulqdq(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValuesFF[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 1, 2, 0x6541a5056544a512, 0x6753a7176756a740) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 5, 6, 0x5fb1fa02ce11d9e9, 0x547462ca50871166) }, + }; + static BS3CPUINSTR3_TEST1_VALUES_T const s_aValues00[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C( 9, 10, 0x6041a0056044a012, 0x6253a2176256a240) }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ RTUINT256_INIT_C( 13, 14, 0x26d2ea34453fe24f, 0x2592f499ed1f651f) }, + }; + + static BS3CPUINSTR3_TEST1_T const s_aTests16[] = + { + { bs3CpuInstr3_pclmulqdq_XMM1_XMM2_0FFh_icebp_c16, 255, RM_REG, T_PCLMUL, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pclmulqdq_XMM1_FSxBX_0FFh_icebp_c16, 255, RM_MEM, T_PCLMUL, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pclmulqdq_XMM1_XMM2_000h_icebp_c16, 255, RM_REG, T_PCLMUL, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pclmulqdq_XMM1_FSxBX_000h_icebp_c16, 255, RM_MEM, T_PCLMUL, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vpclmulqdq_XMM1_XMM2_XMM3_0FFh_icebp_c16, 255, RM_REG, T_AVX_PCLMUL, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpclmulqdq_XMM1_XMM2_FSxBX_0FFh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_PCLMUL, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_vpclmulqdq_XMM1_XMM2_XMM3_000h_icebp_c16, 255, RM_REG, T_AVX_PCLMUL, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpclmulqdq_XMM1_XMM2_FSxBX_000h_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_PCLMUL, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests32[] = + { + { bs3CpuInstr3_pclmulqdq_XMM1_XMM2_0FFh_icebp_c32, 255, RM_REG, T_PCLMUL, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pclmulqdq_XMM1_FSxBX_0FFh_icebp_c32, 255, RM_MEM, T_PCLMUL, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pclmulqdq_XMM1_XMM2_000h_icebp_c32, 255, RM_REG, T_PCLMUL, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pclmulqdq_XMM1_FSxBX_000h_icebp_c32, 255, RM_MEM, T_PCLMUL, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vpclmulqdq_XMM1_XMM2_XMM3_0FFh_icebp_c32, 255, RM_REG, T_AVX_PCLMUL, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpclmulqdq_XMM1_XMM2_FSxBX_0FFh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_PCLMUL, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_vpclmulqdq_XMM1_XMM2_XMM3_000h_icebp_c32, 255, RM_REG, T_AVX_PCLMUL, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpclmulqdq_XMM1_XMM2_FSxBX_000h_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_PCLMUL, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + }; + static BS3CPUINSTR3_TEST1_T const s_aTests64[] = + { + { bs3CpuInstr3_pclmulqdq_XMM1_XMM2_0FFh_icebp_c64, 255, RM_REG, T_PCLMUL, 1, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pclmulqdq_XMM1_FSxBX_0FFh_icebp_c64, 255, RM_MEM, T_PCLMUL, 1, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pclmulqdq_XMM8_XMM9_0FFh_icebp_c64, 255, RM_REG, T_PCLMUL, 8, 8, 9, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pclmulqdq_XMM8_FSxBX_0FFh_icebp_c64, 255, RM_MEM, T_PCLMUL, 8, 8, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_pclmulqdq_XMM1_XMM2_000h_icebp_c64, 255, RM_REG, T_PCLMUL, 1, 1, 2, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pclmulqdq_XMM1_FSxBX_000h_icebp_c64, 255, RM_MEM, T_PCLMUL, 1, 1, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pclmulqdq_XMM8_XMM9_000h_icebp_c64, 255, RM_REG, T_PCLMUL, 8, 8, 9, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pclmulqdq_XMM8_FSxBX_000h_icebp_c64, 255, RM_MEM, T_PCLMUL, 8, 8, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_vpclmulqdq_XMM1_XMM2_XMM3_0FFh_icebp_c64, 255, RM_REG, T_AVX_PCLMUL, 1, 2, 3, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpclmulqdq_XMM1_XMM2_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_PCLMUL, 1, 2, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpclmulqdq_XMM8_XMM9_XMM10_0FFh_icebp_c64, 255, RM_REG, T_AVX_PCLMUL, 8, 9, 10, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpclmulqdq_XMM8_XMM9_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_PCLMUL, 8, 9, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_vpclmulqdq_XMM1_XMM2_XMM3_000h_icebp_c64, 255, RM_REG, T_AVX_PCLMUL, 1, 2, 3, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpclmulqdq_XMM1_XMM2_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_PCLMUL, 1, 2, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpclmulqdq_XMM8_XMM9_XMM10_000h_icebp_c64, 255, RM_REG, T_AVX_PCLMUL, 8, 9, 10, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpclmulqdq_XMM8_XMM9_FSxBX_000h_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_PCLMUL, 8, 9, 255, RT_ELEMENTS(s_aValues00), s_aValues00 }, + }; + static BS3CPUINSTR3_TEST1_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST1_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType1(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * Test type #2 - GPR <- MM/XMM/YMM, no VVVV. + */ + +typedef struct BS3CPUINSTR3_TEST2_VALUES_T +{ + RTUINT256U uMedia; + uint64_t uGpr; +} BS3CPUINSTR3_TEST2_VALUES_T; + +typedef struct BS3CPUINSTR3_TEST2_T +{ + FPFNBS3FAR pfnWorker; + uint8_t bAvxMisalignXcpt; + uint8_t enmRm; + uint8_t enmType; + uint8_t cbGpr; + uint8_t cBitsGprValMask; + bool fInvalidEncoding; + bool fGprDst; + uint8_t iGprReg; + uint8_t iMediaReg; + uint8_t cValues; + BS3CPUINSTR3_TEST2_VALUES_T const BS3_FAR *paValues; +} BS3CPUINSTR3_TEST2_T; + +typedef struct BS3CPUINSTR3_TEST2_MODE_T +{ + BS3CPUINSTR3_TEST2_T const BS3_FAR *paTests; + unsigned cTests; +} BS3CPUINSTR3_TEST2_MODE_T; + +/** Initializer for a BS3CPUINSTR3_TEST2_MODE_T array (three entries). */ +#define BS3CPUINSTR3_TEST2_MODES_INIT(a_aTests16, a_aTests32, a_aTests64) \ + { { a_aTests16, RT_ELEMENTS(a_aTests16) }, { a_aTests32, RT_ELEMENTS(a_aTests32) }, { a_aTests64, RT_ELEMENTS(a_aTests64) } } + + +/** + * Test type #2 worker. + */ +static uint8_t bs3CpuInstr3_WorkerTestType2(uint8_t bMode, BS3CPUINSTR3_TEST2_T const BS3_FAR *paTests, unsigned cTests, + PCBS3CPUINSTR3_CONFIG_T paConfigs, unsigned cConfigs) +{ + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + const char BS3_FAR * const pszMode = Bs3GetModeName(bMode); + uint8_t BS3_FAR *pbBuf = g_pbBuf; + uint32_t cbBuf = g_cbBuf; + uint8_t bRing = BS3_MODE_IS_V86(bMode) ? 3 : 0; + PBS3EXTCTX pExtCtxOut; + PBS3EXTCTX pExtCtx = bs3CpuInstr3AllocExtCtxs(&pExtCtxOut); + if (!pExtCtx) + return 0; + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + pbBuf = bs3CpuInstr3BufSetup(pbBuf, &cbBuf, bMode); + Bs3RegCtxSaveForMode(&Ctx, bMode, 1024); + bs3CpuInstr3SetupSseAndAvx(&Ctx, pExtCtx); + + /* + * Run the tests in all rings since alignment issues may behave + * differently in ring-3 compared to ring-0. + */ + for (;;) + { + unsigned iCfg; + for (iCfg = 0; iCfg < cConfigs; iCfg++) + { + unsigned iTest; + BS3CPUINSTR3_CONFIG_SAVED_T SavedCfg; + if (!bs3CpuInstr3ConfigReconfigure(&SavedCfg, &Ctx, pExtCtx, &paConfigs[iCfg], bMode)) + continue; /* unsupported config */ + + /* + * Iterate the tests. + */ + for (iTest = 0; iTest < cTests; iTest++) + { + BS3CPUINSTR3_TEST2_VALUES_T const BS3_FAR *paValues = paTests[iTest].paValues; + uint8_t const cbInstr = ((uint8_t const BS3_FAR *)(uintptr_t)paTests[iTest].pfnWorker)[-1]; + unsigned const cValues = paTests[iTest].cValues; + bool const fGprDst = paTests[iTest].fGprDst; + bool const fMmxInstr = paTests[iTest].enmType < T_SSE; + bool const fSseInstr = paTests[iTest].enmType >= T_SSE && paTests[iTest].enmType < T_AVX_128; + bool const fAvxInstr = paTests[iTest].enmType >= T_AVX_128; + uint8_t const cbOperand = paTests[iTest].enmType < T_128BITS ? 64/8 + : paTests[iTest].enmType < T_256BITS ? 128/8 : 256/8; + uint8_t const cbMemOp = bs3CpuInstr3MemOpSize(cbOperand, paTests[iTest].enmRm); + uint8_t const cbAlign = RT_MIN(cbOperand, 16); + PRTUINT256U puMemOp = bs3CpuInstr3BufForOperand(pbBuf, cbBuf, cbMemOp, cbAlign, &paConfigs[iCfg]); + uint8_t bXcptExpect = !g_afTypeSupports[paTests[iTest].enmType] + || paTests[iTest].fInvalidEncoding ? X86_XCPT_UD + : fMmxInstr ? paConfigs[iCfg].bXcptMmx + : fSseInstr ? paConfigs[iCfg].bXcptSse + : BS3_MODE_IS_RM_OR_V86(bMode) ? X86_XCPT_UD : paConfigs[iCfg].bXcptAvx; + uint64_t const fGprValMask = paTests[iTest].cBitsGprValMask == 64 ? UINT64_MAX + : RT_BIT_64(paTests[iTest].cBitsGprValMask) - 1; + uint16_t idTestStep = bRing * 10000 + iCfg * 100 + iTest * 10; + unsigned iVal; + + /* If testing unaligned memory accesses, skip register-only tests. This allows + setting bXcptMmx, bXcptSse and bXcptAvx to reflect the misaligned exceptions. */ + if (paTests[iTest].enmRm == RM_REG && (!paConfigs[iCfg].fAligned || paConfigs[iCfg].fAlignCheck)) + continue; + + /* #AC is only raised in ring-3.: */ + if (bXcptExpect == X86_XCPT_AC) + { + if (bRing != 3) + bXcptExpect = X86_XCPT_DB; + else if (fAvxInstr) + bXcptExpect = paTests[iTest].bAvxMisalignXcpt; /* they generally don't raise #AC */ + } + + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, paTests[iTest].pfnWorker); + + /* + * Iterate the test values and do the actual testing. + */ + for (iVal = 0; iVal < cValues; iVal++, idTestStep++) + { + uint16_t cErrors; + uint16_t uSavedFtw = 0xff; + RTUINT256U uMemOpExpect; + + /* + * Set up the context and some expectations. + */ + if (fGprDst) + { + /* dest - gpr/mem */ + if (paTests[iTest].iGprReg == UINT8_MAX) + { + BS3_ASSERT(paTests[iTest].enmRm >= RM_MEM); + Bs3MemSet(puMemOp, 0xcc, cbMemOp); + if (bXcptExpect != X86_XCPT_DB) + Bs3MemSet(&uMemOpExpect, 0xcc, sizeof(uMemOpExpect)); + else + { + Bs3MemSet(&uMemOpExpect, 0xaa, sizeof(uMemOpExpect)); + switch (paTests[iTest].cbGpr) + { + case 1: uMemOpExpect.au8[0] = (uint8_t) (paValues[iVal].uGpr & fGprValMask); break; + case 2: uMemOpExpect.au16[0] = (uint16_t)(paValues[iVal].uGpr & fGprValMask); break; + case 4: uMemOpExpect.au32[0] = (uint32_t)(paValues[iVal].uGpr & fGprValMask); break; + case 8: uMemOpExpect.au64[0] = (paValues[iVal].uGpr & fGprValMask); break; + default: BS3_ASSERT(0); + } + } + } + else + Bs3RegCtxSetGpr(&Ctx, paTests[iTest].iGprReg, UINT64_C(0xcccccccccccccccc), + BS3_MODE_IS_64BIT_CODE(bMode) ? 8 : 4); /* we only restore 63:32 when bMode==LM64 */ + + /* source - media/mem */ + if (paTests[iTest].iMediaReg == UINT8_MAX) + { + BS3_ASSERT(paTests[iTest].enmRm >= RM_MEM); + BS3_ASSERT(paTests[iTest].iGprReg != UINT8_MAX); + Bs3MemCpy(puMemOp, &paValues[iVal].uMedia, cbMemOp); + uMemOpExpect = paValues[iVal].uMedia; + } + else if (fMmxInstr) + Bs3ExtCtxSetMm(pExtCtx, paTests[iTest].iMediaReg, paValues[iVal].uMedia.QWords.qw0, BS3EXTCTXTOPMM_ZERO); + else if (fSseInstr) + Bs3ExtCtxSetXmm(pExtCtx, paTests[iTest].iMediaReg, &paValues[iVal].uMedia.DQWords.dqw0); + else + Bs3ExtCtxSetYmm(pExtCtx, paTests[iTest].iMediaReg, &paValues[iVal].uMedia, 32); + } + else + { + /* dest - media */ + if (paTests[iTest].iMediaReg == UINT8_MAX) + { + BS3_ASSERT(paTests[iTest].enmRm >= RM_MEM); + Bs3MemSet(puMemOp, 0xcc, cbMemOp); + if (bXcptExpect == X86_XCPT_DB) + uMemOpExpect = paValues[iVal].uMedia; + else + Bs3MemSet(&uMemOpExpect, 0xcc, sizeof(uMemOpExpect)); + } + + /* source - gpr/mem */ + if (paTests[iTest].iGprReg == UINT8_MAX) + { + BS3_ASSERT(paTests[iTest].enmRm >= RM_MEM); + Bs3MemSet(&uMemOpExpect, 0xcc, sizeof(uMemOpExpect)); + if (bXcptExpect == X86_XCPT_DB) + switch (paTests[iTest].cbGpr) + { + case 1: uMemOpExpect.au8[0] = (uint8_t) (paValues[iVal].uGpr & fGprValMask); break; + case 2: uMemOpExpect.au16[0] = (uint16_t)(paValues[iVal].uGpr & fGprValMask); break; + case 4: uMemOpExpect.au32[0] = (uint32_t)(paValues[iVal].uGpr & fGprValMask); break; + case 8: uMemOpExpect.au64[0] = (paValues[iVal].uGpr & fGprValMask); break; + default: BS3_ASSERT(0); + } + Bs3MemCpy(puMemOp, &uMemOpExpect, cbMemOp); + } + else + Bs3RegCtxSetGpr(&Ctx, paTests[iTest].iGprReg, paValues[iVal].uGpr & fGprValMask, paTests[iTest].cbGpr); + } + + /* Memory pointer. */ + if (paTests[iTest].enmRm >= RM_MEM) + { + BS3_ASSERT(paTests[iTest].iGprReg == UINT8_MAX || paTests[iTest].iMediaReg == UINT8_MAX); + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, &Ctx.fs, puMemOp); + } + + /* + * Execute. + */ + Bs3TrapSetJmpAndRestoreWithExtCtxAndRm(&Ctx, pExtCtx, &TrapFrame, pExtCtxOut); + + /* + * Check the result: + */ + cErrors = Bs3TestSubErrorCount(); + + if (fMmxInstr && bXcptExpect == X86_XCPT_DB) + { + uSavedFtw = Bs3ExtCtxGetAbridgedFtw(pExtCtx); + Bs3ExtCtxSetAbridgedFtw(pExtCtx, 0xff); + } + if (!fGprDst && bXcptExpect == X86_XCPT_DB && paTests[iTest].iMediaReg != UINT8_MAX) + { + if (fMmxInstr) + Bs3ExtCtxSetMm(pExtCtx, paTests[iTest].iMediaReg, paValues[iVal].uMedia.QWords.qw0, BS3EXTCTXTOPMM_SET); + else if (fSseInstr) + Bs3ExtCtxSetXmm(pExtCtx, paTests[iTest].iMediaReg, &paValues[iVal].uMedia.DQWords.dqw0); + else + Bs3ExtCtxSetYmm(pExtCtx, paTests[iTest].iMediaReg, &paValues[iVal].uMedia, 32); + } + Bs3TestCheckExtCtx(pExtCtxOut, pExtCtx, 0 /*fFlags*/, pszMode, idTestStep); + + if (TrapFrame.bXcpt != bXcptExpect) + Bs3TestFailedF("Expected bXcpt = %#x, got %#x", bXcptExpect, TrapFrame.bXcpt); + + if (fGprDst && bXcptExpect == X86_XCPT_DB && paTests[iTest].iGprReg != UINT8_MAX) + Bs3RegCtxSetGpr(&Ctx, paTests[iTest].iGprReg, paValues[iVal].uGpr & fGprValMask, + paTests[iTest].cbGpr >= 4 ? 8 : paTests[iTest].cbGpr); + /* Kludge! Looks like EFLAGS.AC is cleared when raising #GP in real mode on the 10980XE. WEIRD! */ + if (bMode == BS3_MODE_RM && (Ctx.rflags.u32 & X86_EFL_AC)) + { + if (TrapFrame.Ctx.rflags.u32 & X86_EFL_AC) + Bs3TestFailedF("Expected EFLAGS.AC to be cleared (bXcpt=%d)", TrapFrame.bXcpt); + TrapFrame.Ctx.rflags.u32 |= X86_EFL_AC; + } + Bs3TestCheckRegCtxEx(&TrapFrame.Ctx, &Ctx, bXcptExpect == X86_XCPT_DB ? cbInstr + 1 : 0, 0, + bXcptExpect == X86_XCPT_DB || BS3_MODE_IS_16BIT_SYS(bMode) ? 0 : X86_EFL_RF, + pszMode, idTestStep); + + if ( paTests[iTest].enmRm >= RM_MEM + && Bs3MemCmp(puMemOp, &uMemOpExpect, cbMemOp) != 0) + Bs3TestFailedF("Expected uMemOp %.*Rhxs, got %.*Rhxs", cbMemOp, &uMemOpExpect, cbMemOp, puMemOp); + + if (cErrors != Bs3TestSubErrorCount()) + { + if (paConfigs[iCfg].fAligned) + Bs3TestFailedF("ring-%d/cfg#%u/test#%u/value#%u failed (bXcptExpect=%#x)", + bRing, iCfg, iTest, iVal, bXcptExpect); + else + Bs3TestFailedF("ring-%d/cfg#%u/test#%u/value#%u failed (bXcptExpect=%#x, puMemOp=%p, EFLAGS=%#RX32, CR0=%#RX32)", + bRing, iCfg, iTest, iVal, bXcptExpect, puMemOp, TrapFrame.Ctx.rflags.u32, TrapFrame.Ctx.cr0); + Bs3TestPrintf("\n"); + } + + if (uSavedFtw != 0xff) + Bs3ExtCtxSetAbridgedFtw(pExtCtx, uSavedFtw); + } + } + + bs3CpuInstr3ConfigRestore(&SavedCfg, &Ctx, pExtCtx); + } + + /* + * Next ring. + */ + bRing++; + if (bRing > 3 || bMode == BS3_MODE_RM) + break; + Bs3RegCtxConvertToRingX(&Ctx, bRing); + } + + /* + * Cleanup. + */ + bs3CpuInstr3BufCleanup(pbBuf, cbBuf, bMode); + bs3CpuInstr3FreeExtCtxs(pExtCtx, pExtCtxOut); + return 0; +} + + +/* + * PMOVMSKB, VPMOVMSKB. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovmskb_EAX_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovmskb_EAX_qword_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovmskb_EAX_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovmskb_EAX_dqword_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovmskb_EAX_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovmskb_EAX_dqword_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovmskb_EAX_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovmskb_EAX_qqword_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovmskb_RAX_YMM9_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pmovmskb(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST2_VALUES_T const s_aValues[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), /*->*/ UINT64_C(0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), UINT64_C(0xffffffff) }, + { RTUINT256_INIT_C(0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f), UINT64_C(0x00000000) }, + { RTUINT256_INIT_C(0x8080808080808080, 0x8080808080808080, 0x8080808080808080, 0x8080808080808080), UINT64_C(0xffffffff) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), UINT64_C(0x03000003) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), UINT64_C(0x255193ab) }, + }; + + static BS3CPUINSTR3_TEST2_T const s_aTests16[] = + { + { bs3CpuInstr3_pmovmskb_EAX_MM2_icebp_c16, 255, RM_REG, T_AXMMX_OR_SSE, 4, 8, false, true, 0, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmovmskb_EAX_qword_FSxBX_icebp_c16, 255, RM_MEM, T_AXMMX_OR_SSE, 4, 8, true, true, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmovmskb_EAX_XMM2_icebp_c16, 255, RM_REG, T_SSE2, 4, 16, false, true, 0, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmovmskb_EAX_dqword_FSxBX_icebp_c16, 255, RM_MEM, T_SSE2, 4, 16, true, true, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmovmskb_EAX_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 4, 16, false, true, 0, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmovmskb_EAX_dqword_FSxBX_icebp_c16, 255, RM_MEM, T_AVX_128, 4, 16, true, true, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmovmskb_EAX_YMM2_icebp_c16, 255, RM_REG, T_AVX2_256, 4, 32, false, true, 0, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmovmskb_EAX_qqword_FSxBX_icebp_c16, 255, RM_MEM, T_AVX2_256, 4, 32, true, true, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST2_T const s_aTests32[] = + { + { bs3CpuInstr3_pmovmskb_EAX_MM2_icebp_c32, 255, RM_REG, T_AXMMX_OR_SSE, 4, 8, false, true, 0, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmovmskb_EAX_qword_FSxBX_icebp_c32, 255, RM_MEM, T_AXMMX_OR_SSE, 4, 8, true, true, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmovmskb_EAX_XMM2_icebp_c32, 255, RM_REG, T_SSE2, 4, 16, false, true, 0, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmovmskb_EAX_dqword_FSxBX_icebp_c32, 255, RM_MEM, T_SSE2, 4, 16, true, true, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmovmskb_EAX_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 4, 16, false, true, 0, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmovmskb_EAX_dqword_FSxBX_icebp_c32, 255, RM_MEM, T_AVX_128, 4, 16, true, true, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmovmskb_EAX_YMM2_icebp_c32, 255, RM_REG, T_AVX2_256, 4, 32, false, true, 0, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmovmskb_EAX_qqword_FSxBX_icebp_c32, 255, RM_MEM, T_AVX2_256, 4, 32, true, true, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST2_T const s_aTests64[] = + { + { bs3CpuInstr3_pmovmskb_EAX_MM2_icebp_c64, 255, RM_REG, T_AXMMX_OR_SSE, 8, 8, false, true, 0, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmovmskb_EAX_qword_FSxBX_icebp_c64, 255, RM_MEM, T_AXMMX_OR_SSE, 8, 8, true, true, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmovmskb_EAX_XMM2_icebp_c64, 255, RM_REG, T_SSE2, 8, 16, false, true, 0, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pmovmskb_EAX_dqword_FSxBX_icebp_c64, 255, RM_MEM, T_SSE2, 8, 16, true, true, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmovmskb_EAX_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 8, 16, false, true, 0, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmovmskb_EAX_dqword_FSxBX_icebp_c64, 255, RM_MEM, T_AVX_128, 8, 16, true, true, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmovmskb_EAX_YMM2_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 32, false, true, 0, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmovmskb_EAX_qqword_FSxBX_icebp_c64, 255, RM_MEM, T_AVX2_256, 8, 32, true, true, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpmovmskb_RAX_YMM9_icebp_c64, 255, RM_REG, T_AVX2_256, 8, 32, false, true, 0, 9, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST2_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST2_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType2(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * [V]MOVD / [V]MOVQ - greg/mem variants only. + */ + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movd_MM1_EDX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movd_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movd_EAX_MM1_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movd_FSxBX_MM1_icebp); +extern FNBS3FAR bs3CpuInstr3_movd_MM1_R9D_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movd_R10D_MM0_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movd_XMM1_EAX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movd_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movd_EAX_XMM1_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movd_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_movd_XMM9_R8D_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movd_R8D_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movd_XMM9_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movd_FSxBX_XMM9_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovd_XMM1_EAX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovd_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovd_EDX_XMM1_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovd_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovd_XMM9_R9D_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovd_R8D_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovd_XMM9_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovd_FSxBX_XMM9_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movq_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movq_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movq_FSxBX_MM1_icebp); +extern FNBS3FAR bs3CpuInstr3_movq_R9_MM1_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_06e_movq_MM1_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_07e_movq_FSxBX_MM1_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movq_MM1_R9_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movq_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movq_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_movq_XMM9_R8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movq_R8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movq_XMM9_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movq_FSxBX_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_06e_movq_XMM1_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_06e_movq_XMM9_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_07e_movq_FSxBX_XMM1_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_07e_movq_FSxBX_XMM9_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovq_XMM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovq_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovq_XMM9_R8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovq_R8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovq_XMM9_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovq_FSxBX_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_06e_vmovq_XMM1_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_06e_vmovq_XMM9_FSxBX_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_07e_vmovq_FSxBX_XMM1_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_07e_vmovq_FSxBX_XMM9_icebp_c64; + + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_movd_movq(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST2_VALUES_T const s_aValuesRm[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), /*->*/ UINT64_C(0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), UINT64_C(0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f), UINT64_C(0x7f7f7f7f7f7f7f7f) }, + { RTUINT256_INIT_C(0x8080808080808080, 0x8080808080808080, 0x8080808080808080, 0x8080808080808080), UINT64_C(0x8080808080808080) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), UINT64_C(0x5555666677778888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), UINT64_C(0x9c5ce073930996bb) }, + }; + static BS3CPUINSTR3_TEST2_VALUES_T const s_aValuesMediaD[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), /*->*/ UINT64_C(0) }, + { RTUINT256_INIT_C(0, 0, 0, 0x00000000ffffffff), UINT64_C(0xffffffffffffffff) }, + { RTUINT256_INIT_C(0, 0, 0, 0x000000007f7f7f7f), UINT64_C(0x7f7f7f7f7f7f7f7f) }, + { RTUINT256_INIT_C(0, 0, 0, 0x0000000080808080), UINT64_C(0x8080808080808080) }, + { RTUINT256_INIT_C(0, 0, 0, 0x0000000077778888), UINT64_C(0x5555666677778888) }, + { RTUINT256_INIT_C(0, 0, 0, 0x00000000930996bb), UINT64_C(0x9c5ce073930996bb) }, + }; + static BS3CPUINSTR3_TEST2_VALUES_T const s_aValuesMediaQ[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), /*->*/ UINT64_C(0) }, + { RTUINT256_INIT_C(0, 0, 0, 0xffffffffffffffff), UINT64_C(0xffffffffffffffff) }, + { RTUINT256_INIT_C(0, 0, 0, 0x7f7f7f7f7f7f7f7f), UINT64_C(0x7f7f7f7f7f7f7f7f) }, + { RTUINT256_INIT_C(0, 0, 0, 0x8080808080808080), UINT64_C(0x8080808080808080) }, + { RTUINT256_INIT_C(0, 0, 0, 0x5555666677778888), UINT64_C(0x5555666677778888) }, + { RTUINT256_INIT_C(0, 0, 0, 0x9c5ce073930996bb), UINT64_C(0x9c5ce073930996bb) }, + }; + + /* Note! Seems the 256-bit variants doesn't generate \#ACs on a 10980XE. WEIRD! */ + static BS3CPUINSTR3_TEST2_T const s_aTests16[] = + { + { bs3CpuInstr3_movd_MM1_EDX_icebp_c16, 255, RM_REG, T_MMX, 4, 32, false, false, 2, 1, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_movd_MM1_FSxBX_icebp_c16, 255, RM_MEM32, T_MMX, 4, 32, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_movd_EAX_MM1_icebp_c16, 255, RM_REG, T_MMX, 4, 32, false, true, 0, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_movd_FSxBX_MM1_icebp_c16, 255, RM_MEM32, T_MMX, 4, 32, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + + { bs3CpuInstr3_movd_XMM1_EAX_icebp_c16, 255, RM_REG, T_SSE2, 4, 32, false, false, 0, 1, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_movd_XMM1_FSxBX_icebp_c16, 255, RM_MEM32, T_SSE2, 4, 32, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_movd_EAX_XMM1_icebp_c16, 255, RM_REG, T_SSE2, 4, 32, false, true, 0, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_movd_FSxBX_XMM1_icebp_c16, 255, RM_MEM32, T_SSE2, 4, 32, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + + { bs3CpuInstr3_vmovd_XMM1_EAX_icebp_c16, 255, RM_REG, T_AVX_128, 4, 32, false, false, 0, 1, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_vmovd_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM32, T_AVX_128, 4, 32, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_vmovd_EDX_XMM1_icebp_c16, 255, RM_REG, T_AVX_128, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_vmovd_FSxBX_XMM1_icebp_c16, X86_XCPT_AC, RM_MEM32, T_AVX_128, 4, 32, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_movq_MM1_FSxBX_icebp_c16, 255, RM_MEM64, T_MMX, 8, 64, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaQ), s_aValuesMediaQ }, + { bs3CpuInstr3_movq_FSxBX_MM1_icebp_c16, 255, RM_MEM64, T_MMX, 8, 64, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_movq_XMM1_FSxBX_icebp_c16, 255, RM_MEM64, T_SSE2, 8, 64, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaQ), s_aValuesMediaQ }, + { bs3CpuInstr3_movq_FSxBX_XMM1_icebp_c16, 255, RM_MEM64, T_SSE2, 8, 64, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + + { bs3CpuInstr3_vmovq_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM64, T_AVX_128, 8, 64, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaQ), s_aValuesMediaQ }, + { bs3CpuInstr3_vmovq_FSxBX_XMM1_icebp_c16, X86_XCPT_AC, RM_MEM64, T_AVX_128, 8, 64, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + }; + static BS3CPUINSTR3_TEST2_T const s_aTests32[] = + { + { bs3CpuInstr3_movd_MM1_EDX_icebp_c32, 255, RM_REG, T_MMX, 4, 32, false, false, 2, 1, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_movd_MM1_FSxBX_icebp_c32, 255, RM_MEM32, T_MMX, 4, 32, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_movd_EAX_MM1_icebp_c32, 255, RM_REG, T_MMX, 4, 32, false, true, 0, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_movd_FSxBX_MM1_icebp_c32, 255, RM_MEM32, T_MMX, 4, 32, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + + { bs3CpuInstr3_movd_XMM1_EAX_icebp_c32, 255, RM_REG, T_SSE2, 4, 32, false, false, 0, 1, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_movd_XMM1_FSxBX_icebp_c32, 255, RM_MEM32, T_SSE2, 4, 32, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_movd_EAX_XMM1_icebp_c32, 255, RM_REG, T_SSE2, 4, 32, false, true, 0, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_movd_FSxBX_XMM1_icebp_c32, 255, RM_MEM32, T_SSE2, 4, 32, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + + { bs3CpuInstr3_vmovd_XMM1_EAX_icebp_c32, 255, RM_REG, T_AVX_128, 4, 32, false, false, 0, 1, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_vmovd_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM32, T_AVX_128, 4, 32, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_vmovd_EDX_XMM1_icebp_c32, 255, RM_REG, T_AVX_128, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_vmovd_FSxBX_XMM1_icebp_c32, X86_XCPT_AC, RM_MEM32, T_AVX_128, 4, 32, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + + { bs3CpuInstr3_movq_MM1_FSxBX_icebp_c32, 255, RM_MEM64, T_MMX, 8, 64, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaQ), s_aValuesMediaQ }, + { bs3CpuInstr3_movq_FSxBX_MM1_icebp_c32, 255, RM_MEM64, T_MMX, 8, 64, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + + { bs3CpuInstr3_movq_XMM1_FSxBX_icebp_c32, 255, RM_MEM64, T_SSE2, 8, 64, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaQ), s_aValuesMediaQ }, + { bs3CpuInstr3_movq_FSxBX_XMM1_icebp_c32, 255, RM_MEM64, T_SSE2, 8, 64, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + + { bs3CpuInstr3_vmovq_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM64, T_AVX_128, 8, 64, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaQ), s_aValuesMediaQ }, + { bs3CpuInstr3_vmovq_FSxBX_XMM1_icebp_c32, X86_XCPT_AC, RM_MEM64, T_AVX_128, 8, 64, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + }; + static BS3CPUINSTR3_TEST2_T const s_aTests64[] = + { + { bs3CpuInstr3_movd_MM1_EDX_icebp_c64, 255, RM_REG, T_MMX, 4, 32, false, false, 2, 1, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_movd_MM1_R9D_icebp_c64, 255, RM_REG, T_MMX, 4, 32, false, false, 9, 1, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_movd_MM1_FSxBX_icebp_c64, 255, RM_MEM32, T_MMX, 4, 32, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_movd_EAX_MM1_icebp_c64, 255, RM_REG, T_MMX, 4, 32, false, true, 0, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_movd_R10D_MM0_icebp_c64, 255, RM_REG, T_MMX, 4, 32, false, true, 10, 0, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_movd_FSxBX_MM1_icebp_c64, 255, RM_MEM32, T_MMX, 4, 32, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + + { bs3CpuInstr3_movd_XMM1_EAX_icebp_c64, 255, RM_REG, T_SSE2, 4, 32, false, false, 0, 1, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_movd_XMM9_R8D_icebp_c64, 255, RM_REG, T_SSE2, 4, 32, false, false, 8, 9, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_movd_XMM1_FSxBX_icebp_c64, 255, RM_MEM32, T_SSE2, 4, 32, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_movd_XMM9_FSxBX_icebp_c64, 255, RM_MEM32, T_SSE2, 4, 32, false, false, 255, 9, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_movd_EAX_XMM1_icebp_c64, 255, RM_REG, T_SSE2, 4, 32, false, true, 0, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_movd_R8D_XMM9_icebp_c64, 255, RM_REG, T_SSE2, 4, 32, false, true, 8, 9, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_movd_FSxBX_XMM1_icebp_c64, 255, RM_MEM32, T_SSE2, 4, 32, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + + { bs3CpuInstr3_vmovd_XMM1_EAX_icebp_c64, 255, RM_REG, T_AVX_128, 4, 32, false, false, 0, 1, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_vmovd_XMM9_R9D_icebp_c64, 255, RM_REG, T_AVX_128, 4, 32, false, false, 9, 9, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_vmovd_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM32, T_AVX_128, 4, 32, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_vmovd_XMM9_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM32, T_AVX_128, 4, 32, false, false, 255, 9, RT_ELEMENTS(s_aValuesMediaD), s_aValuesMediaD }, + { bs3CpuInstr3_vmovd_EDX_XMM1_icebp_c64, 255, RM_REG, T_AVX_128, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_vmovd_R8D_XMM9_icebp_c64, 255, RM_REG, T_AVX_128, 4, 32, false, true, 8, 9, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_vmovd_FSxBX_XMM1_icebp_c64, X86_XCPT_AC, RM_MEM32, T_AVX_128, 4, 32, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_vmovd_FSxBX_XMM9_icebp_c64, X86_XCPT_AC, RM_MEM32, T_AVX_128, 4, 32, false, true, 255, 9, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + + { bs3CpuInstr3_movq_MM1_R9_icebp_c64, 255, RM_REG, T_MMX, 8, 64, false, false, 9, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_movq_MM1_FSxBX_icebp_c64, 255, RM_MEM64, T_MMX, 8, 64, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaQ), s_aValuesMediaQ }, + { bs3CpuInstr3_06e_movq_MM1_FSxBX_icebp_c64, 255, RM_MEM64, T_MMX, 8, 64, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaQ), s_aValuesMediaQ }, + { bs3CpuInstr3_movq_R9_MM1_icebp_c64, 255, RM_REG, T_MMX, 8, 64, false, true, 9, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_movq_FSxBX_MM1_icebp_c64, 255, RM_MEM64, T_MMX, 8, 64, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_07e_movq_FSxBX_MM1_icebp_c64, 255, RM_MEM64, T_MMX, 8, 64, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + + { bs3CpuInstr3_movq_XMM9_R8_icebp_c64, 255, RM_REG, T_SSE2, 8, 64, false, false, 8, 9, RT_ELEMENTS(s_aValuesMediaQ), s_aValuesMediaQ }, + { bs3CpuInstr3_movq_XMM1_FSxBX_icebp_c64, 255, RM_MEM64, T_SSE2, 8, 64, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaQ), s_aValuesMediaQ }, + { bs3CpuInstr3_06e_movq_XMM1_FSxBX_icebp_c64, 255, RM_MEM64, T_SSE2, 8, 64, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaQ), s_aValuesMediaQ }, + { bs3CpuInstr3_movq_XMM9_FSxBX_icebp_c64, 255, RM_MEM64, T_SSE2, 8, 64, false, false, 255, 9, RT_ELEMENTS(s_aValuesMediaQ), s_aValuesMediaQ }, + { bs3CpuInstr3_06e_movq_XMM9_FSxBX_icebp_c64, 255, RM_MEM64, T_SSE2, 8, 64, false, false, 255, 9, RT_ELEMENTS(s_aValuesMediaQ), s_aValuesMediaQ }, + { bs3CpuInstr3_movq_R8_XMM9_icebp_c64, 255, RM_REG, T_SSE2, 8, 64, false, true, 8, 9, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_movq_FSxBX_XMM1_icebp_c64, 255, RM_MEM64, T_SSE2, 8, 64, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_07e_movq_FSxBX_XMM1_icebp_c64, 255, RM_MEM64, T_SSE2, 8, 64, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_movq_FSxBX_XMM9_icebp_c64, 255, RM_MEM64, T_SSE2, 8, 64, false, true, 255, 9, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_07e_movq_FSxBX_XMM9_icebp_c64, 255, RM_MEM64, T_SSE2, 8, 64, false, true, 255, 9, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + + { bs3CpuInstr3_vmovq_XMM9_R8_icebp_c64, 255, RM_REG, T_AVX_128, 8, 64, false, false, 8, 9, RT_ELEMENTS(s_aValuesMediaQ), s_aValuesMediaQ }, + { bs3CpuInstr3_vmovq_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 8, 64, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaQ), s_aValuesMediaQ }, + { bs3CpuInstr3_06e_vmovq_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 8, 64, false, false, 255, 1, RT_ELEMENTS(s_aValuesMediaQ), s_aValuesMediaQ }, + { bs3CpuInstr3_vmovq_XMM9_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 8, 64, false, false, 255, 9, RT_ELEMENTS(s_aValuesMediaQ), s_aValuesMediaQ }, + { bs3CpuInstr3_06e_vmovq_XMM9_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 8, 64, false, false, 255, 9, RT_ELEMENTS(s_aValuesMediaQ), s_aValuesMediaQ }, + { bs3CpuInstr3_vmovq_R8_XMM9_icebp_c64, 255, RM_REG, T_AVX_128, 8, 64, false, true, 8, 9, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_vmovq_FSxBX_XMM1_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 8, 64, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_07e_vmovq_FSxBX_XMM1_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 8, 64, false, true, 255, 1, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_vmovq_FSxBX_XMM9_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 8, 64, false, true, 255, 9, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + { bs3CpuInstr3_07e_vmovq_FSxBX_XMM9_icebp_c64, X86_XCPT_AC, RM_MEM64, T_AVX_128, 8, 64, false, true, 255, 9, RT_ELEMENTS(s_aValuesRm), s_aValuesRm }, + }; + static BS3CPUINSTR3_TEST2_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST2_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType2(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig5, RT_ELEMENTS(g_aXcptConfig5)); +} + + +/* + * [V]PEXTRW. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pextrw_EDX_MM1_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pextrw_EDX_XMM1_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_pextrw_RDX_XMM1_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pextrw_R9D_MM1_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pextrw_R9D_XMM8_000h_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pextrw_EDX_MM1_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pextrw_EDX_XMM1_0FFh_icebp); +extern FNBS3FAR bs3CpuInstr3_pextrw_RDX_XMM1_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pextrw_R9D_MM1_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pextrw_R9D_XMM8_0FFh_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpextrw_EDX_XMM1_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_vpextrw_RDX_XMM1_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpextrw_EDX_XMM8_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpextrw_R9D_XMM8_000h_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpextrw_EDX_XMM1_0FFh_icebp); +extern FNBS3FAR bs3CpuInstr3_vpextrw_RDX_XMM1_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpextrw_EDX_XMM8_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpextrw_R9D_XMM8_0FFh_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pextrw(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST2_VALUES_T const s_aValues00[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0x1234), /*->*/ UINT64_C(0x1234) }, + { RTUINT256_INIT_C(1, 2, 0xffffffffffffffff, 0xffffffffffffffff), UINT64_C(0xffff) }, + { RTUINT256_INIT_C(3, 4, 0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f), UINT64_C(0x7f7f) }, + { RTUINT256_INIT_C(5, 6, 0x8080808080808080, 0x8080808080808080), UINT64_C(0x8080) }, + { RTUINT256_INIT_C(7, 8, 0x1111222233334444, 0x5555666677778888), UINT64_C(0x8888) }, + { RTUINT256_INIT_C(9, 10, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), UINT64_C(0x96bb) }, + }; + + static BS3CPUINSTR3_TEST2_VALUES_T const s_aValuesFF[] = + { + { RTUINT256_INIT_C(0, 0, 0x1234000000000000, 0), UINT64_C(0x1234) }, + { RTUINT256_INIT_C(1, 2, 0xffffffffffffffff, 0xffffffffffffffff), UINT64_C(0xffff) }, + { RTUINT256_INIT_C(3, 4, 0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f), UINT64_C(0x7f7f) }, + { RTUINT256_INIT_C(5, 6, 0x8080808080808080, 0x8080808080808080), UINT64_C(0x8080) }, + { RTUINT256_INIT_C(7, 8, 0x1111222233334444, 0x5555666677778888), UINT64_C(0x1111) }, + { RTUINT256_INIT_C(9, 10, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), UINT64_C(0xb421) }, + }; + + static BS3CPUINSTR3_TEST2_VALUES_T const s_aValuesFF_64[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0x1234000000000000), UINT64_C(0x1234) }, + { RTUINT256_INIT_C(1, 2, 0xffffffffffffffff, 0xffffffffffffffff), UINT64_C(0xffff) }, + { RTUINT256_INIT_C(3, 4, 0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f), UINT64_C(0x7f7f) }, + { RTUINT256_INIT_C(5, 6, 0x8080808080808080, 0x8080808080808080), UINT64_C(0x8080) }, + { RTUINT256_INIT_C(7, 8, 0x1111222233334444, 0x5555666677778888), UINT64_C(0x5555) }, + { RTUINT256_INIT_C(9, 10, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), UINT64_C(0x9c5c) }, + }; + + static BS3CPUINSTR3_TEST2_T const s_aTests16[] = + { + { bs3CpuInstr3_pextrw_EDX_MM1_000h_icebp_c16, 255, RM_REG, T_MMX_SSE, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pextrw_EDX_XMM1_000h_icebp_c16, 255, RM_REG, T_SSE2, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpextrw_EDX_XMM1_000h_icebp_c16, 255, RM_REG, T_AVX_128, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_pextrw_EDX_MM1_0FFh_icebp_c16, 255, RM_REG, T_MMX_SSE, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesFF_64), s_aValuesFF_64 }, + { bs3CpuInstr3_pextrw_EDX_XMM1_0FFh_icebp_c16, 255, RM_REG, T_SSE2, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpextrw_EDX_XMM1_0FFh_icebp_c16, 255, RM_REG, T_AVX_128, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + }; + static BS3CPUINSTR3_TEST2_T const s_aTests32[] = + { + { bs3CpuInstr3_pextrw_EDX_MM1_000h_icebp_c32, 255, RM_REG, T_MMX_SSE, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pextrw_EDX_XMM1_000h_icebp_c32, 255, RM_REG, T_SSE2, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpextrw_EDX_XMM1_000h_icebp_c32, 255, RM_REG, T_AVX_128, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_pextrw_EDX_MM1_0FFh_icebp_c32, 255, RM_REG, T_MMX_SSE, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesFF_64), s_aValuesFF_64 }, + { bs3CpuInstr3_pextrw_EDX_XMM1_0FFh_icebp_c32, 255, RM_REG, T_SSE2, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpextrw_EDX_XMM1_0FFh_icebp_c32, 255, RM_REG, T_AVX_128, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + }; + static BS3CPUINSTR3_TEST2_T const s_aTests64[] = + { + { bs3CpuInstr3_pextrw_EDX_MM1_000h_icebp_c64, 255, RM_REG, T_MMX_SSE, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pextrw_R9D_MM1_000h_icebp_c64, 255, RM_REG, T_MMX_SSE, 4, 32, false, true, 9, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pextrw_EDX_XMM1_000h_icebp_c64, 255, RM_REG, T_SSE2, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + /// @todo Emits the SSE4.1 0f3a variant { bs3CpuInstr3_pextrw_RDX_XMM1_000h_icebp_c64, 255, RM_REG, T_SSE2, 8, 64, false, true, 2, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pextrw_R9D_XMM8_000h_icebp_c64, 255, RM_REG, T_SSE2, 4, 32, false, true, 9, 8, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpextrw_EDX_XMM1_000h_icebp_c64, 255, RM_REG, T_AVX_128, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpextrw_RDX_XMM1_000h_icebp_c64, 255, RM_REG, T_AVX_128, 8, 64, false, true, 2, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpextrw_R9D_XMM8_000h_icebp_c64, 255, RM_REG, T_AVX_128, 4, 32, false, true, 9, 8, RT_ELEMENTS(s_aValues00), s_aValues00 }, + + { bs3CpuInstr3_pextrw_EDX_MM1_0FFh_icebp_c64, 255, RM_REG, T_MMX_SSE, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesFF_64), s_aValuesFF_64 }, + { bs3CpuInstr3_pextrw_R9D_MM1_0FFh_icebp_c64, 255, RM_REG, T_MMX_SSE, 4, 32, false, true, 9, 1, RT_ELEMENTS(s_aValuesFF_64), s_aValuesFF_64 }, + { bs3CpuInstr3_pextrw_EDX_XMM1_0FFh_icebp_c64, 255, RM_REG, T_SSE2, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + /// @todo Emits the SSE4.1 0f3a variant { bs3CpuInstr3_pextrw_RDX_XMM1_0FFh_icebp_c64, 255, RM_REG, T_SSE2, 8, 64, false, true, 2, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pextrw_R9D_XMM8_0FFh_icebp_c64, 255, RM_REG, T_SSE2, 4, 32, false, true, 9, 8, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpextrw_EDX_XMM1_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpextrw_RDX_XMM1_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 8, 64, false, true, 2, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpextrw_R9D_XMM8_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 4, 32, false, true, 9, 8, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + }; + static BS3CPUINSTR3_TEST2_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST2_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType2(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig5, RT_ELEMENTS(g_aXcptConfig5)); +} + + +/* + * [V]MOVMSKPS / [V]MOVMSKPD. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movmskps_EDX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_movmskps_RDX_XMM1_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movmskps_R9D_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movmskps_RDX_XMM8_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovmskps_EDX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovmskps_RDX_XMM1_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovmskps_EDX_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovmskps_R9D_XMM8_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovmskps_EDX_YMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovmskps_RDX_YMM1_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovmskps_EDX_YMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovmskps_R9D_YMM8_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movmskpd_EDX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_movmskpd_RDX_XMM1_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movmskpd_R9D_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movmskpd_RDX_XMM8_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovmskpd_EDX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovmskpd_RDX_XMM1_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovmskpd_EDX_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovmskpd_R9D_XMM8_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovmskpd_EDX_YMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovmskpd_RDX_YMM1_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovmskpd_EDX_YMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovmskpd_R9D_YMM8_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_movmskps_movmskpd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST2_VALUES_T const s_aValuesR32_128[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), /*->*/ UINT64_C(0x0) }, + { RTUINT256_INIT_C(1, 2, 0xffffffffffffffff, 0xffffffffffffffff), UINT64_C(0xf) }, + { RTUINT256_INIT_C(3, 4, 0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f), UINT64_C(0x0) }, + { RTUINT256_INIT_C(5, 6, 0x8080808080808080, 0x8080808080808080), UINT64_C(0xf) }, + { RTUINT256_INIT_C(7, 8, 0x1111222233334444, 0x5555666677778888), UINT64_C(0x0) }, + { RTUINT256_INIT_C(9, 10, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), UINT64_C(0xb) }, + }; + + static BS3CPUINSTR3_TEST2_VALUES_T const s_aValuesR32_256[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), /*->*/ UINT64_C(0x00) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), UINT64_C(0xff) }, + { RTUINT256_INIT_C(0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f), UINT64_C(0x00) }, + { RTUINT256_INIT_C(0x8080808080808080, 0x8080808080808080, 0x8080808080808080, 0x8080808080808080), UINT64_C(0xff) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff0000, 0x1111222233334444, 0x5555666677778888), UINT64_C(0xf0) }, + { RTUINT256_INIT_C(0x9c5ce073930996bb, 0xb4212fa8564c9ba2, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), UINT64_C(0xeb) }, + }; + + static BS3CPUINSTR3_TEST2_VALUES_T const s_aValuesR64_128[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), UINT64_C(0x0) }, + { RTUINT256_INIT_C(1, 2, 0xffffffffffffffff, 0xffffffffffffffff), UINT64_C(0x3) }, + { RTUINT256_INIT_C(3, 4, 0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f), UINT64_C(0x0) }, + { RTUINT256_INIT_C(5, 6, 0x8080808080808080, 0x8080808080808080), UINT64_C(0x3) }, + { RTUINT256_INIT_C(7, 8, 0x1111222233334444, 0x5555666677778888), UINT64_C(0x0) }, + { RTUINT256_INIT_C(9, 10, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), UINT64_C(0x3) }, + }; + + static BS3CPUINSTR3_TEST2_VALUES_T const s_aValuesR64_256[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), UINT64_C(0x0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), UINT64_C(0xf) }, + { RTUINT256_INIT_C(0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f, 0x7f7f7f7f7f7f7f7f), UINT64_C(0x0) }, + { RTUINT256_INIT_C(0x8080808080808080, 0x8080808080808080, 0x8080808080808080, 0x8080808080808080), UINT64_C(0xf) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff0000, 0x1111222233334444, 0x5555666677778888), UINT64_C(0xc) }, + { RTUINT256_INIT_C(0x9c5ce073930996bb, 0xb4212fa8564c9ba2, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), UINT64_C(0xf) }, + }; + + static BS3CPUINSTR3_TEST2_T const s_aTests16[] = + { + { bs3CpuInstr3_movmskps_EDX_XMM1_icebp_c16, 255, RM_REG, T_SSE, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesR32_128), s_aValuesR32_128 }, + { bs3CpuInstr3_vmovmskps_EDX_XMM1_icebp_c16, 255, RM_REG, T_AVX_128, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesR32_128), s_aValuesR32_128 }, + { bs3CpuInstr3_vmovmskps_EDX_YMM1_icebp_c16, 255, RM_REG, T_AVX_256, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesR32_256), s_aValuesR32_256 }, + + { bs3CpuInstr3_movmskpd_EDX_XMM1_icebp_c16, 255, RM_REG, T_SSE2, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesR64_128), s_aValuesR64_128 }, + { bs3CpuInstr3_vmovmskpd_EDX_XMM1_icebp_c16, 255, RM_REG, T_AVX_128, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesR64_128), s_aValuesR64_128 }, + { bs3CpuInstr3_vmovmskpd_EDX_YMM1_icebp_c16, 255, RM_REG, T_AVX_256, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesR64_256), s_aValuesR64_256 }, + }; + static BS3CPUINSTR3_TEST2_T const s_aTests32[] = + { + { bs3CpuInstr3_movmskps_EDX_XMM1_icebp_c32, 255, RM_REG, T_SSE, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesR32_128), s_aValuesR32_128 }, + { bs3CpuInstr3_vmovmskps_EDX_XMM1_icebp_c32, 255, RM_REG, T_AVX_128, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesR32_128), s_aValuesR32_128 }, + { bs3CpuInstr3_vmovmskps_EDX_YMM1_icebp_c32, 255, RM_REG, T_AVX_256, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesR32_256), s_aValuesR32_256 }, + + { bs3CpuInstr3_movmskpd_EDX_XMM1_icebp_c32, 255, RM_REG, T_SSE2, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesR64_128), s_aValuesR64_128 }, + { bs3CpuInstr3_vmovmskpd_EDX_XMM1_icebp_c32, 255, RM_REG, T_AVX_128, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesR64_128), s_aValuesR64_128 }, + { bs3CpuInstr3_vmovmskpd_EDX_YMM1_icebp_c32, 255, RM_REG, T_AVX_256, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesR64_256), s_aValuesR64_256 }, + }; + static BS3CPUINSTR3_TEST2_T const s_aTests64[] = + { + { bs3CpuInstr3_movmskps_EDX_XMM1_icebp_c64, 255, RM_REG, T_SSE, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesR32_128), s_aValuesR32_128 }, + { bs3CpuInstr3_movmskps_R9D_XMM8_icebp_c64, 255, RM_REG, T_SSE, 4, 32, false, true, 9, 8, RT_ELEMENTS(s_aValuesR32_128), s_aValuesR32_128 }, + { bs3CpuInstr3_movmskps_RDX_XMM1_icebp_c64, 255, RM_REG, T_SSE, 8, 64, false, true, 2, 1, RT_ELEMENTS(s_aValuesR32_128), s_aValuesR32_128 }, + { bs3CpuInstr3_vmovmskps_EDX_XMM1_icebp_c64, 255, RM_REG, T_AVX_128, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesR32_128), s_aValuesR32_128 }, + { bs3CpuInstr3_vmovmskps_RDX_XMM1_icebp_c64, 255, RM_REG, T_AVX_128, 8, 64, false, true, 2, 1, RT_ELEMENTS(s_aValuesR32_128), s_aValuesR32_128 }, + { bs3CpuInstr3_vmovmskps_R9D_XMM8_icebp_c64, 255, RM_REG, T_AVX_128, 4, 32, false, true, 9, 8, RT_ELEMENTS(s_aValuesR32_128), s_aValuesR32_128 }, + + { bs3CpuInstr3_vmovmskps_EDX_YMM1_icebp_c64, 255, RM_REG, T_AVX_256, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesR32_256), s_aValuesR32_256 }, + { bs3CpuInstr3_vmovmskps_RDX_YMM1_icebp_c64, 255, RM_REG, T_AVX_256, 8, 64, false, true, 2, 1, RT_ELEMENTS(s_aValuesR32_256), s_aValuesR32_256 }, + { bs3CpuInstr3_vmovmskps_R9D_YMM8_icebp_c64, 255, RM_REG, T_AVX_256, 4, 32, false, true, 9, 8, RT_ELEMENTS(s_aValuesR32_256), s_aValuesR32_256 }, + + { bs3CpuInstr3_movmskpd_EDX_XMM1_icebp_c64, 255, RM_REG, T_SSE2, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesR64_128), s_aValuesR64_128 }, + { bs3CpuInstr3_movmskpd_R9D_XMM8_icebp_c64, 255, RM_REG, T_SSE2, 4, 32, false, true, 9, 8, RT_ELEMENTS(s_aValuesR64_128), s_aValuesR64_128 }, + { bs3CpuInstr3_movmskpd_RDX_XMM1_icebp_c64, 255, RM_REG, T_SSE2, 8, 64, false, true, 2, 1, RT_ELEMENTS(s_aValuesR64_128), s_aValuesR64_128 }, + { bs3CpuInstr3_vmovmskpd_EDX_XMM1_icebp_c64, 255, RM_REG, T_AVX_128, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesR64_128), s_aValuesR64_128 }, + { bs3CpuInstr3_vmovmskpd_RDX_XMM1_icebp_c64, 255, RM_REG, T_AVX_128, 8, 64, false, true, 2, 1, RT_ELEMENTS(s_aValuesR64_128), s_aValuesR64_128 }, + { bs3CpuInstr3_vmovmskpd_R9D_XMM8_icebp_c64, 255, RM_REG, T_AVX_128, 4, 32, false, true, 9, 8, RT_ELEMENTS(s_aValuesR64_128), s_aValuesR64_128 }, + + { bs3CpuInstr3_vmovmskpd_EDX_YMM1_icebp_c64, 255, RM_REG, T_AVX_256, 4, 32, false, true, 2, 1, RT_ELEMENTS(s_aValuesR64_256), s_aValuesR64_256 }, + { bs3CpuInstr3_vmovmskpd_RDX_YMM1_icebp_c64, 255, RM_REG, T_AVX_256, 8, 64, false, true, 2, 1, RT_ELEMENTS(s_aValuesR64_256), s_aValuesR64_256 }, + { bs3CpuInstr3_vmovmskpd_R9D_YMM8_icebp_c64, 255, RM_REG, T_AVX_256, 4, 32, false, true, 9, 8, RT_ELEMENTS(s_aValuesR64_256), s_aValuesR64_256 }, + }; + static BS3CPUINSTR3_TEST2_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST2_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType2(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + + + +/* + * Test type #3 - two MM/XMM/YMM operands, no flags. + */ + +typedef struct BS3CPUINSTR3_TEST3_VALUES_T +{ + RTUINT256U uSrc; + RTUINT256U uDstOut; +} BS3CPUINSTR3_TEST3_VALUES_T; + +typedef struct BS3CPUINSTR3_TEST3_T +{ + FPFNBS3FAR pfnWorker; + uint8_t bAvxMisalignXcpt; + uint8_t enmRm; + uint8_t enmType; + uint8_t iRegDst; + uint8_t iRegSrc; + uint8_t cValues; + BS3CPUINSTR3_TEST3_VALUES_T const BS3_FAR *paValues; +} BS3CPUINSTR3_TEST3_T; + +typedef struct BS3CPUINSTR3_TEST3_MODE_T +{ + BS3CPUINSTR3_TEST3_T const BS3_FAR *paTests; + unsigned cTests; +} BS3CPUINSTR3_TEST3_MODE_T; + +/** Initializer for a BS3CPUINSTR3_TEST3_MODE_T array (three entries). */ +#define BS3CPUINSTR3_TEST3_MODES_INIT(a_aTests16, a_aTests32, a_aTests64) \ + { { a_aTests16, RT_ELEMENTS(a_aTests16) }, { a_aTests32, RT_ELEMENTS(a_aTests32) }, { a_aTests64, RT_ELEMENTS(a_aTests64) } } + + +/** + * Test type #1 worker. + */ +static uint8_t bs3CpuInstr3_WorkerTestType3(uint8_t bMode, BS3CPUINSTR3_TEST3_T const BS3_FAR *paTests, unsigned cTests, + PCBS3CPUINSTR3_CONFIG_T paConfigs, unsigned cConfigs, uint8_t cbMaxAlign) +{ + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + const char BS3_FAR * const pszMode = Bs3GetModeName(bMode); + uint8_t BS3_FAR *pbBuf = g_pbBuf; + uint32_t cbBuf = g_cbBuf; + uint8_t bRing = BS3_MODE_IS_V86(bMode) ? 3 : 0; + PBS3EXTCTX pExtCtxOut; + PBS3EXTCTX pExtCtx = bs3CpuInstr3AllocExtCtxs(&pExtCtxOut); + if (!pExtCtx) + return 0; + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + Bs3RegCtxSaveForMode(&Ctx, bMode, 1024); + bs3CpuInstr3SetupSseAndAvx(&Ctx, pExtCtx); + pbBuf = bs3CpuInstr3BufSetup(pbBuf, &cbBuf, bMode); + + /* + * Run the tests in all rings since alignment issues may behave + * differently in ring-3 compared to ring-0. + */ + for (;;) + { + unsigned iCfg; + for (iCfg = 0; iCfg < cConfigs; iCfg++) + { + unsigned iTest; + BS3CPUINSTR3_CONFIG_SAVED_T SavedCfg; + if (!bs3CpuInstr3ConfigReconfigure(&SavedCfg, &Ctx, pExtCtx, &paConfigs[iCfg], bMode)) + continue; /* unsupported config */ + + /* + * Iterate the tests. + */ + for (iTest = 0; iTest < cTests; iTest++) + { + BS3CPUINSTR3_TEST3_VALUES_T const BS3_FAR *paValues = paTests[iTest].paValues; + uint8_t const cbInstr = ((uint8_t const BS3_FAR *)(uintptr_t)paTests[iTest].pfnWorker)[-1]; + unsigned const cValues = paTests[iTest].cValues; + bool const fMmxInstr = paTests[iTest].enmType < T_SSE; + bool const fSseInstr = paTests[iTest].enmType >= T_SSE && paTests[iTest].enmType < T_AVX_128; + bool const fAvxInstr = paTests[iTest].enmType >= T_AVX_128; + uint8_t const cbOperand = paTests[iTest].enmType < T_128BITS ? 64/8 + : paTests[iTest].enmType < T_256BITS ? 128/8 : 256/8; + uint8_t const cbMemOp = cbOperand; + uint8_t const cbAlign = RT_MIN(cbOperand, !cbMaxAlign ? 16 : cbMaxAlign); + PRTUINT256U puMemOp = bs3CpuInstr3BufForOperand(pbBuf, cbBuf, cbMemOp, cbAlign, &paConfigs[iCfg]); + uint8_t bXcptExpect = !g_afTypeSupports[paTests[iTest].enmType] ? X86_XCPT_UD + : fMmxInstr ? paConfigs[iCfg].bXcptMmx + : fSseInstr ? paConfigs[iCfg].bXcptSse + : BS3_MODE_IS_RM_OR_V86(bMode) ? X86_XCPT_UD : paConfigs[iCfg].bXcptAvx; + uint16_t idTestStep = bRing * 10000 + iCfg * 100 + iTest * 10; + unsigned iVal; + + /* If testing unaligned memory accesses, skip register-only tests. This allows + setting bXcptMmx, bXcptSse and bXcptAvx to reflect the misaligned exceptions. */ + if (paTests[iTest].enmRm == RM_REG && (!paConfigs[iCfg].fAligned || paConfigs[iCfg].fAlignCheck)) + continue; + + /* #AC is only raised in ring-3.: */ + if (bXcptExpect == X86_XCPT_AC) + { + if (bRing != 3) + bXcptExpect = X86_XCPT_DB; + else if (fAvxInstr) + bXcptExpect = paTests[iTest].bAvxMisalignXcpt; /* they generally don't raise #AC */ + } + + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, paTests[iTest].pfnWorker); + + /* + * Iterate the test values and do the actual testing. + */ + for (iVal = 0; iVal < cValues; iVal++, idTestStep++) + { + uint16_t cErrors; + uint16_t uSavedFtw = 0xff; + RTUINT256U uMemOpExpect; + + /* + * Set up the context and some expectations. + */ + /* dest */ + if (paTests[iTest].iRegDst == UINT8_MAX) + { + BS3_ASSERT(paTests[iTest].enmRm >= RM_MEM); + Bs3MemSet(puMemOp, 0xcc, cbMemOp); + if (bXcptExpect == X86_XCPT_DB) + uMemOpExpect = paValues[iVal].uDstOut; + else + Bs3MemSet(&uMemOpExpect, 0xcc, sizeof(uMemOpExpect)); + } + else if (fMmxInstr) + Bs3ExtCtxSetMm(pExtCtx, paTests[iTest].iRegSrc, ~paValues[iVal].uDstOut.QWords.qw0, BS3EXTCTXTOPMM_ZERO); + + /* source */ + if (paTests[iTest].iRegSrc == UINT8_MAX) + { + BS3_ASSERT(paTests[iTest].enmRm >= RM_MEM); + BS3_ASSERT(paTests[iTest].iRegDst != UINT8_MAX); + Bs3MemCpy(puMemOp, &paValues[iVal].uSrc, cbMemOp); + uMemOpExpect = paValues[iVal].uSrc; + } + else if (fMmxInstr) + Bs3ExtCtxSetMm(pExtCtx, paTests[iTest].iRegSrc, paValues[iVal].uSrc.QWords.qw0, BS3EXTCTXTOPMM_ZERO); + else if (fSseInstr) + Bs3ExtCtxSetXmm(pExtCtx, paTests[iTest].iRegSrc, &paValues[iVal].uSrc.DQWords.dqw0); + else + Bs3ExtCtxSetYmm(pExtCtx, paTests[iTest].iRegSrc, &paValues[iVal].uSrc, 32); + + /* Memory pointer. */ + if (paTests[iTest].enmRm >= RM_MEM) + { + BS3_ASSERT( paTests[iTest].iRegDst == UINT8_MAX + || paTests[iTest].iRegSrc == UINT8_MAX); + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, &Ctx.fs, puMemOp); + } + + /* + * Execute. + */ + Bs3TrapSetJmpAndRestoreWithExtCtxAndRm(&Ctx, pExtCtx, &TrapFrame, pExtCtxOut); + + /* + * Check the result: + */ + cErrors = Bs3TestSubErrorCount(); + + if (bXcptExpect == X86_XCPT_DB && fMmxInstr) + { + uSavedFtw = Bs3ExtCtxGetAbridgedFtw(pExtCtx); + Bs3ExtCtxSetAbridgedFtw(pExtCtx, 0xff); + } + if (bXcptExpect == X86_XCPT_DB && paTests[iTest].iRegDst != UINT8_MAX) + { + if (fMmxInstr) + Bs3ExtCtxSetMm(pExtCtx, paTests[iTest].iRegDst, paValues[iVal].uDstOut.QWords.qw0, BS3EXTCTXTOPMM_SET); + else if (fSseInstr) + Bs3ExtCtxSetXmm(pExtCtx, paTests[iTest].iRegDst, &paValues[iVal].uDstOut.DQWords.dqw0); + else + Bs3ExtCtxSetYmm(pExtCtx, paTests[iTest].iRegDst, &paValues[iVal].uDstOut, cbOperand); + } + Bs3TestCheckExtCtx(pExtCtxOut, pExtCtx, 0 /*fFlags*/, pszMode, idTestStep); + + if (TrapFrame.bXcpt != bXcptExpect) + Bs3TestFailedF("Expected bXcpt = %#x, got %#x", bXcptExpect, TrapFrame.bXcpt); + + /* Kludge! Looks like EFLAGS.AC is cleared when raising #GP in real mode on the 10980XE. WEIRD! */ + if (bMode == BS3_MODE_RM && (Ctx.rflags.u32 & X86_EFL_AC)) + { + if (TrapFrame.Ctx.rflags.u32 & X86_EFL_AC) + Bs3TestFailedF("Expected EFLAGS.AC to be cleared (bXcpt=%d)", TrapFrame.bXcpt); + TrapFrame.Ctx.rflags.u32 |= X86_EFL_AC; + } + Bs3TestCheckRegCtxEx(&TrapFrame.Ctx, &Ctx, bXcptExpect == X86_XCPT_DB ? cbInstr + 1 : 0, 0, + bXcptExpect == X86_XCPT_DB || BS3_MODE_IS_16BIT_SYS(bMode) ? 0 : X86_EFL_RF, + pszMode, idTestStep); + + if ( paTests[iTest].enmRm >= RM_MEM + && Bs3MemCmp(puMemOp, &uMemOpExpect, cbMemOp) != 0) + Bs3TestFailedF("Expected uMemOp %.*Rhxs, got %.*Rhxs", cbMemOp, &uMemOpExpect, cbMemOp, puMemOp); + + if (cErrors != Bs3TestSubErrorCount()) + { + if (paConfigs[iCfg].fAligned) + Bs3TestFailedF("ring-%d/cfg#%u/test#%u/value#%u failed (bXcptExpect=%#x)", + bRing, iCfg, iTest, iVal, bXcptExpect); + else + Bs3TestFailedF("ring-%d/cfg#%u/test#%u/value#%u failed (bXcptExpect=%#x, puMemOp=%p, EFLAGS=%#RX32, CR0=%#RX32)", + bRing, iCfg, iTest, iVal, bXcptExpect, puMemOp, TrapFrame.Ctx.rflags.u32, TrapFrame.Ctx.cr0); + Bs3TestPrintf("\n"); + } + + if (uSavedFtw != 0xff) + Bs3ExtCtxSetAbridgedFtw(pExtCtx, uSavedFtw); + } + } + + bs3CpuInstr3ConfigRestore(&SavedCfg, &Ctx, pExtCtx); + } + + /* + * Next ring. + */ + bRing++; + if (bRing > 3 || bMode == BS3_MODE_RM) + break; + Bs3RegCtxConvertToRingX(&Ctx, bRing); + } + + /* + * Cleanup. + */ + bs3CpuInstr3BufCleanup(pbBuf, cbBuf, bMode); + bs3CpuInstr3FreeExtCtxs(pExtCtx, pExtCtxOut); + return 0; +} + + +/* + * PSHUFW + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshufw_MM1_MM2_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshufw_MM1_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshufw_MM1_MM2_01Bh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshufw_MM1_FSxBX_01Bh_icebp); + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_pshufw(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValuesFF[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0, 0, 0, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0, 0, 0, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0x5555555555555555) }, + { RTUINT256_INIT_C(0, 0, 0, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0x9c5c9c5c9c5c9c5c) }, + }; + + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValues1B[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0, 0, 0, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0, 0, 0, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0x8888777766665555) }, + { RTUINT256_INIT_C(0, 0, 0, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0x96bb9309e0739c5c) }, + }; + + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_pshufw_MM1_MM2_0FFh_icebp_c16, 255, RM_REG, T_AXMMX_OR_SSE, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshufw_MM1_FSxBX_0FFh_icebp_c16, 255, RM_MEM, T_AXMMX_OR_SSE, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshufw_MM1_MM2_01Bh_icebp_c16, 255, RM_REG, T_AXMMX_OR_SSE, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_pshufw_MM1_FSxBX_01Bh_icebp_c16, 255, RM_MEM, T_AXMMX_OR_SSE, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_pshufw_MM1_MM2_0FFh_icebp_c32, 255, RM_REG, T_AXMMX_OR_SSE, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshufw_MM1_FSxBX_0FFh_icebp_c32, 255, RM_MEM, T_AXMMX_OR_SSE, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshufw_MM1_MM2_01Bh_icebp_c32, 255, RM_REG, T_AXMMX_OR_SSE, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_pshufw_MM1_FSxBX_01Bh_icebp_c32, 255, RM_MEM, T_AXMMX_OR_SSE, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_pshufw_MM1_MM2_0FFh_icebp_c64, 255, RM_REG, T_AXMMX_OR_SSE, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshufw_MM1_FSxBX_0FFh_icebp_c64, 255, RM_MEM, T_AXMMX_OR_SSE, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshufw_MM1_MM2_01Bh_icebp_c64, 255, RM_REG, T_AXMMX_OR_SSE, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_pshufw_MM1_FSxBX_01Bh_icebp_c64, 255, RM_MEM, T_AXMMX_OR_SSE, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4), 0 /*cbMaxAlign*/); +} + + +/* + * [V]PSHUFHW + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshufhw_XMM1_XMM2_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshufhw_XMM1_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshufhw_XMM1_XMM2_01Bh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshufhw_XMM1_FSxBX_01Bh_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufhw_XMM1_XMM2_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufhw_XMM1_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufhw_XMM1_XMM2_01Bh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufhw_XMM1_FSxBX_01Bh_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufhw_YMM1_YMM2_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufhw_YMM1_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufhw_YMM1_YMM2_01Bh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufhw_YMM1_FSxBX_01Bh_icebp); +extern FNBS3FAR bs3CpuInstr3_vpshufhw_YMM12_YMM7_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpshufhw_YMM9_YMM12_01Bh_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pshufhw(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValuesFF[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x5555555555555555, 0x1111222233334444, 0x1111111111111111, 0x5555666677778888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0x4d094d094d094d09, 0x3ef417c8666b3fe6, 0xb421b421b421b421, 0x9c5ce073930996bb) }, + }; + + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValues1B[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x8888777766665555, 0x1111222233334444, 0x4444333322221111, 0x5555666677778888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0x73d56cdcf02a4d09, 0x3ef417c8666b3fe6, 0x9ba2564c2fa8b421, 0x9c5ce073930996bb) }, + }; + + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_pshufhw_XMM1_XMM2_0FFh_icebp_c16, 255, RM_REG, T_SSE2, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshufhw_XMM1_FSxBX_0FFh_icebp_c16, 255, RM_MEM, T_SSE2, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshufhw_XMM1_XMM2_01Bh_icebp_c16, 255, RM_REG, T_SSE2, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_pshufhw_XMM1_FSxBX_01Bh_icebp_c16, 255, RM_MEM, T_SSE2, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + + { bs3CpuInstr3_vpshufhw_XMM1_XMM2_0FFh_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufhw_XMM1_FSxBX_0FFh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufhw_XMM1_XMM2_01Bh_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshufhw_XMM1_FSxBX_01Bh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + + { bs3CpuInstr3_vpshufhw_YMM1_YMM2_0FFh_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufhw_YMM1_FSxBX_0FFh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufhw_YMM1_YMM2_01Bh_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshufhw_YMM1_FSxBX_01Bh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_pshufhw_XMM1_XMM2_0FFh_icebp_c32, 255, RM_REG, T_SSE2, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshufhw_XMM1_FSxBX_0FFh_icebp_c32, 255, RM_MEM, T_SSE2, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshufhw_XMM1_XMM2_01Bh_icebp_c32, 255, RM_REG, T_SSE2, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_pshufhw_XMM1_FSxBX_01Bh_icebp_c32, 255, RM_MEM, T_SSE2, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + + { bs3CpuInstr3_vpshufhw_XMM1_XMM2_0FFh_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufhw_XMM1_FSxBX_0FFh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufhw_XMM1_XMM2_01Bh_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshufhw_XMM1_FSxBX_01Bh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + + { bs3CpuInstr3_vpshufhw_YMM1_YMM2_0FFh_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufhw_YMM1_FSxBX_0FFh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufhw_YMM1_YMM2_01Bh_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshufhw_YMM1_FSxBX_01Bh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_pshufhw_XMM1_XMM2_0FFh_icebp_c64, 255, RM_REG, T_SSE2, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshufhw_XMM1_FSxBX_0FFh_icebp_c64, 255, RM_MEM, T_SSE2, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshufhw_XMM1_XMM2_01Bh_icebp_c64, 255, RM_REG, T_SSE2, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_pshufhw_XMM1_FSxBX_01Bh_icebp_c64, 255, RM_MEM, T_SSE2, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + + { bs3CpuInstr3_vpshufhw_XMM1_XMM2_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufhw_XMM1_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufhw_XMM1_XMM2_01Bh_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshufhw_XMM1_FSxBX_01Bh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + + { bs3CpuInstr3_vpshufhw_YMM1_YMM2_0FFh_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufhw_YMM1_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufhw_YMM1_YMM2_01Bh_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshufhw_YMM1_FSxBX_01Bh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshufhw_YMM12_YMM7_0FFh_icebp_c64, 255, RM_REG, T_AVX2_256, 12, 7, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufhw_YMM9_YMM12_01Bh_icebp_c64, 255, RM_REG, T_AVX2_256, 9, 12, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4), 0 /*cbMaxAlign*/); +} + + +/* + * [V]PSHUFLW + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshuflw_XMM1_XMM2_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshuflw_XMM1_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshuflw_XMM1_XMM2_01Bh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshuflw_XMM1_FSxBX_01Bh_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshuflw_XMM1_XMM2_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshuflw_XMM1_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshuflw_XMM1_XMM2_01Bh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshuflw_XMM1_FSxBX_01Bh_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshuflw_YMM1_YMM2_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshuflw_YMM1_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshuflw_YMM1_YMM2_01Bh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshuflw_YMM1_FSxBX_01Bh_icebp); +extern FNBS3FAR bs3CpuInstr3_vpshuflw_YMM12_YMM7_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpshuflw_YMM9_YMM12_01Bh_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pshuflw(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValuesFF[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x5555666677778888, 0x1111111111111111, 0x1111222233334444, 0x5555555555555555) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef43ef43ef43ef4, 0xb4212fa8564c9ba2, 0x9c5c9c5c9c5c9c5c) }, + }; + + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValues1B[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x5555666677778888, 0x4444333322221111, 0x1111222233334444, 0x8888777766665555) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3fe6666b17c83ef4, 0xb4212fa8564c9ba2, 0x96bb9309e0739c5c) }, + }; + + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_pshuflw_XMM1_XMM2_0FFh_icebp_c16, 255, RM_REG, T_SSE2, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshuflw_XMM1_FSxBX_0FFh_icebp_c16, 255, RM_MEM, T_SSE2, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshuflw_XMM1_XMM2_01Bh_icebp_c16, 255, RM_REG, T_SSE2, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_pshuflw_XMM1_FSxBX_01Bh_icebp_c16, 255, RM_MEM, T_SSE2, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + + { bs3CpuInstr3_vpshuflw_XMM1_XMM2_0FFh_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshuflw_XMM1_FSxBX_0FFh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshuflw_XMM1_XMM2_01Bh_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshuflw_XMM1_FSxBX_01Bh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + + { bs3CpuInstr3_vpshuflw_YMM1_YMM2_0FFh_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshuflw_YMM1_FSxBX_0FFh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshuflw_YMM1_YMM2_01Bh_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshuflw_YMM1_FSxBX_01Bh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_pshuflw_XMM1_XMM2_0FFh_icebp_c32, 255, RM_REG, T_SSE2, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshuflw_XMM1_FSxBX_0FFh_icebp_c32, 255, RM_MEM, T_SSE2, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshuflw_XMM1_XMM2_01Bh_icebp_c32, 255, RM_REG, T_SSE2, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_pshuflw_XMM1_FSxBX_01Bh_icebp_c32, 255, RM_MEM, T_SSE2, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + + { bs3CpuInstr3_vpshuflw_XMM1_XMM2_0FFh_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshuflw_XMM1_FSxBX_0FFh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshuflw_XMM1_XMM2_01Bh_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshuflw_XMM1_FSxBX_01Bh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + + { bs3CpuInstr3_vpshuflw_YMM1_YMM2_0FFh_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshuflw_YMM1_FSxBX_0FFh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshuflw_YMM1_YMM2_01Bh_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshuflw_YMM1_FSxBX_01Bh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_pshuflw_XMM1_XMM2_0FFh_icebp_c64, 255, RM_REG, T_SSE2, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshuflw_XMM1_FSxBX_0FFh_icebp_c64, 255, RM_MEM, T_SSE2, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshuflw_XMM1_XMM2_01Bh_icebp_c64, 255, RM_REG, T_SSE2, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_pshuflw_XMM1_FSxBX_01Bh_icebp_c64, 255, RM_MEM, T_SSE2, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + + { bs3CpuInstr3_vpshuflw_XMM1_XMM2_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshuflw_XMM1_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshuflw_XMM1_XMM2_01Bh_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshuflw_XMM1_FSxBX_01Bh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + + { bs3CpuInstr3_vpshuflw_YMM1_YMM2_0FFh_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshuflw_YMM1_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshuflw_YMM1_YMM2_01Bh_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshuflw_YMM1_FSxBX_01Bh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshuflw_YMM12_YMM7_0FFh_icebp_c64, 255, RM_REG, T_AVX2_256, 12, 7, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshuflw_YMM9_YMM12_01Bh_icebp_c64, 255, RM_REG, T_AVX2_256, 9, 12, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4), 0 /*cbMaxAlign*/); +} + + +/* + * [V]PSHUFD + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshufd_XMM1_XMM2_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshufd_XMM1_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshufd_XMM1_XMM2_01Bh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pshufd_XMM1_FSxBX_01Bh_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufd_XMM1_XMM2_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufd_XMM1_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufd_XMM1_XMM2_01Bh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufd_XMM1_FSxBX_01Bh_icebp); + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufd_YMM1_YMM2_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufd_YMM1_FSxBX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufd_YMM1_YMM2_01Bh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpshufd_YMM1_FSxBX_01Bh_icebp); +extern FNBS3FAR bs3CpuInstr3_vpshufd_YMM12_YMM7_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpshufd_YMM9_YMM12_01Bh_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pshufd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValuesFF[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x5555666655556666, 0x5555666655556666, 0x1111222211112222, 0x1111222211112222) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0x4d09f02a4d09f02a, 0x4d09f02a4d09f02a, 0xb4212fa8b4212fa8, 0xb4212fa8b4212fa8) }, + }; + + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValues1B[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x3333444411112222, 0x7777888855556666, 0x7777888855556666, 0x3333444411112222) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0x666b3fe63ef417c8, 0x6cdc73d54d09f02a, 0x930996bb9c5ce073, 0x564c9ba2b4212fa8) }, + }; + + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_pshufd_XMM1_XMM2_0FFh_icebp_c16, 255, RM_REG, T_SSE2, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshufd_XMM1_FSxBX_0FFh_icebp_c16, 255, RM_MEM, T_SSE2, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshufd_XMM1_XMM2_01Bh_icebp_c16, 255, RM_REG, T_SSE2, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_pshufd_XMM1_FSxBX_01Bh_icebp_c16, 255, RM_MEM, T_SSE2, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + + { bs3CpuInstr3_vpshufd_XMM1_XMM2_0FFh_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufd_XMM1_FSxBX_0FFh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufd_XMM1_XMM2_01Bh_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshufd_XMM1_FSxBX_01Bh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + + { bs3CpuInstr3_vpshufd_YMM1_YMM2_0FFh_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufd_YMM1_FSxBX_0FFh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufd_YMM1_YMM2_01Bh_icebp_c16, 255, RM_REG, T_AVX2_256, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshufd_YMM1_FSxBX_01Bh_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_pshufd_XMM1_XMM2_0FFh_icebp_c32, 255, RM_REG, T_SSE2, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshufd_XMM1_FSxBX_0FFh_icebp_c32, 255, RM_MEM, T_SSE2, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshufd_XMM1_XMM2_01Bh_icebp_c32, 255, RM_REG, T_SSE2, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_pshufd_XMM1_FSxBX_01Bh_icebp_c32, 255, RM_MEM, T_SSE2, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + + { bs3CpuInstr3_vpshufd_XMM1_XMM2_0FFh_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufd_XMM1_FSxBX_0FFh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufd_XMM1_XMM2_01Bh_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshufd_XMM1_FSxBX_01Bh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + + { bs3CpuInstr3_vpshufd_YMM1_YMM2_0FFh_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufd_YMM1_FSxBX_0FFh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufd_YMM1_YMM2_01Bh_icebp_c32, 255, RM_REG, T_AVX2_256, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshufd_YMM1_FSxBX_01Bh_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_pshufd_XMM1_XMM2_0FFh_icebp_c64, 255, RM_REG, T_SSE2, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshufd_XMM1_FSxBX_0FFh_icebp_c64, 255, RM_MEM, T_SSE2, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pshufd_XMM1_XMM2_01Bh_icebp_c64, 255, RM_REG, T_SSE2, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_pshufd_XMM1_FSxBX_01Bh_icebp_c64, 255, RM_MEM, T_SSE2, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + + { bs3CpuInstr3_vpshufd_XMM1_XMM2_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufd_XMM1_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufd_XMM1_XMM2_01Bh_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshufd_XMM1_FSxBX_01Bh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + + { bs3CpuInstr3_vpshufd_YMM1_YMM2_0FFh_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufd_YMM1_FSxBX_0FFh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufd_YMM1_YMM2_01Bh_icebp_c64, 255, RM_REG, T_AVX2_256, 1, 2, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshufd_YMM1_FSxBX_01Bh_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + { bs3CpuInstr3_vpshufd_YMM12_YMM7_0FFh_icebp_c64, 255, RM_REG, T_AVX2_256, 12, 7, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpshufd_YMM9_YMM12_01Bh_icebp_c64, 255, RM_REG, T_AVX2_256, 9, 12, RT_ELEMENTS(s_aValues1B), s_aValues1B }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4), 0 /*cbMaxAlign*/); +} + + +/** + * Values shared by the move functions (same input as output). + */ +static BS3CPUINSTR3_TEST3_VALUES_T const g_aMoveValues3[] = +{ + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb) }, +}; + + +/* + * MOVNTDQA - load double qword, strictly aligned, with non-temporal hint. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movntdqa_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_movntdqa_XMM10_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovntdqa_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovntdqa_XMM11_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovntdqa_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovntdqa_YMM12_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_movntdqa(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_movntdqa_XMM1_FSxBX_icebp_c16, X86_XCPT_GP, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntdqa_XMM1_FSxBX_icebp_c16, X86_XCPT_GP, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntdqa_YMM1_FSxBX_icebp_c16, X86_XCPT_GP, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_movntdqa_XMM1_FSxBX_icebp_c32, X86_XCPT_GP, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntdqa_XMM1_FSxBX_icebp_c32, X86_XCPT_GP, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntdqa_YMM1_FSxBX_icebp_c32, X86_XCPT_GP, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_movntdqa_XMM1_FSxBX_icebp_c64, X86_XCPT_GP, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movntdqa_XMM10_FSxBX_icebp_c64, X86_XCPT_GP, RM_MEM, T_SSE4_1, 10, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntdqa_XMM1_FSxBX_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntdqa_XMM11_FSxBX_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_128, 11, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntdqa_YMM1_FSxBX_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX2_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntdqa_YMM12_FSxBX_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX2_256, 12, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig1, RT_ELEMENTS(g_aXcptConfig1), 255 /*cbMaxAlign*/); +} + + +/* + * MOVNTDQ - store double qword, strictly aligned, with non-temporal hint. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movntdq_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_movntdq_FSxBX_XMM10_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovntdq_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovntdq_FSxBX_XMM10_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovntdq_FSxBX_YMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovntdq_FSxBX_YMM10_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_movntdq(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_movntdq_FSxBX_XMM1_icebp_c16, X86_XCPT_GP, RM_MEM, T_SSE2, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntdq_FSxBX_XMM1_icebp_c16, X86_XCPT_GP, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntdq_FSxBX_YMM1_icebp_c16, X86_XCPT_GP, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_movntdq_FSxBX_XMM1_icebp_c32, X86_XCPT_GP, RM_MEM, T_SSE2, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntdq_FSxBX_XMM1_icebp_c32, X86_XCPT_GP, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntdq_FSxBX_YMM1_icebp_c32, X86_XCPT_GP, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_movntdq_FSxBX_XMM1_icebp_c64, X86_XCPT_GP, RM_MEM, T_SSE2, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movntdq_FSxBX_XMM10_icebp_c64, X86_XCPT_GP, RM_MEM, T_SSE2, 255, 10, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntdq_FSxBX_XMM1_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntdq_FSxBX_XMM10_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_128, 255, 10, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntdq_FSxBX_YMM1_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntdq_FSxBX_YMM10_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_256, 255, 10, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig1, RT_ELEMENTS(g_aXcptConfig1), 255 /*cbMaxAlign*/); +} + + +/* + * [V]MOVNPS / [V]MOVNTPD - load single/double precision floating-point, aligned, + * with non-temporal hint. Only difference is the unit. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movntps_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_movntps_FSxBX_XMM10_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovntps_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovntps_FSxBX_XMM11_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovntps_FSxBX_YMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovntps_FSxBX_YMM12_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movntpd_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_movntpd_FSxBX_XMM10_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovntpd_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovntpd_FSxBX_XMM11_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovntpd_FSxBX_YMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovntpd_FSxBX_YMM12_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_movntps_movntpd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_movntps_FSxBX_XMM1_icebp_c16, X86_XCPT_GP, RM_MEM, T_SSE, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntps_FSxBX_XMM1_icebp_c16, X86_XCPT_GP, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntps_FSxBX_YMM1_icebp_c16, X86_XCPT_GP, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_movntpd_FSxBX_XMM1_icebp_c16, X86_XCPT_GP, RM_MEM, T_SSE, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntpd_FSxBX_XMM1_icebp_c16, X86_XCPT_GP, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntpd_FSxBX_YMM1_icebp_c16, X86_XCPT_GP, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_movntps_FSxBX_XMM1_icebp_c32, X86_XCPT_GP, RM_MEM, T_SSE, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntps_FSxBX_XMM1_icebp_c32, X86_XCPT_GP, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntps_FSxBX_YMM1_icebp_c32, X86_XCPT_GP, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_movntpd_FSxBX_XMM1_icebp_c32, X86_XCPT_GP, RM_MEM, T_SSE, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntpd_FSxBX_XMM1_icebp_c32, X86_XCPT_GP, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntpd_FSxBX_YMM1_icebp_c32, X86_XCPT_GP, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_movntps_FSxBX_XMM1_icebp_c64, X86_XCPT_GP, RM_MEM, T_SSE, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movntps_FSxBX_XMM10_icebp_c64, X86_XCPT_GP, RM_MEM, T_SSE, 255, 10, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntps_FSxBX_XMM1_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntps_FSxBX_XMM11_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_128, 255, 11, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntps_FSxBX_YMM1_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntps_FSxBX_YMM12_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_256, 255, 12, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_movntpd_FSxBX_XMM1_icebp_c64, X86_XCPT_GP, RM_MEM, T_SSE, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movntpd_FSxBX_XMM10_icebp_c64, X86_XCPT_GP, RM_MEM, T_SSE, 255, 10, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntpd_FSxBX_XMM1_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntpd_FSxBX_XMM11_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_128, 255, 11, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntpd_FSxBX_YMM1_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovntpd_FSxBX_YMM12_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_256, 255, 12, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig1, RT_ELEMENTS(g_aXcptConfig1), 255 /*cbMaxAlign*/); +} + + +/* + * MOVUPS - packed single-precision floating point, unaligned. + * + * Note! We only cover one of the two register<->register variants here + * thanks to the assembler (probably the one with the smaller opcode). + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movups_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movups_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_movups_XMM8_XMM12_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movups_XMM10_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovups_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovups_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovups_XMM7_XMM14_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovups_XMM11_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovups_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovups_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovups_YMM12_YMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovups_YMM12_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movups_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_movups_FSxBX_XMM10_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovups_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovups_FSxBX_XMM11_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovups_FSxBX_YMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovups_FSxBX_YMM12_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_movups(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_movups_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movups_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_SSE, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movups_FSxBX_XMM1_icebp_c16, X86_XCPT_DB, RM_MEM, T_SSE, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovups_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovups_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovups_FSxBX_XMM1_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovups_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovups_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovups_FSxBX_YMM1_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_movups_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movups_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_SSE, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movups_FSxBX_XMM1_icebp_c32, X86_XCPT_DB, RM_MEM, T_SSE, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovups_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovups_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovups_FSxBX_XMM1_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovups_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovups_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovups_FSxBX_YMM1_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_movups_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movups_XMM8_XMM12_icebp_c64, 255, RM_REG, T_SSE, 8, 12, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movups_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movups_XMM10_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE, 10, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movups_FSxBX_XMM1_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movups_FSxBX_XMM10_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE, 255, 10, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovups_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovups_XMM7_XMM14_icebp_c64, 255, RM_REG, T_AVX_128, 7, 14, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovups_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovups_XMM11_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 11, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovups_FSxBX_XMM1_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovups_FSxBX_XMM11_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 255, 11, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovups_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovups_YMM12_YMM8_icebp_c64, 255, RM_REG, T_AVX_256, 12, 8, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovups_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovups_YMM12_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 12, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovups_FSxBX_YMM1_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovups_FSxBX_YMM12_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 255, 12, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4Unaligned, RT_ELEMENTS(g_aXcptConfig4Unaligned), 0 /*cbMaxAlign*/); +} + + +/* + * MOVUPD - packed double-precision floating point, unaligned. + * + * Note! We only cover one of the two register<->register variants here + * thanks to the assembler (probably the one with the smaller opcode). + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movupd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movupd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_movupd_XMM8_XMM12_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movupd_XMM10_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovupd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovupd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovupd_XMM7_XMM14_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovupd_XMM11_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovupd_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovupd_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovupd_YMM12_YMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovupd_YMM12_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movupd_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_movupd_FSxBX_XMM10_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovupd_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovupd_FSxBX_XMM11_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovupd_FSxBX_YMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovupd_FSxBX_YMM12_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_movupd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_movupd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movupd_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_SSE, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movupd_FSxBX_XMM1_icebp_c16, X86_XCPT_DB, RM_MEM, T_SSE, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovupd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovupd_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovupd_FSxBX_XMM1_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovupd_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovupd_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovupd_FSxBX_YMM1_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_movupd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movupd_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_SSE, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movupd_FSxBX_XMM1_icebp_c32, X86_XCPT_DB, RM_MEM, T_SSE, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovupd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovupd_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovupd_FSxBX_XMM1_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovupd_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovupd_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovupd_FSxBX_YMM1_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_movupd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movupd_XMM8_XMM12_icebp_c64, 255, RM_REG, T_SSE, 8, 12, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movupd_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movupd_XMM10_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE, 10, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movupd_FSxBX_XMM1_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movupd_FSxBX_XMM10_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE, 255, 10, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovupd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovupd_XMM7_XMM14_icebp_c64, 255, RM_REG, T_AVX_128, 7, 14, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovupd_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovupd_XMM11_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 11, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovupd_FSxBX_XMM1_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovupd_FSxBX_XMM11_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 255, 11, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovupd_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovupd_YMM12_YMM8_icebp_c64, 255, RM_REG, T_AVX_256, 12, 8, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovupd_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovupd_YMM12_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 12, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovupd_FSxBX_YMM1_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovupd_FSxBX_YMM12_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 255, 12, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4Unaligned, RT_ELEMENTS(g_aXcptConfig4Unaligned), 0 /*cbMaxAlign*/); +} + + +/* + * [V]MOVSLDUP - Duplicate even single precision floating-point values. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movsldup_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movsldup_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_movsldup_XMM8_XMM12_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movsldup_XMM10_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovsldup_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovsldup_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovsldup_XMM7_XMM14_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovsldup_XMM11_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovsldup_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovsldup_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovsldup_YMM12_YMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovsldup_YMM12_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_movsldup(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValues[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0xbbbbccccbbbbcccc, 0xffff2121ffff2121, 0x3333444433334444, 0x7777888877778888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0x6cdc73d56cdc73d5, 0x666b3fe6666b3fe6, 0x564c9ba2564c9ba2, 0x930996bb930996bb) }, + }; + + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_movsldup_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE3, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_movsldup_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_SSE3, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vmovsldup_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovsldup_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vmovsldup_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovsldup_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_movsldup_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE3, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_movsldup_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_SSE3, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vmovsldup_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovsldup_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vmovsldup_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovsldup_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_movsldup_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE3, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_movsldup_XMM8_XMM12_icebp_c64, 255, RM_REG, T_SSE3, 8, 12, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_movsldup_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE3, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_movsldup_XMM10_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE3, 10, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vmovsldup_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovsldup_XMM7_XMM14_icebp_c64, 255, RM_REG, T_AVX_128, 7, 14, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovsldup_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovsldup_XMM11_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 11, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vmovsldup_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovsldup_YMM12_YMM8_icebp_c64, 255, RM_REG, T_AVX_256, 12, 8, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovsldup_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovsldup_YMM12_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 12, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4), 0 /*cbMaxAlign*/); +} + + +/* + * [V]MOVSHDUP - Duplicate even single precision floating-point values. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movshdup_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movshdup_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_movshdup_XMM8_XMM12_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movshdup_XMM10_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovshdup_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovshdup_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovshdup_XMM7_XMM14_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovshdup_XMM11_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovshdup_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovshdup_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovshdup_YMM12_YMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovshdup_YMM12_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_movshdup(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValues[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x9999aaaa9999aaaa, 0xddddeeeeddddeeee, 0x1111222211112222, 0x5555666655556666) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0x4d09f02a4d09f02a, 0x3ef417c83ef417c8, 0xb4212fa8b4212fa8, 0x9c5ce0739c5ce073) }, + }; + + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_movshdup_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE3, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_movshdup_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_SSE3, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vmovshdup_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovshdup_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vmovshdup_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovshdup_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_movshdup_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE3, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_movshdup_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_SSE3, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vmovshdup_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovshdup_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vmovshdup_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovshdup_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_movshdup_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE3, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_movshdup_XMM8_XMM12_icebp_c64, 255, RM_REG, T_SSE3, 8, 12, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_movshdup_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE3, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_movshdup_XMM10_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE3, 10, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vmovshdup_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovshdup_XMM7_XMM14_icebp_c64, 255, RM_REG, T_AVX_128, 7, 14, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovshdup_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovshdup_XMM11_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 11, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vmovshdup_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovshdup_YMM12_YMM8_icebp_c64, 255, RM_REG, T_AVX_256, 12, 8, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovshdup_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovshdup_YMM12_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 12, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4), 0 /*cbMaxAlign*/); +} + + +/* + * [V]MOVDDUP - Duplicate even single precision floating-point values. + * + * Similar to MOVSLDUP, but different exception class and unit size. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movddup_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movddup_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_movddup_XMM8_XMM12_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movddup_XMM10_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovddup_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovddup_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovddup_XMM7_XMM14_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovddup_XMM11_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovddup_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovddup_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovddup_YMM12_YMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovddup_YMM12_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_movddup(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValues[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0xddddeeeeffff2121, 0xddddeeeeffff2121, 0x5555666677778888, 0x5555666677778888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0x3ef417c8666b3fe6, 0x3ef417c8666b3fe6, 0x9c5ce073930996bb, 0x9c5ce073930996bb) }, + }; + + /* Note! Seems the 256-bit variants doesn't generate \#ACs on a 10980XE. WEIRD! */ + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_movddup_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE3, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_movddup_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_SSE3, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vmovddup_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovddup_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vmovddup_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovddup_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_movddup_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE3, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_movddup_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_SSE3, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vmovddup_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovddup_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vmovddup_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovddup_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_movddup_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE3, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_movddup_XMM8_XMM12_icebp_c64, 255, RM_REG, T_SSE3, 8, 12, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_movddup_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE3, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_movddup_XMM10_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE3, 10, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vmovddup_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovddup_XMM7_XMM14_icebp_c64, 255, RM_REG, T_AVX_128, 7, 14, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovddup_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovddup_XMM11_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 11, 255, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vmovddup_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovddup_YMM12_YMM8_icebp_c64, 255, RM_REG, T_AVX_256, 12, 8, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovddup_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vmovddup_YMM12_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 12, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig5, RT_ELEMENTS(g_aXcptConfig5), 0 /*cbMaxAlign*/); +} + + +/* + * [V]MOVAPS / [V]MOVAPD + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movaps_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movaps_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_movaps_XMM8_XMM12_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movaps_XMM10_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovaps_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovaps_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovaps_XMM7_XMM14_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovaps_XMM11_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovaps_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovaps_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovaps_YMM12_YMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovaps_YMM12_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movapd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movapd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_movapd_XMM8_XMM12_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movapd_XMM10_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovapd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovapd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovapd_XMM7_XMM14_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovapd_XMM11_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovapd_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovapd_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovapd_YMM12_YMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovapd_YMM12_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_movaps_movapd(uint8_t bMode) +{ + /* Note! Seems the 256-bit variants doesn't generate \#ACs on a 10980XE. WEIRD! */ + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_movaps_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movaps_XMM1_FSxBX_icebp_c16, X86_XCPT_GP, RM_MEM, T_SSE, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movapd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movapd_XMM1_FSxBX_icebp_c16, X86_XCPT_GP, RM_MEM, T_SSE, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovaps_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovaps_XMM1_FSxBX_icebp_c16, X86_XCPT_GP, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovapd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovapd_XMM1_FSxBX_icebp_c16, X86_XCPT_GP, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovaps_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovaps_YMM1_FSxBX_icebp_c16, X86_XCPT_GP, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovapd_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovapd_YMM1_FSxBX_icebp_c16, X86_XCPT_GP, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_movaps_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movaps_XMM1_FSxBX_icebp_c32, X86_XCPT_GP, RM_MEM, T_SSE, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movapd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movapd_XMM1_FSxBX_icebp_c32, X86_XCPT_GP, RM_MEM, T_SSE, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovaps_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovaps_XMM1_FSxBX_icebp_c32, X86_XCPT_GP, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovapd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovapd_XMM1_FSxBX_icebp_c32, X86_XCPT_GP, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovaps_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovaps_YMM1_FSxBX_icebp_c32, X86_XCPT_GP, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovapd_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovapd_YMM1_FSxBX_icebp_c32, X86_XCPT_GP, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_movaps_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movaps_XMM8_XMM12_icebp_c64, 255, RM_REG, T_SSE, 8, 12, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movaps_XMM1_FSxBX_icebp_c64, X86_XCPT_GP, RM_MEM, T_SSE, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movaps_XMM10_FSxBX_icebp_c64, X86_XCPT_GP, RM_MEM, T_SSE, 10, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movapd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movapd_XMM8_XMM12_icebp_c64, 255, RM_REG, T_SSE, 8, 12, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movapd_XMM1_FSxBX_icebp_c64, X86_XCPT_GP, RM_MEM, T_SSE, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movapd_XMM10_FSxBX_icebp_c64, X86_XCPT_GP, RM_MEM, T_SSE, 10, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovaps_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovaps_XMM7_XMM14_icebp_c64, 255, RM_REG, T_AVX_128, 7, 14, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovaps_XMM1_FSxBX_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovaps_XMM11_FSxBX_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_128, 11, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovapd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovapd_XMM7_XMM14_icebp_c64, 255, RM_REG, T_AVX_128, 7, 14, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovapd_XMM1_FSxBX_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovapd_XMM11_FSxBX_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_128, 11, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovaps_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovaps_YMM12_YMM8_icebp_c64, 255, RM_REG, T_AVX_256, 12, 8, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovaps_YMM1_FSxBX_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovaps_YMM12_FSxBX_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_256, 12, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovapd_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovapd_YMM12_YMM8_icebp_c64, 255, RM_REG, T_AVX_256, 12, 8, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovapd_YMM1_FSxBX_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovapd_YMM12_FSxBX_icebp_c64, X86_XCPT_GP, RM_MEM, T_AVX_256, 12, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig1, RT_ELEMENTS(g_aXcptConfig1), 255 /*cbMaxAlign*/); +} + + +/* + * [V]MOVDQU - move unaligned packed qwords. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movdqu_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_07f_movdqu_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movdqu_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_movdqu_XMM8_XMM12_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_07f_movdqu_XMM8_XMM12_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movdqu_XMM10_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovdqu_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_07f_vmovdqu_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovdqu_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovdqu_XMM7_XMM14_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovdqu_XMM11_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovdqu_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_07f_vmovdqu_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovdqu_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovdqu_YMM12_YMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovdqu_YMM12_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movdqu_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_movdqu_FSxBX_XMM10_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovdqu_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovdqu_FSxBX_XMM11_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovdqu_FSxBX_YMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovdqu_FSxBX_YMM12_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_movdqu(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_movdqu_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_movdqu_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movdqu_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_SSE, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movdqu_FSxBX_XMM1_icebp_c16, X86_XCPT_DB, RM_MEM, T_SSE, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovdqu_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_vmovdqu_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqu_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqu_FSxBX_XMM1_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovdqu_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_vmovdqu_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqu_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqu_FSxBX_YMM1_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_movdqu_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_movdqu_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movdqu_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_SSE, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movdqu_FSxBX_XMM1_icebp_c32, X86_XCPT_DB, RM_MEM, T_SSE, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovdqu_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_vmovdqu_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqu_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqu_FSxBX_XMM1_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovdqu_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_vmovdqu_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqu_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqu_FSxBX_YMM1_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_movdqu_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_movdqu_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movdqu_XMM8_XMM12_icebp_c64, 255, RM_REG, T_SSE, 8, 12, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_movdqu_XMM8_XMM12_icebp_c64, 255, RM_REG, T_SSE, 8, 12, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movdqu_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movdqu_XMM10_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE, 10, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movdqu_FSxBX_XMM1_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movdqu_FSxBX_XMM10_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE, 255, 10, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovdqu_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_vmovdqu_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqu_XMM7_XMM14_icebp_c64, 255, RM_REG, T_AVX_128, 7, 14, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqu_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqu_XMM11_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 11, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqu_FSxBX_XMM1_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqu_FSxBX_XMM11_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 255, 11, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovdqu_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_vmovdqu_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqu_YMM12_YMM8_icebp_c64, 255, RM_REG, T_AVX_256, 12, 8, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqu_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqu_YMM12_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 12, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqu_FSxBX_YMM1_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqu_FSxBX_YMM12_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 255, 12, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4Unaligned, RT_ELEMENTS(g_aXcptConfig4Unaligned), 0 /*cbMaxAlign*/); +} + + +/* + * [V]MOVDQA - move aligned packed qwords. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movdqa_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_07f_movdqa_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movdqa_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_movdqa_XMM8_XMM12_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_07f_movdqa_XMM8_XMM12_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_movdqa_XMM10_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovdqa_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_07f_vmovdqa_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovdqa_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovdqa_XMM8_XMM14_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_07f_vmovdqa_XMM8_XMM14_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovdqa_XMM11_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovdqa_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_07f_vmovdqa_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovdqa_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovdqa_YMM12_YMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_07f_vmovdqa_YMM12_YMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vmovdqa_YMM12_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_movdqa_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_movdqa_FSxBX_XMM10_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovdqa_FSxBX_XMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovdqa_FSxBX_XMM11_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vmovdqa_FSxBX_YMM1_icebp); +extern FNBS3FAR bs3CpuInstr3_vmovdqa_FSxBX_YMM12_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_movdqa(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_movdqa_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_movdqa_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movdqa_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movdqa_FSxBX_XMM1_icebp_c16, 255, RM_MEM, T_SSE, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovdqa_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_vmovdqa_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqa_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqa_FSxBX_XMM1_icebp_c16, X86_XCPT_AC, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovdqa_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_vmovdqa_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqa_YMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqa_FSxBX_YMM1_icebp_c16, X86_XCPT_AC, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_movdqa_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_movdqa_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movdqa_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movdqa_FSxBX_XMM1_icebp_c32, 255, RM_MEM, T_SSE, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovdqa_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_vmovdqa_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqa_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqa_FSxBX_XMM1_icebp_c32, X86_XCPT_AC, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovdqa_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_vmovdqa_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqa_YMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqa_FSxBX_YMM1_icebp_c32, X86_XCPT_AC, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_movdqa_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_movdqa_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movdqa_XMM8_XMM12_icebp_c64, 255, RM_REG, T_SSE, 8, 12, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_movdqa_XMM8_XMM12_icebp_c64, 255, RM_REG, T_SSE, 8, 12, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movdqa_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movdqa_XMM10_FSxBX_icebp_c64, 255, RM_MEM, T_SSE, 10, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movdqa_FSxBX_XMM1_icebp_c64, 255, RM_MEM, T_SSE, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_movdqa_FSxBX_XMM10_icebp_c64, 255, RM_MEM, T_SSE, 255, 10, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovdqa_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_vmovdqa_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqa_XMM8_XMM14_icebp_c64, 255, RM_REG, T_AVX_128, 8, 14, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_vmovdqa_XMM8_XMM14_icebp_c64, 255, RM_REG, T_AVX_128, 8, 14, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqa_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqa_XMM11_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 11, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqa_FSxBX_XMM1_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqa_FSxBX_XMM11_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 255, 11, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vmovdqa_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_vmovdqa_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqa_YMM12_YMM8_icebp_c64, 255, RM_REG, T_AVX_256, 12, 8, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_07f_vmovdqa_YMM12_YMM8_icebp_c64, 255, RM_REG, T_AVX_256, 12, 8, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqa_YMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqa_YMM12_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_256, 12, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqa_FSxBX_YMM1_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_256, 255, 1, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vmovdqa_FSxBX_YMM12_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_256, 255, 12, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig1, RT_ELEMENTS(g_aXcptConfig1), 255 /*cbMaxAlign*/); +} + + +/* + * [V]PABSB / [V]PABSW / [V]PABSD + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pabsb_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pabsb_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pabsb_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pabsb_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pabsb_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pabsb_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpabsb_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpabsb_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpabsb_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpabsb_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpabsb_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpabsb_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpabsb_YMM9_YMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpabsb_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pabsw_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pabsw_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pabsw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pabsw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pabsw_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pabsw_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpabsw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpabsw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpabsw_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpabsw_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpabsw_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpabsw_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpabsw_YMM9_YMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpabsw_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pabsd_MM1_MM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pabsd_MM1_FSxBX_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pabsd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pabsd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pabsd_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pabsd_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpabsd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpabsd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpabsd_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpabsd_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpabsd_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpabsd_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpabsd_YMM9_YMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpabsd_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pabsb_pabsw_pabsd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValuesB[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0x0101010101010101, 0x0101010101010101, 0x0101010101010101, 0x0101010101010101) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x6767565645453434, 0x2323121201012121, 0x1111222233334444, 0x5555666677777878) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0x4d09102a6c24732b, 0x3e0c1738666b3f1a, 0x4c212f58564c655e, 0x645c20736d096a45) }, + }; + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValuesW[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0x0001000100010001, 0x0001000100010001, 0x0001000100010001, 0x0001000100010001) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x6667555644453334, 0x2223111200012121, 0x1111222233334444, 0x5555666677777778) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0x4d090fd66cdc73d5, 0x3ef417c8666b3fe6, 0x4bdf2fa8564c645e, 0x63a41f8d6cf76945) }, + }; + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValuesD[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0x0000000100000001, 0x0000000100000001, 0x0000000100000001, 0x0000000100000001) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x6666555644443334, 0x222211120000dedf, 0x1111222233334444, 0x5555666677778888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0x4bded058564c9ba2, 0x63a31f8d6cf66945) }, + }; + + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_pabsb_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pabsb_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pabsb_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pabsb_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpabsb_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpabsb_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpabsb_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpabsb_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pabsw_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pabsw_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pabsw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pabsw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpabsw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpabsw_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpabsw_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpabsw_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pabsd_MM1_MM2_icebp_c16, 255, RM_REG, T_MMX_SSSE3, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pabsd_MM1_FSxBX_icebp_c16, 255, RM_MEM, T_MMX_SSSE3, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pabsd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSSE3, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pabsd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSSE3, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpabsd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpabsd_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpabsd_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpabsd_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_pabsb_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pabsb_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pabsb_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pabsb_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpabsb_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpabsb_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpabsb_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpabsb_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pabsw_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pabsw_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pabsw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pabsw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpabsw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpabsw_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpabsw_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpabsw_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pabsd_MM1_MM2_icebp_c32, 255, RM_REG, T_MMX_SSSE3, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pabsd_MM1_FSxBX_icebp_c32, 255, RM_MEM, T_MMX_SSSE3, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pabsd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSSE3, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pabsd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSSE3, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpabsd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpabsd_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpabsd_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpabsd_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_pabsb_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pabsb_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pabsb_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pabsb_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pabsb_XMM9_XMM8_icebp_c64, 255, RM_REG, T_SSSE3, 9, 8, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_pabsb_XMM9_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 9, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpabsb_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpabsb_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpabsb_XMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_128, 9, 8, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpabsb_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 9, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpabsb_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpabsb_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpabsb_YMM9_YMM8_icebp_c64, 255, RM_REG, T_AVX_256, 9, 8, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + { bs3CpuInstr3_vpabsb_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 9, 255, RT_ELEMENTS(s_aValuesB), s_aValuesB }, + + { bs3CpuInstr3_pabsw_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pabsw_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pabsw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pabsw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pabsw_XMM9_XMM8_icebp_c64, 255, RM_REG, T_SSSE3, 9, 8, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_pabsw_XMM9_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 9, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpabsw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpabsw_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpabsw_XMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_128, 9, 8, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpabsw_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 9, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpabsw_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpabsw_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpabsw_YMM9_YMM8_icebp_c64, 255, RM_REG, T_AVX_256, 9, 8, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + { bs3CpuInstr3_vpabsw_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 9, 255, RT_ELEMENTS(s_aValuesW), s_aValuesW }, + + { bs3CpuInstr3_pabsd_MM1_MM2_icebp_c64, 255, RM_REG, T_MMX_SSSE3, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pabsd_MM1_FSxBX_icebp_c64, 255, RM_MEM, T_MMX_SSSE3, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pabsd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSSE3, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pabsd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pabsd_XMM9_XMM8_icebp_c64, 255, RM_REG, T_SSSE3, 9, 8, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_pabsd_XMM9_FSxBX_icebp_c64, 255, RM_MEM, T_SSSE3, 9, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpabsd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpabsd_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpabsd_XMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_128, 9, 8, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpabsd_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 9, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpabsd_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpabsd_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpabsd_YMM9_YMM8_icebp_c64, 255, RM_REG, T_AVX_256, 9, 8, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + { bs3CpuInstr3_vpabsd_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 9, 255, RT_ELEMENTS(s_aValuesD), s_aValuesD }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4), X86_EFL_STATUS_BITS); +} + + +/* + * [V]PMOVSXBW / [V]PMOVSXBD / [V]PMOVSXBQ / [V]PMOVSXWD / [V]PMOVSXWQ / [V]PMOVSXDQ + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovsxbw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovsxbw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmovsxbw_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmovsxbw_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxbw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxbw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovsxbw_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovsxbw_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxbw_YMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxbw_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovsxbw_YMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovsxbw_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovsxbd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovsxbd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmovsxbd_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmovsxbd_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxbd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxbd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovsxbd_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovsxbd_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxbd_YMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxbd_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovsxbd_YMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovsxbd_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovsxbq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovsxbq_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmovsxbq_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmovsxbq_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxbq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxbq_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovsxbq_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovsxbq_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxbq_YMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxbq_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovsxbq_YMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovsxbq_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovsxwd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovsxwd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmovsxwd_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmovsxwd_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxwd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxwd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovsxwd_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovsxwd_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxwd_YMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxwd_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovsxwd_YMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovsxwd_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovsxwq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovsxwq_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmovsxwq_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmovsxwq_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxwq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxwq_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovsxwq_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovsxwq_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxwq_YMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxwq_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovsxwq_YMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovsxwq_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovsxdq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovsxdq_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmovsxdq_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmovsxdq_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxdq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxdq_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovsxdq_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovsxdq_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxdq_YMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovsxdq_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovsxdq_YMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovsxdq_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pmovsxbw_pmovsxbd_pmovsxbq_pmovsxwd_pmovsxwq_pmovsxdq(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValuesBW[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x0011001100220022, 0x0033003300440044, 0x0055005500660066, 0x00770077ff88ff88) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0xffb40021002fffa8, 0x0056004cff9bffa2, 0xff9c005cffe00073, 0xff930009ff96ffbb) }, + }; + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValuesBD[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x0000005500000055, 0x0000006600000066, 0x0000007700000077, 0xffffff88ffffff88) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0xffffff9c0000005c, 0xffffffe000000073, 0xffffff9300000009, 0xffffff96ffffffbb) }, + }; + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValuesBQ[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x0000000000000077, 0x0000000000000077, 0xffffffffffffff88, 0xffffffffffffff88) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0xffffffffffffff93, 0x0000000000000009, 0xffffffffffffff96, 0xffffffffffffffbb) }, + }; + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValuesWD[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x0000111100002222, 0x0000333300004444, 0x0000555500006666, 0x00007777ffff8888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0xffffb42100002fa8, 0x0000564cffff9ba2, 0xffff9c5cffffe073, 0xffff9309ffff96bb) }, + }; + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValuesWQ[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x0000000000005555, 0x0000000000006666, 0x0000000000007777, 0xffffffffffff8888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0xffffffffffff9c5c, 0xffffffffffffe073, 0xffffffffffff9309, 0xffffffffffff96bb) }, + }; + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValuesDQ[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x0000000011112222, 0x0000000033334444, 0x0000000055556666, 0x0000000077778888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0xffffffffb4212fa8, 0x00000000564c9ba2, 0xffffffff9c5ce073, 0xffffffff930996bb) }, + }; + + /** @todo Some variants produce different results wrt. to #DB vs #AC exceptions on real hardware (i7-6700K) and in a VM. + * The exception encountered on real hardware is put in the comment, the X86_XCPT_DB is when emulating the instruction using IEM. + * Needs investigation + */ + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_pmovsxbw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_pmovsxbw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovsxbw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovsxbw_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovsxbw_YMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovsxbw_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + + { bs3CpuInstr3_pmovsxbd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_pmovsxbd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovsxbd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovsxbd_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovsxbd_YMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovsxbd_YMM1_FSxBX_icebp_c16, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + + { bs3CpuInstr3_pmovsxbq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_pmovsxbq_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovsxbq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovsxbq_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovsxbq_YMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovsxbq_YMM1_FSxBX_icebp_c16, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + + { bs3CpuInstr3_pmovsxwd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_pmovsxwd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovsxwd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovsxwd_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovsxwd_YMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovsxwd_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + + { bs3CpuInstr3_pmovsxwq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_pmovsxwq_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovsxwq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovsxwq_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovsxwq_YMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovsxwq_YMM1_FSxBX_icebp_c16, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + + { bs3CpuInstr3_pmovsxdq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_pmovsxdq_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovsxdq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovsxdq_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovsxdq_YMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovsxdq_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_pmovsxbw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_pmovsxbw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovsxbw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovsxbw_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovsxbw_YMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovsxbw_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + + { bs3CpuInstr3_pmovsxbd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_pmovsxbd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovsxbd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovsxbd_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovsxbd_YMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovsxbd_YMM1_FSxBX_icebp_c32, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + + { bs3CpuInstr3_pmovsxbq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_pmovsxbq_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovsxbq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovsxbq_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovsxbq_YMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovsxbq_YMM1_FSxBX_icebp_c32, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + + { bs3CpuInstr3_pmovsxwd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_pmovsxwd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovsxwd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovsxwd_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovsxwd_YMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovsxwd_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + + { bs3CpuInstr3_pmovsxwq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_pmovsxwq_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovsxwq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovsxwq_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovsxwq_YMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovsxwq_YMM1_FSxBX_icebp_c32, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + + { bs3CpuInstr3_pmovsxdq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_pmovsxdq_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovsxdq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovsxdq_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovsxdq_YMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovsxdq_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_pmovsxbw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_pmovsxbw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_pmovsxbw_XMM9_XMM8_icebp_c64, 255, RM_REG, T_SSE4_1, 9, 8, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_pmovsxbw_XMM9_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 9, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovsxbw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovsxbw_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovsxbw_XMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_128, 9, 8, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovsxbw_XMM9_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 9, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovsxbw_YMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovsxbw_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovsxbw_YMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_256, 9, 8, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovsxbw_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 9, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + + { bs3CpuInstr3_pmovsxbd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_pmovsxbd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_pmovsxbd_XMM9_XMM8_icebp_c64, 255, RM_REG, T_SSE4_1, 9, 8, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_pmovsxbd_XMM9_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 9, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovsxbd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovsxbd_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovsxbd_XMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_128, 9, 8, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovsxbd_XMM9_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 9, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovsxbd_YMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovsxbd_YMM1_FSxBX_icebp_c64, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovsxbd_YMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_256, 9, 8, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovsxbd_YMM9_FSxBX_icebp_c64, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 9, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + + { bs3CpuInstr3_pmovsxbq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_pmovsxbq_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_pmovsxbq_XMM9_XMM8_icebp_c64, 255, RM_REG, T_SSE4_1, 9, 8, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_pmovsxbq_XMM9_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 9, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovsxbq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovsxbq_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovsxbq_XMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_128, 9, 8, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovsxbq_XMM9_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 9, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovsxbq_YMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovsxbq_YMM1_FSxBX_icebp_c64, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovsxbq_YMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_256, 9, 8, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovsxbq_YMM9_FSxBX_icebp_c64, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 9, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + + { bs3CpuInstr3_pmovsxwd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_pmovsxwd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_pmovsxwd_XMM9_XMM8_icebp_c64, 255, RM_REG, T_SSE4_1, 9, 8, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_pmovsxwd_XMM9_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 9, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovsxwd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovsxwd_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovsxwd_XMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_128, 9, 8, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovsxwd_XMM9_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 9, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovsxwd_YMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovsxwd_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovsxwd_YMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_256, 9, 8, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovsxwd_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 9, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + + { bs3CpuInstr3_pmovsxwq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_pmovsxwq_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_pmovsxwq_XMM9_XMM8_icebp_c64, 255, RM_REG, T_SSE4_1, 9, 8, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_pmovsxwq_XMM9_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 9, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovsxwq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovsxwq_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovsxwq_XMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_128, 9, 8, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovsxwq_XMM9_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 9, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovsxwq_YMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovsxwq_YMM1_FSxBX_icebp_c64, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovsxwq_YMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_256, 9, 8, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovsxwq_YMM9_FSxBX_icebp_c64, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 9, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + + { bs3CpuInstr3_pmovsxdq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_pmovsxdq_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_pmovsxdq_XMM9_XMM8_icebp_c64, 255, RM_REG, T_SSE4_1, 9, 8, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_pmovsxdq_XMM9_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 9, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovsxdq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovsxdq_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovsxdq_XMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_128, 9, 8, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovsxdq_XMM9_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 9, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovsxdq_YMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovsxdq_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovsxdq_YMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_256, 9, 8, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovsxdq_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 9, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig5, RT_ELEMENTS(g_aXcptConfig5), X86_EFL_STATUS_BITS); +} + + +/* + * [V]PMOVZXBW / [V]PMOVZXBD / [V]PMOVZXBQ / [V]PMOVZXWD / [V]PMOVZXWQ / [V]PMOVZXDQ + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovzxbw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovzxbw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmovzxbw_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmovzxbw_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxbw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxbw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovzxbw_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovzxbw_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxbw_YMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxbw_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovzxbw_YMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovzxbw_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovzxbd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovzxbd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmovzxbd_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmovzxbd_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxbd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxbd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovzxbd_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovzxbd_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxbd_YMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxbd_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovzxbd_YMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovzxbd_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovzxbq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovzxbq_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmovzxbq_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmovzxbq_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxbq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxbq_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovzxbq_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovzxbq_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxbq_YMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxbq_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovzxbq_YMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovzxbq_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovzxwd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovzxwd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmovzxwd_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmovzxwd_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxwd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxwd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovzxwd_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovzxwd_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxwd_YMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxwd_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovzxwd_YMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovzxwd_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovzxwq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovzxwq_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmovzxwq_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmovzxwq_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxwq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxwq_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovzxwq_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovzxwq_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxwq_YMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxwq_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovzxwq_YMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovzxwq_YMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovzxdq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pmovzxdq_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pmovzxdq_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pmovzxdq_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxdq_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxdq_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovzxdq_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovzxdq_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxdq_YMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpmovzxdq_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vpmovzxdq_YMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpmovzxdq_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pmovzxbw_pmovzxbd_pmovzxbq_pmovzxwd_pmovzxwq_pmovzxdq(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValuesBW[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0x00ff00ff00ff00ff, 0x00ff00ff00ff00ff, 0x00ff00ff00ff00ff, 0x00ff00ff00ff00ff) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x0011001100220022, 0x0033003300440044, 0x0055005500660066, 0x0077007700880088) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0x00b40021002f00a8, 0x0056004c009b00a2, 0x009c005c00e00073, 0x00930009009600bb) }, + }; + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValuesBD[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0x000000ff000000ff, 0x000000ff000000ff, 0x000000ff000000ff, 0x000000ff000000ff) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x0000005500000055, 0x0000006600000066, 0x0000007700000077, 0x0000008800000088) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0x0000009c0000005c, 0x000000e000000073, 0x0000009300000009, 0x00000096000000bb) }, + }; + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValuesBQ[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0x00000000000000ff, 0x00000000000000ff, 0x00000000000000ff, 0x00000000000000ff) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x0000000000000077, 0x0000000000000077, 0x0000000000000088, 0x0000000000000088) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0x0000000000000093, 0x0000000000000009, 0x0000000000000096, 0x00000000000000bb) }, + }; + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValuesWD[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0x0000ffff0000ffff, 0x0000ffff0000ffff, 0x0000ffff0000ffff, 0x0000ffff0000ffff) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x0000111100002222, 0x0000333300004444, 0x0000555500006666, 0x0000777700008888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0x0000b42100002fa8, 0x0000564c00009ba2, 0x00009c5c0000e073, 0x00009309000096bb) }, + }; + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValuesWQ[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0x000000000000ffff, 0x000000000000ffff, 0x000000000000ffff, 0x000000000000ffff) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x0000000000005555, 0x0000000000006666, 0x0000000000007777, 0x0000000000008888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0x0000000000009c5c, 0x000000000000e073, 0x0000000000009309, 0x00000000000096bb) }, + }; + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValuesDQ[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(0x00000000ffffffff, 0x00000000ffffffff, 0x00000000ffffffff, 0x00000000ffffffff) }, + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(0x0000000011112222, 0x0000000033334444, 0x0000000055556666, 0x0000000077778888) }, + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(0x00000000b4212fa8, 0x00000000564c9ba2, 0x000000009c5ce073, 0x00000000930996bb) }, + }; + + /** @todo Some variants produce different results wrt. to #DB vs #AC exceptions on real hardware (i7-6700K) and in a VM. + * The exception encountered on real hardware is put in the comment, the X86_XCPT_DB is when emulating the instruction using IEM. + * Needs investigation + */ + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_pmovzxbw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_pmovzxbw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovzxbw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovzxbw_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovzxbw_YMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovzxbw_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + + { bs3CpuInstr3_pmovzxbd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_pmovzxbd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovzxbd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovzxbd_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovzxbd_YMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovzxbd_YMM1_FSxBX_icebp_c16, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + + { bs3CpuInstr3_pmovzxbq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_pmovzxbq_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovzxbq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovzxbq_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovzxbq_YMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovzxbq_YMM1_FSxBX_icebp_c16, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + + { bs3CpuInstr3_pmovzxwd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_pmovzxwd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovzxwd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovzxwd_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovzxwd_YMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovzxwd_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + + { bs3CpuInstr3_pmovzxwq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_pmovzxwq_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovzxwq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovzxwq_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovzxwq_YMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovzxwq_YMM1_FSxBX_icebp_c16, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + + { bs3CpuInstr3_pmovzxdq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_pmovzxdq_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovzxdq_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovzxdq_XMM1_FSxBX_icebp_c16, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovzxdq_YMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovzxdq_YMM1_FSxBX_icebp_c16, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_pmovzxbw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_pmovzxbw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovzxbw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovzxbw_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovzxbw_YMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovzxbw_YMM1_FSxBX_icebp_c32, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + + { bs3CpuInstr3_pmovzxbd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_pmovzxbd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovzxbd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovzxbd_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovzxbd_YMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovzxbd_YMM1_FSxBX_icebp_c32, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + + { bs3CpuInstr3_pmovzxbq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_pmovzxbq_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovzxbq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovzxbq_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovzxbq_YMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovzxbq_YMM1_FSxBX_icebp_c32, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + + { bs3CpuInstr3_pmovzxwd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_pmovzxwd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovzxwd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovzxwd_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovzxwd_YMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovzxwd_YMM1_FSxBX_icebp_c32, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + + { bs3CpuInstr3_pmovzxwq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_pmovzxwq_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovzxwq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovzxwq_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovzxwq_YMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovzxwq_YMM1_FSxBX_icebp_c32, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + + { bs3CpuInstr3_pmovzxdq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_pmovzxdq_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovzxdq_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovzxdq_XMM1_FSxBX_icebp_c32, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovzxdq_YMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovzxdq_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_pmovzxbw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_pmovzxbw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_pmovzxbw_XMM9_XMM8_icebp_c64, 255, RM_REG, T_SSE4_1, 9, 8, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_pmovzxbw_XMM9_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 9, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovzxbw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovzxbw_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovzxbw_XMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_128, 9, 8, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovzxbw_XMM9_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 9, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovzxbw_YMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovzxbw_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovzxbw_YMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_256, 9, 8, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + { bs3CpuInstr3_vpmovzxbw_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 9, 255, RT_ELEMENTS(s_aValuesBW), s_aValuesBW }, + + { bs3CpuInstr3_pmovzxbd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_pmovzxbd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_pmovzxbd_XMM9_XMM8_icebp_c64, 255, RM_REG, T_SSE4_1, 9, 8, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_pmovzxbd_XMM9_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 9, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovzxbd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovzxbd_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovzxbd_XMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_128, 9, 8, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovzxbd_XMM9_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 9, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovzxbd_YMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovzxbd_YMM1_FSxBX_icebp_c64, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovzxbd_YMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_256, 9, 8, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + { bs3CpuInstr3_vpmovzxbd_YMM9_FSxBX_icebp_c64, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 9, 255, RT_ELEMENTS(s_aValuesBD), s_aValuesBD }, + + { bs3CpuInstr3_pmovzxbq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_pmovzxbq_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_pmovzxbq_XMM9_XMM8_icebp_c64, 255, RM_REG, T_SSE4_1, 9, 8, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_pmovzxbq_XMM9_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 9, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovzxbq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovzxbq_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovzxbq_XMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_128, 9, 8, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovzxbq_XMM9_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 9, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovzxbq_YMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovzxbq_YMM1_FSxBX_icebp_c64, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovzxbq_YMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_256, 9, 8, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + { bs3CpuInstr3_vpmovzxbq_YMM9_FSxBX_icebp_c64, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 9, 255, RT_ELEMENTS(s_aValuesBQ), s_aValuesBQ }, + + { bs3CpuInstr3_pmovzxwd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_pmovzxwd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_pmovzxwd_XMM9_XMM8_icebp_c64, 255, RM_REG, T_SSE4_1, 9, 8, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_pmovzxwd_XMM9_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 9, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovzxwd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovzxwd_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovzxwd_XMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_128, 9, 8, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovzxwd_XMM9_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 9, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovzxwd_YMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovzxwd_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovzxwd_YMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_256, 9, 8, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + { bs3CpuInstr3_vpmovzxwd_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 9, 255, RT_ELEMENTS(s_aValuesWD), s_aValuesWD }, + + { bs3CpuInstr3_pmovzxwq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_pmovzxwq_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_pmovzxwq_XMM9_XMM8_icebp_c64, 255, RM_REG, T_SSE4_1, 9, 8, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_pmovzxwq_XMM9_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 9, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovzxwq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovzxwq_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovzxwq_XMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_128, 9, 8, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovzxwq_XMM9_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 9, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovzxwq_YMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovzxwq_YMM1_FSxBX_icebp_c64, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovzxwq_YMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_256, 9, 8, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + { bs3CpuInstr3_vpmovzxwq_YMM9_FSxBX_icebp_c64, X86_XCPT_DB /*AC*/, RM_MEM, T_AVX_256, 9, 255, RT_ELEMENTS(s_aValuesWQ), s_aValuesWQ }, + + { bs3CpuInstr3_pmovzxdq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_pmovzxdq_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_pmovzxdq_XMM9_XMM8_icebp_c64, 255, RM_REG, T_SSE4_1, 9, 8, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_pmovzxdq_XMM9_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 9, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovzxdq_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovzxdq_XMM1_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovzxdq_XMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_128, 9, 8, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovzxdq_XMM9_FSxBX_icebp_c64, X86_XCPT_AC, RM_MEM, T_AVX_128, 9, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovzxdq_YMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovzxdq_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovzxdq_YMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_256, 9, 8, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + { bs3CpuInstr3_vpmovzxdq_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 9, 255, RT_ELEMENTS(s_aValuesDQ), s_aValuesDQ }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig5, RT_ELEMENTS(g_aXcptConfig5), X86_EFL_STATUS_BITS); +} + + +/* + * [V]LDDQU - Load unaligned integer from memory. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_lddqu_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_lddqu_XMM10_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vlddqu_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vlddqu_XMM11_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vlddqu_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vlddqu_YMM12_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_lddqu(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_lddqu_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_SSE3, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vlddqu_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vlddqu_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_lddqu_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_SSE3, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vlddqu_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vlddqu_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_lddqu_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE3, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_lddqu_XMM10_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_SSE3, 10, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vlddqu_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vlddqu_XMM11_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 11, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + + { bs3CpuInstr3_vlddqu_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + { bs3CpuInstr3_vlddqu_YMM12_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 12, 255, RT_ELEMENTS(g_aMoveValues3), g_aMoveValues3 }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4Unaligned, RT_ELEMENTS(g_aXcptConfig4Unaligned), 0 /*cbMaxAlign*/); +} + + +/* + * [V]PHMINPOSUW + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phminposuw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_phminposuw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_phminposuw_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_phminposuw_XMM9_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphminposuw_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vphminposuw_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vphminposuw_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vphminposuw_XMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_phminposuw(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST3_VALUES_T const s_aValues[] = + { + { RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ RTUINT256_INIT_C(1, 2, 0x0000000000000000, 0x000000000000ffff) }, /* No 256-bit variant */ + { RTUINT256_INIT_C(0x9999aaaabbbbcccc, 0xddddeeeeffff2121, 0x1111222233334444, 0x5555666677778888), + /* => */ RTUINT256_INIT_C(5, 6, 0x0000000000000000, 0x0000000000071111) }, /* No 256-bit variant */ + { RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /* => */ RTUINT256_INIT_C(9, 10, 0x0000000000000000, 0x0000000000062fa8) }, /* No 256-bit variant */ + }; + + static BS3CPUINSTR3_TEST3_T const s_aTests16[] = + { + { bs3CpuInstr3_phminposuw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_phminposuw_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vphminposuw_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vphminposuw_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests32[] = + { + { bs3CpuInstr3_phminposuw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_phminposuw_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vphminposuw_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vphminposuw_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST3_T const s_aTests64[] = + { + { bs3CpuInstr3_phminposuw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_phminposuw_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_phminposuw_XMM9_XMM8_icebp_c64, 255, RM_REG, T_SSE4_1, 9, 8, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_phminposuw_XMM9_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 9, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vphminposuw_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vphminposuw_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vphminposuw_XMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_128, 9, 8, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vphminposuw_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 9, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST3_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST3_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType3(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4), X86_EFL_STATUS_BITS); +} + + +/* + * Test type #4 - two source MM/XMM/YMM operands, outputs only eflags. + * + * Probably only used by the PTEST instruction. + */ + +typedef struct BS3CPUINSTR3_TEST4_VALUES_T +{ + RTUINT256U uSrc2; + RTUINT256U uSrc1; + uint16_t afEflOut[3]; /* [0]=MM result, [1]=XMM result, [2]=YMM result */ +} BS3CPUINSTR3_TEST4_VALUES_T; + +typedef struct BS3CPUINSTR3_TEST4_T +{ + FPFNBS3FAR pfnWorker; + uint8_t bAvxMisalignXcpt; + uint8_t enmRm; + uint8_t enmType; + uint8_t iRegSrc1; + uint8_t iRegSrc2; + uint8_t cValues; + BS3CPUINSTR3_TEST4_VALUES_T const BS3_FAR *paValues; +} BS3CPUINSTR3_TEST4_T; + +typedef struct BS3CPUINSTR3_TEST4_MODE_T +{ + BS3CPUINSTR3_TEST4_T const BS3_FAR *paTests; + unsigned cTests; +} BS3CPUINSTR3_TEST4_MODE_T; + +/** Initializer for a BS3CPUINSTR3_TEST4_MODE_T array (three entries). */ +#define BS3CPUINSTR3_TEST4_MODES_INIT(a_aTests16, a_aTests32, a_aTests64) \ + { { a_aTests16, RT_ELEMENTS(a_aTests16) }, { a_aTests32, RT_ELEMENTS(a_aTests32) }, { a_aTests64, RT_ELEMENTS(a_aTests64) } } + + +/** + * Test type #4 worker. + */ +static uint8_t bs3CpuInstr3_WorkerTestType4(uint8_t bMode, BS3CPUINSTR3_TEST4_T const BS3_FAR *paTests, unsigned cTests, + PCBS3CPUINSTR3_CONFIG_T paConfigs, unsigned cConfigs, uint32_t fEflCheck) +{ + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + const char BS3_FAR * const pszMode = Bs3GetModeName(bMode); + uint8_t BS3_FAR *pbBuf = g_pbBuf; + uint32_t cbBuf = g_cbBuf; + uint8_t bRing = BS3_MODE_IS_V86(bMode) ? 3 : 0; + PBS3EXTCTX pExtCtxOut; + PBS3EXTCTX pExtCtx = bs3CpuInstr3AllocExtCtxs(&pExtCtxOut); + if (!pExtCtx) + return 0; + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + Bs3RegCtxSaveForMode(&Ctx, bMode, 1024); + bs3CpuInstr3SetupSseAndAvx(&Ctx, pExtCtx); + pbBuf = bs3CpuInstr3BufSetup(pbBuf, &cbBuf, bMode); + + /* + * Run the tests in all rings since alignment issues may behave + * differently in ring-3 compared to ring-0. + */ + for (;;) + { + unsigned iCfg; + for (iCfg = 0; iCfg < cConfigs; iCfg++) + { + unsigned iTest; + BS3CPUINSTR3_CONFIG_SAVED_T SavedCfg; + if (!bs3CpuInstr3ConfigReconfigure(&SavedCfg, &Ctx, pExtCtx, &paConfigs[iCfg], bMode)) + continue; /* unsupported config */ + + /* + * Iterate the tests. + */ + for (iTest = 0; iTest < cTests; iTest++) + { + BS3CPUINSTR3_TEST4_VALUES_T const BS3_FAR *paValues = paTests[iTest].paValues; + uint8_t const cbInstr = ((uint8_t const BS3_FAR *)(uintptr_t)paTests[iTest].pfnWorker)[-1]; + unsigned const cValues = paTests[iTest].cValues; + bool const fMmxInstr = paTests[iTest].enmType < T_SSE; + bool const fSseInstr = paTests[iTest].enmType >= T_SSE && paTests[iTest].enmType < T_AVX_128; + bool const fAvxInstr = paTests[iTest].enmType >= T_AVX_128; + uint8_t const cbOperand = paTests[iTest].enmType < T_128BITS ? 64/8 + : paTests[iTest].enmType < T_256BITS ? 128/8 : 256/8; + uint8_t const cbMemOp = cbOperand; + uint8_t const cbAlign = RT_MIN(cbOperand, 16); + PRTUINT256U puMemOp = bs3CpuInstr3BufForOperand(pbBuf, cbBuf, cbMemOp, cbAlign, &paConfigs[iCfg]); + uint8_t const idxEflOut = cbOperand == 32 ? 2 : cbOperand == 16 ? 1 : 0; + uint8_t bXcptExpect = !g_afTypeSupports[paTests[iTest].enmType] ? X86_XCPT_UD + : fMmxInstr ? paConfigs[iCfg].bXcptMmx + : fSseInstr ? paConfigs[iCfg].bXcptSse + : BS3_MODE_IS_RM_OR_V86(bMode) ? X86_XCPT_UD : paConfigs[iCfg].bXcptAvx; + uint16_t idTestStep = bRing * 10000 + iCfg * 100 + iTest * 10; + unsigned iVal; + + /* If testing unaligned memory accesses, skip register-only tests. This allows + setting bXcptMmx, bXcptSse and bXcptAvx to reflect the misaligned exceptions. */ + if (paTests[iTest].enmRm == RM_REG && (!paConfigs[iCfg].fAligned || paConfigs[iCfg].fAlignCheck)) + continue; + + /* #AC is only raised in ring-3.: */ + if (bXcptExpect == X86_XCPT_AC) + { + if (bRing != 3) + bXcptExpect = X86_XCPT_DB; + else if (fAvxInstr) + bXcptExpect = paTests[iTest].bAvxMisalignXcpt; /* they generally don't raise #AC */ + } + + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, paTests[iTest].pfnWorker); + + /* + * Iterate the test values and do the actual testing. + */ + for (iVal = 0; iVal < cValues; iVal++, idTestStep++) + { + unsigned iEflVariation; + uint32_t const fSavedEfl = Ctx.rflags.u32; + for (iEflVariation = 0; iEflVariation < 2; iEflVariation++) + { + uint16_t cErrors; + uint16_t uSavedFtw = 0xff; + RTUINT256U uMemOpExpect; + + /* + * Set up the context and some expectations. + */ + /* eflags */ + if (iEflVariation) + Ctx.rflags.u32 = fSavedEfl | X86_EFL_STATUS_BITS; + else + Ctx.rflags.u32 = fSavedEfl & ~X86_EFL_STATUS_BITS; + + /* source1 */ + if (paTests[iTest].iRegSrc1 == UINT8_MAX) + { + BS3_ASSERT(paTests[iTest].enmRm >= RM_MEM); + BS3_ASSERT(paTests[iTest].iRegSrc2 != UINT8_MAX); + Bs3MemCpy(puMemOp, &paValues[iVal].uSrc1, cbMemOp); + uMemOpExpect = paValues[iVal].uSrc1; + } + else if (fMmxInstr) + Bs3ExtCtxSetMm(pExtCtx, paTests[iTest].iRegSrc1, paValues[iVal].uSrc1.QWords.qw0, BS3EXTCTXTOPMM_ZERO); + else if (fSseInstr) + Bs3ExtCtxSetXmm(pExtCtx, paTests[iTest].iRegSrc1, &paValues[iVal].uSrc1.DQWords.dqw0); + else + Bs3ExtCtxSetYmm(pExtCtx, paTests[iTest].iRegSrc1, &paValues[iVal].uSrc1, 32); + + /* source2 */ + if (paTests[iTest].iRegSrc2 == UINT8_MAX) + { + BS3_ASSERT(paTests[iTest].enmRm >= RM_MEM); + BS3_ASSERT(paTests[iTest].iRegSrc1 != UINT8_MAX); + Bs3MemCpy(puMemOp, &paValues[iVal].uSrc2, cbMemOp); + uMemOpExpect = paValues[iVal].uSrc2; + } + else if (fMmxInstr) + Bs3ExtCtxSetMm(pExtCtx, paTests[iTest].iRegSrc2, paValues[iVal].uSrc2.QWords.qw0, BS3EXTCTXTOPMM_ZERO); + else if (fSseInstr) + Bs3ExtCtxSetXmm(pExtCtx, paTests[iTest].iRegSrc2, &paValues[iVal].uSrc2.DQWords.dqw0); + else + Bs3ExtCtxSetYmm(pExtCtx, paTests[iTest].iRegSrc2, &paValues[iVal].uSrc2, 32); + + /* Memory pointer. */ + if (paTests[iTest].enmRm >= RM_MEM) + { + BS3_ASSERT( paTests[iTest].iRegSrc1 == UINT8_MAX + || paTests[iTest].iRegSrc2 == UINT8_MAX); + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, &Ctx.fs, puMemOp); + } + + /* + * Execute. + */ + Bs3TrapSetJmpAndRestoreWithExtCtxAndRm(&Ctx, pExtCtx, &TrapFrame, pExtCtxOut); + + /* + * Check the result: + */ + cErrors = Bs3TestSubErrorCount(); + + if (bXcptExpect == X86_XCPT_DB && fMmxInstr) + { + uSavedFtw = Bs3ExtCtxGetAbridgedFtw(pExtCtx); + Bs3ExtCtxSetAbridgedFtw(pExtCtx, 0xff); + } + Bs3TestCheckExtCtx(pExtCtxOut, pExtCtx, 0 /*fFlags*/, pszMode, idTestStep); + + if (TrapFrame.bXcpt != bXcptExpect) + Bs3TestFailedF("Expected bXcpt = %#x, got %#x", bXcptExpect, TrapFrame.bXcpt); + + /* Kludge! Looks like EFLAGS.AC is cleared when raising #GP in real mode on the 10980XE. WEIRD! */ + if (bMode == BS3_MODE_RM && (Ctx.rflags.u32 & X86_EFL_AC)) + { + if (TrapFrame.Ctx.rflags.u32 & X86_EFL_AC) + Bs3TestFailedF("Expected EFLAGS.AC to be cleared (bXcpt=%d)", TrapFrame.bXcpt); + TrapFrame.Ctx.rflags.u32 |= X86_EFL_AC; + } + if (bXcptExpect == X86_XCPT_DB) + Ctx.rflags.u32 = (Ctx.rflags.u32 & ~fEflCheck) | paValues[iVal].afEflOut[idxEflOut]; + Bs3TestCheckRegCtxEx(&TrapFrame.Ctx, &Ctx, bXcptExpect == X86_XCPT_DB ? cbInstr + 1 : 0, 0, + bXcptExpect == X86_XCPT_DB || BS3_MODE_IS_16BIT_SYS(bMode) ? 0 : X86_EFL_RF, + pszMode, idTestStep); + + if ( paTests[iTest].enmRm >= RM_MEM + && Bs3MemCmp(puMemOp, &uMemOpExpect, cbMemOp) != 0) + Bs3TestFailedF("Expected uMemOp %.*Rhxs, got %.*Rhxs", cbMemOp, &uMemOpExpect, cbMemOp, puMemOp); + + if (cErrors != Bs3TestSubErrorCount()) + { + if (paConfigs[iCfg].fAligned) + Bs3TestFailedF("ring-%d/cfg#%u/test#%u/value#%u/efl#%u failed (bXcptExpect=%#x)", + bRing, iCfg, iTest, iVal, iEflVariation, bXcptExpect); + else + Bs3TestFailedF("ring-%d/cfg#%u/test#%u/value#%u/efl#%u failed (bXcptExpect=%#x, puMemOp=%p, EFLAGS=%#RX32, CR0=%#RX32)", + bRing, iCfg, iTest, iVal, iEflVariation, bXcptExpect, + puMemOp, TrapFrame.Ctx.rflags.u32, TrapFrame.Ctx.cr0); + Bs3TestPrintf("\n"); + } + + if (uSavedFtw != 0xff) + Bs3ExtCtxSetAbridgedFtw(pExtCtx, uSavedFtw); + } + Ctx.rflags.u32 = fSavedEfl; + } + } + + bs3CpuInstr3ConfigRestore(&SavedCfg, &Ctx, pExtCtx); + } + + /* + * Next ring. + */ + bRing++; + if (bRing > 3 || bMode == BS3_MODE_RM) + break; + Bs3RegCtxConvertToRingX(&Ctx, bRing); + } + + /* + * Cleanup. + */ + bs3CpuInstr3BufCleanup(pbBuf, cbBuf, bMode); + bs3CpuInstr3FreeExtCtxs(pExtCtx, pExtCtxOut); + return 0; +} + + +/* + * PTEST + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_ptest_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_ptest_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_ptest_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_ptest_XMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vptest_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vptest_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vptest_XMM9_XMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vptest_XMM9_FSxBX_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vptest_YMM1_YMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vptest_YMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_vptest_YMM9_YMM8_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vptest_YMM9_FSxBX_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_ptest(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST4_VALUES_T const s_aValues[] = + { + { /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ { 0, X86_EFL_ZF | X86_EFL_CF, X86_EFL_ZF | X86_EFL_CF } }, + { /*src2*/ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /*src1*/ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /* => */ { 0, X86_EFL_CF, X86_EFL_CF } }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ { 0, 0, 0 } }, + { /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ { 0, 0, 0 } }, + { /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ { 0, 0, 0 } }, + { /*src2*/ RTUINT256_INIT_C(0x4d09f02a6cdc73d5, 0x3ef417c8666b3fe6, 0xb4212fa8564c9ba2, 0x9c5ce073930996bb), + /*src1*/ RTUINT256_INIT_C(0x1eddddac09633294, 0xf95c8eec40725633, 0x8800e95bbf9962c3, 0x43d3cda0238499fd), + /* => */ { 0, 0, 0 } }, + }; + + static BS3CPUINSTR3_TEST4_T const s_aTests16[] = + { + { bs3CpuInstr3_ptest_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_ptest_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vptest_XMM1_XMM2_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vptest_XMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vptest_YMM1_YMM2_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vptest_YMM1_FSxBX_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST4_T const s_aTests32[] = + { + { bs3CpuInstr3_ptest_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_ptest_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vptest_XMM1_XMM2_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vptest_XMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vptest_YMM1_YMM2_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vptest_YMM1_FSxBX_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST4_T const s_aTests64[] = + { + { bs3CpuInstr3_ptest_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_ptest_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_ptest_XMM9_XMM8_icebp_c64, 255, RM_REG, T_SSE4_1, 9, 8, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_ptest_XMM9_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 9, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vptest_XMM1_XMM2_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vptest_XMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vptest_XMM9_XMM8_icebp_c64, 255, RM_REG, T_AVX_128, 9, 8, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vptest_XMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 9, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vptest_YMM1_YMM2_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vptest_YMM1_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 255, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vptest_YMM9_YMM8_icebp_c64, 255, RM_REG, T_AVX_256, 9, 8, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vptest_YMM9_FSxBX_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 9, 255, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST4_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST4_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType4(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4), X86_EFL_STATUS_BITS); +} + + +/* + * Test type #5 - three source MM/XMM/YMM operands. + * + * Probably only used by the [P]BLEND instruction. + */ + +typedef struct BS3CPUINSTR3_TEST5_VALUES_T +{ + RTUINT256U uSrc3; + RTUINT256U uSrc2; + RTUINT256U uSrc1; + RTUINT256U uDstOut; +} BS3CPUINSTR3_TEST5_VALUES_T; + +typedef struct BS3CPUINSTR3_TEST5_T +{ + FPFNBS3FAR pfnWorker; + uint8_t bAvxMisalignXcpt; + uint8_t enmRm; + uint8_t enmType; + uint8_t iRegDst; + uint8_t iRegSrc1; + uint8_t iRegSrc2; + uint8_t iRegSrc3; + uint8_t cValues; + BS3CPUINSTR3_TEST5_VALUES_T const BS3_FAR *paValues; +} BS3CPUINSTR3_TEST5_T; + +typedef struct BS3CPUINSTR3_TEST5_MODE_T +{ + BS3CPUINSTR3_TEST5_T const BS3_FAR *paTests; + unsigned cTests; +} BS3CPUINSTR3_TEST5_MODE_T; + +/** Initializer for a BS3CPUINSTR3_TEST5_MODE_T array (three entries). */ +#define BS3CPUINSTR3_TEST5_MODES_INIT(a_aTests16, a_aTests32, a_aTests64) \ + { { a_aTests16, RT_ELEMENTS(a_aTests16) }, { a_aTests32, RT_ELEMENTS(a_aTests32) }, { a_aTests64, RT_ELEMENTS(a_aTests64) } } + + +/** + * Test type #5 worker. + */ +static uint8_t bs3CpuInstr3_WorkerTestType5(uint8_t bMode, BS3CPUINSTR3_TEST5_T const BS3_FAR *paTests, unsigned cTests, + PCBS3CPUINSTR3_CONFIG_T paConfigs, unsigned cConfigs) +{ + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + const char BS3_FAR * const pszMode = Bs3GetModeName(bMode); + uint8_t bRing = BS3_MODE_IS_V86(bMode) ? 3 : 0; + uint8_t BS3_FAR *pbBuf = g_pbBuf; + uint32_t cbBuf = g_cbBuf; + PBS3EXTCTX pExtCtxOut; + PBS3EXTCTX pExtCtx = bs3CpuInstr3AllocExtCtxs(&pExtCtxOut); + if (!pExtCtx) + return 0; + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + pbBuf = bs3CpuInstr3BufSetup(pbBuf, &cbBuf, bMode); + Bs3RegCtxSaveForMode(&Ctx, bMode, 1024); + bs3CpuInstr3SetupSseAndAvx(&Ctx, pExtCtx); + //Bs3TestPrintf("FTW=%#x mm1/st1=%.16Rhxs\n", pExtCtx->Ctx.x87.FTW, &pExtCtx->Ctx.x87.aRegs[1]); + + /* + * Run the tests in all rings since alignment issues may behave + * differently in ring-3 compared to ring-0. + */ + for (;;) + { + unsigned iCfg; + for (iCfg = 0; iCfg < cConfigs; iCfg++) + { + unsigned iTest; + BS3CPUINSTR3_CONFIG_SAVED_T SavedCfg; + if (!bs3CpuInstr3ConfigReconfigure(&SavedCfg, &Ctx, pExtCtx, &paConfigs[iCfg], bMode)) + continue; /* unsupported config */ + + /* + * Iterate the tests. + */ + for (iTest = 0; iTest < cTests; iTest++) + { + BS3CPUINSTR3_TEST5_VALUES_T const BS3_FAR *paValues = paTests[iTest].paValues; + uint8_t const cbInstr = ((uint8_t const BS3_FAR *)(uintptr_t)paTests[iTest].pfnWorker)[-1]; + unsigned const cValues = paTests[iTest].cValues; + bool const fMmxInstr = paTests[iTest].enmType < T_SSE; + bool const fSseInstr = paTests[iTest].enmType >= T_SSE && paTests[iTest].enmType < T_AVX_128; + bool const fAvxInstr = paTests[iTest].enmType >= T_AVX_128; + uint8_t const cbOperand = paTests[iTest].enmType < T_128BITS ? 64/8 + : paTests[iTest].enmType < T_256BITS ? 128/8 : 256/8; + uint8_t const cbMemOp = bs3CpuInstr3MemOpSize(cbOperand, paTests[iTest].enmRm); + uint8_t const cbAlign = cbMemOp; + PRTUINT256U puMemOp = bs3CpuInstr3BufForOperand(pbBuf, cbBuf, cbMemOp, cbAlign, &paConfigs[iCfg]); + uint8_t bXcptExpect = !g_afTypeSupports[paTests[iTest].enmType] ? X86_XCPT_UD + : fMmxInstr ? paConfigs[iCfg].bXcptMmx + : fSseInstr ? paConfigs[iCfg].bXcptSse + : BS3_MODE_IS_RM_OR_V86(bMode) ? X86_XCPT_UD : paConfigs[iCfg].bXcptAvx; + uint16_t idTestStep = bRing * 10000 + iCfg * 100 + iTest * 10; + unsigned iVal; + + /* If testing unaligned memory accesses, skip register-only tests. This allows + setting bXcptMmx, bXcptSse and bXcptAvx to reflect the misaligned exceptions. */ + if (paTests[iTest].enmRm == RM_REG && (!paConfigs[iCfg].fAligned || paConfigs[iCfg].fAlignCheck)) + continue; + + /* #AC is only raised in ring-3.: */ + if (bXcptExpect == X86_XCPT_AC) + { + if (bRing != 3) + bXcptExpect = X86_XCPT_DB; + else if (fAvxInstr) + bXcptExpect = paTests[iTest].bAvxMisalignXcpt; /* they generally don't raise #AC */ + } + + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, paTests[iTest].pfnWorker); + + /* + * Iterate the test values and do the actual testing. + */ + for (iVal = 0; iVal < cValues; iVal++, idTestStep++) + { + uint16_t cErrors; + uint16_t uSavedFtw = 0xff; + RTUINT256U uMemOpExpect; + + /* + * Set up the context and some expectations. + */ + /* dest */ + if (paTests[iTest].iRegDst == UINT8_MAX) + { + BS3_ASSERT(paTests[iTest].enmRm >= RM_MEM); + Bs3MemSet(puMemOp, 0xcc, cbMemOp); + if (bXcptExpect == X86_XCPT_DB) + uMemOpExpect = paValues[iVal].uDstOut; + else + Bs3MemSet(&uMemOpExpect, 0xcc, sizeof(uMemOpExpect)); + } + else if (fMmxInstr) + Bs3ExtCtxSetMm(pExtCtx, paTests[iTest].iRegSrc2, ~paValues[iVal].uDstOut.QWords.qw0, BS3EXTCTXTOPMM_ZERO); + + /* source #1 (/ destination for MMX and SSE) */ + if (paTests[iTest].iRegSrc1 == UINT8_MAX) + { + BS3_ASSERT(paTests[iTest].enmRm >= RM_MEM); + Bs3MemCpy(puMemOp, &paValues[iVal].uSrc1, cbMemOp); + if (paTests[iTest].iRegDst == UINT8_MAX) + BS3_ASSERT(fSseInstr); + else + uMemOpExpect = paValues[iVal].uSrc1; + } + else if (fMmxInstr) + Bs3ExtCtxSetMm(pExtCtx, paTests[iTest].iRegSrc1, paValues[iVal].uSrc1.QWords.qw0, BS3EXTCTXTOPMM_ZERO); + else if (fSseInstr) + Bs3ExtCtxSetXmm(pExtCtx, paTests[iTest].iRegSrc1, &paValues[iVal].uSrc1.DQWords.dqw0); + else + Bs3ExtCtxSetYmm(pExtCtx, paTests[iTest].iRegSrc1, &paValues[iVal].uSrc1, 32); + + /* source #2 */ + if (paTests[iTest].iRegSrc2 == UINT8_MAX) + { + BS3_ASSERT(paTests[iTest].enmRm >= RM_MEM); + BS3_ASSERT(paTests[iTest].iRegDst != UINT8_MAX && paTests[iTest].iRegSrc1 != UINT8_MAX); + Bs3MemCpy(puMemOp, &paValues[iVal].uSrc2, cbMemOp); + uMemOpExpect = paValues[iVal].uSrc2; + } + else if (fMmxInstr) + Bs3ExtCtxSetMm(pExtCtx, paTests[iTest].iRegSrc2, paValues[iVal].uSrc2.QWords.qw0, BS3EXTCTXTOPMM_ZERO); + else if (fSseInstr) + Bs3ExtCtxSetXmm(pExtCtx, paTests[iTest].iRegSrc2, &paValues[iVal].uSrc2.DQWords.dqw0); + else + Bs3ExtCtxSetYmm(pExtCtx, paTests[iTest].iRegSrc2, &paValues[iVal].uSrc2, 32); + + /* source #3 */ + if (paTests[iTest].iRegSrc3 == UINT8_MAX) + { + BS3_ASSERT(paTests[iTest].enmRm >= RM_MEM); + BS3_ASSERT(paTests[iTest].iRegDst != UINT8_MAX && paTests[iTest].iRegSrc1 != UINT8_MAX); + Bs3MemCpy(puMemOp, &paValues[iVal].uSrc3, cbMemOp); + uMemOpExpect = paValues[iVal].uSrc3; + } + else if (fMmxInstr) + Bs3ExtCtxSetMm(pExtCtx, paTests[iTest].iRegSrc3, paValues[iVal].uSrc3.QWords.qw0, BS3EXTCTXTOPMM_ZERO); + else if (fSseInstr) + Bs3ExtCtxSetXmm(pExtCtx, paTests[iTest].iRegSrc3, &paValues[iVal].uSrc3.DQWords.dqw0); + else + Bs3ExtCtxSetYmm(pExtCtx, paTests[iTest].iRegSrc3, &paValues[iVal].uSrc3, 32); + + /* Memory pointer. */ + if (paTests[iTest].enmRm >= RM_MEM) + { + BS3_ASSERT( paTests[iTest].iRegDst == UINT8_MAX + || paTests[iTest].iRegSrc1 == UINT8_MAX + || paTests[iTest].iRegSrc2 == UINT8_MAX + || paTests[iTest].iRegSrc3 == UINT8_MAX); + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, &Ctx.fs, puMemOp); + } + + /* + * Execute. + */ + Bs3TrapSetJmpAndRestoreWithExtCtxAndRm(&Ctx, pExtCtx, &TrapFrame, pExtCtxOut); + + /* + * Check the result: + */ + cErrors = Bs3TestSubErrorCount(); + + if (fMmxInstr && bXcptExpect == X86_XCPT_DB) + { + uSavedFtw = Bs3ExtCtxGetAbridgedFtw(pExtCtx); + Bs3ExtCtxSetAbridgedFtw(pExtCtx, 0xff); /* Observed on 10980xe after pxor mm1, mm2. */ + } + if (bXcptExpect == X86_XCPT_DB && paTests[iTest].iRegDst != UINT8_MAX) + { + if (fMmxInstr) + Bs3ExtCtxSetMm(pExtCtx, paTests[iTest].iRegDst, paValues[iVal].uDstOut.QWords.qw0, BS3EXTCTXTOPMM_SET); + else if (fSseInstr) + Bs3ExtCtxSetXmm(pExtCtx, paTests[iTest].iRegDst, &paValues[iVal].uDstOut.DQWords.dqw0); + else + Bs3ExtCtxSetYmm(pExtCtx, paTests[iTest].iRegDst, &paValues[iVal].uDstOut, cbOperand); + } + Bs3TestCheckExtCtx(pExtCtxOut, pExtCtx, 0 /*fFlags*/, pszMode, idTestStep); + + if (TrapFrame.bXcpt != bXcptExpect) + Bs3TestFailedF("Expected bXcpt = %#x, got %#x", bXcptExpect, TrapFrame.bXcpt); + + /* Kludge! Looks like EFLAGS.AC is cleared when raising #GP in real mode on the 10980XE. WEIRD! */ + if (bMode == BS3_MODE_RM && (Ctx.rflags.u32 & X86_EFL_AC)) + { + if (TrapFrame.Ctx.rflags.u32 & X86_EFL_AC) + Bs3TestFailedF("Expected EFLAGS.AC to be cleared (bXcpt=%d)", TrapFrame.bXcpt); + TrapFrame.Ctx.rflags.u32 |= X86_EFL_AC; + } + Bs3TestCheckRegCtxEx(&TrapFrame.Ctx, &Ctx, bXcptExpect == X86_XCPT_DB ? cbInstr + 1 : 0, 0, + bXcptExpect == X86_XCPT_DB || BS3_MODE_IS_16BIT_SYS(bMode) ? 0 : X86_EFL_RF, + pszMode, idTestStep); + + if ( paTests[iTest].enmRm >= RM_MEM + && Bs3MemCmp(puMemOp, &uMemOpExpect, cbMemOp) != 0) + Bs3TestFailedF("Expected uMemOp %.*Rhxs, got %.*Rhxs", cbMemOp, &uMemOpExpect, cbMemOp, puMemOp); + + if (cErrors != Bs3TestSubErrorCount()) + { + if (paConfigs[iCfg].fAligned) + Bs3TestFailedF("ring-%d/cfg#%u/test#%u/value#%u failed (bXcptExpect=%#x)", + bRing, iCfg, iTest, iVal, bXcptExpect); + else + Bs3TestFailedF("ring-%d/cfg#%u/test#%u/value#%u failed (bXcptExpect=%#x, puMemOp=%p, EFLAGS=%#RX32, CR0=%#RX32)", + bRing, iCfg, iTest, iVal, bXcptExpect, puMemOp, TrapFrame.Ctx.rflags.u32, TrapFrame.Ctx.cr0); + Bs3TestPrintf("\n"); + } + + if (uSavedFtw != 0xff) + Bs3ExtCtxSetAbridgedFtw(pExtCtx, uSavedFtw); + } + } + + bs3CpuInstr3ConfigRestore(&SavedCfg, &Ctx, pExtCtx); + } + + /* + * Next ring. + */ + bRing++; + if (bRing > 3 || bMode == BS3_MODE_RM) + break; + Bs3RegCtxConvertToRingX(&Ctx, bRing); + } + + /* + * Cleanup. + */ + bs3CpuInstr3BufCleanup(pbBuf, cbBuf, bMode); + bs3CpuInstr3FreeExtCtxs(pExtCtx, pExtCtxOut); + return 0; +} + + +/* + * PBLENDVB + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pblendvb_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pblendvb_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_pblendvb_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pblendvb_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpblendvb_XMM1_XMM2_XMM3_XMM4_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpblendvb_XMM1_XMM2_FSxBX_XMM4_icebp); +extern FNBS3FAR bs3CpuInstr3_vpblendvb_XMM8_XMM9_XMM10_XMM11_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpblendvb_XMM8_XMM9_FSxBX_XMM11_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpblendvb_YMM1_YMM2_YMM3_YMM4_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpblendvb_YMM1_YMM2_FSxBX_YMM4_icebp); +extern FNBS3FAR bs3CpuInstr3_vpblendvb_YMM8_YMM9_YMM10_YMM11_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpblendvb_YMM8_YMM9_FSxBX_YMM11_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pblendvb(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST5_VALUES_T const s_aValues[] = + { + { /*mask*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*mask*/ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /*src2*/ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { /*mask*/ RTUINT256_INIT_C(0x0000008000000000, 0x0000091000007f00, 0, 0x8080808080808080), + /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xb1b2b3f4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0xc1c2c3c4c5c6c7c8) }, + { /*mask*/ RTUINT256_INIT_C(0x8080808080808080, 0x8080808080808080, 0x8080808080808080, 0x8080808080808080), + /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8) }, + { /*mask*/ RTUINT256_INIT_C(0x1234567890abcdef, 0xfedcba0987654321, 0xfedcba0987654321, 0x1234567890abcdef), + /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0xddddeeee77778888, 0x111122aa33bbcccc, 0x111122aa33bbcccc, 0xddddeeee77778888) }, + }; + + static BS3CPUINSTR3_TEST5_T const s_aTests16[] = + { + { bs3CpuInstr3_pblendvb_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pblendvb_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, 0, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vpblendvb_XMM1_XMM2_XMM3_XMM4_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpblendvb_XMM1_XMM2_FSxBX_XMM4_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpblendvb_YMM1_YMM2_YMM3_YMM4_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, 3, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpblendvb_YMM1_YMM2_FSxBX_YMM4_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 2, 255, 4, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST5_T const s_aTests32[] = + { + { bs3CpuInstr3_pblendvb_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pblendvb_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, 0, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vpblendvb_XMM1_XMM2_XMM3_XMM4_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpblendvb_XMM1_XMM2_FSxBX_XMM4_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpblendvb_YMM1_YMM2_YMM3_YMM4_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, 3, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpblendvb_YMM1_YMM2_FSxBX_YMM4_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 2, 255, 4, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST5_T const s_aTests64[] = + { + { bs3CpuInstr3_pblendvb_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pblendvb_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pblendvb_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE4_1, 8, 8, 9, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_pblendvb_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 8, 8, 255, 0, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vpblendvb_XMM1_XMM2_XMM3_XMM4_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpblendvb_XMM1_XMM2_FSxBX_XMM4_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpblendvb_XMM8_XMM9_XMM10_XMM11_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, 11, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpblendvb_XMM8_XMM9_FSxBX_XMM11_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, 11, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vpblendvb_YMM1_YMM2_YMM3_YMM4_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, 3, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpblendvb_YMM1_YMM2_FSxBX_YMM4_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 2, 255, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpblendvb_YMM8_YMM9_YMM10_YMM11_icebp_c64, 255, RM_REG, T_AVX_256, 8, 9, 10, 11, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vpblendvb_YMM8_YMM9_FSxBX_YMM11_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 8, 9, 255, 11, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST5_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST5_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType5(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * BLENDPS + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_blendvps_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_blendvps_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_blendvps_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_blendvps_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendvps_XMM1_XMM2_XMM3_XMM4_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendvps_XMM1_XMM2_FSxBX_XMM4_icebp); +extern FNBS3FAR bs3CpuInstr3_vblendvps_XMM8_XMM9_XMM10_XMM11_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vblendvps_XMM8_XMM9_FSxBX_XMM11_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendvps_YMM1_YMM2_YMM3_YMM4_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendvps_YMM1_YMM2_FSxBX_YMM4_icebp); +extern FNBS3FAR bs3CpuInstr3_vblendvps_YMM8_YMM9_YMM10_YMM11_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vblendvps_YMM8_YMM9_FSxBX_YMM11_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_blendvps(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST5_VALUES_T const s_aValues[] = + { + { /*mask*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*mask*/ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /*src2*/ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { /*mask*/ RTUINT256_INIT_C(0x0000008000000000, 0x0000091000007f00, 0, 0x8080808080808080), + /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0xc1c2c3c4c5c6c7c8) }, + { /*mask*/ RTUINT256_INIT_C(0x8080808080808080, 0x8080808080808080, 0x8080808080808080, 0x8080808080808080), + /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8) }, + { /*mask*/ RTUINT256_INIT_C(0x1234567890abcdef, 0xfedcba0987654321, 0xfedcba0987654321, 0x1234567890abcdef), + /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0xddddeeee77778888, 0x1111222233334444, 0x1111222233334444, 0xddddeeee77778888) }, + }; + + static BS3CPUINSTR3_TEST5_T const s_aTests16[] = + { + { bs3CpuInstr3_blendvps_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_blendvps_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, 0, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vblendvps_XMM1_XMM2_XMM3_XMM4_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvps_XMM1_XMM2_FSxBX_XMM4_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvps_YMM1_YMM2_YMM3_YMM4_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, 3, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvps_YMM1_YMM2_FSxBX_YMM4_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 2, 255, 4, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST5_T const s_aTests32[] = + { + { bs3CpuInstr3_blendvps_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_blendvps_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, 0, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vblendvps_XMM1_XMM2_XMM3_XMM4_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvps_XMM1_XMM2_FSxBX_XMM4_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvps_YMM1_YMM2_YMM3_YMM4_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, 3, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvps_YMM1_YMM2_FSxBX_YMM4_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 2, 255, 4, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST5_T const s_aTests64[] = + { + { bs3CpuInstr3_blendvps_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_blendvps_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_blendvps_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE4_1, 8, 8, 9, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_blendvps_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 8, 8, 255, 0, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vblendvps_XMM1_XMM2_XMM3_XMM4_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvps_XMM1_XMM2_FSxBX_XMM4_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvps_XMM8_XMM9_XMM10_XMM11_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, 11, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvps_XMM8_XMM9_FSxBX_XMM11_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, 11, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vblendvps_YMM1_YMM2_YMM3_YMM4_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, 3, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvps_YMM1_YMM2_FSxBX_YMM4_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 2, 255, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvps_YMM8_YMM9_YMM10_YMM11_icebp_c64, 255, RM_REG, T_AVX_256, 8, 9, 10, 11, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvps_YMM8_YMM9_FSxBX_YMM11_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 8, 9, 255, 11, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST5_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST5_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType5(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * BLENDPD + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_blendvpd_XMM1_XMM2_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_blendvpd_XMM1_FSxBX_icebp); +extern FNBS3FAR bs3CpuInstr3_blendvpd_XMM8_XMM9_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_blendvpd_XMM8_FSxBX_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendvpd_XMM1_XMM2_XMM3_XMM4_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendvpd_XMM1_XMM2_FSxBX_XMM4_icebp); +extern FNBS3FAR bs3CpuInstr3_vblendvpd_XMM8_XMM9_XMM10_XMM11_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vblendvpd_XMM8_XMM9_FSxBX_XMM11_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendvpd_YMM1_YMM2_YMM3_YMM4_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vblendvpd_YMM1_YMM2_FSxBX_YMM4_icebp); +extern FNBS3FAR bs3CpuInstr3_vblendvpd_YMM8_YMM9_YMM10_YMM11_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vblendvpd_YMM8_YMM9_FSxBX_YMM11_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_blendvpd(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST5_VALUES_T const s_aValues[] = + { + { /*mask*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src2*/ RTUINT256_INIT_C(0, 0, 0, 0), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0, 0, 0, 0) }, + { /*mask*/ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /*src2*/ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff), + /*src1*/ RTUINT256_INIT_C(0, 0, 0, 0), + /* => */ RTUINT256_INIT_C(0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff) }, + { /*mask*/ RTUINT256_INIT_C(0x0000008000000000, 0x0000091000007f00, 0, 0x8080808080808080), + /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0xc1c2c3c4c5c6c7c8) }, + { /*mask*/ RTUINT256_INIT_C(0x8080808080808080, 0x8080808080808080, 0x8080808080808080, 0x8080808080808080), + /*src2*/ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8), + /*src1*/ RTUINT256_INIT_C(0xb1b2b3b4b5b6b7b8, 0xa1a2a3a4a5a6a7a8, 0x9192939495969798, 0x8182838485868788), + /* => */ RTUINT256_INIT_C(0xf1f2f3f4f5f6f7f8, 0xe1e2e3e4e5e6e7e8, 0xd1d2d3d4d5d6d7d8, 0xc1c2c3c4c5c6c7c8) }, + { /*mask*/ RTUINT256_INIT_C(0x1234567890abcdef, 0xfedcba0987654321, 0xfedcba0987654321, 0x1234567890abcdef), + /*src2*/ RTUINT256_INIT_C(0x5555666677778888, 0x1111222233334444, 0x1111222233334444, 0x5555666677778888), + /*src1*/ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x9999aaaabbbbcccc, 0x9999aaaabbbbcccc, 0xddddeeeeffff0000), + /* => */ RTUINT256_INIT_C(0xddddeeeeffff0000, 0x1111222233334444, 0x1111222233334444, 0xddddeeeeffff0000) }, + }; + + static BS3CPUINSTR3_TEST5_T const s_aTests16[] = + { + { bs3CpuInstr3_blendvpd_XMM1_XMM2_icebp_c16, 255, RM_REG, T_SSE4_1, 1, 1, 2, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_blendvpd_XMM1_FSxBX_icebp_c16, 255, RM_MEM, T_SSE4_1, 1, 1, 255, 0, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vblendvpd_XMM1_XMM2_XMM3_XMM4_icebp_c16, 255, RM_REG, T_AVX_128, 1, 2, 3, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvpd_XMM1_XMM2_FSxBX_XMM4_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvpd_YMM1_YMM2_YMM3_YMM4_icebp_c16, 255, RM_REG, T_AVX_256, 1, 2, 3, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvpd_YMM1_YMM2_FSxBX_YMM4_icebp_c16, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 2, 255, 4, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST5_T const s_aTests32[] = + { + { bs3CpuInstr3_blendvpd_XMM1_XMM2_icebp_c32, 255, RM_REG, T_SSE4_1, 1, 1, 2, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_blendvpd_XMM1_FSxBX_icebp_c32, 255, RM_MEM, T_SSE4_1, 1, 1, 255, 0, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vblendvpd_XMM1_XMM2_XMM3_XMM4_icebp_c32, 255, RM_REG, T_AVX_128, 1, 2, 3, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvpd_XMM1_XMM2_FSxBX_XMM4_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvpd_YMM1_YMM2_YMM3_YMM4_icebp_c32, 255, RM_REG, T_AVX_256, 1, 2, 3, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvpd_YMM1_YMM2_FSxBX_YMM4_icebp_c32, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 2, 255, 4, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST5_T const s_aTests64[] = + { + { bs3CpuInstr3_blendvpd_XMM1_XMM2_icebp_c64, 255, RM_REG, T_SSE4_1, 1, 1, 2, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_blendvpd_XMM1_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 1, 1, 255, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_blendvpd_XMM8_XMM9_icebp_c64, 255, RM_REG, T_SSE4_1, 8, 8, 9, 0, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_blendvpd_XMM8_FSxBX_icebp_c64, 255, RM_MEM, T_SSE4_1, 8, 8, 255, 0, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vblendvpd_XMM1_XMM2_XMM3_XMM4_icebp_c64, 255, RM_REG, T_AVX_128, 1, 2, 3, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvpd_XMM1_XMM2_FSxBX_XMM4_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 1, 2, 255, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvpd_XMM8_XMM9_XMM10_XMM11_icebp_c64, 255, RM_REG, T_AVX_128, 8, 9, 10, 11, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvpd_XMM8_XMM9_FSxBX_XMM11_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_128, 8, 9, 255, 11, RT_ELEMENTS(s_aValues), s_aValues }, + + { bs3CpuInstr3_vblendvpd_YMM1_YMM2_YMM3_YMM4_icebp_c64, 255, RM_REG, T_AVX_256, 1, 2, 3, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvpd_YMM1_YMM2_FSxBX_YMM4_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 1, 2, 255, 4, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvpd_YMM8_YMM9_YMM10_YMM11_icebp_c64, 255, RM_REG, T_AVX_256, 8, 9, 10, 11, RT_ELEMENTS(s_aValues), s_aValues }, + { bs3CpuInstr3_vblendvpd_YMM8_YMM9_FSxBX_YMM11_icebp_c64, X86_XCPT_DB, RM_MEM, T_AVX_256, 8, 9, 255, 11, RT_ELEMENTS(s_aValues), s_aValues }, + }; + static BS3CPUINSTR3_TEST5_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST5_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType5(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig4, RT_ELEMENTS(g_aXcptConfig4)); +} + + +/* + * Test type #6 - MM/XMM/YMM <- GPR, no VVVV. + * + * Used probably only by the PINSRW testcases + */ + +typedef struct BS3CPUINSTR3_TEST6_VALUES_T +{ + RTUINT256U uMediaSrc; + uint64_t uGpr; + RTUINT256U uMediaDst; +} BS3CPUINSTR3_TEST6_VALUES_T; + +typedef struct BS3CPUINSTR3_TEST6_T +{ + FPFNBS3FAR pfnWorker; + uint8_t bAvxMisalignXcpt; + uint8_t enmRm; + uint8_t enmType; + uint8_t cbGpr; + uint8_t cBitsGprValMask; + uint8_t iGprReg; + uint8_t iMediaRegSrc; + uint8_t iMediaRegDst; + uint8_t cValues; + BS3CPUINSTR3_TEST6_VALUES_T const BS3_FAR *paValues; +} BS3CPUINSTR3_TEST6_T; + +typedef struct BS3CPUINSTR3_TEST6_MODE_T +{ + BS3CPUINSTR3_TEST6_T const BS3_FAR *paTests; + unsigned cTests; +} BS3CPUINSTR3_TEST6_MODE_T; + +/** Initializer for a BS3CPUINSTR3_TEST6_MODE_T array (three entries). */ +#define BS3CPUINSTR3_TEST6_MODES_INIT(a_aTests16, a_aTests32, a_aTests64) \ + { { a_aTests16, RT_ELEMENTS(a_aTests16) }, { a_aTests32, RT_ELEMENTS(a_aTests32) }, { a_aTests64, RT_ELEMENTS(a_aTests64) } } + + +/** + * Test type #6 worker. + */ +static uint8_t bs3CpuInstr3_WorkerTestType6(uint8_t bMode, BS3CPUINSTR3_TEST6_T const BS3_FAR *paTests, unsigned cTests, + PCBS3CPUINSTR3_CONFIG_T paConfigs, unsigned cConfigs) +{ + BS3REGCTX Ctx; + BS3TRAPFRAME TrapFrame; + const char BS3_FAR * const pszMode = Bs3GetModeName(bMode); + uint8_t BS3_FAR *pbBuf = g_pbBuf; + uint32_t cbBuf = g_cbBuf; + uint8_t bRing = BS3_MODE_IS_V86(bMode) ? 3 : 0; + PBS3EXTCTX pExtCtxOut; + PBS3EXTCTX pExtCtx = bs3CpuInstr3AllocExtCtxs(&pExtCtxOut); + if (!pExtCtx) + return 0; + + /* Ensure the structures are allocated before we sample the stack pointer. */ + Bs3MemSet(&Ctx, 0, sizeof(Ctx)); + Bs3MemSet(&TrapFrame, 0, sizeof(TrapFrame)); + + /* + * Create test context. + */ + pbBuf = bs3CpuInstr3BufSetup(pbBuf, &cbBuf, bMode); + Bs3RegCtxSaveForMode(&Ctx, bMode, 1024); + bs3CpuInstr3SetupSseAndAvx(&Ctx, pExtCtx); + + /* + * Run the tests in all rings since alignment issues may behave + * differently in ring-3 compared to ring-0. + */ + for (;;) + { + unsigned iCfg; + for (iCfg = 0; iCfg < cConfigs; iCfg++) + { + unsigned iTest; + BS3CPUINSTR3_CONFIG_SAVED_T SavedCfg; + if (!bs3CpuInstr3ConfigReconfigure(&SavedCfg, &Ctx, pExtCtx, &paConfigs[iCfg], bMode)) + continue; /* unsupported config */ + + /* + * Iterate the tests. + */ + for (iTest = 0; iTest < cTests; iTest++) + { + BS3CPUINSTR3_TEST6_VALUES_T const BS3_FAR *paValues = paTests[iTest].paValues; + uint8_t const cbInstr = ((uint8_t const BS3_FAR *)(uintptr_t)paTests[iTest].pfnWorker)[-1]; + unsigned const cValues = paTests[iTest].cValues; + bool const fMmxInstr = paTests[iTest].enmType < T_SSE; + bool const fSseInstr = paTests[iTest].enmType >= T_SSE && paTests[iTest].enmType < T_AVX_128; + bool const fAvxInstr = paTests[iTest].enmType >= T_AVX_128; + uint8_t const cbOperand = paTests[iTest].enmType < T_128BITS ? 64/8 + : paTests[iTest].enmType < T_256BITS ? 128/8 : 256/8; + uint8_t const cbMemOp = bs3CpuInstr3MemOpSize(cbOperand, paTests[iTest].enmRm); + uint8_t const cbAlign = RT_MIN(cbOperand, 16); + PRTUINT256U puMemOp = bs3CpuInstr3BufForOperand(pbBuf, cbBuf, cbMemOp, cbAlign, &paConfigs[iCfg]); + uint8_t bXcptExpect = !g_afTypeSupports[paTests[iTest].enmType] ? X86_XCPT_UD + : fMmxInstr ? paConfigs[iCfg].bXcptMmx + : fSseInstr ? paConfigs[iCfg].bXcptSse + : BS3_MODE_IS_RM_OR_V86(bMode) ? X86_XCPT_UD : paConfigs[iCfg].bXcptAvx; + uint64_t const fGprValMask = paTests[iTest].cBitsGprValMask == 64 ? UINT64_MAX + : RT_BIT_64(paTests[iTest].cBitsGprValMask) - 1; + uint16_t idTestStep = bRing * 10000 + iCfg * 100 + iTest * 10; + unsigned iVal; + + /* If testing unaligned memory accesses, skip register-only tests. This allows + setting bXcptMmx, bXcptSse and bXcptAvx to reflect the misaligned exceptions. */ + if (paTests[iTest].enmRm == RM_REG && (!paConfigs[iCfg].fAligned || paConfigs[iCfg].fAlignCheck)) + continue; + + /* #AC is only raised in ring-3.: */ + if (bXcptExpect == X86_XCPT_AC) + { + if (bRing != 3) + bXcptExpect = X86_XCPT_DB; + else if (fAvxInstr) + bXcptExpect = paTests[iTest].bAvxMisalignXcpt; /* they generally don't raise #AC */ + } + + Bs3RegCtxSetRipCsFromCurPtr(&Ctx, paTests[iTest].pfnWorker); + + /* + * Iterate the test values and do the actual testing. + */ + for (iVal = 0; iVal < cValues; iVal++, idTestStep++) + { + uint16_t cErrors; + uint16_t uSavedFtw = 0xff; + RTUINT256U uMemOpExpect; + + /* + * Set up the context and some expectations. + */ + /* source - media */ + BS3_ASSERT(paTests[iTest].iMediaRegSrc != UINT8_MAX); + if (fMmxInstr) + Bs3ExtCtxSetMm(pExtCtx, paTests[iTest].iMediaRegSrc, paValues[iVal].uMediaSrc.QWords.qw0, BS3EXTCTXTOPMM_ZERO); + else if (fSseInstr) + Bs3ExtCtxSetXmm(pExtCtx, paTests[iTest].iMediaRegSrc, &paValues[iVal].uMediaSrc.DQWords.dqw0); + else + Bs3ExtCtxSetYmm(pExtCtx, paTests[iTest].iMediaRegSrc, &paValues[iVal].uMediaSrc, 32); + + /* source - gpr/mem */ + if (paTests[iTest].iGprReg == UINT8_MAX) + { + BS3_ASSERT(paTests[iTest].enmRm >= RM_MEM); + Bs3MemSet(&uMemOpExpect, 0xcc, sizeof(uMemOpExpect)); + if (bXcptExpect == X86_XCPT_DB) + switch (paTests[iTest].cbGpr) + { + case 1: uMemOpExpect.au8[0] = (uint8_t) (paValues[iVal].uGpr & fGprValMask); break; + case 2: uMemOpExpect.au16[0] = (uint16_t)(paValues[iVal].uGpr & fGprValMask); break; + case 4: uMemOpExpect.au32[0] = (uint32_t)(paValues[iVal].uGpr & fGprValMask); break; + case 8: uMemOpExpect.au64[0] = (paValues[iVal].uGpr & fGprValMask); break; + default: BS3_ASSERT(0); + } + Bs3MemCpy(puMemOp, &uMemOpExpect, cbMemOp); + } + else + Bs3RegCtxSetGpr(&Ctx, paTests[iTest].iGprReg, paValues[iVal].uGpr & fGprValMask, paTests[iTest].cbGpr); + + /* Memory pointer. */ + if (paTests[iTest].enmRm >= RM_MEM) + { + BS3_ASSERT(paTests[iTest].iGprReg == UINT8_MAX || paTests[iTest].iMediaRegSrc == UINT8_MAX); + Bs3RegCtxSetGrpSegFromCurPtr(&Ctx, &Ctx.rbx, &Ctx.fs, puMemOp); + } + + /* + * Execute. + */ + Bs3TrapSetJmpAndRestoreWithExtCtxAndRm(&Ctx, pExtCtx, &TrapFrame, pExtCtxOut); + + /* + * Check the result: + */ + cErrors = Bs3TestSubErrorCount(); + + if (fMmxInstr && bXcptExpect == X86_XCPT_DB) + { + uSavedFtw = Bs3ExtCtxGetAbridgedFtw(pExtCtx); + Bs3ExtCtxSetAbridgedFtw(pExtCtx, 0xff); + } + + if (bXcptExpect == X86_XCPT_DB) + { + if (fMmxInstr) + Bs3ExtCtxSetMm(pExtCtx, paTests[iTest].iMediaRegDst, paValues[iVal].uMediaDst.QWords.qw0, BS3EXTCTXTOPMM_SET); + else if (fSseInstr) + Bs3ExtCtxSetXmm(pExtCtx, paTests[iTest].iMediaRegDst, &paValues[iVal].uMediaDst.DQWords.dqw0); + else + Bs3ExtCtxSetYmm(pExtCtx, paTests[iTest].iMediaRegDst, &paValues[iVal].uMediaDst, 32); + } + + Bs3TestCheckExtCtx(pExtCtxOut, pExtCtx, 0 /*fFlags*/, pszMode, idTestStep); + + if (TrapFrame.bXcpt != bXcptExpect) + Bs3TestFailedF("Expected bXcpt = %#x, got %#x", bXcptExpect, TrapFrame.bXcpt); + + /* Kludge! Looks like EFLAGS.AC is cleared when raising #GP in real mode on the 10980XE. WEIRD! */ + if (bMode == BS3_MODE_RM && (Ctx.rflags.u32 & X86_EFL_AC)) + { + if (TrapFrame.Ctx.rflags.u32 & X86_EFL_AC) + Bs3TestFailedF("Expected EFLAGS.AC to be cleared (bXcpt=%d)", TrapFrame.bXcpt); + TrapFrame.Ctx.rflags.u32 |= X86_EFL_AC; + } + Bs3TestCheckRegCtxEx(&TrapFrame.Ctx, &Ctx, bXcptExpect == X86_XCPT_DB ? cbInstr + 1 : 0, 0, + bXcptExpect == X86_XCPT_DB || BS3_MODE_IS_16BIT_SYS(bMode) ? 0 : X86_EFL_RF, + pszMode, idTestStep); + + if ( paTests[iTest].enmRm >= RM_MEM + && Bs3MemCmp(puMemOp, &uMemOpExpect, cbMemOp) != 0) + Bs3TestFailedF("Expected uMemOp %.*Rhxs, got %.*Rhxs", cbMemOp, &uMemOpExpect, cbMemOp, puMemOp); + + if (cErrors != Bs3TestSubErrorCount()) + { + if (paConfigs[iCfg].fAligned) + Bs3TestFailedF("ring-%d/cfg#%u/test#%u/value#%u failed (bXcptExpect=%#x)", + bRing, iCfg, iTest, iVal, bXcptExpect); + else + Bs3TestFailedF("ring-%d/cfg#%u/test#%u/value#%u failed (bXcptExpect=%#x, puMemOp=%p, EFLAGS=%#RX32, CR0=%#RX32)", + bRing, iCfg, iTest, iVal, bXcptExpect, puMemOp, TrapFrame.Ctx.rflags.u32, TrapFrame.Ctx.cr0); + Bs3TestPrintf("\n"); + } + + if (uSavedFtw != 0xff) + Bs3ExtCtxSetAbridgedFtw(pExtCtx, uSavedFtw); + } + } + + bs3CpuInstr3ConfigRestore(&SavedCfg, &Ctx, pExtCtx); + } + + /* + * Next ring. + */ + bRing++; + if (bRing > 3 || bMode == BS3_MODE_RM) + break; + Bs3RegCtxConvertToRingX(&Ctx, bRing); + } + + /* + * Cleanup. + */ + bs3CpuInstr3BufCleanup(pbBuf, cbBuf, bMode); + bs3CpuInstr3FreeExtCtxs(pExtCtx, pExtCtxOut); + return 0; +} + + +/* + * [V]PINSRW. + */ +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pinsrw_MM1_EDX_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pinsrw_MM1_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_pinsrw_MM1_R9D_000h_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pinsrw_MM1_EDX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pinsrw_MM1_FSxBX_0FFh_icebp); +extern FNBS3FAR bs3CpuInstr3_pinsrw_MM1_R9D_0FFh_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pinsrw_XMM1_EDX_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pinsrw_XMM1_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_pinsrw_XMM8_R9D_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pinsrw_XMM8_FSxBX_000h_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pinsrw_XMM1_EDX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_pinsrw_XMM1_FSxBX_0FFh_icebp); +extern FNBS3FAR bs3CpuInstr3_pinsrw_XMM8_R9D_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_pinsrw_XMM8_FSxBX_0FFh_icebp_c64; + +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpinsrw_XMM1_XMM2_EDX_000h_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpinsrw_XMM1_XMM2_FSxBX_000h_icebp); +extern FNBS3FAR bs3CpuInstr3_vpinsrw_XMM8_XMM9_R9D_000h_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpinsrw_XMM8_XMM9_FSxBX_000h_icebp_c64; +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpinsrw_XMM1_XMM2_EDX_0FFh_icebp); +BS3_FNBS3FAR_PROTOTYPES_CMN(bs3CpuInstr3_vpinsrw_XMM1_XMM2_FSxBX_0FFh_icebp); +extern FNBS3FAR bs3CpuInstr3_vpinsrw_XMM8_XMM9_R9D_0FFh_icebp_c64; +extern FNBS3FAR bs3CpuInstr3_vpinsrw_XMM8_XMM9_FSxBX_0FFh_icebp_c64; + +BS3_DECL_FAR(uint8_t) bs3CpuInstr3_v_pinsrw(uint8_t bMode) +{ + static BS3CPUINSTR3_TEST6_VALUES_T const s_aValues00[] = + { + /* Media source GPR word Media dest. */ + { RTUINT256_INIT_C(0, 0, 0, 0), UINT64_C(0x1234), RTUINT256_INIT_C(0, 0, 0, 0x0000000000001234) }, + }; + static BS3CPUINSTR3_TEST6_VALUES_T const s_aValuesFF_64[] = + { + /* Media source GPR word Media dest. */ + { RTUINT256_INIT_C(0, 0, 0, 0), UINT64_C(0x1234), RTUINT256_INIT_C(0, 0, 0, 0x1234000000000000) }, + }; + static BS3CPUINSTR3_TEST6_VALUES_T const s_aValuesFF[] = + { + /* Media source GPR word Media dest. */ + { RTUINT256_INIT_C(0, 0, 0, 0), UINT64_C(0x1234), RTUINT256_INIT_C(0, 0, 0x1234000000000000, 0) }, + }; + + static BS3CPUINSTR3_TEST6_T const s_aTests16[] = + { + { bs3CpuInstr3_pinsrw_MM1_EDX_000h_icebp_c16, 255, RM_REG, T_MMX_SSE, 4, 32, 2, 1, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pinsrw_MM1_FSxBX_000h_icebp_c16, 255, RM_MEM32, T_MMX_SSE, 4, 32, 255, 1, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pinsrw_MM1_EDX_0FFh_icebp_c16, 255, RM_REG, T_MMX_SSE, 4, 32, 2, 1, 1, RT_ELEMENTS(s_aValuesFF_64), s_aValuesFF_64 }, + { bs3CpuInstr3_pinsrw_MM1_FSxBX_0FFh_icebp_c16, 255, RM_MEM32, T_MMX_SSE, 4, 32, 255, 1, 1, RT_ELEMENTS(s_aValuesFF_64), s_aValuesFF_64 }, + + { bs3CpuInstr3_pinsrw_XMM1_EDX_000h_icebp_c16, 255, RM_REG, T_SSE, 4, 32, 2, 1, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pinsrw_XMM1_FSxBX_000h_icebp_c16, 255, RM_MEM32, T_SSE, 4, 32, 255, 1, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pinsrw_XMM1_EDX_0FFh_icebp_c16, 255, RM_REG, T_SSE, 4, 32, 2, 1, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pinsrw_XMM1_FSxBX_0FFh_icebp_c16, 255, RM_MEM32, T_SSE, 4, 32, 255, 1, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_vpinsrw_XMM1_XMM2_EDX_000h_icebp_c16, 255, RM_REG, T_AVX_128, 4, 32, 2, 2, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpinsrw_XMM1_XMM2_FSxBX_000h_icebp_c16, X86_XCPT_AC, RM_MEM32, T_AVX_128, 4, 32, 255, 2, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpinsrw_XMM1_XMM2_EDX_0FFh_icebp_c16, 255, RM_REG, T_AVX_128, 4, 32, 2, 2, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpinsrw_XMM1_XMM2_FSxBX_0FFh_icebp_c16, X86_XCPT_AC, RM_MEM32, T_AVX_128, 4, 32, 255, 2, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + }; + static BS3CPUINSTR3_TEST6_T const s_aTests32[] = + { + { bs3CpuInstr3_pinsrw_MM1_EDX_000h_icebp_c32, 255, RM_REG, T_MMX_SSE, 4, 32, 2, 1, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pinsrw_MM1_FSxBX_000h_icebp_c32, 255, RM_MEM32, T_MMX_SSE, 4, 32, 255, 1, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pinsrw_MM1_EDX_0FFh_icebp_c32, 255, RM_REG, T_MMX_SSE, 4, 32, 2, 1, 1, RT_ELEMENTS(s_aValuesFF_64), s_aValuesFF_64 }, + { bs3CpuInstr3_pinsrw_MM1_FSxBX_0FFh_icebp_c32, 255, RM_MEM32, T_MMX_SSE, 4, 32, 255, 1, 1, RT_ELEMENTS(s_aValuesFF_64), s_aValuesFF_64 }, + + { bs3CpuInstr3_pinsrw_XMM1_EDX_000h_icebp_c32, 255, RM_REG, T_SSE, 4, 32, 2, 1, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pinsrw_XMM1_FSxBX_000h_icebp_c32, 255, RM_MEM32, T_SSE, 4, 32, 255, 1, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pinsrw_XMM1_EDX_0FFh_icebp_c32, 255, RM_REG, T_SSE, 4, 32, 2, 1, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pinsrw_XMM1_FSxBX_0FFh_icebp_c32, 255, RM_MEM32, T_SSE, 4, 32, 255, 1, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_vpinsrw_XMM1_XMM2_EDX_000h_icebp_c32, 255, RM_REG, T_AVX_128, 4, 32, 2, 2, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpinsrw_XMM1_XMM2_FSxBX_000h_icebp_c32, X86_XCPT_AC, RM_MEM32, T_AVX_128, 4, 32, 255, 2, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpinsrw_XMM1_XMM2_EDX_0FFh_icebp_c32, 255, RM_REG, T_AVX_128, 4, 32, 2, 2, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpinsrw_XMM1_XMM2_FSxBX_0FFh_icebp_c32, X86_XCPT_AC, RM_MEM32, T_AVX_128, 4, 32, 255, 2, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + }; + static BS3CPUINSTR3_TEST6_T const s_aTests64[] = + { + { bs3CpuInstr3_pinsrw_MM1_EDX_000h_icebp_c64, 255, RM_REG, T_MMX_SSE, 4, 32, 2, 1, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pinsrw_MM1_R9D_000h_icebp_c64, 255, RM_REG, T_MMX_SSE, 4, 32, 9, 1, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pinsrw_MM1_FSxBX_000h_icebp_c64, 255, RM_MEM32, T_MMX_SSE, 4, 32, 255, 1, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pinsrw_MM1_EDX_0FFh_icebp_c64, 255, RM_REG, T_MMX_SSE, 4, 32, 2, 1, 1, RT_ELEMENTS(s_aValuesFF_64), s_aValuesFF_64 }, + { bs3CpuInstr3_pinsrw_MM1_R9D_0FFh_icebp_c64, 255, RM_REG, T_MMX_SSE, 4, 32, 9, 1, 1, RT_ELEMENTS(s_aValuesFF_64), s_aValuesFF_64 }, + { bs3CpuInstr3_pinsrw_MM1_FSxBX_0FFh_icebp_c64, 255, RM_MEM32, T_MMX_SSE, 4, 32, 255, 1, 1, RT_ELEMENTS(s_aValuesFF_64), s_aValuesFF_64 }, + + { bs3CpuInstr3_pinsrw_XMM1_EDX_000h_icebp_c64, 255, RM_REG, T_SSE, 4, 32, 2, 1, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pinsrw_XMM1_FSxBX_000h_icebp_c64, 255, RM_MEM32, T_SSE, 4, 32, 255, 1, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pinsrw_XMM1_EDX_0FFh_icebp_c64, 255, RM_REG, T_SSE, 4, 32, 2, 1, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pinsrw_XMM1_FSxBX_0FFh_icebp_c64, 255, RM_MEM32, T_SSE, 4, 32, 255, 1, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_pinsrw_XMM8_R9D_000h_icebp_c64, 255, RM_REG, T_SSE, 4, 32, 9, 8, 8, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pinsrw_XMM8_FSxBX_000h_icebp_c64, 255, RM_MEM32, T_SSE, 4, 32, 255, 8, 8, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_pinsrw_XMM8_R9D_0FFh_icebp_c64, 255, RM_REG, T_SSE, 4, 32, 9, 8, 8, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_pinsrw_XMM8_FSxBX_0FFh_icebp_c64, 255, RM_MEM32, T_SSE, 4, 32, 255, 8, 8, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_vpinsrw_XMM1_XMM2_EDX_000h_icebp_c64, 255, RM_REG, T_AVX_128, 4, 32, 2, 2, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpinsrw_XMM1_XMM2_FSxBX_000h_icebp_c64, X86_XCPT_AC, RM_MEM32, T_AVX_128, 4, 32, 255, 2, 1, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpinsrw_XMM1_XMM2_EDX_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 4, 32, 2, 2, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpinsrw_XMM1_XMM2_FSxBX_0FFh_icebp_c64, X86_XCPT_AC, RM_MEM32, T_AVX_128, 4, 32, 255, 2, 1, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + + { bs3CpuInstr3_vpinsrw_XMM8_XMM9_R9D_000h_icebp_c64, 255, RM_REG, T_AVX_128, 4, 32, 9, 9, 8, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpinsrw_XMM8_XMM9_FSxBX_000h_icebp_c64, X86_XCPT_AC, RM_MEM32, T_AVX_128, 4, 32, 255, 9, 8, RT_ELEMENTS(s_aValues00), s_aValues00 }, + { bs3CpuInstr3_vpinsrw_XMM8_XMM9_R9D_0FFh_icebp_c64, 255, RM_REG, T_AVX_128, 4, 32, 9, 9, 8, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + { bs3CpuInstr3_vpinsrw_XMM8_XMM9_FSxBX_0FFh_icebp_c64, X86_XCPT_AC, RM_MEM32, T_AVX_128, 4, 32, 255, 9, 8, RT_ELEMENTS(s_aValuesFF), s_aValuesFF }, + }; + static BS3CPUINSTR3_TEST6_MODE_T const s_aTests[3] = BS3CPUINSTR3_TEST6_MODES_INIT(s_aTests16, s_aTests32, s_aTests64); + unsigned const iTest = BS3CPUINSTR3_TEST_MODES_INDEX(bMode); + return bs3CpuInstr3_WorkerTestType6(bMode, s_aTests[iTest].paTests, s_aTests[iTest].cTests, + g_aXcptConfig5, RT_ELEMENTS(g_aXcptConfig5)); +} + + + + +/** + * The 32-bit protected mode main function. + * + * The tests a driven by 32-bit test drivers, even for real-mode tests (though + * we'll switch between PE32 and RM for each test step we perform). Given that + * we test MMX, SSE and AVX here, we don't need to worry about 286 or 8086. + * + * Some extra steps needs to be taken to properly handle extended state in LM64 + * (Bs3ExtCtxRestoreEx & Bs3ExtCtxSaveEx) and when testing real mode + * (Bs3RegCtxSaveForMode & Bs3TrapSetJmpAndRestoreWithExtCtxAndRm). + */ +BS3_DECL(void) Main_pe32() +{ + static const BS3TESTMODEBYONEENTRY g_aTests[] = + { +#if 1 /*ndef DEBUG_bird*/ +# define ALL_TESTS +#endif +#if defined(ALL_TESTS) + { "[v]andps/[v]andpd/[v]pand", bs3CpuInstr3_v_andps_andpd_pand, 0 }, + { "[v]andnps/[v]andnpd/[v]pandn", bs3CpuInstr3_v_andnps_andnpd_pandn, 0 }, + { "[v]orps/[v]orpd/[v]or", bs3CpuInstr3_v_orps_orpd_por, 0 }, + { "[v]xorps/[v]xorpd/[v]pxor", bs3CpuInstr3_v_xorps_xorpd_pxor, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]pcmpgtb/[v]pcmpgtw/[v]pcmpgtd/[v]pcmpgtq", bs3CpuInstr3_v_pcmpgtb_pcmpgtw_pcmpgtd_pcmpgtq, 0 }, + { "[v]pcmpeqb/[v]pcmpeqw/[v]pcmpeqd/[v]pcmpeqq", bs3CpuInstr3_v_pcmpeqb_pcmpeqw_pcmpeqd_pcmpeqq, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]paddb/[v]paddw/[v]paddd/[v]paddq", bs3CpuInstr3_v_paddb_paddw_paddd_paddq, 0 }, + { "[v]psubb/[v]psubw/[v]psubd/[v]psubq", bs3CpuInstr3_v_psubb_psubw_psubd_psubq, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]pmullw/[v]pmulld", bs3CpuInstr3_v_pmullw_pmulld, 0 }, + { "[v]pmulhw", bs3CpuInstr3_v_pmulhw, 0 }, + { "[v]pmulhuw", bs3CpuInstr3_v_pmulhuw, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]pmovmskb", bs3CpuInstr3_v_pmovmskb, 0 }, + { "pshufb", bs3CpuInstr3_pshufb, 0 }, + { "pshufw", bs3CpuInstr3_pshufw, 0 }, + { "[v]pshufhw", bs3CpuInstr3_v_pshufhw, 0 }, + { "[v]pshuflw", bs3CpuInstr3_v_pshuflw, 0 }, + { "[v]pshufd", bs3CpuInstr3_v_pshufd, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]punpckhbw", bs3CpuInstr3_v_punpckhbw, 0 }, + { "[v]punpckhwd", bs3CpuInstr3_v_punpckhwd, 0 }, + { "[v]punpckhdq", bs3CpuInstr3_v_punpckhdq, 0 }, + { "[v]punpckhqdq", bs3CpuInstr3_v_punpckhqdq, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]punpcklbw", bs3CpuInstr3_v_punpcklbw, 0 }, + { "[v]punpcklwd", bs3CpuInstr3_v_punpcklwd, 0 }, + { "[v]punpckldq", bs3CpuInstr3_v_punpckldq, 0 }, + { "[v]punpcklqdq", bs3CpuInstr3_v_punpcklqdq, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]packsswb", bs3CpuInstr3_v_packsswb, 0 }, + { "[v]packssdw", bs3CpuInstr3_v_packssdw, 0 }, + { "[v]packuswb", bs3CpuInstr3_v_packuswb, 0 }, + { "[v]packusdw", bs3CpuInstr3_v_packusdw, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]pmaxub/[v]pmaxuw/[v]pmaxud", bs3CpuInstr3_v_pmaxub_pmaxuw_pmaxud, 0 }, + { "[v]pmaxsb/[v]pmaxsw/[v]pmaxsd", bs3CpuInstr3_v_pmaxsb_pmaxsw_pmaxsd, 0 }, + { "[v]pminub/[v]pminuw/[v]pminud", bs3CpuInstr3_v_pminub_pminuw_pminud, 0 }, + { "[v]pminsb/[v]pminsw/[v]pminsd", bs3CpuInstr3_v_pminsb_pminsw_pminsd, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]movntdqa", bs3CpuInstr3_v_movntdqa, 0 }, + { "[v]movntdq", bs3CpuInstr3_v_movntdq, 0 }, + { "[v]movntps_movntpd", bs3CpuInstr3_v_movntps_movntpd, 0 }, + { "[v]movups", bs3CpuInstr3_v_movups, 0 }, + { "[v]movupd", bs3CpuInstr3_v_movupd, 0 }, + { "[v]movss", bs3CpuInstr3_v_movss, 0 }, + { "[v]movsd", bs3CpuInstr3_v_movsd, 0 }, + { "[v]movhlps", bs3CpuInstr3_v_movhlps, 0 }, + { "[v]movlps/[v]movlpd", bs3CpuInstr3_v_movlps_movlpd, 0 }, + { "[v]movhps/[v]movhpd", bs3CpuInstr3_v_movhps_movhpd, 0 }, + { "[v]movsldup", bs3CpuInstr3_v_movsldup, 0 }, + { "[v]movshdup", bs3CpuInstr3_v_movshdup, 0 }, + { "[v]movddup", bs3CpuInstr3_v_movddup, 0 }, + { "[v]movaps_movapd", bs3CpuInstr3_v_movaps_movapd, 0 }, + { "[v]movd_movq", bs3CpuInstr3_v_movd_movq, 0 }, + { "[v]movdqu", bs3CpuInstr3_v_movdqu, 0 }, + { "[v]movdqa", bs3CpuInstr3_v_movdqa, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]ptest", bs3CpuInstr3_v_ptest, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]pavgb/[v]pavgw", bs3CpuInstr3_v_pavgb_pavgw, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]pabsb/[v]pabsw/[v]pabsd", bs3CpuInstr3_v_pabsb_pabsw_pabsd, 0 }, + { "[v]psignb/[v]psignw/[v]psignd", bs3CpuInstr3_v_psignb_psignw_psignd, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]phaddw/[v]phaddd", bs3CpuInstr3_v_phaddw_phaddd, 0 }, + { "[v]phsubw/[v]phsubd", bs3CpuInstr3_v_phsubw_phsubd, 0 }, + { "[v]phaddsw", bs3CpuInstr3_v_phaddsw, 0 }, + { "[v]phsubsw", bs3CpuInstr3_v_phsubsw, 0 }, + { "[v]pmaddubsw", bs3CpuInstr3_v_pmaddubsw, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]pmulhrsw", bs3CpuInstr3_v_pmulhrsw, 0 }, + { "[v]psadbw", bs3CpuInstr3_v_psadbw, 0 }, + { "[v]pmuldq", bs3CpuInstr3_v_pmuldq, 0 }, + { "[v]pmuludq", bs3CpuInstr3_v_pmuludq, 0 }, + { "[v]punpcklps/[v]punpcklpd", bs3CpuInstr3_v_punpcklps_punpcklpd, 0 }, + { "[v]punpckhps/[v]punpckhpd", bs3CpuInstr3_v_punpckhps_punpckhpd, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]pmovsxbw/[v]pmovsxbd/[v]pmovsxbq/[v]pmovsxwd/[v]pmovsxwq/[v]pmovsxdq", + bs3CpuInstr3_v_pmovsxbw_pmovsxbd_pmovsxbq_pmovsxwd_pmovsxwq_pmovsxdq, 0 }, + { "[v]pmovzxbw/[v]pmovzxbd/[v]pmovzxbq/[v]pmovzxwd/[v]pmovzxwq/[v]pmovzxdq", + bs3CpuInstr3_v_pmovzxbw_pmovzxbd_pmovzxbq_pmovzxwd_pmovzxwq_pmovzxdq, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]shufps", bs3CpuInstr3_v_shufps, 0 }, + { "[v]shufpd", bs3CpuInstr3_v_shufpd, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]lddqu", bs3CpuInstr3_v_lddqu, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]phminposuw", bs3CpuInstr3_v_phminposuw, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]pblendvb", bs3CpuInstr3_v_pblendvb, 0 }, + { "[v]blendvps", bs3CpuInstr3_v_blendvps, 0 }, + { "[v]blendvpd", bs3CpuInstr3_v_blendvpd, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]palignr", bs3CpuInstr3_v_palignr, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]pblendw", bs3CpuInstr3_v_pblendw, 0 }, + { "[v]blendps", bs3CpuInstr3_v_blendps, 0 }, + { "[v]blendpd", bs3CpuInstr3_v_blendpd, 0 }, +#endif +#if defined(ALL_TESTS) + { "[v]pclmulqdq", bs3CpuInstr3_v_pclmulqdq, 0 }, + { "[v]pinsrw", bs3CpuInstr3_v_pinsrw, 0 }, + { "[v]pextrw", bs3CpuInstr3_v_pextrw, 0 }, + { "[v]movmskps/[v]movmskpd", bs3CpuInstr3_v_movmskps_movmskpd, 0 }, + +#endif + }; + Bs3TestInit("bs3-cpu-instr-3"); + + /* + * Initialize globals. + */ + if (g_uBs3CpuDetected & BS3CPU_F_CPUID) + { + uint32_t fEbx, fEcx, fEdx; + ASMCpuIdExSlow(1, 0, 0, 0, NULL, NULL, &fEcx, &fEdx); + g_afTypeSupports[T_MMX] = RT_BOOL(fEdx & X86_CPUID_FEATURE_EDX_MMX); + g_afTypeSupports[T_MMX_SSE] = RT_BOOL(fEdx & X86_CPUID_FEATURE_EDX_SSE); + g_afTypeSupports[T_MMX_SSE2] = RT_BOOL(fEdx & X86_CPUID_FEATURE_EDX_SSE2); + g_afTypeSupports[T_MMX_SSSE3] = RT_BOOL(fEdx & X86_CPUID_FEATURE_ECX_SSSE3); + g_afTypeSupports[T_SSE] = RT_BOOL(fEdx & X86_CPUID_FEATURE_EDX_SSE); + g_afTypeSupports[T_SSE2] = RT_BOOL(fEdx & X86_CPUID_FEATURE_EDX_SSE2); + g_afTypeSupports[T_SSE3] = RT_BOOL(fEcx & X86_CPUID_FEATURE_ECX_SSE3); + g_afTypeSupports[T_SSSE3] = RT_BOOL(fEcx & X86_CPUID_FEATURE_ECX_SSSE3); + g_afTypeSupports[T_SSE4_1] = RT_BOOL(fEcx & X86_CPUID_FEATURE_ECX_SSE4_1); + g_afTypeSupports[T_SSE4_2] = RT_BOOL(fEcx & X86_CPUID_FEATURE_ECX_SSE4_2); + g_afTypeSupports[T_PCLMUL] = RT_BOOL(fEcx & X86_CPUID_FEATURE_ECX_PCLMUL); + g_afTypeSupports[T_AVX_128] = RT_BOOL(fEcx & X86_CPUID_FEATURE_ECX_AVX); + g_afTypeSupports[T_AVX_256] = RT_BOOL(fEcx & X86_CPUID_FEATURE_ECX_AVX); + g_afTypeSupports[T_AVX_PCLMUL] = RT_BOOL(fEcx & X86_CPUID_FEATURE_ECX_PCLMUL) + && RT_BOOL(fEcx & X86_CPUID_FEATURE_ECX_AVX); + + if (ASMCpuId_EAX(0) >= 7) + { + ASMCpuIdExSlow(7, 0, 0, 0, NULL, &fEbx, NULL, NULL); + g_afTypeSupports[T_AVX2_128] = RT_BOOL(fEbx & X86_CPUID_STEXT_FEATURE_EBX_AVX2); + g_afTypeSupports[T_AVX2_256] = RT_BOOL(fEbx & X86_CPUID_STEXT_FEATURE_EBX_AVX2); + } + + if (g_uBs3CpuDetected & BS3CPU_F_CPUID_EXT_LEAVES) + { + ASMCpuIdExSlow(UINT32_C(0x80000001), 0, 0, 0, NULL, NULL, &fEcx, &fEdx); + g_afTypeSupports[T_AXMMX] = RT_BOOL(fEcx & X86_CPUID_AMD_FEATURE_EDX_AXMMX); + g_afTypeSupports[T_SSE4A] = RT_BOOL(fEcx & X86_CPUID_AMD_FEATURE_ECX_SSE4A); + g_fAmdMisalignedSse = RT_BOOL(fEcx & X86_CPUID_AMD_FEATURE_ECX_MISALNSSE); + } + g_afTypeSupports[T_AXMMX_OR_SSE] = g_afTypeSupports[T_AXMMX] || g_afTypeSupports[T_SSE]; + } + + /* + * Allocate a buffer for testing. + */ + g_cbBuf = X86_PAGE_SIZE * 4; + g_pbBuf = (uint8_t BS3_FAR *)Bs3MemAlloc(BS3MEMKIND_REAL, g_cbBuf); + if (g_pbBuf) + { + /* + * Do the tests. + */ + Bs3TestDoModesByOne_pe32(g_aTests, RT_ELEMENTS(g_aTests), BS3TESTMODEBYONEENTRY_F_REAL_MODE_READY); + } + else + Bs3TestFailed("Failed to allocate 16K buffer"); + + Bs3TestTerm(); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-state64-1-asm.asm b/src/VBox/ValidationKit/bootsectors/bs3-cpu-state64-1-asm.asm new file mode 100644 index 00000000..7eb487f3 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-state64-1-asm.asm @@ -0,0 +1,242 @@ +; $Id: bs3-cpu-state64-1-asm.asm $ +;; @file +; BS3Kit - bs3-cpu-state64-1 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit.mac" + + +;********************************************************************************************************************************* +;* Global Variables * +;********************************************************************************************************************************* +BS3_BEGIN_DATA16 +BS3_GLOBAL_DATA g_bs3CpuState64CtxCaller, BS3REGCTX_size + resb BS3REGCTX_size +BS3_GLOBAL_DATA g_bs3CpuState64CtxToLoad, BS3REGCTX_size + resb BS3REGCTX_size +BS3_GLOBAL_DATA g_bs3CpuState64CtxSaved, BS3REGCTX_size + resb BS3REGCTX_size + +BS3_GLOBAL_DATA g_bs3CpuState64RCX, 8 + dq 1 + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_BEGIN_TEXT64 +EXTERN Bs3RegCtxRestore_c64 +EXTERN Bs3RegCtxSave_c64 + + +BS3_BEGIN_TEXT64 + BS3_SET_BITS 64 + +;; +;; Test worker that switches between 64-bit and 16-bit real mode, +;; only trashing RAX, BX, DS, RSP (preseved) and RIP. +;; +;; Caller puts the state to load in g_bs3CpuState64CtxToLoad, this function alters +;; the BX and RIP values before loading it. It then switches to 16-bit real mode, +;; executes the worker given as input, re-enters long mode and saves the state to +;; g_bs3CpuState64CtxSaved. +;; +;; @param rcx Address of worker (16-bit) to invoke while in real-mode. +;; +BS3_PROC_BEGIN NAME(bs3CpuState64Worker) + push rbp + mov rbp, rsp + sub rsp, 40h + mov [rbp + 16], rcx + + ; + ; Save the current register state so we can return with the exact state we entered. + ; + lea rcx, [BS3_WRT_RIP(BS3_DATA_NM(g_bs3CpuState64CtxCaller)) wrt FLAT] + mov [rsp], rcx + call NAME(Bs3RegCtxSave_c64) + + ; + ; Load the context. We modify the state to be loaded so that it fits + ; into the code flow here.. + ; + lea rcx, [BS3_WRT_RIP(BS3_DATA_NM(g_bs3CpuState64CtxToLoad)) wrt FLAT] + mov [rcx + BS3REGCTX.rsp], rsp + ;lea rdx, [BS3_WRT_RIP(.ctx_loaded) wrt FLAT] - absolute address cannot be relative. wtf? + mov edx, .ctx_loaded wrt FLAT + mov [rcx + BS3REGCTX.rip], rdx + mov edx, [rbp + 16] ; Worker address. Putting it in the BX register relative to 16-bit CS. + sub edx, BS3_ADDR_BS3TEXT16 + mov [rcx + BS3REGCTX.rbx], dx + mov edx, 0 ; fFlags + mov [rsp], rcx + mov [rsp + 8], rdx + call NAME(Bs3RegCtxRestore_c64) +.ctx_loaded: + + ; + ; Disable long mode. + ; + + ; Construct a far return for switching to 16-bit code. + push BS3_SEL_R0_CS16 + push .sixteen_bit_segment wrt CGROUP16 + xRETF +BS3_BEGIN_TEXT16 + BS3_SET_BITS 16 +BS3_GLOBAL_LOCAL_LABEL .sixteen_bit_segment + ; Make the DS usable from real mode. + mov ax, BS3_SEL_R0_DS16 + mov ds, ax + + ; Exit to real mode. + mov eax, cr0 + and eax, X86_CR0_NO_PE_NO_PG + mov cr0, eax + jmp CGROUP16:.reload_cs16 +BS3_GLOBAL_LOCAL_LABEL .reload_cs16 + + ; + ; Jump to the 16-bit worker function that will make state modifications. + ; + jmp bx +BS3_GLOBAL_LOCAL_LABEL .resume16 + + ; + ; Re-enter long mode. + ; + mov eax, cr0 + or eax, X86_CR0_PE | X86_CR0_PG + mov cr0, eax + jmp CGROUP16:.reload_cs_long_mode +BS3_GLOBAL_LOCAL_LABEL .reload_cs_long_mode + ; Construct a far return for switching to 64-bit code. + push dword BS3_SEL_R0_CS64 + push dword .sixtyfour_bit_segment wrt FLAT + o32 retf +BS3_BEGIN_TEXT64 +BS3_GLOBAL_LOCAL_LABEL .sixtyfour_bit_segment + BS3_SET_BITS 64 + + ; + ; We're back in long mode, save the context. + ; + mov [BS3_WRT_RIP(BS3_DATA_NM(g_bs3CpuState64RCX)) wrt FLAT], rcx + lea rcx, [BS3_WRT_RIP(BS3_DATA_NM(g_bs3CpuState64CtxSaved)) wrt FLAT] + mov [rsp], rcx + call NAME(Bs3RegCtxSave_c64) + lea rcx, [BS3_WRT_RIP(BS3_DATA_NM(g_bs3CpuState64CtxSaved)) wrt FLAT] + mov rax, [BS3_WRT_RIP(BS3_DATA_NM(g_bs3CpuState64RCX)) wrt FLAT] + mov [rcx + BS3REGCTX.rcx], rax + + ; + ; Load the caller's context. + ; + lea rcx, [BS3_WRT_RIP(BS3_DATA_NM(g_bs3CpuState64CtxCaller)) wrt FLAT] + ;lea rdx, [BS3_WRT_RIP(.return_sequence) wrt FLAT] - absolute address cannot be relative. wtf? + mov edx, .return_sequence wrt FLAT + mov [rcx + BS3REGCTX.rip], rdx + mov edx, 0 + mov [rsp], rcx + mov [rsp + 8], rdx + call NAME(Bs3RegCtxRestore_c64) +.return_sequence: + + add rsp, 40h + pop rbp + ret +BS3_PROC_END NAME(bs3CpuState64Worker) + + +BS3_BEGIN_TEXT16 +; +; Real-mod modification workers for bs3CpuState64Worker. +; + +BS3_PROC_BEGIN NAME(bs3CpuState64Worker_Nop) + nop + jmp NAME(bs3CpuState64Worker.resume16) +BS3_PROC_END NAME(bs3CpuState64Worker_Nop) + + +BS3_PROC_BEGIN NAME(bs3CpuState64Worker_ModAll32BitGrps) + mov eax, 0xc0ffee0d ; C code hardcodes these values too. + mov ecx, 0xc0ffee1d + mov edx, 0xc0ffee2d + mov ebx, 0xc0ffee3d + ; leave esp alone for now. + mov ebp, 0xc0ffee5d + mov esi, 0xc0ffee6d + mov edi, 0xc0ffee7d + jmp NAME(bs3CpuState64Worker.resume16) +BS3_PROC_END NAME(bs3CpuState64Worker_ModAll32BitGrps) + + +BS3_PROC_BEGIN NAME(bs3CpuState64Worker_ModAll16BitGrps) + mov ax, 0xfad0 ; C code hardcodes these values too. + mov cx, 0xfad1 + mov dx, 0xfad2 + mov bx, 0xfad3 + ; leave esp alone for now. + mov bp, 0xfad5 + mov si, 0xfad6 + mov di, 0xfad7 + jmp NAME(bs3CpuState64Worker.resume16) +BS3_PROC_END NAME(bs3CpuState64Worker_ModAll16BitGrps) + + +BS3_PROC_BEGIN NAME(bs3CpuState64Worker_ModAll8BitGrps) + mov al, 0x10 ; C code hardcodes these values too. + mov ah, 0x11 + mov cl, 0x20 + mov ch, 0x21 + mov dl, 0x30 + mov dh, 0x31 + mov bl, 0x40 + mov bh, 0x41 + jmp NAME(bs3CpuState64Worker.resume16) +BS3_PROC_END NAME(bs3CpuState64Worker_ModAll8BitGrps) + +BS3_PROC_BEGIN NAME(bs3CpuState64Worker_ModCr2) + mov eax, 0xf00dface ; C code hardcodes this value too. + mov cr2, eax + jmp NAME(bs3CpuState64Worker.resume16) +BS3_PROC_END NAME(bs3CpuState64Worker_ModCr2) + +;; @todo drX registers. + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-state64-1.c64 b/src/VBox/ValidationKit/bootsectors/bs3-cpu-state64-1.c64 new file mode 100644 index 00000000..3f53ed90 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-state64-1.c64 @@ -0,0 +1,166 @@ +/* $Id: bs3-cpu-state64-1.c64 $ */ +/** @file + * BS3Kit - bs3-cpu-state64-1, 64-bit C code. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> +#include <iprt/asm-amd64-x86.h> + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +BS3_DECL(void) bs3CpuState64Worker_Nop(void); +BS3_DECL(void) bs3CpuState64Worker_ModAll32BitGrps(void); +BS3_DECL(void) bs3CpuState64Worker_ModAll16BitGrps(void); +BS3_DECL(void) bs3CpuState64Worker_ModAll8BitGrps(void); +BS3_DECL(void) bs3CpuState64Worker_ModCr2(void); +BS3_DECL(void) bs3CpuState64Worker(PFNBS3NEAR pfnWorker); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +extern BS3REGCTX g_bs3CpuState64CtxToLoad; +extern BS3REGCTX const g_bs3CpuState64CtxSaved; + + +static void bs3CpuState64InitCtx(uint64_t uTweak) +{ + Bs3RegCtxSave(&g_bs3CpuState64CtxToLoad); + g_bs3CpuState64CtxToLoad.rax.u = UINT64_C(0x8877f055443322e0) ^ uTweak; + g_bs3CpuState64CtxToLoad.rcx.u = UINT64_C(0x8877f155443322e1) ^ uTweak; + g_bs3CpuState64CtxToLoad.rdx.u = UINT64_C(0x8877f255443322e2) ^ uTweak; + g_bs3CpuState64CtxToLoad.rbx.u = UINT64_C(0x8877f355443322e3) ^ uTweak; + g_bs3CpuState64CtxToLoad.rsp.u = UINT64_C(0x8877f455443322e4) ^ uTweak; + g_bs3CpuState64CtxToLoad.rbp.u = UINT64_C(0x8877f555443322e5) ^ uTweak; + g_bs3CpuState64CtxToLoad.rsi.u = UINT64_C(0x8877f655443322e6) ^ uTweak; + g_bs3CpuState64CtxToLoad.rdi.u = UINT64_C(0x8877f755443322e7) ^ uTweak; + g_bs3CpuState64CtxToLoad.r8.u = UINT64_C(0x8877f855443322e8) ^ uTweak; + g_bs3CpuState64CtxToLoad.r9.u = UINT64_C(0x8877f955443322e9) ^ uTweak; + g_bs3CpuState64CtxToLoad.r10.u = UINT64_C(0x8877fa55443322ea) ^ uTweak; + g_bs3CpuState64CtxToLoad.r11.u = UINT64_C(0x8877fb55443322eb) ^ uTweak; + g_bs3CpuState64CtxToLoad.r12.u = UINT64_C(0x8877fc55443322ec) ^ uTweak; + g_bs3CpuState64CtxToLoad.r13.u = UINT64_C(0x8877fd55443322ed) ^ uTweak; + g_bs3CpuState64CtxToLoad.r14.u = UINT64_C(0x8877fe55443322ee) ^ uTweak; + g_bs3CpuState64CtxToLoad.r15.u = UINT64_C(0x8877ff55443322ef) ^ uTweak; + g_bs3CpuState64CtxToLoad.cr2.u = UINT64_C(0x89abcdef76543210) ^ uTweak; + //Bs3TestPrintf("** Input:\n"); + //Bs3RegCtxPrint(&g_bs3CpuState64CtxToLoad); +} + + +static void bs3CpuState64Comp(bool fModRbx) +{ + //Bs3TestPrintf("** Result:\n"); + //Bs3RegCtxPrint(&g_bs3CpuState64CtxSaved); + g_bs3CpuState64CtxToLoad.rax = g_bs3CpuState64CtxSaved.rax; + g_bs3CpuState64CtxToLoad.rip = g_bs3CpuState64CtxSaved.rip; + g_bs3CpuState64CtxToLoad.rflags = g_bs3CpuState64CtxSaved.rflags; + g_bs3CpuState64CtxToLoad.rsp = g_bs3CpuState64CtxSaved.rsp; + if (!fModRbx) + g_bs3CpuState64CtxToLoad.rbx.au16[0] = g_bs3CpuState64CtxSaved.rbx.au16[0]; + if (1) + g_bs3CpuState64CtxToLoad.ds = g_bs3CpuState64CtxSaved.ds; + + Bs3TestCheckRegCtxEx(&g_bs3CpuState64CtxSaved, &g_bs3CpuState64CtxToLoad, 0 /*cbPcAdjust*/, 0 /*cbSpAdjust*/, + 0 /*fExtraEfl*/, "lm64", 0 /*idTestStep*/); +} + +BS3_DECL(void) Main_lm64() +{ + Bs3TestInit("bs3-cpu-state64-1"); + /* + * Switch to 64-bit mode and back to rm. + */ + Bs3TestSub("noop"); + bs3CpuState64InitCtx(0); + bs3CpuState64Worker(bs3CpuState64Worker_Nop); + bs3CpuState64Comp(false /*fModRbx*/); + + Bs3TestSub("Modify all 32-bit GPRs"); + bs3CpuState64InitCtx(0); + bs3CpuState64Worker(bs3CpuState64Worker_ModAll32BitGrps); + g_bs3CpuState64CtxToLoad.rax.u = UINT64_C(0xc0ffee0d); + g_bs3CpuState64CtxToLoad.rcx.u = UINT64_C(0xc0ffee1d); + g_bs3CpuState64CtxToLoad.rdx.u = UINT64_C(0xc0ffee2d); + g_bs3CpuState64CtxToLoad.rbx.u = UINT64_C(0xc0ffee3d); + g_bs3CpuState64CtxToLoad.rsp.u = UINT64_C(0xc0ffee4d); + g_bs3CpuState64CtxToLoad.rbp.u = UINT64_C(0xc0ffee5d); + g_bs3CpuState64CtxToLoad.rsi.u = UINT64_C(0xc0ffee6d); + g_bs3CpuState64CtxToLoad.rdi.u = UINT64_C(0xc0ffee7d); + bs3CpuState64Comp(true /*fModRbx*/); + + Bs3TestSub("Modify all 16-bit GPRs"); + bs3CpuState64InitCtx(0); + bs3CpuState64Worker(bs3CpuState64Worker_ModAll16BitGrps); + g_bs3CpuState64CtxToLoad.rax.au16[0] = UINT16_C(0xfad0); + g_bs3CpuState64CtxToLoad.rcx.au16[0] = UINT16_C(0xfad1); + g_bs3CpuState64CtxToLoad.rdx.au16[0] = UINT16_C(0xfad2); + g_bs3CpuState64CtxToLoad.rbx.au16[0] = UINT16_C(0xfad3); + g_bs3CpuState64CtxToLoad.rsp.au16[0] = UINT16_C(0xfad4); + g_bs3CpuState64CtxToLoad.rbp.au16[0] = UINT16_C(0xfad5); + g_bs3CpuState64CtxToLoad.rsi.au16[0] = UINT16_C(0xfad6); + g_bs3CpuState64CtxToLoad.rdi.au16[0] = UINT16_C(0xfad7); + bs3CpuState64Comp(true /*fModRbx*/); + + Bs3TestSub("Modify all 8-bit GPRs"); + bs3CpuState64InitCtx(0); + bs3CpuState64Worker(bs3CpuState64Worker_ModAll8BitGrps); + g_bs3CpuState64CtxToLoad.rax.au8[0] = 0x10; + g_bs3CpuState64CtxToLoad.rax.au8[1] = 0x11; + g_bs3CpuState64CtxToLoad.rcx.au8[0] = 0x20; + g_bs3CpuState64CtxToLoad.rcx.au8[1] = 0x21; + g_bs3CpuState64CtxToLoad.rdx.au8[0] = 0x30; + g_bs3CpuState64CtxToLoad.rdx.au8[1] = 0x31; + g_bs3CpuState64CtxToLoad.rbx.au8[0] = 0x40; + g_bs3CpuState64CtxToLoad.rbx.au8[1] = 0x41; + bs3CpuState64Comp(true /*fModRbx*/); + + Bs3TestSub("Modify CR2"); + bs3CpuState64InitCtx(0); + bs3CpuState64Worker(bs3CpuState64Worker_ModCr2); + g_bs3CpuState64CtxToLoad.cr2.u = 0xf00dface; + bs3CpuState64Comp(true /*fModRbx*/); + + /** @todo DRx */ + Bs3TestTerm(); + for (;;) + ASMHalt(); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-weird-1-asm.asm b/src/VBox/ValidationKit/bootsectors/bs3-cpu-weird-1-asm.asm new file mode 100644 index 00000000..17780c9c --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-weird-1-asm.asm @@ -0,0 +1,69 @@ +; $Id: bs3-cpu-weird-1-asm.asm $ +;; @file +; BS3Kit - bs3-cpu-weird-1 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit.mac" + + +;********************************************************************************************************************************* +;* Global Variables * +;********************************************************************************************************************************* +BS3_BEGIN_DATA16 + + +; +; CPU mode agnostic test code snippets. +; +BS3_BEGIN_TEXT16 + + +; +; CPU mode agnostic test code snippets. +; +BS3_BEGIN_TEXT32 + + +BS3_BEGIN_TEXT16 + +;; +;; Instantiate code templates. +;; +BS3_INSTANTIATE_COMMON_TEMPLATE "bs3-cpu-weird-1-template.mac" +; BS3_INSTANTIATE_TEMPLATE_WITH_WEIRD_ONES "bs3-cpu-weird-1-template.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-weird-1-template.mac b/src/VBox/ValidationKit/bootsectors/bs3-cpu-weird-1-template.mac new file mode 100644 index 00000000..c3da7937 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-weird-1-template.mac @@ -0,0 +1,167 @@ +; $Id: bs3-cpu-weird-1-template.mac $ +;; @file +; BS3Kit - bs3-cpu-weird-1 assembly template. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" ; setup environment + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +TMPL_BEGIN_TEXT + + +; +; Test code snippets containing code which differs between 16-bit, 32-bit +; and 64-bit CPUs modes. +; +%ifdef BS3_INSTANTIATING_CMN + + +; +; Inhibited int 80h. +; +BS3_PROC_BEGIN_CMN bs3CpuWeird1_InhibitedInt80, BS3_PBC_NEAR + ; Load SS from stack. This instruction causes fusing. +%if TMPL_BITS != 64 + pop ss +%else + mov ss, [rsp] +%endif + ; The ring transition instruction. +BS3_GLOBAL_NAME_EX BS3_CMN_NM(bs3CpuWeird1_InhibitedInt80_int80), , 0 + int 80h + ; We shouldn't get here! +.ud2_again: + ud2 + jmp .ud2_again +BS3_PROC_END_CMN bs3CpuWeird1_InhibitedInt80 + +; +; Inhibited int 3. +; +BS3_PROC_BEGIN_CMN bs3CpuWeird1_InhibitedInt3, BS3_PBC_NEAR + ; Load SS from stack. This instruction causes fusing. +%if TMPL_BITS != 64 + pop ss +%else + mov ss, [rsp] +%endif + ; The ring transition instruction. +BS3_GLOBAL_NAME_EX BS3_CMN_NM(bs3CpuWeird1_InhibitedInt3_int3), , 0 + int 3 + ; We shouldn't get here! +.ud2_again: + ud2 + jmp .ud2_again +AssertCompile(.ud2_again - BS3_CMN_NM(bs3CpuWeird1_InhibitedInt3_int3) == 2) +BS3_PROC_END_CMN bs3CpuWeird1_InhibitedInt3 + + +; +; Inhibited int3. +; +BS3_PROC_BEGIN_CMN bs3CpuWeird1_InhibitedBp, BS3_PBC_NEAR + ; Load SS from stack. This instruction causes fusing. +%if TMPL_BITS != 64 + pop ss +%else + mov ss, [rsp] +%endif + ; The ring transition instruction. +BS3_GLOBAL_NAME_EX BS3_CMN_NM(bs3CpuWeird1_InhibitedBp_int3), , 0 + int3 + ; We shouldn't get here! +.ud2_again: + ud2 + jmp .ud2_again +AssertCompile(.ud2_again - BS3_CMN_NM(bs3CpuWeird1_InhibitedBp_int3) == 1) +BS3_PROC_END_CMN bs3CpuWeird1_InhibitedBp + + +; +; PC (IP/EIP) wrapper templates. +; These will potentially trigger VM exits, except for the benign one. +; +; Note! Single instructions as the testcase will shift multibyte variations +; across the wrap-around boundary and that would cause unpredictable +; results for the 16-bit if there is more than one instruction. +; + +BS3_PROC_BEGIN_CMN bs3CpuWeird1_PcWrapBenign1, BS3_PBC_NEAR + nop +BS3_PROC_END_CMN bs3CpuWeird1_PcWrapBenign1 + +BS3_PROC_BEGIN_CMN bs3CpuWeird1_PcWrapBenign2, BS3_PBC_NEAR + xor xDX, xAX +BS3_PROC_END_CMN bs3CpuWeird1_PcWrapBenign2 + +BS3_PROC_BEGIN_CMN bs3CpuWeird1_PcWrapCpuId, BS3_PBC_NEAR + cpuid +BS3_PROC_END_CMN bs3CpuWeird1_PcWrapCpuId + +BS3_PROC_BEGIN_CMN bs3CpuWeird1_PcWrapIn80, BS3_PBC_NEAR + in al, 80h +BS3_PROC_END_CMN bs3CpuWeird1_PcWrapIn80 + +BS3_PROC_BEGIN_CMN bs3CpuWeird1_PcWrapOut80, BS3_PBC_NEAR + out 80h, al +BS3_PROC_END_CMN bs3CpuWeird1_PcWrapOut80 + +BS3_PROC_BEGIN_CMN bs3CpuWeird1_PcWrapSmsw, BS3_PBC_NEAR + smsw si +BS3_PROC_END_CMN bs3CpuWeird1_PcWrapSmsw + +BS3_PROC_BEGIN_CMN bs3CpuWeird1_PcWrapRdCr0, BS3_PBC_NEAR + mov sAX, cr0 +BS3_PROC_END_CMN bs3CpuWeird1_PcWrapRdCr0 + +BS3_PROC_BEGIN_CMN bs3CpuWeird1_PcWrapRdDr0, BS3_PBC_NEAR + mov sAX, dr0 +BS3_PROC_END_CMN bs3CpuWeird1_PcWrapRdDr0 + +BS3_PROC_BEGIN_CMN bs3CpuWeird1_PcWrapWrDr0, BS3_PBC_NEAR + mov dr0, sAX +BS3_PROC_END_CMN bs3CpuWeird1_PcWrapWrDr0 + + +%endif ; BS3_INSTANTIATING_CMN + +%include "bs3kit-template-footer.mac" ; reset environment + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-weird-1-x0.c b/src/VBox/ValidationKit/bootsectors/bs3-cpu-weird-1-x0.c new file mode 100644 index 00000000..a8360255 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-weird-1-x0.c @@ -0,0 +1,1086 @@ +/* $Id: bs3-cpu-weird-1-x0.c $ */ +/** @file + * BS3Kit - bs3-cpu-weird-2, C test driver code (16-bit). + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define BS3_USE_X0_TEXT_SEG +#include <bs3kit.h> +#include <bs3-cmn-memory.h> +#include <iprt/asm.h> +#include <iprt/asm-amd64-x86.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#undef CHECK_MEMBER +#define CHECK_MEMBER(a_szName, a_szFmt, a_Actual, a_Expected) \ + do \ + { \ + if ((a_Actual) == (a_Expected)) { /* likely */ } \ + else bs3CpuWeird1_FailedF(a_szName "=" a_szFmt " expected " a_szFmt, (a_Actual), (a_Expected)); \ + } while (0) + + +/********************************************************************************************************************************* +* External Symbols * +*********************************************************************************************************************************/ +extern FNBS3FAR bs3CpuWeird1_InhibitedInt80_c16; +extern FNBS3FAR bs3CpuWeird1_InhibitedInt80_c32; +extern FNBS3FAR bs3CpuWeird1_InhibitedInt80_c64; +extern FNBS3FAR bs3CpuWeird1_InhibitedInt80_int80_c16; +extern FNBS3FAR bs3CpuWeird1_InhibitedInt80_int80_c32; +extern FNBS3FAR bs3CpuWeird1_InhibitedInt80_int80_c64; + +extern FNBS3FAR bs3CpuWeird1_InhibitedInt3_c16; +extern FNBS3FAR bs3CpuWeird1_InhibitedInt3_c32; +extern FNBS3FAR bs3CpuWeird1_InhibitedInt3_c64; +extern FNBS3FAR bs3CpuWeird1_InhibitedInt3_int3_c16; +extern FNBS3FAR bs3CpuWeird1_InhibitedInt3_int3_c32; +extern FNBS3FAR bs3CpuWeird1_InhibitedInt3_int3_c64; + +extern FNBS3FAR bs3CpuWeird1_InhibitedBp_c16; +extern FNBS3FAR bs3CpuWeird1_InhibitedBp_c32; +extern FNBS3FAR bs3CpuWeird1_InhibitedBp_c64; +extern FNBS3FAR bs3CpuWeird1_InhibitedBp_int3_c16; +extern FNBS3FAR bs3CpuWeird1_InhibitedBp_int3_c32; +extern FNBS3FAR bs3CpuWeird1_InhibitedBp_int3_c64; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static const char BS3_FAR *g_pszTestMode = (const char *)1; +static BS3CPUVENDOR g_enmCpuVendor = BS3CPUVENDOR_INTEL; +static bool g_fVME = false; +//static uint8_t g_bTestMode = 1; +//static bool g_f16BitSys = 1; + + + +/** + * Sets globals according to the mode. + * + * @param bTestMode The test mode. + */ +static void bs3CpuWeird1_SetGlobals(uint8_t bTestMode) +{ +// g_bTestMode = bTestMode; + g_pszTestMode = Bs3GetModeName(bTestMode); +// g_f16BitSys = BS3_MODE_IS_16BIT_SYS(bTestMode); + g_usBs3TestStep = 0; + g_enmCpuVendor = Bs3GetCpuVendor(); + g_fVME = (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80486 + && (Bs3RegGetCr4() & X86_CR4_VME); +} + + +/** + * Wrapper around Bs3TestFailedF that prefixes the error with g_usBs3TestStep + * and g_pszTestMode. + */ +static void bs3CpuWeird1_FailedF(const char *pszFormat, ...) +{ + va_list va; + + char szTmp[168]; + va_start(va, pszFormat); + Bs3StrPrintfV(szTmp, sizeof(szTmp), pszFormat, va); + va_end(va); + + Bs3TestFailedF("%u - %s: %s", g_usBs3TestStep, g_pszTestMode, szTmp); +} + + +/** + * Compares interrupt stuff. + */ +static void bs3CpuWeird1_CompareDbgInhibitRingXfer(PCBS3TRAPFRAME pTrapCtx, PCBS3REGCTX pStartCtx, uint8_t bXcpt, + int8_t cbPcAdjust, int8_t cbSpAdjust, uint32_t uDr6Expected, + uint8_t cbIretFrame, uint64_t uHandlerRsp) +{ + uint32_t uDr6 = (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80386 ? Bs3RegGetDr6() : X86_DR6_INIT_VAL; + uint16_t const cErrorsBefore = Bs3TestSubErrorCount(); + CHECK_MEMBER("bXcpt", "%#04x", pTrapCtx->bXcpt, bXcpt); + CHECK_MEMBER("bErrCd", "%#06RX64", pTrapCtx->uErrCd, 0); + CHECK_MEMBER("cbIretFrame", "%#04x", pTrapCtx->cbIretFrame, cbIretFrame); + CHECK_MEMBER("uHandlerRsp", "%#06RX64", pTrapCtx->uHandlerRsp, uHandlerRsp); + if (uDr6 != uDr6Expected) + bs3CpuWeird1_FailedF("dr6=%#010RX32 expected %#010RX32", uDr6, uDr6Expected); + Bs3TestCheckRegCtxEx(&pTrapCtx->Ctx, pStartCtx, cbPcAdjust, cbSpAdjust, 0 /*fExtraEfl*/, g_pszTestMode, g_usBs3TestStep); + if (Bs3TestSubErrorCount() != cErrorsBefore) + { + Bs3TrapPrintFrame(pTrapCtx); + Bs3TestPrintf("DR6=%#RX32; Handler: CS=%04RX16 SS:ESP=%04RX16:%08RX64 EFL=%RX64 cbIret=%#x\n", + uDr6, pTrapCtx->uHandlerCs, pTrapCtx->uHandlerSs, pTrapCtx->uHandlerRsp, + pTrapCtx->fHandlerRfl, pTrapCtx->cbIretFrame); +#if 0 + Bs3TestPrintf("Halting in CompareIntCtx: bXcpt=%#x\n", bXcpt); + ASMHalt(); +#endif + } +} + +static uint64_t bs3CpuWeird1_GetTrapHandlerEIP(uint8_t bXcpt, uint8_t bMode, bool fV86) +{ + if ( BS3_MODE_IS_RM_SYS(bMode) + || (fV86 && BS3_MODE_IS_V86(bMode))) + { + PRTFAR16 paIvt = (PRTFAR16)Bs3XptrFlatToCurrent(0); + return paIvt[bXcpt].off; + } + if (BS3_MODE_IS_16BIT_SYS(bMode)) + return Bs3Idt16[bXcpt].Gate.u16OffsetLow; + if (BS3_MODE_IS_32BIT_SYS(bMode)) + return RT_MAKE_U32(Bs3Idt32[bXcpt].Gate.u16OffsetLow, Bs3Idt32[bXcpt].Gate.u16OffsetHigh); + return RT_MAKE_U64(RT_MAKE_U32(Bs3Idt64[bXcpt].Gate.u16OffsetLow, Bs3Idt32[bXcpt].Gate.u16OffsetHigh), + Bs3Idt64[bXcpt].Gate.u32OffsetTop); +} + + +static int bs3CpuWeird1_DbgInhibitRingXfer_Worker(uint8_t bTestMode, uint8_t bIntGate, uint8_t cbRingInstr, int8_t cbSpAdjust, + FPFNBS3FAR pfnTestCode, FPFNBS3FAR pfnTestLabel) +{ + BS3TRAPFRAME TrapCtx; + BS3TRAPFRAME TrapExpect; + BS3REGCTX Ctx; + uint8_t bSavedDpl; + uint8_t const offTestLabel = BS3_FP_OFF(pfnTestLabel) - BS3_FP_OFF(pfnTestCode); + //uint8_t const cbIretFrameSame = BS3_MODE_IS_RM_SYS(bTestMode) ? 6 + // : BS3_MODE_IS_16BIT_SYS(bTestMode) ? 12 + // : BS3_MODE_IS_64BIT_SYS(bTestMode) ? 40 : 12; + uint8_t cbIretFrameInt; + uint8_t cbIretFrameIntDb; + uint8_t const cbIretFrameSame = BS3_MODE_IS_16BIT_SYS(bTestMode) ? 6 + : BS3_MODE_IS_32BIT_SYS(bTestMode) ? 12 : 40; + uint8_t const cbSpAdjSame = BS3_MODE_IS_64BIT_SYS(bTestMode) ? 48 : cbIretFrameSame; + uint8_t bVmeMethod = 0; + uint64_t uHandlerRspInt; + uint64_t uHandlerRspIntDb; + BS3_XPTR_AUTO(uint32_t, StackXptr); + + /* make sure they're allocated */ + Bs3MemZero(&Ctx, sizeof(Ctx)); + Bs3MemZero(&TrapCtx, sizeof(TrapCtx)); + Bs3MemZero(&TrapExpect, sizeof(TrapExpect)); + + /* + * Make INT xx accessible from DPL 3 and create a ring-3 context that we can work with. + */ + bSavedDpl = Bs3TrapSetDpl(bIntGate, 3); + + Bs3RegCtxSaveEx(&Ctx, bTestMode, 1024); + Bs3RegCtxSetRipCsFromLnkPtr(&Ctx, pfnTestCode); + if (BS3_MODE_IS_16BIT_SYS(bTestMode)) + g_uBs3TrapEipHint = Ctx.rip.u32; + Ctx.rflags.u32 &= ~X86_EFL_RF; + + /* Raw-mode enablers. */ + Ctx.rflags.u32 |= X86_EFL_IF; + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80486) + Ctx.cr0.u32 |= X86_CR0_WP; + + /* We put the SS value on the stack so we can easily set breakpoints there. */ + Ctx.rsp.u32 -= 8; + BS3_XPTR_SET_FLAT(uint32_t, StackXptr, Ctx.rsp.u32); /* ASSUMES SS.BASE == 0!! */ + + /* ring-3 */ + if (!BS3_MODE_IS_RM_OR_V86(bTestMode)) + Bs3RegCtxConvertToRingX(&Ctx, 3); + + /* V8086: Set IOPL to 3. */ + if (BS3_MODE_IS_V86(bTestMode)) + { + Ctx.rflags.u32 |= X86_EFL_IOPL; + if (g_fVME) + { + Bs3RegSetTr(BS3_SEL_TSS32_IRB); +#if 0 + /* SDMv3b, 20.3.3 method 5: */ + ASMBitClear(&Bs3SharedIntRedirBm, bIntGate); + bVmeMethod = 5; +#else + /* SDMv3b, 20.3.3 method 4 (similar to non-VME): */ + ASMBitSet(&Bs3SharedIntRedirBm, bIntGate); + bVmeMethod = 4; + } +#endif + } + + /* + * Test #0: Test run. Calc expected delayed #DB from it. + */ + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80386) + { + Bs3RegSetDr7(0); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + } + *BS3_XPTR_GET(uint32_t, StackXptr) = Ctx.ss; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapExpect); + if (TrapExpect.bXcpt != bIntGate) + { + + Bs3TestFailedF("%u: bXcpt is %#x, expected %#x!\n", g_usBs3TestStep, TrapExpect.bXcpt, bIntGate); + Bs3TrapPrintFrame(&TrapExpect); + return 1; + } + + cbIretFrameInt = TrapExpect.cbIretFrame; + cbIretFrameIntDb = cbIretFrameInt + cbIretFrameSame; + uHandlerRspInt = TrapExpect.uHandlerRsp; + uHandlerRspIntDb = uHandlerRspInt - cbSpAdjSame; + + TrapExpect.Ctx.bCpl = 0; + TrapExpect.Ctx.cs = TrapExpect.uHandlerCs; + TrapExpect.Ctx.ss = TrapExpect.uHandlerSs; + TrapExpect.Ctx.rsp.u64 = TrapExpect.uHandlerRsp; + TrapExpect.Ctx.rflags.u64 = TrapExpect.fHandlerRfl; + if (BS3_MODE_IS_V86(bTestMode)) + { + if (bVmeMethod >= 5) + { + TrapExpect.Ctx.rflags.u32 |= X86_EFL_VM; + TrapExpect.Ctx.bCpl = 3; + TrapExpect.Ctx.rip.u64 = bs3CpuWeird1_GetTrapHandlerEIP(bIntGate, bTestMode, true); + cbIretFrameIntDb = 36; + if (BS3_MODE_IS_16BIT_SYS(bTestMode)) + uHandlerRspIntDb = Bs3Tss16.sp0 - cbIretFrameIntDb; + else + uHandlerRspIntDb = Bs3Tss32.esp0 - cbIretFrameIntDb; + } + else + { + TrapExpect.Ctx.ds = 0; + TrapExpect.Ctx.es = 0; + TrapExpect.Ctx.fs = 0; + TrapExpect.Ctx.gs = 0; + } + } + + /* + * Test #1: Single stepping ring-3. Ignored except for V8086 w/ VME. + */ + g_usBs3TestStep++; + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80386) + { + Bs3RegSetDr7(0); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + } + *BS3_XPTR_GET(uint32_t, StackXptr) = Ctx.ss; + Ctx.rflags.u32 |= X86_EFL_TF; + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if ( !BS3_MODE_IS_V86(bTestMode) + || bVmeMethod < 5) + bs3CpuWeird1_CompareDbgInhibitRingXfer(&TrapCtx, &Ctx, bIntGate, offTestLabel + cbRingInstr, cbSpAdjust, + X86_DR6_INIT_VAL, cbIretFrameInt, uHandlerRspInt); + else + { + TrapExpect.Ctx.rflags.u32 |= X86_EFL_TF; + bs3CpuWeird1_CompareDbgInhibitRingXfer(&TrapCtx, &TrapExpect.Ctx, X86_XCPT_DB, offTestLabel, -2, + X86_DR6_INIT_VAL | X86_DR6_BS, cbIretFrameIntDb, uHandlerRspIntDb); + TrapExpect.Ctx.rflags.u32 &= ~X86_EFL_TF; + } + + Ctx.rflags.u32 &= ~X86_EFL_TF; + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80386) + { + uint32_t uDr6Expect; + + /* + * Test #2: Execution breakpoint on ring transition instruction. + * This hits on AMD-V (threadripper) but not on VT-x (skylake). + */ + g_usBs3TestStep++; + Bs3RegSetDr0(Bs3SelRealModeCodeToFlat(pfnTestLabel)); + Bs3RegSetDr7(X86_DR7_L0 | X86_DR7_G0 | X86_DR7_RW(0, X86_DR7_RW_EO) | X86_DR7_LEN(0, X86_DR7_LEN_BYTE)); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + *BS3_XPTR_GET(uint32_t, StackXptr) = Ctx.ss; + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + Bs3RegSetDr7(0); + if (g_enmCpuVendor == BS3CPUVENDOR_AMD || g_enmCpuVendor == BS3CPUVENDOR_HYGON) + bs3CpuWeird1_CompareDbgInhibitRingXfer(&TrapCtx, &Ctx, X86_XCPT_DB, offTestLabel, cbSpAdjust, + X86_DR6_INIT_VAL | X86_DR6_B0, cbIretFrameInt, uHandlerRspInt); + else + bs3CpuWeird1_CompareDbgInhibitRingXfer(&TrapCtx, &Ctx, bIntGate, offTestLabel + cbRingInstr, cbSpAdjust, + X86_DR6_INIT_VAL, cbIretFrameInt, uHandlerRspInt); + + /* + * Test #3: Same as above, but with the LE and GE flags set. + */ + g_usBs3TestStep++; + Bs3RegSetDr0(Bs3SelRealModeCodeToFlat(pfnTestLabel)); + Bs3RegSetDr7(X86_DR7_L0 | X86_DR7_G0 | X86_DR7_RW(0, X86_DR7_RW_EO) | X86_DR7_LEN(0, X86_DR7_LEN_BYTE) | X86_DR7_LE | X86_DR7_GE); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + *BS3_XPTR_GET(uint32_t, StackXptr) = Ctx.ss; + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (g_enmCpuVendor == BS3CPUVENDOR_AMD || g_enmCpuVendor == BS3CPUVENDOR_HYGON) + bs3CpuWeird1_CompareDbgInhibitRingXfer(&TrapCtx, &Ctx, X86_XCPT_DB, offTestLabel, cbSpAdjust, + X86_DR6_INIT_VAL | X86_DR6_B0, cbIretFrameInt, uHandlerRspInt); + else + bs3CpuWeird1_CompareDbgInhibitRingXfer(&TrapCtx, &Ctx, bIntGate, offTestLabel + cbRingInstr, cbSpAdjust, + X86_DR6_INIT_VAL, cbIretFrameInt, uHandlerRspInt); + + /* + * Test #4: Execution breakpoint on pop ss / mov ss. Hits. + * Note! In real mode AMD-V updates the stack pointer, or something else is busted. Totally weird! + */ + g_usBs3TestStep++; + Bs3RegSetDr0(Bs3SelRealModeCodeToFlat(pfnTestCode)); + Bs3RegSetDr7(X86_DR7_L0 | X86_DR7_G0 | X86_DR7_RW(0, X86_DR7_RW_EO) | X86_DR7_LEN(0, X86_DR7_LEN_BYTE)); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + *BS3_XPTR_GET(uint32_t, StackXptr) = Ctx.ss; + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuWeird1_CompareDbgInhibitRingXfer(&TrapCtx, &Ctx, X86_XCPT_DB, 0, 0, X86_DR6_INIT_VAL | X86_DR6_B0, + cbIretFrameInt, + uHandlerRspInt - (BS3_MODE_IS_RM_SYS(bTestMode) ? 2 : 0) ); + + /* + * Test #5: Same as above, but with the LE and GE flags set. + */ + g_usBs3TestStep++; + Bs3RegSetDr0(Bs3SelRealModeCodeToFlat(pfnTestCode)); + Bs3RegSetDr7(X86_DR7_L0 | X86_DR7_G0 | X86_DR7_RW(0, X86_DR7_RW_EO) | X86_DR7_LEN(0, X86_DR7_LEN_BYTE) | X86_DR7_LE | X86_DR7_GE); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + *BS3_XPTR_GET(uint32_t, StackXptr) = Ctx.ss; + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + bs3CpuWeird1_CompareDbgInhibitRingXfer(&TrapCtx, &Ctx, X86_XCPT_DB, 0, 0, X86_DR6_INIT_VAL | X86_DR6_B0, + cbIretFrameInt, + uHandlerRspInt - (BS3_MODE_IS_RM_SYS(bTestMode) ? 2 : 0) ); + + /* + * Test #6: Data breakpoint on SS load. The #DB is delivered after ring transition. Weird! + * + * Note! Intel loses the B0 status, probably for reasons similar to Pentium Pro errata 3. Similar + * erratum is seen with virtually every march since, e.g. skylake SKL009 & SKL111. + * Weirdly enougth, they seem to get this right in real mode. Go figure. + */ + g_usBs3TestStep++; + *BS3_XPTR_GET(uint32_t, StackXptr) = Ctx.ss; + Bs3RegSetDr0(BS3_XPTR_GET_FLAT(uint32_t, StackXptr)); + Bs3RegSetDr7(X86_DR7_L0 | X86_DR7_G0 | X86_DR7_RW(0, X86_DR7_RW_RW) | X86_DR7_LEN(0, X86_DR7_LEN_WORD)); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + TrapExpect.Ctx.rip = TrapCtx.Ctx.rip; /// @todo fixme + Bs3RegSetDr7(0); + uDr6Expect = X86_DR6_INIT_VAL | X86_DR6_B0; + if (g_enmCpuVendor == BS3CPUVENDOR_INTEL && bTestMode != BS3_MODE_RM) + uDr6Expect = X86_DR6_INIT_VAL; + bs3CpuWeird1_CompareDbgInhibitRingXfer(&TrapCtx, &TrapExpect.Ctx, X86_XCPT_DB, 0, 0, uDr6Expect, + cbIretFrameSame, uHandlerRspIntDb); + + /* + * Test #7: Same as above, but with the LE and GE flags set. + */ + g_usBs3TestStep++; + *BS3_XPTR_GET(uint32_t, StackXptr) = Ctx.ss; + Bs3RegSetDr0(BS3_XPTR_GET_FLAT(uint32_t, StackXptr)); + Bs3RegSetDr7(X86_DR7_L0 | X86_DR7_G0 | X86_DR7_RW(0, X86_DR7_RW_RW) | X86_DR7_LEN(0, X86_DR7_LEN_WORD) | X86_DR7_LE | X86_DR7_GE); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + TrapExpect.Ctx.rip = TrapCtx.Ctx.rip; /// @todo fixme + Bs3RegSetDr7(0); + uDr6Expect = X86_DR6_INIT_VAL | X86_DR6_B0; + if (g_enmCpuVendor == BS3CPUVENDOR_INTEL && bTestMode != BS3_MODE_RM) + uDr6Expect = X86_DR6_INIT_VAL; + bs3CpuWeird1_CompareDbgInhibitRingXfer(&TrapCtx, &TrapExpect.Ctx, X86_XCPT_DB, 0, 0, uDr6Expect, + cbIretFrameSame, uHandlerRspIntDb); + + if (!BS3_MODE_IS_RM_OR_V86(bTestMode)) + { + /* + * Test #8: Data breakpoint on SS GDT entry. Half weird! + * Note! Intel loses the B1 status, see test #6. + */ + g_usBs3TestStep++; + *BS3_XPTR_GET(uint32_t, StackXptr) = (Ctx.ss & X86_SEL_RPL) | BS3_SEL_SPARE_00; + Bs3GdteSpare00 = Bs3Gdt[Ctx.ss / sizeof(Bs3Gdt[0])]; + + Bs3RegSetDr1(Bs3SelPtrToFlat(&Bs3GdteSpare00)); + Bs3RegSetDr7(X86_DR7_L1 | X86_DR7_G1 | X86_DR7_RW(1, X86_DR7_RW_RW) | X86_DR7_LEN(1, X86_DR7_LEN_DWORD)); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + TrapExpect.Ctx.rip = TrapCtx.Ctx.rip; /// @todo fixme + Bs3RegSetDr7(0); + uDr6Expect = g_enmCpuVendor == BS3CPUVENDOR_INTEL ? X86_DR6_INIT_VAL : X86_DR6_INIT_VAL | X86_DR6_B1; + bs3CpuWeird1_CompareDbgInhibitRingXfer(&TrapCtx, &TrapExpect.Ctx, X86_XCPT_DB, 0, 0, uDr6Expect, + cbIretFrameSame, uHandlerRspIntDb); + + /* + * Test #9: Same as above, but with the LE and GE flags set. + */ + g_usBs3TestStep++; + *BS3_XPTR_GET(uint32_t, StackXptr) = (Ctx.ss & X86_SEL_RPL) | BS3_SEL_SPARE_00; + Bs3GdteSpare00 = Bs3Gdt[Ctx.ss / sizeof(Bs3Gdt[0])]; + + Bs3RegSetDr1(Bs3SelPtrToFlat(&Bs3GdteSpare00)); + Bs3RegSetDr7(X86_DR7_L1 | X86_DR7_G1 | X86_DR7_RW(1, X86_DR7_RW_RW) | X86_DR7_LEN(1, X86_DR7_LEN_DWORD) | X86_DR7_LE | X86_DR7_GE); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + TrapExpect.Ctx.rip = TrapCtx.Ctx.rip; /// @todo fixme + Bs3RegSetDr7(0); + uDr6Expect = g_enmCpuVendor == BS3CPUVENDOR_INTEL ? X86_DR6_INIT_VAL : X86_DR6_INIT_VAL | X86_DR6_B1; + bs3CpuWeird1_CompareDbgInhibitRingXfer(&TrapCtx, &TrapExpect.Ctx, X86_XCPT_DB, 0, 0, uDr6Expect, + cbIretFrameSame, uHandlerRspIntDb); + } + + /* + * Cleanup. + */ + Bs3RegSetDr0(0); + Bs3RegSetDr1(0); + Bs3RegSetDr2(0); + Bs3RegSetDr3(0); + Bs3RegSetDr6(X86_DR6_INIT_VAL); + Bs3RegSetDr7(0); + } + Bs3TrapSetDpl(bIntGate, bSavedDpl); + return 0; +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_FAR_NM(bs3CpuWeird1_DbgInhibitRingXfer)(uint8_t bMode) +{ + if (BS3_MODE_IS_V86(bMode)) + switch (bMode) + { + /** @todo some busted stack stuff with the 16-bit guys. Also, if VME is + * enabled, we're probably not able to do any sensible testing. */ + case BS3_MODE_PP16_V86: + case BS3_MODE_PE16_V86: + case BS3_MODE_PAE16_V86: + return BS3TESTDOMODE_SKIPPED; + } + //if (bMode != BS3_MODE_PE16_V86) return BS3TESTDOMODE_SKIPPED; + //if (bMode != BS3_MODE_PAEV86) return BS3TESTDOMODE_SKIPPED; + + bs3CpuWeird1_SetGlobals(bMode); + + /** @todo test sysenter and syscall too. */ + /** @todo test INTO. */ + /** @todo test all V8086 software INT delivery modes (currently only 4 and 1). */ + + /* Note! Both ICEBP and BOUND has be checked cursorily and found not to be affected. */ + if (BS3_MODE_IS_16BIT_CODE(bMode)) + { + bs3CpuWeird1_DbgInhibitRingXfer_Worker(bMode, 0x80, 2, 2, bs3CpuWeird1_InhibitedInt80_c16, bs3CpuWeird1_InhibitedInt80_int80_c16); + if (!BS3_MODE_IS_V86(bMode) || !g_fVME) + { + /** @todo explain why these GURU */ + bs3CpuWeird1_DbgInhibitRingXfer_Worker(bMode, 0x03, 2, 2, bs3CpuWeird1_InhibitedInt3_c16, bs3CpuWeird1_InhibitedInt3_int3_c16); + bs3CpuWeird1_DbgInhibitRingXfer_Worker(bMode, 0x03, 1, 2, bs3CpuWeird1_InhibitedBp_c16, bs3CpuWeird1_InhibitedBp_int3_c16); + } + } + else if (BS3_MODE_IS_32BIT_CODE(bMode)) + { + bs3CpuWeird1_DbgInhibitRingXfer_Worker(bMode, 0x80, 2, 4, bs3CpuWeird1_InhibitedInt80_c32, bs3CpuWeird1_InhibitedInt80_int80_c32); + bs3CpuWeird1_DbgInhibitRingXfer_Worker(bMode, 0x03, 2, 4, bs3CpuWeird1_InhibitedInt3_c32, bs3CpuWeird1_InhibitedInt3_int3_c32); + bs3CpuWeird1_DbgInhibitRingXfer_Worker(bMode, 0x03, 1, 4, bs3CpuWeird1_InhibitedBp_c32, bs3CpuWeird1_InhibitedBp_int3_c32); + } + else + { + bs3CpuWeird1_DbgInhibitRingXfer_Worker(bMode, 0x80, 2, 0, bs3CpuWeird1_InhibitedInt80_c64, bs3CpuWeird1_InhibitedInt80_int80_c64); + bs3CpuWeird1_DbgInhibitRingXfer_Worker(bMode, 0x03, 2, 0, bs3CpuWeird1_InhibitedInt3_c64, bs3CpuWeird1_InhibitedInt3_int3_c64); + bs3CpuWeird1_DbgInhibitRingXfer_Worker(bMode, 0x03, 1, 0, bs3CpuWeird1_InhibitedBp_c64, bs3CpuWeird1_InhibitedBp_int3_c64); + } + + return 0; +} + + +/********************************************************************************************************************************* +* IP / EIP Wrapping * +*********************************************************************************************************************************/ +#define PROTO_ALL(a_Template) \ + FNBS3FAR a_Template ## _c16, a_Template ## _c16_EndProc, \ + a_Template ## _c32, a_Template ## _c32_EndProc, \ + a_Template ## _c64, a_Template ## _c64_EndProc +PROTO_ALL(bs3CpuWeird1_PcWrapBenign1); +PROTO_ALL(bs3CpuWeird1_PcWrapBenign2); +PROTO_ALL(bs3CpuWeird1_PcWrapCpuId); +PROTO_ALL(bs3CpuWeird1_PcWrapIn80); +PROTO_ALL(bs3CpuWeird1_PcWrapOut80); +PROTO_ALL(bs3CpuWeird1_PcWrapSmsw); +PROTO_ALL(bs3CpuWeird1_PcWrapRdCr0); +PROTO_ALL(bs3CpuWeird1_PcWrapRdDr0); +PROTO_ALL(bs3CpuWeird1_PcWrapWrDr0); +#undef PROTO_ALL + +typedef enum { kPcWrapSetup_None, kPcWrapSetup_ZeroRax } PCWRAPSETUP; + +/** + * Compares pc wraparound result. + */ +static uint8_t bs3CpuWeird1_ComparePcWrap(PCBS3TRAPFRAME pTrapCtx, PCBS3TRAPFRAME pTrapExpect) +{ + uint16_t const cErrorsBefore = Bs3TestSubErrorCount(); + CHECK_MEMBER("bXcpt", "%#04x", pTrapCtx->bXcpt, pTrapExpect->bXcpt); + CHECK_MEMBER("bErrCd", "%#06RX64", pTrapCtx->uErrCd, pTrapExpect->uErrCd); + Bs3TestCheckRegCtxEx(&pTrapCtx->Ctx, &pTrapExpect->Ctx, 0 /*cbPcAdjust*/, 0 /*cbSpAdjust*/, 0 /*fExtraEfl*/, + g_pszTestMode, g_usBs3TestStep); + if (Bs3TestSubErrorCount() != cErrorsBefore) + { + Bs3TrapPrintFrame(pTrapCtx); + Bs3TestPrintf("CS=%04RX16 SS:ESP=%04RX16:%08RX64 EFL=%RX64 cbIret=%#x\n", + pTrapCtx->uHandlerCs, pTrapCtx->uHandlerSs, pTrapCtx->uHandlerRsp, + pTrapCtx->fHandlerRfl, pTrapCtx->cbIretFrame); +#if 0 + Bs3TestPrintf("Halting in ComparePcWrap: bXcpt=%#x\n", pTrapCtx->bXcpt); + ASMHalt(); +#endif + return 1; + } + return 0; +} + + +static uint8_t bs3CpuWeird1_PcWrapping_Worker16(uint8_t bMode, RTSEL SelCode, uint8_t BS3_FAR *pbHead, + uint8_t BS3_FAR *pbTail, uint8_t BS3_FAR *pbAfter, + void const BS3_FAR *pvTemplate, size_t cbTemplate, PCWRAPSETUP enmSetup) +{ + BS3TRAPFRAME TrapCtx; + BS3TRAPFRAME TrapExpect; + BS3REGCTX Ctx; + uint8_t bXcpt; + + /* make sure they're allocated */ + Bs3MemZero(&Ctx, sizeof(Ctx)); + Bs3MemZero(&TrapCtx, sizeof(TrapCtx)); + Bs3MemZero(&TrapExpect, sizeof(TrapExpect)); + + /* + * Create the expected result by first placing the code template + * at the start of the buffer and giving it a quick run. + * + * I cannot think of any instruction always causing #GP(0) right now, so + * we generate a ud2 and modify it instead. + */ + Bs3MemCpy(pbHead, pvTemplate, cbTemplate); + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) <= BS3CPU_80286) + { + pbHead[cbTemplate] = 0xcc; /* int3 */ + bXcpt = X86_XCPT_BP; + } + else + { + pbHead[cbTemplate] = 0x0f; /* ud2 */ + pbHead[cbTemplate + 1] = 0x0b; + bXcpt = X86_XCPT_UD; + } + + Bs3RegCtxSaveEx(&Ctx, bMode, 1024); + + Ctx.cs = SelCode; + Ctx.rip.u = 0; + switch (enmSetup) + { + case kPcWrapSetup_None: + break; + case kPcWrapSetup_ZeroRax: + Ctx.rax.u = 0; + break; + } + + /* V8086: Set IOPL to 3. */ + if (BS3_MODE_IS_V86(bMode)) + Ctx.rflags.u32 |= X86_EFL_IOPL; + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapExpect); + if (TrapExpect.bXcpt != bXcpt) + { + + Bs3TestFailedF("%u: Setup: bXcpt is %#x, expected %#x!\n", g_usBs3TestStep, TrapExpect.bXcpt, bXcpt); + Bs3TrapPrintFrame(&TrapExpect); + return 1; + } + + /* + * Adjust the contexts for the real test. + */ + Ctx.cs = SelCode; + Ctx.rip.u = (uint32_t)_64K - cbTemplate; + + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) <= BS3CPU_80286) + TrapExpect.Ctx.rip.u = 1; + else + { + if (BS3_MODE_IS_16BIT_SYS(bMode)) + TrapExpect.Ctx.rip.u = 0; + else + TrapExpect.Ctx.rip.u = UINT32_C(0x10000); + TrapExpect.bXcpt = X86_XCPT_GP; + TrapExpect.uErrCd = 0; + } + + /* + * Prepare the buffer for 16-bit wrap around. + */ + Bs3MemSet(pbHead, 0xcc, 64); /* int3 */ + if (bXcpt == X86_XCPT_UD) + { + pbHead[0] = 0x0f; /* ud2 */ + pbHead[1] = 0x0b; + } + Bs3MemCpy(&pbTail[_4K - cbTemplate], pvTemplate, cbTemplate); + Bs3MemSet(pbAfter, 0xf1, 64); /* icebp / int1 */ + + /* + * Do a test run. + */ + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (!bs3CpuWeird1_ComparePcWrap(&TrapCtx, &TrapExpect)) + { +#if 0 /* needs more work */ + /* + * Slide the instruction template across the boundrary byte-by-byte and + * check that it triggers #GP on the initial instruction on 386+. + */ + unsigned cbTail; + unsigned cbHead; + g_usBs3TestStep++; + for (cbTail = cbTemplate - 1, cbHead = 1; cbTail > 0; cbTail--, cbHead++, g_usBs3TestStep++) + { + pbTail[X86_PAGE_SIZE - cbTail - 1] = 0xcc; + Bs3MemCpy(&pbTail[X86_PAGE_SIZE - cbTail], pvTemplate, cbTail); + Bs3MemCpy(pbHead, &((uint8_t const *)pvTemplate)[cbTail], cbHead); + if (bXcpt == X86_XCPT_BP) + pbHead[cbHead] = 0xcc; /* int3 */ + else + { + pbHead[cbHead] = 0x0f; /* ud2 */ + pbHead[cbHead + 1] = 0x0b; + } + + Ctx.rip.u = (uint32_t)_64K - cbTail; + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) <= BS3CPU_80286) + TrapExpect.Ctx.rip.u = cbHead + 1; + else + { + TrapExpect.Ctx.rip.u = Ctx.rip.u; + } + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bs3CpuWeird1_ComparePcWrap(&TrapCtx, &TrapExpect)) + return 1; + } +#endif + } + return 0; +} + + +static uint8_t bs3CpuWeird1_PcWrapping_Worker32(uint8_t bMode, RTSEL SelCode, uint8_t BS3_FAR *pbPage1, + uint8_t BS3_FAR *pbPage2, uint32_t uFlatPage2, + void const BS3_FAR *pvTemplate, size_t cbTemplate, PCWRAPSETUP enmSetup) +{ + BS3TRAPFRAME TrapCtx; + BS3TRAPFRAME TrapExpect; + BS3REGCTX Ctx; + unsigned cbPage1; + unsigned cbPage2; + + /* make sure they're allocated */ + Bs3MemZero(&Ctx, sizeof(Ctx)); + Bs3MemZero(&TrapCtx, sizeof(TrapCtx)); + Bs3MemZero(&TrapExpect, sizeof(TrapExpect)); + + //Bs3TestPrintf("SelCode=%#x pbPage1=%p pbPage2=%p uFlatPage2=%RX32 pvTemplate=%p cbTemplate\n", + // SelCode, pbPage1, pbPage2, uFlatPage2, pvTemplate, cbTemplate); + + /* + * Create the expected result by first placing the code template + * at the start of the buffer and giving it a quick run. + */ + Bs3MemSet(pbPage1, 0xcc, _4K); + Bs3MemSet(pbPage2, 0xcc, _4K); + Bs3MemCpy(&pbPage1[_4K - cbTemplate], pvTemplate, cbTemplate); + pbPage2[0] = 0x0f; /* ud2 */ + pbPage2[1] = 0x0b; + + Bs3RegCtxSaveEx(&Ctx, bMode, 1024); + + Ctx.cs = BS3_SEL_R0_CS32; + Ctx.rip.u = uFlatPage2 - cbTemplate; + switch (enmSetup) + { + case kPcWrapSetup_None: + break; + case kPcWrapSetup_ZeroRax: + Ctx.rax.u = 0; + break; + } + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapExpect); + if (TrapExpect.bXcpt != X86_XCPT_UD) + { + + Bs3TestFailedF("%u: Setup: bXcpt is %#x, expected %#x!\n", g_usBs3TestStep, TrapExpect.bXcpt, X86_XCPT_UD); + Bs3TrapPrintFrame(&TrapExpect); + return 1; + } + + /* + * The real test uses the special CS selector. + */ + Ctx.cs = SelCode; + TrapExpect.Ctx.cs = SelCode; + + /* + * Unlike 16-bit mode, the instruction may cross the wraparound boundary, + * so we test by advancing the template across byte-by-byte. + */ + for (cbPage1 = cbTemplate, cbPage2 = 0; cbPage1 > 0; cbPage1--, cbPage2++, g_usBs3TestStep++) + { + pbPage1[X86_PAGE_SIZE - cbPage1 - 1] = 0xcc; + Bs3MemCpy(&pbPage1[X86_PAGE_SIZE - cbPage1], pvTemplate, cbPage1); + Bs3MemCpy(pbPage2, &((uint8_t const *)pvTemplate)[cbPage1], cbPage2); + pbPage2[cbPage2] = 0x0f; /* ud2 */ + pbPage2[cbPage2 + 1] = 0x0b; + + Ctx.rip.u = UINT32_MAX - cbPage1 + 1; + TrapExpect.Ctx.rip.u = cbPage2; + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bs3CpuWeird1_ComparePcWrap(&TrapCtx, &TrapExpect)) + return 1; + } + return 0; +} + + +static uint8_t bs3CpuWeird1_PcWrapping_Worker64(uint8_t bMode, uint8_t BS3_FAR *pbBuf, uint32_t uFlatBuf, + void const BS3_FAR *pvTemplate, size_t cbTemplate, PCWRAPSETUP enmSetup) +{ + uint8_t BS3_FAR * const pbPage1 = pbBuf; /* mapped at 0, 4G and 8G */ + uint8_t BS3_FAR * const pbPage2 = &pbBuf[X86_PAGE_SIZE]; /* mapped at -4K, 4G-4K and 8G-4K. */ + BS3TRAPFRAME TrapCtx; + BS3TRAPFRAME TrapExpect; + BS3REGCTX Ctx; + unsigned cbStart; + unsigned cbEnd; + + /* make sure they're allocated */ + Bs3MemZero(&Ctx, sizeof(Ctx)); + Bs3MemZero(&TrapCtx, sizeof(TrapCtx)); + Bs3MemZero(&TrapExpect, sizeof(TrapExpect)); + + /* + * Create the expected result by first placing the code template + * at the start of the buffer and giving it a quick run. + */ + Bs3MemCpy(pbPage1, pvTemplate, cbTemplate); + pbPage1[cbTemplate] = 0x0f; /* ud2 */ + pbPage1[cbTemplate + 1] = 0x0b; + + Bs3RegCtxSaveEx(&Ctx, bMode, 1024); + + Ctx.rip.u = uFlatBuf; + switch (enmSetup) + { + case kPcWrapSetup_None: + break; + case kPcWrapSetup_ZeroRax: + Ctx.rax.u = 0; + break; + } + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapExpect); + if (TrapExpect.bXcpt != X86_XCPT_UD) + { + + Bs3TestFailedF("%u: Setup: bXcpt is %#x, expected %#x!\n", g_usBs3TestStep, TrapExpect.bXcpt, X86_XCPT_UD); + Bs3TrapPrintFrame(&TrapExpect); + return 1; + } + + /* + * Unlike 16-bit mode, the instruction may cross the wraparound boundary, + * so we test by advancing the template across byte-by-byte. + * + * Page #1 is mapped at address zero and Page #2 as the last one. + */ + Bs3MemSet(pbBuf, 0xf1, X86_PAGE_SIZE * 2); + for (cbStart = cbTemplate, cbEnd = 0; cbStart > 0; cbStart--, cbEnd++) + { + pbPage2[X86_PAGE_SIZE - cbStart - 1] = 0xf1; + Bs3MemCpy(&pbPage2[X86_PAGE_SIZE - cbStart], pvTemplate, cbStart); + Bs3MemCpy(pbPage1, &((uint8_t const *)pvTemplate)[cbStart], cbEnd); + pbPage1[cbEnd] = 0x0f; /* ud2 */ + pbPage1[cbEnd + 1] = 0x0b; + + Ctx.rip.u = UINT64_MAX - cbStart + 1; + TrapExpect.Ctx.rip.u = cbEnd; + + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bs3CpuWeird1_ComparePcWrap(&TrapCtx, &TrapExpect)) + return 1; + g_usBs3TestStep++; + + /* Also check that crossing 4G isn't buggered up in our code by + 32-bit and 16-bit mode support.*/ + Ctx.rip.u = _4G - cbStart; + TrapExpect.Ctx.rip.u = _4G + cbEnd; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bs3CpuWeird1_ComparePcWrap(&TrapCtx, &TrapExpect)) + return 1; + g_usBs3TestStep++; + + Ctx.rip.u = _4G*2 - cbStart; + TrapExpect.Ctx.rip.u = _4G*2 + cbEnd; + Bs3TrapSetJmpAndRestore(&Ctx, &TrapCtx); + if (bs3CpuWeird1_ComparePcWrap(&TrapCtx, &TrapExpect)) + return 1; + g_usBs3TestStep += 2; + } + return 0; +} + + + +BS3_DECL_FAR(uint8_t) BS3_CMN_FAR_NM(bs3CpuWeird1_PcWrapping)(uint8_t bMode) +{ + uint8_t bRet = 1; + size_t i; + + bs3CpuWeird1_SetGlobals(bMode); + + if (BS3_MODE_IS_16BIT_CODE(bMode)) + { + /* + * For 16-bit testing, we need a 68 KB buffer. + * + * This is a little annoying to work with from 16-bit bit, so we use + * separate pointers to each interesting bit of it. + */ + /** @todo add api for doing this, so we don't need to include bs3-cmn-memory.h. */ + uint8_t BS3_FAR *pbBuf = (uint8_t BS3_FAR *)Bs3SlabAllocEx(&g_Bs3Mem4KLow.Core, 17 /*cPages*/, 0 /*fFlags*/); + if (pbBuf != NULL) + { + uint32_t const uFlatBuf = Bs3SelPtrToFlat(pbBuf); + uint8_t BS3_FAR *pbTail = Bs3XptrFlatToCurrent(uFlatBuf + 0x0f000); + uint8_t BS3_FAR *pbAfter = Bs3XptrFlatToCurrent(uFlatBuf + UINT32_C(0x10000)); + RTSEL SelCode; + uint32_t off; + static struct { FPFNBS3FAR pfnStart, pfnEnd; PCWRAPSETUP enmSetup; unsigned fNoV86 : 1; } + const s_aTemplates16[] = + { +#define ENTRY16(a_Template, a_enmSetup, a_fNoV86) { a_Template ## _c16, a_Template ## _c16_EndProc, a_enmSetup, a_fNoV86 } + ENTRY16(bs3CpuWeird1_PcWrapBenign1, kPcWrapSetup_None, 0), + ENTRY16(bs3CpuWeird1_PcWrapBenign2, kPcWrapSetup_None, 0), + ENTRY16(bs3CpuWeird1_PcWrapCpuId, kPcWrapSetup_ZeroRax, 0), + ENTRY16(bs3CpuWeird1_PcWrapIn80, kPcWrapSetup_None, 0), + ENTRY16(bs3CpuWeird1_PcWrapOut80, kPcWrapSetup_None, 0), + ENTRY16(bs3CpuWeird1_PcWrapSmsw, kPcWrapSetup_None, 0), + ENTRY16(bs3CpuWeird1_PcWrapRdCr0, kPcWrapSetup_None, 1), + ENTRY16(bs3CpuWeird1_PcWrapRdDr0, kPcWrapSetup_None, 1), + ENTRY16(bs3CpuWeird1_PcWrapWrDr0, kPcWrapSetup_ZeroRax, 1), +#undef ENTRY16 + }; + + /* Fill the buffer with int1 instructions: */ + for (off = 0; off < UINT32_C(0x11000); off += _4K) + { + uint8_t BS3_FAR *pbPage = Bs3XptrFlatToCurrent(uFlatBuf + off); + Bs3MemSet(pbPage, 0xf1, _4K); + } + + /* Setup the CS for it. */ + SelCode = (uint16_t)(uFlatBuf >> 4); + if (!BS3_MODE_IS_RM_OR_V86(bMode)) + { + Bs3SelSetup16BitCode(&Bs3GdteSpare00, uFlatBuf, 0); + SelCode = BS3_SEL_SPARE_00; + } + + /* Allow IN and OUT to port 80h from V8086 mode. */ + if (BS3_MODE_IS_V86(bMode)) + { + Bs3RegSetTr(BS3_SEL_TSS32_IOBP_IRB); + ASMBitClear(Bs3SharedIobp, 0x80); + } + + for (i = 0; i < RT_ELEMENTS(s_aTemplates16); i++) + { + if (!s_aTemplates16[i].fNoV86 || !BS3_MODE_IS_V86(bMode)) + bs3CpuWeird1_PcWrapping_Worker16(bMode, SelCode, pbBuf, pbTail, pbAfter, s_aTemplates16[i].pfnStart, + (uintptr_t)s_aTemplates16[i].pfnEnd - (uintptr_t)s_aTemplates16[i].pfnStart, + s_aTemplates16[i].enmSetup); + g_usBs3TestStep = i * 256; + } + + if (BS3_MODE_IS_V86(bMode)) + ASMBitSet(Bs3SharedIobp, 0x80); + + Bs3SlabFree(&g_Bs3Mem4KLow.Core, uFlatBuf, 17); + + bRet = 0; + } + else + Bs3TestFailed("Failed to allocate 17 pages (68KB)"); + } + else + { + /* + * For 32-bit and 64-bit mode we just need two pages. + */ + size_t const cbBuf = X86_PAGE_SIZE * 2; + uint8_t BS3_FAR *pbBuf = (uint8_t BS3_FAR *)Bs3MemAlloc(BS3MEMKIND_TILED, cbBuf); + if (pbBuf) + { + uint32_t const uFlatBuf = Bs3SelPtrToFlat(pbBuf); + Bs3MemSet(pbBuf, 0xf1, cbBuf); + + /* + * For 32-bit we set up a CS that starts with the 2nd page and + * ends with the first. + */ + if (BS3_MODE_IS_32BIT_CODE(bMode)) + { + static struct { FPFNBS3FAR pfnStart, pfnEnd; PCWRAPSETUP enmSetup; } const s_aTemplates32[] = + { +#define ENTRY32(a_Template, a_enmSetup) { a_Template ## _c32, a_Template ## _c32_EndProc, a_enmSetup } + ENTRY32(bs3CpuWeird1_PcWrapBenign1, kPcWrapSetup_None), + ENTRY32(bs3CpuWeird1_PcWrapBenign2, kPcWrapSetup_None), + ENTRY32(bs3CpuWeird1_PcWrapCpuId, kPcWrapSetup_ZeroRax), + ENTRY32(bs3CpuWeird1_PcWrapIn80, kPcWrapSetup_None), + ENTRY32(bs3CpuWeird1_PcWrapOut80, kPcWrapSetup_None), + ENTRY32(bs3CpuWeird1_PcWrapSmsw, kPcWrapSetup_None), + ENTRY32(bs3CpuWeird1_PcWrapRdCr0, kPcWrapSetup_None), + ENTRY32(bs3CpuWeird1_PcWrapRdDr0, kPcWrapSetup_None), + ENTRY32(bs3CpuWeird1_PcWrapWrDr0, kPcWrapSetup_ZeroRax), +#undef ENTRY32 + }; + + Bs3SelSetup32BitCode(&Bs3GdteSpare00, uFlatBuf + X86_PAGE_SIZE, UINT32_MAX, 0); + + for (i = 0; i < RT_ELEMENTS(s_aTemplates32); i++) + { + //Bs3TestPrintf("pfnStart=%p pfnEnd=%p\n", s_aTemplates32[i].pfnStart, s_aTemplates32[i].pfnEnd); + bs3CpuWeird1_PcWrapping_Worker32(bMode, BS3_SEL_SPARE_00, pbBuf, &pbBuf[X86_PAGE_SIZE], + uFlatBuf + X86_PAGE_SIZE, Bs3SelLnkPtrToCurPtr(s_aTemplates32[i].pfnStart), + (uintptr_t)s_aTemplates32[i].pfnEnd - (uintptr_t)s_aTemplates32[i].pfnStart, + s_aTemplates32[i].enmSetup); + g_usBs3TestStep = i * 256; + } + + bRet = 0; + } + /* + * For 64-bit we have to alias the two buffer pages to the first and + * last page in the address space. To test that the 32-bit 4G rollover + * isn't incorrectly applied to LM64, we repeat this mappingfor the 4G + * and 8G boundaries too. + * + * This ASSUMES there is nothing important in page 0 when in LM64. + */ + else + { + static const struct { uint64_t uDst; uint16_t off; } s_aMappings[] = + { + { UINT64_MAX - X86_PAGE_SIZE + 1, X86_PAGE_SIZE * 1 }, + { UINT64_C(0), X86_PAGE_SIZE * 0 }, +#if 1 /* technically not required as we just repeat the same 4G address space in long mode: */ + { _4G - X86_PAGE_SIZE, X86_PAGE_SIZE * 1 }, + { _4G, X86_PAGE_SIZE * 0 }, + { _4G*2 - X86_PAGE_SIZE, X86_PAGE_SIZE * 1 }, + { _4G*2, X86_PAGE_SIZE * 0 }, +#endif + }; + int rc = VINF_SUCCESS; + unsigned iMap; + BS3_ASSERT(bMode == BS3_MODE_LM64); + for (iMap = 0; iMap < RT_ELEMENTS(s_aMappings) && RT_SUCCESS(rc); iMap++) + { + rc = Bs3PagingAlias(s_aMappings[iMap].uDst, uFlatBuf + s_aMappings[iMap].off, X86_PAGE_SIZE, + X86_PTE_P | X86_PTE_A | X86_PTE_D | X86_PTE_RW); + if (RT_FAILURE(rc)) + Bs3TestFailedF("Bs3PagingAlias(%#RX64,...) failed: %d", s_aMappings[iMap].uDst, rc); + } + + if (RT_SUCCESS(rc)) + { + static struct { FPFNBS3FAR pfnStart, pfnEnd; PCWRAPSETUP enmSetup; } const s_aTemplates64[] = + { +#define ENTRY64(a_Template, a_enmSetup) { a_Template ## _c64, a_Template ## _c64_EndProc, a_enmSetup } + ENTRY64(bs3CpuWeird1_PcWrapBenign1, kPcWrapSetup_None), + ENTRY64(bs3CpuWeird1_PcWrapBenign2, kPcWrapSetup_None), + ENTRY64(bs3CpuWeird1_PcWrapCpuId, kPcWrapSetup_ZeroRax), + ENTRY64(bs3CpuWeird1_PcWrapIn80, kPcWrapSetup_None), + ENTRY64(bs3CpuWeird1_PcWrapOut80, kPcWrapSetup_None), + ENTRY64(bs3CpuWeird1_PcWrapSmsw, kPcWrapSetup_None), + ENTRY64(bs3CpuWeird1_PcWrapRdCr0, kPcWrapSetup_None), + ENTRY64(bs3CpuWeird1_PcWrapRdDr0, kPcWrapSetup_None), + ENTRY64(bs3CpuWeird1_PcWrapWrDr0, kPcWrapSetup_ZeroRax), +#undef ENTRY64 + }; + + for (i = 0; i < RT_ELEMENTS(s_aTemplates64); i++) + { + bs3CpuWeird1_PcWrapping_Worker64(bMode, pbBuf, uFlatBuf, + Bs3SelLnkPtrToCurPtr(s_aTemplates64[i].pfnStart), + (uintptr_t)s_aTemplates64[i].pfnEnd + - (uintptr_t)s_aTemplates64[i].pfnStart, + s_aTemplates64[i].enmSetup); + g_usBs3TestStep = i * 256; + } + + bRet = 0; + + Bs3PagingUnalias(UINT64_C(0), X86_PAGE_SIZE); + } + + while (iMap-- > 0) + Bs3PagingUnalias(s_aMappings[iMap].uDst, X86_PAGE_SIZE); + } + Bs3MemFree(pbBuf, cbBuf); + } + else + Bs3TestFailed("Failed to allocate 2-3 pages for tests."); + } + + return bRet; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-cpu-weird-1.c b/src/VBox/ValidationKit/bootsectors/bs3-cpu-weird-1.c new file mode 100644 index 00000000..5bb604c0 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-cpu-weird-1.c @@ -0,0 +1,76 @@ +/* $Id: bs3-cpu-weird-1.c $ */ +/** @file + * BS3Kit - bs3-cpu-weird-1, 16-bit C code. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> +#include <iprt/asm-amd64-x86.h> + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +FNBS3TESTDOMODE bs3CpuWeird1_DbgInhibitRingXfer_f16; +FNBS3TESTDOMODE bs3CpuWeird1_PcWrapping_f16; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static const BS3TESTMODEBYONEENTRY g_aModeByOneTests[] = +{ + { "dbg+inhibit+ringxfer", bs3CpuWeird1_DbgInhibitRingXfer_f16, 0 }, + { "pc wrapping", bs3CpuWeird1_PcWrapping_f16, 0 }, +}; + + +BS3_DECL(void) Main_rm() +{ + Bs3InitAll_rm(); + Bs3TestInit("bs3-cpu-weird-1"); + Bs3TestPrintf("g_uBs3CpuDetected=%#x\n", g_uBs3CpuDetected); + + /* + * Do tests driven from 16-bit code. + */ + Bs3TestDoModesByOne_rm(g_aModeByOneTests, RT_ELEMENTS(g_aModeByOneTests), 0); + + Bs3TestTerm(); + Bs3Shutdown(); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-fpustate-1-asm.asm b/src/VBox/ValidationKit/bootsectors/bs3-fpustate-1-asm.asm new file mode 100644 index 00000000..e94281dc --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-fpustate-1-asm.asm @@ -0,0 +1,172 @@ +; $Id: bs3-fpustate-1-asm.asm $ +;; @file +; BS3Kit - bs3-fpustate-1, assembly helpers and template instantiation. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit.mac" + + +;********************************************************************************************************************************* +;* Global Variables * +;********************************************************************************************************************************* +BS3_BEGIN_DATA16 +;; @name Floating point constants. +; @{ +g_r32_0dot1: dd 0.1 +g_r32_3dot2: dd 3.2 +g_r32_Zero: dd 0.0 +g_r32_One: dd 1.0 +g_r32_Two: dd 2.0 +g_r32_Three: dd 3.0 +g_r32_Ten: dd 10.0 +g_r32_Eleven: dd 11.0 +g_r32_ThirtyTwo:dd 32.0 +g_r32_Min: dd 000800000h +g_r32_Max: dd 07f7fffffh +g_r32_Inf: dd 07f800000h +g_r32_SNaN: dd 07f800001h +g_r32_SNaNMax: dd 07fbfffffh +g_r32_QNaN: dd 07fc00000h +g_r32_QNaNMax: dd 07fffffffh +g_r32_NegQNaN: dd 0ffc00000h + +g_r64_0dot1: dq 0.1 +g_r64_6dot9: dq 6.9 +g_r64_Zero: dq 0.0 +g_r64_One: dq 1.0 +g_r64_Two: dq 2.0 +g_r64_Three: dq 3.0 +g_r64_Ten: dq 10.0 +g_r64_Eleven: dq 11.0 +g_r64_ThirtyTwo:dq 32.0 +g_r64_Min: dq 00010000000000000h +g_r64_Max: dq 07fefffffffffffffh +g_r64_Inf: dq 07ff0000000000000h +g_r64_SNaN: dq 07ff0000000000001h +g_r64_SNaNMax: dq 07ff7ffffffffffffh +g_r64_NegQNaN: dq 0fff8000000000000h +g_r64_QNaN: dq 07ff8000000000000h +g_r64_QNaNMax: dq 07fffffffffffffffh +g_r64_DnMin: dq 00000000000000001h +g_r64_DnMax: dq 0000fffffffffffffh + + +g_r80_0dot1: dt 0.1 +g_r80_3dot2: dt 3.2 +g_r80_Zero: dt 0.0 +g_r80_One: dt 1.0 +g_r80_Two: dt 2.0 +g_r80_Three: dt 3.0 +g_r80_Ten: dt 10.0 +g_r80_Eleven: dt 11.0 +g_r80_ThirtyTwo:dt 32.0 +%ifdef __NASM__ +g_r80_Min: dq 08000000000000000h + dw 00001h +g_r80_Max: dq 0ffffffffffffffffh + dw 07ffeh +g_r80_Inf: dq 08000000000000000h + dw 07fffh +g_r80_QNaN: dq 0c000000000000000h + dw 07fffh +g_r80_QNaNMax: dq 0ffffffffffffffffh + dw 07fffh +g_r80_NegQNaN: dq 0c000000000000000h + dw 0ffffh +g_r80_SNaN: dq 08000000000000001h + dw 07fffh +g_r80_SNaNMax: dq 0bfffffffffffffffh + dw 07fffh +g_r80_DnMin: dq 00000000000000001h + dw 00000h +g_r80_DnMax: dq 07fffffffffffffffh + dw 00000h +%else +g_r80_Min: dt 000018000000000000000h +g_r80_Max: dt 07ffeffffffffffffffffh +g_r80_Inf: dt 07fff8000000000000000h +g_r80_QNaN: dt 07fffc000000000000000h +g_r80_QNaNMax: dt 07fffffffffffffffffffh +g_r80_NegQNaN: dt 0ffffc000000000000000h +g_r80_SNaN: dt 07fff8000000000000001h +g_r80_SNaNMax: dt 07fffbfffffffffffffffh +g_r80_DnMin: dt 000000000000000000001h +g_r80_DnMax: dt 000007fffffffffffffffh +%endif + +g_r32V1: dd 3.2 +g_r32V2: dd -1.9 +g_r64V1: dq 6.4 +g_r80V1: dt 8.0 + +; Denormal numbers. +g_r32D0: dd 000200000h +;; @} + +;; @name Upconverted Floating point constants +; @{ +;g_r80_r32_0dot1: dt 0.1 +%ifdef __NASM__ +g_r80_r32_3dot2: dq 0cccccd0000000000h + dw 04000h +%else +g_r80_r32_3dot2: dt 04000cccccd0000000000h +%endif +;g_r80_r32_Zero: dt 0.0 +;g_r80_r32_One: dt 1.0 +;g_r80_r32_Two: dt 2.0 +;g_r80_r32_Three: dt 3.0 +;g_r80_r32_Ten: dt 10.0 +;g_r80_r32_Eleven: dt 11.0 +;g_r80_r32_ThirtyTwo: dt 32.0 +;; @} + +;; @name Decimal constants. +; @{ +g_u64Zero: dd 0 +g_u32Zero: dw 0 +g_u64Two: dd 2 +g_u32Two: dw 2 +;; @} + + +; +; Instantiate code templates. +; +BS3_INSTANTIATE_TEMPLATE_ESSENTIALS "bs3-fpustate-1-template.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-fpustate-1-template.c b/src/VBox/ValidationKit/bootsectors/bs3-fpustate-1-template.c new file mode 100644 index 00000000..5b0c7efa --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-fpustate-1-template.c @@ -0,0 +1,409 @@ +/* $Id: bs3-fpustate-1-template.c $ */ +/** @file + * BS3Kit - bs3-fpustate-1, C code template. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/asm-amd64-x86.h> +#include <VBox/VMMDevTesting.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + + +#ifdef BS3_INSTANTIATING_CMN + +/** + * Displays the differences between the two states. + */ +# define bs3FpuState1_Diff BS3_CMN_NM(bs3FpuState1_Diff) +BS3_DECL_NEAR(void) bs3FpuState1_Diff(X86FXSTATE const BS3_FAR *pExpected, X86FXSTATE const BS3_FAR *pChecking) +{ + unsigned i; + +# define CHECK(a_Member, a_Fmt) \ + if (pExpected->a_Member != pChecking->a_Member) \ + Bs3TestPrintf(" " #a_Member ": " a_Fmt ", expected " a_Fmt "\n", pChecking->a_Member, pExpected->a_Member); \ + else do { } while (0) + CHECK(FCW, "%#RX16"); + CHECK(FSW, "%#RX16"); + CHECK(FTW, "%#RX16"); + CHECK(FOP, "%#RX16"); + CHECK(FPUIP, "%#RX32"); + CHECK(CS, "%#RX16"); + CHECK(Rsrvd1, "%#RX16"); + CHECK(FPUDP, "%#RX32"); + CHECK(DS, "%#RX16"); + CHECK(Rsrvd2, "%#RX16"); + CHECK(MXCSR, "%#RX32"); + CHECK(MXCSR_MASK, "%#RX32"); +# undef CHECK + for (i = 0; i < RT_ELEMENTS(pExpected->aRegs); i++) + if ( pChecking->aRegs[i].au64[0] != pExpected->aRegs[i].au64[0] + || pChecking->aRegs[i].au64[1] != pExpected->aRegs[i].au64[1]) + Bs3TestPrintf("st%u: %.16Rhxs\n" + "exp: %.16Rhxs\n", + i, &pChecking->aRegs[i], &pExpected->aRegs[i]); + for (i = 0; i < RT_ELEMENTS(pExpected->aXMM); i++) + if ( pChecking->aXMM[i].au64[0] != pExpected->aXMM[i].au64[0] + || pChecking->aXMM[i].au64[1] != pExpected->aXMM[i].au64[1]) + Bs3TestPrintf("xmm%u: %.16Rhxs\n" + " %sexp: %.16Rhxs\n", + i, &pChecking->aRegs[i], &pExpected->aRegs[i], i >= 10 ? " " : ""); +} + + +#endif /* BS3_INSTANTIATING_CMN */ + + +/* + * Mode specific code. + * Mode specific code. + * Mode specific code. + */ +#ifdef BS3_INSTANTIATING_MODE +# if TMPL_MODE == BS3_MODE_PE32 \ + || TMPL_MODE == BS3_MODE_PP32 \ + || TMPL_MODE == BS3_MODE_PAE32 \ + || TMPL_MODE == BS3_MODE_LM64 \ + || TMPL_MODE == BS3_MODE_RM + +/* Assembly helpers: */ +BS3_DECL_NEAR(void) TMPL_NM(bs3FpuState1_InitState)(X86FXSTATE BS3_FAR *pFxState, void BS3_FAR *pvMmioReg); +BS3_DECL_NEAR(void) TMPL_NM(bs3FpuState1_Restore)(X86FXSTATE const BS3_FAR *pFxState); +BS3_DECL_NEAR(void) TMPL_NM(bs3FpuState1_Save)(X86FXSTATE BS3_FAR *pFxState); + +BS3_DECL_NEAR(void) TMPL_NM(bs3FpuState1_FNStEnv)(void BS3_FAR *pvMmioReg); +BS3_DECL_NEAR(void) TMPL_NM(bs3FpuState1_MovDQU_Read)(void BS3_FAR *pvMmioReg, void BS3_FAR *pvResult); +BS3_DECL_NEAR(void) TMPL_NM(bs3FpuState1_MovDQU_Write)(void BS3_FAR *pvMmioReg); +BS3_DECL_NEAR(void) TMPL_NM(bs3FpuState1_MovUPS_Read)(void BS3_FAR *pvMmioReg, void BS3_FAR *pvResult); +BS3_DECL_NEAR(void) TMPL_NM(bs3FpuState1_MovUPS_Write)(void BS3_FAR *pvMmioReg); +BS3_DECL_NEAR(void) TMPL_NM(bs3FpuState1_FMul)(void BS3_FAR *pvMmioReg, void BS3_FAR *pvNoResult); + + +/** + * Checks if we're seeing a problem with fnstenv saving zero selectors when + * running on the compare area. + * + * This triggers in NEM mode if the native hypervisor doesn't do a good enough + * job at save the FPU state for 16-bit and 32-bit guests. We have heuristics + * in CPUMInternal.mac (SAVE_32_OR_64_FPU) for this. + * + * @returns true if this the zero selector issue. + * @param pabReadback The MMIO read buffer containing the fnstenv result + * typically produced by IEM. + * @param pabCompare The buffer containing the fnstenv result typcially + * produced by the CPU itself. + */ +static bool TMPL_NM(bs3FpuState1_IsZeroFnStEnvSelectorsProblem)(const uint8_t BS3_FAR *pabReadback, + const uint8_t BS3_FAR *pabCompare) +{ + unsigned const offCs = ARCH_BITS == 16 ? 8 : 16; + unsigned const offDs = ARCH_BITS == 16 ? 12 : 24; + if ( *(const uint16_t BS3_FAR *)&pabCompare[offCs] == 0 + && *(const uint16_t BS3_FAR *)&pabCompare[offDs] == 0) + { + /* Check the stuff before the CS register: */ + if (Bs3MemCmp(pabReadback, pabCompare, offCs) == 0) + { + /* Check the stuff between the DS and CS registers:*/ + if (Bs3MemCmp(&pabReadback[offCs + 2], &pabCompare[offCs + 2], offDs - offCs - 2) == 0) + { +#if ARCH_BITS != 16 + /* Check the stuff after the DS register if 32-bit mode: */ + if ( *(const uint16_t BS3_FAR *)&pabReadback[offDs + 2] + == *(const uint16_t BS3_FAR *)&pabCompare[offDs + 2]) +#endif + return true; + } + } + } + return false; +} + + +/** + * Tests for FPU state corruption. + * + * First we don't do anything to quit guest context for a while. + * Then we start testing weird MMIO accesses, some which amonger other things + * forces the use of the FPU state or host FPU to do the emulation. Both are a + * little complicated in raw-mode and ring-0 contexts. + * + * We ASSUME FXSAVE/FXRSTOR support here. + */ +BS3_DECL_FAR(uint8_t) TMPL_NM(bs3FpuState1_Corruption)(uint8_t bMode) +{ + /* We don't need to test that many modes, probably. */ + + uint8_t abBuf[sizeof(X86FXSTATE)*2 + 32]; + uint8_t BS3_FAR *pbTmp = &abBuf[0x10 - (((uintptr_t)abBuf) & 0x0f)]; + X86FXSTATE BS3_FAR *pExpected = (X86FXSTATE BS3_FAR *)pbTmp; + X86FXSTATE BS3_FAR *pChecking = pExpected + 1; + uint32_t iLoop; + uint32_t uStartTick; + bool fMmioReadback; + bool fReadBackError = false; + bool fReadError = false; + uint32_t cFnStEnvSelectorsZero = 0; + BS3PTRUNION MmioReg; + BS3CPUVENDOR const enmCpuVendor = Bs3GetCpuVendor(); + bool const fSkipStorIdt = Bs3TestQueryCfgBool(VMMDEV_TESTING_CFG_IS_NEM_LINUX); + bool const fMayHaveZeroStEnvSels = Bs3TestQueryCfgBool(VMMDEV_TESTING_CFG_IS_NEM_LINUX); + bool const fFastFxSaveRestore = RT_BOOL(ASMCpuId_EDX(0x80000001) & X86_CPUID_AMD_FEATURE_EDX_FFXSR); + //bool const fFdpXcptOnly = (ASMCpuIdEx_EBX(7, 0) & X86_CPUID_STEXT_FEATURE_EBX_FDP_EXCPTN_ONLY) + // && ASMCpuId_EAX(0) >= 7; + RT_NOREF(bMode); + + if (fSkipStorIdt) + Bs3TestPrintf("NEM/linux - skipping SIDT\n"); + +# undef CHECK_STATE +# define CHECK_STATE(a_Instr, a_fIsFnStEnv) \ + do { \ + TMPL_NM(bs3FpuState1_Save)(pChecking); \ + if (Bs3MemCmp(pExpected, pChecking, sizeof(*pExpected)) != 0) \ + { \ + Bs3TestFailedF("State differs after " #a_Instr " (write) in loop #%RU32\n", iLoop); \ + bs3FpuState1_Diff(pExpected, pChecking); \ + Bs3PitDisable(); \ + return 1; \ + } \ + } while (0) + + + /* Make this code executable in raw-mode. A bit tricky. */ + ASMSetCR0(ASMGetCR0() | X86_CR0_WP); + Bs3PitSetupAndEnablePeriodTimer(20); + ASMIntEnable(); +# if ARCH_BITS != 64 + ASMHalt(); +# endif + + /* Figure out which MMIO region we'll be using so we can correctly initialize FPUDS. */ +# if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + MmioReg.pv = BS3_FP_MAKE(VMMDEV_TESTING_MMIO_RM_SEL, VMMDEV_TESTING_MMIO_RM_OFF2(0)); +# elif BS3_MODE_IS_16BIT_CODE(TMPL_MODE) + MmioReg.pv = BS3_FP_MAKE(BS3_SEL_VMMDEV_MMIO16, 0); +# else + MmioReg.pv = (uint8_t *)VMMDEV_TESTING_MMIO_BASE; +# endif + if (MmioReg.pu32[VMMDEV_TESTING_MMIO_OFF_NOP / sizeof(uint32_t)] == VMMDEV_TESTING_NOP_RET) + { + fMmioReadback = true; + MmioReg.pb += VMMDEV_TESTING_MMIO_OFF_READBACK; + } + else + { + Bs3TestPrintf("VMMDev MMIO not found, using VGA instead\n"); + fMmioReadback = false; + MmioReg.pv = Bs3XptrFlatToCurrent(0xa7800); + } + + /* Make 100% sure we don't trap accessing the FPU state and that we can use fxsave/fxrstor. */ + g_usBs3TestStep = 1; + ASMSetCR0((ASMGetCR0() & ~(X86_CR0_TS | X86_CR0_EM)) | X86_CR0_MP); + ASMSetCR4(ASMGetCR4() | X86_CR4_OSFXSR /*| X86_CR4_OSXMMEEXCPT*/); + + /* Come up with a distinct state. We do that from assembly (will do FPU in R0/RC). */ + g_usBs3TestStep = 2; + Bs3MemSet(abBuf, 0x42, sizeof(abBuf)); + TMPL_NM(bs3FpuState1_InitState)(pExpected, MmioReg.pb); + + + /* + * Test #1: Check that we can keep it consistent for a while. + */ + g_usBs3TestStep = 3; + uStartTick = g_cBs3PitTicks; + for (iLoop = 0; iLoop < _16M; iLoop++) + { + CHECK_STATE(nop, false); + if ( (iLoop & 0xffff) == 0xffff + && g_cBs3PitTicks - uStartTick >= 20 * 20) /* 20 seconds*/ + break; + } + + /* + * Test #2: Use various FPU, SSE and weird instructions to do MMIO writes. + * + * We'll use the VMMDev readback register if possible, but make do + * with VGA if not configured. + */ +# ifdef __WATCOMC__ +# pragma DISABLE_MESSAGE(201) /* Warning! W201: Unreachable code */ +# endif + g_usBs3TestStep = 4; + uStartTick = g_cBs3PitTicks; + for (iLoop = 0; iLoop < _1M; iLoop++) + { + unsigned off; + uint8_t abCompare[64]; + uint8_t abReadback[64]; + + /* Macros */ +# undef CHECK_READBACK_WRITE_RUN +# define CHECK_READBACK_WRITE_RUN(a_Instr, a_Worker, a_Type, a_fIsFnStEnv) \ + do { \ + off = (unsigned)(iLoop & (VMMDEV_TESTING_READBACK_SIZE / 2 - 1)); \ + if (off + sizeof(a_Type) > VMMDEV_TESTING_READBACK_SIZE) \ + off = VMMDEV_TESTING_READBACK_SIZE - sizeof(a_Type); \ + a_Worker((a_Type *)&MmioReg.pb[off]); \ + if (fMmioReadback && (!fReadBackError || iLoop == 0)) \ + { \ + a_Worker((a_Type *)&abCompare[0]); \ + Bs3MemCpy(abReadback, &MmioReg.pb[off], sizeof(a_Type)); \ + if (Bs3MemCmp(abReadback, abCompare, sizeof(a_Type)) == 0) \ + { /* likely */ } \ + else if ( (a_fIsFnStEnv) \ + && fMayHaveZeroStEnvSels \ + && TMPL_NM(bs3FpuState1_IsZeroFnStEnvSelectorsProblem)(abReadback, abCompare)) \ + cFnStEnvSelectorsZero += 1; \ + else \ + { \ + Bs3TestFailedF("Read back error for " #a_Instr " in loop #%RU32:\n%.*Rhxs expected:\n%.*Rhxs\n", \ + iLoop, sizeof(a_Type), abReadback, sizeof(a_Type), abCompare); \ + fReadBackError = true; \ + } \ + } \ + } while (0) + +# undef CHECK_READBACK_WRITE +# define CHECK_READBACK_WRITE(a_Instr, a_Worker, a_Type, a_fIsFnStEnv) \ + CHECK_READBACK_WRITE_RUN(a_Instr, a_Worker, a_Type, a_fIsFnStEnv); \ + CHECK_STATE(a_Instr, a_fIsFnStEnv) +# undef CHECK_READBACK_WRITE_Z +# define CHECK_READBACK_WRITE_Z(a_Instr, a_Worker, a_Type, a_fIsFnStEnv) \ + do { \ + if (fMmioReadback && (!fReadBackError || iLoop == 0)) \ + { \ + Bs3MemZero(&abCompare[0], sizeof(a_Type)); \ + off = (unsigned)(iLoop & (VMMDEV_TESTING_READBACK_SIZE / 2 - 1)); \ + if (off + sizeof(a_Type) > VMMDEV_TESTING_READBACK_SIZE) \ + off = VMMDEV_TESTING_READBACK_SIZE - sizeof(a_Type); \ + Bs3MemZero(&MmioReg.pb[off], sizeof(a_Type)); \ + } \ + CHECK_READBACK_WRITE(a_Instr, a_Worker, a_Type, a_fIsFnStEnv); \ + } while (0) + +# undef CHECK_READBACK_READ_RUN +#define CHECK_READBACK_READ_RUN(a_Instr, a_Worker, a_Type) \ + do { \ + off = (unsigned)(iLoop & (VMMDEV_TESTING_READBACK_SIZE / 2 - 1)); \ + if (off + sizeof(a_Type) > VMMDEV_TESTING_READBACK_SIZE) \ + off = VMMDEV_TESTING_READBACK_SIZE - sizeof(a_Type); \ + a_Worker((a_Type *)&MmioReg.pb[off], (a_Type *)&abReadback[0]); \ + TMPL_NM(bs3FpuState1_Save)(pChecking); \ + } while (0) +# undef CHECK_READBACK_READ +# define CHECK_READBACK_READ(a_Instr, a_Worker, a_Type) \ + do { \ + Bs3MemSet(&abReadback[0], 0xcc, sizeof(abReadback)); \ + CHECK_READBACK_READ_RUN(a_Instr, a_Worker, a_Type); \ + CHECK_STATE(a_Instr, false); \ + if (!fReadError || iLoop == 0) \ + { \ + Bs3MemZero(&abCompare[0], sizeof(abCompare)); \ + Bs3MemCpy(&abCompare[0], &MmioReg.pb[off], sizeof(a_Type)); \ + if (Bs3MemCmp(abReadback, abCompare, sizeof(a_Type)) != 0) \ + { \ + Bs3TestFailedF("Read result check for " #a_Instr " in loop #%RU32:\n%.*Rhxs expected:\n%.*Rhxs\n", \ + iLoop, sizeof(a_Type), abReadback, sizeof(a_Type), abCompare); \ + fReadError = true; \ + } \ + } \ + } while (0) + + /* The tests. */ + if (!fSkipStorIdt) /* KVM doesn't advance RIP executing a SIDT [MMIO-memory], it seems. (Linux 5.13.1) */ + CHECK_READBACK_WRITE_Z(SIDT, ASMGetIDTR, RTIDTR, false); + CHECK_READBACK_WRITE_Z(FNSTENV, TMPL_NM(bs3FpuState1_FNStEnv), X86FSTENV32P, true); /** @todo x86.h is missing types */ + CHECK_READBACK_WRITE( MOVDQU, TMPL_NM(bs3FpuState1_MovDQU_Write), X86XMMREG, false); + CHECK_READBACK_READ( MOVDQU, TMPL_NM(bs3FpuState1_MovDQU_Read), X86XMMREG); + CHECK_READBACK_WRITE( MOVUPS, TMPL_NM(bs3FpuState1_MovUPS_Write), X86XMMREG, false); + CHECK_READBACK_READ( MOVUPS, TMPL_NM(bs3FpuState1_MovUPS_Read), X86XMMREG); + + /* Using the FPU is a little complicated, but we really need to check these things. */ + CHECK_READBACK_READ_RUN(FMUL, TMPL_NM(bs3FpuState1_FMul), uint64_t); + if (enmCpuVendor == BS3CPUVENDOR_INTEL) +# if BS3_MODE_IS_16BIT_CODE(TMPL_MODE) + pExpected->FOP = 0x040f; // skylake 6700k +# else + pExpected->FOP = 0x040b; // skylake 6700k +# endif + else if (enmCpuVendor == BS3CPUVENDOR_AMD && fFastFxSaveRestore) + pExpected->FOP = 0x0000; // Zen2 (3990x) + else + pExpected->FOP = 0x07dc; // dunno where we got this. +# if ARCH_BITS == 64 + pExpected->FPUDP = (uint32_t) (uintptr_t)&MmioReg.pb[off]; + pExpected->DS = (uint16_t)((uintptr_t)&MmioReg.pb[off] >> 32); + pExpected->Rsrvd2 = (uint16_t)((uintptr_t)&MmioReg.pb[off] >> 48); +# elif BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + pExpected->FPUDP = Bs3SelPtrToFlat(&MmioReg.pb[off]); +# else + pExpected->FPUDP = BS3_FP_OFF(&MmioReg.pb[off]); +# endif + if (enmCpuVendor == BS3CPUVENDOR_AMD && fFastFxSaveRestore) + pExpected->FPUDP = 0; // Zen2 (3990x) + CHECK_STATE(FMUL, false); + + /* check for timeout every now an then. */ + if ( (iLoop & 0xfff) == 0xfff + && g_cBs3PitTicks - uStartTick >= 20 * 20) /* 20 seconds*/ + break; + } + + Bs3PitDisable(); + +# ifdef __WATCOMC__ +# pragma ENABLE_MESSAGE(201) /* Warning! W201: Unreachable code */ +# endif + + /* + * Warn if selectors are borked (for real VBox we'll fail and not warn). + */ + if (cFnStEnvSelectorsZero > 0) + Bs3TestPrintf("Warning! NEM borked the FPU selectors %u times.\n", cFnStEnvSelectorsZero); + return 0; +} +# endif +#endif /* BS3_INSTANTIATING_MODE */ + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-fpustate-1-template.mac b/src/VBox/ValidationKit/bootsectors/bs3-fpustate-1-template.mac new file mode 100644 index 00000000..6850431d --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-fpustate-1-template.mac @@ -0,0 +1,418 @@ +; $Id: bs3-fpustate-1-template.mac $ +;; @file +; BS3Kit - bs3-fpustate-1, assembly template. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" ; setup environment + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +TMPL_BEGIN_TEXT + + +;; +; Initializes the FPU state and saves it to pFxState. +; +; BS3_DECL_NEAR(void) TMPL_NM(bs3FpuState1_InitState)(X86FXSTATE BS3_FAR *pFxState, void *pvMmioReg); +; +BS3_PROC_BEGIN_MODE bs3FpuState1_InitState, BS3_PBC_NEAR + BS3_CALL_CONV_PROLOG 2 + push xBP + mov xBP, xSP + push xBX +TONLY16 push ds + pushf +TONLY64 sub xSP, 20h + + ; + ; x87 state. + ; + fninit + fld dword [TMPL_DATA16_WRT(g_r32V1)] + fld qword [TMPL_DATA16_WRT(g_r64V1)] + fld tword [TMPL_DATA16_WRT(g_r80V1)] + fld qword [TMPL_DATA16_WRT(g_r64V1)] + fld dword [TMPL_DATA16_WRT(g_r32V2)] + fld dword [TMPL_DATA16_WRT(g_r80_QNaNMax)] + fld tword [TMPL_DATA16_WRT(g_r80_SNaNMax)] + fld tword [TMPL_DATA16_WRT(g_r80_ThirtyTwo)] + + ; + ; We'll later be using FMUL to test actually using the FPU in RC & R0, + ; so for everything to line up correctly with FPU CS:IP and FPU DS:DP, + ; we'll call the function here too. This has the benefitial side effect + ; of loading correct FPU DS/DS values so we can check that they don't + ; get lost either. Also, we now don't have to guess whether the CPU + ; emulation sets CS/DS or not. + ; +TONLY16 push xPRE [xBP + xCB + cbCurRetAddr + sCB + 2] + push xPRE [xBP + xCB + cbCurRetAddr + sCB] + BS3_CALL TMPL_NM(bs3FpuState1_FMul), 1 + add xSP, sCB + + ; + ; SSE state + ; + movdqu xmm0, [TMPL_DATA16_WRT(g_r32_0dot1)] + movdqu xmm1, [TMPL_DATA16_WRT(g_r32_Two)] + movdqu xmm2, [TMPL_DATA16_WRT(g_r32_ThirtyTwo)] + movdqu xmm3, [TMPL_DATA16_WRT(g_r32_SNaN)] + movdqu xmm4, [TMPL_DATA16_WRT(g_r80_ThirtyTwo)] + movdqu xmm5, [TMPL_DATA16_WRT(g_r32_NegQNaN)] + movdqu xmm6, [TMPL_DATA16_WRT(g_r64_Zero)] + movdqu xmm7, [TMPL_DATA16_WRT(g_r64_Two)] +%if TMPL_BITS == 64 + movdqu xmm8, [TMPL_DATA16_WRT(g_r64_Ten)] + movdqu xmm9, [TMPL_DATA16_WRT(g_r64_ThirtyTwo)] + movdqu xmm10, [TMPL_DATA16_WRT(g_r64_Max)] + movdqu xmm11, [TMPL_DATA16_WRT(g_r64_SNaN)] + movdqu xmm12, [TMPL_DATA16_WRT(g_r64_NegQNaN)] + movdqu xmm13, [TMPL_DATA16_WRT(g_r64_QNaNMax)] + movdqu xmm14, [TMPL_DATA16_WRT(g_r64_DnMax)] + movdqu xmm15, [TMPL_DATA16_WRT(g_r80_Eleven)] +%endif + + ;; @todo status regs + + ; + ; Save it. Note that DS is no longer valid in 16-bit code. + ; To be on the safe side, we load and save the state once again. + ; +TONLY16 mov ds, [xBP + xCB + cbCurRetAddr + 2] + mov xBX, [xBP + xCB + cbCurRetAddr] + cli +%if TMPL_BITS == 64 + o64 fxsave [xBX] + fninit + o64 fxrstor [xBX] + o64 fxsave [xBX] +%else + fxsave [xBX] + fninit + fxrstor [xBX] + fxsave [xBX] +%endif + +.return: +TONLY64 add xSP, 20h + popf +TONLY16 pop ds + pop xBX + mov xSP, xBP + pop xBP + BS3_CALL_CONV_EPILOG 2 + BS3_HYBRID_RET +BS3_PROC_END_MODE bs3FpuState1_InitState + + +;; +; BS3_DECL_NEAR(void) TMPL_NM(bs3FpuState1_Restore)(X86FXSTATE const BS3_FAR *pFxState); +; +BS3_PROC_BEGIN_MODE bs3FpuState1_Restore, BS3_PBC_NEAR + push xBP + mov xBP, xSP + +%if TMPL_BITS == 64 + o64 fxrstor [rcx] + +%elif TMPL_BITS == 32 + mov eax, [xBP + xCB*2] + fxrstor [eax] + +%elif TMPL_BITS == 16 + mov ax, ds + mov ds, [xBP + xCB + cbCurRetAddr + 2] + mov xBX, [xBP + xCB + cbCurRetAddr] + fxrstor [bx] + mov ds, ax +%else + %error TMPL_BITS +%endif + + mov xSP, xBP + pop xBP + BS3_HYBRID_RET +BS3_PROC_END_MODE bs3FpuState1_Restore + +;; +; BS3_DECL_NEAR(void) TMPL_NM(bs3FpuState1_Save)(X86FXSTATE BS3_FAR *pFxState); +; +BS3_PROC_BEGIN_MODE bs3FpuState1_Save, BS3_PBC_NEAR + push xBP + mov xBP, xSP + +%if TMPL_BITS == 64 + o64 fxsave [rcx] + +%elif TMPL_BITS == 32 + mov eax, [xBP + xCB*2] + fxsave [eax] + +%elif TMPL_BITS == 16 + push bx + push ds + mov ds, [xBP + xCB + cbCurRetAddr + 2] + mov bx, [xBP + xCB + cbCurRetAddr] + fxsave [bx] + pop ds + pop bx +%else + %error TMPL_BITS +%endif + + mov xSP, xBP + pop xBP + BS3_HYBRID_RET +BS3_PROC_END_MODE bs3FpuState1_Save + + +;; +; Performs a MOVDQU write on the specified memory. +; +; BS3_DECL_NEAR(void) TMPL_NM(bs3FpuState1_MovDQU_Write)(void *pvMmioReg); +; +BS3_PROC_BEGIN_MODE bs3FpuState1_MovDQU_Write, BS3_PBC_NEAR + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push xBX +TONLY16 push ds + + ; Load the register pointer. + mov xBX, [xBP + xCB + cbCurRetAddr] +TONLY16 mov ds, [xBP + xCB + cbCurRetAddr + 2] + + ; Do read. + movdqu [xBX], xmm0 + +TONLY16 pop ds + pop xBX + leave + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_MODE bs3FpuState1_MovDQU_Write + + +;; +; Performs a MOVDQU write to the specified memory. +; +; BS3_DECL_NEAR(void) TMPL_NM(bs3FpuState1_MovDQU_Read)(void *pvMmioReg); +; +BS3_PROC_BEGIN_MODE bs3FpuState1_MovDQU_Read, BS3_PBC_NEAR + BS3_CALL_CONV_PROLOG 2 + push xBP + mov xBP, xSP + push xBX +TONLY16 push ds + sub xSP, 20h +%if TMPL_BITS == 16 + movdqu [xBP - xCB - xCB - 2 - 18h], xmm2 +%else + movdqu [xSP], xmm2 +%endif + + ; Load the register pointer. + mov xBX, [xBP + xCB + cbCurRetAddr] +TONLY16 mov ds, [xBP + xCB + cbCurRetAddr + 2] + + + ; Do read. + movdqu xmm2, [xBX] + + ; Save the result. + mov xBX, [xBP + xCB + cbCurRetAddr + sCB] +TONLY16 mov ds, [xBP + xCB + cbCurRetAddr + sCB + 2] + movups [xBX], xmm2 + +%if TMPL_BITS == 16 + movdqu xmm2, [xBP - xCB - xCB - 2 - 18h] +%else + movdqu xmm2, [xSP] +%endif + add xSP, 20h +TONLY16 pop ds + pop xBX + mov xSP, xBP + pop xBP + BS3_CALL_CONV_EPILOG 2 + BS3_HYBRID_RET +BS3_PROC_END_MODE bs3FpuState1_MovDQU_Read + + +;; +; Performs a MOVUPS write on the specified memory. +; +; BS3_DECL_NEAR(void) TMPL_NM(bs3FpuState1_MovUPS_Write)(void *pvMmioReg); +; +BS3_PROC_BEGIN_MODE bs3FpuState1_MovUPS_Write, BS3_PBC_NEAR + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push xBX +TONLY16 push ds + + ; Load the register pointer. + mov xBX, [xBP + xCB + cbCurRetAddr] +TONLY16 mov ds, [xBP + xCB + cbCurRetAddr + 2] + + ; Do read. + movups [xBX], xmm3 + +TONLY16 pop ds + pop xBX + leave + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_MODE bs3FpuState1_MovUPS_Write + + +;; +; Performs a MOVUPS write to the specified memory. +; +; BS3_DECL_NEAR(void) TMPL_NM(bs3FpuState1_MovUPS_Read)(void *pvMmioReg, void *pvResult); +; +BS3_PROC_BEGIN_MODE bs3FpuState1_MovUPS_Read, BS3_PBC_NEAR + BS3_CALL_CONV_PROLOG 2 + push xBP + mov xBP, xSP + push xBX +TONLY16 push ds + sub xSP, 20h +%if TMPL_BITS == 16 + movups [xBP - xCB - xCB - 2 - 18h], xmm1 +%else + movups [xSP], xmm1 +%endif + + ; Load the register pointer. + mov xBX, [xBP + xCB + cbCurRetAddr] +TONLY16 mov ds, [xBP + xCB + cbCurRetAddr + 2] + + + ; Do read. + movups xmm1, [xBX] + + ; Save the result. + mov xBX, [xBP + xCB + cbCurRetAddr + sCB] +TONLY16 mov ds, [xBP + xCB + cbCurRetAddr + sCB + 2] + movups [xBX], xmm1 + +%if TMPL_BITS == 16 + movups xmm1, [xBP - xCB - xCB - 2 - 18h] +%else + movups xmm1, [xSP] +%endif + add xSP, 20h +TONLY16 pop ds + pop xBX + mov xSP, xBP + pop xBP + BS3_CALL_CONV_EPILOG 2 + BS3_HYBRID_RET +BS3_PROC_END_MODE bs3FpuState1_MovUPS_Read + + +;; +; Performs a FNSTENV write on the specified memory. +; +; BS3_DECL_NEAR(void) TMPL_NM(bs3FpuState1_FNStEnv)(void *pvMmioReg); +; +BS3_PROC_BEGIN_MODE bs3FpuState1_FNStEnv, BS3_PBC_NEAR + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push xBX +TONLY16 push ds + + ; Load the register pointer. + mov xBX, [xBP + xCB + cbCurRetAddr] +TONLY16 mov ds, [xBP + xCB + cbCurRetAddr + 2] + + ; Just write. + fnstenv [xBX] + +TONLY16 pop ds + pop xBX + mov xSP, xBP + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_MODE bs3FpuState1_FNStEnv + + +;; +; Performs a FMUL on the specified memory, after writing a 64-bit value to it first. +; +; BS3_DECL_NEAR(void) TMPL_NM(bs3FpuState1_FMul)(void *pvMmioReg, void *pvResultIgnored); +; +BS3_PROC_BEGIN_MODE bs3FpuState1_FMul, BS3_PBC_NEAR + BS3_CALL_CONV_PROLOG 2 + push xBP + mov xBP, xSP + push xBX +TONLY16 push ds + + ; Load the value we'll be multiplying with into register(s) while ds is DATA16. + mov sAX, [TMPL_DATA16_WRT(g_r64_One)] +TNOT64 mov edx, [4 + TMPL_DATA16_WRT(g_r64_One)] + + ; Load the register pointer. + mov xBX, [xBP + xCB + cbCurRetAddr] +TONLY16 mov ds, [xBP + xCB + cbCurRetAddr + 2] + + ; Just write. + mov [xBX], sAX +TNOT64 mov [xBX + 4], edx + call .do_it + +TONLY16 pop ds + pop xBX + mov xSP, xBP + pop xBP + BS3_CALL_CONV_EPILOG 2 + BS3_HYBRID_RET +.do_it: + fmul qword [xBX] + ret +BS3_PROC_END_MODE bs3FpuState1_FMul + + +%include "bs3kit-template-footer.mac" ; reset environment + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-fpustate-1.c b/src/VBox/ValidationKit/bootsectors/bs3-fpustate-1.c new file mode 100644 index 00000000..adf1c5e8 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-fpustate-1.c @@ -0,0 +1,94 @@ +/* $Id: bs3-fpustate-1.c $ */ +/** @file + * BS3Kit - bs3-fpustate-1, 16-bit C code. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> +#include <iprt/asm-amd64-x86.h> + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +BS3TESTMODE_PROTOTYPES_MODE(bs3FpuState1_Corruption); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static const BS3TESTMODEENTRY g_aModeTest[] = +{ + { + /*pszSubTest =*/ "corruption", + /*RM*/ bs3FpuState1_Corruption_rm, + /*PE16*/ NULL, //bs3FpuState1_Corruption_pe16, + /*PE16_32*/ NULL, //bs3FpuState1_Corruption_pe16_32, + /*PE16_V86*/ NULL, //bs3FpuState1_Corruption_pe16_v86, + /*PE32*/ bs3FpuState1_Corruption_pe32, + /*PE32_16*/ NULL, //bs3FpuState1_Corruption_pe32_16, + /*PEV86*/ NULL, //bs3FpuState1_Corruption_pev86, + /*PP16*/ NULL, //bs3FpuState1_Corruption_pp16, + /*PP16_32*/ NULL, //bs3FpuState1_Corruption_pp16_32, + /*PP16_V86*/ NULL, //bs3FpuState1_Corruption_pp16_v86, + /*PP32*/ bs3FpuState1_Corruption_pp32, + /*PP32_16*/ NULL, //bs3FpuState1_Corruption_pp32_16, + /*PPV86*/ NULL, //bs3FpuState1_Corruption_ppv86, + /*PAE16*/ NULL, //bs3FpuState1_Corruption_pae16, + /*PAE16_32*/ NULL, //bs3FpuState1_Corruption_pae16_32, + /*PAE16_V86*/ NULL, //bs3FpuState1_Corruption_pae16_v86, + /*PAE32*/ bs3FpuState1_Corruption_pae32, + /*PAE32_16*/ NULL, //bs3FpuState1_Corruption_pae32_16, + /*PAEV86*/ NULL, //bs3FpuState1_Corruption_paev86, + /*LM16*/ NULL, //bs3FpuState1_Corruption_lm16, + /*LM32*/ NULL, //bs3FpuState1_Corruption_lm32, + /*LM64*/ bs3FpuState1_Corruption_lm64, + } +}; + + +BS3_DECL(void) Main_rm() +{ + Bs3InitAll_rm(); + Bs3TestInit("bs3-fpustate-1"); + Bs3TestPrintf("g_uBs3CpuDetected=%#x\n", g_uBs3CpuDetected); + + Bs3TestDoModes_rm(g_aModeTest, RT_ELEMENTS(g_aModeTest)); + + Bs3TestTerm(); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-locking-1.c b/src/VBox/ValidationKit/bootsectors/bs3-locking-1.c new file mode 100644 index 00000000..698ae003 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-locking-1.c @@ -0,0 +1,246 @@ +/* $Id: bs3-locking-1.c $ */ +/** @file + * BS3Kit - bs3-locking-1, 16-bit C code. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> +#include <iprt/asm-amd64-x86.h> + +#include <VBox/VMMDevTesting.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static struct +{ + const char * BS3_FAR pszName; + uint32_t cInnerLoops; + uint32_t uCtrlLo; + uint32_t uCtrlHi; +} g_aLockingTests[] = +{ +#if 1 +# if 1 /* no contention benchmark */ + { + "None 0us/inf/0k", + _32K, + 0, + 0, + }, + { + "RW None Exl 0us/inf/0k", + _32K, + 0, + 0 | VMMDEV_TESTING_LOCKED_HI_TYPE_RW, + }, +# endif + { + "RW None Shr 0us/inf/0k", + _32K, + 0, + 0 | VMMDEV_TESTING_LOCKED_HI_TYPE_RW | VMMDEV_TESTING_LOCKED_HI_EMT_SHARED, + }, +# if 1 + { + "Contention 500us/250us/64k", + 2000 + 16384, + 500 | (UINT32_C(250) << VMMDEV_TESTING_LOCKED_LO_WAIT_SHIFT), + 64 | VMMDEV_TESTING_LOCKED_HI_ENABLED, + }, + { + "Contention 100us/50us/8k", + 10000 + 4096, + 100 | (UINT32_C(50) << VMMDEV_TESTING_LOCKED_LO_WAIT_SHIFT), + 8 | VMMDEV_TESTING_LOCKED_HI_ENABLED, + }, + { + "Contention 10us/1us/0k", + 16384 + 4096, + 10 | (UINT32_C(1) << VMMDEV_TESTING_LOCKED_LO_WAIT_SHIFT), + 0 | VMMDEV_TESTING_LOCKED_HI_ENABLED, + }, + { + "Contention 500us/250us/64k poke", + 2000 + 16384, + 500 | (UINT32_C(250) << VMMDEV_TESTING_LOCKED_LO_WAIT_SHIFT), + 64 | VMMDEV_TESTING_LOCKED_HI_ENABLED | VMMDEV_TESTING_LOCKED_HI_POKE, + }, + { + "Contention 100us/50us/1k poke", + 10000 + 4096, + 100 | (UINT32_C(50) << VMMDEV_TESTING_LOCKED_LO_WAIT_SHIFT), + 1 | VMMDEV_TESTING_LOCKED_HI_ENABLED | VMMDEV_TESTING_LOCKED_HI_POKE, + }, + { + "Contention 500us/250us/64k poke void", + 2000 + 16384, + 500 | (UINT32_C(250) << VMMDEV_TESTING_LOCKED_LO_WAIT_SHIFT), + 64 | VMMDEV_TESTING_LOCKED_HI_ENABLED | VMMDEV_TESTING_LOCKED_HI_POKE | VMMDEV_TESTING_LOCKED_HI_BUSY_SUCCESS + }, + { + "Contention 50us/25us/8k poke void", + 20000 + 4096, + 50 | (UINT32_C(25) << VMMDEV_TESTING_LOCKED_LO_WAIT_SHIFT), + 1 | VMMDEV_TESTING_LOCKED_HI_ENABLED | VMMDEV_TESTING_LOCKED_HI_POKE | VMMDEV_TESTING_LOCKED_HI_BUSY_SUCCESS + }, +# endif +# if 1 + { + "RW Contention Exl/Exl 50us/25us/16k", + 20000 + 4096, + 50 | (UINT32_C(25) << VMMDEV_TESTING_LOCKED_LO_WAIT_SHIFT), + 16 | VMMDEV_TESTING_LOCKED_HI_ENABLED | VMMDEV_TESTING_LOCKED_HI_TYPE_RW + }, +# endif + { + "RW Contention Shr/Exl 50us/25us/16k", + 20000 + 4096, + 50 | (UINT32_C(25) << VMMDEV_TESTING_LOCKED_LO_WAIT_SHIFT), + 16 | VMMDEV_TESTING_LOCKED_HI_ENABLED | VMMDEV_TESTING_LOCKED_HI_TYPE_RW | VMMDEV_TESTING_LOCKED_HI_THREAD_SHARED + }, +# if 1 + { + "RW Contention Exl/Exl 50us/25us/16k poke", + 20000 + 4096, + 50 | (UINT32_C(25) << VMMDEV_TESTING_LOCKED_LO_WAIT_SHIFT), + 16 | VMMDEV_TESTING_LOCKED_HI_ENABLED | VMMDEV_TESTING_LOCKED_HI_TYPE_RW | VMMDEV_TESTING_LOCKED_HI_POKE + }, +# endif + { + "RW Contention Shr/Exl 50us/25us/16k poke", + 20000 + 4096, + 50 | (UINT32_C(25) << VMMDEV_TESTING_LOCKED_LO_WAIT_SHIFT), + 16 | VMMDEV_TESTING_LOCKED_HI_ENABLED | VMMDEV_TESTING_LOCKED_HI_TYPE_RW | VMMDEV_TESTING_LOCKED_HI_THREAD_SHARED + | VMMDEV_TESTING_LOCKED_HI_POKE | VMMDEV_TESTING_LOCKED_HI_BUSY_SUCCESS + }, +# if 1 + { + "RW Contention Exl/Exl 50us/25us/16k poke void", + 20000 + 4096, + 50 | (UINT32_C(25) << VMMDEV_TESTING_LOCKED_LO_WAIT_SHIFT), + 16 | VMMDEV_TESTING_LOCKED_HI_ENABLED | VMMDEV_TESTING_LOCKED_HI_TYPE_RW | VMMDEV_TESTING_LOCKED_HI_POKE + }, +# endif + { + "RW Contention Shr/Exl 50us/25us/16k poke void", + 20000 + 4096, + 50 | (UINT32_C(25) << VMMDEV_TESTING_LOCKED_LO_WAIT_SHIFT), + 16 | VMMDEV_TESTING_LOCKED_HI_ENABLED | VMMDEV_TESTING_LOCKED_HI_TYPE_RW | VMMDEV_TESTING_LOCKED_HI_THREAD_SHARED + | VMMDEV_TESTING_LOCKED_HI_POKE | VMMDEV_TESTING_LOCKED_HI_BUSY_SUCCESS + }, +#endif + + { + "RW Contention Exl/Shr 50us/25us/16k", + 20000 + 4096, + 50 | (UINT32_C(25) << VMMDEV_TESTING_LOCKED_LO_WAIT_SHIFT), + 16 | VMMDEV_TESTING_LOCKED_HI_ENABLED | VMMDEV_TESTING_LOCKED_HI_TYPE_RW | VMMDEV_TESTING_LOCKED_HI_EMT_SHARED + }, + { + "RW Contention Exl/Shr poke 250us/25us/16k", + 10000 + 4096, + 250 | (UINT32_C(25) << VMMDEV_TESTING_LOCKED_LO_WAIT_SHIFT), + 16 | VMMDEV_TESTING_LOCKED_HI_ENABLED | VMMDEV_TESTING_LOCKED_HI_TYPE_RW | VMMDEV_TESTING_LOCKED_HI_EMT_SHARED + | VMMDEV_TESTING_LOCKED_HI_POKE + }, + { + "RW Contention Exl/Shr poke void 250us/25us/16k", + 10000 + 4096, + 250 | (UINT32_C(25) << VMMDEV_TESTING_LOCKED_LO_WAIT_SHIFT), + 16 | VMMDEV_TESTING_LOCKED_HI_ENABLED | VMMDEV_TESTING_LOCKED_HI_TYPE_RW | VMMDEV_TESTING_LOCKED_HI_EMT_SHARED + | VMMDEV_TESTING_LOCKED_HI_POKE | VMMDEV_TESTING_LOCKED_HI_BUSY_SUCCESS + }, + { + "RW None Shr/Shr 50us/25us/16k", + 20000 + 4096, + 50 | (UINT32_C(25) << VMMDEV_TESTING_LOCKED_LO_WAIT_SHIFT), + 16 | VMMDEV_TESTING_LOCKED_HI_ENABLED | VMMDEV_TESTING_LOCKED_HI_TYPE_RW + | VMMDEV_TESTING_LOCKED_HI_THREAD_SHARED | VMMDEV_TESTING_LOCKED_HI_EMT_SHARED + }, +}; + + +BS3_DECL(void) Main_rm() +{ + uint64_t const cNsPerTest = RT_NS_15SEC; + unsigned i; + + Bs3InitAll_rm(); + Bs3TestInit("bs3-locking-1"); + + /* + * Since this is a host-side test and we don't have raw-mode any more, we + * just stay in raw-mode when doing the test. + */ + for (i = 0; i < RT_ELEMENTS(g_aLockingTests); i++) + { + uint64_t const nsStart = Bs3TestNow(); + uint64_t cNsElapsed = 0; + uint32_t cTotal = 0; + uint32_t j; + + Bs3TestSub(g_aLockingTests[i].pszName); + ASMOutU32(VMMDEV_TESTING_IOPORT_LOCKED_LO, g_aLockingTests[i].uCtrlLo); + ASMOutU32(VMMDEV_TESTING_IOPORT_LOCKED_HI, g_aLockingTests[i].uCtrlHi); + + for (j = 0; j < _2M && cTotal < _1G; j++) + { + + /* The inner loop should avoid calling Bs3TestNow too often, while not overshooting the . */ + unsigned iInner = (unsigned)g_aLockingTests[i].cInnerLoops; + cTotal += iInner; + while (iInner-- > 0) + ASMInU32(VMMDEV_TESTING_IOPORT_LOCKED_LO); + + cNsElapsed = Bs3TestNow() - nsStart; + if (cNsElapsed >= cNsPerTest) + break; + } + + /* Disable locking. */ + ASMOutU32(VMMDEV_TESTING_IOPORT_LOCKED_HI, 0); + + Bs3TestValue("Loops", cTotal, VMMDEV_TESTING_UNIT_OCCURRENCES); + Bs3TestValue("Elapsed", cNsElapsed, VMMDEV_TESTING_UNIT_NS); + Bs3TestValue("PerLoop", cNsElapsed / cTotal, VMMDEV_TESTING_UNIT_NS_PER_OCCURRENCE); + } + + Bs3TestTerm(); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-memalloc-1.c64 b/src/VBox/ValidationKit/bootsectors/bs3-memalloc-1.c64 new file mode 100644 index 00000000..2120403e --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-memalloc-1.c64 @@ -0,0 +1,282 @@ +/* $Id: bs3-memalloc-1.c64 $ */ +/** @file + * BS3Kit - bs3-timers-1, 64-bit C code. + */ + +/* + * Copyright (C) 2021-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> +#include <iprt/asm-amd64-x86.h> +#include <VBox/VMMDevTesting.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Copy of interesting E820 entries. */ +static INT15E820ENTRY g_aEntries[16]; +/** Number of interesting entires. */ +static unsigned g_cEntries = 0; +/** Number of intersting bytes found. */ +static uint64_t g_cbInteresting = 0; +/** Lowest interesting address. */ +static uint64_t g_uInterestingStart = UINT64_MAX; +/** End of interesting addresses. */ +static uint64_t g_uInterestingEnd = 0; + + +/** + * For subsequence touch iterations that doesn't allocate any RAM. + * + * This may cause page pool activitiy if we've got more memory than we have room + * for in the pool. This depends on amount of guest RAM and how much could be + * backed by large pages. + */ +static uint64_t CheckTouchedMemory(void) +{ + unsigned iEntry; + uint64_t iPage = 0; + uint64_t cErrors = 0; + for (iEntry = 0; iEntry < g_cEntries; iEntry++) + { + uint64_t volatile *pu64Cur = (uint64_t *)g_aEntries[iEntry].uBaseAddr; + uint64_t cbLeft = g_aEntries[iEntry].cbRange; + while (cbLeft >= X86_PAGE_SIZE) + { + /* Check first. */ + if (RT_LIKELY( pu64Cur[0] == iPage + && pu64Cur[1] == iPage)) + { /* likely */ } + else + { + Bs3TestFailedF("%p: %#llx + %#llx, expected twice %#llx\n", pu64Cur, pu64Cur[0], pu64Cur[1], iPage); + cErrors++; + } + + /* Then write again. */ + pu64Cur[0] = iPage; + pu64Cur[1] = iPage; + + /* Advance. */ + iPage++; + pu64Cur += X86_PAGE_SIZE / sizeof(*pu64Cur); + cbLeft -= X86_PAGE_SIZE; + } + } + return cErrors; +} + + +/** + * First touching of memory, assuming content is ZERO. + */ +static uint64_t FirstTouchMemory(void) +{ + unsigned iEntry; + uint64_t iPage = 0; + for (iEntry = 0; iEntry < g_cEntries; iEntry++) + { + uint64_t volatile *pu64Cur = (uint64_t volatile *)g_aEntries[iEntry].uBaseAddr; + uint64_t cbLeft = g_aEntries[iEntry].cbRange; + while (cbLeft >= X86_PAGE_SIZE) + { + /* + * Write to the page first so we won't waste time mapping the zero + * page and get straight to the actual page allocation. + */ + pu64Cur[0] = iPage; + + /* Then check that the 2nd qword is zero before writing it. */ + if (RT_LIKELY(pu64Cur[1] == 0)) + { /* likely */ } + else + Bs3TestFailedF("%p: %#llx, expected zero\n", pu64Cur, pu64Cur[1]); + pu64Cur[1] = iPage; + + /* Advance. */ + iPage++; + pu64Cur += X86_PAGE_SIZE / sizeof(*pu64Cur); + cbLeft -= X86_PAGE_SIZE; + } + } + return iPage; +} + + +/** + * Translates a E820 entry type to a string. + */ +static const char *getEntryTypeName(uint32_t uType) +{ + switch (uType) + { + case INT15E820_TYPE_USABLE: return "USABLE"; + case INT15E820_TYPE_RESERVED: return "RESERVED"; + case INT15E820_TYPE_ACPI_RECLAIMABLE: return "ACPI_RECLAIMABLE"; + case INT15E820_TYPE_ACPI_NVS: return "ACPI_NVS"; + case INT15E820_TYPE_BAD: return "BAD"; + default: return "unknown"; + } +} + +BS3_DECL(void) Main_lm64() +{ + uint32_t uCont; + unsigned i; + + Bs3TestInit("bs3-memalloc-1"); + + /* + * Get the E820 memory descriptors and pick out those describing memory not + * already used by the Bs3Kit. + */ + Bs3TestSub("INT15h/E820"); + for (uCont = i = 0; i < 2048; i++) + { + uint32_t const uEbxCur = uCont; + INT15E820ENTRY Entry = { 0, 0, 0, 0 }; + uint32_t cbEntry = sizeof(Entry); + if (!Bs3BiosInt15hE820_lm64(&Entry, &cbEntry, &uCont)) + { + Bs3TestFailedF("int15h/E820 failed i=%u", i); + break; + } + Bs3TestPrintf("#%u/%#x: %#018llx LB %#018llx %s (%d)\n", + i, uEbxCur, Entry.uBaseAddr, Entry.cbRange, getEntryTypeName(Entry.uType), Entry.uType); + if (Entry.uType == INT15E820_TYPE_USABLE) + { + if (Entry.uBaseAddr >= _4G) + { + if (g_cEntries < RT_ELEMENTS(g_aEntries)) + { + g_cbInteresting += Entry.cbRange; + if (g_uInterestingStart > Entry.uBaseAddr) + g_uInterestingStart = Entry.uBaseAddr; + if (g_uInterestingEnd < Entry.uBaseAddr + Entry.cbRange) + g_uInterestingEnd = Entry.uBaseAddr + Entry.cbRange; + Bs3MemCpy(&g_aEntries[g_cEntries++], &Entry, sizeof(Entry)); + } + else + Bs3TestFailedF("Too many interesting E820 entries! Extend g_aEntries!\n"); + } + } + + /* Done? */ + if (uCont == 0) + break; + } + if (g_cEntries == 0) + Bs3TestFailedF("No interesting E820 entries! Make sure you've assigned more than 4GB to the VM!\n"); + else + { + uint64_t uFailurePoint = 0; + int rc; + Bs3TestPrintf("Found %u interesting entries covering %#llx bytes (%u GB).\n" + "From %#llx to %#llx\n", + g_cEntries, g_cbInteresting, (unsigned)(g_cbInteresting / _1G), g_uInterestingStart, g_uInterestingEnd); + + if (g_uBs3EndOfRamAbove4G < g_uInterestingEnd) + Bs3TestFailedF("g_uBs3EndOfRamAbove4G (%#llx) is lower than g_uInterestingEnd (%#llx)!\n", + g_uBs3EndOfRamAbove4G, g_uInterestingEnd); + + + /* + * Map all the memory (Bs3Kit only maps memory below 4G). + */ + Bs3TestSub("Mapping memory above 4GB"); + if (!(g_uBs3CpuDetected & BS3CPU_F_PSE)) + Bs3TestFailedF("PSE was not detected!\n"); + else if (!(ASMGetCR4() & X86_CR4_PSE)) + Bs3TestFailedF("PSE was not enabled!\n"); + else if (RT_SUCCESS(rc = Bs3PagingMapRamAbove4GForLM(&uFailurePoint))) + { +#define PAGES_2_MB(a_cPages) ((a_cPages) / (_1M / X86_PAGE_SIZE)) + uint64_t cTotalPages; + unsigned iLoop; + + /* + * Time touching all the memory. + */ + Bs3TestSub("Allocation speed"); + { + uint64_t const nsStart = Bs3TestNow(); + uint64_t const uTscStart = ASMReadTSC(); + uint64_t const cPages = FirstTouchMemory(); + uint64_t const cTicksElapsed = ASMReadTSC() - uTscStart; + uint64_t const cNsElapsed = Bs3TestNow() - nsStart; + uint64_t uThruput; + Bs3TestValue("Pages", cPages, VMMDEV_TESTING_UNIT_PAGES); + Bs3TestValue("MiBs", PAGES_2_MB(cPages), VMMDEV_TESTING_UNIT_MEGABYTES); + Bs3TestValue("Alloc elapsed", cNsElapsed, VMMDEV_TESTING_UNIT_NS); + Bs3TestValue("Alloc elapsed in ticks", cTicksElapsed, VMMDEV_TESTING_UNIT_TICKS); + Bs3TestValue("Page alloc time", cNsElapsed / cPages, VMMDEV_TESTING_UNIT_NS_PER_PAGE); + Bs3TestValue("Page alloc time in ticks", cTicksElapsed / cPages, VMMDEV_TESTING_UNIT_TICKS_PER_PAGE); + uThruput = cPages * RT_NS_1SEC / cNsElapsed; + Bs3TestValue("Alloc thruput", uThruput, VMMDEV_TESTING_UNIT_PAGES_PER_SEC); + Bs3TestValue("Alloc thruput in MiBs", PAGES_2_MB(uThruput), VMMDEV_TESTING_UNIT_MEGABYTES_PER_SEC); + cTotalPages = cPages; + } + + /* + * Time accessing all the memory again. This might give a clue as to page pool performance. + */ + for (iLoop = 0; iLoop < 2; iLoop++) + { + Bs3TestSub(iLoop == 0 ? "2nd access" : "3rd access"); + { + uint64_t const nsStart = Bs3TestNow(); + uint64_t const uTscStart = ASMReadTSC(); + uint64_t const cErrors = CheckTouchedMemory(); + uint64_t const cTicksElapsed = ASMReadTSC() - uTscStart; + uint64_t const cNsElapsed = Bs3TestNow() - nsStart; + uint64_t uThruput; + Bs3TestValue("Access elapsed", cNsElapsed, VMMDEV_TESTING_UNIT_NS); + Bs3TestValue("Access elapsed in ticks", cTicksElapsed, VMMDEV_TESTING_UNIT_TICKS); + Bs3TestValue("Page access time", cNsElapsed / cTotalPages, VMMDEV_TESTING_UNIT_NS_PER_PAGE); + Bs3TestValue("Page access time in ticks", cTicksElapsed / cTotalPages, VMMDEV_TESTING_UNIT_TICKS_PER_PAGE); + uThruput = cTotalPages * RT_NS_1SEC / cNsElapsed; + Bs3TestValue("Access thruput", uThruput, VMMDEV_TESTING_UNIT_PAGES_PER_SEC); + Bs3TestValue("Access thruput in MiBs", PAGES_2_MB(uThruput), VMMDEV_TESTING_UNIT_MEGABYTES_PER_SEC); + } + } + } + else + Bs3TestFailedF("Bs3PagingMapRamAbove4GForLM failed at %#llx: %d", uFailurePoint, rc); + } + + Bs3TestTerm(); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-timers-1-x0.c b/src/VBox/ValidationKit/bootsectors/bs3-timers-1-x0.c new file mode 100644 index 00000000..11923562 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-timers-1-x0.c @@ -0,0 +1,102 @@ +/* $Id: bs3-timers-1-x0.c $ */ +/** @file + * BS3Kit - bs3-timers-1, C test driver code (16-bit). + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define BS3_USE_X0_TEXT_SEG +#include <bs3kit.h> +#include <iprt/asm.h> +#include <iprt/asm-amd64-x86.h> + + +static uint8_t bs3Timers1_Pit(uint8_t bMode, uint16_t uHz, uint32_t cNsMaxDiviation) +{ + uint32_t const cTargetTicks = uHz * 3; + uint16_t uActualHz; + uint64_t cNsElapsed; + int64_t cNsDelta; + uint64_t cNsDeltaAbs; + + Bs3PitSetupAndEnablePeriodTimer(uHz); + cNsElapsed = Bs3TestNow(); + ASMIntEnable(); + uActualHz = g_cBs3PitIntervalHz; + + while (g_cBs3PitTicks < cTargetTicks) + ASMHalt(); + + Bs3PitDisable(); + ASMIntDisable(); + cNsElapsed = Bs3TestNow() - cNsElapsed; + + /* Calculate the absolute delta and fail the test if the diviation is too high... */ + cNsDelta = cNsElapsed - RT_NS_1SEC * 3; + cNsDeltaAbs = RT_ABS(cNsDelta); + /*Bs3TestPrintf("cNsElapsed=%RU64 g_cBs3PitTicks=%RU32 uHz=%u -> %RU64ns\n", + cNsElapsed, g_cBs3PitTicks, uActualHz, cNsDeltaAbs);*/ + if (cNsDeltaAbs > cNsMaxDiviation) + Bs3TestFailedF("delta %c%RU64 ns (%RI32 ms), max %RU32 ns", cNsDelta < 0 ? '-' : '+', cNsDeltaAbs, + (int32_t)((uint64_t)cNsDelta / RT_NS_1MS), cNsMaxDiviation); + + return 0; +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_FAR_NM(bs3Timers1_Pit_100Hz)(uint8_t bMode) +{ + return bs3Timers1_Pit(bMode, 100, RT_NS_10MS); +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_FAR_NM(bs3Timers1_Pit_1000Hz)(uint8_t bMode) +{ + return bs3Timers1_Pit(bMode, 1000, RT_NS_10MS); +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_FAR_NM(bs3Timers1_Pit_2000Hz)(uint8_t bMode) +{ + return bs3Timers1_Pit(bMode, 2000, RT_NS_10MS*2); +} + + +BS3_DECL_FAR(uint8_t) BS3_CMN_FAR_NM(bs3Timers1_Pit_4000Hz)(uint8_t bMode) +{ + return bs3Timers1_Pit(bMode, 4000, RT_NS_10MS*4); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-timers-1.c b/src/VBox/ValidationKit/bootsectors/bs3-timers-1.c new file mode 100644 index 00000000..6a9ba275 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-timers-1.c @@ -0,0 +1,75 @@ +/* $Id: bs3-timers-1.c $ */ +/** @file + * BS3Kit - bs3-timers-1, 16-bit C code. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> +#include <iprt/asm-amd64-x86.h> + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +FNBS3TESTDOMODE bs3Timers1_Pit_100Hz_f16; +FNBS3TESTDOMODE bs3Timers1_Pit_1000Hz_f16; +FNBS3TESTDOMODE bs3Timers1_Pit_2000Hz_f16; +FNBS3TESTDOMODE bs3Timers1_Pit_4000Hz_f16; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static const BS3TESTMODEBYONEENTRY g_aModeByOneTests[] = +{ + { "pit-100Hz", bs3Timers1_Pit_100Hz_f16, BS3TESTMODEBYONEENTRY_F_MINIMAL }, + { "pit-1000Hz", bs3Timers1_Pit_1000Hz_f16, BS3TESTMODEBYONEENTRY_F_MINIMAL }, + { "pit-2000Hz", bs3Timers1_Pit_2000Hz_f16, BS3TESTMODEBYONEENTRY_F_MINIMAL }, + { "pit-4000Hz", bs3Timers1_Pit_4000Hz_f16, BS3TESTMODEBYONEENTRY_F_MINIMAL }, +}; + + +BS3_DECL(void) Main_rm() +{ + Bs3InitAll_rm(); + Bs3TestInit("bs3-timers-1"); + + Bs3TestDoModesByOne_rm(g_aModeByOneTests, RT_ELEMENTS(g_aModeByOneTests), 0); + + Bs3TestTerm(); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-timing-1-32.c32 b/src/VBox/ValidationKit/bootsectors/bs3-timing-1-32.c32 new file mode 100644 index 00000000..f3079421 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-timing-1-32.c32 @@ -0,0 +1,363 @@ +/* $Id: bs3-timing-1-32.c32 $ */ +/** @file + * BS3Kit - bs3-timinig-1, 32-bit C code. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifndef STANDALONE_EXECUTABLE +# include <bs3kit.h> +#endif +#include <iprt/asm-amd64-x86.h> +#include <iprt/asm-math.h> +#include <iprt/asm.h> +#include <iprt/uint128.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** TSC timing results. */ +typedef struct BS3TIMING1RESULT +{ + /** Number of nanoseconds elapsed while testing. */ + uint64_t cNsElapsed; + /** Number of CPU ticks elapsed while testing. */ + uint64_t cTicksElapsed; + /** The minium number of ticks between TSC reads. */ + uint64_t cTicksMin; + /** The maximum number of ticks between TSC reads. */ + uint64_t cTicksMax; + /** The sum of all TSC read deltas. */ + uint64_t cTicksSum; + /** Number of loops (TSC read deltas). */ + uint64_t cTotalLoops; + /** Number of times TSC moved backwards. */ + uint64_t cBackwards; + /** Approx log2(cTicks) distribution. */ + uint64_t aDistribution[65]; +} BS3TIMING1RESULT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The total result. */ +static BS3TIMING1RESULT g_TotalResult; + +/** Set if history wrapped (i.e. table is full). */ +static bool g_fBigHistoryWrapped = false; +/** The next history entry. */ +static uint32_t g_iBigHistory; +/** History of large gaps. */ +static struct { uint64_t uTsc, cTicksDelta; } g_aBigHistory[384]; + + + +/** + * Pretty prints + */ +static void bs3Timing1_PrintTicks(uint64_t cTicks, uint64_t uCpuFreq) +{ + if (uCpuFreq > _128M) + { + if (cTicks >= uCpuFreq * 1000) + Bs3TestPrintf("%'RU64s", cTicks / uCpuFreq); + else + { + const char *pszUnit; + uint64_t uValue; + if (cTicks >= uCpuFreq) + { + pszUnit = "s"; + uValue = (cTicks * RT_MS_1SEC) / uCpuFreq; + } + else if (cTicks * RT_MS_1SEC >= uCpuFreq) + { + pszUnit = "ms"; + uValue = (cTicks * RT_US_1SEC) / uCpuFreq; + } + else if (cTicks * RT_US_1SEC >= uCpuFreq) + { + pszUnit = "us"; + uValue = (cTicks * RT_NS_1SEC) / uCpuFreq; + } + else if (cTicks * RT_NS_1SEC >= uCpuFreq) + { + pszUnit = "ns"; + uValue = (cTicks * UINT64_C(1000000000000)) / uCpuFreq; + } + else + { + Bs3TestPrintf("%'RU64ps", (cTicks * UINT64_C(1000000000000)) / uCpuFreq); + return; + } + Bs3TestPrintf("%u.%03u%s (%'RU64 ticks)", (uint32_t)uValue / 1000, (uint32_t)uValue % 1000, pszUnit, cTicks); + } + } + else + Bs3TestPrintf("%'RU64 ticks", cTicks); +} + + +/** + * Prints a result. + * + * @param pResult The result to print. + * @param iRun The run (loop in qpc parlance). + * @param uVerbosity The verbosity level. + */ +static void bs3Timing1_PrintResult(BS3TIMING1RESULT const *pResult, unsigned iRun, unsigned uVerbosity) +{ + uint64_t uCpuFreq; + + /* + * Calc CPU frequency. + */ + if (pResult->cNsElapsed > 0 && pResult->cTicksElapsed > 0) + { +#if 1 + RTUINT128U Tmp1, Divisor, Result; + RTUInt128Div(&Result, + RTUInt128MulU64ByU64(&Tmp1, pResult->cTicksElapsed, RT_NS_1SEC), + RTUInt128AssignU64(&Divisor, pResult->cNsElapsed)); + uCpuFreq = Result.s.Lo; +#else + unsigned const cShift = pResult->cTicksElapsed < UINT64_C(0x000225C17D04) ? 0 + : pResult->cTicksElapsed < UINT64_C(0x00225C17D04D) ? 4 + : pResult->cTicksElapsed < UINT64_C(0x0225C17D04DA) ? 8 + : pResult->cTicksElapsed < UINT64_C(0x225C1D940BF6) ? 12 + : 16; + uCpuFreq = pResult->cTicksElapsed * ((uint64_t)RT_NS_1SEC >> cShift) / (pResult->cNsElapsed >> cShift); +#endif + } + else + uCpuFreq = 1; + + /* + * Report results. + * + * Note! in 32-bit and 16-bit mode, values 4G or higher gets formatted as + * hexadecimal to avoid 64-bit division. + */ + Bs3TestPrintf("Loop #%u: %'RU64 tests: ", iRun, pResult->cTotalLoops); + + Bs3TestPrintf("average "); + bs3Timing1_PrintTicks(pResult->cTicksSum / pResult->cTotalLoops, uCpuFreq); + Bs3TestPrintf(", min "); + bs3Timing1_PrintTicks(pResult->cTicksMin, uCpuFreq); + Bs3TestPrintf(", max "); + bs3Timing1_PrintTicks(pResult->cTicksMax, uCpuFreq); + Bs3TestPrintf("\n"); + + /* Distribution (tick delta log2-ish). */ + if (uVerbosity > 0) + { + unsigned iItem = 0; + unsigned i; + for (i = uVerbosity > 1 ? 0 : 5; i < RT_ELEMENTS(pResult->aDistribution); i++) + if (pResult->aDistribution[i] != 0) + { + if (iItem >= 6) + { + iItem = 0; + Bs3TestPrintf("\n"); + } + iItem++; + Bs3TestPrintf(" %'11RU64|2^%-2u", pResult->aDistribution[i], i); + } + if (uVerbosity > 1) + Bs3TestPrintf(iItem < 6 ? " (%'RU64 Hz)\n" : "\n (%'RU64 Hz)\n", uCpuFreq); + else + Bs3TestPrintf("\n"); + } + if (pResult->cBackwards != 0) + Bs3TestFailedF("TSC went backwards %'RU64 time(s)", pResult->cBackwards); +} + + +/** + * Do one TSC timing iteration. + * + * @param iRun The iteration number (loop). + * @param cSecs The number of seconds to sample TSCs. + * @param uVerbosity The noise level. + * @param iMinHistory The threshold level to put stuff in g_auTscHistory. + */ +static void bs3Timing1_Tsc_One(unsigned iRun, uint32_t cSecs, unsigned uVerbosity, unsigned iMinHistory) +{ + uint64_t const nsStart = Bs3TestNow(); + uint64_t const uTscStart = ASMReadTSC(); + uint64_t const nsDeadline = nsStart + cSecs * RT_NS_1SEC_64; + uint64_t cNsElapsed; + BS3TIMING1RESULT Result; + unsigned i; + + Bs3MemZero(&Result, sizeof(Result)); + Result.cTicksMin = UINT64_MAX; + + /* + * Test loop. + */ + do + { + unsigned cLoops = 100000 + 1; + Result.cTotalLoops += cLoops - 1; + while (--cLoops != 0) + { + uint64_t uTscPrev = ASMReadTSC(); + uint64_t uTscNow = ASMReadTSC(); + uint64_t cTicks = uTscNow - uTscPrev; + unsigned iBit; + + /* check that it doesn't go backwards*/ + if ((int64_t)cTicks < 0) + Result.cBackwards++; + + /* min/max/avg */ + Result.cTicksSum += cTicks; + if (cTicks < Result.cTicksMin) + Result.cTicksMin = cTicks; + if (cTicks > Result.cTicksMax) + Result.cTicksMax = cTicks; + + /* result distribution by most significant bit. */ + iBit = ASMBitLastSetU64(cTicks); + Result.aDistribution[iBit] += 1; + if (iBit < iMinHistory) + { /* likely */ } + else + { + g_aBigHistory[g_iBigHistory].uTsc = uTscPrev; + g_aBigHistory[g_iBigHistory].cTicksDelta = cTicks; + if (++g_iBigHistory >= RT_ELEMENTS(g_aBigHistory)) + { + g_iBigHistory = 0; + g_fBigHistoryWrapped = true; + } + } + } + } while ((cNsElapsed = Bs3TestNow()) < nsDeadline); + + Result.cTicksElapsed = ASMReadTSC() - uTscStart; + Result.cNsElapsed = cNsElapsed - nsStart; + + bs3Timing1_PrintResult(&Result, iRun, uVerbosity); + + /* Add to total. */ + g_TotalResult.cNsElapsed += Result.cNsElapsed; + g_TotalResult.cTicksElapsed += Result.cTicksElapsed; + if (Result.cTicksMin < g_TotalResult.cTicksMin || g_TotalResult.cTicksMin == 0) + g_TotalResult.cTicksMin = Result.cTicksMin; + if (Result.cTicksMax > g_TotalResult.cTicksMax) + g_TotalResult.cTicksMax += Result.cTicksMax; + g_TotalResult.cTicksSum += Result.cTicksSum; + g_TotalResult.cTotalLoops += Result.cTotalLoops; + g_TotalResult.cBackwards += Result.cBackwards; + for (i = 0; i < RT_ELEMENTS(Result.aDistribution); i++) + g_TotalResult.aDistribution[i] += Result.aDistribution[i]; +} + + +/** + * The TSC test driver. + * + * @param cLoops Number of test iterations. + * @param cSecs The number of seconds per iteration. + * @param uVerbosity How noisy we should be. + * @param iMinHistory The threshold for big gap history. + */ +static void bs3Timing1_Tsc_Driver(unsigned cLoops, unsigned cSecs, unsigned uVerbosity, unsigned iMinHistory) +{ + unsigned iLoop; + +#if 1 + /* + * Verify that the first/last bit in U64 works (didn't). + */ + iLoop = ASMBitLastSetU64( UINT64_C(0x1000100010001000)); if (iLoop != 61) Bs3TestFailedF("%d: iLoop=%d\n", __LINE__, iLoop); + iLoop = ASMBitFirstSetU64(UINT64_C(0x1000100010001000)); if (iLoop != 13) Bs3TestFailedF("%d: iLoop=%d\n", __LINE__, iLoop); + iLoop = ASMBitLastSetU64( UINT64_C(0x000ffff000000000)); if (iLoop != 52) Bs3TestFailedF("%d: iLoop=%d\n", __LINE__, iLoop); + iLoop = ASMBitFirstSetU64(UINT64_C(0x000ffff000000000)); if (iLoop != 37) Bs3TestFailedF("%d: iLoop=%d\n", __LINE__, iLoop); +#endif + + /* + * Do the work. + */ + Bs3TestPrintf("Running %u loops, %u second%s each...\n", cLoops, cSecs, cSecs != 1 ? "s" : ""); + for (iLoop = 1; iLoop <= cLoops; iLoop++) + bs3Timing1_Tsc_One(iLoop, cSecs, uVerbosity, iMinHistory); + + /* + * Report the total. + */ + Bs3TestPrintf("Total:\n"); + bs3Timing1_PrintResult(&g_TotalResult, iLoop, uVerbosity + 1); + + /* + * Dump the large gap history, if any. + */ + if (g_fBigHistoryWrapped || g_iBigHistory > 0) + { + uint32_t const iFirst = g_fBigHistoryWrapped ? g_iBigHistory : 0; + uint32_t const iEnd = g_iBigHistory; + uint64_t uTscPrev = g_aBigHistory[iFirst].uTsc; + uint32_t i = iFirst; + Bs3TestPrintf("Big gap history (TSC, prev delta, test delta|level):\n"); + do + { + Bs3TestPrintf(" %'RU64: %'14RU64 - %'14RU64|%u\n", g_aBigHistory[i].uTsc, g_aBigHistory[i].uTsc - uTscPrev, + g_aBigHistory[i].cTicksDelta, ASMBitLastSetU64(g_aBigHistory[i].cTicksDelta)); + uTscPrev = g_aBigHistory[i].uTsc; + if (++i >= RT_ELEMENTS(g_aBigHistory)) + i = 0; + } while (i != iEnd); + } + else + Bs3TestPrintf("No big gap history.\n"); +} + + +#ifndef STANDALONE_EXECUTABLE +BS3_DECL(void) bs3Timing1_Tsc_pe32(void) +{ + Bs3TestPrintf("bs3Timing1_Tsc_pe32\n"); + bs3Timing1_Tsc_Driver(60, 10 /*sec*/, 1 /*uVerbosity*/, 17); +} +#endif + +/* P.S. don't forget: VBoxManage setextradata bs3-timing-1 VBoxInternal/Devices/VMMDev/0/Config/TestingEnabled 1 */ + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-timing-1-exe.c b/src/VBox/ValidationKit/bootsectors/bs3-timing-1-exe.c new file mode 100644 index 00000000..1d896c43 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-timing-1-exe.c @@ -0,0 +1,126 @@ +/* $Id: bs3-timing-1-exe.c $ */ +/** @file + * BS3Kit - bs3-timing-1, regular executable version of the TSC test. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/time.h> + +/* Fake bs3kit.h bits: */ +#define Bs3TestNow RTTimeNanoTS +#define Bs3TestPrintf RTPrintf +#define Bs3TestFailedF RTMsgError +#define Bs3MemZero RT_BZERO +#define BS3_DECL(t) t + +#define STANDALONE_EXECUTABLE +#include "bs3-timing-1-32.c32" + + +int main(int argc, char **argv) +{ + /* + * Parse arguments. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--loops", 'l', RTGETOPT_REQ_UINT32 }, + { "--seconds", 's', RTGETOPT_REQ_UINT32 }, + { "--min-history", 'm', RTGETOPT_REQ_UINT32 }, + { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + }; + uint32_t cLoops = 5; + uint32_t cSecs = 10; + unsigned uVerbosity = 1; + unsigned iMinHistory = 17; + + RTGETOPTSTATE GetState; + RTGETOPTUNION ValueUnion; + + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertRC(rc); + while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (rc) + { + case 'l': + cLoops = ValueUnion.u32; + break; + case 'm': + iMinHistory = ValueUnion.u32; + break; + case 's': + cSecs = ValueUnion.u32; + if (cSecs == 0 || cSecs > RT_SEC_1DAY / 2) + return RTMsgErrorExitFailure("Seconds value is out of range: %u (min 1, max %u)", cSecs, RT_SEC_1DAY / 2); + break; + case 'q': + uVerbosity = 0; + break; + case 'v': + uVerbosity++; + break; + case 'V': + RTPrintf("v0.1.0\n"); + return RTEXITCODE_SUCCESS; + case 'h': + RTPrintf("usage: %Rbn [-q|-v] [-l <iterations>] [-s <seconds-per-iteration>] [-m <log2-big-gap>]\n", argv[0]); + return RTEXITCODE_SUCCESS; + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + /* + * Run the test. + */ + bs3Timing1_Tsc_Driver(cLoops, cSecs, uVerbosity, iMinHistory); + return RTEXITCODE_SUCCESS; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3-timing-1.c b/src/VBox/ValidationKit/bootsectors/bs3-timing-1.c new file mode 100644 index 00000000..85878d00 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3-timing-1.c @@ -0,0 +1,63 @@ +/* $Id: bs3-timing-1.c $ */ +/** @file + * BS3Kit - bs3-timing-1, 16-bit C code. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> +#include <iprt/asm-amd64-x86.h> + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +BS3_DECL_CALLBACK(void) bs3Timing1_Tsc_pe32(); + + + +BS3_DECL(void) Main_rm() +{ + Bs3InitAll_rm(); + Bs3TestInit("bs3-timing-1"); + + /* Switch to PE32 and do the work from there, all the 64-bit integer + handling should be a little more efficient in 32-bit mode. */ + Bs3SwitchTo32BitAndCallC_rm(bs3Timing1_Tsc_pe32, 0); + + Bs3TestTerm(); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/Makefile.kmk b/src/VBox/ValidationKit/bootsectors/bs3kit/Makefile.kmk new file mode 100644 index 00000000..90e4958f --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/Makefile.kmk @@ -0,0 +1,760 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Bootsector Kit v3 +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + + +# Boot Sector post-link tool (used via the parent Config.kmk). +BLDPROGS += VBoxBs3Linker +VBoxBs3Linker_TEMPLATE = VBoxBldProg +VBoxBs3Linker_SOURCES = $(VBOX_PATH_BS3KIT_SRC)/VBoxBs3Linker.cpp + + +# 64-bit relocation conversion tool (used via the parent Config.kmk). +BLDPROGS += VBoxBs3ObjConverter +VBoxBs3ObjConverter_TEMPLATE = VBoxBldProg +VBoxBs3ObjConverter_DEFS = BS3KIT_BS3CLASS16CODE=$(BS3KIT_BS3CLASS16CODE) IN_RT_R3 +VBoxBs3ObjConverter_SOURCES = \ + $(VBOX_PATH_BS3KIT_SRC)/VBoxBs3ObjConverter.cpp \ + $(PATH_ROOT)/src/VBox/Runtime/common/sort/shellsort.cpp + + +# The boot sector. +MISCBINS += bs3-bootsector +bs3-bootsector_TEMPLATE = VBoxBS3KitBS +bs3-bootsector_SOURCES = bs3-bootsector.asm + + +# +# Common sources to be compiled into _p16, _p32 and _p64 versions. +# +VBOX_BS3KIT_COMMON_SOURCES = \ + bs3-cmn-A20Disable.asm \ + bs3-cmn-A20Enable.asm \ + bs3-cmn-GetCpuVendor.c \ + bs3-cmn-GetModeName.c \ + bs3-cmn-GetModeNameShortLower.c \ + bs3-cmn-KbdRead.asm \ + bs3-cmn-KbdWait.asm \ + bs3-cmn-KbdWrite.asm \ + bs3-cmn-Shutdown.asm \ + bs3-cmn-Panic.asm \ + bs3-cmn-PrintChr.asm \ + bs3-cmn-Printf.c \ + bs3-cmn-PrintU32.asm \ + bs3-cmn-PrintX32.asm \ + bs3-cmn-PrintStr.c \ + bs3-cmn-PrintStrN.asm \ + bs3-cmn-StrFormatV.c \ + bs3-cmn-StrPrintf.c \ + bs3-cmn-StrLen.c \ + bs3-cmn-StrNLen.c \ + bs3-cmn-StrCpy.c \ + bs3-cmn-MemChr.asm \ + bs3-cmn-MemCmp.asm \ + bs3-cmn-MemCpy.c \ + bs3-cmn-MemPCpy.c \ + bs3-cmn-MemMove.c \ + bs3-cmn-MemSet.asm \ + bs3-cmn-MemZero.asm \ + bs3-cmn-MemAlloc.c \ + bs3-cmn-MemAllocZ.c \ + bs3-cmn-MemFree.c \ + bs3-cmn-MemGuardedTestPage.c \ + bs3-cmn-MemPrintInfo.c \ + bs3-cmn-PagingData.c \ + bs3-cmn-PagingInitRootForPP.c \ + bs3-cmn-PagingInitRootForPAE.c \ + bs3-cmn-PagingInitRootForLM.c \ + bs3-cmn-PagingAlias.c \ + bs3-cmn-PagingProtect.c \ + bs3-cmn-PagingQueryAddressInfo.c \ + bs3-cmn-PagingSetupCanonicalTraps.c \ + bs3-cmn-pic-data.c \ + bs3-cmn-PicMaskAll.c \ + bs3-cmn-PicUpdateMask.c \ + bs3-cmn-PicSetup.c \ + bs3-cmn-pit.c \ + bs3-cmn-PitIrqHandler.c \ + bs3-cmn-RegCtxRestore.asm \ + bs3-cmn-RegCtxConvertToRingX.c \ + bs3-cmn-RegCtxConvertV86ToRm.c \ + bs3-cmn-RegCtxPrint.c \ + bs3-cmn-RegCtxGetRspSsAsCurPtr.c \ + bs3-cmn-RegCtxSave.asm \ + bs3-cmn-RegCtxSaveEx.asm \ + bs3-cmn-RegCtxSaveForMode.c \ + bs3-cmn-RegCtxSetGrpSegFromCurPtr.c \ + bs3-cmn-RegCtxSetGrpSegFromFlat.c \ + bs3-cmn-RegCtxSetRipCsFromCurPtr.c \ + bs3-cmn-RegCtxSetRipCsFromFlat.c \ + bs3-cmn-RegCtxSetRipCsFromLnkPtr.c \ + bs3-cmn-RegCtxSetGpr.c \ + bs3-cmn-RegGetCr0.asm \ + bs3-cmn-RegGetCr2.asm \ + bs3-cmn-RegGetCr3.asm \ + bs3-cmn-RegGetCr4.asm \ + bs3-cmn-RegSetCr0.asm \ + bs3-cmn-RegSetCr2.asm \ + bs3-cmn-RegSetCr3.asm \ + bs3-cmn-RegSetCr4.asm \ + bs3-cmn-RegGetDr0.asm \ + bs3-cmn-RegGetDr1.asm \ + bs3-cmn-RegGetDr2.asm \ + bs3-cmn-RegGetDr3.asm \ + bs3-cmn-RegGetDr6.asm \ + bs3-cmn-RegGetDr7.asm \ + bs3-cmn-RegGetDrX.asm \ + bs3-cmn-RegSetDr0.asm \ + bs3-cmn-RegSetDr1.asm \ + bs3-cmn-RegSetDr2.asm \ + bs3-cmn-RegSetDr3.asm \ + bs3-cmn-RegSetDr6.asm \ + bs3-cmn-RegSetDr7.asm \ + bs3-cmn-RegSetDrX.asm \ + bs3-cmn-RegGetTr.asm \ + bs3-cmn-RegSetTr.asm \ + bs3-cmn-RegGetLdtr.asm \ + bs3-cmn-RegSetLdtr.asm \ + bs3-cmn-RegGetXcr0.asm \ + bs3-cmn-RegSetXcr0.asm \ + bs3-cmn-ExtCtxInit.c \ + bs3-cmn-ExtCtxSave.asm \ + bs3-cmn-ExtCtxSaveEx.asm \ + bs3-cmn-ExtCtxRestore.asm \ + bs3-cmn-ExtCtxRestoreEx.asm \ + bs3-cmn-ExtCtxGetSize.c \ + bs3-cmn-ExtCtxAlloc.c \ + bs3-cmn-ExtCtxFree.c \ + bs3-cmn-ExtCtxCopy.c \ + bs3-cmn-ExtCtxGetFcw.c \ + bs3-cmn-ExtCtxSetFcw.c \ + bs3-cmn-ExtCtxGetFsw.c \ + bs3-cmn-ExtCtxSetFsw.c \ + bs3-cmn-ExtCtxGetAbridgedFtw.c \ + bs3-cmn-ExtCtxSetAbridgedFtw.c \ + bs3-cmn-ExtCtxGetMxCsr.c \ + bs3-cmn-ExtCtxSetMxCsr.c \ + bs3-cmn-ExtCtxGetMxCsrMask.c \ + bs3-cmn-ExtCtxSetMxCsrMask.c \ + bs3-cmn-ExtCtxGetMm.c \ + bs3-cmn-ExtCtxSetMm.c \ + bs3-cmn-ExtCtxGetXmm.c \ + bs3-cmn-ExtCtxSetXmm.c \ + bs3-cmn-ExtCtxGetYmm.c \ + bs3-cmn-ExtCtxSetYmm.c \ + bs3-cmn-SelFar32ToFlat32.c \ + bs3-cmn-SelFar32ToFlat32NoClobber.asm \ + bs3-cmn-SelProtFar32ToFlat32.c \ + bs3-cmn-SelProtModeCodeToRealMode.asm \ + bs3-cmn-SelRealModeCodeToProtMode.asm \ + bs3-cmn-SelFlatCodeToRealMode.asm \ + bs3-cmn-SelFlatCodeToProtFar16.asm \ + bs3-cmn-SelRealModeDataToProtFar16.asm \ + bs3-cmn-SelProtFar16DataToRealMode.asm \ + bs3-cmn-SelRealModeDataToFlat.asm \ + bs3-cmn-SelProtFar16DataToFlat.asm \ + bs3-cmn-SelFlatDataToProtFar16.asm \ + bs3-cmn-SelFlatDataToRealMode.asm \ + bs3-cmn-SelLnkPtrToCurPtr.c \ + bs3-cmn-SelLnkPtrToFlat.c \ + bs3-cmn-SelSetup16BitData.c \ + bs3-cmn-SelSetup16BitCode.c \ + bs3-cmn-SelSetup32BitCode.c \ + bs3-cmn-SelSetupGate.c \ + bs3-cmn-SelSetupGate64.c \ + bs3-cmn-SlabInit.c \ + bs3-cmn-SlabAlloc.c \ + bs3-cmn-SlabAllocEx.c \ + bs3-cmn-SlabFree.c \ + bs3-cmn-SlabListInit.c \ + bs3-cmn-SlabListAdd.c \ + bs3-cmn-SlabListAlloc.c \ + bs3-cmn-SlabListAllocEx.c \ + bs3-cmn-SlabListFree.c \ + bs3-cmn-SwitchHlpConvRealModeRetfPopBpDecBpAndReturn.asm \ + bs3-cmn-SwitchHlpConvProtModeRetfPopBpDecBpAndReturn.asm \ + bs3-cmn-SwitchToRing0.asm \ + bs3-cmn-SwitchToRing1.asm \ + bs3-cmn-SwitchToRing2.asm \ + bs3-cmn-SwitchToRing3.asm \ + bs3-cmn-SwitchToRingX.asm \ + bs3-cmn-SwitchTo16Bit.asm \ + bs3-cmn-SwitchTo16BitV86.asm \ + bs3-cmn-SwitchTo32Bit.asm \ + bs3-cmn-SwitchTo64Bit.asm \ + bs3-cmn-Syscall.asm \ + bs3-cmn-TestData.c \ + bs3-cmn-TestInit.c \ + bs3-cmn-TestFailed.c \ + bs3-cmn-TestNow.asm \ + bs3-cmn-TestSkipped.c \ + bs3-cmn-TestSub.c \ + bs3-cmn-TestSubDone.c \ + bs3-cmn-TestSubErrorCount.c \ + bs3-cmn-TestTerm.c \ + bs3-cmn-TestSendCmdWithStr.asm \ + bs3-cmn-TestSendCmdWithU32.asm \ + bs3-cmn-TestIsVmmDevTestingPresent.asm \ + bs3-cmn-TestCheckRegCtxEx.c \ + bs3-cmn-TestCheckExtCtx.c \ + bs3-cmn-TestQueryCfgU8.asm \ + bs3-cmn-TestQueryCfgU32.asm \ + bs3-cmn-TestHostPrintf.c \ + bs3-cmn-TestPrintf.c \ + bs3-cmn-TestValue.c \ + bs3-cmn-TrapReInit.c \ + bs3-cmn-TrapRmV86Init.c \ + bs3-cmn-TrapRmV86SetGate.c \ + bs3-cmn-Trap16Init.c \ + bs3-cmn-Trap16SetGate.c \ + bs3-cmn-Trap32Init.c \ + bs3-cmn-Trap32SetGate.c \ + bs3-cmn-Trap64Init.c \ + bs3-cmn-Trap64SetGate.c \ + bs3-cmn-TrapSetDpl.c \ + bs3-cmn-TrapDefaultHandler.c \ + bs3-cmn-TrapHandlersData.asm \ + bs3-cmn-TrapPrintFrame.c \ + bs3-cmn-TrapSetHandler.c \ + bs3-cmn-TrapSetHandlerEx.c \ + bs3-cmn-TrapSetJmp.asm \ + bs3-cmn-TrapSetJmpAndRestore.c \ + bs3-cmn-TrapSetJmpAndRestoreInRm.c \ + bs3-cmn-TrapSetJmpAndRestoreWithRm.c \ + bs3-cmn-TrapSetJmpAndRestoreWithExtCtx.c \ + bs3-cmn-TrapSetJmpAndRestoreWithExtCtxAndRm.c \ + bs3-cmn-TrapUnsetJmp.c \ + bs3-cmn-UtilSetFullGdtr.asm \ + bs3-cmn-UtilSetFullIdtr.asm \ + bs3-cmn-TestDoModesByOneHlp.asm \ + ../../../Runtime/common/asm/ASMBitFirstClear.asm \ + ../../../Runtime/common/asm/ASMBitFirstSet.asm \ + ../../../Runtime/common/asm/ASMBitNextClear.asm \ + ../../../Runtime/common/asm/ASMBitNextSet.asm \ + ../../../Runtime/common/asm/ASMBitFirstSetU16.asm \ + ../../../Runtime/common/asm/ASMBitFirstSetU32.asm \ + ../../../Runtime/common/asm/ASMBitFirstSetU64.asm \ + ../../../Runtime/common/asm/ASMBitLastSetU16.asm \ + ../../../Runtime/common/asm/ASMBitLastSetU32.asm \ + ../../../Runtime/common/asm/ASMBitLastSetU64.asm \ + ../../../Runtime/common/asm/ASMMemFirstMismatchingU8.asm \ + ../../../Runtime/common/asm/ASMSerializeInstruction-cpuid.asm \ + ../../../Runtime/common/asm/ASMSerializeInstruction-iret.asm \ + ../../../Runtime/common/asm/ASMSerializeInstruction-rdtscp.asm \ + ../../../Runtime/common/asm/ASMCpuIdExSlow.asm \ + ../../../Runtime/common/asm/ASMCpuId.asm \ + ../../../Runtime/common/asm/ASMCpuId_Idx_ECX.asm \ + ../../../Runtime/common/asm/ASMWrMsr.asm \ + ../../../Runtime/common/asm/ASMGetXcr0.asm \ + ../../../Runtime/common/asm/ASMSetXcr0.asm \ + ../../../Runtime/common/asm/ASMSetFlags.asm \ + ../../../Runtime/common/asm/ASMGetFlags.asm \ + ../../../Runtime/common/asm/ASMMultU64ByU32DivByU32.asm \ + +# The 16-bit BS3Kit library. +LIBRARIES += bs3kit-common-16 +bs3kit-common-16_TEMPLATE = VBoxBS3KitImg +bs3kit-common-16_INSTTYPE = none +bs3kit-common-16_DEFS = TMPL_PE16 BS3_CMN_ONLY +bs3kit-common-16_ASDEFS = RT_ASMDEFS_INC_FIRST_FILE +bs3kit-common-16_SOURCES = $(VBOX_BS3KIT_COMMON_SOURCES) \ + bs3-system-data.asm \ + bs3-rm-InitAll.c \ + bs3-rm-InitMemory.c \ + bs3-rm-InitGdt.c \ + bs3-cmn-hexdigits.c \ + bs3-cmn-CpuDetectData.c \ + bs3-cmn-PerCpuData.c \ + bs3-cmn-ConvertRMStackToP16UsingCxReturnToAx.asm \ + bs3-cmn-UInt64Div.c \ + bs3-cmn-UInt32Div.c \ + bs3-wc16-U8DR.asm \ + bs3-wc16-U8DQ.asm \ + bs3-wc16-I8DR.asm \ + bs3-wc16-I8DQ.asm \ + bs3-wc16-I8RS.asm \ + bs3-wc16-U8RS.asm \ + bs3-wc16-U8LS.asm \ + bs3-wc16-U4D.asm \ + bs3-wc16-I4D.asm \ + bs3-c16-SwitchFromV86To16BitAndCallC.asm \ + bs3-c16-Trap16Generic.asm \ + bs3-c16-TrapRmV86Generic.asm \ + bs3-c16-TrapRmV86Data.c \ + bs3-c16-CreateHybridFarRet.asm +bs3kit-common-16_bs3-cmn-UInt64Div.c_CFLAGS = -oh -d0 # -d1+ vs -d0 saves 0x6a3-0x577 = 0x12C (300)! + +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,ASMMemFirstMismatchingU8,8) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,ASMMemFirstNonZero,6) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,ASMCpuIdExSlow,32) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,ASMCpuId,20) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,ASMWrMsr,12) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,ASMSetXcr0,8) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,ASMGetXcr0,0) +-include $(PATH_SUB_CURRENT)/bs3kit-autostubs.kmk # manually generated from headers, see bottom of this file. + +# The 32-bit BS3Kit library. +LIBRARIES += bs3kit-common-32 +bs3kit-common-32_TEMPLATE = VBoxBS3KitImg32 +bs3kit-common-32_INSTTYPE = none +bs3kit-common-32_DEFS = TMPL_PE32 BS3_CMN_ONLY +bs3kit-common-32_ASDEFS = RT_ASMDEFS_INC_FIRST_FILE +bs3kit-common-32_SOURCES = $(VBOX_BS3KIT_COMMON_SOURCES) \ + bs3-cmn-PagingMapRamAbove4GForLM.c \ + bs3-cmn-SwitchHlpConvFlatRetToRetfProtMode.asm \ + bs3-cmn-UInt64Div.c \ + bs3-wc32-U8D.asm \ + bs3-wc32-I8D.asm \ + bs3-wc32-I8RS.asm \ + bs3-wc32-U8RS.asm \ + bs3-wc32-U8LS.asm \ + bs3-wc32-U8M.asm \ + bs3-c32-Trap32Generic.asm + +# The 64-bit BS3Kit library. +LIBRARIES += bs3kit-common-64 +bs3kit-common-64_TEMPLATE = VBoxBS3KitImg64 +bs3kit-common-64_INSTTYPE = none +bs3kit-common-64_DEFS = TMPL_LM64 BS3_CMN_ONLY +bs3kit-common-64_ASDEFS = RT_ASMDEFS_INC_FIRST_FILE +bs3kit-common-64_SOURCES = $(VBOX_BS3KIT_COMMON_SOURCES) \ + bs3-cmn-PagingMapRamAbove4GForLM.c \ + bs3-cmn-SwitchHlpConvFlatRetToRetfProtMode.asm \ + bs3-c64-Trap64Generic.asm \ + ../../../Runtime/common/asm/ASMGetIDTR.asm \ + ../../../Runtime/common/asm/ASMSetIDTR.asm \ + ../../../Runtime/common/asm/ASMGetGDTR.asm \ + ../../../Runtime/common/asm/ASMSetGDTR.asm \ + + +# +# Common sources to be compiled for each CPU mode. +# +VBOX_BS3KIT_MODE_SOURCES = \ + bs3-mode-Name.asm \ + bs3-mode-NameShortLower.asm \ + bs3-mode-SwitchToRM.asm \ + bs3-mode-SwitchToPE16.asm \ + bs3-mode-SwitchToPE16_32.asm \ + bs3-mode-SwitchToPE16_V86.asm \ + bs3-mode-SwitchToPE32.asm \ + bs3-mode-SwitchToPE32_16.asm \ + bs3-mode-SwitchToPEV86.asm \ + bs3-mode-SwitchToPP16.asm \ + bs3-mode-SwitchToPP16_32.asm \ + bs3-mode-SwitchToPP16_V86.asm \ + bs3-mode-SwitchToPP32.asm \ + bs3-mode-SwitchToPP32_16.asm \ + bs3-mode-SwitchToPPV86.asm \ + bs3-mode-SwitchToPAE16.asm \ + bs3-mode-SwitchToPAE16_32.asm \ + bs3-mode-SwitchToPAE16_V86.asm \ + bs3-mode-SwitchToPAE32.asm \ + bs3-mode-SwitchToPAE32_16.asm \ + bs3-mode-SwitchToPAEV86.asm \ + bs3-mode-SwitchToLM64.asm \ + bs3-mode-SwitchToLM32.asm \ + bs3-mode-SwitchToLM16.asm \ + bs3-mode-SwitchTo32BitAndCallC.asm \ + bs3-mode-EnteredMode.asm \ + bs3-mode-PagingGetRootForPP16.asm \ + bs3-mode-PagingGetRootForPP32.asm \ + bs3-mode-PagingGetRootForPAE16.asm \ + bs3-mode-PagingGetRootForPAE32.asm \ + bs3-mode-PagingGetRootForLM64.asm \ + bs3-mode-TrapInit.c \ + bs3-mode-TrapSystemCallHandler.asm \ + bs3-mode-TrapSetJmpAndRestoreInRmAsm.asm \ + bs3-mode-TestDoModes.c \ + bs3-mode-TestDoModesByOne.c \ + bs3-mode-TestDoModesByMax.c \ + bs3-mode-TestDoModesHlp.asm \ + bs3-mode-BiosInt15hE820.asm \ + +# The 16-bit real mode BS3Kit library. +LIBRARIES += bs3kit-rm +bs3kit-rm_TEMPLATE = VBoxBS3KitImg +bs3kit-rm_INSTTYPE = none +bs3kit-rm_DEFS = TMPL_MODE=BS3_MODE_RM +bs3kit-rm_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) \ + bs3-first-rm.asm \ + bs3-mode-CpuDetect.asm \ + bs3-mode-TestDoModesStub.asm \ + bs3-mode-TestDoModesByOneStub.asm \ + bs3-mode-TestDoModesByMaxStub.asm \ + + +# The 16-bit BS3Kit library for 16-bit protected kernel+tss. +LIBRARIES += bs3kit-pe16 +bs3kit-pe16_TEMPLATE = VBoxBS3KitImg +bs3kit-pe16_INSTTYPE = none +bs3kit-pe16_DEFS = TMPL_MODE=BS3_MODE_PE16 +bs3kit-pe16_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) \ + bs3-first-pe16.asm \ + bs3-mode-TestDoModesStub.asm \ + bs3-mode-TestDoModesByOneStub.asm \ + bs3-mode-TestDoModesByMaxStub.asm \ +# bs3-mode-CpuDetect.asm + +# The 32-bit BS3Kit library for 16-bit protected kernel+tss. +LIBRARIES += bs3kit-pe16_32 +bs3kit-pe16_32_TEMPLATE = VBoxBS3KitImg32 +bs3kit-pe16_32_INSTTYPE = none +bs3kit-pe16_32_DEFS = TMPL_MODE=BS3_MODE_PE16_32 +bs3kit-pe16_32_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) \ + bs3-mode-TestDoModesStub.asm \ + bs3-mode-TestDoModesByOneStub.asm \ + bs3-mode-TestDoModesByMaxStub.asm \ + +# The v86 BS3Kit library for 16-bit protected kernel+tss. +LIBRARIES += bs3kit-pe16_v86 +bs3kit-pe16_v86_TEMPLATE = VBoxBS3KitImg +bs3kit-pe16_v86_INSTTYPE = none +bs3kit-pe16_v86_DEFS = TMPL_MODE=BS3_MODE_PE16_V86 +bs3kit-pe16_v86_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) \ + bs3-mode-TestDoModesStub.asm \ + bs3-mode-TestDoModesByOneStub.asm \ + bs3-mode-TestDoModesByMaxStub.asm \ + +# The 32-bit BS3Kit library for 32-bit protected kernel+tss. +LIBRARIES += bs3kit-pe32 +bs3kit-pe32_TEMPLATE = VBoxBS3KitImg32 +bs3kit-pe32_INSTTYPE = none +bs3kit-pe32_DEFS = TMPL_MODE=BS3_MODE_PE32 +bs3kit-pe32_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) \ + bs3-first-init-all-pe32.asm + +# The 16-bit BS3Kit library for 32-bit protected kernel+tss. +LIBRARIES += bs3kit-pe32_16 +bs3kit-pe32_16_TEMPLATE = VBoxBS3KitImg +bs3kit-pe32_16_INSTTYPE = none +bs3kit-pe32_16_DEFS = TMPL_MODE=BS3_MODE_PE32_16 +bs3kit-pe32_16_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) \ + bs3-mode-TestDoModesStub.asm \ + bs3-mode-TestDoModesByOneStub.asm \ + bs3-mode-TestDoModesByMaxStub.asm \ + +# The v8086 BS3Kit library for 32-bit protected kernel+tss. +LIBRARIES += bs3kit-pev86 +bs3kit-pev86_TEMPLATE = VBoxBS3KitImg +bs3kit-pev86_INSTTYPE = none +bs3kit-pev86_DEFS = TMPL_MODE=BS3_MODE_PEV86 +bs3kit-pev86_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) + +# The 16-bit BS3Kit library for 16-bit paged protected kernel+tss. +LIBRARIES += bs3kit-pp16 +bs3kit-pp16_TEMPLATE = VBoxBS3KitImg +bs3kit-pp16_INSTTYPE = none +bs3kit-pp16_DEFS = TMPL_MODE=BS3_MODE_PP16 +bs3kit-pp16_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) \ + bs3-mode-CpuDetect.asm \ + bs3-mode-TestDoModesStub.asm \ + bs3-mode-TestDoModesByOneStub.asm \ + bs3-mode-TestDoModesByMaxStub.asm \ + +# The 32-bit BS3Kit library for 16-bit paged protected kernel+tss. +LIBRARIES += bs3kit-pp16_32 +bs3kit-pp16_32_TEMPLATE = VBoxBS3KitImg32 +bs3kit-pp16_32_INSTTYPE = none +bs3kit-pp16_32_DEFS = TMPL_MODE=BS3_MODE_PP16_32 +bs3kit-pp16_32_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) + +# The v8086 BS3Kit library for 16-bit paged protected kernel+tss. +LIBRARIES += bs3kit-pp16_v86 +bs3kit-pp16_v86_TEMPLATE = VBoxBS3KitImg +bs3kit-pp16_v86_INSTTYPE = none +bs3kit-pp16_v86_DEFS = TMPL_MODE=BS3_MODE_PP16_V86 +bs3kit-pp16_v86_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) + +# The 32-bit BS3Kit library for 32-bit paged protected kernel+tss. +LIBRARIES += bs3kit-pp32 +bs3kit-pp32_TEMPLATE = VBoxBS3KitImg32 +bs3kit-pp32_INSTTYPE = none +bs3kit-pp32_DEFS = TMPL_MODE=BS3_MODE_PP32 +bs3kit-pp32_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) \ + bs3-first-init-all-pp32.asm + +# The 16-bit BS3Kit library for 32-bit paged protected kernel+tss. +LIBRARIES += bs3kit-pp32_16 +bs3kit-pp32_16_TEMPLATE = VBoxBS3KitImg +bs3kit-pp32_16_INSTTYPE = none +bs3kit-pp32_16_DEFS = TMPL_MODE=BS3_MODE_PP32_16 +bs3kit-pp32_16_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) \ + bs3-mode-TestDoModesStub.asm \ + bs3-mode-TestDoModesByOneStub.asm \ + bs3-mode-TestDoModesByMaxStub.asm \ + +# The v8086 BS3Kit library for 32-bit paged protected kernel+tss. +LIBRARIES += bs3kit-ppv86 +bs3kit-ppv86_TEMPLATE = VBoxBS3KitImg +bs3kit-ppv86_INSTTYPE = none +bs3kit-ppv86_DEFS = TMPL_MODE=BS3_MODE_PPV86 +bs3kit-ppv86_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) + + +# The 16-bit BS3Kit library for 16-bit PAE paged protected kernel+tss. +LIBRARIES += bs3kit-pae16 +bs3kit-pae16_TEMPLATE = VBoxBS3KitImg +bs3kit-pae16_INSTTYPE = none +bs3kit-pae16_DEFS = TMPL_MODE=BS3_MODE_PAE16 +bs3kit-pae16_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) \ + bs3-mode-CpuDetect.asm \ + bs3-mode-TestDoModesStub.asm \ + bs3-mode-TestDoModesByOneStub.asm \ + bs3-mode-TestDoModesByMaxStub.asm \ + +# The 16-bit BS3Kit library for 16-bit PAE paged protected kernel+tss. +LIBRARIES += bs3kit-pae16_32 +bs3kit-pae16_32_TEMPLATE = VBoxBS3KitImg32 +bs3kit-pae16_32_INSTTYPE = none +bs3kit-pae16_32_DEFS = TMPL_MODE=BS3_MODE_PAE16_32 +bs3kit-pae16_32_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) + +# The v8086 BS3Kit library for 16-bit PAE paged protected kernel+tss. +LIBRARIES += bs3kit-pae16_v86 +bs3kit-pae16_v86_TEMPLATE = VBoxBS3KitImg +bs3kit-pae16_v86_INSTTYPE = none +bs3kit-pae16_v86_DEFS = TMPL_MODE=BS3_MODE_PAE16_V86 +bs3kit-pae16_v86_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) + +# The 32-bit BS3Kit library for 32-bit PAE paged protected kernel+tss. +LIBRARIES += bs3kit-pae32 +bs3kit-pae32_TEMPLATE = VBoxBS3KitImg32 +bs3kit-pae32_INSTTYPE = none +bs3kit-pae32_DEFS = TMPL_MODE=BS3_MODE_PAE32 +bs3kit-pae32_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) + +# The 16-bit BS3Kit library for 32-bit PAE paged protected kernel+tss. +LIBRARIES += bs3kit-pae32_16 +bs3kit-pae32_16_TEMPLATE = VBoxBS3KitImg +bs3kit-pae32_16_INSTTYPE = none +bs3kit-pae32_16_DEFS = TMPL_MODE=BS3_MODE_PAE32_16 +bs3kit-pae32_16_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) \ + bs3-mode-TestDoModesStub.asm \ + bs3-mode-TestDoModesByOneStub.asm \ + bs3-mode-TestDoModesByMaxStub.asm \ + +# The v8086 BS3Kit library for 32-bit PAE paged protected kernel+tss. +LIBRARIES += bs3kit-paev86 +bs3kit-paev86_TEMPLATE = VBoxBS3KitImg +bs3kit-paev86_INSTTYPE = none +bs3kit-paev86_DEFS = TMPL_MODE=BS3_MODE_PAEV86 +bs3kit-paev86_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) + + +# The 16-bit long mode BS3Kit library. +LIBRARIES += bs3kit-lm16 +bs3kit-lm16_TEMPLATE = VBoxBS3KitImg +bs3kit-lm16_INSTTYPE = none +bs3kit-lm16_DEFS = TMPL_MODE=BS3_MODE_LM16 +bs3kit-lm16_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) \ + bs3-mode-TestDoModesStub.asm \ + bs3-mode-TestDoModesByOneStub.asm \ + bs3-mode-TestDoModesByMaxStub.asm \ + +# The 32-bit long mode BS3Kit library. +LIBRARIES += bs3kit-lm32 +bs3kit-lm32_TEMPLATE = VBoxBS3KitImg32 +bs3kit-lm32_INSTTYPE = none +bs3kit-lm32_DEFS = TMPL_MODE=BS3_MODE_LM32 +bs3kit-lm32_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) + +# The 64-bit long mode BS3Kit library. +LIBRARIES += bs3kit-lm64 +bs3kit-lm64_TEMPLATE = VBoxBS3KitImg64 +bs3kit-lm64_INSTTYPE = none +bs3kit-lm64_DEFS = TMPL_MODE=BS3_MODE_LM64 +bs3kit-lm64_SOURCES = $(VBOX_BS3KIT_MODE_SOURCES) \ + bs3-first-init-all-lm64.asm + + +# +# shutdown example. +# +MISCBINS += bs3-shutdown +bs3-shutdown_TEMPLATE = VBoxBS3KitImg +bs3-shutdown_SOURCES = \ + bs3-first-pe16.asm \ + bs3-shutdown.c + + +# +# DOS Utilities / Testcases. +# +MISCBINS += bs3cpudt +bs3cpudt_TEMPLATE = VBoxBS3KitUtil +bs3cpudt_SOURCES = \ + bs3-first-dosexe.asm \ + bs3cpudt.c + + +# +# Rule for regenerating bs3kit-mangling-functions-undef.h. +# +bs3kit-mangling-code-undef.h: $(PATH_SUB_CURRENT)/bs3kit-mangling-code-define.h $(MAKEFILE) + $(SED) \ + -e 's/#\( *\)define \([a-zA-Z_][a-zA-Z0-9_]*\) .*$(DOLLAR)/#\1undef \2/' \ + -e 's/Function needing mangling.*$(DOLLAR)/Undefining function mangling - automatically generated by the $@ makefile rule./' \ + --output $(dir $<)bs3kit-mangling-code-undef.h \ + $< + +# +# Rule for regenerating bs3kit-mangling-functions-define.h. +# +bs3kit-mangling-code-define.h: \ + $(PATH_SUB_CURRENT)/bs3kit.h \ + $(PATH_SUB_CURRENT)/bs3-cmn-paging.h \ + $(PATH_SUB_CURRENT)/bs3-cmn-test.h \ + $(MAKEFILE) + $(APPEND) -tn "$(dir $<)$@" \ + '/* $(DOLLAR)Id: $(DOLLAR) */' \ + '/** @file' \ + ' * BS3Kit - Function needing mangling - generated by the $@ makefile rule.' \ + ' */' \ + '' \ + '/*' \ + ' * Copyright (C) 2007-2022 Oracle and/or its affiliates.' \ + ' *' \ + ' * This file is part of VirtualBox base platform packages, as' \ + ' * available from https://www.virtualbox.org.' \ + ' *' \ + ' * This program is free software; you can redistribute it and/or' \ + ' * modify it under the terms of the GNU General Public License' \ + ' * as published by the Free Software Foundation, in version 3 of the' \ + ' * License.' \ + ' *' \ + ' * This program is distributed in the hope that it will be useful, but' \ + ' * WITHOUT ANY WARRANTY; without even the implied warranty of' \ + ' * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU' \ + ' * General Public License for more details.' \ + ' *' \ + ' * You should have received a copy of the GNU General Public License' \ + ' * along with this program; if not, see <https://www.gnu.org/licenses>.' \ + ' *' \ + ' * The contents of this file may alternatively be used under the terms' \ + ' * of the Common Development and Distribution License Version 1.0' \ + ' * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included' \ + ' * in the VirtualBox distribution, in which case the provisions of the' \ + ' * CDDL are applicable instead of those of the GPL.' \ + ' *' \ + ' * You may elect to license modified versions of this file under the' \ + ' * terms and conditions of either the GPL or the CDDL or both.' \ + ' *' \ + ' * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0' \ + ' */' \ + '' + $(SED) -n \ + -e 's/^ *BS3_CMN_PROTO_STUB([^,]*, *\([a-zA-Z_][a-zA-Z0-9_]*\) *,.*$(DOLLAR)/#define \1 BS3_CMN_MANGLER(\1)/p' \ + -e 's/^ *BS3_CMN_PROTO_NOSB([^,]*, *\([a-zA-Z_][a-zA-Z0-9_]*\) *,.*$(DOLLAR)/#define \1 BS3_CMN_MANGLER(\1)/p' \ + -e 's/^ *BS3_CMN_PROTO_FARSTUB([^,]*,[^,]*, *\([a-zA-Z_][a-zA-Z0-9_]*\) *,.*$(DOLLAR)/#define \1 BS3_CMN_MANGLER(\1)/p' \ + $(filter %.h,$^) | sort >> "$(dir $<)bs3kit-mangling-code-define.h" + $(APPEND) -n "$(dir $<)$@" '#ifndef BS3_CMN_ONLY' + $(SED) -n \ + -e 's/^ *BS3_MODE_PROTO_STUB([^,]*, *\([a-zA-Z_][a-zA-Z0-9_]*\) *,.*$(DOLLAR)/# define \1 BS3_MODE_MANGLER(\1)/p' \ + -e 's/^ *BS3_MODE_PROTO_NOSB([^,]*, *\([a-zA-Z_][a-zA-Z0-9_]*\) *,.*$(DOLLAR)/# define \1 BS3_MODE_MANGLER(\1)/p' \ + -e 's/^ *BS3_MODE_PROTO_FARSTUB([^,]*,[^,]*, *\([a-zA-Z_][a-zA-Z0-9_]*\) *,.*$(DOLLAR)/# define \1 BS3_MODE_MANGLER(\1)/p' \ + $(filter %.h,$^) | sort >> "$(dir $<)bs3kit-mangling-code-define.h" + $(APPEND) -n "$(dir $<)$@" '#endif /* !BS3_CMN_ONLY */' + +# +# Rule for regenerating bs3kit-autostubs.kmk. +# +bs3kit-autostubs.kmk: \ + $(PATH_SUB_CURRENT)/bs3kit.h \ + $(PATH_SUB_CURRENT)/bs3-cmn-memory.h \ + $(PATH_SUB_CURRENT)/bs3-cmn-paging.h \ + $(PATH_SUB_CURRENT)/bs3-cmn-test.h \ + $(MAKEFILE) + $(APPEND) -tn "$(dir $<)$@" \ + '# $(DOLLAR)Id: $(DOLLAR)' \ + '## @file' \ + '# BS3Kit - Automatic near/far stubs - generated by the $@ makefile rule.' \ + '#' \ + '' \ + '#' \ + '# Copyright (C) 2007-2022 Oracle and/or its affiliates.' \ + '#' \ + '# This file is part of VirtualBox base platform packages, as' \ + '# available from https://www.virtualbox.org.' \ + '#' \ + '# This program is free software; you can redistribute it and/or' \ + '# modify it under the terms of the GNU General Public License' \ + '# as published by the Free Software Foundation, in version 3 of the' \ + '# License.' \ + '#' \ + '# This program is distributed in the hope that it will be useful, but' \ + '# WITHOUT ANY WARRANTY; without even the implied warranty of' \ + '# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU' \ + '# General Public License for more details.' \ + '#' \ + '# You should have received a copy of the GNU General Public License' \ + '# along with this program; if not, see <https://www.gnu.org/licenses>.' \ + '#' \ + '# The contents of this file may alternatively be used under the terms' \ + '# of the Common Development and Distribution License Version 1.0' \ + '# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included' \ + '# in the VirtualBox distribution, in which case the provisions of the' \ + '# CDDL are applicable instead of those of the GPL.' \ + '#' \ + '# You may elect to license modified versions of this file under the' \ + '# terms and conditions of either the GPL or the CDDL or both.' \ + '#' \ + '# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0' \ + '#' \ + '' + $(SED) -n \ + -e '/^ *BS3_CMN_PROTO_STUB/p' \ + -e '/^ *BS3_CMN_PROTO_FARSTUB/p' \ + -e '/^ *BS3_MODE_PROTO_STUB/p' \ + -e '/^ *BS3_MODE_PROTO_FARSTUB/p' \ + $(filter %.h,$^) \ + | sort \ + | $(SED) -n \ + -e 's/^ *BS3_CMN_PROTO_STUB([^,]*, *\([a-zA-Z_][a-zA-Z0-9_]*\) *,.*$(DOLLAR)/\$(DOLLAR)(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,\1)/p' \ + -e 's/^ *BS3_MODE_PROTO_STUB([^,]*, *\([a-zA-Z_][a-zA-Z0-9_]*\) *,.*$(DOLLAR)/\$(DOLLAR)(call BS3KIT_FN_GEN_MODE_NEARSTUB,bs3kit-common-16,\1)/p' \ + -e 's/^ *BS3_CMN_PROTO_FARSTUB( *\([^,]*\),[^,]*, *\([a-zA-Z_][a-zA-Z0-9_]*\) *,.*$(DOLLAR)/\$(DOLLAR)(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,\2,\1)/p' \ + -e 's/^ *BS3_MODE_PROTO_FARSTUB( *\([^,]*\),[^,]*, *\([a-zA-Z_][a-zA-Z0-9_]*\) *,.*$(DOLLAR)/\$(DOLLAR)(call BS3KIT_FN_GEN_MODE_FARSTUB,bs3kit-common-16,\2,\1)/p' \ + --append "$(dir $<)$@" + +bs3kit-update:: bs3kit-autostubs.kmk bs3kit-mangling-code-define.h bs3kit-mangling-code-undef.h +.NOTPARALLEL: bs3kit-autostubs.kmk bs3kit-mangling-code-define.h bs3kit-mangling-code-undef.h + + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/VBoxBs3Linker.cpp b/src/VBox/ValidationKit/bootsectors/bs3kit/VBoxBs3Linker.cpp new file mode 100644 index 00000000..e75fbfaa --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/VBoxBs3Linker.cpp @@ -0,0 +1,362 @@ +/* $Id: VBoxBs3Linker.cpp $ */ +/** @file + * VirtualBox Validation Kit - Boot Sector 3 "linker". + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <iprt/types.h> +#include <iprt/assert.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +#pragma pack(1) +typedef struct BS3BOOTSECTOR +{ + uint8_t abJmp[3]; + char abOemId[8]; + /** @name EBPB, DOS 4.0 style. + * @{ */ + uint16_t cBytesPerSector; /**< 00bh */ + uint8_t cSectorsPerCluster; /**< 00dh */ + uint16_t cReservedSectors; /**< 00eh */ + uint8_t cFATs; /**< 010h */ + uint16_t cRootDirEntries; /**< 011h */ + uint16_t cTotalSectors; /**< 013h */ + uint8_t bMediaDescriptor; /**< 015h */ + uint16_t cSectorsPerFAT; /**< 016h */ + uint16_t cPhysSectorsPerTrack; /**< 018h */ + uint16_t cHeads; /**< 01ah */ + uint32_t cHiddentSectors; /**< 01ch */ + uint32_t cLargeTotalSectors; /**< 020h - We (ab)use this to indicate the number of sectors to load. */ + uint8_t bBootDrv; /**< 024h */ + uint8_t bFlagsEtc; /**< 025h */ + uint8_t bExtendedSignature; /**< 026h */ + uint32_t dwSerialNumber; /**< 027h */ + char abLabel[11]; /**< 02bh */ + char abFSType[8]; /**< 036h */ + /** @} */ +} BS3BOOTSECTOR; +#pragma pack() +typedef BS3BOOTSECTOR *PBS3BOOTSECTOR; + +AssertCompileMemberOffset(BS3BOOTSECTOR, cLargeTotalSectors, 0x20); +AssertCompileMemberOffset(BS3BOOTSECTOR, abLabel, 0x2b); +AssertCompileMemberOffset(BS3BOOTSECTOR, abFSType, 0x36); + +#define BS3_OEMID "BS3Kit\n\n" +#define BS3_FSTYPE "RawCode\n" +#define BS3_LABEL "VirtualBox\n" +#define BS3_MAX_SIZE UINT32_C(491520) /* 480KB */ + + +int main(int argc, char **argv) +{ + const char *pszOutput = NULL; + struct BS3LNKINPUT + { + const char *pszFile; + FILE *pFile; + uint32_t cbFile; + } *paInputs = (struct BS3LNKINPUT *)calloc(sizeof(paInputs[0]), argc); + unsigned cInputs = 0; + uint32_t cSectors = 0; + + /* + * Scan the arguments. + */ + for (int i = 1; i < argc; i++) + { + if (argv[i][0] == '-') + { + const char *pszOpt = &argv[i][1]; + if (*pszOpt == '-') + { + /* Convert long options to short ones. */ + pszOpt--; + if (!strcmp(pszOpt, "--output")) + pszOpt = "o"; + else if (!strcmp(pszOpt, "--version")) + pszOpt = "V"; + else if (!strcmp(pszOpt, "--help")) + pszOpt = "h"; + else + { + fprintf(stderr, "syntax errro: Unknown options '%s'\n", pszOpt); + free(paInputs); + return 2; + } + } + + /* Process the list of short options. */ + while (*pszOpt) + { + switch (*pszOpt++) + { + case 'o': + { + const char *pszValue = pszOpt; + pszOpt = strchr(pszOpt, '\0'); + if (*pszValue == '=') + pszValue++; + else if (!*pszValue) + { + if (i + 1 >= argc) + { + fprintf(stderr, "syntax error: The --output option expects a filename.\n"); + free(paInputs); + return 12; + } + pszValue = argv[++i]; + } + if (pszOutput) + { + fprintf(stderr, "Only one output file is allowed. You've specified '%s' and '%s'\n", + pszOutput, pszValue); + free(paInputs); + return 2; + } + pszOutput = pszValue; + pszOpt = ""; + break; + } + + case 'V': + printf("%s\n", "$Revision: 154607 $"); + free(paInputs); + return 0; + + case '?': + case 'h': + printf("usage: %s [options] -o <output> <input1> [input2 ... [inputN]]\n", + argv[0]); + free(paInputs); + return 0; + } + } + } + else + { + /* + * Add to input file collection. + */ + paInputs[cInputs].pszFile = argv[i]; +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) + FILE *pFile = fopen(paInputs[cInputs].pszFile, "rb"); +#else + FILE *pFile = fopen(paInputs[cInputs].pszFile, "r"); +#endif + if (pFile) + { + if (fseek(pFile, 0, SEEK_END) == 0) + { + paInputs[cInputs].cbFile = (uint32_t)ftell(pFile); + if (fseek(pFile, 0, SEEK_SET) == 0) + { + if (cInputs != 0 || paInputs[cInputs].cbFile == 512) + { + cSectors += RT_ALIGN_32(paInputs[cInputs].cbFile, 512) / 512; + if (cSectors <= BS3_MAX_SIZE / 512) + { + if (cSectors > 0) + { + paInputs[cInputs].pFile = pFile; + pFile = NULL; + } + else + fprintf(stderr, "error: empty input file: '%s'\n", paInputs[cInputs].pszFile); + } + else + fprintf(stderr, "error: input is too big: %u bytes, %u sectors (max %u bytes, %u sectors)\n" + "info: detected loading '%s'\n", + cSectors * 512, cSectors, BS3_MAX_SIZE, BS3_MAX_SIZE / 512, + paInputs[cInputs].pszFile); + } + else + fprintf(stderr, "error: first input file (%s) must be exactly 512 bytes\n", paInputs[cInputs].pszFile); + } + else + fprintf(stderr, "error: seeking to start of '%s' failed\n", paInputs[cInputs].pszFile); + } + else + fprintf(stderr, "error: seeking to end of '%s' failed\n", paInputs[cInputs].pszFile); + } + else + fprintf(stderr, "error: Failed to open input file '%s' for reading\n", paInputs[cInputs].pszFile); + if (pFile) + { + free(paInputs); + return 1; + } + cInputs++; + } + } + + if (!pszOutput) + { + fprintf(stderr, "syntax error: No output file was specified (-o or --output).\n"); + free(paInputs); + return 2; + } + if (cInputs == 0) + { + fprintf(stderr, "syntax error: No input files was specified.\n"); + free(paInputs); + return 2; + } + + /* + * Do the job. + */ + /* Open the output file. */ +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) + FILE *pOutput = fopen(pszOutput, "wb"); +#else + FILE *pOutput = fopen(pszOutput, "w"); +#endif + if (!pOutput) + { + fprintf(stderr, "error: Failed to open output file '%s' for writing\n", pszOutput); + free(paInputs); + return 1; + } + + /* Copy the input files to the output file, with sector padding applied. */ + int rcExit = 0; + size_t off = 0; + for (unsigned i = 0; i < cInputs && rcExit == 0; i++) + { + uint8_t abBuf[4096]; /* Must be multiple of 512! */ + uint32_t cbToRead = paInputs[i].cbFile; + while (cbToRead > 0) + { + /* Read a block from the input file. */ + uint32_t const cbThisRead = RT_MIN(cbToRead, sizeof(abBuf)); + size_t cbRead = fread(abBuf, sizeof(uint8_t), cbThisRead, paInputs[i].pFile); + if (cbRead != cbThisRead) + { + fprintf(stderr, "error: Error reading '%s' (got %d bytes, wanted %u).\n", + paInputs[i].pszFile, (int)cbRead, (unsigned)cbThisRead); + rcExit = 1; + break; + } + cbToRead -= cbThisRead; + + /* Padd the end of the file if necessary. */ + if ((cbRead & 0x1ff) != 0) + { + memset(&abBuf[cbRead], 0, 4096 - cbRead); + cbRead = (cbRead + 0x1ff) & ~0x1ffU; + } + + /* Patch the BPB of the first file. */ + if (off == 0) + { + PBS3BOOTSECTOR pBs = (PBS3BOOTSECTOR)&abBuf[0]; + if ( memcmp(pBs->abLabel, RT_STR_TUPLE(BS3_LABEL)) == 0 + && memcmp(pBs->abFSType, RT_STR_TUPLE(BS3_FSTYPE)) == 0 + && memcmp(pBs->abOemId, RT_STR_TUPLE(BS3_OEMID)) == 0) + pBs->cLargeTotalSectors = cSectors; + else + { + fprintf(stderr, "error: Didn't find magic strings in the first file (%s).\n", paInputs[i].pszFile); + rcExit = 1; + } + } + + /* Write the block to the output file. */ + if (fwrite(abBuf, sizeof(uint8_t), cbRead, pOutput) == cbRead) + off += cbRead; + else + { + fprintf(stderr, "error: fwrite failed\n"); + rcExit = 1; + break; + } + } + + if (ferror(paInputs[i].pFile)) + { + fprintf(stderr, "error: Error reading '%s'.\n", paInputs[i].pszFile); + rcExit = 1; + } + } + + /* Close the input files. */ + for (unsigned i = 0; i < cInputs && rcExit == 0; i++) + fclose(paInputs[i].pFile); + free(paInputs); + + /* Avoid output sizes that makes the FDC code think it's a single sided + floppy. The BIOS always report double sided floppies, and even if we + the bootsector adjust it's bMaxHeads value when getting a 20h error + we end up with a garbaged image (seems somewhere in the BIOS/FDC it is + still treated as a double sided floppy and we get half the data we want + and with gaps). + + Similarly, if the size is 320KB or 360KB the FDC detects it as a double + sided 5.25" floppy with 40 tracks, while the BIOS keeps reporting a + 1.44MB 3.5" floppy. So, just avoid those sizes too. */ + uint32_t cbOutput = ftell(pOutput); + if ( cbOutput == 512 * 8 * 40 * 1 /* 160kB 5"1/4 SS */ + || cbOutput == 512 * 9 * 40 * 1 /* 180kB 5"1/4 SS */ + || cbOutput == 512 * 8 * 40 * 2 /* 320kB 5"1/4 DS */ + || cbOutput == 512 * 9 * 40 * 2 /* 360kB 5"1/4 DS */ ) + { + static uint8_t const s_abZeroSector[512] = { 0 }; + if (fwrite(s_abZeroSector, sizeof(uint8_t), sizeof(s_abZeroSector), pOutput) != sizeof(s_abZeroSector)) + { + fprintf(stderr, "error: fwrite failed (padding)\n"); + rcExit = 1; + } + } + + /* Finally, close the output file (can fail because of buffered data). */ + if (fclose(pOutput) != 0) + { + fprintf(stderr, "error: Error closing '%s'.\n", pszOutput); + rcExit = 1; + } + + fclose(stderr); + return rcExit; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/VBoxBs3ObjConverter.cpp b/src/VBox/ValidationKit/bootsectors/bs3kit/VBoxBs3ObjConverter.cpp new file mode 100644 index 00000000..8307b0d9 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/VBoxBs3ObjConverter.cpp @@ -0,0 +1,5530 @@ +/* $Id: VBoxBs3ObjConverter.cpp $ */ +/** @file + * VirtualBox Validation Kit - Boot Sector 3 object file convert. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <iprt/types.h> +#include <iprt/ctype.h> +#include <iprt/assert.h> +#include <iprt/sort.h> +#include <iprt/x86.h> + +#include <iprt/formats/elf64.h> +#include <iprt/formats/elf-amd64.h> +#include <iprt/formats/pecoff.h> +#include <iprt/formats/omf.h> +#include <iprt/formats/codeview.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#if ARCH_BITS == 64 && !defined(RT_OS_WINDOWS) && !defined(RT_OS_DARWIN) +# define ELF_FMT_X64 "lx" +# define ELF_FMT_D64 "ld" +#else +# define ELF_FMT_X64 "llx" +# define ELF_FMT_D64 "lld" +#endif + +/** Compares an OMF string with a constant string. */ +#define IS_OMF_STR_EQUAL_EX(a_cch1, a_pch1, a_szConst2) \ + ( (a_cch1) == sizeof(a_szConst2) - 1 && memcmp(a_pch1, a_szConst2, sizeof(a_szConst2) - 1) == 0 ) + +/** Compares an OMF string with a constant string. */ +#define IS_OMF_STR_EQUAL(a_pchZeroPrefixed, a_szConst2) \ + IS_OMF_STR_EQUAL_EX((uint8_t)((a_pchZeroPrefixed)[0]), &((a_pchZeroPrefixed)[1]), a_szConst2) + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Verbosity level. */ +static unsigned g_cVerbose = 0; +/** Indicates that it's output from the 16-bit watcom C or C++ compiler. + * We will do some massaging for fixup records when this is used. */ +static bool g_f16BitWatcomC = false; + + +/* + * Minimal assertion support. + */ + +RTDECL(bool) RTAssertShouldPanic(void) +{ + return true; +} + + +RTDECL(void) RTAssertMsg1Weak(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction) +{ + fprintf(stderr, + "VBoxBs3ObjConverter: assertion failed in %s (%s:%u)!\n" + "VBoxBs3ObjConverter: %s\n", + pszFunction, pszFile, uLine, pszExpr); +} + + +/** + * Opens a file for binary reading or writing. + * + * @returns File stream handle. + * @param pszFile The name of the file. + * @param fWrite Whether to open for writing or reading. + */ +static FILE *openfile(const char *pszFile, bool fWrite) +{ +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + FILE *pFile = fopen(pszFile, fWrite ? "wb" : "rb"); +#else + FILE *pFile = fopen(pszFile, fWrite ? "w" : "r"); +#endif + if (!pFile) + fprintf(stderr, "error: Failed to open '%s' for %s: %s (%d)\n", + pszFile, fWrite ? "writing" : "reading", strerror(errno), errno); + return pFile; +} + + +/** + * Read the given file into memory. + * + * @returns true on success, false on failure. + * @param pszFile The file to read. + * @param ppvFile Where to return the memory. + * @param pcbFile Where to return the size. + */ +static bool readfile(const char *pszFile, void **ppvFile, size_t *pcbFile) +{ + FILE *pFile = openfile(pszFile, false); + if (pFile) + { + /* + * Figure the size. + */ + if (fseek(pFile, 0, SEEK_END) == 0) + { + long cbFile = ftell(pFile); + if (cbFile > 0) + { + if (fseek(pFile, SEEK_SET, 0) == 0) + { + /* + * Allocate and read content. + */ + void *pvFile = malloc((size_t)cbFile); + if (pvFile) + { + if (fread(pvFile, cbFile, 1, pFile) == 1) + { + *ppvFile = pvFile; + *pcbFile = (size_t)cbFile; + fclose(pFile); + return true; + } + free(pvFile); + fprintf(stderr, "error: fread failed in '%s': %s (%d)\n", pszFile, strerror(errno), errno); + } + else + fprintf(stderr, "error: failed to allocate %ld bytes of memory for '%s'\n", cbFile, pszFile); + } + else + fprintf(stderr, "error: fseek #2 failed in '%s': %s (%d)\n", pszFile, strerror(errno), errno); + } + else + fprintf(stderr, "error: ftell failed in '%s': %s (%d)\n", pszFile, strerror(errno), errno); + } + else + fprintf(stderr, "error: fseek #1 failed in '%s': %s (%d)\n", pszFile, strerror(errno), errno); + fclose(pFile); + } + return false; +} + + +/** + * Write the given file into memory. + * + * @returns true on success, false on failure. + * @param pszFile The file to write. + * @param pvFile Where to return the memory. + * @param cbFile Where to return the size. + */ +static bool writefile(const char *pszFile, void const *pvFile, size_t cbFile) +{ + remove(pszFile); + + FILE *pFile = openfile(pszFile, true); + if (pFile) + { + if (fwrite(pvFile, cbFile, 1, pFile) == 1) + { + fclose(pFile); + return true; + } + fprintf(stderr, "error: fwrite failed in '%s': %s (%d)\n", pszFile, strerror(errno), errno); + fclose(pFile); + } + return false; +} + + +/** + * Reports an error and returns false. + * + * @returns false + * @param pszFile The filename. + * @param pszFormat The message format string. + * @param ... Format arguments. + */ +static bool error(const char *pszFile, const char *pszFormat, ...) +{ + fflush(stdout); + fprintf(stderr, "error: %s: ", pszFile); + va_list va; + va_start(va, pszFormat); + vfprintf(stderr, pszFormat, va); + va_end(va); + return false; +} + + + +/********************************************************************************************************************************* +* Common OMF Writer * +*********************************************************************************************************************************/ + +/** Entry for each segment/section in the source format for mapping it to a + * segment defintion. */ +typedef struct OMFTOSEGDEF +{ + /** The segment defintion index of the section, UINT16_MAX if not translated. */ + uint16_t iSegDef; + /** The group index for this segment, UINT16_MAX if not applicable. */ + uint16_t iGrpDef; + /** The class name table entry, UINT16_MAX if not applicable. */ + uint16_t iClassNm; + /** The group name for this segment, UINT16_MAX if not applicable. */ + uint16_t iGrpNm; + /** The group name for this segment, UINT16_MAX if not applicable. */ + uint16_t iSegNm; + /** The number of public definitions for this segment. */ + uint32_t cPubDefs; + /** The segment name (OMF). */ + char *pszName; +} OMFTOSEGDEF; +/** Pointer to a segment/section to segdef mapping. */ +typedef OMFTOSEGDEF *POMFTOSEGDEF; + +/** Symbol table translation type. */ +typedef enum OMFSYMTYPE +{ + /** Invalid symbol table entry (aux sym). */ + OMFSYMTYPE_INVALID = 0, + /** Ignored. */ + OMFSYMTYPE_IGNORED, + /** A public defintion. */ + OMFSYMTYPE_PUBDEF, + /** An external definition. */ + OMFSYMTYPE_EXTDEF, + /** A segment reference for fixups. */ + OMFSYMTYPE_SEGDEF, + /** Internal symbol that may be used for fixups. */ + OMFSYMTYPE_INTERNAL +} OMFSYMTYPE; + +/** Symbol table translation. */ +typedef struct OMFSYMBOL +{ + /** What this source symbol table entry should be translated into. */ + OMFSYMTYPE enmType; + /** The OMF table index. UINT16_MAX if not applicable. */ + uint16_t idx; + /** The OMF segment definition index. */ + uint16_t idxSegDef; + /** The OMF group definition index. */ + uint16_t idxGrpDef; +} OMFSYMBOL; +/** Pointer to an source symbol table translation entry. */ +typedef OMFSYMBOL *POMFSYMBOL; + +/** OMF Writer LNAME lookup record. */ +typedef struct OMFWRLNAME +{ + /** Pointer to the next entry with the name hash. */ + struct OMFWRLNAME *pNext; + /** The LNAMES index number. */ + uint16_t idxName; + /** The name length. */ + uint8_t cchName; + /** The name (variable size). */ + char szName[1]; +} OMFWRLNAME; +/** Pointer to the a OMF writer LNAME lookup record. */ +typedef OMFWRLNAME *POMFWRLNAME; + +/** + * OMF converter & writer instance. + */ +typedef struct OMFWRITER +{ + /** The source file name (for bitching). */ + const char *pszSrc; + /** The destination output file. */ + FILE *pDst; + + /** Pointer to the table mapping from source segments/section to segdefs. */ + POMFTOSEGDEF paSegments; + /** Number of source segments/sections. */ + uint32_t cSegments; + + /** Number of entries in the source symbol table. */ + uint32_t cSymbols; + /** Pointer to the table mapping from source symbols to OMF stuff. */ + POMFSYMBOL paSymbols; + + /** LEDATA segment offset. */ + uint32_t offSeg; + /** Start of the current LEDATA record. */ + uint32_t offSegRec; + /** The LEDATA end segment offset. */ + uint32_t offSegEnd; + /** The current LEDATA segment. */ + uint16_t idx; + + /** The index of the next list of names entry. */ + uint16_t idxNextName; + + /** The current record size. */ + uint16_t cbRec; + /** The current record type */ + uint8_t bType; + /** The record data buffer (too large, but whatever). */ + uint8_t abData[_1K + 64]; + + /** Current FIXUPP entry. */ + uint8_t iFixupp; + /** FIXUPP records being prepared for LEDATA currently stashed in abData. + * We may have to adjust addend values in the LEDATA when converting to OMF + * fixups. */ + struct + { + uint16_t cbRec; + uint8_t abData[_1K + 64]; + uint8_t abAlign[2]; /**< Alignment padding. */ + } aFixupps[3]; + + /** The index of the FLAT group. */ + uint16_t idxGrpFlat; + /** The EXTDEF index of the __ImageBase symbol. */ + uint16_t idxExtImageBase; + + /** LNAME lookup hash table. To avoid too many duplicates. */ + POMFWRLNAME apNameLookup[63]; +} OMFWRITE; +/** Pointer to an OMF writer. */ +typedef OMFWRITE *POMFWRITER; + + +/** + * Creates an OMF writer instance. + */ +static POMFWRITER omfWriter_Create(const char *pszSrc, uint32_t cSegments, uint32_t cSymbols, FILE *pDst) +{ + POMFWRITER pThis = (POMFWRITER)calloc(sizeof(OMFWRITER), 1); + if (pThis) + { + pThis->pszSrc = pszSrc; + pThis->idxNextName = 1; /* We start counting at 1. */ + pThis->cSegments = cSegments; + pThis->paSegments = (POMFTOSEGDEF)calloc(sizeof(OMFTOSEGDEF), cSegments); + if (pThis->paSegments) + { + pThis->cSymbols = cSymbols; + pThis->paSymbols = (POMFSYMBOL)calloc(sizeof(OMFSYMBOL), cSymbols); + if (pThis->paSymbols) + { + pThis->pDst = pDst; + return pThis; + } + free(pThis->paSegments); + } + free(pThis); + } + error(pszSrc, "Out of memory!\n"); + return NULL; +} + +/** + * Destroys the given OMF writer instance. + * @param pThis OMF writer instance. + */ +static void omfWriter_Destroy(POMFWRITER pThis) +{ + free(pThis->paSymbols); + + for (uint32_t i = 0; i < pThis->cSegments; i++) + if (pThis->paSegments[i].pszName) + free(pThis->paSegments[i].pszName); + + free(pThis->paSegments); + + uint32_t i = RT_ELEMENTS(pThis->apNameLookup); + while (i-- > 0) + { + POMFWRLNAME pNext = pThis->apNameLookup[i]; + pThis->apNameLookup[i] = NULL; + while (pNext) + { + POMFWRLNAME pFree = pNext; + pNext = pNext->pNext; + free(pFree); + } + } + + free(pThis); +} + +static bool omfWriter_RecBegin(POMFWRITER pThis, uint8_t bType) +{ + pThis->bType = bType; + pThis->cbRec = 0; + return true; +} + +static bool omfWriter_RecAddU8(POMFWRITER pThis, uint8_t b) +{ + if (pThis->cbRec < OMF_MAX_RECORD_PAYLOAD) + { + pThis->abData[pThis->cbRec++] = b; + return true; + } + return error(pThis->pszSrc, "Exceeded max OMF record length (bType=%#x)!\n", pThis->bType); +} + +static bool omfWriter_RecAddU16(POMFWRITER pThis, uint16_t u16) +{ + if (pThis->cbRec + 2U <= OMF_MAX_RECORD_PAYLOAD) + { + pThis->abData[pThis->cbRec++] = (uint8_t)u16; + pThis->abData[pThis->cbRec++] = (uint8_t)(u16 >> 8); + return true; + } + return error(pThis->pszSrc, "Exceeded max OMF record length (bType=%#x)!\n", pThis->bType); +} + +static bool omfWriter_RecAddU32(POMFWRITER pThis, uint32_t u32) +{ + if (pThis->cbRec + 4U <= OMF_MAX_RECORD_PAYLOAD) + { + pThis->abData[pThis->cbRec++] = (uint8_t)u32; + pThis->abData[pThis->cbRec++] = (uint8_t)(u32 >> 8); + pThis->abData[pThis->cbRec++] = (uint8_t)(u32 >> 16); + pThis->abData[pThis->cbRec++] = (uint8_t)(u32 >> 24); + return true; + } + return error(pThis->pszSrc, "Exceeded max OMF record length (bType=%#x)!\n", pThis->bType); +} + +static bool omfWriter_RecAddIdx(POMFWRITER pThis, uint16_t idx) +{ + if (idx < 128) + return omfWriter_RecAddU8(pThis, (uint8_t)idx); + if (idx < _32K) + return omfWriter_RecAddU8(pThis, (uint8_t)(idx >> 8) | 0x80) + && omfWriter_RecAddU8(pThis, (uint8_t)idx); + return error(pThis->pszSrc, "Index out of range %#x\n", idx); +} + +static bool omfWriter_RecAddBytes(POMFWRITER pThis, const void *pvData, size_t cbData) +{ + const uint16_t cbNasmHack = OMF_MAX_RECORD_PAYLOAD + 1; + if (cbData + pThis->cbRec <= cbNasmHack) + { + memcpy(&pThis->abData[pThis->cbRec], pvData, cbData); + pThis->cbRec += (uint16_t)cbData; + return true; + } + return error(pThis->pszSrc, "Exceeded max OMF record length (bType=%#x, cbData=%#x, cbRec=%#x, max=%#x)!\n", + pThis->bType, (unsigned)cbData, pThis->cbRec, OMF_MAX_RECORD_PAYLOAD); +} + +static bool omfWriter_RecAddStringNEx(POMFWRITER pThis, const char *pchString, size_t cchString, bool fPrependUnderscore) +{ + if (cchString < 256) + { + return omfWriter_RecAddU8(pThis, (uint8_t)cchString + fPrependUnderscore) + && (!fPrependUnderscore || omfWriter_RecAddU8(pThis, '_')) + && omfWriter_RecAddBytes(pThis, pchString, cchString); + } + return error(pThis->pszSrc, "String too long (%u bytes): '%*.*s'\n", + (unsigned)cchString, (int)cchString, (int)cchString, pchString); +} + +static bool omfWriter_RecAddStringN(POMFWRITER pThis, const char *pchString, size_t cchString) +{ + return omfWriter_RecAddStringNEx(pThis, pchString, cchString, false /*fPrependUnderscore*/); +} + +static bool omfWriter_RecAddString(POMFWRITER pThis, const char *pszString) +{ + return omfWriter_RecAddStringNEx(pThis, pszString, strlen(pszString), false /*fPrependUnderscore*/); +} + +static bool omfWriter_RecEnd(POMFWRITER pThis, bool fAddCrc) +{ + if ( !fAddCrc + || omfWriter_RecAddU8(pThis, 0)) + { + OMFRECHDR RecHdr = { pThis->bType, RT_H2LE_U16(pThis->cbRec) }; + if ( fwrite(&RecHdr, sizeof(RecHdr), 1, pThis->pDst) == 1 + && fwrite(pThis->abData, pThis->cbRec, 1, pThis->pDst) == 1) + { + pThis->bType = 0; + pThis->cbRec = 0; + return true; + } + return error(pThis->pszSrc, "Write error\n"); + } + return false; +} + +static bool omfWriter_RecEndWithCrc(POMFWRITER pThis) +{ + return omfWriter_RecEnd(pThis, true /*fAddCrc*/); +} + + +static bool omfWriter_BeginModule(POMFWRITER pThis, const char *pszFile) +{ + return omfWriter_RecBegin(pThis, OMF_THEADR) + && omfWriter_RecAddString(pThis, pszFile) + && omfWriter_RecEndWithCrc(pThis); +} + + +/** + * Simple stupid string hashing function (for LNAMES) + * @returns 8-bit hash. + * @param pchName The string. + * @param cchName The string length. + */ +DECLINLINE(uint8_t) omfWriter_HashStrU8(const char *pchName, size_t cchName) +{ + if (cchName) + return (uint8_t)(cchName + pchName[cchName >> 1]); + return 0; +} + +/** + * Looks up a LNAME. + * + * @returns Index (0..32K) if found, UINT16_MAX if not found. + * @param pThis The OMF writer. + * @param pchName The name to look up. + * @param cchName The length of the name. + */ +static uint16_t omfWriter_LNamesLookupN(POMFWRITER pThis, const char *pchName, size_t cchName) +{ + uint8_t uHash = omfWriter_HashStrU8(pchName, cchName); + uHash %= RT_ELEMENTS(pThis->apNameLookup); + + POMFWRLNAME pCur = pThis->apNameLookup[uHash]; + while (pCur) + { + if ( pCur->cchName == cchName + && memcmp(pCur->szName, pchName, cchName) == 0) + return pCur->idxName; + pCur = pCur->pNext; + } + + return UINT16_MAX; +} + +/** + * Add a LNAME lookup record. + * + * @returns success indicator. + * @param pThis The OMF writer. + * @param pchName The name to look up. + * @param cchName The length of the name. + * @param idxName The name index. + */ +static bool omfWriter_LNamesAddLookup(POMFWRITER pThis, const char *pchName, size_t cchName, uint16_t idxName) +{ + POMFWRLNAME pCur = (POMFWRLNAME)malloc(sizeof(*pCur) + cchName); + if (!pCur) + return error("???", "Out of memory!\n"); + + pCur->idxName = idxName; + pCur->cchName = (uint8_t)cchName; + memcpy(pCur->szName, pchName, cchName); + pCur->szName[cchName] = '\0'; + + uint8_t uHash = omfWriter_HashStrU8(pchName, cchName); + uHash %= RT_ELEMENTS(pThis->apNameLookup); + pCur->pNext = pThis->apNameLookup[uHash]; + pThis->apNameLookup[uHash] = pCur; + + return true; +} + + +static bool omfWriter_LNamesAddN(POMFWRITER pThis, const char *pchName, size_t cchName, uint16_t *pidxName) +{ + /* See if we've already got that name in the list. */ + uint16_t idxName; + if (pidxName) /* If pidxName is NULL, we assume the caller might just be passing stuff thru. */ + { + idxName = omfWriter_LNamesLookupN(pThis, pchName, cchName); + if (idxName != UINT16_MAX) + { + *pidxName = idxName; + return true; + } + } + + /* split? */ + if (pThis->cbRec + 1 /*len*/ + cchName + 1 /*crc*/ > OMF_MAX_RECORD_PAYLOAD) + { + if (pThis->cbRec == 0) + return error(pThis->pszSrc, "Too long LNAME '%*.*s'\n", (int)cchName, (int)cchName, pchName); + if ( !omfWriter_RecEndWithCrc(pThis) + || !omfWriter_RecBegin(pThis, OMF_LNAMES)) + return false; + } + + idxName = pThis->idxNextName++; + if (pidxName) + *pidxName = idxName; + return omfWriter_RecAddStringN(pThis, pchName, cchName) + && omfWriter_LNamesAddLookup(pThis, pchName, cchName, idxName); +} + +static bool omfWriter_LNamesAdd(POMFWRITER pThis, const char *pszName, uint16_t *pidxName) +{ + return omfWriter_LNamesAddN(pThis, pszName, strlen(pszName), pidxName); +} + +static bool omfWriter_LNamesBegin(POMFWRITER pThis, bool fAddZeroEntry) +{ + /* First entry is an empty string. */ + return omfWriter_RecBegin(pThis, OMF_LNAMES) + && ( pThis->idxNextName > 1 + || !fAddZeroEntry + || omfWriter_LNamesAddN(pThis, "", 0, NULL)); +} + +static bool omfWriter_LNamesEnd(POMFWRITER pThis) +{ + return omfWriter_RecEndWithCrc(pThis); +} + + +static bool omfWriter_SegDef(POMFWRITER pThis, uint8_t bSegAttr, uint32_t cbSeg, uint16_t idxSegName, uint16_t idxSegClass, + uint16_t idxOverlay = 1 /* NULL entry */) +{ + return omfWriter_RecBegin(pThis, OMF_SEGDEF32) + && omfWriter_RecAddU8(pThis, bSegAttr) + && omfWriter_RecAddU32(pThis, cbSeg) + && omfWriter_RecAddIdx(pThis, idxSegName) + && omfWriter_RecAddIdx(pThis, idxSegClass) + && omfWriter_RecAddIdx(pThis, idxOverlay) + && omfWriter_RecEndWithCrc(pThis); +} + +static bool omfWriter_SegDef16(POMFWRITER pThis, uint8_t bSegAttr, uint32_t cbSeg, uint16_t idxSegName, uint16_t idxSegClass, + uint16_t idxOverlay = 1 /* NULL entry */) +{ + Assert(cbSeg <= UINT16_MAX); + return omfWriter_RecBegin(pThis, OMF_SEGDEF16) + && omfWriter_RecAddU8(pThis, bSegAttr) + && omfWriter_RecAddU16(pThis, cbSeg) + && omfWriter_RecAddIdx(pThis, idxSegName) + && omfWriter_RecAddIdx(pThis, idxSegClass) + && omfWriter_RecAddIdx(pThis, idxOverlay) + && omfWriter_RecEndWithCrc(pThis); +} + +static bool omfWriter_GrpDefBegin(POMFWRITER pThis, uint16_t idxGrpName) +{ + return omfWriter_RecBegin(pThis, OMF_GRPDEF) + && omfWriter_RecAddIdx(pThis, idxGrpName); +} + +static bool omfWriter_GrpDefAddSegDef(POMFWRITER pThis, uint16_t idxSegDef) +{ + return omfWriter_RecAddU8(pThis, 0xff) + && omfWriter_RecAddIdx(pThis, idxSegDef); +} + +static bool omfWriter_GrpDefEnd(POMFWRITER pThis) +{ + return omfWriter_RecEndWithCrc(pThis); +} + + +static bool omfWriter_PubDefBegin(POMFWRITER pThis, uint16_t idxGrpDef, uint16_t idxSegDef) +{ + return omfWriter_RecBegin(pThis, OMF_PUBDEF32) + && omfWriter_RecAddIdx(pThis, idxGrpDef) + && omfWriter_RecAddIdx(pThis, idxSegDef) + && ( idxSegDef != 0 + || omfWriter_RecAddU16(pThis, 0)); + +} + +static bool omfWriter_PubDefAddN(POMFWRITER pThis, uint32_t uValue, const char *pchString, size_t cchString, + bool fPrependUnderscore) +{ + /* Split? */ + if (pThis->cbRec + 1 + cchString + 4 + 1 + 1 + fPrependUnderscore > OMF_MAX_RECORD_PAYLOAD) + { + if (cchString >= 256) + return error(pThis->pszSrc, "PUBDEF string too long %u ('%s')\n", + (unsigned)cchString, (int)cchString, (int)cchString, pchString); + if (!omfWriter_RecEndWithCrc(pThis)) + return false; + + /* Figure out the initial data length. */ + pThis->cbRec = 1 + ((pThis->abData[0] & 0x80) != 0); + if (pThis->abData[pThis->cbRec] != 0) + pThis->cbRec += 1 + ((pThis->abData[pThis->cbRec] & 0x80) != 0); + else + pThis->cbRec += 3; + pThis->bType = OMF_PUBDEF32; + } + + return omfWriter_RecAddStringNEx(pThis, pchString, cchString, fPrependUnderscore) + && omfWriter_RecAddU32(pThis, uValue) + && omfWriter_RecAddIdx(pThis, 0); /* type */ +} + +static bool omfWriter_PubDefAdd(POMFWRITER pThis, uint32_t uValue, const char *pszString, bool fPrependUnderscore) +{ + return omfWriter_PubDefAddN(pThis, uValue, pszString, strlen(pszString), fPrependUnderscore); +} + +static bool omfWriter_PubDefEnd(POMFWRITER pThis) +{ + return omfWriter_RecEndWithCrc(pThis); +} + +/** + * EXTDEF - Begin record. + */ +static bool omfWriter_ExtDefBegin(POMFWRITER pThis) +{ + return omfWriter_RecBegin(pThis, OMF_EXTDEF); + +} + +/** + * EXTDEF - Add an entry, split record if necessary. + */ +static bool omfWriter_ExtDefAddN(POMFWRITER pThis, const char *pchString, size_t cchString, uint16_t idxType, + bool fPrependUnderscore) +{ + /* Split? */ + if (pThis->cbRec + 1 + cchString + 1 + 1 + fPrependUnderscore > OMF_MAX_RECORD_PAYLOAD) + { + if (cchString >= 256) + return error(pThis->pszSrc, "EXTDEF string too long %u ('%s')\n", + (unsigned)cchString, (int)cchString, (int)cchString, pchString); + if ( !omfWriter_RecEndWithCrc(pThis) + || !omfWriter_RecBegin(pThis, OMF_EXTDEF)) + return false; + } + + return omfWriter_RecAddStringNEx(pThis, pchString, cchString, fPrependUnderscore) + && omfWriter_RecAddIdx(pThis, idxType); /* type */ +} + +/** + * EXTDEF - Add an entry, split record if necessary. + */ +static bool omfWriter_ExtDefAdd(POMFWRITER pThis, const char *pszString, bool fPrependUnderscore) +{ + return omfWriter_ExtDefAddN(pThis, pszString, strlen(pszString), 0, fPrependUnderscore); +} + +/** + * EXTDEF - End of record. + */ +static bool omfWriter_ExtDefEnd(POMFWRITER pThis) +{ + return omfWriter_RecEndWithCrc(pThis); +} + +/** + * COMENT/LINK_PASS_SEP - Add a link pass separator comment. + */ +static bool omfWriter_LinkPassSeparator(POMFWRITER pThis) +{ + return omfWriter_RecBegin(pThis, OMF_COMENT) + && omfWriter_RecAddU8(pThis, OMF_CTYP_NO_LIST) + && omfWriter_RecAddU8(pThis, OMF_CCLS_LINK_PASS_SEP) + && omfWriter_RecAddU8(pThis, 1) + && omfWriter_RecEndWithCrc(pThis); +} + + +/** + * LEDATA + FIXUPP - Begin records. + */ +static bool omfWriter_LEDataBegin(POMFWRITER pThis, uint16_t idxSeg, uint32_t offSeg) +{ + if ( omfWriter_RecBegin(pThis, OMF_LEDATA32) + && omfWriter_RecAddIdx(pThis, idxSeg) + && omfWriter_RecAddU32(pThis, offSeg)) + { + pThis->idx = idxSeg; + pThis->offSeg = offSeg; + pThis->offSegRec = offSeg; + pThis->offSegEnd = offSeg + OMF_MAX_RECORD_PAYLOAD - 1 /*CRC*/ - pThis->cbRec; + pThis->offSegEnd &= ~(uint32_t)7; /* qword align. */ + + /* Reset the associated FIXUPP records. */ + pThis->iFixupp = 0; + for (unsigned i = 0; i < RT_ELEMENTS(pThis->aFixupps); i++) + pThis->aFixupps[i].cbRec = 0; + return true; + } + return false; +} + +/** + * LEDATA + FIXUPP - Begin records. + */ +static bool omfWriter_LEDataBeginEx(POMFWRITER pThis, uint16_t idxSeg, uint32_t offSeg, + uint32_t cbData, uint32_t cbRawData, void const *pbRawData, uint8_t **ppbData) +{ + if ( omfWriter_RecBegin(pThis, OMF_LEDATA32) + && omfWriter_RecAddIdx(pThis, idxSeg) + && omfWriter_RecAddU32(pThis, offSeg)) + { + if ( cbData <= _1K + && pThis->cbRec + cbData + 1 <= OMF_MAX_RECORD_PAYLOAD) + { + uint8_t *pbDst = &pThis->abData[pThis->cbRec]; + if (ppbData) + *ppbData = pbDst; + + if (cbRawData) + memcpy(pbDst, pbRawData, RT_MIN(cbData, cbRawData)); + if (cbData > cbRawData) + memset(&pbDst[cbRawData], 0, cbData - cbRawData); + + pThis->cbRec += cbData; + pThis->idx = idxSeg; + pThis->offSegRec = offSeg; + pThis->offSeg = offSeg + cbData; + pThis->offSegEnd = offSeg + cbData; + + /* Reset the associated FIXUPP records. */ + pThis->iFixupp = 0; + for (unsigned i = 0; i < RT_ELEMENTS(pThis->aFixupps); i++) + pThis->aFixupps[i].cbRec = 0; + return true; + } + error(pThis->pszSrc, "Too much data for LEDATA record! (%#x)\n", (unsigned)cbData); + } + return false; +} + +/** + * LEDATA + FIXUPP - Add FIXUPP subrecord bytes, split if necessary. + */ +static bool omfWriter_LEDataAddFixuppBytes(POMFWRITER pThis, void *pvSubRec, size_t cbSubRec) +{ + /* Split? */ + unsigned iFixupp = pThis->iFixupp; + if (pThis->aFixupps[iFixupp].cbRec + cbSubRec >= OMF_MAX_RECORD_PAYLOAD) + { + if (g_cVerbose >= 2) + printf("debug: FIXUPP split\n"); + iFixupp++; + if (iFixupp >= RT_ELEMENTS(pThis->aFixupps)) + return error(pThis->pszSrc, "Out of FIXUPP records\n"); + pThis->iFixupp = iFixupp; + pThis->aFixupps[iFixupp].cbRec = 0; /* paranoia */ + } + + /* Append the sub-record data. */ + memcpy(&pThis->aFixupps[iFixupp].abData[pThis->aFixupps[iFixupp].cbRec], pvSubRec, cbSubRec); + pThis->aFixupps[iFixupp].cbRec += (uint16_t)cbSubRec; + return true; +} + +/** + * LEDATA + FIXUPP - Add fixup, split if necessary. + */ +static bool omfWriter_LEDataAddFixup(POMFWRITER pThis, uint16_t offDataRec, bool fSelfRel, uint8_t bLocation, + uint8_t bFrame, uint16_t idxFrame, + uint8_t bTarget, uint16_t idxTarget, bool fTargetDisp, uint32_t offTargetDisp) +{ + if (g_cVerbose >= 2) + printf("debug: FIXUP[%#x]: off=%#x frame=%u:%#x target=%u:%#x disp=%d:%#x\n", pThis->aFixupps[pThis->iFixupp].cbRec, + offDataRec, bFrame, idxFrame, bTarget, idxTarget, fTargetDisp, offTargetDisp); + + if ( offDataRec >= _1K + || bFrame >= 6 + || bTarget > 6 + || idxFrame >= _32K + || idxTarget >= _32K + || fTargetDisp != (bTarget <= OMF_FIX_T_FRAME_NO) ) + return error(pThis->pszSrc, + "Internal error: offDataRec=%#x bFrame=%u idxFrame=%#x bTarget=%u idxTarget=%#x fTargetDisp=%d offTargetDisp=%#x\n", + offDataRec, bFrame, idxFrame, bTarget, idxTarget, fTargetDisp, offTargetDisp); + + + /* + * Encode the FIXUP subrecord. + */ + uint8_t abFixup[16]; + uint8_t off = 0; + /* Location */ + abFixup[off++] = (offDataRec >> 8) | (bLocation << 2) | ((uint8_t)!fSelfRel << 6) | 0x80; + abFixup[off++] = (uint8_t)offDataRec; + /* Fix Data */ + abFixup[off++] = 0x00 /*F=0*/ | (bFrame << 4) | 0x00 /*T=0*/ | bTarget; + /* Frame Datum */ + if (bFrame <= OMF_FIX_F_FRAME_NO) + { + if (idxFrame >= 128) + abFixup[off++] = (uint8_t)(idxFrame >> 8) | 0x80; + abFixup[off++] = (uint8_t)idxFrame; + } + /* Target Datum */ + if (idxTarget >= 128) + abFixup[off++] = (uint8_t)(idxTarget >> 8) | 0x80; + abFixup[off++] = (uint8_t)idxTarget; + /* Target Displacement */ + if (fTargetDisp) + { + abFixup[off++] = RT_BYTE1(offTargetDisp); + abFixup[off++] = RT_BYTE2(offTargetDisp); + abFixup[off++] = RT_BYTE3(offTargetDisp); + abFixup[off++] = RT_BYTE4(offTargetDisp); + } + + return omfWriter_LEDataAddFixuppBytes(pThis, abFixup, off); +} + +/** + * LEDATA + FIXUPP - Add simple fixup, split if necessary. + */ +static bool omfWriter_LEDataAddFixupNoDisp(POMFWRITER pThis, uint16_t offDataRec, uint8_t bLocation, + uint8_t bFrame, uint16_t idxFrame, uint8_t bTarget, uint16_t idxTarget) +{ + return omfWriter_LEDataAddFixup(pThis, offDataRec, false /*fSelfRel*/, bLocation, bFrame, idxFrame, bTarget, idxTarget, + false /*fTargetDisp*/, 0 /*offTargetDisp*/); +} + + +/** + * LEDATA + FIXUPP - End of records. + */ +static bool omfWriter_LEDataEnd(POMFWRITER pThis) +{ + if (omfWriter_RecEndWithCrc(pThis)) + { + for (unsigned iFixupp = 0; iFixupp <= pThis->iFixupp; iFixupp++) + { + uint16_t const cbRec = pThis->aFixupps[iFixupp].cbRec; + if (!cbRec) + break; + if (g_cVerbose >= 3) + printf("debug: FIXUPP32 #%u cbRec=%#x\n", iFixupp, cbRec); + if ( !omfWriter_RecBegin(pThis, OMF_FIXUPP32) + || !omfWriter_RecAddBytes(pThis, pThis->aFixupps[iFixupp].abData, cbRec) + || !omfWriter_RecEndWithCrc(pThis)) + return false; + } + pThis->iFixupp = 0; + return true; + } + return false; +} + +/** + * LEDATA + FIXUPP - Splits the LEDATA record. + */ +static bool omfWriter_LEDataSplit(POMFWRITER pThis) +{ + return omfWriter_LEDataEnd(pThis) + && omfWriter_LEDataBegin(pThis, pThis->idx, pThis->offSeg); +} + +/** + * LEDATA + FIXUPP - Returns available space in current LEDATA record. + */ +static uint32_t omfWriter_LEDataAvailable(POMFWRITER pThis) +{ + if (pThis->offSeg < pThis->offSegEnd) + return pThis->offSegEnd - pThis->offSeg; + return 0; +} + +/** + * LEDATA + FIXUPP - Splits LEDATA record if less than @a cb bytes available. + */ +static bool omfWriter_LEDataEnsureSpace(POMFWRITER pThis, uint32_t cb) +{ + if ( omfWriter_LEDataAvailable(pThis) >= cb + || omfWriter_LEDataSplit(pThis)) + return true; + return false; +} + +/** + * LEDATA + FIXUPP - Adds data to the LEDATA record, splitting it if needed. + */ +static bool omfWriter_LEDataAddBytes(POMFWRITER pThis, void const *pvData, size_t cbData) +{ + while (cbData > 0) + { + uint32_t cbAvail = omfWriter_LEDataAvailable(pThis); + if (cbAvail >= cbData) + { + if (omfWriter_RecAddBytes(pThis, pvData, cbData)) + { + pThis->offSeg += (uint32_t)cbData; + break; + } + return false; + } + if (!omfWriter_RecAddBytes(pThis, pvData, cbAvail)) + return false; + pThis->offSeg += cbAvail; + pvData = (uint8_t const *)pvData + cbAvail; + cbData -= cbAvail; + if (!omfWriter_LEDataSplit(pThis)) + return false; + } + return true; +} + +/** + * LEDATA + FIXUPP - Adds a U32 to the LEDATA record, splitting if needed. + */ +static bool omfWriter_LEDataAddU32(POMFWRITER pThis, uint32_t u32) +{ + if ( omfWriter_LEDataEnsureSpace(pThis, 4) + && omfWriter_RecAddU32(pThis, u32)) + { + pThis->offSeg += 4; + return true; + } + return false; +} + +/** + * LEDATA + FIXUPP - Adds a U16 to the LEDATA record, splitting if needed. + */ +static bool omfWriter_LEDataAddU16(POMFWRITER pThis, uint16_t u16) +{ + if ( omfWriter_LEDataEnsureSpace(pThis, 2) + && omfWriter_RecAddU16(pThis, u16)) + { + pThis->offSeg += 2; + return true; + } + return false; +} + +#if 0 /* unused */ +/** + * LEDATA + FIXUPP - Adds a byte to the LEDATA record, splitting if needed. + */ +static bool omfWriter_LEDataAddU8(POMFWRITER pThis, uint8_t b) +{ + if ( omfWriter_LEDataEnsureSpace(pThis, 1) + && omfWriter_RecAddU8(pThis, b)) + { + pThis->offSeg += 1; + return true; + } + return false; +} +#endif + +/** + * MODEND - End of module, simple variant. + */ +static bool omfWriter_EndModule(POMFWRITER pThis) +{ + return omfWriter_RecBegin(pThis, OMF_MODEND32) + && omfWriter_RecAddU8(pThis, 0) + && omfWriter_RecEndWithCrc(pThis); +} + + + + +/********************************************************************************************************************************* +* ELF64/AMD64 -> ELF64/i386 Converter * +*********************************************************************************************************************************/ + +/** AMD64 relocation type names for ELF. */ +static const char * const g_apszElfAmd64RelTypes[] = +{ + "R_X86_64_NONE", + "R_X86_64_64", + "R_X86_64_PC32", + "R_X86_64_GOT32", + "R_X86_64_PLT32", + "R_X86_64_COPY", + "R_X86_64_GLOB_DAT", + "R_X86_64_JMP_SLOT", + "R_X86_64_RELATIVE", + "R_X86_64_GOTPCREL", + "R_X86_64_32", + "R_X86_64_32S", + "R_X86_64_16", + "R_X86_64_PC16", + "R_X86_64_8", + "R_X86_64_PC8", + "R_X86_64_DTPMOD64", + "R_X86_64_DTPOFF64", + "R_X86_64_TPOFF64", + "R_X86_64_TLSGD", + "R_X86_64_TLSLD", + "R_X86_64_DTPOFF32", + "R_X86_64_GOTTPOFF", + "R_X86_64_TPOFF32", +}; + +/** AMD64 relocation type sizes for ELF. */ +static uint8_t const g_acbElfAmd64RelTypes[] = +{ + 0, /* R_X86_64_NONE */ + 8, /* R_X86_64_64 */ + 4, /* R_X86_64_PC32 */ + 4, /* R_X86_64_GOT32 */ + 4, /* R_X86_64_PLT32 */ + 0, /* R_X86_64_COPY */ + 0, /* R_X86_64_GLOB_DAT */ + 0, /* R_X86_64_JMP_SLOT */ + 0, /* R_X86_64_RELATIVE */ + 0, /* R_X86_64_GOTPCREL */ + 4, /* R_X86_64_32 */ + 4, /* R_X86_64_32S */ + 2, /* R_X86_64_16 */ + 2, /* R_X86_64_PC16 */ + 1, /* R_X86_64_8 */ + 1, /* R_X86_64_PC8 */ + 0, /* R_X86_64_DTPMOD64 */ + 0, /* R_X86_64_DTPOFF64 */ + 0, /* R_X86_64_TPOFF64 */ + 0, /* R_X86_64_TLSGD */ + 0, /* R_X86_64_TLSLD */ + 0, /* R_X86_64_DTPOFF32 */ + 0, /* R_X86_64_GOTTPOFF */ + 0, /* R_X86_64_TPOFF32 */ +}; + +/** Macro for getting the size of a AMD64 ELF relocation. */ +#define ELF_AMD64_RELOC_SIZE(a_Type) ( (a_Type) < RT_ELEMENTS(g_acbElfAmd64RelTypes) ? g_acbElfAmd64RelTypes[(a_Type)] : 1) + + +typedef struct ELFDETAILS +{ + /** The ELF header. */ + Elf64_Ehdr const *pEhdr; + /** The section header table. */ + Elf64_Shdr const *paShdrs; + /** The string table for the section names. */ + const char *pchShStrTab; + + /** The symbol table section number. UINT16_MAX if not found. */ + uint16_t iSymSh; + /** The string table section number. UINT16_MAX if not found. */ + uint16_t iStrSh; + + /** The symbol table. */ + Elf64_Sym const *paSymbols; + /** The number of symbols in the symbol table. */ + uint32_t cSymbols; + + /** Pointer to the (symbol) string table if found. */ + const char *pchStrTab; + /** The string table size. */ + size_t cbStrTab; + +} ELFDETAILS; +typedef ELFDETAILS *PELFDETAILS; +typedef ELFDETAILS const *PCELFDETAILS; + + +static bool validateElf(const char *pszFile, uint8_t const *pbFile, size_t cbFile, PELFDETAILS pElfStuff) +{ + /* + * Initialize the ELF details structure. + */ + memset(pElfStuff, 0, sizeof(*pElfStuff)); + pElfStuff->iSymSh = UINT16_MAX; + pElfStuff->iStrSh = UINT16_MAX; + + /* + * Validate the header and our other expectations. + */ + Elf64_Ehdr const *pEhdr = (Elf64_Ehdr const *)pbFile; + pElfStuff->pEhdr = pEhdr; + if ( pEhdr->e_ident[EI_CLASS] != ELFCLASS64 + || pEhdr->e_ident[EI_DATA] != ELFDATA2LSB + || pEhdr->e_ehsize != sizeof(Elf64_Ehdr) + || pEhdr->e_shentsize != sizeof(Elf64_Shdr) + || pEhdr->e_version != EV_CURRENT ) + return error(pszFile, "Unsupported ELF config\n"); + if (pEhdr->e_type != ET_REL) + return error(pszFile, "Expected relocatable ELF file (e_type=%d)\n", pEhdr->e_type); + if (pEhdr->e_machine != EM_X86_64) + return error(pszFile, "Expected relocatable ELF file (e_type=%d)\n", pEhdr->e_machine); + if (pEhdr->e_phnum != 0) + return error(pszFile, "Expected e_phnum to be zero not %u\n", pEhdr->e_phnum); + if (pEhdr->e_shnum < 2) + return error(pszFile, "Expected e_shnum to be two or higher\n"); + if (pEhdr->e_shstrndx >= pEhdr->e_shnum || pEhdr->e_shstrndx == 0) + return error(pszFile, "Bad e_shstrndx=%u (e_shnum=%u)\n", pEhdr->e_shstrndx, pEhdr->e_shnum); + if ( pEhdr->e_shoff >= cbFile + || pEhdr->e_shoff + pEhdr->e_shnum * sizeof(Elf64_Shdr) > cbFile) + return error(pszFile, "Section table is outside the file (e_shoff=%#llx, e_shnum=%u, cbFile=%#llx)\n", + pEhdr->e_shstrndx, pEhdr->e_shnum, (uint64_t)cbFile); + + /* + * Locate the section name string table. + * We assume it's okay as we only reference it in verbose mode. + */ + Elf64_Shdr const *paShdrs = (Elf64_Shdr const *)&pbFile[pEhdr->e_shoff]; + pElfStuff->paShdrs = paShdrs; + + Elf64_Xword const cbShStrTab = paShdrs[pEhdr->e_shstrndx].sh_size; + if ( paShdrs[pEhdr->e_shstrndx].sh_offset > cbFile + || cbShStrTab > cbFile + || paShdrs[pEhdr->e_shstrndx].sh_offset + cbShStrTab > cbFile) + return error(pszFile, + "Section string table is outside the file (sh_offset=%#" ELF_FMT_X64 " sh_size=%#" ELF_FMT_X64 " cbFile=%#" ELF_FMT_X64 ")\n", + paShdrs[pEhdr->e_shstrndx].sh_offset, paShdrs[pEhdr->e_shstrndx].sh_size, (Elf64_Xword)cbFile); + const char *pchShStrTab = (const char *)&pbFile[paShdrs[pEhdr->e_shstrndx].sh_offset]; + pElfStuff->pchShStrTab = pchShStrTab; + + /* + * Work the section table. + */ + bool fRet = true; + for (uint32_t i = 1; i < pEhdr->e_shnum; i++) + { + if (paShdrs[i].sh_name >= cbShStrTab) + return error(pszFile, "Invalid sh_name value (%#x) for section #%u\n", paShdrs[i].sh_name, i); + const char *pszShNm = &pchShStrTab[paShdrs[i].sh_name]; + + if ( paShdrs[i].sh_offset > cbFile + || paShdrs[i].sh_size > cbFile + || paShdrs[i].sh_offset + paShdrs[i].sh_size > cbFile) + return error(pszFile, "Section #%u '%s' has data outside the file: %#" ELF_FMT_X64 " LB %#" ELF_FMT_X64 " (cbFile=%#" ELF_FMT_X64 ")\n", + i, pszShNm, paShdrs[i].sh_offset, paShdrs[i].sh_size, (Elf64_Xword)cbFile); + if (g_cVerbose) + printf("shdr[%u]: name=%#x '%s' type=%#x flags=%#" ELF_FMT_X64 " addr=%#" ELF_FMT_X64 " off=%#" ELF_FMT_X64 " size=%#" ELF_FMT_X64 "\n" + " link=%u info=%#x align=%#" ELF_FMT_X64 " entsize=%#" ELF_FMT_X64 "\n", + i, paShdrs[i].sh_name, pszShNm, paShdrs[i].sh_type, paShdrs[i].sh_flags, + paShdrs[i].sh_addr, paShdrs[i].sh_offset, paShdrs[i].sh_size, + paShdrs[i].sh_link, paShdrs[i].sh_info, paShdrs[i].sh_addralign, paShdrs[i].sh_entsize); + + if (paShdrs[i].sh_link >= pEhdr->e_shnum) + return error(pszFile, "Section #%u '%s' links to a section outside the section table: %#x, max %#x\n", + i, pszShNm, paShdrs[i].sh_link, pEhdr->e_shnum); + if (!RT_IS_POWER_OF_TWO(paShdrs[i].sh_addralign)) + return error(pszFile, "Section #%u '%s' alignment value is not a power of two: %#" ELF_FMT_X64 "\n", + i, pszShNm, paShdrs[i].sh_addralign); + if (!RT_IS_POWER_OF_TWO(paShdrs[i].sh_addralign)) + return error(pszFile, "Section #%u '%s' alignment value is not a power of two: %#" ELF_FMT_X64 "\n", + i, pszShNm, paShdrs[i].sh_addralign); + if (paShdrs[i].sh_addr != 0) + return error(pszFile, "Section #%u '%s' has non-zero address: %#" ELF_FMT_X64 "\n", i, pszShNm, paShdrs[i].sh_addr); + + if (paShdrs[i].sh_type == SHT_RELA) + { + if (paShdrs[i].sh_entsize != sizeof(Elf64_Rela)) + return error(pszFile, "Expected sh_entsize to be %u not %u for section #%u (%s)\n", (unsigned)sizeof(Elf64_Rela), + paShdrs[i].sh_entsize, i, pszShNm); + uint32_t const cRelocs = paShdrs[i].sh_size / sizeof(Elf64_Rela); + if (cRelocs * sizeof(Elf64_Rela) != paShdrs[i].sh_size) + return error(pszFile, "Uneven relocation entry count in #%u (%s): sh_size=%#" ELF_FMT_X64 "\n", + i, pszShNm, paShdrs[i].sh_size); + if ( paShdrs[i].sh_offset > cbFile + || paShdrs[i].sh_size >= cbFile + || paShdrs[i].sh_offset + paShdrs[i].sh_size > cbFile) + return error(pszFile, "The content of section #%u '%s' is outside the file (%#" ELF_FMT_X64 " LB %#" ELF_FMT_X64 ", cbFile=%#lx)\n", + i, pszShNm, paShdrs[i].sh_offset, paShdrs[i].sh_size, (unsigned long)cbFile); + if (paShdrs[i].sh_info != i - 1) + return error(pszFile, "Expected relocation section #%u (%s) to link to previous section: sh_info=%#u\n", + i, pszShNm, (unsigned)paShdrs[i].sh_link); + if (paShdrs[paShdrs[i].sh_link].sh_type != SHT_SYMTAB) + return error(pszFile, "Expected relocation section #%u (%s) to link to symbol table: sh_link=%#u -> sh_type=%#x\n", + i, pszShNm, (unsigned)paShdrs[i].sh_link, (unsigned)paShdrs[paShdrs[i].sh_link].sh_type); + uint32_t cSymbols = paShdrs[paShdrs[i].sh_link].sh_size / paShdrs[paShdrs[i].sh_link].sh_entsize; + + Elf64_Rela const *paRelocs = (Elf64_Rela *)&pbFile[paShdrs[i].sh_offset]; + for (uint32_t j = 0; j < cRelocs; j++) + { + uint8_t const bType = ELF64_R_TYPE(paRelocs[j].r_info); + if (RT_UNLIKELY(bType >= R_X86_64_COUNT)) + fRet = error(pszFile, + "%#018" ELF_FMT_X64 " %#018" ELF_FMT_X64 ": unknown fix up %#x (%+" ELF_FMT_D64 ")\n", + paRelocs[j].r_offset, paRelocs[j].r_info, bType, paRelocs[j].r_addend); + if (RT_UNLIKELY( paRelocs[j].r_offset > paShdrs[i - 1].sh_size + || paRelocs[j].r_offset + ELF_AMD64_RELOC_SIZE(ELF64_R_TYPE(paRelocs[j].r_info)) + > paShdrs[i - 1].sh_size)) + fRet = error(pszFile, + "%#018" ELF_FMT_X64 " %#018" ELF_FMT_X64 ": out of bounds (sh_size %" ELF_FMT_X64 ")\n", + paRelocs[j].r_offset, paRelocs[j].r_info, paShdrs[i - 1].sh_size); + + uint32_t const iSymbol = ELF64_R_SYM(paRelocs[j].r_info); + if (RT_UNLIKELY(iSymbol >= cSymbols)) + fRet = error(pszFile, + "%#018" ELF_FMT_X64 " %#018" ELF_FMT_X64 ": symbol index (%#x) out of bounds (%#x)\n", + paRelocs[j].r_offset, paRelocs[j].r_info, iSymbol, cSymbols); + } + } + else if (paShdrs[i].sh_type == SHT_REL) + fRet = error(pszFile, "Section #%u '%s': Unexpected SHT_REL section\n", i, pszShNm); + else if (paShdrs[i].sh_type == SHT_SYMTAB) + { + if (paShdrs[i].sh_entsize != sizeof(Elf64_Sym)) + fRet = error(pszFile, "Section #%u '%s': Unsupported symbol table entry size in : #%u (expected #%u)\n", + i, pszShNm, paShdrs[i].sh_entsize, sizeof(Elf64_Sym)); + Elf64_Xword const cSymbols = paShdrs[i].sh_size / paShdrs[i].sh_entsize; + if (cSymbols * paShdrs[i].sh_entsize != paShdrs[i].sh_size) + fRet = error(pszFile, "Section #%u '%s': Size not a multiple of entry size: %#" ELF_FMT_X64 " %% %#" ELF_FMT_X64 " = %#" ELF_FMT_X64 "\n", + i, pszShNm, paShdrs[i].sh_size, paShdrs[i].sh_entsize, paShdrs[i].sh_size % paShdrs[i].sh_entsize); + if (cSymbols > UINT32_MAX) + fRet = error(pszFile, "Section #%u '%s': too many symbols: %" ELF_FMT_X64 "\n", + i, pszShNm, paShdrs[i].sh_size, cSymbols); + + if (pElfStuff->iSymSh == UINT16_MAX) + { + pElfStuff->iSymSh = (uint16_t)i; + pElfStuff->paSymbols = (Elf64_Sym const *)&pbFile[paShdrs[i].sh_offset]; + pElfStuff->cSymbols = cSymbols; + + if (paShdrs[i].sh_link != 0) + { + /* Note! The symbol string table section header may not have been validated yet! */ + Elf64_Shdr const *pStrTabShdr = &paShdrs[paShdrs[i].sh_link]; + pElfStuff->iStrSh = paShdrs[i].sh_link; + pElfStuff->pchStrTab = (const char *)&pbFile[pStrTabShdr->sh_offset]; + pElfStuff->cbStrTab = (size_t)pStrTabShdr->sh_size; + } + else + fRet = error(pszFile, "Section #%u '%s': String table link is out of bounds (%#x)\n", + i, pszShNm, paShdrs[i].sh_link); + } + else + fRet = error(pszFile, "Section #%u '%s': Found additonal symbol table, previous in #%u\n", + i, pszShNm, pElfStuff->iSymSh); + } + } + return fRet; +} + + +static bool convertElfSectionsToSegDefsAndGrpDefs(POMFWRITER pThis, PCELFDETAILS pElfStuff) +{ + /* + * Do the list of names pass. + */ + uint16_t idxGrpFlat, idxGrpData; + uint16_t idxClassCode, idxClassData, idxClassDwarf; + if ( !omfWriter_LNamesBegin(pThis, true /*fAddZeroEntry*/) + || !omfWriter_LNamesAddN(pThis, RT_STR_TUPLE("FLAT"), &idxGrpFlat) + || !omfWriter_LNamesAddN(pThis, RT_STR_TUPLE("BS3DATA64_GROUP"), &idxGrpData) + || !omfWriter_LNamesAddN(pThis, RT_STR_TUPLE("BS3CLASS64CODE"), &idxClassCode) + || !omfWriter_LNamesAddN(pThis, RT_STR_TUPLE("FAR_DATA"), &idxClassData) + || !omfWriter_LNamesAddN(pThis, RT_STR_TUPLE("DWARF"), &idxClassDwarf) + ) + return false; + + bool fHaveData = false; + Elf64_Shdr const *pShdr = &pElfStuff->paShdrs[1]; + Elf64_Half const cSections = pElfStuff->pEhdr->e_shnum; + for (Elf64_Half i = 1; i < cSections; i++, pShdr++) + { + const char *pszName = &pElfStuff->pchShStrTab[pShdr->sh_name]; + if (*pszName == '\0') + return error(pThis->pszSrc, "Section #%u has an empty name!\n", i); + + switch (pShdr->sh_type) + { + case SHT_PROGBITS: + case SHT_NOBITS: + /* We drop a few sections we don't want:. */ + if ( strcmp(pszName, ".comment") != 0 /* compiler info */ + && strcmp(pszName, ".note.GNU-stack") != 0 /* some empty section for hinting the linker/whatever */ + && strcmp(pszName, ".eh_frame") != 0 /* unwind / exception info */ + ) + { + pThis->paSegments[i].iSegDef = UINT16_MAX; + pThis->paSegments[i].iGrpDef = UINT16_MAX; + + /* Translate the name and determine group and class. + Note! We currently strip sub-sections. */ + if ( strcmp(pszName, ".text") == 0 + || strncmp(pszName, RT_STR_TUPLE(".text.")) == 0) + { + pszName = "BS3TEXT64"; + pThis->paSegments[i].iGrpNm = idxGrpFlat; + pThis->paSegments[i].iClassNm = idxClassCode; + } + else if ( strcmp(pszName, ".data") == 0 + || strncmp(pszName, RT_STR_TUPLE(".data.")) == 0) + { + pszName = "BS3DATA64"; + pThis->paSegments[i].iGrpNm = idxGrpData; + pThis->paSegments[i].iClassNm = idxClassData; + } + else if (strcmp(pszName, ".bss") == 0) + { + pszName = "BS3BSS64"; + pThis->paSegments[i].iGrpNm = idxGrpData; + pThis->paSegments[i].iClassNm = idxClassData; + } + else if ( strcmp(pszName, ".rodata") == 0 + || strncmp(pszName, RT_STR_TUPLE(".rodata.")) == 0) + { + pszName = "BS3DATA64CONST"; + pThis->paSegments[i].iGrpNm = idxGrpData; + pThis->paSegments[i].iClassNm = idxClassData; + } + else if (strncmp(pszName, RT_STR_TUPLE(".debug_")) == 0) + { + pThis->paSegments[i].iGrpNm = UINT16_MAX; + pThis->paSegments[i].iClassNm = idxClassDwarf; + } + else + { + pThis->paSegments[i].iGrpNm = idxGrpData; + pThis->paSegments[i].iClassNm = idxClassData; + error(pThis->pszSrc, "Unknown data (?) segment: '%s'\n", pszName); + } + + /* Save the name. */ + pThis->paSegments[i].pszName = strdup(pszName); + if (!pThis->paSegments[i].pszName) + return error(pThis->pszSrc, "Out of memory!\n"); + + /* Add the section name. */ + if (!omfWriter_LNamesAdd(pThis, pThis->paSegments[i].pszName, &pThis->paSegments[i].iSegNm)) + return false; + + fHaveData |= pThis->paSegments[i].iGrpNm == idxGrpData; + break; + } + RT_FALL_THRU(); + + default: + pThis->paSegments[i].iSegDef = UINT16_MAX; + pThis->paSegments[i].iGrpDef = UINT16_MAX; + pThis->paSegments[i].iSegNm = UINT16_MAX; + pThis->paSegments[i].iGrpNm = UINT16_MAX; + pThis->paSegments[i].iClassNm = UINT16_MAX; + pThis->paSegments[i].pszName = NULL; + break; + } + } + + if (!omfWriter_LNamesEnd(pThis)) + return false; + + /* + * Emit segment definitions. + */ + uint16_t iSegDef = 1; /* Start counting at 1. */ + pShdr = &pElfStuff->paShdrs[1]; + for (Elf64_Half i = 1; i < cSections; i++, pShdr++) + { + if (pThis->paSegments[i].iSegNm == UINT16_MAX) + continue; + + uint8_t bSegAttr = 0; + + /* The A field. */ + switch (pShdr->sh_addralign) + { + case 0: + case 1: + bSegAttr |= 1 << 5; + break; + case 2: + bSegAttr |= 2 << 5; + break; + case 4: + bSegAttr |= 5 << 5; + break; + case 8: + case 16: + bSegAttr |= 3 << 5; + break; + case 32: + case 64: + case 128: + case 256: + bSegAttr |= 4 << 5; + break; + default: + bSegAttr |= 6 << 5; /* page aligned, pharlabs extension. */ + break; + } + + /* The C field. */ + bSegAttr |= 2 << 2; /* public */ + + /* The B field. We don't have 4GB segments, so leave it as zero. */ + + /* The D field shall be set as we're doing USE32. */ + bSegAttr |= 1; + + + /* Done. */ + if (!omfWriter_SegDef(pThis, bSegAttr, (uint32_t)pShdr->sh_size, + pThis->paSegments[i].iSegNm, + pThis->paSegments[i].iClassNm)) + return false; + pThis->paSegments[i].iSegDef = iSegDef++; + } + + /* + * Flat group definition (#1) - special, no members. + */ + uint16_t iGrpDef = 1; + if ( !omfWriter_GrpDefBegin(pThis, idxGrpFlat) + || !omfWriter_GrpDefEnd(pThis)) + return false; + for (uint16_t i = 0; i < cSections; i++) + if (pThis->paSegments[i].iGrpNm == idxGrpFlat) + pThis->paSegments[i].iGrpDef = iGrpDef; + pThis->idxGrpFlat = iGrpDef++; + + /* + * Data group definition (#2). + */ + /** @todo do we need to consider missing segments and ordering? */ + uint16_t cGrpNms = 0; + uint16_t aiGrpNms[2] = { 0, 0 }; /* Shut up, GCC. */ + if (fHaveData) + aiGrpNms[cGrpNms++] = idxGrpData; + for (uint32_t iGrpNm = 0; iGrpNm < cGrpNms; iGrpNm++) + { + if (!omfWriter_GrpDefBegin(pThis, aiGrpNms[iGrpNm])) + return false; + for (uint16_t i = 0; i < cSections; i++) + if (pThis->paSegments[i].iGrpNm == aiGrpNms[iGrpNm]) + { + pThis->paSegments[i].iGrpDef = iGrpDef; + if (!omfWriter_GrpDefAddSegDef(pThis, pThis->paSegments[i].iSegDef)) + return false; + } + if (!omfWriter_GrpDefEnd(pThis)) + return false; + iGrpDef++; + } + + return true; +} + +static bool convertElfSymbolsToPubDefsAndExtDefs(POMFWRITER pThis, PCELFDETAILS pElfStuff) +{ + if (!pElfStuff->cSymbols) + return true; + + /* + * Process the symbols the first. + */ + uint32_t cAbsSyms = 0; + uint32_t cExtSyms = 0; + uint32_t cPubSyms = 0; + for (uint32_t iSeg = 0; iSeg < pThis->cSegments; iSeg++) + pThis->paSegments[iSeg].cPubDefs = 0; + + uint32_t const cSections = pElfStuff->pEhdr->e_shnum; + uint32_t const cSymbols = pElfStuff->cSymbols; + Elf64_Sym const * const paSymbols = pElfStuff->paSymbols; + for (uint32_t iSym = 0; iSym < cSymbols; iSym++) + { + const uint8_t bBind = ELF64_ST_BIND(paSymbols[iSym].st_info); + const uint8_t bType = ELF64_ST_TYPE(paSymbols[iSym].st_info); + const char *pszSymName = &pElfStuff->pchStrTab[paSymbols[iSym].st_name]; + if ( *pszSymName == '\0' + && bType == STT_SECTION + && paSymbols[iSym].st_shndx < cSections) + pszSymName = &pElfStuff->pchShStrTab[pElfStuff->paShdrs[paSymbols[iSym].st_shndx].sh_name]; + + pThis->paSymbols[iSym].enmType = OMFSYMTYPE_IGNORED; + pThis->paSymbols[iSym].idx = UINT16_MAX; + pThis->paSymbols[iSym].idxSegDef = UINT16_MAX; + pThis->paSymbols[iSym].idxGrpDef = UINT16_MAX; + + uint32_t const idxSection = paSymbols[iSym].st_shndx; + if (idxSection == SHN_UNDEF) + { + if (bBind == STB_GLOBAL) + { + pThis->paSymbols[iSym].enmType = OMFSYMTYPE_EXTDEF; + cExtSyms++; + if (*pszSymName == '\0') + return error(pThis->pszSrc, "External symbol #%u (%s) has an empty name.\n", iSym, pszSymName); + } + else if (bBind != STB_LOCAL || iSym != 0) /* Entry zero is usually a dummy. */ + return error(pThis->pszSrc, "Unsupported or invalid bind type %#x for undefined symbol #%u (%s)\n", + bBind, iSym, pszSymName); + } + else if (idxSection < cSections) + { + pThis->paSymbols[iSym].idxSegDef = pThis->paSegments[idxSection].iSegDef; + pThis->paSymbols[iSym].idxGrpDef = pThis->paSegments[idxSection].iGrpDef; + if (bBind == STB_GLOBAL) + { + pThis->paSymbols[iSym].enmType = OMFSYMTYPE_PUBDEF; + pThis->paSegments[idxSection].cPubDefs++; + cPubSyms++; + if (bType == STT_SECTION) + return error(pThis->pszSrc, "Don't know how to export STT_SECTION symbol #%u (%s)\n", iSym, pszSymName); + if (*pszSymName == '\0') + return error(pThis->pszSrc, "Public symbol #%u (%s) has an empty name.\n", iSym, pszSymName); + } + else if (bType == STT_SECTION) + pThis->paSymbols[iSym].enmType = OMFSYMTYPE_SEGDEF; + else + pThis->paSymbols[iSym].enmType = OMFSYMTYPE_INTERNAL; + } + else if (idxSection == SHN_ABS) + { + if (bType != STT_FILE) + { + if (bBind == STB_GLOBAL) + { + pThis->paSymbols[iSym].enmType = OMFSYMTYPE_PUBDEF; + pThis->paSymbols[iSym].idxSegDef = 0; + pThis->paSymbols[iSym].idxGrpDef = 0; + cAbsSyms++; + if (*pszSymName == '\0') + return error(pThis->pszSrc, "Public absolute symbol #%u (%s) has an empty name.\n", iSym, pszSymName); + } + else + return error(pThis->pszSrc, "Unsupported or invalid bind type %#x for absolute symbol #%u (%s)\n", + bBind, iSym, pszSymName); + } + } + else if (idxSection == SHN_COMMON) + return error(pThis->pszSrc, "Symbol #%u (%s) is in the unsupported 'common' section.\n", iSym, pszSymName); + else + return error(pThis->pszSrc, "Unsupported or invalid section number %#x for symbol #%u (%s)\n", + idxSection, iSym, pszSymName); + } + + /* + * Emit the PUBDEFs the first time around (see order of records in TIS spec). + */ + uint16_t idxPubDef = 1; + if (cPubSyms) + { + for (uint32_t iSeg = 0; iSeg < pThis->cSegments; iSeg++) + if (pThis->paSegments[iSeg].cPubDefs > 0) + { + uint16_t const idxSegDef = pThis->paSegments[iSeg].iSegDef; + if (!omfWriter_PubDefBegin(pThis, pThis->paSegments[iSeg].iGrpDef, idxSegDef)) + return false; + for (uint16_t iSym = 0; iSym < cSymbols; iSym++) + if ( pThis->paSymbols[iSym].idxSegDef == idxSegDef + && pThis->paSymbols[iSym].enmType == OMFSYMTYPE_PUBDEF) + { + /* Underscore prefix all names not already underscored/mangled. */ + const char *pszName = &pElfStuff->pchStrTab[paSymbols[iSym].st_name]; + if (!omfWriter_PubDefAdd(pThis, paSymbols[iSym].st_value, pszName, pszName[0] != '_')) + return false; + pThis->paSymbols[iSym].idx = idxPubDef++; + } + if (!omfWriter_PubDefEnd(pThis)) + return false; + } + } + + if (cAbsSyms > 0) + { + if (!omfWriter_PubDefBegin(pThis, 0, 0)) + return false; + for (uint16_t iSym = 0; iSym < cSymbols; iSym++) + if ( pThis->paSymbols[iSym].idxSegDef == 0 + && pThis->paSymbols[iSym].enmType == OMFSYMTYPE_PUBDEF) + { + /* Underscore prefix all names not already underscored/mangled. */ + const char *pszName = &pElfStuff->pchStrTab[paSymbols[iSym].st_name]; + if (!omfWriter_PubDefAdd(pThis, paSymbols[iSym].st_value, pszName, pszName[0] != '_')) + return false; + pThis->paSymbols[iSym].idx = idxPubDef++; + } + if (!omfWriter_PubDefEnd(pThis)) + return false; + } + + /* + * Go over the symbol table and emit external definition records. + */ + if (!omfWriter_ExtDefBegin(pThis)) + return false; + uint16_t idxExtDef = 1; + for (uint16_t iSym = 0; iSym < cSymbols; iSym++) + if (pThis->paSymbols[iSym].enmType == OMFSYMTYPE_EXTDEF) + { + /* Underscore prefix all names not already underscored/mangled. */ + const char *pszName = &pElfStuff->pchStrTab[paSymbols[iSym].st_name]; + if (!omfWriter_ExtDefAdd(pThis, pszName, *pszName != '_')) + return false; + pThis->paSymbols[iSym].idx = idxExtDef++; + } + + if (!omfWriter_ExtDefEnd(pThis)) + return false; + + return true; +} + +/** + * @callback_method_impl{FNRTSORTCMP, For Elf64_Rela tables.} + */ +static DECLCALLBACK(int) convertElfCompareRelA(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + Elf64_Rela const *pReloc1 = (Elf64_Rela const *)pvElement1; + Elf64_Rela const *pReloc2 = (Elf64_Rela const *)pvElement2; + if (pReloc1->r_offset < pReloc2->r_offset) + return -1; + if (pReloc1->r_offset > pReloc2->r_offset) + return 1; + RT_NOREF_PV(pvUser); + return 0; +} + +static bool convertElfSectionsToLeDataAndFixupps(POMFWRITER pThis, PCELFDETAILS pElfStuff, uint8_t const *pbFile, size_t cbFile) +{ + Elf64_Sym const *paSymbols = pElfStuff->paSymbols; + Elf64_Shdr const *paShdrs = pElfStuff->paShdrs; + bool fRet = true; + RT_NOREF_PV(cbFile); + + for (uint32_t i = 1; i < pThis->cSegments; i++) + { + if (pThis->paSegments[i].iSegDef == UINT16_MAX) + continue; + + const char *pszSegNm = &pElfStuff->pchShStrTab[paShdrs[i].sh_name]; + bool const fRelocs = i + 1 < pThis->cSegments && paShdrs[i + 1].sh_type == SHT_RELA; + uint32_t cRelocs = fRelocs ? paShdrs[i + 1].sh_size / sizeof(Elf64_Rela) : 0; + Elf64_Rela const *paRelocs = fRelocs ? (Elf64_Rela *)&pbFile[paShdrs[i + 1].sh_offset] : NULL; + Elf64_Xword cbVirtData = paShdrs[i].sh_size; + Elf64_Xword cbData = paShdrs[i].sh_type == SHT_NOBITS ? 0 : cbVirtData; + uint8_t const *pbData = &pbFile[paShdrs[i].sh_offset]; + uint32_t off = 0; + + /* We sort fixups by r_offset in order to more easily split them into chunks. */ + RTSortShell((void *)paRelocs, cRelocs, sizeof(paRelocs[0]), convertElfCompareRelA, NULL); + + /* The OMF record size requires us to split larger sections up. To make + life simple, we fill zeros for unitialized (BSS) stuff. */ + const uint32_t cbMaxData = RT_MIN(OMF_MAX_RECORD_PAYLOAD - 1 - (pThis->paSegments[i].iSegDef >= 128) - 4 - 1, _1K); + while (cbVirtData > 0) + { + /* Figure out how many bytes to put out in this chunk. Must make sure + fixups doesn't cross chunk boundraries. ASSUMES sorted relocs. */ + uint32_t cChunkRelocs = cRelocs; + uint32_t cbChunk = cbVirtData; + uint32_t offEnd = off + cbChunk; + if (cbChunk > cbMaxData) + { + cbChunk = cbMaxData; + offEnd = off + cbChunk; + cChunkRelocs = 0; + + /* Quickly determin the reloc range. */ + while ( cChunkRelocs < cRelocs + && paRelocs[cChunkRelocs].r_offset < offEnd) + cChunkRelocs++; + + /* Ensure final reloc doesn't go beyond chunk. */ + while ( cChunkRelocs > 0 + && paRelocs[cChunkRelocs - 1].r_offset + + ELF_AMD64_RELOC_SIZE(ELF64_R_TYPE(paRelocs[cChunkRelocs - 1].r_info)) + > offEnd) + { + uint32_t cbDrop = offEnd - paRelocs[cChunkRelocs - 1].r_offset; + cbChunk -= cbDrop; + offEnd -= cbDrop; + cChunkRelocs--; + } + + if (!cbVirtData) + return error(pThis->pszSrc, "Wtf? cbVirtData is zero!\n"); + } + if (g_cVerbose >= 2) + printf("debug: LEDATA off=%#x cb=%#x cRelocs=%#x sect=#%u segdef=%#x grpdef=%#x '%s'\n", + off, cbChunk, cRelocs, i, pThis->paSegments[i].iSegDef, pThis->paSegments[i].iGrpDef, pszSegNm); + + /* + * We stash the bytes into the OMF writer record buffer, receiving a + * pointer to the start of it so we can make adjustments if necessary. + */ + uint8_t *pbCopy; + if (!omfWriter_LEDataBeginEx(pThis, pThis->paSegments[i].iSegDef, off, cbChunk, cbData, pbData, &pbCopy)) + return false; + + /* + * Convert fiuxps. + */ + for (uint32_t iReloc = 0; iReloc < cChunkRelocs; iReloc++) + { + /* Get the OMF and ELF data for the symbol the reloc references. */ + uint32_t const uType = ELF64_R_TYPE(paRelocs[iReloc].r_info); + uint32_t const iSymbol = ELF64_R_SYM(paRelocs[iReloc].r_info); + Elf64_Sym const * const pElfSym = &paSymbols[iSymbol]; + POMFSYMBOL const pOmfSym = &pThis->paSymbols[iSymbol]; + const char * const pszSymName = &pElfStuff->pchStrTab[pElfSym->st_name]; + + /* Calc fixup location in the pending chunk and setup a flexible pointer to it. */ + uint16_t offDataRec = (uint16_t)(paRelocs[iReloc].r_offset - off); + RTPTRUNION uLoc; + uLoc.pu8 = &pbCopy[offDataRec]; + + /* OMF fixup data initialized with typical defaults. */ + bool fSelfRel = true; + uint8_t bLocation = OMF_FIX_LOC_32BIT_OFFSET; + uint8_t bFrame = OMF_FIX_F_GRPDEF; + uint16_t idxFrame = pThis->idxGrpFlat; + uint8_t bTarget; + uint16_t idxTarget; + bool fTargetDisp; + uint32_t offTargetDisp; + switch (pOmfSym->enmType) + { + case OMFSYMTYPE_INTERNAL: + case OMFSYMTYPE_PUBDEF: + bTarget = OMF_FIX_T_SEGDEF; + idxTarget = pOmfSym->idxSegDef; + fTargetDisp = true; + offTargetDisp = pElfSym->st_value; + break; + + case OMFSYMTYPE_SEGDEF: + bTarget = OMF_FIX_T_SEGDEF_NO_DISP; + idxTarget = pOmfSym->idxSegDef; + fTargetDisp = false; + offTargetDisp = 0; + break; + + case OMFSYMTYPE_EXTDEF: + bTarget = OMF_FIX_T_EXTDEF_NO_DISP; + idxTarget = pOmfSym->idx; + fTargetDisp = false; + offTargetDisp = 0; + break; + + default: + return error(pThis->pszSrc, "Relocation in segment #%u '%s' references ignored or invalid symbol (%s)\n", + i, pszSegNm, pszSymName); + } + + /* Do COFF relocation type conversion. */ + switch (uType) + { + case R_X86_64_64: + { + int64_t iAddend = paRelocs[iReloc].r_addend; + if (iAddend > _1G || iAddend < -_1G) + fRet = error(pThis->pszSrc, "R_X86_64_64 with large addend (%" ELF_FMT_D64 ") at %#x in segment #%u '%s'\n", + iAddend, paRelocs[iReloc].r_offset, i, pszSegNm); + *uLoc.pu64 = iAddend; + fSelfRel = false; + break; + } + + case R_X86_64_32: + case R_X86_64_32S: /* signed, unsigned, whatever. */ + fSelfRel = false; + RT_FALL_THRU(); + case R_X86_64_PC32: + case R_X86_64_PLT32: /* binutils commit 451875b4f976a527395e9303224c7881b65e12ed feature/regression. */ + { + /* defaults are ok, just handle the addend. */ + int32_t iAddend = paRelocs[iReloc].r_addend; + if (iAddend != paRelocs[iReloc].r_addend) + fRet = error(pThis->pszSrc, "R_X86_64_PC32 with large addend (%d) at %#x in segment #%u '%s'\n", + iAddend, paRelocs[iReloc].r_offset, i, pszSegNm); + if (fSelfRel) + *uLoc.pu32 = iAddend + 4; + else + *uLoc.pu32 = iAddend; + break; + } + + case R_X86_64_NONE: + continue; /* Ignore this one */ + + case R_X86_64_GOT32: + case R_X86_64_COPY: + case R_X86_64_GLOB_DAT: + case R_X86_64_JMP_SLOT: + case R_X86_64_RELATIVE: + case R_X86_64_GOTPCREL: + case R_X86_64_16: + case R_X86_64_PC16: + case R_X86_64_8: + case R_X86_64_PC8: + case R_X86_64_DTPMOD64: + case R_X86_64_DTPOFF64: + case R_X86_64_TPOFF64: + case R_X86_64_TLSGD: + case R_X86_64_TLSLD: + case R_X86_64_DTPOFF32: + case R_X86_64_GOTTPOFF: + case R_X86_64_TPOFF32: + default: + return error(pThis->pszSrc, "Unsupported fixup type %#x (%s) at rva=%#x in section #%u '%s' against '%s'\n", + uType, g_apszElfAmd64RelTypes[uType], paRelocs[iReloc].r_offset, i, pszSegNm, pszSymName); + } + + /* Add the fixup. */ + if (idxFrame == UINT16_MAX) + error(pThis->pszSrc, "idxFrame=UINT16_MAX for %s type=%s\n", pszSymName, g_apszElfAmd64RelTypes[uType]); + fRet = omfWriter_LEDataAddFixup(pThis, offDataRec, fSelfRel, bLocation, bFrame, idxFrame, + bTarget, idxTarget, fTargetDisp, offTargetDisp) && fRet; + } + + /* + * Write the LEDATA and associated FIXUPPs. + */ + if (!omfWriter_LEDataEnd(pThis)) + return false; + + /* + * Advance. + */ + paRelocs += cChunkRelocs; + cRelocs -= cChunkRelocs; + if (cbData > cbChunk) + { + cbData -= cbChunk; + pbData += cbChunk; + } + else + cbData = 0; + off += cbChunk; + cbVirtData -= cbChunk; + } + } + + return fRet; +} + + +static bool convertElfToOmf(const char *pszFile, uint8_t const *pbFile, size_t cbFile, FILE *pDst) +{ + /* + * Validate the source file a little. + */ + ELFDETAILS ElfStuff; + if (!validateElf(pszFile, pbFile, cbFile, &ElfStuff)) + return false; + + /* + * Instantiate the OMF writer. + */ + POMFWRITER pThis = omfWriter_Create(pszFile, ElfStuff.pEhdr->e_shnum, ElfStuff.cSymbols, pDst); + if (!pThis) + return false; + + /* + * Write the OMF object file. + */ + if (omfWriter_BeginModule(pThis, pszFile)) + { + if ( convertElfSectionsToSegDefsAndGrpDefs(pThis, &ElfStuff) + && convertElfSymbolsToPubDefsAndExtDefs(pThis, &ElfStuff) + && omfWriter_LinkPassSeparator(pThis) + && convertElfSectionsToLeDataAndFixupps(pThis, &ElfStuff, pbFile, cbFile) + && omfWriter_EndModule(pThis) ) + { + + omfWriter_Destroy(pThis); + return true; + } + } + + omfWriter_Destroy(pThis); + return false; +} + + + +/********************************************************************************************************************************* +* COFF -> OMF Converter * +*********************************************************************************************************************************/ + +/** AMD64 relocation type names for (Microsoft) COFF. */ +static const char * const g_apszCoffAmd64RelTypes[] = +{ + "ABSOLUTE", + "ADDR64", + "ADDR32", + "ADDR32NB", + "REL32", + "REL32_1", + "REL32_2", + "REL32_3", + "REL32_4", + "REL32_5", + "SECTION", + "SECREL", + "SECREL7", + "TOKEN", + "SREL32", + "PAIR", + "SSPAN32" +}; + +/** AMD64 relocation type sizes for (Microsoft) COFF. */ +static uint8_t const g_acbCoffAmd64RelTypes[] = +{ + 8, /* ABSOLUTE */ + 8, /* ADDR64 */ + 4, /* ADDR32 */ + 4, /* ADDR32NB */ + 4, /* REL32 */ + 4, /* REL32_1 */ + 4, /* REL32_2 */ + 4, /* REL32_3 */ + 4, /* REL32_4 */ + 4, /* REL32_5 */ + 2, /* SECTION */ + 4, /* SECREL */ + 1, /* SECREL7 */ + 0, /* TOKEN */ + 4, /* SREL32 */ + 0, /* PAIR */ + 4, /* SSPAN32 */ +}; + +/** Macro for getting the size of a AMD64 COFF relocation. */ +#define COFF_AMD64_RELOC_SIZE(a_Type) ( (a_Type) < RT_ELEMENTS(g_acbCoffAmd64RelTypes) ? g_acbCoffAmd64RelTypes[(a_Type)] : 1) + + +static const char *coffGetSymbolName(PCIMAGE_SYMBOL pSym, const char *pchStrTab, uint32_t cbStrTab, char pszShortName[16]) +{ + if (pSym->N.Name.Short != 0) + { + memcpy(pszShortName, pSym->N.ShortName, 8); + pszShortName[8] = '\0'; + return pszShortName; + } + if (pSym->N.Name.Long < cbStrTab) + { + uint32_t const cbLeft = cbStrTab - pSym->N.Name.Long; + const char *pszRet = pchStrTab + pSym->N.Name.Long; + if (memchr(pszRet, '\0', cbLeft) != NULL) + return pszRet; + } + error("<null>", "Invalid string table index %#x!\n", pSym->N.Name.Long); + return "Invalid Symbol Table Entry"; +} + +static bool validateCoff(const char *pszFile, uint8_t const *pbFile, size_t cbFile) +{ + /* + * Validate the header and our other expectations. + */ + PIMAGE_FILE_HEADER pHdr = (PIMAGE_FILE_HEADER)pbFile; + if (pHdr->Machine != IMAGE_FILE_MACHINE_AMD64) + return error(pszFile, "Expected IMAGE_FILE_MACHINE_AMD64 not %#x\n", pHdr->Machine); + if (pHdr->SizeOfOptionalHeader != 0) + return error(pszFile, "Expected SizeOfOptionalHeader to be zero, not %#x\n", pHdr->SizeOfOptionalHeader); + if (pHdr->NumberOfSections == 0) + return error(pszFile, "Expected NumberOfSections to be non-zero\n"); + uint32_t const cbHeaders = pHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER) + sizeof(*pHdr); + if (cbHeaders > cbFile) + return error(pszFile, "Section table goes beyond the end of the of the file (cSections=%#x)\n", pHdr->NumberOfSections); + if (pHdr->NumberOfSymbols) + { + if ( pHdr->PointerToSymbolTable >= cbFile + || pHdr->NumberOfSymbols * (uint64_t)IMAGE_SIZE_OF_SYMBOL > cbFile) + return error(pszFile, "Symbol table goes beyond the end of the of the file (cSyms=%#x, offFile=%#x)\n", + pHdr->NumberOfSymbols, pHdr->PointerToSymbolTable); + } + + return true; +} + + +static bool convertCoffSectionsToSegDefsAndGrpDefs(POMFWRITER pThis, PCIMAGE_SECTION_HEADER paShdrs, uint16_t cSections) +{ + /* + * Do the list of names pass. + */ + uint16_t idxGrpFlat, idxGrpData; + uint16_t idxClassCode, idxClassData, idxClassDebugSymbols, idxClassDebugTypes; + if ( !omfWriter_LNamesBegin(pThis, true /*fAddZeroEntry*/) + || !omfWriter_LNamesAddN(pThis, RT_STR_TUPLE("FLAT"), &idxGrpFlat) + || !omfWriter_LNamesAddN(pThis, RT_STR_TUPLE("BS3DATA64_GROUP"), &idxGrpData) + || !omfWriter_LNamesAddN(pThis, RT_STR_TUPLE("BS3CLASS64CODE"), &idxClassCode) + || !omfWriter_LNamesAddN(pThis, RT_STR_TUPLE("FAR_DATA"), &idxClassData) + || !omfWriter_LNamesAddN(pThis, RT_STR_TUPLE("DEBSYM"), &idxClassDebugSymbols) + || !omfWriter_LNamesAddN(pThis, RT_STR_TUPLE("DEBTYP"), &idxClassDebugTypes) + ) + return false; + + bool fHaveData = false; + for (uint16_t i = 0; i < cSections; i++) + { + /* Copy the name and terminate it. */ + char szName[32]; + memcpy(szName, paShdrs[i].Name, sizeof(paShdrs[i].Name)); + unsigned cchName = sizeof(paShdrs[i].Name); + while (cchName > 0 && RT_C_IS_SPACE(szName[cchName - 1])) + cchName--; + if (cchName == 0) + return error(pThis->pszSrc, "Section #%u has an empty name!\n", i); + szName[cchName] = '\0'; + + if ( (paShdrs[i].Characteristics & (IMAGE_SCN_LNK_REMOVE | IMAGE_SCN_LNK_INFO)) + || strcmp(szName, ".pdata") == 0 /* Exception stuff, I think, so discard it. */ + || strcmp(szName, ".xdata") == 0 /* Ditto. */ ) + { + pThis->paSegments[i].iSegDef = UINT16_MAX; + pThis->paSegments[i].iGrpDef = UINT16_MAX; + pThis->paSegments[i].iSegNm = UINT16_MAX; + pThis->paSegments[i].iGrpNm = UINT16_MAX; + pThis->paSegments[i].iClassNm = UINT16_MAX; + pThis->paSegments[i].pszName = NULL; + } + else + { + /* Translate the name, group and class. */ + if ( strcmp(szName, ".text") == 0 + || strcmp(szName, ".text$mn") == 0 /* Seen first in VC++ 14.1 (could be older). */) + { + strcpy(szName, "BS3TEXT64"); + pThis->paSegments[i].iGrpNm = idxGrpFlat; + pThis->paSegments[i].iClassNm = idxClassCode; + } + else if (strcmp(szName, ".data") == 0) + { + strcpy(szName, "BS3DATA64"); + pThis->paSegments[i].iGrpNm = idxGrpData; + pThis->paSegments[i].iClassNm = idxClassData; + } + else if (strcmp(szName, ".bss") == 0) + { + strcpy(szName, "BS3BSS64"); + pThis->paSegments[i].iGrpNm = idxGrpData; + pThis->paSegments[i].iClassNm = idxClassData; + } + else if (strcmp(szName, ".rdata") == 0) + { + strcpy(szName, "BS3DATA64CONST"); + pThis->paSegments[i].iGrpNm = idxGrpData; + pThis->paSegments[i].iClassNm = idxClassData; + } + else if (strcmp(szName, ".debug$S") == 0) + { + strcpy(szName, "$$SYMBOLS"); + pThis->paSegments[i].iGrpNm = UINT16_MAX; + pThis->paSegments[i].iClassNm = idxClassDebugSymbols; + } + else if (strcmp(szName, ".debug$T") == 0) + { + strcpy(szName, "$$TYPES"); + pThis->paSegments[i].iGrpNm = UINT16_MAX; + pThis->paSegments[i].iClassNm = idxClassDebugTypes; + } + else if (paShdrs[i].Characteristics & (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE)) + { + pThis->paSegments[i].iGrpNm = idxGrpFlat; + pThis->paSegments[i].iClassNm = idxClassCode; + error(pThis->pszSrc, "Unknown code segment: '%s'\n", szName); + } + else + { + pThis->paSegments[i].iGrpNm = idxGrpData; + pThis->paSegments[i].iClassNm = idxClassData; + error(pThis->pszSrc, "Unknown data (?) segment: '%s'\n", szName); + } + + /* Save the name. */ + pThis->paSegments[i].pszName = strdup(szName); + if (!pThis->paSegments[i].pszName) + return error(pThis->pszSrc, "Out of memory!\n"); + + /* Add the section name. */ + if (!omfWriter_LNamesAdd(pThis, pThis->paSegments[i].pszName, &pThis->paSegments[i].iSegNm)) + return false; + + fHaveData |= pThis->paSegments[i].iGrpNm == idxGrpData; + } + } + + if (!omfWriter_LNamesEnd(pThis)) + return false; + + /* + * Emit segment definitions. + */ + uint16_t iSegDef = 1; /* Start counting at 1. */ + for (uint16_t i = 0; i < cSections; i++) + { + if (pThis->paSegments[i].iSegDef == UINT16_MAX) + continue; + + uint8_t bSegAttr = 0; + + /* The A field. */ + switch (paShdrs[i].Characteristics & IMAGE_SCN_ALIGN_MASK) + { + default: + case IMAGE_SCN_ALIGN_1BYTES: + bSegAttr |= 1 << 5; + break; + case IMAGE_SCN_ALIGN_2BYTES: + bSegAttr |= 2 << 5; + break; + case IMAGE_SCN_ALIGN_4BYTES: + bSegAttr |= 5 << 5; + break; + case IMAGE_SCN_ALIGN_8BYTES: + case IMAGE_SCN_ALIGN_16BYTES: + bSegAttr |= 3 << 5; + break; + case IMAGE_SCN_ALIGN_32BYTES: + case IMAGE_SCN_ALIGN_64BYTES: + case IMAGE_SCN_ALIGN_128BYTES: + case IMAGE_SCN_ALIGN_256BYTES: + bSegAttr |= 4 << 5; + break; + case IMAGE_SCN_ALIGN_512BYTES: + case IMAGE_SCN_ALIGN_1024BYTES: + case IMAGE_SCN_ALIGN_2048BYTES: + case IMAGE_SCN_ALIGN_4096BYTES: + case IMAGE_SCN_ALIGN_8192BYTES: + bSegAttr |= 6 << 5; /* page aligned, pharlabs extension. */ + break; + } + + /* The C field. */ + bSegAttr |= 2 << 2; /* public */ + + /* The B field. We don't have 4GB segments, so leave it as zero. */ + + /* The D field shall be set as we're doing USE32. */ + bSegAttr |= 1; + + + /* Done. */ + if (!omfWriter_SegDef(pThis, bSegAttr, paShdrs[i].SizeOfRawData, + pThis->paSegments[i].iSegNm, + pThis->paSegments[i].iClassNm)) + return false; + pThis->paSegments[i].iSegDef = iSegDef++; + } + + /* + * Flat group definition (#1) - special, no members. + */ + uint16_t iGrpDef = 1; + if ( !omfWriter_GrpDefBegin(pThis, idxGrpFlat) + || !omfWriter_GrpDefEnd(pThis)) + return false; + for (uint16_t i = 0; i < cSections; i++) + if (pThis->paSegments[i].iGrpNm == idxGrpFlat) + pThis->paSegments[i].iGrpDef = iGrpDef; + pThis->idxGrpFlat = iGrpDef++; + + /* + * Data group definition (#2). + */ + /** @todo do we need to consider missing segments and ordering? */ + uint16_t cGrpNms = 0; + uint16_t aiGrpNms[2] = { 0, 0 }; /* Shut up, GCC. */ + if (fHaveData) + aiGrpNms[cGrpNms++] = idxGrpData; + for (uint32_t iGrpNm = 0; iGrpNm < cGrpNms; iGrpNm++) + { + if (!omfWriter_GrpDefBegin(pThis, aiGrpNms[iGrpNm])) + return false; + for (uint16_t i = 0; i < cSections; i++) + if (pThis->paSegments[i].iGrpNm == aiGrpNms[iGrpNm]) + { + pThis->paSegments[i].iGrpDef = iGrpDef; + if (!omfWriter_GrpDefAddSegDef(pThis, pThis->paSegments[i].iSegDef)) + return false; + } + if (!omfWriter_GrpDefEnd(pThis)) + return false; + iGrpDef++; + } + + return true; +} + +/** + * This is for matching STATIC symbols with value 0 against the section name, + * to see if it's a section reference or symbol at offset 0 reference. + * + * @returns true / false. + * @param pszSymbol The symbol name. + * @param pachSectName8 The section name (8-bytes). + */ +static bool isCoffSymbolMatchingSectionName(const char *pszSymbol, uint8_t const pachSectName8[8]) +{ + uint32_t off = 0; + char ch; + while (off < 8 && (ch = pszSymbol[off]) != '\0') + { + if (ch != pachSectName8[off]) + return false; + off++; + } + while (off < 8) + { + if (!RT_C_IS_SPACE((ch = pachSectName8[off]))) + return ch == '\0'; + off++; + } + return true; +} + +static bool convertCoffSymbolsToPubDefsAndExtDefs(POMFWRITER pThis, PCIMAGE_SYMBOL paSymbols, uint16_t cSymbols, + const char *pchStrTab, PCIMAGE_SECTION_HEADER paShdrs) +{ + + if (!cSymbols) + return true; + uint32_t const cbStrTab = *(uint32_t const *)pchStrTab; + char szShort[16]; + + /* + * Process the symbols the first. + */ + uint32_t iSymImageBase = UINT32_MAX; + uint32_t cAbsSyms = 0; + uint32_t cExtSyms = 0; + uint32_t cPubSyms = 0; + for (uint32_t iSeg = 0; iSeg < pThis->cSegments; iSeg++) + pThis->paSegments[iSeg].cPubDefs = 0; + + for (uint16_t iSym = 0; iSym < cSymbols; iSym++) + { + const char *pszSymName = coffGetSymbolName(&paSymbols[iSym], pchStrTab, cbStrTab, szShort); + + pThis->paSymbols[iSym].enmType = OMFSYMTYPE_IGNORED; + pThis->paSymbols[iSym].idx = UINT16_MAX; + pThis->paSymbols[iSym].idxSegDef = UINT16_MAX; + pThis->paSymbols[iSym].idxGrpDef = UINT16_MAX; + + int16_t const idxSection = paSymbols[iSym].SectionNumber; + if ( (idxSection >= 1 && idxSection <= (int32_t)pThis->cSegments) + || idxSection == IMAGE_SYM_ABSOLUTE) + { + switch (paSymbols[iSym].StorageClass) + { + case IMAGE_SYM_CLASS_EXTERNAL: + if (idxSection != IMAGE_SYM_ABSOLUTE) + { + if (pThis->paSegments[idxSection - 1].iSegDef != UINT16_MAX) + { + pThis->paSymbols[iSym].enmType = OMFSYMTYPE_PUBDEF; + pThis->paSymbols[iSym].idxSegDef = pThis->paSegments[idxSection - 1].iSegDef; + pThis->paSymbols[iSym].idxGrpDef = pThis->paSegments[idxSection - 1].iGrpDef; + pThis->paSegments[idxSection - 1].cPubDefs++; + cPubSyms++; + } + } + else + { + pThis->paSymbols[iSym].enmType = OMFSYMTYPE_PUBDEF; + pThis->paSymbols[iSym].idxSegDef = 0; + pThis->paSymbols[iSym].idxGrpDef = 0; + cAbsSyms++; + } + break; + + case IMAGE_SYM_CLASS_STATIC: + if ( paSymbols[iSym].Value == 0 + && idxSection != IMAGE_SYM_ABSOLUTE + && isCoffSymbolMatchingSectionName(pszSymName, paShdrs[idxSection - 1].Name) ) + { + pThis->paSymbols[iSym].enmType = OMFSYMTYPE_SEGDEF; + pThis->paSymbols[iSym].idxSegDef = pThis->paSegments[idxSection - 1].iSegDef; + pThis->paSymbols[iSym].idxGrpDef = pThis->paSegments[idxSection - 1].iGrpDef; + break; + } + RT_FALL_THRU(); + + case IMAGE_SYM_CLASS_END_OF_FUNCTION: + case IMAGE_SYM_CLASS_AUTOMATIC: + case IMAGE_SYM_CLASS_REGISTER: + case IMAGE_SYM_CLASS_LABEL: + case IMAGE_SYM_CLASS_MEMBER_OF_STRUCT: + case IMAGE_SYM_CLASS_ARGUMENT: + case IMAGE_SYM_CLASS_STRUCT_TAG: + case IMAGE_SYM_CLASS_MEMBER_OF_UNION: + case IMAGE_SYM_CLASS_UNION_TAG: + case IMAGE_SYM_CLASS_TYPE_DEFINITION: + case IMAGE_SYM_CLASS_ENUM_TAG: + case IMAGE_SYM_CLASS_MEMBER_OF_ENUM: + case IMAGE_SYM_CLASS_REGISTER_PARAM: + case IMAGE_SYM_CLASS_BIT_FIELD: + case IMAGE_SYM_CLASS_BLOCK: + case IMAGE_SYM_CLASS_FUNCTION: + case IMAGE_SYM_CLASS_END_OF_STRUCT: + case IMAGE_SYM_CLASS_FILE: + pThis->paSymbols[iSym].enmType = OMFSYMTYPE_INTERNAL; + if (idxSection != IMAGE_SYM_ABSOLUTE) + { + pThis->paSymbols[iSym].idxSegDef = pThis->paSegments[idxSection - 1].iSegDef; + pThis->paSymbols[iSym].idxGrpDef = pThis->paSegments[idxSection - 1].iGrpDef; + } + else + { + pThis->paSymbols[iSym].idxSegDef = 0; + pThis->paSymbols[iSym].idxGrpDef = 0; + } + break; + + case IMAGE_SYM_CLASS_SECTION: + case IMAGE_SYM_CLASS_EXTERNAL_DEF: + case IMAGE_SYM_CLASS_NULL: + case IMAGE_SYM_CLASS_UNDEFINED_LABEL: + case IMAGE_SYM_CLASS_UNDEFINED_STATIC: + case IMAGE_SYM_CLASS_CLR_TOKEN: + case IMAGE_SYM_CLASS_FAR_EXTERNAL: + case IMAGE_SYM_CLASS_WEAK_EXTERNAL: + return error(pThis->pszSrc, "Unsupported storage class value %#x for symbol #%u (%s)\n", + paSymbols[iSym].StorageClass, iSym, pszSymName); + + default: + return error(pThis->pszSrc, "Unknown storage class value %#x for symbol #%u (%s)\n", + paSymbols[iSym].StorageClass, iSym, pszSymName); + } + } + else if (idxSection == IMAGE_SYM_UNDEFINED) + { + if ( paSymbols[iSym].StorageClass == IMAGE_SYM_CLASS_EXTERNAL + || paSymbols[iSym].StorageClass == IMAGE_SYM_CLASS_EXTERNAL_DEF) + { + pThis->paSymbols[iSym].enmType = OMFSYMTYPE_EXTDEF; + cExtSyms++; + if (iSymImageBase == UINT32_MAX && strcmp(pszSymName, "__ImageBase") == 0) + iSymImageBase = iSym; + } + else + return error(pThis->pszSrc, "Unknown/unknown storage class value %#x for undefined symbol #%u (%s)\n", + paSymbols[iSym].StorageClass, iSym, pszSymName); + } + else if (idxSection != IMAGE_SYM_DEBUG) + return error(pThis->pszSrc, "Invalid section number %#x for symbol #%u (%s)\n", idxSection, iSym, pszSymName); + + /* Skip AUX symbols. */ + uint8_t cAuxSyms = paSymbols[iSym].NumberOfAuxSymbols; + while (cAuxSyms-- > 0) + { + iSym++; + pThis->paSymbols[iSym].enmType = OMFSYMTYPE_INVALID; + pThis->paSymbols[iSym].idx = UINT16_MAX; + } + } + + /* + * Emit the PUBDEFs the first time around (see order of records in TIS spec). + */ + uint16_t idxPubDef = 1; + if (cPubSyms) + { + for (uint32_t iSeg = 0; iSeg < pThis->cSegments; iSeg++) + if (pThis->paSegments[iSeg].cPubDefs > 0) + { + uint16_t const idxSegDef = pThis->paSegments[iSeg].iSegDef; + if (!omfWriter_PubDefBegin(pThis, pThis->paSegments[iSeg].iGrpDef, idxSegDef)) + return false; + for (uint16_t iSym = 0; iSym < cSymbols; iSym++) + if ( pThis->paSymbols[iSym].idxSegDef == idxSegDef + && pThis->paSymbols[iSym].enmType == OMFSYMTYPE_PUBDEF) + { + /* Underscore prefix all symbols not already underscored or mangled. */ + const char *pszName = coffGetSymbolName(&paSymbols[iSym], pchStrTab, cbStrTab, szShort); + if (!omfWriter_PubDefAdd(pThis, paSymbols[iSym].Value, pszName, pszName[0] != '_' && pszName[0] != '?')) + return false; + pThis->paSymbols[iSym].idx = idxPubDef++; + } + if (!omfWriter_PubDefEnd(pThis)) + return false; + } + } + + if (cAbsSyms > 0) + { + if (!omfWriter_PubDefBegin(pThis, 0, 0)) + return false; + for (uint16_t iSym = 0; iSym < cSymbols; iSym++) + if ( pThis->paSymbols[iSym].idxSegDef == 0 + && pThis->paSymbols[iSym].enmType == OMFSYMTYPE_PUBDEF) + { + /* Underscore prefix all symbols not already underscored or mangled. */ + const char *pszName = coffGetSymbolName(&paSymbols[iSym], pchStrTab, cbStrTab, szShort); + if (!omfWriter_PubDefAdd(pThis, paSymbols[iSym].Value, pszName, pszName[0] != '_' && pszName[0] != '?') ) + return false; + pThis->paSymbols[iSym].idx = idxPubDef++; + } + if (!omfWriter_PubDefEnd(pThis)) + return false; + } + + /* + * Go over the symbol table and emit external definition records. + */ + if (!omfWriter_ExtDefBegin(pThis)) + return false; + uint16_t idxExtDef = 1; + for (uint16_t iSym = 0; iSym < cSymbols; iSym++) + if (pThis->paSymbols[iSym].enmType == OMFSYMTYPE_EXTDEF) + { + /* Underscore prefix all symbols not already underscored or mangled. */ + const char *pszName = coffGetSymbolName(&paSymbols[iSym], pchStrTab, cbStrTab, szShort); + if (!omfWriter_ExtDefAdd(pThis, pszName, pszName[0] != '_' && pszName[0] != '?')) + return false; + pThis->paSymbols[iSym].idx = idxExtDef++; + } + + /* Always add an __ImageBase reference, in case we need it to deal with ADDR32NB fixups. */ + /** @todo maybe we don't actually need this and could use FLAT instead? */ + if (iSymImageBase != UINT32_MAX) + pThis->idxExtImageBase = pThis->paSymbols[iSymImageBase].idx; + else if (omfWriter_ExtDefAdd(pThis, "__ImageBase", false /*fPrependUnderscore*/)) + pThis->idxExtImageBase = idxExtDef; + else + return false; + + if (!omfWriter_ExtDefEnd(pThis)) + return false; + + return true; +} + + +static bool convertCoffSectionsToLeDataAndFixupps(POMFWRITER pThis, uint8_t const *pbFile, size_t cbFile, + PCIMAGE_SECTION_HEADER paShdrs, uint16_t cSections, + PCIMAGE_SYMBOL paSymbols, uint16_t cSymbols, const char *pchStrTab) +{ + RT_NOREF_PV(cbFile); + RT_NOREF_PV(cSections); + RT_NOREF_PV(cSymbols); + + uint32_t const cbStrTab = *(uint32_t const *)pchStrTab; + bool fRet = true; + for (uint32_t i = 0; i < pThis->cSegments; i++) + { + if (pThis->paSegments[i].iSegDef == UINT16_MAX) + continue; + + char szShortName[16]; + const char *pszSegNm = pThis->paSegments[i].pszName; + uint16_t cRelocs = paShdrs[i].NumberOfRelocations; + PCIMAGE_RELOCATION paRelocs = (PCIMAGE_RELOCATION)&pbFile[paShdrs[i].PointerToRelocations]; + uint32_t cbVirtData = paShdrs[i].SizeOfRawData; + uint32_t cbData = paShdrs[i].Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA ? 0 : cbVirtData; + uint8_t const *pbData = &pbFile[paShdrs[i].PointerToRawData]; + uint32_t off = 0; + + /* Check that the relocations are sorted and within the section. */ + for (uint32_t iReloc = 1; iReloc < cRelocs; iReloc++) + if (paRelocs[iReloc - 1].u.VirtualAddress >= paRelocs[iReloc].u.VirtualAddress) + return error(pThis->pszSrc, "Section #%u (%s) relocations aren't sorted\n", i, pszSegNm); + if ( cRelocs > 0 + && paRelocs[cRelocs - 1].u.VirtualAddress - paShdrs[i].VirtualAddress + + COFF_AMD64_RELOC_SIZE(paRelocs[cRelocs - 1].Type) > cbVirtData) + return error(pThis->pszSrc, + "Section #%u (%s) relocations beyond section data! cbVirtData=%#x RvaFix=%#x RVASeg=%#x type=%#x\n", + i, pszSegNm, cbVirtData, paRelocs[cRelocs - 1].u.VirtualAddress, paShdrs[i].VirtualAddress, + paRelocs[cRelocs - 1].Type); + + /* The OMF record size requires us to split larger sections up. To make + life simple, we fill zeros for unitialized (BSS) stuff. */ + const uint32_t cbMaxData = RT_MIN(OMF_MAX_RECORD_PAYLOAD - 1 - (pThis->paSegments[i].iSegDef >= 128) - 4 - 1, _1K); + while (cbVirtData > 0) + { + /* Figure out how many bytes to put out in this chunk. Must make sure + fixups doesn't cross chunk boundraries. ASSUMES sorted relocs. */ + uint32_t cChunkRelocs = cRelocs; + uint32_t cbChunk = cbVirtData; + uint32_t uRvaEnd = paShdrs[i].VirtualAddress + off + cbChunk; + if (cbChunk > cbMaxData) + { + cbChunk = cbMaxData; + uRvaEnd = paShdrs[i].VirtualAddress + off + cbChunk; + cChunkRelocs = 0; + + /* Quickly determin the reloc range. */ + while ( cChunkRelocs < cRelocs + && paRelocs[cChunkRelocs].u.VirtualAddress < uRvaEnd) + cChunkRelocs++; + + /* Ensure final reloc doesn't go beyond chunk. */ + while ( cChunkRelocs > 0 + && paRelocs[cChunkRelocs - 1].u.VirtualAddress + COFF_AMD64_RELOC_SIZE(paRelocs[cChunkRelocs - 1].Type) + > uRvaEnd) + { + uint32_t cbDrop = uRvaEnd - paRelocs[cChunkRelocs - 1].u.VirtualAddress; + cbChunk -= cbDrop; + uRvaEnd -= cbDrop; + cChunkRelocs--; + } + + if (!cbVirtData) + return error(pThis->pszSrc, "Wtf? cbVirtData is zero!\n"); + } + + /* + * We stash the bytes into the OMF writer record buffer, receiving a + * pointer to the start of it so we can make adjustments if necessary. + */ + uint8_t *pbCopy; + if (!omfWriter_LEDataBeginEx(pThis, pThis->paSegments[i].iSegDef, off, cbChunk, cbData, pbData, &pbCopy)) + return false; + + /* + * Convert fiuxps. + */ + uint32_t const uRvaChunk = paShdrs[i].VirtualAddress + off; + for (uint32_t iReloc = 0; iReloc < cChunkRelocs; iReloc++) + { + /* Get the OMF and COFF data for the symbol the reloc references. */ + if (paRelocs[iReloc].SymbolTableIndex >= pThis->cSymbols) + return error(pThis->pszSrc, "Relocation symtab index (%#x) is out of range in segment #%u '%s'\n", + paRelocs[iReloc].SymbolTableIndex, i, pszSegNm); + PCIMAGE_SYMBOL pCoffSym = &paSymbols[paRelocs[iReloc].SymbolTableIndex]; + POMFSYMBOL pOmfSym = &pThis->paSymbols[paRelocs[iReloc].SymbolTableIndex]; + + /* Calc fixup location in the pending chunk and setup a flexible pointer to it. */ + uint16_t offDataRec = (uint16_t)(paRelocs[iReloc].u.VirtualAddress - uRvaChunk); + RTPTRUNION uLoc; + uLoc.pu8 = &pbCopy[offDataRec]; + + /* OMF fixup data initialized with typical defaults. */ + bool fSelfRel = true; + uint8_t bLocation = OMF_FIX_LOC_32BIT_OFFSET; + uint8_t bFrame = OMF_FIX_F_GRPDEF; + uint16_t idxFrame = pThis->idxGrpFlat; + uint8_t bTarget; + uint16_t idxTarget; + bool fTargetDisp; + uint32_t offTargetDisp; + switch (pOmfSym->enmType) + { + case OMFSYMTYPE_INTERNAL: + case OMFSYMTYPE_PUBDEF: + bTarget = OMF_FIX_T_SEGDEF; + idxTarget = pOmfSym->idxSegDef; + fTargetDisp = true; + offTargetDisp = pCoffSym->Value; + break; + + case OMFSYMTYPE_SEGDEF: + bTarget = OMF_FIX_T_SEGDEF_NO_DISP; + idxTarget = pOmfSym->idxSegDef; + fTargetDisp = false; + offTargetDisp = 0; + break; + + case OMFSYMTYPE_EXTDEF: + bTarget = OMF_FIX_T_EXTDEF_NO_DISP; + idxTarget = pOmfSym->idx; + fTargetDisp = false; + offTargetDisp = 0; + break; + + default: + return error(pThis->pszSrc, "Relocation in segment #%u '%s' references ignored or invalid symbol (%s)\n", + i, pszSegNm, coffGetSymbolName(pCoffSym, pchStrTab, cbStrTab, szShortName)); + } + + /* Do COFF relocation type conversion. */ + switch (paRelocs[iReloc].Type) + { + case IMAGE_REL_AMD64_ADDR64: + { + uint64_t uAddend = *uLoc.pu64; + if (uAddend > _1G) + fRet = error(pThis->pszSrc, "ADDR64 with large addend (%#llx) at %#x in segment #%u '%s'\n", + uAddend, paRelocs[iReloc].u.VirtualAddress, i, pszSegNm); + fSelfRel = false; + break; + } + + case IMAGE_REL_AMD64_REL32_1: + case IMAGE_REL_AMD64_REL32_2: + case IMAGE_REL_AMD64_REL32_3: + case IMAGE_REL_AMD64_REL32_4: + case IMAGE_REL_AMD64_REL32_5: + /** @todo Check whether OMF read addends from the data or relies on the + * displacement. Also, check what it's relative to. */ + *uLoc.pu32 -= paRelocs[iReloc].Type - IMAGE_REL_AMD64_REL32; + break; + + case IMAGE_REL_AMD64_ADDR32: + fSelfRel = false; + break; + + case IMAGE_REL_AMD64_ADDR32NB: + fSelfRel = false; + bFrame = OMF_FIX_F_EXTDEF; + idxFrame = pThis->idxExtImageBase; + break; + + case IMAGE_REL_AMD64_REL32: + /* defaults are ok. */ + break; + + case IMAGE_REL_AMD64_SECTION: + bLocation = OMF_FIX_LOC_16BIT_SEGMENT; + RT_FALL_THRU(); + + case IMAGE_REL_AMD64_SECREL: + fSelfRel = false; + if (pOmfSym->enmType == OMFSYMTYPE_EXTDEF) + { + bFrame = OMF_FIX_F_EXTDEF; + idxFrame = pOmfSym->idx; + } + else + { + bFrame = OMF_FIX_F_SEGDEF; + idxFrame = pOmfSym->idxSegDef; + } + break; + + case IMAGE_REL_AMD64_ABSOLUTE: + continue; /* Ignore it like the PECOFF.DOC says we should. */ + + case IMAGE_REL_AMD64_SECREL7: + default: + return error(pThis->pszSrc, "Unsupported fixup type %#x (%s) at rva=%#x in section #%u '%-8.8s'\n", + paRelocs[iReloc].Type, + paRelocs[iReloc].Type < RT_ELEMENTS(g_apszCoffAmd64RelTypes) + ? g_apszCoffAmd64RelTypes[paRelocs[iReloc].Type] : "unknown", + paRelocs[iReloc].u.VirtualAddress, i, paShdrs[i].Name); + } + + /* Add the fixup. */ + if (idxFrame == UINT16_MAX) + error(pThis->pszSrc, "idxFrame=UINT16_MAX for %s type=%s\n", + coffGetSymbolName(pCoffSym, pchStrTab, cbStrTab, szShortName), + g_apszCoffAmd64RelTypes[paRelocs[iReloc].Type]); + fRet = omfWriter_LEDataAddFixup(pThis, offDataRec, fSelfRel, bLocation, bFrame, idxFrame, + bTarget, idxTarget, fTargetDisp, offTargetDisp) && fRet; + } + + /* + * Write the LEDATA and associated FIXUPPs. + */ + if (!omfWriter_LEDataEnd(pThis)) + return false; + + /* + * Advance. + */ + paRelocs += cChunkRelocs; + cRelocs -= cChunkRelocs; + if (cbData > cbChunk) + { + cbData -= cbChunk; + pbData += cbChunk; + } + else + cbData = 0; + off += cbChunk; + cbVirtData -= cbChunk; + } + } + + return fRet; +} + + +static bool convertCoffToOmf(const char *pszFile, uint8_t const *pbFile, size_t cbFile, FILE *pDst) +{ + /* + * Validate the source file a little. + */ + if (!validateCoff(pszFile, pbFile, cbFile)) + return false; + + /* + * Instantiate the OMF writer. + */ + PIMAGE_FILE_HEADER pHdr = (PIMAGE_FILE_HEADER)pbFile; + POMFWRITER pThis = omfWriter_Create(pszFile, pHdr->NumberOfSections, pHdr->NumberOfSymbols, pDst); + if (!pThis) + return false; + + /* + * Write the OMF object file. + */ + if (omfWriter_BeginModule(pThis, pszFile)) + { + PCIMAGE_SECTION_HEADER paShdrs = (PCIMAGE_SECTION_HEADER)(pHdr + 1); + PCIMAGE_SYMBOL paSymTab = (PCIMAGE_SYMBOL)&pbFile[pHdr->PointerToSymbolTable]; + const char *pchStrTab = (const char *)&paSymTab[pHdr->NumberOfSymbols]; + if ( convertCoffSectionsToSegDefsAndGrpDefs(pThis, paShdrs, pHdr->NumberOfSections) + && convertCoffSymbolsToPubDefsAndExtDefs(pThis, paSymTab, pHdr->NumberOfSymbols, pchStrTab, paShdrs) + && omfWriter_LinkPassSeparator(pThis) + && convertCoffSectionsToLeDataAndFixupps(pThis, pbFile, cbFile, paShdrs, pHdr->NumberOfSections, + paSymTab, pHdr->NumberOfSymbols, pchStrTab) + && omfWriter_EndModule(pThis) ) + { + + omfWriter_Destroy(pThis); + return true; + } + } + + omfWriter_Destroy(pThis); + return false; +} + + +/********************************************************************************************************************************* +* Mach-O/AMD64 -> OMF/i386 Converter * +*********************************************************************************************************************************/ + +//#define MACHO_TO_OMF_CONVERSION +#ifdef MACHO_TO_OMF_CONVERSION + +/** AMD64 relocation type names for Mach-O. */ +static const char * const g_apszMachOAmd64RelTypes[] = +{ + "X86_64_RELOC_UNSIGNED", + "X86_64_RELOC_SIGNED", + "X86_64_RELOC_BRANCH", + "X86_64_RELOC_GOT_LOAD", + "X86_64_RELOC_GOT", + "X86_64_RELOC_SUBTRACTOR", + "X86_64_RELOC_SIGNED_1", + "X86_64_RELOC_SIGNED_2", + "X86_64_RELOC_SIGNED_4" +}; + +/** AMD64 relocation type sizes for Mach-O. */ +static uint8_t const g_acbMachOAmd64RelTypes[] = +{ + 8, /* X86_64_RELOC_UNSIGNED */ + 4, /* X86_64_RELOC_SIGNED */ + 4, /* X86_64_RELOC_BRANCH */ + 4, /* X86_64_RELOC_GOT_LOAD */ + 4, /* X86_64_RELOC_GOT */ + 8, /* X86_64_RELOC_SUBTRACTOR */ + 4, /* X86_64_RELOC_SIGNED_1 */ + 4, /* X86_64_RELOC_SIGNED_2 */ + 4, /* X86_64_RELOC_SIGNED_4 */ +}; + +/** Macro for getting the size of a AMD64 Mach-O relocation. */ +#define MACHO_AMD64_RELOC_SIZE(a_Type) ( (a_Type) < RT_ELEMENTS(g_acbMachOAmd64RelTypes) ? g_acbMachOAmd64RelTypes[(a_Type)] : 1) + + +typedef struct MACHODETAILS +{ + /** The ELF header. */ + Elf64_Ehdr const *pEhdr; + /** The section header table. */ + Elf64_Shdr const *paShdrs; + /** The string table for the section names. */ + const char *pchShStrTab; + + /** The symbol table section number. UINT16_MAX if not found. */ + uint16_t iSymSh; + /** The string table section number. UINT16_MAX if not found. */ + uint16_t iStrSh; + + /** The symbol table. */ + Elf64_Sym const *paSymbols; + /** The number of symbols in the symbol table. */ + uint32_t cSymbols; + + /** Pointer to the (symbol) string table if found. */ + const char *pchStrTab; + /** The string table size. */ + size_t cbStrTab; + +} MACHODETAILS; +typedef MACHODETAILS *PMACHODETAILS; +typedef MACHODETAILS const *PCMACHODETAILS; + + +static bool validateMacho(const char *pszFile, uint8_t const *pbFile, size_t cbFile, PMACHODETAILS pMachOStuff) +{ + /* + * Initialize the Mach-O details structure. + */ + memset(pMachOStuff, 0, sizeof(*pMachOStuff)); + pMachOStuff->iSymSh = UINT16_MAX; + pMachOStuff->iStrSh = UINT16_MAX; + + /* + * Validate the header and our other expectations. + */ + Elf64_Ehdr const *pEhdr = (Elf64_Ehdr const *)pbFile; + pMachOStuff->pEhdr = pEhdr; + if ( pEhdr->e_ident[EI_CLASS] != ELFCLASS64 + || pEhdr->e_ident[EI_DATA] != ELFDATA2LSB + || pEhdr->e_ehsize != sizeof(Elf64_Ehdr) + || pEhdr->e_shentsize != sizeof(Elf64_Shdr) + || pEhdr->e_version != EV_CURRENT ) + return error(pszFile, "Unsupported ELF config\n"); + if (pEhdr->e_type != ET_REL) + return error(pszFile, "Expected relocatable ELF file (e_type=%d)\n", pEhdr->e_type); + if (pEhdr->e_machine != EM_X86_64) + return error(pszFile, "Expected relocatable ELF file (e_type=%d)\n", pEhdr->e_machine); + if (pEhdr->e_phnum != 0) + return error(pszFile, "Expected e_phnum to be zero not %u\n", pEhdr->e_phnum); + if (pEhdr->e_shnum < 2) + return error(pszFile, "Expected e_shnum to be two or higher\n"); + if (pEhdr->e_shstrndx >= pEhdr->e_shnum || pEhdr->e_shstrndx == 0) + return error(pszFile, "Bad e_shstrndx=%u (e_shnum=%u)\n", pEhdr->e_shstrndx, pEhdr->e_shnum); + if ( pEhdr->e_shoff >= cbFile + || pEhdr->e_shoff + pEhdr->e_shnum * sizeof(Elf64_Shdr) > cbFile) + return error(pszFile, "Section table is outside the file (e_shoff=%#llx, e_shnum=%u, cbFile=%#llx)\n", + pEhdr->e_shstrndx, pEhdr->e_shnum, (uint64_t)cbFile); + + /* + * Locate the section name string table. + * We assume it's okay as we only reference it in verbose mode. + */ + Elf64_Shdr const *paShdrs = (Elf64_Shdr const *)&pbFile[pEhdr->e_shoff]; + pMachOStuff->paShdrs = paShdrs; + + Elf64_Xword const cbShStrTab = paShdrs[pEhdr->e_shstrndx].sh_size; + if ( paShdrs[pEhdr->e_shstrndx].sh_offset > cbFile + || cbShStrTab > cbFile + || paShdrs[pEhdr->e_shstrndx].sh_offset + cbShStrTab > cbFile) + return error(pszFile, + "Section string table is outside the file (sh_offset=%#" ELF_FMT_X64 " sh_size=%#" ELF_FMT_X64 " cbFile=%#" ELF_FMT_X64 ")\n", + paShdrs[pEhdr->e_shstrndx].sh_offset, paShdrs[pEhdr->e_shstrndx].sh_size, (Elf64_Xword)cbFile); + const char *pchShStrTab = (const char *)&pbFile[paShdrs[pEhdr->e_shstrndx].sh_offset]; + pMachOStuff->pchShStrTab = pchShStrTab; + + /* + * Work the section table. + */ + bool fRet = true; + for (uint32_t i = 1; i < pEhdr->e_shnum; i++) + { + if (paShdrs[i].sh_name >= cbShStrTab) + return error(pszFile, "Invalid sh_name value (%#x) for section #%u\n", paShdrs[i].sh_name, i); + const char *pszShNm = &pchShStrTab[paShdrs[i].sh_name]; + + if ( paShdrs[i].sh_offset > cbFile + || paShdrs[i].sh_size > cbFile + || paShdrs[i].sh_offset + paShdrs[i].sh_size > cbFile) + return error(pszFile, "Section #%u '%s' has data outside the file: %#" ELF_FMT_X64 " LB %#" ELF_FMT_X64 " (cbFile=%#" ELF_FMT_X64 ")\n", + i, pszShNm, paShdrs[i].sh_offset, paShdrs[i].sh_size, (Elf64_Xword)cbFile); + if (g_cVerbose) + printf("shdr[%u]: name=%#x '%s' type=%#x flags=%#" ELF_FMT_X64 " addr=%#" ELF_FMT_X64 " off=%#" ELF_FMT_X64 " size=%#" ELF_FMT_X64 "\n" + " link=%u info=%#x align=%#" ELF_FMT_X64 " entsize=%#" ELF_FMT_X64 "\n", + i, paShdrs[i].sh_name, pszShNm, paShdrs[i].sh_type, paShdrs[i].sh_flags, + paShdrs[i].sh_addr, paShdrs[i].sh_offset, paShdrs[i].sh_size, + paShdrs[i].sh_link, paShdrs[i].sh_info, paShdrs[i].sh_addralign, paShdrs[i].sh_entsize); + + if (paShdrs[i].sh_link >= pEhdr->e_shnum) + return error(pszFile, "Section #%u '%s' links to a section outside the section table: %#x, max %#x\n", + i, pszShNm, paShdrs[i].sh_link, pEhdr->e_shnum); + if (!RT_IS_POWER_OF_TWO(paShdrs[i].sh_addralign)) + return error(pszFile, "Section #%u '%s' alignment value is not a power of two: %#" ELF_FMT_X64 "\n", + i, pszShNm, paShdrs[i].sh_addralign); + if (!RT_IS_POWER_OF_TWO(paShdrs[i].sh_addralign)) + return error(pszFile, "Section #%u '%s' alignment value is not a power of two: %#" ELF_FMT_X64 "\n", + i, pszShNm, paShdrs[i].sh_addralign); + if (paShdrs[i].sh_addr != 0) + return error(pszFile, "Section #%u '%s' has non-zero address: %#" ELF_FMT_X64 "\n", i, pszShNm, paShdrs[i].sh_addr); + + if (paShdrs[i].sh_type == SHT_RELA) + { + if (paShdrs[i].sh_entsize != sizeof(Elf64_Rela)) + return error(pszFile, "Expected sh_entsize to be %u not %u for section #%u (%s)\n", (unsigned)sizeof(Elf64_Rela), + paShdrs[i].sh_entsize, i, pszShNm); + uint32_t const cRelocs = paShdrs[i].sh_size / sizeof(Elf64_Rela); + if (cRelocs * sizeof(Elf64_Rela) != paShdrs[i].sh_size) + return error(pszFile, "Uneven relocation entry count in #%u (%s): sh_size=%#" ELF_FMT_X64 "\n", + i, pszShNm, paShdrs[i].sh_size); + if ( paShdrs[i].sh_offset > cbFile + || paShdrs[i].sh_size >= cbFile + || paShdrs[i].sh_offset + paShdrs[i].sh_size > cbFile) + return error(pszFile, "The content of section #%u '%s' is outside the file (%#" ELF_FMT_X64 " LB %#" ELF_FMT_X64 ", cbFile=%#lx)\n", + i, pszShNm, paShdrs[i].sh_offset, paShdrs[i].sh_size, (unsigned long)cbFile); + if (paShdrs[i].sh_info != i - 1) + return error(pszFile, "Expected relocation section #%u (%s) to link to previous section: sh_info=%#u\n", + i, pszShNm, (unsigned)paShdrs[i].sh_link); + if (paShdrs[paShdrs[i].sh_link].sh_type != SHT_SYMTAB) + return error(pszFile, "Expected relocation section #%u (%s) to link to symbol table: sh_link=%#u -> sh_type=%#x\n", + i, pszShNm, (unsigned)paShdrs[i].sh_link, (unsigned)paShdrs[paShdrs[i].sh_link].sh_type); + uint32_t cSymbols = paShdrs[paShdrs[i].sh_link].sh_size / paShdrs[paShdrs[i].sh_link].sh_entsize; + + Elf64_Rela const *paRelocs = (Elf64_Rela *)&pbFile[paShdrs[i].sh_offset]; + for (uint32_t j = 0; j < cRelocs; j++) + { + uint8_t const bType = ELF64_R_TYPE(paRelocs[j].r_info); + if (RT_UNLIKELY(bType >= R_X86_64_COUNT)) + fRet = error(pszFile, + "%#018" ELF_FMT_X64 " %#018" ELF_FMT_X64 ": unknown fix up %#x (%+" ELF_FMT_D64 ")\n", + paRelocs[j].r_offset, paRelocs[j].r_info, bType, paRelocs[j].r_addend); + if (RT_UNLIKELY( j > 1 + && paRelocs[j].r_offset <= paRelocs[j - 1].r_offset + && paRelocs[j].r_offset + ELF_AMD64_RELOC_SIZE(ELF64_R_TYPE(paRelocs[j].r_info)) + < paRelocs[j - 1].r_offset )) + fRet = error(pszFile, + "%#018" ELF_FMT_X64 " %#018" ELF_FMT_X64 ": out of offset order (prev %" ELF_FMT_X64 ")\n", + paRelocs[j].r_offset, paRelocs[j].r_info, paRelocs[j - 1].r_offset); + uint32_t const iSymbol = ELF64_R_SYM(paRelocs[j].r_info); + if (RT_UNLIKELY(iSymbol >= cSymbols)) + fRet = error(pszFile, + "%#018" ELF_FMT_X64 " %#018" ELF_FMT_X64 ": symbol index (%#x) out of bounds (%#x)\n", + paRelocs[j].r_offset, paRelocs[j].r_info, iSymbol, cSymbols); + } + if (RT_UNLIKELY( cRelocs > 0 + && fRet + && ( paRelocs[cRelocs - 1].r_offset > paShdrs[i - 1].sh_size + || paRelocs[cRelocs - 1].r_offset + ELF_AMD64_RELOC_SIZE(ELF64_R_TYPE(paRelocs[cRelocs-1].r_info)) + > paShdrs[i - 1].sh_size ))) + fRet = error(pszFile, + "%#018" ELF_FMT_X64 " %#018" ELF_FMT_X64 ": out of bounds (sh_size %" ELF_FMT_X64 ")\n", + paRelocs[cRelocs - 1].r_offset, paRelocs[cRelocs - 1].r_info, paShdrs[i - 1].sh_size); + + } + else if (paShdrs[i].sh_type == SHT_REL) + fRet = error(pszFile, "Section #%u '%s': Unexpected SHT_REL section\n", i, pszShNm); + else if (paShdrs[i].sh_type == SHT_SYMTAB) + { + if (paShdrs[i].sh_entsize != sizeof(Elf64_Sym)) + fRet = error(pszFile, "Section #%u '%s': Unsupported symbol table entry size in : #%u (expected #%u)\n", + i, pszShNm, paShdrs[i].sh_entsize, sizeof(Elf64_Sym)); + Elf64_Xword const cSymbols = paShdrs[i].sh_size / paShdrs[i].sh_entsize; + if (cSymbols * paShdrs[i].sh_entsize != paShdrs[i].sh_size) + fRet = error(pszFile, "Section #%u '%s': Size not a multiple of entry size: %#" ELF_FMT_X64 " %% %#" ELF_FMT_X64 " = %#" ELF_FMT_X64 "\n", + i, pszShNm, paShdrs[i].sh_size, paShdrs[i].sh_entsize, paShdrs[i].sh_size % paShdrs[i].sh_entsize); + if (cSymbols > UINT32_MAX) + fRet = error(pszFile, "Section #%u '%s': too many symbols: %" ELF_FMT_X64 "\n", + i, pszShNm, paShdrs[i].sh_size, cSymbols); + + if (pMachOStuff->iSymSh == UINT16_MAX) + { + pMachOStuff->iSymSh = (uint16_t)i; + pMachOStuff->paSymbols = (Elf64_Sym const *)&pbFile[paShdrs[i].sh_offset]; + pMachOStuff->cSymbols = cSymbols; + + if (paShdrs[i].sh_link != 0) + { + /* Note! The symbol string table section header may not have been validated yet! */ + Elf64_Shdr const *pStrTabShdr = &paShdrs[paShdrs[i].sh_link]; + pMachOStuff->iStrSh = paShdrs[i].sh_link; + pMachOStuff->pchStrTab = (const char *)&pbFile[pStrTabShdr->sh_offset]; + pMachOStuff->cbStrTab = (size_t)pStrTabShdr->sh_size; + } + else + fRet = error(pszFile, "Section #%u '%s': String table link is out of bounds (%#x)\n", + i, pszShNm, paShdrs[i].sh_link); + } + else + fRet = error(pszFile, "Section #%u '%s': Found additonal symbol table, previous in #%u\n", + i, pszShNm, pMachOStuff->iSymSh); + } + } + return fRet; +} + +static bool convertMachoSectionsToSegDefsAndGrpDefs(POMFWRITER pThis, PCMACHODETAILS pMachOStuff) +{ + /* + * Do the list of names pass. + */ + uint16_t idxGrpFlat, idxGrpData; + uint16_t idxClassCode, idxClassData, idxClassDwarf; + if ( !omfWriter_LNamesBegin(pThis, true /*fAddZeroEntry*/) + || !omfWriter_LNamesAddN(pThis, RT_STR_TUPLE("FLAT"), &idxGrpFlat) + || !omfWriter_LNamesAddN(pThis, RT_STR_TUPLE("BS3DATA64_GROUP"), &idxGrpData) + || !omfWriter_LNamesAddN(pThis, RT_STR_TUPLE("BS3CLASS64CODE"), &idxClassCode) + || !omfWriter_LNamesAddN(pThis, RT_STR_TUPLE("FAR_DATA"), &idxClassData) + || !omfWriter_LNamesAddN(pThis, RT_STR_TUPLE("DWARF"), &idxClassDwarf) + ) + return false; + + bool fHaveData = false; + Elf64_Shdr const *pShdr = &pMachOStuff->paShdrs[1]; + Elf64_Half const cSections = pMachOStuff->pEhdr->e_shnum; + for (Elf64_Half i = 1; i < cSections; i++, pShdr++) + { + const char *pszName = &pMachOStuff->pchShStrTab[pShdr->sh_name]; + if (*pszName == '\0') + return error(pThis->pszSrc, "Section #%u has an empty name!\n", i); + + switch (pShdr->sh_type) + { + case SHT_PROGBITS: + case SHT_NOBITS: + /* We drop a few sections we don't want:. */ + if ( strcmp(pszName, ".comment") != 0 /* compiler info */ + && strcmp(pszName, ".note.GNU-stack") != 0 /* some empty section for hinting the linker/whatever */ + && strcmp(pszName, ".eh_frame") != 0 /* unwind / exception info */ + ) + { + pThis->paSegments[i].iSegDef = UINT16_MAX; + pThis->paSegments[i].iGrpDef = UINT16_MAX; + + /* Translate the name and determine group and class. + Note! We currently strip sub-sections. */ + if ( strcmp(pszName, ".text") == 0 + || strncmp(pszName, RT_STR_TUPLE(".text.")) == 0) + { + pszName = "BS3TEXT64"; + pThis->paSegments[i].iGrpNm = idxGrpFlat; + pThis->paSegments[i].iClassNm = idxClassCode; + } + else if ( strcmp(pszName, ".data") == 0 + || strncmp(pszName, RT_STR_TUPLE(".data.")) == 0) + { + pszName = "BS3DATA64"; + pThis->paSegments[i].iGrpNm = idxGrpData; + pThis->paSegments[i].iClassNm = idxClassData; + } + else if (strcmp(pszName, ".bss") == 0) + { + pszName = "BS3BSS64"; + pThis->paSegments[i].iGrpNm = idxGrpData; + pThis->paSegments[i].iClassNm = idxClassData; + } + else if ( strcmp(pszName, ".rodata") == 0 + || strncmp(pszName, RT_STR_TUPLE(".rodata.")) == 0) + { + pszName = "BS3DATA64CONST"; + pThis->paSegments[i].iGrpNm = idxGrpData; + pThis->paSegments[i].iClassNm = idxClassData; + } + else if (strncmp(pszName, RT_STR_TUPLE(".debug_")) == 0) + { + pThis->paSegments[i].iGrpNm = UINT16_MAX; + pThis->paSegments[i].iClassNm = idxClassDwarf; + } + else + { + pThis->paSegments[i].iGrpNm = idxGrpData; + pThis->paSegments[i].iClassNm = idxClassData; + error(pThis->pszSrc, "Unknown data (?) segment: '%s'\n", pszName); + } + + /* Save the name. */ + pThis->paSegments[i].pszName = strdup(pszName); + if (!pThis->paSegments[i].pszName) + return error(pThis->pszSrc, "Out of memory!\n"); + + /* Add the section name. */ + if (!omfWriter_LNamesAdd(pThis, pThis->paSegments[i].pszName, &pThis->paSegments[i].iSegNm)) + return false; + + fHaveData |= pThis->paSegments[i].iGrpDef == idxGrpData; + break; + } + RT_FALL_THRU(); + + default: + pThis->paSegments[i].iSegDef = UINT16_MAX; + pThis->paSegments[i].iGrpDef = UINT16_MAX; + pThis->paSegments[i].iSegNm = UINT16_MAX; + pThis->paSegments[i].iGrpNm = UINT16_MAX; + pThis->paSegments[i].iClassNm = UINT16_MAX; + pThis->paSegments[i].pszName = NULL; + break; + } + } + + if (!omfWriter_LNamesEnd(pThis)) + return false; + + /* + * Emit segment definitions. + */ + uint16_t iSegDef = 1; /* Start counting at 1. */ + pShdr = &pMachOStuff->paShdrs[1]; + for (Elf64_Half i = 1; i < cSections; i++, pShdr++) + { + if (pThis->paSegments[i].iSegNm == UINT16_MAX) + continue; + + uint8_t bSegAttr = 0; + + /* The A field. */ + switch (pShdr->sh_addralign) + { + case 0: + case 1: + bSegAttr |= 1 << 5; + break; + case 2: + bSegAttr |= 2 << 5; + break; + case 4: + bSegAttr |= 5 << 5; + break; + case 8: + case 16: + bSegAttr |= 3 << 5; + break; + case 32: + case 64: + case 128: + case 256: + bSegAttr |= 4 << 5; + break; + default: + bSegAttr |= 6 << 5; /* page aligned, pharlabs extension. */ + break; + } + + /* The C field. */ + bSegAttr |= 2 << 2; /* public */ + + /* The B field. We don't have 4GB segments, so leave it as zero. */ + + /* The D field shall be set as we're doing USE32. */ + bSegAttr |= 1; + + + /* Done. */ + if (!omfWriter_SegDef(pThis, bSegAttr, (uint32_t)pShdr->sh_size, + pThis->paSegments[i].iSegNm, + pThis->paSegments[i].iClassNm)) + return false; + pThis->paSegments[i].iSegDef = iSegDef++; + } + + /* + * Flat group definition (#1) - special, no members. + */ + uint16_t iGrpDef = 1; + if ( !omfWriter_GrpDefBegin(pThis, idxGrpFlat) + || !omfWriter_GrpDefEnd(pThis)) + return false; + for (uint16_t i = 0; i < cSections; i++) + if (pThis->paSegments[i].iGrpNm == idxGrpFlat) + pThis->paSegments[i].iGrpDef = iGrpDef; + pThis->idxGrpFlat = iGrpDef++; + + /* + * Data group definition (#2). + */ + /** @todo do we need to consider missing segments and ordering? */ + uint16_t cGrpNms = 0; + uint16_t aiGrpNms[2] = { 0, 0 }; /* Shut up, GCC. */ + if (fHaveData) + aiGrpNms[cGrpNms++] = idxGrpData; + for (uint32_t iGrpNm = 0; iGrpNm < cGrpNms; iGrpNm++) + { + if (!omfWriter_GrpDefBegin(pThis, aiGrpNms[iGrpNm])) + return false; + for (uint16_t i = 0; i < cSections; i++) + if (pThis->paSegments[i].iGrpNm == aiGrpNms[iGrpNm]) + { + pThis->paSegments[i].iGrpDef = iGrpDef; + if (!omfWriter_GrpDefAddSegDef(pThis, pThis->paSegments[i].iSegDef)) + return false; + } + if (!omfWriter_GrpDefEnd(pThis)) + return false; + iGrpDef++; + } + + return true; +} + +static bool convertMachOSymbolsToPubDefsAndExtDefs(POMFWRITER pThis, PCMACHODETAILS pMachOStuff) +{ + if (!pMachOStuff->cSymbols) + return true; + + /* + * Process the symbols the first. + */ + uint32_t cAbsSyms = 0; + uint32_t cExtSyms = 0; + uint32_t cPubSyms = 0; + for (uint32_t iSeg = 0; iSeg < pThis->cSegments; iSeg++) + pThis->paSegments[iSeg].cPubDefs = 0; + + uint32_t const cSections = pMachOStuff->pEhdr->e_shnum; + uint32_t const cSymbols = pMachOStuff->cSymbols; + Elf64_Sym const * const paSymbols = pMachOStuff->paSymbols; + for (uint32_t iSym = 0; iSym < cSymbols; iSym++) + { + const uint8_t bBind = ELF64_ST_BIND(paSymbols[iSym].st_info); + const uint8_t bType = ELF64_ST_TYPE(paSymbols[iSym].st_info); + const char *pszSymName = &pMachOStuff->pchStrTab[paSymbols[iSym].st_name]; + if ( *pszSymName == '\0' + && bType == STT_SECTION + && paSymbols[iSym].st_shndx < cSections) + pszSymName = &pMachOStuff->pchShStrTab[pMachOStuff->paShdrs[paSymbols[iSym].st_shndx].sh_name]; + + pThis->paSymbols[iSym].enmType = OMFSYMTYPE_IGNORED; + pThis->paSymbols[iSym].idx = UINT16_MAX; + pThis->paSymbols[iSym].idxSegDef = UINT16_MAX; + pThis->paSymbols[iSym].idxGrpDef = UINT16_MAX; + + uint32_t const idxSection = paSymbols[iSym].st_shndx; + if (idxSection == SHN_UNDEF) + { + if (bBind == STB_GLOBAL) + { + pThis->paSymbols[iSym].enmType = OMFSYMTYPE_EXTDEF; + cExtSyms++; + if (*pszSymName == '\0') + return error(pThis->pszSrc, "External symbol #%u (%s) has an empty name.\n", iSym, pszSymName); + } + else if (bBind != STB_LOCAL || iSym != 0) /* Entry zero is usually a dummy. */ + return error(pThis->pszSrc, "Unsupported or invalid bind type %#x for undefined symbol #%u (%s)\n", + bBind, iSym, pszSymName); + } + else if (idxSection < cSections) + { + pThis->paSymbols[iSym].idxSegDef = pThis->paSegments[idxSection].iSegDef; + pThis->paSymbols[iSym].idxGrpDef = pThis->paSegments[idxSection].iGrpDef; + if (bBind == STB_GLOBAL) + { + pThis->paSymbols[iSym].enmType = OMFSYMTYPE_PUBDEF; + pThis->paSegments[idxSection].cPubDefs++; + cPubSyms++; + if (bType == STT_SECTION) + return error(pThis->pszSrc, "Don't know how to export STT_SECTION symbol #%u (%s)\n", iSym, pszSymName); + if (*pszSymName == '\0') + return error(pThis->pszSrc, "Public symbol #%u (%s) has an empty name.\n", iSym, pszSymName); + } + else if (bType == STT_SECTION) + pThis->paSymbols[iSym].enmType = OMFSYMTYPE_SEGDEF; + else + pThis->paSymbols[iSym].enmType = OMFSYMTYPE_INTERNAL; + } + else if (idxSection == SHN_ABS) + { + if (bType != STT_FILE) + { + if (bBind == STB_GLOBAL) + { + pThis->paSymbols[iSym].enmType = OMFSYMTYPE_PUBDEF; + pThis->paSymbols[iSym].idxSegDef = 0; + pThis->paSymbols[iSym].idxGrpDef = 0; + cAbsSyms++; + if (*pszSymName == '\0') + return error(pThis->pszSrc, "Public absolute symbol #%u (%s) has an empty name.\n", iSym, pszSymName); + } + else + return error(pThis->pszSrc, "Unsupported or invalid bind type %#x for absolute symbol #%u (%s)\n", + bBind, iSym, pszSymName); + } + } + else + return error(pThis->pszSrc, "Unsupported or invalid section number %#x for symbol #%u (%s)\n", + idxSection, iSym, pszSymName); + } + + /* + * Emit the PUBDEFs the first time around (see order of records in TIS spec). + * Note! We expect the os x compiler to always underscore symbols, so unlike the + * other 64-bit converters we don't need to check for underscores and add them. + */ + uint16_t idxPubDef = 1; + if (cPubSyms) + { + for (uint32_t iSeg = 0; iSeg < pThis->cSegments; iSeg++) + if (pThis->paSegments[iSeg].cPubDefs > 0) + { + uint16_t const idxSegDef = pThis->paSegments[iSeg].iSegDef; + if (!omfWriter_PubDefBegin(pThis, pThis->paSegments[iSeg].iGrpDef, idxSegDef)) + return false; + for (uint16_t iSym = 0; iSym < cSymbols; iSym++) + if ( pThis->paSymbols[iSym].idxSegDef == idxSegDef + && pThis->paSymbols[iSym].enmType == OMFSYMTYPE_PUBDEF) + { + const char *pszName = &pMachOStuff->pchStrTab[paSymbols[iSym].st_name]; + if (!omfWriter_PubDefAdd(pThis, paSymbols[iSym].st_value, pszName, false /*fPrependUnderscore*/)) + return false; + pThis->paSymbols[iSym].idx = idxPubDef++; + } + if (!omfWriter_PubDefEnd(pThis)) + return false; + } + } + + if (cAbsSyms > 0) + { + if (!omfWriter_PubDefBegin(pThis, 0, 0)) + return false; + for (uint16_t iSym = 0; iSym < cSymbols; iSym++) + if ( pThis->paSymbols[iSym].idxSegDef == 0 + && pThis->paSymbols[iSym].enmType == OMFSYMTYPE_PUBDEF) + { + const char *pszName = &pMachOStuff->pchStrTab[paSymbols[iSym].st_name]; + if (!omfWriter_PubDefAdd(pThis, paSymbols[iSym].st_value, pszName, false /*fPrependUnderscore*/)) + return false; + pThis->paSymbols[iSym].idx = idxPubDef++; + } + if (!omfWriter_PubDefEnd(pThis)) + return false; + } + + /* + * Go over the symbol table and emit external definition records. + */ + if (!omfWriter_ExtDefBegin(pThis)) + return false; + uint16_t idxExtDef = 1; + for (uint16_t iSym = 0; iSym < cSymbols; iSym++) + if (pThis->paSymbols[iSym].enmType == OMFSYMTYPE_EXTDEF) + { + const char *pszName = &pMachOStuff->pchStrTab[paSymbols[iSym].st_name]; + if (!omfWriter_ExtDefAdd(pThis, pszName, false /*fPrependUnderscore*/)) + return false; + pThis->paSymbols[iSym].idx = idxExtDef++; + } + + if (!omfWriter_ExtDefEnd(pThis)) + return false; + + return true; +} + +static bool convertMachOSectionsToLeDataAndFixupps(POMFWRITER pThis, PCMACHODETAILS pMachOStuff, + uint8_t const *pbFile, size_t cbFile) +{ + Elf64_Sym const *paSymbols = pMachOStuff->paSymbols; + Elf64_Shdr const *paShdrs = pMachOStuff->paShdrs; + bool fRet = true; + for (uint32_t i = 1; i < pThis->cSegments; i++) + { + if (pThis->paSegments[i].iSegDef == UINT16_MAX) + continue; + + const char *pszSegNm = &pMachOStuff->pchShStrTab[paShdrs[i].sh_name]; + bool const fRelocs = i + 1 < pThis->cSegments && paShdrs[i + 1].sh_type == SHT_RELA; + uint32_t cRelocs = fRelocs ? paShdrs[i + 1].sh_size / sizeof(Elf64_Rela) : 0; + Elf64_Rela const *paRelocs = fRelocs ? (Elf64_Rela *)&pbFile[paShdrs[i + 1].sh_offset] : NULL; + Elf64_Xword cbVirtData = paShdrs[i].sh_size; + Elf64_Xword cbData = paShdrs[i].sh_type == SHT_NOBITS ? 0 : cbVirtData; + uint8_t const *pbData = &pbFile[paShdrs[i].sh_offset]; + uint32_t off = 0; + + /* The OMF record size requires us to split larger sections up. To make + life simple, we fill zeros for unitialized (BSS) stuff. */ + const uint32_t cbMaxData = RT_MIN(OMF_MAX_RECORD_PAYLOAD - 1 - (pThis->paSegments[i].iSegDef >= 128) - 4 - 1, _1K); + while (cbVirtData > 0) + { + /* Figure out how many bytes to put out in this chunk. Must make sure + fixups doesn't cross chunk boundraries. ASSUMES sorted relocs. */ + uint32_t cChunkRelocs = cRelocs; + uint32_t cbChunk = cbVirtData; + uint32_t offEnd = off + cbChunk; + if (cbChunk > cbMaxData) + { + cbChunk = cbMaxData; + offEnd = off + cbChunk; + cChunkRelocs = 0; + + /* Quickly determin the reloc range. */ + while ( cChunkRelocs < cRelocs + && paRelocs[cChunkRelocs].r_offset < offEnd) + cChunkRelocs++; + + /* Ensure final reloc doesn't go beyond chunk. */ + while ( cChunkRelocs > 0 + && paRelocs[cChunkRelocs - 1].r_offset + + ELF_AMD64_RELOC_SIZE(ELF64_R_TYPE(paRelocs[cChunkRelocs - 1].r_info)) + > offEnd) + { + uint32_t cbDrop = offEnd - paRelocs[cChunkRelocs - 1].r_offset; + cbChunk -= cbDrop; + offEnd -= cbDrop; + cChunkRelocs--; + } + + if (!cbVirtData) + return error(pThis->pszSrc, "Wtf? cbVirtData is zero!\n"); + } + + /* + * We stash the bytes into the OMF writer record buffer, receiving a + * pointer to the start of it so we can make adjustments if necessary. + */ + uint8_t *pbCopy; + if (!omfWriter_LEDataBeginEx(pThis, pThis->paSegments[i].iSegDef, off, cbChunk, cbData, pbData, &pbCopy)) + return false; + + /* + * Convert fiuxps. + */ + for (uint32_t iReloc = 0; iReloc < cChunkRelocs; iReloc++) + { + /* Get the OMF and ELF data for the symbol the reloc references. */ + uint32_t const uType = ELF64_R_TYPE(paRelocs[iReloc].r_info); + uint32_t const iSymbol = ELF64_R_SYM(paRelocs[iReloc].r_info); + Elf64_Sym const * const pElfSym = &paSymbols[iSymbol]; + POMFSYMBOL const pOmfSym = &pThis->paSymbols[iSymbol]; + const char * const pszSymName = &pMachOStuff->pchStrTab[pElfSym->st_name]; + + /* Calc fixup location in the pending chunk and setup a flexible pointer to it. */ + uint16_t offDataRec = (uint16_t)(paRelocs[iReloc].r_offset - off); + RTPTRUNION uLoc; + uLoc.pu8 = &pbCopy[offDataRec]; + + /* OMF fixup data initialized with typical defaults. */ + bool fSelfRel = true; + uint8_t bLocation = OMF_FIX_LOC_32BIT_OFFSET; + uint8_t bFrame = OMF_FIX_F_GRPDEF; + uint16_t idxFrame = pThis->idxGrpFlat; + uint8_t bTarget; + uint16_t idxTarget; + bool fTargetDisp; + uint32_t offTargetDisp; + switch (pOmfSym->enmType) + { + case OMFSYMTYPE_INTERNAL: + case OMFSYMTYPE_PUBDEF: + bTarget = OMF_FIX_T_SEGDEF; + idxTarget = pOmfSym->idxSegDef; + fTargetDisp = true; + offTargetDisp = pElfSym->st_value; + break; + + case OMFSYMTYPE_SEGDEF: + bTarget = OMF_FIX_T_SEGDEF_NO_DISP; + idxTarget = pOmfSym->idxSegDef; + fTargetDisp = false; + offTargetDisp = 0; + break; + + case OMFSYMTYPE_EXTDEF: + bTarget = OMF_FIX_T_EXTDEF_NO_DISP; + idxTarget = pOmfSym->idx; + fTargetDisp = false; + offTargetDisp = 0; + break; + + default: + return error(pThis->pszSrc, "Relocation in segment #%u '%s' references ignored or invalid symbol (%s)\n", + i, pszSegNm, pszSymName); + } + + /* Do COFF relocation type conversion. */ + switch (uType) + { + case R_X86_64_64: + { + int64_t iAddend = paRelocs[iReloc].r_addend; + if (iAddend > _1G || iAddend < -_1G) + fRet = error(pThis->pszSrc, "R_X86_64_64 with large addend (%" ELF_FMT_D64 ") at %#x in segment #%u '%s'\n", + iAddend, paRelocs[iReloc].r_offset, i, pszSegNm); + *uLoc.pu64 = iAddend; + fSelfRel = false; + break; + } + + case R_X86_64_32: + case R_X86_64_32S: /* signed, unsigned, whatever. */ + fSelfRel = false; + RT_FALL_THRU(); + case R_X86_64_PC32: + { + /* defaults are ok, just handle the addend. */ + int32_t iAddend = paRelocs[iReloc].r_addend; + if (iAddend != paRelocs[iReloc].r_addend) + fRet = error(pThis->pszSrc, "R_X86_64_PC32 with large addend (%d) at %#x in segment #%u '%s'\n", + iAddend, paRelocs[iReloc].r_offset, i, pszSegNm); + *uLoc.pu32 = iAddend; + break; + } + + case R_X86_64_NONE: + continue; /* Ignore this one */ + + case R_X86_64_GOT32: + case R_X86_64_PLT32: + case R_X86_64_COPY: + case R_X86_64_GLOB_DAT: + case R_X86_64_JMP_SLOT: + case R_X86_64_RELATIVE: + case R_X86_64_GOTPCREL: + case R_X86_64_16: + case R_X86_64_PC16: + case R_X86_64_8: + case R_X86_64_PC8: + case R_X86_64_DTPMOD64: + case R_X86_64_DTPOFF64: + case R_X86_64_TPOFF64: + case R_X86_64_TLSGD: + case R_X86_64_TLSLD: + case R_X86_64_DTPOFF32: + case R_X86_64_GOTTPOFF: + case R_X86_64_TPOFF32: + default: + return error(pThis->pszSrc, "Unsupported fixup type %#x (%s) at rva=%#x in section #%u '%s' against '%s'\n", + uType, g_apszElfAmd64RelTypes[uType], paRelocs[iReloc].r_offset, i, pszSegNm, pszSymName); + } + + /* Add the fixup. */ + if (idxFrame == UINT16_MAX) + error(pThis->pszSrc, "idxFrame=UINT16_MAX for %s type=%s\n", pszSymName, g_apszElfAmd64RelTypes[uType]); + fRet = omfWriter_LEDataAddFixup(pThis, offDataRec, fSelfRel, bLocation, bFrame, idxFrame, + bTarget, idxTarget, fTargetDisp, offTargetDisp) && fRet; + } + + /* + * Write the LEDATA and associated FIXUPPs. + */ + if (!omfWriter_LEDataEnd(pThis)) + return false; + + /* + * Advance. + */ + paRelocs += cChunkRelocs; + cRelocs -= cChunkRelocs; + if (cbData > cbChunk) + { + cbData -= cbChunk; + pbData += cbChunk; + } + else + cbData = 0; + off += cbChunk; + cbVirtData -= cbChunk; + } + } + + return fRet; +} + + +static bool convertMachoToOmf(const char *pszFile, uint8_t const *pbFile, size_t cbFile, FILE *pDst) +{ + /* + * Validate the source file a little. + */ + MACHODETAILS MachOStuff; + if (!validateMachO(pszFile, pbFile, cbFile, &MachOStuff)) + return false; + + /* + * Instantiate the OMF writer. + */ + POMFWRITER pThis = omfWriter_Create(pszFile, MachOStuff.pEhdr->e_shnum, MachOStuff.cSymbols, pDst); + if (!pThis) + return false; + + /* + * Write the OMF object file. + */ + if (omfWriter_BeginModule(pThis, pszFile)) + { + Elf64_Ehdr const *pEhdr = (Elf64_Ehdr const *)pbFile; + + if ( convertMachOSectionsToSegDefsAndGrpDefs(pThis, &MachOStuff) + && convertMachOSymbolsToPubDefsAndExtDefs(pThis, &MachOStuff) + && omfWriter_LinkPassSeparator(pThis) + && convertMachOSectionsToLeDataAndFixupps(pThis, &MachOStuff, pbFile, cbFile) + && omfWriter_EndModule(pThis) ) + { + + omfWriter_Destroy(pThis); + return true; + } + } + + omfWriter_Destroy(pThis); + return false; +} + +#endif /* !MACHO_TO_OMF_CONVERSION */ + + +/********************************************************************************************************************************* +* OMF Converter/Tweaker * +*********************************************************************************************************************************/ + +/** Watcom intrinsics we need to modify so we can mix 32-bit and 16-bit + * code, since the 16 and 32 bit compilers share several names. + * The names are length prefixed. + */ +static const char * const g_apszExtDefRenames[] = +{ + "\x05" "__I4D", + "\x05" "__I4M", + "\x05" "__I8D", + "\x06" "__I8DQ", + "\x07" "__I8DQE", + "\x06" "__I8DR", + "\x07" "__I8DRE", + "\x06" "__I8LS", + "\x05" "__I8M", + "\x06" "__I8ME", + "\x06" "__I8RS", + "\x05" "__PIA", + "\x05" "__PIS", + "\x05" "__PTC", + "\x05" "__PTS", + "\x05" "__U4D", + "\x05" "__U4M", + "\x05" "__U8D", + "\x06" "__U8DQ", + "\x07" "__U8DQE", + "\x06" "__U8DR", + "\x07" "__U8DRE", + "\x06" "__U8LS", + "\x05" "__U8M", + "\x06" "__U8ME", + "\x06" "__U8RS", +}; + +/** + * Segment definition. + */ +typedef struct OMFSEGDEF +{ + uint32_t cbSeg; + uint8_t bSegAttr; + uint16_t idxName; + uint16_t idxClass; + uint16_t idxOverlay; + uint8_t cchName; + uint8_t cchClass; + uint8_t cchOverlay; + const char *pchName; + const char *pchClass; + const char *pchOverlay; + bool fUse32; + bool f32bitRec; +} OMFSEGDEF; +typedef OMFSEGDEF *POMFSEGDEF; + +/** + * Group definition. + */ +typedef struct OMFGRPDEF +{ + const char *pchName; + uint16_t idxName; + uint8_t cchName; + uint16_t cSegDefs; + uint16_t *paidxSegDefs; +} OMFGRPDEF; +typedef OMFGRPDEF *POMFGRPDEF; + +/** + * Records line number information for a file in a segment (for CV8 debug info). + */ +typedef struct OMFFILELINES +{ + /** The source info offset. */ + uint32_t offSrcInfo; + /** Number of line/offset pairs. */ + uint32_t cPairs; + /** Number of pairs allocated. */ + uint32_t cPairsAlloc; + /** Table with line number and offset pairs, ordered by offset. */ + PRTCV8LINEPAIR paPairs; +} OMFFILEINES; +typedef OMFFILEINES *POMFFILEINES; + +/** + * Records line number information for a segment (for CV8 debug info). + */ +typedef struct OMFSEGLINES +{ + /** Number of files. */ + uint32_t cFiles; + /** Number of bytes we need. */ + uint32_t cb; + /** The segment index. */ + uint16_t idxSeg; + /** The group index for this segment. Initially OMF_REPLACE_GRP_XXX values, + * later convertOmfWriteDebugGrpDefs replaces them with actual values. */ + uint16_t idxGrp; + /** File table. */ + POMFFILEINES paFiles; +} OMFSEGLINES; +typedef OMFSEGLINES *POMFSEGLINES; + +/** @name OMF_REPLACE_GRP_XXX - Special OMFSEGLINES::idxGrp values. + * @{ */ +#define OMF_REPLACE_GRP_CGROUP16 UINT16_C(0xffe0) +#define OMF_REPLACE_GRP_RMCODE UINT16_C(0xffe1) +#define OMF_REPLACE_GRP_X0CODE UINT16_C(0xffe2) +#define OMF_REPLACE_GRP_X1CODE UINT16_C(0xffe3) +/** @} */ + + +/** + * OMF details allocation that needs to be freed when done. + */ +typedef struct OMFDETAILSALLOC +{ + /** Pointer to the next allocation. */ + struct OMFDETAILSALLOC *pNext; + /** The allocated bytes. */ + RT_FLEXIBLE_ARRAY_EXTENSION + uint8_t abData[RT_FLEXIBLE_ARRAY]; +} OMFDETAILSALLOC; +typedef OMFDETAILSALLOC *POMFDETAILSALLOC; + +/** + * OMF conversion details. + * + * Keeps information relevant to the conversion and CV8 debug info. + */ +typedef struct OMFDETAILS +{ + /** The input file name. */ + const char *pszFile; + + /** Set if it has line numbers. */ + bool fLineNumbers; + /** Set if we think this may be a 32-bit OMF file. */ + bool fProbably32bit; + /** Set if this module may need mangling. */ + bool fMayNeedMangling; + /** The LNAME index of '$$SYMBOLS' or UINT16_MAX it not found. */ + uint16_t iSymbolsNm; + /** The LNAME index of 'DEBSYM' or UINT16_MAX it not found. */ + uint16_t iDebSymNm; + /** The '$$SYMBOLS' segment index. */ + uint16_t iSymbolsSeg; + + /** Number of SEGDEFs records. */ + uint16_t cSegDefs; + /** Number of GRPDEFs records. */ + uint16_t cGrpDefs; + /** Number of listed names. */ + uint16_t cLNames; + + /** Segment defintions. */ + POMFSEGDEF paSegDefs; + /** Group defintions. */ + POMFGRPDEF paGrpDefs; + /** Name list. Points to the size repfix. */ + char **papchLNames; + + /** Code groups we need to keep an eye on for line number fixup purposes. */ + struct OMFLINEGROUPS + { + /** The name. */ + const char *pszName; + /** The primary class name. */ + const char *pszClass1; + /** The secondary class name. */ + const char *pszClass2; + /** The main segment name, NULL if not applicable (CGROUP16). */ + const char *pszSeg; + /** The name length. */ + uint8_t cchName; + /** The primary class name length. */ + uint8_t cchClass1; + /** The secondary class name length. */ + uint8_t cchClass2; + /** Whether this group is needed. */ + bool fNeeded; + /** The group index (UINT16_MAX if not found). */ + uint16_t idxGroup; + /** The group name. */ + uint16_t idxName; + /** The OMF_REPLACE_GRP_XXX value. */ + uint16_t idxReplaceGrp; + } aGroups[4]; + + /** CV8: Filename string table size. */ + uint32_t cbStrTab; + /** CV8: Filename string table allocation size (always multiple of dword, + * zero initialized). */ + uint32_t cbStrTabAlloc; + /** CV8: Filename String table. */ + char *pchStrTab; + /** CV8: Elements in the source info table. */ + uint16_t cSrcInfo; + /** CV8: Source info table. */ + PRTCV8SRCINFO paSrcInfo; + + /** Number of entries in the paSegLines table. */ + uint32_t cSegLines; + /** Segment line numbers, indexed by segment number. */ + POMFSEGLINES paSegLines; + + /** List of allocations that needs freeing. */ + POMFDETAILSALLOC pAllocHead; +} OMFDETAILS; +typedef OMFDETAILS *POMFDETAILS; +typedef OMFDETAILS const *PCOMFDETAILS; + + +/** Grows a table to a given size (a_cNewEntries). */ +#define OMF_GROW_TABLE_EX_RET_ERR(a_EntryType, a_paTable, a_cEntries, a_cNewEntries) \ + do\ + { \ + size_t cbOld = (a_cEntries) * sizeof(a_EntryType); \ + size_t cbNew = (a_cNewEntries) * sizeof(a_EntryType); \ + void *pvNew = realloc(a_paTable, cbNew); \ + if (pvNew) \ + { \ + memset((uint8_t *)pvNew + cbOld, 0, cbNew - cbOld); \ + (a_paTable) = (a_EntryType *)pvNew; \ + } \ + else return error("???", "Out of memory!\n"); \ + } while (0) + +/** Grows a table. */ +#define OMF_GROW_TABLE_RET_ERR(a_EntryType, a_paTable, a_cEntries, a_cEvery) \ + if ((a_cEntries) % (a_cEvery) != 0) { /* likely */ } \ + else do\ + { \ + size_t cbOld = (a_cEntries) * sizeof(a_EntryType); \ + size_t cbNew = cbOld + (a_cEvery) * sizeof(a_EntryType); \ + void *pvNew = realloc(a_paTable, cbNew); \ + if (pvNew) \ + { \ + memset((uint8_t *)pvNew + cbOld, 0, (a_cEvery) * sizeof(a_EntryType)); \ + (a_paTable) = (a_EntryType *)pvNew; \ + } \ + else return error("???", "Out of memory!\n"); \ + } while (0) + +#define OMF_EXPLODE_LNAME(a_pOmfStuff, a_idxName, a_pchName, a_cchName, a_Name) \ + do { \ + if ((a_idxName) < (a_pOmfStuff)->cLNames) \ + { \ + a_cchName = (uint8_t)*(a_pOmfStuff)->papchLNames[(a_idxName)]; \ + a_pchName = (a_pOmfStuff)->papchLNames[(a_idxName)] + 1; \ + } \ + else return error((a_pOmfStuff)->pszFile, "Invalid LNAME reference %#x in " #a_Name "!\n", a_idxName); \ + } while (0) + + +/** + * Allocates memory that will be freed when we're done converting. + * + * @returns Pointer tot he memory. + * @param pOmfStuff The OMF details data. + * @param cbNeeded The amount of memory required. + */ +static void *omfDetails_Alloc(POMFDETAILS pOmfStuff, size_t cbNeeded) +{ + POMFDETAILSALLOC pAlloc = (POMFDETAILSALLOC)malloc(RT_UOFFSETOF_DYN(OMFDETAILSALLOC, abData[cbNeeded])); + if (pAlloc) + { + pAlloc->pNext = pOmfStuff->pAllocHead; + pOmfStuff->pAllocHead = pAlloc; + return &pAlloc->abData[0]; + } + return NULL; +} + +/** + * Adds a line number to the CV8 debug info. + * + * @returns success indicator. + * @param pOmfStuff Where to collect CV8 debug info. + * @param cchSrcFile The length of the source file name. + * @param pchSrcFile The source file name, not terminated. + * @param poffFile Where to return the source file information table + * offset (for use in the line number tables). + */ +static bool collectOmfAddFile(POMFDETAILS pOmfStuff, uint8_t cchSrcFile, const char *pchSrcFile, uint32_t *poffFile) +{ + /* + * Do lookup first. + */ + uint32_t i = pOmfStuff->cSrcInfo; + while (i-- > 0) + { + const char *pszCur = &pOmfStuff->pchStrTab[pOmfStuff->paSrcInfo[i].offSourceName]; + if ( strncmp(pszCur, pchSrcFile, cchSrcFile) == 0 + && pszCur[cchSrcFile] == '\0') + { + *poffFile = i * sizeof(pOmfStuff->paSrcInfo[0]); + return true; + } + } + + /* + * Add it to the string table (dword aligned and zero padded). + */ + uint32_t offSrcTab = pOmfStuff->cbStrTab; + if (offSrcTab + cchSrcFile + 1 > pOmfStuff->cbStrTabAlloc) + { + uint32_t cbNew = (offSrcTab == 0) + offSrcTab + cchSrcFile + 1; + cbNew = RT_ALIGN(cbNew, 256); + void *pvNew = realloc(pOmfStuff->pchStrTab, cbNew); + if (!pvNew) + return error("???", "out of memory"); + pOmfStuff->pchStrTab = (char *)pvNew; + pOmfStuff->cbStrTabAlloc = cbNew; + memset(&pOmfStuff->pchStrTab[offSrcTab], 0, cbNew - offSrcTab); + + if (!offSrcTab) + offSrcTab++; + } + + memcpy(&pOmfStuff->pchStrTab[offSrcTab], pchSrcFile, cchSrcFile); + pOmfStuff->pchStrTab[offSrcTab + cchSrcFile] = '\0'; + pOmfStuff->cbStrTab = offSrcTab + cchSrcFile + 1; + + /* + * Add it to the filename info table. + */ + if ((pOmfStuff->cSrcInfo % 8) == 0) + { + void *pvNew = realloc(pOmfStuff->paSrcInfo, sizeof(pOmfStuff->paSrcInfo[0]) * (pOmfStuff->cSrcInfo + 8)); + if (!pvNew) + return error("???", "out of memory"); + pOmfStuff->paSrcInfo = (PRTCV8SRCINFO)pvNew; + } + + PRTCV8SRCINFO pSrcInfo = &pOmfStuff->paSrcInfo[pOmfStuff->cSrcInfo++]; + pSrcInfo->offSourceName = offSrcTab; + pSrcInfo->uDigestType = RTCV8SRCINFO_DIGEST_TYPE_MD5; + memset(&pSrcInfo->Digest, 0, sizeof(pSrcInfo->Digest)); + + *poffFile = (uint32_t)((uintptr_t)pSrcInfo - (uintptr_t)pOmfStuff->paSrcInfo); + return true; +} + + +/** + * Adds a line number to the CV8 debug info. + * + * @returns success indicator. + * @param pOmfStuff Where to collect CV8 debug info. + * @param idxSeg The segment index. + * @param off The segment offset. + * @param uLine The line number. + * @param offSrcInfo The source file info table offset. + */ +static bool collectOmfAddLine(POMFDETAILS pOmfStuff, uint16_t idxSeg, uint32_t off, uint16_t uLine, uint32_t offSrcInfo) +{ + /* + * Get/add the segment line structure. + */ + if (idxSeg >= pOmfStuff->cSegLines) + { + OMF_GROW_TABLE_EX_RET_ERR(OMFSEGLINES, pOmfStuff->paSegLines, pOmfStuff->cSegLines, idxSeg + 1); + for (uint32_t i = pOmfStuff->cSegLines; i <= idxSeg; i++) + { + pOmfStuff->paSegLines[i].idxSeg = i; + pOmfStuff->paSegLines[i].idxGrp = UINT16_MAX; + pOmfStuff->paSegLines[i].cb = sizeof(RTCV8LINESHDR); + } + pOmfStuff->cSegLines = idxSeg + 1; + } + POMFSEGLINES pSegLines = &pOmfStuff->paSegLines[idxSeg]; + + /* + * Get/add the file structure with the segment. + */ + POMFFILEINES pFileLines = NULL; + uint32_t i = pSegLines->cFiles; + while (i-- > 0) + if (pSegLines->paFiles[i].offSrcInfo == offSrcInfo) + { + pFileLines = &pSegLines->paFiles[i]; + break; + } + if (!pFileLines) + { + i = pSegLines->cFiles; + OMF_GROW_TABLE_RET_ERR(OMFFILEINES, pSegLines->paFiles, pSegLines->cFiles, 4); + pSegLines->cFiles = i + 1; + pSegLines->cb += sizeof(RTCV8LINESSRCMAP); + + pFileLines = &pSegLines->paFiles[i]; + pFileLines->offSrcInfo = offSrcInfo; + pFileLines->cPairs = 0; + pFileLines->cPairsAlloc = 0; + pFileLines->paPairs = NULL; + + /* + * Check for segment group requirements the first time a segment is used. + */ + if (i == 0) + { + if (idxSeg >= pOmfStuff->cSegDefs) + return error("???", "collectOmfAddLine: idxSeg=%#x is out of bounds (%#x)!\n", idxSeg, pOmfStuff->cSegDefs); + POMFSEGDEF pSegDef = &pOmfStuff->paSegDefs[idxSeg]; + unsigned j = RT_ELEMENTS(pOmfStuff->aGroups); + while (j-- > 0) + if ( ( pSegDef->cchClass == pOmfStuff->aGroups[j].cchClass1 + && memcmp(pSegDef->pchClass, pOmfStuff->aGroups[j].pszClass1, pSegDef->cchClass) == 0) + || ( pSegDef->cchClass == pOmfStuff->aGroups[j].cchClass2 + && memcmp(pSegDef->pchClass, pOmfStuff->aGroups[j].pszClass2, pSegDef->cchClass) == 0)) + { + pOmfStuff->aGroups[j].fNeeded = true; + pSegLines->idxGrp = pOmfStuff->aGroups[j].idxReplaceGrp; + break; + } + } + } + + /* + * Add the line number (sorted, duplicates removed). + */ + if (pFileLines->cPairs + 1 > pFileLines->cPairsAlloc) + { + void *pvNew = realloc(pFileLines->paPairs, (pFileLines->cPairsAlloc + 16) * sizeof(pFileLines->paPairs[0])); + if (!pvNew) + return error("???", "out of memory"); + pFileLines->paPairs = (PRTCV8LINEPAIR)pvNew; + pFileLines->cPairsAlloc += 16; + } + + i = pFileLines->cPairs; + while (i > 0 && ( off < pFileLines->paPairs[i - 1].offSection + || ( off == pFileLines->paPairs[i - 1].offSection + && uLine < pFileLines->paPairs[i - 1].uLineNumber)) ) + i--; + if ( i == pFileLines->cPairs + || off != pFileLines->paPairs[i].offSection + || uLine != pFileLines->paPairs[i].uLineNumber) + { + if (i < pFileLines->cPairs) + memmove(&pFileLines->paPairs[i + 1], &pFileLines->paPairs[i], + (pFileLines->cPairs - i) * sizeof(pFileLines->paPairs)); + pFileLines->paPairs[i].offSection = off; + pFileLines->paPairs[i].uLineNumber = uLine; + pFileLines->paPairs[i].fEndOfStatement = true; + pFileLines->cPairs++; + pSegLines->cb += sizeof(pFileLines->paPairs[0]); + } + + return true; +} + + +/** + * Parses OMF file gathering line numbers (for CV8 debug info) and checking out + * external defintions for mangling work (compiler instrinsics). + * + * @returns success indicator. + * @param pszFile The name of the OMF file. + * @param pbFile The file content. + * @param cbFile The size of the file content. + * @param pOmfStuff Where to collect CV8 debug info and anything else we + * find out about the OMF file. + */ +static bool collectOmfDetails(const char *pszFile, uint8_t const *pbFile, size_t cbFile, POMFDETAILS pOmfStuff) +{ + uint32_t cExtDefs = 0; + uint32_t cPubDefs = 0; + uint32_t off = 0; + uint8_t cchSrcFile = 0; + const char *pchSrcFile = NULL; + uint32_t offSrcInfo = UINT32_MAX; + + memset(pOmfStuff, 0, sizeof(*pOmfStuff)); + pOmfStuff->pszFile = pszFile; + pOmfStuff->iDebSymNm = UINT16_MAX; + pOmfStuff->iSymbolsNm = UINT16_MAX; + pOmfStuff->iSymbolsSeg = UINT16_MAX; + + /* Dummy entries. */ + OMF_GROW_TABLE_RET_ERR(char *, pOmfStuff->papchLNames, pOmfStuff->cLNames, 16); + pOmfStuff->papchLNames[0] = (char *)""; + pOmfStuff->cLNames = 1; + + OMF_GROW_TABLE_RET_ERR(OMFSEGDEF, pOmfStuff->paSegDefs, pOmfStuff->cSegDefs, 16); + pOmfStuff->cSegDefs = 1; + + OMF_GROW_TABLE_RET_ERR(OMFGRPDEF, pOmfStuff->paGrpDefs, pOmfStuff->cGrpDefs, 16); + pOmfStuff->cGrpDefs = 1; + + /* Groups we seek. */ +#define OMF_INIT_WANTED_GROUP(a_idx, a_szName, a_szClass1, a_szClass2, a_pszSeg, a_idxReplace) \ + pOmfStuff->aGroups[a_idx].pszName = a_szName; \ + pOmfStuff->aGroups[a_idx].cchName = sizeof(a_szName) - 1; \ + pOmfStuff->aGroups[a_idx].pszClass1 = a_szClass1; \ + pOmfStuff->aGroups[a_idx].cchClass1 = sizeof(a_szClass1) - 1; \ + pOmfStuff->aGroups[a_idx].pszClass2 = a_szClass2; \ + pOmfStuff->aGroups[a_idx].cchClass2 = sizeof(a_szClass2) - 1; \ + pOmfStuff->aGroups[a_idx].pszSeg = a_pszSeg; \ + pOmfStuff->aGroups[a_idx].fNeeded = false; \ + pOmfStuff->aGroups[a_idx].idxGroup = UINT16_MAX; \ + pOmfStuff->aGroups[a_idx].idxName = UINT16_MAX; \ + pOmfStuff->aGroups[a_idx].idxReplaceGrp = a_idxReplace + OMF_INIT_WANTED_GROUP(0, "CGROUP16", "BS3CLASS16CODE", "CODE", NULL, OMF_REPLACE_GRP_CGROUP16); + OMF_INIT_WANTED_GROUP(1, "BS3GROUPRMTEXT16", "BS3CLASS16RMCODE", "", "BS3RMTEXT16", OMF_REPLACE_GRP_RMCODE); + OMF_INIT_WANTED_GROUP(2, "BS3GROUPX0TEXT16", "BS3CLASS16X0CODE", "", "BS3X0TEXT16", OMF_REPLACE_GRP_X0CODE); + OMF_INIT_WANTED_GROUP(3, "BS3GROUPX1TEXT16", "BS3CLASS16X1CODE", "", "BS3X1TEXT16", OMF_REPLACE_GRP_X1CODE); + + /* + * Process the OMF records. + */ + while (off + 3 < cbFile) + { + uint8_t bRecType = pbFile[off]; + uint16_t cbRec = RT_MAKE_U16(pbFile[off + 1], pbFile[off + 2]); + if (g_cVerbose > 2) + printf( "%#07x: type=%#04x len=%#06x\n", off, bRecType, cbRec); + if (off + cbRec > cbFile) + return error(pszFile, "Invalid record length at %#x: %#x (cbFile=%#lx)\n", off, cbRec, (unsigned long)cbFile); + + uint32_t offRec = 0; + uint8_t const *pbRec = &pbFile[off + 3]; +#define OMF_CHECK_RET(a_cbReq, a_Name) /* Not taking the checksum into account, so we're good with 1 or 2 byte fields. */ \ + if (offRec + (a_cbReq) <= cbRec) {/*likely*/} \ + else return error(pszFile, "Malformed " #a_Name "! off=%#x offRec=%#x cbRec=%#x cbNeeded=%#x line=%d\n", \ + off, offRec, cbRec, (a_cbReq), __LINE__) +#define OMF_READ_IDX(a_idx, a_Name) \ + do { \ + OMF_CHECK_RET(2, a_Name); \ + a_idx = pbRec[offRec++]; \ + if ((a_idx) & 0x80) \ + a_idx = (((a_idx) & 0x7f) << 8) | pbRec[offRec++]; \ + } while (0) + +#define OMF_READ_U16(a_u16, a_Name) \ + do { \ + OMF_CHECK_RET(4, a_Name); \ + a_u16 = RT_MAKE_U16(pbRec[offRec], pbRec[offRec + 1]); \ + offRec += 2; \ + } while (0) +#define OMF_READ_U32(a_u32, a_Name) \ + do { \ + OMF_CHECK_RET(4, a_Name); \ + a_u32 = RT_MAKE_U32_FROM_U8(pbRec[offRec], pbRec[offRec + 1], pbRec[offRec + 2], pbRec[offRec + 3]); \ + offRec += 4; \ + } while (0) + + switch (bRecType) + { + /* + * Record LNAME records, scanning for FLAT. + */ + case OMF_LNAMES: + while (offRec + 1 < cbRec) + { + uint8_t cch = pbRec[offRec]; + if (offRec + 1 + cch >= cbRec) + return error(pszFile, "Invalid LNAME string length at %#x+3+%#x: %#x (cbFile=%#lx)\n", + off, offRec, cch, (unsigned long)cbFile); + + if (g_cVerbose > 2) + printf(" LNAME[%u]: %-*.*s\n", pOmfStuff->cLNames, cch, cch, &pbRec[offRec + 1]); + + OMF_GROW_TABLE_RET_ERR(char *, pOmfStuff->papchLNames, pOmfStuff->cLNames, 16); + pOmfStuff->papchLNames[pOmfStuff->cLNames] = (char *)&pbRec[offRec]; + + if (IS_OMF_STR_EQUAL_EX(cch, &pbRec[offRec + 1], "FLAT")) + pOmfStuff->fProbably32bit = true; + + if (IS_OMF_STR_EQUAL_EX(cch, &pbRec[offRec + 1], "DEBSYM")) + pOmfStuff->iDebSymNm = pOmfStuff->cLNames; + if (IS_OMF_STR_EQUAL_EX(cch, &pbRec[offRec + 1], "$$SYMBOLS")) + pOmfStuff->iSymbolsNm = pOmfStuff->cLNames; + + unsigned j = RT_ELEMENTS(pOmfStuff->aGroups); + while (j-- > 0) + if ( cch == pOmfStuff->aGroups[j].cchName + && memcmp(&pbRec[offRec + 1], pOmfStuff->aGroups[j].pszName, pOmfStuff->aGroups[j].cchName) == 0) + { + pOmfStuff->aGroups[j].idxName = pOmfStuff->cLNames; + break; + } + + pOmfStuff->cLNames++; + offRec += cch + 1; + } + break; + + /* + * Display external definitions if -v is specified, also check if anything needs mangling. + */ + case OMF_EXTDEF: + while (offRec + 1 < cbRec) + { + uint8_t cch = pbRec[offRec++]; + OMF_CHECK_RET(cch, EXTDEF); + char *pchName = (char *)&pbRec[offRec]; + offRec += cch; + + uint16_t idxType; + OMF_READ_IDX(idxType, EXTDEF); + + if (g_cVerbose > 2) + printf(" EXTDEF [%u]: %-*.*s type=%#x\n", cExtDefs, cch, cch, pchName, idxType); + else if (g_cVerbose > 0) + printf(" U %-*.*s\n", cch, cch, pchName); + + /* Look for g_apszExtDefRenames entries that requires changing. */ + if ( !pOmfStuff->fMayNeedMangling + && cch >= 5 + && cch <= 7 + && pchName[0] == '_' + && pchName[1] == '_' + && ( pchName[2] == 'U' + || pchName[2] == 'I' + || pchName[2] == 'P') + && ( pchName[3] == '4' + || pchName[3] == '8' + || pchName[3] == 'I' + || pchName[3] == 'T') ) + { + pOmfStuff->fMayNeedMangling = true; + } + } + break; + + /* + * Display public names if -v is specified. + */ + case OMF_PUBDEF32: + case OMF_LPUBDEF32: + pOmfStuff->fProbably32bit = true; + RT_FALL_THRU(); + case OMF_PUBDEF16: + case OMF_LPUBDEF16: + if (g_cVerbose > 0) + { + char const chType = bRecType == OMF_PUBDEF16 || bRecType == OMF_PUBDEF32 ? 'T' : 't'; + const char *pszRec = "LPUBDEF"; + if (chType == 'T') + pszRec++; + + uint16_t idxGrp; + OMF_READ_IDX(idxGrp, [L]PUBDEF); + + uint16_t idxSeg; + OMF_READ_IDX(idxSeg, [L]PUBDEF); + + uint16_t uFrameBase = 0; + if (idxSeg == 0) + { + OMF_CHECK_RET(2, [L]PUBDEF); + uFrameBase = RT_MAKE_U16(pbRec[offRec], pbRec[offRec + 1]); + offRec += 2; + } + if (g_cVerbose > 2) + printf(" %s: idxGrp=%#x idxSeg=%#x uFrameBase=%#x\n", pszRec, idxGrp, idxSeg, uFrameBase); + uint16_t const uSeg = idxSeg ? idxSeg : uFrameBase; + + while (offRec + 1 < cbRec) + { + uint8_t cch = pbRec[offRec++]; + OMF_CHECK_RET(cch, [L]PUBDEF); + const char *pchName = (const char *)&pbRec[offRec]; + offRec += cch; + + uint32_t offSeg; + if (bRecType & OMF_REC32) + { + OMF_CHECK_RET(4, [L]PUBDEF); + offSeg = RT_MAKE_U32_FROM_U8(pbRec[offRec], pbRec[offRec + 1], pbRec[offRec + 2], pbRec[offRec + 3]); + offRec += 4; + } + else + { + OMF_CHECK_RET(2, [L]PUBDEF); + offSeg = RT_MAKE_U16(pbRec[offRec], pbRec[offRec + 1]); + offRec += 2; + } + + uint16_t idxType; + OMF_READ_IDX(idxType, [L]PUBDEF); + + if (g_cVerbose > 2) + printf(" %s[%u]: off=%#010x type=%#x %-*.*s\n", pszRec, cPubDefs, offSeg, idxType, cch, cch, pchName); + else if (g_cVerbose > 0) + printf("%04x:%08x %c %-*.*s\n", uSeg, offSeg, chType, cch, cch, pchName); + } + } + break; + + /* + * Must count segment definitions to figure the index of our segment. + */ + case OMF_SEGDEF16: + case OMF_SEGDEF32: + { + OMF_GROW_TABLE_RET_ERR(OMFSEGDEF, pOmfStuff->paSegDefs, pOmfStuff->cSegDefs, 16); + POMFSEGDEF pSegDef = &pOmfStuff->paSegDefs[pOmfStuff->cSegDefs++]; + + OMF_CHECK_RET(1 + (bRecType == OMF_SEGDEF16 ? 2 : 4) + 1 + 1 + 1, SEGDEF); + pSegDef->f32bitRec = bRecType == OMF_SEGDEF32; + pSegDef->bSegAttr = pbRec[offRec++]; + pSegDef->fUse32 = pSegDef->bSegAttr & 1; + if ((pSegDef->bSegAttr >> 5) == 0) + { + /* A=0: skip frame number of offset. */ + OMF_CHECK_RET(3, SEGDEF); + offRec += 3; + } + if (bRecType == OMF_SEGDEF16) + OMF_READ_U16(pSegDef->cbSeg, SEGDEF16); + else + OMF_READ_U32(pSegDef->cbSeg, SEGDEF32); + OMF_READ_IDX(pSegDef->idxName, SEGDEF); + OMF_READ_IDX(pSegDef->idxClass, SEGDEF); + OMF_READ_IDX(pSegDef->idxOverlay, SEGDEF); + OMF_EXPLODE_LNAME(pOmfStuff, pSegDef->idxName, pSegDef->pchName, pSegDef->cchName, SEGDEF); + OMF_EXPLODE_LNAME(pOmfStuff, pSegDef->idxClass, pSegDef->pchClass, pSegDef->cchClass, SEGDEF); + OMF_EXPLODE_LNAME(pOmfStuff, pSegDef->idxOverlay, pSegDef->pchOverlay, pSegDef->cchOverlay, SEGDEF); + break; + } + + /* + * Must count segment definitions to figure the index of our group. + */ + case OMF_GRPDEF: + { + OMF_GROW_TABLE_RET_ERR(OMFGRPDEF, pOmfStuff->paGrpDefs, pOmfStuff->cGrpDefs, 8); + POMFGRPDEF pGrpDef = &pOmfStuff->paGrpDefs[pOmfStuff->cGrpDefs]; + + OMF_READ_IDX(pGrpDef->idxName, GRPDEF); + OMF_EXPLODE_LNAME(pOmfStuff, pGrpDef->idxName, pGrpDef->pchName, pGrpDef->cchName, GRPDEF); + + unsigned j = RT_ELEMENTS(pOmfStuff->aGroups); + while (j-- > 0) + if (pGrpDef->idxName == pOmfStuff->aGroups[j].idxName) + { + pOmfStuff->aGroups[j].idxGroup = pOmfStuff->cGrpDefs; + break; + } + + pGrpDef->cSegDefs = 0; + pGrpDef->paidxSegDefs = NULL; + while (offRec + 2 + 1 <= cbRec) + { + if (pbRec[offRec] != 0xff) + return error(pszFile, "Unsupported GRPDEF member type: %#x\n", pbRec[offRec]); + offRec++; + OMF_GROW_TABLE_RET_ERR(uint16_t, pGrpDef->paidxSegDefs, pGrpDef->cSegDefs, 16); + OMF_READ_IDX(pGrpDef->paidxSegDefs[pGrpDef->cSegDefs], GRPDEF); + pGrpDef->cSegDefs++; + } + pOmfStuff->cGrpDefs++; + break; + } + + /* + * Gather file names. + */ + case OMF_THEADR: /* watcom */ + cchSrcFile = pbRec[offRec++]; + OMF_CHECK_RET(cchSrcFile, OMF_THEADR); + pchSrcFile = (const char *)&pbRec[offRec]; + if (!collectOmfAddFile(pOmfStuff, cchSrcFile, pchSrcFile, &offSrcInfo)) + return false; + break; + + case OMF_COMENT: + { + OMF_CHECK_RET(2, COMENT); + offRec++; /* skip the type (flags) */ + uint8_t bClass = pbRec[offRec++]; + if (bClass == OMF_CCLS_BORLAND_SRC_FILE) /* nasm */ + { + OMF_CHECK_RET(1+1+4, BORLAND_SRC_FILE); + offRec++; /* skip unknown byte */ + cchSrcFile = pbRec[offRec++]; + OMF_CHECK_RET(cchSrcFile + 4, BORLAND_SRC_FILE); + pchSrcFile = (const char *)&pbRec[offRec]; + offRec += cchSrcFile; + if (offRec + 4 + 1 != cbRec) + return error(pszFile, "BAD BORLAND_SRC_FILE record at %#x: %d bytes left\n", + off, cbRec - offRec - 4 - 1); + if (!collectOmfAddFile(pOmfStuff, cchSrcFile, pchSrcFile, &offSrcInfo)) + return false; + break; + } + break; + } + + /* + * Line number conversion. + */ + case OMF_LINNUM16: + case OMF_LINNUM32: + { + uint16_t idxGrp; + OMF_READ_IDX(idxGrp, LINNUM); + uint16_t idxSeg; + OMF_READ_IDX(idxSeg, LINNUM); + + uint16_t iLine; + uint32_t offSeg; + if (bRecType == OMF_LINNUM16) + while (offRec + 4 < cbRec) + { + iLine = RT_MAKE_U16(pbRec[offRec + 0], pbRec[offRec + 1]); + offSeg = RT_MAKE_U16(pbRec[offRec + 2], pbRec[offRec + 3]); + if (!collectOmfAddLine(pOmfStuff, idxSeg, offSeg, iLine, offSrcInfo)) + return false; + offRec += 4; + } + else + while (offRec + 6 < cbRec) + { + iLine = RT_MAKE_U16(pbRec[offRec + 0], pbRec[offRec + 1]); + offSeg = RT_MAKE_U32_FROM_U8(pbRec[offRec + 2], pbRec[offRec + 3], pbRec[offRec + 4], pbRec[offRec + 5]); + if (!collectOmfAddLine(pOmfStuff, idxSeg, offSeg, iLine, offSrcInfo)) + return false; + offRec += 6; + } + if (offRec + 1 != cbRec) + return error(pszFile, "BAD LINNUM record at %#x: %d bytes left\n", off, cbRec - offRec - 1); + break; + } + } + + /* advance */ + off += cbRec + 3; + } + + return true; +#undef OMF_READ_IDX +#undef OMF_CHECK_RET +} + + +/** + * Adds a LNAMES entry (returns existing). + * + * @returns success indicator. + * @param pOmfStuff The OMF stuff. + * @param pszName The name to add. + * @param pidxName Where to return the name index. + */ +static bool omfDetails_AddLName(POMFDETAILS pOmfStuff, const char *pszName, uint16_t *pidxName) +{ + size_t const cchName = strlen(pszName); + + /* + * Check if we've already got the name. + */ + for (unsigned iName = 1; iName < pOmfStuff->cLNames; iName++) + if ( (unsigned char)pOmfStuff->papchLNames[iName][0] == cchName + && memcmp(pOmfStuff->papchLNames[iName] + 1, pszName, cchName) == 0) + { + *pidxName = iName; + return true; + } + + /* + * Not found, append it. + */ + char *pszCopy = (char *)omfDetails_Alloc(pOmfStuff, cchName + 2); + if (!pszCopy) + return false; + *(unsigned char *)&pszCopy[0] = (unsigned char)cchName; + memcpy(pszCopy + 1, pszName, cchName + 1); + + OMF_GROW_TABLE_RET_ERR(char *, pOmfStuff->papchLNames, pOmfStuff->cLNames, 16); + pOmfStuff->papchLNames[pOmfStuff->cLNames] = (char *)pszCopy; + *pidxName = pOmfStuff->cLNames; + pOmfStuff->cLNames++; + return true; +} + + +/** + * Adds a SEGDEF (always adds a new one). + * + * @returns success indicator. + * @param pOmfStuff The OMF stuff. + * @param bSegAttr The OMF segment attributes. + * @param cbSeg The segment size. + * @param idxSegName The LNAMES index of the segment name. + * @param idxSegClas The LNAMES index of the segment class. + * @param idxOverlay The LNAMES index of the overlay name; pass 1. + * @param fRec32 Set if SEGDEF32 should be emitted, clear for SEGDEF16. + * @param pidxSeg Where to return the segment index. + */ +static bool omfDetails_AddSegDef(POMFDETAILS pOmfStuff, uint8_t bSegAttr, uint32_t cbSeg, uint16_t idxSegName, + uint16_t idxSegClass, uint16_t idxOverlay, bool fRec32, uint16_t *pidxSeg) +{ + Assert(cbSeg <= UINT16_MAX || fRec32); + Assert(idxSegName < pOmfStuff->cLNames); + Assert(idxSegClass < pOmfStuff->cLNames); + + OMF_GROW_TABLE_RET_ERR(OMFSEGDEF, pOmfStuff->paSegDefs, pOmfStuff->cSegDefs, 16); + POMFSEGDEF pSegDef = &pOmfStuff->paSegDefs[pOmfStuff->cSegDefs]; + + pSegDef->bSegAttr = bSegAttr; + pSegDef->fUse32 = bSegAttr & 1; + pSegDef->f32bitRec = fRec32; + pSegDef->cbSeg = cbSeg; + pSegDef->idxName = idxSegName; + pSegDef->idxClass = idxSegClass; + pSegDef->idxOverlay = idxOverlay; + + OMF_EXPLODE_LNAME(pOmfStuff, pSegDef->idxName, pSegDef->pchName, pSegDef->cchName, SEGDEF); + OMF_EXPLODE_LNAME(pOmfStuff, pSegDef->idxClass, pSegDef->pchClass, pSegDef->cchClass, SEGDEF); + OMF_EXPLODE_LNAME(pOmfStuff, pSegDef->idxOverlay, pSegDef->pchOverlay, pSegDef->cchOverlay, SEGDEF); + + *pidxSeg = pOmfStuff->cSegDefs; + pOmfStuff->cSegDefs++; + return true; +} + + +/** + * Adds a SEGDEF if not found. + * + * @returns success indicator. + * @param pOmfStuff The OMF stuff. + * @param bSegAttr The OMF segment attributes. + * @param cbSeg The segment size. + * @param idxSegName The LNAMES index of the segment name. + * @param idxSegClas The LNAMES index of the segment class. + * @param idxOverlay The LNAMES index of the overlay name; pass 1. + * @param fRec32 Set if SEGDEF32 should be emitted, clear for SEGDEF16. + * @param pidxSeg Where to return the segment index. + */ +static bool omfDetails_AddSegDefIfNeeded(POMFDETAILS pOmfStuff, uint8_t bSegAttr, uint32_t cbSeg, uint16_t idxSegName, + uint16_t idxSegClass, uint16_t idxOverlay, bool fRec32, uint16_t *pidxSeg) +{ + /* Search for name */ + for (unsigned iSegDef = 1; iSegDef < pOmfStuff->cSegDefs; iSegDef++) + { + POMFSEGDEF pSegDef = &pOmfStuff->paSegDefs[iSegDef]; + if (pSegDef->idxName == idxSegName) + { + if ( pSegDef->bSegAttr != bSegAttr + || pSegDef->f32bitRec != fRec32 + || pSegDef->idxName != idxSegName + || pSegDef->idxClass != idxSegClass + || pSegDef->idxOverlay != idxOverlay) + return error(pOmfStuff->pszFile, + "Existing SEGDEF differs: bSegAttr=%#x vs %#x, f32bitRec=%d vs %d, idxName=%#x vs %#x, idxClass=%#x vs %#x, idxOverlay=%#x vs %#x\n", + pSegDef->bSegAttr, bSegAttr, + pSegDef->f32bitRec, fRec32, + pSegDef->idxName, idxSegName, + pSegDef->idxClass, idxSegClass, + pSegDef->idxOverlay, idxOverlay); + *pidxSeg = iSegDef; + return true; + } + } + return omfDetails_AddSegDef(pOmfStuff, bSegAttr, cbSeg, idxSegName, idxSegClass, idxOverlay, fRec32, pidxSeg); +} + + +#if 0 /* unused */ +/** + * Looks up a GRPDEF in the . + * + * @returns Index (0..32K) if found, UINT16_MAX if not found. + * @param pOmfStuff The OMF stuff. + * @param pchName The name to look up. + * @param cchName The length of the name. + */ +static uint16_t omfDetails_GrpDefLookupN(POMFDETAILS pOmfStuff, const char *pchName, size_t cchName) +{ + unsigned iGrpDef = pOmfStuff->cGrpDefs; + while (iGrpDef-- > 0) + { + if ( pOmfStuff->paGrpDefs[iGrpDef].cchName == cchName + && memcmp(pOmfStuff->paGrpDefs[iGrpDef].pchName, pchName, cchName) == 0) + return iGrpDef; + } + return UINT16_MAX; +} +#endif + + +/** + * Adds an empty GRPDEF (always adds a new one). + * + * @returns success indicator. + * @param pOmfStuff The OMF stuff. + * @param idxGrpName The LNAMES index of the group name. + * @param pidxGrp Where to return the group index. + */ +static bool omfDetails_AddGrpDef(POMFDETAILS pOmfStuff, uint16_t idxGrpName, uint16_t *pidxGrp) +{ + Assert(idxGrpName < pOmfStuff->cLNames); + + OMF_GROW_TABLE_RET_ERR(OMFGRPDEF, pOmfStuff->paGrpDefs, pOmfStuff->cGrpDefs, 8); + POMFGRPDEF pGrpDef = &pOmfStuff->paGrpDefs[pOmfStuff->cGrpDefs]; + + pGrpDef->idxName = idxGrpName; + pGrpDef->cSegDefs = 0; + pGrpDef->paidxSegDefs = NULL; + + *pidxGrp = pOmfStuff->cGrpDefs; + pOmfStuff->cGrpDefs++; + return true; +} + + +/** + * Adds a segment to an existing GRPDEF. + * + * @returns success indicator. + * @param pOmfStuff The OMF stuff. + * @param idxGrp The GRPDEF index of the group to append a member to. + * @param idxSeg The SEGDEF index of the segment name. + */ +static bool omfDetails_AddSegToGrpDef(POMFDETAILS pOmfStuff, uint16_t idxGrp, uint16_t idxSeg) +{ + Assert(idxGrp < pOmfStuff->cGrpDefs && idxGrp > 0); + Assert(idxSeg < pOmfStuff->cSegDefs && idxSeg > 0); + + POMFGRPDEF pGrpDef = &pOmfStuff->paGrpDefs[idxGrp]; + OMF_GROW_TABLE_RET_ERR(uint16_t, pGrpDef->paidxSegDefs, pGrpDef->cSegDefs, 16); + pGrpDef->paidxSegDefs[pGrpDef->cSegDefs] = idxSeg; + pGrpDef->cSegDefs++; + + return true; +} + + +/** + * Marks 16-bit code segment groups that is used in the object file as needed. + * + * @param pOmfStuff The OMF stuff. + */ +static void convertOmfLookForNeededGroups(POMFDETAILS pOmfStuff) +{ + /* + * Consult the groups in question. We mark the groups which segments are + * included in the segment definitions as needed. + */ + unsigned i = RT_ELEMENTS(pOmfStuff->aGroups); + while (i-- > 0) + if (pOmfStuff->aGroups[i].pszSeg) + { + const char * const pszSegNm = pOmfStuff->aGroups[i].pszSeg; + size_t const cchSegNm = strlen(pszSegNm); + for (unsigned iSegDef = 0; iSegDef < pOmfStuff->cSegDefs; iSegDef++) + if ( pOmfStuff->paSegDefs[iSegDef].cchName == cchSegNm + && memcmp(pOmfStuff->paSegDefs[iSegDef].pchName, pszSegNm, cchSegNm) == 0) + { + pOmfStuff->aGroups[i].fNeeded = true; + break; + } + } +} + + +/** + * Adds necessary group and segment definitions. + * + * @returns success indicator. + * @param pOmfStuff The OMF stuff. + */ +static bool convertOmfAddNeededGrpDefs(POMFDETAILS pOmfStuff) +{ + /* + * Process the groups. + */ + unsigned j = RT_ELEMENTS(pOmfStuff->aGroups); + while (j-- > 0) + if (pOmfStuff->aGroups[j].fNeeded) + { + if (pOmfStuff->aGroups[j].idxName == UINT16_MAX) + { + Assert(pOmfStuff->aGroups[j].idxGroup == UINT16_MAX); + if (!omfDetails_AddLName(pOmfStuff, pOmfStuff->aGroups[j].pszName, &pOmfStuff->aGroups[j].idxName)) + return false; + } + if (pOmfStuff->aGroups[j].idxGroup == UINT16_MAX) + { + if (!omfDetails_AddGrpDef(pOmfStuff, pOmfStuff->aGroups[j].idxName, &pOmfStuff->aGroups[j].idxGroup)) + return false; + + if (pOmfStuff->aGroups[j].pszSeg) + { + /* We need the segment class name. */ + uint16_t idxSegClass; + if (!omfDetails_AddLName(pOmfStuff, pOmfStuff->aGroups[j].pszClass1, &idxSegClass)) + return false; + + /* Prep segment name buffer. */ + size_t cchSegNm = strlen(pOmfStuff->aGroups[j].pszSeg); + char szSegNm[256+16]; + Assert(cchSegNm < 256); + memcpy(szSegNm, pOmfStuff->aGroups[j].pszSeg, cchSegNm); + + /* Add the three segments. */ + static RTSTRTUPLE const s_aSuffixes[3] = { {RT_STR_TUPLE("_START")}, {RT_STR_TUPLE("")}, {RT_STR_TUPLE("_END")}, }; + for (unsigned iSuffix = 0; iSuffix < RT_ELEMENTS(s_aSuffixes); iSuffix++) + { + uint16_t idxSegNm; + memcpy(&szSegNm[cchSegNm], s_aSuffixes[iSuffix].psz, s_aSuffixes[iSuffix].cch + 1); + if (!omfDetails_AddLName(pOmfStuff, szSegNm, &idxSegNm)) + return false; + uint8_t const fAlign = iSuffix == 1 ? OMF_SEG_ATTR_ALIGN_BYTE : OMF_SEG_ATTR_ALIGN_PARA; + uint16_t idxSeg; + if (!omfDetails_AddSegDefIfNeeded(pOmfStuff, fAlign | OMF_SEG_ATTR_COMB_PUBLIC | OMF_SEG_ATTR_USE16, + 0, idxSegNm, idxSegClass, 1, false /*fRec*/, &idxSeg)) + return false; + if (!omfDetails_AddSegToGrpDef(pOmfStuff, pOmfStuff->aGroups[j].idxGroup, idxSeg)) + return false; + } + } + } + } + + /* + * Replace group references in the segment lines table. + */ + j = RT_ELEMENTS(pOmfStuff->aGroups); + while (j-- > 0) + if (pOmfStuff->aGroups[j].fNeeded) + for (unsigned i = 0; i < pOmfStuff->cSegLines; i++) + if (pOmfStuff->paSegLines[i].idxGrp == pOmfStuff->aGroups[j].idxReplaceGrp) + pOmfStuff->paSegLines[i].idxGrp = pOmfStuff->aGroups[j].idxGroup; + return true; +} + + +/** + * Adds the debug segment definitions (names too) to the OMF state. + * + * @returns success indicator. + * @param pOmfStuff The OMF stuff with CV8 line number info. + */ +static bool convertOmfAddDebugSegDefs(POMFDETAILS pOmfStuff) +{ + if ( pOmfStuff->cSegLines == 0 + || pOmfStuff->iSymbolsSeg != UINT16_MAX) + return true; + + /* + * Add the names we need. + */ + if ( pOmfStuff->iSymbolsNm == UINT16_MAX + && !omfDetails_AddLName(pOmfStuff, "$$SYMBOLS", &pOmfStuff->iSymbolsNm)) + return false; + if ( pOmfStuff->iDebSymNm == UINT16_MAX + && !omfDetails_AddLName(pOmfStuff, "DEBSYM", &pOmfStuff->iDebSymNm)) + return false; + + /* + * Add the segment definition. + */ + uint8_t bSegAttr = 0; + bSegAttr |= 5 << 5; /* A: dword alignment */ + bSegAttr |= 0 << 2; /* C: private */ + bSegAttr |= 0 << 1; /* B: not big */ + bSegAttr |= 1; /* D: use32 */ + + /* calc the segment size. */ + uint32_t cbSeg = 4; /* dword 4 */ + cbSeg += 4 + 4 + RT_ALIGN_32(pOmfStuff->cbStrTab, 4); + cbSeg += 4 + 4 + pOmfStuff->cSrcInfo * sizeof(pOmfStuff->paSrcInfo[0]); + uint32_t i = pOmfStuff->cSegLines; + while (i-- > 0) + if (pOmfStuff->paSegLines[i].cFiles > 0) + cbSeg += 4 + 4 + pOmfStuff->paSegLines[i].cb; + return omfDetails_AddSegDef(pOmfStuff, bSegAttr, cbSeg, pOmfStuff->iSymbolsNm, pOmfStuff->iDebSymNm, 1 /*idxOverlay*/, + true /*fRec32*/, &pOmfStuff->iSymbolsSeg); +} + + +/** + * Writes the debug segment data. + * + * @returns success indicator. + * @param pThis The OMF writer. + * @param pOmfStuff The OMF stuff with CV8 line number info. + */ +static bool convertOmfWriteDebugData(POMFWRITER pThis, POMFDETAILS pOmfStuff) +{ + if (pOmfStuff->cSegLines == 0) + return true; + Assert(pOmfStuff->iSymbolsSeg != UINT16_MAX); + + /* Begin and write the CV version signature. */ + if ( !omfWriter_LEDataBegin(pThis, pOmfStuff->iSymbolsSeg, 0) + || !omfWriter_LEDataAddU32(pThis, RTCVSYMBOLS_SIGNATURE_CV8)) + return false; + + /* + * Emit the string table (no fixups). + */ + uint32_t cbLeft = pOmfStuff->cbStrTab; + if ( !omfWriter_LEDataAddU32(pThis, RTCV8SYMBLOCK_TYPE_SRC_STR) + || !omfWriter_LEDataAddU32(pThis, cbLeft) + || !omfWriter_LEDataAddBytes(pThis, pOmfStuff->pchStrTab, RT_ALIGN_32(cbLeft, 4)) ) /* table is zero padded to nearest dword */ + return false; + + /* + * Emit the source file info table (no fixups). + */ + cbLeft = pOmfStuff->cSrcInfo * sizeof(pOmfStuff->paSrcInfo[0]); + if ( !omfWriter_LEDataAddU32(pThis, RTCV8SYMBLOCK_TYPE_SRC_INFO) + || !omfWriter_LEDataAddU32(pThis, cbLeft) + || !omfWriter_LEDataAddBytes(pThis, pOmfStuff->paSrcInfo, cbLeft) ) + return false; + + /* + * Emit the segment line numbers. There are two fixups here at the start + * of each chunk. + */ + POMFSEGLINES pSegLines = pOmfStuff->paSegLines; + uint32_t i = pOmfStuff->cSegLines; + while (i-- > 0) + { + if (pSegLines->cFiles) + { + /* Calc covered area. */ + uint32_t cbSectionCovered = 0; + uint32_t j = pSegLines->cFiles; + while (j-- > 0) + { + uint32_t offLast = pSegLines->paFiles[j].paPairs[pSegLines->paFiles[j].cPairs - 1].offSection; + if (offLast > cbSectionCovered) + offLast = cbSectionCovered; + } + + /* For simplicity and debuggability, just split the LEDATA here. */ + if ( !omfWriter_LEDataSplit(pThis) + || !omfWriter_LEDataAddU32(pThis, RTCV8SYMBLOCK_TYPE_SECT_LINES) + || !omfWriter_LEDataAddU32(pThis, pSegLines->cb) + || !omfWriter_LEDataAddU32(pThis, 0) /*RTCV8LINESHDR::offSection*/ + || !omfWriter_LEDataAddU16(pThis, 0) /*RTCV8LINESHDR::iSection*/ + || !omfWriter_LEDataAddU16(pThis, 0) /*RTCV8LINESHDR::u16Padding*/ + || !omfWriter_LEDataAddU32(pThis, cbSectionCovered) /*RTCV8LINESHDR::cbSectionCovered*/ ) + return false; + + /* Default to the segment (BS3TEXT32, BS3TEXT64) or the group (CGROUP16, + RMGROUP16, etc). The important thing is that we're framing the fixups + using a segment or group which ends up in the codeview segment map. */ + uint16_t idxFrame = pSegLines->idxSeg; + uint8_t bFrame = OMF_FIX_F_SEGDEF; + if (pSegLines->idxGrp != UINT16_MAX) + { + idxFrame = pSegLines->idxGrp; + bFrame = OMF_FIX_F_GRPDEF; + } + + /* Fixup #1: segment offset - IMAGE_REL_AMD64_SECREL. */ + if (!omfWriter_LEDataAddFixupNoDisp(pThis, 4 + 4 + RT_UOFFSETOF(RTCV8LINESHDR, offSection), OMF_FIX_LOC_32BIT_OFFSET, + bFrame, idxFrame, OMF_FIX_T_SEGDEF_NO_DISP, pSegLines->idxSeg)) + return false; + + + /* Fixup #2: segment number - IMAGE_REL_AMD64_SECTION. */ + if (!omfWriter_LEDataAddFixupNoDisp(pThis, 4 + 4 + RT_UOFFSETOF(RTCV8LINESHDR, iSection), OMF_FIX_LOC_16BIT_SEGMENT, + bFrame, idxFrame, OMF_FIX_T_SEGDEF_NO_DISP, pSegLines->idxSeg)) + return false; + + /* Emit data for each source file. */ + for (j = 0; j < pSegLines->cFiles; j++) + { + uint32_t const cbPairs = pSegLines->paFiles[j].cPairs * sizeof(RTCV8LINEPAIR); + if ( !omfWriter_LEDataAddU32(pThis, pSegLines->paFiles[j].offSrcInfo) /*RTCV8LINESSRCMAP::offSourceInfo*/ + || !omfWriter_LEDataAddU32(pThis, pSegLines->paFiles[j].cPairs) /*RTCV8LINESSRCMAP::cLines*/ + || !omfWriter_LEDataAddU32(pThis, cbPairs + sizeof(RTCV8LINESSRCMAP)) /*RTCV8LINESSRCMAP::cb*/ + || !omfWriter_LEDataAddBytes(pThis, pSegLines->paFiles[j].paPairs, cbPairs)) + return false; + } + } + pSegLines++; + } + + return omfWriter_LEDataEnd(pThis); +} + + +/** + * Writes out all the segment group definitions. + * + * @returns success indicator. + * @param pThis The OMF writer. + * @param pOmfStuff The OMF stuff containing the segment defs. + * @param pfFlushState Pointer to the flush state variable. + */ +static bool convertOmfWriteAllSegDefs(POMFWRITER pThis, POMFDETAILS pOmfStuff, int *pfFlushState) +{ + if (*pfFlushState > 0) + { + for (unsigned iSegDef = 1; iSegDef < pOmfStuff->cSegDefs; iSegDef++) + { + if (!(pOmfStuff->paSegDefs[iSegDef].f32bitRec + ? omfWriter_SegDef : omfWriter_SegDef16)(pThis, pOmfStuff->paSegDefs[iSegDef].bSegAttr, + pOmfStuff->paSegDefs[iSegDef].cbSeg, + pOmfStuff->paSegDefs[iSegDef].idxName, + pOmfStuff->paSegDefs[iSegDef].idxClass, + pOmfStuff->paSegDefs[iSegDef].idxOverlay)) + return false; + } + *pfFlushState = -1; + } + return true; +} + + +/** + * Writes out all the segment group definitions. + * + * @returns success indicator. + * @param pThis The OMF writer. + * @param pOmfStuff The OMF stuff containing the group defs. + * @param pfFlushState Pointer to the flush state variable. + */ +static bool convertOmfWriteAllGrpDefs(POMFWRITER pThis, POMFDETAILS pOmfStuff, int *pfFlushState) +{ + if (*pfFlushState > 0) + { + for (unsigned iGrpDef = 1; iGrpDef < pOmfStuff->cGrpDefs; iGrpDef++) + { + if (!omfWriter_GrpDefBegin(pThis, pOmfStuff->paGrpDefs[iGrpDef].idxName)) + return false; + for (unsigned iSegDef = 0; iSegDef < pOmfStuff->paGrpDefs[iGrpDef].cSegDefs; iSegDef++) + if (!omfWriter_GrpDefAddSegDef(pThis, pOmfStuff->paGrpDefs[iGrpDef].paidxSegDefs[iSegDef])) + return false; + if (!omfWriter_GrpDefEnd(pThis)) + return false; + } + *pfFlushState = -1; + } + return true; +} + + +/** + * This does the actual converting, passthru style. + * + * It only modifies, removes and inserts stuff it care about, the rest is passed + * thru as-is. + * + * @returns success indicator. + * @param pThis The OMF writer. + * @param pbFile The original file content. + * @param cbFile The size of the original file. + * @param pOmfStuff The OMF stuff we've gathered during the first pass, + * contains CV8 line number info if we converted anything. + * @param fConvertLineNumbers Whether we're converting line numbers and stuff. + */ +static bool convertOmfPassthru(POMFWRITER pThis, uint8_t const *pbFile, size_t cbFile, POMFDETAILS pOmfStuff, + bool fConvertLineNumbers) +{ + int fFlushLNames = 1; + int fFlushSegDefs = 1; + int fFlushGrpDefs = 1; + bool fSeenTheAdr = false; + bool fConvertFixupp = false; + + uint32_t off = 0; + while (off + 3 < cbFile) + { + uint8_t bRecType = pbFile[off]; + uint16_t cbRec = RT_MAKE_U16(pbFile[off + 1], pbFile[off + 2]); + uint32_t offRec = 0; + uint8_t const *pbRec = &pbFile[off + 3]; + +#define OMF_READ_IDX(a_idx, a_Name) \ + do { \ + a_idx = pbRec[offRec++]; \ + if ((a_idx) & 0x80) \ + a_idx = (((a_idx) & 0x7f) << 8) | pbRec[offRec++]; \ + } while (0) + +#define OMF_PEEK_IDX(a_idx, a_offRec) \ + do { \ + a_idx = pbRec[a_offRec]; \ + if ((a_idx) & 0x80) \ + a_idx = (((a_idx) & 0x7f) << 8) | pbRec[(a_offRec) + 1]; \ + } while (0) + + /* + * Remove/insert switch. will + */ + bool fSkip = false; + switch (bRecType) + { + /* + * Mangle watcom intrinsics if necessary. + */ + case OMF_EXTDEF: + if (pOmfStuff->fMayNeedMangling) + { + if (!omfWriter_ExtDefBegin(pThis)) + return false; + while (offRec + 1 < cbRec) + { + uint8_t cchName = pbRec[offRec++]; + char *pchName = (char *)&pbRec[offRec]; + offRec += cchName; + + uint16_t idxType; + OMF_READ_IDX(idxType, EXTDEF); + + /* Look for g_apszExtDefRenames entries that requires changing. */ + if ( cchName >= 5 + && cchName <= 7 + && pchName[0] == '_' + && pchName[1] == '_' + && ( pchName[2] == 'U' + || pchName[2] == 'I' + || pchName[2] == 'P') + && ( pchName[3] == '4' + || pchName[3] == '8' + || pchName[3] == 'I' + || pchName[3] == 'T') ) + { + char szName[12]; + memcpy(szName, pchName, cchName); + szName[cchName] = '\0'; + + uint32_t i = RT_ELEMENTS(g_apszExtDefRenames); + while (i-- > 0) + if ( cchName == (uint8_t)g_apszExtDefRenames[i][0] + && memcmp(&g_apszExtDefRenames[i][1], szName, cchName) == 0) + { + szName[0] = pOmfStuff->fProbably32bit ? '?' : '_'; + szName[1] = '?'; + break; + } + + if (!omfWriter_ExtDefAddN(pThis, szName, cchName, idxType, false /*fPrependUnderscore*/)) + return false; + } + else if (!omfWriter_ExtDefAddN(pThis, pchName, cchName, idxType, false /*fPrependUnderscore*/)) + return false; + } + if (!omfWriter_ExtDefEnd(pThis)) + return false; + fSkip = true; + } + break; + + /* + * Remove line number records. + */ + case OMF_LINNUM16: + case OMF_LINNUM32: + fSkip = fConvertLineNumbers; + break; + + /* + * Remove all but the first OMF_THEADR. + */ + case OMF_THEADR: + fSkip = fSeenTheAdr && fConvertLineNumbers; + fSeenTheAdr = true; + break; + + /* + * Remove borland source file changes. Also, make sure the group + * definitions are written out. + */ + case OMF_COMENT: + if (pbRec[1] == OMF_CCLS_LINK_PASS_SEP) + { + Assert(fFlushSegDefs <= 0); + if ( fFlushGrpDefs > 0 + && !convertOmfWriteAllGrpDefs(pThis, pOmfStuff, &fFlushGrpDefs)) + return false; + } + if (fConvertLineNumbers) + fSkip = pbRec[1] == OMF_CCLS_BORLAND_SRC_FILE; + break; + + /* + * Redo these so the OMF writer is on top of the index thing. + */ + case OMF_LNAMES: + if (fFlushLNames >= 0) + { + if (!omfWriter_LNamesBegin(pThis, false /*fAddZeroEntry*/)) + return false; + if (!fFlushLNames) + { + while (offRec + 1 < cbRec) + { + uint8_t cch = pbRec[offRec]; + const char *pch = (const char *)&pbRec[offRec + 1]; + if (!omfWriter_LNamesAddN(pThis, pch, cch, NULL)) + return false; + offRec += cch + 1; + } + } + else + { + /* Flush all LNAMES in one go. */ + for (unsigned i = 1; i < pOmfStuff->cLNames; i++) + if (!omfWriter_LNamesAddN(pThis, pOmfStuff->papchLNames[i] + 1, *pOmfStuff->papchLNames[i], NULL)) + return false; + fFlushLNames = -1; + } + if (!omfWriter_LNamesEnd(pThis)) + return false; + } + fSkip = true; + break; + + /* + * We may want to flush all the segments when we see the first one. + */ + case OMF_SEGDEF16: + case OMF_SEGDEF32: + fSkip = fFlushSegDefs != 0; + if (!convertOmfWriteAllSegDefs(pThis, pOmfStuff, &fFlushSegDefs)) + return false; + break; + + /* + * We may want to flush all the groups when we see the first one. + */ + case OMF_GRPDEF: + fSkip = fFlushGrpDefs != 0; + if (!convertOmfWriteAllGrpDefs(pThis, pOmfStuff, &fFlushGrpDefs)) + return false; + break; + + /* + * Hook LEDATA to flush groups and figure out when to convert FIXUPP records. + */ + case OMF_LEDATA16: + case OMF_LEDATA32: + if ( fFlushGrpDefs > 0 + && !convertOmfWriteAllGrpDefs(pThis, pOmfStuff, &fFlushGrpDefs)) + return false; + fConvertFixupp = false; +#if 0 + if ( g_f16BitWatcomC + && bRecType == OMF_LEDATA16) + { + /* Check if this is a code segment. */ + uint16_t idxSeg; + OMF_PEEK_IDX(idxSeg, offRec); + + } +#endif + break; + + + /* + * Convert fixups for 16-bit code segments to groups. + * Deals with switch table trouble. + */ + case OMF_FIXUPP16: + if (fConvertFixupp) + { + /* Gave up on this for now, easier to drop the eyecatcher in the _START segments. */ + } + break; + + /* + * Upon seeing MODEND we write out the debug info. + */ + case OMF_MODEND16: + case OMF_MODEND32: + if (fConvertLineNumbers) + if (!convertOmfWriteDebugData(pThis, pOmfStuff)) + return false; + break; + } + + /* + * Pass the record thru, if so was decided. + */ + if (!fSkip) + { + if ( omfWriter_RecBegin(pThis, bRecType) + && omfWriter_RecAddBytes(pThis, pbRec, cbRec) + && omfWriter_RecEnd(pThis, false)) + { /* likely */ } + else return false; + } + + /* advance */ + off += cbRec + 3; + } + + return true; +} + + +/** + * Converts LINNUMs and compiler intrinsics in an OMF object file. + * + * Wlink does a cheesy (to use their own term) job of generating the + * sstSrcModule subsection. It is limited to one file and cannot deal with line + * numbers in different segment. The latter is very annoying in assembly files + * that jumps between segments, these a frequent on crash stacks. + * + * The solution is to convert to the same line number tables that cl.exe /Z7 + * generates for our 64-bit C code, we named that format codeview v8, or CV8. + * Our code codeview debug info reader can deal with this already because of the + * 64-bit code, so Bob's your uncle. + * + * @returns success indicator. + * @param pszFile The name of the file being converted. + * @param pbFile The file content. + * @param cbFile The size of the file content. + * @param pDst The destiation (output) file. + */ +static bool convertOmfToOmf(const char *pszFile, uint8_t const *pbFile, size_t cbFile, FILE *pDst) +{ + bool const fConvertLineNumbers = true; + + /* + * Collect line number information, names, segment defintions, groups definitions and such. + */ + OMFDETAILS OmfStuff; + if (!collectOmfDetails(pszFile, pbFile, cbFile, &OmfStuff)) + return false; + + /* Mark groups for 16-bit code segments used by this object file as needed + so we can reframe fixups to these segments correctly. */ + convertOmfLookForNeededGroups(&OmfStuff); + + /* Add debug segments definitions. */ + bool fRc = true; + if (fConvertLineNumbers) + fRc = convertOmfAddDebugSegDefs(&OmfStuff); + + /* Add any additional group defintions we may need (for 16-bit code segs). */ + if (fRc) + fRc = convertOmfAddNeededGrpDefs(&OmfStuff); + if (fRc) + { + /* + * Instantiate the OMF writer and do pass-thru modifications. + */ + POMFWRITER pThis = omfWriter_Create(pszFile, 0, 0, pDst); + if (pThis) + { + fRc = convertOmfPassthru(pThis, pbFile, cbFile, &OmfStuff, fConvertLineNumbers); + omfWriter_Destroy(pThis); + } + else + fRc = false; + } + + /* + * Cleanup OmfStuff. + */ + uint32_t i = OmfStuff.cSegLines; + while (i-- >0) + { + uint32_t j = OmfStuff.paSegLines[i].cFiles; + while (j-- > 0) + free(OmfStuff.paSegLines[i].paFiles[j].paPairs); + free(OmfStuff.paSegLines[i].paFiles); + } + free(OmfStuff.paSegLines); + free(OmfStuff.paSrcInfo); + free(OmfStuff.pchStrTab); + + while (OmfStuff.pAllocHead) + { + POMFDETAILSALLOC pFreeMe = OmfStuff.pAllocHead; + OmfStuff.pAllocHead = OmfStuff.pAllocHead->pNext; + free(pFreeMe); + } + + return fRc; +} + + +/** + * Does the convertion using convertelf and convertcoff. + * + * @returns exit code (0 on success, non-zero on failure) + * @param pszFile The file to convert. + */ +static int convertit(const char *pszFile) +{ + /* Construct the filename for saving the unmodified file. */ + char szOrgFile[_4K]; + size_t cchFile = strlen(pszFile); + if (cchFile + sizeof(".original") > sizeof(szOrgFile)) + { + error(pszFile, "Filename too long!\n"); + return RTEXITCODE_FAILURE; + } + memcpy(szOrgFile, pszFile, cchFile); + memcpy(&szOrgFile[cchFile], ".original", sizeof(".original")); + + /* Read the whole file. */ + void *pvFile; + size_t cbFile; + if (readfile(pszFile, &pvFile, &cbFile)) + { + /* + * Do format conversions / adjustments. + */ + bool fRc = false; + uint8_t *pbFile = (uint8_t *)pvFile; + if ( cbFile > sizeof(Elf64_Ehdr) + && pbFile[0] == ELFMAG0 + && pbFile[1] == ELFMAG1 + && pbFile[2] == ELFMAG2 + && pbFile[3] == ELFMAG3) + { + if (writefile(szOrgFile, pvFile, cbFile)) + { + FILE *pDst = openfile(pszFile, true /*fWrite*/); + if (pDst) + { + fRc = convertElfToOmf(pszFile, pbFile, cbFile, pDst); + fRc = fclose(pDst) == 0 && fRc; + } + } + } + else if ( cbFile > sizeof(IMAGE_FILE_HEADER) + && RT_MAKE_U16(pbFile[0], pbFile[1]) == IMAGE_FILE_MACHINE_AMD64 + && RT_MAKE_U16(pbFile[2], pbFile[3]) * sizeof(IMAGE_SECTION_HEADER) + sizeof(IMAGE_FILE_HEADER) + < cbFile + && RT_MAKE_U16(pbFile[2], pbFile[3]) > 0) + { + if (writefile(szOrgFile, pvFile, cbFile)) + { + FILE *pDst = openfile(pszFile, true /*fWrite*/); + if (pDst) + { + fRc = convertCoffToOmf(pszFile, pbFile, cbFile, pDst); + fRc = fclose(pDst) == 0 && fRc; + } + } + } + else if ( cbFile >= 8 + && pbFile[0] == OMF_THEADR + && RT_MAKE_U16(pbFile[1], pbFile[2]) < cbFile) + { + if (writefile(szOrgFile, pvFile, cbFile)) + { + FILE *pDst = openfile(pszFile, true /*fWrite*/); + if (pDst) + { + fRc = convertOmfToOmf(pszFile, pbFile, cbFile, pDst); + fRc = fclose(pDst) == 0 && fRc; + } + } + } + else + fprintf(stderr, "error: Don't recognize format of '%s' (%#x %#x %#x %#x, cbFile=%lu)\n", + pszFile, pbFile[0], pbFile[1], pbFile[2], pbFile[3], (unsigned long)cbFile); + free(pvFile); + if (fRc) + return 0; + } + return 1; +} + + +int main(int argc, char **argv) +{ + int rcExit = 0; + + /* + * Scan the arguments. + */ + for (int i = 1; i < argc; i++) + { + if (argv[i][0] == '-') + { + const char *pszOpt = &argv[i][1]; + if (*pszOpt == '-') + { + /* Convert long options to short ones. */ + pszOpt--; + if (!strcmp(pszOpt, "--wcc")) + pszOpt = "w"; + else if (!strcmp(pszOpt, "--verbose")) + pszOpt = "v"; + else if (!strcmp(pszOpt, "--version")) + pszOpt = "V"; + else if (!strcmp(pszOpt, "--help")) + pszOpt = "h"; + else + { + fprintf(stderr, "syntax errro: Unknown options '%s'\n", pszOpt); + return 2; + } + } + + /* Process the list of short options. */ + while (*pszOpt) + { + switch (*pszOpt++) + { + case 'w': + g_f16BitWatcomC = true; + break; + + case 'v': + g_cVerbose++; + break; + + case 'V': + printf("%s\n", "$Revision: 153224 $"); + return 0; + + case '?': + case 'h': + printf("usage: %s [options] -o <output> <input1> [input2 ... [inputN]]\n", + argv[0]); + return 0; + } + } + } + else + { + /* + * File to convert. Do the job right away. + */ + rcExit = convertit(argv[i]); + if (rcExit != 0) + break; + } + } + + return rcExit; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/asmdefs-first.mac b/src/VBox/ValidationKit/bootsectors/bs3kit/asmdefs-first.mac new file mode 100644 index 00000000..9baa043b --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/asmdefs-first.mac @@ -0,0 +1,62 @@ +; $Id: asmdefs-first.mac $ +;; @file +; BS3Kit - Included by asmdefs.mac when assembling IPRT code. +; +; This will only be included if asmdefs.mac is included before bs3kit.mac, so +; it will not be used for bs3*.asm files, only IPRT ones. +; + +; +; Copyright (C) 2006-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%ifndef ___asmdefs_first_mac +%define ___asmdefs_first_mac + +%include "bs3kit-template-header.mac" + +; +; Redefine some macros to suite us. +; +; We do near 16-bit code and produce far stubs separately as needed. +; +%define BEGINCODE TMPL_BEGIN_TEXT +%define BEGINPROC_EXPORTED BS3_BEGINPROC_EXPORTED_WRAPPER +%define ENDPROC BS3_PROC_END_CMN +%undef NAME +%define NAME(a) BS3_CMN_NM(a) + +%macro BS3_BEGINPROC_EXPORTED_WRAPPER 1 +BS3_PROC_BEGIN_CMN %1, BS3_PBC_NEAR +%endmacro + +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-bootsector.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-bootsector.asm new file mode 100644 index 00000000..eb5311ac --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-bootsector.asm @@ -0,0 +1,594 @@ +; $Id: bs3-bootsector.asm $ +;; @file +; Generic bootsector for BS3. +; +; This sets up stack at %fff0 and loads the next sectors from the floppy at +; %10000 (1000:0000 in real mode), then starts executing at cs:ip=1000:0000. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit.mac" +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" + + +;********************************************************************************************************************************* +;* Defined Constants And Macros * +;********************************************************************************************************************************* +;; Enabled faster loading. +%define BS3KIT_BOOTSECTOR_FASTER_LOAD +;; Enabled load progress dots. +%define BS3KIT_BOOTSECTOR_LOAD_DOTS + +;; Halts on failure location. For debugging. +;%define HLT_ON_FAILURE 1 + +;; Enables saving of initial register state. +;; Dropping this is useful for making more room for debugging. +%define BS3KIT_BOOTSECTOR_SAVE_INITIAL_STATE + + +%ifdef __YASM__ +[map all] +%endif + +; +; Start with a jump just to follow the convention. +; Also declare all segments/sections to establish them and their order. +; + ORG 07c00h + +BITS 16 +CPU 8086 +start: + jmp short bs3InitCode + db 0ah ; Should be nop, but this looks better. +g_OemId: ; 003h + db 'BS3Kit', 0ah, 0ah + +; +; DOS 4.0 Extended Bios Parameter Block: +; +g_cBytesPerSector: ; 00bh + dw 512 +g_cSectorsPerCluster: ; 00dh + db 1 +g_cReservedSectors: ; 00eh + dw 1 +g_cFATs: ; 010h + db 0 +g_cRootDirEntries: ; 011h + dw 0 +g_cTotalSectors: ; 013h + dw 0 +g_bMediaDescriptor: ; 015h + db 0 +g_cSectorsPerFAT: ; 016h + dw 0 +g_cPhysSectorsPerTrack: ; 018h + dw 18 +g_cHeads: ; 01ah + dw 2 +g_cHiddentSectors: ; 01ch + dd 1 +g_cLargeTotalSectors: ; 020h - We (ab)use this to indicate the number of sectors to load. + dd 0 +g_bBootDrv: ; 024h + db 80h +g_bFlagsEtc: ; 025h + db 0 +g_bExtendedSignature: ; 026h + db 0x29 +g_dwSerialNumber: ; 027h + dd 0x0a458634 +g_abLabel: ; 02bh + db 'VirtualBox', 0ah +g_abFSType: ; 036h + db 'RawCode', 0ah +g_BpbEnd: ; 03ch + + +; +; Where to real init code starts. +; +bs3InitCode: + cli + +%ifdef BS3KIT_BOOTSECTOR_SAVE_INITIAL_STATE + ; save the registers. + mov [cs:BS3_ADDR_REG_SAVE + BS3REGCTX.rax], ax + mov [cs:BS3_ADDR_REG_SAVE + BS3REGCTX.ds], ds +%endif + + ; set up the DS segment reister so we can skip the CS prefix when saving more prefixes.. + mov ax, 0 + mov ds, ax + +%ifdef BS3KIT_BOOTSECTOR_SAVE_INITIAL_STATE + mov [BS3_ADDR_REG_SAVE + BS3REGCTX.rdi], di + mov di, BS3_ADDR_REG_SAVE + mov [di + BS3REGCTX.rsp], sp + mov [di + BS3REGCTX.ss], ss + mov [di + BS3REGCTX.rcx], cx + mov [di + BS3REGCTX.es], es + mov [di + BS3REGCTX.rbp], bp +%endif + + ; set up the stack. + mov ss, ax + mov sp, BS3_ADDR_STACK + + ; Load es and setup bp frame. + mov es, ax + mov bp, sp +%if 0 + mov [bp], ax ; clear the first 8 bytes (terminates the ebp chain) + mov [bp + 02h], ax + mov [bp + 04h], ax + mov [bp + 06h], ax +%else + mov di, sp ; Combine clearing the rbp chain and register save area. +%endif + + ; Save flags now that we know that there's a valid stack. + pushf + + ; + ; Clear the register area. + ; +%if 0 + mov di, BS3_ADDR_REG_SAVE + mov cx, BS3REGCTX_size/2 +%else + mov cx, (BS3_ADDR_LOAD - BS3_ADDR_STACK) / 2 +%endif + cld + rep stosw + + ; + ; Do basic CPU detection. + ; + + ; 0. Load the register save area address into DI to avoid absolute addressing + ; when saving additional state. To avoid disp16, offset the address. + mov di, BS3_ADDR_REG_SAVE + 0x70 + + ; 1. bit 15-bit was fixed to 1 in pre-286 CPUs, and fixed to 0 in 286+. + mov ax, [bp - 2] + test ah, 080h ; always set on pre 286, clear on 286 and later + jnz .pre_80286 + + ; 2. On a 286 you cannot popf IOPL and NT from real mode. +.detect_286_or_386plus: +CPU 286 + mov ah, (X86_EFL_IOPL | X86_EFL_NT) >> 8 + push ax + popf + pushf + cmp ah, [bp - 3] + pop ax + je .is_386plus +.is_80286: +CPU 286 +%ifdef BS3KIT_BOOTSECTOR_SAVE_INITIAL_STATE + smsw [di + BS3REGCTX.cr0 - 0x70] +%endif +.pre_80286: +CPU 8086 +%ifdef BS3KIT_BOOTSECTOR_SAVE_INITIAL_STATE + mov [di - 0x70 + BS3REGCTX.rbx], bx + mov [di - 0x70 + BS3REGCTX.rdx], dx + mov [di - 0x70 + BS3REGCTX.rsi], si +%endif + jmp .do_load + + ; Save 386 registers. We can now skip the CS prefix as DS is flat. +CPU 386 +.is_386plus: +%ifdef BS3KIT_BOOTSECTOR_SAVE_INITIAL_STATE + shr eax, 16 + mov [di - 0x70 + BS3REGCTX.rax+2], ax + mov eax, esp + shr eax, 16 + mov [di - 0x70 + BS3REGCTX.rsp+2], ax + mov eax, ebp + shr eax, 16 + mov [di - 0x70 + BS3REGCTX.rbp+2], ax + mov eax, edi + shr eax, 16 + mov [di - 0x70 + BS3REGCTX.rdi+2], ax + shr ecx, 16 + mov [di - 0x70 + BS3REGCTX.rcx+2], cx + mov [di - 0x70 + BS3REGCTX.fs], fs + mov [di - 0x70 + BS3REGCTX.gs], gs + mov [di - 0x70 + BS3REGCTX.rbx], ebx + mov [di - 0x70 + BS3REGCTX.rdx], edx + mov [di - 0x70 + BS3REGCTX.rsi], esi + mov eax, cr2 + mov [di - 0x70 + BS3REGCTX.cr2], eax + mov eax, cr3 + mov [di - 0x70 + BS3REGCTX.cr3], eax + mov byte [di - 0x70 + BS3REGCTX.bMode], BS3_MODE_RM + mov [di - 0x70 + BS3REGCTX.cs], cs + xor eax, eax + mov ax, start + mov [di - 0x70 + BS3REGCTX.rip], eax + + ; Pentium/486+: CR4 requires VME/CPUID, so we need to detect that before accessing it. + mov [di - 0x70 + BS3REGCTX.cr4], eax + popf ; (restores IOPL+NT) + pushfd + pop eax + mov [di - 0x70 + BS3REGCTX.rflags], eax + xor eax, X86_EFL_ID + push eax + popfd + pushfd + pop ebx + cmp ebx, eax + jne .no_cr4 + mov eax, cr4 + mov [di - 0x70 + BS3REGCTX.cr4], eax +.no_cr4: +%endif + ; Make sure caching is enabled and alignment is off. + mov eax, cr0 +%ifdef BS3KIT_BOOTSECTOR_SAVE_INITIAL_STATE + mov [di - 0x70 + BS3REGCTX.cr0], eax +%endif + and eax, ~(X86_CR0_NW | X86_CR0_CD | X86_CR0_AM) + mov cr0, eax + + ; Load all the code. +.do_load + mov [g_bBootDrv], dl + call NAME(bs3InitLoadImage) +%if 0 + mov al, '=' + call bs3PrintChrInAl +%endif + + ; + ; Call the user 'main' procedure (shouldn't return). + ; + cld + call BS3_SEL_TEXT16:0000h + + ; Panic/hang. +Bs3Panic: + cli + jmp Bs3Panic + + +;; For debug and error handling. +; @uses ax +bs3PrintHexInAl: +CPU 286 + push ax + shr al, 4 + call bs3PrintHexDigitInAl + pop ax +bs3PrintHexDigitInAl: + and al, 0fh + cmp al, 10 + jb .decimal + add al, 'a' - '0' - 10 +.decimal: + add al, '0' +bs3PrintChrInAl: + push bx + mov ah, 0eh + mov bx, 0ff00h + int 10h + pop bx + ret + + +;; +; Loads the image off the floppy. +; +; This uses g_cLargeTotalSectors to figure out how much to load. +; +; Clobbers everything except ebp and esp. Panics on failure. +; +; @param dl The boot drive number (from BIOS). +; @uses ax, cx, bx, esi, di +; +BEGINPROC bs3InitLoadImage + push bp + mov bp, sp + push es +%define bSavedDiskNo byte [bp - 04h] + push dx +%define bMaxSector byte [bp - 06h] +%define wMaxSector word [bp - 06h] + xor ax, ax + push ax +%define bMaxHead byte [bp - 08h] + push ax + + ; + ; Try figure the geometry. + ; + mov ah, 08h + int 13h +%ifndef HLT_ON_FAILURE + jc .failure +%else + jnc .ok_geometry_call + cli + hlt +.ok_geometry_call: +%endif + and cl, 63 ; only the sector count. + mov bMaxSector, cl + mov bMaxHead, dh + mov dl, bSavedDiskNo + +%if 0 ; bMaxSector=0x12 (18); bMaxHead=0x01; bMaxCylinder=0x4f (79) + mov al, 'S' + call bs3PrintChrInAl + mov al, bMaxSector + call bs3PrintHexInAl + mov al, 'H' + call bs3PrintChrInAl + mov al, bMaxHead + call bs3PrintHexInAl + mov al, 'C' + call bs3PrintChrInAl + mov al, ch ; first 8-bit of cylinder count. + call bs3PrintHexInAl + mov al, ';' + call bs3PrintChrInAl +%endif + +%ifndef BS3KIT_BOOTSECTOR_FASTER_LOAD + ; + ; Load the sectors following the boot sector one at a time (avoids problems). + ; + mov si, [g_cLargeTotalSectors] ; 16-bit sector count ==> max 512 * 65 535 = 33 553 920 bytes. + dec si ; Practically max: ca 575 KB, or 1150 sectors. Linker set BS3_MAX_SIZE to 480KB. + + mov di, BS3_ADDR_LOAD / 16 ; The current load segment. + mov cx, 0002h ; ch/cylinder=0 (0-based); cl/sector=2 (1-based) + xor dh, dh ; dh/head=0 +.the_load_loop: + %if 0 + mov al, 'c' + call bs3PrintChrInAl + mov al, ch + call bs3PrintHexInAl + mov al, 's' + call bs3PrintChrInAl + mov al, cl + call bs3PrintHexInAl + mov al, 'h' + call bs3PrintChrInAl + mov al, dh + call bs3PrintHexInAl + mov al, ';' + call bs3PrintChrInAl + %elifdef BS3KIT_BOOTSECTOR_LOAD_DOTS + mov al, '.' + call bs3PrintChrInAl + %endif + xor bx, bx + mov es, di ; es:bx -> buffer + mov ax, 0201h ; al=1 sector; ah=read function + int 13h + %ifndef HLT_ON_FAILURE + jc .failure + %else + jnc .read_ok + cli + hlt +.read_ok: + %endif + + ; advance to the next sector/head/cylinder. + inc cl + cmp cl, bMaxSector + jbe .adv_addr + + mov cl, 1 + inc dh + cmp dh, bMaxHead + jbe .adv_addr + + mov dh, 0 + inc ch + +.adv_addr: + add di, 512 / 16 + dec si + jnz .the_load_loop + +%else ; BS3KIT_BOOTSECTOR_FASTER_LOAD + ; + ; Load the sectors following the boot sector, trying to load a whole + ; side in each bios call, falling back on single sector reads if we + ; run into DMA 64KB boundrary issues (BIOS must tell us). + ; + mov si, [g_cLargeTotalSectors] ; 16-bit sector count ==> max 512 * 65 535 = 33 553 920 bytes. + dec si ; Skip the boot sector, it's not part of the test image we execute. + mov di, BS3_ADDR_LOAD / 16 ; The current load segment. + mov cx, 0002h ; ch/cylinder=0 (0-based); cl/sector=0 (1-based) + xor dh, dh ; dh/head=0 +.the_load_loop: + %if 0 + mov al, 'c' + call bs3PrintChrInAl + mov al, ch + call bs3PrintHexInAl + mov al, 's' + call bs3PrintChrInAl + mov al, cl + call bs3PrintHexInAl + mov al, 'h' + call bs3PrintChrInAl + mov al, dh + call bs3PrintHexInAl + mov al, ';' + call bs3PrintChrInAl + %elifdef BS3KIT_BOOTSECTOR_LOAD_DOTS + mov al, '.' + call bs3PrintChrInAl + %endif + mov ax, wMaxSector ; read to the end of the side by default. + sub al, cl + inc al +.read_again: + cmp si, ax + jae .do_read + mov ax, si +.do_read: + mov ah, 02h ; ah=read function + xor bx, bx + mov es, di ; es:bx -> buffer + int 13h + jnc .advance_sector + + cmp ah, 9 ; DMA 64KB crossing error +%if 0 ; This hack doesn't work. If the FDC is in single sided mode we end up with a garbled image. Probably "missing" sides. + je .read_one + + cmp ah, 20h ; Controller error, probably because we're reading side 1 on a single sided floppy + jne .failure + cmp bMaxHead, 0 + je .failure + cmp dh, 1 + jne .failure + xor dh, dh + mov bMaxHead, dh + inc ch + jmp .the_load_loop +.read_one: +%elifdef HLT_ON_FAILURE + je .read_one_ok + cli + hlt +.read_one_ok: +%else + jne .failure +%endif + mov ax, 1 ; Retry reading a single sector. + jmp .read_again + + ; advance to the next sector/head/cylinder and address. +.advance_sector: + inc cl + cmp cl, bMaxSector + jbe .adv_addr + + mov cl, 1 + inc dh + cmp dh, bMaxHead + jbe .adv_addr + + mov dh, 0 + inc ch + +.adv_addr: + dec si + jz .done_reading + add di, 512 / 16 + dec al + jnz .advance_sector + jmp .the_load_loop + +.done_reading: +%endif ; BS3KIT_BOOTSECTOR_FASTER_LOAD +%if 0 + mov al, 'D' + call bs3PrintChrInAl +%elifdef BS3KIT_BOOTSECTOR_LOAD_DOTS + mov al, 13 + call bs3PrintChrInAl + mov al, 10 + call bs3PrintChrInAl +%endif + + add sp, 2*2 + pop dx + pop es + pop bp + ret + +%ifndef HLT_ON_FAILURE + ; + ; Something went wrong, display a message. + ; +.failure: + %if 1 ; Disable to save space for debugging. + %if 1 + push ax + %endif + + ; print message + mov si, .s_szErrMsg +.failure_next_char: + lodsb + call bs3PrintChrInAl + cmp si, .s_szErrMsgEnd + jb .failure_next_char + + ; panic + %if 1 + pop ax + mov al, ah + push bs3PrintHexInAl + %endif + call Bs3Panic +.s_szErrMsg: + db 13, 10, 'rd err! ' + %else + hlt + jmp .failure + %endif +%endif +.s_szErrMsgEnd: +;ENDPROC bs3InitLoadImage - don't want the padding. + + +; +; Pad the remainder of the sector with int3's and end it with the DOS signature. +; +bs3Padding: + times ( 510 - ( (bs3Padding - start) % 512 ) ) db 0cch + db 055h, 0aah + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c16-CreateHybridFarRet.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c16-CreateHybridFarRet.asm new file mode 100644 index 00000000..671d7838 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c16-CreateHybridFarRet.asm @@ -0,0 +1,63 @@ +; $Id: bs3-c16-CreateHybridFarRet.asm $ +;; @file +; BS3Kit - Bs3A20Disable. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "bs3kit.mac" + + +;; +; Worker for BS3_PROC_BEGIN_CMN +; @uses nothing +BS3_PROC_BEGIN Bs3CreateHybridFarRet_c16 + push ax ; reserve space + push bp + mov bp, sp + push ax ; save it + + ; Move the return address up a word. + mov ax, [bp + 4] + mov [bp + 2], ax + ; Move the caller's return address up a word. + mov ax, [bp + 6] + mov [bp + 4], ax + ; Add CS to the caller's far return address. + mov [bp + 6], cs + + pop ax + pop bp + ret +BS3_PROC_END Bs3CreateHybridFarRet_c16 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c16-SwitchFromV86To16BitAndCallC.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c16-SwitchFromV86To16BitAndCallC.asm new file mode 100644 index 00000000..b0ab6e3a --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c16-SwitchFromV86To16BitAndCallC.asm @@ -0,0 +1,109 @@ +; $Id: bs3-c16-SwitchFromV86To16BitAndCallC.asm $ +;; @file +; BS3Kit - Bs3SwitchFromV86To16BitAndCallC +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" +%if TMPL_BITS != 16 + %error "16-bit only" +%endif + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +%ifdef BS3_STRICT +BS3_EXTERN_DATA16 g_bBs3CurrentMode +TMPL_BEGIN_TEXT +BS3_EXTERN_CMN Bs3Panic +%endif +BS3_EXTERN_CMN Bs3SwitchTo16Bit +BS3_EXTERN_CMN Bs3SwitchTo16BitV86 +BS3_EXTERN_CMN Bs3SelRealModeCodeToProtMode + + +;; +; @cproto BS3_CMN_PROTO_STUB(int, Bs3SwitchFromV86To16BitAndCallC,(FPFNBS3FAR fpfnCall, unsigned cbParams, ...)); +; +BS3_PROC_BEGIN_CMN Bs3SwitchFromV86To16BitAndCallC, BS3_PBC_HYBRID + inc bp + push bp + mov bp, sp + + ; + ; Push the arguments first. + ; + mov ax, si ; save si + mov si, [bp + 2 + cbCurRetAddr + 4] +%ifdef BS3_STRICT + test si, 1 + jz .cbParams_ok + call Bs3Panic +.cbParams_ok: + test byte [g_bBs3CurrentMode], BS3_MODE_CODE_V86 + jnz .mode_ok + call Bs3Panic +.mode_ok: +%endif + +.push_more: + push word [bp + 2 + cbCurRetAddr + 4 + 2 + si - 2] + sub si, 2 + jnz .push_more + mov si, ax ; restore si + + ; + ; Convert the code segment to a 16-bit prot mode selector + ; + push word [bp + 2 + cbCurRetAddr + 2] + call Bs3SelRealModeCodeToProtMode + mov [bp + 2 + cbCurRetAddr + 2], ax + add sp, 2 + + ; + ; Switch mode. + ; + call Bs3SwitchTo16Bit + call far [bp + 2 + cbCurRetAddr] + call Bs3SwitchTo16BitV86 + + mov sp, bp + pop bp + dec bp + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3SwitchFromV86To16BitAndCallC + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c16-Trap16Generic.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c16-Trap16Generic.asm new file mode 100644 index 00000000..3ce0ed7b --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c16-Trap16Generic.asm @@ -0,0 +1,720 @@ +; $Id: bs3-c16-Trap16Generic.asm $ +;; @file +; BS3Kit - Trap, 16-bit assembly handlers. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + +%ifndef TMPL_16BIT + %error "16-bit only template" +%endif + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_EXTERN_DATA16 g_bBs3CurrentMode +BS3_EXTERN_DATA16 g_uBs3TrapEipHint +BS3_EXTERN_DATA16 g_uBs3CpuDetected +BS3_EXTERN_DATA16 g_apfnBs3TrapHandlers_c16 +BS3_EXTERN_SYSTEM16 Bs3Gdt +TMPL_BEGIN_TEXT +BS3_EXTERN_CMN Bs3TrapDefaultHandler +BS3_EXTERN_CMN Bs3RegCtxRestore +TMPL_BEGIN_TEXT + + +;; +; Generic entry points for IDT handlers, 8 byte spacing. +; +BS3_PROC_BEGIN _Bs3Trap16GenericEntries +BS3_PROC_BEGIN Bs3Trap16GenericEntries +%macro Bs3Trap16GenericEntryNoErr 1 + push byte 0 ; 2 byte: fake error code + db 06ah, i ; 2 byte: push imm8 - note that this is a signextended value. + jmp %1 ; 3 byte + ALIGNCODE(8) +%assign i i+1 +%endmacro + +%macro Bs3Trap16GenericEntryErrCd 1 + db 06ah, i ; 2 byte: push imm8 - note that this is a signextended value. + jmp %1 ; 3 byte + ALIGNCODE(8) +%assign i i+1 +%endmacro + +%assign i 0 ; start counter. + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 0 + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 1 + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 2 + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 3 + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 4 + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 5 + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 6 + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 7 + Bs3Trap16GenericEntryErrCd bs3Trap16GenericTrapOrInt ; 8 + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 9 + Bs3Trap16GenericEntryErrCd bs3Trap16GenericTrapOrInt ; a + Bs3Trap16GenericEntryErrCd bs3Trap16GenericTrapOrInt ; b + Bs3Trap16GenericEntryErrCd bs3Trap16GenericTrapOrInt ; c + Bs3Trap16GenericEntryErrCd bs3Trap16GenericTrapOrInt ; d + Bs3Trap16GenericEntryErrCd bs3Trap16GenericTrapOrInt ; e + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; f (reserved) + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 10 + Bs3Trap16GenericEntryErrCd bs3Trap16GenericTrapOrInt ; 11 + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 12 + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 13 + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 14 + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 15 (reserved) + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 16 (reserved) + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 17 (reserved) + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 18 (reserved) + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 19 (reserved) + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 1a (reserved) + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 1b (reserved) + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 1c (reserved) + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 1d (reserved) + Bs3Trap16GenericEntryErrCd bs3Trap16GenericTrapOrInt ; 1e + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt ; 1f (reserved) +%rep 224 + Bs3Trap16GenericEntryNoErr bs3Trap16GenericTrapOrInt +%endrep +BS3_PROC_END Bs3Trap16GenericEntries +AssertCompile(Bs3Trap16GenericEntries_EndProc - Bs3Trap16GenericEntries == 8*256) + + +;; +; Trap or interrupt with error code, faked if necessary. +; +; Note! This code is going to "misbehave" if the high word of ESP is not cleared. +; +BS3_PROC_BEGIN _bs3Trap16GenericTrapOrInt +BS3_PROC_BEGIN bs3Trap16GenericTrapOrInt +CPU 386 + jmp near bs3Trap16GenericTrapErrCode80286 ; Bs3Trap16Init adjusts this on 80386+ + push ebp + movzx ebp, sp + push ebx ; BP - 04h + pushfd ; BP - 08h + cld + push edx ; BP - 0ch + push ss ; BP - 0eh + push esp ; BP - 12h + + ; + ; We may be comming from 32-bit code where SS is flat and ESP has a non- + ; zero high word. We need to thunk it for C code to work correctly with + ; [BP+xx] and [SS:BX+xx] style addressing that leaves out the high word. + ; + ; Note! Require ring-0 handler for non-standard stacks (SS.DPL must equal CPL). + ; + mov bx, ss + lar ebx, bx + test ebx, X86LAR_F_D + jz .stack_fine + test esp, 0ffff0000h + jnz .stack_thunk +.stack_load_r0_ss16: + mov bx, ss + and bl, 3 + AssertCompile(BS3_SEL_RING_SHIFT == 8) + mov bh, bl + add bx, BS3_SEL_R0_SS16 + jmp .stack_load_bx_into_ss +.stack_thunk: + mov ebx, esp + shr ebx, 16 + shl ebx, X86_SEL_SHIFT + add ebx, BS3_SEL_TILED_R0 + cmp ebx, BS3_SEL_TILED_R0_LAST + ja .stack_esp_out_of_bounds +.stack_load_bx_into_ss: + mov ss, bx +.stack_fine: + movzx esp, sp + + ; Reserve space for the register and trap frame. + mov bx, (BS3TRAPFRAME_size + 7) / 8 +.more_zeroed_space: + push 0 + push 0 + push 0 + push 0 + dec bx + jnz .more_zeroed_space + movzx ebx, sp + + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rax], eax + mov edx, [bp - 12h] ; This isn't quite right for wrap arounds, but close enough for now + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rsp], edx ; high bits + mov [ss:bx + BS3TRAPFRAME.uHandlerRsp], edx ; high bits + mov dx, [bp - 0eh] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.ss], dx + mov [ss:bx + BS3TRAPFRAME.uHandlerSs], dx + mov edx, [bp - 0ch] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rdx], edx + mov edx, [bp - 8] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rflags], edx ; high bits + mov [ss:bx + BS3TRAPFRAME.fHandlerRfl], edx + mov edx, [bp - 4] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rbx], edx + mov edx, [bp] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rbp], edx + + mov dl, [bp + 4] + mov [ss:bx + BS3TRAPFRAME.bXcpt], dl + + mov dx, [bp + 6] +;; @todo Do voodoo checks for 'int xx' or misguided hardware interrupts. + mov [ss:bx + BS3TRAPFRAME.uErrCd], dx + + add bp, 6 ; adjust so it points to the word before the iret frame. + xor dx, dx + jmp bs3Trap16GenericCommon + +.stack_esp_out_of_bounds: +%ifdef BS3_STRICT + int3 +%endif + jmp .stack_esp_out_of_bounds +BS3_PROC_END bs3Trap16GenericTrapErrCode + +;; +; Trap with error code - 80286 code variant. +; +BS3_PROC_BEGIN bs3Trap16GenericTrapErrCode80286 +CPU 286 + push bp + mov bp, sp + push bx + pushf + cld + + ; Reserve space for the register and trap frame. + mov bx, (BS3TRAPFRAME_size + 7) / 8 +.more_zeroed_space: + push 0 + push 0 + push 0 + push 0 + dec bx + jnz .more_zeroed_space + mov bx, sp + + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rax], ax + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.ss], ss + mov [ss:bx + BS3TRAPFRAME.uHandlerSs], ss + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rdx], dx + mov dx, [bp - 4] + mov [ss:bx + BS3TRAPFRAME.fHandlerRfl], dx + mov dx, [bp - 2] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rbx], dx + mov dx, [bp] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rbp], dx + + mov dl, [bp + 2] + mov [ss:bx + BS3TRAPFRAME.bXcpt], dl + + mov dx, [bp + 4] +;; @todo Do voodoo checks for 'int xx' or misguided hardware interrupts. + mov [ss:bx + BS3TRAPFRAME.uErrCd], dx + + add bp, 4 ; adjust so it points to the word before the iret frame. + mov dl, 1 + jmp bs3Trap16GenericCommon +BS3_PROC_END bs3Trap16GenericTrapErrCode80286 + + +;; +; Common context saving code and dispatching. +; +; @param bx Pointer to the trap frame, zero filled. The following members +; have been filled in by the previous code: +; - bXcpt +; - uErrCd +; - fHandlerRFL +; - Ctx.eax +; - Ctx.edx +; - Ctx.ebx +; - Ctx.ebp +; - Ctx.rflags - high bits only. +; - Ctx.esp - high bits only. +; - Ctx.ss - for same cpl frames +; - All other bytes are zeroed. +; +; @param bp Pointer to the word before the iret frame, i.e. where bp +; would be saved if this was a normal near call. +; @param dx One (1) if 286, zero (0) if 386+. +; +BS3_PROC_BEGIN bs3Trap16GenericCommon +CPU 286 + ; + ; Fake EBP frame. + ; + mov ax, [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rbp] + mov [bp], ax + + ; + ; Save the remaining GPRs and segment registers. + ; + test dx, dx + jnz .save_word_grps +CPU 386 + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rcx], ecx + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rdi], edi + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rsi], esi + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.fs], fs + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.gs], gs + jmp .save_segment_registers +.save_word_grps: +CPU 286 + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rcx], cx + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rdi], di + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rsi], si +.save_segment_registers: + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.ds], ds + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.es], es + mov [ss:bx + BS3TRAPFRAME.uHandlerCs], cs + + ; + ; Load 16-bit data selector for the DPL we're executing at into DS and ES. + ; + mov ax, ss + and ax, 3 + mov cx, ax + shl ax, BS3_SEL_RING_SHIFT + or ax, cx + add ax, BS3_SEL_R0_DS16 + mov ds, ax + mov es, ax + + ; + ; Copy and update the mode now that we've got a flat DS. + ; + mov al, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.bMode], al + mov cl, al + and cl, ~BS3_MODE_CODE_MASK + or cl, BS3_MODE_CODE_16 + mov [BS3_DATA16_WRT(g_bBs3CurrentMode)], cl + + ; + ; Copy iret info. + ; + lea cx, [bp + 2] + mov [ss:bx + BS3TRAPFRAME.uHandlerRsp], cx + mov cx, [bp + 2] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rip], cx + mov cx, [bp + 6] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rflags], cx + mov cx, [bp + 4] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.cs], cx + + test al, BS3_MODE_CODE_V86 + jnz .iret_frame_v8086 + + mov ax, ss + and al, 3 + and cl, 3 + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.bCpl], cl + cmp cl, al + je .iret_frame_same_cpl + +.ret_frame_different_cpl: + mov cx, [bp + 10] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.ss], cx + mov cx, [bp + 8] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rsp], cx + mov byte [ss:bx + BS3TRAPFRAME.cbIretFrame], 5*2 + test dx, dx + jnz .iret_frame_done + jmp .iret_frame_seed_high_eip_word + +.iret_frame_same_cpl: ; (ss and high bits was saved by CPU specific part) + lea cx, [bp + 8] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rsp], cx + mov byte [ss:bx + BS3TRAPFRAME.cbIretFrame], 3*2 + test dx, dx + jnz .iret_frame_done + jmp .iret_frame_seed_high_eip_word + +.iret_frame_v8086: +CPU 386 + or dword [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rflags], X86_EFL_VM + mov byte [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.bCpl], 3 + or byte [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.bMode], BS3_MODE_CODE_V86 ; paranoia ^ 2 +%if 0 ;; @todo testcase: high ESP word from V86 mode, 16-bit TSS. + movzx ecx, word [bp + 8] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rsp], ecx +%else + mov cx, word [bp + 8] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rsp], cx +%endif + mov cx, [bp + 10] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.ss], cx + mov cx, [bp + 12] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.es], cx + mov cx, [bp + 14] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.ds], cx + mov cx, [bp + 16] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.fs], cx + mov cx, [bp + 18] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.gs], cx + mov byte [ss:bx + BS3TRAPFRAME.cbIretFrame], 9*2 + jmp .iret_frame_done + + ; + ; For 386 we do special tricks to supply the high word of EIP when + ; arriving here from 32-bit code. (ESP was seeded earlier.) + ; +.iret_frame_seed_high_eip_word: + lar eax, [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.cs] + jnz .iret_frame_done + test eax, X86LAR_F_D + jz .iret_frame_done + mov ax, [g_uBs3TrapEipHint+2] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rip + 2], ax + +.iret_frame_done: + ; + ; Control registers. + ; + str [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.tr] + sldt [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.ldtr] + test dx, dx + jnz .save_286_control_registers +.save_386_control_registers: +CPU 386 + mov ax, ss + test al, 3 + jnz .skip_crX_because_cpl_not_0 + mov eax, cr0 + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.cr0], eax + mov eax, cr2 + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.cr2], eax + mov eax, cr3 + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.cr3], eax + + test byte [1 + BS3_DATA16_WRT(g_uBs3CpuDetected)], (BS3CPU_F_CPUID >> 8) ; CR4 first appeared in later 486es. + jz .skip_cr4_because_not_there + mov eax, cr4 + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.cr4], eax + jmp .set_flags + +.skip_cr4_because_not_there: + mov byte [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_CR4 + jmp .set_flags + +.skip_crX_because_cpl_not_0: + or byte [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.fbFlags], \ + BS3REG_CTX_F_NO_CR2_CR3 | BS3REG_CTX_F_NO_CR4 | BS3REG_CTX_F_NO_CR0_IS_MSW + +CPU 286 +.save_286_control_registers: + smsw [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.cr0] + +.set_flags: ; The double fault code joins us here. + or byte [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_AMD64 + + ; + ; Dispatch it to C code. + ; +.dispatch_to_handler: + mov di, bx + mov bl, byte [ss:bx + BS3TRAPFRAME.bXcpt] + mov bh, 0 + shl bx, 1 + mov bx, [bx + BS3_DATA16_WRT(_g_apfnBs3TrapHandlers_c16)] + or bx, bx + jnz .call_handler + mov bx, Bs3TrapDefaultHandler +.call_handler: + push ss + push di + call bx + + ; + ; Resume execution using trap frame. + ; + push 0 + push ss + add di, BS3TRAPFRAME.Ctx + push di + call Bs3RegCtxRestore +.panic: + hlt + jmp .panic +BS3_PROC_END bs3Trap16GenericCommon + + +;; +; Helper. +; +; @retruns Flat address in es:di. +; @param di +; @uses eax +; +bs3Trap16TssInDiToFar1616InEsDi: +CPU 286 + push ax + + ; ASSUME Bs3Gdt is being used. + push BS3_SEL_SYSTEM16 + pop es + and di, 0fff8h + add di, Bs3Gdt wrt BS3SYSTEM16 + + ; Load the TSS base into ax:di (di is low, ax high) + mov al, [es:di + (X86DESCGENERIC_BIT_OFF_BASE_HIGH1 / 8)] + mov ah, [es:di + (X86DESCGENERIC_BIT_OFF_BASE_HIGH2 / 8)] + mov di, [es:di + (X86DESCGENERIC_BIT_OFF_BASE_LOW / 8)] + + ; Convert ax to tiled selector, if not within the tiling area we read + ; random BS3SYSTEM16 bits as that's preferable to #GP'ing. + shl ax, X86_SEL_SHIFT + cmp ax, BS3_SEL_TILED_LAST - BS3_SEL_TILED +%ifdef BS3_STRICT + jbe .tiled + int3 +%endif + ja .return ; don't crash again. +.tiled: + add ax, BS3_SEL_TILED + mov es, ax +.return: + pop ax + ret + + +;; +; Double fault handler. +; +; We don't have to load any selectors or clear anything in EFLAGS because the +; TSS specified sane values which got loaded during the task switch. +; +; @param dx Zero (0) for indicating 386+ to the common code. +; +BS3_PROC_BEGIN _Bs3Trap16DoubleFaultHandler80386 +BS3_PROC_BEGIN Bs3Trap16DoubleFaultHandler80386 +CPU 386 + push 0 ; We'll copy the rip from the other TSS here later to create a more sensible call chain. + push ebp + mov bp, sp + pushfd ; Handler flags. + + ; Reserve space for the register and trap frame. + mov bx, (BS3TRAPFRAME_size + 15) / 16 +.more_zeroed_space: + push dword 0 + push dword 0 + push dword 0 + push dword 0 + dec bx + jz .more_zeroed_space + mov bx, sp + + ; + ; Fill in the high GRP register words before we mess them up. + ; + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rax], eax + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rbx], ebx + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rcx], ecx + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rdx], edx + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rsi], esi + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rdi], edi + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rbp], ebp + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rsp], esp + + ; + ; FS and GS are not part of the 16-bit TSS because they are 386+ specfic. + ; + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.fs], fs + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.gs], gs + + ; + ; Fill in the non-context trap frame bits. + ; + mov ecx, [bp - 4] + mov [ss:bx + BS3TRAPFRAME.fHandlerRfl], ecx + mov byte [ss:bx + BS3TRAPFRAME.bXcpt], X86_XCPT_DF + mov [ss:bx + BS3TRAPFRAME.uHandlerCs], cs + mov [ss:bx + BS3TRAPFRAME.uHandlerSs], ss + mov ecx, esp + lea cx, [bp + 8] + mov [ss:bx + BS3TRAPFRAME.uHandlerRsp], ecx + mov cx, [bp + 6] + mov [ss:bx + BS3TRAPFRAME.uErrCd], cx + + ; + ; Copy 80386+ control registers. + ; + mov ecx, cr0 + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.cr0], ecx + mov ecx, cr2 + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.cr2], ecx + mov ecx, cr3 + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.cr3], ecx + + test byte [1 + BS3_DATA16_WRT(g_uBs3CpuDetected)], (BS3CPU_F_CPUID >> 8) ; CR4 first appeared in later 486es. + jz .skip_cr4_because_not_there + mov ecx, cr4 + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.cr4], ecx + jmp .common + +.skip_cr4_because_not_there: + mov byte [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_CR4 + + ; + ; Copy the register state from the previous task segment. + ; The 80286 code with join us here. + ; +.common: +CPU 286 + ; Find our TSS. + str di + call bs3Trap16TssInDiToFar1616InEsDi + + ; Find the previous TSS. + mov di, [es:di + X86TSS32.selPrev] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.tr], ax + call bs3Trap16TssInDiToFar1616InEsDi + + ; Do the copying. + mov cx, [es:di + X86TSS16.ax] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rax], cx + mov cx, [es:di + X86TSS16.cx] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rcx], cx + mov cx, [es:di + X86TSS16.dx] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rdx], cx + mov cx, [es:di + X86TSS16.bx] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rbx], cx + mov cx, [es:di + X86TSS16.sp] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rsp], cx + mov cx, [es:di + X86TSS16.bp] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rbp], cx + mov [bp], cx ; For better call stacks. + mov cx, [es:di + X86TSS16.si] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rsi], cx + mov cx, [es:di + X86TSS16.di] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rdi], cx + mov cx, [es:di + X86TSS16.si] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rsi], cx + mov cx, [es:di + X86TSS16.flags] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rflags], cx + mov cx, [es:di + X86TSS16.ip] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rip], cx + mov [bp + 2], cx ; For better call stacks. + mov cx, [es:di + X86TSS16.cs] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.cs], cx + mov cx, [es:di + X86TSS16.ds] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.ds], cx + mov cx, [es:di + X86TSS16.es] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.es], cx + mov cx, [es:di + X86TSS16.ss] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.ss], cx + mov cx, [es:di + X86TSS16.selLdt] ; Note! This isn't necessarily the ldtr at the time of the fault. + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.ldtr], cx + + ; + ; Set CPL; copy and update mode. + ; + mov cl, [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.ss] + and cl, 3 + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.bCpl], cl + + mov cl, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.bMode], cl + and cl, ~BS3_MODE_CODE_MASK + or cl, BS3_MODE_CODE_16 + mov [BS3_DATA16_WRT(g_bBs3CurrentMode)], cl + + ; + ; Join code paths with the generic handler code. + ; + jmp bs3Trap16GenericCommon.set_flags +BS3_PROC_END Bs3Trap16DoubleFaultHandler + + +;; +; Double fault handler. +; +; We don't have to load any selectors or clear anything in EFLAGS because the +; TSS specified sane values which got loaded during the task switch. +; +; @param dx One (1) for indicating 386+ to the common code. +; +BS3_PROC_BEGIN _Bs3Trap16DoubleFaultHandler80286 +BS3_PROC_BEGIN Bs3Trap16DoubleFaultHandler80286 +CPU 286 + push 0 ; We'll copy the rip from the other TSS here later to create a more sensible call chain. + push bp + mov bp, sp + pushf ; Handler flags. + + ; Reserve space for the register and trap frame. + mov bx, (BS3TRAPFRAME_size + 7) / 8 +.more_zeroed_space: + push 0 + push 0 + push 0 + push 0 + dec bx + jz .more_zeroed_space + mov bx, sp + + ; + ; Fill in the non-context trap frame bits. + ; + mov cx, [bp - 2] + mov [ss:bx + BS3TRAPFRAME.fHandlerRfl], cx + mov byte [ss:bx + BS3TRAPFRAME.bXcpt], X86_XCPT_DF + mov [ss:bx + BS3TRAPFRAME.uHandlerCs], cs + mov [ss:bx + BS3TRAPFRAME.uHandlerSs], ss + lea cx, [bp + 8] + mov [ss:bx + BS3TRAPFRAME.uHandlerRsp], cx + mov cx, [bp + 6] + mov [ss:bx + BS3TRAPFRAME.uErrCd], cx + + ; + ; Copy 80286 specific control register. + ; + smsw [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.cr0] + + jmp Bs3Trap16DoubleFaultHandler80386.common +BS3_PROC_END Bs3Trap16DoubleFaultHandler80286 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c16-TrapRmV86Data.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c16-TrapRmV86Data.c new file mode 100644 index 00000000..8d5959fa --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c16-TrapRmV86Data.c @@ -0,0 +1,53 @@ +/* $Id: bs3-c16-TrapRmV86Data.c $ */ +/** @file + * BS3Kit - Real mode and V86 trap data. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#if ARCH_BITS == 16 +/** Copy of the original real-mode interrupt vector table. */ +RTFAR16 g_aBs3RmIvtOriginal[256]; +/** Indicates whether we've copied the real-mode IVT or not. */ +bool g_fBs3RmIvtCopied = false; +#endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c16-TrapRmV86Generic.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c16-TrapRmV86Generic.asm new file mode 100644 index 00000000..5f7fea43 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c16-TrapRmV86Generic.asm @@ -0,0 +1,401 @@ +; $Id: bs3-c16-TrapRmV86Generic.asm $ +;; @file +; BS3Kit - Trap, 16-bit assembly handlers for real mode and v8086. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + +%ifndef TMPL_16BIT + %error "16-bit only template" +%endif + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_EXTERN_DATA16 g_bBs3CurrentMode +BS3_EXTERN_DATA16 g_uBs3TrapEipHint +BS3_EXTERN_DATA16 g_uBs3CpuDetected +BS3_EXTERN_DATA16 g_apfnBs3TrapHandlers_c16 +TMPL_BEGIN_TEXT +BS3_EXTERN_CMN Bs3TrapDefaultHandler +BS3_EXTERN_CMN Bs3RegCtxRestore +TMPL_BEGIN_TEXT + + +;; +; Generic entry points for IDT handlers, 8 byte spacing. +; +BS3_PROC_BEGIN _Bs3TrapRmV86GenericEntries +BS3_PROC_BEGIN Bs3TrapRmV86GenericEntries +%macro Bs3TrapRmV86GenericEntryNoErr 1 + push ax ; 1 byte: Reserve space for fake error cd. (BP(+2) + 4) + push ax ; 1 byte: Save AX (BP(+2) + 2) + mov ax, i | 00000h ; 2 bytes: AL = trap/interrupt number; AH=indicate no error code + jmp %1 ; 3 bytes: Jump to handler code + ALIGNCODE(8) +%assign i i+1 +%endmacro + +%macro Bs3TrapRmV86GenericEntryErrCd 1 + Bs3TrapRmV86GenericEntryNoErr %1 ; No error code pushed in real mode or V86 mode. +%endmacro + +%assign i 0 ; start counter. + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 0 + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 1 + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 2 + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 3 + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 4 + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 5 + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 6 + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 7 + Bs3TrapRmV86GenericEntryErrCd bs3TrapRmV86GenericTrapOrInt ; 8 + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 9 + Bs3TrapRmV86GenericEntryErrCd bs3TrapRmV86GenericTrapOrInt ; a + Bs3TrapRmV86GenericEntryErrCd bs3TrapRmV86GenericTrapOrInt ; b + Bs3TrapRmV86GenericEntryErrCd bs3TrapRmV86GenericTrapOrInt ; c + Bs3TrapRmV86GenericEntryErrCd bs3TrapRmV86GenericTrapOrInt ; d + Bs3TrapRmV86GenericEntryErrCd bs3TrapRmV86GenericTrapOrInt ; e + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; f (reserved) + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 10 + Bs3TrapRmV86GenericEntryErrCd bs3TrapRmV86GenericTrapOrInt ; 11 + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 12 + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 13 + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 14 + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 15 (reserved) + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 16 (reserved) + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 17 (reserved) + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 18 (reserved) + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 19 (reserved) + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 1a (reserved) + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 1b (reserved) + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 1c (reserved) + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 1d (reserved) + Bs3TrapRmV86GenericEntryErrCd bs3TrapRmV86GenericTrapOrInt ; 1e + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt ; 1f (reserved) +%rep 224 + Bs3TrapRmV86GenericEntryNoErr bs3TrapRmV86GenericTrapOrInt +%endrep +BS3_PROC_END Bs3TrapRmV86GenericEntries +AssertCompile(Bs3TrapRmV86GenericEntries_EndProc - Bs3TrapRmV86GenericEntries == 8*256) + + +;; +; Trap or interrupt with error code, faked if necessary. +; +; early 386+ stack (movzx ebp, sp): +; [bp + 000h] ebp +; [bp + 004h] ax +; [bp + 006h] errcd [bp'+0] <--- bp at jmp to common code. +; [bp + 008h] cs [bp'+2] +; [bp + 00ah] ip [bp'+4] +; [bp + 00ch] flags [bp'+6] +; ([bp + 00eh] post-iret sp value) [bp'+8] +; +BS3_PROC_BEGIN _bs3TrapRmV86GenericTrapOrInt +BS3_PROC_BEGIN bs3TrapRmV86GenericTrapOrInt +CPU 386 + jmp near bs3TrapRmV86GenericTrapErrCode8086 ; Bs3TrapRmV86Init adjusts this on 80386+ + push ebp + movzx ebp, sp + push ebx ; BP - 04h + pushfd ; BP - 08h + cld + push edx ; BP - 0ch + push ss ; BP - 0eh + push esp ; BP - 12h + + ; Reserve space for the register and trap frame. + mov bx, (BS3TRAPFRAME_size + 7) / 8 +.more_zeroed_space: + push 0 + push 0 + push 0 + push 0 + dec bx + jnz .more_zeroed_space + movzx ebx, sp + + + mov edx, [bp - 12h] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rsp], edx ; high bits + mov [ss:bx + BS3TRAPFRAME.uHandlerRsp], edx ; high bits + mov dx, [bp - 0eh] + mov [ss:bx + BS3TRAPFRAME.uHandlerSs], dx + mov edx, [bp - 0ch] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rdx], edx + mov edx, [bp - 8] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rflags], edx ; high bits + mov [ss:bx + BS3TRAPFRAME.fHandlerRfl], edx + mov edx, [bp - 4] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rbx], edx + mov edx, [bp] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rbp], edx + mov edx, eax ; high bits + mov dx, [bp + 4] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rax], edx + + mov [ss:bx + BS3TRAPFRAME.bXcpt], al + + test ah, 0ffh + jz .no_error_code + mov dx, [bp + 6] + mov [ss:bx + BS3TRAPFRAME.uErrCd], dx +.no_error_code: + + add bp, 6 ; adjust so it points to the word before the iret frame. + xor dx, dx + jmp bs3TrapRmV86GenericCommon +BS3_PROC_END bs3TrapRmV86GenericTrapErrCode + +;; +; Trap with error code - 8086/V20/80186/80286 code variant. +; +BS3_PROC_BEGIN bs3TrapRmV86GenericTrapErrCode8086 +CPU 8086 + push bp + mov bp, sp + push bx ; BP - 2 + pushf ; BP - 4 + push ax ; BP - 6 + cld + + ; Reserve space for the register and trap frame. + mov bx, (BS3TRAPFRAME_size + 7) / 8 + xor ax, ax +.more_zeroed_space: + push ax + push ax + push ax + push ax + dec bx + jnz .more_zeroed_space + mov bx, sp + + mov [ss:bx + BS3TRAPFRAME.uHandlerSs], ss + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rdx], dx + mov dx, [bp - 4] + mov [ss:bx + BS3TRAPFRAME.fHandlerRfl], dx + mov dx, [bp - 2] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rbx], dx + mov dx, [bp] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rbp], dx + + mov dx, [bp + 2] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rax], dx + + mov ax, [bp - 6] + mov [ss:bx + BS3TRAPFRAME.bXcpt], al + + test ah, 0ffh + jz .no_error_code + mov dx, [bp + 4] + mov [ss:bx + BS3TRAPFRAME.uErrCd], dx +.no_error_code: + + add bp, 4 ; adjust so it points to the word before the iret frame. + mov dl, 1 + jmp bs3TrapRmV86GenericCommon +BS3_PROC_END bs3TrapRmV86GenericTrapErrCode8086 + + +;; +; Common context saving code and dispatching. +; +; @param ss:bx Pointer to the trap frame, zero filled. The following members +; have been filled in by the previous code: +; - bXcpt +; - uErrCd +; - fHandlerRFL +; - Ctx.eax +; - Ctx.edx +; - Ctx.ebx +; - Ctx.ebp +; - Ctx.rflags - high bits only. +; - Ctx.esp - high bits only. +; - All other bytes are zeroed. +; +; @param bp Pointer to the word before the iret frame, i.e. where bp +; would be saved if this was a normal near call. +; @param dx One (1) if 286, zero (0) if 386+. +; +BS3_PROC_BEGIN bs3TrapRmV86GenericCommon +CPU 8086 + ; + ; Fake EBP frame. + ; + mov ax, [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rbp] + mov [bp], ax + + ; + ; Save the remaining GPRs and segment registers. + ; + test dx, dx + jnz .save_word_grps +CPU 386 + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rcx], ecx + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rdi], edi + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rsi], esi + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.fs], fs + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.gs], gs + jmp .save_segment_registers +.save_word_grps: +CPU 8086 + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rcx], cx + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rdi], di + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rsi], si +.save_segment_registers: + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.ds], ds + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.es], es + mov [ss:bx + BS3TRAPFRAME.uHandlerCs], cs + + ; + ; Load 16-bit BS3KIT_GRPNM_DATA16 into DS and ES so we can access globals. + ; + mov ax, BS3KIT_GRPNM_DATA16 + mov ds, ax + mov es, ax + + ; + ; Copy the mode now that we've got a flat DS. We don't need to update + ; it as it didn't change. + ; + mov al, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.bMode], al + + ; + ; Copy iret info. + ; + lea cx, [bp + 2] + mov [ss:bx + BS3TRAPFRAME.uHandlerRsp], cx + mov cx, [bp + 2] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rip], cx + mov cx, [bp + 6] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rflags], cx + mov cx, [bp + 4] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.cs], cx + mov cx, ss + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.ss], cx + lea cx, [bp + 8] + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rsp], cx + mov byte [ss:bx + BS3TRAPFRAME.cbIretFrame], 3*2 + + ; The VM flag and CPL. + test al, BS3_MODE_CODE_V86 + jz .dont_set_vm + or byte [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.rflags + 2], X86_EFL_VM >> 16 + mov byte [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.bCpl], 3 +.dont_set_vm: + + + ; + ; Control registers. + ; + ; Since we're in real or v8086 here, we cannot save TR and LDTR. + ; But get MSW (CR0) first since that's always accessible and we + ; need it even on a 386 to check whether we're in v8086 mode or not. + ; + cmp byte [BS3_DATA16_WRT(g_uBs3CpuDetected)], BS3CPU_80286 + jb .skip_control_registers_because_80186_or_older +CPU 286 + smsw ax + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.cr0], ax + + test dx, dx + jnz .set_flags +.save_386_control_registers: +CPU 386 + ; 386 control registers are not accessible from virtual 8086 mode. + test al, X86_CR0_PE + jnz .skip_crX_because_v8086 + mov eax, cr0 + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.cr0], eax + mov eax, cr2 + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.cr2], eax + mov eax, cr3 + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.cr3], eax + + test byte [1 + BS3_DATA16_WRT(g_uBs3CpuDetected)], (BS3CPU_F_CPUID >> 8) ; CR4 first appeared in later 486es. + jz .skip_cr4_because_not_there + mov eax, cr4 + mov [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.cr4], eax + jmp .set_flags + +.skip_cr4_because_not_there: + mov byte [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_CR4 + jmp .set_flags + +CPU 8086 +.skip_control_registers_because_80186_or_older: +.skip_crX_because_v8086: + or byte [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.fbFlags], \ + BS3REG_CTX_F_NO_CR0_IS_MSW | BS3REG_CTX_F_NO_CR2_CR3 | BS3REG_CTX_F_NO_CR4 +.set_flags: ; The double fault code joins us here. + or byte [ss:bx + BS3TRAPFRAME.Ctx + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_AMD64 | BS3REG_CTX_F_NO_TR_LDTR + + ; + ; Dispatch it to C code. + ; +.dispatch_to_handler: + mov di, bx + mov bl, byte [ss:bx + BS3TRAPFRAME.bXcpt] + mov bh, 0 + shl bx, 1 + mov bx, [bx + BS3_DATA16_WRT(_g_apfnBs3TrapHandlers_c16)] + or bx, bx + jnz .call_handler + mov bx, Bs3TrapDefaultHandler +.call_handler: + push ss + push di + call bx + + ; + ; Resume execution using trap frame. + ; + xor ax, ax + push ax + push ss + add di, BS3TRAPFRAME.Ctx + push di + call Bs3RegCtxRestore +.panic: + hlt + jmp .panic +BS3_PROC_END bs3TrapRmV86GenericCommon + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c32-Trap32Generic.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c32-Trap32Generic.asm new file mode 100644 index 00000000..4e5a7780 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c32-Trap32Generic.asm @@ -0,0 +1,546 @@ +; $Id: bs3-c32-Trap32Generic.asm $ +;; @file +; BS3Kit - Trap, 32-bit assembly handlers. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + +%ifndef TMPL_32BIT + %error "32-bit only template" +%endif + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_EXTERN_DATA16 g_bBs3CurrentMode +BS3_EXTERN_DATA16 g_uBs3CpuDetected +BS3_EXTERN_DATA16 g_apfnBs3TrapHandlers_c32 +BS3_EXTERN_SYSTEM16 Bs3Gdt +TMPL_BEGIN_TEXT +BS3_EXTERN_CMN Bs3TrapDefaultHandler +BS3_EXTERN_CMN Bs3RegCtxRestore +TMPL_BEGIN_TEXT + + +;********************************************************************************************************************************* +;* Global Variables * +;********************************************************************************************************************************* +BS3_BEGIN_DATA16 +;; Easy to access flat address of Bs3Trap32GenericEntries. +BS3_GLOBAL_DATA g_Bs3Trap32GenericEntriesFlatAddr, 4 + dd Bs3Trap32GenericEntries wrt FLAT +;; Easy to access flat address of Bs3Trap32DoubleFaultHandler. +BS3_GLOBAL_DATA g_Bs3Trap32DoubleFaultHandlerFlatAddr, 4 + dd Bs3Trap32DoubleFaultHandler wrt FLAT + + +TMPL_BEGIN_TEXT + +;; +; Generic entry points for IDT handlers, 8 byte spacing. +; +BS3_PROC_BEGIN Bs3Trap32GenericEntries +%macro Bs3Trap32GenericEntryNoErr 1 + push byte 0 ; 2 byte: fake error code. + db 06ah, i ; 2 byte: push imm8 - note that this is a signextended value. + jmp near %1 ; 5 byte + ALIGNCODE(2) +%assign i i+1 +%endmacro + +%macro Bs3Trap32GenericEntryErrCd 1 + db 06ah, i ; 2 byte: push imm8 - note that this is a signextended value. + jmp near %1 ; 5 byte + db 0cch, 0cch ; 2 byte: padding. + ALIGNCODE(2) +%assign i i+1 +%endmacro + +%assign i 0 ; start counter. + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 0 + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 1 + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 2 + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 3 + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 4 + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 5 + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 6 + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 7 + Bs3Trap32GenericEntryErrCd bs3Trap32GenericTrapOrInt ; 8 + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 9 + Bs3Trap32GenericEntryErrCd bs3Trap32GenericTrapOrInt ; a + Bs3Trap32GenericEntryErrCd bs3Trap32GenericTrapOrInt ; b + Bs3Trap32GenericEntryErrCd bs3Trap32GenericTrapOrInt ; c + Bs3Trap32GenericEntryErrCd bs3Trap32GenericTrapOrInt ; d + Bs3Trap32GenericEntryErrCd bs3Trap32GenericTrapOrInt ; e + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; f (reserved) + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 10 + Bs3Trap32GenericEntryErrCd bs3Trap32GenericTrapOrInt ; 11 + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 12 + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 13 + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 14 + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 15 (reserved) + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 16 (reserved) + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 17 (reserved) + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 18 (reserved) + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 19 (reserved) + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 1a (reserved) + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 1b (reserved) + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 1c (reserved) + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 1d (reserved) + Bs3Trap32GenericEntryErrCd bs3Trap32GenericTrapOrInt ; 1e + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt ; 1f (reserved) +%rep 224 + Bs3Trap32GenericEntryNoErr bs3Trap32GenericTrapOrInt +%endrep +BS3_PROC_END Bs3Trap32GenericEntries +AssertCompile(Bs3Trap32GenericEntries_EndProc - Bs3Trap32GenericEntries == 10*256) + + +;; +; Trap or interrupt with error code, faked if necessary. +; +BS3_PROC_BEGIN bs3Trap32GenericTrapOrInt + push ebp ; 0 + mov ebp, esp + pushfd ; -04h + cld + push eax ; -08h + push edi ; -0ch + lea eax, [esp + (4+1+1)*4] ; 4 pushes above, 1 exception number push, 1 error code. + push eax ; -10h = handler ESP + add eax, 3*4 ; 3 dword iret frame + push eax ; -14h = caller ESP if same CPL + push ss ; -18h + push ds ; -1ch + + ; Make sure we've got a flat DS. It makes everything so much simpler. + mov ax, ss + and al, 3 + AssertCompile(BS3_SEL_RING_SHIFT == 8) + mov ah, al + add ax, BS3_SEL_R0_DS32 + mov ds, ax + + ; + ; We may be comming from 16-bit code with a 16-bit SS. Thunk it as + ; the C code may assume flat SS and we'll mess up by using EBP/ESP/EDI + ; instead of BP/SP/SS:DI. ASSUMES standard GDT selector. + ; + mov ax, ss + lar eax, ax + test eax, X86LAR_F_D + jz .stack_thunk + mov ax, ss + and al, 3 + AssertCompile(BS3_SEL_RING_SHIFT == 8) + mov ah, al + add ax, BS3_SEL_R0_SS32 + mov ss, ax + jmp .stack_flat +.stack_thunk: + mov di, ss + and edi, X86_SEL_MASK_OFF_RPL + mov al, [X86DESCGENERIC_BIT_OFF_BASE_HIGH1 / 8 + edi + Bs3Gdt wrt FLAT] + mov ah, [X86DESCGENERIC_BIT_OFF_BASE_HIGH2 / 8 + edi + Bs3Gdt wrt FLAT] + shl eax, 16 + mov ax, [X86DESCGENERIC_BIT_OFF_BASE_LOW / 8 + edi + Bs3Gdt wrt FLAT] ; eax = SS.base + movzx ebp, bp ; SS:BP -> flat EBP. + add ebp, eax + movzx edi, sp ; SS:SP -> flat ESP in EAX. + add edi, eax + mov ax, ss + and al, 3 + AssertCompile(BS3_SEL_RING_SHIFT == 8) + mov ah, al + add ax, BS3_SEL_R0_SS32 + mov ss, ax + mov esp, edi + sub dword [ebp - 10h], (4+1)*4 ; Recalc handler ESP in case of wraparound. + add word [ebp - 10h], (4+1)*4 + sub dword [ebp - 10h], (4+1+3)*4 ; Recalc caller ESP in case of wraparound. + add word [ebp - 10h], (4+1+3)*4 +.stack_flat: + + ; Reserve space for the register and trap frame. + mov eax, (BS3TRAPFRAME_size + 7) / 8 +AssertCompileSizeAlignment(BS3TRAPFRAME, 8) +.more_zeroed_space: + push dword 0 + push dword 0 + dec eax + jnz .more_zeroed_space + mov edi, esp ; edi points to trapframe structure. + + ; Copy stuff from the stack over. + mov eax, [ebp + 8] +;; @todo Do voodoo checks for 'int xx' or misguided hardware interrupts. + mov [edi + BS3TRAPFRAME.uErrCd], eax + mov al, [ebp + 4] + mov [edi + BS3TRAPFRAME.bXcpt], al + mov eax, [ebp] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rbp], eax + mov eax, [ebp - 04h] + mov [edi + BS3TRAPFRAME.fHandlerRfl], eax + mov eax, [ebp - 08h] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rax], eax + mov eax, [ebp - 0ch] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rdi], eax + mov eax, [ebp - 10h] + mov [edi + BS3TRAPFRAME.uHandlerRsp], eax + mov eax, [ebp - 14h] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rsp], eax + mov ax, [ebp - 18h] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.ss], ax + mov [edi + BS3TRAPFRAME.uHandlerSs], ax + mov ax, [ebp - 1ch] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.ds], ax + + lea ebp, [ebp + 8] ; iret - 4 (i.e. ebp frame chain location) + jmp bs3Trap32GenericCommon +BS3_PROC_END bs3Trap32GenericTrapErrCode + + +;; +; Common context saving code and dispatching. +; +; @param edi Pointer to the trap frame. The following members have been +; filled in by the previous code: +; - bXcpt +; - uErrCd +; - fHandlerRfl +; - uHandlerRsp +; - uHandlerSs +; - Ctx.rax +; - Ctx.rbp +; - Ctx.rdi +; - Ctx.rsp - assuming same CPL +; - Ctx.ds +; - Ctx.ss +; +; @param ebp Pointer to the dword before the iret frame, i.e. where ebp +; would be saved if this was a normal call. +; +; @remarks This is a separate function for hysterical raisins. +; +BS3_PROC_BEGIN bs3Trap32GenericCommon + ; + ; Fake EBP frame. + ; + mov eax, [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rbp] + mov [ebp], eax + + ; + ; Save the remaining GPRs and segment registers. + ; + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rcx], ecx + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rdx], edx + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rbx], ebx + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rsi], esi + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.es], es + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.fs], fs + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.gs], gs + + ; + ; Load 32-bit data selector for the DPL we're executing at into DS and ES. + ; Save the handler CS value first. + ; + mov ax, cs + mov [edi + BS3TRAPFRAME.uHandlerCs], ax + and al, 3 + AssertCompile(BS3_SEL_RING_SHIFT == 8) + mov ah, al + add ax, BS3_SEL_R0_DS32 + mov ds, ax + mov es, ax + + ; + ; Copy and update the mode now that we've got a flat DS. + ; + mov al, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.bMode], al + and al, ~BS3_MODE_CODE_MASK + or al, BS3_MODE_CODE_32 + mov [BS3_DATA16_WRT(g_bBs3CurrentMode)], al + + ; + ; Copy iret info. + ; + mov ecx, [ebp + 4] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rip], ecx + mov ecx, [ebp + 12] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rflags], ecx + mov cx, [ebp + 8] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.cs], cx + test dword [ebp + 12], X86_EFL_VM + jnz .iret_frame_v8086 + mov ax, ss + and al, 3 + and cl, 3 + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.bCpl], cl + cmp cl, al + je .iret_frame_same_cpl + +.iret_frame_different_cpl: + mov ecx, [ebp + 16] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rsp], ecx + mov cx, [ebp + 20] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.ss], cx + mov byte [edi + BS3TRAPFRAME.cbIretFrame], 5*4 + jmp .iret_frame_done + +.iret_frame_v8086: + mov byte [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.bCpl], 3 + or byte [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.bMode], BS3_MODE_CODE_V86 ; paranoia ^ 2 + mov ecx, [ebp + 16] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rsp], ecx + mov cx, [ebp + 20] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.ss], cx + mov cx, [ebp + 24] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.es], cx + mov cx, [ebp + 28] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.ds], cx + mov cx, [ebp + 32] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.fs], cx + mov cx, [ebp + 36] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.gs], cx + mov byte [edi + BS3TRAPFRAME.cbIretFrame], 9*4 + jmp .iret_frame_done + +.iret_frame_same_cpl: ; (caller already set SS:RSP and uHandlerRsp for same CPL iret frames) + mov byte [edi + BS3TRAPFRAME.cbIretFrame], 3*4 + +.iret_frame_done: + ; + ; Control registers. + ; + str ax + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.tr], ax + sldt ax + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.ldtr], ax + + mov ax, ss + test al, 3 + jnz .skip_crX_because_cpl_not_0 + + mov eax, cr3 + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.cr3], eax +.save_cr0_cr2_cr4: ; The double fault code joins us here. + mov eax, cr0 + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.cr0], eax + mov eax, cr2 + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.cr2], eax + + test byte [1 + BS3_DATA16_WRT(g_uBs3CpuDetected)], (BS3CPU_F_CPUID >> 8) ; CR4 first appeared in later 486es. + jz .skip_cr4_because_not_there + mov eax, cr4 + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.cr4], eax + jmp .set_flags + +.skip_cr4_because_not_there: + mov byte [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_CR4 + jmp .set_flags + +.skip_crX_because_cpl_not_0: + or byte [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.fbFlags], \ + BS3REG_CTX_F_NO_CR0_IS_MSW | BS3REG_CTX_F_NO_CR2_CR3 | BS3REG_CTX_F_NO_CR4 + smsw [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.cr0] +.set_flags: + or byte [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_AMD64 + + ; + ; Dispatch it to C code. + ; +.dispatch_to_handler: + movzx ebx, byte [edi + BS3TRAPFRAME.bXcpt] + mov eax, [ebx * 4 + BS3_DATA16_WRT(_g_apfnBs3TrapHandlers_c32)] + or eax, eax + jnz .call_handler + mov eax, Bs3TrapDefaultHandler +.call_handler: + push edi + call eax + + ; + ; Resume execution using trap frame. + ; + push 0 + add edi, BS3TRAPFRAME.Ctx + push edi + call Bs3RegCtxRestore +.panic: + hlt + jmp .panic +BS3_PROC_END bs3Trap32GenericCommon + + +;; +; Helper. +; +; @retruns Flat address in eax. +; @param ax +; @uses eax +; +bs3Trap32TssInAxToFlatInEax: + ; Get the GDT base address and find the descriptor address (EAX) + sub esp, 8+2 + sgdt [esp] + and eax, 0fff8h + add eax, [esp + 2] ; GDT base address. + add esp, 8+2 + + ; Get the flat TSS address from the descriptor. + mov al, [eax + (X86DESCGENERIC_BIT_OFF_BASE_HIGH1 / 8)] + mov ah, [eax + (X86DESCGENERIC_BIT_OFF_BASE_HIGH2 / 8)] + shl eax, 16 + mov ax, [eax + (X86DESCGENERIC_BIT_OFF_BASE_LOW / 8)] + ret + +;; +; Double fault handler. +; +; We don't have to load any selectors or clear anything in EFLAGS because the +; TSS specified sane values which got loaded during the task switch. +; +BS3_PROC_BEGIN Bs3Trap32DoubleFaultHandler + push 0 ; We'll copy the rip from the other TSS here later to create a more sensible call chain. + push ebp + mov ebp, esp + + pushfd ; Get handler flags. + pop ecx + + xor edx, edx ; NULL register. + + ; + ; Allocate a zero filled trap frame. + ; + mov eax, (BS3TRAPFRAME_size + 7) / 8 +AssertCompileSizeAlignment(BS3TRAPFRAME, 8) +.more_zeroed_space: + push edx + push edx + dec eax + jz .more_zeroed_space + mov edi, esp + + ; + ; Fill in the non-context trap frame bits. + ; + mov [edi + BS3TRAPFRAME.fHandlerRfl], ecx + mov word [edi + BS3TRAPFRAME.bXcpt], X86_XCPT_DF + mov [edi + BS3TRAPFRAME.uHandlerCs], cs + mov [edi + BS3TRAPFRAME.uHandlerSs], ss + lea ecx, [ebp + 3*4] ; two pushes, one error code. + mov [edi + BS3TRAPFRAME.uHandlerRsp], ecx + mov ecx, [ebp + 8] + mov [edi + BS3TRAPFRAME.uErrCd], ecx + + ; + ; Copy the register state from the previous task segment. + ; + + ; Find our TSS. + str ax + call bs3Trap32TssInAxToFlatInEax + + ; Find the previous TSS. + mov ax, [eax + X86TSS32.selPrev] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.tr], ax + call bs3Trap32TssInAxToFlatInEax + + ; Do the copying. + mov ecx, [eax + X86TSS32.eax] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rax], ecx + mov ecx, [eax + X86TSS32.ecx] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rcx], ecx + mov ecx, [eax + X86TSS32.edx] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rdx], ecx + mov ecx, [eax + X86TSS32.ebx] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rbx], ecx + mov ecx, [eax + X86TSS32.esp] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rsp], ecx + mov ecx, [eax + X86TSS32.ebp] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rbp], ecx + mov [ebp], ecx ; For better call stacks. + mov ecx, [eax + X86TSS32.esi] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rsi], ecx + mov ecx, [eax + X86TSS32.edi] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rdi], ecx + mov ecx, [eax + X86TSS32.esi] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rsi], ecx + mov ecx, [eax + X86TSS32.eflags] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rflags], ecx + mov ecx, [eax + X86TSS32.eip] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rip], ecx + mov [ebp + 4], ecx ; For better call stacks. + mov cx, [eax + X86TSS32.cs] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.cs], cx + mov cx, [eax + X86TSS32.ds] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.ds], cx + mov cx, [eax + X86TSS32.es] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.es], cx + mov cx, [eax + X86TSS32.fs] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.fs], cx + mov cx, [eax + X86TSS32.gs] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.gs], cx + mov cx, [eax + X86TSS32.ss] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.ss], cx + mov cx, [eax + X86TSS32.selLdt] ; Note! This isn't necessarily the ldtr at the time of the fault. + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.ldtr], cx + mov cx, [eax + X86TSS32.cr3] ; Note! This isn't necessarily the cr3 at the time of the fault. + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.cr3], ecx + + ; + ; Set CPL; copy and update mode. + ; + mov cl, [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.ss] + and cl, 3 + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.bCpl], cl + + mov cl, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.bMode], cl + and cl, ~BS3_MODE_CODE_MASK + or cl, BS3_MODE_CODE_32 + mov [BS3_DATA16_WRT(g_bBs3CurrentMode)], cl + + ; + ; Join code paths with the generic handler code. + ; + jmp bs3Trap32GenericCommon.save_cr0_cr2_cr4 +BS3_PROC_END Bs3Trap32DoubleFaultHandler + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c64-Trap64Generic.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c64-Trap64Generic.asm new file mode 100644 index 00000000..d9134152 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-c64-Trap64Generic.asm @@ -0,0 +1,337 @@ +; $Id: bs3-c64-Trap64Generic.asm $ +;; @file +; BS3Kit - Trap, 64-bit assembly handlers. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + +%ifndef TMPL_64BIT + %error "64-bit only template" +%endif + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_EXTERN_DATA16 g_bBs3CurrentMode +BS3_EXTERN_DATA16 g_apfnBs3TrapHandlers_c64 +TMPL_BEGIN_TEXT +BS3_EXTERN_CMN Bs3TrapDefaultHandler +BS3_EXTERN_CMN Bs3RegCtxRestore +TMPL_BEGIN_TEXT + + +;********************************************************************************************************************************* +;* Global Variables * +;********************************************************************************************************************************* +BS3_BEGIN_DATA16 +;; Easy to access flat address of Bs3Trap64GenericEntries. +BS3_GLOBAL_DATA g_Bs3Trap64GenericEntriesFlatAddr, 4 + dd Bs3Trap64GenericEntries wrt FLAT + + +TMPL_BEGIN_TEXT + +;; +; Generic entry points for IDT handlers, 8 byte spacing. +; +BS3_PROC_BEGIN Bs3Trap64GenericEntries +%macro Bs3Trap64GenericEntry 1 + db 06ah, i ; push imm8 - note that this is a signextended value. + jmp %1 + ALIGNCODE(8) +%assign i i+1 +%endmacro + +%assign i 0 ; start counter. + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 0 + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 1 + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 2 + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 3 + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 4 + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 5 + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 6 + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 7 + Bs3Trap64GenericEntry Bs3Trap64GenericTrapErrCode ; 8 + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 9 + Bs3Trap64GenericEntry Bs3Trap64GenericTrapErrCode ; a + Bs3Trap64GenericEntry Bs3Trap64GenericTrapErrCode ; b + Bs3Trap64GenericEntry Bs3Trap64GenericTrapErrCode ; c + Bs3Trap64GenericEntry Bs3Trap64GenericTrapErrCode ; d + Bs3Trap64GenericEntry Bs3Trap64GenericTrapErrCode ; e + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; f (reserved) + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 10 + Bs3Trap64GenericEntry Bs3Trap64GenericTrapErrCode ; 11 + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 12 + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 13 + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 14 + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 15 (reserved) + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 16 (reserved) + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 17 (reserved) + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 18 (reserved) + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 19 (reserved) + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 1a (reserved) + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 1b (reserved) + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 1c (reserved) + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 1d (reserved) + Bs3Trap64GenericEntry Bs3Trap64GenericTrapErrCode ; 1e + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt ; 1f (reserved) +%rep 224 + Bs3Trap64GenericEntry Bs3Trap64GenericTrapOrInt +%endrep +BS3_PROC_END Bs3Trap64GenericEntries + + + + +;; +; Trap or interrupt (no error code). +; +BS3_PROC_BEGIN Bs3Trap64GenericTrapOrInt + push rbp ; 0 + mov rbp, rsp + pushfq ; -08h + cld + push rdi + + ; Reserve space for the register and trap frame. + mov edi, (BS3TRAPFRAME_size + 15) / 16 +.more_zeroed_space: + push qword 0 + push qword 0 + dec edi + jnz .more_zeroed_space + mov rdi, rsp ; rdi points to trapframe structure. + + ; Free up rax. + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rax], rax + + ; Copy stuff from the stack over. + mov al, [rbp + 08h] + mov [rdi + BS3TRAPFRAME.bXcpt], al + mov rax, [rbp] + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.rbp], rax + mov rax, [rbp - 08h] + mov [rdi + BS3TRAPFRAME.fHandlerRfl], rax + mov rax, [rbp - 10h] + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.rdi], rax + + lea rbp, [rbp + 08h] ; iret - 8 (i.e. rbp frame chain location) + jmp Bs3Trap64GenericCommon +BS3_PROC_END Bs3Trap64GenericTrapOrInt + + +;; +; Trap with error code. +; +BS3_PROC_BEGIN Bs3Trap64GenericTrapErrCode + push rbp ; 0 + mov rbp, rsp + pushfq ; -08h + cld + push rdi + + ; Reserve space for the register and trap frame. + mov edi, (BS3TRAPFRAME_size + 15) / 16 +.more_zeroed_space: + push qword 0 + push qword 0 + dec edi + jnz .more_zeroed_space + mov rdi, rsp ; rdi points to trapframe structure. + + ; Free up rax. + mov [edi + BS3TRAPFRAME.Ctx + BS3REGCTX.rax], rax + + ; Copy stuff from the stack over. + mov rax, [rbp + 10h] + mov [rdi + BS3TRAPFRAME.uErrCd], rax + mov al, [rbp + 08h] + mov [rdi + BS3TRAPFRAME.bXcpt], al + mov rax, [rbp] + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.rbp], rax + mov rax, [rbp - 08h] + mov [rdi + BS3TRAPFRAME.fHandlerRfl], rax + mov rax, [rbp - 10h] + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.rdi], rax + + lea rbp, [rbp + 10h] ; iret - 8 (i.e. rbp frame chain location) + jmp Bs3Trap64GenericCommon +BS3_PROC_END Bs3Trap64GenericTrapErrCode + + +;; +; Common context saving code and dispatching. +; +; @param rdi Pointer to the trap frame. The following members have been +; filled in by the previous code: +; - bXcpt +; - uErrCd +; - fHandlerRfl +; - Ctx.rax +; - Ctx.rbp +; - Ctx.rdi +; +; @param rbp Pointer to the dword before the iret frame, i.e. where rbp +; would be saved if this was a normal call. +; +BS3_PROC_BEGIN Bs3Trap64GenericCommon + ; + ; Fake RBP frame. + ; + mov rax, [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.rbp] + mov [rbp], rax + + ; + ; Save the remaining GPRs and segment registers. + ; + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.rcx], rcx + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.rdx], rdx + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.rbx], rbx + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.rsi], rsi + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.r8 ], r8 + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.r9 ], r9 + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.r10], r10 + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.r11], r11 + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.r12], r12 + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.r13], r13 + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.r14], r14 + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.r15], r15 + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.ds], ds + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.es], es + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.fs], fs + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.gs], gs + lea rax, [rbp + 8h] + mov [rdi + BS3TRAPFRAME.uHandlerRsp], rax + mov [rdi + BS3TRAPFRAME.uHandlerSs], ss + + ; + ; Load 32-bit data selector for the DPL we're executing at into DS, ES and SS. + ; Save the handler CS value first. + ; + mov ax, cs + mov [rdi + BS3TRAPFRAME.uHandlerCs], ax + AssertCompile(BS3_SEL_RING_SHIFT == 8) + and al, 3 + mov ah, al + add ax, BS3_SEL_R0_DS64 + mov ds, ax + mov es, ax + mov ss, ax + + ; + ; Copy and update the mode. + ; + mov al, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.bMode], al + and al, ~BS3_MODE_CODE_MASK + or al, BS3_MODE_CODE_64 + mov [BS3_DATA16_WRT(g_bBs3CurrentMode)], al + + ; + ; Copy iret info. Bless AMD for only doing one 64-bit iret frame layout. + ; + mov rcx, [rbp + 08] + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.rip], rcx + mov cx, [rbp + 10h] + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.cs], cx + and cl, 3 + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.bCpl], cl + mov rcx, [rbp + 18h] + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.rflags], rcx + mov rcx, [rbp + 20h] + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.rsp], rcx + mov cx, [rbp + 28h] + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.ss], cx + mov byte [rdi + BS3TRAPFRAME.cbIretFrame], 5*8 + + ; + ; Control registers. + ; + str ax + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.tr], ax + sldt ax + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.ldtr], ax + + mov ax, ss + test al, 3 + jnz .skip_crX_because_cpl_not_0 + + mov rax, cr0 + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.cr0], rax + mov rax, cr2 + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.cr2], rax + mov rax, cr3 + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.cr3], rax + mov rax, cr4 + mov [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.cr4], rax + jmp .dispatch_to_handler + +.skip_crX_because_cpl_not_0: + or byte [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.fbFlags], \ + BS3REG_CTX_F_NO_CR0_IS_MSW | BS3REG_CTX_F_NO_CR2_CR3 | BS3REG_CTX_F_NO_CR4 + smsw [rdi + BS3TRAPFRAME.Ctx + BS3REGCTX.cr0] + + ; + ; Dispatch it to C code. + ; +.dispatch_to_handler: ; The double fault code joins us here. + movzx ebx, byte [rdi + BS3TRAPFRAME.bXcpt] + lea rax, [BS3_DATA16_WRT(_g_apfnBs3TrapHandlers_c64)] + mov rax, [rax + rbx * 8] + or rax, rax + jnz .call_handler + lea rax, [BS3_WRT_RIP(Bs3TrapDefaultHandler)] +.call_handler: + sub rsp, 20h + mov [rsp], rdi + mov rcx, rdi + call rax + + ; + ; Resume execution using trap frame. + ; + xor edx, edx ; fFlags + mov [rsp + 8], rdx + lea rcx, [rdi + BS3TRAPFRAME.Ctx] ; pCtx + mov [rsp], rcx + call Bs3RegCtxRestore +.panic: + hlt + jmp .panic +BS3_PROC_END Bs3Trap64GenericCommon + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-A20Disable.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-A20Disable.asm new file mode 100644 index 00000000..1867a512 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-A20Disable.asm @@ -0,0 +1,115 @@ +; $Id: bs3-cmn-A20Disable.asm $ +;; @file +; BS3Kit - Bs3A20Disable. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3KbdWait +BS3_EXTERN_CMN Bs3KbdRead +BS3_EXTERN_CMN Bs3KbdWrite + + +;; +; Disables the A20 gate. +; +; @uses Nothing. +; +BS3_PROC_BEGIN_CMN Bs3A20Disable, BS3_PBC_HYBRID_0_ARGS + ; Must call both because they may be ORed together on real HW. +BONLY64 sub rsp, 20h + call BS3_CMN_NM(Bs3A20DisableViaKbd) + call BS3_CMN_NM(Bs3A20DisableViaPortA) +BONLY64 add rsp, 20h + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3A20Disable + + +;; +; Disables the A20 gate via control port A (PS/2 style). +; +; @uses Nothing. +; +BS3_PROC_BEGIN_CMN Bs3A20DisableViaPortA, BS3_PBC_HYBRID_0_ARGS + push xAX + + ; Use Control port A, assuming a PS/2 style system. + in al, 092h + test al, 02h + jz .done ; avoid trouble writing back the same value. + and al, 0fdh ; disable the A20 gate. + out 092h, al + +.done: + pop xAX + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3A20DisableViaPortA + + +;; +; Disables the A20 gate via the keyboard controller. +; +; @uses Nothing. +; +BS3_PROC_BEGIN_CMN Bs3A20DisableViaKbd, BS3_PBC_HYBRID_0_ARGS + push xBP + mov xBP, xSP + push xAX + pushf + cli +BONLY64 sub rsp, 20h + + call Bs3KbdWait + push 0d0h ; KBD_CCMD_READ_OUTPORT + call Bs3KbdRead + + and al, 0fdh ; ~2 + push xAX + push 0d1h ; KBD_CCMD_WRITE_OUTPORT + call Bs3KbdWrite + + add xSP, xCB*3 ; Clean up both the above calls. + + mov al, 0ffh ; KBD_CMD_RESET + out 64h, al + call Bs3KbdWait + +BONLY64 add rsp, 20h + popf + pop xAX + pop xBP + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3A20DisableViaKbd + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-A20Enable.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-A20Enable.asm new file mode 100644 index 00000000..0ff4d341 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-A20Enable.asm @@ -0,0 +1,122 @@ +; $Id: bs3-cmn-A20Enable.asm $ +;; @file +; BS3Kit - Bs3A20Enable. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3KbdWait +BS3_EXTERN_CMN Bs3KbdRead +BS3_EXTERN_CMN Bs3KbdWrite + + +;; +; Enables the A20 gate. +; +; @uses Nothing. +; +BS3_PROC_BEGIN_CMN Bs3A20Enable, BS3_PBC_HYBRID_0_ARGS + push xBP + mov xBP, xSP +BONLY64 sub rsp, 20h + + call BS3_CMN_NM(Bs3A20EnableViaPortA) +;; @todo real 286 support +; call BS3_CMN_NM(Bs3A20EnableViaKbd) + + pop xBP + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3A20Enable + + +;; +; Enables the A20 gate via the keyboard controller. +; +; @uses Nothing. +; +BS3_PROC_BEGIN_CMN Bs3A20EnableViaKbd, BS3_PBC_HYBRID_0_ARGS + push xBP + mov xBP, xSP + push xAX + pushf + cli +BONLY64 sub rsp, 20h + + call Bs3KbdWait + push 0d0h ; KBD_CCMD_READ_OUTPORT + call Bs3KbdRead + + or al, 002h + push xAX + push 0d1h ; KBD_CCMD_WRITE_OUTPORT + call Bs3KbdWrite + + add xSP, xCB*3 ; both the above calls + + mov al, 0ffh ; KBD_CMD_RESET + out 64h, al + call Bs3KbdWait + +BONLY64 add rsp, 20h + popf + pop xAX + pop xBP + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3A20EnableViaKbd + + +;; +; Enables the A20 gate via control port A (PS/2 style). +; +; @uses Nothing. +; +BS3_PROC_BEGIN_CMN Bs3A20EnableViaPortA, BS3_PBC_HYBRID_0_ARGS + push xBP + mov xBP, xSP + push xAX + + ; Use Control port A, assuming a PS/2 style system. + in al, 092h + test al, 02h + jnz .done ; avoid trouble writing back the same value. + or al, 2 ; enable the A20 gate. + out 092h, al + +.done: + pop xAX + pop xBP + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3A20EnableViaPortA + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ConvertRMStackToP16UsingCxReturnToAx.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ConvertRMStackToP16UsingCxReturnToAx.asm new file mode 100644 index 00000000..83949bc9 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ConvertRMStackToP16UsingCxReturnToAx.asm @@ -0,0 +1,88 @@ +; $Id: bs3-cmn-ConvertRMStackToP16UsingCxReturnToAx.asm $ +;; @file +; BS3Kit - Bs3ConvertRMStackToP16UsingCxReturnToAx. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +%if TMPL_BITS != 16 + %error 16-bit only! +%endif + +;; +; An internal helper for converting a real-mode stack into a 16-bit protected +; mode stack. +; +; This is used by the mode switchers that ends up in 16-bit mode. It is +; assumed that we're in ring-0. +; +; @param ax The return address. +; +; @uses cx, ss, esp +; +BS3_PROC_BEGIN_CMN Bs3ConvertRMStackToP16UsingCxReturnToAx, BS3_PBC_NEAR + + ; + ; Check if it looks like the normal stack, if use BS3_SEL_R0_SS16. + ; + mov cx, ss + cmp cx, 0 + jne .stack_tiled + mov cx, BS3_SEL_R0_SS16 + mov ss, cx + jmp ax + + ; + ; Some custom stack address, just use the 16-bit tiled mappings + ; +.stack_tiled: +int3 ; debug this, shouldn't happen yet. Bs3EnteredMode_xxx isn't prepared. + shl cx, 4 + add sp, cx + mov cx, ss + jc .stack_carry + shr cx, 12 + jmp .stack_join_up_again +.stack_carry: + shr cx, 12 + inc cx +.stack_join_up_again: + shl cx, 3 + adc cx, BS3_SEL_TILED + mov ss, cx + jmp ax + +BS3_PROC_END_CMN Bs3ConvertRMStackToP16UsingCxReturnToAx + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-CpuDetectData.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-CpuDetectData.c new file mode 100644 index 00000000..ba9a24cd --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-CpuDetectData.c @@ -0,0 +1,54 @@ +/* $Id: bs3-cmn-CpuDetectData.c $ */ +/** @file + * BS3Kit - Detected CPU data. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-test.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#if ARCH_BITS == 16 + +uint16_t g_uBs3CpuDetected = BS3CPU_TYPE_MASK | BS3CPU_F_CPUID | BS3CPU_F_CPUID_EXT_LEAVES + | BS3CPU_F_PAE | BS3CPU_F_PSE | BS3CPU_F_LONG_MODE; + +#endif /* ARCH_BITS == 16 */ + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxAlloc.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxAlloc.c new file mode 100644 index 00000000..1d904611 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxAlloc.c @@ -0,0 +1,54 @@ +/* $Id: bs3-cmn-ExtCtxAlloc.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxAlloc + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3ExtCtxAlloc +BS3_CMN_DEF(PBS3EXTCTX, Bs3ExtCtxAlloc,(BS3MEMKIND enmKind)) +{ + uint64_t fFlags; + uint16_t cbExtCtx = Bs3ExtCtxGetSize(&fFlags); + PBS3EXTCTX pExtCtx = (PBS3EXTCTX)Bs3MemAlloc(enmKind, cbExtCtx); + if (pExtCtx) + return Bs3ExtCtxInit(pExtCtx, cbExtCtx, fFlags); + return NULL; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxCopy.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxCopy.c new file mode 100644 index 00000000..e6179d74 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxCopy.c @@ -0,0 +1,53 @@ +/* $Id: bs3-cmn-ExtCtxCopy.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxCopy + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include <iprt/asm-amd64-x86.h> + + +#undef Bs3ExtCtxCopy +BS3_CMN_DEF(PBS3EXTCTX, Bs3ExtCtxCopy,(PBS3EXTCTX pDst, PCBS3EXTCTX pSrc)) +{ + BS3_ASSERT(pDst->cb == pSrc->cb && pDst->enmMethod == pSrc->enmMethod && pDst->fXcr0Nominal == pSrc->fXcr0Nominal); + Bs3MemCpy(&pDst->Ctx, &pSrc->Ctx, pDst->cb - RT_UOFFSETOF(BS3EXTCTX, Ctx)); + pDst->fXcr0Saved = pSrc->fXcr0Saved; + return pDst; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxFree.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxFree.c new file mode 100644 index 00000000..ce7be29b --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxFree.c @@ -0,0 +1,56 @@ +/* $Id: bs3-cmn-ExtCtxFree.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxFree + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3ExtCtxFree +BS3_CMN_DEF(void, Bs3ExtCtxFree,(PBS3EXTCTX pExtCtx)) +{ + if (pExtCtx) + { + if (pExtCtx->u16Magic == BS3EXTCTX_MAGIC) + { + pExtCtx->u16Magic = ~BS3EXTCTX_MAGIC; + Bs3MemFree(pExtCtx, pExtCtx->cb); + } + } +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetAbridgedFtw.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetAbridgedFtw.c new file mode 100644 index 00000000..015a7e9a --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetAbridgedFtw.c @@ -0,0 +1,71 @@ +/* $Id: bs3-cmn-ExtCtxGetAbridgedFtw.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxGetAbridgedFtw + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3ExtCtxGetAbridgedFtw +BS3_CMN_DEF(uint16_t, Bs3ExtCtxGetAbridgedFtw,(PCBS3EXTCTX pExtCtx)) +{ + AssertCompileMembersAtSameOffset(BS3EXTCTX, Ctx.x87.FTW, BS3EXTCTX, Ctx.x.x87.FTW); + switch (pExtCtx->enmMethod) + { + case BS3EXTCTXMETHOD_FXSAVE: + case BS3EXTCTXMETHOD_XSAVE: + return pExtCtx->Ctx.x87.FTW; + + case BS3EXTCTXMETHOD_ANCIENT: + { + /* iemFpuCompressFtw: */ + uint16_t u16FullFtw = pExtCtx->Ctx.Ancient.FTW; + uint8_t u8Ftw = 0; + unsigned i; + for (i = 0; i < 8; i++) + { + if ((u16FullFtw & 3) != 3 /*empty*/) + u8Ftw |= RT_BIT(i); + u16FullFtw >>= 2; + } + return u8Ftw; + } + } + return 0; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetFcw.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetFcw.c new file mode 100644 index 00000000..cc927253 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetFcw.c @@ -0,0 +1,59 @@ +/* $Id: bs3-cmn-ExtCtxGetFcw.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxGetFcw + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3ExtCtxGetFcw +BS3_CMN_DEF(uint16_t, Bs3ExtCtxGetFcw,(PCBS3EXTCTX pExtCtx)) +{ + AssertCompileMembersAtSameOffset(BS3EXTCTX, Ctx.x87.FCW, BS3EXTCTX, Ctx.x.x87.FCW); + switch (pExtCtx->enmMethod) + { + case BS3EXTCTXMETHOD_FXSAVE: + case BS3EXTCTXMETHOD_XSAVE: + return pExtCtx->Ctx.x87.FCW; + + case BS3EXTCTXMETHOD_ANCIENT: + return pExtCtx->Ctx.Ancient.FCW; + } + return 0; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetFsw.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetFsw.c new file mode 100644 index 00000000..6eb373f2 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetFsw.c @@ -0,0 +1,59 @@ +/* $Id: bs3-cmn-ExtCtxGetFsw.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxGetFsw + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3ExtCtxGetFsw +BS3_CMN_DEF(uint16_t, Bs3ExtCtxGetFsw,(PCBS3EXTCTX pExtCtx)) +{ + AssertCompileMembersAtSameOffset(BS3EXTCTX, Ctx.x87.FSW, BS3EXTCTX, Ctx.x.x87.FSW); + switch (pExtCtx->enmMethod) + { + case BS3EXTCTXMETHOD_FXSAVE: + case BS3EXTCTXMETHOD_XSAVE: + return pExtCtx->Ctx.x87.FSW; + + case BS3EXTCTXMETHOD_ANCIENT: + return pExtCtx->Ctx.Ancient.FSW; + } + return 0; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetMm.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetMm.c new file mode 100644 index 00000000..f4edaec9 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetMm.c @@ -0,0 +1,60 @@ +/* $Id: bs3-cmn-ExtCtxGetMm.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxGetMm + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3ExtCtxGetMm +BS3_CMN_DEF(uint64_t, Bs3ExtCtxGetMm,(PCBS3EXTCTX pExtCtx, uint8_t iReg)) +{ + AssertCompileMembersAtSameOffset(BS3EXTCTX, Ctx.x87.aRegs, BS3EXTCTX, Ctx.x.x87.aRegs); + if (iReg < RT_ELEMENTS(pExtCtx->Ctx.x87.aRegs)) + switch (pExtCtx->enmMethod) + { + case BS3EXTCTXMETHOD_FXSAVE: + case BS3EXTCTXMETHOD_XSAVE: + return pExtCtx->Ctx.x87.aRegs[iReg].mmx; + + case BS3EXTCTXMETHOD_ANCIENT: + return pExtCtx->Ctx.Ancient.regs[iReg].mmx; + } + return 0; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetMxCsr.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetMxCsr.c new file mode 100644 index 00000000..dd3d692f --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetMxCsr.c @@ -0,0 +1,53 @@ +/* $Id: bs3-cmn-ExtCtxGetMxCsr.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxGetMxCsr + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3ExtCtxGetMxCsr +BS3_CMN_DEF(uint32_t, Bs3ExtCtxGetMxCsr,(PCBS3EXTCTX pExtCtx)) +{ + AssertCompileMembersAtSameOffset(BS3EXTCTX, Ctx.x87.MXCSR, BS3EXTCTX, Ctx.x.x87.MXCSR); + if ( pExtCtx->enmMethod == BS3EXTCTXMETHOD_FXSAVE + || pExtCtx->enmMethod == BS3EXTCTXMETHOD_XSAVE) + return pExtCtx->Ctx.x87.MXCSR; + return 0; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetMxCsrMask.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetMxCsrMask.c new file mode 100644 index 00000000..edf22c8e --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetMxCsrMask.c @@ -0,0 +1,53 @@ +/* $Id: bs3-cmn-ExtCtxGetMxCsrMask.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxGetMxCsrMask + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3ExtCtxGetMxCsrMask +BS3_CMN_DEF(uint32_t, Bs3ExtCtxGetMxCsrMask,(PCBS3EXTCTX pExtCtx)) +{ + AssertCompileMembersAtSameOffset(BS3EXTCTX, Ctx.x87.MXCSR_MASK, BS3EXTCTX, Ctx.x.x87.MXCSR_MASK); + if ( pExtCtx->enmMethod == BS3EXTCTXMETHOD_FXSAVE + || pExtCtx->enmMethod == BS3EXTCTXMETHOD_XSAVE) + return pExtCtx->Ctx.x87.MXCSR_MASK; + return 0; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetSize.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetSize.c new file mode 100644 index 00000000..0cca002e --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetSize.c @@ -0,0 +1,69 @@ +/* $Id: bs3-cmn-ExtCtxGetSize.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxGetSize + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include <iprt/asm-amd64-x86.h> + + +#undef Bs3ExtCtxGetSize +BS3_CMN_DEF(uint16_t, Bs3ExtCtxGetSize,(uint64_t BS3_FAR *pfFlags)) +{ + uint32_t fEcx, fEdx; + *pfFlags = 0; + + ASMCpuIdExSlow(1, 0, 0, 0, NULL, NULL, &fEcx, &fEdx); +#if 1 /* To disable xsave/xrstor till IEM groks it... */ + if (fEcx & X86_CPUID_FEATURE_ECX_XSAVE) + { + uint32_t fEax; + ASMCpuIdExSlow(13, 0, 0, 0, &fEax, NULL, &fEcx, &fEdx); + if ( fEcx >= sizeof(X86FXSTATE) + sizeof(X86XSAVEHDR) + && fEcx < _32K) + { + *pfFlags = fEax | ((uint64_t)fEdx << 32); + return RT_UOFFSETOF(BS3EXTCTX, Ctx) + RT_ALIGN(fEcx, 256); + } + } +#endif + if (fEdx & X86_CPUID_FEATURE_EDX_FXSR) + return RT_UOFFSETOF(BS3EXTCTX, Ctx) + sizeof(X86FXSTATE); + return RT_UOFFSETOF(BS3EXTCTX, Ctx) + sizeof(X86FPUSTATE); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetXmm.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetXmm.c new file mode 100644 index 00000000..90b53f42 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetXmm.c @@ -0,0 +1,64 @@ +/* $Id: bs3-cmn-ExtCtxGetXmm.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxGetXmm + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3ExtCtxGetXmm +BS3_CMN_DEF(PRTUINT128U, Bs3ExtCtxGetXmm,(PCBS3EXTCTX pExtCtx, uint8_t iReg, PRTUINT128U pValue)) +{ + AssertCompileMembersAtSameOffset(BS3EXTCTX, Ctx.x87.aXMM, BS3EXTCTX, Ctx.x.x87.aXMM); + switch (pExtCtx->enmMethod) + { + case BS3EXTCTXMETHOD_FXSAVE: + case BS3EXTCTXMETHOD_XSAVE: + if (iReg < RT_ELEMENTS(pExtCtx->Ctx.x87.aXMM)) + { + pValue->u = pExtCtx->Ctx.x87.aXMM[iReg].xmm; + return pValue; + } + break; + } + + pValue->au64[0] = 0; + pValue->au64[1] = 0; + return pValue; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetYmm.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetYmm.c new file mode 100644 index 00000000..9de49be4 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxGetYmm.c @@ -0,0 +1,70 @@ +/* $Id: bs3-cmn-ExtCtxGetYmm.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxGetYmm + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3ExtCtxGetYmm +BS3_CMN_DEF(PRTUINT256U, Bs3ExtCtxGetYmm,(PCBS3EXTCTX pExtCtx, uint8_t iReg, PRTUINT256U pValue)) +{ + pValue->au128[0].au64[0] = 0; + pValue->au128[0].au64[1] = 0; + pValue->au128[1].au64[0] = 0; + pValue->au128[1].au64[1] = 0; + + switch (pExtCtx->enmMethod) + { + case BS3EXTCTXMETHOD_FXSAVE: + if (iReg < RT_ELEMENTS(pExtCtx->Ctx.x87.aXMM)) + pValue->au128[0] = pExtCtx->Ctx.x87.aXMM[iReg].uXmm; + break; + + case BS3EXTCTXMETHOD_XSAVE: + if (iReg < RT_ELEMENTS(pExtCtx->Ctx.x.x87.aXMM)) + { + pValue->au128[0] = pExtCtx->Ctx.x87.aXMM[iReg].uXmm; + if (pExtCtx->fXcr0Nominal & XSAVE_C_YMM) + pValue->au128[1] = pExtCtx->Ctx.x.u.YmmHi.aYmmHi[iReg].uXmm; + } + break; + } + return pValue; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxInit.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxInit.c new file mode 100644 index 00000000..050f6bf0 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxInit.c @@ -0,0 +1,84 @@ +/* $Id: bs3-cmn-ExtCtxInit.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxInit + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include <iprt/asm-amd64-x86.h> + + +#undef Bs3ExtCtxInit +BS3_CMN_DEF(PBS3EXTCTX, Bs3ExtCtxInit,(PBS3EXTCTX pExtCtx, uint16_t cbExtCtx, uint64_t fFlags)) +{ + Bs3MemSet(pExtCtx, 0, cbExtCtx); + + if (cbExtCtx >= RT_UOFFSETOF(BS3EXTCTX, Ctx) + sizeof(X86FXSTATE) + sizeof(X86XSAVEHDR)) + { + BS3_ASSERT(fFlags & XSAVE_C_X87); + pExtCtx->enmMethod = BS3EXTCTXMETHOD_XSAVE; + pExtCtx->Ctx.x.Hdr.bmXState = fFlags; + + /* Setting bit 6 (0x40) here as it kept sneaking in when loading/saving state in 16-bit and v8086 mode. */ + pExtCtx->Ctx.x.x87.FCW = X86_FCW_RC_NEAREST | X86_FCW_PC_64 /* go figure:*/ | RT_BIT(6); + pExtCtx->Ctx.x.x87.MXCSR = X86_MXCSR_RC_NEAREST; + pExtCtx->Ctx.x.x87.MXCSR_MASK = 0xffff; + } + else if (cbExtCtx >= RT_UOFFSETOF(BS3EXTCTX, Ctx) + sizeof(X86FXSTATE)) + { + BS3_ASSERT(fFlags == 0); + pExtCtx->enmMethod = BS3EXTCTXMETHOD_FXSAVE; + pExtCtx->Ctx.x87.FCW = X86_FCW_RC_NEAREST | X86_FCW_PC_64 /* go figure:*/ | RT_BIT(6); + pExtCtx->Ctx.x87.MXCSR = X86_MXCSR_RC_NEAREST; + pExtCtx->Ctx.x87.MXCSR_MASK = 0xffff; + } + else + { + BS3_ASSERT(fFlags == 0); + BS3_ASSERT(cbExtCtx >= RT_UOFFSETOF(BS3EXTCTX, Ctx) + sizeof(X86FPUSTATE)); + pExtCtx->enmMethod = BS3EXTCTXMETHOD_ANCIENT; + pExtCtx->Ctx.Ancient.FCW = X86_FCW_RC_NEAREST | X86_FCW_PC_64 /* go figure:*/ | RT_BIT(6); + pExtCtx->Ctx.Ancient.FTW = UINT16_MAX; /* all registers empty */ + } + + pExtCtx->cb = cbExtCtx; + pExtCtx->u16Magic = BS3EXTCTX_MAGIC; + pExtCtx->fXcr0Nominal = fFlags; + pExtCtx->fXcr0Saved = fFlags; + return pExtCtx; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxRestore.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxRestore.asm new file mode 100644 index 00000000..475255a8 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxRestore.asm @@ -0,0 +1,155 @@ +; $Id: bs3-cmn-ExtCtxRestore.asm $ +;; @file +; BS3Kit - Bs3ExtCtxRestore. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +extern BS3_CMN_NM(Bs3RegSetXcr0) + + +;; +; Restores the extended CPU context (FPU, SSE, AVX, ++). +; +; @param pExtCtx +; +BS3_PROC_BEGIN_CMN Bs3ExtCtxRestore, BS3_PBC_NEAR + push xBP + mov xBP, xSP + push sAX + push sCX + push sDX + push xBX +BONLY16 push es + +%if ARCH_BITS == 16 + les bx, [xBP + xCB + cbCurRetAddr] + mov al, [es:bx + BS3EXTCTX.enmMethod] + cmp al, BS3EXTCTXMETHOD_XSAVE + je .do_16_xsave + cmp al, BS3EXTCTXMETHOD_FXSAVE + je .do_16_fxsave + cmp al, BS3EXTCTXMETHOD_ANCIENT + je .do_16_ancient + int3 + +.do_16_ancient: + frstor [es:bx + BS3EXTCTX.Ctx] + jmp .return + +.do_16_fxsave: + fxrstor [es:bx + BS3EXTCTX.Ctx] + jmp .return + +.do_16_xsave: + push dword [es:bx + BS3EXTCTX.fXcr0Nominal + 4] + push dword [es:bx + BS3EXTCTX.fXcr0Nominal] + call BS3_CMN_NM(Bs3RegSetXcr0) + + mov eax, [es:bx + BS3EXTCTX.fXcr0Nominal] + mov edx, [es:bx + BS3EXTCTX.fXcr0Nominal + 4] + xrstor [es:bx + BS3EXTCTX.Ctx] + + push dword [es:bx + BS3EXTCTX.fXcr0Saved + 4] + push dword [es:bx + BS3EXTCTX.fXcr0Saved] + call BS3_CMN_NM(Bs3RegSetXcr0) + + add xSP, 4 * 2 * 2 ; clean up both calls + ;jmp .return + +%else +BONLY32 mov ebx, [xBP + xCB + cbCurRetAddr] +BONLY64 mov rbx, rcx + + mov al, [xBX + BS3EXTCTX.enmMethod] + cmp al, BS3EXTCTXMETHOD_XSAVE + je .do_xsave + cmp al, BS3EXTCTXMETHOD_FXSAVE + je .do_fxsave + cmp al, BS3EXTCTXMETHOD_ANCIENT + je .do_ancient + int3 + +.do_ancient: + frstor [xBX + BS3EXTCTX.Ctx] + jmp .return + +.do_fxsave: +BONLY32 fxrstor [xBX + BS3EXTCTX.Ctx] +BONLY64 fxrstor64 [xBX + BS3EXTCTX.Ctx] + jmp .return + +.do_xsave: + %if ARCH_BITS == 32 + push dword [xBX + BS3EXTCTX.fXcr0Nominal + 4] + push dword [xBX + BS3EXTCTX.fXcr0Nominal] + call BS3_CMN_NM(Bs3RegSetXcr0) + + mov eax, [xBX + BS3EXTCTX.fXcr0Nominal] + mov edx, [xBX + BS3EXTCTX.fXcr0Nominal + 4] + xrstor [xBX + BS3EXTCTX.Ctx] + + push dword [xBX + BS3EXTCTX.fXcr0Saved + 4] + push dword [xBX + BS3EXTCTX.fXcr0Saved] + call BS3_CMN_NM(Bs3RegSetXcr0) + + add xSP, 4 * 2 * 2 ; clean up both calls + %else + mov rcx, [xBX + BS3EXTCTX.fXcr0Nominal] + push rcx ; just for reserving parameter dumping space needed by Bs3RegSetXcr0 + call BS3_CMN_NM(Bs3RegSetXcr0) + + mov eax, [xBX + BS3EXTCTX.fXcr0Nominal] + mov edx, [xBX + BS3EXTCTX.fXcr0Nominal + 4] + xrstor64 [xBX + BS3EXTCTX.Ctx] + + mov rcx, [xBX + BS3EXTCTX.fXcr0Saved] + call BS3_CMN_NM(Bs3RegSetXcr0) + + add xSP, 8 ; clean up parameter space + ;jmp .return + %endif +%endif + +.return: +BONLY16 pop es + pop xBX + pop sDX + pop sCX + pop sAX + mov xSP, xBP + pop xBP + ret +BS3_PROC_END_CMN Bs3ExtCtxRestore + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxRestoreEx.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxRestoreEx.asm new file mode 100644 index 00000000..eddba966 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxRestoreEx.asm @@ -0,0 +1,136 @@ +; $Id: bs3-cmn-ExtCtxRestoreEx.asm $ +;; @file +; BS3Kit - Bs3ExtCtxRestoreEx. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +%if ARCH_BITS != 64 +BS3_EXTERN_DATA16 g_bBs3CurrentMode + +BS3_BEGIN_TEXT64 +extern _Bs3ExtCtxRestore_c64 + %if ARCH_BITS == 16 +extern BS3_CMN_NM(Bs3SelProtFar16DataToFlat) +extern _Bs3SwitchTo16Bit_c64 + %else +extern _Bs3SwitchTo32Bit_c64 + %endif + +TMPL_BEGIN_TEXT +extern BS3_CMN_NM(Bs3SwitchTo64Bit) + %if ARCH_BITS == 16 +extern BS3_CMN_NM(Bs3SelProtFar16DataToFlat) + %endif +%endif + +extern BS3_CMN_NM(Bs3ExtCtxRestore) + + + +;; +; Restores the extended CPU context (FPU, SSE, AVX, ++), full 64-bit +; when in long mode. +; +; @param pExtCtx +; +BS3_PROC_BEGIN_CMN Bs3ExtCtxRestoreEx, BS3_PBC_NEAR +%if ARCH_BITS == 64 + jmp BS3_CMN_NM(Bs3ExtCtxRestore) +%else + push xBP + mov xBP, xSP + push sAX + + ; + ; Check if we're in long mode. + ; + mov al, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + and al, BS3_MODE_SYS_MASK + cmp al, BS3_MODE_SYS_LM + je .in_long_mode + + ; + ; Not in long mode, so do normal restore. + ; + pop sAX + leave + jmp BS3_CMN_NM(Bs3ExtCtxRestore) + + ; + ; Switch to 64-bit to do the restoring so we can restore 64-bit only state. + ; +.in_long_mode: + push sCX +BONLY16 push sDX + + ; Load ecx with the flat pExtCtx address. + mov ecx, [xBP + xCB + cbCurRetAddr] + + %if ARCH_BITS == 16 + push ecx + call BS3_CMN_NM(Bs3SelProtFar16DataToFlat) + mov ecx, edx + shl ecx, 16 + mov cx, ax + %endif + + ; Switch to 64-bit mode. + call BS3_CMN_NM(Bs3SwitchTo64Bit) + BITS 64 + + ; Do the restore. + sub rsp, 20h + call _Bs3ExtCtxRestore_c64 + add rsp, 20h + + ; Switch back to the original mode. + %if ARCH_BITS == 16 + call _Bs3SwitchTo16Bit_c64 + %else + call _Bs3SwitchTo32Bit_c64 + %endif + BITS ARCH_BITS + + ; Restore context and return. +BONLY16 pop sDX + pop sCX + + pop sAX + leave + ret +%endif +BS3_PROC_END_CMN Bs3ExtCtxRestoreEx + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSave.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSave.asm new file mode 100644 index 00000000..37db1464 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSave.asm @@ -0,0 +1,166 @@ +; $Id: bs3-cmn-ExtCtxSave.asm $ +;; @file +; BS3Kit - Bs3ExtCtxSave. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +extern BS3_CMN_NM(Bs3RegSetXcr0) + +;; +; Saves the extended CPU context (FPU, SSE, AVX, ++). +; +; @param pExtCtx +; +BS3_PROC_BEGIN_CMN Bs3ExtCtxSave, BS3_PBC_NEAR + push xBP + mov xBP, xSP + push sAX + push sCX + push sDX + push xBX +BONLY16 push es + +%if ARCH_BITS == 16 + les bx, [xBP + xCB + cbCurRetAddr] + mov al, [es:bx + BS3EXTCTX.enmMethod] + cmp al, BS3EXTCTXMETHOD_XSAVE + je .do_16_xsave + cmp al, BS3EXTCTXMETHOD_FXSAVE + je .do_16_fxsave + cmp al, BS3EXTCTXMETHOD_ANCIENT + je .do_16_ancient + int3 + +.do_16_ancient: + fnsave [es:bx + BS3EXTCTX.Ctx] + jmp .return + +.do_16_fxsave: + fxsave [es:bx + BS3EXTCTX.Ctx] + jmp .return + +.do_16_xsave: + ; xgetbv can be used in any ring! + xor ecx, ecx + xgetbv + mov [es:bx + BS3EXTCTX.fXcr0Saved], eax + mov [es:bx + BS3EXTCTX.fXcr0Saved + 4], edx + + push dword [es:bx + BS3EXTCTX.fXcr0Nominal + 4] + push dword [es:bx + BS3EXTCTX.fXcr0Nominal] + call BS3_CMN_NM(Bs3RegSetXcr0) + + mov eax, [es:bx + BS3EXTCTX.fXcr0Nominal] + mov edx, [es:bx + BS3EXTCTX.fXcr0Nominal + 4] + xsave [es:bx + BS3EXTCTX.Ctx] + + push dword [es:bx + BS3EXTCTX.fXcr0Saved + 4] + push dword [es:bx + BS3EXTCTX.fXcr0Saved] + call BS3_CMN_NM(Bs3RegSetXcr0) + + add xSP, 4 * 2 * 2 ; clean up both calls + ;jmp .return + +%else +BONLY32 mov ebx, [xBP + xCB + cbCurRetAddr] +BONLY64 mov rbx, rcx + + mov al, [xBX + BS3EXTCTX.enmMethod] + cmp al, BS3EXTCTXMETHOD_XSAVE + je .do_xsave + cmp al, BS3EXTCTXMETHOD_FXSAVE + je .do_fxsave + cmp al, BS3EXTCTXMETHOD_ANCIENT + je .do_ancient + int3 + +.do_ancient: + fnsave [xBX + BS3EXTCTX.Ctx] + jmp .return + +.do_fxsave: +BONLY32 fxsave [xBX + BS3EXTCTX.Ctx] +BONLY64 fxsave64 [xBX + BS3EXTCTX.Ctx] + jmp .return + +.do_xsave: + xor ecx, ecx + xgetbv + mov [xBX + BS3EXTCTX.fXcr0Saved], eax + mov [xBX + BS3EXTCTX.fXcr0Saved + 4], edx + + %if ARCH_BITS == 32 + push dword [xBX + BS3EXTCTX.fXcr0Nominal + 4] + push dword [xBX + BS3EXTCTX.fXcr0Nominal] + call BS3_CMN_NM(Bs3RegSetXcr0) + + mov eax, [xBX + BS3EXTCTX.fXcr0Nominal] + mov edx, [xBX + BS3EXTCTX.fXcr0Nominal + 4] + xsave [xBX + BS3EXTCTX.Ctx] + + push dword [xBX + BS3EXTCTX.fXcr0Saved + 4] + push dword [xBX + BS3EXTCTX.fXcr0Saved] + call BS3_CMN_NM(Bs3RegSetXcr0) + + add xSP, 4 * 2 * 2 ; clean up both calls + %else + mov rcx, [xBX + BS3EXTCTX.fXcr0Nominal] + push rcx ; only to reserve necessary stack space for the Bs3RegSetXcr0 param dump. + call BS3_CMN_NM(Bs3RegSetXcr0) + + mov eax, [xBX + BS3EXTCTX.fXcr0Nominal] + mov edx, [xBX + BS3EXTCTX.fXcr0Nominal + 4] + xsave64 [xBX + BS3EXTCTX.Ctx] + + mov rcx, [xBX + BS3EXTCTX.fXcr0Saved] + call BS3_CMN_NM(Bs3RegSetXcr0) + + add xSP, 8h ; clean up + %endif + ;jmp .return + +%endif + +.return: +BONLY16 pop es + pop xBX + pop sDX + pop sCX + pop sAX + mov xSP, xBP + pop xBP + ret +BS3_PROC_END_CMN Bs3ExtCtxSave + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSaveEx.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSaveEx.asm new file mode 100644 index 00000000..0b90ae2b --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSaveEx.asm @@ -0,0 +1,136 @@ +; $Id: bs3-cmn-ExtCtxSaveEx.asm $ +;; @file +; BS3Kit - Bs3ExtCtxSaveEx. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +%if ARCH_BITS != 64 +BS3_EXTERN_DATA16 g_bBs3CurrentMode + +BS3_BEGIN_TEXT64 +extern _Bs3ExtCtxSave_c64 + %if ARCH_BITS == 16 +extern BS3_CMN_NM(Bs3SelProtFar16DataToFlat) +extern _Bs3SwitchTo16Bit_c64 + %else +extern _Bs3SwitchTo32Bit_c64 + %endif + +TMPL_BEGIN_TEXT +extern BS3_CMN_NM(Bs3SwitchTo64Bit) + %if ARCH_BITS == 16 +extern BS3_CMN_NM(Bs3SelProtFar16DataToFlat) + %endif +%endif + +extern BS3_CMN_NM(Bs3ExtCtxSave) + + + +;; +; Saves the extended CPU context (FPU, SSE, AVX, ++), full 64-bit +; when in long mode. +; +; @param pExtCtx +; +BS3_PROC_BEGIN_CMN Bs3ExtCtxSaveEx, BS3_PBC_NEAR +%if ARCH_BITS == 64 + jmp BS3_CMN_NM(Bs3ExtCtxSave) +%else + push xBP + mov xBP, xSP + push sAX + + ; + ; Check if we're in long mode. + ; + mov al, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + and al, BS3_MODE_SYS_MASK + cmp al, BS3_MODE_SYS_LM + je .in_long_mode + + ; + ; Not in long mode, so do normal save. + ; + pop sAX + leave + jmp BS3_CMN_NM(Bs3ExtCtxSave) + + ; + ; Switch to 64-bit to do the restoring so we can save the 64-bit only state. + ; +.in_long_mode: + push sCX +BONLY16 push sDX + + ; Load ecx with the flat pExtCtx address. + mov ecx, [xBP + xCB + cbCurRetAddr] + + %if ARCH_BITS == 16 + push ecx + call BS3_CMN_NM(Bs3SelProtFar16DataToFlat) + mov ecx, edx + shl ecx, 16 + mov cx, ax + %endif + + ; Switch to 64-bit mode. + call BS3_CMN_NM(Bs3SwitchTo64Bit) + BITS 64 + + ; Do the save. + sub rsp, 20h + call _Bs3ExtCtxSave_c64 + add rsp, 20h + + ; Switch back to the original mode. + %if ARCH_BITS == 16 + call _Bs3SwitchTo16Bit_c64 + %else + call _Bs3SwitchTo32Bit_c64 + %endif + BITS ARCH_BITS + + ; Save context and return. +BONLY16 pop sDX + pop sCX + + pop sAX + leave + ret +%endif +BS3_PROC_END_CMN Bs3ExtCtxSaveEx + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetAbridgedFtw.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetAbridgedFtw.c new file mode 100644 index 00000000..bf830a4f --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetAbridgedFtw.c @@ -0,0 +1,61 @@ +/* $Id: bs3-cmn-ExtCtxSetAbridgedFtw.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxSetAbridgedFtw + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3ExtCtxSetAbridgedFtw +BS3_CMN_DEF(bool, Bs3ExtCtxSetAbridgedFtw,(PBS3EXTCTX pExtCtx, uint16_t uValue)) +{ + AssertCompileMembersAtSameOffset(BS3EXTCTX, Ctx.x87.FTW, BS3EXTCTX, Ctx.x.x87.FTW); + switch (pExtCtx->enmMethod) + { + case BS3EXTCTXMETHOD_FXSAVE: + case BS3EXTCTXMETHOD_XSAVE: + pExtCtx->Ctx.x87.FTW = uValue; + return true; + + case BS3EXTCTXMETHOD_ANCIENT: + /* Could do iemFpuCalcFullFtw here, but too much work for now... */ + break; + } + return false; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetFcw.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetFcw.c new file mode 100644 index 00000000..bb543b59 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetFcw.c @@ -0,0 +1,60 @@ +/* $Id: bs3-cmn-ExtCtxSetFcw.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxSetFcw + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3ExtCtxSetFcw +BS3_CMN_DEF(void, Bs3ExtCtxSetFcw,(PBS3EXTCTX pExtCtx, uint16_t uValue)) +{ + AssertCompileMembersAtSameOffset(BS3EXTCTX, Ctx.x87.FCW, BS3EXTCTX, Ctx.x.x87.FCW); + switch (pExtCtx->enmMethod) + { + case BS3EXTCTXMETHOD_FXSAVE: + case BS3EXTCTXMETHOD_XSAVE: + pExtCtx->Ctx.x87.FCW = uValue; + break; + + case BS3EXTCTXMETHOD_ANCIENT: + pExtCtx->Ctx.Ancient.FCW = uValue; + break; + } +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetFsw.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetFsw.c new file mode 100644 index 00000000..860f408c --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetFsw.c @@ -0,0 +1,60 @@ +/* $Id: bs3-cmn-ExtCtxSetFsw.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxSetFsw + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3ExtCtxSetFsw +BS3_CMN_DEF(void, Bs3ExtCtxSetFsw,(PBS3EXTCTX pExtCtx, uint16_t uValue)) +{ + AssertCompileMembersAtSameOffset(BS3EXTCTX, Ctx.x87.FSW, BS3EXTCTX, Ctx.x.x87.FSW); + switch (pExtCtx->enmMethod) + { + case BS3EXTCTXMETHOD_FXSAVE: + case BS3EXTCTXMETHOD_XSAVE: + pExtCtx->Ctx.x87.FSW = uValue; + break; + + case BS3EXTCTXMETHOD_ANCIENT: + pExtCtx->Ctx.Ancient.FSW = uValue; + break; + } +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetMm.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetMm.c new file mode 100644 index 00000000..0c37f230 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetMm.c @@ -0,0 +1,66 @@ +/* $Id: bs3-cmn-ExtCtxSetMm.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxSetMm + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3ExtCtxSetMm +BS3_CMN_DEF(bool, Bs3ExtCtxSetMm,(PBS3EXTCTX pExtCtx, uint8_t iReg, uint64_t uValue, BS3EXTCTXTOPMM enmTop)) +{ + AssertCompileMembersAtSameOffset(BS3EXTCTX, Ctx.x87.aRegs, BS3EXTCTX, Ctx.x.x87.aRegs); + if (iReg < RT_ELEMENTS(pExtCtx->Ctx.x87.aRegs)) + switch (pExtCtx->enmMethod) + { + case BS3EXTCTXMETHOD_FXSAVE: + case BS3EXTCTXMETHOD_XSAVE: + pExtCtx->Ctx.x87.aRegs[iReg].mmx = uValue; + if (enmTop == BS3EXTCTXTOPMM_SET || enmTop == BS3EXTCTXTOPMM_ZERO) + pExtCtx->Ctx.x87.aRegs[iReg].au16[4] = enmTop == BS3EXTCTXTOPMM_SET ? UINT16_MAX : 0; + return true; + + case BS3EXTCTXMETHOD_ANCIENT: + pExtCtx->Ctx.Ancient.regs[iReg].mmx = uValue; + if (enmTop == BS3EXTCTXTOPMM_SET || enmTop == BS3EXTCTXTOPMM_ZERO) + pExtCtx->Ctx.Ancient.regs[iReg].au16[4] = enmTop == BS3EXTCTXTOPMM_SET ? UINT16_MAX : 0; + return true; + } + return false; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetMxCsr.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetMxCsr.c new file mode 100644 index 00000000..39c5f18c --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetMxCsr.c @@ -0,0 +1,56 @@ +/* $Id: bs3-cmn-ExtCtxSetMxCsr.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxSetMxCsr + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3ExtCtxSetMxCsr +BS3_CMN_DEF(bool, Bs3ExtCtxSetMxCsr,(PBS3EXTCTX pExtCtx, uint32_t uValue)) +{ + AssertCompileMembersAtSameOffset(BS3EXTCTX, Ctx.x87.MXCSR, BS3EXTCTX, Ctx.x.x87.MXCSR); + if ( pExtCtx->enmMethod == BS3EXTCTXMETHOD_FXSAVE + || pExtCtx->enmMethod == BS3EXTCTXMETHOD_XSAVE) + { + pExtCtx->Ctx.x87.MXCSR = uValue; + return true; + } + return false; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetMxCsrMask.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetMxCsrMask.c new file mode 100644 index 00000000..75d0e7c8 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetMxCsrMask.c @@ -0,0 +1,56 @@ +/* $Id: bs3-cmn-ExtCtxSetMxCsrMask.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxSetMxCsrMask + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3ExtCtxSetMxCsrMask +BS3_CMN_DEF(bool, Bs3ExtCtxSetMxCsrMask,(PBS3EXTCTX pExtCtx, uint32_t uValue)) +{ + AssertCompileMembersAtSameOffset(BS3EXTCTX, Ctx.x87.MXCSR, BS3EXTCTX, Ctx.x.x87.MXCSR); + if ( pExtCtx->enmMethod == BS3EXTCTXMETHOD_FXSAVE + || pExtCtx->enmMethod == BS3EXTCTXMETHOD_XSAVE) + { + pExtCtx->Ctx.x87.MXCSR_MASK = uValue; + return true; + } + return false; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetXmm.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetXmm.c new file mode 100644 index 00000000..cdc864c6 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetXmm.c @@ -0,0 +1,61 @@ +/* $Id: bs3-cmn-ExtCtxSetXmm.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxSetXmm + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3ExtCtxSetXmm +BS3_CMN_DEF(bool, Bs3ExtCtxSetXmm,(PBS3EXTCTX pExtCtx, uint8_t iReg, PCRTUINT128U pValue)) +{ + AssertCompileMembersAtSameOffset(BS3EXTCTX, Ctx.x87.aXMM, BS3EXTCTX, Ctx.x.x87.aXMM); + switch (pExtCtx->enmMethod) + { + case BS3EXTCTXMETHOD_FXSAVE: + case BS3EXTCTXMETHOD_XSAVE: + if (iReg < RT_ELEMENTS(pExtCtx->Ctx.x87.aXMM)) + { + pExtCtx->Ctx.x87.aXMM[iReg].xmm = pValue->u; + return true; + } + break; + } + return false; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetYmm.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetYmm.c new file mode 100644 index 00000000..df87cd1c --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-ExtCtxSetYmm.c @@ -0,0 +1,79 @@ +/* $Id: bs3-cmn-ExtCtxSetYmm.c $ */ +/** @file + * BS3Kit - Bs3ExtCtxSetYmm + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3ExtCtxSetYmm +BS3_CMN_DEF(bool, Bs3ExtCtxSetYmm,(PBS3EXTCTX pExtCtx, uint8_t iReg, PCRTUINT256U pValue, uint8_t cbValue)) +{ + BS3_ASSERT(cbValue == 16 || cbValue == 32); + switch (pExtCtx->enmMethod) + { + case BS3EXTCTXMETHOD_FXSAVE: + if (iReg < RT_ELEMENTS(pExtCtx->Ctx.x87.aXMM)) + { + pExtCtx->Ctx.x87.aXMM[iReg].uXmm = pValue->DQWords.dqw0; + return true; + } + break; + + case BS3EXTCTXMETHOD_XSAVE: + if (iReg < RT_ELEMENTS(pExtCtx->Ctx.x.x87.aXMM)) + { + pExtCtx->Ctx.x87.aXMM[iReg].uXmm = pValue->DQWords.dqw0; + if (pExtCtx->fXcr0Nominal & XSAVE_C_YMM) + { + if (cbValue >= 32) + pExtCtx->Ctx.x.u.YmmHi.aYmmHi[iReg].uXmm = pValue->DQWords.dqw1; + else + { + pExtCtx->Ctx.x.u.YmmHi.aYmmHi[iReg].au64[0] = 0; + pExtCtx->Ctx.x.u.YmmHi.aYmmHi[iReg].au64[1] = 0; + } + /** @todo zero high ZMM part. */ + } + return true; + } + break; + } + return false; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-GetCpuVendor.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-GetCpuVendor.c new file mode 100644 index 00000000..de9d14ef --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-GetCpuVendor.c @@ -0,0 +1,63 @@ +/* $Id: bs3-cmn-GetCpuVendor.c $ */ +/** @file + * BS3Kit - Bs3GetCpuVendor + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "bs3kit-template-header.h" + +#include <iprt/asm-amd64-x86.h> + + +#undef Bs3GetCpuVendor +BS3_CMN_DEF(BS3CPUVENDOR, Bs3GetCpuVendor,(void)) +{ + if (g_uBs3CpuDetected & BS3CPU_F_CPUID) + { + uint32_t uEbx, uEcx, uEdx; + ASMCpuIdExSlow(0, 0, 0, 0, NULL, &uEbx, &uEcx, &uEdx); + if (RTX86IsIntelCpu(uEbx, uEcx, uEdx)) + return BS3CPUVENDOR_INTEL; + if (RTX86IsAmdCpu(uEbx, uEcx, uEdx)) + return BS3CPUVENDOR_AMD; + if (RTX86IsViaCentaurCpu(uEbx, uEcx, uEdx)) + return BS3CPUVENDOR_VIA; + if (RTX86IsShanghaiCpu(uEbx, uEcx, uEdx)) + return BS3CPUVENDOR_SHANGHAI; + if (RTX86IsHygonCpu(uEbx, uEcx, uEdx)) + return BS3CPUVENDOR_HYGON; + return BS3CPUVENDOR_UNKNOWN; + } + return BS3CPUVENDOR_INTEL; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-GetModeName.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-GetModeName.c new file mode 100644 index 00000000..fa521869 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-GetModeName.c @@ -0,0 +1,72 @@ +/* $Id: bs3-cmn-GetModeName.c $ */ +/** @file + * BS3Kit - Bs3GetModeName + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "bs3kit-template-header.h" + + + +#undef Bs3GetModeName +BS3_CMN_DEF(const char BS3_FAR *, Bs3GetModeName,(uint8_t bMode)) +{ + switch (bMode) + { + case BS3_MODE_RM: return g_szBs3ModeName_rm; + case BS3_MODE_PE16: return g_szBs3ModeName_pe16; + case BS3_MODE_PE16_32: return g_szBs3ModeName_pe16_32; + case BS3_MODE_PE16_V86: return g_szBs3ModeName_pe16_v86; + case BS3_MODE_PE32: return g_szBs3ModeName_pe32; + case BS3_MODE_PE32_16: return g_szBs3ModeName_pe32_16; + case BS3_MODE_PEV86: return g_szBs3ModeName_pev86; + case BS3_MODE_PP16: return g_szBs3ModeName_pp16; + case BS3_MODE_PP16_32: return g_szBs3ModeName_pp16_32; + case BS3_MODE_PP16_V86: return g_szBs3ModeName_pp16_v86; + case BS3_MODE_PP32: return g_szBs3ModeName_pp32; + case BS3_MODE_PP32_16: return g_szBs3ModeName_pp32_16; + case BS3_MODE_PPV86: return g_szBs3ModeName_ppv86; + case BS3_MODE_PAE16: return g_szBs3ModeName_pae16; + case BS3_MODE_PAE16_32: return g_szBs3ModeName_pae16_32; + case BS3_MODE_PAE16_V86: return g_szBs3ModeName_pae16_v86; + case BS3_MODE_PAE32: return g_szBs3ModeName_pae32; + case BS3_MODE_PAE32_16: return g_szBs3ModeName_pae32_16; + case BS3_MODE_PAEV86: return g_szBs3ModeName_paev86; + case BS3_MODE_LM16: return g_szBs3ModeName_lm16; + case BS3_MODE_LM32: return g_szBs3ModeName_lm32; + case BS3_MODE_LM64: return g_szBs3ModeName_lm64; + case BS3_MODE_INVALID: return "invalid"; + default: return "unknow"; + } +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-GetModeNameShortLower.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-GetModeNameShortLower.c new file mode 100644 index 00000000..2a158056 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-GetModeNameShortLower.c @@ -0,0 +1,72 @@ +/* $Id: bs3-cmn-GetModeNameShortLower.c $ */ +/** @file + * BS3Kit - Bs3GetModeNameShortLower + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "bs3kit-template-header.h" + + + +#undef Bs3GetModeNameShortLower +BS3_CMN_DEF(const char BS3_FAR *, Bs3GetModeNameShortLower,(uint8_t bMode)) +{ + switch (bMode) + { + case BS3_MODE_RM: return g_szBs3ModeNameShortLower_rm; + case BS3_MODE_PE16: return g_szBs3ModeNameShortLower_pe16; + case BS3_MODE_PE16_32: return g_szBs3ModeNameShortLower_pe16_32; + case BS3_MODE_PE16_V86: return g_szBs3ModeNameShortLower_pe16_v86; + case BS3_MODE_PE32: return g_szBs3ModeNameShortLower_pe32; + case BS3_MODE_PE32_16: return g_szBs3ModeNameShortLower_pe32_16; + case BS3_MODE_PEV86: return g_szBs3ModeNameShortLower_pev86; + case BS3_MODE_PP16: return g_szBs3ModeNameShortLower_pp16; + case BS3_MODE_PP16_32: return g_szBs3ModeNameShortLower_pp16_32; + case BS3_MODE_PP16_V86: return g_szBs3ModeNameShortLower_pp16_v86; + case BS3_MODE_PP32: return g_szBs3ModeNameShortLower_pp32; + case BS3_MODE_PP32_16: return g_szBs3ModeNameShortLower_pp32_16; + case BS3_MODE_PPV86: return g_szBs3ModeNameShortLower_ppv86; + case BS3_MODE_PAE16: return g_szBs3ModeNameShortLower_pae16; + case BS3_MODE_PAE16_32: return g_szBs3ModeNameShortLower_pae16_32; + case BS3_MODE_PAE16_V86: return g_szBs3ModeNameShortLower_pae16_v86; + case BS3_MODE_PAE32: return g_szBs3ModeNameShortLower_pae32; + case BS3_MODE_PAE32_16: return g_szBs3ModeNameShortLower_pae32_16; + case BS3_MODE_PAEV86: return g_szBs3ModeNameShortLower_paev86; + case BS3_MODE_LM16: return g_szBs3ModeNameShortLower_lm16; + case BS3_MODE_LM32: return g_szBs3ModeNameShortLower_lm32; + case BS3_MODE_LM64: return g_szBs3ModeNameShortLower_lm64; + case BS3_MODE_INVALID: return "inv"; + default: return "unk"; + } +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-KbdRead.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-KbdRead.asm new file mode 100644 index 00000000..07722c77 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-KbdRead.asm @@ -0,0 +1,75 @@ +; $Id: bs3-cmn-KbdRead.asm $ +;; @file +; BS3Kit - Bs3KbdRead. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Sends a read command to the keyboard controller and gets the result. +; +; The caller is responsible for making sure the keyboard controller is ready +; for a command (call Bs3KbdWait if unsure). +; +; @returns The value read is returned (in al). +; @param bCmd The read command. +; @uses al (obviously) +; +; @cproto BS3_DECL(uint8_t) Bs3KbdRead_c16(uint8_t bCmd); +; +BS3_PROC_BEGIN_CMN Bs3KbdRead, BS3_PBC_NEAR + push xBP + mov xBP, xSP + + mov al, [xBP + xCB*2] + out 64h, al ; Write the command. + +.check_status: + in al, 64h + test al, 1 ; KBD_STAT_OBF + jz .check_status + + in al, 60h ; Read the data. + + pop xBP + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3KbdRead + +; +; We may be using the near code in some critical code paths, so don't +; penalize it. +; +BS3_CMN_FAR_STUB Bs3KbdRead, 2 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-KbdWait.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-KbdWait.asm new file mode 100644 index 00000000..4018c1bb --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-KbdWait.asm @@ -0,0 +1,64 @@ +; $Id: bs3-cmn-KbdWait.asm $ +;; @file +; BS3Kit - Bs3KbdWait. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +;; +; Waits for the keyboard controller to become ready. +; +; @cproto BS3_DECL(void) Bs3KbdWait_c16(void); +; +BS3_PROC_BEGIN_CMN Bs3KbdWait, BS3_PBC_HYBRID_0_ARGS + push xBP + mov xBP, xSP + push xAX + +.check_status: + in al, 64h + test al, 1 ; KBD_STAT_OBF + jnz .read_data_and_status + test al, 2 ; KBD_STAT_IBF + jnz .check_status + + pop xAX + pop xBP + BS3_HYBRID_RET + +.read_data_and_status: + in al, 60h + jmp .check_status +BS3_PROC_END_CMN Bs3KbdWait + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-KbdWrite.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-KbdWrite.asm new file mode 100644 index 00000000..ab9752fa --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-KbdWrite.asm @@ -0,0 +1,82 @@ +; $Id: bs3-cmn-KbdWrite.asm $ +;; @file +; BS3Kit - Bs3KbdRead. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3KbdWait + + +;; +; Sends a write command to the keyboard controller and then sends the data. +; +; The caller is responsible for making sure the keyboard controller is ready +; for a command (call Bs3KbdWait if unsure). +; +; @param bCmd The write command. +; @param bData The data to write. +; @uses Nothing. +; +; @todo Return status? +; +; @cproto BS3_DECL(void) Bs3KbdWait_c16(uint8_t bCmd, uint8_t bData); +; +BS3_PROC_BEGIN_CMN Bs3KbdWrite, BS3_PBC_NEAR + push xBP + mov xBP, xSP + push xAX +BONLY64 sub rsp, 20h + + mov al, [xBP + xCB*2] + out 64h, al ; Write the command. + call Bs3KbdWait + + mov al, [xBP + xCB*3] + out 60h, al ; Write the data + call Bs3KbdWait + +BONLY64 add rsp, 20h + pop xAX + pop xBP + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3KbdWrite + +; +; We may be using the near code in some critical code paths, so don't +; penalize it. +; +BS3_CMN_FAR_STUB Bs3KbdWrite, 4 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemAlloc.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemAlloc.c new file mode 100644 index 00000000..6d24784b --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemAlloc.c @@ -0,0 +1,111 @@ +/* $Id: bs3-cmn-MemAlloc.c $ */ +/** @file + * BS3Kit - Bs3MemAlloc + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-memory.h" +#include <iprt/asm.h> + + +#undef Bs3MemAlloc +BS3_CMN_DEF(void BS3_FAR *, Bs3MemAlloc,(BS3MEMKIND enmKind, size_t cb)) +{ + void BS3_FAR *pvRet; + uint8_t idxSlabList; + +#if ARCH_BITS == 16 + /* Don't try allocate memory which address we cannot return. */ + if ( enmKind != BS3MEMKIND_REAL + && BS3_MODE_IS_RM_OR_V86(g_bBs3CurrentMode)) + enmKind = BS3MEMKIND_REAL; +#endif + + idxSlabList = bs3MemSizeToSlabListIndex(cb); + if (idxSlabList < BS3_MEM_SLAB_LIST_COUNT) + { + /* + * Try allocate a chunk from the list. + */ + PBS3SLABHEAD pHead = enmKind == BS3MEMKIND_REAL + ? &g_aBs3LowSlabLists[idxSlabList] + : &g_aBs3UpperTiledSlabLists[idxSlabList]; + + BS3_ASSERT(g_aBs3LowSlabLists[idxSlabList].cbChunk >= cb); + pvRet = Bs3SlabListAlloc(pHead); + if (pvRet) + { /* likely */ } + else + { + /* + * Grow the list. + */ + PBS3SLABCTL pNew = (PBS3SLABCTL)Bs3SlabAlloc( enmKind == BS3MEMKIND_REAL + ? &g_Bs3Mem4KLow.Core + : &g_Bs3Mem4KUpperTiled.Core); + BS3_ASSERT(((uintptr_t)pNew & 0xfff) == 0); + if (pNew) + { + uint16_t const cbHdr = g_cbBs3SlabCtlSizesforLists[idxSlabList]; + BS3_XPTR_AUTO(void, pvNew); + BS3_XPTR_SET(void, pvNew, pNew); + + Bs3SlabInit(pNew, cbHdr, BS3_XPTR_GET_FLAT(void, pvNew) + cbHdr, _4K - cbHdr, pHead->cbChunk); + Bs3SlabListAdd(pHead, pNew); + + pvRet = Bs3SlabListAlloc(pHead); + } + } + } + else + { + /* + * Allocate one or more pages. + */ + size_t const cbAligned = RT_ALIGN_Z(cb, _4K); + uint16_t const cPages = cbAligned >> 12 /* div _4K */; + PBS3SLABCTL pSlabCtl = enmKind == BS3MEMKIND_REAL + ? &g_Bs3Mem4KLow.Core : &g_Bs3Mem4KUpperTiled.Core; + + pvRet = Bs3SlabAllocEx(pSlabCtl, + cPages, + cPages <= _64K / _4K ? BS3_SLAB_ALLOC_F_SAME_TILE : 0); + } + return pvRet; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemAllocZ.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemAllocZ.c new file mode 100644 index 00000000..104142e7 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemAllocZ.c @@ -0,0 +1,53 @@ +/* $Id: bs3-cmn-MemAllocZ.c $ */ +/** @file + * BS3Kit - Bs3MemAllocZ + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-memory.h" + + +#undef Bs3MemAllocZ +BS3_CMN_DEF(void BS3_FAR *, Bs3MemAllocZ,(BS3MEMKIND enmKind, size_t cb)) +{ + void BS3_FAR *pvRet = Bs3MemAlloc(enmKind, cb); + if (pvRet) + Bs3MemZero(pvRet, cb); + return pvRet; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemChr.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemChr.asm new file mode 100644 index 00000000..b5234d7a --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemChr.asm @@ -0,0 +1,88 @@ +; $Id: bs3-cmn-MemChr.asm $ +;; @file +; BS3Kit - Bs3MemChr. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +;; +; @cproto BS3_CMN_PROTO_NOSB(void BS3_FAR *, Bs3MemChr,(void BS3_FAR const *pvHaystack, uint8_t bNeedle, size_t cbHaystack)); +; +BS3_PROC_BEGIN_CMN Bs3MemChr, BS3_PBC_HYBRID + push xBP + mov xBP, xSP + push xDI +TONLY16 push es + +%if TMPL_BITS == 64 + + mov rdi, rcx ; rdi = pvHaystack + mov rcx, r8 ; rcx = cbHaystack + mov al, dl ; bNeedle + mov rcx, r8 + +%elif TMPL_BITS == 16 + mov di, [bp + 2 + cbCurRetAddr] ; pvHaystack.off + mov es, [bp + 2 + cbCurRetAddr + 2] ; pvHaystack.sel + mov al, [bp + 2 + cbCurRetAddr + 4] ; bNeedle + mov cx, [bp + 2 + cbCurRetAddr + 6] ; cbHaystack + +%elif TMPL_BITS == 32 + mov edi, [ebp + 8] ; pvHaystack + mov al, byte [ebp + 4 + cbCurRetAddr + 4] ; bNeedle + mov ecx, [ebp + 4 + cbCurRetAddr + 8] ; cbHaystack +%else + %error "TMPL_BITS!" +%endif + + cld + repne scasb + je .found + + xor xAX, xAX +TONLY16 xor dx, dx + +.return: +TONLY16 pop es + pop xDI + pop xBP + BS3_HYBRID_RET + +.found: + lea xAX, [xDI - 1] +TONLY16 mov dx, es + jmp .return + +BS3_PROC_END_CMN Bs3MemChr + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemCmp.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemCmp.asm new file mode 100644 index 00000000..0aa4af7b --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemCmp.asm @@ -0,0 +1,99 @@ +; $Id: bs3-cmn-MemCmp.asm $ +;; @file +; BS3Kit - Bs3MemCmp. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +;; +; @cproto BS3_CMN_PROTO_NOSB(int, Bs3MemCmp,(void const BS3_FAR *pv1, void const BS3_FAR *pv2, size_t cb)); +; +BS3_PROC_BEGIN_CMN Bs3MemCmp, BS3_PBC_HYBRID +TONLY16 CPU 8086 + push xBP + mov xBP, xSP + push xDI + push xSI +TNOT64 push es +TONLY16 push ds + cld + + ; + ; To save complexity and space, do straight forward byte compares. + ; +%if TMPL_BITS == 16 + mov di, [bp + 2 + cbCurRetAddr] ; pv1.off + mov es, [bp + 2 + cbCurRetAddr + 2] ; pv1.sel + mov si, [bp + 2 + cbCurRetAddr + 4] ; pv2.off + mov ds, [bp + 2 + cbCurRetAddr + 6] ; pv2.sel + mov cx, [bp + 2 + cbCurRetAddr + 8] ; cbDst + xor ax, ax + repe cmpsb + je .return + + mov al, [es:di - 1] + xor dx, dx + mov dl, [esi - 1] + sub ax, dx + +%else + %if TMPL_BITS == 64 + mov rdi, rcx ; rdi = pv1 + mov rsi, rdx ; rdi = pv2 + mov rcx, r8 ; rcx = cbDst + %else + mov ax, ds + mov es, ax ; paranoia + mov edi, [ebp + 4 + cbCurRetAddr] ; pv1 + mov esi, [ebp + 4 + cbCurRetAddr + 4] ; pv2 + mov ecx, [ebp + 4 + cbCurRetAddr + 8] ; cbDst + %endif + xor eax, eax + repe cmpsb + je .return + + mov al, [xDI - 1] + movzx edx, byte [xSI - 1] + sub eax, edx +%endif + +.return: +TONLY16 pop ds +TNOT64 pop es + pop xSI + pop xDI + pop xBP + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3MemCmp + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemCpy.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemCpy.c new file mode 100644 index 00000000..0c1a5ca8 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemCpy.c @@ -0,0 +1,85 @@ +/* $Id: bs3-cmn-MemCpy.c $ */ +/** @file + * BS3Kit - Bs3MemCpy + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "bs3kit-template-header.h" + +#undef Bs3MemCpy +BS3_CMN_DEF(void BS3_FAR *, Bs3MemCpy,(void BS3_FAR *pvDst, const void BS3_FAR *pvSrc, size_t cbToCopy)) +{ +#if 1 + const size_t BS3_FAR *pBigSrc = (const size_t BS3_FAR *)pvSrc; + size_t BS3_FAR *pBigDst = (size_t *)pvDst; + size_t cBig = cbToCopy / sizeof(size_t); + while (cBig-- > 0) + *pBigDst++ = *pBigSrc++; + + switch (cbToCopy % sizeof(size_t)) + { +#if TMPL_BITS >= 64 + case 7: ((uint8_t BS3_FAR *)pBigDst)[6] = ((const uint8_t BS3_FAR *)pBigSrc)[6]; + case 6: ((uint8_t BS3_FAR *)pBigDst)[5] = ((const uint8_t BS3_FAR *)pBigSrc)[5]; + case 5: ((uint8_t BS3_FAR *)pBigDst)[4] = ((const uint8_t BS3_FAR *)pBigSrc)[4]; + case 4: ((uint8_t BS3_FAR *)pBigDst)[3] = ((const uint8_t BS3_FAR *)pBigSrc)[3]; +#endif +#if TMPL_BITS >= 32 + case 3: ((uint8_t BS3_FAR *)pBigDst)[2] = ((const uint8_t BS3_FAR *)pBigSrc)[2]; + case 2: ((uint8_t BS3_FAR *)pBigDst)[1] = ((const uint8_t BS3_FAR *)pBigSrc)[1]; +#endif + case 1: ((uint8_t BS3_FAR *)pBigDst)[0] = ((const uint8_t BS3_FAR *)pBigSrc)[0]; + case 0: + break; + } + +#else + size_t cLargeRounds; + BS3CPTRUNION uSrc; + BS3PTRUNION uDst; + uSrc.pv = pvSrc; + uDst.pv = pvDst; + + cLargeRounds = cbToCopy / sizeof(*uSrc.pcb); + while (cLargeRounds-- > 0) + *uDst.pcb++ = *uSrc.pcb++; + + cbToCopy %= sizeof(*uSrc.pcb); + while (cbToCopy-- > 0) + *uDst.pb++ = *uSrc.pb++; + +#endif + + return pvDst; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemFree.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemFree.c new file mode 100644 index 00000000..51c48389 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemFree.c @@ -0,0 +1,73 @@ +/* $Id: bs3-cmn-MemFree.c $ */ +/** @file + * BS3Kit - Bs3MemFree + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-memory.h" + + +#undef Bs3MemFree +BS3_CMN_DEF(void, Bs3MemFree,(void BS3_FAR *pv, size_t cb)) +{ + if (pv != NULL) + { + uint16_t cChunks; + PBS3SLABCTL pCtl; + BS3_XPTR_AUTO(void, pvFlat); + BS3_XPTR_SET(void, pvFlat, pv); + + if (BS3_XPTR_GET_FLAT(void, pvFlat) & 0xfffU) + { + /* Use an XPTR here in case we're in real mode and the caller has + messed around with the pointer. */ + BS3_XPTR_AUTO(BS3SLABCTL, pTmp); + BS3_XPTR_SET_FLAT(BS3SLABCTL, pTmp, BS3_XPTR_GET_FLAT(void, pvFlat) & ~(uint32_t)0xfff); + pCtl = BS3_XPTR_GET(BS3SLABCTL, pTmp); + BS3_ASSERT(pCtl->cbChunk >= cb); + cChunks = 1; + } + else + { + pCtl = BS3_XPTR_GET_FLAT(void, pvFlat) < _1M ? &g_Bs3Mem4KLow.Core : &g_Bs3Mem4KUpperTiled.Core; + cChunks = RT_ALIGN_Z(cb, _4K) >> 12; + } + Bs3SlabFree(pCtl, BS3_XPTR_GET_FLAT(void, pvFlat), cChunks); + } +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemGuardedTestPage.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemGuardedTestPage.c new file mode 100644 index 00000000..e4a76434 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemGuardedTestPage.c @@ -0,0 +1,109 @@ +/* $Id: bs3-cmn-MemGuardedTestPage.c $ */ +/** @file + * BS3Kit - Bs3MemGuardedTestPageAlloc, Bs3MemGuardedTestPageFree + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "iprt/asm.h" + + +#undef Bs3MemGuardedTestPageAllocEx +BS3_CMN_PROTO_STUB(void BS3_FAR *, Bs3MemGuardedTestPageAllocEx,(BS3MEMKIND enmKind, uint64_t fPte)) +{ + uint8_t BS3_FAR *pb = (uint8_t BS3_FAR *)Bs3MemAlloc(enmKind, X86_PAGE_4K_SIZE * 3); + if (pb) + { + int rc; + + Bs3MemSet(pb, 0xcc, X86_PAGE_4K_SIZE); + Bs3MemSet(&pb[X86_PAGE_4K_SIZE], 0x00, X86_PAGE_4K_SIZE); + Bs3MemSet(&pb[X86_PAGE_4K_SIZE*2], 0xaa, X86_PAGE_4K_SIZE); + + rc = Bs3PagingProtectPtr(pb, X86_PAGE_4K_SIZE, fPte, UINT64_MAX & ~fPte); + if (RT_SUCCESS(rc)) + { + rc = Bs3PagingProtectPtr(&pb[X86_PAGE_4K_SIZE*2], X86_PAGE_4K_SIZE, fPte, UINT64_MAX & ~fPte); + if (RT_SUCCESS(rc)) + return pb + X86_PAGE_4K_SIZE; + + Bs3TestPrintf("warning: Bs3MemGuardedTestPageAlloc - Tail protect error %d (mode %#x)\n", rc, g_bBs3CurrentMode); + Bs3PagingProtectPtr(pb, X86_PAGE_4K_SIZE, X86_PTE_P, 0); + } + else + Bs3TestPrintf("warning: Bs3MemGuardedTestPageAlloc - Head protect error %d (mode %#x)\n", rc, g_bBs3CurrentMode); + Bs3MemFree(pb, X86_PAGE_4K_SIZE * 3); + } + else + Bs3TestPrintf("warning: Bs3MemGuardedTestPageAlloc - out of memory (mode %#x)\n", g_bBs3CurrentMode); + return NULL; +} + + +#undef Bs3MemGuardedTestPageAlloc +BS3_CMN_DEF(void BS3_FAR *, Bs3MemGuardedTestPageAlloc,(BS3MEMKIND enmKind)) +{ + return BS3_CMN_FAR_NM(Bs3MemGuardedTestPageAllocEx)(enmKind, 0); +} + + +#undef Bs3MemGuardedTestPageFree +BS3_CMN_DEF(void, Bs3MemGuardedTestPageFree,(void BS3_FAR *pvGuardedPage)) +{ + if (pvGuardedPage) + { + uint8_t BS3_FAR *pbGuardViolation; + uint8_t BS3_FAR *pb = (uint8_t BS3_FAR *)pvGuardedPage - X86_PAGE_4K_SIZE; + Bs3PagingProtectPtr(pb, X86_PAGE_4K_SIZE, + X86_PTE_P | X86_PTE_RW | X86_PTE_US | X86_PTE_A | X86_PTE_D, UINT64_MAX); + Bs3PagingProtectPtr(&pb[X86_PAGE_4K_SIZE*2], X86_PAGE_4K_SIZE, + X86_PTE_P | X86_PTE_RW | X86_PTE_US | X86_PTE_A | X86_PTE_D, UINT64_MAX); + + pbGuardViolation = ASMMemFirstMismatchingU8(pb, X86_PAGE_4K_SIZE, 0xcc); + if (pbGuardViolation) + Bs3TestFailedF("Leading guard page touched: byte %#05x is %#04x instead of 0xcc\n", + (unsigned)(uintptr_t)(pbGuardViolation - pb), *pbGuardViolation); + + pbGuardViolation = ASMMemFirstMismatchingU8(&pb[X86_PAGE_4K_SIZE*2], X86_PAGE_4K_SIZE, 0xaa); + if (pbGuardViolation) + Bs3TestFailedF("Trailing guard page touched: byte %#05x is %#04x instead of 0xaa\n", + (unsigned)(uintptr_t)(pbGuardViolation - &pb[X86_PAGE_4K_SIZE*2]), *pbGuardViolation); + + Bs3MemFree(pb, X86_PAGE_4K_SIZE * 3); + } +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemMove.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemMove.c new file mode 100644 index 00000000..0ecdf695 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemMove.c @@ -0,0 +1,88 @@ +/* $Id: bs3-cmn-MemMove.c $ */ +/** @file + * BS3Kit - Bs3MemMove + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "bs3kit-template-header.h" + +#undef Bs3MemMove +BS3_CMN_DEF(void BS3_FAR *, Bs3MemMove,(void BS3_FAR *pvDst, const void BS3_FAR *pvSrc, size_t cbToCopy)) +{ + size_t cLargeRounds; + BS3CVPTRUNION uSrc; + BS3PTRUNION uDst; + uSrc.pv = pvSrc; + uDst.pv = pvDst; + + /* We don't care about segment wrapping here. */ + if ((uintptr_t)pvDst > (uintptr_t)pvSrc + cbToCopy) + { + /* Reverse copy. */ + uSrc.pb += cbToCopy; + uDst.pb += cbToCopy; + + cLargeRounds = cbToCopy / sizeof(*uSrc.pcb); + while (cLargeRounds-- > 0) + { + size_t uTmp = *--uSrc.pcb; + *--uDst.pcb = uTmp; + } + + cbToCopy %= sizeof(*uSrc.pcb); + while (cbToCopy-- > 0) + { + uint8_t b = *--uSrc.pb; + *--uDst.pb = b; + } + } + else + { + /* Forward copy. */ + cLargeRounds = cbToCopy / sizeof(*uSrc.pcb); + while (cLargeRounds-- > 0) + { + size_t uTmp = *uSrc.pcb++; + *uDst.pcb++ = uTmp; + } + + cbToCopy %= sizeof(*uSrc.pcb); + while (cbToCopy-- > 0) + { + uint8_t b = *uSrc.pb++; + *uDst.pb++ = b; + } + } + return pvDst; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemPCpy.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemPCpy.c new file mode 100644 index 00000000..b7fad002 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemPCpy.c @@ -0,0 +1,58 @@ +/* $Id: bs3-cmn-MemPCpy.c $ */ +/** @file + * BS3Kit - Bs3MemPCpy + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "bs3kit-template-header.h" + +#undef Bs3MemPCpy +BS3_CMN_DEF(void BS3_FAR *, Bs3MemPCpy,(void BS3_FAR *pvDst, const void BS3_FAR *pvSrc, size_t cbToCopy)) +{ + size_t cLargeRounds; + BS3CPTRUNION uSrc; + BS3PTRUNION uDst; + uSrc.pv = pvSrc; + uDst.pv = pvDst; + + cLargeRounds = cbToCopy / sizeof(*uSrc.pcb); + while (cLargeRounds-- > 0) + *uDst.pcb++ = *uSrc.pcb++; + + cbToCopy %= sizeof(*uSrc.pcb); + while (cbToCopy-- > 0) + *uDst.pb++ = *uSrc.pb++; + + return uDst.pv; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemPrintInfo.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemPrintInfo.c new file mode 100644 index 00000000..b1c65b86 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemPrintInfo.c @@ -0,0 +1,95 @@ +/* $Id: bs3-cmn-MemPrintInfo.c $ */ +/** @file + * BS3Kit - Bs3MemPrintInfo + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-memory.h" +#include <iprt/asm.h> + + +/** + * Prints a slab control structure with allocation map. + * + * @param pCtl The slab control structure to print. + * @param pszPrefix The output prefix. + */ +static void Bs3MemPrintInfoSlabCtl(PBS3SLABCTL pCtl, const char BS3_FAR *pszPrefix) +{ + unsigned iChunk; + Bs3TestPrintf("%s / %#06x: %u of %u chunks free", pszPrefix, pCtl->cbChunk, pCtl->cFreeChunks, pCtl->cChunks); + for (iChunk = 0; iChunk < pCtl->cChunks; iChunk++) + { + if ((iChunk & 63) == 0) + Bs3TestPrintf("\n%s:", pszPrefix); + if (ASMBitTest(pCtl->bmAllocated, iChunk)) + Bs3TestPrintf((iChunk & 7) != 0 ? "x" : " x"); + else + Bs3TestPrintf((iChunk & 7) != 0 ? "-" : " -"); + } + Bs3TestPrintf("\n"); +} + + + +/** + * Prints a summary of a slab allocation list (i.e. the heap). + * + * @param paLists Array of BS3_MEM_SLAB_LIST_COUNT lists. + * @param pszPrefix The output prefix. + */ +static void Bs3MemPrintInfoSlabList(PBS3SLABHEAD paLists, const char BS3_FAR *pszPrefix) +{ + unsigned iSlab; + for (iSlab = 0; iSlab < BS3_MEM_SLAB_LIST_COUNT; iSlab++) + if (paLists[iSlab].cSlabs) + Bs3TestPrintf("%s / %#06x: %u slabs, %RU32 of %RU32 chunks free\n", + pszPrefix, paLists[iSlab].cbChunk, paLists[iSlab].cSlabs, + paLists[iSlab].cFreeChunks, paLists[iSlab].cChunks); +} + + +#undef Bs3MemPrintInfo +BS3_CMN_DEF(void, Bs3MemPrintInfo,(void)) +{ + Bs3MemPrintInfoSlabList(g_aBs3LowSlabLists, "Lower"); + Bs3MemPrintInfoSlabList(g_aBs3LowSlabLists, "Upper"); + Bs3MemPrintInfoSlabCtl(&g_Bs3Mem4KLow.Core, "4KLow"); + Bs3MemPrintInfoSlabCtl(&g_Bs3Mem4KUpperTiled.Core, "Tiled"); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemSet.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemSet.asm new file mode 100644 index 00000000..ecfc790b --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemSet.asm @@ -0,0 +1,102 @@ +; $Id: bs3-cmn-MemSet.asm $ +;; @file +; BS3Kit - Bs3MemSet. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +;; +; @cproto BS3_CMN_PROTO_NOSB(void, Bs3MemSet,(void BS3_FAR *pvDst, uint8_t bFiller, size_t cbDst)); +; +BS3_PROC_BEGIN_CMN Bs3MemSet, BS3_PBC_HYBRID + push xBP + mov xBP, xSP + push xDI +%ifdef RT_ARCH_AMD64 + + mov rdi, rcx ; rdi = pvDst + mov rcx, r8 ; rcx = cbDst + movzx edx, dl ; bFiller + mov rax, 0101010101010101h + mul rdx + mov rcx, r8 + shr rcx, 3 ; calc qword count. + cld + rep stosq + + mov rcx, r8 ; cbDst + and rcx, 7 ; calc trailing byte count. + rep stosb + +%elif ARCH_BITS == 16 + push es + + mov di, [bp + 2 + cbCurRetAddr] ; pvDst.off + mov es, [bp + 2 + cbCurRetAddr + 2] ; pvDst.sel + mov al, [bp + 2 + cbCurRetAddr + 4] ; bFiller + mov ah, al + mov cx, [bp + 2 + cbCurRetAddr + 6] ; cbDst + shr cx, 1 ; calc dword count. + rep stosw + + mov cx, [bp + 2 + cbCurRetAddr + 6] ; cbDst + and cx, 1 ; calc tailing byte count. + rep stosb + + pop es + +%elif ARCH_BITS == 32 + mov edi, [ebp + 8] ; pvDst + mov al, byte [ebp + 4 + cbCurRetAddr + 4] ; bFiller + mov ah, al + mov dx, ax + shl eax, 16 + mov ax, dx ; eax = RT_MAKE_U32_FROM_U8(bFiller, bFiller, bFiller, bFiller) + mov ecx, [ebp + 4 + cbCurRetAddr + 8] ; cbDst + shr cx, 2 ; calc dword count. + rep stosd + + mov ecx, [ebp + 4 + cbCurRetAddr + 8] ; cbDst + and ecx, 3 ; calc tailing byte count. + rep stosb + +%else + %error "Unknown bitness." +%endif + + pop xDI + pop xBP + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3MemSet + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemZero.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemZero.asm new file mode 100644 index 00000000..c5923c12 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-MemZero.asm @@ -0,0 +1,103 @@ +; $Id: bs3-cmn-MemZero.asm $ +;; @file +; BS3Kit - Bs3MemZero. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +;; +; @cproto BS3_DECL(void) Bs3MemZero_c16(void BS3_FAR *pvDst, size_t cbDst); +; +BS3_PROC_BEGIN_CMN Bs3MemZero, BS3_PBC_HYBRID +%ifdef RT_ARCH_AMD64 + push rdi + + mov rdi, rcx ; rdi = pvDst + mov rcx, rdx ; rcx = cbDst + shr rcx, 3 ; calc qword count. + xor eax, eax ; rax = 0 (filler qword) + cld + rep stosq + + mov rcx, rdx ; cbDst + and rcx, 7 ; calc trailing byte count. + rep stosb + + pop rdi + BS3_HYBRID_RET + +%elif ARCH_BITS == 16 + push bp + mov bp, sp + push di + push es + + mov di, [bp + 2 + cbCurRetAddr] ; pvDst.off + mov dx, [bp + 2 + cbCurRetAddr + 2] ; pvDst.sel + mov es, dx + mov cx, [bp + 2 + cbCurRetAddr + 4] ; cbDst + shr cx, 1 ; calc dword count. + xor ax, ax + rep stosw + + mov cx, [bp + 2 + cbCurRetAddr + 4] ; cbDst + and cx, 1 ; calc tailing byte count. + rep stosb + + pop es + pop di + pop bp + BS3_HYBRID_RET + +%elif ARCH_BITS == 32 + push edi + + mov edi, [esp + 8] ; pvDst + mov ecx, [esp + 8 + 4] ; cbDst + shr cx, 2 ; calc dword count. + xor eax, eax + rep stosd + + mov ecx, [esp + 8 + 4] ; cbDst + and ecx, 3 ; calc tailing byte count. + rep stosb + + pop edi + BS3_HYBRID_RET + +%else + %error "Unknown bitness." +%endif +BS3_PROC_END_CMN Bs3MemZero + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingAlias.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingAlias.c new file mode 100644 index 00000000..bed85ab4 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingAlias.c @@ -0,0 +1,193 @@ +/* $Id: bs3-cmn-PagingAlias.c $ */ +/** @file + * BS3Kit - Bs3PagingAlias, Bs3PagingUnalias + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-paging.h" +#include "iprt/asm-amd64-x86.h" + + +#undef Bs3PagingAlias +BS3_CMN_DEF(int, Bs3PagingAlias,(uint64_t uDst, uint64_t uPhysToAlias, uint32_t cbHowMuch, uint64_t fPte)) +{ +#if ARCH_BITS == 16 + if (!BS3_MODE_IS_V86(g_bBs3CurrentMode)) +#endif + { + RTCCUINTXREG cr3 = ASMGetCR3(); + uint32_t cPages; + int rc; + + /* + * Validate and adjust the input a little. + */ + if (uDst & X86_PAGE_OFFSET_MASK) + { + cbHowMuch += X86_PAGE_SIZE - (uDst & X86_PAGE_OFFSET_MASK); + uDst &= ~(uint64_t)X86_PAGE_OFFSET_MASK; + } + uPhysToAlias &= X86_PTE_PAE_PG_MASK; + fPte &= ~(X86_PTE_PAE_MBZ_MASK_NX | X86_PTE_PAE_PG_MASK); + cbHowMuch = RT_ALIGN_32(cbHowMuch, X86_PAGE_SIZE); + cPages = cbHowMuch >> X86_PAGE_SHIFT; + //Bs3TestPrintf("Bs3PagingAlias: adjusted: uDst=%RX64 uPhysToAlias=%RX64 cbHowMuch=%RX32 fPte=%Rx64 cPages=%RX32\n", uDst, uPhysToAlias, cbHowMuch, fPte, cPages); + if (BS3_MODE_IS_LEGACY_PAGING(g_bBs3CurrentMode)) + { + X86PTE BS3_FAR *pPteLegacy; + uint32_t uDst32 = (uint32_t)uDst; + uint32_t uPhysToAlias32 = (uint32_t)uPhysToAlias; + if (uDst32 != uDst) + { + Bs3TestPrintf("warning: Bs3PagingAlias - uDst=%RX64 is out of range for legacy paging!\n", uDst); + return VERR_INVALID_PARAMETER; + } + if (uPhysToAlias32 != uPhysToAlias) + { + Bs3TestPrintf("warning: Bs3PagingAlias - uPhysToAlias=%RX64 is out of range for legacy paging!\n", uPhysToAlias); + return VERR_INVALID_PARAMETER; + } + + /* + * Trigger page table splitting first. + */ + while (cPages > 0) + { + pPteLegacy = bs3PagingGetLegacyPte(cr3, uDst32, false, &rc); + if (pPteLegacy) + { + uint32_t cLeftInPt = X86_PG_ENTRIES - ((uDst32 >> X86_PT_SHIFT) & X86_PT_MASK); + if (cPages <= cLeftInPt) + break; + uDst32 += cLeftInPt << X86_PAGE_SHIFT; + cPages -= cLeftInPt; + } + else + { + Bs3TestPrintf("warning: Bs3PagingAlias - bs3PagingGetLegacyPte failed: rc=%d\n", rc); + return rc; + } + } + + /* + * Make the changes. + */ + cPages = cbHowMuch >> X86_PAGE_SHIFT; + uDst32 = (uint32_t)uDst; + while (cPages > 0) + { + uint32_t cLeftInPt = X86_PG_ENTRIES - ((uDst32 >> X86_PT_SHIFT) & X86_PT_MASK); + pPteLegacy = bs3PagingGetLegacyPte(cr3, uDst32, false, &rc); + while (cLeftInPt > 0 && cPages > 0) + { + pPteLegacy->u = uPhysToAlias32 | (uint32_t)fPte; + pPteLegacy++; + uDst32 += X86_PAGE_SIZE; + uPhysToAlias32 += X86_PAGE_SIZE; + cPages--; + cLeftInPt--; + } + } + } + else + { + X86PTEPAE BS3_FAR *pPtePae; + uint64_t const uDstSaved = uDst; + + /* + * Trigger page table splitting first. + */ + while (cPages > 0) + { + pPtePae = bs3PagingGetPaePte(cr3, g_bBs3CurrentMode, uDst, false, &rc); + if (pPtePae) + { + uint32_t cLeftInPt = X86_PG_PAE_ENTRIES - ((uDst >> X86_PT_PAE_SHIFT) & X86_PT_PAE_MASK); + if (cPages <= cLeftInPt) + break; + cPages -= cLeftInPt; + uDst += cLeftInPt << X86_PAGE_SHIFT; + } + else + { + Bs3TestPrintf("warning: Bs3PagingAlias - bs3PagingGetLegacyPte failed: rc=%d\n", rc); + return rc; + } + } + + /* + * Make the changes. + */ + cPages = cbHowMuch >> X86_PAGE_SHIFT; + uDst = uDstSaved; + while (cPages > 0) + { + uint32_t cLeftInPt = X86_PG_PAE_ENTRIES - ((uDst >> X86_PT_PAE_SHIFT) & X86_PT_PAE_MASK); + pPtePae = bs3PagingGetPaePte(cr3, g_bBs3CurrentMode, uDst, false, &rc); + while (cLeftInPt > 0 && cPages > 0) + { + pPtePae->u = uPhysToAlias | fPte; + pPtePae++; + uDst += X86_PAGE_SIZE; + uPhysToAlias += X86_PAGE_SIZE; + cPages--; + cLeftInPt--; + } + } + } + + ASMReloadCR3(); + } +#if ARCH_BITS == 16 + /* + * We can't do this stuff in v8086 mode, so switch to 16-bit prot mode and do it there. + */ + else + return Bs3SwitchFromV86To16BitAndCallC((FPFNBS3FAR)Bs3PagingAlias_f16, sizeof(uint64_t)*3 + sizeof(uint32_t), + uDst, uPhysToAlias, cbHowMuch, fPte); +#endif + return VINF_SUCCESS; +} + + +#undef Bs3PagingUnalias +BS3_CMN_DEF(int, Bs3PagingUnalias,(uint64_t uDst, uint32_t cbHowMuch)) +{ + return BS3_CMN_NM(Bs3PagingAlias)(uDst, uDst, cbHowMuch, X86_PTE_P | X86_PTE_RW | X86_PTE_US | X86_PTE_A | X86_PTE_D); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingData.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingData.c new file mode 100644 index 00000000..20dbde48 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingData.c @@ -0,0 +1,59 @@ +/* $Id: bs3-cmn-PagingData.c $ */ +/** @file + * BS3Kit - Paging Data. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-paging.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#if ARCH_BITS == 16 + +uint32_t g_PhysPagingRootPP = UINT32_MAX; +uint32_t g_PhysPagingRootPAE = UINT32_MAX; +uint32_t g_PhysPagingRootLM = UINT32_MAX; + +uint32_t g_uBs3PagingCanonicalTrapsAddr = UINT32_MAX; +uint16_t g_cbBs3PagingCanonicalTraps = 0; +uint16_t g_cbBs3PagingOneCanonicalTrap = 0; + +#endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingInitRootForLM.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingInitRootForLM.c new file mode 100644 index 00000000..5dac3e2c --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingInitRootForLM.c @@ -0,0 +1,115 @@ +/* $Id: bs3-cmn-PagingInitRootForLM.c $ */ +/** @file + * BS3Kit - Bs3PagingInitRootForLM + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-paging.h" + + +#undef Bs3PagingInitRootForLM +BS3_CMN_DEF(int, Bs3PagingInitRootForLM,(void)) +{ + X86PML4 BS3_FAR *pPml4; + + BS3_ASSERT(g_PhysPagingRootLM == UINT32_MAX); + + /* + * The default is an identity mapping of the first 4GB repeated for the + * whole 48-bit virtual address space. So, we need one level more than PAE. + */ + pPml4 = (X86PML4 BS3_FAR *)Bs3MemAlloc(BS3MEMKIND_TILED, _4K); + if (pPml4) + { + X86PDPT BS3_FAR *pPdPtr = (X86PDPT BS3_FAR *)Bs3MemAlloc(BS3MEMKIND_TILED, _4K); + BS3_ASSERT((uintptr_t)pPdPtr != (uintptr_t)pPml4); + if (pPdPtr) + { + X86PDPAE BS3_FAR *paPgDirs = (X86PDPAE BS3_FAR *)Bs3MemAlloc(BS3MEMKIND_TILED, _4K * 4U); + BS3_ASSERT((uintptr_t)paPgDirs != (uintptr_t)pPml4); + if (paPgDirs) + { + unsigned i; + BS3_XPTR_AUTO(X86PML4, XPtrPml4); + BS3_XPTR_AUTO(X86PDPT, XPtrPdPtr); + BS3_XPTR_AUTO(X86PDPAE, XPtrPgDirs); + + /* Set up the 2048 2MB pages first. */ + for (i = 0; i < RT_ELEMENTS(paPgDirs->a) * 4U; i++) + paPgDirs->a[i].u = ((uint32_t)i << X86_PD_PAE_SHIFT) + | X86_PDE4M_P | X86_PDE4M_RW | X86_PDE4M_US | X86_PDE4M_PS | X86_PDE4M_A | X86_PDE4M_D; + + /* Set up the page directory pointer table next (4GB replicated, remember). */ + BS3_XPTR_SET(X86PDPAE, XPtrPgDirs, paPgDirs); + pPdPtr->a[0].u = BS3_XPTR_GET_FLAT(X86PDPAE, XPtrPgDirs) + | X86_PDPE_P | X86_PDPE_RW | X86_PDPE_US | X86_PDPE_A; + pPdPtr->a[1].u = pPdPtr->a[0].u + _4K; + pPdPtr->a[2].u = pPdPtr->a[1].u + _4K; + pPdPtr->a[3].u = pPdPtr->a[2].u + _4K; + + for (i = 4; i < RT_ELEMENTS(pPdPtr->a); i += 4) + { + pPdPtr->a[i + 0].u = pPdPtr->a[0].u; + pPdPtr->a[i + 1].u = pPdPtr->a[1].u; + pPdPtr->a[i + 2].u = pPdPtr->a[2].u; + pPdPtr->a[i + 3].u = pPdPtr->a[3].u; + } + + /* Set up the page map level 4 (all entries are the same). */ + BS3_XPTR_SET(X86PDPT, XPtrPdPtr, pPdPtr); + pPml4->a[0].u = BS3_XPTR_GET_FLAT(X86PDPT, XPtrPdPtr) + | X86_PML4E_P | X86_PML4E_RW | X86_PML4E_US | X86_PML4E_A; + for (i = 1; i < RT_ELEMENTS(pPml4->a); i++) + pPml4->a[i].u = pPml4->a[0].u; + + /* Set the global root pointer and we're done. */ + BS3_XPTR_SET(X86PML4, XPtrPml4, pPml4); + g_PhysPagingRootLM = BS3_XPTR_GET_FLAT(X86PML4, XPtrPml4); + return VINF_SUCCESS; + } + + BS3_ASSERT(false); + Bs3MemFree(pPdPtr, _4K); + } + BS3_ASSERT(false); + Bs3MemFree(pPml4, _4K); + } + BS3_ASSERT(false); + return VERR_NO_MEMORY; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingInitRootForPAE.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingInitRootForPAE.c new file mode 100644 index 00000000..d50fdba7 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingInitRootForPAE.c @@ -0,0 +1,102 @@ +/* $Id: bs3-cmn-PagingInitRootForPAE.c $ */ +/** @file + * BS3Kit - Bs3PagingInitRootForPAE + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-paging.h" + + +#undef Bs3PagingInitRootForPAE +BS3_CMN_DEF(int, Bs3PagingInitRootForPAE,(void)) +{ + X86PDPT BS3_FAR *pPdPtr; + + BS3_ASSERT(g_PhysPagingRootPAE == UINT32_MAX); + + /* + * By default we do a identity mapping of the entire address space + * using 2 GB pages. So, we need four page directories and one page + * directory pointer table with 4 entries. (We cannot share the PDPT with + * long mode because of reserved bit which will cause fatal trouble.) + * + * We assume that the availability of PAE means that PSE is available too. + */ +/** @todo testcase: loading invalid PDPTREs will tripple fault the CPU, won't it? We guru with invalid guest state. */ + pPdPtr = (X86PDPT BS3_FAR *)Bs3MemAlloc(BS3MEMKIND_TILED, sizeof(X86PDPE) * 4U); + if (pPdPtr) + { + X86PDPAE BS3_FAR *paPgDirs; + BS3_ASSERT(((uintptr_t)pPdPtr & 0x3f) == 0); + + paPgDirs = (X86PDPAE BS3_FAR *)Bs3MemAlloc(BS3MEMKIND_TILED, _4K * 4U); + if (paPgDirs) + { + unsigned i; + BS3_XPTR_AUTO(X86PDPT, XPtrPdPtr); + BS3_XPTR_AUTO(X86PDPAE, XPtrPgDirs); + + /* Set up the 2048 2MB pages first. */ + for (i = 0; i < RT_ELEMENTS(paPgDirs->a) * 4U; i++) + paPgDirs->a[i].u = ((uint32_t)i << X86_PD_PAE_SHIFT) + | X86_PDE4M_P | X86_PDE4M_RW | X86_PDE4M_US | X86_PDE4M_PS | X86_PDE4M_A | X86_PDE4M_D; + + /* Set up the four page directory pointer table entries. */ + BS3_XPTR_SET(X86PDPAE, XPtrPgDirs, paPgDirs); + pPdPtr->a[0].u = BS3_XPTR_GET_FLAT(X86PDPAE, XPtrPgDirs) | X86_PDPE_P; + pPdPtr->a[1].u = pPdPtr->a[0].u + _4K; + pPdPtr->a[2].u = pPdPtr->a[1].u + _4K; + pPdPtr->a[3].u = pPdPtr->a[2].u + _4K; + + /* Free up 8 consequtive entries for raw-mode hypervisor code. */ + if (1) /** @todo detect raw-mode and only do this then. */ + for (i = 0; i < 8; i++) + paPgDirs->a[i + (UINT32_C(0xc0000000) >> X86_PD_PAE_SHIFT)].b.u1Present = 0; + + /* Set the global root pointer and we're done. */ + BS3_XPTR_SET(X86PDPT, XPtrPdPtr, pPdPtr); + g_PhysPagingRootPAE = BS3_XPTR_GET_FLAT(X86PDPT, XPtrPdPtr); + return VINF_SUCCESS; + } + BS3_ASSERT(false); + Bs3MemFree(pPdPtr, _4K); + } + BS3_ASSERT(false); + return VERR_NO_MEMORY; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingInitRootForPP.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingInitRootForPP.c new file mode 100644 index 00000000..a6dc2eb9 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingInitRootForPP.c @@ -0,0 +1,163 @@ +/* $Id: bs3-cmn-PagingInitRootForPP.c $ */ +/** @file + * BS3Kit - Bs3PagingInitRootForPP + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-paging.h" +#include "bs3-cmn-memory.h" /* bad bird */ +#include <iprt/param.h> + + +/** + * Creates page tables for a section of the page directory. + * + * @returns VINF_SUCCESS or VERR_NO_MEMORY. + * @param pPgDir The page directory. + * @param iFirst The first PD entry. + * @param cEntries How many PD entries to create pages tables for. + */ +static int Bs3PagingInitPageTablesForPgDir(X86PD BS3_FAR *pPgDir, unsigned iFirst, unsigned cEntries) +{ + uint32_t uCurPhys = (uint32_t)iFirst << X86_PD_SHIFT; + + while (cEntries--) + { + X86PT BS3_FAR *pPt = (X86PT BS3_FAR *)Bs3MemAlloc(BS3MEMKIND_TILED, _4K); + if (pPt) + { + unsigned j = 0; + for (j = 0; j < RT_ELEMENTS(pPt->a); j++, uCurPhys += PAGE_SIZE) + { + pPt->a[j].u = uCurPhys; + pPt->a[j].u |= X86_PTE_P | X86_PTE_RW | X86_PTE_US | X86_PTE_A | X86_PTE_D; + } + pPgDir->a[iFirst].u = Bs3SelPtrToFlat(pPt); + pPgDir->a[iFirst].u |= X86_PDE_P | X86_PDE_RW | X86_PDE_US | X86_PDE_A; + iFirst++; + } + else + return VERR_NO_MEMORY; + } + return VINF_SUCCESS; +} + + +#undef Bs3PagingInitRootForPP +BS3_CMN_DEF(int, Bs3PagingInitRootForPP,(void)) +{ + X86PD BS3_FAR *pPgDir; + + BS3_ASSERT(g_PhysPagingRootPP == UINT32_MAX); + + + /* + * By default we do a identity mapping of the entire address space + * using 4 GB pages. So, we only really need one page directory, + * that's all. + * + * ASSUMES page size extension available, i.e. pentium+. + */ + pPgDir = (X86PD BS3_FAR *)Bs3MemAllocZ(BS3MEMKIND_TILED, _4K); + if (pPgDir) + { + BS3_XPTR_AUTO(X86PD, XptrPgDir); + unsigned i; + int rc = VINF_SUCCESS; + + if (g_uBs3CpuDetected & BS3CPU_F_PSE) + { + for (i = 0; i < RT_ELEMENTS(pPgDir->a); i++) + { + pPgDir->a[i].u = (uint32_t)i << X86_PD_SHIFT; + pPgDir->a[i].u |= X86_PDE4M_P | X86_PDE4M_RW | X86_PDE4M_US | X86_PDE4M_PS | X86_PDE4M_A | X86_PDE4M_D; + } + + /* Free up 4 consequtive entries for raw-mode hypervisor code. */ + if (1) /** @todo detect raw-mode and only do this then. */ + for (i = 0; i < 4; i++) + pPgDir->a[i + (UINT32_C(0xc0000000) >> X86_PD_SHIFT)].b.u1Present = 0; + } + else + { + /* + * This requires 4MB of page tables if we map everything. + * So, we check how much memory we have available and make sure we + * don't use all of it for page tables. + */ + unsigned cMax = RT_ELEMENTS(pPgDir->a); + uint32_t cFreePages = g_Bs3Mem4KUpperTiled.Core.cFreeChunks + g_Bs3Mem4KLow.Core.cFreeChunks; + if (cFreePages >= cMax + 128) + Bs3PagingInitPageTablesForPgDir(pPgDir, 0, cMax); + else + { + unsigned cTop; + if (cMax >= 256 /*1MB*/) + { + cMax = cFreePages - 128; + cTop = 32; + } + else if (cMax >= 128) + { + cMax = cFreePages - 48; + cTop = 16; + } + else + { + cMax = cFreePages - 16; + cTop = RT_MIN(16, cMax / 4); + } + Bs3TestPrintf("Bs3PagingInitRootForPP: Warning! insufficient memory for mapping all 4GB!\n" + " Will only map 0x00000000-%#010RX32 and %#010RX32-0xffffffff.\n", + (uint32_t)(cMax - cTop) << PAGE_SHIFT, UINT32_MAX - ((uint32_t)cTop << PAGE_SHIFT) + 1); + rc = Bs3PagingInitPageTablesForPgDir(pPgDir, 0, cMax - cTop); + if (RT_SUCCESS(rc)) + rc = Bs3PagingInitPageTablesForPgDir(pPgDir, RT_ELEMENTS(pPgDir->a) - cTop, cTop); + } + } + + BS3_XPTR_SET(X86PD, XptrPgDir, pPgDir); + g_PhysPagingRootPP = BS3_XPTR_GET_FLAT(X86PD, XptrPgDir); + return rc; + } + + Bs3Printf("Bs3PagingInitRootForPP: No memory!\n"); + BS3_ASSERT(false); + return VERR_NO_MEMORY; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingMapRamAbove4GForLM.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingMapRamAbove4GForLM.c new file mode 100644 index 00000000..18221067 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingMapRamAbove4GForLM.c @@ -0,0 +1,120 @@ +/* $Id: bs3-cmn-PagingMapRamAbove4GForLM.c $ */ +/** @file + * BS3Kit - Bs3PagingInitMapAbove4GForLM + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-paging.h" + +#if ARCH_BITS == 16 +# error "This does not work in 16-bit mode, only 32-bit and 64-bit" +#endif + +#undef Bs3PagingMapRamAbove4GForLM +BS3_CMN_DEF(int, Bs3PagingMapRamAbove4GForLM,(uint64_t *puFailurePoint)) +{ + X86PML4 * const pPml4 = (X86PML4 *)((uintptr_t)g_PhysPagingRootLM); + unsigned iPml4 = 0; + unsigned iPdpt = 4; + uint64_t uAddr = _4G; + X86PDPT * pPdpt; + + if (puFailurePoint) + *puFailurePoint = 0; + + /* Must call Bs3PagingInitRootForLM first! */ + if (g_PhysPagingRootLM == UINT32_MAX) + return VERR_WRONG_ORDER; + + /* Done already? */ + if (pPml4->a[0].u != pPml4->a[4].u) + return VINF_ALREADY_INITIALIZED; + + /* + * Map RAM pages up to g_uBs3EndOfRamAbove4G. + */ + pPdpt = (X86PDPT *)(pPml4->a[0].u & X86_PML4E_PG_MASK); + while (uAddr < g_uBs3EndOfRamAbove4G) + { + X86PDPAE *pPd; + unsigned i; + + /* Do we need a new PDPT? */ + if (iPdpt >= RT_ELEMENTS(pPdpt->a)) + { + if (iPml4 >= RT_ELEMENTS(pPml4->a) / 2) + { + if (puFailurePoint) + *puFailurePoint = uAddr; + return VERR_OUT_OF_RANGE; + } + pPdpt = (X86PDPT *)Bs3MemAllocZ(BS3MEMKIND_FLAT32, X86_PAGE_SIZE); + if (!pPdpt || ((uintptr_t)pPdpt & X86_PAGE_OFFSET_MASK)) + { + if (puFailurePoint) + *puFailurePoint = uAddr; + return !pPdpt ? VERR_NO_MEMORY : VERR_UNSUPPORTED_ALIGNMENT; + } + pPml4->a[++iPml4].u = X86_PML4E_P | X86_PML4E_RW | X86_PML4E_US | X86_PML4E_A + | (uintptr_t)pPdpt; + iPdpt = 0; + } + + /* Allocate a new page directory. */ + pPd = (X86PDPAE *)Bs3MemAlloc(BS3MEMKIND_FLAT32, X86_PAGE_SIZE); + if (!pPd || ((uintptr_t)pPd & X86_PAGE_OFFSET_MASK)) + { + if (puFailurePoint) + *puFailurePoint = uAddr; + return !pPd ? VERR_NO_MEMORY : VERR_UNSUPPORTED_ALIGNMENT; + } + + /* Initialize it. */ + for (i = 0; i < RT_ELEMENTS(pPd->a); i++) + { + pPd->a[i].u = uAddr | X86_PDE4M_P | X86_PDE4M_RW | X86_PDE4M_US | X86_PDE4M_PS | X86_PDE4M_A | X86_PDE4M_D; + uAddr += _2M; + } + + /* Insert it into the page directory pointer table. */ + pPdpt->a[iPdpt++].u = (uintptr_t)pPd | X86_PDPE_P | X86_PDPE_RW | X86_PDPE_US | X86_PDPE_A; + } + + return VINF_SUCCESS; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingProtect.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingProtect.c new file mode 100644 index 00000000..51422d9a --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingProtect.c @@ -0,0 +1,392 @@ +/* $Id: bs3-cmn-PagingProtect.c $ */ +/** @file + * BS3Kit - Bs3PagingProtect + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-paging.h" +#include <iprt/asm-amd64-x86.h> +#include <iprt/param.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#if 0 +# define BS3PAGING_DPRINTF1(a) Bs3TestPrintf a +#else +# define BS3PAGING_DPRINTF1(a) do { } while (0) +#endif +#if 0 +# define BS3PAGING_DPRINTF2(a) Bs3TestPrintf a +#else +# define BS3PAGING_DPRINTF2(a) do { } while (0) +#endif + + +static void *bs3PagingBuildPaeTable(uint64_t uTmpl, uint64_t cbIncrement, BS3MEMKIND enmKind, int *prc) +{ + uint64_t BS3_FAR *pau64 = (uint64_t BS3_FAR *)Bs3MemAlloc(enmKind, _4K); + if (pau64) + { + unsigned i; + for (i = 0; i < _4K / sizeof(uint64_t); i++, uTmpl += cbIncrement) + pau64[i] = uTmpl; + } + else + *prc = VERR_NO_MEMORY; + return pau64; +} + + +#undef bs3PagingGetLegacyPte +BS3_CMN_DEF(X86PTE BS3_FAR *, bs3PagingGetLegacyPte,(RTCCUINTXREG cr3, uint32_t uFlat, bool fUseInvlPg, int *prc)) +{ + X86PTE BS3_FAR *pPTE = NULL; +#if TMPL_BITS == 16 + uint32_t const uMaxAddr = BS3_MODE_IS_RM_OR_V86(g_bBs3CurrentMode) ? _1M - 1 : BS3_SEL_TILED_AREA_SIZE - 1; +#else + uint32_t const uMaxAddr = UINT32_MAX; +#endif + BS3PAGING_DPRINTF2(("bs3PagingGetLegacyPte: cr3=%RX32 uFlat=%RX32 uMaxAddr=%RX32\n", (uint32_t)cr3, uFlat, uMaxAddr)); + + *prc = VERR_OUT_OF_RANGE; + if (cr3 <= uMaxAddr) + { + unsigned const iPde = (uFlat >> X86_PD_SHIFT) & X86_PD_MASK; + PX86PD const pPD = (PX86PD)Bs3XptrFlatToCurrent(cr3 & X86_CR3_PAGE_MASK); + + BS3_ASSERT(pPD->a[iPde].b.u1Present); + if (pPD->a[iPde].b.u1Present) + { + unsigned const iPte = (uFlat >> X86_PT_SHIFT) & X86_PT_MASK; + + BS3_ASSERT(pPD->a[iPde].b.u1Present); + BS3PAGING_DPRINTF2(("bs3PagingGetLegacyPte: pPD=%p iPde=%#x: %#RX32\n", pPD, iPde, pPD->a[iPde])); + if (pPD->a[iPde].b.u1Present) + { + if (!pPD->a[iPde].b.u1Size) + { + if (pPD->a[iPde].u <= uMaxAddr) + pPTE = &((X86PT BS3_FAR *)Bs3XptrFlatToCurrent(pPD->a[iPde].u & ~(uint32_t)PAGE_OFFSET_MASK))->a[iPte]; + else + BS3PAGING_DPRINTF1(("bs3PagingGetLegacyPte: out of range! iPde=%#x: %#x\n", iPde, pPD->a[iPde].u)); + } + else + { + X86PT BS3_FAR *pPT; + uint32_t uPte = (pPD->a[iPde].u & ~(uint32_t)(X86_PDE4M_PS | X86_PDE4M_G | X86_PDE4M_PG_HIGH_MASK)) \ + | X86_PTE_D; + if (pPD->a[iPde].b.u1Global) + uPte |= X86_PTE_G; + if (pPD->a[iPde].b.u1PAT) + uPte |= X86_PTE_PAT; + + pPT = (X86PT BS3_FAR *)bs3PagingBuildPaeTable(RT_MAKE_U64(uPte, uPte | PAGE_SIZE), + RT_MAKE_U64(PAGE_SIZE*2, PAGE_SIZE*2), + uMaxAddr > _1M ? BS3MEMKIND_TILED : BS3MEMKIND_REAL, prc); + + BS3PAGING_DPRINTF2(("bs3PagingGetLegacyPte: Built pPT=%p uPte=%RX32\n", pPT, uPte)); + if (pPT) + { + ASMAtomicUoWriteU32(&pPD->a[iPde].u, + Bs3SelPtrToFlat(pPT) + | ( pPD->a[iPde].u + & ~(uint32_t)(X86_PTE_PG_MASK | X86_PDE4M_PS | X86_PDE4M_G | X86_PDE4M_D))); + BS3PAGING_DPRINTF2(("bs3PagingGetLegacyPte: iPde=%#x: %#RX32\n", iPde, pPD->a[iPde].u)); + if (fUseInvlPg) + ASMInvalidatePage(uFlat); + pPTE = &pPT->a[iPte]; + } + } + } + } + } + else + BS3PAGING_DPRINTF1(("bs3PagingGetLegacyPte: out of range! cr3=%#x\n", cr3)); + return pPTE; +} + + +/** + * Get the PTE for an address, given a PAE or long mode CR3. + * + * @returns Pointer to the PTE on success, NULL on failure. + * @param cr3 The CR3. + * @param bMode Indicates whether it's PAE or long mode. + * @param uFlat The address for which we want the PTE. + * @param fUseInvlPg Whether we can use invalidate page when + * replacing large pages. + * @param prc Updated only on failure. + */ +#undef bs3PagingGetPaePte +BS3_CMN_DEF(X86PTEPAE BS3_FAR *, bs3PagingGetPaePte,(RTCCUINTXREG cr3, uint8_t bMode, uint64_t uFlat, bool fUseInvlPg, int *prc)) +{ + X86PTEPAE BS3_FAR *pPTE = NULL; +#if TMPL_BITS == 16 + uint32_t const uMaxAddr = BS3_MODE_IS_RM_OR_V86(g_bBs3CurrentMode) ? _1M - 1 : BS3_SEL_TILED_AREA_SIZE - 1; +#else + uintptr_t const uMaxAddr = ~(uintptr_t)0; +#endif + + *prc = VERR_OUT_OF_RANGE; + if ((cr3 & X86_CR3_AMD64_PAGE_MASK) <= uMaxAddr) + { + X86PDPAE BS3_FAR *pPD; + if (BS3_MODE_IS_64BIT_SYS(bMode)) + { + unsigned const iPml4e = (uFlat >> X86_PML4_SHIFT) & X86_PML4_MASK; + X86PML4 BS3_FAR *pPml4 = (X86PML4 BS3_FAR *)Bs3XptrFlatToCurrent(cr3 & X86_CR3_AMD64_PAGE_MASK); + BS3_ASSERT(pPml4->a[iPml4e].n.u1Present); + if ((pPml4->a[iPml4e].u & X86_PML4E_PG_MASK) <= uMaxAddr) + { + unsigned const iPdpte = (uFlat >> X86_PDPT_SHIFT) & X86_PDPT_MASK_AMD64; + X86PDPT BS3_FAR *pPdpt = (X86PDPT BS3_FAR *)Bs3XptrFlatToCurrent(pPml4->a[iPml4e].u & X86_PML4E_PG_MASK); + BS3_ASSERT(pPdpt->a[iPdpte].n.u1Present); + if (!pPdpt->a[iPdpte].b.u1Size) + { + if ((pPdpt->a[iPdpte].u & X86_PDPE_PG_MASK) <= uMaxAddr) + pPD = (X86PDPAE BS3_FAR *)Bs3XptrFlatToCurrent(pPdpt->a[iPdpte].u & ~(uint64_t)PAGE_OFFSET_MASK); + else + BS3PAGING_DPRINTF1(("bs3PagingGetPaePte: out of range! iPdpte=%#x: %RX64 max=%RX32\n", + iPdpte, pPdpt->a[iPdpte].u, (uint32_t)uMaxAddr)); + } + else + { + /* Split 1GB page. */ + pPD = (X86PDPAE BS3_FAR *)bs3PagingBuildPaeTable(pPdpt->a[iPdpte].u, _2M, + uMaxAddr > _1M ? BS3MEMKIND_TILED : BS3MEMKIND_REAL, prc); + if (pPD) + { + ASMAtomicUoWriteU64(&pPdpt->a[iPdpte].u, + Bs3SelPtrToFlat(pPD) + | ( pPdpt->a[iPdpte].u + & ~(uint64_t)(X86_PDPE_PG_MASK | X86_PDE4M_PS | X86_PDE4M_G | X86_PDE4M_D))); + if (fUseInvlPg) + ASMInvalidatePage(uFlat); + } + } + } + } + //else if (uFlat <= UINT32_MAX) - fixme! + else if (!(uFlat >> 32)) + { + unsigned const iPdpte = ((uint32_t)uFlat >> X86_PDPT_SHIFT) & X86_PDPT_MASK_PAE; + X86PDPT BS3_FAR *pPdpt = (X86PDPT BS3_FAR *)Bs3XptrFlatToCurrent(cr3 & X86_CR3_PAE_PAGE_MASK); + BS3_ASSERT(pPdpt->a[iPdpte].n.u1Present); + if ((pPdpt->a[iPdpte].u & X86_PDPE_PG_MASK) <= uMaxAddr) + pPD = (X86PDPAE BS3_FAR *)Bs3XptrFlatToCurrent(pPdpt->a[iPdpte].u & X86_PDPE_PG_MASK); + else + BS3PAGING_DPRINTF1(("bs3PagingGetPaePte: out of range! iPdpte=%#x: %RX64 max=%RX32\n", + iPdpte, pPdpt->a[iPdpte].u, (uint32_t)uMaxAddr)); + } + else + { + pPD = NULL; + BS3PAGING_DPRINTF1(("bs3PagingGetPaePte: out of range! uFlat=%#RX64 max=%RX32\n", uFlat, (uint32_t)uMaxAddr)); + } + if (pPD) + { + unsigned const iPte = (uFlat >> X86_PT_PAE_SHIFT) & X86_PT_PAE_MASK; + unsigned const iPde = (uFlat >> X86_PD_PAE_SHIFT) & X86_PD_PAE_MASK; + if (!pPD->a[iPde].b.u1Size) + { + if ((pPD->a[iPde].u & X86_PDE_PAE_PG_MASK) <= uMaxAddr) + pPTE = &((X86PTPAE BS3_FAR *)Bs3XptrFlatToCurrent(pPD->a[iPde].u & ~(uint64_t)PAGE_OFFSET_MASK))->a[iPte]; + else + BS3PAGING_DPRINTF1(("bs3PagingGetPaePte: out of range! iPde=%#x: %RX64 max=%RX32\n", + iPde, pPD->a[iPde].u, (uint32_t)uMaxAddr)); + } + else + { + /* Split 2MB page. */ + X86PTPAE BS3_FAR *pPT; + uint64_t uTmpl = pPD->a[iPde].u & ~(uint64_t)(X86_PDE4M_G | X86_PDE4M_PS | X86_PDE4M_PAT); + if (!pPD->a[iPde].b.u1Global) + uTmpl |= X86_PTE_G; + if (!pPD->a[iPde].b.u1PAT) + uTmpl |= X86_PTE_PAT; + + pPT = (X86PTPAE BS3_FAR *)bs3PagingBuildPaeTable(uTmpl, PAGE_SIZE, + uMaxAddr > _1M ? BS3MEMKIND_TILED : BS3MEMKIND_REAL, prc); + if (pPT) + { + ASMAtomicUoWriteU64(&pPD->a[iPde].u, + Bs3SelPtrToFlat(pPT) + | ( pPD->a[iPde].u + & ~(uint64_t)(X86_PTE_PAE_PG_MASK | X86_PDE4M_PS | X86_PDE4M_G | X86_PDE4M_D))); + if (fUseInvlPg) + ASMInvalidatePage(uFlat); + pPTE = &pPT->a[iPte]; + } + } + } + } + else + BS3PAGING_DPRINTF1(("bs3PagingGetPaePte: out of range! cr3=%#RX32 uMaxAddr=%#RX32\n", (uint32_t)cr3, (uint32_t)uMaxAddr)); + return pPTE; +} + + +#undef Bs3PagingProtect +BS3_CMN_DEF(int, Bs3PagingProtect,(uint64_t uFlat, uint64_t cb, uint64_t fSet, uint64_t fClear)) +{ +#if ARCH_BITS == 16 + if (!BS3_MODE_IS_V86(g_bBs3CurrentMode)) +#endif + { + RTCCUINTXREG const cr3 = ASMGetCR3(); + RTCCUINTXREG const cr4 = g_uBs3CpuDetected & BS3CPU_F_CPUID ? ASMGetCR4() : 0; + bool const fLegacyPTs = !(cr4 & X86_CR4_PAE); + bool const fUseInvlPg = (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80486 + && ( cb < UINT64_C(16)*PAGE_SIZE + || (cr4 & X86_CR4_PGE)); + unsigned cEntries; + int rc; + + /* + * Adjust the range parameters. + */ + cb += uFlat & PAGE_OFFSET_MASK; + cb = RT_ALIGN_64(cb, PAGE_SIZE); + uFlat &= ~(uint64_t)PAGE_OFFSET_MASK; + + fSet &= ~X86_PTE_PAE_PG_MASK; + fClear &= ~X86_PTE_PAE_PG_MASK; + + BS3PAGING_DPRINTF1(("Bs3PagingProtect: uFlat=%RX64 cb=%RX64 fSet=%RX64 fClear=%RX64 %s %s\n", uFlat, cb, fSet, fClear, + fLegacyPTs ? "legacy" : "pae/amd64", fUseInvlPg ? "invlpg" : "reload-cr3")); + if (fLegacyPTs) + { + /* + * Legacy page tables. + */ + while ((uint32_t)cb > 0) + { + PX86PTE pPte = BS3_CMN_FAR_NM(bs3PagingGetLegacyPte)(cr3, (uint32_t)uFlat, fUseInvlPg, &rc); + if (!pPte) + return rc; + + cEntries = X86_PG_ENTRIES - ((uFlat >> X86_PT_SHIFT) & X86_PT_MASK); + while (cEntries-- > 0 && cb > 0) + { + pPte->u &= ~(uint32_t)fClear; + pPte->u |= (uint32_t)fSet; + if (fUseInvlPg) + ASMInvalidatePage(uFlat); + + pPte++; + uFlat += PAGE_SIZE; + cb -= PAGE_SIZE; + } + } + } + else + { + /* + * Long mode or PAE page tables (at this level they are the same). + */ + while (cb > 0) + { + PX86PTEPAE pPte = BS3_CMN_FAR_NM(bs3PagingGetPaePte)(cr3, g_bBs3CurrentMode, uFlat, fUseInvlPg, &rc); + if (!pPte) + return rc; + + cEntries = X86_PG_ENTRIES - ((uFlat >> X86_PT_PAE_SHIFT) & X86_PT_PAE_MASK); + while (cEntries-- > 0 && cb > 0) + { + pPte->u &= ~fClear; + pPte->u |= fSet; + if (fUseInvlPg) + ASMInvalidatePage(uFlat); + + pPte++; + uFlat += PAGE_SIZE; + cb -= PAGE_SIZE; + } + } + } + + /* + * Flush the TLB if we didn't use INVLPG above. + */ + BS3PAGING_DPRINTF2(("Bs3PagingProtect: reloading cr3=%RX32\n", (uint32_t)cr3)); + //if (!fUseInvlPg) + ASMSetCR3(cr3); + BS3PAGING_DPRINTF2(("Bs3PagingProtect: reloaded cr3=%RX32\n", (uint32_t)cr3)); + } +#if ARCH_BITS == 16 + /* + * We can do this stuff in v8086 mode. + */ + else + return Bs3SwitchFromV86To16BitAndCallC((FPFNBS3FAR)Bs3PagingProtect_f16, sizeof(uint64_t) * 4, uFlat, cb, fSet, fClear); +#endif + return VINF_SUCCESS; +} + + +#undef Bs3PagingProtectPtr +BS3_CMN_DEF(int, Bs3PagingProtectPtr,(void *pv, size_t cb, uint64_t fSet, uint64_t fClear)) +{ +#if ARCH_BITS == 16 + return BS3_CMN_NM(Bs3PagingProtect)(Bs3SelPtrToFlat(pv), cb, fSet, fClear); +#else + return BS3_CMN_NM(Bs3PagingProtect)((uintptr_t)pv, cb, fSet, fClear); +#endif +} + + +#undef Bs3PagingGetPte +BS3_CMN_DEF(void BS3_FAR *, Bs3PagingGetPte,(uint64_t uFlat, int *prc)) +{ + RTCCUINTXREG const cr3 = ASMGetCR3(); + RTCCUINTXREG const cr4 = g_uBs3CpuDetected & BS3CPU_F_CPUID ? ASMGetCR4() : 0; + bool const fLegacyPTs = !(cr4 & X86_CR4_PAE); + bool const fUseInvlPg = (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80486; + int rc; + if (!prc) + prc = &rc; + if (!fLegacyPTs) + return BS3_CMN_FAR_NM(bs3PagingGetPaePte)(cr3, g_bBs3CurrentMode, uFlat, fUseInvlPg, prc); + if (uFlat < _4G) + return BS3_CMN_FAR_NM(bs3PagingGetLegacyPte)(cr3, (uint32_t)uFlat, fUseInvlPg, prc); + *prc = VERR_OUT_OF_RANGE; + return NULL; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingQueryAddressInfo.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingQueryAddressInfo.c new file mode 100644 index 00000000..ca414c1d --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingQueryAddressInfo.c @@ -0,0 +1,159 @@ +/* $Id: bs3-cmn-PagingQueryAddressInfo.c $ */ +/** @file + * BS3Kit - Bs3PagingQueryAddressInfo + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> +#include <iprt/asm-amd64-x86.h> +#include <VBox/err.h> + + +#undef Bs3PagingQueryAddressInfo +BS3_CMN_DEF(int, Bs3PagingQueryAddressInfo,(uint64_t uFlat, PBS3PAGINGINFO4ADDR pPgInfo)) +{ + RTCCUINTXREG const cr3 = ASMGetCR3(); + RTCCUINTXREG const cr4 = g_uBs3CpuDetected & BS3CPU_F_CPUID ? ASMGetCR4() : 0; + bool const fLegacyPTs = !(cr4 & X86_CR4_PAE); + int rc = VERR_OUT_OF_RANGE; + + + pPgInfo->fFlags = 0; + pPgInfo->u.apbEntries[0] = NULL; + pPgInfo->u.apbEntries[1] = NULL; + pPgInfo->u.apbEntries[2] = NULL; + pPgInfo->u.apbEntries[3] = NULL; + + if (!fLegacyPTs) + { +#if TMPL_BITS == 16 + uint32_t const uMaxAddr = BS3_MODE_IS_RM_OR_V86(g_bBs3CurrentMode) ? _1M - 1 : BS3_SEL_TILED_AREA_SIZE - 1; +#else + uintptr_t const uMaxAddr = ~(uintptr_t)0; +#endif + uint64_t const fEfer = g_uBs3CpuDetected & BS3CPU_F_LONG_MODE ? ASMRdMsr(MSR_K6_EFER) : 0; + + pPgInfo->cEntries = fEfer & MSR_K6_EFER_LMA ? 4 : 3; + pPgInfo->cbEntry = sizeof(X86PTEPAE); + if ((cr3 & X86_CR3_AMD64_PAGE_MASK) <= uMaxAddr) + { + if ( (fEfer & MSR_K6_EFER_LMA) + && X86_IS_CANONICAL(uFlat)) + { + /* 48-bit long mode paging. */ + pPgInfo->u.Pae.pPml4e = (X86PML4E BS3_FAR *)Bs3XptrFlatToCurrent(cr3 & X86_CR3_AMD64_PAGE_MASK); + pPgInfo->u.Pae.pPml4e += (uFlat >> X86_PML4_SHIFT) & X86_PML4_MASK; + if (!pPgInfo->u.Pae.pPml4e->n.u1Present) + rc = VERR_PAGE_NOT_PRESENT; + else if ((pPgInfo->u.Pae.pPml4e->u & X86_PML4E_PG_MASK) <= uMaxAddr) + { + pPgInfo->u.Pae.pPdpe = (X86PDPE BS3_FAR *)Bs3XptrFlatToCurrent(pPgInfo->u.Pae.pPml4e->u & X86_PML4E_PG_MASK); + pPgInfo->u.Pae.pPdpe += (uFlat >> X86_PDPT_SHIFT) & X86_PDPT_MASK_AMD64; + if (!pPgInfo->u.Pae.pPdpe->n.u1Present) + rc = VERR_PAGE_NOT_PRESENT; + else if (pPgInfo->u.Pae.pPdpe->b.u1Size) + rc = VINF_SUCCESS; + else + rc = VINF_TRY_AGAIN; + } + } + else if ( !(fEfer & MSR_K6_EFER_LMA) + && uFlat <= _4G) + { + /* 32-bit PAE paging. */ + pPgInfo->u.Pae.pPdpe = (X86PDPE BS3_FAR *)Bs3XptrFlatToCurrent(cr3 & X86_CR3_PAE_PAGE_MASK); + pPgInfo->u.Pae.pPdpe += ((uint32_t)uFlat >> X86_PDPT_SHIFT) & X86_PDPT_MASK_PAE; + if (!pPgInfo->u.Pae.pPdpe->n.u1Present) + rc = VERR_PAGE_NOT_PRESENT; + else + rc = VINF_TRY_AGAIN; + } + + /* Common code for the PD and PT levels. */ + if ( rc == VINF_TRY_AGAIN + && (pPgInfo->u.Pae.pPdpe->u & X86_PDPE_PG_MASK) <= uMaxAddr) + { + rc = VERR_OUT_OF_RANGE; + pPgInfo->u.Pae.pPde = (X86PDEPAE BS3_FAR *)Bs3XptrFlatToCurrent(pPgInfo->u.Pae.pPdpe->u & X86_PDPE_PG_MASK); + pPgInfo->u.Pae.pPde += (uFlat >> X86_PD_PAE_SHIFT) & X86_PD_PAE_MASK; + if (!pPgInfo->u.Pae.pPde->n.u1Present) + rc = VERR_PAGE_NOT_PRESENT; + else if (pPgInfo->u.Pae.pPde->b.u1Size) + rc = VINF_SUCCESS; + else if ((pPgInfo->u.Pae.pPde->u & X86_PDE_PAE_PG_MASK) <= uMaxAddr) + { + pPgInfo->u.Pae.pPte = (X86PTEPAE BS3_FAR *)Bs3XptrFlatToCurrent(pPgInfo->u.Pae.pPde->u & X86_PDE_PAE_PG_MASK); + rc = VINF_SUCCESS; + } + } + else if (rc == VINF_TRY_AGAIN) + rc = VERR_OUT_OF_RANGE; + } + } + else + { +#if TMPL_BITS == 16 + uint32_t const uMaxAddr = BS3_MODE_IS_RM_OR_V86(g_bBs3CurrentMode) ? _1M - 1 : BS3_SEL_TILED_AREA_SIZE - 1; +#else + uint32_t const uMaxAddr = UINT32_MAX; +#endif + + pPgInfo->cEntries = 2; + pPgInfo->cbEntry = sizeof(X86PTE); + if ( uFlat < _4G + && cr3 <= uMaxAddr) + { + pPgInfo->u.Legacy.pPde = (X86PDE BS3_FAR *)Bs3XptrFlatToCurrent(cr3 & X86_CR3_PAGE_MASK); + pPgInfo->u.Legacy.pPde += ((uint32_t)uFlat >> X86_PD_SHIFT) & X86_PD_MASK; + if (!pPgInfo->u.Legacy.pPde->b.u1Present) + rc = VERR_PAGE_NOT_PRESENT; + else if (pPgInfo->u.Legacy.pPde->b.u1Size) + rc = VINF_SUCCESS; + else if (pPgInfo->u.Legacy.pPde->u <= uMaxAddr) + { + pPgInfo->u.Legacy.pPte = (X86PTE BS3_FAR *)Bs3XptrFlatToCurrent(pPgInfo->u.Legacy.pPde->u & X86_PDE_PG_MASK); + pPgInfo->u.Legacy.pPte += ((uint32_t)uFlat >> X86_PT_SHIFT) & X86_PT_MASK; + if (pPgInfo->u.Legacy.pPte->n.u1Present) + rc = VINF_SUCCESS; + else + rc = VERR_PAGE_NOT_PRESENT; + } + } + } + return rc; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingSetupCanonicalTraps.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingSetupCanonicalTraps.c new file mode 100644 index 00000000..27494c75 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PagingSetupCanonicalTraps.c @@ -0,0 +1,123 @@ +/* $Id: bs3-cmn-PagingSetupCanonicalTraps.c $ */ +/** @file + * BS3Kit - Bs3PagingSetupCanonicalTraps + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-paging.h" +#include "iprt/asm-amd64-x86.h" + + +#undef Bs3PagingSetupCanonicalTraps +BS3_CMN_PROTO_STUB(void BS3_FAR *, Bs3PagingSetupCanonicalTraps,(void)) +{ + if (g_uBs3CpuDetected & BS3CPU_F_LONG_MODE) + { +#if ARCH_BITS == 16 + if (!BS3_MODE_IS_RM_OR_V86(g_bBs3CurrentMode)) +#endif + { + uint8_t BS3_FAR *pb; + X86PTEPAE BS3_FAR *paLoPtes; + X86PTEPAE BS3_FAR *paHiPtes; + int rc; + + /* Already initialized? Likely. */ + if (g_cbBs3PagingCanonicalTraps != 0) + return Bs3XptrFlatToCurrent(g_uBs3PagingCanonicalTrapsAddr); + + /* Initialize AMD64 page tables if necessary (unlikely). */ + if (g_PhysPagingRootLM == UINT32_MAX) + { + rc = Bs3PagingInitRootForLM(); + if (RT_FAILURE(rc)) + return NULL; + } + + /* + * Get the page table entries first to avoid having to unmap things. + */ + paLoPtes = bs3PagingGetPaePte(g_PhysPagingRootLM, BS3_MODE_LM64, UINT64_C(0x00007fffffffe000), false, &rc); + paHiPtes = bs3PagingGetPaePte(g_PhysPagingRootLM, BS3_MODE_LM64, UINT64_C(0xffff800000000000), false, &rc); + if (!paHiPtes || !paLoPtes) + { + Bs3TestPrintf("warning: Bs3PagingSetupCanonicalTraps - failed to get PTEs!\n"); + return NULL; + } + + /* + * Allocate the buffer. Currently using 8KB on each side. + */ + pb = (uint8_t BS3_FAR *)Bs3MemAlloc(BS3MEMKIND_TILED, X86_PAGE_SIZE * 4); + if (pb) + { + RTCCUINTXREG uFlat = Bs3SelPtrToFlat(pb); + + /* + * Inject it into the page tables. + */ + paLoPtes[0].u &= ~X86_PTE_PAE_PG_MASK; + paLoPtes[0].u |= uFlat + X86_PAGE_SIZE * 0; + paLoPtes[1].u &= ~X86_PTE_PAE_PG_MASK; + paLoPtes[1].u |= uFlat + X86_PAGE_SIZE * 1; + + paHiPtes[0].u &= ~X86_PTE_PAE_PG_MASK; + paHiPtes[0].u |= uFlat + X86_PAGE_SIZE * 2; + paHiPtes[1].u &= ~X86_PTE_PAE_PG_MASK; + paHiPtes[1].u |= uFlat + X86_PAGE_SIZE * 3; + ASMReloadCR3(); + + /* + * Update globals and return successfully. + */ + g_uBs3PagingCanonicalTrapsAddr = uFlat; + g_cbBs3PagingCanonicalTraps = X86_PAGE_SIZE * 4; + g_cbBs3PagingOneCanonicalTrap = X86_PAGE_SIZE * 2; + return pb; + } + + Bs3TestPrintf("warning: Bs3PagingSetupCanonicalTraps - out of memory (mode %#x)\n", g_bBs3CurrentMode); + } +#if ARCH_BITS == 16 + else + Bs3TestPrintf("warning: Bs3PagingSetupCanonicalTraps was called in RM or V86 mode (%#x)!\n", g_bBs3CurrentMode); +#endif + } + return NULL; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Panic.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Panic.asm new file mode 100644 index 00000000..d3891b37 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Panic.asm @@ -0,0 +1,48 @@ +; $Id: bs3-cmn-Panic.asm $ +;; @file +; BS3Kit - Bs3Panic, Common. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_PROC_BEGIN_CMN Bs3Panic, BS3_PBC_HYBRID_0_ARGS + push xBP + mov xBP, xSP + cli +.panic_again: + hlt + jmp .panic_again +BS3_PROC_END_CMN Bs3Panic + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PerCpuData.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PerCpuData.c new file mode 100644 index 00000000..c07629b9 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PerCpuData.c @@ -0,0 +1,74 @@ +/* $Id: bs3-cmn-PerCpuData.c $ */ +/** @file + * BS3Kit - Per CPU Data. + * + * @remarks Not quite sure how to do per-cpu data yet, but this is stuff + * that eventually needs to be per CPU. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-test.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#if ARCH_BITS == 16 + +/** Hint for 16-bit trap handlers regarding the high word of EIP. */ +uint32_t g_uBs3TrapEipHint = 0; + +/** Flat pointer to a BS3TRAPFRAME registered by Bs3TrapSetJmp. + * When this is non-zero, the setjmp is considered armed. */ +uint32_t g_pBs3TrapSetJmpFrame = 0; + +/** The current CPU mode. */ +uint8_t g_bBs3CurrentMode = BS3_MODE_RM; + +uint8_t g_bStupidUnalignedCompiler1 = 0xfe; + +/** Set to disable special V8086 \#GP and \#UD handling in Bs3TrapDefaultHandler. + * This is useful for getting */ +bool volatile g_fBs3TrapNoV86Assist = false; + +/** The context of the last Bs3TrapSetJmp call. + * This will have eax set to 1 and need only be restored when it triggers. */ +BS3REGCTX g_Bs3TrapSetJmpCtx; + +#endif /* ARCH_BITS == 16 */ + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PicMaskAll.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PicMaskAll.c new file mode 100644 index 00000000..a5d5657b --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PicMaskAll.c @@ -0,0 +1,51 @@ +/* $Id: bs3-cmn-PicMaskAll.c $ */ +/** @file + * BS3Kit - Masks all IRQs on the PIC. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include <iprt/asm-amd64-x86.h> + + +#undef Bs3PicMaskAll +BS3_CMN_DEF(void, Bs3PicMaskAll,(void)) +{ + ASMOutU8(0xa1, 0xff); + ASMOutU8(0x21, 0xff); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PicSetup.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PicSetup.c new file mode 100644 index 00000000..a72ec009 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PicSetup.c @@ -0,0 +1,90 @@ +/* $Id: bs3-cmn-PicSetup.c $ */ +/** @file + * BS3Kit - PIC Setup. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include <iprt/asm-amd64-x86.h> +#include "bs3-cmn-pic.h" + + + +/** + * Configures the PIC, once only. + * + * Subsequent calls to this function will not do anything. + * + * The PIC will be programmed to use IDT/IVT vectors 0x70 thru 0x7f, auto + * end-of-interrupt, and all IRQs masked. The individual PIC users will have to + * use #Bs3PicUpdateMask unmask their IRQ once they've got all the handlers + * installed. + */ +#undef Bs3PicSetup +BS3_CMN_DEF(void, Bs3PicSetup,(bool fForcedReInit)) +{ + /* + * The first call configures the PIC to send interrupts to vectors 0x70 thru 0x7f, + * masking all of them. Things producing IRQs is responsible for configure their + * handlers and then(!) use Bs3PicUpdateMask to unmask the IRQ. + */ + if (!g_fBs3PicConfigured || fForcedReInit) + { + g_fBs3PicConfigured = true; + + /* Start init. */ + ASMOutU8(BS3_PIC_PORT_MASTER, BS3_PIC_CMD_INIT | BS3_PIC_CMD_INIT_F_4STEP); + ASMOutU8(BS3_PIC_PORT_SLAVE, BS3_PIC_CMD_INIT | BS3_PIC_CMD_INIT_F_4STEP); + + /* Set IRQ base. */ + ASMOutU8(BS3_PIC_PORT_MASTER + 1, 0x70); + ASMOutU8(BS3_PIC_PORT_SLAVE + 1, 0x78); + + /* Dunno. */ + ASMOutU8(BS3_PIC_PORT_MASTER + 1, 4); + ASMOutU8(BS3_PIC_PORT_SLAVE + 1, 2); + + /* Set IRQ base. */ + ASMOutU8(BS3_PIC_PORT_MASTER + 1, BS3_PIC_I4_F_AUTO_EOI); + ASMOutU8(BS3_PIC_PORT_SLAVE + 1, BS3_PIC_I4_F_AUTO_EOI); + + /* Mask everything. */ + ASMOutU8(BS3_PIC_PORT_MASTER + 1, UINT8_MAX); + ASMOutU8(BS3_PIC_PORT_SLAVE + 1, UINT8_MAX); + } +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PicUpdateMask.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PicUpdateMask.c new file mode 100644 index 00000000..9f6a0778 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PicUpdateMask.c @@ -0,0 +1,55 @@ +/* $Id: bs3-cmn-PicUpdateMask.c $ */ +/** @file + * BS3Kit - PIC Setup. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include <iprt/asm-amd64-x86.h> +#include "bs3-cmn-pic.h" + + +#undef Bs3PicUpdateMask +BS3_CMN_DEF(uint16_t, Bs3PicUpdateMask,(uint16_t fAndMask, uint16_t fOrMask)) +{ + uint8_t bPic0Mask = (ASMInU8(BS3_PIC_PORT_MASTER + 1) & (uint8_t)fAndMask) | (uint8_t)fOrMask; + uint8_t bPic1Mask = (ASMInU8(BS3_PIC_PORT_SLAVE + 1) & (fAndMask >> 8)) | (fOrMask >> 8); + ASMOutU8(BS3_PIC_PORT_SLAVE + 1, bPic1Mask); + ASMOutU8(BS3_PIC_PORT_MASTER + 1, bPic0Mask); + return RT_MAKE_U16(bPic0Mask, bPic1Mask); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PitIrqHandler.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PitIrqHandler.c new file mode 100644 index 00000000..cde3ae47 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PitIrqHandler.c @@ -0,0 +1,76 @@ +/* $Id: bs3-cmn-PitIrqHandler.c $ */ +/** @file + * BS3Kit - The PIT IRQ Handler and associated data. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include <iprt/asm-amd64-x86.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#if ARCH_BITS == 16 +/** Nano seconds (approx) since last the PIT timer was started. */ +uint64_t volatile g_cBs3PitNs = 0; +/** Milliseconds seconds (very approx) since last the PIT timer was started. */ +uint64_t volatile g_cBs3PitMs = 0; +/** Number of ticks since last the PIT timer was started. */ +uint32_t volatile g_cBs3PitTicks = 0; +/** The current interval in nanon seconds. */ +uint32_t g_cBs3PitIntervalNs = 0; +/** The current interval in milliseconds (approximately). + * This is 0 if not yet started (used for checking the state internally). */ +uint16_t g_cBs3PitIntervalMs = 0; +/** The current PIT frequency (approximately). 0 if not yet started. */ +uint16_t volatile g_cBs3PitIntervalHz = 0; +#endif + + +BS3_DECL_NEAR_CALLBACK(void) BS3_CMN_NM(bs3PitIrqHandler)(PBS3TRAPFRAME pTrapFrame) +{ + if (g_cBs3PitIntervalHz) + { + g_cBs3PitMs += g_cBs3PitIntervalMs; + g_cBs3PitNs += g_cBs3PitIntervalNs; + g_cBs3PitTicks++; + } + NOREF(pTrapFrame); + ASMOutU8(0x20, 0x20); /** @todo function! */ +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PrintChr.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PrintChr.asm new file mode 100644 index 00000000..4099a967 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PrintChr.asm @@ -0,0 +1,115 @@ +; $Id: bs3-cmn-PrintChr.asm $ +;; @file +; BS3Kit - Bs3PrintChr. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +BS3_EXTERN_CMN Bs3Syscall + + +TMPL_BEGIN_TEXT + +;; +; @cproto BS3_DECL(void) Bs3PrintChr_c16(char ch); +; +BS3_PROC_BEGIN_CMN Bs3PrintChr, BS3_PBC_NEAR + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push xAX + push xCX + push xBX + +%if TMPL_BITS == 16 + ; If we're in real mode or v8086 mode, call the VGA BIOS directly. + mov bl, [g_bBs3CurrentMode] + cmp bl, BS3_MODE_RM + je .do_vga_bios_call + %if 0 + test bl, BS3_MODE_CODE_V86 + jz .do_system_call + %else + jmp .do_system_call + %endif + +.do_vga_bios_call: + mov al, [xBP + xCB*2] ; Load the char + cmp al, 0ah ; \n + je .newline + mov bx, 0ff00h + mov ah, 0eh + int 10h + jmp .return +.newline: + mov ax, 0e0dh ; cmd + '\r'. + mov bx, 0ff00h + int 10h + mov ax, 0e0ah ; cmd + '\n'. + mov bx, 0ff00h + int 10h + jmp .return +%endif + +.do_system_call: + mov cl, [xBP + xCB*2] ; Load the char + mov ax, BS3_SYSCALL_PRINT_CHR + call Bs3Syscall ; near! no BS3_CALL! + +.return: + pop xBX + pop xCX + pop xAX + pop xBP + BS3_CALL_CONV_EPILOG 1 + ret +BS3_PROC_END_CMN Bs3PrintChr + +; +; Generate 16-bit far stub. +; Peformance critical, so don't penalize near calls. +; +BS3_CMN_FAR_STUB Bs3PrintChr, 2 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PrintStr.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PrintStr.c new file mode 100644 index 00000000..34c7c6de --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PrintStr.c @@ -0,0 +1,44 @@ +/* $Id: bs3-cmn-PrintStr.c $ */ +/** @file + * BS3Kit - Bs3PrintStr + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "bs3kit-template-header.h" + +#undef Bs3PrintStr +BS3_CMN_DEF(void, Bs3PrintStr,(const char BS3_FAR *pszString)) +{ + Bs3PrintStrN(pszString, Bs3StrLen(pszString)); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PrintStrN.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PrintStrN.asm new file mode 100644 index 00000000..a6684f42 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PrintStrN.asm @@ -0,0 +1,204 @@ +; $Id: bs3-cmn-PrintStrN.asm $ +;; @file +; BS3Kit - Bs3PrintStrN. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +BS3_EXTERN_CMN Bs3Syscall + + +TMPL_BEGIN_TEXT + +;; +; @cproto BS3_DECL(void) Bs3PrintStrN_c16(const char BS3_FAR *pszString, size_t cchString); +; +; ASSUMES cchString < 64KB! +; +BS3_PROC_BEGIN_CMN Bs3PrintStrN, BS3_PBC_NEAR + BS3_CALL_CONV_PROLOG 2 + push xBP + mov xBP, xSP + push xAX + push xCX + push xBX + push xSI + +%if TMPL_BITS == 16 + ; If we're in real mode or v8086 mode, call the VGA BIOS directly. + mov bl, [g_bBs3CurrentMode] + cmp bl, BS3_MODE_RM + je .do_bios_call + %if 0 + test bl, BS3_MODE_CODE_V86 + jz .do_system_call + %else + jmp .do_system_call + %endif + + ; + ; We can do the work right here. + ; +.do_bios_call: + push ds + lds si, [xBP + xCB + cbCurRetAddr] ; DS:SI -> string. + cld + mov cx, [xBP + xCB + cbCurRetAddr + sCB] ; Use CX for counting down. + call Bs3PrintStrN_c16_CX_Bytes_At_DS_SI + pop ds + jmp .return +%endif + + + ; + ; Need to do system call(s). + ; String goes into CX:xSI, count into DX. + ; + ; We must ensure the string is real-mode addressable first, if not we + ; must do it char-by-char. + ; +.do_system_call: +%if TMPL_BITS == 16 + mov cx, [xBP + xCB + cbCurRetAddr + 2] +%else + mov cx, ds +%endif + mov xSI, [xBP + xCB + cbCurRetAddr] + mov dx, [xBP + xCB + cbCurRetAddr + sCB] +%if TMPL_BITS == 16 + +%else + cmp xSI, _1M + jae .char_by_char +%endif + mov ax, BS3_SYSCALL_PRINT_STR + call Bs3Syscall ; near! no BS3_CALL! + +.return: + pop xSI + pop xBX + pop xCX + pop xAX + pop xBP + BS3_CALL_CONV_EPILOG 2 + BS3_HYBRID_RET + + ; + ; Doesn't look like it's real-mode addressable. So, char-by-char. + ; +.char_by_char: +%if TMPL_BITS == 16 + push es + mov es, cx +%endif + cld + test dx, dx + jz .char_by_char_return +.char_by_char_loop: + mov ax, BS3_SYSCALL_PRINT_CHR + mov cl, [BS3_ONLY_16BIT(es:) xSI] + call Bs3Syscall ; near! no BS3_CALL! + inc xSI + dec xDX + jnz .char_by_char_loop +.char_by_char_return: +%if TMPL_BITS == 16 + pop es +%endif + jmp .return + +BS3_PROC_END_CMN Bs3PrintStrN + + +%if TMPL_BITS == 16 +; +; This code is shared with the system handler. +; +; @param CX Number of byte sto print. +; @param DS:SI The string to print +; @uses AX, BX, CX, SI +; +BS3_PROC_BEGIN Bs3PrintStrN_c16_CX_Bytes_At_DS_SI + CPU 8086 + ; Check if CX is zero first. + test cx, cx + jz .bios_loop_done + + ; The loop, processing the string char-by-char. +.bios_loop: + mov bx, 0ff00h + lodsb ; al = next char + cmp al, 0ah ; \n + je .bios_loop_newline +%ifdef BS3_STRICT + test al, al + jnz .not_zero + hlt +.not_zero: +%endif + mov ah, 0eh +.bios_loop_int10h: + int 10h + loop .bios_loop +.bios_loop_done: + ret + +.bios_loop_newline: + mov ax, 0e0dh ; cmd + '\r'. + int 10h + mov ax, 0e0ah ; cmd + '\n'. + mov bx, 0ff00h + jmp .bios_loop_int10h +BS3_PROC_END Bs3PrintStrN_c16_CX_Bytes_At_DS_SI + + +; +; Generate 16-bit far stub. +; Peformance critical, so don't penalize near calls. +; +BS3_CMN_FAR_STUB Bs3PrintStrN, 6 + +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PrintU32.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PrintU32.asm new file mode 100644 index 00000000..fa50ff2b --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PrintU32.asm @@ -0,0 +1,93 @@ +; $Id: bs3-cmn-PrintU32.asm $ +;; @file +; BS3Kit - Bs3PrintU32, Common. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3PrintStr + +;; +; Prints a 32-bit unsigned integer value. +; +; @param [xBP + xCB*2] 32-bit value to format and print. +; +BS3_PROC_BEGIN_CMN Bs3PrintU32, BS3_PBC_HYBRID + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push sAX + push sDX + push sCX + push sBX +BONLY16 push ds + + mov eax, [xBP + xCB + cbCurRetAddr] + + ; Allocate a stack buffer and terminate it. ds:bx points ot the end. + sub xSP, 30h +BONLY16 mov bx, ss +BONLY16 mov ds, bx + mov xBX, xSP + add xBX, 2fh + mov byte [xBX], 0 + + mov ecx, 10 ; what to divide by +.next: + xor edx, edx + div ecx ; edx:eax / ecx -> eax and rest in edx. + add dl, '0' + dec xBX + mov [BS3_ONLY_16BIT(ss:)xBX], dl + cmp eax, 0 + jnz .next + + ; Print the string. +BONLY64 add rsp, 18h +BONLY16 push ss + push xBX + BS3_CALL Bs3PrintStr, 1 + + add xSP, 30h + BS3_IF_16_32_64BIT(2, 0, 18h) + xCB +BONLY16 pop ds + pop sBX + pop sCX + pop sDX + pop sAX + leave + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3PrintU32 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PrintX32.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PrintX32.asm new file mode 100644 index 00000000..a07eb375 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-PrintX32.asm @@ -0,0 +1,97 @@ +; $Id: bs3-cmn-PrintX32.asm $ +;; @file +; BS3Kit - Bs3PrintU32, Common. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3PrintStr + +;; +; Prints a 32-bit unsigned integer value as hex. +; +; @param [xBP + xCB*2] 32-bit value to format and print. +; +BS3_PROC_BEGIN_CMN Bs3PrintX32, BS3_PBC_HYBRID + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push sAX + push sDX + push sCX + push sBX +BONLY16 push ds + + mov eax, [xBP + xCB + cbCurRetAddr] + + ; Allocate a stack buffer and terminate it. ds:bx points ot the end. + sub xSP, 30h +BONLY16 mov bx, ss +BONLY16 mov ds, bx + mov xBX, xSP + add xBX, 2fh + mov byte [xBX], 0 + + mov ecx, 16 ; what to divide by +.next: + xor edx, edx + div ecx ; edx:eax / ecx -> eax and rest in edx. + cmp dl, 10 + jb .decimal + add dl, 'a' - '0' - 10 +.decimal: + add dl, '0' + dec xBX + mov [BS3_ONLY_16BIT(ss:)xBX], dl + cmp eax, 0 + jnz .next + + ; Print the string. +BONLY64 add rsp, 18h +BONLY16 push ss + push xBX + BS3_CALL Bs3PrintStr, 1 + + add xSP, 30h + BS3_IF_16_32_64BIT(2, 0, 18h) + xCB +BONLY16 pop ds + pop sBX + pop sCX + pop sDX + pop sAX + leave + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3PrintX32 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Printf.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Printf.c new file mode 100644 index 00000000..610417dd --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Printf.c @@ -0,0 +1,95 @@ +/* $Id: bs3-cmn-Printf.c $ */ +/** @file + * BS3Kit - Bs3Printf, Bs3PrintfV + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include <iprt/ctype.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Output buffering for Bs3TestPrintfV. */ +typedef struct BS3PRINTBUF +{ + uint8_t cchBuf; + char achBuf[79]; +} BS3PRINTBUF; + + +static BS3_DECL_CALLBACK(size_t) bs3PrintFmtOutput(char ch, void BS3_FAR *pvUser) +{ + BS3PRINTBUF BS3_FAR *pBuf = (BS3PRINTBUF BS3_FAR *)pvUser; + if (ch != '\0') + { + BS3_ASSERT(pBuf->cchBuf < RT_ELEMENTS(pBuf->achBuf)); + pBuf->achBuf[pBuf->cchBuf++] = ch; + + /* Whether to flush the buffer. We do line flushing here to avoid + dropping too much info when the formatter crashes on bad input. */ + if ( pBuf->cchBuf < RT_ELEMENTS(pBuf->achBuf) + && ch != '\n') + return 1; + } + Bs3PrintStrN(&pBuf->achBuf[0], pBuf->cchBuf); + pBuf->cchBuf = 0; + return ch != '\0'; +} + + +#undef Bs3PrintfV +BS3_CMN_DEF(size_t, Bs3PrintfV,(const char BS3_FAR *pszFormat, va_list BS3_FAR va)) +{ + BS3PRINTBUF Buf; + Buf.cchBuf = 0; + return Bs3StrFormatV(pszFormat, va, bs3PrintFmtOutput, &Buf); +} + + +#undef Bs3Printf +BS3_CMN_DEF(size_t, Bs3Printf,(const char BS3_FAR *pszFormat, ...)) +{ + size_t cchRet; + va_list va; + va_start(va, pszFormat); + cchRet = BS3_CMN_NM(Bs3PrintfV)(pszFormat, va); + va_end(va); + return cchRet; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxConvertToRingX.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxConvertToRingX.c new file mode 100644 index 00000000..79b8a569 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxConvertToRingX.c @@ -0,0 +1,182 @@ +/* $Id: bs3-cmn-RegCtxConvertToRingX.c $ */ +/** @file + * BS3Kit - Bs3RegCtxConvertToRingX + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +/** + * Transforms a real mode segment into a protected mode selector. + * + * @returns Protected mode selector. + * @param uSeg The real mode segment. + * @param bRing The target ring. + */ +static uint16_t bs3RegCtxConvertRealSegToRingX(uint16_t uSeg, uint8_t bRing) +{ + uint16_t uSel; + if ( uSeg == 0 + || uSeg == BS3_SEL_R0_SS16) + uSel = BS3_SEL_R0_SS16 + ((uint16_t)bRing << BS3_SEL_RING_SHIFT); + else if ( uSeg == (BS3_ADDR_BS3TEXT16 >> 4) + || uSeg == BS3_SEL_R0_CS16) + uSel = BS3_SEL_R0_CS16 + ((uint16_t)bRing << BS3_SEL_RING_SHIFT); + else if ( uSeg == (BS3_ADDR_BS3DATA16 >> 4) + || uSeg == BS3_SEL_R0_DS16) + uSel = BS3_SEL_R0_DS16 + ((uint16_t)bRing << BS3_SEL_RING_SHIFT); + else if (uSeg == (BS3_ADDR_BS3SYSTEM16 >> 4)) + uSel = BS3_SEL_SYSTEM16; + else if (!(uSeg & 0xfff)) + uSel = (uSeg >> (12 - X86_SEL_SHIFT)) + BS3_SEL_TILED; + else if (uSeg == BS3_SEL_R0_DS16) + uSel = (uSeg >> (12 - X86_SEL_SHIFT)) + BS3_SEL_TILED; + else + { + Bs3Printf("uSeg=%#x\n", uSeg); + BS3_ASSERT(0); + return 0; + } + uSel |= bRing; + return uSel; +} + + +/** + * Transforms a protected mode selector to a different ring. + * + * @returns Adjusted protected mode selector. + * @param uSel The current selector value. + * @param bRing The target ring. + * @param iReg Register index. + */ +static uint16_t bs3RegCtxConvertProtSelToRingX(uint16_t uSel, uint8_t bRing, uint8_t iReg) +{ + if ( uSel > X86_SEL_RPL + && !(uSel & X86_SEL_LDT) ) + { + if (uSel >= BS3_SEL_R0_FIRST && uSel < BS3_SEL_R0_FIRST + (5 << BS3_SEL_RING_SHIFT)) + { + /* Convert BS3_SEL_R*_XXX to the target ring. */ + uSel &= BS3_SEL_RING_SUB_MASK; + uSel |= bRing; + uSel += BS3_SEL_R0_FIRST; + uSel += (uint16_t)bRing << BS3_SEL_RING_SHIFT; + } + else + { + /* Convert TEXT16 and DATA16 to BS3_SEL_R*_XXX. */ + uint16_t const uSelRaw = uSel & X86_SEL_MASK_OFF_RPL; + if (uSelRaw == BS3_SEL_TEXT16) + uSel = (BS3_SEL_R0_CS16 | bRing) + ((uint16_t)bRing << BS3_SEL_RING_SHIFT); + else if (uSelRaw == BS3_SEL_DATA16) + uSel = (BS3_SEL_R0_DS16 | bRing) + ((uint16_t)bRing << BS3_SEL_RING_SHIFT); + /* CS and SS must have CPL == DPL. So, convert to standard selectors as we're + usually here because Bs3SwitchToRing0 was called to get out of a test situation. */ + else if (iReg == X86_SREG_CS || iReg == X86_SREG_SS) + { + if ( Bs3Gdt[uSel >> X86_SEL_SHIFT].Gen.u1Long + && BS3_MODE_IS_64BIT_SYS(g_bBs3CurrentMode) ) + uSel = iReg == X86_SREG_CS ? BS3_SEL_R0_CS64 : BS3_SEL_R0_DS64; + else + { + uint32_t uFlat = Bs3SelFar32ToFlat32(0, uSel); + bool fDefBig = Bs3Gdt[uSel >> X86_SEL_SHIFT].Gen.u1DefBig; + if (!fDefBig && uFlat == BS3_ADDR_BS3TEXT16 && iReg == X86_SREG_CS) + uSel = BS3_SEL_R0_CS16; + else if (!fDefBig && uFlat == 0 && iReg == X86_SREG_SS) + uSel = BS3_SEL_R0_SS16; + else if (fDefBig && uFlat == 0) + uSel = iReg == X86_SREG_CS ? BS3_SEL_R0_CS32 : BS3_SEL_R0_SS32; + else + { + Bs3Printf("uSel=%#x iReg=%d\n", uSel, iReg); + BS3_ASSERT(0); + return uSel; + } + uSel |= bRing; + uSel += (uint16_t)bRing << BS3_SEL_RING_SHIFT; + } + } + /* Adjust the RPL on tiled and MMIO selectors. */ + else if ( uSelRaw == BS3_SEL_VMMDEV_MMIO16 + || uSelRaw >= BS3_SEL_TILED) + uSel = uSelRaw | bRing; + } + } + return uSel; +} + + +/** + * Transforms a register context to a different ring. + * + * @param pRegCtx The register context. + * @param bRing The target ring (0..3). + * + * @note Do _NOT_ call this for creating real mode or v8086 contexts, because + * it will always output a protected mode context! + */ +#undef Bs3RegCtxConvertToRingX +BS3_CMN_DEF(void, Bs3RegCtxConvertToRingX,(PBS3REGCTX pRegCtx, uint8_t bRing)) +{ + if ( (pRegCtx->rflags.u32 & X86_EFL_VM) + || pRegCtx->bMode == BS3_MODE_RM) + { + pRegCtx->rflags.u32 &= ~X86_EFL_VM; + pRegCtx->bMode &= ~BS3_MODE_CODE_MASK; + pRegCtx->bMode |= BS3_MODE_CODE_16; + pRegCtx->cs = bs3RegCtxConvertRealSegToRingX(pRegCtx->cs, bRing); + pRegCtx->ss = bs3RegCtxConvertRealSegToRingX(pRegCtx->ss, bRing); + pRegCtx->ds = bs3RegCtxConvertRealSegToRingX(pRegCtx->ds, bRing); + pRegCtx->es = bs3RegCtxConvertRealSegToRingX(pRegCtx->es, bRing); + pRegCtx->fs = bs3RegCtxConvertRealSegToRingX(pRegCtx->fs, bRing); + pRegCtx->gs = bs3RegCtxConvertRealSegToRingX(pRegCtx->gs, bRing); + } + else + { + pRegCtx->cs = bs3RegCtxConvertProtSelToRingX(pRegCtx->cs, bRing, X86_SREG_CS); + pRegCtx->ss = bs3RegCtxConvertProtSelToRingX(pRegCtx->ss, bRing, X86_SREG_SS); + pRegCtx->ds = bs3RegCtxConvertProtSelToRingX(pRegCtx->ds, bRing, X86_SREG_DS); + pRegCtx->es = bs3RegCtxConvertProtSelToRingX(pRegCtx->es, bRing, X86_SREG_ES); + pRegCtx->fs = bs3RegCtxConvertProtSelToRingX(pRegCtx->fs, bRing, X86_SREG_FS); + pRegCtx->gs = bs3RegCtxConvertProtSelToRingX(pRegCtx->gs, bRing, X86_SREG_GS); + } + pRegCtx->bCpl = bRing; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxConvertV86ToRm.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxConvertV86ToRm.c new file mode 100644 index 00000000..ec75a104 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxConvertV86ToRm.c @@ -0,0 +1,55 @@ +/* $Id: bs3-cmn-RegCtxConvertV86ToRm.c $ */ +/** @file + * BS3Kit - Bs3RegCtxConvertV86ToRm + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3RegCtxConvertV86ToRm +BS3_CMN_DEF(void, Bs3RegCtxConvertV86ToRm,(PBS3REGCTX pRegCtx)) +{ + BS3_ASSERT(BS3_MODE_IS_V86(pRegCtx->bMode)); + + pRegCtx->cr0.u32 &= ~(X86_CR0_PE | X86_CR0_PG); + pRegCtx->rflags.u32 &= ~X86_EFL_VM; + pRegCtx->fbFlags |= BS3REG_CTX_F_NO_TR_LDTR | BS3REG_CTX_F_NO_AMD64; + pRegCtx->bCpl = 0; + pRegCtx->bMode = BS3_MODE_RM; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxGetRspSsAsCurPtr.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxGetRspSsAsCurPtr.c new file mode 100644 index 00000000..f6a5fe44 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxGetRspSsAsCurPtr.c @@ -0,0 +1,71 @@ +/* $Id: bs3-cmn-RegCtxGetRspSsAsCurPtr.c $ */ +/** @file + * BS3Kit - Bs3RegCtxGetRspSsAsCurPtr + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3RegCtxGetRspSsAsCurPtr +BS3_CMN_DEF(void BS3_FAR *, Bs3RegCtxGetRspSsAsCurPtr,(PBS3REGCTX pRegCtx)) +{ + uint64_t uFlat; + if (BS3_MODE_IS_RM_OR_V86(pRegCtx->bMode)) + { +#if ARCH_BITS == 16 + if (BS3_MODE_IS_RM_OR_V86(g_bBs3CurrentMode)) + return BS3_FP_MAKE(pRegCtx->ss, pRegCtx->rsp.u16); +#endif + uFlat = ((uint32_t)pRegCtx->ss << 4) + pRegCtx->rsp.u16; + } + else if (!BS3_MODE_IS_64BIT_CODE(pRegCtx->bMode)) + uFlat = Bs3SelFar32ToFlat32(pRegCtx->rsp.u32, pRegCtx->ss); + else + uFlat = pRegCtx->rsp.u64; + +#if ARCH_BITS == 16 + if (uFlat >= (BS3_MODE_IS_RM_OR_V86(g_bBs3CurrentMode) ? _1M : BS3_SEL_TILED_AREA_SIZE)) + return NULL; + return BS3_FP_MAKE(Bs3Sel16HighFlatPtrToSelector((uint32_t)uFlat >> 16), (uint16_t)uFlat); +#else + /* Typically no need to check limit in 32-bit mode, because 64-bit mode + just repeats the first 4GB for the rest of the address space. */ + return (void *)(uintptr_t)uFlat; +#endif +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxPrint.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxPrint.c new file mode 100644 index 00000000..cbae2628 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxPrint.c @@ -0,0 +1,77 @@ +/* $Id: bs3-cmn-RegCtxPrint.c $ */ +/** @file + * BS3Kit - Bs3RegCtxPrint + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3RegCtxPrint +BS3_CMN_DEF(void, Bs3RegCtxPrint,(PCBS3REGCTX pRegCtx)) +{ + if (!BS3_MODE_IS_64BIT_CODE(pRegCtx->bMode)) + { + Bs3TestPrintf("eax=%08RX32 ebx=%08RX32 ecx=%08RX32 edx=%08RX32 esi=%08RX32 edi=%08RX32\n", + pRegCtx->rax.u32, pRegCtx->rbx.u32, pRegCtx->rcx.u32, pRegCtx->rdx.u32, pRegCtx->rsi.u32, pRegCtx->rdi.u32); + Bs3TestPrintf("eip=%08RX32 esp=%08RX32 ebp=%08RX32 efl=%08RX32 cr0=%08RX32 cr2=%08RX32\n", + pRegCtx->rip.u32, pRegCtx->rsp.u32, pRegCtx->rbp.u32, pRegCtx->rflags.u32, + pRegCtx->cr0.u32, pRegCtx->cr2.u32); + Bs3TestPrintf("cs=%04RX16 ds=%04RX16 es=%04RX16 fs=%04RX16 gs=%04RX16 ss=%04RX16 cr3=%08RX32 cr4=%08RX32\n", + pRegCtx->cs, pRegCtx->ds, pRegCtx->es, pRegCtx->fs, pRegCtx->gs, pRegCtx->ss, + pRegCtx->cr3.u32, pRegCtx->cr4.u32); + } + else + { + Bs3TestPrintf("rax=%016RX64 rbx=%016RX64 rcx=%016RX64 rdx=%016RX64\n", + pRegCtx->rax.u64, pRegCtx->rbx.u64, pRegCtx->rcx.u64, pRegCtx->rdx.u64); + Bs3TestPrintf("rsi=%016RX64 rdi=%016RX64 r8 =%016RX64 r9 =%016RX64\n", + pRegCtx->rsi.u64, pRegCtx->rdi.u64, pRegCtx->r8.u64, pRegCtx->r9.u64); + Bs3TestPrintf("r10=%016RX64 r11=%016RX64 r12=%016RX64 r13=%016RX64\n", + pRegCtx->r10.u64, pRegCtx->r11.u64, pRegCtx->r12.u64, pRegCtx->r13.u64); + Bs3TestPrintf("r14=%016RX64 r15=%016RX64 cr0=%08RX64 cr4=%08RX64 cr3=%08RX64\n", + pRegCtx->r14.u64, pRegCtx->r15.u64, pRegCtx->cr0.u64, pRegCtx->cr4.u64, pRegCtx->cr3.u64); + Bs3TestPrintf("rip=%016RX64 rsp=%016RX64 rbp=%016RX64 rfl=%08RX64\n", + pRegCtx->rip.u64, pRegCtx->rsp.u64, pRegCtx->rbp.u64, pRegCtx->rflags.u32); + Bs3TestPrintf("cs=%04RX16 ds=%04RX16 es=%04RX16 fs=%04RX16 gs=%04RX16 ss=%04RX16 cr2=%016RX64\n", + pRegCtx->cs, pRegCtx->ds, pRegCtx->es, pRegCtx->fs, pRegCtx->gs, pRegCtx->ss, + pRegCtx->cr3.u64, pRegCtx->cr2.u64); + } + Bs3TestPrintf("tr=%04RX16 ldtr=%04RX16 cpl=%d mode=%#x fbFlags=%#x\n", + pRegCtx->tr, pRegCtx->ldtr, pRegCtx->bCpl, pRegCtx->bMode, pRegCtx->fbFlags); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxRestore.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxRestore.asm new file mode 100644 index 00000000..546ddee1 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxRestore.asm @@ -0,0 +1,608 @@ +; $Id: bs3-cmn-RegCtxRestore.asm $ +;; @file +; BS3Kit - Bs3RegCtxRestore. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_SYSTEM16 Bs3Gdt +BS3_EXTERN_DATA16 g_bBs3CurrentMode +BS3_EXTERN_DATA16 g_fBs3TrapNoV86Assist +%if TMPL_BITS != 64 +BS3_EXTERN_DATA16 g_uBs3CpuDetected +%endif +TMPL_BEGIN_TEXT +BS3_EXTERN_CMN Bs3Syscall +BS3_EXTERN_CMN Bs3Panic +TMPL_BEGIN_TEXT + + +;; +; Restores the given register context. +; +; @param pRegCtx +; @param fFlags +; @uses All registers and may trash stack immediately before the resume point. +; +; @note Only respects the BS3_MODE_CODE_MASK part of pRegCtx->bMode. +; +%if TMPL_BITS == 16 +BS3_PROC_BEGIN_CMN Bs3RegCtxRestore_aborts, BS3_PBC_FAR ; special entry point for when watcom applies __aborts +BS3_PROC_BEGIN_CMN Bs3RegCtxRestore_aborts, BS3_PBC_NEAR ; special entry point for when watcom applies __aborts + CPU 8086 + xor xAX, xAX + push xAX ; fake return address. + push xAX + jmp _Bs3RegCtxRestore_f16 +%elif TMPL_BITS == 32 +BS3_PROC_BEGIN_CMN Bs3RegCtxRestore_aborts, BS3_PBC_NEAR ; special entry point for when watcom applies __aborts + push 0feedfaceh ; fake return address. +%endif +BS3_PROC_BEGIN_CMN Bs3RegCtxRestore, BS3_PBC_HYBRID + BS3_CALL_CONV_PROLOG 2 + push xBP + mov xBP, xSP + + ; + ; If we're not in ring-0, ask the kernel to restore it for us (quicker + ; and less problematic if we're in a funny context right now with weird + ; CS or SS values). + ; +%if TMPL_BITS == 16 + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .in_ring0 + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .do_syscall_restore_ctx +%endif + mov ax, ss + test al, 3 + jz .in_ring0 + +.do_syscall_restore_ctx: +%if TMPL_BITS != 16 +.do_syscall_restore_ctx_restore_ds: + mov cx, ds + mov xSI, [xBP + xCB*2] + movzx edx, word [xBP + xCB*3] + mov eax, BS3_SYSCALL_RESTORE_CTX +%else + mov si, [bp + xCB + cbCurRetAddr] + mov cx, [bp + xCB + cbCurRetAddr + 2] + mov dx, [bp + xCB + cbCurRetAddr + sCB] + mov ax, BS3_SYSCALL_RESTORE_CTX +%endif + call Bs3Syscall + call Bs3Panic + +%if TMPL_BITS == 16 +.do_syscall_restore_ctx_restore_ds: + push es + pop ds + jmp .do_syscall_restore_ctx +%endif + + ; + ; Prologue. Loads ES with BS3KIT_GRPNM_DATA16/FLAT (for g_bBs3CurrentMode + ; and g_uBs3CpuDetected), DS:xBX with pRegCtx and fFlags into xCX. + ; +.in_ring0: +%if TMPL_BITS == 16 + mov ax, BS3_SEL_DATA16 + mov es, ax + lds bx, [bp + xCB + cbCurRetAddr] + mov cx, [bp + xCB + cbCurRetAddr + sCB] +%elif TMPL_BITS == 32 + mov ax, BS3_SEL_R0_DS32 + mov ds, ax + mov xBX, [xBP + xCB*2] + movzx xCX, word [xBP + xCB*3] +%else + mov ax, BS3_SEL_R0_DS64 + mov ds, ax + mov xBX, [xBP + xCB*2] + movzx xCX, word [xBP + xCB*3] +%endif + + +%if TMPL_BITS != 64 + ; Restoring a 64-bit context is best done from 64-bit code. + mov al, [xBX + BS3REGCTX.bMode] + test al, BS3_MODE_CODE_64 + jnz .do_syscall_restore_ctx_restore_ds +%endif + + ; The remainder must be done with interrupts disabled. + cli + + ; + ; Update g_bs3CurrentMode. + ; +%if TMPL_BITS == 64 + mov al, [xBX + BS3REGCTX.bMode] +%endif + and al, BS3_MODE_CODE_MASK + mov ah, [BS3_ONLY_16BIT(es:) BS3_DATA16_WRT(g_bBs3CurrentMode)] + and ah, ~BS3_MODE_CODE_MASK + or al, ah + mov [BS3_ONLY_16BIT(es:) BS3_DATA16_WRT(g_bBs3CurrentMode)], al + + ; + ; Set g_fBs3TrapNoV86Assist if BS3REGCTXRESTORE_F_NO_V86_ASSIST specified. + ; + test cl, BS3REGCTXRESTORE_F_NO_V86_ASSIST + jz .no_f_no_v86_assist + mov byte [BS3_ONLY_16BIT(es:) BS3_DATA16_WRT(g_fBs3TrapNoV86Assist)], 1 +.no_f_no_v86_assist: + +%if TMPL_BITS == 16 + ; + ; Check what the CPU can do. + ; + cmp byte [es:BS3_DATA16_WRT(g_uBs3CpuDetected)], BS3CPU_80386 + jae .restore_full + + ; Do the 80286 specifics first. + cmp byte [es:BS3_DATA16_WRT(g_uBs3CpuDetected)], BS3CPU_80286 + jb .restore_16_bit_ancient + CPU 286 + + lmsw [bx + BS3REGCTX.cr0] + cmp byte [es:BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .restore_16_bit_ancient + lldt [bx + BS3REGCTX.ldtr] + + ; TR - complicated because we need to clear the busy bit. ASSUMES GDT. + str ax + cmp ax, [bx + BS3REGCTX.tr] + je .skip_tr_286 + + mov di, word [xBX + BS3REGCTX.tr] + or di, di ; check for null. + jz .load_tr_286 + + push ds + push BS3_SEL_SYSTEM16 + pop ds + add di, Bs3Gdt wrt BS3SYSTEM16 + add di, X86DESCGENERIC_BIT_OFF_TYPE / 8 + and byte [di], ~(X86_SEL_TYPE_SYS_TSS_BUSY_MASK << (X86DESCGENERIC_BIT_OFF_TYPE % 8)) + pop ds + +.load_tr_286: + ltr [bx + BS3REGCTX.tr] +.skip_tr_286: + +.restore_16_bit_ancient: + CPU 8086 + ; Some general registers. + mov cx, [bx + BS3REGCTX.rcx] + mov dx, [bx + BS3REGCTX.rdx] + + ; Do the return frame and final registers (keep short as we're not quite + ; NMI safe here if pRegCtx is on the stack). + cmp byte [es:BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + mov di, [bx + BS3REGCTX.rsp] + je .restore_16_bit_same_privilege + cmp byte [bx + BS3REGCTX.bCpl], 0 + je .restore_16_bit_same_privilege + + mov ax, [bx + BS3REGCTX.ss] + push ax + mov ax, [bx + BS3REGCTX.rsp] + push ax + mov ax, [bx + BS3REGCTX.rflags] + push ax + mov ax, [bx + BS3REGCTX.cs] + push ax + mov ax, [bx + BS3REGCTX.rip] + push ax + mov ax, [bx + BS3REGCTX.ds] + push ax + + mov si, [bx + BS3REGCTX.rsi] + mov di, [bx + BS3REGCTX.rdi] + mov es, [bx + BS3REGCTX.es] + mov ax, [bx + BS3REGCTX.rax] + mov bp, [bx + BS3REGCTX.rbp] ; restore late for better stacks. + mov bx, [bx + BS3REGCTX.rbx] + + pop ds + iret + +.restore_16_bit_same_privilege: + sub di, 2*5 ; iret frame + pop ds + mov si, di + mov es, [bx + BS3REGCTX.ss] ; ES is target stack segment. + cld + + mov ax, [bx + BS3REGCTX.ds] + stosw + mov ax, [bx + BS3REGCTX.rbp] ; Restore esp as late as possible for better stacks. + stosw + mov ax, [bx + BS3REGCTX.rip] + stosw + mov ax, [bx + BS3REGCTX.cs] + stosw + mov ax, [bx + BS3REGCTX.rflags] + stosw + + mov di, [bx + BS3REGCTX.rdi] + mov es, [bx + BS3REGCTX.es] + mov ax, [bx + BS3REGCTX.rax] + mov ss, [bx + BS3REGCTX.ss] + mov sp, si + mov si, [bx + BS3REGCTX.rsi] + mov bx, [bx + BS3REGCTX.rbx] + + pop ds + pop bp + iret + + CPU 386 +%endif + +.restore_full: + ; + ; 80386 or later. + ; For 32-bit and 16-bit versions, we always use 32-bit iret. + ; + + ; Restore control registers if they've changed. + test cl, BS3REGCTXRESTORE_F_SKIP_CRX + jnz .skip_control_regs + test byte [xBX + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_CR0_IS_MSW | BS3REG_CTX_F_NO_CR2_CR3 + jnz .skip_control_regs + + test byte [xBX + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_CR4 ; (old 486s and 386s didn't have CR4) + jnz .skip_cr4 +%if TMPL_BITS != 64 + test word [BS3_ONLY_16BIT(es:) BS3_DATA16_WRT(g_uBs3CpuDetected)], BS3CPU_F_CPUID + jz .skip_cr4 +%endif + mov sAX, [xBX + BS3REGCTX.cr4] + mov sDX, cr4 + cmp sAX, sDX + je .skip_cr4 + mov cr4, sAX +.skip_cr4: + + mov sAX, [xBX + BS3REGCTX.cr0] + mov sDX, cr0 + cmp sAX, sDX + je .skip_cr0 + mov cr0, sAX +.skip_cr0: + + mov sAX, [xBX + BS3REGCTX.cr3] + mov sDX, cr3 + cmp sAX, sDX + je .skip_cr3 + mov cr3, sAX +.skip_cr3: + + mov sAX, [xBX + BS3REGCTX.cr2] + mov sDX, cr2 + cmp sAX, sDX + je .skip_cr2 + mov cr2, sAX +.skip_cr2: + + ; + ; Restore + ; +%if TMPL_BITS != 64 + ; We cannot restore ldtr and tr if we're in real-mode. + cmp byte [BS3_ONLY_16BIT(es:) BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .skip_control_regs +%endif + test byte [xBX + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_TR_LDTR + jnz .skip_control_regs + + ; LDTR + sldt ax + cmp ax, [xBX + BS3REGCTX.ldtr] + je .skip_ldtr + lldt [xBX + BS3REGCTX.ldtr] +.skip_ldtr: + + ; TR - complicated because we need to clear the busy bit. ASSUMES GDT. + str ax + cmp ax, [xBX + BS3REGCTX.tr] + je .skip_tr + + movzx edi, word [xBX + BS3REGCTX.tr] + or edi, edi ; check for null. + jz .load_tr + +%if TMPL_BITS == 16 + push ds + push BS3_SEL_SYSTEM16 + pop ds + add xDI, Bs3Gdt wrt BS3SYSTEM16 +%else + add xDI, Bs3Gdt wrt FLAT +%endif + add xDI, X86DESCGENERIC_BIT_OFF_TYPE / 8 + and byte [xDI], ~(X86_SEL_TYPE_SYS_TSS_BUSY_MASK << (X86DESCGENERIC_BIT_OFF_TYPE % 8)) +%if TMPL_BITS == 16 + pop ds +%endif +.load_tr: + ltr [xBX + BS3REGCTX.tr] +.skip_tr: + +.skip_control_regs: + + +%if TMPL_BITS == 64 + ; + ; 64-bit returns are simple because ss:rsp are always restored. + ; + ; A small complication here when returning to a 16-bit stack (only + ; applicable to 16-bit and 32-bit code), iret doesn't touch the high + ; ESP bits and we can easily later end up with trap handlers + ; accessing memory never intended as stack. + ; + mov rcx, qword [xBX + BS3REGCTX.rsp] ; (also 1st param for conv call below) + cmp rcx, 0ffffh + ja .iretq_maybe_annoying_16bit_stack + cmp rsp, 0ffffh + ja .iretq_maybe_annoying_16bit_stack +.iretq_ok: + + movzx eax, word [xBX + BS3REGCTX.ss] + push rax + push qword [xBX + BS3REGCTX.rsp] + push qword [xBX + BS3REGCTX.rflags] + movzx eax, word [xBX + BS3REGCTX.cs] + push rax + push qword [xBX + BS3REGCTX.rip] + +.iretq_restore_regs_and_iret: + mov es, [xBX + BS3REGCTX.es] + mov fs, [xBX + BS3REGCTX.fs] + mov gs, [xBX + BS3REGCTX.gs] + mov rax, [xBX + BS3REGCTX.rax] + mov rdx, [xBX + BS3REGCTX.rdx] + mov rcx, [xBX + BS3REGCTX.rcx] + mov rsi, [xBX + BS3REGCTX.rsi] + mov rdi, [xBX + BS3REGCTX.rdi] + mov r8, [xBX + BS3REGCTX.r8] + mov r9, [xBX + BS3REGCTX.r9] + mov r10, [xBX + BS3REGCTX.r10] + mov r11, [xBX + BS3REGCTX.r11] + mov r12, [xBX + BS3REGCTX.r12] + mov r13, [xBX + BS3REGCTX.r13] + mov r14, [xBX + BS3REGCTX.r14] + mov r15, [xBX + BS3REGCTX.r15] + mov rbp, [xBX + BS3REGCTX.rbp] ; restore late for better stacks. + mov ds, [xBX + BS3REGCTX.ds] + mov rbx, [xBX + BS3REGCTX.rbx] + iretq + +.iretq_maybe_annoying_16bit_stack: + movzx edx, word [xBX + BS3REGCTX.ss] ; (also 2nd param for conv call below) + lar eax, dx + jnz .iretq_ok + test eax, X86LAR_F_D | X86LAR_F_L + jnz .iretq_ok ; Returning to a big of long SS needs not extra work. + + lar eax, word [xBX + BS3REGCTX.cs] + jnz .iretq_ok + test eax, X86LAR_F_L + jnz .iretq_ok ; It doesn't matter when returning to 64-bit code. + + ; Convert ss:sp to a flat address. + BS3_EXTERN_CMN Bs3SelFar32ToFlat32NoClobber + call Bs3SelFar32ToFlat32NoClobber + mov rdi, rax + + ; 2nd return frame (32-bit, same CPL). + mov eax, [xBX + BS3REGCTX.rflags] + mov [rdi - 4], eax + movzx eax, word [xBX + BS3REGCTX.cs] + mov [rdi - 8], eax + mov eax, [xBX + BS3REGCTX.rip] + mov [rdi - 12], eax + mov ecx, [xBX + BS3REGCTX.rsp] + sub cx, 12 + mov [rdi - 16], ecx + + ; 1st return frame. + movzx eax, word [xBX + BS3REGCTX.ss] + push rax ; new 16-bit SS + sub cx, 4 + push rcx ; new esp + mov rax, [xBX + BS3REGCTX.rflags] + and rax, ~(X86_EFL_NT | X86_EFL_TF) + push rax ; rflags + AssertCompile(BS3_SEL_RING_SHIFT == 8) + mov eax, BS3_SEL_R0_CS32 + add ah, [xBX + BS3REGCTX.bCpl] + or al, [xBX + BS3REGCTX.bCpl] + push rax ; 32-bit CS + push .iretq_pop_real_esp_and_iret_again wrt FLAT + jmp .iretq_restore_regs_and_iret + + BS3_SET_BITS 32 +.iretq_pop_real_esp_and_iret_again: + pop esp + iretd + BS3_SET_BITS 64 + +%else + ; + ; 32-bit/16-bit is more complicated as we have three different iret frames. + ; + mov al, [BS3_ONLY_16BIT(es:) BS3_DATA16_WRT(g_bBs3CurrentMode)] + cmp al, BS3_MODE_RM + je .iretd_same_cpl_rm + + test dword [xBX + BS3REGCTX.rflags], X86_EFL_VM + jnz .restore_v8086 + + cmp byte [xBX + BS3REGCTX.bCpl], 0 + je .iretd_same_cpl + + ; + ; IRETD to different CPL. Frame includes ss:esp. + ; +.iretd_different_cpl: + or eax, 0ffffffffh ; poison unused parts of segment pushes + mov ax, [xBX + BS3REGCTX.ss] + push eax + push dword [xBX + BS3REGCTX.rsp] + push dword [xBX + BS3REGCTX.rflags] + mov ax, [xBX + BS3REGCTX.cs] + push eax + push dword [xBX + BS3REGCTX.rip] + push dword [xBX + BS3REGCTX.rbp] ; Restore esp as late as possible for better stacks. + mov ax, [xBX + BS3REGCTX.ds] + push xAX + + mov es, [xBX + BS3REGCTX.es] + mov fs, [xBX + BS3REGCTX.fs] + mov gs, [xBX + BS3REGCTX.gs] + mov eax, [xBX + BS3REGCTX.rax] + mov edx, [xBX + BS3REGCTX.rdx] + mov ecx, [xBX + BS3REGCTX.rcx] + mov esi, [xBX + BS3REGCTX.rsi] + %if TMPL_BITS == 16 ; if SS is 16-bit, we will not be able to restore the high word. +;; @todo 16-bit stack will also mess us up in 32-bit code, so this needs fixing (see 64-bit above). + mov edi, [xBX + BS3REGCTX.rsp] + mov di, sp + mov esp, edi + %endif + mov edi, [xBX + BS3REGCTX.rdi] + mov ebx, [xBX + BS3REGCTX.rbx] + + pop ds + pop ebp + iretd + + ; + ; IRETD to same CPL (includes real mode). + ; +.iretd_same_cpl_rm: + ; Use STOSD/ES:EDI to create the frame. + mov es, [xBX + BS3REGCTX.ss] + mov esi, [xBX + BS3REGCTX.rsp] + sub esi, 5*4 + movzx edi, si + jmp .es_edi_is_pointing_to_return_frame_location + +.iretd_same_cpl: + ; Use STOSD/ES:EDI to create the frame. + mov es, [xBX + BS3REGCTX.ss] + mov edi, [xBX + BS3REGCTX.rsp] + sub edi, 5*4 + + ; Which part of the stack pointer is actually used depends on the SS.D/B bit. + lar eax, [xBX + BS3REGCTX.ss] + jnz .using_32_bit_stack_pointer + test eax, X86LAR_F_D + jnz .using_32_bit_stack_pointer +.using_16_bit_stack_pointer: + mov esi, edi ; save rsp for later. + movzx edi, di + jmp .es_edi_is_pointing_to_return_frame_location +.using_32_bit_stack_pointer: + mov esi, edi +.es_edi_is_pointing_to_return_frame_location: + cld + mov ax, [xBX + BS3REGCTX.ds] + o32 stosd + mov eax, [xBX + BS3REGCTX.rbp] ; Restore esp as late as possible for better stacks. + o32 stosd + mov eax, [xBX + BS3REGCTX.rip] + o32 stosd + mov ax, [xBX + BS3REGCTX.cs] + o32 stosd + mov eax, [xBX + BS3REGCTX.rflags] + o32 stosd + + mov es, [xBX + BS3REGCTX.es] + mov fs, [xBX + BS3REGCTX.fs] + mov gs, [xBX + BS3REGCTX.gs] + mov eax, [xBX + BS3REGCTX.rax] + mov edx, [xBX + BS3REGCTX.rdx] + mov ecx, [xBX + BS3REGCTX.rcx] + mov edi, [xBX + BS3REGCTX.rdi] + mov ebp, [xBX + BS3REGCTX.rbp] ; restore late for better stacks. + + mov ss, [xBX + BS3REGCTX.ss] + mov esp, esi + mov esi, [xBX + BS3REGCTX.rsi] + mov ebx, [xBX + BS3REGCTX.rbx] + + o32 pop ds + pop ebp + iretd + + ; + ; IRETD to v8086 mode. Frame includes ss:esp and the 4 data segment registers. + ; +.restore_v8086: + ; Create the return frame. + or eax, 0ffffffffh ; poison unused parts of segment pushes + mov eax, [xBX + BS3REGCTX.gs] + push eax + mov eax, [xBX + BS3REGCTX.fs] + push eax + mov eax, [xBX + BS3REGCTX.ds] + push eax + mov eax, [xBX + BS3REGCTX.es] + push eax + mov eax, [xBX + BS3REGCTX.ss] + push eax + push dword [xBX + BS3REGCTX.rsp] + push dword [xBX + BS3REGCTX.rflags] + mov ax, [xBX + BS3REGCTX.cs] + push eax + push dword [xBX + BS3REGCTX.rip] + + ; Load registers. + mov eax, [xBX + BS3REGCTX.rax] + mov edx, [xBX + BS3REGCTX.rdx] + mov ecx, [xBX + BS3REGCTX.rcx] + mov esi, [xBX + BS3REGCTX.rsi] + mov edi, [xBX + BS3REGCTX.rdi] + mov ebp, [xBX + BS3REGCTX.rbp] ; restore late for better stacks. + mov ebx, [xBX + BS3REGCTX.rbx] + + iretd +%endif +BS3_PROC_END_CMN Bs3RegCtxRestore + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSave.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSave.asm new file mode 100644 index 00000000..59b5f281 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSave.asm @@ -0,0 +1,271 @@ +; $Id: bs3-cmn-RegCtxSave.asm $ +;; @file +; BS3Kit - Bs3RegCtxSave. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_SYSTEM16 Bs3Gdt +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%if TMPL_BITS != 64 +BS3_EXTERN_DATA16 g_uBs3CpuDetected +%endif +TMPL_BEGIN_TEXT + + + +;; +; Saves the current register context. +; +; @param pRegCtx +; @uses None. +; +BS3_PROC_BEGIN_CMN Bs3RegCtxSave, BS3_PBC_HYBRID_SAFE +TONLY16 CPU 8086 + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + xPUSHF ; xBP - xCB*1: save the incoming flags exactly. + push xAX ; xBP - xCB*2: save incoming xAX + push xCX ; xBP - xCB*3: save incoming xCX + push xDI ; xBP - xCB*4: save incoming xDI +BONLY16 push es ; xBP - xCB*5 +BONLY16 push ds ; xBP - xCB*6 + + ; + ; Clear the whole structure first. + ; + xor xAX, xAX + cld + AssertCompileSizeAlignment(BS3REGCTX, 4) +%if TMPL_BITS == 16 + les xDI, [xBP + xCB + cbCurRetAddr] + mov xCX, BS3REGCTX_size / 2 + rep stosw +%else + mov xDI, [xBP + xCB + cbCurRetAddr] + mov xCX, BS3REGCTX_size / 4 + rep stosd +%endif + mov xDI, [xBP + xCB + cbCurRetAddr] + + ; + ; Save the current mode. + ; + mov cl, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + mov [BS3_ONLY_16BIT(es:) xDI + BS3REGCTX.bMode], cl +%if TMPL_BITS == 16 + + ; + ; In 16-bit mode we could be running on really ancient CPUs, so check + ; mode and detected CPU and proceed with care. + ; + cmp cl, BS3_MODE_PP16 + jae .save_full + + mov cl, [BS3_DATA16_WRT(g_uBs3CpuDetected)] + cmp cl, BS3CPU_80386 + jae .save_full + + ; load ES into DS so we can save some segment prefix bytes. + push es + pop ds + + ; 16-bit GPRs not on the stack. + mov [xDI + BS3REGCTX.rdx], dx + mov [xDI + BS3REGCTX.rbx], bx + mov [xDI + BS3REGCTX.rsi], si + + ; Join the common code. + cmp cl, BS3CPU_80286 + jb .common_ancient + CPU 286 + smsw [xDI + BS3REGCTX.cr0] + + mov cl, [xDI + BS3REGCTX.bMode] ; assumed by jump destination + jmp .common_80286 + + CPU 386 +%endif + + +.save_full: + ; + ; 80386 or later. + ; +%if TMPL_BITS != 64 + ; Check for CR4 here while we've got a working DS in all contexts. + test byte [1 + BS3_DATA16_WRT(g_uBs3CpuDetected)], (BS3CPU_F_CPUID >> 8) + jnz .save_full_have_cr4 + or byte [BS3_ONLY_16BIT(es:) xDI + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_CR4 +.save_full_have_cr4: +%endif +%if TMPL_BITS == 16 + ; Load es into ds so we can save ourselves some segment prefix bytes. + push es + pop ds +%endif + + ; GPRs first. + mov [xDI + BS3REGCTX.rdx], sDX + mov [xDI + BS3REGCTX.rbx], sBX + mov [xDI + BS3REGCTX.rsi], sSI +%if TMPL_BITS == 64 + mov [xDI + BS3REGCTX.r8], r8 + mov [xDI + BS3REGCTX.r9], r9 + mov [xDI + BS3REGCTX.r10], r10 + mov [xDI + BS3REGCTX.r11], r11 + mov [xDI + BS3REGCTX.r12], r12 + mov [xDI + BS3REGCTX.r13], r13 + mov [xDI + BS3REGCTX.r14], r14 + mov [xDI + BS3REGCTX.r15], r15 +%else + or byte [xDI + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_AMD64 +%endif +%if TMPL_BITS == 16 ; Save high bits. + mov [xDI + BS3REGCTX.rax], eax + mov [xDI + BS3REGCTX.rcx], ecx + mov [xDI + BS3REGCTX.rdi], edi + mov [xDI + BS3REGCTX.rbp], ebp + mov [xDI + BS3REGCTX.rsp], esp + pushfd + pop dword [xDI + BS3REGCTX.rflags] +%endif +%if TMPL_BITS != 64 + ; The VM flag is never on the stack, so derive it from the bMode we saved above. + test byte [xDI + BS3REGCTX.bMode], BS3_MODE_CODE_V86 + jz .not_v8086 + or byte [xDI + BS3REGCTX.rflags + 2], X86_EFL_VM >> 16 + mov byte [xDI + BS3REGCTX.bCpl], 3 +.not_v8086: +%endif + + ; 386 segment registers. + mov [xDI + BS3REGCTX.fs], fs + mov [xDI + BS3REGCTX.gs], gs + +%if TMPL_BITS == 16 ; v8086 and real mode woes. + mov cl, [xDI + BS3REGCTX.bMode] + cmp cl, BS3_MODE_RM + je .common_full_control_regs + test cl, BS3_MODE_CODE_V86 + jnz .common_full_no_control_regs +%endif + mov ax, ss + test al, 3 + jnz .common_full_no_control_regs + + ; Control registers (ring-0 and real-mode only). +.common_full_control_regs: + mov sAX, cr0 + mov [xDI + BS3REGCTX.cr0], sAX + mov sAX, cr2 + mov [xDI + BS3REGCTX.cr2], sAX + mov sAX, cr3 + mov [xDI + BS3REGCTX.cr3], sAX +%if TMPL_BITS != 64 + test byte [xDI + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_CR4 + jnz .common_80286 +%endif + mov sAX, cr4 + mov [xDI + BS3REGCTX.cr4], sAX + jmp .common_80286 + +.common_full_no_control_regs: + or byte [xDI + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_CR0_IS_MSW | BS3REG_CTX_F_NO_CR2_CR3 | BS3REG_CTX_F_NO_CR4 + smsw [xDI + BS3REGCTX.cr0] + + ; 80286 control registers. +.common_80286: +TONLY16 CPU 286 +%if TMPL_BITS != 64 + cmp cl, BS3_MODE_RM + je .no_str_sldt + test cl, BS3_MODE_CODE_V86 + jnz .no_str_sldt +%endif + str [xDI + BS3REGCTX.tr] + sldt [xDI + BS3REGCTX.ldtr] + jmp .common_ancient + +.no_str_sldt: + or byte [xDI + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_TR_LDTR + + ; Common stuff - stuff on the stack, 286 segment registers. +.common_ancient: +TONLY16 CPU 8086 + mov xAX, [xBP - xCB*1] + mov [xDI + BS3REGCTX.rflags], xAX + mov xAX, [xBP - xCB*2] + mov [xDI + BS3REGCTX.rax], xAX + mov xAX, [xBP - xCB*3] + mov [xDI + BS3REGCTX.rcx], xAX + mov xAX, [xBP - xCB*4] + mov [xDI + BS3REGCTX.rdi], xAX + mov xAX, [xBP] + mov [xDI + BS3REGCTX.rbp], xAX + mov xAX, [xBP + xCB] + mov [xDI + BS3REGCTX.rip], xAX + lea xAX, [xBP + xCB + cbCurRetAddr] + mov [xDI + BS3REGCTX.rsp], xAX + +%if TMPL_BITS == 16 + mov ax, [xBP + xCB + 2] + mov [xDI + BS3REGCTX.cs], ax + mov ax, [xBP - xCB*6] + mov [xDI + BS3REGCTX.ds], ax + mov ax, [xBP - xCB*5] + mov [xDI + BS3REGCTX.es], ax +%else + mov [xDI + BS3REGCTX.cs], cs + mov [xDI + BS3REGCTX.ds], ds + mov [xDI + BS3REGCTX.es], es +%endif + mov [xDI + BS3REGCTX.ss], ss + + ; + ; Return. + ; +.return: +BONLY16 pop ds +BONLY16 pop es + pop xDI + pop xCX + pop xAX + xPOPF + pop xBP + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegCtxSave + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSaveEx.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSaveEx.asm new file mode 100644 index 00000000..7b3bc676 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSaveEx.asm @@ -0,0 +1,460 @@ +; $Id: bs3-cmn-RegCtxSaveEx.asm $ +;; @file +; BS3Kit - Bs3RegCtxSaveEx. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%if ARCH_BITS != 64 +BS3_EXTERN_DATA16 g_uBs3CpuDetected +%endif + +TMPL_BEGIN_TEXT +BS3_EXTERN_CMN Bs3Panic +BS3_EXTERN_CMN Bs3RegCtxSave +BS3_EXTERN_CMN Bs3SwitchTo16Bit +%if TMPL_BITS != 64 +BS3_EXTERN_CMN Bs3SwitchTo16BitV86 +%endif +%if TMPL_BITS != 32 +BS3_EXTERN_CMN Bs3SwitchTo32Bit +%endif +%if TMPL_BITS != 64 +BS3_EXTERN_CMN Bs3SwitchTo64Bit +%endif +%if TMPL_BITS == 16 +BS3_EXTERN_CMN Bs3SelRealModeDataToProtFar16 +BS3_EXTERN_CMN Bs3SelProtFar16DataToRealMode +BS3_EXTERN_CMN Bs3SelRealModeDataToFlat +BS3_EXTERN_CMN Bs3SelProtFar16DataToFlat +%else +BS3_EXTERN_CMN Bs3SelFlatDataToProtFar16 +%endif +%if TMPL_BITS == 32 +BS3_EXTERN_CMN Bs3SelFlatDataToRealMode +%endif + +BS3_BEGIN_TEXT16 +%if TMPL_BITS != 16 +extern _Bs3RegCtxSave_c16 +extern _Bs3SwitchTo%[TMPL_BITS]Bit_c16 +%endif + +BS3_BEGIN_TEXT32 +%if TMPL_BITS != 32 +extern _Bs3RegCtxSave_c32 +extern _Bs3SwitchTo%[TMPL_BITS]Bit_c32 +%endif +%if TMPL_BITS == 16 +extern _Bs3SwitchTo16BitV86_c32 +%endif + +BS3_BEGIN_TEXT64 +%if TMPL_BITS != 64 +extern _Bs3RegCtxSave_c64 +extern _Bs3SwitchTo%[TMPL_BITS]Bit_c64 +%endif + +TMPL_BEGIN_TEXT + + + +;; +; Saves the current register context. +; +; @param pRegCtx +; @param bBitMode (8) +; @param cbExtraStack (16) +; @uses xAX, xDX, xCX +; +BS3_PROC_BEGIN_CMN Bs3RegCtxSaveEx, BS3_PBC_NEAR ; Far stub generated by the makefile/bs3kit.h. +TONLY16 CPU 8086 + BS3_CALL_CONV_PROLOG 3 + push xBP + mov xBP, xSP +%if ARCH_BITS == 64 + push rcx ; Save pRegCtx +%endif + + ; + ; Get the CPU bitcount part of the current mode. + ; + mov dl, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + and dl, BS3_MODE_CODE_MASK +%if TMPL_BITS == 16 + push dx ; bp - 2: previous CPU mode (16-bit) +%endif + + ; + ; Reserve extra stack space. Make sure we've got 20h here in case we + ; are saving a 64-bit context. + ; +TONLY16 mov ax, [xBP + xCB + cbCurRetAddr + sCB + xCB] +TNOT16 movzx eax, word [xBP + xCB + cbCurRetAddr + sCB + xCB] +%ifdef BS3_STRICT + cmp xAX, 4096 + jb .extra_stack_ok + call Bs3Panic +.extra_stack_ok: +%endif + cmp xAX, 20h + jae .at_least_20h_extra_stack + add xAX, 20h +.at_least_20h_extra_stack: + sub xSP, xAX + + ; + ; Are we just saving the mode we're already in? + ; + mov al, [xBP + xCB + cbCurRetAddr + sCB] + and al, BS3_MODE_CODE_MASK + cmp dl, al + jne .not_the_same_mode + +%if TMPL_BITS == 16 + push word [xBP + xCB + cbCurRetAddr + 2] + push word [xBP + xCB + cbCurRetAddr] +%elif TMPL_BITS == 32 + push dword [xBP + xCB + cbCurRetAddr] +%endif + call Bs3RegCtxSave ; 64-bit: rcx is untouched thus far. + + + ; + ; Return - no need to pop xAX and xDX as the last two + ; operations preserves all registers. + ; +.return: + mov xSP, xBP + pop xBP + BS3_CALL_CONV_EPILOG 3 + BS3_HYBRID_RET + + ; + ; Turns out we have to do switch to a different bitcount before saving. + ; +.not_the_same_mode: + cmp al, BS3_MODE_CODE_16 + je .code_16 + +TONLY16 CPU 386 +%if TMPL_BITS != 32 + cmp al, BS3_MODE_CODE_32 + je .code_32 +%endif +%if TMPL_BITS != 64 + cmp al, BS3_MODE_CODE_V86 + je .code_v86 + cmp al, BS3_MODE_CODE_64 + jne .bad_input_mode + jmp .code_64 +%endif + + ; Bad input (al=input, dl=current). +.bad_input_mode: + call Bs3Panic + + + ; + ; Save a 16-bit context. + ; + ; Convert pRegCtx to 16:16 protected mode and make sure we're in the + ; 16-bit code segment. + ; +.code_16: +%if TMPL_BITS == 16 + %ifdef BS3_STRICT + cmp dl, BS3_MODE_CODE_V86 + jne .bad_input_mode + %endif + push word [xBP + xCB + cbCurRetAddr + 2] + push word [xBP + xCB + cbCurRetAddr] + call Bs3SelRealModeDataToProtFar16 + add sp, 4h + push dx ; Parameter #0 for _Bs3RegCtxSave_c16 + push ax +%else + %if TMPL_BITS == 32 + push dword [xBP + xCB + cbCurRetAddr] + %endif + call Bs3SelFlatDataToProtFar16 ; 64-bit: BS3_CALL not needed, ecx not touched thus far. + mov [xSP], eax ; Parameter #0 for _Bs3RegCtxSave_c16 + jmp .code_16_safe_segment + BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +.code_16_safe_segment: +%endif + call Bs3SwitchTo16Bit + BS3_SET_BITS 16 + + call _Bs3RegCtxSave_c16 + +%if TMPL_BITS == 16 + call _Bs3SwitchTo16BitV86_c16 +%else + call _Bs3SwitchTo%[TMPL_BITS]Bit_c16 +%endif + BS3_SET_BITS TMPL_BITS + jmp .supplement_and_return + TMPL_BEGIN_TEXT + +TONLY16 CPU 386 + + +%if TMPL_BITS != 64 + ; + ; Save a v8086 context. + ; +.code_v86: + %if TMPL_BITS == 16 + %ifdef BS3_STRICT + cmp dl, BS3_MODE_CODE_16 + jne .bad_input_mode + %endif + push word [xBP + xCB + cbCurRetAddr + 2] + push word [xBP + xCB + cbCurRetAddr] + call Bs3SelProtFar16DataToRealMode + add sp, 4h + push dx ; Parameter #0 for _Bs3RegCtxSave_c16 + push ax + %else + push dword [xBP + xCB + cbCurRetAddr] + call Bs3SelFlatDataToRealMode + mov [xSP], eax ; Parameter #0 for _Bs3RegCtxSave_c16 + jmp .code_v86_safe_segment + BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +.code_v86_safe_segment: + %endif + call Bs3SwitchTo16BitV86 + BS3_SET_BITS 16 + + call _Bs3RegCtxSave_c16 + + call _Bs3SwitchTo%[TMPL_BITS]Bit_c16 + BS3_SET_BITS TMPL_BITS + jmp .supplement_and_return +TMPL_BEGIN_TEXT +%endif + + +%if TMPL_BITS != 32 + ; + ; Save a 32-bit context. + ; +.code_32: + %if TMPL_BITS == 16 + push word [xBP + xCB + cbCurRetAddr + 2] + push word [xBP + xCB + cbCurRetAddr] + test dl, BS3_MODE_CODE_V86 + jnz .code_32_from_v86 + call Bs3SelProtFar16DataToFlat + jmp .code_32_flat_ptr +.code_32_from_v86: + call Bs3SelRealModeDataToFlat +.code_32_flat_ptr: + add sp, 4h + push dx ; Parameter #0 for _Bs3RegCtxSave_c32 + push ax + %else + mov [rsp], ecx ; Parameter #0 for _Bs3RegCtxSave_c16 + %endif + call Bs3SwitchTo32Bit + BS3_SET_BITS 32 + + call _Bs3RegCtxSave_c32 + + %if TMPL_BITS == 16 + cmp byte [bp - 2], BS3_MODE_CODE_V86 + je .code_32_back_to_v86 + call _Bs3SwitchTo16Bit_c32 + BS3_SET_BITS TMPL_BITS + jmp .supplement_and_return +.code_32_back_to_v86: + BS3_SET_BITS 32 + call _Bs3SwitchTo16BitV86_c32 + BS3_SET_BITS TMPL_BITS + jmp .return + %else + call _Bs3SwitchTo64Bit_c32 + BS3_SET_BITS TMPL_BITS + jmp .supplement_and_return + %endif +%endif + + +%if TMPL_BITS != 64 + ; + ; Save a 64-bit context. + ; + CPU x86-64 +.code_64: + %if TMPL_BITS == 16 + %ifdef BS3_STRICT + cmp dl, BS3_MODE_CODE_16 + jne .bad_input_mode + %endif + push word [xBP + xCB + cbCurRetAddr + 2] + push word [xBP + xCB + cbCurRetAddr] + call Bs3SelProtFar16DataToFlat + add sp, 4h + mov cx, dx ; Parameter #0 for _Bs3RegCtxSave_c64 + shl ecx, 16 + mov cx, ax + %else + mov ecx, [xBP + xCB + cbCurRetAddr] ; Parameter #0 for _Bs3RegCtxSave_c64 + %endif + call Bs3SwitchTo64Bit ; (preserves all 32-bit GPRs) + BS3_SET_BITS 64 + + call _Bs3RegCtxSave_c64 ; No BS3_CALL as rcx is already ready. + + call _Bs3SwitchTo%[TMPL_BITS]Bit_c64 + BS3_SET_BITS TMPL_BITS + jmp .return +%endif + + + ; + ; Supplement the state out of the current context and then return. + ; +.supplement_and_return: +%if ARCH_BITS == 16 + CPU 8086 + ; Skip 286 and older. Also make 101% sure we not in real mode or v8086 mode. + cmp byte [BS3_DATA16_WRT(g_uBs3CpuDetected)], BS3CPU_80386 + jb .return ; Just skip if 286 or older. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .return + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + jne .return ; paranoia + CPU 386 +%endif + + ; Load the context pointer into a suitable register. +%if ARCH_BITS == 64 + %define pRegCtx rcx + mov rcx, [xBP - xCB] +%elif ARCH_BITS == 32 + %define pRegCtx ecx + mov ecx, [xBP + xCB + cbCurRetAddr] +%else + %define pRegCtx es:bx + push es + push bx + les bx, [xBP + xCB + cbCurRetAddr] +%endif +%if ARCH_BITS == 64 + ; If we're in 64-bit mode we can capture and restore the high bits. + test byte [pRegCtx + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_AMD64 + jz .supplemented_64bit_registers + mov [pRegCtx + BS3REGCTX.r8], r8 + mov [pRegCtx + BS3REGCTX.r9], r9 + mov [pRegCtx + BS3REGCTX.r10], r10 + mov [pRegCtx + BS3REGCTX.r11], r11 + mov [pRegCtx + BS3REGCTX.r12], r12 + mov [pRegCtx + BS3REGCTX.r13], r13 + mov [pRegCtx + BS3REGCTX.r14], r14 + mov [pRegCtx + BS3REGCTX.r15], r15 + shr rax, 32 + mov [pRegCtx + BS3REGCTX.rax + 4], eax + mov rax, rbx + shr rax, 32 + mov [pRegCtx + BS3REGCTX.rbx + 4], eax + mov rax, rcx + shr rax, 32 + mov [pRegCtx + BS3REGCTX.rcx + 4], eax + mov rax, rdx + shr rax, 32 + mov [pRegCtx + BS3REGCTX.rdx + 4], eax + mov rax, rsp + shr rax, 32 + mov [pRegCtx + BS3REGCTX.rsp + 4], eax + mov rax, rbp + shr rax, 32 + mov [pRegCtx + BS3REGCTX.rbp + 4], eax + mov rax, rsi + shr rax, 32 + mov [pRegCtx + BS3REGCTX.rsi + 4], eax + mov rax, rdi + shr rax, 32 + mov [pRegCtx + BS3REGCTX.rdi + 4], eax + and byte [pRegCtx + BS3REGCTX.fbFlags], ~BS3REG_CTX_F_NO_AMD64 +.supplemented_64bit_registers: +%endif + ; The rest requires ring-0 (at least during restore). + mov ax, ss + test ax, 3 + jnz .done_supplementing + + ; Do control registers. + test byte [pRegCtx + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_CR2_CR3 | BS3REG_CTX_F_NO_CR0_IS_MSW | BS3REG_CTX_F_NO_CR4 + jz .supplemented_control_registers + mov sAX, cr0 + mov [pRegCtx + BS3REGCTX.cr0], sAX + mov sAX, cr2 + mov [pRegCtx + BS3REGCTX.cr2], sAX + mov sAX, cr3 + mov [pRegCtx + BS3REGCTX.cr3], sAX + and byte [pRegCtx + BS3REGCTX.fbFlags], ~(BS3REG_CTX_F_NO_CR2_CR3 | BS3REG_CTX_F_NO_CR0_IS_MSW) + +%if ARCH_BITS != 64 + test byte [1 + BS3_DATA16_WRT(g_uBs3CpuDetected)], (BS3CPU_F_CPUID >> 8) + jz .supplemented_control_registers +%endif + mov sAX, cr4 + mov [pRegCtx + BS3REGCTX.cr4], sAX + and byte [pRegCtx + BS3REGCTX.fbFlags], ~BS3REG_CTX_F_NO_CR4 +.supplemented_control_registers: + + ; Supply tr and ldtr if necessary + test byte [pRegCtx + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_TR_LDTR + jz .done_supplementing + str [pRegCtx + BS3REGCTX.tr] + sldt [pRegCtx + BS3REGCTX.ldtr] + and byte [pRegCtx + BS3REGCTX.fbFlags], ~BS3REG_CTX_F_NO_TR_LDTR + +.done_supplementing: +TONLY16 pop bx +TONLY16 pop es + jmp .return +%undef pRegCtx +BS3_PROC_END_CMN Bs3RegCtxSaveEx + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSaveForMode.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSaveForMode.c new file mode 100644 index 00000000..c16c8204 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSaveForMode.c @@ -0,0 +1,65 @@ +/* $Id: bs3-cmn-RegCtxSaveForMode.c $ */ +/** @file + * BS3Kit - Bs3RegCtxSaveForMode + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3RegCtxSaveForMode +BS3_CMN_DEF(void, Bs3RegCtxSaveForMode,(PBS3REGCTX pRegCtx, uint8_t bMode, uint16_t cbExtraStack)) +{ + if ( bMode != BS3_MODE_RM +#if ARCH_BIT == 16 + || g_bBs3CurrentMode == BS3_MODE_RM +#endif + ) + { + BS3_ASSERT((bMode & BS3_MODE_SYS_MASK) == (g_bBs3CurrentMode & BS3_MODE_SYS_MASK)); + Bs3RegCtxSaveEx(pRegCtx, bMode, cbExtraStack); + } + else + { +#if ARCH_BIT == 64 + BS3_ASSERT(0); /* No V86 mode in LM! */ +#endif + Bs3RegCtxSaveEx(pRegCtx, (bMode & ~BS3_MODE_CODE_MASK) | BS3_MODE_CODE_V86, cbExtraStack); + Bs3RegCtxConvertV86ToRm(pRegCtx); + } +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSetGpr.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSetGpr.c new file mode 100644 index 00000000..cb3f8a29 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSetGpr.c @@ -0,0 +1,64 @@ +/* $Id: bs3-cmn-RegCtxSetGpr.c $ */ +/** @file + * BS3Kit - Bs3RegCtxSetGpr + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3RegCtxSetGpr +BS3_CMN_DEF(bool, Bs3RegCtxSetGpr,(PBS3REGCTX pRegCtx, uint8_t iGpr, uint64_t uValue, uint8_t cb)) +{ + if (iGpr < 16) + { + PBS3REG pGpr = &pRegCtx->rax + iGpr; + switch (cb) + { + case 1: pGpr->u8 = (uint8_t)uValue; break; + case 2: pGpr->u16 = (uint16_t)uValue; break; + case 4: pGpr->u32 = (uint32_t)uValue; break; + case 8: pGpr->u64 = uValue; break; + default: + BS3_ASSERT(false); + return false; + } + return true; + } + return false; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSetGrpSegFromCurPtr.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSetGrpSegFromCurPtr.c new file mode 100644 index 00000000..b7802db3 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSetGrpSegFromCurPtr.c @@ -0,0 +1,60 @@ +/* $Id: bs3-cmn-RegCtxSetGrpSegFromCurPtr.c $ */ +/** @file + * BS3Kit - Bs3RegCtxSetGrpSegFromCurPtr, Bs3RegCtxSetGrpDsFromCurPtr + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3RegCtxSetGrpSegFromCurPtr +BS3_CMN_DEF(void, Bs3RegCtxSetGrpSegFromCurPtr,(PBS3REGCTX pRegCtx, PBS3REG pGpr, PRTSEL pSel, void BS3_FAR *pvPtr)) +{ +#if ARCH_BITS == 16 + Bs3RegCtxSetGrpSegFromFlat(pRegCtx, pGpr, pSel, Bs3SelPtrToFlat(pvPtr)); +#else + Bs3RegCtxSetGrpSegFromFlat(pRegCtx, pGpr, pSel, (uintptr_t)pvPtr); +#endif +} + + +#undef Bs3RegCtxSetGrpDsFromCurPtr +BS3_CMN_DEF(void, Bs3RegCtxSetGrpDsFromCurPtr,(PBS3REGCTX pRegCtx, PBS3REG pGpr, void BS3_FAR *pvPtr)) +{ + BS3_CMN_FAR_NM(Bs3RegCtxSetGrpSegFromCurPtr)(pRegCtx, pGpr, &pRegCtx->ds, pvPtr); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSetGrpSegFromFlat.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSetGrpSegFromFlat.c new file mode 100644 index 00000000..31e09ce8 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSetGrpSegFromFlat.c @@ -0,0 +1,75 @@ +/* $Id: bs3-cmn-RegCtxSetGrpSegFromFlat.c $ */ +/** @file + * BS3Kit - Bs3RegCtxSetGrpSegFromFlat + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3RegCtxSetGrpSegFromFlat +BS3_CMN_DEF(void, Bs3RegCtxSetGrpSegFromFlat,(PBS3REGCTX pRegCtx, PBS3REG pGpr, PRTSEL pSel, RTCCUINTXREG uFlat)) +{ + if (BS3_MODE_IS_16BIT_CODE(pRegCtx->bMode)) + { + uint32_t uFar1616; + if (BS3_MODE_IS_RM_OR_V86(pRegCtx->bMode)) + uFar1616 = Bs3SelFlatDataToRealMode(uFlat); + else + uFar1616 = Bs3SelFlatDataToProtFar16(uFlat); + pGpr->u = uFar1616 & UINT16_MAX; + *pSel = uFar1616 >> 16; + } + else + { + pGpr->u = uFlat; + if (BS3_MODE_IS_32BIT_CODE(pRegCtx->bMode)) + *pSel = BS3_SEL_R0_DS32; + else + *pSel = BS3_SEL_R0_DS64; + } + + /* Adjust CS to the right ring, if not ring-0 or V86 context. */ + if ( pRegCtx->bCpl != 0 + && !BS3_MODE_IS_RM_OR_V86(pRegCtx->bMode)) + { + if (BS3_SEL_IS_IN_R0_RANGE(*pSel)) + *pSel += (uint16_t)pRegCtx->bCpl << BS3_SEL_RING_SHIFT; + *pSel |= pRegCtx->bCpl; + } +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSetRipCsFromCurPtr.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSetRipCsFromCurPtr.c new file mode 100644 index 00000000..8ebb6bfb --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSetRipCsFromCurPtr.c @@ -0,0 +1,53 @@ +/* $Id: bs3-cmn-RegCtxSetRipCsFromCurPtr.c $ */ +/** @file + * BS3Kit - Bs3RegCtxSetRipCsFromCurPtr + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3RegCtxSetRipCsFromCurPtr +BS3_CMN_DEF(void, Bs3RegCtxSetRipCsFromCurPtr,(PBS3REGCTX pRegCtx, FPFNBS3FAR pfnCode)) +{ +#if ARCH_BITS == 16 + Bs3RegCtxSetRipCsFromFlat(pRegCtx, Bs3SelPtrToFlat((void BS3_FAR *)pfnCode)); +#else + Bs3RegCtxSetRipCsFromFlat(pRegCtx, (uintptr_t)pfnCode); +#endif +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSetRipCsFromFlat.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSetRipCsFromFlat.c new file mode 100644 index 00000000..8e70c7df --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSetRipCsFromFlat.c @@ -0,0 +1,75 @@ +/* $Id: bs3-cmn-RegCtxSetRipCsFromFlat.c $ */ +/** @file + * BS3Kit - Bs3RegCtxSetRipCsFromFlat + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3RegCtxSetRipCsFromFlat +BS3_CMN_DEF(void, Bs3RegCtxSetRipCsFromFlat,(PBS3REGCTX pRegCtx, RTCCUINTXREG uFlatCode)) +{ + if (BS3_MODE_IS_16BIT_CODE(pRegCtx->bMode)) + { + uint32_t uFar1616; + if (BS3_MODE_IS_RM_OR_V86(pRegCtx->bMode)) + uFar1616 = Bs3SelFlatCodeToRealMode(uFlatCode); + else + uFar1616 = Bs3SelFlatCodeToProtFar16(uFlatCode); + pRegCtx->rip.u = uFar1616 & UINT16_MAX; + pRegCtx->cs = uFar1616 >> 16; + } + else + { + pRegCtx->rip.u = uFlatCode; + if (BS3_MODE_IS_32BIT_CODE(pRegCtx->bMode)) + pRegCtx->cs = BS3_SEL_R0_CS32; + else + pRegCtx->cs = BS3_SEL_R0_CS64; + } + + /* Adjust CS to the right ring, if not ring-0 or V86 context. */ + if ( pRegCtx->bCpl != 0 + && !BS3_MODE_IS_RM_OR_V86(pRegCtx->bMode) + && BS3_SEL_IS_IN_R0_RANGE(pRegCtx->cs)) + { + pRegCtx->cs += (uint16_t)pRegCtx->bCpl << BS3_SEL_RING_SHIFT; + pRegCtx->cs |= pRegCtx->bCpl; + } +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSetRipCsFromLnkPtr.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSetRipCsFromLnkPtr.c new file mode 100644 index 00000000..e6d3a978 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegCtxSetRipCsFromLnkPtr.c @@ -0,0 +1,87 @@ +/* $Id: bs3-cmn-RegCtxSetRipCsFromLnkPtr.c $ */ +/** @file + * BS3Kit - Bs3RegCtxSetRipCsFromLnkPtr + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3RegCtxSetRipCsFromLnkPtr +BS3_CMN_DEF(void, Bs3RegCtxSetRipCsFromLnkPtr,(PBS3REGCTX pRegCtx, FPFNBS3FAR pfnCode)) +{ + if (BS3_MODE_IS_16BIT_CODE(pRegCtx->bMode)) + { +#if ARCH_BITS == 16 + pRegCtx->rip.u = BS3_FP_OFF(pfnCode); + if (BS3_MODE_IS_RM_OR_V86(pRegCtx->bMode)) + pRegCtx->cs = BS3_FP_SEG(pfnCode); + else + pRegCtx->cs = Bs3SelRealModeCodeToProtMode(BS3_FP_SEG(pfnCode)); +#else + uint32_t uFar1616; + if (BS3_MODE_IS_RM_OR_V86(pRegCtx->bMode)) + uFar1616 = Bs3SelFlatCodeToRealMode((uint32_t)(uintptr_t)pfnCode); + else + uFar1616 = Bs3SelFlatCodeToProtFar16((uint32_t)(uintptr_t)pfnCode); + pRegCtx->rip.u = uFar1616 & UINT16_MAX; + pRegCtx->cs = uFar1616 >> 16; +#endif + } + else + { +#if ARCH_BITS == 16 + pRegCtx->rip.u = Bs3SelRealModeCodeToFlat(pfnCode); +#else + pRegCtx->rip.u = (uintptr_t)pfnCode; +#endif + if (BS3_MODE_IS_32BIT_CODE(pRegCtx->bMode)) + pRegCtx->cs = BS3_SEL_R0_CS32; + else + pRegCtx->cs = BS3_SEL_R0_CS64; + } + + /* Adjust CS to the right ring, if not ring-0 or V86 context. */ + if ( pRegCtx->bCpl != 0 + && !BS3_MODE_IS_RM_OR_V86(pRegCtx->bMode) + && BS3_SEL_IS_IN_R0_RANGE(pRegCtx->cs)) + { + pRegCtx->cs += (uint16_t)pRegCtx->bCpl << BS3_SEL_RING_SHIFT; + pRegCtx->cs |= pRegCtx->bCpl; + } +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetCr0.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetCr0.asm new file mode 100644 index 00000000..593ade35 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetCr0.asm @@ -0,0 +1,89 @@ +; $Id: bs3-cmn-RegGetCr0.asm $ +;; @file +; BS3Kit - Bs3RegGetCr0 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Panic +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(RTCCUINTXREG, Bs3RegGetCr0,(void)); +; +; @returns Register value. +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs (only return full register(s)). +; +BS3_PROC_BEGIN_CMN Bs3RegGetCr0, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 0 + push xBP + mov xBP, xSP + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov ax, ss + and ax, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sAX, cr0 +TONLY16 mov edx, eax +TONLY16 shr edx, 16 + jmp .return + +.via_system_call: + mov xAX, BS3_SYSCALL_GET_CRX + mov dl, 0 + call Bs3Syscall + +.return: + pop xBP + BS3_CALL_CONV_EPILOG 0 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegGetCr0 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetCr2.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetCr2.asm new file mode 100644 index 00000000..520f732a --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetCr2.asm @@ -0,0 +1,89 @@ +; $Id: bs3-cmn-RegGetCr2.asm $ +;; @file +; BS3Kit - Bs3RegGetCr2 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Panic +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(RTCCUINTXREG, Bs3RegGetCr2,(void)); +; +; @returns Register value. +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs (only return full register(s)). +; +BS3_PROC_BEGIN_CMN Bs3RegGetCr2, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 0 + push xBP + mov xBP, xSP + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov ax, ss + and ax, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sAX, cr2 +TONLY16 mov edx, eax +TONLY16 shr edx, 16 + jmp .return + +.via_system_call: + mov xAX, BS3_SYSCALL_GET_CRX + mov dl, 2 + call Bs3Syscall + +.return: + pop xBP + BS3_CALL_CONV_EPILOG 0 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegGetCr2 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetCr3.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetCr3.asm new file mode 100644 index 00000000..3720bb2d --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetCr3.asm @@ -0,0 +1,89 @@ +; $Id: bs3-cmn-RegGetCr3.asm $ +;; @file +; BS3Kit - Bs3RegGetCr3 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Panic +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(RTCCUINTXREG, Bs3RegGetCr3,(void)); +; +; @returns Register value. +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs (only return full register(s)). +; +BS3_PROC_BEGIN_CMN Bs3RegGetCr3, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 0 + push xBP + mov xBP, xSP + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov ax, ss + and ax, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sAX, cr3 +TONLY16 mov edx, eax +TONLY16 shr edx, 16 + jmp .return + +.via_system_call: + mov xAX, BS3_SYSCALL_GET_CRX + mov dl, 3 + call Bs3Syscall + +.return: + pop xBP + BS3_CALL_CONV_EPILOG 0 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegGetCr3 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetCr4.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetCr4.asm new file mode 100644 index 00000000..2ed2e968 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetCr4.asm @@ -0,0 +1,89 @@ +; $Id: bs3-cmn-RegGetCr4.asm $ +;; @file +; BS3Kit - Bs3RegGetCr4 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Panic +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(RTCCUINTXREG, Bs3RegGetCr4,(void)); +; +; @returns Register value. +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs (only return full register(s)). +; +BS3_PROC_BEGIN_CMN Bs3RegGetCr4, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 0 + push xBP + mov xBP, xSP + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov ax, ss + and ax, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sAX, cr4 +TONLY16 mov edx, eax +TONLY16 shr edx, 16 + jmp .return + +.via_system_call: + mov xAX, BS3_SYSCALL_GET_CRX + mov dl, 4 + call Bs3Syscall + +.return: + pop xBP + BS3_CALL_CONV_EPILOG 0 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegGetCr4 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDr0.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDr0.asm new file mode 100644 index 00000000..c282f373 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDr0.asm @@ -0,0 +1,89 @@ +; $Id: bs3-cmn-RegGetDr0.asm $ +;; @file +; BS3Kit - Bs3RegGetDr0 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Panic +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(RTCCUINTXREG, Bs3RegGetDr0,(void)); +; +; @returns Register value. +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs (only return full register(s)). +; +BS3_PROC_BEGIN_CMN Bs3RegGetDr0, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 0 + push xBP + mov xBP, xSP + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov ax, ss + and ax, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sAX, dr0 +TONLY16 mov edx, eax +TONLY16 shr edx, 16 + jmp .return + +.via_system_call: + mov xAX, BS3_SYSCALL_GET_DRX + mov dl, 0 + call Bs3Syscall + +.return: + pop xBP + BS3_CALL_CONV_EPILOG 0 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegGetDr0 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDr1.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDr1.asm new file mode 100644 index 00000000..c0460f6b --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDr1.asm @@ -0,0 +1,89 @@ +; $Id: bs3-cmn-RegGetDr1.asm $ +;; @file +; BS3Kit - Bs3RegGetDr1 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Panic +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(RTCCUINTXREG, Bs3RegGetDr1,(void)); +; +; @returns Register value. +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs (only return full register(s)). +; +BS3_PROC_BEGIN_CMN Bs3RegGetDr1, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 0 + push xBP + mov xBP, xSP + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov ax, ss + and ax, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sAX, dr1 +TONLY16 mov edx, eax +TONLY16 shr edx, 16 + jmp .return + +.via_system_call: + mov xAX, BS3_SYSCALL_GET_DRX + mov dl, 1 + call Bs3Syscall + +.return: + pop xBP + BS3_CALL_CONV_EPILOG 0 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegGetDr1 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDr2.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDr2.asm new file mode 100644 index 00000000..79bc3d5e --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDr2.asm @@ -0,0 +1,89 @@ +; $Id: bs3-cmn-RegGetDr2.asm $ +;; @file +; BS3Kit - Bs3RegGetDr2 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Panic +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(RTCCUINTXREG, Bs3RegGetDr2,(void)); +; +; @returns Register value. +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs (only return full register(s)). +; +BS3_PROC_BEGIN_CMN Bs3RegGetDr2, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 0 + push xBP + mov xBP, xSP + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov ax, ss + and ax, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sAX, dr2 +TONLY16 mov edx, eax +TONLY16 shr edx, 16 + jmp .return + +.via_system_call: + mov xAX, BS3_SYSCALL_GET_DRX + mov dl, 2 + call Bs3Syscall + +.return: + pop xBP + BS3_CALL_CONV_EPILOG 0 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegGetDr2 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDr3.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDr3.asm new file mode 100644 index 00000000..8ad3fb07 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDr3.asm @@ -0,0 +1,89 @@ +; $Id: bs3-cmn-RegGetDr3.asm $ +;; @file +; BS3Kit - Bs3RegGetDr3 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Panic +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(RTCCUINTXREG, Bs3RegGetDr3,(void)); +; +; @returns Register value. +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs (only return full register(s)). +; +BS3_PROC_BEGIN_CMN Bs3RegGetDr3, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 0 + push xBP + mov xBP, xSP + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov ax, ss + and ax, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sAX, dr3 +TONLY16 mov edx, eax +TONLY16 shr edx, 16 + jmp .return + +.via_system_call: + mov xAX, BS3_SYSCALL_GET_DRX + mov dl, 3 + call Bs3Syscall + +.return: + pop xBP + BS3_CALL_CONV_EPILOG 0 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegGetDr3 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDr6.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDr6.asm new file mode 100644 index 00000000..cf5c64af --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDr6.asm @@ -0,0 +1,89 @@ +; $Id: bs3-cmn-RegGetDr6.asm $ +;; @file +; BS3Kit - Bs3RegGetDr6 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Panic +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(RTCCUINTXREG, Bs3RegGetDr6,(void)); +; +; @returns Register value. +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs (only return full register(s)). +; +BS3_PROC_BEGIN_CMN Bs3RegGetDr6, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 0 + push xBP + mov xBP, xSP + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov ax, ss + and ax, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sAX, dr6 +TONLY16 mov edx, eax +TONLY16 shr edx, 16 + jmp .return + +.via_system_call: + mov xAX, BS3_SYSCALL_GET_DRX + mov dl, 6 + call Bs3Syscall + +.return: + pop xBP + BS3_CALL_CONV_EPILOG 0 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegGetDr6 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDr7.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDr7.asm new file mode 100644 index 00000000..cf258ef5 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDr7.asm @@ -0,0 +1,89 @@ +; $Id: bs3-cmn-RegGetDr7.asm $ +;; @file +; BS3Kit - Bs3RegGetDr7 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Panic +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(RTCCUINTXREG, Bs3RegGetDr7,(void)); +; +; @returns Register value. +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs (only return full register(s)). +; +BS3_PROC_BEGIN_CMN Bs3RegGetDr7, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 0 + push xBP + mov xBP, xSP + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov ax, ss + and ax, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sAX, dr7 +TONLY16 mov edx, eax +TONLY16 shr edx, 16 + jmp .return + +.via_system_call: + mov xAX, BS3_SYSCALL_GET_DRX + mov dl, 7 + call Bs3Syscall + +.return: + pop xBP + BS3_CALL_CONV_EPILOG 0 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegGetDr7 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDrX.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDrX.asm new file mode 100644 index 00000000..eff57bf4 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetDrX.asm @@ -0,0 +1,135 @@ +; $Id: bs3-cmn-RegGetDrX.asm $ +;; @file +; BS3Kit - Bs3RegGetDrX +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Panic +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + +;TONLY16 CPU 386 + +;; +; @cproto BS3_CMN_PROTO_STUB(RTCCUINTXREG, Bs3RegGetDrX,(uint8_t iReg)); +; +; @returns Register value. +; @param iRegister The source register +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs (only return full register(s)). +; +BS3_PROC_BEGIN_CMN Bs3RegGetDrX, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov ax, ss + and ax, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + ; Switch (iRegister) + mov al, [xBP + xCB + cbCurRetAddr] + cmp al, 6 + jz .get_dr6 + cmp al, 7 + jz .get_dr7 + cmp al, 0 + jz .get_dr0 + cmp al, 1 + jz .get_dr1 + cmp al, 2 + jz .get_dr2 + cmp al, 3 + jz .get_dr3 + cmp al, 4 + jz .get_dr4 + cmp al, 5 + jz .get_dr5 + call Bs3Panic + +.get_dr0: + mov sAX, dr0 + jmp .return_fixup +.get_dr1: + mov sAX, dr1 + jmp .return_fixup +.get_dr2: + mov sAX, dr2 + jmp .return_fixup +.get_dr3: + mov sAX, dr3 + jmp .return_fixup +.get_dr4: + mov sAX, dr4 + jmp .return_fixup +.get_dr5: + mov sAX, dr5 + jmp .return_fixup +.get_dr7: + mov sAX, dr7 + jmp .return_fixup +.get_dr6: + mov sAX, dr6 + jmp .return_fixup + +.via_system_call: + mov xAX, BS3_SYSCALL_GET_DRX + mov dl, [xBP + xCB + cbCurRetAddr] + call Bs3Syscall + jmp .return + +.return_fixup: +TONLY16 mov edx, eax +TONLY16 shr edx, 16 +.return: + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegGetDrX + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetLdtr.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetLdtr.asm new file mode 100644 index 00000000..6d438336 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetLdtr.asm @@ -0,0 +1,80 @@ +; $Id: bs3-cmn-RegGetLdtr.asm $ +;; @file +; BS3Kit - Bs3RegGetLdtr +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +BS3_EXTERN_SYSTEM16 Bs3Gdt +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(uint16_t, Bs3RegGetLdtr,(void)); +; +; @returns The LDTR value. + +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3RegGetLdtr, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 0 + push xBP + mov xBP, xSP + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call +%endif + ; Load it. + sldt ax + jmp .return + +.via_system_call: + mov ax, BS3_SYSCALL_GET_LDTR + call Bs3Syscall + +.return: + pop xBP + BS3_CALL_CONV_EPILOG 0 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegGetLdtr + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetTr.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetTr.asm new file mode 100644 index 00000000..68e4b572 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetTr.asm @@ -0,0 +1,80 @@ +; $Id: bs3-cmn-RegGetTr.asm $ +;; @file +; BS3Kit - Bs3RegGetTr +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +BS3_EXTERN_SYSTEM16 Bs3Gdt +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(uint16_t, Bs3RegGetTr,(void)); +; +; @returns The LDTR value. + +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3RegGetTr, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 0 + push xBP + mov xBP, xSP + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call +%endif + ; Load it. + str ax + jmp .return + +.via_system_call: + mov ax, BS3_SYSCALL_GET_TR + call Bs3Syscall + +.return: + pop xBP + BS3_CALL_CONV_EPILOG 0 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegGetTr + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetXcr0.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetXcr0.asm new file mode 100644 index 00000000..7b27fd7c --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegGetXcr0.asm @@ -0,0 +1,77 @@ +; $Id: bs3-cmn-RegGetXcr0.asm $ +;; @file +; BS3Kit - Bs3RegGetXcr0 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; @cproto BS3_CMN_PROTO_STUB(uint64_t, Bs3RegGetXcr0,(void)); +; +; @returns Register value. +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs, though 16-bit mode the upper 48-bits of RAX, RDX and RCX are cleared. +; +BS3_PROC_BEGIN_CMN Bs3RegGetXcr0, BS3_PBC_HYBRID_SAFE + push xBP + mov xBP, xSP +TONLY64 push rdx + + ; Read the value. +TNOT16 push sCX + xor ecx, ecx + xgetbv +TNOT16 pop sCX + + ; Move the edx:eax value into the appropriate return register(s). +%if TMPL_BITS == 16 + ; value [dx cx bx ax] + ror eax, 16 + mov bx, ax + mov cx, dx + shr eax, 16 + shr edx, 16 +%elif TMPL_BITS == 64 + mov eax, eax + shr rdx, 32 + or rax, rdx +%endif + +TONLY64 pop rdx + pop xBP + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegGetXcr0 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetCr0.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetCr0.asm new file mode 100644 index 00000000..f5f6d9ae --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetCr0.asm @@ -0,0 +1,96 @@ +; $Id: bs3-cmn-RegSetCr0.asm $ +;; @file +; BS3Kit - Bs3RegSetCr0 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(void, Bs3RegSetCr0,(RTCCUINTXREG uValue)); +; +; @param uValue The value to set. + +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3RegSetCr0, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push sSI + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov si, ss + and si, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sSI, [xBP + xCB + cbCurRetAddr] + mov cr0, sSI + jmp .return + +.via_system_call: + push xDX + push xAX + + mov sSI, [xBP + xCB + cbCurRetAddr] + mov xAX, BS3_SYSCALL_SET_CRX + mov dl, 0 + call Bs3Syscall + pop xAX + pop xDX + +.return: + pop sSI + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegSetCr0 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetCr2.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetCr2.asm new file mode 100644 index 00000000..d563cf0b --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetCr2.asm @@ -0,0 +1,96 @@ +; $Id: bs3-cmn-RegSetCr2.asm $ +;; @file +; BS3Kit - Bs3RegSetCr2 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(void, Bs3RegSetCr1,(RTCCUINTXREG uValue)); +; +; @param uValue The value to set. + +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3RegSetCr2, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push sSI + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov si, ss + and si, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sSI, [xBP + xCB + cbCurRetAddr] + mov cr2, sSI + jmp .return + +.via_system_call: + push xDX + push xAX + + mov sSI, [xBP + xCB + cbCurRetAddr] + mov xAX, BS3_SYSCALL_SET_DRX + mov dl, 2 + call Bs3Syscall + pop xAX + pop xDX + +.return: + pop sSI + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegSetCr2 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetCr3.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetCr3.asm new file mode 100644 index 00000000..acb39b70 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetCr3.asm @@ -0,0 +1,96 @@ +; $Id: bs3-cmn-RegSetCr3.asm $ +;; @file +; BS3Kit - Bs3RegSetCr3 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(void, Bs3RegSetCr3,(RTCCUINTXREG uValue)); +; +; @param uValue The value to set. + +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3RegSetCr3, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push sSI + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov si, ss + and si, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sSI, [xBP + xCB + cbCurRetAddr] + mov cr3, sSI + jmp .return + +.via_system_call: + push xDX + push xAX + + mov sSI, [xBP + xCB + cbCurRetAddr] + mov xAX, BS3_SYSCALL_SET_DRX + mov dl, 3 + call Bs3Syscall + pop xAX + pop xDX + +.return: + pop sSI + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegSetCr3 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetCr4.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetCr4.asm new file mode 100644 index 00000000..cabc632c --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetCr4.asm @@ -0,0 +1,96 @@ +; $Id: bs3-cmn-RegSetCr4.asm $ +;; @file +; BS3Kit - Bs3RegSetCr4 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(void, Bs3RegSetCr4,(RTCCUINTXREG uValue)); +; +; @param uValue The value to set. + +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3RegSetCr4, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push sSI + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov si, ss + and si, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sSI, [xBP + xCB + cbCurRetAddr] + mov cr4, sSI + jmp .return + +.via_system_call: + push xDX + push xAX + + mov sSI, [xBP + xCB + cbCurRetAddr] + mov xAX, BS3_SYSCALL_SET_CRX + mov dl, 4 + call Bs3Syscall + pop xAX + pop xDX + +.return: + pop sSI + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegSetCr4 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDr0.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDr0.asm new file mode 100644 index 00000000..87722f96 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDr0.asm @@ -0,0 +1,96 @@ +; $Id: bs3-cmn-RegSetDr0.asm $ +;; @file +; BS3Kit - Bs3RegSetDr0 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(void, Bs3RegSetDr0,(RTCCUINTXREG uValue)); +; +; @param uValue The value to set. + +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3RegSetDr0, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push sSI + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov si, ss + and si, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sSI, [xBP + xCB + cbCurRetAddr] + mov dr0, sSI + jmp .return + +.via_system_call: + push xDX + push xAX + + mov sSI, [xBP + xCB + cbCurRetAddr] + mov xAX, BS3_SYSCALL_SET_DRX + mov dl, 0 + call Bs3Syscall + pop xAX + pop xDX + +.return: + pop sSI + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegSetDr0 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDr1.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDr1.asm new file mode 100644 index 00000000..e7832a5a --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDr1.asm @@ -0,0 +1,96 @@ +; $Id: bs3-cmn-RegSetDr1.asm $ +;; @file +; BS3Kit - Bs3RegSetDr1 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(void, Bs3RegSetDr1,(RTCCUINTXREG uValue)); +; +; @param uValue The value to set. + +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3RegSetDr1, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push sSI + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov si, ss + and si, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sSI, [xBP + xCB + cbCurRetAddr] + mov dr1, sSI + jmp .return + +.via_system_call: + push xDX + push xAX + + mov sSI, [xBP + xCB + cbCurRetAddr] + mov xAX, BS3_SYSCALL_SET_DRX + mov dl, 1 + call Bs3Syscall + pop xAX + pop xDX + +.return: + pop sSI + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegSetDr1 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDr2.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDr2.asm new file mode 100644 index 00000000..8d4fd678 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDr2.asm @@ -0,0 +1,96 @@ +; $Id: bs3-cmn-RegSetDr2.asm $ +;; @file +; BS3Kit - Bs3RegSetDr2 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(void, Bs3RegSetDr2,(RTCCUINTXREG uValue)); +; +; @param uValue The value to set. + +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3RegSetDr2, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push sSI + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov si, ss + and si, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sSI, [xBP + xCB + cbCurRetAddr] + mov dr2, sSI + jmp .return + +.via_system_call: + push xDX + push xAX + + mov sSI, [xBP + xCB + cbCurRetAddr] + mov xAX, BS3_SYSCALL_SET_DRX + mov dl, 2 + call Bs3Syscall + pop xAX + pop xDX + +.return: + pop sSI + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegSetDr2 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDr3.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDr3.asm new file mode 100644 index 00000000..7ee23061 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDr3.asm @@ -0,0 +1,96 @@ +; $Id: bs3-cmn-RegSetDr3.asm $ +;; @file +; BS3Kit - Bs3RegSetDr3 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(void, Bs3RegSetDr3,(RTCCUINTXREG uValue)); +; +; @param uValue The value to set. + +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3RegSetDr3, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push sSI + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov si, ss + and si, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sSI, [xBP + xCB + cbCurRetAddr] + mov dr3, sSI + jmp .return + +.via_system_call: + push xDX + push xAX + + mov sSI, [xBP + xCB + cbCurRetAddr] + mov xAX, BS3_SYSCALL_SET_DRX + mov dl, 3 + call Bs3Syscall + pop xAX + pop xDX + +.return: + pop sSI + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegSetDr3 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDr6.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDr6.asm new file mode 100644 index 00000000..2ffa1600 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDr6.asm @@ -0,0 +1,96 @@ +; $Id: bs3-cmn-RegSetDr6.asm $ +;; @file +; BS3Kit - Bs3RegSetDr6 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(void, Bs3RegSetDr6,(RTCCUINTXREG uValue)); +; +; @param uValue The value to set. + +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3RegSetDr6, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push sSI + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov si, ss + and si, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sSI, [xBP + xCB + cbCurRetAddr] + mov dr6, sSI + jmp .return + +.via_system_call: + push xDX + push xAX + + mov sSI, [xBP + xCB + cbCurRetAddr] + mov xAX, BS3_SYSCALL_SET_DRX + mov dl, 6 + call Bs3Syscall + pop xAX + pop xDX + +.return: + pop sSI + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegSetDr6 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDr7.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDr7.asm new file mode 100644 index 00000000..207061c0 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDr7.asm @@ -0,0 +1,96 @@ +; $Id: bs3-cmn-RegSetDr7.asm $ +;; @file +; BS3Kit - Bs3RegSetDr7 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(void, Bs3RegSetDr7,(RTCCUINTXREG uValue)); +; +; @param uValue The value to set. + +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3RegSetDr7, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push sSI + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov si, ss + and si, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + mov sSI, [xBP + xCB + cbCurRetAddr] + mov dr7, sSI + jmp .return + +.via_system_call: + push xDX + push xAX + + mov sSI, [xBP + xCB + cbCurRetAddr] + mov xAX, BS3_SYSCALL_SET_DRX + mov dl, 7 + call Bs3Syscall + pop xAX + pop xDX + +.return: + pop sSI + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegSetDr7 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDrX.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDrX.asm new file mode 100644 index 00000000..a86befa7 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetDrX.asm @@ -0,0 +1,142 @@ +; $Id: bs3-cmn-RegSetDrX.asm $ +;; @file +; BS3Kit - Bs3RegSetDrX +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Panic +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + +;TONLY16 CPU 386 + +;; +; @cproto BS3_CMN_PROTO_STUB(void, Bs3RegSetDrX,(uint8_t iReg, RTCCUINTXREG uValue)); +; +; @returns Register value. +; @param iRegister The source register +; @param uValue The new Value. + +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3RegSetDrX, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 2 + push xBP + mov xBP, xSP + push sSI + push xDX + + mov sSI, [xBP + xCB + cbCurRetAddr + xCB] + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov dx, ss + and dx, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + ; Switch (iRegister) + mov dl, [xBP + xCB + cbCurRetAddr] + cmp dl, 6 + jz .set_dr6 + cmp dl, 7 + jz .set_dr7 + cmp dl, 0 + jz .set_dr0 + cmp dl, 1 + jz .set_dr1 + cmp dl, 2 + jz .set_dr2 + cmp dl, 3 + jz .set_dr3 + cmp dl, 4 + jz .set_dr4 + cmp dl, 5 + jz .set_dr5 + + call Bs3Panic + +.set_dr0: + mov dr0, sSI + jmp .return +.set_dr1: + mov dr1, sSI + jmp .return +.set_dr2: + mov dr2, sSI + jmp .return +.set_dr3: + mov dr3, sSI + jmp .return +.set_dr4: + mov dr4, sSI + jmp .return +.set_dr5: + mov dr5, sSI + jmp .return +.set_dr7: + mov dr7, sSI + jmp .return +.set_dr6: + mov dr6, sSI + jmp .return + +.via_system_call: + mov dl, [xBP + xCB + cbCurRetAddr] + push xAX + mov xAX, BS3_SYSCALL_SET_DRX + call Bs3Syscall + pop xAX + +.return: + pop xDX + pop sSI + pop xBP + BS3_CALL_CONV_EPILOG 2 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegSetDrX + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetLdtr.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetLdtr.asm new file mode 100644 index 00000000..846ecaf3 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetLdtr.asm @@ -0,0 +1,91 @@ +; $Id: bs3-cmn-RegSetLdtr.asm $ +;; @file +; BS3Kit - Bs3RegSetLdtr +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +BS3_EXTERN_SYSTEM16 Bs3Gdt +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(void, Bs3RegSetLdtr,(uint16_t uValue)); +; +; @param uValue The value to set. + +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3RegSetLdtr, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push xDX + push xAX + + mov dx, [xBP + xCB + cbCurRetAddr] + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call +%endif + ; If not in ring-0, we have to make a system call. + mov ax, ss + and ax, X86_SEL_RPL + jnz .via_system_call + + ; Load it. + lldt dx + jmp .return + +.via_system_call: + mov ax, BS3_SYSCALL_SET_LDTR + call Bs3Syscall + +.return: + pop xAX + pop xDX + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegSetLdtr + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetTr.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetTr.asm new file mode 100644 index 00000000..84ee2f37 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetTr.asm @@ -0,0 +1,108 @@ +; $Id: bs3-cmn-RegSetTr.asm $ +;; @file +; BS3Kit - Bs3RegSetTr +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +BS3_EXTERN_SYSTEM16 Bs3Gdt +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(void, Bs3RegSetTr,(uint16_t uValue)); +; +; @param uValue The value to set. + +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3RegSetTr, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push xDX + push xAX + push xDI + + mov dx, [xBP + xCB + cbCurRetAddr] + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call +%endif + ; If not in ring-0, we have to make a system call. + mov ax, ss + and ax, X86_SEL_RPL + jnz .via_system_call + + ; Before we can load it, we must mark it non-busy. +%if TMPL_BITS == 16 + push ds + mov ax, BS3_SEL_SYSTEM16 + mov ds, ax + mov di, dx + add xDI, Bs3Gdt wrt BS3SYSTEM16 +%else + movzx edi, dx + add xDI, Bs3Gdt wrt FLAT +%endif + add xDI, X86DESCGENERIC_BIT_OFF_TYPE / 8 + and byte [xDI], ~(X86_SEL_TYPE_SYS_TSS_BUSY_MASK << (X86DESCGENERIC_BIT_OFF_TYPE % 8)) +%if TMPL_BITS == 16 + pop ds +%endif + ltr dx + jmp .return + +.via_system_call: + mov ax, BS3_SYSCALL_SET_TR + call Bs3Syscall + +.return: + pop xDI + pop xAX + pop xDX + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegSetTr + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetXcr0.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetXcr0.asm new file mode 100644 index 00000000..83dcacd8 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-RegSetXcr0.asm @@ -0,0 +1,104 @@ +; $Id: bs3-cmn-RegSetXcr0.asm $ +;; @file +; BS3Kit - Bs3RegSetXcr0 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO_STUB(void, Bs3RegSetXcr0,(uint64_t uValue)); +; +; @param uValue The value to set. + +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3RegSetXcr0, BS3_PBC_HYBRID_SAFE + push xBP + mov xBP, xSP + push sSI + push sDX + push sAX + + ; Load the value +%if TMPL_BITS == 64 + mov eax, ecx + mov rdx, rcx + shr rdx, 32 +%else + mov sAX, [xBP + xCB + cbCurRetAddr] + mov sDX, [xBP + xCB + cbCurRetAddr + 4] +%endif + +%if TMPL_BITS == 16 + ; If V8086 mode we have to go thru a syscall. + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + jnz .via_system_call + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_RM + je .direct_access +%endif + ; If not in ring-0, we have to make a system call. + mov si, ss + and si, X86_SEL_RPL + jnz .via_system_call + +.direct_access: + push sCX + xor ecx, ecx + xsetbv + pop sCX + jmp .return + +.via_system_call: + xchg esi, eax + mov xAX, BS3_SYSCALL_SET_XCR0 + call Bs3Syscall + +.return: + pop sAX + pop sDX + pop sSI + pop xBP + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3RegSetXcr0 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelFar32ToFlat32.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelFar32ToFlat32.c new file mode 100644 index 00000000..460a29b4 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelFar32ToFlat32.c @@ -0,0 +1,47 @@ +/* $Id: bs3-cmn-SelFar32ToFlat32.c $ */ +/** @file + * BS3Kit - Bs3SelFar32ToFlat32 + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "bs3kit-template-header.h" + + +#undef Bs3SelFar32ToFlat32 +BS3_CMN_DEF(uint32_t, Bs3SelFar32ToFlat32,(uint32_t off, uint16_t uSel)) +{ + if (BS3_MODE_IS_RM_OR_V86(g_bBs3CurrentMode)) + return ((uint32_t)uSel << 4) + off; + return Bs3SelProtFar32ToFlat32(off, uSel); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelFar32ToFlat32NoClobber.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelFar32ToFlat32NoClobber.asm new file mode 100644 index 00000000..34d054a9 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelFar32ToFlat32NoClobber.asm @@ -0,0 +1,114 @@ +; $Id: bs3-cmn-SelFar32ToFlat32NoClobber.asm $ +;; @file +; BS3Kit - Bs3SelFar32ToFlat32NoClobber. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_EXTERN_CMN Bs3SelFar32ToFlat32 +TMPL_BEGIN_TEXT + + +;; +; Wrapper around the Bs3SelFar32ToFlat32 C function that doesn't +; clobber any registers nor require 20h stack scratch area (64-bit). +; +; @uses Only return registers (ax:dx, eax, eax) +; @remarks No 20h scratch space required in 64-bit mode. +; +BS3_PROC_BEGIN_CMN Bs3SelFar32ToFlat32NoClobber, BS3_PBC_NEAR ; Far stub generated by the makefile. + push xBP + mov xBP, xSP + +%if TMPL_BITS == 16 + push bx + push cx + push es + + push word [xBP + xCB + cbCurRetAddr + 4] ; uSel + push word [xBP + xCB + cbCurRetAddr + 2] ; high off + push word [xBP + xCB + cbCurRetAddr] ; low off + call Bs3SelFar32ToFlat32 + add sp, 6 + + pop es + pop cx + pop bx +%else + push xDX + push xCX + %if TMPL_BITS == 32 + push es + push fs + push gs + + push dword [xBP + xCB + cbCurRetAddr + 4] ; uSel + push dword [xBP + xCB + cbCurRetAddr] ; off + call Bs3SelFar32ToFlat32 + add esp, 8 + + pop gs + pop fs + pop es + %else + push r8 + push r9 + push r10 + push r11 + sub rsp, 20h + + call Bs3SelFar32ToFlat32 ; Just pass ECX and DX along as-is. + + add rsp, 20h + pop r11 + pop r10 + pop r9 + pop r8 + %endif + pop xCX + pop xDX +%endif + + pop xBP + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3SelFar32ToFlat32NoClobber + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelFlatCodeToProtFar16.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelFlatCodeToProtFar16.asm new file mode 100644 index 00000000..47b6558d --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelFlatCodeToProtFar16.asm @@ -0,0 +1,128 @@ +; $Id: bs3-cmn-SelFlatCodeToProtFar16.asm $ +;; @file +; BS3Kit - Bs3SelFlatCodeToProtFar16. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_EXTERN_CMN Bs3SelFlatCodeToRealMode +BS3_EXTERN_CMN Bs3SelRealModeCodeToProtMode +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO(uint32_t, Bs3SelRealModeCodeToProtMode,(uint32_t uFlatAddr), false); +; +; @uses Only return registers (ax:dx, eax, eax) +; +BS3_PROC_BEGIN_CMN Bs3SelFlatCodeToProtFar16, BS3_PBC_NEAR + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + + ; + ; Call Bs3SelFlatCodeToRealMode and then Bs3SelRealModeCodeToProtMode. + ; This avoid some code duplication. + ; +%if TMPL_BITS == 16 + push word [xBP + xCB + cbCurRetAddr + 2] + push word [xBP + xCB + cbCurRetAddr] + call Bs3SelFlatCodeToRealMode + add sp, 4h + + push ax ; save the offset as it will be the same. + + push dx + call Bs3SelRealModeCodeToProtMode + add sp, 2h + + mov dx, ax ; The protected mode selector. + pop ax ; The offset. + +%elif TMPL_BITS == 32 + push dword [xBP + xCB + cbCurRetAddr] + call Bs3SelFlatCodeToRealMode + add esp, 4h + + push eax ; save the result. + + shr eax, 16 + push eax + call Bs3SelRealModeCodeToProtMode + add esp, 4h + + mov [esp + 2], ax ; Update the selector before popping the result. + pop eax + +%elif TMPL_BITS == 64 + push xCX ; Preserve RCX to make the behaviour uniform. + sub xSP, 28h ; 20h bytes of calling convention scratch and 8 byte for saving the result. + + mov ecx, [xBP + xCB + cbCurRetAddr] ; move straight to parameter 0 register. + call Bs3SelFlatCodeToRealMode + + mov [xBP - xCB*2], eax ; Save the result. + + shr eax, 16 + mov ecx, eax ; Move straight to parameter 0 register. + call Bs3SelRealModeCodeToProtMode + + shl eax, 16 ; Shift prot mode selector into result position. + mov ax, [xBP - xCB*2] ; The segment offset from the previous call. + + add xSP, 28h + pop xCX +%else + %error "TMPL_BITS=" TMPL_BITS "!" +%endif + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3SelFlatCodeToProtFar16 + + +; +; We may be using the near code in some critical code paths, so don't +; penalize it. +; +BS3_CMN_FAR_STUB Bs3SelFlatCodeToProtFar16, 4 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelFlatCodeToRealMode.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelFlatCodeToRealMode.asm new file mode 100644 index 00000000..9f7b4384 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelFlatCodeToRealMode.asm @@ -0,0 +1,163 @@ +; $Id: bs3-cmn-SelFlatCodeToRealMode.asm $ +;; @file +; BS3Kit - Bs3SelFlatCodeToRealMode. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* Global Variables * +;********************************************************************************************************************************* +BS3_EXTERN_DATA16 Bs3RmText16_EndOfSegment +BS3_EXTERN_DATA16 Bs3X0Text16_EndOfSegment +BS3_EXTERN_DATA16 Bs3X1Text16_EndOfSegment + + +; +; Make sure we can get at all the segments. +; +BS3_BEGIN_TEXT16 +BS3_BEGIN_RMTEXT16 +BS3_BEGIN_X0TEXT16 +BS3_BEGIN_X1TEXT16 +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO(uint32_t, Bs3SelRealModeCodeToProtMode,(uint32_t uFlatAddr), false); +; +; @uses Only return registers (ax:dx, eax, eax) +; +BS3_PROC_BEGIN_CMN Bs3SelFlatCodeToRealMode, BS3_PBC_NEAR + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push xCX + push xBX +%if TMPL_BITS != 16 + push xDX +%endif + + ; + ; Load the real mode frame number into DX so we can compare with the + ; segment frame numbers fixed up by the linker. + ; + ; Imagine: FlatAddr = 0x054321 + ; + mov dx, [xBP + xCB + cbCurRetAddr + 1] ; dx = 0x0543 + mov al, [xBP + xCB + cbCurRetAddr + 0] ; al = 0x21 + mov cl,4 + shl dx, 4 ; dx = 0x5430 + shr al, 4 ; al = 0x02 + or dl, al ; dx = 0x5432 + + mov ax, dx + sub ax, CGROUP16 + cmp ax, 1000h + jb .bs3text16 + + mov ax, dx + sub ax, BS3GROUPRMTEXT16 + mov bx, Bs3RmText16_EndOfSegment wrt BS3GROUPRMTEXT16 + add bx, 15 + shr bx, cl + cmp ax, bx + jb .bs3rmtext16 + + mov ax, dx + sub ax, BS3GROUPX0TEXT16 + mov bx, Bs3X0Text16_EndOfSegment wrt BS3GROUPX0TEXT16 + add bx, 15 + shr bx, cl + cmp ax, bx + jb .bs3x0text16 + + mov ax, dx + sub ax, BS3GROUPX1TEXT16 + mov bx, Bs3X1Text16_EndOfSegment wrt BS3GROUPX1TEXT16 + add bx, 15 + shr bx, cl + cmp ax, bx + jb .bs3x1text16 + + extern BS3_CMN_NM(Bs3Panic) + call BS3_CMN_NM(Bs3Panic) + + ; + ; Load the real-mode frame into DX and calc the offset in AX. + ; +.bs3x1text16: + mov dx, BS3GROUPX1TEXT16 + jmp .calc_return +.bs3x0text16: + mov dx, BS3GROUPX0TEXT16 + jmp .calc_return +.bs3rmtext16: + mov dx, BS3GROUPRMTEXT16 + jmp .calc_return +.bs3text16: + mov dx, CGROUP16 +.calc_return: + ; Convert the real-mode frame into the low 16-bit base (BX). + mov bx, dx + shl bx, cl + ; Subtract the 16-bit base from the flat address. (No need to consider + ; the top half on either side.) + mov ax, [xBP + xCB + cbCurRetAddr + 0] + sub ax, bx +%if TMPL_BITS != 16 + ; Got a single 32-bit return register here. + shl edx, 16 + mov dx, ax + mov eax, edx + pop xDX +%endif + pop xBX + pop xCX + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3SelFlatCodeToRealMode + +; +; We may be using the near code in some critical code paths, so don't +; penalize it. +; +BS3_CMN_FAR_STUB Bs3SelFlatCodeToRealMode, 4 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelFlatDataToProtFar16.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelFlatDataToProtFar16.asm new file mode 100644 index 00000000..163b9b43 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelFlatDataToProtFar16.asm @@ -0,0 +1,142 @@ +; $Id: bs3-cmn-SelFlatDataToProtFar16.asm $ +;; @file +; BS3Kit - Bs3SelFlatDataToProtFar16. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +%ifdef BS3_STRICT +BS3_EXTERN_CMN Bs3Panic +%endif + +TMPL_BEGIN_TEXT +%if TMPL_BITS == 16 +CPU 8086 +%endif + + +;; +; @cproto BS3_CMN_PROTO_NOSB(uint32_t, Bs3SelFlatDataToProtFar16,(uint32_t uFlatAddr)); +; +; @uses Only return registers (ax:dx, eax, eax) +; @remarks No 20h scratch area requirements. +; +BS3_PROC_BEGIN_CMN Bs3SelFlatDataToProtFar16, BS3_PBC_NEAR ; Far stub generated by the makefile/bs3kit.h. + push xBP + mov xBP, xSP + + ; + ; Check if we can use the protected mode stack or data selector. + ; The latter ensures the usability of this function for setting SS. + ; +%if TMPL_BITS == 16 + mov ax, [xBP + xCB + cbCurRetAddr] + mov dx, [xBP + xCB + cbCurRetAddr + 2] + test dx, dx + jnz .not_stack + mov dx, BS3_SEL_R0_SS16 +%else +TNOT64 mov eax, [xBP + xCB + cbCurRetAddr] +TONLY64 mov eax, ecx + test eax, 0ffff0000h + jnz .not_stack + or eax, BS3_SEL_R0_SS16 << 16 +%endif + jmp .return + +.not_stack: +%if TMPL_BITS == 16 + sub ax, BS3_ADDR_BS3DATA16 & 0xffff + sbb dx, BS3_ADDR_BS3DATA16 >> 16 + jnz .do_tiled + mov dx, BS3_SEL_R0_DS16 +%else + sub eax, BS3_ADDR_BS3DATA16 + test eax, 0ffff0000h + jnz .do_tiled + or eax, BS3_SEL_R0_DS16 << 16 +%endif + jmp .return + + ; + ; Just translate the address to tiled. + ; +.do_tiled: +%if TMPL_BITS == 16 + ; Convert upper 16-bit to a tiled selector. + mov ax, cx ; save cx + mov dx, [xBP + xCB + cbCurRetAddr + 2] + %ifdef BS3_STRICT + cmp dx, BS3_SEL_TILED_AREA_SIZE >> 16 + jb .address_ok + call Bs3Panic +.address_ok: + %endif + mov cl, X86_SEL_SHIFT + shl dx, cl + add dx, BS3_SEL_TILED + mov cx, ax ; restore cx + + ; Load segment offset and return. + mov ax, [xBP + xCB + cbCurRetAddr] + +%else + ; Convert upper 16-bit to tiled selector. +TNOT64 mov eax, [xBP + xCB + cbCurRetAddr] +TONLY64 mov rax, rcx + %ifdef BS3_STRICT + cmp xAX, BS3_SEL_TILED_AREA_SIZE + jb .address_ok + call Bs3Panic +.address_ok: + %endif + ror eax, 16 + shl ax, X86_SEL_SHIFT + add ax, BS3_SEL_TILED + rol eax, 16 +%endif + +.return: + pop xBP + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3SelFlatDataToProtFar16 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelFlatDataToRealMode.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelFlatDataToRealMode.asm new file mode 100644 index 00000000..68e4f1c1 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelFlatDataToRealMode.asm @@ -0,0 +1,104 @@ +; $Id: bs3-cmn-SelFlatDataToRealMode.asm $ +;; @file +; BS3Kit - Bs3SelFlatDataToRealMode +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +%ifdef BS3_STRICT +BS3_EXTERN_CMN Bs3Panic +%endif +TMPL_BEGIN_TEXT +%if TMPL_BITS == 16 +CPU 8086 +%endif + + +;; +; @cproto BS3_CMN_PROTO_NOSB(uint32_t, Bs3SelFlatDataToRealMode,(uint32_t uFlatAddr)); +; +; @uses Only return registers (ax:dx, eax, eax) +; @remarks No 20h scratch area requirements. +; +BS3_PROC_BEGIN_CMN Bs3SelFlatDataToRealMode, BS3_PBC_NEAR ; Far stub generated by the makefile/bs3kit.h. + push xBP + mov xBP, xSP + + ; + ; Take the simplest approach possible (64KB tiled). + ; +%if TMPL_BITS == 16 + mov ax, cx ; save cx + mov dx, [xBP + xCB + cbCurRetAddr + 2] + %ifdef BS3_STRICT + cmp dx, _1M >> 16 + jb .address_ok + call Bs3Panic +.address_ok: + %endif + mov cl, 12 + shl dx, cl + mov ax, cx ; restore cx + + mov ax, [xBP + xCB + cbCurRetAddr] + +%else + %if TMPL_BITS == 32 + mov eax, [xBP + xCB + cbCurRetAddr] + %else + mov rax, rcx + %endif + %ifdef BS3_STRICT + cmp xAX, _1M + jb .address_ok + call Bs3Panic +.address_ok: + %endif + ror eax, 16 + shl ax, 12 + rol eax, 16 +%endif + + pop xBP + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3SelFlatDataToRealMode + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelLnkPtrToCurPtr.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelLnkPtrToCurPtr.c new file mode 100644 index 00000000..30fa351e --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelLnkPtrToCurPtr.c @@ -0,0 +1,51 @@ +/* $Id: bs3-cmn-SelLnkPtrToCurPtr.c $ */ +/** @file + * BS3Kit - Bs3SelLnkPtrToCurPtr + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "bs3kit-template-header.h" + + +#undef Bs3SelLnkPtrToCurPtr +BS3_CMN_DEF(void BS3_FAR *, Bs3SelLnkPtrToCurPtr,(void BS3_FAR *pvLnkPtr)) +{ +#if ARCH_BITS == 16 + if (BS3_MODE_IS_RM_OR_V86(g_bBs3CurrentMode)) + return pvLnkPtr; + return (void BS3_FAR *)Bs3SelRealModeDataToProtFar16((uint32_t)pvLnkPtr); +#else + return pvLnkPtr; +#endif +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelLnkPtrToFlat.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelLnkPtrToFlat.c new file mode 100644 index 00000000..c7f288b4 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelLnkPtrToFlat.c @@ -0,0 +1,49 @@ +/* $Id: bs3-cmn-SelLnkPtrToFlat.c $ */ +/** @file + * BS3Kit - Bs3SelLnkPtrToFlat + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "bs3kit-template-header.h" + + +#undef Bs3SelLnkPtrToFlat +BS3_CMN_DEF(uint32_t, Bs3SelLnkPtrToFlat,(void BS3_FAR *pvLnkPtr)) +{ +#if ARCH_BITS == 16 + return Bs3SelRealModeDataToFlat((uint32_t)pvLnkPtr); +#else + return (uint32_t)(uintptr_t)pvLnkPtr; +#endif +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelProtFar16DataToFlat.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelProtFar16DataToFlat.asm new file mode 100644 index 00000000..56dd9502 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelProtFar16DataToFlat.asm @@ -0,0 +1,100 @@ +; $Id: bs3-cmn-SelProtFar16DataToFlat.asm $ +;; @file +; BS3Kit - Bs3SelProtFar16DataToFlat. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_EXTERN_CMN Bs3SelFar32ToFlat32NoClobber +TMPL_BEGIN_TEXT +%if TMPL_BITS == 16 +CPU 8086 +%endif + + +;; +; @cproto BS3_CMN_PROTO_NOSB(uint32_t, Bs3SelProtFar16DataToFlat,(uint32_t uFar1616)); +; +; @uses Only return registers (ax:dx, eax, eax) +; @remarks No 20h scratch area requirements. +; +BS3_PROC_BEGIN_CMN Bs3SelProtFar16DataToFlat, BS3_PBC_NEAR ; Far stub generated by the makefile/bs3kit.h. + push xBP + mov xBP, xSP + + ; + ; Just call Bs3SelFar32ToFlat32NoClobber to do the job. + ; +%if TMPL_BITS == 16 + push word [xBP + xCB + cbCurRetAddr + 2] + xor ax, ax + push ax + push word [xBP + xCB + cbCurRetAddr] + call Bs3SelFar32ToFlat32NoClobber + add sp, 6 +%else + %if TMPL_BITS == 32 + movzx eax, word [xBP + xCB + cbCurRetAddr + 2] + push eax + movzx eax, word [xBP + xCB + cbCurRetAddr] + push eax + call Bs3SelFar32ToFlat32NoClobber + add esp, 8 + %else + push xDX + push xCX + + mov edx, ecx ; arg #2: selector + shr edx, 16 + movzx ecx, cx ; arg #1: offset + call Bs3SelFar32ToFlat32NoClobber + + pop xDX + pop xCX + %endif +%endif + +.return: + pop xBP + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3SelProtFar16DataToFlat + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelProtFar16DataToRealMode.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelProtFar16DataToRealMode.asm new file mode 100644 index 00000000..6e87da3d --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelProtFar16DataToRealMode.asm @@ -0,0 +1,157 @@ +; $Id: bs3-cmn-SelProtFar16DataToRealMode.asm $ +;; @file +; BS3Kit - Bs3SelProtFar16DataToRealMode. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_BEGIN_DATA16 ; For real mode segment value. +BS3_BEGIN_SYSTEM16 ; Ditto. +TMPL_BEGIN_TEXT +BS3_EXTERN_CMN Bs3SelFar32ToFlat32NoClobber + +TMPL_BEGIN_TEXT +%if TMPL_BITS == 16 +CPU 8086 +%endif + + +;; +; @cproto BS3_CMN_PROTO_NOSB(uint32_t, Bs3SelProtFar16DataToRealMode,(uint32_t uFar1616)); +; +; @uses Only return registers (ax:dx, eax, eax) +; @remarks No 20h scratch area requirements. +; +BS3_PROC_BEGIN_CMN Bs3SelProtFar16DataToRealMode, BS3_PBC_NEAR ; Far stub generated by the makefile/bs3kit.h. + push xBP + mov xBP, xSP + + ; + ; See if it's our default 16-bit ring-0 data, stack or system data segment. + ; +%if TMPL_BITS == 16 + mov ax, [xBP + xCB + cbCurRetAddr + 2] +%elif TMPL_BITS == 32 + movzx eax, word [xBP + xCB + cbCurRetAddr + 2] +%else + mov eax, ecx + shr eax, 16 +%endif + cmp ax, BS3_SEL_R0_SS16 + jne .not_stack + mov ax, 0 + +.quick_return: +%if TMPL_BITS == 16 + mov dx, ax + mov ax, [xBP + xCB + cbCurRetAddr] +%elif TMPL_BITS == 32 + shl eax, 16 + mov ax, word [xBP + xCB + cbCurRetAddr] +%else + shl eax, 16 + mov ax, cx +%endif + +.return: + pop xBP + BS3_HYBRID_RET + +.not_stack: + cmp ax, BS3_SEL_R0_DS16 + jne .not_dgroup + mov ax, BS3KIT_GRPNM_DATA16 + jmp .quick_return + +.not_dgroup: + cmp ax, BS3_SEL_SYSTEM16 + jne .not_system16 + mov ax, BS3SYSTEM16 + jmp .quick_return + + ; + ; Call worker function to convert it to flat and the do tiled + ; calculation from that. + ; +.not_system16: +%if TMPL_BITS == 16 + push word [xBP + xCB + cbCurRetAddr + 2] + xor ax, ax + push ax + push word [xBP + xCB + cbCurRetAddr] + call Bs3SelFar32ToFlat32NoClobber + add sp, 6 + + ; Convert upper 16-bit of the flat address to a tiled selector. + push cx + mov cl, X86_SEL_SHIFT + shl dx, cl + add dx, BS3_SEL_TILED + pop cx +%else + %if TMPL_BITS == 32 + push eax + movzx eax, word [xBP + xCB + cbCurRetAddr] + push eax + call Bs3SelFar32ToFlat32NoClobber + add esp, 8 + %else + push xDX + push xCX + + mov edx, eax ; arg #2: selector + movzx ecx, cx ; arg #1: offset + call Bs3SelFar32ToFlat32NoClobber + + pop xDX + pop xCX + %endif + + ; Convert upper 16-bit to tiled selector. + rol eax, 16 + shl ax, X86_SEL_SHIFT + add ax, BS3_SEL_TILED + ror eax, 16 +%endif + jmp .return +BS3_PROC_END_CMN Bs3SelProtFar16DataToRealMode + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelProtFar32ToFlat32.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelProtFar32ToFlat32.c new file mode 100644 index 00000000..23e7c879 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelProtFar32ToFlat32.c @@ -0,0 +1,55 @@ +/* $Id: bs3-cmn-SelProtFar32ToFlat32.c $ */ +/** @file + * BS3Kit - Bs3SelProtFar32ToFlat32 + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "bs3kit-template-header.h" + + +#undef Bs3SelProtFar32ToFlat32 +BS3_CMN_DEF(uint32_t, Bs3SelProtFar32ToFlat32,(uint32_t off, uint16_t uSel)) +{ + uint32_t uRet; + PCX86DESC pEntry; + if (!(uSel & X86_SEL_LDT)) + pEntry = &Bs3Gdt[uSel >> X86_SEL_SHIFT]; + else + pEntry = &Bs3Ldt[uSel >> X86_SEL_SHIFT]; + uRet = pEntry->Gen.u16BaseLow; + uRet |= (uint32_t)pEntry->Gen.u8BaseHigh1 << 16; + uRet |= (uint32_t)pEntry->Gen.u8BaseHigh2 << 24; + uRet += off; + return uRet; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelProtModeCodeToRealMode.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelProtModeCodeToRealMode.asm new file mode 100644 index 00000000..8c4ae4ba --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelProtModeCodeToRealMode.asm @@ -0,0 +1,122 @@ +; $Id: bs3-cmn-SelProtModeCodeToRealMode.asm $ +;; @file +; BS3Kit - Bs3SelProtModeCodeToRealMode. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +; +; Make sure we can get at all the segments. +; +BS3_BEGIN_TEXT16 +BS3_BEGIN_RMTEXT16 +BS3_BEGIN_X0TEXT16 +BS3_BEGIN_X1TEXT16 +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO(uint16_t, Bs3SelProtModeCodeToRealMode,(uint16_t uRealSel), false); +; @uses ax (return register) +; +BS3_PROC_BEGIN_CMN Bs3SelProtModeCodeToRealMode, BS3_PBC_NEAR + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + + ; Load it and mask off the RPL. + mov ax, [xBP + xCB + cbCurRetAddr] + and ax, X86_SEL_MASK_OFF_RPL + + ; We're allowed to use the real-mode segment value. + cmp ax, CGROUP16 + je .bs3text16 + + ; Check for typical code segments. + cmp ax, BS3_SEL_R0_CS16 + je .bs3text16 + cmp ax, BS3_SEL_RMTEXT16_CS + je .bs3rmtext16 + cmp ax, BS3_SEL_X0TEXT16_CS + je .bs3x0text16 + cmp ax, BS3_SEL_X1TEXT16_CS + je .bs3x1text16 + + ; Check for less common BS3_SEL_R*_CS16_* values. + cmp ax, BS3_SEL_R0_FIRST + jb .panic + cmp ax, BS3_SEL_R3_FIRST + (1 << BS3_SEL_RING_SHIFT) + jae .panic + + ; Since the relevant bits now are the lower 8 ones, we skip the + ; AND AX, BS3_SEL_RING_SHIFT + ; ADD AX, BS3_SEL_R0_FIRST + ; bits and compare AL with lower 8-bit of the BS3_SEL_R0_CS16* values. +AssertCompile(BS3_SEL_RING_SHIFT == 8) + cmp al, BS3_SEL_R0_CS16 & 0xff + je .bs3text16 + cmp al, BS3_SEL_R0_CS16_EO & 0xff + je .bs3text16 + cmp ax, BS3_SEL_R0_CS16_CNF & 0xff + je .bs3text16 + cmp ax, BS3_SEL_R0_CS16_CNF_EO & 0xff + je .bs3text16 +.panic: + extern BS3_CMN_NM(Bs3Panic) + call BS3_CMN_NM(Bs3Panic) + jmp .return + +.bs3x1text16: + mov ax, BS3GROUPX1TEXT16 + jmp .return +.bs3x0text16: + mov ax, BS3GROUPX0TEXT16 + jmp .return +.bs3rmtext16: + mov ax, BS3GROUPRMTEXT16 + jmp .return +.bs3text16: + mov ax, CGROUP16 +.return: + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3SelProtModeCodeToRealMode + +; +; We may be using the near code in some critical code paths, so don't +; penalize it. +; +BS3_CMN_FAR_STUB Bs3SelProtModeCodeToRealMode, 2 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelRealModeCodeToProtMode.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelRealModeCodeToProtMode.asm new file mode 100644 index 00000000..9d03d64b --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelRealModeCodeToProtMode.asm @@ -0,0 +1,94 @@ +; $Id: bs3-cmn-SelRealModeCodeToProtMode.asm $ +;; @file +; BS3Kit - Bs3SelRealModeCodeToProtMode. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +; +; Make sure we can get at all the segments. +; +BS3_BEGIN_TEXT16 +BS3_BEGIN_RMTEXT16 +BS3_BEGIN_X0TEXT16 +BS3_BEGIN_X1TEXT16 +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_CMN_PROTO(uint16_t, Bs3SelRealModeCodeToProtMode,(uint16_t uRealSel), false); +; @uses ax (return register) +; +BS3_PROC_BEGIN_CMN Bs3SelRealModeCodeToProtMode, BS3_PBC_NEAR + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + + mov ax, [xBP + xCB + cbCurRetAddr] + cmp ax, CGROUP16 + je .bs3text16 + cmp ax, BS3GROUPRMTEXT16 + je .bs3rmtext16 + cmp ax, BS3GROUPX0TEXT16 + je .bs3x0text16 + cmp ax, BS3GROUPX1TEXT16 + je .bs3x1text16 + + extern BS3_CMN_NM(Bs3Panic) + call BS3_CMN_NM(Bs3Panic) + jmp .return + +.bs3x1text16: + mov ax, BS3_SEL_X1TEXT16_CS + jmp .return +.bs3x0text16: + mov ax, BS3_SEL_X0TEXT16_CS + jmp .return +.bs3rmtext16: + mov ax, BS3_SEL_RMTEXT16_CS + jmp .return +.bs3text16: + mov ax, BS3_SEL_R0_CS16 +.return: + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3SelRealModeCodeToProtMode + +; +; We may be using the near code in some critical code paths, so don't +; penalize it. +; +BS3_CMN_FAR_STUB Bs3SelRealModeCodeToProtMode, 2 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelRealModeDataToFlat.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelRealModeDataToFlat.asm new file mode 100644 index 00000000..af5ac8d7 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelRealModeDataToFlat.asm @@ -0,0 +1,101 @@ +; $Id: bs3-cmn-SelRealModeDataToFlat.asm $ +;; @file +; BS3Kit - Bs3SelRealModeDataToFlat. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +TMPL_BEGIN_TEXT +%if TMPL_BITS == 16 +CPU 8086 +%endif + + +;; +; @cproto BS3_CMN_PROTO_NOSB(uint32_t, Bs3SelRealModeDataToFlat,(uint32_t uFar1616)); +; @cproto BS3_CMN_PROTO_NOSB(uint32_t, Bs3SelRealModeCodeToFlat,(uint32_t uFar1616)); +; +; @uses Only return registers (ax:dx, eax, eax); +; @remarks No 20h scratch area requirements. +; +BS3_PROC_BEGIN_CMN Bs3SelRealModeCodeToFlat, BS3_PBC_NEAR ; Far stub generated by the makefile/bs3kit.h. +BS3_PROC_BEGIN_CMN Bs3SelRealModeDataToFlat, BS3_PBC_NEAR ; Far stub generated by the makefile/bs3kit.h. + push xBP + mov xBP, xSP + + ; Calc flat address. +%if TMPL_BITS == 16 + push cx + mov dx, [xBP + xCB + cbCurRetAddr + 2] + mov ax, dx + mov cl, 12 + shr dx, cl + mov cl, 4 + shl ax, cl + add ax, [xBP + xCB + cbCurRetAddr] + adc dx, 0 + pop cx + +%elif TMPL_BITS == 32 + movzx eax, word [xBP + xCB + cbCurRetAddr + 2] + shl eax, 4 + add ax, [xBP + xCB + cbCurRetAddr] + jnc .return + add eax, 10000h + +%elif TMPL_BITS == 64 + mov eax, ecx + shr eax, 16 + shl eax, 4 + add ax, cx + jnc .return + add eax, 10000h + +%else + %error "TMPL_BITS!" +%endif + +.return: + pop xBP + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3SelRealModeDataToFlat + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelRealModeDataToProtFar16.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelRealModeDataToProtFar16.asm new file mode 100644 index 00000000..eaf5d79d --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelRealModeDataToProtFar16.asm @@ -0,0 +1,151 @@ +; $Id: bs3-cmn-SelRealModeDataToProtFar16.asm $ +;; @file +; BS3Kit - Bs3SelRealModeDataToProtFar16. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_BEGIN_DATA16 ; For real mode segment value. +BS3_BEGIN_SYSTEM16 ; Ditto. + +TMPL_BEGIN_TEXT +%if TMPL_BITS == 16 +CPU 8086 +%endif + + +;; +; @cproto BS3_CMN_PROTO_NOSB(uint32_t, Bs3SelRealModeDataToProtFar16,(uint32_t uFar1616)); +; +; @uses Only return registers (ax:dx, eax, eax) +; @remarks No 20h scratch area requirements. +; +BS3_PROC_BEGIN_CMN Bs3SelRealModeDataToProtFar16, BS3_PBC_NEAR ; Far stub generated by the makefile/bs3kit.h. + push xBP + mov xBP, xSP + + ; + ; See if it's our default 16-bit data, stack or system data segment. + ; +%if TMPL_BITS == 16 + mov ax, [xBP + xCB + cbCurRetAddr + 2] +%elif TMPL_BITS == 32 + movzx eax, word [xBP + xCB + cbCurRetAddr + 2] +%else + mov eax, ecx + shr eax, 16 +%endif + cmp ax, 0 + jnz .not_stack + mov ax, BS3_SEL_R0_SS16 + +.quick_return: +%if TMPL_BITS == 16 + mov dx, ax + mov ax, [xBP + xCB + cbCurRetAddr] +%elif TMPL_BITS == 32 + shl eax, 16 + mov ax, word [xBP + xCB + cbCurRetAddr] +%else + shl eax, 16 + mov ax, cx +%endif + +.return: + pop xBP + BS3_HYBRID_RET + +.not_stack: + cmp ax, BS3KIT_GRPNM_DATA16 + jne .not_dgroup + mov ax, BS3_SEL_R0_DS16 + jmp .quick_return + +.not_dgroup: + cmp ax, BS3SYSTEM16 + jne .not_system16 + mov ax, BS3_SEL_SYSTEM16 + jmp .quick_return + + ; + ; Compute flat address and translate it to tiled. + ; +.not_system16: +%if TMPL_BITS == 16 + push cx + + ; Calc flat address. + mov dx, ax + mov cl, 12 + shr dx, cl + mov cl, 4 + shl ax, cl + add ax, [xBP + xCB + cbCurRetAddr] + adc dx, 0 + + ; Convert upper 16-bit to tiled selector. + mov cl, X86_SEL_SHIFT + shl dx, cl + add dx, BS3_SEL_TILED + + pop cx +%else + ; Calc flat address. + shl eax, 4 + %if TMPL_BITS == 32 + add ax, [xBP + xCB + cbCurRetAddr] + %else + add ax, cx + %endif + jnc .no_carry + add eax, 10000h +.no_carry: + + ; Convert upper 16-bit to tiled selector. + rol eax, 16 + shl ax, X86_SEL_SHIFT + add ax, BS3_SEL_TILED + ror eax, 16 +%endif + jmp .return +BS3_PROC_END_CMN Bs3SelRealModeDataToProtFar16 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetup16BitCode.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetup16BitCode.c new file mode 100644 index 00000000..1ee57b53 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetup16BitCode.c @@ -0,0 +1,61 @@ +/* $Id: bs3-cmn-SelSetup16BitCode.c $ */ +/** @file + * BS3Kit - Bs3SelSetup16BitCode + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> + + +#undef Bs3SelSetup16BitCode +BS3_CMN_DEF(void, Bs3SelSetup16BitCode,(X86DESC BS3_FAR *pDesc, uint32_t uBaseAddr, uint8_t bDpl)) +{ + pDesc->Gen.u16LimitLow = UINT16_C(0xffff); + pDesc->Gen.u16BaseLow = (uint16_t)uBaseAddr; + pDesc->Gen.u8BaseHigh1 = (uint8_t)(uBaseAddr >> 16); + pDesc->Gen.u4Type = X86_SEL_TYPE_ER_ACC; + pDesc->Gen.u1DescType = 1; /* data/code */ + pDesc->Gen.u2Dpl = bDpl & 3; + pDesc->Gen.u1Present = 1; + pDesc->Gen.u4LimitHigh = 0; + pDesc->Gen.u1Available = 0; + pDesc->Gen.u1Long = 0; + pDesc->Gen.u1DefBig = 0; + pDesc->Gen.u1Granularity = 0; + pDesc->Gen.u8BaseHigh2 = (uint8_t)(uBaseAddr >> 24); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetup16BitData.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetup16BitData.c new file mode 100644 index 00000000..bddb3188 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetup16BitData.c @@ -0,0 +1,61 @@ +/* $Id: bs3-cmn-SelSetup16BitData.c $ */ +/** @file + * BS3Kit - Bs3SelSetup16BitData + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> + + +#undef Bs3SelSetup16BitData +BS3_CMN_DEF(void, Bs3SelSetup16BitData,(X86DESC BS3_FAR *pDesc, uint32_t uBaseAddr)) +{ + pDesc->Gen.u16LimitLow = UINT16_C(0xffff); + pDesc->Gen.u16BaseLow = (uint16_t)uBaseAddr; + pDesc->Gen.u8BaseHigh1 = (uint8_t)(uBaseAddr >> 16); + pDesc->Gen.u4Type = X86_SEL_TYPE_RW_ACC; + pDesc->Gen.u1DescType = 1; /* data/code */ + pDesc->Gen.u2Dpl = 3; + pDesc->Gen.u1Present = 1; + pDesc->Gen.u4LimitHigh = 0; + pDesc->Gen.u1Available = 0; + pDesc->Gen.u1Long = 0; + pDesc->Gen.u1DefBig = 0; + pDesc->Gen.u1Granularity = 0; + pDesc->Gen.u8BaseHigh2 = (uint8_t)(uBaseAddr >> 24); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetup32BitCode.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetup32BitCode.c new file mode 100644 index 00000000..37f60e9c --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetup32BitCode.c @@ -0,0 +1,62 @@ +/* $Id: bs3-cmn-SelSetup32BitCode.c $ */ +/** @file + * BS3Kit - Bs3SelSetup32BitCode + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> + + +#undef Bs3SelSetup32BitCode +BS3_CMN_DEF(void, Bs3SelSetup32BitCode,(X86DESC BS3_FAR *pDesc, uint32_t uBaseAddr, uint32_t uLimit, uint8_t bDpl)) +{ + uint8_t const cLimitShift = uLimit <= UINT32_C(0xfffff) ? 0 : 12; + pDesc->Gen.u16LimitLow = (uint16_t)(uLimit >> cLimitShift); + pDesc->Gen.u16BaseLow = (uint16_t)uBaseAddr; + pDesc->Gen.u8BaseHigh1 = (uint8_t)(uBaseAddr >> 16); + pDesc->Gen.u4Type = X86_SEL_TYPE_ER_ACC; + pDesc->Gen.u1DescType = 1; /* data/code */ + pDesc->Gen.u2Dpl = bDpl & 3; + pDesc->Gen.u1Present = 1; + pDesc->Gen.u4LimitHigh = (unsigned)(uLimit >> (16 + cLimitShift)); + pDesc->Gen.u1Available = 0; + pDesc->Gen.u1Long = 0; + pDesc->Gen.u1DefBig = 1; + pDesc->Gen.u1Granularity = cLimitShift != 0; + pDesc->Gen.u8BaseHigh2 = (uint8_t)(uBaseAddr >> 24); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetupGate.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetupGate.c new file mode 100644 index 00000000..07f06e1d --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetupGate.c @@ -0,0 +1,61 @@ +/* $Id: bs3-cmn-SelSetupGate.c $ */ +/** @file + * BS3Kit - Bs3SelSetupGate + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3SelSetupGate +BS3_CMN_DEF(void, Bs3SelSetupGate,(X86DESC BS3_FAR *pDesc, uint8_t bType, uint8_t bDpl, + uint16_t uSel, uint32_t off, uint8_t cParams)) +{ + BS3_ASSERT(bDpl <= 3); + BS3_ASSERT(bType <= 15); + BS3_ASSERT(cParams <= 15); + pDesc->Gate.u16OffsetLow = (uint16_t)off; + pDesc->Gate.u16OffsetHigh = (uint16_t)(off >> 16); + pDesc->Gate.u16Sel = uSel; + pDesc->Gate.u5ParmCount = cParams; + pDesc->Gate.u4Type = bType; + pDesc->Gate.u2Dpl = bDpl; + pDesc->Gate.u3Reserved = 0; + pDesc->Gate.u1DescType = 0; /* system */ + pDesc->Gate.u1Present = 1; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetupGate64.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetupGate64.c new file mode 100644 index 00000000..70dc644f --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetupGate64.c @@ -0,0 +1,61 @@ +/* $Id: bs3-cmn-SelSetupGate64.c $ */ +/** @file + * BS3Kit - Bs3SelSetupGate64 + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3SelSetupGate64 +BS3_CMN_DEF(void, Bs3SelSetupGate64,(X86DESC BS3_FAR *pDescPair, uint8_t bType, uint8_t bDpl, uint16_t uSel, uint64_t off)) +{ + BS3_ASSERT(bDpl <= 3); + BS3_ASSERT(bType <= 15); + pDescPair[0].Gate.u16OffsetLow = (uint16_t)off; + pDescPair[0].Gate.u16OffsetHigh = (uint16_t)(off >> 16); + pDescPair[0].Gate.u16Sel = uSel; + pDescPair[0].Gate.u5ParmCount = 0; + pDescPair[0].Gate.u4Type = bType; + pDescPair[0].Gate.u2Dpl = bDpl; + pDescPair[0].Gate.u3Reserved = 0; + pDescPair[0].Gate.u1DescType = 0; /* system */ + pDescPair[0].Gate.u1Present = 1; + pDescPair[1].au32[0] = (uint32_t)(off >> 32); + pDescPair[1].au32[1] = 0; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Shutdown.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Shutdown.asm new file mode 100644 index 00000000..c113d22f --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Shutdown.asm @@ -0,0 +1,63 @@ +; $Id: bs3-cmn-Shutdown.asm $ +;; @file +; BS3Kit - Bs3Shutdown +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" +%include "VBox/bios.mac" + +BS3_EXTERN_CMN Bs3Panic + +BS3_PROC_BEGIN_CMN Bs3Shutdown, BS3_PBC_HYBRID_0_ARGS + cli +%ifdef TMPL_16BIT + mov ax, cs + mov ds, ax +%endif + mov bl, 64 + mov dx, VBOX_BIOS_SHUTDOWN_PORT + mov ax, VBOX_BIOS_OLD_SHUTDOWN_PORT +.retry: + mov ecx, 8 + mov esi, .s_szShutdown + rep outsb + xchg ax, dx ; alternate between the new (VBox) and old (Bochs) ports. + dec bl + jnz .retry + ; Shutdown failed! + jmp Bs3Panic +.s_szShutdown: + db 'Shutdown', 0 +BS3_PROC_END_CMN Bs3Shutdown + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabAlloc.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabAlloc.c new file mode 100644 index 00000000..ee8b57e8 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabAlloc.c @@ -0,0 +1,64 @@ +/* $Id: bs3-cmn-SlabAlloc.c $ */ +/** @file + * BS3Kit - Bs3SlabAlloc + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include <iprt/asm.h> + + +#undef Bs3SlabAlloc +BS3_CMN_DEF(void BS3_FAR *, Bs3SlabAlloc,(PBS3SLABCTL pSlabCtl)) +{ + if (pSlabCtl->cFreeChunks) + { + int32_t iBit = ASMBitFirstClear(&pSlabCtl->bmAllocated, pSlabCtl->cChunks); + if (iBit >= 0) + { + BS3_XPTR_AUTO(void, pvRet); + ASMBitSet(&pSlabCtl->bmAllocated, iBit); + pSlabCtl->cFreeChunks -= 1; + + BS3_XPTR_SET_FLAT(void, pvRet, + BS3_XPTR_GET_FLAT(uint8_t, pSlabCtl->pbStart) + ((uint32_t)iBit << pSlabCtl->cChunkShift)); + return BS3_XPTR_GET(void, pvRet); + } + } + return NULL; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabAllocEx.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabAllocEx.c new file mode 100644 index 00000000..305f5f85 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabAllocEx.c @@ -0,0 +1,111 @@ +/* $Id: bs3-cmn-SlabAllocEx.c $ */ +/** @file + * BS3Kit - Bs3SlabAllocEx + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include <iprt/asm.h> + + +#undef Bs3SlabAllocEx +BS3_CMN_DEF(void BS3_FAR *, Bs3SlabAllocEx,(PBS3SLABCTL pSlabCtl, uint16_t cChunks, uint16_t fFlags)) +{ + BS3_ASSERT(cChunks > 0); + if (pSlabCtl->cFreeChunks >= cChunks) + { + int32_t iBit = ASMBitFirstClear(&pSlabCtl->bmAllocated, pSlabCtl->cChunks); + if (iBit >= 0) + { + BS3_ASSERT(!ASMBitTest(&pSlabCtl->bmAllocated, iBit)); + + while ((uint32_t)iBit + cChunks <= pSlabCtl->cChunks) + { + /* Check that we've got the requested number of free chunks here. */ + uint16_t i; + for (i = 1; i < cChunks; i++) + if (ASMBitTest(&pSlabCtl->bmAllocated, iBit + i)) + break; + if (i == cChunks) + { + /* Check if the chunks are all in the same tiled segment. */ + BS3_XPTR_AUTO(void, pvRet); + BS3_XPTR_SET_FLAT(void, pvRet, + BS3_XPTR_GET_FLAT(uint8_t, pSlabCtl->pbStart) + ((uint32_t)iBit << pSlabCtl->cChunkShift)); + if ( !(fFlags & BS3_SLAB_ALLOC_F_SAME_TILE) + || (BS3_XPTR_GET_FLAT(void, pvRet) >> 16) + == ((BS3_XPTR_GET_FLAT(void, pvRet) + ((uint32_t)cChunks << pSlabCtl->cChunkShift) - 1) >> 16) ) + { + /* Complete the allocation. */ + void *fpRet; + for (i = 0; i < cChunks; i++) + ASMBitSet(&pSlabCtl->bmAllocated, iBit + i); + pSlabCtl->cFreeChunks -= cChunks; + fpRet = BS3_XPTR_GET(void, pvRet); +#if ARCH_BITS == 16 + BS3_ASSERT(fpRet != NULL); +#endif + return fpRet; + } + + /* + * We're crossing a tiled segment boundrary. + * Skip to the start of the next segment and retry there. + * (We already know that the first chunk in the next tiled + * segment is free, otherwise we wouldn't have a crossing.) + */ + BS3_ASSERT(((uint32_t)cChunks << pSlabCtl->cChunkShift) <= _64K); + i = BS3_XPTR_GET_FLAT_LOW(void, pvRet); + i = UINT16_C(0) - i; + i >>= pSlabCtl->cChunkShift; + iBit += i; + } + else + { + /* + * Continue searching. + */ + iBit = ASMBitNextClear(&pSlabCtl->bmAllocated, pSlabCtl->cChunks, iBit + i); + if (iBit < 0) + break; + } + } + } + } + return NULL; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabFree.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabFree.c new file mode 100644 index 00000000..22493229 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabFree.c @@ -0,0 +1,69 @@ +/* $Id: bs3-cmn-SlabFree.c $ */ +/** @file + * BS3Kit - Bs3SlabFree + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include <iprt/asm.h> + + +#undef Bs3SlabFree +BS3_CMN_DEF(uint16_t, Bs3SlabFree,(PBS3SLABCTL pSlabCtl, uint32_t uFlatChunkPtr, uint16_t cChunks)) +{ + uint16_t cFreed = 0; + BS3_ASSERT(cChunks > 0); + if (cChunks > 0) + { + uint16_t iChunk = (uint16_t)((uFlatChunkPtr - BS3_XPTR_GET_FLAT(uint8_t, pSlabCtl->pbStart)) >> pSlabCtl->cChunkShift); + BS3_ASSERT(iChunk < pSlabCtl->cChunks); + BS3_ASSERT(iChunk + cChunks <= pSlabCtl->cChunks); + + do + { + if (ASMBitTestAndClear(&pSlabCtl->bmAllocated, iChunk)) + cFreed++; + else + BS3_ASSERT(0); + iChunk++; + } while (--cChunks > 0); + + pSlabCtl->cFreeChunks += cFreed; + } + return cFreed; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabInit.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabInit.c new file mode 100644 index 00000000..1a2ffe63 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabInit.c @@ -0,0 +1,72 @@ +/* $Id: bs3-cmn-SlabInit.c $ */ +/** @file + * BS3Kit - Bs3SlabInit + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include <iprt/asm.h> + + +#undef Bs3SlabInit +BS3_CMN_DEF(void, Bs3SlabInit,(PBS3SLABCTL pSlabCtl, size_t cbSlabCtl, uint32_t uFlatSlabPtr, uint32_t cbSlab, uint16_t cbChunk)) +{ + uint16_t cBits; + BS3_ASSERT(RT_IS_POWER_OF_TWO(cbChunk)); + BS3_ASSERT(cbSlab >= cbChunk * 4); + BS3_ASSERT(!(uFlatSlabPtr & (cbChunk - 1))); + + BS3_XPTR_SET_FLAT(BS3SLABCTL, pSlabCtl->pNext, 0); + BS3_XPTR_SET_FLAT(BS3SLABCTL, pSlabCtl->pHead, 0); + BS3_XPTR_SET_FLAT(BS3SLABCTL, pSlabCtl->pbStart, uFlatSlabPtr); + pSlabCtl->cbChunk = cbChunk; + pSlabCtl->cChunkShift = ASMBitFirstSetU16(cbChunk) - 1; + pSlabCtl->cChunks = cbSlab >> pSlabCtl->cChunkShift; + pSlabCtl->cFreeChunks = pSlabCtl->cChunks; + cBits = RT_ALIGN_T(pSlabCtl->cChunks, 32, uint16_t); + BS3_ASSERT(cbSlabCtl >= RT_UOFFSETOF_DYN(BS3SLABCTL, bmAllocated[cBits >> 3])); + Bs3MemZero(&pSlabCtl->bmAllocated, cBits >> 3); + + /* Mark excess bitmap padding bits as allocated. */ + if (cBits != pSlabCtl->cChunks) + { + uint16_t iBit; + for (iBit = pSlabCtl->cChunks; iBit < cBits; iBit++) + ASMBitSet(pSlabCtl->bmAllocated, iBit); + } +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabListAdd.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabListAdd.c new file mode 100644 index 00000000..2a84b4d5 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabListAdd.c @@ -0,0 +1,53 @@ +/* $Id: bs3-cmn-SlabListAdd.c $ */ +/** @file + * BS3Kit - Bs3SlabListAdd + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "bs3kit-template-header.h" + + +#undef Bs3SlabListAdd +BS3_CMN_DEF(void, Bs3SlabListAdd,(PBS3SLABHEAD pHead, PBS3SLABCTL pSlabCtl)) +{ + BS3_ASSERT(pHead->cbChunk == pSlabCtl->cbChunk); + BS3_ASSERT(BS3_XPTR_IS_NULL(BS3SLABHEAD, pSlabCtl->pNext)); + + BS3_XPTR_SET_FLAT(BS3SLABCTL, pSlabCtl->pNext, BS3_XPTR_GET_FLAT(BS3SLABCTL, pHead->pFirst)); + BS3_XPTR_SET(BS3SLABCTL, pHead->pFirst, pSlabCtl); + + pHead->cSlabs += 1; + pHead->cChunks += pSlabCtl->cChunks; + pHead->cFreeChunks += pSlabCtl->cFreeChunks; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabListAlloc.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabListAlloc.c new file mode 100644 index 00000000..083de069 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabListAlloc.c @@ -0,0 +1,67 @@ +/* $Id: bs3-cmn-SlabListAlloc.c $ */ +/** @file + * BS3Kit - Bs3SlabListAlloc + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3SlabListAlloc +BS3_CMN_DEF(void BS3_FAR *, Bs3SlabListAlloc,(PBS3SLABHEAD pHead)) +{ + if (pHead->cFreeChunks) + { + PBS3SLABCTL pCur; + for (pCur = BS3_XPTR_GET(BS3SLABCTL, pHead->pFirst); + pCur != NULL; + pCur = BS3_XPTR_GET(BS3SLABCTL, pCur->pNext)) + { + if (pCur->cFreeChunks) + { + void BS3_FAR *pvRet = Bs3SlabAlloc(pCur); + if (pvRet) + { + pHead->cFreeChunks -= 1; + return pvRet; + } + } + } + } + return NULL; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabListAllocEx.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabListAllocEx.c new file mode 100644 index 00000000..62d44e4a --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabListAllocEx.c @@ -0,0 +1,68 @@ +/* $Id: bs3-cmn-SlabListAllocEx.c $ */ +/** @file + * BS3Kit - Bs3SlabListAllocEx + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3SlabListAllocEx +BS3_CMN_DEF(void BS3_FAR *, Bs3SlabListAllocEx,(PBS3SLABHEAD pHead, uint16_t cChunks, uint16_t fFlags)) +{ + BS3_ASSERT(!(fFlags & ~BS3_SLAB_ALLOC_F_SAME_TILE)); + if (pHead->cFreeChunks >= cChunks) + { + PBS3SLABCTL pCur; + for (pCur = BS3_XPTR_GET(BS3SLABCTL, pHead->pFirst); + pCur != NULL; + pCur = BS3_XPTR_GET(BS3SLABCTL, pCur->pNext)) + { + if (pCur->cFreeChunks >= cChunks) + { + void BS3_FAR *pvRet = Bs3SlabAllocEx(pCur, cChunks, fFlags); + if (pvRet) + { + pHead->cFreeChunks -= cChunks; + return pvRet; + } + } + } + } + return NULL; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabListFree.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabListFree.c new file mode 100644 index 00000000..8d8bf01e --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabListFree.c @@ -0,0 +1,64 @@ +/* $Id: bs3-cmn-SlabListFree.c $ */ +/** @file + * BS3Kit - Bs3SlabListFree + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "bs3kit-template-header.h" + + +#undef Bs3SlabListFree +BS3_CMN_DEF(void, Bs3SlabListFree,(PBS3SLABHEAD pHead, void BS3_FAR *pvChunks, uint16_t cChunks)) +{ + BS3_ASSERT(cChunks > 0); + if (cChunks > 0) + { + PBS3SLABCTL pCur; + BS3_XPTR_AUTO(void, pvFlatChunk); + BS3_XPTR_SET(void, pvFlatChunk, pvChunks); + + for (pCur = BS3_XPTR_GET(BS3SLABCTL, pHead->pFirst); + pCur != NULL; + pCur = BS3_XPTR_GET(BS3SLABCTL, pCur->pNext)) + { + if ( ((BS3_XPTR_GET_FLAT(void, pvFlatChunk) - BS3_XPTR_GET_FLAT(uint8_t, pCur->pbStart)) >> pCur->cChunkShift) + < pCur->cChunks) + { + pHead->cFreeChunks += Bs3SlabFree(pCur, BS3_XPTR_GET_FLAT(void, pvFlatChunk), cChunks); + return; + } + } + BS3_ASSERT(0); + } +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabListInit.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabListInit.c new file mode 100644 index 00000000..a1840caa --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SlabListInit.c @@ -0,0 +1,50 @@ +/* $Id: bs3-cmn-SlabListInit.c $ */ +/** @file + * BS3Kit - Bs3SlabListInit + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "bs3kit-template-header.h" + + +#undef Bs3SlabListInit +BS3_CMN_DEF(void, Bs3SlabListInit,(PBS3SLABHEAD pHead, uint16_t cbChunk)) +{ + BS3_ASSERT(RT_IS_POWER_OF_TWO(cbChunk)); + BS3_XPTR_SET(struct BS3SLABCTL, pHead->pFirst, 0); + pHead->cbChunk = cbChunk; + pHead->cSlabs = 0; + pHead->cChunks = 0; + pHead->cFreeChunks = 0; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-StrCpy.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-StrCpy.c new file mode 100644 index 00000000..90b6f749 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-StrCpy.c @@ -0,0 +1,51 @@ +/* $Id: bs3-cmn-StrCpy.c $ */ +/** @file + * BS3Kit - Bs3StrCpy + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "bs3kit-template-header.h" + +#undef Bs3StrCpy +BS3_CMN_DEF(char BS3_FAR *, Bs3StrCpy,(char BS3_FAR *pszDst, const char BS3_FAR *pszSrc)) +{ + char BS3_FAR *pszRet = pszDst; + char ch; + do + { + ch = *pszSrc++; + *pszDst++ = ch; + } while (ch != '\0'); + return pszRet; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-StrFormatV.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-StrFormatV.c new file mode 100644 index 00000000..b32e8f14 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-StrFormatV.c @@ -0,0 +1,788 @@ +/* $Id: bs3-cmn-StrFormatV.c $ */ +/** @file + * BS3Kit - Bs3StrFormatV + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include <iprt/ctype.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define STR_F_CAPITAL 0x0001 +#define STR_F_LEFT 0x0002 +#define STR_F_ZEROPAD 0x0004 +#define STR_F_SPECIAL 0x0008 +#define STR_F_VALSIGNED 0x0010 +#define STR_F_PLUS 0x0020 +#define STR_F_BLANK 0x0040 +#define STR_F_WIDTH 0x0080 +#define STR_F_PRECISION 0x0100 +#define STR_F_THOUSAND_SEP 0x0200 +#define STR_F_NEGATIVE 0x0400 /**< Used to indicated '-' must be printed. */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Size of the temporary buffer. */ +#define BS3FMT_TMP_SIZE 64 + +/** + * BS3kit string format state. + */ +typedef struct BS3FMTSTATE +{ + /** The output function. */ + PFNBS3STRFORMATOUTPUT pfnOutput; + /** User argument for pfnOutput. */ + void BS3_FAR *pvUser; + + /** STR_F_XXX flags. */ + unsigned fFlags; + /** The width when STR_F_WIDTH is specific. */ + int cchWidth; + /** The width when STR_F_PRECISION is specific. */ + int cchPrecision; + /** The number format base. */ + unsigned uBase; + /** Temporary buffer. */ + char szTmp[BS3FMT_TMP_SIZE]; +} BS3FMTSTATE; +/** Pointer to a BS3Kit string formatter state. */ +typedef BS3FMTSTATE BS3_FAR *PBS3FMTSTATE; + + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +#if ARCH_BITS != 64 +static size_t bs3StrFormatU32(PBS3FMTSTATE pState, uint32_t uValue); +#endif + + + +/** + * Formats a number string. + * + * @returns Number of chars printed. + * @param pState The string formatter state. + * @param pszNumber The formatted number string. + * @param cchNumber The length of the number. + */ +static size_t bs3StrFormatNumberString(PBS3FMTSTATE pState, char const BS3_FAR *pszNumber, size_t cchNumber) +{ + /* + * Calc the length of the core number with prefixes. + */ + size_t cchActual = 0; + size_t cchRet = cchNumber; + + /* Accunt for sign char. */ + cchRet += !!(pState->fFlags & (STR_F_NEGATIVE | STR_F_PLUS | STR_F_BLANK)); + + /* Account for the hex prefix: '0x' or '0X' */ + if (pState->fFlags & STR_F_SPECIAL) + { + cchRet += 2; + BS3_ASSERT(pState->uBase == 16); + } + + /* Account for thousand separators (applied while printing). */ + if (pState->fFlags & STR_F_THOUSAND_SEP) + cchRet += (cchNumber - 1) / (pState->uBase == 10 ? 3 : 8); + + /* + * Do left blank padding. + */ + if ((pState->fFlags & (STR_F_ZEROPAD | STR_F_LEFT | STR_F_WIDTH)) == STR_F_WIDTH) + while (cchRet < pState->cchWidth) + { + cchActual += pState->pfnOutput(' ', pState->pvUser); + cchRet++; + } + + /* + * Sign indicator / space. + */ + if (pState->fFlags & (STR_F_NEGATIVE | STR_F_PLUS | STR_F_BLANK)) + { + char ch; + if (pState->fFlags & STR_F_NEGATIVE) + ch = '-'; + else if (pState->fFlags & STR_F_PLUS) + ch = '+'; + else + ch = ' '; + cchActual += pState->pfnOutput(ch, pState->pvUser); + } + + /* + * Hex prefix. + */ + if (pState->fFlags & STR_F_SPECIAL) + { + cchActual += pState->pfnOutput('0', pState->pvUser); + cchActual += pState->pfnOutput(!(pState->fFlags & STR_F_CAPITAL) ? 'x' : 'X', pState->pvUser); + } + + /* + * Zero padding. + */ + if (pState->fFlags & STR_F_ZEROPAD) + while (cchRet < pState->cchWidth) + { + cchActual += pState->pfnOutput('0', pState->pvUser); + cchRet++; + } + + /* + * Output the number. + */ + if ( !(pState->fFlags & STR_F_THOUSAND_SEP) + || cchNumber < 4) + while (cchNumber-- > 0) + cchActual += pState->pfnOutput(*pszNumber++, pState->pvUser); + else + { + char const chSep = pState->uBase == 10 ? ' ' : '\''; + unsigned const cchEvery = pState->uBase == 10 ? 3 : 8; + unsigned cchLeft = --cchNumber % cchEvery; + + cchActual += pState->pfnOutput(*pszNumber++, pState->pvUser); + while (cchNumber-- > 0) + { + if (cchLeft == 0) + { + cchActual += pState->pfnOutput(chSep, pState->pvUser); + cchLeft = cchEvery; + } + cchLeft--; + cchActual += pState->pfnOutput(*pszNumber++, pState->pvUser); + } + } + + /* + * Do right blank padding. + */ + if ((pState->fFlags & (STR_F_ZEROPAD | STR_F_LEFT | STR_F_WIDTH)) == (STR_F_WIDTH | STR_F_LEFT)) + while (cchRet < pState->cchWidth) + { + cchActual += pState->pfnOutput(' ', pState->pvUser); + cchRet++; + } + + return cchActual; +} + + +/** + * Format a 64-bit number. + * + * @returns Number of characters. + * @param pState The string formatter state. + * @param uValue The value. + */ +static size_t bs3StrFormatU64(PBS3FMTSTATE pState, uint64_t uValue) +{ +#if ARCH_BITS != 64 + /* Avoid 64-bit division by formatting 64-bit numbers as hex if they're higher than _4G. */ + if (pState->uBase == 10) + { + if (!(uValue >> 32)) /* uValue <= UINT32_MAX does not work, trouble with 64-bit compile time math! */ + return bs3StrFormatU32(pState, uValue); + pState->fFlags |= STR_F_SPECIAL; + pState->uBase = 16; + } +#endif + + { + const char BS3_FAR *pachDigits = !(pState->fFlags & STR_F_CAPITAL) ? g_achBs3HexDigits : g_achBs3HexDigitsUpper; + char BS3_FAR *psz = &pState->szTmp[BS3FMT_TMP_SIZE]; + + *--psz = '\0'; +#if ARCH_BITS == 64 + if (pState->uBase == 10) + { + do + { + *--psz = pachDigits[uValue % 10]; + uValue /= 10; + } while (uValue > 0); + } + else +#endif + { + BS3_ASSERT(pState->uBase == 16); + do + { + *--psz = pachDigits[uValue & 0xf]; + uValue >>= 4; + } while (uValue > 0); + } + return bs3StrFormatNumberString(pState, psz, &pState->szTmp[BS3FMT_TMP_SIZE - 1] - psz); + } +} + + +/** + * Format a 32-bit number. + * + * @returns Number of characters. + * @param pState The string formatter state. + * @param uValue The value. + */ +static size_t bs3StrFormatU32(PBS3FMTSTATE pState, uint32_t uValue) +{ +#if ARCH_BITS < 64 + const char BS3_FAR *pachDigits = !(pState->fFlags & STR_F_CAPITAL) ? g_achBs3HexDigits : g_achBs3HexDigitsUpper; + char BS3_FAR *psz = &pState->szTmp[BS3FMT_TMP_SIZE]; + + *--psz = '\0'; + if (pState->uBase == 10) + { + do + { + *--psz = pachDigits[uValue % 10]; + uValue /= 10; + } while (uValue > 0); + } + else + { + BS3_ASSERT(pState->uBase == 16); + do + { + *--psz = pachDigits[uValue & 0xf]; + uValue >>= 4; + } while (uValue > 0); + } + return bs3StrFormatNumberString(pState, psz, &pState->szTmp[BS3FMT_TMP_SIZE - 1] - psz); + +#else + /* We've got native 64-bit division, save space. */ + return bs3StrFormatU64(pState, uValue); +#endif +} + + +#if ARCH_BITS == 16 +/** + * Format a 16-bit number. + * + * @returns Number of characters. + * @param pState The string formatter state. + * @param uValue The value. + */ +static size_t bs3StrFormatU16(PBS3FMTSTATE pState, uint16_t uValue) +{ + if (pState->uBase == 10) + { + const char BS3_FAR *pachDigits = !(pState->fFlags & STR_F_CAPITAL) + ? g_achBs3HexDigits : g_achBs3HexDigitsUpper; + char BS3_FAR *psz = &pState->szTmp[BS3FMT_TMP_SIZE]; + + *--psz = '\0'; + do + { + *--psz = pachDigits[uValue % 10]; + uValue /= 10; + } while (uValue > 0); + return bs3StrFormatNumberString(pState, psz, &pState->szTmp[BS3FMT_TMP_SIZE - 1] - psz); + } + + /* + * 32-bit shifting is reasonably cheap and inlined, so combine with 32-bit. + */ + return bs3StrFormatU32(pState, uValue); +} +#endif + + +static size_t bs3StrFormatS64(PBS3FMTSTATE pState, int32_t iValue) +{ + if (iValue < 0) + { + iValue = -iValue; + pState->fFlags |= STR_F_NEGATIVE; + } + return bs3StrFormatU64(pState, iValue); +} + + +static size_t bs3StrFormatS32(PBS3FMTSTATE pState, int32_t iValue) +{ + if (iValue < 0) + { + iValue = -iValue; + pState->fFlags |= STR_F_NEGATIVE; + } + return bs3StrFormatU32(pState, iValue); +} + + +#if ARCH_BITS == 16 +static size_t bs3StrFormatS16(PBS3FMTSTATE pState, int16_t iValue) +{ + if (iValue < 0) + { + iValue = -iValue; + pState->fFlags |= STR_F_NEGATIVE; + } + return bs3StrFormatU16(pState, iValue); +} +#endif + + +#undef Bs3StrFormatV +BS3_CMN_DEF(size_t, Bs3StrFormatV,(const char BS3_FAR *pszFormat, va_list BS3_FAR va, + PFNBS3STRFORMATOUTPUT pfnOutput, void BS3_FAR *pvUser)) +{ + BS3FMTSTATE State; + size_t cchRet = 0; + char ch; +#if ARCH_BITS == 16 + typedef int SIZE_CHECK_TYPE1[sizeof(va) == 4 && sizeof(va[0]) == 4]; +#endif + + State.pfnOutput = pfnOutput; + State.pvUser = pvUser; + + while ((ch = *pszFormat++) != '\0') + { + char chArgSize; + + /* + * Deal with plain chars. + */ + if (ch != '%') + { + cchRet += State.pfnOutput(ch, State.pvUser); + continue; + } + + ch = *pszFormat++; + if (ch == '%') + { + cchRet += State.pfnOutput(ch, State.pvUser); + continue; + } + + /* + * Flags. + */ + State.fFlags = 0; + for (;;) + { + unsigned int fThis; + switch (ch) + { + default: fThis = 0; break; + case '#': fThis = STR_F_SPECIAL; break; + case '-': fThis = STR_F_LEFT; break; + case '+': fThis = STR_F_PLUS; break; + case ' ': fThis = STR_F_BLANK; break; + case '0': fThis = STR_F_ZEROPAD; break; + case '\'': fThis = STR_F_THOUSAND_SEP; break; + } + if (!fThis) + break; + State.fFlags |= fThis; + ch = *pszFormat++; + } + + /* + * Width. + */ + State.cchWidth = 0; + if (RT_C_IS_DIGIT(ch)) + { + do + { + State.cchWidth *= 10; + State.cchWidth += ch - '0'; + ch = *pszFormat++; + } while (RT_C_IS_DIGIT(ch)); + State.fFlags |= STR_F_WIDTH; + } + else if (ch == '*') + { + State.cchWidth = va_arg(va, int); + if (State.cchWidth < 0) + { + State.cchWidth = -State.cchWidth; + State.fFlags |= STR_F_LEFT; + } + State.fFlags |= STR_F_WIDTH; + ch = *pszFormat++; + } + + /* + * Precision + */ + State.cchPrecision = 0; + if (ch == '.') + { + ch = *pszFormat++; + if (RT_C_IS_DIGIT(ch)) + { + do + { + State.cchPrecision *= 10; + State.cchPrecision += ch - '0'; + ch = *pszFormat++; + } while (RT_C_IS_DIGIT(ch)); + State.fFlags |= STR_F_PRECISION; + } + else if (ch == '*') + { + State.cchPrecision = va_arg(va, int); + if (State.cchPrecision < 0) + State.cchPrecision = 0; + State.fFlags |= STR_F_PRECISION; + ch = *pszFormat++; + } + } + + /* + * Argument size. + */ + chArgSize = ch; + switch (ch) + { + default: + chArgSize = 0; + break; + + case 'z': + case 'L': + case 'j': + case 't': + ch = *pszFormat++; + break; + + case 'l': + ch = *pszFormat++; + if (ch == 'l') + { + chArgSize = 'L'; + ch = *pszFormat++; + } + break; + + case 'h': + ch = *pszFormat++; + if (ch == 'h') + { + chArgSize = 'H'; + ch = *pszFormat++; + } + break; + } + + /* + * The type. + */ + switch (ch) + { + /* + * Char + */ + case 'c': + { + char ch = va_arg(va, int /*char*/); + cchRet += State.pfnOutput(ch, State.pvUser); + break; + } + + /* + * String. + */ + case 's': + { + const char BS3_FAR *psz = va_arg(va, const char BS3_FAR *); + size_t cch; + if (psz != NULL) + cch = Bs3StrNLen(psz, State.fFlags & STR_F_PRECISION ? RT_ABS(State.cchPrecision) : ~(size_t)0); + else + { + psz = "<NULL>"; + cch = 6; + } + + if ((State.fFlags & (STR_F_LEFT | STR_F_WIDTH)) == STR_F_WIDTH) + while (--State.cchWidth >= cch) + cchRet += State.pfnOutput(' ', State.pvUser); + + while (cch-- > 0) + cchRet += State.pfnOutput(*psz++, State.pvUser); + + if ((State.fFlags & (STR_F_LEFT | STR_F_WIDTH)) == (STR_F_LEFT | STR_F_WIDTH)) + while (--State.cchWidth >= cch) + cchRet += State.pfnOutput(' ', State.pvUser); + break; + } + + /* + * Signed integers. + */ + case 'i': + case 'd': + State.fFlags &= ~STR_F_SPECIAL; + State.fFlags |= STR_F_VALSIGNED; + State.uBase = 10; + switch (chArgSize) + { + case 0: + case 'h': /* signed short should be promoted to int or be the same as int */ + case 'H': /* signed char should be promoted to int. */ + { + signed int iValue = va_arg(va, signed int); +#if ARCH_BITS == 16 + cchRet += bs3StrFormatS16(&State, iValue); +#else + cchRet += bs3StrFormatS32(&State, iValue); +#endif + break; + } + case 'l': + { + signed long lValue = va_arg(va, signed long); + if (sizeof(lValue) == 4) + cchRet += bs3StrFormatS32(&State, lValue); + else + cchRet += bs3StrFormatS64(&State, lValue); + break; + } + case 'L': + { + unsigned long long ullValue = va_arg(va, unsigned long long); + cchRet += bs3StrFormatS64(&State, ullValue); + break; + } + } + break; + + /* + * Unsigned integers. + */ + case 'X': + State.fFlags |= STR_F_CAPITAL; + case 'x': + case 'u': + { + if (ch == 'u') + { + State.uBase = 10; + State.fFlags &= ~(STR_F_PLUS | STR_F_BLANK | STR_F_SPECIAL); + } + else + { + State.uBase = 16; + State.fFlags &= ~(STR_F_PLUS | STR_F_BLANK); + } + switch (chArgSize) + { + case 0: + case 'h': /* unsigned short should be promoted to int or be the same as int */ + case 'H': /* unsigned char should be promoted to int. */ + { + unsigned int uValue = va_arg(va, unsigned int); +#if ARCH_BITS == 16 + cchRet += bs3StrFormatU16(&State, uValue); +#else + cchRet += bs3StrFormatU32(&State, uValue); +#endif + break; + } + case 'l': + { + unsigned long ulValue = va_arg(va, unsigned long); + if (sizeof(ulValue) == 4) + cchRet += bs3StrFormatU32(&State, ulValue); + else + cchRet += bs3StrFormatU64(&State, ulValue); + break; + } + case 'L': + { + unsigned long long ullValue = va_arg(va, unsigned long long); + cchRet += bs3StrFormatU64(&State, ullValue); + break; + } + } + break; + } + + /* + * Our stuff. + */ + case 'R': + { + ch = *pszFormat++; + switch (ch) + { + case 'I': + State.fFlags |= STR_F_VALSIGNED; + State.uBase &= ~STR_F_SPECIAL; + State.uBase = 10; + break; + case 'U': + State.fFlags &= ~(STR_F_PLUS | STR_F_BLANK | STR_F_SPECIAL); + State.uBase = 10; + break; + case 'X': + State.fFlags &= ~(STR_F_PLUS | STR_F_BLANK); + State.uBase = 16; + break; + case 'h': + ch = *pszFormat++; + if (ch == 'x') + { + /* Hex dumping. */ + uint8_t const BS3_FAR *pbHex = va_arg(va, uint8_t const BS3_FAR *); + if (State.cchPrecision < 0) + State.cchPrecision = 16; + ch = *pszFormat++; + if (ch == 's' || ch == 'd') + { + /* %Rhxd is currently implemented as %Rhxs. */ + while (State.cchPrecision-- > 0) + { + uint8_t b = *pbHex++; + State.pfnOutput(g_achBs3HexDigits[b >> 4], State.pvUser); + State.pfnOutput(g_achBs3HexDigits[b & 0x0f], State.pvUser); + if (State.cchPrecision) + State.pfnOutput(' ', State.pvUser); + } + } + } + State.uBase = 0; + break; + default: + State.uBase = 0; + break; + } + if (State.uBase) + { + ch = *pszFormat++; + switch (ch) + { +#if ARCH_BITS != 16 + case '3': + case '1': /* Will an unsigned 16-bit value always be promoted + to a 16-bit unsigned int. It certainly will be promoted to a 32-bit int. */ + pszFormat++; /* Assumes (1)'6' or (3)'2' */ +#else + case '1': + pszFormat++; /* Assumes (1)'6' */ +#endif + case '8': /* An unsigned 8-bit value should be promoted to int, which is at least 16-bit. */ + { + unsigned int uValue = va_arg(va, unsigned int); +#if ARCH_BITS == 16 + cchRet += bs3StrFormatU16(&State, uValue); +#else + cchRet += bs3StrFormatU32(&State, uValue); +#endif + break; + } +#if ARCH_BITS == 16 + case '3': + { + uint32_t uValue = va_arg(va, uint32_t); + pszFormat++; + cchRet += bs3StrFormatU32(&State, uValue); + break; + } +#endif + case '6': + { + uint64_t uValue = va_arg(va, uint64_t); + pszFormat++; + cchRet += bs3StrFormatU64(&State, uValue); + break; + } + } + } + break; + } + + /* + * Pointers. + */ + case 'P': + State.fFlags |= STR_F_CAPITAL; + RT_FALL_THRU(); + case 'p': + { + void BS3_FAR *pv = va_arg(va, void BS3_FAR *); + State.uBase = 16; + State.fFlags &= ~(STR_F_PLUS | STR_F_BLANK); +#if ARCH_BITS == 16 + State.fFlags |= STR_F_ZEROPAD; + State.cchWidth = State.fFlags & STR_F_SPECIAL ? 6: 4; + cchRet += bs3StrFormatU16(&State, BS3_FP_SEG(pv)); + cchRet += State.pfnOutput(':', State.pvUser); + cchRet += bs3StrFormatU16(&State, BS3_FP_OFF(pv)); +#elif ARCH_BITS == 32 + State.fFlags |= STR_F_SPECIAL | STR_F_ZEROPAD; + State.cchWidth = 10; + cchRet += bs3StrFormatU32(&State, (uintptr_t)pv); +#elif ARCH_BITS == 64 + State.fFlags |= STR_F_SPECIAL | STR_F_ZEROPAD | STR_F_THOUSAND_SEP; + State.cchWidth = 19; + cchRet += bs3StrFormatU64(&State, (uintptr_t)pv); +#else +# error "Undefined or invalid ARCH_BITS." +#endif + break; + } + + } + } + + /* + * Termination call. + */ + cchRet += State.pfnOutput(0, State.pvUser); + + return cchRet; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-StrLen.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-StrLen.c new file mode 100644 index 00000000..cc65024a --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-StrLen.c @@ -0,0 +1,47 @@ +/* $Id: bs3-cmn-StrLen.c $ */ +/** @file + * BS3Kit - Bs3StrLen + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "bs3kit-template-header.h" + +#undef Bs3StrLen +BS3_CMN_DEF(size_t, Bs3StrLen,(const char BS3_FAR *pszString)) +{ + size_t cch = 0; + while (pszString[cch] != '\0') + cch++; + return cch; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-StrNLen.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-StrNLen.c new file mode 100644 index 00000000..d6fb3dc3 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-StrNLen.c @@ -0,0 +1,47 @@ +/* $Id: bs3-cmn-StrNLen.c $ */ +/** @file + * BS3Kit - Bs3StrNLen + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "bs3kit-template-header.h" + +#undef Bs3StrNLen +BS3_CMN_DEF(size_t, Bs3StrNLen,(const char BS3_FAR *pszString, size_t cchMax)) +{ + size_t cch = 0; + while (cchMax-- > 0 && pszString[cch] != '\0') + cch++; + return cch; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-StrPrintf.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-StrPrintf.c new file mode 100644 index 00000000..26956e77 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-StrPrintf.c @@ -0,0 +1,105 @@ +/* $Id: bs3-cmn-StrPrintf.c $ */ +/** @file + * BS3Kit - Bs3StrPrintf, Bs3StrPrintfV + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include <iprt/ctype.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct BS3STRPRINTFSTATE +{ + /** Current buffer position. */ + char *pchBuf; + /** Number of bytes left in the buffer. */ + size_t cchLeft; +} BS3STRPRINTFSTATE; +typedef BS3STRPRINTFSTATE BS3_FAR *PBS3STRPRINTFSTATE; + + + +static BS3_DECL_CALLBACK(size_t) bs3StrPrintfFmtOutput(char ch, void BS3_FAR *pvUser) +{ + PBS3STRPRINTFSTATE pState = (PBS3STRPRINTFSTATE)pvUser; + if (ch) + { + /* Put to the buffer if there is place for this char and a terminator. */ + if (pState->cchLeft > 1) + { + pState->cchLeft--; + *pState->pchBuf++ = ch; + } + + /* Always return 1. */ + return 1; + } + + /* Terminate the string. */ + if (pState->cchLeft) + { + pState->cchLeft--; + *pState->pchBuf++ = '\0'; + } + return 0; +} + + +#undef Bs3StrPrintfV +BS3_CMN_DEF(size_t, Bs3StrPrintfV,(char BS3_FAR *pszBuf, size_t cchBuf, const char BS3_FAR *pszFormat, va_list BS3_FAR va)) +{ + BS3STRPRINTFSTATE State; + State.pchBuf = pszBuf; + State.cchLeft = cchBuf; + return Bs3StrFormatV(pszFormat, va, bs3StrPrintfFmtOutput, &State); +} + + +#undef Bs3StrPrintf +BS3_CMN_DEF(size_t, Bs3StrPrintf,(char BS3_FAR *pszBuf, size_t cchBuf, const char BS3_FAR *pszFormat, ...)) +{ + size_t cchRet; + va_list va; + va_start(va, pszFormat); + cchRet = BS3_CMN_NM(Bs3StrPrintfV)(pszBuf, cchBuf, pszFormat, va); + va_end(va); + return cchRet; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchHlpConvFlatRetToRetfProtMode.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchHlpConvFlatRetToRetfProtMode.asm new file mode 100644 index 00000000..525ae16f --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchHlpConvFlatRetToRetfProtMode.asm @@ -0,0 +1,78 @@ +; $Id: bs3-cmn-SwitchHlpConvFlatRetToRetfProtMode.asm $ +;; @file +; BS3Kit - SwitchHlpConvFlatRetToRetfProtMode +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +%if TMPL_BITS != 16 +BS3_EXTERN_CMN Bs3SelFlatCodeToProtFar16 + +;; +; SwitchToXxx helper that converts a 32-bit or 64-bit flat return address +; into a 16-bit protected mode far return. +; +; +; The caller calls this routine before switching modes. The flat return +; to be converted is immediately after our own return address on the stack. +; +; @uses Nothing. +; @remarks No 16-bit version. +; +BS3_PROC_BEGIN_CMN Bs3SwitchHlpConvFlatRetToRetfProtMode, BS3_PBC_NEAR + %if TMPL_BITS == 64 + push xAX + push xCX + sub xSP, 20h + + mov xCX, [xSP + xCB*3 + 20h] + call Bs3SelFlatCodeToProtFar16 ; well behaved assembly function, only clobbers ecx + mov [xSP + xCB*3 + 20h + 4], eax + + add xSP, 20h + pop xCX + pop xAX + ret 4 + %else + xchg eax, [xSP + xCB] + push xAX + call Bs3SelFlatCodeToProtFar16 ; well behaved assembly function, only clobbers eax + add xSP, 4 + xchg [xSP + xCB], eax + ret + %endif +BS3_PROC_END_CMN Bs3SwitchHlpConvFlatRetToRetfProtMode + +%endif ; 32 || 64 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchHlpConvProtModeRetfPopBpDecBpAndReturn.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchHlpConvProtModeRetfPopBpDecBpAndReturn.asm new file mode 100644 index 00000000..19f91426 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchHlpConvProtModeRetfPopBpDecBpAndReturn.asm @@ -0,0 +1,126 @@ +; $Id: bs3-cmn-SwitchHlpConvProtModeRetfPopBpDecBpAndReturn.asm $ +;; @file +; BS3Kit - Bs3SwitchToPP32 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +%if TMPL_BITS == 16 +BS3_EXTERN_CMN Bs3SelProtModeCodeToRealMode +%else +BS3_EXTERN_CMN Bs3SelFar32ToFlat32 +%endif + + +;; +; SwitchToXxx helper that converts a 16-bit protected mode far return +; into something suitable for the current mode and performs the return. +; +; The caller jmps to this routine. The stack holds an incremented BP (odd is +; far indicator) and a 16-bit far return address. +; +; @uses Nothing. +; @remarks 16-bit ASSUMES we're returning to protected mode!! +; +%if TMPL_BITS == 16 +BS3_BEGIN_TEXT16_FARSTUBS +%endif +BS3_PROC_BEGIN_CMN Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn, BS3_PBC_NEAR +%if TMPL_BITS == 16 + ; Convert the selector of the 16:16 protected mode return address to the + ; corresponding 16-bit real mode segment. + push ax + + mov ax, [bp + 2 + 2] + push ax + call Bs3SelProtModeCodeToRealMode ; This doesn't trash any registers (except AX). + add sp, 2 + mov [bp + 2 + 2], ax + + pop ax + + pop bp + dec bp + retf + +%elif TMPL_BITS == 32 + push eax + push ecx + push edx + + movzx eax, word [esp + 4*3 + 2] ; return offset + movzx edx, word [esp + 4*3 + 2 + 2] ; return selector + push eax + push edx + call Bs3SelFar32ToFlat32 + add esp, 8 + mov [esp + 4*3 + 2], eax + + pop edx + pop ecx + pop eax + pop bp + dec bp + ret +%else + push rax + push rcx + push rdx + push r8 + push r9 + push r10 + push r11 + + movzx ecx, word [rsp + 8*7 + 2] ; return offset + movzx edx, word [rsp + 8*7 + 2 + 2] ; return selector + sub rsp, 20h + call Bs3SelFar32ToFlat32 + add rsp, 20h + mov [rsp + 8*7 + 2], eax + + pop r11 + pop r10 + pop r9 + pop r8 + pop rdx + pop rcx + pop rax + mov bp, [rsp] + add rsp, 2h + dec bp + o32 ret +%endif +BS3_PROC_END_CMN Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchHlpConvRealModeRetfPopBpDecBpAndReturn.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchHlpConvRealModeRetfPopBpDecBpAndReturn.asm new file mode 100644 index 00000000..ce3b5521 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchHlpConvRealModeRetfPopBpDecBpAndReturn.asm @@ -0,0 +1,109 @@ +; $Id: bs3-cmn-SwitchHlpConvRealModeRetfPopBpDecBpAndReturn.asm $ +;; @file +; BS3Kit - SwitchHlpConvRealModeRetfPopBpDecBpAndReturn +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +BS3_EXTERN_CMN Bs3SelFar32ToFlat32 + +;; +; SwitchToXxx helper that converts a 16-bit real mode far return +; into something suitable for the current mode and performs the return. +; +; The caller jmps to this routine. The stack holds an incremented BP (odd is +; far indicator) and a 16-bit far return address. +; +; @uses Nothing. +; @remarks 16-bit ASSUMES we're returning to protected mode!! +; +%if TMPL_BITS == 16 +BS3_BEGIN_TEXT16_FARSTUBS +%endif +BS3_PROC_BEGIN_CMN Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn, BS3_PBC_NEAR +%if TMPL_BITS == 16 + ; Convert the selector of the 16:16 real mode return address to the + ; corresponding 16-bit protected mode selector. + push ax + + mov ax, [bp + 2 + 2] + push ax + BS3_EXTERN_CMN Bs3SelRealModeCodeToProtMode + call Bs3SelRealModeCodeToProtMode ; This doesn't trash any registers (except AX). + add sp, 2 + mov [bp + 2 + 2], ax + + pop ax + + pop bp + dec bp + retf + +%elif TMPL_BITS == 32 + push xAX + push xDX + + movzx eax, word [xSP + xCB*2 + 2 + 2] ; return segment + movzx edx, word [xSP + xCB*2 + 2] ; return offset + shl eax, 4 + add eax, edx + mov [xSP + xCB*2 + 2], eax + + pop xDX + pop xAX + pop bp + dec bp + ret +%else + sub rsp, 2h + + push xAX + push xDX + + movzx eax, word [xSP + xCB*2 + 4 + 2] ; return segment + movzx edx, word [xSP + xCB*2 + 4] ; return offset + shl eax, 4 + add eax, edx + + mov bp, [xSP + xCB*2 + 2] + dec bp + + mov [xSP + xCB*2], rax + + pop xDX + pop xAX + ret +%endif +BS3_PROC_END_CMN Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchTo16Bit.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchTo16Bit.asm new file mode 100644 index 00000000..9b9d97ee --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchTo16Bit.asm @@ -0,0 +1,130 @@ +; $Id: bs3-cmn-SwitchTo16Bit.asm $ +;; @file +; BS3Kit - Bs3SwitchTo16Bit +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%if TMPL_BITS == 16 +BS3_EXTERN_CMN Bs3Syscall +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_DECL(void) Bs3SwitchTo16Bit(void); +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +BS3_PROC_BEGIN_CMN Bs3SwitchTo16Bit, BS3_PBC_NEAR +%if TMPL_BITS == 16 + push ax + push ds + + ; Check g_bBs3CurrentMode whether we're in v8086 mode or not. + mov al, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + test al, BS3_MODE_CODE_V86 + jz .ret_16bit + + ; Switch to ring-0 if v8086 mode. + mov ax, BS3_SYSCALL_TO_RING0 + call Bs3Syscall + +.ret_16bit: + pop ds + pop ax + ret + +%else + push xAX + push xBX + xPUSHF + cli + + ; Calc new CS. + mov ax, cs + and xAX, 3 + shl xAX, BS3_SEL_RING_SHIFT ; ring addend. + add xAX, BS3_SEL_R0_CS16 + + ; Construct a far return for switching to 16-bit code. + push xAX + push .sixteen_bit + xRETF + +BS3_BEGIN_TEXT16 +BS3_GLOBAL_LOCAL_LABEL .sixteen_bit + + ; Load 16-bit segment registers. + add ax, BS3_SEL_R0_SS16 - BS3_SEL_R0_CS16 + mov ss, ax + + add ax, BS3_SEL_R0_DS16 - BS3_SEL_R0_SS16 + mov ds, ax + mov es, ax + + ; Thunk the stack if necessary. + mov ebx, esp + shr ebx, 16 + jz .stack_ok +int3 ; This is for later, just remove this int3 once needed. + test ax, X86_SEL_RPL + jnz .stack_rpl_must_be_0_for_custom_stacks + shl bx, X86_SEL_SHIFT + add bx, BS3_SEL_TILED + mov ss, bx + movzx esp, sp +.stack_ok: + + ; Update globals. + and byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], ~BS3_MODE_CODE_MASK + or byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_16 + + popfd +TONLY64 pop ebx + pop ebx +TONLY64 pop eax + pop eax +TONLY64 add sp, 4 + ret (TMPL_BITS - 16) / 8 ; Return and pop 2 or 6 bytes of "parameters" (unused return value) + +.stack_rpl_must_be_0_for_custom_stacks: + int3 + jmp .stack_rpl_must_be_0_for_custom_stacks +TMPL_BEGIN_TEXT +%endif +BS3_PROC_END_CMN Bs3SwitchTo16Bit + +;; @todo far 16-bit variant. + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchTo16BitV86.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchTo16BitV86.asm new file mode 100644 index 00000000..e58ccef0 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchTo16BitV86.asm @@ -0,0 +1,133 @@ +; $Id: bs3-cmn-SwitchTo16BitV86.asm $ +;; @file +; BS3Kit - Bs3SwitchTo16BitV86 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +%if TMPL_BITS != 64 + +BS3_EXTERN_DATA16 g_bBs3CurrentMode +BS3_EXTERN_CMN Bs3SwitchToRing0 +BS3_EXTERN_CMN Bs3SelProtFar32ToFlat32 +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_DECL(void) Bs3SwitchTo16BitV86(void); +; @uses No general registers modified. Regment registers loaded with specific +; values and the stack register converted to real mode (not ebp). +; +BS3_PROC_BEGIN_CMN Bs3SwitchTo16BitV86, BS3_PBC_NEAR + ; Construct basic v8086 return frame. +BONLY16 movzx esp, sp + push dword 0 ; +0x20: GS + push dword 0 ; +0x1c: FS + push dword BS3_SEL_DATA16 ; +0x18: ES + push dword BS3_SEL_DATA16 ; +0x14: DS + push dword 0 ; +0x10: SS - later + push dword 0 ; +0x0c: return ESP, later. + pushfd + or dword [esp], X86_EFL_VM | X86_EFL_IOPL ; +0x08: Set IOPL=3 and the VM flag (EFLAGS). + push dword BS3_SEL_TEXT16 ; +0x04 + push word 0 + push word [esp + 24h - 2] ; +0x00 + ; Save registers and stuff. + push eax + push edx + push ecx + push ebx + %if TMPL_BITS == 16 + push ds + + ; Check g_bBs3CurrentMode whether we're in v8086 mode or not. + mov ax, seg g_bBs3CurrentMode + mov ds, ax + mov al, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + test al, BS3_MODE_CODE_V86 + jz .not_v8086 + + pop ds + pop ebx + pop ecx + pop edx + pop eax + add xSP, 0x24 + ret + +.not_v8086: + pop ax ; Drop the push ds so the stacks are identical. Keep DS = BS3KIT_GRPNM_DATA16 though. + %endif + + ; Ensure that we're in ring-0. + mov ax, ss + test ax, 3 + jz .is_ring0 + call Bs3SwitchToRing0 + %if TMPL_BITS == 16 + mov ax, seg g_bBs3CurrentMode + mov ds, ax ; parnoia + %endif +.is_ring0: + + ; Update globals. + and byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], ~BS3_MODE_CODE_MASK + or byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + + ; Thunk return SS:ESP to real-mode address via 32-bit flat. + lea eax, [esp + 4*4 + 24h + xCB] + push ss + push eax + BS3_CALL Bs3SelProtFar32ToFlat32, 2 + add esp, sCB + xCB + mov [esp + 4*4 + 0ch], ax ; high word is already zero + %if TMPL_BITS == 16 + mov [esp + 4*4 + 10h], dx + %else + shr eax, 16 + mov [esp + 4*4 + 10h], ax + %endif + + ; Return to v8086 mode. + pop ebx + pop ecx + pop edx + pop eax + iretd +BS3_PROC_END_CMN Bs3SwitchTo16BitV86 + +;; @todo far 16-bit variant. + +%endif ; ! 64-bit + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchTo32Bit.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchTo32Bit.asm new file mode 100644 index 00000000..db131a67 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchTo32Bit.asm @@ -0,0 +1,162 @@ +; $Id: bs3-cmn-SwitchTo32Bit.asm $ +;; @file +; BS3Kit - Bs3SwitchTo32Bit +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +%if TMPL_BITS == 16 +BS3_EXTERN_CMN Bs3SelProtFar32ToFlat32 +BS3_EXTERN_CMN Bs3Syscall +%endif +%if TMPL_BITS != 32 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +TMPL_BEGIN_TEXT +%endif + + +;; +; @cproto BS3_DECL(void) Bs3SwitchTo32Bit(void); +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +BS3_PROC_BEGIN_CMN Bs3SwitchTo32Bit, BS3_PBC_NEAR +%if TMPL_BITS == 32 + ret +%else + %if TMPL_BITS == 16 + push ax ; Reserve space for larger return value (adjusted in 32-bit code). + push eax + pushfd + push edx + %else + pushfq + mov [rsp + 4], eax + %endif + cli + + %if TMPL_BITS == 16 + ; Check for v8086 mode, we need to exit it to enter 32-bit mode. + mov ax, seg g_bBs3CurrentMode + mov ds, ax + mov al, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + test al, BS3_MODE_CODE_V86 + jz .not_v8086 + + ; Calc flat stack into edx. + mov dx, ss + movzx edx, dx + shl edx, 4 + add dx, sp + adc edx, 0 ; edx = flat stack address corresponding to ss:sp + + ; Switch to 16-bit ring0 and go on to do the far jump to 32-bit code. + mov ax, BS3_SYSCALL_TO_RING0 + call Bs3Syscall + + mov xAX, BS3_SEL_R0_CS32 + jmp .do_far_jump + %endif + +.not_v8086: + %if TMPL_BITS == 16 + ; Calc flat stack into edx. + movzx eax, sp + push ecx + push ebx + push ss + push eax + call Bs3SelProtFar32ToFlat32 + add sp, 6 + shl edx, 16 + mov dx, ax ; edx = flat stack address corresponding to ss:sp + pop ebx + pop ecx + %endif + + ; Calc ring addend. + mov ax, cs + and xAX, 3 + shl xAX, BS3_SEL_RING_SHIFT + add xAX, BS3_SEL_R0_CS32 + + ; Create far return for switching to 32-bit mode. +.do_far_jump: + push sAX + %if TMPL_BITS == 16 + push dword .thirty_two_bit wrt FLAT + o32 retf + %else + push .thirty_two_bit + o64 retf + %endif + +BS3_SET_BITS 32 +.thirty_two_bit: + ; Load 32-bit segment registers. + add eax, BS3_SEL_R0_SS32 - BS3_SEL_R0_CS32 + mov ss, ax + %if TMPL_BITS == 16 + mov esp, edx ; Load flat stack address. + %endif + + add eax, BS3_SEL_R0_DS32 - BS3_SEL_R0_SS32 + mov ds, ax + mov es, ax + + ; Update globals. + and byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], ~BS3_MODE_CODE_MASK + or byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_32 + + %if TMPL_BITS == 16 + ; Adjust the return address. + movzx eax, word [esp + 4*3 + 2] + add eax, BS3_ADDR_BS3TEXT16 + mov [esp + 4*3], eax + %endif + + ; Restore and return. + %if TMPL_BITS == 16 + pop edx + %endif + popfd + pop eax +TONLY64 ret 4 +TNOT64 ret +%endif +BS3_PROC_END_CMN Bs3SwitchTo32Bit + +;; @todo far 16-bit variant. + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchTo64Bit.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchTo64Bit.asm new file mode 100644 index 00000000..c241c42c --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchTo64Bit.asm @@ -0,0 +1,120 @@ +; $Id: bs3-cmn-SwitchTo64Bit.asm $ +;; @file +; BS3Kit - Bs3SwitchTo64Bit +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +%if TMPL_BITS != 64 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +TMPL_BEGIN_TEXT +%endif + + +;; +; @cproto BS3_DECL(void) Bs3SwitchTo64Bit(void); +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3SwitchTo64Bit, BS3_PBC_NEAR +%if TMPL_BITS == 64 + ret + +%else + %if TMPL_BITS == 16 + sub sp, 6 ; Space for extended return value (corrected in 64-bit mode). + %else + push xPRE [xSP] ; Duplicate the return address. + and dword [xSP + xCB], 0 ; Clear the high dword or it. + %endif + push dword 0 + push sAX + push dword 0 + pushfd + cli + + %if TMPL_BITS == 16 + ; Check that this is LM16 + mov ax, seg g_bBs3CurrentMode + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_LM16 + je .ok_lm16 + int3 + .ok_lm16: + %endif + + ; Calc ring addend. + mov ax, cs + and xAX, 3 + shl xAX, BS3_SEL_RING_SHIFT + add xAX, BS3_SEL_R0_CS64 + + ; setup far return. + push sAX + %if TMPL_BITS == 16 + push dword .sixty_four_bit wrt FLAT + o32 retf + %else + push .sixty_four_bit + retf + %endif + +BS3_SET_BITS 64 +.sixty_four_bit: + + ; Load 64-bit segment registers (SS64==DS64). + add eax, BS3_SEL_R0_DS64 - BS3_SEL_R0_CS64 + mov ss, ax + mov ds, ax + mov es, ax + + ; Update globals. + and byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], ~BS3_MODE_CODE_MASK + or byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_64 + + %if TMPL_BITS == 16 + movzx eax, word [rsp + 8*2+6] + add eax, BS3_ADDR_BS3TEXT16 + mov [rsp + 8*2], rax + %endif + + popfq + pop rax + ret +%endif +BS3_PROC_END_CMN Bs3SwitchTo64Bit + + +;; @todo far 16-bit variant. + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchToRing0.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchToRing0.asm new file mode 100644 index 00000000..63657add --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchToRing0.asm @@ -0,0 +1,73 @@ +; $Id: bs3-cmn-SwitchToRing0.asm $ +;; @file +; BS3Kit - Bs3SwitchToRing0 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_EXTERN_CMN_FAR Bs3SwitchToRingX +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_DECL(void) Bs3SwitchToRing0(void); +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3SwitchToRing0, BS3_PBC_HYBRID_0_ARGS +%if TMPL_BITS == 64 + push rcx + sub rsp, 20h + mov ecx, 0 + mov [rsp], rcx + call Bs3SwitchToRingX + add rsp, 20h + pop rcx +%else + push 0 +TONLY16 push cs + call Bs3SwitchToRingX + add xSP, xCB +%endif + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3SwitchToRing0 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchToRing1.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchToRing1.asm new file mode 100644 index 00000000..0399095c --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchToRing1.asm @@ -0,0 +1,73 @@ +; $Id: bs3-cmn-SwitchToRing1.asm $ +;; @file +; BS3Kit - Bs3SwitchToRing1 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_EXTERN_CMN_FAR Bs3SwitchToRingX +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_DECL(void) Bs3SwitchToRing1(void); +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3SwitchToRing1, BS3_PBC_HYBRID_0_ARGS +%if TMPL_BITS == 64 + push rcx + sub rsp, 20h + mov ecx, 1 + mov [rsp], rcx + call Bs3SwitchToRingX + add rsp, 20h + pop rcx +%else + push 1 +TONLY16 push cs + call Bs3SwitchToRingX + add xSP, xCB +%endif + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3SwitchToRing1 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchToRing2.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchToRing2.asm new file mode 100644 index 00000000..76a2be19 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchToRing2.asm @@ -0,0 +1,73 @@ +; $Id: bs3-cmn-SwitchToRing2.asm $ +;; @file +; BS3Kit - Bs3SwitchToRing2 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_EXTERN_CMN_FAR Bs3SwitchToRingX +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_DECL(void) Bs3SwitchToRing2(void); +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3SwitchToRing2, BS3_PBC_HYBRID_0_ARGS +%if TMPL_BITS == 64 + push rcx + sub rsp, 20h + mov ecx, 2 + mov [rsp], rcx + call Bs3SwitchToRingX + add rsp, 20h + pop rcx +%else + push 2 +TONLY16 push cs + call Bs3SwitchToRingX + add xSP, xCB +%endif + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3SwitchToRing2 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchToRing3.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchToRing3.asm new file mode 100644 index 00000000..5ea1e6f3 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchToRing3.asm @@ -0,0 +1,73 @@ +; $Id: bs3-cmn-SwitchToRing3.asm $ +;; @file +; BS3Kit - Bs3SwitchToRing3 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_EXTERN_CMN_FAR Bs3SwitchToRingX +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_DECL(void) Bs3SwitchToRing3(void); +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3SwitchToRing3, BS3_PBC_HYBRID_0_ARGS +%if TMPL_BITS == 64 + push rcx + sub rsp, 20h + mov ecx, 3 + mov [rsp], rcx + call Bs3SwitchToRingX + add rsp, 20h + pop rcx +%else + push 3 +TONLY16 push cs + call Bs3SwitchToRingX + add xSP, xCB +%endif + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3SwitchToRing3 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchToRingX.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchToRingX.asm new file mode 100644 index 00000000..b683779c --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SwitchToRingX.asm @@ -0,0 +1,103 @@ +; $Id: bs3-cmn-SwitchToRingX.asm $ +;; @file +; BS3Kit - Bs3SwitchToRingX +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_EXTERN_CMN Bs3Syscall +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_DECL(void) Bs3SwitchToRingX(uint8_t bRing); +; +; @param bRing The target ring (0..3). +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +; @uses No GPRs. +; +BS3_PROC_BEGIN_CMN Bs3SwitchToRingX, BS3_PBC_HYBRID_SAFE + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push xAX + +%if TMPL_BITS == 16 + ; Check the current mode. + mov al, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + + ; If real mode: Nothing we can do, but we'll bitch if the request isn't for ring-0. + cmp al, BS3_MODE_RM + je .return_real_mode + + ; If V8086 mode: Always do syscall and add a lock prefix to make sure it gets to the VMM. + test al, BS3_MODE_CODE_V86 + jnz .just_do_it +%endif + + ; In protected mode: Check the CPL we're currently at skip syscall if ring-0 already. + mov ax, cs + and al, 3 + cmp al, byte [xBP + xCB + cbCurRetAddr] + je .return + +.just_do_it: + mov xAX, BS3_SYSCALL_TO_RING0 + add al, [xBP + xCB + cbCurRetAddr] + call Bs3Syscall + +%ifndef BS3_STRICT +.return_real_mode: +%endif +.return: + pop xAX + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET + +%ifdef BS3_STRICT +; In real mode, only ring-0 makes any sense. +.return_real_mode: + cmp byte [xBP + xCB + cbCurRetAddr], 0 + je .return + int3 + jmp .return +%endif +BS3_PROC_END_CMN Bs3SwitchToRingX + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Syscall.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Syscall.asm new file mode 100644 index 00000000..0edd204e --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Syscall.asm @@ -0,0 +1,94 @@ +; $Id: bs3-cmn-Syscall.asm $ +;; @file +; BS3Kit - Bs3Syscall. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +BS3_EXTERN_DATA16 g_uBs3TrapEipHint +TMPL_BEGIN_TEXT + + +;; +; Worker for doing a syscall - Assembly only. +; +; This worker deals with the needing to use a different opcode +; sequence in v8086 mode as well as the high EIP word hint for +; the weird PE16_32, PP16_32 and PAE16_32 modes. +; +; @uses Whatever the syscall modified (xBX and XBP are always saved). +; +BS3_PROC_BEGIN_CMN Bs3Syscall, BS3_PBC_HYBRID_0_ARGS ; (all parameters are in registers) + push xBP + mov xBP, xSP + push xBX + +%if TMPL_BITS == 32 + mov ebx, .return + xchg ebx, [BS3_DATA16_WRT(g_uBs3TrapEipHint)] +%elif TMPL_BITS == 16 + test byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], BS3_MODE_CODE_V86 + mov bx, 0 + xchg bx, [2 + BS3_DATA16_WRT(g_uBs3TrapEipHint)] + jz .normal + + db 0xf0 ; Lock prefix for causing #UD in V8086 mode. +%endif +.normal: + int BS3_TRAP_SYSCALL + +.return: + ; Restore the EIP hint so the testcase code doesn't need to set it all the time. +%if TMPL_BITS == 32 + mov [BS3_DATA16_WRT(g_uBs3TrapEipHint)], ebx +%elif TMPL_BITS == 16 + mov [2 + BS3_DATA16_WRT(g_uBs3TrapEipHint)], bx +%endif + + pop xBX + pop xBP + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3Syscall + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestCheckExtCtx.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestCheckExtCtx.c new file mode 100644 index 00000000..d8d91150 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestCheckExtCtx.c @@ -0,0 +1,287 @@ +/* $Id: bs3-cmn-TestCheckExtCtx.c $ */ +/** @file + * BS3Kit - Bs3TestCheckExtCtx + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Field descriptors. */ +static struct +{ + uint8_t enmMethod; + uint8_t cb; + uint16_t off; + const char BS3_FAR *pszName; +} const g_aFields[] = +{ +#define BS3EXTCTX_FIELD_ENTRY(a_enmMethod, a_Member) \ + { a_enmMethod, RT_SIZEOFMEMB(BS3EXTCTX, a_Member), RT_OFFSETOF(BS3EXTCTX, a_Member), #a_Member } + BS3EXTCTX_FIELD_ENTRY(BS3EXTCTXMETHOD_END, fXcr0Saved), + +#define BS3EXTCTX_FIELD_ENTRY_CTX(a_enmMethod, a_Member) \ + { a_enmMethod, RT_SIZEOFMEMB(BS3EXTCTX, Ctx.a_Member), RT_OFFSETOF(BS3EXTCTX, Ctx.a_Member), #a_Member } + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_ANCIENT, Ancient.FCW), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_ANCIENT, Ancient.Dummy1), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_ANCIENT, Ancient.FSW), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_ANCIENT, Ancient.Dummy2), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_ANCIENT, Ancient.FTW), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_ANCIENT, Ancient.Dummy3), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_ANCIENT, Ancient.FPUIP), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_ANCIENT, Ancient.CS), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_ANCIENT, Ancient.FOP), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_ANCIENT, Ancient.FPUOO), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_ANCIENT, Ancient.FPUOS), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_ANCIENT, Ancient.regs[0]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_ANCIENT, Ancient.regs[1]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_ANCIENT, Ancient.regs[2]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_ANCIENT, Ancient.regs[3]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_ANCIENT, Ancient.regs[4]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_ANCIENT, Ancient.regs[5]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_ANCIENT, Ancient.regs[6]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_ANCIENT, Ancient.regs[7]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.FCW), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.FSW), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.FTW), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.FOP), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.FPUIP), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.CS), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.Rsrvd1), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.FPUDP), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.DS), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.Rsrvd2), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.MXCSR), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.MXCSR_MASK), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aRegs[0]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aRegs[1]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aRegs[2]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aRegs[3]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aRegs[4]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aRegs[5]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aRegs[6]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aRegs[7]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aXMM[0]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aXMM[1]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aXMM[2]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aXMM[3]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aXMM[4]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aXMM[5]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aXMM[6]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aXMM[7]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aXMM[8]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aXMM[9]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aXMM[10]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aXMM[11]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aXMM[12]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aXMM[13]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aXMM[14]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_FXSAVE, x87.aXMM[15]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.FCW), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.FSW), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.FTW), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.FOP), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.FPUIP), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.CS), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.Rsrvd1), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.FPUDP), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.DS), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.Rsrvd2), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.MXCSR), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.MXCSR_MASK), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aRegs[0]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aRegs[1]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aRegs[2]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aRegs[3]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aRegs[4]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aRegs[5]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aRegs[6]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aRegs[7]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aXMM[0]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aXMM[1]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aXMM[2]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aXMM[3]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aXMM[4]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aXMM[5]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aXMM[6]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aXMM[7]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aXMM[8]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aXMM[9]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aXMM[10]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aXMM[11]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aXMM[12]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aXMM[13]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aXMM[14]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.x87.aXMM[15]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.Hdr.bmXState), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.Hdr.bmXComp), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.u.YmmHi.aYmmHi[0]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.u.YmmHi.aYmmHi[1]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.u.YmmHi.aYmmHi[2]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.u.YmmHi.aYmmHi[3]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.u.YmmHi.aYmmHi[4]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.u.YmmHi.aYmmHi[5]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.u.YmmHi.aYmmHi[6]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.u.YmmHi.aYmmHi[7]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.u.YmmHi.aYmmHi[8]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.u.YmmHi.aYmmHi[9]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.u.YmmHi.aYmmHi[10]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.u.YmmHi.aYmmHi[11]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.u.YmmHi.aYmmHi[12]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.u.YmmHi.aYmmHi[13]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.u.YmmHi.aYmmHi[14]), + BS3EXTCTX_FIELD_ENTRY_CTX(BS3EXTCTXMETHOD_XSAVE, x.u.YmmHi.aYmmHi[15]), +}; + + +#undef Bs3TestCheckExtCtx +BS3_CMN_DEF(bool, Bs3TestCheckExtCtx,(PCBS3EXTCTX pActualExtCtx, PCBS3EXTCTX pExpectedExtCtx, uint16_t fFlags, + const char BS3_FAR *pszMode, uint16_t idTestStep)) +{ + /* + * Make sure the context of a similar and valid before starting. + */ + if (!pActualExtCtx || pActualExtCtx->u16Magic != BS3EXTCTX_MAGIC) + return Bs3TestFailedF("%u - %s: invalid actual context pointer: %p", idTestStep, pszMode, pActualExtCtx); + if (!pExpectedExtCtx || pExpectedExtCtx->u16Magic != BS3EXTCTX_MAGIC) + return Bs3TestFailedF("%u - %s: invalid expected context pointer: %p", idTestStep, pszMode, pExpectedExtCtx); + if ( pActualExtCtx->enmMethod != pExpectedExtCtx->enmMethod + || pActualExtCtx->enmMethod == BS3EXTCTXMETHOD_INVALID + || pActualExtCtx->enmMethod >= BS3EXTCTXMETHOD_END) + return Bs3TestFailedF("%u - %s: mismatching or/and invalid context methods: %d vs %d", + idTestStep, pszMode, pActualExtCtx->enmMethod, pExpectedExtCtx->enmMethod); + if (pActualExtCtx->cb != pExpectedExtCtx->cb) + return Bs3TestFailedF("%u - %s: mismatching context sizes: %#x vs %#x", + idTestStep, pszMode, pActualExtCtx->cb, pExpectedExtCtx->cb); + + /* + * Try get the job done quickly with a memory compare. + */ + if (Bs3MemCmp(pActualExtCtx, pExpectedExtCtx, pActualExtCtx->cb) == 0) + return true; + + Bs3TestFailedF("%u - %s: context memory differs", idTestStep, pszMode); // debug + { + uint8_t const BS3_FAR *pb1 = (uint8_t const BS3_FAR *)pActualExtCtx; + uint8_t const BS3_FAR *pb2 = (uint8_t const BS3_FAR *)pExpectedExtCtx; + unsigned const cb = pActualExtCtx->cb; + unsigned off; + for (off = 0; off < cb; off++) + if (pb1[off] != pb2[off]) + { + unsigned offStart = off++; + const char BS3_FAR *pszName = NULL; + unsigned cbDiff = 0; + unsigned idxField; + for (idxField = 0; idxField < RT_ELEMENTS(g_aFields); idxField++) + if ( offStart - g_aFields[idxField].off < g_aFields[idxField].cb + && ( g_aFields[idxField].enmMethod == BS3EXTCTXMETHOD_END + || g_aFields[idxField].enmMethod == pActualExtCtx->enmMethod)) + { + pszName = g_aFields[idxField].pszName; + cbDiff = g_aFields[idxField].cb; + offStart = g_aFields[idxField].off; + off = offStart + cbDiff; + break; + } + if (!pszName) + { + while (off < cb && pb1[off] != pb2[off]) + off++; + cbDiff = off - offStart; + pszName = "unknown"; + } + switch (cbDiff) + { + case 1: + Bs3TestFailedF("%u - %s: Byte difference at %#x (%s): %#04x, expected %#04x", + idTestStep, pszMode, offStart, pszName, pb1[offStart], pb2[offStart]); + break; + case 2: + Bs3TestFailedF("%u - %s: Word difference at %#x (%s): %#06x, expected %#06x", + idTestStep, pszMode, offStart, pszName, + RT_MAKE_U16(pb1[offStart], pb1[offStart + 1]), + RT_MAKE_U16(pb2[offStart], pb2[offStart + 1])); + break; + case 4: + Bs3TestFailedF("%u - %s: DWord difference at %#x (%s): %#010RX32, expected %#010RX32", + idTestStep, pszMode, offStart, pszName, + RT_MAKE_U32_FROM_U8(pb1[offStart], pb1[offStart + 1], pb1[offStart + 2], pb1[offStart + 3]), + RT_MAKE_U32_FROM_U8(pb2[offStart], pb2[offStart + 1], pb2[offStart + 2], pb2[offStart + 3])); + break; + case 8: + Bs3TestFailedF("%u - %s: QWord difference at %#x (%s): %#018RX64, expected %#018RX64", + idTestStep, pszMode, offStart, pszName, + RT_MAKE_U64_FROM_U8(pb1[offStart], pb1[offStart + 1], pb1[offStart + 2], pb1[offStart + 3], + pb1[offStart + 4], pb1[offStart + 5], pb1[offStart + 6], pb1[offStart + 7]), + RT_MAKE_U64_FROM_U8(pb2[offStart], pb2[offStart + 1], pb2[offStart + 2], pb2[offStart + 3], + pb2[offStart + 4], pb2[offStart + 5], pb2[offStart + 6], pb2[offStart + 7])); + break; + case 16: + Bs3TestFailedF("%u - %s: DQword difference at %#x (%s): \n" + "got %#018RX64'%#018RX64\n" + "expected %#018RX64'%#018RX64", + idTestStep, pszMode, offStart, pszName, + RT_MAKE_U64_FROM_U8(pb1[offStart + 8], pb1[offStart + 9], pb1[offStart + 10], pb1[offStart + 11], + pb1[offStart + 12], pb1[offStart + 13], pb1[offStart + 14], pb1[offStart + 15]), + RT_MAKE_U64_FROM_U8(pb1[offStart], pb1[offStart + 1], pb1[offStart + 2], pb1[offStart + 3], + pb1[offStart + 4], pb1[offStart + 5], pb1[offStart + 6], pb1[offStart + 7]), + + RT_MAKE_U64_FROM_U8(pb2[offStart + 8], pb2[offStart + 9], pb2[offStart + 10], pb2[offStart + 11], + pb2[offStart + 12], pb2[offStart + 13], pb2[offStart + 14], pb2[offStart + 15]), + RT_MAKE_U64_FROM_U8(pb2[offStart], pb2[offStart + 1], pb2[offStart + 2], pb2[offStart + 3], + pb2[offStart + 4], pb2[offStart + 5], pb2[offStart + 6], pb2[offStart + 7]) + ); + break; + + default: + Bs3TestFailedF("%u - %s: %#x..%#x differs (%s)\n" + "got %.*Rhxs\n" + "expected %.*Rhxs", + idTestStep, pszMode, offStart, off - 1, pszName, + off - offStart, &pb1[offStart], + off - offStart, &pb2[offStart]); + break; + } + } + } + return false; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestCheckRegCtxEx.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestCheckRegCtxEx.c new file mode 100644 index 00000000..eb716ea4 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestCheckRegCtxEx.c @@ -0,0 +1,107 @@ +/* $Id: bs3-cmn-TestCheckRegCtxEx.c $ */ +/** @file + * BS3Kit - TestCheckRegCtxEx + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3TestCheckRegCtxEx +BS3_CMN_DEF(bool, Bs3TestCheckRegCtxEx,(PCBS3REGCTX pActualCtx, PCBS3REGCTX pExpectedCtx, uint16_t cbPcAdjust, int16_t cbSpAcjust, + uint32_t fExtraEfl, const char BS3_FAR *pszMode, uint16_t idTestStep)) +{ + uint16_t const cErrorsBefore = Bs3TestSubErrorCount(); + uint8_t const fbFlags = pActualCtx->fbFlags | pExpectedCtx->fbFlags; + +#define CHECK_MEMBER(a_szName, a_szFmt, a_Actual, a_Expected) \ + do { \ + if ((a_Actual) == (a_Expected)) { /* likely */ } \ + else Bs3TestFailedF("%u - %s: " a_szName "=" a_szFmt " expected " a_szFmt, idTestStep, pszMode, (a_Actual), (a_Expected)); \ + } while (0) + + CHECK_MEMBER("rax", "%08RX64", pActualCtx->rax.u, pExpectedCtx->rax.u); + CHECK_MEMBER("rcx", "%08RX64", pActualCtx->rcx.u, pExpectedCtx->rcx.u); + CHECK_MEMBER("rdx", "%08RX64", pActualCtx->rdx.u, pExpectedCtx->rdx.u); + CHECK_MEMBER("rbx", "%08RX64", pActualCtx->rbx.u, pExpectedCtx->rbx.u); + CHECK_MEMBER("rsp", "%08RX64", pActualCtx->rsp.u, pExpectedCtx->rsp.u + cbSpAcjust); + CHECK_MEMBER("rbp", "%08RX64", pActualCtx->rbp.u, pExpectedCtx->rbp.u); + CHECK_MEMBER("rsi", "%08RX64", pActualCtx->rsi.u, pExpectedCtx->rsi.u); + CHECK_MEMBER("rdi", "%08RX64", pActualCtx->rdi.u, pExpectedCtx->rdi.u); + if (!(fbFlags & BS3REG_CTX_F_NO_AMD64)) + { + CHECK_MEMBER("r8", "%08RX64", pActualCtx->r8.u, pExpectedCtx->r8.u); + CHECK_MEMBER("r9", "%08RX64", pActualCtx->r9.u, pExpectedCtx->r9.u); + CHECK_MEMBER("r10", "%08RX64", pActualCtx->r10.u, pExpectedCtx->r10.u); + CHECK_MEMBER("r11", "%08RX64", pActualCtx->r11.u, pExpectedCtx->r11.u); + CHECK_MEMBER("r12", "%08RX64", pActualCtx->r12.u, pExpectedCtx->r12.u); + CHECK_MEMBER("r13", "%08RX64", pActualCtx->r13.u, pExpectedCtx->r13.u); + CHECK_MEMBER("r14", "%08RX64", pActualCtx->r14.u, pExpectedCtx->r14.u); + CHECK_MEMBER("r15", "%08RX64", pActualCtx->r15.u, pExpectedCtx->r15.u); + } + CHECK_MEMBER("rflags", "%08RX64", pActualCtx->rflags.u, pExpectedCtx->rflags.u | fExtraEfl); + CHECK_MEMBER("rip", "%08RX64", pActualCtx->rip.u, pExpectedCtx->rip.u + cbPcAdjust); + CHECK_MEMBER("cs", "%04RX16", pActualCtx->cs, pExpectedCtx->cs); + CHECK_MEMBER("ds", "%04RX16", pActualCtx->ds, pExpectedCtx->ds); + CHECK_MEMBER("es", "%04RX16", pActualCtx->es, pExpectedCtx->es); + CHECK_MEMBER("fs", "%04RX16", pActualCtx->fs, pExpectedCtx->fs); + CHECK_MEMBER("gs", "%04RX16", pActualCtx->gs, pExpectedCtx->gs); + + if (!(fbFlags & BS3REG_CTX_F_NO_TR_LDTR)) + { + CHECK_MEMBER("tr", "%04RX16", pActualCtx->tr, pExpectedCtx->tr); + CHECK_MEMBER("ldtr", "%04RX16", pActualCtx->ldtr, pExpectedCtx->ldtr); + } + CHECK_MEMBER("bMode", "%#04x", pActualCtx->bMode, pExpectedCtx->bMode); + CHECK_MEMBER("bCpl", "%u", pActualCtx->bCpl, pExpectedCtx->bCpl); + + if (!(fbFlags & BS3REG_CTX_F_NO_CR0_IS_MSW)) + CHECK_MEMBER("cr0", "%08RX64", pActualCtx->cr0.u, pExpectedCtx->cr0.u); + else + CHECK_MEMBER("msw", "%08RX16", pActualCtx->cr0.u16, pExpectedCtx->cr0.u16); + if (!(fbFlags & BS3REG_CTX_F_NO_CR2_CR3)) + { + CHECK_MEMBER("cr2", "%08RX64", pActualCtx->cr2.u, pExpectedCtx->cr2.u); + CHECK_MEMBER("cr3", "%08RX64", pActualCtx->cr3.u, pExpectedCtx->cr3.u); + } + if (!(fbFlags & BS3REG_CTX_F_NO_CR4)) + CHECK_MEMBER("cr4", "%08RX64", pActualCtx->cr4.u, pExpectedCtx->cr4.u); +#undef CHECK_MEMBER + + return Bs3TestSubErrorCount() == cErrorsBefore; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestData.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestData.c new file mode 100644 index 00000000..ae96cc1a --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestData.c @@ -0,0 +1,135 @@ +/* $Id: bs3-cmn-TestData.c $ */ +/** @file + * BS3Kit - Test Data. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-test.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#if ARCH_BITS == 16 + +/** Indicates whether the VMMDev is operational. */ +bool g_fbBs3VMMDevTesting = true; + +/** Alignment padding. */ +bool g_fTestDataPadding0 = true; + +/** The number of tests that have failed. */ +uint16_t g_cusBs3TestErrors = 0; + +/** The start error count of the current subtest. */ +uint16_t g_cusBs3SubTestAtErrors = 0; + +/** Whether we've reported the sub-test result or not. */ +bool g_fbBs3SubTestReported = true; +/** Whether the sub-test has been skipped or not. */ +bool g_fbBs3SubTestSkipped = false; + +/** The number of sub tests. */ +uint16_t g_cusBs3SubTests = 0; + +/** The number of sub tests that failed. */ +uint16_t g_cusBs3SubTestsFailed = 0; + +/** VMMDEV_TESTING_UNIT_XXX -> string */ +char const g_aszBs3TestUnitNames[][12] = +{ + "inv", + "%", + "bytes", + "bytes/s", + "KB", + "KB/s", + "MB", + "MB/s", + "packets", + "packets/s", + "frames", + "frames/", + "occ", + "occ/s", + "rndtrp", + "calls", + "calls/s", + "s", + "ms", + "ns", + "ns/call", + "ns/frame", + "ns/occ", + "ns/packet", + "ns/rndtrp", + "ins", + "ins/s", + "", /* none */ + "pp1k", + "pp10k", + "ppm", + "ppb", + "ticks", + "ticks/call", + "ticks/occ", + "pages", + "pages/s", + "ticks/page", + "ns/page", + "ps", + "ps/call", + "ps/frame", + "ps/occ", + "ps/packet", + "ps/rndtrp", + "ps/page", +}; + + +/** The subtest name. */ +char g_szBs3SubTest[64]; + +/** The current test step. */ +uint16_t g_usBs3TestStep; + +#endif /* ARCH_BITS == 16 */ + +/** The test name. */ +const char BS3_FAR *BS3_CMN_NM(g_pszBs3Test) = NULL; + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestDoModesByOneHlp.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestDoModesByOneHlp.asm new file mode 100644 index 00000000..fe6841e4 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestDoModesByOneHlp.asm @@ -0,0 +1,253 @@ +; $Id: bs3-cmn-TestDoModesByOneHlp.asm $ +;; @file +; BS3Kit - Bs3TestDoModesByOne Helpers for switching to the bit-count of the worker function. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* Global Variables * +;********************************************************************************************************************************* +BS3_BEGIN_DATA16 +BS3_GLOBAL_NAME_EX BS3_CMN_NM(g_pfnBs3TestDoModesByOneCurrent),,0 + RTCCPTR_DEF 0 + + +;********************************************************************************************************************************* +;* Exported Symbols * +;********************************************************************************************************************************* +%ifdef BS3_STRICT +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif + +%if TMPL_BITS == 16 +BS3_BEGIN_TEXT16 +extern _Bs3SelRealModeCodeToProtMode_c16 +%endif + + +;; +; @cproto FNBS3TESTDOMODE +; +; @param bMode The current mode +; @uses What allowed by calling convention and possibly mode, caller deals with it. +; + +%if TMPL_BITS == 16 + ; + ; For 16-bit workers. + ; +BS3_BEGIN_TEXT16 + +BS3_SET_BITS 32 +BS3_PROC_BEGIN _Bs3TestCallDoerTo16_c32 + push xBP + mov xBP, xSP + + ; Load bMode into eax. + movzx eax, byte [xBP + xCB*2] + %ifdef BS3_STRICT + cmp al, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + je .ok_mode + int3 +.ok_mode: + %endif + ; Switch to 16-bit. + extern _Bs3SwitchTo16Bit_c32 + call _Bs3SwitchTo16Bit_c32 + BS3_SET_BITS 16 + + push ax ; Worker bMode argument. + + ; Assuming real mode far pointer, convert protected mode before calling it. + push word [2 + BS3_DATA16_WRT(BS3_CMN_NM(g_pfnBs3TestDoModesByOneCurrent))] + call _Bs3SelRealModeCodeToProtMode_c16 + add sp, 2 + + push cs ; return selector + push word .return ; return address + + push ax ; call converted selector + push word [BS3_DATA16_WRT(BS3_CMN_NM(g_pfnBs3TestDoModesByOneCurrent))] ; call offset + retf + +.return: + ; Switch back to 32-bit mode. + extern _Bs3SwitchTo32Bit_c16 + call _Bs3SwitchTo32Bit_c16 + BS3_SET_BITS 32 + + leave + ret +BS3_PROC_END _Bs3TestCallDoerTo16_c32 + + +BS3_SET_BITS 64 +BS3_PROC_BEGIN _Bs3TestCallDoerTo16_c64 + push xBP + mov xBP, xSP + + ; Load bMode into eax. + movzx eax, cl + %ifdef BS3_STRICT + cmp al, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + je .ok_mode + int3 +.ok_mode: + %endif + ; Switch to 16-bit. + extern _Bs3SwitchTo16Bit_c64 + call _Bs3SwitchTo16Bit_c64 + BS3_SET_BITS 16 + + push ax ; Worker bMode argument. + + ; Assuming real mode far pointer, convert protected mode before calling it. + push word [2 + BS3_DATA16_WRT(BS3_CMN_NM(g_pfnBs3TestDoModesByOneCurrent))] + call _Bs3SelRealModeCodeToProtMode_c16 + add sp, 2 + + push cs ; return selector + push word .return ; return address + push ax ; call converted selector + push word [BS3_DATA16_WRT(BS3_CMN_NM(g_pfnBs3TestDoModesByOneCurrent))] ; call offset + retf + +.return: + ; Switch back to 64-bit mode. + extern _Bs3SwitchTo64Bit_c16 + call _Bs3SwitchTo64Bit_c16 + BS3_SET_BITS 64 + + leave + ret +BS3_PROC_END _Bs3TestCallDoerTo16_c64 + + +%elif TMPL_BITS == 32 + ; + ; For 32-bit workers. + ; + +BS3_BEGIN_TEXT16 +BS3_SET_BITS 16 +BS3_PROC_BEGIN _Bs3TestCallDoerTo32_f16 + push xBP + mov xBP, xSP + + ; Load bMode into eax. + movzx eax, byte [xBP + xCB + sCB] + %ifdef BS3_STRICT + cmp al, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + je .ok_mode + int3 +.ok_mode: + %endif + ; Switch to 32-bit. + extern _Bs3SwitchTo32Bit_c16 + call _Bs3SwitchTo32Bit_c16 + BS3_SET_BITS 32 + + push eax ; Worker bMode argument. + + test al, BS3_MODE_CODE_V86 + jnz .return_to_v86 ; Need to figure this while we still have the mode value. + + call [BS3_DATA16_WRT(BS3_CMN_NM(g_pfnBs3TestDoModesByOneCurrent))] + + ; Switch back to 16-bit mode. + extern _Bs3SwitchTo16Bit_c32 + call _Bs3SwitchTo16Bit_c32 + BS3_SET_BITS 16 +.return: + leave + retf + + BS3_SET_BITS 32 +.return_to_v86: + call [BS3_DATA16_WRT(BS3_CMN_NM(g_pfnBs3TestDoModesByOneCurrent))] + + ; Switch back to v8086 mode. + extern _Bs3SwitchTo16BitV86_c32 + call _Bs3SwitchTo16BitV86_c32 + BS3_SET_BITS 16 + jmp .return +BS3_PROC_END _Bs3TestCallDoerTo32_f16 + + +BS3_BEGIN_TEXT32 +BS3_SET_BITS 64 +BS3_PROC_BEGIN _Bs3TestCallDoerTo32_c64 + push xBP + mov xBP, xSP + + ; Load bMode into eax. + movzx eax, cl + %ifdef BS3_STRICT + cmp al, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + je .ok_mode + int3 +.ok_mode: + %endif + ; Switch to 32-bit. + extern _Bs3SwitchTo32Bit_c64 + call _Bs3SwitchTo32Bit_c64 + BS3_SET_BITS 32 + + push eax ; Worker bMode argument. + call [BS3_DATA16_WRT(BS3_CMN_NM(g_pfnBs3TestDoModesByOneCurrent))] + + ; Switch back to 64-bit mode. + extern _Bs3SwitchTo64Bit_c32 + call _Bs3SwitchTo64Bit_c32 + BS3_SET_BITS 64 + + leave + ret +BS3_PROC_END _Bs3TestCallDoerTo32_c64 + + +%elif TMPL_BITS == 64 +; +; 64-bit workers makes no sense, so skip that. +; +%else + %error "TMPL_BITS!" +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestFailed.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestFailed.c new file mode 100644 index 00000000..25722c99 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestFailed.c @@ -0,0 +1,151 @@ +/* $Id: bs3-cmn-TestFailed.c $ */ +/** @file + * BS3Kit - Bs3TestFailed, Bs3TestFailedF, Bs3TestFailedV. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-test.h" +#include <iprt/asm-amd64-x86.h> + + +/** + * @callback_method_impl{FNBS3STRFORMATOUTPUT, + * Used by Bs3TestFailedV and Bs3TestSkippedV.} + */ +BS3_DECL_CALLBACK(size_t) bs3TestFailedStrOutput(char ch, void BS3_FAR *pvUser) +{ + PBS3TESTFAILEDBUF pBuf = (PBS3TESTFAILEDBUF)pvUser; + + /* + * VMMDev first. We postpone newline processing here so we can strip one + * trailing newline. + */ + if (g_fbBs3VMMDevTesting) + { + if (pBuf->fNewLine && ch != '\0') + ASMOutU8(VMMDEV_TESTING_IOPORT_DATA, '\n'); + pBuf->fNewLine = ch == '\n'; + if (ch != '\n') + ASMOutU8(VMMDEV_TESTING_IOPORT_DATA, ch); + } + + /* + * Console next. + */ + if (ch != '\0') + { + BS3_ASSERT(pBuf->cchBuf < RT_ELEMENTS(pBuf->achBuf)); + pBuf->achBuf[pBuf->cchBuf++] = ch; + + /* Whether to flush the buffer. We do line flushing here to avoid + dropping too much info when the formatter crashes on bad input. */ + if ( pBuf->cchBuf < RT_ELEMENTS(pBuf->achBuf) + && ch != '\n') + { + pBuf->fNewLine = false; + return 1; + } + pBuf->fNewLine = '\n'; + } + /* Try fit missing newline into the buffer. */ + else if (!pBuf->fNewLine && pBuf->cchBuf < RT_ELEMENTS(pBuf->achBuf)) + { + pBuf->fNewLine = true; + pBuf->achBuf[pBuf->cchBuf++] = '\n'; + } + + BS3_ASSERT(pBuf->cchBuf <= RT_ELEMENTS(pBuf->achBuf)); + Bs3PrintStrN(&pBuf->achBuf[0], pBuf->cchBuf); + pBuf->cchBuf = 0; + + /* In case we failed to add trailing new line, print one separately. */ + if (!pBuf->fNewLine) + Bs3PrintChr('\n'); + + return ch != '\0'; +} + + +/** + * Equivalent to RTTestIFailedV. + */ +#undef Bs3TestFailedV +BS3_CMN_DEF(bool, Bs3TestFailedV,(const char *pszFormat, va_list BS3_FAR va)) +{ + BS3TESTFAILEDBUF Buf; + + if (!++g_cusBs3TestErrors) + g_cusBs3TestErrors++; + + if (g_fbBs3VMMDevTesting) +#if ARCH_BITS == 16 + ASMOutU16(VMMDEV_TESTING_IOPORT_CMD, (uint16_t)VMMDEV_TESTING_CMD_FAILED); +#else + ASMOutU32(VMMDEV_TESTING_IOPORT_CMD, VMMDEV_TESTING_CMD_FAILED); +#endif + + Buf.fNewLine = false; + Buf.cchBuf = 0; + Bs3StrFormatV(pszFormat, va, bs3TestFailedStrOutput, &Buf); + return false; +} + + +/** + * Equivalent to RTTestIFailedF. + */ +#undef Bs3TestFailedF +BS3_CMN_DEF(bool, Bs3TestFailedF,(const char *pszFormat, ...)) +{ + va_list va; + va_start(va, pszFormat); + BS3_CMN_NM(Bs3TestFailedV)(pszFormat, va); + va_end(va); + return false; +} + + +/** + * Equivalent to RTTestIFailed. + */ +#undef Bs3TestFailed +BS3_CMN_DEF(bool, Bs3TestFailed,(const char *pszMessage)) +{ + return BS3_CMN_NM(Bs3TestFailedF)("%s", pszMessage); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestHostPrintf.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestHostPrintf.c new file mode 100644 index 00000000..658ba7c4 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestHostPrintf.c @@ -0,0 +1,114 @@ +/* $Id: bs3-cmn-TestHostPrintf.c $ */ +/** @file + * BS3Kit - BS3TestPrintf, BS3TestPrintfV + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-test.h" + +#include <iprt/asm-amd64-x86.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Output state for Bs3TestHostPrintfV. */ +typedef struct BS3TESTHOSTPRINTF +{ + bool fNewCmd; +} BS3TESTHOSTPRINTF; + + +/** + * @callback_method_impl{FNBS3STRFORMATOUTPUT, Prints to screen and VMMDev} + */ +static BS3_DECL_CALLBACK(size_t) bs3TestPrintfStrOutput(char ch, void BS3_FAR *pvUser) +{ + BS3TESTHOSTPRINTF BS3_FAR *pState = (BS3TESTHOSTPRINTF BS3_FAR *)pvUser; + + /* + * VMMDev first. We do line by line processing to avoid running out of + * string buffer on the host side. + */ + if (g_fbBs3VMMDevTesting) + { + if (ch != '\n' && !pState->fNewCmd) + ASMOutU8(VMMDEV_TESTING_IOPORT_DATA, ch); + else if (ch != '\0') + { + if (pState->fNewCmd) + { +#if ARCH_BITS == 16 + ASMOutU16(VMMDEV_TESTING_IOPORT_CMD, (uint16_t)VMMDEV_TESTING_CMD_PRINT); +#else + ASMOutU32(VMMDEV_TESTING_IOPORT_CMD, VMMDEV_TESTING_CMD_PRINT); +#endif + pState->fNewCmd = false; + } + ASMOutU8(VMMDEV_TESTING_IOPORT_DATA, ch); + if (ch == '\n') + { + ASMOutU8(VMMDEV_TESTING_IOPORT_DATA, '\0'); + pState->fNewCmd = true; + } + } + } + + return ch != '\0'; +} + + +#undef Bs3TestHostPrintfV +BS3_CMN_DEF(void, Bs3TestHostPrintfV,(const char BS3_FAR *pszFormat, va_list BS3_FAR va)) +{ + BS3TESTHOSTPRINTF State; + State.fNewCmd = true; + Bs3StrFormatV(pszFormat, va, bs3TestPrintfStrOutput, &State); +} + + + +#undef Bs3TestHostPrintf +BS3_CMN_DEF(void, Bs3TestHostPrintf,(const char BS3_FAR *pszFormat, ...)) +{ + va_list va; + va_start(va, pszFormat); + BS3_CMN_NM(Bs3TestHostPrintfV)(pszFormat, va); + va_end(va); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestInit.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestInit.c new file mode 100644 index 00000000..a3a4e7a9 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestInit.c @@ -0,0 +1,77 @@ +/* $Id: bs3-cmn-TestInit.c $ */ +/** @file + * BS3Kit - Bs3TestInit + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-test.h" + + +/** + * Equivalent to RTTestCreate + RTTestBanner. + * + * @param pszTest The test name. + */ +#undef Bs3TestInit +BS3_CMN_DEF(void, Bs3TestInit,(const char BS3_FAR *pszTest)) +{ + /* + * Initialize the globals. + */ + BS3_CMN_NM(g_pszBs3Test) = pszTest; + g_szBs3SubTest[0] = '\0'; + g_cusBs3TestErrors = 0; + g_cusBs3SubTestAtErrors = 0; + g_fbBs3SubTestReported = true; + g_fbBs3SubTestSkipped = false; + g_cusBs3SubTests = 0; + g_cusBs3SubTestsFailed = 0; + g_fbBs3VMMDevTesting = bs3TestIsVmmDevTestingPresent(); + + /* + * Print the name - RTTestBanner. + */ + Bs3PrintStr(pszTest); + Bs3PrintStr(": TESTING...\n"); + + /* + * Report it to the VMMDev. + */ + bs3TestSendCmdWithStr(VMMDEV_TESTING_CMD_INIT, pszTest); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestIsVmmDevTestingPresent.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestIsVmmDevTestingPresent.asm new file mode 100644 index 00000000..5a158fde --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestIsVmmDevTestingPresent.asm @@ -0,0 +1,78 @@ +; $Id: bs3-cmn-TestIsVmmDevTestingPresent.asm $ +;; @file +; BS3Kit - bs3TestIsVmmDevTestingPresent +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" +%include "VBox/VMMDevTesting.mac" + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_EXTERN_DATA16 g_uBs3CpuDetected +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_DECL(bool) bs3TestIsVmmDevTestingPresent_c16(void); +; +BS3_PROC_BEGIN_CMN bs3TestIsVmmDevTestingPresent, BS3_PBC_HYBRID_0_ARGS + BS3_CALL_CONV_PROLOG 2 + push xBP + mov xBP, xSP + push xDX + + ; Check the response from the NOP port. + mov dx, VMMDEV_TESTING_IOPORT_NOP + cmp byte [g_uBs3CpuDetected], BS3CPU_80386 + jb .ancient_cpu + in eax, dx + cmp eax, VMMDEV_TESTING_NOP_RET +.set_ax_and_return: + mov ax, 0 + jne .return + mov ax, 1 + +.return: + pop xDX + pop xBP + BS3_CALL_CONV_EPILOG 2 + BS3_HYBRID_RET + +.ancient_cpu: + in ax, dx + cmp ax, (VMMDEV_TESTING_NOP_RET & 0xffff) + jmp .set_ax_and_return +BS3_PROC_END_CMN bs3TestIsVmmDevTestingPresent + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestNow.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestNow.asm new file mode 100644 index 00000000..7a8778a7 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestNow.asm @@ -0,0 +1,113 @@ +; $Id: bs3-cmn-TestNow.asm $ +;; @file +; BS3Kit - Bs3TestNow. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" +%include "VBox/VMMDevTesting.mac" + +BS3_EXTERN_DATA16 g_fbBs3VMMDevTesting +TMPL_BEGIN_TEXT + +;; +; @cproto BS3_DECL(uint64_t) Bs3TestNow(void); +; +; @uses eflags, return register(s) +; +BS3_PROC_BEGIN_CMN Bs3TestNow, BS3_PBC_HYBRID + BS3_CALL_CONV_PROLOG 0 + push xBP + mov xBP, xSP +%if __BITS__ == 16 +BONLY16 push sAX +%else + push xCX +BONLY64 push xDX +%endif + + cmp byte [BS3_DATA16_WRT(g_fbBs3VMMDevTesting)], 0 + je .no_vmmdev + + ; Read the lower timestamp. + mov dx, VMMDEV_TESTING_IOPORT_TS_LOW + in eax, dx +%if __BITS__ == 16 + mov bx, ax ; Save the first word in BX (returned in DX). + shr eax, 16 + mov cx, ax ; The second word is returned in CX. +%else + mov ecx, eax +%endif + + ; Read the high timestamp (latached in above read). + mov dx, VMMDEV_TESTING_IOPORT_TS_HIGH + in eax, dx +%if __BITS__ == 16 + mov dx, bx ; The first word is returned in DX. + mov bx, ax ; The third word is returned in BX. + shr eax, 16 ; The fourth word is returned in AX. +%elif __BITS__ == 32 + mov edx, eax + mov eax, ecx +%else + shl rax, 32 + or rax, rcx +%endif + +.return: +%if __BITS__ == 16 + mov [bp - sCB], ax ; Update the AX part of the saved EAX. + pop sAX +%else + pop xCX +BONLY64 pop xDX +%endif + pop xBP + BS3_CALL_CONV_EPILOG 0 + BS3_HYBRID_RET + +.no_vmmdev: + ; No fallback, just zero the result. +%if __BITS__ == 16 + xor ax, ax + xor bx, bx + xor cx, cx + xor dx, dx +%else + xor eax, eax +BONLY32 xor edx, edx +%endif + jmp .return +BS3_PROC_END_CMN Bs3TestNow + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestPrintf.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestPrintf.c new file mode 100644 index 00000000..9d352bf6 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestPrintf.c @@ -0,0 +1,146 @@ +/* $Id: bs3-cmn-TestPrintf.c $ */ +/** @file + * BS3Kit - BS3TestPrintf, BS3TestPrintfV + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-test.h" + +#include <iprt/asm-amd64-x86.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define SMALL_BUFFER 1 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Output buffering for Bs3TestPrintfV. */ +typedef struct BS3TESTPRINTBUF +{ + bool fNewCmd; +#if SMALL_BUFFER + uint8_t cchBuf; + char achBuf[78]; +#else + uint16_t cchBuf; + char achBuf[512]; +#endif +} BS3TESTPRINTBUF; + + +/** + * @callback_method_impl{FNBS3STRFORMATOUTPUT, Prints to screen and VMMDev} + */ +static BS3_DECL_CALLBACK(size_t) bs3TestPrintfStrOutput(char ch, void BS3_FAR *pvUser) +{ + BS3TESTPRINTBUF BS3_FAR *pBuf = (BS3TESTPRINTBUF BS3_FAR *)pvUser; + + /* + * VMMDev first. We do line by line processing to avoid running out of + * string buffer on the host side. + */ + if (g_fbBs3VMMDevTesting) + { + if (ch != '\n' && !pBuf->fNewCmd) + ASMOutU8(VMMDEV_TESTING_IOPORT_DATA, ch); + else if (ch != '\0') + { + if (pBuf->fNewCmd) + { +#if ARCH_BITS == 16 + ASMOutU16(VMMDEV_TESTING_IOPORT_CMD, (uint16_t)VMMDEV_TESTING_CMD_PRINT); +#else + ASMOutU32(VMMDEV_TESTING_IOPORT_CMD, VMMDEV_TESTING_CMD_PRINT); +#endif + pBuf->fNewCmd = false; + } + ASMOutU8(VMMDEV_TESTING_IOPORT_DATA, ch); + if (ch == '\n') + { + ASMOutU8(VMMDEV_TESTING_IOPORT_DATA, '\0'); + pBuf->fNewCmd = true; + } + } + } + + /* + * Console next. + */ + if (ch != '\0') + { + BS3_ASSERT(pBuf->cchBuf < RT_ELEMENTS(pBuf->achBuf)); + pBuf->achBuf[pBuf->cchBuf++] = ch; + + /* Whether to flush the buffer. We do line flushing here to avoid + dropping too much info when the formatter crashes on bad input. */ + if ( pBuf->cchBuf < RT_ELEMENTS(pBuf->achBuf) + && (!SMALL_BUFFER || ch != '\n') ) + return 1; + } + BS3_ASSERT(pBuf->cchBuf <= RT_ELEMENTS(pBuf->achBuf)); + Bs3PrintStrN(&pBuf->achBuf[0], pBuf->cchBuf); + pBuf->cchBuf = 0; + return ch != '\0'; +} + + + +#undef Bs3TestPrintfV +BS3_CMN_DEF(void, Bs3TestPrintfV,(const char BS3_FAR *pszFormat, va_list BS3_FAR va)) +{ + BS3TESTPRINTBUF Buf; + Buf.fNewCmd = true; + Buf.cchBuf = 0; + Bs3StrFormatV(pszFormat, va, bs3TestPrintfStrOutput, &Buf); +} + + + +#undef Bs3TestPrintf +BS3_CMN_DEF(void, Bs3TestPrintf,(const char BS3_FAR *pszFormat, ...)) +{ + va_list va; + va_start(va, pszFormat); + BS3_CMN_NM(Bs3TestPrintfV)(pszFormat, va); + va_end(va); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestQueryCfgU32.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestQueryCfgU32.asm new file mode 100644 index 00000000..ce2f9199 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestQueryCfgU32.asm @@ -0,0 +1,94 @@ +; $Id: bs3-cmn-TestQueryCfgU32.asm $ +;; @file +; BS3Kit - Bs3TestQueryCfgU8. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" +%include "VBox/VMMDevTesting.mac" + +BS3_EXTERN_DATA16 g_fbBs3VMMDevTesting +TMPL_BEGIN_TEXT + +;; +; @cproto BS3_DECL(uint32_t) Bs3TestQueryCfgU32(uint16_t uCfg); +; +BS3_PROC_BEGIN_CMN Bs3TestQueryCfgU32, BS3_PBC_HYBRID + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP +TNOT16 push xDX + + cmp byte [BS3_DATA16_WRT(g_fbBs3VMMDevTesting)], 0 + je .no_vmmdev + + ; Issue the query command. + mov dx, VMMDEV_TESTING_IOPORT_CMD +%if TMPL_BITS == 16 + mov ax, VMMDEV_TESTING_CMD_QUERY_CFG - VMMDEV_TESTING_CMD_MAGIC_HI_WORD + out dx, ax +%else + mov eax, VMMDEV_TESTING_CMD_QUERY_CFG + out dx, eax +%endif + + ; Write what we wish to query. + mov ax, [xBP + xCB + cbCurRetAddr] + mov dx, VMMDEV_TESTING_IOPORT_DATA + out dx, ax + + ; Read back the result. +%if TMPL_BITS == 16 + in ax, dx + push ax + in ax, dx + mov dx, ax + pop ax +%else + in eax, dx +%endif + +.return: +TNOT16 pop xDX + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET + +.no_vmmdev: + xor xAX, xAX +%if TMPL_BITS == 16 + xor xDX, xDX +%endif + jmp .return +BS3_PROC_END_CMN Bs3TestQueryCfgU32 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestQueryCfgU8.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestQueryCfgU8.asm new file mode 100644 index 00000000..25c7072d --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestQueryCfgU8.asm @@ -0,0 +1,86 @@ +; $Id: bs3-cmn-TestQueryCfgU8.asm $ +;; @file +; BS3Kit - Bs3TestQueryCfgU8, Bs3TestQueryCfgBool. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" +%include "VBox/VMMDevTesting.mac" + +BS3_EXTERN_DATA16 g_fbBs3VMMDevTesting +TMPL_BEGIN_TEXT + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestQueryCfgU8(uint16_t uCfg); +; @cproto BS3_DECL(bool) Bs3TestQueryCfgBool(uint16_t uCfg); +; +BS3_GLOBAL_NAME_EX BS3_CMN_NM(Bs3TestQueryCfgBool), function, 3 +BS3_PROC_BEGIN_CMN Bs3TestQueryCfgU8, BS3_PBC_HYBRID + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push xDX + + xor al, al + cmp byte [BS3_DATA16_WRT(g_fbBs3VMMDevTesting)], 0 + je .no_vmmdev + + ; Issue the query command. + mov dx, VMMDEV_TESTING_IOPORT_CMD +%if TMPL_BITS == 16 + mov ax, VMMDEV_TESTING_CMD_QUERY_CFG - VMMDEV_TESTING_CMD_MAGIC_HI_WORD + out dx, ax +%else + mov eax, VMMDEV_TESTING_CMD_QUERY_CFG + out dx, eax +%endif + + ; Write what we wish to query. + mov ax, [xBP + xCB + cbCurRetAddr] + mov dx, VMMDEV_TESTING_IOPORT_DATA + out dx, ax + + ; Read back the result. + in al, dx + +.no_vmmdev: + pop xDX + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3TestQueryCfgU8 + +%if TMPL_BITS == 16 +BS3_GLOBAL_NAME_EX BS3_CMN_NM_FAR(Bs3TestQueryCfgBool), function, 3 + jmp BS3_CMN_NM_FAR(Bs3TestQueryCfgU8) +%endif diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestSendCmdWithStr.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestSendCmdWithStr.asm new file mode 100644 index 00000000..7f083c52 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestSendCmdWithStr.asm @@ -0,0 +1,90 @@ +; $Id: bs3-cmn-TestSendCmdWithStr.asm $ +;; @file +; BS3Kit - bs3TestSendStrCmd. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" +%include "VBox/VMMDevTesting.mac" + +BS3_EXTERN_DATA16 g_fbBs3VMMDevTesting +TMPL_BEGIN_TEXT + +;; +; @cproto BS3_DECL(void) bs3TestSendCmdWithStr_c16(uint32_t uCmd, const char BS3_FAR *pszString); +; +BS3_PROC_BEGIN_CMN bs3TestSendCmdWithStr, BS3_PBC_HYBRID + BS3_CALL_CONV_PROLOG 2 + push xBP + mov xBP, xSP + push xAX + push xDX + push xSI +BONLY16 push ds + + cmp byte [BS3_DATA16_WRT(g_fbBs3VMMDevTesting)], 0 + je .no_vmmdev + + ; The command (uCmd). + mov dx, VMMDEV_TESTING_IOPORT_CMD +%if TMPL_BITS == 16 + mov ax, [xBP + xCB + cbCurRetAddr] ; We ignore the top bits in 16-bit mode. + out dx, ax +%else + mov eax, [xBP + xCB + cbCurRetAddr] + out dx, eax +%endif + + ; The string. + mov dx, VMMDEV_TESTING_IOPORT_DATA +%if TMPL_BITS == 16 + lds si, [xBP + xCB + cbCurRetAddr + sCB] +%else + mov xSI, [xBP + xCB + cbCurRetAddr + sCB] +%endif +.next_char: + lodsb + out dx, al + test al, al + jnz .next_char + +.no_vmmdev: +BONLY16 pop ds + pop xSI + pop xDX + pop xAX + pop xBP + BS3_CALL_CONV_EPILOG 2 + BS3_HYBRID_RET +BS3_PROC_END_CMN bs3TestSendCmdWithStr + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestSendCmdWithU32.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestSendCmdWithU32.asm new file mode 100644 index 00000000..729e0cb8 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestSendCmdWithU32.asm @@ -0,0 +1,88 @@ +; $Id: bs3-cmn-TestSendCmdWithU32.asm $ +;; @file +; BS3Kit - bs3TestSendCmdWithU32. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" +%include "VBox/VMMDevTesting.mac" + +BS3_EXTERN_DATA16 g_fbBs3VMMDevTesting +TMPL_BEGIN_TEXT + +;; +; @cproto BS3_DECL(void) bs3TestSendCmdWithU32_c16(uint32_t uCmd, uint32_t uValue); +; +BS3_PROC_BEGIN_CMN bs3TestSendCmdWithU32, BS3_PBC_HYBRID + BS3_CALL_CONV_PROLOG 2 + push xBP + mov xBP, xSP + push xAX + push xDX + push xSI + + cmp byte [BS3_DATA16_WRT(g_fbBs3VMMDevTesting)], 0 + je .no_vmmdev + + ; The command (uCmd) - + mov dx, VMMDEV_TESTING_IOPORT_CMD +%if TMPL_BITS == 16 + mov ax, [xBP + xCB + cbCurRetAddr] ; We ignore the top bits in 16-bit mode. + out dx, ax +%else + mov eax, [xBP + xCB*2] + out dx, eax +%endif + + + ; The value (uValue). + mov dx, VMMDEV_TESTING_IOPORT_DATA +%if TMPL_BITS == 16 + mov ax, [xBP + xCB + cbCurRetAddr + sCB] + out dx, ax + mov ax, [xBP + xCB + cbCurRetAddr + sCB + 2] + out dx, ax +%else + mov eax, [xBP + xCB*2 + sCB] + out dx, eax +%endif + +.no_vmmdev: + pop xSI + pop xDX + pop xAX + pop xBP + BS3_CALL_CONV_EPILOG 2 + BS3_HYBRID_RET +BS3_PROC_END_CMN bs3TestSendCmdWithU32 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestSkipped.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestSkipped.c new file mode 100644 index 00000000..bb60a7c2 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestSkipped.c @@ -0,0 +1,100 @@ +/* $Id: bs3-cmn-TestSkipped.c $ */ +/** @file + * BS3Kit - Bs3TestSkipped + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-test.h" +#include <iprt/asm-amd64-x86.h> + + +/** + * Equivalent to RTTestSkippedV. + */ +#undef Bs3TestSkippedV +BS3_CMN_DEF(void, Bs3TestSkippedV,(const char *pszFormat, va_list BS3_FAR va)) +{ + if (g_cusBs3TestErrors == g_cusBs3SubTestAtErrors) + { + /* Just mark it as skipped and deal with it when the sub-test is done. */ + g_fbBs3SubTestSkipped = true; + + /* Tell VMMDev */ + if (g_fbBs3VMMDevTesting) +#if ARCH_BITS == 16 + ASMOutU16(VMMDEV_TESTING_IOPORT_CMD, (uint16_t)VMMDEV_TESTING_CMD_SKIPPED); +#else + ASMOutU32(VMMDEV_TESTING_IOPORT_CMD, VMMDEV_TESTING_CMD_SKIPPED); +#endif + + /* The reason why it was skipped is optional. */ + if (pszFormat) + { + BS3TESTFAILEDBUF Buf; + Buf.fNewLine = false; + Buf.cchBuf = 0; + Bs3StrFormatV(pszFormat, va, bs3TestFailedStrOutput, &Buf); + } + else if (g_fbBs3VMMDevTesting) + ASMOutU8(VMMDEV_TESTING_IOPORT_DATA, 0); + } +} + + +/** + * Equivalent to RTTestSkipped. + */ +#undef Bs3TestSkippedF +BS3_CMN_DEF(void, Bs3TestSkippedF,(const char *pszFormat, ...)) +{ + va_list va; + va_start(va, pszFormat); + BS3_CMN_NM(Bs3TestSkippedV)(pszFormat, va); + va_end(va); +} + + +/** + * Equivalent to RTTestSkipped. + */ +#undef Bs3TestSkipped +BS3_CMN_DEF(void, Bs3TestSkipped,(const char *pszWhy)) +{ + BS3_CMN_NM(Bs3TestSkippedF)(pszWhy ? "%s" : NULL, pszWhy); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestSub.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestSub.c new file mode 100644 index 00000000..8a9df675 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestSub.c @@ -0,0 +1,105 @@ +/* $Id: bs3-cmn-TestSub.c $ */ +/** @file + * BS3Kit - Bs3TestSub, Bs3TestSubF, Bs3TestSubV. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-test.h" + + + +/** + * Equivalent to RTTestISubV. + */ +#undef Bs3TestSubV +BS3_CMN_DEF(void, Bs3TestSubV,(const char *pszFormat, va_list BS3_FAR va)) +{ + size_t cch; + + /* + * Cleanup any previous sub-test. + */ + bs3TestSubCleanup(); + + /* + * Format the sub-test name and update globals. + */ + cch = Bs3StrPrintfV(g_szBs3SubTest, sizeof(g_szBs3SubTest), pszFormat, va); + g_cusBs3SubTestAtErrors = g_cusBs3TestErrors; + BS3_ASSERT(!g_fbBs3SubTestSkipped); + g_cusBs3SubTests++; + + /* + * Tell VMMDev and output to the console. + */ + bs3TestSendCmdWithStr(VMMDEV_TESTING_CMD_SUB_NEW, g_szBs3SubTest); + + Bs3PrintStr(g_szBs3SubTest); + Bs3PrintChr(':'); + do + Bs3PrintChr(' '); + while (cch++ < 48); + Bs3PrintStr(" TESTING\n"); + + /* The sub-test result is not yet reported. */ + g_fbBs3SubTestReported = false; +} + + +/** + * Equivalent to RTTestIFailedF. + */ +#undef Bs3TestSubF +BS3_CMN_DEF(void, Bs3TestSubF,(const char *pszFormat, ...)) +{ + va_list va; + va_start(va, pszFormat); + BS3_CMN_NM(Bs3TestSubV)(pszFormat, va); + va_end(va); +} + + +/** + * Equivalent to RTTestISub. + */ +#undef Bs3TestSub +BS3_CMN_DEF(void, Bs3TestSub,(const char *pszMessage)) +{ + BS3_CMN_NM(Bs3TestSubF)("%s", pszMessage); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestSubDone.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestSubDone.c new file mode 100644 index 00000000..88bc57ef --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestSubDone.c @@ -0,0 +1,54 @@ +/* $Id: bs3-cmn-TestSubDone.c $ */ +/** @file + * BS3Kit - Bs3TestSubDone. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-test.h" + + + +/** + * Equivalent to RTTestISubDone. + */ +#undef Bs3TestSubDone +BS3_CMN_DEF(void, Bs3TestSubDone,(void)) +{ + bs3TestSubCleanup(); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestSubErrorCount.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestSubErrorCount.c new file mode 100644 index 00000000..757eb23e --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestSubErrorCount.c @@ -0,0 +1,54 @@ +/* $Id: bs3-cmn-TestSubErrorCount.c $ */ +/** @file + * BS3Kit - Bs3TestSubErrorCount. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-test.h" + + + +/** + * Equivalent to RTTestSubErrorCount. + */ +#undef Bs3TestSubErrorCount +BS3_CMN_DEF(uint16_t, Bs3TestSubErrorCount,(void)) +{ + return g_cusBs3TestErrors - g_cusBs3SubTestAtErrors; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestTerm.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestTerm.c new file mode 100644 index 00000000..5823ce22 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestTerm.c @@ -0,0 +1,117 @@ +/* $Id: bs3-cmn-TestTerm.c $ */ +/** @file + * BS3Kit - Bs3TestTerm + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-test.h" + + + +/** + * Equivalent to rtTestSubCleanup + rtTestSubTestReport. + */ +BS3_DECL(void) bs3TestSubCleanup(void) +{ + if (g_szBs3SubTest[0] != '\0') + { + if (!g_fbBs3SubTestReported) + { + size_t cch; + uint16_t cErrors = g_cusBs3TestErrors - g_cusBs3SubTestAtErrors; + + /* Tell VMMDev. */ + bs3TestSendCmdWithU32(VMMDEV_TESTING_CMD_SUB_DONE, cErrors); + + /* Print result to the console. */ + Bs3PrintStr(g_szBs3SubTest); + Bs3PrintChr(':'); + cch = Bs3StrLen(g_szBs3SubTest); + do + Bs3PrintChr(' '); + while (cch++ < 49); + + if (!cErrors) + Bs3PrintStr(!g_fbBs3SubTestSkipped ? "PASSED\n" : "SKIPPED\n"); + else + { + g_cusBs3SubTestsFailed++; + Bs3Printf("FAILED (%u errors)\n", g_szBs3SubTest, cErrors); + } + } + + /* Reset the sub-test. */ + g_fbBs3SubTestReported = true; + g_fbBs3SubTestSkipped = false; + g_szBs3SubTest[0] = '\0'; + } +} + + +/** + * Equivalent to RTTestSummaryAndDestroy. + */ +#undef Bs3TestTerm +BS3_CMN_DEF(void, Bs3TestTerm,(void)) +{ + /* + * Close any current sub-test. + */ + bs3TestSubCleanup(); + + /* + * Report summary. + */ + if (BS3_CMN_NM(g_pszBs3Test)) + { + Bs3PrintStr(BS3_CMN_NM(g_pszBs3Test)); + if (g_cusBs3TestErrors == 0) + Bs3Printf(": SUCCESS (%u tests)\n", g_cusBs3SubTests); + else + Bs3Printf(": FAILURE - %u (%u of %u tests)\n", + g_cusBs3TestErrors, g_cusBs3SubTestsFailed, g_cusBs3SubTests); + } + + /* + * Tell VMMDev. + */ + bs3TestSendCmdWithU32(VMMDEV_TESTING_CMD_TERM, g_cusBs3TestErrors); + + BS3_CMN_NM(g_pszBs3Test) = NULL; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestValue.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestValue.c new file mode 100644 index 00000000..580d0f76 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TestValue.c @@ -0,0 +1,86 @@ +/* $Id: bs3-cmn-TestValue.c $ */ +/** @file + * BS3Kit - Bs3TestValue + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-test.h" + +#include <iprt/asm-amd64-x86.h> + + +#undef Bs3TestValue +BS3_CMN_DEF(void, Bs3TestValue,(const char BS3_FAR *pszName, uint64_t u64Value, uint8_t bUnit)) +{ + const char * const pszUnit = g_aszBs3TestUnitNames[bUnit]; + Bs3Printf(" %-48s: %'16llu %s\n", pszName, u64Value, pszUnit); + + /* + * Report it to the host. + */ + if (g_fbBs3VMMDevTesting) + { +#if ARCH_BITS == 16 + ASMOutU16(VMMDEV_TESTING_IOPORT_CMD, (uint16_t)VMMDEV_TESTING_CMD_VALUE); + ASMOutU16(VMMDEV_TESTING_IOPORT_DATA, (uint16_t)u64Value); + ASMOutU16(VMMDEV_TESTING_IOPORT_DATA, (uint16_t)(u64Value >> 16)); + ASMOutU16(VMMDEV_TESTING_IOPORT_DATA, (uint16_t)(u64Value >> 32)); + ASMOutU16(VMMDEV_TESTING_IOPORT_DATA, (uint16_t)(u64Value >> 48)); + ASMOutU16(VMMDEV_TESTING_IOPORT_DATA, (uint16_t)bUnit); + ASMOutU16(VMMDEV_TESTING_IOPORT_DATA, 0); +# if 1 + ASMOutStrU8(VMMDEV_TESTING_IOPORT_DATA, pszName, Bs3StrLen(pszName) + 1); +# else + for (;;) + { + uint8_t const b = *pszName++; + ASMOutU8(VMMDEV_TESTING_IOPORT_DATA, b); + if (!b) + break; + } +# endif +#else + ASMOutU32(VMMDEV_TESTING_IOPORT_CMD, VMMDEV_TESTING_CMD_VALUE); + ASMOutU32(VMMDEV_TESTING_IOPORT_DATA, (uint32_t)u64Value); + ASMOutU32(VMMDEV_TESTING_IOPORT_DATA, (uint32_t)(u64Value >> 32)); + ASMOutU32(VMMDEV_TESTING_IOPORT_DATA, (uint32_t)bUnit); + ASMOutStrU8(VMMDEV_TESTING_IOPORT_DATA, pszName, Bs3StrLen(pszName) + 1); +#endif + } +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Trap16Init.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Trap16Init.c new file mode 100644 index 00000000..82622ce9 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Trap16Init.c @@ -0,0 +1,129 @@ +/* $Id: bs3-cmn-Trap16Init.c $ */ +/** @file + * BS3Kit - Bs3Trap16Init + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/* We ASSUME that BS3CLASS16CODE is 64KB aligned, so the low 16-bit of the + flat address matches. Also, these symbols are defined both with + and without underscore prefixes. */ +extern BS3_DECL(void) BS3_FAR_CODE Bs3Trap16DoubleFaultHandler80386(void); +extern BS3_DECL(void) BS3_FAR_CODE Bs3Trap16DoubleFaultHandler80286(void); +extern BS3_DECL(void) BS3_FAR_CODE Bs3Trap16GenericEntries(void); + +/* These two are ugly. Need data access for patching purposes. */ +extern uint8_t BS3_FAR_DATA bs3Trap16GenericTrapOrInt[]; + + +#undef Bs3Trap16InitEx +BS3_CMN_DEF(void, Bs3Trap16InitEx,(bool f386Plus)) +{ + X86TSS16 BS3_FAR *pTss; + unsigned iIdt; + + /* + * If 386 or later, patch the trap handler code to not jump to the 80286 + * code but continue with the next instruction (the 386+ code). + */ + if (f386Plus) + { + uint8_t BS3_FAR_DATA *pbFunction = &bs3Trap16GenericTrapOrInt[0]; +#if ARCH_BITS == 16 + if (g_bBs3CurrentMode != BS3_MODE_RM) + pbFunction = (uint8_t BS3_FAR_DATA *)BS3_FP_MAKE(BS3_SEL_TILED + 1, BS3_FP_OFF(pbFunction)); +#endif + pbFunction[1] = 0; + pbFunction[2] = 0; + } + + /* + * IDT entries, except the system call gate. + */ + for (iIdt = 0; iIdt < 256; iIdt++) + if (iIdt != BS3_TRAP_SYSCALL) + Bs3Trap16SetGate(iIdt, X86_SEL_TYPE_SYS_286_INT_GATE, 0 /*bDpl*/, + BS3_SEL_R0_CS16, (uint16_t)(uintptr_t)Bs3Trap16GenericEntries + iIdt * 8, 0 /*cParams*/); + + /* + * Initialize the normal TSS so we can do ring transitions via the IDT. + */ + pTss = &Bs3Tss16; + Bs3MemZero(pTss, sizeof(*pTss)); + pTss->sp0 = BS3_ADDR_STACK_R0; + pTss->ss0 = BS3_SEL_R0_SS16; + pTss->sp1 = BS3_ADDR_STACK_R1; + pTss->ss1 = BS3_SEL_R1_SS16 | 1; + pTss->sp2 = BS3_ADDR_STACK_R2; + pTss->ss2 = BS3_SEL_R2_SS16 | 2; + + /* + * Initialize the double fault TSS. + * cr3 is filled in by switcher code, when needed. + */ + pTss = &Bs3Tss16DoubleFault; + Bs3MemZero(pTss, sizeof(*pTss)); + pTss->sp0 = BS3_ADDR_STACK_R0; + pTss->ss0 = BS3_SEL_R0_SS16; + pTss->sp1 = BS3_ADDR_STACK_R1; + pTss->ss1 = BS3_SEL_R1_SS16 | 1; + pTss->sp2 = BS3_ADDR_STACK_R2; + pTss->ss2 = BS3_SEL_R2_SS16 | 2; + pTss->ip = (uint16_t)(uintptr_t)(f386Plus ? &Bs3Trap16DoubleFaultHandler80386 : &Bs3Trap16DoubleFaultHandler80286); + pTss->flags = X86_EFL_1; + pTss->sp = BS3_ADDR_STACK_R0_IST1; + pTss->es = BS3_SEL_R0_DS16; + pTss->ds = BS3_SEL_R0_DS16; + pTss->cs = BS3_SEL_R0_CS16; + pTss->ss = BS3_SEL_R0_SS16; + pTss->dx = f386Plus; + + Bs3Trap16SetGate(X86_XCPT_DF, X86_SEL_TYPE_SYS_TASK_GATE, 0 /*bDpl*/, BS3_SEL_TSS16_DF, 0, 0 /*cParams*/); +} + + +#undef Bs3Trap16Init +BS3_CMN_DEF(void, Bs3Trap16Init,(void)) +{ + BS3_CMN_NM(Bs3Trap16InitEx)((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80386); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Trap16SetGate.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Trap16SetGate.c new file mode 100644 index 00000000..afc19968 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Trap16SetGate.c @@ -0,0 +1,62 @@ +/* $Id: bs3-cmn-Trap16SetGate.c $ */ +/** @file + * BS3Kit - Bs3Trap16SetGate + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3Trap16SetGate +BS3_CMN_DEF(void, Bs3Trap16SetGate,(uint8_t iIdt, uint8_t bType, uint8_t bDpl, uint16_t uSel, uint16_t off, uint8_t cParams)) +{ + X86DESC BS3_FAR *pIdte = &Bs3Idt16[iIdt]; + + BS3_ASSERT(bDpl <= 3); + BS3_ASSERT(bType <= 15); + BS3_ASSERT(cParams <= 15); + pIdte->Gate.u16OffsetLow = (uint16_t)off; + pIdte->Gate.u16OffsetHigh = 0; + pIdte->Gate.u16Sel = uSel; + pIdte->Gate.u5ParmCount = cParams; + pIdte->Gate.u4Type = bType; + pIdte->Gate.u2Dpl = bDpl; + pIdte->Gate.u3Reserved = 0; + pIdte->Gate.u1DescType = 0; /* system */ + pIdte->Gate.u1Present = 1; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Trap32Init.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Trap32Init.c new file mode 100644 index 00000000..b0f74460 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Trap32Init.c @@ -0,0 +1,101 @@ +/* $Id: bs3-cmn-Trap32Init.c $ */ +/** @file + * BS3Kit - Bs3Trap32Init + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +/********************************************************************************************************************************* +* External Symbols * +*********************************************************************************************************************************/ +#define g_Bs3Trap32DoubleFaultHandlerFlatAddr BS3_DATA_NM(g_Bs3Trap32DoubleFaultHandlerFlatAddr) +extern uint32_t g_Bs3Trap32DoubleFaultHandlerFlatAddr; + + +#undef Bs3Trap32Init +BS3_CMN_DEF(void, Bs3Trap32Init,(void)) +{ + X86TSS32 BS3_FAR *pTss; + unsigned iIdt; + + /* + * IDT entries, except the system call gate. + */ + for (iIdt = 0; iIdt < BS3_TRAP_SYSCALL; iIdt++) + Bs3Trap32SetGate(iIdt, X86_SEL_TYPE_SYS_386_INT_GATE, 0 /*bDpl*/, + BS3_SEL_R0_CS32, g_Bs3Trap32GenericEntriesFlatAddr + iIdt * 10, 0 /*cParams*/); + for (iIdt = BS3_TRAP_SYSCALL + 1; iIdt < 256; iIdt++) + Bs3Trap32SetGate(iIdt, X86_SEL_TYPE_SYS_386_INT_GATE, 0 /*bDpl*/, + BS3_SEL_R0_CS32, g_Bs3Trap32GenericEntriesFlatAddr + iIdt * 10, 0 /*cParams*/); + + /* + * Initialize the normal TSS so we can do ring transitions via the IDT. + */ + pTss = &Bs3Tss32; + Bs3MemZero(pTss, sizeof(*pTss)); + pTss->esp0 = BS3_ADDR_STACK_R0; + pTss->ss0 = BS3_SEL_R0_SS32; + pTss->esp1 = BS3_ADDR_STACK_R1; + pTss->ss1 = BS3_SEL_R1_SS32 | 1; + pTss->esp2 = BS3_ADDR_STACK_R2; + pTss->ss2 = BS3_SEL_R2_SS32 | 2; + + /* + * Initialize the double fault TSS. + * cr3 is filled in by switcher code, when needed. + */ + pTss = &Bs3Tss32DoubleFault; + Bs3MemZero(pTss, sizeof(*pTss)); + pTss->esp0 = BS3_ADDR_STACK_R0; + pTss->ss0 = BS3_SEL_R0_SS32; + pTss->esp1 = BS3_ADDR_STACK_R1; + pTss->ss1 = BS3_SEL_R1_SS32 | 1; + pTss->esp2 = BS3_ADDR_STACK_R2; + pTss->ss2 = BS3_SEL_R2_SS32 | 2; + pTss->eip = g_Bs3Trap32DoubleFaultHandlerFlatAddr; + pTss->eflags = X86_EFL_1; + pTss->esp = BS3_ADDR_STACK_R0_IST1; + pTss->es = BS3_SEL_R0_DS32; + pTss->ds = BS3_SEL_R0_DS32; + pTss->cs = BS3_SEL_R0_CS32; + pTss->ss = BS3_SEL_R0_SS32; + + Bs3Trap32SetGate(X86_XCPT_DF, X86_SEL_TYPE_SYS_TASK_GATE, 0 /*bDpl*/, BS3_SEL_TSS32_DF, 0, 0 /*cParams*/); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Trap32SetGate.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Trap32SetGate.c new file mode 100644 index 00000000..3504787b --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Trap32SetGate.c @@ -0,0 +1,62 @@ +/* $Id: bs3-cmn-Trap32SetGate.c $ */ +/** @file + * BS3Kit - Bs3Trap32SetGate + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3Trap32SetGate +BS3_CMN_DEF(void, Bs3Trap32SetGate,(uint8_t iIdt, uint8_t bType, uint8_t bDpl, uint16_t uSel, uint32_t off, uint8_t cParams)) +{ + X86DESC BS3_FAR *pIdte = &Bs3Idt32[iIdt]; + + BS3_ASSERT(bDpl <= 3); + BS3_ASSERT(bType <= 15); + BS3_ASSERT(cParams <= 15); + pIdte->Gate.u16OffsetLow = (uint16_t)off; + pIdte->Gate.u16OffsetHigh = (uint16_t)(off >> 16); + pIdte->Gate.u16Sel = uSel; + pIdte->Gate.u5ParmCount = cParams; + pIdte->Gate.u4Type = bType; + pIdte->Gate.u2Dpl = bDpl; + pIdte->Gate.u3Reserved = 0; + pIdte->Gate.u1DescType = 0; /* system */ + pIdte->Gate.u1Present = 1; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Trap64Init.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Trap64Init.c new file mode 100644 index 00000000..d6d3c337 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Trap64Init.c @@ -0,0 +1,109 @@ +/* $Id: bs3-cmn-Trap64Init.c $ */ +/** @file + * BS3Kit - Bs3Trap64Init + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3Trap64Init +BS3_CMN_DEF(void, Bs3Trap64Init,(void)) +{ + Bs3Trap64InitEx(false); +} + +#undef Bs3Trap64InitEx +BS3_CMN_DEF(void, Bs3Trap64InitEx,(bool fMoreIstUsage)) +{ + static const uint8_t s_aAssignments[] = + { + /* [X86_XCPT_DE] = */ 3, + /* [X86_XCPT_DB] = */ 2, + /* [X86_XCPT_NMI] = */ 0, + /* [X86_XCPT_BP] = */ 2, + /* [X86_XCPT_OF] = */ 3, + /* [X86_XCPT_BR] = */ 3, + /* [X86_XCPT_UD] = */ 4, + /* [X86_XCPT_NM] = */ 3, + /* [X86_XCPT_DF] = */ 1, + /* [0x09] = */ 0, + /* [X86_XCPT_TS] = */ 1, + /* [X86_XCPT_NP] = */ 5, + /* [X86_XCPT_SS] = */ 5, + /* [X86_XCPT_GP] = */ 6, + /* [X86_XCPT_PF] = */ 7, + /* [0x0f] = */ 0, + /* [X86_XCPT_MF] = */ 0, + /* [X86_XCPT_AC] = */ 3, + /* [X86_XCPT_MC] = */ 0, + /* [X86_XCPT_XF] = */ 0, + /* [X86_XCPT_VE] = */ 0, + /* [X86_XCPT_CP] = */ 6, + }; + X86TSS64 BS3_FAR *pTss; + unsigned iIdt; + + /* + * IDT entries, except the system call gate. + * The #DF entry get IST=1, all others IST=0. + */ + for (iIdt = 0; iIdt < BS3_TRAP_SYSCALL; iIdt++) + Bs3Trap64SetGate(iIdt, AMD64_SEL_TYPE_SYS_INT_GATE, 0 /*bDpl*/, + BS3_SEL_R0_CS64, g_Bs3Trap64GenericEntriesFlatAddr + iIdt * 8, + !fMoreIstUsage ? iIdt == X86_XCPT_DF : iIdt < RT_ELEMENTS(s_aAssignments) ? s_aAssignments[iIdt] : 0); + for (iIdt = BS3_TRAP_SYSCALL + 1; iIdt < 256; iIdt++) + Bs3Trap64SetGate(iIdt, AMD64_SEL_TYPE_SYS_INT_GATE, 0 /*bDpl*/, + BS3_SEL_R0_CS64, g_Bs3Trap64GenericEntriesFlatAddr + iIdt * 8, 0); + + /* + * Initialize the normal TSS so we can do ring transitions via the IDT. + */ + pTss = &Bs3Tss64; + Bs3MemZero(pTss, sizeof(*pTss)); + pTss->rsp0 = BS3_ADDR_STACK_R0; + pTss->rsp1 = BS3_ADDR_STACK_R1; + pTss->rsp2 = BS3_ADDR_STACK_R2; + pTss->ist1 = BS3_ADDR_STACK_R0_IST1; + pTss->ist2 = BS3_ADDR_STACK_R0_IST2; + pTss->ist3 = BS3_ADDR_STACK_R0_IST3; + pTss->ist4 = BS3_ADDR_STACK_R0_IST4; + pTss->ist5 = BS3_ADDR_STACK_R0_IST5; + pTss->ist6 = BS3_ADDR_STACK_R0_IST6; + pTss->ist7 = BS3_ADDR_STACK_R0_IST7; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Trap64SetGate.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Trap64SetGate.c new file mode 100644 index 00000000..1b6f6dea --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-Trap64SetGate.c @@ -0,0 +1,65 @@ +/* $Id: bs3-cmn-Trap64SetGate.c $ */ +/** @file + * BS3Kit - Bs3Trap64SetGate + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3Trap64SetGate +BS3_CMN_DEF(void, Bs3Trap64SetGate,(uint8_t iIdt, uint8_t bType, uint8_t bDpl, uint16_t uSel, uint64_t off, uint8_t bIst)) +{ + X86DESC64 BS3_FAR *pIdte = &Bs3Idt64[iIdt]; + + BS3_ASSERT(bDpl <= 3); + BS3_ASSERT(bType <= 15); + BS3_ASSERT(bIst <= 7); + pIdte->Gate.u16OffsetLow = (uint16_t)off; + pIdte->Gate.u16OffsetHigh = (uint16_t)((uint32_t)off >> 16); + pIdte->Gate.u32OffsetTop = (uint32_t)(off >> 32); + pIdte->Gate.u16Sel = uSel; + pIdte->Gate.u3IST = bIst; + pIdte->Gate.u4Type = bType; + pIdte->Gate.u2Dpl = bDpl; + pIdte->Gate.u5Reserved = 0; + pIdte->Gate.u1DescType = 0; /* system */ + pIdte->Gate.u1Present = 1; + pIdte->Gate.u32Reserved = 0; + +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapDefaultHandler.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapDefaultHandler.c new file mode 100644 index 00000000..88a5c873 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapDefaultHandler.c @@ -0,0 +1,331 @@ +/* $Id: bs3-cmn-TrapDefaultHandler.c $ */ +/** @file + * BS3Kit - Bs3TrapDefaultHandler + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#if TMPL_BITS != 64 +# include <VBox/VMMDevTesting.h> +# include <iprt/asm-amd64-x86.h> +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#define g_pBs3TrapSetJmpFrame BS3_DATA_NM(g_pBs3TrapSetJmpFrame) +extern uint32_t g_pBs3TrapSetJmpFrame; + +#define g_Bs3TrapSetJmpCtx BS3_DATA_NM(g_Bs3TrapSetJmpCtx) +extern BS3REGCTX g_Bs3TrapSetJmpCtx; + + +#if TMPL_BITS != 64 +/** + * V86 syscall handler. + */ +static void bs3TrapDefaultHandlerV8086Syscall(PBS3TRAPFRAME pTrapFrame) +{ + /* Minimal syscall. */ + uint16_t uSyscallNo = pTrapFrame->Ctx.rax.u16; + if (uSyscallNo == BS3_SYSCALL_PRINT_CHR) + Bs3PrintChr(pTrapFrame->Ctx.rcx.u8); + else if (uSyscallNo == BS3_SYSCALL_PRINT_STR) + Bs3PrintStrN(Bs3XptrFlatToCurrent(((uint32_t)pTrapFrame->Ctx.rcx.u16 << 4) + pTrapFrame->Ctx.rsi.u16), + pTrapFrame->Ctx.rdx.u16); + else if (uSyscallNo == BS3_SYSCALL_RESTORE_CTX) + Bs3RegCtxRestore(Bs3XptrFlatToCurrent(((uint32_t)pTrapFrame->Ctx.rcx.u16 << 4) + pTrapFrame->Ctx.rsi.u16), + pTrapFrame->Ctx.rdx.u16); + else if ( uSyscallNo == BS3_SYSCALL_TO_RING0 + || uSyscallNo == BS3_SYSCALL_TO_RING1 + || uSyscallNo == BS3_SYSCALL_TO_RING2 + || uSyscallNo == BS3_SYSCALL_TO_RING3) + { + Bs3RegCtxConvertToRingX(&pTrapFrame->Ctx, pTrapFrame->Ctx.rax.u16 - BS3_SYSCALL_TO_RING0); + } + else if (uSyscallNo == BS3_SYSCALL_SET_DRX) + { + uint32_t uValue = pTrapFrame->Ctx.rsi.u32; + switch (pTrapFrame->Ctx.rdx.u8) + { + case 0: ASMSetDR0(uValue); break; + case 1: ASMSetDR1(uValue); break; + case 2: ASMSetDR2(uValue); break; + case 3: ASMSetDR3(uValue); break; + case 6: ASMSetDR6(uValue); break; + case 7: ASMSetDR7(uValue); break; + default: Bs3Panic(); + } + } + else if (uSyscallNo == BS3_SYSCALL_GET_DRX) + { + uint32_t uValue; + switch (pTrapFrame->Ctx.rdx.u8) + { + case 0: uValue = ASMGetDR0(); break; + case 1: uValue = ASMGetDR1(); break; + case 2: uValue = ASMGetDR2(); break; + case 3: uValue = ASMGetDR3(); break; + case 6: uValue = ASMGetDR6(); break; + case 7: uValue = ASMGetDR7(); break; + default: uValue = 0; Bs3Panic(); + } + pTrapFrame->Ctx.rax.u32 = uValue; + pTrapFrame->Ctx.rdx.u32 = uValue >> 16; + } + else if (uSyscallNo == BS3_SYSCALL_SET_CRX) + { + uint32_t uValue = pTrapFrame->Ctx.rsi.u32; + switch (pTrapFrame->Ctx.rdx.u8) + { + case 0: ASMSetCR0(uValue); pTrapFrame->Ctx.cr0.u32 = uValue; break; + case 2: ASMSetCR2(uValue); pTrapFrame->Ctx.cr2.u32 = uValue; break; + case 3: ASMSetCR3(uValue); pTrapFrame->Ctx.cr3.u32 = uValue; break; + case 4: ASMSetCR4(uValue); pTrapFrame->Ctx.cr4.u32 = uValue; break; + default: Bs3Panic(); + } + } + else if (uSyscallNo == BS3_SYSCALL_GET_CRX) + { + uint32_t uValue; + switch (pTrapFrame->Ctx.rdx.u8) + { + case 0: uValue = ASMGetCR0(); break; + case 2: uValue = ASMGetCR2(); break; + case 3: uValue = ASMGetCR3(); break; + case 4: uValue = ASMGetCR4(); break; + default: uValue = 0; Bs3Panic(); + } + pTrapFrame->Ctx.rax.u32 = uValue; + pTrapFrame->Ctx.rdx.u32 = uValue >> 16; + } + else if (uSyscallNo == BS3_SYSCALL_SET_TR) + { + Bs3RegSetTr(pTrapFrame->Ctx.rdx.u16); + pTrapFrame->Ctx.tr = pTrapFrame->Ctx.rdx.u16; + } + else if (uSyscallNo == BS3_SYSCALL_GET_TR) + pTrapFrame->Ctx.rax.u16 = ASMGetTR(); + else if (uSyscallNo == BS3_SYSCALL_SET_LDTR) + { + Bs3RegSetLdtr(pTrapFrame->Ctx.rdx.u16); + pTrapFrame->Ctx.ldtr = pTrapFrame->Ctx.rdx.u16; + } + else if (uSyscallNo == BS3_SYSCALL_GET_LDTR) + pTrapFrame->Ctx.rax.u16 = ASMGetLDTR(); + else if (uSyscallNo == BS3_SYSCALL_SET_XCR0) + ASMSetXcr0(RT_MAKE_U64(pTrapFrame->Ctx.rsi.u32, pTrapFrame->Ctx.rdx.u32)); + else if (uSyscallNo == BS3_SYSCALL_GET_XCR0) + { + uint64_t const uValue = ASMGetXcr0(); + pTrapFrame->Ctx.rax.u32 = (uint32_t)uValue; + pTrapFrame->Ctx.rdx.u32 = (uint32_t)(uValue >> 32); + } + else + Bs3Panic(); +} +#endif + +#undef Bs3TrapDefaultHandler +BS3_CMN_DEF(void, Bs3TrapDefaultHandler,(PBS3TRAPFRAME pTrapFrame)) +{ +#if TMPL_BITS != 64 + /* + * v8086 VMM tasks. + */ + //Bs3TestHostPrintf("Bs3____DefaultHandler: %02xh %04RX16:%08RX32 %08RX32 %04RX16:%08RX32 %d %d\n", pTrapFrame->bXcpt, + // pTrapFrame->Ctx.cs, pTrapFrame->Ctx.rip.u32, pTrapFrame->Ctx.rflags.u32, pTrapFrame->Ctx.ss, + // pTrapFrame->Ctx.rsp.u32, g_fBs3TrapNoV86Assist, 42); + if ((pTrapFrame->Ctx.rflags.u32 & X86_EFL_VM)) + { + bool fHandled = true; + uint8_t cBitsOpcode = 16; + uint8_t bOpCode; + uint8_t const BS3_FAR *pbCodeStart; + uint8_t const BS3_FAR *pbCode; + uint16_t BS3_FAR *pusStack; + + pusStack = (uint16_t BS3_FAR *)BS3_MAKE_PROT_R0PTR_FROM_REAL(pTrapFrame->Ctx.ss, pTrapFrame->Ctx.rsp.u16); + pbCode = (uint8_t const BS3_FAR *)BS3_MAKE_PROT_R0PTR_FROM_REAL(pTrapFrame->Ctx.cs, pTrapFrame->Ctx.rip.u16); + pbCodeStart = pbCode; + + /* + * Deal with GPs in V8086 mode. + */ + if ( pTrapFrame->bXcpt == X86_XCPT_GP + && !g_fBs3TrapNoV86Assist) + { + bOpCode = *pbCode++; + if (bOpCode == 0x66) + { + cBitsOpcode = 32; + bOpCode = *pbCode++; + } + + /* INT xx: Real mode behaviour, but intercepting and implementing most of our syscall interface. */ + if (bOpCode == 0xcd) + { + uint8_t bVector = *pbCode++; + if (bVector == BS3_TRAP_SYSCALL) + bs3TrapDefaultHandlerV8086Syscall(pTrapFrame); + else + { + /* Real mode behaviour. */ + uint16_t BS3_FAR *pusIvte = (uint16_t BS3_FAR *)BS3_MAKE_PROT_R0PTR_FROM_REAL(0, 0); + pusIvte += (uint16_t)bVector *2; + + pusStack[0] = pTrapFrame->Ctx.rflags.u16; + pusStack[1] = pTrapFrame->Ctx.cs; + pusStack[2] = pTrapFrame->Ctx.rip.u16 + (uint16_t)(pbCode - pbCodeStart); + + pTrapFrame->Ctx.rip.u16 = pusIvte[0]; + pTrapFrame->Ctx.cs = pusIvte[1]; + pTrapFrame->Ctx.rflags.u16 &= ~X86_EFL_IF; /** @todo this isn't all, but it'll do for now, I hope. */ + Bs3RegCtxRestore(&pTrapFrame->Ctx, 0/*fFlags*/); /* does not return. */ + } + } + /* PUSHF: Real mode behaviour. */ + else if (bOpCode == 0x9c) + { + if (cBitsOpcode == 32) + *pusStack++ = pTrapFrame->Ctx.rflags.au16[1] & ~(X86_EFL_VM | X86_EFL_RF); + *pusStack++ = pTrapFrame->Ctx.rflags.u16; + pTrapFrame->Ctx.rsp.u16 += cBitsOpcode / 8; + } + /* POPF: Real mode behaviour. */ + else if (bOpCode == 0x9d) + { + if (cBitsOpcode == 32) + { + pTrapFrame->Ctx.rflags.u32 &= ~X86_EFL_POPF_BITS; + pTrapFrame->Ctx.rflags.u32 |= X86_EFL_POPF_BITS & *(uint32_t const *)pusStack; + } + else + { + pTrapFrame->Ctx.rflags.u32 &= ~(X86_EFL_POPF_BITS | UINT32_C(0xffff0000)) & ~X86_EFL_RF; + pTrapFrame->Ctx.rflags.u16 |= (uint16_t)X86_EFL_POPF_BITS & *pusStack; + } + pTrapFrame->Ctx.rsp.u16 -= cBitsOpcode / 8; + } + /* CLI: Real mode behaviour. */ + else if (bOpCode == 0xfa) + pTrapFrame->Ctx.rflags.u16 &= ~X86_EFL_IF; + /* STI: Real mode behaviour. */ + else if (bOpCode == 0xfb) + pTrapFrame->Ctx.rflags.u16 |= X86_EFL_IF; + /* OUT: byte I/O to VMMDev. */ + else if ( bOpCode == 0xee + && ((unsigned)(pTrapFrame->Ctx.rdx.u16 - VMMDEV_TESTING_IOPORT_BASE) < (unsigned)VMMDEV_TESTING_IOPORT_COUNT)) + ASMOutU8(pTrapFrame->Ctx.rdx.u16, pTrapFrame->Ctx.rax.u8); + /* OUT: [d]word I/O to VMMDev. */ + else if ( bOpCode == 0xef + && ((unsigned)(pTrapFrame->Ctx.rdx.u16 - VMMDEV_TESTING_IOPORT_BASE) < (unsigned)VMMDEV_TESTING_IOPORT_COUNT)) + { + if (cBitsOpcode != 32) + ASMOutU16(pTrapFrame->Ctx.rdx.u16, pTrapFrame->Ctx.rax.u16); + else + ASMOutU32(pTrapFrame->Ctx.rdx.u16, pTrapFrame->Ctx.rax.u32); + } + /* IN: byte I/O to VMMDev. */ + else if ( bOpCode == 0xec + && ((unsigned)(pTrapFrame->Ctx.rdx.u16 - VMMDEV_TESTING_IOPORT_BASE) < (unsigned)VMMDEV_TESTING_IOPORT_COUNT)) + pTrapFrame->Ctx.rax.u8 = ASMInU8(pTrapFrame->Ctx.rdx.u16); + /* IN: [d]word I/O to VMMDev. */ + else if ( bOpCode == 0xed + && ((unsigned)(pTrapFrame->Ctx.rdx.u16 - VMMDEV_TESTING_IOPORT_BASE) < (unsigned)VMMDEV_TESTING_IOPORT_COUNT)) + { + if (cBitsOpcode != 32) + pTrapFrame->Ctx.rax.u16 = ASMInU16(pTrapFrame->Ctx.rdx.u16); + else + pTrapFrame->Ctx.rax.u32 = ASMInU32(pTrapFrame->Ctx.rdx.u32); + } + /* Unexpected. */ + else + fHandled = false; + } + /* + * Deal with lock prefixed int xxh syscall in v8086 mode. + */ + else if ( pTrapFrame->bXcpt == X86_XCPT_UD + && pTrapFrame->Ctx.cs == BS3_SEL_TEXT16 + && pTrapFrame->Ctx.rax.u16 <= BS3_SYSCALL_LAST + && pbCode[0] == 0xf0 + && pbCode[1] == 0xcd + && pbCode[2] == BS3_TRAP_SYSCALL) + { + pbCode += 3; + bs3TrapDefaultHandlerV8086Syscall(pTrapFrame); + } + else + { + fHandled = false; + } + if (fHandled) + { + pTrapFrame->Ctx.rip.u16 += (uint16_t)(pbCode - pbCodeStart); +# if 0 + Bs3Printf("Calling Bs3RegCtxRestore\n"); + Bs3RegCtxPrint(&pTrapFrame->Ctx); +# endif + Bs3RegCtxRestore(&pTrapFrame->Ctx, 0 /*fFlags*/); /* does not return. */ + return; + } + } +#endif + + /* + * Any pending setjmp? + */ + if (g_pBs3TrapSetJmpFrame != 0) + { + PBS3TRAPFRAME pSetJmpFrame = (PBS3TRAPFRAME)Bs3XptrFlatToCurrent(g_pBs3TrapSetJmpFrame); + //Bs3Printf("Calling longjmp: pSetJmpFrame=%p (%#lx)\n", pSetJmpFrame, g_pBs3TrapSetJmpFrame); + g_pBs3TrapSetJmpFrame = 0; + Bs3MemCpy(pSetJmpFrame, pTrapFrame, sizeof(*pSetJmpFrame)); + //Bs3RegCtxPrint(&g_Bs3TrapSetJmpCtx); + Bs3RegCtxRestore(&g_Bs3TrapSetJmpCtx, 0 /*fFlags*/); + } + + /* + * Fatal. + */ + Bs3TestPrintf("*** GURU ***\n"); + Bs3TrapPrintFrame(pTrapFrame); + Bs3Panic(); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapHandlersData.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapHandlersData.asm new file mode 100644 index 00000000..ded2d69d --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapHandlersData.asm @@ -0,0 +1,52 @@ +; $Id: bs3-cmn-TrapHandlersData.asm $ +;; @file +; BS3Kit - Per bit-count trap data. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + + +BS3_BEGIN_DATA16 + BS3_SET_BITS ARCH_BITS ; Override the 16-bit mode that BS3_BEGIN_DATA16 implies so we get a correct xCB value. + +;; Pointer C trap handlers. +;; Note! The 16-bit ones are all near so we can share them between real, v86 and prot mode. +;; Note! Must be in 16-bit data because of BS3TrapSetHandlerEx. +BS3_GLOBAL_NAME_EX BS3_CMN_NM(g_apfnBs3TrapHandlers), , 256 * xCB + resb (256 * xCB) + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapPrintFrame.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapPrintFrame.c new file mode 100644 index 00000000..9d9d4b73 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapPrintFrame.c @@ -0,0 +1,89 @@ +/* $Id: bs3-cmn-TrapPrintFrame.c $ */ +/** @file + * BS3Kit - Bs3TrapPrintFrame + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3TrapPrintFrame +BS3_CMN_DEF(void, Bs3TrapPrintFrame,(PCBS3TRAPFRAME pTrapFrame)) +{ +#if 1 + Bs3TestPrintf("Trap %#04x errcd=%#06RX64 at %04x:%016RX64 - test step %d (%#x)\n" + "Handler: ss:rsp=%04x:%08RX64 cs=%04x cbIret=%#x rflags=%#06RX64\n" + , + pTrapFrame->bXcpt, + pTrapFrame->uErrCd, + pTrapFrame->Ctx.cs, + pTrapFrame->Ctx.rip.u64, + g_usBs3TestStep, g_usBs3TestStep, + pTrapFrame->uHandlerSs, + pTrapFrame->uHandlerRsp, + pTrapFrame->uHandlerCs, + pTrapFrame->cbIretFrame, + pTrapFrame->fHandlerRfl); + Bs3RegCtxPrint(&pTrapFrame->Ctx); +#else + /* This is useful if having trouble returning from real mode. */ + PCBS3REGCTX pRegCtx = &pTrapFrame->Ctx; + Bs3TestPrintf("Trap %#04x errcd=%#06RX64 at %04x:%016RX64 - test step %d (%#x)\n" + "eax=%08RX32 ebx=%08RX32 ecx=%08RX32 edx=%08RX32 esi=%08RX32 edi=%08RX32\n" + "eip=%08RX32 esp=%08RX32 ebp=%08RX32 efl=%08RX32 cr0=%08RX32 cr2=%08RX32\n" + "cs=%04RX16 ds=%04RX16 es=%04RX16 fs=%04RX16 gs=%04RX16 ss=%04RX16 cr3=%08RX32 cr4=%08RX32\n" + "tr=%04RX16 ldtr=%04RX16 cpl=%d mode=%#x fbFlags=%#x\n" + , + pTrapFrame->bXcpt, + pTrapFrame->uErrCd, + pTrapFrame->Ctx.cs, + pTrapFrame->Ctx.rip.u64, + g_usBs3TestStep, g_usBs3TestStep + , + pRegCtx->rax.u32, pRegCtx->rbx.u32, pRegCtx->rcx.u32, pRegCtx->rdx.u32, pRegCtx->rsi.u32, pRegCtx->rdi.u32 + , + pRegCtx->rip.u32, pRegCtx->rsp.u32, pRegCtx->rbp.u32, pRegCtx->rflags.u32, + pRegCtx->cr0.u32, pRegCtx->cr2.u32 + , + pRegCtx->cs, pRegCtx->ds, pRegCtx->es, pRegCtx->fs, pRegCtx->gs, pRegCtx->ss, + pRegCtx->cr3.u32, pRegCtx->cr4.u32 + , + pRegCtx->tr, pRegCtx->ldtr, pRegCtx->bCpl, pRegCtx->bMode, pRegCtx->fbFlags); + +#endif +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapReInit.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapReInit.c new file mode 100644 index 00000000..5b86f2d7 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapReInit.c @@ -0,0 +1,65 @@ +/* $Id: bs3-cmn-TrapReInit.c $ */ +/** @file + * BS3Kit - Bs3TrapReInit + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3TrapReInit +BS3_CMN_DEF(void, Bs3TrapReInit,(void)) +{ + if (BS3_MODE_IS_RM_SYS(g_bBs3CurrentMode)) + Bs3TrapRmV86Init(); + else if (BS3_MODE_IS_16BIT_SYS(g_bBs3CurrentMode)) + { + Bs3TrapRmV86Init(); + Bs3Trap16Init(); + } + else if (BS3_MODE_IS_32BIT_SYS(g_bBs3CurrentMode)) + { + Bs3TrapRmV86Init(); + Bs3Trap32Init(); + } + else + { + BS3_ASSERT(BS3_MODE_IS_64BIT_SYS(g_bBs3CurrentMode)); + Bs3Trap64Init(); + } +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapRmV86Init.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapRmV86Init.c new file mode 100644 index 00000000..dee1b912 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapRmV86Init.c @@ -0,0 +1,122 @@ +/* $Id: bs3-cmn-TrapRmV86Init.c $ */ +/** @file + * BS3Kit - Bs3TrapRmV86Init + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/* We ASSUME that BS3CLASS16CODE is 64KB aligned, so the low 16-bit of the + flat address matches. Also, these symbols are defined both with + and without underscore prefixes. */ +extern BS3_DECL(void) BS3_FAR_CODE Bs3TrapRmV86GenericEntries(void); + +/* These two are ugly. Need data access for patching purposes. */ +extern uint8_t BS3_FAR_DATA bs3TrapRmV86GenericTrapOrInt[]; + +/* bs3-cmn-TrapRmV86Data.c: */ +#define g_fBs3RmIvtCopied BS3_DATA_NM(g_fBs3RmIvtCopied) +extern bool g_fBs3RmIvtCopied; + + +#undef Bs3TrapRmV86InitEx +BS3_CMN_DEF(void, Bs3TrapRmV86InitEx,(bool f386Plus)) +{ + RTFAR16 BS3_FAR *paIvt = Bs3XptrFlatToCurrent(0); + unsigned iIvt; + + /* + * Copy the real mode IVT the first time we are here. + */ + if (!g_fBs3RmIvtCopied) + { + Bs3MemCpy(g_aBs3RmIvtOriginal, paIvt, sizeof(g_aBs3RmIvtOriginal)); + g_fBs3RmIvtCopied = true; + } + /* + * The rest of the times, we copy back the original and modify it. + */ + else + Bs3MemCpy(paIvt, g_aBs3RmIvtOriginal, sizeof(g_aBs3RmIvtOriginal)); + + + /* + * If 386 or later, patch the trap handler code to not jump to the 80286 + * code but continue with the next instruction (the 386+ code). + */ + if (f386Plus) + { + uint8_t BS3_FAR_DATA *pbFunction = &bs3TrapRmV86GenericTrapOrInt[0]; +#if ARCH_BITS == 16 + if (g_bBs3CurrentMode != BS3_MODE_RM) + pbFunction = (uint8_t BS3_FAR_DATA *)BS3_FP_MAKE(BS3_SEL_TILED + 1, BS3_FP_OFF(pbFunction)); +#endif + pbFunction[1] = 0; + pbFunction[2] = 0; + } + + /* + * Since we want to play with V86 mode as well as 8086 and 186 CPUs, we + * cannot move the IVT from its default location. So, modify it in place. + * + * Note! We must keep INT 10h working, which is easy since the CPU does + * use it (well, it's been reserved for 30+ years). + * Turns out we must not hook INT 6Dh either then, as some real VGA + * BIOS installs their INT 10h handler there as well, and seemingly + * must be using it internally or something. + * + * We also keep 15h working for memory interfaces (see bs3-mode-BiosInt15*). + */ + for (iIvt = 0; iIvt < 256; iIvt++) + if (iIvt != 0x10 && iIvt != 0x15 && iIvt != 0x6d && iIvt != BS3_TRAP_SYSCALL) + { + paIvt[iIvt].off = (uint16_t)(uintptr_t)Bs3TrapRmV86GenericEntries + iIvt * 8; + paIvt[iIvt].sel = BS3_SEL_TEXT16; + } +} + + +#undef Bs3TrapRmV86Init +BS3_CMN_DEF(void, Bs3TrapRmV86Init,(void)) +{ + BS3_CMN_NM(Bs3TrapRmV86InitEx)((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80386); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapRmV86SetGate.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapRmV86SetGate.c new file mode 100644 index 00000000..a64205c7 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapRmV86SetGate.c @@ -0,0 +1,51 @@ +/* $Id: bs3-cmn-TrapRmV86SetGate.c $ */ +/** @file + * BS3Kit - Bs3TrapRmV86SetGate + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3TrapRmV86SetGate +BS3_CMN_DEF(void, Bs3TrapRmV86SetGate,(uint8_t iIvt, uint16_t uSeg, uint16_t off)) +{ + RTFAR16 BS3_FAR *paIvt = Bs3XptrFlatToCurrent(0); + paIvt[iIvt].off = off; + paIvt[iIvt].sel = uSeg; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetDpl.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetDpl.c new file mode 100644 index 00000000..d7632d94 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetDpl.c @@ -0,0 +1,57 @@ +/* $Id: bs3-cmn-TrapSetDpl.c $ */ +/** @file + * BS3Kit - Bs3TrapSetDpl + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3TrapSetDpl +BS3_CMN_DEF(uint8_t, Bs3TrapSetDpl,(uint8_t iIdt, uint8_t bDpl)) +{ + uint8_t bRet; + BS3_ASSERT(bDpl <= 3); + + Bs3Idt16[iIdt].Gate.u2Dpl = bDpl; + Bs3Idt32[iIdt].Gate.u2Dpl = bDpl; + bRet = Bs3Idt64[iIdt].Gate.u2Dpl; + Bs3Idt64[iIdt].Gate.u2Dpl = bDpl; + + return bRet; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetHandler.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetHandler.c new file mode 100644 index 00000000..06cf3f16 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetHandler.c @@ -0,0 +1,57 @@ +/* $Id: bs3-cmn-TrapSetHandler.c $ */ +/** @file + * BS3Kit - Bs3Trap32SetHandler + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +/********************************************************************************************************************************* +* External Symbols * +*********************************************************************************************************************************/ +extern PFNBS3TRAPHANDLER BS3_DATA_NM(BS3_CMN_NM(g_apfnBs3TrapHandlers))[256]; + + +#undef Bs3TrapSetHandler +BS3_CMN_DEF(PFNBS3TRAPHANDLER, Bs3TrapSetHandler,(uint8_t iIdt, PFNBS3TRAPHANDLER pfnHandler)) +{ + PFNBS3TRAPHANDLER pfnOld = BS3_DATA_NM(BS3_CMN_NM(g_apfnBs3TrapHandlers))[iIdt]; + BS3_DATA_NM(BS3_CMN_NM(g_apfnBs3TrapHandlers))[iIdt] = pfnHandler; + return pfnOld; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetHandlerEx.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetHandlerEx.c new file mode 100644 index 00000000..e39b1580 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetHandlerEx.c @@ -0,0 +1,71 @@ +/* $Id: bs3-cmn-TrapSetHandlerEx.c $ */ +/** @file + * BS3Kit - Bs3Trap32SetHandlerEx + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include <iprt/asm-amd64-x86.h> + + +/********************************************************************************************************************************* +* External Symbols * +*********************************************************************************************************************************/ +extern uint16_t BS3_DATA_NM(g_apfnBs3TrapHandlers_c16)[256]; +extern uint32_t BS3_DATA_NM(g_apfnBs3TrapHandlers_c32)[256]; +extern uint64_t BS3_DATA_NM(g_apfnBs3TrapHandlers_c64)[256]; + + +#undef Bs3TrapSetHandlerEx +BS3_CMN_DEF(void, Bs3TrapSetHandlerEx,(uint8_t iIdt, PFNBS3TRAPHANDLER16 pfnHandler16, + PFNBS3TRAPHANDLER32 pfnHandler32, PFNBS3TRAPHANDLER64 pfnHandler64)) +{ + RTCCUINTREG fFlags = ASMIntDisableFlags(); +#if ARCH_BITS == 16 + /* Far real mode pointers as input. */ + g_apfnBs3TrapHandlers_c16[iIdt] = (uint16_t)pfnHandler16; + g_apfnBs3TrapHandlers_c32[iIdt] = Bs3SelRealModeCodeToFlat((PFNBS3FARADDRCONV)pfnHandler32); + g_apfnBs3TrapHandlers_c64[iIdt] = Bs3SelRealModeCodeToFlat((PFNBS3FARADDRCONV)pfnHandler64); +#else + /* Flat pointers as input. */ + g_apfnBs3TrapHandlers_c16[iIdt] = (uint16_t)Bs3SelFlatCodeToProtFar16((uintptr_t)pfnHandler16); + g_apfnBs3TrapHandlers_c32[iIdt] = (uint32_t)(uintptr_t)pfnHandler32; + g_apfnBs3TrapHandlers_c64[iIdt] = (uintptr_t)pfnHandler64; +#endif + ASMSetFlags(fFlags); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetJmp.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetJmp.asm new file mode 100644 index 00000000..41fedf1d --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetJmp.asm @@ -0,0 +1,143 @@ +; $Id: bs3-cmn-TrapSetJmp.asm $ +;; @file +; BS3Kit - Bs3TrapSetJmp. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_EXTERN_CMN Bs3RegCtxSave +%if TMPL_BITS == 16 +BS3_EXTERN_CMN_FAR Bs3SelFar32ToFlat32 +%endif +BS3_EXTERN_DATA16 g_Bs3TrapSetJmpCtx +BS3_EXTERN_DATA16 g_pBs3TrapSetJmpFrame +TMPL_BEGIN_TEXT + + +;; +; Sets up a one-shot set-jmp-on-trap. +; +; @uses See, applicable C calling convention. +; +BS3_PROC_BEGIN_CMN Bs3TrapSetJmp, BS3_PBC_HYBRID + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + push xBX +BONLY64 sub xSP, 20h + + ; + ; Save the current register context. + ; +BONLY16 push ds + BS3_LEA_MOV_WRT_RIP(xAX, BS3_DATA16_WRT(g_Bs3TrapSetJmpCtx)) + push xAX + BS3_CALL Bs3RegCtxSave, 1 + add xSP, sCB + + ; + ; Adjust the return context a little. + ; + BS3_LEA_MOV_WRT_RIP(xBX, BS3_DATA16_WRT(g_Bs3TrapSetJmpCtx)) + mov xAX, [xBP + xCB] ; The return address of this function + mov [xBX + BS3REGCTX.rip], xAX +%if TMPL_BITS == 16 + mov xAX, [xBP + xCB+2] ; The return address CS of this function. + mov [xBX + BS3REGCTX.cs], xAX +%endif + mov xAX, [xBP] + mov [xBX + BS3REGCTX.rbp], xAX + lea xAX, [xBP + xCB + cbCurRetAddr] + mov [xBX + BS3REGCTX.rsp], xAX + mov xAX, [xBP - xCB] + mov [xBX + BS3REGCTX.rbx], xAX + xor xAX, xAX + mov [xBX + BS3REGCTX.rax], xAX ; the return value. + + ; + ; Fill the trap frame return structure. + ; + push xDI +%if TMPL_BITS == 16 + push es + les di, [xBP + xCB + cbCurRetAddr] + mov cx, BS3TRAPFRAME_size / 2 + mov ax, 0faceh + rep stosw + pop es +%else + mov xDI, [xBP + xCB*2] + mov ecx, BS3TRAPFRAME_size / 4 + mov xAX, 0feedfaceh + rep stosd +%endif + pop xDI + + ; + ; Save the (flat) pointer to the trap frame return structure. + ; +%if TMPL_BITS == 16 + xor ax, ax + push word [xBP + xCB + cbCurRetAddr + 2] + push ax + push word [xBP + xCB + cbCurRetAddr] + push cs + call Bs3SelFar32ToFlat32 + add sp, 6h + mov [BS3_DATA16_WRT(g_pBs3TrapSetJmpFrame)], ax + mov [2 + BS3_DATA16_WRT(g_pBs3TrapSetJmpFrame)], dx +%else + mov xAX, [xBP + xCB*2] + mov [BS3_DATA16_WRT(g_pBs3TrapSetJmpFrame)], eax +%endif + + ; + ; Return 'true'. + ; + mov xAX, 1 +BONLY64 add xSP, 20h + pop xBX + pop xBP + BS3_CALL_CONV_EPILOG 1 + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3TrapSetJmp + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetJmpAndRestore.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetJmpAndRestore.c new file mode 100644 index 00000000..22b1b870 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetJmpAndRestore.c @@ -0,0 +1,56 @@ +/* $Id: bs3-cmn-TrapSetJmpAndRestore.c $ */ +/** @file + * BS3Kit - Bs3TrapSetJmpAndRestore + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3TrapSetJmpAndRestore +BS3_CMN_DEF(void, Bs3TrapSetJmpAndRestore,(PCBS3REGCTX pCtxRestore, PBS3TRAPFRAME pTrapFrame)) +{ + if (Bs3TrapSetJmp(pTrapFrame)) + { +#if TMPL_BITS == 32 + g_uBs3TrapEipHint = pCtxRestore->rip.u32; +#endif + Bs3RegCtxRestore(pCtxRestore, BS3REGCTXRESTORE_F_NO_V86_ASSIST); + } + g_fBs3TrapNoV86Assist = false; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetJmpAndRestoreInRm.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetJmpAndRestoreInRm.c new file mode 100644 index 00000000..502a5bf8 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetJmpAndRestoreInRm.c @@ -0,0 +1,133 @@ +/* $Id: bs3-cmn-TrapSetJmpAndRestoreInRm.c $ */ +/** @file + * BS3Kit - Bs3TrapSetJmpAndRestoreInRm + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +/* assembly helpers */ +BS3_MODE_PROTO_NOSB(void, Bs3TrapSetJmpAndRestoreInRmAsm, (uint32_t, uint32_t)); + + +#undef Bs3TrapSetJmpAndRestoreInRm +BS3_CMN_DEF(void, Bs3TrapSetJmpAndRestoreInRm,(PCBS3REGCTX pCtxRestore, PBS3TRAPFRAME pTrapFrame)) +{ +#if TMPL_BITS == 16 + if (g_bBs3CurrentMode == BS3_MODE_RM) + Bs3TrapSetJmpAndRestore(pCtxRestore, pTrapFrame); + else +#endif + { + uint32_t const pfRealModeCtxRestore = Bs3SelFlatDataToRealMode(Bs3SelPtrToFlat((PBS3REGCTX)pCtxRestore)); + uint32_t const pfRealModeTrapFrame = Bs3SelFlatDataToRealMode(Bs3SelPtrToFlat(pTrapFrame)); + +#if TMPL_BITS == 16 + switch (g_bBs3CurrentMode & BS3_MODE_SYS_MASK) + { + case BS3_MODE_SYS_PE16: + Bs3TrapSetJmpAndRestoreInRmAsm_pe16(pfRealModeCtxRestore, pfRealModeTrapFrame); + break; + case BS3_MODE_SYS_PE32: + Bs3TrapSetJmpAndRestoreInRmAsm_pe32_16(pfRealModeCtxRestore, pfRealModeTrapFrame); + break; + case BS3_MODE_SYS_PP16: + Bs3TrapSetJmpAndRestoreInRmAsm_pp16(pfRealModeCtxRestore, pfRealModeTrapFrame); + break; + case BS3_MODE_SYS_PP32: + Bs3TrapSetJmpAndRestoreInRmAsm_pp32_16(pfRealModeCtxRestore, pfRealModeTrapFrame); + break; + case BS3_MODE_SYS_PAE16: + Bs3TrapSetJmpAndRestoreInRmAsm_pae16(pfRealModeCtxRestore, pfRealModeTrapFrame); + break; + case BS3_MODE_SYS_PAE32: + Bs3TrapSetJmpAndRestoreInRmAsm_pae32_16(pfRealModeCtxRestore, pfRealModeTrapFrame); + break; + case BS3_MODE_SYS_LM: + Bs3TrapSetJmpAndRestoreInRmAsm_lm16(pfRealModeCtxRestore, pfRealModeTrapFrame); + break; + default: + BS3_ASSERT(0); + } + +#elif TMPL_BITS == 32 + switch (g_bBs3CurrentMode & BS3_MODE_SYS_MASK) + { + case BS3_MODE_SYS_PE16: + Bs3TrapSetJmpAndRestoreInRmAsm_pe16_32(pfRealModeCtxRestore, pfRealModeTrapFrame); + break; + case BS3_MODE_SYS_PE32: + Bs3TrapSetJmpAndRestoreInRmAsm_pe32(pfRealModeCtxRestore, pfRealModeTrapFrame); + break; + case BS3_MODE_SYS_PP16: + Bs3TrapSetJmpAndRestoreInRmAsm_pp16_32(pfRealModeCtxRestore, pfRealModeTrapFrame); + break; + case BS3_MODE_SYS_PP32: + Bs3TrapSetJmpAndRestoreInRmAsm_pp32(pfRealModeCtxRestore, pfRealModeTrapFrame); + break; + case BS3_MODE_SYS_PAE16: + Bs3TrapSetJmpAndRestoreInRmAsm_pae16_32(pfRealModeCtxRestore, pfRealModeTrapFrame); + break; + case BS3_MODE_SYS_PAE32: + Bs3TrapSetJmpAndRestoreInRmAsm_pae32(pfRealModeCtxRestore, pfRealModeTrapFrame); + break; + case BS3_MODE_SYS_LM: + Bs3TrapSetJmpAndRestoreInRmAsm_lm32(pfRealModeCtxRestore, pfRealModeTrapFrame); + break; + default: + BS3_ASSERT(0); + } + +#elif TMPL_BITS == 64 + switch (g_bBs3CurrentMode & BS3_MODE_SYS_MASK) + { + case BS3_MODE_SYS_LM: + Bs3TrapSetJmpAndRestoreInRmAsm_lm64(pfRealModeCtxRestore, pfRealModeTrapFrame); + break; + default: + BS3_ASSERT(0); + } +#else +# error Bogus TMPL_BITS +#endif + } +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetJmpAndRestoreWithExtCtx.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetJmpAndRestoreWithExtCtx.c new file mode 100644 index 00000000..bdc74784 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetJmpAndRestoreWithExtCtx.c @@ -0,0 +1,61 @@ +/* $Id: bs3-cmn-TrapSetJmpAndRestoreWithExtCtx.c $ */ +/** @file + * BS3Kit - Bs3TrapSetJmpAndRestoreWithExtCtx + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3TrapSetJmpAndRestoreWithExtCtx +BS3_CMN_DEF(void, Bs3TrapSetJmpAndRestoreWithExtCtx,(PCBS3REGCTX pCtxRestore, PCBS3EXTCTX pExtCtxRestore, + PBS3TRAPFRAME pTrapFrame, PBS3EXTCTX pExtCtxTrap)) +{ + /* ASSUMES compile emits no SSE instructions between the calls here + (only a potential issue in 64-bit mode). */ + Bs3ExtCtxRestoreEx(pExtCtxRestore); + if (Bs3TrapSetJmp(pTrapFrame)) + { +#if TMPL_BITS == 32 + g_uBs3TrapEipHint = pCtxRestore->rip.u32; +#endif + Bs3RegCtxRestore(pCtxRestore, BS3REGCTXRESTORE_F_NO_V86_ASSIST); + } + g_fBs3TrapNoV86Assist = false; + Bs3ExtCtxSaveEx(pExtCtxTrap); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetJmpAndRestoreWithExtCtxAndRm.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetJmpAndRestoreWithExtCtxAndRm.c new file mode 100644 index 00000000..d59abcee --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetJmpAndRestoreWithExtCtxAndRm.c @@ -0,0 +1,66 @@ +/* $Id: bs3-cmn-TrapSetJmpAndRestoreWithExtCtxAndRm.c $ */ +/** @file + * BS3Kit - Bs3TrapSetJmpAndRestoreWithExtCtxAndRm + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3TrapSetJmpAndRestoreWithExtCtxAndRm +BS3_CMN_DEF(void, Bs3TrapSetJmpAndRestoreWithExtCtxAndRm,(PCBS3REGCTX pCtxRestore, PCBS3EXTCTX pExtCtxRestore, + PBS3TRAPFRAME pTrapFrame, PBS3EXTCTX pExtCtxTrap)) +{ + if ( pCtxRestore->bMode != BS3_MODE_RM +#if ARCH_BITS == 16 + || g_bBs3CurrentMode == BS3_MODE_RM +#endif + ) + { + BS3_ASSERT((pCtxRestore->bMode & BS3_MODE_SYS_MASK) == (g_bBs3CurrentMode & BS3_MODE_SYS_MASK)); + Bs3TrapSetJmpAndRestoreWithExtCtx(pCtxRestore, pExtCtxRestore, pTrapFrame, pExtCtxTrap); + } + else + { + /* ASSUMES compile emits no SSE instructions between the calls here + (only a potential issue in 64-bit mode). */ + Bs3ExtCtxRestoreEx(pExtCtxRestore); + Bs3TrapSetJmpAndRestoreInRm(pCtxRestore, pTrapFrame); + Bs3ExtCtxSaveEx(pExtCtxTrap); + } +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetJmpAndRestoreWithRm.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetJmpAndRestoreWithRm.c new file mode 100644 index 00000000..17090d8b --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapSetJmpAndRestoreWithRm.c @@ -0,0 +1,59 @@ +/* $Id: bs3-cmn-TrapSetJmpAndRestoreWithRm.c $ */ +/** @file + * BS3Kit - Bs3TrapSetJmpAndRestoreWithRm + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3TrapSetJmpAndRestoreWithRm +BS3_CMN_DEF(void, Bs3TrapSetJmpAndRestoreWithRm,(PCBS3REGCTX pCtxRestore, PBS3TRAPFRAME pTrapFrame)) +{ + if ( pCtxRestore->bMode != BS3_MODE_RM +#if ARCH_BITS == 16 + || g_bBs3CurrentMode == BS3_MODE_RM +#endif + ) + { + BS3_ASSERT((pCtxRestore->bMode & BS3_MODE_SYS_MASK) == (g_bBs3CurrentMode & BS3_MODE_SYS_MASK)); + Bs3TrapSetJmpAndRestore(pCtxRestore, pTrapFrame); + } + else + Bs3TrapSetJmpAndRestoreInRm(pCtxRestore, pTrapFrame); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapUnsetJmp.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapUnsetJmp.c new file mode 100644 index 00000000..d9cb54ad --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-TrapUnsetJmp.c @@ -0,0 +1,58 @@ +/* $Id: bs3-cmn-TrapUnsetJmp.c $ */ +/** @file + * BS3Kit - Bs3TrapUnsetJmp + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#ifndef DOXYGEN_RUNNING +# define g_pBs3TrapSetJmpFrame BS3_DATA_NM(g_pBs3TrapSetJmpFrame) +#endif +extern uint32_t g_pBs3TrapSetJmpFrame; + + +#undef Bs3TrapUnsetJmp +BS3_CMN_DEF(void, Bs3TrapUnsetJmp,(void)) +{ + g_pBs3TrapSetJmpFrame = 0; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-UInt32Div.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-UInt32Div.c new file mode 100644 index 00000000..8903879a --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-UInt32Div.c @@ -0,0 +1,50 @@ +/* $Id: bs3-cmn-UInt32Div.c $ */ +/** @file + * BS3Kit - Unsigned 32-bit division (compiler support routine helper). + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> +#include <iprt/uint32.h> + + +#undef Bs3UInt32Div +BS3_CMN_DEF(void, Bs3UInt32Div,(RTUINT32U uDividend, RTUINT32U uDivisor, RTUINT32U BS3_FAR *paQuotientReminder)) +{ + RTUInt32DivRem(&paQuotientReminder[0], &paQuotientReminder[1], &uDividend, &uDivisor); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-UInt64Div.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-UInt64Div.c new file mode 100644 index 00000000..f150d2fd --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-UInt64Div.c @@ -0,0 +1,50 @@ +/* $Id: bs3-cmn-UInt64Div.c $ */ +/** @file + * BS3Kit - Unsigned 64-bit division (compiler support routine helper). + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <bs3kit.h> +#include <iprt/uint64.h> + + +#undef Bs3UInt64Div +BS3_CMN_DEF(void, Bs3UInt64Div,(RTUINT64U uDividend, RTUINT64U uDivisor, RTUINT64U BS3_FAR *paQuotientReminder)) +{ + RTUInt64DivRem(&paQuotientReminder[0], &paQuotientReminder[1], &uDividend, &uDivisor); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-UtilSetFullGdtr.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-UtilSetFullGdtr.asm new file mode 100644 index 00000000..82312076 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-UtilSetFullGdtr.asm @@ -0,0 +1,195 @@ +; $Id: bs3-cmn-UtilSetFullGdtr.asm $ +;; @file +; BS3Kit - Bs3UtilSetFullGdtr +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_uBs3CpuDetected +%endif +%if TMPL_BITS != 64 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + + +;; +; @cproto BS3_CMN_PROTO_NOSB(void, Bs3UtilSetFullGdtr,(uint16_t cbLimit, uint64_t uBase)); +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; @uses eax/rax; cbLimit on stack in 32-bit mode. +; +BS3_PROC_BEGIN_CMN Bs3UtilSetFullGdtr, BS3_PBC_HYBRID +TONLY16 inc xBP + push xBP + mov xBP, xSP + +%if TMPL_BITS == 64 + ; + ; It doesn't (currently) get any better than 64-bit mode. + ; + push rdx + mov rax, rcx + shl rax, 48 + push rax + lgdt [rsp + 6] + add rsp, 10h + + +%elif TMPL_BITS == 32 + ; + ; Move the limit up two bytes so we can use it directly. + ; + shl dword [xBP + xCB + cbCurRetAddr], 16 + + ; + ; If the system is currently running in long mode, we have to switch to + ; it in order to do the job with the highest precision. + ; + mov al, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + and al, BS3_MODE_SYS_MASK + cmp al, BS3_MODE_SYS_LM + je .do_64bit + + ; 32-bit is the best we can do. +.do_32bit: + lgdt [xBP + xCB + cbCurRetAddr + 2] + jmp .return + + ; Must switch to long mode and do it there. +.do_64bit: + jmp BS3_SEL_R0_CS64:.in_64bit wrt FLAT +.in_64bit: + BS3_SET_BITS 64 + lgdt [xSP + 4 + cbCurRetAddr + 2] + push BS3_SEL_R0_CS32 + push .return wrt FLAT + o64 retf + BS3_SET_BITS 32 + + +%elif TMPL_BITS == 16 + ; + ; All options are open here, we can be in any 16-bit mode, + ; including real mode. + ; + mov al, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + test al, BS3_MODE_CODE_V86 + jnz .do_v8086 + and al, BS3_MODE_SYS_MASK + cmp al, BS3_MODE_SYS_LM + je .do_64bit + cmp al, BS3_MODE_SYS_RM + je .do_16bit + cmp byte [ BS3_DATA16_WRT(g_uBs3CpuDetected)], BS3CPU_80386 + jae .do_32bit + + ; + ; We're in real mode or in 16-bit protected mode on a 286. + ; +.do_16bit: ;ba x 1 127f5 + lgdt [xBP + xCB + cbCurRetAddr] + jmp .return + + ; + ; We're in some kind of protected mode on a 386 or better. + ; +.do_32bit: + jmp dword BS3_SEL_R0_CS32:.in_32bit wrt FLAT +.in_32bit: + BS3_SET_BITS 32 + lgdt [bp + 2 + cbCurRetAddr] + jmp BS3_SEL_R0_CS16:.return wrt CGROUP16 + BS3_SET_BITS 16 + + ; + ; V8086 mode - need to switch to 32-bit kernel code to do stuff here. + ; +.do_v8086: + BS3_EXTERN_CMN Bs3SwitchTo32Bit + call Bs3SwitchTo32Bit + BS3_SET_BITS 32 + + lgdt [xSP + 2 + cbCurRetAddr] + + extern _Bs3SwitchTo16BitV86_c32 + call _Bs3SwitchTo16BitV86_c32 + BS3_SET_BITS 16 + jmp .return + + ; + ; System is in long mode, so we can switch to 64-bit mode and do the job there. + ; +.do_64bit: + push edx ; save + push ss + push 0 + push bp + BS3_EXTERN_CMN Bs3SelFar32ToFlat32NoClobber + call Bs3SelFar32ToFlat32NoClobber + add sp, 6 + shl edx, 16 + mov dx, ax + mov eax, edx ; eax = flattened ss:bp + pop edx ; restore + jmp dword BS3_SEL_R0_CS64:.in_64bit wrt FLAT +.in_64bit: + BS3_SET_BITS 64 + lgdt [rax + 2 + cbCurRetAddr] + + push BS3_SEL_R0_CS16 + push .return wrt CGROUP16 + o64 retf + BS3_SET_BITS 16 + +%else + %error "TMPL_BITS!" +%endif + +.return: + pop xBP +TONLY16 dec xBP + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3UtilSetFullGdtr + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-UtilSetFullIdtr.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-UtilSetFullIdtr.asm new file mode 100644 index 00000000..99f3b822 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-UtilSetFullIdtr.asm @@ -0,0 +1,195 @@ +; $Id: bs3-cmn-UtilSetFullIdtr.asm $ +;; @file +; BS3Kit - Bs3UtilSetFullIdtr +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_uBs3CpuDetected +%endif +%if TMPL_BITS != 64 +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%endif +TMPL_BEGIN_TEXT + + + +;; +; @cproto BS3_CMN_PROTO_NOSB(void, Bs3UtilSetFullIdtr,(uint16_t cbLimit, uint64_t uBase)); +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; @uses eax/rax; cbLimit on stack in 32-bit mode. +; +BS3_PROC_BEGIN_CMN Bs3UtilSetFullIdtr, BS3_PBC_HYBRID +TONLY16 inc xBP + push xBP + mov xBP, xSP + +%if TMPL_BITS == 64 + ; + ; It doesn't (currently) get any better than 64-bit mode. + ; + push rdx + mov rax, rcx + shl rax, 48 + push rax + lidt [rsp + 6] + add rsp, 10h + + +%elif TMPL_BITS == 32 + ; + ; Move the limit up two bytes so we can use it directly. + ; + shl dword [xBP + xCB + cbCurRetAddr], 16 + + ; + ; If the system is currently running in long mode, we have to switch to + ; it in order to do the job with the highest precision. + ; + mov al, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + and al, BS3_MODE_SYS_MASK + cmp al, BS3_MODE_SYS_LM + je .do_64bit + + ; 32-bit is the best we can do. +.do_32bit: + lidt [xBP + xCB + cbCurRetAddr + 2] + jmp .return + + ; Must switch to long mode and do it there. +.do_64bit: + jmp BS3_SEL_R0_CS64:.in_64bit wrt FLAT +.in_64bit: + BS3_SET_BITS 64 + lidt [xSP + 4 + cbCurRetAddr + 2] + push BS3_SEL_R0_CS32 + push .return wrt FLAT + o64 retf + BS3_SET_BITS 32 + + +%elif TMPL_BITS == 16 + ; + ; All options are open here, we can be in any 16-bit mode, + ; including real mode. + ; + mov al, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + test al, BS3_MODE_CODE_V86 + jnz .do_v8086 + and al, BS3_MODE_SYS_MASK + cmp al, BS3_MODE_SYS_LM + je .do_64bit + cmp al, BS3_MODE_SYS_RM + je .do_16bit + cmp byte [ BS3_DATA16_WRT(g_uBs3CpuDetected)], BS3CPU_80386 + jae .do_32bit + + ; + ; We're in real mode or in 16-bit protected mode on a 286. + ; +.do_16bit: ;ba x 1 127f5 + lidt [xBP + xCB + cbCurRetAddr] + jmp .return + + ; + ; We're in some kind of protected mode on a 386 or better. + ; +.do_32bit: + jmp dword BS3_SEL_R0_CS32:.in_32bit wrt FLAT +.in_32bit: + BS3_SET_BITS 32 + lidt [bp + 2 + cbCurRetAddr] + jmp BS3_SEL_R0_CS16:.return wrt CGROUP16 + BS3_SET_BITS 16 + + ; + ; V8086 mode - need to switch to 32-bit kernel code to do stuff here. + ; +.do_v8086: + BS3_EXTERN_CMN Bs3SwitchTo32Bit + call Bs3SwitchTo32Bit + BS3_SET_BITS 32 + + lidt [xSP + 2 + cbCurRetAddr] + + extern _Bs3SwitchTo16BitV86_c32 + call _Bs3SwitchTo16BitV86_c32 + BS3_SET_BITS 16 + jmp .return + + ; + ; System is in long mode, so we can switch to 64-bit mode and do the job there. + ; +.do_64bit: + push edx ; save + push ss + push 0 + push bp + BS3_EXTERN_CMN Bs3SelFar32ToFlat32NoClobber + call Bs3SelFar32ToFlat32NoClobber + add sp, 6 + shl edx, 16 + mov dx, ax + mov eax, edx ; eax = flattened ss:bp + pop edx ; restore + jmp dword BS3_SEL_R0_CS64:.in_64bit wrt FLAT +.in_64bit: + BS3_SET_BITS 64 + lidt [rax + 2 + cbCurRetAddr] + + push BS3_SEL_R0_CS16 + push .return wrt CGROUP16 + o64 retf + BS3_SET_BITS 16 + +%else + %error "TMPL_BITS!" +%endif + +.return: + pop xBP +TONLY16 dec xBP + BS3_HYBRID_RET +BS3_PROC_END_CMN Bs3UtilSetFullIdtr + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-hexdigits.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-hexdigits.c new file mode 100644 index 00000000..8c2aa713 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-hexdigits.c @@ -0,0 +1,42 @@ +/* $Id: bs3-cmn-hexdigits.c $ */ +/** @file + * BS3Kit - Hex digits constants. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "bs3kit-template-header.h" + + +char const g_achBs3HexDigits[16+1] = "0123456789abcdef"; +char const g_achBs3HexDigitsUpper[16+1] = "0123456789ABCDEF"; + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-instantiate-common.h b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-instantiate-common.h new file mode 100644 index 00000000..bf26ef1f --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-instantiate-common.h @@ -0,0 +1,214 @@ +/* $Id: bs3-cmn-instantiate-common.h $ */ +/** @file + * BS3Kit - Common template instantiator body. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/* + * Instantiating common code (c16, c32, c64). + * This must be done first. + */ + +/** @def BS3_INSTANTIATING_CMN + * @ingroup grp_bs3kit_tmpl + * Indicates that we're instantiating common code (c16, c32, c64). + */ +#define BS3_INSTANTIATING_CMN + +#ifdef BS3_CMN_INSTANTIATE_FILE1 + +# define BS3_CMN_INSTANTIATE_FILE1_B <BS3_CMN_INSTANTIATE_FILE1> + +# if ARCH_BITS == 16 /* 16-bit - real mode. */ +# define TMPL_MODE BS3_MODE_RM +# include <bs3kit/bs3kit-template-header.h> +# include BS3_CMN_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> +# endif + +# if ARCH_BITS == 32 /* 32-bit - paged protected mode. */ +# define TMPL_MODE BS3_MODE_PP32 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_CMN_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> +# endif + +# if ARCH_BITS == 64 /* 64-bit. */ +# define TMPL_MODE BS3_MODE_LM64 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_CMN_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> +# endif + +#endif + +#undef BS3_INSTANTIATING_CMN + + +/* + * Instantiating code for each individual mode (rm, pe16, pe16_32, ...). + */ + +/** @def BS3_INSTANTIATING_MODE + * @ingroup grp_bs3kit_tmpl + * Indicates that we're instantiating mode specific code (rm, pe16, ...). + */ +#define BS3_INSTANTIATING_MODE + +#ifdef BS3_MODE_INSTANTIATE_FILE1 + +# define BS3_MODE_INSTANTIATE_FILE1_B <BS3_MODE_INSTANTIATE_FILE1> + +# if ARCH_BITS == 16 /* 16-bit */ + +# define TMPL_MODE BS3_MODE_RM +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# define TMPL_MODE BS3_MODE_PE16 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# define TMPL_MODE BS3_MODE_PE16_V86 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# define TMPL_MODE BS3_MODE_PE32_16 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# define TMPL_MODE BS3_MODE_PEV86 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# define TMPL_MODE BS3_MODE_PP16 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# define TMPL_MODE BS3_MODE_PP16_V86 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# define TMPL_MODE BS3_MODE_PP32_16 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# define TMPL_MODE BS3_MODE_PPV86 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# define TMPL_MODE BS3_MODE_PAE16 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# define TMPL_MODE BS3_MODE_PAE16_V86 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# define TMPL_MODE BS3_MODE_PAE32_16 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# define TMPL_MODE BS3_MODE_PAEV86 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# define TMPL_MODE BS3_MODE_LM16 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# endif + +# if ARCH_BITS == 32 /* 32-bit */ + +# define TMPL_MODE BS3_MODE_PE16_32 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# define TMPL_MODE BS3_MODE_PE32 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# define TMPL_MODE BS3_MODE_PP16_32 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# define TMPL_MODE BS3_MODE_PP32 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# define TMPL_MODE BS3_MODE_PAE16_32 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# define TMPL_MODE BS3_MODE_PAE32 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# define TMPL_MODE BS3_MODE_LM32 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> + +# endif + +# if ARCH_BITS == 64 /* 64-bit. */ +# define TMPL_MODE BS3_MODE_LM64 +# include <bs3kit/bs3kit-template-header.h> +# include BS3_MODE_INSTANTIATE_FILE1_B +# include <bs3kit/bs3kit-template-footer.h> +# endif + +#endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-instantiate-x0.c16 b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-instantiate-x0.c16 new file mode 100644 index 00000000..08618626 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-instantiate-x0.c16 @@ -0,0 +1,39 @@ +/* $Id: bs3-cmn-instantiate-x0.c16 $ */ +/** @file + * BS3Kit - 16-bit common C template instantiator, using the BS3X0TEXT16 segment. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define BS3_USE_X0_TEXT_SEG 1 +#include "bs3-cmn-instantiate-common.h" + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-instantiate-x1.c16 b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-instantiate-x1.c16 new file mode 100644 index 00000000..ef7c3090 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-instantiate-x1.c16 @@ -0,0 +1,39 @@ +/* $Id: bs3-cmn-instantiate-x1.c16 $ */ +/** @file + * BS3Kit - 16-bit common C template instantiator, using the BS3X1TEXT16 segment. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define BS3_USE_X1_TEXT_SEG 1 +#include "bs3-cmn-instantiate-common.h" + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-instantiate.c16 b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-instantiate.c16 new file mode 100644 index 00000000..a6d5c376 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-instantiate.c16 @@ -0,0 +1,39 @@ +/* $Id: bs3-cmn-instantiate.c16 $ */ +/** @file + * BS3Kit - 16-bit common C template instantiator. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +#include "bs3-cmn-instantiate-common.h" + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-instantiate.c32 b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-instantiate.c32 new file mode 100644 index 00000000..149cfe53 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-instantiate.c32 @@ -0,0 +1,39 @@ +/* $Id: bs3-cmn-instantiate.c32 $ */ +/** @file + * BS3Kit - 32-bit common C template instantiator. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +#include "bs3-cmn-instantiate-common.h" + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-instantiate.c64 b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-instantiate.c64 new file mode 100644 index 00000000..d1688165 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-instantiate.c64 @@ -0,0 +1,39 @@ +/* $Id: bs3-cmn-instantiate.c64 $ */ +/** @file + * BS3Kit - 64-bit common C template instantiator. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +#include "bs3-cmn-instantiate-common.h" + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-memory.h b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-memory.h new file mode 100644 index 00000000..143dcf52 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-memory.h @@ -0,0 +1,109 @@ +/* $Id: bs3-cmn-memory.h $ */ +/** @file + * BS3Kit - Internal Memory Structures, Variables and Functions. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef BS3KIT_INCLUDED_bs3_cmn_memory_h +#define BS3KIT_INCLUDED_bs3_cmn_memory_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "bs3kit.h" +#include <iprt/asm.h> + +RT_C_DECLS_BEGIN; + + +typedef union BS3SLABCTLLOW +{ + BS3SLABCTL Core; + uint32_t au32Alloc[(sizeof(BS3SLABCTL) + (0xA0000 / _4K / 8) ) / 4]; +} BS3SLABCTLLOW; +#ifndef DOXYGEN_RUNNING +# define g_Bs3Mem4KLow BS3_DATA_NM(g_Bs3Mem4KLow) +#endif +extern BS3SLABCTLLOW g_Bs3Mem4KLow; + + +typedef union BS3SLABCTLUPPERTILED +{ + BS3SLABCTL Core; + uint32_t au32Alloc[(sizeof(BS3SLABCTL) + ((BS3_SEL_TILED_AREA_SIZE - _1M) / _4K / 8) ) / 4]; +} BS3SLABCTLUPPERTILED; +#ifndef DOXYGEN_RUNNING +# define g_Bs3Mem4KUpperTiled BS3_DATA_NM(g_Bs3Mem4KUpperTiled) +#endif +extern BS3SLABCTLUPPERTILED g_Bs3Mem4KUpperTiled; + + +/** The number of chunk sizes used by the slab list arrays + * (g_aBs3LowSlabLists, g_aBs3UpperTiledSlabLists, more?). */ +#define BS3_MEM_SLAB_LIST_COUNT 6 + +#ifndef DOXYGEN_RUNNING +# define g_aiBs3SlabListsByPowerOfTwo BS3_DATA_NM(g_aiBs3SlabListsByPowerOfTwo) +# define g_acbBs3SlabLists BS3_DATA_NM(g_acbBs3SlabLists) +# define g_aBs3LowSlabLists BS3_DATA_NM(g_aBs3LowSlabLists) +# define g_aBs3UpperTiledSlabLists BS3_DATA_NM(g_aBs3UpperTiledSlabLists) +# define g_cbBs3SlabCtlSizesforLists BS3_DATA_NM(g_cbBs3SlabCtlSizesforLists) +#endif +extern uint8_t const g_aiBs3SlabListsByPowerOfTwo[12]; +extern uint16_t const g_acbBs3SlabLists[BS3_MEM_SLAB_LIST_COUNT]; +extern BS3SLABHEAD g_aBs3LowSlabLists[BS3_MEM_SLAB_LIST_COUNT]; +extern BS3SLABHEAD g_aBs3UpperTiledSlabLists[BS3_MEM_SLAB_LIST_COUNT]; +extern uint16_t const g_cbBs3SlabCtlSizesforLists[BS3_MEM_SLAB_LIST_COUNT]; + + +/** + * Translates a allocation request size to a slab list index. + * + * @returns Slab list index if small request, UINT8_MAX if large. + * @param cbRequest The number of bytes requested. + */ +DECLINLINE(uint8_t) bs3MemSizeToSlabListIndex(size_t cbRequest) +{ + if (cbRequest <= g_acbBs3SlabLists[BS3_MEM_SLAB_LIST_COUNT - 1]) + { + unsigned idx = cbRequest ? ASMBitLastSetU16((uint16_t)(cbRequest - 1)) : 0; + return g_aiBs3SlabListsByPowerOfTwo[idx]; + } + return UINT8_MAX; +} + + +RT_C_DECLS_END; + +#endif /* !BS3KIT_INCLUDED_bs3_cmn_memory_h */ + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-paging.h b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-paging.h new file mode 100644 index 00000000..8d37afa0 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-paging.h @@ -0,0 +1,69 @@ +/* $Id: bs3-cmn-paging.h $ */ +/** @file + * BS3Kit - Internal Paging Structures, Variables and Functions. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef BS3KIT_INCLUDED_bs3_cmn_paging_h +#define BS3KIT_INCLUDED_bs3_cmn_paging_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "bs3kit.h" +#include <iprt/asm.h> + +RT_C_DECLS_BEGIN + +/** Root directory for page protected mode. + * UINT32_MAX if not initialized. */ +extern uint32_t g_PhysPagingRootPP; +/** Root directory pointer table for PAE mode. + * UINT32_MAX if not initialized. */ +extern uint32_t g_PhysPagingRootPAE; +/** Root table (level 4) for long mode. + * UINT32_MAX if not initialized. */ +extern uint32_t g_PhysPagingRootLM; + +#undef bs3PagingGetLegacyPte +BS3_CMN_PROTO_STUB(X86PTE BS3_FAR *, bs3PagingGetLegacyPte,(RTCCUINTXREG cr3, uint32_t uFlat, bool fUseInvlPg, int *prc)); +#undef bs3PagingGetPaePte +BS3_CMN_PROTO_STUB(X86PTEPAE BS3_FAR *, bs3PagingGetPaePte,(RTCCUINTXREG cr3, uint8_t bMode, uint64_t uFlat, + bool fUseInvlPg, int *prc)); + +RT_C_DECLS_END + +#include "bs3kit-mangling-code.h" + +#endif /* !BS3KIT_INCLUDED_bs3_cmn_paging_h */ + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-pic-data.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-pic-data.c new file mode 100644 index 00000000..b8289e8c --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-pic-data.c @@ -0,0 +1,52 @@ +/* $Id: bs3-cmn-pic-data.c $ */ +/** @file + * BS3Kit - PIC Data. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include "bs3-cmn-pic.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#if ARCH_BITS == 16 +/** Set by the first call to Bs3PicSetup. */ +bool g_fBs3PicConfigured = false; +#endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-pic.h b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-pic.h new file mode 100644 index 00000000..908f6a6f --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-pic.h @@ -0,0 +1,70 @@ +/* $Id: bs3-cmn-pic.h $ */ +/** @file + * BS3Kit - Internal PIC Defines, Variables and Functions. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef BS3KIT_INCLUDED_bs3_cmn_pic_h +#define BS3KIT_INCLUDED_bs3_cmn_pic_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "bs3kit.h" + + +/** The master PIC port (base). */ +#define BS3_PIC_PORT_MASTER UINT8_C(0x20) +/** The slave PIC port (base). */ +#define BS3_PIC_PORT_SLAVE UINT8_C(0xa0) + +/** The init command. */ +#define BS3_PIC_CMD_INIT UINT8_C(0x10) +/** 4th init step option for the init command. */ +#define BS3_PIC_CMD_INIT_F_4STEP UINT8_C(0x01) + +/** Auto end of interrupt flag for the 4th init step. */ +#define BS3_PIC_I4_F_AUTO_EOI UINT8_C(0x01) + + +RT_C_DECLS_BEGIN + +extern bool g_fBs3PicConfigured; + +RT_C_DECLS_END + + +#include "bs3kit-mangling-code.h" + +#endif /* !BS3KIT_INCLUDED_bs3_cmn_pic_h */ + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-pit.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-pit.c new file mode 100644 index 00000000..5ae51211 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-pit.c @@ -0,0 +1,170 @@ +/* $Id: bs3-cmn-pit.c $ */ +/** @file + * BS3Kit - PIT Setup and Disable code. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" +#include <iprt/asm-amd64-x86.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define BS3_PIT_PORT_CMD 0x43 +#define BS3_PIT_PORT_CH0_DATA 0x40 +#define BS3_PIT_HZ UINT32_C(1193182) + + +/********************************************************************************************************************************* +* External Symbols * +*********************************************************************************************************************************/ +extern FNBS3TRAPHANDLER16 bs3PitIrqHandler_c16; +extern FNBS3TRAPHANDLER32 bs3PitIrqHandler_c32; +extern FNBS3TRAPHANDLER64 bs3PitIrqHandler_c64; + + +#undef Bs3PitSetupAndEnablePeriodTimer +BS3_CMN_DEF(void, Bs3PitSetupAndEnablePeriodTimer,(uint16_t cHzDesired)) +{ + RTCCUINTREG fSaved; + uint16_t cCount; + uint16_t cMsInterval; + uint32_t cNsInterval; + + /* + * Disable the PIT and make sure we've configured the IRQ handlers. + */ + Bs3PitDisable(); + Bs3PicSetup(false /*fForcedReInit*/); + Bs3TrapSetHandlerEx(0x70, bs3PitIrqHandler_c16, bs3PitIrqHandler_c32, bs3PitIrqHandler_c64); + + /* + * Reset the counters. + */ + g_cBs3PitNs = 0; + g_cBs3PitMs = 0; + g_cBs3PitTicks = 0; + + /* + * Calculate an interval. + */ + if (cHzDesired <= 18) + { + cCount = 0; /* 1193182 / 65536 = 18.206512451171875 Hz */ + cHzDesired = 18; + cNsInterval = UINT32_C(54925401); /* 65536 / 1193182 = 0.054925401154224586022920225078823 seconds */ + cMsInterval = 55; + } + else + { + cCount = BS3_PIT_HZ / cHzDesired; + cHzDesired = BS3_PIT_HZ / cCount; + /* 1s/1193182 = 0.000 000 838 095 110 38550698887512550474278 */ +#if ARCH_BITS == 64 + cNsInterval = cCount * UINT64_C(838095110) / 1000000; +#elif ARCH_BITS == 32 + cNsInterval = cCount * UINT32_C(8381) / 10; +#else + cNsInterval = cCount * 838; +#endif + if (cCount <= 1194) + cMsInterval = 1; /* Must not be zero! */ + else + cMsInterval = cCount / 1194; + } + + + /* + * Do the reprogramming. + */ + fSaved = ASMIntDisableFlags(); + ASMOutU8(BS3_PIT_PORT_CMD, + (0 << 6) /* select: channel 0 */ + | (3 << 4) /* access mode: lobyte/hibyte */ + | (2 << 1) /* operation: Mode 2 */ + | 0 /* binary mode */ + ); + ASMOutU8(BS3_PIT_PORT_CH0_DATA, (uint8_t)cCount); + ASMOutU8(BS3_PIT_PORT_CH0_DATA, (uint8_t)(cCount >> 8)); + + g_cBs3PitIntervalNs = cNsInterval; + g_cBs3PitIntervalHz = cHzDesired; + g_cBs3PitIntervalMs = cMsInterval; + + Bs3PicUpdateMask(UINT16_C(0xfffe), 0); + + ASMSetFlags(fSaved); +} + + +#undef Bs3PitDisable +BS3_CMN_DEF(void, Bs3PitDisable,(void)) +{ + if (g_cBs3PitIntervalHz != 0) + { + RTCCUINTREG fSaved = ASMIntDisableFlags(); + + /* + * Not entirely sure what's the best way to do this, but let's try reprogram + * it to a no-reload mode like 0 and set the count to 1. + */ + g_cBs3PitIntervalMs = 0; + ASMOutU8(BS3_PIT_PORT_CMD, + (0 << 6) /* select: channel 0 */ + | (1 << 4) /* access mode: lobyte */ + | (0 << 1) /* operation: Mode 0 */ + | 0 /* binary mode */ + ); + ASMOutU8(BS3_PIT_PORT_CH0_DATA, (uint8_t)1); + + /* + * Then mask the PIT IRQ on the PIC. + */ + Bs3PicUpdateMask(UINT16_C(0xffff), 1); + + ASMSetFlags(fSaved); + } + + /* + * Reset the interval values (leave the ticks and elapsed ns/ms values as-is). + */ + g_cBs3PitIntervalNs = 0; + g_cBs3PitIntervalMs = 0; + g_cBs3PitIntervalHz = 0; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-test.h b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-test.h new file mode 100644 index 00000000..571b268f --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-test.h @@ -0,0 +1,179 @@ +/* $Id: bs3-cmn-test.h $ */ +/** @file + * BS3Kit - Bs3Test internal header. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef BS3KIT_INCLUDED_bs3_cmn_test_h +#define BS3KIT_INCLUDED_bs3_cmn_test_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "bs3kit.h" +#include <VBox/VMMDevTesting.h> + + +/** Indicates whether the VMMDev is operational. */ +#ifndef DOXYGEN_RUNNING +# define g_fbBs3VMMDevTesting BS3_DATA_NM(g_fbBs3VMMDevTesting) +#endif +extern bool g_fbBs3VMMDevTesting; + +/** The number of tests that have failed. */ +#ifndef DOXYGEN_RUNNING +# define g_cusBs3TestErrors BS3_DATA_NM(g_cusBs3TestErrors) +#endif +extern uint16_t g_cusBs3TestErrors; + +/** The start error count of the current subtest. */ +#ifndef DOXYGEN_RUNNING +# define g_cusBs3SubTestAtErrors BS3_DATA_NM(g_cusBs3SubTestAtErrors) +#endif +extern uint16_t g_cusBs3SubTestAtErrors; + +/** Whether we've reported the sub-test result or not. */ +#ifndef DOXYGEN_RUNNING +# define g_fbBs3SubTestReported BS3_DATA_NM(g_fbBs3SubTestReported) +#endif +extern bool g_fbBs3SubTestReported; +/** Whether the sub-test has been skipped or not. */ +#ifndef DOXYGEN_RUNNING +# define g_fbBs3SubTestSkipped BS3_DATA_NM(g_fbBs3SubTestSkipped) +#endif +extern bool g_fbBs3SubTestSkipped; + +/** The number of sub tests. */ +#ifndef DOXYGEN_RUNNING +# define g_cusBs3SubTests BS3_DATA_NM(g_cusBs3SubTests) +#endif +extern uint16_t g_cusBs3SubTests; + +/** The number of sub tests that failed. */ +#ifndef DOXYGEN_RUNNING +# define g_cusBs3SubTestsFailed BS3_DATA_NM(g_cusBs3SubTestsFailed) +#endif +extern uint16_t g_cusBs3SubTestsFailed; + +/** VMMDEV_TESTING_UNIT_XXX -> string */ +#ifndef DOXYGEN_RUNNING +# define g_aszBs3TestUnitNames BS3_DATA_NM(g_aszBs3TestUnitNames) +#endif +extern char const g_aszBs3TestUnitNames[][12]; + +/** The test name. */ +extern const char BS3_FAR *g_pszBs3Test_c16; +extern const char *g_pszBs3Test_c32; +extern const char *g_pszBs3Test_c64; + +/** The subtest name. */ +#ifndef DOXYGEN_RUNNING +# define g_szBs3SubTest BS3_DATA_NM(g_szBs3SubTest) +#endif +extern char g_szBs3SubTest[64]; + + +/** + * Sends a command to VMMDev followed by a single string. + * + * If the VMMDev is not present or is not being used, this function will + * do nothing. + * + * @param uCmd The command. + * @param pszString The string. + */ +#ifndef DOXYGEN_RUNNING +# define bs3TestSendCmdWithStr BS3_CMN_NM(bs3TestSendCmdWithStr) +#endif +BS3_DECL(void) bs3TestSendCmdWithStr(uint32_t uCmd, const char BS3_FAR *pszString); + +/** + * Sends a command to VMMDev followed by a 32-bit unsigned integer value. + * + * If the VMMDev is not present or is not being used, this function will + * do nothing. + * + * @param uCmd The command. + * @param uValue The value. + */ +#ifndef DOXYGEN_RUNNING +# define bs3TestSendCmdWithU32 BS3_CMN_NM(bs3TestSendCmdWithU32) +#endif +BS3_DECL(void) bs3TestSendCmdWithU32(uint32_t uCmd, uint32_t uValue); + +/** + * Checks if the VMMDev is configured for testing. + * + * @returns true / false. + */ +#ifndef DOXYGEN_RUNNING +# define bs3TestIsVmmDevTestingPresent BS3_CMN_NM(bs3TestIsVmmDevTestingPresent) +#endif +BS3_DECL(bool) bs3TestIsVmmDevTestingPresent(void); + +/** + * Similar to rtTestSubCleanup. + */ +#ifndef DOXYGEN_RUNNING +# define bs3TestSubCleanup BS3_CMN_NM(bs3TestSubCleanup) +#endif +BS3_DECL(void) bs3TestSubCleanup(void); + +/** + * @callback_method_impl{FNBS3STRFORMATOUTPUT, + * Used by Bs3TestFailedV and Bs3TestSkippedV. + * + * The @a pvUser parameter must point a BS3TESTFAILEDBUF structure. } + */ +#ifndef DOXYGEN_RUNNING +# define bs3TestFailedStrOutput BS3_CMN_NM(bs3TestFailedStrOutput) +#endif +BS3_DECL_CALLBACK(size_t) bs3TestFailedStrOutput(char ch, void BS3_FAR *pvUser); + +/** + * Output buffering for bs3TestFailedStrOutput. + */ +typedef struct BS3TESTFAILEDBUF +{ + /** Initialize to false. */ + bool fNewLine; + /** Initialize to zero. */ + uint8_t cchBuf; + /** Buffer, uninitialized. */ + char achBuf[128]; +} BS3TESTFAILEDBUF; +/** Pointer to a bs3TestFailedStrOutput buffer. */ +typedef BS3TESTFAILEDBUF BS3_FAR *PBS3TESTFAILEDBUF; + +#endif /* !BS3KIT_INCLUDED_bs3_cmn_test_h */ + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-common.mac b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-common.mac new file mode 100644 index 00000000..23381a49 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-common.mac @@ -0,0 +1,281 @@ +; $Id: bs3-first-common.mac $ +;; @file +; BS3Kit - First Object, common stuff. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%define BS3_BEGIN_TEXT16_WITHOUT_GROUP +%define BS3_BEGIN_DATA16_WITHOUT_GROUP +%define BS3_BEGIN_RMTEXT16_WITHOUT_GROUP +%define BS3_BEGIN_X0TEXT16_WITHOUT_GROUP +%define BS3_BEGIN_X1TEXT16_WITHOUT_GROUP + +%include "bs3kit.mac" + + +; +; +; Define all the segments and their grouping, just to get that right once at +; the start of everything. +; +; + +; +; 16-bit text +; +%ifndef BS3_IS_DOS_EXE +BS3_BEGIN_TEXT16 +%else +section BEGTEXT align=2 CLASS=BS3CLASS16CODE PUBLIC USE16 +BS3_BEGIN_TEXT16 +section BEGTEXT +%endif +BS3_GLOBAL_DATA Bs3Text16_StartOfSegment, 0 + +; Entry point with eye-catcher. +GLOBALNAME start +global __ImageBase ; for MS compiler - must be first! +__ImageBase: +global ___begtext ; for DOS EXEs (causes harmless duplicate symbol warning) +___begtext: +%ifndef BS3_IS_DOS_EXE + jmp .after_eye_catcher +%else + int3 + jmp __ImageBase +%endif + db 10,13,'eye-catcher: BS3TEXT16',10,13 +BS3_BEGIN_TEXT16 +.after_eye_catcher: + +section _TEXT align=2 CLASS=BS3CLASS16CODE PUBLIC USE16 +section BS3TEXT16_NEARSTUBS align=1 CLASS=BS3CLASS16CODE PUBLIC USE16 +section BS3TEXT16_FARSTUBS align=1 CLASS=BS3CLASS16CODE PUBLIC USE16 +section BS3TEXT16_END align=1 CLASS=BS3CLASS16CODE PUBLIC USE16 + +BS3_GLOBAL_DATA Bs3Text16_EndOfSegment, 0 + +%ifndef BS3_IS_DOS_EXE +GROUP CGROUP16 BS3TEXT16 _TEXT BS3TEXT16_NEARSTUBS BS3TEXT16_FARSTUBS BS3TEXT16_END +%else +GROUP CGROUP16 BEGTEXT BS3TEXT16 _TEXT BS3TEXT16_NEARSTUBS BS3TEXT16_FARSTUBS BS3TEXT16_END +%endif + + +; +; 16-bit data +; +BS3_BEGIN_DATA16 +BS3_GLOBAL_DATA Bs3Data16_StartOfSegment, 0 + db 10,13,'eye-catcher: BS3DATA16',10,13 + +ALIGNDATA(16) +BS3_GLOBAL_DATA Bs3Data16_Size, 4 + dd BS3_DATA_NM(Bs3Data16_EndOfSegment) wrt BS3KIT_GRPNM_DATA16 +BS3_GLOBAL_DATA Bs3Data16Thru64Text32And64_TotalSize, 4 + dd BS3_DATA_NM(Bs3Data64_EndOfSegment) wrt BS3KIT_GRPNM_DATA16 +BS3_GLOBAL_DATA Bs3TotalImageSize, 4 + dd BS3_DATA_NM(Bs3Text64_EndOfSegment) wrt CGROUP16 ; ASSUMES TEXT64 is last. + +BS3_GLOBAL_DATA Bs3Text16_Size, 2 + dw BS3_DATA_NM(Bs3Text16_EndOfSegment) wrt CGROUP16 +BS3_GLOBAL_DATA Bs3RmText16_Size, 2 + dw BS3_DATA_NM(Bs3RmText16_EndOfSegment) wrt BS3GROUPRMTEXT16 +BS3_GLOBAL_DATA Bs3X0Text16_Size, 2 + dw BS3_DATA_NM(Bs3X0Text16_EndOfSegment) wrt BS3GROUPX0TEXT16 +BS3_GLOBAL_DATA Bs3X1Text16_Size, 2 + dw BS3_DATA_NM(Bs3X1Text16_EndOfSegment) wrt BS3GROUPX1TEXT16 + +BS3_GLOBAL_DATA Bs3RmText16_FlatAddr, 4 + dd BS3_DATA_NM(Bs3RmText16_StartOfSegment) wrt BS3FLAT +BS3_GLOBAL_DATA Bs3X0Text16_FlatAddr, 4 + dd BS3_DATA_NM(Bs3X0Text16_StartOfSegment) wrt BS3FLAT +BS3_GLOBAL_DATA Bs3X1Text16_FlatAddr, 4 + dd BS3_DATA_NM(Bs3X1Text16_StartOfSegment) wrt BS3FLAT + +section BS3DATA16CONST align=2 CLASS=BS3KIT_CLASS_DATA16 PUBLIC USE16 +section BS3DATA16CONST2 align=2 CLASS=BS3KIT_CLASS_DATA16 PUBLIC USE16 +section BS3DATA16_DATA align=2 CLASS=BS3KIT_CLASS_DATA16 PUBLIC USE16 +%ifdef BS3_IS_DOS_EXE +section _NULL align=16 CLASS=BEGDATA PUBLIC USE16 +section _AFTERNULL align=2 CLASS=BEGDATA PUBLIC USE16 +%endif +section CONST align=2 CLASS=DATA PUBLIC USE16 +section CONST2 align=2 CLASS=DATA PUBLIC USE16 +section _DATA align=2 CLASS=DATA PUBLIC USE16 +%ifdef BS3_IS_DOS_EXE +section XIB align=1 CLASS=DATA PUBLIC USE16 +section XI align=1 CLASS=DATA PUBLIC USE16 +section XIE align=1 CLASS=DATA PUBLIC USE16 +section YIB align=1 CLASS=DATA PUBLIC USE16 +section YI align=1 CLASS=DATA PUBLIC USE16 +section YIE align=1 CLASS=DATA PUBLIC USE16 +%endif +section STRINGS align=2 CLASS=DATA PUBLIC USE16 +section DATA align=2 CLASS=DATA PUBLIC USE16 +section _BSS align=2 CLASS=BS3KIT_CLASS_BSS16 PUBLIC USE16 +section BSS align=2 CLASS=BS3KIT_CLASS_BSS16 PUBLIC USE16 +%ifdef BS3_IS_DOS_EXE +section STACK align=16 CLASS=STACK STACK USE16 +%endif +section BS3DATA16_END align=2 CLASS=BS3KIT_CLASS_BSS16 PUBLIC USE16 + +BS3_GLOBAL_DATA Bs3Data16_EndOfSegment, 0 + +%ifndef BS3_IS_DOS_EXE +GROUP BS3KIT_GRPNM_DATA16 BS3DATA16 BS3DATA16_DATA _DATA DATA BS3DATA16CONST CONST BS3DATA16CONST2 CONST2 STRINGS _BSS BSS BS3DATA16_END +%else +GROUP BS3KIT_GRPNM_DATA16 \ + _NULL _AFTERNULL \ + CONST BS3DATA16CONST CONST2 BS3DATA16CONST2 _DATA XIB XI XIE YIB YI YIE STRINGS DATA BS3DATA16 BS3DATA16_DATA \ + _BSS BSS BS3DATA16_END \ + STACK +%endif + +; +; 16-bit real-mode text +; +section BS3RMTEXT16_START align=16 CLASS=BS3CLASS16RMCODE PUBLIC USE16 +BS3_GLOBAL_DATA Bs3RmText16_StartOfSegment, 0 + ;db 10,13,'eye-catcher: BS3RMTEXT16',10,13 - messes up switch in C code. Alt. is fConvertFixupp VBoxBs3ObjConverter.cpp. +BS3_BEGIN_RMTEXT16 +section BS3RMTEXT16_END align=1 CLASS=BS3CLASS16RMCODE PUBLIC USE16 +BS3_GLOBAL_DATA Bs3RmText16_EndOfSegment, 0 +GROUP BS3GROUPRMTEXT16 BS3RMTEXT16_START BS3RMTEXT16 BS3RMTEXT16_END + + +; +; 16-bit extra text segment #0. +; +section BS3X0TEXT16_START align=16 CLASS=BS3CLASS16X0CODE PUBLIC USE16 +BS3_GLOBAL_DATA Bs3X0Text16_StartOfSegment, 0 + ;db 10,13,'eye-catcher: BS3X0TEXT16',10,13 - messes up switch in C code. Alt. is fConvertFixupp VBoxBs3ObjConverter.cpp. +BS3_BEGIN_X0TEXT16 4 +section BS3X0TEXT16_END align=16 CLASS=BS3CLASS16X0CODE PUBLIC USE16 +BS3_GLOBAL_DATA Bs3X0Text16_EndOfSegment, 0 +GROUP BS3GROUPX0TEXT16 BS3X0TEXT16_START BS3X0TEXT16 BS3X0TEXT16_END + + +; +; 16-bit extra text segment #1. +; +section BS3X1TEXT16_START align=16 CLASS=BS3CLASS16X1CODE PUBLIC USE16 +BS3_GLOBAL_DATA Bs3X1Text16_StartOfSegment, 0 + ;db 10,13,'eye-catcher: BS3X1TEXT16',10,13 - messes up switch in C code. Alt. is fConvertFixupp VBoxBs3ObjConverter.cpp. +BS3_BEGIN_X1TEXT16 4 +section BS3X1TEXT16_END align=16 CLASS=BS3CLASS16X1CODE PUBLIC USE16 +BS3_GLOBAL_DATA Bs3X1Text16_EndOfSegment, 0 +GROUP BS3GROUPX1TEXT16 BS3X1TEXT16_START BS3X1TEXT16 BS3X1TEXT16_END + + +; +; 32-bit text +; +BS3_BEGIN_TEXT32 +BS3_GLOBAL_DATA Bs3Text32_StartOfSegment, 0 + db 10,13,'eye-catcher: BS3TEXT32',10,13 +section BS3TEXT32_END align=1 CLASS=BS3CLASS32CODE PUBLIC USE32 FLAT +BS3_GLOBAL_DATA Bs3Text32_EndOfSegment, 0 + + +; +; This is a hack to separate the 32-bit and 64-bit text segments when linking, +; such that they don't share the same base frame because they're both assigned +; to the AUTO group by the linker. +; +section BS3SEPARATE32AND64BITCODE align=16 CLASS=BS3CLASSSEPARATE32AND64BITCODE PUBLIC USE16 +BS3_GLOBAL_DATA Bs3Separate32And64BitCode_StartOfSegment, 0 + db 10,13,'eye-catcher: 32-64 wedge',10,13 +section BS3SEPARATE32AND64BITCODE_END align=16 CLASS=BS3CLASSSEPARATE32AND64BITCODE PUBLIC USE16 +BS3_GLOBAL_DATA Bs3Separate32And64BitCode_EndOfSegment, 0 +GROUP BS3SEPARATE32AND64BITCODEGROUP BS3SEPARATE32AND64BITCODE BS3SEPARATE32AND64BITCODE_END + + +; +; 64-bit text +; +BS3_BEGIN_TEXT64 +BS3_GLOBAL_DATA Bs3Text64_StartOfSegment, 0 + db 10,13,'eye-catcher: BS3TEXT64',10,13 +section BS3TEXT64_END align=1 CLASS=BS3CLASS64CODE PUBLIC USE32 FLAT +BS3_GLOBAL_DATA Bs3Text64_EndOfSegment, 0 + + +; +; FAR_DATA segment in DOS EXEs should be near the other FAR_DATA class segments. +; +%ifdef BS3_IS_DOS_EXE +section FAR_DATA align=1 CLASS=FAR_DATA PUBLIC USE16 +%endif + +; +; 32-bit data +; +BS3_BEGIN_DATA32 +BS3_GLOBAL_DATA Bs3Data32_StartOfSegment, 0 + db 10,13,'eye-catcher: BS3DATA32',10,13 +section BS3DATA32CONST align=16 CLASS=FAR_DATA PUBLIC USE32 +section BS3DATA32CONST2 align=16 CLASS=FAR_DATA PUBLIC USE32 +section BS3DATA32_DATA align=16 CLASS=FAR_DATA PUBLIC USE32 +section BS3DATA32_BSS align=16 CLASS=FAR_DATA PUBLIC USE32 +section BS3DATA32_END align=16 CLASS=FAR_DATA PUBLIC USE32 +BS3_GLOBAL_DATA Bs3Data32_EndOfSegment, 0 +GROUP BS3DATA32_GROUP BS3DATA32 BS3DATA32_DATA BS3DATA32CONST BS3DATA32CONST2 BS3DATA32_BSS BS3DATA32_END + +; +; 64-bit data +; +BS3_BEGIN_DATA64 +BS3_GLOBAL_DATA Bs3Data64_StartOfSegment, 0 + db 10,13,'eye-catcher: BS3DATA64',10,13 +section BS3DATA64CONST align=16 CLASS=FAR_DATA PUBLIC USE32 +section BS3DATA64_BSS align=16 CLASS=FAR_DATA PUBLIC USE32 +section BS3DATA64_END align=16 CLASS=FAR_DATA PUBLIC USE32 +BS3_GLOBAL_DATA Bs3Data64_EndOfSegment, 0 +GROUP BS3DATA64_GROUP BS3DATA64 BS3DATA64CONST BS3DATA64_BSS BS3DATA64_END + + +; +; 16-bit accessible system data. +; No need to do anything here. +; +BS3_BEGIN_SYSTEM16 + + +; +; Switch back to the 16-bit code segment and the startup code. +; +BS3_BEGIN_TEXT16 +BS3_GLOBAL_NAME_EX Bs3KitEntryPoint, function, 0 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-dosexe.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-dosexe.asm new file mode 100644 index 00000000..e40fdaa7 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-dosexe.asm @@ -0,0 +1,43 @@ +; $Id: bs3-first-dosexe.asm $ +;; @file +; BS3Kit - First Object for DOS executables, defines segments only. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +; +; Segment defs, grouping and related variables. +; Defines the entry point 'start' as well, leaving us in BS3TEXT16. +; +%include "bs3-first-common.mac" + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-init-all-lm64.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-init-all-lm64.asm new file mode 100644 index 00000000..128a57ee --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-init-all-lm64.asm @@ -0,0 +1,91 @@ +; $Id: bs3-first-init-all-lm64.asm $ +;; @file +; BS3Kit - First Object, calling 32-bit paged protected mode main() after full init. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +; +; Segment defs, grouping and related variables. +; Defines the entry point 'start' as well, leaving us in BS3TEXT16. +; +%include "bs3-first-common.mac" + +extern NAME(Bs3InitAll_rm) +extern NAME(Bs3SwitchToLM64_rm) +extern NAME(Main_lm64) +extern NAME(Bs3Shutdown_c64) +extern BS3_DATA_NM(g_uBs3CpuDetected) +extern NAME(Bs3PrintStrN_c16) +extern NAME(Bs3Panic_c16) + +;; Entry point. + push word 0 ; zero return address. + push word 0 ; zero caller BP + mov bp, sp + + ; + ; Init all while we're in real mode. + ; + mov ax, BS3_SEL_DATA16 + mov es, ax + mov ds, ax + call NAME(Bs3InitAll_rm) + + ; + ; Check that long mode is supported. + ; + test word [BS3_DATA_NM(g_uBs3CpuDetected)], BS3CPU_F_LONG_MODE + jnz .long_mode_supported + push .s_szLongModeError_End - .s_szLongModeError + push cs + push .s_szLongModeError wrt CGROUP16 + call NAME(Bs3PrintStrN_c16) + call NAME(Bs3Panic_c16) +.long_mode_supported: + + ; + ; Switch to LM64 and call main. + ; + call _Bs3SwitchToLM64_rm + BITS 64 + call NAME(Main_lm64) + + ; Try shutdown if it returns. + call NAME(Bs3Shutdown_c64) + +.s_szLongModeError: + db 'BS3 Error! Long mode not supported!', 0ah, 0dh +.s_szLongModeError_End: + db 00h + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-init-all-pe32.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-init-all-pe32.asm new file mode 100644 index 00000000..ff166f13 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-init-all-pe32.asm @@ -0,0 +1,69 @@ +; $Id: bs3-first-init-all-pe32.asm $ +;; @file +; BS3Kit - First Object, calling 32-bit protected mode main() after full init. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +; +; Segment defs, grouping and related variables. +; Defines the entry point 'start' as well, leaving us in BS3TEXT16. +; +%include "bs3-first-common.mac" + +extern NAME(Bs3InitAll_rm) +extern NAME(Bs3SwitchToPE32_rm) + +;; Entry point. + push word 0 ; zero return address. + push word 0 ; zero caller BP + mov bp, sp + + ; + ; Init all while we're in real mode. + ; + mov ax, BS3_SEL_DATA16 + mov es, ax + mov ds, ax + call NAME(Bs3InitAll_rm) + + ; + ; Switch to 32-bit protected mode and call main. + ; + call NAME(Bs3SwitchToPE32_rm) + BS3_SET_BITS 32 + call _Main_pe32 +extern _Main_pe32 +BS3_EXTERN_CMN Bs3Shutdown + call Bs3Shutdown + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-init-all-pp32.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-init-all-pp32.asm new file mode 100644 index 00000000..4c2d2ca0 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-init-all-pp32.asm @@ -0,0 +1,69 @@ +; $Id: bs3-first-init-all-pp32.asm $ +;; @file +; BS3Kit - First Object, calling 32-bit paged protected mode main() after full init. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +; +; Segment defs, grouping and related variables. +; Defines the entry point 'start' as well, leaving us in BS3TEXT16. +; +%include "bs3-first-common.mac" + +extern NAME(Bs3InitAll_rm) +extern NAME(Bs3SwitchToPP32_rm) + +;; Entry point. + push word 0 ; zero return address. + push word 0 ; zero caller BP + mov bp, sp + + ; + ; Init all while we're in real mode. + ; + mov ax, BS3_SEL_DATA16 + mov es, ax + mov ds, ax + call NAME(Bs3InitAll_rm) + + ; + ; Switch to 32-bit protected mode and call main. + ; + call NAME(Bs3SwitchToPP32_rm) + BS3_SET_BITS 32 + call _Main_pp32 +extern _Main_pp32 +BS3_EXTERN_CMN Bs3Shutdown + call Bs3Shutdown + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-pe16.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-pe16.asm new file mode 100644 index 00000000..51884181 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-pe16.asm @@ -0,0 +1,105 @@ +; $Id: bs3-first-pe16.asm $ +;; @file +; BS3Kit - First Object, calling 16-bit protected-mode main(). +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* + +; +; Segment defs, grouping and related variables. +; Defines the entry point 'start' as well, leaving us in BS3TEXT16. +; +%include "bs3-first-common.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_BEGIN_DATA16 + +BS3_BEGIN_RMTEXT16 +extern _Bs3CpuDetect_rm_far +extern _Bs3InitMemory_rm_far + +BS3_BEGIN_TEXT16 +BS3_EXTERN_CMN Bs3PicMaskAll +BS3_EXTERN_CMN Bs3Trap16Init +extern _Bs3SwitchToPE16_rm +extern _Main_pe16 +BS3_EXTERN_CMN Bs3Shutdown + + +BS3_BEGIN_TEXT16 + ; + ; Zero return address and zero caller BP. + ; + xor ax, ax + push ax + push ax + mov bp, sp + + ; + ; Load DS and ES with data selectors. + ; + mov ax, BS3KIT_GRPNM_DATA16 + mov ds, ax + mov es, ax + + + ; + ; Make sure interrupts are disabled as we cannot (don't want to) service + ; BIOS interrupts once we switch mode. + ; + cli + call Bs3PicMaskAll + + ; + ; Initialize 16-bit protected mode. + ; + call far _Bs3CpuDetect_rm_far + call far _Bs3InitMemory_rm_far + call Bs3Trap16Init + + ; + ; Switch to PE16 and call main. + ; + call _Bs3SwitchToPE16_rm + call _Main_pe16 + + ; Try shutdown if it returns. + call Bs3Shutdown + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-rm.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-rm.asm new file mode 100644 index 00000000..2b2a6159 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-first-rm.asm @@ -0,0 +1,59 @@ +; $Id: bs3-first-rm.asm $ +;; @file +; BS3Kit - First Object, calling real-mode main(). +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +; +; Segment defs, grouping and related variables. +; Defines the entry point 'start' as well, leaving us in BS3TEXT16. +; +%include "bs3-first-common.mac" + + +EXTERN Main_rm +BS3_EXTERN_CMN Bs3Shutdown + push word 0 ; zero return address. + push word 0 ; zero caller BP + mov bp, sp + + ; + ; Nothing to init here, just call main and shutdown if it returns. + ; + mov ax, BS3_SEL_DATA16 + mov es, ax + mov ds, ax + call NAME(Main_rm) + call Bs3Shutdown + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-BiosInt15hE820.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-BiosInt15hE820.asm new file mode 100644 index 00000000..c18e9320 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-BiosInt15hE820.asm @@ -0,0 +1,245 @@ +; $Id: bs3-mode-BiosInt15hE820.asm $ +;; @file +; BS3Kit - Bs3BiosInt15hE820 +; + +; +; Copyright (C) 2021-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* Defined Constants And Macros * +;********************************************************************************************************************************* +;; Signature: 'SMAP' +%define INT15_E820_SIGNATURE 0534d4150h + + +;********************************************************************************************************************************* +;* External symbols * +;********************************************************************************************************************************* +TMPL_BEGIN_TEXT +extern TMPL_NM(Bs3SwitchToRM) +BS3_BEGIN_TEXT16 +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_rm) + + +;; +; Performs a int 15h function 0xe820 call. +; +; @cproto BS3_MODE_PROTO_STUB(bool, Bs3BiosInt15hE820,(INT15E820ENTRY BS3_FAR *pEntry, uint32_t BS3_FAR *pcbEntry, +; uint32_t BS3_FAR *puContinuationValue)); +; +; @returns Success indicator. +; @param pEntry The return buffer. +; @param pcbEntry Input: The size of the buffer (min 20 bytes); +; Output: The size of the returned data. +; @param puContinuationValue Where to get and return the continuation value (EBX) +; Set to zero the for the first call. Returned as zero +; after the last entry. +; +; @remarks ASSUMES we're in ring-0 when not in some kind of real mode. +; @remarks ASSUMES we're on a 16-bit suitable stack. +; +; @uses rax +; +TMPL_BEGIN_TEXT +BS3_PROC_BEGIN_MODE Bs3BiosInt15hE820, BS3_PBC_HYBRID + push xBP + mov xBP, xSP + sPUSHF + cli + push sBX + push sCX + push sDX + push sSI + push sDI +%ifdef TMPL_16BIT + push ds + push es +%endif + ; Load/Save parameters. +%define a_pEntry [xBP + xCB + cbCurRetAddr + sCB*0] +%define a_pcbEntry [xBP + xCB + cbCurRetAddr + sCB*1] +%define a_puContinuationValue [xBP + xCB + cbCurRetAddr + sCB*2] +%ifdef TMPL_64BIT + mov a_pEntry, rcx ; save pEntry + mov a_pcbEntry, rdx ; save pcbEntry + mov a_puContinuationValue, r8 ; save a_puContinuationValue + mov ebx, [r8] ; uContinuationValue for int15 + mov ecx, [rdx] ; Buffer size for int15. +%elifdef TMPL_16BIT + les bx, a_pcbEntry + mov ecx, [es:bx] ; Buffer size for int15. + les bx, a_puContinuationValue + mov ebx, [es:bx] ; Buffer size for int15. +%else + mov ecx, a_pcbEntry + mov ecx, [ecx] ; Buffer size for int15. + mov ebx, a_puContinuationValue + mov ebx, [ebx] ; uContinuationValue for int15 +%endif + ; + ; Check that the cbEntry isn't too big or too small before doing + ; the stack allocation. (Our BIOS doesn't check if too small.) + ; + cmp ecx, 100h + jae .failed + cmp cl, 14h + jb .failed + +%if TMPL_MODE != BS3_MODE_RM + sub xSP, xCX ; allocate a temporary buffer on the stack. + and xSP, ~0fh +%endif + + ; + ; Switch to real mode, first we just ot the 16-bit text segment. + ; This preserve all 32-bit register values. + ; +%if TMPL_MODE != BS3_MODE_RM + %ifndef TMPL_16BIT + jmp .to_text16 +BS3_BEGIN_TEXT16 +.to_text16: + BS3_SET_BITS TMPL_BITS + %endif + call TMPL_NM(Bs3SwitchToRM) + BS3_SET_BITS 16 +%endif + + ; + ; Make the call. + ; +%if TMPL_MODE == BS3_MODE_RM + les di, a_pEntry +%else + push ss ; es:di -> ss:sp + pop es + mov di, sp +%endif + mov edx, INT15_E820_SIGNATURE + mov eax, 0e820h ; BIOS function number + int 15h + + ; + ; Switch back. + ; +%if TMPL_MODE != BS3_MODE_RM + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_rm) + BS3_SET_BITS TMPL_BITS + %ifndef TMPL_16BIT + jmp .from_text16 +TMPL_BEGIN_TEXT +.from_text16: + %endif +%endif + ; + ; Check that we didn't failed. + ; + jc .failed + cmp eax, INT15_E820_SIGNATURE + jc .failed + cmp ecx, 20 + jb .failed + + ; + ; Save the continuation value. + ; +%ifdef TMPL_16BIT + mov eax, ebx + lds bx, a_puContinuationValue + mov [bx], eax +%else + mov xAX, a_puContinuationValue + mov [xAX], ebx +%endif + + ; + ; Save the entry size. + ; +%ifdef TMPL_16BIT + lds bx, a_pcbEntry +%else + mov xBX, a_pcbEntry +%endif + mov [xBX], ecx + +%if TMPL_MODE != BS3_MODE_RM + ; + ; Copy the returned stuff into the caller's buffer. + ; + mov xSI, xSP + %ifdef TMPL_16BIT + push ss + pop es + lds di, a_pEntry + %else + mov xDI, a_pEntry + %endif + cld + rep movsb +%endif + + ; + ; Return success + ; + mov al, 1 + +.return: +%ifdef TMPL_16BIT + lea xSP, [xBP - sCB * 6 - xCB*2] + pop es + pop ds +%else + lea xSP, [xBP - sCB * 6] +%endif + pop sDI + pop sSI + pop sDX + pop sCX + pop sBX + sPOPF + leave + BS3_HYBRID_RET + + ; + ; Failed. + ; +.failed: + xor al, al + jmp .return +BS3_PROC_END_MODE Bs3BiosInt15hE820 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-CpuDetect.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-CpuDetect.asm new file mode 100644 index 00000000..670ebac5 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-CpuDetect.asm @@ -0,0 +1,347 @@ +; $Id: bs3-mode-CpuDetect.asm $ +;; @file +; BS3Kit - Bs3CpuDetect +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +BS3_EXTERN_DATA16 g_uBs3CpuDetected + + +;; +; Rough CPU detection, mainly for detecting really old CPUs. +; +; A Bs3CpuDetectEx can be added if this is insufficient. +; +; @returns BS3CPU_xxx in xAX. +; @cproto BS3_DECL(BS3CPU) Bs3CpuDetect(void); +; +; @uses xAX. +; +; @remarks ASSUMES we're in ring-0 when not in some kind of real mode. +; +; @note We put the real mode version of this code in the RMTEXT16 segment +; to save space elsewhere. We generate a far call stub that goes +; to the right segment. +; +%if TMPL_MODE == BS3_MODE_RM +BS3_BEGIN_RMTEXT16 +BS3_PROC_BEGIN_MODE Bs3CpuDetect, BS3_PBC_FAR +%else +TMPL_BEGIN_TEXT +BS3_PROC_BEGIN_MODE Bs3CpuDetect, BS3_PBC_HYBRID +%endif +CPU 8086 + push xBP + mov xBP, xSP + pushf ; xBP - xCB*1 + push xCX ; xBP - xCB*2 + push xDX ; xBP - xCB*3 + push xBX ; xBP - xCB*4 + sub xSP, 20h ; xBP - xCB*4 - 20h + +%ifndef TMPL_CMN_PAGING + %ifdef TMPL_RM + %if 1 ; this is simpler + ; + ; FLAGS bits 15:12 are always set on 8086, 8088, V20, V30, 80186, and + ; 80188. FLAGS bit 15 is always zero on 286+, whereas bit 14 is NT and + ; bits 13:12 are IOPL. + ; + test byte [xBP - xCB + 1], 80h ; Top byte of saved flags. + jz .286plus + %else + ; + ; When executing 'PUSH SP' the 8086, 8088, V20, V30, 80186, and 80188 + ; should be pushing the updated SP value instead of the initial one. + ; + push xSP + pop xAX + cmp xAX, xSP + je .286plus + %endif + + ; + ; Older than 286. + ; + ; Detect 8086/8088/V20/V30 vs. 80186/80188 by checking for pre 80186 + ; shift behavior. the 80186/188 and later will mask the CL value according + ; to the width of the destination register, whereas 8086/88 and V20/30 will + ; perform the exact number of shifts specified. + ; + mov cl, 20h ; Shift count; 80186/88 and later will mask this by 0x1f (or 0xf)? + mov dx, 7fh + shl dx, cl + cmp dx, 7fh ; If no change, this is a 80186/88. + mov xAX, BS3CPU_80186 + je .return + + ; + ; Detect 8086/88 vs V20/30 by exploiting undocumented POP CS encoding + ; that was redefined on V20/30 to SET1. + ; + xor ax, ax ; clear + push cs + db 0fh ; 8086/88: pop cs V20/30: set1 bl,cl + db 14h, 3ch ; 8086/88: add al, 3ch + ; 8086/88: al = 3ch V20/30: al = 0, cs on stack, bl modified. + cmp al, 3ch + jne .is_v20_or_v30 + mov xAX, BS3CPU_8086 + jmp .return + +.is_v20_or_v30: + pop xCX ; unclaimed CS + mov xAX, BS3CPU_V20 + jmp .return + + %endif ; TMPL_RM + +CPU 286 +.286plus: + ; + ; The 4th bit of the machine status word / CR0 indicates the precense + ; of a 80387 or later co-processor (a 80287+80386 => ET=0). 486 and + ; later should be hardcoding this to 1, according to the documentation + ; (need to test on 486SX). The initial idea here then would be to + ; assume 386+ if ET=1. + ; + ; The second idea was to check whether any reserved bits are set, + ; because the 286 here has bits 4 thru 15 all set. Unfortunately, it + ; turned out the 386SX and AMD 486DX-40 also sets bits 4 thru 15 when + ; using SMSW. So, nothing conclusive to distinguish 386 from 286, but + ; we've probably got a safe 486+ detection here. + ; + ;; @todo check if LOADALL can set any of the reserved bits on a 286 or 386. + smsw ax + test ax, ~(X86_CR0_PE | X86_CR0_MP | X86_CR0_EM | X86_CR0_TS | X86_CR0_ET | X86_CR0_NE) + jz .486plus + + ; + ; The 286 stores 0xff in the high byte of the SIDT and SGDT base + ; address (since it only did 24-bit addressing and the top 8-bit was + ; reserved for the 386). ASSUMES low IDT (which is the case for BS3Kit). + ; + sidt [xBP - xCB*4 - 20h] + cmp byte [xBP - xCB*4 - 20h + 2 + 3], 0ffh + jne .386plus + + %if 0 + ; + ; Detect 80286 by checking whether the IOPL and NT bits of EFLAGS can be + ; modified or not. There are different accounts of these bits. Dr.Dobb's + ; (http://www.drdobbs.com/embedded-systems/processor-detection-schemes/184409011) + ; say they are undefined on 286es and will always be zero. Whereas Intel + ; iAPX 286 Programmer's Reference Manual (both order #210498-001 and + ; #210498-003) documents both IOPL and NT, but with comment 4 on page + ; C-43 stating that they cannot be POPFed in real mode and will both + ; remain 0. This is different from the 386+, where the NT flag isn't + ; privileged according to page 3-37 in #230985-003. Later Intel docs + ; (#235383-052US, page 4-192) documents real mode as taking both NT and + ; IOPL from what POPF reads off the stack - which is the behavior + ; observed a 386SX here. + ; + test al, X86_CR0_PE ; This flag test doesn't work in protected mode, ... + jnz .386plus ; ... so ASSUME 386plus if in PE for now. + + pushf ; Save a copy of the original flags for restoring IF. + pushf + pop ax + xor ax, X86_EFL_IOPL | X86_EFL_NT ; Try modify IOPL and NT. + and ax, ~X86_EFL_IF ; Try clear IF. + push ax ; Load modified flags. + popf + pushf ; Get actual flags. + pop dx + popf ; Restore IF, IOPL and NT. + cmp ax, dx + je .386plus ; If any of the flags are set, we're on 386+. + + ; While we could in theory be in v8086 mode at this point and be fooled + ; by a flaky POPF implementation, we assume this isn't the case in our + ; execution environment. + %endif +.is_286: + mov ax, BS3CPU_80286 + jmp .return +%endif ; !TMPL_CMN_PAGING + +CPU 386 +.386plus: +.486plus: + ; + ; Check for CPUID and AC. The former flag indicates CPUID support, the + ; latter was introduced with the 486. + ; + mov ebx, esp ; Save esp. + and esp, 0fffch ; Clear high word and don't trigger ACs. + pushfd + mov eax, [esp] ; eax = original EFLAGS. + xor dword [esp], X86_EFL_ID | X86_EFL_AC ; Flip the ID and AC flags. + popfd ; Load modified flags. + pushfd ; Save actual flags. + xchg eax, [esp] ; Switch, so the stack has the original flags. + xor eax, [esp] ; Calc changed flags. + popf ; Restore EFLAGS. + mov esp, ebx ; Restore possibly unaligned ESP. + test eax, X86_EFL_ID + jnz .have_cpuid ; If ID changed, we've got CPUID. + test eax, X86_EFL_AC + mov xAX, BS3CPU_80486 + jnz .return ; If AC changed, we've got a 486 without CPUID (or similar). + mov xAX, BS3CPU_80386 + jmp .return + +CPU 586 +.have_cpuid: + ; + ; Do a very simple minded check here using the (standard) family field. + ; While here, we also check for PAE. + ; + mov eax, 1 + cpuid + + ; Calc the extended family and model values before we mess up EAX. + mov cl, ah + and cl, 0fh + cmp cl, 0fh + jnz .not_extended_family + mov ecx, eax + shr ecx, 20 + and cl, 7fh + add cl, 0fh +.not_extended_family: ; cl = family + mov ch, al + shr ch, 4 + cmp cl, 0fh + jae .extended_model + cmp cl, 06h ; actually only intel, but we'll let this slip for now. + jne .done_model +.extended_model: + shr eax, 12 + and al, 0f0h + or ch, al +.done_model: ; ch = model + + ; Start assembling return flags, checking for PSE + PAE. + mov eax, X86_CPUID_FEATURE_EDX_PSE | X86_CPUID_FEATURE_EDX_PAE + and eax, edx + mov ah, al + AssertCompile(X86_CPUID_FEATURE_EDX_PAE_BIT > BS3CPU_F_PAE_BIT - 8) ; 6 vs 10-8=2 + and al, X86_CPUID_FEATURE_EDX_PAE + shr al, X86_CPUID_FEATURE_EDX_PAE_BIT - (BS3CPU_F_PAE_BIT - 8) + AssertCompile(X86_CPUID_FEATURE_EDX_PSE_BIT == BS3CPU_F_PSE_BIT - 8) ; 3 vs 11-8=3 + and ah, X86_CPUID_FEATURE_EDX_PSE + or ah, al + or ah, (BS3CPU_F_CPUID >> 8) + + ; Add the CPU type based on the family and model values. + cmp cl, 6 + jne .not_family_06h + mov al, BS3CPU_PPro + cmp ch, 1 + jbe .return + mov al, BS3CPU_PProOrNewer + jmp .NewerThanPPro + +.not_family_06h: + mov al, BS3CPU_PProOrNewer + ja .NewerThanPPro + cmp cl, 5 + mov al, BS3CPU_Pentium + je .return + cmp cl, 4 + mov al, BS3CPU_80486 + je .return + cmp cl, 3 + mov al, BS3CPU_80386 + je .return + +.NewerThanPPro: + + ; Check for extended leaves and long mode. + push xAX ; save PAE+PProOrNewer + mov eax, 0x80000000 + cpuid + sub eax, 0x80000001 ; Minimum leaf 0x80000001 + cmp eax, 0x00010000 ; At most 0x10000 leaves. + ja .no_ext_leaves + + mov eax, 0x80000001 + cpuid + pop xAX ; restore PAE+PProOrNewer + test edx, X86_CPUID_EXT_FEATURE_EDX_LONG_MODE + jz .no_long_mode + or ah, ((BS3CPU_F_CPUID_EXT_LEAVES | BS3CPU_F_LONG_MODE) >> 8) + jmp .no_check_for_nx +.no_long_mode: + or ah, (BS3CPU_F_CPUID_EXT_LEAVES >> 8) +.no_check_for_nx: + test edx, X86_CPUID_EXT_FEATURE_EDX_NX + jz .return + or ax, BS3CPU_F_NX + jmp .return + +.no_ext_leaves: + pop xAX ; restore PAE+PProOrNewer + +CPU 8086 +.return: + ; + ; Save the return value. + ; + mov [BS3_DATA16_WRT(g_uBs3CpuDetected)], ax + + ; + ; Epilogue. + ; + add xSP, 20h + pop xBX + pop xDX + pop xCX + popf + pop xBP + BS3_HYBRID_RET + +BS3_PROC_END_MODE Bs3CpuDetect + + +%if TMPL_MODE == BS3_MODE_RM +BS3_BEGIN_TEXT16_NEARSTUBS +BS3_PROC_BEGIN_MODE Bs3CpuDetect, BS3_PBC_NEAR + call far TMPL_FAR_NM(Bs3CpuDetect) + ret +BS3_PROC_END_MODE Bs3CpuDetect +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-EnteredMode.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-EnteredMode.asm new file mode 100644 index 00000000..79001705 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-EnteredMode.asm @@ -0,0 +1,279 @@ +; $Id: bs3-mode-EnteredMode.asm $ +;; @file +; BS3Kit - Bs3EnteredMode +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_uBs3CpuDetected +%endif +BS3_EXTERN_DATA16 g_bBs3CurrentMode +TMPL_BEGIN_TEXT + +;; +; @cproto BS3_DECL(void) Bs3EnteredMode(void); +; +; @uses Nothing. +; +; @remarks ASSUMES we're in ring-0 when not in some kind of real mode. +; +BS3_PROC_BEGIN_MODE Bs3EnteredMode, BS3_PBC_NEAR ; won't need this outside the switchers, so always near. + push xBP + mov xBP, xSP + push xAX + push xCX + push xDX +TONLY16 push xBX +%if BS3_MODE_IS_64BIT_CODE(TMPL_MODE) + push r8 + push r9 +%endif + + ; + ; Load stack selector (not always necessary) and sometimes CS too. + ; +%if BS3_MODE_IS_RM_SYS(TMPL_MODE) + xor ax, ax +%elif BS3_MODE_IS_V86(TMPL_MODE) + extern v86_versions_of_Bs3EnteredMode_should_not_be_dragged_into_the_link + call v86_versions_of_Bs3EnteredMode_should_not_be_dragged_into_the_link +%elif BS3_MODE_IS_16BIT_CODE(TMPL_MODE) + jmp BS3_SEL_R0_CS16:.reloaded_cs +.reloaded_cs: + mov ax, BS3_SEL_R0_SS16 +%elif BS3_MODE_IS_32BIT_CODE(TMPL_MODE) + mov ax, BS3_SEL_R0_SS32 +%elif BS3_MODE_IS_64BIT_CODE(TMPL_MODE) + mov ax, BS3_SEL_R0_DS64 +%else + %error "TMPL_MODE" +%endif + mov ss, ax + + ; + ; Load selector appropriate for accessing BS3SYSTEM16 data. + ; +%if BS3_MODE_IS_16BIT_CODE(TMPL_MODE) + mov ax, BS3_SEL_SYSTEM16 +%else + mov ax, RT_CONCAT(BS3_SEL_R0_DS,TMPL_BITS) +%endif + mov ds, ax + + ; + ; Load the appropritate IDT or IVT. + ; Always 64-bit in long mode, otherwise according to TMPL_BITS. + ; +%if BS3_MODE_IS_RM_SYS(TMPL_MODE) + BS3_EXTERN_SYSTEM16 Bs3Lidt_Ivt + TMPL_BEGIN_TEXT + lidt [Bs3Lidt_Ivt] + +%elif BS3_MODE_IS_16BIT_SYS(TMPL_MODE) + BS3_EXTERN_SYSTEM16 Bs3Lidt_Idt16 + TMPL_BEGIN_TEXT + lidt [Bs3Lidt_Idt16 TMPL_WRT_SYSTEM16_OR_FLAT] + +%elif BS3_MODE_IS_32BIT_SYS(TMPL_MODE) + BS3_EXTERN_SYSTEM16 Bs3Lidt_Idt32 + TMPL_BEGIN_TEXT + lidt [Bs3Lidt_Idt32 TMPL_WRT_SYSTEM16_OR_FLAT] + +%elif BS3_MODE_IS_64BIT_SYS(TMPL_MODE) + BS3_EXTERN_SYSTEM16 Bs3Lidt_Idt64 + TMPL_BEGIN_TEXT + lidt [Bs3Lidt_Idt64 TMPL_WRT_SYSTEM16_OR_FLAT] +%else + %error "TMPL_MODE" +%endif + +%if !BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + ; + ; Load the appropriate task selector. + ; Always 64-bit in long mode, otherwise according to TMPL_BITS. + ; + %if BS3_MODE_IS_64BIT_SYS(TMPL_MODE) + BS3_EXTERN_SYSTEM16 Bs3Gdte_Tss64 + TMPL_BEGIN_TEXT + and byte [5 + Bs3Gdte_Tss64 TMPL_WRT_SYSTEM16_OR_FLAT], ~X86_SEL_TYPE_SYS_TSS_BUSY_MASK + mov ax, BS3_SEL_TSS64 + + %elif BS3_MODE_IS_16BIT_SYS(TMPL_MODE) + BS3_EXTERN_SYSTEM16 Bs3Gdte_Tss16 + BS3_EXTERN_SYSTEM16 Bs3Gdte_Tss16DoubleFault + TMPL_BEGIN_TEXT + and byte [5 + Bs3Gdte_Tss16 TMPL_WRT_SYSTEM16_OR_FLAT], ~X86_SEL_TYPE_SYS_TSS_BUSY_MASK + and byte [5 + Bs3Gdte_Tss16DoubleFault TMPL_WRT_SYSTEM16_OR_FLAT], ~X86_SEL_TYPE_SYS_TSS_BUSY_MASK + mov ax, BS3_SEL_TSS16 + + %elif BS3_MODE_IS_32BIT_SYS(TMPL_MODE) + BS3_EXTERN_SYSTEM16 Bs3Gdte_Tss32 + BS3_EXTERN_SYSTEM16 Bs3Gdte_Tss32DoubleFault + BS3_EXTERN_SYSTEM16 Bs3Tss32 + BS3_EXTERN_SYSTEM16 Bs3Tss32DoubleFault + TMPL_BEGIN_TEXT + and byte [5 + Bs3Gdte_Tss32 TMPL_WRT_SYSTEM16_OR_FLAT], ~X86_SEL_TYPE_SYS_TSS_BUSY_MASK + and byte [5 + Bs3Gdte_Tss32DoubleFault TMPL_WRT_SYSTEM16_OR_FLAT], ~X86_SEL_TYPE_SYS_TSS_BUSY_MASK + mov eax, cr3 + mov [X86TSS32.cr3 + Bs3Tss32 TMPL_WRT_SYSTEM16_OR_FLAT], eax + mov [X86TSS32.cr3 + Bs3Tss32DoubleFault TMPL_WRT_SYSTEM16_OR_FLAT], eax + mov ax, BS3_SEL_TSS32 + %else + %error "TMPL_BITS" + %endif + ltr ax +%endif ; !TMPL_CMN_R86 + +%if !BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + ; + ; Load the LDT. + ; + mov ax, BS3_SEL_LDT + lldt ax +%endif + + ; + ; Load ds and es; clear fs and gs. + ; +%if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + mov ax, BS3_SEL_DATA16 +%else + mov ax, RT_CONCAT(BS3_SEL_R0_DS,TMPL_BITS) +%endif + mov ds, ax + mov es, ax + +%if TMPL_BITS == 16 + ; For restoring after Bs3Trap* calls below. + push ax + push ax + + cmp byte [BS3_DATA16_WRT(g_uBs3CpuDetected)], BS3CPU_80286 + jbe .skip_fs_gs +%endif + xor ax, ax + mov fs, ax + mov gs, ax +.skip_fs_gs: + + ; + ; Set global indicating CPU mode. + ; + mov byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], TMPL_MODE + + ; + ; Install system call handler. + ; Always 64-bit in long mode, otherwise according to TMPL_BITS. + ; +%if BS3_MODE_IS_RM_SYS(TMPL_MODE) + extern _Bs3TrapSystemCallHandler_rm + mov word [ss: BS3_TRAP_SYSCALL*4], _Bs3TrapSystemCallHandler_rm wrt CGROUP16 + mov word [ss: BS3_TRAP_SYSCALL*4 + 2], CGROUP16 + +%elif BS3_MODE_IS_16BIT_SYS(TMPL_MODE) + BS3_EXTERN_CMN Bs3Trap16SetGate + extern TMPL_NM(Bs3TrapSystemCallHandler) + BS3_BEGIN_TEXT16 + TMPL_BEGIN_TEXT + push 0 ; cParams + push TMPL_NM(Bs3TrapSystemCallHandler) wrt CGROUP16 + push BS3_SEL_R0_CS16 + push 3 ; DPL + push X86_SEL_TYPE_SYS_286_INT_GATE + push BS3_TRAP_SYSCALL + BS3_CALL Bs3Trap16SetGate,6 + add xSP, xCB * 6 + +%elif BS3_MODE_IS_32BIT_SYS(TMPL_MODE) + BS3_EXTERN_CMN Bs3Trap32SetGate + extern TMPL_NM(Bs3TrapSystemCallHandler) + TMPL_BEGIN_TEXT + push 0 ; cParams + push dword TMPL_NM(Bs3TrapSystemCallHandler) wrt FLAT + push BS3_SEL_R0_CS32 + push 3 ; DPL + push X86_SEL_TYPE_SYS_386_INT_GATE + push BS3_TRAP_SYSCALL + BS3_CALL Bs3Trap32SetGate,6 + add xSP, xCB * 6 + +%elif BS3_MODE_IS_64BIT_SYS(TMPL_MODE) + BS3_EXTERN_CMN Bs3Trap64SetGate + extern _Bs3TrapSystemCallHandler_lm64 + TMPL_BEGIN_TEXT + push 0 ; bIst + %if BS3_MODE_IS_64BIT_CODE(TMPL_MODE) + push _Bs3TrapSystemCallHandler_lm64 wrt FLAT + %else + push dword 0 ; upper offset + push dword _Bs3TrapSystemCallHandler_lm64 wrt FLAT + %endif + push BS3_SEL_R0_CS64 + push 3 ; DPL + push AMD64_SEL_TYPE_SYS_INT_GATE + push BS3_TRAP_SYSCALL + BS3_CALL Bs3Trap64SetGate,6 + add xSP, xCB * 5 + 8 +%else + %error "TMPL_BITS" +%endif + +%if TMPL_BITS == 16 + ; Restoring ds and es after the above calls. + pop es + pop ds +%endif + + ; + ; Epilogue. + ; +%if TMPL_BITS == 64 + pop r9 + pop r8 +%endif +TONLY16 pop xBX + pop xDX + pop xCX + pop xAX +%ifdef BS3_STRICT + cmp xBP, xSP + je .return_stack_ok + int3 +.return_stack_ok: +%endif + pop xBP + ret +BS3_PROC_END_MODE Bs3EnteredMode + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-Name.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-Name.asm new file mode 100644 index 00000000..c7bfbe8a --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-Name.asm @@ -0,0 +1,44 @@ +; $Id: bs3-mode-Name.asm $ +;; @file +; BS3Kit - g_szBs3ModeName_xxx +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_BEGIN_DATA16 +BS3_GLOBAL_NAME_EX RT_CONCAT3(g_szBs3ModeName, _, TMPL_MODE_LNAME), , %strlen(TMPL_MODE_STR) +BS3_GLOBAL_NAME_EX RT_CONCAT3(_g_szBs3ModeName, _, TMPL_MODE_LNAME), , %strlen(TMPL_MODE_STR) + db TMPL_MODE_STR, 0 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-NameShortLower.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-NameShortLower.asm new file mode 100644 index 00000000..c4573d78 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-NameShortLower.asm @@ -0,0 +1,46 @@ +; $Id: bs3-mode-NameShortLower.asm $ +;; @file +; BS3Kit - g_szBs3ModeName_xxx +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +BS3_BEGIN_DATA16 +%undef MY_MODE_NAME_STR +%defstr MY_MODE_NAME_STR TMPL_MODE_LNAME +BS3_GLOBAL_NAME_EX RT_CONCAT3(g_szBs3ModeNameShortLower, _, TMPL_MODE_LNAME), , %strlen(MY_MODE_NAME_STR) +BS3_GLOBAL_NAME_EX RT_CONCAT3(_g_szBs3ModeNameShortLower, _, TMPL_MODE_LNAME), , %strlen(MY_MODE_NAME_STR) + db MY_MODE_NAME_STR, 0 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-PagingGetRootForLM64.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-PagingGetRootForLM64.asm new file mode 100644 index 00000000..754a6537 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-PagingGetRootForLM64.asm @@ -0,0 +1,141 @@ +; $Id: bs3-mode-PagingGetRootForLM64.asm $ +;; @file +; BS3Kit - Bs3PagingGetRootForLM64 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +%ifdef TMPL_RM +extern TMPL_NM(Bs3SwitchToPE16) +extern NAME(Bs3SwitchToRM_pe16) +%elifdef TMPL_CMN_V86 +extern TMPL_NM(Bs3SwitchToRing0) +extern TMPL_NM(Bs3SwitchTo16BitV86) +%endif + +BS3_EXTERN_CMN Bs3PagingInitRootForLM + +BS3_EXTERN_DATA16 g_PhysPagingRootLM +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_DECL(uint32_t) Bs3PagingGetRootForLM64(void) +; +; @returns eax +; +; @uses ax +; +; @remarks returns value in EAX, not dx:ax! +; +BS3_PROC_BEGIN_MODE Bs3PagingGetRootForLM64, BS3_PBC_NEAR ; Internal function, no far variant necessary. + mov eax, [BS3_DATA16_WRT(g_PhysPagingRootLM)] + cmp eax, 0ffffffffh + je .init_root +%ifdef BS3_STRICT +.return: + cmp eax, 1000h + jnb .cr3_ok_low + hlt +.cr3_ok_low: + cmp eax, 16*_1M + jb .cr3_ok_high + hlt +.cr3_ok_high: +%endif + ret + +.init_root: + push xBP + mov xBP, xSP +BONLY16 push es + push sDX + push sCX + push sBX +%if TMPL_BITS == 64 + push r8 + push r9 + push r10 + push r11 +%endif + +%ifdef TMPL_RM + ; + ; We don't want to be restricted to real mode addressing, so + ; temporarily switch to 16-bit protected mode. + ; + call TMPL_NM(Bs3SwitchToPE16) + call Bs3PagingInitRootForLM + call NAME(Bs3SwitchToRM_pe16) +%elifdef TMPL_CMN_V86 + ; + ; V8086 mode uses real mode addressing too. Unlikly that we'll + ; ever end up here though. + ; + call TMPL_NM(Bs3SwitchToRing0) + call Bs3PagingInitRootForLM + call TMPL_NM(Bs3SwitchTo16BitV86) +%else + ; + ; Not a problematic addressing mode. + ; +BONLY64 sub rsp, 20h + BS3_CALL Bs3PagingInitRootForLM, 0 +BONLY64 add rsp, 20h +%endif + + ; + ; Load the value and return. + ; + mov eax, [BS3_DATA16_WRT(g_PhysPagingRootLM)] + +%if TMPL_BITS == 64 + pop r11 + pop r10 + pop r9 + pop r8 +%endif + pop sBX + pop sCX + pop sDX +BONLY16 pop es + leave +%ifdef BS3_STRICT + jmp .return +%else + ret +%endif +BS3_PROC_END_MODE Bs3PagingGetRootForLM64 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-PagingGetRootForPAE16.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-PagingGetRootForPAE16.asm new file mode 100644 index 00000000..f654e5e2 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-PagingGetRootForPAE16.asm @@ -0,0 +1,55 @@ +; $Id: bs3-mode-PagingGetRootForPAE16.asm $ +;; @file +; BS3Kit - Bs3PagingGetRootForPAE16 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +extern TMPL_NM(Bs3PagingGetRootForPAE32) + + +;; +; @cproto BS3_DECL(uint32_t) Bs3PagingGetRootForPAE16(void) +; +; @returns eax +; +; @uses ax +; +; @remarks returns value in EAX, not dx:ax! +; +BS3_PROC_BEGIN_MODE Bs3PagingGetRootForPAE16, BS3_PBC_NEAR ; Internal function, no far variant necessary. + jmp TMPL_NM(Bs3PagingGetRootForPAE32) +BS3_PROC_END_MODE Bs3PagingGetRootForPAE16 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-PagingGetRootForPAE32.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-PagingGetRootForPAE32.asm new file mode 100644 index 00000000..9e9460bf --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-PagingGetRootForPAE32.asm @@ -0,0 +1,127 @@ +; $Id: bs3-mode-PagingGetRootForPAE32.asm $ +;; @file +; BS3Kit - Bs3PagingGetRootForPAE32 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +%ifdef TMPL_RM +extern TMPL_NM(Bs3SwitchToPE16) +extern NAME(Bs3SwitchToRM_pe16) +%elifdef TMPL_CMN_V86 +extern TMPL_NM(Bs3SwitchToRing0) +extern TMPL_NM(Bs3SwitchTo16BitV86) +%endif + +BS3_EXTERN_CMN Bs3PagingInitRootForPAE + +BS3_EXTERN_DATA16 g_PhysPagingRootPAE +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_DECL(uint32_t) Bs3PagingGetRootForPAE32(void) +; +; @returns eax +; +; @uses ax +; +; @remarks returns value in EAX, not dx:ax! +; +BS3_PROC_BEGIN_MODE Bs3PagingGetRootForPAE32, BS3_PBC_NEAR ; Internal function, no far variant necessary. + mov eax, [BS3_DATA16_WRT(g_PhysPagingRootPAE)] + cmp eax, 0ffffffffh + je .init_root + ret + +.init_root: + push xBP + mov xBP, xSP +BONLY16 push es + push sDX + push sCX + push sBX +%if TMPL_BITS == 64 + push r8 + push r9 + push r10 + push r11 +%endif + +%ifdef TMPL_RM + ; + ; We don't want to be restricted to real mode addressing, so + ; temporarily switch to 16-bit protected mode. + ; + call TMPL_NM(Bs3SwitchToPE16) + call Bs3PagingInitRootForPAE + call NAME(Bs3SwitchToRM_pe16) + +%elifdef TMPL_CMN_V86 + ; + ; V8086 mode uses real mode addressing too. Unlikly that we'll + ; ever end up here though. + ; + call TMPL_NM(Bs3SwitchToRing0) + call Bs3PagingInitRootForPAE + call TMPL_NM(Bs3SwitchTo16BitV86) +%else + ; + ; Not a problematic addressing mode. + ; +BONLY64 sub rsp, 20h + BS3_CALL Bs3PagingInitRootForPAE, 0 +BONLY64 add rsp, 20h +%endif + + ; + ; Load the value and return. + ; + mov eax, [BS3_DATA16_WRT(g_PhysPagingRootPAE)] + +%if TMPL_BITS == 64 + pop r11 + pop r10 + pop r9 + pop r8 +%endif + pop sBX + pop sCX + pop sDX +BONLY16 pop es + leave + ret +BS3_PROC_END_MODE Bs3PagingGetRootForPAE32 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-PagingGetRootForPP16.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-PagingGetRootForPP16.asm new file mode 100644 index 00000000..269d1341 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-PagingGetRootForPP16.asm @@ -0,0 +1,55 @@ +; $Id: bs3-mode-PagingGetRootForPP16.asm $ +;; @file +; BS3Kit - Bs3PagingGetRootForPP16 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +extern TMPL_NM(Bs3PagingGetRootForPP32) + + +;; +; @cproto BS3_DECL(uint32_t) Bs3PagingGetRootForPP16(void) +; +; @returns eax +; +; @uses ax +; +; @remarks returns value in EAX, not dx:ax! +; +BS3_PROC_BEGIN_MODE Bs3PagingGetRootForPP16, BS3_PBC_NEAR ; Internal function, no far variant necessary. + jmp TMPL_NM(Bs3PagingGetRootForPP32) +BS3_PROC_END_MODE Bs3PagingGetRootForPP16 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-PagingGetRootForPP32.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-PagingGetRootForPP32.asm new file mode 100644 index 00000000..d48a24dc --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-PagingGetRootForPP32.asm @@ -0,0 +1,142 @@ +; $Id: bs3-mode-PagingGetRootForPP32.asm $ +;; @file +; BS3Kit - Bs3PagingGetRootForPP32 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +%ifdef TMPL_RM +extern TMPL_NM(Bs3SwitchToPE16) +extern NAME(Bs3SwitchToRM_pe16) +%elifdef TMPL_CMN_V86 +extern TMPL_NM(Bs3SwitchToRing0) +extern TMPL_NM(Bs3SwitchTo16BitV86) +%endif + +BS3_EXTERN_CMN Bs3PagingInitRootForPP + +BS3_EXTERN_DATA16 g_PhysPagingRootPP +TMPL_BEGIN_TEXT + + +;; +; @cproto BS3_DECL(uint32_t) Bs3PagingGetRootForPP32(void) +; +; @returns eax +; +; @uses ax +; +; @remarks returns value in EAX, not dx:ax! +; +BS3_PROC_BEGIN_MODE Bs3PagingGetRootForPP32, BS3_PBC_NEAR ; Internal function, no far variant necessary. + mov eax, [BS3_DATA16_WRT(g_PhysPagingRootPP)] + cmp eax, 0ffffffffh + je .init_root +%ifdef BS3_STRICT +.return: + cmp eax, 1000h + jnb .cr3_ok_low + hlt +.cr3_ok_low: + cmp eax, 16*_1M + jb .cr3_ok_high + hlt +.cr3_ok_high: +%endif + ret + +.init_root: + push xBP + mov xBP, xSP +BONLY16 push es + push sDX + push sCX + push sBX +%if TMPL_BITS == 64 + push r8 + push r9 + push r10 + push r11 +%endif + +%ifdef TMPL_RM + ; + ; We don't want to be restricted to real mode addressing, so + ; temporarily switch to 16-bit protected mode. + ; + call TMPL_NM(Bs3SwitchToPE16) + call Bs3PagingInitRootForPP + call NAME(Bs3SwitchToRM_pe16) + +%elifdef TMPL_CMN_V86 + ; + ; V8086 mode uses real mode addressing too. Unlikly that we'll + ; ever end up here though. + ; + call TMPL_NM(Bs3SwitchToRing0) + call Bs3PagingInitRootForPP + call TMPL_NM(Bs3SwitchTo16BitV86) +%else + ; + ; Not a problematic addressing mode. + ; +BONLY64 sub rsp, 20h + BS3_CALL Bs3PagingInitRootForPP, 0 +BONLY64 add rsp, 20h +%endif + + ; + ; Load the value and return. + ; + mov eax, [BS3_DATA16_WRT(g_PhysPagingRootPP)] + +%if TMPL_BITS == 64 + pop r11 + pop r10 + pop r9 + pop r8 +%endif + pop sBX + pop sCX + pop sDX +BONLY16 pop es + leave +%ifdef BS3_STRICT + jmp .return +%else + ret +%endif +BS3_PROC_END_MODE Bs3PagingGetRootForPP32 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchTo32BitAndCallC.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchTo32BitAndCallC.asm new file mode 100644 index 00000000..37a18515 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchTo32BitAndCallC.asm @@ -0,0 +1,164 @@ +; $Id: bs3-mode-SwitchTo32BitAndCallC.asm $ +;; @file +; BS3Kit - bs3SwitchTo32BitAndCallC +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_EXTERN_DATA16 g_bBs3CurrentMode +TMPL_BEGIN_TEXT + +%ifdef BS3_STRICT +BS3_EXTERN_CMN Bs3Panic +%endif + +%if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) +BS3_EXTERN_CMN Bs3SelRealModeCodeToFlat +%endif + +%if TMPL_MODE == BS3_MODE_RM +extern NAME(Bs3SwitchToPE32_rm) +extern NAME(Bs3SwitchToRM_pe32) +%elif !BS3_MODE_IS_32BIT_CODE(TMPL_MODE) +BS3_EXTERN_CMN Bs3SwitchTo32Bit + %if BS3_MODE_IS_16BIT_CODE_NO_V86(TMPL_MODE) +extern _Bs3SwitchTo16Bit_c32 + %elif BS3_MODE_IS_V86(TMPL_MODE) +extern _Bs3SwitchTo16BitV86_c32 + %elif !BS3_MODE_IS_32BIT_CODE(TMPL_MODE) +extern _Bs3SwitchTo64_c32 + %endif +%endif + + + +;; +; @cproto BS3_MODE_PROTO_STUB(int, Bs3SwitchTo32BitAndCallC,(PFNBS3FARADDRCONV fpfnCall, unsigned cbParams, ...)); +; +BS3_PROC_BEGIN_MODE Bs3SwitchTo32BitAndCallC, BS3_PBC_HYBRID + BS3_CALL_CONV_PROLOG 4 +TONLY16 inc xBP + push xBP + mov xBP, xSP + push xSI + + ; + ; Push the arguments first. + ; +TONLY16 mov si, [xBP + xCB + cbCurRetAddr + sCB] +TNOT16 mov esi, [xBP + xCB + cbCurRetAddr + sCB] +%ifdef BS3_STRICT + test xSI, 3 + jz .cbParams_ok + call Bs3Panic +.cbParams_ok: + cmp byte [BS3_DATA16_WRT(g_bBs3CurrentMode)], TMPL_MODE + je .mode_ok + call Bs3Panic +.mode_ok: +%endif + add xSI, sCB - 1 ; round it up to nearest push size / dword. + and xSI, ~(sCB - 1) + jz .done_pushing ; skip if zero +.push_more: + push xPRE [xBP + xCB + cbCurRetAddr + sCB + xCB + xSI - xCB] + sub xSI, xCB + jnz .push_more + mov xSI, xAX ; restore xSI +.done_pushing: + + ; + ; Load fpfnCall into eax. + ; +%if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + push sPRE [xBP + xCB + cbCurRetAddr] + BS3_CALL Bs3SelRealModeCodeToFlat, 1 + add xSP, sCB + rol eax, 16 + mov ax, dx + rol eax, 16 +%else + mov eax, [xBP + xCB + cbCurRetAddr] +%endif + + ; + ; Switch to 32-bit mode, if this is real mode pick PE32. + ; +%if TMPL_MODE == BS3_MODE_RM + call NAME(Bs3SwitchToPE32_rm) + BS3_SET_BITS 32 +%elif !BS3_MODE_IS_32BIT_CODE(TMPL_MODE) + call Bs3SwitchTo32Bit + BS3_SET_BITS 32 +%endif + + ; + ; Make the call. + ; + call eax + + ; + ; Return, preserving xAX. + ; +%if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + mov edx, eax + shr edx, 16 +%endif +%if TMPL_MODE == BS3_MODE_RM + call NAME(Bs3SwitchToRM_pe32) +%elif BS3_MODE_IS_16BIT_CODE_NO_V86(TMPL_MODE) + call _Bs3SwitchTo16Bit_c32 +%elif BS3_MODE_IS_V86(TMPL_MODE) + call _Bs3SwitchTo16BitV86_c32 +%elif !BS3_MODE_IS_32BIT_CODE(TMPL_MODE) + call _Bs3SwitchTo64_c32 +%endif + BS3_SET_BITS TMPL_BITS + + ; Epilog. + lea xSP, [xBP - xCB] + pop xSI + pop xBP +TONLY16 dec xBP + BS3_CALL_CONV_EPILOG 4 + BS3_HYBRID_RET +BS3_PROC_END_MODE Bs3SwitchTo32BitAndCallC + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToLM16.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToLM16.asm new file mode 100644 index 00000000..00db81c9 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToLM16.asm @@ -0,0 +1,136 @@ +; $Id: bs3-mode-SwitchToLM16.asm $ +;; @file +; BS3Kit - Bs3SwitchToLM16 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Switch to 16-bit long mode from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToLM16(void); +; +; @uses Nothing (except possibly high 32-bit and/or upper 64-bit register parts). +; +; @remarks Obviously returns to 16-bit mode, even if the caller was in 32-bit +; or 64-bit mode. It doesn't not preserve the callers ring, but +; instead changes to ring-0. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +%if TMPL_BITS == 16 +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToLM16_Safe), function , 0 +%endif +BS3_PROC_BEGIN_MODE Bs3SwitchToLM16, BS3_PBC_NEAR +%ifdef TMPL_LM16 + extern BS3_CMN_NM(Bs3SwitchToRing0) + call BS3_CMN_NM(Bs3SwitchToRing0) + push ax + mov ax, BS3_SEL_R0_DS16 + mov ds, ax + mov es, ax + pop ax + ret + +%elifdef TMPL_CMN_LM + ; + ; Already in long mode, just switch to 16-bit. + ; + extern BS3_CMN_NM(Bs3SwitchTo16Bit) + jmp BS3_CMN_NM(Bs3SwitchTo16Bit) + +%else + ; + ; Switch to LM32 and then switch to 64-bits (IDT & TSS are the same for + ; LM16, LM32 and LM64, unlike the rest). + ; + ; (The long mode switching code is going via 32-bit protected mode, so + ; Bs3SwitchToLM32 contains the actual code for switching to avoid + ; unnecessary 32-bit -> 64-bit -> 32-bit trips.) + ; + extern TMPL_NM(Bs3SwitchToLM32) + call TMPL_NM(Bs3SwitchToLM32) + BS3_SET_BITS 32 + + extern _Bs3SwitchTo16Bit_c32 + %if TMPL_BITS == 16 + sub esp, 2 + shr dword [esp], 16 + %elif TMPL_BITS == 64 + pop dword [esp + 4] + %endif + jmp _Bs3SwitchTo16Bit_c32 +%endif +BS3_PROC_END_MODE Bs3SwitchToLM16 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToLM16, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToLM16) + + %if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + ; Jmp to common code for the tedious conversion. + BS3_EXTERN_CMN Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn + jmp Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn + %else + pop bp + dec bp + retf + %endif +BS3_PROC_END_MODE Bs3SwitchToLM16 + +%else +;; +; Safe far return to non-BS3TEXT16 code. +BS3_EXTERN_CMN Bs3SwitchHlpConvFlatRetToRetfProtMode +BS3_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_PROC_BEGIN_MODE Bs3SwitchToLM16_Safe, BS3_PBC_NEAR + call Bs3SwitchHlpConvFlatRetToRetfProtMode ; Special internal function. Uses nothing, but modifies the stack. + call TMPL_NM(Bs3SwitchToLM16) + BS3_SET_BITS 16 + retf +BS3_PROC_END_MODE Bs3SwitchToLM16_Safe + +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToLM32.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToLM32.asm new file mode 100644 index 00000000..c33c1cb3 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToLM32.asm @@ -0,0 +1,203 @@ +; $Id: bs3-mode-SwitchToLM32.asm $ +;; @file +; BS3Kit - Bs3SwitchToLM32 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Switch to 32-bit long mode from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToLM32(void); +; +; @uses Nothing (except possibly high 32-bit and/or upper 64-bit register parts). +; +; @remarks There are no IDT or TSS differences between LM16, LM32 and LM64 (unlike +; PE16 & PE32, PP16 & PP32, and PAE16 & PAE32). +; +; @remarks Obviously returns to 32-bit mode, even if the caller was in 16-bit +; or 64-bit mode. It doesn't not preserve the callers ring, but +; instead changes to ring-0. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToLM32_Safe), function, 0 +BS3_PROC_BEGIN_MODE Bs3SwitchToLM32, BS3_PBC_NEAR +%ifdef TMPL_LM32 + ret + +%elifdef TMPL_CMN_LM + ; + ; Already in long mode, just switch to 32-bit. + ; + extern BS3_CMN_NM(Bs3SwitchTo32Bit) + jmp BS3_CMN_NM(Bs3SwitchTo32Bit) + +%elif BS3_MODE_IS_V86(TMPL_MODE) + ; + ; V8086 - Switch to 16-bit ring-0 and call worker for that mode. + ; + extern BS3_CMN_NM(Bs3SwitchToRing0) + call BS3_CMN_NM(Bs3SwitchToRing0) + extern %[BS3_MODE_R0_NM_ %+ TMPL_MODE](Bs3SwitchToLM32) + jmp %[BS3_MODE_R0_NM_ %+ TMPL_MODE](Bs3SwitchToLM32) + +%else + %if TMPL_BITS == 16 + push word 0 ; save space for extending the return value. + %endif + + ; + ; Switch to 32-bit protected mode (for identify mapped pages). + ; + extern TMPL_NM(Bs3SwitchToPE32) + call TMPL_NM(Bs3SwitchToPE32) + BS3_SET_BITS 32 + %if TMPL_BITS == 16 + jmp .thirty_two_bit_segment +BS3_BEGIN_TEXT32 +BS3_GLOBAL_LOCAL_LABEL .thirty_two_bit_segment + %endif + + push eax + push ecx + push edx + pushfd + + ; + ; Make sure both PAE and PSE are enabled (requires pentium pro). + ; + mov eax, cr4 + mov ecx, eax + or eax, X86_CR4_PAE | X86_CR4_PSE + cmp eax, ecx + je .cr4_is_fine + mov cr4, eax +.cr4_is_fine: + + ; + ; Get the page directory (returned in eax). + ; Will lazy init page tables. + ; + extern NAME(Bs3PagingGetRootForLM64_pe32) + call NAME(Bs3PagingGetRootForLM64_pe32) + + cli + mov cr3, eax + + ; + ; Enable long mode in EFER. + ; + mov ecx, MSR_K6_EFER + rdmsr + or eax, MSR_K6_EFER_LME + wrmsr + + ; + ; Enable paging and thereby activating LM64. + ; +BS3_EXTERN_SYSTEM16 Bs3Lgdt_Gdt +BS3_BEGIN_TEXT32 + mov eax, cr0 + or eax, X86_CR0_PG + mov cr0, eax + jmp .in_lm32 +.in_lm32: + + ; + ; Call rountine for doing mode specific setups. + ; + extern NAME(Bs3EnteredMode_lm32) + call NAME(Bs3EnteredMode_lm32) + + ; + ; Load full 64-bit GDT base address from 64-bit segment. + ; + jmp dword BS3_SEL_R0_CS64:.load_full_gdt_base wrt FLAT +.load_full_gdt_base: + BS3_SET_BITS 64 + lgdt [Bs3Lgdt_Gdt wrt FLAT] + push BS3_SEL_R0_CS32 + push .back_to_32bit wrt FLAT + o64 retf +.back_to_32bit: + BS3_SET_BITS 32 + + ; + ; Restore ecx, eax and flags (IF). + ; + %if TMPL_BITS == 16 + movzx eax, word [esp + 16 + 2] ; Load return address. + add eax, BS3_ADDR_BS3TEXT16 ; Convert it to a flat address. + mov [esp + 16], eax ; Store it in the place right for 32-bit returns. + %endif + popfd + pop edx + pop ecx + pop eax + ret + + %if TMPL_BITS != 32 +TMPL_BEGIN_TEXT + %endif +%endif +BS3_PROC_END_MODE Bs3SwitchToLM32 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToLM32, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToLM32) + BS3_SET_BITS 32 + + ; Jmp to common code for the tedious conversion. + %if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + extern _Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn_c32 + jmp _Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn_c32 + %else + extern _Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn_c32 + jmp _Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn_c32 + %endif + BS3_SET_BITS 16 +BS3_PROC_END_MODE Bs3SwitchToLM32 +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToLM64.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToLM64.asm new file mode 100644 index 00000000..2d314332 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToLM64.asm @@ -0,0 +1,114 @@ +; $Id: bs3-mode-SwitchToLM64.asm $ +;; @file +; BS3Kit - Bs3SwitchToLM64 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Switch to 64-bit long mode from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToLM64(void); +; +; @uses Nothing (except possibly high 32-bit and/or upper 64-bit register parts). +; +; @remarks Obviously returns to 64-bit mode, even if the caller was in 16-bit +; or 32-bit mode. It doesn't not preserve the callers ring, but +; instead changes to ring-0. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToLM64_Safe), function, 0 +BS3_PROC_BEGIN_MODE Bs3SwitchToLM64, BS3_PBC_NEAR +%ifdef TMPL_LM64 + ret + +%elifdef TMPL_CMN_LM + ; + ; Already in long mode, just switch to 64-bit. + ; + extern BS3_CMN_NM(Bs3SwitchTo64Bit) + jmp BS3_CMN_NM(Bs3SwitchTo64Bit) + +%else + ; + ; Switch to LM32 and then switch to 64-bits (IDT & TSS are the same for + ; LM16, LM32 and LM64, unlike the rest). + ; + ; (The long mode switching code is going via 32-bit protected mode, so + ; Bs3SwitchToLM32 contains the actual code for switching to avoid + ; unnecessary 32-bit -> 64-bit -> 32-bit trips.) + ; + %ifdef TMPL_16BIT + and esp, 0ffffh + push word [esp] ; copy return address. + and word [esp + 2], 0 ; clear upper return address + add dword [esp], BS3_ADDR_BS3TEXT16 ; Add base of return segment, completing 32-bit conversion. + %endif + extern TMPL_NM(Bs3SwitchToLM32) + call TMPL_NM(Bs3SwitchToLM32) + BS3_SET_BITS 32 + + extern _Bs3SwitchTo64Bit_c32 + jmp _Bs3SwitchTo64Bit_c32 +%endif +BS3_PROC_END_MODE Bs3SwitchToLM64 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToLM64, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToLM64) + BS3_SET_BITS 64 + + ; Jmp to common code for the tedious conversion. + %if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + extern _Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn_c64 + jmp _Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn_c64 + %else + extern _Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn_c64 + jmp _Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn_c64 + %endif + BS3_SET_BITS 16 +BS3_PROC_END_MODE Bs3SwitchToLM64 +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPAE16.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPAE16.asm new file mode 100644 index 00000000..e4dab838 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPAE16.asm @@ -0,0 +1,243 @@ +; $Id: bs3-mode-SwitchToPAE16.asm $ +;; @file +; BS3Kit - Bs3SwitchToPAE16 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +%ifndef TMPL_PAE16 +BS3_BEGIN_TEXT16 +extern NAME(Bs3EnteredMode_pae16) + %ifdef TMPL_PAE32 + BS3_EXTERN_CMN Bs3SwitchTo16Bit + %endif +TMPL_BEGIN_TEXT +%endif + + +;; +; Switch to 16-bit paged protected mode from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToPAE16(void); +; +; @uses Nothing (except high 32-bit register parts). +; +; @remarks Obviously returns to 16-bit mode, even if the caller was +; in 32-bit or 64-bit mode. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +%if TMPL_BITS == 16 +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToPAE16_Safe), function , 0 +%endif +BS3_PROC_BEGIN_MODE Bs3SwitchToPAE16, BS3_PBC_NEAR +%ifdef TMPL_PAE16 + extern BS3_CMN_NM(Bs3SwitchToRing0) + call BS3_CMN_NM(Bs3SwitchToRing0) + push ax + mov ax, BS3_SEL_R0_DS16 + mov ds, ax + mov es, ax + pop ax + ret + +%elif BS3_MODE_IS_V86(TMPL_MODE) + ; + ; V8086 - Switch to 16-bit ring-0 and call worker for that mode. + ; + extern BS3_CMN_NM(Bs3SwitchToRing0) + call BS3_CMN_NM(Bs3SwitchToRing0) + extern %[BS3_MODE_R0_NM_ %+ TMPL_MODE](Bs3SwitchToPAE16) + jmp %[BS3_MODE_R0_NM_ %+ TMPL_MODE](Bs3SwitchToPAE16) + +%else + ; + ; Switch to 16-bit text segment and prepare for returning in 16-bit mode. + ; + %if TMPL_BITS != 16 + shl xPRE [xSP], TMPL_BITS - 16 ; Adjust the return address. + add xSP, xCB - 2 + + ; Must be in 16-bit segment when calling Bs3SwitchToRM and Bs3SwitchTo16Bit. + jmp .sixteen_bit_segment +BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .sixteen_bit_segment + %endif + + %ifdef TMPL_PAE32 + ; + ; No need to go to real-mode here, we use the same CR3 and stuff. + ; Just switch to 32-bit mode and call the Bs3EnteredMode routine to + ; load the right descriptor tables. + ; + call Bs3SwitchTo16Bit + BS3_SET_BITS 16 + call NAME(Bs3EnteredMode_pae16) + ret + %else + + ; + ; Switch to real mode. + ; + extern TMPL_NM(Bs3SwitchToRM) + call TMPL_NM(Bs3SwitchToRM) + BS3_SET_BITS 16 + + push eax + push ecx + pushfd + + ; + ; Get the page directory (returned in eax). + ; Will lazy init page tables (in 16-bit prot mode). + ; + extern NAME(Bs3PagingGetRootForPAE16_rm) + call NAME(Bs3PagingGetRootForPAE16_rm) + + cli + mov cr3, eax + + ; + ; Make sure PAE, PSE, and VME are enabled (former two require pentium pro, latter 486). + ; + mov eax, cr4 + mov ecx, eax + or eax, X86_CR4_PAE | X86_CR4_PSE | X86_CR4_VME + cmp eax, ecx + je .cr4_is_fine + mov cr4, eax +.cr4_is_fine: + + ; + ; Load the GDT and enable PP16. + ; +BS3_EXTERN_SYSTEM16 Bs3LgdtDef_Gdt +BS3_EXTERN_SYSTEM16 Bs3Lgdt_Gdt +BS3_BEGIN_TEXT16 + mov ax, BS3SYSTEM16 + mov ds, ax + lgdt [Bs3LgdtDef_Gdt] ; Will only load 24-bit base! + + mov eax, cr0 + or eax, X86_CR0_PE | X86_CR0_PG + mov cr0, eax + jmp BS3_SEL_R0_CS16:.reload_cs_and_stuff +.reload_cs_and_stuff: + + ; + ; Convert the (now) real mode stack to 16-bit. + ; + mov ax, .stack_fix_return + extern NAME(Bs3ConvertRMStackToP16UsingCxReturnToAx_c16) + jmp NAME(Bs3ConvertRMStackToP16UsingCxReturnToAx_c16) +.stack_fix_return: + + ; + ; Call rountine for doing mode specific setups. + ; + call NAME(Bs3EnteredMode_pae16) + + ; + ; Load full 32-bit GDT base address from 32-bit segment. + ; + push ds + mov ax, BS3_SEL_SYSTEM16 + mov ds, ax + jmp dword BS3_SEL_R0_CS32:.load_full_gdt_base wrt FLAT +.load_full_gdt_base: + BS3_SET_BITS 32 + lgdt [Bs3Lgdt_Gdt wrt BS3SYSTEM16] + jmp BS3_SEL_R0_CS16:.back_to_16bit +.back_to_16bit: + BS3_SET_BITS 16 + pop ds + + popfd + pop ecx + pop eax + ret + + %endif ; !TMPL_PP32 + %if TMPL_BITS != 16 +TMPL_BEGIN_TEXT + %endif +%endif +BS3_PROC_END_MODE Bs3SwitchToPAE16 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToPAE16, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToPAE16) + + %if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + ; Jmp to common code for the tedious conversion. + BS3_EXTERN_CMN Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn + jmp Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn + %else + pop bp + dec bp + retf + %endif +BS3_PROC_END_MODE Bs3SwitchToPAE16 + +%else +;; +; Safe far return to non-BS3TEXT16 code. +BS3_EXTERN_CMN Bs3SwitchHlpConvFlatRetToRetfProtMode +BS3_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_PROC_BEGIN_MODE Bs3SwitchToPAE16_Safe, BS3_PBC_NEAR + call Bs3SwitchHlpConvFlatRetToRetfProtMode ; Special internal function. Uses nothing, but modifies the stack. + call TMPL_NM(Bs3SwitchToPAE16) + BS3_SET_BITS 16 + retf +BS3_PROC_END_MODE Bs3SwitchToPAE16_Safe +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPAE16_32.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPAE16_32.asm new file mode 100644 index 00000000..b267a62a --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPAE16_32.asm @@ -0,0 +1,114 @@ +; $Id: bs3-mode-SwitchToPAE16_32.asm $ +;; @file +; BS3Kit - Bs3SwitchToPAE16_32 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Switch to 32-bit code under 16-bit PAE paged protected mode sys/tss from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToPE16_32(void); +; +; @uses Nothing (except high 32-bit register parts). +; +; @remarks Obviously returns to 32-bit mode, even if the caller was +; in 16-bit or 64-bit mode. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToPAE16_32_Safe), function, 0 +BS3_PROC_BEGIN_MODE Bs3SwitchToPAE16_32, BS3_PBC_NEAR +%ifdef TMPL_PAE16_32 + ret + +%else + ; + ; Make sure we're in the 16-bit segment and then call Bs3SwitchToPAE16. + ; + %if TMPL_BITS != 16 + jmp .sixteen_bit_segment +BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .sixteen_bit_segment + %endif + extern TMPL_NM(Bs3SwitchToPAE16) + call TMPL_NM(Bs3SwitchToPAE16) + BS3_SET_BITS 16 + + ; + ; Switch to 32-bit mode. + ; + extern _Bs3SwitchTo32Bit_c16 + %if TMPL_BITS == 16 + jmp _Bs3SwitchTo32Bit_c16 + %else + call _Bs3SwitchTo32Bit_c16 + BS3_SET_BITS 32 + %if TMPL_BITS == 32 + ret + %else + ret 4 ; Return and pop 4 bytes of "parameters" (unused return address). + %endif + %endif +%endif +BS3_PROC_END_MODE Bs3SwitchToPAE16_32 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToPAE16_32, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToPAE16_32) + BS3_SET_BITS 32 + + ; Jmp to common code for the tedious conversion. + %if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + extern _Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn_c32 + jmp _Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn_c32 + %else + extern _Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn_c32 + jmp _Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn_c32 + %endif + BS3_SET_BITS 16 +BS3_PROC_END_MODE Bs3SwitchToPAE16_32 +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPAE16_V86.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPAE16_V86.asm new file mode 100644 index 00000000..74acddd3 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPAE16_V86.asm @@ -0,0 +1,124 @@ +; $Id: bs3-mode-SwitchToPAE16_V86.asm $ +;; @file +; BS3Kit - Bs3SwitchToPAE16_V86 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Switch to 16-bit PAE paged protected mode with 16-bit sys+tss from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToPAE16(void); +; +; @uses Nothing (except high 32-bit register parts). +; +; @remarks Obviously returns to v8086 16-bit mode, even if the caller was +; in 16-bit, 32-bit or 64-bit mode. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +%if TMPL_BITS == 16 +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToPAE16_V86_Safe), function , 0 +%endif +BS3_PROC_BEGIN_MODE Bs3SwitchToPAE16_V86, BS3_PBC_NEAR +%ifdef TMPL_PAE16_V86 + ret + +%else + ; + ; Convert the return address and jump to the 16-bit code segment. + ; + %if TMPL_BITS != 16 + shl xPRE [xSP], TMPL_BITS - 16 + add xSP, (TMPL_BITS - 16) / 8 + jmp .sixteen_bit_segment +BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .sixteen_bit_segment + %endif + + ; + ; Switch to 16-bit PAE16 and from there to V8086. + ; + extern TMPL_NM(Bs3SwitchToPAE16) + call TMPL_NM(Bs3SwitchToPAE16) + BS3_SET_BITS 16 + + ; + ; Switch to v8086 mode (return address is already 16-bit). + ; + extern _Bs3SwitchTo16BitV86_c16 + jmp _Bs3SwitchTo16BitV86_c16 +%endif +BS3_PROC_END_MODE Bs3SwitchToPAE16_V86 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToPAE16_V86, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToPAE16_V86) + + %if !BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + ; Jmp to common code for the tedious conversion. + BS3_EXTERN_CMN Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn + jmp Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn + %else + pop bp + dec bp + retf + %endif +BS3_PROC_END_MODE Bs3SwitchToPAE16_V86 + +%else +;; +; Safe far return to non-BS3TEXT16 code. +BS3_EXTERN_CMN Bs3SwitchHlpConvFlatRetToRetfProtMode +BS3_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_PROC_BEGIN_MODE Bs3SwitchToPAE16_V86_Safe, BS3_PBC_NEAR + call Bs3SwitchHlpConvFlatRetToRetfProtMode ; Special internal function. Uses nothing, but modifies the stack. + call TMPL_NM(Bs3SwitchToPAE16_V86) + BS3_SET_BITS 16 + retf +BS3_PROC_END_MODE Bs3SwitchToPAE16_V86_Safe +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPAE32.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPAE32.asm new file mode 100644 index 00000000..d5bc7402 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPAE32.asm @@ -0,0 +1,202 @@ +; $Id: bs3-mode-SwitchToPAE32.asm $ +;; @file +; BS3Kit - Bs3SwitchToPAE32 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Switch to PAE paged protected mode from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToPAE32(void); +; +; @uses Nothing (except high 32-bit register parts), upper part of ESP is +; cleared if caller is in 16-bit mode. +; +; @remarks Obviously returns to 32-bit mode, even if the caller was +; in 16-bit or 64-bit mode. It doesn't not preserve the callers +; ring, but instead changes to ring-0. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToPAE32_Safe), function, 0 +BS3_PROC_BEGIN_MODE Bs3SwitchToPAE32, BS3_PBC_NEAR +%ifdef TMPL_PAE32 + ret + +%elif BS3_MODE_IS_V86(TMPL_MODE) + ; + ; V8086 - Switch to 16-bit ring-0 and call worker for that mode. + ; + extern BS3_CMN_NM(Bs3SwitchToRing0) + call BS3_CMN_NM(Bs3SwitchToRing0) + extern %[BS3_MODE_R0_NM_ %+ TMPL_MODE](Bs3SwitchToPAE32) + jmp %[BS3_MODE_R0_NM_ %+ TMPL_MODE](Bs3SwitchToPAE32) + +%else + ; + ; Switch to real mode. + ; + %if TMPL_BITS != 32 + %if TMPL_BITS > 32 + shl xPRE [xSP], 32 ; Adjust the return address from 64-bit to 32-bit. + add rsp, xCB - 4 + %else + push word 0 ; Reserve space to expand the return address. + %endif + %endif + %if TMPL_BITS != 16 + ; Must be in 16-bit segment when calling Bs3SwitchTo16Bit. + jmp .sixteen_bit_segment +BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .sixteen_bit_segment + %endif + + ; + ; Switch to real mode. + ; + extern TMPL_NM(Bs3SwitchToRM) + call TMPL_NM(Bs3SwitchToRM) + BS3_SET_BITS 16 + + push eax + push ecx + pushfd + + ; + ; Get the page directory (returned in eax). + ; Will lazy init page tables (in 16-bit prot mode). + ; + extern NAME(Bs3PagingGetRootForPAE32_rm) + call NAME(Bs3PagingGetRootForPAE32_rm) + + cli + mov cr3, eax + + ; + ; Make sure PAE, PSE, and VME are enabled (former two require pentium pro, latter 486). + ; + mov eax, cr4 + mov ecx, eax + or eax, X86_CR4_PAE | X86_CR4_PSE | X86_CR4_VME + cmp eax, ecx + je .cr4_is_fine + mov cr4, eax +.cr4_is_fine: + + ; + ; Load the GDT and enable PE32. + ; +BS3_EXTERN_SYSTEM16 Bs3LgdtDef_Gdt +BS3_EXTERN_SYSTEM16 Bs3Lgdt_Gdt +BS3_BEGIN_TEXT16 + mov ax, BS3SYSTEM16 + mov ds, ax + lgdt [Bs3LgdtDef_Gdt] ; Will only load 24-bit base! + + mov eax, cr0 + or eax, X86_CR0_PE | X86_CR0_PG + mov cr0, eax + jmp BS3_SEL_R0_CS32:dword .thirty_two_bit wrt FLAT +BS3_BEGIN_TEXT32 +BS3_GLOBAL_LOCAL_LABEL .thirty_two_bit + + ; + ; Convert the (now) real mode stack pointer to 32-bit flat. + ; + xor eax, eax + mov ax, ss + shl eax, 4 + and esp, 0ffffh + add esp, eax + + mov ax, BS3_SEL_R0_SS32 + mov ss, ax + + ; + ; Call rountine for doing mode specific setups. + ; + extern NAME(Bs3EnteredMode_pae32) + call NAME(Bs3EnteredMode_pae32) + + ; Load full 32-bit GDT base address. + lgdt [Bs3Lgdt_Gdt wrt FLAT] + + ; + ; Restore ecx, eax and flags (IF). + ; + %if TMPL_BITS < 32 + movzx eax, word [esp + 12 + 2] ; Load return address. + add eax, BS3_ADDR_BS3TEXT16 ; Convert it to a flat address. + mov [esp + 12], eax ; Store it in the place right for 32-bit returns. + %endif + popfd + pop ecx + pop eax + ret + + %if TMPL_BITS != 32 +TMPL_BEGIN_TEXT + %endif +%endif +BS3_PROC_END_MODE Bs3SwitchToPAE32 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToPAE32, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToPAE32) + BS3_SET_BITS 32 + + ; Jmp to common code for the tedious conversion. + %if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + extern _Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn_c32 + jmp _Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn_c32 + %else + extern _Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn_c32 + jmp _Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn_c32 + %endif + BS3_SET_BITS 16 +BS3_PROC_END_MODE Bs3SwitchToPAE32 +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPAE32_16.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPAE32_16.asm new file mode 100644 index 00000000..e04502b4 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPAE32_16.asm @@ -0,0 +1,132 @@ +; $Id: bs3-mode-SwitchToPAE32_16.asm $ +;; @file +; BS3Kit - Bs3SwitchToPAE32_16 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Switch to 16-bit code under 32-bit PAE paged protected mode sys/tss from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToPAE32_16(void); +; +; @uses Nothing (except high 32-bit register parts). +; +; @remarks Obviously returns to 16-bit mode, even if the caller was +; in 32-bit or 64-bit mode. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +%if TMPL_BITS == 16 +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToPAE32_16_Safe), function , 0 +%endif +BS3_PROC_BEGIN_MODE Bs3SwitchToPAE32_16, BS3_PBC_NEAR +%if TMPL_MODE == BS3_MODE_PAE32_16 + ret + +%elif TMPL_MODE == BS3_MODE_PAE32 + extern BS3_CMN_NM(Bs3SwitchTo16Bit) + jmp BS3_CMN_NM(Bs3SwitchTo16Bit) + +%else + ; + ; Switch to PAE32. + ; + extern TMPL_NM(Bs3SwitchToPAE32) + call TMPL_NM(Bs3SwitchToPAE32) + BS3_SET_BITS 32 + + ; + ; Make sure we're in the 16-bit segment and then do the switch to 16-bit. + ; + %if TMPL_BITS != 16 + jmp .sixteen_bit_segment +BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .sixteen_bit_segment + %endif + extern _Bs3SwitchTo16Bit_c32 + %if TMPL_BITS == 32 + jmp _Bs3SwitchTo16Bit_c32 + %else + call _Bs3SwitchTo16Bit_c32 + BS3_SET_BITS 16 + %if TMPL_BITS == 16 + ret + %else + ret 6 ; Return and pop 6 bytes of "parameters" (unused return address). + %endif + %endif +%endif +BS3_PROC_END_MODE Bs3SwitchToPAE32_16 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToPAE32_16, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToPAE32_16) + + %if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + ; Jmp to common code for the tedious conversion. + BS3_EXTERN_CMN Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn + jmp Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn + %else + pop bp + dec bp + retf + %endif +BS3_PROC_END_MODE Bs3SwitchToPAE32_16 + +%else +;; +; Safe far return to non-BS3TEXT16 code. +BS3_EXTERN_CMN Bs3SwitchHlpConvFlatRetToRetfProtMode +BS3_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_PROC_BEGIN_MODE Bs3SwitchToPAE32_16_Safe, BS3_PBC_NEAR + call Bs3SwitchHlpConvFlatRetToRetfProtMode ; Special internal function. Uses nothing, but modifies the stack. + call TMPL_NM(Bs3SwitchToPAE32_16) + BS3_SET_BITS 16 + retf +BS3_PROC_END_MODE Bs3SwitchToPAE32_16_Safe +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPAEV86.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPAEV86.asm new file mode 100644 index 00000000..661e738e --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPAEV86.asm @@ -0,0 +1,118 @@ +; $Id: bs3-mode-SwitchToPAEV86.asm $ +;; @file +; BS3Kit - Bs3SwitchToPAEV86 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Switch to 16-bit v8086 PAE paged protected mode with 32-bit sys+tss from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToPAEV86(void); +; +; @uses Nothing (except high 32-bit register parts). +; +; @remarks Obviously returns to 16-bit v8086 mode, even if the caller was +; in 32-bit or 64-bit mode. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +%if TMPL_BITS == 16 +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToPAEV86_Safe), function , 0 +%endif +BS3_PROC_BEGIN_MODE Bs3SwitchToPAEV86, BS3_PBC_NEAR +%if TMPL_MODE == BS3_MODE_PAEV86 + ret + +%else + ; + ; Switch to 32-bit PAE32 and from there to V8086. + ; + extern TMPL_NM(Bs3SwitchToPAE32) + call TMPL_NM(Bs3SwitchToPAE32) + BS3_SET_BITS 32 + + ; + ; Switch to v8086 mode after adjusting the return address. + ; + %if TMPL_BITS == 16 + push word [esp] + mov word [esp + 2], 0 + %elif TMPL_BITS == 64 + pop dword [esp + 4] + %endif + extern _Bs3SwitchTo16BitV86_c32 + jmp _Bs3SwitchTo16BitV86_c32 +%endif +BS3_PROC_END_MODE Bs3SwitchToPAEV86 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToPAEV86, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToPAEV86) + + %if !BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + ; Jmp to common code for the tedious conversion. + BS3_EXTERN_CMN Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn + jmp Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn + %else + pop bp + dec bp + retf + %endif +BS3_PROC_END_MODE Bs3SwitchToPAEV86 + +%else +;; +; Safe far return to non-BS3TEXT16 code. +BS3_EXTERN_CMN Bs3SwitchHlpConvFlatRetToRetfProtMode +BS3_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_PROC_BEGIN_MODE Bs3SwitchToPAEV86_Safe, BS3_PBC_NEAR + call Bs3SwitchHlpConvFlatRetToRetfProtMode ; Special internal function. Uses nothing, but modifies the stack. + call TMPL_NM(Bs3SwitchToPAEV86) + BS3_SET_BITS 16 + retf +BS3_PROC_END_MODE Bs3SwitchToPAEV86_Safe +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPE16.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPE16.asm new file mode 100644 index 00000000..696ead0c --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPE16.asm @@ -0,0 +1,198 @@ +; $Id: bs3-mode-SwitchToPE16.asm $ +;; @file +; BS3Kit - Bs3SwitchToPE16 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Switch to 16-bit unpaged protected mode from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToPE16(void); +; +; @uses Nothing (except high 32-bit register parts). +; +; @remarks Obviously returns to 16-bit mode, even if the caller was +; in 32-bit or 64-bit mode. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +%if TMPL_BITS == 16 +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToPE16_Safe), function , 0 +%endif +BS3_PROC_BEGIN_MODE Bs3SwitchToPE16, BS3_PBC_NEAR +%ifdef TMPL_PE16 + extern BS3_CMN_NM(Bs3SwitchToRing0) + call BS3_CMN_NM(Bs3SwitchToRing0) + push ax + mov ax, BS3_SEL_R0_DS16 + mov ds, ax + mov es, ax + pop ax + ret + +%elif BS3_MODE_IS_V86(TMPL_MODE) + ; + ; V8086 - Switch to 16-bit ring-0 and call worker for that mode. + ; + extern BS3_CMN_NM(Bs3SwitchToRing0) + call BS3_CMN_NM(Bs3SwitchToRing0) + extern %[BS3_MODE_R0_NM_ %+ TMPL_MODE](Bs3SwitchToPE16) + jmp %[BS3_MODE_R0_NM_ %+ TMPL_MODE](Bs3SwitchToPE16) + +%else + ; + ; Switch to 16-bit mode and prepare for returning in 16-bit mode. + ; + %if TMPL_BITS != 16 + shl xPRE [xSP], TMPL_BITS - 16 ; Adjust the return address. + add xSP, xCB - 2 + + ; Must be in 16-bit segment when calling Bs3SwitchTo16Bit. + jmp .sixteen_bit_segment +BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .sixteen_bit_segment + %endif + + ; + ; Switch to real mode. + ; + extern TMPL_NM(Bs3SwitchToRM) + call TMPL_NM(Bs3SwitchToRM) + BS3_SET_BITS 16 + + push ax + push cx + pushf + cli + + ; + ; Load the GDT and enable PE16. + ; +BS3_EXTERN_SYSTEM16 Bs3Lgdt_Gdt +BS3_EXTERN_SYSTEM16 Bs3LgdtDef_Gdt +BS3_BEGIN_TEXT16 + mov ax, BS3SYSTEM16 + mov ds, ax + lgdt [Bs3LgdtDef_Gdt] ; Will only load 24-bit base! + + smsw ax + or ax, X86_CR0_PE + lmsw ax + + ; + ; Convert from real mode stack to protected mode stack. + ; + mov ax, .p16_stack + extern NAME(Bs3ConvertRMStackToP16UsingCxReturnToAx_c16) + jmp NAME(Bs3ConvertRMStackToP16UsingCxReturnToAx_c16) +.p16_stack: + + ; + ; Call routine for doing mode specific setups. + ; + extern NAME(Bs3EnteredMode_pe16) + call NAME(Bs3EnteredMode_pe16) + + ; + ; Load full 32-bit GDT base address from 32-bit segment, if 386+ CPU. + ; + BS3_EXTERN_DATA16 g_uBs3CpuDetected + BS3_BEGIN_TEXT16 + cmp byte [g_uBs3CpuDetected], BS3CPU_80386 + jb .old_cpu_skip_32bit_lgdt + push ds + mov ax, BS3_SEL_SYSTEM16 + mov ds, ax + jmp dword BS3_SEL_R0_CS32:.load_full_gdt_base wrt FLAT +.load_full_gdt_base: + BS3_SET_BITS 32 + lgdt [Bs3Lgdt_Gdt wrt BS3SYSTEM16] + jmp BS3_SEL_R0_CS16:.back_to_16bit +.back_to_16bit: + BS3_SET_BITS 16 + pop ds +.old_cpu_skip_32bit_lgdt: + + popf + pop cx + pop ax + ret + + %if TMPL_BITS != 16 +TMPL_BEGIN_TEXT + %endif +%endif +BS3_PROC_END_MODE Bs3SwitchToPE16 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToPE16, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToPE16) + + %if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + ; Jmp to common code for the tedious conversion. + BS3_EXTERN_CMN Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn + jmp Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn + %else + pop bp + dec bp + retf + %endif +BS3_PROC_END_MODE Bs3SwitchToPE16 + +%else +;; +; Safe far return to non-BS3TEXT16 code. +BS3_EXTERN_CMN Bs3SwitchHlpConvFlatRetToRetfProtMode +BS3_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_PROC_BEGIN_MODE Bs3SwitchToPE16_Safe, BS3_PBC_NEAR + call Bs3SwitchHlpConvFlatRetToRetfProtMode ; Special internal function. Uses nothing, but modifies the stack. + call TMPL_NM(Bs3SwitchToPE16) + BS3_SET_BITS 16 + retf +BS3_PROC_END_MODE Bs3SwitchToPE16_Safe +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPE16_32.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPE16_32.asm new file mode 100644 index 00000000..c321ded6 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPE16_32.asm @@ -0,0 +1,114 @@ +; $Id: bs3-mode-SwitchToPE16_32.asm $ +;; @file +; BS3Kit - Bs3SwitchToPE16_32 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Switch to 32-bit code under 16-bit unpaged protected mode sys/tss from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToPE16_32(void); +; +; @uses Nothing (except high 32-bit register parts). +; +; @remarks Obviously returns to 32-bit mode, even if the caller was +; in 16-bit or 64-bit mode. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToPE16_32_Safe), function, 0 +BS3_PROC_BEGIN_MODE Bs3SwitchToPE16_32, BS3_PBC_NEAR +%ifdef TMPL_PE16_32 + ret + +%else + ; + ; Make sure we're in the 16-bit segment and then call Bs3SwitchToPE16. + ; + %if TMPL_BITS != 16 + jmp .sixteen_bit_segment +BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .sixteen_bit_segment + %endif + extern TMPL_NM(Bs3SwitchToPE16) + call TMPL_NM(Bs3SwitchToPE16) + BS3_SET_BITS 16 + + ; + ; Switch to 32-bit mode. + ; + extern _Bs3SwitchTo32Bit_c16 + %if TMPL_BITS == 16 + jmp _Bs3SwitchTo32Bit_c16 + %else + call _Bs3SwitchTo32Bit_c16 + BS3_SET_BITS 32 + %if TMPL_BITS == 32 + ret + %else + ret 4 ; Return and pop 4 bytes of "parameters" (unused return address). + %endif + %endif +%endif +BS3_PROC_END_MODE Bs3SwitchToPE16_32 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToPE16_32, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToPE16_32) + BS3_SET_BITS 32 + + ; Jmp to common code for the tedious conversion. + %if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + extern _Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn_c32 + jmp _Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn_c32 + %else + extern _Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn_c32 + jmp _Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn_c32 + %endif + BS3_SET_BITS 16 +BS3_PROC_END_MODE Bs3SwitchToPE16_32 +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPE16_V86.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPE16_V86.asm new file mode 100644 index 00000000..721aaf66 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPE16_V86.asm @@ -0,0 +1,124 @@ +; $Id: bs3-mode-SwitchToPE16_V86.asm $ +;; @file +; BS3Kit - Bs3SwitchToPE16_V86 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Switch to 16-bit unpaged protected mode with 16-bit sys+tss from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToPE16(void); +; +; @uses Nothing (except high 32-bit register parts). +; +; @remarks Obviously returns to 16-bit v8086 mode, even if the caller was +; in 16-bit, 32-bit or 64-bit mode. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +%if TMPL_BITS == 16 +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToPE16_V86_Safe), function , 0 +%endif +BS3_PROC_BEGIN_MODE Bs3SwitchToPE16_V86, BS3_PBC_NEAR +%ifdef TMPL_PE16_V86 + ret + +%else + ; + ; Convert the return address and jump to the 16-bit code segment. + ; + %if TMPL_BITS != 16 + shl xPRE [xSP], TMPL_BITS - 16 + add xSP, (TMPL_BITS - 16) / 8 + jmp .sixteen_bit_segment +BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .sixteen_bit_segment + %endif + + ; + ; Switch to 16-bit PE16 and from there to V8086. + ; + extern TMPL_NM(Bs3SwitchToPE16) + call TMPL_NM(Bs3SwitchToPE16) + BS3_SET_BITS 16 + + ; + ; Switch to v8086 mode (return address is already 16-bit). + ; + extern _Bs3SwitchTo16BitV86_c16 + jmp _Bs3SwitchTo16BitV86_c16 +%endif +BS3_PROC_END_MODE Bs3SwitchToPE16_V86 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToPE16_V86, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToPE16_V86) + + %if !BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + ; Jmp to common code for the tedious conversion. + BS3_EXTERN_CMN Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn + jmp Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn + %else + pop bp + dec bp + retf + %endif +BS3_PROC_END_MODE Bs3SwitchToPE16_V86 + +%else +;; +; Safe far return to non-BS3TEXT16 code. +BS3_EXTERN_CMN Bs3SwitchHlpConvFlatRetToRetfProtMode +BS3_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_PROC_BEGIN_MODE Bs3SwitchToPE16_V86_Safe, BS3_PBC_NEAR + call Bs3SwitchHlpConvFlatRetToRetfProtMode ; Special internal function. Uses nothing, but modifies the stack. + call TMPL_NM(Bs3SwitchToPE16_V86) + BS3_SET_BITS 16 + retf +BS3_PROC_END_MODE Bs3SwitchToPE16_V86_Safe +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPE32.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPE32.asm new file mode 100644 index 00000000..544a70d2 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPE32.asm @@ -0,0 +1,179 @@ +; $Id: bs3-mode-SwitchToPE32.asm $ +;; @file +; BS3Kit - Bs3SwitchToPE32 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Switch to 32-bit unpaged protected mode from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToPE32(void); +; +; @uses Nothing (except high 32-bit register parts), upper part of ESP is +; cleared if caller is in 16-bit mode. +; +; @remarks Obviously returns to 32-bit mode, even if the caller was +; in 16-bit or 64-bit mode. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToPE32_Safe), function, 0 +BS3_PROC_BEGIN_MODE Bs3SwitchToPE32, BS3_PBC_NEAR +%ifdef TMPL_PE32 + ret + +%elif BS3_MODE_IS_V86(TMPL_MODE) + ; + ; V8086 - Switch to 16-bit ring-0 and call worker for that mode. + ; + extern BS3_CMN_NM(Bs3SwitchToRing0) + call BS3_CMN_NM(Bs3SwitchToRing0) + extern %[BS3_MODE_R0_NM_ %+ TMPL_MODE](Bs3SwitchToPE32) + jmp %[BS3_MODE_R0_NM_ %+ TMPL_MODE](Bs3SwitchToPE32) + +%else + ; + ; Switch to real mode. + ; + %if TMPL_BITS != 32 + %if TMPL_BITS > 32 + shl xPRE [xSP], 32 ; Adjust the return address from 64-bit to 32-bit. + add rsp, xCB - 4 + %else + push word 0 ; Reserve space to expand the return address. + %endif + %endif + %if TMPL_BITS != 16 + ; Must be in 16-bit segment when calling Bs3SwitchTo16Bit. + jmp .sixteen_bit_segment +BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .sixteen_bit_segment + %endif + ; + ; Switch to real mode. + ; + extern TMPL_NM(Bs3SwitchToRM) + call TMPL_NM(Bs3SwitchToRM) + BS3_SET_BITS 16 + + push eax + pushfd + cli + + ; + ; Load the GDT and enable PE32. + ; +BS3_EXTERN_SYSTEM16 Bs3LgdtDef_Gdt +BS3_EXTERN_SYSTEM16 Bs3Lgdt_Gdt +BS3_BEGIN_TEXT16 + mov ax, BS3SYSTEM16 + mov ds, ax + lgdt [Bs3LgdtDef_Gdt] ; Will only load 24-bit base! + + mov eax, cr0 + or eax, X86_CR0_PE + mov cr0, eax + jmp BS3_SEL_R0_CS32:dword .thirty_two_bit wrt FLAT +BS3_BEGIN_TEXT32 +BS3_GLOBAL_LOCAL_LABEL .thirty_two_bit + + ; + ; Convert the (now) real mode stack pointer to 32-bit flat. + ; + xor eax, eax + mov ax, ss + shl eax, 4 + and esp, 0ffffh + add esp, eax + + mov ax, BS3_SEL_R0_SS32 + mov ss, ax + + ; + ; Call rountine for doing mode specific setups. + ; + extern NAME(Bs3EnteredMode_pe32) + call NAME(Bs3EnteredMode_pe32) + + ; Load full 32-bit GDT base address. + lgdt [Bs3Lgdt_Gdt wrt FLAT] + + ; + ; Restore eax and flags (IF). + ; + %if TMPL_BITS < 32 + and esp, 0ffffh ; Make sure the high word is zero. + movzx eax, word [esp + 8 + 2] ; Load return address. + add eax, BS3_ADDR_BS3TEXT16 ; Convert it to a flat address. + mov [esp + 8], eax ; Store it in the place right for 32-bit returns. + %endif + popfd + pop eax + ret + + %if TMPL_BITS != 32 +TMPL_BEGIN_TEXT + %endif +%endif +BS3_PROC_END_MODE Bs3SwitchToPE32 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToPE32, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToPE32) + BS3_SET_BITS 32 + + ; Jmp to common code for the tedious conversion. + %if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + extern _Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn_c32 + jmp _Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn_c32 + %else + extern _Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn_c32 + jmp _Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn_c32 + %endif + BS3_SET_BITS 16 +BS3_PROC_END_MODE Bs3SwitchToPE32 +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPE32_16.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPE32_16.asm new file mode 100644 index 00000000..cdb2261c --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPE32_16.asm @@ -0,0 +1,132 @@ +; $Id: bs3-mode-SwitchToPE32_16.asm $ +;; @file +; BS3Kit - Bs3SwitchToPE32_16 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Switch to 16-bit code under 32-bit unpaged protected mode sys/tss from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToPE32_16(void); +; +; @uses Nothing (except high 32-bit register parts). +; +; @remarks Obviously returns to 16-bit mode, even if the caller was +; in 32-bit or 64-bit mode. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +%if TMPL_BITS == 16 +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToPE32_16_Safe), function , 0 +%endif +BS3_PROC_BEGIN_MODE Bs3SwitchToPE32_16, BS3_PBC_NEAR +%if TMPL_MODE == BS3_MODE_PE32_16 + ret + +%elif TMPL_MODE == BS3_MODE_PE32 + extern BS3_CMN_NM(Bs3SwitchTo16Bit) + jmp BS3_CMN_NM(Bs3SwitchTo16Bit) + +%else + ; + ; Switch to PE32. + ; + extern TMPL_NM(Bs3SwitchToPE32) + call TMPL_NM(Bs3SwitchToPE32) + BS3_SET_BITS 32 + + ; + ; Make sure we're in the 16-bit segment and then do the switch to 16-bit. + ; + %if TMPL_BITS != 16 + jmp .sixteen_bit_segment +BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .sixteen_bit_segment + %endif + extern _Bs3SwitchTo16Bit_c32 + %if TMPL_BITS == 32 + jmp _Bs3SwitchTo16Bit_c32 + %else + call _Bs3SwitchTo16Bit_c32 + BS3_SET_BITS 16 + %if TMPL_BITS == 16 + ret + %else + ret 6 ; Return and pop 6 bytes of "parameters" (unused return address). + %endif + %endif +%endif +BS3_PROC_END_MODE Bs3SwitchToPE32_16 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToPE32_16, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToPE32_16) + + %if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + ; Jmp to common code for the tedious conversion. + BS3_EXTERN_CMN Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn + jmp Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn + %else + pop bp + dec bp + retf + %endif +BS3_PROC_END_MODE Bs3SwitchToPE32_16 + +%else +;; +; Safe far return to non-BS3TEXT16 code. +BS3_EXTERN_CMN Bs3SwitchHlpConvFlatRetToRetfProtMode +BS3_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_PROC_BEGIN_MODE Bs3SwitchToPE32_16_Safe, BS3_PBC_NEAR + call Bs3SwitchHlpConvFlatRetToRetfProtMode ; Special internal function. Uses nothing, but modifies the stack. + call TMPL_NM(Bs3SwitchToPE32_16) + BS3_SET_BITS 16 + retf +BS3_PROC_END_MODE Bs3SwitchToPE32_16_Safe +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPEV86.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPEV86.asm new file mode 100644 index 00000000..3796d91e --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPEV86.asm @@ -0,0 +1,118 @@ +; $Id: bs3-mode-SwitchToPEV86.asm $ +;; @file +; BS3Kit - Bs3SwitchToPEV86 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Switch to 16-bit v8086 unpaged protected mode with 32-bit sys+tss from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToPEV86(void); +; +; @uses Nothing (except high 32-bit register parts). +; +; @remarks Obviously returns to 16-bit v8086 mode, even if the caller was +; in 32-bit or 64-bit mode. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +%if TMPL_BITS == 16 +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToPEV86_Safe), function , 0 +%endif +BS3_PROC_BEGIN_MODE Bs3SwitchToPEV86, BS3_PBC_NEAR +%if TMPL_MODE == BS3_MODE_PEV86 + ret + +%else + ; + ; Switch to 32-bit PE32 and from there to V8086. + ; + extern TMPL_NM(Bs3SwitchToPE32) + call TMPL_NM(Bs3SwitchToPE32) + BS3_SET_BITS 32 + + ; + ; Switch to v8086 mode after adjusting the return address. + ; + %if TMPL_BITS == 16 + push word [esp] + mov word [esp + 2], 0 + %elif TMPL_BITS == 64 + pop dword [esp + 4] + %endif + extern _Bs3SwitchTo16BitV86_c32 + jmp _Bs3SwitchTo16BitV86_c32 +%endif +BS3_PROC_END_MODE Bs3SwitchToPEV86 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToPEV86, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToPEV86) + + %if !BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + ; Jmp to common code for the tedious conversion. + BS3_EXTERN_CMN Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn + jmp Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn + %else + pop bp + dec bp + retf + %endif +BS3_PROC_END_MODE Bs3SwitchToPEV86 + +%else +;; +; Safe far return to non-BS3TEXT16 code. +BS3_EXTERN_CMN Bs3SwitchHlpConvFlatRetToRetfProtMode +BS3_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_PROC_BEGIN_MODE Bs3SwitchToPEV86_Safe, BS3_PBC_NEAR + call Bs3SwitchHlpConvFlatRetToRetfProtMode ; Special internal function. Uses nothing, but modifies the stack. + call TMPL_NM(Bs3SwitchToPEV86) + BS3_SET_BITS 16 + retf +BS3_PROC_END_MODE Bs3SwitchToPEV86_Safe +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPP16.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPP16.asm new file mode 100644 index 00000000..2e8332bf --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPP16.asm @@ -0,0 +1,258 @@ +; $Id: bs3-mode-SwitchToPP16.asm $ +;; @file +; BS3Kit - Bs3SwitchToPP16 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +%ifndef TMPL_PP16 +BS3_BEGIN_TEXT16 +extern NAME(Bs3EnteredMode_pp16) + %ifdef TMPL_PP32 + BS3_EXTERN_CMN Bs3SwitchTo16Bit + %endif +TMPL_BEGIN_TEXT +%endif + + +;; +; Switch to 16-bit paged protected mode from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToPP16(void); +; +; @uses Nothing (except high 32-bit register parts). +; +; @remarks Obviously returns to 16-bit mode, even if the caller was +; in 32-bit or 64-bit mode. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +%if TMPL_BITS == 16 +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToPP16_Safe), function , 0 +%endif +BS3_PROC_BEGIN_MODE Bs3SwitchToPP16, BS3_PBC_NEAR +%ifdef TMPL_PP16 + extern BS3_CMN_NM(Bs3SwitchToRing0) + call BS3_CMN_NM(Bs3SwitchToRing0) + push ax + mov ax, BS3_SEL_R0_DS16 + mov ds, ax + mov es, ax + pop ax + ret + +%elif BS3_MODE_IS_V86(TMPL_MODE) + ; + ; V8086 - Switch to 16-bit ring-0 and call worker for that mode. + ; + extern BS3_CMN_NM(Bs3SwitchToRing0) + call BS3_CMN_NM(Bs3SwitchToRing0) + extern %[BS3_MODE_R0_NM_ %+ TMPL_MODE](Bs3SwitchToPP16) + jmp %[BS3_MODE_R0_NM_ %+ TMPL_MODE](Bs3SwitchToPP16) + +%else + + ; + ; Switch to 16-bit text segment and prepare for returning in 16-bit mode. + ; + %if TMPL_BITS != 16 + shl xPRE [xSP], TMPL_BITS - 16 ; Adjust the return address. + add xSP, xCB - 2 + + ; Must be in 16-bit segment when calling Bs3SwitchToRM and Bs3SwitchTo16Bit. + jmp .sixteen_bit_segment +BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .sixteen_bit_segment + %endif + + %ifdef TMPL_PP32 + ; + ; No need to go to real-mode here, we use the same CR3 and stuff. + ; Just switch to 32-bit mode and call the Bs3EnteredMode routine to + ; load the right descriptor tables. + ; + call Bs3SwitchTo16Bit + BS3_SET_BITS 16 + call NAME(Bs3EnteredMode_pp16) + ret + %else + + ; + ; Switch to real mode. + ; + extern TMPL_NM(Bs3SwitchToRM) + call TMPL_NM(Bs3SwitchToRM) + BS3_SET_BITS 16 + + push eax + push ecx + pushfd +%ifdef BS3_STRICT + mov ax, ds + cmp ax, BS3_ADDR_BS3DATA16 >> 4 + je .real_mode_ds_ok + hlt +.real_mode_ds_ok: +%endif + + ; + ; Get the page directory (returned in eax). + ; Will lazy init page tables (in 16-bit prot mode). + ; + extern NAME(Bs3PagingGetRootForPP16_rm) + call NAME(Bs3PagingGetRootForPP16_rm) + + cli + mov cr3, eax + + ; + ; Make sure PAE is really off and that PSE is enabled when supported. + ; +BS3_EXTERN_DATA16 g_uBs3CpuDetected +BS3_BEGIN_TEXT16 + test byte [1 + BS3_DATA16_WRT(g_uBs3CpuDetected)], (BS3CPU_F_CPUID >> 8) + jz .cr4_is_fine + mov eax, cr4 + mov ecx, eax + and eax, ~(X86_CR4_PAE | X86_CR4_PSE) + test byte [1 + BS3_DATA16_WRT(g_uBs3CpuDetected)], (BS3CPU_F_PSE >> 8) + jz .no_pse + or eax, X86_CR4_PSE +.no_pse: + cmp eax, ecx + je .cr4_is_fine + mov cr4, eax +.cr4_is_fine: + + ; + ; Load the GDT and enable PP16. + ; +BS3_EXTERN_SYSTEM16 Bs3LgdtDef_Gdt +BS3_EXTERN_SYSTEM16 Bs3Lgdt_Gdt +BS3_BEGIN_TEXT16 + mov ax, BS3SYSTEM16 + mov ds, ax + lgdt [Bs3LgdtDef_Gdt] ; Will only load 24-bit base! + + mov eax, cr0 + or eax, X86_CR0_PE | X86_CR0_PG + mov cr0, eax + jmp BS3_SEL_R0_CS16:.reload_cs_and_stuff +.reload_cs_and_stuff: + + ; + ; Convert the (now) real mode stack to 16-bit. + ; + mov ax, .stack_fix_return + extern NAME(Bs3ConvertRMStackToP16UsingCxReturnToAx_c16) + jmp NAME(Bs3ConvertRMStackToP16UsingCxReturnToAx_c16) +.stack_fix_return: + + ; + ; Call rountine for doing mode specific setups. + ; + call NAME(Bs3EnteredMode_pp16) + + ; + ; Load full 32-bit GDT base address from 32-bit segment. + ; + push ds + mov ax, BS3_SEL_SYSTEM16 + mov ds, ax + jmp dword BS3_SEL_R0_CS32:.load_full_gdt_base wrt FLAT +.load_full_gdt_base: + BS3_SET_BITS 32 + lgdt [Bs3Lgdt_Gdt wrt BS3SYSTEM16] + jmp BS3_SEL_R0_CS16:.back_to_16bit +.back_to_16bit: + BS3_SET_BITS 16 + pop ds + + popfd + pop ecx + pop eax + ret + + %endif ; !TMPL_PP32 + %if TMPL_BITS != 16 +TMPL_BEGIN_TEXT + %endif +%endif +BS3_PROC_END_MODE Bs3SwitchToPP16 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToPP16, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToPP16) + + %if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + ; Jmp to common code for the tedious conversion. + BS3_EXTERN_CMN Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn + jmp Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn + %else + pop bp + dec bp + retf + %endif +BS3_PROC_END_MODE Bs3SwitchToPP16 + +%else +;; +; Safe far return to non-BS3TEXT16 code. +BS3_EXTERN_CMN Bs3SwitchHlpConvFlatRetToRetfProtMode +BS3_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_PROC_BEGIN_MODE Bs3SwitchToPP16_Safe, BS3_PBC_NEAR + call Bs3SwitchHlpConvFlatRetToRetfProtMode ; Special internal function. Uses nothing, but modifies the stack. + call TMPL_NM(Bs3SwitchToPP16) + BS3_SET_BITS 16 + retf +BS3_PROC_END_MODE Bs3SwitchToPP16_Safe +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPP16_32.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPP16_32.asm new file mode 100644 index 00000000..884aefb1 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPP16_32.asm @@ -0,0 +1,114 @@ +; $Id: bs3-mode-SwitchToPP16_32.asm $ +;; @file +; BS3Kit - Bs3SwitchToPP16_32 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Switch to 32-bit code under 16-bit paged protected mode sys/tss from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToPP16_32(void); +; +; @uses Nothing (except high 32-bit register parts). +; +; @remarks Obviously returns to 32-bit mode, even if the caller was +; in 16-bit or 64-bit mode. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToPP16_32_Safe), function, 0 +BS3_PROC_BEGIN_MODE Bs3SwitchToPP16_32, BS3_PBC_NEAR +%ifdef TMPL_PP16_32 + ret + +%else + ; + ; Make sure we're in the 16-bit segment and then call Bs3SwitchToPP16. + ; + %if TMPL_BITS != 16 + jmp .sixteen_bit_segment +BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .sixteen_bit_segment + %endif + extern TMPL_NM(Bs3SwitchToPP16) + call TMPL_NM(Bs3SwitchToPP16) + BS3_SET_BITS 16 + + ; + ; Switch to 32-bit mode. + ; + extern _Bs3SwitchTo32Bit_c16 + %if TMPL_BITS == 16 + jmp _Bs3SwitchTo32Bit_c16 + %else + call _Bs3SwitchTo32Bit_c16 + BS3_SET_BITS 32 + %if TMPL_BITS == 32 + ret + %else + ret 4 ; Return and pop 4 bytes of "parameters" (unused return address). + %endif + %endif +%endif +BS3_PROC_END_MODE Bs3SwitchToPP16_32 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToPP16_32, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToPP16_32) + BS3_SET_BITS 32 + + ; Jmp to common code for the tedious conversion. + %if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + extern _Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn_c32 + jmp _Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn_c32 + %else + extern _Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn_c32 + jmp _Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn_c32 + %endif + BS3_SET_BITS 16 +BS3_PROC_END_MODE Bs3SwitchToPP16_32 +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPP16_V86.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPP16_V86.asm new file mode 100644 index 00000000..7cda92a8 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPP16_V86.asm @@ -0,0 +1,124 @@ +; $Id: bs3-mode-SwitchToPP16_V86.asm $ +;; @file +; BS3Kit - Bs3SwitchToPP16_V86 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Switch to 16-bit paged protected mode with 16-bit sys+tss from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToPP16(void); +; +; @uses Nothing (except high 32-bit register parts). +; +; @remarks Obviously returns to v8086 16-bit mode, even if the caller was +; in 16-bit, 32-bit or 64-bit mode. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +%if TMPL_BITS == 16 +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToPP16_V86_Safe), function , 0 +%endif +BS3_PROC_BEGIN_MODE Bs3SwitchToPP16_V86, BS3_PBC_NEAR +%ifdef TMPL_PP16_V86 + ret + +%else + ; + ; Convert the return address and jump to the 16-bit code segment. + ; + %if TMPL_BITS != 16 + shl xPRE [xSP], TMPL_BITS - 16 + add xSP, (TMPL_BITS - 16) / 8 + jmp .sixteen_bit_segment +BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .sixteen_bit_segment + %endif + + ; + ; Switch to 16-bit PP16 and from there to V8086. + ; + extern TMPL_NM(Bs3SwitchToPP16) + call TMPL_NM(Bs3SwitchToPP16) + BS3_SET_BITS 16 + + ; + ; Switch to v8086 mode (return address is already 16-bit). + ; + extern _Bs3SwitchTo16BitV86_c16 + jmp _Bs3SwitchTo16BitV86_c16 +%endif +BS3_PROC_END_MODE Bs3SwitchToPP16_V86 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToPP16_V86, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToPP16_V86) + + %if !BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + ; Jmp to common code for the tedious conversion. + BS3_EXTERN_CMN Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn + jmp Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn + %else + pop bp + dec bp + retf + %endif +BS3_PROC_END_MODE Bs3SwitchToPP16_V86 + +%else +;; +; Safe far return to non-BS3TEXT16 code. +BS3_EXTERN_CMN Bs3SwitchHlpConvFlatRetToRetfProtMode +BS3_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_PROC_BEGIN_MODE Bs3SwitchToPP16_V86_Safe, BS3_PBC_NEAR + call Bs3SwitchHlpConvFlatRetToRetfProtMode ; Special internal function. Uses nothing, but modifies the stack. + call TMPL_NM(Bs3SwitchToPP16_V86) + BS3_SET_BITS 16 + retf +BS3_PROC_END_MODE Bs3SwitchToPP16_V86_Safe +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPP32.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPP32.asm new file mode 100644 index 00000000..101e328f --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPP32.asm @@ -0,0 +1,209 @@ +; $Id: bs3-mode-SwitchToPP32.asm $ +;; @file +; BS3Kit - Bs3SwitchToPP32 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Switch to 32-bit paged protected mode from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToPE32(void); +; +; @uses Nothing (except high 32-bit register parts), upper part of ESP is +; cleared if caller is in 16-bit mode. +; +; @remarks Obviously returns to 32-bit mode, even if the caller was +; in 16-bit or 64-bit mode. It doesn't not preserve the callers +; ring, but instead changes to ring-0. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToPP32_Safe), function, 0 +BS3_PROC_BEGIN_MODE Bs3SwitchToPP32, BS3_PBC_NEAR +%ifdef TMPL_PP32 + ret + +%elif BS3_MODE_IS_V86(TMPL_MODE) + ; + ; V8086 - Switch to 16-bit ring-0 and call worker for that mode. + ; + extern BS3_CMN_NM(Bs3SwitchToRing0) + call BS3_CMN_NM(Bs3SwitchToRing0) + extern %[BS3_MODE_R0_NM_ %+ TMPL_MODE](Bs3SwitchToPP32) + jmp %[BS3_MODE_R0_NM_ %+ TMPL_MODE](Bs3SwitchToPP32) + +%else + ; + ; Switch to real mode. + ; + %if TMPL_BITS != 32 + %if TMPL_BITS > 32 + shl xPRE [xSP], 32 ; Adjust the return address from 64-bit to 32-bit. + add rsp, xCB - 4 + %else + push word 0 ; Reserve space to expand the return address. + %endif + %endif + %if TMPL_BITS != 16 + ; Must be in 16-bit segment when calling Bs3SwitchTo16Bit. + jmp .sixteen_bit_segment +BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .sixteen_bit_segment + %endif + + ; + ; Switch to real mode. + ; + extern TMPL_NM(Bs3SwitchToRM) + call TMPL_NM(Bs3SwitchToRM) + BS3_SET_BITS 16 + + push eax + push ecx + pushfd + + ; + ; Make sure PAE is really off and that PSE is on when supported. + ; +BS3_EXTERN_DATA16 g_uBs3CpuDetected +BS3_BEGIN_TEXT16 + test byte [1 + BS3_DATA16_WRT(g_uBs3CpuDetected)], (BS3CPU_F_CPUID >> 8) + jz .cr4_is_fine + mov eax, cr4 + mov ecx, eax + and eax, ~(X86_CR4_PAE | X86_CR4_PSE) + test byte [1 + BS3_DATA16_WRT(g_uBs3CpuDetected)], (BS3CPU_F_PSE >> 8) + jz .no_pse + or eax, X86_CR4_PSE +.no_pse: + cmp eax, ecx + je .cr4_is_fine + mov cr4, eax +.cr4_is_fine: + + ; + ; Get the page directory (returned in eax). + ; Will lazy init page tables (in 16-bit prot mode). + ; + extern NAME(Bs3PagingGetRootForPP32_rm) + call NAME(Bs3PagingGetRootForPP32_rm) + + cli + mov cr3, eax + + ; + ; Load the GDT and enable PE32. + ; +BS3_EXTERN_SYSTEM16 Bs3LgdtDef_Gdt +BS3_EXTERN_SYSTEM16 Bs3Lgdt_Gdt +BS3_BEGIN_TEXT16 + mov ax, BS3SYSTEM16 + mov ds, ax + lgdt [Bs3LgdtDef_Gdt] ; Will only load 24-bit base! + + mov eax, cr0 + or eax, X86_CR0_PE | X86_CR0_PG + mov cr0, eax + jmp BS3_SEL_R0_CS32:dword .thirty_two_bit wrt FLAT +BS3_BEGIN_TEXT32 +BS3_GLOBAL_LOCAL_LABEL .thirty_two_bit + ; + ; Convert the (now) real mode stack pointer to 32-bit flat. + ; + xor eax, eax + mov ax, ss + shl eax, 4 + and esp, 0ffffh + add esp, eax + + mov ax, BS3_SEL_R0_SS32 + mov ss, ax + + ; + ; Call rountine for doing mode specific setups. + ; + extern NAME(Bs3EnteredMode_pp32) + call NAME(Bs3EnteredMode_pp32) + + ; Load full 32-bit GDT base address. + lgdt [Bs3Lgdt_Gdt wrt FLAT] + + ; + ; Restore ecx, eax and flags (IF). + ; + %if TMPL_BITS < 32 + movzx eax, word [esp + 12 + 2] ; Load return address. + add eax, BS3_ADDR_BS3TEXT16 ; Convert it to a flat address. + mov [esp + 12], eax ; Store it in the place right for 32-bit returns. + %endif + popfd + pop ecx + pop eax + ret + + %if TMPL_BITS != 32 +TMPL_BEGIN_TEXT + %endif +%endif +BS3_PROC_END_MODE Bs3SwitchToPP32 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToPP32, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToPP32) + BS3_SET_BITS 32 + + ; Jmp to common code for the tedious conversion. + %if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + extern _Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn_c32 + jmp _Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn_c32 + %else + extern _Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn_c32 + jmp _Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn_c32 + %endif + BS3_SET_BITS 16 +BS3_PROC_END_MODE Bs3SwitchToPP32 +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPP32_16.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPP32_16.asm new file mode 100644 index 00000000..77fc6d86 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPP32_16.asm @@ -0,0 +1,132 @@ +; $Id: bs3-mode-SwitchToPP32_16.asm $ +;; @file +; BS3Kit - Bs3SwitchToPP32_16 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Switch to 16-bit code under 32-bit paged protected mode sys/tss from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToPP32_16(void); +; +; @uses Nothing (except high 32-bit register parts). +; +; @remarks Obviously returns to 16-bit mode, even if the caller was +; in 32-bit or 64-bit mode. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +%if TMPL_BITS == 16 +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToPP32_16_Safe), function , 0 +%endif +BS3_PROC_BEGIN_MODE Bs3SwitchToPP32_16, BS3_PBC_NEAR +%if TMPL_MODE == BS3_MODE_PP32_16 + ret + +%elif TMPL_MODE == BS3_MODE_PP32 + extern BS3_CMN_NM(Bs3SwitchTo16Bit) + jmp BS3_CMN_NM(Bs3SwitchTo16Bit) + +%else + ; + ; Switch to PP32. + ; + extern TMPL_NM(Bs3SwitchToPP32) + call TMPL_NM(Bs3SwitchToPP32) + BS3_SET_BITS 32 + + ; + ; Make sure we're in the 16-bit segment and then do the switch to 16-bit. + ; + %if TMPL_BITS != 16 + jmp .sixteen_bit_segment +BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +.sixteen_bit_segment: + %endif + extern _Bs3SwitchTo16Bit_c32 + %if TMPL_BITS == 32 + jmp _Bs3SwitchTo16Bit_c32 + %else + call _Bs3SwitchTo16Bit_c32 + BS3_SET_BITS 16 + %if TMPL_BITS == 16 + ret + %else + ret 6 ; Return and pop 6 bytes of "parameters" (unused return address). + %endif + %endif +%endif +BS3_PROC_END_MODE Bs3SwitchToPP32_16 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToPP32_16, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToPP32_16) + + %if BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + ; Jmp to common code for the tedious conversion. + BS3_EXTERN_CMN Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn + jmp Bs3SwitchHlpConvRealModeRetfPopBpDecBpAndReturn + %else + pop bp + dec bp + retf + %endif +BS3_PROC_END_MODE Bs3SwitchToPP32_16 + +%else +;; +; Safe far return to non-BS3TEXT16 code. +BS3_EXTERN_CMN Bs3SwitchHlpConvFlatRetToRetfProtMode +BS3_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_PROC_BEGIN_MODE Bs3SwitchToPP32_16_Safe, BS3_PBC_NEAR + call Bs3SwitchHlpConvFlatRetToRetfProtMode ; Special internal function. Uses nothing, but modifies the stack. + call TMPL_NM(Bs3SwitchToPP32_16) + BS3_SET_BITS 16 + retf +BS3_PROC_END_MODE Bs3SwitchToPP32_16_Safe +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPPV86.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPPV86.asm new file mode 100644 index 00000000..9e5de487 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToPPV86.asm @@ -0,0 +1,118 @@ +; $Id: bs3-mode-SwitchToPPV86.asm $ +;; @file +; BS3Kit - Bs3SwitchToPPV86 +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; Switch to 16-bit v8086 paged protected mode with 32-bit sys+tss from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToPPV86(void); +; +; @uses Nothing (except high 32-bit register parts). +; +; @remarks Obviously returns to 16-bit v8086 mode, even if the caller was +; in 32-bit or 64-bit mode. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +%if TMPL_BITS == 16 +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToPPV86_Safe), function , 0 +%endif +BS3_PROC_BEGIN_MODE Bs3SwitchToPPV86, BS3_PBC_NEAR +%if TMPL_MODE == BS3_MODE_PPV86 + ret + +%else + ; + ; Switch to 32-bit PP32 and from there to V8086. + ; + extern TMPL_NM(Bs3SwitchToPP32) + call TMPL_NM(Bs3SwitchToPP32) + BS3_SET_BITS 32 + + ; + ; Switch to v8086 mode after adjusting the return address. + ; + %if TMPL_BITS == 16 + push word [esp] + mov word [esp + 2], 0 + %elif TMPL_BITS == 64 + pop dword [esp + 4] + %endif + extern _Bs3SwitchTo16BitV86_c32 + jmp _Bs3SwitchTo16BitV86_c32 +%endif +BS3_PROC_END_MODE Bs3SwitchToPPV86 + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToPPV86, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToPPV86) + + %if !BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + ; Jmp to common code for the tedious conversion. + BS3_EXTERN_CMN Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn + jmp Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn + %else + pop bp + dec bp + retf + %endif +BS3_PROC_END_MODE Bs3SwitchToPPV86 + +%else +;; +; Safe far return to non-BS3TEXT16 code. +BS3_EXTERN_CMN Bs3SwitchHlpConvFlatRetToRetfProtMode +BS3_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_PROC_BEGIN_MODE Bs3SwitchToPPV86_Safe, BS3_PBC_NEAR + call Bs3SwitchHlpConvFlatRetToRetfProtMode ; Special internal function. Uses nothing, but modifies the stack. + call TMPL_NM(Bs3SwitchToPPV86) + BS3_SET_BITS 16 + retf +BS3_PROC_END_MODE Bs3SwitchToPPV86_Safe +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToRM.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToRM.asm new file mode 100644 index 00000000..541a0492 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-SwitchToRM.asm @@ -0,0 +1,411 @@ +; $Id: bs3-mode-SwitchToRM.asm $ +;; @file +; BS3Kit - Bs3SwitchToRM +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +BS3_EXTERN_SYSTEM16 Bs3Gdt +%if TMPL_MODE == BS3_MODE_PE16 +BS3_EXTERN_DATA16 g_uBs3CpuDetected +BS3_EXTERN_CMN Bs3KbdWrite +BS3_EXTERN_CMN Bs3KbdWait +%endif + + +;********************************************************************************************************************************* +;* Global Variables * +;********************************************************************************************************************************* +%if TMPL_MODE == BS3_MODE_PE16 +BS3_BEGIN_DATA16 +;; Where to start restoring stack. +g_ResumeSp: dw 0xfeed +;; Where to start restoring stack. +g_ResumeSs: dw 0xface +%endif + +TMPL_BEGIN_TEXT + + +;; +; Switch to real mode from any other mode. +; +; @cproto BS3_DECL(void) Bs3SwitchToRM(void); +; +; @uses GPRs and EFLAGS are unchanged (except high 32-bit register (AMD64) parts). +; CS is loaded with CGROUP16. +; SS:[RE]SP is converted to real mode address. +; DS and ES are loaded with BS3DATA16_GROUP. +; FS and GS are loaded with zero if present. +; +; @remarks Obviously returns to 16-bit mode, even if the caller was +; in 32-bit or 64-bit mode. +; +; @remarks Does not require 20h of parameter scratch space in 64-bit mode. +; +%if TMPL_BITS == 16 +BS3_GLOBAL_NAME_EX TMPL_NM(Bs3SwitchToRM_Safe), function , 0 +%endif +BS3_PROC_BEGIN_MODE Bs3SwitchToRM, BS3_PBC_NEAR +%ifdef TMPL_RM + push ax + mov ax, BS3_SEL_DATA16 + mov ds, ax + mov es, ax + pop ax + ret + +%elif BS3_MODE_IS_V86(TMPL_MODE) + ; + ; V8086 - Switch to 16-bit ring-0 and call worker for that mode. + ; + extern BS3_CMN_NM(Bs3SwitchToRing0) + call BS3_CMN_NM(Bs3SwitchToRing0) + extern %[BS3_MODE_R0_NM_ %+ TMPL_MODE](Bs3SwitchToRM) + jmp %[BS3_MODE_R0_NM_ %+ TMPL_MODE](Bs3SwitchToRM) + +%else + ; + ; Protected mode. + ; 80286 requirements for PE16 clutters the code a little. + ; + %if TMPL_MODE == BS3_MODE_PE16 + cmp byte [BS3_DATA16_WRT(g_uBs3CpuDetected)], BS3CPU_80286 + ja .do_386_prologue + push ax + push bx + pushf + push word 1 + jmp .done_prologue + %endif +.do_386_prologue: + push sAX + push sBX + sPUSHF + %if TMPL_MODE == BS3_MODE_PE16 + push word 0 + %elif BS3_MODE_IS_64BIT_SYS(TMPL_MODE) + push sDX + push sCX + %endif +.done_prologue: + + ; + ; Get to 16-bit ring-0 and disable interrupts. + ; + extern BS3_CMN_NM(Bs3SwitchToRing0) + call BS3_CMN_NM(Bs3SwitchToRing0) + + cli + + %if TMPL_MODE == BS3_MODE_PE16 + ; + ; On 80286 we must reset the CPU to get back to real mode. + ; + CPU 286 + pop ax + push ax + test ax, ax + jz .is_386_or_better + + ; Save registers and flags, storing SS:SP in at a known global address. +%ifdef BS3_STRICT + mov ax, 0feedh + mov bx, 0faceh +%endif + push di + push si + push bp + push bx + push dx + push cx + push ax + pushf + + ; Convert ss:sp to real mode address. + BS3_EXTERN_CMN Bs3SelProtFar32ToFlat32 + mov ax, sp + push ss + push 0 + push ax + call Bs3SelProtFar32ToFlat32 + add sp, 6 + + mov [g_ResumeSp], ax + shl dx, 12 + mov [g_ResumeSs], dx + + ; Setup resume vector. + mov bx, BS3_SEL_R0_SS16 + mov es, bx + mov word [es:467h], .resume + mov word [es:467h+2], BS3_SEL_TEXT16 + + mov al, 0fh | 80h + out 70h, al ; set register index + in al, 80h + mov al, 0ah ; shutdown action command - no EOI, no 287 reset. + out 71h, al ; set cmos[f] = al - invoke testResume as early as possible. + in al, 71h ; flush + + %if 0 ; for testing in VM + CPU 386 + mov ax, BS3_SEL_R0_DS16 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + mov eax, cr0 + and ax, ~X86_CR0_PE + mov cr0, eax + jmp BS3_SEL_TEXT16:.resume + %endif + + ; Port A reset. (FYI: tripple fault does not do the trick) + in al, 92h + or al, 1 + out 92h, al + in al, 80h ; flush + mov cx, 0ffffh +.reset_delay: + loop .reset_delay + + ; Keyboard controller reset. + call Bs3KbdWait + push 0 ; zero data (whatever. + push 0fh ; KBD_CCMD_RESET + call Bs3KbdWrite +.forever: + jmp .forever + + ; This is the resume point. We should be in real mode now, at least in theory. +.resume: + mov ax, BS3_SEL_DATA16 + mov ds, ax + mov es, ax + mov ax, [g_ResumeSp] + mov ss, [g_ResumeSs] + mov sp, ax + + popf + pop ax + pop cx + pop dx + pop bx + pop bp + pop si + pop di + %ifdef BS3_STRICT + cmp ax, 0feedh + jne .bad_286_rm_switch + cmp bx, 0faceh + jne .bad_286_rm_switch + %endif + jmp .enter_mode + + %ifdef BS3_STRICT +.bad_286_rm_switch: + mov ax, 0e00h + 'Q' + mov bx, 0ff00h + int 10h + jmp .bad_286_rm_switch + %endif + + CPU 386 + %elif TMPL_BITS != 16 + ; + ; Must be in 16-bit segment when calling Bs3SwitchTo16Bit. + ; + jmp .sixteen_bit_segment wrt FLAT +BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .sixteen_bit_segment + + extern BS3_CMN_NM(Bs3SwitchTo16Bit) + call BS3_CMN_NM(Bs3SwitchTo16Bit) + BS3_SET_BITS 16 + %endif + ; + ; Before exiting to real mode we must load sensible selectors into the + ; segment registers so the hidden parts (which doesn't get reloaded in + ; real mode) are real mode compatible. + ; + ; ASSUMES BS3_SEL_R0_SS16 and BS3_SEL_R0_CS16 are both maxed out and + ; has no funny bits set! + ; +.is_386_or_better: +;; @todo Testcase: Experiment leaving weird stuff in the hidden segment registers. + mov ax, BS3_SEL_R0_DS16 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + ; + ; Exit to real mode. + ; + mov eax, cr0 + and eax, X86_CR0_NO_PE_NO_PG + mov cr0, eax + jmp CGROUP16:.reload_cs +.reload_cs: + + ; + ; Convert the stack (now 16-bit prot) to real mode. + ; + mov ax, BS3_SEL_SYSTEM16 + mov ds, ax + mov bx, ss + and bx, X86_SEL_MASK ; ASSUMES GDT stack selector + mov al, [bx + 4 + Bs3Gdt] + mov ah, [bx + 7 + Bs3Gdt] + add sp, [bx + 2 + Bs3Gdt] ; ASSUMES not expand down segment. + adc ax, 0 + %ifdef BS3_STRICT + test ax, 0fff0h + jz .stack_conv_ok + int3 +.stack_conv_ok: + %endif + shl ax, 12 + mov ss, ax + %if TMPL_BITS != 16 + and esp, 0ffffh + %endif + + %if BS3_MODE_IS_64BIT_SYS(TMPL_MODE) + ; + ; Clear the long mode enable bit. + ; + mov ecx, MSR_K6_EFER + rdmsr + and eax, ~MSR_K6_EFER_LME + wrmsr + %endif + + ; + ; Call routine for doing mode specific setups. + ; +.enter_mode: + extern NAME(Bs3EnteredMode_rm) + call NAME(Bs3EnteredMode_rm) + + %if TMPL_MODE == BS3_MODE_PE16 + pop ax + test ax, ax + jz .do_386_epilogue + popf + pop bx + pop ax + ret + %endif +.do_386_epilogue: + %if BS3_MODE_IS_64BIT_SYS(TMPL_MODE) + pop ecx +TONLY64 pop eax + pop edx +TONLY64 pop eax + %endif + popfd +TONLY64 pop eax + pop ebx +TONLY64 pop eax + pop eax +TONLY64 add sp, 4 + retn (TMPL_BITS - 16) / 8 + + %if TMPL_BITS != 16 +TMPL_BEGIN_TEXT + %endif +%endif +BS3_PROC_END_MODE Bs3SwitchToRM + + +%if TMPL_BITS == 16 +;; +; Custom far stub. +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_MODE Bs3SwitchToRM, BS3_PBC_FAR + inc bp + push bp + mov bp, sp + + ; Call the real thing. + call TMPL_NM(Bs3SwitchToRM) + + %if !BS3_MODE_IS_RM_OR_V86(TMPL_MODE) + ; Jmp to common code for the tedious conversion. + BS3_EXTERN_CMN Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn + jmp Bs3SwitchHlpConvProtModeRetfPopBpDecBpAndReturn + %else + pop bp + dec bp + retf + %endif +BS3_PROC_END_MODE Bs3SwitchToRM + +%else +;; +; Safe far return to non-BS3TEXT16 code. +BS3_EXTERN_CMN Bs3SelFlatCodeToRealMode +BS3_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_PROC_BEGIN_MODE Bs3SwitchToRM_Safe, BS3_PBC_NEAR + %if TMPL_BITS == 64 + push xAX + push xCX + sub xSP, 20h + + mov xCX, [xSP + xCB*2 + 20h] + call Bs3SelFlatCodeToRealMode ; well behaved assembly function, only clobbers ecx + mov [xSP + xCB*2 + 20h + 4], eax + + add xSP, 20h + pop xCX + pop xAX + add xSP, 4 + %else + xchg eax, [xSP] + push xAX + call Bs3SelFlatCodeToRealMode ; well behaved assembly function, only clobbers eax + add xSP, 4 + xchg [xSP], eax + %endif + call TMPL_NM(Bs3SwitchToRM) + BS3_SET_BITS 16 + retf +BS3_PROC_END_MODE Bs3SwitchToRM_Safe +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModes.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModes.c new file mode 100644 index 00000000..cf7ad7a5 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModes.c @@ -0,0 +1,380 @@ +/* $Id: bs3-mode-TestDoModes.c $ */ +/** @file + * BS3Kit - Bs3TestDoModes + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#if TMPL_MODE == BS3_MODE_RM +# define BS3_USE_RM_TEXT_SEG 1 /* Real mode version in RMTEXT16 segment to save space. */ +# include "bs3kit-template-header.h" +# include "bs3-cmn-test.h" +#else +# include "bs3kit-template-header.h" +# include "bs3-cmn-test.h" +#endif +#include "bs3-mode-TestDoModes.h" + + + +/** + * Warns about CPU modes that must be skipped. + * + * It will try not warn about modes for which there are no tests. + * + * @param paEntries The mode test entries. + * @param cEntries The number of tests. + * @param bCpuType The CPU type byte (see #BS3CPU_TYPE_MASK). + * @param fHavePae Whether the CPU has PAE. + * @param fHaveLongMode Whether the CPU does long mode. + */ +static void bs3TestWarnAboutSkippedModes(PCBS3TESTMODEENTRY paEntries, unsigned cEntries, + uint8_t bCpuType, bool fHavePae, bool fHaveLongMode) +{ + bool fComplained286 = false; + bool fComplained386 = false; + bool fComplainedPAE = false; + bool fComplainedAMD64 = false; + unsigned i; + + /* + * Complaint run. + */ + for (i = 0; i < cEntries; i++) + { + if ( !fComplained286 + && paEntries[i].pfnDoPE16) + { + if (bCpuType < BS3CPU_80286) + { + Bs3Printf("Only executing real-mode tests as no 80286+ CPU was detected.\n"); + break; + } + fComplained286 = true; + } + + if ( !fComplained386 + && ( paEntries[i].pfnDoPE16_32 + || paEntries[i].pfnDoPE16_V86 + || paEntries[i].pfnDoPE32 + || paEntries[i].pfnDoPE32_16 + || paEntries[i].pfnDoPEV86 + || paEntries[i].pfnDoPP16 + || paEntries[i].pfnDoPP16_32 + || paEntries[i].pfnDoPP16_V86 + || paEntries[i].pfnDoPP32 + || paEntries[i].pfnDoPP32_16 + || paEntries[i].pfnDoPPV86) ) + { + if (bCpuType < BS3CPU_80386) + { + Bs3Printf("80286 CPU: Only executing 16-bit protected and real mode tests.\n"); + break; + } + fComplained386 = true; + } + + if ( !fComplainedPAE + && ( paEntries[i].pfnDoPAE16 + || paEntries[i].pfnDoPAE16_32 + || paEntries[i].pfnDoPAE16_V86 + || paEntries[i].pfnDoPAE32 + || paEntries[i].pfnDoPAE32_16 + || paEntries[i].pfnDoPAEV86) ) + { + if (!fHavePae) + { + Bs3Printf("PAE and long mode tests will be skipped.\n"); + break; + } + fComplainedPAE = true; + } + + if ( !fComplainedAMD64 + && ( paEntries[i].pfnDoLM16 + || paEntries[i].pfnDoLM32 + || paEntries[i].pfnDoLM64) ) + { + if (!fHaveLongMode) + { + Bs3Printf("Long mode tests will be skipped.\n"); + break; + } + fComplainedAMD64 = true; + } + } +} + +#undef Bs3TestDoModes +BS3_MODE_DEF(void, Bs3TestDoModes,(PCBS3TESTMODEENTRY paEntries, size_t cEntries)) +{ + bool const fVerbose = true; + bool const fDoV86Modes = true; + bool const fDoWeirdV86Modes = true; + uint16_t const uCpuDetected = g_uBs3CpuDetected; + uint8_t const bCpuType = uCpuDetected & BS3CPU_TYPE_MASK; + bool const fHavePae = RT_BOOL(uCpuDetected & BS3CPU_F_PAE); + bool const fHaveLongMode = RT_BOOL(uCpuDetected & BS3CPU_F_LONG_MODE); + unsigned i; + +#if 1 /* debug. */ + Bs3Printf("Bs3TestDoModes: uCpuDetected=%#x fHavePae=%d fHaveLongMode=%d\n", uCpuDetected, fHavePae, fHaveLongMode); +#endif + bs3TestWarnAboutSkippedModes(paEntries, cEntries, bCpuType, fHavePae, fHaveLongMode); + + /* + * The real run. + */ + for (i = 0; i < cEntries; i++) + { + const char *pszFmtStr = "Error #%u (%#x) in %s!\n"; + bool fSkipped = true; + uint8_t bErrNo; + + if (paEntries[i].pszSubTest != NULL) + Bs3TestSub(paEntries[i].pszSubTest); + +#define PRE_DO_CALL(a_szModeName) do { if (fVerbose) Bs3TestPrintf("...%s\n", a_szModeName); } while (0) +#define CHECK_RESULT(a_szModeName) \ + do { \ + if (bErrNo != BS3TESTDOMODE_SKIPPED) \ + { \ + /*Bs3Printf("bErrNo=%#x %s\n", bErrNo, a_szModeName);*/ \ + fSkipped = false; \ + if (bErrNo != 0) \ + Bs3TestFailedF(pszFmtStr, bErrNo, bErrNo, a_szModeName); \ + } \ + } while (0) + + if (paEntries[i].pfnDoRM) + { + PRE_DO_CALL(g_szBs3ModeName_rm); + bErrNo = TMPL_NM(Bs3TestCallDoerInRM)(CONV_TO_RM_FAR16(paEntries[i].pfnDoRM)); + CHECK_RESULT(g_szBs3ModeName_rm); + } + + if (bCpuType < BS3CPU_80286) + { + if (fSkipped) + Bs3TestSkipped(NULL); + continue; + } + + /* + * Unpaged prot mode. + */ + if (paEntries[i].pfnDoPE16) + { + PRE_DO_CALL(g_szBs3ModeName_pe16); + bErrNo = TMPL_NM(Bs3TestCallDoerInPE16)(CONV_TO_PROT_FAR16(paEntries[i].pfnDoPE16)); + CHECK_RESULT(g_szBs3ModeName_pe16); + } + if (bCpuType < BS3CPU_80386) + { + if (fSkipped) + Bs3TestSkipped(NULL); + continue; + } + + if (paEntries[i].pfnDoPE16_32) + { + PRE_DO_CALL(g_szBs3ModeName_pe16_32); + bErrNo = TMPL_NM(Bs3TestCallDoerInPE16_32)(CONV_TO_FLAT(paEntries[i].pfnDoPE16_32), BS3_MODE_PE16_32); + CHECK_RESULT(g_szBs3ModeName_pe16_32); + } + + if (paEntries[i].pfnDoPE16_V86 && fDoWeirdV86Modes) + { + PRE_DO_CALL(g_szBs3ModeName_pe16_v86); + bErrNo = TMPL_NM(Bs3TestCallDoerInPE16_V86)(CONV_TO_RM_FAR16(paEntries[i].pfnDoPE16_V86)); + CHECK_RESULT(g_szBs3ModeName_pe16_v86); + } + + if (paEntries[i].pfnDoPE32) + { + PRE_DO_CALL(g_szBs3ModeName_pe32); + bErrNo = TMPL_NM(Bs3TestCallDoerInPE32)(CONV_TO_FLAT(paEntries[i].pfnDoPE32), BS3_MODE_PE32); + CHECK_RESULT(g_szBs3ModeName_pe32); + } + + if (paEntries[i].pfnDoPE32_16) + { + PRE_DO_CALL(g_szBs3ModeName_pe32_16); + bErrNo = TMPL_NM(Bs3TestCallDoerInPE32_16)(CONV_TO_PROT_FAR16(paEntries[i].pfnDoPE32_16)); + CHECK_RESULT(g_szBs3ModeName_pe32_16); + } + + if (paEntries[i].pfnDoPEV86 && fDoV86Modes) + { + PRE_DO_CALL(g_szBs3ModeName_pev86); + bErrNo = TMPL_NM(Bs3TestCallDoerInPEV86)(CONV_TO_RM_FAR16(paEntries[i].pfnDoPEV86)); + CHECK_RESULT(g_szBs3ModeName_pev86); + } + + /* + * Paged protected mode. + */ + if (paEntries[i].pfnDoPP16) + { + PRE_DO_CALL(g_szBs3ModeName_pp16); + bErrNo = TMPL_NM(Bs3TestCallDoerInPP16)(CONV_TO_PROT_FAR16(paEntries[i].pfnDoPP16)); + CHECK_RESULT(g_szBs3ModeName_pp16); + } + + if (paEntries[i].pfnDoPP16_32) + { + PRE_DO_CALL(g_szBs3ModeName_pp16_32); + bErrNo = TMPL_NM(Bs3TestCallDoerInPP16_32)(CONV_TO_FLAT(paEntries[i].pfnDoPP16_32), BS3_MODE_PP16_32); + CHECK_RESULT(g_szBs3ModeName_pp16_32); + } + + if (paEntries[i].pfnDoPP16_V86 && fDoWeirdV86Modes) + { + PRE_DO_CALL(g_szBs3ModeName_pp16_v86); + bErrNo = TMPL_NM(Bs3TestCallDoerInPP16_V86)(CONV_TO_RM_FAR16(paEntries[i].pfnDoPP16_V86)); + CHECK_RESULT(g_szBs3ModeName_pp16_v86); + } + + if (paEntries[i].pfnDoPP32) + { + PRE_DO_CALL(g_szBs3ModeName_pp32); + bErrNo = TMPL_NM(Bs3TestCallDoerInPP32)(CONV_TO_FLAT(paEntries[i].pfnDoPP32), BS3_MODE_PP32); + CHECK_RESULT(g_szBs3ModeName_pp32); + } + + if (paEntries[i].pfnDoPP32_16) + { + PRE_DO_CALL(g_szBs3ModeName_pp32_16); + bErrNo = TMPL_NM(Bs3TestCallDoerInPP32_16)(CONV_TO_PROT_FAR16(paEntries[i].pfnDoPP32_16)); + CHECK_RESULT(g_szBs3ModeName_pp32_16); + } + + if (paEntries[i].pfnDoPPV86 && fDoV86Modes) + { + PRE_DO_CALL(g_szBs3ModeName_ppv86); + bErrNo = TMPL_NM(Bs3TestCallDoerInPPV86)(CONV_TO_RM_FAR16(paEntries[i].pfnDoPPV86)); + CHECK_RESULT(g_szBs3ModeName_ppv86); + } + + /* + * Protected mode with PAE paging. + */ + if (!fHavePae) + { + if (fSkipped) + Bs3TestSkipped(NULL); + continue; + } + + if (paEntries[i].pfnDoPAE16) + { + PRE_DO_CALL(g_szBs3ModeName_pae16); + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE16)(CONV_TO_PROT_FAR16(paEntries[i].pfnDoPAE16)); + CHECK_RESULT(g_szBs3ModeName_pae16); + } + + if (paEntries[i].pfnDoPAE16_32) + { + PRE_DO_CALL(g_szBs3ModeName_pae16_32); + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE16_32)(CONV_TO_FLAT(paEntries[i].pfnDoPAE16_32), BS3_MODE_PAE16_32); + CHECK_RESULT(g_szBs3ModeName_pae16_32); + } + + if (paEntries[i].pfnDoPAE16_V86 && fDoWeirdV86Modes) + { + PRE_DO_CALL(g_szBs3ModeName_pae16_v86); + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE16_V86)(CONV_TO_RM_FAR16(paEntries[i].pfnDoPAE16_V86)); + CHECK_RESULT(g_szBs3ModeName_pae16_v86); + } + + if (paEntries[i].pfnDoPAE32) + { + PRE_DO_CALL(g_szBs3ModeName_pae32); + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE32)(CONV_TO_FLAT(paEntries[i].pfnDoPAE32), BS3_MODE_PAE32); + CHECK_RESULT(g_szBs3ModeName_pae32); + } + + if (paEntries[i].pfnDoPAE32_16) + { + PRE_DO_CALL(g_szBs3ModeName_pae32_16); + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE32_16)(CONV_TO_PROT_FAR16(paEntries[i].pfnDoPAE32_16)); + CHECK_RESULT(g_szBs3ModeName_pae32_16); + } + + if (paEntries[i].pfnDoPAEV86 && fDoV86Modes) + { + PRE_DO_CALL(g_szBs3ModeName_paev86); + bErrNo = TMPL_NM(Bs3TestCallDoerInPAEV86)(CONV_TO_RM_FAR16(paEntries[i].pfnDoPAEV86)); + CHECK_RESULT(g_szBs3ModeName_paev86); + } + + /* + * Long mode. + */ + if (!fHaveLongMode) + { + if (fSkipped) + Bs3TestSkipped(NULL); + continue; + } + + if (paEntries[i].pfnDoLM16) + { + PRE_DO_CALL(g_szBs3ModeName_lm16); + bErrNo = TMPL_NM(Bs3TestCallDoerInLM16)(CONV_TO_PROT_FAR16(paEntries[i].pfnDoLM16)); + CHECK_RESULT(g_szBs3ModeName_lm16); + } + + if (paEntries[i].pfnDoLM32) + { + PRE_DO_CALL(g_szBs3ModeName_lm32); + bErrNo = TMPL_NM(Bs3TestCallDoerInLM32)(CONV_TO_FLAT(paEntries[i].pfnDoLM32)); + CHECK_RESULT(g_szBs3ModeName_lm32); + } + + if (paEntries[i].pfnDoLM64) + { + PRE_DO_CALL(g_szBs3ModeName_lm64); + bErrNo = TMPL_NM(Bs3TestCallDoerInLM64)(CONV_TO_FLAT(paEntries[i].pfnDoLM64), BS3_MODE_LM64); + CHECK_RESULT(g_szBs3ModeName_lm64); + } + + if (fSkipped) + Bs3TestSkipped("skipped\n"); + } + Bs3TestSubDone(); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModes.h b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModes.h new file mode 100644 index 00000000..ed54eb26 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModes.h @@ -0,0 +1,99 @@ +/* $Id: bs3-mode-TestDoModes.h $ */ +/** @file + * BS3Kit - Common header for the Bs3TestDoModes family. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef BS3KIT_INCLUDED_bs3_mode_TestDoModes_h +#define BS3KIT_INCLUDED_bs3_mode_TestDoModes_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "bs3kit.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @def CONV_TO_FLAT + * Get flat address. In 16-bit the parameter is a real mode far address, while + * in 32-bit and 64-bit modes it is already flat. + */ +/** @def CONV_TO_PROT_FAR16 + * Get a 32-bit value that makes a protected mode far 16:16 address. + */ +/** @def CONV_TO_RM_FAR16 + * Get a 32-bit value that makes a real mode far 16:16 address. In 16-bit mode + * this is already what we've got, except must be converted to uint32_t. + */ +#if ARCH_BITS == 16 +# define CONV_TO_FLAT(a_fpfn) (((uint32_t)BS3_FP_SEG(a_fpfn) << 4) + BS3_FP_OFF(a_fpfn)) +# define CONV_TO_PROT_FAR16(a_fpfn) RT_MAKE_U32(BS3_FP_OFF(a_fpfn), Bs3SelRealModeCodeToProtMode(BS3_FP_SEG(a_fpfn))) +# define CONV_TO_RM_FAR16(a_fpfn) RT_MAKE_U32(BS3_FP_OFF(a_fpfn), BS3_FP_SEG(a_fpfn)) +#else +# define CONV_TO_FLAT(a_fpfn) ((uint32_t)(uintptr_t)(a_fpfn)) +# define CONV_TO_PROT_FAR16(a_fpfn) Bs3SelFlatCodeToProtFar16((uint32_t)(uintptr_t)(a_fpfn)) +# define CONV_TO_RM_FAR16(a_fpfn) Bs3SelFlatCodeToRealMode( (uint32_t)(uintptr_t)(a_fpfn)) +#endif + + +/********************************************************************************************************************************* +* Assembly Symbols * +*********************************************************************************************************************************/ +/* These are in the same code segment as the main API, so no FAR necessary. */ +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInRM)(uint32_t uCallbackFarPtr); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInPE16)(uint32_t uCallbackFarPtr); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInPE16_32)(uint32_t uFlatAddrCallback, uint8_t bMode); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInPE16_V86)(uint32_t uCallbackFarPtr); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInPE32)(uint32_t uFlatAddrCallback, uint8_t bMode); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInPE32_16)(uint32_t uCallbackFarPtr); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInPEV86)(uint32_t uCallbackFarPtr); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInPP16)(uint32_t uCallbackFarPtr); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInPP16_32)(uint32_t uFlatAddrCallback, uint8_t bMode); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInPP16_V86)(uint32_t uCallbackFarPtr); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInPP32)(uint32_t uFlatAddrCallback, uint8_t bMode); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInPP32_16)(uint32_t uCallbackFarPtr); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInPPV86)(uint32_t uCallbackFarPtr); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInPAE16)(uint32_t uCallbackFarPtr); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInPAE16_32)(uint32_t uFlatAddrCallback, uint8_t bMode); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInPAE16_V86)(uint32_t uCallbackFarPtr); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInPAE32)(uint32_t uFlatAddrCallback, uint8_t bMode); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInPAE32_16)(uint32_t uCallbackFarPtr); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInPAEV86)(uint32_t uCallbackFarPtr); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInLM16)(uint32_t uCallbackFarPtr); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInLM32)(uint32_t uFlatAddrCallback); +BS3_DECL_NEAR(uint8_t) TMPL_NM(Bs3TestCallDoerInLM64)(uint32_t uFlatAddrCallback, uint8_t bMode); + +#endif /* !BS3KIT_INCLUDED_bs3_mode_TestDoModes_h */ + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModesByMax.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModesByMax.c new file mode 100644 index 00000000..7666f2f6 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModesByMax.c @@ -0,0 +1,380 @@ +/* $Id: bs3-mode-TestDoModesByMax.c $ */ +/** @file + * BS3Kit - Bs3TestDoModesByMax + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#if TMPL_MODE == BS3_MODE_RM +# define BS3_USE_RM_TEXT_SEG 1 /* Real mode version in RMTEXT16 segment to save space. */ +# include "bs3kit-template-header.h" +# include "bs3-cmn-test.h" +#else +# include "bs3kit-template-header.h" +# include "bs3-cmn-test.h" +#endif +#include "bs3-mode-TestDoModes.h" + + + +/** + * Warns about CPU modes that must be skipped. + * + * It will try not warn about modes for which there are no tests. + * + * @param paEntries The mode test entries. + * @param cEntries The number of tests. + * @param bCpuType The CPU type byte (see #BS3CPU_TYPE_MASK). + * @param fHavePae Whether the CPU has PAE. + * @param fHaveLongMode Whether the CPU does long mode. + */ +static void bs3TestWarnAboutSkippedModes(PCBS3TESTMODEBYMAXENTRY paEntries, unsigned cEntries, + uint8_t bCpuType, bool fHavePae, bool fHaveLongMode) +{ + bool fComplained286 = false; + bool fComplained386 = false; + bool fComplainedPAE = false; + bool fComplainedAMD64 = false; + unsigned i; + + /* + * Complaint run. + */ + for (i = 0; i < cEntries; i++) + { + if ( !fComplained286 + && paEntries[i].pfnDoPE16) + { + if (bCpuType < BS3CPU_80286) + { + Bs3Printf("Only executing real-mode tests as no 80286+ CPU was detected.\n"); + break; + } + fComplained286 = true; + } + + if ( !fComplained386 + && ( paEntries[i].fDoPE16_32 + || paEntries[i].fDoPE16_V86 + || paEntries[i].fDoPE32 + || paEntries[i].fDoPE32_16 + || paEntries[i].fDoPEV86 + || paEntries[i].fDoPP16 + || paEntries[i].fDoPP16_32 + || paEntries[i].fDoPP16_V86 + || paEntries[i].fDoPP32 + || paEntries[i].fDoPP32_16 + || paEntries[i].fDoPPV86) ) + { + if (bCpuType < BS3CPU_80386) + { + Bs3Printf("80286 CPU: Only executing 16-bit protected and real mode tests.\n"); + break; + } + fComplained386 = true; + } + + if ( !fComplainedPAE + && ( paEntries[i].fDoPAE16 + || paEntries[i].fDoPAE16_32 + || paEntries[i].fDoPAE16_V86 + || paEntries[i].fDoPAE32 + || paEntries[i].fDoPAE32_16 + || paEntries[i].fDoPAEV86) ) + { + if (!fHavePae) + { + Bs3Printf("PAE and long mode tests will be skipped.\n"); + break; + } + fComplainedPAE = true; + } + + if ( !fComplainedAMD64 + && ( paEntries[i].fDoLM16 + || paEntries[i].fDoLM32 + || paEntries[i].fDoLM64) ) + { + if (!fHaveLongMode) + { + Bs3Printf("Long mode tests will be skipped.\n"); + break; + } + fComplainedAMD64 = true; + } + } +} + +#undef Bs3TestDoModesByMax +BS3_MODE_DEF(void, Bs3TestDoModesByMax,(PCBS3TESTMODEBYMAXENTRY paEntries, size_t cEntries)) +{ + bool const fVerbose = true; + bool const fDoV86Modes = true; + bool const fDoWeirdV86Modes = true; + uint16_t const uCpuDetected = g_uBs3CpuDetected; + uint8_t const bCpuType = uCpuDetected & BS3CPU_TYPE_MASK; + bool const fHavePae = RT_BOOL(uCpuDetected & BS3CPU_F_PAE); + bool const fHaveLongMode = RT_BOOL(uCpuDetected & BS3CPU_F_LONG_MODE); + unsigned i; + +#if 1 /* debug. */ + Bs3Printf("Bs3TestDoModes: uCpuDetected=%#x fHavePae=%d fHaveLongMode=%d\n", uCpuDetected, fHavePae, fHaveLongMode); +#endif + bs3TestWarnAboutSkippedModes(paEntries, cEntries, bCpuType, fHavePae, fHaveLongMode); + + /* + * The real run. + */ + for (i = 0; i < cEntries; i++) + { + const char *pszFmtStr = "Error #%u (%#x) in %s!\n"; + bool fSkipped = true; + uint8_t bErrNo; + + if (paEntries[i].pszSubTest != NULL) + Bs3TestSub(paEntries[i].pszSubTest); + +#define PRE_DO_CALL(a_szModeName) do { if (fVerbose) Bs3TestPrintf("...%s\n", a_szModeName); } while (0) +#define CHECK_RESULT(a_szModeName) \ + do { \ + if (bErrNo != BS3TESTDOMODE_SKIPPED) \ + { \ + /*Bs3Printf("bErrNo=%#x %s\n", bErrNo, a_szModeName);*/ \ + fSkipped = false; \ + if (bErrNo != 0) \ + Bs3TestFailedF(pszFmtStr, bErrNo, bErrNo, a_szModeName); \ + } \ + } while (0) + + if (paEntries[i].fDoRM) + { + PRE_DO_CALL(g_szBs3ModeName_rm); + bErrNo = TMPL_NM(Bs3TestCallDoerInRM)(CONV_TO_RM_FAR16(paEntries[i].pfnDoRM)); + CHECK_RESULT(g_szBs3ModeName_rm); + } + + if (bCpuType < BS3CPU_80286) + { + if (fSkipped) + Bs3TestSkipped(NULL); + continue; + } + + /* + * Unpaged prot mode. + */ + if (paEntries[i].fDoPE16) + { + PRE_DO_CALL(g_szBs3ModeName_pe16); + bErrNo = TMPL_NM(Bs3TestCallDoerInPE16)(CONV_TO_PROT_FAR16(paEntries[i].pfnDoPE16)); + CHECK_RESULT(g_szBs3ModeName_pe16); + } + if (bCpuType < BS3CPU_80386) + { + if (fSkipped) + Bs3TestSkipped(NULL); + continue; + } + + if (paEntries[i].fDoPE16_32) + { + PRE_DO_CALL(g_szBs3ModeName_pe16_32); + bErrNo = TMPL_NM(Bs3TestCallDoerInPE16_32)(CONV_TO_FLAT(paEntries[i].pfnDoPE16_32), BS3_MODE_PE16_32); + CHECK_RESULT(g_szBs3ModeName_pe16_32); + } + + if (paEntries[i].fDoPE16_V86 && fDoWeirdV86Modes) + { + PRE_DO_CALL(g_szBs3ModeName_pe16_v86); + bErrNo = TMPL_NM(Bs3TestCallDoerInPE16_32)(CONV_TO_FLAT(paEntries[i].pfnDoPE16_32), BS3_MODE_PE16_V86); + CHECK_RESULT(g_szBs3ModeName_pe16_v86); + } + + if (paEntries[i].fDoPE32) + { + PRE_DO_CALL(g_szBs3ModeName_pe32); + bErrNo = TMPL_NM(Bs3TestCallDoerInPE32)(CONV_TO_FLAT(paEntries[i].pfnDoPE32), BS3_MODE_PE32); + CHECK_RESULT(g_szBs3ModeName_pe32); + } + + if (paEntries[i].fDoPE32_16) + { + PRE_DO_CALL(g_szBs3ModeName_pe32_16); + bErrNo = TMPL_NM(Bs3TestCallDoerInPE32)(CONV_TO_FLAT(paEntries[i].pfnDoPE32), BS3_MODE_PE32_16); + CHECK_RESULT(g_szBs3ModeName_pe32_16); + } + + if (paEntries[i].fDoPEV86 && fDoV86Modes) + { + PRE_DO_CALL(g_szBs3ModeName_pev86); + bErrNo = TMPL_NM(Bs3TestCallDoerInPE32)(CONV_TO_FLAT(paEntries[i].pfnDoPE32), BS3_MODE_PEV86); + CHECK_RESULT(g_szBs3ModeName_pev86); + } + + /* + * Paged protected mode. + */ + if (paEntries[i].fDoPP16) + { + PRE_DO_CALL(g_szBs3ModeName_pp16); + bErrNo = TMPL_NM(Bs3TestCallDoerInPP16_32)(CONV_TO_FLAT(paEntries[i].pfnDoPP16_32), BS3_MODE_PP16); + CHECK_RESULT(g_szBs3ModeName_pp16); + } + + if (paEntries[i].fDoPP16_32) + { + PRE_DO_CALL(g_szBs3ModeName_pp16_32); + bErrNo = TMPL_NM(Bs3TestCallDoerInPP16_32)(CONV_TO_FLAT(paEntries[i].pfnDoPP16_32), BS3_MODE_PP16_32); + CHECK_RESULT(g_szBs3ModeName_pp16_32); + } + + if (paEntries[i].fDoPP16_V86 && fDoWeirdV86Modes) + { + PRE_DO_CALL(g_szBs3ModeName_pp16_v86); + bErrNo = TMPL_NM(Bs3TestCallDoerInPP16_32)(CONV_TO_FLAT(paEntries[i].pfnDoPP16_32), BS3_MODE_PP16_V86); + CHECK_RESULT(g_szBs3ModeName_pp16_v86); + } + + if (paEntries[i].fDoPP32) + { + PRE_DO_CALL(g_szBs3ModeName_pp32); + bErrNo = TMPL_NM(Bs3TestCallDoerInPP32)(CONV_TO_FLAT(paEntries[i].pfnDoPP32), BS3_MODE_PP32); + CHECK_RESULT(g_szBs3ModeName_pp32); + } + + if (paEntries[i].fDoPP32_16) + { + PRE_DO_CALL(g_szBs3ModeName_pp32_16); + bErrNo = TMPL_NM(Bs3TestCallDoerInPP32)(CONV_TO_FLAT(paEntries[i].pfnDoPP32), BS3_MODE_PP32_16); + CHECK_RESULT(g_szBs3ModeName_pp32_16); + } + + if (paEntries[i].fDoPPV86 && fDoV86Modes) + { + PRE_DO_CALL(g_szBs3ModeName_ppv86); + bErrNo = TMPL_NM(Bs3TestCallDoerInPP32)(CONV_TO_FLAT(paEntries[i].pfnDoPP32), BS3_MODE_PPV86); + CHECK_RESULT(g_szBs3ModeName_ppv86); + } + + /* + * Protected mode with PAE paging. + */ + if (!fHavePae) + { + if (fSkipped) + Bs3TestSkipped(NULL); + continue; + } + + if (paEntries[i].fDoPAE16) + { + PRE_DO_CALL(g_szBs3ModeName_pae16); + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE16_32)(CONV_TO_FLAT(paEntries[i].pfnDoPAE16_32), BS3_MODE_PAE16); + CHECK_RESULT(g_szBs3ModeName_pae16); + } + + if (paEntries[i].fDoPAE16_32) + { + PRE_DO_CALL(g_szBs3ModeName_pae16_32); + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE16_32)(CONV_TO_FLAT(paEntries[i].pfnDoPAE16_32), BS3_MODE_PAE16_32); + CHECK_RESULT(g_szBs3ModeName_pae16_32); + } + + if (paEntries[i].fDoPAE16_V86 && fDoWeirdV86Modes) + { + PRE_DO_CALL(g_szBs3ModeName_pae16_v86); + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE16_32)(CONV_TO_FLAT(paEntries[i].pfnDoPAE16_32), BS3_MODE_PAE16_V86); + CHECK_RESULT(g_szBs3ModeName_pae16_v86); + } + + if (paEntries[i].fDoPAE32) + { + PRE_DO_CALL(g_szBs3ModeName_pae32); + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE32)(CONV_TO_FLAT(paEntries[i].pfnDoPAE32), BS3_MODE_PAE32); + CHECK_RESULT(g_szBs3ModeName_pae32); + } + + if (paEntries[i].fDoPAE32_16) + { + PRE_DO_CALL(g_szBs3ModeName_pae32_16); + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE32)(CONV_TO_FLAT(paEntries[i].pfnDoPAE32), BS3_MODE_PAE32_16); + CHECK_RESULT(g_szBs3ModeName_pae32_16); + } + + if (paEntries[i].fDoPAEV86 && fDoV86Modes) + { + PRE_DO_CALL(g_szBs3ModeName_paev86); + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE32)(CONV_TO_FLAT(paEntries[i].pfnDoPAE32), BS3_MODE_PAEV86); + CHECK_RESULT(g_szBs3ModeName_paev86); + } + + /* + * Long mode. + */ + if (!fHaveLongMode) + { + if (fSkipped) + Bs3TestSkipped(NULL); + continue; + } + + if (paEntries[i].fDoLM16) + { + PRE_DO_CALL(g_szBs3ModeName_lm16); + bErrNo = TMPL_NM(Bs3TestCallDoerInLM64)(CONV_TO_FLAT(paEntries[i].pfnDoLM64), BS3_MODE_LM16); + CHECK_RESULT(g_szBs3ModeName_lm16); + } + + if (paEntries[i].fDoLM32) + { + PRE_DO_CALL(g_szBs3ModeName_lm32); + bErrNo = TMPL_NM(Bs3TestCallDoerInLM64)(CONV_TO_FLAT(paEntries[i].pfnDoLM64), BS3_MODE_LM32); + CHECK_RESULT(g_szBs3ModeName_lm32); + } + + if (paEntries[i].fDoLM64) + { + PRE_DO_CALL(g_szBs3ModeName_lm64); + bErrNo = TMPL_NM(Bs3TestCallDoerInLM64)(CONV_TO_FLAT(paEntries[i].pfnDoLM64), BS3_MODE_LM64); + CHECK_RESULT(g_szBs3ModeName_lm64); + } + + if (fSkipped) + Bs3TestSkipped("skipped\n"); + } + Bs3TestSubDone(); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModesByMaxStub.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModesByMaxStub.asm new file mode 100644 index 00000000..d0fcc71c --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModesByMaxStub.asm @@ -0,0 +1,63 @@ +; $Id: bs3-mode-TestDoModesByMaxStub.asm $ +;; @file +; BS3Kit - Bs3TestDoModesByMax near stub. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + +; +; Near stub for the API call (16-bit only). +; +%if TMPL_BITS == 16 + %if TMPL_MODE == BS3_MODE_RM +BS3_BEGIN_RMTEXT16 + %endif +BS3_BEGIN_TEXT16_NEARSTUBS +BS3_PROC_BEGIN_MODE Bs3TestDoModesByMax, BS3_PBC_NEAR + pop ax + push cs + push ax + %if TMPL_MODE == BS3_MODE_RM + extern TMPL_FAR_NM(Bs3TestDoModesByMax):wrt BS3GROUPRMTEXT16 + jmp far TMPL_FAR_NM(Bs3TestDoModesByMax) + %else + extern TMPL_FAR_NM(Bs3TestDoModesByMax):wrt CGROUP16 + jmp TMPL_NM(Bs3TestDoModesByMax) + %endif +BS3_PROC_END_MODE Bs3TestDoModesByMax +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModesByOne.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModesByOne.c new file mode 100644 index 00000000..55112ddc --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModesByOne.c @@ -0,0 +1,424 @@ +/* $Id: bs3-mode-TestDoModesByOne.c $ */ +/** @file + * BS3Kit - Bs3TestDoModesByOne + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#if TMPL_MODE == BS3_MODE_RM +# define BS3_USE_RM_TEXT_SEG 1 /* Real mode version in RMTEXT16 segment to save space. */ +# include "bs3kit-template-header.h" +# include "bs3-cmn-test.h" +#else +# include "bs3kit-template-header.h" +# include "bs3-cmn-test.h" +#endif +#include "bs3-mode-TestDoModes.h" + + +/********************************************************************************************************************************* +* Assembly Symbols * +*********************************************************************************************************************************/ +/* Assembly helpers for switching to the work bitcount and calling it. */ +BS3_DECL_FAR(uint8_t) Bs3TestCallDoerTo16_f16(uint8_t bMode); +BS3_DECL_FAR(uint8_t) Bs3TestCallDoerTo16_c32(uint8_t bMode); +BS3_DECL_FAR(uint8_t) Bs3TestCallDoerTo16_c64(uint8_t bMode); +BS3_DECL_FAR(uint8_t) Bs3TestCallDoerTo32_f16(uint8_t bMode); +BS3_DECL_FAR(uint8_t) Bs3TestCallDoerTo32_c32(uint8_t bMode); +BS3_DECL_FAR(uint8_t) Bs3TestCallDoerTo32_c64(uint8_t bMode); +BS3_DECL_FAR(uint8_t) Bs3TestCallDoerTo64_f16(uint8_t bMode); +BS3_DECL_FAR(uint8_t) Bs3TestCallDoerTo64_c32(uint8_t bMode); +BS3_DECL_FAR(uint8_t) Bs3TestCallDoerTo64_c64(uint8_t bMode); + + +/** The current worker function, picked up by our assembly helpers. */ +#ifndef DOXYGEN_RUNNING +# define g_pfnBs3TestDoModesByOneCurrent BS3_CMN_NM(g_pfnBs3TestDoModesByOneCurrent) +#endif +extern PFNBS3TESTDOMODE g_pfnBs3TestDoModesByOneCurrent; + +#include <iprt/asm-amd64-x86.h> + + +#undef Bs3TestDoModesByOne +BS3_MODE_DEF(void, Bs3TestDoModesByOne,(PCBS3TESTMODEBYONEENTRY paEntries, size_t cEntries, uint32_t fFlags)) +{ + bool const fVerbose = true; + bool const fDoV86Modes = true; + bool const fDoWeirdV86Modes = true; + uint16_t const uCpuDetected = g_uBs3CpuDetected; + uint8_t const bCpuType = uCpuDetected & BS3CPU_TYPE_MASK; + bool const fHavePae = RT_BOOL(uCpuDetected & BS3CPU_F_PAE); + bool const fHaveLongMode = RT_BOOL(uCpuDetected & BS3CPU_F_LONG_MODE); + unsigned i; + +#if 1 /* debug. */ + Bs3Printf("Bs3TestDoModesByOne: uCpuDetected=%#x fHavePae=%d fHaveLongMode=%d\n", uCpuDetected, fHavePae, fHaveLongMode); +#endif + + /* + * Inform about modes we won't test (if any). + */ + if (bCpuType < BS3CPU_80286) + Bs3Printf("Only executing real-mode tests as no 80286+ CPU was detected.\n"); + else if (bCpuType < BS3CPU_80386) + Bs3Printf("80286 CPU: Only executing 16-bit protected and real mode tests.\n"); + else if (!fHavePae) + Bs3Printf("PAE and long mode tests will be skipped.\n"); + else if (!fHaveLongMode) + Bs3Printf("Long mode tests will be skipped.\n"); +#if ARCH_BITS != 16 + Bs3Printf("Real-mode tests will be skipped.\n"); +#endif + + /* + * The real run. + */ + for (i = 0; i < cEntries; i++) + { + const char *pszFmtStr = "Error #%u (%#x) in %s!\n"; + bool fSkipped = true; + bool const fOnlyPaging = RT_BOOL((paEntries[i].fFlags | fFlags) & BS3TESTMODEBYONEENTRY_F_ONLY_PAGING); + bool const fMinimal = RT_BOOL((paEntries[i].fFlags | fFlags) & BS3TESTMODEBYONEENTRY_F_MINIMAL); + bool const fCurDoV86Modes = fDoV86Modes && !fMinimal; + bool const fCurDoWeirdV86Modes = fDoWeirdV86Modes && fCurDoV86Modes; + uint8_t bErrNo; + Bs3TestSub(paEntries[i].pszSubTest); + +#define PRE_DO_CALL(a_szModeName) do { if (fVerbose) Bs3TestPrintf("...%s\n", a_szModeName); } while (0) +#define CHECK_RESULT(a_szModeName) \ + do { \ + if (bErrNo != BS3TESTDOMODE_SKIPPED) \ + { \ + /*Bs3Printf("bErrNo=%#x %s\n", bErrNo, a_szModeName);*/ \ + fSkipped = false; \ + if (bErrNo != 0) \ + Bs3TestFailedF(pszFmtStr, bErrNo, bErrNo, a_szModeName); \ + } \ + } while (0) + + g_pfnBs3TestDoModesByOneCurrent = paEntries[i].pfnWorker; + +#if ARCH_BITS != 64 + +# if ARCH_BITS == 16 + if (!fOnlyPaging) + { + PRE_DO_CALL(g_szBs3ModeName_rm); + bErrNo = TMPL_NM(Bs3TestCallDoerInRM)(CONV_TO_RM_FAR16(paEntries[i].pfnWorker)); + CHECK_RESULT(g_szBs3ModeName_rm); + } +# else + if (!fOnlyPaging && (paEntries[i].fFlags | fFlags) & BS3TESTMODEBYONEENTRY_F_REAL_MODE_READY) + { + PRE_DO_CALL(g_szBs3ModeName_rm); + bErrNo = TMPL_NM(Bs3TestCallDoerInPE32)(CONV_TO_FLAT(paEntries[i].pfnWorker), BS3_MODE_RM); + CHECK_RESULT(g_szBs3ModeName_rm); + } +# endif + + if (bCpuType < BS3CPU_80286) + { + if (fSkipped) + Bs3TestSkipped(NULL); + continue; + } + + /* + * Unpaged prot mode. + */ + if (!fOnlyPaging && (!fMinimal || bCpuType < BS3CPU_80386)) + { + PRE_DO_CALL(g_szBs3ModeName_pe16); +# if ARCH_BITS == 16 + bErrNo = TMPL_NM(Bs3TestCallDoerInPE16)(CONV_TO_PROT_FAR16(paEntries[i].pfnWorker)); +# else + bErrNo = TMPL_NM(Bs3TestCallDoerInPE16)(CONV_TO_PROT_FAR16(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_f16))); +# endif + CHECK_RESULT(g_szBs3ModeName_pe16); + } + if (bCpuType < BS3CPU_80386) + { + if (fSkipped) + Bs3TestSkipped(NULL); + continue; + } + + if (!fOnlyPaging) + { + PRE_DO_CALL(g_szBs3ModeName_pe16_32); +# if ARCH_BITS == 32 + bErrNo = TMPL_NM(Bs3TestCallDoerInPE16_32)(CONV_TO_FLAT(paEntries[i].pfnWorker), BS3_MODE_PE16_32); +# else + bErrNo = TMPL_NM(Bs3TestCallDoerInPE16_32)(CONV_TO_FLAT(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_c32)), BS3_MODE_PE16_32); +# endif + CHECK_RESULT(g_szBs3ModeName_pe16_32); + } + + if (fCurDoWeirdV86Modes && !fOnlyPaging) + { + PRE_DO_CALL(g_szBs3ModeName_pe16_v86); +# if ARCH_BITS == 16 + bErrNo = TMPL_NM(Bs3TestCallDoerInPE16_V86)(CONV_TO_RM_FAR16(paEntries[i].pfnWorker)); +# else + bErrNo = TMPL_NM(Bs3TestCallDoerInPE16_V86)(CONV_TO_RM_FAR16(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_f16))); +# endif + CHECK_RESULT(g_szBs3ModeName_pe16_v86); + } + + if (!fOnlyPaging) + { + PRE_DO_CALL(g_szBs3ModeName_pe32); +# if ARCH_BITS == 32 + bErrNo = TMPL_NM(Bs3TestCallDoerInPE32)(CONV_TO_FLAT(paEntries[i].pfnWorker), BS3_MODE_PE32); +# else + bErrNo = TMPL_NM(Bs3TestCallDoerInPE32)(CONV_TO_FLAT(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_c32)), BS3_MODE_PE32); +# endif + CHECK_RESULT(g_szBs3ModeName_pe32); + } + + if (!fOnlyPaging && !fMinimal) + { + PRE_DO_CALL(g_szBs3ModeName_pe32_16); +# if ARCH_BITS == 16 + bErrNo = TMPL_NM(Bs3TestCallDoerInPE32_16)(CONV_TO_PROT_FAR16(paEntries[i].pfnWorker)); +# else + bErrNo = TMPL_NM(Bs3TestCallDoerInPE32_16)(CONV_TO_PROT_FAR16(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_f16))); +# endif + CHECK_RESULT(g_szBs3ModeName_pe32_16); + } + + if (fCurDoV86Modes && !fOnlyPaging) + { + PRE_DO_CALL(g_szBs3ModeName_pev86); +# if ARCH_BITS == 16 + bErrNo = TMPL_NM(Bs3TestCallDoerInPEV86)(CONV_TO_RM_FAR16(paEntries[i].pfnWorker)); +# else + bErrNo = TMPL_NM(Bs3TestCallDoerInPEV86)(CONV_TO_RM_FAR16(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_f16))); +# endif + CHECK_RESULT(g_szBs3ModeName_pev86); + } + + /* + * Paged protected mode. + */ + if (!fMinimal) + { + PRE_DO_CALL(g_szBs3ModeName_pp16); +# if ARCH_BITS == 16 + bErrNo = TMPL_NM(Bs3TestCallDoerInPP16)(CONV_TO_PROT_FAR16(paEntries[i].pfnWorker)); +# else + bErrNo = TMPL_NM(Bs3TestCallDoerInPP16)(CONV_TO_PROT_FAR16(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_f16))); +# endif + CHECK_RESULT(g_szBs3ModeName_pp16); + } + + if (!fMinimal) + { + PRE_DO_CALL(g_szBs3ModeName_pp16_32); +# if ARCH_BITS == 32 + bErrNo = TMPL_NM(Bs3TestCallDoerInPP16_32)(CONV_TO_FLAT(paEntries[i].pfnWorker), BS3_MODE_PP16_32); +# else + bErrNo = TMPL_NM(Bs3TestCallDoerInPP16_32)(CONV_TO_FLAT(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_c32)), BS3_MODE_PP16_32); +# endif + CHECK_RESULT(g_szBs3ModeName_pp16_32); + } + + if (fCurDoWeirdV86Modes) + { + PRE_DO_CALL(g_szBs3ModeName_pp16_v86); +# if ARCH_BITS == 16 + bErrNo = TMPL_NM(Bs3TestCallDoerInPP16_V86)(CONV_TO_RM_FAR16(paEntries[i].pfnWorker)); +# else + bErrNo = TMPL_NM(Bs3TestCallDoerInPP16_V86)(CONV_TO_RM_FAR16(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_f16))); +# endif + CHECK_RESULT(g_szBs3ModeName_pp16_v86); + } + + if (true) + { + PRE_DO_CALL(g_szBs3ModeName_pp32); +# if ARCH_BITS == 32 + bErrNo = TMPL_NM(Bs3TestCallDoerInPP32)(CONV_TO_FLAT(paEntries[i].pfnWorker), BS3_MODE_PP32); +# else + bErrNo = TMPL_NM(Bs3TestCallDoerInPP32)(CONV_TO_FLAT(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_c32)), BS3_MODE_PP32); +# endif + CHECK_RESULT(g_szBs3ModeName_pp32); + } + + if (!fMinimal) + { + PRE_DO_CALL(g_szBs3ModeName_pp32_16); +# if ARCH_BITS == 16 + bErrNo = TMPL_NM(Bs3TestCallDoerInPP32_16)(CONV_TO_PROT_FAR16(paEntries[i].pfnWorker)); +# else + bErrNo = TMPL_NM(Bs3TestCallDoerInPP32_16)(CONV_TO_PROT_FAR16(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_f16))); +# endif + CHECK_RESULT(g_szBs3ModeName_pp32_16); + } + + if (fCurDoV86Modes) + { + PRE_DO_CALL(g_szBs3ModeName_ppv86); +# if ARCH_BITS == 16 + bErrNo = TMPL_NM(Bs3TestCallDoerInPPV86)(CONV_TO_RM_FAR16(paEntries[i].pfnWorker)); +# else + bErrNo = TMPL_NM(Bs3TestCallDoerInPPV86)(CONV_TO_RM_FAR16(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_f16))); +# endif + CHECK_RESULT(g_szBs3ModeName_ppv86); + } + + + /* + * Protected mode with PAE paging. + */ + if (!fMinimal) + { + PRE_DO_CALL(g_szBs3ModeName_pae16); +# if ARCH_BITS == 16 + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE16)(CONV_TO_PROT_FAR16(paEntries[i].pfnWorker)); +# else + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE16)(CONV_TO_PROT_FAR16(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_f16))); +# endif + CHECK_RESULT(g_szBs3ModeName_pae16); + } + + if (!fMinimal) + { + PRE_DO_CALL(g_szBs3ModeName_pae16_32); +# if ARCH_BITS == 32 + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE16_32)(CONV_TO_FLAT(paEntries[i].pfnWorker), BS3_MODE_PAE16_32); +# else + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE16_32)(CONV_TO_FLAT(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_c32)), BS3_MODE_PAE16_32); +# endif + CHECK_RESULT(g_szBs3ModeName_pae16_32); + } + + if (fCurDoWeirdV86Modes) + { + PRE_DO_CALL(g_szBs3ModeName_pae16_v86); +# if ARCH_BITS == 16 + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE16_V86)(CONV_TO_RM_FAR16(paEntries[i].pfnWorker)); +# else + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE16_V86)(CONV_TO_RM_FAR16(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_f16))); +# endif + CHECK_RESULT(g_szBs3ModeName_pae16_v86); + } + + if (true) + { + PRE_DO_CALL(g_szBs3ModeName_pae32); +# if ARCH_BITS == 32 + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE32)(CONV_TO_FLAT(paEntries[i].pfnWorker), BS3_MODE_PAE32); +# else + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE32)(CONV_TO_FLAT(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_c32)), BS3_MODE_PAE32); +# endif + CHECK_RESULT(g_szBs3ModeName_pae32); + } + + if (!fMinimal) + { + PRE_DO_CALL(g_szBs3ModeName_pae32_16); +# if ARCH_BITS == 16 + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE32_16)(CONV_TO_PROT_FAR16(paEntries[i].pfnWorker)); +# else + bErrNo = TMPL_NM(Bs3TestCallDoerInPAE32_16)(CONV_TO_PROT_FAR16(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_f16))); +# endif + CHECK_RESULT(g_szBs3ModeName_pae32_16); + } + + if (fCurDoV86Modes) + { + PRE_DO_CALL(g_szBs3ModeName_paev86); +# if ARCH_BITS == 16 + bErrNo = TMPL_NM(Bs3TestCallDoerInPAEV86)(CONV_TO_RM_FAR16(paEntries[i].pfnWorker)); +# else + bErrNo = TMPL_NM(Bs3TestCallDoerInPAEV86)(CONV_TO_RM_FAR16(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_f16))); +# endif + CHECK_RESULT(g_szBs3ModeName_paev86); + } + +#endif /* ARCH_BITS != 64 */ + + /* + * Long mode. + */ + if (!fHaveLongMode) + { + if (fSkipped) + Bs3TestSkipped(NULL); + continue; + } + + if (!fMinimal) + { + PRE_DO_CALL(g_szBs3ModeName_lm16); +#if ARCH_BITS == 16 + bErrNo = TMPL_NM(Bs3TestCallDoerInLM16)(CONV_TO_PROT_FAR16(paEntries[i].pfnWorker)); +#else + bErrNo = TMPL_NM(Bs3TestCallDoerInLM16)(CONV_TO_PROT_FAR16(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_f16))); +#endif + CHECK_RESULT(g_szBs3ModeName_lm16); + } + + if (!fMinimal) + { + PRE_DO_CALL(g_szBs3ModeName_lm32); +#if ARCH_BITS == 32 + bErrNo = TMPL_NM(Bs3TestCallDoerInLM32)(CONV_TO_FLAT(paEntries[i].pfnWorker)); +#else + bErrNo = TMPL_NM(Bs3TestCallDoerInLM32)(CONV_TO_FLAT(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_c32))); +#endif + CHECK_RESULT(g_szBs3ModeName_lm32); + } + + if (true) + { + PRE_DO_CALL(g_szBs3ModeName_lm64); +#if ARCH_BITS == 64 + bErrNo = TMPL_NM(Bs3TestCallDoerInLM64)(CONV_TO_FLAT(paEntries[i].pfnWorker), BS3_MODE_LM64); +#else + bErrNo = TMPL_NM(Bs3TestCallDoerInLM64)(CONV_TO_FLAT(RT_CONCAT3(Bs3TestCallDoerTo,ARCH_BITS,_c64)), BS3_MODE_LM64); +#endif + CHECK_RESULT(g_szBs3ModeName_lm64); + } + + if (fSkipped) + Bs3TestSkipped("skipped\n"); + } + Bs3TestSubDone(); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModesByOneStub.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModesByOneStub.asm new file mode 100644 index 00000000..7e084397 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModesByOneStub.asm @@ -0,0 +1,63 @@ +; $Id: bs3-mode-TestDoModesByOneStub.asm $ +;; @file +; BS3Kit - Bs3TestDoModesByOne near stub. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + +; +; Near stub for the API call (16-bit only). +; +%if TMPL_BITS == 16 + %if TMPL_MODE == BS3_MODE_RM +BS3_BEGIN_RMTEXT16 + %endif +BS3_BEGIN_TEXT16_NEARSTUBS +BS3_PROC_BEGIN_MODE Bs3TestDoModesByOne, BS3_PBC_NEAR + pop ax + push cs + push ax + %if TMPL_MODE == BS3_MODE_RM + extern TMPL_FAR_NM(Bs3TestDoModesByOne):wrt BS3GROUPRMTEXT16 + jmp far TMPL_FAR_NM(Bs3TestDoModesByOne) + %else + extern TMPL_FAR_NM(Bs3TestDoModesByOne):wrt CGROUP16 + jmp TMPL_NM(Bs3TestDoModesByOne) + %endif +BS3_PROC_END_MODE Bs3TestDoModesByOne +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModesHlp.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModesHlp.asm new file mode 100644 index 00000000..601c7019 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModesHlp.asm @@ -0,0 +1,1139 @@ +; $Id: bs3-mode-TestDoModesHlp.asm $ +;; @file +; BS3Kit - Bs3TestDoModes helpers +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* Defined Constants And Macros * +;********************************************************************************************************************************* +; +; We put most of this mess in the RMTEXT16 segment when in real mode. +; +%if TMPL_MODE == BS3_MODE_RM + %define MY_BEGIN_TEXT BS3_BEGIN_RMTEXT16 + %define MY_BEGIN_TEXT16 BS3_BEGIN_RMTEXT16 + %define MY_TEXT16_WRT(a_Label) a_Label wrt BS3GROUPRMTEXT16 +%else + %define MY_BEGIN_TEXT TMPL_BEGIN_TEXT + %define MY_BEGIN_TEXT16 BS3_BEGIN_TEXT16 + %define MY_TEXT16_WRT(a_Label) BS3_TEXT16_WRT(a_Label) +%endif + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +%if TMPL_MODE == BS3_MODE_RM +BS3_BEGIN_TEXT16_FARSTUBS +extern TMPL_FAR_NM(Bs3SwitchToRM) +extern TMPL_FAR_NM(Bs3SwitchToPE16) +extern TMPL_FAR_NM(Bs3SwitchToPE16_32) +extern TMPL_FAR_NM(Bs3SwitchToPE16_V86) +extern TMPL_FAR_NM(Bs3SwitchToPE32) +extern TMPL_FAR_NM(Bs3SwitchToPE32_16) +extern TMPL_FAR_NM(Bs3SwitchToPEV86) +extern TMPL_FAR_NM(Bs3SwitchToPP16) +extern TMPL_FAR_NM(Bs3SwitchToPP16_32) +extern TMPL_FAR_NM(Bs3SwitchToPP16_V86) +extern TMPL_FAR_NM(Bs3SwitchToPP32) +extern TMPL_FAR_NM(Bs3SwitchToPP32_16) +extern TMPL_FAR_NM(Bs3SwitchToPPV86) +extern TMPL_FAR_NM(Bs3SwitchToPAE16) +extern TMPL_FAR_NM(Bs3SwitchToPAE16_32) +extern TMPL_FAR_NM(Bs3SwitchToPAE16_V86) +extern TMPL_FAR_NM(Bs3SwitchToPAE32) +extern TMPL_FAR_NM(Bs3SwitchToPAE32_16) +extern TMPL_FAR_NM(Bs3SwitchToPAEV86) +extern TMPL_FAR_NM(Bs3SwitchToLM16) +extern TMPL_FAR_NM(Bs3SwitchToLM32) +extern TMPL_FAR_NM(Bs3SwitchToLM64) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_rm_far) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pe16_far) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pe16_v86_far) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pe32_16_far) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pev86_far) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pp16_far) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pp16_v86_far) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pp32_16_far) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_ppv86_far) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pae16_far) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pae16_v86_far) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pae32_16_far) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_paev86_far) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_lm16_far) +%else +BS3_BEGIN_TEXT16 +extern TMPL_NM(Bs3SwitchToRM) +extern TMPL_NM(Bs3SwitchToPE16) +extern TMPL_NM(Bs3SwitchToPE16_32) +extern TMPL_NM(Bs3SwitchToPE16_V86) +extern TMPL_NM(Bs3SwitchToPE32) +extern TMPL_NM(Bs3SwitchToPE32_16) +extern TMPL_NM(Bs3SwitchToPEV86) +extern TMPL_NM(Bs3SwitchToPP16) +extern TMPL_NM(Bs3SwitchToPP16_32) +extern TMPL_NM(Bs3SwitchToPP16_V86) +extern TMPL_NM(Bs3SwitchToPP32) +extern TMPL_NM(Bs3SwitchToPP32_16) +extern TMPL_NM(Bs3SwitchToPPV86) +extern TMPL_NM(Bs3SwitchToPAE16) +extern TMPL_NM(Bs3SwitchToPAE16_32) +extern TMPL_NM(Bs3SwitchToPAE16_V86) +extern TMPL_NM(Bs3SwitchToPAE32) +extern TMPL_NM(Bs3SwitchToPAE32_16) +extern TMPL_NM(Bs3SwitchToPAEV86) +extern TMPL_NM(Bs3SwitchToLM16) +extern TMPL_NM(Bs3SwitchToLM32) +extern TMPL_NM(Bs3SwitchToLM64) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_rm) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pe16) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pe16_v86) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pe32_16) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pev86) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pp16) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pp16_v86) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pp32_16) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_ppv86) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pae16) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pae16_v86) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pae32_16) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_paev86) +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_lm16) +%endif +BS3_BEGIN_TEXT16 +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pe16_32):wrt BS3FLAT +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pe32):wrt BS3FLAT +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pp16_32):wrt BS3FLAT +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pp32):wrt BS3FLAT +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pae16_32):wrt BS3FLAT +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pae32):wrt BS3FLAT +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_lm32):wrt BS3FLAT +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_lm64):wrt BS3FLAT + + +MY_BEGIN_TEXT16 ; need the group definition +MY_BEGIN_TEXT + +;; +; Shared prologue code. +; @param xAX Where to jump to for the main event. +; +BS3_GLOBAL_NAME_EX TMPL_NM(bs3TestCallDoerPrologue), , 0 + BS3_CALL_CONV_PROLOG 1 + push xBP + mov xBP, xSP + xPUSHF + + ; Save non-volatile registers so the DO function doesn't have to. + push xBX + push xCX + push xDX + push xSI + push xDI +%if TMPL_BITS != 64 + push ds + push es + push ss + %if TMPL_BITS != 16 + push fs + push gs + %endif +%endif +%if TMPL_BITS == 64 + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 +%endif + + ; Jump to the main code. + jmp xAX + +;; +; Shared epilogue code. +; @param xAX Return code. +; +BS3_GLOBAL_NAME_EX TMPL_NM(bs3TestCallDoerEpilogue), , 0 + ; Restore registers. +%if TMPL_BITS == 16 + sub bp, (1+5+3)*2 + mov sp, bp +%elif TMPL_BITS == 32 + lea xSP, [xBP - (1+5+5)*4] +%else + lea xSP, [xBP - (1+5+8)*8] + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 +%endif +%if TMPL_BITS != 64 + %if TMPL_BITS != 16 + pop gs + pop fs + %endif + pop ss + pop es + pop ds +%endif + pop xDI + pop xSI + pop xDX + pop xCX + pop xBX + xPOPF + pop xBP + ret + +; +; For checking that the mode switching macros doesn't screw up GPRs. +; Note! Does not work on pre 286 hardware! So, for debugging only. +; +%if 0 + %macro STRICT_SAVE_REGS 0 + movzx esp, sp + sub esp, BS3REGCTX_size + mov [esp + BS3REGCTX.rax], eax + mov dword [esp + BS3REGCTX.rax+4], 0xdead0000 + mov [esp + BS3REGCTX.rcx], ecx + mov dword [esp + BS3REGCTX.rcx+4], 0xdead0001 + mov [esp + BS3REGCTX.rdx], edx + mov dword [esp + BS3REGCTX.rdx+4], 0xdead0002 + mov [esp + BS3REGCTX.rbx], ebx + mov dword [esp + BS3REGCTX.rbx+4], 0xdead0003 + mov [esp + BS3REGCTX.rbp], ebp + mov [esp + BS3REGCTX.rsp], esp + mov [esp + BS3REGCTX.rsi], esi + mov [esp + BS3REGCTX.rdi], edi + %endmacro + + %macro STRICT_CHECK_REGS 0 +%%_esp: cmp [esp + BS3REGCTX.rsp], esp + jne %%_esp +%%_eax: cmp [esp + BS3REGCTX.rax], eax + jne %%_eax +%%_ecx: mov [esp + BS3REGCTX.rcx], ecx + jne %%_ecx +%%_edx: cmp [esp + BS3REGCTX.rdx], edx + jne %%_edx +%%_ebx: cmp [esp + BS3REGCTX.rbx], ebx + jne %%_ebx +%%_ebp: cmp [esp + BS3REGCTX.rbp], ebp + jne %%_ebp +%%_esi: cmp [esp + BS3REGCTX.rsi], esi + jne %%_esi +%%_edi: cmp [esp + BS3REGCTX.rdi], edi + jne %%_edi + add esp, BS3REGCTX_size + %endmacro +%else + + %macro STRICT_SAVE_REGS 0 + %endmacro + %macro STRICT_CHECK_REGS 0 + %endmacro +%endif + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Real mode +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInRM(uint16_t offBs3Text16); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInRM, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +MY_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .doit + mov ax, [xBP + xCB + cbCurRetAddr] ; Load far function pointer. + mov dx, [xBP + xCB + cbCurRetAddr + 2] + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToRM) +%else + call TMPL_NM(Bs3SwitchToRM) +%endif + BS3_SET_BITS 16 + STRICT_CHECK_REGS + + mov cx, BS3_MODE_RM + push cx + push cs + mov cx, .return + push cx + push dx + push ax + retf +.return: + + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_rm_far) +%else + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_rm) +%endif + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +MY_BEGIN_TEXT +BS3_PROC_END_MODE Bs3TestCallDoerInRM + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Unpage protection mode. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInPE16(uint16_t offBs3Text16); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInPE16, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +MY_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .doit + mov ax, [xBP + xCB + cbCurRetAddr] ; Load far function pointer. + mov dx, [xBP + xCB + cbCurRetAddr + 2] + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToPE16) +%else + call TMPL_NM(Bs3SwitchToPE16) +%endif + BS3_SET_BITS 16 + STRICT_CHECK_REGS + + push BS3_MODE_PE16 + push cs + push .return + push dx + push ax + retf +.return: + + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pe16_far) +%else + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pe16) +%endif + BS3_SET_BITS TMPL_BITS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +MY_BEGIN_TEXT +BS3_PROC_END_MODE Bs3TestCallDoerInPE16 + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInPE16_32(uint32_t FlatWorkerAddr, uint8_t bMode); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInPE16_32, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +.doit: + mov eax, [xBP + xCB + cbCurRetAddr] ; Load function pointer. + movzx edx, byte [xBP + xCB + cbCurRetAddr + sCB] ; bMode + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToPE16_32) +%else + call TMPL_NM(Bs3SwitchToPE16_32) +%endif + BS3_SET_BITS 32 + STRICT_CHECK_REGS + + push edx ; bMode + call eax + + STRICT_SAVE_REGS + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pe16_32) + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +BS3_PROC_END_MODE Bs3TestCallDoerInPE16_32 + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInPE16_V86(uint16_t offBs3Text16); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInPE16_V86, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +MY_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .doit + mov ax, [xBP + xCB + cbCurRetAddr] ; Load far function pointer. + mov dx, [xBP + xCB + cbCurRetAddr + 2] + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToPE16_V86) +%else + call TMPL_NM(Bs3SwitchToPE16_V86) +%endif + BS3_SET_BITS 16 + STRICT_CHECK_REGS + + push BS3_MODE_PE16_V86 + push cs + push .return + push dx + push ax + retf +.return: + + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pe16_v86_far) +%else + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pe16_v86) +%endif + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +MY_BEGIN_TEXT +BS3_PROC_END_MODE Bs3TestCallDoerInPE16_V86 + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInPE32(uint32_t FlatWorkerAddr, uint8_t bMode); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInPE32, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +.doit: + mov eax, [xBP + xCB + cbCurRetAddr] ; Load function pointer. + movzx edx, byte [xBP + xCB + cbCurRetAddr + sCB] ; bMode + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToPE32) +%else + call TMPL_NM(Bs3SwitchToPE32) +%endif + BS3_SET_BITS 32 + STRICT_CHECK_REGS + + push edx ; bMode + call eax + + STRICT_SAVE_REGS + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pe32) + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +BS3_PROC_END_MODE Bs3TestCallDoerInPE32 + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInPE32_16(uint16_t offBs3Text16); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInPE32_16, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +MY_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .doit + mov ax, [xBP + xCB + cbCurRetAddr] ; Load far function pointer. + mov dx, [xBP + xCB + cbCurRetAddr + 2] + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToPE32_16) +%else + call TMPL_NM(Bs3SwitchToPE32_16) +%endif + BS3_SET_BITS 16 + STRICT_CHECK_REGS + + push BS3_MODE_PE32_16 + push cs + push .return + push dx + push ax + retf +.return: + + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pe32_16_far) +%else + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pe32_16) +%endif + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +MY_BEGIN_TEXT +BS3_PROC_END_MODE Bs3TestCallDoerInPE32_16 + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInPEV86(uint16_t offBs3Text16); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInPEV86, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +MY_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .doit + mov ax, [xBP + xCB + cbCurRetAddr] ; Load far function pointer. + mov dx, [xBP + xCB + cbCurRetAddr + 2] + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToPEV86) +%else + call TMPL_NM(Bs3SwitchToPEV86) +%endif + BS3_SET_BITS 16 + STRICT_CHECK_REGS + + push BS3_MODE_PEV86 + push cs + push .return + push dx + push ax + retf +.return: + + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pev86_far) +%else + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pev86) +%endif + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +MY_BEGIN_TEXT +BS3_PROC_END_MODE Bs3TestCallDoerInPEV86 + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Page protection mode. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInPP16(uint16_t offBs3Text16); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInPP16, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +MY_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .doit + mov ax, [xBP + xCB + cbCurRetAddr] ; Load far function pointer. + mov dx, [xBP + xCB + cbCurRetAddr + 2] + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToPP16) +%else + call TMPL_NM(Bs3SwitchToPP16) +%endif + BS3_SET_BITS 16 + STRICT_CHECK_REGS + + push BS3_MODE_PP16 + push cs + push .return + push dx + push ax + retf +.return: + + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pp16_far) +%else + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pp16) +%endif + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +MY_BEGIN_TEXT +BS3_PROC_END_MODE Bs3TestCallDoerInPP16 + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInPP16_32(uint32_t uFlatWorkerAddr, uint8_t bMode); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInPP16_32, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +.doit: + mov eax, [xBP + xCB + cbCurRetAddr] ; Load function pointer. + movzx edx, byte [xBP + xCB + cbCurRetAddr + sCB] ; bMode + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToPP16_32) +%else + call TMPL_NM(Bs3SwitchToPP16_32) +%endif + BS3_SET_BITS 32 + STRICT_CHECK_REGS + + push edx + call eax + + STRICT_SAVE_REGS + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pp16_32) + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +BS3_PROC_END_MODE Bs3TestCallDoerInPP16_32 + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInPP16_V86(uint16_t offBs3Text16); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInPP16_V86, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +MY_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .doit + mov ax, [xBP + xCB + cbCurRetAddr] ; Load far function pointer. + mov dx, [xBP + xCB + cbCurRetAddr + 2] + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToPP16_V86) +%else + call TMPL_NM(Bs3SwitchToPP16_V86) +%endif + BS3_SET_BITS 16 + STRICT_CHECK_REGS + + push BS3_MODE_PP16_V86 + push cs + push .return + push dx + push ax + retf +.return: + + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pp16_v86_far) +%else + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pp16_v86) +%endif + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +MY_BEGIN_TEXT +BS3_PROC_END_MODE Bs3TestCallDoerInPP16_V86 + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInPP32(uint32_t uFlatWorkerAddr, uint8_t bMode); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInPP32, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +.doit: + mov eax, [xBP + xCB + cbCurRetAddr] ; Load function pointer. + movzx edx, byte [xBP + xCB + cbCurRetAddr + sCB] ; bMode + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToPP32) +%else + call TMPL_NM(Bs3SwitchToPP32) +%endif + BS3_SET_BITS 32 + STRICT_CHECK_REGS + + push edx ; bMode + call eax + + STRICT_SAVE_REGS + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pp32) + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +BS3_PROC_END_MODE Bs3TestCallDoerInPP32 + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInPP32_16(uint16_t offBs3Text16); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInPP32_16, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +MY_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .doit + mov ax, [xBP + xCB + cbCurRetAddr] ; Load far function pointer. + mov dx, [xBP + xCB + cbCurRetAddr + 2] + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToPP32_16) +%else + call TMPL_NM(Bs3SwitchToPP32_16) +%endif + BS3_SET_BITS 16 + STRICT_CHECK_REGS + + push BS3_MODE_PP32_16 + push cs + push .return + push dx + push ax + retf +.return: + + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pp32_16_far) +%else + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pp32_16) +%endif + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +MY_BEGIN_TEXT +BS3_PROC_END_MODE Bs3TestCallDoerInPP32_16 + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInPPV86(uint16_t offBs3Text16); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInPPV86, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +MY_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .doit + mov ax, [xBP + xCB + cbCurRetAddr] ; Load far function pointer. + mov dx, [xBP + xCB + cbCurRetAddr + 2] + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToPPV86) +%else + call TMPL_NM(Bs3SwitchToPPV86) +%endif + BS3_SET_BITS 16 + STRICT_CHECK_REGS + + push BS3_MODE_PPV86 + push cs + push .return + push dx + push ax + retf +.return: + + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_ppv86_far) +%else + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_ppv86) +%endif + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +MY_BEGIN_TEXT +BS3_PROC_END_MODE Bs3TestCallDoerInPPV86 + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; PAE paged protection mode. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInPAE16(uint16_t offBs3Text16); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInPAE16, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +MY_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .doit + mov ax, [xBP + xCB + cbCurRetAddr] ; Load far function pointer. + mov dx, [xBP + xCB + cbCurRetAddr + 2] + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToPAE16) +%else + call TMPL_NM(Bs3SwitchToPAE16) +%endif + BS3_SET_BITS 16 + STRICT_CHECK_REGS + + push BS3_MODE_PAE16 + push cs + push .return + push dx + push ax + retf +.return: + + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pae16_far) +%else + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pae16) +%endif + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +MY_BEGIN_TEXT +BS3_PROC_END_MODE Bs3TestCallDoerInPAE16 + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInPAE16_32(uint32_t uFlatWorkerAddr, uint8_t bMode); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInPAE16_32, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +.doit: + mov eax, [xBP + xCB + cbCurRetAddr] ; Load function pointer. + movzx edx, byte [xBP + xCB + cbCurRetAddr + sCB] ; bMode + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToPAE16_32) +%else + call TMPL_NM(Bs3SwitchToPAE16_32) +%endif + BS3_SET_BITS 32 + STRICT_CHECK_REGS + + push edx ; bMode + call eax + + STRICT_SAVE_REGS + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pae16_32) + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +BS3_PROC_END_MODE Bs3TestCallDoerInPAE16_32 + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInPAE16_V86(uint16_t offBs3Text16); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInPAE16_V86, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +MY_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .doit + mov ax, [xBP + xCB + cbCurRetAddr] ; Load far function pointer. + mov dx, [xBP + xCB + cbCurRetAddr + 2] + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToPAE16_V86) +%else + call TMPL_NM(Bs3SwitchToPAE16_V86) +%endif + BS3_SET_BITS 16 + STRICT_CHECK_REGS + + push BS3_MODE_PAE16_V86 + push cs + push .return + push dx + push ax + retf +.return: + + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pae16_v86_far) +%else + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pae16_v86) +%endif + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +MY_BEGIN_TEXT +BS3_PROC_END_MODE Bs3TestCallDoerInPAE16_V86 + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInPAE32(uint32_t uFlatWorkerAddr, uint8_t bMode); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInPAE32, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +.doit: + mov eax, [xBP + xCB + cbCurRetAddr] ; Load function pointer. + movzx edx, byte [xBP + xCB + cbCurRetAddr + sCB] ; bMode + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToPAE32) +%else + call TMPL_NM(Bs3SwitchToPAE32) +%endif + BS3_SET_BITS 32 + STRICT_CHECK_REGS + + push edx ; bMode + call eax + + STRICT_SAVE_REGS + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pae32) + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +BS3_PROC_END_MODE Bs3TestCallDoerInPAE32 + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInPAE32_16(uint16_t offBs3Text16); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInPAE32_16, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +MY_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .doit + mov ax, [xBP + xCB + cbCurRetAddr] ; Load far function pointer. + mov dx, [xBP + xCB + cbCurRetAddr + 2] + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToPAE32_16) +%else + call TMPL_NM(Bs3SwitchToPAE32_16) +%endif + BS3_SET_BITS 16 + STRICT_CHECK_REGS + + push BS3_MODE_PAE32_16 + push cs + push .return + push dx + push ax + retf +.return: + + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_pae32_16_far) +%else + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_pae32_16) +%endif + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +MY_BEGIN_TEXT +BS3_PROC_END_MODE Bs3TestCallDoerInPAE32_16 + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInPAEV86(uint16_t offBs3Text16); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInPAEV86, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +MY_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .doit + mov ax, [xBP + xCB + cbCurRetAddr] ; Load far function pointer. + mov dx, [xBP + xCB + cbCurRetAddr + 2] + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToPAEV86) +%else + call TMPL_NM(Bs3SwitchToPAEV86) +%endif + BS3_SET_BITS 16 + STRICT_CHECK_REGS + + push BS3_MODE_PAEV86 + push cs + push .return + push dx + push ax + retf +.return: + + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_paev86_far) +%else + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_paev86) +%endif + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +MY_BEGIN_TEXT +BS3_PROC_END_MODE Bs3TestCallDoerInPAEV86 + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Long mode +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInLM16(uint16_t offBs3Text16); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInLM16, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +MY_BEGIN_TEXT16 +BS3_SET_BITS TMPL_BITS +BS3_GLOBAL_LOCAL_LABEL .doit + mov ax, [xBP + xCB + cbCurRetAddr] ; Load far function pointer. + mov dx, [xBP + xCB + cbCurRetAddr + 2] + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToLM16) +%else + call TMPL_NM(Bs3SwitchToLM16) +%endif + BS3_SET_BITS 16 + STRICT_CHECK_REGS + + push BS3_MODE_LM16 + push cs + push .return + push dx + push ax + retf +.return: + + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_lm16_far) +%else + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_lm16) +%endif + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +MY_BEGIN_TEXT +BS3_PROC_END_MODE Bs3TestCallDoerInLM16 + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInLM32(uint16_t offBs3Text16); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInLM32, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +.doit: + mov eax, [xBP + xCB + cbCurRetAddr] ; Load function pointer. + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToLM32) +%else + call TMPL_NM(Bs3SwitchToLM32) +%endif + BS3_SET_BITS 32 + STRICT_CHECK_REGS + + and esp, ~03h + push BS3_MODE_LM32 + call eax + + STRICT_SAVE_REGS + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_lm32) + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +BS3_PROC_END_MODE Bs3TestCallDoerInLM32 + +;; +; @cproto BS3_DECL(uint8_t) Bs3TestCallDoerInLM64(uint32_t uFlatWorkerAddr, uint8_t bMode); +; @uses rax +BS3_PROC_BEGIN_MODE Bs3TestCallDoerInLM64, BS3_PBC_NEAR + BS3_LEA_MOV_WRT_RIP(xAX, MY_TEXT16_WRT(.doit)) + jmp TMPL_NM(bs3TestCallDoerPrologue) +.doit: + mov eax, [xBP + xCB + cbCurRetAddr] ; Load function pointer. + movzx edx, byte [xBP + xCB + cbCurRetAddr + sCB] ; bMode + + ; Mode switch, make the call, switch back. + STRICT_SAVE_REGS +%if TMPL_MODE == BS3_MODE_RM + call far TMPL_FAR_NM(Bs3SwitchToLM64) +%else + call TMPL_NM(Bs3SwitchToLM64) +%endif + BS3_SET_BITS 64 + STRICT_CHECK_REGS + + and rsp, ~0fh + sub rsp, 18h + push rdx ; bMode + BS3_CALL rax, 1 + + STRICT_SAVE_REGS + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_lm64) + BS3_SET_BITS TMPL_BITS + STRICT_CHECK_REGS + jmp TMPL_NM(bs3TestCallDoerEpilogue) +BS3_PROC_END_MODE Bs3TestCallDoerInLM64 + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModesStub.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModesStub.asm new file mode 100644 index 00000000..661e9a98 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TestDoModesStub.asm @@ -0,0 +1,63 @@ +; $Id: bs3-mode-TestDoModesStub.asm $ +;; @file +; BS3Kit - Bs3TestDoModes near stub. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + +; +; Near stub for the API call (16-bit only). +; +%if TMPL_BITS == 16 + %if TMPL_MODE == BS3_MODE_RM +BS3_BEGIN_RMTEXT16 + %endif +BS3_BEGIN_TEXT16_NEARSTUBS +BS3_PROC_BEGIN_MODE Bs3TestDoModes, BS3_PBC_NEAR + pop ax + push cs + push ax + %if TMPL_MODE == BS3_MODE_RM + extern TMPL_FAR_NM(Bs3TestDoModes):wrt BS3GROUPRMTEXT16 + jmp far TMPL_FAR_NM(Bs3TestDoModes) + %else + extern TMPL_FAR_NM(Bs3TestDoModes):wrt CGROUP16 + jmp TMPL_NM(Bs3TestDoModes) + %endif +BS3_PROC_END_MODE Bs3TestDoModes +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TrapInit.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TrapInit.c new file mode 100644 index 00000000..d3994b69 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TrapInit.c @@ -0,0 +1,61 @@ +/* $Id: bs3-mode-TrapInit.c $ */ +/** @file + * BS3Kit - Bs3TrapInit + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit-template-header.h" + + +#undef Bs3TrapInit +BS3_MODE_DEF(void, Bs3TrapInit,(void)) +{ +#if BS3_MODE_IS_RM_SYS(TMPL_MODE) + Bs3TrapRmV86Init(); +#elif BS3_MODE_IS_16BIT_SYS(TMPL_MODE) + Bs3TrapRmV86Init(); + Bs3Trap16Init(); +#elif BS3_MODE_IS_32BIT_SYS(TMPL_MODE) + Bs3TrapRmV86Init(); + Bs3Trap32Init(); +#elif BS3_MODE_IS_64BIT_SYS(TMPL_MODE) + Bs3Trap64Init(); +#else +# error "TMPL_MODE" +#endif +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TrapSetJmpAndRestoreInRmAsm.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TrapSetJmpAndRestoreInRmAsm.asm new file mode 100644 index 00000000..5a6c3d92 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TrapSetJmpAndRestoreInRmAsm.asm @@ -0,0 +1,174 @@ +; $Id: bs3-mode-TrapSetJmpAndRestoreInRmAsm.asm $ +;; @file +; BS3Kit - Bs3TrapSetJmpAndRestoreInRm helper +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_BEGIN_TEXT16 +extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_rm) +extern _Bs3TrapSetJmpAndRestore_c16 + +TMPL_BEGIN_TEXT +extern TMPL_NM(Bs3SwitchToRM) + + +;; +; Shared prologue code. +; @param xAX Where to jump to for the main event. +; +BS3_PROC_BEGIN_MODE Bs3TrapSetJmpAndRestoreInRmAsm, BS3_PBC_NEAR + BS3_CALL_CONV_PROLOG 2 + push xBP + mov xBP, xSP + xPUSHF + + ; + ; Save non-volatile registers so the DO function doesn't have to. + ; + push xBX + push xCX + push xDX + push xSI + push xDI +%if TMPL_BITS != 64 + push ds + push es + push ss + %if TMPL_BITS != 16 + push fs + push gs + %endif +%endif +%if TMPL_BITS == 64 + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 +%endif + + ; + ; Load EAX and EDX with the two pointers. + ; + mov eax, [xBP + xCB + cbCurRetAddr] + mov edx, [xBP + xCB + cbCurRetAddr + sCB] + + ; + ; Jump to 16-bit segment for the mode switching. + ; +%if TMPL_BITS != 16 + jmp .in_16bit_segment +BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +.in_16bit_segment: +%endif + + ; + ; Switch to real-mode. + ; + call TMPL_NM(Bs3SwitchToRM) + BS3_SET_BITS 16 + + ; + ; Now we do the + ; + push edx + push eax + call _Bs3TrapSetJmpAndRestore_c16 + add sp, 8h + + ; + ; Switch back to the original mode. + ; + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_Safe_rm) + BS3_SET_BITS TMPL_BITS + + ; + ; Jump back to the 32-bit or 64-bit segment. + ; +%if TMPL_BITS != 16 + jmp .in_text_segment +TMPL_BEGIN_TEXT +.in_text_segment: +%endif + + ; + ; Restore registers. + ; +%if TMPL_BITS == 16 + sub bp, (1+5+3)*2 + mov sp, bp +%elif TMPL_BITS == 32 + lea xSP, [xBP - (1+5+5)*4] +%else + lea xSP, [xBP - (1+5+8)*8] + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 +%endif +%if TMPL_BITS != 64 + %if TMPL_BITS != 16 + pop gs + pop fs + %endif + pop ss + pop es + pop ds +%endif + pop xDI + pop xSI + pop xDX + pop xCX + pop xBX + xPOPF + pop xBP + ret +BS3_PROC_END_MODE Bs3TrapSetJmpAndRestoreInRmAsm + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TrapSystemCallHandler.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TrapSystemCallHandler.asm new file mode 100644 index 00000000..80cb56b8 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-mode-TrapSystemCallHandler.asm @@ -0,0 +1,891 @@ +; $Id: bs3-mode-TrapSystemCallHandler.asm $ +;; @file +; BS3Kit - System call trap handler. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "bs3kit-template-header.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +BS3_EXTERN_DATA16 g_bBs3CurrentMode +%if TMPL_BITS != 64 +BS3_EXTERN_DATA16 g_uBs3CpuDetected +%endif +%if TMPL_BITS == 16 +BS3_EXTERN_DATA16 g_uBs3TrapEipHint +%endif +TMPL_BEGIN_TEXT + +BS3_EXTERN_CMN Bs3SelProtFar32ToFlat32 +BS3_EXTERN_CMN Bs3RegCtxConvertToRingX +BS3_EXTERN_CMN Bs3RegCtxRestore +BS3_EXTERN_CMN Bs3Panic + +BS3_BEGIN_TEXT16 +extern Bs3PrintStrN_c16_CX_Bytes_At_DS_SI +TMPL_BEGIN_TEXT + + +;; +; System call handler. +; +; This is an assembly trap handler that is called in response to a system call +; request from 'user' code. The only fixed parameter is [ER]AX which contains +; the system call number. Other registers are assigned on a per system call +; basis, ditto for which registers are preserved and which are used to return +; stuff. Generally, though, we preserve all registers not used as return +; values or otherwise implicitly transformed by the call. +; +; Note! The 16-bit versions of this code must be careful with using extended +; registers as we wish this code to work on real 80286 (maybe even 8086) +; CPUs too! +; +BS3_PROC_BEGIN_MODE Bs3TrapSystemCallHandler, BS3_PBC_NEAR ; Near because we'll probably only ever need this from CGROUP16. + ; + ; This prologue is kind of complicated because of 80286 and older CPUs + ; as well as different requirements for 64-bit and the other modes. + ; +%define VAR_CALLER_BP [xBP] +%if TMPL_BITS != 64 + %define VAR_CALLER_DS [xBP - xCB] +%endif +%define VAR_CALLER_BX [xBP - sCB*1 - xCB] ; Note! the upper word is not clean on pre-386 (16-bit mode). +%define VAR_CALLER_AX [xBP - sCB*2 - xCB] +%define VAR_CALLER_CX [xBP - sCB*3 - xCB] +%define VAR_CALLER_DX [xBP - sCB*4 - xCB] +%define VAR_CALLER_SI [xBP - sCB*5 - xCB] +%define VAR_CALLER_SI_HI [xBP - sCB*5 - xCB + 2] +%define VAR_CALLER_DI [xBP - sCB*6 - xCB] +%define VAR_CALLER_DI_HI [xBP - sCB*6 - xCB + 2] +%if TMPL_BITS == 16 + %define VAR_CALLER_EBP [xBP - sCB*7 - xCB] + %define VAR_CALLER_ESP [xBP - sCB*8 - xCB] + %define VAR_CALLER_EFLAGS [xBP - sCB*9 - xCB] + %define VAR_CALLER_MODE [xBP - sCB*9 - xCB*2] + %define BP_TOP_STACK_EXPR xBP - sCB*9 - xCB*2 +%else + %define VAR_CALLER_MODE [xBP - sCB*6 - xCB*2] + %define BP_TOP_STACK_EXPR xBP - sCB*6 - xCB*2 +%endif + push xBP + mov xBP, xSP +%if TMPL_BITS == 64 + push 0 + mov [rsp+2], es + mov [rsp], ds +%else + push ds + %ifdef TMPL_CMN_R86 + push BS3_SEL_DATA16 + %else + push RT_CONCAT(BS3_SEL_R0_DS,TMPL_BITS) + %endif + pop ds ; DS = BS3KIT_GRPNM_DATA16 or FLAT and we can safely access data + %if TMPL_BITS == 16 && (TMPL_MODE == BS3_MODE_RM || TMPL_MODE == BS3_MODE_PE16) + cmp byte [BS3_DATA16_WRT(g_uBs3CpuDetected)], BS3CPU_80286 + jbe .prologue_pre_80386 + %endif +%endif + push sBX + push sAX + push sCX + push sDX + push sSI + push sDI +%if TMPL_BITS == 16 + push ebp + push esp + pushfd + %if TMPL_MODE == BS3_MODE_RM || TMPL_MODE == BS3_MODE_PE16 + jmp .prologue_end + +.prologue_pre_80386: + push bx ; dummy + push bx + xor bx, bx + push bx ; dummy + push ax + push bx ; dummy + push cx + push bx ; dummy + push dx + push bx ; dummy + push si + push bx ; dummy + push di + sub sp, 0ch ; dummy + %endif +%endif +.prologue_end: + + ; + ; VAR_CALLER_MODE: Save the current mode (important for v8086 with 16-bit kernel). + ; + xor xBX, xBX + mov bl, [BS3_DATA16_WRT(g_bBs3CurrentMode)] + push xBX + + ; + ; Dispatch the system call. + ; + cmp ax, BS3_SYSCALL_LAST + ja .invalid_syscall +%if TMPL_BITS == 16 + mov bx, ax + shl bx, 1 + jmp word [cs:.aoffSyscallHandlers + bx] +%else + movzx ebx, ax + mov ebx, [.aoffSyscallHandlers + ebx * 4] + jmp xBX +%endif +.aoffSyscallHandlers: +%ifdef TMPL_16BIT + dw .invalid_syscall wrt CGROUP16 + dw .print_chr wrt CGROUP16 + dw .print_str wrt CGROUP16 + dw .to_ringX wrt CGROUP16 + dw .to_ringX wrt CGROUP16 + dw .to_ringX wrt CGROUP16 + dw .to_ringX wrt CGROUP16 + dw .restore_ctx wrt CGROUP16 +%else + dd .invalid_syscall wrt FLAT + dd .print_chr wrt FLAT + dd .print_str wrt FLAT + dd .to_ringX wrt FLAT + dd .to_ringX wrt FLAT + dd .to_ringX wrt FLAT + dd .to_ringX wrt FLAT + dd .restore_ctx wrt FLAT +%endif + + ; + ; Invalid system call. + ; +.invalid_syscall: + int3 + jmp .return + + ; + ; Print char in the CL register. + ; + ; We use the vga bios teletype interrupt to do the writing, so we must + ; be in some kind of real mode for this to work. 16-bit code segment + ; requried for the mode switching code. + ; +BS3_BEGIN_TEXT16 + BS3_SET_BITS TMPL_BITS +.print_chr: +%if TMPL_BITS != 64 + push es + mov di, ss ; Must save and restore SS for supporting 16/32 and 32/16 caller/kernel ring-0 combinations. +%endif +%ifndef TMPL_CMN_R86 + ; Switch to real mode (20h param scratch area not required). + extern TMPL_NM(Bs3SwitchToRM) + call TMPL_NM(Bs3SwitchToRM) + BS3_SET_BITS 16 +%endif + + ; Print the character, turning '\n' into '\r\n'. + cmp cl, 0ah ; \n + je .print_chr_newline + mov ah, 0eh + mov al, cl + mov bx, 0ff00h + int 10h + jmp .print_chr_done + +.print_chr_newline: + mov ax, 0e0dh ; cmd + \r + mov bx, 0ff00h + int 10h + mov ax, 0e0ah ; cmd + \n + mov bx, 0ff00h + int 10h + +.print_chr_done: +%ifndef TMPL_CMN_R86 + ; Switch back (20h param scratch area not required). + extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_rm) + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_rm) + BS3_SET_BITS TMPL_BITS +%endif +%if TMPL_BITS != 64 + mov ss, di + pop es +%endif + jmp .return +TMPL_BEGIN_TEXT + + + ; + ; Prints DX chars from the string pointed to by CX:xSI to the screen. + ; + ; We use the vga bios teletype interrupt to do the writing, so we must + ; be in some kind of real mode for this to work. The string must be + ; accessible from real mode too. + ; +.print_str: +%if TMPL_BITS != 64 + push es + push ss ; Must save and restore SS for supporting 16/32 and 32/16 caller/kernel ring-0 combinations. +%endif + ; Convert the incoming pointer to real mode (assuming caller checked + ; that real mode can access it). + call .convert_ptr_arg_to_real_mode_ax_si + mov cx, VAR_CALLER_DX + + ; Switch to real mode (no 20h scratch required) +%ifndef TMPL_CMN_R86 + %if TMPL_BITS != 16 + jmp .print_str_to_16bit +BS3_BEGIN_TEXT16 +.print_str_to_16bit: + BS3_SET_BITS TMPL_BITS + %endif + extern TMPL_NM(Bs3SwitchToRM) + call TMPL_NM(Bs3SwitchToRM) + BS3_SET_BITS 16 +%endif + ; Call code in Bs3PrintStrN to do the work. + mov ds, ax + call Bs3PrintStrN_c16_CX_Bytes_At_DS_SI + + ; Switch back (20h param scratch area not required). +%ifndef TMPL_CMN_R86 + extern RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_rm) + call RT_CONCAT3(_Bs3SwitchTo,TMPL_MODE_UNAME,_rm) + %if TMPL_BITS != 16 + BS3_SET_BITS TMPL_BITS + jmp .print_str_end +TMPL_BEGIN_TEXT + %endif +.print_str_end: +%endif +%if TMPL_BITS != 64 + pop ss + pop es +%endif + jmp .return + + + ; + ; Switch the caller to ring-0, ring-1, ring-2 or ring-3. + ; + ; This implement this by saving the entire register context, calling + ; a transformation function (C) and restoring the modified register + ; context using a generic worker. + ; +.to_ringX: + sub xSP, BS3REGCTX_size + mov xBX, xSP ; xBP = BS3REGCTX pointer. + call .save_context + +%if TMPL_BITS == 32 + ; Convert xBP to flat pointer in 32-bit + push ss + push xBX + call Bs3SelProtFar32ToFlat32 + add sSP, 8 + mov xBX, xAX +%endif + push xBX ; Save pointer for the final restore call. + + ; Convert the register context from whatever it is to ring-0. +BONLY64 sub rsp, 10h + mov ax, VAR_CALLER_AX + sub ax, BS3_SYSCALL_TO_RING0 + push xAX +BONLY16 push ss + push xBX + BS3_CALL Bs3RegCtxConvertToRingX, 2 + add xSP, sCB + xCB BS3_ONLY_64BIT(+ 10h) + + ; Restore the register context (does not return). + pop xBX ; restore saved pointer. +BONLY64 sub rsp, 18h +BONLY16 push ss + push xBX + BS3_CALL Bs3RegCtxRestore, 1 + jmp Bs3Panic + + + ; + ; Restore context pointed to by cx:xSI. + ; +.restore_ctx: + call .convert_ptr_arg_to_cx_xSI +BONLY64 sub rsp, 10h + mov xDX, VAR_CALLER_DX + push xDX +BONLY16 push cx + push xSI + BS3_CALL Bs3RegCtxRestore, 2 + jmp Bs3Panic + + ; + ; Return. + ; +.return: + pop xBX ; saved mode + mov [BS3_DATA16_WRT(g_bBs3CurrentMode)], bl +%if TMPL_BITS == 16 + and bl, BS3_MODE_CODE_MASK + cmp bl, BS3_MODE_CODE_V86 + je .return_to_v8086_from_16bit_krnl + cmp bl, BS3_MODE_CODE_32 + je .return_to_32bit_from_16bit_krnl + %if TMPL_MODE == BS3_MODE_RM || TMPL_MODE == BS3_MODE_PE16 + cmp byte [BS3_DATA16_WRT(g_uBs3CpuDetected)], BS3CPU_80286 + jbe .return_pre_80386 + %endif + + popfd + pop esp + pop ebp +%endif + pop sDI + pop sSI + pop sDX + pop sCX + pop sAX + pop sBX +%if TMPL_BITS != 64 + pop ds + leave + iret +%else + mov es, [rsp+2] + mov ds, [rsp] + leave ; skips ds + iretq +%endif + +%if TMPL_BITS == 16 + %if TMPL_MODE == BS3_MODE_RM || TMPL_MODE == BS3_MODE_PE16 + ; Variant of the above for 80286 and older. +.return_pre_80386: + add sp, 0ch + pop di + pop bx ; dummy + pop si + pop bx ; dummy + pop dx + pop bx ; dummy + pop cx + pop bx ; dummy + pop ax + pop bx ; dummy + pop bx ; pushed twice + pop bx + pop ds + pop bp + iret + %endif + +.return_to_v8086_from_16bit_krnl: + int3 + jmp .return_to_v8086_from_16bit_krnl + + ; + ; Returning to 32-bit code may require us to expand and seed the eip + ; and esp addresses in the iret frame since these are truncated when + ; using a 16-bit interrupt handler. + ; + ; Incoming stack: New stack diff cpl: + ; bp + 0ah: [ss] + ; bp + 08h: [sp] bx + 38h: [ss] New stack same cpl: + ; bp + 06h: flags + ; bp + 04h: cs bx + 34h: [esp] bx + 30h: eflags + ; bp + 02h: ip + ; -------------- bx + 30h: eflags bx + 2ch: cs + ; bp + 00h: bp + ; bp - 02h: ds bx + 2ch: cs bx + 28h: eip + ; ------------- + ; bp - 06h: ebx bx + 28h: eip bx + 26h: bp + ; -------------- bx + 24h: ds + ; bp - 0ah: eax bx + 26h: bp + ; bx + 24h: ds bx + 20h: ebx + ; bp - 0eh: ecx + ; bx + 20h: ebx bx + 1ch: eax + ; bp - 12h: edx + ; bx + 1ch: eax bx + 18h: ecx + ; bp - 16h: esi + ; bx + 18h: ecx bx + 14h: edx + ; bp - 1ah: edi + ; bx + 14h: edx bx + 10h: esi + ; bp - 1eh: esp + ; bx + 10h: esi bx + 0ch: edi + ; bp - 22h: ebp + ; bx + 0ch: edi bx + 08h: esp + ; bp - 26h: eflags + ; bx + 08h: esp bx + 04h: ebp + ; + ; bx + 04h: ebp bx + 00h: eflags + ; + ; bx + 00h: eflags + ; + ; + ; If we're returning to the same CPL, we're still using the stack of + ; the 32-bit caller. The high ESP word does not need restoring. + ; + ; If we're returning to a lower CPL, there on a 16-bit ring-0 stack, + ; however, the high ESP word is still that of the caller. + ; +.return_to_32bit_from_16bit_krnl: + mov ax, cs + and al, 3 + mov ah, 3 + and ah, [xBP + xCB*2] + ; The iret frame doubles in size, so allocate more stack. + cmp al, ah + je .return_to_32bit_from_16bit_krnl_same_cpl_sub_sp + sub sp, 2*2 +.return_to_32bit_from_16bit_krnl_same_cpl_sub_sp: + sub sp, 3*2 + mov bx, sp + ; Copy the saved registers. + xor di, di +.return_to_32bit_from_16bit_krnl_copy_loop: + mov ecx, [bp + di - 26h] + mov [ss:bx + di], ecx + add di, 4 + cmp di, 28h + jb .return_to_32bit_from_16bit_krnl_copy_loop + ; Convert the 16-bit iret frame to a 32-bit iret frame. + mov ecx, [BS3_DATA16_WRT(g_uBs3TrapEipHint)] + mov cx, [bp + 02h] ; ip + mov [ss:bx + 28h], ecx + mov ecx, 0f00d0000h + mov cx, [bp + 04h] ; cs + mov [ss:bx + 2ch], ecx + mov ecx, [ss:bx] ; caller eflags + mov cx, [bp + 06h] ; flags + mov [ss:bx + 30h], ecx + cmp al, ah + jz .return_to_32bit_from_16bit_krnl_do_return + mov ecx, [ss:bx + 08h] ; caller esp + mov cx, [bp + 08h] ; sp + mov [ss:bx + 34h], ecx + mov ecx, 0f00d0000h + mov cx, [bp + 0ah] ; ss + mov [ss:bx + 38h], ecx +.return_to_32bit_from_16bit_krnl_do_return: + popfd + pop ecx ; esp - only the high bits! + mov cx, sp + mov esp, ecx + pop ebp + lea bp, [bx + 26h] + pop edi + pop esi + pop edx + pop ecx + pop eax + pop ebx + pop ds + leave + iretd + +%endif ; 16-bit + + + ; + ; Internal function. ss:xBX = Pointer to register frame (BS3REGCTX). + ; @uses xAX + ; +.save_context: +%if TMPL_BITS == 16 + cmp byte [BS3_DATA16_WRT(g_uBs3CpuDetected)], BS3CPU_80386 + jae .save_context_full + + ; + ; 80286 or earlier. + ; + + ; Clear the state area first. + push di + xor di, di +.save_context_16_clear_loop: + mov word [ss:bx + di], 0 + mov word [ss:bx + di + 2], 0 + mov word [ss:bx + di + 4], 0 + mov word [ss:bx + di + 6], 0 + add di, 8 + cmp di, BS3REGCTX_size + jb .save_context_16_clear_loop + pop di + + ; Do the 8086/80186/80286 state saving. + mov ax, VAR_CALLER_AX + mov [ss:bx + BS3REGCTX.rax], ax + mov cx, VAR_CALLER_CX + mov [ss:bx + BS3REGCTX.rcx], ax + mov ax, VAR_CALLER_DX + mov [ss:bx + BS3REGCTX.rdx], ax + mov ax, VAR_CALLER_BX + mov [ss:bx + BS3REGCTX.rbx], ax + mov [ss:bx + BS3REGCTX.rsi], si + mov [ss:bx + BS3REGCTX.rdi], di + mov ax, VAR_CALLER_BP + mov [ss:bx + BS3REGCTX.rbp], ax + mov ax, VAR_CALLER_DS + mov [ss:bx + BS3REGCTX.ds], ax + mov [ss:bx + BS3REGCTX.es], es + mov ax, [xBP + xCB] + mov [ss:bx + BS3REGCTX.rip], ax + mov ax, [xBP + xCB*2] + mov [ss:bx + BS3REGCTX.cs], ax + and al, X86_SEL_RPL + mov [ss:bx + BS3REGCTX.bCpl], al + cmp al, 0 + je .save_context_16_same + mov ax, [xBP + xCB*4] + mov [ss:bx + BS3REGCTX.rsp], ax + mov ax, [xBP + xCB*5] + mov [ss:bx + BS3REGCTX.ss], ax + jmp .save_context_16_done_stack +.save_context_16_same: + mov ax, bp + add ax, xCB * (1 + 3) + mov [ss:bx + BS3REGCTX.rsp], ax + mov ax, ss + mov [ss:bx + BS3REGCTX.ss], ax +.save_context_16_done_stack: + mov ax, [xBP + xCB*3] + mov [ss:bx + BS3REGCTX.rflags], ax + mov al, VAR_CALLER_MODE + mov [ss:bx + BS3REGCTX.bMode], al + cmp byte [BS3_DATA16_WRT(g_uBs3CpuDetected)], BS3CPU_80286 + jne .save_context_16_return + smsw [ss:bx + BS3REGCTX.cr0] + str [ss:bx + BS3REGCTX.tr] + sldt [ss:bx + BS3REGCTX.ldtr] +.save_context_16_return: + or byte [ss:bx + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_AMD64 | BS3REG_CTX_F_NO_CR4 + ret +%endif ; TMPL_BITS == 16 + + ; + ; 80386 or later. + ; +.save_context_full: + + ; Clear the state area. + push xDI + xor xDI, xDI + AssertCompileSizeAlignment(BS3REGCTX, 16) +.save_context_full_clear_loop: +%if TMPL_BITS != 64 + mov dword [ss:xBX + xDI], 0 + mov dword [ss:xBX + xDI + 4], 0 + add xDI, 8 +%else + mov qword [xBX + xDI], 0 + mov qword [xBX + xDI + 8], 0 + add xDI, 10h +%endif + cmp xDI, BS3REGCTX_size + jb .save_context_full_clear_loop + pop xDI + + ; Do the 386+ state saving. +%if TMPL_BITS == 16 ; save the high word of registered pushed on the stack. + mov ecx, VAR_CALLER_AX + mov [ss:bx + BS3REGCTX.rax], ecx + mov ecx, VAR_CALLER_CX + mov [ss:bx + BS3REGCTX.rcx], ecx + mov ecx, VAR_CALLER_DX + mov [ss:bx + BS3REGCTX.rdx], ecx + mov ecx, VAR_CALLER_BX + mov [ss:bx + BS3REGCTX.rbx], ecx + mov ecx, VAR_CALLER_EBP + mov [ss:bx + BS3REGCTX.rbp], ecx + mov ecx, VAR_CALLER_ESP + mov [ss:bx + BS3REGCTX.rsp], ecx + mov ecx, VAR_CALLER_SI + mov [ss:bx + BS3REGCTX.rsi], ecx + mov ecx, VAR_CALLER_DI + mov [ss:bx + BS3REGCTX.rdi], ecx + mov ecx, VAR_CALLER_EFLAGS + mov [ss:bx + BS3REGCTX.rflags], ecx + + ; Seed high EIP word if 32-bit CS. + lar ecx, [bp + 4] + jnz .save_context_full_done_16bit_high_word + test ecx, X86LAR_F_D + jz .save_context_full_done_16bit_high_word + mov ecx, [BS3_DATA16_WRT(g_uBs3TrapEipHint)] + mov [ss:bx + BS3REGCTX.rip], ecx +.save_context_full_done_16bit_high_word: +%endif ; 16-bit + mov xAX, VAR_CALLER_AX + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.rax], xAX + mov xCX, VAR_CALLER_CX + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.rcx], xCX + mov xAX, VAR_CALLER_DX + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.rdx], xAX + mov xAX, VAR_CALLER_BX + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.rbx], xAX + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.rsi], sSI + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.rdi], sDI + mov xAX, VAR_CALLER_BP + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.rbp], xAX +%if TMPL_BITS != 64 + mov ax, VAR_CALLER_DS + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.ds], ax +%else + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.ds], ds +%endif + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.es], es + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.fs], fs + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.gs], gs + mov xAX, [xBP + xCB] + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.rip], xAX + mov ax, [xBP + xCB*2] + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.cs], ax +%if TMPL_MODE != BS3_MODE_RM + and al, X86_SEL_RPL + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.bCpl], al + cmp al, 0 + je .save_context_full_same + mov xAX, [xBP + xCB*4] + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.rsp], xAX + mov ax, [xBP + xCB*5] + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.ss], ax + jmp .save_context_full_done_stack +%else + mov byte [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.bCpl], 0 +%endif +.save_context_full_same: + mov xAX, xBP + add xAX, xCB * (1 + 3) + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.rsp], xAX + mov ax, ss + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.ss], ax +.save_context_full_done_stack: + mov xAX, [xBP + xCB*3] + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.rflags], xAX + + mov al, VAR_CALLER_MODE + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.bMode], al +%if TMPL_BITS == 64 + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.r8], r8 + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.r9], r9 + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.r10], r10 + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.r11], r11 + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.r12], r12 + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.r13], r13 + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.r14], r14 + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.r15], r15 +%endif + str [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.tr] + sldt [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.ldtr] + mov sAX, cr0 + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.cr0], sAX + mov sAX, cr2 + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.cr2], sAX + mov sAX, cr3 + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.cr3], sAX +%if TMPL_BITS != 64 + test byte [BS3_DATA16_WRT(g_uBs3CpuDetected)], (BS3CPU_F_CPUID >> 8) + jnz .have_cr4 + or byte [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_CR4 + jmp .done_cr4 +.have_cr4: +%endif + mov sAX, cr4 + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.cr4], sAX +%if TMPL_BITS != 64 +.done_cr4: + or byte [ss:xBX + BS3REGCTX.fbFlags], BS3REG_CTX_F_NO_AMD64 + + ; Deal with extended v8086 frame. + %if TMPL_BITS == 32 + test dword [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.rflags], X86_EFL_VM + jz .save_context_full_return + %else + test byte VAR_CALLER_MODE, BS3_MODE_CODE_V86 + jz .save_context_full_return + mov dword [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.rflags], X86_EFL_VM + %endif + mov xAX, [xBP + xCB*4] + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.rsp], xAX + mov ax, [xBP + xCB*5] + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.ss], ax + mov ax, [xBP + xCB*6] + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.es], ax + mov ax, [xBP + xCB*7] + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.ds], ax + mov ax, [xBP + xCB*8] + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.fs], ax + mov ax, [xBP + xCB*9] + mov [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.gs], ax + mov byte [BS3_NOT_64BIT(ss:) xBX + BS3REGCTX.bCpl], 3 + jmp .save_context_full_return + +%endif ; !64-bit + +.save_context_full_return: + ret + +%if TMPL_BITS == 16 + CPU 286 +%endif + + ; + ; Internal function for converting a syscall pointer parameter (cx:xSI) + ; to a pointer we can use here in this context. + ; + ; Returns the result in cx:xSI. + ; @uses xAX, xCX, xDX + ; +.convert_ptr_arg_to_cx_xSI: + call .convert_ptr_arg_to_flat +%if TMPL_BITS == 16 + ; Convert to tiled address. + mov si, ax ; offset. + shl dx, X86_SEL_SHIFT + add dx, BS3_SEL_TILED + mov cx, dx +%else + ; Just supply a flat selector. + mov xSI, xAX + mov cx, ds +%endif + ret + + ; + ; Internal function for converting a syscall pointer parameter (caller CX:xSI) + ; to a real mode pointer. + ; + ; Returns the result in AX:SI. + ; @uses xAX, xCX, xDX + ; +.convert_ptr_arg_to_real_mode_ax_si: + call .convert_ptr_arg_to_flat + mov si, ax +%if TMPL_BITS == 16 + mov ax, dx +%else + shr eax, 16 +%endif + shl ax, 12 + ret + + ; + ; Internal function for the above that wraps the Bs3SelProtFar32ToFlat32 call. + ; + ; @returns eax (32-bit, 64-bit), dx+ax (16-bit). + ; @uses eax, ecx, edx + ; +.convert_ptr_arg_to_flat: +%if TMPL_BITS == 16 + ; Convert to (32-bit) flat address first. + test byte VAR_CALLER_MODE, BS3_MODE_CODE_V86 + jz .convert_ptr_arg_to_flat_prot_16 + + mov ax, VAR_CALLER_CX + mov dx, ax + shl ax, 4 + shr dx, 12 + add ax, VAR_CALLER_SI + adc dx, 0 + ret + +.convert_ptr_arg_to_flat_prot_16: + push es + push bx + push word VAR_CALLER_CX ; selector + xor ax, ax + test byte VAR_CALLER_MODE, BS3_MODE_CODE_16 + jnz .caller_is_16_bit + mov ax, VAR_CALLER_SI_HI +.caller_is_16_bit: + push ax ; offset high + push word VAR_CALLER_SI ; offset low + call Bs3SelProtFar32ToFlat32 + add sp, 2*3 + pop bx + pop es + ret + +%else ; 32 or 64 bit + test byte VAR_CALLER_MODE, BS3_MODE_CODE_V86 + jz .convert_ptr_arg_to_cx_xSI_prot + + ; Convert real mode address to flat address and return it. + movzx eax, word VAR_CALLER_CX + shl eax, 4 + movzx edx, word VAR_CALLER_SI + add eax, edx + ret + + ; Convert to (32-bit) flat address. +.convert_ptr_arg_to_cx_xSI_prot: + %if TMPL_BITS == 64 + push r11 + push r10 + push r9 + push r8 + sub rsp, 10h + %endif + movzx ecx, word VAR_CALLER_CX + push xCX + mov eax, VAR_CALLER_SI + test byte VAR_CALLER_MODE, BS3_MODE_CODE_16 + jz .no_masking_offset + and eax, 0ffffh +.no_masking_offset: + push xAX + BS3_CALL Bs3SelProtFar32ToFlat32,2 + add xSP, xCB*2 BS3_ONLY_64BIT(+ 10h) + %if TMPL_BITS == 64 + pop r8 + pop r9 + pop r10 + pop r11 + %endif +%endif + ret + +BS3_PROC_END_MODE Bs3TrapSystemCallHandler + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-rm-InitAll.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-rm-InitAll.c new file mode 100644 index 00000000..ad7ae47e --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-rm-InitAll.c @@ -0,0 +1,100 @@ +/* $Id: bs3-rm-InitAll.c $ */ +/** @file + * BS3Kit - Initialize all components, real mode. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +//#define BS3_USE_RM_TEXT_SEG 1 +#include "bs3kit-template-header.h" +#include "bs3-cmn-test.h" +#include <iprt/asm-amd64-x86.h> + +BS3_MODE_PROTO_NOSB(void, Bs3EnteredMode,(void)); + + +BS3_DECL(void) Bs3InitAll_rm(void) +{ + uint8_t volatile BS3_FAR *pcTicksFlpyOff; + + /* + * Detect CPU first as the memory init code will otherwise use 386 + * instrunctions and cause trouble on older CPUs. + */ + Bs3CpuDetect_rm_far(); + Bs3InitMemory_rm_far(); + Bs3InitGdt_rm_far(); + + /* + * Before we disable all interrupts, try convince the BIOS to stop the + * floppy motor, as it is kind of disturbing when the floppy light remains + * on for the whole testcase execution. + */ + ASMIntDisable(); /* (probably already disabled, but no guarantees) */ + pcTicksFlpyOff = (uint8_t volatile BS3_FAR *)BS3_FP_MAKE(0x40, 0x40); + if (*pcTicksFlpyOff) + { + uint32_t volatile BS3_FAR *pcTicks = (uint32_t volatile BS3_FAR *)BS3_FP_MAKE(0x40, 0x6c); + uint32_t cInitialTicks; + + *pcTicksFlpyOff = 1; /* speed up the countdown, don't want to wait for two seconds here. */ + cInitialTicks = *pcTicks; + ASMIntEnable(); + + while (*pcTicks == cInitialTicks) + ASMHalt(); + } + ASMIntDisable(); + Bs3PicSetup(false /*fForcedReInit*/); + + /* + * Initialize IDTs and such. + */ + if (g_uBs3CpuDetected & BS3CPU_F_LONG_MODE) + Bs3Trap64Init(); + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80386) + Bs3Trap32Init(); + if ((g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80286) + Bs3Trap16Init(); + Bs3TrapRmV86Init(); + + /* + * Perform a real-mode enter to make some final environment adjustments + * (like installing our syscall). + */ + Bs3EnteredMode_rm(); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-rm-InitGdt.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-rm-InitGdt.c new file mode 100644 index 00000000..60d7af6d --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-rm-InitGdt.c @@ -0,0 +1,71 @@ +/* $Id: bs3-rm-InitGdt.c $ */ +/** @file + * BS3Kit - Bs3InitGdt + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define BS3_USE_RM_TEXT_SEG 1 +#include "bs3kit-template-header.h" +#include <iprt/asm.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + + +BS3_DECL_FAR(void) Bs3InitGdt_rm_far(void) +{ +#if 0 /* This totaly messes us up when going back to raw-mode for BIOS work. */ + Bs3Gdte_R0_CS16.Gen.u16LimitLow = Bs3Text16_Size - 1; + Bs3Gdte_R1_CS16.Gen.u16LimitLow = Bs3Text16_Size - 1; + Bs3Gdte_R2_CS16.Gen.u16LimitLow = Bs3Text16_Size - 1; + Bs3Gdte_R3_CS16.Gen.u16LimitLow = Bs3Text16_Size - 1; +#endif + Bs3Gdte_RMTEXT16_CS.Gen.u16LimitLow = Bs3RmText16_Size - 1; + Bs3Gdte_X0TEXT16_CS.Gen.u16LimitLow = Bs3X0Text16_Size - 1; + Bs3Gdte_X1TEXT16_CS.Gen.u16LimitLow = Bs3X1Text16_Size - 1; + + Bs3Gdte_RMTEXT16_CS.Gen.u16BaseLow = (uint16_t)Bs3RmText16_FlatAddr; + Bs3Gdte_X0TEXT16_CS.Gen.u16BaseLow = (uint16_t)Bs3X0Text16_FlatAddr; + Bs3Gdte_X1TEXT16_CS.Gen.u16BaseLow = (uint16_t)Bs3X1Text16_FlatAddr; + + Bs3Gdte_RMTEXT16_CS.Gen.u8BaseHigh1 = (uint8_t)(Bs3RmText16_FlatAddr >> 16); + Bs3Gdte_X0TEXT16_CS.Gen.u8BaseHigh1 = (uint8_t)(Bs3X0Text16_FlatAddr >> 16); + Bs3Gdte_X1TEXT16_CS.Gen.u8BaseHigh1 = (uint8_t)(Bs3X1Text16_FlatAddr >> 16); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-rm-InitMemory.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-rm-InitMemory.c new file mode 100644 index 00000000..5907d191 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-rm-InitMemory.c @@ -0,0 +1,317 @@ +/* $Id: bs3-rm-InitMemory.c $ */ +/** @file + * BS3Kit - Bs3InitMemory + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define BS3_USE_RM_TEXT_SEG 1 +#define BS3_BIOS_INLINE_RM +#include "bs3kit-template-header.h" +#include "bs3-cmn-memory.h" +#include <iprt/asm.h> +#include <VBox/VMMDevTesting.h> + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Slab control structure for the 4K management of low memory (< 1MB). */ +BS3SLABCTLLOW g_Bs3Mem4KLow; +/** Slab control structure for the 4K management of tiled upper memory, + * between 1 MB and 16MB. */ +BS3SLABCTLUPPERTILED g_Bs3Mem4KUpperTiled; + + +/** Translates a power of two request size to an slab list index. */ +uint8_t const g_aiBs3SlabListsByPowerOfTwo[12] = +{ + /* 2^0 = 1 */ 0, + /* 2^1 = 2 */ 0, + /* 2^2 = 4 */ 0, + /* 2^3 = 8 */ 0, + /* 2^4 = 16 */ 0, + /* 2^5 = 32 */ 1, + /* 2^6 = 64 */ 2, + /* 2^7 = 128 */ 3, + /* 2^8 = 256 */ 4, + /* 2^9 = 512 */ 5, + /* 2^10 = 1024 */ -1 + /* 2^11 = 2048 */ -1 +}; + +/** The slab list chunk sizes. */ +uint16_t const g_acbBs3SlabLists[BS3_MEM_SLAB_LIST_COUNT] = +{ + 16, + 32, + 64, + 128, + 256, + 512, +}; + +/** Low memory slab lists, sizes given by g_acbBs3SlabLists. */ +BS3SLABHEAD g_aBs3LowSlabLists[BS3_MEM_SLAB_LIST_COUNT]; +/** Upper tiled memory slab lists, sizes given by g_acbBs3SlabLists. */ +BS3SLABHEAD g_aBs3UpperTiledSlabLists[BS3_MEM_SLAB_LIST_COUNT]; + +/** Slab control structure sizes for the slab lists. + * This is to help the allocator when growing a list. */ +uint16_t const g_cbBs3SlabCtlSizesforLists[BS3_MEM_SLAB_LIST_COUNT] = +{ + RT_ALIGN(sizeof(BS3SLABCTL) - 4 + (4096 / 16 / 8 /*=32*/), 16), + RT_ALIGN(sizeof(BS3SLABCTL) - 4 + (4096 / 32 / 8 /*=16*/), 32), + RT_ALIGN(sizeof(BS3SLABCTL) - 4 + (4096 / 64 / 8 /*=8*/), 64), + RT_ALIGN(sizeof(BS3SLABCTL) - 4 + (4096 / 128 / 8 /*=4*/), 128), + RT_ALIGN(sizeof(BS3SLABCTL) - 4 + (4096 / 256 / 8 /*=2*/), 256), + RT_ALIGN(sizeof(BS3SLABCTL) - 4 + (4096 / 512 / 8 /*=1*/), 512), +}; + + +/** The end RAM address below 4GB (approximately). */ +uint32_t g_uBs3EndOfRamBelow4G = 0; +/** The end RAM address above 4GB, zero if no memory above 4GB. */ +uint64_t g_uBs3EndOfRamAbove4G = 0; + + +/** + * Adds a range of memory to the tiled slabs. + * + * @param uRange Start of range. + * @param cbRange Size of range. + */ +static void bs3InitMemoryAddRange32(uint32_t uRange, uint32_t cbRange) +{ + uint32_t uRangeEnd = uRange + cbRange; + if (uRangeEnd < uRange) + uRangeEnd = UINT32_MAX; + + /* Raise the end-of-ram-below-4GB marker? */ + if (uRangeEnd > g_uBs3EndOfRamBelow4G) + g_uBs3EndOfRamBelow4G = uRangeEnd; + + /* Applicable to tiled memory? */ + if ( uRange < BS3_SEL_TILED_AREA_SIZE + && ( uRange >= _1M + || uRangeEnd >= _1M)) + { + uint16_t cPages; + + /* Adjust the start of the range such that it's at or above 1MB and page aligned. */ + if (uRange < _1M) + { + cbRange -= _1M - uRange; + uRange = _1M; + } + else if (uRange & (_4K - 1U)) + { + cbRange -= uRange & (_4K - 1U); + uRange = RT_ALIGN_32(uRange, _4K); + } + + /* Adjust the end/size of the range such that it's page aligned and not beyond the tiled area. */ + if (uRangeEnd > BS3_SEL_TILED_AREA_SIZE) + { + cbRange -= uRangeEnd - BS3_SEL_TILED_AREA_SIZE; + uRangeEnd = BS3_SEL_TILED_AREA_SIZE; + } + else if (uRangeEnd & (_4K - 1U)) + { + cbRange -= uRangeEnd & (_4K - 1U); + uRangeEnd &= ~(uint32_t)(_4K - 1U); + } + + /* If there is still something, enable it. + (We're a bit paranoid here don't trust the BIOS to only report a page once.) */ + cPages = cbRange >> 12; /*div 4K*/ + if (cPages) + { + unsigned i; + uRange -= _1M; + i = uRange >> 12; /*div _4K*/ + while (cPages-- > 0) + { + uint16_t uLineToLong = ASMBitTestAndClear(g_Bs3Mem4KUpperTiled.Core.bmAllocated, i); + g_Bs3Mem4KUpperTiled.Core.cFreeChunks += uLineToLong; + i++; + } + } + } +} + + +BS3_DECL(void) BS3_FAR_CODE Bs3InitMemory_rm_far(void) +{ + INT15E820ENTRY Entry = { 0, 0, 0, 0 }; + uint32_t cbEntry = sizeof(Entry); + uint32_t uCont = 0; + uint16_t i; + uint16_t cPages; + uint32_t u32; + uint32_t BS3_FAR *pu32Mmio; + + /* + * Enable the A20 gate. + */ + Bs3A20Enable(); + + /* + * Low memory (4K chunks). + * - 0x00000 to 0x004ff - Interrupt Vector table, BIOS data area. + * - 0x01000 to 0x0ffff - Stacks. + * - 0x10000 to 0x1yyyy - BS3TEXT16 + * - 0x20000 to 0x26fff - BS3SYSTEM16 + * - 0x29000 to 0xzzzzz - BS3DATA16, BS3TEXT32, BS3TEXT64, BS3DATA32, BS3DATA64 (in that order). + * - 0xzzzzZ to 0x9fdff - Free conventional memory. + * - 0x9fc00 to 0x9ffff - Extended BIOS data area (exact start may vary). + * - 0xa0000 to 0xbffff - VGA MMIO + * - 0xc0000 to 0xc7fff - VGA BIOS + * - 0xc8000 to 0xeffff - ROMs, tables, unusable. + * - 0xf0000 to 0xfffff - PC BIOS. + */ + Bs3SlabInit(&g_Bs3Mem4KLow.Core, sizeof(g_Bs3Mem4KLow), 0 /*uFlatSlabPtr*/, 0xA0000 /* 640 KB*/, _4K); + + /* Mark the stacks and whole image as allocated. */ + cPages = (Bs3TotalImageSize + _4K - 1U) >> 12; + ASMBitSetRange(g_Bs3Mem4KLow.Core.bmAllocated, 0, 0x10 + cPages); + + /* Mark any unused pages between BS3TEXT16 and BS3SYSTEM16 as free. */ + cPages = (Bs3Text16_Size + (uint32_t)_4K - 1U) >> 12; + ASMBitClearRange(g_Bs3Mem4KLow.Core.bmAllocated, 0x10U + cPages, 0x20U); + + /* In case the system has less than 640KB of memory, check the BDA variable for it. */ + cPages = *(uint16_t BS3_FAR *)BS3_FP_MAKE(0x0000, 0x0413); /* KB of low memory */ + if (cPages < 640) + { + cPages = 640 - cPages; + cPages = RT_ALIGN(cPages, 4); + cPages >>= 2; + ASMBitSetRange(g_Bs3Mem4KLow.Core.bmAllocated, 0xA0 - cPages, 0xA0); + } + else + ASMBitSet(g_Bs3Mem4KLow.Core.bmAllocated, 0x9F); + + /* Recalc free pages. */ + cPages = 0; + i = g_Bs3Mem4KLow.Core.cChunks; + while (i-- > 0) + cPages += !ASMBitTest(g_Bs3Mem4KLow.Core.bmAllocated, i); + g_Bs3Mem4KLow.Core.cFreeChunks = cPages; + + /* + * First 16 MB of memory above 1MB. We start out by marking it all allocated. + */ + Bs3SlabInit(&g_Bs3Mem4KUpperTiled.Core, sizeof(g_Bs3Mem4KUpperTiled), _1M, BS3_SEL_TILED_AREA_SIZE - _1M, _4K); + + ASMBitSetRange(g_Bs3Mem4KUpperTiled.Core.bmAllocated, 0, g_Bs3Mem4KUpperTiled.Core.cChunks); + g_Bs3Mem4KUpperTiled.Core.cFreeChunks = 0; + + /* Ask the BIOS about where there's memory, and make pages in between 1MB + and BS3_SEL_TILED_AREA_SIZE present. This means we're only interested + in entries describing usable memory, ASSUMING of course no overlaps. */ + if ( (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80386 + && Bs3BiosInt15hE820_rm_far(&Entry, &cbEntry, &uCont)) + { + unsigned i = 0; + do + { + if (Entry.uType == INT15E820_TYPE_USABLE) + { + if (!(Entry.uBaseAddr >> 32)) + /* Convert from 64-bit to 32-bit value and record it. */ + bs3InitMemoryAddRange32((uint32_t)Entry.uBaseAddr, + (Entry.cbRange >> 32) ? UINT32_C(0xfffff000) : (uint32_t)Entry.cbRange); + else + { + uint64_t uEnd = Entry.uBaseAddr + Entry.cbRange; + if (uEnd > g_uBs3EndOfRamAbove4G) + g_uBs3EndOfRamAbove4G = uEnd; + } + } + + /* next */ + Entry.uType = 0; + cbEntry = sizeof(Entry); + i++; + } while ( uCont != 0 + && i < 2048 + && Bs3BiosInt15hE820_rm_far(&Entry, &cbEntry, &uCont)); + } + /* Try the 286+ API for getting memory above 1MB and (usually) below 16MB. */ + else if ( (g_uBs3CpuDetected & BS3CPU_TYPE_MASK) >= BS3CPU_80286 + && (u32 = Bs3BiosInt15h88()) != UINT32_MAX + && u32 > 0) + bs3InitMemoryAddRange32(_1M, u32 * _1K); + + /* + * Check if we've got the VMMDev MMIO testing memory mapped above 1MB. + */ + pu32Mmio = (uint32_t BS3_FAR *)BS3_FP_MAKE(VMMDEV_TESTING_MMIO_RM_SEL, + VMMDEV_TESTING_MMIO_RM_OFF2(VMMDEV_TESTING_MMIO_OFF_NOP)); + if (*pu32Mmio == VMMDEV_TESTING_NOP_RET) + { + Bs3Printf("Memory: Found VMMDev MMIO testing region\n"); + if (!ASMBitTestAndSet(g_Bs3Mem4KUpperTiled.Core.bmAllocated, 1)) + g_Bs3Mem4KUpperTiled.Core.cFreeChunks--; + + } + + /* + * Initialize the slab lists. + */ + for (i = 0; i < BS3_MEM_SLAB_LIST_COUNT; i++) + { + Bs3SlabListInit(&g_aBs3LowSlabLists[i], g_acbBs3SlabLists[i]); + Bs3SlabListInit(&g_aBs3UpperTiledSlabLists[i], g_acbBs3SlabLists[i]); + } + +#if 0 + /* + * For debugging. + */ + Bs3Printf("Memory-low: %u/%u chunks bmAllocated[]=", g_Bs3Mem4KLow.Core.cFreeChunks, g_Bs3Mem4KLow.Core.cChunks); + for (i = 0; i < 20; i++) + Bs3Printf("%02x ", g_Bs3Mem4KLow.Core.bmAllocated[i]); + Bs3Printf("\n"); + Bs3Printf("Memory-upt: %u/%u chunks bmAllocated[]=", g_Bs3Mem4KUpperTiled.Core.cFreeChunks, g_Bs3Mem4KUpperTiled.Core.cChunks); + for (i = 0; i < 32; i++) + Bs3Printf("%02x ", g_Bs3Mem4KUpperTiled.Core.bmAllocated[i]); + Bs3Printf("...\n"); +#endif +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-shutdown.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-shutdown.c new file mode 100644 index 00000000..83ee3cfe --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-shutdown.c @@ -0,0 +1,87 @@ +/* $Id: bs3-shutdown.c $ */ +/** @file + * BS3Kit - Shutdown VM from PE16 - proof of concept (BS3Kit). + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "bs3kit.h" +#include <iprt/assert.h> +#include <iprt/asm-amd64-x86.h> + +AssertCompileSize(uint16_t, 2); +AssertCompileSize(uint32_t, 4); +AssertCompileSize(uint64_t, 8); + +extern uint16_t ASMGetMsw(); +#pragma aux ASMGetMsw = \ + ".286" \ + "smsw ax" \ + value [ax] \ + modify exact; + +extern void ASMSetMsw(uint16_t uMsw); +#pragma aux ASMSetMsw = \ + ".286p" \ + "lmsw ax" \ + parm [ax] \ + modify exact; + +/* Just a sample. */ +BS3_DECL(void) Main_pe16(void) +{ + uint16_t uMsw = ASMGetMsw(); + Bs3Printf("msw=%#x cr0=%RX32 g_uBs3CpuDetected=%#x\n", uMsw, ASMGetCR0(), g_uBs3CpuDetected); + Bs3Printf("cr2=%RX32 cr3=%RX32\n", ASMGetCR2(), ASMGetCR3()); + ASMSetMsw(X86_CR0_PE); + Bs3Printf("lmsw(PE) => msw=%#x cr0=%RX32\n", ASMGetMsw(), ASMGetCR0()); + ASMSetMsw(UINT16_MAX); + Bs3Printf("lmsw(0xffff) => msw=%#x cr0=%RX32\n", ASMGetMsw(), ASMGetCR0()); + ASMSetCR0(X86_CR0_PE); + Bs3Printf("ASMSetCR0(X86_CR0_PE) => msw=%#x cr0=%RX32\n", ASMGetMsw(), ASMGetCR0()); + ASMSetCR0(UINT32_C(0x7fffffff)); + Bs3Printf("ASMSetCR0(0x7fffffff) => msw=%#x cr0=%RX32\n", ASMGetMsw(), ASMGetCR0()); + + Bs3TestInit("bs3-shutdown"); + Bs3TestPrintf("detected cpu: %#x\n", g_uBs3CpuDetected); +#if 1 + ASMHalt(); +#else + Bs3Shutdown(); +#endif + return; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-system-data.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-system-data.asm new file mode 100644 index 00000000..f0cd99c0 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-system-data.asm @@ -0,0 +1,1056 @@ +; $Id: bs3-system-data.asm $ +;; @file +; BS3Kit - GDT +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit.mac" + +%define BS3_SYSTEM16_BASE_16_23 ((BS3_ADDR_BS3SYSTEM16 >> 16) & 0xff) +%define BS3_SYSTEM16_BASE_LOW(a_DataSym) ((BS3_DATA_NM(a_DataSym) - StartSystem16) & 0xffff) + +;; +; The GDT (X86DESCGENERIC). +; +BS3_BEGIN_SYSTEM16 +StartSystem16: + db 10, 13, 'eye-catcher: SYSTEM16.......', 10, 13 ; 32 bytes long +BS3_GLOBAL_DATA Bs3Gdt, 4000h - 20h + +;; Macro for checking GDT offsets as we go along. +;; @param %1 The expected current offset. +%macro BS3GdtAssertOffset 1 + %ifndef KBUILD_GENERATING_MAKEFILE_DEPENDENCIES + %if ($ - BS3_DATA_NM(Bs3Gdt)) != %1 + %assign offActual ($ - BS3_DATA_NM(Bs3Gdt)) + %error "BS3GdtAssertOffset: Bad offset: " %+ offActual %+ ", expected " %+ %1 + %endif + %endif +%endmacro + + dw 00000h, 00000h, 00000h, 00000h ; null selector +BS3GdtAssertOffset 8 + + ; + ; 008h..0f8h - System selectors and other stuff + ; + dw 00000h, 00000h, 00000h, 00000h ; Entry 008h - currently unused + +BS3_GLOBAL_DATA Bs3Gdte_Ldt, 16 ; Entry 010h + dw BS3_DATA_NM(Bs3LdtEnd) - BS3_DATA_NM(Bs3Ldt) - 1 + dw BS3_SYSTEM16_BASE_LOW(Bs3Ldt) + db BS3_SYSTEM16_BASE_16_23 + db X86_SEL_TYPE_SYS_LDT | 0x80 + dw 00000h + dw 00000h, 00000h, 00000h, 00000h ; zero for 64-bit mode. + +BS3_GLOBAL_DATA Bs3Gdte_Tss16, 8 ; Entry 020h + dw 0002bh ; 16-bit TSS. + dw BS3_SYSTEM16_BASE_LOW(Bs3Tss16) + db BS3_SYSTEM16_BASE_16_23 + db X86_SEL_TYPE_SYS_286_TSS_AVAIL | 0x80 + dw 0 + +BS3_GLOBAL_DATA Bs3Gdte_Tss16DoubleFault, 8 ; Entry 028h + dw 0002bh ; 16-bit TSS, double fault. + dw BS3_SYSTEM16_BASE_LOW(Bs3Tss16DoubleFault) + db BS3_SYSTEM16_BASE_16_23 + db X86_SEL_TYPE_SYS_286_TSS_AVAIL | 0x80 + dw 0 + +BS3_GLOBAL_DATA Bs3Gdte_Tss16Spare0, 8 ; Entry 030h + dw 0002bh ; 16-bit TSS, spare 0. + dw BS3_SYSTEM16_BASE_LOW(Bs3Tss16Spare0) + db BS3_SYSTEM16_BASE_16_23 + db X86_SEL_TYPE_SYS_286_TSS_AVAIL | 0x80 + dw 0 + +BS3_GLOBAL_DATA Bs3Gdte_Tss16Spare1, 8 ; Entry 038h + dw 0002bh ; 16-bit TSS, spare 0. + dw BS3_SYSTEM16_BASE_LOW(Bs3Tss16Spare1) + db BS3_SYSTEM16_BASE_16_23 + db X86_SEL_TYPE_SYS_286_TSS_AVAIL | 0x80 + dw 0 + +BS3_GLOBAL_DATA Bs3Gdte_Tss32, 8 ; Entry 040h + dw 00067h ; 32-bit TSS. + dw BS3_SYSTEM16_BASE_LOW(Bs3Tss32) + db BS3_SYSTEM16_BASE_16_23 + db X86_SEL_TYPE_SYS_386_TSS_AVAIL | 0x80 + dw 0 + +BS3_GLOBAL_DATA Bs3Gdte_Tss32DoubleFault, 8 ; Entry 048h + dw 00067h ; 32-bit TSS, double fault. + dw BS3_SYSTEM16_BASE_LOW(Bs3Tss32DoubleFault) + db BS3_SYSTEM16_BASE_16_23 + db X86_SEL_TYPE_SYS_386_TSS_AVAIL | 0x80 + dw 0 + +BS3_GLOBAL_DATA Bs3Gdte_Tss32Spare0, 8 ; Entry 050h + dw 00067h ; 32-bit TSS, spare 0. + dw BS3_SYSTEM16_BASE_LOW(Bs3Tss32Spare0) + db BS3_SYSTEM16_BASE_16_23 + db X86_SEL_TYPE_SYS_386_TSS_AVAIL | 0x80 + dw 0 + +BS3_GLOBAL_DATA Bs3Gdte_Tss32Spare1, 8 ; Entry 058h + dw 00067h ; 32-bit TSS, spare 1. + dw BS3_SYSTEM16_BASE_LOW(Bs3Tss32Spare1) + db BS3_SYSTEM16_BASE_16_23 + db X86_SEL_TYPE_SYS_386_TSS_AVAIL | 0x80 + dw 0 + +BS3_GLOBAL_DATA Bs3Gdte_Tss32IobpIntRedirBm, 8 ; Entry 060h + ; 32-bit TSS, with I/O permission & interrupt redirection bitmaps. + dw BS3_DATA_NM(Bs3SharedIobpEnd) - BS3_DATA_NM(Bs3Tss32WithIopb) - 1 + dw BS3_SYSTEM16_BASE_LOW(Bs3Tss32WithIopb) + db BS3_SYSTEM16_BASE_16_23 + db X86_SEL_TYPE_SYS_386_TSS_AVAIL | 0x80 + dw 0 + +BS3_GLOBAL_DATA Bs3Gdte_Tss32IntRedirBm, 8 ; Entry 068h + ; 32-bit TSS, with interrupt redirection bitmap (IOBP stripped by limit). + dw BS3_DATA_NM(Bs3SharedIobp) - BS3_DATA_NM(Bs3Tss32WithIopb) - 1 + dw BS3_SYSTEM16_BASE_LOW(Bs3Tss32WithIopb) + db BS3_SYSTEM16_BASE_16_23 + db X86_SEL_TYPE_SYS_386_TSS_AVAIL | 0x80 + dw 0 + +BS3_GLOBAL_DATA Bs3Gdte_Tss64, 8 ; Entry 070h + dw 00067h ; 64-bit TSS. + dw BS3_SYSTEM16_BASE_LOW(Bs3Tss64) + db BS3_SYSTEM16_BASE_16_23 + db AMD64_SEL_TYPE_SYS_TSS_AVAIL | 0x80 + dw 0 + dw 00000h, 00000h, 00000h, 00000h + +BS3_GLOBAL_DATA Bs3Gdte_Tss64Spare0, 8 ; Entry 080h + dw 00067h ; 64-bit TSS, spare 0. + dw BS3_SYSTEM16_BASE_LOW(Bs3Tss64Spare0) + db BS3_SYSTEM16_BASE_16_23 + db AMD64_SEL_TYPE_SYS_TSS_AVAIL | 0x80 + dw 0 + dw 00000h, 00000h, 00000h, 00000h + +BS3_GLOBAL_DATA Bs3Gdte_Tss64Spare1, 8 ; Entry 090h + dw 00067h ; 64-bit TSS, spare 1. + dw BS3_SYSTEM16_BASE_LOW(Bs3Tss64Spare1) + db BS3_SYSTEM16_BASE_16_23 + db AMD64_SEL_TYPE_SYS_TSS_AVAIL | 0x80 + dw 0 + dw 00000h, 00000h, 00000h, 00000h + +BS3_GLOBAL_DATA Bs3Gdte_Tss64Iobp, 8 ; Entry 0a0h + ; 64-bit TSS, with I/O permission bitmap + dw BS3_DATA_NM(Bs3SharedIobp) - BS3_DATA_NM(Bs3Tss64WithIopb) - 1 + dw BS3_SYSTEM16_BASE_LOW(Bs3Tss64WithIopb) + db BS3_SYSTEM16_BASE_16_23 + db AMD64_SEL_TYPE_SYS_TSS_AVAIL | 0x80 + dw 0 + dw 00000h, 00000h, 00000h, 00000h + +BS3GdtAssertOffset 0b0h + dw 00000h, 00000h, 00000h, 00000h ; Entry 0b0h - currently unused + dw 00000h, 00000h, 00000h, 00000h ; Entry 0b8h - currently unused + dw 00000h, 00000h, 00000h, 00000h ; Entry 0c0h - currently unused + dw 00000h, 00000h, 00000h, 00000h ; Entry 0c8h - currently unused + dw 00000h, 00000h, 00000h, 00000h ; Entry 0d0h - currently unused + dw 00000h, 00000h, 00000h, 00000h ; Entry 0d8h - currently unused + + ; Misc selectors. +BS3_GLOBAL_DATA Bs3Gdte_RMTEXT16_CS, 8 ; Entry 0e0h + dw 0fffeh, 00000h ; 16-bit conforming code (read+exec) segment, accessed. Will be finalized at startup. + dw 09f00h, 00000h +BS3_GLOBAL_DATA Bs3Gdte_X0TEXT16_CS, 8 ; Entry 0e8h + dw 0fffeh, 00000h ; 16-bit conforming code (read+exec) segment, accessed. Will be finalized at startup. + dw 09f00h, 00000h +BS3_GLOBAL_DATA Bs3Gdte_X1TEXT16_CS, 8 ; Entry 0f0h + dw 0fffeh, 00000h ; 16-bit conforming code (read+exec) segment, accessed. Will be finalized at startup. + dw 09f00h, 00000h +BS3_GLOBAL_DATA Bs3Gdte_R0_MMIO16, 8 ; Entry 0f8h + dw 0ffffh, 0f000h, 0930dh, 00000h ; 16-bit VMMDev MMIO segment with base 0df000h. +BS3GdtAssertOffset 0100h + + +;; +; Macro that defines the selectors for ring-%1. +; +%macro BS3_GDT_RING_X_SELECTORS 1 +BS3_GLOBAL_DATA Bs3Gdte_R %+ %1 %+ _First, 80h +BS3_GLOBAL_DATA Bs3Gdte_R %+ %1 %+ _CS16, 8 ; Entry 100h + dw 0ffffh, (0xffff & BS3_ADDR_BS3TEXT16) ; 16-bit code segment with base 010000h. + dw 09b01h | (%1 << 0dh) | (0xff & (BS3_ADDR_BS3TEXT16 >> 16)), 00000h | (0xff00 & (BS3_ADDR_BS3TEXT16 >> 16)) + +BS3_GLOBAL_DATA Bs3Gdte_R %+ %1 %+ _DS16, 8 ; Entry 108h + dw 0ffffh, (0xffff & BS3_ADDR_BS3DATA16) ; 16-bit data segment with base 029000h. + dw 09300h | (%1 << 0dh) | (0xff & (BS3_ADDR_BS3DATA16 >> 16)), 00000h | (0xff00 & (BS3_ADDR_BS3DATA16 >> 16)) + +BS3_GLOBAL_DATA Bs3Gdte_R %+ %1 %+ _SS16, 8 ; Entry 110h + dw 0ffffh, 00000h ; 16-bit stack segment with base 0. + dw 09300h | (%1 << 0dh), 00000h + +BS3_GLOBAL_DATA Bs3Gdte_R %+ %1 %+ _CS32, 8 ; Entry 118h + dw 0ffffh, 00000h ; 32-bit flat code segment. + dw 09b00h | (%1 << 0dh), 000cfh + +BS3_GLOBAL_DATA Bs3Gdte_R %+ %1 %+ _DS32, 8 ; Entry 120h + dw 0ffffh, 00000h ; 32-bit flat data segment. + dw 09300h | (%1 << 0dh), 000cfh + +BS3_GLOBAL_DATA Bs3Gdte_R %+ %1 %+ _SS32, 8 ; Entry 128h + dw 0ffffh, 00000h ; 32-bit flat stack segment. + dw 09300h | (%1 << 0dh), 000cfh + +BS3_GLOBAL_DATA Bs3Gdte_R %+ %1 %+ _CS64, 8 ; Entry 130h + dw 0ffffh, 00000h ; 64-bit code segment. + dw 09a00h | (%1 << 0dh), 000afh + +BS3_GLOBAL_DATA Bs3Gdte_R %+ %1 %+ _DS64, 8 ; Entry 138h (also SS64) + dw 0ffffh, 00000h ; 64-bit stack and data segment. + dw 09300h | (%1 << 0dh), 000afh + +BS3_GLOBAL_DATA Bs3Gdte_R %+ %1 %+ _CS16_EO, 8 ; Entry 140h + dw 0ffffh, (0xffff & BS3_ADDR_BS3TEXT16) ; 16-bit code segment with base 01000h, not accessed, execute only, short limit. + dw 09800h | (%1 << 0dh) | (0xff & (BS3_ADDR_BS3TEXT16 >> 16)), 00000h | (0xff00 & (BS3_ADDR_BS3TEXT16 >> 16)) + +BS3_GLOBAL_DATA Bs3Gdte_R %+ %1 %+ _CS16_CNF, 8 ; Entry 148h + dw 0ffffh, (0xffff & BS3_ADDR_BS3TEXT16) ; 16-bit code segment with base 01000h, not accessed, execute only, short limit. + dw 09e00h | (%1 << 0dh) | (0xff & (BS3_ADDR_BS3TEXT16 >> 16)), 00000h | (0xff00 & (BS3_ADDR_BS3TEXT16 >> 16)) + +BS3_GLOBAL_DATA Bs3Gdte_R %+ %1 %+ _CS16_CND_EO, 8 ; Entry 150h + dw 0fffeh, 00000h ; 16-bit conforming code segment with base 0, not accessed, execute only, short limit. + dw 09c00h | (%1 << 0dh), 000cfh + +BS3_GLOBAL_DATA Bs3Gdte_R %+ %1 %+ _CS32_EO, 8 ; Entry 158h + dw 0ffffh, 00000h ; 32-bit flat code segment, not accessed, execute only. + dw 09800h | (%1 << 0dh), 000cfh + +BS3_GLOBAL_DATA Bs3Gdte_R %+ %1 %+ _CS32_CNF, 8 ; Entry 160h + dw 0ffffh, 00000h ; 32-bit flat conforming code segment, not accessed. + dw 09e00h | (%1 << 0dh), 000cfh + +BS3_GLOBAL_DATA Bs3Gdte_R %+ %1 %+ _CS32_CNF_EO, 8 ; Entry 168h + dw 0ffffh, 00000h ; 32-bit flat conforming code segment, not accessed, execute only. + dw 09c00h | (%1 << 0dh), 000cfh + +BS3_GLOBAL_DATA Bs3Gdte_R %+ %1 %+ _CS64_EO, 8 ; Entry 170h + dw 0ffffh, 00000h ; 64-bit code segment, not accessed, execute only. + dw 09800h | (%1 << 0dh), 000afh + +BS3_GLOBAL_DATA Bs3Gdte_R %+ %1 %+ _CS64_CNF, 8 ; Entry 178h + dw 0ffffh, 00000h ; 64-bit conforming code segment, not accessed. + dw 09e00h | (%1 << 0dh), 000afh + +BS3_GLOBAL_DATA Bs3Gdte_R %+ %1 %+ _CS64_CNF_EO, 8 ; Entry 180h + dw 0ffffh, 00000h ; 64-bit conforming code segment, execute only, not accessed. + dw 09c00h | (%1 << 0dh), 000afh + +;; @todo expand down segments. + dw 00000h, 00000h, 00000h, 00000h ; Entry 188h - unused. + dw 00000h, 00000h, 00000h, 00000h ; Entry 190h - unused. + dw 00000h, 00000h, 00000h, 00000h ; Entry 198h - unused. + dw 00000h, 00000h, 00000h, 00000h ; Entry 1a0h - unused. + dw 00000h, 00000h, 00000h, 00000h ; Entry 1a8h - unused. + dw 00000h, 00000h, 00000h, 00000h ; Entry 1b0h - unused. + dw 00000h, 00000h, 00000h, 00000h ; Entry 1b8h - unused. + dw 00000h, 00000h, 00000h, 00000h ; Entry 1c0h - unused. + dw 00000h, 00000h, 00000h, 00000h ; Entry 1c8h - unused. + dw 00000h, 00000h, 00000h, 00000h ; Entry 1d0h - unused. + dw 00000h, 00000h, 00000h, 00000h ; Entry 1d8h - unused. + dw 00000h, 00000h, 00000h, 00000h ; Entry 1e0h - unused. + dw 00000h, 00000h, 00000h, 00000h ; Entry 1e8h - unused. + dw 00000h, 00000h, 00000h, 00000h ; Entry 1f0h - unused. + dw 00000h, 00000h, 00000h, 00000h ; Entry 1f8h - unused. +%endmacro + + ; + ; 100h..1f8h - Ring-0 selectors. + ; + BS3_GDT_RING_X_SELECTORS 0 + + ; + ; 200h..2f8h - Ring-1 selectors. + ; + BS3_GDT_RING_X_SELECTORS 1 + + ; + ; 300h..3f8h - Ring-2 selectors. + ; + BS3_GDT_RING_X_SELECTORS 2 + + ; + ; 400h..4f8h - Ring-3 selectors. + ; + BS3_GDT_RING_X_SELECTORS 3 + + ; + ; 500..5f8h - Named spare GDT entries. + ; +BS3GdtAssertOffset 0500h +BS3_GLOBAL_DATA Bs3GdteSpare00, 8 ; Entry 500h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare01, 8 ; Entry 508h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare02, 8 ; Entry 510h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare03, 8 ; Entry 518h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare04, 8 ; Entry 520h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare05, 8 ; Entry 528h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare06, 8 ; Entry 530h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare07, 8 ; Entry 538h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare08, 8 ; Entry 540h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare09, 8 ; Entry 548h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare0a, 8 ; Entry 550h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare0b, 8 ; Entry 558h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare0c, 8 ; Entry 560h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare0d, 8 ; Entry 568h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare0e, 8 ; Entry 570h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare0f, 8 ; Entry 578h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare10, 8 ; Entry 580h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare11, 8 ; Entry 588h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare12, 8 ; Entry 590h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare13, 8 ; Entry 598h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare14, 8 ; Entry 5a0h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare15, 8 ; Entry 5a8h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare16, 8 ; Entry 5b0h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare17, 8 ; Entry 5b8h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare18, 8 ; Entry 5c0h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare19, 8 ; Entry 5c8h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare1a, 8 ; Entry 5d0h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare1b, 8 ; Entry 5d8h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare1c, 8 ; Entry 5e0h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare1d, 8 ; Entry 5e8h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare1e, 8 ; Entry 5f0h + dq 0 +BS3_GLOBAL_DATA Bs3GdteSpare1f, 8 ; Entry 5f8h + dq 0 + + ; + ; 600..df8h - 16-bit DPL=3 data segments covering the first 16MB of memory. + ; +BS3_GLOBAL_DATA Bs3GdteTiled, 8 ; Entry 600h +%assign u8HighBase 0 +%rep 256 + dw 0ffffh, 00000h, 0f300h | u8HighBase, 00000h +%assign u8HighBase u8HighBase + 1 +%endrep + ; + ; e00..ff8h - Free GDTEs. + ; +BS3GdtAssertOffset 0e00h +BS3_GLOBAL_DATA Bs3GdteFreePart1, 200h + times 200h db 0 + + ; + ; 1000h - the real mode segment number for BS3TEXT16. DPL=0, BASE=0x10000h, conforming, exec, read. + ; +BS3GdtAssertOffset 01000h +BS3_GLOBAL_DATA Bs3Gdte_CODE16, 8h + dw 0ffffh, 00000h, 09f01h, 00000h + + ; + ; 1008..17f8h - Free GDTEs. + ; +BS3GdtAssertOffset 01008h +BS3_GLOBAL_DATA Bs3GdteFreePart2, 07f8h + times 07f8h db 0 + + ; + ; 1800..1ff8h - 16-bit DPL=0 data/stack segments covering the first 16MB of memory. + ; +BS3GdtAssertOffset 01800h +BS3_GLOBAL_DATA Bs3GdteTiledR0, 8 ; Entry 1800h +%assign u8HighBase 0 +%rep 256 + dw 0ffffh, 00000h, 09300h | u8HighBase, 00000h +%assign u8HighBase u8HighBase + 1 +%endrep + + ; + ; 2000h - the real mode segment number for BS3SYSTEM. DPL=3. BASE=0x20000h + ; +BS3GdtAssertOffset 02000h +BS3_GLOBAL_DATA Bs3Gdte_SYSTEM16, 8h + dw 0ffffh, 00000h, 0f302h, 00000h + + ; + ; 2008..28f8h - Free GDTEs. + ; +BS3_GLOBAL_DATA Bs3GdteFreePart3, 08f8h + times 08f8h db 0 + + ; + ; 2900h - the real mode segment number for BS3KIT_GRPNM_DATA16. DPL=3. BASE=0x29000h + ; +BS3GdtAssertOffset 02900h +BS3_GLOBAL_DATA Bs3Gdte_DATA16, 8h + dw 0ffffh, 09000h, 0f302h, 00000h + + ; + ; 2908..2f98h - Free GDTEs. + ; +BS3GdtAssertOffset 02908h +BS3_GLOBAL_DATA Bs3GdteFreePart4, 698h + times 698h db 0 + + ; + ; 2be0..2fe0h - 8 spare entries preceeding the test page which we're free + ; to mess with page table protection. + ; +BS3GdtAssertOffset 02fa0h +BS3_GLOBAL_DATA Bs3GdtePreTestPage08, 8 + dq 0 +BS3_GLOBAL_DATA Bs3GdtePreTestPage07, 8 + dq 0 +BS3_GLOBAL_DATA Bs3GdtePreTestPage06, 8 + dq 0 +BS3_GLOBAL_DATA Bs3GdtePreTestPage05, 8 + dq 0 +BS3_GLOBAL_DATA Bs3GdtePreTestPage04, 8 + dq 0 +BS3_GLOBAL_DATA Bs3GdtePreTestPage03, 8 + dq 0 +BS3_GLOBAL_DATA Bs3GdtePreTestPage02, 8 + dq 0 +BS3_GLOBAL_DATA Bs3GdtePreTestPage01, 8 + dq 0 + + ; + ; 2fe0..3fd8h - 16 Test entries at the start of the page where we're free + ; to mess with page table protection. + ; +BS3GdtAssertOffset 02fe0h +AssertCompile(($ - $$) == 0x3000) +BS3_GLOBAL_DATA Bs3GdteTestPage, 0 +BS3_GLOBAL_DATA Bs3GdteTestPage00, 8 + dq 0 +BS3_GLOBAL_DATA Bs3GdteTestPage01, 8 + dq 0 +BS3_GLOBAL_DATA Bs3GdteTestPage02, 8 + dq 0 +BS3_GLOBAL_DATA Bs3GdteTestPage03, 8 + dq 0 +BS3_GLOBAL_DATA Bs3GdteTestPage04, 8 + dq 0 +BS3_GLOBAL_DATA Bs3GdteTestPage05, 8 + dq 0 +BS3_GLOBAL_DATA Bs3GdteTestPage06, 8 + dq 0 +BS3_GLOBAL_DATA Bs3GdteTestPage07, 8 + dq 0 +BS3GdtAssertOffset 3020h + times 0fb8h db 0 +BS3GdtAssertOffset 3fd8h +BS3_GLOBAL_DATA Bs3GdtEnd, 0 + db 10, 13, 'GDTE', 10, 13 ; alignment padding (next address on 16 byte boundrary). +BS3GdtAssertOffset 4000h - 20h ; We're at a page boundrary here! Only GDT and eyecatchers on page starting at 3000h! +AssertCompile(($ - $$) == 0x4000) + + + +;; +; The 16-bit TSS. +; +BS3_GLOBAL_DATA Bs3Tss16, X86TSS16_size +istruc X86TSS16 + at X86TSS16.selPrev, dw 0 + at X86TSS16.sp0, dw BS3_ADDR_STACK_R0 + at X86TSS16.ss0, dw BS3_SEL_R0_SS16 + at X86TSS16.sp1, dw BS3_ADDR_STACK_R1 + at X86TSS16.ss1, dw BS3_SEL_R1_SS16 + at X86TSS16.sp2, dw BS3_ADDR_STACK_R2 + at X86TSS16.ss2, dw BS3_SEL_R2_SS16 + at X86TSS16.ip, dw 0 + at X86TSS16.flags, dw 0 + at X86TSS16.ax, dw 0 + at X86TSS16.cx, dw 0 + at X86TSS16.dx, dw 0 + at X86TSS16.bx, dw 0 + at X86TSS16.sp, dw 0 + at X86TSS16.bp, dw 0 + at X86TSS16.si, dw 0 + at X86TSS16.di, dw 0 + at X86TSS16.es, dw 0 + at X86TSS16.cs, dw 0 + at X86TSS16.ss, dw 0 + at X86TSS16.ds, dw 0 + at X86TSS16.selLdt, dw 0 +iend + +;; +; 16-bit TSS for (trying to) handle double faults. +BS3_GLOBAL_DATA Bs3Tss16DoubleFault, X86TSS16_size +istruc X86TSS16 + at X86TSS16.selPrev, dw 0 + at X86TSS16.sp0, dw BS3_ADDR_STACK_R0 + at X86TSS16.ss0, dw BS3_SEL_R0_SS16 + at X86TSS16.sp1, dw BS3_ADDR_STACK_R1 + at X86TSS16.ss1, dw BS3_SEL_R1_SS16 + at X86TSS16.sp2, dw BS3_ADDR_STACK_R2 + at X86TSS16.ss2, dw BS3_SEL_R2_SS16 + at X86TSS16.ip, dw 0 ; Will be filled in by routine setting up 16-bit mode w/ traps++. + at X86TSS16.flags, dw X86_EFL_1 + at X86TSS16.ax, dw 0 + at X86TSS16.cx, dw 0 + at X86TSS16.dx, dw 0 + at X86TSS16.bx, dw 0 + at X86TSS16.sp, dw BS3_ADDR_STACK_R0_IST1 + at X86TSS16.bp, dw 0 + at X86TSS16.si, dw 0 + at X86TSS16.di, dw 0 + at X86TSS16.es, dw BS3_SEL_R0_DS16 + at X86TSS16.cs, dw BS3_SEL_R0_CS16 + at X86TSS16.ss, dw BS3_SEL_R0_SS16 + at X86TSS16.ds, dw BS3_SEL_R0_DS16 + at X86TSS16.selLdt, dw 0 +iend + +;; +; A spare 16-bit TSS for testcases to play around with. +BS3_GLOBAL_DATA Bs3Tss16Spare0, X86TSS16_size +istruc X86TSS16 + at X86TSS16.selPrev, dw 0 + at X86TSS16.sp0, dw BS3_ADDR_STACK_R0 + at X86TSS16.ss0, dw BS3_SEL_R0_SS16 + at X86TSS16.sp1, dw BS3_ADDR_STACK_R1 + at X86TSS16.ss1, dw BS3_SEL_R1_SS16 + at X86TSS16.sp2, dw BS3_ADDR_STACK_R2 + at X86TSS16.ss2, dw BS3_SEL_R2_SS16 + at X86TSS16.ip, dw 0 ; Will be filled in by routine setting up 16-bit mode w/ traps++. + at X86TSS16.flags, dw X86_EFL_1 + at X86TSS16.ax, dw 0 + at X86TSS16.cx, dw 0 + at X86TSS16.dx, dw 0 + at X86TSS16.bx, dw 0 + at X86TSS16.sp, dw BS3_ADDR_STACK_R0_IST2 + at X86TSS16.bp, dw 0 + at X86TSS16.si, dw 0 + at X86TSS16.di, dw 0 + at X86TSS16.es, dw BS3_SEL_R0_DS16 + at X86TSS16.cs, dw BS3_SEL_R0_CS16 + at X86TSS16.ss, dw BS3_SEL_R0_SS16 + at X86TSS16.ds, dw BS3_SEL_R0_DS16 + at X86TSS16.selLdt, dw 0 +iend + +;; +; A spare 16-bit TSS for testcases to play around with. +BS3_GLOBAL_DATA Bs3Tss16Spare1, X86TSS16_size +istruc X86TSS16 + at X86TSS16.selPrev, dw 0 + at X86TSS16.sp0, dw BS3_ADDR_STACK_R0 + at X86TSS16.ss0, dw BS3_SEL_R0_SS16 + at X86TSS16.sp1, dw BS3_ADDR_STACK_R1 + at X86TSS16.ss1, dw BS3_SEL_R1_SS16 + at X86TSS16.sp2, dw BS3_ADDR_STACK_R2 + at X86TSS16.ss2, dw BS3_SEL_R2_SS16 + at X86TSS16.ip, dw 0 ; Will be filled in by routine setting up 16-bit mode w/ traps++. + at X86TSS16.flags, dw X86_EFL_1 + at X86TSS16.ax, dw 0 + at X86TSS16.cx, dw 0 + at X86TSS16.dx, dw 0 + at X86TSS16.bx, dw 0 + at X86TSS16.sp, dw BS3_ADDR_STACK_R0_IST4 + at X86TSS16.bp, dw 0 + at X86TSS16.si, dw 0 + at X86TSS16.di, dw 0 + at X86TSS16.es, dw BS3_SEL_R0_DS16 + at X86TSS16.cs, dw BS3_SEL_R0_CS16 + at X86TSS16.ss, dw BS3_SEL_R0_SS16 + at X86TSS16.ds, dw BS3_SEL_R0_DS16 + at X86TSS16.selLdt, dw 0 +iend + + +;; +; The 32-bit TSS. +; +BS3_GLOBAL_DATA Bs3Tss32, X86TSS32_size +istruc X86TSS32 + at X86TSS32.selPrev, dw 0 + at X86TSS32.padding1, dw 0 + at X86TSS32.esp0, dd BS3_ADDR_STACK_R0 + at X86TSS32.ss0, dw BS3_SEL_R0_SS32 + at X86TSS32.padding_ss0, dw 1 + at X86TSS32.esp1, dd 1 + at X86TSS32.ss1, dw BS3_SEL_R1_SS32 + at X86TSS32.padding_ss1, dw 1 + at X86TSS32.esp2, dd 1 + at X86TSS32.ss2, dw BS3_SEL_R2_SS32 + at X86TSS32.padding_ss2, dw 1 + at X86TSS32.cr3, dd 0 + at X86TSS32.eip, dd 0 + at X86TSS32.eflags, dd X86_EFL_1 + at X86TSS32.eax, dd 0 + at X86TSS32.ecx, dd 0 + at X86TSS32.edx, dd 0 + at X86TSS32.ebx, dd 0 + at X86TSS32.esp, dd 0 + at X86TSS32.ebp, dd 0 + at X86TSS32.esi, dd 0 + at X86TSS32.edi, dd 0 + at X86TSS32.es, dw 0 + at X86TSS32.padding_es, dw 0 + at X86TSS32.cs, dw 0 + at X86TSS32.padding_cs, dw 0 + at X86TSS32.ss, dw 0 + at X86TSS32.padding_ss, dw 0 + at X86TSS32.ds, dw 0 + at X86TSS32.padding_ds, dw 0 + at X86TSS32.fs, dw 0 + at X86TSS32.padding_fs, dw 0 + at X86TSS32.gs, dw 0 + at X86TSS32.padding_gs, dw 0 + at X86TSS32.selLdt, dw 0 + at X86TSS32.padding_ldt, dw 0 + at X86TSS32.fDebugTrap, dw 0 + at X86TSS32.offIoBitmap, dw (BS3_DATA_NM(Bs3SharedIobp) - BS3_DATA_NM(Bs3Tss32WithIopb)) +iend + +;; +; The 32-bit TSS for handling double faults. +BS3_GLOBAL_DATA Bs3Tss32DoubleFault, X86TSS32_size +istruc X86TSS32 + at X86TSS32.selPrev, dw 0 + at X86TSS32.padding1, dw 0 + at X86TSS32.esp0, dd BS3_ADDR_STACK_R0 + at X86TSS32.ss0, dw BS3_SEL_R0_SS32 + at X86TSS32.padding_ss0, dw 1 + at X86TSS32.esp1, dd 1 + at X86TSS32.ss1, dw BS3_SEL_R1_SS32 + at X86TSS32.padding_ss1, dw 1 + at X86TSS32.esp2, dd 1 + at X86TSS32.ss2, dw BS3_SEL_R2_SS32 + at X86TSS32.padding_ss2, dw 1 + at X86TSS32.cr3, dd 0 ; Will be filled in by routine setting up paged 32-bit mode w/ traps++. + at X86TSS32.eip, dd 0 ; Will be filled in by routine setting up 32-bit mode w/ traps++. + at X86TSS32.eflags, dd X86_EFL_1 + at X86TSS32.eax, dd 0 + at X86TSS32.ecx, dd 0 + at X86TSS32.edx, dd 0 + at X86TSS32.ebx, dd 0 + at X86TSS32.esp, dd BS3_ADDR_STACK_R0_IST1 + at X86TSS32.ebp, dd 0 + at X86TSS32.esi, dd 0 + at X86TSS32.edi, dd 0 + at X86TSS32.es, dw BS3_SEL_R0_DS32 + at X86TSS32.padding_es, dw 0 + at X86TSS32.cs, dw BS3_SEL_R0_CS32 + at X86TSS32.padding_cs, dw 0 + at X86TSS32.ss, dw BS3_SEL_R0_SS32 + at X86TSS32.padding_ss, dw 0 + at X86TSS32.ds, dw BS3_SEL_R0_DS32 + at X86TSS32.padding_ds, dw 0 + at X86TSS32.fs, dw BS3_SEL_R0_DS32 + at X86TSS32.padding_fs, dw 0 + at X86TSS32.gs, dw BS3_SEL_R0_DS32 + at X86TSS32.padding_gs, dw 0 + at X86TSS32.selLdt, dw 0 + at X86TSS32.padding_ldt, dw 0 + at X86TSS32.fDebugTrap, dw 0 + at X86TSS32.offIoBitmap, dw 0 +iend + +;; +; A spare 32-bit TSS testcases to play around with. +BS3_GLOBAL_DATA Bs3Tss32Spare0, X86TSS32_size +istruc X86TSS32 + at X86TSS32.selPrev, dw 0 + at X86TSS32.padding1, dw 0 + at X86TSS32.esp0, dd BS3_ADDR_STACK_R0 + at X86TSS32.ss0, dw BS3_SEL_R0_SS32 + at X86TSS32.padding_ss0, dw 1 + at X86TSS32.esp1, dd 1 + at X86TSS32.ss1, dw BS3_SEL_R1_SS32 + at X86TSS32.padding_ss1, dw 1 + at X86TSS32.esp2, dd 1 + at X86TSS32.ss2, dw BS3_SEL_R2_SS32 + at X86TSS32.padding_ss2, dw 1 + at X86TSS32.cr3, dd 0 ; Will be filled in by routine setting up paged 32-bit mode w/ traps++. + at X86TSS32.eip, dd 0 ; Will be filled in by routine setting up 32-bit mode w/ traps++. + at X86TSS32.eflags, dd X86_EFL_1 + at X86TSS32.eax, dd 0 + at X86TSS32.ecx, dd 0 + at X86TSS32.edx, dd 0 + at X86TSS32.ebx, dd 0 + at X86TSS32.esp, dd BS3_ADDR_STACK_R0_IST2 + at X86TSS32.ebp, dd 0 + at X86TSS32.esi, dd 0 + at X86TSS32.edi, dd 0 + at X86TSS32.es, dw BS3_SEL_R0_DS32 + at X86TSS32.padding_es, dw 0 + at X86TSS32.cs, dw BS3_SEL_R0_CS32 + at X86TSS32.padding_cs, dw 0 + at X86TSS32.ss, dw BS3_SEL_R0_SS32 + at X86TSS32.padding_ss, dw 0 + at X86TSS32.ds, dw BS3_SEL_R0_DS32 + at X86TSS32.padding_ds, dw 0 + at X86TSS32.fs, dw BS3_SEL_R0_DS32 + at X86TSS32.padding_fs, dw 0 + at X86TSS32.gs, dw BS3_SEL_R0_DS32 + at X86TSS32.padding_gs, dw 0 + at X86TSS32.selLdt, dw 0 + at X86TSS32.padding_ldt, dw 0 + at X86TSS32.fDebugTrap, dw 0 + at X86TSS32.offIoBitmap, dw 0 +iend + +;; +; A spare 32-bit TSS testcases to play around with. +BS3_GLOBAL_DATA Bs3Tss32Spare1, X86TSS32_size +istruc X86TSS32 + at X86TSS32.selPrev, dw 0 + at X86TSS32.padding1, dw 0 + at X86TSS32.esp0, dd BS3_ADDR_STACK_R0 + at X86TSS32.ss0, dw BS3_SEL_R0_SS32 + at X86TSS32.padding_ss0, dw 1 + at X86TSS32.esp1, dd 1 + at X86TSS32.ss1, dw BS3_SEL_R1_SS32 + at X86TSS32.padding_ss1, dw 1 + at X86TSS32.esp2, dd 1 + at X86TSS32.ss2, dw BS3_SEL_R2_SS32 + at X86TSS32.padding_ss2, dw 1 + at X86TSS32.cr3, dd 0 ; Will be filled in by routine setting up paged 32-bit mode w/ traps++. + at X86TSS32.eip, dd 0 ; Will be filled in by routine setting up 32-bit mode w/ traps++. + at X86TSS32.eflags, dd X86_EFL_1 + at X86TSS32.eax, dd 0 + at X86TSS32.ecx, dd 0 + at X86TSS32.edx, dd 0 + at X86TSS32.ebx, dd 0 + at X86TSS32.esp, dd BS3_ADDR_STACK_R0_IST4 + at X86TSS32.ebp, dd 0 + at X86TSS32.esi, dd 0 + at X86TSS32.edi, dd 0 + at X86TSS32.es, dw BS3_SEL_R0_DS32 + at X86TSS32.padding_es, dw 0 + at X86TSS32.cs, dw BS3_SEL_R0_CS32 + at X86TSS32.padding_cs, dw 0 + at X86TSS32.ss, dw BS3_SEL_R0_SS32 + at X86TSS32.padding_ss, dw 0 + at X86TSS32.ds, dw BS3_SEL_R0_DS32 + at X86TSS32.padding_ds, dw 0 + at X86TSS32.fs, dw BS3_SEL_R0_DS32 + at X86TSS32.padding_fs, dw 0 + at X86TSS32.gs, dw BS3_SEL_R0_DS32 + at X86TSS32.padding_gs, dw 0 + at X86TSS32.selLdt, dw 0 + at X86TSS32.padding_ldt, dw 0 + at X86TSS32.fDebugTrap, dw 0 + at X86TSS32.offIoBitmap, dw 0 +iend + + + +;; +; 64-bit TSS +BS3_GLOBAL_DATA Bs3Tss64, X86TSS64_size +istruc X86TSS64 + at X86TSS64.u32Reserved, dd 0 + at X86TSS64.rsp0, dq BS3_ADDR_STACK_R0 + at X86TSS64.rsp1, dq BS3_ADDR_STACK_R1 + at X86TSS64.rsp2, dq BS3_ADDR_STACK_R2 + at X86TSS64.u32Reserved2, dd 0 + at X86TSS64.ist1, dq BS3_ADDR_STACK_R0_IST1 + at X86TSS64.ist2, dq BS3_ADDR_STACK_R0_IST2 + at X86TSS64.ist3, dq BS3_ADDR_STACK_R0_IST3 + at X86TSS64.ist4, dq BS3_ADDR_STACK_R0_IST4 + at X86TSS64.ist5, dq BS3_ADDR_STACK_R0_IST5 + at X86TSS64.ist6, dq BS3_ADDR_STACK_R0_IST6 + at X86TSS64.ist7, dq BS3_ADDR_STACK_R0_IST7 + at X86TSS64.u16Reserved, dw 0 + at X86TSS64.offIoBitmap, dw 0 +iend + +;; +; A spare TSS for testcases to play around with. +BS3_GLOBAL_DATA Bs3Tss64Spare0, X86TSS64_size +istruc X86TSS64 + at X86TSS64.u32Reserved, dd 0 + at X86TSS64.rsp0, dq BS3_ADDR_STACK_R0 + at X86TSS64.rsp1, dq BS3_ADDR_STACK_R1 + at X86TSS64.rsp2, dq BS3_ADDR_STACK_R2 + at X86TSS64.u32Reserved2, dd 0 + at X86TSS64.ist1, dq BS3_ADDR_STACK_R0_IST1 + at X86TSS64.ist2, dq BS3_ADDR_STACK_R0_IST2 + at X86TSS64.ist3, dq BS3_ADDR_STACK_R0_IST3 + at X86TSS64.ist4, dq BS3_ADDR_STACK_R0_IST4 + at X86TSS64.ist5, dq BS3_ADDR_STACK_R0_IST5 + at X86TSS64.ist6, dq BS3_ADDR_STACK_R0_IST6 + at X86TSS64.ist7, dq BS3_ADDR_STACK_R0_IST7 + at X86TSS64.u16Reserved, dw 0 + at X86TSS64.offIoBitmap, dw 0 +iend + +;; +; A spare TSS for testcases to play around with. +BS3_GLOBAL_DATA Bs3Tss64Spare1, X86TSS64_size +istruc X86TSS64 + at X86TSS64.u32Reserved, dd 0 + at X86TSS64.rsp0, dq BS3_ADDR_STACK_R0 + at X86TSS64.rsp1, dq BS3_ADDR_STACK_R1 + at X86TSS64.rsp2, dq BS3_ADDR_STACK_R2 + at X86TSS64.u32Reserved2, dd 0 + at X86TSS64.ist1, dq BS3_ADDR_STACK_R0_IST1 + at X86TSS64.ist2, dq BS3_ADDR_STACK_R0_IST2 + at X86TSS64.ist3, dq BS3_ADDR_STACK_R0_IST3 + at X86TSS64.ist4, dq BS3_ADDR_STACK_R0_IST4 + at X86TSS64.ist5, dq BS3_ADDR_STACK_R0_IST5 + at X86TSS64.ist6, dq BS3_ADDR_STACK_R0_IST6 + at X86TSS64.ist7, dq BS3_ADDR_STACK_R0_IST7 + at X86TSS64.u16Reserved, dw 0 + at X86TSS64.offIoBitmap, dw 0 +iend + + + +;; +; 64-bit TSS sharing an I/O permission bitmap (Bs3SharedIobp) with a 32-bit TSS. +; +BS3_GLOBAL_DATA Bs3Tss64WithIopb, X86TSS64_size +istruc X86TSS64 + at X86TSS64.u32Reserved, dd 0 + at X86TSS64.rsp0, dq BS3_ADDR_STACK_R0 + at X86TSS64.rsp1, dq BS3_ADDR_STACK_R1 + at X86TSS64.rsp2, dq BS3_ADDR_STACK_R2 + at X86TSS64.u32Reserved2, dd 0 + at X86TSS64.ist1, dq BS3_ADDR_STACK_R0_IST1 + at X86TSS64.ist2, dq BS3_ADDR_STACK_R0_IST2 + at X86TSS64.ist3, dq BS3_ADDR_STACK_R0_IST3 + at X86TSS64.ist4, dq BS3_ADDR_STACK_R0_IST4 + at X86TSS64.ist5, dq BS3_ADDR_STACK_R0_IST5 + at X86TSS64.ist6, dq BS3_ADDR_STACK_R0_IST6 + at X86TSS64.ist7, dq BS3_ADDR_STACK_R0_IST7 + at X86TSS64.u16Reserved, dw 0 + at X86TSS64.offIoBitmap, dw (BS3_DATA_NM(Bs3SharedIobp) - BS3_DATA_NM(Bs3Tss64WithIopb)) +iend + +;; +; 32-bit TSS sharing an I/O permission bitmap (Bs3SharedIobp) with a 64-bit TSS, +; and sporting an interrupt redirection bitmap (Bs3SharedIntRedirBm). +BS3_GLOBAL_DATA Bs3Tss32WithIopb, X86TSS32_size +istruc X86TSS32 + at X86TSS32.selPrev, dw 0 + at X86TSS32.padding1, dw 0 + at X86TSS32.esp0, dd BS3_ADDR_STACK_R0 + at X86TSS32.ss0, dw BS3_SEL_R0_SS32 + at X86TSS32.padding_ss0, dw 1 + at X86TSS32.esp1, dd 1 + at X86TSS32.ss1, dw BS3_SEL_R1_SS32 + at X86TSS32.padding_ss1, dw 1 + at X86TSS32.esp2, dd 1 + at X86TSS32.ss2, dw BS3_SEL_R2_SS32 + at X86TSS32.padding_ss2, dw 1 + at X86TSS32.cr3, dd 0 ; Will be filled in by routine setting up paged 32-bit mode w/ traps++. + at X86TSS32.eip, dd 0 ; Will be filled in by routine setting up 32-bit mode w/ traps++. + at X86TSS32.eflags, dd X86_EFL_1 + at X86TSS32.eax, dd 0 + at X86TSS32.ecx, dd 0 + at X86TSS32.edx, dd 0 + at X86TSS32.ebx, dd 0 + at X86TSS32.esp, dd 0 + at X86TSS32.ebp, dd 0 + at X86TSS32.esi, dd 0 + at X86TSS32.edi, dd 0 + at X86TSS32.es, dw 0 + at X86TSS32.padding_es, dw 0 + at X86TSS32.cs, dw 0 + at X86TSS32.padding_cs, dw 0 + at X86TSS32.ss, dw 0 + at X86TSS32.padding_ss, dw 0 + at X86TSS32.ds, dw 0 + at X86TSS32.padding_ds, dw 0 + at X86TSS32.fs, dw 0 + at X86TSS32.padding_fs, dw 0 + at X86TSS32.gs, dw 0 + at X86TSS32.padding_gs, dw 0 + at X86TSS32.selLdt, dw 0 + at X86TSS32.padding_ldt, dw 0 + at X86TSS32.fDebugTrap, dw 0 + at X86TSS32.offIoBitmap, dw (BS3_DATA_NM(Bs3SharedIobp) - BS3_DATA_NM(Bs3Tss32WithIopb)) +iend + +; +; We insert 6 bytes before the interrupt redirection bitmap just to make sure +; we've all got the same idea about where it starts (i.e. 32 bytes before IOBP). +; + times 6 db 0ffh + +;; +; Interrupt redirection bitmap (used by 32-bit TSS). +BS3_GLOBAL_DATA Bs3SharedIntRedirBm, 32 + times 32 db 00h + +;; +; Shared I/O permission bitmap used both by Bs3Tss64WithIopb and Bs3Tss32WithIopb. +BS3_GLOBAL_DATA Bs3SharedIobp, 8192+2 + times 8192+2 db 0ffh +BS3_GLOBAL_DATA Bs3SharedIobpEnd, 0 + + +align 128 + +;; +; 16-bit IDT. +; This requires manual setup by code fielding traps, so we'll just reserve the +; memory here. +; +BS3_GLOBAL_DATA Bs3Idt16, 256*8 + times 256 dq 0 + +;; +; 32-bit IDT. +; This requires manual setup by code fielding traps, so we'll just reserve the +; memory here. +; +BS3_GLOBAL_DATA Bs3Idt32, 256*8 + times 256 dq 0 + +;; +; 64-bit IDT. +; This requires manual setup by code fielding traps, so we'll just reserve the +; memory here. +; +BS3_GLOBAL_DATA Bs3Idt64, 256*16 + times 256 dq 0, 0 + + + times 6 db 0 ; Pad the first LIDT correctly. + +;; +; LIDT structure for the 16-bit IDT (8-byte aligned on offset). +BS3_GLOBAL_DATA Bs3Lidt_Idt16, 2+8 + dw 256*8 - 1 ; limit + dw BS3_SYSTEM16_BASE_LOW(Bs3Idt16) ; low offset + dw (BS3_ADDR_BS3SYSTEM16 >> 16) ; high offset + dd 0 ; top32 offset + + times 4 db 0 ; padding the start of the next + +;; +; LIDT structure for the 32-bit IDT (8-byte aligned on offset). +BS3_GLOBAL_DATA Bs3Lidt_Idt32, 2+8 + dw 256*8 - 1 ; limit + dw BS3_SYSTEM16_BASE_LOW(Bs3Idt32) ; low offset + dw (BS3_ADDR_BS3SYSTEM16 >> 16) ; high offset + dd 0 ; top32 offset + + times 4 db 0 ; padding the start of the next + +;; +; LIDT structure for the 64-bit IDT (8-byte aligned on offset). +BS3_GLOBAL_DATA Bs3Lidt_Idt64, 2+8 + dw 256*16 - 1 ; limit + dw BS3_SYSTEM16_BASE_LOW(Bs3Idt64) ; low offset + dw (BS3_ADDR_BS3SYSTEM16 >> 16) ; high offset + dd 0 ; top32 offset + + times 4 db 0 ; padding the start of the next + +;; +; LIDT structure for the real mode IVT at address 0x00000000 (8-byte aligned on offset). +BS3_GLOBAL_DATA Bs3Lidt_Ivt, 2+8 + dw 0ffffh ; limit + dw 0 ; low offset + dw 0 ; high offset + dd 0 ; top32 offset + + times 4 db 0 ; padding the start of the next + +;; +; LGDT structure for the current GDT (8-byte aligned on offset). +BS3_GLOBAL_DATA Bs3Lgdt_Gdt, 2+8 + dw BS3_DATA_NM(Bs3GdtEnd) - BS3_DATA_NM(Bs3Gdt) - 1 ; limit + dw BS3_SYSTEM16_BASE_LOW(Bs3Gdt) ; low offset + dw (BS3_ADDR_BS3SYSTEM16 >> 16) ; high offset + dd 0 ; top32 offset + +;; +; LGDT structure for the default GDT (8-byte aligned on offset). +; This must not be modified, whereas Bs3Lgdt_Gdt can be modified by the user. +BS3_GLOBAL_DATA Bs3LgdtDef_Gdt, 2+8 + dw BS3_DATA_NM(Bs3GdtEnd) - BS3_DATA_NM(Bs3Gdt) - 1 ; limit + dw BS3_SYSTEM16_BASE_LOW(Bs3Gdt) ; low offset + dw (BS3_ADDR_BS3SYSTEM16 >> 16) ; high offset + dd 0 ; top32 offset + + + +align 16 +;; +; LDT filling up the rest of the segment. +; +; Currently this starts at 0x84e0, which leaves us with 0xb20 bytes. We'll use +; the last 32 of those for an eye catcher. +; +BS3_GLOBAL_DATA Bs3Ldt, 0b20h - 32 + times (0b20h - 32) db 0 +BS3_GLOBAL_DATA Bs3LdtEnd, 0 + db 10, 13, 'eye-catcher: SYSTEM16 END', 10, 13, 0, 0, 0 ; 32 bytes long + +; +; Check the segment size. +; +%ifndef KBUILD_GENERATING_MAKEFILE_DEPENDENCIES + %if ($ - $$) != 09000h + %assign offActual ($ - $$) + %error "Bad BS3SYSTEM16 segment size: " %+ offActual %+ ", expected 0x9000 (36864)" + %endif +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-I4D.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-I4D.asm new file mode 100644 index 00000000..9a5a7f80 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-I4D.asm @@ -0,0 +1,80 @@ +; $Id: bs3-wc16-I4D.asm $ +;; @file +; BS3Kit - 16-bit Watcom C/C++, 32-bit signed integer division. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; 32-bit signed integer division. +; +; @returns DX:AX quotient, CX:BX remainder. +; @param DX:AX Dividend. +; @param CX:BX Divisor +; +; @uses Nothing. +; +global $_?I4D +$_?I4D: +;; @todo no idea if we're getting the negative division stuff right here according to what watcom expectes... +extern TODO_NEGATIVE_SIGNED_DIVISION + ; Move dividend into EDX:EAX + shl eax, 10h + mov ax, dx + sar dx, 0fh + movsx edx, dx + + ; Move divisor into ebx. + shl ebx, 10h + mov bx, cx + + ; Do it! + idiv ebx + + ; Reminder in to CX:BX + mov bx, dx + shr edx, 10h + mov cx, dx + + ; Quotient into DX:AX + mov edx, eax + shr edx, 10h + +%ifdef ASM_MODEL_FAR_CODE + retf +%else + ret +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-I8DQ.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-I8DQ.asm new file mode 100644 index 00000000..65e30eeb --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-I8DQ.asm @@ -0,0 +1,121 @@ +; $Id: bs3-wc16-I8DQ.asm $ +;; @file +; BS3Kit - 16-bit Watcom C/C++, 64-bit unsigned integer division. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +BS3_EXTERN_CMN Bs3Int64Div + + +;; +; 64-bit unsigned integer division, SS variant. +; +; @returns ax:bx:cx:dx quotient. (AX is the most significant, DX the least) +; @param ax:bx:cx:dx Dividend. +; @param [ss:si] Divisor +; +; @uses Nothing. +; +global $_?I8DQ +$_?I8DQ: + push es + push ss + pop es +%ifdef ASM_MODEL_FAR_CODE + push cs +%endif + call $_?I8DQE + pop es +%ifdef ASM_MODEL_FAR_CODE + retf +%else + ret +%endif + +;; +; 64-bit unsigned integer division, ES variant. +; +; @returns ax:bx:cx:dx quotient. (AX is the most significant, DX the least) +; @param ax:bx:cx:dx Dividend. +; @param [es:si] Divisor +; +; @uses Nothing. +; +global $_?I8DQE +$_?I8DQE: + push ds + push es + + ; + ; Convert to a C __cdecl call - not doing this in assembly. + ; + + ; Set up a frame of sorts, allocating 16 bytes for the result buffer. + push bp + sub sp, 10h + mov bp, sp + + ; Pointer to the return buffer. + push ss + push bp + add bp, 10h ; Correct bp. + + ; The divisor. + push dword [es:si + 4] + push dword [es:si] + + ; The dividend. + push ax + push bx + push cx + push dx + + call Bs3Int64Div + + ; Load the quotient. + mov ax, [bp - 10h + 8 + 6] + mov bx, [bp - 10h + 8 + 4] + mov cx, [bp - 10h + 8 + 2] + mov dx, [bp - 10h + 8] + + leave + pop es + pop ds +%ifdef ASM_MODEL_FAR_CODE + retf +%else + ret +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-I8DR.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-I8DR.asm new file mode 100644 index 00000000..ec00ea72 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-I8DR.asm @@ -0,0 +1,121 @@ +; $Id: bs3-wc16-I8DR.asm $ +;; @file +; BS3Kit - 16-bit Watcom C/C++, 64-bit unsigned integer modulo. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +BS3_EXTERN_CMN Bs3Int64Div + + +;; +; 64-bit unsigned integer modulo, SS variant. +; +; @returns ax:bx:cx:dx reminder. (AX is the most significant, DX the least) +; @param ax:bx:cx:dx Dividend. +; @param [ss:si] Divisor +; +; @uses Nothing. +; +global $_?I8DR +$_?I8DR: + push es + push ss + pop es +%ifdef ASM_MODEL_FAR_CODE + push cs +%endif + call $_?I8DRE + pop es +%ifdef ASM_MODEL_FAR_CODE + retf +%else + ret +%endif + +;; +; 64-bit unsigned integer modulo, ES variant. +; +; @returns ax:bx:cx:dx reminder. +; @param ax:bx:cx:dx Dividend. +; @param [es:si] Divisor +; +; @uses Nothing. +; +global $_?I8DRE +$_?I8DRE: + push ds + push es + + ; + ; Convert to a C __cdecl call - not doing this in assembly. + ; + + ; Set up a frame of sorts, allocating 16 bytes for the result buffer. + push bp + sub sp, 10h + mov bp, sp + + ; Pointer to the return buffer. + push ss + push bp + add bp, 10h ; Correct bp. + + ; The divisor. + push dword [es:si + 4] + push dword [es:si] + + ; The dividend. + push ax + push bx + push cx + push dx + + call Bs3Int64Div + + ; Load the reminder. + mov ax, [bp - 10h + 8 + 6] + mov bx, [bp - 10h + 8 + 4] + mov cx, [bp - 10h + 8 + 2] + mov dx, [bp - 10h + 8] + + leave + pop es + pop ds +%ifdef ASM_MODEL_FAR_CODE + retf +%else + ret +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-I8RS.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-I8RS.asm new file mode 100644 index 00000000..805f5d76 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-I8RS.asm @@ -0,0 +1,78 @@ +; $Id: bs3-wc16-I8RS.asm $ +;; @file +; BS3Kit - 16-bit Watcom C/C++, 64-bit signed integer right shift. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; 64-bit signed integer left shift. +; +; @returns AX:BX:CX:DX (AX is the most significant, DX the least) +; @param AX:BX:CX:DX Value to shift. +; @param SI Shift count. +; +global $_?I8RS +$_?I8RS: + push si + + ; + ; The 16-bit watcom code differs from the 32-bit one in the way it + ; handles the shift count. All 16-bit bits are used in the 16-bit + ; code, we do the same as the 32-bit one as we don't want to wast + ; time in the below loop. + ; + ; Using 8086 comatible approach here as it's less hazzle to write + ; and smaller. + ; + and si, 3fh + jz .return + +.next_shift: + sar ax, 1 + rcr bx, 1 + rcr cx, 1 + rcr dx, 1 + dec si + jnz .next_shift + +.return: + pop si +%ifdef ASM_MODEL_FAR_CODE + retf +%else + ret +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-U4D.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-U4D.asm new file mode 100644 index 00000000..878c077a --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-U4D.asm @@ -0,0 +1,124 @@ +; $Id: bs3-wc16-U4D.asm $ +;; @file +; BS3Kit - 16-bit Watcom C/C++, 32-bit unsigned integer division. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + + +;; +; 32-bit unsigned integer division. +; +; @returns DX:AX quotient, CX:BX remainder. +; @param DX:AX Dividend. +; @param CX:BX Divisor +; +; @uses Nothing. +; +%ifdef BS3KIT_WITH_REAL_WATCOM_INTRINSIC_NAMES +global __U4D +__U4D: +%endif +global $_?U4D +$_?U4D: +%if TMPL_BITS >= 32 + ; Move dividend into EDX:EAX + shl eax, 10h + mov ax, dx + xor edx, edx + + ; Move divisor into ebx. + shl ebx, 10h + mov bx, cx + + ; Do it! + div ebx + + ; Reminder in to CX:BX + mov bx, dx + shr edx, 10h + mov cx, dx + + ; Quotient into DX:AX + mov edx, eax + shr edx, 10h +%else + push ds + push es + + ; + ; Convert to a C __cdecl call - too lazy to do this in assembly. + ; + + ; Set up a frame of sorts, allocating 8 bytes for the result buffer. + push bp + sub sp, 08h + mov bp, sp + + ; Pointer to the return buffer. + push ss + push bp + add bp, 08h ; Correct bp. + + ; The divisor. + push cx + push bx + + ; The dividend. + push dx + push ax + + BS3_EXTERN_CMN Bs3UInt32Div + call Bs3UInt32Div + + ; Load the reminder. + mov cx, [bp - 08h + 6] + mov bx, [bp - 08h + 4] + ; Load the quotient. + mov dx, [bp - 08h + 2] + mov ax, [bp - 08h] + + mov sp, bp + pop bp + pop es + pop ds + +%endif +%ifdef ASM_MODEL_FAR_CODE + retf +%else + ret +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-U8DQ.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-U8DQ.asm new file mode 100644 index 00000000..e44104be --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-U8DQ.asm @@ -0,0 +1,124 @@ +; $Id: bs3-wc16-U8DQ.asm $ +;; @file +; BS3Kit - 16-bit Watcom C/C++, 64-bit unsigned integer division. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +BS3_EXTERN_CMN Bs3UInt64Div + + +;; +; 64-bit unsigned integer division, SS variant. +; +; @returns ax:bx:cx:dx quotient. (AX is the most significant, DX the least) +; @param ax:bx:cx:dx Dividend. +; @param [ss:si] Divisor +; +; @uses Nothing. +; +global $_?U8DQ +$_?U8DQ: + push es + push ss + pop es +%ifdef ASM_MODEL_FAR_CODE + push cs +%endif + call $_?U8DQE + pop es +%ifdef ASM_MODEL_FAR_CODE + retf +%else + ret +%endif + +;; +; 64-bit unsigned integer division, ES variant. +; +; @returns ax:bx:cx:dx quotient. (AX is the most significant, DX the least) +; @param ax:bx:cx:dx Dividend. +; @param [es:si] Divisor +; +; @uses Nothing. +; +global $_?U8DQE +$_?U8DQE: + push ds + push es + + ; + ; Convert to a C __cdecl call - not doing this in assembly. + ; + + ; Set up a frame of sorts, allocating 16 bytes for the result buffer. + push bp + sub sp, 10h + mov bp, sp + + ; Pointer to the return buffer. + push ss + push bp + add bp, 10h ; Correct bp. + + ; The divisor. + push word [es:si + 6] + push word [es:si + 4] + push word [es:si + 2] + push word [es:si] + + ; The dividend. + push ax + push bx + push cx + push dx + + call Bs3UInt64Div + + ; Load the quotient. + mov ax, [bp - 10h + 6] + mov bx, [bp - 10h + 4] + mov cx, [bp - 10h + 2] + mov dx, [bp - 10h] + + mov sp, bp + pop bp + pop es + pop ds +%ifdef ASM_MODEL_FAR_CODE + retf +%else + ret +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-U8DR.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-U8DR.asm new file mode 100644 index 00000000..4c118efb --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-U8DR.asm @@ -0,0 +1,124 @@ +; $Id: bs3-wc16-U8DR.asm $ +;; @file +; BS3Kit - 16-bit Watcom C/C++, 64-bit unsigned integer modulo. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +BS3_EXTERN_CMN Bs3UInt64Div + + +;; +; 64-bit unsigned integer modulo, SS variant. +; +; @returns ax:bx:cx:dx reminder. (AX is the most significant, DX the least) +; @param ax:bx:cx:dx Dividend. +; @param [ss:si] Divisor +; +; @uses Nothing. +; +global $_?U8DR +$_?U8DR: + push es + push ss + pop es +%ifdef ASM_MODEL_FAR_CODE + push cs +%endif + call $_?U8DRE + pop es +%ifdef ASM_MODEL_FAR_CODE + retf +%else + ret +%endif + +;; +; 64-bit unsigned integer modulo, ES variant. +; +; @returns ax:bx:cx:dx reminder. (AX is the most significant, DX the least) +; @param ax:bx:cx:dx Dividend. +; @param [es:si] Divisor +; +; @uses Nothing. +; +global $_?U8DRE +$_?U8DRE: + push ds + push es + + ; + ; Convert to a C __cdecl call - not doing this in assembly. + ; + + ; Set up a frame of sorts, allocating 16 bytes for the result buffer. + push bp + sub sp, 10h + mov bp, sp + + ; Pointer to the return buffer. + push ss + push bp + add bp, 10h ; Correct bp. + + ; The divisor. + push word [es:si + 6] + push word [es:si + 4] + push word [es:si + 2] + push word [es:si] + + ; The dividend. + push ax + push bx + push cx + push dx + + call Bs3UInt64Div + + ; Load the reminder. + mov ax, [bp - 10h + 8 + 6] + mov bx, [bp - 10h + 8 + 4] + mov cx, [bp - 10h + 8 + 2] + mov dx, [bp - 10h + 8] + + mov sp, bp + pop bp + pop es + pop ds +%ifdef ASM_MODEL_FAR_CODE + retf +%else + ret +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-U8LS.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-U8LS.asm new file mode 100644 index 00000000..b43e64e6 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-U8LS.asm @@ -0,0 +1,80 @@ +; $Id: bs3-wc16-U8LS.asm $ +;; @file +; BS3Kit - 16-bit Watcom C/C++, 64-bit integer left shift. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; 64-bit integer left shift. +; +; @returns AX:BX:CX:DX (AX is the most significant, DX the least) +; @param AX:BX:CX:DX Value to shift. +; @param SI Shift count. +; +global $_?U8LS +$_?U8LS: +global $_?I8LS +$_?I8LS: + push si + + ; + ; The 16-bit watcom code differs from the 32-bit one in the way it + ; handles the shift count. All 16-bit bits are used in the 16-bit + ; code, we do the same as the 32-bit one as we don't want to wast + ; time in the below loop. + ; + ; Using 8086 compatible approach here as it's less hazzle to write + ; and smaller. + ; + and si, 3fh + + jz .return +.next_shift: + shl dx, 1 + rcl cx, 1 + rcl bx, 1 + rcl ax, 1 + dec si + jnz .next_shift + +.return: + pop si +%ifdef ASM_MODEL_FAR_CODE + retf +%else + ret +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-U8RS.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-U8RS.asm new file mode 100644 index 00000000..6d860f0b --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc16-U8RS.asm @@ -0,0 +1,82 @@ +; $Id: bs3-wc16-U8RS.asm $ +;; @file +; BS3Kit - 16-bit Watcom C/C++, 64-bit unsigned integer right shift. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; 64-bit unsigned integer left shift. +; +; @returns AX:BX:CX:DX (AX is the most significant, DX the least) +; @param AX:BX:CX:DX Value to shift. +; @param SI Shift count. +; +%ifdef BS3KIT_WITH_REAL_WATCOM_INTRINSIC_NAMES +global __U8RS +__U8RS: +%endif +global $_?U8RS +$_?U8RS: + push si + + ; + ; The 16-bit watcom code differs from the 32-bit one in the way it + ; handles the shift count. All 16-bit bits are used in the 16-bit + ; code, we do the same as the 32-bit one as we don't want to wast + ; time in the below loop. + ; + ; Using 8086 comatible approach here as it's less hazzle to write + ; and smaller. + ; + and si, 3fh + jz .return + +.next_shift: + shr ax, 1 + rcr bx, 1 + rcr cx, 1 + rcr dx, 1 + dec si + jnz .next_shift + +.return: + pop si +%ifdef ASM_MODEL_FAR_CODE + retf +%else + ret +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc32-I8D.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc32-I8D.asm new file mode 100644 index 00000000..beefae02 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc32-I8D.asm @@ -0,0 +1,81 @@ +; $Id: bs3-wc32-I8D.asm $ +;; @file +; BS3Kit - 32-bit Watcom C/C++, 64-bit signed integer division. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +BS3_EXTERN_CMN Bs3Int64Div + + +;; +; 64-bit signed integer division. +; +; @returns EDX:EAX Quotient, ECX:EBX Remainder. +; @param EDX:EAX Dividend. +; @param ECX:EBX Divisor +; +global $??I8D +$??I8D: + ; + ; Convert to a C __cdecl call - not doing this in assembly. + ; + + ; Set up a frame, allocating 16 bytes for the result buffer. + push ebp + mov ebp, esp + sub esp, 10h + + ; Pointer to the return buffer. + push esp + + ; The dividend. + push ecx + push ebx + + ; The dividend. + push edx + push eax + + call Bs3Int64Div + + ; Load the result. + mov ecx, [ebp - 10h + 12] + mov ebx, [ebp - 10h + 8] + mov edx, [ebp - 10h + 4] + mov eax, [ebp - 10h] + + leave + ret + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc32-I8RS.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc32-I8RS.asm new file mode 100644 index 00000000..c0d3abc6 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc32-I8RS.asm @@ -0,0 +1,70 @@ +; $Id: bs3-wc32-I8RS.asm $ +;; @file +; BS3Kit - 32-bit Watcom C/C++, 64-bit signed integer right shift. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; 64-bit signed integer left shift. +; +; @returns EDX:EAX +; @param EDX:EAX Value to shift. +; @param BL Shift count (it's specified as ECX:EBX, but we only use BL). +; +global $??I8RS +$??I8RS: + push ecx ; We're allowed to trash ECX, but why bother. + + mov cl, bl + and cl, 3fh + test cl, 20h + jnz .big_shift + + ; Shifting less than 32. + shrd eax, edx, cl + sar edx, cl + +.return: + pop ecx + ret + +.big_shift: + ; Shifting 32 or more. + mov eax, edx + sar eax, cl ; Only uses lower 5 bits. + sar edx, 1fh ; Sign extend it. + jmp .return + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc32-U8D.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc32-U8D.asm new file mode 100644 index 00000000..34ccfbc9 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc32-U8D.asm @@ -0,0 +1,81 @@ +; $Id: bs3-wc32-U8D.asm $ +;; @file +; BS3Kit - 32-bit Watcom C/C++, 64-bit unsigned integer division. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + +BS3_EXTERN_CMN Bs3UInt64Div + + +;; +; 64-bit unsigned integer division. +; +; @returns EDX:EAX Quotient, ECX:EBX Remainder. +; @param EDX:EAX Dividend. +; @param ECX:EBX Divisor +; +global $??U8D +$??U8D: + ; + ; Convert to a C __cdecl call - not doing this in assembly. + ; + + ; Set up a frame, allocating 16 bytes for the result buffer. + push ebp + mov ebp, esp + sub esp, 10h + + ; Pointer to the return buffer. + push esp + + ; The divisor. + push ecx + push ebx + + ; The dividend. + push edx + push eax + + call Bs3UInt64Div + + ; Load the result. + mov ecx, [ebp - 10h + 12] + mov ebx, [ebp - 10h + 8] + mov edx, [ebp - 10h + 4] + mov eax, [ebp - 10h] + + leave + ret + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc32-U8LS.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc32-U8LS.asm new file mode 100644 index 00000000..626e1b4e --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc32-U8LS.asm @@ -0,0 +1,72 @@ +; $Id: bs3-wc32-U8LS.asm $ +;; @file +; BS3Kit - 32-bit Watcom C/C++, 64-bit integer left shift. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; 64-bit integer left shift. +; +; @returns EDX:EAX +; @param EDX:EAX Value to shift. +; @param BL Shift count (it's specified as ECX:EBX, but we only use BL). +; +global $??U8LS +$??U8LS: +global $??I8LS +$??I8LS: + push ecx ; We're allowed to trash ECX, but why bother. + + mov cl, bl + and cl, 3fh + test cl, 20h + jnz .big_shift + + ; Shifting less than 32. + shld edx, eax, cl + shl eax, cl + +.return: + pop ecx + ret + +.big_shift: + ; Shifting 32 or more. + mov edx, eax + shl edx, cl ; Only uses lower 5 bits. + xor eax, eax + jmp .return + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc32-U8M.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc32-U8M.asm new file mode 100644 index 00000000..e94aafe4 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc32-U8M.asm @@ -0,0 +1,93 @@ +; $Id: bs3-wc32-U8M.asm $ +;; @file +; BS3Kit - 32-bit Watcom C/C++, 64-bit integer multiplication. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; 64-bit unsigned & signed integer multiplication. +; +; @returns EDX:EAX as the product. +; @param EDX:EAX Factor 1 - edx=F1H, eax=F1L. +; @param ECX:EBX Factor 2 - ecx=F2H, ebx=F2L. +; +global $??I8M +$??I8M: +global $??U8M +$??U8M: + ; + ; If both the high dwords are zero, we can get away with + ; a simple 32-bit multiplication. + ; + test ecx, ecx + jnz .big + test edx, edx + jnz .big + mul ebx + ret + +.big: + ; + ; Imagine we use 4294967296-base (2^32), so each factor has two + ; digits H and L, thus we have: F1H:F1L * F2H:F1L which we can + ; multipy like we learned in primary school. Since the result + ; is limited to 64-bit, we can skip F1H*F2H and discard the + ; high 32-bit in F1L*F2H and F1H*F2L. + ; result = ((F1L*F2H) << 32) + ; + ((F1H*F2L) << 32) + ; + (F1L*F2L); + ; + push ecx ; Preserve ECX just to be nice. + push eax ; Stash F1L for later. + push edx ; Stash F1H for later. + + ; ECX = F1L*F2H + mul ecx + mov ecx, eax + + ; ECX += F1H * F2L + pop eax + mul ebx + add ecx, eax + + ; EDX:EAX = F1L * F2L + pop eax + mul ebx + add edx, ecx + + pop ecx + ret + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc32-U8RS.asm b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc32-U8RS.asm new file mode 100644 index 00000000..5d4741c2 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-wc32-U8RS.asm @@ -0,0 +1,70 @@ +; $Id: bs3-wc32-U8RS.asm $ +;; @file +; BS3Kit - 32-bit Watcom C/C++, 64-bit unsigned integer right shift. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit-template-header.mac" + + +;; +; 64-bit unsigned integer right shift. +; +; @returns EDX:EAX +; @param EDX:EAX Value to shift. +; @param BL Shift count (it's specified as ECX:EBX, but we only use BL). +; +global $??U8RS +$??U8RS: + push ecx ; We're allowed to trash ECX, but why bother. + + mov cl, bl + and cl, 3fh + test cl, 20h + jnz .big_shift + + ; Shifting less than 32. + shrd eax, edx, cl + shr edx, cl + +.return: + pop ecx + ret + +.big_shift: + ; Shifting 32 or more. + mov eax, edx + shr eax, cl ; Only uses lower 5 bits. + xor edx, edx + jmp .return + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3cpudt.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3cpudt.c new file mode 100644 index 00000000..ff33f32b --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3cpudt.c @@ -0,0 +1,71 @@ +/* $Id: bs3cpudt.c $ */ +/** @file + * BS3Kit - Tests Bs3CpuDetect_rm. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ + +#include "bs3kit.h" +#include <stdio.h> +#include <stdint.h> + + +unsigned StoreMsw(void); +#pragma aux StoreMsw = \ + ".286" \ + "smsw ax" \ + value [ax]; + +void LoadMsw(unsigned); +#pragma aux LoadMsw = \ + ".286p" \ + "lmsw ax" \ + parm [ax]; + +int main() +{ + uint16_t volatile usCpu = Bs3CpuDetect_rm(); + printf("usCpu=%#x\n", usCpu); + if ((usCpu & BS3CPU_TYPE_MASK) >= BS3CPU_80286) + { + printf("(42=%d) msw=%#x (42=%d)\n", 42, StoreMsw(), 42); + LoadMsw(0); + printf("lmsw 0 => msw=%#x (42=%d)\n", StoreMsw(), 42); + } + return 0; +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-autostubs.kmk b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-autostubs.kmk new file mode 100644 index 00000000..5c47f3b0 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-autostubs.kmk @@ -0,0 +1,194 @@ +# $Id: bs3kit-autostubs.kmk $ +## @file +# BS3Kit - Automatic near/far stubs - generated by the bs3kit-autostubs.kmk makefile rule. +# + +# +# Copyright (C) 2007-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,Bs3SelFlatDataToProtFar16,4) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,Bs3SelFlatDataToRealMode,4) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,Bs3SelLnkPtrToFlat,4) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,Bs3SelProtFar16DataToFlat,4) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,Bs3SelProtFar16DataToRealMode,4) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,Bs3SelRealModeCodeToFlat,4) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,Bs3SelRealModeDataToFlat,4) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,Bs3SelRealModeDataToProtFar16,4) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,Bs3ExtCtxRestoreEx,4) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,Bs3ExtCtxRestore,4) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,Bs3ExtCtxSaveEx,4) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,Bs3ExtCtxSave,4) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,Bs3SelLnkPtrToCurPtr,4) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,Bs3SelFar32ToFlat32NoClobber,6) +$(call BS3KIT_FN_GEN_CMN_FARSTUB,bs3kit-common-16,Bs3RegCtxSaveEx,8) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxSetAbridgedFtw) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxSetMm) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxSetMxCsrMask) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxSetMxCsr) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxSetXmm) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxSetYmm) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3RegCtxSetGpr) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestCheckExtCtx) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestCheckRegCtxEx) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestFailed) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestFailedF) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestFailedV) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestQueryCfgBool) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3GetCpuVendor) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3StrCpy) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3GetModeNameShortLower) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3GetModeName) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3PagingAlias) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3PagingInitRootForLM) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3PagingInitRootForPAE) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3PagingInitRootForPP) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3PagingMapRamAbove4GForLM) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3PagingProtectPtr) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3PagingProtect) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3PagingQueryAddressInfo) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3PagingUnalias) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3SwitchFromV86To16BitAndCallC) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxAlloc) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxCopy) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxInit) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TrapSetHandler) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxGetXmm) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxGetYmm) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3Printf) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3PrintfV) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3StrFormatV) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3StrLen) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3StrNLen) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3StrPrintf) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3StrPrintfV) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxGetAbridgedFtw) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxGetFcw) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxGetFsw) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxGetSize) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3PicUpdateMask) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3SlabFree) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestSubErrorCount) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxGetMxCsrMask) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxGetMxCsr) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3SelFar32ToFlat32) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3SelProtFar32ToFlat32) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestQueryCfgU32) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxGetMm) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestNow) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestQueryCfgU8) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TrapSetDpl) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxFree) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxSetFcw) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3ExtCtxSetFsw) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3MemAlloc) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3MemAllocZ) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3MemCpy) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3MemGuardedTestPageAlloc) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3MemGuardedTestPageAllocEx) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3MemMove) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3MemPCpy) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3PagingGetPte) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3PagingSetupCanonicalTraps) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3RegCtxGetRspSsAsCurPtr) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3SlabAllocEx) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3SlabAlloc) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3SlabListAllocEx) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3SlabListAlloc) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3MemFree) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3MemGuardedTestPageFree) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3MemPrintInfo) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3PicMaskAll) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3PicSetup) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3PitDisable) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3PitSetupAndEnablePeriodTimer) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3PrintStr) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3RegCtxConvertToRingX) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3RegCtxConvertV86ToRm) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3RegCtxPrint) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3RegCtxSaveForMode) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3RegCtxSetGrpDsFromCurPtr) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3RegCtxSetGrpSegFromCurPtr) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3RegCtxSetGrpSegFromFlat) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3RegCtxSetRipCsFromCurPtr) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3RegCtxSetRipCsFromFlat) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3RegCtxSetRipCsFromLnkPtr) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3SelSetup16BitCode) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3SelSetup16BitData) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3SelSetup32BitCode) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3SelSetupGate64) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3SelSetupGate) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3SlabInit) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3SlabListAdd) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3SlabListFree) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3SlabListInit) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestHostPrintf) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestHostPrintfV) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestInit) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestPrintf) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestPrintfV) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestSkipped) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestSkippedF) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestSkippedV) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestSub) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestSubDone) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestSubF) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestSubV) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestTerm) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TestValue) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3Trap16InitEx) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3Trap16Init) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3Trap16SetGate) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3Trap32Init) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3Trap32SetGate) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3Trap64InitEx) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3Trap64Init) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3Trap64SetGate) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TrapDefaultHandler) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TrapPrintFrame) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TrapReInit) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TrapRmV86InitEx) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TrapRmV86Init) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TrapRmV86SetGate) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TrapSetHandlerEx) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TrapSetJmpAndRestoreInRm) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TrapSetJmpAndRestore) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TrapSetJmpAndRestoreWithExtCtxAndRm) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TrapSetJmpAndRestoreWithExtCtx) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TrapSetJmpAndRestoreWithRm) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3TrapUnsetJmp) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3UInt32Div) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,Bs3UInt64Div) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,bs3PagingGetLegacyPte) +$(call BS3KIT_FN_GEN_CMN_NEARSTUB,bs3kit-common-16,bs3PagingGetPaePte) +$(call BS3KIT_FN_GEN_MODE_NEARSTUB,bs3kit-common-16,Bs3BiosInt15hE820) +$(call BS3KIT_FN_GEN_MODE_NEARSTUB,bs3kit-common-16,Bs3SwitchTo32BitAndCallC) +$(call BS3KIT_FN_GEN_MODE_NEARSTUB,bs3kit-common-16,Bs3BiosInt15h88) +$(call BS3KIT_FN_GEN_MODE_NEARSTUB,bs3kit-common-16,Bs3TrapInit) diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-docs.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-docs.c new file mode 100644 index 00000000..142a9c75 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-docs.c @@ -0,0 +1,170 @@ +/* $Id: bs3kit-docs.c $ */ +/** @file + * BS3Kit - Documentation. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + + +/** @page pg_bs3kit BS3Kit - Boot Sector Kit \#3 + * + * The BS3Kit is a framework for bare metal floppy/usb image tests. + * + * The 3rd iteration of the framework includes support for 16-bit and 32-bit + * C/C++ code, with provisions for 64-bit C code to possibly be added later. + * The C code have to do without a runtime library, otherwhat what we can share + * possibly with IPRT. + * + * This iteration also adds a real linker into the picture, which is an + * improvment over early when all had to done in a single assembler run with + * lots of includes and macros controlling what we needed. The functions are no + * in separate files and compiled/assembled into libraries, so the linker will + * only include exactly what is needed. The current linker is the OpenWatcom + * one, wlink, that we're already using when building the BIOSes. If it wasn't + * for the segment/selector fixups in 16-bit code (mostly), maybe we could + * convince the ELF linker from GNU binutils to do the job too (with help from + * the ). + * + * + * @sa grp_bs3kit, grp_bs3kit_tmpl, grp_bs3kit_cmn, grp_bs3kit_mode, + * grp_bs3kit_system + * + * @section sec_calling_convention Calling convention + * + * Because we're not mixing with C code, we will use __cdecl for 16-bit and + * 32-bit code, where as 64-bit code will use the microsoft calling AMD64 + * convention. To avoid unnecessary %ifdef'ing in assembly code, we will use a + * macro to load the RCX, RDX, R8 and R9 registers off the stack in 64-bit + * assembly code. + * + * Register treatment in 16-bit __cdecl, 32-bit __cdecl and 64-bit msabi: + * + * | Register | 16-bit | 32-bit | 64-bit | ASM template | + * | ------------ | ----------- | ---------- | --------------- | ------------ | + * | EAX, RAX | volatile | volatile | volatile | volatile | + * | EBX, RBX | volatile | preserved | preserved | both | + * | ECX, RCX | volatile | volatile | volatile, arg 0 | volatile | + * | EDX, RDX | volatile | volatile | volatile, arg 1 | volatile | + * | ESP, RSP | preserved | preserved | preserved | preserved | + * | EBP, RBP | preserved | preserved | preserved | preserved | + * | EDI, RDI | preserved | preserved | preserved | preserved | + * | ESI, RSI | preserved | preserved | preserved | preserved | + * | R8 | volatile | volatile | volatile, arg 2 | volatile | + * | R9 | volatile | volatile | volatile, arg 3 | volatile | + * | R10 | volatile | volatile | volatile | volatile | + * | R11 | volatile | volatile | volatile | volatile | + * | R12 | volatile | volatile | preserved | preserved(*) | + * | R13 | volatile | volatile | preserved | preserved(*) | + * | R14 | volatile | volatile | preserved | preserved(*) | + * | R15 | volatile | volatile | preserved | preserved(*) | + * | RFLAGS.DF | =0 | =0 | =0 | =0 | + * | CS | preserved | preserved | preserved | preserved | + * | DS | preserved! | preserved? | preserved | both | + * | ES | volatile | volatile | preserved | volatile | + * | FS | preserved | preserved | preserved | preserved | + * | GS | preserved | volatile | preserved | both | + * | SS | preserved | preserved | preserved | preserved | + * + * The 'both' here means that we preserve it wrt to our caller, while at the + * same time assuming anything we call will clobber it. + * + * The 'preserved(*)' marking of R12-R15 indicates that they'll be preserved in + * 64-bit mode, but may be changed in certain cases when running 32-bit or + * 16-bit code. This is especially true if switching CPU mode, e.g. from 32-bit + * protected mode to 32-bit long mode. + * + * Return values are returned in the xAX register, but with the following + * caveats for values larger than ARCH_BITS: + * - 16-bit code: + * - 32-bit values are returned in AX:DX, where AX holds bits 15:0 and + * DX bits 31:16. + * - 64-bit values are returned in DX:CX:BX:AX, where DX holds bits + * 15:0, CX bits 31:16, BX bits 47:32, and AX bits 63:48. + * - 32-bit code: + * - 64-bit values are returned in EAX:EDX, where eax holds the least + * significant bits. + * + * The DS segment register is pegged to BS3DATA16_GROUP in 16-bit code so that + * we don't need to reload it all the time. This allows us to modify it in + * ring-0 and mode switching code without ending up in any serious RPL or DPL + * trouble. In 32-bit and 64-bit mode the DS register is a flat, unlimited, + * writable selector. + * + * In 16-bit and 32-bit code we do not assume anything about ES, FS, and GS. + * + * + * For an in depth coverage of x86 and AMD64 calling convensions, see + * http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/function-calling-conventions.html + * + * + * + * @section sec_modes Execution Modes + * + * BS3Kit defines a number of execution modes in order to be able to test the + * full CPU capabilities (that VirtualBox care about anyways). It currently + * omits system management mode, hardware virtualization modes, and security + * modes as those aren't supported by VirtualBox or are difficult to handle. + * + * The modes are categorized into normal and weird ones. + * + * The normal ones are: + * + RM - Real mode. + * + PE16 - Protected mode running 16-bit code, 16-bit TSS and 16-bit handlers. + * + PE32 - Protected mode running 32-bit code, 32-bit TSS and 32-bit handlers. + * + PEV86 - Protected mode running v8086 code, 32-bit TSS and 32-bit handlers. + * + PP16 - 386 paged mode running 16-bit code, 16-bit TSS and 16-bit handlers. + * + PP32 - 386 paged mode running 32-bit code, 32-bit TSS and 32-bit handlers. + * + PPV86 - 386 paged mode running v8086 code, 32-bit TSS and 32-bit handlers. + * + PAE16 - PAE paged mode running 16-bit code, 16-bit TSS and 16-bit handlers. + * + PAE32 - PAE paged mode running 32-bit code, 32-bit TSS and 32-bit handlers. + * + PAEV86 - PAE paged mode running v8086 code, 32-bit TSS and 32-bit handlers. + * + LM16 - AMD64 long mode running 16-bit code, 64-bit TSS and 64-bit handlers. + * + LM32 - AMD64 long mode running 32-bit code, 64-bit TSS and 64-bit handlers. + * + LM64 - AMD64 long mode running 64-bit code, 64-bit TSS and 64-bit handlers. + * + * The weird ones: + * + PE16_32 - Protected mode running 16-bit code, 16-bit TSS and 16-bit handlers. + * + PE16_V86 - Protected mode running 16-bit code, 16-bit TSS and 16-bit handlers. + * + PE32_16 - Protected mode running 32-bit code, 32-bit TSS and 32-bit handlers. + * + PP16_32 - 386 paged mode running 16-bit code, 16-bit TSS and 16-bit handlers. + * + PP16_V86 - 386 paged mode running 16-bit code, 16-bit TSS and 16-bit handlers. + * + PP32_16 - 386 paged mode running 32-bit code, 32-bit TSS and 32-bit handlers. + * + PAE16_32 - PAE paged mode running 16-bit code, 16-bit TSS and 16-bit handlers. + * + PAE16_V86 - PAE paged mode running 16-bit code, 16-bit TSS and 16-bit handlers. + * + PAE32_16 - PAE paged mode running 32-bit code, 32-bit TSS and 32-bit handlers. + * + * Actually, the PE32_16, PP32_16 and PAE32_16 modes aren't all that weird and fits in + * right next to LM16 and LM32, but this is the way it ended up. :-) + * + */ + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-define.h b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-define.h new file mode 100644 index 00000000..ecfe333c --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-define.h @@ -0,0 +1,256 @@ +/* $Id: bs3kit-mangling-code-define.h $ */ +/** @file + * BS3Kit - Function needing mangling - generated by the bs3kit-mangling-code-define.h makefile rule. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define Bs3A20Disable BS3_CMN_MANGLER(Bs3A20Disable) +#define Bs3A20DisableViaKbd BS3_CMN_MANGLER(Bs3A20DisableViaKbd) +#define Bs3A20DisableViaPortA BS3_CMN_MANGLER(Bs3A20DisableViaPortA) +#define Bs3A20Enable BS3_CMN_MANGLER(Bs3A20Enable) +#define Bs3A20EnableViaKbd BS3_CMN_MANGLER(Bs3A20EnableViaKbd) +#define Bs3A20EnableViaPortA BS3_CMN_MANGLER(Bs3A20EnableViaPortA) +#define Bs3ExtCtxAlloc BS3_CMN_MANGLER(Bs3ExtCtxAlloc) +#define Bs3ExtCtxCopy BS3_CMN_MANGLER(Bs3ExtCtxCopy) +#define Bs3ExtCtxFree BS3_CMN_MANGLER(Bs3ExtCtxFree) +#define Bs3ExtCtxGetAbridgedFtw BS3_CMN_MANGLER(Bs3ExtCtxGetAbridgedFtw) +#define Bs3ExtCtxGetFcw BS3_CMN_MANGLER(Bs3ExtCtxGetFcw) +#define Bs3ExtCtxGetFsw BS3_CMN_MANGLER(Bs3ExtCtxGetFsw) +#define Bs3ExtCtxGetMm BS3_CMN_MANGLER(Bs3ExtCtxGetMm) +#define Bs3ExtCtxGetMxCsr BS3_CMN_MANGLER(Bs3ExtCtxGetMxCsr) +#define Bs3ExtCtxGetMxCsrMask BS3_CMN_MANGLER(Bs3ExtCtxGetMxCsrMask) +#define Bs3ExtCtxGetSize BS3_CMN_MANGLER(Bs3ExtCtxGetSize) +#define Bs3ExtCtxGetXmm BS3_CMN_MANGLER(Bs3ExtCtxGetXmm) +#define Bs3ExtCtxGetYmm BS3_CMN_MANGLER(Bs3ExtCtxGetYmm) +#define Bs3ExtCtxInit BS3_CMN_MANGLER(Bs3ExtCtxInit) +#define Bs3ExtCtxRestore BS3_CMN_MANGLER(Bs3ExtCtxRestore) +#define Bs3ExtCtxRestoreEx BS3_CMN_MANGLER(Bs3ExtCtxRestoreEx) +#define Bs3ExtCtxSave BS3_CMN_MANGLER(Bs3ExtCtxSave) +#define Bs3ExtCtxSaveEx BS3_CMN_MANGLER(Bs3ExtCtxSaveEx) +#define Bs3ExtCtxSetAbridgedFtw BS3_CMN_MANGLER(Bs3ExtCtxSetAbridgedFtw) +#define Bs3ExtCtxSetFcw BS3_CMN_MANGLER(Bs3ExtCtxSetFcw) +#define Bs3ExtCtxSetFsw BS3_CMN_MANGLER(Bs3ExtCtxSetFsw) +#define Bs3ExtCtxSetMm BS3_CMN_MANGLER(Bs3ExtCtxSetMm) +#define Bs3ExtCtxSetMxCsr BS3_CMN_MANGLER(Bs3ExtCtxSetMxCsr) +#define Bs3ExtCtxSetMxCsrMask BS3_CMN_MANGLER(Bs3ExtCtxSetMxCsrMask) +#define Bs3ExtCtxSetXmm BS3_CMN_MANGLER(Bs3ExtCtxSetXmm) +#define Bs3ExtCtxSetYmm BS3_CMN_MANGLER(Bs3ExtCtxSetYmm) +#define Bs3GetCpuVendor BS3_CMN_MANGLER(Bs3GetCpuVendor) +#define Bs3GetModeName BS3_CMN_MANGLER(Bs3GetModeName) +#define Bs3GetModeNameShortLower BS3_CMN_MANGLER(Bs3GetModeNameShortLower) +#define Bs3KbdRead BS3_CMN_MANGLER(Bs3KbdRead) +#define Bs3KbdWait BS3_CMN_MANGLER(Bs3KbdWait) +#define Bs3KbdWrite BS3_CMN_MANGLER(Bs3KbdWrite) +#define Bs3MemAlloc BS3_CMN_MANGLER(Bs3MemAlloc) +#define Bs3MemAllocZ BS3_CMN_MANGLER(Bs3MemAllocZ) +#define Bs3MemChr BS3_CMN_MANGLER(Bs3MemChr) +#define Bs3MemCmp BS3_CMN_MANGLER(Bs3MemCmp) +#define Bs3MemCpy BS3_CMN_MANGLER(Bs3MemCpy) +#define Bs3MemFree BS3_CMN_MANGLER(Bs3MemFree) +#define Bs3MemGuardedTestPageAlloc BS3_CMN_MANGLER(Bs3MemGuardedTestPageAlloc) +#define Bs3MemGuardedTestPageAllocEx BS3_CMN_MANGLER(Bs3MemGuardedTestPageAllocEx) +#define Bs3MemGuardedTestPageFree BS3_CMN_MANGLER(Bs3MemGuardedTestPageFree) +#define Bs3MemMove BS3_CMN_MANGLER(Bs3MemMove) +#define Bs3MemPCpy BS3_CMN_MANGLER(Bs3MemPCpy) +#define Bs3MemPrintInfo BS3_CMN_MANGLER(Bs3MemPrintInfo) +#define Bs3MemSet BS3_CMN_MANGLER(Bs3MemSet) +#define Bs3MemZero BS3_CMN_MANGLER(Bs3MemZero) +#define Bs3PagingAlias BS3_CMN_MANGLER(Bs3PagingAlias) +#define bs3PagingGetLegacyPte BS3_CMN_MANGLER(bs3PagingGetLegacyPte) +#define bs3PagingGetPaePte BS3_CMN_MANGLER(bs3PagingGetPaePte) +#define Bs3PagingGetPte BS3_CMN_MANGLER(Bs3PagingGetPte) +#define Bs3PagingInitRootForLM BS3_CMN_MANGLER(Bs3PagingInitRootForLM) +#define Bs3PagingInitRootForPAE BS3_CMN_MANGLER(Bs3PagingInitRootForPAE) +#define Bs3PagingInitRootForPP BS3_CMN_MANGLER(Bs3PagingInitRootForPP) +#define Bs3PagingMapRamAbove4GForLM BS3_CMN_MANGLER(Bs3PagingMapRamAbove4GForLM) +#define Bs3PagingProtect BS3_CMN_MANGLER(Bs3PagingProtect) +#define Bs3PagingProtectPtr BS3_CMN_MANGLER(Bs3PagingProtectPtr) +#define Bs3PagingQueryAddressInfo BS3_CMN_MANGLER(Bs3PagingQueryAddressInfo) +#define Bs3PagingSetupCanonicalTraps BS3_CMN_MANGLER(Bs3PagingSetupCanonicalTraps) +#define Bs3PagingUnalias BS3_CMN_MANGLER(Bs3PagingUnalias) +#define Bs3Panic BS3_CMN_MANGLER(Bs3Panic) +#define Bs3PicMaskAll BS3_CMN_MANGLER(Bs3PicMaskAll) +#define Bs3PicSetup BS3_CMN_MANGLER(Bs3PicSetup) +#define Bs3PicUpdateMask BS3_CMN_MANGLER(Bs3PicUpdateMask) +#define Bs3PitDisable BS3_CMN_MANGLER(Bs3PitDisable) +#define Bs3PitSetupAndEnablePeriodTimer BS3_CMN_MANGLER(Bs3PitSetupAndEnablePeriodTimer) +#define Bs3PrintChr BS3_CMN_MANGLER(Bs3PrintChr) +#define Bs3Printf BS3_CMN_MANGLER(Bs3Printf) +#define Bs3PrintfV BS3_CMN_MANGLER(Bs3PrintfV) +#define Bs3PrintStr BS3_CMN_MANGLER(Bs3PrintStr) +#define Bs3PrintStrN BS3_CMN_MANGLER(Bs3PrintStrN) +#define Bs3PrintU32 BS3_CMN_MANGLER(Bs3PrintU32) +#define Bs3PrintX32 BS3_CMN_MANGLER(Bs3PrintX32) +#define Bs3RegCtxConvertToRingX BS3_CMN_MANGLER(Bs3RegCtxConvertToRingX) +#define Bs3RegCtxConvertV86ToRm BS3_CMN_MANGLER(Bs3RegCtxConvertV86ToRm) +#define Bs3RegCtxGetRspSsAsCurPtr BS3_CMN_MANGLER(Bs3RegCtxGetRspSsAsCurPtr) +#define Bs3RegCtxPrint BS3_CMN_MANGLER(Bs3RegCtxPrint) +#define Bs3RegCtxRestore BS3_CMN_MANGLER(Bs3RegCtxRestore) +#define Bs3RegCtxSave BS3_CMN_MANGLER(Bs3RegCtxSave) +#define Bs3RegCtxSaveEx BS3_CMN_MANGLER(Bs3RegCtxSaveEx) +#define Bs3RegCtxSaveForMode BS3_CMN_MANGLER(Bs3RegCtxSaveForMode) +#define Bs3RegCtxSetGpr BS3_CMN_MANGLER(Bs3RegCtxSetGpr) +#define Bs3RegCtxSetGrpDsFromCurPtr BS3_CMN_MANGLER(Bs3RegCtxSetGrpDsFromCurPtr) +#define Bs3RegCtxSetGrpSegFromCurPtr BS3_CMN_MANGLER(Bs3RegCtxSetGrpSegFromCurPtr) +#define Bs3RegCtxSetGrpSegFromFlat BS3_CMN_MANGLER(Bs3RegCtxSetGrpSegFromFlat) +#define Bs3RegCtxSetRipCsFromCurPtr BS3_CMN_MANGLER(Bs3RegCtxSetRipCsFromCurPtr) +#define Bs3RegCtxSetRipCsFromFlat BS3_CMN_MANGLER(Bs3RegCtxSetRipCsFromFlat) +#define Bs3RegCtxSetRipCsFromLnkPtr BS3_CMN_MANGLER(Bs3RegCtxSetRipCsFromLnkPtr) +#define Bs3RegGetCr0 BS3_CMN_MANGLER(Bs3RegGetCr0) +#define Bs3RegGetCr2 BS3_CMN_MANGLER(Bs3RegGetCr2) +#define Bs3RegGetCr3 BS3_CMN_MANGLER(Bs3RegGetCr3) +#define Bs3RegGetCr4 BS3_CMN_MANGLER(Bs3RegGetCr4) +#define Bs3RegGetDr0 BS3_CMN_MANGLER(Bs3RegGetDr0) +#define Bs3RegGetDr1 BS3_CMN_MANGLER(Bs3RegGetDr1) +#define Bs3RegGetDr2 BS3_CMN_MANGLER(Bs3RegGetDr2) +#define Bs3RegGetDr3 BS3_CMN_MANGLER(Bs3RegGetDr3) +#define Bs3RegGetDr6 BS3_CMN_MANGLER(Bs3RegGetDr6) +#define Bs3RegGetDr7 BS3_CMN_MANGLER(Bs3RegGetDr7) +#define Bs3RegGetDrX BS3_CMN_MANGLER(Bs3RegGetDrX) +#define Bs3RegGetLdtr BS3_CMN_MANGLER(Bs3RegGetLdtr) +#define Bs3RegGetTr BS3_CMN_MANGLER(Bs3RegGetTr) +#define Bs3RegGetXcr0 BS3_CMN_MANGLER(Bs3RegGetXcr0) +#define Bs3RegSetCr0 BS3_CMN_MANGLER(Bs3RegSetCr0) +#define Bs3RegSetCr2 BS3_CMN_MANGLER(Bs3RegSetCr2) +#define Bs3RegSetCr3 BS3_CMN_MANGLER(Bs3RegSetCr3) +#define Bs3RegSetCr4 BS3_CMN_MANGLER(Bs3RegSetCr4) +#define Bs3RegSetDr0 BS3_CMN_MANGLER(Bs3RegSetDr0) +#define Bs3RegSetDr1 BS3_CMN_MANGLER(Bs3RegSetDr1) +#define Bs3RegSetDr2 BS3_CMN_MANGLER(Bs3RegSetDr2) +#define Bs3RegSetDr3 BS3_CMN_MANGLER(Bs3RegSetDr3) +#define Bs3RegSetDr6 BS3_CMN_MANGLER(Bs3RegSetDr6) +#define Bs3RegSetDr7 BS3_CMN_MANGLER(Bs3RegSetDr7) +#define Bs3RegSetDrX BS3_CMN_MANGLER(Bs3RegSetDrX) +#define Bs3RegSetLdtr BS3_CMN_MANGLER(Bs3RegSetLdtr) +#define Bs3RegSetTr BS3_CMN_MANGLER(Bs3RegSetTr) +#define Bs3RegSetXcr0 BS3_CMN_MANGLER(Bs3RegSetXcr0) +#define Bs3SelFar32ToFlat32 BS3_CMN_MANGLER(Bs3SelFar32ToFlat32) +#define Bs3SelFar32ToFlat32NoClobber BS3_CMN_MANGLER(Bs3SelFar32ToFlat32NoClobber) +#define Bs3SelFlatCodeToProtFar16 BS3_CMN_MANGLER(Bs3SelFlatCodeToProtFar16) +#define Bs3SelFlatCodeToRealMode BS3_CMN_MANGLER(Bs3SelFlatCodeToRealMode) +#define Bs3SelFlatDataToProtFar16 BS3_CMN_MANGLER(Bs3SelFlatDataToProtFar16) +#define Bs3SelFlatDataToRealMode BS3_CMN_MANGLER(Bs3SelFlatDataToRealMode) +#define Bs3SelLnkPtrToCurPtr BS3_CMN_MANGLER(Bs3SelLnkPtrToCurPtr) +#define Bs3SelLnkPtrToFlat BS3_CMN_MANGLER(Bs3SelLnkPtrToFlat) +#define Bs3SelProtFar16DataToFlat BS3_CMN_MANGLER(Bs3SelProtFar16DataToFlat) +#define Bs3SelProtFar16DataToRealMode BS3_CMN_MANGLER(Bs3SelProtFar16DataToRealMode) +#define Bs3SelProtFar32ToFlat32 BS3_CMN_MANGLER(Bs3SelProtFar32ToFlat32) +#define Bs3SelProtModeCodeToRealMode BS3_CMN_MANGLER(Bs3SelProtModeCodeToRealMode) +#define Bs3SelRealModeCodeToFlat BS3_CMN_MANGLER(Bs3SelRealModeCodeToFlat) +#define Bs3SelRealModeCodeToProtMode BS3_CMN_MANGLER(Bs3SelRealModeCodeToProtMode) +#define Bs3SelRealModeDataToFlat BS3_CMN_MANGLER(Bs3SelRealModeDataToFlat) +#define Bs3SelRealModeDataToProtFar16 BS3_CMN_MANGLER(Bs3SelRealModeDataToProtFar16) +#define Bs3SelSetup16BitCode BS3_CMN_MANGLER(Bs3SelSetup16BitCode) +#define Bs3SelSetup16BitData BS3_CMN_MANGLER(Bs3SelSetup16BitData) +#define Bs3SelSetup32BitCode BS3_CMN_MANGLER(Bs3SelSetup32BitCode) +#define Bs3SelSetupGate64 BS3_CMN_MANGLER(Bs3SelSetupGate64) +#define Bs3SelSetupGate BS3_CMN_MANGLER(Bs3SelSetupGate) +#define Bs3Shutdown BS3_CMN_MANGLER(Bs3Shutdown) +#define Bs3SlabAlloc BS3_CMN_MANGLER(Bs3SlabAlloc) +#define Bs3SlabAllocEx BS3_CMN_MANGLER(Bs3SlabAllocEx) +#define Bs3SlabFree BS3_CMN_MANGLER(Bs3SlabFree) +#define Bs3SlabInit BS3_CMN_MANGLER(Bs3SlabInit) +#define Bs3SlabListAdd BS3_CMN_MANGLER(Bs3SlabListAdd) +#define Bs3SlabListAlloc BS3_CMN_MANGLER(Bs3SlabListAlloc) +#define Bs3SlabListAllocEx BS3_CMN_MANGLER(Bs3SlabListAllocEx) +#define Bs3SlabListFree BS3_CMN_MANGLER(Bs3SlabListFree) +#define Bs3SlabListInit BS3_CMN_MANGLER(Bs3SlabListInit) +#define Bs3StrCpy BS3_CMN_MANGLER(Bs3StrCpy) +#define Bs3StrFormatV BS3_CMN_MANGLER(Bs3StrFormatV) +#define Bs3StrLen BS3_CMN_MANGLER(Bs3StrLen) +#define Bs3StrNLen BS3_CMN_MANGLER(Bs3StrNLen) +#define Bs3StrPrintf BS3_CMN_MANGLER(Bs3StrPrintf) +#define Bs3StrPrintfV BS3_CMN_MANGLER(Bs3StrPrintfV) +#define Bs3SwitchFromV86To16BitAndCallC BS3_CMN_MANGLER(Bs3SwitchFromV86To16BitAndCallC) +#define Bs3TestCheckExtCtx BS3_CMN_MANGLER(Bs3TestCheckExtCtx) +#define Bs3TestCheckRegCtxEx BS3_CMN_MANGLER(Bs3TestCheckRegCtxEx) +#define Bs3TestFailed BS3_CMN_MANGLER(Bs3TestFailed) +#define Bs3TestFailedF BS3_CMN_MANGLER(Bs3TestFailedF) +#define Bs3TestFailedV BS3_CMN_MANGLER(Bs3TestFailedV) +#define Bs3TestHostPrintf BS3_CMN_MANGLER(Bs3TestHostPrintf) +#define Bs3TestHostPrintfV BS3_CMN_MANGLER(Bs3TestHostPrintfV) +#define Bs3TestInit BS3_CMN_MANGLER(Bs3TestInit) +#define Bs3TestNow BS3_CMN_MANGLER(Bs3TestNow) +#define Bs3TestPrintf BS3_CMN_MANGLER(Bs3TestPrintf) +#define Bs3TestPrintfV BS3_CMN_MANGLER(Bs3TestPrintfV) +#define Bs3TestQueryCfgBool BS3_CMN_MANGLER(Bs3TestQueryCfgBool) +#define Bs3TestQueryCfgU32 BS3_CMN_MANGLER(Bs3TestQueryCfgU32) +#define Bs3TestQueryCfgU8 BS3_CMN_MANGLER(Bs3TestQueryCfgU8) +#define Bs3TestSkipped BS3_CMN_MANGLER(Bs3TestSkipped) +#define Bs3TestSkippedF BS3_CMN_MANGLER(Bs3TestSkippedF) +#define Bs3TestSkippedV BS3_CMN_MANGLER(Bs3TestSkippedV) +#define Bs3TestSub BS3_CMN_MANGLER(Bs3TestSub) +#define Bs3TestSubDone BS3_CMN_MANGLER(Bs3TestSubDone) +#define Bs3TestSubErrorCount BS3_CMN_MANGLER(Bs3TestSubErrorCount) +#define Bs3TestSubF BS3_CMN_MANGLER(Bs3TestSubF) +#define Bs3TestSubV BS3_CMN_MANGLER(Bs3TestSubV) +#define Bs3TestTerm BS3_CMN_MANGLER(Bs3TestTerm) +#define Bs3TestValue BS3_CMN_MANGLER(Bs3TestValue) +#define Bs3Trap16Init BS3_CMN_MANGLER(Bs3Trap16Init) +#define Bs3Trap16InitEx BS3_CMN_MANGLER(Bs3Trap16InitEx) +#define Bs3Trap16SetGate BS3_CMN_MANGLER(Bs3Trap16SetGate) +#define Bs3Trap32Init BS3_CMN_MANGLER(Bs3Trap32Init) +#define Bs3Trap32SetGate BS3_CMN_MANGLER(Bs3Trap32SetGate) +#define Bs3Trap64Init BS3_CMN_MANGLER(Bs3Trap64Init) +#define Bs3Trap64InitEx BS3_CMN_MANGLER(Bs3Trap64InitEx) +#define Bs3Trap64SetGate BS3_CMN_MANGLER(Bs3Trap64SetGate) +#define Bs3TrapDefaultHandler BS3_CMN_MANGLER(Bs3TrapDefaultHandler) +#define Bs3TrapPrintFrame BS3_CMN_MANGLER(Bs3TrapPrintFrame) +#define Bs3TrapReInit BS3_CMN_MANGLER(Bs3TrapReInit) +#define Bs3TrapRmV86Init BS3_CMN_MANGLER(Bs3TrapRmV86Init) +#define Bs3TrapRmV86InitEx BS3_CMN_MANGLER(Bs3TrapRmV86InitEx) +#define Bs3TrapRmV86SetGate BS3_CMN_MANGLER(Bs3TrapRmV86SetGate) +#define Bs3TrapSetDpl BS3_CMN_MANGLER(Bs3TrapSetDpl) +#define Bs3TrapSetHandler BS3_CMN_MANGLER(Bs3TrapSetHandler) +#define Bs3TrapSetHandlerEx BS3_CMN_MANGLER(Bs3TrapSetHandlerEx) +#define Bs3TrapSetJmpAndRestore BS3_CMN_MANGLER(Bs3TrapSetJmpAndRestore) +#define Bs3TrapSetJmpAndRestoreInRm BS3_CMN_MANGLER(Bs3TrapSetJmpAndRestoreInRm) +#define Bs3TrapSetJmpAndRestoreWithExtCtxAndRm BS3_CMN_MANGLER(Bs3TrapSetJmpAndRestoreWithExtCtxAndRm) +#define Bs3TrapSetJmpAndRestoreWithExtCtx BS3_CMN_MANGLER(Bs3TrapSetJmpAndRestoreWithExtCtx) +#define Bs3TrapSetJmpAndRestoreWithRm BS3_CMN_MANGLER(Bs3TrapSetJmpAndRestoreWithRm) +#define Bs3TrapSetJmp BS3_CMN_MANGLER(Bs3TrapSetJmp) +#define Bs3TrapUnsetJmp BS3_CMN_MANGLER(Bs3TrapUnsetJmp) +#define Bs3UInt32Div BS3_CMN_MANGLER(Bs3UInt32Div) +#define Bs3UInt64Div BS3_CMN_MANGLER(Bs3UInt64Div) +#define Bs3UtilSetFullGdtr BS3_CMN_MANGLER(Bs3UtilSetFullGdtr) +#define Bs3UtilSetFullIdtr BS3_CMN_MANGLER(Bs3UtilSetFullIdtr) +#ifndef BS3_CMN_ONLY +# define Bs3BiosInt15h88 BS3_MODE_MANGLER(Bs3BiosInt15h88) +# define Bs3BiosInt15hE820 BS3_MODE_MANGLER(Bs3BiosInt15hE820) +# define Bs3CpuDetect BS3_MODE_MANGLER(Bs3CpuDetect) +# define Bs3SwitchTo32BitAndCallC BS3_MODE_MANGLER(Bs3SwitchTo32BitAndCallC) +# define Bs3TestDoModes BS3_MODE_MANGLER(Bs3TestDoModes) +# define Bs3TestDoModesByMax BS3_MODE_MANGLER(Bs3TestDoModesByMax) +# define Bs3TestDoModesByOne BS3_MODE_MANGLER(Bs3TestDoModesByOne) +# define Bs3TrapInit BS3_MODE_MANGLER(Bs3TrapInit) +#endif /* !BS3_CMN_ONLY */ diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-undef.h b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-undef.h new file mode 100644 index 00000000..f2812dac --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-undef.h @@ -0,0 +1,256 @@ +/* $Id: bs3kit-mangling-code-undef.h $ */ +/** @file + * BS3Kit - Undefining function mangling - automatically generated by the bs3kit-mangling-code-undef.h makefile rule. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#undef Bs3A20Disable +#undef Bs3A20DisableViaKbd +#undef Bs3A20DisableViaPortA +#undef Bs3A20Enable +#undef Bs3A20EnableViaKbd +#undef Bs3A20EnableViaPortA +#undef Bs3ExtCtxAlloc +#undef Bs3ExtCtxCopy +#undef Bs3ExtCtxFree +#undef Bs3ExtCtxGetAbridgedFtw +#undef Bs3ExtCtxGetFcw +#undef Bs3ExtCtxGetFsw +#undef Bs3ExtCtxGetMm +#undef Bs3ExtCtxGetMxCsr +#undef Bs3ExtCtxGetMxCsrMask +#undef Bs3ExtCtxGetSize +#undef Bs3ExtCtxGetXmm +#undef Bs3ExtCtxGetYmm +#undef Bs3ExtCtxInit +#undef Bs3ExtCtxRestore +#undef Bs3ExtCtxRestoreEx +#undef Bs3ExtCtxSave +#undef Bs3ExtCtxSaveEx +#undef Bs3ExtCtxSetAbridgedFtw +#undef Bs3ExtCtxSetFcw +#undef Bs3ExtCtxSetFsw +#undef Bs3ExtCtxSetMm +#undef Bs3ExtCtxSetMxCsr +#undef Bs3ExtCtxSetMxCsrMask +#undef Bs3ExtCtxSetXmm +#undef Bs3ExtCtxSetYmm +#undef Bs3GetCpuVendor +#undef Bs3GetModeName +#undef Bs3GetModeNameShortLower +#undef Bs3KbdRead +#undef Bs3KbdWait +#undef Bs3KbdWrite +#undef Bs3MemAlloc +#undef Bs3MemAllocZ +#undef Bs3MemChr +#undef Bs3MemCmp +#undef Bs3MemCpy +#undef Bs3MemFree +#undef Bs3MemGuardedTestPageAlloc +#undef Bs3MemGuardedTestPageAllocEx +#undef Bs3MemGuardedTestPageFree +#undef Bs3MemMove +#undef Bs3MemPCpy +#undef Bs3MemPrintInfo +#undef Bs3MemSet +#undef Bs3MemZero +#undef Bs3PagingAlias +#undef bs3PagingGetLegacyPte +#undef bs3PagingGetPaePte +#undef Bs3PagingGetPte +#undef Bs3PagingInitRootForLM +#undef Bs3PagingInitRootForPAE +#undef Bs3PagingInitRootForPP +#undef Bs3PagingMapRamAbove4GForLM +#undef Bs3PagingProtect +#undef Bs3PagingProtectPtr +#undef Bs3PagingQueryAddressInfo +#undef Bs3PagingSetupCanonicalTraps +#undef Bs3PagingUnalias +#undef Bs3Panic +#undef Bs3PicMaskAll +#undef Bs3PicSetup +#undef Bs3PicUpdateMask +#undef Bs3PitDisable +#undef Bs3PitSetupAndEnablePeriodTimer +#undef Bs3PrintChr +#undef Bs3Printf +#undef Bs3PrintfV +#undef Bs3PrintStr +#undef Bs3PrintStrN +#undef Bs3PrintU32 +#undef Bs3PrintX32 +#undef Bs3RegCtxConvertToRingX +#undef Bs3RegCtxConvertV86ToRm +#undef Bs3RegCtxGetRspSsAsCurPtr +#undef Bs3RegCtxPrint +#undef Bs3RegCtxRestore +#undef Bs3RegCtxSave +#undef Bs3RegCtxSaveEx +#undef Bs3RegCtxSaveForMode +#undef Bs3RegCtxSetGpr +#undef Bs3RegCtxSetGrpDsFromCurPtr +#undef Bs3RegCtxSetGrpSegFromCurPtr +#undef Bs3RegCtxSetGrpSegFromFlat +#undef Bs3RegCtxSetRipCsFromCurPtr +#undef Bs3RegCtxSetRipCsFromFlat +#undef Bs3RegCtxSetRipCsFromLnkPtr +#undef Bs3RegGetCr0 +#undef Bs3RegGetCr2 +#undef Bs3RegGetCr3 +#undef Bs3RegGetCr4 +#undef Bs3RegGetDr0 +#undef Bs3RegGetDr1 +#undef Bs3RegGetDr2 +#undef Bs3RegGetDr3 +#undef Bs3RegGetDr6 +#undef Bs3RegGetDr7 +#undef Bs3RegGetDrX +#undef Bs3RegGetLdtr +#undef Bs3RegGetTr +#undef Bs3RegGetXcr0 +#undef Bs3RegSetCr0 +#undef Bs3RegSetCr2 +#undef Bs3RegSetCr3 +#undef Bs3RegSetCr4 +#undef Bs3RegSetDr0 +#undef Bs3RegSetDr1 +#undef Bs3RegSetDr2 +#undef Bs3RegSetDr3 +#undef Bs3RegSetDr6 +#undef Bs3RegSetDr7 +#undef Bs3RegSetDrX +#undef Bs3RegSetLdtr +#undef Bs3RegSetTr +#undef Bs3RegSetXcr0 +#undef Bs3SelFar32ToFlat32 +#undef Bs3SelFar32ToFlat32NoClobber +#undef Bs3SelFlatCodeToProtFar16 +#undef Bs3SelFlatCodeToRealMode +#undef Bs3SelFlatDataToProtFar16 +#undef Bs3SelFlatDataToRealMode +#undef Bs3SelLnkPtrToCurPtr +#undef Bs3SelLnkPtrToFlat +#undef Bs3SelProtFar16DataToFlat +#undef Bs3SelProtFar16DataToRealMode +#undef Bs3SelProtFar32ToFlat32 +#undef Bs3SelProtModeCodeToRealMode +#undef Bs3SelRealModeCodeToFlat +#undef Bs3SelRealModeCodeToProtMode +#undef Bs3SelRealModeDataToFlat +#undef Bs3SelRealModeDataToProtFar16 +#undef Bs3SelSetup16BitCode +#undef Bs3SelSetup16BitData +#undef Bs3SelSetup32BitCode +#undef Bs3SelSetupGate64 +#undef Bs3SelSetupGate +#undef Bs3Shutdown +#undef Bs3SlabAlloc +#undef Bs3SlabAllocEx +#undef Bs3SlabFree +#undef Bs3SlabInit +#undef Bs3SlabListAdd +#undef Bs3SlabListAlloc +#undef Bs3SlabListAllocEx +#undef Bs3SlabListFree +#undef Bs3SlabListInit +#undef Bs3StrCpy +#undef Bs3StrFormatV +#undef Bs3StrLen +#undef Bs3StrNLen +#undef Bs3StrPrintf +#undef Bs3StrPrintfV +#undef Bs3SwitchFromV86To16BitAndCallC +#undef Bs3TestCheckExtCtx +#undef Bs3TestCheckRegCtxEx +#undef Bs3TestFailed +#undef Bs3TestFailedF +#undef Bs3TestFailedV +#undef Bs3TestHostPrintf +#undef Bs3TestHostPrintfV +#undef Bs3TestInit +#undef Bs3TestNow +#undef Bs3TestPrintf +#undef Bs3TestPrintfV +#undef Bs3TestQueryCfgBool +#undef Bs3TestQueryCfgU32 +#undef Bs3TestQueryCfgU8 +#undef Bs3TestSkipped +#undef Bs3TestSkippedF +#undef Bs3TestSkippedV +#undef Bs3TestSub +#undef Bs3TestSubDone +#undef Bs3TestSubErrorCount +#undef Bs3TestSubF +#undef Bs3TestSubV +#undef Bs3TestTerm +#undef Bs3TestValue +#undef Bs3Trap16Init +#undef Bs3Trap16InitEx +#undef Bs3Trap16SetGate +#undef Bs3Trap32Init +#undef Bs3Trap32SetGate +#undef Bs3Trap64Init +#undef Bs3Trap64InitEx +#undef Bs3Trap64SetGate +#undef Bs3TrapDefaultHandler +#undef Bs3TrapPrintFrame +#undef Bs3TrapReInit +#undef Bs3TrapRmV86Init +#undef Bs3TrapRmV86InitEx +#undef Bs3TrapRmV86SetGate +#undef Bs3TrapSetDpl +#undef Bs3TrapSetHandler +#undef Bs3TrapSetHandlerEx +#undef Bs3TrapSetJmpAndRestore +#undef Bs3TrapSetJmpAndRestoreInRm +#undef Bs3TrapSetJmpAndRestoreWithExtCtxAndRm +#undef Bs3TrapSetJmpAndRestoreWithExtCtx +#undef Bs3TrapSetJmpAndRestoreWithRm +#undef Bs3TrapSetJmp +#undef Bs3TrapUnsetJmp +#undef Bs3UInt32Div +#undef Bs3UInt64Div +#undef Bs3UtilSetFullGdtr +#undef Bs3UtilSetFullIdtr +#ifndef BS3_CMN_ONLY +# undef Bs3BiosInt15h88 +# undef Bs3BiosInt15hE820 +# undef Bs3CpuDetect +# undef Bs3SwitchTo32BitAndCallC +# undef Bs3TestDoModes +# undef Bs3TestDoModesByMax +# undef Bs3TestDoModesByOne +# undef Bs3TrapInit +#endif /* !BS3_CMN_ONLY */ diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code.h b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code.h new file mode 100644 index 00000000..d5b71ccb --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code.h @@ -0,0 +1,52 @@ +/* $Id: bs3kit-mangling-code.h $ */ +/** @file + * BS3Kit - Symbol mangling, code. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/* + * Do function mangling. This can be redone at compile time (templates). + */ +#undef BS3_CMN_MANGLER +#undef BS3_MODE_MANGLER +#if ARCH_BITS != 16 || !defined(BS3_USE_ALT_16BIT_TEXT_SEG) +# define BS3_CMN_MANGLER(a_Function) BS3_CMN_NM(a_Function) +# define BS3_MODE_MANGLER(a_Function) TMPL_NM(a_Function) +#else +# define BS3_CMN_MANGLER(a_Function) BS3_CMN_FAR_NM(a_Function) +# define BS3_MODE_MANGLER(a_Function) TMPL_FAR_NM(a_Function) +#endif +#include "bs3kit-mangling-code-undef.h" +#include "bs3kit-mangling-code-define.h" + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-data.h b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-data.h new file mode 100644 index 00000000..51cdbbed --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-data.h @@ -0,0 +1,295 @@ +/* $Id: bs3kit-mangling-data.h $ */ +/** @file + * BS3Kit - Symbol mangling. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/* + * First part is only applied once. It concerns itself with data symbols. + */ + +#ifndef BS3KIT_INCLUDED_bs3kit_mangling_data_h +#define BS3KIT_INCLUDED_bs3kit_mangling_data_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#if 0 /* the object converter deals with this now */ +#if ARCH_BITS == 64 + +# define Bs3Gdt BS3_DATA_NM(Bs3Gdt) +# define Bs3Gdt_Ldt BS3_DATA_NM(Bs3Gdt_Ldt) +# define Bs3Gdte_Tss16 BS3_DATA_NM(Bs3Gdte_Tss16) +# define Bs3Gdte_Tss16DoubleFault BS3_DATA_NM(Bs3Gdte_Tss16DoubleFault) +# define Bs3Gdte_Tss16Spare0 BS3_DATA_NM(Bs3Gdte_Tss16Spare0) +# define Bs3Gdte_Tss16Spare1 BS3_DATA_NM(Bs3Gdte_Tss16Spare1) +# define Bs3Gdte_Tss32 BS3_DATA_NM(Bs3Gdte_Tss32) +# define Bs3Gdte_Tss32DoubleFault BS3_DATA_NM(Bs3Gdte_Tss32DoubleFault) +# define Bs3Gdte_Tss32Spare0 BS3_DATA_NM(Bs3Gdte_Tss32Spare0) +# define Bs3Gdte_Tss32Spare1 BS3_DATA_NM(Bs3Gdte_Tss32Spare1) +# define Bs3Gdte_Tss32IobpIntRedirBm BS3_DATA_NM(Bs3Gdte_Tss32IobpIntRedirBm) +# define Bs3Gdte_Tss32IntRedirBm BS3_DATA_NM(Bs3Gdte_Tss32IntRedirBm) +# define Bs3Gdte_Tss64 BS3_DATA_NM(Bs3Gdte_Tss64) +# define Bs3Gdte_Tss64Spare0 BS3_DATA_NM(Bs3Gdte_Tss64Spare0) +# define Bs3Gdte_Tss64Spare1 BS3_DATA_NM(Bs3Gdte_Tss64Spare1) +# define Bs3Gdte_Tss64Iobp BS3_DATA_NM(Bs3Gdte_Tss64Iobp) +# define Bs3Gdte_RMTEXT16_CS BS3_DATA_NM(Bs3Gdte_RMTEXT16_CS) +# define Bs3Gdte_X0TEXT16_CS BS3_DATA_NM(Bs3Gdte_X0TEXT16_CS) +# define Bs3Gdte_X1TEXT16_CS BS3_DATA_NM(Bs3Gdte_X1TEXT16_CS) +# define Bs3Gdte_R0_MMIO16 BS3_DATA_NM(Bs3Gdte_R0_MMIO16) + +# define Bs3Gdte_R0_First BS3_DATA_NM(Bs3Gdte_R0_First) +# define Bs3Gdte_R0_CS16 BS3_DATA_NM(Bs3Gdte_R0_CS16) +# define Bs3Gdte_R0_DS16 BS3_DATA_NM(Bs3Gdte_R0_DS16) +# define Bs3Gdte_R0_SS16 BS3_DATA_NM(Bs3Gdte_R0_SS16) +# define Bs3Gdte_R0_CS32 BS3_DATA_NM(Bs3Gdte_R0_CS32) +# define Bs3Gdte_R0_DS32 BS3_DATA_NM(Bs3Gdte_R0_DS32) +# define Bs3Gdte_R0_SS32 BS3_DATA_NM(Bs3Gdte_R0_SS32) +# define Bs3Gdte_R0_CS64 BS3_DATA_NM(Bs3Gdte_R0_CS64) +# define Bs3Gdte_R0_DS64 BS3_DATA_NM(Bs3Gdte_R0_DS64) +# define Bs3Gdte_R0_CS16_EO BS3_DATA_NM(Bs3Gdte_R0_CS16_EO) +# define Bs3Gdte_R0_CS16_CNF BS3_DATA_NM(Bs3Gdte_R0_CS16_CNF) +# define Bs3Gdte_R0_CS16_CND_EO BS3_DATA_NM(Bs3Gdte_R0_CS16_CND_EO) +# define Bs3Gdte_R0_CS32_EO BS3_DATA_NM(Bs3Gdte_R0_CS32_EO) +# define Bs3Gdte_R0_CS32_CNF BS3_DATA_NM(Bs3Gdte_R0_CS32_CNF) +# define Bs3Gdte_R0_CS32_CNF_EO BS3_DATA_NM(Bs3Gdte_R0_CS32_CNF_EO) +# define Bs3Gdte_R0_CS64_EO BS3_DATA_NM(Bs3Gdte_R0_CS64_EO) +# define Bs3Gdte_R0_CS64_CNF BS3_DATA_NM(Bs3Gdte_R0_CS64_CNF) +# define Bs3Gdte_R0_CS64_CNF_EO BS3_DATA_NM(Bs3Gdte_R0_CS64_CNF_EO) + +# define Bs3Gdte_R1_First BS3_DATA_NM(Bs3Gdte_R1_First) +# define Bs3Gdte_R1_CS16 BS3_DATA_NM(Bs3Gdte_R1_CS16) +# define Bs3Gdte_R1_DS16 BS3_DATA_NM(Bs3Gdte_R1_DS16) +# define Bs3Gdte_R1_SS16 BS3_DATA_NM(Bs3Gdte_R1_SS16) +# define Bs3Gdte_R1_CS32 BS3_DATA_NM(Bs3Gdte_R1_CS32) +# define Bs3Gdte_R1_DS32 BS3_DATA_NM(Bs3Gdte_R1_DS32) +# define Bs3Gdte_R1_SS32 BS3_DATA_NM(Bs3Gdte_R1_SS32) +# define Bs3Gdte_R1_CS64 BS3_DATA_NM(Bs3Gdte_R1_CS64) +# define Bs3Gdte_R1_DS64 BS3_DATA_NM(Bs3Gdte_R1_DS64) +# define Bs3Gdte_R1_CS16_EO BS3_DATA_NM(Bs3Gdte_R1_CS16_EO) +# define Bs3Gdte_R1_CS16_CNF BS3_DATA_NM(Bs3Gdte_R1_CS16_CNF) +# define Bs3Gdte_R1_CS16_CND_EO BS3_DATA_NM(Bs3Gdte_R1_CS16_CND_EO) +# define Bs3Gdte_R1_CS32_EO BS3_DATA_NM(Bs3Gdte_R1_CS32_EO) +# define Bs3Gdte_R1_CS32_CNF BS3_DATA_NM(Bs3Gdte_R1_CS32_CNF) +# define Bs3Gdte_R1_CS32_CNF_EO BS3_DATA_NM(Bs3Gdte_R1_CS32_CNF_EO) +# define Bs3Gdte_R1_CS64_EO BS3_DATA_NM(Bs3Gdte_R1_CS64_EO) +# define Bs3Gdte_R1_CS64_CNF BS3_DATA_NM(Bs3Gdte_R1_CS64_CNF) +# define Bs3Gdte_R1_CS64_CNF_EO BS3_DATA_NM(Bs3Gdte_R1_CS64_CNF_EO) + +# define Bs3Gdte_R2_First BS3_DATA_NM(Bs3Gdte_R2_First) +# define Bs3Gdte_R2_CS16 BS3_DATA_NM(Bs3Gdte_R2_CS16) +# define Bs3Gdte_R2_DS16 BS3_DATA_NM(Bs3Gdte_R2_DS16) +# define Bs3Gdte_R2_SS16 BS3_DATA_NM(Bs3Gdte_R2_SS16) +# define Bs3Gdte_R2_CS32 BS3_DATA_NM(Bs3Gdte_R2_CS32) +# define Bs3Gdte_R2_DS32 BS3_DATA_NM(Bs3Gdte_R2_DS32) +# define Bs3Gdte_R2_SS32 BS3_DATA_NM(Bs3Gdte_R2_SS32) +# define Bs3Gdte_R2_CS64 BS3_DATA_NM(Bs3Gdte_R2_CS64) +# define Bs3Gdte_R2_DS64 BS3_DATA_NM(Bs3Gdte_R2_DS64) +# define Bs3Gdte_R2_CS16_EO BS3_DATA_NM(Bs3Gdte_R2_CS16_EO) +# define Bs3Gdte_R2_CS16_CNF BS3_DATA_NM(Bs3Gdte_R2_CS16_CNF) +# define Bs3Gdte_R2_CS16_CND_EO BS3_DATA_NM(Bs3Gdte_R2_CS16_CND_EO) +# define Bs3Gdte_R2_CS32_EO BS3_DATA_NM(Bs3Gdte_R2_CS32_EO) +# define Bs3Gdte_R2_CS32_CNF BS3_DATA_NM(Bs3Gdte_R2_CS32_CNF) +# define Bs3Gdte_R2_CS32_CNF_EO BS3_DATA_NM(Bs3Gdte_R2_CS32_CNF_EO) +# define Bs3Gdte_R2_CS64_EO BS3_DATA_NM(Bs3Gdte_R2_CS64_EO) +# define Bs3Gdte_R2_CS64_CNF BS3_DATA_NM(Bs3Gdte_R2_CS64_CNF) +# define Bs3Gdte_R2_CS64_CNF_EO BS3_DATA_NM(Bs3Gdte_R2_CS64_CNF_EO) + +# define Bs3Gdte_R3_First BS3_DATA_NM(Bs3Gdte_R3_First) +# define Bs3Gdte_R3_CS16 BS3_DATA_NM(Bs3Gdte_R3_CS16) +# define Bs3Gdte_R3_DS16 BS3_DATA_NM(Bs3Gdte_R3_DS16) +# define Bs3Gdte_R3_SS16 BS3_DATA_NM(Bs3Gdte_R3_SS16) +# define Bs3Gdte_R3_CS32 BS3_DATA_NM(Bs3Gdte_R3_CS32) +# define Bs3Gdte_R3_DS32 BS3_DATA_NM(Bs3Gdte_R3_DS32) +# define Bs3Gdte_R3_SS32 BS3_DATA_NM(Bs3Gdte_R3_SS32) +# define Bs3Gdte_R3_CS64 BS3_DATA_NM(Bs3Gdte_R3_CS64) +# define Bs3Gdte_R3_DS64 BS3_DATA_NM(Bs3Gdte_R3_DS64) +# define Bs3Gdte_R3_CS16_EO BS3_DATA_NM(Bs3Gdte_R3_CS16_EO) +# define Bs3Gdte_R3_CS16_CNF BS3_DATA_NM(Bs3Gdte_R3_CS16_CNF) +# define Bs3Gdte_R3_CS16_CND_EO BS3_DATA_NM(Bs3Gdte_R3_CS16_CND_EO) +# define Bs3Gdte_R3_CS32_EO BS3_DATA_NM(Bs3Gdte_R3_CS32_EO) +# define Bs3Gdte_R3_CS32_CNF BS3_DATA_NM(Bs3Gdte_R3_CS32_CNF) +# define Bs3Gdte_R3_CS32_CNF_EO BS3_DATA_NM(Bs3Gdte_R3_CS32_CNF_EO) +# define Bs3Gdte_R3_CS64_EO BS3_DATA_NM(Bs3Gdte_R3_CS64_EO) +# define Bs3Gdte_R3_CS64_CNF BS3_DATA_NM(Bs3Gdte_R3_CS64_CNF) +# define Bs3Gdte_R3_CS64_CNF_EO BS3_DATA_NM(Bs3Gdte_R3_CS64_CNF_EO) + +# define Bs3GdteSpare00 BS3_DATA_NM(Bs3GdteSpare00) +# define Bs3GdteSpare01 BS3_DATA_NM(Bs3GdteSpare01) +# define Bs3GdteSpare02 BS3_DATA_NM(Bs3GdteSpare02) +# define Bs3GdteSpare03 BS3_DATA_NM(Bs3GdteSpare03) +# define Bs3GdteSpare04 BS3_DATA_NM(Bs3GdteSpare04) +# define Bs3GdteSpare05 BS3_DATA_NM(Bs3GdteSpare05) +# define Bs3GdteSpare06 BS3_DATA_NM(Bs3GdteSpare06) +# define Bs3GdteSpare07 BS3_DATA_NM(Bs3GdteSpare07) +# define Bs3GdteSpare08 BS3_DATA_NM(Bs3GdteSpare08) +# define Bs3GdteSpare09 BS3_DATA_NM(Bs3GdteSpare09) +# define Bs3GdteSpare0a BS3_DATA_NM(Bs3GdteSpare0a) +# define Bs3GdteSpare0b BS3_DATA_NM(Bs3GdteSpare0b) +# define Bs3GdteSpare0c BS3_DATA_NM(Bs3GdteSpare0c) +# define Bs3GdteSpare0d BS3_DATA_NM(Bs3GdteSpare0d) +# define Bs3GdteSpare0e BS3_DATA_NM(Bs3GdteSpare0e) +# define Bs3GdteSpare0f BS3_DATA_NM(Bs3GdteSpare0f) +# define Bs3GdteSpare10 BS3_DATA_NM(Bs3GdteSpare10) +# define Bs3GdteSpare11 BS3_DATA_NM(Bs3GdteSpare11) +# define Bs3GdteSpare12 BS3_DATA_NM(Bs3GdteSpare12) +# define Bs3GdteSpare13 BS3_DATA_NM(Bs3GdteSpare13) +# define Bs3GdteSpare14 BS3_DATA_NM(Bs3GdteSpare14) +# define Bs3GdteSpare15 BS3_DATA_NM(Bs3GdteSpare15) +# define Bs3GdteSpare16 BS3_DATA_NM(Bs3GdteSpare16) +# define Bs3GdteSpare17 BS3_DATA_NM(Bs3GdteSpare17) +# define Bs3GdteSpare18 BS3_DATA_NM(Bs3GdteSpare18) +# define Bs3GdteSpare19 BS3_DATA_NM(Bs3GdteSpare19) +# define Bs3GdteSpare1a BS3_DATA_NM(Bs3GdteSpare1a) +# define Bs3GdteSpare1b BS3_DATA_NM(Bs3GdteSpare1b) +# define Bs3GdteSpare1c BS3_DATA_NM(Bs3GdteSpare1c) +# define Bs3GdteSpare1d BS3_DATA_NM(Bs3GdteSpare1d) +# define Bs3GdteSpare1e BS3_DATA_NM(Bs3GdteSpare1e) +# define Bs3GdteSpare1f BS3_DATA_NM(Bs3GdteSpare1f) + +# define Bs3GdteTiled BS3_DATA_NM(Bs3GdteTiled) +# define Bs3GdteFreePart1 BS3_DATA_NM(Bs3GdteFreePart1) +# define Bs3Gdte_CODE16 BS3_DATA_NM(Bs3Gdte_CODE16) +# define Bs3GdteFreePart2 BS3_DATA_NM(Bs3GdteFreePart2) +# define Bs3Gdte_SYSTEM16 BS3_DATA_NM(Bs3Gdte_SYSTEM16) +# define Bs3GdteFreePart3 BS3_DATA_NM(Bs3GdteFreePart3) +# define Bs3Gdte_DATA16 BS3_DATA_NM(Bs3Gdte_DATA16) + +# define Bs3GdteFreePart4 BS3_DATA_NM(Bs3GdteFreePart4) +# define Bs3GdtePreTestPage08 BS3_DATA_NM(Bs3GdtePreTestPage08) +# define Bs3GdtePreTestPage07 BS3_DATA_NM(Bs3GdtePreTestPage07) +# define Bs3GdtePreTestPage06 BS3_DATA_NM(Bs3GdtePreTestPage06) +# define Bs3GdtePreTestPage05 BS3_DATA_NM(Bs3GdtePreTestPage05) +# define Bs3GdtePreTestPage04 BS3_DATA_NM(Bs3GdtePreTestPage04) +# define Bs3GdtePreTestPage03 BS3_DATA_NM(Bs3GdtePreTestPage03) +# define Bs3GdtePreTestPage02 BS3_DATA_NM(Bs3GdtePreTestPage02) +# define Bs3GdtePreTestPage01 BS3_DATA_NM(Bs3GdtePreTestPage01) +# define Bs3GdteTestPage BS3_DATA_NM(Bs3GdteTestPage) +# define Bs3GdteTestPage00 BS3_DATA_NM(Bs3GdteTestPage00) +# define Bs3GdteTestPage01 BS3_DATA_NM(Bs3GdteTestPage01) +# define Bs3GdteTestPage02 BS3_DATA_NM(Bs3GdteTestPage02) +# define Bs3GdteTestPage03 BS3_DATA_NM(Bs3GdteTestPage03) +# define Bs3GdteTestPage04 BS3_DATA_NM(Bs3GdteTestPage04) +# define Bs3GdteTestPage05 BS3_DATA_NM(Bs3GdteTestPage05) +# define Bs3GdteTestPage06 BS3_DATA_NM(Bs3GdteTestPage06) +# define Bs3GdteTestPage07 BS3_DATA_NM(Bs3GdteTestPage07) + +# define Bs3GdtEnd BS3_DATA_NM(Bs3GdtEnd) + +# define Bs3Tss16 BS3_DATA_NM(Bs3Tss16) +# define Bs3Tss16DoubleFault BS3_DATA_NM(Bs3Tss16DoubleFault) +# define Bs3Tss16Spare0 BS3_DATA_NM(Bs3Tss16Spare0) +# define Bs3Tss16Spare1 BS3_DATA_NM(Bs3Tss16Spare1) +# define Bs3Tss32 BS3_DATA_NM(Bs3Tss32) +# define Bs3Tss32DoubleFault BS3_DATA_NM(Bs3Tss32DoubleFault) +# define Bs3Tss32Spare0 BS3_DATA_NM(Bs3Tss32Spare0) +# define Bs3Tss32Spare1 BS3_DATA_NM(Bs3Tss32Spare1) +# define Bs3Tss64 BS3_DATA_NM(Bs3Tss64) +# define Bs3Tss64Spare0 BS3_DATA_NM(Bs3Tss64Spare0) +# define Bs3Tss64Spare1 BS3_DATA_NM(Bs3Tss64Spare1) +# define Bs3Tss64WithIopb BS3_DATA_NM(Bs3Tss64WithIopb) +# define Bs3Tss32WithIopb BS3_DATA_NM(Bs3Tss32WithIopb) +# define Bs3SharedIntRedirBm BS3_DATA_NM(Bs3SharedIntRedirBm) +# define Bs3SharedIobp BS3_DATA_NM(Bs3SharedIobp) +# define Bs3SharedIobpEnd BS3_DATA_NM(Bs3SharedIobpEnd) +# define Bs3Idt16 BS3_DATA_NM(Bs3Idt16) +# define Bs3Idt32 BS3_DATA_NM(Bs3Idt32) +# define Bs3Idt64 BS3_DATA_NM(Bs3Idt64) +# define Bs3Lidt_Idt16 BS3_DATA_NM(Bs3Lidt_Idt16) +# define Bs3Lidt_Idt32 BS3_DATA_NM(Bs3Lidt_Idt32) +# define Bs3Lidt_Idt64 BS3_DATA_NM(Bs3Lidt_Idt64) +# define Bs3Lidt_Ivt BS3_DATA_NM(Bs3Lidt_Ivt) +# define Bs3Lgdt_Gdt BS3_DATA_NM(Bs3Lgdt_Gdt) +# define Bs3Ldt BS3_DATA_NM(Bs3Ldt) +# define Bs3LdtEnd BS3_DATA_NM(Bs3LdtEnd) + +# define Bs3Text16_StartOfSegment BS3_DATA_NM(Bs3Text16_StartOfSegment) +# define Bs3Text16_EndOfSegment BS3_DATA_NM(Bs3Text16_EndOfSegment) +# define Bs3Text16_Size BS3_DATA_NM(Bs3Text16_Size) + +# define Bs3System16_StartOfSegment BS3_DATA_NM(Bs3System16_StartOfSegment) +# define Bs3System16_EndOfSegment BS3_DATA_NM(Bs3System16_EndOfSegment) + +# define Bs3Data16_StartOfSegment BS3_DATA_NM(Bs3Data16_StartOfSegment) +# define Bs3Data16_EndOfSegment BS3_DATA_NM(Bs3Data16_EndOfSegment) + +# define Bs3RmText16_StartOfSegment BS3_DATA_NM(Bs3RmText16_StartOfSegment) +# define Bs3RmText16_EndOfSegment BS3_DATA_NM(Bs3RmText16_EndOfSegment) +# define Bs3RmText16_Size BS3_DATA_NM(Bs3RmText16_Size) +# define Bs3RmText16_FlatAddr BS3_DATA_NM(Bs3RmText16_FlatAddr) + +# define Bs3X0Text16_StartOfSegment BS3_DATA_NM(Bs3X0Text16_StartOfSegment) +# define Bs3X0Text16_EndOfSegment BS3_DATA_NM(Bs3X0Text16_EndOfSegment) +# define Bs3X0Text16_Size BS3_DATA_NM(Bs3X0Text16_Size) +# define Bs3X0Text16_FlatAddr BS3_DATA_NM(Bs3X0Text16_FlatAddr) + +# define Bs3X1Text16_StartOfSegment BS3_DATA_NM(Bs3X1Text16_StartOfSegment) +# define Bs3X1Text16_EndOfSegment BS3_DATA_NM(Bs3X1Text16_EndOfSegment) +# define Bs3X1Text16_Size BS3_DATA_NM(Bs3X1Text16_Size) +# define Bs3X1Text16_FlatAddr BS3_DATA_NM(Bs3X1Text16_FlatAddr) + +# define Bs3Text32_StartOfSegment BS3_DATA_NM(Bs3Text32_StartOfSegment) +# define Bs3Text32_EndOfSegment BS3_DATA_NM(Bs3Text32_EndOfSegment) + +# define Bs3Data32_StartOfSegment BS3_DATA_NM(Bs3Data32_StartOfSegment) +# define Bs3Data32_EndOfSegment BS3_DATA_NM(Bs3Data32_EndOfSegment) + +# define Bs3Text64_StartOfSegment BS3_DATA_NM(Bs3Text64_StartOfSegment) +# define Bs3Text64_EndOfSegment BS3_DATA_NM(Bs3Text64_EndOfSegment) + +# define Bs3Data64_StartOfSegment BS3_DATA_NM(Bs3Data64_StartOfSegment) +# define Bs3Data64_EndOfSegment BS3_DATA_NM(Bs3Data64_EndOfSegment) + +# define Bs3Data16Thru64Text32And64_TotalSize BS3_DATA_NM(Bs3Data16Thru64Text32And64_TotalSize) +# define Bs3TotalImageSize BS3_DATA_NM(Bs3TotalImageSize) + +# define g_achBs3HexDigits BS3_DATA_NM(g_achBs3HexDigits) +# define g_achBs3HexDigitsUpper BS3_DATA_NM(g_achBs3HexDigitsUpper) +# define g_bBs3CurrentMode BS3_DATA_NM(g_bBs3CurrentMode) +# define g_uBs3TrapEipHint BS3_DATA_NM(g_uBs3TrapEipHint) +# define g_aBs3RmIvtOriginal BS3_DATA_NM(g_aBs3RmIvtOriginal) + +# define g_usBs3TestStep BS3_DATA_NM(g_usBs3TestStep) +# define g_usBs3TestStep BS3_DATA_NM(g_usBs3TestStep) +# define g_Bs3Trap16GenericEntriesFlatAddr BS3_DATA_NM(g_Bs3Trap16GenericEntriesFlatAddr) +# define g_Bs3Trap32GenericEntriesFlatAddr BS3_DATA_NM(g_Bs3Trap32GenericEntriesFlatAddr) +# define g_Bs3Trap64GenericEntriesFlatAddr BS3_DATA_NM(g_Bs3Trap64GenericEntriesFlatAddr) + +# define g_uBs3CpuDetected BS3_DATA_NM(g_uBs3CpuDetected) + +#endif /* ARCH_BITS == 64 */ +#endif /* not needed */ + +#endif /* !BS3KIT_INCLUDED_bs3kit_mangling_data_h */ + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-template-footer.h b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-template-footer.h new file mode 100644 index 00000000..39004229 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-template-footer.h @@ -0,0 +1,93 @@ +/* $Id: bs3kit-template-footer.h $ */ +/** @file + * BS3Kit footer for multi-mode code templates. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/* + * Undefine macros defined by the header. + * This is a subset of what bs3kit-template-footer.mac does. + */ +#undef TMPL_RM +#undef TMPL_PE16 +#undef TMPL_PE16_32 +#undef TMPL_PE16_V86 +#undef TMPL_PE32 +#undef TMPL_PE32_16 +#undef TMPL_PEV86 +#undef TMPL_PP16 +#undef TMPL_PP16_32 +#undef TMPL_PP16_V86 +#undef TMPL_PP32 +#undef TMPL_PP32_16 +#undef TMPL_PPV86 +#undef TMPL_PAE16 +#undef TMPL_PAE16_32 +#undef TMPL_PAE16_V86 +#undef TMPL_PAE32 +#undef TMPL_PAE32_16 +#undef TMPL_PAEV86 +#undef TMPL_LM16 +#undef TMPL_LM32 +#undef TMPL_LM64 + +#undef TMPL_CMN_PE +#undef TMPL_SYS_PE16 +#undef TMPL_SYS_PE32 +#undef TMPL_CMN_PP +#undef TMPL_SYS_PP16 +#undef TMPL_SYS_PP32 +#undef TMPL_CMN_PAE +#undef TMPL_SYS_PAE16 +#undef TMPL_SYS_PAE32 +#undef TMPL_CMN_LM +#undef TMPL_CMN_V86 +#undef TMPL_CMN_R86 +#undef TMPL_CMN_PAGING +#undef TMPL_CMN_WEIRD +#undef TMPL_CMN_WEIRD_V86 + +#undef TMPL_CMN_R86 + +#undef TMPL_NM +#undef TMPL_FAR_NM +#undef TMPL_MODE +#undef TMPL_MODE_STR +#undef TMPL_MODE_LNAME +#undef TMPL_MODE_UNAME +#undef TMPL_16BIT +#undef TMPL_32BIT +#undef TMPL_64BIT +#undef TMPL_BITS + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-template-footer.mac b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-template-footer.mac new file mode 100644 index 00000000..ec24e3fc --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-template-footer.mac @@ -0,0 +1,142 @@ +; $Id: bs3kit-template-footer.mac $ +;; @file +; BS3Kit footer for multi-mode code templates. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +; +; Undefine macros defined by the header. +; +; Note! The following is useful for verifying that all macros are included here: +; +; for i in `grep "%define" bootsector2-template-header.mac \ +; | sed -e 's/^ *%define *//' -e 's/^\([^() ]*\).*$/\1/' \ +; | sort -u` +; do +; if ! grep -wF "%undef $i" bootsector2-template-footer.mac; then +; echo $i +; fi +; done +; +%undef TMPL_RM +%undef TMPL_PE16 +%undef TMPL_PE16_32 +%undef TMPL_PE16_V86 +%undef TMPL_PE32 +%undef TMPL_PE32_16 +%undef TMPL_PEV86 +%undef TMPL_PP16 +%undef TMPL_PP16_32 +%undef TMPL_PP16_V86 +%undef TMPL_PP32 +%undef TMPL_PP32_16 +%undef TMPL_PPV86 +%undef TMPL_PAE16 +%undef TMPL_PAE16_32 +%undef TMPL_PAE16_V86 +%undef TMPL_PAE32 +%undef TMPL_PAE32_16 +%undef TMPL_PAEV86 +%undef TMPL_LM16 +%undef TMPL_LM32 +%undef TMPL_LM64 + +%undef TMPL_CMN_PE +%undef TMPL_SYS_PE16 +%undef TMPL_SYS_PE32 +%undef TMPL_CMN_PP +%undef TMPL_SYS_PP16 +%undef TMPL_SYS_PP32 +%undef TMPL_CMN_PAE +%undef TMPL_SYS_PAE16 +%undef TMPL_SYS_PAE32 +%undef TMPL_CMN_LM +%undef TMPL_CMN_V86 +%undef TMPL_CMN_R86 +%undef TMPL_CMN_PAGING +%undef TMPL_CMN_WEIRD +%undef TMPL_CMN_WEIRD_V86 + +%undef TMPL_CMN_R86 + +%undef TMPL_NM +%undef TMPL_NM_U +%undef TMPL_FAR_NM +%undef BS3_CMN_NM +%undef TMPL_UNDESCORE +%undef TMPL_MODE_UNAME +%undef TMPL_MODE_LNAME +%undef TMPL_MODE_STR +%undef TMPL_16BIT +%undef TMPL_32BIT +%undef TMPL_64BIT +%undef TMPL_BITS +%undef TMPL_PTR_DEF +%undef TMPL_HAVE_BIOS +%undef TMPL_BEGINCODE + +%undef xCB +%undef xDEF +%undef xRES +%undef xPRE +%undef xSP +%undef xBP +%undef xAX +%undef xBX +%undef xCX +%undef xDX +%undef xDI +%undef xSI +%undef xWrtRIP + +%undef sCB +%undef sDEF +%undef sRES +%undef sPRE +%undef sSP +%undef sBP +%undef sAX +%undef sBX +%undef sCX +%undef sDX +%undef sDI +%undef sSI + +%unmacro TONLY16 1+ +%unmacro TONLY32 1+ +%unmacro TONLY64 1+ +%unmacro TNOT16 1+ +%unmacro TNOT32 1+ +%unmacro TNOT64 1+ + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-template-header.h b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-template-header.h new file mode 100644 index 00000000..5975bd9f --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-template-header.h @@ -0,0 +1,532 @@ +/* $Id: bs3kit-template-header.h $ */ +/** @file + * BS3Kit header for multi-mode code templates. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "bs3kit.h" + +/** @defgroup grp_bs3kit_tmpl Multi-Mode Code Templates + * @ingroup grp_bs3kit + * + * Multi-mode code templates avoid duplicating code for each of the CPU modes. + * Instead the code is compiled multiple times, either via multiple inclusions + * into a source files with different mode selectors defined or by multiple + * compiler invocations. + * + * In C/C++ code we're restricted to the compiler target bit count, whereas in + * assembly we can do everything in assembler run (with some 64-bit + * restrictions, that is). + * + * Before \#defining the next mode selector and including + * bs3kit-template-header.h again, include bs3kit-template-footer.h to undefine + * all the previous mode selectors and the macros defined by the header. + * + * @{ + */ + +#ifdef DOXYGEN_RUNNING +/** @name Template mode selectors. + * + * Exactly one of these are defined by the file including the + * bs3kit-template-header.h header file. When building the code libraries, the + * kBuild target defines this. + * + * @{ */ +# define TMPL_RM /**< real mode. */ + +# define TMPL_PE16 /**< 16-bit protected mode kernel+tss, running 16-bit code, unpaged. */ +# define TMPL_PE16_32 /**< 16-bit protected mode kernel+tss, running 32-bit code, unpaged. */ +# define TMPL_PE16_V86 /**< 16-bit protected mode kernel+tss, running virtual 8086 mode code, unpaged. */ +# define TMPL_PE32 /**< 32-bit protected mode kernel+tss, running 32-bit code, unpaged. */ +# define TMPL_PE32_16 /**< 32-bit protected mode kernel+tss, running 16-bit code, unpaged. */ +# define TMPL_PEV86 /**< 32-bit protected mode kernel+tss, running virtual 8086 mode code, unpaged. */ + +# define TMPL_PP16 /**< 16-bit protected mode kernel+tss, running 16-bit code, paged. */ +# define TMPL_PP16_32 /**< 16-bit protected mode kernel+tss, running 32-bit code, paged. */ +# define TMPL_PP16_V86 /**< 16-bit protected mode kernel+tss, running virtual 8086 mode code, paged. */ +# define TMPL_PP32 /**< 32-bit protected mode kernel+tss, running 32-bit code, paged. */ +# define TMPL_PP32_16 /**< 32-bit protected mode kernel+tss, running 16-bit code, paged. */ +# define TMPL_PPV86 /**< 32-bit protected mode kernel+tss, running virtual 8086 mode code, paged. */ + +# define TMPL_PAE16 /**< 16-bit protected mode kernel+tss, running 16-bit code, PAE paging. */ +# define TMPL_PAE16_32 /**< 16-bit protected mode kernel+tss, running 32-bit code, PAE paging. */ +# define TMPL_PAE16_V86 /**< 16-bit protected mode kernel+tss, running virtual 8086 mode code, PAE paging. */ +# define TMPL_PAE32 /**< 32-bit protected mode kernel+tss, running 32-bit code, PAE paging. */ +# define TMPL_PAE32_16 /**< 32-bit protected mode kernel+tss, running 16-bit code, PAE paging. */ +# define TMPL_PAEV86 /**< 32-bit protected mode kernel+tss, running virtual 8086 mode code, PAE paging. */ + +# define TMPL_LM16 /**< 16-bit long mode (paged), kernel+tss always 64-bit. */ +# define TMPL_LM32 /**< 32-bit long mode (paged), kernel+tss always 64-bit. */ +# define TMPL_LM64 /**< 64-bit long mode (paged), kernel+tss always 64-bit. */ +/** @} */ + +/** @name Derived Indicators + * @{ */ +# define TMPL_CMN_PE /**< TMPL_PE16 | TMPL_PE16_32 | TMPL_PE16_V86 | TMPL_PE32 | TMPL_PE32_16 | TMPL_PEV86 */ +# define TMPL_SYS_PE16 /**< TMPL_PE16 | TMPL_PE16_32 | TMPL_PE16_V86 */ +# define TMPL_SYS_PE32 /**< TMPL_PE32 | TMPL_PE32_16 | TMPL_PEV86 */ +# define TMPL_CMN_PP /**< TMPL_PP16 | TMPL_PP16_32 | TMPL_PP16_V86 | TMPL_PP32 | TMPL_PP32_16 | TMPL_PPV86 */ +# define TMPL_SYS_PP16 /**< TMPL_PP16 | TMPL_PP16_32 | TMPL_PP16_V86 */ +# define TMPL_SYS_PP32 /**< TMPL_PP32 | TMPL_PP32_16 | TMPL_PPV86 */ +# define TMPL_CMN_PAE /**< TMPL_PAE16 | TMPL_PAE16_32 | TMPL_PAE16_V86 | TMPL_PAE32 | TMPL_PAE32_16 | TMPL_PAEV86 */ +# define TMPL_SYS_PAE16 /**< TMPL_PAE16 | TMPL_PAE16_32 | TMPL_PAE16_V86 */ +# define TMPL_SYS_PAE32 /**< TMPL_PAE32 | TMPL_PAE32_16 | TMPL_PAEV86 */ +# define TMPL_CMN_LM /**< TMPL_LM16 | TMPL_LM32 | TMPL_LM64 */ +# define TMPL_CMN_V86 /**< TMPL_PEV86 | TMPL_PE16_V86 | TMPL_PPV86 | TMPL_PP16_V86 | TMPL_PAEV86 | TMPL_PAE16_V86 */ +# define TMPL_CMN_R86 /**< TMPL_CMN_V86 | TMPL_RM */ +# define TMPL_CMN_PAGING /**< TMPL_CMN_PP | TMPL_CMN_PAE | TMPL_CMN_LM */ +# define TMPL_CMN_WEIRD /**< TMPL_PE16_32 | TMPL_PE32_16 | TMPL_PP16_32 | TMPL_PP32_16 | TMPL_PAE16_32 | TMPL_PAE32_16 | TMPL_CMN_WEIRD_V86 */ +# define TMPL_CMN_WEIRD_V86 /**< TMPL_PE16_V86 | TMPL_PP16_V86 | TMPL_PAE16_V86 */ +/** @} */ + +/** @def TMPL_NM + * Name mangling macro for the current mode. + * + * Example: TMPL_NM(PrintChr) + * + * @param Name The function or variable name to mangle. + * @sa #TMPL_FAR_NM, #BS3_CMN_NM, #BS3_CMN_FAR_NM + */ +# define TMPL_NM(Name) RT_CONCAT(Name,_mode) + +/** @def TMPL_FAR_NM + * Name mangling macro for the current mode into a far function name. + * + * In 32-bit and 64-bit code this does not differ from #TMPL_NM. + * + * Example: TMPL_FAR_NM(PrintChr) + * + * @param Name The function or variable name to mangle. + * @sa #TMPL_NM, #BS3_CMN_FAR_NM, #BS3_CMN_NM + */ +# define TMPL_FAR_NM(Name) RT_CONCAT3(Name,_mode,_far) + +/** @def TMPL_MODE_STR + * Short mode description. */ +# define TMPL_MODE_STR + +/** @def TMPL_HAVE_BIOS + * Indicates that we have direct access to the BIOS (only in real mode). */ +# define TMPL_HAVE_BIOS + + +/** @name For ASM compatability + * @{ */ +/** @def TMPL_16BIT + * For ASM compatibility - please use ARCH_BITS == 16. */ +# define TMPL_16BIT +/** @def TMPL_32BIT + * For ASM compatibility - please use ARCH_BITS == 32. */ +# define TMPL_32BIT +/** @def TMPL_64BIT + * For ASM compatibility - please use ARCH_BITS == 64. */ +# define TMPL_64BIT + +/** @def TMPL_BITS + * For ASM compatibility - please use ARCH_BITS instead. */ +# define TMPL_BITS ARCH_BITS +/** @} */ + +#else /* !DOXYGEN_RUNNING */ + +//#undef BS3_CMN_NM +//#undef BS3_CMN_FAR_NM + + +/* + * Convert TMPL_XXX to TMPL_MODE. + */ +#ifndef TMPL_MODE +# ifdef TMPL_RM +# define TMPL_MODE BS3_MODE_RM +# elif defined(TMPL_PE16) +# define TMPL_MODE BS3_MODE_PE16 +# elif defined(TMPL_PE16_32) +# define TMPL_MODE BS3_MODE_PE16_32 +# elif defined(TMPL_PE16_V86) +# define TMPL_MODE BS3_MODE_PE16_V86 +# elif defined(TMPL_PE32) +# define TMPL_MODE BS3_MODE_PE32 +# elif defined(TMPL_PE32_16) +# define TMPL_MODE BS3_MODE_PE32_16 +# elif defined(TMPL_PEV86) +# define TMPL_MODE BS3_MODE_PEV86 +# elif defined(TMPL_PP16) +# define TMPL_MODE BS3_MODE_PP16 +# elif defined(TMPL_PP16_32) +# define TMPL_MODE BS3_MODE_PP16_32 +# elif defined(TMPL_PP16_V86) +# define TMPL_MODE BS3_MODE_PP16_V86 +# elif defined(TMPL_PP32) +# define TMPL_MODE BS3_MODE_PP32 +# elif defined(TMPL_PP32_16) +# define TMPL_MODE BS3_MODE_PP32_16 +# elif defined(TMPL_PPV86) +# define TMPL_MODE BS3_MODE_PPV86 +# elif defined(TMPL_PAE16) +# define TMPL_MODE BS3_MODE_PAE16 +# elif defined(TMPL_PAE16_32) +# define TMPL_MODE BS3_MODE_PAE16_32 +# elif defined(TMPL_PAE16_V86) +# define TMPL_MODE BS3_MODE_PAE16_V86 +# elif defined(TMPL_PAE32) +# define TMPL_MODE BS3_MODE_PAE32 +# elif defined(TMPL_PAE32_16) +# define TMPL_MODE BS3_MODE_PAE32_16 +# elif defined(TMPL_PAEV86) +# define TMPL_MODE BS3_MODE_PAEV86 +# elif defined(TMPL_LM16) +# define TMPL_MODE BS3_MODE_LM16 +# elif defined(TMPL_LM32) +# define TMPL_MODE BS3_MODE_LM32 +# elif defined(TMPL_LM64) +# define TMPL_MODE BS3_MODE_LM64 +# else +# error "Unable to to figure out the template mode." +# endif +#endif + + +/* + * Check the code bitness and set derived defines. + */ +#if (TMPL_MODE & BS3_MODE_CODE_MASK) == BS3_MODE_CODE_16 +# if ARCH_BITS != 16 +# error "BS3_MODE_CODE_16 requires ARCH_BITS to be 16." +# endif +# define TMPL_16BIT +# define TMPL_BITS 16 +# define TMPL_UNDERSCORE _ +//# define BS3_CMN_NM(Name) RT_CONCAT(Name,_c16) +//# define BS3_CMN_FAR_NM(Name) RT_CONCAT(Name,_f16) + + +#elif (TMPL_MODE & BS3_MODE_CODE_MASK) == BS3_MODE_CODE_32 +# if ARCH_BITS != 32 +# error "BS3_MODE_CODE_32 requires ARCH_BITS to be 32." +# endif +# define TMPL_32BIT +# define TMPL_BITS 32 +# define TMPL_UNDERSCORE _ +//# define BS3_CMN_NM(Name) RT_CONCAT(Name,_c32) +//# define BS3_CMN_FAR_NM(a_Name) RT_CONCAT(Name,_c32) + +#elif (TMPL_MODE & BS3_MODE_CODE_MASK) == BS3_MODE_CODE_V86 +# if ARCH_BITS != 16 +# error "BS3_MODE_CODE_V86 requires ARCH_BITS to be 16." +# endif +# define TMPL_16BIT +# define TMPL_BITS 16 +# define TMPL_UNDERSCORE _ +//# define BS3_CMN_NM(Name) RT_CONCAT(Name,_c16) +//# define BS3_CMN_FAR_NM(Name) RT_CONCAT(Name,_f16) +# define TMPL_CMN_R86 +# define TMPL_CMN_V86 + +#elif (TMPL_MODE & BS3_MODE_CODE_MASK) == BS3_MODE_CODE_64 +# if ARCH_BITS != 64 +# error "BS3_MODE_CODE_64 requires ARCH_BITS to be 64." +# endif +# define TMPL_64BIT +# define TMPL_BITS 64 +# define TMPL_UNDERSCORE +//# define BS3_CMN_NM(Name) RT_CONCAT(Name,_c64) +//# define BS3_CMN_FAR_NM(a_Name) RT_CONCAT(Name,_c64) + +#else +# error "Invalid TMPL_MODE value!" +#endif + + +/* + * Check the system specific mask and set derived values. + */ +#if (TMPL_MODE & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_RM +# define TMPL_HAVE_BIOS +# define TMPL_CMN_R86 + +#elif (TMPL_MODE & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PE16 +# define TMPL_SYS_PE16 +# define TMPL_CMN_PE + +#elif (TMPL_MODE & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PE32 +# define TMPL_SYS_PE32 +# define TMPL_CMN_PE + +#elif (TMPL_MODE & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PP16 +# define TMPL_SYS_PP16 +# define TMPL_CMN_PP +# define TMPL_CMN_PAGING + +#elif (TMPL_MODE & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PP32 +# define TMPL_SYS_PP32 +# define TMPL_CMN_PP +# define TMPL_CMN_PAGING + +#elif (TMPL_MODE & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PAE16 +# define TMPL_SYS_PAE16 +# define TMPL_CMN_PAE +# define TMPL_CMN_PAGING + +#elif (TMPL_MODE & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PAE32 +# define TMPL_SYS_PAE32 +# define TMPL_CMN_PAE +# define TMPL_CMN_PAGING + +#elif (TMPL_MODE & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_LM +# define TMPL_SYS_LM +# define TMPL_CMN_LM +# define TMPL_CMN_PAGING + +#else +# error "Invalid TMPL_MODE value!" +#endif + + +/* + * Mode specific stuff. + */ +#if TMPL_MODE == BS3_MODE_RM +# define TMPL_RM 1 +# define TMPL_MODE_STR "real mode" +# define TMPL_NM(Name) RT_CONCAT(Name,_rm) +# define TMPL_MODE_LNAME rm +# define TMPL_MODE_UNAME RM + + +#elif TMPL_MODE == BS3_MODE_PE16 +# define TMPL_PE16 1 +# define TMPL_MODE_STR "16-bit prot, 16-bit" +# define TMPL_NM(Name) RT_CONCAT(Name,_pe16) +# define TMPL_MODE_LNAME pe16 +# define TMPL_MODE_UNAME PE16 + +#elif TMPL_MODE == BS3_MODE_PE16_32 +# define TMPL_PE16_32 1 +# define TMPL_MODE_STR "16-bit prot, 32-bit" +# define TMPL_NM(Name) RT_CONCAT(Name,_pe16_32) +# define TMPL_MODE_LNAME pe16_32 +# define TMPL_MODE_UNAME PE16_32 +# define TMPL_CMN_WEIRD + +#elif TMPL_MODE == BS3_MODE_PE16_V86 +# define TMPL_PE16_V86 1 +# define TMPL_MODE_STR "16-bit prot, v8086" +# define TMPL_NM(Name) RT_CONCAT(Name,_pe16_v86) +# define TMPL_MODE_LNAME pe16_v86 +# define TMPL_MODE_UNAME PE16_v86 +# define TMPL_CMN_WEIRD +# define TMPL_CMN_WEIRD_V86 + + +#elif TMPL_MODE == BS3_MODE_PE32 +# define TMPL_PE32 1 +# define TMPL_MODE_STR "32-bit prot, 32-bit" +# define TMPL_NM(Name) RT_CONCAT(Name,_pe32) +# define TMPL_MODE_LNAME pe32 +# define TMPL_MODE_UNAME PE32 + +#elif TMPL_MODE == BS3_MODE_PE32_16 +# define TMPL_PE32_16 1 +# define TMPL_MODE_STR "32-bit prot, 16-bit" +# define TMPL_NM(Name) RT_CONCAT(Name,_pe32_16) +# define TMPL_MODE_LNAME pe32_16 +# define TMPL_MODE_UNAME PE32_16 +# define TMPL_CMN_WEIRD + +#elif TMPL_MODE == BS3_MODE_PEV86 +# define TMPL_PEV86 1 +# define TMPL_MODE_STR "32-bit prot, v8086" +# define TMPL_NM(Name) RT_CONCAT(Name,_pev86) +# define TMPL_MODE_LNAME pev86 +# define TMPL_MODE_UNAME PEV86 + + +#elif TMPL_MODE == BS3_MODE_PP16 +# define TMPL_PP16 1 +# define TMPL_MODE_STR "16-bit paged, 16-bit" +# define TMPL_NM(Name) RT_CONCAT(Name,_pp16) +# define TMPL_MODE_LNAME pp16 +# define TMPL_MODE_UNAME PP16 + +#elif TMPL_MODE == BS3_MODE_PP16_32 +# define TMPL_PP16_32 1 +# define TMPL_MODE_STR "16-bit paged, 32-bit" +# define TMPL_NM(Name) RT_CONCAT(Name,_pp16_32) +# define TMPL_MODE_LNAME pp16_32 +# define TMPL_MODE_UNAME PP16_32 +# define TMPL_CMN_WEIRD + +#elif TMPL_MODE == BS3_MODE_PP16_V86 +# define TMPL_PP16_V86 1 +# define TMPL_MODE_STR "16-bit paged, v8086" +# define TMPL_NM(Name) RT_CONCAT(Name,_pp16_v86) +# define TMPL_MODE_LNAME pp16_v86 +# define TMPL_MODE_UNAME PP16_v86 +# define TMPL_CMN_WEIRD +# define TMPL_CMN_WEIRD_V86 + + +#elif TMPL_MODE == BS3_MODE_PP32 +# define TMPL_PP32 1 +# define TMPL_MODE_STR "32-bit paged, 32-bit" +# define TMPL_NM(Name) RT_CONCAT(Name,_pp32) +# define TMPL_MODE_LNAME pp32 +# define TMPL_MODE_UNAME PP32 + +#elif TMPL_MODE == BS3_MODE_PP32_16 +# define TMPL_PP32_16 1 +# define TMPL_MODE_STR "32-bit paged, 16-bit" +# define TMPL_NM(Name) RT_CONCAT(Name,_pp32_16) +# define TMPL_MODE_LNAME pp32_16 +# define TMPL_MODE_UNAME PP32_16 +# define TMPL_CMN_WEIRD + +#elif TMPL_MODE == BS3_MODE_PPV86 +# define TMPL_PPV86 1 +# define TMPL_MODE_STR "32-bit paged, v8086" +# define TMPL_NM(Name) RT_CONCAT(Name,_ppv86) +# define TMPL_MODE_LNAME ppv86 +# define TMPL_MODE_UNAME PPV86 + + +#elif TMPL_MODE == BS3_MODE_PAE16 +# define TMPL_PAE16 1 +# define TMPL_MODE_STR "16-bit pae, 16-bit" +# define TMPL_NM(Name) RT_CONCAT(Name,_pae16) +# define TMPL_MODE_LNAME pae16 +# define TMPL_MODE_UNAME PAE16 + +#elif TMPL_MODE == BS3_MODE_PAE16_32 +# define TMPL_PAE16_32 1 +# define TMPL_MODE_STR "16-bit pae, 32-bit" +# define TMPL_NM(Name) RT_CONCAT(Name,_pae16_32) +# define TMPL_MODE_LNAME pae16_32 +# define TMPL_MODE_UNAME PAE16_32 +# define TMPL_CMN_WEIRD + +#elif TMPL_MODE == BS3_MODE_PAE16_V86 +# define TMPL_PAE16_V86 1 +# define TMPL_MODE_STR "16-bit pae, v8086" +# define TMPL_NM(Name) RT_CONCAT(Name,_pae16_v86) +# define TMPL_MODE_LNAME pae16_v86 +# define TMPL_MODE_UNAME PAE16_v86 +# define TMPL_CMN_WEIRD +# define TMPL_CMN_WEIRD_V86 + + +#elif TMPL_MODE == BS3_MODE_PAE32 +# define TMPL_PAE32 1 +# define TMPL_MODE_STR "32-bit pae, 32-bit" +# define TMPL_NM(Name) RT_CONCAT(Name,_pae32) +# define TMPL_MODE_LNAME pae32 +# define TMPL_MODE_UNAME PAE32 + +#elif TMPL_MODE == BS3_MODE_PAE32_16 +# define TMPL_PAE32_16 1 +# define TMPL_MODE_STR "32-bit pae, 32-bit" +# define TMPL_NM(Name) RT_CONCAT(Name,_pae32_16) +# define TMPL_MODE_LNAME pae32_16 +# define TMPL_MODE_UNAME PAE32_16 +# define TMPL_CMN_WEIRD + +#elif TMPL_MODE == BS3_MODE_PAEV86 +# define TMPL_PAEV86 1 +# define TMPL_MODE_STR "32-bit pae, v8086 pae" +# define TMPL_NM(Name) RT_CONCAT(Name,_paev86) +# define TMPL_MODE_LNAME paev86 +# define TMPL_MODE_UNAME PAEV86 + + +#elif TMPL_MODE == BS3_MODE_LM16 +# define TMPL_LM16 1 +# define TMPL_MODE_STR "long, 16-bit" +# define TMPL_NM(Name) RT_CONCAT(Name,_lm16) +# define TMPL_MODE_LNAME lm16 +# define TMPL_MODE_UNAME LM16 + +#elif TMPL_MODE == BS3_MODE_LM32 +# define TMPL_LM32 1 +# define TMPL_MODE_STR "long, 32-bit" +# define TMPL_NM(Name) RT_CONCAT(Name,_lm32) +# define TMPL_MODE_LNAME lm32 +# define TMPL_MODE_UNAME LM32 + +#elif TMPL_MODE == BS3_MODE_LM64 +# define TMPL_LM64 1 +# define TMPL_MODE_STR "long, 64-bit" +# define TMPL_NM(Name) RT_CONCAT(Name,_lm64) +# define TMPL_MODE_LNAME lm64 +# define TMPL_MODE_UNAME LM64 + +#else +# error "Invalid TMPL_MODE value!!" +#endif + + +#if TMPL_MODE & (BS3_MODE_CODE_16 | BS3_MODE_CODE_V86) +# define TMPL_FAR_NM(Name) RT_CONCAT3(TMPL_NM(Name),_f,ar) /* _far and far may be #defined already. */ +#else +# define TMPL_FAR_NM(Name) TMPL_NM(Name) +#endif + + +/** @def BS3_MODE_DEF + * Macro for defining a mode specific function. + * + * This makes 16-bit mode functions far, while 32-bit and 64-bit are near. + * You need to update the make file to generate near->far wrappers in most + * cases. + * + * @param a_RetType The return type. + * @param a_Name The function basename. + * @param a_Params The parameter list (in parentheses). + * + * @sa BS3_MODE_PROTO + */ +#if ARCH_BITS == 16 +# define BS3_MODE_DEF(a_RetType, a_Name, a_Params) BS3_DECL_FAR(a_RetType) TMPL_FAR_NM(a_Name) a_Params +#else +# define BS3_MODE_DEF(a_RetType, a_Name, a_Params) BS3_DECL_NEAR(a_RetType) TMPL_NM(a_Name) a_Params +#endif + + + +#ifndef TMPL_MODE_STR +# error "internal error" +#endif + +#endif /* !DOXYGEN_RUNNING */ +/** @} */ + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-template-header.mac b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-template-header.mac new file mode 100644 index 00000000..e12533af --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-template-header.mac @@ -0,0 +1,532 @@ +; $Id: bs3kit-template-header.mac $ +;; @file +; BS3Kit header for multi-mode code templates. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "bs3kit.mac" + +; +; Check and expand the mode defines. +; One of the following must be defined: +; - TMPL_RM - real mode. +; - TMPL_PE16 - 16-bit protected mode, unpaged. +; - TMPL_PE32 - 32-bit protected mode, unpaged. +; - TMPL_PEV86 - virtual 8086 mode under protected mode, unpaged. +; - TMPL_PP16 - 16-bit protected mode, paged. +; - TMPL_PP32 - 32-bit protected mode, paged. +; - TMPL_PPV86 - virtual 8086 mode under protected mode, paged. +; - TMPL_PAE16 - 16-bit protected mode with PAE (paged). +; - TMPL_PAE32 - 16-bit protected mode with PAE (paged). +; - TMPL_PAEV86- virtual 8086 mode under protected mode with PAE (paged). +; - TMPL_LM16 - 16-bit long mode (paged). +; - TMPL_LM32 - 32-bit long mode (paged). +; - TMPL_LM64 - 64-bit long mode (paged). +; +; Derived indicators: +; - TMPL_CMN_PE = TMPL_PE16 | TMPL_PE32 | TMPL_PEV86 +; - TMPL_CMN_PP = TMPL_PP16 | TMPL_PP32 | TMPL_PPV86 +; - TMPL_CMN_PAE = TMPL_PAE16 | TMPL_PAE32 | TMPL_PAEV86 +; - TMPL_CMN_LM = TMPL_LM16 | TMPL_LM32 | TMPL_LM64 +; - TMPL_CMN_V86 = TMPL_PEV86 | TMPL_PPV86 | TMPL_PAEV86 +; - TMPL_CMN_R86 = TMPL_CMN_V86 | TMPL_RM +; - TMPL_CMN_PAGING = TMPL_CMN_PP | TMPL_CMN_PAE | TMPL_CMN_LM +; + + +; +; Convert TMPL_XXX to TMPL_MODE. +; +%ifndef TMPL_MODE + %ifdef TMPL_RM + %define TMPL_MODE BS3_MODE_RM + %elifdef TMPL_PE16 + %define TMPL_MODE BS3_MODE_PE16 + %elifdef TMPL_PE16_32 + %define TMPL_MODE BS3_MODE_PE16_32 + %elifdef TMPL_PE16_V86 + %define TMPL_MODE BS3_MODE_PE16_V86 + %elifdef TMPL_PE32 + %define TMPL_MODE BS3_MODE_PE32 + %elifdef TMPL_PE32_16 + %define TMPL_MODE BS3_MODE_PE32_16 + %elifdef TMPL_PEV86 + %define TMPL_MODE BS3_MODE_PEV86 + %elifdef TMPL_PP16 + %define TMPL_MODE BS3_MODE_PP16 + %elifdef TMPL_PP16_32 + %define TMPL_MODE BS3_MODE_PP16_32 + %elifdef TMPL_PP16_V86 + %define TMPL_MODE BS3_MODE_PP16_V86 + %elifdef TMPL_PP32 + %define TMPL_MODE BS3_MODE_PP32 + %elifdef TMPL_PP32_16 + %define TMPL_MODE BS3_MODE_PP32_16 + %elifdef TMPL_PPV86 + %define TMPL_MODE BS3_MODE_PPV86 + %elifdef TMPL_PAE16 + %define TMPL_MODE BS3_MODE_PAE16 + %elifdef TMPL_PAE16_32 + %define TMPL_MODE BS3_MODE_PAE16_32 + %elifdef TMPL_PAE16_V86 + %define TMPL_MODE BS3_MODE_PAE16_V86 + %elifdef TMPL_PAE32 + %define TMPL_MODE BS3_MODE_PAE32 + %elifdef TMPL_PAE32_16 + %define TMPL_MODE BS3_MODE_PAE32_16 + %elifdef TMPL_PAEV86 + %define TMPL_MODE BS3_MODE_PAEV86 + %elifdef TMPL_LM16 + %define TMPL_MODE BS3_MODE_LM16 + %elifdef TMPL_LM32 + %define TMPL_MODE BS3_MODE_LM32 + %elifdef TMPL_LM64 + %define TMPL_MODE BS3_MODE_LM64 + %else + %error "Unable to to figure out the template mode." + %endif +%endif + +; +; Check the code bitness and set TMPL_XXBITS, TMPL_BITS, BS3_CMN_NM +; +%if (TMPL_MODE & BS3_MODE_CODE_MASK) == BS3_MODE_CODE_16 + %define TMPL_16BIT + %define TMPL_BITS 16 + %define TMPL_PTR_DEF dw + %define TMPL_UNDERSCORE _ + %define BS3_CMN_NM(Name) _ %+ Name %+ _c16 + +%elif (TMPL_MODE & BS3_MODE_CODE_MASK) == BS3_MODE_CODE_32 + %define TMPL_32BIT + %define TMPL_BITS 32 + %define TMPL_PTR_DEF dd + %define TMPL_UNDERSCORE _ + %define BS3_CMN_NM(Name) _ %+ Name %+ _c32 + +%elif (TMPL_MODE & BS3_MODE_CODE_MASK) == BS3_MODE_CODE_V86 + %define TMPL_16BIT + %define TMPL_BITS 16 + %define TMPL_UNDERSCORE _ + %define BS3_CMN_NM(Name) _ %+ Name %+ _c16 + %define TMPL_CMN_R86 + %define TMPL_CMN_V86 + +%elif (TMPL_MODE & BS3_MODE_CODE_MASK) == BS3_MODE_CODE_64 + %define TMPL_64BIT + %define TMPL_BITS 64 + %define TMPL_PTR_DEF dq + %define TMPL_UNDERSCORE _ + %define BS3_CMN_NM(Name) _ %+ Name %+ _c64 + +%else + %error "Invalid TMPL_MODE value!" +%endif + +; +; Check the system specific mask and set derived values. +; +%if (TMPL_MODE & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_RM + %define TMPL_HAVE_BIOS + %define TMPL_CMN_R86 + +%elif (TMPL_MODE & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PE16 + %define TMPL_SYS_PE16 + %define TMPL_CMN_PE + +%elif (TMPL_MODE & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PE32 + %define TMPL_SYS_PE32 + %define TMPL_CMN_PE + +%elif (TMPL_MODE & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PP16 + %define TMPL_SYS_PP16 + %define TMPL_CMN_PP + %define TMPL_CMN_PAGING + +%elif (TMPL_MODE & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PP32 + %define TMPL_SYS_PP32 + %define TMPL_CMN_PP + %define TMPL_CMN_PAGING + +%elif (TMPL_MODE & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PAE16 + %define TMPL_SYS_PAE16 + %define TMPL_CMN_PAE + %define TMPL_CMN_PAGING + +%elif (TMPL_MODE & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PAE32 + %define TMPL_SYS_PAE32 + %define TMPL_CMN_PAE + %define TMPL_CMN_PAGING + +%elif (TMPL_MODE & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_LM + %define TMPL_SYS_LM + %define TMPL_CMN_LM + %define TMPL_CMN_PAGING + +%else + %error "Invalid TMPL_MODE value!" +%endif + + +; +; Mode specific stuff. +; +%if TMPL_MODE == BS3_MODE_RM + %define TMPL_RM + %define TMPL_MODE_STR "real mode" + %define TMPL_NM(Name) _ %+ Name %+ _rm + %define TMPL_MODE_LNAME rm + %define TMPL_MODE_UNAME RM + + +%elif TMPL_MODE == BS3_MODE_PE16 + %define TMPL_PE16 + %define TMPL_MODE_STR "16-bit prot, 16-bit" + %define TMPL_NM(Name) _ %+ Name %+ _pe16 + %define TMPL_MODE_LNAME pe16 + %define TMPL_MODE_UNAME PE16 + +%elif TMPL_MODE == BS3_MODE_PE16_32 + %define TMPL_PE16_32 + %define TMPL_MODE_STR "16-bit prot, 32-bit" + %define TMPL_NM(Name) _ %+ Name %+ _pe16_32 + %define TMPL_MODE_LNAME pe16_32 + %define TMPL_MODE_UNAME PE16_32 + %define TMPL_CMN_WEIRD + +%elif TMPL_MODE == BS3_MODE_PE16_V86 + %define TMPL_PE16_V86 + %define TMPL_MODE_STR "16-bit prot, v8086" + %define TMPL_NM(Name) _ %+ Name %+ _pe16_v86 + %define TMPL_MODE_LNAME pe16_v86 + %define TMPL_MODE_UNAME PE16_v86 + %define TMPL_CMN_WEIRD + %define TMPL_CMN_WEIRD_V86 + + +%elif TMPL_MODE == BS3_MODE_PE32 + %define TMPL_PE32 + %define TMPL_MODE_STR "32-bit prot, 32-bit" + %define TMPL_NM(Name) _ %+ Name %+ _pe32 + %define TMPL_MODE_LNAME pe32 + %define TMPL_MODE_UNAME PE32 + +%elif TMPL_MODE == BS3_MODE_PE32_16 + %define TMPL_PE32_16 + %define TMPL_MODE_STR "32-bit prot, 16-bit" + %define TMPL_NM(Name) _ %+ Name %+ _pe32_16 + %define TMPL_MODE_LNAME pe32_16 + %define TMPL_MODE_UNAME PE32_16 + %define TMPL_CMN_WEIRD + +%elif TMPL_MODE == BS3_MODE_PEV86 + %define TMPL_PEV86 + %define TMPL_MODE_STR "32-bit prot, v8086" + %define TMPL_NM(Name) _ %+ Name %+ _pev86 + %define TMPL_MODE_LNAME pev86 + %define TMPL_MODE_UNAME PEV86 + + +%elif TMPL_MODE == BS3_MODE_PP16 + %define TMPL_PP16 + %define TMPL_MODE_STR "16-bit paged, 16-bit" + %define TMPL_NM(Name) _ %+ Name %+ _pp16 + %define TMPL_MODE_LNAME pp16 + %define TMPL_MODE_UNAME PP16 + +%elif TMPL_MODE == BS3_MODE_PP16_32 + %define TMPL_PP16_32 + %define TMPL_MODE_STR "16-bit paged, 32-bit" + %define TMPL_NM(Name) _ %+ Name %+ _pp16_32 + %define TMPL_MODE_LNAME pp16_32 + %define TMPL_MODE_UNAME PP16_32 + %define TMPL_CMN_WEIRD + +%elif TMPL_MODE == BS3_MODE_PP16_V86 + %define TMPL_PP16_V86 + %define TMPL_MODE_STR "16-bit paged, v8086" + %define TMPL_NM(Name) _ %+ Name %+ _pp16_v86 + %define TMPL_MODE_LNAME pp16_v86 + %define TMPL_MODE_UNAME PP16_v86 + %define TMPL_CMN_WEIRD + %define TMPL_CMN_WEIRD_V86 + + +%elif TMPL_MODE == BS3_MODE_PP32 + %define TMPL_PP32 + %define TMPL_MODE_STR "32-bit paged, 32-bit" + %define TMPL_NM(Name) _ %+ Name %+ _pp32 + %define TMPL_MODE_LNAME pp32 + %define TMPL_MODE_UNAME PP32 + +%elif TMPL_MODE == BS3_MODE_PP32_16 + %define TMPL_PP32_16 + %define TMPL_MODE_STR "32-bit paged, 16-bit" + %define TMPL_NM(Name) _ %+ Name %+ _pp32_16 + %define TMPL_MODE_LNAME pp32_16 + %define TMPL_MODE_UNAME PP32_16 + %define TMPL_CMN_WEIRD + +%elif TMPL_MODE == BS3_MODE_PPV86 + %define TMPL_PPV86 + %define TMPL_MODE_STR "32-bit paged, v8086" + %define TMPL_NM(Name) _ %+ Name %+ _ppv86 + %define TMPL_MODE_LNAME ppv86 + %define TMPL_MODE_UNAME PPV86 + + +%elif TMPL_MODE == BS3_MODE_PAE16 + %define TMPL_PAE16 + %define TMPL_MODE_STR "16-bit pae, 16-bit" + %define TMPL_NM(Name) _ %+ Name %+ _pae16 + %define TMPL_MODE_LNAME pae16 + %define TMPL_MODE_UNAME PAE16 + +%elif TMPL_MODE == BS3_MODE_PAE16_32 + %define TMPL_PAE16_32 + %define TMPL_MODE_STR "16-bit pae, 32-bit" + %define TMPL_NM(Name) _ %+ Name %+ _pae16_32 + %define TMPL_MODE_LNAME pae16_32 + %define TMPL_MODE_UNAME PAE16_32 + %define TMPL_CMN_WEIRD + +%elif TMPL_MODE == BS3_MODE_PAE16_V86 + %define TMPL_PAE16_V86 + %define TMPL_MODE_STR "16-bit pae, v8086" + %define TMPL_NM(Name) _ %+ Name %+ _pae16_v86 + %define TMPL_MODE_LNAME pae16_v86 + %define TMPL_MODE_UNAME PAE16_v86 + %define TMPL_CMN_WEIRD + %define TMPL_CMN_WEIRD_V86 + + +%elif TMPL_MODE == BS3_MODE_PAE32 + %define TMPL_PAE32 + %define TMPL_MODE_STR "32-bit pae, 32-bit" + %define TMPL_NM(Name) _ %+ Name %+ _pae32 + %define TMPL_MODE_LNAME pae32 + %define TMPL_MODE_UNAME PAE32 + +%elif TMPL_MODE == BS3_MODE_PAE32_16 + %define TMPL_PAE32_16 + %define TMPL_MODE_STR "32-bit pae, 16-bit" + %define TMPL_NM(Name) _ %+ Name %+ _pae32_16 + %define TMPL_MODE_LNAME pae32_16 + %define TMPL_MODE_UNAME PAE32_16 + %define TMPL_CMN_WEIRD + +%elif TMPL_MODE == BS3_MODE_PAEV86 + %define TMPL_PAEV86 + %define TMPL_MODE_STR "32-bit pae, v8086 pae" + %define TMPL_NM(Name) _ %+ Name %+ _paev86 + %define TMPL_MODE_LNAME paev86 + %define TMPL_MODE_UNAME PAEV86 + + +%elif TMPL_MODE == BS3_MODE_LM16 + %define TMPL_LM16 + %define TMPL_MODE_STR "long, 16-bit" + %define TMPL_NM(Name) _ %+ Name %+ _lm16 + %define TMPL_MODE_LNAME lm16 + %define TMPL_MODE_UNAME LM16 + +%elif TMPL_MODE == BS3_MODE_LM32 + %define TMPL_LM32 + %define TMPL_MODE_STR "long, 32-bit" + %define TMPL_NM(Name) _ %+ Name %+ _lm32 + %define TMPL_MODE_LNAME lm32 + %define TMPL_MODE_UNAME LM32 + +%elif TMPL_MODE == BS3_MODE_LM64 + %define TMPL_LM64 + %define TMPL_MODE_STR "long, 64-bit" + %define TMPL_NM(Name) _ %+ Name %+ _lm64 + %define TMPL_MODE_LNAME lm64 + %define TMPL_MODE_UNAME LM64 + +%else + %error "Invalid TMPL_MODE value!!" +%endif + +%ifnidn TMPL_UNDERSCORE,_; RT_CONCAT3 doesn't work with TMPL_UNDERSCORE being empty. duh. + %ifidn RT_CONCAT(TestName_,TMPL_MODE_LNAME),TMPL_NM(TestName) + %else + %error internal error: RT_CONCAT(TestName_,TMPL_MODE_LNAME) vs TMPL_NM(TestName) + %endif +%else + %ifidn RT_CONCAT3(TMPL_UNDERSCORE,TestName_,TMPL_MODE_LNAME),TMPL_NM(TestName) + %else + %error internal error: RT_CONCAT3(TMPL_UNDERSCORE,TestName_,TMPL_MODE_LNAME) vs TMPL_NM(TestName) + %endif +%endif + +; TMPL_NM version with uppercased suffix and no underscore separating them. +%define TMPL_NM_U(Name) TMPL_UNDERSCORE %+ Name %+ TMPL_MODE_UNAME + +; TMPL_FAR_NM +%if TMPL_MODE & (BS3_MODE_CODE_16 | BS3_MODE_CODE_V86) + %define TMPL_FAR_NM(Name) TMPL_NM(Name) %+ _far +%else + %define TMPL_FAR_NM(Name) TMPL_NM(Name) +%endif + + +;; @def TMPL_WRT_FLAT +; WRT flat when not in 16-bit modes. +; +%ifdef TMPL_16BIT + %define TMPL_WRT_FLAT +%else + %define TMPL_WRT_FLAT wrt FLAT +%endif + +;; @def TMPL_WRT_DATA16_OR_FLAT +; WRT DATA16 in 16-bit mode, WRT FLAT in 32- and 64-bit modes. +; This is important when accessing global variables. +; +%ifdef TMPL_16BIT + %define TMPL_WRT_DATA16_OR_FLAT wrt BS3KIT_GRPNM_DATA16 +%else + %define TMPL_WRT_DATA16_OR_FLAT wrt FLAT +%endif + +;; @def TMPL_DATA16_WRT +; WRT DATA16 in 16-bit mode, WRT FLAT in 32- and 64-bit modes. +; This is important when accessing global variables. +; +%if TMPL_BITS == 16 + %define TMPL_DATA16_WRT(a_Var) a_Var wrt BS3KIT_GRPNM_DATA16 +%elif TMPL_BITS == 32 + %define TMPL_DATA16_WRT(a_Var) a_Var wrt FLAT +%elif TMPL_BITS == 64 + %define TMPL_DATA16_WRT(a_Var) rel a_Var wrt FLAT +%else + %error TMPL_BITS +%endif + +;; @def TMPL_WRT_SYSTEM16_OR_FLAT +; WRT BS3SYSTEM16 in 16-bit mode, WRT FLAT in 32- and 64-bit modes. +; This is important when accessing global variables in the BS3SYSTEM16 segment. +%ifdef TMPL_16BIT + %define TMPL_WRT_SYSTEM16_OR_FLAT wrt BS3SYSTEM16 +%else + %define TMPL_WRT_SYSTEM16_OR_FLAT wrt FLAT +%endif + +;; @def TONLY16 +; Version of BONLY16 that follows the code template. +; Like BONLY16 this normally goes in column 1. +%if TMPL_BITS == 16 + %macro TONLY16 1+ + %1 + %endmacro +%else + %macro TONLY16 1+ + %endmacro +%endif + +;; @def TONLY32 +; Version of BONLY32 that follows the code template. +; Like BONLY32 this normally goes in column 1. +%if TMPL_BITS == 32 + %macro TONLY32 1+ + %1 + %endmacro +%else + %macro TONLY32 1+ + %endmacro +%endif + +;; @def TONLY64 +; Version of BONLY64 that follows the code template. +; Like BONLY64 this normally goes in column 1. +%if TMPL_BITS == 64 + %macro TONLY64 1+ + %1 + %endmacro +%else + %macro TONLY64 1+ + %endmacro +%endif + +;; @def TNOT16 +; Version of BNOT16 that follows the code template. +; Like BNOT16 this normally goes in column 1. +%if TMPL_BITS == 16 + %macro TNOT16 1+ + %endmacro +%else + %macro TNOT16 1+ + %1 + %endmacro +%endif + +;; @def TNOT32 +; Version of BNOT32 that follows the code template. +; Like BNOT32 this normally goes in column 1. +%if TMPL_BITS == 32 + %macro TNOT32 1+ + %endmacro +%else + %macro TNOT32 1+ + %1 + %endmacro +%endif + +;; @def TNOT64 +; Version of BNOT64 that follows the code template. +; Like BNOT64 this normally goes in column 1. +%if TMPL_BITS == 64 + %macro TNOT64 1+ + %endmacro +%else + %macro TNOT64 1+ + %1 + %endmacro +%endif + + +; +; Default code segment (changes BITS too). +; +%ifdef TMPL_64BIT + %define TMPL_BEGIN_TEXT BS3_BEGIN_TEXT64 +%elifdef TMPL_32BIT + %define TMPL_BEGIN_TEXT BS3_BEGIN_TEXT32 +%elifdef TMPL_16BIT + %define TMPL_BEGIN_TEXT BS3_BEGIN_TEXT16 +%else + %error "Missing TMPL_xxBIT!" +%endif +TMPL_BEGIN_TEXT + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit.h b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit.h new file mode 100644 index 00000000..222d5728 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit.h @@ -0,0 +1,4534 @@ +/* $Id: bs3kit.h $ */ +/** @file + * BS3Kit - structures, symbols, macros and stuff. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef BS3KIT_INCLUDED_bs3kit_h +#define BS3KIT_INCLUDED_bs3kit_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifndef DOXYGEN_RUNNING +# undef IN_RING0 +# define IN_RING0 +#endif + +#define RT_NO_STRICT /* Don't drag in IPRT assertion code in inline code we may use (asm.h). */ +#include <iprt/cdefs.h> +#include <iprt/types.h> + +#ifndef DOXYGEN_RUNNING +# undef IN_RING0 +#endif + +/* + * Make asm.h and friend compatible with our 64-bit assembly config (ASM_CALL64_MSC). + */ +#if defined(__GNUC__) && ARCH_BITS == 64 +# undef DECLASM +# ifdef __cplusplus +# define DECLASM(type) extern "C" type BS3_CALL +# else +# define DECLASM(type) type BS3_CALL +# endif +#endif + + +/* + * Work around ms_abi trouble in the gcc camp (gcc bugzilla #50818). + * ASSUMES all va_lists are in functions with + */ +#if defined(__GNUC__) && ARCH_BITS == 64 +# undef va_list +# undef va_start +# undef va_end +# undef va_copy +# define va_list __builtin_ms_va_list +# define va_start(a_Va, a_Arg) __builtin_ms_va_start(a_Va, a_Arg) +# define va_end(a_Va) __builtin_ms_va_end(a_Va) +# define va_copy(a_DstVa, a_SrcVa) __builtin_ms_va_copy(a_DstVa, a_SrcVa) +#endif + + +/** @def BS3_USE_ALT_16BIT_TEXT_SEG + * @ingroup grp_bs3kit + * Combines the BS3_USE_RM_TEXT_SEG, BS3_USE_X0_TEXT_SEG, and + * BS3_USE_X1_TEXT_SEG indicators into a single one. + */ +#if defined(BS3_USE_RM_TEXT_SEG) || defined(BS3_USE_X0_TEXT_SEG) || defined(BS3_USE_X1_TEXT_SEG) || defined(DOXYGEN_RUNNING) +# define BS3_USE_ALT_16BIT_TEXT_SEG +#else +# undef BS3_USE_ALT_16BIT_TEXT_SEG +#endif + +/** @def BS3_USE_X0_TEXT_SEG + * @ingroup grp_bs3kit + * Emit 16-bit code to the BS3X0TEXT16 segment - ignored for 32-bit and 64-bit. + * + * Calling directly into the BS3X0TEXT16 segment is only possible in real-mode + * and v8086 mode. In protected mode the real far pointer have to be converted + * to a protected mode pointer that uses BS3_SEL_X0TEXT16_CS, Bs3TestDoModes and + * associates does this automatically. + */ +#ifdef DOXYGEN_RUNNING +# define BS3_USE_X0_TEXT_SEG +#endif + +/** @def BS3_USE_X1_TEXT_SEG + * @ingroup grp_bs3kit + * Emit 16-bit code to the BS3X1TEXT16 segment - ignored for 32-bit and 64-bit. + * + * Calling directly into the BS3X1TEXT16 segment is only possible in real-mode + * and v8086 mode. In protected mode the real far pointer have to be converted + * to a protected mode pointer that uses BS3_SEL_X1TEXT16_CS, Bs3TestDoModes and + * associates does this automatically. + */ +#ifdef DOXYGEN_RUNNING +# define BS3_USE_X1_TEXT_SEG +#endif + +/** @def BS3_USE_RM_TEXT_SEG + * @ingroup grp_bs3kit + * Emit 16-bit code to the BS3RMTEXT16 segment - ignored for 32-bit and 64-bit. + * + * This segment is normally used for real-mode only code, though + * BS3_SEL_RMTEXT16_CS can be used to call it from protected mode. Unlike the + * BS3X0TEXT16 and BS3X1TEXT16 segments which are empty by default, this segment + * is used by common BS3Kit code. + */ +#ifdef DOXYGEN_RUNNING +# define BS3_USE_X0_TEXT_SEG +#endif + +/** @def BS3_MODEL_FAR_CODE + * @ingroup grp_bs3kit + * Default compiler model indicates far code. + */ +#ifdef DOXYGEN_RUNNING +# define BS3_MODEL_FAR_CODE +#elif !defined(BS3_MODEL_FAR_CODE) && (defined(__LARGE__) || defined(__MEDIUM__) || defined(__HUGE__)) && ARCH_BITS == 16 +# define BS3_MODEL_FAR_CODE +#endif + + +/* + * We normally don't want the noreturn / aborts attributes as they mess up stack traces. + * + * Note! pragma aux <fnname> aborts can only be used with functions + * implemented in C and functions that does not have parameters. + */ +#define BS3_KIT_WITH_NO_RETURN +#ifndef BS3_KIT_WITH_NO_RETURN +# undef DECL_NO_RETURN +# define DECL_NO_RETURN(type) type +#endif + + +/* + * We may want to reuse some IPRT code in the common name space, so we + * redefine the RT_MANGLER to work like BS3_CMN_NM. (We cannot use + * BS3_CMN_NM yet, as we need to include IPRT headers with function + * declarations before we can define it. Thus the duplciate effort.) + */ +#if ARCH_BITS == 16 +# undef RTCALL +# if defined(BS3_USE_ALT_16BIT_TEXT_SEG) +# define RTCALL __cdecl __far +# define RT_MANGLER(a_Name) RT_CONCAT(a_Name,_f16) +# else +# define RTCALL __cdecl __near +# define RT_MANGLER(a_Name) RT_CONCAT(a_Name,_c16) +# endif +#else +# define RT_MANGLER(a_Name) RT_CONCAT3(a_Name,_c,ARCH_BITS) +#endif +#include <iprt/mangling.h> +#include <iprt/x86.h> +#include <iprt/err.h> + +/* + * Include data symbol mangling (function mangling/mapping must be done + * after the protypes). + */ +#include "bs3kit-mangling-data.h" + + + +RT_C_DECLS_BEGIN + +/** @defgroup grp_bs3kit BS3Kit - Boot Sector Kit \#3 + * + * The BS3Kit is a framework for bare metal floppy/usb image tests, + * see the @ref pg_bs3kit "doc page" for more. + * + * @{ */ + +/** @name Execution modes. + * @{ */ +#define BS3_MODE_INVALID UINT8_C(0x00) +#define BS3_MODE_RM UINT8_C(0x01) /**< real mode. */ +#define BS3_MODE_PE16 UINT8_C(0x11) /**< 16-bit protected mode kernel+tss, running 16-bit code, unpaged. */ +#define BS3_MODE_PE16_32 UINT8_C(0x12) /**< 16-bit protected mode kernel+tss, running 32-bit code, unpaged. */ +#define BS3_MODE_PE16_V86 UINT8_C(0x18) /**< 16-bit protected mode kernel+tss, running virtual 8086 mode code, unpaged. */ +#define BS3_MODE_PE32 UINT8_C(0x22) /**< 32-bit protected mode kernel+tss, running 32-bit code, unpaged. */ +#define BS3_MODE_PE32_16 UINT8_C(0x21) /**< 32-bit protected mode kernel+tss, running 16-bit code, unpaged. */ +#define BS3_MODE_PEV86 UINT8_C(0x28) /**< 32-bit protected mode kernel+tss, running virtual 8086 mode code, unpaged. */ +#define BS3_MODE_PP16 UINT8_C(0x31) /**< 16-bit protected mode kernel+tss, running 16-bit code, paged. */ +#define BS3_MODE_PP16_32 UINT8_C(0x32) /**< 16-bit protected mode kernel+tss, running 32-bit code, paged. */ +#define BS3_MODE_PP16_V86 UINT8_C(0x38) /**< 16-bit protected mode kernel+tss, running virtual 8086 mode code, paged. */ +#define BS3_MODE_PP32 UINT8_C(0x42) /**< 32-bit protected mode kernel+tss, running 32-bit code, paged. */ +#define BS3_MODE_PP32_16 UINT8_C(0x41) /**< 32-bit protected mode kernel+tss, running 16-bit code, paged. */ +#define BS3_MODE_PPV86 UINT8_C(0x48) /**< 32-bit protected mode kernel+tss, running virtual 8086 mode code, paged. */ +#define BS3_MODE_PAE16 UINT8_C(0x51) /**< 16-bit protected mode kernel+tss, running 16-bit code, PAE paging. */ +#define BS3_MODE_PAE16_32 UINT8_C(0x52) /**< 16-bit protected mode kernel+tss, running 32-bit code, PAE paging. */ +#define BS3_MODE_PAE16_V86 UINT8_C(0x58) /**< 16-bit protected mode kernel+tss, running virtual 8086 mode, PAE paging. */ +#define BS3_MODE_PAE32 UINT8_C(0x62) /**< 32-bit protected mode kernel+tss, running 32-bit code, PAE paging. */ +#define BS3_MODE_PAE32_16 UINT8_C(0x61) /**< 32-bit protected mode kernel+tss, running 16-bit code, PAE paging. */ +#define BS3_MODE_PAEV86 UINT8_C(0x68) /**< 32-bit protected mode kernel+tss, running virtual 8086 mode, PAE paging. */ +#define BS3_MODE_LM16 UINT8_C(0x71) /**< 16-bit long mode (paged), kernel+tss always 64-bit. */ +#define BS3_MODE_LM32 UINT8_C(0x72) /**< 32-bit long mode (paged), kernel+tss always 64-bit. */ +#define BS3_MODE_LM64 UINT8_C(0x74) /**< 64-bit long mode (paged), kernel+tss always 64-bit. */ + +#define BS3_MODE_CODE_MASK UINT8_C(0x0f) /**< Running code mask. */ +#define BS3_MODE_CODE_16 UINT8_C(0x01) /**< Running 16-bit code. */ +#define BS3_MODE_CODE_32 UINT8_C(0x02) /**< Running 32-bit code. */ +#define BS3_MODE_CODE_64 UINT8_C(0x04) /**< Running 64-bit code. */ +#define BS3_MODE_CODE_V86 UINT8_C(0x08) /**< Running 16-bit virtual 8086 code. */ + +#define BS3_MODE_SYS_MASK UINT8_C(0xf0) /**< kernel+tss mask. */ +#define BS3_MODE_SYS_RM UINT8_C(0x00) /**< Real mode kernel+tss. */ +#define BS3_MODE_SYS_PE16 UINT8_C(0x10) /**< 16-bit protected mode kernel+tss. */ +#define BS3_MODE_SYS_PE32 UINT8_C(0x20) /**< 32-bit protected mode kernel+tss. */ +#define BS3_MODE_SYS_PP16 UINT8_C(0x30) /**< 16-bit paged protected mode kernel+tss. */ +#define BS3_MODE_SYS_PP32 UINT8_C(0x40) /**< 32-bit paged protected mode kernel+tss. */ +#define BS3_MODE_SYS_PAE16 UINT8_C(0x50) /**< 16-bit PAE paged protected mode kernel+tss. */ +#define BS3_MODE_SYS_PAE32 UINT8_C(0x60) /**< 32-bit PAE paged protected mode kernel+tss. */ +#define BS3_MODE_SYS_LM UINT8_C(0x70) /**< 64-bit (paged) long mode protected mode kernel+tss. */ + +/** Whether the mode has paging enabled. */ +#define BS3_MODE_IS_PAGED(a_fMode) ((a_fMode) >= BS3_MODE_PP16) +/** Whether the mode has legacy paging enabled (legacy as opposed to PAE or + * long mode). */ +#define BS3_MODE_IS_LEGACY_PAGING(a_fMode) ((a_fMode) >= BS3_MODE_PP16 && (a_fMode) < BS3_MODE_PAE16) + +/** Whether the mode is running v8086 code. */ +#define BS3_MODE_IS_V86(a_fMode) (((a_fMode) & BS3_MODE_CODE_MASK) == BS3_MODE_CODE_V86) +/** Whether the we're executing in real mode or v8086 mode. */ +#define BS3_MODE_IS_RM_OR_V86(a_fMode) ((a_fMode) == BS3_MODE_RM || BS3_MODE_IS_V86(a_fMode)) +/** Whether the mode is running 16-bit code, except v8086. */ +#define BS3_MODE_IS_16BIT_CODE_NO_V86(a_fMode) (((a_fMode) & BS3_MODE_CODE_MASK) == BS3_MODE_CODE_16) +/** Whether the mode is running 16-bit code (includes v8086). */ +#define BS3_MODE_IS_16BIT_CODE(a_fMode) (BS3_MODE_IS_16BIT_CODE_NO_V86(a_fMode) || BS3_MODE_IS_V86(a_fMode)) +/** Whether the mode is running 32-bit code. */ +#define BS3_MODE_IS_32BIT_CODE(a_fMode) (((a_fMode) & BS3_MODE_CODE_MASK) == BS3_MODE_CODE_32) +/** Whether the mode is running 64-bit code. */ +#define BS3_MODE_IS_64BIT_CODE(a_fMode) (((a_fMode) & BS3_MODE_CODE_MASK) == BS3_MODE_CODE_64) + +/** Whether the system is in real mode. */ +#define BS3_MODE_IS_RM_SYS(a_fMode) (((a_fMode) & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_RM) +/** Whether the system is some 16-bit mode that isn't real mode. */ +#define BS3_MODE_IS_16BIT_SYS_NO_RM(a_fMode) ( ((a_fMode) & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PE16 \ + || ((a_fMode) & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PP16 \ + || ((a_fMode) & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PAE16) +/** Whether the system is some 16-bit mode (includes real mode). */ +#define BS3_MODE_IS_16BIT_SYS(a_fMode) (BS3_MODE_IS_16BIT_SYS_NO_RM(a_fMode) || BS3_MODE_IS_RM_SYS(a_fMode)) +/** Whether the system is some 32-bit mode. */ +#define BS3_MODE_IS_32BIT_SYS(a_fMode) ( ((a_fMode) & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PE32 \ + || ((a_fMode) & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PP32 \ + || ((a_fMode) & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PAE32) +/** Whether the system is long mode. */ +#define BS3_MODE_IS_64BIT_SYS(a_fMode) (((a_fMode) & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_LM) + +/** Whether the system is in protected mode (with or without paging). + * @note Long mode is not included. */ +#define BS3_MODE_IS_PM_SYS(a_fMode) ((a_fMode) >= BS3_MODE_SYS_PE16 && (a_fMode) < BS3_MODE_SYS_LM) + +/** @todo testcase: How would long-mode handle a 16-bit TSS loaded prior to the switch? (mainly stack switching wise) Hopefully, it will tripple fault, right? */ +/** @} */ + + +/** @name BS3_ADDR_XXX - Static Memory Allocation + * @{ */ +/** The flat load address for the code after the bootsector. */ +#define BS3_ADDR_LOAD 0x10000 +/** Where we save the boot registers during init. + * Located right before the code. */ +#define BS3_ADDR_REG_SAVE (BS3_ADDR_LOAD - sizeof(BS3REGCTX) - 8) +/** Where the stack starts (initial RSP value). + * Located 16 bytes (assumed by boot sector) before the saved registers. + * SS.BASE=0. The size is a little short of 32KB */ +#define BS3_ADDR_STACK (BS3_ADDR_REG_SAVE - 16) +/** The ring-0 stack (8KB) for ring transitions. */ +#define BS3_ADDR_STACK_R0 0x06000 +/** The ring-1 stack (8KB) for ring transitions. */ +#define BS3_ADDR_STACK_R1 0x04000 +/** The ring-2 stack (8KB) for ring transitions. */ +#define BS3_ADDR_STACK_R2 0x02000 +/** IST1 ring-0 stack for long mode (4KB), used for double faults elsewhere. */ +#define BS3_ADDR_STACK_R0_IST1 0x09000 +/** IST2 ring-0 stack for long mode (3KB), used for spare 0 stack elsewhere. */ +#define BS3_ADDR_STACK_R0_IST2 0x08000 +/** IST3 ring-0 stack for long mode (1KB). */ +#define BS3_ADDR_STACK_R0_IST3 0x07400 +/** IST4 ring-0 stack for long mode (1KB), used for spare 1 stack elsewhere. */ +#define BS3_ADDR_STACK_R0_IST4 0x07000 +/** IST5 ring-0 stack for long mode (1KB). */ +#define BS3_ADDR_STACK_R0_IST5 0x06c00 +/** IST6 ring-0 stack for long mode (1KB). */ +#define BS3_ADDR_STACK_R0_IST6 0x06800 +/** IST7 ring-0 stack for long mode (1KB). */ +#define BS3_ADDR_STACK_R0_IST7 0x06400 + +/** The base address of the BS3TEXT16 segment (same as BS3_LOAD_ADDR). + * @sa BS3_SEL_TEXT16 */ +#define BS3_ADDR_BS3TEXT16 0x10000 +/** The base address of the BS3SYSTEM16 segment. + * @sa BS3_SEL_SYSTEM16 */ +#define BS3_ADDR_BS3SYSTEM16 0x20000 +/** The base address of the BS3DATA16/BS3KIT_GRPNM_DATA16 segment. + * @sa BS3_SEL_DATA16 */ +#define BS3_ADDR_BS3DATA16 0x29000 +/** @} */ + +/** @name BS3_SEL_XXX - GDT selector assignments. + * + * The real mode segment numbers for BS16TEXT, BS16DATA and BS16SYSTEM are + * present in the GDT, this allows the 16-bit C/C++ and assembly code to + * continue using the real mode segment values in ring-0 protected mode. + * + * The three segments have fixed locations: + * | segment | flat address | real mode segment | + * | ----------- | ------------ | ----------------- | + * | BS3TEXT16 | 0x00010000 | 1000h | + * | BS3SYSTEM16 | 0x00020000 | 2000h | + * | BS3DATA16 | 0x00029000 | 2900h | + * + * This means that we've got a lot of GDT space to play around with. + * + * @{ */ +#define BS3_SEL_LDT 0x0010 /**< The LDT selector for Bs3Ldt. */ +#define BS3_SEL_TSS16 0x0020 /**< The 16-bit TSS selector. */ +#define BS3_SEL_TSS16_DF 0x0028 /**< The 16-bit TSS selector for double faults. */ +#define BS3_SEL_TSS16_SPARE0 0x0030 /**< The 16-bit TSS selector for testing. */ +#define BS3_SEL_TSS16_SPARE1 0x0038 /**< The 16-bit TSS selector for testing. */ +#define BS3_SEL_TSS32 0x0040 /**< The 32-bit TSS selector. */ +#define BS3_SEL_TSS32_DF 0x0048 /**< The 32-bit TSS selector for double faults. */ +#define BS3_SEL_TSS32_SPARE0 0x0050 /**< The 32-bit TSS selector for testing. */ +#define BS3_SEL_TSS32_SPARE1 0x0058 /**< The 32-bit TSS selector for testing. */ +#define BS3_SEL_TSS32_IOBP_IRB 0x0060 /**< The 32-bit TSS selector with I/O permission and interrupt redirection bitmaps. */ +#define BS3_SEL_TSS32_IRB 0x0068 /**< The 32-bit TSS selector with only interrupt redirection bitmap (IOPB stripped by limit). */ +#define BS3_SEL_TSS64 0x0070 /**< The 64-bit TSS selector. */ +#define BS3_SEL_TSS64_SPARE0 0x0080 /**< The 64-bit TSS selector. */ +#define BS3_SEL_TSS64_SPARE1 0x0090 /**< The 64-bit TSS selector. */ +#define BS3_SEL_TSS64_IOBP 0x00a0 /**< The 64-bit TSS selector. */ + +#define BS3_SEL_RMTEXT16_CS 0x00e0 /**< Conforming code selector for accessing the BS3RMTEXT16 segment. Runtime config. */ +#define BS3_SEL_X0TEXT16_CS 0x00e8 /**< Conforming code selector for accessing the BS3X0TEXT16 segment. Runtime config. */ +#define BS3_SEL_X1TEXT16_CS 0x00f0 /**< Conforming code selector for accessing the BS3X1TEXT16 segment. Runtime config. */ +#define BS3_SEL_VMMDEV_MMIO16 0x00f8 /**< Selector for accessing the VMMDev MMIO segment at 00df000h from 16-bit code. */ + +/** Checks if @a uSel is in the BS3_SEL_RX_XXX range. */ +#define BS3_SEL_IS_IN_RING_RANGE(uSel) ( (unsigned)(uSel - BS3_SEL_R0_FIRST) < (unsigned)(4 << BS3_SEL_RING_SHIFT) ) +#define BS3_SEL_RING_SHIFT 8 /**< For the formula: BS3_SEL_R0_XXX + ((cs & 3) << BS3_SEL_RING_SHIFT) */ +#define BS3_SEL_RING_SUB_MASK 0x00f8 /**< Mask for getting the sub-selector. For use with BS3_SEL_R*_FIRST. */ + +/** Checks if @a uSel is in the BS3_SEL_R0_XXX range. */ +#define BS3_SEL_IS_IN_R0_RANGE(uSel) ( (unsigned)(uSel - BS3_SEL_R0_FIRST) < (unsigned)(1 << BS3_SEL_RING_SHIFT) ) +#define BS3_SEL_R0_FIRST 0x0100 /**< The first selector in the ring-0 block. */ +#define BS3_SEL_R0_CS16 0x0100 /**< ring-0: 16-bit code selector, base 0x10000. */ +#define BS3_SEL_R0_DS16 0x0108 /**< ring-0: 16-bit data selector, base 0x23000. */ +#define BS3_SEL_R0_SS16 0x0110 /**< ring-0: 16-bit stack selector, base 0x00000. */ +#define BS3_SEL_R0_CS32 0x0118 /**< ring-0: 32-bit flat code selector. */ +#define BS3_SEL_R0_DS32 0x0120 /**< ring-0: 32-bit flat data selector. */ +#define BS3_SEL_R0_SS32 0x0128 /**< ring-0: 32-bit flat stack selector. */ +#define BS3_SEL_R0_CS64 0x0130 /**< ring-0: 64-bit flat code selector. */ +#define BS3_SEL_R0_DS64 0x0138 /**< ring-0: 64-bit flat data & stack selector. */ +#define BS3_SEL_R0_CS16_EO 0x0140 /**< ring-0: 16-bit execute-only code selector, not accessed, 0xfffe limit, CS16 base. */ +#define BS3_SEL_R0_CS16_CNF 0x0148 /**< ring-0: 16-bit conforming code selector, not accessed, 0xfffe limit, CS16 base. */ +#define BS3_SEL_R0_CS16_CNF_EO 0x0150 /**< ring-0: 16-bit execute-only conforming code selector, not accessed, 0xfffe limit, CS16 base. */ +#define BS3_SEL_R0_CS32_EO 0x0158 /**< ring-0: 32-bit execute-only code selector, not accessed, flat. */ +#define BS3_SEL_R0_CS32_CNF 0x0160 /**< ring-0: 32-bit conforming code selector, not accessed, flat. */ +#define BS3_SEL_R0_CS32_CNF_EO 0x0168 /**< ring-0: 32-bit execute-only conforming code selector, not accessed, flat. */ +#define BS3_SEL_R0_CS64_EO 0x0170 /**< ring-0: 64-bit execute-only code selector, not accessed, flat. */ +#define BS3_SEL_R0_CS64_CNF 0x0178 /**< ring-0: 64-bit conforming code selector, not accessed, flat. */ +#define BS3_SEL_R0_CS64_CNF_EO 0x0180 /**< ring-0: 64-bit execute-only conforming code selector, not accessed, flat. */ + +#define BS3_SEL_R1_FIRST 0x0200 /**< The first selector in the ring-1 block. */ +#define BS3_SEL_R1_CS16 0x0200 /**< ring-1: 16-bit code selector, base 0x10000. */ +#define BS3_SEL_R1_DS16 0x0208 /**< ring-1: 16-bit data selector, base 0x23000. */ +#define BS3_SEL_R1_SS16 0x0210 /**< ring-1: 16-bit stack selector, base 0x00000. */ +#define BS3_SEL_R1_CS32 0x0218 /**< ring-1: 32-bit flat code selector. */ +#define BS3_SEL_R1_DS32 0x0220 /**< ring-1: 32-bit flat data selector. */ +#define BS3_SEL_R1_SS32 0x0228 /**< ring-1: 32-bit flat stack selector. */ +#define BS3_SEL_R1_CS64 0x0230 /**< ring-1: 64-bit flat code selector. */ +#define BS3_SEL_R1_DS64 0x0238 /**< ring-1: 64-bit flat data & stack selector. */ +#define BS3_SEL_R1_CS16_EO 0x0240 /**< ring-1: 16-bit execute-only code selector, not accessed, 0xfffe limit, CS16 base. */ +#define BS3_SEL_R1_CS16_CNF 0x0248 /**< ring-1: 16-bit conforming code selector, not accessed, 0xfffe limit, CS16 base. */ +#define BS3_SEL_R1_CS16_CNF_EO 0x0250 /**< ring-1: 16-bit execute-only conforming code selector, not accessed, 0xfffe limit, CS16 base. */ +#define BS3_SEL_R1_CS32_EO 0x0258 /**< ring-1: 32-bit execute-only code selector, not accessed, flat. */ +#define BS3_SEL_R1_CS32_CNF 0x0260 /**< ring-1: 32-bit conforming code selector, not accessed, flat. */ +#define BS3_SEL_R1_CS32_CNF_EO 0x0268 /**< ring-1: 32-bit execute-only conforming code selector, not accessed, flat. */ +#define BS3_SEL_R1_CS64_EO 0x0270 /**< ring-1: 64-bit execute-only code selector, not accessed, flat. */ +#define BS3_SEL_R1_CS64_CNF 0x0278 /**< ring-1: 64-bit conforming code selector, not accessed, flat. */ +#define BS3_SEL_R1_CS64_CNF_EO 0x0280 /**< ring-1: 64-bit execute-only conforming code selector, not accessed, flat. */ + +#define BS3_SEL_R2_FIRST 0x0300 /**< The first selector in the ring-2 block. */ +#define BS3_SEL_R2_CS16 0x0300 /**< ring-2: 16-bit code selector, base 0x10000. */ +#define BS3_SEL_R2_DS16 0x0308 /**< ring-2: 16-bit data selector, base 0x23000. */ +#define BS3_SEL_R2_SS16 0x0310 /**< ring-2: 16-bit stack selector, base 0x00000. */ +#define BS3_SEL_R2_CS32 0x0318 /**< ring-2: 32-bit flat code selector. */ +#define BS3_SEL_R2_DS32 0x0320 /**< ring-2: 32-bit flat data selector. */ +#define BS3_SEL_R2_SS32 0x0328 /**< ring-2: 32-bit flat stack selector. */ +#define BS3_SEL_R2_CS64 0x0330 /**< ring-2: 64-bit flat code selector. */ +#define BS3_SEL_R2_DS64 0x0338 /**< ring-2: 64-bit flat data & stack selector. */ +#define BS3_SEL_R2_CS16_EO 0x0340 /**< ring-2: 16-bit execute-only code selector, not accessed, 0xfffe limit, CS16 base. */ +#define BS3_SEL_R2_CS16_CNF 0x0348 /**< ring-2: 16-bit conforming code selector, not accessed, 0xfffe limit, CS16 base. */ +#define BS3_SEL_R2_CS16_CNF_EO 0x0350 /**< ring-2: 16-bit execute-only conforming code selector, not accessed, 0xfffe limit, CS16 base. */ +#define BS3_SEL_R2_CS32_EO 0x0358 /**< ring-2: 32-bit execute-only code selector, not accessed, flat. */ +#define BS3_SEL_R2_CS32_CNF 0x0360 /**< ring-2: 32-bit conforming code selector, not accessed, flat. */ +#define BS3_SEL_R2_CS32_CNF_EO 0x0368 /**< ring-2: 32-bit execute-only conforming code selector, not accessed, flat. */ +#define BS3_SEL_R2_CS64_EO 0x0370 /**< ring-2: 64-bit execute-only code selector, not accessed, flat. */ +#define BS3_SEL_R2_CS64_CNF 0x0378 /**< ring-2: 64-bit conforming code selector, not accessed, flat. */ +#define BS3_SEL_R2_CS64_CNF_EO 0x0380 /**< ring-2: 64-bit execute-only conforming code selector, not accessed, flat. */ + +#define BS3_SEL_R3_FIRST 0x0400 /**< The first selector in the ring-3 block. */ +#define BS3_SEL_R3_CS16 0x0400 /**< ring-3: 16-bit code selector, base 0x10000. */ +#define BS3_SEL_R3_DS16 0x0408 /**< ring-3: 16-bit data selector, base 0x23000. */ +#define BS3_SEL_R3_SS16 0x0410 /**< ring-3: 16-bit stack selector, base 0x00000. */ +#define BS3_SEL_R3_CS32 0x0418 /**< ring-3: 32-bit flat code selector. */ +#define BS3_SEL_R3_DS32 0x0420 /**< ring-3: 32-bit flat data selector. */ +#define BS3_SEL_R3_SS32 0x0428 /**< ring-3: 32-bit flat stack selector. */ +#define BS3_SEL_R3_CS64 0x0430 /**< ring-3: 64-bit flat code selector. */ +#define BS3_SEL_R3_DS64 0x0438 /**< ring-3: 64-bit flat data & stack selector. */ +#define BS3_SEL_R3_CS16_EO 0x0440 /**< ring-3: 16-bit execute-only code selector, not accessed, 0xfffe limit, CS16 base. */ +#define BS3_SEL_R3_CS16_CNF 0x0448 /**< ring-3: 16-bit conforming code selector, not accessed, 0xfffe limit, CS16 base. */ +#define BS3_SEL_R3_CS16_CNF_EO 0x0450 /**< ring-3: 16-bit execute-only conforming code selector, not accessed, 0xfffe limit, CS16 base. */ +#define BS3_SEL_R3_CS32_EO 0x0458 /**< ring-3: 32-bit execute-only code selector, not accessed, flat. */ +#define BS3_SEL_R3_CS32_CNF 0x0460 /**< ring-3: 32-bit conforming code selector, not accessed, flat. */ +#define BS3_SEL_R3_CS32_CNF_EO 0x0468 /**< ring-3: 32-bit execute-only conforming code selector, not accessed, flat. */ +#define BS3_SEL_R3_CS64_EO 0x0470 /**< ring-3: 64-bit execute-only code selector, not accessed, flat. */ +#define BS3_SEL_R3_CS64_CNF 0x0478 /**< ring-3: 64-bit conforming code selector, not accessed, flat. */ +#define BS3_SEL_R3_CS64_CNF_EO 0x0480 /**< ring-3: 64-bit execute-only conforming code selector, not accessed, flat. */ + +#define BS3_SEL_R3_LAST 0x04f8 /**< ring-3: Last of the BS3_SEL_RX_XXX range. */ + +#define BS3_SEL_SPARE_FIRST 0x0500 /**< The first selector in the spare block */ +#define BS3_SEL_SPARE_00 0x0500 /**< Spare selector number 00h. */ +#define BS3_SEL_SPARE_01 0x0508 /**< Spare selector number 01h. */ +#define BS3_SEL_SPARE_02 0x0510 /**< Spare selector number 02h. */ +#define BS3_SEL_SPARE_03 0x0518 /**< Spare selector number 03h. */ +#define BS3_SEL_SPARE_04 0x0520 /**< Spare selector number 04h. */ +#define BS3_SEL_SPARE_05 0x0528 /**< Spare selector number 05h. */ +#define BS3_SEL_SPARE_06 0x0530 /**< Spare selector number 06h. */ +#define BS3_SEL_SPARE_07 0x0538 /**< Spare selector number 07h. */ +#define BS3_SEL_SPARE_08 0x0540 /**< Spare selector number 08h. */ +#define BS3_SEL_SPARE_09 0x0548 /**< Spare selector number 09h. */ +#define BS3_SEL_SPARE_0a 0x0550 /**< Spare selector number 0ah. */ +#define BS3_SEL_SPARE_0b 0x0558 /**< Spare selector number 0bh. */ +#define BS3_SEL_SPARE_0c 0x0560 /**< Spare selector number 0ch. */ +#define BS3_SEL_SPARE_0d 0x0568 /**< Spare selector number 0dh. */ +#define BS3_SEL_SPARE_0e 0x0570 /**< Spare selector number 0eh. */ +#define BS3_SEL_SPARE_0f 0x0578 /**< Spare selector number 0fh. */ +#define BS3_SEL_SPARE_10 0x0580 /**< Spare selector number 10h. */ +#define BS3_SEL_SPARE_11 0x0588 /**< Spare selector number 11h. */ +#define BS3_SEL_SPARE_12 0x0590 /**< Spare selector number 12h. */ +#define BS3_SEL_SPARE_13 0x0598 /**< Spare selector number 13h. */ +#define BS3_SEL_SPARE_14 0x05a0 /**< Spare selector number 14h. */ +#define BS3_SEL_SPARE_15 0x05a8 /**< Spare selector number 15h. */ +#define BS3_SEL_SPARE_16 0x05b0 /**< Spare selector number 16h. */ +#define BS3_SEL_SPARE_17 0x05b8 /**< Spare selector number 17h. */ +#define BS3_SEL_SPARE_18 0x05c0 /**< Spare selector number 18h. */ +#define BS3_SEL_SPARE_19 0x05c8 /**< Spare selector number 19h. */ +#define BS3_SEL_SPARE_1a 0x05d0 /**< Spare selector number 1ah. */ +#define BS3_SEL_SPARE_1b 0x05d8 /**< Spare selector number 1bh. */ +#define BS3_SEL_SPARE_1c 0x05e0 /**< Spare selector number 1ch. */ +#define BS3_SEL_SPARE_1d 0x05e8 /**< Spare selector number 1dh. */ +#define BS3_SEL_SPARE_1e 0x05f0 /**< Spare selector number 1eh. */ +#define BS3_SEL_SPARE_1f 0x05f8 /**< Spare selector number 1fh. */ + +#define BS3_SEL_TILED 0x0600 /**< 16-bit data tiling: First - base=0x00000000, limit=64KB, DPL=3. */ +#define BS3_SEL_TILED_LAST 0x0df8 /**< 16-bit data tiling: Last - base=0x00ff0000, limit=64KB, DPL=3. */ +#define BS3_SEL_TILED_AREA_SIZE 0x001000000 /**< 16-bit data tiling: Size of addressable area, in bytes. (16 MB) */ + +#define BS3_SEL_FREE_PART1 0x0e00 /**< Free selector space - part \#1. */ +#define BS3_SEL_FREE_PART1_LAST 0x0ff8 /**< Free selector space - part \#1, last entry. */ + +#define BS3_SEL_TEXT16 0x1000 /**< The BS3TEXT16 selector. */ + +#define BS3_SEL_FREE_PART2 0x1008 /**< Free selector space - part \#2. */ +#define BS3_SEL_FREE_PART2_LAST 0x17f8 /**< Free selector space - part \#2, last entry. */ + +#define BS3_SEL_TILED_R0 0x1800 /**< 16-bit data/stack tiling: First - base=0x00000000, limit=64KB, DPL=0. */ +#define BS3_SEL_TILED_R0_LAST 0x1ff8 /**< 16-bit data/stack tiling: Last - base=0x00ff0000, limit=64KB, DPL=0. */ + +#define BS3_SEL_SYSTEM16 0x2000 /**< The BS3SYSTEM16 selector. */ + +#define BS3_SEL_FREE_PART3 0x2008 /**< Free selector space - part \#3. */ +#define BS3_SEL_FREE_PART3_LAST 0x28f8 /**< Free selector space - part \#3, last entry. */ + +#define BS3_SEL_DATA16 0x2900 /**< The BS3DATA16/BS3KIT_GRPNM_DATA16 selector. */ + +#define BS3_SEL_FREE_PART4 0x2908 /**< Free selector space - part \#4. */ +#define BS3_SEL_FREE_PART4_LAST 0x2f98 /**< Free selector space - part \#4, last entry. */ + +#define BS3_SEL_PRE_TEST_PAGE_08 0x2fa0 /**< Selector located 8 selectors before the test page. */ +#define BS3_SEL_PRE_TEST_PAGE_07 0x2fa8 /**< Selector located 7 selectors before the test page. */ +#define BS3_SEL_PRE_TEST_PAGE_06 0x2fb0 /**< Selector located 6 selectors before the test page. */ +#define BS3_SEL_PRE_TEST_PAGE_05 0x2fb8 /**< Selector located 5 selectors before the test page. */ +#define BS3_SEL_PRE_TEST_PAGE_04 0x2fc0 /**< Selector located 4 selectors before the test page. */ +#define BS3_SEL_PRE_TEST_PAGE_03 0x2fc8 /**< Selector located 3 selectors before the test page. */ +#define BS3_SEL_PRE_TEST_PAGE_02 0x2fd0 /**< Selector located 2 selectors before the test page. */ +#define BS3_SEL_PRE_TEST_PAGE_01 0x2fd8 /**< Selector located 1 selector before the test page. */ +#define BS3_SEL_TEST_PAGE 0x2fe0 /**< Start of the test page intended for playing around with paging and GDT. */ +#define BS3_SEL_TEST_PAGE_00 0x2fe0 /**< Test page selector number 00h (convenience). */ +#define BS3_SEL_TEST_PAGE_01 0x2fe8 /**< Test page selector number 01h (convenience). */ +#define BS3_SEL_TEST_PAGE_02 0x2ff0 /**< Test page selector number 02h (convenience). */ +#define BS3_SEL_TEST_PAGE_03 0x2ff8 /**< Test page selector number 03h (convenience). */ +#define BS3_SEL_TEST_PAGE_04 0x3000 /**< Test page selector number 04h (convenience). */ +#define BS3_SEL_TEST_PAGE_05 0x3008 /**< Test page selector number 05h (convenience). */ +#define BS3_SEL_TEST_PAGE_06 0x3010 /**< Test page selector number 06h (convenience). */ +#define BS3_SEL_TEST_PAGE_07 0x3018 /**< Test page selector number 07h (convenience). */ +#define BS3_SEL_TEST_PAGE_LAST 0x3fd0 /**< The last selector in the spare page. */ + +#define BS3_SEL_GDT_LIMIT 0x3fd8 /**< The GDT limit. */ +/** @} */ + +/** @name BS3_SEL_IS_XXX - Predicates for standard selectors. + * + * Standard selectors are in the range BS3_SEL_R0_FIRST thru BS3_SEL_R3_LAST. + * + * @{ */ +#define BS3_SEL_IS_CS16(a_uSel) (((a_uSel) & 0xf8) == 0x00) +#define BS3_SEL_IS_CS32(a_uSel) (((a_uSel) & 0xf8) == 0x18) +#define BS3_SEL_IS_CS64(a_uSel) (((a_uSel) & 0xf8) == 0x30) + +#define BS3_SEL_IS_ANY_CS16(a_uSel) ( ((a_uSel) & 0xf8) == 0x00 \ + || ((a_uSel) & 0xf8) == 0x40 \ + || ((a_uSel) & 0xf8) == 0x48 \ + || ((a_uSel) & 0xf8) == 0x50 ) +#define BS3_SEL_IS_ANY_CS32(a_uSel) ( ((a_uSel) & 0xf8) == 0x18 \ + || ((a_uSel) & 0xf8) == 0x58 \ + || ((a_uSel) & 0xf8) == 0x60 \ + || ((a_uSel) & 0xf8) == 0x68 ) +#define BS3_SEL_IS_ANY_CS64(a_uSel) ( ((a_uSel) & 0xf8) == 0x18 \ + || ((a_uSel) & 0xf8) == 0x58 \ + || ((a_uSel) & 0xf8) == 0x60 \ + || ((a_uSel) & 0xf8) == 0x68 ) + +#define BS3_SEL_IS_DS16(a_uSel) (((a_uSel) & 0xf8) == 0x08) +#define BS3_SEL_IS_DS32(a_uSel) (((a_uSel) & 0xf8) == 0x20) +#define BS3_SEL_IS_DS64(a_uSel) (((a_uSel) & 0xf8) == 0x38) + +#define BS3_SEL_IS_SS16(a_uSel) (((a_uSel) & 0xf8) == 0x10) +#define BS3_SEL_IS_SS32(a_uSel) (((a_uSel) & 0xf8) == 0x28) +/** @} */ + + +/** @def BS3_FAR + * For indicating far pointers in 16-bit code. + * Does nothing in 32-bit and 64-bit code. */ +/** @def BS3_NEAR + * For indicating near pointers in 16-bit code. + * Does nothing in 32-bit and 64-bit code. */ +/** @def BS3_FAR_CODE + * For indicating far 16-bit functions. + * Does nothing in 32-bit and 64-bit code. */ +/** @def BS3_NEAR_CODE + * For indicating near 16-bit functions. + * Does nothing in 32-bit and 64-bit code. */ +/** @def BS3_FAR_DATA + * For indicating far 16-bit external data, i.e. in a segment other than DATA16. + * Does nothing in 32-bit and 64-bit code. */ +#ifdef M_I86 +# define BS3_FAR __far +# define BS3_NEAR __near +# define BS3_FAR_CODE __far +# define BS3_NEAR_CODE __near +# define BS3_FAR_DATA __far +#else +# define BS3_FAR +# define BS3_NEAR +# define BS3_FAR_CODE +# define BS3_NEAR_CODE +# define BS3_FAR_DATA +#endif + +#if ARCH_BITS == 16 || defined(DOXYGEN_RUNNING) +/** @def BS3_FP_SEG + * Get the selector (segment) part of a far pointer. + * + * @returns selector. + * @param a_pv Far pointer. + */ +# define BS3_FP_SEG(a_pv) ((uint16_t)(__segment)(void BS3_FAR *)(a_pv)) +/** @def BS3_FP_OFF + * Get the segment offset part of a far pointer. + * + * For sake of convenience, this works like a uintptr_t cast in 32-bit and + * 64-bit code. + * + * @returns offset. + * @param a_pv Far pointer. + */ +# define BS3_FP_OFF(a_pv) ((uint16_t)(void __near *)(a_pv)) +/** @def BS3_FP_MAKE + * Create a far pointer. + * + * @returns Far pointer. + * @param a_uSeg The selector/segment. + * @param a_off The offset into the segment. + */ +# define BS3_FP_MAKE(a_uSeg, a_off) (((__segment)(a_uSeg)) :> ((void __near *)(a_off))) +#else +# define BS3_FP_OFF(a_pv) ((uintptr_t)(a_pv)) +#endif + +/** @def BS3_MAKE_PROT_R0PTR_FROM_FLAT + * Creates a protected mode pointer from a flat address. + * + * For sake of convenience, this macro also works in 32-bit and 64-bit mode, + * only there it doesn't return a far pointer but a flat point. + * + * @returns far void pointer if 16-bit code, near/flat void pointer in 32-bit + * and 64-bit. + * @param a_uFlat Flat address in the first 16MB. */ +#if ARCH_BITS == 16 +# define BS3_MAKE_PROT_R0PTR_FROM_FLAT(a_uFlat) \ + BS3_FP_MAKE(((uint16_t)(a_uFlat >> 16) << 3) + BS3_SEL_TILED, (uint16_t)(a_uFlat)) +#else +# define BS3_MAKE_PROT_R0PTR_FROM_FLAT(a_uFlat) ((void *)(uintptr_t)(a_uFlat)) +#endif + +/** @def BS3_MAKE_PROT_R0PTR_FROM_REAL + * Creates a protected mode pointer from a far real mode address. + * + * For sake of convenience, this macro also works in 32-bit and 64-bit mode, + * only there it doesn't return a far pointer but a flat point. + * + * @returns far void pointer if 16-bit code, near/flat void pointer in 32-bit + * and 64-bit. + * @param a_uSeg The selector/segment. + * @param a_off The offset into the segment. + */ +#if ARCH_BITS == 16 +# define BS3_MAKE_PROT_R0PTR_FROM_REAL(a_uSeg, a_off) BS3_MAKE_PROT_R0PTR_FROM_FLAT(((uint32_t)(a_uSeg) << 4) + (uint16_t)(a_off)) +#else +# define BS3_MAKE_PROT_R0PTR_FROM_REAL(a_uSeg, a_off) ( (void *)(uintptr_t)(((uint32_t)(a_uSeg) << 4) + (uint16_t)(a_off)) ) +#endif + + +/** @def BS3_CALL + * The calling convension used by BS3 functions. */ +#if ARCH_BITS != 64 +# define BS3_CALL __cdecl +#elif !defined(_MSC_VER) +# define BS3_CALL __attribute__((__ms_abi__)) +#else +# define BS3_CALL +#endif + +/** @def IN_BS3KIT + * Indicates that we're in the same link job as the BS3Kit code. */ +#ifdef DOXYGEN_RUNNING +# define IN_BS3KIT +#endif + +/** @def BS3_DECL + * Declares a BS3Kit function with default far/near. + * + * Until we outgrow BS3TEXT16, we use all near functions in 16-bit. + * + * @param a_Type The return type. */ +#if ARCH_BITS != 16 || !defined(BS3_USE_ALT_16BIT_TEXT_SEG) +# define BS3_DECL(a_Type) BS3_DECL_NEAR(a_Type) +#else +# define BS3_DECL(a_Type) BS3_DECL_FAR(a_Type) +#endif + +/** @def BS3_DECL_NEAR + * Declares a BS3Kit function, always near everywhere. + * + * Until we outgrow BS3TEXT16, we use all near functions in 16-bit. + * + * @param a_Type The return type. */ +#ifdef IN_BS3KIT +# define BS3_DECL_NEAR(a_Type) DECLEXPORT(a_Type) BS3_NEAR_CODE BS3_CALL +#else +# define BS3_DECL_NEAR(a_Type) DECLIMPORT(a_Type) BS3_NEAR_CODE BS3_CALL +#endif + +/** @def BS3_DECL_FAR + * Declares a BS3Kit function, far 16-bit, otherwise near. + * + * Until we outgrow BS3TEXT16, we use all near functions in 16-bit. + * + * @param a_Type The return type. */ +#ifdef IN_BS3KIT +# define BS3_DECL_FAR(a_Type) DECLEXPORT(a_Type) BS3_FAR_CODE BS3_CALL +#else +# define BS3_DECL_FAR(a_Type) DECLIMPORT(a_Type) BS3_FAR_CODE BS3_CALL +#endif + +/** @def BS3_DECL_CALLBACK + * Declares a BS3Kit callback function (typically static). + * + * @param a_Type The return type. */ +#ifdef IN_BS3KIT +# define BS3_DECL_CALLBACK(a_Type) a_Type BS3_FAR_CODE BS3_CALL +#else +# define BS3_DECL_CALLBACK(a_Type) a_Type BS3_FAR_CODE BS3_CALL +#endif + +/** @def BS3_DECL_NEAR_CALLBACK + * Declares a near BS3Kit callback function (typically static). + * + * 16-bit users must be in CGROUP16! + * + * @param a_Type The return type. */ +#ifdef IN_BS3KIT +# define BS3_DECL_NEAR_CALLBACK(a_Type) a_Type BS3_NEAR_CODE BS3_CALL +#else +# define BS3_DECL_NEAR_CALLBACK(a_Type) a_Type BS3_NEAR_CODE BS3_CALL +#endif + +/** + * Constructs a common name. + * + * Example: BS3_CMN_NM(Bs3Shutdown) + * + * @param a_Name The name of the function or global variable. + */ +#define BS3_CMN_NM(a_Name) RT_CONCAT3(a_Name,_c,ARCH_BITS) + +/** + * Constructs a common function name, far in 16-bit code. + * + * Example: BS3_CMN_FAR_NM(Bs3Shutdown) + * + * @param a_Name The name of the function. + */ +#if ARCH_BITS == 16 +# define BS3_CMN_FAR_NM(a_Name) RT_CONCAT(a_Name,_f16) +#else +# define BS3_CMN_FAR_NM(a_Name) RT_CONCAT3(a_Name,_c,ARCH_BITS) +#endif + +/** + * Constructs a common function name, far or near as defined by the source. + * + * Which to use in 16-bit mode is defined by BS3_USE_ALT_16BIT_TEXT_SEG. In + * 32-bit and 64-bit mode there are no far symbols, only near ones. + * + * Example: BS3_CMN_FN_NM(Bs3Shutdown) + * + * @param a_Name The name of the function. + */ +#if ARCH_BITS != 16 || !defined(BS3_USE_ALT_16BIT_TEXT_SEG) +# define BS3_CMN_FN_NM(a_Name) BS3_CMN_NM(a_Name) +#else +# define BS3_CMN_FN_NM(a_Name) BS3_CMN_FAR_NM(a_Name) +#endif + + +/** + * Constructs a data name. + * + * This glosses over the underscore prefix usage of our 16-bit, 32-bit and + * 64-bit compilers. + * + * Example: @code{.c} + * \#define Bs3Gdt BS3_DATA_NM(Bs3Gdt) + * extern X86DESC BS3_FAR_DATA Bs3Gdt + * @endcode + * + * @param a_Name The name of the global variable. + * @remarks Mainly used in bs3kit-mangling.h, internal headers and templates. + */ +//converter does this now//#if ARCH_BITS == 64 +//converter does this now//# define BS3_DATA_NM(a_Name) RT_CONCAT(_,a_Name) +//converter does this now//#else +# define BS3_DATA_NM(a_Name) a_Name +//converter does this now//#endif + +/** + * Template for creating a pointer union type. + * @param a_BaseName The base type name. + * @param a_Modifiers The type modifier. + */ +#define BS3_PTR_UNION_TEMPLATE(a_BaseName, a_Modifiers) \ + typedef union a_BaseName \ + { \ + /** Pointer into the void. */ \ + a_Modifiers void BS3_FAR *pv; \ + /** As a signed integer. */ \ + intptr_t i; \ + /** As an unsigned integer. */ \ + uintptr_t u; \ + /** Pointer to char value. */ \ + a_Modifiers char BS3_FAR *pch; \ + /** Pointer to char value. */ \ + a_Modifiers unsigned char BS3_FAR *puch; \ + /** Pointer to a int value. */ \ + a_Modifiers int BS3_FAR *pi; \ + /** Pointer to a unsigned int value. */ \ + a_Modifiers unsigned int BS3_FAR *pu; \ + /** Pointer to a long value. */ \ + a_Modifiers long BS3_FAR *pl; \ + /** Pointer to a long value. */ \ + a_Modifiers unsigned long BS3_FAR *pul; \ + /** Pointer to a memory size value. */ \ + a_Modifiers size_t BS3_FAR *pcb; \ + /** Pointer to a byte value. */ \ + a_Modifiers uint8_t BS3_FAR *pb; \ + /** Pointer to a 8-bit unsigned value. */ \ + a_Modifiers uint8_t BS3_FAR *pu8; \ + /** Pointer to a 16-bit unsigned value. */ \ + a_Modifiers uint16_t BS3_FAR *pu16; \ + /** Pointer to a 32-bit unsigned value. */ \ + a_Modifiers uint32_t BS3_FAR *pu32; \ + /** Pointer to a 64-bit unsigned value. */ \ + a_Modifiers uint64_t BS3_FAR *pu64; \ + /** Pointer to a UTF-16 character. */ \ + a_Modifiers RTUTF16 BS3_FAR *pwc; \ + /** Pointer to a UUID character. */ \ + a_Modifiers RTUUID BS3_FAR *pUuid; \ + } a_BaseName; \ + /** Pointer to a pointer union. */ \ + typedef a_BaseName *RT_CONCAT(P,a_BaseName) +BS3_PTR_UNION_TEMPLATE(BS3PTRUNION, RT_NOTHING); +BS3_PTR_UNION_TEMPLATE(BS3CPTRUNION, const); +BS3_PTR_UNION_TEMPLATE(BS3VPTRUNION, volatile); +BS3_PTR_UNION_TEMPLATE(BS3CVPTRUNION, const volatile); + +/** Generic far function type. */ +typedef BS3_DECL_FAR(void) FNBS3FAR(void); +/** Generic far function pointer type. */ +typedef FNBS3FAR *FPFNBS3FAR; + +/** Generic near function type. */ +typedef BS3_DECL_NEAR(void) FNBS3NEAR(void); +/** Generic near function pointer type. */ +typedef FNBS3NEAR *PFNBS3NEAR; + +/** Generic far 16:16 function pointer type for address conversion functions. */ +#if ARCH_BITS == 16 +typedef FPFNBS3FAR PFNBS3FARADDRCONV; +#else +typedef uint32_t PFNBS3FARADDRCONV; +#endif + +/** The system call vector. */ +#define BS3_TRAP_SYSCALL UINT8_C(0x20) + +/** @name System call numbers (ax). + * Paramenters are generally passed in registers specific to each system call, + * however cx:xSI is used for passing a pointer parameter. + * @{ */ +/** Print char (cl). */ +#define BS3_SYSCALL_PRINT_CHR UINT16_C(0x0001) +/** Print string (pointer in cx:xSI, length in dx). */ +#define BS3_SYSCALL_PRINT_STR UINT16_C(0x0002) +/** Switch to ring-0. */ +#define BS3_SYSCALL_TO_RING0 UINT16_C(0x0003) +/** Switch to ring-1. */ +#define BS3_SYSCALL_TO_RING1 UINT16_C(0x0004) +/** Switch to ring-2. */ +#define BS3_SYSCALL_TO_RING2 UINT16_C(0x0005) +/** Switch to ring-3. */ +#define BS3_SYSCALL_TO_RING3 UINT16_C(0x0006) +/** Restore context (pointer in cx:xSI, flags in dx). */ +#define BS3_SYSCALL_RESTORE_CTX UINT16_C(0x0007) +/** Set DRx register (value in ESI, register number in dl). */ +#define BS3_SYSCALL_SET_DRX UINT16_C(0x0008) +/** Get DRx register (register number in dl, value returned in ax:dx). */ +#define BS3_SYSCALL_GET_DRX UINT16_C(0x0009) +/** Set CRx register (value in ESI, register number in dl). */ +#define BS3_SYSCALL_SET_CRX UINT16_C(0x000a) +/** Get CRx register (register number in dl, value returned in ax:dx). */ +#define BS3_SYSCALL_GET_CRX UINT16_C(0x000b) +/** Set the task register (value in ESI). */ +#define BS3_SYSCALL_SET_TR UINT16_C(0x000c) +/** Get the task register (value returned in ax). */ +#define BS3_SYSCALL_GET_TR UINT16_C(0x000d) +/** Set the LDT register (value in ESI). */ +#define BS3_SYSCALL_SET_LDTR UINT16_C(0x000e) +/** Get the LDT register (value returned in ax). */ +#define BS3_SYSCALL_GET_LDTR UINT16_C(0x000f) +/** Set XCR0 register (value in edx:esi). */ +#define BS3_SYSCALL_SET_XCR0 UINT16_C(0x0010) +/** Get XCR0 register (value returned in edx:eax). */ +#define BS3_SYSCALL_GET_XCR0 UINT16_C(0x0011) +/** The last system call value. */ +#define BS3_SYSCALL_LAST BS3_SYSCALL_GET_XCR0 +/** @} */ + + + +/** @defgroup grp_bs3kit_system System Structures + * @{ */ +/** The GDT, indexed by BS3_SEL_XXX shifted by 3. */ +extern X86DESC BS3_FAR_DATA Bs3Gdt[(BS3_SEL_GDT_LIMIT + 1) / 8]; + +extern X86DESC64 BS3_FAR_DATA Bs3Gdt_Ldt; /**< @see BS3_SEL_LDT */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_Tss16; /**< @see BS3_SEL_TSS16 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_Tss16DoubleFault; /**< @see BS3_SEL_TSS16_DF */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_Tss16Spare0; /**< @see BS3_SEL_TSS16_SPARE0 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_Tss16Spare1; /**< @see BS3_SEL_TSS16_SPARE1 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_Tss32; /**< @see BS3_SEL_TSS32 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_Tss32DoubleFault; /**< @see BS3_SEL_TSS32_DF */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_Tss32Spare0; /**< @see BS3_SEL_TSS32_SPARE0 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_Tss32Spare1; /**< @see BS3_SEL_TSS32_SPARE1 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_Tss32IobpIntRedirBm; /**< @see BS3_SEL_TSS32_IOBP_IRB */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_Tss32IntRedirBm; /**< @see BS3_SEL_TSS32_IRB */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_Tss64; /**< @see BS3_SEL_TSS64 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_Tss64Spare0; /**< @see BS3_SEL_TSS64_SPARE0 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_Tss64Spare1; /**< @see BS3_SEL_TSS64_SPARE1 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_Tss64Iobp; /**< @see BS3_SEL_TSS64_IOBP */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_RMTEXT16_CS; /**< @see BS3_SEL_RMTEXT16_CS */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_X0TEXT16_CS; /**< @see BS3_SEL_X0TEXT16_CS */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_X1TEXT16_CS; /**< @see BS3_SEL_X1TEXT16_CS */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R0_MMIO16; /**< @see BS3_SEL_VMMDEV_MMIO16 */ + +extern X86DESC BS3_FAR_DATA Bs3Gdte_R0_First; /**< @see BS3_SEL_R0_FIRST */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R0_CS16; /**< @see BS3_SEL_R0_CS16 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R0_DS16; /**< @see BS3_SEL_R0_DS16 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R0_SS16; /**< @see BS3_SEL_R0_SS16 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R0_CS32; /**< @see BS3_SEL_R0_CS32 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R0_DS32; /**< @see BS3_SEL_R0_DS32 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R0_SS32; /**< @see BS3_SEL_R0_SS32 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R0_CS64; /**< @see BS3_SEL_R0_CS64 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R0_DS64; /**< @see BS3_SEL_R0_DS64 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R0_CS16_EO; /**< @see BS3_SEL_R0_CS16_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R0_CS16_CNF; /**< @see BS3_SEL_R0_CS16_CNF */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R0_CS16_CND_EO; /**< @see BS3_SEL_R0_CS16_CNF_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R0_CS32_EO; /**< @see BS3_SEL_R0_CS32_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R0_CS32_CNF; /**< @see BS3_SEL_R0_CS32_CNF */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R0_CS32_CNF_EO; /**< @see BS3_SEL_R0_CS32_CNF_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R0_CS64_EO; /**< @see BS3_SEL_R0_CS64_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R0_CS64_CNF; /**< @see BS3_SEL_R0_CS64_CNF */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R0_CS64_CNF_EO; /**< @see BS3_SEL_R0_CS64_CNF_EO */ + +extern X86DESC BS3_FAR_DATA Bs3Gdte_R1_First; /**< @see BS3_SEL_R1_FIRST */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R1_CS16; /**< @see BS3_SEL_R1_CS16 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R1_DS16; /**< @see BS3_SEL_R1_DS16 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R1_SS16; /**< @see BS3_SEL_R1_SS16 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R1_CS32; /**< @see BS3_SEL_R1_CS32 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R1_DS32; /**< @see BS3_SEL_R1_DS32 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R1_SS32; /**< @see BS3_SEL_R1_SS32 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R1_CS64; /**< @see BS3_SEL_R1_CS64 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R1_DS64; /**< @see BS3_SEL_R1_DS64 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R1_CS16_EO; /**< @see BS3_SEL_R1_CS16_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R1_CS16_CNF; /**< @see BS3_SEL_R1_CS16_CNF */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R1_CS16_CND_EO; /**< @see BS3_SEL_R1_CS16_CNF_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R1_CS32_EO; /**< @see BS3_SEL_R1_CS32_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R1_CS32_CNF; /**< @see BS3_SEL_R1_CS32_CNF */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R1_CS32_CNF_EO; /**< @see BS3_SEL_R1_CS32_CNF_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R1_CS64_EO; /**< @see BS3_SEL_R1_CS64_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R1_CS64_CNF; /**< @see BS3_SEL_R1_CS64_CNF */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R1_CS64_CNF_EO; /**< @see BS3_SEL_R1_CS64_CNF_EO */ + +extern X86DESC BS3_FAR_DATA Bs3Gdte_R2_First; /**< @see BS3_SEL_R2_FIRST */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R2_CS16; /**< @see BS3_SEL_R2_CS16 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R2_DS16; /**< @see BS3_SEL_R2_DS16 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R2_SS16; /**< @see BS3_SEL_R2_SS16 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R2_CS32; /**< @see BS3_SEL_R2_CS32 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R2_DS32; /**< @see BS3_SEL_R2_DS32 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R2_SS32; /**< @see BS3_SEL_R2_SS32 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R2_CS64; /**< @see BS3_SEL_R2_CS64 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R2_DS64; /**< @see BS3_SEL_R2_DS64 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R2_CS16_EO; /**< @see BS3_SEL_R2_CS16_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R2_CS16_CNF; /**< @see BS3_SEL_R2_CS16_CNF */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R2_CS16_CND_EO; /**< @see BS3_SEL_R2_CS16_CNF_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R2_CS32_EO; /**< @see BS3_SEL_R2_CS32_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R2_CS32_CNF; /**< @see BS3_SEL_R2_CS32_CNF */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R2_CS32_CNF_EO; /**< @see BS3_SEL_R2_CS32_CNF_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R2_CS64_EO; /**< @see BS3_SEL_R2_CS64_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R2_CS64_CNF; /**< @see BS3_SEL_R2_CS64_CNF */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R2_CS64_CNF_EO; /**< @see BS3_SEL_R2_CS64_CNF_EO */ + +extern X86DESC BS3_FAR_DATA Bs3Gdte_R3_First; /**< @see BS3_SEL_R3_FIRST */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R3_CS16; /**< @see BS3_SEL_R3_CS16 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R3_DS16; /**< @see BS3_SEL_R3_DS16 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R3_SS16; /**< @see BS3_SEL_R3_SS16 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R3_CS32; /**< @see BS3_SEL_R3_CS32 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R3_DS32; /**< @see BS3_SEL_R3_DS32 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R3_SS32; /**< @see BS3_SEL_R3_SS32 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R3_CS64; /**< @see BS3_SEL_R3_CS64 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R3_DS64; /**< @see BS3_SEL_R3_DS64 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R3_CS16_EO; /**< @see BS3_SEL_R3_CS16_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R3_CS16_CNF; /**< @see BS3_SEL_R3_CS16_CNF */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R3_CS16_CND_EO; /**< @see BS3_SEL_R3_CS16_CNF_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R3_CS32_EO; /**< @see BS3_SEL_R3_CS32_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R3_CS32_CNF; /**< @see BS3_SEL_R3_CS32_CNF */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R3_CS32_CNF_EO; /**< @see BS3_SEL_R3_CS32_CNF_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R3_CS64_EO; /**< @see BS3_SEL_R3_CS64_EO */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R3_CS64_CNF; /**< @see BS3_SEL_R3_CS64_CNF */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_R3_CS64_CNF_EO; /**< @see BS3_SEL_R3_CS64_CNF_EO */ + +extern X86DESC BS3_FAR_DATA Bs3GdteSpare00; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_00 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare01; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_01 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare02; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_02 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare03; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_03 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare04; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_04 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare05; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_05 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare06; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_06 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare07; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_07 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare08; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_08 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare09; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_09 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare0a; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_0a */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare0b; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_0b */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare0c; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_0c */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare0d; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_0d */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare0e; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_0e */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare0f; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_0f */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare10; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_10 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare11; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_11 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare12; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_12 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare13; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_13 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare14; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_14 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare15; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_15 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare16; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_16 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare17; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_17 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare18; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_18 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare19; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_19 */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare1a; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_1a */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare1b; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_1b */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare1c; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_1c */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare1d; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_1d */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare1e; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_1e */ +extern X86DESC BS3_FAR_DATA Bs3GdteSpare1f; /**< GDT entry for playing with in testcases. @see BS3_SEL_SPARE_1f */ + +/** GDTs setting up the tiled 16-bit access to the first 16 MBs of memory. + * @see BS3_SEL_TILED, BS3_SEL_TILED_LAST, BS3_SEL_TILED_AREA_SIZE */ +extern X86DESC BS3_FAR_DATA Bs3GdteTiled[256]; +/** Free GDTes, part \#1. */ +extern X86DESC BS3_FAR_DATA Bs3GdteFreePart1[64]; +/** The BS3TEXT16/BS3CLASS16CODE GDT entry. @see BS3_SEL_TEXT16 */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_CODE16; +/** Free GDTes, part \#2. */ +extern X86DESC BS3_FAR_DATA Bs3GdteFreePart2[511]; +/** The BS3SYSTEM16 GDT entry. */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_SYSTEM16; +/** Free GDTes, part \#3. */ +extern X86DESC BS3_FAR_DATA Bs3GdteFreePart3[223]; +/** The BS3DATA16/BS3KIT_GRPNM_DATA16 GDT entry. */ +extern X86DESC BS3_FAR_DATA Bs3Gdte_DATA16; + +/** Free GDTes, part \#4. */ +extern X86DESC BS3_FAR_DATA Bs3GdteFreePart4[211]; + +extern X86DESC BS3_FAR_DATA Bs3GdtePreTestPage08; /**< GDT entry 8 selectors prior to the test page, testcase resource. @see BS3_SEL_PRE_TEST_PAGE_08 */ +extern X86DESC BS3_FAR_DATA Bs3GdtePreTestPage07; /**< GDT entry 7 selectors prior to the test page, testcase resource. @see BS3_SEL_PRE_TEST_PAGE_07 */ +extern X86DESC BS3_FAR_DATA Bs3GdtePreTestPage06; /**< GDT entry 6 selectors prior to the test page, testcase resource. @see BS3_SEL_PRE_TEST_PAGE_06 */ +extern X86DESC BS3_FAR_DATA Bs3GdtePreTestPage05; /**< GDT entry 5 selectors prior to the test page, testcase resource. @see BS3_SEL_PRE_TEST_PAGE_05 */ +extern X86DESC BS3_FAR_DATA Bs3GdtePreTestPage04; /**< GDT entry 4 selectors prior to the test page, testcase resource. @see BS3_SEL_PRE_TEST_PAGE_04 */ +extern X86DESC BS3_FAR_DATA Bs3GdtePreTestPage03; /**< GDT entry 3 selectors prior to the test page, testcase resource. @see BS3_SEL_PRE_TEST_PAGE_03 */ +extern X86DESC BS3_FAR_DATA Bs3GdtePreTestPage02; /**< GDT entry 2 selectors prior to the test page, testcase resource. @see BS3_SEL_PRE_TEST_PAGE_02 */ +extern X86DESC BS3_FAR_DATA Bs3GdtePreTestPage01; /**< GDT entry 1 selectors prior to the test page, testcase resource. @see BS3_SEL_PRE_TEST_PAGE_01 */ +/** Array of GDT entries starting on a page boundrary and filling (almost) the + * whole page. This is for playing with paging and GDT usage. + * @see BS3_SEL_TEST_PAGE */ +extern X86DESC BS3_FAR_DATA Bs3GdteTestPage[2043]; +extern X86DESC BS3_FAR_DATA Bs3GdteTestPage00; /**< GDT entry 0 on the test page (convenience). @see BS3_SEL_TEST_PAGE_00 */ +extern X86DESC BS3_FAR_DATA Bs3GdteTestPage01; /**< GDT entry 1 on the test page (convenience). @see BS3_SEL_TEST_PAGE_01 */ +extern X86DESC BS3_FAR_DATA Bs3GdteTestPage02; /**< GDT entry 2 on the test page (convenience). @see BS3_SEL_TEST_PAGE_02 */ +extern X86DESC BS3_FAR_DATA Bs3GdteTestPage03; /**< GDT entry 3 on the test page (convenience). @see BS3_SEL_TEST_PAGE_03 */ +extern X86DESC BS3_FAR_DATA Bs3GdteTestPage04; /**< GDT entry 4 on the test page (convenience). @see BS3_SEL_TEST_PAGE_04 */ +extern X86DESC BS3_FAR_DATA Bs3GdteTestPage05; /**< GDT entry 5 on the test page (convenience). @see BS3_SEL_TEST_PAGE_05 */ +extern X86DESC BS3_FAR_DATA Bs3GdteTestPage06; /**< GDT entry 6 on the test page (convenience). @see BS3_SEL_TEST_PAGE_06 */ +extern X86DESC BS3_FAR_DATA Bs3GdteTestPage07; /**< GDT entry 7 on the test page (convenience). @see BS3_SEL_TEST_PAGE_07 */ + +/** The end of the GDT (exclusive - contains eye-catcher string). */ +extern X86DESC BS3_FAR_DATA Bs3GdtEnd; + +/** The default 16-bit TSS. */ +extern X86TSS16 BS3_FAR_DATA Bs3Tss16; +extern X86TSS16 BS3_FAR_DATA Bs3Tss16DoubleFault; +extern X86TSS16 BS3_FAR_DATA Bs3Tss16Spare0; +extern X86TSS16 BS3_FAR_DATA Bs3Tss16Spare1; +/** The default 32-bit TSS. */ +extern X86TSS32 BS3_FAR_DATA Bs3Tss32; +extern X86TSS32 BS3_FAR_DATA Bs3Tss32DoubleFault; +extern X86TSS32 BS3_FAR_DATA Bs3Tss32Spare0; +extern X86TSS32 BS3_FAR_DATA Bs3Tss32Spare1; +/** The default 64-bit TSS. */ +extern X86TSS64 BS3_FAR_DATA Bs3Tss64; +extern X86TSS64 BS3_FAR_DATA Bs3Tss64Spare0; +extern X86TSS64 BS3_FAR_DATA Bs3Tss64Spare1; +extern X86TSS64 BS3_FAR_DATA Bs3Tss64WithIopb; +extern X86TSS32 BS3_FAR_DATA Bs3Tss32WithIopb; +/** Interrupt redirection bitmap used by Bs3Tss32WithIopb. */ +extern uint8_t BS3_FAR_DATA Bs3SharedIntRedirBm[32]; +/** I/O permission bitmap used by Bs3Tss32WithIopb and Bs3Tss64WithIopb. */ +extern uint8_t BS3_FAR_DATA Bs3SharedIobp[8192+2]; +/** End of the I/O permission bitmap (exclusive). */ +extern uint8_t BS3_FAR_DATA Bs3SharedIobpEnd; +/** 16-bit IDT. */ +extern X86DESC BS3_FAR_DATA Bs3Idt16[256]; +/** 32-bit IDT. */ +extern X86DESC BS3_FAR_DATA Bs3Idt32[256]; +/** 64-bit IDT. */ +extern X86DESC64 BS3_FAR_DATA Bs3Idt64[256]; +/** Structure for the LIDT instruction for loading the 16-bit IDT. */ +extern X86XDTR64 BS3_FAR_DATA Bs3Lidt_Idt16; +/** Structure for the LIDT instruction for loading the 32-bit IDT. */ +extern X86XDTR64 BS3_FAR_DATA Bs3Lidt_Idt32; +/** Structure for the LIDT instruction for loading the 64-bit IDT. */ +extern X86XDTR64 BS3_FAR_DATA Bs3Lidt_Idt64; +/** Structure for the LIDT instruction for loading the real mode interrupt + * vector table. */ +extern X86XDTR64 BS3_FAR_DATA Bs3Lidt_Ivt; +/** Structure for the LGDT instruction for loading the current GDT. */ +extern X86XDTR64 BS3_FAR_DATA Bs3Lgdt_Gdt; +/** Structure for the LGDT instruction for loading the default GDT. */ +extern X86XDTR64 BS3_FAR_DATA Bs3LgdtDef_Gdt; +/** The LDT (all entries are empty, fill in for testing). */ +extern X86DESC BS3_FAR_DATA Bs3Ldt[116]; +/** The end of the LDT (exclusive). */ +extern X86DESC BS3_FAR_DATA Bs3LdtEnd; + +/** @} */ + + +/** @name Segment start and end markers, sizes. + * @{ */ +/** Start of the BS3TEXT16 segment. */ +extern uint8_t BS3_FAR_DATA Bs3Text16_StartOfSegment; +/** End of the BS3TEXT16 segment. */ +extern uint8_t BS3_FAR_DATA Bs3Text16_EndOfSegment; +/** The size of the BS3TEXT16 segment. */ +extern uint16_t BS3_FAR_DATA Bs3Text16_Size; + +/** Start of the BS3SYSTEM16 segment. */ +extern uint8_t BS3_FAR_DATA Bs3System16_StartOfSegment; +/** End of the BS3SYSTEM16 segment. */ +extern uint8_t BS3_FAR_DATA Bs3System16_EndOfSegment; + +/** Start of the BS3DATA16/BS3KIT_GRPNM_DATA16 segment. */ +extern uint8_t BS3_FAR_DATA Bs3Data16_StartOfSegment; +/** End of the BS3DATA16/BS3KIT_GRPNM_DATA16 segment. */ +extern uint8_t BS3_FAR_DATA Bs3Data16_EndOfSegment; + +/** Start of the BS3RMTEXT16 segment. */ +extern uint8_t BS3_FAR_DATA Bs3RmText16_StartOfSegment; +/** End of the BS3RMTEXT16 segment. */ +extern uint8_t BS3_FAR_DATA Bs3RmText16_EndOfSegment; +/** The size of the BS3RMTEXT16 segment. */ +extern uint16_t BS3_FAR_DATA Bs3RmText16_Size; +/** The flat start address of the BS3X0TEXT16 segment. */ +extern uint32_t BS3_FAR_DATA Bs3RmText16_FlatAddr; + +/** Start of the BS3X0TEXT16 segment. */ +extern uint8_t BS3_FAR_DATA Bs3X0Text16_StartOfSegment; +/** End of the BS3X0TEXT16 segment. */ +extern uint8_t BS3_FAR_DATA Bs3X0Text16_EndOfSegment; +/** The size of the BS3X0TEXT16 segment. */ +extern uint16_t BS3_FAR_DATA Bs3X0Text16_Size; +/** The flat start address of the BS3X0TEXT16 segment. */ +extern uint32_t BS3_FAR_DATA Bs3X0Text16_FlatAddr; + +/** Start of the BS3X1TEXT16 segment. */ +extern uint8_t BS3_FAR_DATA Bs3X1Text16_StartOfSegment; +/** End of the BS3X1TEXT16 segment. */ +extern uint8_t BS3_FAR_DATA Bs3X1Text16_EndOfSegment; +/** The size of the BS3X1TEXT16 segment. */ +extern uint16_t BS3_FAR_DATA Bs3X1Text16_Size; +/** The flat start address of the BS3X1TEXT16 segment. */ +extern uint32_t BS3_FAR_DATA Bs3X1Text16_FlatAddr; + +/** Start of the BS3TEXT32 segment. */ +extern uint8_t BS3_FAR_DATA Bs3Text32_StartOfSegment; +/** Start of the BS3TEXT32 segment. */ +extern uint8_t BS3_FAR_DATA Bs3Text32_EndOfSegment; + +/** Start of the BS3DATA32 segment. */ +extern uint8_t BS3_FAR_DATA Bs3Data32_StartOfSegment; +/** Start of the BS3DATA32 segment. */ +extern uint8_t BS3_FAR_DATA Bs3Data32_EndOfSegment; + +/** Start of the BS3TEXT64 segment. */ +extern uint8_t BS3_FAR_DATA Bs3Text64_StartOfSegment; +/** Start of the BS3TEXT64 segment. */ +extern uint8_t BS3_FAR_DATA Bs3Text64_EndOfSegment; + +/** Start of the BS3DATA64 segment. */ +extern uint8_t BS3_FAR_DATA Bs3Data64_StartOfSegment; +/** Start of the BS3DATA64 segment. */ +extern uint8_t BS3_FAR_DATA Bs3Data64_EndOfSegment; + +/** The size of the Data16, Text32, Text64, Data32 and Data64 blob. */ +extern uint32_t BS3_FAR_DATA Bs3Data16Thru64Text32And64_TotalSize; +/** The total image size (from Text16 thu Data64). */ +extern uint32_t BS3_FAR_DATA Bs3TotalImageSize; +/** @} */ + + +/** Lower case hex digits. */ +extern char const g_achBs3HexDigits[16+1]; +/** Upper case hex digits. */ +extern char const g_achBs3HexDigitsUpper[16+1]; + + +/** The current mode (BS3_MODE_XXX) of CPU \#0. */ +extern uint8_t g_bBs3CurrentMode; + +/** Hint for 16-bit trap handlers regarding the high word of EIP. */ +extern uint32_t g_uBs3TrapEipHint; + +/** Set to disable special V8086 \#GP and \#UD handling in Bs3TrapDefaultHandler. + * This is useful for getting */ +extern bool volatile g_fBs3TrapNoV86Assist; + +/** Copy of the original real-mode interrupt vector table. */ +extern RTFAR16 g_aBs3RmIvtOriginal[256]; + + +#ifdef __WATCOMC__ +/** + * Executes the SMSW instruction and returns the value. + * + * @returns Machine status word. + */ +uint16_t Bs3AsmSmsw(void); +# pragma aux Bs3AsmSmsw = \ + ".286" \ + "smsw ax" \ + value [ax] modify exact [ax] nomemory; +#endif + + +/** @defgroup bs3kit_cross_ptr Cross Context Pointer Type + * + * The cross context pointer type is + * + * @{ */ + +/** + * Cross context pointer base type. + */ +typedef union BS3XPTR +{ + /** The flat pointer. */ + uint32_t uFlat; + /** 16-bit view. */ + struct + { + uint16_t uLow; + uint16_t uHigh; + } u; +#if ARCH_BITS == 16 + /** 16-bit near pointer. */ + void __near *pvNear; +#elif ARCH_BITS == 32 + /** 32-bit pointer. */ + void *pvRaw; +#endif +} BS3XPTR; +AssertCompileSize(BS3XPTR, 4); + + +/** @def BS3_XPTR_DEF_INTERNAL + * Internal worker. + * + * @param a_Scope RT_NOTHING if structure or global, static or extern + * otherwise. + * @param a_Type The type we're pointing to. + * @param a_Name The member or variable name. + * @internal + */ +#if ARCH_BITS == 16 +# define BS3_XPTR_DEF_INTERNAL(a_Scope, a_Type, a_Name) \ + a_Scope union \ + { \ + BS3XPTR XPtr; \ + a_Type __near *pNearTyped; \ + } a_Name +#elif ARCH_BITS == 32 +# define BS3_XPTR_DEF_INTERNAL(a_Scope, a_Type, a_Name) \ + a_Scope union \ + { \ + BS3XPTR XPtr; \ + a_Type *pTyped; \ + } a_Name +#elif ARCH_BITS == 64 +# define BS3_XPTR_DEF_INTERNAL(a_Scope, a_Type, a_Name) \ + a_Scope union \ + { \ + BS3XPTR XPtr; \ + } a_Name +#else +# error "ARCH_BITS" +#endif + +/** @def BS3_XPTR_MEMBER + * Defines a pointer member that can be shared by all CPU modes. + * + * @param a_Type The type we're pointing to. + * @param a_Name The member or variable name. + */ +#define BS3_XPTR_MEMBER(a_Type, a_Name) BS3_XPTR_DEF_INTERNAL(RT_NOTHING, a_Type, a_Name) + +/** @def BS3_XPTR_AUTO + * Defines a pointer static variable for working with an XPTR. + * + * This is typically used to convert flat pointers into context specific + * pointers. + * + * @param a_Type The type we're pointing to. + * @param a_Name The member or variable name. + */ +#define BS3_XPTR_AUTO(a_Type, a_Name) BS3_XPTR_DEF_INTERNAL(RT_NOTHING, a_Type, a_Name) + +/** @def BS3_XPTR_SET_FLAT + * Sets a cross context pointer. + * + * @param a_Type The type we're pointing to. + * @param a_Name The member or variable name. + * @param a_uFlatPtr The flat pointer value to assign. If the x-pointer is + * used in real mode, this must be less than 1MB. + * Otherwise the limit is 16MB (due to selector tiling). + */ +#define BS3_XPTR_SET_FLAT(a_Type, a_Name, a_uFlatPtr) \ + do { a_Name.XPtr.uFlat = (a_uFlatPtr); } while (0) + +/** @def BS3_XPTR_GET_FLAT + * Gets the flat address of a cross context pointer. + * + * @returns 32-bit flat pointer. + * @param a_Type The type we're pointing to. + * @param a_Name The member or variable name. + */ +#define BS3_XPTR_GET_FLAT(a_Type, a_Name) (a_Name.XPtr.uFlat) + +/** @def BS3_XPTR_GET_FLAT_LOW + * Gets the low 16 bits of the flat address. + * + * @returns Low 16 bits of the flat pointer. + * @param a_Type The type we're pointing to. + * @param a_Name The member or variable name. + */ +#define BS3_XPTR_GET_FLAT_LOW(a_Type, a_Name) (a_Name.XPtr.u.uLow) + + +#if ARCH_BITS == 16 + +/** + * Gets the current ring number. + * @returns Ring number. + */ +DECLINLINE(uint16_t) Bs3Sel16GetCurRing(void); +# pragma aux Bs3Sel16GetCurRing = \ + "mov ax, ss" \ + "and ax, 3" \ + value [ax] modify exact [ax] nomemory; + +/** + * Converts the high word of a flat pointer into a 16-bit selector. + * + * This makes use of the tiled area. It also handles real mode. + * + * @returns Segment selector value. + * @param uHigh The high part of flat pointer. + * @sa BS3_XPTR_GET, BS3_XPTR_SET + */ +DECLINLINE(__segment) Bs3Sel16HighFlatPtrToSelector(uint16_t uHigh) +{ + if (!BS3_MODE_IS_RM_OR_V86(g_bBs3CurrentMode)) + return (__segment)(((uHigh << 3) + BS3_SEL_TILED) | Bs3Sel16GetCurRing()); + return (__segment)(uHigh << 12); +} + +#endif /* ARCH_BITS == 16 */ + +/** @def BS3_XPTR_GET + * Gets the current context pointer value. + * + * @returns Usable pointer. + * @param a_Type The type we're pointing to. + * @param a_Name The member or variable name. + */ +#if ARCH_BITS == 16 +# define BS3_XPTR_GET(a_Type, a_Name) \ + ((a_Type BS3_FAR *)BS3_FP_MAKE(Bs3Sel16HighFlatPtrToSelector((a_Name).XPtr.u.uHigh), (a_Name).pNearTyped)) +#elif ARCH_BITS == 32 +# define BS3_XPTR_GET(a_Type, a_Name) ((a_Name).pTyped) +#elif ARCH_BITS == 64 +# define BS3_XPTR_GET(a_Type, a_Name) ((a_Type *)(uintptr_t)(a_Name).XPtr.uFlat) +#else +# error "ARCH_BITS" +#endif + +/** @def BS3_XPTR_SET + * Gets the current context pointer value. + * + * @returns Usable pointer. + * @param a_Type The type we're pointing to. + * @param a_Name The member or variable name. + * @param a_pValue The new pointer value, current context pointer. + */ +#if ARCH_BITS == 16 +# define BS3_XPTR_SET(a_Type, a_Name, a_pValue) \ + do { \ + a_Type BS3_FAR *pTypeCheck = (a_pValue); \ + if (BS3_MODE_IS_RM_OR_V86(g_bBs3CurrentMode)) \ + (a_Name).XPtr.uFlat = BS3_FP_OFF(pTypeCheck) + ((uint32_t)BS3_FP_SEG(pTypeCheck) << 4); \ + else \ + { \ + (a_Name).XPtr.u.uLow = BS3_FP_OFF(pTypeCheck); \ + (a_Name).XPtr.u.uHigh = ((BS3_FP_SEG(pTypeCheck) & UINT16_C(0xfff8)) - BS3_SEL_TILED) >> 3; \ + } \ + } while (0) +#elif ARCH_BITS == 32 +# define BS3_XPTR_SET(a_Type, a_Name, a_pValue) \ + do { (a_Name).pTyped = (a_pValue); } while (0) +#elif ARCH_BITS == 64 +# define BS3_XPTR_SET(a_Type, a_Name, a_pValue) \ + do { \ + a_Type *pTypeCheck = (a_pValue); \ + (a_Name).XPtr.uFlat = (uint32_t)(uintptr_t)pTypeCheck; \ + } while (0) +#else +# error "ARCH_BITS" +#endif + + +/** @def BS3_XPTR_IS_NULL + * Checks if the cross context pointer is NULL. + * + * @returns true if NULL, false if not. + * @param a_Type The type we're pointing to. + * @param a_Name The member or variable name. + */ +#define BS3_XPTR_IS_NULL(a_Type, a_Name) ((a_Name).XPtr.uFlat == 0) + +/** + * Gets a working pointer from a flat address. + * + * @returns Current context pointer. + * @param uFlatPtr The flat address to convert (32-bit or 64-bit). + */ +DECLINLINE(void BS3_FAR *) Bs3XptrFlatToCurrent(RTCCUINTXREG uFlatPtr) +{ + BS3_XPTR_AUTO(void, pTmp); + BS3_XPTR_SET_FLAT(void, pTmp, uFlatPtr); + return BS3_XPTR_GET(void, pTmp); +} + +/** @} */ + + + +/** @defgroup grp_bs3kit_cmn Common Functions and Data + * + * The common functions comes in three variations: 16-bit, 32-bit and 64-bit. + * Templated code uses the #BS3_CMN_NM macro to mangle the name according to the + * desired + * + * @{ + */ + +/** @def BS3_CMN_PROTO_INT + * Internal macro for prototyping all the variations of a common function. + * @param a_RetType The return type. + * @param a_Name The function basename. + * @param a_Params The parameter list (in parentheses). + * @sa BS3_CMN_PROTO_STUB, BS3_CMN_PROTO_NOSB + */ +#if ARCH_BITS == 16 +# ifndef BS3_USE_ALT_16BIT_TEXT_SEG +# define BS3_CMN_PROTO_INT(a_RetType, a_Name, a_Params) \ + BS3_DECL_NEAR(a_RetType) BS3_CMN_NM(a_Name) a_Params; \ + BS3_DECL_FAR(a_RetType) BS3_CMN_FAR_NM(a_Name) a_Params +# else +# define BS3_CMN_PROTO_INT(a_RetType, a_Name, a_Params) \ + BS3_DECL_FAR(a_RetType) BS3_CMN_FAR_NM(a_Name) a_Params +# endif +#else +# define BS3_CMN_PROTO_INT(a_RetType, a_Name, a_Params) \ + BS3_DECL_NEAR(a_RetType) BS3_CMN_NM(a_Name) a_Params +#endif + +/** @def BS3_CMN_PROTO_STUB + * Macro for prototyping all the variations of a common function with automatic + * near -> far stub. + * + * @param a_RetType The return type. + * @param a_Name The function basename. + * @param a_Params The parameter list (in parentheses). + * @sa BS3_CMN_PROTO_NOSB + */ +#define BS3_CMN_PROTO_STUB(a_RetType, a_Name, a_Params) BS3_CMN_PROTO_INT(a_RetType, a_Name, a_Params) + +/** @def BS3_CMN_PROTO_NOSB + * Macro for prototyping all the variations of a common function without any + * near > far stub. + * + * @param a_RetType The return type. + * @param a_Name The function basename. + * @param a_Params The parameter list (in parentheses). + * @sa BS3_CMN_PROTO_STUB + */ +#define BS3_CMN_PROTO_NOSB(a_RetType, a_Name, a_Params) BS3_CMN_PROTO_INT(a_RetType, a_Name, a_Params) + +/** @def BS3_CMN_PROTO_FARSTUB + * Macro for prototyping all the variations of a common function with automatic + * far -> near stub. + * + * @param a_cbParam16 The size of the 16-bit parameter list in bytes. + * @param a_RetType The return type. + * @param a_Name The function basename. + * @param a_Params The parameter list (in parentheses). + * @sa BS3_CMN_PROTO_STUB + */ +#define BS3_CMN_PROTO_FARSTUB(a_cbParam16, a_RetType, a_Name, a_Params) BS3_CMN_PROTO_INT(a_RetType, a_Name, a_Params) + + +/** @def BS3_CMN_DEF + * Macro for defining a common function. + * + * This makes 16-bit common function far, while 32-bit and 64-bit are near. + * + * @param a_RetType The return type. + * @param a_Name The function basename. + * @param a_Params The parameter list (in parentheses). + */ +#if ARCH_BITS == 16 +# define BS3_CMN_DEF(a_RetType, a_Name, a_Params) \ + BS3_DECL_FAR(a_RetType) BS3_CMN_FAR_NM(a_Name) a_Params +#else +# define BS3_CMN_DEF(a_RetType, a_Name, a_Params) \ + BS3_DECL_NEAR(a_RetType) BS3_CMN_NM(a_Name) a_Params +#endif + +/** @def BS3_ASSERT + * Assert that an expression is true. + * + * Calls Bs3Panic if false and it's a strict build. Does nothing in + * non-strict builds. */ +#ifdef BS3_STRICT +# define BS3_ASSERT(a_Expr) do { if (!!(a_Expr)) { /* likely */ } else { Bs3Panic(); } } while (0) /**< @todo later */ +#else +# define BS3_ASSERT(a_Expr) do { } while (0) +#endif + +/** + * Panic, never return. + * + * The current implementation will only halt the CPU. + */ +BS3_CMN_PROTO_NOSB(DECL_NO_RETURN(void), Bs3Panic,(void)); +#if !defined(BS3_KIT_WITH_NO_RETURN) && defined(__WATCOMC__) +# pragma aux Bs3Panic_c16 __aborts +# pragma aux Bs3Panic_f16 __aborts +# pragma aux Bs3Panic_c32 __aborts +#endif + + +/** + * Translate a mode into a string. + * + * @returns Pointer to read-only mode name string. + * @param bMode The mode value (BS3_MODE_XXX). + */ +BS3_CMN_PROTO_STUB(const char BS3_FAR *, Bs3GetModeName,(uint8_t bMode)); + +/** + * Translate a mode into a short lower case string. + * + * @returns Pointer to read-only short mode name string. + * @param bMode The mode value (BS3_MODE_XXX). + */ +BS3_CMN_PROTO_STUB(const char BS3_FAR *, Bs3GetModeNameShortLower,(uint8_t bMode)); + +/** CPU vendors. */ +typedef enum BS3CPUVENDOR +{ + BS3CPUVENDOR_INVALID = 0, + BS3CPUVENDOR_INTEL, + BS3CPUVENDOR_AMD, + BS3CPUVENDOR_VIA, + BS3CPUVENDOR_CYRIX, + BS3CPUVENDOR_SHANGHAI, + BS3CPUVENDOR_HYGON, + BS3CPUVENDOR_UNKNOWN, + BS3CPUVENDOR_END +} BS3CPUVENDOR; + +/** + * Tries to detect the CPU vendor. + * + * @returns CPU vendor. + */ +BS3_CMN_PROTO_STUB(BS3CPUVENDOR, Bs3GetCpuVendor,(void)); + +/** + * Shutdown the system, never returns. + * + * This currently only works for VMs. When running on real systems it will + * just halt the CPU. + */ +BS3_CMN_PROTO_NOSB(void, Bs3Shutdown,(void)); + +/** + * Prints a 32-bit unsigned value as decimal to the screen. + * + * @param uValue The 32-bit value. + */ +BS3_CMN_PROTO_NOSB(void, Bs3PrintU32,(uint32_t uValue)); + +/** + * Prints a 32-bit unsigned value as hex to the screen. + * + * @param uValue The 32-bit value. + */ +BS3_CMN_PROTO_NOSB(void, Bs3PrintX32,(uint32_t uValue)); + +/** + * Formats and prints a string to the screen. + * + * See #Bs3StrFormatV for supported format types. + * + * @param pszFormat The format string. + * @param ... Format arguments. + */ +BS3_CMN_PROTO_STUB(size_t, Bs3Printf,(const char BS3_FAR *pszFormat, ...)); + +/** + * Formats and prints a string to the screen, va_list version. + * + * See #Bs3StrFormatV for supported format types. + * + * @param pszFormat The format string. + * @param va Format arguments. + */ +BS3_CMN_PROTO_STUB(size_t, Bs3PrintfV,(const char BS3_FAR *pszFormat, va_list BS3_FAR va)); + +/** + * Prints a string to the screen. + * + * @param pszString The string to print. + */ +BS3_CMN_PROTO_STUB(void, Bs3PrintStr,(const char BS3_FAR *pszString)); + +/** + * Prints a string to the screen. + * + * @param pszString The string to print. Any terminator charss will be printed. + * @param cchString The exact number of characters to print. + */ +BS3_CMN_PROTO_NOSB(void, Bs3PrintStrN,(const char BS3_FAR *pszString, size_t cchString)); + +/** + * Prints a char to the screen. + * + * @param ch The character to print. + */ +BS3_CMN_PROTO_NOSB(void, Bs3PrintChr,(char ch)); + + +/** + * An output function for #Bs3StrFormatV. + * + * @returns Number of characters written. + * @param ch The character to write. Zero in the final call. + * @param pvUser User argument supplied to #Bs3StrFormatV. + */ +typedef BS3_DECL_CALLBACK(size_t) FNBS3STRFORMATOUTPUT(char ch, void BS3_FAR *pvUser); +/** Pointer to an output function for #Bs3StrFormatV. */ +typedef FNBS3STRFORMATOUTPUT *PFNBS3STRFORMATOUTPUT; + +/** + * Formats a string, sending the output to @a pfnOutput. + * + * Supported types: + * - %RI8, %RI16, %RI32, %RI64 + * - %RU8, %RU16, %RU32, %RU64 + * - %RX8, %RX16, %RX32, %RX64 + * - %i, %d + * - %u + * - %x + * - %c + * - %p (far pointer) + * - %s (far pointer) + * + * @returns Sum of @a pfnOutput return values. + * @param pszFormat The format string. + * @param va Format arguments. + * @param pfnOutput The output function. + * @param pvUser The user argument for the output function. + */ +BS3_CMN_PROTO_STUB(size_t, Bs3StrFormatV,(const char BS3_FAR *pszFormat, va_list BS3_FAR va, + PFNBS3STRFORMATOUTPUT pfnOutput, void BS3_FAR *pvUser)); + +/** + * Formats a string into a buffer. + * + * See #Bs3StrFormatV for supported format types. + * + * @returns The length of the formatted string (excluding terminator). + * This will be higher or equal to @c cbBuf in case of an overflow. + * @param pszBuf The output buffer. + * @param cbBuf The size of the output buffer. + * @param pszFormat The format string. + * @param va Format arguments. + */ +BS3_CMN_PROTO_STUB(size_t, Bs3StrPrintfV,(char BS3_FAR *pszBuf, size_t cbBuf, const char BS3_FAR *pszFormat, va_list BS3_FAR va)); + +/** + * Formats a string into a buffer. + * + * See #Bs3StrFormatV for supported format types. + * + * @returns The length of the formatted string (excluding terminator). + * This will be higher or equal to @c cbBuf in case of an overflow. + * @param pszBuf The output buffer. + * @param cbBuf The size of the output buffer. + * @param pszFormat The format string. + * @param ... Format arguments. + */ +BS3_CMN_PROTO_STUB(size_t, Bs3StrPrintf,(char BS3_FAR *pszBuf, size_t cbBuf, const char BS3_FAR *pszFormat, ...)); + + +/** + * Finds the length of a zero terminated string. + * + * @returns String length in chars/bytes. + * @param pszString The string to examine. + */ +BS3_CMN_PROTO_STUB(size_t, Bs3StrLen,(const char BS3_FAR *pszString)); + +/** + * Finds the length of a zero terminated string, but with a max length. + * + * @returns String length in chars/bytes, or @a cchMax if no zero-terminator + * was found before we reached the limit. + * @param pszString The string to examine. + * @param cchMax The max length to examine. + */ +BS3_CMN_PROTO_STUB(size_t, Bs3StrNLen,(const char BS3_FAR *pszString, size_t cchMax)); + +/** + * CRT style unsafe strcpy. + * + * @returns pszDst. + * @param pszDst The destination buffer. Must be large enough to + * hold the source string. + * @param pszSrc The source string. + */ +BS3_CMN_PROTO_STUB(char BS3_FAR *, Bs3StrCpy,(char BS3_FAR *pszDst, const char BS3_FAR *pszSrc)); + +/** + * CRT style memcpy. + * + * @returns pvDst + * @param pvDst The destination buffer. + * @param pvSrc The source buffer. + * @param cbToCopy The number of bytes to copy. + */ +BS3_CMN_PROTO_STUB(void BS3_FAR *, Bs3MemCpy,(void BS3_FAR *pvDst, const void BS3_FAR *pvSrc, size_t cbToCopy)); + +/** + * GNU (?) style mempcpy. + * + * @returns pvDst + cbCopy + * @param pvDst The destination buffer. + * @param pvSrc The source buffer. + * @param cbToCopy The number of bytes to copy. + */ +BS3_CMN_PROTO_STUB(void BS3_FAR *, Bs3MemPCpy,(void BS3_FAR *pvDst, const void BS3_FAR *pvSrc, size_t cbToCopy)); + +/** + * CRT style memmove (overlapping buffers is fine). + * + * @returns pvDst + * @param pvDst The destination buffer. + * @param pvSrc The source buffer. + * @param cbToCopy The number of bytes to copy. + */ +BS3_CMN_PROTO_STUB(void BS3_FAR *, Bs3MemMove,(void BS3_FAR *pvDst, const void BS3_FAR *pvSrc, size_t cbToCopy)); + +/** + * BSD style bzero. + * + * @param pvDst The buffer to be zeroed. + * @param cbDst The number of bytes to zero. + */ +BS3_CMN_PROTO_NOSB(void, Bs3MemZero,(void BS3_FAR *pvDst, size_t cbDst)); + +/** + * CRT style memset. + * + * @param pvDst The buffer to be fill. + * @param bFiller The filler byte. + * @param cbDst The number of bytes to fill. + */ +BS3_CMN_PROTO_NOSB(void, Bs3MemSet,(void BS3_FAR *pvDst, uint8_t bFiller, size_t cbDst)); + +/** + * CRT style memchr. + * + * @param pvHaystack The memory to scan for @a bNeedle. + * @param bNeedle The byte to search for. + * @param cbHaystack The amount of memory to search. + */ +BS3_CMN_PROTO_NOSB(void BS3_FAR *, Bs3MemChr,(void const BS3_FAR *pvHaystack, uint8_t bNeedle, size_t cbHaystack)); + +/** + * CRT style memcmp. + * + * @returns 0 if equal. Negative if the left side is 'smaller' than the right + * side, and positive in the other case. + * @param pv1 The left hand memory. + * @param pv2 The right hand memory. + * @param cb The number of bytes to compare. + */ +BS3_CMN_PROTO_NOSB(int, Bs3MemCmp,(void const BS3_FAR *pv1, void const BS3_FAR *pv2, size_t cb)); + +BS3_CMN_PROTO_STUB(void, Bs3UInt64Div,(RTUINT64U uDividend, RTUINT64U uDivisor, RTUINT64U BS3_FAR *paQuotientReminder)); +BS3_CMN_PROTO_STUB(void, Bs3UInt32Div,(RTUINT32U uDividend, RTUINT32U uDivisor, RTUINT32U BS3_FAR *paQuotientReminder)); + + +/** + * Converts a protected mode 32-bit far pointer to a 32-bit flat address. + * + * @returns 32-bit flat address. + * @param off The segment offset. + * @param uSel The protected mode segment selector. + */ +BS3_CMN_PROTO_STUB(uint32_t, Bs3SelProtFar32ToFlat32,(uint32_t off, uint16_t uSel)); + +/** + * Converts a current mode 32-bit far pointer to a 32-bit flat address. + * + * @returns 32-bit flat address. + * @param off The segment offset. + * @param uSel The current mode segment selector. + */ +BS3_CMN_PROTO_STUB(uint32_t, Bs3SelFar32ToFlat32,(uint32_t off, uint16_t uSel)); + +/** + * Wrapper around Bs3SelFar32ToFlat32 that makes it easier to use in tight + * assembly spots. + * + * @returns 32-bit flat address. + * @param off The segment offset. + * @param uSel The current mode segment selector. + * @remarks All register are preserved, except return. + * @remarks No 20h scratch space required in 64-bit mode. + */ +BS3_CMN_PROTO_FARSTUB(6, uint32_t, Bs3SelFar32ToFlat32NoClobber,(uint32_t off, uint16_t uSel)); + +/** + * Converts a real mode code segment to a protected mode code segment selector. + * + * @returns protected mode segment selector. + * @param uRealSeg Real mode code segment. + * @remarks All register are preserved, except return and parameter. + */ +BS3_CMN_PROTO_NOSB(uint16_t, Bs3SelRealModeCodeToProtMode,(uint16_t uRealSeg)); + +/** + * Converts a real mode code segment to a protected mode code segment selector. + * + * @returns protected mode segment selector. + * @param uProtSel Real mode code segment. + * @remarks All register are preserved, except return and parameter. + */ +BS3_CMN_PROTO_NOSB(uint16_t, Bs3SelProtModeCodeToRealMode,(uint16_t uProtSel)); + +/** + * Converts a flat code address to a real mode segment and offset. + * + * @returns Far real mode address (high 16-bit is segment, low is offset). + * @param uFlatAddr Flat code address. + * @remarks All register are preserved, except return and parameter. + */ +BS3_CMN_PROTO_NOSB(uint32_t, Bs3SelFlatCodeToRealMode,(uint32_t uFlatAddr)); + +/** + * Converts a flat code address to a protected mode 16-bit far pointer (ring-0). + * + * @returns Far 16-bit protected mode address (high 16-bit is segment selector, + * low is segment offset). + * @param uFlatAddr Flat code address. + * @remarks All register are preserved, except return and parameter. + */ +BS3_CMN_PROTO_NOSB(uint32_t, Bs3SelFlatCodeToProtFar16,(uint32_t uFlatAddr)); + +/** + * Converts a far 16:16 real mode (code) address to a flat address. + * + * @returns 32-bit flat address. + * @param uFar1616 Far real mode address (high 16-bit is segment, low + * is offset). + * @remarks All register are preserved, except return. + * @remarks No 20h scratch space required in 64-bit mode. + * @remarks Exactly the same as Bs3SelRealModeDataToFlat, except for param. + */ +BS3_CMN_PROTO_FARSTUB(4, uint32_t, Bs3SelRealModeCodeToFlat,(PFNBS3FARADDRCONV uFar1616)); + +/** + * Converts a flat data address to a real mode segment and offset. + * + * @returns Far real mode address (high 16-bit is segment, low is offset) + * @param uFlatAddr Flat code address. + * @remarks All register are preserved, except return. + * @remarks No 20h scratch space required in 64-bit mode. + */ +BS3_CMN_PROTO_FARSTUB(4, uint32_t, Bs3SelFlatDataToRealMode,(uint32_t uFlatAddr)); + +/** + * Converts a flat data address to a real mode segment and offset. + * + * @returns Far 16-bit protected mode address (high 16-bit is segment selector, + * low is segment offset). + * @param uFlatAddr Flat code address. + * @remarks All register are preserved, except return. + * @remarks No 20h scratch space required in 64-bit mode. + */ +BS3_CMN_PROTO_FARSTUB(4, uint32_t, Bs3SelFlatDataToProtFar16,(uint32_t uFlatAddr)); + +/** + * Converts a far 16:16 data address to a real mode segment and offset. + * + * @returns Far real mode address (high 16-bit is segment, low is offset) + * @param uFar1616 Far 16-bit protected mode address (high 16-bit is + * segment selector, low is segment offset). + * @remarks All register are preserved, except return. + * @remarks No 20h scratch space required in 64-bit mode. + */ +BS3_CMN_PROTO_FARSTUB(4, uint32_t, Bs3SelProtFar16DataToRealMode,(uint32_t uFar1616)); + +/** + * Converts a far 16:16 real mode address to a 16-bit protected mode address. + * + * @returns Far real mode address (high 16-bit is segment, low is offset) + * @param uFar1616 Far real mode address (high 16-bit is segment, low + * is offset). + * @remarks All register are preserved, except return. + * @remarks No 20h scratch space required in 64-bit mode. + */ +BS3_CMN_PROTO_FARSTUB(4, uint32_t, Bs3SelRealModeDataToProtFar16,(uint32_t uFar1616)); + +/** + * Converts a far 16:16 data address to a flat 32-bit address. + * + * @returns 32-bit flat address. + * @param uFar1616 Far 16-bit protected mode address (high 16-bit is + * segment selector, low is segment offset). + * @remarks All register are preserved, except return. + * @remarks No 20h scratch space required in 64-bit mode. + */ +BS3_CMN_PROTO_FARSTUB(4, uint32_t, Bs3SelProtFar16DataToFlat,(uint32_t uFar1616)); + +/** + * Converts a far 16:16 real mode address to a flat address. + * + * @returns 32-bit flat address. + * @param uFar1616 Far real mode address (high 16-bit is segment, low + * is offset). + * @remarks All register are preserved, except return. + * @remarks No 20h scratch space required in 64-bit mode. + */ +BS3_CMN_PROTO_FARSTUB(4, uint32_t, Bs3SelRealModeDataToFlat,(uint32_t uFar1616)); + +/** + * Converts a link-time pointer to a current context pointer. + * + * @returns Converted pointer. + * @param pvLnkPtr The pointer the linker produced. + */ +BS3_CMN_PROTO_FARSTUB(4, void BS3_FAR *, Bs3SelLnkPtrToCurPtr,(void BS3_FAR *pvLnkPtr)); + +/** + * Converts a link-time pointer to a flat address. + * + * @returns 32-bit flag address. + * @param pvLnkPtr The pointer the linker produced. + */ +BS3_CMN_PROTO_FARSTUB(4, uint32_t, Bs3SelLnkPtrToFlat,(void BS3_FAR *pvLnkPtr)); + +/** + * Gets a flat address from a working poitner. + * + * @returns flat address (32-bit or 64-bit). + * @param pv Current context pointer. + */ +DECLINLINE(RTCCUINTXREG) Bs3SelPtrToFlat(void BS3_FAR *pv) +{ +#if ARCH_BITS == 16 + return BS3_CMN_FN_NM(Bs3SelFar32ToFlat32)(BS3_FP_OFF(pv), BS3_FP_SEG(pv)); +#else + return (uintptr_t)pv; +#endif +} + +/** + * Sets up a 16-bit read-write data selector with ring-3 access and 64KB limit. + * + * @param pDesc Pointer to the descriptor table entry. + * @param uBaseAddr The base address of the descriptor. + */ +BS3_CMN_PROTO_STUB(void, Bs3SelSetup16BitData,(X86DESC BS3_FAR *pDesc, uint32_t uBaseAddr)); + +/** + * Sets up a 16-bit execute-read selector with a 64KB limit. + * + * @param pDesc Pointer to the descriptor table entry. + * @param uBaseAddr The base address of the descriptor. + * @param bDpl The descriptor privilege level. + */ +BS3_CMN_PROTO_STUB(void, Bs3SelSetup16BitCode,(X86DESC BS3_FAR *pDesc, uint32_t uBaseAddr, uint8_t bDpl)); + +/** + * Sets up a 32-bit execute-read selector with a user specified limit. + * + * @param pDesc Pointer to the descriptor table entry. + * @param uBaseAddr The base address of the descriptor. + * @param uLimit The limit. (This is included here and not in the 16-bit + * functions because we're more likely to want to set it + * than for 16-bit selectors.) + * @param bDpl The descriptor privilege level. + */ +BS3_CMN_PROTO_STUB(void, Bs3SelSetup32BitCode,(X86DESC BS3_FAR *pDesc, uint32_t uBaseAddr, uint32_t uLimit, uint8_t bDpl)); + +/** + * Sets up a 16-bit or 32-bit gate descriptor. + * + * This can be used both for GDT/LDT and IDT. + * + * @param pDesc Pointer to the descriptor table entry. + * @param bType The gate type. + * @param bDpl The gate DPL. + * @param uSel The gate selector value. + * @param off The gate IP/EIP value. + * @param cParams Number of parameters to copy if call-gate. + */ +BS3_CMN_PROTO_STUB(void, Bs3SelSetupGate,(X86DESC BS3_FAR *pDesc, uint8_t bType, uint8_t bDpl, + uint16_t uSel, uint32_t off, uint8_t cParams)); + +/** + * Sets up a 64-bit gate descriptor. + * + * This can be used both for GDT/LDT and IDT. + * + * @param pDescPair Pointer to the _two_ descriptor table entries. + * @param bType The gate type. + * @param bDpl The gate DPL. + * @param uSel The gate selector value. + * @param off The gate IP/EIP value. + */ +BS3_CMN_PROTO_STUB(void, Bs3SelSetupGate64,(X86DESC BS3_FAR *pDescPair, uint8_t bType, uint8_t bDpl, uint16_t uSel, uint64_t off)); + + +/** + * Slab control structure list head. + * + * The slabs on the list must all have the same chunk size. + */ +typedef struct BS3SLABHEAD +{ + /** Pointer to the first slab. */ + BS3_XPTR_MEMBER(struct BS3SLABCTL, pFirst); + /** The allocation chunk size. */ + uint16_t cbChunk; + /** Number of slabs in the list. */ + uint16_t cSlabs; + /** Number of chunks in the list. */ + uint32_t cChunks; + /** Number of free chunks. */ + uint32_t cFreeChunks; +} BS3SLABHEAD; +AssertCompileSize(BS3SLABHEAD, 16); +/** Pointer to a slab list head. */ +typedef BS3SLABHEAD BS3_FAR *PBS3SLABHEAD; + +/** + * Allocation slab control structure. + * + * This may live at the start of the slab for 4KB slabs, while in a separate + * static location for the larger ones. + */ +typedef struct BS3SLABCTL +{ + /** Pointer to the next slab control structure in this list. */ + BS3_XPTR_MEMBER(struct BS3SLABCTL, pNext); + /** Pointer to the slab list head. */ + BS3_XPTR_MEMBER(BS3SLABHEAD, pHead); + /** The base address of the slab. */ + BS3_XPTR_MEMBER(uint8_t, pbStart); + /** Number of chunks in this slab. */ + uint16_t cChunks; + /** Number of currently free chunks. */ + uint16_t cFreeChunks; + /** The chunk size. */ + uint16_t cbChunk; + /** The shift count corresponding to cbChunk. + * This is for turning a chunk number into a byte offset and vice versa. */ + uint16_t cChunkShift; + /** Bitmap where set bits indicates allocated blocks (variable size, + * multiple of 4). */ + uint8_t bmAllocated[4]; +} BS3SLABCTL; +/** Pointer to a bs3kit slab control structure. */ +typedef BS3SLABCTL BS3_FAR *PBS3SLABCTL; + +/** The chunks must all be in the same 16-bit segment tile. */ +#define BS3_SLAB_ALLOC_F_SAME_TILE UINT16_C(0x0001) + +/** + * Initializes a slab. + * + * @param pSlabCtl The slab control structure to initialize. + * @param cbSlabCtl The size of the slab control structure. + * @param uFlatSlabPtr The base address of the slab. + * @param cbSlab The size of the slab. + * @param cbChunk The chunk size. + */ +BS3_CMN_PROTO_STUB(void, Bs3SlabInit,(PBS3SLABCTL pSlabCtl, size_t cbSlabCtl, uint32_t uFlatSlabPtr, + uint32_t cbSlab, uint16_t cbChunk)); + +/** + * Allocates one chunk from a slab. + * + * @returns Pointer to a chunk on success, NULL if we're out of chunks. + * @param pSlabCtl The slab control structure to allocate from. + */ +BS3_CMN_PROTO_STUB(void BS3_FAR *, Bs3SlabAlloc,(PBS3SLABCTL pSlabCtl)); + +/** + * Allocates one or more chunks rom a slab. + * + * @returns Pointer to the request number of chunks on success, NULL if we're + * out of chunks. + * @param pSlabCtl The slab control structure to allocate from. + * @param cChunks The number of contiguous chunks we want. + * @param fFlags Flags, see BS3_SLAB_ALLOC_F_XXX + */ +BS3_CMN_PROTO_STUB(void BS3_FAR *, Bs3SlabAllocEx,(PBS3SLABCTL pSlabCtl, uint16_t cChunks, uint16_t fFlags)); + +/** + * Frees one or more chunks from a slab. + * + * @returns Number of chunks actually freed. When correctly used, this will + * match the @a cChunks parameter, of course. + * @param pSlabCtl The slab control structure to free from. + * @param uFlatChunkPtr The flat address of the chunks to free. + * @param cChunks The number of contiguous chunks to free. + */ +BS3_CMN_PROTO_STUB(uint16_t, Bs3SlabFree,(PBS3SLABCTL pSlabCtl, uint32_t uFlatChunkPtr, uint16_t cChunks)); + + +/** + * Initializes the given slab list head. + * + * @param pHead The slab list head. + * @param cbChunk The chunk size. + */ +BS3_CMN_PROTO_STUB(void, Bs3SlabListInit,(PBS3SLABHEAD pHead, uint16_t cbChunk)); + +/** + * Adds an initialized slab control structure to the list. + * + * @param pHead The slab list head to add it to. + * @param pSlabCtl The slab control structure to add. + */ +BS3_CMN_PROTO_STUB(void, Bs3SlabListAdd,(PBS3SLABHEAD pHead, PBS3SLABCTL pSlabCtl)); + +/** + * Allocates one chunk. + * + * @returns Pointer to a chunk on success, NULL if we're out of chunks. + * @param pHead The slab list to allocate from. + */ +BS3_CMN_PROTO_STUB(void BS3_FAR *, Bs3SlabListAlloc,(PBS3SLABHEAD pHead)); + +/** + * Allocates one or more chunks. + * + * @returns Pointer to the request number of chunks on success, NULL if we're + * out of chunks. + * @param pHead The slab list to allocate from. + * @param cChunks The number of contiguous chunks we want. + * @param fFlags Flags, see BS3_SLAB_ALLOC_F_XXX + */ +BS3_CMN_PROTO_STUB(void BS3_FAR *, Bs3SlabListAllocEx,(PBS3SLABHEAD pHead, uint16_t cChunks, uint16_t fFlags)); + +/** + * Frees one or more chunks from a slab list. + * + * @param pHead The slab list to allocate from. + * @param pvChunks Pointer to the first chunk to free. + * @param cChunks The number of contiguous chunks to free. + */ +BS3_CMN_PROTO_STUB(void, Bs3SlabListFree,(PBS3SLABHEAD pHead, void BS3_FAR *pvChunks, uint16_t cChunks)); + +/** + * Allocation addressing constraints. + */ +typedef enum BS3MEMKIND +{ + /** Invalid zero type. */ + BS3MEMKIND_INVALID = 0, + /** Real mode addressable memory. */ + BS3MEMKIND_REAL, + /** Memory addressable using the 16-bit protected mode tiling. */ + BS3MEMKIND_TILED, + /** Memory addressable using 32-bit flat addressing. */ + BS3MEMKIND_FLAT32, + /** Memory addressable using 64-bit flat addressing. */ + BS3MEMKIND_FLAT64, + /** End of valid types. */ + BS3MEMKIND_END, +} BS3MEMKIND; + +/** + * Allocates low memory. + * + * @returns Pointer to a chunk on success, NULL if we're out of chunks. + * @param enmKind The kind of addressing constraints imposed on the + * allocation. + * @param cb How much to allocate. + */ +BS3_CMN_PROTO_STUB(void BS3_FAR *, Bs3MemAlloc,(BS3MEMKIND enmKind, size_t cb)); + +/** + * Allocates zero'ed memory. + * + * @param enmKind The kind of addressing constraints imposed on the + * allocation. + * @param cb How much to allocate. + */ +BS3_CMN_PROTO_STUB(void BS3_FAR *, Bs3MemAllocZ,(BS3MEMKIND enmKind, size_t cb)); + +/** + * Frees memory. + * + * @returns Pointer to a chunk on success, NULL if we're out of chunks. + * @param pv The memory to free (returned by #Bs3MemAlloc). + * @param cb The size of the allocation. + */ +BS3_CMN_PROTO_STUB(void, Bs3MemFree,(void BS3_FAR *pv, size_t cb)); + +/** + * Allocates a page with non-present pages on each side. + * + * @returns Pointer to the usable page. NULL on failure. Use + * Bs3MemGuardedTestPageFree to free the allocation. + * @param enmKind The kind of addressing constraints imposed on the + * allocation. + */ +BS3_CMN_PROTO_STUB(void BS3_FAR *, Bs3MemGuardedTestPageAlloc,(BS3MEMKIND enmKind)); + +/** + * Allocates a page with pages on each side to the @a fPte specification. + * + * @returns Pointer to the usable page. NULL on failure. Use + * Bs3MemGuardedTestPageFree to free the allocation. + * @param enmKind The kind of addressing constraints imposed on the + * allocation. + * @param fPte The page table entry specification for the guard pages. + */ +BS3_CMN_PROTO_STUB(void BS3_FAR *, Bs3MemGuardedTestPageAllocEx,(BS3MEMKIND enmKind, uint64_t fPte)); + +/** + * Frees guarded page allocated by Bs3MemGuardedTestPageAlloc or + * Bs3MemGuardedTestPageAllocEx. + * + * @param pvGuardedPage Pointer returned by Bs3MemGuardedTestPageAlloc or + * Bs3MemGuardedTestPageAllocEx. NULL is ignored. + */ +BS3_CMN_PROTO_STUB(void, Bs3MemGuardedTestPageFree,(void BS3_FAR *pvGuardedPage)); + +/** + * Print all heap info. + */ +BS3_CMN_PROTO_STUB(void, Bs3MemPrintInfo, (void)); + +/** The end RAM address below 4GB (approximately). */ +extern uint32_t g_uBs3EndOfRamBelow4G; +/** The end RAM address above 4GB, zero if no memory above 4GB. */ +extern uint64_t g_uBs3EndOfRamAbove4G; + + +/** + * Enables the A20 gate. + */ +BS3_CMN_PROTO_NOSB(void, Bs3A20Enable,(void)); + +/** + * Enables the A20 gate via the keyboard controller + */ +BS3_CMN_PROTO_NOSB(void, Bs3A20EnableViaKbd,(void)); + +/** + * Enables the A20 gate via the PS/2 control port A. + */ +BS3_CMN_PROTO_NOSB(void, Bs3A20EnableViaPortA,(void)); + +/** + * Disables the A20 gate. + */ +BS3_CMN_PROTO_NOSB(void, Bs3A20Disable,(void)); + +/** + * Disables the A20 gate via the keyboard controller + */ +BS3_CMN_PROTO_NOSB(void, Bs3A20DisableViaKbd,(void)); + +/** + * Disables the A20 gate via the PS/2 control port A. + */ +BS3_CMN_PROTO_NOSB(void, Bs3A20DisableViaPortA,(void)); + + +/** + * Initializes root page tables for page protected mode (PP16, PP32). + * + * @returns IPRT status code. + * @remarks Must not be called in real-mode! + */ +BS3_CMN_PROTO_STUB(int, Bs3PagingInitRootForPP,(void)); + +/** + * Initializes root page tables for PAE page protected mode (PAE16, PAE32). + * + * @returns IPRT status code. + * @remarks The default long mode page tables depends on the PAE ones. + * @remarks Must not be called in real-mode! + */ +BS3_CMN_PROTO_STUB(int, Bs3PagingInitRootForPAE,(void)); + +/** + * Initializes root page tables for long mode (LM16, LM32, LM64). + * + * @returns IPRT status code. + * @remarks The default long mode page tables depends on the PAE ones. + * @remarks Must not be called in real-mode! + */ +BS3_CMN_PROTO_STUB(int, Bs3PagingInitRootForLM,(void)); + +/** + * Maps all RAM above 4GB into the long mode page tables. + * + * This requires Bs3PagingInitRootForLM to have been called first. + * + * @returns IPRT status code. + * @retval VERR_WRONG_ORDER if Bs3PagingInitRootForLM wasn't called. + * @retval VINF_ALREADY_INITIALIZED if already called or someone mapped + * something else above 4GiB already. + * @retval VERR_OUT_OF_RANGE if too much RAM (more than 2^47 bytes). + * @retval VERR_NO_MEMORY if no more memory for paging structures. + * @retval VERR_UNSUPPORTED_ALIGNMENT if the bs3kit allocator malfunctioned and + * didn't give us page aligned memory as it should. + * + * @param puFailurePoint Where to return the address where we encountered + * a failure. Optional. + * + * @remarks Must be called in 32-bit or 64-bit mode as paging structures will be + * allocated using BS3MEMKIND_FLAT32, as there might not be sufficient + * BS3MEMKIND_TILED memory around. (Also, too it's simply too much of + * a bother to deal with 16-bit for something that's long-mode only.) + */ +BS3_CMN_PROTO_STUB(int, Bs3PagingMapRamAbove4GForLM,(uint64_t *puFailurePoint)); + +/** + * Modifies the page table protection of an address range. + * + * This only works on the lowest level of the page tables in the current mode. + * + * Since we generally use the largest pages available when setting up the + * initial page tables, this function will usually have to allocate and create + * more tables. This may fail if we're low on memory. + * + * @returns IPRT status code. + * @param uFlat The flat address of the first page in the range (rounded + * down nearest page boundrary). + * @param cb The range size from @a pv (rounded up to nearest page boundrary). + * @param fSet Mask of zero or more X86_PTE_XXX values to set for the range. + * @param fClear Mask of zero or more X86_PTE_XXX values to clear for the range. + */ +BS3_CMN_PROTO_STUB(int, Bs3PagingProtect,(uint64_t uFlat, uint64_t cb, uint64_t fSet, uint64_t fClear)); + +/** + * Modifies the page table protection of an address range. + * + * This only works on the lowest level of the page tables in the current mode. + * + * Since we generally use the largest pages available when setting up the + * initial page tables, this function will usually have to allocate and create + * more tables. This may fail if we're low on memory. + * + * @returns IPRT status code. + * @param pv The address of the first page in the range (rounded + * down nearest page boundrary). + * @param cb The range size from @a pv (rounded up to nearest page boundrary). + * @param fSet Mask of zero or more X86_PTE_XXX values to set for the range. + * @param fClear Mask of zero or more X86_PTE_XXX values to clear for the range. + */ +BS3_CMN_PROTO_STUB(int, Bs3PagingProtectPtr,(void BS3_FAR *pv, size_t cb, uint64_t fSet, uint64_t fClear)); + +/** + * Aliases (maps) one or more contiguous physical pages to a virtual range. + * + * @returns VBox status code. + * @retval VERR_INVALID_PARAMETER if we're in legacy paging mode and @a uDst or + * @a uPhysToAlias are not compatible with legacy paging. + * @retval VERR_OUT_OF_RANGE if we cannot traverse the page tables in this mode + * (typically real mode or v86, maybe 16-bit PE). + * @retval VERR_NO_MEMORY if we cannot allocate page tables for splitting up + * the necessary large pages. No aliasing was performed. + * + * @param uDst The virtual address to map it at. Rounded down + * to the nearest page (@a cbHowMuch is adjusted + * up). + * @param uPhysToAlias The physical address of the first page in the + * (contiguous) range to map. Chopped down to + * nearest page boundrary (@a cbHowMuch is not + * adjusted). + * @param cbHowMuch How much to map. Rounded up to nearest page. + * @param fPte The PTE flags. + */ +BS3_CMN_PROTO_STUB(int, Bs3PagingAlias,(uint64_t uDst, uint64_t uPhysToAlias, uint32_t cbHowMuch, uint64_t fPte)); + +/** + * Unaliases memory, i.e. restores the 1:1 mapping. + * + * @returns VBox status code. Cannot fail if @a uDst and @a cbHowMuch specify + * the range of a successful Bs3PagingAlias call, however it may run + * out of memory if it's breaking new ground. + * + * @param uDst The virtual address to restore to 1:1 mapping. + * Rounded down to the nearest page (@a cbHowMuch + * is adjusted up). + * @param cbHowMuch How much to restore. Rounded up to nearest page. + */ +BS3_CMN_PROTO_STUB(int, Bs3PagingUnalias,(uint64_t uDst, uint32_t cbHowMuch)); + +/** + * Get the pointer to the PTE for the given address. + * + * @returns Pointer to the PTE. + * @param uFlat The flat address of the page which PTE we want. + * @param prc Where to return additional error info. Optional. + */ +BS3_CMN_PROTO_STUB(void BS3_FAR *, Bs3PagingGetPte,(uint64_t uFlat, int *prc)); + +/** + * Paging information for an address. + */ +typedef struct BS3PAGINGINFO4ADDR +{ + /** The depth of the system's paging mode. + * This is always 2 for legacy, 3 for PAE and 4 for long mode. */ + uint8_t cEntries; + /** The size of the page structures (the entires). */ + uint8_t cbEntry; + /** Flags defined for future fun, currently zero. */ + uint16_t fFlags; + /** Union display different view on the entry pointers. */ + union + { + /** Pointer to the page structure entries, starting with the PTE as 0. + * If large pages are involved, the first entry will be NULL (first two if 1GB + * page). Same if the address is invalid on a higher level. */ + uint8_t BS3_FAR *apbEntries[4]; + /** Alternative view for legacy mode. */ + struct + { + X86PTE BS3_FAR *pPte; + X86PDE BS3_FAR *pPde; + void *pvUnused2; + void *pvUnused3; + } Legacy; + /** Alternative view for PAE and Long mode. */ + struct + { + X86PTEPAE BS3_FAR *pPte; + X86PDEPAE BS3_FAR *pPde; + X86PDPE BS3_FAR *pPdpe; + X86PML4E BS3_FAR *pPml4e; + } Pae; + } u; +} BS3PAGINGINFO4ADDR; +/** Pointer to paging information for and address. */ +typedef BS3PAGINGINFO4ADDR BS3_FAR *PBS3PAGINGINFO4ADDR; + +/** + * Queries paging information about the given virtual address. + * + * @returns VBox status code. + * @param uFlat The flat address to query information about. + * @param pPgInfo Where to return the information. + */ +BS3_CMN_PROTO_STUB(int, Bs3PagingQueryAddressInfo,(uint64_t uFlat, PBS3PAGINGINFO4ADDR pPgInfo)); + + +/** The physical / flat address of the buffer backing the canonical traps. + * This buffer is spread equally on each side of the 64-bit non-canonical + * address divide. Non-64-bit code can use this to setup trick shots and + * inspect their results. */ +extern uint32_t g_uBs3PagingCanonicalTrapsAddr; +/** The size of the buffer at g_uPagingCanonicalTraps (both sides). */ +extern uint16_t g_cbBs3PagingCanonicalTraps; +/** The size of one trap buffer (low or high). + * This is g_cbBs3PagingCanonicalTraps divided by two. */ +extern uint16_t g_cbBs3PagingOneCanonicalTrap; + +/** + * Sets up the 64-bit canonical address space trap buffers, if neceessary. + * + * @returns Pointer to the buffers (i.e. the first page of the low one) on + * success. NULL on failure. + */ +BS3_CMN_PROTO_STUB(void BS3_FAR *, Bs3PagingSetupCanonicalTraps,(void)); + +/** + * Waits for the keyboard controller to become ready. + */ +BS3_CMN_PROTO_NOSB(void, Bs3KbdWait,(void)); + +/** + * Sends a read command to the keyboard controller and gets the result. + * + * The caller is responsible for making sure the keyboard controller is ready + * for a command (call #Bs3KbdWait if unsure). + * + * @returns The value read is returned (in al). + * @param bCmd The read command. + */ +BS3_CMN_PROTO_NOSB(uint8_t, Bs3KbdRead,(uint8_t bCmd)); + +/** + * Sends a write command to the keyboard controller and then sends the data. + * + * The caller is responsible for making sure the keyboard controller is ready + * for a command (call #Bs3KbdWait if unsure). + * + * @param bCmd The write command. + * @param bData The data to write. + */ +BS3_CMN_PROTO_NOSB(void, Bs3KbdWrite,(uint8_t bCmd, uint8_t bData)); + + +/** + * Configures the PIC, once only. + * + * Subsequent calls to this function will not do anything. + * + * The PIC will be programmed to use IDT/IVT vectors 0x70 thru 0x7f, auto + * end-of-interrupt, and all IRQs masked. The individual PIC users will have to + * use #Bs3PicUpdateMask unmask their IRQ once they've got all the handlers + * installed. + * + * @param fForcedReInit Force a reinitialization. + */ +BS3_CMN_PROTO_STUB(void, Bs3PicSetup,(bool fForcedReInit)); + +/** + * Updates the PIC masks. + * + * @returns The new mask - master in low, slave in high byte. + * @param fAndMask Things to keep as-is. Master in low, slave in high byte. + * @param fOrMask Things to start masking. Ditto wrt bytes. + */ +BS3_CMN_PROTO_STUB(uint16_t, Bs3PicUpdateMask,(uint16_t fAndMask, uint16_t fOrMask)); + +/** + * Disables all IRQs on the PIC. + */ +BS3_CMN_PROTO_STUB(void, Bs3PicMaskAll,(void)); + + +/** + * Sets up the PIT for periodic callback. + * + * @param cHzDesired The desired Hz. Zero means max interval length + * (18.2Hz). Plase check the various PIT globals for + * the actual interval length. + */ +BS3_CMN_PROTO_STUB(void, Bs3PitSetupAndEnablePeriodTimer,(uint16_t cHzDesired)); + +/** + * Disables the PIT if active. + */ +BS3_CMN_PROTO_STUB(void, Bs3PitDisable,(void)); + +/** Nanoseconds (approx) since last the PIT timer was started. */ +extern uint64_t volatile g_cBs3PitNs; +/** Milliseconds seconds (very approx) since last the PIT timer was started. */ +extern uint64_t volatile g_cBs3PitMs; +/** Number of ticks since last the PIT timer was started. */ +extern uint32_t volatile g_cBs3PitTicks; +/** The current interval in nanoseconds. + * This is 0 if not yet started (cleared by Bs3PitDisable). */ +extern uint32_t g_cBs3PitIntervalNs; +/** The current interval in milliseconds (approximately). + * This is 0 if not yet started (cleared by Bs3PitDisable). */ +extern uint16_t g_cBs3PitIntervalMs; +/** The current PIT frequency (approximately). + * 0 if not yet started (cleared by Bs3PitDisable; used for checking the + * state internally). */ +extern uint16_t volatile g_cBs3PitIntervalHz; + + +/** + * Call 16-bit prot mode function from v8086 mode. + * + * This switches from v8086 mode to 16-bit protected mode (code) and executed + * @a fpfnCall with @a cbParams bytes of parameters pushed on the stack. + * Afterwards it switches back to v8086 mode and returns a 16-bit status code. + * + * @returns 16-bit status code if the function returned anything. + * @param fpfnCall Far real mode pointer to the function to call. + * @param cbParams The size of the parameter list, in bytes. + * @param ... The parameters. + * @sa Bs3SwitchTo32BitAndCallC + */ +BS3_CMN_PROTO_STUB(int, Bs3SwitchFromV86To16BitAndCallC,(FPFNBS3FAR fpfnCall, unsigned cbParams, ...)); + + +/** + * BS3 integer register. + */ +typedef union BS3REG +{ + /** 8-bit unsigned integer. */ + uint8_t u8; + /** 16-bit unsigned integer. */ + uint16_t u16; + /** 32-bit unsigned integer. */ + uint32_t u32; + /** 64-bit unsigned integer. */ + uint64_t u64; + /** Full unsigned integer. */ + uint64_t u; + /** High/low byte view. */ + struct + { + uint8_t bLo; + uint8_t bHi; + } b; + /** 8-bit view. */ + uint8_t au8[8]; + /** 16-bit view. */ + uint16_t au16[4]; + /** 32-bit view. */ + uint32_t au32[2]; + /** Unsigned integer, depending on compiler context. + * This generally follows ARCH_BITS. */ + RTCCUINTREG uCcReg; + /** Extended unsigned integer, depending on compiler context. + * This is 32-bit in 16-bit and 32-bit compiler contexts, and 64-bit in + * 64-bit. */ + RTCCUINTXREG uCcXReg; +} BS3REG; +/** Pointer to an integer register. */ +typedef BS3REG BS3_FAR *PBS3REG; +/** Pointer to a const integer register. */ +typedef BS3REG const BS3_FAR *PCBS3REG; + +/** + * Register context (without FPU). + */ +typedef struct BS3REGCTX +{ + BS3REG rax; /**< 0x00 */ + BS3REG rcx; /**< 0x08 */ + BS3REG rdx; /**< 0x10 */ + BS3REG rbx; /**< 0x18 */ + BS3REG rsp; /**< 0x20 */ + BS3REG rbp; /**< 0x28 */ + BS3REG rsi; /**< 0x30 */ + BS3REG rdi; /**< 0x38 */ + BS3REG r8; /**< 0x40 */ + BS3REG r9; /**< 0x48 */ + BS3REG r10; /**< 0x50 */ + BS3REG r11; /**< 0x58 */ + BS3REG r12; /**< 0x60 */ + BS3REG r13; /**< 0x68 */ + BS3REG r14; /**< 0x70 */ + BS3REG r15; /**< 0x78 */ + BS3REG rflags; /**< 0x80 */ + BS3REG rip; /**< 0x88 */ + uint16_t cs; /**< 0x90 */ + uint16_t ds; /**< 0x92 */ + uint16_t es; /**< 0x94 */ + uint16_t fs; /**< 0x96 */ + uint16_t gs; /**< 0x98 */ + uint16_t ss; /**< 0x9a */ + uint16_t tr; /**< 0x9c */ + uint16_t ldtr; /**< 0x9e */ + uint8_t bMode; /**< 0xa0: BS3_MODE_XXX. */ + uint8_t bCpl; /**< 0xa1: 0-3, 0 is used for real mode. */ + uint8_t fbFlags; /**< 0xa2: BS3REG_CTX_F_XXX */ + uint8_t abPadding[5]; /**< 0xa3 */ + BS3REG cr0; /**< 0xa8 */ + BS3REG cr2; /**< 0xb0 */ + BS3REG cr3; /**< 0xb8 */ + BS3REG cr4; /**< 0xc0 */ + uint64_t uUnused; /**< 0xc8 */ +} BS3REGCTX; +AssertCompileSize(BS3REGCTX, 0xd0); +/** Pointer to a register context. */ +typedef BS3REGCTX BS3_FAR *PBS3REGCTX; +/** Pointer to a const register context. */ +typedef BS3REGCTX const BS3_FAR *PCBS3REGCTX; + +/** @name BS3REG_CTX_F_XXX - BS3REGCTX::fbFlags masks. + * @{ */ +/** The CR0 is MSW (only low 16-bit). */ +#define BS3REG_CTX_F_NO_CR0_IS_MSW UINT8_C(0x01) +/** No CR2 and CR3 values. Not in CPL 0 or CPU too old for CR2 & CR3. */ +#define BS3REG_CTX_F_NO_CR2_CR3 UINT8_C(0x02) +/** No CR4 value. The CPU is too old for CR4. */ +#define BS3REG_CTX_F_NO_CR4 UINT8_C(0x04) +/** No TR and LDTR values. Context gathered in real mode or v8086 mode. */ +#define BS3REG_CTX_F_NO_TR_LDTR UINT8_C(0x08) +/** The context doesn't have valid values for AMD64 GPR extensions. */ +#define BS3REG_CTX_F_NO_AMD64 UINT8_C(0x10) +/** @} */ + +/** + * Saves the current register context. + * + * @param pRegCtx Where to store the register context. + */ +BS3_CMN_PROTO_NOSB(void, Bs3RegCtxSave,(PBS3REGCTX pRegCtx)); + +/** + * Switch to the specified CPU bitcount, reserve additional stack and save the + * CPU context. + * + * This is for writing more flexible test drivers that can test more than the + * CPU bitcount (16-bit, 32-bit, 64-bit, and virtual 8086) of the driver itself. + * For instance a 32-bit driver can do V86 and 16-bit testing, thus saving space + * by avoiding duplicate 16-bit driver code. + * + * @param pRegCtx Where to store the register context. + * @param bBitMode Bit mode to switch to, BS3_MODE_CODE_XXX. Only + * BS3_MODE_CODE_MASK is used, other bits are ignored + * to make it possible to pass a full mode value. + * @param cbExtraStack Number of bytes of additional stack to allocate. + */ +BS3_CMN_PROTO_FARSTUB(8, void, Bs3RegCtxSaveEx,(PBS3REGCTX pRegCtx, uint8_t bBitMode, uint16_t cbExtraStack)); + +/** + * This is Bs3RegCtxSaveEx with automatic Bs3RegCtxConvertV86ToRm thrown in. + * + * This is for simplifying writing 32-bit test drivers that covers real-mode as + * well as virtual 8086, 16-bit, 32-bit, and 64-bit modes. + * + * @param pRegCtx Where to store the register context. + * @param bMode The mode to get a context for. If this isn't + * BS3_MODE_RM, the BS3_MODE_SYS_MASK has to match the + * one of the current mode. + * @param cbExtraStack Number of bytes of additional stack to allocate. + */ +BS3_CMN_PROTO_STUB(void, Bs3RegCtxSaveForMode,(PBS3REGCTX pRegCtx, uint8_t bMode, uint16_t cbExtraStack)); + +/** + * Transforms a register context to a different ring. + * + * @param pRegCtx The register context. + * @param bRing The target ring (0..3). + * + * @note Do _NOT_ call this for creating real mode or v8086 contexts, because + * it will always output a protected mode context! + */ +BS3_CMN_PROTO_STUB(void, Bs3RegCtxConvertToRingX,(PBS3REGCTX pRegCtx, uint8_t bRing)); + +/** + * Transforms a V8086 register context to a real mode one. + * + * @param pRegCtx The register context. + * + * @note Will assert if called on a non-V8086 context. + */ +BS3_CMN_PROTO_STUB(void, Bs3RegCtxConvertV86ToRm,(PBS3REGCTX pRegCtx)); + +/** + * Restores a register context. + * + * @param pRegCtx The register context to be restored and resumed. + * @param fFlags BS3REGCTXRESTORE_F_XXX. + * + * @remarks Will switch to ring-0. + * @remarks Does not return. + */ +BS3_CMN_PROTO_NOSB(DECL_NO_RETURN(void), Bs3RegCtxRestore,(PCBS3REGCTX pRegCtx, uint16_t fFlags)); +#if !defined(BS3_KIT_WITH_NO_RETURN) && defined(__WATCOMC__) +# pragma aux Bs3RegCtxRestore_c16 "_Bs3RegCtxRestore_aborts_c16" __aborts +# pragma aux Bs3RegCtxRestore_f16 "_Bs3RegCtxRestore_aborts_f16" __aborts +# pragma aux Bs3RegCtxRestore_c32 "_Bs3RegCtxRestore_aborts_c32" __aborts +#endif + +/** @name Flags for Bs3RegCtxRestore + * @{ */ +/** Skip restoring the CRx registers. */ +#define BS3REGCTXRESTORE_F_SKIP_CRX UINT16_C(0x0001) +/** Sets g_fBs3TrapNoV86Assist. */ +#define BS3REGCTXRESTORE_F_NO_V86_ASSIST UINT16_C(0x0002) +/** @} */ + +/** + * Prints the register context. + * + * @param pRegCtx The register context to be printed. + */ +BS3_CMN_PROTO_STUB(void, Bs3RegCtxPrint,(PCBS3REGCTX pRegCtx)); + +/** + * Sets a GPR and segment register to point at the same location as @a uFlat. + * + * @param pRegCtx The register context. + * @param pGpr The general purpose register to set (points within + * @a pRegCtx). + * @param pSel The selector register (points within @a pRegCtx). + * @param uFlat Flat location address. + */ +BS3_CMN_PROTO_STUB(void, Bs3RegCtxSetGrpSegFromFlat,(PBS3REGCTX pRegCtx, PBS3REG pGpr, PRTSEL pSel, RTCCUINTXREG uFlat)); + +/** + * Sets a GPR and segment register to point at the same location as @a ovPtr. + * + * @param pRegCtx The register context. + * @param pGpr The general purpose register to set (points within + * @a pRegCtx). + * @param pSel The selector register (points within @a pRegCtx). + * @param pvPtr Current context pointer. + */ +BS3_CMN_PROTO_STUB(void, Bs3RegCtxSetGrpSegFromCurPtr,(PBS3REGCTX pRegCtx, PBS3REG pGpr, PRTSEL pSel, void BS3_FAR *pvPtr)); + +/** + * Sets a GPR and DS to point at the same location as @a pvPtr. + * + * @param pRegCtx The register context. + * @param pGpr The general purpose register to set (points within + * @a pRegCtx). + * @param pvPtr Current context pointer. + */ +BS3_CMN_PROTO_STUB(void, Bs3RegCtxSetGrpDsFromCurPtr,(PBS3REGCTX pRegCtx, PBS3REG pGpr, void BS3_FAR *pvPtr)); + +/** + * Sets CS:RIP to point at the same piece of code as @a uFlatCode. + * + * @param pRegCtx The register context. + * @param uFlatCode Flat code pointer + * @sa Bs3RegCtxSetRipCsFromLnkPtr, Bs3RegCtxSetRipCsFromCurPtr + */ +BS3_CMN_PROTO_STUB(void, Bs3RegCtxSetRipCsFromFlat,(PBS3REGCTX pRegCtx, RTCCUINTXREG uFlatCode)); + +/** + * Sets CS:RIP to point at the same piece of code as @a pfnCode. + * + * The 16-bit edition of this function expects a far 16:16 address as written by + * the linker (i.e. real mode). + * + * @param pRegCtx The register context. + * @param pfnCode Pointer to the code. In 32-bit and 64-bit mode this is a + * flat address, while in 16-bit it's a far 16:16 address + * as fixed up by the linker (real mode selector). This + * address is converted to match the mode of the context. + * @sa Bs3RegCtxSetRipCsFromCurPtr, Bs3RegCtxSetRipCsFromFlat + */ +BS3_CMN_PROTO_STUB(void, Bs3RegCtxSetRipCsFromLnkPtr,(PBS3REGCTX pRegCtx, FPFNBS3FAR pfnCode)); + +/** + * Sets CS:RIP to point at the same piece of code as @a pfnCode. + * + * @param pRegCtx The register context. + * @param pfnCode Pointer to the code. Current mode pointer. + * @sa Bs3RegCtxSetRipCsFromLnkPtr, Bs3RegCtxSetRipCsFromFlat + */ +BS3_CMN_PROTO_STUB(void, Bs3RegCtxSetRipCsFromCurPtr,(PBS3REGCTX pRegCtx, FPFNBS3FAR pfnCode)); + +/** + * Sets a GPR by number. + * + * @return true if @a iGpr is valid, false if not. + * @param pRegCtx The register context. + * @param iGpr The GPR number. + * @param uValue The new value. + * @param cbValue The size of the value: 1, 2, 4 or 8. + */ +BS3_CMN_PROTO_STUB(bool, Bs3RegCtxSetGpr,(PBS3REGCTX pRegCtx, uint8_t iGpr, uint64_t uValue, uint8_t cb)); + +/** + * Gets the stack pointer as a current context pointer. + * + * @return Pointer to the top of the stack. NULL on failure. + * @param pRegCtx The register context. + */ +BS3_CMN_PROTO_STUB(void BS3_FAR *, Bs3RegCtxGetRspSsAsCurPtr,(PBS3REGCTX pRegCtx)); + + +/** + * The method to be used to save and restore the extended context. + */ +typedef enum BS3EXTCTXMETHOD +{ + BS3EXTCTXMETHOD_INVALID = 0, + BS3EXTCTXMETHOD_ANCIENT, /**< Ancient fnsave/frstor format. */ + BS3EXTCTXMETHOD_FXSAVE, /**< fxsave/fxrstor format. */ + BS3EXTCTXMETHOD_XSAVE, /**< xsave/xrstor format. */ + BS3EXTCTXMETHOD_END, +} BS3EXTCTXMETHOD; + + +/** + * Extended CPU context (FPU, SSE, AVX, ++). + * + * @remarks Also in bs3kit.inc + */ +typedef struct BS3EXTCTX +{ + /** Dummy/magic value. */ + uint16_t u16Magic; + /** The size of the structure. */ + uint16_t cb; + /** The method used to save and restore the context (BS3EXTCTXMETHOD). */ + uint8_t enmMethod; + uint8_t abPadding0[3]; + /** Nominal XSAVE_C_XXX. */ + uint64_t fXcr0Nominal; + /** The saved XCR0 mask (restored after xrstor). */ + uint64_t fXcr0Saved; + + /** Explicit alignment padding. */ + uint8_t abPadding[64 - 2 - 2 - 1 - 3 - 8 - 8]; + + /** The context, variable size (see above). + * This must be aligned on a 64 byte boundrary. */ + union + { + /** fnsave/frstor. */ + X86FPUSTATE Ancient; + /** fxsave/fxrstor */ + X86FXSTATE x87; + /** xsave/xrstor */ + X86XSAVEAREA x; + /** Byte array view. */ + uint8_t ab[sizeof(X86XSAVEAREA)]; + } Ctx; +} BS3EXTCTX; +AssertCompileMemberAlignment(BS3EXTCTX, Ctx, 64); +/** Pointer to an extended CPU context. */ +typedef BS3EXTCTX BS3_FAR *PBS3EXTCTX; +/** Pointer to a const extended CPU context. */ +typedef BS3EXTCTX const BS3_FAR *PCBS3EXTCTX; + +/** Magic value for BS3EXTCTX. */ +#define BS3EXTCTX_MAGIC UINT16_C(0x1980) + +/** + * Allocates and initializes the extended CPU context structure. + * + * @returns The new extended CPU context structure. + * @param enmKind The kind of allocation to make. + */ +BS3_CMN_PROTO_STUB(PBS3EXTCTX, Bs3ExtCtxAlloc,(BS3MEMKIND enmKind)); + +/** + * Frees an extended CPU context structure. + * + * @param pExtCtx The extended CPU context (returned by + * Bs3ExtCtxAlloc). + */ +BS3_CMN_PROTO_STUB(void, Bs3ExtCtxFree,(PBS3EXTCTX pExtCtx)); + +/** + * Get the size required for a BS3EXTCTX structure. + * + * @returns size in bytes of the whole structure. + * @param pfFlags Where to return flags for Bs3ExtCtxInit. + * @note Use Bs3ExtCtxAlloc when possible. + */ +BS3_CMN_PROTO_STUB(uint16_t, Bs3ExtCtxGetSize,(uint64_t *pfFlags)); + +/** + * Initializes the extended CPU context structure. + * @returns pExtCtx + * @param pExtCtx The extended CPU context. + * @param cbExtCtx The size of the @a pExtCtx allocation. + * @param fFlags XSAVE_C_XXX flags. + */ +BS3_CMN_PROTO_STUB(PBS3EXTCTX, Bs3ExtCtxInit,(PBS3EXTCTX pExtCtx, uint16_t cbExtCtx, uint64_t fFlags)); + +/** + * Saves the extended CPU state to the given structure. + * + * @param pExtCtx The extended CPU context. + * @remarks All GPRs preserved. + */ +BS3_CMN_PROTO_FARSTUB(4, void, Bs3ExtCtxSave,(PBS3EXTCTX pExtCtx)); + +/** + * Saves the extended CPU state to the given structure, when in long mode this + * is done from 64-bit mode to capture YMM8 thru YMM15. + * + * This is for testing 64-bit code from a 32-bit test driver. + * + * @param pExtCtx The extended CPU context. + * @note Only safe to call from ring-0 at present. + * @remarks All GPRs preserved. + * @sa Bs3ExtCtxRestoreEx + */ +BS3_CMN_PROTO_FARSTUB(4, void, Bs3ExtCtxSaveEx,(PBS3EXTCTX pExtCtx)); + +/** + * Restores the extended CPU state from the given structure. + * + * @param pExtCtx The extended CPU context. + * @remarks All GPRs preserved. + */ +BS3_CMN_PROTO_FARSTUB(4, void, Bs3ExtCtxRestore,(PCBS3EXTCTX pExtCtx)); + +/** + * Restores the extended CPU state from the given structure and in long mode + * switch to 64-bit mode to do this so YMM8-YMM15 are also loaded. + * + * This is for testing 64-bit code from a 32-bit test driver. + * + * @param pExtCtx The extended CPU context. + * @note Only safe to call from ring-0 at present. + * @remarks All GPRs preserved. + * @sa Bs3ExtCtxSaveEx + */ +BS3_CMN_PROTO_FARSTUB(4, void, Bs3ExtCtxRestoreEx,(PCBS3EXTCTX pExtCtx)); + +/** + * Copies the state from one context to another. + * + * @returns pDst + * @param pDst The destination extended CPU context. + * @param pSrc The source extended CPU context. + */ +BS3_CMN_PROTO_STUB(PBS3EXTCTX, Bs3ExtCtxCopy,(PBS3EXTCTX pDst, PCBS3EXTCTX pSrc)); + +/** + * Gets the FCW register value from @a pExtCtx. + * + * @returns FCW value. + * @param pExtCtx The extended CPU context. + */ +BS3_CMN_PROTO_STUB(uint16_t, Bs3ExtCtxGetFcw,(PCBS3EXTCTX pExtCtx)); + +/** + * Sets the FCW register value in @a pExtCtx. + * + * @param pExtCtx The extended CPU context. + * @param uValue The new FCW value. + */ +BS3_CMN_PROTO_STUB(void, Bs3ExtCtxSetFcw,(PBS3EXTCTX pExtCtx, uint16_t uValue)); + +/** + * Gets the FSW register value from @a pExtCtx. + * + * @returns FSW value. + * @param pExtCtx The extended CPU context. + */ +BS3_CMN_PROTO_STUB(uint16_t, Bs3ExtCtxGetFsw,(PCBS3EXTCTX pExtCtx)); + +/** + * Sets the FSW register value in @a pExtCtx. + * + * @param pExtCtx The extended CPU context. + * @param uValue The new FSW value. + */ +BS3_CMN_PROTO_STUB(void, Bs3ExtCtxSetFsw,(PBS3EXTCTX pExtCtx, uint16_t uValue)); + +/** + * Gets the abridged FTW register value from @a pExtCtx. + * + * @returns FTW value. + * @param pExtCtx The extended CPU context. + */ +BS3_CMN_PROTO_STUB(uint16_t, Bs3ExtCtxGetAbridgedFtw,(PCBS3EXTCTX pExtCtx)); + +/** + * Sets the abridged FTW register value in @a pExtCtx. + * + * Currently this requires that the state stores teh abridged FTW, no conversion + * to the two-bit variant will be attempted. + * + * @returns true if set successfully, false if not. + * @param pExtCtx The extended CPU context. + * @param uValue The new FTW value. + */ +BS3_CMN_PROTO_STUB(bool, Bs3ExtCtxSetAbridgedFtw,(PBS3EXTCTX pExtCtx, uint16_t uValue)); + +/** + * Gets the MXCSR register value from @a pExtCtx. + * + * @returns MXCSR value, 0 if not part of context. + * @param pExtCtx The extended CPU context. + */ +BS3_CMN_PROTO_STUB(uint32_t, Bs3ExtCtxGetMxCsr,(PCBS3EXTCTX pExtCtx)); + +/** + * Sets the MXCSR register value in @a pExtCtx. + * + * @returns true if set, false if not supported by the format. + * @param pExtCtx The extended CPU context. + * @param uValue The new MXCSR value. + */ +BS3_CMN_PROTO_STUB(bool, Bs3ExtCtxSetMxCsr,(PBS3EXTCTX pExtCtx, uint32_t uValue)); + +/** + * Gets the MXCSR MASK value from @a pExtCtx. + * + * @returns MXCSR MASK value, 0 if not part of context. + * @param pExtCtx The extended CPU context. + */ +BS3_CMN_PROTO_STUB(uint32_t, Bs3ExtCtxGetMxCsrMask,(PCBS3EXTCTX pExtCtx)); + +/** + * Sets the MXCSR MASK value in @a pExtCtx. + * + * @returns true if set, false if not supported by the format. + * @param pExtCtx The extended CPU context. + * @param uValue The new MXCSR MASK value. + */ +BS3_CMN_PROTO_STUB(bool, Bs3ExtCtxSetMxCsrMask,(PBS3EXTCTX pExtCtx, uint32_t uValue)); + +/** + * Gets the value of MM register number @a iReg from @a pExtCtx. + * + * @returns The MM register value. + * @param pExtCtx The extended CPU context. + * @param iReg The register to get (0 thru 7). + */ +BS3_CMN_PROTO_STUB(uint64_t, Bs3ExtCtxGetMm,(PCBS3EXTCTX pExtCtx, uint8_t iReg)); + +/** What to do about the 16-bit above the MM QWORD. */ +typedef enum BS3EXTCTXTOPMM +{ + /** Invalid zero value. */ + BS3EXTCTXTOPMM_INVALID = 0, + /** Set to 0FFFFh like real CPUs typically does when updating an MM register. */ + BS3EXTCTXTOPMM_SET, + /** Set to zero. */ + BS3EXTCTXTOPMM_ZERO, + /** Don't change the value, leaving it as-is. */ + BS3EXTCTXTOPMM_AS_IS, + /** End of valid values. */ + BS3EXTCTXTOPMM_END +} BS3EXTCTXTOPMM; + +/** + * Sets the value of YMM register number @a iReg in @a pExtCtx to @a pValue. + * + * @returns True if set, false if not. + * @param pExtCtx The extended CPU context. + * @param iReg The register to set. + * @param uValue The new register value. + * @param enmTop What to do about the 16-bit value above the MM + * QWord. + */ +BS3_CMN_PROTO_STUB(bool, Bs3ExtCtxSetMm,(PBS3EXTCTX pExtCtx, uint8_t iReg, uint64_t uValue, BS3EXTCTXTOPMM enmTop)); + +/** + * Gets the value of XMM register number @a iReg from @a pExtCtx. + * + * @returns pValue + * @param pExtCtx The extended CPU context. + * @param iReg The register to get. + * @param pValue Where to return the value. Zeroed if the state + * doesn't support SSE or if @a iReg is invalid. + */ +BS3_CMN_PROTO_STUB(PRTUINT128U, Bs3ExtCtxGetXmm,(PCBS3EXTCTX pExtCtx, uint8_t iReg, PRTUINT128U pValue)); + +/** + * Sets the value of XMM register number @a iReg in @a pExtCtx to @a pValue. + * + * @returns True if set, false if not set (not supported by state format or + * invalid iReg). + * @param pExtCtx The extended CPU context. + * @param iReg The register to set. + * @param pValue The new register value. + */ +BS3_CMN_PROTO_STUB(bool, Bs3ExtCtxSetXmm,(PBS3EXTCTX pExtCtx, uint8_t iReg, PCRTUINT128U pValue)); + +/** + * Gets the value of YMM register number @a iReg from @a pExtCtx. + * + * @returns pValue + * @param pExtCtx The extended CPU context. + * @param iReg The register to get. + * @param pValue Where to return the value. Parts not in the + * extended state are zeroed. For absent or invalid + * @a iReg values this is set to zero. + */ +BS3_CMN_PROTO_STUB(PRTUINT256U, Bs3ExtCtxGetYmm,(PCBS3EXTCTX pExtCtx, uint8_t iReg, PRTUINT256U pValue)); + +/** + * Sets the value of YMM register number @a iReg in @a pExtCtx to @a pValue. + * + * @returns true if set (even if only partially). False if not set (not + * supported by state format, unsupported/invalid iReg). + * @param pExtCtx The extended CPU context. + * @param iReg The register to set. + * @param pValue The new register value. + * @param cbValue Number of bytes to take from @a pValue, either 16 or + * 32. If 16, the high part will be zeroed when present + * in the state. + */ +BS3_CMN_PROTO_STUB(bool, Bs3ExtCtxSetYmm,(PBS3EXTCTX pExtCtx, uint8_t iReg, PCRTUINT256U pValue, uint8_t cbValue)); + + +/** @name Debug register accessors for V8086 mode (works everwhere). + * @{ */ +BS3_CMN_PROTO_NOSB(RTCCUINTXREG, Bs3RegGetDr0,(void)); +BS3_CMN_PROTO_NOSB(RTCCUINTXREG, Bs3RegGetDr1,(void)); +BS3_CMN_PROTO_NOSB(RTCCUINTXREG, Bs3RegGetDr2,(void)); +BS3_CMN_PROTO_NOSB(RTCCUINTXREG, Bs3RegGetDr3,(void)); +BS3_CMN_PROTO_NOSB(RTCCUINTXREG, Bs3RegGetDr6,(void)); +BS3_CMN_PROTO_NOSB(RTCCUINTXREG, Bs3RegGetDr7,(void)); + +BS3_CMN_PROTO_NOSB(void, Bs3RegSetDr0,(RTCCUINTXREG uValue)); +BS3_CMN_PROTO_NOSB(void, Bs3RegSetDr1,(RTCCUINTXREG uValue)); +BS3_CMN_PROTO_NOSB(void, Bs3RegSetDr2,(RTCCUINTXREG uValue)); +BS3_CMN_PROTO_NOSB(void, Bs3RegSetDr3,(RTCCUINTXREG uValue)); +BS3_CMN_PROTO_NOSB(void, Bs3RegSetDr6,(RTCCUINTXREG uValue)); +BS3_CMN_PROTO_NOSB(void, Bs3RegSetDr7,(RTCCUINTXREG uValue)); + +BS3_CMN_PROTO_NOSB(RTCCUINTXREG, Bs3RegGetDrX,(uint8_t iReg)); +BS3_CMN_PROTO_NOSB(void, Bs3RegSetDrX,(uint8_t iReg, RTCCUINTXREG uValue)); +/** @} */ + + +/** @name Control register accessors for V8086 mode (works everwhere). + * @{ */ +BS3_CMN_PROTO_NOSB(RTCCUINTXREG, Bs3RegGetCr0,(void)); +BS3_CMN_PROTO_NOSB(RTCCUINTXREG, Bs3RegGetCr2,(void)); +BS3_CMN_PROTO_NOSB(RTCCUINTXREG, Bs3RegGetCr3,(void)); +BS3_CMN_PROTO_NOSB(RTCCUINTXREG, Bs3RegGetCr4,(void)); +BS3_CMN_PROTO_NOSB(uint16_t, Bs3RegGetTr,(void)); +BS3_CMN_PROTO_NOSB(uint16_t, Bs3RegGetLdtr,(void)); +BS3_CMN_PROTO_NOSB(uint64_t, Bs3RegGetXcr0,(void)); + +BS3_CMN_PROTO_NOSB(void, Bs3RegSetCr0,(RTCCUINTXREG uValue)); +BS3_CMN_PROTO_NOSB(void, Bs3RegSetCr2,(RTCCUINTXREG uValue)); +BS3_CMN_PROTO_NOSB(void, Bs3RegSetCr3,(RTCCUINTXREG uValue)); +BS3_CMN_PROTO_NOSB(void, Bs3RegSetCr4,(RTCCUINTXREG uValue)); +BS3_CMN_PROTO_NOSB(void, Bs3RegSetTr,(uint16_t uValue)); +BS3_CMN_PROTO_NOSB(void, Bs3RegSetLdtr,(uint16_t uValue)); +BS3_CMN_PROTO_NOSB(void, Bs3RegSetXcr0,(uint64_t uValue)); +/** @} */ + + +/** + * Trap frame. + */ +typedef struct BS3TRAPFRAME +{ + /** 0x00: Exception/interrupt number. */ + uint8_t bXcpt; + /** 0x01: The size of the IRET frame. */ + uint8_t cbIretFrame; + /** 0x02: The handler CS. */ + uint16_t uHandlerCs; + /** 0x04: The handler SS. */ + uint16_t uHandlerSs; + /** 0x06: Explicit alignment. */ + uint16_t usAlignment; + /** 0x08: The handler RSP (pointer to the iret frame, skipping ErrCd). */ + uint64_t uHandlerRsp; + /** 0x10: The handler RFLAGS value. */ + uint64_t fHandlerRfl; + /** 0x18: The error code (if applicable). */ + uint64_t uErrCd; + /** 0x20: The register context. */ + BS3REGCTX Ctx; +} BS3TRAPFRAME; +AssertCompileSize(BS3TRAPFRAME, 0x20 + 0xd0); +/** Pointer to a trap frame. */ +typedef BS3TRAPFRAME BS3_FAR *PBS3TRAPFRAME; +/** Pointer to a const trap frame. */ +typedef BS3TRAPFRAME const BS3_FAR *PCBS3TRAPFRAME; + + +/** + * Re-initializes the trap handling for the current mode. + * + * Useful after a test that messes with the IDT/IVT. + * + * @sa Bs3TrapInit + */ +BS3_CMN_PROTO_STUB(void, Bs3TrapReInit,(void)); + +/** + * Initializes real mode and v8086 trap handling. + * + * @remarks Does not install RM/V86 trap handling, just initializes the + * structures. + */ +BS3_CMN_PROTO_STUB(void, Bs3TrapRmV86Init,(void)); + +/** + * Initializes real mode and v8086 trap handling, extended version. + * + * @param f386Plus Set if the CPU is 80386 or later and + * extended registers should be saved. Once initialized + * with this parameter set to @a true, the effect cannot be + * reversed. + * + * @remarks Does not install RM/V86 trap handling, just initializes the + * structures. + */ +BS3_CMN_PROTO_STUB(void, Bs3TrapRmV86InitEx,(bool f386Plus)); + +/** + * Initializes 16-bit (protected mode) trap handling. + * + * @remarks Does not install 16-bit trap handling, just initializes the + * structures. + */ +BS3_CMN_PROTO_STUB(void, Bs3Trap16Init,(void)); + +/** + * Initializes 16-bit (protected mode) trap handling, extended version. + * + * @param f386Plus Set if the CPU is 80386 or later and + * extended registers should be saved. Once initialized + * with this parameter set to @a true, the effect cannot be + * reversed. + * + * @remarks Does not install 16-bit trap handling, just initializes the + * structures. + */ +BS3_CMN_PROTO_STUB(void, Bs3Trap16InitEx,(bool f386Plus)); + +/** + * Initializes 32-bit trap handling. + * + * @remarks Does not install 32-bit trap handling, just initializes the + * structures. + */ +BS3_CMN_PROTO_STUB(void, Bs3Trap32Init,(void)); + +/** + * Initializes 64-bit trap handling + * + * @remarks Does not install 64-bit trap handling, just initializes the + * structures. + */ +BS3_CMN_PROTO_STUB(void, Bs3Trap64Init,(void)); + +/** + * Initializes 64-bit trap handling, extended version. + * + * @remarks Does not install 64-bit trap handling, just initializes the + * structures. + * @param fMoreIstUsage Use the interrupt stacks for more CPU exceptions. + * Default (false) is to only IST1 for the double fault + * handler and the rest uses IST0. + */ +BS3_CMN_PROTO_STUB(void, Bs3Trap64InitEx,(bool fMoreIstUsage)); + +/** + * Modifies the real-mode / V86 IVT entry specified by @a iIvt. + * + * @param iIvt The index of the IDT entry to set. + * @param uSeg The handler real-mode segment. + * @param off The handler offset. + */ +BS3_CMN_PROTO_STUB(void, Bs3TrapRmV86SetGate,(uint8_t iIvt, uint16_t uSeg, uint16_t off)); + +/** + * Modifies the 16-bit IDT entry (protected mode) specified by @a iIdt. + * + * @param iIdt The index of the IDT entry to set. + * @param bType The gate type (X86_SEL_TYPE_SYS_XXX). + * @param bDpl The DPL. + * @param uSel The handler selector. + * @param off The handler offset (if applicable). + * @param cParams The parameter count (for call gates). + */ +BS3_CMN_PROTO_STUB(void, Bs3Trap16SetGate,(uint8_t iIdt, uint8_t bType, uint8_t bDpl, + uint16_t uSel, uint16_t off, uint8_t cParams)); + +/** The address of Bs3Trap16GenericEntries. + * Bs3Trap16GenericEntries is an array of interrupt/trap/whatever entry + * points, 8 bytes each, that will create a register frame and call the generic + * C compatible trap handlers. */ +extern uint32_t g_Bs3Trap16GenericEntriesFlatAddr; + +/** + * Modifies the 32-bit IDT entry specified by @a iIdt. + * + * @param iIdt The index of the IDT entry to set. + * @param bType The gate type (X86_SEL_TYPE_SYS_XXX). + * @param bDpl The DPL. + * @param uSel The handler selector. + * @param off The handler offset (if applicable). + * @param cParams The parameter count (for call gates). + */ +BS3_CMN_PROTO_STUB(void, Bs3Trap32SetGate,(uint8_t iIdt, uint8_t bType, uint8_t bDpl, + uint16_t uSel, uint32_t off, uint8_t cParams)); + +/** The address of Bs3Trap32GenericEntries. + * Bs3Trap32GenericEntries is an array of interrupt/trap/whatever entry + * points, 10 bytes each, that will create a register frame and call the generic + * C compatible trap handlers. */ +extern uint32_t g_Bs3Trap32GenericEntriesFlatAddr; + +/** + * Modifies the 64-bit IDT entry specified by @a iIdt. + * + * @param iIdt The index of the IDT entry to set. + * @param bType The gate type (X86_SEL_TYPE_SYS_XXX). + * @param bDpl The DPL. + * @param uSel The handler selector. + * @param off The handler offset (if applicable). + * @param bIst The interrupt stack to use. + */ +BS3_CMN_PROTO_STUB(void, Bs3Trap64SetGate,(uint8_t iIdt, uint8_t bType, uint8_t bDpl, uint16_t uSel, uint64_t off, uint8_t bIst)); + +/** The address of Bs3Trap64GenericEntries. + * Bs3Trap64GenericEntries is an array of interrupt/trap/whatever entry + * points, 8 bytes each, that will create a register frame and call the generic + * C compatible trap handlers. */ +extern uint32_t g_Bs3Trap64GenericEntriesFlatAddr; + +/** + * Adjusts the DPL the IDT entry specified by @a iIdt. + * + * The change is applied to the 16-bit, 32-bit and 64-bit IDTs. + * + * @returns Old DPL (from 64-bit IDT). + * @param iIdt The index of the IDT and IVT entry to set. + * @param bDpl The DPL. + */ +BS3_CMN_PROTO_STUB(uint8_t, Bs3TrapSetDpl,(uint8_t iIdt, uint8_t bDpl)); + +/** + * C-style trap handler. + * + * The caller will resume the context in @a pTrapFrame upon return. + * + * @param pTrapFrame The trap frame. Registers can be modified. + * @note The 16-bit versions must be in CGROUP16! + */ +typedef BS3_DECL_NEAR_CALLBACK(void) FNBS3TRAPHANDLER(PBS3TRAPFRAME pTrapFrame); +/** Pointer to a trap handler (current template context). */ +typedef FNBS3TRAPHANDLER *PFNBS3TRAPHANDLER; + +#if ARCH_BITS == 16 +/** @copydoc FNBS3TRAPHANDLER */ +typedef FNBS3FAR FNBS3TRAPHANDLER32; +/** @copydoc FNBS3TRAPHANDLER */ +typedef FNBS3FAR FNBS3TRAPHANDLER64; +#else +/** @copydoc FNBS3TRAPHANDLER */ +typedef FNBS3TRAPHANDLER FNBS3TRAPHANDLER32; +/** @copydoc FNBS3TRAPHANDLER */ +typedef FNBS3TRAPHANDLER FNBS3TRAPHANDLER64; +#endif +/** @copydoc PFNBS3TRAPHANDLER */ +typedef FNBS3TRAPHANDLER32 *PFNBS3TRAPHANDLER32; +/** @copydoc PFNBS3TRAPHANDLER */ +typedef FNBS3TRAPHANDLER64 *PFNBS3TRAPHANDLER64; + + +/** + * C-style trap handler, near 16-bit (CGROUP16). + * + * The caller will resume the context in @a pTrapFrame upon return. + * + * @param pTrapFrame The trap frame. Registers can be modified. + */ +typedef BS3_DECL_NEAR_CALLBACK(void) FNBS3TRAPHANDLER16(PBS3TRAPFRAME pTrapFrame); +/** Pointer to a trap handler (current template context). */ +typedef FNBS3TRAPHANDLER16 *PFNBS3TRAPHANDLER16; + +/** + * C-style trap handler, near 16-bit (CGROUP16). + * + * The caller will resume the context in @a pTrapFrame upon return. + * + * @param pTrapFrame The trap frame. Registers can be modified. + */ +typedef BS3_DECL_CALLBACK(void) FNBS3TRAPHANDLER3264(PBS3TRAPFRAME pTrapFrame); +/** Pointer to a trap handler (current template context). */ +typedef FNBS3TRAPHANDLER3264 *FPFNBS3TRAPHANDLER3264; + + +/** + * Sets a trap handler (C/C++/assembly) for the current bitcount. + * + * @returns Previous handler. + * @param iIdt The index of the IDT entry to set. + * @param pfnHandler Pointer to the handler. + * @sa Bs3TrapSetHandlerEx + */ +BS3_CMN_PROTO_STUB(PFNBS3TRAPHANDLER, Bs3TrapSetHandler,(uint8_t iIdt, PFNBS3TRAPHANDLER pfnHandler)); + +/** + * Sets a trap handler (C/C++/assembly) for all the bitcounts. + * + * @param iIdt The index of the IDT and IVT entry to set. + * @param pfnHandler16 Pointer to the 16-bit handler. (Assumes linker addresses.) + * @param pfnHandler32 Pointer to the 32-bit handler. (Assumes linker addresses.) + * @param pfnHandler64 Pointer to the 64-bit handler. (Assumes linker addresses.) + * @sa Bs3TrapSetHandler + */ +BS3_CMN_PROTO_STUB(void, Bs3TrapSetHandlerEx,(uint8_t iIdt, PFNBS3TRAPHANDLER16 pfnHandler16, + PFNBS3TRAPHANDLER32 pfnHandler32, PFNBS3TRAPHANDLER64 pfnHandler64)); + +/** + * Default C/C++ trap handler. + * + * This will check trap record and panic if no match was found. + * + * @param pTrapFrame Trap frame of the trap to handle. + */ +BS3_CMN_PROTO_STUB(void, Bs3TrapDefaultHandler,(PBS3TRAPFRAME pTrapFrame)); + +/** + * Prints the trap frame (to screen). + * @param pTrapFrame Trap frame to print. + */ +BS3_CMN_PROTO_STUB(void, Bs3TrapPrintFrame,(PCBS3TRAPFRAME pTrapFrame)); + +/** + * Sets up a long jump from a trap handler. + * + * The long jump will only be performed once, but will catch any kind of trap, + * fault, interrupt or irq. + * + * @retval true on the initial call. + * @retval false on trap return. + * @param pTrapFrame Where to store the trap information when + * returning @c false. + * @sa #Bs3TrapUnsetJmp + */ +BS3_CMN_PROTO_NOSB(DECL_RETURNS_TWICE(bool),Bs3TrapSetJmp,(PBS3TRAPFRAME pTrapFrame)); + +/** + * Combination of #Bs3TrapSetJmp and #Bs3RegCtxRestore. + * + * @param pCtxRestore The context to restore. + * @param pTrapFrame Where to store the trap information. + */ +BS3_CMN_PROTO_STUB(void, Bs3TrapSetJmpAndRestore,(PCBS3REGCTX pCtxRestore, PBS3TRAPFRAME pTrapFrame)); + +/** + * Variation of Bs3TrapSetJmpAndRestore that includes + * #Bs3TrapSetJmpAndRestoreInRm and calls is if pCtxRestore is a real mode + * context and we're not in real mode. + * + * This is useful for 32-bit test drivers running via #Bs3TestDoModesByOne using + * BS3TESTMODEBYONEENTRY_F_REAL_MODE_READY to allow them to test real-mode too. + * + * @param pCtxRestore The context to restore. + * @param pTrapFrame Where to store the trap information. + */ +BS3_CMN_PROTO_STUB(void, Bs3TrapSetJmpAndRestoreWithRm,(PCBS3REGCTX pCtxRestore, PBS3TRAPFRAME pTrapFrame)); + +/** + * Combination of #Bs3ExtCtxRestoreEx, #Bs3TrapSetJmp, #Bs3RegCtxRestore and + * #Bs3ExtCtxSaveEx. + * + * @param pCtxRestore The context to restore. + * @param pExtCtxRestore The extended context to restore. + * @param pTrapFrame Where to store the trap information. + * @param pExtCtxTrap Where to store the extended context after the trap. + * Note, the saving isn't done from the trap handler, + * but after #Bs3TrapSetJmp returns zero (i.e. for the + * 2nd time). + */ +BS3_CMN_PROTO_STUB(void, Bs3TrapSetJmpAndRestoreWithExtCtx,(PCBS3REGCTX pCtxRestore, PCBS3EXTCTX pExtCtxRestore, + PBS3TRAPFRAME pTrapFrame, PBS3EXTCTX pExtCtxTrap)); + +/** + * Variation of Bs3TrapSetJmpAndRestoreWithExtCtx that includes + * #Bs3TrapSetJmpAndRestoreInRm and calls is if pCtxRestore is a real mode + * context and we're not in real mode. + * + * This is useful for 32-bit test drivers running via #Bs3TestDoModesByOne using + * BS3TESTMODEBYONEENTRY_F_REAL_MODE_READY to allow them to test real-mode too. + * + * @param pCtxRestore The context to restore. + * @param pExtCtxRestore The extended context to restore. + * @param pTrapFrame Where to store the trap information. + * @param pExtCtxTrap Where to store the extended context after the trap. + * Note, the saving isn't done from the trap handler, + * but after #Bs3TrapSetJmp returns zero (i.e. for the + * 2nd time). + */ +BS3_CMN_PROTO_STUB(void, Bs3TrapSetJmpAndRestoreWithExtCtxAndRm,(PCBS3REGCTX pCtxRestore, PCBS3EXTCTX pExtCtxRestore, + PBS3TRAPFRAME pTrapFrame, PBS3EXTCTX pExtCtxTrap)); + +/** + * Combination of Bs3SwitchToRM, #Bs3TrapSetJmp and #Bs3RegCtxRestore. + * + * @param pCtxRestore The context to restore. Must be real-mode + * addressable. + * @param pTrapFrame Where to store the trap information. Must be + * real-mode addressable. + */ +BS3_CMN_PROTO_STUB(void, Bs3TrapSetJmpAndRestoreInRm,(PCBS3REGCTX pCtxRestore, PBS3TRAPFRAME pTrapFrame)); + +/** + * Disables a previous #Bs3TrapSetJmp call. + */ +BS3_CMN_PROTO_STUB(void, Bs3TrapUnsetJmp,(void)); + + +/** + * The current test step. + */ +extern uint16_t g_usBs3TestStep; + +/** + * Equivalent to RTTestCreate + RTTestBanner. + * + * @param pszTest The test name. + */ +BS3_CMN_PROTO_STUB(void, Bs3TestInit,(const char BS3_FAR *pszTest)); + + +/** + * Equivalent to RTTestSummaryAndDestroy. + */ +BS3_CMN_PROTO_STUB(void, Bs3TestTerm,(void)); + +/** + * Equivalent to RTTestISub. + */ +BS3_CMN_PROTO_STUB(void, Bs3TestSub,(const char BS3_FAR *pszSubTest)); + +/** + * Equivalent to RTTestIFailedF. + */ +BS3_CMN_PROTO_STUB(void, Bs3TestSubF,(const char BS3_FAR *pszFormat, ...)); + +/** + * Equivalent to RTTestISubV. + */ +BS3_CMN_PROTO_STUB(void, Bs3TestSubV,(const char BS3_FAR *pszFormat, va_list BS3_FAR va)); + +/** + * Equivalent to RTTestISubDone. + */ +BS3_CMN_PROTO_STUB(void, Bs3TestSubDone,(void)); + +/** + * Equivalent to RTTestIValue. + */ +BS3_CMN_PROTO_STUB(void, Bs3TestValue,(const char BS3_FAR *pszName, uint64_t u64Value, uint8_t bUnit)); + +/** + * Equivalent to RTTestSubErrorCount. + */ +BS3_CMN_PROTO_STUB(uint16_t, Bs3TestSubErrorCount,(void)); + +/** + * Get nanosecond host timestamp. + * + * This only works when testing is enabled and will not work in VMs configured + * with a 286, 186 or 8086/8088 CPU profile. + */ +BS3_CMN_PROTO_STUB(uint64_t, Bs3TestNow,(void)); + + +/** + * Queries an unsigned 8-bit configuration value. + * + * @returns Value. + * @param uCfg A VMMDEV_TESTING_CFG_XXX value. + */ +BS3_CMN_PROTO_STUB(uint8_t, Bs3TestQueryCfgU8,(uint16_t uCfg)); + +/** + * Queries an unsigned 8-bit configuration value. + * + * @returns Value. + * @param uCfg A VMMDEV_TESTING_CFG_XXX value. + */ +BS3_CMN_PROTO_STUB(bool, Bs3TestQueryCfgBool,(uint16_t uCfg)); + +/** + * Queries an unsigned 32-bit configuration value. + * + * @returns Value. + * @param uCfg A VMMDEV_TESTING_CFG_XXX value. + */ +BS3_CMN_PROTO_STUB(uint32_t, Bs3TestQueryCfgU32,(uint16_t uCfg)); + +/** + * Equivalent to RTTestIPrintf with RTTESTLVL_ALWAYS. + * + * @param pszFormat What to print, format string. Explicit newline char. + * @param ... String format arguments. + */ +BS3_CMN_PROTO_STUB(void, Bs3TestPrintf,(const char BS3_FAR *pszFormat, ...)); + +/** + * Equivalent to RTTestIPrintfV with RTTESTLVL_ALWAYS. + * + * @param pszFormat What to print, format string. Explicit newline char. + * @param va String format arguments. + */ +BS3_CMN_PROTO_STUB(void, Bs3TestPrintfV,(const char BS3_FAR *pszFormat, va_list BS3_FAR va)); + +/** + * Same as Bs3TestPrintf, except no guest screen echo. + * + * @param pszFormat What to print, format string. Explicit newline char. + * @param ... String format arguments. + */ +BS3_CMN_PROTO_STUB(void, Bs3TestHostPrintf,(const char BS3_FAR *pszFormat, ...)); + +/** + * Same as Bs3TestPrintfV, except no guest screen echo. + * + * @param pszFormat What to print, format string. Explicit newline char. + * @param va String format arguments. + */ +BS3_CMN_PROTO_STUB(void, Bs3TestHostPrintfV,(const char BS3_FAR *pszFormat, va_list BS3_FAR va)); + +/** + * Equivalent to RTTestIFailed. + * @returns false. + */ +BS3_CMN_PROTO_STUB(bool, Bs3TestFailed,(const char BS3_FAR *pszMessage)); + +/** + * Equivalent to RTTestIFailedF. + * @returns false. + */ +BS3_CMN_PROTO_STUB(bool, Bs3TestFailedF,(const char BS3_FAR *pszFormat, ...)); + +/** + * Equivalent to RTTestIFailedV. + * @returns false. + */ +BS3_CMN_PROTO_STUB(bool, Bs3TestFailedV,(const char BS3_FAR *pszFormat, va_list BS3_FAR va)); + +/** + * Equivalent to RTTestISkipped. + * + * @param pszWhy Optional reason why it's being skipped. + */ +BS3_CMN_PROTO_STUB(void, Bs3TestSkipped,(const char BS3_FAR *pszWhy)); + +/** + * Equivalent to RTTestISkippedF. + * + * @param pszFormat Optional reason why it's being skipped. + * @param ... Format arguments. + */ +BS3_CMN_PROTO_STUB(void, Bs3TestSkippedF,(const char BS3_FAR *pszFormat, ...)); + +/** + * Equivalent to RTTestISkippedV. + * + * @param pszFormat Optional reason why it's being skipped. + * @param va Format arguments. + */ +BS3_CMN_PROTO_STUB(void, Bs3TestSkippedV,(const char BS3_FAR *pszFormat, va_list BS3_FAR va)); + +/** + * Compares two register contexts, with PC and SP adjustments. + * + * Differences will be reported as test failures. + * + * @returns true if equal, false if not. + * @param pActualCtx The actual register context. + * @param pExpectedCtx Expected register context. + * @param cbPcAdjust Program counter adjustment (applied to @a pExpectedCtx). + * @param cbSpAdjust Stack pointer adjustment (applied to @a pExpectedCtx). + * @param fExtraEfl Extra EFLAGS to OR into @a pExepctedCtx. + * @param pszMode CPU mode or some other helpful text. + * @param idTestStep Test step identifier. + */ +BS3_CMN_PROTO_STUB(bool, Bs3TestCheckRegCtxEx,(PCBS3REGCTX pActualCtx, PCBS3REGCTX pExpectedCtx, uint16_t cbPcAdjust, + int16_t cbSpAdjust, uint32_t fExtraEfl, + const char BS3_FAR *pszMode, uint16_t idTestStep)); + +/** + * Compares two extended register contexts. + * + * Differences will be reported as test failures. + * + * @returns true if equal, false if not. + * @param pActualExtCtx The actual register context. + * @param pExpectedExtCtx Expected register context. + * @param fFlags Reserved, pass 0. + * @param pszMode CPU mode or some other helpful text. + * @param idTestStep Test step identifier. + */ +BS3_CMN_PROTO_STUB(bool, Bs3TestCheckExtCtx,(PCBS3EXTCTX pActualExtCtx, PCBS3EXTCTX pExpectedExtCtx, uint16_t fFlags, + const char BS3_FAR *pszMode, uint16_t idTestStep)); + +/** + * Performs the testing for the given mode. + * + * This is called with the CPU already switch to that mode. + * + * @returns 0 on success or directly Bs3TestFailed calls, non-zero to indicate + * where the test when wrong. Special value BS3TESTDOMODE_SKIPPED + * should be returned to indicate that the test has been skipped. + * @param bMode The current CPU mode. + */ +typedef BS3_DECL_CALLBACK(uint8_t) FNBS3TESTDOMODE(uint8_t bMode); +/** Pointer (far) to a test (for 32-bit and 64-bit code, will be flatten). */ +typedef FNBS3TESTDOMODE *PFNBS3TESTDOMODE; + +/** Special FNBS3TESTDOMODE return code for indicating a skipped mode test. */ +#define BS3TESTDOMODE_SKIPPED UINT8_MAX + +/** + * Mode sub-test entry. + * + * This can only be passed around to functions with the same bit count, as it + * contains function pointers. In 16-bit mode, the 16-bit pointers are near and + * implies BS3TEXT16, whereas the 32-bit and 64-bit pointers are far real mode + * addresses that will be converted to flat address prior to calling them. + * Similarly, in 32-bit and 64-bit the addresses are all flat and the 16-bit + * ones will be converted to BS3TEXT16 based addresses prior to calling. + */ +typedef struct BS3TESTMODEENTRY +{ + /** The sub-test name to be passed to Bs3TestSub if not NULL. */ + const char * BS3_FAR pszSubTest; + + PFNBS3TESTDOMODE pfnDoRM; + + PFNBS3TESTDOMODE pfnDoPE16; + PFNBS3TESTDOMODE pfnDoPE16_32; + PFNBS3TESTDOMODE pfnDoPE16_V86; + PFNBS3TESTDOMODE pfnDoPE32; + PFNBS3TESTDOMODE pfnDoPE32_16; + PFNBS3TESTDOMODE pfnDoPEV86; + + PFNBS3TESTDOMODE pfnDoPP16; + PFNBS3TESTDOMODE pfnDoPP16_32; + PFNBS3TESTDOMODE pfnDoPP16_V86; + PFNBS3TESTDOMODE pfnDoPP32; + PFNBS3TESTDOMODE pfnDoPP32_16; + PFNBS3TESTDOMODE pfnDoPPV86; + + PFNBS3TESTDOMODE pfnDoPAE16; + PFNBS3TESTDOMODE pfnDoPAE16_32; + PFNBS3TESTDOMODE pfnDoPAE16_V86; + PFNBS3TESTDOMODE pfnDoPAE32; + PFNBS3TESTDOMODE pfnDoPAE32_16; + PFNBS3TESTDOMODE pfnDoPAEV86; + + PFNBS3TESTDOMODE pfnDoLM16; + PFNBS3TESTDOMODE pfnDoLM32; + PFNBS3TESTDOMODE pfnDoLM64; + +} BS3TESTMODEENTRY; +/** Pointer to a mode sub-test entry. */ +typedef BS3TESTMODEENTRY const *PCBS3TESTMODEENTRY; + +/** @def BS3TESTMODEENTRY_CMN + * Produces a BS3TESTMODEENTRY initializer for common (c16,c32,c64) test + * functions. */ +#define BS3TESTMODEENTRY_CMN(a_szTest, a_BaseNm) \ + { /*pszSubTest =*/ a_szTest, \ + /*RM*/ RT_CONCAT(a_BaseNm, _c16), \ + /*PE16*/ RT_CONCAT(a_BaseNm, _c16), \ + /*PE16_32*/ RT_CONCAT(a_BaseNm, _c32), \ + /*PE16_V86*/ RT_CONCAT(a_BaseNm, _c16), \ + /*PE32*/ RT_CONCAT(a_BaseNm, _c32), \ + /*PE32_16*/ RT_CONCAT(a_BaseNm, _c16), \ + /*PEV86*/ RT_CONCAT(a_BaseNm, _c16), \ + /*PP16*/ RT_CONCAT(a_BaseNm, _c16), \ + /*PP16_32*/ RT_CONCAT(a_BaseNm, _c32), \ + /*PP16_V86*/ RT_CONCAT(a_BaseNm, _c16), \ + /*PP32*/ RT_CONCAT(a_BaseNm, _c32), \ + /*PP32_16*/ RT_CONCAT(a_BaseNm, _c16), \ + /*PPV86*/ RT_CONCAT(a_BaseNm, _c16), \ + /*PAE16*/ RT_CONCAT(a_BaseNm, _c16), \ + /*PAE16_32*/ RT_CONCAT(a_BaseNm, _c32), \ + /*PAE16_V86*/ RT_CONCAT(a_BaseNm, _c16), \ + /*PAE32*/ RT_CONCAT(a_BaseNm, _c32), \ + /*PAE32_16*/ RT_CONCAT(a_BaseNm, _c16), \ + /*PAEV86*/ RT_CONCAT(a_BaseNm, _c16), \ + /*LM16*/ RT_CONCAT(a_BaseNm, _c16), \ + /*LM32*/ RT_CONCAT(a_BaseNm, _c32), \ + /*LM64*/ RT_CONCAT(a_BaseNm, _c64), \ + } + +/** @def BS3TESTMODE_PROTOTYPES_CMN + * A set of standard protypes to go with #BS3TESTMODEENTRY_CMN. */ +#define BS3TESTMODE_PROTOTYPES_CMN(a_BaseNm) \ + FNBS3TESTDOMODE /*BS3_FAR_CODE*/ RT_CONCAT(a_BaseNm, _c16); \ + FNBS3TESTDOMODE /*BS3_FAR_CODE*/ RT_CONCAT(a_BaseNm, _c32); \ + FNBS3TESTDOMODE /*BS3_FAR_CODE*/ RT_CONCAT(a_BaseNm, _c64) + +/** @def BS3TESTMODEENTRY_CMN_64 + * Produces a BS3TESTMODEENTRY initializer for common 64-bit test functions. */ +#define BS3TESTMODEENTRY_CMN_64(a_szTest, a_BaseNm) \ + { /*pszSubTest =*/ a_szTest, \ + /*RM*/ NULL, \ + /*PE16*/ NULL, \ + /*PE16_32*/ NULL, \ + /*PE16_V86*/ NULL, \ + /*PE32*/ NULL, \ + /*PE32_16*/ NULL, \ + /*PEV86*/ NULL, \ + /*PP16*/ NULL, \ + /*PP16_32*/ NULL, \ + /*PP16_V86*/ NULL, \ + /*PP32*/ NULL, \ + /*PP32_16*/ NULL, \ + /*PPV86*/ NULL, \ + /*PAE16*/ NULL, \ + /*PAE16_32*/ NULL, \ + /*PAE16_V86*/ NULL, \ + /*PAE32*/ NULL, \ + /*PAE32_16*/ NULL, \ + /*PAEV86*/ NULL, \ + /*LM16*/ NULL, \ + /*LM32*/ NULL, \ + /*LM64*/ RT_CONCAT(a_BaseNm, _c64), \ + } + +/** @def BS3TESTMODE_PROTOTYPES_CMN + * Standard protype to go with #BS3TESTMODEENTRY_CMN_64. */ +#define BS3TESTMODE_PROTOTYPES_CMN_64(a_BaseNm) \ + FNBS3TESTDOMODE /*BS3_FAR_CODE*/ RT_CONCAT(a_BaseNm, _c64) + +/** @def BS3TESTMODEENTRY_MODE + * Produces a BS3TESTMODEENTRY initializer for a full set of mode test + * functions. */ +#define BS3TESTMODEENTRY_MODE(a_szTest, a_BaseNm) \ + { /*pszSubTest =*/ a_szTest, \ + /*RM*/ RT_CONCAT(a_BaseNm, _rm), \ + /*PE16*/ RT_CONCAT(a_BaseNm, _pe16), \ + /*PE16_32*/ RT_CONCAT(a_BaseNm, _pe16_32), \ + /*PE16_V86*/ RT_CONCAT(a_BaseNm, _pe16_v86), \ + /*PE32*/ RT_CONCAT(a_BaseNm, _pe32), \ + /*PE32_16*/ RT_CONCAT(a_BaseNm, _pe32_16), \ + /*PEV86*/ RT_CONCAT(a_BaseNm, _pev86), \ + /*PP16*/ RT_CONCAT(a_BaseNm, _pp16), \ + /*PP16_32*/ RT_CONCAT(a_BaseNm, _pp16_32), \ + /*PP16_V86*/ RT_CONCAT(a_BaseNm, _pp16_v86), \ + /*PP32*/ RT_CONCAT(a_BaseNm, _pp32), \ + /*PP32_16*/ RT_CONCAT(a_BaseNm, _pp32_16), \ + /*PPV86*/ RT_CONCAT(a_BaseNm, _ppv86), \ + /*PAE16*/ RT_CONCAT(a_BaseNm, _pae16), \ + /*PAE16_32*/ RT_CONCAT(a_BaseNm, _pae16_32), \ + /*PAE16_V86*/ RT_CONCAT(a_BaseNm, _pae16_v86), \ + /*PAE32*/ RT_CONCAT(a_BaseNm, _pae32), \ + /*PAE32_16*/ RT_CONCAT(a_BaseNm, _pae32_16), \ + /*PAEV86*/ RT_CONCAT(a_BaseNm, _paev86), \ + /*LM16*/ RT_CONCAT(a_BaseNm, _lm16), \ + /*LM32*/ RT_CONCAT(a_BaseNm, _lm32), \ + /*LM64*/ RT_CONCAT(a_BaseNm, _lm64), \ + } + +/** @def BS3TESTMODE_PROTOTYPES_MODE + * A set of standard protypes to go with #BS3TESTMODEENTRY_MODE. */ +#define BS3TESTMODE_PROTOTYPES_MODE(a_BaseNm) \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _rm); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pe16); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pe16_32); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pe16_v86); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pe32); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pe32_16); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pev86); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pp16); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pp16_32); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pp16_v86); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pp32); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pp32_16); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _ppv86); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pae16); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pae16_32); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pae16_v86); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pae32); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pae32_16); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _paev86); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _lm16); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _lm32); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _lm64) + + +/** + * Mode sub-test entry, max bit-count driven + * + * This is an alternative to BS3TESTMODEENTRY where a few workers (test drivers) + * does all the work, using faster 32-bit and 64-bit code where possible. This + * avoids executing workers in V8086 mode. It allows for modifying and checking + * 64-bit register content when testing LM16 and LM32. + * + * The 16-bit workers are only used for real mode and 16-bit protected mode. + * So, the 16-bit version of the code template can be stripped of anything + * related to paging and/or v8086, saving code space. + */ +typedef struct BS3TESTMODEBYMAXENTRY +{ + /** The sub-test name to be passed to Bs3TestSub if not NULL. */ + const char * BS3_FAR pszSubTest; + + PFNBS3TESTDOMODE pfnDoRM; + PFNBS3TESTDOMODE pfnDoPE16; + PFNBS3TESTDOMODE pfnDoPE16_32; + PFNBS3TESTDOMODE pfnDoPE32; + PFNBS3TESTDOMODE pfnDoPP16_32; + PFNBS3TESTDOMODE pfnDoPP32; + PFNBS3TESTDOMODE pfnDoPAE16_32; + PFNBS3TESTDOMODE pfnDoPAE32; + PFNBS3TESTDOMODE pfnDoLM64; + + bool fDoRM : 1; + + bool fDoPE16 : 1; + bool fDoPE16_32 : 1; + bool fDoPE16_V86 : 1; + bool fDoPE32 : 1; + bool fDoPE32_16 : 1; + bool fDoPEV86 : 1; + + bool fDoPP16 : 1; + bool fDoPP16_32 : 1; + bool fDoPP16_V86 : 1; + bool fDoPP32 : 1; + bool fDoPP32_16 : 1; + bool fDoPPV86 : 1; + + bool fDoPAE16 : 1; + bool fDoPAE16_32 : 1; + bool fDoPAE16_V86 : 1; + bool fDoPAE32 : 1; + bool fDoPAE32_16 : 1; + bool fDoPAEV86 : 1; + + bool fDoLM16 : 1; + bool fDoLM32 : 1; + bool fDoLM64 : 1; + +} BS3TESTMODEBYMAXENTRY; +/** Pointer to a mode-by-max sub-test entry. */ +typedef BS3TESTMODEBYMAXENTRY const *PCBS3TESTMODEBYMAXENTRY; + +/** @def BS3TESTMODEBYMAXENTRY_CMN + * Produces a BS3TESTMODEBYMAXENTRY initializer for common (c16,c32,c64) test + * functions. */ +#define BS3TESTMODEBYMAXENTRY_CMN(a_szTest, a_BaseNm) \ + { /*pszSubTest =*/ a_szTest, \ + /*RM*/ RT_CONCAT(a_BaseNm, _c16), \ + /*PE16*/ RT_CONCAT(a_BaseNm, _c16), \ + /*PE16_32*/ RT_CONCAT(a_BaseNm, _c32), \ + /*PE32*/ RT_CONCAT(a_BaseNm, _c32), \ + /*PP16_32*/ RT_CONCAT(a_BaseNm, _c32), \ + /*PP32*/ RT_CONCAT(a_BaseNm, _c32), \ + /*PAE16_32*/ RT_CONCAT(a_BaseNm, _c32), \ + /*PAE32*/ RT_CONCAT(a_BaseNm, _c32), \ + /*LM64*/ RT_CONCAT(a_BaseNm, _c64), \ + /*fDoRM*/ true, \ + /*fDoPE16*/ true, \ + /*fDoPE16_32*/ true, \ + /*fDoPE16_V86*/ true, \ + /*fDoPE32*/ true, \ + /*fDoPE32_16*/ true, \ + /*fDoPEV86*/ true, \ + /*fDoPP16*/ true, \ + /*fDoPP16_32*/ true, \ + /*fDoPP16_V86*/ true, \ + /*fDoPP32*/ true, \ + /*fDoPP32_16*/ true, \ + /*fDoPPV86*/ true, \ + /*fDoPAE16*/ true, \ + /*fDoPAE16_32*/ true, \ + /*fDoPAE16_V86*/ true, \ + /*fDoPAE32*/ true, \ + /*fDoPAE32_16*/ true, \ + /*fDoPAEV86*/ true, \ + /*fDoLM16*/ true, \ + /*fDoLM32*/ true, \ + /*fDoLM64*/ true, \ + } + +/** @def BS3TESTMODEBYMAX_PROTOTYPES_CMN + * A set of standard protypes to go with #BS3TESTMODEBYMAXENTRY_CMN. */ +#define BS3TESTMODEBYMAX_PROTOTYPES_CMN(a_BaseNm) \ + FNBS3TESTDOMODE /*BS3_FAR_CODE*/ RT_CONCAT(a_BaseNm, _c16); \ + FNBS3TESTDOMODE /*BS3_FAR_CODE*/ RT_CONCAT(a_BaseNm, _c32); \ + FNBS3TESTDOMODE /*BS3_FAR_CODE*/ RT_CONCAT(a_BaseNm, _c64) + + +/** @def BS3TESTMODEBYMAXENTRY_MODE + * Produces a BS3TESTMODEBYMAXENTRY initializer for a full set of mode test + * functions. */ +#define BS3TESTMODEBYMAXENTRY_MODE(a_szTest, a_BaseNm) \ + { /*pszSubTest =*/ a_szTest, \ + /*RM*/ RT_CONCAT(a_BaseNm, _rm), \ + /*PE16*/ RT_CONCAT(a_BaseNm, _pe16), \ + /*PE16_32*/ RT_CONCAT(a_BaseNm, _pe16_32), \ + /*PE32*/ RT_CONCAT(a_BaseNm, _pe32), \ + /*PP16_32*/ RT_CONCAT(a_BaseNm, _pp16_32), \ + /*PP32*/ RT_CONCAT(a_BaseNm, _pp32), \ + /*PAE16_32*/ RT_CONCAT(a_BaseNm, _pae16_32), \ + /*PAE32*/ RT_CONCAT(a_BaseNm, _pae32), \ + /*LM64*/ RT_CONCAT(a_BaseNm, _lm64), \ + /*fDoRM*/ true, \ + /*fDoPE16*/ true, \ + /*fDoPE16_32*/ true, \ + /*fDoPE16_V86*/ true, \ + /*fDoPE32*/ true, \ + /*fDoPE32_16*/ true, \ + /*fDoPEV86*/ true, \ + /*fDoPP16*/ true, \ + /*fDoPP16_32*/ true, \ + /*fDoPP16_V86*/ true, \ + /*fDoPP32*/ true, \ + /*fDoPP32_16*/ true, \ + /*fDoPPV86*/ true, \ + /*fDoPAE16*/ true, \ + /*fDoPAE16_32*/ true, \ + /*fDoPAE16_V86*/ true, \ + /*fDoPAE32*/ true, \ + /*fDoPAE32_16*/ true, \ + /*fDoPAEV86*/ true, \ + /*fDoLM16*/ true, \ + /*fDoLM32*/ true, \ + /*fDoLM64*/ true, \ + } + +/** @def BS3TESTMODEBYMAX_PROTOTYPES_MODE + * A set of standard protypes to go with #BS3TESTMODEBYMAXENTRY_MODE. */ +#define BS3TESTMODEBYMAX_PROTOTYPES_MODE(a_BaseNm) \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _rm); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pe16); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pe16_32); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pe32); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pp16_32); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pp32); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pae16_32); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _pae32); \ + FNBS3TESTDOMODE RT_CONCAT(a_BaseNm, _lm64) + + +/** + * One worker drives all modes. + * + * This is an alternative to BS3TESTMODEENTRY where one worker, typically + * 16-bit, does all the test driver work. It's called repeatedly from all + * the modes being tested. + */ +typedef struct BS3TESTMODEBYONEENTRY +{ + const char * BS3_FAR pszSubTest; + PFNBS3TESTDOMODE pfnWorker; + /** BS3TESTMODEBYONEENTRY_F_XXX. */ + uint32_t fFlags; +} BS3TESTMODEBYONEENTRY; +/** Pointer to a mode-by-one sub-test entry. */ +typedef BS3TESTMODEBYONEENTRY const *PCBS3TESTMODEBYONEENTRY; + +/** @name BS3TESTMODEBYONEENTRY_F_XXX - flags. + * @{ */ +/** Only test modes that has paging enabled. */ +#define BS3TESTMODEBYONEENTRY_F_ONLY_PAGING RT_BIT_32(0) +/** Minimal mode selection. */ +#define BS3TESTMODEBYONEENTRY_F_MINIMAL RT_BIT_32(1) +/** The 32-bit worker is ready to handle real-mode by mode switching. */ +#define BS3TESTMODEBYONEENTRY_F_REAL_MODE_READY RT_BIT_32(2) +/** @} */ + + +/** + * Sets the full GDTR register. + * + * @param cbLimit The limit. + * @param uBase The base address - 24, 32 or 64 bit depending on the + * CPU mode. + */ +BS3_CMN_PROTO_NOSB(void, Bs3UtilSetFullGdtr,(uint16_t cbLimit, uint64_t uBase)); + +/** + * Sets the full IDTR register. + * + * @param cbLimit The limit. + * @param uBase The base address - 24, 32 or 64 bit depending on the + * CPU mode. + */ +BS3_CMN_PROTO_NOSB(void, Bs3UtilSetFullIdtr,(uint16_t cbLimit, uint64_t uBase)); + + +/** @} */ + + +/** + * Initializes all of boot sector kit \#3. + */ +BS3_DECL(void) Bs3InitAll_rm(void); + +/** + * Initializes the REAL and TILED memory pools. + * + * For proper operation on OLDer CPUs, call #Bs3CpuDetect_mmm first. + */ +BS3_DECL_FAR(void) Bs3InitMemory_rm_far(void); + +/** + * Initializes the X0TEXT16 and X1TEXT16 GDT entries. + */ +BS3_DECL_FAR(void) Bs3InitGdt_rm_far(void); + + + +/** @defgroup grp_bs3kit_mode Mode Specific Functions and Data + * + * The mode specific functions come in bit count variations and CPU mode + * variations. The bs3kit-template-header.h/mac defines the BS3_NM macro to + * mangle a function or variable name according to the target CPU mode. In + * non-templated code, it's common to spell the name out in full. + * + * @{ + */ + + +/** @def BS3_MODE_PROTO_INT + * Internal macro for emitting prototypes for mode functions. + * + * @param a_RetType The return type. + * @param a_Name The function basename. + * @param a_Params The parameter list (in parentheses). + * @sa BS3_MODE_PROTO_STUB, BS3_MODE_PROTO_NOSB + */ +#define BS3_MODE_PROTO_INT(a_RetType, a_Name, a_Params) \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_rm) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_pe16) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_pe16_32) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_pe16_v86) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_pe32) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_pe32_16) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_pev86) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_pp16) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_pp16_32) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_pp16_v86) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_pp32) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_pp32_16) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_ppv86) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_pae16) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_pae16_32) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_pae16_v86) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_pae32) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_pae32_16) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_paev86) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_lm16) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_lm32) a_Params; \ + BS3_DECL_NEAR(a_RetType) RT_CONCAT(a_Name,_lm64) a_Params; \ + BS3_DECL_FAR(a_RetType) RT_CONCAT(a_Name,_rm_far) a_Params; \ + BS3_DECL_FAR(a_RetType) RT_CONCAT(a_Name,_pe16_far) a_Params; \ + BS3_DECL_FAR(a_RetType) RT_CONCAT(a_Name,_pe16_v86_far) a_Params; \ + BS3_DECL_FAR(a_RetType) RT_CONCAT(a_Name,_pe32_16_far) a_Params; \ + BS3_DECL_FAR(a_RetType) RT_CONCAT(a_Name,_pev86_far) a_Params; \ + BS3_DECL_FAR(a_RetType) RT_CONCAT(a_Name,_pp16_far) a_Params; \ + BS3_DECL_FAR(a_RetType) RT_CONCAT(a_Name,_pp16_v86_far) a_Params; \ + BS3_DECL_FAR(a_RetType) RT_CONCAT(a_Name,_pp32_16_far) a_Params; \ + BS3_DECL_FAR(a_RetType) RT_CONCAT(a_Name,_ppv86_far) a_Params; \ + BS3_DECL_FAR(a_RetType) RT_CONCAT(a_Name,_pae16_far) a_Params; \ + BS3_DECL_FAR(a_RetType) RT_CONCAT(a_Name,_pae16_v86_far)a_Params; \ + BS3_DECL_FAR(a_RetType) RT_CONCAT(a_Name,_pae32_16_far) a_Params; \ + BS3_DECL_FAR(a_RetType) RT_CONCAT(a_Name,_paev86_far) a_Params; \ + BS3_DECL_FAR(a_RetType) RT_CONCAT(a_Name,_lm16_far) a_Params + +/** @def BS3_MODE_PROTO_STUB + * Macro for prototyping all the variations of a mod function with automatic + * near -> far stub. + * + * @param a_RetType The return type. + * @param a_Name The function basename. + * @param a_Params The parameter list (in parentheses). + * @sa BS3_MODE_PROTO_STUB, BS3_MODE_PROTO_NOSB + */ +#define BS3_MODE_PROTO_STUB(a_RetType, a_Name, a_Params) BS3_MODE_PROTO_INT(a_RetType, a_Name, a_Params) + +/** @def BS3_MODE_PROTO_STUB + * Macro for prototyping all the variations of a mod function without any + * near -> far stub. + * + * @param a_RetType The return type. + * @param a_Name The function basename. + * @param a_Params The parameter list (in parentheses). + * @sa BS3_MODE_PROTO_STUB, BS3_MODE_PROTO_NOSB + */ +#define BS3_MODE_PROTO_NOSB(a_RetType, a_Name, a_Params) BS3_MODE_PROTO_INT(a_RetType, a_Name, a_Params) + + +/** + * Macro for reducing typing. + * + * Doxygen knows how to expand this, well, kind of. + * + * @remarks Variables instantiated in assembly code should define two labels, + * with and without leading underscore. Variables instantiated from + * C/C++ code doesn't need to as the object file convert does this for + * 64-bit object files. + */ +#define BS3_MODE_EXPAND_EXTERN_DATA16(a_VarType, a_VarName, a_Suffix) \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_rm) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_pe16) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_pe16_32) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_pe16_v86) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_pe32) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_pe32_16) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_pev86) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_pp16) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_pp16_32) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_pp16_v86) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_pp32) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_pp32_16) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_ppv86) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_pae16) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_pae16_32) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_pae16_v86)a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_pae32) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_pae32_16) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_paev86) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_lm16) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_lm32) a_Suffix; \ + extern a_VarType BS3_FAR_DATA RT_CONCAT(a_VarName,_lm64) a_Suffix + + +/** The TMPL_MODE_STR value for each mode. + * These are all in DATA16 so they can be accessed from any code. */ +BS3_MODE_EXPAND_EXTERN_DATA16(const char, g_szBs3ModeName, []); +/** The TMPL_MODE_LNAME value for each mode. + * These are all in DATA16 so they can be accessed from any code. */ +BS3_MODE_EXPAND_EXTERN_DATA16(const char, g_szBs3ModeNameShortLower, []); + + +/** + * Basic CPU detection. + * + * This sets the #g_uBs3CpuDetected global variable to the return value. + * + * @returns BS3CPU_XXX value with the BS3CPU_F_CPUID flag set depending on + * capabilities. + */ +BS3_MODE_PROTO_NOSB(uint8_t, Bs3CpuDetect,(void)); + +/** @name BS3CPU_XXX - CPU detected by BS3CpuDetect_c16() and friends. + * @{ */ +#define BS3CPU_8086 UINT16_C(0x0001) /**< Both 8086 and 8088. */ +#define BS3CPU_V20 UINT16_C(0x0002) /**< Both NEC V20, V30 and relatives. */ +#define BS3CPU_80186 UINT16_C(0x0003) /**< Both 80186 and 80188. */ +#define BS3CPU_80286 UINT16_C(0x0004) +#define BS3CPU_80386 UINT16_C(0x0005) +#define BS3CPU_80486 UINT16_C(0x0006) +#define BS3CPU_Pentium UINT16_C(0x0007) +#define BS3CPU_PPro UINT16_C(0x0008) +#define BS3CPU_PProOrNewer UINT16_C(0x0009) +/** CPU type mask. This is a full byte so it's possible to use byte access + * without and AND'ing to get the type value. */ +#define BS3CPU_TYPE_MASK UINT16_C(0x00ff) +/** Flag indicating that the CPUID instruction is supported by the CPU. */ +#define BS3CPU_F_CPUID UINT16_C(0x0100) +/** Flag indicating that extend CPUID leaves are available (at least two). */ +#define BS3CPU_F_CPUID_EXT_LEAVES UINT16_C(0x0200) +/** Flag indicating that the CPU supports PAE. */ +#define BS3CPU_F_PAE UINT16_C(0x0400) +/** Flag indicating that the CPU supports the page size extension (4MB pages). */ +#define BS3CPU_F_PSE UINT16_C(0x0800) +/** Flag indicating that the CPU supports long mode. */ +#define BS3CPU_F_LONG_MODE UINT16_C(0x1000) +/** Flag indicating that the CPU supports NX. */ +#define BS3CPU_F_NX UINT16_C(0x2000) +/** @} */ + +/** The return value of #Bs3CpuDetect_mmm. (Initial value is BS3CPU_TYPE_MASK.) */ +extern uint16_t g_uBs3CpuDetected; + +/** + * Call 32-bit prot mode C function. + * + * This switches to 32-bit mode and calls the 32-bit @a fpfnCall C code with @a + * cbParams on the stack, then returns in the original mode. When called in + * real mode, this will switch to PE32. + * + * @returns 32-bit status code if the function returned anything. + * @param fpfnCall Address of the 32-bit C function to call. When + * called from 16-bit code, this is a far real mode + * function pointer, i.e. as fixed up by the linker. + * In 32-bit and 64-bit code, this is a flat address. + * @param cbParams The size of the parameter list, in bytes. + * @param ... The parameters. + * @sa Bs3SwitchFromV86To16BitAndCallC + * + * @remarks WARNING! This probably doesn't work in 64-bit mode yet. + * Only tested for 16-bit real mode. + */ +BS3_MODE_PROTO_STUB(int32_t, Bs3SwitchTo32BitAndCallC,(FPFNBS3FAR fpfnCall, unsigned cbParams, ...)); + +/** + * Initializes trap handling for the current system. + * + * Calls the appropriate Bs3Trap16Init, Bs3Trap32Init or Bs3Trap64Init function. + */ +BS3_MODE_PROTO_STUB(void, Bs3TrapInit,(void)); + +/** + * Executes the array of tests in every possibly mode. + * + * @param paEntries The mode sub-test entries. + * @param cEntries The number of sub-test entries. + */ +BS3_MODE_PROTO_NOSB(void, Bs3TestDoModes,(PCBS3TESTMODEENTRY paEntries, size_t cEntries)); + +/** + * Executes the array of tests in every possibly mode, unified driver. + * + * This requires much less code space than Bs3TestDoModes as there is only one + * instace of each sub-test driver code, instead of 3 (cmn) or 22 (per-mode) + * copies. + * + * @param paEntries The mode sub-test-by-one entries. + * @param cEntries The number of sub-test-by-one entries. + * @param fFlags BS3TESTMODEBYONEENTRY_F_XXX. + */ +BS3_MODE_PROTO_NOSB(void, Bs3TestDoModesByOne,(PCBS3TESTMODEBYONEENTRY paEntries, size_t cEntries, uint32_t fFlags)); + +/** + * Executes the array of tests in every possibly mode, using the max bit-count + * worker for each. + * + * @param paEntries The mode sub-test entries. + * @param cEntries The number of sub-test entries. + */ +BS3_MODE_PROTO_NOSB(void, Bs3TestDoModesByMax,(PCBS3TESTMODEBYMAXENTRY paEntries, size_t cEntries)); + +/** @} */ + + +/** @defgroup grp_bs3kit_bios_int15 BIOS - int 15h + * @{ */ + +/** An INT15E820 data entry. */ +typedef struct INT15E820ENTRY +{ + uint64_t uBaseAddr; + uint64_t cbRange; + /** Memory type this entry describes, see INT15E820_TYPE_XXX. */ + uint32_t uType; + /** Optional. */ + uint32_t fAcpi3; +} INT15E820ENTRY; +AssertCompileSize(INT15E820ENTRY,24); + + +/** @name INT15E820_TYPE_XXX - Memory types returned by int 15h function 0xe820. + * @{ */ +#define INT15E820_TYPE_USABLE 1 /**< Usable RAM. */ +#define INT15E820_TYPE_RESERVED 2 /**< Reserved by the system, unusable. */ +#define INT15E820_TYPE_ACPI_RECLAIMABLE 3 /**< ACPI reclaimable memory, whatever that means. */ +#define INT15E820_TYPE_ACPI_NVS 4 /**< ACPI non-volatile storage? */ +#define INT15E820_TYPE_BAD 5 /**< Bad memory, unusable. */ +/** @} */ + + +/** + * Performs an int 15h function 0xe820 call. + * + * @returns Success indicator. + * @param pEntry The return buffer. + * @param pcbEntry Input: The size of the buffer (min 20 bytes); + * Output: The size of the returned data. + * @param puContinuationValue Where to get and return the continuation value (EBX) + * Set to zero the for the first call. Returned as zero + * after the last entry. + */ +BS3_MODE_PROTO_STUB(bool, Bs3BiosInt15hE820,(INT15E820ENTRY BS3_FAR *pEntry, uint32_t BS3_FAR *pcbEntry, + uint32_t BS3_FAR *puContinuationValue)); + +/** + * Performs an int 15h function 0x88 call. + * + * @returns UINT32_MAX on failure, number of KBs above 1MB otherwise. + */ +#if ARCH_BITS != 16 || !defined(BS3_BIOS_INLINE_RM) +BS3_MODE_PROTO_STUB(uint32_t, Bs3BiosInt15h88,(void)); +#else +BS3_DECL(uint32_t) Bs3BiosInt15h88(void); +# pragma aux Bs3BiosInt15h88 = \ + ".286" \ + "clc" \ + "mov ax, 08800h" \ + "int 15h" \ + "jc failed" \ + "xor dx, dx" \ + "jmp done" \ + "failed:" \ + "xor ax, ax" \ + "dec ax" \ + "mov dx, ax" \ + "done:" \ + value [ax dx] \ + modify exact [ax bx cx dx es]; +#endif + +/** @} */ + + +/** @} */ + +RT_C_DECLS_END + + +/* + * Include default function symbol mangling. + */ +#include "bs3kit-mangling-code.h" + +/* + * Change 16-bit text segment if requested. + */ +#if defined(BS3_USE_ALT_16BIT_TEXT_SEG) && ARCH_BITS == 16 && !defined(BS3_DONT_CHANGE_TEXT_SEG) +# if (defined(BS3_USE_RM_TEXT_SEG) + defined(BS3_USE_X0_TEXT_SEG) + defined(BS3_USE_X1_TEXT_SEG)) != 1 +# error "Cannot set more than one alternative 16-bit text segment!" +# elif defined(BS3_USE_RM_TEXT_SEG) +# pragma code_seg("BS3RMTEXT16", "BS3CLASS16RMCODE") +# elif defined(BS3_USE_X0_TEXT_SEG) +# pragma code_seg("BS3X0TEXT16", "BS3CLASS16X0CODE") +# elif defined(BS3_USE_X1_TEXT_SEG) +# pragma code_seg("BS3X1TEXT16", "BS3CLASS16X1CODE") +# else +# error "Huh? Which alternative text segment did you want again?" +# endif +#endif + +#endif /* !BS3KIT_INCLUDED_bs3kit_h */ + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit.mac b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit.mac new file mode 100644 index 00000000..fa1a56f0 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit.mac @@ -0,0 +1,1767 @@ +; $Id: bs3kit.mac $ +;; @file +; BS3Kit - structures, symbols, macros and stuff. +; + +; +; Copyright (C) 2007-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%ifndef ___bs3kit_mac___ +%define ___bs3kit_mac___ + +; +; Before we can include anything, we need to override NAME and switch section. +; If we don't do the latter we end up with an unused 'text' section. +; + +; Drop the asmdefs-first.mac header for native bs3kit files. +%undef RT_ASMDEFS_INC_FIRST_FILE + +;; +; Macro for setting register aliases according to the bit count given by %1. +; +%macro BS3_SET_REG_ALIASES 1 + ; + ; Register aliases. + ; + %if %1 == 64 + %define xCB 8 + %define xDEF dq + %define xRES resq + %define xPRE qword + %define xSP rsp + %define xBP rbp + %define xAX rax + %define xBX rbx + %define xCX rcx + %define xDX rdx + %define xDI rdi + %define xSI rsi + %define xWrtRIP wrt rip + %define xPUSHF pushfq + %define xPOPF popfq + %define xRETF o64 retf + %elif %1 == 32 + %define xCB 4 + %define xDEF dd + %define xRES resd + %define xPRE dword + %define xSP esp + %define xBP ebp + %define xAX eax + %define xBX ebx + %define xCX ecx + %define xDX edx + %define xDI edi + %define xSI esi + %define xWrtRIP + %define xPUSHF pushfd + %define xPOPF popfd + %define xRETF retf + %elif %1 == 16 + %define xCB 2 + %define xDEF dw + %define xRES resw + %define xPRE word + %define xSP sp + %define xBP bp + %define xAX ax + %define xBX bx + %define xCX cx + %define xDX dx + %define xDI di + %define xSI si + %define xWrtRIP + %define xPUSHF pushf + %define xPOPF popf + %define xRETF retf + %else + %error "Invalid BS3_SET_REG_ALIASES argument:" %1 + %endif + + + ; + ; Register names corresponding to the max size for pop/push <reg>. + ; + ; 16-bit can push both 32-bit and 16-bit registers. This 's' prefixed variant + ; is used when 16-bit should use the 32-bit register. + ; + %if %1 == 64 + %define sCB 8 + %define sDEF dq + %define sRES resq + %define sPRE qword + %define sSP rsp + %define sBP rbp + %define sAX rax + %define sBX rbx + %define sCX rcx + %define sDX rdx + %define sDI rdi + %define sSI rsi + %define sPUSHF pushfq + %define sPOPF popfq + %else + %define sCB 4 + %define sDEF dd + %define sRES resd + %define sPRE dword + %define sSP esp + %define sBP ebp + %define sAX eax + %define sBX ebx + %define sCX ecx + %define sDX edx + %define sDI edi + %define sSI esi + %define sPUSHF pushfd + %define sPOPF popfd + %endif +%endmacro + +;; +; Redefines macros that follows __BITS__. +%macro BS3_SET_BITS_MACROS 1 + ;; Emulate the __BITS__ macro in NASM 2.0+. Follows BS3_SET_BITS. + %ifdef __YASM__ + %undef __BITS__ + %define __BITS__ %1 + %endif + + ;; Mostly internal macro. Follows BS3_SET_BITS. + %undef BS3_NAME_UNDERSCORE + %define BS3_NAME_UNDERSCORE _ + + ;; For segment overrides and stuff. Follows BS3_SET_BITS. + %undef BS3_ONLY_16BIT + %if %1 == 16 + %define BS3_ONLY_16BIT(a_Expr) a_Expr + %else + %define BS3_ONLY_16BIT(a_Expr) + %endif + + ;; For odd 64-bit stuff. Follows BS3_SET_BITS. + %undef BS3_ONLY_64BIT + %if %1 == 64 + %define BS3_ONLY_64BIT(a_Expr) a_Expr + %else + %define BS3_ONLY_64BIT(a_Expr) + %endif + + ;; For segment overrides and stuff. Follows BS3_SET_BITS. + %undef BS3_NOT_64BIT + %if %1 == 64 + %define BS3_NOT_64BIT(a_Expr) + %else + %define BS3_NOT_64BIT(a_Expr) a_Expr + %endif + + ;; For stack cleanups and similar where each bit mode is different. Follows BS3_SET_BITS. + %undef BS3_IF_16_32_64BIT + %if %1 == 16 + %define BS3_IF_16_32_64BIT(a_16BitExpr, a_32BitExpr, a_64BitExpr) a_16BitExpr + %elif %1 == 32 + %define BS3_IF_16_32_64BIT(a_16BitExpr, a_32BitExpr, a_64BitExpr) a_32BitExpr + %else + %define BS3_IF_16_32_64BIT(a_16BitExpr, a_32BitExpr, a_64BitExpr) a_64BitExpr + %endif + + ;; For RIP relative addressing in 64-bit mode and absolute addressing in + ; other modes. Follows BS3_SET_BITS. + %undef BS3_WRT_RIP + %if %1 == 64 + %define BS3_WRT_RIP(a_Sym) rel a_Sym + %else + %define BS3_WRT_RIP(a_Sym) a_Sym + %endif + + %undef BS3_LEA_MOV_WRT_RIP + %if %1 == 64 + %define BS3_LEA_MOV_WRT_RIP(a_DstReg, a_Sym) lea a_DstReg, [BS3_WRT_RIP(a_Sym)] + %else + %define BS3_LEA_MOV_WRT_RIP(a_DstReg, a_Sym) mov a_DstReg, a_Sym + %endif + + ;; @def BS3_DATA16_WRT + ; For accessing BS3DATA16 correctly. + ; @param a_Var The BS3DATA16 variable. + %undef BS3_DATA16_WRT + %if %1 == 16 + %define BS3_DATA16_WRT(a_Var) a_Var wrt BS3KIT_GRPNM_DATA16 + %elif %1 == 32 + %define BS3_DATA16_WRT(a_Var) a_Var wrt FLAT + %else + %define BS3_DATA16_WRT(a_Var) BS3_WRT_RIP(a_Var) wrt FLAT + %endif + + ;; @def BS3_TEXT16_WRT + ; For accessing BS3DATA16 correctly. + ; @param a_Label The BS3TEXT16 label. + %undef BS3_TEXT16_WRT + %if %1 == 16 + %define BS3_TEXT16_WRT(a_Label) a_Label wrt CGROUP16 + %elif %1 == 32 + %define BS3_TEXT16_WRT(a_Label) a_Label wrt FLAT + %else + %define BS3_TEXT16_WRT(a_Label) BS3_WRT_RIP(a_Label) wrt FLAT + %endif + + %undef BS3_IF_16BIT_OTHERWISE + %if %1 == 16 + %define BS3_IF_16BIT_OTHERWISE(a_16BitExpr, a_OtherwiseExpr) a_16BitExpr + %else + %define BS3_IF_16BIT_OTHERWISE(a_16BitExpr, a_OtherwiseExpr) a_OtherwiseExpr + %endif + + %undef BS3_IF_32BIT_OTHERWISE + %if %1 == 32 + %define BS3_IF_32BIT_OTHERWISE(a_32BitExpr, a_OtherwiseExpr) a_32BitExpr + %else + %define BS3_IF_32BIT_OTHERWISE(a_32BitExpr, a_OtherwiseExpr) a_OtherwiseExpr + %endif + + %undef BS3_IF_64BIT_OTHERWISE + %if %1 == 64 + %define BS3_IF_64BIT_OTHERWISE(a_64BitExpr, a_OtherwiseExpr) a_64BitExpr + %else + %define BS3_IF_64BIT_OTHERWISE(a_64BitExpr, a_OtherwiseExpr) a_OtherwiseExpr + %endif + + ;; + ; Same as BS3_CMN_NM except in 16-bit mode, it will generate the far name. + ; (16-bit code generally have both near and far callable symbols, so we won't + ; be restricted to 64KB test code.) + %if %1 == 16 + %define BS3_CMN_NM_FAR(a_Name) BS3_NAME_UNDERSCORE %+ a_Name %+ _f %+ __BITS__ + %else + %define BS3_CMN_NM_FAR(a_Name) BS3_CMN_NM(a_Name) + %endif + +%endmacro + +; Default to register aliases for ARCH_BITS. +BS3_SET_REG_ALIASES ARCH_BITS + +; Define macros for ARCH_BITS. +BS3_SET_BITS_MACROS ARCH_BITS + + +;; Wrapper around BITS. +; Updates __BITS__ (built-in variable in nasm, we work it for yasm) as well +; a number of convenient macros and register aliases. +; +; @param %1 The CPU bit count: 16, 32 or 64 +; @remarks ARCH_BITS is not modified and will remain what it was on the +; assembler command line. +%macro BS3_SET_BITS 1 + BITS %1 + BS3_SET_BITS_MACROS %1 + BS3_SET_REG_ALIASES %1 +%endmacro + +;; +; For instruction that should only be emitted in 16-bit mode. Follows BS3_SET_BITS. +; BONLY16 normally goes in column 1. +%macro BONLY16 1+ + %if __BITS__ == 16 + %1 + %endif +%endmacro + +;; +; For instruction that should only be emitted in 32-bit mode. Follows BS3_SET_BITS. +; BONLY32 normally goes in column 1. +%macro BONLY32 1+ + %if __BITS__ == 32 + %1 + %endif +%endmacro + +;; +; For instruction that should only be emitted in 64-bit mode. Follows BS3_SET_BITS. +; BONLY64 normally goes in column 1. +%macro BONLY64 1+ + %if __BITS__ == 64 + %1 + %endif +%endmacro + + + +;; @name Segment definitions. +;; @{ + +%ifndef ASM_FORMAT_BIN +; !!HACK ALERT!! +; +; To make FLAT actually be flat, i.e. have a base of 0 rather than the same as +; the target (?) segment, we tweak it a little bit here. We associate a segment +; with it so that we can get at it in the class/segment ordering directives +; we pass to the linker. The segment does not contain any data or anything, it +; is just an empty one which we assign the address of zero. +; +; Look for 'clname BS3FLAT segaddr=0x0000' and 'segment BS3FLAT segaddr=0x0000' +; in the makefile. +; +; !!HACK ALERT!! +segment BS3FLAT use32 class=BS3FLAT +GROUP FLAT BS3FLAT +%endif + + +;; +; Changes to the BS3TEXT16 segment, defining it if necessary. +; @param %1 The bitcount to invoke BS3_SET_BITS with, default is 16. +%macro BS3_BEGIN_TEXT16 0-1 16 + %ifndef BS3_BEGIN_TEXT16_NOT_FIRST + %define BS3_BEGIN_TEXT16_NOT_FIRST + section BS3TEXT16 align=2 CLASS=BS3CLASS16CODE PUBLIC USE16 + %ifndef BS3_BEGIN_TEXT16_WITHOUT_GROUP ; bs3-first-common.mac trick. + %ifndef BS3_BEGIN_TEXT16_NEARSTUBS_NOT_FIRST + %define BS3_BEGIN_TEXT16_NEARSTUBS_NOT_FIRST + section BS3TEXT16_NEARSTUBS align=1 CLASS=BS3CLASS16CODE PUBLIC USE16 + %endif + %ifndef BS3_BEGIN_TEXT16_FARSTUBS_NOT_FIRST + %define BS3_BEGIN_TEXT16_FARSTUBS_NOT_FIRST + section BS3TEXT16_FARSTUBS align=1 CLASS=BS3CLASS16CODE PUBLIC USE16 + %endif + GROUP CGROUP16 BS3TEXT16 BS3TEXT16_NEARSTUBS BS3TEXT16_FARSTUBS + section BS3TEXT16 + %endif + %else + section BS3TEXT16 + %endif + %undef BS3_CUR_SEG_BEGIN_MACRO + %xdefine BS3_CUR_SEG_BEGIN_MACRO BS3_BEGIN_TEXT16 + BS3_SET_BITS %1 +%endmacro + +%macro BS3_BEGIN_TEXT16_NEARSTUBS 0 + %ifndef BS3_BEGIN_TEXT16_NEARSTUBS_NOT_FIRST + %define BS3_BEGIN_TEXT16_NEARSTUBS_NOT_FIRST + section BS3TEXT16_NEARSTUBS align=1 CLASS=BS3CLASS16CODE PUBLIC USE16 + %else + section BS3TEXT16_NEARSTUBS + %endif + %undef BS3_CUR_SEG_BEGIN_MACRO + %xdefine BS3_CUR_SEG_BEGIN_MACRO BS3_BEGIN_TEXT16_NEARSTUBS + BS3_SET_BITS 16 +%endmacro + +%macro BS3_BEGIN_TEXT16_FARSTUBS 0 + %ifndef BS3_BEGIN_TEXT16_FARSTUBS_NOT_FIRST + %define BS3_BEGIN_TEXT16_FARSTUBS_NOT_FIRST + section BS3TEXT16_FARSTUBS align=1 CLASS=BS3CLASS16CODE PUBLIC USE16 + %else + section BS3TEXT16_FARSTUBS + %endif + %undef BS3_CUR_SEG_BEGIN_MACRO + %xdefine BS3_CUR_SEG_BEGIN_MACRO BS3_BEGIN_TEXT16_FARSTUBS + BS3_SET_BITS 16 +%endmacro + +%macro BS3_BEGIN_RMTEXT16 0-1 2 + %ifndef BS3_BEGIN_RMTEXT16_NOT_FIRST + %define BS3_BEGIN_RMTEXT16_NOT_FIRST + section BS3RMTEXT16 align=%1 CLASS=BS3CLASS16RMCODE PUBLIC USE16 + %ifndef BS3_BEGIN_RMTEXT16_WITHOUT_GROUP ; bs3-first-common.mac trick. + GROUP BS3GROUPRMTEXT16 BS3RMTEXT16 + %endif + %else + section BS3RMTEXT16 + %endif + %undef BS3_CUR_SEG_BEGIN_MACRO + %xdefine BS3_CUR_SEG_BEGIN_MACRO BS3_BEGIN_RMTEXT16 + BS3_SET_BITS 16 +%endmacro + +%macro BS3_BEGIN_X0TEXT16 0-1 2 + %ifndef BS3_BEGIN_X0TEXT16_NOT_FIRST + %define BS3_BEGIN_X0TEXT16_NOT_FIRST + section BS3X0TEXT16 align=%1 CLASS=BS3CLASS16X0CODE PUBLIC USE16 + %ifndef BS3_BEGIN_X0TEXT16_WITHOUT_GROUP ; bs3-first-common.mac trick. + GROUP BS3GROUPX0TEXT16 BS3X0TEXT16 + %endif + %else + section BS3X0TEXT16 + %endif + %undef BS3_CUR_SEG_BEGIN_MACRO + %xdefine BS3_CUR_SEG_BEGIN_MACRO BS3_BEGIN_X0TEXT16 + BS3_SET_BITS 16 +%endmacro + +%macro BS3_BEGIN_X1TEXT16 0-1 2 + %ifndef BS3_BEGIN_X1TEXT16_NOT_FIRST + %define BS3_BEGIN_X1TEXT16_NOT_FIRST + section BS3X1TEXT16 align=%1 CLASS=BS3CLASS16X1CODE PUBLIC USE16 + %ifndef BS3_BEGIN_X1TEXT16_WITHOUT_GROUP ; bs3-first-common.mac trick. + GROUP BS3GROUPX1TEXT16 BS3X1TEXT16 + %endif + %else + section BS3X1TEXT16 + %endif + %undef BS3_CUR_SEG_BEGIN_MACRO + %xdefine BS3_CUR_SEG_BEGIN_MACRO BS3_BEGIN_X1TEXT16 + BS3_SET_BITS 16 +%endmacro + + +%macro BS3_BEGIN_DATA16 0-1 2 + %ifndef BS3_BEGIN_DATA16_NOT_FIRST + %define BS3_BEGIN_DATA16_NOT_FIRST + section BS3DATA16 align=%1 CLASS=BS3KIT_CLASS_DATA16 PUBLIC USE16 + %ifndef BS3_BEGIN_DATA16_WITHOUT_GROUP ; bs3-first-common.mac trick. + GROUP BS3KIT_GRPNM_DATA16 BS3DATA16 + %endif + %else + section BS3DATA16 + %endif + %undef BS3_CUR_SEG_BEGIN_MACRO + %xdefine BS3_CUR_SEG_BEGIN_MACRO BS3_BEGIN_DATA16 + BS3_SET_BITS 16 +%endmacro + +%macro BS3_BEGIN_TEXT32 0-1 2 + %ifndef BS3_BEGIN_TEXT32_NOT_FIRST + %define BS3_BEGIN_TEXT32_NOT_FIRST + section BS3TEXT32 align=%1 CLASS=BS3CLASS32CODE PUBLIC USE32 FLAT + %else + section BS3TEXT32 + %endif + %undef BS3_CUR_SEG_BEGIN_MACRO + %xdefine BS3_CUR_SEG_BEGIN_MACRO BS3_BEGIN_TEXT32 + BS3_SET_BITS 32 +%endmacro + +%macro BS3_BEGIN_DATA32 0-1 16 + %ifndef BS3_BEGIN_DATA32_NOT_FIRST + %define BS3_BEGIN_DATA32_NOT_FIRST + section BS3DATA32 align=%1 CLASS=FAR_DATA PUBLIC USE32 ;FLAT - compiler doesn't make data flat. + %else + section BS3DATA32 + %endif + %undef BS3_CUR_SEG_BEGIN_MACRO + %xdefine BS3_CUR_SEG_BEGIN_MACRO BS3_BEGIN_DATA32 + BS3_SET_BITS 32 +%endmacro + +%macro BS3_BEGIN_TEXT64 0-1 2 + %ifndef BS3_BEGIN_TEXT64_NOT_FIRST + %define BS3_BEGIN_TEXT64_NOT_FIRST + section BS3TEXT64 align=%1 CLASS=BS3CLASS64CODE PUBLIC USE32 FLAT + %else + section BS3TEXT64 + %endif + %undef BS3_CUR_SEG_BEGIN_MACRO + %xdefine BS3_CUR_SEG_BEGIN_MACRO BS3_BEGIN_TEXT64 + BS3_SET_BITS 64 +%endmacro + +%macro BS3_BEGIN_DATA64 0-1 16 + %ifndef BS3_BEGIN_DATA64_NOT_FIRST + %define BS3_BEGIN_DATA64_NOT_FIRST + section BS3DATA64 align=%1 CLASS=FAR_DATA PUBLIC USE32 ;FLAT (see DATA32) + %else + section BS3DATA64 + %endif + %undef BS3_CUR_SEG_BEGIN_MACRO + %xdefine BS3_CUR_SEG_BEGIN_MACRO BS3_BEGIN_DATA64 + BS3_SET_BITS 64 +%endmacro + +;; The system data segment containing the GDT, TSSes and IDTs. +%macro BS3_BEGIN_SYSTEM16 0-1 16 + %ifndef BS3_BEGIN_SYSTEM16_NOT_FIRST + %define BS3_BEGIN_SYSTEM16_NOT_FIRST + section BS3SYSTEM16 align=%1 CLASS=BS3SYSTEM16 PUBLIC USE16 + %else + section BS3SYSTEM16 + %endif + %undef BS3_CUR_SEG_BEGIN_MACRO + %xdefine BS3_CUR_SEG_BEGIN_MACRO BS3_BEGIN_SYSTEM16 + BS3_SET_BITS 16 +%endmacro + +;; Default text section. +%macro BS3_BEGIN_DEFAULT_TEXT 0 + %if ARCH_BITS == 16 + BS3_BEGIN_TEXT16 + %elif ARCH_BITS == 32 + BS3_BEGIN_TEXT32 + %elif ARCH_BITS == 64 + BS3_BEGIN_TEXT64 + %else + %error "ARCH_BITS must be defined as either 16, 32, or 64!" + INVALID_ARCH_BITS + %endif +%endmacro + +;; @} + + +; +; Now, ditch the default 'text' section and define our own NAME macro. +; +%ifndef ASM_FORMAT_BIN + BS3_BEGIN_DEFAULT_TEXT + BS3_BEGIN_DEFAULT_TEXT ; stupid nasm automagically repeats the segment attributes. +%endif + +;; When using watcom + OMF, we're using __cdecl by default, which +; get an underscore added in front. +%define NAME(name) _ %+ NAME_OVERLOAD(name) + + +; +; Include the standard headers from iprt. +; + + +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" + + +;; +; Extern macro which mangles the name using NAME(). +%macro EXTERN 1 + extern NAME(%1) +%endmacro + +;; +; Mangles a common name according to the current cpu bit count. +; @remarks Requires the use of the BS3_SET_BITS macro instead of the BITS directive. +%define BS3_CMN_NM(a_Name) BS3_NAME_UNDERSCORE %+ a_Name %+ _c %+ __BITS__ + +;; +; Extern macro which mangles the common name correctly, redefining the unmangled +; name to the mangled one for ease of use. +; +; @param %1 The unmangled common name. +; +; @remarks Must enter the segment in which this name is defined. +; +%macro BS3_EXTERN_CMN 1 + extern BS3_CMN_NM(%1) + %undef %1 + %define %1 BS3_CMN_NM(%1) +%endmacro + +;; +; Same as BS3_EXTERN_CMN except it picks the far variant in 16-bit code. +; +; @param %1 The unmangled common name. +; +; @remarks Must enter the segment in which this name is defined. +; +%macro BS3_EXTERN_CMN_FAR 1 + extern BS3_CMN_NM_FAR(%1) + %undef %1 + %define %1 BS3_CMN_NM_FAR(%1) +%endmacro + +;; @def BS3_EXTERN_TMPL +; Mangles the given name into a template specific one. For ease of use, the +; name is redefined to the mangled one, just like BS3_EXTERN_CMN does. +; @note Segment does not change. +%macro BS3_EXTERN_TMPL 1 + extern TMPL_NM(%1) + %undef %1 + %define %1 TMPL_NM(%1) +%endmacro + + +;; +; Mangles a 16-bit and 32-bit accessible data name. +; @remarks Requires the use of the BS3_SET_BITS macro instead of the BITS directive. +%define BS3_DATA_NM(a_Name) _ %+ a_Name + +;; +; Extern macro which mangles a DATA16 symbol correctly, redefining the +; unmangled name to the mangled one for ease of use. +; +; @param %1 The unmangled common name. +; +; @remarks Will change to the DATA16 segment, use must switch back afterwards! +; +%macro BS3_EXTERN_DATA16 1 + BS3_BEGIN_DATA16 + extern _ %+ %1 + %undef %1 + %define %1 _ %+ %1 +%endmacro + +;; +; Extern macro which mangles a BS3SYSTEM16 symbol correctly, redefining the +; unmangled name to the mangled one for ease of use. +; +; @param %1 The unmangled common name. +; +; @remarks Will change to the SYSTEM16 segment, use must switch back afterwards! +; +%macro BS3_EXTERN_SYSTEM16 1 + BS3_BEGIN_SYSTEM16 + extern _ %+ %1 + %undef %1 + %define %1 _ %+ %1 +%endmacro + + +;; +; Global name with ELF attributes and size. +; +; This differs from GLOBALNAME_EX in that it expects a mangled symbol name, +; and allows for nasm style symbol size expressions. +; +; @param %1 The mangled name. +; @param %2 Symbol attributes. +; @param %3 The size expression. +; +%macro BS3_GLOBAL_NAME_EX 3 +global %1 +%1: +%undef BS3_LAST_LABEL +%xdefine BS3_LAST_LABEL %1 +%endmacro + +;; +; Global local label. +; +; This should be used when switching segments and jumping to it via a local lable. +; It makes the lable visible to the debugger and map file. +; +%macro BS3_GLOBAL_LOCAL_LABEL 1 +global RT_CONCAT(BS3_LAST_LABEL,%1) +%1: +%endmacro + +;; +; Global data unmangled label. +; +; @param %1 The unmangled name. +; @param %2 The size (0 is fine). +; +%macro BS3_GLOBAL_DATA 2 +BS3_GLOBAL_NAME_EX BS3_DATA_NM(%1), , %2 +%endmacro + +;; +; Starts a procedure. +; +; This differs from BEGINPROC in that it expects a mangled symbol name and +; does the NASM symbol size stuff. +; +; @param %1 The mangled name. +; +%macro BS3_PROC_BEGIN 1 +BS3_GLOBAL_NAME_EX %1, function, (%1 %+ _EndProc - %1) +%endmacro + +;; +; Ends a procedure. +; +; Counter part to BS3_PROC_BEGIN. +; +; @param %1 The mangled name. +; +%macro BS3_PROC_END 1 +BS3_GLOBAL_NAME_EX %1 %+ _EndProc, function hidden, (%1 %+ _EndProc - %1) + int3 ; handy and avoids overlapping labels. +%endmacro + + +;; @name BS3_PBC_XXX - For use as the 2nd parameter to BS3_PROC_BEGIN_CMN and BS3_PROC_BEGIN_MODE. +;; @{ +%define BS3_PBC_NEAR 1 ;;< Only near. +%define BS3_PBC_FAR 2 ;;< Only far. +%define BS3_PBC_HYBRID 3 ;;< Hybrid near/far procedure, trashing AX. Use BS3_HYBRID_RET to return. +%define BS3_PBC_HYBRID_SAFE 4 ;;< Hybrid near/far procedure, no trashing but slower. Use BS3_HYBRID_RET to return. +%define BS3_PBC_HYBRID_0_ARGS 5 ;;< Hybrid near/far procedure, no parameters so separate far stub, no trashing, fast near calls. +;; @} + +;; Internal begin procedure macro. +; +; @param 1 The near name. +; @param 2 The far name +; @param 3 BS3_PBC_XXX. +%macro BS3_PROC_BEGIN_INT 3 + ;%warning "BS3_PROC_BEGIN_INT:" 1=%1 2=%2 3=%3 + %undef BS3_CUR_PROC_FLAGS + %if __BITS__ == 16 + %if %3 == BS3_PBC_NEAR + %xdefine BS3_CUR_PROC_FLAGS BS3_PBC_NEAR + %xdefine cbCurRetAddr 2 + BS3_PROC_BEGIN %1 + + %elif %3 == BS3_PBC_FAR + %xdefine BS3_CUR_PROC_FLAGS BS3_PBC_FAR + %xdefine cbCurRetAddr 4 + BS3_PROC_BEGIN %2 + + %elif %3 == BS3_PBC_HYBRID + %xdefine BS3_CUR_PROC_FLAGS BS3_PBC_HYBRID + %xdefine cbCurRetAddr 4 + BS3_GLOBAL_NAME_EX %1, function, 3 + pop ax + push cs + push ax + BS3_PROC_BEGIN %2 + + %elif %3 == BS3_PBC_HYBRID_SAFE + %xdefine BS3_CUR_PROC_FLAGS BS3_PBC_HYBRID_SAFE + %xdefine cbCurRetAddr 4 + BS3_GLOBAL_NAME_EX %1, function, 3 + extern Bs3CreateHybridFarRet_c16 + call Bs3CreateHybridFarRet_c16 + BS3_PROC_BEGIN %2 + + %elif %3 == BS3_PBC_HYBRID_0_ARGS + %xdefine BS3_CUR_PROC_FLAGS BS3_PBC_NEAR + %xdefine cbCurRetAddr 2 + %xdefine TMP_BEGIN_PREV_SEG BS3_CUR_SEG_BEGIN_MACRO + + BS3_BEGIN_TEXT16_FARSTUBS + BS3_PROC_BEGIN %2 + call %1 + retf + BS3_PROC_END %2 + + TMP_BEGIN_PREV_SEG + BS3_PROC_BEGIN %1 + %undef TMP_BEGIN_PREV_SEG + + %else + %error BS3_PROC_BEGIN_CMN parameter 2 value %3 is not recognized. + + %xdefine BS3_CUR_PROC_FLAGS BS3_PBC_NEAR + %xdefine cbCurRetAddr 4 + BS3_PROC_BEGIN %1 + %endif + %else + %xdefine BS3_CUR_PROC_FLAGS BS3_PBC_NEAR + %xdefine cbCurRetAddr xCB + BS3_PROC_BEGIN %1 + %endif +%endmacro + +;; Internal end procedure macro +; +; @param 1 The near name. +; @param 2 The far name +; +%macro BS3_PROC_END_INT 2 + %if __BITS__ == 16 + %if BS3_CUR_PROC_FLAGS == BS3_PBC_NEAR + BS3_PROC_END %1 + %else + BS3_PROC_END %2 + %endif + %else + BS3_PROC_END %1 + %endif + %undef BS3_CUR_PROC_FLAGS + %undef cbCurRetAddr +%endmacro + + +;; Convenience macro for defining common procedures. +; This will emit both near and far 16-bit symbols according to parameter %2 (BS3_PBC_XXX). +%macro BS3_PROC_BEGIN_CMN 2 + BS3_PROC_BEGIN_INT BS3_CMN_NM(%1), BS3_CMN_NM_FAR(%1), %2 +%endmacro + +;; Convenience macro for defining common procedures. +%macro BS3_PROC_END_CMN 1 + BS3_PROC_END_INT BS3_CMN_NM(%1), BS3_CMN_NM_FAR(%1) +%endmacro + +;; +; Generate a safe 16-bit far stub for function %1, shuffling %2 bytes of parameters. +; +; This does absolutely nothing in 32-bit and 64-bit mode. +; +; @param 1 The function basename. +; @param 2 The number of bytes of parameters on the stack, must be a multiple of 2. +; @remarks Changes the segment to TEXT16. +; +%macro BS3_CMN_FAR_STUB 2 + %if %2 <= 1 || (%2 & 1) + %error Invalid parameter frame size passed to BS3_CMN_FAR_STUB: %2 + %endif + %if __BITS__ == 16 +BS3_BEGIN_TEXT16_FARSTUBS +BS3_PROC_BEGIN_CMN %1, BS3_PBC_FAR + CPU 8086 + inc bp ; Odd bp is far call indicator. + push bp + mov bp, sp + %assign offParam %2 + %rep %2/2 + push word [bp + xCB + cbCurRetAddr + offParam - 2] + %assign offParam offParam - 2 + %endrep + call BS3_CMN_NM(%1) + add sp, %2 + pop bp + dec bp + retf +BS3_PROC_END_CMN %1 +BS3_BEGIN_TEXT16 + %endif +%endmacro + + +;; Convenience macro for defining mode specific procedures. +%macro BS3_PROC_BEGIN_MODE 2 + ;%warning "BS3_PROC_BEGIN_MODE: 1=" %1 "2=" %2 + BS3_PROC_BEGIN_INT TMPL_NM(%1), TMPL_FAR_NM(%1), %2 +%endmacro + +;; Convenience macro for defining mode specific procedures. +%macro BS3_PROC_END_MODE 1 + BS3_PROC_END_INT TMPL_NM(%1), TMPL_FAR_NM(%1) +%endmacro + +;; Does a far return in 16-bit code, near return in 32-bit and 64-bit. +; This is for use with BS3_PBC_XXX +%macro BS3_HYBRID_RET 0-1 + %if __BITS__ == 16 + %if %0 > 0 + %if BS3_CUR_PROC_FLAGS == BS3_PBC_NEAR || BS3_CUR_PROC_FLAGS == BS3_PBC_HYBRID_0_ARGS + ret %1 + %else + retf %1 + %endif + %else + %if BS3_CUR_PROC_FLAGS == BS3_PBC_NEAR || BS3_CUR_PROC_FLAGS == BS3_PBC_HYBRID_0_ARGS + ret + %else + retf + %endif + %endif + %else + %if BS3_CUR_PROC_FLAGS != BS3_PBC_NEAR + %error Expected BS3_CUR_PROC_FLAGS to be BS3_PBC_NEAR in non-16-bit code. + %endif + %if %0 > 0 + ret %1 + %else + ret + %endif + %endif +%endmacro + + +;; +; Prologue hacks for 64-bit code. +; +; This saves the four register parameters onto the stack so we can pretend +; the calling convention is stack based. The 64-bit calling convension is +; the microsoft one, so this is straight forward. +; +; Pairs with BS3_CALL_CONV_EPILOG. +; +; @param %1 The number of parameters. +; +; @remarks Must be invoked before any stack changing instructions are emitted. +; +%macro BS3_CALL_CONV_PROLOG 1 + %undef BS3_CALL_CONV_PROLOG_PARAMS + %define BS3_CALL_CONV_PROLOG_PARAMS %1 + %if __BITS__ == 64 + %if %1 >= 1 + mov [rsp + 008h], rcx + %elifdef BS3_STRICT + and qword [rsp + 008h], 1 + %endif + %if %1 >= 2 + mov [rsp + 010h], rdx + %elifdef BS3_STRICT + and qword [rsp + 010h], 2 + %endif + %if %1 >= 3 + mov [rsp + 018h], r8 + %elifdef BS3_STRICT + and qword [rsp + 018h], 3 + %endif + %if %1 >= 4 + mov [rsp + 020h], r9 + %elifdef BS3_STRICT + and qword [rsp + 020h], 4 + %endif + %endif +%endmacro + +;; +; Epilogue hacks for 64-bit code. +; +; Counter part to BS3_CALL_CONV_PROLOG. +; +; @param %1 The number of parameters. +; +; @remarks Must be invoked right before the return instruction as it uses RSP. +; +%macro BS3_CALL_CONV_EPILOG 1 + %if BS3_CALL_CONV_PROLOG_PARAMS != %1 + %error "BS3_CALL_CONV_EPILOG argument differs from BS3_CALL_CONV_PROLOG." + %endif + %if __BITS__ == 64 + %ifdef BS3_STRICT + mov dword [rsp + 008h], 31h + mov dword [rsp + 010h], 32h + mov dword [rsp + 018h], 33h + mov dword [rsp + 020h], 34h + %endif + %endif +%endmacro + +;; +; Wrapper for the call instruction that hides calling convension differences. +; +; This always calls %1. +; In 64-bit code, it will load up to 4 parameters into register. +; +; @param %1 The function to call (mangled). +; @param %2 The number of parameters. +; +%macro BS3_CALL 2 + %if __BITS__ == 64 + %if %2 >= 1 + mov rcx, [rsp] + %ifdef BS3_STRICT + and qword [rsp], 11h + %endif + %endif + %if %2 >= 2 + mov rdx, [rsp + 008h] + %ifdef BS3_STRICT + and qword [rsp + 008h], 12h + %endif + %endif + %if %2 >= 3 + mov r8, [rsp + 010h] + %ifdef BS3_STRICT + and qword [rsp + 010h], 13h + %endif + %endif + %if %2 >= 4 + mov r9, [rsp + 018h] + %ifdef BS3_STRICT + and qword [rsp + 018h], 14h + %endif + %endif + %endif + call %1 +%endmacro + + +;; @name Execution Modes +; @{ +%define BS3_MODE_INVALID 000h +%define BS3_MODE_RM 001h ;;< real mode. +%define BS3_MODE_PE16 011h ;;< 16-bit protected mode kernel+tss, running 16-bit code, unpaged. +%define BS3_MODE_PE16_32 012h ;;< 16-bit protected mode kernel+tss, running 32-bit code, unpaged. +%define BS3_MODE_PE16_V86 018h ;;< 16-bit protected mode kernel+tss, running virtual 8086 mode code, unpaged. +%define BS3_MODE_PE32 022h ;;< 32-bit protected mode kernel+tss, running 32-bit code, unpaged. +%define BS3_MODE_PE32_16 021h ;;< 32-bit protected mode kernel+tss, running 16-bit code, unpaged. +%define BS3_MODE_PEV86 028h ;;< 32-bit protected mode kernel+tss, running virtual 8086 mode code, unpaged. +%define BS3_MODE_PP16 031h ;;< 16-bit protected mode kernel+tss, running 16-bit code, paged. +%define BS3_MODE_PP16_32 032h ;;< 16-bit protected mode kernel+tss, running 32-bit code, paged. +%define BS3_MODE_PP16_V86 038h ;;< 16-bit protected mode kernel+tss, running virtual 8086 mode code, paged. +%define BS3_MODE_PP32 042h ;;< 32-bit protected mode kernel+tss, running 32-bit code, paged. +%define BS3_MODE_PP32_16 041h ;;< 32-bit protected mode kernel+tss, running 16-bit code, paged. +%define BS3_MODE_PPV86 048h ;;< 32-bit protected mode kernel+tss, running virtual 8086 mode code, paged. +%define BS3_MODE_PAE16 051h ;;< 16-bit protected mode kernel+tss, running 16-bit code, PAE paging. +%define BS3_MODE_PAE16_32 052h ;;< 16-bit protected mode kernel+tss, running 32-bit code, PAE paging. +%define BS3_MODE_PAE16_V86 058h ;;< 16-bit protected mode kernel+tss, running virtual 8086 mode, PAE paging. +%define BS3_MODE_PAE32 062h ;;< 32-bit protected mode kernel+tss, running 32-bit code, PAE paging. +%define BS3_MODE_PAE32_16 061h ;;< 32-bit protected mode kernel+tss, running 16-bit code, PAE paging. +%define BS3_MODE_PAEV86 068h ;;< 32-bit protected mode kernel+tss, running virtual 8086 mode, PAE paging. +%define BS3_MODE_LM16 071h ;;< 16-bit long mode (paged), kernel+tss always 64-bit. +%define BS3_MODE_LM32 072h ;;< 32-bit long mode (paged), kernel+tss always 64-bit. +%define BS3_MODE_LM64 074h ;;< 64-bit long mode (paged), kernel+tss always 64-bit. + +%define BS3_MODE_CODE_MASK 00fh ;;< Running code mask. +%define BS3_MODE_CODE_16 001h ;;< Running 16-bit code. +%define BS3_MODE_CODE_32 002h ;;< Running 32-bit code. +%define BS3_MODE_CODE_64 004h ;;< Running 64-bit code. +%define BS3_MODE_CODE_V86 008h ;;< Running 16-bit virtual 8086 code. + +%define BS3_MODE_SYS_MASK 0f0h ;;< kernel+tss mask. +%define BS3_MODE_SYS_RM 000h ;;< Real mode kernel+tss. +%define BS3_MODE_SYS_PE16 010h ;;< 16-bit protected mode kernel+tss. +%define BS3_MODE_SYS_PE32 020h ;;< 32-bit protected mode kernel+tss. +%define BS3_MODE_SYS_PP16 030h ;;< 16-bit paged protected mode kernel+tss. +%define BS3_MODE_SYS_PP32 040h ;;< 32-bit paged protected mode kernel+tss. +%define BS3_MODE_SYS_PAE16 050h ;;< 16-bit PAE paged protected mode kernel+tss. +%define BS3_MODE_SYS_PAE32 060h ;;< 32-bit PAE paged protected mode kernel+tss. +%define BS3_MODE_SYS_LM 070h ;;< 64-bit (paged) long mode protected mode kernel+tss. + +;; Whether the mode has paging enabled. +%define BS3_MODE_IS_PAGED(a_fMode) ((a_fMode) >= BS3_MODE_PP16) + +;; Whether the mode is running v8086 code. +%define BS3_MODE_IS_V86(a_fMode) (((a_fMode) & BS3_MODE_CODE_MASK) == BS3_MODE_CODE_V86) +;; Whether the we're executing in real mode or v8086 mode. +%define BS3_MODE_IS_RM_OR_V86(a_fMode) ((a_fMode) == BS3_MODE_RM || BS3_MODE_IS_V86(a_fMode)) +;; Whether the mode is running 16-bit code, except v8086. +%define BS3_MODE_IS_16BIT_CODE_NO_V86(a_fMode) (((a_fMode) & BS3_MODE_CODE_MASK) == BS3_MODE_CODE_16) +;; Whether the mode is running 16-bit code (includes v8086). +%define BS3_MODE_IS_16BIT_CODE(a_fMode) (BS3_MODE_IS_16BIT_CODE_NO_V86(a_fMode) || BS3_MODE_IS_V86(a_fMode)) +;; Whether the mode is running 32-bit code. +%define BS3_MODE_IS_32BIT_CODE(a_fMode) (((a_fMode) & BS3_MODE_CODE_MASK) == BS3_MODE_CODE_32) +;; Whether the mode is running 64-bit code. +%define BS3_MODE_IS_64BIT_CODE(a_fMode) (((a_fMode) & BS3_MODE_CODE_MASK) == BS3_MODE_CODE_64) + +;; Whether the system is in real mode. +%define BS3_MODE_IS_RM_SYS(a_fMode) (((a_fMode) & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_RM) +;; Whether the system is some 16-bit mode that isn't real mode. +%define BS3_MODE_IS_16BIT_SYS_NO_RM(a_fMode) ( ((a_fMode) & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PE16 \ + || ((a_fMode) & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PP16 \ + || ((a_fMode) & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PAE16) +;; Whether the system is some 16-bit mode (includes real mode). +%define BS3_MODE_IS_16BIT_SYS(a_fMode) (BS3_MODE_IS_16BIT_SYS_NO_RM(a_fMode) || BS3_MODE_IS_RM_SYS(a_fMode)) +;; Whether the system is some 32-bit mode. +%define BS3_MODE_IS_32BIT_SYS(a_fMode) ( ((a_fMode) & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PE32 \ + || ((a_fMode) & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PP32 \ + || ((a_fMode) & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_PAE32) +;; Whether the system is long mode. +%define BS3_MODE_IS_64BIT_SYS(a_fMode) (((a_fMode) & BS3_MODE_SYS_MASK) == BS3_MODE_SYS_LM) + +;; @} + +;; @name For mode specfic lookups: +;; %[BS3_MODE_NM %+ BS3_MODE_PE32](SomeBaseName) +;; %[BS3_MODE_LNAME_ %+ TMPL_MODE] +;; @{ +%define BS3_MODE_NM_001h(a_Name) _ %+ a_Name %+ _rm +%define BS3_MODE_NM_011h(a_Name) _ %+ a_Name %+ _pe16 +%define BS3_MODE_NM_012h(a_Name) _ %+ a_Name %+ _pe16_32 +%define BS3_MODE_NM_018h(a_Name) _ %+ a_Name %+ _pe16_v86 +%define BS3_MODE_NM_022h(a_Name) _ %+ a_Name %+ _pe32 +%define BS3_MODE_NM_021h(a_Name) _ %+ a_Name %+ _pe32_16 +%define BS3_MODE_NM_028h(a_Name) _ %+ a_Name %+ _pev86 +%define BS3_MODE_NM_031h(a_Name) _ %+ a_Name %+ _pp16 +%define BS3_MODE_NM_032h(a_Name) _ %+ a_Name %+ _pp16_32 +%define BS3_MODE_NM_038h(a_Name) _ %+ a_Name %+ _pp16_v86 +%define BS3_MODE_NM_042h(a_Name) _ %+ a_Name %+ _pp32 +%define BS3_MODE_NM_041h(a_Name) _ %+ a_Name %+ _pp32_16 +%define BS3_MODE_NM_048h(a_Name) _ %+ a_Name %+ _ppv86 +%define BS3_MODE_NM_051h(a_Name) _ %+ a_Name %+ _pae16 +%define BS3_MODE_NM_052h(a_Name) _ %+ a_Name %+ _pae16_32 +%define BS3_MODE_NM_058h(a_Name) _ %+ a_Name %+ _pae16_v86 +%define BS3_MODE_NM_062h(a_Name) _ %+ a_Name %+ _pae32 +%define BS3_MODE_NM_061h(a_Name) _ %+ a_Name %+ _pae32_16 +%define BS3_MODE_NM_068h(a_Name) _ %+ a_Name %+ _paev86 +%define BS3_MODE_NM_071h(a_Name) _ %+ a_Name %+ _lm16 +%define BS3_MODE_NM_072h(a_Name) _ %+ a_Name %+ _lm32 +%define BS3_MODE_NM_074h(a_Name) _ %+ a_Name %+ _lm64 + +%define BS3_MODE_LNAME_001h rm +%define BS3_MODE_LNAME_011h pe16 +%define BS3_MODE_LNAME_012h pe16_32 +%define BS3_MODE_LNAME_018h pe16_v86 +%define BS3_MODE_LNAME_022h pe32 +%define BS3_MODE_LNAME_021h pe32_16 +%define BS3_MODE_LNAME_028h pev86 +%define BS3_MODE_LNAME_031h pp16 +%define BS3_MODE_LNAME_032h pp16_32 +%define BS3_MODE_LNAME_038h pp16_v86 +%define BS3_MODE_LNAME_042h pp32 +%define BS3_MODE_LNAME_041h pp32_16 +%define BS3_MODE_LNAME_048h ppv86 +%define BS3_MODE_LNAME_051h pae16 +%define BS3_MODE_LNAME_052h pae16_32 +%define BS3_MODE_LNAME_058h pae16_v86 +%define BS3_MODE_LNAME_062h pae32 +%define BS3_MODE_LNAME_061h pae32_16 +%define BS3_MODE_LNAME_068h paev86 +%define BS3_MODE_LNAME_071h lm16 +%define BS3_MODE_LNAME_072h lm32 +%define BS3_MODE_LNAME_074h lm64 + +%define BS3_MODE_UNAME_001h RM +%define BS3_MODE_UNAME_011h PE16 +%define BS3_MODE_UNAME_012h PE16_32 +%define BS3_MODE_UNAME_018h PE16_V86 +%define BS3_MODE_UNAME_022h PE32 +%define BS3_MODE_UNAME_021h PE32_16 +%define BS3_MODE_UNAME_028h PEV86 +%define BS3_MODE_UNAME_031h PP16 +%define BS3_MODE_UNAME_032h PP16_32 +%define BS3_MODE_UNAME_038h PP16_V86 +%define BS3_MODE_UNAME_042h PP32 +%define BS3_MODE_UNAME_041h PP32_16 +%define BS3_MODE_UNAME_048h PPV86 +%define BS3_MODE_UNAME_051h PAE16 +%define BS3_MODE_UNAME_052h PAE16_32 +%define BS3_MODE_UNAME_058h PAE16_V86 +%define BS3_MODE_UNAME_062h PAE32 +%define BS3_MODE_UNAME_061h PAE32_16 +%define BS3_MODE_UNAME_068h PAEV86 +%define BS3_MODE_UNAME_071h LM16 +%define BS3_MODE_UNAME_072h LM32 +%define BS3_MODE_UNAME_074h LM64 + +%define BS3_MODE_UNDERSCORE_001h _ +%define BS3_MODE_UNDERSCORE_011h _ +%define BS3_MODE_UNDERSCORE_012h _ +%define BS3_MODE_UNDERSCORE_018h _ +%define BS3_MODE_UNDERSCORE_022h _ +%define BS3_MODE_UNDERSCORE_021h _ +%define BS3_MODE_UNDERSCORE_028h _ +%define BS3_MODE_UNDERSCORE_031h _ +%define BS3_MODE_UNDERSCORE_032h _ +%define BS3_MODE_UNDERSCORE_038h _ +%define BS3_MODE_UNDERSCORE_042h _ +%define BS3_MODE_UNDERSCORE_041h _ +%define BS3_MODE_UNDERSCORE_048h _ +%define BS3_MODE_UNDERSCORE_051h _ +%define BS3_MODE_UNDERSCORE_052h _ +%define BS3_MODE_UNDERSCORE_058h _ +%define BS3_MODE_UNDERSCORE_062h _ +%define BS3_MODE_UNDERSCORE_061h _ +%define BS3_MODE_UNDERSCORE_068h _ +%define BS3_MODE_UNDERSCORE_071h _ +%define BS3_MODE_UNDERSCORE_072h _ +%define BS3_MODE_UNDERSCORE_074h _ + +%define BS3_MODE_CNAME_001h c16 +%define BS3_MODE_CNAME_011h c16 +%define BS3_MODE_CNAME_012h c32 +%define BS3_MODE_CNAME_018h c16 +%define BS3_MODE_CNAME_022h c32 +%define BS3_MODE_CNAME_021h c16 +%define BS3_MODE_CNAME_028h c16 +%define BS3_MODE_CNAME_031h c16 +%define BS3_MODE_CNAME_032h c32 +%define BS3_MODE_CNAME_038h c16 +%define BS3_MODE_CNAME_042h c32 +%define BS3_MODE_CNAME_041h c16 +%define BS3_MODE_CNAME_048h c16 +%define BS3_MODE_CNAME_051h c16 +%define BS3_MODE_CNAME_052h c32 +%define BS3_MODE_CNAME_058h c16 +%define BS3_MODE_CNAME_062h c32 +%define BS3_MODE_CNAME_061h c16 +%define BS3_MODE_CNAME_068h c16 +%define BS3_MODE_CNAME_071h c16 +%define BS3_MODE_CNAME_072h c32 +%define BS3_MODE_CNAME_074h c64 +;; @} + +;; @name For getting the ring-0 mode for v86 modes: %[BS3_MODE_R0_NM_001h %+ TMPL_MODE](Bs3SwitchToRM) +;; @{ +%define BS3_MODE_R0_NM_001h(a_Name) _ %+ a_Name %+ _rm +%define BS3_MODE_R0_NM_011h(a_Name) _ %+ a_Name %+ _pe16 +%define BS3_MODE_R0_NM_012h(a_Name) _ %+ a_Name %+ _pe16_32 +%define BS3_MODE_R0_NM_018h(a_Name) _ %+ a_Name %+ _pe16 +%define BS3_MODE_R0_NM_022h(a_Name) _ %+ a_Name %+ _pe32 +%define BS3_MODE_R0_NM_021h(a_Name) _ %+ a_Name %+ _pe32_16 +%define BS3_MODE_R0_NM_028h(a_Name) _ %+ a_Name %+ _pe32_16 +%define BS3_MODE_R0_NM_031h(a_Name) _ %+ a_Name %+ _pp16 +%define BS3_MODE_R0_NM_032h(a_Name) _ %+ a_Name %+ _pp16_32 +%define BS3_MODE_R0_NM_038h(a_Name) _ %+ a_Name %+ _pp16 +%define BS3_MODE_R0_NM_042h(a_Name) _ %+ a_Name %+ _pp32 +%define BS3_MODE_R0_NM_041h(a_Name) _ %+ a_Name %+ _pp32_16 +%define BS3_MODE_R0_NM_048h(a_Name) _ %+ a_Name %+ _pp32_16 +%define BS3_MODE_R0_NM_051h(a_Name) _ %+ a_Name %+ _pae16 +%define BS3_MODE_R0_NM_052h(a_Name) _ %+ a_Name %+ _pae16_32 +%define BS3_MODE_R0_NM_058h(a_Name) _ %+ a_Name %+ _pae16 +%define BS3_MODE_R0_NM_062h(a_Name) _ %+ a_Name %+ _pae32 +%define BS3_MODE_R0_NM_061h(a_Name) _ %+ a_Name %+ _pae32_16 +%define BS3_MODE_R0_NM_068h(a_Name) _ %+ a_Name %+ _pae32_16 +%define BS3_MODE_R0_NM_071h(a_Name) _ %+ a_Name %+ _lm16 +%define BS3_MODE_R0_NM_072h(a_Name) _ %+ a_Name %+ _lm32 +%define BS3_MODE_R0_NM_074h(a_Name) _ %+ a_Name %+ _lm64 +;; @} + + +;; +; Includes the file %1 with TMPL_MODE set to all possible value. +; @param 1 Double quoted include file name. +%macro BS3_INSTANTIATE_TEMPLATE_WITH_WEIRD_ONES 1 + %define BS3_INSTANTIATING_MODE + %define BS3_INSTANTIATING_ALL_MODES + + %define TMPL_MODE BS3_MODE_RM + %include %1 + + %define TMPL_MODE BS3_MODE_PE16 + %include %1 + %define TMPL_MODE BS3_MODE_PE16_32 + %include %1 + %define TMPL_MODE BS3_MODE_PE16_V86 + %include %1 + + %define TMPL_MODE BS3_MODE_PE32 + %include %1 + %define TMPL_MODE BS3_MODE_PE32_16 + %include %1 + %define TMPL_MODE BS3_MODE_PEV86 + %include %1 + + %define TMPL_MODE BS3_MODE_PP16 + %include %1 + %define TMPL_MODE BS3_MODE_PP16_32 + %include %1 + %define TMPL_MODE BS3_MODE_PP16_V86 + %include %1 + + %define TMPL_MODE BS3_MODE_PP32 + %include %1 + %define TMPL_MODE BS3_MODE_PP32_16 + %include %1 + %define TMPL_MODE BS3_MODE_PPV86 + %include %1 + + %define TMPL_MODE BS3_MODE_PAE16 + %include %1 + %define TMPL_MODE BS3_MODE_PAE16_32 + %include %1 + %define TMPL_MODE BS3_MODE_PAE16_V86 + %include %1 + + %define TMPL_MODE BS3_MODE_PAE32 + %include %1 + %define TMPL_MODE BS3_MODE_PAE32_16 + %include %1 + %define TMPL_MODE BS3_MODE_PAEV86 + %include %1 + + %define TMPL_MODE BS3_MODE_LM16 + %include %1 + %define TMPL_MODE BS3_MODE_LM32 + %include %1 + %define TMPL_MODE BS3_MODE_LM64 + %include %1 + + %undef BS3_INSTANTIATING_MODE + %undef BS3_INSTANTIATING_ALL_MODES +%endmacro + + +;; +; Includes the file %1 with TMPL_MODE set to all but the "weird" value. +; @param 1 Double quoted include file name. +%macro BS3_INSTANTIATE_TEMPLATE_ESSENTIALS 1 + %define BS3_INSTANTIATING_MODE + %define BS3_INSTANTIATING_ESSENTIAL_MODES + + %define TMPL_MODE BS3_MODE_RM + %include %1 + + %define TMPL_MODE BS3_MODE_PE16 + %include %1 + + %define TMPL_MODE BS3_MODE_PE32 + %include %1 + %define TMPL_MODE BS3_MODE_PEV86 + %include %1 + + %define TMPL_MODE BS3_MODE_PP16 + %include %1 + + %define TMPL_MODE BS3_MODE_PP32 + %include %1 + %define TMPL_MODE BS3_MODE_PPV86 + %include %1 + + %define TMPL_MODE BS3_MODE_PAE16 + %include %1 + + %define TMPL_MODE BS3_MODE_PAE32 + %include %1 + %define TMPL_MODE BS3_MODE_PAEV86 + %include %1 + + %define TMPL_MODE BS3_MODE_LM16 + %include %1 + %define TMPL_MODE BS3_MODE_LM32 + %include %1 + %define TMPL_MODE BS3_MODE_LM64 + %include %1 + + %undef BS3_INSTANTIATING_MODE + %undef BS3_INSTANTIATING_ESSENTIAL_MODES +%endmacro + +;; +; Includes the file %1 with TMPL_MODE set to a 16-bit, a 32-bit and a 64-bit value. +; @param 1 Double quoted include file name. +%macro BS3_INSTANTIATE_COMMON_TEMPLATE 1 + %define BS3_INSTANTIATING_CMN + + %define TMPL_MODE BS3_MODE_RM + %include %1 + %define TMPL_MODE BS3_MODE_PE32 + %include %1 + %define TMPL_MODE BS3_MODE_LM64 + %include %1 + + %undef BS3_INSTANTIATING_CMN +%endmacro + + +;; @name Static Memory Allocation +; @{ +;; The flat load address for the code after the bootsector. +%define BS3_ADDR_LOAD 010000h +;; Where we save the boot registers during init. +; Located right before the code. +%define BS3_ADDR_REG_SAVE (BS3_ADDR_LOAD - BS3REGCTX_size - 8) +;; Where the stack starts (initial RSP value). +; Located 16 bytes (assumed by boot sector) before the saved registers. SS.BASE=0. +%define BS3_ADDR_STACK (BS3_ADDR_REG_SAVE - 16) +;; The ring-0 stack (8KB) for ring transitions. +%define BS3_ADDR_STACK_R0 006000h +;; The ring-1 stack (8KB) for ring transitions. +%define BS3_ADDR_STACK_R1 004000h +;; The ring-2 stack (8KB) for ring transitions. +%define BS3_ADDR_STACK_R2 002000h +;; IST1 ring-0 stack for long mode (4KB), used for double faults elsewhere. +%define BS3_ADDR_STACK_R0_IST1 009000h +;; IST2 ring-0 stack for long mode (3KB), used for spare 0 stack elsewhere. +%define BS3_ADDR_STACK_R0_IST2 008000h +;; IST3 ring-0 stack for long mode (1KB). +%define BS3_ADDR_STACK_R0_IST3 007400h +;; IST4 ring-0 stack for long mode (1KB), used for spare 1 stack elsewhere. +%define BS3_ADDR_STACK_R0_IST4 007000h +;; IST5 ring-0 stack for long mode (1KB). +%define BS3_ADDR_STACK_R0_IST5 006c00h +;; IST6 ring-0 stack for long mode (1KB). +%define BS3_ADDR_STACK_R0_IST6 006800h +;; IST7 ring-0 stack for long mode (1KB). +%define BS3_ADDR_STACK_R0_IST7 006400h + +;; The base address of the BS3TEXT16 segment (same as BS3_LOAD_ADDR). +;; @sa BS3_SEL_TEXT16 +%define BS3_ADDR_BS3TEXT16 010000h +;; The base address of the BS3SYSTEM16 segment. +;; @sa BS3_SEL_SYSTEM16 +%define BS3_ADDR_BS3SYSTEM16 020000h +;; The base address of the BS3DATA16/BS3KIT_GRPNM_DATA16 segment. +;; @sa BS3_SEL_DATA16 +%define BS3_ADDR_BS3DATA16 029000h +;; @} + + +;; +; BS3 register context. Used by traps and such. +; +struc BS3REGCTX + .rax resq 1 ; BS3REG rax; /**< 0x00 */ + .rcx resq 1 ; BS3REG rcx; /**< 0x08 */ + .rdx resq 1 ; BS3REG rdx; /**< 0x10 */ + .rbx resq 1 ; BS3REG rbx; /**< 0x18 */ + .rsp resq 1 ; BS3REG rsp; /**< 0x20 */ + .rbp resq 1 ; BS3REG rbp; /**< 0x28 */ + .rsi resq 1 ; BS3REG rsi; /**< 0x30 */ + .rdi resq 1 ; BS3REG rdi; /**< 0x38 */ + .r8 resq 1 ; BS3REG r8; /**< 0x40 */ + .r9 resq 1 ; BS3REG r9; /**< 0x48 */ + .r10 resq 1 ; BS3REG r10; /**< 0x50 */ + .r11 resq 1 ; BS3REG r11; /**< 0x58 */ + .r12 resq 1 ; BS3REG r12; /**< 0x60 */ + .r13 resq 1 ; BS3REG r13; /**< 0x68 */ + .r14 resq 1 ; BS3REG r14; /**< 0x70 */ + .r15 resq 1 ; BS3REG r15; /**< 0x78 */ + .rflags resq 1 ; BS3REG rflags; /**< 0x80 */ + .rip resq 1 ; BS3REG rip; /**< 0x88 */ + .cs resw 1 ; uint16_t cs; /**< 0x90 */ + .ds resw 1 ; uint16_t ds; /**< 0x92 */ + .es resw 1 ; uint16_t es; /**< 0x94 */ + .fs resw 1 ; uint16_t fs; /**< 0x96 */ + .gs resw 1 ; uint16_t gs; /**< 0x98 */ + .ss resw 1 ; uint16_t ss; /**< 0x9a */ + .tr resw 1 ; uint16_t tr; /**< 0x9c */ + .ldtr resw 1 ; uint16_t ldtr; /**< 0x9e */ + .bMode resb 1 ; uint8_t bMode; /**< 0xa0: BS3_MODE_XXX. */ + .bCpl resb 1 ; uint8_t bCpl; /**< 0xa1: 0-3, 0 is used for real mode. */ + .fbFlags resb 1 ; uint8_t fbFlags; /**< 0xa2: BS3REG_CTX_F_XXX */ + .abPadding resb 5 ; uint8_t abPadding[5]; /**< 0xa4 */ + .cr0 resq 1 ; BS3REG cr0; /**< 0xa8 */ + .cr2 resq 1 ; BS3REG cr2; /**< 0xb0 */ + .cr3 resq 1 ; BS3REG cr3; /**< 0xb8 */ + .cr4 resq 1 ; BS3REG cr4; /**< 0xc0 */ + .uUnused resq 1 ; BS3REG uUnused; /**< 0xc8 */ +endstruc +AssertCompileSize(BS3REGCTX, 0xd0) + +;; @name BS3REG_CTX_F_XXX - BS3REGCTX::fbFlags masks. +; @{ +;; The CR0 is MSW (only low 16-bit). */ +%define BS3REG_CTX_F_NO_CR0_IS_MSW 0x01 +;; No CR2 and CR3 values. Not in CPL 0 or CPU too old for CR2 & CR3. +%define BS3REG_CTX_F_NO_CR2_CR3 0x02 +;; No CR4 value. The CPU is too old for CR4. +%define BS3REG_CTX_F_NO_CR4 0x04 +;; No TR and LDTR values. Context gathered in real mode or v8086 mode. +%define BS3REG_CTX_F_NO_TR_LDTR 0x08 +;; The context doesn't have valid values for AMD64 GPR extensions. +%define BS3REG_CTX_F_NO_AMD64 0x10 +;; @} + + +;; @name Flags for Bs3RegCtxRestore +; @{ +;; Skip restoring the CRx registers. +%define BS3REGCTXRESTORE_F_SKIP_CRX 1 +;; Sets g_fBs3TrapNoV86Assist. +%define BS3REGCTXRESTORE_F_NO_V86_ASSIST 2 +;; @} + + +;; +; BS3 extended register context (FPU, SSE, AVX, ++) +; +struc BS3EXTCTX + .u16Magic resw 1 ; uint16_t u16Magic; + .cb resw 1 ; uint16_t cb; + .enmMethod resb 1 ; uint8_t enmMethod; + alignb 8 + .fXcr0Nominal resq 1 ; uint64_t fXcr0Nominal; + .fXcr0Saved resq 1 ; uint64_t fXcr0Saved; + alignb 64 + .Ctx resb 512 +endstruc +%define BS3EXTCTXMETHOD_ANCIENT 1 +%define BS3EXTCTXMETHOD_FXSAVE 2 +%define BS3EXTCTXMETHOD_XSAVE 3 + +;; +; BS3 Trap Frame. +; +struc BS3TRAPFRAME + .bXcpt resb 1 + .cbIretFrame resb 1 + .uHandlerCs resw 1 + .uHandlerSs resw 1 + .usAlignment resw 1 + .uHandlerRsp resq 1 + .fHandlerRfl resq 1 + .uErrCd resq 1 + .Ctx resb BS3REGCTX_size +endstruc +AssertCompileSize(BS3TRAPFRAME, 0x20 + 0xd0) + +;; +; Trap record. +; +struc BS3TRAPREC + ;; The trap location relative to the base address given at + ; registration time. + .offWhere resd 1 + ;; What to add to .offWhere to calculate the resume address. + .offResumeAddend resb 1 + ;; The trap number. + .u8TrapNo resb 1 + ;; The error code if the trap takes one. + .u16ErrCd resw 1 +endstruc + +;; The size shift. +%define BS3TRAPREC_SIZE_SHIFT 3 + + +;; The system call vector. +%define BS3_TRAP_SYSCALL 20h + +;; @name System call numbers (ax) +;; @note Pointers are always passed in cx:xDI. +;; @{ +;; Print char (cl). +%define BS3_SYSCALL_PRINT_CHR 0001h +;; Print string (pointer in cx:xDI, length in xDX). +%define BS3_SYSCALL_PRINT_STR 0002h +;; Switch to ring-0. +%define BS3_SYSCALL_TO_RING0 0003h +;; Switch to ring-1. +%define BS3_SYSCALL_TO_RING1 0004h +;; Switch to ring-2. +%define BS3_SYSCALL_TO_RING2 0005h +;; Switch to ring-3. +%define BS3_SYSCALL_TO_RING3 0006h +;; Restore context (pointer in cx:xDI, flags in dx). +%define BS3_SYSCALL_RESTORE_CTX 0007h +;; Set DRx register (value in ESI, register number in dl). +%define BS3_SYSCALL_SET_DRX 0008h +;; GET DRx register (register number in dl, value returned in ax:dx). +%define BS3_SYSCALL_GET_DRX 0009h +;; Set CRx register (value in ESI, register number in dl). +%define BS3_SYSCALL_SET_CRX 000ah +;; Get CRx register (register number in dl, value returned in ax:dx). +%define BS3_SYSCALL_GET_CRX 000bh +;; Set the task register (value in dx). */ +%define BS3_SYSCALL_SET_TR 000ch +;; Get the task register (value returned in ax). +%define BS3_SYSCALL_GET_TR 000dh +;; Set the LDT register (value in dx). +%define BS3_SYSCALL_SET_LDTR 000eh +;; Get the LDT register (value returned in ax). +%define BS3_SYSCALL_GET_LDTR 000fh +;; Set XCR0 register (value in edx:esi). +%define BS3_SYSCALL_SET_XCR0 0010h +;; Get XCR0 register (value returned in edx:eax). +%define BS3_SYSCALL_GET_XCR0 0011h +;; The last system call value. +%define BS3_SYSCALL_LAST BS3_SYSCALL_GET_XCR0 +;; @} + + + +;; @name BS3_SEL_XXX - GDT selectors +;; @{ + +%define BS3_SEL_LDT 0010h ;;< The LDT selector (requires setting up). +%define BS3_SEL_TSS16 0020h ;;< The 16-bit TSS selector. +%define BS3_SEL_TSS16_DF 0028h ;;< The 16-bit TSS selector for double faults. +%define BS3_SEL_TSS16_SPARE0 0030h ;;< The 16-bit TSS selector for testing. +%define BS3_SEL_TSS16_SPARE1 0038h ;;< The 16-bit TSS selector for testing. +%define BS3_SEL_TSS32 0040h ;;< The 32-bit TSS selector. +%define BS3_SEL_TSS32_DF 0048h ;;< The 32-bit TSS selector for double faults. +%define BS3_SEL_TSS32_SPARE0 0050h ;;< The 32-bit TSS selector for testing. +%define BS3_SEL_TSS32_SPARE1 0058h ;;< The 32-bit TSS selector for testing. +%define BS3_SEL_TSS32_IOBP_IRB 0060h ;;< The 32-bit TSS selector with I/O permission and interrupt redirection bitmaps. +%define BS3_SEL_TSS32_IRB 0068h ;;< The 32-bit TSS selector with only interrupt redirection bitmap (IOPB stripped by limit). +%define BS3_SEL_TSS64 0070h ;;< The 64-bit TSS selector. +%define BS3_SEL_TSS64_SPARE0 0080h ;;< The 64-bit TSS selector. +%define BS3_SEL_TSS64_SPARE1 0090h ;;< The 64-bit TSS selector. +%define BS3_SEL_TSS64_IOBP 00a0h ;;< The 64-bit TSS selector. + +%define BS3_SEL_RMTEXT16_CS 00e0h ;;< Conforming code selector for accessing the BS3RMTEXT16 segment. Runtime config. +%define BS3_SEL_X0TEXT16_CS 00e8h ;;< Conforming code selector for accessing the BS3X0TEXT16 segment. Runtime config. +%define BS3_SEL_X1TEXT16_CS 00f0h ;;< Conforming code selector for accessing the BS3X1TEXT16 segment. Runtime config. +%define BS3_SEL_VMMDEV_MMIO16 00f8h ;;< Selector for accessing the VMMDev MMIO segment at 0100000h from 16-bit code. + +%define BS3_SEL_RING_SHIFT 8 ;;< For the formula: BS3_SEL_R0_XXX + ((cs & 3) << BS3_SEL_RING_SHIFT) + +%define BS3_SEL_R0_FIRST 0100h ;;< The first selector in the ring-0 block. +%define BS3_SEL_R0_CS16 0100h ;;< ring-0: 16-bit code selector, base 0x10000. +%define BS3_SEL_R0_DS16 0108h ;;< ring-0: 16-bit data selector, base 0x23000. +%define BS3_SEL_R0_SS16 0110h ;;< ring-0: 16-bit stack selector, base 0x00000. +%define BS3_SEL_R0_CS32 0118h ;;< ring-0: 32-bit flat code selector. +%define BS3_SEL_R0_DS32 0120h ;;< ring-0: 32-bit flat data selector. +%define BS3_SEL_R0_SS32 0128h ;;< ring-0: 32-bit flat stack selector. +%define BS3_SEL_R0_CS64 0130h ;;< ring-0: 64-bit flat code selector. +%define BS3_SEL_R0_DS64 0138h ;;< ring-0: 64-bit flat data & stack selector. +%define BS3_SEL_R0_CS16_EO 0140h ;;< ring-0: 16-bit execute-only code selector, not accessed, 0xfffe limit, CS16 base. +%define BS3_SEL_R0_CS16_CNF 0148h ;;< ring-0: 16-bit conforming code selector, not accessed, 0xfffe limit, CS16 base. +%define BS3_SEL_R0_CS16_CNF_EO 0150h ;;< ring-0: 16-bit execute-only conforming code selector, not accessed, 0xfffe limit, CS16 base. +%define BS3_SEL_R0_CS32_EO 0158h ;;< ring-0: 32-bit execute-only code selector, not accessed, flat. +%define BS3_SEL_R0_CS32_CNF 0160h ;;< ring-0: 32-bit conforming code selector, not accessed, flat. +%define BS3_SEL_R0_CS32_CNF_EO 0168h ;;< ring-0: 32-bit execute-only conforming code selector, not accessed, flat. +%define BS3_SEL_R0_CS64_EO 0170h ;;< ring-0: 64-bit execute-only code selector, not accessed, flat. +%define BS3_SEL_R0_CS64_CNF 0178h ;;< ring-0: 64-bit conforming code selector, not accessed, flat. +%define BS3_SEL_R0_CS64_CNF_EO 0180h ;;< ring-0: 64-bit execute-only conforming code selector, not accessed, flat. + +%define BS3_SEL_R1_FIRST 0200h ;;< The first selector in the ring-1 block. +%define BS3_SEL_R1_CS16 0200h ;;< ring-1: 16-bit code selector, base 0x10000. +%define BS3_SEL_R1_DS16 0208h ;;< ring-1: 16-bit data selector, base 0x23000. +%define BS3_SEL_R1_SS16 0210h ;;< ring-1: 16-bit stack selector, base 0x00000. +%define BS3_SEL_R1_CS32 0218h ;;< ring-1: 32-bit flat code selector. +%define BS3_SEL_R1_DS32 0220h ;;< ring-1: 32-bit flat data selector. +%define BS3_SEL_R1_SS32 0228h ;;< ring-1: 32-bit flat stack selector. +%define BS3_SEL_R1_CS64 0230h ;;< ring-1: 64-bit flat code selector. +%define BS3_SEL_R1_DS64 0238h ;;< ring-1: 64-bit flat data & stack selector. +%define BS3_SEL_R1_CS16_EO 0240h ;;< ring-1: 16-bit execute-only code selector, not accessed, 0xfffe limit, CS16 base. +%define BS3_SEL_R1_CS16_CNF 0248h ;;< ring-1: 16-bit conforming code selector, not accessed, 0xfffe limit, CS16 base. +%define BS3_SEL_R1_CS16_CNF_EO 0250h ;;< ring-1: 16-bit execute-only conforming code selector, not accessed, 0xfffe limit, CS16 base. +%define BS3_SEL_R1_CS32_EO 0258h ;;< ring-1: 32-bit execute-only code selector, not accessed, flat. +%define BS3_SEL_R1_CS32_CNF 0260h ;;< ring-1: 32-bit conforming code selector, not accessed, flat. +%define BS3_SEL_R1_CS32_CNF_EO 0268h ;;< ring-1: 32-bit execute-only conforming code selector, not accessed, flat. +%define BS3_SEL_R1_CS64_EO 0270h ;;< ring-1: 64-bit execute-only code selector, not accessed, flat. +%define BS3_SEL_R1_CS64_CNF 0278h ;;< ring-1: 64-bit conforming code selector, not accessed, flat. +%define BS3_SEL_R1_CS64_CNF_EO 0280h ;;< ring-1: 64-bit execute-only conforming code selector, not accessed, flat. + +%define BS3_SEL_R2_FIRST 0300h ;;< The first selector in the ring-2 block. +%define BS3_SEL_R2_CS16 0300h ;;< ring-2: 16-bit code selector, base 0x10000. +%define BS3_SEL_R2_DS16 0308h ;;< ring-2: 16-bit data selector, base 0x23000. +%define BS3_SEL_R2_SS16 0310h ;;< ring-2: 16-bit stack selector, base 0x00000. +%define BS3_SEL_R2_CS32 0318h ;;< ring-2: 32-bit flat code selector. +%define BS3_SEL_R2_DS32 0320h ;;< ring-2: 32-bit flat data selector. +%define BS3_SEL_R2_SS32 0328h ;;< ring-2: 32-bit flat stack selector. +%define BS3_SEL_R2_CS64 0330h ;;< ring-2: 64-bit flat code selector. +%define BS3_SEL_R2_DS64 0338h ;;< ring-2: 64-bit flat data & stack selector. +%define BS3_SEL_R2_CS16_EO 0340h ;;< ring-2: 16-bit execute-only code selector, not accessed, 0xfffe limit, CS16 base. +%define BS3_SEL_R2_CS16_CNF 0348h ;;< ring-2: 16-bit conforming code selector, not accessed, 0xfffe limit, CS16 base. +%define BS3_SEL_R2_CS16_CNF_EO 0350h ;;< ring-2: 16-bit execute-only conforming code selector, not accessed, 0xfffe limit, CS16 base. +%define BS3_SEL_R2_CS32_EO 0358h ;;< ring-2: 32-bit execute-only code selector, not accessed, flat. +%define BS3_SEL_R2_CS32_CNF 0360h ;;< ring-2: 32-bit conforming code selector, not accessed, flat. +%define BS3_SEL_R2_CS32_CNF_EO 0368h ;;< ring-2: 32-bit execute-only conforming code selector, not accessed, flat. +%define BS3_SEL_R2_CS64_EO 0370h ;;< ring-2: 64-bit execute-only code selector, not accessed, flat. +%define BS3_SEL_R2_CS64_CNF 0378h ;;< ring-2: 64-bit conforming code selector, not accessed, flat. +%define BS3_SEL_R2_CS64_CNF_EO 0380h ;;< ring-2: 64-bit execute-only conforming code selector, not accessed, flat. + +%define BS3_SEL_R3_FIRST 0400h ;;< The first selector in the ring-3 block. +%define BS3_SEL_R3_CS16 0400h ;;< ring-3: 16-bit code selector, base 0x10000. +%define BS3_SEL_R3_DS16 0408h ;;< ring-3: 16-bit data selector, base 0x23000. +%define BS3_SEL_R3_SS16 0410h ;;< ring-3: 16-bit stack selector, base 0x00000. +%define BS3_SEL_R3_CS32 0418h ;;< ring-3: 32-bit flat code selector. +%define BS3_SEL_R3_DS32 0420h ;;< ring-3: 32-bit flat data selector. +%define BS3_SEL_R3_SS32 0428h ;;< ring-3: 32-bit flat stack selector. +%define BS3_SEL_R3_CS64 0430h ;;< ring-3: 64-bit flat code selector. +%define BS3_SEL_R3_DS64 0438h ;;< ring-3: 64-bit flat data & stack selector. +%define BS3_SEL_R3_CS16_EO 0440h ;;< ring-3: 16-bit execute-only code selector, not accessed, 0xfffe limit, CS16 base. +%define BS3_SEL_R3_CS16_CNF 0448h ;;< ring-3: 16-bit conforming code selector, not accessed, 0xfffe limit, CS16 base. +%define BS3_SEL_R3_CS16_CNF_EO 0450h ;;< ring-3: 16-bit execute-only conforming code selector, not accessed, 0xfffe limit, CS16 base. +%define BS3_SEL_R3_CS32_EO 0458h ;;< ring-3: 32-bit execute-only code selector, not accessed, flat. +%define BS3_SEL_R3_CS32_CNF 0460h ;;< ring-3: 32-bit conforming code selector, not accessed, flat. +%define BS3_SEL_R3_CS32_CNF_EO 0468h ;;< ring-3: 32-bit execute-only conforming code selector, not accessed, flat. +%define BS3_SEL_R3_CS64_EO 0470h ;;< ring-3: 64-bit execute-only code selector, not accessed, flat. +%define BS3_SEL_R3_CS64_CNF 0478h ;;< ring-3: 64-bit conforming code selector, not accessed, flat. +%define BS3_SEL_R3_CS64_CNF_EO 0480h ;;< ring-3: 64-bit execute-only conforming code selector, not accessed, flat. + +%define BS3_SEL_SPARE_FIRST 0500h ;;< The first selector in the spare block +%define BS3_SEL_SPARE_00 0500h ;;< Spare selector number 00h. +%define BS3_SEL_SPARE_01 0508h ;;< Spare selector number 01h. +%define BS3_SEL_SPARE_02 0510h ;;< Spare selector number 02h. +%define BS3_SEL_SPARE_03 0518h ;;< Spare selector number 03h. +%define BS3_SEL_SPARE_04 0520h ;;< Spare selector number 04h. +%define BS3_SEL_SPARE_05 0528h ;;< Spare selector number 05h. +%define BS3_SEL_SPARE_06 0530h ;;< Spare selector number 06h. +%define BS3_SEL_SPARE_07 0538h ;;< Spare selector number 07h. +%define BS3_SEL_SPARE_08 0540h ;;< Spare selector number 08h. +%define BS3_SEL_SPARE_09 0548h ;;< Spare selector number 09h. +%define BS3_SEL_SPARE_0a 0550h ;;< Spare selector number 0ah. +%define BS3_SEL_SPARE_0b 0558h ;;< Spare selector number 0bh. +%define BS3_SEL_SPARE_0c 0560h ;;< Spare selector number 0ch. +%define BS3_SEL_SPARE_0d 0568h ;;< Spare selector number 0dh. +%define BS3_SEL_SPARE_0e 0570h ;;< Spare selector number 0eh. +%define BS3_SEL_SPARE_0f 0578h ;;< Spare selector number 0fh. +%define BS3_SEL_SPARE_10 0580h ;;< Spare selector number 10h. +%define BS3_SEL_SPARE_11 0588h ;;< Spare selector number 11h. +%define BS3_SEL_SPARE_12 0590h ;;< Spare selector number 12h. +%define BS3_SEL_SPARE_13 0598h ;;< Spare selector number 13h. +%define BS3_SEL_SPARE_14 05a0h ;;< Spare selector number 14h. +%define BS3_SEL_SPARE_15 05a8h ;;< Spare selector number 15h. +%define BS3_SEL_SPARE_16 05b0h ;;< Spare selector number 16h. +%define BS3_SEL_SPARE_17 05b8h ;;< Spare selector number 17h. +%define BS3_SEL_SPARE_18 05c0h ;;< Spare selector number 18h. +%define BS3_SEL_SPARE_19 05c8h ;;< Spare selector number 19h. +%define BS3_SEL_SPARE_1a 05d0h ;;< Spare selector number 1ah. +%define BS3_SEL_SPARE_1b 05d8h ;;< Spare selector number 1bh. +%define BS3_SEL_SPARE_1c 05e0h ;;< Spare selector number 1ch. +%define BS3_SEL_SPARE_1d 05e8h ;;< Spare selector number 1dh. +%define BS3_SEL_SPARE_1e 05f0h ;;< Spare selector number 1eh. +%define BS3_SEL_SPARE_1f 05f8h ;;< Spare selector number 1fh. + +%define BS3_SEL_TILED 0600h ;;< 16-bit data tiling: First - base=0x00000000, limit=64KB, DPL=3. +%define BS3_SEL_TILED_LAST 0df8h ;;< 16-bit data tiling: Last - base=0x00ff0000, limit=64KB, DPL=3. +%define BS3_SEL_TILED_AREA_SIZE 001000000h ;;< 16-bit data tiling: Size of addressable area, in bytes. (16 MB) + +%define BS3_SEL_FREE_PART1 0e00h ;;< Free selector space - part \%1. +%define BS3_SEL_FREE_PART1_LAST 0ff8h ;;< Free selector space - part \%1, last entry. + +%define BS3_SEL_TEXT16 1000h ;;< The BS3TEXT16 selector. + +%define BS3_SEL_FREE_PART2 1008h ;;< Free selector space - part \#2. +%define BS3_SEL_FREE_PART2_LAST 17f8h ;;< Free selector space - part \#2, last entry. + +%define BS3_SEL_TILED_R0 1800h ;;< 16-bit data/stack tiling: First - base=0x00000000, limit=64KB, DPL=0. +%define BS3_SEL_TILED_R0_LAST 1ff8h ;;< 16-bit data/stack tiling: Last - base=0x00ff0000, limit=64KB, DPL=0. + +%define BS3_SEL_SYSTEM16 2000h ;;< The BS3SYSTEM16 selector. + +%define BS3_SEL_FREE_PART3 2008h ;;< Free selector space - part \%3. +%define BS3_SEL_FREE_PART3_LAST 28f8h ;;< Free selector space - part \%3, last entry. + +%define BS3_SEL_DATA16 2900h ;;< The BS3DATA16/BS3KIT_GRPNM_DATA16 selector. + +%define BS3_SEL_FREE_PART4 2908h ;;< Free selector space - part \#4. +%define BS3_SEL_FREE_PART4_LAST 2f98h ;;< Free selector space - part \#4, last entry. + +%define BS3_SEL_PRE_TEST_PAGE_08 2fa0h ;;< Selector located 8 selectors before the test page. +%define BS3_SEL_PRE_TEST_PAGE_07 2fa8h ;;< Selector located 7 selectors before the test page. +%define BS3_SEL_PRE_TEST_PAGE_06 2fb0h ;;< Selector located 6 selectors before the test page. +%define BS3_SEL_PRE_TEST_PAGE_05 2fb8h ;;< Selector located 5 selectors before the test page. +%define BS3_SEL_PRE_TEST_PAGE_04 2fc0h ;;< Selector located 4 selectors before the test page. +%define BS3_SEL_PRE_TEST_PAGE_03 2fc8h ;;< Selector located 3 selectors before the test page. +%define BS3_SEL_PRE_TEST_PAGE_02 2fd0h ;;< Selector located 2 selectors before the test page. +%define BS3_SEL_PRE_TEST_PAGE_01 2fd8h ;;< Selector located 1 selector before the test page. +%define BS3_SEL_TEST_PAGE 2fe0h ;;< Start of the test page intended for playing around with paging and GDT. +%define BS3_SEL_TEST_PAGE_00 2fe0h ;;< Test page selector number 00h (convenience). +%define BS3_SEL_TEST_PAGE_01 2fe8h ;;< Test page selector number 01h (convenience). +%define BS3_SEL_TEST_PAGE_02 2ff0h ;;< Test page selector number 02h (convenience). +%define BS3_SEL_TEST_PAGE_03 2ff8h ;;< Test page selector number 03h (convenience). +%define BS3_SEL_TEST_PAGE_04 3000h ;;< Test page selector number 04h (convenience). +%define BS3_SEL_TEST_PAGE_05 3008h ;;< Test page selector number 05h (convenience). +%define BS3_SEL_TEST_PAGE_06 3010h ;;< Test page selector number 06h (convenience). +%define BS3_SEL_TEST_PAGE_07 3018h ;;< Test page selector number 07h (convenience). +%define BS3_SEL_TEST_PAGE_LAST 3fd0h ;;< The last selector in the spare page. + +%define BS3_SEL_GDT_LIMIT 3fd8h ;;< The GDT limit. + +;; @} + + +; +; Sanity checks. +; +%if BS3_ADDR_BS3TEXT16 != BS3_ADDR_LOAD + %error "BS3_ADDR_BS3TEXT16 and BS3_ADDR_LOAD are out of sync" +%endif +%if (BS3_ADDR_BS3TEXT16 / 16) != BS3_SEL_TEXT16 + %error "BS3_ADDR_BS3TEXT16 and BS3_SEL_TEXT16 are out of sync" +%endif +%if (BS3_ADDR_BS3DATA16 / 16) != BS3_SEL_DATA16 + %error "BS3_ADDR_BS3DATA16 and BS3_SEL_DATA16 are out of sync" +%endif +%if (BS3_ADDR_BS3SYSTEM16 / 16) != BS3_SEL_SYSTEM16 + %error "BS3_ADDR_BS3SYSTEM16 and BS3_SEL_SYSTEM16 are out of sync" +%endif + + +;; @name BS3CPU_XXX - Bs3CpuDetect_mmm return value and g_bBs3CpuDetected. +;; @{ +%define BS3CPU_8086 0x0001 +%define BS3CPU_V20 0x0002 +%define BS3CPU_80186 0x0003 +%define BS3CPU_80286 0x0004 +%define BS3CPU_80386 0x0005 +%define BS3CPU_80486 0x0006 +%define BS3CPU_Pentium 0x0007 +%define BS3CPU_PPro 0x0008 +%define BS3CPU_PProOrNewer 0x0009 +%define BS3CPU_TYPE_MASK 0x00ff +%define BS3CPU_F_CPUID 0x0100 +%define BS3CPU_F_CPUID_EXT_LEAVES 0x0200 +%define BS3CPU_F_PAE 0x0400 +%define BS3CPU_F_PAE_BIT 10 +%define BS3CPU_F_PSE 0x0800 +%define BS3CPU_F_PSE_BIT 11 +%define BS3CPU_F_LONG_MODE 0x1000 +%define BS3CPU_F_LONG_MODE_BIT 12 +%define BS3CPU_F_NX 0x2000 +%define BS3CPU_F_NX_BIT 13 +;; @} + +%endif + diff --git a/src/VBox/ValidationKit/bootsectors/todo.txt b/src/VBox/ValidationKit/bootsectors/todo.txt new file mode 100644 index 00000000..0d8d407d --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/todo.txt @@ -0,0 +1,10 @@ +$Id: todo.txt $ + +Tripple fault variations: + - VT-x + NP: #PF w/ bad 32-bit IDT (set u1DescType=1). Injection causes #GP(73) loop. (r63775 cpu-pf-1) + - HWACCM (?): Enable PAE with bad PDPE for the next instr/jmp. Loops in at least one setup. + +Special General Protection Faults: + - Bad IDT entry. For instance X86DESCGATE::u1DescType = 1 (!system) of the #PF + entry and trigger a page fault. VT-x then tries to raise #GP(0x73). + diff --git a/src/VBox/ValidationKit/common/Makefile.kmk b/src/VBox/ValidationKit/common/Makefile.kmk new file mode 100644 index 00000000..184e9440 --- /dev/null +++ b/src/VBox/ValidationKit/common/Makefile.kmk @@ -0,0 +1,77 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Common Python Code. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Install targets for the two zip files. +# + +INSTALLS += testboxscript-common +testboxscript-common_TEMPLATE = VBoxValidationKitR3 +testboxscript-common_INST = $(INST_TESTBOXSCRIPT)common/ +testboxscript-common_SOURCES = \ + __init__.py \ + utils.py \ + netutils.py \ + pathutils.py \ + webutils.py \ + constants/__init__.py=>constants/__init__.py \ + constants/tbreq.py=>constants/tbreq.py \ + constants/tbresp.py=>constants/tbresp.py \ + constants/result.py=>constants/result.py \ + constants/rtexitcode.py=>constants/rtexitcode.py \ + constants/valueunit.py=>constants/valueunit.py + +INSTALLS += validationkit-common +validationkit-common_TEMPLATE = VBoxValidationKitR3 +validationkit-common_INST = $(INST_VALIDATIONKIT)common/ +validationkit-common_SOURCES = $(testboxscript-common_SOURCES) + + +# +# Generate pylint and pychecker targets. +# +VBOX_VALIDATIONKIT_PYTHON_SOURCES += \ + $(wildcard \ + $(PATH_SUB_CURRENT)/*.py \ + $(PATH_SUB_CURRENT)/*/*.py \ + ) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/common/__init__.py b/src/VBox/ValidationKit/common/__init__.py new file mode 100755 index 00000000..e1f780ca --- /dev/null +++ b/src/VBox/ValidationKit/common/__init__.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# $Id: __init__.py $ + +""" +Common code between testmanager, testbox and testdriver. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +from common import constants; +from common import utils; +from common import netutils; +from common import pathutils; +from common import webutils; + diff --git a/src/VBox/ValidationKit/common/constants/Makefile.kup b/src/VBox/ValidationKit/common/constants/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/ValidationKit/common/constants/Makefile.kup diff --git a/src/VBox/ValidationKit/common/constants/__init__.py b/src/VBox/ValidationKit/common/constants/__init__.py new file mode 100755 index 00000000..dc2b7c10 --- /dev/null +++ b/src/VBox/ValidationKit/common/constants/__init__.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# $Id: __init__.py $ + +""" +Constants. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +from common.constants import tbreq; +from common.constants import tbresp; +from common.constants import result; +from common.constants import rtexitcode; +from common.constants import valueunit; + diff --git a/src/VBox/ValidationKit/common/constants/result.py b/src/VBox/ValidationKit/common/constants/result.py new file mode 100644 index 00000000..59f9faec --- /dev/null +++ b/src/VBox/ValidationKit/common/constants/result.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# $Id: result.py $ + +""" +Test statuses. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +PASSED = 'PASSED'; +SKIPPED = 'SKIPPED'; +ABORTED = 'ABORTED'; +BAD_TESTBOX = 'BAD_TESTBOX'; +FAILED = 'FAILED'; +TIMED_OUT = 'TIMED_OUT'; +REBOOTED = 'REBOOTED'; + +## List of valid result valies. +g_kasValidResults = [ PASSED, SKIPPED, ABORTED, BAD_TESTBOX, FAILED, TIMED_OUT, REBOOTED, ]; diff --git a/src/VBox/ValidationKit/common/constants/rtexitcode.py b/src/VBox/ValidationKit/common/constants/rtexitcode.py new file mode 100644 index 00000000..f10a7e3d --- /dev/null +++ b/src/VBox/ValidationKit/common/constants/rtexitcode.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# $Id: rtexitcode.py $ + +""" +RTEXITCODE from iprt/types.h. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +## Success. +RTEXITCODE_SUCCESS = 0; +SUCCESS = RTEXITCODE_SUCCESS; +## General failure. +RTEXITCODE_FAILURE = 1; +FAILURE = RTEXITCODE_FAILURE; +## Invalid arguments. +RTEXITCODE_SYNTAX = 2; +SYNTAX = RTEXITCODE_SYNTAX; +## Initialization failure. +RTEXITCODE_INIT = 3; +INIT = RTEXITCODE_INIT; +## Test skipped. +RTEXITCODE_SKIPPED = 4; +SKIPPED = RTEXITCODE_SKIPPED; +## Bad-testbox. +RTEXITCODE_BAD_TESTBOX = 32; +## Bad-testbox. +BAD_TESTBOX = RTEXITCODE_BAD_TESTBOX; + diff --git a/src/VBox/ValidationKit/common/constants/tbreq.py b/src/VBox/ValidationKit/common/constants/tbreq.py new file mode 100644 index 00000000..c3c066f3 --- /dev/null +++ b/src/VBox/ValidationKit/common/constants/tbreq.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +# $Id: tbreq.py $ + +""" +Test Manager Requests from the TestBox Script. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +## @name Test Manager actions +# @{ +## TestBox sign-on. +SIGNON = 'SIGNON' +## TestBox request for a command while busy (EXEC). +REQUEST_COMMAND_BUSY = 'REQUEST_COMMAND_BUSY' +## TestBox request for a command while idling. +REQUEST_COMMAND_IDLE = 'REQUEST_COMMAND_IDLE' +## TestBox ACKs a command. +COMMAND_ACK = 'COMMAND_ACK' +## TestBox NACKs a command. +COMMAND_NACK = 'COMMAND_NACK' +## TestBox NACKs an unsupported command. +COMMAND_NOTSUP = 'COMMAND_NOTSUP' +## TestBox adds to the main log. +LOG_MAIN = 'LOG_MAIN' +## TestBox uploads a file to the current test result. +UPLOAD = 'UPLOAD' +## TestBox reports completion of an EXEC command. +EXEC_COMPLETED = 'EXEC_COMPLETED' +## Push more "XML" results to the server. +XML_RESULTS = 'XML_RESULTS'; +## @} + + +## @name Parameters for all actions. +# @{ +ALL_PARAM_ACTION = 'ACTION' +ALL_PARAM_TESTBOX_ID = 'TESTBOX_ID' ##< Not supplied by SIGNON. +ALL_PARAM_TESTBOX_UUID = 'TESTBOX_UUID' +## @} + +## @name SIGNON parameters. +# @{ +SIGNON_PARAM_OS = 'OS'; +SIGNON_PARAM_OS_VERSION = 'OS_VERSION'; +SIGNON_PARAM_CPU_VENDOR = 'CPU_VENDOR'; +SIGNON_PARAM_CPU_ARCH = 'CPU_ARCH'; +SIGNON_PARAM_CPU_NAME = 'CPU_NAME'; +SIGNON_PARAM_CPU_REVISION = 'CPU_REVISION'; +SIGNON_PARAM_CPU_COUNT = 'CPU_COUNT'; +SIGNON_PARAM_HAS_HW_VIRT = 'HAS_HW_VIRT'; +SIGNON_PARAM_HAS_NESTED_PAGING = 'HAS_NESTED_PAGING'; +SIGNON_PARAM_HAS_64_BIT_GUEST = 'HAS_64_BIT_GUST'; +SIGNON_PARAM_HAS_IOMMU = 'HAS_IOMMU'; +SIGNON_PARAM_WITH_RAW_MODE = 'WITH_RAW_MODE'; +SIGNON_PARAM_MEM_SIZE = 'MEM_SIZE'; +SIGNON_PARAM_SCRATCH_SIZE = 'SCRATCH_SIZE'; +SIGNON_PARAM_REPORT = 'REPORT'; +SIGNON_PARAM_SCRIPT_REV = 'SCRIPT_REV'; +SIGNON_PARAM_PYTHON_VERSION = 'PYTHON_VERSION'; +## @} + +## @name Parameters for actions reporting results. +# @{ +RESULT_PARAM_TEST_SET_ID = 'TEST_SET_ID' +## @} + +## @name EXEC_COMPLETED parameters. +# @{ +EXEC_COMPLETED_PARAM_RESULT = 'EXEC_RESULT' +## @} + +## @name COMMAND_ACK, COMMAND_NACK and COMMAND_NOTSUP parameters. +# @{ +## The name of the command that's being +COMMAND_ACK_PARAM_CMD_NAME = 'CMD_NAME' +## @} + +## @name LOG_MAIN parameters. +## The log body. +LOG_PARAM_BODY = 'LOG_BODY' +## @} + +## @name UPLOAD_FILE parameters. +## The file name. +UPLOAD_PARAM_NAME = 'UPLOAD_NAME'; +## The MIME type of the file. +UPLOAD_PARAM_MIME = 'UPLOAD_MIME'; +## The kind of file. +UPLOAD_PARAM_KIND = 'UPLOAD_KIND'; +## The file description. +UPLOAD_PARAM_DESC = 'UPLOAD_DESC'; +## @} + +## @name XML_RESULT parameters. +## The "XML" body. +XML_RESULT_PARAM_BODY = 'XML_BODY' +## @} + diff --git a/src/VBox/ValidationKit/common/constants/tbresp.py b/src/VBox/ValidationKit/common/constants/tbresp.py new file mode 100644 index 00000000..5d554daf --- /dev/null +++ b/src/VBox/ValidationKit/common/constants/tbresp.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +# $Id: tbresp.py $ + +""" +Test Manager Responses to the TestBox Script. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +## All test manager actions responses to the testbox include a RESULT field. +ALL_PARAM_RESULT = 'RESULT' + +## @name Statuses (returned in ALL_PARAM_RESULT). +## Acknowledgement. +STATUS_ACK = 'ACK' +## Negative acknowledgement. +STATUS_NACK = 'NACK' +## The testbox is dead, i.e. it no longer exists with the test manager. +# @note Not used by the SIGNON action, but all the rest uses it. +STATUS_DEAD = 'DEAD' +## @} + +## @name Command names (returned in ALL_PARAM_RESULT). +# @{ +CMD_IDLE = 'IDLE' +CMD_WAIT = 'WAIT' +CMD_EXEC = 'EXEC' +CMD_ABORT = 'ABORT' +CMD_REBOOT = 'REBOOT' +CMD_UPGRADE = 'UPGRADE' +CMD_UPGRADE_AND_REBOOT = 'UPGRADE_AND_REBOOT' +CMD_SPECIAL = 'SPECIAL' +## @ } + +## @name SIGNON parameter names. +# @{ +## The TestBox ID. +SIGNON_PARAM_ID = 'TESTBOX_ID' +## The TestBox name. +SIGNON_PARAM_NAME = 'TESTBOX_NAME' +## @} + + +## @name EXEC parameter names +# @{ +## The test set id, used for reporting results. +EXEC_PARAM_RESULT_ID = 'TEST_SET_ID' +## The file to download/copy and unpack into TESTBOX_SCRIPT. +EXEC_PARAM_SCRIPT_ZIPS = 'SCRIPT_ZIPS' +## The testcase invocation command line (bourne shell style). +EXEC_PARAM_SCRIPT_CMD_LINE = 'SCRIPT_CMD_LINE' +## The testcase timeout in seconds. +EXEC_PARAM_TIMEOUT = 'TIMEOUT' +## @} + +## @name UPGRADE and @name UPGRADE_AND_REBOOT parameter names. +# @{ +## A URL for downloading new version of Test Box Script archive +UPGRADE_PARAM_URL = 'DOWNLOAD_URL' +## @} + diff --git a/src/VBox/ValidationKit/common/constants/valueunit.py b/src/VBox/ValidationKit/common/constants/valueunit.py new file mode 100644 index 00000000..390ef80c --- /dev/null +++ b/src/VBox/ValidationKit/common/constants/valueunit.py @@ -0,0 +1,168 @@ +# -*- coding: utf-8 -*- +# $Id: valueunit.py $ + +""" +Test Value Unit Definititions. + +This must correspond 1:1 with include/iprt/test.h and +include/VBox/VMMDevTesting.h. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + + +## @name Unit constants. +## Used everywhere. +## @note Using upper case here so we can copy, past and chop from the other +# headers. +## @{ +PCT = 0x01; +BYTES = 0x02; +BYTES_PER_SEC = 0x03; +KILOBYTES = 0x04; +KILOBYTES_PER_SEC = 0x05; +MEGABYTES = 0x06; +MEGABYTES_PER_SEC = 0x07; +PACKETS = 0x08; +PACKETS_PER_SEC = 0x09; +FRAMES = 0x0a; +FRAMES_PER_SEC = 0x0b; +OCCURRENCES = 0x0c; +OCCURRENCES_PER_SEC = 0x0d; +CALLS = 0x0e; +CALLS_PER_SEC = 0x0f; +ROUND_TRIP = 0x10; +SECS = 0x11; +MS = 0x12; +NS = 0x13; +NS_PER_CALL = 0x14; +NS_PER_FRAME = 0x15; +NS_PER_OCCURRENCE = 0x16; +NS_PER_PACKET = 0x17; +NS_PER_ROUND_TRIP = 0x18; +INSTRS = 0x19; +INSTRS_PER_SEC = 0x1a; +NONE = 0x1b; +PP1K = 0x1c; +PP10K = 0x1d; +PPM = 0x1e; +PPB = 0x1f; +TICKS = 0x20; +TICKS_PER_CALL = 0x21; +TICKS_PER_OCCURENCE = 0x22; +PAGES = 0x23; +PAGES_PER_SEC = 0x24; +TICKS_PER_PAGE = 0x25; +NS_PER_PAGE = 0x26; +PS = 0x27; +PS_PER_CALL = 0x28; +PS_PER_FRAME = 0x29; +PS_PER_OCCURRENCE = 0x2a; +PS_PER_PACKET = 0x2b; +PS_PER_ROUND_TRIP = 0x2c; +PS_PER_PAGE = 0x2d; +END = 0x2e; +## @} + + +## Translate constant to string. +g_asNames = \ +[ + 'invalid', # 0 + '%', + 'bytes', + 'bytes/s', + 'KiB', + 'KiB/s', + 'MiB', + 'MiB/s', + 'packets', + 'packets/s', + 'frames', + 'frames/s', + 'occurrences', + 'occurrences/s', + 'calls', + 'calls/s', + 'roundtrips', + 's', + 'ms', + 'ns', + 'ns/call', + 'ns/frame', + 'ns/occurrences', + 'ns/packet', + 'ns/roundtrips', + 'ins', + 'ins/s', + '', # none + 'pp1k', + 'pp10k', + 'ppm', + 'ppb', + 'ticks', + 'ticks/call', + 'ticks/occ', + 'pages', + 'pages/s', + 'ticks/page', + 'ns/page', + 'ps', + 'ps/call', + 'ps/frame', + 'ps/occurrences', + 'ps/packet', + 'ps/roundtrips', + 'ps/page', +]; +assert g_asNames[PP1K] == 'pp1k'; +assert g_asNames[NS_PER_PAGE] == 'ns/page'; +assert g_asNames[PS_PER_PAGE] == 'ps/page'; + + +## Translation table for XML -> number. +g_kdNameToConst = \ +{ + 'KB': KILOBYTES, + 'KB/s': KILOBYTES_PER_SEC, + 'MB': MEGABYTES, + 'MB/s': MEGABYTES_PER_SEC, + 'occurrences': OCCURRENCES, + 'occurrences/s': OCCURRENCES_PER_SEC, + +}; +for i in range(1, len(g_asNames)): + g_kdNameToConst[g_asNames[i]] = i; + diff --git a/src/VBox/ValidationKit/common/netutils.py b/src/VBox/ValidationKit/common/netutils.py new file mode 100755 index 00000000..6f5f6366 --- /dev/null +++ b/src/VBox/ValidationKit/common/netutils.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +# $Id: netutils.py $ +# pylint: disable=too-many-lines + +""" +Common Network Utility Functions. +""" + +from __future__ import print_function; + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import socket; + + +def getPrimaryHostIpByUdp(sPeerIp = '255.255.255.255'): + """ + Worker for getPrimaryHostIp. + + The method is opening a UDP socket targetting a random port on a + limited (local LAN) broadcast address. We then use getsockname() to + obtain our own IP address, which should then be the primary IP. + + Unfortunately, this doesn't always work reliably on Solaris. When for + instance our host only is configured, which interface we end up on seems + to be totally random. + """ + + try: oSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); + except: oSocket = None; + if oSocket is not None: + try: + oSocket.connect((sPeerIp, 1984)); + sHostIp = oSocket.getsockname()[0]; + except: + sHostIp = None; + oSocket.close(); + if sHostIp is not None: + return sHostIp; + return '127.0.0.1'; + + +def getPrimaryHostIpByHostname(): + """ + Worker for getPrimaryHostIp. + + Attempts to resolve the hostname. + """ + try: + return socket.gethostbyname(getHostnameFqdn()); + except: + return '127.0.0.1'; + + +def getPrimaryHostIp(): + """ + Tries to figure out the primary (the one with default route), local + IPv4 address. + + Returns the IP address on success and otherwise '127.0.0.1'. + """ + + # + # This isn't quite as easy as one would think. Doing a UDP connect to + # 255.255.255.255 turns out to be problematic on solaris with more than one + # network interface (IP is random selected it seems), as well as linux + # where we've seen 127.0.1.1 being returned on some hosts. + # + # So a modified algorithm first try a known public IP address, ASSUMING + # that the primary interface is the one that gets us onto the internet. + # If that fails, due to routing or whatever, we try 255.255.255.255 and + # then finally hostname resolution. + # + sHostIp = getPrimaryHostIpByUdp('8.8.8.8'); + if sHostIp.startswith('127.'): + sHostIp = getPrimaryHostIpByUdp('255.255.255.255'); + if sHostIp.startswith('127.'): + sHostIp = getPrimaryHostIpByHostname(); + return sHostIp; + + +def getHostnameFqdn(): + """ + Wrapper around getfqdn. + + Returns the fully qualified hostname, None if not found. + """ + + try: + sHostname = socket.getfqdn(); + except: + return None; + + if '.' in sHostname or sHostname.startswith('localhost'): + return sHostname; + + # + # Somewhat misconfigured system, needs expensive approach to guessing FQDN. + # Get address information on the hostname and do a reverse lookup from that. + # + try: + aAddressInfo = socket.getaddrinfo(sHostname, None); + except: + return sHostname; + + for aAI in aAddressInfo: + try: sName, _ = socket.getnameinfo(aAI[4], 0); + except: continue; + if '.' in sName and not set(sName).issubset(set('0123456789.')) and not sName.startswith('localhost'): + return sName; + + return sHostname; + diff --git a/src/VBox/ValidationKit/common/pathutils.py b/src/VBox/ValidationKit/common/pathutils.py new file mode 100644 index 00000000..92800871 --- /dev/null +++ b/src/VBox/ValidationKit/common/pathutils.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +# $Id: pathutils.py $ +# pylint: disable=too-many-lines + +""" +Path Utility Functions. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import unittest; + + +## @name Path data keyed by fDosStyle bool value. +## @{ +g_dsPathSlash = { False: '/', True: '\\', }; +g_dasPathSlashes = { False: ('/',), True: ('\\', '/', ), }; +g_dasPathSeparators = { False: ('/',), True: ('\\', '/', ':', ), }; +## @} + + +def joinEx(fDosStyle, sBase, *asAppend): + """ + Mimicking os.path.join, but where target system isn't the host. + The code is very simple at present. + """ + # Get the first non-None element and use it as base. + i = 0; + sRet = sBase; + while sRet is None and i < len(asAppend): + sRet = asAppend[i]; + i += 1; + + while i < len(asAppend): + sAppend = asAppend[i]; + + # Skip None elements. + if sAppend is not None: + # Strip leading slashes from sAppend: + offSkip = 0; + while offSkip < len(sAppend) and sAppend[offSkip] in g_dasPathSlashes[fDosStyle]: + offSkip += 1; + + # Add separator if needed before appending the new bit: + if not sRet or sRet[-1] not in g_dasPathSeparators[fDosStyle]: + sRet += g_dsPathSlash[fDosStyle] + sAppend[offSkip:]; + else: + sRet += sAppend[offSkip:]; + + i += 1; + + return sRet; + + +# +# Unit testing. +# + +# pylint: disable=missing-docstring,undefined-variable +class JoinExTestCase(unittest.TestCase): + def testJoinEx(self): + self.assertEqual(joinEx(True, None), None); + self.assertEqual(joinEx(False, None), None); + self.assertEqual(joinEx(True, ''), ''); + self.assertEqual(joinEx(False, ''), ''); + self.assertEqual(joinEx(True, '',''), '\\'); + self.assertEqual(joinEx(False, '',''), '/'); + self.assertEqual(joinEx(True, 'C:','dos'), 'C:dos'); + self.assertEqual(joinEx(True, 'C:/','dos'), 'C:/dos'); + self.assertEqual(joinEx(True, 'C:\\','dos'), 'C:\\dos'); + self.assertEqual(joinEx(True, 'C:\\dos','edlin.com'), 'C:\\dos\\edlin.com'); + self.assertEqual(joinEx(True, 'C:\\dos\\','edlin.com'), 'C:\\dos\\edlin.com'); + self.assertEqual(joinEx(True, 'C:\\dos/','edlin.com'), 'C:\\dos/edlin.com'); + self.assertEqual(joinEx(True, 'C:\\dos//','edlin.com'), 'C:\\dos//edlin.com'); + self.assertEqual(joinEx(True, 'C:\\dos','\\/edlin.com'), 'C:\\dos\\edlin.com'); + self.assertEqual(joinEx(True, 'C:\\dos', None, 'edlin.com'), 'C:\\dos\\edlin.com'); + self.assertEqual(joinEx(True, None, 'C:\\dos', None, 'edlin.com'), 'C:\\dos\\edlin.com'); + self.assertEqual(joinEx(True, None, None, 'C:\\dos', None, 'edlin.com', None), 'C:\\dos\\edlin.com'); + self.assertEqual(joinEx(False, '/', 'bin', 'ls'), '/bin/ls'); + self.assertEqual(joinEx(False, '/', '/bin', 'ls'), '/bin/ls'); + self.assertEqual(joinEx(False, '/', '/bin/', 'ls'), '/bin/ls'); + self.assertEqual(joinEx(False, '/', '/bin//', 'ls'), '/bin//ls'); + self.assertEqual(joinEx(False, '/', None, 'bin', None, 'ls', None), '/bin/ls'); + self.assertEqual(joinEx(False, None, '/', None, 'bin', None, 'ls', None), '/bin/ls'); + + +if __name__ == '__main__': + unittest.main(); + # not reached. + diff --git a/src/VBox/ValidationKit/common/utils.py b/src/VBox/ValidationKit/common/utils.py new file mode 100755 index 00000000..c6bc9b32 --- /dev/null +++ b/src/VBox/ValidationKit/common/utils.py @@ -0,0 +1,2571 @@ +# -*- coding: utf-8 -*- +# $Id: utils.py $ +# pylint: disable=too-many-lines + +""" +Common Utility Functions. +""" + +from __future__ import print_function; + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard Python imports. +import datetime; +import errno; +import os; +import platform; +import re; +import stat; +import subprocess; +import sys; +import time; +import traceback; +import unittest; + +if sys.platform == 'win32': + import ctypes; + import msvcrt; # pylint: disable=import-error + import win32api; # pylint: disable=import-error + import win32con; # pylint: disable=import-error + import win32console; # pylint: disable=import-error + import win32file; # pylint: disable=import-error + import win32process; # pylint: disable=import-error + import winerror; # pylint: disable=import-error + import pywintypes; # pylint: disable=import-error +else: + import signal; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + unicode = str; # pylint: disable=redefined-builtin,invalid-name + xrange = range; # pylint: disable=redefined-builtin,invalid-name + long = int; # pylint: disable=redefined-builtin,invalid-name + + +# +# Strings. +# + +def toUnicode(sString, encoding = None, errors = 'strict'): + """ + A little like the python 2 unicode() function. + """ + if sys.version_info[0] >= 3: + if isinstance(sString, bytes): + return str(sString, encoding if encoding else 'utf-8', errors); + else: + if not isinstance(sString, unicode): + return unicode(sString, encoding if encoding else 'utf-8', errors); + return sString; + + + +# +# Output. +# + +def printOut(sString): + """ + Outputs a string to standard output, dealing with python 2.x encoding stupidity. + """ + sStreamEncoding = sys.stdout.encoding; + if sStreamEncoding is None: # Files, pipes and such on 2.x. (pylint is confused here) + sStreamEncoding = 'US-ASCII'; # pylint: disable=redefined-variable-type + if sStreamEncoding == 'UTF-8' or not isinstance(sString, unicode): + print(sString); + else: + print(sString.encode(sStreamEncoding, 'backslashreplace').decode(sStreamEncoding)); + +def printErr(sString): + """ + Outputs a string to standard error, dealing with python 2.x encoding stupidity. + """ + sStreamEncoding = sys.stderr.encoding; + if sStreamEncoding is None: # Files, pipes and such on 2.x. (pylint is confused here) + sStreamEncoding = 'US-ASCII'; # pylint: disable=redefined-variable-type + if sStreamEncoding == 'UTF-8' or not isinstance(sString, unicode): + print(sString, file = sys.stderr); + else: + print(sString.encode(sStreamEncoding, 'backslashreplace').decode(sStreamEncoding), file = sys.stderr); + + +# +# Host OS and CPU. +# + +def getHostOs(): + """ + Gets the host OS name (short). + + See the KBUILD_OSES variable in kBuild/header.kmk for possible return values. + """ + sPlatform = platform.system(); + if sPlatform in ('Linux', 'Darwin', 'Solaris', 'FreeBSD', 'NetBSD', 'OpenBSD'): + sPlatform = sPlatform.lower(); + elif sPlatform == 'Windows': + sPlatform = 'win'; + elif sPlatform == 'SunOS': + sPlatform = 'solaris'; + else: + raise Exception('Unsupported platform "%s"' % (sPlatform,)); + return sPlatform; + +g_sHostArch = None; + +def getHostArch(): + """ + Gets the host CPU architecture. + + See the KBUILD_ARCHES variable in kBuild/header.kmk for possible return values. + """ + global g_sHostArch; + if g_sHostArch is None: + sArch = platform.machine(); + if sArch in ('i386', 'i486', 'i586', 'i686', 'i786', 'i886', 'x86'): + sArch = 'x86'; + elif sArch in ('AMD64', 'amd64', 'x86_64'): + sArch = 'amd64'; + elif sArch == 'i86pc': # SunOS + if platform.architecture()[0] == '64bit': + sArch = 'amd64'; + else: + try: + sArch = str(processOutputChecked(['/usr/bin/isainfo', '-n',])); + except: + pass; + sArch = sArch.strip(); + if sArch != 'amd64': + sArch = 'x86'; + else: + raise Exception('Unsupported architecture/machine "%s"' % (sArch,)); + g_sHostArch = sArch; + return g_sHostArch; + + +def getHostOsDotArch(): + """ + Gets the 'os.arch' for the host. + """ + return '%s.%s' % (getHostOs(), getHostArch()); + + +def isValidOs(sOs): + """ + Validates the OS name. + """ + if sOs in ('darwin', 'dos', 'dragonfly', 'freebsd', 'haiku', 'l4', 'linux', 'netbsd', 'nt', 'openbsd', \ + 'os2', 'solaris', 'win', 'os-agnostic'): + return True; + return False; + + +def isValidArch(sArch): + """ + Validates the CPU architecture name. + """ + if sArch in ('x86', 'amd64', 'sparc32', 'sparc64', 's390', 's390x', 'ppc32', 'ppc64', \ + 'mips32', 'mips64', 'ia64', 'hppa32', 'hppa64', 'arm', 'alpha'): + return True; + return False; + +def isValidOsDotArch(sOsDotArch): + """ + Validates the 'os.arch' string. + """ + + asParts = sOsDotArch.split('.'); + if asParts.length() != 2: + return False; + return isValidOs(asParts[0]) \ + and isValidArch(asParts[1]); + +def getHostOsVersion(): + """ + Returns the host OS version. This is platform.release with additional + distro indicator on linux. + """ + sVersion = platform.release(); + sOs = getHostOs(); + if sOs == 'linux': + sDist = ''; + try: + # try /etc/lsb-release first to distinguish between Debian and Ubuntu + with open('/etc/lsb-release') as oFile: # pylint: disable=unspecified-encoding + for sLine in oFile: + oMatch = re.search(r'(?:DISTRIB_DESCRIPTION\s*=)\s*"*(.*)"', sLine); + if oMatch is not None: + sDist = oMatch.group(1).strip(); + except: + pass; + if sDist: + sVersion += ' / ' + sDist; + else: + asFiles = \ + [ + [ '/etc/debian_version', 'Debian v'], + [ '/etc/gentoo-release', '' ], + [ '/etc/oracle-release', '' ], + [ '/etc/redhat-release', '' ], + [ '/etc/SuSE-release', '' ], + ]; + for sFile, sPrefix in asFiles: + if os.path.isfile(sFile): + try: + with open(sFile) as oFile: # pylint: disable=unspecified-encoding + sLine = oFile.readline(); + except: + continue; + sLine = sLine.strip() + if sLine: + sVersion += ' / ' + sPrefix + sLine; + break; + + elif sOs == 'solaris': + sVersion = platform.version(); + if os.path.isfile('/etc/release'): + try: + with open('/etc/release') as oFile: # pylint: disable=unspecified-encoding + sLast = oFile.readlines()[-1]; + sLast = sLast.strip(); + if sLast: + sVersion += ' (' + sLast + ')'; + except: + pass; + + elif sOs == 'darwin': + def getMacVersionName(sVersion): + """ + Figures out the Mac OS X/macOS code name from the numeric version. + """ + aOsVersion = sVersion.split('.') # example: ('10','15','7') + codenames = {"4": "Tiger", + "5": "Leopard", + "6": "Snow Leopard", + "7": "Lion", + "8": "Mountain Lion", + "9": "Mavericks", + "10": "Yosemite", + "11": "El Capitan", + "12": "Sierra", + "13": "High Sierra", + "14": "Mojave", + "15": "Catalina", + "16": "Wrong version", + } + codenames_afterCatalina = {"11": "Big Sur", + "12": "Monterey", + "13": "Ventura", + "14": "Unknown 14", + "15": "Unknown 15"} + + if aOsVersion[0] == '10': + sResult = codenames[aOsVersion[1]] + else: + sResult = codenames_afterCatalina[aOsVersion[0]] + return sResult + + sOsxVersion = platform.mac_ver()[0] + sVersion += ' / OS X ' + sOsxVersion + ' (' + getMacVersionName(sOsxVersion) + ')' + + elif sOs == 'win': + class OSVersionInfoEx(ctypes.Structure): + """ OSVERSIONEX """ + kaFields = [ + ('dwOSVersionInfoSize', ctypes.c_ulong), + ('dwMajorVersion', ctypes.c_ulong), + ('dwMinorVersion', ctypes.c_ulong), + ('dwBuildNumber', ctypes.c_ulong), + ('dwPlatformId', ctypes.c_ulong), + ('szCSDVersion', ctypes.c_wchar*128), + ('wServicePackMajor', ctypes.c_ushort), + ('wServicePackMinor', ctypes.c_ushort), + ('wSuiteMask', ctypes.c_ushort), + ('wProductType', ctypes.c_byte), + ('wReserved', ctypes.c_byte)] + _fields_ = kaFields # pylint: disable=invalid-name + + def __init__(self): + super(OSVersionInfoEx, self).__init__() + self.dwOSVersionInfoSize = ctypes.sizeof(self) + + oOsVersion = OSVersionInfoEx() + rc = ctypes.windll.Ntdll.RtlGetVersion(ctypes.byref(oOsVersion)) + if rc == 0: + # Python platform.release() is not reliable for newer server releases + if oOsVersion.wProductType != 1: + if oOsVersion.dwMajorVersion == 10 and oOsVersion.dwMinorVersion == 0: + sVersion = '2016Server'; + elif oOsVersion.dwMajorVersion == 6 and oOsVersion.dwMinorVersion == 3: + sVersion = '2012ServerR2'; + elif oOsVersion.dwMajorVersion == 6 and oOsVersion.dwMinorVersion == 2: + sVersion = '2012Server'; + elif oOsVersion.dwMajorVersion == 6 and oOsVersion.dwMinorVersion == 1: + sVersion = '2008ServerR2'; + elif oOsVersion.dwMajorVersion == 6 and oOsVersion.dwMinorVersion == 0: + sVersion = '2008Server'; + elif oOsVersion.dwMajorVersion == 5 and oOsVersion.dwMinorVersion == 2: + sVersion = '2003Server'; + sVersion += ' build ' + str(oOsVersion.dwBuildNumber) + if oOsVersion.wServicePackMajor: + sVersion += ' SP' + str(oOsVersion.wServicePackMajor) + if oOsVersion.wServicePackMinor: + sVersion += '.' + str(oOsVersion.wServicePackMinor) + + return sVersion; + +def getPresentCpuCount(): + """ + Gets the number of CPUs present in the system. + + This differs from multiprocessor.cpu_count() and os.cpu_count() on windows in + that we return the active count rather than the maximum count. If we don't, + we will end up thinking testboxmem1 has 512 CPU threads, which it doesn't and + never will have. + + @todo This is probably not exactly what we get on non-windows... + """ + + if getHostOs() == 'win': + fnGetActiveProcessorCount = getattr(ctypes.windll.kernel32, 'GetActiveProcessorCount', None); + if fnGetActiveProcessorCount: + cCpus = fnGetActiveProcessorCount(ctypes.c_ushort(0xffff)); + if cCpus > 0: + return cCpus; + + import multiprocessing + return multiprocessing.cpu_count(); + + +# +# File system. +# + +def openNoInherit(sFile, sMode = 'r'): + """ + Wrapper around open() that tries it's best to make sure the file isn't + inherited by child processes. + + This is a best effort thing at the moment as it doesn't synchronizes with + child process spawning in any way. Thus it can be subject to races in + multithreaded programs. + """ + + # Python 3.4 and later automatically creates non-inherit handles. See PEP-0446. + uPythonVer = (sys.version_info[0] << 16) | (sys.version_info[1] & 0xffff); + if uPythonVer >= ((3 << 16) | 4): + oFile = open(sFile, sMode); # pylint: disable=consider-using-with,unspecified-encoding + else: + try: + from fcntl import FD_CLOEXEC, F_GETFD, F_SETFD, fcntl; # pylint: disable=import-error + except: + # On windows, we can use the 'N' flag introduced in Visual C++ 7.0 or 7.1 with python 2.x. + if getHostOs() == 'win': + if uPythonVer < (3 << 16): + offComma = sMode.find(','); + if offComma < 0: + return open(sFile, sMode + 'N'); # pylint: disable=consider-using-with,unspecified-encoding + return open(sFile, # pylint: disable=consider-using-with,unspecified-encoding,bad-open-mode + sMode[:offComma] + 'N' + sMode[offComma:]); + + # Just in case. + return open(sFile, sMode); # pylint: disable=consider-using-with,unspecified-encoding + + oFile = open(sFile, sMode); # pylint: disable=consider-using-with,unspecified-encoding + #try: + fcntl(oFile, F_SETFD, fcntl(oFile, F_GETFD) | FD_CLOEXEC); + #except: + # pass; + return oFile; + +def openNoDenyDeleteNoInherit(sFile, sMode = 'r'): + """ + Wrapper around open() that tries it's best to make sure the file isn't + inherited by child processes. + + This is a best effort thing at the moment as it doesn't synchronizes with + child process spawning in any way. Thus it can be subject to races in + multithreaded programs. + """ + + if getHostOs() == 'win': + # Need to use CreateFile directly to open the file so we can feed it FILE_SHARE_DELETE. + # pylint: disable=no-member,c-extension-no-member + fAccess = 0; + fDisposition = win32file.OPEN_EXISTING; + if 'r' in sMode or '+' in sMode: + fAccess |= win32file.GENERIC_READ; + if 'a' in sMode: + fAccess |= win32file.GENERIC_WRITE; + fDisposition = win32file.OPEN_ALWAYS; + elif 'w' in sMode: + fAccess = win32file.GENERIC_WRITE; + if '+' in sMode: + fDisposition = win32file.OPEN_ALWAYS; + fAccess |= win32file.GENERIC_READ; + else: + fDisposition = win32file.CREATE_ALWAYS; + if not fAccess: + fAccess |= win32file.GENERIC_READ; + fSharing = (win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE + | win32file.FILE_SHARE_DELETE); + hFile = win32file.CreateFile(sFile, fAccess, fSharing, None, fDisposition, 0, None); + if 'a' in sMode: + win32file.SetFilePointer(hFile, 0, win32file.FILE_END); + + # Turn the NT handle into a CRT file descriptor. + hDetachedFile = hFile.Detach(); + if fAccess == win32file.GENERIC_READ: + fOpen = os.O_RDONLY; + elif fAccess == win32file.GENERIC_WRITE: + fOpen = os.O_WRONLY; + else: + fOpen = os.O_RDWR; + # pulint: enable=no-member,c-extension-no-member + if 'a' in sMode: + fOpen |= os.O_APPEND; + if 'b' in sMode or 't' in sMode: + fOpen |= os.O_TEXT; # pylint: disable=no-member + fdFile = msvcrt.open_osfhandle(hDetachedFile, fOpen); + + # Tell python to use this handle. + oFile = os.fdopen(fdFile, sMode); + else: + oFile = open(sFile, sMode); # pylint: disable=consider-using-with,unspecified-encoding + + # Python 3.4 and later automatically creates non-inherit handles. See PEP-0446. + uPythonVer = (sys.version_info[0] << 16) | (sys.version_info[1] & 0xffff); + if uPythonVer < ((3 << 16) | 4): + try: + from fcntl import FD_CLOEXEC, F_GETFD, F_SETFD, fcntl; # pylint: disable=import-error + except: + pass; + else: + fcntl(oFile, F_SETFD, fcntl(oFile, F_GETFD) | FD_CLOEXEC); + return oFile; + +def noxcptReadLink(sPath, sXcptRet, sEncoding = 'utf-8'): + """ + No exceptions os.readlink wrapper. + """ + try: + sRet = os.readlink(sPath); # pylint: disable=no-member + except: + return sXcptRet; + if hasattr(sRet, 'decode'): + sRet = sRet.decode(sEncoding, 'ignore'); + return sRet; + +def readFile(sFile, sMode = 'rb'): + """ + Reads the entire file. + """ + with open(sFile, sMode) as oFile: # pylint: disable=unspecified-encoding + sRet = oFile.read(); + return sRet; + +def noxcptReadFile(sFile, sXcptRet, sMode = 'rb', sEncoding = 'utf-8'): + """ + No exceptions common.readFile wrapper. + """ + try: + sRet = readFile(sFile, sMode); + except: + sRet = sXcptRet; + if sEncoding is not None and hasattr(sRet, 'decode'): + sRet = sRet.decode(sEncoding, 'ignore'); + return sRet; + +def noxcptRmDir(sDir, oXcptRet = False): + """ + No exceptions os.rmdir wrapper. + """ + oRet = True; + try: + os.rmdir(sDir); + except: + oRet = oXcptRet; + return oRet; + +def noxcptDeleteFile(sFile, oXcptRet = False): + """ + No exceptions os.remove wrapper. + """ + oRet = True; + try: + os.remove(sFile); + except: + oRet = oXcptRet; + return oRet; + + +def dirEnumerateTree(sDir, fnCallback, fIgnoreExceptions = True): + # type: (string, (string, stat) -> bool) -> bool + """ + Recursively walks a directory tree, calling fnCallback for each. + + fnCallback takes a full path and stat object (can be None). It + returns a boolean value, False stops walking and returns immediately. + + Returns True or False depending on fnCallback. + Returns None fIgnoreExceptions is True and an exception was raised by listdir. + """ + def __worker(sCurDir): + """ Worker for """ + try: + asNames = os.listdir(sCurDir); + except: + if not fIgnoreExceptions: + raise; + return None; + rc = True; + for sName in asNames: + if sName not in [ '.', '..' ]: + sFullName = os.path.join(sCurDir, sName); + try: oStat = os.lstat(sFullName); + except: oStat = None; + if fnCallback(sFullName, oStat) is False: + return False; + if oStat is not None and stat.S_ISDIR(oStat.st_mode): + rc = __worker(sFullName); + if rc is False: + break; + return rc; + + # Ensure unicode path here so listdir also returns unicode on windows. + ## @todo figure out unicode stuff on non-windows. + if sys.platform == 'win32': + sDir = unicode(sDir); + return __worker(sDir); + + + +def formatFileMode(uMode): + # type: (int) -> string + """ + Format a st_mode value 'ls -la' fasion. + Returns string. + """ + if stat.S_ISDIR(uMode): sMode = 'd'; + elif stat.S_ISREG(uMode): sMode = '-'; + elif stat.S_ISLNK(uMode): sMode = 'l'; + elif stat.S_ISFIFO(uMode): sMode = 'p'; + elif stat.S_ISCHR(uMode): sMode = 'c'; + elif stat.S_ISBLK(uMode): sMode = 'b'; + elif stat.S_ISSOCK(uMode): sMode = 's'; + else: sMode = '?'; + ## @todo sticky bits. + sMode += 'r' if uMode & stat.S_IRUSR else '-'; + sMode += 'w' if uMode & stat.S_IWUSR else '-'; + sMode += 'x' if uMode & stat.S_IXUSR else '-'; + sMode += 'r' if uMode & stat.S_IRGRP else '-'; + sMode += 'w' if uMode & stat.S_IWGRP else '-'; + sMode += 'x' if uMode & stat.S_IXGRP else '-'; + sMode += 'r' if uMode & stat.S_IROTH else '-'; + sMode += 'w' if uMode & stat.S_IWOTH else '-'; + sMode += 'x' if uMode & stat.S_IXOTH else '-'; + sMode += ' '; + return sMode; + + +def formatFileStat(oStat): + # type: (stat) -> string + """ + Format a stat result 'ls -la' fasion (numeric IDs). + Returns string. + """ + return '%s %3s %4s %4s %10s %s' \ + % (formatFileMode(oStat.st_mode), oStat.st_nlink, oStat.st_uid, oStat.st_gid, oStat.st_size, + time.strftime('%Y-%m-%d %H:%M', time.localtime(oStat.st_mtime)), ); + +## Good buffer for file operations. +g_cbGoodBufferSize = 256*1024; + +## The original shutil.copyfileobj. +g_fnOriginalShCopyFileObj = None; + +def __myshutilcopyfileobj(fsrc, fdst, length = g_cbGoodBufferSize): + """ shutil.copyfileobj with different length default value (16384 is slow with python 2.7 on windows). """ + return g_fnOriginalShCopyFileObj(fsrc, fdst, length); + +def __installShUtilHacks(shutil): + """ Installs the shutil buffer size hacks. """ + global g_fnOriginalShCopyFileObj; + if g_fnOriginalShCopyFileObj is None: + g_fnOriginalShCopyFileObj = shutil.copyfileobj; + shutil.copyfileobj = __myshutilcopyfileobj; + return True; + + +def copyFileSimple(sFileSrc, sFileDst): + """ + Wrapper around shutil.copyfile that simply copies the data of a regular file. + Raises exception on failure. + Return True for show. + """ + import shutil; + __installShUtilHacks(shutil); + return shutil.copyfile(sFileSrc, sFileDst); + + +def getDiskUsage(sPath): + """ + Get free space of a partition that corresponds to specified sPath in MB. + + Returns partition free space value in MB. + """ + if platform.system() == 'Windows': + oCTypeFreeSpace = ctypes.c_ulonglong(0); + ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(sPath), None, None, + ctypes.pointer(oCTypeFreeSpace)); + cbFreeSpace = oCTypeFreeSpace.value; + else: + oStats = os.statvfs(sPath); # pylint: disable=no-member + cbFreeSpace = long(oStats.f_frsize) * oStats.f_bfree; + + # Convert to MB + cMbFreeSpace = long(cbFreeSpace) / (1024 * 1024); + + return cMbFreeSpace; + + + +# +# SubProcess. +# + +def _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs): + """ + If the "executable" is a python script, insert the python interpreter at + the head of the argument list so that it will work on systems which doesn't + support hash-bang scripts. + """ + + asArgs = dKeywordArgs.get('args'); + if asArgs is None: + asArgs = aPositionalArgs[0]; + + if asArgs[0].endswith('.py'): + if sys.executable: + asArgs.insert(0, sys.executable); + else: + asArgs.insert(0, 'python'); + + # paranoia... + if dKeywordArgs.get('args') is not None: + dKeywordArgs['args'] = asArgs; + else: + aPositionalArgs = (asArgs,) + aPositionalArgs[1:]; + return None; + +def processPopenSafe(*aPositionalArgs, **dKeywordArgs): + """ + Wrapper for subprocess.Popen that's Ctrl-C safe on windows. + """ + if getHostOs() == 'win': + if dKeywordArgs.get('creationflags', 0) == 0: + dKeywordArgs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP; + return subprocess.Popen(*aPositionalArgs, **dKeywordArgs); # pylint: disable=consider-using-with + +def processStart(*aPositionalArgs, **dKeywordArgs): + """ + Wrapper around subprocess.Popen to deal with its absence in older + python versions. + Returns process object on success which can be worked on. + """ + _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs); + return processPopenSafe(*aPositionalArgs, **dKeywordArgs); + +def processCall(*aPositionalArgs, **dKeywordArgs): + """ + Wrapper around subprocess.call to deal with its absence in older + python versions. + Returns process exit code (see subprocess.poll). + """ + assert dKeywordArgs.get('stdout') is None; + assert dKeywordArgs.get('stderr') is None; + oProcess = processStart(*aPositionalArgs, **dKeywordArgs); + return oProcess.wait(); + +def processOutputChecked(*aPositionalArgs, **dKeywordArgs): + """ + Wrapper around subprocess.check_output to deal with its absense in older + python versions. + + Extra keywords for specifying now output is to be decoded: + sEncoding='utf-8 + fIgnoreEncoding=True/False + """ + sEncoding = dKeywordArgs.get('sEncoding'); + if sEncoding is not None: del dKeywordArgs['sEncoding']; + else: sEncoding = 'utf-8'; + + fIgnoreEncoding = dKeywordArgs.get('fIgnoreEncoding'); + if fIgnoreEncoding is not None: del dKeywordArgs['fIgnoreEncoding']; + else: fIgnoreEncoding = True; + + _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs); + oProcess = processPopenSafe(stdout=subprocess.PIPE, *aPositionalArgs, **dKeywordArgs); + + sOutput, _ = oProcess.communicate(); + iExitCode = oProcess.poll(); + + if iExitCode != 0: + asArgs = dKeywordArgs.get('args'); + if asArgs is None: + asArgs = aPositionalArgs[0]; + print(sOutput); + raise subprocess.CalledProcessError(iExitCode, asArgs); + + if hasattr(sOutput, 'decode'): + sOutput = sOutput.decode(sEncoding, 'ignore' if fIgnoreEncoding else 'strict'); + return sOutput; + +def processOutputUnchecked(*aPositionalArgs, **dKeywordArgs): + """ + Similar to processOutputChecked, but returns status code and both stdout + and stderr results. + + Extra keywords for specifying now output is to be decoded: + sEncoding='utf-8 + fIgnoreEncoding=True/False + """ + sEncoding = dKeywordArgs.get('sEncoding'); + if sEncoding is not None: del dKeywordArgs['sEncoding']; + else: sEncoding = 'utf-8'; + + fIgnoreEncoding = dKeywordArgs.get('fIgnoreEncoding'); + if fIgnoreEncoding is not None: del dKeywordArgs['fIgnoreEncoding']; + else: fIgnoreEncoding = True; + + _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs); + oProcess = processPopenSafe(stdout = subprocess.PIPE, stderr = subprocess.PIPE, *aPositionalArgs, **dKeywordArgs); + + sOutput, sError = oProcess.communicate(); + iExitCode = oProcess.poll(); + + if hasattr(sOutput, 'decode'): + sOutput = sOutput.decode(sEncoding, 'ignore' if fIgnoreEncoding else 'strict'); + if hasattr(sError, 'decode'): + sError = sError.decode(sEncoding, 'ignore' if fIgnoreEncoding else 'strict'); + return (iExitCode, sOutput, sError); + +g_fOldSudo = None; +def _sudoFixArguments(aPositionalArgs, dKeywordArgs, fInitialEnv = True): + """ + Adds 'sudo' (or similar) to the args parameter, whereever it is. + """ + + # Are we root? + fIsRoot = True; + try: + fIsRoot = os.getuid() == 0; # pylint: disable=no-member + except: + pass; + + # If not, prepend sudo (non-interactive, simulate initial login). + if fIsRoot is not True: + asArgs = dKeywordArgs.get('args'); + if asArgs is None: + asArgs = aPositionalArgs[0]; + + # Detect old sudo. + global g_fOldSudo; + if g_fOldSudo is None: + try: + sVersion = str(processOutputChecked(['sudo', '-V'])); + except: + sVersion = '1.7.0'; + sVersion = sVersion.strip().split('\n', 1)[0]; + sVersion = sVersion.replace('Sudo version', '').strip(); + g_fOldSudo = len(sVersion) >= 4 \ + and sVersion[0] == '1' \ + and sVersion[1] == '.' \ + and sVersion[2] <= '6' \ + and sVersion[3] == '.'; + + asArgs.insert(0, 'sudo'); + if not g_fOldSudo: + asArgs.insert(1, '-n'); + if fInitialEnv and not g_fOldSudo: + asArgs.insert(1, '-i'); + + # paranoia... + if dKeywordArgs.get('args') is not None: + dKeywordArgs['args'] = asArgs; + else: + aPositionalArgs = (asArgs,) + aPositionalArgs[1:]; + return None; + + +def sudoProcessStart(*aPositionalArgs, **dKeywordArgs): + """ + sudo (or similar) + subprocess.Popen, + returning the process object on success. + """ + _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs); + _sudoFixArguments(aPositionalArgs, dKeywordArgs); + return processStart(*aPositionalArgs, **dKeywordArgs); + +def sudoProcessCall(*aPositionalArgs, **dKeywordArgs): + """ + sudo (or similar) + subprocess.call + """ + _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs); + _sudoFixArguments(aPositionalArgs, dKeywordArgs); + return processCall(*aPositionalArgs, **dKeywordArgs); + +def sudoProcessOutputChecked(*aPositionalArgs, **dKeywordArgs): + """ + sudo (or similar) + subprocess.check_output. + """ + _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs); + _sudoFixArguments(aPositionalArgs, dKeywordArgs); + return processOutputChecked(*aPositionalArgs, **dKeywordArgs); + +def sudoProcessOutputCheckedNoI(*aPositionalArgs, **dKeywordArgs): + """ + sudo (or similar) + subprocess.check_output, except '-i' isn't used. + """ + _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs); + _sudoFixArguments(aPositionalArgs, dKeywordArgs, False); + return processOutputChecked(*aPositionalArgs, **dKeywordArgs); + +def sudoProcessPopen(*aPositionalArgs, **dKeywordArgs): + """ + sudo (or similar) + processPopenSafe. + """ + _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs); + _sudoFixArguments(aPositionalArgs, dKeywordArgs); + return processPopenSafe(*aPositionalArgs, **dKeywordArgs); + + +def whichProgram(sName, sPath = None): + """ + Works similar to the 'which' utility on unix. + + Returns path to the given program if found. + Returns None if not found. + """ + sHost = getHostOs(); + sSep = ';' if sHost in [ 'win', 'os2' ] else ':'; + + if sPath is None: + if sHost == 'win': + sPath = os.environ.get('Path', None); + else: + sPath = os.environ.get('PATH', None); + if sPath is None: + return None; + + for sDir in sPath.split(sSep): + if sDir.strip() != '': + sTest = os.path.abspath(os.path.join(sDir, sName)); + else: + sTest = os.path.abspath(sName); + if os.path.exists(sTest): + return sTest; + + return None; + +# +# Generic process stuff. +# + +def processInterrupt(uPid): + """ + Sends a SIGINT or equivalent to interrupt the specified process. + Returns True on success, False on failure. + + On Windows hosts this may not work unless the process happens to be a + process group leader. + """ + if sys.platform == 'win32': + try: + win32console.GenerateConsoleCtrlEvent(win32con.CTRL_BREAK_EVENT, # pylint: disable=no-member,c-extension-no-member + uPid); + fRc = True; + except: + fRc = False; + else: + try: + os.kill(uPid, signal.SIGINT); + fRc = True; + except: + fRc = False; + return fRc; + +def sendUserSignal1(uPid): + """ + Sends a SIGUSR1 or equivalent to nudge the process into shutting down + (VBoxSVC) or something. + Returns True on success, False on failure or if not supported (win). + + On Windows hosts this may not work unless the process happens to be a + process group leader. + """ + if sys.platform == 'win32': + fRc = False; + else: + try: + os.kill(uPid, signal.SIGUSR1); # pylint: disable=no-member + fRc = True; + except: + fRc = False; + return fRc; + +def processTerminate(uPid): + """ + Terminates the process in a nice manner (SIGTERM or equivalent). + Returns True on success, False on failure. + """ + fRc = False; + if sys.platform == 'win32': + try: + hProcess = win32api.OpenProcess(win32con.PROCESS_TERMINATE, # pylint: disable=no-member,c-extension-no-member + False, uPid); + except: + pass; + else: + try: + win32process.TerminateProcess(hProcess, # pylint: disable=no-member,c-extension-no-member + 0x40010004); # DBG_TERMINATE_PROCESS + fRc = True; + except: + pass; + hProcess.Close(); #win32api.CloseHandle(hProcess) + else: + try: + os.kill(uPid, signal.SIGTERM); + fRc = True; + except: + pass; + return fRc; + +def processKill(uPid): + """ + Terminates the process with extreme prejudice (SIGKILL). + Returns True on success, False on failure. + """ + if sys.platform == 'win32': + fRc = processTerminate(uPid); + else: + try: + os.kill(uPid, signal.SIGKILL); # pylint: disable=no-member + fRc = True; + except: + fRc = False; + return fRc; + +def processKillWithNameCheck(uPid, sName): + """ + Like processKill(), but checks if the process name matches before killing + it. This is intended for killing using potentially stale pid values. + + Returns True on success, False on failure. + """ + + if processCheckPidAndName(uPid, sName) is not True: + return False; + return processKill(uPid); + + +def processExists(uPid): + """ + Checks if the specified process exits. + This will only work if we can signal/open the process. + + Returns True if it positively exists, False otherwise. + """ + sHostOs = getHostOs(); + if sHostOs == 'win': + fRc = False; + # We try open the process for waiting since this is generally only forbidden in a very few cases. + try: + hProcess = win32api.OpenProcess(win32con.SYNCHRONIZE, # pylint: disable=no-member,c-extension-no-member + False, uPid); + except pywintypes.error as oXcpt: # pylint: disable=no-member + if oXcpt.winerror == winerror.ERROR_ACCESS_DENIED: + fRc = True; + except: + pass; + else: + hProcess.Close(); + fRc = True; + else: + fRc = False; + try: + os.kill(uPid, 0); + fRc = True; + except OSError as oXcpt: + if oXcpt.errno == errno.EPERM: + fRc = True; + except: + pass; + return fRc; + +def processCheckPidAndName(uPid, sName): + """ + Checks if a process PID and NAME matches. + """ + fRc = processExists(uPid); + if fRc is not True: + return False; + + if sys.platform == 'win32': + try: + from win32com.client import GetObject; # pylint: disable=import-error + oWmi = GetObject('winmgmts:'); + aoProcesses = oWmi.InstancesOf('Win32_Process'); + for oProcess in aoProcesses: + if long(oProcess.Properties_("ProcessId").Value) == uPid: + sCurName = oProcess.Properties_("Name").Value; + #reporter.log2('uPid=%s sName=%s sCurName=%s' % (uPid, sName, sCurName)); + sName = sName.lower(); + sCurName = sCurName.lower(); + if os.path.basename(sName) == sName: + sCurName = os.path.basename(sCurName); + + if sCurName == sName \ + or sCurName + '.exe' == sName \ + or sCurName == sName + '.exe': + fRc = True; + break; + except: + #reporter.logXcpt('uPid=%s sName=%s' % (uPid, sName)); + pass; + else: + if sys.platform in ('linux2', 'linux', 'linux3', 'linux4', 'linux5', 'linux6'): + asPsCmd = ['/bin/ps', '-p', '%u' % (uPid,), '-o', 'fname=']; + elif sys.platform in ('sunos5',): + asPsCmd = ['/usr/bin/ps', '-p', '%u' % (uPid,), '-o', 'fname=']; + elif sys.platform in ('darwin',): + asPsCmd = ['/bin/ps', '-p', '%u' % (uPid,), '-o', 'ucomm=']; + else: + asPsCmd = None; + + if asPsCmd is not None: + try: + oPs = subprocess.Popen(asPsCmd, stdout=subprocess.PIPE); # pylint: disable=consider-using-with + sCurName = oPs.communicate()[0]; + iExitCode = oPs.wait(); + except: + #reporter.logXcpt(); + return False; + + # ps fails with non-zero exit code if the pid wasn't found. + if iExitCode != 0: + return False; + if sCurName is None: + return False; + sCurName = sCurName.strip(); + if not sCurName: + return False; + + if os.path.basename(sName) == sName: + sCurName = os.path.basename(sCurName); + elif os.path.basename(sCurName) == sCurName: + sName = os.path.basename(sName); + + if sCurName != sName: + return False; + + fRc = True; + return fRc; + +def processGetInfo(uPid, fSudo = False): + """ + Tries to acquire state information of the given process. + + Returns a string with the information on success or None on failure or + if the host is not supported. + + Note that the format of the information is host system dependent and will + likely differ much between different hosts. + """ + fRc = processExists(uPid); + if fRc is not True: + return None; + + sHostOs = getHostOs(); + if sHostOs in [ 'linux',]: + sGdb = '/usr/bin/gdb'; + if not os.path.isfile(sGdb): sGdb = '/usr/local/bin/gdb'; + if not os.path.isfile(sGdb): sGdb = 'gdb'; + aasCmd = [ + [ sGdb, '-batch', + '-ex', 'set pagination off', + '-ex', 'thread apply all bt', + '-ex', 'info proc mapping', + '-ex', 'info sharedlibrary', + '-p', '%u' % (uPid,), ], + ]; + elif sHostOs == 'darwin': + # LLDB doesn't work in batch mode when attaching to a process, at least + # with macOS Sierra (10.12). GDB might not be installed. Use the sample + # tool instead with a 1 second duration and 1000ms sampling interval to + # get one stack trace. For the process mappings use vmmap. + aasCmd = [ + [ '/usr/bin/sample', '-mayDie', '%u' % (uPid,), '1', '1000', ], + [ '/usr/bin/vmmap', '%u' % (uPid,), ], + ]; + elif sHostOs == 'solaris': + aasCmd = [ + [ '/usr/bin/pstack', '%u' % (uPid,), ], + [ '/usr/bin/pmap', '%u' % (uPid,), ], + ]; + else: + aasCmd = []; + + sInfo = ''; + for asCmd in aasCmd: + try: + if fSudo: + sThisInfo = sudoProcessOutputChecked(asCmd); + else: + sThisInfo = processOutputChecked(asCmd); + if sThisInfo is not None: + sInfo += sThisInfo; + except: + pass; + if not sInfo: + sInfo = None; + + return sInfo; + + +class ProcessInfo(object): + """Process info.""" + def __init__(self, iPid): + self.iPid = iPid; + self.iParentPid = None; + self.sImage = None; + self.sName = None; + self.asArgs = None; + self.sCwd = None; + self.iGid = None; + self.iUid = None; + self.iProcGroup = None; + self.iSessionId = None; + + def loadAll(self): + """Load all the info.""" + sOs = getHostOs(); + if sOs == 'linux': + sProc = '/proc/%s/' % (self.iPid,); + if self.sImage is None: self.sImage = noxcptReadLink(sProc + 'exe', None); + if self.sImage is None: + self.sImage = noxcptReadFile(sProc + 'comm', None); + if self.sImage: self.sImage = self.sImage.strip(); + if self.sCwd is None: self.sCwd = noxcptReadLink(sProc + 'cwd', None); + if self.asArgs is None: self.asArgs = noxcptReadFile(sProc + 'cmdline', '').split('\x00'); + #elif sOs == 'solaris': - doesn't work for root processes, suid proces, and other stuff. + # sProc = '/proc/%s/' % (self.iPid,); + # if self.sImage is None: self.sImage = noxcptReadLink(sProc + 'path/a.out', None); + # if self.sCwd is None: self.sCwd = noxcptReadLink(sProc + 'path/cwd', None); + else: + pass; + if self.sName is None and self.sImage is not None: + self.sName = self.sImage; + + def windowsGrabProcessInfo(self, oProcess): + """Windows specific loadAll.""" + try: self.sName = oProcess.Properties_("Name").Value; + except: pass; + try: self.sImage = oProcess.Properties_("ExecutablePath").Value; + except: pass; + try: self.asArgs = [oProcess.Properties_("CommandLine").Value]; ## @todo split it. + except: pass; + try: self.iParentPid = oProcess.Properties_("ParentProcessId").Value; + except: pass; + try: self.iSessionId = oProcess.Properties_("SessionId").Value; + except: pass; + if self.sName is None and self.sImage is not None: + self.sName = self.sImage; + + def getBaseImageName(self): + """ + Gets the base image name if available, use the process name if not available. + Returns image/process base name or None. + """ + sRet = self.sImage if self.sName is None else self.sName; + if sRet is None: + self.loadAll(); + sRet = self.sImage if self.sName is None else self.sName; + if sRet is None: + if not self.asArgs: + return None; + sRet = self.asArgs[0]; + if not sRet: + return None; + return os.path.basename(sRet); + + def getBaseImageNameNoExeSuff(self): + """ + Same as getBaseImageName, except any '.exe' or similar suffix is stripped. + """ + sRet = self.getBaseImageName(); + if sRet is not None and len(sRet) > 4 and sRet[-4] == '.': + if (sRet[-4:]).lower() in [ '.exe', '.com', '.msc', '.vbs', '.cmd', '.bat' ]: + sRet = sRet[:-4]; + return sRet; + + +def processListAll(): + """ + Return a list of ProcessInfo objects for all the processes in the system + that the current user can see. + """ + asProcesses = []; + + sOs = getHostOs(); + if sOs == 'win': + from win32com.client import GetObject; # pylint: disable=import-error + oWmi = GetObject('winmgmts:'); + aoProcesses = oWmi.InstancesOf('Win32_Process'); + for oProcess in aoProcesses: + try: + iPid = int(oProcess.Properties_("ProcessId").Value); + except: + continue; + oMyInfo = ProcessInfo(iPid); + oMyInfo.windowsGrabProcessInfo(oProcess); + asProcesses.append(oMyInfo); + return asProcesses; + + if sOs in [ 'linux', ]: # Not solaris, ps gets more info than /proc/. + try: + asDirs = os.listdir('/proc'); + except: + asDirs = []; + for sDir in asDirs: + if sDir.isdigit(): + asProcesses.append(ProcessInfo(int(sDir),)); + return asProcesses; + + # + # The other OSes parses the output from the 'ps' utility. + # + asPsCmd = [ + '/bin/ps', # 0 + '-A', # 1 + '-o', 'pid=', # 2,3 + '-o', 'ppid=', # 4,5 + '-o', 'pgid=', # 6,7 + '-o', 'sid=', # 8,9 + '-o', 'uid=', # 10,11 + '-o', 'gid=', # 12,13 + '-o', 'comm=' # 14,15 + ]; + + if sOs == 'darwin': + assert asPsCmd[9] == 'sid='; + asPsCmd[9] = 'sess='; + elif sOs == 'solaris': + asPsCmd[0] = '/usr/bin/ps'; + + try: + sRaw = processOutputChecked(asPsCmd); + except: + return asProcesses; + + for sLine in sRaw.split('\n'): + sLine = sLine.lstrip(); + if len(sLine) < 7 or not sLine[0].isdigit(): + continue; + + iField = 0; + off = 0; + aoFields = [None, None, None, None, None, None, None]; + while iField < 7: + # Eat whitespace. + while off < len(sLine) and (sLine[off] == ' ' or sLine[off] == '\t'): + off += 1; + + # Final field / EOL. + if iField == 6: + aoFields[6] = sLine[off:]; + break; + if off >= len(sLine): + break; + + # Generic field parsing. + offStart = off; + off += 1; + while off < len(sLine) and sLine[off] != ' ' and sLine[off] != '\t': + off += 1; + try: + if iField != 3: + aoFields[iField] = int(sLine[offStart:off]); + else: + aoFields[iField] = long(sLine[offStart:off], 16); # sess is a hex address. + except: + pass; + iField += 1; + + if aoFields[0] is not None: + oMyInfo = ProcessInfo(aoFields[0]); + oMyInfo.iParentPid = aoFields[1]; + oMyInfo.iProcGroup = aoFields[2]; + oMyInfo.iSessionId = aoFields[3]; + oMyInfo.iUid = aoFields[4]; + oMyInfo.iGid = aoFields[5]; + oMyInfo.sName = aoFields[6]; + asProcesses.append(oMyInfo); + + return asProcesses; + + +def processCollectCrashInfo(uPid, fnLog, fnCrashFile): + """ + Looks for information regarding the demise of the given process. + """ + sOs = getHostOs(); + if sOs == 'darwin': + # + # On darwin we look for crash and diagnostic reports. + # + asLogDirs = [ + u'/Library/Logs/DiagnosticReports/', + u'/Library/Logs/CrashReporter/', + u'~/Library/Logs/DiagnosticReports/', + u'~/Library/Logs/CrashReporter/', + ]; + for sDir in asLogDirs: + sDir = os.path.expanduser(sDir); + if not os.path.isdir(sDir): + continue; + try: + asDirEntries = os.listdir(sDir); + except: + continue; + for sEntry in asDirEntries: + # Only interested in .crash files. + _, sSuff = os.path.splitext(sEntry); + if sSuff != '.crash': + continue; + + # The pid can be found at the end of the first line. + sFull = os.path.join(sDir, sEntry); + try: + with open(sFull, 'r') as oFile: # pylint: disable=unspecified-encoding + sFirstLine = oFile.readline(); + except: + continue; + if len(sFirstLine) <= 4 or sFirstLine[-2] != ']': + continue; + offPid = len(sFirstLine) - 3; + while offPid > 1 and sFirstLine[offPid - 1].isdigit(): + offPid -= 1; + try: uReportPid = int(sFirstLine[offPid:-2]); + except: continue; + + # Does the pid we found match? + if uReportPid == uPid: + fnLog('Found crash report for %u: %s' % (uPid, sFull,)); + fnCrashFile(sFull, False); + elif sOs == 'win': + # + # Getting WER reports would be great, however we have trouble match the + # PID to those as they seems not to mention it in the brief reports. + # Instead we'll just look for crash dumps in C:\CrashDumps (our custom + # location - see the windows readme for the testbox script) and what + # the MSDN article lists for now. + # + # It's been observed on Windows server 2012 that the dump files takes + # the form: <processimage>.<decimal-pid>.dmp + # + asDmpDirs = [ + u'%SystemDrive%/CrashDumps/', # Testboxes. + u'%LOCALAPPDATA%/CrashDumps/', # MSDN example. + u'%WINDIR%/ServiceProfiles/LocalServices/', # Local and network service. + u'%WINDIR%/ServiceProfiles/NetworkSerices/', + u'%WINDIR%/ServiceProfiles/', + u'%WINDIR%/System32/Config/SystemProfile/', # System services. + ]; + sMatchSuffix = '.%u.dmp' % (uPid,); + + for sDir in asDmpDirs: + sDir = os.path.expandvars(sDir); + if not os.path.isdir(sDir): + continue; + try: + asDirEntries = os.listdir(sDir); + except: + continue; + for sEntry in asDirEntries: + if sEntry.endswith(sMatchSuffix): + sFull = os.path.join(sDir, sEntry); + fnLog('Found crash dump for %u: %s' % (uPid, sFull,)); + fnCrashFile(sFull, True); + elif sOs == 'solaris': + asDmpDirs = []; + try: + sScratchPath = os.environ.get('TESTBOX_PATH_SCRATCH', None); + asDmpDirs.extend([ sScratchPath ]); + except: + pass; + # Some other useful locations as fallback. + asDmpDirs.extend([ + u'/var/cores/', + u'/var/core/', + ]); + # + # Solaris by default creates a core file in the directory of the crashing process with the name 'core'. + # + # As we need to distinguish the core files correlating to their PIDs and have a persistent storage location, + # the host needs to be tweaked via: + # + # ```coreadm -g /path/to/cores/core.%f.%p``` + # + sMatchSuffix = '.%u.core' % (uPid,); + for sDir in asDmpDirs: + sDir = os.path.expandvars(sDir); + if not os.path.isdir(sDir): + continue; + try: + asDirEntries = os.listdir(sDir); + except: + continue; + for sEntry in asDirEntries: + fnLog('Entry: %s' % (os.path.join(sDir, sEntry))); + if sEntry.endswith(sMatchSuffix): + sFull = os.path.join(sDir, sEntry); + fnLog('Found crash dump for %u: %s' % (uPid, sFull,)); + fnCrashFile(sFull, True); + else: + pass; ## TODO + return None; + + +# +# Time. +# + +# +# The following test case shows how time.time() only have ~ms resolution +# on Windows (tested W10) and why it therefore makes sense to try use +# performance counters. +# +# Note! We cannot use time.clock() as the timestamp must be portable across +# processes. See timeout testcase problem on win hosts (no logs). +# Also, time.clock() was axed in python 3.8 (https://bugs.python.org/issue31803). +# +#import sys; +#import time; +#from common import utils; +# +#atSeries = []; +#for i in xrange(1,160): +# if i == 159: time.sleep(10); +# atSeries.append((utils.timestampNano(), long(time.clock() * 1000000000), long(time.time() * 1000000000))); +# +#tPrev = atSeries[0] +#for tCur in atSeries: +# print 't1=%+22u, %u' % (tCur[0], tCur[0] - tPrev[0]); +# print 't2=%+22u, %u' % (tCur[1], tCur[1] - tPrev[1]); +# print 't3=%+22u, %u' % (tCur[2], tCur[2] - tPrev[2]); +# print ''; +# tPrev = tCur +# +#print 't1=%u' % (atSeries[-1][0] - atSeries[0][0]); +#print 't2=%u' % (atSeries[-1][1] - atSeries[0][1]); +#print 't3=%u' % (atSeries[-1][2] - atSeries[0][2]); + +g_fWinUseWinPerfCounter = sys.platform == 'win32'; +g_fpWinPerfCounterFreq = None; +g_oFuncwinQueryPerformanceCounter = None; + +def _winInitPerfCounter(): + """ Initializes the use of performance counters. """ + global g_fWinUseWinPerfCounter, g_fpWinPerfCounterFreq, g_oFuncwinQueryPerformanceCounter + + uFrequency = ctypes.c_ulonglong(0); + if ctypes.windll.kernel32.QueryPerformanceFrequency(ctypes.byref(uFrequency)): + if uFrequency.value >= 1000: + #print 'uFrequency = %s' % (uFrequency,); + #print 'type(uFrequency) = %s' % (type(uFrequency),); + g_fpWinPerfCounterFreq = float(uFrequency.value); + + # Check that querying the counter works too. + global g_oFuncwinQueryPerformanceCounter + g_oFuncwinQueryPerformanceCounter = ctypes.windll.kernel32.QueryPerformanceCounter; + uCurValue = ctypes.c_ulonglong(0); + if g_oFuncwinQueryPerformanceCounter(ctypes.byref(uCurValue)): + if uCurValue.value > 0: + return True; + g_fWinUseWinPerfCounter = False; + return False; + +def _winFloatTime(): + """ Gets floating point time on windows. """ + if g_fpWinPerfCounterFreq is not None or _winInitPerfCounter(): + uCurValue = ctypes.c_ulonglong(0); + if g_oFuncwinQueryPerformanceCounter(ctypes.byref(uCurValue)): + return float(uCurValue.value) / g_fpWinPerfCounterFreq; + return time.time(); + +def timestampNano(): + """ + Gets a nanosecond timestamp. + """ + if g_fWinUseWinPerfCounter is True: + return long(_winFloatTime() * 1000000000); + return long(time.time() * 1000000000); + +def timestampMilli(): + """ + Gets a millisecond timestamp. + """ + if g_fWinUseWinPerfCounter is True: + return long(_winFloatTime() * 1000); + return long(time.time() * 1000); + +def timestampSecond(): + """ + Gets a second timestamp. + """ + if g_fWinUseWinPerfCounter is True: + return long(_winFloatTime()); + return long(time.time()); + +def secondsSinceUnixEpoch(): + """ + Returns unix time, floating point second count since 1970-01-01T00:00:00Z + """ + ## ASSUMES This returns unix epoch time on all systems we care about... + return time.time(); + +def getTimePrefix(): + """ + Returns a timestamp prefix, typically used for logging. UTC. + """ + try: + oNow = datetime.datetime.utcnow(); + sTs = '%02u:%02u:%02u.%06u' % (oNow.hour, oNow.minute, oNow.second, oNow.microsecond); + except: + sTs = 'getTimePrefix-exception'; + return sTs; + +def getTimePrefixAndIsoTimestamp(): + """ + Returns current UTC as log prefix and iso timestamp. + """ + try: + oNow = datetime.datetime.utcnow(); + sTsPrf = '%02u:%02u:%02u.%06u' % (oNow.hour, oNow.minute, oNow.second, oNow.microsecond); + sTsIso = formatIsoTimestamp(oNow); + except: + sTsPrf = sTsIso = 'getTimePrefix-exception'; + return (sTsPrf, sTsIso); + +class UtcTzInfo(datetime.tzinfo): + """UTC TZ Info Class""" + def utcoffset(self, _): + return datetime.timedelta(0); + def tzname(self, _): + return "UTC"; + def dst(self, _): + return datetime.timedelta(0); + +class GenTzInfo(datetime.tzinfo): + """Generic TZ Info Class""" + def __init__(self, offInMin): + datetime.tzinfo.__init__(self); + self.offInMin = offInMin; + def utcoffset(self, _): + return datetime.timedelta(minutes = self.offInMin); + def tzname(self, _): + if self.offInMin >= 0: + return "+%02d%02d" % (self.offInMin // 60, self.offInMin % 60); + return "-%02d%02d" % (-self.offInMin // 60, -self.offInMin % 60); + def dst(self, _): + return datetime.timedelta(0); + +def formatIsoTimestamp(oNow): + """Formats the datetime object as an ISO timestamp.""" + assert oNow.tzinfo is None or isinstance(oNow.tzinfo, UtcTzInfo); + sTs = '%s.%09uZ' % (oNow.strftime('%Y-%m-%dT%H:%M:%S'), oNow.microsecond * 1000); + return sTs; + +def getIsoTimestamp(): + """Returns the current UTC timestamp as a string.""" + return formatIsoTimestamp(datetime.datetime.utcnow()); + +def formatShortIsoTimestamp(oNow): + """Formats the datetime object as an ISO timestamp, but w/o microseconds.""" + assert oNow.tzinfo is None or isinstance(oNow.tzinfo, UtcTzInfo); + return oNow.strftime('%Y-%m-%dT%H:%M:%SZ'); + +def getShortIsoTimestamp(): + """Returns the current UTC timestamp as a string, but w/o microseconds.""" + return formatShortIsoTimestamp(datetime.datetime.utcnow()); + +def convertDateTimeToZulu(oDateTime): + """ Converts oDateTime to zulu time if it has timezone info. """ + if oDateTime.tzinfo is not None: + oDateTime = oDateTime.astimezone(UtcTzInfo()); + else: + oDateTime = oDateTime.replace(tzinfo = UtcTzInfo()); + return oDateTime; + +def parseIsoTimestamp(sTs): + """ + Parses a typical ISO timestamp, returing a datetime object, reasonably + forgiving, but will throw weird indexing/conversion errors if the input + is malformed. + """ + # YYYY-MM-DD + iYear = int(sTs[0:4]); + assert(sTs[4] == '-'); + iMonth = int(sTs[5:7]); + assert(sTs[7] == '-'); + iDay = int(sTs[8:10]); + + # Skip separator + sTime = sTs[10:]; + while sTime[0] in 'Tt \t\n\r': + sTime = sTime[1:]; + + # HH:MM[:SS] + iHour = int(sTime[0:2]); + assert(sTime[2] == ':'); + iMin = int(sTime[3:5]); + if sTime[5] == ':': + iSec = int(sTime[6:8]); + + # Fraction? + offTime = 8; + iMicroseconds = 0; + if offTime < len(sTime) and sTime[offTime] in '.,': + offTime += 1; + cchFraction = 0; + while offTime + cchFraction < len(sTime) and sTime[offTime + cchFraction] in '0123456789': + cchFraction += 1; + if cchFraction > 0: + iMicroseconds = int(sTime[offTime : (offTime + cchFraction)]); + offTime += cchFraction; + while cchFraction < 6: + iMicroseconds *= 10; + cchFraction += 1; + while cchFraction > 6: + iMicroseconds = iMicroseconds // 10; + cchFraction -= 1; + + else: + iSec = 0; + iMicroseconds = 0; + offTime = 5; + + # Naive? + if offTime >= len(sTime): + return datetime.datetime(iYear, iMonth, iDay, iHour, iMin, iSec, iMicroseconds); + + # Zulu? + if offTime >= len(sTime) or sTime[offTime] in 'Zz': + return datetime.datetime(iYear, iMonth, iDay, iHour, iMin, iSec, iMicroseconds, tzinfo = UtcTzInfo()); + + # Some kind of offset afterwards, and strptime is useless. sigh. + if sTime[offTime] in '+-': + chSign = sTime[offTime]; + offTime += 1; + cMinTz = int(sTime[offTime : (offTime + 2)]) * 60; + offTime += 2; + if offTime < len(sTime) and sTime[offTime] in ':': + offTime += 1; + if offTime + 2 <= len(sTime): + cMinTz += int(sTime[offTime : (offTime + 2)]); + offTime += 2; + assert offTime == len(sTime); + if chSign == '-': + cMinTz = -cMinTz; + return datetime.datetime(iYear, iMonth, iDay, iHour, iMin, iSec, iMicroseconds, tzinfo = GenTzInfo(cMinTz)); + assert False, sTs; + return datetime.datetime(iYear, iMonth, iDay, iHour, iMin, iSec, iMicroseconds); + +def normalizeIsoTimestampToZulu(sTs): + """ + Takes a iso timestamp string and normalizes it (basically parseIsoTimestamp + + convertDateTimeToZulu + formatIsoTimestamp). + Returns ISO tiemstamp string. + """ + return formatIsoTimestamp(convertDateTimeToZulu(parseIsoTimestamp(sTs))); + +def getLocalHourOfWeek(): + """ Local hour of week (0 based). """ + oNow = datetime.datetime.now(); + return (oNow.isoweekday() - 1) * 24 + oNow.hour; + + +def formatIntervalSeconds(cSeconds): + """ Format a seconds interval into a nice 01h 00m 22s string """ + # Two simple special cases. + if cSeconds < 60: + return '%ss' % (cSeconds,); + if cSeconds < 3600: + cMins = cSeconds // 60; + cSecs = cSeconds % 60; + if cSecs == 0: + return '%sm' % (cMins,); + return '%sm %ss' % (cMins, cSecs,); + + # Generic and a bit slower. + cDays = cSeconds // 86400; + cSeconds %= 86400; + cHours = cSeconds // 3600; + cSeconds %= 3600; + cMins = cSeconds // 60; + cSecs = cSeconds % 60; + sRet = ''; + if cDays > 0: + sRet = '%sd ' % (cDays,); + if cHours > 0: + sRet += '%sh ' % (cHours,); + if cMins > 0: + sRet += '%sm ' % (cMins,); + if cSecs > 0: + sRet += '%ss ' % (cSecs,); + assert sRet; assert sRet[-1] == ' '; + return sRet[:-1]; + +def formatIntervalSeconds2(oSeconds): + """ + Flexible input version of formatIntervalSeconds for use in WUI forms where + data is usually already string form. + """ + if isinstance(oSeconds, (int, long)): + return formatIntervalSeconds(oSeconds); + if not isString(oSeconds): + try: + lSeconds = long(oSeconds); + except: + pass; + else: + if lSeconds >= 0: + return formatIntervalSeconds2(lSeconds); + return oSeconds; + +def parseIntervalSeconds(sString): + """ + Reverse of formatIntervalSeconds. + + Returns (cSeconds, sError), where sError is None on success. + """ + + # We might given non-strings, just return them without any fuss. + if not isString(sString): + if isinstance(sString, (int, long)) or sString is None: + return (sString, None); + ## @todo time/date objects? + return (int(sString), None); + + # Strip it and make sure it's not empty. + sString = sString.strip(); + if not sString: + return (0, 'Empty interval string.'); + + # + # Split up the input into a list of 'valueN, unitN, ...'. + # + # Don't want to spend too much time trying to make re.split do exactly what + # I need here, so please forgive the extra pass I'm making here. + # + asRawParts = re.split(r'\s*([0-9]+)\s*([^0-9,;]*)[\s,;]*', sString); + asParts = []; + for sPart in asRawParts: + sPart = sPart.strip(); + if sPart: + asParts.append(sPart); + if not asParts: + return (0, 'Empty interval string or something?'); + + # + # Process them one or two at the time. + # + cSeconds = 0; + asErrors = []; + i = 0; + while i < len(asParts): + sNumber = asParts[i]; + i += 1; + if sNumber.isdigit(): + iNumber = int(sNumber); + + sUnit = 's'; + if i < len(asParts) and not asParts[i].isdigit(): + sUnit = asParts[i]; + i += 1; + + sUnitLower = sUnit.lower(); + if sUnitLower in [ 's', 'se', 'sec', 'second', 'seconds' ]: + pass; + elif sUnitLower in [ 'm', 'mi', 'min', 'minute', 'minutes' ]: + iNumber *= 60; + elif sUnitLower in [ 'h', 'ho', 'hou', 'hour', 'hours' ]: + iNumber *= 3600; + elif sUnitLower in [ 'd', 'da', 'day', 'days' ]: + iNumber *= 86400; + elif sUnitLower in [ 'w', 'week', 'weeks' ]: + iNumber *= 7 * 86400; + else: + asErrors.append('Unknown unit "%s".' % (sUnit,)); + cSeconds += iNumber; + else: + asErrors.append('Bad number "%s".' % (sNumber,)); + return (cSeconds, None if not asErrors else ' '.join(asErrors)); + +def formatIntervalHours(cHours): + """ Format a hours interval into a nice 1w 2d 1h string. """ + # Simple special cases. + if cHours < 24: + return '%sh' % (cHours,); + + # Generic and a bit slower. + cWeeks = cHours / (7 * 24); + cHours %= 7 * 24; + cDays = cHours / 24; + cHours %= 24; + sRet = ''; + if cWeeks > 0: + sRet = '%sw ' % (cWeeks,); + if cDays > 0: + sRet = '%sd ' % (cDays,); + if cHours > 0: + sRet += '%sh ' % (cHours,); + assert sRet; assert sRet[-1] == ' '; + return sRet[:-1]; + +def parseIntervalHours(sString): + """ + Reverse of formatIntervalHours. + + Returns (cHours, sError), where sError is None on success. + """ + + # We might given non-strings, just return them without any fuss. + if not isString(sString): + if isinstance(sString, (int, long)) or sString is None: + return (sString, None); + ## @todo time/date objects? + return (int(sString), None); + + # Strip it and make sure it's not empty. + sString = sString.strip(); + if not sString: + return (0, 'Empty interval string.'); + + # + # Split up the input into a list of 'valueN, unitN, ...'. + # + # Don't want to spend too much time trying to make re.split do exactly what + # I need here, so please forgive the extra pass I'm making here. + # + asRawParts = re.split(r'\s*([0-9]+)\s*([^0-9,;]*)[\s,;]*', sString); + asParts = []; + for sPart in asRawParts: + sPart = sPart.strip(); + if sPart: + asParts.append(sPart); + if not asParts: + return (0, 'Empty interval string or something?'); + + # + # Process them one or two at the time. + # + cHours = 0; + asErrors = []; + i = 0; + while i < len(asParts): + sNumber = asParts[i]; + i += 1; + if sNumber.isdigit(): + iNumber = int(sNumber); + + sUnit = 'h'; + if i < len(asParts) and not asParts[i].isdigit(): + sUnit = asParts[i]; + i += 1; + + sUnitLower = sUnit.lower(); + if sUnitLower in [ 'h', 'ho', 'hou', 'hour', 'hours' ]: + pass; + elif sUnitLower in [ 'd', 'da', 'day', 'days' ]: + iNumber *= 24; + elif sUnitLower in [ 'w', 'week', 'weeks' ]: + iNumber *= 7 * 24; + else: + asErrors.append('Unknown unit "%s".' % (sUnit,)); + cHours += iNumber; + else: + asErrors.append('Bad number "%s".' % (sNumber,)); + return (cHours, None if not asErrors else ' '.join(asErrors)); + + +# +# Introspection. +# + +def getCallerName(oFrame=None, iFrame=2): + """ + Returns the name of the caller's caller. + """ + if oFrame is None: + try: + raise Exception(); + except: + oFrame = sys.exc_info()[2].tb_frame.f_back; + while iFrame > 1: + if oFrame is not None: + oFrame = oFrame.f_back; + iFrame = iFrame - 1; + if oFrame is not None: + sName = '%s:%u' % (oFrame.f_code.co_name, oFrame.f_lineno); + return sName; + return "unknown"; + + +def getXcptInfo(cFrames = 1): + """ + Gets text detailing the exception. (Good for logging.) + Returns list of info strings. + """ + + # + # Try get exception info. + # + try: + oType, oValue, oTraceback = sys.exc_info(); + except: + oType = oValue = oTraceback = None; + if oType is not None: + + # + # Try format the info + # + asRet = []; + try: + try: + asRet = asRet + traceback.format_exception_only(oType, oValue); + asTraceBack = traceback.format_tb(oTraceback); + if cFrames is not None and cFrames <= 1: + asRet.append(asTraceBack[-1]); + else: + asRet.append('Traceback:') + for iFrame in range(min(cFrames, len(asTraceBack))): + asRet.append(asTraceBack[-iFrame - 1]); + asRet.append('Stack:') + asRet = asRet + traceback.format_stack(oTraceback.tb_frame.f_back, cFrames); + except: + asRet.append('internal-error: Hit exception #2! %s' % (traceback.format_exc(),)); + + if not asRet: + asRet.append('No exception info...'); + except: + asRet.append('internal-error: Hit exception! %s' % (traceback.format_exc(),)); + else: + asRet = ['Couldn\'t find exception traceback.']; + return asRet; + + +def getObjectTypeName(oObject): + """ + Get the type name of the given object. + """ + if oObject is None: + return 'None'; + + # Get the type object. + try: + oType = type(oObject); + except: + return 'type-throws-exception'; + + # Python 2.x only: Handle old-style object wrappers. + if sys.version_info[0] < 3: + try: + from types import InstanceType; # pylint: disable=no-name-in-module + if oType == InstanceType: + oType = oObject.__class__; + except: + pass; + + # Get the name. + try: + return oType.__name__; + except: + return '__type__-throws-exception'; + + +def chmodPlusX(sFile): + """ + Makes the specified file or directory executable. + Returns success indicator, no exceptions. + + Note! Symbolic links are followed and the target will be changed. + """ + try: + oStat = os.stat(sFile); + except: + return False; + try: + os.chmod(sFile, oStat.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH); + except: + return False; + return True; + + +# +# TestSuite stuff. +# + +def isRunningFromCheckout(cScriptDepth = 1): + """ + Checks if we're running from the SVN checkout or not. + """ + + try: + sFile = __file__; + cScriptDepth = 1; + except: + sFile = sys.argv[0]; + + sDir = os.path.abspath(sFile); + while cScriptDepth >= 0: + sDir = os.path.dirname(sDir); + if os.path.exists(os.path.join(sDir, 'Makefile.kmk')) \ + or os.path.exists(os.path.join(sDir, 'Makefile.kup')): + return True; + cScriptDepth -= 1; + + return False; + + +# +# Bourne shell argument fun. +# + + +def argsSplit(sCmdLine): + """ + Given a bourne shell command line invocation, split it up into arguments + assuming IFS is space. + Returns None on syntax error. + """ + ## @todo bourne shell argument parsing! + return sCmdLine.split(' '); + +def argsGetFirst(sCmdLine): + """ + Given a bourne shell command line invocation, get return the first argument + assuming IFS is space. + Returns None on invalid syntax, otherwise the parsed and unescaped argv[0] string. + """ + asArgs = argsSplit(sCmdLine); + if not asArgs: + return None; + + return asArgs[0]; + +# +# String helpers. +# + +def stricmp(sFirst, sSecond): + """ + Compares to strings in an case insensitive fashion. + + Python doesn't seem to have any way of doing the correctly, so this is just + an approximation using lower. + """ + if sFirst == sSecond: + return 0; + sLower1 = sFirst.lower(); + sLower2 = sSecond.lower(); + if sLower1 == sLower2: + return 0; + if sLower1 < sLower2: + return -1; + return 1; + + +def versionCompare(sVer1, sVer2): + """ + Compares to version strings in a fashion similar to RTStrVersionCompare. + """ + + ## @todo implement me!! + + if sVer1 == sVer2: + return 0; + if sVer1 < sVer2: + return -1; + return 1; + + +def formatNumber(lNum, sThousandSep = ' '): + """ + Formats a decimal number with pretty separators. + """ + sNum = str(lNum); + sRet = sNum[-3:]; + off = len(sNum) - 3; + while off > 0: + off -= 3; + sRet = sNum[(off if off >= 0 else 0):(off + 3)] + sThousandSep + sRet; + return sRet; + + +def formatNumberNbsp(lNum): + """ + Formats a decimal number with pretty separators. + """ + sRet = formatNumber(lNum); + return unicode(sRet).replace(' ', u'\u00a0'); + + +def isString(oString): + """ + Checks if the object is a string object, hiding difference between python 2 and 3. + + Returns True if it's a string of some kind. + Returns False if not. + """ + if sys.version_info[0] >= 3: + return isinstance(oString, str); + return isinstance(oString, basestring); # pylint: disable=undefined-variable + + +def hasNonAsciiCharacters(sText): + """ + Returns True is specified string has non-ASCII characters, False if ASCII only. + """ + if isString(sText): + for ch in sText: + if ord(ch) >= 128: + return True; + else: + # Probably byte array or some such thing. + for ch in sText: + if ch >= 128 or ch < 0: + return True; + return False; + + +# +# Unpacking. +# + +def unpackZipFile(sArchive, sDstDir, fnLog, fnError = None, fnFilter = None): + # type: (string, string, (string) -> None, (string) -> None, (string) -> bool) -> list[string] + """ + Worker for unpackFile that deals with ZIP files, same function signature. + """ + import zipfile + if fnError is None: + fnError = fnLog; + + fnLog('Unzipping "%s" to "%s"...' % (sArchive, sDstDir)); + + # Open it. + try: oZipFile = zipfile.ZipFile(sArchive, 'r'); # pylint: disable=consider-using-with + except Exception as oXcpt: + fnError('Error opening "%s" for unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt,)); + return None; + + # Extract all members. + asMembers = []; + try: + for sMember in oZipFile.namelist(): + if fnFilter is None or fnFilter(sMember) is not False: + if sMember.endswith('/'): + os.makedirs(os.path.join(sDstDir, sMember.replace('/', os.path.sep)), 0x1fd); # octal: 0775 (python 3/2) + else: + oZipFile.extract(sMember, sDstDir); + asMembers.append(os.path.join(sDstDir, sMember.replace('/', os.path.sep))); + except Exception as oXcpt: + fnError('Error unpacking "%s" into "%s": %s' % (sArchive, sDstDir, oXcpt)); + asMembers = None; + + # close it. + try: oZipFile.close(); + except Exception as oXcpt: + fnError('Error closing "%s" after unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt)); + asMembers = None; + + return asMembers; + + +## Set if we've replaced tarfile.copyfileobj with __mytarfilecopyfileobj already. +g_fTarCopyFileObjOverriddend = False; + +def __mytarfilecopyfileobj(src, dst, length = None, exception = OSError, bufsize = None): + """ tarfile.copyfileobj with different buffer size (16384 is slow on windows). """ + _ = bufsize; + if length is None: + __myshutilcopyfileobj(src, dst, g_cbGoodBufferSize); + elif length > 0: + cFull, cbRemainder = divmod(length, g_cbGoodBufferSize); + for _ in xrange(cFull): + abBuffer = src.read(g_cbGoodBufferSize); + dst.write(abBuffer); + if len(abBuffer) != g_cbGoodBufferSize: + raise exception('unexpected end of source file'); + if cbRemainder > 0: + abBuffer = src.read(cbRemainder); + dst.write(abBuffer); + if len(abBuffer) != cbRemainder: + raise exception('unexpected end of source file'); + + +def unpackTarFile(sArchive, sDstDir, fnLog, fnError = None, fnFilter = None): + # type: (string, string, (string) -> None, (string) -> None, (string) -> bool) -> list[string] + """ + Worker for unpackFile that deals with tarballs, same function signature. + """ + import shutil; + import tarfile; + if fnError is None: + fnError = fnLog; + + fnLog('Untarring "%s" to "%s"...' % (sArchive, sDstDir)); + + # + # Default buffer sizes of 16384 bytes is causing too many syscalls on Windows. + # 60%+ speedup for python 2.7 and 50%+ speedup for python 3.5, both on windows with PDBs. + # 20%+ speedup for python 2.7 and 15%+ speedup for python 3.5, both on windows skipping PDBs. + # + if True is True: # pylint: disable=comparison-with-itself + __installShUtilHacks(shutil); + global g_fTarCopyFileObjOverriddend; + if g_fTarCopyFileObjOverriddend is False: + g_fTarCopyFileObjOverriddend = True; + #if sys.hexversion < 0x03060000: + tarfile.copyfileobj = __mytarfilecopyfileobj; + + # + # Open it. + # + # Note! We not using 'r:*' because we cannot allow seeking compressed files! + # That's how we got a 13 min unpack time for VBoxAll on windows (hardlinked pdb). + # + try: + if sys.hexversion >= 0x03060000: + oTarFile = tarfile.open(sArchive, 'r|*', # pylint: disable=consider-using-with + bufsize = g_cbGoodBufferSize, copybufsize = g_cbGoodBufferSize); + else: + oTarFile = tarfile.open(sArchive, 'r|*', bufsize = g_cbGoodBufferSize); # pylint: disable=consider-using-with + except Exception as oXcpt: + fnError('Error opening "%s" for unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt,)); + return None; + + # Extract all members. + asMembers = []; + try: + for oTarInfo in oTarFile: + try: + if fnFilter is None or fnFilter(oTarInfo.name) is not False: + if oTarInfo.islnk(): + # Links are trouble, especially on Windows. We must avoid the falling that will end up seeking + # in the compressed tar stream. So, fall back on shutil.copy2 instead. + sLinkFile = os.path.join(sDstDir, oTarInfo.name.rstrip('/').replace('/', os.path.sep)); + sLinkTarget = os.path.join(sDstDir, oTarInfo.linkname.rstrip('/').replace('/', os.path.sep)); + sParentDir = os.path.dirname(sLinkFile); + try: os.unlink(sLinkFile); + except: pass; + if sParentDir and not os.path.exists(sParentDir): + os.makedirs(sParentDir); + try: os.link(sLinkTarget, sLinkFile); + except: shutil.copy2(sLinkTarget, sLinkFile); + else: + if oTarInfo.isdir(): + # Just make sure the user (we) got full access to dirs. Don't bother getting it 100% right. + oTarInfo.mode |= 0x1c0; # (octal: 0700) + oTarFile.extract(oTarInfo, sDstDir); + asMembers.append(os.path.join(sDstDir, oTarInfo.name.replace('/', os.path.sep))); + except Exception as oXcpt: + fnError('Error unpacking "%s" member "%s" into "%s": %s' % (sArchive, oTarInfo.name, sDstDir, oXcpt)); + for sAttr in [ 'name', 'linkname', 'type', 'mode', 'size', 'mtime', 'uid', 'uname', 'gid', 'gname' ]: + fnError('Info: %8s=%s' % (sAttr, getattr(oTarInfo, sAttr),)); + for sFn in [ 'isdir', 'isfile', 'islnk', 'issym' ]: + fnError('Info: %8s=%s' % (sFn, getattr(oTarInfo, sFn)(),)); + asMembers = None; + break; + except Exception as oXcpt: + fnError('Error unpacking "%s" into "%s": %s' % (sArchive, sDstDir, oXcpt)); + asMembers = None; + + # + # Finally, close it. + # + try: oTarFile.close(); + except Exception as oXcpt: + fnError('Error closing "%s" after unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt)); + asMembers = None; + + return asMembers; + + +def unpackFile(sArchive, sDstDir, fnLog, fnError = None, fnFilter = None): + # type: (string, string, (string) -> None, (string) -> None, (string) -> bool) -> list[string] + """ + Unpacks the given file if it has a know archive extension, otherwise do + nothing. + + fnLog & fnError both take a string parameter. + + fnFilter takes a member name (string) and returns True if it's included + and False if excluded. + + Returns list of the extracted files (full path) on success. + Returns empty list if not a supported archive format. + Returns None on failure. Raises no exceptions. + """ + sBaseNameLower = os.path.basename(sArchive).lower(); + + # + # Zip file? + # + if sBaseNameLower.endswith('.zip'): + return unpackZipFile(sArchive, sDstDir, fnLog, fnError, fnFilter); + + # + # Tarball? + # + if sBaseNameLower.endswith('.tar') \ + or sBaseNameLower.endswith('.tar.gz') \ + or sBaseNameLower.endswith('.tgz') \ + or sBaseNameLower.endswith('.tar.bz2'): + return unpackTarFile(sArchive, sDstDir, fnLog, fnError, fnFilter); + + # + # Cannot classify it from the name, so just return that to the caller. + # + fnLog('Not unpacking "%s".' % (sArchive,)); + return []; + + +# +# Misc. +# +def areBytesEqual(oLeft, oRight): + """ + Compares two byte arrays, strings or whatnot. + + returns true / false accordingly. + """ + + # If both are None, consider them equal (bogus?): + if oLeft is None and oRight is None: + return True; + + # If just one is None, they can't match: + if oLeft is None or oRight is None: + return False; + + # If both have the same type, use the compare operator of the class: + if type(oLeft) is type(oRight): + #print('same type: %s' % (oLeft == oRight,)); + return oLeft == oRight; + + # On the offchance that they're both strings, but of different types. + if isString(oLeft) and isString(oRight): + #print('string compare: %s' % (oLeft == oRight,)); + return oLeft == oRight; + + # + # See if byte/buffer stuff that can be compared directory. If not convert + # strings to bytes. + # + # Note! For 2.x, we must convert both sides to the buffer type or the + # comparison may fail despite it working okay in test cases. + # + if sys.version_info[0] >= 3: + if isinstance(oLeft, (bytearray, memoryview, bytes)) and isinstance(oRight, (bytearray, memoryview, bytes)): # pylint: disable=undefined-variable + return oLeft == oRight; + + if isString(oLeft): + try: oLeft = bytes(oLeft, 'utf-8'); + except: pass; + if isString(oRight): + try: oRight = bytes(oRight, 'utf-8'); + except: pass; + else: + if isinstance(oLeft, (bytearray, buffer)) and isinstance(oRight, (bytearray, buffer)): # pylint: disable=undefined-variable + if isinstance(oLeft, bytearray): + oLeft = buffer(oLeft); # pylint: disable=redefined-variable-type,undefined-variable + else: + oRight = buffer(oRight); # pylint: disable=redefined-variable-type,undefined-variable + #print('buf/byte #1 compare: %s (%s vs %s)' % (oLeft == oRight, type(oLeft), type(oRight),)); + return oLeft == oRight; + + if isString(oLeft): + try: oLeft = bytearray(oLeft, 'utf-8'); # pylint: disable=redefined-variable-type + except: pass; + if isString(oRight): + try: oRight = bytearray(oRight, 'utf-8'); # pylint: disable=redefined-variable-type + except: pass; + + # Check if we now have the same type for both: + if type(oLeft) is type(oRight): + #print('same type now: %s' % (oLeft == oRight,)); + return oLeft == oRight; + + # Check if we now have buffer/memoryview vs bytes/bytesarray again. + if sys.version_info[0] >= 3: + if isinstance(oLeft, (bytearray, memoryview, bytes)) and isinstance(oRight, (bytearray, memoryview, bytes)): # pylint: disable=undefined-variable + return oLeft == oRight; + else: + if isinstance(oLeft, (bytearray, buffer)) and isinstance(oRight, (bytearray, buffer)): # pylint: disable=undefined-variable + if isinstance(oLeft, bytearray): + oLeft = buffer(oLeft); # pylint: disable=redefined-variable-type,undefined-variable + else: + oRight = buffer(oRight); # pylint: disable=redefined-variable-type,undefined-variable + #print('buf/byte #2 compare: %s (%s vs %s)' % (oLeft == oRight, type(oLeft), type(oRight),)); + return oLeft == oRight; + + # Do item by item comparison: + if len(oLeft) != len(oRight): + #print('different length: %s vs %s' % (len(oLeft), len(oRight))); + return False; + i = len(oLeft); + while i > 0: + i = i - 1; + + iElmLeft = oLeft[i]; + if not isinstance(iElmLeft, int) and not isinstance(iElmLeft, long): + iElmLeft = ord(iElmLeft); + + iElmRight = oRight[i]; + if not isinstance(iElmRight, int) and not isinstance(iElmRight, long): + iElmRight = ord(iElmRight); + + if iElmLeft != iElmRight: + #print('element %d differs: %x %x' % (i, iElmLeft, iElmRight,)); + return False; + return True; + + +def calcCrc32OfFile(sFile): + """ + Simple helper for calculating the CRC32 of a file. + + Throws stuff if the file cannot be opened or read successfully. + """ + import zlib; + + uCrc32 = 0; + with open(sFile, 'rb') as oFile: # pylint: disable=unspecified-encoding + while True: + oBuf = oFile.read(1024 * 1024); + if not oBuf: + break + uCrc32 = zlib.crc32(oBuf, uCrc32); + + return uCrc32 % 2**32; + + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +# pylint: disable=undefined-variable +class BuildCategoryDataTestCase(unittest.TestCase): + def testIntervalSeconds(self): + self.assertEqual(parseIntervalSeconds(formatIntervalSeconds(3600)), (3600, None)); + self.assertEqual(parseIntervalSeconds(formatIntervalSeconds(1209438593)), (1209438593, None)); + self.assertEqual(parseIntervalSeconds('123'), (123, None)); + self.assertEqual(parseIntervalSeconds(123), (123, None)); + self.assertEqual(parseIntervalSeconds(99999999999), (99999999999, None)); + self.assertEqual(parseIntervalSeconds(''), (0, 'Empty interval string.')); + self.assertEqual(parseIntervalSeconds('1X2'), (3, 'Unknown unit "X".')); + self.assertEqual(parseIntervalSeconds('1 Y3'), (4, 'Unknown unit "Y".')); + self.assertEqual(parseIntervalSeconds('1 Z 4'), (5, 'Unknown unit "Z".')); + self.assertEqual(parseIntervalSeconds('1 hour 2m 5second'), (3725, None)); + self.assertEqual(parseIntervalSeconds('1 hour,2m ; 5second'), (3725, None)); + + def testZuluNormalization(self): + self.assertEqual(normalizeIsoTimestampToZulu('2011-01-02T03:34:25.000000000Z'), '2011-01-02T03:34:25.000000000Z'); + self.assertEqual(normalizeIsoTimestampToZulu('2011-01-02T03:04:25-0030'), '2011-01-02T03:34:25.000000000Z'); + self.assertEqual(normalizeIsoTimestampToZulu('2011-01-02T03:04:25+0030'), '2011-01-02T02:34:25.000000000Z'); + self.assertEqual(normalizeIsoTimestampToZulu('2020-03-20T20:47:39,832312863+01:00'), '2020-03-20T19:47:39.832312000Z'); + self.assertEqual(normalizeIsoTimestampToZulu('2020-03-20T20:47:39,832312863-02:00'), '2020-03-20T22:47:39.832312000Z'); + + def testHasNonAsciiChars(self): + self.assertEqual(hasNonAsciiCharacters(''), False); + self.assertEqual(hasNonAsciiCharacters('asdfgebASDFKJ@#$)(!@#UNASDFKHB*&$%&)@#(!)@(#!(#$&*#$&%*Y@#$IQWN---00;'), False); + self.assertEqual(hasNonAsciiCharacters('\x80 '), True); + self.assertEqual(hasNonAsciiCharacters('\x79 '), False); + self.assertEqual(hasNonAsciiCharacters(u'12039889y!@#$%^&*()0-0asjdkfhoiuyweasdfASDFnvV'), False); + self.assertEqual(hasNonAsciiCharacters(u'\u0079'), False); + self.assertEqual(hasNonAsciiCharacters(u'\u0080'), True); + self.assertEqual(hasNonAsciiCharacters(u'\u0081 \u0100'), True); + self.assertEqual(hasNonAsciiCharacters(b'\x20\x20\x20'), False); + self.assertEqual(hasNonAsciiCharacters(b'\x20\x81\x20'), True); + + def testAreBytesEqual(self): + self.assertEqual(areBytesEqual(None, None), True); + self.assertEqual(areBytesEqual(None, ''), False); + self.assertEqual(areBytesEqual('', ''), True); + self.assertEqual(areBytesEqual('1', '1'), True); + self.assertEqual(areBytesEqual('12345', '1234'), False); + self.assertEqual(areBytesEqual('1234', '1234'), True); + self.assertEqual(areBytesEqual('1234', b'1234'), True); + self.assertEqual(areBytesEqual(b'1234', b'1234'), True); + self.assertEqual(areBytesEqual(b'1234', '1234'), True); + self.assertEqual(areBytesEqual(b'1234', bytearray([0x31,0x32,0x33,0x34])), True); + self.assertEqual(areBytesEqual('1234', bytearray([0x31,0x32,0x33,0x34])), True); + self.assertEqual(areBytesEqual(u'1234', bytearray([0x31,0x32,0x33,0x34])), True); + self.assertEqual(areBytesEqual(bytearray([0x31,0x32,0x33,0x34]), bytearray([0x31,0x32,0x33,0x34])), True); + self.assertEqual(areBytesEqual(bytearray([0x31,0x32,0x33,0x34]), '1224'), False); + self.assertEqual(areBytesEqual(bytearray([0x31,0x32,0x33,0x34]), bytearray([0x31,0x32,0x32,0x34])), False); + if sys.version_info[0] >= 3: + pass; + else: + self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1), + bytearray([0x31,0x32,0x33,0x34])), True); + self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1), + bytearray([0x99,0x32,0x32,0x34])), False); + self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1), + buffer(bytearray([0x31,0x32,0x33,0x34,0x34]), 0, 4)), True); + self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1), + buffer(bytearray([0x99,0x32,0x33,0x34,0x34]), 0, 4)), False); + self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1), b'1234'), True); + self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1), '1234'), True); + self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1), u'1234'), True); + +if __name__ == '__main__': + unittest.main(); + # not reached. diff --git a/src/VBox/ValidationKit/common/webutils.py b/src/VBox/ValidationKit/common/webutils.py new file mode 100755 index 00000000..b462b039 --- /dev/null +++ b/src/VBox/ValidationKit/common/webutils.py @@ -0,0 +1,223 @@ +# -*- coding: utf-8 -*- +# $Id: webutils.py $ + +""" +Common Web Utility Functions. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + +# Standard Python imports. +import os; +import sys; +import unittest; + +# Python 3 hacks: +if sys.version_info[0] < 3: + from urllib2 import quote as urllib_quote; # pylint: disable=import-error,no-name-in-module + from urllib import urlencode as urllib_urlencode; # pylint: disable=import-error,no-name-in-module + from urllib2 import ProxyHandler as urllib_ProxyHandler; # pylint: disable=import-error,no-name-in-module + from urllib2 import build_opener as urllib_build_opener; # pylint: disable=import-error,no-name-in-module +else: + from urllib.parse import quote as urllib_quote; # pylint: disable=import-error,no-name-in-module + from urllib.parse import urlencode as urllib_urlencode; # pylint: disable=import-error,no-name-in-module + from urllib.request import ProxyHandler as urllib_ProxyHandler; # pylint: disable=import-error,no-name-in-module + from urllib.request import build_opener as urllib_build_opener; # pylint: disable=import-error,no-name-in-module + +# Validation Kit imports. +from common import utils; + + +def escapeElem(sText): + """ + Escapes special character to HTML-safe sequences. + """ + sText = sText.replace('&', '&') + sText = sText.replace('<', '<') + return sText.replace('>', '>') + +def escapeAttr(sText): + """ + Escapes special character to HTML-safe sequences. + """ + sText = sText.replace('&', '&') + sText = sText.replace('<', '<') + sText = sText.replace('>', '>') + return sText.replace('"', '"') + +def escapeElemToStr(oObject): + """ + Stringifies the object and hands it to escapeElem. + """ + if utils.isString(oObject): + return escapeElem(oObject); + return escapeElem(str(oObject)); + +def escapeAttrToStr(oObject): + """ + Stringifies the object and hands it to escapeAttr. May return unicode string. + """ + if utils.isString(oObject): + return escapeAttr(oObject); + return escapeAttr(str(oObject)); + +def escapeAttrJavaScriptStringDQ(sText): + """ Escapes a javascript string that is to be emitted between double quotes. """ + if '"' not in sText: + chMin = min(sText); + if ord(chMin) >= 0x20: + return sText; + + sRet = ''; + for ch in sText: + if ch == '"': + sRet += '\\"'; + elif ord(ch) >= 0x20: + sRet += ch; + elif ch == '\n': + sRet += '\\n'; + elif ch == '\r': + sRet += '\\r'; + elif ch == '\t': + sRet += '\\t'; + else: + sRet += '\\x%02x' % (ch,); + return sRet; + +def quoteUrl(sText): + """ + See urllib.quote(). + """ + return urllib_quote(sText); + +def encodeUrlParams(dParams): + """ + See urllib.urlencode(). + """ + return urllib_urlencode(dParams, doseq=True) + +def hasSchema(sUrl): + """ + Checks if the URL has a schema (e.g. http://) or is file/server relative. + Returns True if schema is present, False if not. + """ + iColon = sUrl.find(':'); + if iColon > 0: + sSchema = sUrl[0:iColon]; + if len(sSchema) >= 2 and len(sSchema) < 16 and sSchema.islower() and sSchema.isalpha(): + return True; + return False; + +def getFilename(sUrl): + """ + Extracts the filename from the URL. + """ + ## @TODO This isn't entirely correct. Use the urlparser instead! + sFilename = os.path.basename(sUrl.replace('/', os.path.sep)); + return sFilename; + + +def downloadFile(sUrlFile, sDstFile, sLocalPrefix, fnLog, fnError = None, fNoProxies=True): + """ + Downloads the given file if an URL is given, otherwise assume it's + something on the build share and copy it from there. + + Raises no exceptions, returns log + success indicator instead. + + Note! This method may use proxies configured on the system and the + http_proxy, ftp_proxy, no_proxy environment variables. + + """ + if fnError is None: + fnError = fnLog; + + if sUrlFile.startswith('http://') \ + or sUrlFile.startswith('https://') \ + or sUrlFile.startswith('ftp://'): + # Download the file. + fnLog('Downloading "%s" to "%s"...' % (sUrlFile, sDstFile)); + try: + ## @todo We get 404.html content instead of exceptions here, which is confusing and should be addressed. + if not fNoProxies: + oOpener = urllib_build_opener(); + else: + oOpener = urllib_build_opener(urllib_ProxyHandler(proxies = {} )); + oSrc = oOpener.open(sUrlFile); + oDst = utils.openNoInherit(sDstFile, 'wb'); + oDst.write(oSrc.read()); + oDst.close(); + oSrc.close(); + except Exception as oXcpt: + fnError('Error downloading "%s" to "%s": %s' % (sUrlFile, sDstFile, oXcpt)); + return False; + else: + # Assumes file from the build share. + if sUrlFile.startswith('file:///'): + sSrcPath = sUrlFile[7:]; + elif sUrlFile.startswith('file://'): + sSrcPath = sUrlFile[6:]; + elif os.path.isabs(sUrlFile): + sSrcPath = sUrlFile; + else: + sSrcPath = os.path.join(sLocalPrefix, sUrlFile); + fnLog('Copying "%s" to "%s"...' % (sSrcPath, sDstFile)); + try: + utils.copyFileSimple(sSrcPath, sDstFile); + except Exception as oXcpt: + fnError('Error copying "%s" to "%s": %s' % (sSrcPath, sDstFile, oXcpt)); + return False; + + return True; + + + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +class CommonUtilsTestCase(unittest.TestCase): + def testHasSchema(self): + self.assertTrue(hasSchema('http://www.oracle.com/')); + self.assertTrue(hasSchema('https://virtualbox.com/')); + self.assertFalse(hasSchema('://virtualbox.com/')); + self.assertFalse(hasSchema('/usr/bin')); + self.assertFalse(hasSchema('usr/bin')); + self.assertFalse(hasSchema('bin')); + self.assertFalse(hasSchema('C:\\WINNT')); + +if __name__ == '__main__': + unittest.main(); + # not reached. + diff --git a/src/VBox/ValidationKit/docs/AutomaticTestingRevamp.html b/src/VBox/ValidationKit/docs/AutomaticTestingRevamp.html new file mode 100644 index 00000000..58f78b1f --- /dev/null +++ b/src/VBox/ValidationKit/docs/AutomaticTestingRevamp.html @@ -0,0 +1,1354 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<meta name="generator" content="Docutils 0.18: http://docutils.sourceforge.net/" /> +<title>AutomaticTestingRevamp.txt</title> +<style type="text/css"> + +/* +:Author: David Goodger (goodger@python.org) +:Id: $Id: AutomaticTestingRevamp.html $ +:Copyright: This stylesheet has been placed in the public domain. + +Default cascading style sheet for the HTML output of Docutils. + +See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to +customize this style sheet. +*/ + +/* used to remove borders from tables and images */ +.borderless, table.borderless td, table.borderless th { + border: 0 } + +table.borderless td, table.borderless th { + /* Override padding for "table.docutils td" with "! important". + The right padding separates the table cells. */ + padding: 0 0.5em 0 0 ! important } + +.first { + /* Override more specific margin styles with "! important". */ + margin-top: 0 ! important } + +.last, .with-subtitle { + margin-bottom: 0 ! important } + +.hidden { + display: none } + +.subscript { + vertical-align: sub; + font-size: smaller } + +.superscript { + vertical-align: super; + font-size: smaller } + +a.toc-backref { + text-decoration: none ; + color: black } + +blockquote.epigraph { + margin: 2em 5em ; } + +dl.docutils dd { + margin-bottom: 0.5em } + +object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { + overflow: hidden; +} + +/* Uncomment (and remove this text!) to get bold-faced definition list terms +dl.docutils dt { + font-weight: bold } +*/ + +div.abstract { + margin: 2em 5em } + +div.abstract p.topic-title { + font-weight: bold ; + text-align: center } + +div.admonition, div.attention, div.caution, div.danger, div.error, +div.hint, div.important, div.note, div.tip, div.warning { + margin: 2em ; + border: medium outset ; + padding: 1em } + +div.admonition p.admonition-title, div.hint p.admonition-title, +div.important p.admonition-title, div.note p.admonition-title, +div.tip p.admonition-title { + font-weight: bold ; + font-family: sans-serif } + +div.attention p.admonition-title, div.caution p.admonition-title, +div.danger p.admonition-title, div.error p.admonition-title, +div.warning p.admonition-title, .code .error { + color: red ; + font-weight: bold ; + font-family: sans-serif } + +/* Uncomment (and remove this text!) to get reduced vertical space in + compound paragraphs. +div.compound .compound-first, div.compound .compound-middle { + margin-bottom: 0.5em } + +div.compound .compound-last, div.compound .compound-middle { + margin-top: 0.5em } +*/ + +div.dedication { + margin: 2em 5em ; + text-align: center ; + font-style: italic } + +div.dedication p.topic-title { + font-weight: bold ; + font-style: normal } + +div.figure { + margin-left: 2em ; + margin-right: 2em } + +div.footer, div.header { + clear: both; + font-size: smaller } + +div.line-block { + display: block ; + margin-top: 1em ; + margin-bottom: 1em } + +div.line-block div.line-block { + margin-top: 0 ; + margin-bottom: 0 ; + margin-left: 1.5em } + +div.sidebar { + margin: 0 0 0.5em 1em ; + border: medium outset ; + padding: 1em ; + background-color: #ffffee ; + width: 40% ; + float: right ; + clear: right } + +div.sidebar p.rubric { + font-family: sans-serif ; + font-size: medium } + +div.system-messages { + margin: 5em } + +div.system-messages h1 { + color: red } + +div.system-message { + border: medium outset ; + padding: 1em } + +div.system-message p.system-message-title { + color: red ; + font-weight: bold } + +div.topic { + margin: 2em } + +h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, +h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { + margin-top: 0.4em } + +h1.title { + text-align: center } + +h2.subtitle { + text-align: center } + +hr.docutils { + width: 75% } + +img.align-left, .figure.align-left, object.align-left, table.align-left { + clear: left ; + float: left ; + margin-right: 1em } + +img.align-right, .figure.align-right, object.align-right, table.align-right { + clear: right ; + float: right ; + margin-left: 1em } + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left } + +.align-center { + clear: both ; + text-align: center } + +.align-right { + text-align: right } + +/* reset inner alignment in figures */ +div.align-right { + text-align: inherit } + +/* div.align-center * { */ +/* text-align: left } */ + +.align-top { + vertical-align: top } + +.align-middle { + vertical-align: middle } + +.align-bottom { + vertical-align: bottom } + +ol.simple, ul.simple { + margin-bottom: 1em } + +ol.arabic { + list-style: decimal } + +ol.loweralpha { + list-style: lower-alpha } + +ol.upperalpha { + list-style: upper-alpha } + +ol.lowerroman { + list-style: lower-roman } + +ol.upperroman { + list-style: upper-roman } + +p.attribution { + text-align: right ; + margin-left: 50% } + +p.caption { + font-style: italic } + +p.credits { + font-style: italic ; + font-size: smaller } + +p.label { + white-space: nowrap } + +p.rubric { + font-weight: bold ; + font-size: larger ; + color: maroon ; + text-align: center } + +p.sidebar-title { + font-family: sans-serif ; + font-weight: bold ; + font-size: larger } + +p.sidebar-subtitle { + font-family: sans-serif ; + font-weight: bold } + +p.topic-title { + font-weight: bold } + +pre.address { + margin-bottom: 0 ; + margin-top: 0 ; + font: inherit } + +pre.literal-block, pre.doctest-block, pre.math, pre.code { + margin-left: 2em ; + margin-right: 2em } + +pre.code .ln { color: grey; } /* line numbers */ +pre.code, code { background-color: #eeeeee } +pre.code .comment, code .comment { color: #5C6576 } +pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } +pre.code .literal.string, code .literal.string { color: #0C5404 } +pre.code .name.builtin, code .name.builtin { color: #352B84 } +pre.code .deleted, code .deleted { background-color: #DEB0A1} +pre.code .inserted, code .inserted { background-color: #A3D289} + +span.classifier { + font-family: sans-serif ; + font-style: oblique } + +span.classifier-delimiter { + font-family: sans-serif ; + font-weight: bold } + +span.interpreted { + font-family: sans-serif } + +span.option { + white-space: nowrap } + +span.pre { + white-space: pre } + +span.problematic { + color: red } + +span.section-subtitle { + /* font-size relative to parent (h1..h6 element) */ + font-size: 80% } + +table.citation { + border-left: solid 1px gray; + margin-left: 1px } + +table.docinfo { + margin: 2em 4em } + +table.docutils { + margin-top: 0.5em ; + margin-bottom: 0.5em } + +table.footnote { + border-left: solid 1px black; + margin-left: 1px } + +table.docutils td, table.docutils th, +table.docinfo td, table.docinfo th { + padding-left: 0.5em ; + padding-right: 0.5em ; + vertical-align: top } + +table.docutils th.field-name, table.docinfo th.docinfo-name { + font-weight: bold ; + text-align: left ; + white-space: nowrap ; + padding-left: 0 } + +/* "booktabs" style (no vertical lines) */ +table.docutils.booktabs { + border: 0px; + border-top: 2px solid; + border-bottom: 2px solid; + border-collapse: collapse; +} +table.docutils.booktabs * { + border: 0px; +} +table.docutils.booktabs th { + border-bottom: thin solid; + text-align: left; +} + +h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, +h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { + font-size: 100% } + +ul.auto-toc { + list-style-type: none } + +</style> +</head> +<body> +<div class="document"> + + +<div class="section" id="revamp-of-automatic-virtualbox-testing"> +<h1>Revamp of Automatic VirtualBox Testing</h1> +<div class="section" id="introduction"> +<h2>Introduction</h2> +<p>This is the design document for a revamped automatic testing framework. +The revamp aims at replacing the current tinderbox based testing by a new +system that is written from scratch.</p> +<p>The old system is not easy to work with and was never meant to be used for +managing tests, after all it just a simple a build manager tailored for +contiguous building. Modifying the existing tinderbox system to do what +we want would require fundamental changes that would render it useless as +a build manager, it would therefore end up as a fork. The amount of work +required would probably be about the same as writing a new system from +scratch. Other considerations, such as the license of the tinderbox +system (MPL) and language it is realized in (Perl), are also in favor of +doing it from scratch.</p> +<p>The language envisioned for the new automatic testing framework is Python. This +is for several reasons:</p> +<blockquote> +<ul class="simple"> +<li>The VirtualBox API has Python bindings.</li> +<li>Python is used quite a bit inside Sun (dunno about Oracle).</li> +<li>Works relatively well with Apache for the server side bits.</li> +<li>It is more difficult to produce write-only code in Python (alias the +we-don't-like-perl argument).</li> +<li>You don't need to compile stuff.</li> +</ul> +</blockquote> +<p>Note that the author of this document has no special training as a test +engineer and may therefore be using the wrong terms here and there. The +primary focus is to express what we need to do in order to improve +testing.</p> +<p>This document is written in reStructuredText (rst) which just happens to +be used by Python, the primary language for this revamp. For more +information on reStructuredText: <a class="reference external" href="http://docutils.sourceforge.net/rst.html">http://docutils.sourceforge.net/rst.html</a></p> +</div> +</div> +<div class="section" id="definitions-glossary"> +<h1>Definitions / Glossary</h1> +<dl class="docutils"> +<dt>sub-test driver</dt> +<dd>A set of test cases that can be used by more than one test driver. Could +also be called a test unit, in the pascal sense of unit, if it wasn't so +easily confused with 'unit test'.</dd> +<dt>test</dt> +<dd>This is somewhat ambiguous and this document try avoid using it where +possible. When used it normally refers to doing testing by executing one or +more testcases.</dd> +<dt>test case</dt> +<dd>A set of inputs, test programs and expected results. It validates system +requirements and generates a pass or failed status. A basic unit of testing. +Note that we use the term in a rather broad sense.</dd> +<dt>test driver</dt> +<dd>A program/script used to execute a test. Also known as a test harness. +Generally abbreviated 'td'. It can have sub-test drivers.</dd> +<dt>test manager</dt> +<dd>Software managing the automatic testing. This is a web application that runs +on a dedicated server (tindertux).</dd> +<dt>test set</dt> +<dd>The output of testing activity. Logs, results, ++. Our usage of this should +probably be renamed to 'test run'.</dd> +<dt>test group</dt> +<dd>A collection of related test cases.</dd> +<dt>testbox</dt> +<dd>A computer that does testing.</dd> +<dt>testbox script</dt> +<dd>Script executing orders from the test manager on a testbox. Started +automatically upon bootup.</dd> +<dt>testing</dt> +<dd>todo</dd> +<dt>TODO: Check that we've got all this right and make them more exact</dt> +<dd>where possible.</dd> +</dl> +<p>See also <a class="reference external" href="http://encyclopedia2.thefreedictionary.com/testing%20types">http://encyclopedia2.thefreedictionary.com/testing%20types</a> +and <a class="reference external" href="http://www.aptest.com/glossary.html">http://www.aptest.com/glossary.html</a> .</p> +</div> +<div class="section" id="objectives"> +<h1>Objectives</h1> +<blockquote> +<ul class="simple"> +<li>A scalable test manager (>200 testboxes).</li> +<li>Optimize the web user interface (WUI) for typical workflows and analysis.</li> +<li>Efficient and flexibile test configuration.</li> +<li>Import test result from other test systems (logo testing, VDI, ++).</li> +<li>Easy to add lots of new testscripts.</li> +<li>Run tests locally without a manager.</li> +<li>Revamp a bit at the time.</li> +</ul> +</blockquote> +</div> +<div class="section" id="the-testbox-side"> +<h1>The Testbox Side</h1> +<p>Each testbox has a unique name corresponding to its DNS zone entry. When booted +a testbox script is started automatically. This script will query the test +manager for orders and execute them. The core order downloads and executes a +test driver with parameters (configuration) from the server. The test driver +does all the necessary work for executing the test. In a typical VirtualBox +test this means picking a build, installing it, configuring VMs, running the +test VMs, collecting the results, submitting them to the server, and finally +cleaning up afterwards.</p> +<p>The testbox environment which the test drivers are executed in will have a +number of environment variables for determining location of the source images +and other test data, scratch space, test set id, server URL, and so on and so +forth.</p> +<p>On startup, the testbox script will look for crash dumps and similar on +systems where this is possible. If any sign of a crash is found, it will +put any dumps and reports in the upload directory and inform the test +manager before reporting for duty. In order to generate the proper file +names and report the crash in the right test set as well as prevent +reporting crashes unrelated to automatic testing, the testbox script will +keep information (test set id, ++) in a separate scratch directory +(${TESTBOX_PATH_SCRATCH}/../testbox) and make sure it is synced to the +disk (both files and directories).</p> +<p>After checking for crashes, the testbox script will clean up any previous test +which might be around. This involves first invoking the test script in cleanup +mode and the wiping the scratch space.</p> +<p>When reporting for duty the script will submit information about the host: OS +name, OS version, OS bitness, CPU vendor, total number of cores, VT-x support, +AMD-V support, amount of memory, amount of scratch space, and anything else that +can be found useful for scheduling tests or filtering test configurations.</p> +<div class="section" id="testbox-script-orders"> +<h2>Testbox Script Orders</h2> +<p>The orders are kept in a queue on the server and the testbox script will fetch +them one by one. Orders that cannot be executed at the moment will be masked in +the query from the testbox.</p> +<dl class="docutils"> +<dt>Execute Test Driver</dt> +<dd>Downloads and executes the a specified test driver with the given +configuration (arguments). Only one test driver can be executed at a time. +The server can specify more than one ZIP file to be downloaded and unpacked +before executing the test driver. The testbox script may cache these zip +files using http time stamping.</dd> +<dt>Abort Test Driver</dt> +<dd>Aborts the current test driver. This will drop a hint to the driver and give +it 60 seconds to shut down the normal way. If that fails, the testbox script +will kill the driver processes (SIGKILL or equivalent), invoke the +testdriver in cleanup mode, and finally wipe the scratch area. Should either +of the last two steps fail in some way, the testbox will be rebooted.</dd> +<dt>Idle</dt> +<dd>Ask again in X seconds, where X is specified by the server.</dd> +<dt>Reboot</dt> +<dd>Reboot the testbox. If a test driver is current running, an attempt at +aborting it (Abort Test Driver) will be made first.</dd> +<dt>Update</dt> +<dd>Updates the testbox script. The order includes a server relative path to the +new testbox script. This can only be executed when no test driver is +currently being executed.</dd> +</dl> +</div> +<div class="section" id="testbox-environment-variables"> +<h2>Testbox Environment: Variables</h2> +<dl class="docutils"> +<dt>COMSPEC</dt> +<dd>This will be set to C:WindowsSystem32cmd.exe on Windows.</dd> +<dt>PATH</dt> +<dd>This will contain the kBuild binary directory for the host platform.</dd> +<dt>SHELL</dt> +<dd>This will be set to point to kmk_ash(.exe) on all platforms.</dd> +<dt>TESTBOX_NAME</dt> +<dd>The testbox name. +This is not required by the local reporter.</dd> +<dt>TESTBOX_PATH_BUILDS</dt> +<dd>The absolute path to where the build repository can be found. This should be +a read only mount when possible.</dd> +<dt>TESTBOX_PATH_RESOURCES</dt> +<dd>The absolute path to where static test resources like ISOs and VDIs can be +found. The test drivers knows the layout of this. This should be a read only +mount when possible.</dd> +<dt>TESTBOX_PATH_SCRATCH</dt> +<dd>The absolute path to the scratch space. This is the current directory when +starting the test driver. It will be wiped automatically after executing the +test. +(Envisioned as ${TESTBOX_PATH_SCRIPTS}/../scratch and that +${TESTBOX_PATH_SCRATCH}/ will be automatically wiped by the testbox script.)</dd> +<dt>TESTBOX_PATH_SCRIPTS</dt> +<dd>The absolute path to the test driver and the other files that was unzipped +together with it. This is also where the test-driver-abort file will be put. +(Envisioned as ${TESTBOX_PATH_SCRATCH}/../driver, see above.)</dd> +<dt>TESTBOX_PATH_UPLOAD</dt> +<dd>The absolute path to the upload directory for the testbox. This is for +putting VOBs, PNGs, core dumps, crash dumps, and such on. The files should be +bzipped or zipped if they aren't compress already. The names should contain +the testbox and test set ID.</dd> +<dt>TESTBOX_REPORTER</dt> +<dd>The name of the test reporter back end. If not present, it will default to +the local reporter.</dd> +<dt>TESTBOX_TEST_SET_ID</dt> +<dd>The test set ID if we're running. +This is not required by the local reporter.</dd> +<dt>TESTBOX_MANAGER_URL</dt> +<dd>The URL to the test manager. +This is not required by the local reporter.</dd> +<dt>TESTBOX_XYZ</dt> +<dd>There will probably be some more of these.</dd> +</dl> +</div> +<div class="section" id="testbox-environment-core-utilities"> +<h2>Testbox Environment: Core Utilities</h2> +<p>The testbox will not provide the typical unix /bin and /usr/bin utilities. In +other words, cygwin will not be used on Windows!</p> +<p>The testbox will provide the unixy utilities that ships with kBuild and possibly +some additional ones from tools/<em>.</em>/bin in the VirtualBox tree (wget, unzip, +zip, and so on). The test drivers will avoid invoking any of these utilities +directly and instead rely on generic utility methods in the test driver +framework. That way we can more easily reimplement the functionality of the +core utilities and drop the dependency on them. It also allows us to quickly +work around platform specific oddities and bugs.</p> +</div> +<div class="section" id="test-drivers"> +<h2>Test Drivers</h2> +<p>The test drivers are programs that will do the actual testing. In addition to +run under the testbox script, they can be executed in the VirtualBox development +environment. This is important for bug analysis and for simplifying local +testing by the developers before committing changes. It also means the test +drivers can be developed locally in the VirtualBox development environment.</p> +<p>The main difference between executing a driver under the testbox script and +running it manually is that there is no test manager in the latter case. The +test result reporter will not talk to the server, but report things to a local +log file and/or standard out/err. When invoked manually, all the necessary +arguments will need to be specified by hand of course - it should be possible +to extract them from a test set as well.</p> +<p>For the early implementation stages, an implementation of the reporter interface +that talks to the tinderbox base test manager will be needed. This will be +dropped later on when a new test manager is ready.</p> +<p>As hinted at in other sections, there will be a common framework +(libraries/packages/classes) for taking care of the tedious bits that every +test driver needs to do. Sharing code is essential to easing test driver +development as well as reducing their complexity. The framework will contain:</p> +<blockquote> +<ul class="simple"> +<li>A generic way of submitting output. This will be a generic interface with +multiple implementation, the TESTBOX_REPORTER environment variable +will decide which of them to use. The interface will have very specific +methods to allow the reporter to do a best possible job in reporting the +results to the test manager.</li> +<li><dl class="first docutils"> +<dt>Helpers for typical tasks, like:</dt> +<dd><ul class="first last"> +<li>Copying files.</li> +<li>Deleting files, directory trees and scratch space.</li> +<li>Unzipping files.</li> +<li>Creating ISOs</li> +<li>And such things.</li> +</ul> +</dd> +</dl> +</li> +<li>Helpers for installing and uninstalling VirtualBox.</li> +<li>Helpers for defining VMs. (The VBox API where available.)</li> +<li>Helpers for controlling VMs. (The VBox API where available.)</li> +</ul> +</blockquote> +<p>The VirtualBox bits will be separate from the more generic ones, simply because +this is cleaner it will allow us to reuse the system for testing other products.</p> +<p>The framework will be packaged in a zip file other than the test driver so we +don't waste time and space downloading the same common code.</p> +<p>The test driver will poll for the file +${TESTBOX_PATH_SCRIPTS}/test-driver-abort and abort all testing when it sees it.</p> +<p>The test driver can be invoked in three modes: execute, help and cleanup. The +default is execute mode, the help shows an configuration summary and the cleanup +is for cleaning up after a reboot or aborted run. The latter is done by the +testbox script on startup and after abort - the driver is expected to clean up +by itself after a normal run.</p> +</div> +</div> +<div class="section" id="the-server-side"> +<h1>The Server Side</h1> +<p>The server side will be implemented using a webserver (apache), a database +(postgres) and cgi scripts (Python). In addition a cron job (Python) running +once a minute will generate static html for frequently used pages and maybe +execute some other tasks for driving the testing forwards. The order queries +from the testbox script is the primary driving force in the system. The total +makes up the test manager.</p> +<p>The test manager can be split up into three rough parts:</p> +<blockquote> +<ul class="simple"> +<li>Configuration (of tests, testgroups and testboxes).</li> +<li>Execution (of tests, collecting and organizing the output).</li> +<li>Analysis (of test output, mostly about presentation).</li> +</ul> +</blockquote> +</div> +<div class="section" id="test-manager-requirements"> +<h1>Test Manager: Requirements</h1> +<p>List of requirements:</p> +<blockquote> +<ul class="simple"> +<li>Two level testing - L1 quick smoke tests and L2 longer tests performed on +builds passing L1. (Klaus (IIRC) meant this could be realized using +test dependency.)</li> +<li>Black listing builds (by revision or similar) known to be bad.</li> +<li>Distinguish between build types so we can do a portion of the testing with +strict builds.</li> +<li>Easy to re-configure build source for testing different branch or for +testing a release candidate. (Directory based is fine.)</li> +<li>Useful to be able to partition testboxes (run specific builds on some +boxes, let an engineer have a few boxes for a while).</li> +<li>Interaction with ILOM/...: reset systems.</li> +<li>Be able to suspend testing on selected testboxes when doing maintenance +(where automatically resuming testing on reboot is undesired) or similar +activity.</li> +<li>Abort testing on selected testboxes.</li> +<li>Scheduling of tests requiring more than one testbox.</li> +<li>Scheduling of tests that cannot be executing concurrently on several +machines because of some global resource like an iSCSI target.</li> +<li>Jump the scheduling queue. Scheduling of specified test the next time a +testbox is available (optionally specifying which testbox to schedule it +on).</li> +<li><dl class="first docutils"> +<dt>Configure tests with variable configuration to get better coverage. Two modes:</dt> +<dd><ul class="first last"> +<li>TM generates the permutations based on one or more sets of test script arguments.</li> +<li>Each configuration permutation is specified manually.</li> +</ul> +</dd> +</dl> +</li> +<li>Test specification needs to be flexible (select tests, disable test, test +scheduling (run certain tests nightly), ... ).</li> +<li>Test scheduling by hour+weekday and by priority.</li> +<li>Test dependencies (test A depends on test B being successful).</li> +<li>Historize all configuration data, in particular test configs (permutations +included) and testboxes.</li> +<li>Test sets has at a minimum a build reference, a testbox reference and a +primary log associated with it.</li> +<li><dl class="first docutils"> +<dt>Test sets stores further result as a recursive collection of:</dt> +<dd><ul class="first last"> +<li>hierarchical subtest name (slash sep)</li> +<li>test parameters / config</li> +<li>bool fail/succ</li> +<li>attributes (typed?)</li> +<li>test time</li> +<li>e.g. throughput</li> +<li>subresults</li> +<li>log</li> +<li>screenshots, video,...</li> +</ul> +</dd> +</dl> +</li> +<li>The test sets database structure needs to designed such that data mining +can be done in an efficient manner.</li> +<li>Presentation/analysis: graphs!, categorize bugs, columns reorganizing +grouped by test (hierarchical), overviews, result for last day.</li> +</ul> +</blockquote> +</div> +<div class="section" id="test-manager-configuration"> +<h1>Test Manager: Configuration</h1> +<div class="section" id="testboxes"> +<h2>Testboxes</h2> +<p>Configuration of testboxes doesn't involve much work normally. A testbox +is added manually to the test manager by entering the DNS entry and/or IP +address (the test manager resolves the missing one when necessary) as well as +the system UUID (when obtainable - should be displayed by the testbox script +installer). Queries from unregistered testboxes will be declined as a kind of +security measure, the incident should be logged in the webserver log if +possible. In later dealings with the client the System UUID will be the key +identifier. It's permittable for the IP address to change when the testbox +isn't online, but not while testing (just imagine live migration tests and +network tests). Ideally, the testboxes should not change IP address.</p> +<p>The testbox edit function must allow changing the name and system UUID.</p> +<p>One further idea for the testbox configuration is indicating what they are +capable of to filter out tests and test configurations that won't work on that +testbox. To examplify this take the ACP2 installation test. If the test +manager does not make sure the testbox have VT-x or AMD-v capabilities, the test +is surely going to fail. Other testbox capabilities would be total number of +CPU cores, memory size, scratch space. These testbox capabilities should be +collected automatically on bootup by the testbox script together with OS name, +OS version and OS bitness.</p> +<p>A final thought, instead of outright declining all requests from new testboxes, +we could record the unregistered testboxes with ip, UUID, name, os info and +capabilities but mark them as inactive. The test operator can then activate +them on an activation page or edit the testbox or something.</p> +</div> +<div class="section" id="testcases"> +<h2>Testcases</h2> +<p>We use the term testcase for a test.</p> +</div> +<div class="section" id="testgroups"> +<h2>Testgroups</h2> +<p>Testcases are organized into groups. A testcase can be member of more than one +group. The testcase gets a priority assigned to it in connection with the +group membership.</p> +<p>Testgroups are picked up by a testbox partition (aka scheduling group) and a +prioirty, scheduling time restriction and dependencies on other test groups are +associated with the assignment. A testgroup can be used by several testbox +partitions.</p> +<p>(This used to be called 'testsuites' but was renamed to avoid confusion with +the VBox Test Suite.)</p> +</div> +<div class="section" id="scheduling"> +<h2>Scheduling</h2> +<p>The initial scheduler will be modelled after what we're doing already on in the +tinderbox driven testing. It's best described as a best effort continuous +integration scheduler. Meaning, it will always use the latest build suitable +for a testcase. It will schedule on a testcase level, using the combined +priority of the testcase in the test group and the test group with the testbox +partition, trying to spread the test case argument variation out accordingly +over the whole scheduilng queue. Which argument variation to start with, is +not undefined (random would be best).</p> +<p>Later, we may add other schedulers as needed.</p> +</div> +</div> +<div class="section" id="the-test-manager-database"> +<h1>The Test Manager Database</h1> +<p>First a general warning:</p> +<blockquote> +The guys working on this design are not database experts, web +programming experts or similar, rather we are low level guys +who's main job is x86 & AMD64 virtualization. So, please don't +be too hard on us. :-)</blockquote> +<p>A logical table layout can be found in TestManagerDatabaseMap.png (created by +Oracle SQL Data Modeler, stored in TestManagerDatabase.dmd). The physical +database layout can be found in TestManagerDatabaseInit.pgsql postgreSQL +script. The script is commented.</p> +<div class="section" id="data-history"> +<h2>Data History</h2> +<p>We need to somehow track configuration changes over time. We also need to +be able to query the exact configuration a test set was run with so we can +understand and make better use of the results.</p> +<p>There are different techniques for archiving this, one is tuple-versioning +( <a class="reference external" href="http://en.wikipedia.org/wiki/Tuple-versioning">http://en.wikipedia.org/wiki/Tuple-versioning</a> ), another is log trigger +( <a class="reference external" href="http://en.wikipedia.org/wiki/Log_trigger">http://en.wikipedia.org/wiki/Log_trigger</a> ). We use tuple-versioning in +this database, with 'effective' as start date field name and 'expire' as +the end (exclusive).</p> +<p>Tuple-versioning has a shortcoming wrt to keys, both primary and foreign. +The primary key of a table employing tuple-versioning is really +'id' + 'valid_period', where the latter is expressed using two fields +([effective...expire-1]). Only, how do you tell the database engine that +it should not allow overlapping valid_periods? Useful suggestions are +welcomed. :-)</p> +<p>Foreign key references to a table using tuple-versioning is running into +trouble because of the time axis and that to our knowledge foreign keys +must reference exactly one row in the other table. When time is involved +what we wish to tell the database is that at any given time, there actually +is exactly one row we want to match in the other table, only we've no idea +how to express this. So, many foreign keys are not expressed in SQL of this +database.</p> +<p>In some cases, we extend the tuple-versioning with a generation ID so that +normal foreign key referencing can be used. We only use this for recording +(references in testset) and scheduling (schedqueue), as using it more widely +would force updates (gen_id changes) to propagate into all related tables.</p> +<dl class="docutils"> +<dt>See also:</dt> +<dd><ul class="first last simple"> +<li><a class="reference external" href="http://en.wikipedia.org/wiki/Slowly_changing_dimension">http://en.wikipedia.org/wiki/Slowly_changing_dimension</a></li> +<li><a class="reference external" href="http://en.wikipedia.org/wiki/Change_data_capture">http://en.wikipedia.org/wiki/Change_data_capture</a></li> +<li><a class="reference external" href="http://en.wikipedia.org/wiki/Temporal_database">http://en.wikipedia.org/wiki/Temporal_database</a></li> +</ul> +</dd> +</dl> +</div> +</div> +<div class="section" id="test-manager-execution"> +<h1>Test Manager: Execution</h1> +</div> +<div class="section" id="test-manager-scenarios"> +<h1>Test Manager: Scenarios</h1> +<div class="section" id="testbox-signs-on-at-bootup"> +<h2>#1 - Testbox Signs On (At Bootup)</h2> +<dl class="docutils"> +<dt>The testbox supplies a number of inputs when reporting for duty:</dt> +<dd><ul class="first last simple"> +<li>IP address.</li> +<li>System UUID.</li> +<li>OS name.</li> +<li>OS version.</li> +<li>CPU architecture.</li> +<li>CPU count (= threads).</li> +<li>CPU VT-x/AMD-V capability.</li> +<li>CPU nested paging capability.</li> +<li>Chipset I/O MMU capability.</li> +<li>Memory size.</li> +<li>Scratch size space (for testing).</li> +<li>Testbox Script revision.</li> +</ul> +</dd> +<dt>Results:</dt> +<dd><ul class="first last simple"> +<li>ACK or NACK.</li> +<li>Testbox ID and name on ACK.</li> +</ul> +</dd> +</dl> +<p>After receiving a ACK the testbox will ask for work to do, i.e. continue with +scenario #2. In the NACK case, it will sleep for 60 seconds and try again.</p> +<p>Actions:</p> +<ol class="arabic"> +<li><p class="first">Validate the testbox by looking the UUID up in the TestBoxes table. +If not found, NACK the request. SQL:</p> +<pre class="literal-block"> +SELECT idTestBox, sName +FROM TestBoxes +WHERE uuidSystem = :sUuid + AND tsExpire = 'infinity'::timestamp; +</pre> +</li> +<li><p class="first">Check if any of the information by testbox script has changed. The two +sizes are normalized first, memory size rounded to nearest 4 MB and scratch +space is rounded down to nearest 64 MB. If anything changed, insert a new +row in the testbox table and historize the current one, i.e. set +OLD.tsExpire to NEW.tsEffective and get a new value for NEW.idGenTestBox.</p> +</li> +<li><dl class="first docutils"> +<dt>Check with TestBoxStatuses:</dt> +<dd><ol class="first last loweralpha simple"> +<li>If there is an row for the testbox in it already clean up change it +to 'idle' state and deal with any open testset like described in +scenario #9.</li> +<li>If there is no row, add one with 'idle' state.</li> +</ol> +</dd> +</dl> +</li> +<li><p class="first">ACK the request and pass back the idTestBox.</p> +</li> +</ol> +<dl class="docutils"> +<dt>Note! Testbox.enabled is not checked here, that is only relevant when it asks</dt> +<dd>for a new task (scenario #2 and #5).</dd> +<dt>Note! Should the testbox script detect changes in any of the inputs, it should</dt> +<dd>redo the sign in.</dd> +<dt>Note! In scenario #8, the box will not sign on until it has done the reboot and</dt> +<dd>cleanup reporting!</dd> +</dl> +</div> +<div class="section" id="testbox-asks-for-work-to-do"> +<h2>#2 - Testbox Asks For Work To Do</h2> +<dl class="docutils"> +<dt>Inputs:</dt> +<dd><ul class="first last simple"> +<li>The testbox is supplying its IP indirectly.</li> +<li>The testbox should supply its UUID and ID directly.</li> +</ul> +</dd> +<dt>Results:</dt> +<dd><ul class="first last simple"> +<li>IDLE, WAIT, EXEC, REBOOT, UPGRADE, UPGRADE-AND-REBOOT, SPECIAL or DEAD.</li> +</ul> +</dd> +</dl> +<p>Actions:</p> +<ol class="arabic"> +<li><p class="first">Validate the ID and IP by selecting the currently valid testbox row:</p> +<pre class="literal-block"> +SELECT idGenTestBox, fEnabled, idSchedGroup, enmPendingCmd +FROM TestBoxes +WHERE id = :id + AND uuidSystem = :sUuid + AND ip = :ip + AND tsExpire = 'infinity'::timestamp; +</pre> +<p>If NOT found return DEAD to the testbox client (it will go back to sign on +mode and retry every 60 seconds or so - see scenario #1).</p> +<dl class="docutils"> +<dt>Note! The WUI will do all necessary clean-ups when deleting a testbox, so</dt> +<dd><p class="first last">contrary to the initial plans, we don't need to do anything more for +the DEAD status.</p> +</dd> +</dl> +</li> +<li><p class="first">Check with TestBoxStatuses (maybe joined with query from 1).</p> +<p>If enmState is 'gang-gathering': Goto scenario #6 on timeout or pending +'abort' or 'reboot' command. Otherwise, tell the testbox to WAIT [done].</p> +<p>If enmState is 'gang-testing': The gang has been gathered and execution +has been triggered. Goto 5.</p> +<p>If enmState is not 'idle', change it to 'idle'.</p> +<p>If idTestSet is not NULL, CALL scenario #9 to it up.</p> +<p>If there is a pending abort command, remove it.</p> +<p>If there is a pending command and the old state doesn't indicate that it was +being executed, GOTO scenario #3.</p> +<dl class="docutils"> +<dt>Note! There should be a TestBoxStatuses row after executing scenario #1,</dt> +<dd><p class="first last">however should none be found for some funky reason, returning DEAD +will fix the problem (see above)</p> +</dd> +</dl> +</li> +<li><p class="first">If the testbox was marked as disabled, respond with an IDLE command to the +testbox [done]. (Note! Must do this after TestBoxStatuses maintenance from +point 2, or abandoned tests won't be cleaned up after a testbox is disabled.)</p> +</li> +<li><p class="first">Consider testcases in the scheduling queue, pick the first one which the +testbox can execute. There is a concurrency issue here, so we put and +exclusive lock on the SchedQueues table while considering its content.</p> +<p>The cursor we open looks something like this:</p> +<pre class="literal-block"> +SELECT idItem, idGenTestCaseArgs, + idTestSetGangLeader, cMissingGangMembers +FROM SchedQueues +WHERE idSchedGroup = :idSchedGroup + AND ( bmHourlySchedule is NULL + OR get_bit(bmHourlySchedule, :iHourOfWeek) = 1 ) --< does this work? +ORDER BY ASC idItem; +</pre> +</li> +</ol> +<blockquote> +<p>If there no rows are returned (this can happen because no testgroups are +associated with this scheduling group, the scheduling group is disabled, +or because the queue is being regenerated), we will tell the testbox to +IDLE [done].</p> +<dl class="docutils"> +<dt>For each returned row we will:</dt> +<dd><ol class="first last loweralpha"> +<li><p class="first">Check testcase/group dependencies.</p> +</li> +<li><p class="first">Select a build (and default testsuite) satisfying the dependencies.</p> +</li> +<li><p class="first">Check the testcase requirements with that build in mind.</p> +</li> +<li><p class="first">If idTestSetGangLeader is NULL, try allocate the necessary resources.</p> +</li> +<li><p class="first">If it didn't check out, fetch the next row and redo from (a).</p> +</li> +<li><p class="first">Tentatively create a new test set row.</p> +</li> +<li><dl class="first docutils"> +<dt>If not gang scheduling:</dt> +<dd><ul class="first last simple"> +<li>Next state: 'testing'</li> +</ul> +</dd> +<dt>ElIf we're the last gang participant:</dt> +<dd><ul class="first last simple"> +<li>Set idTestSetGangLeader to NULL.</li> +<li>Set cMissingGangMembers to 0.</li> +<li>Next state: 'gang-testing'</li> +</ul> +</dd> +<dt>ElIf we're the first gang member:</dt> +<dd><ul class="first last simple"> +<li>Set cMissingGangMembers to TestCaseArgs.cGangMembers - 1.</li> +<li>Set idTestSetGangLeader to our idTestSet.</li> +<li>Next state: 'gang-gathering'</li> +</ul> +</dd> +<dt>Else:</dt> +<dd><ul class="first last simple"> +<li>Decrement cMissingGangMembers.</li> +<li>Next state: 'gang-gathering'</li> +</ul> +</dd> +<dt>If we're not gang scheduling OR cMissingGangMembers is 0:</dt> +<dd><p class="first last">Move the scheduler queue entry to the end of the queue.</p> +</dd> +</dl> +<p>Update our TestBoxStatuses row with the new state and test set. +COMMIT;</p> +</li> +</ol> +</dd> +</dl> +</blockquote> +<ol class="arabic" start="5"> +<li><dl class="first docutils"> +<dt>If state is 'testing' or 'gang-testing':</dt> +<dd><p class="first">EXEC reponse.</p> +<p class="last">The EXEC response for a gang scheduled testcase includes a number of +extra arguments so that the script knows the position of the testbox +it is running on and of the other members. This means the that the +TestSet.iGangMemberNo is passed using --gang-member-no and the IP +addresses of the all gang members using --gang-ipv4-<memb-no> <ip>.</p> +</dd> +<dt>Else (state is 'gang-gathering'):</dt> +<dd><p class="first last">WAIT</p> +</dd> +</dl> +</li> +</ol> +</div> +<div class="section" id="pending-command-when-testbox-asks-for-work"> +<h2>#3 - Pending Command When Testbox Asks For Work</h2> +<p>This is a subfunction of scenario #2 and #5.</p> +<p>As seen in scenario #2, the testbox will send 'abort' commands to /dev/null +when it finds one when not executing a test. This includes when it reports +that the test has completed (no need to abort a completed test, wasting lot +of effort when standing at the finish line).</p> +<p>The other commands, though, are passed back to the testbox. The testbox +script will respond with an ACK or NACK as it sees fit. If NACKed, the +pending command will be removed (pending_cmd set to none) and that's it. +If ACKed, the state of the testbox will change to that appropriate for the +command and the pending_cmd set to none. Should the testbox script fail to +respond, the command will be repeated the next time it asks for work.</p> +</div> +<div class="section" id="testbox-uploads-results-during-test"> +<h2>#4 - Testbox Uploads Results During Test</h2> +<p>TODO</p> +</div> +<div class="section" id="testbox-completes-test-and-asks-for-work"> +<h2>#5 - Testbox Completes Test and Asks For Work</h2> +<p>This is very similar to scenario #2</p> +<p>TODO</p> +</div> +<div class="section" id="gang-gathering-timeout"> +<h2>#6 - Gang Gathering Timeout</h2> +<p>This is a subfunction of scenario #2.</p> +<p>When gathering a gang of testboxes for a testcase, we do not want to wait +forever and have testboxes doing nothing for hours while waiting for partners. +So, the gathering has a reasonable timeout (imagine something like 20-30 mins).</p> +<p>Also, we need some way of dealing with 'abort' and 'reboot' commands being +issued while waiting. The easy way out is pretend it's a time out.</p> +<p>When changing the status to 'gang-timeout' we have to be careful. First of all, +we need to exclusively lock the SchedQueues and TestBoxStatuses (in that order) +and re-query our status. If it changed redo the checks in scenario #2 point 2.</p> +<p>If we still want to timeout/abort, change the state from 'gang-gathering' to +'gang-gathering-timedout' on all the gang members that has gathered so far. +Then reset the scheduling queue record and move it to the end of the queue.</p> +<p>When acting on 'gang-timeout' the TM will fail the testset in a manner similar +to scenario #9. No need to repeat that.</p> +</div> +<div class="section" id="gang-cleanup"> +<h2>#7 - Gang Cleanup</h2> +<p>When a testbox completes a gang scheduled test, we will have to serialize +resource cleanup (both globally and on testboxes) as they stop. More details +can be found in the documentation of 'gang-cleanup'.</p> +<p>So, the transition from 'gang-testing' is always to 'gang-cleanup'. When we +can safely leave 'gang-cleanup' is decided by the query:</p> +<pre class="literal-block"> +SELECT COUNT(*) +FROM TestBoxStatuses, + TestSets +WHERE TestSets.idTestSetGangLeader = :idTestSetGangLeader + AND TestSets.idTestBox = TestBoxStatuses.idTestBox + AND TestBoxStatuses.enmState = 'gang-running'::TestBoxState_T; +</pre> +<p>As long as there are testboxes still running, we stay in the 'gang-cleanup' +state. Once there are none, we continue closing the testset and such.</p> +</div> +<div class="section" id="testbox-reports-a-crash-during-test-execution"> +<h2>#8 - Testbox Reports A Crash During Test Execution</h2> +<p>TODO</p> +</div> +<div class="section" id="cleaning-up-abandoned-testcase"> +<h2>#9 - Cleaning Up Abandoned Testcase</h2> +<p>This is a subfunction of scenario #1 and #2. The actions taken are the same in +both situations. The precondition for taking this path is that the row in the +testboxstatus table is referring to a testset (i.e. testset_id is not NULL).</p> +<p>Actions:</p> +<ol class="arabic simple"> +<li><dl class="first docutils"> +<dt>If the testset is incomplete, we need to completed:</dt> +<dd><ol class="first last loweralpha"> +<li>Add a message to the root TestResults row, creating one if necessary, +that explains that the test was abandoned. This is done +by inserting/finding the string into/in TestResultStrTab and adding +a row to TestResultMsgs with idStrMsg set to that string id and +enmLevel set to 'failure'.</li> +<li>Mark the testset as failed.</li> +</ol> +</dd> +</dl> +</li> +<li>Free any global resources referenced by the test set. This is done by +deleting all rows in GlobalResourceStatuses matching the testbox id.</li> +<li>Set the idTestSet to NULL in the TestBoxStatuses row.</li> +</ol> +</div> +<div class="section" id="cleaning-up-a-disabled-dead-testbox"> +<h2>#10 - Cleaning Up a Disabled/Dead TestBox</h2> +<p>The UI needs to be able to clean up the remains of a testbox which for some +reason is out of action. Normal cleaning up of abandoned testcases requires +that the testbox signs on or asks for work, but if the testbox is dead or +in some way indisposed, it won't be doing any of that. So, the testbox +sheriff needs to have a way of cleaning up after it.</p> +<p>It's basically a manual scenario #9 but with some safe guards, like checking +that the box hasn't been active for the last 1-2 mins (max idle/wait time * 2).</p> +<dl class="docutils"> +<dt>Note! When disabling a box that still executing the testbox script, this</dt> +<dd>cleanup isn't necessary as it will happen automatically. Also, it's +probably desirable that the testbox finishes what ever it is doing first +before going dormant.</dd> +</dl> +</div> +</div> +<div class="section" id="test-manager-analysis"> +<h1>Test Manager: Analysis</h1> +<p>One of the testbox sheriff's tasks is to try figure out the reason why something +failed. The test manager will provide facilities for doing so from very early +in it's implementation.</p> +<p>We need to work out some useful status reports for the early implementation. +Later there will be more advanced analysis tools, where for instance we can +create graphs from selected test result values or test execution times.</p> +</div> +<div class="section" id="implementation-plan"> +<h1>Implementation Plan</h1> +<p>This has changed for various reasons. The current plan is to implement the +infrastructure (TM & testbox script) first and do a small deployment with the +2-5 test drivers in the Testsuite as basis. Once the bugs are worked out, we +will convert the rest of the tests and start adding new ones.</p> +<p>We just need to finally get this done, no point in doing it piecemeal by now!</p> +<div class="section" id="test-manager-implementation-sub-tasks"> +<h2>Test Manager Implementation Sub-Tasks</h2> +<p>The implementation of the test manager and adjusting/completing of the testbox +script and the test drivers are tasks which can be done by more than one +person. Splitting up the TM implementation into smaller tasks should allow +parallel development of different tasks and get us working code sooner.</p> +</div> +<div class="section" id="milestone-1"> +<h2>Milestone #1</h2> +<p>The goal is to getting the fundamental testmanager engine implemented, debugged +and working. With the exception of testboxes, the configuration will be done +via SQL inserts.</p> +<p>Tasks in somewhat prioritized order:</p> +<blockquote> +<ul class="simple"> +<li>Kick off test manager. It will live in testmanager/. Salvage as much as +possible from att/testserv. Create basic source and file layout.</li> +<li>Adjust the testbox script, part one. There currently is a testbox script +in att/testbox, this shall be moved up into testboxscript/. The script +needs to be adjusted according to the specification layed down earlier +in this document. Installers or installation scripts for all relevant +host OSes are required. Left for part two is result reporting beyond the +primary log. This task must be 100% feature complete, on all host OSes, +there is no room for FIXME, XXX or @todo here.</li> +<li>Implement the schedule queue generator.</li> +<li>Implement the testbox dispatcher in TM. Support all the testbox script +responses implemented above, including upgrading the testbox script.</li> +<li>Implement simple testbox management page.</li> +<li>Implement some basic activity and result reports so that we can see +what's going on.</li> +<li>Create a testmanager / testbox test setup. This lives in selftest/.<ol class="arabic"> +<li>Set up something that runs, no fiddly bits. Debug till it works.</li> +<li>Create a setup that tests testgroup dependencies, i.e. real tests +depending on smoke tests.</li> +<li>Create a setup that exercises testcase dependency.</li> +<li>Create a setup that exercises global resource allocation.</li> +<li>Create a setup that exercises gang scheduling.</li> +</ol> +</li> +<li>Check that all features work.</li> +</ul> +</blockquote> +</div> +<div class="section" id="milestone-2"> +<h2>Milestone #2</h2> +<p>The goal is getting to VBox testing.</p> +<p>Tasks in somewhat prioritized order:</p> +<blockquote> +<ul class="simple"> +<li>Implement full result reporting in the testbox script and testbox driver. +A testbox script specific reporter needs to be implemented for the +testdriver framework. The testbox script needs to forward the results to +the test manager, or alternatively the testdriver report can talk +directly to the TM.</li> +<li>Implement the test manager side of the test result reporting.</li> +<li>Extend the selftest with some setup that report all kinds of test +results.</li> +<li>Implement script/whatever feeding builds to the test manager from the +tinderboxes.</li> +<li>The toplevel test driver is a VBox thing that must be derived from the +base TestDriver class or maybe the VBox one. It should move from +toptestdriver to testdriver and be renamed to vboxtltd or smth.</li> +<li>Create a vbox testdriver that boots the t-xppro VM once and that's it.</li> +<li>Create a selftest setup which tests booting t-xppro taking builds from +the tinderbox.</li> +</ul> +</blockquote> +</div> +<div class="section" id="milestone-3"> +<h2>Milestone #3</h2> +<p>The goal for this milestone is configuration and converting current testcases, +the result will be the a minimal test deployment (4-5 new testboxes).</p> +<p>Tasks in somewhat prioritized order:</p> +<blockquote> +<ul class="simple"> +<li>Implement testcase configuration.</li> +<li>Implement testgroup configuration.</li> +<li>Implement build source configuration.</li> +<li>Implement scheduling group configuration.</li> +<li>Implement global resource configuration.</li> +<li>Re-visit the testbox configuration.</li> +<li>Black listing of builds.</li> +<li>Implement simple failure analysis and reporting.</li> +<li>Implement the initial smoke tests modelled on the current smoke tests.</li> +<li>Implement installation tests for Windows guests.</li> +<li>Implement installation tests for Linux guests.</li> +<li>Implement installation tests for Solaris guest.</li> +<li>Implement installation tests for OS/2 guest.</li> +<li>Set up a small test deployment.</li> +</ul> +</blockquote> +</div> +<div class="section" id="further-work"> +<h2>Further work</h2> +<p>After milestone #3 has been reached and issues found by the other team members +have been addressed, we will probably go for full deployment.</p> +<p>Beyond this point we will need to improve reporting and analysis. There may be +configuration aspects needing reporting as well.</p> +<p>Once deployed, a golden rule will be that all new features shall have test +coverage. Preferably, implemented by someone else and prior to the feature +implementation.</p> +</div> +</div> +<div class="section" id="discussion-logs"> +<h1>Discussion Logs</h1> +<div class="section" id="various-discussions-with-michal-and-or-klaus"> +<h2>2009-07-21,22,23 Various Discussions with Michal and/or Klaus</h2> +<ul class="simple"> +<li>Scheduling of tests requiring more than one testbox.</li> +<li>Scheduling of tests that cannot be executing concurrently on several machines +because of some global resource like an iSCSI target.</li> +<li>Manually create the test config permutations instead of having the test +manager create all possible ones and wasting time.</li> +<li>Distinguish between built types so we can run smoke tests on strick builds as +well as release ones.</li> +</ul> +</div> +<div class="section" id="brief-discussion-with-michal"> +<h2>2009-07-20 Brief Discussion with Michal</h2> +<ul class="simple"> +<li>Installer for the testbox script to make bringing up a new testbox even +smoother.</li> +</ul> +</div> +<div class="section" id="raw-input"> +<h2>2009-07-16 Raw Input</h2> +<ul class="simple"> +<li><dl class="first docutils"> +<dt>test set. recursive collection of:</dt> +<dd><ul class="first last"> +<li>hierachical subtest name (slash sep)</li> +<li>test parameters / config</li> +<li>bool fail/succ</li> +<li>attributes (typed?)</li> +<li>test time</li> +<li>e.g. throughput</li> +<li>subresults</li> +<li>log</li> +<li>screenshots,....</li> +</ul> +</dd> +</dl> +</li> +<li>client package (zip) dl from server (maybe client caching)</li> +<li><dl class="first docutils"> +<dt>thoughts on bits to do at once.</dt> +<dd><ul class="first last"> +<li>We <em>really</em> need the basic bits ASAP.</li> +<li>client -> support for test driver</li> +<li>server -> controls configs</li> +<li>cleanup on both sides</li> +</ul> +</dd> +</dl> +</li> +</ul> +</div> +<div class="section" id="raw-input-1"> +<h2>2009-07-15 Raw Input</h2> +<ul class="simple"> +<li>testing should start automatically</li> +<li>switching to branch too tedious</li> +<li>useful to be able to partition testboxes (run specific builds on some boxes, let an engineer have a few boxes for a while).</li> +<li>test specification needs to be more flexible (select tests, disable test, test scheduling (run certain tests nightly), ... )</li> +<li>testcase dependencies (blacklisting builds, run smoketests on box A before long tests on box B, ...)</li> +<li>more testing flexibility, more test than just install/moke. For instance unit tests, benchmarks, ...</li> +<li>presentation/analysis: graphs!, categorize bugs, columns reorganizing grouped by test (hierarchical), overviews, result for last day.</li> +<li>testcase specificion, variables (e.g. I/O-APIC, SMP, HWVIRT, SATA...) as sub-tests</li> +<li>interation with ILOM/...: reset systems</li> +<li>Changes needs LDAP authentication</li> +<li>historize all configuration w/ name</li> +<li>ability to run testcase locally (provided the VDI/ISO/whatever extra requirements can be met).</li> +</ul> +<hr class="docutils" /> +<table class="docutils footnote" frame="void" id="footnote-1" rules="none"> +<colgroup><col class="label" /><col /></colgroup> +<tbody valign="top"> +<tr><td class="label">[1]</td><td>no such footnote</td></tr> +</tbody> +</table> +<hr class="docutils" /> +<table class="docutils field-list" frame="void" rules="none"> +<col class="field-name" /> +<col class="field-body" /> +<tbody valign="top"> +<tr class="field"><th class="field-name">Status:</th><td class="field-body">$Id: AutomaticTestingRevamp.html $</td> +</tr> +<tr class="field"><th class="field-name">Copyright:</th><td class="field-body">Copyright (C) 2010-2020 Oracle Corporation.</td> +</tr> +</tbody> +</table> +</div> +</div> +</div> +</body> +</html> diff --git a/src/VBox/ValidationKit/docs/AutomaticTestingRevamp.txt b/src/VBox/ValidationKit/docs/AutomaticTestingRevamp.txt new file mode 100644 index 00000000..fd7719c6 --- /dev/null +++ b/src/VBox/ValidationKit/docs/AutomaticTestingRevamp.txt @@ -0,0 +1,1061 @@ + +Revamp of Automatic VirtualBox Testing +====================================== + + +Introduction +------------ + +This is the design document for a revamped automatic testing framework. +The revamp aims at replacing the current tinderbox based testing by a new +system that is written from scratch. + +The old system is not easy to work with and was never meant to be used for +managing tests, after all it just a simple a build manager tailored for +contiguous building. Modifying the existing tinderbox system to do what +we want would require fundamental changes that would render it useless as +a build manager, it would therefore end up as a fork. The amount of work +required would probably be about the same as writing a new system from +scratch. Other considerations, such as the license of the tinderbox +system (MPL) and language it is realized in (Perl), are also in favor of +doing it from scratch. + +The language envisioned for the new automatic testing framework is Python. This +is for several reasons: + + - The VirtualBox API has Python bindings. + - Python is used quite a bit inside Sun (dunno about Oracle). + - Works relatively well with Apache for the server side bits. + - It is more difficult to produce write-only code in Python (alias the + we-don't-like-perl argument). + - You don't need to compile stuff. + +Note that the author of this document has no special training as a test +engineer and may therefore be using the wrong terms here and there. The +primary focus is to express what we need to do in order to improve +testing. + +This document is written in reStructuredText (rst) which just happens to +be used by Python, the primary language for this revamp. For more +information on reStructuredText: http://docutils.sourceforge.net/rst.html + + +Definitions / Glossary +====================== + +sub-test driver + A set of test cases that can be used by more than one test driver. Could + also be called a test unit, in the pascal sense of unit, if it wasn't so + easily confused with 'unit test'. + +test + This is somewhat ambiguous and this document try avoid using it where + possible. When used it normally refers to doing testing by executing one or + more testcases. + +test case + A set of inputs, test programs and expected results. It validates system + requirements and generates a pass or failed status. A basic unit of testing. + Note that we use the term in a rather broad sense. + +test driver + A program/script used to execute a test. Also known as a test harness. + Generally abbreviated 'td'. It can have sub-test drivers. + +test manager + Software managing the automatic testing. This is a web application that runs + on a dedicated server (tindertux). + +test set + The output of testing activity. Logs, results, ++. Our usage of this should + probably be renamed to 'test run'. + +test group + A collection of related test cases. + +testbox + A computer that does testing. + +testbox script + Script executing orders from the test manager on a testbox. Started + automatically upon bootup. + +testing + todo + +TODO: Check that we've got all this right and make them more exact + where possible. + +See also http://encyclopedia2.thefreedictionary.com/testing%20types +and http://www.aptest.com/glossary.html . + + + +Objectives +========== + + - A scalable test manager (>200 testboxes). + - Optimize the web user interface (WUI) for typical workflows and analysis. + - Efficient and flexibile test configuration. + - Import test result from other test systems (logo testing, VDI, ++). + - Easy to add lots of new testscripts. + - Run tests locally without a manager. + - Revamp a bit at the time. + + + +The Testbox Side +================ + +Each testbox has a unique name corresponding to its DNS zone entry. When booted +a testbox script is started automatically. This script will query the test +manager for orders and execute them. The core order downloads and executes a +test driver with parameters (configuration) from the server. The test driver +does all the necessary work for executing the test. In a typical VirtualBox +test this means picking a build, installing it, configuring VMs, running the +test VMs, collecting the results, submitting them to the server, and finally +cleaning up afterwards. + +The testbox environment which the test drivers are executed in will have a +number of environment variables for determining location of the source images +and other test data, scratch space, test set id, server URL, and so on and so +forth. + +On startup, the testbox script will look for crash dumps and similar on +systems where this is possible. If any sign of a crash is found, it will +put any dumps and reports in the upload directory and inform the test +manager before reporting for duty. In order to generate the proper file +names and report the crash in the right test set as well as prevent +reporting crashes unrelated to automatic testing, the testbox script will +keep information (test set id, ++) in a separate scratch directory +(${TESTBOX_PATH_SCRATCH}/../testbox) and make sure it is synced to the +disk (both files and directories). + +After checking for crashes, the testbox script will clean up any previous test +which might be around. This involves first invoking the test script in cleanup +mode and the wiping the scratch space. + +When reporting for duty the script will submit information about the host: OS +name, OS version, OS bitness, CPU vendor, total number of cores, VT-x support, +AMD-V support, amount of memory, amount of scratch space, and anything else that +can be found useful for scheduling tests or filtering test configurations. + + + +Testbox Script Orders +--------------------- + +The orders are kept in a queue on the server and the testbox script will fetch +them one by one. Orders that cannot be executed at the moment will be masked in +the query from the testbox. + +Execute Test Driver + Downloads and executes the a specified test driver with the given + configuration (arguments). Only one test driver can be executed at a time. + The server can specify more than one ZIP file to be downloaded and unpacked + before executing the test driver. The testbox script may cache these zip + files using http time stamping. + +Abort Test Driver + Aborts the current test driver. This will drop a hint to the driver and give + it 60 seconds to shut down the normal way. If that fails, the testbox script + will kill the driver processes (SIGKILL or equivalent), invoke the + testdriver in cleanup mode, and finally wipe the scratch area. Should either + of the last two steps fail in some way, the testbox will be rebooted. + +Idle + Ask again in X seconds, where X is specified by the server. + +Reboot + Reboot the testbox. If a test driver is current running, an attempt at + aborting it (Abort Test Driver) will be made first. + +Update + Updates the testbox script. The order includes a server relative path to the + new testbox script. This can only be executed when no test driver is + currently being executed. + + +Testbox Environment: Variables +------------------------------ + +COMSPEC + This will be set to C:\Windows\System32\cmd.exe on Windows. + +PATH + This will contain the kBuild binary directory for the host platform. + +SHELL + This will be set to point to kmk_ash(.exe) on all platforms. + +TESTBOX_NAME + The testbox name. + This is not required by the local reporter. + +TESTBOX_PATH_BUILDS + The absolute path to where the build repository can be found. This should be + a read only mount when possible. + +TESTBOX_PATH_RESOURCES + The absolute path to where static test resources like ISOs and VDIs can be + found. The test drivers knows the layout of this. This should be a read only + mount when possible. + +TESTBOX_PATH_SCRATCH + The absolute path to the scratch space. This is the current directory when + starting the test driver. It will be wiped automatically after executing the + test. + (Envisioned as ${TESTBOX_PATH_SCRIPTS}/../scratch and that + ${TESTBOX_PATH_SCRATCH}/ will be automatically wiped by the testbox script.) + +TESTBOX_PATH_SCRIPTS + The absolute path to the test driver and the other files that was unzipped + together with it. This is also where the test-driver-abort file will be put. + (Envisioned as ${TESTBOX_PATH_SCRATCH}/../driver, see above.) + +TESTBOX_PATH_UPLOAD + The absolute path to the upload directory for the testbox. This is for + putting VOBs, PNGs, core dumps, crash dumps, and such on. The files should be + bzipped or zipped if they aren't compress already. The names should contain + the testbox and test set ID. + +TESTBOX_REPORTER + The name of the test reporter back end. If not present, it will default to + the local reporter. + +TESTBOX_TEST_SET_ID + The test set ID if we're running. + This is not required by the local reporter. + +TESTBOX_MANAGER_URL + The URL to the test manager. + This is not required by the local reporter. + +TESTBOX_XYZ + There will probably be some more of these. + + +Testbox Environment: Core Utilities +----------------------------------- + +The testbox will not provide the typical unix /bin and /usr/bin utilities. In +other words, cygwin will not be used on Windows! + +The testbox will provide the unixy utilities that ships with kBuild and possibly +some additional ones from tools/*.*/bin in the VirtualBox tree (wget, unzip, +zip, and so on). The test drivers will avoid invoking any of these utilities +directly and instead rely on generic utility methods in the test driver +framework. That way we can more easily reimplement the functionality of the +core utilities and drop the dependency on them. It also allows us to quickly +work around platform specific oddities and bugs. + + +Test Drivers +------------ + +The test drivers are programs that will do the actual testing. In addition to +run under the testbox script, they can be executed in the VirtualBox development +environment. This is important for bug analysis and for simplifying local +testing by the developers before committing changes. It also means the test +drivers can be developed locally in the VirtualBox development environment. + +The main difference between executing a driver under the testbox script and +running it manually is that there is no test manager in the latter case. The +test result reporter will not talk to the server, but report things to a local +log file and/or standard out/err. When invoked manually, all the necessary +arguments will need to be specified by hand of course - it should be possible +to extract them from a test set as well. + +For the early implementation stages, an implementation of the reporter interface +that talks to the tinderbox base test manager will be needed. This will be +dropped later on when a new test manager is ready. + +As hinted at in other sections, there will be a common framework +(libraries/packages/classes) for taking care of the tedious bits that every +test driver needs to do. Sharing code is essential to easing test driver +development as well as reducing their complexity. The framework will contain: + + - A generic way of submitting output. This will be a generic interface with + multiple implementation, the TESTBOX_REPORTER environment variable + will decide which of them to use. The interface will have very specific + methods to allow the reporter to do a best possible job in reporting the + results to the test manager. + + - Helpers for typical tasks, like: + - Copying files. + - Deleting files, directory trees and scratch space. + - Unzipping files. + - Creating ISOs + - And such things. + + - Helpers for installing and uninstalling VirtualBox. + + - Helpers for defining VMs. (The VBox API where available.) + + - Helpers for controlling VMs. (The VBox API where available.) + +The VirtualBox bits will be separate from the more generic ones, simply because +this is cleaner it will allow us to reuse the system for testing other products. + +The framework will be packaged in a zip file other than the test driver so we +don't waste time and space downloading the same common code. + +The test driver will poll for the file +${TESTBOX_PATH_SCRIPTS}/test-driver-abort and abort all testing when it sees it. + +The test driver can be invoked in three modes: execute, help and cleanup. The +default is execute mode, the help shows an configuration summary and the cleanup +is for cleaning up after a reboot or aborted run. The latter is done by the +testbox script on startup and after abort - the driver is expected to clean up +by itself after a normal run. + + + +The Server Side +=============== + +The server side will be implemented using a webserver (apache), a database +(postgres) and cgi scripts (Python). In addition a cron job (Python) running +once a minute will generate static html for frequently used pages and maybe +execute some other tasks for driving the testing forwards. The order queries +from the testbox script is the primary driving force in the system. The total +makes up the test manager. + +The test manager can be split up into three rough parts: + + - Configuration (of tests, testgroups and testboxes). + - Execution (of tests, collecting and organizing the output). + - Analysis (of test output, mostly about presentation). + + +Test Manager: Requirements +========================== + +List of requirements: + + - Two level testing - L1 quick smoke tests and L2 longer tests performed on + builds passing L1. (Klaus (IIRC) meant this could be realized using + test dependency.) + - Black listing builds (by revision or similar) known to be bad. + - Distinguish between build types so we can do a portion of the testing with + strict builds. + - Easy to re-configure build source for testing different branch or for + testing a release candidate. (Directory based is fine.) + - Useful to be able to partition testboxes (run specific builds on some + boxes, let an engineer have a few boxes for a while). + - Interaction with ILOM/...: reset systems. + - Be able to suspend testing on selected testboxes when doing maintenance + (where automatically resuming testing on reboot is undesired) or similar + activity. + - Abort testing on selected testboxes. + - Scheduling of tests requiring more than one testbox. + - Scheduling of tests that cannot be executing concurrently on several + machines because of some global resource like an iSCSI target. + - Jump the scheduling queue. Scheduling of specified test the next time a + testbox is available (optionally specifying which testbox to schedule it + on). + - Configure tests with variable configuration to get better coverage. Two modes: + - TM generates the permutations based on one or more sets of test script arguments. + - Each configuration permutation is specified manually. + - Test specification needs to be flexible (select tests, disable test, test + scheduling (run certain tests nightly), ... ). + - Test scheduling by hour+weekday and by priority. + - Test dependencies (test A depends on test B being successful). + - Historize all configuration data, in particular test configs (permutations + included) and testboxes. + - Test sets has at a minimum a build reference, a testbox reference and a + primary log associated with it. + - Test sets stores further result as a recursive collection of: + - hierarchical subtest name (slash sep) + - test parameters / config + - bool fail/succ + - attributes (typed?) + - test time + - e.g. throughput + - subresults + - log + - screenshots, video,... + - The test sets database structure needs to designed such that data mining + can be done in an efficient manner. + - Presentation/analysis: graphs!, categorize bugs, columns reorganizing + grouped by test (hierarchical), overviews, result for last day. + + + +Test Manager: Configuration +=========================== + + +Testboxes +--------- + +Configuration of testboxes doesn't involve much work normally. A testbox +is added manually to the test manager by entering the DNS entry and/or IP +address (the test manager resolves the missing one when necessary) as well as +the system UUID (when obtainable - should be displayed by the testbox script +installer). Queries from unregistered testboxes will be declined as a kind of +security measure, the incident should be logged in the webserver log if +possible. In later dealings with the client the System UUID will be the key +identifier. It's permittable for the IP address to change when the testbox +isn't online, but not while testing (just imagine live migration tests and +network tests). Ideally, the testboxes should not change IP address. + +The testbox edit function must allow changing the name and system UUID. + +One further idea for the testbox configuration is indicating what they are +capable of to filter out tests and test configurations that won't work on that +testbox. To examplify this take the ACP2 installation test. If the test +manager does not make sure the testbox have VT-x or AMD-v capabilities, the test +is surely going to fail. Other testbox capabilities would be total number of +CPU cores, memory size, scratch space. These testbox capabilities should be +collected automatically on bootup by the testbox script together with OS name, +OS version and OS bitness. + +A final thought, instead of outright declining all requests from new testboxes, +we could record the unregistered testboxes with ip, UUID, name, os info and +capabilities but mark them as inactive. The test operator can then activate +them on an activation page or edit the testbox or something. + + +Testcases +--------- + +We use the term testcase for a test. + + +Testgroups +---------- + +Testcases are organized into groups. A testcase can be member of more than one +group. The testcase gets a priority assigned to it in connection with the +group membership. + +Testgroups are picked up by a testbox partition (aka scheduling group) and a +prioirty, scheduling time restriction and dependencies on other test groups are +associated with the assignment. A testgroup can be used by several testbox +partitions. + +(This used to be called 'testsuites' but was renamed to avoid confusion with +the VBox Test Suite.) + + +Scheduling +---------- + +The initial scheduler will be modelled after what we're doing already on in the +tinderbox driven testing. It's best described as a best effort continuous +integration scheduler. Meaning, it will always use the latest build suitable +for a testcase. It will schedule on a testcase level, using the combined +priority of the testcase in the test group and the test group with the testbox +partition, trying to spread the test case argument variation out accordingly +over the whole scheduilng queue. Which argument variation to start with, is +not undefined (random would be best). + +Later, we may add other schedulers as needed. + + + +The Test Manager Database +========================= + +First a general warning: + + The guys working on this design are not database experts, web + programming experts or similar, rather we are low level guys + who's main job is x86 & AMD64 virtualization. So, please don't + be too hard on us. :-) + + +A logical table layout can be found in TestManagerDatabaseMap.png (created by +Oracle SQL Data Modeler, stored in TestManagerDatabase.dmd). The physical +database layout can be found in TestManagerDatabaseInit.pgsql postgreSQL +script. The script is commented. + + +Data History +------------ + +We need to somehow track configuration changes over time. We also need to +be able to query the exact configuration a test set was run with so we can +understand and make better use of the results. + +There are different techniques for archiving this, one is tuple-versioning +( http://en.wikipedia.org/wiki/Tuple-versioning ), another is log trigger +( http://en.wikipedia.org/wiki/Log_trigger ). We use tuple-versioning in +this database, with 'effective' as start date field name and 'expire' as +the end (exclusive). + +Tuple-versioning has a shortcoming wrt to keys, both primary and foreign. +The primary key of a table employing tuple-versioning is really +'id' + 'valid_period', where the latter is expressed using two fields +([effective...expire-1]). Only, how do you tell the database engine that +it should not allow overlapping valid_periods? Useful suggestions are +welcomed. :-) + +Foreign key references to a table using tuple-versioning is running into +trouble because of the time axis and that to our knowledge foreign keys +must reference exactly one row in the other table. When time is involved +what we wish to tell the database is that at any given time, there actually +is exactly one row we want to match in the other table, only we've no idea +how to express this. So, many foreign keys are not expressed in SQL of this +database. + +In some cases, we extend the tuple-versioning with a generation ID so that +normal foreign key referencing can be used. We only use this for recording +(references in testset) and scheduling (schedqueue), as using it more widely +would force updates (gen_id changes) to propagate into all related tables. + +See also: + - http://en.wikipedia.org/wiki/Slowly_changing_dimension + - http://en.wikipedia.org/wiki/Change_data_capture + - http://en.wikipedia.org/wiki/Temporal_database + + + +Test Manager: Execution +======================= + + + +Test Manager: Scenarios +======================= + + + +#1 - Testbox Signs On (At Bootup) +--------------------------------- + +The testbox supplies a number of inputs when reporting for duty: + - IP address. + - System UUID. + - OS name. + - OS version. + - CPU architecture. + - CPU count (= threads). + - CPU VT-x/AMD-V capability. + - CPU nested paging capability. + - Chipset I/O MMU capability. + - Memory size. + - Scratch size space (for testing). + - Testbox Script revision. + +Results: + - ACK or NACK. + - Testbox ID and name on ACK. + +After receiving a ACK the testbox will ask for work to do, i.e. continue with +scenario #2. In the NACK case, it will sleep for 60 seconds and try again. + + +Actions: + +1. Validate the testbox by looking the UUID up in the TestBoxes table. + If not found, NACK the request. SQL:: + + SELECT idTestBox, sName + FROM TestBoxes + WHERE uuidSystem = :sUuid + AND tsExpire = 'infinity'::timestamp; + +2. Check if any of the information by testbox script has changed. The two + sizes are normalized first, memory size rounded to nearest 4 MB and scratch + space is rounded down to nearest 64 MB. If anything changed, insert a new + row in the testbox table and historize the current one, i.e. set + OLD.tsExpire to NEW.tsEffective and get a new value for NEW.idGenTestBox. + +3. Check with TestBoxStatuses: + a) If there is an row for the testbox in it already clean up change it + to 'idle' state and deal with any open testset like described in + scenario #9. + b) If there is no row, add one with 'idle' state. + +4. ACK the request and pass back the idTestBox. + + +Note! Testbox.enabled is not checked here, that is only relevant when it asks + for a new task (scenario #2 and #5). + +Note! Should the testbox script detect changes in any of the inputs, it should + redo the sign in. + +Note! In scenario #8, the box will not sign on until it has done the reboot and + cleanup reporting! + + +#2 - Testbox Asks For Work To Do +--------------------------------- + + +Inputs: + - The testbox is supplying its IP indirectly. + - The testbox should supply its UUID and ID directly. + +Results: + - IDLE, WAIT, EXEC, REBOOT, UPGRADE, UPGRADE-AND-REBOOT, SPECIAL or DEAD. + +Actions: + +1. Validate the ID and IP by selecting the currently valid testbox row:: + + SELECT idGenTestBox, fEnabled, idSchedGroup, enmPendingCmd + FROM TestBoxes + WHERE id = :id + AND uuidSystem = :sUuid + AND ip = :ip + AND tsExpire = 'infinity'::timestamp; + + If NOT found return DEAD to the testbox client (it will go back to sign on + mode and retry every 60 seconds or so - see scenario #1). + + Note! The WUI will do all necessary clean-ups when deleting a testbox, so + contrary to the initial plans, we don't need to do anything more for + the DEAD status. + +2. Check with TestBoxStatuses (maybe joined with query from 1). + + If enmState is 'gang-gathering': Goto scenario #6 on timeout or pending + 'abort' or 'reboot' command. Otherwise, tell the testbox to WAIT [done]. + + If enmState is 'gang-testing': The gang has been gathered and execution + has been triggered. Goto 5. + + If enmState is not 'idle', change it to 'idle'. + + If idTestSet is not NULL, CALL scenario #9 to it up. + + If there is a pending abort command, remove it. + + If there is a pending command and the old state doesn't indicate that it was + being executed, GOTO scenario #3. + + Note! There should be a TestBoxStatuses row after executing scenario #1, + however should none be found for some funky reason, returning DEAD + will fix the problem (see above) + +3. If the testbox was marked as disabled, respond with an IDLE command to the + testbox [done]. (Note! Must do this after TestBoxStatuses maintenance from + point 2, or abandoned tests won't be cleaned up after a testbox is disabled.) + +4. Consider testcases in the scheduling queue, pick the first one which the + testbox can execute. There is a concurrency issue here, so we put and + exclusive lock on the SchedQueues table while considering its content. + + The cursor we open looks something like this:: + + SELECT idItem, idGenTestCaseArgs, + idTestSetGangLeader, cMissingGangMembers + FROM SchedQueues + WHERE idSchedGroup = :idSchedGroup + AND ( bmHourlySchedule is NULL + OR get_bit(bmHourlySchedule, :iHourOfWeek) = 1 ) --< does this work? + ORDER BY ASC idItem; + + If there no rows are returned (this can happen because no testgroups are + associated with this scheduling group, the scheduling group is disabled, + or because the queue is being regenerated), we will tell the testbox to + IDLE [done]. + + For each returned row we will: + a) Check testcase/group dependencies. + b) Select a build (and default testsuite) satisfying the dependencies. + c) Check the testcase requirements with that build in mind. + d) If idTestSetGangLeader is NULL, try allocate the necessary resources. + e) If it didn't check out, fetch the next row and redo from (a). + f) Tentatively create a new test set row. + g) If not gang scheduling: + - Next state: 'testing' + ElIf we're the last gang participant: + - Set idTestSetGangLeader to NULL. + - Set cMissingGangMembers to 0. + - Next state: 'gang-testing' + ElIf we're the first gang member: + - Set cMissingGangMembers to TestCaseArgs.cGangMembers - 1. + - Set idTestSetGangLeader to our idTestSet. + - Next state: 'gang-gathering' + Else: + - Decrement cMissingGangMembers. + - Next state: 'gang-gathering' + + If we're not gang scheduling OR cMissingGangMembers is 0: + Move the scheduler queue entry to the end of the queue. + + Update our TestBoxStatuses row with the new state and test set. + COMMIT; + +5. If state is 'testing' or 'gang-testing': + EXEC reponse. + + The EXEC response for a gang scheduled testcase includes a number of + extra arguments so that the script knows the position of the testbox + it is running on and of the other members. This means the that the + TestSet.iGangMemberNo is passed using --gang-member-no and the IP + addresses of the all gang members using --gang-ipv4-<memb-no> <ip>. + Else (state is 'gang-gathering'): + WAIT + + + +#3 - Pending Command When Testbox Asks For Work +----------------------------------------------- + +This is a subfunction of scenario #2 and #5. + +As seen in scenario #2, the testbox will send 'abort' commands to /dev/null +when it finds one when not executing a test. This includes when it reports +that the test has completed (no need to abort a completed test, wasting lot +of effort when standing at the finish line). + +The other commands, though, are passed back to the testbox. The testbox +script will respond with an ACK or NACK as it sees fit. If NACKed, the +pending command will be removed (pending_cmd set to none) and that's it. +If ACKed, the state of the testbox will change to that appropriate for the +command and the pending_cmd set to none. Should the testbox script fail to +respond, the command will be repeated the next time it asks for work. + + + +#4 - Testbox Uploads Results During Test +---------------------------------------- + + +TODO + + +#5 - Testbox Completes Test and Asks For Work +--------------------------------------------- + +This is very similar to scenario #2 + +TODO + + +#6 - Gang Gathering Timeout +--------------------------- + +This is a subfunction of scenario #2. + +When gathering a gang of testboxes for a testcase, we do not want to wait +forever and have testboxes doing nothing for hours while waiting for partners. +So, the gathering has a reasonable timeout (imagine something like 20-30 mins). + +Also, we need some way of dealing with 'abort' and 'reboot' commands being +issued while waiting. The easy way out is pretend it's a time out. + +When changing the status to 'gang-timeout' we have to be careful. First of all, +we need to exclusively lock the SchedQueues and TestBoxStatuses (in that order) +and re-query our status. If it changed redo the checks in scenario #2 point 2. + +If we still want to timeout/abort, change the state from 'gang-gathering' to +'gang-gathering-timedout' on all the gang members that has gathered so far. +Then reset the scheduling queue record and move it to the end of the queue. + + +When acting on 'gang-timeout' the TM will fail the testset in a manner similar +to scenario #9. No need to repeat that. + + + +#7 - Gang Cleanup +----------------- + +When a testbox completes a gang scheduled test, we will have to serialize +resource cleanup (both globally and on testboxes) as they stop. More details +can be found in the documentation of 'gang-cleanup'. + +So, the transition from 'gang-testing' is always to 'gang-cleanup'. When we +can safely leave 'gang-cleanup' is decided by the query:: + + SELECT COUNT(*) + FROM TestBoxStatuses, + TestSets + WHERE TestSets.idTestSetGangLeader = :idTestSetGangLeader + AND TestSets.idTestBox = TestBoxStatuses.idTestBox + AND TestBoxStatuses.enmState = 'gang-running'::TestBoxState_T; + +As long as there are testboxes still running, we stay in the 'gang-cleanup' +state. Once there are none, we continue closing the testset and such. + + + +#8 - Testbox Reports A Crash During Test Execution +-------------------------------------------------- + +TODO + + +#9 - Cleaning Up Abandoned Testcase +----------------------------------- + +This is a subfunction of scenario #1 and #2. The actions taken are the same in +both situations. The precondition for taking this path is that the row in the +testboxstatus table is referring to a testset (i.e. testset_id is not NULL). + + +Actions: + +1. If the testset is incomplete, we need to completed: + a) Add a message to the root TestResults row, creating one if necessary, + that explains that the test was abandoned. This is done + by inserting/finding the string into/in TestResultStrTab and adding + a row to TestResultMsgs with idStrMsg set to that string id and + enmLevel set to 'failure'. + b) Mark the testset as failed. + +2. Free any global resources referenced by the test set. This is done by + deleting all rows in GlobalResourceStatuses matching the testbox id. + +3. Set the idTestSet to NULL in the TestBoxStatuses row. + + + +#10 - Cleaning Up a Disabled/Dead TestBox +----------------------------------------- + +The UI needs to be able to clean up the remains of a testbox which for some +reason is out of action. Normal cleaning up of abandoned testcases requires +that the testbox signs on or asks for work, but if the testbox is dead or +in some way indisposed, it won't be doing any of that. So, the testbox +sheriff needs to have a way of cleaning up after it. + +It's basically a manual scenario #9 but with some safe guards, like checking +that the box hasn't been active for the last 1-2 mins (max idle/wait time * 2). + + +Note! When disabling a box that still executing the testbox script, this + cleanup isn't necessary as it will happen automatically. Also, it's + probably desirable that the testbox finishes what ever it is doing first + before going dormant. + + + +Test Manager: Analysis +======================= + +One of the testbox sheriff's tasks is to try figure out the reason why something +failed. The test manager will provide facilities for doing so from very early +in it's implementation. + + +We need to work out some useful status reports for the early implementation. +Later there will be more advanced analysis tools, where for instance we can +create graphs from selected test result values or test execution times. + + + +Implementation Plan +=================== + +This has changed for various reasons. The current plan is to implement the +infrastructure (TM & testbox script) first and do a small deployment with the +2-5 test drivers in the Testsuite as basis. Once the bugs are worked out, we +will convert the rest of the tests and start adding new ones. + +We just need to finally get this done, no point in doing it piecemeal by now! + + +Test Manager Implementation Sub-Tasks +------------------------------------- + +The implementation of the test manager and adjusting/completing of the testbox +script and the test drivers are tasks which can be done by more than one +person. Splitting up the TM implementation into smaller tasks should allow +parallel development of different tasks and get us working code sooner. + + +Milestone #1 +------------ + +The goal is to getting the fundamental testmanager engine implemented, debugged +and working. With the exception of testboxes, the configuration will be done +via SQL inserts. + +Tasks in somewhat prioritized order: + + - Kick off test manager. It will live in testmanager/. Salvage as much as + possible from att/testserv. Create basic source and file layout. + + - Adjust the testbox script, part one. There currently is a testbox script + in att/testbox, this shall be moved up into testboxscript/. The script + needs to be adjusted according to the specification layed down earlier + in this document. Installers or installation scripts for all relevant + host OSes are required. Left for part two is result reporting beyond the + primary log. This task must be 100% feature complete, on all host OSes, + there is no room for FIXME, XXX or @todo here. + + - Implement the schedule queue generator. + + - Implement the testbox dispatcher in TM. Support all the testbox script + responses implemented above, including upgrading the testbox script. + + - Implement simple testbox management page. + + - Implement some basic activity and result reports so that we can see + what's going on. + + - Create a testmanager / testbox test setup. This lives in selftest/. + + 1. Set up something that runs, no fiddly bits. Debug till it works. + 2. Create a setup that tests testgroup dependencies, i.e. real tests + depending on smoke tests. + 3. Create a setup that exercises testcase dependency. + 4. Create a setup that exercises global resource allocation. + 5. Create a setup that exercises gang scheduling. + + - Check that all features work. + + +Milestone #2 +------------ + +The goal is getting to VBox testing. + +Tasks in somewhat prioritized order: + + - Implement full result reporting in the testbox script and testbox driver. + A testbox script specific reporter needs to be implemented for the + testdriver framework. The testbox script needs to forward the results to + the test manager, or alternatively the testdriver report can talk + directly to the TM. + + - Implement the test manager side of the test result reporting. + + - Extend the selftest with some setup that report all kinds of test + results. + + - Implement script/whatever feeding builds to the test manager from the + tinderboxes. + + - The toplevel test driver is a VBox thing that must be derived from the + base TestDriver class or maybe the VBox one. It should move from + toptestdriver to testdriver and be renamed to vboxtltd or smth. + + - Create a vbox testdriver that boots the t-xppro VM once and that's it. + + - Create a selftest setup which tests booting t-xppro taking builds from + the tinderbox. + + +Milestone #3 +------------ + +The goal for this milestone is configuration and converting current testcases, +the result will be the a minimal test deployment (4-5 new testboxes). + +Tasks in somewhat prioritized order: + + - Implement testcase configuration. + + - Implement testgroup configuration. + + - Implement build source configuration. + + - Implement scheduling group configuration. + + - Implement global resource configuration. + + - Re-visit the testbox configuration. + + - Black listing of builds. + + - Implement simple failure analysis and reporting. + + - Implement the initial smoke tests modelled on the current smoke tests. + + - Implement installation tests for Windows guests. + + - Implement installation tests for Linux guests. + + - Implement installation tests for Solaris guest. + + - Implement installation tests for OS/2 guest. + + - Set up a small test deployment. + + +Further work +------------ + +After milestone #3 has been reached and issues found by the other team members +have been addressed, we will probably go for full deployment. + +Beyond this point we will need to improve reporting and analysis. There may be +configuration aspects needing reporting as well. + +Once deployed, a golden rule will be that all new features shall have test +coverage. Preferably, implemented by someone else and prior to the feature +implementation. + + + + +Discussion Logs +=============== + +2009-07-21,22,23 Various Discussions with Michal and/or Klaus +------------------------------------------------------------- + +- Scheduling of tests requiring more than one testbox. +- Scheduling of tests that cannot be executing concurrently on several machines + because of some global resource like an iSCSI target. +- Manually create the test config permutations instead of having the test + manager create all possible ones and wasting time. +- Distinguish between built types so we can run smoke tests on strick builds as + well as release ones. + + +2009-07-20 Brief Discussion with Michal +---------------------------------------- + +- Installer for the testbox script to make bringing up a new testbox even + smoother. + + +2009-07-16 Raw Input +-------------------- + +- test set. recursive collection of: + - hierachical subtest name (slash sep) + - test parameters / config + - bool fail/succ + - attributes (typed?) + - test time + - e.g. throughput + - subresults + - log + - screenshots,.... + +- client package (zip) dl from server (maybe client caching) + + +- thoughts on bits to do at once. + - We *really* need the basic bits ASAP. + - client -> support for test driver + - server -> controls configs + - cleanup on both sides + + +2009-07-15 Raw Input +-------------------- + +- testing should start automatically +- switching to branch too tedious +- useful to be able to partition testboxes (run specific builds on some boxes, let an engineer have a few boxes for a while). +- test specification needs to be more flexible (select tests, disable test, test scheduling (run certain tests nightly), ... ) +- testcase dependencies (blacklisting builds, run smoketests on box A before long tests on box B, ...) +- more testing flexibility, more test than just install/moke. For instance unit tests, benchmarks, ... +- presentation/analysis: graphs!, categorize bugs, columns reorganizing grouped by test (hierarchical), overviews, result for last day. +- testcase specificion, variables (e.g. I/O-APIC, SMP, HWVIRT, SATA...) as sub-tests +- interation with ILOM/...: reset systems +- Changes needs LDAP authentication +- historize all configuration w/ name +- ability to run testcase locally (provided the VDI/ISO/whatever extra requirements can be met). + + +----- + +.. [1] no such footnote + +----- + +:Status: $Id: AutomaticTestingRevamp.txt $ +:Copyright: Copyright (C) 2010-2020 Oracle Corporation. diff --git a/src/VBox/ValidationKit/docs/Makefile.kmk b/src/VBox/ValidationKit/docs/Makefile.kmk new file mode 100644 index 00000000..5ca83e50 --- /dev/null +++ b/src/VBox/ValidationKit/docs/Makefile.kmk @@ -0,0 +1,69 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Makefile for generating .html from .txt. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +DEPTH = ../../../.. +include $(KBUILD_PATH)/header.kmk + +# Figure out where rst2html.py is. +ifndef VBOX_RST2HTML + VBOX_RST2HTML := $(firstword $(which $(foreach pyver, 3.2 3.1 3.0 2.8 2.7 2.6 2.5 2.4 ,rst2html-$(pyver).py) ) ) + ifeq ($(VBOX_RST2HTML),) + if $(KBUILD_HOST) == "win" && $(VBOX_BLD_PYTHON) != "" && $(dir $(VBOX_BLD_PYTHON)) != "./" + VBOX_RST2HTML := $(dir $(VBOX_BLD_PYTHON))Scripts/rst2html.py + else + VBOX_RST2HTML := rst2html.py + endif + endif + if1of ($(KBUILD_HOST),win) + VBOX_RST2HTML := $(VBOX_BLD_PYTHON) $(VBOX_RST2HTML) + endif +endif + +GENERATED_FILES = \ + AutomaticTestingRevamp.html \ + VBoxValidationKitReadMe.html \ + VBoxAudioValidationKitReadMe.html \ + TestBoxImaging.html + +all: $(GENERATED_FILES) + +$(foreach html,$(GENERATED_FILES) \ +,$(eval $(html): $(basename $(html)).txt ; $$(REDIRECT) -E LC_ALL=C -- $$(VBOX_RST2HTML) --no-generator $$< $$@)) + +$(foreach html,$(GENERATED_FILES), $(eval $(basename $(html)).o:: $(html))) # editor compile aliases + +clean: + kmk_builtin_rm -f -- $(GENERATED_FILES) diff --git a/src/VBox/ValidationKit/docs/TestBoxImaging.html b/src/VBox/ValidationKit/docs/TestBoxImaging.html new file mode 100644 index 00000000..e8635641 --- /dev/null +++ b/src/VBox/ValidationKit/docs/TestBoxImaging.html @@ -0,0 +1,758 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<meta name="generator" content="Docutils 0.18: http://docutils.sourceforge.net/" /> +<title>TestBoxImaging.txt</title> +<style type="text/css"> + +/* +:Author: David Goodger (goodger@python.org) +:Id: $Id: TestBoxImaging.html $ +:Copyright: This stylesheet has been placed in the public domain. + +Default cascading style sheet for the HTML output of Docutils. + +See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to +customize this style sheet. +*/ + +/* used to remove borders from tables and images */ +.borderless, table.borderless td, table.borderless th { + border: 0 } + +table.borderless td, table.borderless th { + /* Override padding for "table.docutils td" with "! important". + The right padding separates the table cells. */ + padding: 0 0.5em 0 0 ! important } + +.first { + /* Override more specific margin styles with "! important". */ + margin-top: 0 ! important } + +.last, .with-subtitle { + margin-bottom: 0 ! important } + +.hidden { + display: none } + +.subscript { + vertical-align: sub; + font-size: smaller } + +.superscript { + vertical-align: super; + font-size: smaller } + +a.toc-backref { + text-decoration: none ; + color: black } + +blockquote.epigraph { + margin: 2em 5em ; } + +dl.docutils dd { + margin-bottom: 0.5em } + +object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { + overflow: hidden; +} + +/* Uncomment (and remove this text!) to get bold-faced definition list terms +dl.docutils dt { + font-weight: bold } +*/ + +div.abstract { + margin: 2em 5em } + +div.abstract p.topic-title { + font-weight: bold ; + text-align: center } + +div.admonition, div.attention, div.caution, div.danger, div.error, +div.hint, div.important, div.note, div.tip, div.warning { + margin: 2em ; + border: medium outset ; + padding: 1em } + +div.admonition p.admonition-title, div.hint p.admonition-title, +div.important p.admonition-title, div.note p.admonition-title, +div.tip p.admonition-title { + font-weight: bold ; + font-family: sans-serif } + +div.attention p.admonition-title, div.caution p.admonition-title, +div.danger p.admonition-title, div.error p.admonition-title, +div.warning p.admonition-title, .code .error { + color: red ; + font-weight: bold ; + font-family: sans-serif } + +/* Uncomment (and remove this text!) to get reduced vertical space in + compound paragraphs. +div.compound .compound-first, div.compound .compound-middle { + margin-bottom: 0.5em } + +div.compound .compound-last, div.compound .compound-middle { + margin-top: 0.5em } +*/ + +div.dedication { + margin: 2em 5em ; + text-align: center ; + font-style: italic } + +div.dedication p.topic-title { + font-weight: bold ; + font-style: normal } + +div.figure { + margin-left: 2em ; + margin-right: 2em } + +div.footer, div.header { + clear: both; + font-size: smaller } + +div.line-block { + display: block ; + margin-top: 1em ; + margin-bottom: 1em } + +div.line-block div.line-block { + margin-top: 0 ; + margin-bottom: 0 ; + margin-left: 1.5em } + +div.sidebar { + margin: 0 0 0.5em 1em ; + border: medium outset ; + padding: 1em ; + background-color: #ffffee ; + width: 40% ; + float: right ; + clear: right } + +div.sidebar p.rubric { + font-family: sans-serif ; + font-size: medium } + +div.system-messages { + margin: 5em } + +div.system-messages h1 { + color: red } + +div.system-message { + border: medium outset ; + padding: 1em } + +div.system-message p.system-message-title { + color: red ; + font-weight: bold } + +div.topic { + margin: 2em } + +h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, +h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { + margin-top: 0.4em } + +h1.title { + text-align: center } + +h2.subtitle { + text-align: center } + +hr.docutils { + width: 75% } + +img.align-left, .figure.align-left, object.align-left, table.align-left { + clear: left ; + float: left ; + margin-right: 1em } + +img.align-right, .figure.align-right, object.align-right, table.align-right { + clear: right ; + float: right ; + margin-left: 1em } + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left } + +.align-center { + clear: both ; + text-align: center } + +.align-right { + text-align: right } + +/* reset inner alignment in figures */ +div.align-right { + text-align: inherit } + +/* div.align-center * { */ +/* text-align: left } */ + +.align-top { + vertical-align: top } + +.align-middle { + vertical-align: middle } + +.align-bottom { + vertical-align: bottom } + +ol.simple, ul.simple { + margin-bottom: 1em } + +ol.arabic { + list-style: decimal } + +ol.loweralpha { + list-style: lower-alpha } + +ol.upperalpha { + list-style: upper-alpha } + +ol.lowerroman { + list-style: lower-roman } + +ol.upperroman { + list-style: upper-roman } + +p.attribution { + text-align: right ; + margin-left: 50% } + +p.caption { + font-style: italic } + +p.credits { + font-style: italic ; + font-size: smaller } + +p.label { + white-space: nowrap } + +p.rubric { + font-weight: bold ; + font-size: larger ; + color: maroon ; + text-align: center } + +p.sidebar-title { + font-family: sans-serif ; + font-weight: bold ; + font-size: larger } + +p.sidebar-subtitle { + font-family: sans-serif ; + font-weight: bold } + +p.topic-title { + font-weight: bold } + +pre.address { + margin-bottom: 0 ; + margin-top: 0 ; + font: inherit } + +pre.literal-block, pre.doctest-block, pre.math, pre.code { + margin-left: 2em ; + margin-right: 2em } + +pre.code .ln { color: grey; } /* line numbers */ +pre.code, code { background-color: #eeeeee } +pre.code .comment, code .comment { color: #5C6576 } +pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } +pre.code .literal.string, code .literal.string { color: #0C5404 } +pre.code .name.builtin, code .name.builtin { color: #352B84 } +pre.code .deleted, code .deleted { background-color: #DEB0A1} +pre.code .inserted, code .inserted { background-color: #A3D289} + +span.classifier { + font-family: sans-serif ; + font-style: oblique } + +span.classifier-delimiter { + font-family: sans-serif ; + font-weight: bold } + +span.interpreted { + font-family: sans-serif } + +span.option { + white-space: nowrap } + +span.pre { + white-space: pre } + +span.problematic { + color: red } + +span.section-subtitle { + /* font-size relative to parent (h1..h6 element) */ + font-size: 80% } + +table.citation { + border-left: solid 1px gray; + margin-left: 1px } + +table.docinfo { + margin: 2em 4em } + +table.docutils { + margin-top: 0.5em ; + margin-bottom: 0.5em } + +table.footnote { + border-left: solid 1px black; + margin-left: 1px } + +table.docutils td, table.docutils th, +table.docinfo td, table.docinfo th { + padding-left: 0.5em ; + padding-right: 0.5em ; + vertical-align: top } + +table.docutils th.field-name, table.docinfo th.docinfo-name { + font-weight: bold ; + text-align: left ; + white-space: nowrap ; + padding-left: 0 } + +/* "booktabs" style (no vertical lines) */ +table.docutils.booktabs { + border: 0px; + border-top: 2px solid; + border-bottom: 2px solid; + border-collapse: collapse; +} +table.docutils.booktabs * { + border: 0px; +} +table.docutils.booktabs th { + border-bottom: thin solid; + text-align: left; +} + +h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, +h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { + font-size: 100% } + +ul.auto-toc { + list-style-type: none } + +</style> +</head> +<body> +<div class="document"> + + +<div class="section" id="testbox-imaging-backup-restore"> +<h1>Testbox Imaging (Backup / Restore)</h1> +<div class="section" id="introduction"> +<h2>Introduction</h2> +<p>This document is explores deploying a very simple drive imaging solution to help +avoid needing to manually reinstall testboxes when a disk goes bust or the OS +install seems to be corrupted.</p> +</div> +</div> +<div class="section" id="definitions-glossary"> +<h1>Definitions / Glossary</h1> +<p>See AutomaticTestingRevamp.txt.</p> +</div> +<div class="section" id="objectives"> +<h1>Objectives</h1> +<blockquote> +<ul class="simple"> +<li>Off site, no admin interaction (no need for ILOM or similar).</li> +<li>OS independent.</li> +<li>Space and bandwidth efficient.</li> +<li>As automatic as possible.</li> +<li>Logging.</li> +</ul> +</blockquote> +</div> +<div class="section" id="overview-of-the-solution"> +<h1>Overview of the Solution</h1> +<p>Here is a brief summary:</p> +<blockquote> +<ul class="simple"> +<li>Always boot testboxes via PXE using PXELINUX.</li> +<li>Default configuration is local boot (hard disk / SSD)</li> +<li>Restore/backup action triggered by machine specific PXE config.</li> +<li>Boots special debian maintenance install off NFS.</li> +<li>A maintenance service (systemd style) does the work.</li> +<li>The service reads action from TFTP location and performs it.</li> +<li>When done the service removes the TFTP machine specific config +and reboots the system.</li> +</ul> +</blockquote> +<dl class="docutils"> +<dt>Maintenance actions are:</dt> +<dd><ul class="first last simple"> +<li>backup</li> +<li>backup-again</li> +<li>restore</li> +<li>refresh-info</li> +<li>rescue</li> +</ul> +</dd> +</dl> +<p>Possible modifier that indicates a subset of disk on testboxes with other OSes +installed. Support for partition level backup/restore is not explored here.</p> +<div class="section" id="how-to-use"> +<h2>How to use</h2> +<p>To perform one of the above maintenance actions on a testbox, run the +<tt class="docutils literal"><span class="pre">testbox-pxe-conf.sh</span></tt> script:</p> +<pre class="literal-block"> +/mnt/testbox-tftp/pxeclient.cfg/testbox-pxe-conf.sh 10.165.98.220 rescue +</pre> +<p>Then trigger a reboot. The box will then boot the NFS rooted debian image and +execute the maintenance action. On success, it will remove the testbox hex-IP +config file and reboot again.</p> +</div> +</div> +<div class="section" id="storage-server"> +<h1>Storage Server</h1> +<p>The storage server will have three areas used here. Using NFS for all three +avoids extra work getting CIFS sharing right too (NFS is already a pain).</p> +<blockquote> +<ol class="arabic simple"> +<li>/export/testbox-tftp - TFTP config area. Read-write.</li> +<li>/export/testbox-backup - Images and logs. Read-write.</li> +<li>/export/testbox-nfsroot - Custom debian. Read-only, no root squash.</li> +</ol> +</blockquote> +</div> +<div class="section" id="tftp-export-testbox-tftp"> +<h1>TFTP (/export/testbox-tftp)</h1> +<p>The testbox-tftp share needs to be writable, root squashing is okay.</p> +<p>We need files from both PXELINUX and SYSLINUX to make this work now. On a +debian system, the <tt class="docutils literal">pxelinux</tt> and <tt class="docutils literal">syslinux</tt> packages needs to be +installed. We actually do this further down when setting up the nfsroot, so +it's possible to get them from there by postponing this step a little. On +debian 8.6.0 the PXELINUX files are found in <tt class="docutils literal">/usr/lib/PXELINUX</tt> and the +SYSLINUX ones in <tt class="docutils literal">/usr/lib/syslinux</tt>.</p> +<p>The initial PXE image as well as associated modules comes in three variants, +BIOS, 32-bit EFI and 64-bit EFI. We'll only need the BIOS one for now. +Perform the following copy operations:</p> +<pre class="literal-block"> +cp /usr/lib/PXELINUX/pxelinux.0 /mnt/testbox-tftp/ +cp /usr/lib/syslinux/modules/*/ldlinux.* /mnt/testbox-tftp/ +cp -R /usr/lib/syslinux/modules/bios /mnt/testbox-tftp/ +cp -R /usr/lib/syslinux/modules/efi32 /mnt/testbox-tftp/ +cp -R /usr/lib/syslinux/modules/efi64 /mnt/testbox-tftp/ +</pre> +<p>For simplicity, all the testboxes boot using good old fashioned BIOS, no EFI. +However, it doesn't really hurt to be prepared.</p> +<p>The PXELINUX related files goes in the root of the testbox-tftp share. (As +mentioned further down, these can be installed on a debian system by running +<tt class="docutils literal"><span class="pre">apt-get</span> install pxelinux syslinux</tt>.) We need the <tt class="docutils literal">*pxelinux.0</tt> files +typically found in <tt class="docutils literal">/usr/lib/PXELINUX/</tt> on debian systems (recent ones +anyway). It is possible we may need one ore more fo the modules <a class="footnote-reference" href="#footnote-1" id="footnote-reference-1">[1]</a> that +ships with PXELINUX/SYSLINUX, so do copy <tt class="docutils literal">/usr/lib/syslinux/modules</tt> to +<tt class="docutils literal"><span class="pre">testbox-tftp/modules</span></tt> as well.</p> +<p>The directory layout related to the configuration files is dictated by the +PXELINUX configuration file searching algorithm <a class="footnote-reference" href="#footnote-2" id="footnote-reference-2">[2]</a>. Create a subdirectory +<tt class="docutils literal">pxelinux.cfg/</tt> under <tt class="docutils literal"><span class="pre">testbox-tftp</span></tt> and create the world readable file +<tt class="docutils literal">default</tt> with the following content:</p> +<pre class="literal-block"> +PATH bios +DEFAULT local-boot +LABEL local-boot +LOCALBOOT +</pre> +<p>This will make the default behavior to boot the local disk system.</p> +<p>Copy the <tt class="docutils literal"><span class="pre">testbox-pxe-conf.sh</span></tt> script file found in the same directory as +this document to <tt class="docutils literal"><span class="pre">/mnt/testbox-tftp/pxelinux.cfg/</span></tt>. Edit the copy to correct +the IP addresses near the top, as well as any linux, TFTP and PXE details near +the bottom of the file. This script will generate the PXE configuration file +when performing maintenance on a testbox.</p> +</div> +<div class="section" id="images-and-logs-export-testbox-backup"> +<h1>Images and logs (/export/testbox-backup)</h1> +<p>The testbox-backup share needs to be writable, root squashing is okay.</p> +<p>In the root there must be a file <tt class="docutils literal"><span class="pre">testbox-backup</span></tt> so we can easily tell +whether we've actually mounted the share or are just staring at an empty mount +point directory.</p> +<p>The <tt class="docutils literal"><span class="pre">testbox-maintenance.sh</span></tt> script maintains a global log in the root +directory that's called <tt class="docutils literal">maintenance.log</tt>. Errors will be logged there as +well as a ping and the action.</p> +<p>We use a directory layout based on dotted decimal IP addresses here, so for a +server with the IP 10.40.41.42 all its file will be under <tt class="docutils literal">10.40.41.42/</tt>:</p> +<dl class="docutils"> +<dt><tt class="docutils literal"><hostname></tt></dt> +<dd>The name of the testbox (empty file). Help finding a testbox by name.</dd> +<dt><tt class="docutils literal"><span class="pre">testbox-info.txt</span></tt></dt> +<dd>Information about the testbox. Starting off with the name, decimal IP, +PXELINUX style hexadecimal IP, and more.</dd> +<dt><tt class="docutils literal">maintenance.log</tt></dt> +<dd>Maintenance log file recording what the maintenance service does.</dd> +<dt><tt class="docutils literal"><span class="pre">disk-devices.lst</span></tt></dt> +<dd>Optional list of disk devices to consider backuping up or restoring. This is +intended for testboxes with additional disks that are used for other purposes +and should touched.</dd> +<dt><tt class="docutils literal">sda.raw.gz</tt></dt> +<dd>The gzipped raw copy of the sda device of the testbox.</dd> +<dt><tt class="docutils literal"><span class="pre">sd[bcdefgh].raw.gz</span></tt></dt> +<dd>The gzipped raw copy sdb, sdc, sde, sdf, sdg, sdh, etc if any of them exists +and are disks/SSDs.</dd> +<dt>Note! If it turns out we can be certain to get a valid host name, we might just</dt> +<dd>switch to use the hostname as the directory name instead of the IP.</dd> +</dl> +</div> +<div class="section" id="debian-nfs-root-export-testbox-nfsroot"> +<h1>Debian NFS root (/export/testbox-nfsroot)</h1> +<p>The testbox-nfsroot share should be read-only and must <strong>not</strong> have root +squashing enabled. Also, make sure setting the set-uid-bit is allowed by the +server, or <tt class="docutils literal">su` and ``sudo</tt> won't work</p> +<p>There are several ways of creating a debian nfsroot, but since we've got a +tool like VirtualBox around we've just installed it in a VM, prepared it, +and copied it onto the NFS server share.</p> +<p>As of writing debian 8.6.0 is current, so a minimal 64-bit install of it was +done in a VM. After installation the following modifications was done:</p> +<blockquote> +<ul> +<li><p class="first"><tt class="docutils literal"><span class="pre">apt-get</span> install pxelinux syslinux <span class="pre">initramfs-tools</span> zip gddrescue sudo joe</tt> +and optionally <tt class="docutils literal"><span class="pre">apt-get</span> install smbclient <span class="pre">cifs-utils</span></tt>.</p> +</li> +<li><p class="first"><tt class="docutils literal">/etc/default/grub</tt> was modified to set <tt class="docutils literal">GRUB_CMDLINE_LINUX_DEFAULT</tt> to +<tt class="docutils literal">""</tt> instead of <tt class="docutils literal">"quiet"</tt>. This allows us to see messages during boot +and perhaps spot why something doesn't work on a testbox. Regenerate the +grub configuration file by running <tt class="docutils literal"><span class="pre">update-grub</span></tt> afterwards.</p> +</li> +<li><p class="first"><tt class="docutils literal">/etc/sudoers</tt> was modified to allow the <tt class="docutils literal">vbox</tt> user use sudo without +requring any password.</p> +</li> +<li><p class="first">Create the directory <tt class="docutils literal">/etc/systemd/system/getty@tty1.service.d</tt> and create +the file <tt class="docutils literal">noclear.conf</tt> in it with the following content:</p> +<pre class="literal-block"> +[Service] +TTYVTDisallocate=no +</pre> +<p>This stops getty from clearing VT1 and let us see the tail of the boot up +messages, which includes messages from the testbox-maintenance service.</p> +</li> +<li><p class="first">Mount the testbox-nfsroot under <tt class="docutils literal">/mnt/</tt> with write privileges. (The write +privileges are temporary - don't forget to remove them later on.):</p> +<pre class="literal-block"> +mount -t nfs myserver.com:/export/testbox-nfsroot +</pre> +<p>Note! Adding <tt class="docutils literal"><span class="pre">-o</span> nfsvers=3</tt> may help with some NTFv4 servers.</p> +</li> +<li><p class="first">Copy the debian root and dev file system onto nfsroot. If you have ssh +access to the NFS server, the quickest way to do it is to use <tt class="docutils literal">tar</tt>:</p> +<pre class="literal-block"> +tar -cz --one-file-system -f /mnt/testbox-maintenance-nfsroot.tar.gz . dev/ +</pre> +<p>An alternative is <tt class="docutils literal">cp <span class="pre">-ax</span> . /mnt/. && cp <span class="pre">-ax</span> dev/. /mnt/dev/.</tt> but this +is quite a bit slower, obviously.</p> +</li> +<li><p class="first">Edit <tt class="docutils literal">/etc/ssh/sshd_config</tt> setting <tt class="docutils literal">PermitRootLogin</tt> to <tt class="docutils literal">yes</tt> so we can ssh +in as root later on.</p> +</li> +<li><p class="first">chroot into the nfsroot: <tt class="docutils literal">chroot /mnt/</tt></p> +<blockquote> +<ul> +<li><p class="first"><tt class="docutils literal">mount <span class="pre">-o</span> proc proc /proc</tt></p> +</li> +<li><p class="first"><tt class="docutils literal">mount <span class="pre">-o</span> sysfs sysfs /sys</tt></p> +</li> +<li><p class="first"><tt class="docutils literal">mkdir <span class="pre">/mnt/testbox-tftp</span> <span class="pre">/mnt/testbox-backup</span></tt></p> +</li> +<li><p class="first">Recreate <tt class="docutils literal">/etc/fstab</tt> with:</p> +<pre class="literal-block"> +proc /proc proc defaults 0 0 +/dev/nfs / nfs defaults 1 1 +10.42.1.1:/export/testbox-tftp /mnt/testbox-tftp nfs tcp,nfsvers=3,noauto 2 2 +10.42.1.1:/export/testbox-backup /mnt/testbox-backup nfs tcp,nfsvers=3,noauto 3 3 +</pre> +<p>We use NFS version 3 as that works better for our NFS server and client, +remove if not necessary. The <tt class="docutils literal">noauto</tt> option is to work around mount +trouble during early bootup on some of our boxes.</p> +</li> +<li><p class="first">Do <tt class="docutils literal">mount <span class="pre">/mnt/testbox-tftp</span> && mount <span class="pre">/mnt/testbox-backup</span></tt> to mount the +two shares. This may be a good time to execute the instructions in the +sections above relating to these two shares.</p> +</li> +<li><p class="first">Edit <tt class="docutils literal"><span class="pre">/etc/initramfs-tools/initramfs.conf</span></tt> and change the <tt class="docutils literal">MODULES</tt> +value from <tt class="docutils literal">most</tt> to <tt class="docutils literal">netboot</tt>.</p> +</li> +<li><p class="first">Append <tt class="docutils literal">aufs</tt> to <tt class="docutils literal"><span class="pre">/etc/initramfs-tools/modules</span></tt>. The advanced +multi-layered unification filesystem (aufs) enables us to use a +read-only NFS root. <a class="footnote-reference" href="#footnote-3" id="footnote-reference-3">[3]</a> <a class="footnote-reference" href="#footnote-4" id="footnote-reference-4">[4]</a> <a class="footnote-reference" href="#footnote-5" id="footnote-reference-5">[5]</a></p> +</li> +<li><p class="first">Create <tt class="docutils literal"><span class="pre">/etc/initramfs-tools/scripts/init-bottom/00_aufs_init</span></tt> as +an executable file with the following content:</p> +<pre class="literal-block"> +#!/bin/sh +# Don't run during update-initramfs: +case "$1" in + prereqs) + exit 0; + ;; +esac + +modprobe aufs +mkdir -p /ro /rw /aufs +mount -t tmpfs tmpfs /rw -o noatime,mode=0755 +mount --move $rootmnt /ro +mount -t aufs aufs /aufs -o noatime,dirs=/rw:/ro=ro +mkdir -p /aufs/rw /aufs/ro +mount --move /ro /aufs/ro +mount --move /rw /aufs/rw +mount --move /aufs /root +exit 0 +</pre> +</li> +<li><p class="first">Update the init ramdisk: <tt class="docutils literal"><span class="pre">update-initramfs</span> <span class="pre">-u</span> <span class="pre">-k</span> all</tt></p> +<dl class="docutils"> +<dt>Note! It may be necessary to do <tt class="docutils literal">mount <span class="pre">-t</span> tmpfs tmpfs /var/tmp</tt> to help</dt> +<dd><p class="first last">this operation succeed.</p> +</dd> +</dl> +</li> +<li><p class="first">Copy <tt class="docutils literal">/boot</tt> to <tt class="docutils literal"><span class="pre">/mnt/testbox-tftp/maintenance-boot/</span></tt>.</p> +</li> +<li><p class="first">Copy the <tt class="docutils literal"><span class="pre">testbox-maintenance.sh</span></tt> file found in the same directory as this +document to <tt class="docutils literal">/root/scripts/</tt> (need to create the dir) and make it +executable.</p> +</li> +<li><p class="first">Create the systemd service file for the maintenance service as +<tt class="docutils literal"><span class="pre">/etc/systemd/system/testbox-maintenance.service</span></tt> with the content:</p> +<pre class="literal-block"> +[Unit] +Description=Testbox Maintenance +After=network.target +Before=getty@tty1.service + +[Service] +Type=oneshot +RemainAfterExit=True +ExecStart=/root/scripts/testbox-maintenance.sh +ExecStartPre=/bin/echo -e \033%G +ExecReload=/bin/kill -HUP $MAINPID +WorkingDirectory=/tmp +Environment=TERM=xterm +StandardOutput=journal+console + +[Install] +WantedBy=multi-user.target +</pre> +</li> +<li><p class="first">Enable our service: <tt class="docutils literal">systemctl enable <span class="pre">/etc/systemd/system/testbox-maintenance.service</span></tt></p> +</li> +<li><p class="first">xxxx ... more ???</p> +</li> +<li><p class="first">Before leaving the chroot, do <tt class="docutils literal">mount /proc /sys <span class="pre">/mnt/testbox-*</span></tt>.</p> +</li> +</ul> +</blockquote> +</li> +<li><p class="first">Testing the setup from a VM is kind of useful (if the nfs server can be +convinced to accept root nfs mounts from non-privileged clinet ports):</p> +<blockquote> +<ul> +<li><p class="first">Create a VM using the 64-bit debian profile. Let's call it "pxe-vm".</p> +</li> +<li><p class="first">Mount the TFTP share somewhere, like M: or /mnt/testbox-tftp.</p> +</li> +<li><p class="first">Reconfigure the NAT DHCP and TFTP bits:</p> +<pre class="literal-block"> +VBoxManage setextradata pxe-vm VBoxInternal/PDM/DriverTransformations/pxe/AboveDriver NAT +VBoxManage setextradata pxe-vm VBoxInternal/PDM/DriverTransformations/pxe/Action mergeconfig +VBoxManage setextradata pxe-vm VBoxInternal/PDM/DriverTransformations/pxe/Config/TFTPPrefix M:/ +VBoxManage setextradata pxe-vm VBoxInternal/PDM/DriverTransformations/pxe/Config/BootFile pxelinux.0 +</pre> +</li> +<li><p class="first">Create the file <tt class="docutils literal"><span class="pre">testbox-tftp/pxelinux.cfg/0A00020F</span></tt> containing:</p> +<pre class="literal-block"> +PATH bios +DEFAULT maintenance +LABEL maintenance + MENU LABEL Maintenance (NFS) + KERNEL maintenance-boot/vmlinuz-3.16.0-4-amd64 + APPEND initrd=maintenance-boot/initrd.img-3.16.0-4-amd64 ro ip=dhcp aufs=tmpfs \ + boot=nfs root=/dev/nfs nfsroot=10.42.1.1:/export/testbox-nfsroot +LABEL local-boot +LOCALBOOT +</pre> +</li> +</ul> +</blockquote> +</li> +</ul> +</blockquote> +</div> +<div class="section" id="troubleshooting"> +<h1>Troubleshooting</h1> +<dl class="docutils"> +<dt><tt class="docutils literal"><span class="pre">PXE-E11</span></tt> or something like <tt class="docutils literal">No ARP reply</tt></dt> +<dd>You probably got the TFTP and DHCP on different machines. Try move the TFTP +to the same machine as the DHCP, then the PXE stack won't have to do any +additional ARP resolving. Google results suggest that a congested network +could use the ARP reply to get lost. Our suspicion is that it might also be +related to the PXE stack shipping with the NIC.</dd> +</dl> +<hr class="docutils" /> +<table class="docutils footnote" frame="void" id="footnote-1" rules="none"> +<colgroup><col class="label" /><col /></colgroup> +<tbody valign="top"> +<tr><td class="label"><a class="fn-backref" href="#footnote-reference-1">[1]</a></td><td>See <a class="reference external" href="http://www.syslinux.org/wiki/index.php?title=Category:Modules">http://www.syslinux.org/wiki/index.php?title=Category:Modules</a></td></tr> +</tbody> +</table> +<table class="docutils footnote" frame="void" id="footnote-2" rules="none"> +<colgroup><col class="label" /><col /></colgroup> +<tbody valign="top"> +<tr><td class="label"><a class="fn-backref" href="#footnote-reference-2">[2]</a></td><td>See <a class="reference external" href="http://www.syslinux.org/wiki/index.php?title=PXELINUX#Configuration">http://www.syslinux.org/wiki/index.php?title=PXELINUX#Configuration</a></td></tr> +</tbody> +</table> +<table class="docutils footnote" frame="void" id="footnote-3" rules="none"> +<colgroup><col class="label" /><col /></colgroup> +<tbody valign="top"> +<tr><td class="label"><a class="fn-backref" href="#footnote-reference-3">[3]</a></td><td>See <a class="reference external" href="https://en.wikipedia.org/wiki/Aufs">https://en.wikipedia.org/wiki/Aufs</a></td></tr> +</tbody> +</table> +<table class="docutils footnote" frame="void" id="footnote-4" rules="none"> +<colgroup><col class="label" /><col /></colgroup> +<tbody valign="top"> +<tr><td class="label"><a class="fn-backref" href="#footnote-reference-4">[4]</a></td><td>See <a class="reference external" href="http://shitwefoundout.com/wiki/Diskless_ubuntu">http://shitwefoundout.com/wiki/Diskless_ubuntu</a></td></tr> +</tbody> +</table> +<table class="docutils footnote" frame="void" id="footnote-5" rules="none"> +<colgroup><col class="label" /><col /></colgroup> +<tbody valign="top"> +<tr><td class="label"><a class="fn-backref" href="#footnote-reference-5">[5]</a></td><td>See <a class="reference external" href="http://debianaddict.com/2012/06/19/diskless-debian-linux-booting-via-dhcppxenfstftp/">http://debianaddict.com/2012/06/19/diskless-debian-linux-booting-via-dhcppxenfstftp/</a></td></tr> +</tbody> +</table> +<hr class="docutils" /> +<table class="docutils field-list" frame="void" rules="none"> +<col class="field-name" /> +<col class="field-body" /> +<tbody valign="top"> +<tr class="field"><th class="field-name">Status:</th><td class="field-body">$Id: TestBoxImaging.html $</td> +</tr> +<tr class="field"><th class="field-name">Copyright:</th><td class="field-body">Copyright (C) 2010-2020 Oracle Corporation.</td> +</tr> +</tbody> +</table> +</div> +</div> +</body> +</html> diff --git a/src/VBox/ValidationKit/docs/TestBoxImaging.txt b/src/VBox/ValidationKit/docs/TestBoxImaging.txt new file mode 100644 index 00000000..c84e4951 --- /dev/null +++ b/src/VBox/ValidationKit/docs/TestBoxImaging.txt @@ -0,0 +1,368 @@ + +Testbox Imaging (Backup / Restore) +================================== + + +Introduction +------------ + +This document is explores deploying a very simple drive imaging solution to help +avoid needing to manually reinstall testboxes when a disk goes bust or the OS +install seems to be corrupted. + + +Definitions / Glossary +====================== + +See AutomaticTestingRevamp.txt. + + +Objectives +========== + + - Off site, no admin interaction (no need for ILOM or similar). + - OS independent. + - Space and bandwidth efficient. + - As automatic as possible. + - Logging. + + +Overview of the Solution +======================== + +Here is a brief summary: + + - Always boot testboxes via PXE using PXELINUX. + - Default configuration is local boot (hard disk / SSD) + - Restore/backup action triggered by machine specific PXE config. + - Boots special debian maintenance install off NFS. + - A maintenance service (systemd style) does the work. + - The service reads action from TFTP location and performs it. + - When done the service removes the TFTP machine specific config + and reboots the system. + +Maintenance actions are: + - backup + - backup-again + - restore + - refresh-info + - rescue + +Possible modifier that indicates a subset of disk on testboxes with other OSes +installed. Support for partition level backup/restore is not explored here. + + +How to use +---------- + +To perform one of the above maintenance actions on a testbox, run the +``testbox-pxe-conf.sh`` script:: + + /mnt/testbox-tftp/pxeclient.cfg/testbox-pxe-conf.sh 10.165.98.220 rescue + +Then trigger a reboot. The box will then boot the NFS rooted debian image and +execute the maintenance action. On success, it will remove the testbox hex-IP +config file and reboot again. + + +Storage Server +============== + +The storage server will have three areas used here. Using NFS for all three +avoids extra work getting CIFS sharing right too (NFS is already a pain). + + 1. /export/testbox-tftp - TFTP config area. Read-write. + 2. /export/testbox-backup - Images and logs. Read-write. + 3. /export/testbox-nfsroot - Custom debian. Read-only, no root squash. + + +TFTP (/export/testbox-tftp) +============================ + +The testbox-tftp share needs to be writable, root squashing is okay. + +We need files from both PXELINUX and SYSLINUX to make this work now. On a +debian system, the ``pxelinux`` and ``syslinux`` packages needs to be +installed. We actually do this further down when setting up the nfsroot, so +it's possible to get them from there by postponing this step a little. On +debian 8.6.0 the PXELINUX files are found in ``/usr/lib/PXELINUX`` and the +SYSLINUX ones in ``/usr/lib/syslinux``. + +The initial PXE image as well as associated modules comes in three variants, +BIOS, 32-bit EFI and 64-bit EFI. We'll only need the BIOS one for now. +Perform the following copy operations:: + + cp /usr/lib/PXELINUX/pxelinux.0 /mnt/testbox-tftp/ + cp /usr/lib/syslinux/modules/*/ldlinux.* /mnt/testbox-tftp/ + cp -R /usr/lib/syslinux/modules/bios /mnt/testbox-tftp/ + cp -R /usr/lib/syslinux/modules/efi32 /mnt/testbox-tftp/ + cp -R /usr/lib/syslinux/modules/efi64 /mnt/testbox-tftp/ + + +For simplicity, all the testboxes boot using good old fashioned BIOS, no EFI. +However, it doesn't really hurt to be prepared. + +The PXELINUX related files goes in the root of the testbox-tftp share. (As +mentioned further down, these can be installed on a debian system by running +``apt-get install pxelinux syslinux``.) We need the ``*pxelinux.0`` files +typically found in ``/usr/lib/PXELINUX/`` on debian systems (recent ones +anyway). It is possible we may need one ore more fo the modules [1]_ that +ships with PXELINUX/SYSLINUX, so do copy ``/usr/lib/syslinux/modules`` to +``testbox-tftp/modules`` as well. + + +The directory layout related to the configuration files is dictated by the +PXELINUX configuration file searching algorithm [2]_. Create a subdirectory +``pxelinux.cfg/`` under ``testbox-tftp`` and create the world readable file +``default`` with the following content:: + + PATH bios + DEFAULT local-boot + LABEL local-boot + LOCALBOOT + +This will make the default behavior to boot the local disk system. + +Copy the ``testbox-pxe-conf.sh`` script file found in the same directory as +this document to ``/mnt/testbox-tftp/pxelinux.cfg/``. Edit the copy to correct +the IP addresses near the top, as well as any linux, TFTP and PXE details near +the bottom of the file. This script will generate the PXE configuration file +when performing maintenance on a testbox. + + +Images and logs (/export/testbox-backup) +========================================= + +The testbox-backup share needs to be writable, root squashing is okay. + +In the root there must be a file ``testbox-backup`` so we can easily tell +whether we've actually mounted the share or are just staring at an empty mount +point directory. + +The ``testbox-maintenance.sh`` script maintains a global log in the root +directory that's called ``maintenance.log``. Errors will be logged there as +well as a ping and the action. + +We use a directory layout based on dotted decimal IP addresses here, so for a +server with the IP 10.40.41.42 all its file will be under ``10.40.41.42/``: + +``<hostname>`` + The name of the testbox (empty file). Help finding a testbox by name. + +``testbox-info.txt`` + Information about the testbox. Starting off with the name, decimal IP, + PXELINUX style hexadecimal IP, and more. + +``maintenance.log`` + Maintenance log file recording what the maintenance service does. + +``disk-devices.lst`` + Optional list of disk devices to consider backuping up or restoring. This is + intended for testboxes with additional disks that are used for other purposes + and should touched. + +``sda.raw.gz`` + The gzipped raw copy of the sda device of the testbox. + +``sd[bcdefgh].raw.gz`` + The gzipped raw copy sdb, sdc, sde, sdf, sdg, sdh, etc if any of them exists + and are disks/SSDs. + + +Note! If it turns out we can be certain to get a valid host name, we might just + switch to use the hostname as the directory name instead of the IP. + + +Debian NFS root (/export/testbox-nfsroot) +========================================== + +The testbox-nfsroot share should be read-only and must **not** have root +squashing enabled. Also, make sure setting the set-uid-bit is allowed by the +server, or ``su` and ``sudo`` won't work + +There are several ways of creating a debian nfsroot, but since we've got a +tool like VirtualBox around we've just installed it in a VM, prepared it, +and copied it onto the NFS server share. + +As of writing debian 8.6.0 is current, so a minimal 64-bit install of it was +done in a VM. After installation the following modifications was done: + + - ``apt-get install pxelinux syslinux initramfs-tools zip gddrescue sudo joe`` + and optionally ``apt-get install smbclient cifs-utils``. + + - ``/etc/default/grub`` was modified to set ``GRUB_CMDLINE_LINUX_DEFAULT`` to + ``""`` instead of ``"quiet"``. This allows us to see messages during boot + and perhaps spot why something doesn't work on a testbox. Regenerate the + grub configuration file by running ``update-grub`` afterwards. + + - ``/etc/sudoers`` was modified to allow the ``vbox`` user use sudo without + requring any password. + + - Create the directory ``/etc/systemd/system/getty@tty1.service.d`` and create + the file ``noclear.conf`` in it with the following content:: + + [Service] + TTYVTDisallocate=no + + This stops getty from clearing VT1 and let us see the tail of the boot up + messages, which includes messages from the testbox-maintenance service. + + - Mount the testbox-nfsroot under ``/mnt/`` with write privileges. (The write + privileges are temporary - don't forget to remove them later on.):: + + mount -t nfs myserver.com:/export/testbox-nfsroot + + Note! Adding ``-o nfsvers=3`` may help with some NTFv4 servers. + + - Copy the debian root and dev file system onto nfsroot. If you have ssh + access to the NFS server, the quickest way to do it is to use ``tar``:: + + tar -cz --one-file-system -f /mnt/testbox-maintenance-nfsroot.tar.gz . dev/ + + An alternative is ``cp -ax . /mnt/. && cp -ax dev/. /mnt/dev/.`` but this + is quite a bit slower, obviously. + + - Edit ``/etc/ssh/sshd_config`` setting ``PermitRootLogin`` to ``yes`` so we can ssh + in as root later on. + + - chroot into the nfsroot: ``chroot /mnt/`` + + - ``mount -o proc proc /proc`` + + - ``mount -o sysfs sysfs /sys`` + + - ``mkdir /mnt/testbox-tftp /mnt/testbox-backup`` + + - Recreate ``/etc/fstab`` with:: + + proc /proc proc defaults 0 0 + /dev/nfs / nfs defaults 1 1 + 10.42.1.1:/export/testbox-tftp /mnt/testbox-tftp nfs tcp,nfsvers=3,noauto 2 2 + 10.42.1.1:/export/testbox-backup /mnt/testbox-backup nfs tcp,nfsvers=3,noauto 3 3 + + We use NFS version 3 as that works better for our NFS server and client, + remove if not necessary. The ``noauto`` option is to work around mount + trouble during early bootup on some of our boxes. + + - Do ``mount /mnt/testbox-tftp && mount /mnt/testbox-backup`` to mount the + two shares. This may be a good time to execute the instructions in the + sections above relating to these two shares. + + - Edit ``/etc/initramfs-tools/initramfs.conf`` and change the ``MODULES`` + value from ``most`` to ``netboot``. + + - Append ``aufs`` to ``/etc/initramfs-tools/modules``. The advanced + multi-layered unification filesystem (aufs) enables us to use a + read-only NFS root. [3]_ [4]_ [5]_ + + - Create ``/etc/initramfs-tools/scripts/init-bottom/00_aufs_init`` as + an executable file with the following content:: + + #!/bin/sh + # Don't run during update-initramfs: + case "$1" in + prereqs) + exit 0; + ;; + esac + + modprobe aufs + mkdir -p /ro /rw /aufs + mount -t tmpfs tmpfs /rw -o noatime,mode=0755 + mount --move $rootmnt /ro + mount -t aufs aufs /aufs -o noatime,dirs=/rw:/ro=ro + mkdir -p /aufs/rw /aufs/ro + mount --move /ro /aufs/ro + mount --move /rw /aufs/rw + mount --move /aufs /root + exit 0 + + - Update the init ramdisk: ``update-initramfs -u -k all`` + + Note! It may be necessary to do ``mount -t tmpfs tmpfs /var/tmp`` to help + this operation succeed. + + - Copy ``/boot`` to ``/mnt/testbox-tftp/maintenance-boot/``. + + - Copy the ``testbox-maintenance.sh`` file found in the same directory as this + document to ``/root/scripts/`` (need to create the dir) and make it + executable. + + - Create the systemd service file for the maintenance service as + ``/etc/systemd/system/testbox-maintenance.service`` with the content:: + + [Unit] + Description=Testbox Maintenance + After=network.target + Before=getty@tty1.service + + [Service] + Type=oneshot + RemainAfterExit=True + ExecStart=/root/scripts/testbox-maintenance.sh + ExecStartPre=/bin/echo -e \033%G + ExecReload=/bin/kill -HUP $MAINPID + WorkingDirectory=/tmp + Environment=TERM=xterm + StandardOutput=journal+console + + [Install] + WantedBy=multi-user.target + + - Enable our service: ``systemctl enable /etc/systemd/system/testbox-maintenance.service`` + + - xxxx ... more ??? + + - Before leaving the chroot, do ``mount /proc /sys /mnt/testbox-*``. + + + - Testing the setup from a VM is kind of useful (if the nfs server can be + convinced to accept root nfs mounts from non-privileged clinet ports): + + - Create a VM using the 64-bit debian profile. Let's call it "pxe-vm". + - Mount the TFTP share somewhere, like M: or /mnt/testbox-tftp. + - Reconfigure the NAT DHCP and TFTP bits:: + + VBoxManage setextradata pxe-vm VBoxInternal/PDM/DriverTransformations/pxe/AboveDriver NAT + VBoxManage setextradata pxe-vm VBoxInternal/PDM/DriverTransformations/pxe/Action mergeconfig + VBoxManage setextradata pxe-vm VBoxInternal/PDM/DriverTransformations/pxe/Config/TFTPPrefix M:/ + VBoxManage setextradata pxe-vm VBoxInternal/PDM/DriverTransformations/pxe/Config/BootFile pxelinux.0 + + - Create the file ``testbox-tftp/pxelinux.cfg/0A00020F`` containing:: + + PATH bios + DEFAULT maintenance + LABEL maintenance + MENU LABEL Maintenance (NFS) + KERNEL maintenance-boot/vmlinuz-3.16.0-4-amd64 + APPEND initrd=maintenance-boot/initrd.img-3.16.0-4-amd64 ro ip=dhcp aufs=tmpfs \ + boot=nfs root=/dev/nfs nfsroot=10.42.1.1:/export/testbox-nfsroot + LABEL local-boot + LOCALBOOT + + +Troubleshooting +=============== + +``PXE-E11`` or something like ``No ARP reply`` + You probably got the TFTP and DHCP on different machines. Try move the TFTP + to the same machine as the DHCP, then the PXE stack won't have to do any + additional ARP resolving. Google results suggest that a congested network + could use the ARP reply to get lost. Our suspicion is that it might also be + related to the PXE stack shipping with the NIC. + + + +----- + +.. [1] See http://www.syslinux.org/wiki/index.php?title=Category:Modules +.. [2] See http://www.syslinux.org/wiki/index.php?title=PXELINUX#Configuration +.. [3] See https://en.wikipedia.org/wiki/Aufs +.. [4] See http://shitwefoundout.com/wiki/Diskless_ubuntu +.. [5] See http://debianaddict.com/2012/06/19/diskless-debian-linux-booting-via-dhcppxenfstftp/ + + +----- + +:Status: $Id: TestBoxImaging.txt $ +:Copyright: Copyright (C) 2010-2020 Oracle Corporation. diff --git a/src/VBox/ValidationKit/docs/VBoxAudioValidationKitReadMe.html b/src/VBox/ValidationKit/docs/VBoxAudioValidationKitReadMe.html new file mode 100644 index 00000000..b2476022 --- /dev/null +++ b/src/VBox/ValidationKit/docs/VBoxAudioValidationKitReadMe.html @@ -0,0 +1,601 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<meta name="generator" content="Docutils 0.18: http://docutils.sourceforge.net/" /> +<title>Audio Testing of VirtualBox</title> +<style type="text/css"> + +/* +:Author: David Goodger (goodger@python.org) +:Id: $Id: VBoxAudioValidationKitReadMe.html $ +:Copyright: This stylesheet has been placed in the public domain. + +Default cascading style sheet for the HTML output of Docutils. + +See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to +customize this style sheet. +*/ + +/* used to remove borders from tables and images */ +.borderless, table.borderless td, table.borderless th { + border: 0 } + +table.borderless td, table.borderless th { + /* Override padding for "table.docutils td" with "! important". + The right padding separates the table cells. */ + padding: 0 0.5em 0 0 ! important } + +.first { + /* Override more specific margin styles with "! important". */ + margin-top: 0 ! important } + +.last, .with-subtitle { + margin-bottom: 0 ! important } + +.hidden { + display: none } + +.subscript { + vertical-align: sub; + font-size: smaller } + +.superscript { + vertical-align: super; + font-size: smaller } + +a.toc-backref { + text-decoration: none ; + color: black } + +blockquote.epigraph { + margin: 2em 5em ; } + +dl.docutils dd { + margin-bottom: 0.5em } + +object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { + overflow: hidden; +} + +/* Uncomment (and remove this text!) to get bold-faced definition list terms +dl.docutils dt { + font-weight: bold } +*/ + +div.abstract { + margin: 2em 5em } + +div.abstract p.topic-title { + font-weight: bold ; + text-align: center } + +div.admonition, div.attention, div.caution, div.danger, div.error, +div.hint, div.important, div.note, div.tip, div.warning { + margin: 2em ; + border: medium outset ; + padding: 1em } + +div.admonition p.admonition-title, div.hint p.admonition-title, +div.important p.admonition-title, div.note p.admonition-title, +div.tip p.admonition-title { + font-weight: bold ; + font-family: sans-serif } + +div.attention p.admonition-title, div.caution p.admonition-title, +div.danger p.admonition-title, div.error p.admonition-title, +div.warning p.admonition-title, .code .error { + color: red ; + font-weight: bold ; + font-family: sans-serif } + +/* Uncomment (and remove this text!) to get reduced vertical space in + compound paragraphs. +div.compound .compound-first, div.compound .compound-middle { + margin-bottom: 0.5em } + +div.compound .compound-last, div.compound .compound-middle { + margin-top: 0.5em } +*/ + +div.dedication { + margin: 2em 5em ; + text-align: center ; + font-style: italic } + +div.dedication p.topic-title { + font-weight: bold ; + font-style: normal } + +div.figure { + margin-left: 2em ; + margin-right: 2em } + +div.footer, div.header { + clear: both; + font-size: smaller } + +div.line-block { + display: block ; + margin-top: 1em ; + margin-bottom: 1em } + +div.line-block div.line-block { + margin-top: 0 ; + margin-bottom: 0 ; + margin-left: 1.5em } + +div.sidebar { + margin: 0 0 0.5em 1em ; + border: medium outset ; + padding: 1em ; + background-color: #ffffee ; + width: 40% ; + float: right ; + clear: right } + +div.sidebar p.rubric { + font-family: sans-serif ; + font-size: medium } + +div.system-messages { + margin: 5em } + +div.system-messages h1 { + color: red } + +div.system-message { + border: medium outset ; + padding: 1em } + +div.system-message p.system-message-title { + color: red ; + font-weight: bold } + +div.topic { + margin: 2em } + +h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, +h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { + margin-top: 0.4em } + +h1.title { + text-align: center } + +h2.subtitle { + text-align: center } + +hr.docutils { + width: 75% } + +img.align-left, .figure.align-left, object.align-left, table.align-left { + clear: left ; + float: left ; + margin-right: 1em } + +img.align-right, .figure.align-right, object.align-right, table.align-right { + clear: right ; + float: right ; + margin-left: 1em } + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left } + +.align-center { + clear: both ; + text-align: center } + +.align-right { + text-align: right } + +/* reset inner alignment in figures */ +div.align-right { + text-align: inherit } + +/* div.align-center * { */ +/* text-align: left } */ + +.align-top { + vertical-align: top } + +.align-middle { + vertical-align: middle } + +.align-bottom { + vertical-align: bottom } + +ol.simple, ul.simple { + margin-bottom: 1em } + +ol.arabic { + list-style: decimal } + +ol.loweralpha { + list-style: lower-alpha } + +ol.upperalpha { + list-style: upper-alpha } + +ol.lowerroman { + list-style: lower-roman } + +ol.upperroman { + list-style: upper-roman } + +p.attribution { + text-align: right ; + margin-left: 50% } + +p.caption { + font-style: italic } + +p.credits { + font-style: italic ; + font-size: smaller } + +p.label { + white-space: nowrap } + +p.rubric { + font-weight: bold ; + font-size: larger ; + color: maroon ; + text-align: center } + +p.sidebar-title { + font-family: sans-serif ; + font-weight: bold ; + font-size: larger } + +p.sidebar-subtitle { + font-family: sans-serif ; + font-weight: bold } + +p.topic-title { + font-weight: bold } + +pre.address { + margin-bottom: 0 ; + margin-top: 0 ; + font: inherit } + +pre.literal-block, pre.doctest-block, pre.math, pre.code { + margin-left: 2em ; + margin-right: 2em } + +pre.code .ln { color: grey; } /* line numbers */ +pre.code, code { background-color: #eeeeee } +pre.code .comment, code .comment { color: #5C6576 } +pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } +pre.code .literal.string, code .literal.string { color: #0C5404 } +pre.code .name.builtin, code .name.builtin { color: #352B84 } +pre.code .deleted, code .deleted { background-color: #DEB0A1} +pre.code .inserted, code .inserted { background-color: #A3D289} + +span.classifier { + font-family: sans-serif ; + font-style: oblique } + +span.classifier-delimiter { + font-family: sans-serif ; + font-weight: bold } + +span.interpreted { + font-family: sans-serif } + +span.option { + white-space: nowrap } + +span.pre { + white-space: pre } + +span.problematic { + color: red } + +span.section-subtitle { + /* font-size relative to parent (h1..h6 element) */ + font-size: 80% } + +table.citation { + border-left: solid 1px gray; + margin-left: 1px } + +table.docinfo { + margin: 2em 4em } + +table.docutils { + margin-top: 0.5em ; + margin-bottom: 0.5em } + +table.footnote { + border-left: solid 1px black; + margin-left: 1px } + +table.docutils td, table.docutils th, +table.docinfo td, table.docinfo th { + padding-left: 0.5em ; + padding-right: 0.5em ; + vertical-align: top } + +table.docutils th.field-name, table.docinfo th.docinfo-name { + font-weight: bold ; + text-align: left ; + white-space: nowrap ; + padding-left: 0 } + +/* "booktabs" style (no vertical lines) */ +table.docutils.booktabs { + border: 0px; + border-top: 2px solid; + border-bottom: 2px solid; + border-collapse: collapse; +} +table.docutils.booktabs * { + border: 0px; +} +table.docutils.booktabs th { + border-bottom: thin solid; + text-align: left; +} + +h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, +h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { + font-size: 100% } + +ul.auto-toc { + list-style-type: none } + +</style> +</head> +<body> +<div class="document" id="audio-testing-of-virtualbox"> +<h1 class="title">Audio Testing of VirtualBox</h1> + +<div class="section" id="overview-goal"> +<h1>Overview / Goal</h1> +<p>The goal is to create a flexible testing framework to test the +VirtualBox audio stack.</p> +<p>It should be runnable with an easy-to-use setup so that also regular users +can perform tests on request, without having to install or set up additional +dependencies.</p> +<p>That framework must be runnable on all host/guest combinations together with all +audio drivers ("backends") and device emulations being offered. This makes it a +rather big testing matrix which therefore has to be processed in an automated +fashion.</p> +<p>Additionally it should be flexible enough to add more (custom) tests later on.</p> +</div> +<div class="section" id="operation"> +<h1>Operation</h1> +<p>The framework consists of several components which try to make use as much of +the existing audio stack code as possible. This allows the following +operation modes:</p> +<dl class="docutils"> +<dt>Standalone</dt> +<dd>Playing back / recording audio data (test tones / .WAV files) in a +standalone scenario, i.e. no VirtualBox / VMs required). This mode is using +VirtualBox' audio (mixing) stack and available backend drivers without the +need of VirtualBox being installed.</dd> +<dt>Manual</dt> +<dd>Performing single / multiple tests manually on a local machine. +Requires a running and set up test VM.</dd> +<dt>Automated</dt> +<dd>Performs single / multiple tests via the Validation Kit audio test +driver and can be triggered via the Validation Kit Test Manager.</dd> +<dt>(Re-)validation of previously ran tests</dt> +<dd>This takes two test sets and runs the validation / analysis on them.</dd> +<dt>Self testing mode</dt> +<dd>Performs standalone self tests to verify / debug the involved components.</dd> +</dl> +</div> +<div class="section" id="components-and-terminology"> +<h1>Components and Terminology</h1> +<p>The following components are in charge for performing the audio tests +(depends on the operation mode, see above):</p> +<ul> +<li><p class="first">VBoxAudioTest (also known as VKAT, "Validation Kit Audio Test"): +A binary which can perform the standalone audio tests mentioned above, as well +as acting as the guest and host service(s) when performing manual or automated +tests. It also includes the analysis / verification of audio test sets. +VKAT also is included in host installations and Guest Additions since +VirtualBox 7.0 to give customers and end users the opportunity to test and +verify the audio stack.</p> +<dl class="docutils"> +<dt>Additional features include:</dt> +<dd><ul class="first last simple"> +<li>Automatic probing of audio backends ("--probe-backends")</li> +<li>Manual playback of test tones ("play -t")</li> +<li>Manual playback of .WAV files ("play <WAV-File>")</li> +<li>Manual recording to .WAV files ("recording <WAV-File>")</li> +<li>Manual device enumeration (sub command "enum")</li> +<li>Manual (re-)verification of test sets (sub command "verify")</li> +<li>Self-contained self tests (sub command "selftest")</li> +</ul> +</dd> +</dl> +<p>See the syntax help ("--help") for more.</p> +</li> +<li><p class="first">ATS ("Audio Testing Service"): Component which is being used by 1 and the +Validation Kit audio driver (backend) to communicate across guest and host +boundaries. Currently using a TCP/IP transport layer. Also works with VMs +which are configured with NAT networking ("reverse connection").</p> +</li> +<li><p class="first">Validation Kit audio test driver (tdAudioTest.py): Used for integrating and +invoking VKAT for manual and automated tests via the Validation Kit framework +(Test Manager). Optional. The test driver can be found at <a class="footnote-reference" href="#footnote-1" id="footnote-reference-1">[1]</a>.</p> +</li> +<li><p class="first">Validation Kit audio driver (backend): A dedicated audio backend which +communicates with VKAT running on the same host to perform the actual audio +tests on a VirtualBox installation. This makes it possible to test the full +audio stack on a running VM without any additional / external tools.</p> +<p>On guest playback, data will be recorded, on guest recording, data will be +injected from the host into the audio stack.</p> +</li> +<li><dl class="first docutils"> +<dt>Test sets contain</dt> +<dd><ul class="first last simple"> +<li>a test manifest with all information required (vkat_manifest.ini)</li> +<li>the generated / captured audio data (as raw PCM)</li> +</ul> +</dd> +</dl> +<p>and are either packed as .tar.gz archives or consist of a dedicated directory +per test set.</p> +<p>There always must be at least two test sets - one from the host side and one +from the guest side - to perform a verification.</p> +<p>Each test set contains a test tag so that matching test sets can be +identified.</p> +</li> +</ul> +<p>The above components are also included in VirtualBox release builds and can be +optionally enabled (disabled by default).</p> +<table class="docutils footnote" frame="void" id="footnote-1" rules="none"> +<colgroup><col class="label" /><col /></colgroup> +<tbody valign="top"> +<tr><td class="label"><a class="fn-backref" href="#footnote-reference-1">[1]</a></td><td>src/VBox/ValidationKit/tests/audio/tdAudioTest.py</td></tr> +</tbody> +</table> +</div> +<div class="section" id="setup-instructions"> +<h1>Setup instructions</h1> +<ul> +<li><p class="first">VM needs to be configured to have audio emulation and audio testing enabled +(via extra-data, set "VBoxInternal2/Audio/Debug/Enabled" to "true").</p> +</li> +<li><p class="first">Audio input / output for the VM needs to be enabled (depending on the test).</p> +</li> +<li><p class="first">Start VBoxAudioTest on the guest, for example:</p> +<p>VBoxAudioTest test --mode guest --tcp-connect-address 10.0.2.2</p> +<dl class="docutils"> +<dt>Note: VBoxAudioTest is included with the Guest Additions starting at</dt> +<dd><p class="first last">VirtualBox 7.0.</p> +</dd> +<dt>Note: Depending on the VM's networking configuration there might be further</dt> +<dd><p class="first last">steps necessary in order to be able to reach the host from the guest. +See the VirtualBox manual for more information.</p> +</dd> +</dl> +</li> +</ul> +</div> +<div class="section" id="performing-a-manual-test"> +<h1>Performing a manual test</h1> +<ul> +<li><p class="first">Follow "Setup instructions".</p> +</li> +<li><p class="first">Start VBoxAudioTest on the host with selected test(s), for example:</p> +<p>VBoxAudioTest test --mode host</p> +<blockquote> +<dl class="docutils"> +<dt>Note: VBoxAudioTest is included with the VirtualBox 7.0 host installers and</dt> +<dd><p class="first last">will be installed by default.</p> +</dd> +</dl> +</blockquote> +</li> +<li><p class="first">By default the test verification will be done automatically after running the +tests.</p> +</li> +</ul> +</div> +<div class="section" id="advanced-performing-manual-verification"> +<h1>Advanced: Performing manual verification</h1> +<p>VBoxAudioTest can manually be used with the "verify" sub command in order to +(re-)verify previously generated test sets. It then will return different exit +codes based on the verification result.</p> +</div> +<div class="section" id="advanced-performing-an-automated-test"> +<h1>Advanced: Performing an automated test</h1> +<ul class="simple"> +<li>TxS (Test E[x]ecution Service) has to be up and running (part of the +Validation Kit) on the guest.</li> +<li>Invoke the tdAudioTest.py test driver, either manually or fully automated +via Test Manager.</li> +</ul> +</div> +<div class="section" id="internals-workflow-for-a-single-test"> +<h1>Internals: Workflow for a single test</h1> +<p>When a single test is being executed on a running VM, the following (simplified) +workflow applies:</p> +<ul class="simple"> +<li>VKAT on the host connects to VKAT running on the guest (via ATS, also can be a +remote machine in theory).</li> +<li>VKAT on the host connects to Validation Kit audio driver on the host +(via ATS, also can be a remote machine in theory).</li> +<li><dl class="first docutils"> +<dt>For example, when doing playback tests, VKAT on the host ...</dt> +<dd><ul class="first last"> +<li><dl class="first docutils"> +<dt>... tells the Validation Kit audio driver to start recording</dt> +<dd>guest playback.</dd> +</dl> +</li> +<li>... tells the VKAT on the guest to start playing back audio data.</li> +<li><dl class="first docutils"> +<dt>... gathers all test data (generated from/by the guest and recorded from</dt> +<dd>the host) as separate test sets.</dd> +</dl> +</li> +<li>... starts verification / analysis of the test sets.</li> +</ul> +</dd> +</dl> +</li> +</ul> +</div> +<div class="section" id="current-status-limitations"> +<h1>Current status / limitations</h1> +<ul class="simple"> +<li><dl class="first docutils"> +<dt>The following test types are currently implemented:</dt> +<dd><ul class="first last"> +<li>Test tone (sine wave) playback from the guest</li> +<li>Test tone (sine wave) recording by the guest (injected from the host)</li> +</ul> +</dd> +</dl> +</li> +<li>Only the HDA device emulation has been verified so far.</li> +<li>Only the ALSA audio stack on Debian 10 has been verified so far. +Note: This is different from PulseAudio using the ALSA plugin!</li> +</ul> +</div> +<div class="section" id="troubleshooting"> +<h1>Troubleshooting</h1> +<ul class="simple"> +<li>Make sure that audio device emulation is enabled and can be used within the +guest. Also, audio input / output has to be enabled, depending on the tests.</li> +<li>Make sure that the guest's VBoxAudioTest's instance can reach the host via +the selected transport layer (TCP/IP by default).</li> +<li>Increase the hosts audio logging level +(via extra-data, set "VBoxInternal2/Audio/Debug/Level" to "5").</li> +<li>Increase VBoxAudioTest's verbosity level (add "-v", can be specified +multiple times).</li> +<li>Check if the VBox release log contains any warnings / errors with the +"ValKit:" prefix.</li> +</ul> +<table class="docutils field-list" frame="void" rules="none"> +<col class="field-name" /> +<col class="field-body" /> +<tbody valign="top"> +<tr class="field"><th class="field-name">Status:</th><td class="field-body">$Id: VBoxAudioValidationKitReadMe.html $</td> +</tr> +<tr class="field"><th class="field-name">Copyright:</th><td class="field-body">Copyright (C) 2021 Oracle Corporation.</td> +</tr> +</tbody> +</table> +</div> +</div> +</body> +</html> diff --git a/src/VBox/ValidationKit/docs/VBoxAudioValidationKitReadMe.txt b/src/VBox/ValidationKit/docs/VBoxAudioValidationKitReadMe.txt new file mode 100644 index 00000000..1d547941 --- /dev/null +++ b/src/VBox/ValidationKit/docs/VBoxAudioValidationKitReadMe.txt @@ -0,0 +1,207 @@ +Audio Testing of VirtualBox +=========================== + + +Overview / Goal +--------------- + +The goal is to create a flexible testing framework to test the +VirtualBox audio stack. + +It should be runnable with an easy-to-use setup so that also regular users +can perform tests on request, without having to install or set up additional +dependencies. + +That framework must be runnable on all host/guest combinations together with all +audio drivers ("backends") and device emulations being offered. This makes it a +rather big testing matrix which therefore has to be processed in an automated +fashion. + +Additionally it should be flexible enough to add more (custom) tests later on. + + +Operation +--------- + +The framework consists of several components which try to make use as much of +the existing audio stack code as possible. This allows the following +operation modes: + +Standalone + Playing back / recording audio data (test tones / .WAV files) in a + standalone scenario, i.e. no VirtualBox / VMs required). This mode is using + VirtualBox' audio (mixing) stack and available backend drivers without the + need of VirtualBox being installed. + +Manual + Performing single / multiple tests manually on a local machine. + Requires a running and set up test VM. + +Automated + Performs single / multiple tests via the Validation Kit audio test + driver and can be triggered via the Validation Kit Test Manager. + +(Re-)validation of previously ran tests + This takes two test sets and runs the validation / analysis on them. + +Self testing mode + Performs standalone self tests to verify / debug the involved components. + + +Components and Terminology +-------------------------- + +The following components are in charge for performing the audio tests +(depends on the operation mode, see above): + +- VBoxAudioTest (also known as VKAT, "Validation Kit Audio Test"): + A binary which can perform the standalone audio tests mentioned above, as well + as acting as the guest and host service(s) when performing manual or automated + tests. It also includes the analysis / verification of audio test sets. + VKAT also is included in host installations and Guest Additions since + VirtualBox 7.0 to give customers and end users the opportunity to test and + verify the audio stack. + + Additional features include: + * Automatic probing of audio backends ("--probe-backends") + * Manual playback of test tones ("play -t") + * Manual playback of .WAV files ("play <WAV-File>") + * Manual recording to .WAV files ("recording <WAV-File>") + * Manual device enumeration (sub command "enum") + * Manual (re-)verification of test sets (sub command "verify") + * Self-contained self tests (sub command "selftest") + + See the syntax help ("--help") for more. + +- ATS ("Audio Testing Service"): Component which is being used by 1 and the + Validation Kit audio driver (backend) to communicate across guest and host + boundaries. Currently using a TCP/IP transport layer. Also works with VMs + which are configured with NAT networking ("reverse connection"). + +- Validation Kit audio test driver (tdAudioTest.py): Used for integrating and + invoking VKAT for manual and automated tests via the Validation Kit framework + (Test Manager). Optional. The test driver can be found at [1]_. + +- Validation Kit audio driver (backend): A dedicated audio backend which + communicates with VKAT running on the same host to perform the actual audio + tests on a VirtualBox installation. This makes it possible to test the full + audio stack on a running VM without any additional / external tools. + + On guest playback, data will be recorded, on guest recording, data will be + injected from the host into the audio stack. + +- Test sets contain + - a test manifest with all information required (vkat_manifest.ini) + - the generated / captured audio data (as raw PCM) + + and are either packed as .tar.gz archives or consist of a dedicated directory + per test set. + + There always must be at least two test sets - one from the host side and one + from the guest side - to perform a verification. + + Each test set contains a test tag so that matching test sets can be + identified. + +The above components are also included in VirtualBox release builds and can be +optionally enabled (disabled by default). + +.. [1] src/VBox/ValidationKit/tests/audio/tdAudioTest.py + + +Setup instructions +------------------ + +- VM needs to be configured to have audio emulation and audio testing enabled + (via extra-data, set "VBoxInternal2/Audio/Debug/Enabled" to "true"). +- Audio input / output for the VM needs to be enabled (depending on the test). +- Start VBoxAudioTest on the guest, for example: + + VBoxAudioTest test --mode guest --tcp-connect-address 10.0.2.2 + + Note: VBoxAudioTest is included with the Guest Additions starting at + VirtualBox 7.0. + Note: Depending on the VM's networking configuration there might be further + steps necessary in order to be able to reach the host from the guest. + See the VirtualBox manual for more information. + + +Performing a manual test +------------------------ + +- Follow "Setup instructions". +- Start VBoxAudioTest on the host with selected test(s), for example: + + VBoxAudioTest test --mode host + + Note: VBoxAudioTest is included with the VirtualBox 7.0 host installers and + will be installed by default. + +- By default the test verification will be done automatically after running the + tests. + + +Advanced: Performing manual verification +---------------------------------------- + +VBoxAudioTest can manually be used with the "verify" sub command in order to +(re-)verify previously generated test sets. It then will return different exit +codes based on the verification result. + + +Advanced: Performing an automated test +-------------------------------------- + +- TxS (Test E[x]ecution Service) has to be up and running (part of the + Validation Kit) on the guest. +- Invoke the tdAudioTest.py test driver, either manually or fully automated + via Test Manager. + + +Internals: Workflow for a single test +------------------------------------- + +When a single test is being executed on a running VM, the following (simplified) +workflow applies: + +- VKAT on the host connects to VKAT running on the guest (via ATS, also can be a + remote machine in theory). +- VKAT on the host connects to Validation Kit audio driver on the host + (via ATS, also can be a remote machine in theory). +- For example, when doing playback tests, VKAT on the host ... + * ... tells the Validation Kit audio driver to start recording + guest playback. + * ... tells the VKAT on the guest to start playing back audio data. + * ... gathers all test data (generated from/by the guest and recorded from + the host) as separate test sets. + * ... starts verification / analysis of the test sets. + + +Current status / limitations +---------------------------- + +- The following test types are currently implemented: + * Test tone (sine wave) playback from the guest + * Test tone (sine wave) recording by the guest (injected from the host) +- Only the HDA device emulation has been verified so far. +- Only the ALSA audio stack on Debian 10 has been verified so far. + Note: This is different from PulseAudio using the ALSA plugin! + + +Troubleshooting +--------------- + +- Make sure that audio device emulation is enabled and can be used within the + guest. Also, audio input / output has to be enabled, depending on the tests. +- Make sure that the guest's VBoxAudioTest's instance can reach the host via + the selected transport layer (TCP/IP by default). +- Increase the hosts audio logging level + (via extra-data, set "VBoxInternal2/Audio/Debug/Level" to "5"). +- Increase VBoxAudioTest's verbosity level (add "-v", can be specified + multiple times). +- Check if the VBox release log contains any warnings / errors with the + "ValKit:" prefix. + + +:Status: $Id: VBoxAudioValidationKitReadMe.txt $ +:Copyright: Copyright (C) 2021 Oracle Corporation. diff --git a/src/VBox/ValidationKit/docs/VBoxValidationKitReadMe.html b/src/VBox/ValidationKit/docs/VBoxValidationKitReadMe.html new file mode 100644 index 00000000..766d949f --- /dev/null +++ b/src/VBox/ValidationKit/docs/VBoxValidationKitReadMe.html @@ -0,0 +1,467 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<meta name="generator" content="Docutils 0.18: http://docutils.sourceforge.net/" /> +<title>The VirtualBox Validation Kit</title> +<style type="text/css"> + +/* +:Author: David Goodger (goodger@python.org) +:Id: $Id: VBoxValidationKitReadMe.html $ +:Copyright: This stylesheet has been placed in the public domain. + +Default cascading style sheet for the HTML output of Docutils. + +See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to +customize this style sheet. +*/ + +/* used to remove borders from tables and images */ +.borderless, table.borderless td, table.borderless th { + border: 0 } + +table.borderless td, table.borderless th { + /* Override padding for "table.docutils td" with "! important". + The right padding separates the table cells. */ + padding: 0 0.5em 0 0 ! important } + +.first { + /* Override more specific margin styles with "! important". */ + margin-top: 0 ! important } + +.last, .with-subtitle { + margin-bottom: 0 ! important } + +.hidden { + display: none } + +.subscript { + vertical-align: sub; + font-size: smaller } + +.superscript { + vertical-align: super; + font-size: smaller } + +a.toc-backref { + text-decoration: none ; + color: black } + +blockquote.epigraph { + margin: 2em 5em ; } + +dl.docutils dd { + margin-bottom: 0.5em } + +object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { + overflow: hidden; +} + +/* Uncomment (and remove this text!) to get bold-faced definition list terms +dl.docutils dt { + font-weight: bold } +*/ + +div.abstract { + margin: 2em 5em } + +div.abstract p.topic-title { + font-weight: bold ; + text-align: center } + +div.admonition, div.attention, div.caution, div.danger, div.error, +div.hint, div.important, div.note, div.tip, div.warning { + margin: 2em ; + border: medium outset ; + padding: 1em } + +div.admonition p.admonition-title, div.hint p.admonition-title, +div.important p.admonition-title, div.note p.admonition-title, +div.tip p.admonition-title { + font-weight: bold ; + font-family: sans-serif } + +div.attention p.admonition-title, div.caution p.admonition-title, +div.danger p.admonition-title, div.error p.admonition-title, +div.warning p.admonition-title, .code .error { + color: red ; + font-weight: bold ; + font-family: sans-serif } + +/* Uncomment (and remove this text!) to get reduced vertical space in + compound paragraphs. +div.compound .compound-first, div.compound .compound-middle { + margin-bottom: 0.5em } + +div.compound .compound-last, div.compound .compound-middle { + margin-top: 0.5em } +*/ + +div.dedication { + margin: 2em 5em ; + text-align: center ; + font-style: italic } + +div.dedication p.topic-title { + font-weight: bold ; + font-style: normal } + +div.figure { + margin-left: 2em ; + margin-right: 2em } + +div.footer, div.header { + clear: both; + font-size: smaller } + +div.line-block { + display: block ; + margin-top: 1em ; + margin-bottom: 1em } + +div.line-block div.line-block { + margin-top: 0 ; + margin-bottom: 0 ; + margin-left: 1.5em } + +div.sidebar { + margin: 0 0 0.5em 1em ; + border: medium outset ; + padding: 1em ; + background-color: #ffffee ; + width: 40% ; + float: right ; + clear: right } + +div.sidebar p.rubric { + font-family: sans-serif ; + font-size: medium } + +div.system-messages { + margin: 5em } + +div.system-messages h1 { + color: red } + +div.system-message { + border: medium outset ; + padding: 1em } + +div.system-message p.system-message-title { + color: red ; + font-weight: bold } + +div.topic { + margin: 2em } + +h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, +h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { + margin-top: 0.4em } + +h1.title { + text-align: center } + +h2.subtitle { + text-align: center } + +hr.docutils { + width: 75% } + +img.align-left, .figure.align-left, object.align-left, table.align-left { + clear: left ; + float: left ; + margin-right: 1em } + +img.align-right, .figure.align-right, object.align-right, table.align-right { + clear: right ; + float: right ; + margin-left: 1em } + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left } + +.align-center { + clear: both ; + text-align: center } + +.align-right { + text-align: right } + +/* reset inner alignment in figures */ +div.align-right { + text-align: inherit } + +/* div.align-center * { */ +/* text-align: left } */ + +.align-top { + vertical-align: top } + +.align-middle { + vertical-align: middle } + +.align-bottom { + vertical-align: bottom } + +ol.simple, ul.simple { + margin-bottom: 1em } + +ol.arabic { + list-style: decimal } + +ol.loweralpha { + list-style: lower-alpha } + +ol.upperalpha { + list-style: upper-alpha } + +ol.lowerroman { + list-style: lower-roman } + +ol.upperroman { + list-style: upper-roman } + +p.attribution { + text-align: right ; + margin-left: 50% } + +p.caption { + font-style: italic } + +p.credits { + font-style: italic ; + font-size: smaller } + +p.label { + white-space: nowrap } + +p.rubric { + font-weight: bold ; + font-size: larger ; + color: maroon ; + text-align: center } + +p.sidebar-title { + font-family: sans-serif ; + font-weight: bold ; + font-size: larger } + +p.sidebar-subtitle { + font-family: sans-serif ; + font-weight: bold } + +p.topic-title { + font-weight: bold } + +pre.address { + margin-bottom: 0 ; + margin-top: 0 ; + font: inherit } + +pre.literal-block, pre.doctest-block, pre.math, pre.code { + margin-left: 2em ; + margin-right: 2em } + +pre.code .ln { color: grey; } /* line numbers */ +pre.code, code { background-color: #eeeeee } +pre.code .comment, code .comment { color: #5C6576 } +pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } +pre.code .literal.string, code .literal.string { color: #0C5404 } +pre.code .name.builtin, code .name.builtin { color: #352B84 } +pre.code .deleted, code .deleted { background-color: #DEB0A1} +pre.code .inserted, code .inserted { background-color: #A3D289} + +span.classifier { + font-family: sans-serif ; + font-style: oblique } + +span.classifier-delimiter { + font-family: sans-serif ; + font-weight: bold } + +span.interpreted { + font-family: sans-serif } + +span.option { + white-space: nowrap } + +span.pre { + white-space: pre } + +span.problematic { + color: red } + +span.section-subtitle { + /* font-size relative to parent (h1..h6 element) */ + font-size: 80% } + +table.citation { + border-left: solid 1px gray; + margin-left: 1px } + +table.docinfo { + margin: 2em 4em } + +table.docutils { + margin-top: 0.5em ; + margin-bottom: 0.5em } + +table.footnote { + border-left: solid 1px black; + margin-left: 1px } + +table.docutils td, table.docutils th, +table.docinfo td, table.docinfo th { + padding-left: 0.5em ; + padding-right: 0.5em ; + vertical-align: top } + +table.docutils th.field-name, table.docinfo th.docinfo-name { + font-weight: bold ; + text-align: left ; + white-space: nowrap ; + padding-left: 0 } + +/* "booktabs" style (no vertical lines) */ +table.docutils.booktabs { + border: 0px; + border-top: 2px solid; + border-bottom: 2px solid; + border-collapse: collapse; +} +table.docutils.booktabs * { + border: 0px; +} +table.docutils.booktabs th { + border-bottom: thin solid; + text-align: left; +} + +h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, +h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { + font-size: 100% } + +ul.auto-toc { + list-style-type: none } + +</style> +</head> +<body> +<div class="document" id="the-virtualbox-validation-kit"> +<h1 class="title">The VirtualBox Validation Kit</h1> + +<div class="section" id="introduction"> +<h1>Introduction</h1> +<p>The VirtualBox Validation Kit is our new public tool for doing automated +testing of VirtualBox. We are continually working on adding new features +and guest operating systems to our battery of tests.</p> +<p>We warmly welcome contributions, new ideas for good tests and fixes.</p> +</div> +<div class="section" id="directory-layout"> +<h1>Directory Layout</h1> +<dl class="docutils"> +<dt>./docs/</dt> +<dd><p class="first">The documentation for the test suite mostly lives here, the exception being +readme.txt files that are better off living near what they concern.</p> +<p class="last">For a definition of terms used here, see the Definitions / Glossary section +of ./docs/AutomaticTestingRevamp.txt / ./docs/AutomaticTestingRevamp.html.</p> +</dd> +<dt>./testdriver/</dt> +<dd><p class="first">Python module implementing the base test drivers and supporting stuff. +The base test driver implementation is found in ./testdriver/base.py while +the VBox centric specialization is in ./testdriver/vbox.py. Various VBox +API wrappers that makes things easier to use and glosses over a lot of API +version differences that live in ./testdriver/vboxwrappers.py.</p> +<p>Test VM collections are often managed thru ./testdriver/vboxtestvms.py, but +doesn't necessarily have to be, it's up to the individual test driver.</p> +<p>For logging, reporting result, uploading useful files and such we have a +reporter singleton sub-package, ./testdriver/reporter.py. It implements +both local (for local testing) and remote (for testboxes + test manager) +reporting.</p> +<p class="last">There is also a VBoxTXS client implementation in txsclient.py and a stacked +test driver for installing VBox (vboxinstaller.py). Most test drivers will +use the TXS client indirectly thru vbox.py methods. The installer driver +is a special trick for the testbox+testmanager setup.</p> +</dd> +<dt>./tests/</dt> +<dd>The python scripts driving the tests. These are organized by what they +test and are all derived from the base classes in ./testdriver (mostly from +vbox.py of course). Most tests use one or more VMs from a standard set of +preconfigured VMs defined by ./testdriver/vboxtestvms.py (mentioned above), +though the installation tests used prepared ISOs and floppy images.</dd> +<dt>./vms/</dt> +<dd>Text documents describing the preconfigured test VMs defined by +./testdrive/vboxtestvms.py. This will also contain description of how to +prepare installation ISOs when we get around to it (soon).</dd> +<dt>./utils/</dt> +<dd><p class="first">Test utilities and lower level test programs, compiled from C, C++ and +Assembly mostly. Generally available for both host and guest, i.e. in the +zip and on the VBoxValidationKit.iso respectively.</p> +<p>The Test eXecution Service (VBoxTXS) found in ./utils/TestExecServ is one +of the more important utilities. It implements a remote execution service +for running programs/tests inside VMs and on other test boxes. See +./utils/TestExecServ/vboxtxs-readme.txt for more details.</p> +<p class="last">A simple network bandwidth and latency test program can be found in +./utils/network/NetPerf.cpp.</p> +</dd> +<dt>./bootsectors/</dt> +<dd><p class="first">Boot sector test environment. This allows creating floppy images in +assembly that tests specific CPU or device behavior. Most tests can be +put on a USB stick, floppy or similar and booted up on real hardware for +comparison. All floppy images can be used for manual testing by developers +and most will be used by test drivers (./tests/<em>/td</em>.py) sooner or later.</p> +<p class="last">The boot sector environment is heavily bound to yasm and it's ability to +link binary images for single assembly input units. There is a "library" +of standard initialization code and runtime code, which include switch to +all (well V8086 mode is still missing, but we'll get that done eventually) +processor modes and paging modes. The image specific code is split into +init/driver code and test template, the latter can be instantiated for each +process execution+paging mode.</p> +</dd> +<dt>./common/</dt> +<dd>Python package containing common python code.</dd> +<dt>./testboxscript/</dt> +<dd>The testbox script. This is installed on testboxes used for automatic +testing with the testmanager.</dd> +<dt>./testmanager/</dt> +<dd>The VirtualBox Test Manager (server side code). This is written in Python +and currently uses postgresql as database backend for no particular reason +other than that it was already installed on the server the test manager was +going to run on. It's relatively generic, though there are of course +things in there that are of more use when testing VirtualBox than other +things. A more detailed account (though perhaps a little dated) of the +test manager can be found in ./docs/AutomaticTestingRevamp.txt and +./docs/AutomaticTestingRevamp.html.</dd> +<dt>./testanalysis/</dt> +<dd>A start a local test result analysis, comparing network test output. We'll +probably be picking this up again later.</dd> +<dt>./snippets/</dt> +<dd>Various code snippets that may be turned into real tests at some point.</dd> +</dl> +<table class="docutils field-list" frame="void" rules="none"> +<col class="field-name" /> +<col class="field-body" /> +<tbody valign="top"> +<tr class="field"><th class="field-name">Status:</th><td class="field-body">$Id: VBoxValidationKitReadMe.html $</td> +</tr> +<tr class="field"><th class="field-name">Copyright:</th><td class="field-body">Copyright (C) 2010-2020 Oracle Corporation.</td> +</tr> +</tbody> +</table> +</div> +</div> +</body> +</html> diff --git a/src/VBox/ValidationKit/docs/VBoxValidationKitReadMe.txt b/src/VBox/ValidationKit/docs/VBoxValidationKitReadMe.txt new file mode 100644 index 00000000..896df86d --- /dev/null +++ b/src/VBox/ValidationKit/docs/VBoxValidationKitReadMe.txt @@ -0,0 +1,113 @@ + +The VirtualBox Validation Kit +============================= + + +Introduction +------------ + +The VirtualBox Validation Kit is our new public tool for doing automated +testing of VirtualBox. We are continually working on adding new features +and guest operating systems to our battery of tests. + +We warmly welcome contributions, new ideas for good tests and fixes. + + +Directory Layout +---------------- + +./docs/ + The documentation for the test suite mostly lives here, the exception being + readme.txt files that are better off living near what they concern. + + For a definition of terms used here, see the Definitions / Glossary section + of ./docs/AutomaticTestingRevamp.txt / ./docs/AutomaticTestingRevamp.html. + +./testdriver/ + Python module implementing the base test drivers and supporting stuff. + The base test driver implementation is found in ./testdriver/base.py while + the VBox centric specialization is in ./testdriver/vbox.py. Various VBox + API wrappers that makes things easier to use and glosses over a lot of API + version differences that live in ./testdriver/vboxwrappers.py. + + Test VM collections are often managed thru ./testdriver/vboxtestvms.py, but + doesn't necessarily have to be, it's up to the individual test driver. + + For logging, reporting result, uploading useful files and such we have a + reporter singleton sub-package, ./testdriver/reporter.py. It implements + both local (for local testing) and remote (for testboxes + test manager) + reporting. + + There is also a VBoxTXS client implementation in txsclient.py and a stacked + test driver for installing VBox (vboxinstaller.py). Most test drivers will + use the TXS client indirectly thru vbox.py methods. The installer driver + is a special trick for the testbox+testmanager setup. + +./tests/ + The python scripts driving the tests. These are organized by what they + test and are all derived from the base classes in ./testdriver (mostly from + vbox.py of course). Most tests use one or more VMs from a standard set of + preconfigured VMs defined by ./testdriver/vboxtestvms.py (mentioned above), + though the installation tests used prepared ISOs and floppy images. + +./vms/ + Text documents describing the preconfigured test VMs defined by + ./testdrive/vboxtestvms.py. This will also contain description of how to + prepare installation ISOs when we get around to it (soon). + +./utils/ + Test utilities and lower level test programs, compiled from C, C++ and + Assembly mostly. Generally available for both host and guest, i.e. in the + zip and on the VBoxValidationKit.iso respectively. + + The Test eXecution Service (VBoxTXS) found in ./utils/TestExecServ is one + of the more important utilities. It implements a remote execution service + for running programs/tests inside VMs and on other test boxes. See + ./utils/TestExecServ/vboxtxs-readme.txt for more details. + + A simple network bandwidth and latency test program can be found in + ./utils/network/NetPerf.cpp. + +./bootsectors/ + Boot sector test environment. This allows creating floppy images in + assembly that tests specific CPU or device behavior. Most tests can be + put on a USB stick, floppy or similar and booted up on real hardware for + comparison. All floppy images can be used for manual testing by developers + and most will be used by test drivers (./tests/*/td*.py) sooner or later. + + The boot sector environment is heavily bound to yasm and it's ability to + link binary images for single assembly input units. There is a "library" + of standard initialization code and runtime code, which include switch to + all (well V8086 mode is still missing, but we'll get that done eventually) + processor modes and paging modes. The image specific code is split into + init/driver code and test template, the latter can be instantiated for each + process execution+paging mode. + +./common/ + Python package containing common python code. + +./testboxscript/ + The testbox script. This is installed on testboxes used for automatic + testing with the testmanager. + +./testmanager/ + The VirtualBox Test Manager (server side code). This is written in Python + and currently uses postgresql as database backend for no particular reason + other than that it was already installed on the server the test manager was + going to run on. It's relatively generic, though there are of course + things in there that are of more use when testing VirtualBox than other + things. A more detailed account (though perhaps a little dated) of the + test manager can be found in ./docs/AutomaticTestingRevamp.txt and + ./docs/AutomaticTestingRevamp.html. + +./testanalysis/ + A start a local test result analysis, comparing network test output. We'll + probably be picking this up again later. + +./snippets/ + Various code snippets that may be turned into real tests at some point. + + + +:Status: $Id: VBoxValidationKitReadMe.txt $ +:Copyright: Copyright (C) 2010-2020 Oracle Corporation. diff --git a/src/VBox/ValidationKit/docs/WindbgPython.txt b/src/VBox/ValidationKit/docs/WindbgPython.txt new file mode 100644 index 00000000..198ec917 --- /dev/null +++ b/src/VBox/ValidationKit/docs/WindbgPython.txt @@ -0,0 +1,10 @@ +$Id: WindbgPython.txt $ + +Just a couple of useful windbg commands: + +Show python filenames + frame line number (not statement) up the call stack: +!for_each_frame ".block { dt python27!_frame qwo(!f) f_lineno; da qwo(qwo(qwo(!f)+0x20) + 50) + 20 } " + +Same, alternative version: +!for_each_frame .if ( $spat("${@#FunctionName}","*PyEval_EvalFrameEx*") ) { .printf "python frame: line %d\npython frame: filename %ma\n", @@c++(f->f_lineno), qwo(qwo(qwo(!f)+0x20) + 50) + 20 } + diff --git a/src/VBox/ValidationKit/docs/testbox-maintenance.sh b/src/VBox/ValidationKit/docs/testbox-maintenance.sh new file mode 100755 index 00000000..50d82792 --- /dev/null +++ b/src/VBox/ValidationKit/docs/testbox-maintenance.sh @@ -0,0 +1,409 @@ +#!/bin/bash +# $Id: testbox-maintenance.sh $ +## @file +# VirtualBox Validation Kit - testbox maintenance service +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + + +# +# Global Variables (config first). +# +MY_REBOOT_WHEN_DONE="yes" +#MY_REBOOT_WHEN_DONE="" # enable this for debugging the script + +MY_TFTP_ROOT="/mnt/testbox-tftp" +MY_BACKUP_ROOT="/mnt/testbox-backup" +MY_BACKUP_MNT_TEST_FILE="/mnt/testbox-backup/testbox-backup" +MY_GLOBAL_LOG_FILE="${MY_BACKUP_ROOT}/maintenance.log" +MY_DD_BLOCK_SIZE=256K + +MY_IP="" +MY_BACKUP_DIR="" +MY_LOG_FILE="" +MY_PXELINUX_CFG_FILE="" + + +## +# Info message. +# +InfoMsg() +{ + echo $*; + if test -n "${MY_LOG_FILE}"; then + echo "`date -uIsec`: ${MY_IP}: info:" $* >> ${MY_LOG_FILE}; + fi +} + + +## +# Error message and reboot+exit. First argument is exit code. +# +ErrorMsgExit() +{ + MY_RET=$1 + shift + echo "testbox-maintenance.sh: error:" $* >&2; + # Append to the testbox log. + if test -n "${MY_LOG_FILE}"; then + echo "`date -uIsec`: ${MY_IP}: error:" $* >> "${MY_LOG_FILE}"; + fi + # Append to the global log. + if test -f "${MY_BACKUP_MNT_TEST_FILE}"; then + echo "`date -uIsec`: ${MY_IP}: error:" $* >> "${MY_GLOBAL_LOG_FILE}"; + fi + + # + # On error we normally wait 5min before rebooting to avoid repeating the + # same error too many time before the admin finds out. We choose NOT to + # remove the PXE config file here because (a) the admin might otherwise + # not notice something went wrong, (b) the system could easily be in a + # weird unbootable state, (c) the problem might be temporary. + # + # While debugging, we just exit here. + # + if test -n "${MY_REBOOT_WHEN_DONE}"; then + sleep 5m + echo "testbox-maintenance.sh: rebooting (after error)" >&2; + reboot + fi + exit ${MY_RET} +} + +# +# Try figure out the IP address of the box and the hostname from it again. +# +MY_IP=` hostname -I | cut -f1 -d' ' | head -1 ` +if test -z "${MY_IP}" -o `echo "${MY_IP}" | wc -w` -ne "1" -o "${MY_IP}" = "127.0.0.1"; then + ErrorMsgExit 10 "Failed to get a good IP! (MY_IP=${MY_IP})" +fi +MY_HOSTNAME=`getent hosts "${MY_IP}" | sed -s 's/[[:space:]][[:space:]]*/ /g' | cut -d' ' -f2 ` +if test -z "${MY_HOSTNAME}"; then + MY_HOSTNAME="unknown"; +fi + +# Derive the backup dir and log file name from it. +if test ! -f "${MY_BACKUP_MNT_TEST_FILE}"; then + mount "${MY_BACKUP_ROOT}" + if test ! -f "${MY_BACKUP_MNT_TEST_FILE}"; then + echo "Retrying mounting '${MY_BACKUP_ROOT}' in 15 seconds..." >&2 + sleep 15 + mount "${MY_BACKUP_ROOT}" + fi + if test ! -f "${MY_BACKUP_MNT_TEST_FILE}"; then + ErrorMsgExit 11 "Backup directory is not mounted." + fi +fi +MY_BACKUP_DIR="${MY_BACKUP_ROOT}/${MY_IP}" +MY_LOG_FILE="${MY_BACKUP_DIR}/maintenance.log" +mkdir -p "${MY_BACKUP_DIR}" +echo "================ `date -uIsec`: ${MY_IP}: ${MY_HOSTNAME} starts a new session ================" >> "${MY_LOG_FILE}" +echo "`date -uIsec`: ${MY_IP}: ${MY_HOSTNAME} says hi." >> "${MY_GLOBAL_LOG_FILE}" +InfoMsg "MY_IP=${MY_IP}<eol>" + +# +# Redirect stderr+stdout thru tee and to a log file on the server. +# +MY_OUTPUT_LOG_FILE="${MY_BACKUP_DIR}/maintenance-output.log" +echo "" >> "${MY_OUTPUT_LOG_FILE}" +echo "================ `date -uIsec`: ${MY_IP}: ${MY_HOSTNAME} starts a new session ================" >> "${MY_OUTPUT_LOG_FILE}" +exec &> >(tee -a "${MY_OUTPUT_LOG_FILE}") + +# +# Convert the IP address to PXELINUX hex format, then check that we've got +# a config file on the TFTP share that we later can remove. We consider it a +# fatal failure if we don't because we've probably got the wrong IP and we'll +# be stuck doing the same stuff over and over again. +# +MY_TMP=`echo "${MY_IP}" | sed -e 's/\./ /g' ` +MY_IP_HEX=`printf "%02X%02X%02X%02X" ${MY_TMP}` +InfoMsg "MY_IP_HEX=${MY_IP_HEX}<eol>" + +if test ! -f "${MY_TFTP_ROOT}/pxelinux.0"; then + mount "${MY_TFTP_ROOT}" + if test ! -f "${MY_TFTP_ROOT}/pxelinux.0"; then + echo "Retrying mounting '${MY_TFTP_ROOT}' in 15 seconds..." >&2 + sleep 15 + mount "${MY_BACKUP_ROOT}" + fi + if test ! -f "${MY_TFTP_ROOT}/pxelinux.0"; then + ErrorMsgExit 12 "TFTP share mounted or mixxing pxelinux.0 in the root." + fi +fi + +MY_PXELINUX_CFG_FILE="${MY_TFTP_ROOT}/pxelinux.cfg/${MY_IP_HEX}" +if test ! -f "${MY_PXELINUX_CFG_FILE}"; then + ErrorMsgExit 13 "No pxelinux.cfg file found (${MY_PXELINUX_CFG_FILE}) - wrong IP?" +fi + +# +# Dig the action out of from the kernel command line. +# +if test -n "${MY_REBOOT_WHEN_DONE}"; then + InfoMsg "/proc/cmdline: `cat /proc/cmdline`" + set `cat /proc/cmdline` +else + InfoMsg "Using script command line: $*" +fi +MY_ACTION=not-found +while test $# -ge 1; do + case "$1" in + testbox-action-*) + MY_ACTION="$1" + ;; + esac + shift +done +if test "${MY_ACTION}" = "not-found"; then + ErrorMsgExit 14 "No action given. Expected testbox-action-backup, testbox-action-backup-again, testbox-action-restore," \ + "testbox-action-refresh-info, or testbox-action-rescue on the kernel command line."; +fi + +# Validate and shorten the action. +case "${MY_ACTION}" in + testbox-action-backup) + MY_ACTION="backup"; + ;; + testbox-action-backup-again) + MY_ACTION="backup-again"; + ;; + testbox-action-restore) + MY_ACTION="restore"; + ;; + testbox-action-refresh-info) + MY_ACTION="refresh-info"; + ;; + testbox-action-rescue) + MY_ACTION="rescue"; + ;; + *) ErrorMsgExit 15 "Invalid action '${MY_ACTION}'"; + ;; +esac + +# Log the action in both logs. +echo "`date -uIsec`: ${MY_IP}: info: Executing '${MY_ACTION}'." >> "${MY_GLOBAL_LOG_FILE}"; + +# +# Generate missing info for this testbox if backing up. +# +MY_INFO_FILE="${MY_BACKUP_DIR}/testbox-info.txt" +if test '!' -f "${MY_INFO_FILE}" \ + -o "${MY_ACTION}" = "backup" \ + -o "${MY_ACTION}" = "backup-again" \ + -o "${MY_ACTION}" = "refresh-info" ; +then + echo "IP: ${MY_IP}" > ${MY_INFO_FILE}; + echo "HEX-IP: ${MY_IP_HEX}" >> ${MY_INFO_FILE}; + echo "Hostname: ${MY_HOSTNAME}" >> ${MY_INFO_FILE}; + echo "" >> ${MY_INFO_FILE}; + echo "**** cat /proc/cpuinfo ****" >> ${MY_INFO_FILE}; + echo "**** cat /proc/cpuinfo ****" >> ${MY_INFO_FILE}; + echo "**** cat /proc/cpuinfo ****" >> ${MY_INFO_FILE}; + cat /proc/cpuinfo >> ${MY_INFO_FILE}; + echo "" >> ${MY_INFO_FILE}; + echo "**** lspci -vvv ****" >> ${MY_INFO_FILE}; + echo "**** lspci -vvv ****" >> ${MY_INFO_FILE}; + echo "**** lspci -vvv ****" >> ${MY_INFO_FILE}; + lspci -vvv >> ${MY_INFO_FILE} 2>&1; + echo "" >> ${MY_INFO_FILE}; + echo "**** biosdecode ****" >> ${MY_INFO_FILE}; + echo "**** biosdecode ****" >> ${MY_INFO_FILE}; + echo "**** biosdecode ****" >> ${MY_INFO_FILE}; + biosdecode >> ${MY_INFO_FILE} 2>&1; + echo "" >> ${MY_INFO_FILE}; + echo "**** dmidecode ****" >> ${MY_INFO_FILE}; + echo "**** dmidecode ****" >> ${MY_INFO_FILE}; + echo "**** dmidecode ****" >> ${MY_INFO_FILE}; + dmidecode >> ${MY_INFO_FILE} 2>&1; + echo "" >> ${MY_INFO_FILE}; + echo "**** fdisk -l ****" >> ${MY_INFO_FILE}; + echo "**** fdisk -l ****" >> ${MY_INFO_FILE}; + echo "**** fdisk -l ****" >> ${MY_INFO_FILE}; + fdisk -l >> ${MY_INFO_FILE} 2>&1; + echo "" >> ${MY_INFO_FILE}; + echo "**** dmesg ****" >> ${MY_INFO_FILE}; + echo "**** dmesg ****" >> ${MY_INFO_FILE}; + echo "**** dmesg ****" >> ${MY_INFO_FILE}; + dmesg >> ${MY_INFO_FILE} 2>&1; + + # + # Get the raw ACPI tables and whatnot since we can. Use zip as tar will + # zero pad virtual files due to wrong misleading size returned by stat (4K). + # + # Note! /sys/firmware/dmi/entries/15-0/system_event_log/raw_event_log has been + # see causing fatal I/O errors, so skip all raw_event_log files. + # + zip -qr9 "${MY_BACKUP_DIR}/testbox-info.zip" \ + /proc/cpuinfo \ + /sys/firmware/ \ + -x "*/raw_event_log" +fi + +if test '!' -f "${MY_BACKUP_DIR}/${MY_HOSTNAME}" -a "${MY_HOSTNAME}" != "unknown"; then + echo "${MY_HOSTNAME}" > "${MY_BACKUP_DIR}/${MY_HOSTNAME}" +fi + +if test '!' -f "${MY_BACKUP_DIR}/${MY_IP_HEX}"; then + echo "${MY_IP}" > "${MY_BACKUP_DIR}/${MY_IP_HEX}" +fi + +# +# Assemble a list of block devices using /sys/block/* and some filtering. +# +if test -f "${MY_BACKUP_DIR}/disk-devices.lst"; then + MY_BLOCK_DEVS=`cat ${MY_BACKUP_DIR}/disk-devices.lst \ + | sed -e 's/[[:space:]][::space::]]*/ /g' -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' `; + if test -z "${MY_BLOCK_DEVS}"; then + ErrorMsgExit 17 "No block devices found via sys/block." + fi + InfoMsg "disk-device.lst: MY_BLOCK_DEVS=${MY_BLOCK_DEVS}"; +else + MY_BLOCK_DEVS=""; + for MY_DEV in `ls /sys/block`; do + case "${MY_DEV}" in + [sh]d*) + MY_BLOCK_DEVS="${MY_BLOCK_DEVS} ${MY_DEV}" + ;; + *) InfoMsg "Ignoring /sys/block/${MY_DEV}"; + ;; + esac + done + if test -z "${MY_BLOCK_DEVS}"; then + ErrorMsgExit 17 "No block devices found via /sys/block." + fi + InfoMsg "/sys/block: MY_BLOCK_DEVS=${MY_BLOCK_DEVS}"; +fi + +# +# Take action +# +case "${MY_ACTION}" in + # + # Create a backup. The 'backup' action refuses to overwrite an + # existing backup, but is otherwise identical to 'backup-again'. + # + backup|backup-again) + for MY_DEV in ${MY_BLOCK_DEVS}; do + MY_DST="${MY_BACKUP_DIR}/${MY_DEV}.gz" + if test -f "${MY_DST}"; then + if test "${MY_ACTION}" != 'backup-again'; then + ErrorMsgExit 18 "${MY_DST} already exists" + fi + InfoMsg "${MY_DST} already exists" + fi + done + + # Do the backing up. + for MY_DEV in ${MY_BLOCK_DEVS}; do + MY_SRC="/dev/${MY_DEV}" + MY_DST="${MY_BACKUP_DIR}/${MY_DEV}.gz" + if test -f "${MY_DST}"; then + mv -f "${MY_DST}" "${MY_DST}.old"; + fi + if test -b "${MY_SRC}"; then + InfoMsg "Backing up ${MY_SRC} to ${MY_DST}..."; + dd if="${MY_SRC}" bs=${MY_DD_BLOCK_SIZE} | gzip -c > "${MY_DST}"; + MY_RCS=("${PIPESTATUS[@]}"); + if test "${MY_RCS[0]}" -eq 0 -a "${MY_RCS[1]}" -eq 0; then + InfoMsg "Successfully backed up ${MY_SRC} to ${MY_DST}"; + else + rm -f "${MY_DST}"; + ErrorMsgExit 19 "There was a problem backing up ${MY_SRC} to ${MY_DST}: dd => ${MY_RCS[0]}; gzip => ${MY_RCS[1]}"; + fi + else + InfoMsg "Skipping ${MY_SRC} as it either doesn't exist or isn't a block device"; + fi + done + ;; + + # + # Restore existing. + # + restore) + for MY_DEV in ${MY_BLOCK_DEVS}; do + MY_SRC="${MY_BACKUP_DIR}/${MY_DEV}.gz" + MY_DST="/dev/${MY_DEV}" + if test -b "${MY_DST}"; then + if test -f "${MY_SRC}"; then + InfoMsg "Restoring ${MY_SRC} onto ${MY_DST}..."; + gunzip -c "${MY_SRC}" | dd of="${MY_DST}" bs=${MY_DD_BLOCK_SIZE} iflag=fullblock; + MY_RCS=("${PIPESTATUS[@]}"); + if test ${MY_RCS[0]} -eq 0 -a ${MY_RCS[1]} -eq 0; then + InfoMsg "Successfully restored ${MY_SRC} onto ${MY_DST}"; + else + ErrorMsgExit 20 "There was a problem restoring ${MY_SRC} onto ${MY_DST}: dd => ${MY_RCS[1]}; gunzip => ${MY_RCS[0]}"; + fi + else + InfoMsg "Skipping ${MY_DST} because ${MY_SRC} does not exist."; + fi + else + InfoMsg "Skipping ${MY_DST} as it either doesn't exist or isn't a block device."; + fi + done + ;; + + # + # Nothing else to do for refresh-info. + # + refresh-info) + ;; + + # + # For the rescue action, we just quit without removing the PXE config or + # rebooting the box. The admin will do that once the system has been rescued. + # + rescue) + InfoMsg "rescue: exiting. Admin must remove PXE config and reboot manually when done." + exit 0; + ;; + + *) ErrorMsgExit 98 "Huh? MY_ACTION='${MY_ACTION}'" + ;; +esac + +# +# If we get here, remove the PXE config and reboot immediately. +# +InfoMsg "'${MY_ACTION}' - done"; +if test -n "${MY_REBOOT_WHEN_DONE}"; then + sync + if rm -f "${MY_PXELINUX_CFG_FILE}"; then + InfoMsg "removed ${MY_PXELINUX_CFG_FILE}"; + else + ErrorMsgExit 99 "failed to remove ${MY_PXELINUX_CFG_FILE}"; + fi + sync + InfoMsg "rebooting"; + reboot +fi +exit 0 diff --git a/src/VBox/ValidationKit/docs/testbox-pxe-conf.sh b/src/VBox/ValidationKit/docs/testbox-pxe-conf.sh new file mode 100755 index 00000000..b538f4b3 --- /dev/null +++ b/src/VBox/ValidationKit/docs/testbox-pxe-conf.sh @@ -0,0 +1,162 @@ +#!/bin/bash +# $Id: testbox-pxe-conf.sh $ +## @file +# VirtualBox Validation Kit - testbox pxe config emitter. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + + +# +# Global Variables (config first). +# +MY_NFS_SERVER_IP="10.165.98.101" +MY_GATEWAY_IP="10.165.98.1" +MY_NETMASK="255.255.254.0" +MY_ETH_DEV="eth0" +MY_AUTO_CFG="none" + +# options +MY_PXELINUX_CFG_DIR="/mnt/testbox-tftp/pxelinux.cfg" +MY_ACTION="" +MY_IP="" +MY_IP_HEX="" + +# +# Parse arguments. +# +while test "$#" -ge 1; do + MY_ARG=$1 + shift + case "${MY_ARG}" in + -c|--cfg-dir) + MY_PXELINUX_CFG_DIR="$1"; + shift; + if test -z "${MY_PXELINUX_CFG_DIR}"; then + echo "syntax error: Empty pxeclient.cfg path." >&2; + exit 2; + fi + ;; + + -h|--help) + echo "usage: testbox-pxe-conf.sh: [-c /mnt/testbox-tftp/pxelinux.cfg] <ip> <action>"; + echo "Actions: backup, backup-again, restore, refresh-info, rescue"; + exit 0; + ;; + -*) + echo "syntax error: Invalid option: ${MY_ARG}" >&2; + exit 2; + ;; + + *) if test -z "$MY_ARG"; then + echo "syntax error: Empty argument" >&2; + exit 2; + fi + if test -z "${MY_IP}"; then + # Split up the IP if possible, if not do gethostbyname on the argument. + MY_TMP=`echo "${MY_ARG}" | sed -e 's/\./ /g'` + if test `echo "${MY_TMP}" | wc -w` -ne 4 \ + || ! printf "%02X%02X%02X%02X" ${MY_TMP} > /dev/null 2>&1; then + MY_TMP2=`getent hosts "${MY_ARG}" | head -1 | cut -d' ' -f1`; + MY_TMP=`echo "${MY_TMP2}" | sed -e 's/\./ /g'` + if test `echo "${MY_TMP}" | wc -w` -eq 4 \ + && printf "%02X%02X%02X%02X" ${MY_TMP} > /dev/null 2>&1; then + echo "info: resolved '${MY_ARG}' as '${MY_TMP2}'"; + MY_ARG="${MY_TMP2}"; + else + echo "syntax error: Invalid IP: ${MY_ARG}" >&2; + exit 2; + fi + fi + MY_IP_HEX=`printf "%02X%02X%02X%02X" ${MY_TMP}`; + MY_IP="${MY_ARG}"; + else + if test -z "${MY_ACTION}"; then + case "${MY_ARG}" in + backup|backup-again|restore|refresh-info|rescue) + MY_ACTION="${MY_ARG}"; + ;; + *) + echo "syntax error: Invalid action: ${MY_ARG}" >&2; + exit 2; + ;; + esac + else + echo "syntax error: Too many arguments" >&2; + exit 2; + fi + fi + ;; + esac +done + +if test -z "${MY_ACTION}"; then + echo "syntax error: Insufficient arguments" >&2; + exit 2; +fi +if test ! -d "${MY_PXELINUX_CFG_DIR}"; then + echo "error: pxeclient.cfg path does not point to a directory: ${MY_PXELINUX_CFG_DIR}" >&2; + exit 1; +fi +if test ! -f "${MY_PXELINUX_CFG_DIR}/default"; then + echo "error: pxeclient.cfg path does contain a 'default' file: ${MY_PXELINUX_CFG_DIR}" >&2; + exit 1; +fi + + +# +# Produce the file. +# Using echo here so we can split up the APPEND line more easily. +# +MY_CFG_FILE="${MY_PXELINUX_CFG_DIR}/${MY_IP_HEX}" +set +e +echo "PATH bios" > "${MY_CFG_FILE}"; +echo "DEFAULT maintenance" >> "${MY_CFG_FILE}"; +echo "LABEL maintenance" >> "${MY_CFG_FILE}"; +echo " MENU LABEL Maintenance (NFS)" >> "${MY_CFG_FILE}"; +echo " KERNEL maintenance-boot/vmlinuz-3.16.0-4-amd64" >> "${MY_CFG_FILE}"; +echo -n " APPEND initrd=maintenance-boot/initrd.img-3.16.0-4-amd64 testbox-action-${MY_ACTION}" >> "${MY_CFG_FILE}"; +echo -n " ro aufs=tmpfs boot=nfs root=/dev/nfs" >> "${MY_CFG_FILE}"; +echo -n " nfsroot=${MY_NFS_SERVER_IP}:/export/testbox-nfsroot,ro,tcp" >> "${MY_CFG_FILE}"; +echo -n " nfsvers=3 nfsrootdebug" >> "${MY_CFG_FILE}"; +if test "${MY_AUTO_CFG}" = "none"; then + # Note! Only 6 arguments to ip! Userland ipconfig utility barfs if autoconf and dns options are given. + echo -n " ip=${MY_IP}:${MY_NFS_SERVER_IP}:${MY_GATEWAY_IP}:${MY_NETMASK}:maintenance:${MY_ETH_DEV}" >> "${MY_CFG_FILE}"; +else + echo -n " ip=${MY_AUTO_CFG}" >> "${MY_CFG_FILE}"; +fi +echo "" >> "${MY_CFG_FILE}"; +echo "LABEL local-boot" >> "${MY_CFG_FILE}"; +echo "LOCALBOOT" >> "${MY_CFG_FILE}"; +echo "Successfully generated '${MY_CFG_FILE}'." +exit 0; + diff --git a/src/VBox/ValidationKit/docs/valkit.txt b/src/VBox/ValidationKit/docs/valkit.txt new file mode 100644 index 00000000..9e94eff5 --- /dev/null +++ b/src/VBox/ValidationKit/docs/valkit.txt @@ -0,0 +1 @@ +The VirtualBox ValidationKit ISO. diff --git a/src/VBox/ValidationKit/jshintrc.js b/src/VBox/ValidationKit/jshintrc.js new file mode 100644 index 00000000..ddd640cf --- /dev/null +++ b/src/VBox/ValidationKit/jshintrc.js @@ -0,0 +1,40 @@ +/* $Id: jshintrc.js $ */ +/** @file + * JSHint configuration file. + */ + +/* + * Copyright (C) 2012-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +{ + "laxbreak": true +} + diff --git a/src/VBox/ValidationKit/readme.txt b/src/VBox/ValidationKit/readme.txt new file mode 100644 index 00000000..ac747f2f --- /dev/null +++ b/src/VBox/ValidationKit/readme.txt @@ -0,0 +1,3 @@ + +See docs/VBoxValidationKitReadMe.txt or docs/VBoxValidationKitReadMe.html. + diff --git a/src/VBox/ValidationKit/snippets/alloc-1.c b/src/VBox/ValidationKit/snippets/alloc-1.c new file mode 100644 index 00000000..9b1dbd10 --- /dev/null +++ b/src/VBox/ValidationKit/snippets/alloc-1.c @@ -0,0 +1,110 @@ +/* $Id: alloc-1.c $ */ +/** @file + * Allocate lots of memory, portable ANSI C code. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <stdlib.h> +#include <stdio.h> + + +int main(int argc, char **argv) +{ + unsigned uPct; + unsigned long cbDone; + unsigned long cMBs = 1024; + unsigned long cb; + + /* + * Some quick and dirty argument parsing. + */ + if (argc == 2) + cMBs = strtoul(argv[1], 0, 0); + if (!cMBs || argc > 2) + { + printf("usage: alloc-1 [MBs]\n"); + return 1; + } + cb = cMBs * 1024 * 1024; + if (cb / (1024 * 1024) != cMBs) + cb = ~(unsigned long)0 / (1024 * 1024) * (1024 * 1024); + printf("alloc-1: allocating %lu MB (%lu bytes)\n", cb/1024/1024, cb); + + /* + * The allocation loop. + */ + printf("alloc-1: 0%%"); + fflush(stdout); + cbDone = 0; + uPct = 0; + while (cbDone < cb) + { + unsigned uPctNow; + unsigned long cbThis = cb > 10*1024*1024 ? 10*1024*1024 : cb; + char *pb = malloc(cbThis); + if (!pb) + { + printf("\nalloc-1: calloc failed, cbDone=%lu MB (%lu bytes)\n", + cbDone/1024/1024, cbDone); + return 1; + } + cbDone += cbThis; + + /* touch the memory. */ + while (cbThis >= 0x1000) + { + *pb = (char)cbThis; + pb += 0x1000; + cbThis -= 0x1000; + } + + /* progress */ + uPctNow = 100.0 * cbDone / cb; + if (uPctNow != uPct && !(uPctNow & 1)) + { + if (!(uPctNow % 10)) + printf("%u%%", uPctNow); + else + printf("."); + fflush(stdout); + } + uPct = uPctNow; + } + + printf("\nalloc-1: done\n"); + return 0; +} diff --git a/src/VBox/ValidationKit/snippets/time-1.c b/src/VBox/ValidationKit/snippets/time-1.c new file mode 100644 index 00000000..0381129d --- /dev/null +++ b/src/VBox/ValidationKit/snippets/time-1.c @@ -0,0 +1,123 @@ +/* $Id: time-1.c $ */ +/** @file + * Query the time and check that it always goes forward, POSIX only. + */ + +/* + * Copyright (C) 2011-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <stdio.h> +#include <time.h> +#include <sys/time.h> + + + +int main() +{ + unsigned cErrors = 0; +#ifdef USE_CLOCK_MONOTONIC + struct timespec aTs[2]; + struct timespec *pCur = &aTs[0]; + struct timespec *pPrev = &aTs[1]; + struct timespec *pTmp; +#else + struct timeval aTv[2]; + struct timeval *pCur = &aTv[0]; + struct timeval *pPrev = &aTv[1]; + struct timeval *pTmp; +#endif + +#ifdef USE_CLOCK_MONOTONIC + clock_gettime(CLOCK_MONOTONIC, pPrev); +#else + gettimeofday(pPrev, NULL); +#endif + for (;;) + { +#ifdef USE_CLOCK_MONOTONIC + clock_gettime(CLOCK_MONOTONIC, pCur); +#else + gettimeofday(pCur, NULL); +#endif + + if ( pCur->tv_sec == pPrev->tv_sec +#ifdef USE_CLOCK_MONOTONIC + && pCur->tv_nsec < pPrev->tv_nsec +#else + && pCur->tv_usec < pPrev->tv_usec +#endif + ) + { +#ifdef USE_CLOCK_MONOTONIC + printf("tv_nsec in the past: %ld.%09u < %ld.%09u - %u nsec\n", + (long)pCur->tv_sec, (unsigned)pCur->tv_nsec, + (long)pPrev->tv_sec, (unsigned)pPrev->tv_nsec, + (unsigned)pPrev->tv_nsec - (unsigned)pCur->tv_nsec); +#else + printf("tv_usec in the past: %ld.%06u < %ld.%06u - %u usec\n", + (long)pCur->tv_sec, (unsigned)pCur->tv_usec, + (long)pPrev->tv_sec, (unsigned)pPrev->tv_usec, + (unsigned)pPrev->tv_usec - (unsigned)pCur->tv_usec); +#endif + cErrors++; + if (cErrors > 1000) + break; + } + else if (pCur->tv_sec < pPrev->tv_sec) + { +#ifdef USE_CLOCK_MONOTONIC + printf("tv_sec in the past: %ld.%09u < %ld.%09u\n", + (long)pCur->tv_sec, (unsigned)pCur->tv_nsec, + (long)pPrev->tv_sec, (unsigned)pPrev->tv_nsec); +#else + printf("tv_sec in the past: %ld.%06u < %ld.%06u\n", + (long)pCur->tv_sec, (unsigned)pCur->tv_usec, + (long)pPrev->tv_sec, (unsigned)pPrev->tv_usec); +#endif + cErrors++; + if (cErrors > 1000) + break; + } + else + { + /* swap */ + pTmp = pPrev; + pPrev = pCur; + pCur = pTmp; + } + } + + return 1; +} diff --git a/src/VBox/ValidationKit/testboxscript/Makefile.kmk b/src/VBox/ValidationKit/testboxscript/Makefile.kmk new file mode 100644 index 00000000..c3322d1b --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/Makefile.kmk @@ -0,0 +1,97 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - TestBox Script. +# + +# +# Copyright (C) 2012-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +# +# The TestBox script. +# +INSTALLS += testboxscript +testboxscript_TEMPLATE = VBoxValidationKitR3 +testboxscript_INST = $(INST_TESTBOXSCRIPT)testboxscript/ +testboxscript_EXEC_SOURCES = \ + testboxscript.py \ + $(testboxscript_0_OUTDIR)/testboxscript_real.py \ + setup.sh +$(call VBOX_EDIT_VERSION_RULE_FN,testboxscript,testboxscript_real.py) + +testboxscript_SOURCES = \ + testboxcommand.py \ + testboxcommons.py \ + testboxconnection.py \ + testboxtasks.py \ + testboxupgrade.py + +testboxscript_SOURCES.darwin = \ + darwin/setup-routines.sh=>darwin/setup-routines.sh + +testboxscript_EXEC_SOURCES.linux = \ + linux/testboxscript-service.sh=>linux/testboxscript-service.sh +testboxscript_SOURCES.linux = \ + ../../Installer/linux/routines.sh=>linux/setup-installer-routines.sh \ + linux/setup-routines.sh=>linux/setup-routines.sh + +testboxscript_SOURCES.solaris = \ + solaris/setup-routines.sh=>solaris/setup-routines.sh \ + +testboxscript_SOURCES.win = \ + win/autoexec-testbox.cmd=>win/autoexec-testbox.cmd \ + win/readme.txt=>win/readme.txt \ + $(if $(VBOX_OSE),,win/fix_stale_refs.py=>win/fix_stale_refs.py) + + +# +# Helper program, mostly for obtaining system information. +# +PROGRAMS += TestBoxHelper +TestBoxHelper_TEMPLATE = VBoxValidationKitR3 +TestBoxHelper_INST = $(INST_TESTBOXSCRIPT)$(KBUILD_TARGET)/$(KBUILD_TARGET_ARCH)/ +TestBoxHelper_SOURCES = TestBoxHelper.cpp +TestBoxHelper_LIBS.win = $(PATH_SDK_$(VBOX_WINPSDK)_LIB)/wbemuuid.lib +TestBoxHelper_LDFLAGS.darwin = -framework CoreFoundation +TestBoxHelper_VBOX_IMPORT_CHECKER.win.x86 = $(NO_SUCH_VARIABLE) + + +# +# Generate pylint & pychecker targets. +# +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(wildcard $(PATH_SUB_CURRENT)/*.py) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/testboxscript/TestBoxHelper.cpp b/src/VBox/ValidationKit/testboxscript/TestBoxHelper.cpp new file mode 100644 index 00000000..d6707ac7 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/TestBoxHelper.cpp @@ -0,0 +1,780 @@ +/* $Id: TestBoxHelper.cpp $ */ +/** @file + * VirtualBox Validation Kit - Testbox C Helper Utility. + */ + +/* + * Copyright (C) 2012-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/buildconfig.h> +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/path.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/mp.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/system.h> + +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) +# include <iprt/x86.h> +# include <iprt/asm-amd64-x86.h> +#endif + +#ifdef RT_OS_DARWIN +# include <sys/types.h> +# include <sys/sysctl.h> +#endif + + + +/** + * Does one free space wipe, using the given filename. + * + * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE on failure (fully + * bitched). + * @param pszFilename The filename to use for wiping free space. Will be + * replaced and afterwards deleted. + * @param pvFiller The filler block buffer. + * @param cbFiller The size of the filler block buffer. + * @param cbMinLeftOpt When to stop wiping. + */ +static RTEXITCODE doOneFreeSpaceWipe(const char *pszFilename, void const *pvFiller, size_t cbFiller, uint64_t cbMinLeftOpt) +{ + /* + * Open the file. + */ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + RTFILE hFile = NIL_RTFILE; + int rc = RTFileOpen(&hFile, pszFilename, + RTFILE_O_WRITE | RTFILE_O_DENY_NONE | RTFILE_O_CREATE_REPLACE | (0775 << RTFILE_O_CREATE_MODE_SHIFT)); + if (RT_SUCCESS(rc)) + { + /* + * Query the amount of available free space. Figure out which API we should use. + */ + RTFOFF cbTotal = 0; + RTFOFF cbFree = 0; + rc = RTFileQueryFsSizes(hFile, &cbTotal, &cbFree, NULL, NULL); + bool const fFileHandleApiSupported = rc != VERR_NOT_SUPPORTED && rc != VERR_NOT_IMPLEMENTED; + if (!fFileHandleApiSupported) + rc = RTFsQuerySizes(pszFilename, &cbTotal, &cbFree, NULL, NULL); + if (RT_SUCCESS(rc)) + { + RTPrintf("%s: %'9RTfoff MiB out of %'9RTfoff are free\n", pszFilename, cbFree / _1M, cbTotal / _1M); + + /* + * Start filling up the free space, down to the last 32MB. + */ + uint64_t const nsStart = RTTimeNanoTS(); /* for speed calcs */ + uint64_t nsStat = nsStart; /* for speed calcs */ + uint64_t cbStatWritten = 0; /* for speed calcs */ + RTFOFF const cbMinLeft = RT_MAX(cbMinLeftOpt, cbFiller * 2); + RTFOFF cbLeftToWrite = cbFree - cbMinLeft; + uint64_t cbWritten = 0; + uint32_t iLoop = 0; + while (cbLeftToWrite >= (RTFOFF)cbFiller) + { + rc = RTFileWrite(hFile, pvFiller, cbFiller, NULL); + if (RT_FAILURE(rc)) + { + if (rc == VERR_DISK_FULL) + RTPrintf("%s: Disk full after writing %'9RU64 MiB\n", pszFilename, cbWritten / _1M); + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Write error after %'RU64 bytes: %Rrc\n", + pszFilename, cbWritten, rc); + break; + } + + /* Flush every now and then as we approach a completely full disk. */ + if (cbLeftToWrite <= _1G && (iLoop & (cbLeftToWrite > _128M ? 15 : 3)) == 0) + RTFileFlush(hFile); + + /* + * Advance and maybe recheck the amount of free space. + */ + cbWritten += cbFiller; + cbLeftToWrite -= (ssize_t)cbFiller; + iLoop++; + if ((iLoop & (16 - 1)) == 0 || cbLeftToWrite < _256M) + { + RTFOFF cbFreeUpdated; + if (fFileHandleApiSupported) + rc = RTFileQueryFsSizes(hFile, NULL, &cbFreeUpdated, NULL, NULL); + else + rc = RTFsQuerySizes(pszFilename, NULL, &cbFreeUpdated, NULL, NULL); + if (RT_SUCCESS(rc)) + { + cbFree = cbFreeUpdated; + cbLeftToWrite = cbFree - cbMinLeft; + } + else + { + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Failed to query free space after %'RU64 bytes: %Rrc\n", + pszFilename, cbWritten, rc); + break; + } + if ((iLoop & (512 - 1)) == 0) + { + uint64_t const nsNow = RTTimeNanoTS(); + uint64_t cNsInterval = nsNow - nsStat; + uint64_t cbInterval = cbWritten - cbStatWritten; + uint64_t cbIntervalPerSec = !cbInterval ? 0 + : (uint64_t)((double)cbInterval / ((double)cNsInterval / (double)RT_NS_1SEC)); + + RTPrintf("%s: %'9RTfoff MiB out of %'9RTfoff are free after writing %'9RU64 MiB (%'5RU64 MiB/s)\n", + pszFilename, cbFree / _1M, cbTotal / _1M, cbWritten / _1M, cbIntervalPerSec / _1M); + nsStat = nsNow; + cbStatWritten = cbWritten; + } + } + } + + /* + * Now flush the file and then reduce the size a little before closing + * it so the system won't entirely run out of space. The flush should + * ensure the data has actually hit the disk. + */ + rc = RTFileFlush(hFile); + if (RT_FAILURE(rc)) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Flush failed at %'RU64 bytes: %Rrc\n", pszFilename, cbWritten, rc); + + uint64_t cbReduced = cbWritten > _512M ? cbWritten - _512M : cbWritten / 2; + rc = RTFileSetSize(hFile, cbReduced); + if (RT_FAILURE(rc)) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Failed to reduce file size from %'RU64 to %'RU64 bytes: %Rrc\n", + pszFilename, cbWritten, cbReduced, rc); + + /* Issue a summary statements. */ + uint64_t cNsElapsed = RTTimeNanoTS() - nsStart; + uint64_t cbPerSec = cbWritten ? (uint64_t)((double)cbWritten / ((double)cNsElapsed / (double)RT_NS_1SEC)) : 0; + RTPrintf("%s: Wrote %'RU64 MiB in %'RU64 s, avg %'RU64 MiB/s.\n", + pszFilename, cbWritten / _1M, cNsElapsed / RT_NS_1SEC, cbPerSec / _1M); + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Initial free space query failed: %Rrc \n", pszFilename, rc); + + RTFileClose(hFile); + + /* + * Delete the file. + */ + rc = RTFileDelete(pszFilename); + if (RT_FAILURE(rc)) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Delete failed: %Rrc !!\n", pszFilename, rc); + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Open failed: %Rrc\n", pszFilename, rc); + return rcExit; +} + + +/** + * Wipes free space on one or more volumes by creating large files. + */ +static RTEXITCODE handlerWipeFreeSpace(int argc, char **argv) +{ + /* + * Parse arguments. + */ + const char *apszDefFiles[2] = { "./wipefree.spc", NULL }; + bool fAll = false; + uint32_t u32Filler = UINT32_C(0xf6f6f6f6); + uint64_t cbMinLeftOpt = _32M; + + static RTGETOPTDEF const s_aOptions[] = + { + { "--all", 'a', RTGETOPT_REQ_NOTHING }, + { "--filler", 'f', RTGETOPT_REQ_UINT32 }, + { "--min-free", 'm', RTGETOPT_REQ_UINT64 }, + }; + RTGETOPTSTATE State; + RTGetOptInit(&State, argc, argv, &s_aOptions[0], RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + RTGETOPTUNION ValueUnion; + int chOpt; + while ( (chOpt = RTGetOpt(&State, &ValueUnion)) != 0 + && chOpt != VINF_GETOPT_NOT_OPTION) + { + switch (chOpt) + { + case 'a': + fAll = true; + break; + case 'f': + u32Filler = ValueUnion.u32; + break; + case 'm': + cbMinLeftOpt = ValueUnion.u64; + break; + case 'h': + RTPrintf("usage: wipefrespace [options] [filename1 [..]]\n" + "\n" + "Options:\n" + " -a, --all\n" + " Try do the free space wiping on all seemingly relevant file systems.\n" + " Changes the meaning of the filenames " + " This is not yet implemented\n" + " -p, --filler <32-bit value>\n" + " What to fill the blocks we write with.\n" + " Default: 0xf6f6f6f6\n" + " -m, --min-free <64-bit byte count>\n" + " Specifies when to stop in terms of free disk space (in bytes).\n" + " Default: 32MB\n" + "\n" + "Zero or more names of files to do the free space wiping thru can be given.\n" + "When --all is NOT used, each of the files are used to do free space wiping on\n" + "the volume they will live on. However, when --all is in effect the files are\n" + "appended to the volume mountpoints and only the first that can be created will\n" + "be used. Files (used ones) will be removed when done.\n" + "\n" + "If no filename is given, the default is: %s\n" + , apszDefFiles[0]); + return RTEXITCODE_SUCCESS; + + default: + return RTGetOptPrintError(chOpt, &ValueUnion); + } + } + + char **papszFiles; + if (chOpt == 0) + papszFiles = (char **)apszDefFiles; + else + papszFiles = RTGetOptNonOptionArrayPtr(&State); + + /* + * Allocate and prep a memory which we'll write over and over again. + */ + uint32_t cbFiller = _2M; + uint32_t *pu32Filler = (uint32_t *)RTMemPageAlloc(cbFiller); + while (!pu32Filler) + { + cbFiller <<= 1; + if (cbFiller >= _4K) + pu32Filler = (uint32_t *)RTMemPageAlloc(cbFiller); + else + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTMemPageAlloc failed for sizes between 4KB and 2MB!\n"); + } + for (uint32_t i = 0; i < cbFiller / sizeof(pu32Filler[0]); i++) + pu32Filler[i] = u32Filler; + + /* + * Do the requested work. + */ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + if (!fAll) + { + for (uint32_t iFile = 0; papszFiles[iFile] != NULL; iFile++) + { + RTEXITCODE rcExit2 = doOneFreeSpaceWipe(papszFiles[iFile], pu32Filler, cbFiller, cbMinLeftOpt); + if (rcExit2 != RTEXITCODE_SUCCESS && rcExit == RTEXITCODE_SUCCESS) + rcExit = rcExit2; + } + } + else + { + /* + * Reject --all for now. + */ + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "The --all option is not yet implemented!\n"); + } + + RTMemPageFree(pu32Filler, cbFiller); + return rcExit; +} + + +/** + * Generates a kind of report of the hardware, software and whatever else we + * think might be useful to know about the testbox. + */ +static RTEXITCODE handlerReport(int argc, char **argv) +{ + NOREF(argc); NOREF(argv); + +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + /* + * For now, a simple CPUID dump. Need to figure out how to share code + * like this with other bits, putting it in IPRT. + */ + RTPrintf("CPUID Dump\n" + "Leaf eax ebx ecx edx\n" + "---------------------------------------------\n"); + static uint32_t const s_auRanges[] = + { + UINT32_C(0x00000000), + UINT32_C(0x80000000), + UINT32_C(0x80860000), + UINT32_C(0xc0000000), + UINT32_C(0x40000000), + }; + for (uint32_t iRange = 0; iRange < RT_ELEMENTS(s_auRanges); iRange++) + { + uint32_t const uFirst = s_auRanges[iRange]; + + uint32_t uEax, uEbx, uEcx, uEdx; + ASMCpuIdExSlow(uFirst, 0, 0, 0, &uEax, &uEbx, &uEcx, &uEdx); + if (uEax >= uFirst && uEax < uFirst + 100) + { + uint32_t const cLeafs = RT_MIN(uEax - uFirst + 1, 32); + for (uint32_t iLeaf = 0; iLeaf < cLeafs; iLeaf++) + { + uint32_t uLeaf = uFirst + iLeaf; + ASMCpuIdExSlow(uLeaf, 0, 0, 0, &uEax, &uEbx, &uEcx, &uEdx); + + /* Clear APIC IDs to avoid submitting new reports all the time. */ + if (uLeaf == 1) + uEbx &= UINT32_C(0x00ffffff); + if (uLeaf == 0xb) + uEdx = 0; + if (uLeaf == 0x8000001e) + uEax = 0; + + /* Clear some other node/cpu/core/thread ids. */ + if (uLeaf == 0x8000001e) + { + uEbx &= UINT32_C(0xffffff00); + uEcx &= UINT32_C(0xffffff00); + } + + RTPrintf("%08x: %08x %08x %08x %08x\n", uLeaf, uEax, uEbx, uEcx, uEdx); + } + } + } + RTPrintf("\n"); + + /* + * DMI info. + */ + RTPrintf("DMI Info\n" + "--------\n"); + static const struct { const char *pszName; RTSYSDMISTR enmDmiStr; } s_aDmiStrings[] = + { + { "Product Name", RTSYSDMISTR_PRODUCT_NAME }, + { "Product version", RTSYSDMISTR_PRODUCT_VERSION }, + { "Product UUID", RTSYSDMISTR_PRODUCT_UUID }, + { "Product Serial", RTSYSDMISTR_PRODUCT_SERIAL }, + { "System Manufacturer", RTSYSDMISTR_MANUFACTURER }, + }; + for (uint32_t iDmiString = 0; iDmiString < RT_ELEMENTS(s_aDmiStrings); iDmiString++) + { + char szTmp[4096]; + RT_ZERO(szTmp); + int rc = RTSystemQueryDmiString(s_aDmiStrings[iDmiString].enmDmiStr, szTmp, sizeof(szTmp) - 1); + if (RT_SUCCESS(rc)) + RTPrintf("%25s: %s\n", s_aDmiStrings[iDmiString].pszName, RTStrStrip(szTmp)); + else + RTPrintf("%25s: %s [rc=%Rrc]\n", s_aDmiStrings[iDmiString].pszName, RTStrStrip(szTmp), rc); + } + RTPrintf("\n"); + +#else +#endif + + /* + * Dump the environment. + */ + RTPrintf("Environment\n" + "-----------\n"); + RTENV hEnv; + int rc = RTEnvClone(&hEnv, RTENV_DEFAULT); + if (RT_SUCCESS(rc)) + { + uint32_t cVars = RTEnvCountEx(hEnv); + for (uint32_t iVar = 0; iVar < cVars; iVar++) + { + char szVar[1024]; + char szValue[16384]; + rc = RTEnvGetByIndexEx(hEnv, iVar, szVar, sizeof(szVar), szValue, sizeof(szValue)); + + /* zap the value of variables that are subject to change. */ + if ( (RT_SUCCESS(rc) || rc == VERR_BUFFER_OVERFLOW) + && ( !strcmp(szVar, "TESTBOX_SCRIPT_REV") + || !strcmp(szVar, "TESTBOX_ID") + || !strcmp(szVar, "TESTBOX_SCRATCH_SIZE") + || !strcmp(szVar, "TESTBOX_TIMEOUT") + || !strcmp(szVar, "TESTBOX_TIMEOUT_ABS") + || !strcmp(szVar, "TESTBOX_TEST_SET_ID") + ) + ) + strcpy(szValue, "<volatile>"); + + if (RT_SUCCESS(rc)) + RTPrintf("%25s=%s\n", szVar, szValue); + else if (rc == VERR_BUFFER_OVERFLOW) + RTPrintf("%25s=%s [VERR_BUFFER_OVERFLOW]\n", szVar, szValue); + else + RTPrintf("rc=%Rrc\n", rc); + } + RTEnvDestroy(hEnv); + } + + /** @todo enumerate volumes and whatnot. */ + + int cch = RTPrintf("\n"); + return cch > 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** Print the total memory size in bytes. */ +static RTEXITCODE handlerMemSize(int argc, char **argv) +{ + NOREF(argc); NOREF(argv); + + uint64_t cb; + int rc = RTSystemQueryTotalRam(&cb); + if (RT_SUCCESS(rc)) + { + int cch = RTPrintf("%llu\n", cb); + return cch > 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; + } + RTPrintf("%Rrc\n", rc); + return RTEXITCODE_FAILURE; +} + +typedef enum { HWVIRTTYPE_NONE, HWVIRTTYPE_VTX, HWVIRTTYPE_AMDV } HWVIRTTYPE; +static HWVIRTTYPE isHwVirtSupported(void) +{ +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + uint32_t uEax, uEbx, uEcx, uEdx; + + /* VT-x */ + ASMCpuId(0x00000000, &uEax, &uEbx, &uEcx, &uEdx); + if (RTX86IsValidStdRange(uEax)) + { + ASMCpuId(0x00000001, &uEax, &uEbx, &uEcx, &uEdx); + if (uEcx & X86_CPUID_FEATURE_ECX_VMX) + return HWVIRTTYPE_VTX; + } + + /* AMD-V */ + ASMCpuId(0x80000000, &uEax, &uEbx, &uEcx, &uEdx); + if (RTX86IsValidExtRange(uEax)) + { + ASMCpuId(0x80000001, &uEax, &uEbx, &uEcx, &uEdx); + if (uEcx & X86_CPUID_AMD_FEATURE_ECX_SVM) + return HWVIRTTYPE_AMDV; + } +#endif + + return HWVIRTTYPE_NONE; +} + +/** Print the 'true' if VT-x or AMD-v is supported, 'false' it not. */ +static RTEXITCODE handlerCpuHwVirt(int argc, char **argv) +{ + NOREF(argc); NOREF(argv); + int cch = RTPrintf(isHwVirtSupported() != HWVIRTTYPE_NONE ? "true\n" : "false\n"); + return cch > 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** Print the 'true' if nested paging is supported, 'false' if not and + * 'dunno' if we cannot tell. */ +static RTEXITCODE handlerCpuNestedPaging(int argc, char **argv) +{ + NOREF(argc); NOREF(argv); + HWVIRTTYPE enmHwVirt = isHwVirtSupported(); + int fSupported = -1; + +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + if (enmHwVirt == HWVIRTTYPE_AMDV) + { + uint32_t uEax, uEbx, uEcx, uEdx; + ASMCpuId(0x80000000, &uEax, &uEbx, &uEcx, &uEdx); + if (RTX86IsValidExtRange(uEax) && uEax >= 0x8000000a) + { + ASMCpuId(0x8000000a, &uEax, &uEbx, &uEcx, &uEdx); + if (uEdx & RT_BIT(0) /* AMD_CPUID_SVM_FEATURE_EDX_NESTED_PAGING */) + fSupported = 1; + else + fSupported = 0; + } + } +# if defined(RT_OS_LINUX) + else if (enmHwVirt == HWVIRTTYPE_VTX) + { + /* + * For Intel there is no generic way to query EPT support but on + * Linux we can resort to checking for the EPT flag in /proc/cpuinfo + */ + RTFILE hFileCpu; + int rc = RTFileOpen(&hFileCpu, "/proc/cpuinfo", RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + /* + * Read enough to fit the first CPU entry in, we only check the first + * CPU as all the others should have the same features. + */ + char szBuf[_4K]; + size_t cbRead = 0; + + RT_ZERO(szBuf); /* Ensure proper termination. */ + rc = RTFileRead(hFileCpu, &szBuf[0], sizeof(szBuf) - 1, &cbRead); + if (RT_SUCCESS(rc)) + { + /* Look for the start of the flags section. */ + char *pszStrFlags = RTStrStr(&szBuf[0], "flags"); + if (pszStrFlags) + { + /* Look for the end as indicated by new line. */ + char *pszEnd = pszStrFlags; + while ( *pszEnd != '\0' + && *pszEnd != '\n') + pszEnd++; + *pszEnd = '\0'; /* Cut off everything after the flags section. */ + + /* + * Search for the ept flag indicating support and the absence meaning + * not supported. + */ + if (RTStrStr(pszStrFlags, "ept")) + fSupported = 1; + else + fSupported = 0; + } + } + RTFileClose(hFileCpu); + } + } +# elif defined(RT_OS_DARWIN) + else if (enmHwVirt == HWVIRTTYPE_VTX) + { + /* + * The kern.hv_support parameter indicates support for the hypervisor API in the + * kernel, which in turn is documented require nested paging and unrestricted + * guest mode. So, if it's there and set we've got nested paging. Howeber, if + * it's there and clear we have not definite answer as it might be due to lack + * of unrestricted guest mode support. + */ + int32_t fHvSupport = 0; + size_t cbOld = sizeof(fHvSupport); + if (sysctlbyname("kern.hv_support", &fHvSupport, &cbOld, NULL, 0) == 0) + { + if (fHvSupport != 0) + fSupported = true; + } + } +# endif +#endif + + int cch = RTPrintf(fSupported == 1 ? "true\n" : fSupported == 0 ? "false\n" : "dunno\n"); + return cch > 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** Print the 'true' if long mode guests are supported, 'false' if not and + * 'dunno' if we cannot tell. */ +static RTEXITCODE handlerCpuLongMode(int argc, char **argv) +{ + NOREF(argc); NOREF(argv); + HWVIRTTYPE enmHwVirt = isHwVirtSupported(); + int fSupported = 0; + + if (enmHwVirt != HWVIRTTYPE_NONE) + { +#if defined(RT_ARCH_AMD64) + fSupported = 1; /* We're running long mode, so it must be supported. */ + +#elif defined(RT_ARCH_X86) +# ifdef RT_OS_DARWIN + /* On darwin, we just ask the kernel via sysctl. Rules are a bit different here. */ + int f64bitCapable = 0; + size_t cbParameter = sizeof(f64bitCapable); + int rc = sysctlbyname("hw.cpu64bit_capable", &f64bitCapable, &cbParameter, NULL, 0); + if (rc != -1) + fSupported = f64bitCapable != 0; + else +# endif + { + /* PAE and HwVirt are required */ + uint32_t uEax, uEbx, uEcx, uEdx; + ASMCpuId(0x00000000, &uEax, &uEbx, &uEcx, &uEdx); + if (RTX86IsValidStdRange(uEax)) + { + ASMCpuId(0x00000001, &uEax, &uEbx, &uEcx, &uEdx); + if (uEdx & X86_CPUID_FEATURE_EDX_PAE) + { + /* AMD will usually advertise long mode in 32-bit mode. Intel OTOH, + won't necessarily do so. */ + ASMCpuId(0x80000000, &uEax, &uEbx, &uEcx, &uEdx); + if (RTX86IsValidExtRange(uEax)) + { + ASMCpuId(0x80000001, &uEax, &uEbx, &uEcx, &uEdx); + if (uEdx & X86_CPUID_EXT_FEATURE_EDX_LONG_MODE) + fSupported = 1; + else if (enmHwVirt != HWVIRTTYPE_AMDV) + fSupported = -1; + } + } + } + } +#endif + } + + int cch = RTPrintf(fSupported == 1 ? "true\n" : fSupported == 0 ? "false\n" : "dunno\n"); + return cch > 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** Print the CPU 'revision', if available. */ +static RTEXITCODE handlerCpuRevision(int argc, char **argv) +{ + NOREF(argc); NOREF(argv); + +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + uint32_t uEax, uEbx, uEcx, uEdx; + ASMCpuId(0, &uEax, &uEbx, &uEcx, &uEdx); + if (RTX86IsValidStdRange(uEax) && uEax >= 1) + { + uint32_t uEax1 = ASMCpuId_EAX(1); + uint32_t uVersion = (RTX86GetCpuFamily(uEax1) << 24) + | (RTX86GetCpuModel(uEax1, RTX86IsIntelCpu(uEbx, uEcx, uEdx)) << 8) + | RTX86GetCpuStepping(uEax1); + int cch = RTPrintf("%#x\n", uVersion); + return cch > 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; + } +#endif + return RTEXITCODE_FAILURE; +} + + +/** Print the CPU name, if available. */ +static RTEXITCODE handlerCpuName(int argc, char **argv) +{ + NOREF(argc); NOREF(argv); + + char szTmp[1024]; + int rc = RTMpGetDescription(NIL_RTCPUID, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(rc)) + { + int cch = RTPrintf("%s\n", RTStrStrip(szTmp)); + return cch > 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; + } + return RTEXITCODE_FAILURE; +} + + +/** Print the CPU vendor name, 'GenuineIntel' and such. */ +static RTEXITCODE handlerCpuVendor(int argc, char **argv) +{ + NOREF(argc); NOREF(argv); + +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + uint32_t uEax, uEbx, uEcx, uEdx; + ASMCpuId(0, &uEax, &uEbx, &uEcx, &uEdx); + int cch = RTPrintf("%.04s%.04s%.04s\n", &uEbx, &uEdx, &uEcx); +#else + int cch = RTPrintf("%s\n", RTBldCfgTargetArch()); +#endif + return cch > 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * The first argument is a command. Figure out which and call its handler. + */ + static const struct + { + const char *pszCommand; + RTEXITCODE (*pfnHandler)(int argc, char **argv); + bool fNoArgs; + } s_aHandlers[] = + { + { "cpuvendor", handlerCpuVendor, true }, + { "cpuname", handlerCpuName, true }, + { "cpurevision", handlerCpuRevision, true }, + { "cpuhwvirt", handlerCpuHwVirt, true }, + { "nestedpaging", handlerCpuNestedPaging, true }, + { "longmode", handlerCpuLongMode, true }, + { "memsize", handlerMemSize, true }, + { "report", handlerReport, true }, + { "wipefreespace", handlerWipeFreeSpace, false } + }; + + if (argc < 2) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "expected command as the first argument"); + + for (unsigned i = 0; i < RT_ELEMENTS(s_aHandlers); i++) + { + if (!strcmp(argv[1], s_aHandlers[i].pszCommand)) + { + if ( s_aHandlers[i].fNoArgs + && argc != 2) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "the command '%s' does not take any arguments", argv[1]); + return s_aHandlers[i].pfnHandler(argc - 1, argv + 1); + } + } + + /* + * Help or version query? + */ + for (int i = 1; i < argc; i++) + if ( !strcmp(argv[i], "--help") + || !strcmp(argv[i], "-h") + || !strcmp(argv[i], "-?") + || !strcmp(argv[i], "help") ) + { + RTPrintf("usage: %s <cmd> [cmd specific args]\n" + "\n" + "commands:\n", argv[0]); + for (unsigned j = 0; j < RT_ELEMENTS(s_aHandlers); j++) + RTPrintf(" %s\n", s_aHandlers[j].pszCommand); + return RTEXITCODE_FAILURE; + } + else if ( !strcmp(argv[i], "--version") + || !strcmp(argv[i], "-V") ) + { + RTPrintf("%sr%u", RTBldCfgVersion(), RTBldCfgRevision()); + return argc == 2 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; + } + + /* + * Syntax error. + */ + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "unknown command '%s'", argv[1]); +} + diff --git a/src/VBox/ValidationKit/testboxscript/darwin/setup-routines.sh b/src/VBox/ValidationKit/testboxscript/darwin/setup-routines.sh new file mode 100644 index 00000000..9ef924d1 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/darwin/setup-routines.sh @@ -0,0 +1,190 @@ +# $Id: setup-routines.sh $ +## @file +# VirtualBox Validation Kit - TestBoxScript Service Setup on Mac OS X (darwin). +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +MY_CONFIG_FILE=/Library/LaunchDaemons/org.virtualbox.testboxscript.plist + +## +# Loads config values from the current installation. +# +os_load_config() { + if [ -r "${MY_CONFIG_FILE}" ]; then + # User. + MY_TMP=`/usr/bin/tr '\n' ' ' < "${MY_CONFIG_FILE}" \ + | /usr/bin/sed \ + -e 's/ */ /g' \ + -e 's|\(</[[:alnum:]]*>\)<|\1 <|g' \ + -e 's|^.*<key>UserName</key> *<string>\([^<>]*\)</string>.*$|\1|'`; + if [ -n "${MY_TMP}" ]; then + TESTBOXSCRIPT_USER="${MY_TMP}"; + fi + + # Arguments. + XMLARGS=`/usr/bin/tr '\n' ' ' < "${MY_CONFIG_FILE}" \ + | /usr/bin/sed \ + -e 's/ */ /g' \ + -e 's|\(</[[:alnum:]]*>\)<|\1 <|g' \ + -e 's|^.*ProgramArguments</key> *<array> *\(.*\)</array>.*$|\1|'`; + eval common_testboxscript_args_to_config `echo "${XMLARGS}" | sed -e "s/<string>/'/g" -e "s/<\/string>/'/g" `; + fi +} + +## +# Adds an argument ($1) to MY_ARGV (XML plist format). +# +os_add_args() { + while [ $# -gt 0 ]; + do + case "$1" in + *\<* | *\>* | *\&*) + MY_TMP='`echo "$1" | sed -e 's/&/&/g' -e 's/</</g' -e 's/>/>/g'`'; + MY_ARGV="${MY_ARGV} <string>${MY_TMP}</string>"; + ;; + *) + MY_ARGV="${MY_ARGV} <string>$1</string>"; + ;; + esac + shift; + done + MY_ARGV="${MY_ARGV}"' + '; + return 0; +} + +os_install_service() { + # Calc the command line. + MY_ARGV="" + common_compile_testboxscript_command_line + + + # Note! It's not possible to use screen 4.0.3 with the launchd due to buggy + # "setsid off" handling (and possible other things). + cat > "${MY_CONFIG_FILE}" <<EOF +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>Label</key> <string>org.virtualbox.testboxscript</string> + <key>UserName</key> <string>${TESTBOXSCRIPT_USER}</string> + <key>WorkingDirectory</key> <string>${TESTBOXSCRIPT_DIR}</string> + <key>Enabled</key> <true/> + <key>RunAtLoad</key> <true/> + <key>KeepAlive</key> <true/> + <key>StandardInPath</key> <string>/dev/null</string> + <key>StandardOutPath</key> <string>/dev/null</string> + <key>StandardErrorPath</key> <string>/dev/null</string> + <key>ProgramArguments</key> + <array> + ${MY_ARGV}</array> +</dict> +</plist> +EOF + + return 0; +} + +os_enable_service() { + launchctl load -w "${MY_CONFIG_FILE}" + return 0; +} + +os_disable_service() { + if [ -r "${MY_CONFIG_FILE}" ]; then + launchctl unload "${MY_CONFIG_FILE}" + fi + return 0; +} + +os_add_user() { + NEWUID=$(expr `dscl . -readall /Users UniqueID | sed -ne 's/UniqueID: *\([0123456789]*\) *$/\1/p' | sort -n | tail -1 ` + 1) + if [ -z "$NEWUID" -o "${NEWUID}" -lt 502 ]; then + NEWUID=502; + fi + + dscl . -create "/Users/${TESTBOXSCRIPT_USER}" UserShell /bin/bash + dscl . -create "/Users/${TESTBOXSCRIPT_USER}" RealName "VBox Test User" + dscl . -create "/Users/${TESTBOXSCRIPT_USER}" UniqueID ${NEWUID} + dscl . -create "/Users/${TESTBOXSCRIPT_USER}" PrimaryGroupID 80 + dscl . -create "/Users/${TESTBOXSCRIPT_USER}" NFSHomeDirectory "/Users/vbox" + dscl . -passwd "/Users/${TESTBOXSCRIPT_USER}" "password" + mkdir -p "/Users/${TESTBOXSCRIPT_USER}" +} + +os_final_message() { + cat <<EOF + +Additional things to do:" + 1. Change the 'Energy Saver' options to never turn off the computer: + $ systemsetup -setcomputersleep Never -setdisplaysleep 5 -setharddisksleep 15 + 2. Check 'Restart automatically if the computer freezes' if available in + the 'Energy Saver' settings. + $ systemsetup -setrestartfreeze on + 3. In the 'Sharing' panel enable (VBox/Oracle): + a) 'Remote Login' so ssh works. + $ systemsetup -setremotelogin on + b) 'Remote Management, tick all the checkboxes in the sheet dialog. + Open the 'Computer Settings' and check 'Show Remote Management + status in menu bar', 'Anyone may request permission to control + screen' and 'VNC viewers may control screen with password'. Set the + VNC password to 'password'. + 4. Make sure the proxy is configured correctly for your network by going to + the 'Network' panel, open 'Advanced...'. For Oracle this means 'TCP/IP' + should be configured by 'DHCP' (IPv4) and 'automatically' (IPv6), and + the 'Proxies' tab should have 'Automatic Proxy Configuration' checked + with the URL containing 'http://wpad.oracle.com/wpad.dat'. (Make sure + to hit OK to close the dialog.) + 5. Configure NTP to the nearest local time source. For VBox/Oracle this + means wei01-time.de.oracle.com: + $ systemsetup -setnetworktimeserver wei01-time.de.oracle.com + 6. Configure the vbox (pw:password) account for automatic login. + 7. For configure the kernel to keep symbols you might need to: + a) For 10.11 (El Capitan) and later boot to the recovery partition and + either enabling loading of unsigned kexts: + $ csrutil enable --without kext + or disable SIP all together: + $ csrutil disable + b) For 10.15 (Catalina) and later you also need to disable + the reboot requirement (also from recovery partition): + $ spctl kext-consent disable + c) If you are running 10.10 (Yosemite) there is a boot-args option for + allowing the loading of unsigned kexts. Run the following and reboot: + $ sudo nvram boot-args="kext-dev-mode=1" + And then run the following: + $ sudo nvram boot-args="keepsyms=1" + +Enjoy! +EOF +} + diff --git a/src/VBox/ValidationKit/testboxscript/linux/setup-routines.sh b/src/VBox/ValidationKit/testboxscript/linux/setup-routines.sh new file mode 100755 index 00000000..28c3d0db --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/linux/setup-routines.sh @@ -0,0 +1,172 @@ +#!/bin/sh +# $Id: setup-routines.sh $ +## @file +# VirtualBox Validation Kit - TestBoxScript Service Setup. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + + +# Load the routines we share with the linux installer. +if test ! -r "${DIR}/linux/setup-installer-routines.sh" -a -r "${DIR}/../../Installer/linux/routines.sh"; then + . "${DIR}/../../Installer/linux/routines.sh" +else + . "${DIR}/linux/setup-installer-routines.sh" +fi + + +os_load_config() { + if [ -d /etc/conf.d/ ]; then + MY_CONFIG_FILE="/etc/conf.d/testboxscript" + elif [ -d /etc/default/ ]; then + MY_CONFIG_FILE="/etc/default/testboxscript" + else + echo "Port me!" + exit 1; + fi + if [ -r "${MY_CONFIG_FILE}" ]; then + . "${MY_CONFIG_FILE}" + fi +} + +os_install_service() { + # + # Install the runlevel script. + # + install_init_script "${TESTBOXSCRIPT_DIR}/testboxscript/linux/testboxscript-service.sh" "testboxscript-service" + set +e + delrunlevel "testboxscript-service" > /dev/null 2>&1 + addrunlevel "testboxscript-service" 90 10 + set -e + + # + # Install the configuration file. + # + echo "# Generated by $0." > "${MY_CONFIG_FILE}" + for var in ${TESTBOXSCRIPT_CFG_NAMES}; + do + varcfg=TESTBOXSCRIPT_${var} + vardef=TESTBOXSCRIPT_DEFAULT_${var} + if [ "${!varcfg}" = "${!vardef}" ]; then + echo "# using default value: ${varcfg}=${!varcfg}" >> "${MY_CONFIG_FILE}" + else + echo "${varcfg}=${!varcfg}" >> "${MY_CONFIG_FILE}" + fi + done + + # Work around a bug with arrays in old bash versions. + if [ ${#TESTBOXSCRIPT_ENVVARS[@]} -ne 0 ]; then + set | sed -n -e '/^TESTBOXSCRIPT_ENVVARS=/p' >> "${MY_CONFIG_FILE}" + fi + return 0; +} + +os_enable_service() { + start_init_script testboxscript-service + return 0; +} + +os_disable_service() { + stop_init_script testboxscript-service 2>&1 || true # Ignore + return 0; +} + +os_add_user() { + ADD_GROUPS="" + if ! grep -q wheel /etc/group; then + ADD_GROUPS="-G wheel" + fi + set -e + useradd -m -U -p password -s /bin/bash ${ADD_GROUPS} "${TESTBOXSCRIPT_USER}" + set +e + return 0; +} + +check_for_cifs() { + test -x /sbin/mount.cifs -o -x /usr/sbin/mount.cifs + grep -wq cifs /proc/filesystems || modprobe cifs; + # Note! If modprobe doesn't work above, /sbin and /usr/sbin are probably missing from the search PATH. + return 0; +} + +## +# Test if core dumps are enabled. See https://wiki.ubuntu.com/Apport! +# +test_coredumps() { + if test "`lsb_release -is`" = "Ubuntu"; then + if grep -q "apport" /proc/sys/kernel/core_pattern; then + if grep -q "#.*problem_types" /etc/apport/crashdb.conf; then + echo "It looks like core dumps are properly configured, good!" + else + echo "Warning: Core dumps will be not always generated!" + fi + else + echo "Warning: Apport not installed! This package is required for core dump handling!" + fi + fi +} + +## +# Test if unattended updates are disabled. See +# http://ask.xmodulo.com/disable-automatic-updates-ubuntu.html +test_unattended_updates_disabled() { + if grep "APT::Periodic::Unattended-Upgrade.*1" /etc/apt/apt.conf.d/* 2>/dev/null; then + echo "Unattended updates enabled?" + return 1 + fi + if grep "APT::Periodic::Update-Package-List.*1" /etc/apt/apt.conf.d/* 2>/dev/null; then + echo "Unattended package updates enabled?" + return 1 + fi +} + +os_final_message() { + cat <<EOF + +Additional things to do:" + 1. Check if the proxy settings are appropriate for reaching the test + manager host. Python does not support domain matches starting with ".". + + For Debian and Ubuntu: check /etc/environment. + For EL: check /etc/profile and/or the files in /etc/profile.d/. + + 2. If the system should be doing RAM disk based testing, add the following + (or something similar, adapted to the system) to /etc/fstab: + + tmpfs /var/tmp/testbox-1000 tmpfs defaults,size=16G 0 0 + +After making such adjustments, it's the easiest solution to reboot the testbox. + +Enjoy! +EOF +} + diff --git a/src/VBox/ValidationKit/testboxscript/linux/testboxscript-service.sh b/src/VBox/ValidationKit/testboxscript/linux/testboxscript-service.sh new file mode 100755 index 00000000..9708cdea --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/linux/testboxscript-service.sh @@ -0,0 +1,519 @@ +#!/bin/sh +## @file +# VirtualBox Validation Kit - TestBoxScript service init script. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +# chkconfig: 35 35 65 +# description: TestBoxScript service +# +### BEGIN INIT INFO +# Provides: testboxscript-service +# Required-Start: $network +# Required-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Description: TestBoxScript service +### END INIT INFO + + +PATH=$PATH:/bin:/sbin:/usr/sbin + +# +# Load config and set up defaults. +# +service_name="testboxscript" + +[ -r /etc/default/${service_name} ] && . /etc/default/${service_name} +[ -r /etc/conf.d/${service_name} ] && . /etc/conf.d/${service_name} + +if [ -z "${TESTBOXSCRIPT_DIR}" ]; then + TESTBOXSCRIPT_DIR="/opt/testboxscript" +fi +if [ -z "${TESTBOXSCRIPT_USER}" ]; then + TESTBOXSCRIPT_USER="vbox" +fi +binary="${TESTBOXSCRIPT_DIR}/testboxscript/testboxscript.py" +binary_real="${TESTBOXSCRIPT_DIR}/testboxscript/testboxscript_real.py" + + + +# +# Detect and abstract distro +# +[ -f /etc/debian_release -a -f /lib/lsb/init-functions ] || NOLSB=yes + +system=unknown +if [ -f /etc/redhat-release ]; then + system=redhat + PIDFILE="/var/run/${service_name}-service.pid" +elif [ -f /etc/SuSE-release ]; then + system=suse + PIDFILE="/var/lock/subsys/${service_name}-service" +elif [ -f /etc/debian_version ]; then + system=debian + PIDFILE="/var/run/${service_name}-service" +elif [ -f /etc/gentoo-release ]; then + system=gentoo + PIDFILE="/var/run/${service_name}-service" +elif [ -f /etc/arch-release ]; then + system=arch + PIDFILE="/var/run/${service_name}-service" +elif [ -f /etc/slackware-version ]; then + system=slackware + PIDFILE="/var/run/${service_name}-service" +elif [ -f /etc/lfs-release ]; then + system=lfs + PIDFILE="/var/run/${service_name}-service.pid" +else + system=other + if [ -d /var/run -a -w /var/run ]; then + PIDFILE="/var/run/${service_name}-service" + fi +fi + + +# +# Generic implementation. +# + +## Query daemon status. +# $1 = daemon-user; $2 = binary name +# returns 0 if running, 1 if started but no longer running, 3 if not started. +# When 0 is return the pid variable contains a list of relevant pids. +my_query_status() { + a_USER="$1"; + a_BINARY="$2"; + pid=""; + if [ -f "${PIDFILE}" -a -s "${PIDFILE}" ]; then + MY_LINE=""; + read MY_LINE < "${PIDFILE}"; + for MY_PID in `echo $MY_LINE | sed -e 's/[^0123456789 ]/ /g'`; + do + if [ "`stat -c '%U' /proc/$MY_PID 2> /dev/null `" = "$a_USER" ]; then + pid="${pid} ${MY_PID}"; + fi + done + if [ -n "${pid}" ]; then + RETVAL=0; + else + RETVAL=1; + fi + else + RETVAL=3 + fi + return $RETVAL; +} + +## Starts detached daeamon in screen or tmux. +# $1 = daemon-user; $2+ = daemon and its arguments +my_start_daemon() { + a_USER="$1" + shift + if touch "${PIDFILE}" && chown "${a_USER}" -- "${PIDFILE}"; then + ARGS="" + while [ $# -gt 0 ]; + do + ARGS="$ARGS '$1'"; + shift + done + ARGS="$ARGS --pidfile '$PIDFILE'"; + if type screen > /dev/null; then + su - "${a_USER}" -c "screen -S ${service_name} -d -m ${ARGS}"; + elif type tmux > /dev/null; then + su - "${a_USER}" -c "tmux new-session -AdD -s ${service_name} ${ARGS}"; + else + echo "Need screen or tmux, please install!" + exit 1 + fi + RETVAL=$?; + if [ $RETVAL -eq 0 ]; then + sleep 0.6; + if [ ! -s "$PIDFILE" ]; then sleep 1; fi + if [ ! -s "$PIDFILE" ]; then sleep 2; fi + if [ ! -s "$PIDFILE" ]; then sleep 3; fi + if [ -s "$PIDFILE" ]; then + RETVAL=0; + else + RETVAL=1; + fi + else + fail_msg "su failed with exit code $RETVAL"; + fi + else + fail_msg "Failed to create pid file and change it's ownership to ${a_USER}." + RETVAL=1; + fi + return $RETVAL; +} + +## Stops the daemon. +# $1 = daemon-user; $2 = binary name +my_stop_daemon() { + a_USER="$1"; + a_BINARY="$2"; + my_query_status "$a_USER" "$a_BINARY" + RETVAL=$? + if [ $RETVAL -eq 0 -a -n "$pid" ]; then + kill $pid; + fi + sleep 0.6 + if my_query_status "$a_USER" "$a_BINARY"; then sleep 1; fi + if my_query_status "$a_USER" "$a_BINARY"; then sleep 2; fi + if my_query_status "$a_USER" "$a_BINARY"; then sleep 3; fi + if ! my_query_status "$a_USER" "$a_BINARY"; then + rm -f -- "${PIDFILE}" + return 0; + fi + return 1; +} + +if [ -z "$NOLSB" ]; then + . /lib/lsb/init-functions + fail_msg() { + echo "" + log_failure_msg "$1" + } + succ_msg() { + log_success_msg " done." + } + begin_msg() { + log_daemon_msg "$@" + } +else + fail_msg() { + echo " ...fail!" + echo "$@" + } + succ_msg() { + echo " ...done." + } + begin_msg() { + echo -n "$1" + } +fi + +# +# System specific overrides. +# + +if [ "$system" = "redhat" ]; then + . /etc/init.d/functions + if [ -n "$NOLSB" ]; then + fail_msg() { + echo_failure + echo + } + succ_msg() { + echo_success + echo + } + begin_msg() { + echo -n "$1" + } + fi +fi + +if [ "$system" = "suse" ]; then + . /etc/rc.status + if [ -n "$NOLSB" ]; then + fail_msg() { + rc_failed 1 + rc_status -v + } + succ_msg() { + rc_reset + rc_status -v + } + begin_msg() { + echo -n "$1" + } + fi +fi + +if [ "$system" = "debian" ]; then + # Share my_start_daemon and my_stop_daemon with gentoo + if [ -n "$NOLSB" ]; then + fail_msg() { + echo " ...fail!" + } + succ_msg() { + echo " ...done." + } + begin_msg() { + echo -n "$1" + } + fi +fi + +if [ "$system" = "gentoo" ]; then + if [ -f /sbin/functions.sh ]; then + . /sbin/functions.sh + elif [ -f /etc/init.d/functions.sh ]; then + . /etc/init.d/functions.sh + fi + # Share my_start_daemon and my_stop_daemon with debian. + if [ -n "$NOLSB" ]; then + if [ "`which $0`" = "/sbin/rc" ]; then + shift + fi + fi +fi + +if [ "$system" = "debian" -o "$system" = "gentoo" ]; then + #my_start_daemon() { + # usr="$1" + # shift + # bin="$1" + # shift + # echo usr=$usr + # start-stop-daemon --start --background --pidfile "${PIDFILE}" --make-pidfile --chuid "${usr}" --user "${usr}" \ + # --exec $bin -- $@ + #} + my_stop_daemon() { + a_USER="$1" + a_BINARY="$2" + start-stop-daemon --stop --user "${a_USER}" --pidfile "${PIDFILE}" + RETVAL=$? + rm -f "${PIDFILE}" + return $RETVAL + } +fi + +if [ "$system" = "arch" ]; then + USECOLOR=yes + . /etc/rc.d/functions + if [ -n "$NOLSB" ]; then + fail_msg() { + stat_fail + } + succ_msg() { + stat_done + } + begin_msg() { + stat_busy "$1" + } + fi +fi + +if [ "$system" = "lfs" ]; then + . /etc/rc.d/init.d/functions + if [ -n "$NOLSB" ]; then + fail_msg() { + echo_failure + } + succ_msg() { + echo_ok + } + begin_msg() { + echo $1 + } + fi +fi + +# +# Implement the actions. +# +check_single_user() { + if [ -n "$2" ]; then + fail_msg "TESTBOXSCRIPT_USER must not contain multiple users!" + exit 1 + fi +} + +# +# Open ports at the firewall: +# 6000..6100 / TCP for VRDP +# 5000..5032 / TCP for netperf +# 5000..5032 / UDP for netperf +# +set_iptables() { + if [ -x /sbin/iptables ]; then + I="/sbin/iptables -j ACCEPT -A INPUT -m state --state NEW" + if ! /sbin/iptables -L INPUT | grep -q "testsuite vrdp"; then + $I -m tcp -p tcp --dport 6000:6100 -m comment --comment "testsuite vrdp" + fi + if ! /sbin/iptables -L INPUT | grep -q "testsuite perftcp"; then + $I -m tcp -p tcp --dport 5000:5032 -m comment --comment "testsuite perftcp" + fi + if ! /sbin/iptables -L INPUT | grep -q "testsuite perfudp"; then + $I -m udp -p udp --dport 5000:5032 -m comment --comment "testsuite perfudp" + fi + fi +} + + +start() { + if [ ! -f "${PIDFILE}" ]; then + begin_msg "Starting TestBoxScript"; + + # + # Verify config and installation. + # + if [ ! -d "$TESTBOXSCRIPT_DIR" -o ! -r "$binary" -o ! -r "$binary_real" ]; then + fail_msg "Cannot find TestBoxScript installation under '$TESTBOXSCRIPT_DIR'!" + exit 0; + fi + ## @todo check ownership (for upgrade purposes) + check_single_user $TESTBOXSCRIPT_USER + + # + # Open some ports in the firewall + # Allows to access VMs remotely by VRDP, netperf + # + set_iptables + + # + # Set execute bits to make installation (unzip) easier. + # + chmod a+x > /dev/null 2>&1 \ + "${binary}" \ + "${binary_real}" \ + "${TESTBOXSCRIPT_DIR}/linux/amd64/TestBoxHelper" \ + "${TESTBOXSCRIPT_DIR}/linux/x86/TestBoxHelper" + + # + # Start the daemon as the specified user. + # + PARAMS="" + if [ "${TESTBOXSCRIPT_HWVIRT}" = "yes" ]; then PARAMS="${PARAMS} --hwvirt"; fi + if [ "${TESTBOXSCRIPT_HWVIRT}" = "no" ]; then PARAMS="${PARAMS} --no-hwvirt"; fi + if [ "${TESTBOXSCRIPT_NESTED_PAGING}" = "yes" ]; then PARAMS="${PARAMS} --nested-paging"; fi + if [ "${TESTBOXSCRIPT_NESTED_PAGING}" = "no" ]; then PARAMS="${PARAMS} --no-nested-paging"; fi + if [ "${TESTBOXSCRIPT_IOMMU}" = "yes" ]; then PARAMS="${PARAMS} --io-mmu"; fi + if [ "${TESTBOXSCRIPT_IOMMU}" = "no" ]; then PARAMS="${PARAMS} --no-io-mmu"; fi + if [ "${TESTBOXSCRIPT_SPB}" = "yes" ]; then PARAMS="${PARAMS} --spb"; fi + if [ -n "${TESTBOXSCRIPT_SYSTEM_UUID}" ]; then PARAMS="${PARAMS} --system-uuid '${TESTBOXSCRIPT_SYSTEM_UUID}'"; fi + if [ -n "${TESTBOXSCRIPT_TEST_MANAGER}" ]; then PARAMS="${PARAMS} --test-manager '${TESTBOXSCRIPT_TEST_MANAGER}'"; fi + if [ -n "${TESTBOXSCRIPT_SCRATCH_ROOT}" ]; then PARAMS="${PARAMS} --scratch-root '${TESTBOXSCRIPT_SCRATCH_ROOT}'"; fi + + if [ -n "${TESTBOXSCRIPT_BUILDS_PATH}" ]; then PARAMS="${PARAMS} --builds-path '${TESTBOXSCRIPT_BUILDS_PATH}'"; fi + if [ -n "${TESTBOXSCRIPT_BUILDS_TYPE}" ]; then PARAMS="${PARAMS} --builds-server-type '${TESTBOXSCRIPT_BUILDS_TYPE}'"; fi + if [ -n "${TESTBOXSCRIPT_BUILDS_NAME}" ]; then PARAMS="${PARAMS} --builds-server-name '${TESTBOXSCRIPT_BUILDS_NAME}'"; fi + if [ -n "${TESTBOXSCRIPT_BUILDS_SHARE}" ]; then PARAMS="${PARAMS} --builds-server-share '${TESTBOXSCRIPT_BUILDS_SHARE}'"; fi + if [ -n "${TESTBOXSCRIPT_BUILDS_USER}" ]; then PARAMS="${PARAMS} --builds-server-user '${TESTBOXSCRIPT_BUILDS_USER}'"; fi + if [ -n "${TESTBOXSCRIPT_BUILDS_PASSWD}" ]; then PARAMS="${PARAMS} --builds-server-passwd '${TESTBOXSCRIPT_BUILDS_PASSWD}'"; fi + if [ -n "${TESTBOXSCRIPT_BUILDS_MOUNTOPT}" ]; then PARAMS="${PARAMS} --builds-server-mountopt '${TESTBOXSCRIPT_BUILDS_MOUNTOPT}'"; fi + if [ -n "${TESTBOXSCRIPT_TESTRSRC_PATH}" ]; then PARAMS="${PARAMS} --testrsrc-path '${TESTBOXSCRIPT_TESTRSRC_PATH}'"; fi + if [ -n "${TESTBOXSCRIPT_TESTRSRC_TYPE}" ]; then PARAMS="${PARAMS} --testrsrc-server-type '${TESTBOXSCRIPT_TESTRSRC_TYPE}'"; fi + if [ -n "${TESTBOXSCRIPT_TESTRSRC_NAME}" ]; then PARAMS="${PARAMS} --testrsrc-server-name '${TESTBOXSCRIPT_TESTRSRC_NAME}'"; fi + if [ -n "${TESTBOXSCRIPT_TESTRSRC_SHARE}" ]; then PARAMS="${PARAMS} --testrsrc-server-share '${TESTBOXSCRIPT_TESTRSRC_SHARE}'"; fi + if [ -n "${TESTBOXSCRIPT_TESTRSRC_USER}" ]; then PARAMS="${PARAMS} --testrsrc-server-user '${TESTBOXSCRIPT_TESTRSRC_USER}'"; fi + if [ -n "${TESTBOXSCRIPT_TESTRSRC_PASSWD}" ]; then PARAMS="${PARAMS} --testrsrc-server-passwd '${TESTBOXSCRIPT_TESTRSRC_PASSWD}'"; fi + if [ -n "${TESTBOXSCRIPT_TESTRSRC_MOUNTOPT}" ]; then PARAMS="${PARAMS} --testrsrc-server-mountopt '${TESTBOXSCRIPT_TESTRSRC_MOUNTOPT}'"; fi + + if [ -n "${TESTBOXSCRIPT_PYTHON}" ]; then + my_start_daemon "${TESTBOXSCRIPT_USER}" "${TESTBOXSCRIPT_PYTHON}" "${binary}" ${PARAMS} + else + my_start_daemon "${TESTBOXSCRIPT_USER}" "${binary}" ${PARAMS} + fi + RETVAL=$? + + if [ $RETVAL -eq 0 ]; then + succ_msg + else + fail_msg + fi + else + succ_msg "Already running." + RETVAL=0 + fi + return $RETVAL +} + +stop() { + if [ -f "${PIDFILE}" ]; then + begin_msg "Stopping TestBoxScript"; + my_stop_daemon "${TESTBOXSCRIPT_USER}" "${binary}" + RETVAL=$? + if [ $RETVAL -eq 0 ]; then + succ_msg + else + fail_msg + fi + else + RETVAL=0 + fi + return $RETVAL +} + +restart() { + stop && sleep 1 && start +} + +status() { + echo -n "Checking for TestBoxScript" + my_query_status "${TESTBOXSCRIPT_USER}" "${binary}" + RETVAL=$? + if [ ${RETVAL} -eq 0 ]; then + echo " ...running" + elif [ ${RETVAL} -eq 3 ]; then + echo " ...stopped" + elif [ ${RETVAL} -eq 1 ]; then + echo " ...started but not running" + else + echo " ...unknown status '${RETVAL}'" + fi +} + + +# +# main(). +# +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + restart + ;; + force-reload) + restart + ;; + status) + status + ;; + setup) + ;; + cleanup) + ;; + *) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 +esac + +exit $RETVAL + diff --git a/src/VBox/ValidationKit/testboxscript/setup.sh b/src/VBox/ValidationKit/testboxscript/setup.sh new file mode 100755 index 00000000..19726cea --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/setup.sh @@ -0,0 +1,714 @@ +#!/usr/bin/env bash +# $Id: setup.sh $ +## @file +# VirtualBox Validation Kit - TestBoxScript Service Setup on Unixy platforms. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + + +# +# !WARNING! Running the whole script in exit-on-failure mode. +# +# Note! Looking at the ash sources, it seems flags will be saved and restored +# when calling functions. That's comforting. +# +set -e +#set -x # debug only, disable! + +## +# Get the host OS name, returning it in RETVAL. +# +get_host_os() { + RETVAL=`uname` + case "$RETVAL" in + Darwin|darwin) + RETVAL=darwin + ;; + + DragonFly) + RETVAL=dragonfly + ;; + + freebsd|FreeBSD|FREEBSD) + RETVAL=freebsd + ;; + + Haiku) + RETVAL=haiku + ;; + + linux|Linux|GNU/Linux|LINUX) + RETVAL=linux + ;; + + netbsd|NetBSD|NETBSD) + RETVAL=netbsd + ;; + + openbsd|OpenBSD|OPENBSD) + RETVAL=openbsd + ;; + + os2|OS/2|OS2) + RETVAL=os2 + ;; + + SunOS) + RETVAL=solaris + ;; + + WindowsNT|CYGWIN_NT-*) + RETVAL=win + ;; + + *) + echo "$0: unknown os $RETVAL" 1>&2 + exit 1 + ;; + esac + return 0; +} + +## +# Get the host OS/CPU arch, returning it in RETVAL. +# +get_host_arch() { + if [ "${HOST_OS}" = "solaris" ]; then + RETVAL=`isainfo | cut -f 1 -d ' '` + else + RETVAL=`uname -m` + fi + case "${RETVAL}" in + amd64|AMD64|x86_64|k8|k8l|k9|k10) + RETVAL='amd64' + ;; + x86|i86pc|ia32|i[3456789]86|BePC) + RETVAL='x86' + ;; + sparc32|sparc|sparcv8|sparcv7|sparcv8e) + RETVAL='sparc32' + ;; + sparc64|sparcv9) + RETVAL='sparc64' + ;; + s390) + RETVAL='s390' + ;; + s390x) + RETVAL='s390x' + ;; + ppc32|ppc|powerpc) + RETVAL='ppc32' + ;; + ppc64|powerpc64) + RETVAL='ppc64' + ;; + mips32|mips) + RETVAL='mips32' + ;; + mips64) + RETVAL='mips64' + ;; + ia64) + RETVAL='ia64' + ;; + hppa32|parisc32|parisc) + RETVAL='hppa32' + ;; + hppa64|parisc64) + RETVAL='hppa64' + ;; + arm|arm64|armv4l|armv5tel|armv5tejl) + RETVAL='arm' + ;; + arm64|aarch64) + RETVAL='arm64' + ;; + alpha) + RETVAL='alpha' + ;; + + *) + echo "$0: unknown cpu/arch - $RETVAL" 1>&$2 + exit 1 + ;; + esac + return 0; +} + + +## +# Loads config values from the current installation. +# +os_load_config() { + echo "os_load_config is not implemented" 2>&1 + exit 1 +} + +## +# Installs, configures and starts the service. +# +os_install_service() { + echo "os_install_service is not implemented" 2>&1 + exit 1 +} + +## +# Enables (starts) the service. +os_enable_service() { + echo "os_enable_service is not implemented" 2>&1 + return 0; +} + +## +# Disables (stops) the service. +os_disable_service() { + echo "os_disable_service is not implemented" 2>&1 + return 0; +} + +## +# Adds the testbox user +# +os_add_user() { + echo "os_add_user is not implemented" 2>&1 + exit 1 +} + +## +# Prints a final message after successful script execution. +# This can contain additional instructions which needs to be carried out +# manually or similar. +os_final_message() { + return 0; +} + +## +# Checks the installation, verifying that files are there and scripts work fine. +# +check_testboxscript_install() { + + # Presence + test -r "${TESTBOXSCRIPT_DIR}/testboxscript/testboxscript.py" + test -r "${TESTBOXSCRIPT_DIR}/testboxscript/testboxscript_real.py" + test -r "${TESTBOXSCRIPT_DIR}/testboxscript/linux/testboxscript-service.sh" -o "${HOST_OS}" != "linux" + test -r "${TESTBOXSCRIPT_DIR}/${HOST_OS}/${HOST_ARCH}/TestBoxHelper" + + # Zip file may be missing the x bits, so set them. + chmod a+x \ + "${TESTBOXSCRIPT_DIR}/testboxscript/testboxscript.py" \ + "${TESTBOXSCRIPT_DIR}/testboxscript/testboxscript_real.py" \ + "${TESTBOXSCRIPT_DIR}/${HOST_OS}/${HOST_ARCH}/TestBoxHelper" \ + "${TESTBOXSCRIPT_DIR}/testboxscript/linux/testboxscript-service.sh" + + + # Check that the scripts work. + set +e + "${TESTBOXSCRIPT_PYTHON}" "${TESTBOXSCRIPT_DIR}/testboxscript/testboxscript.py" --version > /dev/null + if [ $? -ne 2 ]; then + echo "$0: error: testboxscript.py didn't respons correctly to the --version option." + exit 1; + fi + + "${TESTBOXSCRIPT_PYTHON}" "${TESTBOXSCRIPT_DIR}/testboxscript/testboxscript_real.py" --version > /dev/null + if [ $? -ne 2 ]; then + echo "$0: error: testboxscript.py didn't respons correctly to the --version option." + exit 1; + fi + set -e + + return 0; +} + +## +# Check that sudo is installed. +# +check_for_sudo() { + which sudo + test -f "${MY_ETC_SUDOERS}" +} + +## +# Check that sudo is installed. +# +check_for_cifs() { + return 0; +} + +## +# Checks if the testboxscript_user exists. +does_testboxscript_user_exist() { + id "${TESTBOXSCRIPT_USER}" > /dev/null 2>&1 + return $?; +} + +## +# hushes up the root login. +maybe_hush_up_root_login() { + # This is a solaris hook. + return 0; +} + +## +# Adds the testbox user and make sure it has unrestricted sudo access. +maybe_add_testboxscript_user() { + if ! does_testboxscript_user_exist; then + os_add_user "${TESTBOXSCRIPT_USER}" + fi + + SUDOERS_LINE="${TESTBOXSCRIPT_USER} ALL=(ALL) NOPASSWD: ALL" + if ! ${MY_FGREP} -q "${SUDOERS_LINE}" ${MY_ETC_SUDOERS}; then + echo "# begin tinderboxscript setup.sh" >> ${MY_ETC_SUDOERS} + echo "${SUDOERS_LINE}" >> ${MY_ETC_SUDOERS} + echo "# end tinderboxscript setup.sh" >> ${MY_ETC_SUDOERS} + fi + + maybe_hush_up_root_login; +} + + +## +# Test the user. +# +test_user() { + su - "${TESTBOXSCRIPT_USER}" -c "true" + + # sudo 1.7.0 adds the -n option. + MY_TMP="`sudo -V 2>&1 | head -1 | sed -e 's/^.*version 1\.[6543210]\..*$/old/'`" + if [ "${MY_TMP}" != "old" ]; then + echo "Warning: If sudo starts complaining about not having a tty," + echo " disable the requiretty option in /etc/sudoers." + su - "${TESTBOXSCRIPT_USER}" -c "sudo -n -i true" + else + echo "Warning: You've got an old sudo installed. If it starts" + echo " complaining about not having a tty, disable the" + echo " requiretty option in /etc/sudoers." + su - "${TESTBOXSCRIPT_USER}" -c "sudo true" + fi +} + +## +# Test if core dumps are enabled. See https://wiki.ubuntu.com/Apport! +# +test_coredumps() { + # This is a linux hook. + return 0; +} + +## +# Test if unattended updates are disabled. See +# http://ask.xmodulo.com/disable-automatic-updates-ubuntu.html +test_unattended_updates_disabled() { + # This is a linux hook. + return 0; +} + +## +# Grants the user write access to the testboxscript files so it can perform +# upgrades. +# +grant_user_testboxscript_write_access() { + chown -R "${TESTBOXSCRIPT_USER}" "${TESTBOXSCRIPT_DIR}" +} + +## +# Check the proxy setup. +# +check_proxy_config() { + if [ -n "${http_proxy}" -o -n "${ftp_proxy}" ]; then + if [ -z "${no_proxy}" ]; then + echo "Error: Env.vars. http_proxy/ftp_proxy without no_proxy is going to break upgrade among other things." + exit 1 + fi + fi +} + +## +# Parses the testboxscript.py invocation, setting TESTBOXSCRIPT_xxx config +# variables accordingly. Both darwin and solaris uses this. +common_testboxscript_args_to_config() +{ + MY_ARG=0 + while [ $# -gt 0 ]; + do + case "$1" in + # boolean + "--hwvirt") TESTBOXSCRIPT_HWVIRT="yes";; + "--no-hwvirt") TESTBOXSCRIPT_HWVIRT="no";; + "--nested-paging") TESTBOXSCRIPT_NESTED_PAGING="yes";; + "--no-nested-paging") TESTBOXSCRIPT_NESTED_PAGING="no";; + "--io-mmu") TESTBOXSCRIPT_IOMMU="yes";; + "--no-io-mmu") TESTBOXSCRIPT_IOMMU="no";; + # optios taking values. + "--system-uuid") TESTBOXSCRIPT_SYSTEM_UUID="$2"; shift;; + "--scratch-root") TESTBOXSCRIPT_SCRATCH_ROOT="$2"; shift;; + "--test-manager") TESTBOXSCRIPT_TEST_MANAGER="$2"; shift;; + "--builds-path") TESTBOXSCRIPT_BUILDS_PATH="$2"; shift;; + "--builds-server-type") TESTBOXSCRIPT_BUILDS_TYPE="$2"; shift;; + "--builds-server-name") TESTBOXSCRIPT_BUILDS_NAME="$2"; shift;; + "--builds-server-share") TESTBOXSCRIPT_BUILDS_SHARE="$2"; shift;; + "--builds-server-user") TESTBOXSCRIPT_BUILDS_USER="$2"; shift;; + "--builds-server-passwd") TESTBOXSCRIPT_BUILDS_PASSWD="$2"; shift;; + "--builds-server-mountopt") TESTBOXSCRIPT_BUILDS_MOUNTOPT="$2"; shift;; + "--testrsrc-path") TESTBOXSCRIPT_TESTRSRC_PATH="$2"; shift;; + "--testrsrc-server-type") TESTBOXSCRIPT_TESTRSRC_TYPE="$2"; shift;; + "--testrsrc-server-name") TESTBOXSCRIPT_TESTRSRC_NAME="$2"; shift;; + "--testrsrc-server-share") TESTBOXSCRIPT_TESTRSRC_SHARE="$2"; shift;; + "--testrsrc-server-user") TESTBOXSCRIPT_TESTRSRC_USER="$2"; shift;; + "--testrsrc-server-passwd") TESTBOXSCRIPT_TESTRSRC_PASSWD="$2"; shift;; + "--testrsrc-server-mountopt") TESTBOXSCRIPT_TESTRSRC_MOUNTOPT="$2"; shift;; + "--spb") ;; + "--putenv") + MY_FOUND=no + MY_VAR=`echo $2 | sed -e 's/=.*$//' ` + for i in ${!TESTBOXSCRIPT_ENVVARS[@]}; + do + MY_CURVAR=`echo "${TESTBOXSCRIPT_ENVVARS[i]}" | sed -e 's/=.*$//' ` + if [ -n "${MY_CURVAR}" -a "${MY_CURVAR}" = "${MY_VAR}" ]; then + TESTBOXSCRIPT_ENVVARS[$i]="$2" + MY_FOUND=yes + fi + done + if [ "${MY_FOUND}" = "no" ]; then + TESTBOXSCRIPT_ENVVARS=( "${TESTBOXSCRIPT_ENVVARS[@]}" "$2" ); + fi + shift;; + --*) + echo "error: Unknown option '$1' in existing config" + exit 1 + ;; + + # Non-option bits. + *.py) ;; # ignored, should be the script. + + *) if [ ${MY_ARG} -ne 0 ]; then + echo "error: unknown non-option '$1' in existing config" + exit 1 + fi + TESTBOXSCRIPT_PYTHON="$1" + ;; + esac + shift + MY_ARG=$((${MY_ARG} + 1)) + done +} + +## +# Used by common_compile_testboxscript_command_line, please override. +# +os_add_args() { + echo "os_add_args is not implemented" 2>&1 + exit 1 +} + +## +# Compiles the testboxscript.py command line given the current +# configuration and defaults. +# +# This is used by solaris and darwin. +# +# The os_add_args function will be called several with one or two arguments +# each time. The caller must override it. +# +common_compile_testboxscript_command_line() { + if [ -n "${TESTBOXSCRIPT_PYTHON}" ]; then + os_add_args "${TESTBOXSCRIPT_PYTHON}" + fi + os_add_args "${TESTBOXSCRIPT_DIR}/testboxscript/testboxscript.py" + + for var in ${TESTBOXSCRIPT_CFG_NAMES}; + do + varcfg=TESTBOXSCRIPT_${var} + vardef=TESTBOXSCRIPT_DEFAULT_${var} + if [ "${!varcfg}" != "${!vardef}" -a "${var}" != "PYTHON" ]; then # PYTHON handled above. + my_opt=TESTBOXSCRIPT_OPT_${var} + if [ -n "${!my_opt}" ]; then + if [ "${!my_opt}" == "--spb" ]; then + os_add_args "${!my_opt}" + elif [ "${!my_opt}" != "--skip" ]; then + os_add_args "${!my_opt}" "${!varcfg}" + fi + else + my_opt_yes=${my_opt}_YES + my_opt_no=${my_opt}_NO + if [ -n "${!my_opt_yes}" -a -n "${!my_opt_no}" ]; then + if [ "${!varcfg}" = "yes" ]; then + os_add_args "${!my_opt_yes}"; + else + if [ "${!varcfg}" != "no" ]; then + echo "internal option misconfig: var=${var} not a yes/no value: ${!varcfg}"; + exit 1; + fi + os_add_args "${!my_opt_yes}"; + fi + else + echo "internal option misconfig: var=${var} my_opt_yes=${my_opt_yes}=${!my_opt_yes} my_opt_no=${my_opt_no}=${!my_opt_no}" + exit 1; + fi + fi + fi + done + + i=0 + while [ "${i}" -lt "${#TESTBOXSCRIPT_ENVVARS[@]}" ]; + do + os_add_args "--putenv" "${TESTBOXSCRIPT_ENVVARS[${i}]}" + i=$((${i} + 1)) + done +} + + +# +# +# main() +# +# + + +# +# Get our bearings and include the host specific code. +# +MY_ETC_SUDOERS="/etc/sudoers" +MY_FGREP=fgrep +DIR=`dirname "$0"` +DIR=`cd "${DIR}"; /bin/pwd` + +get_host_os +HOST_OS=${RETVAL} +get_host_arch +HOST_ARCH=${RETVAL} + +. "${DIR}/${HOST_OS}/setup-routines.sh" + + +# +# Config. +# +TESTBOXSCRIPT_CFG_NAMES="DIR PYTHON USER HWVIRT IOMMU NESTED_PAGING SYSTEM_UUID PATH_TESTRSRC TEST_MANAGER SCRATCH_ROOT" +TESTBOXSCRIPT_CFG_NAMES="${TESTBOXSCRIPT_CFG_NAMES} BUILDS_PATH BUILDS_TYPE BUILDS_NAME BUILDS_SHARE BUILDS_USER" +TESTBOXSCRIPT_CFG_NAMES="${TESTBOXSCRIPT_CFG_NAMES} BUILDS_PASSWD BUILDS_MOUNTOPT TESTRSRC_PATH TESTRSRC_TYPE TESTRSRC_NAME" +TESTBOXSCRIPT_CFG_NAMES="${TESTBOXSCRIPT_CFG_NAMES} TESTRSRC_SHARE TESTRSRC_USER TESTRSRC_PASSWD TESTRSRC_MOUNTOPT SPB" + +# testboxscript.py option to config mappings. +TESTBOXSCRIPT_OPT_DIR="--skip" +TESTBOXSCRIPT_OPT_PYTHON="--skip" +TESTBOXSCRIPT_OPT_USER="--skip" +TESTBOXSCRIPT_OPT_HWVIRT_YES="--hwvirt" +TESTBOXSCRIPT_OPT_HWVIRT_NO="--no-hwvirt" +TESTBOXSCRIPT_OPT_NESTED_PAGING_YES="--nested-paging" +TESTBOXSCRIPT_OPT_NESTED_PAGING_NO="--no-nested-paging" +TESTBOXSCRIPT_OPT_IOMMU_YES="--io-mmu" +TESTBOXSCRIPT_OPT_IOMMU_NO="--no-io-mmu" +TESTBOXSCRIPT_OPT_SPB="--spb" +TESTBOXSCRIPT_OPT_SYSTEM_UUID="--system-uuid" +TESTBOXSCRIPT_OPT_TEST_MANAGER="--test-manager" +TESTBOXSCRIPT_OPT_SCRATCH_ROOT="--scratch-root" +TESTBOXSCRIPT_OPT_BUILDS_PATH="--builds-path" +TESTBOXSCRIPT_OPT_BUILDS_TYPE="--builds-server-type" +TESTBOXSCRIPT_OPT_BUILDS_NAME="--builds-server-name" +TESTBOXSCRIPT_OPT_BUILDS_SHARE="--builds-server-share" +TESTBOXSCRIPT_OPT_BUILDS_USER="--builds-server-user" +TESTBOXSCRIPT_OPT_BUILDS_PASSWD="--builds-server-passwd" +TESTBOXSCRIPT_OPT_BUILDS_MOUNTOPT="--builds-server-mountopt" +TESTBOXSCRIPT_OPT_PATH_TESTRSRC="--testrsrc-path" +TESTBOXSCRIPT_OPT_TESTRSRC_TYPE="--testrsrc-server-type" +TESTBOXSCRIPT_OPT_TESTRSRC_NAME="--testrsrc-server-name" +TESTBOXSCRIPT_OPT_TESTRSRC_SHARE="--testrsrc-server-share" +TESTBOXSCRIPT_OPT_TESTRSRC_USER="--testrsrc-server-user" +TESTBOXSCRIPT_OPT_TESTRSRC_PASSWD="--testrsrc-server-passwd" +TESTBOXSCRIPT_OPT_TESTRSRC_MOUNTOPT="--testrsrc-server-mountopt" + +# Defaults: +TESTBOXSCRIPT_DEFAULT_DIR="there-is-no-default-for-this-value" +TESTBOXSCRIPT_DEFAULT_PYTHON="" +TESTBOXSCRIPT_DEFAULT_USER="vbox" +TESTBOXSCRIPT_DEFAULT_HWVIRT="" +TESTBOXSCRIPT_DEFAULT_IOMMU="" +TESTBOXSCRIPT_DEFAULT_NESTED_PAGING="" +TESTBOXSCRIPT_DEFAULT_SPB="" +TESTBOXSCRIPT_DEFAULT_SYSTEM_UUID="" +TESTBOXSCRIPT_DEFAULT_PATH_TESTRSRC="" +TESTBOXSCRIPT_DEFAULT_TEST_MANAGER="" +TESTBOXSCRIPT_DEFAULT_SCRATCH_ROOT="" +TESTBOXSCRIPT_DEFAULT_BUILDS_PATH="" +TESTBOXSCRIPT_DEFAULT_BUILDS_TYPE="cifs" +TESTBOXSCRIPT_DEFAULT_BUILDS_NAME="vboxstor.de.oracle.com" +TESTBOXSCRIPT_DEFAULT_BUILDS_SHARE="builds" +TESTBOXSCRIPT_DEFAULT_BUILDS_USER="guestr" +TESTBOXSCRIPT_DEFAULT_BUILDS_PASSWD="guestr" +TESTBOXSCRIPT_DEFAULT_BUILDS_MOUNTOPT="" +TESTBOXSCRIPT_DEFAULT_TESTRSRC_PATH="" +TESTBOXSCRIPT_DEFAULT_TESTRSRC_TYPE="cifs" +TESTBOXSCRIPT_DEFAULT_TESTRSRC_NAME="teststor.de.oracle.com" +TESTBOXSCRIPT_DEFAULT_TESTRSRC_SHARE="testrsrc" +TESTBOXSCRIPT_DEFAULT_TESTRSRC_USER="guestr" +TESTBOXSCRIPT_DEFAULT_TESTRSRC_PASSWD="guestr" +TESTBOXSCRIPT_DEFAULT_TESTRSRC_MOUNTOPT="" + +# Set config values to defaults. +for var in ${TESTBOXSCRIPT_CFG_NAMES} +do + defvar=TESTBOXSCRIPT_DEFAULT_${var} + eval TESTBOXSCRIPT_${var}="${!defvar}" +done +declare -a TESTBOXSCRIPT_ENVVARS + +# Load old config values (platform specific). +os_load_config + + +# +# Config tweaks. +# + +# The USER must be a non-empty value for the successful execution of this script. +if [ -z "${TESTBOXSCRIPT_USER}" ]; then + TESTBOXSCRIPT_USER=${TESTBOXSCRIPT_DEFAULT_USER}; +fi; + +# The DIR must be according to the setup.sh location. +TESTBOXSCRIPT_DIR=`dirname "${DIR}"` + +# Storage server replacement trick. +if [ "${TESTBOXSCRIPT_BUILDS_NAME}" = "solserv.de.oracle.com" ]; then + TESTBOXSCRIPT_BUILDS_NAME=${TESTBOXSCRIPT_DEFAULT_BUILDS_NAME} +fi +if [ "${TESTBOXSCRIPT_TESTRSRC_NAME}" = "solserv.de.oracle.com" ]; then + TESTBOXSCRIPT_TESTRSRC_NAME=${TESTBOXSCRIPT_DEFAULT_TESTRSRC_NAME} +fi + + +# +# Parse arguments. +# +while test $# -gt 0; +do + case "$1" in + -h|--help) + echo "TestBox Script setup utility." + echo ""; + echo "Usage: setup.sh [options]"; + echo ""; + echo "Options:"; + echo " Later..."; + exit 0; + ;; + -V|--version) + echo '$Revision: 155040 $' + exit 0; + ;; + + --python) TESTBOXSCRIPT_PYTHON="$2"; shift;; + --test-manager) TESTBOXSCRIPT_TEST_MANAGER="$2"; shift;; + --scratch-root) TESTBOXSCRIPT_SCRATCH_ROOT="$2"; shift;; + --system-uuid) TESTBOXSCRIPT_SYSTEM_UUID="$2"; shift;; + --hwvirt) TESTBOXSCRIPT_HWVIRT="yes";; + --no-hwvirt) TESTBOXSCRIPT_HWVIRT="no";; + --nested-paging) TESTBOXSCRIPT_NESTED_PAGING="yes";; + --no-nested-paging) TESTBOXSCRIPT_NESTED_PAGING="no";; + --io-mmu) TESTBOXSCRIPT_IOMMU="yes";; + --no-io-mmu) TESTBOXSCRIPT_IOMMU="no";; + --builds-path) TESTBOXSCRIPT_BUILDS_PATH="$2"; shift;; + --builds-server-type) TESTBOXSCRIPT_BUILDS_TYPE="$2"; shift;; + --builds-server-name) TESTBOXSCRIPT_BUILDS_NAME="$2"; shift;; + --builds-server-share) TESTBOXSCRIPT_BUILDS_SHARE="$2"; shift;; + --builds-server-user) TESTBOXSCRIPT_BUILDS_USER="$2"; shift;; + --builds-server-passwd) TESTBOXSCRIPT_BUILDS_PASSWD="$2"; shift;; + --builds-server-mountopt) TESTBOXSCRIPT_BUILDS_MOUNTOPT="$2"; shift;; + --testrsrc-path) TESTBOXSCRIPT_TESTRSRC_PATH="$2"; shift;; + --testrsrc-server-type) TESTBOXSCRIPT_TESTRSRC_TYPE="$2"; shift;; + --testrsrc-server-name) TESTBOXSCRIPT_TESTRSRC_NAME="$2"; shift;; + --testrsrc-server-share) TESTBOXSCRIPT_TESTRSRC_SHARE="$2"; shift;; + --testrsrc-server-user) TESTBOXSCRIPT_TESTRSRC_USER="$2"; shift;; + --testrsrc-server-passwd) TESTBOXSCRIPT_TESTRSRC_PASSWD="$2"; shift;; + --testrsrc-server-mountopt) TESTBOXSCRIPT_TESTRSRC_MOUNTOPT="$2"; shift;; + --spb) TESTBOXSCRIPT_SPB="yes";; + *) + echo 'Syntax error: Unknown option:' "$1" >&2; + exit 1; + ;; + esac + shift; +done + + +# +# Find usable python if not already specified. +# +if [ -z "${TESTBOXSCRIPT_PYTHON}" ]; then + set +e + MY_PYTHON_VER_TEST="\ +import sys;\ +x = sys.version_info[0] == 3 or (sys.version_info[0] == 2 and (sys.version_info[1] >= 6 or (sys.version_info[1] == 5 and sys.version_info[2] >= 1)));\ +sys.exit(not x);\ +"; + for python in python2.7 python2.6 python2.5 python; + do + python=`which ${python} 2> /dev/null` + if [ -n "${python}" -a -x "${python}" ]; then + if ${python} -c "${MY_PYTHON_VER_TEST}"; then + TESTBOXSCRIPT_PYTHON="${python}"; + break; + fi + fi + done + set -e + test -n "${TESTBOXSCRIPT_PYTHON}"; +fi + + +# +# Do the job +# +set -e +check_testboxscript_install; +check_for_sudo; +check_for_cifs; +check_proxy_config; + +maybe_add_testboxscript_user; +test_user; +test_coredumps; +test_unattended_updates_disabled; + +grant_user_testboxscript_write_access; + +os_disable_service; +os_install_service; +os_enable_service; + +# +# That's all folks. +# +echo "done" +os_final_message; diff --git a/src/VBox/ValidationKit/testboxscript/solaris/setup-routines.sh b/src/VBox/ValidationKit/testboxscript/solaris/setup-routines.sh new file mode 100644 index 00000000..53e54634 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/solaris/setup-routines.sh @@ -0,0 +1,360 @@ +# $Id: setup-routines.sh $ +## @file +# VirtualBox Validation Kit - TestBoxScript Service Setup on Solaris. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +# +# Detect solaris version. +# +MY_SOLARIS_VER=`uname -r` +case "${MY_SOLARIS_VER}" in + 5.10) MY_SOLARIS_VER=10;; + 5.11) MY_SOLARIS_VER=11;; + 5.12) MY_SOLARIS_VER=12;; + *) + echo "Your solaris version (${MY_SOLARIS_VER}) is not supported." >&2 + exit 1;; +esac + +# +# Overriding setup.sh bits. +# +MY_FGREP="/usr/xpg4/bin/fgrep" # The other one does grok -q. +if [ ! -f "${MY_ETC_SUDOERS}" ]; then # sudo isn't standard on S10. + if [ -f "/opt/csw/etc/sudoers" ]; then + MY_ETC_SUDOERS=/opt/csw/etc/sudoers + fi + if [ -f "/etc/opt/csw/sudoers" ]; then + MY_ETC_SUDOERS=/etc/opt/csw/sudoers + fi +fi + +# +# Solaris variables. +# +MY_SVC_FMRI="svc:/system/virtualbox/testboxscript" +MY_SVCCFG="/usr/sbin/svccfg" +MY_SVCADM="/usr/sbin/svcadm" +MY_CHGRP="/usr/bin/chgrp" +MY_TR="/usr/bin/tr" +MY_TAB=`printf "\t"` + +if test "${MY_SOLARIS_VER}" -lt 11; then + # solaris 10 service import + MY_SVC="/tmp/testboxscript.xml" +else + # use propper manifest directory + # /lib/svc/manifest/system for solaris 11 and higher for testboxscript.xml file + + # Since sol 11.4 the solaris testboxscript service + # generates Warnings in /var/svc/log/system-manifest-import:default.log + # -------- Warning!! + # Configuring services... + # * Warning!! Importing Zone access service ...FAILED. + + MY_SVC="/lib/svc/manifest/system/testboxscript.xml" +fi +if test "${MY_SOLARIS_VER}" -lt 11; then ## No gsed on S10?? ARG! + MY_SED="/usr/xpg4/bin/sed" +else + MY_SED="/usr/bin/gsed" +fi +if test "${MY_SOLARIS_VER}" -lt 11; then + MY_SCREEN="/opt/csw/bin/screen" +else + MY_SCREEN="screen" +fi + + +check_for_cifs() { + if [ ! -f /usr/kernel/fs/amd64/smbfs -a ! -f /usr/kernel/fs/smbfs -a "${MY_SOLARIS_VER}" -ge 11 ]; then + echo "error: smbfs client not installed?" >&2 + echo "Please install smbfs client support:" >&2 + echo " pkg install system/file-system/smb" >&2 + echo " svcadm enable svc:/system/idmap" >&2 + echo " svcadm enable svc:/network/smb/client" >&2 + echo " svcs svc:/system/idmap" >&2 + return 1; + fi + return 0; +} + +## +# Loads config values from the current installation. +# +os_load_config() { + # + # Adjust defaults. + # + # - Use NFS instead of CIFS because S10 doesn't have smbfs and S11 has + # problems getting the password. + # - Pass the PATH along so we'll find sudo and other stuff later. + # + TESTBOXSCRIPT_BUILDS_TYPE="nfs" + TESTBOXSCRIPT_TESTRSRC_TYPE="nfs" + TESTBOXSCRIPT_DEFAULT_BUILDS_TYPE="nfs" + TESTBOXSCRIPT_DEFAULT_TESTRSRC_TYPE="nfs" + TESTBOXSCRIPT_ENVVARS[${#TESTBOXSCRIPT_ENVVARS[@]}]="PATH=${PATH}"; + + # Load old current. + if "${MY_SVCCFG}" "export" "${MY_SVC_FMRI}" > /dev/null 2>&1; then + # User. ASSUMES single quoted attribs. + MY_TMP=`"${MY_SVCCFG}" "export" "${MY_SVC_FMRI}" \ + | ${MY_TR} '\n' ' ' \ + `; + MY_TMP=`echo "${MY_TMP} " \ + | ${MY_SED} \ + -e 's/> */> /g' \ + -e 's/ *\/>/ \/>/g' \ + -e 's/^.*<method_credential \([^>]*\) \/>.*$/\1/' \ + -e "s/^.*user='\([^']*\)'.*\$/\1/" \ + `; + if [ -n "${MY_TMP}" ]; then + TESTBOXSCRIPT_USER="${MY_TMP}"; + fi + + # Arguments. ASSUMES sub-elements. ASSUMES single quoted attribs. + XMLARGS=`"${MY_SVCCFG}" "export" "${MY_SVC_FMRI}" \ + | ${MY_TR} '\n' ' ' \ + `; + case "${XMLARGS}" in + *exec_method*) + XMLARGS=`echo "${XMLARGS} " \ + | ${MY_SED} \ + -e 's/> */> /g' \ + -e 's/ *\/>/ \/>/g' \ + -e "s/^.*<exec_method \([^>]*\)name='start'\([^>]*\)>.*\$/\1 \2/" \ + -e "s/^.*exec='\([^']*\)'.*\$/\1/" \ + -e 's/"/"/g' \ + -e 's/</</g' \ + -e 's/>/>/g' \ + -e 's/&/&/g' \ + | ${MY_SED} \ + -e 's/^.*testboxscript -d -m *//' \ + `; + eval common_testboxscript_args_to_config ${XMLARGS} + ;; + *) + echo "error: ${MY_SVCCFG}" "export" "${MY_SVC_FMRI} contains no exec_method element." >&2 + echo " Please delete the service manually and restart setup.sh" >&2 + exit 2 + ;; + esac + fi +} + +## +# Adds one or more arguments to MY_ARGV after checking them for conformity. +# +os_add_args() { + while [ $# -gt 0 ]; + do + case "$1" in + *\ *) + echo "error: Space in option value is not allowed ($1)" >&2 + exit 1; + ;; + *${MY_TAB}*) + echo "error: Tab in option value is not allowed ($1)" >&2 + exit 1; + ;; + *\&*) + echo "error: Ampersand in option value is not allowed ($1)" >&2 + exit 1; + ;; + *\<*) + echo "error: Greater-than in option value is not allowed ($1)" >&2 + exit 1; + ;; + *\>*) + echo "error: Less-than in option value is not allowed ($1)" >&2 + exit 1; + ;; + *) + MY_ARGV="${MY_ARGV} $1"; + ;; + esac + shift; + done + return 0; +} + +## +# Installs, configures and starts the service. +# +os_install_service() { + # Only NFS for S10. + if [ "${MY_SOLARIS_VER}" -lt 11 ]; then + if [ "${TESTBOXSCRIPT_BUILDS_TYPE}" != "nfs" -o "${TESTBOXSCRIPT_TESTRSRC_TYPE}" != "nfs" ]; then + echo "On solaris 10 both share types must be 'nfs', cifs (smbfs) is not supported." >&2 + return 1; + fi + fi + + # Calc the command line. + MY_ARGV="" + common_compile_testboxscript_command_line + + # Create the service xml config file. + cat > "${MY_SVC}" <<EOF +<?xml version='1.0'?> +<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1"> +<service_bundle type='manifest' name='export'> + <service name='system/virtualbox/testboxscript' type='service' version='1'> + <create_default_instance enabled='false' /> + <single_instance/> + + <!-- Wait for the network to start up --> + <dependency name='milestone-network' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/network:default' /> + </dependency> + + <!-- We wish to be started as late as possible... so go crazy with deps. --> + <dependency name='milestone-devices' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/devices:default' /> + </dependency> + <dependency name='multi-user' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/multi-user:default' /> + </dependency> + <dependency name='multi-user-server' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/multi-user-server:default' /> + </dependency> + <dependency name='filesystem-local' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/system/filesystem/local:default' /> + </dependency> + <dependency name='filesystem-autofs' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/system/filesystem/autofs:default' /> + </dependency> +EOF + if [ "`uname -r`" = "5.10" ]; then # Seems to be gone in S11? + cat >> "${MY_SVC}" <<EOF + <dependency name='filesystem-volfs' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/system/filesystem/volfs:default' /> + </dependency> +EOF + fi + cat >> "${MY_SVC}" <<EOF + <!-- start + stop methods --> + <exec_method type='method' name='start' exec='${MY_SCREEN} -S testboxscript -d -m ${MY_ARGV}' + timeout_seconds='30'> + <method_context working_directory='${TESTBOXSCRIPT_DIR}'> + <method_credential user='${TESTBOXSCRIPT_USER}' /> + <method_environment> + <envvar name='PATH' value='${PATH}' /> + </method_environment> + </method_context> + </exec_method> + + <exec_method type='method' name='stop' exec=':kill' timeout_seconds='60' /> + + <property_group name='startd' type='framework'> + <!-- sub-process core dumps/signals should not restart session --> + <propval name='ignore_error' type='astring' value='core,signal' /> + </property_group> + + <!-- Description --> + <template> + <common_name> + <loctext xml:lang='C'>VirtualBox TestBox Script</loctext> + </common_name> + </template> + </service> +</service_bundle> +EOF + + if test "${MY_SOLARIS_VER}" -lt 11; then + # Install the service, replacing old stuff. + if "${MY_SVCCFG}" "export" "${MY_SVC_FMRI}" > /dev/null 2>&1; then + "${MY_SVCCFG}" "delete" "${MY_SVC_FMRI}" + fi + "${MY_SVCCFG}" "import" "${MY_SVC}" + + # only for solaris version less than 11 + rm -f "${MY_SVC}" + else + "${MY_CHGRP}" "sys" "${MY_SVC}" + "${MY_SVCADM}" "restart" "manifest-import" + + # Do not remove the xml file in Solaris versions 11 and higher. + # The service will be removed automatically, if the command + # svcadm restart manifest-import + # will be executed + + fi + return 0; +} + +os_enable_service() { + "${MY_SVCADM}" "enable" "${MY_SVC_FMRI}" + return 0; +} + +os_disable_service() { + if "${MY_SVCCFG}" "export" "${MY_SVC_FMRI}" > /dev/null 2>&1; then + "${MY_SVCADM}" "disable" "${MY_SVC_FMRI}" + sleep 1 + fi + return 0; +} + +os_add_user() { + useradd -m -s /usr/bin/bash -G staff "${TESTBOXSCRIPT_USER}" + passwd "${TESTBOXSCRIPT_USER}" # This sucker prompts, seemingly no way around that. + return 0; +} + + +maybe_hush_up_root_login() { + # We don't want /etc/profile to display /etc/motd, quotas and mail status + # every time we do sudo -i... It may screw up serious if we parse the + # output of the command we sudid. + > ~root/.hushlogin + return 0; +} + +os_final_message() { + cat <<EOF + +Additional things to do:" + 1. Configure NTP: + a) echo "server wei01-time.de.oracle.com" > /etc/inet/ntp.conf + echo "driftfile /var/ntp/ntp.drift" >> /etc/inet/ntp.conf + b) Enable the service: svcadm enable ntp + c) Sync once in case of big diff: ntpdate wei01-time.de.oracle.com + d) Check that it works: ntpq -p + +Enjoy! +EOF +} + diff --git a/src/VBox/ValidationKit/testboxscript/testboxcommand.py b/src/VBox/ValidationKit/testboxscript/testboxcommand.py new file mode 100755 index 00000000..5f0c3cc3 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/testboxcommand.py @@ -0,0 +1,362 @@ +# -*- coding: utf-8 -*- +# $Id: testboxcommand.py $ + +""" +TestBox Script - Command Processor. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + +# Standard python imports. +import os; +import sys; +import threading; + +# Validation Kit imports. +from common import constants; +from common import utils, webutils; +import testboxcommons; +from testboxcommons import TestBoxException; +from testboxscript import TBS_EXITCODE_NEED_UPGRADE; +from testboxupgrade import upgradeFromZip; +from testboxtasks import TestBoxExecTask, TestBoxCleanupTask, TestBoxTestDriverTask; + +# Figure where we are. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksTestScriptDir = os.path.dirname(os.path.abspath(__file__)); + + + +class TestBoxCommand(object): + """ + Implementation of Test Box command. + """ + + ## The time to wait on the current task to abort. + kcSecStopTimeout = 360 + ## The time to wait on the current task to abort before rebooting. + kcSecStopBeforeRebootTimeout = 360 + + def __init__(self, oTestBoxScript): + """ + Class instance init + """ + self._oTestBoxScript = oTestBoxScript; + self._oCurTaskLock = threading.RLock(); + self._oCurTask = None; + + # List of available commands and their handlers + self._dfnCommands = \ + { + constants.tbresp.CMD_IDLE: self._cmdIdle, + constants.tbresp.CMD_WAIT: self._cmdWait, + constants.tbresp.CMD_EXEC: self._cmdExec, + constants.tbresp.CMD_ABORT: self._cmdAbort, + constants.tbresp.CMD_REBOOT: self._cmdReboot, + constants.tbresp.CMD_UPGRADE: self._cmdUpgrade, + constants.tbresp.CMD_UPGRADE_AND_REBOOT: self._cmdUpgradeAndReboot, + constants.tbresp.CMD_SPECIAL: self._cmdSpecial, + } + + def _cmdIdle(self, oResponse, oConnection): + """ + Idle response, no ACK. + """ + oResponse.checkParameterCount(1); + + # The dispatch loop will delay for us, so nothing to do here. + _ = oConnection; # Leave the connection open. + return True; + + def _cmdWait(self, oResponse, oConnection): + """ + Gang scheduling wait response, no ACK. + """ + oResponse.checkParameterCount(1); + + # The dispatch loop will delay for us, so nothing to do here. + _ = oConnection; # Leave the connection open. + return True; + + def _cmdExec(self, oResponse, oConnection): + """ + Execute incoming command + """ + + # Check if required parameters given and make a little sense. + idResult = oResponse.getIntChecked( constants.tbresp.EXEC_PARAM_RESULT_ID, 1); + sScriptZips = oResponse.getStringChecked(constants.tbresp.EXEC_PARAM_SCRIPT_ZIPS); + sScriptCmdLine = oResponse.getStringChecked(constants.tbresp.EXEC_PARAM_SCRIPT_CMD_LINE); + cSecTimeout = oResponse.getIntChecked( constants.tbresp.EXEC_PARAM_TIMEOUT, 30); + oResponse.checkParameterCount(5); + + sScriptFile = utils.argsGetFirst(sScriptCmdLine); + if sScriptFile is None: + raise TestBoxException('Bad script command line: "%s"' % (sScriptCmdLine,)); + if len(os.path.basename(sScriptFile)) < len('t.py'): + raise TestBoxException('Script file name too short: "%s"' % (sScriptFile,)); + if len(sScriptZips) < len('x.zip'): + raise TestBoxException('Script zip name too short: "%s"' % (sScriptFile,)); + + # One task at the time. + if self.isRunning(): + raise TestBoxException('Already running other command'); + + # Don't bother running the task without the shares mounted. + self._oTestBoxScript.mountShares(); # Raises exception on failure. + + # Kick off the task and ACK the command. + with self._oCurTaskLock: + self._oCurTask = TestBoxExecTask(self._oTestBoxScript, idResult = idResult, sScriptZips = sScriptZips, + sScriptCmdLine = sScriptCmdLine, cSecTimeout = cSecTimeout); + oConnection.sendAckAndClose(constants.tbresp.CMD_EXEC); + return True; + + def _cmdAbort(self, oResponse, oConnection): + """ + Abort background task + """ + oResponse.checkParameterCount(1); + oConnection.sendAck(constants.tbresp.CMD_ABORT); + + oCurTask = self._getCurTask(); + if oCurTask is not None: + oCurTask.terminate(); + oCurTask.flushLogOnConnection(oConnection); + oConnection.close(); + oCurTask.wait(self.kcSecStopTimeout); + + return True; + + def doReboot(self): + """ + Worker common to _cmdReboot and _doUpgrade that performs a system reboot. + """ + # !! Not more exceptions beyond this point !! + testboxcommons.log('Rebooting'); + + # Stop anything that might be executing at this point. + oCurTask = self._getCurTask(); + if oCurTask is not None: + oCurTask.terminate(); + oCurTask.wait(self.kcSecStopBeforeRebootTimeout); + + # Invoke shutdown command line utility. + sOs = utils.getHostOs(); + asCmd2 = None; + if sOs == 'win': + asCmd = ['shutdown', '/r', '/t', '0', '/c', '"ValidationKit triggered reboot"', '/d', '4:1']; + elif sOs == 'os2': + asCmd = ['setboot', '/B']; + elif sOs in ('solaris',): + asCmd = ['/usr/sbin/reboot', '-p']; + asCmd2 = ['/usr/sbin/reboot']; # Hack! S10 doesn't have -p, but don't know how to reliably detect S10. + else: + asCmd = ['/sbin/shutdown', '-r', 'now']; + try: + utils.sudoProcessOutputChecked(asCmd); + except Exception as oXcpt: + if asCmd2 is not None: + try: + utils.sudoProcessOutputChecked(asCmd2); + except Exception as oXcpt: + testboxcommons.log('Error executing reboot command "%s" as well as "%s": %s' % (asCmd, asCmd2, oXcpt)); + return False; + testboxcommons.log('Error executing reboot command "%s": %s' % (asCmd, oXcpt)); + return False; + + # Quit the script. + while True: + sys.exit(32); + return True; + + def _cmdReboot(self, oResponse, oConnection): + """ + Reboot Test Box + """ + oResponse.checkParameterCount(1); + oConnection.sendAckAndClose(constants.tbresp.CMD_REBOOT); + return self.doReboot(); + + def _doUpgrade(self, oResponse, oConnection, fReboot): + """ + Common worker for _cmdUpgrade and _cmdUpgradeAndReboot. + Will sys.exit on success! + """ + + # + # The server specifies a ZIP archive with the new scripts. It's ASSUMED + # that the zip is of selected files at g_ksValidationKitDir in SVN. It's + # further ASSUMED that we're executing from + # + sZipUrl = oResponse.getStringChecked(constants.tbresp.UPGRADE_PARAM_URL) + oResponse.checkParameterCount(2); + + if utils.isRunningFromCheckout(): + raise TestBoxException('Cannot upgrade when running from the tree!'); + oConnection.sendAckAndClose(constants.tbresp.CMD_UPGRADE_AND_REBOOT if fReboot else constants.tbresp.CMD_UPGRADE); + + testboxcommons.log('Upgrading...'); + + # + # Download the file and install it. + # + sDstFile = os.path.join(g_ksTestScriptDir, 'VBoxTestBoxScript.zip'); + if os.path.exists(sDstFile): + os.unlink(sDstFile); + fRc = webutils.downloadFile(sZipUrl, sDstFile, self._oTestBoxScript.getPathBuilds(), testboxcommons.log); + if fRc is not True: + return False; + + if upgradeFromZip(sDstFile) is not True: + return False; + + # + # Restart the system or the script (we have a parent script which + # respawns us when we quit). + # + if fReboot: + self.doReboot(); + sys.exit(TBS_EXITCODE_NEED_UPGRADE); + return False; # shuts up pylint (it will probably complain later when it learns DECL_NO_RETURN). + + def _cmdUpgrade(self, oResponse, oConnection): + """ + Upgrade Test Box Script + """ + return self._doUpgrade(oResponse, oConnection, False); + + def _cmdUpgradeAndReboot(self, oResponse, oConnection): + """ + Upgrade Test Box Script + """ + return self._doUpgrade(oResponse, oConnection, True); + + def _cmdSpecial(self, oResponse, oConnection): + """ + Reserved for future fun. + """ + oConnection.sendReplyAndClose(constants.tbreq.COMMAND_NOTSUP, constants.tbresp.CMD_SPECIAL); + testboxcommons.log('Special command %s not supported...' % (oResponse,)); + return False; + + + def handleCommand(self, oResponse, oConnection): + """ + Handles a command from the test manager. + + Some commands will close the connection, others (generally the simple + ones) wont, leaving the caller the option to use it for log flushing. + + Returns success indicator. + Raises no exception. + """ + try: + sCmdName = oResponse.getStringChecked(constants.tbresp.ALL_PARAM_RESULT); + except: + oConnection.close(); + return False; + + # Do we know the command? + fRc = False; + if sCmdName in self._dfnCommands: + testboxcommons.log(sCmdName); + try: + # Execute the handler. + fRc = self._dfnCommands[sCmdName](oResponse, oConnection) + except Exception as oXcpt: + # NACK the command if an exception is raised during parameter validation. + testboxcommons.log1Xcpt('Exception executing "%s": %s' % (sCmdName, oXcpt)); + if oConnection.isConnected(): + try: + oConnection.sendReplyAndClose(constants.tbreq.COMMAND_NACK, sCmdName); + except Exception as oXcpt2: + testboxcommons.log('Failed to NACK "%s": %s' % (sCmdName, oXcpt2)); + elif sCmdName in [constants.tbresp.STATUS_DEAD, constants.tbresp.STATUS_NACK]: + testboxcommons.log('Received status instead of command: %s' % (sCmdName, )); + else: + # NOTSUP the unknown command. + testboxcommons.log('Received unknown command: %s' % (sCmdName, )); + try: + oConnection.sendReplyAndClose(constants.tbreq.COMMAND_NOTSUP, sCmdName); + except Exception as oXcpt: + testboxcommons.log('Failed to NOTSUP "%s": %s' % (sCmdName, oXcpt)); + return fRc; + + def resumeIncompleteCommand(self): + """ + Resumes an incomplete command at startup. + + The EXEC commands saves essential state information in the scratch area + so we can resume them in case the testbox panics or is rebooted. + Current "resume" means doing cleanups, but we may need to implement + test scenarios involving rebooting the testbox later. + + Returns (idTestBox, sTestBoxName, True) if a command was resumed, + otherwise (-1, '', False). Raises no exceptions. + """ + + try: + oTask = TestBoxCleanupTask(self._oTestBoxScript); + except: + return (-1, '', False); + + with self._oCurTaskLock: + self._oCurTask = oTask; + + return (oTask.idTestBox, oTask.sTestBoxName, True); + + def isRunning(self): + """ + Check if we're running a task or not. + """ + oCurTask = self._getCurTask(); + return oCurTask is not None and oCurTask.isRunning(); + + def flushLogOnConnection(self, oGivenConnection): + """ + Flushes the log of any running task with a log buffer. + """ + oCurTask = self._getCurTask(); + if oCurTask is not None and isinstance(oCurTask, TestBoxTestDriverTask): + return oCurTask.flushLogOnConnection(oGivenConnection); + return None; + + def _getCurTask(self): + """ Gets the current task in a paranoidly safe manny. """ + with self._oCurTaskLock: + oCurTask = self._oCurTask; + return oCurTask; + diff --git a/src/VBox/ValidationKit/testboxscript/testboxcommons.py b/src/VBox/ValidationKit/testboxscript/testboxcommons.py new file mode 100755 index 00000000..26761833 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/testboxcommons.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +# $Id: testboxcommons.py $ + +""" +TestBox Script - Common Functions and Classes. + +This module contains constants and functions that are useful for all +the files in this (testbox) directory. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import sys +import traceback + +# Validation Kit imports. +from common import utils; + +# +# Exceptions. +# + +class TestBoxException(Exception): + """ + Custom exception class + """ + pass; # pylint: disable=unnecessary-pass + +# +# Logging. +# + +def log(sMessage, sCaller = None, sTsPrf = None): + """ + Print out a message and flush stdout + """ + if sTsPrf is None: sTsPrf = utils.getTimePrefix(); + print('[%s] %s' % (sTsPrf, sMessage,)); + sys.stdout.flush(); + _ = sCaller; + +def log2(sMessage, sCaller = None, sTsPrf = None): + """ + Debug logging, will later be disabled by default. + """ + if True is True: # pylint: disable=comparison-with-itself + if sTsPrf is None: sTsPrf = utils.getTimePrefix(); + print('[%s] %s' % (sTsPrf, sMessage,)); + sys.stdout.flush() + _ = sCaller; + +def _logXcptWorker(fnLogger, sPrefix = '', sText = None, cFrames = 1, fnLogger1 = log): + """ + Log an exception, optionally with a preceeding message and more than one + call frame. + """ + ## @todo skip all this if iLevel is too high! + + # Try get exception info. + sTsPrf = utils.getTimePrefix(); + try: + oType, oValue, oTraceback = sys.exc_info(); + except: + oType = oValue = oTraceback = None; + if oType is not None: + + # Try format the info + try: + rc = 0; + sCaller = utils.getCallerName(oTraceback.tb_frame); + if sText is not None: + rc = fnLogger('%s%s' % (sPrefix, sText), sCaller, sTsPrf); + asInfo = []; + try: + asInfo = asInfo + traceback.format_exception_only(oType, oValue); + if cFrames is not None and cFrames <= 1: + asInfo = asInfo + traceback.format_tb(oTraceback, 1); + else: + asInfo.append('Traceback:') + asInfo = asInfo + traceback.format_tb(oTraceback, cFrames); + asInfo.append('Stack:') + asInfo = asInfo + traceback.format_stack(oTraceback.tb_frame.f_back, cFrames); + except: + fnLogger1('internal-error: Hit exception #2! %s' % (traceback.format_exc()), sCaller, sTsPrf); + + if asInfo: + # Do the logging. + for sItem in asInfo: + asLines = sItem.splitlines(); + for sLine in asLines: + rc = fnLogger('%s%s' % (sPrefix, sLine), sCaller, sTsPrf); + + else: + fnLogger('No exception info...', sCaller, sTsPrf); + rc = -3; + except: + fnLogger1('internal-error: Hit exception! %s' % (traceback.format_exc()), None, sTsPrf); + rc = -2; + else: + fnLogger1('internal-error: No exception! %s' % (utils.getCallerName(iFrame=3)), utils.getCallerName(iFrame=3), sTsPrf); + rc = -1; + + return rc; + + +def log1Xcpt(sText = None, cFrames = 1): + """Logs an exception.""" + return _logXcptWorker(log, '', sText, cFrames); + +def log2Xcpt(sText = None, cFrames = 1): + """Debug logging of an exception.""" + return _logXcptWorker(log2, '', sText, cFrames); + diff --git a/src/VBox/ValidationKit/testboxscript/testboxconnection.py b/src/VBox/ValidationKit/testboxscript/testboxconnection.py new file mode 100755 index 00000000..b28d606e --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/testboxconnection.py @@ -0,0 +1,312 @@ +# -*- coding: utf-8 -*- +# $Id: testboxconnection.py $ + +""" +TestBox Script - HTTP Connection Handling. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard python imports. +import sys; +if sys.version_info[0] >= 3: + import http.client as httplib; # pylint: disable=import-error,no-name-in-module + import urllib.parse as urlparse; # pylint: disable=import-error,no-name-in-module + from urllib.parse import urlencode as urllib_urlencode; # pylint: disable=import-error,no-name-in-module +else: + import httplib; # pylint: disable=import-error,no-name-in-module + import urlparse; # pylint: disable=import-error,no-name-in-module + from urllib import urlencode as urllib_urlencode; # pylint: disable=import-error,no-name-in-module + +# Validation Kit imports. +from common import constants +from common import utils +import testboxcommons + + + +class TestBoxResponse(object): + """ + Response object return by TestBoxConnection.request(). + """ + def __init__(self, oResponse): + """ + Convert the HTTPResponse to a dictionary, raising TestBoxException on + malformed response. + """ + if oResponse is not None: + # Read the whole response (so we can log it). + sBody = oResponse.read(); + sBody = sBody.decode('utf-8'); + + # Check the content type. + sContentType = oResponse.getheader('Content-Type'); + if sContentType is None or sContentType != 'application/x-www-form-urlencoded; charset=utf-8': + testboxcommons.log('SERVER RESPONSE: Content-Type: %s' % (sContentType,)); + testboxcommons.log('SERVER RESPONSE: %s' % (sBody.rstrip(),)) + raise testboxcommons.TestBoxException('Invalid server response type: "%s"' % (sContentType,)); + + # Parse the body (this should be the exact reverse of what + # TestBoxConnection.postRequestRaw). + ##testboxcommons.log2('SERVER RESPONSE: "%s"' % (sBody,)) + self._dResponse = urlparse.parse_qs(sBody, strict_parsing=True); + + # Convert the dictionary from 'field:values' to 'field:value'. Fail + # if a field has more than one value (i.e. given more than once). + for sField in self._dResponse: + if len(self._dResponse[sField]) != 1: + raise testboxcommons.TestBoxException('The field "%s" appears more than once in the server response' \ + % (sField,)); + self._dResponse[sField] = self._dResponse[sField][0] + else: + # Special case, dummy response object. + self._dResponse = {}; + # Done. + + def getStringChecked(self, sField): + """ + Check if specified field is present in server response and returns it as string. + If not present, a fitting exception will be raised. + """ + if not sField in self._dResponse: + raise testboxcommons.TestBoxException('Required data (' + str(sField) + ') was not found in server response'); + return str(self._dResponse[sField]).strip(); + + def getIntChecked(self, sField, iMin = None, iMax = None): + """ + Check if specified field is present in server response and returns it as integer. + If not present, a fitting exception will be raised. + + The iMin and iMax values are inclusive. + """ + if not sField in self._dResponse: + raise testboxcommons.TestBoxException('Required data (' + str(sField) + ') was not found in server response') + try: + iValue = int(self._dResponse[sField]); + except: + raise testboxcommons.TestBoxException('Malformed integer field %s: "%s"' % (sField, self._dResponse[sField])); + + if (iMin is not None and iValue < iMin) \ + or (iMax is not None and iValue > iMax): + raise testboxcommons.TestBoxException('Value (%d) of field %s is out of range [%s..%s]' \ + % (iValue, sField, iMin, iMax)); + return iValue; + + def checkParameterCount(self, cExpected): + """ + Checks the parameter count, raise TestBoxException if it doesn't meet + the expectations. + """ + if len(self._dResponse) != cExpected: + raise testboxcommons.TestBoxException('Expected %d parameters, server sent %d' % (cExpected, len(self._dResponse))); + return True; + + def toString(self): + """ + Convers the response to a string (for debugging purposes). + """ + return str(self._dResponse); + + +class TestBoxConnection(object): + """ + Wrapper around HTTPConnection. + """ + + def __init__(self, sTestManagerUrl, sTestBoxId, sTestBoxUuid, fLongTimeout = False): + """ + Constructor. + """ + self._oConn = None; + self._oParsedUrl = urlparse.urlparse(sTestManagerUrl); + self._sTestBoxId = sTestBoxId; + self._sTestBoxUuid = sTestBoxUuid; + + # + # Connect to it - may raise exception on failure. + # When connecting we're using a 15 second timeout, we increase it later. + # + if self._oParsedUrl.scheme == 'https': # pylint: disable=no-member + fnCtor = httplib.HTTPSConnection; + else: + fnCtor = httplib.HTTPConnection; + if sys.version_info[0] >= 3 \ + or (sys.version_info[0] == 2 and sys.version_info[1] >= 6): + + self._oConn = fnCtor(self._oParsedUrl.hostname, timeout=15); + else: + self._oConn = fnCtor(self._oParsedUrl.hostname); + + if self._oConn.sock is None: + self._oConn.connect(); + + # + # Increase the timeout for the non-connect operations. + # + try: + self._oConn.sock.settimeout(5*60 if fLongTimeout else 1 * 60); + except: + pass; + + ##testboxcommons.log2('hostname=%s timeout=%u' % (self._oParsedUrl.hostname, self._oConn.sock.gettimeout())); + + def __del__(self): + """ Makes sure the connection is really closed on destruction """ + self.close() + + def close(self): + """ Closes the connection """ + if self._oConn is not None: + self._oConn.close(); + self._oConn = None; + + def postRequestRaw(self, sAction, dParams): + """ + Posts a request to the test manager and gets the response. The dParams + argument is a dictionary of unencoded key-value pairs (will be + modified). + Raises exception on failure. + """ + dHeader = \ + { + 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', + 'User-Agent': 'TestBoxScript/%s.0 (%s, %s)' % (__version__, utils.getHostOs(), utils.getHostArch()), + 'Accept': 'text/plain,application/x-www-form-urlencoded', + 'Accept-Encoding': 'identity', + 'Cache-Control': 'max-age=0', + 'Connection': 'keep-alive', + }; + sServerPath = '/%s/testboxdisp.py' % (self._oParsedUrl.path.strip('/'),); # pylint: disable=no-member + dParams[constants.tbreq.ALL_PARAM_ACTION] = sAction; + sBody = urllib_urlencode(dParams); + ##testboxcommons.log2('sServerPath=%s' % (sServerPath,)); + try: + self._oConn.request('POST', sServerPath, sBody, dHeader); + oResponse = self._oConn.getresponse(); + oResponse2 = TestBoxResponse(oResponse); + except: + testboxcommons.log2Xcpt(); + raise + return oResponse2; + + def postRequest(self, sAction, dParams = None): + """ + Posts a request to the test manager, prepending the testbox ID and + UUID to the arguments, and gets the response. The dParams argument is a + is a dictionary of unencoded key-value pairs (will be modified). + Raises exception on failure. + """ + if dParams is None: + dParams = {}; + dParams[constants.tbreq.ALL_PARAM_TESTBOX_ID] = self._sTestBoxId; + dParams[constants.tbreq.ALL_PARAM_TESTBOX_UUID] = self._sTestBoxUuid; + return self.postRequestRaw(sAction, dParams); + + def sendReply(self, sReplyAction, sCmdName): + """ + Sends a reply to a test manager command. + Raises exception on failure. + """ + return self.postRequest(sReplyAction, { constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME: sCmdName }); + + def sendReplyAndClose(self, sReplyAction, sCmdName): + """ + Sends a reply to a test manager command and closes the connection. + Raises exception on failure. + """ + self.sendReply(sReplyAction, sCmdName); + self.close(); + return True; + + def sendAckAndClose(self, sCmdName): + """ + Acks a command and closes the connection to the test manager. + Raises exception on failure. + """ + return self.sendReplyAndClose(constants.tbreq.COMMAND_ACK, sCmdName); + + def sendAck(self, sCmdName): + """ + Acks a command. + Raises exception on failure. + """ + return self.sendReply(constants.tbreq.COMMAND_ACK, sCmdName); + + @staticmethod + def sendSignOn(sTestManagerUrl, dParams): + """ + Sends a sign-on request to the server, returns the response (TestBoxResponse). + No exceptions will be raised. + """ + oConnection = None; + try: + oConnection = TestBoxConnection(sTestManagerUrl, None, None); + return oConnection.postRequestRaw(constants.tbreq.SIGNON, dParams); + except: + testboxcommons.log2Xcpt(); + if oConnection is not None: # Be kind to apache. + try: oConnection.close(); + except: pass; + + return TestBoxResponse(None); + + @staticmethod + def requestCommandWithConnection(sTestManagerUrl, sTestBoxId, sTestBoxUuid, fBusy): + """ + Queries the test manager for a command and returns its respons + an open + connection for acking/nack the command (and maybe more). + + No exceptions will be raised. On failure (None, None) will be returned. + """ + oConnection = None; + try: + oConnection = TestBoxConnection(sTestManagerUrl, sTestBoxId, sTestBoxUuid, fLongTimeout = not fBusy); + if fBusy: + oResponse = oConnection.postRequest(constants.tbreq.REQUEST_COMMAND_BUSY); + else: + oResponse = oConnection.postRequest(constants.tbreq.REQUEST_COMMAND_IDLE); + return (oResponse, oConnection); + except: + testboxcommons.log2Xcpt(); + if oConnection is not None: # Be kind to apache. + try: oConnection.close(); + except: pass; + return (None, None); + + def isConnected(self): + """ + Checks if we are still connected. + """ + return self._oConn is not None; diff --git a/src/VBox/ValidationKit/testboxscript/testboxscript.py b/src/VBox/ValidationKit/testboxscript/testboxscript.py new file mode 100755 index 00000000..7538565d --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/testboxscript.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: testboxscript.py $ + +""" +TestBox Script Wrapper. + +This script aimes at respawning the Test Box Script when it terminates +abnormally or due to an UPGRADE request. +""" + +from __future__ import print_function; + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154257 $" + +import platform; +import subprocess; +import sys; +import os; +import time; + + +## @name Test Box script exit statuses (see also RTEXITCODE) +# @remarks These will _never_ change +# @{ +TBS_EXITCODE_FAILURE = 1; # RTEXITCODE_FAILURE +TBS_EXITCODE_SYNTAX = 2; # RTEXITCODE_SYNTAX +TBS_EXITCODE_NEED_UPGRADE = 9; +## @} + + +class TestBoxScriptWrapper(object): # pylint: disable=too-few-public-methods + """ + Wrapper class + """ + + TESTBOX_SCRIPT_FILENAME = 'testboxscript_real.py' + + def __init__(self): + """ + Init + """ + self.oTask = None + + def __del__(self): + """ + Cleanup + """ + if self.oTask is not None: + print('Wait for child task...'); + self.oTask.terminate() + self.oTask.wait() + print('done. Exiting'); + self.oTask = None; + + def run(self): + """ + Start spawning the real TestBox script. + """ + + # Figure out where we live first. + try: + __file__ + except: + __file__ = sys.argv[0]; + sTestBoxScriptDir = os.path.dirname(os.path.abspath(__file__)); + + # Construct the argument list for the real script (same dir). + sRealScript = os.path.join(sTestBoxScriptDir, TestBoxScriptWrapper.TESTBOX_SCRIPT_FILENAME); + asArgs = sys.argv[1:]; + asArgs.insert(0, sRealScript); + if sys.executable: + asArgs.insert(0, sys.executable); + + # Look for --pidfile <name> and write a pid file. + sPidFile = None; + for i, _ in enumerate(asArgs): + if asArgs[i] == '--pidfile' and i + 1 < len(asArgs): + sPidFile = asArgs[i + 1]; + break; + if asArgs[i] == '--': + break; + if sPidFile: + with open(sPidFile, 'w') as oPidFile: + oPidFile.write(str(os.getpid())); + + # Execute the testbox script almost forever in a relaxed loop. + rcExit = TBS_EXITCODE_FAILURE; + while True: + fCreationFlags = 0; + if platform.system() == 'Windows': + fCreationFlags = getattr(subprocess, 'CREATE_NEW_PROCESS_GROUP', 0x00000200); # for Ctrl-C isolation (python 2.7) + self.oTask = subprocess.Popen(asArgs, shell = False, # pylint: disable=consider-using-with + creationflags = fCreationFlags); + rcExit = self.oTask.wait(); + self.oTask = None; + if rcExit == TBS_EXITCODE_SYNTAX: + break; + + # Relax. + time.sleep(1); + return rcExit; + +if __name__ == '__main__': + sys.exit(TestBoxScriptWrapper().run()); + diff --git a/src/VBox/ValidationKit/testboxscript/testboxscript_real.py b/src/VBox/ValidationKit/testboxscript/testboxscript_real.py new file mode 100755 index 00000000..f72fc6cc --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/testboxscript_real.py @@ -0,0 +1,1073 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: testboxscript_real.py $ + +""" +TestBox Script - main(). +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import math +import os +from optparse import OptionParser # pylint: disable=deprecated-module +import platform +import random +import shutil +import sys +import tempfile +import time +import uuid + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksTestScriptDir = os.path.dirname(os.path.abspath(__file__)); +g_ksValidationKitDir = os.path.dirname(g_ksTestScriptDir); +sys.path.extend([g_ksTestScriptDir, g_ksValidationKitDir]); + +# Validation Kit imports. +from common import constants; +from common import utils; +import testboxcommons; +from testboxcommons import TestBoxException; +from testboxcommand import TestBoxCommand; +from testboxconnection import TestBoxConnection; +from testboxscript import TBS_EXITCODE_SYNTAX, TBS_EXITCODE_FAILURE; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + + +class TestBoxScriptException(Exception): + """ For raising exceptions during TestBoxScript.__init__. """ + pass; # pylint: disable=unnecessary-pass + + +class TestBoxScript(object): + """ + Implementation of the test box script. + Communicate with test manager and perform offered actions. + """ + + ## @name Class Constants. + # @{ + + # Scratch space round value (MB). + kcMbScratchSpaceRounding = 64 + # Memory size round value (MB). + kcMbMemoryRounding = 4 + # A NULL UUID in string form. + ksNullUuid = '00000000-0000-0000-0000-000000000000'; + # The minimum dispatch loop delay. + kcSecMinDelay = 12; + # The maximum dispatch loop delay (inclusive). + kcSecMaxDelay = 24; + # The minimum sign-on delay. + kcSecMinSignOnDelay = 30; + # The maximum sign-on delay (inclusive). + kcSecMaxSignOnDelay = 60; + + # Keys for config params + VALUE = 'value' + FN = 'fn' # pylint: disable=invalid-name + + ## @} + + + def __init__(self, oOptions): + """ + Initialize internals + """ + self._oOptions = oOptions; + self._sTestBoxHelper = None; + + # Signed-on state + self._cSignOnAttempts = 0; + self._fSignedOn = False; + self._fNeedReSignOn = False; + self._fFirstSignOn = True; + self._idTestBox = None; + self._sTestBoxName = ''; + self._sTestBoxUuid = self.ksNullUuid; # convenience, assigned below. + + # Command processor. + self._oCommand = TestBoxCommand(self); + + # + # Scratch dir setup. Use /var/tmp instead of /tmp because we may need + # many many GBs for some test scenarios and /tmp can be backed by swap + # or be a fast+small disk of some kind, while /var/tmp is normally + # larger, if slower. /var/tmp is generally not cleaned up on reboot, + # /tmp often is, this would break host panic / triple-fault detection. + # + if self._oOptions.sScratchRoot is None: + if utils.getHostOs() in ('win', 'os2', 'haiku', 'dos'): + # We need *lots* of space, so avoid /tmp as it may be a memory + # file system backed by the swap file, or worse. + self._oOptions.sScratchRoot = tempfile.gettempdir(); + else: + self._oOptions.sScratchRoot = '/var/tmp'; + sSubDir = 'testbox'; + try: + sSubDir = '%s-%u' % (sSubDir, os.getuid()); # pylint: disable=no-member + except: + pass; + self._oOptions.sScratchRoot = os.path.join(self._oOptions.sScratchRoot, sSubDir); + + self._sScratchSpill = os.path.join(self._oOptions.sScratchRoot, 'scratch'); + self._sScratchScripts = os.path.join(self._oOptions.sScratchRoot, 'scripts'); + self._sScratchState = os.path.join(self._oOptions.sScratchRoot, 'state'); # persistant storage. + + for sDir in [self._oOptions.sScratchRoot, self._sScratchSpill, self._sScratchScripts, self._sScratchState]: + if not os.path.isdir(sDir): + os.makedirs(sDir, 0o700); + + # We count consecutive reinitScratch failures and will reboot the + # testbox after a while in the hope that it will correct the issue. + self._cReinitScratchErrors = 0; + + # + # Mount builds and test resources if requested. + # + self.mountShares(); + + # + # Sign-on parameters: Packed into list of records of format: + # { <Parameter ID>: { <Current value>, <Check function> } } + # + self._ddSignOnParams = \ + { + constants.tbreq.ALL_PARAM_TESTBOX_UUID: { self.VALUE: self._getHostSystemUuid(), self.FN: None }, + constants.tbreq.SIGNON_PARAM_OS: { self.VALUE: utils.getHostOs(), self.FN: None }, + constants.tbreq.SIGNON_PARAM_OS_VERSION: { self.VALUE: utils.getHostOsVersion(), self.FN: None }, + constants.tbreq.SIGNON_PARAM_CPU_ARCH: { self.VALUE: utils.getHostArch(), self.FN: None }, + constants.tbreq.SIGNON_PARAM_CPU_VENDOR: { self.VALUE: self._getHostCpuVendor(), self.FN: None }, + constants.tbreq.SIGNON_PARAM_CPU_NAME: { self.VALUE: self._getHostCpuName(), self.FN: None }, + constants.tbreq.SIGNON_PARAM_CPU_REVISION: { self.VALUE: self._getHostCpuRevision(), self.FN: None }, + constants.tbreq.SIGNON_PARAM_HAS_HW_VIRT: { self.VALUE: self._hasHostHwVirt(), self.FN: None }, + constants.tbreq.SIGNON_PARAM_HAS_NESTED_PAGING:{ self.VALUE: self._hasHostNestedPaging(), self.FN: None }, + constants.tbreq.SIGNON_PARAM_HAS_64_BIT_GUEST: { self.VALUE: self._can64BitGuest(), self.FN: None }, + constants.tbreq.SIGNON_PARAM_HAS_IOMMU: { self.VALUE: self._hasHostIoMmu(), self.FN: None }, + #constants.tbreq.SIGNON_PARAM_WITH_RAW_MODE: { self.VALUE: self._withRawModeSupport(), self.FN: None }, + constants.tbreq.SIGNON_PARAM_SCRIPT_REV: { self.VALUE: self._getScriptRev(), self.FN: None }, + constants.tbreq.SIGNON_PARAM_REPORT: { self.VALUE: self._getHostReport(), self.FN: None }, + constants.tbreq.SIGNON_PARAM_PYTHON_VERSION: { self.VALUE: self._getPythonHexVersion(), self.FN: None }, + constants.tbreq.SIGNON_PARAM_CPU_COUNT: { self.VALUE: None, self.FN: utils.getPresentCpuCount }, + constants.tbreq.SIGNON_PARAM_MEM_SIZE: { self.VALUE: None, self.FN: self._getHostMemSize }, + constants.tbreq.SIGNON_PARAM_SCRATCH_SIZE: { self.VALUE: None, self.FN: self._getFreeScratchSpace }, + } + for sItem in self._ddSignOnParams: # pylint: disable=consider-using-dict-items + if self._ddSignOnParams[sItem][self.FN] is not None: + self._ddSignOnParams[sItem][self.VALUE] = self._ddSignOnParams[sItem][self.FN]() + + testboxcommons.log('Starting Test Box script (%s)' % (self._getScriptRev(),)); + testboxcommons.log('Test Manager URL: %s' % self._oOptions.sTestManagerUrl,) + testboxcommons.log('Scratch root path: %s' % self._oOptions.sScratchRoot,) + for sItem in self._ddSignOnParams: # pylint: disable=consider-using-dict-items + testboxcommons.log('Sign-On value %18s: %s' % (sItem, self._ddSignOnParams[sItem][self.VALUE])); + + # + # The System UUID is the primary identification of the machine, so + # refuse to cooperate if it's NULL. + # + self._sTestBoxUuid = self.getSignOnParam(constants.tbreq.ALL_PARAM_TESTBOX_UUID); + if self._sTestBoxUuid == self.ksNullUuid: + raise TestBoxScriptException('Couldn\'t determine the System UUID, please use --system-uuid to specify it.'); + + # + # Export environment variables, clearing any we don't know yet. + # + for sEnvVar in self._oOptions.asEnvVars: + iEqual = sEnvVar.find('='); + if iEqual == -1: # No '=', remove it. + if sEnvVar in os.environ: + del os.environ[sEnvVar]; + elif iEqual > 0: # Set it. + os.environ[sEnvVar[:iEqual]] = sEnvVar[iEqual+1:]; + else: # Starts with '=', bad user. + raise TestBoxScriptException('Invalid -E argument: "%s"' % (sEnvVar,)); + + os.environ['TESTBOX_PATH_BUILDS'] = self._oOptions.sBuildsPath; + os.environ['TESTBOX_PATH_RESOURCES'] = self._oOptions.sTestRsrcPath; + os.environ['TESTBOX_PATH_SCRATCH'] = self._sScratchSpill; + os.environ['TESTBOX_PATH_SCRIPTS'] = self._sScratchScripts; + os.environ['TESTBOX_PATH_UPLOAD'] = self._sScratchSpill; ## @todo drop the UPLOAD dir? + os.environ['TESTBOX_HAS_HW_VIRT'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_HAS_HW_VIRT); + os.environ['TESTBOX_HAS_NESTED_PAGING'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_HAS_NESTED_PAGING); + os.environ['TESTBOX_HAS_IOMMU'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_HAS_IOMMU); + os.environ['TESTBOX_SCRIPT_REV'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_SCRIPT_REV); + os.environ['TESTBOX_CPU_COUNT'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_CPU_COUNT); + os.environ['TESTBOX_MEM_SIZE'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_MEM_SIZE); + os.environ['TESTBOX_SCRATCH_SIZE'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_SCRATCH_SIZE); + #TODO: os.environ['TESTBOX_WITH_RAW_MODE'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_WITH_RAW_MODE); + os.environ['TESTBOX_WITH_RAW_MODE'] = str(self._withRawModeSupport()); + os.environ['TESTBOX_MANAGER_URL'] = self._oOptions.sTestManagerUrl; + os.environ['TESTBOX_UUID'] = self._sTestBoxUuid; + os.environ['TESTBOX_REPORTER'] = 'remote'; + os.environ['TESTBOX_NAME'] = ''; + os.environ['TESTBOX_ID'] = ''; + os.environ['TESTBOX_TEST_SET_ID'] = ''; + os.environ['TESTBOX_TIMEOUT'] = '0'; + os.environ['TESTBOX_TIMEOUT_ABS'] = '0'; + + if utils.getHostOs() == 'win': + os.environ['COMSPEC'] = os.path.join(os.environ['SystemRoot'], 'System32', 'cmd.exe'); + # Currently omitting any kBuild tools. + + def mountShares(self): + """ + Mounts the shares. + Raises exception on failure. + """ + self._mountShare(self._oOptions.sBuildsPath, self._oOptions.sBuildsServerType, self._oOptions.sBuildsServerName, + self._oOptions.sBuildsServerShare, + self._oOptions.sBuildsServerUser, self._oOptions.sBuildsServerPasswd, + self._oOptions.sBuildsServerMountOpt, 'builds'); + self._mountShare(self._oOptions.sTestRsrcPath, self._oOptions.sTestRsrcServerType, self._oOptions.sTestRsrcServerName, + self._oOptions.sTestRsrcServerShare, + self._oOptions.sTestRsrcServerUser, self._oOptions.sTestRsrcServerPasswd, + self._oOptions.sTestRsrcServerMountOpt, 'testrsrc'); + return True; + + def _mountShare(self, sMountPoint, sType, sServer, sShare, sUser, sPassword, sMountOpt, sWhat): + """ + Mounts the specified share if needed. + Raises exception on failure. + """ + # Only mount if the type is specified. + if sType is None: + return True; + + # Test if already mounted. + sTestFile = os.path.join(sMountPoint + os.path.sep, os.path.basename(sShare) + '-new.txt'); + if os.path.isfile(sTestFile): + return True; + + # + # Platform specific mount code. + # + sHostOs = utils.getHostOs() + if sHostOs in ('darwin', 'freebsd'): + if sMountOpt != '': + sMountOpt = ',' + sMountOpt + utils.sudoProcessCall(['/sbin/umount', sMountPoint]); + utils.sudoProcessCall(['/bin/mkdir', '-p', sMountPoint]); + utils.sudoProcessCall(['/usr/sbin/chown', str(os.getuid()), sMountPoint]); # pylint: disable=no-member + if sType == 'cifs': + # Note! no smb://server/share stuff here, 10.6.8 didn't like it. + utils.processOutputChecked(['/sbin/mount_smbfs', + '-o', + 'automounted,nostreams,soft,noowners,noatime,rdonly' + sMountOpt, + '-f', '0555', '-d', '0555', + '//%s:%s@%s/%s' % (sUser, sPassword, sServer, sShare), + sMountPoint]); + else: + raise TestBoxScriptException('Unsupported server type %s.' % (sType,)); + + elif sHostOs == 'linux': + if sMountOpt != '': + sMountOpt = ',' + sMountOpt + utils.sudoProcessCall(['/bin/umount', sMountPoint]); + utils.sudoProcessCall(['/bin/mkdir', '-p', sMountPoint]); + if sType == 'cifs': + utils.sudoProcessOutputChecked(['/bin/mount', '-t', 'cifs', + '-o', + 'user=' + sUser + + ',password=' + sPassword + + ',sec=ntlmv2' + + ',uid=' + str(os.getuid()) # pylint: disable=no-member + + ',gid=' + str(os.getgid()) # pylint: disable=no-member + + ',nounix,file_mode=0555,dir_mode=0555,soft,ro' + + sMountOpt, + '//%s/%s' % (sServer, sShare), + sMountPoint]); + elif sType == 'nfs': + utils.sudoProcessOutputChecked(['/bin/mount', '-t', 'nfs', + '-o', 'soft,ro' + sMountOpt, + '%s:%s' % (sServer, sShare if sShare.find('/') >= 0 else ('/export/' + sShare)), + sMountPoint]); + + else: + raise TestBoxScriptException('Unsupported server type %s.' % (sType,)); + + elif sHostOs == 'solaris': + if sMountOpt != '': + sMountOpt = ',' + sMountOpt + utils.sudoProcessCall(['/sbin/umount', sMountPoint]); + utils.sudoProcessCall(['/bin/mkdir', '-p', sMountPoint]); + if sType == 'cifs': + ## @todo This stuff doesn't work on wei01-x4600b.de.oracle.com running 11.1. FIXME! + oPasswdFile = tempfile.TemporaryFile(); # pylint: disable=consider-using-with + oPasswdFile.write(sPassword + '\n'); + oPasswdFile.flush(); + utils.sudoProcessOutputChecked(['/sbin/mount', '-F', 'smbfs', + '-o', + 'user=' + sUser + + ',uid=' + str(os.getuid()) # pylint: disable=no-member + + ',gid=' + str(os.getgid()) # pylint: disable=no-member + + ',fileperms=0555,dirperms=0555,noxattr,ro' + + sMountOpt, + '//%s/%s' % (sServer, sShare), + sMountPoint], + stdin = oPasswdFile); + oPasswdFile.close(); + elif sType == 'nfs': + utils.sudoProcessOutputChecked(['/sbin/mount', '-F', 'nfs', + '-o', 'noxattr,ro' + sMountOpt, + '%s:%s' % (sServer, sShare if sShare.find('/') >= 0 else ('/export/' + sShare)), + sMountPoint]); + + else: + raise TestBoxScriptException('Unsupported server type %s.' % (sType,)); + + + elif sHostOs == 'win': + if sType != 'cifs': + raise TestBoxScriptException('Only CIFS mounts are supported on Windows.'); + utils.processCall(['net', 'use', sMountPoint, '/d']); + utils.processOutputChecked(['net', 'use', sMountPoint, + '\\\\' + sServer + '\\' + sShare, + sPassword, + '/USER:' + sUser,]); + else: + raise TestBoxScriptException('Unsupported host %s' % (sHostOs,)); + + # + # Re-test. + # + if not os.path.isfile(sTestFile): + raise TestBoxException('Failed to mount %s (%s[%s]) at %s: %s not found' + % (sWhat, sServer, sShare, sMountPoint, sTestFile)); + + return True; + + ## @name Signon property releated methods. + # @{ + + def _getHelperOutput(self, sCmd): + """ + Invokes TestBoxHelper to obtain information hard to access from python. + """ + if self._sTestBoxHelper is None: + if not utils.isRunningFromCheckout(): + # See VBoxTestBoxScript.zip for layout. + self._sTestBoxHelper = os.path.join(g_ksValidationKitDir, utils.getHostOs(), utils.getHostArch(), \ + 'TestBoxHelper'); + else: # Only for in-tree testing, so don't bother be too accurate right now. + sType = os.environ.get('KBUILD_TYPE', 'debug'); + self._sTestBoxHelper = os.path.join(g_ksValidationKitDir, os.pardir, os.pardir, os.pardir, 'out', \ + utils.getHostOsDotArch(), sType, 'testboxscript', \ + utils.getHostOs(), utils.getHostArch(), \ + 'TestBoxHelper'); + if utils.getHostOs() in ['win', 'os2']: + self._sTestBoxHelper += '.exe'; + + return utils.processOutputChecked([self._sTestBoxHelper, sCmd]).strip(); + + def _getHelperOutputTristate(self, sCmd, fDunnoValue): + """ + Invokes TestBoxHelper to obtain information hard to access from python. + """ + sValue = self._getHelperOutput(sCmd); + sValue = sValue.lower(); + if sValue == 'true': + return True; + if sValue == 'false': + return False; + if sValue not in ('dunno', 'none',): + raise TestBoxException('Unexpected response "%s" to helper command "%s"' % (sValue, sCmd)); + return fDunnoValue; + + + @staticmethod + def _isUuidGood(sUuid): + """ + Checks if the UUID looks good. + + There are systems with really bad UUIDs, for instance + "03000200-0400-0500-0006-000700080009". + """ + if sUuid == TestBoxScript.ksNullUuid: + return False; + sUuid = sUuid.lower(); + for sDigit in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']: + if sUuid.count(sDigit) > 16: + return False; + return True; + + def _getHostSystemUuid(self): + """ + Get the system UUID string from the System, return null-uuid if + unable to get retrieve it. + """ + if self._oOptions.sSystemUuid is not None: + return self._oOptions.sSystemUuid; + + sUuid = self.ksNullUuid; + + # + # Try get at the firmware UUID. + # + if utils.getHostOs() == 'linux': + # NOTE: This requires to have kernel option enabled: + # Firmware Drivers -> Export DMI identification via sysfs to userspace + if os.path.exists('/sys/devices/virtual/dmi/id/product_uuid'): + try: + sVar = utils.sudoProcessOutputChecked(['cat', '/sys/devices/virtual/dmi/id/product_uuid']); + sUuid = str(uuid.UUID(sVar.strip())); + except: + pass; + ## @todo consider dmidecoder? What about EFI systems? + + elif utils.getHostOs() == 'win': + # Windows: WMI + try: + import win32com.client; # pylint: disable=import-error + oWmi = win32com.client.Dispatch('WbemScripting.SWbemLocator'); + oWebm = oWmi.ConnectServer('.', 'root\\cimv2'); + for oItem in oWebm.ExecQuery('SELECT * FROM Win32_ComputerSystemProduct'): + if oItem.UUID is not None: + sUuid = str(uuid.UUID(oItem.UUID)); + except: + pass; + + elif utils.getHostOs() == 'darwin': + try: + sVar = utils.processOutputChecked(['/bin/sh', '-c', + '/usr/sbin/ioreg -k IOPlatformUUID' \ + + '| /usr/bin/grep IOPlatformUUID' \ + + '| /usr/bin/head -1']); + sVar = sVar.strip()[-(len(self.ksNullUuid) + 1):-1]; + sUuid = str(uuid.UUID(sVar)); + except: + pass; + + elif utils.getHostOs() == 'solaris': + # Solaris: The smbios util. + try: + sVar = utils.processOutputChecked(['/bin/sh', '-c', + '/usr/sbin/smbios ' \ + + '| /usr/xpg4/bin/sed -ne \'s/^.*UUID: *//p\'' \ + + '| /usr/bin/head -1']); + sUuid = str(uuid.UUID(sVar.strip())); + except: + pass; + + if self._isUuidGood(sUuid): + return sUuid; + + # + # Try add the MAC address. + # uuid.getnode may provide it, or it may return a random number... + # + lMacAddr = uuid.getnode(); + sNode = '%012x' % (lMacAddr,) + if lMacAddr == uuid.getnode() and lMacAddr != 0 and len(sNode) == 12: + return sUuid[:-12] + sNode; + + return sUuid; + + def _getHostCpuVendor(self): + """ + Get the CPUID vendor string on intel HW. + """ + return self._getHelperOutput('cpuvendor'); + + def _getHostCpuName(self): + """ + Get the CPU name/description string. + """ + return self._getHelperOutput('cpuname'); + + def _getHostCpuRevision(self): + """ + Get the CPU revision (family/model/stepping) value. + """ + return self._getHelperOutput('cpurevision'); + + def _hasHostHwVirt(self): + """ + Check if the host supports AMD-V or VT-x + """ + if self._oOptions.fHasHwVirt is None: + self._oOptions.fHasHwVirt = self._getHelperOutput('cpuhwvirt'); + return self._oOptions.fHasHwVirt; + + def _hasHostNestedPaging(self): + """ + Check if the host supports nested paging. + """ + if not self._hasHostHwVirt(): + return False; + if self._oOptions.fHasNestedPaging is None: + self._oOptions.fHasNestedPaging = self._getHelperOutputTristate('nestedpaging', False); + return self._oOptions.fHasNestedPaging; + + def _can64BitGuest(self): + """ + Check if the we (VBox) can run 64-bit guests. + """ + if not self._hasHostHwVirt(): + return False; + if self._oOptions.fCan64BitGuest is None: + self._oOptions.fCan64BitGuest = self._getHelperOutputTristate('longmode', True); + return self._oOptions.fCan64BitGuest; + + def _hasHostIoMmu(self): + """ + Check if the host has an I/O MMU of the VT-d kind. + """ + if not self._hasHostHwVirt(): + return False; + if self._oOptions.fHasIoMmu is None: + ## @todo Any way to figure this one out on any host OS? + self._oOptions.fHasIoMmu = False; + return self._oOptions.fHasIoMmu; + + def _withRawModeSupport(self): + """ + Check if the testbox is configured with raw-mode support or not. + """ + if self._oOptions.fWithRawMode is None: + self._oOptions.fWithRawMode = True; + return self._oOptions.fWithRawMode; + + def _getHostReport(self): + """ + Generate a report about the host hardware and software. + """ + return self._getHelperOutput('report'); + + + def _getHostMemSize(self): + """ + Gets the amount of physical memory on the host (and accessible to the + OS, i.e. don't report stuff over 4GB if Windows doesn't wanna use it). + Unit: MiB. + """ + cMbMemory = long(self._getHelperOutput('memsize').strip()) / (1024 * 1024); + + # Round it. + cMbMemory = long(math.floor(cMbMemory / self.kcMbMemoryRounding)) * self.kcMbMemoryRounding; + return cMbMemory; + + def _getFreeScratchSpace(self): + """ + Get free space on the volume where scratch directory is located and + return it in bytes rounded down to nearest 64MB + (currently works on Linux only) + Unit: MiB. + """ + if platform.system() == 'Windows': + import ctypes + cTypeMbFreeSpace = ctypes.c_ulonglong(0) + ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(self._oOptions.sScratchRoot), None, None, + ctypes.pointer(cTypeMbFreeSpace)) + cMbFreeSpace = cTypeMbFreeSpace.value + else: + stats = os.statvfs(self._oOptions.sScratchRoot); # pylint: disable=no-member + cMbFreeSpace = stats.f_frsize * stats.f_bfree + + # Convert to MB + cMbFreeSpace = long(cMbFreeSpace) /(1024 * 1024) + + # Round free space size + cMbFreeSpace = long(math.floor(cMbFreeSpace / self.kcMbScratchSpaceRounding)) * self.kcMbScratchSpaceRounding; + return cMbFreeSpace; + + def _getScriptRev(self): + """ + The script (subversion) revision number. + """ + sRev = '@VBOX_SVN_REV@'; + sRev = sRev.strip(); # just in case... + try: + _ = int(sRev); + except: + return __version__[11:-1].strip(); + return sRev; + + def _getPythonHexVersion(self): + """ + The python hex version number. + """ + uHexVersion = getattr(sys, 'hexversion', None); + if uHexVersion is None: + uHexVersion = (sys.version_info[0] << 24) | (sys.version_info[1] << 16) | (sys.version_info[2] << 8); + if sys.version_info[3] == 'final': + uHexVersion |= 0xf0; + return uHexVersion; + + # @} + + def openTestManagerConnection(self): + """ + Opens up a connection to the test manager. + + Raises exception on failure. + """ + return TestBoxConnection(self._oOptions.sTestManagerUrl, self._idTestBox, self._sTestBoxUuid); + + def getSignOnParam(self, sName): + """ + Returns a sign-on parameter value as string. + Raises exception if the name is incorrect. + """ + return str(self._ddSignOnParams[sName][self.VALUE]); + + def getPathState(self): + """ + Get the path to the state dir in the scratch area. + """ + return self._sScratchState; + + def getPathScripts(self): + """ + Get the path to the scripts dir (TESTBOX_PATH_SCRIPTS) in the scratch area. + """ + return self._sScratchScripts; + + def getPathSpill(self): + """ + Get the path to the spill dir (TESTBOX_PATH_SCRATCH) in the scratch area. + """ + return self._sScratchSpill; + + def getPathBuilds(self): + """ + Get the path to the builds. + """ + return self._oOptions.sBuildsPath; + + def getTestBoxId(self): + """ + Get the TestBox ID for state saving purposes. + """ + return self._idTestBox; + + def getTestBoxName(self): + """ + Get the TestBox name for state saving purposes. + """ + return self._sTestBoxName; + + def _reinitScratch(self, fnLog, fUseTheForce): + """ + Wipes the scratch directories and re-initializes them. + + No exceptions raise, returns success indicator instead. + """ + if fUseTheForce is None: + fUseTheForce = self._fFirstSignOn; + + class ErrorCallback(object): # pylint: disable=too-few-public-methods + """ + Callbacks + state for the cleanup. + """ + def __init__(self): + self.fRc = True; + def onErrorCallback(self, sFnName, sPath, aXcptInfo): + """ Logs error during shutil.rmtree operation. """ + fnLog('Error removing "%s": fn=%s %s' % (sPath, sFnName, aXcptInfo[1])); + self.fRc = False; + oRc = ErrorCallback(); + + # + # Cleanup. + # + for sName in os.listdir(self._oOptions.sScratchRoot): + sFullName = os.path.join(self._oOptions.sScratchRoot, sName); + try: + if os.path.isdir(sFullName): + shutil.rmtree(sFullName, False, oRc.onErrorCallback); + else: + os.remove(sFullName); + if os.path.exists(sFullName): + raise Exception('Still exists after deletion, weird.'); + except Exception as oXcpt: + if fUseTheForce is True \ + and utils.getHostOs() not in ['win', 'os2'] \ + and len(sFullName) >= 8 \ + and sFullName[0] == '/' \ + and sFullName[1] != '/' \ + and sFullName.find('/../') < 0: + fnLog('Problems deleting "%s" (%s) using the force...' % (sFullName, oXcpt)); + try: + if os.path.isdir(sFullName): + iRc = utils.sudoProcessCall(['/bin/rm', '-Rf', sFullName]) + else: + iRc = utils.sudoProcessCall(['/bin/rm', '-f', sFullName]) + if iRc != 0: + raise Exception('exit code %s' % iRc); + if os.path.exists(sFullName): + raise Exception('Still exists after forced deletion, weird^2.'); + except: + fnLog('Error sudo deleting "%s": %s' % (sFullName, oXcpt)); + oRc.fRc = False; + else: + fnLog('Error deleting "%s": %s' % (sFullName, oXcpt)); + oRc.fRc = False; + + # Display files left behind. + def dirEnumCallback(sName, oStat): + """ callback for dirEnumerateTree """ + fnLog(u'%s %s' % (utils.formatFileStat(oStat) if oStat is not None else '????????????', sName)); + utils.dirEnumerateTree(self._oOptions.sScratchRoot, dirEnumCallback); + + # + # Re-create the directories. + # + for sDir in [self._oOptions.sScratchRoot, self._sScratchSpill, self._sScratchScripts, self._sScratchState]: + if not os.path.isdir(sDir): + try: + os.makedirs(sDir, 0o700); + except Exception as oXcpt: + fnLog('Error creating "%s": %s' % (sDir, oXcpt)); + oRc.fRc = False; + + if oRc.fRc is True: + self._cReinitScratchErrors = 0; + else: + self._cReinitScratchErrors += 1; + return oRc.fRc; + + def reinitScratch(self, fnLog = testboxcommons.log, fUseTheForce = None, cRetries = 0, cMsDelay = 5000): + """ + Wipes the scratch directories and re-initializes them. + + Will retry according to the cRetries and cMsDelay parameters. Windows + forces us to apply this hack as it ships with services asynchronously + scanning files after they execute, thus racing us cleaning up after a + test. On testboxwin3 we had frequent trouble with aelupsvc.dll keeping + vts_rm.exe kind of open, somehow preventing us from removing the + directory containing it, despite not issuing any errors deleting the + file itself. The service is called "Application Experience", which + feels like a weird joke here. + + No exceptions raise, returns success indicator instead. + """ + fRc = self._reinitScratch(fnLog, fUseTheForce) + while fRc is False and cRetries > 0: + time.sleep(cMsDelay / 1000.0); + fnLog('reinitScratch: Retrying...'); + fRc = self._reinitScratch(fnLog, fUseTheForce) + cRetries -= 1; + return fRc; + + + def _doSignOn(self): + """ + Worker for _maybeSignOn that does the actual signing on. + """ + assert not self._oCommand.isRunning(); + + # Reset the siged-on state. + testboxcommons.log('Signing-on...') + self._fSignedOn = False + self._idTestBox = None + self._cSignOnAttempts += 1; + + # Assemble SIGN-ON request parameters and send the request. + dParams = {}; + for sParam in self._ddSignOnParams: # pylint: disable=consider-using-dict-items + dParams[sParam] = self._ddSignOnParams[sParam][self.VALUE]; + oResponse = TestBoxConnection.sendSignOn(self._oOptions.sTestManagerUrl, dParams); + + # Check response. + try: + sResult = oResponse.getStringChecked(constants.tbresp.ALL_PARAM_RESULT); + if sResult != constants.tbresp.STATUS_ACK: + raise TestBoxException('Result is %s' % (sResult,)); + oResponse.checkParameterCount(3); + idTestBox = oResponse.getIntChecked(constants.tbresp.SIGNON_PARAM_ID, 1, 0x7ffffffe); + sTestBoxName = oResponse.getStringChecked(constants.tbresp.SIGNON_PARAM_NAME); + except TestBoxException as err: + testboxcommons.log('Failed to sign-on: %s' % (str(err),)) + testboxcommons.log('Server response: %s' % (oResponse.toString(),)); + return False; + + # Successfully signed on, update the state. + self._fSignedOn = True; + self._fNeedReSignOn = False; + self._cSignOnAttempts = 0; + self._idTestBox = idTestBox; + self._sTestBoxName = sTestBoxName; + + # Update the environment. + os.environ['TESTBOX_ID'] = str(self._idTestBox); + os.environ['TESTBOX_NAME'] = sTestBoxName; + os.environ['TESTBOX_CPU_COUNT'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_CPU_COUNT); + os.environ['TESTBOX_MEM_SIZE'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_MEM_SIZE); + os.environ['TESTBOX_SCRATCH_SIZE'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_SCRATCH_SIZE); + + testboxcommons.log('Successfully signed-on with Test Box ID #%s and given the name "%s"' \ + % (self._idTestBox, self._sTestBoxName)); + + # Set up the scratch area. + self.reinitScratch(fUseTheForce = self._fFirstSignOn, cRetries = 2); + + self._fFirstSignOn = False; + return True; + + def _maybeSignOn(self): + """ + Check if Test Box parameters were changed + and do sign-in in case of positive result + """ + + # Skip sign-on check if background command is currently in + # running state (avoid infinite signing on). + if self._oCommand.isRunning(): + return None; + + # Refresh sign-on parameters, changes triggers sign-on. + fNeedSignOn = not self._fSignedOn or self._fNeedReSignOn; + for sItem in self._ddSignOnParams: # pylint: disable=consider-using-dict-items + if self._ddSignOnParams[sItem][self.FN] is None: + continue + + sOldValue = self._ddSignOnParams[sItem][self.VALUE] + self._ddSignOnParams[sItem][self.VALUE] = self._ddSignOnParams[sItem][self.FN]() + if sOldValue != self._ddSignOnParams[sItem][self.VALUE]: + fNeedSignOn = True + testboxcommons.log('Detected %s parameter change: %s -> %s' + % (sItem, sOldValue, self._ddSignOnParams[sItem][self.VALUE],)) + + if fNeedSignOn: + self._doSignOn(); + return None; + + def dispatch(self): + """ + Receive orders from Test Manager and execute them + """ + + (self._idTestBox, self._sTestBoxName, self._fSignedOn) = self._oCommand.resumeIncompleteCommand(); + self._fNeedReSignOn = self._fSignedOn; + if self._fSignedOn: + os.environ['TESTBOX_ID'] = str(self._idTestBox); + os.environ['TESTBOX_NAME'] = self._sTestBoxName; + + while True: + # Make sure we're signed on before trying to do anything. + self._maybeSignOn(); + while not self._fSignedOn: + iFactor = 1 if self._cSignOnAttempts < 100 else 4; + time.sleep(random.randint(self.kcSecMinSignOnDelay * iFactor, self.kcSecMaxSignOnDelay * iFactor)); + self._maybeSignOn(); + + # Retrieve and handle command from the TM. + (oResponse, oConnection) = TestBoxConnection.requestCommandWithConnection(self._oOptions.sTestManagerUrl, + self._idTestBox, + self._sTestBoxUuid, + self._oCommand.isRunning()); + if oResponse is not None: + self._oCommand.handleCommand(oResponse, oConnection); + if oConnection is not None: + if oConnection.isConnected(): + self._oCommand.flushLogOnConnection(oConnection); + oConnection.close(); + + # Automatically reboot if scratch init fails. + #if self._cReinitScratchErrors > 8 and self.reinitScratch(cRetries = 3) is False: + # testboxcommons.log('Scratch does not initialize cleanly after %d attempts, rebooting...' + # % ( self._cReinitScratchErrors, )); + # self._oCommand.doReboot(); + + # delay a wee bit before looping. + ## @todo We shouldn't bother the server too frequently. We should try combine the test reporting done elsewhere + # with the command retrieval done here. I believe tinderclient.pl is capable of doing that. + iFactor = 1; + if self._cReinitScratchErrors > 0: + iFactor = 4; + time.sleep(random.randint(self.kcSecMinDelay * iFactor, self.kcSecMaxDelay * iFactor)); + + # Not reached. + + + @staticmethod + def main(): + """ + Main function a la C/C++. Returns exit code. + """ + + # + # Parse arguments. + # + sDefShareType = 'nfs' if utils.getHostOs() == 'solaris' else 'cifs'; + if utils.getHostOs() in ('win', 'os2'): + sDefTestRsrc = 'T:'; + sDefBuilds = 'U:'; + elif utils.getHostOs() == 'darwin': + sDefTestRsrc = '/Volumes/testrsrc'; + sDefBuilds = '/Volumes/builds'; + else: + sDefTestRsrc = '/mnt/testrsrc'; + sDefBuilds = '/mnt/builds'; + + class MyOptionParser(OptionParser): + """ We need to override the exit code on --help, error and so on. """ + def __init__(self, *args, **kwargs): + OptionParser.__init__(self, *args, **kwargs); + def exit(self, status = 0, msg = None): + OptionParser.exit(self, TBS_EXITCODE_SYNTAX, msg); + + parser = MyOptionParser(version=__version__[11:-1].strip()); + for sMixed, sDefault, sDesc in [('Builds', sDefBuilds, 'builds'), ('TestRsrc', sDefTestRsrc, 'test resources') ]: + sLower = sMixed.lower(); + sPrefix = 's' + sMixed; + parser.add_option('--' + sLower + '-path', + dest=sPrefix + 'Path', metavar='<abs-path>', default=sDefault, + help='Where ' + sDesc + ' can be found'); + parser.add_option('--' + sLower + '-server-type', + dest=sPrefix + 'ServerType', metavar='<nfs|cifs>', default=sDefShareType, + help='The type of server, cifs (default) or nfs. If empty, we won\'t try mount anything.'); + parser.add_option('--' + sLower + '-server-name', + dest=sPrefix + 'ServerName', metavar='<server>', + default='vboxstor.de.oracle.com' if sLower == 'builds' else 'teststor.de.oracle.com', + help='The name of the server with the builds.'); + parser.add_option('--' + sLower + '-server-share', + dest=sPrefix + 'ServerShare', metavar='<share>', default=sLower, + help='The name of the builds share.'); + parser.add_option('--' + sLower + '-server-user', + dest=sPrefix + 'ServerUser', metavar='<user>', default='guestr', + help='The user name to use when accessing the ' + sDesc + ' share.'); + parser.add_option('--' + sLower + '-server-passwd', '--' + sLower + '-server-password', + dest=sPrefix + 'ServerPasswd', metavar='<password>', default='guestr', + help='The password to use when accessing the ' + sDesc + ' share.'); + parser.add_option('--' + sLower + '-server-mountopt', + dest=sPrefix + 'ServerMountOpt', metavar='<mountopt>', default='', + help='The mount options to use when accessing the ' + sDesc + ' share.'); + + parser.add_option("--test-manager", metavar="<url>", + dest="sTestManagerUrl", + help="Test Manager URL", + default="http://tindertux.de.oracle.com/testmanager") + parser.add_option("--scratch-root", metavar="<abs-path>", + dest="sScratchRoot", + help="Path to the scratch directory", + default=None) + parser.add_option("--system-uuid", metavar="<uuid>", + dest="sSystemUuid", + help="The system UUID of the testbox, used for uniquely identifiying the machine", + default=None) + parser.add_option("--hwvirt", + dest="fHasHwVirt", action="store_true", default=None, + help="Hardware virtualization available in the CPU"); + parser.add_option("--no-hwvirt", + dest="fHasHwVirt", action="store_false", default=None, + help="Hardware virtualization not available in the CPU"); + parser.add_option("--nested-paging", + dest="fHasNestedPaging", action="store_true", default=None, + help="Nested paging is available"); + parser.add_option("--no-nested-paging", + dest="fHasNestedPaging", action="store_false", default=None, + help="Nested paging is not available"); + parser.add_option("--64-bit-guest", + dest="fCan64BitGuest", action="store_true", default=None, + help="Host can execute 64-bit guests"); + parser.add_option("--no-64-bit-guest", + dest="fCan64BitGuest", action="store_false", default=None, + help="Host cannot execute 64-bit guests"); + parser.add_option("--io-mmu", + dest="fHasIoMmu", action="store_true", default=None, + help="I/O MMU available"); + parser.add_option("--no-io-mmu", + dest="fHasIoMmu", action="store_false", default=None, + help="No I/O MMU available"); + parser.add_option("--raw-mode", + dest="fWithRawMode", action="store_true", default=None, + help="Use raw-mode on this host."); + parser.add_option("--no-raw-mode", + dest="fWithRawMode", action="store_false", default=None, + help="Disables raw-mode tests on this host."); + parser.add_option("--pidfile", + dest="sPidFile", default=None, + help="For the parent script, ignored."); + parser.add_option("-E", "--putenv", metavar = "<variable>=<value>", action = "append", + dest = "asEnvVars", default = [], + help = "Sets an environment variable. Can be repeated."); + def sbp_callback(option, opt_str, value, parser): + _, _, _ = opt_str, value, option + parser.values.sTestManagerUrl = 'http://10.162.100.8/testmanager/' + parser.values.sBuildsServerName = 'vbox-st02.ru.oracle.com' + parser.values.sTestRsrcServerName = 'vbox-st02.ru.oracle.com' + parser.values.sTestRsrcServerShare = 'scratch/data/testrsrc' + parser.add_option("--spb", "--load-sbp-defaults", action="callback", callback=sbp_callback, + help="Load defaults for the sbp setup.") + + (oOptions, args) = parser.parse_args() + # Check command line + if args != []: + parser.print_help(); + return TBS_EXITCODE_SYNTAX; + + if oOptions.sSystemUuid is not None: + uuid.UUID(oOptions.sSystemUuid); + if not oOptions.sTestManagerUrl.startswith('http://') \ + and not oOptions.sTestManagerUrl.startswith('https://'): + print('Syntax error: Invalid test manager URL "%s"' % (oOptions.sTestManagerUrl,)); + return TBS_EXITCODE_SYNTAX; + + for sPrefix in ['sBuilds', 'sTestRsrc']: + sType = getattr(oOptions, sPrefix + 'ServerType'); + if sType is None or not sType.strip(): + setattr(oOptions, sPrefix + 'ServerType', None); + elif sType not in ['cifs', 'nfs']: + print('Syntax error: Invalid server type "%s"' % (sType,)); + return TBS_EXITCODE_SYNTAX; + + + # + # Instantiate the testbox script and start dispatching work. + # + try: + oTestBoxScript = TestBoxScript(oOptions); + except TestBoxScriptException as oXcpt: + print('Error: %s' % (oXcpt,)); + return TBS_EXITCODE_SYNTAX; + oTestBoxScript.dispatch(); + + # Not supposed to get here... + return TBS_EXITCODE_FAILURE; + + + +if __name__ == '__main__': + sys.exit(TestBoxScript.main()); + diff --git a/src/VBox/ValidationKit/testboxscript/testboxtasks.py b/src/VBox/ValidationKit/testboxscript/testboxtasks.py new file mode 100755 index 00000000..388940fc --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/testboxtasks.py @@ -0,0 +1,944 @@ +# -*- coding: utf-8 -*- +# $Id: testboxtasks.py $ + +""" +TestBox Script - Async Tasks. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard python imports. +from datetime import datetime +import os +import re +import signal; +import sys +import subprocess +import threading +import time + +# Validation Kit imports. +from common import constants +from common import utils; +from common import webutils; +import testboxcommons + +# Figure where we are. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksTestScriptDir = os.path.dirname(os.path.abspath(__file__)); + + + +class TestBoxBaseTask(object): + """ + Asynchronous task employing a thread to do the actual work. + """ + + ## Time to wait for a task to terminate. + kcSecTerminateTimeout = 60 + + def __init__(self, oTestBoxScript, cSecTimeout, fnThreadProc): + self._oTestBoxScript = oTestBoxScript; + self._cSecTimeout = cSecTimeout; + self._tsSecStarted = utils.timestampSecond(); + self.__oRLock = threading.RLock(); + self._oCv = threading.Condition(self.__oRLock); + self._fRunning = True; # Protected by lock. + self._fShouldTerminate = False; # Protected by lock. + + # Spawn the worker thread. + self._oThread = threading.Thread(target=fnThreadProc); + self._oThread.daemon = True; + self._oThread.start(); + + def _lock(self): + """ Take the CV lock. """ + self._oCv.acquire(); + + def _unlock(self): + """ Release the CV lock. """ + self._oCv.release(); + + def _complete(self): + """ + Indicate that the task is complete, waking up the main thread. + Usually called at the end of the thread procedure. + """ + self._lock(); + self._fRunning = False; + self._oCv.notifyAll(); # pylint: disable=deprecated-method + self._unlock(); + + def isRunning(self): + """ Check if the task is still running. """ + self._lock(); + fRunning = self._fRunning; + self._unlock(); + return fRunning; + + def wait(self, cSecTimeout): + """ Wait for the task to complete. """ + self._lock(); + fRunning = self._fRunning; + if fRunning is True and cSecTimeout > 0: + self._oCv.wait(cSecTimeout) + self._unlock(); + return fRunning; + + def terminate(self, cSecTimeout = kcSecTerminateTimeout): + """ Terminate the task. """ + self._lock(); + self._fShouldTerminate = True; + self._unlock(); + + return self.wait(cSecTimeout); + + def _shouldTerminate(self): + """ + Returns True if we should terminate, False if not. + """ + self._lock(); + fShouldTerminate = self._fShouldTerminate is True; + self._unlock(); + return fShouldTerminate; + + +class TestBoxTestDriverTask(TestBoxBaseTask): + """ + Base class for tasks involving test drivers. + """ + + ## When to flush the backlog of log messages. + kcchMaxBackLog = 32768; + + ## The backlog sync time (seconds). + kcSecBackLogFlush = 30; + + ## The timeout for the cleanup job (5 mins). + kcSecCleanupTimeout = 300; + ## The timeout to wait for the abort command before killing it. + kcSecAbortTimeout = 300; + + ## The timeout to wait for the final output to be processed. + kcSecFinalOutputTimeout = 180; + ## The timeout to wait for the abort command output to be processed. + kcSecAbortCmdOutputTimeout = 30; + ## The timeout to wait for the terminate output to be processed. + kcSecTerminateOutputTimeout = 30; + ## The timeout to wait for the kill output to be processed. + kcSecKillOutputTimeout = 30; + + ## The timeout for talking to the test manager. + ksecTestManagerTimeout = 60; + + + def __init__(self, oTestBoxScript, fnThreadProc, cSecTimeout, idResult, sScriptCmdLine): + """ + Class instance init + """ + # Init our instance data. + self._idResult = idResult; + self._sScriptCmdLine = sScriptCmdLine; + self._oChild = None; + self._oBackLogLock = threading.RLock(); + self._oBackLogFlushLock = threading.RLock(); + self._asBackLog = []; + self._cchBackLog = 0; + self._secTsBackLogFlush = utils.timestampSecond(); + + # Init super. + TestBoxBaseTask.__init__(self, oTestBoxScript, cSecTimeout, fnThreadProc); + + def terminate(self, cSecTimeout = kcSecCleanupTimeout): + """ Reimplement with higher default timeout. """ + return TestBoxBaseTask.terminate(self, cSecTimeout); + + def _logFlush(self, oGivenConnection = None): + """ + Flushes the log to the test manager. + + No exceptions. + """ + fRc = True; + + with self._oBackLogFlushLock: + # Grab the current back log. + with self._oBackLogLock: + asBackLog = self._asBackLog; + self._asBackLog = []; + self._cchBackLog = 0; + self._secTsBackLogFlush = utils.timestampSecond(); + + # If there is anything to flush, flush it. + if asBackLog: + sBody = ''; + for sLine in asBackLog: + sBody += sLine + '\n'; + + oConnection = None; + try: + if oGivenConnection is None: + oConnection = self._oTestBoxScript.openTestManagerConnection(); + oConnection.postRequest(constants.tbreq.LOG_MAIN, {constants.tbreq.LOG_PARAM_BODY: sBody}); + oConnection.close(); + else: + oGivenConnection.postRequest(constants.tbreq.LOG_MAIN, {constants.tbreq.LOG_PARAM_BODY: sBody}); + except Exception as oXcpt: + testboxcommons.log('_logFlush error: %s' % (oXcpt,)); + if len(sBody) < self.kcchMaxBackLog * 4: + with self._oBackLogLock: + asBackLog.extend(self._asBackLog); + self._asBackLog = asBackLog; + # Don't restore _cchBackLog as there is no point in retrying immediately. + if oConnection is not None: # Be kind to apache. + try: oConnection.close(); + except: pass; + fRc = False; + + return fRc; + + def flushLogOnConnection(self, oConnection): + """ + Attempts to flush the logon the given connection. + + No exceptions. + """ + return self._logFlush(oConnection); + + def _logInternal(self, sMessage, fPrefix = True, fFlushCheck = False): + """ + Internal logging. + Won't flush the backlog, returns a flush indicator so the caller can + do it instead. + """ + if fPrefix: + try: + oNow = datetime.utcnow(); + sTs = '%02u:%02u:%02u.%06u ' % (oNow.hour, oNow.minute, oNow.second, oNow.microsecond); + except Exception as oXcpt: + sTs = 'oXcpt=%s ' % (oXcpt); + sFullMsg = sTs + sMessage; + else: + sFullMsg = sMessage; + + with self._oBackLogLock: + self._asBackLog.append(sFullMsg); + cchBackLog = self._cchBackLog + len(sFullMsg) + 1; + self._cchBackLog = cchBackLog; + secTsBackLogFlush = self._secTsBackLogFlush; + + testboxcommons.log(sFullMsg); + return fFlushCheck \ + and ( cchBackLog >= self.kcchMaxBackLog \ + or utils.timestampSecond() - secTsBackLogFlush >= self.kcSecBackLogFlush); + + def _log(self, sMessage): + """ + General logging function, will flush. + """ + if self._logInternal(sMessage, fFlushCheck = True): + self._logFlush(); + return True; + + def _reportDone(self, sResult): + """ + Report EXEC job done to the test manager. + + sResult is a value from constants.result. + """ + ## @todo optimize this to use one server connection. + + # + # Log it. + # + assert sResult in constants.result.g_kasValidResults; + self._log('Done %s' % (sResult,)); + + # + # Report it. + # + fRc = True; + secStart = utils.timestampSecond(); + while True: + self._logFlush(); ## @todo Combine this with EXEC_COMPLETED. + oConnection = None; + try: + oConnection = self._oTestBoxScript.openTestManagerConnection(); + oConnection.postRequest(constants.tbreq.EXEC_COMPLETED, {constants.tbreq.EXEC_COMPLETED_PARAM_RESULT: sResult}); + oConnection.close(); + except Exception as oXcpt: + if utils.timestampSecond() - secStart < self.ksecTestManagerTimeout: + self._log('_reportDone exception (%s) - retrying...' % (oXcpt,)); + time.sleep(2); + continue; + self._log('_reportDone error: %s' % (oXcpt,)); + if oConnection is not None: # Be kind to apache. + try: oConnection.close(); + except: pass; + fRc = False; + break; + + # + # Mark the task as completed. + # + self._complete(); + return fRc; + + def _assembleArguments(self, sAction, fWithInterpreter = True): + """ + Creates an argument array for subprocess.Popen, splitting the + sScriptCmdLine like bourne shell would. + fWithInterpreter is used (False) when checking that the script exists. + + Returns None on bad input. + """ + + # + # This is a good place to export the test set id to the environment. + # + os.environ['TESTBOX_TEST_SET_ID'] = str(self._idResult); + cTimeoutLeft = utils.timestampSecond() - self._tsSecStarted; + cTimeoutLeft = 0 if cTimeoutLeft >= self._cSecTimeout else self._cSecTimeout - cTimeoutLeft; + os.environ['TESTBOX_TIMEOUT'] = str(cTimeoutLeft); + os.environ['TESTBOX_TIMEOUT_ABS'] = str(self._tsSecStarted + self._cSecTimeout); + + # + # Do replacements and split the command line into arguments. + # + if self._sScriptCmdLine.find('@ACTION@') >= 0: + sCmdLine = self._sScriptCmdLine.replace('@ACTION@', sAction); + else: + sCmdLine = self._sScriptCmdLine + ' ' + sAction; + for sVar in [ 'TESTBOX_PATH_BUILDS', 'TESTBOX_PATH_RESOURCES', 'TESTBOX_PATH_SCRATCH', 'TESTBOX_PATH_SCRIPTS', + 'TESTBOX_PATH_UPLOAD', 'TESTBOX_UUID', 'TESTBOX_REPORTER', 'TESTBOX_ID', 'TESTBOX_TEST_SET_ID', + 'TESTBOX_TIMEOUT', 'TESTBOX_TIMEOUT_ABS' ]: + if sCmdLine.find('${' + sVar + '}') >= 0: + sCmdLine = sCmdLine.replace('${' + sVar + '}', os.environ[sVar]); + + asArgs = utils.argsSplit(sCmdLine); + + # + # Massage argv[0]: + # - Convert portable slashes ('/') to the flavor preferred by the + # OS we're currently running on. + # - Run python script thru the current python interpreter (important + # on systems that doesn't sport native hash-bang script execution). + # + asArgs[0] = asArgs[0].replace('/', os.path.sep); + if not os.path.isabs(asArgs[0]): + asArgs[0] = os.path.join(self._oTestBoxScript.getPathScripts(), asArgs[0]); + + if asArgs[0].endswith('.py') and fWithInterpreter: + if sys.executable: + asArgs.insert(0, sys.executable); + else: + asArgs.insert(0, 'python'); + + return asArgs; + + def _outputThreadProc(self, oChild, oStdOut, sAction): + """ + Thread procedure for the thread that reads the output of the child + process. We use a dedicated thread for this purpose since non-blocking + I/O may be hard to keep portable according to hints around the web... + """ + oThread = oChild.oOutputThread; + while not oThread.fPleaseQuit: + # Get a line. + try: + sLine = oStdOut.readline(); + except Exception as oXcpt: + self._log('child (%s) pipe I/O error: %s' % (sAction, oXcpt,)); + break; + + # EOF? + if not sLine: + break; + + # Strip trailing new line (DOS and UNIX). + if sLine.endswith("\r\n"): + sLine = sLine[0:-2]; + elif sLine.endswith("\n"): + sLine = sLine[0:-1]; + + # Log it. + if self._logInternal(sLine, fPrefix = False, fFlushCheck = True): + self._logFlush(); + + # Close the stdout pipe in case we were told to get lost. + try: + oStdOut.close(); + except Exception as oXcpt: + self._log('warning: Exception closing stdout pipe of "%s" child: %s' % (sAction, oXcpt,)); + + # This is a bit hacky, but try reap the child so it won't hang as + # defunkt during abort/timeout. + if oChild.poll() is None: + for _ in range(15): + time.sleep(0.2); + if oChild.poll() is not None: + break; + + oChild = None; + return None; + + def _spawnChild(self, sAction): + """ + Spawns the child process, returning success indicator + child object. + """ + + # Argument list. + asArgs = self._assembleArguments(sAction) + if asArgs is None: + self._log('Malformed command line: "%s"' % (self._sScriptCmdLine,)); + return (False, None); + + # Spawn child. + try: + oChild = utils.processPopenSafe(asArgs, + shell = False, + bufsize = -1, + stdout = subprocess.PIPE, + stderr = subprocess.STDOUT, + cwd = self._oTestBoxScript.getPathSpill(), + universal_newlines = True, + close_fds = utils.getHostOs() != 'win', + preexec_fn = (None if utils.getHostOs() in ['win', 'os2'] + else os.setsid)); # pylint: disable=no-member + except Exception as oXcpt: + self._log('Error creating child process %s: %s' % (asArgs, oXcpt)); + return (False, None); + + oChild.sTestBoxScriptAction = sAction; + + # Start output thread, extending the child object to keep track of it. + oChild.oOutputThread = threading.Thread(target=self._outputThreadProc, args=(oChild, oChild.stdout, sAction)) + oChild.oOutputThread.daemon = True; + oChild.oOutputThread.fPleaseQuit = False; # Our extension. + oChild.oOutputThread.start(); + + return (True, oChild); + + def _monitorChild(self, cSecTimeout, fTryKillCommand = True, oChild = None): + """ + Monitors the child process. If the child executes longer that + cSecTimeout allows, we'll terminate it. + Returns Success indicator and constants.result value. + """ + + if oChild is None: + oChild = self._oChild; + + iProcGroup = oChild.pid; + if utils.getHostOs() in ['win', 'os2'] or iProcGroup <= 0: + iProcGroup = -2; + + # + # Do timeout processing and check the health of the child. + # + sResult = constants.result.PASSED; + seStarted = utils.timestampSecond(); + while True: + # Check status. + iRc = oChild.poll(); + if iRc is not None: + self._log('Child doing "%s" completed with exit code %d' % (oChild.sTestBoxScriptAction, iRc)); + oChild.oOutputThread.join(self.kcSecFinalOutputTimeout); + + if oChild is self._oChild: + self._oChild = None; + + if iRc == constants.rtexitcode.SUCCESS: + return (True, constants.result.PASSED); + if iRc == constants.rtexitcode.SKIPPED: + return (True, constants.result.SKIPPED); + if iRc == constants.rtexitcode.BAD_TESTBOX: + return (False, constants.result.BAD_TESTBOX); + return (False, constants.result.FAILED); + + # Check for abort first, since that has less of a stigma. + if self._shouldTerminate() is True: + sResult = constants.result.ABORTED; + break; + + # Check timeout. + cSecElapsed = utils.timestampSecond() - seStarted; + if cSecElapsed > cSecTimeout: + self._log('Timeout: %u secs (limit %u secs)' % (cSecElapsed, cSecTimeout)); + sResult = constants.result.TIMED_OUT; + break; + + # Wait. + cSecLeft = cSecTimeout - cSecElapsed; + oChild.oOutputThread.join(15 if cSecLeft > 15 else (cSecLeft + 1)); + + # + # If the child is still alive, try use the abort command to stop it + # very gently. This let's the testdriver clean up daemon processes + # and such that our code below won't catch. + # + if fTryKillCommand and oChild.poll() is None: + self._log('Attempting to abort child...'); + (fRc2, oAbortChild) = self._spawnChild('abort'); + if oAbortChild is not None and fRc2 is True: + self._monitorChild(self.kcSecAbortTimeout, False, oAbortChild); + oAbortChild = None; + + # + # If the child is still alive, try the polite way. + # + if oChild.poll() is None: + self._log('Attempting to terminate child doing "%s"...' % (oChild.sTestBoxScriptAction,)); + + if iProcGroup > 0: + try: + os.killpg(iProcGroup, signal.SIGTERM); # pylint: disable=no-member + except Exception as oXcpt: + self._log('killpg() failed: %s' % (oXcpt,)); + + try: + self._oChild.terminate(); + oChild.oOutputThread.join(self.kcSecTerminateOutputTimeout); + except Exception as oXcpt: + self._log('terminate() failed: %s' % (oXcpt,)); + + # + # If the child doesn't respond to polite, kill it. Always do a killpg + # should there be any processes left in the group. + # + if iProcGroup > 0: + try: + os.killpg(iProcGroup, signal.SIGKILL); # pylint: disable=no-member + except Exception as oXcpt: + self._log('killpg() failed: %s' % (oXcpt,)); + + if oChild.poll() is None: + self._log('Attemting to kill child doing "%s"...' % (oChild.sTestBoxScriptAction,)); + try: + self._oChild.kill(); + oChild.oOutputThread.join(self.kcSecKillOutputTimeout); + except Exception as oXcpt: + self._log('kill() failed: %s' % (oXcpt,)); + + # + # Give the whole mess a couple of more seconds to respond in case the + # output thread exitted prematurely for some weird reason. + # + if oChild.poll() is None: + time.sleep(2); + time.sleep(2); + time.sleep(2); + + iRc = oChild.poll(); + if iRc is not None: + self._log('Child doing "%s" aborted with exit code %d' % (oChild.sTestBoxScriptAction, iRc)); + else: + self._log('Child doing "%s" is still running, giving up...' % (oChild.sTestBoxScriptAction,)); + ## @todo in this case we should probably try reboot the testbox... + oChild.oOutputThread.fPleaseQuit = True; + + if oChild is self._oChild: + self._oChild = None; + return (False, sResult); + + def _terminateChild(self): + """ + Terminates the child forcefully. + """ + if self._oChild is not None: + pass; + + def _cleanupAfter(self): + """ + Cleans up after a test failure. (On success, cleanup is implicit.) + """ + assert self._oChild is None; + + # + # Tell the script to clean up. + # + if self._sScriptCmdLine: # can be empty if cleanup crashed. + (fRc, self._oChild) = self._spawnChild('cleanup-after'); + if fRc is True: + (fRc, _) = self._monitorChild(self.kcSecCleanupTimeout, False); + self._terminateChild(); + else: + fRc = False; + + # + # Wipe the stuff clean. + # + fRc2 = self._oTestBoxScript.reinitScratch(fnLog = self._log, cRetries = 6); + + return fRc and fRc2; + + + +class TestBoxCleanupTask(TestBoxTestDriverTask): + """ + Special asynchronous task for cleaning up a stale test when starting the + testbox script. It's assumed that the reason for the stale test lies in + it causing a panic, reboot, or similar, so we'll also try collect some + info about recent system crashes and reboots. + """ + + def __init__(self, oTestBoxScript): + # Read the old state, throwing a fit if it's invalid. + sScriptState = oTestBoxScript.getPathState(); + sScriptCmdLine = self._readStateFile(os.path.join(sScriptState, 'script-cmdline.txt')); + sResultId = self._readStateFile(os.path.join(sScriptState, 'result-id.txt')); + try: + idResult = int(sResultId); + if idResult <= 0 or idResult >= 0x7fffffff: + raise Exception(''); + except: + raise Exception('Invalid id value "%s" found in %s' % (sResultId, os.path.join(sScriptState, 'result-id.txt'))); + + sTestBoxId = self._readStateFile(os.path.join(sScriptState, 'testbox-id.txt')); + try: + self.idTestBox = int(sTestBoxId); + if self.idTestBox <= 0 or self.idTestBox >= 0x7fffffff: + raise Exception(''); + except: + raise Exception('Invalid id value "%s" found in %s' % (sTestBoxId, os.path.join(sScriptState, 'testbox-id.txt'))); + self.sTestBoxName = self._readStateFile(os.path.join(sScriptState, 'testbox-name.txt')); + + # Init super. + TestBoxTestDriverTask.__init__(self, oTestBoxScript, self._threadProc, self.kcSecCleanupTimeout, + idResult, sScriptCmdLine); + + @staticmethod + def _readStateFile(sPath): + """ + Reads a state file, returning a string on success and otherwise raising + an exception. + """ + try: + with open(sPath, "rb") as oFile: + sStr = oFile.read(); + sStr = sStr.decode('utf-8'); + return sStr.strip(); + except Exception as oXcpt: + raise Exception('Failed to read "%s": %s' % (sPath, oXcpt)); + + def _threadProc(self): + """ + Perform the actual clean up on script startup. + """ + + # + # First make sure we won't repeat this exercise should it turn out to + # trigger another reboot/panic/whatever. + # + sScriptCmdLine = os.path.join(self._oTestBoxScript.getPathState(), 'script-cmdline.txt'); + try: + os.remove(sScriptCmdLine); + open(sScriptCmdLine, 'wb').close(); # pylint: disable=consider-using-with + except Exception as oXcpt: + self._log('Error truncating "%s": %s' % (sScriptCmdLine, oXcpt)); + + # + # Report the incident. + # + self._log('Seems we rebooted!'); + self._log('script-cmdline="%s"' % (self._sScriptCmdLine)); + self._log('result-id=%d' % (self._idResult)); + self._log('testbox-id=%d' % (self.idTestBox)); + self._log('testbox-name=%s' % (self.sTestBoxName)); + self._logFlush(); + + # System specific info. + sOs = utils.getHostOs(); + if sOs == 'darwin': + self._log('NVRAM Panic Info:\n%s\n' % (self.darwinGetPanicInfo(),)); + + self._logFlush(); + ## @todo Add some special command for reporting this situation so we get something + # useful in the event log. + + # + # Do the cleaning up. + # + self._cleanupAfter(); + + self._reportDone(constants.result.REBOOTED); + return False; + + def darwinGetPanicInfo(self): + """ + Returns a string with the aapl,panic-info content. + """ + # Retriev the info. + try: + sRawInfo = utils.processOutputChecked(['nvram', 'aapl,panic-info']); + except Exception as oXcpt: + return 'exception running nvram: %s' % (oXcpt,); + + # Decode (%xx) and decompact it (7-bit -> 8-bit). + ahDigits = \ + { + '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, + '8': 8, '9': 9, 'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15, + }; + sInfo = ''; + off = len('aapl,panic-info') + 1; + iBit = 0; + bLow = 0; + + while off < len(sRawInfo): + # isprint is used to determine whether to %xx or %c it, so we have to + # be a little careful before assuming % sequences are hex bytes. + if sRawInfo[off] == '%' \ + and off + 3 <= len(sRawInfo) \ + and sRawInfo[off + 1] in ahDigits \ + and sRawInfo[off + 2] in ahDigits: + bCur = ahDigits[sRawInfo[off + 1]] * 0x10 + ahDigits[sRawInfo[off + 2]]; + off += 3; + else: + bCur = ord(sRawInfo[off]); + off += 1; + + sInfo += chr(((bCur & (0x7f >> iBit)) << iBit) | bLow); + bLow = bCur >> (7 - iBit); + + if iBit < 6: + iBit += 1; + else: + # Final bit in sequence. + sInfo += chr(bLow); + bLow = 0; + iBit = 0; + + # Expand shorthand. + sInfo = sInfo.replace('@', 'com.apple.'); + sInfo = sInfo.replace('>', 'com.apple.driver.'); + sInfo = sInfo.replace('|', 'com.apple.iokit.'); + sInfo = sInfo.replace('$', 'com.apple.security.'); + sInfo = sInfo.replace('!A', 'Apple'); + sInfo = sInfo.replace('!a', 'Action'); + sInfo = sInfo.replace('!B', 'Bluetooth'); + sInfo = sInfo.replace('!C', 'Controller'); + sInfo = sInfo.replace('!F', 'Family'); + sInfo = sInfo.replace('!I', 'Intel'); + sInfo = sInfo.replace('!U', 'AppleUSB'); + sInfo = sInfo.replace('!P', 'Profile'); + + # Done. + return sInfo + + +class TestBoxExecTask(TestBoxTestDriverTask): + """ + Implementation of a asynchronous EXEC task. + + This uses a thread for doing the actual work, i.e. starting and monitoring + the child process, processing its output, and more. + """ + + def __init__(self, oTestBoxScript, idResult, sScriptZips, sScriptCmdLine, cSecTimeout): + """ + Class instance init + """ + # Init our instance data. + self._sScriptZips = sScriptZips; + + # Init super. + TestBoxTestDriverTask.__init__(self, oTestBoxScript, self._threadProc, cSecTimeout, idResult, sScriptCmdLine); + + @staticmethod + def _writeStateFile(sPath, sContent): + """ + Writes a state file, raising an exception on failure. + """ + try: + with open(sPath, "wb") as oFile: + oFile.write(sContent.encode('utf-8')); + oFile.flush(); + try: os.fsync(oFile.fileno()); + except: pass; + except Exception as oXcpt: + raise Exception('Failed to write "%s": %s' % (sPath, oXcpt)); + return True; + + @staticmethod + def _environTxtContent(): + """ + Collects environment variables and values for the environ.txt stat file + (for external monitoring tool). + """ + sText = ''; + for sVar in [ 'TESTBOX_PATH_BUILDS', 'TESTBOX_PATH_RESOURCES', 'TESTBOX_PATH_SCRATCH', 'TESTBOX_PATH_SCRIPTS', + 'TESTBOX_PATH_UPLOAD', 'TESTBOX_HAS_HW_VIRT', 'TESTBOX_HAS_NESTED_PAGING', 'TESTBOX_HAS_IOMMU', + 'TESTBOX_SCRIPT_REV', 'TESTBOX_CPU_COUNT', 'TESTBOX_MEM_SIZE', 'TESTBOX_SCRATCH_SIZE', + 'TESTBOX_WITH_RAW_MODE', 'TESTBOX_WITH_RAW_MODE', 'TESTBOX_MANAGER_URL', 'TESTBOX_UUID', + 'TESTBOX_REPORTER', 'TESTBOX_NAME', 'TESTBOX_ID', 'TESTBOX_TEST_SET_ID', + 'TESTBOX_TIMEOUT', 'TESTBOX_TIMEOUT_ABS', ]: + sValue = os.environ.get(sVar); + if sValue: + sText += sVar + '=' + sValue + '\n'; + return sText; + + def _saveState(self): + """ + Saves the task state on disk so we can launch a TestBoxCleanupTask job + if the test should cause system panic or similar. + + Note! May later be extended to support tests that reboots the host. + """ + sScriptState = self._oTestBoxScript.getPathState(); + try: + self._writeStateFile(os.path.join(sScriptState, 'script-cmdline.txt'), self._sScriptCmdLine); + self._writeStateFile(os.path.join(sScriptState, 'result-id.txt'), str(self._idResult)); + self._writeStateFile(os.path.join(sScriptState, 'testbox-id.txt'), str(self._oTestBoxScript.getTestBoxId())); + self._writeStateFile(os.path.join(sScriptState, 'testbox-name.txt'), self._oTestBoxScript.getTestBoxName()); + self._writeStateFile(os.path.join(sScriptState, 'environ.txt'), self._environTxtContent()); + except Exception as oXcpt: + self._log('Failed to write state: %s' % (oXcpt,)); + return False; + return True; + + def _downloadAndUnpackScriptZips(self): + """ + Downloads/copies the script ZIPs into TESTBOX_SCRIPT and unzips them to + the same directory. + + Raises no exceptions, returns log + success indicator instead. + """ + sPathScript = self._oTestBoxScript.getPathScripts(); + asArchives = self._sScriptZips.split(','); + for sArchive in asArchives: + sArchive = sArchive.strip(); + if not sArchive: + continue; + + # Figure the destination name (in scripts). + sDstFile = webutils.getFilename(sArchive); + if not sDstFile \ + or re.search('[^a-zA-Z0-9 !#$%&\'()@^_`{}~.-]', sDstFile) is not None: # FAT charset sans 128-255 + '.'. + self._log('Malformed script zip filename: %s' % (sArchive,)); + return False; + sDstFile = os.path.join(sPathScript, sDstFile); + + # Do the work. + if webutils.downloadFile(sArchive, sDstFile, self._oTestBoxScript.getPathBuilds(), self._log, self._log) is not True: + return False; + asFiles = utils.unpackFile(sDstFile, sPathScript, self._log, self._log); + if asFiles is None: + return False; + + # Since zip files doesn't always include mode masks, set the X bit + # of all of them so we can execute binaries and hash-bang scripts. + for sFile in asFiles: + utils.chmodPlusX(sFile); + + return True; + + def _threadProc(self): + """ + Do the work of an EXEC command. + """ + + sResult = constants.result.PASSED; + + # + # Start by preparing the scratch directories. + # + # Note! Failures at this stage are not treated as real errors since + # they may be caused by the previous test and other circumstances + # so we don't want to go fail a build because of this. + # + fRc = self._oTestBoxScript.reinitScratch(self._logInternal); + fNeedCleanUp = fRc; + if fRc is True: + fRc = self._downloadAndUnpackScriptZips(); + testboxcommons.log2('_threadProc: _downloadAndUnpackScriptZips -> %s' % (fRc,)); + if fRc is not True: + sResult = constants.result.BAD_TESTBOX; + + # + # Make sure the script exists. + # + if fRc is True: + sScript = self._assembleArguments('none', fWithInterpreter = False)[0]; + if not os.path.exists(sScript): + self._log('The test driver script "%s" cannot be found.' % (sScript,)); + sDir = sScript; + while len(sDir) > 3: + sDir = os.path.dirname(sDir); + if os.path.exists(sDir): + self._log('First existing parent directory is "%s".' % (sDir,)); + break; + fRc = False; + + if fRc is True: + # + # Start testdriver script. + # + fRc = self._saveState(); + if fRc: + (fRc, self._oChild) = self._spawnChild('all'); + testboxcommons.log2('_threadProc: _spawnChild -> %s, %s' % (fRc, self._oChild)); + if fRc: + (fRc, sResult) = self._monitorChild(self._cSecTimeout); + testboxcommons.log2('_threadProc: _monitorChild -> %s' % (fRc,)); + + # If the run failed, do explicit cleanup unless its a BAD_TESTBOX, since BAD_TESTBOX is + # intended for pre-cleanup problems caused by previous test failures. Do a cleanup on + # a BAD_TESTBOX could easily trigger an uninstallation error and change status to FAILED. + if fRc is not True: + if sResult != constants.result.BAD_TESTBOX: + testboxcommons.log2('_threadProc: explicit cleanups...'); + self._terminateChild(); + self._cleanupAfter(); + fNeedCleanUp = False; + assert self._oChild is None; + + # + # Clean up scratch. + # + if fNeedCleanUp: + if self._oTestBoxScript.reinitScratch(self._logInternal, cRetries = 6) is not True: + self._log('post run reinitScratch failed.'); + fRc = False; + + # + # Report status and everything back to the test manager. + # + if fRc is False and sResult == constants.result.PASSED: + sResult = constants.result.FAILED; + self._reportDone(sResult); + return fRc; + diff --git a/src/VBox/ValidationKit/testboxscript/testboxupgrade.py b/src/VBox/ValidationKit/testboxscript/testboxupgrade.py new file mode 100755 index 00000000..2e44a7a6 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/testboxupgrade.py @@ -0,0 +1,339 @@ +# -*- coding: utf-8 -*- +# $Id: testboxupgrade.py $ + +""" +TestBox Script - Upgrade from local file ZIP. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Standard python imports. +import os +import shutil +import sys +import subprocess +import threading +import time +import uuid; +import zipfile + +# Validation Kit imports. +from common import utils; +import testboxcommons +from testboxscript import TBS_EXITCODE_SYNTAX; + +# Figure where we are. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksTestScriptDir = os.path.dirname(os.path.abspath(__file__)); +g_ksValidationKitDir = os.path.dirname(g_ksTestScriptDir); + + +def _doUpgradeThreadProc(oStdOut, asBuf): + """Thread procedure for the upgrade test drive.""" + asBuf.append(oStdOut.read()); + return True; + + +def _doUpgradeCheckZip(oZip): + """ + Check that the essential files are there. + Returns list of members on success, None on failure. + """ + asMembers = oZip.namelist(); + if ('testboxscript/testboxscript/testboxscript.py' not in asMembers) \ + or ('testboxscript/testboxscript/testboxscript_real.py' not in asMembers): + testboxcommons.log('Missing one or both testboxscripts (members: %s)' % (asMembers,)); + return None; + + for sMember in asMembers: + if not sMember.startswith('testboxscript/'): + testboxcommons.log('zip file contains member outside testboxscript/: "%s"' % (sMember,)); + return None; + if sMember.find('/../') > 0 or sMember.endswith('/..'): + testboxcommons.log('zip file contains member with escape sequence: "%s"' % (sMember,)); + return None; + + return asMembers; + +def _doUpgradeUnzipAndCheck(oZip, sUpgradeDir, asMembers): + """ + Unzips the files into sUpdateDir, does chmod(755) on all files and + checks that there are no symlinks or special files. + Returns True/False. + """ + # + # Extract the files. + # + if os.path.exists(sUpgradeDir): + shutil.rmtree(sUpgradeDir); + for sMember in asMembers: + if sMember.endswith('/'): + os.makedirs(os.path.join(sUpgradeDir, sMember.replace('/', os.path.sep)), 0o775); + else: + oZip.extract(sMember, sUpgradeDir); + + # + # Make all files executable and make sure only owner can write to them. + # While at it, also check that there are only files and directory, no + # symbolic links or special stuff. + # + for sMember in asMembers: + sFull = os.path.join(sUpgradeDir, sMember); + if sMember.endswith('/'): + if not os.path.isdir(sFull): + testboxcommons.log('Not directory: "%s"' % sFull); + return False; + else: + if not os.path.isfile(sFull): + testboxcommons.log('Not regular file: "%s"' % sFull); + return False; + try: + os.chmod(sFull, 0o755); + except Exception as oXcpt: + testboxcommons.log('warning chmod error on %s: %s' % (sFull, oXcpt)); + return True; + +def _doUpgradeTestRun(sUpgradeDir): + """ + Do a testrun of the new script, to make sure it doesn't fail with + to run in any way because of old python, missing import or generally + busted upgrade. + Returns True/False. + """ + asArgs = [os.path.join(sUpgradeDir, 'testboxscript', 'testboxscript', 'testboxscript.py'), '--version' ]; + testboxcommons.log('Testing the new testbox script (%s)...' % (asArgs[0],)); + if sys.executable: + asArgs.insert(0, sys.executable); + oChild = subprocess.Popen(asArgs, shell = False, # pylint: disable=consider-using-with + stdout=subprocess.PIPE, stderr=subprocess.STDOUT); + + asBuf = [] + oThread = threading.Thread(target=_doUpgradeThreadProc, args=(oChild.stdout, asBuf)); + oThread.daemon = True; + oThread.start(); + oThread.join(30); + + # Give child up to 5 seconds to terminate after producing output. + if sys.version_info[0] >= 3 and sys.version_info[1] >= 3: + oChild.wait(5); # pylint: disable=too-many-function-args + else: + for _ in range(50): + iStatus = oChild.poll(); + if iStatus is None: + break; + time.sleep(0.1); + iStatus = oChild.poll(); + if iStatus is None: + testboxcommons.log('Checking the new testboxscript timed out.'); + oChild.terminate(); + oThread.join(5); + return False; + if iStatus is not TBS_EXITCODE_SYNTAX: + testboxcommons.log('The new testboxscript returned %d instead of %d during check.' \ + % (iStatus, TBS_EXITCODE_SYNTAX)); + return False; + + sOutput = b''.join(asBuf).decode('utf-8'); + sOutput = sOutput.strip(); + try: + iNewVersion = int(sOutput); + except: + testboxcommons.log('The new testboxscript returned an unparseable version string: "%s"!' % (sOutput,)); + return False; + testboxcommons.log('New script version: %s' % (iNewVersion,)); + return True; + +def _doUpgradeApply(sUpgradeDir, asMembers): + """ + # Apply the directories and files from the upgrade. + returns True/False/Exception. + """ + + # + # Create directories first since that's least intrusive. + # + for sMember in asMembers: + if sMember[-1] == '/': + sMember = sMember[len('testboxscript/'):]; + if sMember != '': + sFull = os.path.join(g_ksValidationKitDir, sMember); + if not os.path.isdir(sFull): + os.makedirs(sFull, 0o755); + + # + # Move the files into place. + # + fRc = True; + asOldFiles = []; + for sMember in asMembers: + if sMember[-1] != '/': + sSrc = os.path.join(sUpgradeDir, sMember); + sDst = os.path.join(g_ksValidationKitDir, sMember[len('testboxscript/'):]); + + # Move the old file out of the way first. + sDstRm = None; + if os.path.exists(sDst): + testboxcommons.log2('Info: Installing "%s"' % (sDst,)); + sDstRm = '%s-delete-me-%s' % (sDst, uuid.uuid4(),); + try: + os.rename(sDst, sDstRm); + except Exception as oXcpt: + testboxcommons.log('Error: failed to rename (old) "%s" to "%s": %s' % (sDst, sDstRm, oXcpt)); + try: + shutil.copy(sDst, sDstRm); + except Exception as oXcpt: + testboxcommons.log('Error: failed to copy (old) "%s" to "%s": %s' % (sDst, sDstRm, oXcpt)); + break; + try: + os.unlink(sDst); + except Exception as oXcpt: + testboxcommons.log('Error: failed to unlink (old) "%s": %s' % (sDst, oXcpt)); + break; + + # Move/copy the new one into place. + testboxcommons.log2('Info: Installing "%s"' % (sDst,)); + try: + os.rename(sSrc, sDst); + except Exception as oXcpt: + testboxcommons.log('Warning: failed to rename (new) "%s" to "%s": %s' % (sSrc, sDst, oXcpt)); + try: + shutil.copy(sSrc, sDst); + except: + testboxcommons.log('Error: failed to copy (new) "%s" to "%s": %s' % (sSrc, sDst, oXcpt)); + fRc = False; + break; + + # + # Roll back on failure. + # + if fRc is not True: + testboxcommons.log('Attempting to roll back old files...'); + for sDstRm in asOldFiles: + sDst = sDstRm[:sDstRm.rfind('-delete-me')]; + testboxcommons.log2('Info: Rolling back "%s" (%s)' % (sDst, os.path.basename(sDstRm))); + try: + shutil.move(sDstRm, sDst); + except: + testboxcommons.log('Error: failed to rollback "%s" onto "%s": %s' % (sDstRm, sDst, oXcpt)); + return False; + return True; + +def _doUpgradeRemoveOldStuff(sUpgradeDir, asMembers): + """ + Clean up all obsolete files and directories. + Returns True (shouldn't fail or raise any exceptions). + """ + + try: + shutil.rmtree(sUpgradeDir, ignore_errors = True); + except: + pass; + + asKnownFiles = []; + asKnownDirs = []; + for sMember in asMembers: + sMember = sMember[len('testboxscript/'):]; + if sMember == '': + continue; + if sMember[-1] == '/': + asKnownDirs.append(os.path.normpath(os.path.join(g_ksValidationKitDir, sMember[:-1]))); + else: + asKnownFiles.append(os.path.normpath(os.path.join(g_ksValidationKitDir, sMember))); + + for sDirPath, asDirs, asFiles in os.walk(g_ksValidationKitDir, topdown=False): + for sDir in asDirs: + sFull = os.path.normpath(os.path.join(sDirPath, sDir)); + if sFull not in asKnownDirs: + testboxcommons.log2('Info: Removing obsolete directory "%s"' % (sFull,)); + try: + os.rmdir(sFull); + except Exception as oXcpt: + testboxcommons.log('Warning: failed to rmdir obsolete dir "%s": %s' % (sFull, oXcpt)); + + for sFile in asFiles: + sFull = os.path.normpath(os.path.join(sDirPath, sFile)); + if sFull not in asKnownFiles: + testboxcommons.log2('Info: Removing obsolete file "%s"' % (sFull,)); + try: + os.unlink(sFull); + except Exception as oXcpt: + testboxcommons.log('Warning: failed to unlink obsolete file "%s": %s' % (sFull, oXcpt)); + return True; + +def upgradeFromZip(sZipFile): + """ + Upgrade the testboxscript install using the specified zip file. + Returns True/False. + """ + + # A little precaution. + if utils.isRunningFromCheckout(): + testboxcommons.log('Use "svn up" to "upgrade" your source tree!'); + return False; + + # + # Prepare. + # + # Note! Don't bother cleaning up files and dirs in the error paths, + # they'll be restricted to the one zip and the one upgrade dir. + # We'll remove them next time we upgrade. + # + oZip = zipfile.ZipFile(sZipFile, 'r'); # No 'with' support in 2.6 class: pylint: disable=consider-using-with + asMembers = _doUpgradeCheckZip(oZip); + if asMembers is None: + return False; + + sUpgradeDir = os.path.join(g_ksTestScriptDir, 'upgrade'); + testboxcommons.log('Unzipping "%s" to "%s"...' % (sZipFile, sUpgradeDir)); + if _doUpgradeUnzipAndCheck(oZip, sUpgradeDir, asMembers) is not True: + return False; + oZip.close(); + + if _doUpgradeTestRun(sUpgradeDir) is not True: + return False; + + # + # Execute. + # + if _doUpgradeApply(sUpgradeDir, asMembers) is not True: + return False; + _doUpgradeRemoveOldStuff(sUpgradeDir, asMembers); + return True; + + +# For testing purposes. +if __name__ == '__main__': + sys.exit(upgradeFromZip(sys.argv[1])); + diff --git a/src/VBox/ValidationKit/testboxscript/win/autoexec-testbox.cmd b/src/VBox/ValidationKit/testboxscript/win/autoexec-testbox.cmd new file mode 100644 index 00000000..60700584 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/win/autoexec-testbox.cmd @@ -0,0 +1,72 @@ +@echo off
+REM $Id: autoexec-testbox.cmd $
+REM REM @file
+REM VirtualBox Validation Kit - testbox script, automatic execution wrapper.
+REM
+
+REM
+REM Copyright (C) 2006-2022 Oracle and/or its affiliates.
+REM
+REM This file is part of VirtualBox base platform packages, as
+REM available from https://www.virtualbox.org.
+REM
+REM This program is free software; you can redistribute it and/or
+REM modify it under the terms of the GNU General Public License
+REM as published by the Free Software Foundation, in version 3 of the
+REM License.
+REM
+REM This program is distributed in the hope that it will be useful, but
+REM WITHOUT ANY WARRANTY; without even the implied warranty of
+REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+REM General Public License for more details.
+REM
+REM You should have received a copy of the GNU General Public License
+REM along with this program; if not, see <https://www.gnu.org/licenses>.
+REM
+REM The contents of this file may alternatively be used under the terms
+REM of the Common Development and Distribution License Version 1.0
+REM (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+REM in the VirtualBox distribution, in which case the provisions of the
+REM CDDL are applicable instead of those of the GPL.
+REM
+REM You may elect to license modified versions of this file under the
+REM terms and conditions of either the GPL or the CDDL or both.
+REM
+REM SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+REM
+
+@echo "$Id: autoexec-testbox.cmd $"
+@echo on
+setlocal EnableExtensions
+set exe=python.exe
+for /f %%x in ('tasklist /NH /FI "IMAGENAME eq %exe%"') do if %%x == %exe% goto end
+
+if exist %SystemRoot%\System32\aim_ll.exe (
+ set RAMEXE=aim
+) else if exist %SystemRoot%\System32\imdisk.exe (
+ set RAMEXE=imdisk
+) else goto defaulttest
+
+REM Take presence of imdisk.exe or aim_ll.exe as order to test in ramdisk.
+set RAMDRIVE=D:
+if exist %RAMDRIVE%\TEMP goto skip
+if %RAMEXE% == aim (
+ aim_ll -a -t vm -s 16G -m %RAMDRIVE% -p "/fs:ntfs /q /y"
+) else if %RAMEXE% == imdisk (
+ imdisk -a -s 16GB -m %RAMDRIVE% -p "/fs:ntfs /q /y" -o "awe"
+) else goto defaulttest
+:skip
+
+set VBOX_INSTALL_PATH=%RAMDRIVE%\VBoxInstall
+set TMP=%RAMDRIVE%\TEMP
+set TEMP=%TMP%
+
+mkdir %VBOX_INSTALL_PATH%
+mkdir %TMP%
+
+set TESTBOXSCRIPT_OPTS=--scratch-root=%RAMDRIVE%\testbox
+
+:defaulttest
+%SystemDrive%\Python27\python.exe %SystemDrive%\testboxscript\testboxscript\testboxscript.py --testrsrc-server-type=cifs --builds-server-type=cifs %TESTBOXSCRIPT_OPTS%
+pause
+:end
diff --git a/src/VBox/ValidationKit/testboxscript/win/fix_stale_refs.py b/src/VBox/ValidationKit/testboxscript/win/fix_stale_refs.py new file mode 100755 index 00000000..b8330866 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/win/fix_stale_refs.py @@ -0,0 +1,160 @@ +# -*- coding: utf-8 -*- +# $Id: fix_stale_refs.py $ + +""" +This module must be used interactively! +Use with caution as it will delete some values from the regisry! + +It tries to locate client references to products that no longer exist. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +from _winreg import HKEY_LOCAL_MACHINE, KEY_ALL_ACCESS +from _winreg import OpenKey, CloseKey, EnumKey, QueryInfoKey, EnumValue, DeleteValue, QueryValueEx +from distutils.util import strtobool + +def reverse_bytes(hex_string): + """ + This function reverses the order of bytes in the provided string. + Each byte is represented by two characters which are reversed as well. + """ + #print 'reverse_bytes(' + hex_string + ')' + chars = len(hex_string) + if chars > 2: + return reverse_bytes(hex_string[chars/2:]) + reverse_bytes(hex_string[:chars/2]) + else: + return hex_string[1] + hex_string[0] + +def transpose_guid(guid): + """ + Windows Installer uses different way to present GUID string. This function converts GUID + from installer's presentation to more conventional form. + """ + return '{' + reverse_bytes(guid[0:8]) + '-' + reverse_bytes(guid[8:12]) + \ + '-' + reverse_bytes(guid[12:16]) + \ + '-' + reverse_bytes(guid[16:18]) + reverse_bytes(guid[18:20]) + \ + '-' + ''.join([reverse_bytes(guid[i:i+2]) for i in range(20, 32, 2)]) + '}' + +PRODUCTS_KEY = r'SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products' +COMPONENTS_KEY = r'SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components' + +def get_installed_products(): + """ + Enumerate all installed products. + """ + products = {} + hkey_products = OpenKey(HKEY_LOCAL_MACHINE, PRODUCTS_KEY, 0, KEY_ALL_ACCESS) + + try: + product_index = 0 + while True: + product_guid = EnumKey(hkey_products, product_index) + hkey_product_properties = OpenKey(hkey_products, product_guid + r'\InstallProperties', 0, KEY_ALL_ACCESS) + try: + value = QueryValueEx(hkey_product_properties, 'DisplayName')[0] + except WindowsError as oXcpt: + if oXcpt.winerror != 2: + raise + value = '<unknown>' + CloseKey(hkey_product_properties) + products[product_guid] = value + product_index += 1 + except WindowsError as oXcpt: + if oXcpt.winerror != 259: + print(oXcpt.strerror + '.', 'error', oXcpt.winerror) + CloseKey(hkey_products) + + print('Installed products:') + for product_key in sorted(products.keys()): + print(transpose_guid(product_key), '=', products[product_key]) + + print() + return products + +def get_missing_products(hkey_components): + """ + Detect references to missing products. + """ + products = get_installed_products() + + missing_products = {} + + for component_index in xrange(0, QueryInfoKey(hkey_components)[0]): + component_guid = EnumKey(hkey_components, component_index) + hkey_component = OpenKey(hkey_components, component_guid, 0, KEY_ALL_ACCESS) + clients = [] + for value_index in xrange(0, QueryInfoKey(hkey_component)[1]): + client_guid, client_path = EnumValue(hkey_component, value_index)[:2] + clients.append((client_guid, client_path)) + if not client_guid in products: + if client_guid in missing_products: + missing_products[client_guid].append((component_guid, client_path)) + else: + missing_products[client_guid] = [(component_guid, client_path)] + CloseKey(hkey_component) + return missing_products + +def main(): + """ + Enumerate all installed products, go through all components and check if client refences + point to valid products. Remove references to non-existing products if the user allowed it. + """ + hkey_components = OpenKey(HKEY_LOCAL_MACHINE, COMPONENTS_KEY, 0, KEY_ALL_ACCESS) + + missing_products = get_missing_products(hkey_components) + + print('Missing products refer the following components:') + for product_guid in sorted(missing_products.keys()): + if product_guid[1:] == '0'*31: + continue + print('Product', transpose_guid(product_guid) + ':') + for component_guid, component_file in missing_products[product_guid]: + print(' ' + transpose_guid(component_guid), '=', component_file) + + print('Remove all references to product', transpose_guid(product_guid) + '? [y/n]') + if strtobool(raw_input().lower()): + for component_guid, component_file in missing_products[product_guid]: + hkey_component = OpenKey(hkey_components, component_guid, 0, KEY_ALL_ACCESS) + print('Removing reference in ' + transpose_guid(component_guid), '=', component_file) + DeleteValue(hkey_component, product_guid) + CloseKey(hkey_component) + else: + print('Cancelled removal of product', transpose_guid(product_guid)) + + CloseKey(hkey_components) + +if __name__ == "__main__": + main() diff --git a/src/VBox/ValidationKit/testboxscript/win/readme.txt b/src/VBox/ValidationKit/testboxscript/win/readme.txt new file mode 100644 index 00000000..3da82f9d --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/win/readme.txt @@ -0,0 +1,157 @@ +$Id: readme.txt $ + + +Preparations: + +0. Make sure the computer name (what hostname prints) is the same as the DNS + returns (sans domain) for the host IP. + +1. Install Python 2.7.x from python.org to C:\Python27 or Python 3.y.x to + C:\Python3%y%, where y >= 5. Matching bit count as the host windows version. + +2. Install the win32 extension for python. + +3. Append C:\Python27 or C:\Python3%y% to the system PATH (tail). + +4. Disable UAC. + + Windows 8 / 8.1 / Server 2012: Set the following key to zero: + "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\policies\system\EnableLUA" + +5. Disable Automatic updates. (No rebooting during tests, thank you!) + + Ideally we would prevent windows from even checking for updates to avoid + influencing benchmarks and such, however the microsofties aren't keen on it. + So, disable it as much as possible. + + W10: gpedit.msc -> "Administrative Templates" -> "Windows Components" + -> "Windows Update": + - "Configure Automatic Updates": Enable and select "2 - Notify for + download and notiy for install". + - "Allow Automatic Updates immediate installation": Disable. + - "No auto-restart with logged on users for scheduled automatic + updates installations": Enabled. + +6. Go to the group policy editor (gpedit.msc) and change "Computer Configuration" + -> "Windows Settings" -> "Security Settings" -> "Local Policies" + -> "Security Options" -> "Network security: LAN Manager authentication level" + to "Send LM & NTLM- use NTLMv2 session security if negotiated". This fixed + passing the password as an argument to "NET USE" (don't ask why!). + +6b. While in the group policy editor, make sure that "Computer Configuration" + -> "Windows Settings" -> "Security Settings" [ -> "Local Policies" ] + -> "Account Policy" -> "Password must meet complexity requirements" is + disabled so the vbox account can be created later one. + +7. Need to disable the error popups blocking testing. + + Set "HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Windows\ErrorMode" + to 2. This immediately disables hard error popups (missing DLLs and such). + + Then there are the sending info to microsoft, debug, dump, look for solution + questions we don't want. Not entirely sure what's required here yet, but + the following stuff might hopefully help (update after testing): + + On Windows XP: + + Go "Control Panel" -> "System Properties" -> "Advanced" + -> "Error Reporting" and check "Disable error reporting" + and uncheck "But notify me when critical erorr occurs". + + On Windows Vista and later: + + In gpedit change the following settings under "Computer Configuration" + -> "Administrative Templates" -> "Windows Components" + -> "Windows Error Reporting": + 1) Enable "Prevent display of the user interface for critical errors". + ... -> "Advanced Error Reporting Settings": + 1) Enable "Configure Report Archive" and set it to "Store All" for + up to 500 (or less) reports. + 2) Disable "Configure Report Queue". + + Run 'serverWerOptin /disable'. + + Then set "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\DontShowUI" + to 1. (Could do all the above from regedit if we wanted...) + +7b. Configure application crash dumps on Vista SP1 and later: + + Set the following values under the key + HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps: + DumpFolder [string] = C:\CrashDumps + DumpCount [dword] = 10 + DumpType [dword] = 1 (minidump) + CustomDumpFlags [dword] = 0 + + mkdir C:\CrashDumps + + See also http://msdn.microsoft.com/en-us/library/windows/desktop/bb787181%28v=vs.85%29.aspx + +7c. Enable verbose driver installation logging (C:\Windows\setupapi.dev.log): + + Create the following value under the key + HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Setup\ + LogLevel [dword] = 0xFF (255) + + If it already exists (typical on W10), just OR 0xff into the existing value. + +8. Install firefox or chrome, download the latest testboxscript*.zip from + the build box. If the testbox is very short on disk space, i.e. less than + 15GB free disk space after installing Windows Updates, install ImDisk 2.0.9 + or later from e.g. http://www.ltr-data.se/opencode.html/ + +9. Create a user named "vbox" with password "password". Must be an + Administrator user! + +10. Configure user "vbox" to log in automatically via "control userpasswords2". + +11. Open up the port ranges 6000-6100 (VRDP) for TCP traffic and 5000-5032 + (NetPerf) for both TCP and UDP traffic in the Windows Firewall. + From the command line (recommended in vista): + for /L %i in (6000,1,6100) do netsh firewall add portopening TCP %i "VRDP %i" + for /L %i in (5000,1,5032) do netsh firewall add portopening TCP %i "NetPerf %i TCP" + for /L %i in (5000,1,5032) do netsh firewall add portopening UDP %i "NetPerf %i UDP" + netsh firewall set icmpsetting type=ALL + +11b. Set a hostname which the test script can resolve to the host's IP address. + +12. Setup time server to "wei01-time.de.oracle.com" and update date/time. + +13. Activate windows. "https://linserv.de.oracle.com/vbox/wiki/MSDN Volume License Keys" + +14. Windows 2012 R2: If you experience mouse pointer problems connecting with rdesktop, + open the mouse pointer settings and disable mouse pointer shadow. + +15. Enable RDP access by opening "System Properties" and selecting "Allow + remote connections to this computer" in the "Remote" tab. Ensure that + "Allow connections only from computers running Remote Desktop with Network + Level Authentication" is not checked or rdesktop can't access it. + + W10: Make old rdesktop connect: + \HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp\SecurityLayer + Change DWORD Hex '2' -> '1' + +15b. While you're in "System Properties", in the "Hardware" tab, button + "Driver Signing" tell it to ignore logo testing requirements. + + W10: Doesn't exist any more. + +The install (as user vbox): + +16. Disable loading CONIME. Set "HKEY_CURRENT_USER\Console\LoadConIme" to 0. + +17. Unzip (/ copy) the content of the testboxscript-*.zip to C:\testboxscript. + +18. Copy C:\testboxscript\testboxscript\win\autoexec-testbox.cmd to C:\. + +19. Create a shortcut to C:\autoexec-testbox.cmd and drag it into + "Start" -> "All Programs" -> "Startup". + + W10: Find startup folder by hitting Win+R and entering "shell:startup". + +20. If this is an Intel box and the CPU is capable of Nested Paging, edit C:\autoexec-testbox.cmd + and append '--nested-paging' + + +That's currently it. + diff --git a/src/VBox/ValidationKit/testdriver/Makefile.kmk b/src/VBox/ValidationKit/testdriver/Makefile.kmk new file mode 100644 index 00000000..4aa49adc --- /dev/null +++ b/src/VBox/ValidationKit/testdriver/Makefile.kmk @@ -0,0 +1,48 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Python Test Driver. +# + +# +# Copyright (C) 2010-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(filter-out %/winbase.py %/vboxcon.py, $(wildcard $(PATH_SUB_CURRENT)/*.py)) +ifeq ($(KBUILD_HOST),win) + VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(PATH_SUB_CURRENT)/winbase.py +endif + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/testdriver/__init__.py b/src/VBox/ValidationKit/testdriver/__init__.py new file mode 100644 index 00000000..f6534f5b --- /dev/null +++ b/src/VBox/ValidationKit/testdriver/__init__.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# $Id: __init__.py $ + +""" +Test driver package +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" + +__version__ = "$Revision: 153224 $"; + diff --git a/src/VBox/ValidationKit/testdriver/base.py b/src/VBox/ValidationKit/testdriver/base.py new file mode 100755 index 00000000..b90dd0a8 --- /dev/null +++ b/src/VBox/ValidationKit/testdriver/base.py @@ -0,0 +1,1860 @@ +# -*- coding: utf-8 -*- +# $Id: base.py $ +# pylint: disable=too-many-lines + +""" +Base testdriver module. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154842 $" + + +# Standard Python imports. +import os +import os.path +import signal +import socket +import stat +import subprocess +import sys +import time +if sys.version_info[0] < 3: import thread; # pylint: disable=import-error +else: import _thread as thread; # pylint: disable=import-error +import threading +import traceback +import tempfile; +import unittest; + +# Validation Kit imports. +from common import utils; +from common.constants import rtexitcode; +from testdriver import reporter; +if sys.platform == 'win32': + from testdriver import winbase; + +# Figure where we are. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))); + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + + +# +# Some utility functions. +# + +def exeSuff(): + """ + Returns the executable suffix. + """ + if os.name in ('nt', 'os2'): + return '.exe'; + return ''; + +def searchPath(sExecName): + """ + Searches the PATH for the specified executable name, returning the first + existing file/directory/whatever. The return is abspath'ed. + """ + sSuff = exeSuff(); + + sPath = os.getenv('PATH', os.getenv('Path', os.path.defpath)); + aPaths = sPath.split(os.path.pathsep) + for sDir in aPaths: + sFullExecName = os.path.join(sDir, sExecName); + if os.path.exists(sFullExecName): + return os.path.abspath(sFullExecName); + sFullExecName += sSuff; + if os.path.exists(sFullExecName): + return os.path.abspath(sFullExecName); + return sExecName; + +def getEnv(sVar, sLocalAlternative = None): + """ + Tries to get an environment variable, optionally with a local run alternative. + Will raise an exception if sLocalAlternative is None and the variable is + empty or missing. + """ + try: + sVal = os.environ.get(sVar, None); + if sVal is None: + raise GenError('environment variable "%s" is missing' % (sVar)); + if sVal == "": + raise GenError('environment variable "%s" is empty' % (sVar)); + except: + if sLocalAlternative is None or not reporter.isLocal(): + raise + sVal = sLocalAlternative; + return sVal; + +def getDirEnv(sVar, sAlternative = None, fLocalReq = False, fTryCreate = False): + """ + Tries to get an environment variable specifying a directory path. + + Resolves it into an absolute path and verifies its existance before + returning it. + + If the environment variable is empty or isn't set, or if the directory + doesn't exist or isn't a directory, sAlternative is returned instead. + If sAlternative is None, then we'll raise a GenError. For local runs we'll + only do this if fLocalReq is True. + """ + assert sAlternative is None or fTryCreate is False; + try: + sVal = os.environ.get(sVar, None); + if sVal is None: + raise GenError('environment variable "%s" is missing' % (sVar)); + if sVal == "": + raise GenError('environment variable "%s" is empty' % (sVar)); + + sVal = os.path.abspath(sVal); + if not os.path.isdir(sVal): + if not fTryCreate or os.path.exists(sVal): + reporter.error('the value of env.var. "%s" is not a dir: "%s"' % (sVar, sVal)); + raise GenError('the value of env.var. "%s" is not a dir: "%s"' % (sVar, sVal)); + try: + os.makedirs(sVal, 0o700); + except: + reporter.error('makedirs failed on the value of env.var. "%s": "%s"' % (sVar, sVal)); + raise GenError('makedirs failed on the value of env.var. "%s": "%s"' % (sVar, sVal)); + except: + if sAlternative is None: + if reporter.isLocal() and fLocalReq: + raise; + sVal = None; + else: + sVal = os.path.abspath(sAlternative); + return sVal; + +def timestampMilli(): + """ + Gets a millisecond timestamp. + """ + return utils.timestampMilli(); + +def timestampNano(): + """ + Gets a nanosecond timestamp. + """ + return utils.timestampNano(); + +def tryGetHostByName(sName): + """ + Wrapper around gethostbyname. + """ + if sName is not None: + try: + sIpAddr = socket.gethostbyname(sName); + except: + reporter.errorXcpt('gethostbyname(%s)' % (sName)); + else: + if sIpAddr != '0.0.0.0': + sName = sIpAddr; + else: + reporter.error('gethostbyname(%s) -> %s' % (sName, sIpAddr)); + return sName; + +def __processSudoKill(uPid, iSignal, fSudo): + """ + Does the sudo kill -signal pid thing if fSudo is true, else uses os.kill. + """ + try: + if fSudo: + return utils.sudoProcessCall(['/bin/kill', '-%s' % (iSignal,), str(uPid)]) == 0; + os.kill(uPid, iSignal); + return True; + except: + reporter.logXcpt('uPid=%s' % (uPid,)); + return False; + +def processInterrupt(uPid, fSudo = False): + """ + Sends a SIGINT or equivalent to interrupt the specified process. + Returns True on success, False on failure. + + On Windows hosts this may not work unless the process happens to be a + process group leader. + """ + if sys.platform == 'win32': + fRc = winbase.processInterrupt(uPid) + else: + fRc = __processSudoKill(uPid, signal.SIGINT, fSudo); + return fRc; + +def sendUserSignal1(uPid, fSudo = False): + """ + Sends a SIGUSR1 or equivalent to nudge the process into shutting down + (VBoxSVC) or something. + Returns True on success, False on failure or if not supported (win). + + On Windows hosts this may not work unless the process happens to be a + process group leader. + """ + if sys.platform == 'win32': + fRc = False; + else: + fRc = __processSudoKill(uPid, signal.SIGUSR1, fSudo); # pylint: disable=no-member + return fRc; + +def processTerminate(uPid, fSudo = False): + """ + Terminates the process in a nice manner (SIGTERM or equivalent). + Returns True on success, False on failure (logged). + """ + fRc = False; + if sys.platform == 'win32': + fRc = winbase.processTerminate(uPid); + else: + fRc = __processSudoKill(uPid, signal.SIGTERM, fSudo); + return fRc; + +def processKill(uPid, fSudo = False): + """ + Terminates the process with extreme prejudice (SIGKILL). + Returns True on success, False on failure. + """ + fRc = False; + if sys.platform == 'win32': + fRc = winbase.processKill(uPid); + else: + fRc = __processSudoKill(uPid, signal.SIGKILL, fSudo); # pylint: disable=no-member + return fRc; + +def processKillWithNameCheck(uPid, sName): + """ + Like processKill(), but checks if the process name matches before killing + it. This is intended for killing using potentially stale pid values. + + Returns True on success, False on failure. + """ + + if processCheckPidAndName(uPid, sName) is not True: + return False; + return processKill(uPid); + + +def processExists(uPid): + """ + Checks if the specified process exits. + This will only work if we can signal/open the process. + + Returns True if it positively exists, False otherwise. + """ + return utils.processExists(uPid); + +def processCheckPidAndName(uPid, sName): + """ + Checks if a process PID and NAME matches. + """ + if sys.platform == 'win32': + fRc = winbase.processCheckPidAndName(uPid, sName); + else: + sOs = utils.getHostOs(); + if sOs == 'linux': + asPsCmd = ['/bin/ps', '-p', '%u' % (uPid,), '-o', 'fname=']; + elif sOs == 'solaris': + asPsCmd = ['/usr/bin/ps', '-p', '%u' % (uPid,), '-o', 'fname=']; + elif sOs == 'darwin': + asPsCmd = ['/bin/ps', '-p', '%u' % (uPid,), '-o', 'ucomm=']; + else: + asPsCmd = None; + + if asPsCmd is not None: + try: + oPs = subprocess.Popen(asPsCmd, stdout=subprocess.PIPE); # pylint: disable=consider-using-with + sCurName = oPs.communicate()[0]; + iExitCode = oPs.wait(); + except: + reporter.logXcpt(); + return False; + + # ps fails with non-zero exit code if the pid wasn't found. + if iExitCode != 0: + return False; + if sCurName is None: + return False; + sCurName = sCurName.strip(); + if sCurName == '': + return False; + + if os.path.basename(sName) == sName: + sCurName = os.path.basename(sCurName); + elif os.path.basename(sCurName) == sCurName: + sName = os.path.basename(sName); + + if sCurName != sName: + return False; + + fRc = True; + return fRc; + +def wipeDirectory(sDir): + """ + Deletes all file and sub-directories in sDir, leaving sDir in empty afterwards. + Returns the number of errors after logging them as errors. + """ + if not os.path.exists(sDir): + return 0; + + try: + asNames = os.listdir(sDir); + except: + return reporter.errorXcpt('os.listdir("%s")' % (sDir)); + + cErrors = 0; + for sName in asNames: + # Build full path and lstat the object. + sFullName = os.path.join(sDir, sName) + try: + oStat = os.lstat(sFullName); + except: + reporter.errorXcpt('lstat("%s")' % (sFullName,)); + cErrors = cErrors + 1; + continue; + + if stat.S_ISDIR(oStat.st_mode): + # Directory - recurse and try remove it. + cErrors = cErrors + wipeDirectory(sFullName); + try: + os.rmdir(sFullName); + except: + reporter.errorXcpt('rmdir("%s")' % (sFullName,)); + cErrors = cErrors + 1; + else: + # File, symlink, fifo or something - remove/unlink. + try: + os.remove(sFullName); + except: + reporter.errorXcpt('remove("%s")' % (sFullName,)); + cErrors = cErrors + 1; + return cErrors; + + +# +# Classes +# + +class GenError(Exception): + """ + Exception class which only purpose it is to allow us to only catch our own + exceptions. Better design later. + """ + + def __init__(self, sWhat = "whatever"): + Exception.__init__(self); + self.sWhat = sWhat + + def str(self): + """Get the message string.""" + return self.sWhat; + + +class InvalidOption(GenError): + """ + Exception thrown by TestDriverBase.parseOption(). It contains the error message. + """ + def __init__(self, sWhat): + GenError.__init__(self, sWhat); + + +class QuietInvalidOption(GenError): + """ + Exception thrown by TestDriverBase.parseOption(). Error already printed, just + return failure. + """ + def __init__(self): + GenError.__init__(self, ""); + + +class TdTaskBase(object): + """ + The base task. + """ + + def __init__(self, sCaller, fnProcessEvents = None): + self.sDbgCreated = '%s: %s' % (utils.getTimePrefix(), sCaller); + self.fSignalled = False; + self.__oRLock = threading.RLock(); + self.oCv = threading.Condition(self.__oRLock); + self.oOwner = None; + self.msStart = timestampMilli(); + self.oLocker = None; + + ## Callback function that takes no parameters and will not be called holding the lock. + ## It is a hack to work the XPCOM and COM event queues, so we won't hold back events + ## that could block task progress (i.e. hangs VM). + self.fnProcessEvents = fnProcessEvents; + + def __del__(self): + """In case we need it later on.""" + pass; # pylint: disable=unnecessary-pass + + def toString(self): + """ + Stringifies the object, mostly as a debug aid. + """ + return '<%s: fSignalled=%s, __oRLock=%s, oCv=%s, oOwner=%s, oLocker=%s, msStart=%s, sDbgCreated=%s>' \ + % (type(self).__name__, self.fSignalled, self.__oRLock, self.oCv, repr(self.oOwner), self.oLocker, self.msStart, + self.sDbgCreated,); + + def __str__(self): + return self.toString(); + + def lockTask(self): + """ Wrapper around oCv.acquire(). """ + if True is True: # change to False for debugging deadlocks. # pylint: disable=comparison-with-itself + self.oCv.acquire(); + else: + msStartWait = timestampMilli(); + while self.oCv.acquire(0) is False: + if timestampMilli() - msStartWait > 30*1000: + reporter.error('!!! timed out waiting for %s' % (self, )); + traceback.print_stack(); + reporter.logAllStacks() + self.oCv.acquire(); + break; + time.sleep(0.5); + self.oLocker = thread.get_ident() + return None; + + def unlockTask(self): + """ Wrapper around oCv.release(). """ + self.oLocker = None; + self.oCv.release(); + return None; + + def getAgeAsMs(self): + """ + Returns the number of milliseconds the task has existed. + """ + return timestampMilli() - self.msStart; + + def setTaskOwner(self, oOwner): + """ + Sets or clears the task owner. (oOwner can be None.) + + Returns the previous owner, this means None if not owned. + """ + self.lockTask(); + oOldOwner = self.oOwner; + self.oOwner = oOwner; + self.unlockTask(); + return oOldOwner; + + def signalTaskLocked(self): + """ + Variant of signalTask that can be called while owning the lock. + """ + fOld = self.fSignalled; + if not fOld: + reporter.log2('signalTaskLocked(%s)' % (self,)); + self.fSignalled = True; + self.oCv.notifyAll(); # pylint: disable=deprecated-method + if self.oOwner is not None: + self.oOwner.notifyAboutReadyTask(self); + return fOld; + + def signalTask(self): + """ + Signals the task, internal use only. + + Returns the previous state. + """ + self.lockTask(); + fOld = self.signalTaskLocked(); + self.unlockTask(); + return fOld + + def resetTaskLocked(self): + """ + Variant of resetTask that can be called while owning the lock. + """ + fOld = self.fSignalled; + self.fSignalled = False; + return fOld; + + def resetTask(self): + """ + Resets the task signal, internal use only. + + Returns the previous state. + """ + self.lockTask(); + fOld = self.resetTaskLocked(); + self.unlockTask(); + return fOld + + def pollTask(self, fLocked = False): + """ + Poll the signal status of the task. + Returns True if signalled, False if not. + + Override this method. + """ + if not fLocked: + self.lockTask(); + fState = self.fSignalled; + if not fLocked: + self.unlockTask(); + return fState + + def waitForTask(self, cMsTimeout = 0): + """ + Waits for the task to be signalled. + + Returns True if the task is/became ready before the timeout expired. + Returns False if the task is still not after cMsTimeout have elapsed. + + Overriable. + """ + if self.fnProcessEvents: + self.fnProcessEvents(); + + self.lockTask(); + + fState = self.pollTask(True); + if not fState: + # Don't wait more than 1s. This allow lazy state polling and avoid event processing trouble. + msStart = timestampMilli(); + while not fState: + cMsElapsed = timestampMilli() - msStart; + if cMsElapsed >= cMsTimeout: + break; + + cMsWait = cMsTimeout - cMsElapsed + cMsWait = min(cMsWait, 1000); + try: + self.oCv.wait(cMsWait / 1000.0); + except: + pass; + + if self.fnProcessEvents: + self.unlockTask(); + self.fnProcessEvents(); + self.lockTask(); + + reporter.doPollWork('TdTaskBase.waitForTask'); + fState = self.pollTask(True); + + self.unlockTask(); + + if self.fnProcessEvents: + self.fnProcessEvents(); + + return fState; + + +class Process(TdTaskBase): + """ + Child Process. + """ + + def __init__(self, sName, asArgs, uPid, hWin = None, uTid = None): + TdTaskBase.__init__(self, utils.getCallerName()); + self.sName = sName; + self.asArgs = asArgs; + self.uExitCode = -127; + self.uPid = uPid; + self.hWin = hWin; + self.uTid = uTid; + self.sKindCrashReport = None; + self.sKindCrashDump = None; + + def toString(self): + return '<%s uExitcode=%s, uPid=%s, sName=%s, asArgs=%s, hWin=%s, uTid=%s>' \ + % (TdTaskBase.toString(self), self.uExitCode, self.uPid, self.sName, self.asArgs, self.hWin, self.uTid); + + # + # Instantiation methods. + # + + @staticmethod + def spawn(sName, *asArgsIn): + """ + Similar to os.spawnl(os.P_NOWAIT,). + + """ + # Make argument array (can probably use asArgsIn directly, but wtf). + asArgs = []; + for sArg in asArgsIn: + asArgs.append(sArg); + + # Special case: Windows. + if sys.platform == 'win32': + (uPid, hProcess, uTid) = winbase.processCreate(searchPath(sName), asArgs); + if uPid == -1: + return None; + return Process(sName, asArgs, uPid, hProcess, uTid); + + # Unixy. + try: + uPid = os.spawnv(os.P_NOWAIT, sName, asArgs); + except: + reporter.logXcpt('sName=%s' % (sName,)); + return None; + return Process(sName, asArgs, uPid); + + @staticmethod + def spawnp(sName, *asArgsIn): + """ + Similar to os.spawnlp(os.P_NOWAIT,). + + """ + return Process.spawn(searchPath(sName), *asArgsIn); + + # + # Task methods + # + + def pollTask(self, fLocked = False): + """ + Overridden pollTask method. + """ + if not fLocked: + self.lockTask(); + + fRc = self.fSignalled; + if not fRc: + if sys.platform == 'win32': + if winbase.processPollByHandle(self.hWin): + try: + if hasattr(self.hWin, '__int__'): # Needed for newer pywin32 versions. + (uPid, uStatus) = os.waitpid(self.hWin.__int__(), 0); + else: + (uPid, uStatus) = os.waitpid(self.hWin, 0); + if uPid in (self.hWin, self.uPid,): + self.hWin.Detach(); # waitpid closed it, so it's now invalid. + self.hWin = None; + uPid = self.uPid; + except: + reporter.logXcpt(); + uPid = self.uPid; + uStatus = 0xffffffff; + else: + uPid = 0; + uStatus = 0; # pylint: disable=redefined-variable-type + else: + try: + (uPid, uStatus) = os.waitpid(self.uPid, os.WNOHANG); # pylint: disable=no-member + except: + reporter.logXcpt(); + uPid = self.uPid; + uStatus = 0xffffffff; + + # Got anything? + if uPid == self.uPid: + self.uExitCode = uStatus; + reporter.log('Process %u -> %u (%#x)' % (uPid, uStatus, uStatus)); + self.signalTaskLocked(); + if self.uExitCode != 0 and (self.sKindCrashReport is not None or self.sKindCrashDump is not None): + reporter.error('Process "%s" returned/crashed with a non-zero status code!! rc=%u sig=%u%s (raw=%#x)' + % ( self.sName, self.uExitCode >> 8, self.uExitCode & 0x7f, + ' w/ core' if self.uExitCode & 0x80 else '', self.uExitCode)) + utils.processCollectCrashInfo(self.uPid, reporter.log, self._addCrashFile); + + fRc = self.fSignalled; + if not fLocked: + self.unlockTask(); + return fRc; + + def _addCrashFile(self, sFile, fBinary): + """ + Helper for adding a crash report or dump to the test report. + """ + sKind = self.sKindCrashDump if fBinary else self.sKindCrashReport; + if sKind is not None: + reporter.addLogFile(sFile, sKind); + return None; + + + # + # Methods + # + + def enableCrashReporting(self, sKindCrashReport, sKindCrashDump): + """ + Enabling (or disables) automatic crash reporting on systems where that + is possible. The two file kind parameters are on the form + 'crash/log/client' and 'crash/dump/client'. If both are None, + reporting will be disabled. + """ + self.sKindCrashReport = sKindCrashReport; + self.sKindCrashDump = sKindCrashDump; + + sCorePath = None; + sOs = utils.getHostOs(); + if sOs == 'solaris': + if sKindCrashDump is not None: # Enable. + sCorePath = getDirEnv('TESTBOX_PATH_SCRATCH', sAlternative = '/var/cores', fTryCreate = False); + (iExitCode, _, sErr) = utils.processOutputUnchecked([ 'coreadm', '-e', 'global', '-e', 'global-setid', \ + '-e', 'process', '-e', 'proc-setid', \ + '-g', os.path.join(sCorePath, '%f.%p.core')]); + else: # Disable. + (iExitCode, _, sErr) = utils.processOutputUnchecked([ 'coreadm', \ + '-d', 'global', '-d', 'global-setid', \ + '-d', 'process', '-d', 'proc-setid' ]); + if iExitCode != 0: # Don't report an actual error, just log this. + reporter.log('%s coreadm failed: %s' % ('Enabling' if sKindCrashDump else 'Disabling', sErr)); + + if sKindCrashDump is not None: + if sCorePath is not None: + reporter.log('Crash dumps enabled -- path is "%s"' % (sCorePath,)); + else: + reporter.log('Crash dumps disabled'); + + return True; + + def isRunning(self): + """ + Returns True if the process is still running, False if not. + """ + return not self.pollTask(); + + def wait(self, cMsTimeout = 0): + """ + Wait for the process to exit. + + Returns True if the process exited withint the specified wait period. + Returns False if still running. + """ + return self.waitForTask(cMsTimeout); + + def getExitCode(self): + """ + Returns the exit code of the process. + The process must have exited or the result will be wrong. + """ + if self.isRunning(): + return -127; + return self.uExitCode >> 8; + + def isNormalExit(self): + """ + Returns True if regular exit(), False if signal or still running. + """ + if self.isRunning(): + return False; + if sys.platform == 'win32': + return True; + return os.WIFEXITED(self.uExitCode); # pylint: disable=no-member + + def interrupt(self): + """ + Sends a SIGINT or equivalent to interrupt the process. + Returns True on success, False on failure. + + On Windows hosts this may not work unless the process happens to be a + process group leader. + """ + if sys.platform == 'win32': + return winbase.postThreadMesssageQuit(self.uTid); + return processInterrupt(self.uPid); + + def sendUserSignal1(self): + """ + Sends a SIGUSR1 or equivalent to nudge the process into shutting down + (VBoxSVC) or something. + Returns True on success, False on failure. + + On Windows hosts this may not work unless the process happens to be a + process group leader. + """ + #if sys.platform == 'win32': + # return winbase.postThreadMesssageClose(self.uTid); + return sendUserSignal1(self.uPid); + + def terminate(self): + """ + Terminates the process in a nice manner (SIGTERM or equivalent). + Returns True on success, False on failure (logged). + """ + if sys.platform == 'win32': + return winbase.processTerminateByHandle(self.hWin); + return processTerminate(self.uPid); + + def getPid(self): + """ Returns the process id. """ + return self.uPid; + + +class SubTestDriverBase(object): + """ + The base sub-test driver. + + It helps thinking of these as units/sets/groups of tests, where the test + cases are (mostly) realized in python. + + The sub-test drivers are subordinates of one or more test drivers. They + can be viewed as test code libraries that is responsible for parts of a + test driver run in different setups. One example would be testing a guest + additions component, which is applicable both to freshly installed guest + additions and VMs with old guest. + + The test drivers invokes the sub-test drivers in a private manner during + test execution, but some of the generic bits are done automagically by the + base class: options, help, resources, various other actions. + """ + + def __init__(self, oTstDrv, sName, sTestName): + self.oTstDrv = oTstDrv # type: TestDriverBase + self.sName = sName; # For use with options (--enable-sub-driver sName:sName2) + self.sTestName = sTestName; # More descriptive for passing to reporter.testStart(). + self.asRsrcs = [] # type: List(str) + self.fEnabled = True; # TestDriverBase --enable-sub-driver and --disable-sub-driver. + + def showUsage(self): + """ + Show usage information if any. + + The default implementation only prints the name. + """ + reporter.log(''); + reporter.log('Options for sub-test driver %s (%s):' % (self.sTestName, self.sName,)); + return True; + + def parseOption(self, asArgs, iArg): + """ + Parse an option. Override this. + + @param asArgs The argument vector. + @param iArg The index of the current argument. + + @returns The index of the next argument if consumed, @a iArg if not. + + @throws InvalidOption or QuietInvalidOption on syntax error or similar. + """ + _ = asArgs; + return iArg; + + +class TestDriverBase(object): # pylint: disable=too-many-instance-attributes + """ + The base test driver. + """ + + def __init__(self): + self.fInterrupted = False; + + # Actions. + self.asSpecialActions = ['extract', 'abort']; + self.asNormalActions = ['cleanup-before', 'verify', 'config', 'execute', 'cleanup-after' ]; + self.asActions = []; + self.sExtractDstPath = None; + + # Options. + self.fNoWipeClean = False; + + # Tasks - only accessed by one thread atm, so no need for locking. + self.aoTasks = []; + + # Host info. + self.sHost = utils.getHostOs(); + self.sHostArch = utils.getHostArch(); + + # Skipped status modifier (see end of innerMain()). + self.fBadTestbox = False; + + # + # Get our bearings and adjust the environment. + # + if not utils.isRunningFromCheckout(): + self.sBinPath = os.path.join(g_ksValidationKitDir, utils.getHostOs(), utils.getHostArch()); + else: + self.sBinPath = os.path.join(g_ksValidationKitDir, os.pardir, os.pardir, os.pardir, 'out', utils.getHostOsDotArch(), + os.environ.get('KBUILD_TYPE', 'debug'), + 'validationkit', utils.getHostOs(), utils.getHostArch()); + self.sOrgShell = os.environ.get('SHELL'); + self.sOurShell = os.path.join(self.sBinPath, 'vts_shell' + exeSuff()); # No shell yet. + os.environ['SHELL'] = self.sOurShell; + + self.sScriptPath = getDirEnv('TESTBOX_PATH_SCRIPTS'); + if self.sScriptPath is None: + self.sScriptPath = os.path.abspath(os.path.join(os.getcwd(), '..')); + os.environ['TESTBOX_PATH_SCRIPTS'] = self.sScriptPath; + + self.sScratchPath = getDirEnv('TESTBOX_PATH_SCRATCH', fTryCreate = True); + if self.sScratchPath is None: + sTmpDir = tempfile.gettempdir(); + if sTmpDir == '/tmp': # /var/tmp is generally more suitable on all platforms. + sTmpDir = '/var/tmp'; + self.sScratchPath = os.path.abspath(os.path.join(sTmpDir, 'VBoxTestTmp')); + if not os.path.isdir(self.sScratchPath): + os.makedirs(self.sScratchPath, 0o700); + os.environ['TESTBOX_PATH_SCRATCH'] = self.sScratchPath; + + self.sTestBoxName = getEnv( 'TESTBOX_NAME', 'local'); + self.sTestSetId = getEnv( 'TESTBOX_TEST_SET_ID', 'local'); + self.sBuildPath = getDirEnv('TESTBOX_PATH_BUILDS'); + self.sUploadPath = getDirEnv('TESTBOX_PATH_UPLOAD'); + self.sResourcePath = getDirEnv('TESTBOX_PATH_RESOURCES'); + if self.sResourcePath is None: + if self.sHost == 'darwin': self.sResourcePath = "/Volumes/testrsrc/"; + elif self.sHost == 'freebsd': self.sResourcePath = "/mnt/testrsrc/"; + elif self.sHost == 'linux': self.sResourcePath = "/mnt/testrsrc/"; + elif self.sHost == 'os2': self.sResourcePath = "T:/"; + elif self.sHost == 'solaris': self.sResourcePath = "/mnt/testrsrc/"; + elif self.sHost == 'win': self.sResourcePath = "T:/"; + else: raise GenError('unknown host OS "%s"' % (self.sHost)); + + # PID file for the testdriver. + self.sPidFile = os.path.join(self.sScratchPath, 'testdriver.pid'); + + # Some stuff for the log... + reporter.log('scratch: %s' % (self.sScratchPath,)); + + # Get the absolute timeout (seconds since epoch, see + # utils.timestampSecond()). None if not available. + self.secTimeoutAbs = os.environ.get('TESTBOX_TIMEOUT_ABS', None); + if self.secTimeoutAbs is not None: + self.secTimeoutAbs = long(self.secTimeoutAbs); + reporter.log('secTimeoutAbs: %s' % (self.secTimeoutAbs,)); + else: + reporter.log('TESTBOX_TIMEOUT_ABS not found in the environment'); + + # Distance from secTimeoutAbs that timeouts should be adjusted to. + self.secTimeoutFudge = 30; + + # List of sub-test drivers (SubTestDriverBase derivatives). + self.aoSubTstDrvs = [] # type: list(SubTestDriverBase) + + # Use the scratch path for temporary files. + if self.sHost in ['win', 'os2']: + os.environ['TMP'] = self.sScratchPath; + os.environ['TEMP'] = self.sScratchPath; + os.environ['TMPDIR'] = self.sScratchPath; + os.environ['IPRT_TMPDIR'] = self.sScratchPath; # IPRT/VBox specific. + + + # + # Resource utility methods. + # + + def isResourceFile(self, sFile): + """ + Checks if sFile is in in the resource set. + """ + ## @todo need to deal with stuff in the validationkit.zip and similar. + asRsrcs = self.getResourceSet(); + if sFile in asRsrcs: + return os.path.isfile(os.path.join(self.sResourcePath, sFile)); + for sRsrc in asRsrcs: + if sFile.startswith(sRsrc): + sFull = os.path.join(self.sResourcePath, sRsrc); + if os.path.isdir(sFull): + return os.path.isfile(os.path.join(self.sResourcePath, sRsrc)); + return False; + + def getFullResourceName(self, sName): + """ + Returns the full resource name. + """ + if os.path.isabs(sName): ## @todo Hack. Need to deal properly with stuff in the validationkit.zip and similar. + return sName; + return os.path.join(self.sResourcePath, sName); + + # + # Scratch related utility methods. + # + + def wipeScratch(self): + """ + Removes the content of the scratch directory. + Returns True on no errors, False + log entries on errors. + """ + cErrors = wipeDirectory(self.sScratchPath); + return cErrors == 0; + + # + # Sub-test driver related methods. + # + + def addSubTestDriver(self, oSubTstDrv): + """ + Adds a sub-test driver. + + Returns True on success, false on failure. + """ + assert isinstance(oSubTstDrv, SubTestDriverBase); + if oSubTstDrv in self.aoSubTstDrvs: + reporter.error('Attempt at adding sub-test driver %s twice.' % (oSubTstDrv.sName,)); + return False; + self.aoSubTstDrvs.append(oSubTstDrv); + return True; + + def showSubTstDrvUsage(self): + """ + Shows the usage of the sub-test drivers. + """ + for oSubTstDrv in self.aoSubTstDrvs: + oSubTstDrv.showUsage(); + return True; + + def subTstDrvParseOption(self, asArgs, iArgs): + """ + Lets the sub-test drivers have a go at the option. + Returns the index of the next option if handled, otherwise iArgs. + """ + for oSubTstDrv in self.aoSubTstDrvs: + iNext = oSubTstDrv.parseOption(asArgs, iArgs) + if iNext != iArgs: + assert iNext > iArgs; + assert iNext <= len(asArgs); + return iNext; + return iArgs; + + def findSubTstDrvByShortName(self, sShortName): + """ + Locates a sub-test driver by it's short name. + Returns sub-test driver object reference if found, None if not. + """ + for oSubTstDrv in self.aoSubTstDrvs: + if oSubTstDrv.sName == sShortName: + return oSubTstDrv; + return None; + + + # + # Task related methods. + # + + def addTask(self, oTask): + """ + Adds oTask to the task list. + + Returns True if the task was added. + + Returns False if the task was already in the task list. + """ + if oTask in self.aoTasks: + return False; + #reporter.log2('adding task %s' % (oTask,)); + self.aoTasks.append(oTask); + oTask.setTaskOwner(self); + #reporter.log2('tasks now in list: %d - %s' % (len(self.aoTasks), self.aoTasks)); + return True; + + def removeTask(self, oTask): + """ + Removes oTask to the task list. + + Returns oTask on success and None on failure. + """ + try: + #reporter.log2('removing task %s' % (oTask,)); + self.aoTasks.remove(oTask); + except: + return None; + else: + oTask.setTaskOwner(None); + #reporter.log2('tasks left: %d - %s' % (len(self.aoTasks), self.aoTasks)); + return oTask; + + def removeAllTasks(self): + """ + Removes all the task from the task list. + + Returns None. + """ + aoTasks = self.aoTasks; + self.aoTasks = []; + for oTask in aoTasks: + oTask.setTaskOwner(None); + return None; + + def notifyAboutReadyTask(self, oTask): + """ + Notificiation that there is a ready task. May be called owning the + task lock, so be careful wrt deadlocks. + + Remember to call super when overriding this. + """ + if oTask is None: pass; # lint + return None; + + def pollTasks(self): + """ + Polls the task to see if any of them are ready. + Returns the ready task, None if none are ready. + """ + for oTask in self.aoTasks: + if oTask.pollTask(): + return oTask; + return None; + + def waitForTasksSleepWorker(self, cMsTimeout): + """ + Overridable method that does the sleeping for waitForTask(). + + cMsTimeout will not be larger than 1000, so there is normally no need + to do any additional splitting up of the polling interval. + + Returns True if cMillieSecs elapsed. + Returns False if some exception was raised while we waited or + there turned out to be nothing to wait on. + """ + try: + self.aoTasks[0].waitForTask(cMsTimeout); + return True; + except Exception as oXcpt: + reporter.log("waitForTasksSleepWorker: %s" % (str(oXcpt),)); + return False; + + def waitForTasks(self, cMsTimeout): + """ + Waits for any of the tasks to require attention or a KeyboardInterrupt. + Returns the ready task on success, None on timeout or interrupt. + """ + try: + #reporter.log2('waitForTasks: cMsTimeout=%d' % (cMsTimeout,)); + + if cMsTimeout == 0: + return self.pollTasks(); + + if not self.aoTasks: + return None; + + fMore = True; + if cMsTimeout < 0: + while fMore: + oTask = self.pollTasks(); + if oTask is not None: + return oTask; + fMore = self.waitForTasksSleepWorker(1000); + else: + msStart = timestampMilli(); + while fMore: + oTask = self.pollTasks(); + if oTask is not None: + #reporter.log2('waitForTasks: returning %s, msStart=%d' % \ + # (oTask, msStart)); + return oTask; + + cMsElapsed = timestampMilli() - msStart; + if cMsElapsed > cMsTimeout: # not ==, we want the final waitForEvents. + break; + cMsSleep = cMsTimeout - cMsElapsed; + cMsSleep = min(cMsSleep, 1000); + fMore = self.waitForTasksSleepWorker(cMsSleep); + except KeyboardInterrupt: + self.fInterrupted = True; + reporter.errorXcpt('KeyboardInterrupt', 6); + except: + reporter.errorXcpt(None, 6); + return None; + + # + # PID file management methods. + # + + def pidFileRead(self): + """ + Worker that reads the PID file. + Returns dictionary of PID with value (sName, fSudo), empty if no file. + """ + dPids = {}; + if os.path.isfile(self.sPidFile): + try: + oFile = utils.openNoInherit(self.sPidFile, 'r'); + sContent = str(oFile.read()); + oFile.close(); + except: + reporter.errorXcpt(); + return dPids; + + sContent = str(sContent).strip().replace('\n', ' ').replace('\r', ' ').replace('\t', ' '); + for sProcess in sContent.split(' '): + asFields = sProcess.split(':'); + if len(asFields) == 3 and asFields[0].isdigit(): + try: + dPids[int(asFields[0])] = (asFields[2], asFields[1] == 'sudo'); + except: + reporter.logXcpt('sProcess=%s' % (sProcess,)); + else: + reporter.log('%s: "%s"' % (self.sPidFile, sProcess)); + + return dPids; + + def pidFileAdd(self, iPid, sName, fSudo = False): + """ + Adds a PID to the PID file, creating the file if necessary. + """ + try: + oFile = utils.openNoInherit(self.sPidFile, 'a'); + oFile.write('%s:%s:%s\n' + % ( iPid, + 'sudo' if fSudo else 'normal', + sName.replace(' ', '_').replace(':','_').replace('\n','_').replace('\r','_').replace('\t','_'),)); + oFile.close(); + except: + reporter.errorXcpt(); + return False; + ## @todo s/log/log2/ + reporter.log('pidFileAdd: added %s (%#x) %s fSudo=%s (new content: %s)' + % (iPid, iPid, sName, fSudo, self.pidFileRead(),)); + return True; + + def pidFileRemove(self, iPid, fQuiet = False): + """ + Removes a PID from the PID file. + """ + dPids = self.pidFileRead(); + if iPid not in dPids: + if not fQuiet: + reporter.log('pidFileRemove could not find %s in the PID file (content: %s)' % (iPid, dPids)); + return False; + + sName = dPids[iPid][0]; + del dPids[iPid]; + + sPid = ''; + for iPid2, tNameSudo in dPids.items(): + sPid += '%s:%s:%s\n' % (iPid2, 'sudo' if tNameSudo[1] else 'normal', tNameSudo[0]); + + try: + oFile = utils.openNoInherit(self.sPidFile, 'w'); + oFile.write(sPid); + oFile.close(); + except: + reporter.errorXcpt(); + return False; + ## @todo s/log/log2/ + reporter.log('pidFileRemove: removed PID %d [%s] (new content: %s)' % (iPid, sName, self.pidFileRead(),)); + return True; + + def pidFileDelete(self): + """Creates the testdriver PID file.""" + if os.path.isfile(self.sPidFile): + try: + os.unlink(self.sPidFile); + except: + reporter.logXcpt(); + return False; + ## @todo s/log/log2/ + reporter.log('pidFileDelete: deleted "%s"' % (self.sPidFile,)); + return True; + + # + # Misc helper methods. + # + + def requireMoreArgs(self, cMinNeeded, asArgs, iArg): + """ + Checks that asArgs has at least cMinNeeded args following iArg. + + Returns iArg + 1 if it checks out fine. + Raise appropritate exception if not, ASSUMING that the current argument + is found at iArg. + """ + assert cMinNeeded >= 1; + if iArg + cMinNeeded > len(asArgs): + if cMinNeeded > 1: + raise InvalidOption('The "%s" option takes %s values' % (asArgs[iArg], cMinNeeded,)); + raise InvalidOption('The "%s" option takes 1 value' % (asArgs[iArg],)); + return iArg + 1; + + def getBinTool(self, sName): + """ + Returns the full path to the given binary validation kit tool. + """ + return os.path.join(self.sBinPath, sName) + exeSuff(); + + def adjustTimeoutMs(self, cMsTimeout, cMsMinimum = None): + """ + Adjusts the given timeout (milliseconds) to take TESTBOX_TIMEOUT_ABS + and cMsMinimum (optional) into account. + + Returns adjusted timeout. + Raises no exceptions. + """ + if self.secTimeoutAbs is not None: + cMsToDeadline = self.secTimeoutAbs * 1000 - utils.timestampMilli(); + if cMsToDeadline >= 0: + # Adjust for fudge and enforce the minimum timeout + cMsToDeadline -= self.secTimeoutFudge * 1000; + if cMsToDeadline < (cMsMinimum if cMsMinimum is not None else 10000): + cMsToDeadline = cMsMinimum if cMsMinimum is not None else 10000; + + # Is the timeout beyond the (adjusted) deadline, if so change it. + if cMsTimeout > cMsToDeadline: + reporter.log('adjusting timeout: %s ms -> %s ms (deadline)\n' % (cMsTimeout, cMsToDeadline,)); + return cMsToDeadline; + reporter.log('adjustTimeoutMs: cMsTimeout (%s) > cMsToDeadline (%s)' % (cMsTimeout, cMsToDeadline,)); + else: + # Don't bother, we've passed the deadline. + reporter.log('adjustTimeoutMs: ooops! cMsToDeadline=%s (%s), timestampMilli()=%s, timestampSecond()=%s' + % (cMsToDeadline, cMsToDeadline*1000, utils.timestampMilli(), utils.timestampSecond())); + + # Only enforce the minimum timeout if specified. + if cMsMinimum is not None and cMsTimeout < cMsMinimum: + reporter.log('adjusting timeout: %s ms -> %s ms (minimum)\n' % (cMsTimeout, cMsMinimum,)); + cMsTimeout = cMsMinimum; + + return cMsTimeout; + + def prepareResultFile(self, sName = 'results.xml'): + """ + Given a base name (no path, but extension if required), a scratch file + name is computed and any previous file removed. + + Returns the full path to the file sName. + Raises exception on failure. + """ + sXmlFile = os.path.join(self.sScratchPath, sName); + if os.path.exists(sXmlFile): + os.unlink(sXmlFile); + return sXmlFile; + + + # + # Overridable methods. + # + + def showUsage(self): + """ + Shows the usage. + + When overriding this, call super first. + """ + sName = os.path.basename(sys.argv[0]); + reporter.log('Usage: %s [options] <action(s)>' % (sName,)); + reporter.log(''); + reporter.log('Actions (in execution order):'); + reporter.log(' cleanup-before'); + reporter.log(' Cleanups done at the start of testing.'); + reporter.log(' verify'); + reporter.log(' Verify that all necessary resources are present.'); + reporter.log(' config'); + reporter.log(' Configure the tests.'); + reporter.log(' execute'); + reporter.log(' Execute the tests.'); + reporter.log(' cleanup-after'); + reporter.log(' Cleanups done at the end of the testing.'); + reporter.log(''); + reporter.log('Special Actions:'); + reporter.log(' all'); + reporter.log(' Alias for: %s' % (' '.join(self.asNormalActions),)); + reporter.log(' extract <path>'); + reporter.log(' Extract the test resources and put them in the specified'); + reporter.log(' path for off side/line testing.'); + reporter.log(' abort'); + reporter.log(' Aborts the test.'); + reporter.log(''); + reporter.log('Base Options:'); + reporter.log(' -h, --help'); + reporter.log(' Show this help message.'); + reporter.log(' -v, --verbose'); + reporter.log(' Increase logging verbosity, repeat for more logging.'); + reporter.log(' -d, --debug'); + reporter.log(' Increase the debug logging level, repeat for more info.'); + reporter.log(' --no-wipe-clean'); + reporter.log(' Do not wipe clean the scratch area during the two clean up'); + reporter.log(' actions. This is for facilitating nested test driver execution.'); + if self.aoSubTstDrvs: + reporter.log(' --enable-sub-driver <sub1>[:..]'); + reporter.log(' --disable-sub-driver <sub1>[:..]'); + reporter.log(' Enables or disables one or more of the sub drivers: %s' + % (', '.join([oSubTstDrv.sName for oSubTstDrv in self.aoSubTstDrvs]),)); + return True; + + def parseOption(self, asArgs, iArg): + """ + Parse an option. Override this. + + Keyword arguments: + asArgs -- The argument vector. + iArg -- The index of the current argument. + + Returns iArg if the option was not recognized. + Returns the index of the next argument when something is consumed. + In the event of a syntax error, a InvalidOption or QuietInvalidOption + should be thrown. + """ + + if asArgs[iArg] in ('--help', '-help', '-h', '-?', '/?', '/help', '/H', '-H'): + self.showUsage(); + self.showSubTstDrvUsage(); + raise QuietInvalidOption(); + + # options + if asArgs[iArg] in ('--verbose', '-v'): + reporter.incVerbosity() + elif asArgs[iArg] in ('--debug', '-d'): + reporter.incDebug() + elif asArgs[iArg] == '--no-wipe-clean': + self.fNoWipeClean = True; + elif asArgs[iArg] in ('--enable-sub-driver', '--disable-sub-driver') and self.aoSubTstDrvs: + sOption = asArgs[iArg]; + iArg = self.requireMoreArgs(1, asArgs, iArg); + for sSubTstDrvName in asArgs[iArg].split(':'): + oSubTstDrv = self.findSubTstDrvByShortName(sSubTstDrvName); + if oSubTstDrv is None: + raise InvalidOption('Unknown sub-test driver given to %s: %s' % (sOption, sSubTstDrvName,)); + oSubTstDrv.fEnabled = sOption == '--enable-sub-driver'; + elif (asArgs[iArg] == 'all' or asArgs[iArg] in self.asNormalActions) \ + and self.asActions in self.asSpecialActions: + raise InvalidOption('selected special action "%s" already' % (self.asActions[0], )); + # actions + elif asArgs[iArg] == 'all': + self.asActions = [ 'all' ]; + elif asArgs[iArg] in self.asNormalActions: + self.asActions.append(asArgs[iArg]) + elif asArgs[iArg] in self.asSpecialActions: + if self.asActions: + raise InvalidOption('selected special action "%s" already' % (self.asActions[0], )); + self.asActions = [ asArgs[iArg] ]; + # extact <destination> + if asArgs[iArg] == 'extract': + iArg = iArg + 1; + if iArg >= len(asArgs): raise InvalidOption('The "extract" action requires a destination directory'); + self.sExtractDstPath = asArgs[iArg]; + else: + return iArg; + return iArg + 1; + + def completeOptions(self): + """ + This method is called after parsing all the options. + Returns success indicator. Use the reporter to complain. + + Overriable, call super. + """ + return True; + + def getResourceSet(self): + """ + Returns a set of file and/or directory names relative to + TESTBOX_PATH_RESOURCES. + + Override this, call super when using sub-test drivers. + """ + asRsrcs = []; + for oSubTstDrv in self.aoSubTstDrvs: + asRsrcs.extend(oSubTstDrv.asRsrcs); + return asRsrcs; + + def actionExtract(self): + """ + Handle the action that extracts the test resources for off site use. + Returns a success indicator and error details with the reporter. + + There is usually no need to override this. + """ + fRc = True; + asRsrcs = self.getResourceSet(); + for iRsrc, sRsrc in enumerate(asRsrcs): + reporter.log('Resource #%s: "%s"' % (iRsrc, sRsrc)); + sSrcPath = os.path.normpath(os.path.abspath(os.path.join(self.sResourcePath, sRsrc.replace('/', os.path.sep)))); + sDstPath = os.path.normpath(os.path.join(self.sExtractDstPath, sRsrc.replace('/', os.path.sep))); + + sDstDir = os.path.dirname(sDstPath); + if not os.path.exists(sDstDir): + try: os.makedirs(sDstDir, 0o775); + except: fRc = reporter.errorXcpt('Error creating directory "%s":' % (sDstDir,)); + + if os.path.isfile(sSrcPath): + try: utils.copyFileSimple(sSrcPath, sDstPath); + except: fRc = reporter.errorXcpt('Error copying "%s" to "%s":' % (sSrcPath, sDstPath,)); + elif os.path.isdir(sSrcPath): + fRc = reporter.error('Extracting directories have not been implemented yet'); + else: + fRc = reporter.error('Missing or unsupported resource type: %s' % (sSrcPath,)); + return fRc; + + def actionVerify(self): + """ + Handle the action that verify the test resources. + Returns a success indicator and error details with the reporter. + + There is usually no need to override this. + """ + + asRsrcs = self.getResourceSet(); + for sRsrc in asRsrcs: + # Go thru some pain to catch escape sequences. + if sRsrc.find("//") >= 0: + reporter.error('Double slash test resource name: "%s"' % (sRsrc)); + return False; + if sRsrc == ".." \ + or sRsrc.startswith("../") \ + or sRsrc.find("/../") >= 0 \ + or sRsrc.endswith("/.."): + reporter.error('Relative path in test resource name: "%s"' % (sRsrc)); + return False; + + sFull = os.path.normpath(os.path.abspath(os.path.join(self.sResourcePath, sRsrc))); + if not sFull.startswith(os.path.normpath(self.sResourcePath)): + reporter.error('sFull="%s" self.sResourcePath=%s' % (sFull, self.sResourcePath)); + reporter.error('The resource "%s" seems to specify a relative path' % (sRsrc)); + return False; + + reporter.log2('Checking for resource "%s" at "%s" ...' % (sRsrc, sFull)); + if os.path.isfile(sFull): + try: + oFile = utils.openNoInherit(sFull, "rb"); + oFile.close(); + except Exception as oXcpt: + reporter.error('The file resource "%s" cannot be accessed: %s' % (sFull, oXcpt)); + return False; + elif os.path.isdir(sFull): + if not os.path.isdir(os.path.join(sFull, '.')): + reporter.error('The directory resource "%s" cannot be accessed' % (sFull)); + return False; + elif os.path.exists(sFull): + reporter.error('The resource "%s" is not a file or directory' % (sFull)); + return False; + else: + reporter.error('The resource "%s" was not found' % (sFull)); + return False; + return True; + + def actionConfig(self): + """ + Handle the action that configures the test. + Returns True (success), False (failure) or None (skip the test), + posting complaints and explanations with the reporter. + + Override this. + """ + return True; + + def actionExecute(self): + """ + Handle the action that executes the test. + + Returns True (success), False (failure) or None (skip the test), + posting complaints and explanations with the reporter. + + Override this. + """ + return True; + + def actionCleanupBefore(self): + """ + Handle the action that cleans up spills from previous tests before + starting the tests. This is mostly about wiping the scratch space + clean in local runs. On a testbox the testbox script will use the + cleanup-after if the test is interrupted. + + Returns True (success), False (failure) or None (skip the test), + posting complaints and explanations with the reporter. + + Override this, but call super to wipe the scratch directory. + """ + if self.fNoWipeClean is False: + self.wipeScratch(); + return True; + + def actionCleanupAfter(self): + """ + Handle the action that cleans up all spills from executing the test. + + Returns True (success) or False (failure) posting complaints and + explanations with the reporter. + + Override this, but call super to wipe the scratch directory. + """ + if self.fNoWipeClean is False: + self.wipeScratch(); + return True; + + def actionAbort(self): + """ + Handle the action that aborts a (presumed) running testdriver, making + sure to include all it's children. + + Returns True (success) or False (failure) posting complaints and + explanations with the reporter. + + Override this, but call super to kill the testdriver script and any + other process covered by the testdriver PID file. + """ + + dPids = self.pidFileRead(); + reporter.log('The pid file contained: %s' % (dPids,)); + + # + # Try convince the processes to quit with increasing impoliteness. + # + if sys.platform == 'win32': + afnMethods = [ processInterrupt, processTerminate ]; + else: + afnMethods = [ sendUserSignal1, processInterrupt, processTerminate, processKill ]; + for fnMethod in afnMethods: + for iPid, tNameSudo in dPids.items(): + fnMethod(iPid, fSudo = tNameSudo[1]); + + for i in range(10): + if i > 0: + time.sleep(1); + + dPidsToRemove = []; # Temporary dict to append PIDs to remove later. + + for iPid, tNameSudo in dPids.items(): + if not processExists(iPid): + reporter.log('%s (%s) terminated' % (tNameSudo[0], iPid,)); + self.pidFileRemove(iPid, fQuiet = True); + dPidsToRemove.append(iPid); + continue; + + # Remove PIDs from original dictionary, as removing keys from a + # dictionary while iterating on it won't work and will result in a RuntimeError. + for iPidToRemove in dPidsToRemove: + del dPids[iPidToRemove]; + + if not dPids: + reporter.log('All done.'); + return True; + + if i in [4, 8]: + reporter.log('Still waiting for: %s (method=%s)' % (dPids, fnMethod,)); + + reporter.log('Failed to terminate the following processes: %s' % (dPids,)); + return False; + + + def onExit(self, iRc): + """ + Hook for doing very important cleanups on the way out. + + iRc is the exit code or -1 in the case of an unhandled exception. + Returns nothing and shouldn't raise exceptions (will be muted+ignored). + """ + _ = iRc; + return None; + + + # + # main() - don't override anything! + # + + def main(self, asArgs = None): + """ + The main function of the test driver. + + Keyword arguments: + asArgs -- The argument vector. Defaults to sys.argv. + + Returns exit code. No exceptions. + """ + + # + # Wrap worker in exception handler and always call a 'finally' like + # method to do crucial cleanups on the way out. + # + try: + iRc = self.innerMain(asArgs); + except: + reporter.logXcpt(cFrames = None); + try: + self.onExit(-1); + except: + reporter.logXcpt(); + raise; + self.onExit(iRc); + return iRc; + + + def innerMain(self, asArgs = None): # pylint: disable=too-many-statements + """ + Exception wrapped main() worker. + """ + + # + # Parse the arguments. + # + if asArgs is None: + asArgs = list(sys.argv); + iArg = 1; + try: + while iArg < len(asArgs): + iNext = self.parseOption(asArgs, iArg); + if iNext == iArg: + iNext = self.subTstDrvParseOption(asArgs, iArg); + if iNext == iArg: + raise InvalidOption('unknown option: %s' % (asArgs[iArg])) + iArg = iNext; + except QuietInvalidOption: + return rtexitcode.RTEXITCODE_SYNTAX; + except InvalidOption as oXcpt: + reporter.error(oXcpt.str()); + return rtexitcode.RTEXITCODE_SYNTAX; + except: + reporter.error('unexpected exception while parsing argument #%s' % (iArg)); + traceback.print_exc(); + return rtexitcode.RTEXITCODE_SYNTAX; + + if not self.completeOptions(): + return rtexitcode.RTEXITCODE_SYNTAX; + + if not self.asActions: + reporter.error('no action was specified'); + reporter.error('valid actions: %s' % (self.asNormalActions + self.asSpecialActions + ['all'])); + return rtexitcode.RTEXITCODE_SYNTAX; + + # + # Execte the actions. + # + fRc = True; # Tristate - True (success), False (failure), None (skipped). + asActions = list(self.asActions); # Must copy it or vboxinstaller.py breaks. + if 'extract' in asActions: + reporter.log('*** extract action ***'); + asActions.remove('extract'); + fRc = self.actionExtract(); + reporter.log('*** extract action completed (fRc=%s) ***' % (fRc)); + elif 'abort' in asActions: + reporter.appendToProcessName('/abort'); # Make it easier to spot in the log. + reporter.log('*** abort action ***'); + asActions.remove('abort'); + fRc = self.actionAbort(); + reporter.log('*** abort action completed (fRc=%s) ***' % (fRc)); + else: + if asActions == [ 'all' ]: + asActions = list(self.asNormalActions); + + if 'verify' in asActions: + reporter.log('*** verify action ***'); + asActions.remove('verify'); + fRc = self.actionVerify(); + if fRc is True: reporter.log("verified succeeded"); + else: reporter.log("verified failed (fRc=%s)" % (fRc,)); + reporter.log('*** verify action completed (fRc=%s) ***' % (fRc,)); + + if 'cleanup-before' in asActions: + reporter.log('*** cleanup-before action ***'); + asActions.remove('cleanup-before'); + fRc2 = self.actionCleanupBefore(); + if fRc2 is not True: reporter.log("cleanup-before failed"); + if fRc2 is not True and fRc is True: fRc = fRc2; + reporter.log('*** cleanup-before action completed (fRc2=%s, fRc=%s) ***' % (fRc2, fRc,)); + + self.pidFileAdd(os.getpid(), os.path.basename(sys.argv[0])); + + if 'config' in asActions and fRc is True: + asActions.remove('config'); + reporter.log('*** config action ***'); + fRc = self.actionConfig(); + if fRc is True: reporter.log("config succeeded"); + elif fRc is None: reporter.log("config skipping test"); + else: reporter.log("config failed"); + reporter.log('*** config action completed (fRc=%s) ***' % (fRc,)); + + if 'execute' in asActions and fRc is True: + asActions.remove('execute'); + reporter.log('*** execute action ***'); + fRc = self.actionExecute(); + if fRc is True: reporter.log("execute succeeded"); + elif fRc is None: reporter.log("execute skipping test"); + else: reporter.log("execute failed (fRc=%s)" % (fRc,)); + reporter.testCleanup(); + reporter.log('*** execute action completed (fRc=%s) ***' % (fRc,)); + + if 'cleanup-after' in asActions: + reporter.log('*** cleanup-after action ***'); + asActions.remove('cleanup-after'); + fRc2 = self.actionCleanupAfter(); + if fRc2 is not True: reporter.log("cleanup-after failed"); + if fRc2 is not True and fRc is True: fRc = fRc2; + reporter.log('*** cleanup-after action completed (fRc2=%s, fRc=%s) ***' % (fRc2, fRc,)); + + self.pidFileRemove(os.getpid()); + + if asActions and fRc is True: + reporter.error('unhandled actions: %s' % (asActions,)); + fRc = False; + + # + # Done - report the final result. + # + if fRc is None: + if self.fBadTestbox: + reporter.log('****************************************************************'); + reporter.log('*** The test driver SKIPPED the test because of BAD_TESTBOX. ***'); + reporter.log('****************************************************************'); + return rtexitcode.RTEXITCODE_BAD_TESTBOX; + reporter.log('*****************************************'); + reporter.log('*** The test driver SKIPPED the test. ***'); + reporter.log('*****************************************'); + return rtexitcode.RTEXITCODE_SKIPPED; + if fRc is not True: + reporter.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'); + reporter.error('!!! The test driver FAILED (in case we forgot to mention it). !!!'); + reporter.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'); + return rtexitcode.RTEXITCODE_FAILURE; + reporter.log('*******************************************'); + reporter.log('*** The test driver exits successfully. ***'); + reporter.log('*******************************************'); + return rtexitcode.RTEXITCODE_SUCCESS; + +# The old, deprecated name. +TestDriver = TestDriverBase; # pylint: disable=invalid-name + + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +class TestDriverBaseTestCase(unittest.TestCase): + def setUp(self): + self.oTstDrv = TestDriverBase(); + self.oTstDrv.pidFileDelete(); + + def tearDown(self): + pass; # clean up scratch dir and such. + + def testPidFile(self): + + iPid1 = os.getpid() + 1; + iPid2 = os.getpid() + 2; + + self.assertTrue(self.oTstDrv.pidFileAdd(iPid1, 'test1')); + self.assertEqual(self.oTstDrv.pidFileRead(), {iPid1:('test1',False)}); + + self.assertTrue(self.oTstDrv.pidFileAdd(iPid2, 'test2', fSudo = True)); + self.assertEqual(self.oTstDrv.pidFileRead(), {iPid1:('test1',False), iPid2:('test2',True)}); + + self.assertTrue(self.oTstDrv.pidFileRemove(iPid1)); + self.assertEqual(self.oTstDrv.pidFileRead(), {iPid2:('test2',True)}); + + self.assertTrue(self.oTstDrv.pidFileRemove(iPid2)); + self.assertEqual(self.oTstDrv.pidFileRead(), {}); + + self.assertTrue(self.oTstDrv.pidFileDelete()); + +if __name__ == '__main__': + unittest.main(); + # not reached. diff --git a/src/VBox/ValidationKit/testdriver/btresolver.py b/src/VBox/ValidationKit/testdriver/btresolver.py new file mode 100755 index 00000000..b89d860c --- /dev/null +++ b/src/VBox/ValidationKit/testdriver/btresolver.py @@ -0,0 +1,626 @@ +# -*- coding: utf-8 -*- +# $Id: btresolver.py $ +# pylint: disable=too-many-lines + +""" +Backtrace resolver using external debugging symbols and RTLdrFlt. +""" + +__copyright__ = \ +""" +Copyright (C) 2016-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154739 $" + + +# Standard Python imports. +import os; +import re; +import shutil; +import subprocess; + +# Validation Kit imports. +from common import utils; + +def getRTLdrFltPath(asPaths): + """ + Returns the path to the RTLdrFlt tool looking in the provided paths + or None if not found. + """ + + for sPath in asPaths: + for sDirPath, _, asFiles in os.walk(sPath): + if 'RTLdrFlt' in asFiles: + return os.path.join(sDirPath, 'RTLdrFlt'); + + return None; + + + +class BacktraceResolverOs(object): + """ + Base class for all OS specific resolvers. + """ + + def __init__(self, sScratchPath, sBuildRoot, fnLog = None): + self.sScratchPath = sScratchPath; + self.sBuildRoot = sBuildRoot; + self.fnLog = fnLog; + + def log(self, sText): + """ + Internal logger callback. + """ + if self.fnLog is not None: + self.fnLog(sText); + + + +class BacktraceResolverOsLinux(BacktraceResolverOs): + """ + Linux specific backtrace resolver. + """ + + def __init__(self, sScratchPath, sBuildRoot, fnLog = None): + """ + Constructs a Linux host specific backtrace resolver. + """ + BacktraceResolverOs.__init__(self, sScratchPath, sBuildRoot, fnLog); + + self.asDbgFiles = {}; + + def prepareEnv(self): + """ + Prepares the environment for annotating Linux reports. + """ + fRc = False; + try: + sDbgArchive = os.path.join(self.sBuildRoot, 'bin', 'VirtualBox-dbg.tar.bz2'); + + # Extract debug symbol archive if it was found. + if os.path.exists(sDbgArchive): + asMembers = utils.unpackFile(sDbgArchive, self.sScratchPath, self.fnLog, + self.fnLog); + if asMembers: + # Populate the list of debug files. + for sMember in asMembers: + if os.path.isfile(sMember): + self.asDbgFiles[os.path.basename(sMember)] = sMember; + fRc = True; + except: + self.log('Failed to setup debug symbols'); + + return fRc; + + def cleanupEnv(self): + """ + Cleans up the environment. + """ + fRc = False; + try: + shutil.rmtree(self.sScratchPath, True); + fRc = True; + except: + pass; + + return fRc; + + def getDbgSymPathFromBinary(self, sBinary, sArch): + """ + Returns the path to file containing the debug symbols for the specified binary. + """ + _ = sArch; + sDbgFilePath = None; + try: + sDbgFilePath = self.asDbgFiles[sBinary]; + except: + pass; + + return sDbgFilePath; + + def getBinaryListWithLoadAddrFromReport(self, asReport): + """ + Parses the given VM state report and returns a list of binaries and their + load address. + + Returns a list if tuples containing the binary and load addres or an empty + list on failure. + """ + asListBinaries = []; + + # Look for the line "Mapped address spaces:" + iLine = 0; + while iLine < len(asReport): + if asReport[iLine].startswith('Mapped address spaces:'): + break; + iLine += 1; + + for sLine in asReport[iLine:]: + asCandidate = sLine.split(); + if len(asCandidate) == 5 \ + and asCandidate[0].startswith('0x') \ + and asCandidate[1].startswith('0x') \ + and asCandidate[2].startswith('0x') \ + and (asCandidate[3] == '0x0' or asCandidate[3] == '0')\ + and 'VirtualBox' in asCandidate[4]: + asListBinaries.append((asCandidate[0], os.path.basename(asCandidate[4]))); + + return asListBinaries; + + + +class BacktraceResolverOsDarwin(BacktraceResolverOs): + """ + Darwin specific backtrace resolver. + """ + + def __init__(self, sScratchPath, sBuildRoot, fnLog = None): + """ + Constructs a Linux host specific backtrace resolver. + """ + BacktraceResolverOs.__init__(self, sScratchPath, sBuildRoot, fnLog); + + self.asDbgFiles = {}; + + def prepareEnv(self): + """ + Prepares the environment for annotating Darwin reports. + """ + fRc = False; + try: + # + # Walk the scratch path directory and look for .dSYM directories, building a + # list of them. + # + asDSymPaths = []; + + for sDirPath, asDirs, _ in os.walk(self.sBuildRoot): + for sDir in asDirs: + if sDir.endswith('.dSYM'): + asDSymPaths.append(os.path.join(sDirPath, sDir)); + + # Expand the dSYM paths to full DWARF debug files in the next step + # and add them to the debug files dictionary. + for sDSymPath in asDSymPaths: + sBinary = os.path.basename(sDSymPath).strip('.dSYM'); + self.asDbgFiles[sBinary] = os.path.join(sDSymPath, 'Contents', 'Resources', + 'DWARF', sBinary); + + fRc = True; + except: + self.log('Failed to setup debug symbols'); + + return fRc; + + def cleanupEnv(self): + """ + Cleans up the environment. + """ + fRc = False; + try: + shutil.rmtree(self.sScratchPath, True); + fRc = True; + except: + pass; + + return fRc; + + def getDbgSymPathFromBinary(self, sBinary, sArch): + """ + Returns the path to file containing the debug symbols for the specified binary. + """ + # Hack to exclude executables as RTLdrFlt has some problems with it currently. + _ = sArch; + sDbgSym = None; + try: + sDbgSym = self.asDbgFiles[sBinary]; + except: + pass; + + if sDbgSym is not None and sDbgSym.endswith('.dylib'): + return sDbgSym; + + return None; + + def _getReportVersion(self, asReport): + """ + Returns the version of the darwin report. + """ + # Find the line starting with "Report Version:" + iLine = 0; + iVersion = 0; + while iLine < len(asReport): + if asReport[iLine].startswith('Report Version:'): + break; + iLine += 1; + + if iLine < len(asReport): + # Look for the start of the number + sVersion = asReport[iLine]; + iStartVersion = len('Report Version:'); + iEndVersion = len(sVersion); + + while iStartVersion < len(sVersion) \ + and not sVersion[iStartVersion:iStartVersion+1].isdigit(): + iStartVersion += 1; + + while iEndVersion > 0 \ + and not sVersion[iEndVersion-1:iEndVersion].isdigit(): + iEndVersion -= 1; + + iVersion = int(sVersion[iStartVersion:iEndVersion]); + else: + self.log('Couldn\'t find the report version'); + + return iVersion; + + def _getListOfBinariesFromReportPreSierra(self, asReport): + """ + Returns a list of loaded binaries with their load address obtained from + a pre Sierra report. + """ + asListBinaries = []; + + # Find the line starting with "Binary Images:" + iLine = 0; + while iLine < len(asReport): + if asReport[iLine].startswith('Binary Images:'): + break; + iLine += 1; + + if iLine < len(asReport): + # List starts after that + iLine += 1; + + # A line for a loaded binary looks like the following: + # 0x100042000 - 0x100095fff +VBoxDDU.dylib (4.3.15) <EB19C44D-F882-0803-DBDD-9995723111B7> /Application... + # We need the start address and the library name. + # To distinguish between our own libraries and ones from Apple we check whether the path at the end starts with + # /Applications/VirtualBox.app/Contents/MacOS + oRegExpPath = re.compile(r'/VirtualBox.app/Contents/MacOS'); + oRegExpAddr = re.compile(r'0x\w+'); + oRegExpBinPath = re.compile(r'VirtualBox.app/Contents/MacOS/\S*'); + while iLine < len(asReport): + asMatches = oRegExpPath.findall(asReport[iLine]); + if asMatches: + # Line contains the path, extract start address and path to binary + sAddr = oRegExpAddr.findall(asReport[iLine]); + sPath = oRegExpBinPath.findall(asReport[iLine]); + + if sAddr and sPath: + # Construct the path in into the build cache containing the debug symbols + oRegExp = re.compile(r'\w+\.{0,1}\w*$'); + sFilename = oRegExp.findall(sPath[0]); + + asListBinaries.append((sAddr[0], sFilename[0])); + else: + break; # End of image list + iLine += 1; + else: + self.log('Couldn\'t find the list of loaded binaries in the given report'); + + return asListBinaries; + + def _getListOfBinariesFromReportSierra(self, asReport): + """ + Returns a list of loaded binaries with their load address obtained from + a Sierra+ report. + """ + asListBinaries = []; + + # A line for a loaded binary looks like the following: + # 4 VBoxXPCOMIPCC.dylib 0x00000001139f17ea 0x1139e4000 + 55274 + # We need the start address and the library name. + # To distinguish between our own libraries and ones from Apple we check whether the library + # name contains VBox or VirtualBox + iLine = 0; + while iLine < len(asReport): + asStackTrace = asReport[iLine].split(); + + # Check whether the line is made up of 6 elements separated by whitespace + # and the first one is a number. + if len(asStackTrace) == 6 and asStackTrace[0].isdigit() \ + and (asStackTrace[1].find('VBox') != -1 or asStackTrace[1].find('VirtualBox') != -1) \ + and asStackTrace[3].startswith('0x'): + + # Check whether the library is already in our list an only add new ones + fFound = False; + for _, sLibrary in asListBinaries: + if asStackTrace[1] == sLibrary: + fFound = True; + break; + + if not fFound: + asListBinaries.append((asStackTrace[3], asStackTrace[1])); + iLine += 1; + + return asListBinaries; + + def getBinaryListWithLoadAddrFromReport(self, asReport): + """ + Parses the given VM state report and returns a list of binaries and their + load address. + + Returns a list if tuples containing the binary and load addres or an empty + list on failure. + """ + asListBinaries = []; + + iVersion = self._getReportVersion(asReport); + if iVersion > 0: + if iVersion <= 11: + self.log('Pre Sierra Report'); + asListBinaries = self._getListOfBinariesFromReportPreSierra(asReport); + elif iVersion == 12: + self.log('Sierra report'); + asListBinaries = self._getListOfBinariesFromReportSierra(asReport); + else: + self.log('Unsupported report version %s' % (iVersion, )); + + return asListBinaries; + + + +class BacktraceResolverOsSolaris(BacktraceResolverOs): + """ + Solaris specific backtrace resolver. + """ + + def __init__(self, sScratchPath, sBuildRoot, fnLog = None): + """ + Constructs a Linux host specific backtrace resolver. + """ + BacktraceResolverOs.__init__(self, sScratchPath, sBuildRoot, fnLog); + + self.asDbgFiles = {}; + + def prepareEnv(self): + """ + Prepares the environment for annotating Linux reports. + """ + fRc = False; + try: + sDbgArchive = os.path.join(self.sBuildRoot, 'bin', 'VirtualBoxDebug.tar.bz2'); + + # Extract debug symbol archive if it was found. + if os.path.exists(sDbgArchive): + asMembers = utils.unpackFile(sDbgArchive, self.sScratchPath, self.fnLog, + self.fnLog); + if asMembers: + # Populate the list of debug files. + for sMember in asMembers: + if os.path.isfile(sMember): + sArch = ''; + if 'amd64' in sMember: + sArch = 'amd64'; + else: + sArch = 'x86'; + self.asDbgFiles[os.path.basename(sMember) + '/' + sArch] = sMember; + fRc = True; + else: + self.log('Unpacking the debug archive failed'); + except: + self.log('Failed to setup debug symbols'); + + return fRc; + + def cleanupEnv(self): + """ + Cleans up the environment. + """ + fRc = False; + try: + shutil.rmtree(self.sScratchPath, True); + fRc = True; + except: + pass; + + return fRc; + + def getDbgSymPathFromBinary(self, sBinary, sArch): + """ + Returns the path to file containing the debug symbols for the specified binary. + """ + sDbgFilePath = None; + try: + sDbgFilePath = self.asDbgFiles[sBinary + '/' + sArch]; + except: + pass; + + return sDbgFilePath; + + def getBinaryListWithLoadAddrFromReport(self, asReport): + """ + Parses the given VM state report and returns a list of binaries and their + load address. + + Returns a list if tuples containing the binary and load addres or an empty + list on failure. + """ + asListBinaries = []; + + # Look for the beginning of the process address space mappings" + for sLine in asReport: + asItems = sLine.split(); + if len(asItems) == 4 \ + and asItems[3].startswith('/opt/VirtualBox') \ + and ( asItems[2] == 'r-x--' \ + or asItems[2] == 'r-x----'): + fFound = False; + sBinaryFile = os.path.basename(asItems[3]); + for _, sBinary in asListBinaries: + if sBinary == sBinaryFile: + fFound = True; + break; + if not fFound: + asListBinaries.append(('0x' + asItems[0], sBinaryFile)); + + return asListBinaries; + + + +class BacktraceResolver(object): + """ + A backtrace resolving class. + """ + + def __init__(self, sScratchPath, sBuildRoot, sTargetOs, sArch, sRTLdrFltPath = None, fnLog = None): + """ + Constructs a backtrace resolver object for the given target OS, + architecture and path to the directory containing the debug symbols and tools + we need. + """ + # Initialize all members first. + self.sScratchPath = sScratchPath; + self.sBuildRoot = sBuildRoot; + self.sTargetOs = sTargetOs; + self.sArch = sArch; + self.sRTLdrFltPath = sRTLdrFltPath; + self.fnLog = fnLog; + self.sDbgSymPath = None; + self.oResolverOs = None; + self.sScratchDbgPath = os.path.join(self.sScratchPath, 'dbgsymbols'); + + if self.fnLog is None: + self.fnLog = self.logStub; + + if self.sRTLdrFltPath is None: + self.sRTLdrFltPath = getRTLdrFltPath([self.sScratchPath, self.sBuildRoot]); + if self.sRTLdrFltPath is not None: + self.log('Found RTLdrFlt in %s' % (self.sRTLdrFltPath,)); + else: + self.log('Couldn\'t find RTLdrFlt in either %s or %s' % (self.sScratchPath, self.sBuildRoot)); + + def log(self, sText): + """ + Internal logger callback. + """ + if self.fnLog is not None: + self.fnLog(sText); + + def logStub(self, sText): + """ + Logging stub doing nothing. + """ + _ = sText; + + def prepareEnv(self): + """ + Prepares the environment to annotate backtraces, finding the required tools + and retrieving the debug symbols depending on the host OS. + + Returns True on success and False on error or if not supported. + """ + + # No access to the RTLdrFlt tool means no symbols so no point in trying + # to set something up. + if self.sRTLdrFltPath is None: + return False; + + # Create a directory containing the scratch space for the OS resolver backends. + fRc = True; + if not os.path.exists(self.sScratchDbgPath): + try: + os.makedirs(self.sScratchDbgPath, 0o750); + except: + fRc = False; + self.log('Failed to create scratch directory for debug symbols'); + + if fRc: + if self.sTargetOs == 'linux': + self.oResolverOs = BacktraceResolverOsLinux(self.sScratchDbgPath, self.sScratchPath, self.fnLog); + elif self.sTargetOs == 'darwin': + self.oResolverOs = BacktraceResolverOsDarwin(self.sScratchDbgPath, self.sScratchPath, self.fnLog); # pylint: disable=redefined-variable-type + elif self.sTargetOs == 'solaris': + self.oResolverOs = BacktraceResolverOsSolaris(self.sScratchDbgPath, self.sScratchPath, self.fnLog); # pylint: disable=redefined-variable-type + else: + self.log('The backtrace resolver is not supported on %s' % (self.sTargetOs,)); + fRc = False; + + if fRc: + fRc = self.oResolverOs.prepareEnv(); + if not fRc: + self.oResolverOs = None; + + if not fRc: + shutil.rmtree(self.sScratchDbgPath, True) + + return fRc; + + def cleanupEnv(self): + """ + Prepares the environment to annotate backtraces, finding the required tools + and retrieving the debug symbols depending on the host OS. + + Returns True on success and False on error or if not supported. + """ + fRc = False; + if self.oResolverOs is not None: + fRc = self.oResolverOs.cleanupEnv(); + + shutil.rmtree(self.sScratchDbgPath, True); + return fRc; + + def annotateReport(self, sReport): + """ + Annotates the given report with the previously prepared environment. + + Returns the annotated report on success or None on failure. + """ + sReportAn = None; + + if self.oResolverOs is not None: + asListBinaries = self.oResolverOs.getBinaryListWithLoadAddrFromReport(sReport.split('\n')); + + if asListBinaries: + asArgs = [self.sRTLdrFltPath, ]; + + for sLoadAddr, sBinary in asListBinaries: + sDbgSymPath = self.oResolverOs.getDbgSymPathFromBinary(sBinary, self.sArch); + if sDbgSymPath is not None: + asArgs.append(sDbgSymPath); + asArgs.append(sLoadAddr); + + oRTLdrFltProc = subprocess.Popen(asArgs, stdin=subprocess.PIPE, # pylint: disable=consider-using-with + stdout=subprocess.PIPE, bufsize=0); + if oRTLdrFltProc is not None: + try: + sReportAn, _ = oRTLdrFltProc.communicate(sReport); + except: + self.log('Retrieving annotation report failed (broken pipe / no matching interpreter?)'); + else: + self.log('Error spawning RTLdrFlt process'); + else: + self.log('Getting list of loaded binaries failed'); + else: + self.log('Backtrace resolver not fully initialized, not possible to annotate'); + + return sReportAn; + diff --git a/src/VBox/ValidationKit/testdriver/reporter.py b/src/VBox/ValidationKit/testdriver/reporter.py new file mode 100755 index 00000000..aad672d4 --- /dev/null +++ b/src/VBox/ValidationKit/testdriver/reporter.py @@ -0,0 +1,1984 @@ +# -*- coding: utf-8 -*- +# $Id: reporter.py $ +# pylint: disable=too-many-lines + +""" +Testdriver reporter module. +""" + +from __future__ import print_function; + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153426 $" + + +# Standard Python imports. +import array +import datetime +import errno +import gc +import os +import os.path +import sys +import time +import threading +import traceback + +# Validation Kit imports. +from common import utils; + +## test reporter instance +g_oReporter = None # type: ReporterBase +g_sReporterName = None; + + +class ReporterLock(object): + """ + Work around problem with garbage collection triggering __del__ method with + logging while inside the logger lock and causing a deadlock. + """ + + def __init__(self, sName): + self.sName = sName; + self.oLock = threading.RLock(); + self.oOwner = None; + self.cRecursion = 0; + self.fRestoreGC = False; + + def acquire(self): + """ Acquire the lock. """ + oSelf = threading.current_thread(); + + # Take the lock. + if not self.oLock.acquire(): # pylint: disable=consider-using-with + return False; + + self.oOwner = oSelf; + self.cRecursion += 1; + + # Disable GC to avoid __del__ w/ log statement randomly reenter the logger. + if self.cRecursion == 1: + self.fRestoreGC = gc.isenabled(); + if self.fRestoreGC: + gc.disable(); + + return True; + + def release(self): + """ Release the lock. """ + oSelf = threading.current_thread(); + + # Check the ownership. + if oSelf != self.oOwner: + raise threading.ThreadError(); + + # Drop one recursion. + self.cRecursion -= 1; + if self.cRecursion <= 0: + + # Final recursion. Clear owner and re-enable GC. + self.oOwner = None; + if self.fRestoreGC: + self.fRestoreGC = False; + gc.enable(); + + self.oLock.release(); + +## Reporter lock. +g_oLock = ReporterLock('reporter'); + + + +class PythonLoggingStream(object): + """ + Python logging => testdriver/reporter.py stream. + """ + + def write(self, sText): + """Writes python log message to our stream.""" + if g_oReporter is not None: + sText = sText.rstrip("\r\n"); + #g_oReporter.log(0, 'python: %s' % (sText), utils.getCallerName(), utils.getTimePrefix()); + return True; + + def flush(self): + """Flushes the stream.""" + return True; + + +class ReporterBase(object): + """ + Base class for the reporters. + """ + + def __init__(self): + self.iVerbose = 1; + self.iDebug = 0; + self.cErrors = 0; + self.fTimedOut = False; # Once set, it trickles all the way up. + self.atTests = []; + self.sName = os.path.splitext(os.path.basename(sys.argv[0]))[0]; + + # Hook into the python logging. + import logging; + logging.basicConfig(stream = PythonLoggingStream(), + level = logging.DEBUG, + format = '%(name)-12s %(levelname)-8s %(message)s'); + # + # Introspection and configuration. + # + + def isLocal(self): + """Is this a local reporter?""" + return False; + + def incVerbosity(self): + """Increases the verbosity level.""" + self.iVerbose += 1; + + def incDebug(self): + """Increases the debug level.""" + self.iDebug += 1; + + def getVerbosity(self): + """Returns the current verbosity level.""" + return self.iVerbose; + + def getDebug(self): + """Returns the current debug level.""" + return self.iDebug; + + def appendToProcessName(self, sAppend): + """ + Appends sAppend to the base process name. + Returns the new process name. + """ + self.sName = os.path.splitext(os.path.basename(sys.argv[0]))[0] + sAppend; + return self.sName; + + + # + # Generic logging. + # + + def log(self, iLevel, sText, sCaller, sTsPrf): + """ + Writes the specfied text to the log if iLevel is less or requal + to iVerbose. + """ + _ = iLevel; _ = sText; _ = sCaller; _ = sTsPrf; + return 0; + + # + # XML output from the reporter. + # + + def _xmlEscAttr(self, sValue): + """Escapes an XML attribute value.""" + sValue = sValue.replace('&', '&'); + sValue = sValue.replace('<', '<'); + sValue = sValue.replace('>', '>'); + #sValue = sValue.replace('\'', '''); + sValue = sValue.replace('"', '"'); + sValue = sValue.replace('\n', '
'); + sValue = sValue.replace('\r', '
'); + return sValue; + + def _xmlWrite(self, asText, fIndent = True): + """XML output function for the reporter.""" + _ = asText; _ = fIndent; + return None; + + def xmlFlush(self, fRetry = False, fForce = False): + """Flushes XML output if buffered.""" + _ = fRetry; _ = fForce; + return True; + + # + # XML output from child. + # + + def subXmlStart(self, oFileWrapper): + """Called by the file wrapper when the first bytes are written to the test pipe.""" + _ = oFileWrapper; + return None; + + def subXmlWrite(self, oFileWrapper, sRawXml, sCaller): + """Called by the file wrapper write method for test pipes.""" + return self.log(0, 'raw xml%s: %s' % (oFileWrapper.sPrefix, sRawXml), sCaller, utils.getTimePrefix()); + + def subXmlEnd(self, oFileWrapper): + """Called by the file wrapper __del__ method for test pipes.""" + _ = oFileWrapper; + return None; + + # + # File output. + # + + def addLogFile(self, oSrcFile, sSrcFilename, sAltName, sDescription, sKind, sCaller, sTsPrf): + """ + Adds the file to the report. + Returns True on success, False on failure. + """ + _ = oSrcFile; _ = sSrcFilename; _ = sAltName; _ = sDescription; _ = sKind; _ = sCaller; _ = sTsPrf; + return True; + + def addLogString(self, sLog, sLogName, sDescription, sKind, sCaller, sTsPrf): + """ + Adds the file to the report. + Returns True on success, False on failure. + """ + _ = sLog; _ = sLogName; _ = sDescription; _ = sKind; _ = sCaller; _ = sTsPrf; + return True; + + # + # Test reporting + # + + def _testGetFullName(self): + """ + Mangles the test names in atTest into a single name to make it easier + to spot where we are. + """ + sName = ''; + for t in self.atTests: + if sName != '': + sName += ', '; + sName += t[0]; + return sName; + + def testIncErrors(self): + """Increates the error count.""" + self.cErrors += 1; + return self.cErrors; + + def testSetTimedOut(self): + """Sets time out indicator for the current test and increases the error counter.""" + self.fTimedOut = True; + self.cErrors += 1; + return None; + + def testStart(self, sName, sCaller): + """ Starts a new test, may be nested. """ + (sTsPrf, sTsIso) = utils.getTimePrefixAndIsoTimestamp(); + self._xmlWrite([ '<Test timestamp="%s" name="%s">' % (sTsIso, self._xmlEscAttr(sName),), ]); + self.atTests.append((sName, self.cErrors, self.fTimedOut)); + self.fTimedOut = False; + return self.log(1, ' %-50s: TESTING' % (self._testGetFullName()), sCaller, sTsPrf); + + def testValue(self, sName, sValue, sUnit, sCaller): + """ Reports a benchmark value or something simiarlly useful. """ + (sTsPrf, sTsIso) = utils.getTimePrefixAndIsoTimestamp(); + self._xmlWrite([ '<Value timestamp="%s" name="%s" unit="%s" value="%s"/>' + % (sTsIso, self._xmlEscAttr(sName), self._xmlEscAttr(sUnit), self._xmlEscAttr(sValue)), ]); + return self.log(0, '** %-48s: %12s %s' % (sName, sValue, sUnit), sCaller, sTsPrf); + + def testFailure(self, sDetails, sCaller): + """ Reports a failure. """ + (sTsPrf, sTsIso) = utils.getTimePrefixAndIsoTimestamp(); + self.cErrors = self.cErrors + 1; + self._xmlWrite([ '<FailureDetails timestamp="%s" text="%s"/>' % (sTsIso, self._xmlEscAttr(sDetails),), ]); + return self.log(0, sDetails, sCaller, sTsPrf); + + def testDone(self, fSkipped, sCaller): + """ + Marks the current test as DONE, pops it and maks the next test on the + stack current. + Returns (name, errors). + """ + (sTsPrf, sTsIso) = utils.getTimePrefixAndIsoTimestamp(); + sFullName = self._testGetFullName(); + + # safe pop + if not self.atTests: + self.log(0, 'testDone on empty test stack!', sCaller, sTsPrf); + return ('internal error', 0); + fTimedOut = self.fTimedOut; + sName, cErrorsStart, self.fTimedOut = self.atTests.pop(); + + # log + xml. + cErrors = self.cErrors - cErrorsStart; + if cErrors == 0: + if fSkipped is not True: + self._xmlWrite([ ' <Passed timestamp="%s"/>' % (sTsIso,), '</Test>' ],); + self.log(1, '** %-50s: PASSED' % (sFullName,), sCaller, sTsPrf); + else: + self._xmlWrite([ ' <Skipped timestamp="%s"/>' % (sTsIso,), '</Test>' ]); + self.log(1, '** %-50s: SKIPPED' % (sFullName,), sCaller, sTsPrf); + elif fTimedOut: + self._xmlWrite([ ' <TimedOut timestamp="%s" errors="%d"/>' % (sTsIso, cErrors), '</Test>' ]); + self.log(0, '** %-50s: TIMED-OUT - %d errors' % (sFullName, cErrors), sCaller, sTsPrf); + else: + self._xmlWrite([ ' <Failed timestamp="%s" errors="%d"/>' % (sTsIso, cErrors), '</Test>' ]); + self.log(0, '** %-50s: FAILED - %d errors' % (sFullName, cErrors), sCaller, sTsPrf); + + # Flush buffers when reaching the last test. + if not self.atTests: + self.xmlFlush(fRetry = True); + + return (sName, cErrors); + + def testErrorCount(self): + """ + Returns the number of errors accumulated by the current test. + """ + cTests = len(self.atTests); + if cTests <= 0: + return self.cErrors; + return self.cErrors - self.atTests[cTests - 1][1]; + + def testCleanup(self, sCaller): + """ + Closes all open test as failed. + Returns True if no open tests, False if there were open tests. + """ + if not self.atTests: + return True; + for _ in range(len(self.atTests)): + self.testFailure('Test not closed by test drver', sCaller) + self.testDone(False, sCaller); + return False; + + # + # Misc. + # + + def doPollWork(self, sDebug = None): + """ + Check if any pending stuff expired and needs doing. + """ + _ = sDebug; + return None; + + + + +class LocalReporter(ReporterBase): + """ + Local reporter instance. + """ + + def __init__(self): + ReporterBase.__init__(self); + self.oLogFile = None; + self.oXmlFile = None; + self.fXmlOk = True; + self.iSubXml = 0; + self.iOtherFile = 0; + self.fnGetIsoTimestamp = utils.getIsoTimestamp; # Hack to get a timestamp in __del__. + self.oStdErr = sys.stderr; # Hack for __del__ output. + + # + # Figure the main log directory. + # + try: + self.sDefLogDir = os.path.abspath(os.path.expanduser(os.path.join('~', 'VBoxTestLogs'))); + except: + self.sDefLogDir = os.path.abspath("VBoxTestLogs"); + try: + sLogDir = os.path.abspath(os.environ.get('TESTBOX_REPORTER_LOG_DIR', self.sDefLogDir)); + if not os.path.isdir(sLogDir): + os.makedirs(sLogDir, 0o750); + except: + sLogDir = self.sDefLogDir; + if not os.path.isdir(sLogDir): + os.makedirs(sLogDir, 0o750); + + # + # Make a subdirectory for this test run. + # + sTs = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H-%M-%S.log'); + self.sLogDir = sLogDir = os.path.join(sLogDir, '%s-%s' % (sTs, self.sName)); + try: + os.makedirs(self.sLogDir, 0o750); + except: + self.sLogDir = '%s-%s' % (self.sLogDir, os.getpid()); + os.makedirs(self.sLogDir, 0o750); + + # + # Open the log file and write a header. + # + sLogName = os.path.join(self.sLogDir, 'testsuite.log'); + sTsIso = utils.getIsoTimestamp(); + if sys.version_info[0] >= 3: # Add 'b' to prevent write taking issue with encode('utf-8') not returning a string. + self.oLogFile = utils.openNoInherit(sLogName, "wb"); + else: + self.oLogFile = utils.openNoInherit(sLogName, "w"); + self.oLogFile.write(('Created log file at %s.\nRunning: %s' % (sTsIso, sys.argv)).encode('utf-8')); + + # + # Open the xml log file and write the mandatory introduction. + # + # Note! This is done here and not in the base class because the remote + # logger doesn't really need this. It doesn't need the outer + # test wrapper either. + # + sXmlName = os.path.join(self.sLogDir, 'testsuite.xml'); + if sys.version_info[0] >= 3: # Add 'b' to prevent write taking issue with encode('utf-8') not returning a string. + self.oXmlFile = utils.openNoInherit(sXmlName, "wb"); + else: + self.oXmlFile = utils.openNoInherit(sXmlName, "w"); + self._xmlWrite([ '<?xml version="1.0" encoding="UTF-8" ?>', + '<Test timestamp="%s" name="%s">' % (sTsIso, self._xmlEscAttr(self.sName),), ], + fIndent = False); + + def __del__(self): + """Ends and completes the log files.""" + try: sTsIso = self.fnGetIsoTimestamp(); + except Exception as oXcpt: + sTsIso = str(oXcpt); + + if self.oLogFile is not None: + try: + self.oLogFile.write(('\nThe End %s\n' % (sTsIso,)).encode('utf-8')); + self.oLogFile.close(); + except: pass; + self.oLogFile = None; + + if self.oXmlFile is not None: + self._closeXml(sTsIso); + self.oXmlFile = None; + + def _closeXml(self, sTsIso): + """Closes the XML file.""" + if self.oXmlFile is not None: + # pop the test stack + while self.atTests: + sName, cErrorsStart, self.fTimedOut = self.atTests.pop(); + self._xmlWrite([ '<End timestamp="%s" errors="%d"/>' % (sTsIso, self.cErrors - cErrorsStart,), + '</%s>' % (sName,), ]); + + # The outer one is not on the stack. + self._xmlWrite([ ' <End timestamp="%s"/>' % (sTsIso,), + '</Test>', ], fIndent = False); + try: + self.oXmlFile.close(); + self.oXmlFile = None; + except: + pass; + + def _xmlWrite(self, asText, fIndent = True): + """Writes to the XML file.""" + for sText in asText: + if fIndent: + sIndent = ''.ljust((len(self.atTests) + 1) * 2); + sText = sIndent + sText; + sText += '\n'; + + try: + self.oXmlFile.write(sText.encode('utf-8')); + except: + if self.fXmlOk: + traceback.print_exc(); + self.fXmlOk = False; + return False; + return True; + + # + # Overridden methods. + # + + def isLocal(self): + """Is this a local reporter?""" + return True; + + def log(self, iLevel, sText, sCaller, sTsPrf): + if iLevel <= self.iVerbose: + # format it. + if self.iDebug <= 0: + sLogText = '%s %s' % (sTsPrf, sText); + elif self.iDebug <= 1: + sLogText = '%s %30s: %s' % (sTsPrf, sCaller, sText); + else: + sLogText = '%s e=%u %30s: %s' % (sTsPrf, self.cErrors, sCaller, sText); + + # output it. + if sys.version_info[0] >= 3: + sAscii = sLogText; + else: + sAscii = sLogText.encode('ascii', 'replace'); + if self.iDebug == 0: + print('%s: %s' % (self.sName, sAscii), file = self.oStdErr); + else: + print('%s' % (sAscii), file = self.oStdErr); + sLogText += '\n'; + try: + self.oLogFile.write(sLogText.encode('utf-8')); + except: + pass; + return 0; + + def addLogFile(self, oSrcFile, sSrcFilename, sAltName, sDescription, sKind, sCaller, sTsPrf): + # Figure the destination filename. + iOtherFile = self.iOtherFile; + self.iOtherFile += 1; + sDstFilename = os.path.join(self.sLogDir, 'other-%d-%s.log' \ + % (iOtherFile, os.path.splitext(os.path.basename(sSrcFilename))[0])); + self.log(0, '** Other log file: %s - %s (%s)' % (sDstFilename, sDescription, sSrcFilename), sCaller, sTsPrf); + + # Open the destination file and copy over the data. + fRc = True; + try: + oDstFile = utils.openNoInherit(sDstFilename, 'wb'); + except Exception as oXcpt: + self.log(0, 'error opening %s: %s' % (sDstFilename, oXcpt), sCaller, sTsPrf); + else: + while True: + try: + abBuf = oSrcFile.read(65536); + except Exception as oXcpt: + fRc = False; + self.log(0, 'error reading %s: %s' % (sSrcFilename, oXcpt), sCaller, sTsPrf); + else: + try: + oDstFile.write(abBuf); + except Exception as oXcpt: + fRc = False; + self.log(0, 'error writing %s: %s' % (sDstFilename, oXcpt), sCaller, sTsPrf); + else: + if abBuf: + continue; + break; + oDstFile.close(); + + # Leave a mark in the XML log. + self._xmlWrite(['<LogFile timestamp="%s" filename="%s" source="%s" kind="%s" ok="%s">%s</LogFile>\n' + % (utils.getIsoTimestamp(), self._xmlEscAttr(os.path.basename(sDstFilename)), self._xmlEscAttr(sSrcFilename), \ + self._xmlEscAttr(sKind), fRc, self._xmlEscAttr(sDescription))] ); + _ = sAltName; + return fRc; + + def addLogString(self, sLog, sLogName, sDescription, sKind, sCaller, sTsPrf): + # Figure the destination filename. + iOtherFile = self.iOtherFile; + self.iOtherFile += 1; + sDstFilename = os.path.join(self.sLogDir, 'other-%d-%s.log' \ + % (iOtherFile, os.path.splitext(os.path.basename(sLogName))[0])); + self.log(0, '** Other log file: %s - %s (%s)' % (sDstFilename, sDescription, sLogName), sCaller, sTsPrf); + + # Open the destination file and copy over the data. + fRc = True; + try: + oDstFile = utils.openNoInherit(sDstFilename, 'w'); + except Exception as oXcpt: + self.log(0, 'error opening %s: %s' % (sDstFilename, oXcpt), sCaller, sTsPrf); + else: + try: + oDstFile.write(sLog); + except Exception as oXcpt: + fRc = False; + self.log(0, 'error writing %s: %s' % (sDstFilename, oXcpt), sCaller, sTsPrf); + + oDstFile.close(); + + # Leave a mark in the XML log. + self._xmlWrite(['<LogFile timestamp="%s" filename="%s" source="%s" kind="%s" ok="%s">%s</LogFile>\n' + % (utils.getIsoTimestamp(), self._xmlEscAttr(os.path.basename(sDstFilename)), self._xmlEscAttr(sLogName), \ + self._xmlEscAttr(sKind), fRc, self._xmlEscAttr(sDescription))] ); + return fRc; + + def subXmlStart(self, oFileWrapper): + # Open a new file and just include it from the main XML. + iSubXml = self.iSubXml; + self.iSubXml += 1; + sSubXmlName = os.path.join(self.sLogDir, 'sub-%d.xml' % (iSubXml,)); + try: + oFileWrapper.oSubXmlFile = utils.openNoInherit(sSubXmlName, "w"); + except: + errorXcpt('open(%s)' % oFileWrapper.oSubXmlName); + oFileWrapper.oSubXmlFile = None; + else: + self._xmlWrite(['<Include timestamp="%s" filename="%s"/>\n' + % (utils.getIsoTimestamp(), self._xmlEscAttr(os.path.basename(sSubXmlName)))]); + return None; + + def subXmlWrite(self, oFileWrapper, sRawXml, sCaller): + if oFileWrapper.oSubXmlFile is not None: + try: + oFileWrapper.oSubXmlFile.write(sRawXml); + except: + pass; + if sCaller is None: pass; # pychecker - NOREF + return None; + + def subXmlEnd(self, oFileWrapper): + if oFileWrapper.oSubXmlFile is not None: + try: + oFileWrapper.oSubXmlFile.close(); + oFileWrapper.oSubXmlFile = None; + except: + pass; + return None; + + + +class RemoteReporter(ReporterBase): + """ + Reporter that talks to the test manager server. + """ + + + ## The XML sync min time (seconds). + kcSecXmlFlushMin = 30; + ## The XML sync max time (seconds). + kcSecXmlFlushMax = 120; + ## The XML sync idle time before flushing (seconds). + kcSecXmlFlushIdle = 5; + ## The XML sync line count threshold. + kcLinesXmlFlush = 512; + + ## The retry timeout. + kcSecTestManagerRetryTimeout = 120; + ## The request timeout. + kcSecTestManagerRequestTimeout = 30; + + + def __init__(self): + ReporterBase.__init__(self); + self.sTestManagerUrl = os.environ.get('TESTBOX_MANAGER_URL'); + self.sTestBoxUuid = os.environ.get('TESTBOX_UUID'); + self.idTestBox = int(os.environ.get('TESTBOX_ID')); + self.idTestSet = int(os.environ.get('TESTBOX_TEST_SET_ID')); + self._asXml = []; + self._secTsXmlFlush = utils.timestampSecond(); + self._secTsXmlLast = self._secTsXmlFlush; + self._fXmlFlushing = False; + self.oOutput = sys.stdout; # Hack for __del__ output. + self.fFlushEachLine = True; + self.fDebugXml = 'TESTDRIVER_REPORTER_DEBUG_XML' in os.environ; + + # Prepare the TM connecting. + from common import constants; + if sys.version_info[0] >= 3: + import urllib; + self._fnUrlEncode = urllib.parse.urlencode; # pylint: disable=no-member + self._fnUrlParseQs = urllib.parse.parse_qs; # pylint: disable=no-member + self._oParsedTmUrl = urllib.parse.urlparse(self.sTestManagerUrl); # pylint: disable=no-member + import http.client as httplib; # pylint: disable=no-name-in-module,import-error + else: + import urllib; + self._fnUrlEncode = urllib.urlencode; # pylint: disable=no-member + import urlparse; # pylint: disable=import-error + self._fnUrlParseQs = urlparse.parse_qs; # pylint: disable=no-member + self._oParsedTmUrl = urlparse.urlparse(self.sTestManagerUrl); # pylint: disable=no-member + import httplib; # pylint: disable=no-name-in-module,import-error + + if sys.version_info[0] >= 3 \ + or (sys.version_info[0] == 2 and sys.version_info[1] >= 6): + if self._oParsedTmUrl.scheme == 'https': # pylint: disable=no-member + self._fnTmConnect = lambda: httplib.HTTPSConnection(self._oParsedTmUrl.hostname, + timeout = self.kcSecTestManagerRequestTimeout); + else: + self._fnTmConnect = lambda: httplib.HTTPConnection( self._oParsedTmUrl.hostname, + timeout = self.kcSecTestManagerRequestTimeout); + else: + if self._oParsedTmUrl.scheme == 'https': # pylint: disable=no-member + self._fnTmConnect = lambda: httplib.HTTPSConnection(self._oParsedTmUrl.hostname); + else: + self._fnTmConnect = lambda: httplib.HTTPConnection( self._oParsedTmUrl.hostname); + self._dHttpHeader = \ + { + 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', + 'User-Agent': 'TestDriverReporter/%s.0 (%s, %s)' % (__version__, utils.getHostOs(), utils.getHostArch(),), + 'Accept': 'text/plain,application/x-www-form-urlencoded', + 'Accept-Encoding': 'identity', + 'Cache-Control': 'max-age=0', + #'Connection': 'keep-alive', + }; + + dParams = { + constants.tbreq.ALL_PARAM_TESTBOX_UUID: self.sTestBoxUuid, + constants.tbreq.ALL_PARAM_TESTBOX_ID: self.idTestBox, + constants.tbreq.RESULT_PARAM_TEST_SET_ID: self.idTestSet, + }; + self._sTmServerPath = '/%s/testboxdisp.py?%s' \ + % ( self._oParsedTmUrl.path.strip('/'), # pylint: disable=no-member + self._fnUrlEncode(dParams), ); + + def __del__(self): + """Flush pending log messages?""" + if self._asXml: + self._xmlDoFlush(self._asXml, fRetry = True, fDtor = True); + + def _writeOutput(self, sText): + """ Does the actual writing and flushing. """ + if sys.version_info[0] >= 3: + print(sText, file = self.oOutput); + else: + print(sText.encode('ascii', 'replace'), file = self.oOutput); + if self.fFlushEachLine: self.oOutput.flush(); + return None; + + # + # Talking to TM. + # + + def _processTmStatusResponse(self, oConn, sOperation, fClose = True): + """ + Processes HTTP reponse from the test manager. + Returns True, False or None. None should be retried, the others not. + May raise exception on HTTP issue (retry ok). + """ + if sys.version_info[0] >= 3: import http.client as httplib; # pylint: disable=no-name-in-module,import-error + else: import httplib; # pylint: disable=import-error + from common import constants; + + # Read the response and (optionally) close the connection. + oResponse = oConn.getresponse(); + try: + sRspBody = oResponse.read(); + except httplib.IncompleteRead as oXcpt: + self._writeOutput('%s: %s: Warning: httplib.IncompleteRead: %s [expected %s, got %s]' + % (utils.getTimePrefix(), sOperation, oXcpt, oXcpt.expected, len(oXcpt.partial),)); + sRspBody = oXcpt.partial; + if fClose is True: + try: oConn.close(); + except: pass; + + # Make sure it's a string which encoding we grok. + if hasattr(sRspBody, 'decode'): + sRspBody = sRspBody.decode('utf-8', 'ignore'); + + # Check the content type. + sContentType = oResponse.getheader('Content-Type'); + if sContentType is not None and sContentType == 'application/x-www-form-urlencoded; charset=utf-8': + + # Parse the body and check the RESULT parameter. + dResponse = self._fnUrlParseQs(sRspBody, strict_parsing = True); + sResult = dResponse.get(constants.tbresp.ALL_PARAM_RESULT, None); + if isinstance(sResult, list): + sResult = sResult[0] if len(sResult) == 1 else '%d results' % (len(sResult),); + + if sResult is not None: + if sResult == constants.tbresp.STATUS_ACK: + return True; + if sResult == constants.tbresp.STATUS_NACK: + self._writeOutput('%s: %s: Failed (%s). (dResponse=%s)' + % (utils.getTimePrefix(), sOperation, sResult, dResponse,)); + return False; + + self._writeOutput('%s: %s: Failed - dResponse=%s' % (utils.getTimePrefix(), sOperation, dResponse,)); + else: + self._writeOutput('%s: %s: Unexpected Content-Type: %s' % (utils.getTimePrefix(), sOperation, sContentType,)); + self._writeOutput('%s: %s: Body: %s' % (utils.getTimePrefix(), sOperation, sRspBody,)); + return None; + + def _doUploadFile(self, oSrcFile, sSrcFilename, sDescription, sKind, sMime): + """ Uploads the given file to the test manager. """ + + # Prepare header and url. + dHeader = dict(self._dHttpHeader); + dHeader['Content-Type'] = 'application/octet-stream'; + self._writeOutput('%s: _doUploadFile: sHeader=%s' % (utils.getTimePrefix(), dHeader,)); + oSrcFile.seek(0, 2); + cbFileSize = oSrcFile.tell(); + self._writeOutput('%s: _doUploadFile: size=%d' % (utils.getTimePrefix(), cbFileSize,)); + oSrcFile.seek(0); + + if cbFileSize <= 0: # The Test Manager will bitch if the file size is 0, so skip uploading. + self._writeOutput('%s: _doUploadFile: Empty file, skipping upload' % utils.getTimePrefix()); + return False; + + from common import constants; + sUrl = self._sTmServerPath + '&' \ + + self._fnUrlEncode({ constants.tbreq.UPLOAD_PARAM_NAME: os.path.basename(sSrcFilename), + constants.tbreq.UPLOAD_PARAM_DESC: sDescription, + constants.tbreq.UPLOAD_PARAM_KIND: sKind, + constants.tbreq.UPLOAD_PARAM_MIME: sMime, + constants.tbreq.ALL_PARAM_ACTION: constants.tbreq.UPLOAD, + }); + + # Retry loop. + secStart = utils.timestampSecond(); + while True: + try: + oConn = self._fnTmConnect(); + oConn.request('POST', sUrl, oSrcFile.read(), dHeader); + fRc = self._processTmStatusResponse(oConn, '_doUploadFile', fClose = True); + oConn.close(); + if fRc is not None: + return fRc; + except: + logXcpt('warning: exception during UPLOAD request'); + + if utils.timestampSecond() - secStart >= self.kcSecTestManagerRetryTimeout: + self._writeOutput('%s: _doUploadFile: Timed out.' % (utils.getTimePrefix(),)); + break; + try: oSrcFile.seek(0); + except: + logXcpt(); + break; + self._writeOutput('%s: _doUploadFile: Retrying...' % (utils.getTimePrefix(), )); + time.sleep(2); + + return False; + + def _doUploadString(self, sSrc, sSrcName, sDescription, sKind, sMime): + """ Uploads the given string as a separate file to the test manager. """ + + # Prepare header and url. + dHeader = dict(self._dHttpHeader); + dHeader['Content-Type'] = 'application/octet-stream'; + self._writeOutput('%s: _doUploadString: sHeader=%s' % (utils.getTimePrefix(), dHeader,)); + self._writeOutput('%s: _doUploadString: size=%d' % (utils.getTimePrefix(), sys.getsizeof(sSrc),)); + + from common import constants; + sUrl = self._sTmServerPath + '&' \ + + self._fnUrlEncode({ constants.tbreq.UPLOAD_PARAM_NAME: os.path.basename(sSrcName), + constants.tbreq.UPLOAD_PARAM_DESC: sDescription, + constants.tbreq.UPLOAD_PARAM_KIND: sKind, + constants.tbreq.UPLOAD_PARAM_MIME: sMime, + constants.tbreq.ALL_PARAM_ACTION: constants.tbreq.UPLOAD, + }); + + # Retry loop. + secStart = utils.timestampSecond(); + while True: + try: + oConn = self._fnTmConnect(); + oConn.request('POST', sUrl, sSrc, dHeader); + fRc = self._processTmStatusResponse(oConn, '_doUploadString', fClose = True); + oConn.close(); + if fRc is not None: + return fRc; + except: + logXcpt('warning: exception during UPLOAD request'); + + if utils.timestampSecond() - secStart >= self.kcSecTestManagerRetryTimeout: + self._writeOutput('%s: _doUploadString: Timed out.' % (utils.getTimePrefix(),)); + break; + self._writeOutput('%s: _doUploadString: Retrying...' % (utils.getTimePrefix(), )); + time.sleep(2); + + return False; + + def _xmlDoFlush(self, asXml, fRetry = False, fDtor = False): + """ + The code that does the actual talking to the server. + Used by both xmlFlush and __del__. + """ + secStart = utils.timestampSecond(); + while True: + fRc = None; + try: + # Post. + from common import constants; + sPostBody = self._fnUrlEncode({constants.tbreq.XML_RESULT_PARAM_BODY: '\n'.join(asXml),}); + oConn = self._fnTmConnect(); + oConn.request('POST', + self._sTmServerPath + ('&%s=%s' % (constants.tbreq.ALL_PARAM_ACTION, constants.tbreq.XML_RESULTS)), + sPostBody, + self._dHttpHeader); + + fRc = self._processTmStatusResponse(oConn, '_xmlDoFlush', fClose = True); + if fRc is True: + if self.fDebugXml: + self._writeOutput('_xmlDoFlush:\n%s' % ('\n'.join(asXml),)); + return (None, False); + if fRc is False: + self._writeOutput('_xmlDoFlush: Failed - we should abort the test, really.'); + return (None, True); + except Exception as oXcpt: + if not fDtor: + logXcpt('warning: exception during XML_RESULTS request'); + else: + self._writeOutput('warning: exception during XML_RESULTS request: %s' % (oXcpt,)); + + if fRetry is not True \ + or utils.timestampSecond() - secStart >= self.kcSecTestManagerRetryTimeout: + break; + time.sleep(2); + + return (asXml, False); + + + # + # Overridden methods. + # + + def isLocal(self): + return False; + + def log(self, iLevel, sText, sCaller, sTsPrf): + if iLevel <= self.iVerbose: + if self.iDebug <= 0: + sLogText = '%s %s' % (sTsPrf, sText); + elif self.iDebug <= 1: + sLogText = '%s %30s: %s' % (sTsPrf, sCaller, sText); + else: + sLogText = '%s e=%u %30s: %s' % (sTsPrf, self.cErrors, sCaller, sText); + self._writeOutput(sLogText); + return 0; + + def addLogFile(self, oSrcFile, sSrcFilename, sAltName, sDescription, sKind, sCaller, sTsPrf): + fRc = True; + if sKind in [ 'text', 'log', 'process'] \ + or sKind.startswith('log/') \ + or sKind.startswith('info/') \ + or sKind.startswith('process/'): + self.log(0, '*** Uploading "%s" - KIND: "%s" - DESC: "%s" ***' + % (sSrcFilename, sKind, sDescription), sCaller, sTsPrf); + self.xmlFlush(); + g_oLock.release(); + try: + self._doUploadFile(oSrcFile, sAltName, sDescription, sKind, 'text/plain'); + finally: + g_oLock.acquire(); + elif sKind.startswith('screenshot/'): + self.log(0, '*** Uploading "%s" - KIND: "%s" - DESC: "%s" ***' + % (sSrcFilename, sKind, sDescription), sCaller, sTsPrf); + self.xmlFlush(); + g_oLock.release(); + try: + self._doUploadFile(oSrcFile, sAltName, sDescription, sKind, 'image/png'); + finally: + g_oLock.acquire(); + elif sKind.startswith('screenrecording/'): + self.log(0, '*** Uploading "%s" - KIND: "%s" - DESC: "%s" ***' + % (sSrcFilename, sKind, sDescription), sCaller, sTsPrf); + self.xmlFlush(); + g_oLock.release(); + try: + self._doUploadFile(oSrcFile, sAltName, sDescription, sKind, 'video/webm'); + finally: + g_oLock.acquire(); + elif sKind.startswith('misc/'): + self.log(0, '*** Uploading "%s" - KIND: "%s" - DESC: "%s" ***' + % (sSrcFilename, sKind, sDescription), sCaller, sTsPrf); + self.xmlFlush(); + g_oLock.release(); + try: + self._doUploadFile(oSrcFile, sAltName, sDescription, sKind, 'application/octet-stream'); + finally: + g_oLock.acquire(); + else: + self.log(0, '*** UNKNOWN FILE "%s" - KIND "%s" - DESC "%s" ***' + % (sSrcFilename, sKind, sDescription), sCaller, sTsPrf); + return fRc; + + def addLogString(self, sLog, sLogName, sDescription, sKind, sCaller, sTsPrf): + fRc = True; + if sKind in [ 'text', 'log', 'process'] \ + or sKind.startswith('log/') \ + or sKind.startswith('info/') \ + or sKind.startswith('process/'): + self.log(0, '*** Uploading "%s" - KIND: "%s" - DESC: "%s" ***' + % (sLogName, sKind, sDescription), sCaller, sTsPrf); + self.xmlFlush(); + g_oLock.release(); + try: + self._doUploadString(sLog, sLogName, sDescription, sKind, 'text/plain'); + finally: + g_oLock.acquire(); + else: + self.log(0, '*** UNKNOWN FILE "%s" - KIND "%s" - DESC "%s" ***' + % (sLogName, sKind, sDescription), sCaller, sTsPrf); + return fRc; + + def xmlFlush(self, fRetry = False, fForce = False): + """ + Flushes the XML back log. Called with the lock held, may leave it + while communicating with the server. + """ + if not self._fXmlFlushing: + asXml = self._asXml; + self._asXml = []; + if asXml or fForce is True: + self._fXmlFlushing = True; + + g_oLock.release(); + try: + (asXml, fIncErrors) = self._xmlDoFlush(asXml, fRetry = fRetry); + finally: + g_oLock.acquire(); + + if fIncErrors: + self.testIncErrors(); + + self._fXmlFlushing = False; + if asXml is None: + self._secTsXmlFlush = utils.timestampSecond(); + else: + self._asXml = asXml + self._asXml; + return True; + + self._secTsXmlFlush = utils.timestampSecond(); + return False; + + def _xmlFlushIfNecessary(self, fPolling = False, sDebug = None): + """Flushes the XML back log if necessary.""" + tsNow = utils.timestampSecond(); + cSecs = tsNow - self._secTsXmlFlush; + cSecsLast = tsNow - self._secTsXmlLast; + if fPolling is not True: + self._secTsXmlLast = tsNow; + + # Absolute flush thresholds. + if cSecs >= self.kcSecXmlFlushMax: + return self.xmlFlush(); + if len(self._asXml) >= self.kcLinesXmlFlush: + return self.xmlFlush(); + + # Flush if idle long enough. + if cSecs >= self.kcSecXmlFlushMin \ + and cSecsLast >= self.kcSecXmlFlushIdle: + return self.xmlFlush(); + + _ = sDebug; + return False; + + def _xmlWrite(self, asText, fIndent = True): + """XML output function for the reporter.""" + self._asXml += asText; + self._xmlFlushIfNecessary(); + _ = fIndent; # No pretty printing, thank you. + return None; + + def subXmlStart(self, oFileWrapper): + oFileWrapper.sXmlBuffer = ''; + return None; + + def subXmlWrite(self, oFileWrapper, sRawXml, sCaller): + oFileWrapper.sXmlBuffer += sRawXml; + _ = sCaller; + return None; + + def subXmlEnd(self, oFileWrapper): + sRawXml = oFileWrapper.sXmlBuffer; + ## @todo should validate the document here and maybe auto terminate things. Adding some hints to have the server do + # this instead. + g_oLock.acquire(); + try: + self._asXml += [ '<PushHint testdepth="%d"/>' % (len(self.atTests),), + sRawXml, + '<PopHint testdepth="%d"/>' % (len(self.atTests),),]; + self._xmlFlushIfNecessary(); + finally: + g_oLock.release(); + return None; + + def doPollWork(self, sDebug = None): + if self._asXml: + g_oLock.acquire(); + try: + self._xmlFlushIfNecessary(fPolling = True, sDebug = sDebug); + finally: + g_oLock.release(); + return None; + + +# +# Helpers +# + +g_fnComXcptFormatter = None; + +def setComXcptFormatter(fnCallback): + """ + Install callback for prettier COM exception formatting. + + The callback replaces the work done by format_exception_only() and + takes the same arguments. It returns None if not interested in the + exception. + """ + global g_fnComXcptFormatter; + g_fnComXcptFormatter = fnCallback; + return True; + +def formatExceptionOnly(oType, oXcpt, sCaller, sTsPrf): + """ + Wrapper around traceback.format_exception_only and __g_fnComXcptFormatter. + """ + #asRet = ['oType=%s type(oXcpt)=%s' % (oType, type(oXcpt),)]; + asRet = []; + + # Try the callback first. + fnCallback = g_fnComXcptFormatter; + if fnCallback: + try: + asRetCb = fnCallback(oType, oXcpt); + if asRetCb: + return asRetCb; + #asRet += asRetCb; + except: + g_oReporter.log(0, '** internal-error: Hit exception #2 in __g_fnComXcptFormatter! %s' + % (traceback.format_exc()), sCaller, sTsPrf); + asRet += ['internal error: exception in __g_fnComXcptFormatter']; + + # Now try format_exception_only: + try: + asRet += traceback.format_exception_only(oType, oXcpt); + except: + g_oReporter.log(0, '** internal-error: Hit exception #2 in format_exception_only! %s' + % (traceback.format_exc()), sCaller, sTsPrf); + asRet += ['internal error: Exception in format_exception_only!']; + return asRet; + + +def logXcptWorker(iLevel, fIncErrors, sPrefix="", sText=None, cFrames=1): + """ + Log an exception, optionally with a preceeding message and more than one + call frame. + """ + g_oLock.acquire(); + try: + + if fIncErrors: + g_oReporter.testIncErrors(); + + ## @todo skip all this if iLevel is too high! + + # Try get exception info. + sTsPrf = utils.getTimePrefix(); + try: + oType, oValue, oTraceback = sys.exc_info(); + except: + oType = oValue = oTraceback = None; + if oType is not None: + + # Try format the info + try: + rc = 0; + sCaller = utils.getCallerName(oTraceback.tb_frame); + if sText is not None: + rc = g_oReporter.log(iLevel, "%s%s" % (sPrefix, sText), sCaller, sTsPrf); + asInfo = None; + try: + asInfo = formatExceptionOnly(oType, oValue, sCaller, sTsPrf); + atEntries = traceback.extract_tb(oTraceback); + atEntries.reverse(); + if cFrames is not None and cFrames <= 1: + if atEntries: + asInfo = asInfo + traceback.format_list(atEntries[:1]); + else: + asInfo.append('Traceback (stack order):') + if cFrames is not None and cFrames < len(atEntries): + asInfo = asInfo + traceback.format_list(atEntries[:cFrames]); + else: + asInfo = asInfo + traceback.format_list(atEntries); + asInfo.append('Stack:') + asInfo = asInfo + traceback.format_stack(oTraceback.tb_frame.f_back, cFrames); + except: + g_oReporter.log(0, '** internal-error: Hit exception #2! %s' % (traceback.format_exc()), sCaller, sTsPrf); + + if asInfo: + # Do the logging. + for sItem in asInfo: + asLines = sItem.splitlines(); + for sLine in asLines: + rc = g_oReporter.log(iLevel, '%s%s' % (sPrefix, sLine), sCaller, sTsPrf); + + else: + g_oReporter.log(iLevel, 'No exception info...', sCaller, sTsPrf); + rc = -3; + except: + g_oReporter.log(0, '** internal-error: Hit exception! %s' % (traceback.format_exc()), None, sTsPrf); + rc = -2; + else: + g_oReporter.log(0, '** internal-error: No exception! %s' + % (utils.getCallerName(iFrame=3)), utils.getCallerName(iFrame=3), sTsPrf); + rc = -1; + + finally: + g_oLock.release(); + return rc; + + +# +# The public Classes +# +class FileWrapper(object): + """ File like class for TXS EXEC and similar. """ + def __init__(self, sPrefix): + self.sPrefix = sPrefix; + + def __del__(self): + self.close(); + + def close(self): + """ file.close """ + # Nothing to be done. + return; + + def read(self, cb): + """file.read""" + _ = cb; + return ""; + + def write(self, sText): + """file.write""" + if not utils.isString(sText): + if isinstance(sText, array.array): + try: + if sys.version_info < (3, 9, 0): + # Removed since Python 3.9. + sText = sText.tostring(); # pylint: disable=no-member + else: + sText = sText.tobytes(); + except: + pass; + if hasattr(sText, 'decode'): + try: + sText = sText.decode('utf-8', 'ignore'); + except: + pass; + g_oLock.acquire(); + try: + sTsPrf = utils.getTimePrefix(); + sCaller = utils.getCallerName(); + asLines = sText.splitlines(); + for sLine in asLines: + g_oReporter.log(0, '%s: %s' % (self.sPrefix, sLine), sCaller, sTsPrf); + except: + traceback.print_exc(); + finally: + g_oLock.release(); + return None; + +class FileWrapperTestPipe(object): + """ + File like class for the test pipe (TXS EXEC and similar). + + This is also used to submit XML test result files. + """ + def __init__(self): + self.sPrefix = ''; + self.fStarted = False; + self.fClosed = False; + self.sTagBuffer = None; + self.cTestDepth = 0; + self.acTestErrors = []; + + def __del__(self): + self.close(); + + def close(self): + """ file.close """ + if self.fStarted is True and self.fClosed is False: + self.fClosed = True; + + # Close open <Test> elements: + if self.cTestDepth > 0: + sNow = utils.getIsoTimestamp() + cErrors = 0; + while self.cTestDepth > 0: + self.cTestDepth -= 1; + if self.acTestErrors: + cErrors += self.acTestErrors.pop(); + cErrors += 1; + g_oReporter.subXmlWrite(self, + '\n%s <Failed timestamp="%s" errors="%s"/>\n%s</Test>\n' + % (' ' * self.cTestDepth, sNow, cErrors, ' ' * self.cTestDepth), + utils.getCallerName()); + + # Tell the reporter that the XML input is done. + try: g_oReporter.subXmlEnd(self); + except: + try: traceback.print_exc(); + except: pass; + return True; + + def read(self, cb = None): + """file.read""" + _ = cb; + return ""; + + def write(self, sText): + """file.write""" + # lazy start. + if self.fStarted is not True: + try: + g_oReporter.subXmlStart(self); + except: + traceback.print_exc(); + self.fStarted = True; + + # Turn non-string stuff into strings. + if not utils.isString(sText): + if isinstance(sText, array.array): + try: + if sys.version_info < (3, 9, 0): + # Removed since Python 3.9. + sText = sText.tostring(); # pylint: disable=no-member + else: + sText = sText.tobytes(); + except: + pass; + if hasattr(sText, 'decode'): + try: sText = sText.decode('utf-8', 'ignore'); + except: pass; + + try: + # + # Write the XML to the reporter. + # + g_oReporter.subXmlWrite(self, sText, utils.getCallerName()); + + # + # Parse the supplied text and look for <Failed.../> tags to keep track of the + # error counter. This is only a very lazy aproach. + # + idxText = 0; + while sText: + if self.sTagBuffer is None: + # Look for the start of a tag. + idxStart = sText.find('<', idxText); + if idxStart != -1: + # If the end was found inside the current buffer, parse the line, + # otherwise we have to save it for later. + idxEnd = sText.find('>', idxStart); + if idxEnd != -1: + self._processXmlElement(sText[idxStart:idxEnd+1]); + idxText = idxEnd; + else: + self.sTagBuffer = sText[idxStart:]; + break; + else: + break; + else: + # Search for the end of the tag and parse the whole tag. + assert(idxText == 0); + idxEnd = sText.find('>'); + if idxEnd != -1: + self._processXmlElement(self.sTagBuffer + sText[:idxEnd+1]); + self.sTagBuffer = None; + idxText = idxEnd; + else: + self.sTagBuffer = self.sTagBuffer + sText[idxText:]; + break; + except: + traceback.print_exc(); + return None; + + def _processXmlElement(self, sElement): + """ + Processes a complete XML tag. + + We handle the 'Failed' tag to keep track of the error counter. + We also track 'Test' tags to make sure we close with all of them properly closed. + """ + # Make sure we don't parse any space between < and the element name. + sElement = sElement.strip(); + + # Find the end of the name + idxEndName = sElement.find(' '); + if idxEndName == -1: + idxEndName = sElement.find('>'); + if idxEndName >= 0: + if sElement[idxEndName - 1] == '/': + idxEndName -= 1; + else: + idxEndName = len(sElement); + sElementName = sElement[1:idxEndName]; + + # <Failed>: + if sElementName == 'Failed': + g_oLock.acquire(); + try: + g_oReporter.testIncErrors(); + finally: + g_oLock.release(); + if self.acTestErrors: + self.acTestErrors[-1] += 1; # get errors attrib + # <Test> + elif sElementName == 'Test': + self.cTestDepth += 1; + self.acTestErrors.append(0); + # </Test> + elif sElementName == '/Test': + self.cTestDepth -= 1; + if self.acTestErrors: + cErrors = self.acTestErrors.pop(); + if self.acTestErrors: + self.acTestErrors[-1] += cErrors; + + +# +# The public APIs. +# + +def log(sText, sCaller = None): + """Writes the specfied text to the log.""" + g_oLock.acquire(); + try: + rc = g_oReporter.log(1, sText, sCaller if sCaller else utils.getCallerName(), utils.getTimePrefix()); + except: + rc = -1; + finally: + g_oLock.release(); + return rc; + +def logXcpt(sText=None, cFrames=1): + """ + Log an exception, optionally with a preceeding message and more than one + call frame. + """ + return logXcptWorker(1, False, "", sText, cFrames); + +def log2(sText, sCaller = None): + """Log level 2: Writes the specfied text to the log.""" + g_oLock.acquire(); + try: + rc = g_oReporter.log(2, sText, sCaller if sCaller else utils.getCallerName(), utils.getTimePrefix()); + except: + rc = -1; + finally: + g_oLock.release(); + return rc; + +def log2Xcpt(sText=None, cFrames=1): + """ + Log level 2: Log an exception, optionally with a preceeding message and + more than one call frame. + """ + return logXcptWorker(2, False, "", sText, cFrames); + +def log3(sText, sCaller = None): + """Log level 3: Writes the specfied text to the log.""" + g_oLock.acquire(); + try: + rc = g_oReporter.log(3, sText, sCaller if sCaller else utils.getCallerName(), utils.getTimePrefix()); + except: + rc = -1; + finally: + g_oLock.release(); + return rc; + +def log3Xcpt(sText=None, cFrames=1): + """ + Log level 3: Log an exception, optionally with a preceeding message and + more than one call frame. + """ + return logXcptWorker(3, False, "", sText, cFrames); + +def log4(sText, sCaller = None): + """Log level 4: Writes the specfied text to the log.""" + g_oLock.acquire(); + try: + rc = g_oReporter.log(4, sText, sCaller if sCaller else utils.getCallerName(), utils.getTimePrefix()); + except: + rc = -1; + finally: + g_oLock.release(); + return rc; + +def log4Xcpt(sText=None, cFrames=1): + """ + Log level 4: Log an exception, optionally with a preceeding message and + more than one call frame. + """ + return logXcptWorker(4, False, "", sText, cFrames); + +def log5(sText, sCaller = None): + """Log level 2: Writes the specfied text to the log.""" + g_oLock.acquire(); + try: + rc = g_oReporter.log(5, sText, sCaller if sCaller else utils.getCallerName(), utils.getTimePrefix()); + except: + rc = -1; + finally: + g_oLock.release(); + return rc; + +def log5Xcpt(sText=None, cFrames=1): + """ + Log level 5: Log an exception, optionally with a preceeding message and + more than one call frame. + """ + return logXcptWorker(5, False, "", sText, cFrames); + +def log6(sText, sCaller = None): + """Log level 6: Writes the specfied text to the log.""" + g_oLock.acquire(); + try: + rc = g_oReporter.log(6, sText, sCaller if sCaller else utils.getCallerName(), utils.getTimePrefix()); + except: + rc = -1; + finally: + g_oLock.release(); + return rc; + +def log6Xcpt(sText=None, cFrames=1): + """ + Log level 6: Log an exception, optionally with a preceeding message and + more than one call frame. + """ + return logXcptWorker(6, False, "", sText, cFrames); + +def maybeErr(fIsError, sText): + """ Maybe error or maybe normal log entry. """ + if fIsError is True: + return error(sText, sCaller = utils.getCallerName()); + return log(sText, sCaller = utils.getCallerName()); + +def maybeErrXcpt(fIsError, sText=None, cFrames=1): + """ Maybe error or maybe normal log exception entry. """ + if fIsError is True: + return errorXcpt(sText, cFrames); + return logXcpt(sText, cFrames); + +def maybeLog(fIsNotError, sText): + """ Maybe error or maybe normal log entry. """ + if fIsNotError is not True: + return error(sText, sCaller = utils.getCallerName()); + return log(sText, sCaller = utils.getCallerName()); + +def maybeLogXcpt(fIsNotError, sText=None, cFrames=1): + """ Maybe error or maybe normal log exception entry. """ + if fIsNotError is not True: + return errorXcpt(sText, cFrames); + return logXcpt(sText, cFrames); + +def error(sText, sCaller = None): + """ + Writes the specfied error message to the log. + + This will add an error to the current test. + + Always returns False for the convenience of methods returning boolean + success indicators. + """ + g_oLock.acquire(); + try: + g_oReporter.testIncErrors(); + g_oReporter.log(0, '** error: %s' % (sText), sCaller if sCaller else utils.getCallerName(), utils.getTimePrefix()); + except: + pass; + finally: + g_oLock.release(); + return False; + +def errorXcpt(sText=None, cFrames=1): + """ + Log an error caused by an exception. If sText is given, it will preceed + the exception information. cFrames can be used to display more stack. + + This will add an error to the current test. + + Always returns False for the convenience of methods returning boolean + success indicators. + """ + logXcptWorker(0, True, '** error: ', sText, cFrames); + return False; + +def errorTimeout(sText): + """ + Flags the current test as having timed out and writes the specified message to the log. + + This will add an error to the current test. + + Always returns False for the convenience of methods returning boolean + success indicators. + """ + g_oLock.acquire(); + try: + g_oReporter.testSetTimedOut(); + g_oReporter.log(0, '** timeout-error: %s' % (sText), utils.getCallerName(), utils.getTimePrefix()); + except: + pass; + finally: + g_oLock.release(); + return False; + +def fatal(sText): + """ + Writes a fatal error to the log. + + This will add an error to the current test. + + Always returns False for the convenience of methods returning boolean + success indicators. + """ + g_oLock.acquire(); + try: + g_oReporter.testIncErrors(); + g_oReporter.log(0, '** fatal error: %s' % (sText), utils.getCallerName(), utils.getTimePrefix()); + except: + pass + finally: + g_oLock.release(); + return False; + +def fatalXcpt(sText=None, cFrames=1): + """ + Log a fatal error caused by an exception. If sText is given, it will + preceed the exception information. cFrames can be used to display more + stack. + + This will add an error to the current test. + + Always returns False for the convenience of methods returning boolean + success indicators. + """ + logXcptWorker(0, True, "** fatal error: ", sText, cFrames); + return False; + +def addLogFile(sFilename, sKind, sDescription = '', sAltName = None): + """ + Adds the specified log file to the report if the file exists. + + The sDescription is a free form description of the log file. + + The sKind parameter is for adding some machine parsable hint what kind of + log file this really is. + + Returns True on success, False on failure (no ENOENT errors are logged). + """ + sTsPrf = utils.getTimePrefix(); + sCaller = utils.getCallerName(); + fRc = False; + if sAltName is None: + sAltName = sFilename; + + try: + oSrcFile = utils.openNoInherit(sFilename, 'rb'); + except IOError as oXcpt: + if oXcpt.errno != errno.ENOENT: + logXcpt('addLogFile(%s,%s,%s)' % (sFilename, sDescription, sKind)); + else: + logXcpt('addLogFile(%s,%s,%s) IOError' % (sFilename, sDescription, sKind)); + except: + logXcpt('addLogFile(%s,%s,%s)' % (sFilename, sDescription, sKind)); + else: + g_oLock.acquire(); + try: + fRc = g_oReporter.addLogFile(oSrcFile, sFilename, sAltName, sDescription, sKind, sCaller, sTsPrf); + finally: + g_oLock.release(); + oSrcFile.close(); + return fRc; + +def addLogString(sLog, sLogName, sKind, sDescription = ''): + """ + Adds the specified log string to the report. + + The sLog parameter sets the name of the log file. + + The sDescription is a free form description of the log file. + + The sKind parameter is for adding some machine parsable hint what kind of + log file this really is. + + Returns True on success, False on failure (no ENOENT errors are logged). + """ + sTsPrf = utils.getTimePrefix(); + sCaller = utils.getCallerName(); + fRc = False; + + g_oLock.acquire(); + try: + fRc = g_oReporter.addLogString(sLog, sLogName, sDescription, sKind, sCaller, sTsPrf); + finally: + g_oLock.release(); + return fRc; + +def isLocal(): + """Is this a local reporter?""" + return g_oReporter.isLocal() + +def incVerbosity(): + """Increases the verbosity level.""" + return g_oReporter.incVerbosity() + +def incDebug(): + """Increases the debug level.""" + return g_oReporter.incDebug() + +def getVerbosity(): + """Returns the current verbosity level.""" + return g_oReporter.getVerbosity() + +def getDebug(): + """Returns the current debug level.""" + return g_oReporter.getDebug() + +def appendToProcessName(sAppend): + """ + Appends sAppend to the base process name. + Returns the new process name. + """ + return g_oReporter.appendToProcessName(sAppend); + +def getErrorCount(): + """ + Get the current error count for the entire test run. + """ + g_oLock.acquire(); + try: + cErrors = g_oReporter.cErrors; + finally: + g_oLock.release(); + return cErrors; + +def doPollWork(sDebug = None): + """ + This can be called from wait loops and similar to make the reporter call + home with pending XML and such. + """ + g_oReporter.doPollWork(sDebug); + return None; + + +# +# Test reporting, a bit similar to RTTestI*. +# + +def testStart(sName): + """ + Starts a new test (pushes it). + """ + g_oLock.acquire(); + try: + rc = g_oReporter.testStart(sName, utils.getCallerName()); + finally: + g_oLock.release(); + return rc; + +def testValue(sName, sValue, sUnit): + """ + Reports a benchmark value or something simiarlly useful. + """ + g_oLock.acquire(); + try: + rc = g_oReporter.testValue(sName, str(sValue), sUnit, utils.getCallerName()); + finally: + g_oLock.release(); + return rc; + +def testFailure(sDetails): + """ + Reports a failure. + We count these calls and testDone will use them to report PASSED or FAILED. + + Returns False so that a return False line can be saved. + """ + g_oLock.acquire(); + try: + g_oReporter.testFailure(sDetails, utils.getCallerName()); + finally: + g_oLock.release(); + return False; + +def testFailureXcpt(sDetails = ''): + """ + Reports a failure with exception. + We count these calls and testDone will use them to report PASSED or FAILED. + + Returns False so that a return False line can be saved. + """ + # Extract exception info. + try: + oType, oValue, oTraceback = sys.exc_info(); + except: + oType = oValue, oTraceback = None; + if oType is not None: + sCaller = utils.getCallerName(oTraceback.tb_frame); + sXcpt = ' '.join(formatExceptionOnly(oType, oValue, sCaller, utils.getTimePrefix())); + else: + sCaller = utils.getCallerName(); + sXcpt = 'No exception at %s' % (sCaller,); + + # Use testFailure to do the work. + g_oLock.acquire(); + try: + if sDetails == '': + g_oReporter.testFailure('Exception: %s' % (sXcpt,), sCaller); + else: + g_oReporter.testFailure('%s: %s' % (sDetails, sXcpt), sCaller); + finally: + g_oLock.release(); + return False; + +def testDone(fSkipped = False): + """ + Completes the current test (pops it), logging PASSED / FAILURE. + + Returns a tuple with the name of the test and its error count. + """ + g_oLock.acquire(); + try: + rc = g_oReporter.testDone(fSkipped, utils.getCallerName()); + finally: + g_oLock.release(); + return rc; + +def testErrorCount(): + """ + Gets the error count of the current test. + + Returns the number of errors. + """ + g_oLock.acquire(); + try: + cErrors = g_oReporter.testErrorCount(); + finally: + g_oLock.release(); + return cErrors; + +def testCleanup(): + """ + Closes all open tests with a generic error condition. + + Returns True if no open tests, False if something had to be closed with failure. + """ + g_oLock.acquire(); + try: + fRc = g_oReporter.testCleanup(utils.getCallerName()); + g_oReporter.xmlFlush(fRetry = False, fForce = True); + finally: + g_oLock.release(); + fRc = False; + return fRc; + + +# +# Sub XML stuff. +# + +def addSubXmlFile(sFilename): + """ + Adds a sub-xml result file to the party. + """ + fRc = False; + try: + oSrcFile = utils.openNoInherit(sFilename, 'r'); + except IOError as oXcpt: + if oXcpt.errno != errno.ENOENT: + logXcpt('addSubXmlFile(%s)' % (sFilename,)); + except: + logXcpt('addSubXmlFile(%s)' % (sFilename,)); + else: + try: + oWrapper = FileWrapperTestPipe() + oWrapper.write(oSrcFile.read()); + oWrapper.close(); + except: + logXcpt('addSubXmlFile(%s)' % (sFilename,)); + oSrcFile.close(); + + return fRc; + + +# +# Other useful debugging tools. +# + +def logAllStacks(cFrames = None): + """ + Logs the stacks of all python threads. + """ + sTsPrf = utils.getTimePrefix(); + sCaller = utils.getCallerName(); + g_oLock.acquire(); + + cThread = 0; + for idThread, oStack in sys._current_frames().items(): # >=2.5, a bit ugly - pylint: disable=protected-access + try: + if cThread > 0: + g_oReporter.log(1, '', sCaller, sTsPrf); + g_oReporter.log(1, 'Thread %s (%#x)' % (idThread, idThread), sCaller, sTsPrf); + try: + asInfo = traceback.format_stack(oStack, cFrames); + except: + g_oReporter.log(1, ' Stack formatting failed w/ exception', sCaller, sTsPrf); + else: + for sInfo in asInfo: + asLines = sInfo.splitlines(); + for sLine in asLines: + g_oReporter.log(1, sLine, sCaller, sTsPrf); + except: + pass; + cThread += 1; + + g_oLock.release(); + return None; + +def checkTestManagerConnection(): + """ + Checks the connection to the test manager. + + Returns True if the connection is fine, False if not, None if not remote + reporter. + + Note! This as the sideeffect of flushing XML. + """ + g_oLock.acquire(); + try: + fRc = g_oReporter.xmlFlush(fRetry = False, fForce = True); + finally: + g_oLock.release(); + fRc = False; + return fRc; + +def flushall(fSkipXml = False): + """ + Flushes all output streams, both standard and logger related. + This may also push data to the remote test manager. + """ + try: sys.stdout.flush(); + except: pass; + try: sys.stderr.flush(); + except: pass; + + if fSkipXml is not True: + g_oLock.acquire(); + try: + g_oReporter.xmlFlush(fRetry = False); + finally: + g_oLock.release(); + + return True; + + +# +# Module initialization. +# + +def _InitReporterModule(): + """ + Instantiate the test reporter. + """ + global g_oReporter, g_sReporterName + + g_sReporterName = os.getenv("TESTBOX_REPORTER", "local"); + if g_sReporterName == "local": + g_oReporter = LocalReporter(); + elif g_sReporterName == "remote": + g_oReporter = RemoteReporter(); # Correct, but still plain stupid. pylint: disable=redefined-variable-type + else: + print(os.path.basename(__file__) + ": Unknown TESTBOX_REPORTER value: '" + g_sReporterName + "'", file = sys.stderr); + raise Exception("Unknown TESTBOX_REPORTER value '" + g_sReporterName + "'"); + +if __name__ != "checker": # pychecker avoidance. + _InitReporterModule(); diff --git a/src/VBox/ValidationKit/testdriver/testfileset.py b/src/VBox/ValidationKit/testdriver/testfileset.py new file mode 100755 index 00000000..3aa3b0ce --- /dev/null +++ b/src/VBox/ValidationKit/testdriver/testfileset.py @@ -0,0 +1,690 @@ +# -*- coding: utf-8 -*- +# $Id: testfileset.py $ +# pylint: disable=too-many-lines + +""" +Test File Set +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154442 $" + + +# Standard Python imports. +import os; +import random; +import string; +import sys; +import tarfile; +import unittest; + +# Validation Kit imports. +from common import utils; +from common import pathutils; +from testdriver import reporter; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + xrange = range; # pylint: disable=redefined-builtin,invalid-name + + + +class TestFsObj(object): + """ A file system object we created in for test purposes. """ + def __init__(self, oParent, sPath, sName = None): + self.oParent = oParent # type: TestDir + self.sPath = sPath # type: str + self.sName = sName # type: str + if oParent: + assert sPath.startswith(oParent.sPath); + assert sName is None; + self.sName = sPath[len(oParent.sPath) + 1:]; + # Add to parent. + oParent.aoChildren.append(self); + oParent.dChildrenUpper[self.sName.upper()] = self; + + def buildPath(self, sRoot, sSep): + """ + Build the path from sRoot using sSep. + + This is handy for getting the path to an object in a different context + (OS, path) than what it was generated for. + """ + if self.oParent: + return self.oParent.buildPath(sRoot, sSep) + sSep + self.sName; + return sRoot + sSep + self.sName; + + +class TestFile(TestFsObj): + """ A file object in the guest. """ + def __init__(self, oParent, sPath, abContent): + TestFsObj.__init__(self, oParent, sPath); + self.abContent = abContent # type: bytearray + self.cbContent = len(abContent); + self.off = 0; + + def read(self, cbToRead): + """ read() emulation. """ + assert self.off <= self.cbContent; + cbLeft = self.cbContent - self.off; + if cbLeft < cbToRead: + cbToRead = cbLeft; + abRet = self.abContent[self.off:(self.off + cbToRead)]; + assert len(abRet) == cbToRead; + self.off += cbToRead; + if sys.version_info[0] < 3: + return bytes(abRet); + return abRet; + + def equalFile(self, oFile): + """ Compares the content of oFile with self.abContent. """ + + # Check the size first. + try: + cbFile = os.fstat(oFile.fileno()).st_size; + except: + return reporter.errorXcpt(); + if cbFile != self.cbContent: + return reporter.error('file size differs: %s, cbContent=%s' % (cbFile, self.cbContent)); + + # Compare the bytes next. + offFile = 0; + try: + oFile.seek(offFile); + except: + return reporter.error('seek error'); + while offFile < self.cbContent: + cbToRead = self.cbContent - offFile; + if cbToRead > 256*1024: + cbToRead = 256*1024; + try: + abRead = oFile.read(cbToRead); + except: + return reporter.error('read error at offset %s' % (offFile,)); + cbRead = len(abRead); + if cbRead == 0: + return reporter.error('premature end of file at offset %s' % (offFile,)); + if not utils.areBytesEqual(abRead, self.abContent[offFile:(offFile + cbRead)]): + return reporter.error('%s byte block at offset %s differs' % (cbRead, offFile,)); + # Advance: + offFile += cbRead; + + return True; + + @staticmethod + def hexFormatBytes(abBuf): + """ Formats a buffer/string/whatever as a string of hex bytes """ + if sys.version_info[0] >= 3: + if utils.isString(abBuf): + try: abBuf = bytes(abBuf, 'utf-8'); + except: pass; + else: + if utils.isString(abBuf): + try: abBuf = bytearray(abBuf, 'utf-8'); # pylint: disable=redefined-variable-type + except: pass; + sRet = ''; + off = 0; + for off, bByte in enumerate(abBuf): + if off > 0: + sRet += ' ' if off & 7 else '-'; + if isinstance(bByte, int): + sRet += '%02x' % (bByte,); + else: + sRet += '%02x' % (ord(bByte),); + return sRet; + + def checkRange(self, cbRange, offFile = 0): + """ Check if the specified range is entirely within the file or not. """ + if offFile >= self.cbContent: + return reporter.error('buffer @ %s LB %s is beyond the end of the file (%s bytes)!' + % (offFile, cbRange, self.cbContent,)); + if offFile + cbRange > self.cbContent: + return reporter.error('buffer @ %s LB %s is partially beyond the end of the file (%s bytes)!' + % (offFile, cbRange, self.cbContent,)); + return True; + + def equalMemory(self, abBuf, offFile = 0): + """ + Compares the content of the given buffer with the file content at that + file offset. + + Returns True if it matches, False + error logging if it does not match. + """ + if not abBuf: + return True; + + if not self.checkRange(len(abBuf), offFile): + return False; + + if sys.version_info[0] >= 3: + if utils.areBytesEqual(abBuf, self.abContent[offFile:(offFile + len(abBuf))]): + return True; + else: + if utils.areBytesEqual(abBuf, buffer(self.abContent, offFile, len(abBuf))): # pylint: disable=undefined-variable + return True; + + reporter.error('mismatch with buffer @ %s LB %s (cbContent=%s)!' % (offFile, len(abBuf), self.cbContent,)); + reporter.error(' type(abBuf): %s' % (type(abBuf),)); + #if isinstance(abBuf, memoryview): + # reporter.error(' nbytes=%s len=%s itemsize=%s type(obj)=%s' + # % (abBuf.nbytes, len(abBuf), abBuf.itemsize, type(abBuf.obj),)); + reporter.error('type(abContent): %s' % (type(self.abContent),)); + + offBuf = 0; + cbLeft = len(abBuf); + while cbLeft > 0: + cbLine = min(16, cbLeft); + abBuf1 = abBuf[offBuf:(offBuf + cbLine)]; + abBuf2 = self.abContent[offFile:(offFile + cbLine)]; + if not utils.areBytesEqual(abBuf1, abBuf2): + try: sStr1 = self.hexFormatBytes(abBuf1); + except: sStr1 = 'oops'; + try: sStr2 = self.hexFormatBytes(abBuf2); + except: sStr2 = 'oops'; + reporter.log('%#10x: %s' % (offBuf, sStr1,)); + reporter.log('%#10x: %s' % (offFile, sStr2,)); + + # Advance. + offBuf += 16; + offFile += 16; + cbLeft -= 16; + + return False; + + +class TestFileZeroFilled(TestFile): + """ + Zero filled test file. + """ + + def __init__(self, oParent, sPath, cbContent): + TestFile.__init__(self, oParent, sPath, bytearray(1)); + self.cbContent = cbContent; + + def read(self, cbToRead): + """ read() emulation. """ + assert self.off <= self.cbContent; + cbLeft = self.cbContent - self.off; + if cbLeft < cbToRead: + cbToRead = cbLeft; + abRet = bytearray(cbToRead); + assert len(abRet) == cbToRead; + self.off += cbToRead; + if sys.version_info[0] < 3: + return bytes(abRet); + return abRet; + + def equalFile(self, oFile): + _ = oFile; + assert False, "not implemented"; + return False; + + def equalMemory(self, abBuf, offFile = 0): + if not abBuf: + return True; + + if not self.checkRange(len(abBuf), offFile): + return False; + + if utils.areBytesEqual(abBuf, bytearray(len(abBuf))): + return True; + + cErrors = 0; + offBuf = 0 + while offBuf < len(abBuf): + bByte = abBuf[offBuf]; + if not isinstance(bByte, int): + bByte = ord(bByte); + if bByte != 0: + reporter.error('Mismatch @ %s/%s: %#x, expected 0!' % (offFile, offBuf, bByte,)); + cErrors += 1; + if cErrors > 32: + return False; + offBuf += 1; + return cErrors == 0; + + +class TestDir(TestFsObj): + """ A file object in the guest. """ + def __init__(self, oParent, sPath, sName = None): + TestFsObj.__init__(self, oParent, sPath, sName); + self.aoChildren = [] # type: list(TestFsObj) + self.dChildrenUpper = {} # type: dict(str, TestFsObj) + + def contains(self, sName): + """ Checks if the directory contains the given name. """ + return sName.upper() in self.dChildrenUpper + + +class TestFileSet(object): + """ + A generated set of files and directories for use in a test. + + Can be wrapped up into a tarball or written directly to the file system. + """ + + ksReservedWinOS2 = '/\\"*:<>?|\t\v\n\r\f\a\b'; + ksReservedUnix = '/'; + ksReservedTrailingWinOS2 = ' .'; + ksReservedTrailingUnix = ''; + + ## @name Path style. + ## @{ + + ## @} + + def __init__(self, fDosStyle, sBasePath, sSubDir, # pylint: disable=too-many-arguments + asCompatibleWith = None, # List of getHostOs values to the names must be compatible with. + oRngFileSizes = xrange(0, 16384), + oRngManyFiles = xrange(128, 512), + oRngTreeFiles = xrange(128, 384), + oRngTreeDepth = xrange(92, 256), + oRngTreeDirs = xrange(2, 16), + cchMaxPath = 230, + cchMaxName = 230, + uSeed = None): + ## @name Parameters + ## @{ + self.fDosStyle = fDosStyle; + self.sMinStyle = 'win' if fDosStyle else 'linux'; + if asCompatibleWith is not None: + for sOs in asCompatibleWith: + assert sOs in ('win', 'os2', 'darwin', 'linux', 'solaris', 'cross'), sOs; + if 'os2' in asCompatibleWith: + self.sMinStyle = 'os2'; + elif 'win' in asCompatibleWith: + self.sMinStyle = 'win'; + # 'cross' marks a lowest common denominator for all supported platforms. + # Used for Guest Control testing. + elif 'cross' in asCompatibleWith: + self.sMinStyle = 'cross'; + self.sBasePath = sBasePath; + self.sSubDir = sSubDir; + self.oRngFileSizes = oRngFileSizes; + self.oRngManyFiles = oRngManyFiles; + self.oRngTreeFiles = oRngTreeFiles; + self.oRngTreeDepth = oRngTreeDepth; + self.oRngTreeDirs = oRngTreeDirs; + self.cchMaxPath = cchMaxPath; + self.cchMaxName = cchMaxName + ## @} + + ## @name Charset stuff + ## @todo allow more chars for unix hosts + guests. + ## @todo include unicode stuff, except on OS/2 and DOS. + ## @{ + ## The filename charset. + self.sFileCharset = string.printable; + ## Set of characters that should not trail a guest filename. + self.sReservedTrailing = self.ksReservedTrailingWinOS2; + if self.sMinStyle in ('win', 'os2'): + for ch in self.ksReservedWinOS2: + self.sFileCharset = self.sFileCharset.replace(ch, ''); + elif self.sMinStyle in ('darwin', 'linux', 'solaris'): + self.sReservedTrailing = self.ksReservedTrailingUnix; + for ch in self.ksReservedUnix: + self.sFileCharset = self.sFileCharset.replace(ch, ''); + else: # 'cross' + # Filter out all reserved charsets from all platforms. + for ch in self.ksReservedWinOS2: + self.sFileCharset = self.sFileCharset.replace(ch, ''); + for ch in self.ksReservedUnix: + self.sFileCharset = self.sFileCharset.replace(ch, ''); + self.sReservedTrailing = self.ksReservedTrailingWinOS2 \ + + self.ksReservedTrailingUnix; + # More spaces and dot: + self.sFileCharset += ' ...'; + ## @} + + ## The root directory. + self.oRoot = None # type: TestDir; + ## An empty directory (under root). + self.oEmptyDir = None # type: TestDir; + + ## A directory with a lot of files in it. + self.oManyDir = None # type: TestDir; + + ## A directory with a mixed tree structure under it. + self.oTreeDir = None # type: TestDir; + ## Number of files in oTreeDir. + self.cTreeFiles = 0; + ## Number of directories under oTreeDir. + self.cTreeDirs = 0; + ## Number of other file types under oTreeDir. + self.cTreeOthers = 0; + + ## All directories in creation order. + self.aoDirs = [] # type: list(TestDir); + ## All files in creation order. + self.aoFiles = [] # type: list(TestFile); + ## Path to object lookup. + self.dPaths = {} # type: dict(str, TestFsObj); + + # + # Do the creating. + # + self.uSeed = uSeed if uSeed is not None else utils.timestampMilli(); + self.oRandom = random.Random(); + self.oRandom.seed(self.uSeed); + reporter.log('prepareGuestForTesting: random seed %s' % (self.uSeed,)); + + self.__createTestStuff(); + + def __createFilename(self, oParent, sCharset, sReservedTrailing): + """ + Creates a filename contains random characters from sCharset and together + with oParent.sPath doesn't exceed the given max chars in length. + """ + ## @todo Consider extending this to take UTF-8 and UTF-16 encoding so we + ## can safely use the full unicode range. Need to check how + ## RTZipTarCmd handles file name encoding in general... + + if oParent: + cchMaxName = self.cchMaxPath - len(oParent.sPath) - 1; + else: + cchMaxName = self.cchMaxPath - 4; + if cchMaxName > self.cchMaxName: + cchMaxName = self.cchMaxName; + if cchMaxName <= 1: + cchMaxName = 2; + + while True: + cchName = self.oRandom.randrange(1, cchMaxName); + sName = ''.join(self.oRandom.choice(sCharset) for _ in xrange(cchName)); + if oParent is None or not oParent.contains(sName): + if sName[-1] not in sReservedTrailing: + if sName not in ('.', '..',): + return sName; + return ''; # never reached, but makes pylint happy. + + def generateFilenameEx(self, cchMax = -1, cchMin = -1): + """ + Generates a filename according to the given specs. + + This is for external use, whereas __createFilename is for internal. + + Returns generated filename. + """ + assert cchMax == -1 or (cchMax >= 1 and cchMax > cchMin); + if cchMin <= 0: + cchMin = 1; + if cchMax < cchMin: + cchMax = self.cchMaxName; + + while True: + cchName = self.oRandom.randrange(cchMin, cchMax + 1); + sName = ''.join(self.oRandom.choice(self.sFileCharset) for _ in xrange(cchName)); + if sName[-1] not in self.sReservedTrailing: + if sName not in ('.', '..',): + return sName; + return ''; # never reached, but makes pylint happy. + + def __createTestDir(self, oParent, sDir, sName = None): + """ + Creates a test directory. + """ + oDir = TestDir(oParent, sDir, sName); + self.aoDirs.append(oDir); + self.dPaths[sDir] = oDir; + return oDir; + + def __createTestFile(self, oParent, sFile): + """ + Creates a test file with random size up to cbMaxContent and random content. + """ + cbFile = self.oRandom.choice(self.oRngFileSizes); + abContent = bytearray(self.oRandom.getrandbits(8) for _ in xrange(cbFile)); + + oFile = TestFile(oParent, sFile, abContent); + self.aoFiles.append(oFile); + self.dPaths[sFile] = oFile; + return oFile; + + def __createTestStuff(self): + """ + Create a random file set that we can work on in the tests. + Returns True/False. + """ + + # + # Create the root test dir. + # + sRoot = pathutils.joinEx(self.fDosStyle, self.sBasePath, self.sSubDir); + self.oRoot = self.__createTestDir(None, sRoot, self.sSubDir); + self.oEmptyDir = self.__createTestDir(self.oRoot, pathutils.joinEx(self.fDosStyle, sRoot, 'empty')); + + # + # Create a directory with lots of files in it: + # + oDir = self.__createTestDir(self.oRoot, pathutils.joinEx(self.fDosStyle, sRoot, 'many')); + self.oManyDir = oDir; + cManyFiles = self.oRandom.choice(self.oRngManyFiles); + for _ in xrange(cManyFiles): + sName = self.__createFilename(oDir, self.sFileCharset, self.sReservedTrailing); + self.__createTestFile(oDir, pathutils.joinEx(self.fDosStyle, oDir.sPath, sName)); + + # + # Generate a tree of files and dirs. + # + oDir = self.__createTestDir(self.oRoot, pathutils.joinEx(self.fDosStyle, sRoot, 'tree')); + uMaxDepth = self.oRandom.choice(self.oRngTreeDepth); + cMaxFiles = self.oRandom.choice(self.oRngTreeFiles); + cMaxDirs = self.oRandom.choice(self.oRngTreeDirs); + self.oTreeDir = oDir; + self.cTreeFiles = 0; + self.cTreeDirs = 0; + uDepth = 0; + while self.cTreeFiles < cMaxFiles and self.cTreeDirs < cMaxDirs: + iAction = self.oRandom.randrange(0, 2+1); + # 0: Add a file: + if iAction == 0 and self.cTreeFiles < cMaxFiles and len(oDir.sPath) < 230 - 2: + sName = self.__createFilename(oDir, self.sFileCharset, self.sReservedTrailing); + self.__createTestFile(oDir, pathutils.joinEx(self.fDosStyle, oDir.sPath, sName)); + self.cTreeFiles += 1; + # 1: Add a subdirector and descend into it: + elif iAction == 1 and self.cTreeDirs < cMaxDirs and uDepth < uMaxDepth and len(oDir.sPath) < 220: + sName = self.__createFilename(oDir, self.sFileCharset, self.sReservedTrailing); + oDir = self.__createTestDir(oDir, pathutils.joinEx(self.fDosStyle, oDir.sPath, sName)); + self.cTreeDirs += 1; + uDepth += 1; + # 2: Ascend to parent dir: + elif iAction == 2 and uDepth > 0: + oDir = oDir.oParent; + uDepth -= 1; + + return True; + + def createTarball(self, sTarFileHst): + """ + Creates a tarball on the host. + Returns success indicator. + """ + reporter.log('Creating tarball "%s" with test files for the guest...' % (sTarFileHst,)); + + cchSkip = len(self.sBasePath) + 1; + + # Open the tarball: + try: + # Make sure to explicitly set GNU_FORMAT here, as with Python 3.8 the default format (tarfile.DEFAULT_FORMAT) + # has been changed to tarfile.PAX_FORMAT, which our extraction code (vts_tar) currently can't handle. + ## @todo Remove tarfile.GNU_FORMAT and use tarfile.PAX_FORMAT as soon as we have PAX support. + oTarFile = tarfile.open(sTarFileHst, 'w:gz', format = tarfile.GNU_FORMAT); # pylint: disable=consider-using-with + except: + return reporter.errorXcpt('Failed to open new tar file: %s' % (sTarFileHst,)); + + # Directories: + for oDir in self.aoDirs: + sPath = oDir.sPath[cchSkip:]; + if self.fDosStyle: + sPath = sPath.replace('\\', '/'); + oTarInfo = tarfile.TarInfo(sPath + '/'); + oTarInfo.mode = 0o777; + oTarInfo.type = tarfile.DIRTYPE; + try: + oTarFile.addfile(oTarInfo); + except: + return reporter.errorXcpt('Failed adding directory tarfile: %s' % (oDir.sPath,)); + + # Files: + for oFile in self.aoFiles: + sPath = oFile.sPath[cchSkip:]; + if self.fDosStyle: + sPath = sPath.replace('\\', '/'); + oTarInfo = tarfile.TarInfo(sPath); + oTarInfo.mode = 0o666; + oTarInfo.size = len(oFile.abContent); + oFile.off = 0; + try: + oTarFile.addfile(oTarInfo, oFile); + except: + return reporter.errorXcpt('Failed adding directory tarfile: %s' % (oFile.sPath,)); + + # Complete the tarball. + try: + oTarFile.close(); + except: + return reporter.errorXcpt('Error closing new tar file: %s' % (sTarFileHst,)); + return True; + + def writeToDisk(self, sAltBase = None): + """ + Writes out the files to disk. + Returns True on success, False + error logging on failure. + """ + + # We only need to flip DOS slashes to unix ones, since windows & OS/2 can handle unix slashes. + fDosToUnix = self.fDosStyle and os.path.sep != '\\'; + + # The directories: + for oDir in self.aoDirs: + sPath = oDir.sPath; + if sAltBase: + if fDosToUnix: + sPath = sAltBase + sPath[len(self.sBasePath):].replace('\\', os.path.sep); + else: + sPath = sAltBase + sPath[len(self.sBasePath):]; + elif fDosToUnix: + sPath = sPath.replace('\\', os.path.sep); + + try: + os.mkdir(sPath, 0o770); + except: + return reporter.errorXcpt('mkdir(%s) failed' % (sPath,)); + + # The files: + for oFile in self.aoFiles: + sPath = oFile.sPath; + if sAltBase: + if fDosToUnix: + sPath = sAltBase + sPath[len(self.sBasePath):].replace('\\', os.path.sep); + else: + sPath = sAltBase + sPath[len(self.sBasePath):]; + elif fDosToUnix: + sPath = sPath.replace('\\', os.path.sep); + + try: + oOutFile = open(sPath, 'wb'); # pylint: disable=consider-using-with + except: + return reporter.errorXcpt('open(%s, "wb") failed' % (sPath,)); + try: + if sys.version_info[0] < 3: + oOutFile.write(bytes(oFile.abContent)); + else: + oOutFile.write(oFile.abContent); + except: + try: oOutFile.close(); + except: pass; + return reporter.errorXcpt('%s: write(%s bytes) failed' % (sPath, oFile.cbContent,)); + try: + oOutFile.close(); + except: + return reporter.errorXcpt('%s: close() failed' % (sPath,)); + + return True; + + + def chooseRandomFile(self): + """ + Returns a random file. + """ + return self.aoFiles[self.oRandom.choice(xrange(len(self.aoFiles)))]; + + def chooseRandomDirFromTree(self, fLeaf = False, fNonEmpty = False, cMaxRetries = 1024): + """ + Returns a random directory from the tree (self.oTreeDir). + Will return None if no directory with given parameters was found. + """ + cRetries = 0; + while cRetries < cMaxRetries: + oDir = self.aoDirs[self.oRandom.choice(xrange(len(self.aoDirs)))]; + # Check fNonEmpty requirement: + if not fNonEmpty or oDir.aoChildren: + # Check leaf requirement: + if not fLeaf: + for oChild in oDir.aoChildren: + if isinstance(oChild, TestDir): + continue; # skip it. + + # Return if in the tree: + oParent = oDir.oParent; + while oParent is not None: + if oParent is self.oTreeDir: + return oDir; + oParent = oParent.oParent; + cRetries += 1; + + return None; # make pylint happy + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +# pylint: disable=undefined-variable +class TestFileSetUnitTests(unittest.TestCase): + def testGeneral(self): + oSet = TestFileSet(False, '/tmp', 'unittest'); + self.assertTrue(isinstance(oSet.chooseRandomDirFromTree(), TestDir)); + self.assertTrue(isinstance(oSet.chooseRandomFile(), TestFile)); + + def testHexFormatBytes(self): + self.assertEqual(TestFile.hexFormatBytes(bytearray([0,1,2,3,4,5,6,7,8,9])), + '00 01 02 03 04 05 06 07-08 09'); + self.assertEqual(TestFile.hexFormatBytes(memoryview(bytearray([0,1,2,3,4,5,6,7,8,9,10, 16]))), + '00 01 02 03 04 05 06 07-08 09 0a 10'); + + +if __name__ == '__main__': + unittest.main(); + # not reached. + diff --git a/src/VBox/ValidationKit/testdriver/tst-txsclient.py b/src/VBox/ValidationKit/testdriver/tst-txsclient.py new file mode 100755 index 00000000..192cf2cb --- /dev/null +++ b/src/VBox/ValidationKit/testdriver/tst-txsclient.py @@ -0,0 +1,315 @@ +# -*- coding: utf-8 -*- +# $Id: tst-txsclient.py $ + +""" +Simple testcase for txsclient.py. +""" + +from __future__ import print_function; + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Standard python imports. +import os +import sys + +# Validation Kit imports. +sys.path.insert(0, '.'); +sys.path.insert(0, '..'); +from common import utils; +from testdriver import txsclient; +from testdriver import reporter; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + +g_cTests = 0; +g_cFailures = 0 + +def boolRes(rc, fExpect = True): + """Checks a boolean result.""" + global g_cTests, g_cFailures; + g_cTests = g_cTests + 1; + if isinstance(rc, bool): + if rc == fExpect: + return 'PASSED'; + g_cFailures = g_cFailures + 1; + return 'FAILED'; + +def stringRes(rc, sExpect): + """Checks a string result.""" + global g_cTests, g_cFailures; + g_cTests = g_cTests + 1; + if utils.isString(rc): + if rc == sExpect: + return 'PASSED'; + g_cFailures = g_cFailures + 1; + return 'FAILED'; + +def main(asArgs): # pylint: disable=missing-docstring,too-many-locals,too-many-statements + cMsTimeout = long(30*1000); + sAddress = 'localhost'; + uPort = None; + fReversedSetup = False; + fReboot = False; + fShutdown = False; + fStdTests = True; + + i = 1; + while i < len(asArgs): + if asArgs[i] == '--hostname': + sAddress = asArgs[i + 1]; + i = i + 2; + elif asArgs[i] == '--port': + uPort = int(asArgs[i + 1]); + i = i + 2; + elif asArgs[i] == '--reversed-setup': + fReversedSetup = True; + i = i + 1; + elif asArgs[i] == '--timeout': + cMsTimeout = long(asArgs[i + 1]); + i = i + 2; + elif asArgs[i] == '--reboot': + fReboot = True; + fShutdown = False; + fStdTests = False; + i = i + 1; + elif asArgs[i] == '--shutdown': + fShutdown = True; + fReboot = False; + fStdTests = False; + i = i + 1; + elif asArgs[i] == '--help': + print('tst-txsclient.py [--hostname <addr|name>] [--port <num>] [--timeout <cMS>] ' + '[--reboot|--shutdown] [--reversed-setup]'); + return 0; + else: + print('Unknown argument: %s' % (asArgs[i])); + return 2; + + if uPort is None: + oSession = txsclient.openTcpSession(cMsTimeout, sAddress, fReversedSetup = fReversedSetup); + else: + oSession = txsclient.openTcpSession(cMsTimeout, sAddress, uPort = uPort, fReversedSetup = fReversedSetup); + if oSession is None: + print('openTcpSession failed'); + return 1; + + fDone = oSession.waitForTask(30*1000); + print('connect: waitForTask -> %s, result %s' % (fDone, oSession.getResult())); + if fDone is True and oSession.isSuccess(): + if fStdTests: + # Get the UUID of the remote instance. + sUuid = oSession.syncUuid(); + if sUuid is not False: + print('%s: UUID = %s' % (boolRes(True), sUuid)); + else: + print('%s: UUID' % (boolRes(False),)); + + # Create and remove a directory on the scratch area. + rc = oSession.syncMkDir('${SCRATCH}/testdir1'); + print('%s: MKDIR(${SCRATCH}/testdir1) -> %s' % (boolRes(rc), rc)); + + rc = oSession.syncIsDir('${SCRATCH}/testdir1'); + print('%s: ISDIR(${SCRATCH}/testdir1) -> %s' % (boolRes(rc), rc)); + + rc = oSession.syncRmDir('${SCRATCH}/testdir1'); + print('%s: RMDIR(${SCRATCH}/testdir1) -> %s' % (boolRes(rc), rc)); + + # Create a two-level subdir. + rc = oSession.syncMkDirPath('${SCRATCH}/testdir2/subdir1'); + print('%s: MKDRPATH(${SCRATCH}/testdir2/subdir1) -> %s' % (boolRes(rc), rc)); + + rc = oSession.syncIsDir('${SCRATCH}/testdir2'); + print('%s: ISDIR(${SCRATCH}/testdir2) -> %s' % (boolRes(rc), rc)); + rc = oSession.syncIsDir('${SCRATCH}/testdir2/'); + print('%s: ISDIR(${SCRATCH}/testdir2/) -> %s' % (boolRes(rc), rc)); + rc = oSession.syncIsDir('${SCRATCH}/testdir2/subdir1'); + print('%s: ISDIR(${SCRATCH}/testdir2/subdir1) -> %s' % (boolRes(rc), rc)); + + rc = oSession.syncRmTree('${SCRATCH}/testdir2'); + print('%s: RMTREE(${SCRATCH}/testdir2) -> %s' % (boolRes(rc), rc)); + + # Check out a simple file. + rc = oSession.syncUploadString('howdy', '${SCRATCH}/howdyfile'); + print('%s: PUT FILE(${SCRATCH}/howdyfile) -> %s' % (boolRes(rc), rc)); + + rc = oSession.syncUploadString('howdy-replaced', '${SCRATCH}/howdyfile'); + print('%s: PUT FILE(${SCRATCH}/howdyfile) -> %s' % (boolRes(rc), rc)); + + rc = oSession.syncDownloadString('${SCRATCH}/howdyfile'); + print('%s: GET FILE(${SCRATCH}/howdyfile) -> "%s" expected "howdy-replaced"' % (stringRes(rc, 'howdy-replaced'), rc)); + + rc = oSession.syncIsFile('${SCRATCH}/howdyfile'); + print('%s: ISFILE(${SCRATCH}/howdyfile) -> %s' % (boolRes(rc), rc)); + rc = oSession.syncIsDir('${SCRATCH}/howdyfile'); + print('%s: ISDIR(${SCRATCH}/howdyfile) -> %s' % (boolRes(rc, False), rc)); + rc = oSession.syncIsSymlink('${SCRATCH}/howdyfile'); + print('%s: ISSYMLNK(${SCRATCH}/howdyfile) -> %s' % (boolRes(rc, False), rc)); + + rc = oSession.syncRmFile('${SCRATCH}/howdyfile'); + print('%s: RMFILE(${SCRATCH}/howdyfile) -> %s' % (boolRes(rc), rc)); + + # Unicode filename (may or may not work, LANG/LC_TYPE dependent on some hosts). + rc = oSession.syncUploadString('howdy', u'${SCRATCH}/Schröder'); + print((u'%s: PUT FILE(${SCRATCH}/Schröder) -> %s' % (boolRes(rc), rc)).encode('ascii', 'replace')); + + rc = oSession.syncIsFile(u'${SCRATCH}/Schröder'); + print((u'%s: ISFILE(${SCRATCH}/Schröder) -> %s' % (boolRes(rc), rc)).encode('ascii', 'replace')); + + rc = oSession.syncRmFile(u'${SCRATCH}/Schröder'); + print((u'%s: RMFILE(${SCRATCH}/Schröder) -> %s' % (boolRes(rc), rc)).encode('ascii', 'replace')); + + # Finally, some file uploading and downloading with unicode filenames. + strUpFile = 'tst-txsclient-upload.bin'; + strDwnFile = 'tst-txsclient-download.bin'; + try: + abRandFile = os.urandom(257897); + except: + print('INFO: no urandom... falling back on a simple string.'); + abRandFile = 'asdflkjasdlfkjasdlfkjq023942relwjgkna9epr865u2nm345;hndafgoukhasre5kb2453km'; + for i in range(1, 64): + abRandFile += abRandFile; + try: + oLocalFile = utils.openNoInherit(strUpFile, 'w+b'); + oLocalFile.write(abRandFile); + oLocalFile.close(); + rc = True; + except: + rc = False; + print('%s: creating file (%s) to upload failed....' % (boolRes(rc), strUpFile)); + + if rc is True: + rc = oSession.syncUploadFile(strUpFile, '${SCRATCH}/tst-txsclient-uploaded.bin') + print('%s: PUT FILE(%s, ${SCRATCH}/tst-txsclient-uploaded.bin) -> %s' % (boolRes(rc), strUpFile, rc)); + + rc = oSession.syncDownloadFile('${SCRATCH}/tst-txsclient-uploaded.bin', strDwnFile) + print('%s: GET FILE(${SCRATCH}/tst-txsclient-uploaded.bin, tst-txsclient-downloaded.txt) -> %s' + % (boolRes(rc), rc)); + + try: + oLocalFile = utils.openNoInherit(strDwnFile, "rb"); + abDwnFile = oLocalFile.read(); + oLocalFile.close(); + if abRandFile == abDwnFile: + print('%s: downloaded file matches the uploaded file' % (boolRes(True),)); + else: + print('%s: downloaded file does not match the uploaded file' % (boolRes(False),)); + print('abRandFile=%s' % (abRandFile,)); + print('abDwnFile =%s' % (abRandFile,)); + except: + print('%s: reading downloaded file (%s) failed....' % (boolRes(False), strDwnFile)); + + rc = oSession.syncRmFile(u'${SCRATCH}/tst-txsclient-uploaded.bin'); + print('%s: RMFILE(${SCRATCH}/tst-txsclient-uploaded.bin) -> %s' % (boolRes(rc), rc)); + + try: os.remove(strUpFile); + except: pass; + try: os.remove(strDwnFile); + except: pass; + + # Execute some simple thing, if available. + # Intentionally skip this test if file is not available due to + # another inserted CD-ROM (e.g. not TestSuite.iso). + sProg = '${CDROM}/${OS/ARCH}/NetPerf${EXESUFF}'; + rc = oSession.syncIsFile(sProg, 30 * 1000, True); + if rc is True: + rc = oSession.syncExecEx(sProg, (sProg, '--help')); + print('%s: EXEC(%s ${SCRATCH}) -> %s' % (boolRes(rc), sProg, rc)); + + rc = oSession.syncExecEx(sProg, (sProg, 'there', 'is no such', 'parameter'), \ + oStdOut='${SCRATCH}/stdout', \ + oStdErr='${SCRATCH}/stderr'); + print('%s: EXEC(%s there is not such parameter > ${SCRATCH}/stdout 2> ${SCRATCH}/stderr) -> %s' + % (boolRes(rc, False), sProg, rc)); + + rc = oSession.syncDownloadString('${SCRATCH}/stdout'); + print('INFO: GET FILE(${SCRATCH}/stdout) -> "%s"' % (rc)); + rc = oSession.syncDownloadString('${SCRATCH}/stderr'); + print('INFO: GET FILE(${SCRATCH}/stderr) -> "%s"' % (rc)); + + print('TESTING: syncExec...'); + rc = oSession.syncExec(sProg, (sProg, '--version')); + print('%s: EXEC(%s --version) -> %s' % (boolRes(rc), sProg, rc)); + + print('TESTING: syncExec...'); + rc = oSession.syncExec(sProg, (sProg, '--help')); + print('%s: EXEC(%s --help) -> %s' % (boolRes(rc), sProg, rc)); + + #print('TESTING: syncExec sleep 30...' + #rc = oSession.syncExec('/usr/bin/sleep', ('/usr/bin/sleep', '30'))); + #print('%s: EXEC(/bin/sleep 30) -> %s' % (boolRes(rc), rc)); + else: + print('SKIP: Execution of %s skipped, does not exist on CD-ROM' % (sProg,)); + + # Execute a non-existing file on CD-ROM. + sProg = '${CDROM}/${OS/ARCH}/NonExisting${EXESUFF}'; + rc = oSession.syncExecEx(sProg, (sProg,), oStdIn = '/dev/null', oStdOut = '/dev/null', \ + oStdErr = '/dev/null', oTestPipe = '/dev/null', \ + sAsUser = '', cMsTimeout = 3600000, fIgnoreErrors = True); + if rc is None: + rc = True; + else: + reporter.error('Unexpected value \"%s\" while executing non-existent file "%s"' % (rc, sProg)); + print('%s: EXEC(%s ${SCRATCH}) -> %s' % (boolRes(rc), sProg, rc)); + + # Done + rc = oSession.syncDisconnect(); + print('%s: disconnect() -> %s' % (boolRes(rc), rc)); + + elif fReboot: + print('TESTING: syncReboot...'); + rc = oSession.syncReboot(); + print('%s: REBOOT() -> %s' % (boolRes(rc), rc)); + elif fShutdown: + print('TESTING: syncShutdown...'); + rc = oSession.syncShutdown(); + print('%s: SHUTDOWN() -> %s' % (boolRes(rc), rc)); + + + if g_cFailures != 0: + print('tst-txsclient.py: %u out of %u test failed' % (g_cFailures, g_cTests)); + return 1; + print('tst-txsclient.py: all %u tests passed!' % (g_cTests)); + return 0; + + +if __name__ == '__main__': + reporter.incVerbosity(); + reporter.incVerbosity(); + reporter.incVerbosity(); + reporter.incVerbosity(); + sys.exit(main(sys.argv)); + diff --git a/src/VBox/ValidationKit/testdriver/txsclient.py b/src/VBox/ValidationKit/testdriver/txsclient.py new file mode 100755 index 00000000..942fab57 --- /dev/null +++ b/src/VBox/ValidationKit/testdriver/txsclient.py @@ -0,0 +1,2376 @@ +# -*- coding: utf-8 -*- +# $Id: txsclient.py $ +# pylint: disable=too-many-lines + +""" +Test eXecution Service Client. +""" +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + +# Standard Python imports. +import array; +import errno; +import os; +import select; +import socket; +import sys; +import threading; +import time; +import zlib; +import uuid; + +# Validation Kit imports. +from common import utils; +from testdriver import base; +from testdriver import reporter; +from testdriver.base import TdTaskBase; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + +# +# Helpers for decoding data received from the TXS. +# These are used both the Session and Transport classes. +# + +def getU32(abData, off): + """Get a U32 field.""" + return abData[off] \ + + abData[off + 1] * 256 \ + + abData[off + 2] * 65536 \ + + abData[off + 3] * 16777216; + +def getSZ(abData, off, sDefault = None): + """ + Get a zero-terminated string field. + Returns sDefault if the string is invalid. + """ + cchStr = getSZLen(abData, off); + if cchStr >= 0: + abStr = abData[off:(off + cchStr)]; + try: + if sys.version_info < (3, 9, 0): + # Removed since Python 3.9. + sStr = abStr.tostring(); # pylint: disable=no-member + else: + sStr = abStr.tobytes(); + return sStr.decode('utf_8'); + except: + reporter.errorXcpt('getSZ(,%u)' % (off)); + return sDefault; + +def getSZLen(abData, off): + """ + Get the length of a zero-terminated string field, in bytes. + Returns -1 if off is beyond the data packet or not properly terminated. + """ + cbData = len(abData); + if off >= cbData: + return -1; + + offCur = off; + while abData[offCur] != 0: + offCur = offCur + 1; + if offCur >= cbData: + return -1; + + return offCur - off; + +def isValidOpcodeEncoding(sOpcode): + """ + Checks if the specified opcode is valid or not. + Returns True on success. + Returns False if it is invalid, details in the log. + """ + sSet1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + sSet2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_ "; + if len(sOpcode) != 8: + reporter.error("invalid opcode length: %s" % (len(sOpcode))); + return False; + for i in range(0, 1): + if sSet1.find(sOpcode[i]) < 0: + reporter.error("invalid opcode char #%u: %s" % (i, sOpcode)); + return False; + for i in range(2, 7): + if sSet2.find(sOpcode[i]) < 0: + reporter.error("invalid opcode char #%u: %s" % (i, sOpcode)); + return False; + return True; + +# +# Helper for encoding data sent to the TXS. +# + +def u32ToByteArray(u32): + """Encodes the u32 value as a little endian byte (B) array.""" + return array.array('B', + ( u32 % 256, + (u32 // 256) % 256, + (u32 // 65536) % 256, + (u32 // 16777216) % 256) ); + +def escapeString(sString): + """ + Does $ escaping of the string so TXS doesn't try do variable expansion. + """ + return sString.replace('$', '$$'); + + + +class TransportBase(object): + """ + Base class for the transport layer. + """ + + def __init__(self, sCaller): + self.sDbgCreated = '%s: %s' % (utils.getTimePrefix(), sCaller); + self.fDummy = 0; + self.abReadAheadHdr = array.array('B'); + + def toString(self): + """ + Stringify the instance for logging and debugging. + """ + return '<%s: abReadAheadHdr=%s, sDbgCreated=%s>' % (type(self).__name__, self.abReadAheadHdr, self.sDbgCreated); + + def __str__(self): + return self.toString(); + + def cancelConnect(self): + """ + Cancels any pending connect() call. + Returns None; + """ + return None; + + def connect(self, cMsTimeout): + """ + Quietly attempts to connect to the TXS. + + Returns True on success. + Returns False on retryable errors (no logging). + Returns None on fatal errors with details in the log. + + Override this method, don't call super. + """ + _ = cMsTimeout; + return False; + + def disconnect(self, fQuiet = False): + """ + Disconnect from the TXS. + + Returns True. + + Override this method, don't call super. + """ + _ = fQuiet; + return True; + + def sendBytes(self, abBuf, cMsTimeout): + """ + Sends the bytes in the buffer abBuf to the TXS. + + Returns True on success. + Returns False on failure and error details in the log. + + Override this method, don't call super. + + Remarks: len(abBuf) is always a multiple of 16. + """ + _ = abBuf; _ = cMsTimeout; + return False; + + def recvBytes(self, cb, cMsTimeout, fNoDataOk): + """ + Receive cb number of bytes from the TXS. + + Returns the bytes (array('B')) on success. + Returns None on failure and error details in the log. + + Override this method, don't call super. + + Remarks: cb is always a multiple of 16. + """ + _ = cb; _ = cMsTimeout; _ = fNoDataOk; + return None; + + def isConnectionOk(self): + """ + Checks if the connection is OK. + + Returns True if it is. + Returns False if it isn't (caller should call diconnect). + + Override this method, don't call super. + """ + return True; + + def isRecvPending(self, cMsTimeout = 0): + """ + Checks if there is incoming bytes, optionally waiting cMsTimeout + milliseconds for something to arrive. + + Returns True if there is, False if there isn't. + + Override this method, don't call super. + """ + _ = cMsTimeout; + return False; + + def sendMsgInt(self, sOpcode, cMsTimeout, abPayload = array.array('B')): + """ + Sends a message (opcode + encoded payload). + + Returns True on success. + Returns False on failure and error details in the log. + """ + # Fix + check the opcode. + if len(sOpcode) < 2: + reporter.fatal('sendMsgInt: invalid opcode length: %d (\"%s\")' % (len(sOpcode), sOpcode)); + return False; + sOpcode = sOpcode.ljust(8); + if not isValidOpcodeEncoding(sOpcode): + reporter.fatal('sendMsgInt: invalid opcode encoding: \"%s\"' % (sOpcode)); + return False; + + # Start construct the message. + cbMsg = 16 + len(abPayload); + abMsg = array.array('B'); + abMsg.extend(u32ToByteArray(cbMsg)); + abMsg.extend((0, 0, 0, 0)); # uCrc32 + try: + abMsg.extend(array.array('B', \ + ( ord(sOpcode[0]), \ + ord(sOpcode[1]), \ + ord(sOpcode[2]), \ + ord(sOpcode[3]), \ + ord(sOpcode[4]), \ + ord(sOpcode[5]), \ + ord(sOpcode[6]), \ + ord(sOpcode[7]) ) ) ); + if abPayload: + abMsg.extend(abPayload); + except: + reporter.fatalXcpt('sendMsgInt: packing problem...'); + return False; + + # checksum it, padd it and send it off. + uCrc32 = zlib.crc32(abMsg[8:]); + abMsg[4:8] = u32ToByteArray(uCrc32); + + while len(abMsg) % 16: + abMsg.append(0); + + reporter.log2('sendMsgInt: op=%s len=%d timeout=%d' % (sOpcode, len(abMsg), cMsTimeout)); + return self.sendBytes(abMsg, cMsTimeout); + + def recvMsg(self, cMsTimeout, fNoDataOk = False): + """ + Receives a message from the TXS. + + Returns the message three-tuple: length, opcode, payload. + Returns (None, None, None) on failure and error details in the log. + """ + + # Read the header. + if self.abReadAheadHdr: + assert(len(self.abReadAheadHdr) == 16); + abHdr = self.abReadAheadHdr; + self.abReadAheadHdr = array.array('B'); + else: + abHdr = self.recvBytes(16, cMsTimeout, fNoDataOk); # (virtual method) # pylint: disable=assignment-from-none + if abHdr is None: + return (None, None, None); + if len(abHdr) != 16: + reporter.fatal('recvBytes(16) returns %d bytes!' % (len(abHdr))); + return (None, None, None); + + # Unpack and validate the header. + cbMsg = getU32(abHdr, 0); + uCrc32 = getU32(abHdr, 4); + + if sys.version_info < (3, 9, 0): + # Removed since Python 3.9. + sOpcode = abHdr[8:16].tostring(); # pylint: disable=no-member + else: + sOpcode = abHdr[8:16].tobytes(); + sOpcode = sOpcode.decode('ascii'); + + if cbMsg < 16: + reporter.fatal('recvMsg: message length is out of range: %s (min 16 bytes)' % (cbMsg)); + return (None, None, None); + if cbMsg > 1024*1024: + reporter.fatal('recvMsg: message length is out of range: %s (max 1MB)' % (cbMsg)); + return (None, None, None); + if not isValidOpcodeEncoding(sOpcode): + reporter.fatal('recvMsg: invalid opcode \"%s\"' % (sOpcode)); + return (None, None, None); + + # Get the payload (if any), dropping the padding. + abPayload = array.array('B'); + if cbMsg > 16: + if cbMsg % 16: + cbPadding = 16 - (cbMsg % 16); + else: + cbPadding = 0; + abPayload = self.recvBytes(cbMsg - 16 + cbPadding, cMsTimeout, False); # pylint: disable=assignment-from-none + if abPayload is None: + self.abReadAheadHdr = abHdr; + if not fNoDataOk : + reporter.log('recvMsg: failed to recv payload bytes!'); + return (None, None, None); + + while cbPadding > 0: + abPayload.pop(); + cbPadding = cbPadding - 1; + + # Check the CRC-32. + if uCrc32 != 0: + uActualCrc32 = zlib.crc32(abHdr[8:]); + if cbMsg > 16: + uActualCrc32 = zlib.crc32(abPayload, uActualCrc32); + uActualCrc32 = uActualCrc32 & 0xffffffff; + if uCrc32 != uActualCrc32: + reporter.fatal('recvMsg: crc error: expected %s, got %s' % (hex(uCrc32), hex(uActualCrc32))); + return (None, None, None); + + reporter.log2('recvMsg: op=%s len=%d' % (sOpcode, len(abPayload))); + return (cbMsg, sOpcode, abPayload); + + def sendMsg(self, sOpcode, cMsTimeout, aoPayload = ()): + """ + Sends a message (opcode + payload tuple). + + Returns True on success. + Returns False on failure and error details in the log. + Returns None if you pass the incorrectly typed parameters. + """ + # Encode the payload. + abPayload = array.array('B'); + for o in aoPayload: + try: + if utils.isString(o): + if sys.version_info[0] >= 3: + abPayload.extend(o.encode('utf_8')); + else: + # the primitive approach... + sUtf8 = o.encode('utf_8'); + for ch in sUtf8: + abPayload.append(ord(ch)) + abPayload.append(0); + elif isinstance(o, (long, int)): + if o < 0 or o > 0xffffffff: + reporter.fatal('sendMsg: uint32_t payload is out of range: %s' % (hex(o))); + return None; + abPayload.extend(u32ToByteArray(o)); + elif isinstance(o, array.array): + abPayload.extend(o); + else: + reporter.fatal('sendMsg: unexpected payload type: %s (%s) (aoPayload=%s)' % (type(o), o, aoPayload)); + return None; + except: + reporter.fatalXcpt('sendMsg: screwed up the encoding code...'); + return None; + return self.sendMsgInt(sOpcode, cMsTimeout, abPayload); + + +class Session(TdTaskBase): + """ + A Test eXecution Service (TXS) client session. + """ + + def __init__(self, oTransport, cMsTimeout, cMsIdleFudge, fTryConnect = False, fnProcessEvents = None): + """ + Construct a TXS session. + + This starts by connecting to the TXS and will enter the signalled state + when connected or the timeout has been reached. + """ + TdTaskBase.__init__(self, utils.getCallerName(), fnProcessEvents); + self.oTransport = oTransport; + self.sStatus = ""; + self.cMsTimeout = 0; + self.fErr = True; # Whether to report errors as error. + self.msStart = 0; + self.oThread = None; + self.fnTask = self.taskDummy; + self.aTaskArgs = None; + self.oTaskRc = None; + self.t3oReply = (None, None, None); + self.fScrewedUpMsgState = False; + self.fTryConnect = fTryConnect; + + if not self.startTask(cMsTimeout, False, "connecting", self.taskConnect, (cMsIdleFudge,)): + raise base.GenError("startTask failed"); + + def __del__(self): + """Make sure to cancel the task when deleted.""" + self.cancelTask(); + + def toString(self): + return '<%s fnTask=%s, aTaskArgs=%s, sStatus=%s, oTaskRc=%s, cMsTimeout=%s,' \ + ' msStart=%s, fTryConnect=%s, fErr=%s, fScrewedUpMsgState=%s, t3oReply=%s oTransport=%s, oThread=%s>' \ + % (TdTaskBase.toString(self), self.fnTask, self.aTaskArgs, self.sStatus, self.oTaskRc, self.cMsTimeout, + self.msStart, self.fTryConnect, self.fErr, self.fScrewedUpMsgState, self.t3oReply, self.oTransport, self.oThread); + + def taskDummy(self): + """Place holder to catch broken state handling.""" + raise Exception(); + + def startTask(self, cMsTimeout, fIgnoreErrors, sStatus, fnTask, aArgs = ()): + """ + Kicks of a new task. + + cMsTimeout: The task timeout in milliseconds. Values less than + 500 ms will be adjusted to 500 ms. This means it is + OK to use negative value. + sStatus: The task status. + fnTask: The method that'll execute the task. + aArgs: Arguments to pass to fnTask. + + Returns True on success, False + error in log on failure. + """ + if not self.cancelTask(): + reporter.maybeErr(not fIgnoreErrors, 'txsclient.Session.startTask: failed to cancel previous task.'); + return False; + + # Change status and make sure we're the + self.lockTask(); + if self.sStatus != "": + self.unlockTask(); + reporter.maybeErr(not fIgnoreErrors, 'txsclient.Session.startTask: race.'); + return False; + self.sStatus = "setup"; + self.oTaskRc = None; + self.t3oReply = (None, None, None); + self.resetTaskLocked(); + self.unlockTask(); + + self.cMsTimeout = max(cMsTimeout, 500); + self.fErr = not fIgnoreErrors; + self.fnTask = fnTask; + self.aTaskArgs = aArgs; + self.oThread = threading.Thread(target=self.taskThread, args=(), name=('TXS-%s' % (sStatus))); + self.oThread.setDaemon(True); # pylint: disable=deprecated-method + self.msStart = base.timestampMilli(); + + self.lockTask(); + self.sStatus = sStatus; + self.unlockTask(); + self.oThread.start(); + + return True; + + def cancelTask(self, fSync = True): + """ + Attempts to cancel any pending tasks. + Returns success indicator (True/False). + """ + self.lockTask(); + + if self.sStatus == "": + self.unlockTask(); + return True; + if self.sStatus == "setup": + self.unlockTask(); + return False; + if self.sStatus == "cancelled": + self.unlockTask(); + return False; + + reporter.log('txsclient: cancelling "%s"...' % (self.sStatus)); + if self.sStatus == 'connecting': + self.oTransport.cancelConnect(); + + self.sStatus = "cancelled"; + oThread = self.oThread; + self.unlockTask(); + + if not fSync: + return False; + + oThread.join(61.0); + + if sys.version_info < (3, 9, 0): + # Removed since Python 3.9. + return oThread.isAlive(); # pylint: disable=no-member + return oThread.is_alive(); + + def taskThread(self): + """ + The task thread function. + This does some housekeeping activities around the real task method call. + """ + if not self.isCancelled(): + try: + fnTask = self.fnTask; + oTaskRc = fnTask(*self.aTaskArgs); + except: + reporter.fatalXcpt('taskThread', 15); + oTaskRc = None; + else: + reporter.log('taskThread: cancelled already'); + + self.lockTask(); + + reporter.log('taskThread: signalling task with status "%s", oTaskRc=%s' % (self.sStatus, oTaskRc)); + self.oTaskRc = oTaskRc; + self.oThread = None; + self.sStatus = ''; + self.signalTaskLocked(); + + self.unlockTask(); + return None; + + def isCancelled(self): + """Internal method for checking if the task has been cancelled.""" + self.lockTask(); + sStatus = self.sStatus; + self.unlockTask(); + if sStatus == "cancelled": + return True; + return False; + + def hasTimedOut(self): + """Internal method for checking if the task has timed out or not.""" + cMsLeft = self.getMsLeft(); + if cMsLeft <= 0: + return True; + return False; + + def getMsLeft(self, cMsMin = 0, cMsMax = -1): + """Gets the time left until the timeout.""" + cMsElapsed = base.timestampMilli() - self.msStart; + if cMsElapsed < 0: + return cMsMin; + cMsLeft = self.cMsTimeout - cMsElapsed; + if cMsLeft <= cMsMin: + return cMsMin; + if cMsLeft > cMsMax > 0: + return cMsMax + return cMsLeft; + + def recvReply(self, cMsTimeout = None, fNoDataOk = False): + """ + Wrapper for TransportBase.recvMsg that stashes the response away + so the client can inspect it later on. + """ + if cMsTimeout is None: + cMsTimeout = self.getMsLeft(500); + cbMsg, sOpcode, abPayload = self.oTransport.recvMsg(cMsTimeout, fNoDataOk); + self.lockTask(); + self.t3oReply = (cbMsg, sOpcode, abPayload); + self.unlockTask(); + return (cbMsg, sOpcode, abPayload); + + def recvAck(self, fNoDataOk = False): + """ + Receives an ACK or error response from the TXS. + + Returns True on success. + Returns False on timeout or transport error. + Returns (sOpcode, sDetails) tuple on failure. The opcode is stripped + and there are always details of some sort or another. + """ + cbMsg, sOpcode, abPayload = self.recvReply(None, fNoDataOk); + if cbMsg is None: + return False; + sOpcode = sOpcode.strip() + if sOpcode == "ACK": + return True; + return (sOpcode, getSZ(abPayload, 0, sOpcode)); + + def recvAckLogged(self, sCommand, fNoDataOk = False): + """ + Wrapper for recvAck and logging. + Returns True on success (ACK). + Returns False on time, transport error and errors signalled by TXS. + """ + rc = self.recvAck(fNoDataOk); + if rc is not True and not fNoDataOk: + if rc is False: + reporter.maybeErr(self.fErr, 'recvAckLogged: %s transport error' % (sCommand)); + else: + reporter.maybeErr(self.fErr, 'recvAckLogged: %s response was %s: %s' % (sCommand, rc[0], rc[1])); + rc = False; + return rc; + + def recvTrueFalse(self, sCommand): + """ + Receives a TRUE/FALSE response from the TXS. + Returns True on TRUE, False on FALSE and None on error/other (logged). + """ + cbMsg, sOpcode, abPayload = self.recvReply(); + if cbMsg is None: + reporter.maybeErr(self.fErr, 'recvAckLogged: %s transport error' % (sCommand)); + return None; + + sOpcode = sOpcode.strip() + if sOpcode == "TRUE": + return True; + if sOpcode == "FALSE": + return False; + reporter.maybeErr(self.fErr, 'recvAckLogged: %s response was %s: %s' % (sCommand, sOpcode, getSZ(abPayload, 0, sOpcode))); + return None; + + def sendMsg(self, sOpcode, aoPayload = (), cMsTimeout = None): + """ + Wrapper for TransportBase.sendMsg that inserts the correct timeout. + """ + if cMsTimeout is None: + cMsTimeout = self.getMsLeft(500); + return self.oTransport.sendMsg(sOpcode, cMsTimeout, aoPayload); + + def asyncToSync(self, fnAsync, *aArgs): + """ + Wraps an asynchronous task into a synchronous operation. + + Returns False on failure, task return status on success. + """ + rc = fnAsync(*aArgs); + if rc is False: + reporter.log2('asyncToSync(%s): returns False (#1)' % (fnAsync)); + return rc; + + rc = self.waitForTask(self.cMsTimeout + 5000); + if rc is False: + reporter.maybeErr(self.fErr, 'asyncToSync: waitForTask (timeout %d) failed...' % (self.cMsTimeout,)); + self.cancelTask(); + #reporter.log2('asyncToSync(%s): returns False (#2)' % (fnAsync, rc)); + return False; + + rc = self.getResult(); + #reporter.log2('asyncToSync(%s): returns %s' % (fnAsync, rc)); + return rc; + + # + # Connection tasks. + # + + def taskConnect(self, cMsIdleFudge): + """Tries to connect to the TXS""" + while not self.isCancelled(): + reporter.log2('taskConnect: connecting ...'); + rc = self.oTransport.connect(self.getMsLeft(500)); + if rc is True: + reporter.log('taskConnect: succeeded'); + return self.taskGreet(cMsIdleFudge); + if rc is None: + reporter.log2('taskConnect: unable to connect'); + return None; + if self.hasTimedOut(): + reporter.log2('taskConnect: timed out'); + if not self.fTryConnect: + reporter.maybeErr(self.fErr, 'taskConnect: timed out'); + return False; + time.sleep(self.getMsLeft(1, 1000) / 1000.0); + if not self.fTryConnect: + reporter.maybeErr(self.fErr, 'taskConnect: cancelled'); + return False; + + def taskGreet(self, cMsIdleFudge): + """Greets the TXS""" + rc = self.sendMsg("HOWDY", ()); + if rc is True: + rc = self.recvAckLogged("HOWDY", self.fTryConnect); + if rc is True: + while cMsIdleFudge > 0: + cMsIdleFudge -= 1000; + time.sleep(1); + else: + self.oTransport.disconnect(self.fTryConnect); + return rc; + + def taskBye(self): + """Says goodbye to the TXS""" + rc = self.sendMsg("BYE"); + if rc is True: + rc = self.recvAckLogged("BYE"); + self.oTransport.disconnect(); + return rc; + + def taskVer(self): + """Requests version information from TXS""" + rc = self.sendMsg("VER"); + if rc is True: + rc = False; + cbMsg, sOpcode, abPayload = self.recvReply(); + if cbMsg is not None: + sOpcode = sOpcode.strip(); + if sOpcode == "ACK VER": + sVer = getSZ(abPayload, 0); + if sVer is not None: + rc = sVer; + else: + reporter.maybeErr(self.fErr, 'taskVer got a bad reply: %s' % (sOpcode,)); + else: + reporter.maybeErr(self.fErr, 'taskVer got 3xNone from recvReply.'); + return rc; + + def taskUuid(self): + """Gets the TXS UUID""" + rc = self.sendMsg("UUID"); + if rc is True: + rc = False; + cbMsg, sOpcode, abPayload = self.recvReply(); + if cbMsg is not None: + sOpcode = sOpcode.strip() + if sOpcode == "ACK UUID": + sUuid = getSZ(abPayload, 0); + if sUuid is not None: + sUuid = '{%s}' % (sUuid,) + try: + _ = uuid.UUID(sUuid); + rc = sUuid; + except: + reporter.errorXcpt('taskUuid got an invalid UUID string %s' % (sUuid,)); + else: + reporter.maybeErr(self.fErr, 'taskUuid did not get a UUID string.'); + else: + reporter.maybeErr(self.fErr, 'taskUuid got a bad reply: %s' % (sOpcode,)); + else: + reporter.maybeErr(self.fErr, 'taskUuid got 3xNone from recvReply.'); + return rc; + + # + # Process task + # pylint: disable=missing-docstring + # + + def taskExecEx(self, sExecName, fFlags, asArgs, asAddEnv, oStdIn, oStdOut, oStdErr, oTestPipe, sAsUser): # pylint: disable=too-many-arguments,too-many-locals,too-many-statements,line-too-long + # Construct the payload. + aoPayload = [long(fFlags), '%s' % (sExecName), long(len(asArgs))]; + for sArg in asArgs: + aoPayload.append('%s' % (sArg)); + aoPayload.append(long(len(asAddEnv))); + for sPutEnv in asAddEnv: + aoPayload.append('%s' % (sPutEnv)); + for o in (oStdIn, oStdOut, oStdErr, oTestPipe): + if utils.isString(o): + aoPayload.append(o); + elif o is not None: + aoPayload.append('|'); + o.uTxsClientCrc32 = zlib.crc32(b''); + else: + aoPayload.append(''); + aoPayload.append('%s' % (sAsUser)); + aoPayload.append(long(self.cMsTimeout)); + + # Kick of the EXEC command. + rc = self.sendMsg('EXEC', aoPayload) + if rc is True: + rc = self.recvAckLogged('EXEC'); + if rc is True: + # Loop till the process completes, feed input to the TXS and + # receive output from it. + sFailure = ""; + msPendingInputReply = None; + cbMsg, sOpcode, abPayload = (None, None, None); + while True: + # Pending input? + if msPendingInputReply is None \ + and oStdIn is not None \ + and not utils.isString(oStdIn): + try: + sInput = oStdIn.read(65536); + except: + reporter.errorXcpt('read standard in'); + sFailure = 'exception reading stdin'; + rc = None; + break; + if sInput: + # Convert to a byte array before handing it of to sendMsg or the string + # will get some zero termination added breaking the CRC (and injecting + # unwanted bytes). + abInput = array.array('B', sInput.encode('utf-8')); + oStdIn.uTxsClientCrc32 = zlib.crc32(abInput, oStdIn.uTxsClientCrc32); + rc = self.sendMsg('STDIN', (long(oStdIn.uTxsClientCrc32 & 0xffffffff), abInput)); + if rc is not True: + sFailure = 'sendMsg failure'; + break; + msPendingInputReply = base.timestampMilli(); + continue; + + rc = self.sendMsg('STDINEOS'); + oStdIn = None; + if rc is not True: + sFailure = 'sendMsg failure'; + break; + msPendingInputReply = base.timestampMilli(); + + # Wait for input (500 ms timeout). + if cbMsg is None: + cbMsg, sOpcode, abPayload = self.recvReply(cMsTimeout=500, fNoDataOk=True); + if cbMsg is None: + # Check for time out before restarting the loop. + # Note! Only doing timeout checking here does mean that + # the TXS may prevent us from timing out by + # flooding us with data. This is unlikely though. + if self.hasTimedOut() \ + and ( msPendingInputReply is None \ + or base.timestampMilli() - msPendingInputReply > 30000): + reporter.maybeErr(self.fErr, 'taskExecEx: timed out'); + sFailure = 'timeout'; + rc = None; + break; + # Check that the connection is OK. + if not self.oTransport.isConnectionOk(): + self.oTransport.disconnect(); + sFailure = 'disconnected'; + rc = False; + break; + continue; + + # Handle the response. + sOpcode = sOpcode.rstrip(); + if sOpcode == 'STDOUT': + oOut = oStdOut; + elif sOpcode == 'STDERR': + oOut = oStdErr; + elif sOpcode == 'TESTPIPE': + oOut = oTestPipe; + else: + oOut = None; + if oOut is not None: + # Output from the process. + if len(abPayload) < 4: + sFailure = 'malformed output packet (%s, %u bytes)' % (sOpcode, cbMsg); + reporter.maybeErr(self.fErr, 'taskExecEx: %s' % (sFailure)); + rc = None; + break; + uStreamCrc32 = getU32(abPayload, 0); + oOut.uTxsClientCrc32 = zlib.crc32(abPayload[4:], oOut.uTxsClientCrc32); + if uStreamCrc32 != (oOut.uTxsClientCrc32 & 0xffffffff): + sFailure = 'crc error - mine=%#x their=%#x (%s, %u bytes)' \ + % (oOut.uTxsClientCrc32 & 0xffffffff, uStreamCrc32, sOpcode, cbMsg); + reporter.maybeErr(self.fErr, 'taskExecEx: %s' % (sFailure)); + rc = None; + break; + try: + oOut.write(abPayload[4:]); + except: + sFailure = 'exception writing %s' % (sOpcode); + reporter.errorXcpt('taskExecEx: %s' % (sFailure)); + rc = None; + break; + elif sOpcode == 'STDINIGN' and msPendingInputReply is not None: + # Standard input is ignored. Ignore this condition for now. + msPendingInputReply = None; + reporter.log('taskExecEx: Standard input is ignored... why?'); + del oStdIn.uTxsClientCrc32; + oStdIn = '/dev/null'; + elif sOpcode in ('STDINMEM', 'STDINBAD', 'STDINCRC',)\ + and msPendingInputReply is not None: + # TXS STDIN error, abort. + # TODO: STDINMEM - consider undoing the previous stdin read and try resubmitt it. + msPendingInputReply = None; + sFailure = 'TXS is out of memory for std input buffering'; + reporter.maybeErr(self.fErr, 'taskExecEx: %s' % (sFailure)); + rc = None; + break; + elif sOpcode == 'ACK' and msPendingInputReply is not None: + msPendingInputReply = None; + elif sOpcode.startswith('PROC '): + # Process status message, handle it outside the loop. + rc = True; + break; + else: + sFailure = 'Unexpected opcode %s' % (sOpcode); + reporter.maybeErr(self.fErr, 'taskExecEx: %s' % (sFailure)); + rc = None; + break; + # Clear the message. + cbMsg, sOpcode, abPayload = (None, None, None); + + # If we sent an STDIN packet and didn't get a reply yet, we'll give + # TXS some 5 seconds to reply to this. If we don't wait here we'll + # get screwed later on if we mix it up with the reply to some other + # command. Hackish. + if msPendingInputReply is not None: + cbMsg2, sOpcode2, abPayload2 = self.oTransport.recvMsg(5000); + if cbMsg2 is not None: + reporter.log('taskExecEx: Out of order STDIN, got reply: %s, %s, %s [ignored]' + % (cbMsg2, sOpcode2, abPayload2)); + msPendingInputReply = None; + else: + reporter.maybeErr(self.fErr, 'taskExecEx: Pending STDIN, no reply after 5 secs!'); + self.fScrewedUpMsgState = True; + + # Parse the exit status (True), abort (None) or do nothing (False). + if rc is True: + if sOpcode == 'PROC OK': + pass; + else: + rc = False; + # Do proper parsing some other day if needed: + # PROC TOK, PROC TOA, PROC DWN, PROC DOO, + # PROC NOK + rc, PROC SIG + sig, PROC ABD, FAILED. + if sOpcode == 'PROC DOO': + reporter.log('taskExecEx: PROC DOO[FUS]: %s' % (abPayload,)); + elif sOpcode.startswith('PROC NOK'): + reporter.log('taskExecEx: PROC NOK: rcExit=%s' % (abPayload,)); + elif abPayload and sOpcode.startswith('PROC '): + reporter.log('taskExecEx: %s payload=%s' % (sOpcode, abPayload,)); + + else: + if rc is None: + # Abort it. + reporter.log('taskExecEx: sending ABORT...'); + rc = self.sendMsg('ABORT'); + while rc is True: + cbMsg, sOpcode, abPayload = self.oTransport.recvMsg(30000); + if cbMsg is None: + reporter.maybeErr(self.fErr, 'taskExecEx: Pending ABORT, no reply after 30 secs!') + self.fScrewedUpMsgState = True; + break; + if sOpcode.startswith('PROC '): + reporter.log('taskExecEx: ABORT reply: %s, %s, %s [ignored]' % (cbMsg, sOpcode, abPayload)); + break; + reporter.log('taskExecEx: ABORT in process, ignoring reply: %s, %s, %s' % (cbMsg, sOpcode, abPayload)); + # Check that the connection is OK before looping. + if not self.oTransport.isConnectionOk(): + self.oTransport.disconnect(); + break; + + # Fake response with the reason why we quit. + if sFailure is not None: + self.t3oReply = (0, 'EXECFAIL', sFailure); + rc = None; + else: + rc = None; + + # Cleanup. + for o in (oStdIn, oStdOut, oStdErr, oTestPipe): + if o is not None and not utils.isString(o): + del o.uTxsClientCrc32; # pylint: disable=maybe-no-member + # Make sure all files are closed + o.close(); # pylint: disable=maybe-no-member + reporter.log('taskExecEx: returns %s' % (rc)); + return rc; + + # + # Admin tasks + # + + def hlpRebootShutdownWaitForAck(self, sCmd): + """Wait for reboot/shutodwn ACK.""" + rc = self.recvAckLogged(sCmd); + if rc is True: + # poll a little while for server to disconnect. + uMsStart = base.timestampMilli(); + while self.oTransport.isConnectionOk() \ + and base.timestampMilli() - uMsStart >= 5000: + if self.oTransport.isRecvPending(min(500, self.getMsLeft())): + break; + self.oTransport.disconnect(); + return rc; + + def taskReboot(self): + rc = self.sendMsg('REBOOT'); + if rc is True: + rc = self.hlpRebootShutdownWaitForAck('REBOOT'); + return rc; + + def taskShutdown(self): + rc = self.sendMsg('SHUTDOWN'); + if rc is True: + rc = self.hlpRebootShutdownWaitForAck('SHUTDOWN'); + return rc; + + # + # CD/DVD control tasks. + # + + ## TODO + + # + # File system tasks + # + + def taskMkDir(self, sRemoteDir, fMode): + rc = self.sendMsg('MKDIR', (fMode, sRemoteDir)); + if rc is True: + rc = self.recvAckLogged('MKDIR'); + return rc; + + def taskMkDirPath(self, sRemoteDir, fMode): + rc = self.sendMsg('MKDRPATH', (fMode, sRemoteDir)); + if rc is True: + rc = self.recvAckLogged('MKDRPATH'); + return rc; + + def taskMkSymlink(self, sLinkTarget, sLink): + rc = self.sendMsg('MKSYMLNK', (sLinkTarget, sLink)); + if rc is True: + rc = self.recvAckLogged('MKSYMLNK'); + return rc; + + def taskRmDir(self, sRemoteDir): + rc = self.sendMsg('RMDIR', (sRemoteDir,)); + if rc is True: + rc = self.recvAckLogged('RMDIR'); + return rc; + + def taskRmFile(self, sRemoteFile): + rc = self.sendMsg('RMFILE', (sRemoteFile,)); + if rc is True: + rc = self.recvAckLogged('RMFILE'); + return rc; + + def taskRmSymlink(self, sRemoteSymlink): + rc = self.sendMsg('RMSYMLNK', (sRemoteSymlink,)); + if rc is True: + rc = self.recvAckLogged('RMSYMLNK'); + return rc; + + def taskRmTree(self, sRemoteTree): + rc = self.sendMsg('RMTREE', (sRemoteTree,)); + if rc is True: + rc = self.recvAckLogged('RMTREE'); + return rc; + + def taskChMod(self, sRemotePath, fMode): + rc = self.sendMsg('CHMOD', (int(fMode), sRemotePath,)); + if rc is True: + rc = self.recvAckLogged('CHMOD'); + return rc; + + def taskChOwn(self, sRemotePath, idUser, idGroup): + rc = self.sendMsg('CHOWN', (int(idUser), int(idGroup), sRemotePath,)); + if rc is True: + rc = self.recvAckLogged('CHOWN'); + return rc; + + def taskIsDir(self, sRemoteDir): + rc = self.sendMsg('ISDIR', (sRemoteDir,)); + if rc is True: + rc = self.recvTrueFalse('ISDIR'); + return rc; + + def taskIsFile(self, sRemoteFile): + rc = self.sendMsg('ISFILE', (sRemoteFile,)); + if rc is True: + rc = self.recvTrueFalse('ISFILE'); + return rc; + + def taskIsSymlink(self, sRemoteSymlink): + rc = self.sendMsg('ISSYMLNK', (sRemoteSymlink,)); + if rc is True: + rc = self.recvTrueFalse('ISSYMLNK'); + return rc; + + #def "STAT " + #def "LSTAT " + #def "LIST " + + def taskCopyFile(self, sSrcFile, sDstFile, fMode, fFallbackOkay): + """ Copies a file within the remote from source to destination. """ + _ = fFallbackOkay; # Not used yet. + # Note: If fMode is set to 0, it's up to the target OS' implementation with + # what a file mode the destination file gets created (i.e. via umask). + rc = self.sendMsg('CPFILE', (int(fMode), sSrcFile, sDstFile,)); + if rc is True: + rc = self.recvAckLogged('CPFILE'); + return rc; + + def taskUploadFile(self, sLocalFile, sRemoteFile, fMode, fFallbackOkay): + # + # Open the local file (make sure it exist before bothering TXS) and + # tell TXS that we want to upload a file. + # + try: + oLocalFile = utils.openNoInherit(sLocalFile, 'rb'); + except: + reporter.errorXcpt('taskUpload: failed to open "%s"' % (sLocalFile)); + return False; + + # Common cause with taskUploadStr + rc = self.taskUploadCommon(oLocalFile, sRemoteFile, fMode, fFallbackOkay); + + # Cleanup. + oLocalFile.close(); + return rc; + + def taskUploadString(self, sContent, sRemoteFile, fMode, fFallbackOkay): + # Wrap sContent in a file like class. + class InStringFile(object): # pylint: disable=too-few-public-methods + def __init__(self, sContent): + self.sContent = sContent; + self.off = 0; + + def read(self, cbMax): + cbLeft = len(self.sContent) - self.off; + if cbLeft == 0: + return ""; + if cbLeft <= cbMax: + sRet = self.sContent[self.off:(self.off + cbLeft)]; + else: + sRet = self.sContent[self.off:(self.off + cbMax)]; + self.off = self.off + len(sRet); + return sRet; + + oLocalString = InStringFile(sContent); + return self.taskUploadCommon(oLocalString, sRemoteFile, fMode, fFallbackOkay); + + def taskUploadCommon(self, oLocalFile, sRemoteFile, fMode, fFallbackOkay): + """Common worker used by taskUploadFile and taskUploadString.""" + # + # Command + ACK. + # + # Only used the new PUT2FILE command if we've got a non-zero mode mask. + # Fall back on the old command if the new one is not known by the TXS. + # + if fMode == 0: + rc = self.sendMsg('PUT FILE', (sRemoteFile,)); + if rc is True: + rc = self.recvAckLogged('PUT FILE'); + else: + rc = self.sendMsg('PUT2FILE', (fMode, sRemoteFile)); + if rc is True: + rc = self.recvAck(); + if rc is False: + reporter.maybeErr(self.fErr, 'recvAckLogged: PUT2FILE transport error'); + elif rc is not True: + if rc[0] == 'UNKNOWN' and fFallbackOkay: + # Fallback: + rc = self.sendMsg('PUT FILE', (sRemoteFile,)); + if rc is True: + rc = self.recvAckLogged('PUT FILE'); + else: + reporter.maybeErr(self.fErr, 'recvAckLogged: PUT2FILE response was %s: %s' % (rc[0], rc[1],)); + rc = False; + if rc is True: + # + # Push data packets until eof. + # + uMyCrc32 = zlib.crc32(b''); + while True: + # Read up to 64 KB of data. + try: + sRaw = oLocalFile.read(65536); + except: + rc = None; + break; + + # Convert to array - this is silly! + abBuf = array.array('B'); + if utils.isString(sRaw): + for i, _ in enumerate(sRaw): + abBuf.append(ord(sRaw[i])); + else: + abBuf.extend(sRaw); + sRaw = None; + + # Update the file stream CRC and send it off. + uMyCrc32 = zlib.crc32(abBuf, uMyCrc32); + if not abBuf: + rc = self.sendMsg('DATA EOF', (long(uMyCrc32 & 0xffffffff), )); + else: + rc = self.sendMsg('DATA ', (long(uMyCrc32 & 0xffffffff), abBuf)); + if rc is False: + break; + + # Wait for the reply. + rc = self.recvAck(); + if rc is not True: + if rc is False: + reporter.maybeErr(self.fErr, 'taskUpload: transport error waiting for ACK'); + else: + reporter.maybeErr(self.fErr, 'taskUpload: DATA response was %s: %s' % (rc[0], rc[1])); + rc = False; + break; + + # EOF? + if not abBuf: + break; + + # Send ABORT on ACK and I/O errors. + if rc is None: + rc = self.sendMsg('ABORT'); + if rc is True: + self.recvAckLogged('ABORT'); + rc = False; + return rc; + + def taskDownloadFile(self, sRemoteFile, sLocalFile): + try: + oLocalFile = utils.openNoInherit(sLocalFile, 'wb'); + except: + reporter.errorXcpt('taskDownload: failed to open "%s"' % (sLocalFile)); + return False; + + rc = self.taskDownloadCommon(sRemoteFile, oLocalFile); + + oLocalFile.close(); + if rc is False: + try: + os.remove(sLocalFile); + except: + reporter.errorXcpt(); + return rc; + + def taskDownloadString(self, sRemoteFile, sEncoding = 'utf-8', fIgnoreEncodingErrors = True): + # Wrap sContent in a file like class. + class OutStringFile(object): # pylint: disable=too-few-public-methods + def __init__(self): + self.asContent = []; + + def write(self, sBuf): + self.asContent.append(sBuf); + return None; + + oLocalString = OutStringFile(); + rc = self.taskDownloadCommon(sRemoteFile, oLocalString); + if rc is True: + rc = ''; + for sBuf in oLocalString.asContent: + if hasattr(sBuf, 'decode'): + rc += sBuf.decode(sEncoding, 'ignore' if fIgnoreEncodingErrors else 'strict'); + else: + rc += sBuf; + return rc; + + def taskDownloadCommon(self, sRemoteFile, oLocalFile): + """Common worker for taskDownloadFile and taskDownloadString.""" + rc = self.sendMsg('GET FILE', (sRemoteFile,)) + if rc is True: + # + # Process data packets until eof. + # + uMyCrc32 = zlib.crc32(b''); + while rc is True: + cbMsg, sOpcode, abPayload = self.recvReply(); + if cbMsg is None: + reporter.maybeErr(self.fErr, 'taskDownload got 3xNone from recvReply.'); + rc = None; + break; + + # Validate. + sOpcode = sOpcode.rstrip(); + if sOpcode not in ('DATA', 'DATA EOF',): + reporter.maybeErr(self.fErr, 'taskDownload got a error reply: opcode="%s" details="%s"' + % (sOpcode, getSZ(abPayload, 0, "None"))); + rc = False; + break; + if sOpcode == 'DATA' and len(abPayload) < 4: + reporter.maybeErr(self.fErr, 'taskDownload got a bad DATA packet: len=%u' % (len(abPayload))); + rc = None; + break; + if sOpcode == 'DATA EOF' and len(abPayload) != 4: + reporter.maybeErr(self.fErr, 'taskDownload got a bad EOF packet: len=%u' % (len(abPayload))); + rc = None; + break; + + # Check the CRC (common for both packets). + uCrc32 = getU32(abPayload, 0); + if sOpcode == 'DATA': + uMyCrc32 = zlib.crc32(abPayload[4:], uMyCrc32); + if uCrc32 != (uMyCrc32 & 0xffffffff): + reporter.maybeErr(self.fErr, 'taskDownload got a bad CRC: mycrc=%s remotecrc=%s' + % (hex(uMyCrc32), hex(uCrc32))); + rc = None; + break; + if sOpcode == 'DATA EOF': + rc = self.sendMsg('ACK'); + break; + + # Finally, push the data to the file. + try: + if sys.version_info < (3, 9, 0): + # Removed since Python 3.9. + abData = abPayload[4:].tostring(); + else: + abData = abPayload[4:].tobytes(); + oLocalFile.write(abData); + except: + reporter.errorXcpt('I/O error writing to "%s"' % (sRemoteFile)); + rc = None; + break; + rc = self.sendMsg('ACK'); + + # Send NACK on validation and I/O errors. + if rc is None: + rc = self.sendMsg('NACK'); + rc = False; + return rc; + + def taskPackFile(self, sRemoteFile, sRemoteSource): + rc = self.sendMsg('PKFILE', (sRemoteFile, sRemoteSource)); + if rc is True: + rc = self.recvAckLogged('PKFILE'); + return rc; + + def taskUnpackFile(self, sRemoteFile, sRemoteDir): + rc = self.sendMsg('UNPKFILE', (sRemoteFile, sRemoteDir)); + if rc is True: + rc = self.recvAckLogged('UNPKFILE'); + return rc; + + def taskExpandString(self, sString): + rc = self.sendMsg('EXP STR ', (sString,)); + if rc is True: + rc = False; + cbMsg, sOpcode, abPayload = self.recvReply(); + if cbMsg is not None: + sOpcode = sOpcode.strip(); + if sOpcode == "STRING": + sStringExp = getSZ(abPayload, 0); + if sStringExp is not None: + rc = sStringExp; + else: # Also handles SHORTSTR reply (not enough space to store result). + reporter.maybeErr(self.fErr, 'taskExpandString got a bad reply: %s' % (sOpcode,)); + else: + reporter.maybeErr(self.fErr, 'taskExpandString got 3xNone from recvReply.'); + return rc; + + # pylint: enable=missing-docstring + + + # + # Public methods - generic task queries + # + + def isSuccess(self): + """Returns True if the task completed successfully, otherwise False.""" + self.lockTask(); + sStatus = self.sStatus; + oTaskRc = self.oTaskRc; + self.unlockTask(); + if sStatus != "": + return False; + if oTaskRc is False or oTaskRc is None: + return False; + return True; + + def getResult(self): + """ + Returns the result of a completed task. + Returns None if not completed yet or no previous task. + """ + self.lockTask(); + sStatus = self.sStatus; + oTaskRc = self.oTaskRc; + self.unlockTask(); + if sStatus != "": + return None; + return oTaskRc; + + def getLastReply(self): + """ + Returns the last reply three-tuple: cbMsg, sOpcode, abPayload. + Returns a None, None, None three-tuple if there was no last reply. + """ + self.lockTask(); + t3oReply = self.t3oReply; + self.unlockTask(); + return t3oReply; + + # + # Public methods - connection. + # + + def asyncDisconnect(self, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a disconnect task. + + Returns True on success, False on failure (logged). + + The task returns True on success and False on failure. + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "bye", self.taskBye); + + def syncDisconnect(self, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncDisconnect, cMsTimeout, fIgnoreErrors); + + def asyncVer(self, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a task for getting the TXS version information. + + Returns True on success, False on failure (logged). + + The task returns the version string on success and False on failure. + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "ver", self.taskVer); + + def syncVer(self, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncVer, cMsTimeout, fIgnoreErrors); + + def asyncUuid(self, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a task for getting the TXS UUID. + + Returns True on success, False on failure (logged). + + The task returns UUID string (in {}) on success and False on failure. + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "uuid", self.taskUuid); + + def syncUuid(self, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncUuid, cMsTimeout, fIgnoreErrors); + + # + # Public methods - execution. + # + + def asyncExecEx(self, sExecName, asArgs = (), asAddEnv = (), # pylint: disable=too-many-arguments + oStdIn = None, oStdOut = None, oStdErr = None, oTestPipe = None, + sAsUser = "", cMsTimeout = 3600000, fIgnoreErrors = False): + """ + Initiates a exec process task. + + Returns True on success, False on failure (logged). + + The task returns True if the process exited normally with status code 0. + The task returns None if on failure prior to executing the process, and + False if the process exited with a different status or in an abnormal + manner. Both None and False are logged of course and further info can + also be obtained by getLastReply(). + + The oStdIn, oStdOut, oStdErr and oTestPipe specifiy how to deal with + these streams. If None, no special action is taken and the output goes + to where ever the TXS sends its output, and ditto for input. + - To send to / read from the bitbucket, pass '/dev/null'. + - To redirect to/from a file, just specify the remote filename. + - To append to a file use '>>' followed by the remote filename. + - To pipe the stream to/from the TXS, specify a file like + object. For StdIn a non-blocking read() method is required. For + the other a write() method is required. Watch out for deadlock + conditions between StdIn and StdOut/StdErr/TestPipe piping. + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "exec", self.taskExecEx, + (sExecName, long(0), asArgs, asAddEnv, oStdIn, + oStdOut, oStdErr, oTestPipe, sAsUser)); + + def syncExecEx(self, sExecName, asArgs = (), asAddEnv = (), # pylint: disable=too-many-arguments + oStdIn = '/dev/null', oStdOut = '/dev/null', + oStdErr = '/dev/null', oTestPipe = '/dev/null', + sAsUser = '', cMsTimeout = 3600000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncExecEx, sExecName, asArgs, asAddEnv, oStdIn, oStdOut, \ + oStdErr, oTestPipe, sAsUser, cMsTimeout, fIgnoreErrors); + + def asyncExec(self, sExecName, asArgs = (), asAddEnv = (), sAsUser = "", fWithTestPipe = True, sPrefix = '', \ + cMsTimeout = 3600000, fIgnoreErrors = False): + """ + Initiates a exec process test task. + + Returns True on success, False on failure (logged). + + The task returns True if the process exited normally with status code 0. + The task returns None if on failure prior to executing the process, and + False if the process exited with a different status or in an abnormal + manner. Both None and False are logged of course and further info can + also be obtained by getLastReply(). + + Standard in is taken from /dev/null. While both standard output and + standard error goes directly to reporter.log(). The testpipe is piped + to reporter.xxxx. + """ + + sStdIn = '/dev/null'; + oStdOut = reporter.FileWrapper('%sstdout' % sPrefix); + oStdErr = reporter.FileWrapper('%sstderr' % sPrefix); + if fWithTestPipe: oTestPipe = reporter.FileWrapperTestPipe(); + else: oTestPipe = '/dev/null'; # pylint: disable=redefined-variable-type + + return self.startTask(cMsTimeout, fIgnoreErrors, "exec", self.taskExecEx, + (sExecName, long(0), asArgs, asAddEnv, sStdIn, oStdOut, oStdErr, oTestPipe, sAsUser)); + + def syncExec(self, sExecName, asArgs = (), asAddEnv = (), sAsUser = '', fWithTestPipe = True, sPrefix = '', + cMsTimeout = 3600000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncExec, sExecName, asArgs, asAddEnv, sAsUser, fWithTestPipe, sPrefix, \ + cMsTimeout, fIgnoreErrors); + + # + # Public methods - system + # + + def asyncReboot(self, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a reboot task. + + Returns True on success, False on failure (logged). + + The task returns True on success, False on failure (logged). The + session will be disconnected on successful task completion. + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "reboot", self.taskReboot, ()); + + def syncReboot(self, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncReboot, cMsTimeout, fIgnoreErrors); + + def asyncShutdown(self, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a shutdown task. + + Returns True on success, False on failure (logged). + + The task returns True on success, False on failure (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "shutdown", self.taskShutdown, ()); + + def syncShutdown(self, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncShutdown, cMsTimeout, fIgnoreErrors); + + + # + # Public methods - file system + # + + def asyncMkDir(self, sRemoteDir, fMode = 0o700, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a mkdir task. + + Returns True on success, False on failure (logged). + + The task returns True on success, False on failure (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "mkDir", self.taskMkDir, (sRemoteDir, long(fMode))); + + def syncMkDir(self, sRemoteDir, fMode = 0o700, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncMkDir, sRemoteDir, long(fMode), cMsTimeout, fIgnoreErrors); + + def asyncMkDirPath(self, sRemoteDir, fMode = 0o700, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a mkdir -p task. + + Returns True on success, False on failure (logged). + + The task returns True on success, False on failure (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "mkDirPath", self.taskMkDirPath, (sRemoteDir, long(fMode))); + + def syncMkDirPath(self, sRemoteDir, fMode = 0o700, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncMkDirPath, sRemoteDir, long(fMode), cMsTimeout, fIgnoreErrors); + + def asyncMkSymlink(self, sLinkTarget, sLink, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a symlink task. + + Returns True on success, False on failure (logged). + + The task returns True on success, False on failure (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "mkSymlink", self.taskMkSymlink, (sLinkTarget, sLink)); + + def syncMkSymlink(self, sLinkTarget, sLink, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncMkSymlink, sLinkTarget, sLink, cMsTimeout, fIgnoreErrors); + + def asyncRmDir(self, sRemoteDir, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a rmdir task. + + Returns True on success, False on failure (logged). + + The task returns True on success, False on failure (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "rmDir", self.taskRmDir, (sRemoteDir,)); + + def syncRmDir(self, sRemoteDir, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncRmDir, sRemoteDir, cMsTimeout, fIgnoreErrors); + + def asyncRmFile(self, sRemoteFile, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a rmfile task. + + Returns True on success, False on failure (logged). + + The task returns True on success, False on failure (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "rmFile", self.taskRmFile, (sRemoteFile,)); + + def syncRmFile(self, sRemoteFile, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncRmFile, sRemoteFile, cMsTimeout, fIgnoreErrors); + + def asyncRmSymlink(self, sRemoteSymlink, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a rmsymlink task. + + Returns True on success, False on failure (logged). + + The task returns True on success, False on failure (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "rmSymlink", self.taskRmSymlink, (sRemoteSymlink,)); + + def syncRmSymlink(self, sRemoteSymlink, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncRmSymlink, sRemoteSymlink, cMsTimeout, fIgnoreErrors); + + def asyncRmTree(self, sRemoteTree, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a rmtree task. + + Returns True on success, False on failure (logged). + + The task returns True on success, False on failure (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "rmTree", self.taskRmTree, (sRemoteTree,)); + + def syncRmTree(self, sRemoteTree, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncRmTree, sRemoteTree, cMsTimeout, fIgnoreErrors); + + def asyncChMod(self, sRemotePath, fMode, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a chmod task. + + Returns True on success, False on failure (logged). + + The task returns True on success, False on failure (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "chMod", self.taskChMod, (sRemotePath, fMode)); + + def syncChMod(self, sRemotePath, fMode, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncChMod, sRemotePath, fMode, cMsTimeout, fIgnoreErrors); + + def asyncChOwn(self, sRemotePath, idUser, idGroup, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a chown task. + + Returns True on success, False on failure (logged). + + The task returns True on success, False on failure (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "chOwn", self.taskChOwn, (sRemotePath, idUser, idGroup)); + + def syncChOwn(self, sRemotePath, idUser, idGroup, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncChMod, sRemotePath, idUser, idGroup, cMsTimeout, fIgnoreErrors); + + def asyncIsDir(self, sRemoteDir, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a is-dir query task. + + Returns True on success, False on failure (logged). + + The task returns True if it's a directory, False if it isn't, and + None on error (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "isDir", self.taskIsDir, (sRemoteDir,)); + + def syncIsDir(self, sRemoteDir, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncIsDir, sRemoteDir, cMsTimeout, fIgnoreErrors); + + def asyncIsFile(self, sRemoteFile, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a is-file query task. + + Returns True on success, False on failure (logged). + + The task returns True if it's a file, False if it isn't, and None on + error (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "isFile", self.taskIsFile, (sRemoteFile,)); + + def syncIsFile(self, sRemoteFile, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncIsFile, sRemoteFile, cMsTimeout, fIgnoreErrors); + + def asyncIsSymlink(self, sRemoteSymlink, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a is-symbolic-link query task. + + Returns True on success, False on failure (logged). + + The task returns True if it's a symbolic linke, False if it isn't, and + None on error (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "isSymlink", self.taskIsSymlink, (sRemoteSymlink,)); + + def syncIsSymlink(self, sRemoteSymlink, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncIsSymlink, sRemoteSymlink, cMsTimeout, fIgnoreErrors); + + #def "STAT " + #def "LSTAT " + #def "LIST " + + @staticmethod + def calcFileXferTimeout(cbFile): + """ + Calculates a reasonable timeout for an upload/download given the file size. + + Returns timeout in milliseconds. + """ + return 30000 + cbFile / 32; # 32 KiB/s (picked out of thin air) + + @staticmethod + def calcUploadTimeout(sLocalFile): + """ + Calculates a reasonable timeout for an upload given the file (will stat it). + + Returns timeout in milliseconds. + """ + try: cbFile = os.path.getsize(sLocalFile); + except: cbFile = 1024*1024; + return Session.calcFileXferTimeout(cbFile); + + def asyncCopyFile(self, sSrcFile, sDstFile, + fMode = 0, fFallbackOkay = True, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a file copying task on the remote. + + Returns True on success, False on failure (logged). + + The task returns True on success, False on failure (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "cpfile", + self.taskCopyFile, (sSrcFile, sDstFile, fMode, fFallbackOkay)); + + def syncCopyFile(self, sSrcFile, sDstFile, fMode = 0, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncCopyFile, sSrcFile, sDstFile, fMode, cMsTimeout, fIgnoreErrors); + + def asyncUploadFile(self, sLocalFile, sRemoteFile, + fMode = 0, fFallbackOkay = True, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a download query task. + + Returns True on success, False on failure (logged). + + The task returns True on success, False on failure (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "upload", + self.taskUploadFile, (sLocalFile, sRemoteFile, fMode, fFallbackOkay)); + + def syncUploadFile(self, sLocalFile, sRemoteFile, fMode = 0, fFallbackOkay = True, cMsTimeout = 0, fIgnoreErrors = False): + """Synchronous version.""" + if cMsTimeout <= 0: + cMsTimeout = self.calcUploadTimeout(sLocalFile); + return self.asyncToSync(self.asyncUploadFile, sLocalFile, sRemoteFile, fMode, fFallbackOkay, cMsTimeout, fIgnoreErrors); + + def asyncUploadString(self, sContent, sRemoteFile, + fMode = 0, fFallbackOkay = True, cMsTimeout = 0, fIgnoreErrors = False): + """ + Initiates a upload string task. + + Returns True on success, False on failure (logged). + + The task returns True on success, False on failure (logged). + """ + if cMsTimeout <= 0: + cMsTimeout = self.calcFileXferTimeout(len(sContent)); + return self.startTask(cMsTimeout, fIgnoreErrors, "uploadString", + self.taskUploadString, (sContent, sRemoteFile, fMode, fFallbackOkay)); + + def syncUploadString(self, sContent, sRemoteFile, fMode = 0, fFallbackOkay = True, cMsTimeout = 0, fIgnoreErrors = False): + """Synchronous version.""" + if cMsTimeout <= 0: + cMsTimeout = self.calcFileXferTimeout(len(sContent)); + return self.asyncToSync(self.asyncUploadString, sContent, sRemoteFile, fMode, fFallbackOkay, cMsTimeout, fIgnoreErrors); + + def asyncDownloadFile(self, sRemoteFile, sLocalFile, cMsTimeout = 120000, fIgnoreErrors = False): + """ + Initiates a download file task. + + Returns True on success, False on failure (logged). + + The task returns True on success, False on failure (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "downloadFile", self.taskDownloadFile, (sRemoteFile, sLocalFile)); + + def syncDownloadFile(self, sRemoteFile, sLocalFile, cMsTimeout = 120000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncDownloadFile, sRemoteFile, sLocalFile, cMsTimeout, fIgnoreErrors); + + def asyncDownloadString(self, sRemoteFile, sEncoding = 'utf-8', fIgnoreEncodingErrors = True, + cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a download string task. + + Returns True on success, False on failure (logged). + + The task returns a byte string on success, False on failure (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "downloadString", + self.taskDownloadString, (sRemoteFile, sEncoding, fIgnoreEncodingErrors)); + + def syncDownloadString(self, sRemoteFile, sEncoding = 'utf-8', fIgnoreEncodingErrors = True, + cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncDownloadString, sRemoteFile, sEncoding, fIgnoreEncodingErrors, + cMsTimeout, fIgnoreErrors); + + def asyncPackFile(self, sRemoteFile, sRemoteSource, cMsTimeout = 120000, fIgnoreErrors = False): + """ + Initiates a packing file/directory task. + + Returns True on success, False on failure (logged). + + The task returns True on success, False on failure (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "packFile", self.taskPackFile, + (sRemoteFile, sRemoteSource)); + + def syncPackFile(self, sRemoteFile, sRemoteSource, cMsTimeout = 120000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncPackFile, sRemoteFile, sRemoteSource, cMsTimeout, fIgnoreErrors); + + def asyncUnpackFile(self, sRemoteFile, sRemoteDir, cMsTimeout = 120000, fIgnoreErrors = False): + """ + Initiates a unpack file task. + + Returns True on success, False on failure (logged). + + The task returns True on success, False on failure (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "unpackFile", self.taskUnpackFile, + (sRemoteFile, sRemoteDir)); + + def syncUnpackFile(self, sRemoteFile, sRemoteDir, cMsTimeout = 120000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncUnpackFile, sRemoteFile, sRemoteDir, cMsTimeout, fIgnoreErrors); + + def asyncExpandString(self, sString, cMsTimeout = 120000, fIgnoreErrors = False): + """ + Initiates an expand string task. + + Returns expanded string on success, False on failure (logged). + + The task returns True on success, False on failure (logged). + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "expandString", + self.taskExpandString, (sString,)); + + def syncExpandString(self, sString, cMsTimeout = 120000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncExpandString, sString, cMsTimeout, fIgnoreErrors); + + +class TransportTcp(TransportBase): + """ + TCP transport layer for the TXS client session class. + """ + + def __init__(self, sHostname, uPort, fReversedSetup): + """ + Save the parameters. The session will call us back to make the + connection later on its worker thread. + """ + TransportBase.__init__(self, utils.getCallerName()); + self.sHostname = sHostname; + self.fReversedSetup = fReversedSetup; + self.uPort = uPort if uPort is not None else 5042 if fReversedSetup is False else 5048; + self.oSocket = None; + self.oWakeupW = None; + self.oWakeupR = None; + self.fConnectCanceled = False; + self.fIsConnecting = False; + self.oCv = threading.Condition(); + self.abReadAhead = array.array('B'); + + def toString(self): + return '<%s sHostname=%s, fReversedSetup=%s, uPort=%s, oSocket=%s,'\ + ' fConnectCanceled=%s, fIsConnecting=%s, oCv=%s, abReadAhead=%s>' \ + % (TransportBase.toString(self), self.sHostname, self.fReversedSetup, self.uPort, self.oSocket, + self.fConnectCanceled, self.fIsConnecting, self.oCv, self.abReadAhead); + + def __isInProgressXcpt(self, oXcpt): + """ In progress exception? """ + try: + if isinstance(oXcpt, socket.error): + try: + if oXcpt.errno == errno.EINPROGRESS: + return True; + except: pass; + # Windows? + try: + if oXcpt.errno == errno.EWOULDBLOCK: + return True; + except: pass; + except: + pass; + return False; + + def __isWouldBlockXcpt(self, oXcpt): + """ Would block exception? """ + try: + if isinstance(oXcpt, socket.error): + try: + if oXcpt.errno == errno.EWOULDBLOCK: + return True; + except: pass; + try: + if oXcpt.errno == errno.EAGAIN: + return True; + except: pass; + except: + pass; + return False; + + def __isConnectionReset(self, oXcpt): + """ Connection reset by Peer or others. """ + try: + if isinstance(oXcpt, socket.error): + try: + if oXcpt.errno == errno.ECONNRESET: + return True; + except: pass; + try: + if oXcpt.errno == errno.ENETRESET: + return True; + except: pass; + except: + pass; + return False; + + def _closeWakeupSockets(self): + """ Closes the wakup sockets. Caller should own the CV. """ + oWakeupR = self.oWakeupR; + self.oWakeupR = None; + if oWakeupR is not None: + oWakeupR.close(); + + oWakeupW = self.oWakeupW; + self.oWakeupW = None; + if oWakeupW is not None: + oWakeupW.close(); + + return None; + + def cancelConnect(self): + # This is bad stuff. + self.oCv.acquire(); + reporter.log2('TransportTcp::cancelConnect: fIsConnecting=%s oSocket=%s' % (self.fIsConnecting, self.oSocket)); + self.fConnectCanceled = True; + if self.fIsConnecting: + oSocket = self.oSocket; + self.oSocket = None; + if oSocket is not None: + reporter.log2('TransportTcp::cancelConnect: closing the socket'); + oSocket.close(); + + oWakeupW = self.oWakeupW; + self.oWakeupW = None; + if oWakeupW is not None: + reporter.log2('TransportTcp::cancelConnect: wakeup call'); + try: oWakeupW.send(b'cancelled!\n'); + except: reporter.logXcpt(); + try: oWakeupW.shutdown(socket.SHUT_WR); + except: reporter.logXcpt(); + oWakeupW.close(); + self.oCv.release(); + + def _connectAsServer(self, oSocket, oWakeupR, cMsTimeout): + """ Connects to the TXS server as server, i.e. the reversed setup. """ + assert(self.fReversedSetup); + + reporter.log2('TransportTcp::_connectAsServer: oSocket=%s, cMsTimeout=%u' % (oSocket, cMsTimeout)); + + # Workaround for bind() failure... + try: + oSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1); + except: + reporter.errorXcpt('socket.listen(1) failed'); + return None; + + # Bind the socket and make it listen. + try: + oSocket.bind((self.sHostname, self.uPort)); + except: + reporter.errorXcpt('socket.bind((%s,%s)) failed' % (self.sHostname, self.uPort)); + return None; + try: + oSocket.listen(1); + except: + reporter.errorXcpt('socket.listen(1) failed'); + return None; + + # Accept connections. + oClientSocket = None; + tClientAddr = None; + try: + (oClientSocket, tClientAddr) = oSocket.accept(); + except socket.error as e: + if not self.__isInProgressXcpt(e): + raise; + + # Do the actual waiting. + reporter.log2('TransportTcp::accept: operation in progress (%s)...' % (e,)); + try: + select.select([oSocket, oWakeupR], [], [oSocket, oWakeupR], cMsTimeout / 1000.0); + except socket.error as oXctp: + if oXctp.errno != errno.EBADF or not self.fConnectCanceled: + raise; + reporter.log('socket.select() on accept was canceled'); + return None; + except: + reporter.logXcpt('socket.select() on accept'); + + # Try accept again. + try: + (oClientSocket, tClientAddr) = oSocket.accept(); + except socket.error as oXcpt: + if not self.__isInProgressXcpt(e): + if oXcpt.errno != errno.EBADF or not self.fConnectCanceled: + raise; + reporter.log('socket.accept() was canceled'); + return None; + reporter.log('socket.accept() timed out'); + return False; + except: + reporter.errorXcpt('socket.accept() failed'); + return None; + except: + reporter.errorXcpt('socket.accept() failed'); + return None; + + # Store the connected socket and throw away the server socket. + self.oCv.acquire(); + if not self.fConnectCanceled: + self.oSocket.close(); + self.oSocket = oClientSocket; + self.sHostname = "%s:%s" % (tClientAddr[0], tClientAddr[1]); + self.oCv.release(); + return True; + + def _connectAsClient(self, oSocket, oWakeupR, cMsTimeout): + """ Connects to the TXS server as client. """ + assert(not self.fReversedSetup); + + # Connect w/ timeouts. + rc = None; + try: + oSocket.connect((self.sHostname, self.uPort)); + rc = True; + except socket.error as oXcpt: + iRc = oXcpt.errno; + if self.__isInProgressXcpt(oXcpt): + # Do the actual waiting. + reporter.log2('TransportTcp::connect: operation in progress (%s)...' % (oXcpt,)); + try: + ttRc = select.select([oWakeupR], [oSocket], [oSocket, oWakeupR], cMsTimeout / 1000.0); + if len(ttRc[1]) + len(ttRc[2]) == 0: + raise socket.error(errno.ETIMEDOUT, 'select timed out'); + iRc = oSocket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR); + rc = iRc == 0; + except socket.error as oXcpt2: + iRc = oXcpt2.errno; + except: + iRc = -42; + reporter.fatalXcpt('socket.select() on connect failed'); + + if rc is True: + pass; + elif iRc in (errno.ECONNREFUSED, errno.EHOSTUNREACH, errno.EINTR, errno.ENETDOWN, errno.ENETUNREACH, errno.ETIMEDOUT): + rc = False; # try again. + else: + if iRc != errno.EBADF or not self.fConnectCanceled: + reporter.fatalXcpt('socket.connect((%s,%s)) failed; iRc=%s' % (self.sHostname, self.uPort, iRc)); + reporter.log2('TransportTcp::connect: rc=%s iRc=%s' % (rc, iRc)); + except: + reporter.fatalXcpt('socket.connect((%s,%s)) failed' % (self.sHostname, self.uPort)); + return rc; + + + def connect(self, cMsTimeout): + # Create a non-blocking socket. + reporter.log2('TransportTcp::connect: cMsTimeout=%s sHostname=%s uPort=%s' % (cMsTimeout, self.sHostname, self.uPort)); + try: + oSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0); + except: + reporter.fatalXcpt('socket.socket() failed'); + return None; + try: + oSocket.setblocking(0); + except: + oSocket.close(); + reporter.fatalXcpt('socket.socket() failed'); + return None; + + # Create wakeup socket pair for unix (select doesn't wake up on socket close on Linux). + oWakeupR = None; + oWakeupW = None; + if hasattr(socket, 'socketpair'): + try: (oWakeupR, oWakeupW) = socket.socketpair(); # pylint: disable=no-member + except: reporter.logXcpt('socket.socketpair() failed'); + + # Update the state. + self.oCv.acquire(); + rc = None; + if not self.fConnectCanceled: + self.oSocket = oSocket; + self.oWakeupW = oWakeupW; + self.oWakeupR = oWakeupR; + self.fIsConnecting = True; + self.oCv.release(); + + # Try connect. + if oWakeupR is None: + oWakeupR = oSocket; # Avoid select failure. + if self.fReversedSetup: + rc = self._connectAsServer(oSocket, oWakeupR, cMsTimeout); + else: + rc = self._connectAsClient(oSocket, oWakeupR, cMsTimeout); + oSocket = None; + + # Update the state and cleanup on failure/cancel. + self.oCv.acquire(); + if rc is True and self.fConnectCanceled: + rc = False; + self.fIsConnecting = False; + + if rc is not True: + if self.oSocket is not None: + self.oSocket.close(); + self.oSocket = None; + self._closeWakeupSockets(); + self.oCv.release(); + + reporter.log2('TransportTcp::connect: returning %s' % (rc,)); + return rc; + + def disconnect(self, fQuiet = False): + if self.oSocket is not None: + self.abReadAhead = array.array('B'); + + # Try a shutting down the socket gracefully (draining it). + try: + self.oSocket.shutdown(socket.SHUT_WR); + except: + if not fQuiet: + reporter.error('shutdown(SHUT_WR)'); + try: + self.oSocket.setblocking(0); # just in case it's not set. + sData = "1"; + while sData: + sData = self.oSocket.recv(16384); + except: + pass; + + # Close it. + self.oCv.acquire(); + try: self.oSocket.setblocking(1); + except: pass; + self.oSocket.close(); + self.oSocket = None; + else: + self.oCv.acquire(); + self._closeWakeupSockets(); + self.oCv.release(); + + def sendBytes(self, abBuf, cMsTimeout): + if self.oSocket is None: + reporter.error('TransportTcp.sendBytes: No connection.'); + return False; + + # Try send it all. + try: + cbSent = self.oSocket.send(abBuf); + if cbSent == len(abBuf): + return True; + except Exception as oXcpt: + if not self.__isWouldBlockXcpt(oXcpt): + reporter.errorXcpt('TranportTcp.sendBytes: %s bytes' % (len(abBuf))); + return False; + cbSent = 0; + + # Do a timed send. + msStart = base.timestampMilli(); + while True: + cMsElapsed = base.timestampMilli() - msStart; + if cMsElapsed > cMsTimeout: + reporter.error('TranportTcp.sendBytes: %s bytes timed out (1)' % (len(abBuf))); + break; + + # wait. + try: + ttRc = select.select([], [self.oSocket], [self.oSocket], (cMsTimeout - cMsElapsed) / 1000.0); + if ttRc[2] and not ttRc[1]: + reporter.error('TranportTcp.sendBytes: select returned with exception'); + break; + if not ttRc[1]: + reporter.error('TranportTcp.sendBytes: %s bytes timed out (2)' % (len(abBuf))); + break; + except: + reporter.errorXcpt('TranportTcp.sendBytes: select failed'); + break; + + # Try send more. + try: + cbSent += self.oSocket.send(abBuf[cbSent:]); + if cbSent == len(abBuf): + return True; + except Exception as oXcpt: + if not self.__isWouldBlockXcpt(oXcpt): + reporter.errorXcpt('TranportTcp.sendBytes: %s bytes' % (len(abBuf))); + break; + + return False; + + def __returnReadAheadBytes(self, cb): + """ Internal worker for recvBytes. """ + assert(len(self.abReadAhead) >= cb); + abRet = self.abReadAhead[:cb]; + self.abReadAhead = self.abReadAhead[cb:]; + return abRet; + + def recvBytes(self, cb, cMsTimeout, fNoDataOk): + if self.oSocket is None: + reporter.error('TransportTcp.recvBytes(%s,%s): No connection.' % (cb, cMsTimeout)); + return None; + + # Try read in some more data without bothering with timeout handling first. + if len(self.abReadAhead) < cb: + try: + abBuf = self.oSocket.recv(cb - len(self.abReadAhead)); + if abBuf: + self.abReadAhead.extend(array.array('B', abBuf)); + except Exception as oXcpt: + if not self.__isWouldBlockXcpt(oXcpt): + reporter.errorXcpt('TranportTcp.recvBytes: 0/%s bytes' % (cb,)); + return None; + + if len(self.abReadAhead) >= cb: + return self.__returnReadAheadBytes(cb); + + # Timeout loop. + msStart = base.timestampMilli(); + while True: + cMsElapsed = base.timestampMilli() - msStart; + if cMsElapsed > cMsTimeout: + if not fNoDataOk or self.abReadAhead: + reporter.error('TranportTcp.recvBytes: %s/%s bytes timed out (1)' % (len(self.abReadAhead), cb)); + break; + + # Wait. + try: + ttRc = select.select([self.oSocket], [], [self.oSocket], (cMsTimeout - cMsElapsed) / 1000.0); + if ttRc[2] and not ttRc[0]: + reporter.error('TranportTcp.recvBytes: select returned with exception'); + break; + if not ttRc[0]: + if not fNoDataOk or self.abReadAhead: + reporter.error('TranportTcp.recvBytes: %s/%s bytes timed out (2) fNoDataOk=%s' + % (len(self.abReadAhead), cb, fNoDataOk)); + break; + except: + reporter.errorXcpt('TranportTcp.recvBytes: select failed'); + break; + + # Try read more. + try: + abBuf = self.oSocket.recv(cb - len(self.abReadAhead)); + if not abBuf: + reporter.error('TranportTcp.recvBytes: %s/%s bytes (%s) - connection has been shut down' + % (len(self.abReadAhead), cb, fNoDataOk)); + self.disconnect(); + return None; + + self.abReadAhead.extend(array.array('B', abBuf)); + + except Exception as oXcpt: + reporter.log('recv => exception %s' % (oXcpt,)); + if not self.__isWouldBlockXcpt(oXcpt): + if not fNoDataOk or not self.__isConnectionReset(oXcpt) or self.abReadAhead: + reporter.errorXcpt('TranportTcp.recvBytes: %s/%s bytes (%s)' % (len(self.abReadAhead), cb, fNoDataOk)); + break; + + # Done? + if len(self.abReadAhead) >= cb: + return self.__returnReadAheadBytes(cb); + + #reporter.log('recv => None len(self.abReadAhead) -> %d' % (len(self.abReadAhead), )); + return None; + + def isConnectionOk(self): + if self.oSocket is None: + return False; + try: + ttRc = select.select([], [], [self.oSocket], 0.0); + if ttRc[2]: + return False; + + self.oSocket.send(array.array('B')); # send zero bytes. + except: + return False; + return True; + + def isRecvPending(self, cMsTimeout = 0): + try: + ttRc = select.select([self.oSocket], [], [], cMsTimeout / 1000.0); + if not ttRc[0]: + return False; + except: + pass; + return True; + + +def openTcpSession(cMsTimeout, sHostname, uPort = None, fReversedSetup = False, cMsIdleFudge = 0, fnProcessEvents = None): + """ + Opens a connection to a Test Execution Service via TCP, given its name. + + The optional fnProcessEvents callback should be set to vbox.processPendingEvents + or similar. + """ + reporter.log2('openTcpSession(%s, %s, %s, %s, %s)' % + (cMsTimeout, sHostname, uPort, fReversedSetup, cMsIdleFudge)); + try: + oTransport = TransportTcp(sHostname, uPort, fReversedSetup); + oSession = Session(oTransport, cMsTimeout, cMsIdleFudge, fnProcessEvents = fnProcessEvents); + except: + reporter.errorXcpt(None, 15); + return None; + return oSession; + + +def tryOpenTcpSession(cMsTimeout, sHostname, uPort = None, fReversedSetup = False, cMsIdleFudge = 0, fnProcessEvents = None): + """ + Tries to open a connection to a Test Execution Service via TCP, given its name. + + This differs from openTcpSession in that it won't log a connection failure + as an error. + """ + try: + oTransport = TransportTcp(sHostname, uPort, fReversedSetup); + oSession = Session(oTransport, cMsTimeout, cMsIdleFudge, fTryConnect = True, fnProcessEvents = fnProcessEvents); + except: + reporter.errorXcpt(None, 15); + return None; + return oSession; diff --git a/src/VBox/ValidationKit/testdriver/vbox.py b/src/VBox/ValidationKit/testdriver/vbox.py new file mode 100755 index 00000000..89bb7ff8 --- /dev/null +++ b/src/VBox/ValidationKit/testdriver/vbox.py @@ -0,0 +1,4569 @@ +# -*- coding: utf-8 -*- +# $Id: vbox.py $ +# pylint: disable=too-many-lines + +""" +VirtualBox Specific base testdriver. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + +# pylint: disable=unnecessary-semicolon + +# Standard Python imports. +import datetime +import os +import platform +import re; +import sys +import threading +import time +import traceback + +# Figure out where the validation kit lives and make sure it's in the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))); +if g_ksValidationKitDir not in sys.path: + sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from common import utils; +from testdriver import base; +from testdriver import btresolver; +from testdriver import reporter; +from testdriver import vboxcon; +from testdriver import vboxtestvms; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + xrange = range; # pylint: disable=redefined-builtin,invalid-name + long = int; # pylint: disable=redefined-builtin,invalid-name + +# +# Exception and Error Unification Hacks. +# Note! This is pretty gross stuff. Be warned! +# TODO: Find better ways of doing these things, preferrably in vboxapi. +# + +ComException = None; # pylint: disable=invalid-name +__fnComExceptionGetAttr__ = None; # pylint: disable=invalid-name + +def __MyDefaultGetAttr(oSelf, sName): + """ __getattribute__/__getattr__ default fake.""" + try: + oAttr = oSelf.__dict__[sName]; + except: + oAttr = dir(oSelf)[sName]; + return oAttr; + +def __MyComExceptionGetAttr(oSelf, sName): + """ ComException.__getattr__ wrapper - both XPCOM and COM. """ + try: + oAttr = __fnComExceptionGetAttr__(oSelf, sName); + except AttributeError: + if platform.system() == 'Windows': + if sName == 'errno': + oAttr = __fnComExceptionGetAttr__(oSelf, 'hresult'); + elif sName == 'msg': + oAttr = __fnComExceptionGetAttr__(oSelf, 'strerror'); + else: + raise; + else: + if sName == 'hresult': + oAttr = __fnComExceptionGetAttr__(oSelf, 'errno'); + elif sName == 'strerror': + oAttr = __fnComExceptionGetAttr__(oSelf, 'msg'); + elif sName == 'excepinfo': + oAttr = None; + elif sName == 'argerror': + oAttr = None; + else: + raise; + #print '__MyComExceptionGetAttr(,%s) -> "%s"' % (sName, oAttr); + return oAttr; + +def __deployExceptionHacks__(oNativeComExceptionClass): + """ + Deploys the exception and error hacks that helps unifying COM and XPCOM + exceptions and errors. + """ + global ComException # pylint: disable=invalid-name + global __fnComExceptionGetAttr__ # pylint: disable=invalid-name + + # Hook up our attribute getter for the exception class (ASSUMES new-style). + if __fnComExceptionGetAttr__ is None: + try: + __fnComExceptionGetAttr__ = getattr(oNativeComExceptionClass, '__getattr__'); + except: + try: + __fnComExceptionGetAttr__ = getattr(oNativeComExceptionClass, '__getattribute__'); + except: + __fnComExceptionGetAttr__ = __MyDefaultGetAttr; + setattr(oNativeComExceptionClass, '__getattr__', __MyComExceptionGetAttr) + + # Make the modified classes accessible (are there better ways to do this?) + ComException = oNativeComExceptionClass + return None; + + + +# +# Utility functions. +# + +def isIpAddrValid(sIpAddr): + """ + Checks if a IPv4 address looks valid. This will return false for + localhost and similar. + Returns True / False. + """ + if sIpAddr is None: return False; + if len(sIpAddr.split('.')) != 4: return False; + if sIpAddr.endswith('.0'): return False; + if sIpAddr.endswith('.255'): return False; + if sIpAddr.startswith('127.'): return False; + if sIpAddr.startswith('169.254.'): return False; + if sIpAddr.startswith('192.0.2.'): return False; + if sIpAddr.startswith('224.0.0.'): return False; + return True; + +def stringifyErrorInfo(oErrInfo): + """ + Stringifies the error information in a IVirtualBoxErrorInfo object. + + Returns string with error info. + """ + try: + rc = oErrInfo.resultCode; + sText = oErrInfo.text; + sIid = oErrInfo.interfaceID; + sComponent = oErrInfo.component; + except: + sRet = 'bad error object (%s)?' % (oErrInfo,); + traceback.print_exc(); + else: + sRet = 'rc=%s text="%s" IID=%s component=%s' % (ComError.toString(rc), sText, sIid, sComponent); + return sRet; + +def reportError(oErr, sText): + """ + Report a VirtualBox error on oErr. oErr can be IVirtualBoxErrorInfo + or IProgress. Anything else is ignored. + + Returns the same a reporter.error(). + """ + try: + oErrObj = oErr.errorInfo; # IProgress. + except: + oErrObj = oErr; + reporter.error(sText); + return reporter.error(stringifyErrorInfo(oErrObj)); + +def formatComOrXpComException(oType, oXcpt): + """ + Callback installed with the reporter to better format COM exceptions. + Similar to format_exception_only, only it returns None if not interested. + """ + _ = oType; + oVBoxMgr = vboxcon.goHackModuleClass.oVBoxMgr; + if oVBoxMgr is None: + return None; + if not oVBoxMgr.xcptIsOurXcptKind(oXcpt): # pylint: disable=not-callable + return None; + + if platform.system() == 'Windows': + hrc = oXcpt.hresult; + if hrc == ComError.DISP_E_EXCEPTION and oXcpt.excepinfo is not None and len(oXcpt.excepinfo) > 5: + hrc = oXcpt.excepinfo[5]; + sWhere = oXcpt.excepinfo[1]; + sMsg = oXcpt.excepinfo[2]; + else: + sWhere = None; + sMsg = oXcpt.strerror; + else: + hrc = oXcpt.errno; + sWhere = None; + sMsg = oXcpt.msg; + + sHrc = oVBoxMgr.xcptToString(hrc); # pylint: disable=not-callable + if sHrc.find('(') < 0: + sHrc = '%s (%#x)' % (sHrc, hrc & 0xffffffff,); + + asRet = ['COM-Xcpt: %s' % (sHrc,)]; + if sMsg and sWhere: + asRet.append('--------- %s: %s' % (sWhere, sMsg,)); + elif sMsg: + asRet.append('--------- %s' % (sMsg,)); + return asRet; + #if sMsg and sWhere: + # return ['COM-Xcpt: %s - %s: %s' % (sHrc, sWhere, sMsg,)]; + #if sMsg: + # return ['COM-Xcpt: %s - %s' % (sHrc, sMsg,)]; + #return ['COM-Xcpt: %s' % (sHrc,)]; + +# +# Classes +# + +class ComError(object): + """ + Unified COM and XPCOM status code repository. + This works more like a module than a class since it's replacing a module. + """ + + # The VBOX_E_XXX bits: + __VBOX_E_BASE = -2135228416; + VBOX_E_OBJECT_NOT_FOUND = __VBOX_E_BASE + 1; + VBOX_E_INVALID_VM_STATE = __VBOX_E_BASE + 2; + VBOX_E_VM_ERROR = __VBOX_E_BASE + 3; + VBOX_E_FILE_ERROR = __VBOX_E_BASE + 4; + VBOX_E_IPRT_ERROR = __VBOX_E_BASE + 5; + VBOX_E_PDM_ERROR = __VBOX_E_BASE + 6; + VBOX_E_INVALID_OBJECT_STATE = __VBOX_E_BASE + 7; + VBOX_E_HOST_ERROR = __VBOX_E_BASE + 8; + VBOX_E_NOT_SUPPORTED = __VBOX_E_BASE + 9; + VBOX_E_XML_ERROR = __VBOX_E_BASE + 10; + VBOX_E_INVALID_SESSION_STATE = __VBOX_E_BASE + 11; + VBOX_E_OBJECT_IN_USE = __VBOX_E_BASE + 12; + VBOX_E_DONT_CALL_AGAIN = __VBOX_E_BASE + 13; + + # Reverse lookup table. + dDecimalToConst = {}; # pylint: disable=invalid-name + + def __init__(self): + raise base.GenError('No instances, please'); + + @staticmethod + def copyErrors(oNativeComErrorClass): + """ + Copy all error codes from oNativeComErrorClass to this class and + install compatability mappings. + """ + + # First, add the VBOX_E_XXX constants to dDecimalToConst. + for sAttr in dir(ComError): + if sAttr.startswith('VBOX_E'): + oAttr = getattr(ComError, sAttr); + ComError.dDecimalToConst[oAttr] = sAttr; + + # Copy all error codes from oNativeComErrorClass to this class. + for sAttr in dir(oNativeComErrorClass): + if sAttr[0].isupper(): + oAttr = getattr(oNativeComErrorClass, sAttr); + setattr(ComError, sAttr, oAttr); + if isinstance(oAttr, int): + ComError.dDecimalToConst[oAttr] = sAttr; + + # Install mappings to the other platform. + if platform.system() == 'Windows': + ComError.NS_OK = ComError.S_OK; + ComError.NS_ERROR_FAILURE = ComError.E_FAIL; + ComError.NS_ERROR_ABORT = ComError.E_ABORT; + ComError.NS_ERROR_NULL_POINTER = ComError.E_POINTER; + ComError.NS_ERROR_NO_INTERFACE = ComError.E_NOINTERFACE; + ComError.NS_ERROR_INVALID_ARG = ComError.E_INVALIDARG; + ComError.NS_ERROR_OUT_OF_MEMORY = ComError.E_OUTOFMEMORY; + ComError.NS_ERROR_NOT_IMPLEMENTED = ComError.E_NOTIMPL; + ComError.NS_ERROR_UNEXPECTED = ComError.E_UNEXPECTED; + else: + ComError.E_ACCESSDENIED = -2147024891; # see VBox/com/defs.h + ComError.S_OK = ComError.NS_OK; + ComError.E_FAIL = ComError.NS_ERROR_FAILURE; + ComError.E_ABORT = ComError.NS_ERROR_ABORT; + ComError.E_POINTER = ComError.NS_ERROR_NULL_POINTER; + ComError.E_NOINTERFACE = ComError.NS_ERROR_NO_INTERFACE; + ComError.E_INVALIDARG = ComError.NS_ERROR_INVALID_ARG; + ComError.E_OUTOFMEMORY = ComError.NS_ERROR_OUT_OF_MEMORY; + ComError.E_NOTIMPL = ComError.NS_ERROR_NOT_IMPLEMENTED; + ComError.E_UNEXPECTED = ComError.NS_ERROR_UNEXPECTED; + ComError.DISP_E_EXCEPTION = -2147352567; # For COM compatability only. + return True; + + @staticmethod + def getXcptResult(oXcpt): + """ + Gets the result code for an exception. + Returns COM status code (or E_UNEXPECTED). + """ + if platform.system() == 'Windows': + # The DISP_E_EXCEPTION + excptinfo fun needs checking up, only + # empirical info on it so far. + try: + hrXcpt = oXcpt.hresult; + except AttributeError: + hrXcpt = ComError.E_UNEXPECTED; + if hrXcpt == ComError.DISP_E_EXCEPTION and oXcpt.excepinfo is not None: + hrXcpt = oXcpt.excepinfo[5]; + else: + try: + hrXcpt = oXcpt.errno; + except AttributeError: + hrXcpt = ComError.E_UNEXPECTED; + return hrXcpt; + + @staticmethod + def equal(oXcpt, hr): + """ + Checks if the ComException e is not equal to the COM status code hr. + This takes DISP_E_EXCEPTION & excepinfo into account. + + This method can be used with any Exception derivate, however it will + only return True for classes similar to the two ComException variants. + """ + if platform.system() == 'Windows': + # The DISP_E_EXCEPTION + excptinfo fun needs checking up, only + # empirical info on it so far. + try: + hrXcpt = oXcpt.hresult; + except AttributeError: + return False; + if hrXcpt == ComError.DISP_E_EXCEPTION and oXcpt.excepinfo is not None: + hrXcpt = oXcpt.excepinfo[5]; + else: + try: + hrXcpt = oXcpt.errno; + except AttributeError: + return False; + return hrXcpt == hr; + + @staticmethod + def notEqual(oXcpt, hr): + """ + Checks if the ComException e is not equal to the COM status code hr. + See equal() for more details. + """ + return not ComError.equal(oXcpt, hr) + + @staticmethod + def toString(hr): + """ + Converts the specified COM status code to a string. + """ + try: + sStr = ComError.dDecimalToConst[int(hr)]; + except KeyError: + hrLong = long(hr); + sStr = '%#x (%d)' % (hrLong, hrLong); + return sStr; + + +class Build(object): # pylint: disable=too-few-public-methods + """ + A VirtualBox build. + + Note! After dropping the installation of VBox from this code and instead + realizing that with the vboxinstall.py wrapper driver, this class is + of much less importance and contains unnecessary bits and pieces. + """ + + def __init__(self, oDriver, strInstallPath): + """ + Construct a build object from a build file name and/or install path. + """ + # Initialize all members first. + self.oDriver = oDriver; + self.sInstallPath = strInstallPath; + self.sSdkPath = None; + self.sSrcRoot = None; + self.sKind = None; + self.sDesignation = None; + self.sType = None; + self.sOs = None; + self.sArch = None; + self.sGuestAdditionsIso = None; + + # Figure out the values as best we can. + if strInstallPath is None: + # + # Both parameters are None, which means we're falling back on a + # build in the development tree. + # + self.sKind = "development"; + + if self.sType is None: + self.sType = os.environ.get("KBUILD_TYPE", "release"); + if self.sOs is None: + self.sOs = os.environ.get("KBUILD_TARGET", oDriver.sHost); + if self.sArch is None: + self.sArch = os.environ.get("KBUILD_TARGET_ARCH", oDriver.sHostArch); + + sOut = os.path.join('out', self.sOs + '.' + self.sArch, self.sType); + sSearch = os.environ.get('VBOX_TD_DEV_TREE', os.path.dirname(__file__)); # Env.var. for older trees or testboxscript. + sCandidat = None; + for i in range(0, 10): # pylint: disable=unused-variable + sBldDir = os.path.join(sSearch, sOut); + if os.path.isdir(sBldDir): + sCandidat = os.path.join(sBldDir, 'bin', 'VBoxSVC' + base.exeSuff()); + if os.path.isfile(sCandidat): + self.sSdkPath = os.path.join(sBldDir, 'bin/sdk'); + break; + sCandidat = os.path.join(sBldDir, 'dist/VirtualBox.app/Contents/MacOS/VBoxSVC'); + if os.path.isfile(sCandidat): + self.sSdkPath = os.path.join(sBldDir, 'dist/sdk'); + break; + sSearch = os.path.abspath(os.path.join(sSearch, '..')); + if sCandidat is None or not os.path.isfile(sCandidat): + raise base.GenError(); + self.sInstallPath = os.path.abspath(os.path.dirname(sCandidat)); + self.sSrcRoot = os.path.abspath(sSearch); + + self.sDesignation = os.environ.get('TEST_BUILD_DESIGNATION', None); + if self.sDesignation is None: + try: + oFile = utils.openNoInherit(os.path.join(self.sSrcRoot, sOut, 'revision.kmk'), 'r'); + except: + pass; + else: + s = oFile.readline(); + oFile.close(); + oMatch = re.search("VBOX_SVN_REV=(\\d+)", s); + if oMatch is not None: + self.sDesignation = oMatch.group(1); + + if self.sDesignation is None: + self.sDesignation = 'XXXXX' + else: + # + # We've been pointed to an existing installation, this could be + # in the out dir of a svn checkout, untarred VBoxAll or a real + # installation directory. + # + self.sKind = "preinstalled"; + self.sType = "release"; + self.sOs = oDriver.sHost; + self.sArch = oDriver.sHostArch; + self.sInstallPath = os.path.abspath(strInstallPath); + self.sSdkPath = os.path.join(self.sInstallPath, 'sdk'); + self.sSrcRoot = None; + self.sDesignation = os.environ.get('TEST_BUILD_DESIGNATION', 'XXXXX'); + ## @todo Much more work is required here. + + # Try Determine the build type. + sVBoxManage = os.path.join(self.sInstallPath, 'VBoxManage' + base.exeSuff()); + if os.path.isfile(sVBoxManage): + try: + (iExit, sStdOut, _) = utils.processOutputUnchecked([sVBoxManage, '--dump-build-type']); + sStdOut = sStdOut.strip(); + if iExit == 0 and sStdOut in ('release', 'debug', 'strict', 'dbgopt', 'asan'): + self.sType = sStdOut; + reporter.log('Build: Detected build type: %s' % (self.sType)); + else: + reporter.log('Build: --dump-build-type -> iExit=%u sStdOut=%s' % (iExit, sStdOut,)); + except: + reporter.logXcpt('Build: Running "%s --dump-build-type" failed!' % (sVBoxManage,)); + else: + reporter.log3('Build: sVBoxManage=%s not found' % (sVBoxManage,)); + + # Do some checks. + sVMMR0 = os.path.join(self.sInstallPath, 'VMMR0.r0'); + if not os.path.isfile(sVMMR0) and utils.getHostOs() == 'solaris': # solaris is special. + sVMMR0 = os.path.join(self.sInstallPath, 'amd64' if utils.getHostArch() == 'amd64' else 'i386', 'VMMR0.r0'); + if not os.path.isfile(sVMMR0): + raise base.GenError('%s is missing' % (sVMMR0,)); + + # Guest additions location is different on windows for some _stupid_ reason. + if self.sOs == 'win' and self.sKind != 'development': + self.sGuestAdditionsIso = '%s/VBoxGuestAdditions.iso' % (self.sInstallPath,); + elif self.sOs == 'darwin': + self.sGuestAdditionsIso = '%s/VBoxGuestAdditions.iso' % (self.sInstallPath,); + elif self.sOs == 'solaris': + self.sGuestAdditionsIso = '%s/VBoxGuestAdditions.iso' % (self.sInstallPath,); + else: + self.sGuestAdditionsIso = '%s/additions/VBoxGuestAdditions.iso' % (self.sInstallPath,); + + # __init__ end; + + def isDevBuild(self): + """ Returns True if it's development build (kind), otherwise False. """ + return self.sKind == 'development'; + + +class EventHandlerBase(object): + """ + Base class for both Console and VirtualBox event handlers. + """ + + def __init__(self, dArgs, fpApiVer, sName = None): + self.oVBoxMgr = dArgs['oVBoxMgr']; + self.oEventSrc = dArgs['oEventSrc']; # Console/VirtualBox for < 3.3 + self.oListener = dArgs['oListener']; + self.fPassive = self.oListener is not None; + self.sName = sName + self.fShutdown = False; + self.oThread = None; + self.fpApiVer = fpApiVer; + self.dEventNo2Name = {}; + for sKey, iValue in self.oVBoxMgr.constants.all_values('VBoxEventType').items(): + self.dEventNo2Name[iValue] = sKey; + + def threadForPassiveMode(self): + """ + The thread procedure for the event processing thread. + """ + assert self.fPassive is not None; + while not self.fShutdown: + try: + oEvt = self.oEventSrc.getEvent(self.oListener, 500); + except: + if not self.oVBoxMgr.xcptIsDeadInterface(): reporter.logXcpt(); + else: reporter.log('threadForPassiveMode/%s: interface croaked (ignored)' % (self.sName,)); + break; + if oEvt: + self.handleEvent(oEvt); + if not self.fShutdown: + try: + self.oEventSrc.eventProcessed(self.oListener, oEvt); + except: + reporter.logXcpt(); + break; + self.unregister(fWaitForThread = False); + return None; + + def startThreadForPassiveMode(self): + """ + Called when working in passive mode. + """ + self.oThread = threading.Thread(target = self.threadForPassiveMode, \ + args=(), name=('PAS-%s' % (self.sName,))); + self.oThread.setDaemon(True); # pylint: disable=deprecated-method + self.oThread.start(); + return None; + + def unregister(self, fWaitForThread = True): + """ + Unregister the event handler. + """ + fRc = False; + if not self.fShutdown: + self.fShutdown = True; + + if self.oEventSrc is not None: + if self.fpApiVer < 3.3: + try: + self.oEventSrc.unregisterCallback(self.oListener); + fRc = True; + except: + reporter.errorXcpt('unregisterCallback failed on %s' % (self.oListener,)); + else: + try: + self.oEventSrc.unregisterListener(self.oListener); + fRc = True; + except: + if self.oVBoxMgr.xcptIsDeadInterface(): + reporter.log('unregisterListener failed on %s because of dead interface (%s)' + % (self.oListener, self.oVBoxMgr.xcptToString(),)); + else: + reporter.errorXcpt('unregisterListener failed on %s' % (self.oListener,)); + + if self.oThread is not None \ + and self.oThread != threading.current_thread(): + self.oThread.join(); + self.oThread = None; + + _ = fWaitForThread; + return fRc; + + def handleEvent(self, oEvt): + """ + Compatibility wrapper that child classes implement. + """ + _ = oEvt; + return None; + + @staticmethod + def registerDerivedEventHandler(oVBoxMgr, fpApiVer, oSubClass, dArgsCopy, # pylint: disable=too-many-arguments + oSrcParent, sSrcParentNm, sICallbackNm, + fMustSucceed = True, sLogSuffix = '', aenmEvents = None): + """ + Registers the callback / event listener. + """ + dArgsCopy['oVBoxMgr'] = oVBoxMgr; + dArgsCopy['oListener'] = None; + if fpApiVer < 3.3: + dArgsCopy['oEventSrc'] = oSrcParent; + try: + oRet = oVBoxMgr.createCallback(sICallbackNm, oSubClass, dArgsCopy); + except: + reporter.errorXcpt('%s::registerCallback(%s) failed%s' % (sSrcParentNm, oRet, sLogSuffix)); + else: + try: + oSrcParent.registerCallback(oRet); + return oRet; + except Exception as oXcpt: + if fMustSucceed or ComError.notEqual(oXcpt, ComError.E_UNEXPECTED): + reporter.errorXcpt('%s::registerCallback(%s)%s' % (sSrcParentNm, oRet, sLogSuffix)); + else: + # + # Scalable event handling introduced in VBox 4.0. + # + fPassive = sys.platform == 'win32'; # or webservices. + + if not aenmEvents: + aenmEvents = (vboxcon.VBoxEventType_Any,); + + try: + oEventSrc = oSrcParent.eventSource; + dArgsCopy['oEventSrc'] = oEventSrc; + if not fPassive: + oListener = oRet = oVBoxMgr.createListener(oSubClass, dArgsCopy); + else: + oListener = oEventSrc.createListener(); + dArgsCopy['oListener'] = oListener; + oRet = oSubClass(dArgsCopy); + except: + reporter.errorXcpt('%s::eventSource.createListener(%s) failed%s' % (sSrcParentNm, oListener, sLogSuffix)); + else: + try: + oEventSrc.registerListener(oListener, aenmEvents, not fPassive); + except Exception as oXcpt: + if fMustSucceed or ComError.notEqual(oXcpt, ComError.E_UNEXPECTED): + reporter.errorXcpt('%s::eventSource.registerListener(%s) failed%s' + % (sSrcParentNm, oListener, sLogSuffix)); + else: + if not fPassive: + if sys.platform == 'win32': + from win32com.server.util import unwrap # pylint: disable=import-error + oRet = unwrap(oRet); + oRet.oListener = oListener; + else: + oRet.startThreadForPassiveMode(); + return oRet; + return None; + + + + +class ConsoleEventHandlerBase(EventHandlerBase): + """ + Base class for handling IConsole events. + + The class has IConsoleCallback (<=3.2) compatible callback methods which + the user can override as needed. + + Note! This class must not inherit from object or we'll get type errors in VBoxPython. + """ + def __init__(self, dArgs, sName = None): + self.oSession = dArgs['oSession']; + self.oConsole = dArgs['oConsole']; + if sName is None: + sName = self.oSession.sName; + EventHandlerBase.__init__(self, dArgs, self.oSession.fpApiVer, sName); + + + # pylint: disable=missing-docstring,too-many-arguments,unused-argument + def onMousePointerShapeChange(self, fVisible, fAlpha, xHot, yHot, cx, cy, abShape): + reporter.log2('onMousePointerShapeChange/%s' % (self.sName)); + def onMouseCapabilityChange(self, fSupportsAbsolute, *aArgs): # Extra argument was added in 3.2. + reporter.log2('onMouseCapabilityChange/%s' % (self.sName)); + def onKeyboardLedsChange(self, fNumLock, fCapsLock, fScrollLock): + reporter.log2('onKeyboardLedsChange/%s' % (self.sName)); + def onStateChange(self, eState): + reporter.log2('onStateChange/%s' % (self.sName)); + def onAdditionsStateChange(self): + reporter.log2('onAdditionsStateChange/%s' % (self.sName)); + def onNetworkAdapterChange(self, oNic): + reporter.log2('onNetworkAdapterChange/%s' % (self.sName)); + def onSerialPortChange(self, oPort): + reporter.log2('onSerialPortChange/%s' % (self.sName)); + def onParallelPortChange(self, oPort): + reporter.log2('onParallelPortChange/%s' % (self.sName)); + def onStorageControllerChange(self): + reporter.log2('onStorageControllerChange/%s' % (self.sName)); + def onMediumChange(self, attachment): + reporter.log2('onMediumChange/%s' % (self.sName)); + def onCPUChange(self, iCpu, fAdd): + reporter.log2('onCPUChange/%s' % (self.sName)); + def onVRDPServerChange(self): + reporter.log2('onVRDPServerChange/%s' % (self.sName)); + def onRemoteDisplayInfoChange(self): + reporter.log2('onRemoteDisplayInfoChange/%s' % (self.sName)); + def onUSBControllerChange(self): + reporter.log2('onUSBControllerChange/%s' % (self.sName)); + def onUSBDeviceStateChange(self, oDevice, fAttached, oError): + reporter.log2('onUSBDeviceStateChange/%s' % (self.sName)); + def onSharedFolderChange(self, fGlobal): + reporter.log2('onSharedFolderChange/%s' % (self.sName)); + def onRuntimeError(self, fFatal, sErrId, sMessage): + reporter.log2('onRuntimeError/%s' % (self.sName)); + def onCanShowWindow(self): + reporter.log2('onCanShowWindow/%s' % (self.sName)); + return True + def onShowWindow(self): + reporter.log2('onShowWindow/%s' % (self.sName)); + return None; + # pylint: enable=missing-docstring,too-many-arguments,unused-argument + + def handleEvent(self, oEvt): + """ + Compatibility wrapper. + """ + try: + oEvtBase = self.oVBoxMgr.queryInterface(oEvt, 'IEvent'); + eType = oEvtBase.type; + except: + reporter.logXcpt(); + return None; + if eType == vboxcon.VBoxEventType_OnRuntimeError: + try: + oEvtIt = self.oVBoxMgr.queryInterface(oEvtBase, 'IRuntimeErrorEvent'); + return self.onRuntimeError(oEvtIt.fatal, oEvtIt.id, oEvtIt.message) + except: + reporter.logXcpt(); + ## @todo implement the other events. + try: + if eType not in (vboxcon.VBoxEventType_OnMousePointerShapeChanged, + vboxcon.VBoxEventType_OnCursorPositionChanged): + if eType in self.dEventNo2Name: + reporter.log2('%s(%s)/%s' % (self.dEventNo2Name[eType], str(eType), self.sName)); + else: + reporter.log2('%s/%s' % (str(eType), self.sName)); + except AttributeError: # Handle older VBox versions which don't have a specific event. + pass; + return None; + + +class VirtualBoxEventHandlerBase(EventHandlerBase): + """ + Base class for handling IVirtualBox events. + + The class has IConsoleCallback (<=3.2) compatible callback methods which + the user can override as needed. + + Note! This class must not inherit from object or we'll get type errors in VBoxPython. + """ + def __init__(self, dArgs, sName = "emanon"): + self.oVBoxMgr = dArgs['oVBoxMgr']; + self.oVBox = dArgs['oVBox']; + EventHandlerBase.__init__(self, dArgs, self.oVBox.fpApiVer, sName); + + # pylint: disable=missing-docstring,unused-argument + def onMachineStateChange(self, sMachineId, eState): + pass; + def onMachineDataChange(self, sMachineId): + pass; + def onExtraDataCanChange(self, sMachineId, sKey, sValue): + # The COM bridge does tuples differently. Not very funny if you ask me... ;-) + if self.oVBoxMgr.type == 'MSCOM': + return '', 0, True; + return True, '' + def onExtraDataChange(self, sMachineId, sKey, sValue): + pass; + def onMediumRegistered(self, sMediumId, eMediumType, fRegistered): + pass; + def onMachineRegistered(self, sMachineId, fRegistered): + pass; + def onSessionStateChange(self, sMachineId, eState): + pass; + def onSnapshotTaken(self, sMachineId, sSnapshotId): + pass; + def onSnapshotDiscarded(self, sMachineId, sSnapshotId): + pass; + def onSnapshotChange(self, sMachineId, sSnapshotId): + pass; + def onGuestPropertyChange(self, sMachineId, sName, sValue, sFlags, fWasDeleted): + pass; + # pylint: enable=missing-docstring,unused-argument + + def handleEvent(self, oEvt): + """ + Compatibility wrapper. + """ + try: + oEvtBase = self.oVBoxMgr.queryInterface(oEvt, 'IEvent'); + eType = oEvtBase.type; + except: + reporter.logXcpt(); + return None; + if eType == vboxcon.VBoxEventType_OnMachineStateChanged: + try: + oEvtIt = self.oVBoxMgr.queryInterface(oEvtBase, 'IMachineStateChangedEvent'); + return self.onMachineStateChange(oEvtIt.machineId, oEvtIt.state) + except: + reporter.logXcpt(); + elif eType == vboxcon.VBoxEventType_OnGuestPropertyChanged: + try: + oEvtIt = self.oVBoxMgr.queryInterface(oEvtBase, 'IGuestPropertyChangedEvent'); + if hasattr(oEvtIt, 'fWasDeleted'): # Since 7.0 we have a dedicated flag + fWasDeleted = oEvtIt.fWasDeleted; + else: + fWasDeleted = False; # Don't indicate deletion here -- there can be empty guest properties. + return self.onGuestPropertyChange(oEvtIt.machineId, oEvtIt.name, oEvtIt.value, oEvtIt.flags, fWasDeleted); + except: + reporter.logXcpt(); + ## @todo implement the other events. + if eType in self.dEventNo2Name: + reporter.log2('%s(%s)/%s' % (self.dEventNo2Name[eType], str(eType), self.sName)); + else: + reporter.log2('%s/%s' % (str(eType), self.sName)); + return None; + + +class SessionConsoleEventHandler(ConsoleEventHandlerBase): + """ + For catching machine state changes and waking up the task machinery at that point. + """ + def __init__(self, dArgs): + ConsoleEventHandlerBase.__init__(self, dArgs); + + def onMachineStateChange(self, sMachineId, eState): # pylint: disable=unused-argument + """ Just interrupt the wait loop here so it can check again. """ + _ = sMachineId; _ = eState; + self.oVBoxMgr.interruptWaitEvents(); + + def onRuntimeError(self, fFatal, sErrId, sMessage): + reporter.log('onRuntimeError/%s: fFatal=%d sErrId=%s sMessage=%s' % (self.sName, fFatal, sErrId, sMessage)); + oSession = self.oSession; + if oSession is not None: # paranoia + if sErrId == 'HostMemoryLow': + oSession.signalHostMemoryLow(); + if sys.platform == 'win32': + from testdriver import winbase; + winbase.logMemoryStats(); + oSession.signalTask(); + self.oVBoxMgr.interruptWaitEvents(); + + + +class TestDriver(base.TestDriver): # pylint: disable=too-many-instance-attributes + """ + This is the VirtualBox test driver. + """ + + def __init__(self): + base.TestDriver.__init__(self); + self.fImportedVBoxApi = False; + self.fpApiVer = 3.2; + self.uRevision = 0; + self.uApiRevision = 0; + self.oBuild = None; + self.oVBoxMgr = None; + self.oVBox = None; + self.aoRemoteSessions = []; + self.aoVMs = []; ## @todo not sure if this list will be of any use. + self.oTestVmManager = vboxtestvms.TestVmManager(self.sResourcePath); + self.oTestVmSet = vboxtestvms.TestVmSet(); + self.sSessionTypeDef = 'headless'; + self.sSessionType = self.sSessionTypeDef; + self.fEnableVrdp = True; + self.uVrdpBasePortDef = 6000; + self.uVrdpBasePort = self.uVrdpBasePortDef; + self.sDefBridgedNic = None; + self.fUseDefaultSvc = False; + self.sLogSelfGroups = ''; + self.sLogSelfFlags = 'time'; + self.sLogSelfDest = ''; + self.sLogSessionGroups = ''; + self.sLogSessionFlags = 'time'; + self.sLogSessionDest = ''; + self.sLogSvcGroups = ''; + self.sLogSvcFlags = 'time'; + self.sLogSvcDest = ''; + self.sSelfLogFile = None; + self.sSessionLogFile = None; + self.sVBoxSvcLogFile = None; + self.oVBoxSvcProcess = None; + self.sVBoxSvcPidFile = None; + self.fVBoxSvcInDebugger = False; + self.fVBoxSvcWaitForDebugger = False; + self.sVBoxValidationKit = None; + self.sVBoxValidationKitIso = None; + self.sVBoxBootSectors = None; + self.fAlwaysUploadLogs = False; + self.fAlwaysUploadScreenshots = False; + self.fAlwaysUploadRecordings = False; # Only upload recording files on failure by default. + self.fEnableDebugger = True; + self.adRecordingFiles = []; + self.fRecordingEnabled = False; # Don't record by default (yet). + self.fRecordingAudio = False; # Don't record audio by default. + self.cSecsRecordingMax = 0; # No recording time limit in seconds. + self.cMbRecordingMax = 195; # The test manager web server has a configured upload limit of 200 MiBs. + ## @todo Can we query the configured value here + # (via `from testmanager import config`)? + + # Drop LD_PRELOAD and enable memory leak detection in LSAN_OPTIONS from vboxinstall.py + # before doing build detection. This is a little crude and inflexible... + if 'LD_PRELOAD' in os.environ: + del os.environ['LD_PRELOAD']; + if 'LSAN_OPTIONS' in os.environ: + asLSanOptions = os.environ['LSAN_OPTIONS'].split(':'); + try: asLSanOptions.remove('detect_leaks=0'); + except: pass; + if asLSanOptions: os.environ['LSAN_OPTIONS'] = ':'.join(asLSanOptions); + else: del os.environ['LSAN_OPTIONS']; + + # Quietly detect build and validation kit. + self._detectBuild(False); + self._detectValidationKit(False); + + # Make sure all debug logs goes to the scratch area unless + # specified otherwise (more of this later on). + if 'VBOX_LOG_DEST' not in os.environ: + os.environ['VBOX_LOG_DEST'] = 'nodeny dir=%s' % (self.sScratchPath); + + + def _detectBuild(self, fQuiet = False): + """ + This is used internally to try figure a locally installed build when + running tests manually. + """ + if self.oBuild is not None: + return True; + + # Try dev build first since that's where I'll be using it first... + if True is True: # pylint: disable=comparison-with-itself + try: + self.oBuild = Build(self, None); + reporter.log('VBox %s build at %s (%s).' + % (self.oBuild.sType, self.oBuild.sInstallPath, self.oBuild.sDesignation,)); + return True; + except base.GenError: + pass; + + # Try default installation locations. + if self.sHost == 'win': + sProgFiles = os.environ.get('ProgramFiles', 'C:\\Program Files'); + asLocs = [ + os.path.join(sProgFiles, 'Oracle', 'VirtualBox'), + os.path.join(sProgFiles, 'OracleVM', 'VirtualBox'), + os.path.join(sProgFiles, 'Sun', 'VirtualBox'), + ]; + elif self.sHost == 'solaris': + asLocs = [ '/opt/VirtualBox-3.2', '/opt/VirtualBox-3.1', '/opt/VirtualBox-3.0', '/opt/VirtualBox' ]; + elif self.sHost == 'darwin': + asLocs = [ '/Applications/VirtualBox.app/Contents/MacOS' ]; + elif self.sHost == 'linux': + asLocs = [ '/opt/VirtualBox-3.2', '/opt/VirtualBox-3.1', '/opt/VirtualBox-3.0', '/opt/VirtualBox' ]; + else: + asLocs = [ '/opt/VirtualBox' ]; + if 'VBOX_INSTALL_PATH' in os.environ: + asLocs.insert(0, os.environ['VBOX_INSTALL_PATH']); + + for sLoc in asLocs: + try: + self.oBuild = Build(self, sLoc); + reporter.log('VBox %s build at %s (%s).' + % (self.oBuild.sType, self.oBuild.sInstallPath, self.oBuild.sDesignation,)); + return True; + except base.GenError: + pass; + + if not fQuiet: + reporter.error('failed to find VirtualBox installation'); + return False; + + def _detectValidationKit(self, fQuiet = False): + """ + This is used internally by the constructor to try locate an unzipped + VBox Validation Kit somewhere in the immediate proximity. + """ + if self.sVBoxValidationKit is not None: + return True; + + # + # Normally it's found where we're running from, which is the same as + # the script directly on the testboxes. + # + asCandidates = [self.sScriptPath, ]; + if g_ksValidationKitDir not in asCandidates: + asCandidates.append(g_ksValidationKitDir); + if os.getcwd() not in asCandidates: + asCandidates.append(os.getcwd()); + if self.oBuild is not None and self.oBuild.sInstallPath not in asCandidates: + asCandidates.append(self.oBuild.sInstallPath); + + # + # When working out of the tree, we'll search the current directory + # as well as parent dirs. + # + for sDir in list(asCandidates): + for i in range(10): + sDir = os.path.dirname(sDir); + if sDir not in asCandidates: + asCandidates.append(sDir); + + # + # Do the searching. + # + sCandidate = None; + for i, _ in enumerate(asCandidates): + sCandidate = asCandidates[i]; + if os.path.isfile(os.path.join(sCandidate, 'VBoxValidationKit.iso')): + break; + sCandidate = os.path.join(sCandidate, 'validationkit'); + if os.path.isfile(os.path.join(sCandidate, 'VBoxValidationKit.iso')): + break; + sCandidate = None; + + fRc = sCandidate is not None; + if fRc is False: + if not fQuiet: + reporter.error('failed to find VBox Validation Kit installation (candidates: %s)' % (asCandidates,)); + sCandidate = os.path.join(self.sScriptPath, 'validationkit'); # Don't leave the values as None. + + # + # Set the member values. + # + self.sVBoxValidationKit = sCandidate; + self.sVBoxValidationKitIso = os.path.join(sCandidate, 'VBoxValidationKit.iso'); + self.sVBoxBootSectors = os.path.join(sCandidate, 'bootsectors'); + return fRc; + + def _makeEnvironmentChanges(self): + """ + Make the necessary VBox related environment changes. + Children not importing the VBox API should call this. + """ + # Make sure we've got our own VirtualBox config and VBoxSVC (on XPCOM at least). + if not self.fUseDefaultSvc: + os.environ['VBOX_USER_HOME'] = os.path.join(self.sScratchPath, 'VBoxUserHome'); + sUser = os.environ.get('USERNAME', os.environ.get('USER', os.environ.get('LOGNAME', 'unknown'))); + os.environ['VBOX_IPC_SOCKETID'] = sUser + '-VBoxTest'; + return True; + + @staticmethod + def makeApiRevision(uMajor, uMinor, uBuild, uApiRevision): + """ Calculates an API revision number. """ + return (long(uMajor) << 56) | (long(uMinor) << 48) | (long(uBuild) << 40) | uApiRevision; + + def importVBoxApi(self): + """ + Import the 'vboxapi' module from the VirtualBox build we're using and + instantiate the two basic objects. + + This will try detect an development or installed build if no build has + been associated with the driver yet. + """ + if self.fImportedVBoxApi: + return True; + + self._makeEnvironmentChanges(); + + # Do the detecting. + self._detectBuild(); + if self.oBuild is None: + return False; + + # Avoid crashing when loading the 32-bit module (or whatever it is that goes bang). + if self.oBuild.sArch == 'x86' \ + and self.sHost == 'darwin' \ + and platform.architecture()[0] == '64bit' \ + and self.oBuild.sKind == 'development' \ + and os.getenv('VERSIONER_PYTHON_PREFER_32_BIT') != 'yes': + reporter.log("WARNING: 64-bit python on darwin, 32-bit VBox development build => crash"); + reporter.log("WARNING: bash-3.2$ /usr/bin/python2.5 ./testdriver"); + reporter.log("WARNING: or"); + reporter.log("WARNING: bash-3.2$ VERSIONER_PYTHON_PREFER_32_BIT=yes ./testdriver"); + return False; + + # Start VBoxSVC and load the vboxapi bits. + if self._startVBoxSVC() is True: + assert(self.oVBoxSvcProcess is not None); + + sSavedSysPath = sys.path; + self._setupVBoxApi(); + sys.path = sSavedSysPath; + + # Adjust the default machine folder. + if self.fImportedVBoxApi and not self.fUseDefaultSvc and self.fpApiVer >= 4.0: + sNewFolder = os.path.join(self.sScratchPath, 'VBoxUserHome', 'Machines'); + try: + self.oVBox.systemProperties.defaultMachineFolder = sNewFolder; + except: + self.fImportedVBoxApi = False; + self.oVBoxMgr = None; + self.oVBox = None; + reporter.logXcpt("defaultMachineFolder exception (sNewFolder=%s)" % (sNewFolder,)); + + # Kill VBoxSVC on failure. + if self.oVBoxMgr is None: + self._stopVBoxSVC(); + else: + assert(self.oVBoxSvcProcess is None); + return self.fImportedVBoxApi; + + def _startVBoxSVC(self): # pylint: disable=too-many-statements + """ Starts VBoxSVC. """ + assert(self.oVBoxSvcProcess is None); + + # Setup vbox logging for VBoxSVC now and start it manually. This way + # we can control both logging and shutdown. + self.sVBoxSvcLogFile = '%s/VBoxSVC-debug.log' % (self.sScratchPath,); + try: os.remove(self.sVBoxSvcLogFile); + except: pass; + os.environ['VBOX_LOG'] = self.sLogSvcGroups; + os.environ['VBOX_LOG_FLAGS'] = '%s append' % (self.sLogSvcFlags,); # Append becuse of VBoxXPCOMIPCD. + if self.sLogSvcDest: + os.environ['VBOX_LOG_DEST'] = 'nodeny ' + self.sLogSvcDest; + else: + os.environ['VBOX_LOG_DEST'] = 'nodeny file=%s' % (self.sVBoxSvcLogFile,); + os.environ['VBOXSVC_RELEASE_LOG_FLAGS'] = 'time append'; + + reporter.log2('VBoxSVC environment:'); + for sKey, sVal in sorted(os.environ.items()): + reporter.log2('%s=%s' % (sKey, sVal)); + + # Always leave a pid file behind so we can kill it during cleanup-before. + self.sVBoxSvcPidFile = '%s/VBoxSVC.pid' % (self.sScratchPath,); + fWritePidFile = True; + + cMsFudge = 1; + sVBoxSVC = '%s/VBoxSVC' % (self.oBuild.sInstallPath,); ## @todo .exe and stuff. + if self.fVBoxSvcInDebugger: + if self.sHost in ('darwin', 'freebsd', 'linux', 'solaris', ): + # Start VBoxSVC in gdb in a new terminal. + #sTerm = '/usr/bin/gnome-terminal'; - doesn't work, some fork+exec stuff confusing us. + sTerm = '/usr/bin/xterm'; + if not os.path.isfile(sTerm): sTerm = '/usr/X11/bin/xterm'; + if not os.path.isfile(sTerm): sTerm = '/usr/X11R6/bin/xterm'; + if not os.path.isfile(sTerm): sTerm = '/usr/bin/xterm'; + if not os.path.isfile(sTerm): sTerm = 'xterm'; + sGdb = '/usr/bin/gdb'; + if not os.path.isfile(sGdb): sGdb = '/usr/local/bin/gdb'; + if not os.path.isfile(sGdb): sGdb = '/usr/sfw/bin/gdb'; + if not os.path.isfile(sGdb): sGdb = 'gdb'; + sGdbCmdLine = '%s --args %s --pidfile %s' % (sGdb, sVBoxSVC, self.sVBoxSvcPidFile); + # Cool tweak to run performance analysis instead of gdb: + #sGdb = '/usr/bin/valgrind'; + #sGdbCmdLine = '%s --tool=callgrind --collect-atstart=no -- %s --pidfile %s' \ + # % (sGdb, sVBoxSVC, self.sVBoxSvcPidFile); + reporter.log('term="%s" gdb="%s"' % (sTerm, sGdbCmdLine)); + os.environ['SHELL'] = self.sOrgShell; # Non-working shell may cause gdb and/or the term problems. + ## @todo -e is deprecated; use "-- <args>". + self.oVBoxSvcProcess = base.Process.spawnp(sTerm, sTerm, '-e', sGdbCmdLine); + os.environ['SHELL'] = self.sOurShell; + if self.oVBoxSvcProcess is not None: + reporter.log('Press enter or return after starting VBoxSVC in the debugger...'); + sys.stdin.read(1); + fWritePidFile = False; + + elif self.sHost == 'win': + sWinDbg = 'c:\\Program Files\\Debugging Tools for Windows\\windbg.exe'; + if not os.path.isfile(sWinDbg): sWinDbg = 'c:\\Program Files\\Debugging Tools for Windows (x64)\\windbg.exe'; + if not os.path.isfile(sWinDbg): sWinDbg = 'c:\\Programme\\Debugging Tools for Windows\\windbg.exe'; # Localization rulez! pylint: disable=line-too-long + if not os.path.isfile(sWinDbg): sWinDbg = 'c:\\Programme\\Debugging Tools for Windows (x64)\\windbg.exe'; + if not os.path.isfile(sWinDbg): sWinDbg = 'windbg'; # WinDbg must be in the path; better than nothing. + # Assume that everything WinDbg needs is defined using the environment variables. + # See WinDbg help for more information. + reporter.log('windbg="%s"' % (sWinDbg)); + self.oVBoxSvcProcess = base.Process.spawn(sWinDbg, sWinDbg, sVBoxSVC + base.exeSuff()); + if self.oVBoxSvcProcess is not None: + reporter.log('Press enter or return after starting VBoxSVC in the debugger...'); + sys.stdin.read(1); + fWritePidFile = False; + ## @todo add a pipe interface similar to xpcom if feasible, i.e. if + # we can get actual handle values for pipes in python. + + else: + reporter.error('Port me!'); + else: # Run without a debugger attached. + if self.sHost in ('darwin', 'freebsd', 'linux', 'solaris', ): + # + # XPCOM - We can use a pipe to let VBoxSVC notify us when it's ready. + # + iPipeR, iPipeW = os.pipe(); + if hasattr(os, 'set_inheritable'): + os.set_inheritable(iPipeW, True); # pylint: disable=no-member + os.environ['NSPR_INHERIT_FDS'] = 'vboxsvc:startup-pipe:5:0x%x' % (iPipeW,); + reporter.log2("NSPR_INHERIT_FDS=%s" % (os.environ['NSPR_INHERIT_FDS'])); + + self.oVBoxSvcProcess = base.Process.spawn(sVBoxSVC, sVBoxSVC, '--auto-shutdown'); # SIGUSR1 requirement. + try: # Try make sure we get the SIGINT and not VBoxSVC. + os.setpgid(self.oVBoxSvcProcess.getPid(), 0); # pylint: disable=no-member + os.setpgid(0, 0); # pylint: disable=no-member + except: + reporter.logXcpt(); + + os.close(iPipeW); + try: + sResponse = os.read(iPipeR, 32); + except: + reporter.logXcpt(); + sResponse = None; + os.close(iPipeR); + + if hasattr(sResponse, 'decode'): + sResponse = sResponse.decode('utf-8', 'ignore'); + + if sResponse is None or sResponse.strip() != 'READY': + reporter.error('VBoxSVC failed starting up... (sResponse=%s)' % (sResponse,)); + if not self.oVBoxSvcProcess.wait(5000): + self.oVBoxSvcProcess.terminate(); + self.oVBoxSvcProcess.wait(5000); + self.oVBoxSvcProcess = None; + + elif self.sHost == 'win': + # + # Windows - Just fudge it for now. + # + cMsFudge = 2000; + self.oVBoxSvcProcess = base.Process.spawn(sVBoxSVC, sVBoxSVC); + + else: + reporter.error('Port me!'); + + # + # Enable automatic crash reporting if we succeeded. + # + if self.oVBoxSvcProcess is not None: + self.oVBoxSvcProcess.enableCrashReporting('crash/report/svc', 'crash/dump/svc'); + + # + # Wait for debugger to attach. + # + if self.oVBoxSvcProcess is not None and self.fVBoxSvcWaitForDebugger: + reporter.log('Press any key after attaching to VBoxSVC (pid %s) with a debugger...' + % (self.oVBoxSvcProcess.getPid(),)); + sys.stdin.read(1); + + # + # Fudge and pid file. + # + if self.oVBoxSvcProcess is not None and not self.oVBoxSvcProcess.wait(cMsFudge): + if fWritePidFile: + iPid = self.oVBoxSvcProcess.getPid(); + try: + oFile = utils.openNoInherit(self.sVBoxSvcPidFile, "w+"); + oFile.write('%s' % (iPid,)); + oFile.close(); + except: + reporter.logXcpt('sPidFile=%s' % (self.sVBoxSvcPidFile,)); + reporter.log('VBoxSVC PID=%u' % (iPid,)); + + # + # Finally add the task so we'll notice when it dies in a relatively timely manner. + # + self.addTask(self.oVBoxSvcProcess); + else: + self.oVBoxSvcProcess = None; + try: os.remove(self.sVBoxSvcPidFile); + except: pass; + + return self.oVBoxSvcProcess is not None; + + + def _killVBoxSVCByPidFile(self, sPidFile): + """ Kill a VBoxSVC given the pid from it's pid file. """ + + # Read the pid file. + if not os.path.isfile(sPidFile): + return False; + try: + oFile = utils.openNoInherit(sPidFile, "r"); + sPid = oFile.readline().strip(); + oFile.close(); + except: + reporter.logXcpt('sPidfile=%s' % (sPidFile,)); + return False; + + # Convert the pid to an integer and validate the range a little bit. + try: + iPid = long(sPid); + except: + reporter.logXcpt('sPidfile=%s sPid="%s"' % (sPidFile, sPid)); + return False; + if iPid <= 0: + reporter.log('negative pid - sPidfile=%s sPid="%s" iPid=%d' % (sPidFile, sPid, iPid)); + return False; + + # Take care checking that it's VBoxSVC we're about to inhume. + if base.processCheckPidAndName(iPid, "VBoxSVC") is not True: + reporter.log('Ignoring stale VBoxSVC pid file (pid=%s)' % (iPid,)); + return False; + + # Loop thru our different ways of getting VBoxSVC to terminate. + for aHow in [ [ base.sendUserSignal1, 5000, 'Dropping VBoxSVC a SIGUSR1 hint...'], \ + [ base.processInterrupt, 5000, 'Dropping VBoxSVC a SIGINT hint...'], \ + [ base.processTerminate, 7500, 'VBoxSVC is still around, killing it...'] ]: + reporter.log(aHow[2]); + if aHow[0](iPid) is True: + msStart = base.timestampMilli(); + while base.timestampMilli() - msStart < 5000 \ + and base.processExists(iPid): + time.sleep(0.2); + + fRc = not base.processExists(iPid); + if fRc is True: + break; + if fRc: + reporter.log('Successfully killed VBoxSVC (pid=%s)' % (iPid,)); + else: + reporter.log('Failed to kill VBoxSVC (pid=%s)' % (iPid,)); + return fRc; + + def _stopVBoxSVC(self): + """ + Stops VBoxSVC. Try the polite way first. + """ + + if self.oVBoxSvcProcess: + self.removeTask(self.oVBoxSvcProcess); + self.oVBoxSvcProcess.enableCrashReporting(None, None); # Disables it. + + fRc = False; + if self.oVBoxSvcProcess is not None \ + and not self.fVBoxSvcInDebugger: + # by process object. + if self.oVBoxSvcProcess.isRunning(): + reporter.log('Dropping VBoxSVC a SIGUSR1 hint...'); + if not self.oVBoxSvcProcess.sendUserSignal1() \ + or not self.oVBoxSvcProcess.wait(5000): + reporter.log('Dropping VBoxSVC a SIGINT hint...'); + if not self.oVBoxSvcProcess.interrupt() \ + or not self.oVBoxSvcProcess.wait(5000): + reporter.log('VBoxSVC is still around, killing it...'); + self.oVBoxSvcProcess.terminate(); + self.oVBoxSvcProcess.wait(7500); + else: + reporter.log('VBoxSVC is no longer running...'); + + if not self.oVBoxSvcProcess.isRunning(): + iExit = self.oVBoxSvcProcess.getExitCode(); + if iExit != 0 or not self.oVBoxSvcProcess.isNormalExit(): + reporter.error("VBoxSVC exited with status %d (%#x)" % (iExit, self.oVBoxSvcProcess.uExitCode)); + self.oVBoxSvcProcess = None; + else: + # by pid file. + self._killVBoxSVCByPidFile('%s/VBoxSVC.pid' % (self.sScratchPath,)); + return fRc; + + def _setupVBoxApi(self): + """ + Import and set up the vboxapi. + The caller saves and restores sys.path. + """ + + # Setup vbox logging for self (the test driver). + self.sSelfLogFile = '%s/VBoxTestDriver.log' % (self.sScratchPath,); + try: os.remove(self.sSelfLogFile); + except: pass; + os.environ['VBOX_LOG'] = self.sLogSelfGroups; + os.environ['VBOX_LOG_FLAGS'] = '%s append' % (self.sLogSelfFlags, ); + if self.sLogSelfDest: + os.environ['VBOX_LOG_DEST'] = 'nodeny ' + self.sLogSelfDest; + else: + os.environ['VBOX_LOG_DEST'] = 'nodeny file=%s' % (self.sSelfLogFile,); + os.environ['VBOX_RELEASE_LOG_FLAGS'] = 'time append'; + + reporter.log2('Self environment:'); + for sKey, sVal in sorted(os.environ.items()): + reporter.log2('%s=%s' % (sKey, sVal)); + + # Hack the sys.path + environment so the vboxapi can be found. + sys.path.insert(0, self.oBuild.sInstallPath); + if self.oBuild.sSdkPath is not None: + sys.path.insert(0, os.path.join(self.oBuild.sSdkPath, 'installer')) + sys.path.insert(1, os.path.join(self.oBuild.sSdkPath, 'install')); # stupid stupid windows installer! + sys.path.insert(2, os.path.join(self.oBuild.sSdkPath, 'bindings', 'xpcom', 'python')) + os.environ['VBOX_PROGRAM_PATH'] = self.oBuild.sInstallPath; + reporter.log("sys.path: %s" % (sys.path)); + + try: + from vboxapi import VirtualBoxManager; # pylint: disable=import-error + except: + reporter.logXcpt('Error importing vboxapi'); + return False; + + # Exception and error hacks. + try: + # pylint: disable=import-error + if self.sHost == 'win': + from pythoncom import com_error as NativeComExceptionClass # pylint: disable=no-name-in-module + import winerror as NativeComErrorClass + else: + from xpcom import Exception as NativeComExceptionClass + from xpcom import nsError as NativeComErrorClass + # pylint: enable=import-error + except: + reporter.logXcpt('Error importing (XP)COM related stuff for exception hacks and errors'); + return False; + __deployExceptionHacks__(NativeComExceptionClass) + ComError.copyErrors(NativeComErrorClass); + + # Create the manager. + try: + self.oVBoxMgr = VirtualBoxManager(None, None) + except: + self.oVBoxMgr = None; + reporter.logXcpt('VirtualBoxManager exception'); + return False; + + # Figure the API version. + try: + oVBox = self.oVBoxMgr.getVirtualBox(); + + try: + sVer = oVBox.version; + except: + reporter.logXcpt('Failed to get VirtualBox version, assuming 4.0.0'); + sVer = "4.0.0"; + reporter.log("IVirtualBox.version=%s" % (sVer,)); + + # Convert the string to three integer values and check ranges. + asVerComponents = sVer.split('.'); + try: + sLast = asVerComponents[2].split('_')[0].split('r')[0]; + aiVerComponents = (int(asVerComponents[0]), int(asVerComponents[1]), int(sLast)); + except: + raise base.GenError('Malformed version "%s"' % (sVer,)); + if aiVerComponents[0] < 3 or aiVerComponents[0] > 19: + raise base.GenError('Malformed version "%s" - 1st component is out of bounds 3..19: %u' + % (sVer, aiVerComponents[0])); + if aiVerComponents[1] < 0 or aiVerComponents[1] > 9: + raise base.GenError('Malformed version "%s" - 2nd component is out of bounds 0..9: %u' + % (sVer, aiVerComponents[1])); + if aiVerComponents[2] < 0 or aiVerComponents[2] > 99: + raise base.GenError('Malformed version "%s" - 3rd component is out of bounds 0..99: %u' + % (sVer, aiVerComponents[2])); + + # Convert the three integers into a floating point value. The API is stable within a + # x.y release, so the third component only indicates whether it's a stable or + # development build of the next release. + self.fpApiVer = aiVerComponents[0] + 0.1 * aiVerComponents[1]; + if aiVerComponents[2] >= 51: + if self.fpApiVer not in [6.1, 5.2, 4.3, 3.2,]: + self.fpApiVer += 0.1; + else: + self.fpApiVer = int(self.fpApiVer) + 1.0; + # fudge value to be always bigger than the nominal value (0.1 gets rounded down) + if round(self.fpApiVer, 1) > self.fpApiVer: + self.fpApiVer += sys.float_info.epsilon * self.fpApiVer / 2.0; + + try: + self.uRevision = oVBox.revision; + except: + reporter.logXcpt('Failed to get VirtualBox revision, assuming 0'); + self.uRevision = 0; + reporter.log("IVirtualBox.revision=%u" % (self.uRevision,)); + + try: + self.uApiRevision = oVBox.APIRevision; + except: + reporter.logXcpt('Failed to get VirtualBox APIRevision, faking it.'); + self.uApiRevision = self.makeApiRevision(aiVerComponents[0], aiVerComponents[1], aiVerComponents[2], 0); + reporter.log("IVirtualBox.APIRevision=%#x" % (self.uApiRevision,)); + + # Patch VBox manage to gloss over portability issues (error constants, etc). + self._patchVBoxMgr(); + + # Wrap oVBox. + from testdriver.vboxwrappers import VirtualBoxWrapper; + self.oVBox = VirtualBoxWrapper(oVBox, self.oVBoxMgr, self.fpApiVer, self); + + # Install the constant wrapping hack. + vboxcon.goHackModuleClass.oVBoxMgr = self.oVBoxMgr; # VBoxConstantWrappingHack. + vboxcon.fpApiVer = self.fpApiVer; + reporter.setComXcptFormatter(formatComOrXpComException); + + except: + self.oVBoxMgr = None; + self.oVBox = None; + reporter.logXcpt("getVirtualBox / API version exception"); + return False; + + # Done + self.fImportedVBoxApi = True; + reporter.log('Found version %s (%s)' % (self.fpApiVer, sVer)); + return True; + + def _patchVBoxMgr(self): + """ + Glosses over missing self.oVBoxMgr methods on older VBox versions. + """ + + def _xcptGetResult(oSelf, oXcpt = None): + """ See vboxapi. """ + _ = oSelf; + if oXcpt is None: oXcpt = sys.exc_info()[1]; + if sys.platform == 'win32': + import winerror; # pylint: disable=import-error + hrXcpt = oXcpt.hresult; + if hrXcpt == winerror.DISP_E_EXCEPTION: + hrXcpt = oXcpt.excepinfo[5]; + else: + hrXcpt = oXcpt.error; + return hrXcpt; + + def _xcptIsDeadInterface(oSelf, oXcpt = None): + """ See vboxapi. """ + return oSelf.xcptGetStatus(oXcpt) in [ + 0x80004004, -2147467260, # NS_ERROR_ABORT + 0x800706be, -2147023170, # NS_ERROR_CALL_FAILED (RPC_S_CALL_FAILED) + 0x800706ba, -2147023174, # RPC_S_SERVER_UNAVAILABLE. + 0x800706be, -2147023170, # RPC_S_CALL_FAILED. + 0x800706bf, -2147023169, # RPC_S_CALL_FAILED_DNE. + 0x80010108, -2147417848, # RPC_E_DISCONNECTED. + 0x800706b5, -2147023179, # RPC_S_UNKNOWN_IF + ]; + + def _xcptIsOurXcptKind(oSelf, oXcpt = None): + """ See vboxapi. """ + _ = oSelf; + if oXcpt is None: oXcpt = sys.exc_info()[1]; + if sys.platform == 'win32': + from pythoncom import com_error as NativeComExceptionClass # pylint: disable=import-error,no-name-in-module + else: + from xpcom import Exception as NativeComExceptionClass # pylint: disable=import-error + return isinstance(oXcpt, NativeComExceptionClass); + + def _xcptIsEqual(oSelf, oXcpt, hrStatus): + """ See vboxapi. """ + hrXcpt = oSelf.xcptGetResult(oXcpt); + return hrXcpt == hrStatus or hrXcpt == hrStatus - 0x100000000; # pylint: disable=consider-using-in + + def _xcptToString(oSelf, oXcpt): + """ See vboxapi. """ + _ = oSelf; + if oXcpt is None: oXcpt = sys.exc_info()[1]; + return str(oXcpt); + + def _getEnumValueName(oSelf, sEnumTypeNm, oEnumValue, fTypePrefix = False): + """ See vboxapi. """ + _ = oSelf; _ = fTypePrefix; + return '%s::%s' % (sEnumTypeNm, oEnumValue); + + # Add utilities found in newer vboxapi revision. + if not hasattr(self.oVBoxMgr, 'xcptIsDeadInterface'): + import types; + self.oVBoxMgr.xcptGetResult = types.MethodType(_xcptGetResult, self.oVBoxMgr); + self.oVBoxMgr.xcptIsDeadInterface = types.MethodType(_xcptIsDeadInterface, self.oVBoxMgr); + self.oVBoxMgr.xcptIsOurXcptKind = types.MethodType(_xcptIsOurXcptKind, self.oVBoxMgr); + self.oVBoxMgr.xcptIsEqual = types.MethodType(_xcptIsEqual, self.oVBoxMgr); + self.oVBoxMgr.xcptToString = types.MethodType(_xcptToString, self.oVBoxMgr); + if not hasattr(self.oVBoxMgr, 'getEnumValueName'): + import types; + self.oVBoxMgr.getEnumValueName = types.MethodType(_getEnumValueName, self.oVBoxMgr); + + + def _teardownVBoxApi(self): # pylint: disable=too-many-statements + """ + Drop all VBox object references and shutdown com/xpcom. + """ + if not self.fImportedVBoxApi: + return True; + import gc; + + # Drop all references we've have to COM objects. + self.aoRemoteSessions = []; + self.aoVMs = []; + self.oVBoxMgr = None; + self.oVBox = None; + vboxcon.goHackModuleClass.oVBoxMgr = None; # VBoxConstantWrappingHack. + reporter.setComXcptFormatter(None); + + # Do garbage collection to try get rid of those objects. + try: + gc.collect(); + except: + reporter.logXcpt(); + self.fImportedVBoxApi = False; + + # Check whether the python is still having any COM objects/interfaces around. + cVBoxMgrs = 0; + aoObjsLeftBehind = []; + if self.sHost == 'win': + import pythoncom; # pylint: disable=import-error + try: + cIfs = pythoncom._GetInterfaceCount(); # pylint: disable=no-member,protected-access + cObjs = pythoncom._GetGatewayCount(); # pylint: disable=no-member,protected-access + if cObjs == 0 and cIfs == 0: + reporter.log('_teardownVBoxApi: no interfaces or objects left behind.'); + else: + reporter.log('_teardownVBoxApi: Python COM still has %s objects and %s interfaces...' % ( cObjs, cIfs)); + + from win32com.client import DispatchBaseClass; # pylint: disable=import-error + for oObj in gc.get_objects(): + if isinstance(oObj, DispatchBaseClass): + reporter.log('_teardownVBoxApi: %s' % (oObj,)); + aoObjsLeftBehind.append(oObj); + elif utils.getObjectTypeName(oObj) == 'VirtualBoxManager': + reporter.log('_teardownVBoxApi: %s' % (oObj,)); + cVBoxMgrs += 1; + aoObjsLeftBehind.append(oObj); + oObj = None; + except: + reporter.logXcpt(); + + # If not being used, we can safely uninitialize COM. + if cIfs == 0 and cObjs == 0 and cVBoxMgrs == 0 and not aoObjsLeftBehind: + reporter.log('_teardownVBoxApi: Calling CoUninitialize...'); + try: pythoncom.CoUninitialize(); # pylint: disable=no-member + except: reporter.logXcpt(); + else: + reporter.log('_teardownVBoxApi: Returned from CoUninitialize.'); + else: + try: + # XPCOM doesn't crash and burn like COM if you shut it down with interfaces and objects around. + # Also, it keeps a number of internal objects and interfaces around to do its job, so shutting + # it down before we go looking for dangling interfaces is more or less required. + from xpcom import _xpcom as _xpcom; # pylint: disable=import-error,useless-import-alias + hrc = _xpcom.DeinitCOM(); + cIfs = _xpcom._GetInterfaceCount(); # pylint: disable=protected-access + cObjs = _xpcom._GetGatewayCount(); # pylint: disable=protected-access + + if cObjs == 0 and cIfs == 0: + reporter.log('_teardownVBoxApi: No XPCOM interfaces or objects active. (hrc=%#x)' % (hrc,)); + else: + reporter.log('_teardownVBoxApi: %s XPCOM objects and %s interfaces still around! (hrc=%#x)' + % (cObjs, cIfs, hrc)); + if hasattr(_xpcom, '_DumpInterfaces'): + try: _xpcom._DumpInterfaces(); # pylint: disable=protected-access + except: reporter.logXcpt('_teardownVBoxApi: _DumpInterfaces failed'); + + from xpcom.client import Component; # pylint: disable=import-error + for oObj in gc.get_objects(): + if isinstance(oObj, Component): + reporter.log('_teardownVBoxApi: %s' % (oObj,)); + aoObjsLeftBehind.append(oObj); + if utils.getObjectTypeName(oObj) == 'VirtualBoxManager': + reporter.log('_teardownVBoxApi: %s' % (oObj,)); + cVBoxMgrs += 1; + aoObjsLeftBehind.append(oObj); + oObj = None; + except: + reporter.logXcpt(); + + # Try get the referrers to (XP)COM interfaces and objects that was left behind. + for iObj in range(len(aoObjsLeftBehind)): # pylint: disable=consider-using-enumerate + try: + aoReferrers = gc.get_referrers(aoObjsLeftBehind[iObj]); + reporter.log('_teardownVBoxApi: Found %u referrers to %s:' % (len(aoReferrers), aoObjsLeftBehind[iObj],)); + for oReferrer in aoReferrers: + oMyFrame = sys._getframe(0); # pylint: disable=protected-access + if oReferrer is oMyFrame: + reporter.log('_teardownVBoxApi: - frame of this function'); + elif oReferrer is aoObjsLeftBehind: + reporter.log('_teardownVBoxApi: - aoObjsLeftBehind'); + else: + fPrinted = False; + if isinstance(oReferrer, (dict, list, tuple)): + try: + aoSubReferreres = gc.get_referrers(oReferrer); + for oSubRef in aoSubReferreres: + if not isinstance(oSubRef, list) \ + and not isinstance(oSubRef, dict) \ + and oSubRef is not oMyFrame \ + and oSubRef is not aoSubReferreres: + reporter.log('_teardownVBoxApi: - %s :: %s:' + % (utils.getObjectTypeName(oSubRef), utils.getObjectTypeName(oReferrer))); + fPrinted = True; + break; + del aoSubReferreres; + except: + reporter.logXcpt('subref'); + if not fPrinted: + reporter.log('_teardownVBoxApi: - %s:' % (utils.getObjectTypeName(oReferrer),)); + try: + import pprint; + for sLine in pprint.pformat(oReferrer, width = 130).split('\n'): + reporter.log('_teardownVBoxApi: %s' % (sLine,)); + except: + reporter.log('_teardownVBoxApi: %s' % (oReferrer,)); + except: + reporter.logXcpt(); + del aoObjsLeftBehind; + + # Force garbage collection again, just for good measure. + try: + gc.collect(); + time.sleep(0.5); # fudge factor + except: + reporter.logXcpt(); + return True; + + def _powerOffAllVms(self): + """ + Tries to power off all running VMs. + """ + for oSession in self.aoRemoteSessions: + uPid = oSession.getPid(); + if uPid is not None: + reporter.log('_powerOffAllVms: PID is %s for %s, trying to kill it.' % (uPid, oSession.sName,)); + base.processKill(uPid); + else: + reporter.log('_powerOffAllVms: No PID for %s' % (oSession.sName,)); + oSession.close(); + return None; + + + + # + # Build type, OS and arch getters. + # + + def getBuildType(self): + """ + Get the build type. + """ + if not self._detectBuild(): + return 'release'; + return self.oBuild.sType; + + def getBuildOs(self): + """ + Get the build OS. + """ + if not self._detectBuild(): + return self.sHost; + return self.oBuild.sOs; + + def getBuildArch(self): + """ + Get the build arch. + """ + if not self._detectBuild(): + return self.sHostArch; + return self.oBuild.sArch; + + def getGuestAdditionsIso(self): + """ + Get the path to the guest addition iso. + """ + if not self._detectBuild(): + return None; + return self.oBuild.sGuestAdditionsIso; + + # + # Override everything from the base class so the testdrivers don't have to + # check whether we have overridden a method or not. + # + + def showUsage(self): + rc = base.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('Generic VirtualBox Options:'); + reporter.log(' --vbox-session-type <type>'); + reporter.log(' Sets the session type. Typical values are: gui, headless, sdl'); + reporter.log(' Default: %s' % (self.sSessionTypeDef)); + reporter.log(' --vrdp, --no-vrdp'); + reporter.log(' Enables VRDP, ports starting at 6000'); + reporter.log(' Default: --vrdp'); + reporter.log(' --vrdp-base-port <port>'); + reporter.log(' Sets the base for VRDP port assignments.'); + reporter.log(' Default: %s' % (self.uVrdpBasePortDef)); + reporter.log(' --vbox-default-bridged-nic <interface>'); + reporter.log(' Sets the default interface for bridged networking.'); + reporter.log(' Default: autodetect'); + reporter.log(' --vbox-use-svc-defaults'); + reporter.log(' Use default locations and files for VBoxSVC. This is useful'); + reporter.log(' for automatically configuring the test VMs for debugging.'); + reporter.log(' --vbox-log'); + reporter.log(' The VBox logger group settings for everyone.'); + reporter.log(' --vbox-log-flags'); + reporter.log(' The VBox logger flags settings for everyone.'); + reporter.log(' --vbox-log-dest'); + reporter.log(' The VBox logger destination settings for everyone.'); + reporter.log(' --vbox-self-log'); + reporter.log(' The VBox logger group settings for the testdriver.'); + reporter.log(' --vbox-self-log-flags'); + reporter.log(' The VBox logger flags settings for the testdriver.'); + reporter.log(' --vbox-self-log-dest'); + reporter.log(' The VBox logger destination settings for the testdriver.'); + reporter.log(' --vbox-session-log'); + reporter.log(' The VM session logger group settings.'); + reporter.log(' --vbox-session-log-flags'); + reporter.log(' The VM session logger flags.'); + reporter.log(' --vbox-session-log-dest'); + reporter.log(' The VM session logger destination settings.'); + reporter.log(' --vbox-svc-log'); + reporter.log(' The VBoxSVC logger group settings.'); + reporter.log(' --vbox-svc-log-flags'); + reporter.log(' The VBoxSVC logger flag settings.'); + reporter.log(' --vbox-svc-log-dest'); + reporter.log(' The VBoxSVC logger destination settings.'); + reporter.log(' --vbox-svc-debug'); + reporter.log(' Start VBoxSVC in a debugger.'); + reporter.log(' --vbox-svc-wait-debug'); + reporter.log(' Start VBoxSVC and wait for debugger to attach to it.'); + reporter.log(' --vbox-always-upload-logs'); + reporter.log(' Whether to always upload log files, or only do so on failure.'); + reporter.log(' --vbox-always-upload-screenshots'); + reporter.log(' Whether to always upload final screen shots, or only do so on failure.'); + reporter.log(' --vbox-always-upload-recordings, --no-vbox-always-upload-recordings'); + reporter.log(' Whether to always upload recordings, or only do so on failure.'); + reporter.log(' Default: --no-vbox-always-upload-recordings'); + reporter.log(' --vbox-debugger, --no-vbox-debugger'); + reporter.log(' Enables the VBox debugger, port at 5000'); + reporter.log(' Default: --vbox-debugger'); + reporter.log(' --vbox-recording, --no-vbox-recording'); + reporter.log(' Enables/disables recording.'); + reporter.log(' Default: --no-vbox-recording'); + reporter.log(' --vbox-recording-audio, --no-vbox-recording-audio'); + reporter.log(' Enables/disables audio recording.'); + reporter.log(' Default: --no-vbox-recording-audio'); + reporter.log(' --vbox-recording-max-time <seconds>'); + reporter.log(' Limits the maximum recording time in seconds.'); + reporter.log(' Default: Unlimited.'); + reporter.log(' --vbox-recording-max-file-size <MiB>'); + reporter.log(' Limits the maximum per-file size in MiB.'); + reporter.log(' Explicitly specify 0 for unlimited size.'); + reporter.log(' Default: 195 MB.'); + if self.oTestVmSet is not None: + self.oTestVmSet.showUsage(); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--vbox-session-type': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--vbox-session-type" takes an argument'); + self.sSessionType = asArgs[iArg]; + elif asArgs[iArg] == '--vrdp': + self.fEnableVrdp = True; + elif asArgs[iArg] == '--no-vrdp': + self.fEnableVrdp = False; + elif asArgs[iArg] == '--vrdp-base-port': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--vrdp-base-port" takes an argument'); + try: self.uVrdpBasePort = int(asArgs[iArg]); + except: raise base.InvalidOption('The "--vrdp-base-port" value "%s" is not a valid integer' % (asArgs[iArg],)); + if self.uVrdpBasePort <= 0 or self.uVrdpBasePort >= 65530: + raise base.InvalidOption('The "--vrdp-base-port" value "%s" is not in the valid range (1..65530)' + % (asArgs[iArg],)); + elif asArgs[iArg] == '--vbox-default-bridged-nic': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--vbox-default-bridged-nic" takes an argument'); + self.sDefBridgedNic = asArgs[iArg]; + elif asArgs[iArg] == '--vbox-use-svc-defaults': + self.fUseDefaultSvc = True; + elif asArgs[iArg] == '--vbox-self-log': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--vbox-self-log" takes an argument'); + self.sLogSelfGroups = asArgs[iArg]; + elif asArgs[iArg] == '--vbox-self-log-flags': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--vbox-self-log-flags" takes an argument'); + self.sLogSelfFlags = asArgs[iArg]; + elif asArgs[iArg] == '--vbox-self-log-dest': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--vbox-self-log-dest" takes an argument'); + self.sLogSelfDest = asArgs[iArg]; + elif asArgs[iArg] == '--vbox-session-log': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--vbox-session-log" takes an argument'); + self.sLogSessionGroups = asArgs[iArg]; + elif asArgs[iArg] == '--vbox-session-log-flags': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--vbox-session-log-flags" takes an argument'); + self.sLogSessionFlags = asArgs[iArg]; + elif asArgs[iArg] == '--vbox-session-log-dest': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--vbox-session-log-dest" takes an argument'); + self.sLogSessionDest = asArgs[iArg]; + elif asArgs[iArg] == '--vbox-svc-log': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--vbox-svc-log" takes an argument'); + self.sLogSvcGroups = asArgs[iArg]; + elif asArgs[iArg] == '--vbox-svc-log-flags': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--vbox-svc-log-flags" takes an argument'); + self.sLogSvcFlags = asArgs[iArg]; + elif asArgs[iArg] == '--vbox-svc-log-dest': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--vbox-svc-log-dest" takes an argument'); + self.sLogSvcDest = asArgs[iArg]; + elif asArgs[iArg] == '--vbox-log': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--vbox-log" takes an argument'); + self.sLogSelfGroups = asArgs[iArg]; + self.sLogSessionGroups = asArgs[iArg]; + self.sLogSvcGroups = asArgs[iArg]; + elif asArgs[iArg] == '--vbox-log-flags': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--vbox-svc-flags" takes an argument'); + self.sLogSelfFlags = asArgs[iArg]; + self.sLogSessionFlags = asArgs[iArg]; + self.sLogSvcFlags = asArgs[iArg]; + elif asArgs[iArg] == '--vbox-log-dest': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--vbox-log-dest" takes an argument'); + self.sLogSelfDest = asArgs[iArg]; + self.sLogSessionDest = asArgs[iArg]; + self.sLogSvcDest = asArgs[iArg]; + elif asArgs[iArg] == '--vbox-svc-debug': + self.fVBoxSvcInDebugger = True; + elif asArgs[iArg] == '--vbox-svc-wait-debug': + self.fVBoxSvcWaitForDebugger = True; + elif asArgs[iArg] == '--vbox-always-upload-logs': + self.fAlwaysUploadLogs = True; + elif asArgs[iArg] == '--vbox-always-upload-screenshots': + self.fAlwaysUploadScreenshots = True; + elif asArgs[iArg] == '--no-vbox-always-upload-recordings': + self.fAlwaysUploadRecordings = False; + elif asArgs[iArg] == '--vbox-always-upload-recordings': + self.fAlwaysUploadRecordings = True; + elif asArgs[iArg] == '--vbox-debugger': + self.fEnableDebugger = True; + elif asArgs[iArg] == '--no-vbox-debugger': + self.fEnableDebugger = False; + elif asArgs[iArg] == '--vbox-recording': + self.fRecordingEnabled = True; + elif asArgs[iArg] == '--vbox-no-recording': + self.fRecordingEnabled = False; + elif asArgs[iArg] == '--no-vbox-recording-audio': + self.fRecordingAudio = False; + elif asArgs[iArg] == '--vbox-recording-audio': + self.fRecordingAudio = True; + elif asArgs[iArg] == '--vbox-recording-max-time': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--vbox-recording-max-time" takes an argument'); + self.cSecsRecordingMax = int(asArgs[iArg]); + elif asArgs[iArg] == '--vbox-recording-max-file-size': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--vbox-recording-max-file-size" takes an argument'); + self.cMbRecordingMax = int(asArgs[iArg]); + else: + # Relevant for selecting VMs to test? + if self.oTestVmSet is not None: + iRc = self.oTestVmSet.parseOption(asArgs, iArg); + if iRc != iArg: + return iRc; + + # Hand it to the base class. + return base.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def completeOptions(self): + return base.TestDriver.completeOptions(self); + + def getNetworkAdapterNameFromType(self, oNic): + """ + Returns the network adapter name from a given adapter type. + + Returns an empty string if not found / invalid. + """ + sAdpName = ''; + if oNic.adapterType in (vboxcon.NetworkAdapterType_Am79C970A, \ + vboxcon.NetworkAdapterType_Am79C973, \ + vboxcon.NetworkAdapterType_Am79C960): + sAdpName = 'pcnet'; + elif oNic.adapterType in (vboxcon.NetworkAdapterType_I82540EM, \ + vboxcon.NetworkAdapterType_I82543GC, \ + vboxcon.NetworkAdapterType_I82545EM): + sAdpName = 'e1000'; + elif oNic.adapterType == vboxcon.NetworkAdapterType_Virtio: + sAdpName = 'virtio-net'; + return sAdpName; + + def getResourceSet(self): + asRsrcs = []; + if self.oTestVmSet is not None: + asRsrcs.extend(self.oTestVmSet.getResourceSet()); + asRsrcs.extend(base.TestDriver.getResourceSet(self)); + return asRsrcs; + + def actionExtract(self): + return base.TestDriver.actionExtract(self); + + def actionVerify(self): + return base.TestDriver.actionVerify(self); + + def actionConfig(self): + return base.TestDriver.actionConfig(self); + + def actionExecute(self): + return base.TestDriver.actionExecute(self); + + def actionCleanupBefore(self): + """ + Kill any VBoxSVC left behind by a previous test run. + """ + self._killVBoxSVCByPidFile('%s/VBoxSVC.pid' % (self.sScratchPath,)); + return base.TestDriver.actionCleanupBefore(self); + + def actionCleanupAfter(self): + """ + Clean up the VBox bits and then call the base driver. + + If your test driver overrides this, it should normally call us at the + end of the job. + """ + cErrorsEntry = reporter.getErrorCount(); + + # Kill any left over VM processes. + self._powerOffAllVms(); + + # Drop all VBox object references and shutdown xpcom then + # terminating VBoxSVC, with extreme prejudice if need be. + self._teardownVBoxApi(); + self._stopVBoxSVC(); + + # Add the VBoxSVC and testdriver debug+release log files. + if self.fAlwaysUploadLogs or reporter.getErrorCount() > 0: + if self.sVBoxSvcLogFile is not None and os.path.isfile(self.sVBoxSvcLogFile): + reporter.addLogFile(self.sVBoxSvcLogFile, 'log/debug/svc', 'Debug log file for VBoxSVC'); + self.sVBoxSvcLogFile = None; + + if self.sSelfLogFile is not None and os.path.isfile(self.sSelfLogFile): + reporter.addLogFile(self.sSelfLogFile, 'log/debug/client', 'Debug log file for the test driver'); + self.sSelfLogFile = None; + + if self.sSessionLogFile is not None and os.path.isfile(self.sSessionLogFile): + reporter.addLogFile(self.sSessionLogFile, 'log/debug/session', 'Debug log file for the VM session'); + self.sSessionLogFile = None; + + sVBoxSvcRelLog = os.path.join(self.sScratchPath, 'VBoxUserHome', 'VBoxSVC.log'); + if os.path.isfile(sVBoxSvcRelLog): + reporter.addLogFile(sVBoxSvcRelLog, 'log/release/svc', 'Release log file for VBoxSVC'); + for sSuff in [ '.1', '.2', '.3', '.4', '.5', '.6', '.7', '.8' ]: + if os.path.isfile(sVBoxSvcRelLog + sSuff): + reporter.addLogFile(sVBoxSvcRelLog + sSuff, 'log/release/svc', 'Release log file for VBoxSVC'); + + # Finally, call the base driver to wipe the scratch space. + fRc = base.TestDriver.actionCleanupAfter(self); + + # Flag failure if the error count increased. + if reporter.getErrorCount() > cErrorsEntry: + fRc = False; + return fRc; + + + def actionAbort(self): + """ + Terminate VBoxSVC if we've got a pid file. + """ + # + # Take default action first, then kill VBoxSVC. The other way around + # is problematic since the testscript would continue running and possibly + # trigger a new VBoxSVC to start. + # + fRc1 = base.TestDriver.actionAbort(self); + fRc2 = self._killVBoxSVCByPidFile('%s/VBoxSVC.pid' % (self.sScratchPath,)); + return fRc1 is True and fRc2 is True; + + def onExit(self, iRc): + """ + Stop VBoxSVC if we've started it. + """ + if self.oVBoxSvcProcess is not None: + reporter.log('*** Shutting down the VBox API... (iRc=%s)' % (iRc,)); + self._powerOffAllVms(); + self._teardownVBoxApi(); + self._stopVBoxSVC(); + reporter.log('*** VBox API shutdown done.'); + return base.TestDriver.onExit(self, iRc); + + + # + # Task wait method override. + # + + def notifyAboutReadyTask(self, oTask): + """ + Overriding base.TestDriver.notifyAboutReadyTask. + """ + try: + self.oVBoxMgr.interruptWaitEvents(); + reporter.log2('vbox.notifyAboutReadyTask: called interruptWaitEvents'); + except: + reporter.logXcpt('vbox.notifyAboutReadyTask'); + return base.TestDriver.notifyAboutReadyTask(self, oTask); + + def waitForTasksSleepWorker(self, cMsTimeout): + """ + Overriding base.TestDriver.waitForTasksSleepWorker. + """ + try: + rc = self.oVBoxMgr.waitForEvents(int(cMsTimeout)); + _ = rc; #reporter.log2('vbox.waitForTasksSleepWorker(%u): true (waitForEvents -> %s)' % (cMsTimeout, rc)); + reporter.doPollWork('vbox.TestDriver.waitForTasksSleepWorker'); + return True; + except KeyboardInterrupt: + raise; + except: + reporter.logXcpt('vbox.waitForTasksSleepWorker'); + return False; + + # + # Utility methods. + # + + def processEvents(self, cMsTimeout = 0): + """ + Processes events, returning after the first batch has been processed + or the time limit has been reached. + + Only Ctrl-C exception, no return. + """ + try: + self.oVBoxMgr.waitForEvents(cMsTimeout); + except KeyboardInterrupt: + raise; + except: + pass; + return None; + + def processPendingEvents(self): + """ processEvents(0) - no waiting. """ + return self.processEvents(0); + + def sleep(self, cSecs): + """ + Sleep for a specified amount of time, processing XPCOM events all the while. + """ + cMsTimeout = long(cSecs * 1000); + msStart = base.timestampMilli(); + self.processEvents(0); + while True: + cMsElapsed = base.timestampMilli() - msStart; + if cMsElapsed > cMsTimeout: + break; + #reporter.log2('cMsTimeout=%s - cMsElapsed=%d => %s' % (cMsTimeout, cMsElapsed, cMsTimeout - cMsElapsed)); + self.processEvents(cMsTimeout - cMsElapsed); + return None; + + def _logVmInfoUnsafe(self, oVM): # pylint: disable=too-many-statements,too-many-branches + """ + Internal worker for logVmInfo that is wrapped in try/except. + """ + reporter.log(" Name: %s" % (oVM.name,)); + reporter.log(" ID: %s" % (oVM.id,)); + oOsType = self.oVBox.getGuestOSType(oVM.OSTypeId); + reporter.log(" OS Type: %s - %s" % (oVM.OSTypeId, oOsType.description,)); + reporter.log(" Machine state: %s" % (oVM.state,)); + reporter.log(" Session state: %s" % (oVM.sessionState,)); + if self.fpApiVer >= 4.2: + reporter.log(" Session PID: %u (%#x)" % (oVM.sessionPID, oVM.sessionPID,)); + else: + reporter.log(" Session PID: %u (%#x)" % (oVM.sessionPid, oVM.sessionPid,)); + if self.fpApiVer >= 5.0: + reporter.log(" Session Name: %s" % (oVM.sessionName,)); + else: + reporter.log(" Session Name: %s" % (oVM.sessionType,)); + reporter.log(" CPUs: %s" % (oVM.CPUCount,)); + reporter.log(" RAM: %sMB" % (oVM.memorySize,)); + if self.fpApiVer >= 6.1 and hasattr(oVM, 'graphicsAdapter'): + reporter.log(" VRAM: %sMB" % (oVM.graphicsAdapter.VRAMSize,)); + reporter.log(" Monitors: %s" % (oVM.graphicsAdapter.monitorCount,)); + reporter.log(" GraphicsController: %s" + % (self.oVBoxMgr.getEnumValueName('GraphicsControllerType', # pylint: disable=not-callable + oVM.graphicsAdapter.graphicsControllerType),)); + else: + reporter.log(" VRAM: %sMB" % (oVM.VRAMSize,)); + reporter.log(" Monitors: %s" % (oVM.monitorCount,)); + reporter.log(" GraphicsController: %s" + % (self.oVBoxMgr.getEnumValueName('GraphicsControllerType', oVM.graphicsControllerType),)); # pylint: disable=not-callable + reporter.log(" Chipset: %s" % (self.oVBoxMgr.getEnumValueName('ChipsetType', oVM.chipsetType),)); # pylint: disable=not-callable + if self.fpApiVer >= 6.2 and hasattr(vboxcon, 'IommuType_None'): + reporter.log(" IOMMU: %s" % (self.oVBoxMgr.getEnumValueName('IommuType', oVM.iommuType),)); # pylint: disable=not-callable + reporter.log(" Firmware: %s" % (self.oVBoxMgr.getEnumValueName('FirmwareType', oVM.firmwareType),)); # pylint: disable=not-callable + reporter.log(" HwVirtEx: %s" % (oVM.getHWVirtExProperty(vboxcon.HWVirtExPropertyType_Enabled),)); + reporter.log(" VPID support: %s" % (oVM.getHWVirtExProperty(vboxcon.HWVirtExPropertyType_VPID),)); + reporter.log(" Nested paging: %s" % (oVM.getHWVirtExProperty(vboxcon.HWVirtExPropertyType_NestedPaging),)); + atTypes = [ + ( 'CPUPropertyType_PAE', 'PAE: '), + ( 'CPUPropertyType_LongMode', 'Long-mode: '), + ( 'CPUPropertyType_HWVirt', 'Nested VT-x/AMD-V: '), + ( 'CPUPropertyType_APIC', 'APIC: '), + ( 'CPUPropertyType_X2APIC', 'X2APIC: '), + ( 'CPUPropertyType_TripleFaultReset', 'TripleFaultReset: '), + ( 'CPUPropertyType_IBPBOnVMExit', 'IBPBOnVMExit: '), + ( 'CPUPropertyType_SpecCtrl', 'SpecCtrl: '), + ( 'CPUPropertyType_SpecCtrlByHost', 'SpecCtrlByHost: '), + ]; + for sEnumValue, sDesc in atTypes: + if hasattr(vboxcon, sEnumValue): + reporter.log(" %s%s" % (sDesc, oVM.getCPUProperty(getattr(vboxcon, sEnumValue)),)); + reporter.log(" ACPI: %s" % (oVM.BIOSSettings.ACPIEnabled,)); + reporter.log(" IO-APIC: %s" % (oVM.BIOSSettings.IOAPICEnabled,)); + if self.fpApiVer >= 3.2: + if self.fpApiVer >= 4.2: + reporter.log(" HPET: %s" % (oVM.HPETEnabled,)); + else: + reporter.log(" HPET: %s" % (oVM.hpetEnabled,)); + if self.fpApiVer >= 6.1 and hasattr(oVM, 'graphicsAdapter'): + reporter.log(" 3D acceleration: %s" % (oVM.graphicsAdapter.accelerate3DEnabled,)); + reporter.log(" 2D acceleration: %s" % (oVM.graphicsAdapter.accelerate2DVideoEnabled,)); + else: + reporter.log(" 3D acceleration: %s" % (oVM.accelerate3DEnabled,)); + reporter.log(" 2D acceleration: %s" % (oVM.accelerate2DVideoEnabled,)); + reporter.log(" TeleporterEnabled: %s" % (oVM.teleporterEnabled,)); + reporter.log(" TeleporterPort: %s" % (oVM.teleporterPort,)); + reporter.log(" TeleporterAddress: %s" % (oVM.teleporterAddress,)); + reporter.log(" TeleporterPassword: %s" % (oVM.teleporterPassword,)); + reporter.log(" Clipboard mode: %s" % (oVM.clipboardMode,)); + if self.fpApiVer >= 5.0: + reporter.log(" Drag and drop mode: %s" % (oVM.dnDMode,)); + elif self.fpApiVer >= 4.3: + reporter.log(" Drag and drop mode: %s" % (oVM.dragAndDropMode,)); + if self.fpApiVer >= 4.0: + reporter.log(" VRDP server: %s" % (oVM.VRDEServer.enabled,)); + try: sPorts = oVM.VRDEServer.getVRDEProperty("TCP/Ports"); + except: sPorts = ""; + reporter.log(" VRDP server ports: %s" % (sPorts,)); + reporter.log(" VRDP auth: %s (%s)" % (oVM.VRDEServer.authType, oVM.VRDEServer.authLibrary,)); + else: + reporter.log(" VRDP server: %s" % (oVM.VRDPServer.enabled,)); + reporter.log(" VRDP server ports: %s" % (oVM.VRDPServer.ports,)); + reporter.log(" Last changed: %s" % (oVM.lastStateChange,)); + + aoControllers = self.oVBoxMgr.getArray(oVM, 'storageControllers') + if aoControllers: + reporter.log(" Controllers:"); + for oCtrl in aoControllers: + reporter.log(" %s %s bus: %s type: %s" % (oCtrl.name, oCtrl.controllerType, oCtrl.bus, oCtrl.controllerType,)); + if self.fpApiVer >= 7.0: + oAdapter = oVM.audioSettings.adapter; + else: + oAdapter = oVM.audioAdapter; + reporter.log(" AudioController: %s" + % (self.oVBoxMgr.getEnumValueName('AudioControllerType', oAdapter.audioController),)); # pylint: disable=not-callable + reporter.log(" AudioEnabled: %s" % (oAdapter.enabled,)); + reporter.log(" Host AudioDriver: %s" + % (self.oVBoxMgr.getEnumValueName('AudioDriverType', oAdapter.audioDriver),)); # pylint: disable=not-callable + + self.processPendingEvents(); + aoAttachments = self.oVBoxMgr.getArray(oVM, 'mediumAttachments') + if aoAttachments: + reporter.log(" Attachments:"); + for oAtt in aoAttachments: + sCtrl = "Controller: %s port: %s device: %s type: %s" % (oAtt.controller, oAtt.port, oAtt.device, oAtt.type); + oMedium = oAtt.medium + if oAtt.type == vboxcon.DeviceType_HardDisk: + reporter.log(" %s: HDD" % sCtrl); + reporter.log(" Id: %s" % (oMedium.id,)); + reporter.log(" Name: %s" % (oMedium.name,)); + reporter.log(" Format: %s" % (oMedium.format,)); + reporter.log(" Location: %s" % (oMedium.location,)); + + if oAtt.type == vboxcon.DeviceType_DVD: + reporter.log(" %s: DVD" % sCtrl); + if oMedium: + reporter.log(" Id: %s" % (oMedium.id,)); + reporter.log(" Name: %s" % (oMedium.name,)); + if oMedium.hostDrive: + reporter.log(" Host DVD %s" % (oMedium.location,)); + if oAtt.passthrough: + reporter.log(" [passthrough mode]"); + else: + reporter.log(" Virtual image: %s" % (oMedium.location,)); + reporter.log(" Size: %s" % (oMedium.size,)); + else: + reporter.log(" empty"); + + if oAtt.type == vboxcon.DeviceType_Floppy: + reporter.log(" %s: Floppy" % sCtrl); + if oMedium: + reporter.log(" Id: %s" % (oMedium.id,)); + reporter.log(" Name: %s" % (oMedium.name,)); + if oMedium.hostDrive: + reporter.log(" Host floppy: %s" % (oMedium.location,)); + else: + reporter.log(" Virtual image: %s" % (oMedium.location,)); + reporter.log(" Size: %s" % (oMedium.size,)); + else: + reporter.log(" empty"); + self.processPendingEvents(); + + reporter.log(" Network Adapter:"); + for iSlot in range(0, 32): + try: oNic = oVM.getNetworkAdapter(iSlot) + except: break; + if not oNic.enabled: + reporter.log2(" slot #%d found but not enabled, skipping" % (iSlot,)); + continue; + reporter.log(" slot #%d: type: %s (%s) MAC Address: %s lineSpeed: %s" + % (iSlot, self.oVBoxMgr.getEnumValueName('NetworkAdapterType', oNic.adapterType), # pylint: disable=not-callable + oNic.adapterType, oNic.MACAddress, oNic.lineSpeed) ); + + if oNic.attachmentType == vboxcon.NetworkAttachmentType_NAT: + reporter.log(" attachmentType: NAT (%s)" % (oNic.attachmentType,)); + if self.fpApiVer >= 4.1: + reporter.log(" nat-network: %s" % (oNic.NATNetwork,)); + if self.fpApiVer >= 7.0 and hasattr(oNic.NATEngine, 'localhostReachable'): + reporter.log(" localhostReachable: %s" % (oNic.NATEngine.localhostReachable,)); + + elif oNic.attachmentType == vboxcon.NetworkAttachmentType_Bridged: + reporter.log(" attachmentType: Bridged (%s)" % (oNic.attachmentType,)); + if self.fpApiVer >= 4.1: + reporter.log(" hostInterface: %s" % (oNic.bridgedInterface,)); + else: + reporter.log(" hostInterface: %s" % (oNic.hostInterface,)); + elif oNic.attachmentType == vboxcon.NetworkAttachmentType_Internal: + reporter.log(" attachmentType: Internal (%s)" % (oNic.attachmentType,)); + reporter.log(" intnet-name: %s" % (oNic.internalNetwork,)); + elif oNic.attachmentType == vboxcon.NetworkAttachmentType_HostOnly: + reporter.log(" attachmentType: HostOnly (%s)" % (oNic.attachmentType,)); + if self.fpApiVer >= 4.1: + reporter.log(" hostInterface: %s" % (oNic.hostOnlyInterface,)); + else: + reporter.log(" hostInterface: %s" % (oNic.hostInterface,)); + else: + if self.fpApiVer >= 7.0: + if oNic.attachmentType == vboxcon.NetworkAttachmentType_HostOnlyNetwork: + reporter.log(" attachmentType: HostOnlyNetwork (%s)" % (oNic.attachmentType,)); + reporter.log(" hostonly-net: %s" % (oNic.hostOnlyNetwork,)); + elif self.fpApiVer >= 4.1: + if oNic.attachmentType == vboxcon.NetworkAttachmentType_Generic: + reporter.log(" attachmentType: Generic (%s)" % (oNic.attachmentType,)); + reporter.log(" generic-driver: %s" % (oNic.GenericDriver,)); + else: + reporter.log(" attachmentType: unknown-%s" % (oNic.attachmentType,)); + else: + reporter.log(" attachmentType: unknown-%s" % (oNic.attachmentType,)); + if oNic.traceEnabled: + reporter.log(" traceFile: %s" % (oNic.traceFile,)); + self.processPendingEvents(); + + reporter.log(" Serial ports:"); + for iSlot in range(0, 8): + try: oPort = oVM.getSerialPort(iSlot) + except: break; + if oPort is not None and oPort.enabled: + enmHostMode = oPort.hostMode; + reporter.log(" slot #%d: hostMode: %s (%s) I/O port: %s IRQ: %s server: %s path: %s" % + (iSlot, self.oVBoxMgr.getEnumValueName('PortMode', enmHostMode), # pylint: disable=not-callable + enmHostMode, oPort.IOBase, oPort.IRQ, oPort.server, oPort.path,) ); + self.processPendingEvents(); + + return True; + + def logVmInfo(self, oVM): # pylint: disable=too-many-statements,too-many-branches + """ + Logs VM configuration details. + + This is copy, past, search, replace and edit of infoCmd from vboxshell.py. + """ + try: + fRc = self._logVmInfoUnsafe(oVM); + except: + reporter.logXcpt(); + fRc = False; + return fRc; + + def logVmInfoByName(self, sName): + """ + logVmInfo + getVmByName. + """ + return self.logVmInfo(self.getVmByName(sName)); + + def tryFindGuestOsId(self, sIdOrDesc): + """ + Takes a guest OS ID or Description and returns the ID. + If nothing matching it is found, the input is returned unmodified. + """ + + if self.fpApiVer >= 4.0: + if sIdOrDesc == 'Solaris (64 bit)': + sIdOrDesc = 'Oracle Solaris 10 5/09 and earlier (64 bit)'; + + try: + aoGuestTypes = self.oVBoxMgr.getArray(self.oVBox, 'GuestOSTypes'); + except: + reporter.logXcpt(); + else: + for oGuestOS in aoGuestTypes: + try: + sId = oGuestOS.id; + sDesc = oGuestOS.description; + except: + reporter.logXcpt(); + else: + if sIdOrDesc in (sId, sDesc,): + sIdOrDesc = sId; + break; + self.processPendingEvents(); + return sIdOrDesc + + def resourceFindVmHd(self, sVmName, sFlavor): + """ + Search the test resources for the most recent VM HD. + + Returns path relative to the test resource root. + """ + ## @todo implement a proper search algo here. + return '4.2/' + sFlavor + '/' + sVmName + '/t-' + sVmName + '.vdi'; + + + # + # VM Api wrappers that logs errors, hides exceptions and other details. + # + + def createTestVMOnly(self, sName, sKind): + """ + Creates and register a test VM without doing any kind of configuration. + + Returns VM object (IMachine) on success, None on failure. + """ + if not self.importVBoxApi(): + return None; + + # create + register the VM + try: + if self.fpApiVer >= 7.0: # Introduces VM encryption (three new parameters, empty for now). + oVM = self.oVBox.createMachine("", sName, [], self.tryFindGuestOsId(sKind), "", "", "", ""); + elif self.fpApiVer >= 4.2: # Introduces grouping (third parameter, empty for now). + oVM = self.oVBox.createMachine("", sName, [], self.tryFindGuestOsId(sKind), ""); + elif self.fpApiVer >= 4.0: + oVM = self.oVBox.createMachine("", sName, self.tryFindGuestOsId(sKind), "", False); + elif self.fpApiVer >= 3.2: + oVM = self.oVBox.createMachine(sName, self.tryFindGuestOsId(sKind), "", "", False); + else: + oVM = self.oVBox.createMachine(sName, self.tryFindGuestOsId(sKind), "", ""); + try: + oVM.saveSettings(); + try: + self.oVBox.registerMachine(oVM); + return oVM; + except: + reporter.logXcpt(); + raise; + except: + reporter.logXcpt(); + if self.fpApiVer >= 4.0: + try: + if self.fpApiVer >= 4.3: + oProgress = oVM.deleteConfig([]); + else: + oProgress = oVM.delete(None); + self.waitOnProgress(oProgress); + except: + reporter.logXcpt(); + else: + try: oVM.deleteSettings(); + except: reporter.logXcpt(); + raise; + except: + reporter.errorXcpt('failed to create vm "%s"' % (sName)); + return None; + + # pylint: disable=too-many-arguments,too-many-locals,too-many-statements,too-many-branches + def createTestVM(self, + sName, + iGroup, + sHd = None, + cMbRam = None, + cCpus = 1, + fVirtEx = None, + fNestedPaging = None, + sDvdImage = None, + sKind = "Other", + fIoApic = None, + fNstHwVirt = None, + fPae = None, + fFastBootLogo = True, + eNic0Type = None, + eNic0AttachType = None, + sNic0NetName = 'default', + sNic0MacAddr = 'grouped', + sFloppy = None, + fNatForwardingForTxs = None, + sHddControllerType = 'IDE Controller', + fVmmDevTestingPart = None, + fVmmDevTestingMmio = False, + sFirmwareType = 'bios', + sChipsetType = 'piix3', + sIommuType = 'none', + sDvdControllerType = 'IDE Controller', + sCom1RawFile = None): + """ + Creates a test VM with a immutable HD from the test resources. + """ + # create + register the VM + oVM = self.createTestVMOnly(sName, sKind); + if not oVM: + return None; + + # Configure the VM. + fRc = True; + oSession = self.openSession(oVM); + if oSession is not None: + fRc = oSession.setupPreferredConfig(); + + if fRc and cMbRam is not None : + fRc = oSession.setRamSize(cMbRam); + if fRc and cCpus is not None: + fRc = oSession.setCpuCount(cCpus); + if fRc and fVirtEx is not None: + fRc = oSession.enableVirtEx(fVirtEx); + if fRc and fNestedPaging is not None: + fRc = oSession.enableNestedPaging(fNestedPaging); + if fRc and fIoApic is not None: + fRc = oSession.enableIoApic(fIoApic); + if fRc and fNstHwVirt is not None: + fRc = oSession.enableNestedHwVirt(fNstHwVirt); + if fRc and fPae is not None: + fRc = oSession.enablePae(fPae); + if fRc and sDvdImage is not None: + fRc = oSession.attachDvd(sDvdImage, sDvdControllerType); + if fRc and sHd is not None: + fRc = oSession.attachHd(sHd, sHddControllerType); + if fRc and sFloppy is not None: + fRc = oSession.attachFloppy(sFloppy); + if fRc and eNic0Type is not None: + fRc = oSession.setNicType(eNic0Type, 0); + if fRc and (eNic0AttachType is not None or (sNic0NetName is not None and sNic0NetName != 'default')): + fRc = oSession.setNicAttachment(eNic0AttachType, sNic0NetName, 0); + if fRc and sNic0MacAddr is not None: + if sNic0MacAddr == 'grouped': + sNic0MacAddr = '%02X' % (iGroup); + fRc = oSession.setNicMacAddress(sNic0MacAddr, 0); + # Needed to reach the host (localhost) from the guest. See xTracker #9896. + if fRc and self.fpApiVer >= 7.0: + fRc = oSession.setNicLocalhostReachable(True, 0); + if fRc and fNatForwardingForTxs is True: + fRc = oSession.setupNatForwardingForTxs(); + if fRc and fFastBootLogo is not None: + fRc = oSession.setupBootLogo(fFastBootLogo); + if fRc and self.fEnableVrdp: + fRc = oSession.setupVrdp(True, self.uVrdpBasePort + iGroup); + if fRc and fVmmDevTestingPart is not None: + fRc = oSession.enableVmmDevTestingPart(fVmmDevTestingPart, fVmmDevTestingMmio); + if fRc and sFirmwareType == 'bios': + fRc = oSession.setFirmwareType(vboxcon.FirmwareType_BIOS); + elif fRc and sFirmwareType == 'efi': + fRc = oSession.setFirmwareType(vboxcon.FirmwareType_EFI); + if fRc and self.fEnableDebugger: + fRc = oSession.setExtraData('VBoxInternal/DBGC/Enabled', '1'); + if fRc and self.fRecordingEnabled: + try: + if self.fpApiVer >= 6.1: # Only for VBox 6.1 and up now. + reporter.log('Recording enabled'); + if self.cSecsRecordingMax > 0: + reporter.log('Recording time limit is set to %d seconds' % (self.cSecsRecordingMax)); + if self.cMbRecordingMax > 0: + reporter.log('Recording file limit is set to %d MB' % (self.cMbRecordingMax)); + oRecSettings = oSession.o.machine.recordingSettings; + oRecSettings.enabled = True; + aoScreens = self.oVBoxMgr.getArray(oRecSettings, 'screens'); + for oScreen in aoScreens: + try: + oScreen.enabled = True; + sRecFile = os.path.join(self.sScratchPath, "recording-%s.webm" % (sName)); + oScreen.filename = sRecFile; + sRecFile = oScreen.filename; # Get back the file from Main, in case it was modified somehow. + dRecFile = { 'id' : oScreen.id, 'file' : sRecFile }; + self.adRecordingFiles.append(dRecFile); + if self.fpApiVer >= 7.0: + aFeatures = [ vboxcon.RecordingFeature_Video, ]; + if self.fRecordingAudio: + aFeatures.append(vboxcon.RecordingFeature_Audio); + try: + oScreen.setFeatures(aFeatures); + except: ## @todo Figure out why this is needed on Windows. + oScreen.features = aFeatures; + else: # <= VBox 6.1 the feature were kept as a ULONG. + uFeatures = vboxcon.RecordingFeature_Video; + if self.fRecordingAudio: + uFeatures = uFeatures | vboxcon.RecordingFeature_Audio; + oScreen.features = uFeatures; + reporter.log2('Recording screen %d to "%s"' % (dRecFile['id'], dRecFile['file'],)); + oScreen.maxTime = self.cSecsRecordingMax; + oScreen.maxFileSize = self.cMbRecordingMax; + except: + reporter.errorXcpt('failed to configure recording for "%s" (screen %d)' % (sName, oScreen.id)); + else: + # Not fatal. + reporter.log('Recording only available for VBox >= 6.1, sorry!') + except: + reporter.errorXcpt('failed to configure recording for "%s"' % (sName)); + if fRc and sChipsetType == 'piix3': + fRc = oSession.setChipsetType(vboxcon.ChipsetType_PIIX3); + elif fRc and sChipsetType == 'ich9': + fRc = oSession.setChipsetType(vboxcon.ChipsetType_ICH9); + if fRc and sCom1RawFile: + fRc = oSession.setupSerialToRawFile(0, sCom1RawFile); + if fRc and self.fpApiVer >= 6.2 and hasattr(vboxcon, 'IommuType_AMD') and sIommuType == 'amd': + fRc = oSession.setIommuType(vboxcon.IommuType_AMD); + elif fRc and self.fpApiVer >= 6.2 and hasattr(vboxcon, 'IommuType_Intel') and sIommuType == 'intel': + fRc = oSession.setIommuType(vboxcon.IommuType_Intel); + + if fRc: fRc = oSession.saveSettings(); + if not fRc: oSession.discardSettings(True); + oSession.close(); + if not fRc: + if self.fpApiVer >= 4.0: + try: oVM.unregister(vboxcon.CleanupMode_Full); + except: reporter.logXcpt(); + try: + if self.fpApiVer >= 4.3: + oProgress = oVM.deleteConfig([]); + else: + oProgress = oVM.delete([]); + self.waitOnProgress(oProgress); + except: + reporter.logXcpt(); + else: + try: self.oVBox.unregisterMachine(oVM.id); + except: reporter.logXcpt(); + try: oVM.deleteSettings(); + except: reporter.logXcpt(); + return None; + + # success. + reporter.log('created "%s" with name "%s"' % (oVM.id, sName)); + self.aoVMs.append(oVM); + self.logVmInfo(oVM); # testing... + return oVM; + # pylint: enable=too-many-arguments,too-many-locals,too-many-statements + + def createTestVmWithDefaults(self, # pylint: disable=too-many-arguments + sName, + iGroup, + sKind, + sDvdImage = None, + fFastBootLogo = True, + eNic0AttachType = None, + sNic0NetName = 'default', + sNic0MacAddr = 'grouped', + fVmmDevTestingPart = None, + fVmmDevTestingMmio = False, + sCom1RawFile = None): + """ + Creates a test VM with all defaults and no HDs. + """ + # create + register the VM + oVM = self.createTestVMOnly(sName, sKind); + if oVM is not None: + # Configure the VM with defaults according to sKind. + fRc = True; + oSession = self.openSession(oVM); + if oSession is not None: + if self.fpApiVer >= 6.0: + try: + oSession.o.machine.applyDefaults(''); + except: + reporter.errorXcpt('failed to apply defaults to vm "%s"' % (sName,)); + fRc = False; + else: + reporter.error("Implement applyDefaults for vbox version %s" % (self.fpApiVer,)); + #fRc = oSession.setupPreferredConfig(); + fRc = False; + + # Apply the specified configuration: + if fRc and sDvdImage is not None: + #fRc = oSession.insertDvd(sDvdImage); # attachDvd + reporter.error('Implement: oSession.insertDvd(%s)' % (sDvdImage,)); + fRc = False; + + if fRc and fFastBootLogo is not None: + fRc = oSession.setupBootLogo(fFastBootLogo); + + if fRc and (eNic0AttachType is not None or (sNic0NetName is not None and sNic0NetName != 'default')): + fRc = oSession.setNicAttachment(eNic0AttachType, sNic0NetName, 0); + if fRc and sNic0MacAddr is not None: + if sNic0MacAddr == 'grouped': + sNic0MacAddr = '%02X' % (iGroup,); + fRc = oSession.setNicMacAddress(sNic0MacAddr, 0); + # Needed to reach the host (localhost) from the guest. See xTracker #9896. + if fRc and self.fpApiVer >= 7.0: + fRc = oSession.setNicLocalhostReachable(True, 0); + + if fRc and self.fEnableVrdp: + fRc = oSession.setupVrdp(True, self.uVrdpBasePort + iGroup); + + if fRc and fVmmDevTestingPart is not None: + fRc = oSession.enableVmmDevTestingPart(fVmmDevTestingPart, fVmmDevTestingMmio); + + if fRc and sCom1RawFile: + fRc = oSession.setupSerialToRawFile(0, sCom1RawFile); + + # Save the settings if we were successfull, otherwise discard them. + if fRc: + fRc = oSession.saveSettings(); + if not fRc: + oSession.discardSettings(True); + oSession.close(); + + if fRc is True: + # If we've been successful, add the VM to the list and return it. + # success. + reporter.log('created "%s" with name "%s"' % (oVM.id, sName, )); + self.aoVMs.append(oVM); + self.logVmInfo(oVM); # testing... + return oVM; + + # Failed. Unregister the machine and delete it. + if self.fpApiVer >= 4.0: + try: oVM.unregister(vboxcon.CleanupMode_Full); + except: reporter.logXcpt(); + try: + if self.fpApiVer >= 4.3: + oProgress = oVM.deleteConfig([]); + else: + oProgress = oVM.delete([]); + self.waitOnProgress(oProgress); + except: + reporter.logXcpt(); + else: + try: self.oVBox.unregisterMachine(oVM.id); + except: reporter.logXcpt(); + try: oVM.deleteSettings(); + except: reporter.logXcpt(); + return None; + + def addTestMachine(self, sNameOrId, fQuiet = False): + """ + Adds an already existing (that is, configured) test VM to the + test VM list. + + Returns the VM object on success, None if failed. + """ + # find + add the VM to the list. + oVM = None; + try: + if self.fpApiVer >= 4.0: + oVM = self.oVBox.findMachine(sNameOrId); + else: + reporter.error('fpApiVer=%s - did you remember to initialize the API' % (self.fpApiVer,)); + except: + reporter.errorXcpt('could not find vm "%s"' % (sNameOrId,)); + + if oVM: + self.aoVMs.append(oVM); + if not fQuiet: + reporter.log('Added "%s" with name "%s"' % (oVM.id, sNameOrId)); + self.logVmInfo(oVM); + return oVM; + + def forgetTestMachine(self, oVM, fQuiet = False): + """ + Forget about an already known test VM in the test VM list. + + Returns True on success, False if failed. + """ + try: + sUuid = oVM.id; + sName = oVM.name; + except: + reporter.errorXcpt('failed to get the UUID for VM "%s"' % (oVM,)); + return False; + try: + self.aoVMs.remove(oVM); + if not fQuiet: + reporter.log('Removed "%s" with name "%s"' % (sUuid, sName)); + except: + reporter.errorXcpt('could not find vm "%s"' % (sName,)); + return False; + return True; + + def openSession(self, oVM): + """ + Opens a session for the VM. Returns the a Session wrapper object that + will automatically close the session when the wrapper goes out of scope. + + On failure None is returned and an error is logged. + """ + try: + sUuid = oVM.id; + except: + reporter.errorXcpt('failed to get the UUID for VM "%s"' % (oVM,)); + return None; + + # This loop is a kludge to deal with us racing the closing of the + # direct session of a previous VM run. See waitOnDirectSessionClose. + for i in range(10): + try: + if self.fpApiVer <= 3.2: + oSession = self.oVBoxMgr.openMachineSession(sUuid); + else: + oSession = self.oVBoxMgr.openMachineSession(oVM); + break; + except: + if i == 9: + reporter.errorXcpt('failed to open session for "%s" ("%s")' % (sUuid, oVM)); + return None; + if i > 0: + reporter.logXcpt('warning: failed to open session for "%s" ("%s") - retrying in %u secs' % (sUuid, oVM, i)); + self.waitOnDirectSessionClose(oVM, 5000 + i * 1000); + from testdriver.vboxwrappers import SessionWrapper; + return SessionWrapper(oSession, oVM, self.oVBox, self.oVBoxMgr, self, False); + + # + # Guest locations. + # + + @staticmethod + def getGuestTempDir(oTestVm): + """ + Helper for finding a temporary directory in the test VM. + + Note! It may be necessary to create it! + """ + if oTestVm.isWindows(): + return "C:\\Temp"; + if oTestVm.isOS2(): + return "C:\\Temp"; + return '/var/tmp'; + + @staticmethod + def getGuestSystemDir(oTestVm, sPathPrefix = ''): + """ + Helper for finding a system directory in the test VM that we can play around with. + sPathPrefix can be used to specify other directories, such as /usr/local/bin/ or /usr/bin, for instance. + + On Windows this is always the System32 directory, so this function can be used as + basis for locating other files in or under that directory. + """ + if oTestVm.isWindows(): + return oTestVm.pathJoin(TestDriver.getGuestWinDir(oTestVm), 'System32'); + if oTestVm.isOS2(): + return 'C:\\OS2\\DLL'; + + # OL / RHEL symlinks "/bin"/ to "/usr/bin". To avoid (unexpectedly) following symlinks, use "/usr/bin" then instead. + if not sPathPrefix \ + and oTestVm.sKind in ('Oracle_64', 'Oracle'): ## @todo Does this apply for "RedHat" as well? + return "/usr/bin"; + + return sPathPrefix + "/bin"; + + @staticmethod + def getGuestSystemAdminDir(oTestVm, sPathPrefix = ''): + """ + Helper for finding a system admin directory ("sbin") in the test VM that we can play around with. + sPathPrefix can be used to specify other directories, such as /usr/local/sbin/ or /usr/sbin, for instance. + + On Windows this is always the System32 directory, so this function can be used as + basis for locating other files in or under that directory. + On UNIX-y systems this always is the "sh" shell to guarantee a common shell syntax. + """ + if oTestVm.isWindows(): + return oTestVm.pathJoin(TestDriver.getGuestWinDir(oTestVm), 'System32'); + if oTestVm.isOS2(): + return 'C:\\OS2\\DLL'; ## @todo r=andy Not sure here. + + # OL / RHEL symlinks "/sbin"/ to "/usr/sbin". To avoid (unexpectedly) following symlinks, use "/usr/sbin" then instead. + if not sPathPrefix \ + and oTestVm.sKind in ('Oracle_64', 'Oracle'): ## @todo Does this apply for "RedHat" as well? + return "/usr/sbin"; + + return sPathPrefix + "/sbin"; + + @staticmethod + def getGuestWinDir(oTestVm): + """ + Helper for finding the Windows directory in the test VM that we can play around with. + ASSUMES that we always install Windows on drive C. + + Returns the Windows directory, or an empty string when executed on a non-Windows guest (asserts). + """ + sWinDir = ''; + if oTestVm.isWindows(): + if oTestVm.sKind in ['WindowsNT4', 'WindowsNT3x',]: + sWinDir = 'C:\\WinNT\\'; + else: + sWinDir = 'C:\\Windows\\'; + assert sWinDir != '', 'Retrieving Windows directory for non-Windows OS'; + return sWinDir; + + @staticmethod + def getGuestSystemShell(oTestVm): + """ + Helper for finding the default system shell in the test VM. + """ + if oTestVm.isWindows(): + return TestDriver.getGuestSystemDir(oTestVm) + '\\cmd.exe'; + if oTestVm.isOS2(): + return TestDriver.getGuestSystemDir(oTestVm) + '\\..\\CMD.EXE'; + return "/bin/sh"; + + @staticmethod + def getGuestSystemFileForReading(oTestVm): + """ + Helper for finding a file in the test VM that we can read. + """ + if oTestVm.isWindows(): + return TestDriver.getGuestSystemDir(oTestVm) + '\\ntdll.dll'; + if oTestVm.isOS2(): + return TestDriver.getGuestSystemDir(oTestVm) + '\\DOSCALL1.DLL'; + return "/bin/sh"; + + def getVmByName(self, sName): + """ + Get a test VM by name. Returns None if not found, logged. + """ + # Look it up in our 'cache'. + for oVM in self.aoVMs: + try: + #reporter.log2('cur: %s / %s (oVM=%s)' % (oVM.name, oVM.id, oVM)); + if oVM.name == sName: + return oVM; + except: + reporter.errorXcpt('failed to get the name from the VM "%s"' % (oVM)); + + # Look it up the standard way. + return self.addTestMachine(sName, fQuiet = True); + + def getVmByUuid(self, sUuid): + """ + Get a test VM by uuid. Returns None if not found, logged. + """ + # Look it up in our 'cache'. + for oVM in self.aoVMs: + try: + if oVM.id == sUuid: + return oVM; + except: + reporter.errorXcpt('failed to get the UUID from the VM "%s"' % (oVM)); + + # Look it up the standard way. + return self.addTestMachine(sUuid, fQuiet = True); + + def waitOnProgress(self, oProgress, cMsTimeout = 1000000, fErrorOnTimeout = True, cMsInterval = 1000): + """ + Waits for a progress object to complete. Returns the status code. + """ + # Wait for progress no longer than cMsTimeout time period. + tsStart = datetime.datetime.now() + while True: + self.processPendingEvents(); + try: + if oProgress.completed: + break; + except: + return -1; + self.processPendingEvents(); + + tsNow = datetime.datetime.now() + tsDelta = tsNow - tsStart + if ((tsDelta.microseconds + tsDelta.seconds * 1000000) // 1000) > cMsTimeout: + if fErrorOnTimeout: + reporter.errorTimeout('Timeout while waiting for progress.') + return -1 + + reporter.doPollWork('vbox.TestDriver.waitOnProgress'); + try: oProgress.waitForCompletion(cMsInterval); + except: return -2; + + try: rc = oProgress.resultCode; + except: rc = -2; + self.processPendingEvents(); + return rc; + + def waitOnDirectSessionClose(self, oVM, cMsTimeout): + """ + Waits for the VM process to close it's current direct session. + + Returns None. + """ + # Get the original values so we're not subject to + try: + eCurState = oVM.sessionState; + if self.fpApiVer >= 5.0: + sCurName = sOrgName = oVM.sessionName; + else: + sCurName = sOrgName = oVM.sessionType; + if self.fpApiVer >= 4.2: + iCurPid = iOrgPid = oVM.sessionPID; + else: + iCurPid = iOrgPid = oVM.sessionPid; + except Exception as oXcpt: + if ComError.notEqual(oXcpt, ComError.E_ACCESSDENIED): + reporter.logXcpt(); + self.processPendingEvents(); + return None; + self.processPendingEvents(); + + msStart = base.timestampMilli(); + while iCurPid == iOrgPid \ + and sCurName == sOrgName \ + and sCurName != '' \ + and base.timestampMilli() - msStart < cMsTimeout \ + and eCurState in (vboxcon.SessionState_Unlocking, vboxcon.SessionState_Spawning, vboxcon.SessionState_Locked,): + self.processEvents(1000); + try: + eCurState = oVM.sessionState; + sCurName = oVM.sessionName if self.fpApiVer >= 5.0 else oVM.sessionType; + iCurPid = oVM.sessionPID if self.fpApiVer >= 4.2 else oVM.sessionPid; + except Exception as oXcpt: + if ComError.notEqual(oXcpt, ComError.E_ACCESSDENIED): + reporter.logXcpt(); + break; + self.processPendingEvents(); + self.processPendingEvents(); + return None; + + def uploadStartupLogFile(self, oVM, sVmName): + """ + Uploads the VBoxStartup.log when present. + """ + fRc = True; + try: + sLogFile = os.path.join(oVM.logFolder, 'VBoxHardening.log'); + except: + reporter.logXcpt(); + fRc = False; + else: + if os.path.isfile(sLogFile): + reporter.addLogFile(sLogFile, 'log/release/vm', '%s hardening log' % (sVmName, ), + sAltName = '%s-%s' % (sVmName, os.path.basename(sLogFile),)); + return fRc; + + def annotateAndUploadProcessReport(self, sProcessReport, sFilename, sKind, sDesc): + """ + Annotates the given VM process report and uploads it if successfull. + """ + fRc = False; + if self.oBuild is not None and self.oBuild.sInstallPath is not None: + oResolver = btresolver.BacktraceResolver(self.sScratchPath, self.oBuild.sInstallPath, + self.getBuildOs(), self.getBuildArch(), + fnLog = reporter.log); + fRcTmp = oResolver.prepareEnv(); + if fRcTmp: + reporter.log('Successfully prepared environment'); + sReportDbgSym = oResolver.annotateReport(sProcessReport); + if sReportDbgSym and len(sReportDbgSym) > 8: + reporter.addLogString(sReportDbgSym, sFilename, sKind, sDesc); + fRc = True; + else: + reporter.log('Annotating report failed'); + oResolver.cleanupEnv(); + return fRc; + + def startVmEx(self, oVM, fWait = True, sType = None, sName = None, asEnv = None): # pylint: disable=too-many-locals,too-many-statements + """ + Start the VM, returning the VM session and progress object on success. + The session is also added to the task list and to the aoRemoteSessions set. + + asEnv is a list of string on the putenv() form. + + On failure (None, None) is returned and an error is logged. + """ + # Massage and check the input. + if sType is None: + sType = self.sSessionType; + if sName is None: + try: sName = oVM.name; + except: sName = 'bad-vm-handle'; + reporter.log('startVmEx: sName=%s fWait=%s sType=%s' % (sName, fWait, sType)); + if oVM is None: + return (None, None); + + ## @todo Do this elsewhere. + # Hack alert. Disables all annoying GUI popups. + if sType == 'gui' and not self.aoRemoteSessions: + try: + self.oVBox.setExtraData('GUI/Input/AutoCapture', 'false'); + if self.fpApiVer >= 3.2: + self.oVBox.setExtraData('GUI/LicenseAgreed', '8'); + else: + self.oVBox.setExtraData('GUI/LicenseAgreed', '7'); + self.oVBox.setExtraData('GUI/RegistrationData', 'triesLeft=0'); + self.oVBox.setExtraData('GUI/SUNOnlineData', 'triesLeft=0'); + self.oVBox.setExtraData('GUI/SuppressMessages', 'confirmVMReset,remindAboutMouseIntegrationOn,' + 'remindAboutMouseIntegrationOff,remindAboutPausedVMInput,confirmInputCapture,' + 'confirmGoingFullscreen,remindAboutInaccessibleMedia,remindAboutWrongColorDepth,' + 'confirmRemoveMedium,allPopupPanes,allMessageBoxes,all'); + self.oVBox.setExtraData('GUI/UpdateDate', 'never'); + self.oVBox.setExtraData('GUI/PreventBetaWarning', self.oVBox.version); + except: + reporter.logXcpt(); + + # The UUID for the name. + try: + sUuid = oVM.id; + except: + reporter.errorXcpt('failed to get the UUID for VM "%s"' % (oVM)); + return (None, None); + self.processPendingEvents(); + + # Construct the environment. + self.sSessionLogFile = '%s/VM-%s.log' % (self.sScratchPath, sUuid); + try: os.remove(self.sSessionLogFile); + except: pass; + if self.sLogSessionDest: + sLogDest = self.sLogSessionDest; + else: + sLogDest = 'file=%s' % (self.sSessionLogFile,); + asEnvFinal = [ + 'VBOX_LOG=%s' % (self.sLogSessionGroups,), + 'VBOX_LOG_FLAGS=%s' % (self.sLogSessionFlags,), + 'VBOX_LOG_DEST=nodeny %s' % (sLogDest,), + 'VBOX_RELEASE_LOG_FLAGS=append time', + ]; + if sType == 'gui': + asEnvFinal.append('VBOX_GUI_DBG_ENABLED=1'); + if asEnv is not None and asEnv: + asEnvFinal += asEnv; + + reporter.log2('Session environment:\n%s' % (asEnvFinal,)); + + # Shortcuts for local testing. + oProgress = oWrapped = None; + oTestVM = self.oTestVmSet.findTestVmByName(sName) if self.oTestVmSet is not None else None; + try: + if oTestVM is not None \ + and oTestVM.fSnapshotRestoreCurrent is True: + if oVM.state is vboxcon.MachineState_Running: + reporter.log2('Machine "%s" already running.' % (sName,)); + oProgress = None; + oWrapped = self.openSession(oVM); + else: + reporter.log2('Checking if snapshot for machine "%s" exists.' % (sName,)); + oSessionWrapperRestore = self.openSession(oVM); + if oSessionWrapperRestore is not None: + oSnapshotCur = oVM.currentSnapshot; + if oSnapshotCur is not None: + reporter.log2('Restoring snapshot for machine "%s".' % (sName,)); + oSessionWrapperRestore.restoreSnapshot(oSnapshotCur); + reporter.log2('Current snapshot for machine "%s" restored.' % (sName,)); + else: + reporter.log('warning: no current snapshot for machine "%s" found.' % (sName,)); + oSessionWrapperRestore.close(); + except: + reporter.errorXcpt(); + return (None, None); + + oSession = None; # Must be initialized, otherwise the log statement at the end of the function can fail. + + # Open a remote session, wait for this operation to complete. + # (The loop is a kludge to deal with us racing the closing of the + # direct session of a previous VM run. See waitOnDirectSessionClose.) + if oWrapped is None: + for i in range(10): + try: + if self.fpApiVer < 4.3 \ + or (self.fpApiVer == 4.3 and not hasattr(self.oVBoxMgr, 'getSessionObject')): + oSession = self.oVBoxMgr.mgr.getSessionObject(self.oVBox); # pylint: disable=no-member + elif self.fpApiVer < 5.2 \ + or (self.fpApiVer == 5.2 and hasattr(self.oVBoxMgr, 'vbox')): + oSession = self.oVBoxMgr.getSessionObject(self.oVBox); # pylint: disable=no-member + else: + oSession = self.oVBoxMgr.getSessionObject(); # pylint: disable=no-member,no-value-for-parameter + if self.fpApiVer < 3.3: + oProgress = self.oVBox.openRemoteSession(oSession, sUuid, sType, '\n'.join(asEnvFinal)); + else: + if self.uApiRevision >= self.makeApiRevision(6, 1, 0, 1): + oProgress = oVM.launchVMProcess(oSession, sType, asEnvFinal); + else: + oProgress = oVM.launchVMProcess(oSession, sType, '\n'.join(asEnvFinal)); + break; + except: + if i == 9: + reporter.errorXcpt('failed to start VM "%s" ("%s"), aborting.' % (sUuid, sName)); + return (None, None); + oSession = None; + if i >= 0: + reporter.logXcpt('warning: failed to start VM "%s" ("%s") - retrying in %u secs.' % (sUuid, oVM, i)); # pylint: disable=line-too-long + self.waitOnDirectSessionClose(oVM, 5000 + i * 1000); + if fWait and oProgress is not None: + rc = self.waitOnProgress(oProgress); + if rc < 0: + self.waitOnDirectSessionClose(oVM, 5000); + + # VM failed to power up, still collect VBox.log, need to wrap the session object + # in order to use the helper for adding the log files to the report. + from testdriver.vboxwrappers import SessionWrapper; + oTmp = SessionWrapper(oSession, oVM, self.oVBox, self.oVBoxMgr, self, True, sName, self.sSessionLogFile); + oTmp.addLogsToReport(); + + # Try to collect a stack trace of the process for further investigation of any startup hangs. + uPid = oTmp.getPid(); + if uPid is not None: + sHostProcessInfoHung = utils.processGetInfo(uPid, fSudo = True); + if sHostProcessInfoHung is not None: + reporter.log('Trying to annotate the hung VM startup process report, please stand by...'); + fRcTmp = self.annotateAndUploadProcessReport(sHostProcessInfoHung, 'vmprocess-startup-hung.log', + 'process/report/vm', 'Annotated hung VM process state during startup'); # pylint: disable=line-too-long + # Upload the raw log for manual annotation in case resolving failed. + if not fRcTmp: + reporter.log('Failed to annotate hung VM process report, uploading raw report'); + reporter.addLogString(sHostProcessInfoHung, 'vmprocess-startup-hung.log', 'process/report/vm', + 'Hung VM process state during startup'); + + try: + if oSession is not None: + oSession.close(); + except: pass; + reportError(oProgress, 'failed to open session for "%s"' % (sName)); + self.uploadStartupLogFile(oVM, sName); + return (None, None); + reporter.log2('waitOnProgress -> %s' % (rc,)); + + # Wrap up the session object and push on to the list before returning it. + if oWrapped is None: + from testdriver.vboxwrappers import SessionWrapper; + oWrapped = SessionWrapper(oSession, oVM, self.oVBox, self.oVBoxMgr, self, True, sName, self.sSessionLogFile); + + oWrapped.registerEventHandlerForTask(); + self.aoRemoteSessions.append(oWrapped); + if oWrapped is not self.aoRemoteSessions[len(self.aoRemoteSessions) - 1]: + reporter.error('not by reference: oWrapped=%s aoRemoteSessions[%s]=%s' + % (oWrapped, len(self.aoRemoteSessions) - 1, + self.aoRemoteSessions[len(self.aoRemoteSessions) - 1])); + self.addTask(oWrapped); + + reporter.log2('startVmEx: oSession=%s, oSessionWrapper=%s, oProgress=%s' % (oSession, oWrapped, oProgress)); + + from testdriver.vboxwrappers import ProgressWrapper; + return (oWrapped, ProgressWrapper(oProgress, self.oVBoxMgr, self, + 'starting %s' % (sName,)) if oProgress else None); + + def startVm(self, oVM, sType=None, sName = None, asEnv = None): + """ Simplified version of startVmEx. """ + oSession, _ = self.startVmEx(oVM, True, sType, sName, asEnv = asEnv); + return oSession; + + def startVmByNameEx(self, sName, fWait=True, sType=None, asEnv = None): + """ + Start the VM, returning the VM session and progress object on success. + The session is also added to the task list and to the aoRemoteSessions set. + + On failure (None, None) is returned and an error is logged. + """ + oVM = self.getVmByName(sName); + if oVM is None: + return (None, None); + return self.startVmEx(oVM, fWait, sType, sName, asEnv = asEnv); + + def startVmByName(self, sName, sType=None, asEnv = None): + """ + Start the VM, returning the VM session on success. The session is + also added to the task list and to the aoRemoteSessions set. + + On failure None is returned and an error is logged. + """ + oSession, _ = self.startVmByNameEx(sName, True, sType, asEnv = asEnv); + return oSession; + + def terminateVmBySession(self, oSession, oProgress = None, fTakeScreenshot = None): # pylint: disable=too-many-statements + """ + Terminates the VM specified by oSession and adds the release logs to + the test report. + + This will try achieve this by using powerOff, but will resort to + tougher methods if that fails. + + The session will always be removed from the task list. + The session will be closed unless we fail to kill the process. + The session will be removed from the remote session list if closed. + + The progress object (a wrapper!) is for teleportation and similar VM + operations, it will be attempted canceled before powering off the VM. + Failures are logged but ignored. + The progress object will always be removed from the task list. + + Returns True if powerOff and session close both succeed. + Returns False if on failure (logged), including when we successfully + kill the VM process. + """ + reporter.log2('terminateVmBySession: oSession=%s (pid=%s) oProgress=%s' % (oSession.sName, oSession.getPid(), oProgress)); + + # Call getPid first to make sure the PID is cached in the wrapper. + oSession.getPid(); + + # + # If the host is out of memory, just skip all the info collection as it + # requires memory too and seems to wedge. + # + sHostProcessInfo = None; + sHostProcessInfoHung = None; + sLastScreenshotPath = None; + sOsKernelLog = None; + sVgaText = None; + asMiscInfos = []; + + if not oSession.fHostMemoryLow: + # Try to fetch the VM process info before meddling with its state. + if self.fAlwaysUploadLogs or reporter.testErrorCount() > 0: + sHostProcessInfo = utils.processGetInfo(oSession.getPid(), fSudo = True); + + # + # Pause the VM if we're going to take any screenshots or dig into the + # guest. Failures are quitely ignored. + # + if self.fAlwaysUploadLogs or reporter.testErrorCount() > 0: + try: + if oSession.oVM.state in [ vboxcon.MachineState_Running, + vboxcon.MachineState_LiveSnapshotting, + vboxcon.MachineState_Teleporting ]: + oSession.o.console.pause(); + except: + reporter.logXcpt(); + + # + # Take Screenshot and upload it (see below) to Test Manager if appropriate/requested. + # + if fTakeScreenshot is True or self.fAlwaysUploadScreenshots or reporter.testErrorCount() > 0: + sLastScreenshotPath = os.path.join(self.sScratchPath, "LastScreenshot-%s.png" % oSession.sName); + fRc = oSession.takeScreenshot(sLastScreenshotPath); + if fRc is not True: + sLastScreenshotPath = None; + + # Query the OS kernel log from the debugger if appropriate/requested. + if self.fAlwaysUploadLogs or reporter.testErrorCount() > 0: + sOsKernelLog = oSession.queryOsKernelLog(); + + # Do "info vgatext all" separately. + if self.fAlwaysUploadLogs or reporter.testErrorCount() > 0: + sVgaText = oSession.queryDbgInfoVgaText(); + + # Various infos (do after kernel because of symbols). + if self.fAlwaysUploadLogs or reporter.testErrorCount() > 0: + # Dump the guest stack for all CPUs. + cCpus = oSession.getCpuCount(); + if cCpus > 0: + for iCpu in xrange(0, cCpus): + sThis = oSession.queryDbgGuestStack(iCpu); + if sThis: + asMiscInfos += [ + '================ start guest stack VCPU %s ================\n' % (iCpu,), + sThis, + '================ end guest stack VCPU %s ==================\n' % (iCpu,), + ]; + + for sInfo, sArg in [ ('mode', 'all'), + ('fflags', ''), + ('cpumguest', 'verbose all'), + ('cpumguestinstr', 'symbol all'), + ('exits', ''), + ('pic', ''), + ('apic', ''), + ('apiclvt', ''), + ('apictimer', ''), + ('ioapic', ''), + ('pit', ''), + ('phys', ''), + ('clocks', ''), + ('timers', ''), + ('gdt', ''), + ('ldt', ''), + ]: + if sInfo in ['apic',] and self.fpApiVer < 5.1: # asserts and burns + continue; + sThis = oSession.queryDbgInfo(sInfo, sArg); + if sThis: + if sThis[-1] != '\n': + sThis += '\n'; + asMiscInfos += [ + '================ start %s %s ================\n' % (sInfo, sArg), + sThis, + '================ end %s %s ==================\n' % (sInfo, sArg), + ]; + + # + # Terminate the VM + # + + # Cancel the progress object if specified. + if oProgress is not None: + if not oProgress.isCompleted() and oProgress.isCancelable(): + reporter.log2('terminateVmBySession: canceling "%s"...' % (oProgress.sName)); + try: + oProgress.o.cancel(); + except: + reporter.logXcpt(); + else: + oProgress.wait(); + self.removeTask(oProgress); + + # Check if the VM has terminated by itself before powering it off. + fClose = True; + fRc = True; + if oSession.needsPoweringOff(): + reporter.log('terminateVmBySession: powering off "%s"...' % (oSession.sName,)); + fRc = oSession.powerOff(fFudgeOnFailure = False); + if fRc is not True: + # power off failed, try terminate it in a nice manner. + fRc = False; + uPid = oSession.getPid(); + if uPid is not None: + # + # Collect some information about the VM process first to have + # some state information for further investigation why powering off failed. + # + sHostProcessInfoHung = utils.processGetInfo(uPid, fSudo = True); + + # Exterminate... + reporter.error('terminateVmBySession: Terminating PID %u (VM %s)' % (uPid, oSession.sName)); + fClose = base.processTerminate(uPid); + if fClose is True: + self.waitOnDirectSessionClose(oSession.oVM, 5000); + fClose = oSession.waitForTask(1000); + + if fClose is not True: + # Being nice failed... + reporter.error('terminateVmBySession: Termination failed, trying to kill PID %u (VM %s) instead' \ + % (uPid, oSession.sName)); + fClose = base.processKill(uPid); + if fClose is True: + self.waitOnDirectSessionClose(oSession.oVM, 5000); + fClose = oSession.waitForTask(1000); + if fClose is not True: + reporter.error('terminateVmBySession: Failed to kill PID %u (VM %s)' % (uPid, oSession.sName)); + + # The final steps. + if fClose is True: + reporter.log('terminateVmBySession: closing session "%s"...' % (oSession.sName,)); + oSession.close(); + self.waitOnDirectSessionClose(oSession.oVM, 10000); + try: + eState = oSession.oVM.state; + except: + reporter.logXcpt(); + else: + if eState == vboxcon.MachineState_Aborted: + reporter.error('terminateVmBySession: The VM "%s" aborted!' % (oSession.sName,)); + self.removeTask(oSession); + + # + # Add the release log, debug log and a screenshot of the VM to the test report. + # + if self.fAlwaysUploadLogs or reporter.testErrorCount() > 0: + oSession.addLogsToReport(); + + # Add a screenshot if it has been requested and taken successfully. + if sLastScreenshotPath is not None: + if reporter.testErrorCount() > 0: + reporter.addLogFile(sLastScreenshotPath, 'screenshot/failure', 'Last VM screenshot'); + else: + reporter.addLogFile(sLastScreenshotPath, 'screenshot/success', 'Last VM screenshot'); + + # Add the guest OS log if it has been requested and taken successfully. + if sOsKernelLog is not None: + reporter.addLogString(sOsKernelLog, 'kernel.log', 'log/guest/kernel', 'Guest OS kernel log'); + + # Add "info vgatext all" if we've got it. + if sVgaText is not None: + reporter.addLogString(sVgaText, 'vgatext.txt', 'info/vgatext', 'info vgatext all'); + + # Add the "info xxxx" items if we've got any. + if asMiscInfos: + reporter.addLogString(u''.join(asMiscInfos), 'info.txt', 'info/collection', 'A bunch of info items.'); + + # Add the host process info if we were able to retrieve it. + if sHostProcessInfo is not None: + reporter.log('Trying to annotate the VM process report, please stand by...'); + fRcTmp = self.annotateAndUploadProcessReport(sHostProcessInfo, 'vmprocess.log', + 'process/report/vm', 'Annotated VM process state'); + # Upload the raw log for manual annotation in case resolving failed. + if not fRcTmp: + reporter.log('Failed to annotate VM process report, uploading raw report'); + reporter.addLogString(sHostProcessInfo, 'vmprocess.log', 'process/report/vm', 'VM process state'); + + # Add the host process info for failed power off attempts if we were able to retrieve it. + if sHostProcessInfoHung is not None: + reporter.log('Trying to annotate the hung VM process report, please stand by...'); + fRcTmp = self.annotateAndUploadProcessReport(sHostProcessInfoHung, 'vmprocess-hung.log', + 'process/report/vm', 'Annotated hung VM process state'); + # Upload the raw log for manual annotation in case resolving failed. + if not fRcTmp: + reporter.log('Failed to annotate hung VM process report, uploading raw report'); + fRcTmp = reporter.addLogString(sHostProcessInfoHung, 'vmprocess-hung.log', 'process/report/vm', + 'Hung VM process state'); + if not fRcTmp: + try: reporter.log('******* START vmprocess-hung.log *******\n%s\n******* END vmprocess-hung.log *******\n' + % (sHostProcessInfoHung,)); + except: pass; # paranoia + + # Upload the screen video recordings if appropriate. + if self.fAlwaysUploadRecordings or reporter.testErrorCount() > 0: + reporter.log2('Uploading %d screen recordings ...' % (len(self.adRecordingFiles),)); + for dRecFile in self.adRecordingFiles: + reporter.log2('Uploading screen recording "%s" (screen %d)' % (dRecFile['file'], dRecFile['id'])); + reporter.addLogFile(dRecFile['file'], + 'screenrecording/failure' if reporter.testErrorCount() > 0 else 'screenrecording/success', + 'Recording of screen #%d' % (dRecFile['id'],)); + + return fRc; + + + # + # Some information query functions (mix). + # + # Methods require the VBox API. If the information is provided by both + # the testboxscript as well as VBox API, we'll check if it matches. + # + + def _hasHostCpuFeature(self, sEnvVar, sEnum, fpApiMinVer, fQuiet): + """ + Common Worker for hasHostNestedPaging() and hasHostHwVirt(). + + Returns True / False. + Raises exception on environment / host mismatch. + """ + fEnv = os.environ.get(sEnvVar, None); + if fEnv is not None: + fEnv = fEnv.lower() not in [ 'false', 'f', 'not', 'no', 'n', '0', ]; + + fVBox = None; + self.importVBoxApi(); + if self.fpApiVer >= fpApiMinVer and hasattr(vboxcon, sEnum): + try: + fVBox = self.oVBox.host.getProcessorFeature(getattr(vboxcon, sEnum)); + except: + if not fQuiet: + reporter.logXcpt(); + + if fVBox is not None: + if fEnv is not None: + if fEnv != fVBox and not fQuiet: + reporter.log('TestBox configuration overwritten: fVBox=%s (%s) vs. fEnv=%s (%s)' + % (fVBox, sEnum, fEnv, sEnvVar)); + return fEnv; + return fVBox; + if fEnv is not None: + return fEnv; + return False; + + def hasHostHwVirt(self, fQuiet = False): + """ + Checks if hardware assisted virtualization is supported by the host. + + Returns True / False. + Raises exception on environment / host mismatch. + """ + return self._hasHostCpuFeature('TESTBOX_HAS_HW_VIRT', 'ProcessorFeature_HWVirtEx', 3.1, fQuiet); + + def hasHostNestedPaging(self, fQuiet = False): + """ + Checks if nested paging is supported by the host. + + Returns True / False. + Raises exception on environment / host mismatch. + """ + return self._hasHostCpuFeature('TESTBOX_HAS_NESTED_PAGING', 'ProcessorFeature_NestedPaging', 4.2, fQuiet) \ + and self.hasHostHwVirt(fQuiet); + + def hasHostNestedHwVirt(self, fQuiet = False): + """ + Checks if nested hardware-assisted virtualization is supported by the host. + + Returns True / False. + Raises exception on environment / host mismatch. + """ + return self._hasHostCpuFeature('TESTBOX_HAS_NESTED_HWVIRT', 'ProcessorFeature_NestedHWVirt', 6.0, fQuiet) \ + and self.hasHostHwVirt(fQuiet); + + def hasHostLongMode(self, fQuiet = False): + """ + Checks if the host supports 64-bit guests. + + Returns True / False. + Raises exception on environment / host mismatch. + """ + # Note that the testboxscript doesn't export this variable atm. + return self._hasHostCpuFeature('TESTBOX_HAS_LONG_MODE', 'ProcessorFeature_LongMode', 3.1, fQuiet); + + def getHostCpuCount(self, fQuiet = False): + """ + Returns the number of CPUs on the host. + + Returns True / False. + Raises exception on environment / host mismatch. + """ + cEnv = os.environ.get('TESTBOX_CPU_COUNT', None); + if cEnv is not None: + cEnv = int(cEnv); + + try: + cVBox = self.oVBox.host.processorOnlineCount; + except: + if not fQuiet: + reporter.logXcpt(); + cVBox = None; + + if cVBox is not None: + if cEnv is not None: + assert cVBox == cEnv, 'Misconfigured TestBox: VBox: %u CPUs, testboxscript: %u CPUs' % (cVBox, cEnv); + return cVBox; + if cEnv is not None: + return cEnv; + return 1; + + def _getHostCpuDesc(self, fQuiet = False): + """ + Internal method used for getting the host CPU description from VBoxSVC. + Returns description string, on failure an empty string is returned. + """ + try: + return self.oVBox.host.getProcessorDescription(0); + except: + if not fQuiet: + reporter.logXcpt(); + return ''; + + def isHostCpuAmd(self, fQuiet = False): + """ + Checks if the host CPU vendor is AMD. + + Returns True / False. + """ + sCpuDesc = self._getHostCpuDesc(fQuiet); + return 'AMD' in sCpuDesc or sCpuDesc == 'AuthenticAMD'; + + def isHostCpuIntel(self, fQuiet = False): + """ + Checks if the host CPU vendor is Intel. + + Returns True / False. + """ + sCpuDesc = self._getHostCpuDesc(fQuiet); + return sCpuDesc.startswith("Intel") or sCpuDesc == 'GenuineIntel'; + + def isHostCpuVia(self, fQuiet = False): + """ + Checks if the host CPU vendor is VIA (or Centaur). + + Returns True / False. + """ + sCpuDesc = self._getHostCpuDesc(fQuiet); + return sCpuDesc.startswith("VIA") or sCpuDesc == 'CentaurHauls'; + + def isHostCpuShanghai(self, fQuiet = False): + """ + Checks if the host CPU vendor is Shanghai (or Zhaoxin). + + Returns True / False. + """ + sCpuDesc = self._getHostCpuDesc(fQuiet); + return sCpuDesc.startswith("ZHAOXIN") or sCpuDesc.strip(' ') == 'Shanghai'; + + def isHostCpuP4(self, fQuiet = False): + """ + Checks if the host CPU is a Pentium 4 / Pentium D. + + Returns True / False. + """ + if not self.isHostCpuIntel(fQuiet): + return False; + + (uFamilyModel, _, _, _) = self.oVBox.host.getProcessorCPUIDLeaf(0, 0x1, 0); + return ((uFamilyModel >> 8) & 0xf) == 0xf; + + def hasRawModeSupport(self, fQuiet = False): + """ + Checks if raw-mode is supported by VirtualBox that the testbox is + configured for it. + + Returns True / False. + Raises no exceptions. + + Note! Differs from the rest in that we don't require the + TESTBOX_WITH_RAW_MODE value to match the API. It is + sometimes helpful to disable raw-mode on individual + test boxes. (This probably goes for + """ + # The environment variable can be used to disable raw-mode. + fEnv = os.environ.get('TESTBOX_WITH_RAW_MODE', None); + if fEnv is not None: + fEnv = fEnv.lower() not in [ 'false', 'f', 'not', 'no', 'n', '0', ]; + if fEnv is False: + return False; + + # Starting with 5.0 GA / RC2 the API can tell us whether VBox was built + # with raw-mode support or not. + self.importVBoxApi(); + if self.fpApiVer >= 5.0: + try: + fVBox = self.oVBox.systemProperties.rawModeSupported; + except: + if not fQuiet: + reporter.logXcpt(); + fVBox = True; + if fVBox is False: + return False; + + return True; + + # + # Testdriver execution methods. + # + + def handleTask(self, oTask, sMethod): + """ + Callback method for handling unknown tasks in the various run loops. + + The testdriver should override this if it already tasks running when + calling startVmAndConnectToTxsViaTcp, txsRunTest or similar methods. + Call super to handle unknown tasks. + + Returns True if handled, False if not. + """ + reporter.error('%s: unknown task %s' % (sMethod, oTask)); + return False; + + def txsDoTask(self, oSession, oTxsSession, fnAsync, aArgs): + """ + Generic TXS task wrapper which waits both on the TXS and the session tasks. + + Returns False on error, logged. + Returns task result on success. + """ + # All async methods ends with the following two args. + cMsTimeout = aArgs[-2]; + fIgnoreErrors = aArgs[-1]; + + fRemoveVm = self.addTask(oSession); + fRemoveTxs = self.addTask(oTxsSession); + + rc = fnAsync(*aArgs); # pylint: disable=star-args + if rc is True: + rc = False; + oTask = self.waitForTasks(cMsTimeout + 1); + if oTask is oTxsSession: + if oTxsSession.isSuccess(): + rc = oTxsSession.getResult(); + elif fIgnoreErrors is True: + reporter.log( 'txsDoTask: task failed (%s)' % (oTxsSession.getLastReply()[1],)); + else: + reporter.error('txsDoTask: task failed (%s)' % (oTxsSession.getLastReply()[1],)); + else: + oTxsSession.cancelTask(); + if oTask is None: + if fIgnoreErrors is True: + reporter.log( 'txsDoTask: The task timed out.'); + else: + reporter.errorTimeout('txsDoTask: The task timed out.'); + elif oTask is oSession: + reporter.error('txsDoTask: The VM terminated unexpectedly'); + else: + if fIgnoreErrors is True: + reporter.log( 'txsDoTask: An unknown task %s was returned' % (oTask,)); + else: + reporter.error('txsDoTask: An unknown task %s was returned' % (oTask,)); + else: + reporter.error('txsDoTask: fnAsync returned %s' % (rc,)); + + if fRemoveTxs: + self.removeTask(oTxsSession); + if fRemoveVm: + self.removeTask(oSession); + return rc; + + # pylint: disable=missing-docstring + + def txsDisconnect(self, oSession, oTxsSession, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncDisconnect, + (self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsVer(self, oSession, oTxsSession, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncVer, + (self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsUuid(self, oSession, oTxsSession, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncUuid, + (self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsMkDir(self, oSession, oTxsSession, sRemoteDir, fMode = 0o700, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncMkDir, + (sRemoteDir, fMode, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsMkDirPath(self, oSession, oTxsSession, sRemoteDir, fMode = 0o700, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncMkDirPath, + (sRemoteDir, fMode, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsMkSymlink(self, oSession, oTxsSession, sLinkTarget, sLink, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncMkSymlink, + (sLinkTarget, sLink, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsRmDir(self, oSession, oTxsSession, sRemoteDir, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncRmDir, + (sRemoteDir, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsRmFile(self, oSession, oTxsSession, sRemoteFile, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncRmFile, + (sRemoteFile, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsRmSymlink(self, oSession, oTxsSession, sRemoteSymlink, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncRmSymlink, + (sRemoteSymlink, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsRmTree(self, oSession, oTxsSession, sRemoteTree, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncRmTree, + (sRemoteTree, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsIsDir(self, oSession, oTxsSession, sRemoteDir, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncIsDir, + (sRemoteDir, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsIsFile(self, oSession, oTxsSession, sRemoteFile, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncIsFile, + (sRemoteFile, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsIsSymlink(self, oSession, oTxsSession, sRemoteSymlink, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncIsSymlink, + (sRemoteSymlink, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsCopyFile(self, oSession, oTxsSession, sSrcFile, sDstFile, fMode = 0, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncCopyFile, \ + (sSrcFile, sDstFile, fMode, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsUploadFile(self, oSession, oTxsSession, sLocalFile, sRemoteFile, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncUploadFile, \ + (sLocalFile, sRemoteFile, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsUploadString(self, oSession, oTxsSession, sContent, sRemoteFile, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncUploadString, \ + (sContent, sRemoteFile, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsDownloadFile(self, oSession, oTxsSession, sRemoteFile, sLocalFile, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncDownloadFile, \ + (sRemoteFile, sLocalFile, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsDownloadFiles(self, oSession, oTxsSession, aasFiles, fAddToLog = True, fIgnoreErrors = False): + """ + Convenience function to get files from the guest, storing them in the + scratch and adding them to the test result set (optional, but default). + + The aasFiles parameter contains an array of with guest-path + host-path + pairs, optionally a file 'kind', description and an alternative upload + filename can also be specified. + + Host paths are relative to the scratch directory or they must be given + in absolute form. The guest path should be using guest path style. + + Returns True on success. + Returns False on failure (unless fIgnoreErrors is set), logged. + """ + for asEntry in aasFiles: + # Unpack: + sGstFile = asEntry[0]; + sHstFile = asEntry[1]; + sKind = asEntry[2] if len(asEntry) > 2 and asEntry[2] else 'misc/other'; + sDescription = asEntry[3] if len(asEntry) > 3 and asEntry[3] else ''; + sAltName = asEntry[4] if len(asEntry) > 4 and asEntry[4] else None; + assert len(asEntry) <= 5 and sGstFile and sHstFile; + if not os.path.isabs(sHstFile): + sHstFile = os.path.join(self.sScratchPath, sHstFile); + + reporter.log2('Downloading file "%s" to "%s" ...' % (sGstFile, sHstFile,)); + + try: os.unlink(sHstFile); ## @todo txsDownloadFile doesn't truncate the output file. + except: pass; + + fRc = self.txsDownloadFile(oSession, oTxsSession, sGstFile, sHstFile, 30 * 1000, fIgnoreErrors); + if fRc: + if fAddToLog: + reporter.addLogFile(sHstFile, sKind, sDescription, sAltName); + else: + if fIgnoreErrors is not True: + return reporter.error('error downloading file "%s" to "%s"' % (sGstFile, sHstFile)); + reporter.log('warning: file "%s" was not downloaded, ignoring.' % (sGstFile,)); + return True; + + def txsDownloadString(self, oSession, oTxsSession, sRemoteFile, sEncoding = 'utf-8', fIgnoreEncodingErrors = True, + cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncDownloadString, + (sRemoteFile, sEncoding, fIgnoreEncodingErrors, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsPackFile(self, oSession, oTxsSession, sRemoteFile, sRemoteSource, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncPackFile, \ + (sRemoteFile, sRemoteSource, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsUnpackFile(self, oSession, oTxsSession, sRemoteFile, sRemoteDir, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncUnpackFile, \ + (sRemoteFile, sRemoteDir, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + def txsExpandString(self, oSession, oTxsSession, sString, cMsTimeout = 30000, fIgnoreErrors = False): + return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncExpandString, \ + (sString, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors)); + + # pylint: enable=missing-docstring + + def txsCdWait(self, + oSession, # type: vboxwrappers.SessionWrapper + oTxsSession, # type: txsclient.Session + cMsTimeout = 30000, # type: int + sFile = None # type: String + ): # -> bool + """ + Mostly an internal helper for txsRebootAndReconnectViaTcp and + startVmAndConnectToTxsViaTcp that waits for the CDROM drive to become + ready. It does this by polling for a file it knows to exist on the CD. + + Returns True on success. + + Returns False on failure, logged. + """ + + if sFile is None: + sFile = 'valkit.txt'; + + reporter.log('txsCdWait: Waiting for file "%s" to become available ...' % (sFile,)); + + fRemoveVm = self.addTask(oSession); + fRemoveTxs = self.addTask(oTxsSession); + cMsTimeout = self.adjustTimeoutMs(cMsTimeout); + msStart = base.timestampMilli(); + cMsTimeout2 = cMsTimeout; + fRc = oTxsSession.asyncIsFile('${CDROM}/%s' % (sFile,), cMsTimeout2); + if fRc is True: + while True: + # wait for it to complete. + oTask = self.waitForTasks(cMsTimeout2 + 1); + if oTask is not oTxsSession: + oTxsSession.cancelTask(); + if oTask is None: + reporter.errorTimeout('txsCdWait: The task timed out (after %s ms).' + % (base.timestampMilli() - msStart,)); + elif oTask is oSession: + reporter.error('txsCdWait: The VM terminated unexpectedly'); + else: + reporter.error('txsCdWait: An unknown task %s was returned' % (oTask,)); + fRc = False; + break; + if oTxsSession.isSuccess(): + break; + + # Check for timeout. + cMsElapsed = base.timestampMilli() - msStart; + if cMsElapsed >= cMsTimeout: + reporter.error('txsCdWait: timed out'); + fRc = False; + break; + # delay. + self.sleep(1); + + # resubmit the task. + cMsTimeout2 = msStart + cMsTimeout - base.timestampMilli(); + cMsTimeout2 = max(cMsTimeout2, 500); + fRc = oTxsSession.asyncIsFile('${CDROM}/%s' % (sFile,), cMsTimeout2); + if fRc is not True: + reporter.error('txsCdWait: asyncIsFile failed'); + break; + else: + reporter.error('txsCdWait: asyncIsFile failed'); + + if not fRc: + # Do some diagnosis to find out why this failed. + ## @todo Identify guest OS type and only run one of the following commands. + fIsNotWindows = True; + reporter.log('txsCdWait: Listing root contents of ${CDROM}:'); + if fIsNotWindows: + reporter.log('txsCdWait: Tiggering udevadm ...'); + oTxsSession.syncExec("/sbin/udevadm", ("/sbin/udevadm", "trigger", "--verbose"), fIgnoreErrors = True); + time.sleep(15); + oTxsSession.syncExec("/bin/ls", ("/bin/ls", "-al", "${CDROM}"), fIgnoreErrors = True); + reporter.log('txsCdWait: Listing media directory:'); + oTxsSession.syncExec('/bin/ls', ('/bin/ls', '-l', '-a', '-R', '/media'), fIgnoreErrors = True); + reporter.log('txsCdWait: Listing mount points / drives:'); + oTxsSession.syncExec('/bin/mount', ('/bin/mount',), fIgnoreErrors = True); + oTxsSession.syncExec('/bin/cat', ('/bin/cat', '/etc/fstab'), fIgnoreErrors = True); + oTxsSession.syncExec('/bin/dmesg', ('/bin/dmesg',), fIgnoreErrors = True); + oTxsSession.syncExec('/usr/bin/lshw', ('/usr/bin/lshw', '-c', 'disk'), fIgnoreErrors = True); + oTxsSession.syncExec('/bin/journalctl', + ('/bin/journalctl', '-x', '-b'), fIgnoreErrors = True); + oTxsSession.syncExec('/bin/journalctl', + ('/bin/journalctl', '-x', '-b', '/usr/lib/udisks2/udisksd'), fIgnoreErrors = True); + oTxsSession.syncExec('/usr/bin/udisksctl', + ('/usr/bin/udisksctl', 'info', '-b', '/dev/sr0'), fIgnoreErrors = True); + oTxsSession.syncExec('/bin/systemctl', + ('/bin/systemctl', 'status', 'udisks2'), fIgnoreErrors = True); + oTxsSession.syncExec('/bin/ps', + ('/bin/ps', '-a', '-u', '-x'), fIgnoreErrors = True); + reporter.log('txsCdWait: Mounting manually ...'); + for _ in range(3): + oTxsSession.syncExec('/bin/mount', ('/bin/mount', '/dev/sr0', '${CDROM}'), fIgnoreErrors = True); + time.sleep(5); + reporter.log('txsCdWait: Re-Listing media directory:'); + oTxsSession.syncExec('/bin/ls', ('/bin/ls', '-l', '-a', '-R', '/media'), fIgnoreErrors = True); + else: + # ASSUMES that we always install Windows on drive C right now. + sWinDir = "C:\\Windows\\System32\\"; + # Should work since WinXP Pro. + oTxsSession.syncExec(sWinDir + "wbem\\WMIC.exe", + ("WMIC.exe", "logicaldisk", "get", + "deviceid, volumename, description"), + fIgnoreErrors = True); + oTxsSession.syncExec(sWinDir + " cmd.exe", + ('cmd.exe', '/C', 'dir', '${CDROM}'), + fIgnoreErrors = True); + + if fRemoveTxs: + self.removeTask(oTxsSession); + if fRemoveVm: + self.removeTask(oSession); + return fRc; + + def txsDoConnectViaTcp(self, oSession, cMsTimeout, fNatForwardingForTxs = False): + """ + Mostly an internal worker for connecting to TXS via TCP used by the + *ViaTcp methods. + + Returns a tuplet with True/False and TxsSession/None depending on the + result. Errors are logged. + """ + + reporter.log2('txsDoConnectViaTcp: oSession=%s, cMsTimeout=%s, fNatForwardingForTxs=%s' + % (oSession, cMsTimeout, fNatForwardingForTxs)); + + cMsTimeout = self.adjustTimeoutMs(cMsTimeout); + oTxsConnect = oSession.txsConnectViaTcp(cMsTimeout, fNatForwardingForTxs = fNatForwardingForTxs); + if oTxsConnect is not None: + self.addTask(oTxsConnect); + fRemoveVm = self.addTask(oSession); + oTask = self.waitForTasks(cMsTimeout + 1); + reporter.log2('txsDoConnectViaTcp: waitForTasks returned %s' % (oTask,)); + self.removeTask(oTxsConnect); + if oTask is oTxsConnect: + oTxsSession = oTxsConnect.getResult(); + if oTxsSession is not None: + reporter.log('txsDoConnectViaTcp: Connected to TXS on %s.' % (oTxsSession.oTransport.sHostname,)); + return (True, oTxsSession); + + reporter.error('txsDoConnectViaTcp: failed to connect to TXS.'); + else: + oTxsConnect.cancelTask(); + if oTask is None: + reporter.errorTimeout('txsDoConnectViaTcp: connect stage 1 timed out'); + elif oTask is oSession: + oSession.reportPrematureTermination('txsDoConnectViaTcp: '); + else: + reporter.error('txsDoConnectViaTcp: unknown/wrong task %s' % (oTask,)); + if fRemoveVm: + self.removeTask(oSession); + else: + reporter.error('txsDoConnectViaTcp: txsConnectViaTcp failed'); + return (False, None); + + def startVmAndConnectToTxsViaTcp(self, sVmName, fCdWait = False, cMsTimeout = 15*60000, \ + cMsCdWait = 30000, sFileCdWait = None, \ + fNatForwardingForTxs = False): + """ + Starts the specified VM and tries to connect to its TXS via TCP. + The VM will be powered off if TXS doesn't respond before the specified + time has elapsed. + + Returns a the VM and TXS sessions (a two tuple) on success. The VM + session is in the task list, the TXS session is not. + Returns (None, None) on failure, fully logged. + """ + + # Zap the guest IP to make sure we're not getting a stale entry + # (unless we're restoring the VM of course). + oTestVM = self.oTestVmSet.findTestVmByName(sVmName) if self.oTestVmSet is not None else None; + if oTestVM is None \ + or oTestVM.fSnapshotRestoreCurrent is False: + try: + oSession1 = self.openSession(self.getVmByName(sVmName)); + oSession1.delGuestPropertyValue('/VirtualBox/GuestInfo/Net/0/V4/IP'); + oSession1.saveSettings(True); + del oSession1; + except: + reporter.logXcpt(); + + # Start the VM. + reporter.log('startVmAndConnectToTxsViaTcp: Starting(/preparing) "%s" (timeout %s s)...' % (sVmName, cMsTimeout / 1000)); + reporter.flushall(); + oSession = self.startVmByName(sVmName); + if oSession is not None: + # Connect to TXS. + reporter.log2('startVmAndConnectToTxsViaTcp: Started(/prepared) "%s", connecting to TXS ...' % (sVmName,)); + (fRc, oTxsSession) = self.txsDoConnectViaTcp(oSession, cMsTimeout, fNatForwardingForTxs); + if fRc is True: + if fCdWait: + # Wait for CD? + reporter.log2('startVmAndConnectToTxsViaTcp: Waiting for file "%s" to become available ...' % (sFileCdWait,)); + fRc = self.txsCdWait(oSession, oTxsSession, cMsCdWait, sFileCdWait); + if fRc is not True: + reporter.error('startVmAndConnectToTxsViaTcp: txsCdWait failed'); + + sVer = self.txsVer(oSession, oTxsSession, cMsTimeout, fIgnoreErrors = True); + if sVer is not False: + reporter.log('startVmAndConnectToTxsViaTcp: TestExecService version %s' % (sVer,)); + else: + reporter.log('startVmAndConnectToTxsViaTcp: Unable to retrieve TestExecService version'); + + if fRc is True: + # Success! + return (oSession, oTxsSession); + else: + reporter.error('startVmAndConnectToTxsViaTcp: txsDoConnectViaTcp failed'); + # If something went wrong while waiting for TXS to be started - take VM screenshot before terminate it + self.terminateVmBySession(oSession); + return (None, None); + + def txsRebootAndReconnectViaTcp(self, oSession, oTxsSession, fCdWait = False, cMsTimeout = 15*60000, \ + cMsCdWait = 30000, sFileCdWait = None, fNatForwardingForTxs = False): + """ + Executes the TXS reboot command + + Returns A tuple of True and the new TXS session on success. + + Returns A tuple of False and either the old TXS session or None on failure. + """ + reporter.log2('txsRebootAndReconnect: cMsTimeout=%u' % (cMsTimeout,)); + + # + # This stuff is a bit complicated because of rebooting being kind of + # disruptive to the TXS and such... The protocol is that TXS will: + # - ACK the reboot command. + # - Shutdown the transport layer, implicitly disconnecting us. + # - Execute the reboot operation. + # - On failure, it will be re-init the transport layer and be + # available pretty much immediately. UUID unchanged. + # - On success, it will be respawed after the reboot (hopefully), + # with a different UUID. + # + fRc = False; + iStart = base.timestampMilli(); + + # Get UUID. + cMsTimeout2 = min(60000, cMsTimeout); + sUuidBefore = self.txsUuid(oSession, oTxsSession, self.adjustTimeoutMs(cMsTimeout2, 60000)); + if sUuidBefore is not False: + # Reboot. + cMsElapsed = base.timestampMilli() - iStart; + cMsTimeout2 = cMsTimeout - cMsElapsed; + fRc = self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncReboot, + (self.adjustTimeoutMs(cMsTimeout2, 60000), False)); + if fRc is True: + # Reconnect. + if fNatForwardingForTxs is True: + self.sleep(22); # NAT fudge - Two fixes are wanted: 1. TXS connect retries. 2. Main API reboot/reset hint. + cMsElapsed = base.timestampMilli() - iStart; + (fRc, oTxsSession) = self.txsDoConnectViaTcp(oSession, cMsTimeout - cMsElapsed, fNatForwardingForTxs); + if fRc is True: + # Check the UUID. + cMsElapsed = base.timestampMilli() - iStart; + cMsTimeout2 = min(60000, cMsTimeout - cMsElapsed); + sUuidAfter = self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncUuid, + (self.adjustTimeoutMs(cMsTimeout2, 60000), False)); + if sUuidBefore is not False: + if sUuidAfter != sUuidBefore: + reporter.log('The guest rebooted (UUID %s -> %s)' % (sUuidBefore, sUuidAfter)) + + # Do CD wait if specified. + if fCdWait: + fRc = self.txsCdWait(oSession, oTxsSession, cMsCdWait, sFileCdWait); + if fRc is not True: + reporter.error('txsRebootAndReconnectViaTcp: txsCdWait failed'); + + sVer = self.txsVer(oSession, oTxsSession, cMsTimeout, fIgnoreErrors = True); + if sVer is not False: + reporter.log('txsRebootAndReconnectViaTcp: TestExecService version %s' % (sVer,)); + else: + reporter.log('txsRebootAndReconnectViaTcp: Unable to retrieve TestExecService version'); + else: + reporter.error('txsRebootAndReconnectViaTcp: failed to get UUID (after)'); + else: + reporter.error('txsRebootAndReconnectViaTcp: did not reboot (UUID %s)' % (sUuidBefore,)); + else: + reporter.error('txsRebootAndReconnectViaTcp: txsDoConnectViaTcp failed'); + else: + reporter.error('txsRebootAndReconnectViaTcp: reboot failed'); + else: + reporter.error('txsRebootAndReconnectViaTcp: failed to get UUID (before)'); + return (fRc, oTxsSession); + + # pylint: disable=too-many-locals,too-many-arguments + + def txsRunTest(self, oTxsSession, sTestName, cMsTimeout, sExecName, asArgs = (), asAddEnv = (), sAsUser = "", + fCheckSessionStatus = False): + """ + Executes the specified test task, waiting till it completes or times out. + + The VM session (if any) must be in the task list. + + Returns True if we executed the task and nothing abnormal happend. + Query the process status from the TXS session. + + Returns False if some unexpected task was signalled or we failed to + submit the job. + + If fCheckSessionStatus is set to True, the overall session status will be + taken into account and logged as an error on failure. + """ + reporter.testStart(sTestName); + reporter.log2('txsRunTest: cMsTimeout=%u sExecName=%s asArgs=%s' % (cMsTimeout, sExecName, asArgs)); + + # Submit the job. + fRc = False; + if oTxsSession.asyncExec(sExecName, asArgs, asAddEnv, sAsUser, cMsTimeout = self.adjustTimeoutMs(cMsTimeout)): + self.addTask(oTxsSession); + + # Wait for the job to complete. + while True: + oTask = self.waitForTasks(cMsTimeout + 1); + if oTask is None: + if fCheckSessionStatus: + reporter.error('txsRunTest: waitForTasks for test "%s" timed out' % (sTestName,)); + else: + reporter.log('txsRunTest: waitForTasks for test "%s" timed out' % (sTestName,)); + break; + if oTask is oTxsSession: + if fCheckSessionStatus \ + and not oTxsSession.isSuccess(): + reporter.error('txsRunTest: Test "%s" failed' % (sTestName,)); + else: + fRc = True; + reporter.log('txsRunTest: isSuccess=%s getResult=%s' \ + % (oTxsSession.isSuccess(), oTxsSession.getResult())); + break; + if not self.handleTask(oTask, 'txsRunTest'): + break; + + self.removeTask(oTxsSession); + if not oTxsSession.pollTask(): + oTxsSession.cancelTask(); + else: + reporter.error('txsRunTest: asyncExec failed'); + + reporter.testDone(); + return fRc; + + def txsRunTestRedirectStd(self, oTxsSession, sTestName, cMsTimeout, sExecName, asArgs = (), asAddEnv = (), sAsUser = "", + oStdIn = '/dev/null', oStdOut = '/dev/null', oStdErr = '/dev/null', oTestPipe = '/dev/null'): + """ + Executes the specified test task, waiting till it completes or times out, + redirecting stdin, stdout and stderr to the given objects. + + The VM session (if any) must be in the task list. + + Returns True if we executed the task and nothing abnormal happend. + Query the process status from the TXS session. + + Returns False if some unexpected task was signalled or we failed to + submit the job. + """ + reporter.testStart(sTestName); + reporter.log2('txsRunTestRedirectStd: cMsTimeout=%u sExecName=%s asArgs=%s' % (cMsTimeout, sExecName, asArgs)); + + # Submit the job. + fRc = False; + if oTxsSession.asyncExecEx(sExecName, asArgs, asAddEnv, oStdIn, oStdOut, oStdErr, + oTestPipe, sAsUser, cMsTimeout = self.adjustTimeoutMs(cMsTimeout)): + self.addTask(oTxsSession); + + # Wait for the job to complete. + while True: + oTask = self.waitForTasks(cMsTimeout + 1); + if oTask is None: + reporter.log('txsRunTestRedirectStd: waitForTasks timed out'); + break; + if oTask is oTxsSession: + fRc = True; + reporter.log('txsRunTestRedirectStd: isSuccess=%s getResult=%s' + % (oTxsSession.isSuccess(), oTxsSession.getResult())); + break; + if not self.handleTask(oTask, 'txsRunTestRedirectStd'): + break; + + self.removeTask(oTxsSession); + if not oTxsSession.pollTask(): + oTxsSession.cancelTask(); + else: + reporter.error('txsRunTestRedirectStd: asyncExec failed'); + + reporter.testDone(); + return fRc; + + def txsRunTest2(self, oTxsSession1, oTxsSession2, sTestName, cMsTimeout, + sExecName1, asArgs1, + sExecName2, asArgs2, + asAddEnv1 = (), sAsUser1 = '', fWithTestPipe1 = True, + asAddEnv2 = (), sAsUser2 = '', fWithTestPipe2 = True): + """ + Executes the specified test tasks, waiting till they complete or + times out. The 1st task is started after the 2nd one. + + The VM session (if any) must be in the task list. + + Returns True if we executed the task and nothing abnormal happend. + Query the process status from the TXS sessions. + + Returns False if some unexpected task was signalled or we failed to + submit the job. + """ + reporter.testStart(sTestName); + + # Submit the jobs. + fRc = False; + if oTxsSession1.asyncExec(sExecName1, asArgs1, asAddEnv1, sAsUser1, fWithTestPipe1, '1-', + self.adjustTimeoutMs(cMsTimeout)): + self.addTask(oTxsSession1); + + self.sleep(2); # fudge! grr + + if oTxsSession2.asyncExec(sExecName2, asArgs2, asAddEnv2, sAsUser2, fWithTestPipe2, '2-', + self.adjustTimeoutMs(cMsTimeout)): + self.addTask(oTxsSession2); + + # Wait for the jobs to complete. + cPendingJobs = 2; + while True: + oTask = self.waitForTasks(cMsTimeout + 1); + if oTask is None: + reporter.log('txsRunTest2: waitForTasks timed out'); + break; + + if oTask is oTxsSession1 or oTask is oTxsSession2: + if oTask is oTxsSession1: iTask = 1; + else: iTask = 2; + reporter.log('txsRunTest2: #%u - isSuccess=%s getResult=%s' \ + % (iTask, oTask.isSuccess(), oTask.getResult())); + self.removeTask(oTask); + cPendingJobs -= 1; + if cPendingJobs <= 0: + fRc = True; + break; + + elif not self.handleTask(oTask, 'txsRunTest'): + break; + + self.removeTask(oTxsSession2); + if not oTxsSession2.pollTask(): + oTxsSession2.cancelTask(); + else: + reporter.error('txsRunTest2: asyncExec #2 failed'); + + self.removeTask(oTxsSession1); + if not oTxsSession1.pollTask(): + oTxsSession1.cancelTask(); + else: + reporter.error('txsRunTest2: asyncExec #1 failed'); + + reporter.testDone(); + return fRc; + + # pylint: enable=too-many-locals,too-many-arguments + + + # + # Working with test results via serial port. + # + + class TxsMonitorComFile(base.TdTaskBase): + """ + Class that monitors a COM output file. + """ + + def __init__(self, sComRawFile, asStopWords = None): + base.TdTaskBase.__init__(self, utils.getCallerName()); + self.sComRawFile = sComRawFile; + self.oStopRegExp = re.compile('\\b(' + '|'.join(asStopWords if asStopWords else ('PASSED', 'FAILED',)) + ')\\b'); + self.sResult = None; ##< The result. + self.cchDisplayed = 0; ##< Offset into the file string of what we've already fed to the logger. + + def toString(self): + return '<%s sComRawFile=%s oStopRegExp=%s sResult=%s cchDisplayed=%s>' \ + % (base.TdTaskBase.toString(self), self.sComRawFile, self.oStopRegExp, self.sResult, self.cchDisplayed,); + + def pollTask(self, fLocked = False): + """ + Overrides TdTaskBase.pollTask() for the purpose of polling the file. + """ + if not fLocked: + self.lockTask(); + + sFile = utils.noxcptReadFile(self.sComRawFile, '', 'rU'); + if len(sFile) > self.cchDisplayed: + sNew = sFile[self.cchDisplayed:]; + oMatch = self.oStopRegExp.search(sNew); + if oMatch: + # Done! Get result, flush all the output and signal the task. + self.sResult = oMatch.group(1); + for sLine in sNew.split('\n'): + reporter.log('COM OUTPUT: %s' % (sLine,)); + self.cchDisplayed = len(sFile); + self.signalTaskLocked(); + else: + # Output whole lines only. + offNewline = sFile.find('\n', self.cchDisplayed); + while offNewline >= 0: + reporter.log('COM OUTPUT: %s' % (sFile[self.cchDisplayed:offNewline])) + self.cchDisplayed = offNewline + 1; + offNewline = sFile.find('\n', self.cchDisplayed); + + fRet = self.fSignalled; + if not fLocked: + self.unlockTask(); + return fRet; + + # Our stuff. + def getResult(self): + """ + Returns the connected TXS session object on success. + Returns None on failure or if the task has not yet completed. + """ + self.oCv.acquire(); + sResult = self.sResult; + self.oCv.release(); + return sResult; + + def cancelTask(self): + """ Cancels the task. """ + self.signalTask(); + return True; + + + def monitorComRawFile(self, oSession, sComRawFile, cMsTimeout = 15*60000, asStopWords = None): + """ + Monitors the COM output file for stop words (PASSED and FAILED by default). + + Returns the stop word. + Returns None on VM error and timeout. + """ + + reporter.log2('monitorComRawFile: oSession=%s, cMsTimeout=%s, sComRawFile=%s' % (oSession, cMsTimeout, sComRawFile)); + + oMonitorTask = self.TxsMonitorComFile(sComRawFile, asStopWords); + self.addTask(oMonitorTask); + + cMsTimeout = self.adjustTimeoutMs(cMsTimeout); + oTask = self.waitForTasks(cMsTimeout + 1); + reporter.log2('monitorComRawFile: waitForTasks returned %s' % (oTask,)); + + if oTask is not oMonitorTask: + oMonitorTask.cancelTask(); + self.removeTask(oMonitorTask); + + oMonitorTask.pollTask(); + return oMonitorTask.getResult(); + + + def runVmAndMonitorComRawFile(self, sVmName, sComRawFile, cMsTimeout = 15*60000, asStopWords = None): + """ + Runs the specified VM and monitors the given COM output file for stop + words (PASSED and FAILED by default). + + The caller is assumed to have configured the VM to use the given + file. The method will take no action to verify this. + + Returns the stop word. + Returns None on VM error and timeout. + """ + + # Start the VM. + reporter.log('runVmAndMonitorComRawFile: Starting(/preparing) "%s" (timeout %s s)...' % (sVmName, cMsTimeout / 1000)); + reporter.flushall(); + oSession = self.startVmByName(sVmName); + if oSession is not None: + # Let it run and then terminate it. + sRet = self.monitorComRawFile(oSession, sComRawFile, cMsTimeout, asStopWords); + self.terminateVmBySession(oSession); + else: + sRet = None; + return sRet; + + # + # Other stuff + # + + def waitForGAs(self, + oSession, # type: vboxwrappers.SessionWrapper + cMsTimeout = 120000, aenmWaitForRunLevels = None, aenmWaitForActive = None, aenmWaitForInactive = None): + """ + Waits for the guest additions to enter a certain state. + + aenmWaitForRunLevels - List of run level values to wait for (success if one matches). + aenmWaitForActive - List facilities (type values) that must be active. + aenmWaitForInactive - List facilities (type values) that must be inactive. + + Defaults to wait for AdditionsRunLevelType_Userland if nothing else is given. + + Returns True on success, False w/ error logging on timeout or failure. + """ + reporter.log2('waitForGAs: oSession=%s, cMsTimeout=%s' % (oSession, cMsTimeout,)); + + # + # Get IGuest: + # + try: + oIGuest = oSession.o.console.guest; + except: + return reporter.errorXcpt(); + + # + # Create a wait task: + # + from testdriver.vboxwrappers import AdditionsStatusTask; + try: + oGaStatusTask = AdditionsStatusTask(oSession = oSession, + oIGuest = oIGuest, + cMsTimeout = cMsTimeout, + aenmWaitForRunLevels = aenmWaitForRunLevels, + aenmWaitForActive = aenmWaitForActive, + aenmWaitForInactive = aenmWaitForInactive); + except: + return reporter.errorXcpt(); + + # + # Add the task and make sure the VM session is also present. + # + self.addTask(oGaStatusTask); + fRemoveSession = self.addTask(oSession); + oTask = self.waitForTasks(cMsTimeout + 1); + reporter.log2('waitForGAs: returned %s (oGaStatusTask=%s, oSession=%s)' % (oTask, oGaStatusTask, oSession,)); + self.removeTask(oGaStatusTask); + if fRemoveSession: + self.removeTask(oSession); + + # + # Digest the result. + # + if oTask is oGaStatusTask: + fSucceeded = oGaStatusTask.getResult(); + if fSucceeded is True: + reporter.log('waitForGAs: Succeeded.'); + else: + reporter.error('waitForGAs: Failed.'); + else: + oGaStatusTask.cancelTask(); + if oTask is None: + reporter.error('waitForGAs: Timed out.'); + elif oTask is oSession: + oSession.reportPrematureTermination('waitForGAs: '); + else: + reporter.error('waitForGAs: unknown/wrong task %s' % (oTask,)); + fSucceeded = False; + return fSucceeded; + + @staticmethod + def controllerTypeToName(eControllerType): + """ + Translate a controller type to a standard controller name. + """ + if eControllerType in (vboxcon.StorageControllerType_PIIX3, vboxcon.StorageControllerType_PIIX4,): + sName = "IDE Controller"; + elif eControllerType == vboxcon.StorageControllerType_IntelAhci: + sName = "SATA Controller"; + elif eControllerType == vboxcon.StorageControllerType_LsiLogicSas: + sName = "SAS Controller"; + elif eControllerType in (vboxcon.StorageControllerType_LsiLogic, vboxcon.StorageControllerType_BusLogic,): + sName = "SCSI Controller"; + elif eControllerType == vboxcon.StorageControllerType_NVMe: + sName = "NVMe Controller"; + elif eControllerType == vboxcon.StorageControllerType_VirtioSCSI: + sName = "VirtIO SCSI Controller"; + else: + sName = "Storage Controller"; + return sName; diff --git a/src/VBox/ValidationKit/testdriver/vboxcon.py b/src/VBox/ValidationKit/testdriver/vboxcon.py new file mode 100755 index 00000000..30b83dd2 --- /dev/null +++ b/src/VBox/ValidationKit/testdriver/vboxcon.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +# $Id: vboxcon.py $ + +""" +VirtualBox Constants. + +See VBoxConstantWrappingHack for details. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import sys + + +class VBoxConstantWrappingHack(object): # pylint: disable=too-few-public-methods + """ + This is a hack to avoid the self.oVBoxMgr.constants.MachineState_Running + ugliness that forces one into the right margin... Anyone using this module + can get to the constants easily by: + + from testdriver import vboxcon + if self.o.machine.state == vboxcon.MachineState_Running: + do stuff; + + For our own convenience there's a vboxcon attribute set up in vbox.py, + class TestDriver which is the basis for the VirtualBox testcases. It takes + care of setting things up properly through the global variable + 'goHackModuleClass' that refers to the instance of this class(if we didn't + we'd have to use testdriver.vboxcon.MachineState_Running). + """ + def __init__(self, oWrapped): + self.oWrapped = oWrapped; + self.oVBoxMgr = None; + self.fpApiVer = 99.0; + + def __getattr__(self, sName): + # Our self. + try: + return getattr(self.oWrapped, sName) + except AttributeError: + # The VBox constants. + if self.oVBoxMgr is None: + raise; + try: + return getattr(self.oVBoxMgr.constants, sName); + except AttributeError: + # Do some compatability mappings to keep it working with + # older versions. + if self.fpApiVer < 3.3: + if sName == 'SessionState_Locked': + return getattr(self.oVBoxMgr.constants, 'SessionState_Open'); + if sName == 'SessionState_Unlocked': + return getattr(self.oVBoxMgr.constants, 'SessionState_Closed'); + if sName == 'SessionState_Unlocking': + return getattr(self.oVBoxMgr.constants, 'SessionState_Closing'); + raise; + + +goHackModuleClass = VBoxConstantWrappingHack(sys.modules[__name__]); # pylint: disable=invalid-name +sys.modules[__name__] = goHackModuleClass; + diff --git a/src/VBox/ValidationKit/testdriver/vboxinstaller.py b/src/VBox/ValidationKit/testdriver/vboxinstaller.py new file mode 100755 index 00000000..8d973bf1 --- /dev/null +++ b/src/VBox/ValidationKit/testdriver/vboxinstaller.py @@ -0,0 +1,1251 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +VirtualBox Installer Wrapper Driver. + +This installs VirtualBox, starts a sub driver which does the real testing, +and then uninstall VirtualBox afterwards. This reduces the complexity of the +other VBox test drivers. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 155126 $" + + +# Standard Python imports. +import os +import sys +import re +import socket +import tempfile +import time + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from common import utils, webutils; +from common.constants import rtexitcode; +from testdriver import reporter; +from testdriver.base import TestDriverBase; + + + +class VBoxInstallerTestDriver(TestDriverBase): + """ + Implementation of a top level test driver. + """ + + + ## State file indicating that we've skipped installation. + ksVar_Skipped = 'vboxinstaller-skipped'; + + + def __init__(self): + TestDriverBase.__init__(self); + self._asSubDriver = []; # The sub driver and it's arguments. + self._asBuildUrls = []; # The URLs passed us on the command line. + self._asBuildFiles = []; # The downloaded file names. + self._fUnpackedBuildFiles = False; + self._fAutoInstallPuelExtPack = True; + self._fKernelDrivers = True; + self._fWinForcedInstallTimestampCA = True; + self._fInstallMsCrt = False; # By default we don't install the Microsoft CRT (only needed once). + + # + # Base method we override + # + + def showUsage(self): + rc = TestDriverBase.showUsage(self); + # 0 1 2 3 4 5 6 7 8 + # 012345678901234567890123456789012345678901234567890123456789012345678901234567890 + reporter.log(''); + reporter.log('vboxinstaller Options:'); + reporter.log(' --vbox-build <url[,url2[,...]]>'); + reporter.log(' Comma separated list of URL to file to download and install or/and'); + reporter.log(' unpack. URLs without a schema are assumed to be files on the'); + reporter.log(' build share and will be copied off it.'); + reporter.log(' --no-puel-extpack'); + reporter.log(' Indicates that the PUEL extension pack should not be installed if found.'); + reporter.log(' The default is to install it when found in the vbox-build.'); + reporter.log(' --no-kernel-drivers'); + reporter.log(' Indicates that the kernel drivers should not be installed on platforms'); + reporter.log(' where this is optional. The default is to install them.'); + reporter.log(' --forced-win-install-timestamp-ca, --no-forced-win-install-timestamp-ca'); + reporter.log(' Whether to force installation of the legacy Windows timestamp CA.'); + reporter.log(' If not forced, it will only installed on the hosts that needs it.'); + reporter.log(' Default: --no-forced-win-install-timestamp-ca'); + reporter.log(' --win-install-mscrt, --no-win-install-mscrt'); + reporter.log(' Whether to install the MS Visual Studio Redistributable.'); + reporter.log(' Default: --no-win-install-mscrt'); + reporter.log(' --'); + reporter.log(' Indicates the end of our parameters and the start of the sub'); + reporter.log(' testdriver and its arguments.'); + return rc; + + def parseOption(self, asArgs, iArg): + """ + Parse our arguments. + """ + if asArgs[iArg] == '--': + # End of our parameters and start of the sub driver invocation. + iArg = self.requireMoreArgs(1, asArgs, iArg); + assert not self._asSubDriver; + self._asSubDriver = asArgs[iArg:]; + self._asSubDriver[0] = self._asSubDriver[0].replace('/', os.path.sep); + iArg = len(asArgs) - 1; + elif asArgs[iArg] == '--vbox-build': + # List of files to copy/download and install. + iArg = self.requireMoreArgs(1, asArgs, iArg); + self._asBuildUrls = asArgs[iArg].split(','); + elif asArgs[iArg] == '--no-puel-extpack': + self._fAutoInstallPuelExtPack = False; + elif asArgs[iArg] == '--puel-extpack': + self._fAutoInstallPuelExtPack = True; + elif asArgs[iArg] == '--no-kernel-drivers': + self._fKernelDrivers = False; + elif asArgs[iArg] == '--kernel-drivers': + self._fKernelDrivers = True; + elif asArgs[iArg] == '--no-forced-win-install-timestamp-ca': + self._fWinForcedInstallTimestampCA = False; + elif asArgs[iArg] == '--forced-win-install-timestamp-ca': + self._fWinForcedInstallTimestampCA = True; + elif asArgs[iArg] == '--no-win-install-mscrt': + self._fInstallMsCrt = False; + elif asArgs[iArg] == '--win-install-mscrt': + self._fInstallMsCrt = True; + else: + return TestDriverBase.parseOption(self, asArgs, iArg); + return iArg + 1; + + def completeOptions(self): + # + # Check that we've got what we need. + # + if not self._asBuildUrls: + reporter.error('No build files specified ("--vbox-build file1[,file2[...]]")'); + return False; + if not self._asSubDriver: + reporter.error('No sub testdriver specified. (" -- test/stuff/tdStuff1.py args")'); + return False; + + # + # Construct _asBuildFiles as an array parallel to _asBuildUrls. + # + for sUrl in self._asBuildUrls: + sDstFile = os.path.join(self.sScratchPath, webutils.getFilename(sUrl)); + self._asBuildFiles.append(sDstFile); + + return TestDriverBase.completeOptions(self); + + def actionExtract(self): + reporter.error('vboxinstall does not support extracting resources, you have to do that using the sub testdriver.'); + return False; + + def actionCleanupBefore(self): + """ + Kills all VBox process we see. + + This is only supposed to execute on a testbox so we don't need to go + all complicated wrt other users. + """ + return self._killAllVBoxProcesses(); + + def actionConfig(self): + """ + Install VBox and pass on the configure request to the sub testdriver. + """ + fRc = self._installVBox(); + if fRc is None: + self._persistentVarSet(self.ksVar_Skipped, 'true'); + self.fBadTestbox = True; + else: + self._persistentVarUnset(self.ksVar_Skipped); + + ## @todo vbox.py still has bugs preventing us from invoking it seperately with each action. + if fRc is True and 'execute' not in self.asActions and 'all' not in self.asActions: + fRc = self._executeSubDriver([ 'verify', ]); + if fRc is True and 'execute' not in self.asActions and 'all' not in self.asActions: + fRc = self._executeSubDriver([ 'config', ], fPreloadASan = True); + return fRc; + + def actionExecute(self): + """ + Execute the sub testdriver. + """ + return self._executeSubDriver(self.asActions, fPreloadASan = True); + + def actionCleanupAfter(self): + """ + Forward this to the sub testdriver, then uninstall VBox. + """ + fRc = True; + if 'execute' not in self.asActions and 'all' not in self.asActions: + fRc = self._executeSubDriver([ 'cleanup-after', ], fMaySkip = False); + + if not self._killAllVBoxProcesses(): + fRc = False; + + if not self._uninstallVBox(self._persistentVarExists(self.ksVar_Skipped)): + fRc = False; + + if utils.getHostOs() == 'darwin': + self._darwinUnmountDmg(fIgnoreError = True); # paranoia + + if not TestDriverBase.actionCleanupAfter(self): + fRc = False; + + return fRc; + + + def actionAbort(self): + """ + Forward this to the sub testdriver first, then wipe all VBox like + processes, and finally do the pid file processing (again). + """ + fRc1 = self._executeSubDriver([ 'abort', ], fMaySkip = False, fPreloadASan = True); + fRc2 = self._killAllVBoxProcesses(); + fRc3 = TestDriverBase.actionAbort(self); + return fRc1 and fRc2 and fRc3; + + + # + # Persistent variables. + # + ## @todo integrate into the base driver. Persistent accross scratch wipes? + + def __persistentVarCalcName(self, sVar): + """Returns the (full) filename for the given persistent variable.""" + assert re.match(r'^[a-zA-Z0-9_-]*$', sVar) is not None; + return os.path.join(self.sScratchPath, 'persistent-%s.var' % (sVar,)); + + def _persistentVarSet(self, sVar, sValue = ''): + """ + Sets a persistent variable. + + Returns True on success, False + reporter.error on failure. + + May raise exception if the variable name is invalid or something + unexpected happens. + """ + sFull = self.__persistentVarCalcName(sVar); + try: + with open(sFull, 'w') as oFile: # pylint: disable=unspecified-encoding + if sValue: + oFile.write(sValue.encode('utf-8')); + except: + reporter.errorXcpt('Error creating "%s"' % (sFull,)); + return False; + return True; + + def _persistentVarUnset(self, sVar): + """ + Unsets a persistent variable. + + Returns True on success, False + reporter.error on failure. + + May raise exception if the variable name is invalid or something + unexpected happens. + """ + sFull = self.__persistentVarCalcName(sVar); + if os.path.exists(sFull): + try: + os.unlink(sFull); + except: + reporter.errorXcpt('Error unlinking "%s"' % (sFull,)); + return False; + return True; + + def _persistentVarExists(self, sVar): + """ + Checks if a persistent variable exists. + + Returns true/false. + + May raise exception if the variable name is invalid or something + unexpected happens. + """ + return os.path.exists(self.__persistentVarCalcName(sVar)); + + def _persistentVarGet(self, sVar): + """ + Gets the value of a persistent variable. + + Returns variable value on success. + Returns None if the variable doesn't exist or if an + error (reported) occured. + + May raise exception if the variable name is invalid or something + unexpected happens. + """ + sFull = self.__persistentVarCalcName(sVar); + if not os.path.exists(sFull): + return None; + try: + with open(sFull, 'r') as oFile: # pylint: disable=unspecified-encoding + sValue = oFile.read().decode('utf-8'); + except: + reporter.errorXcpt('Error creating "%s"' % (sFull,)); + return None; + return sValue; + + + # + # Helpers. + # + + def _killAllVBoxProcesses(self): + """ + Kills all virtual box related processes we find in the system. + """ + sHostOs = utils.getHostOs(); + asDebuggers = [ 'cdb', 'windbg', ] if sHostOs == 'windows' else [ 'gdb', 'gdb-i386-apple-darwin', 'lldb' ]; + + for iIteration in range(22): + # Gather processes to kill. + aoTodo = []; + aoDebuggers = []; + for oProcess in utils.processListAll(): + sBase = oProcess.getBaseImageNameNoExeSuff(); + if sBase is None: + continue; + sBase = sBase.lower(); + if sBase in [ 'vboxsvc', 'vboxsds', 'virtualbox', 'virtualboxvm', 'vboxheadless', 'vboxmanage', 'vboxsdl', + 'vboxwebsrv', 'vboxautostart', 'vboxballoonctrl', 'vboxbfe', 'vboxextpackhelperapp', 'vboxnetdhcp', + 'vboxnetnat', 'vboxnetadpctl', 'vboxtestogl', 'vboxtunctl', 'vboxvmmpreload', 'vboxxpcomipcd', ]: + aoTodo.append(oProcess); + if sBase.startswith('virtualbox-') and sBase.endswith('-multiarch.exe'): + aoTodo.append(oProcess); + if sBase in asDebuggers: + aoDebuggers.append(oProcess); + if iIteration in [0, 21]: + reporter.log('Warning: debugger running: %s (%s %s)' % (oProcess.iPid, sBase, oProcess.asArgs)); + if not aoTodo: + return True; + + # Are any of the debugger processes hooked up to a VBox process? + if sHostOs == 'windows': + # On demand debugging windows: windbg -p <decimal-pid> -e <decimal-event> -g + for oDebugger in aoDebuggers: + for oProcess in aoTodo: + # The whole command line is asArgs[0] here. Fix if that changes. + if oDebugger.asArgs and oDebugger.asArgs[0].find('-p %s ' % (oProcess.iPid,)) >= 0: + aoTodo.append(oDebugger); + break; + else: + for oDebugger in aoDebuggers: + for oProcess in aoTodo: + # Simplistic approach: Just check for argument equaling our pid. + if oDebugger.asArgs and ('%s' % oProcess.iPid) in oDebugger.asArgs: + aoTodo.append(oDebugger); + break; + + # Kill. + for oProcess in aoTodo: + reporter.log('Loop #%d - Killing %s (%s, uid=%s)' + % ( iIteration, oProcess.iPid, oProcess.sImage if oProcess.sName is None else oProcess.sName, + oProcess.iUid, )); + if not utils.processKill(oProcess.iPid) \ + and sHostOs != 'windows' \ + and utils.processExists(oProcess.iPid): + # Many of the vbox processes are initially set-uid-to-root and associated debuggers are running + # via sudo, so we might not be able to kill them unless we sudo and use /bin/kill. + try: utils.sudoProcessCall(['/bin/kill', '-9', '%s' % (oProcess.iPid,)]); + except: reporter.logXcpt(); + + # Check if they're all dead like they should be. + time.sleep(0.1); + for oProcess in aoTodo: + if utils.processExists(oProcess.iPid): + time.sleep(2); + break; + + return False; + + def _executeSync(self, asArgs, fMaySkip = False): + """ + Executes a child process synchronously. + + Returns True if the process executed successfully and returned 0. + Returns None if fMaySkip is true and the child exits with RTEXITCODE_SKIPPED. + Returns False for all other cases. + """ + reporter.log('Executing: %s' % (asArgs, )); + reporter.flushall(); + try: + iRc = utils.processCall(asArgs, shell = False, close_fds = False); + except: + reporter.errorXcpt(); + return False; + reporter.log('Exit code: %s (%s)' % (iRc, asArgs)); + if fMaySkip and iRc == rtexitcode.RTEXITCODE_SKIPPED: + return None; + return iRc == 0; + + def _sudoExecuteSync(self, asArgs): + """ + Executes a sudo child process synchronously. + Returns a tuple [True, 0] if the process executed successfully + and returned 0, otherwise [False, rc] is returned. + """ + reporter.log('Executing [sudo]: %s' % (asArgs, )); + reporter.flushall(); + iRc = 0; + try: + iRc = utils.sudoProcessCall(asArgs, shell = False, close_fds = False); + except: + reporter.errorXcpt(); + return (False, 0); + reporter.log('Exit code [sudo]: %s (%s)' % (iRc, asArgs)); + return (iRc == 0, iRc); + + def _findASanLibsForASanBuild(self): + """ + Returns a list of (address) santizier related libraries to preload + when launching the sub driver. + Returns empty list for non-asan builds or on platforms where this isn't needed. + """ + # Note! We include libasan.so.X in the VBoxAll tarball for asan builds, so we + # can use its presence both to detect an 'asan' build and to return it. + # Only the libasan.so.X library needs preloading at present. + if self.sHost in ('linux',): + sLibASan = self._findFile(r'libasan\.so\..*'); + if sLibASan: + return [sLibASan,]; + return []; + + def _executeSubDriver(self, asActions, fMaySkip = True, fPreloadASan = True): + """ + Execute the sub testdriver with the specified action. + """ + asArgs = list(self._asSubDriver) + asArgs.append('--no-wipe-clean'); + asArgs.extend(asActions); + + asASanLibs = []; + if fPreloadASan: + asASanLibs = self._findASanLibsForASanBuild(); + if asASanLibs: + os.environ['LD_PRELOAD'] = ':'.join(asASanLibs); + os.environ['LSAN_OPTIONS'] = 'detect_leaks=0'; # We don't want python leaks. vbox.py disables this. + + # Because of https://github.com/google/sanitizers/issues/856 we must try use setarch to disable + # address space randomization. + + reporter.log('LD_PRELOAD...') + if utils.getHostArch() == 'amd64': + sSetArch = utils.whichProgram('setarch'); + reporter.log('sSetArch=%s' % (sSetArch,)); + if sSetArch: + asArgs = [ sSetArch, 'x86_64', '-R', sys.executable ] + asArgs; + reporter.log('asArgs=%s' % (asArgs,)); + + rc = self._executeSync(asArgs, fMaySkip = fMaySkip); + + del os.environ['LSAN_OPTIONS']; + del os.environ['LD_PRELOAD']; + return rc; + + return self._executeSync(asArgs, fMaySkip = fMaySkip); + + def _maybeUnpackArchive(self, sMaybeArchive, fNonFatal = False): + """ + Attempts to unpack the given build file. + Updates _asBuildFiles. + Returns True/False. No exceptions. + """ + def unpackFilter(sMember): + # type: (string) -> bool + """ Skips debug info. """ + sLower = sMember.lower(); + if sLower.endswith('.pdb'): + return False; + return True; + + asMembers = utils.unpackFile(sMaybeArchive, self.sScratchPath, reporter.log, + reporter.log if fNonFatal else reporter.error, + fnFilter = unpackFilter); + if asMembers is None: + return False; + self._asBuildFiles.extend(asMembers); + return True; + + + def _installVBox(self): + """ + Download / copy the build files into the scratch area and install them. + """ + reporter.testStart('Installing VirtualBox'); + reporter.log('CWD=%s' % (os.getcwd(),)); # curious + + # + # Download the build files. + # + for i, sBuildUrl in enumerate(self._asBuildUrls): + if webutils.downloadFile(sBuildUrl, self._asBuildFiles[i], self.sBuildPath, reporter.log, reporter.log) is not True: + reporter.testDone(fSkipped = True); + return None; # Failed to get binaries, probably deleted. Skip the test run. + + # + # Unpack anything we know what is and append it to the build files + # list. This allows us to use VBoxAll*.tar.gz files. + # + for sFile in list(self._asBuildFiles): # Note! We copy the list as _maybeUnpackArchive updates it. + if self._maybeUnpackArchive(sFile, fNonFatal = True) is not True: + reporter.testDone(fSkipped = True); + return None; # Failed to unpack. Probably local error, like busy + # DLLs on windows, no reason for failing the build. + self._fUnpackedBuildFiles = True; + + # + # Go to system specific installation code. + # + sHost = utils.getHostOs() + if sHost == 'darwin': fRc = self._installVBoxOnDarwin(); + elif sHost == 'linux': fRc = self._installVBoxOnLinux(); + elif sHost == 'solaris': fRc = self._installVBoxOnSolaris(); + elif sHost == 'win': fRc = self._installVBoxOnWindows(); + else: + reporter.error('Unsupported host "%s".' % (sHost,)); + if fRc is False: + reporter.testFailure('Installation error.'); + elif fRc is not True: + reporter.log('Seems installation was skipped. Old version lurking behind? Not the fault of this build/test run!'); + + # + # Install the extension pack. + # + if fRc is True and self._fAutoInstallPuelExtPack: + fRc = self._installExtPack(); + if fRc is False: + reporter.testFailure('Extension pack installation error.'); + + # Some debugging... + try: + cMbFreeSpace = utils.getDiskUsage(self.sScratchPath); + reporter.log('Disk usage after VBox install: %d MB available at %s' % (cMbFreeSpace, self.sScratchPath,)); + except: + reporter.logXcpt('Unable to get disk free space. Ignored. Continuing.'); + + reporter.testDone(fRc is None); + return fRc; + + def _uninstallVBox(self, fIgnoreError = False): + """ + Uninstall VirtualBox. + """ + reporter.testStart('Uninstalling VirtualBox'); + + sHost = utils.getHostOs() + if sHost == 'darwin': fRc = self._uninstallVBoxOnDarwin(); + elif sHost == 'linux': fRc = self._uninstallVBoxOnLinux(); + elif sHost == 'solaris': fRc = self._uninstallVBoxOnSolaris(True); + elif sHost == 'win': fRc = self._uninstallVBoxOnWindows('uninstall'); + else: + reporter.error('Unsupported host "%s".' % (sHost,)); + if fRc is False and not fIgnoreError: + reporter.testFailure('Uninstallation failed.'); + + fRc2 = self._uninstallAllExtPacks(); + if not fRc2 and fRc: + fRc = fRc2; + + reporter.testDone(fSkipped = (fRc is None)); + return fRc; + + def _findFile(self, sRegExp, fMandatory = False): + """ + Returns the first build file that matches the given regular expression + (basename only). + + Returns None if no match was found, logging it as an error if + fMandatory is set. + """ + oRegExp = re.compile(sRegExp); + + reporter.log('_findFile: %s' % (sRegExp,)); + for sFile in self._asBuildFiles: + if oRegExp.match(os.path.basename(sFile)) and os.path.exists(sFile): + return sFile; + + # If we didn't unpack the build files, search all the files in the scratch area: + if not self._fUnpackedBuildFiles: + for sDir, _, asFiles in os.walk(self.sScratchPath): + for sFile in asFiles: + #reporter.log('_findFile: considering %s' % (sFile,)); + if oRegExp.match(sFile): + return os.path.join(sDir, sFile); + + if fMandatory: + reporter.error('Failed to find a file matching "%s" in %s.' % (sRegExp, self._asBuildFiles,)); + return None; + + def _waitForTestManagerConnectivity(self, cSecTimeout): + """ + Check and wait for network connectivity to the test manager. + + This is used with the windows installation and uninstallation since + these usually disrupts network connectivity when installing the filter + driver. If we proceed to quickly, we might finish the test at a time + when we cannot report to the test manager and thus end up with an + abandonded test error. + """ + cSecElapsed = 0; + secStart = utils.timestampSecond(); + while reporter.checkTestManagerConnection() is False: + cSecElapsed = utils.timestampSecond() - secStart; + if cSecElapsed >= cSecTimeout: + reporter.log('_waitForTestManagerConnectivity: Giving up after %u secs.' % (cSecTimeout,)); + return False; + time.sleep(2); + + if cSecElapsed > 0: + reporter.log('_waitForTestManagerConnectivity: Waited %s secs.' % (cSecTimeout,)); + return True; + + + # + # Darwin (Mac OS X). + # + + def _darwinDmgPath(self): + """ Returns the path to the DMG mount.""" + return os.path.join(self.sScratchPath, 'DmgMountPoint'); + + def _darwinUnmountDmg(self, fIgnoreError): + """ + Umount any DMG on at the default mount point. + """ + sMountPath = self._darwinDmgPath(); + if not os.path.exists(sMountPath): + return True; + + # Unmount. + fRc = self._executeSync(['hdiutil', 'detach', sMountPath ]); + if not fRc and not fIgnoreError: + # In case it's busy for some reason or another, just retry after a little delay. + for iTry in range(6): + time.sleep(5); + reporter.error('Retry #%s unmount DMT at %s' % (iTry + 1, sMountPath,)); + fRc = self._executeSync(['hdiutil', 'detach', sMountPath ]); + if fRc: + break; + if not fRc: + reporter.error('Failed to unmount DMG at %s' % (sMountPath,)); + + # Remove dir. + try: + os.rmdir(sMountPath); + except: + if not fIgnoreError: + reporter.errorXcpt('Failed to remove directory %s' % (sMountPath,)); + return fRc; + + def _darwinMountDmg(self, sDmg): + """ + Mount the DMG at the default mount point. + """ + self._darwinUnmountDmg(fIgnoreError = True) + + sMountPath = self._darwinDmgPath(); + if not os.path.exists(sMountPath): + try: + os.mkdir(sMountPath, 0o755); + except: + reporter.logXcpt(); + return False; + + return self._executeSync(['hdiutil', 'attach', '-readonly', '-mount', 'required', '-mountpoint', sMountPath, sDmg, ]); + + def _generateWithoutKextsChoicesXmlOnDarwin(self): + """ + Generates the choices XML when kernel drivers are disabled. + None is returned on failure. + """ + sPath = os.path.join(self.sScratchPath, 'DarwinChoices.xml'); + oFile = utils.openNoInherit(sPath, 'wt'); + oFile.write('<?xml version="1.0" encoding="UTF-8"?>\n' + '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n' + '<plist version="1.0">\n' + '<array>\n' + ' <dict>\n' + ' <key>attributeSetting</key>\n' + ' <integer>0</integer>\n' + ' <key>choiceAttribute</key>\n' + ' <string>selected</string>\n' + ' <key>choiceIdentifier</key>\n' + ' <string>choiceVBoxKEXTs</string>\n' + ' </dict>\n' + '</array>\n' + '</plist>\n'); + oFile.close(); + return sPath; + + def _installVBoxOnDarwin(self): + """ Installs VBox on Mac OS X.""" + + # TEMPORARY HACK - START + # Don't install the kernel drivers on the testboxes with BigSur and later + # Needs a more generic approach but that one needs more effort. + sHostName = socket.getfqdn(); + if sHostName.startswith('testboxmac10') \ + or sHostName.startswith('testboxmac11'): + self._fKernelDrivers = False; + # TEMPORARY HACK - END + + sDmg = self._findFile('^VirtualBox-.*\\.dmg$'); + if sDmg is None: + return False; + + # Mount the DMG. + fRc = self._darwinMountDmg(sDmg); + if fRc is not True: + return False; + + # Uninstall any previous vbox version first. + sUninstaller = os.path.join(self._darwinDmgPath(), 'VirtualBox_Uninstall.tool'); + fRc, _ = self._sudoExecuteSync([sUninstaller, '--unattended',]); + if fRc is True: + + # Install the package. + sPkg = os.path.join(self._darwinDmgPath(), 'VirtualBox.pkg'); + if self._fKernelDrivers: + fRc, _ = self._sudoExecuteSync(['installer', '-verbose', '-dumplog', '-pkg', sPkg, '-target', '/']); + else: + sChoicesXml = self._generateWithoutKextsChoicesXmlOnDarwin(); + if sChoicesXml is not None: + fRc, _ = self._sudoExecuteSync(['installer', '-verbose', '-dumplog', '-pkg', sPkg, \ + '-applyChoiceChangesXML', sChoicesXml, '-target', '/']); + else: + fRc = False; + + # Unmount the DMG and we're done. + if not self._darwinUnmountDmg(fIgnoreError = False): + fRc = False; + return fRc; + + def _uninstallVBoxOnDarwin(self): + """ Uninstalls VBox on Mac OS X.""" + + # Is VirtualBox installed? If not, don't try uninstall it. + sVBox = self._getVBoxInstallPath(fFailIfNotFound = False); + if sVBox is None: + return True; + + # Find the dmg. + sDmg = self._findFile('^VirtualBox-.*\\.dmg$'); + if sDmg is None: + return False; + if not os.path.exists(sDmg): + return True; + + # Mount the DMG. + fRc = self._darwinMountDmg(sDmg); + if fRc is not True: + return False; + + # Execute the uninstaller. + sUninstaller = os.path.join(self._darwinDmgPath(), 'VirtualBox_Uninstall.tool'); + fRc, _ = self._sudoExecuteSync([sUninstaller, '--unattended',]); + + # Unmount the DMG and we're done. + if not self._darwinUnmountDmg(fIgnoreError = False): + fRc = False; + return fRc; + + # + # GNU/Linux + # + + def _installVBoxOnLinux(self): + """ Installs VBox on Linux.""" + sRun = self._findFile('^VirtualBox-.*\\.run$'); + if sRun is None: + return False; + utils.chmodPlusX(sRun); + + # Install the new one. + fRc, _ = self._sudoExecuteSync([sRun,]); + return fRc; + + def _uninstallVBoxOnLinux(self): + """ Uninstalls VBox on Linux.""" + + # Is VirtualBox installed? If not, don't try uninstall it. + sVBox = self._getVBoxInstallPath(fFailIfNotFound = False); + if sVBox is None: + return True; + + # Find the .run file and use it. + sRun = self._findFile('^VirtualBox-.*\\.run$', fMandatory = False); + if sRun is not None: + utils.chmodPlusX(sRun); + fRc, _ = self._sudoExecuteSync([sRun, 'uninstall']); + return fRc; + + # Try the installed uninstaller. + for sUninstaller in [os.path.join(sVBox, 'uninstall.sh'), '/opt/VirtualBox/uninstall.sh', ]: + if os.path.isfile(sUninstaller): + reporter.log('Invoking "%s"...' % (sUninstaller,)); + fRc, _ = self._sudoExecuteSync([sUninstaller, 'uninstall']); + return fRc; + + reporter.log('Did not find any VirtualBox install to uninstall.'); + return True; + + + # + # Solaris + # + + def _generateAutoResponseOnSolaris(self): + """ + Generates an autoresponse file on solaris, returning the name. + None is return on failure. + """ + sPath = os.path.join(self.sScratchPath, 'SolarisAutoResponse'); + oFile = utils.openNoInherit(sPath, 'wt'); + oFile.write('basedir=default\n' + 'runlevel=nocheck\n' + 'conflict=quit\n' + 'setuid=nocheck\n' + 'action=nocheck\n' + 'partial=quit\n' + 'instance=unique\n' + 'idepend=quit\n' + 'rdepend=quit\n' + 'space=quit\n' + 'mail=\n'); + oFile.close(); + return sPath; + + def _installVBoxOnSolaris(self): + """ Installs VBox on Solaris.""" + sPkg = self._findFile('^VirtualBox-.*\\.pkg$', fMandatory = False); + if sPkg is None: + sTar = self._findFile('^VirtualBox-.*-SunOS-.*\\.tar.gz$', fMandatory = False); + if sTar is not None: + if self._maybeUnpackArchive(sTar) is not True: + return False; + sPkg = self._findFile('^VirtualBox-.*\\.pkg$', fMandatory = True); + sRsp = self._findFile('^autoresponse$', fMandatory = True); + if sPkg is None or sRsp is None: + return False; + + # Uninstall first (ignore result). + self._uninstallVBoxOnSolaris(False); + + # Install the new one. + fRc, _ = self._sudoExecuteSync(['pkgadd', '-d', sPkg, '-n', '-a', sRsp, 'SUNWvbox']); + return fRc; + + def _uninstallVBoxOnSolaris(self, fRestartSvcConfigD): + """ Uninstalls VBox on Solaris.""" + reporter.flushall(); + if utils.processCall(['pkginfo', '-q', 'SUNWvbox']) != 0: + return True; + sRsp = self._generateAutoResponseOnSolaris(); + fRc, _ = self._sudoExecuteSync(['pkgrm', '-n', '-a', sRsp, 'SUNWvbox']); + + # + # Restart the svc.configd as it has a tendency to clog up with time and + # become unresponsive. It will handle SIGHUP by exiting the sigwait() + # look in the main function and shut down the service nicely (backend_fini). + # The restarter will then start a new instance of it. + # + if fRestartSvcConfigD: + time.sleep(1); # Give it a chance to flush pkgrm stuff. + self._sudoExecuteSync(['pkill', '-HUP', 'svc.configd']); + time.sleep(5); # Spare a few cpu cycles it to shutdown and restart. + + return fRc; + + # + # Windows + # + + ## VBox windows services we can query the status of. + kasWindowsServices = [ 'vboxsup', 'vboxusbmon', 'vboxnetadp', 'vboxnetflt', 'vboxnetlwf' ]; + + def _installVBoxOnWindows(self): + """ Installs VBox on Windows.""" + sExe = self._findFile('^VirtualBox-.*-(MultiArch|Win).exe$'); + if sExe is None: + return False; + + # TEMPORARY HACK - START + # It seems that running the NDIS cleanup script upon uninstallation is not + # a good idea, so let's run it before installing VirtualBox. + #sHostName = socket.getfqdn(); + #if not sHostName.startswith('testboxwin3') \ + # and not sHostName.startswith('testboxharp2') \ + # and not sHostName.startswith('wei01-b6ka-3') \ + # and utils.getHostOsVersion() in ['8', '8.1', '9', '2008Server', '2008ServerR2', '2012Server']: + # reporter.log('Peforming extra NDIS cleanup...'); + # sMagicScript = os.path.abspath(os.path.join(g_ksValidationKitDir, 'testdriver', 'win-vbox-net-uninstall.ps1')); + # fRc2, _ = self._sudoExecuteSync(['powershell.exe', '-Command', 'set-executionpolicy unrestricted']); + # if not fRc2: + # reporter.log('set-executionpolicy failed.'); + # self._sudoExecuteSync(['powershell.exe', '-Command', 'get-executionpolicy']); + # fRc2, _ = self._sudoExecuteSync(['powershell.exe', '-File', sMagicScript]); + # if not fRc2: + # reporter.log('NDIS cleanup failed.'); + # TEMPORARY HACK - END + + # Uninstall any previous vbox version first. + fRc = self._uninstallVBoxOnWindows('install'); + if fRc is not True: + return None; # There shouldn't be anything to uninstall, and if there is, it's not our fault. + + # Install the MS Visual Studio Redistributable, if requested. (VBox 7.0+ needs this installed once.) + if self._fInstallMsCrt: + reporter.log('Installing MS Visual Studio Redistributable (untested code)...'); + ## @todo Test this. + ## @todo We could cache this on the testrsrc share. + sName = "vc_redist.x64.exe" + sUrl = "https://aka.ms/vs/17/release/" + sName # Permalink, according to MS. + sExe = os.path.join(self.sBuildPath, sName); + if webutils.downloadFile(sUrl, sExe, None, reporter.log, reporter.log): + asArgs = [ sExe, '/Q' ]; + fRc2, iRc = self._sudoExecuteSync(asArgs); + if fRc2 is False: + return reporter.error('Installing MS Visual Studio Redistributable failed, exit code: %s' % (iRc,)); + reporter.log('Installing MS Visual Studio Redistributable done'); + else: + return False; + + # We need the help text to detect supported options below. + reporter.log('Executing: %s' % ([sExe, '--silent', '--help'], )); + reporter.flushall(); + (iExitCode, sHelp, _) = utils.processOutputUnchecked([sExe, '--silent', '--help'], fIgnoreEncoding = True); + reporter.log('Exit code: %d, %u chars of help text' % (iExitCode, len(sHelp),)); + + # Gather installer arguments. + asArgs = [sExe, '-vvvv', '--silent', '--logging']; + asArgs.extend(['--msiparams', 'REBOOT=ReallySuppress']); + sVBoxInstallPath = os.environ.get('VBOX_INSTALL_PATH', None); + if sVBoxInstallPath is not None: + asArgs.extend(['INSTALLDIR="%s"' % (sVBoxInstallPath,)]); + + if sHelp.find("--msi-log-file") >= 0: + sLogFile = os.path.join(self.sScratchPath, 'VBoxInstallLog.txt'); # Specify location to prevent a random one. + asArgs.extend(['--msi-log-file', sLogFile]); + else: + sLogFile = os.path.join(tempfile.gettempdir(), 'VirtualBox', 'VBoxInstallLog.txt'); # Hardcoded TMP location. + + if self._fWinForcedInstallTimestampCA and sHelp.find("--force-install-timestamp-ca") >= 0: + asArgs.extend(['--force-install-timestamp-ca']); + + # Install it. + fRc2, iRc = self._sudoExecuteSync(asArgs); + if fRc2 is False: + if iRc == 3010: # ERROR_SUCCESS_REBOOT_REQUIRED + reporter.error('Installer required a reboot to complete installation (ERROR_SUCCESS_REBOOT_REQUIRED)'); + else: + reporter.error('Installer failed, exit code: %s' % (iRc,)); + fRc = False; + + # Add the installer log if present and wait for the network connection to be restore after the filter driver upset. + if os.path.isfile(sLogFile): + reporter.addLogFile(sLogFile, 'log/installer', "Verbose MSI installation log file"); + self._waitForTestManagerConnectivity(30); + + return fRc; + + def _isProcessPresent(self, sName): + """ Checks whether the named process is present or not. """ + for oProcess in utils.processListAll(): + sBase = oProcess.getBaseImageNameNoExeSuff(); + if sBase is not None and sBase.lower() == sName: + return True; + return False; + + def _killProcessesByName(self, sName, sDesc, fChildren = False): + """ Kills the named process, optionally including children. """ + cKilled = 0; + aoProcesses = utils.processListAll(); + for oProcess in aoProcesses: + sBase = oProcess.getBaseImageNameNoExeSuff(); + if sBase is not None and sBase.lower() == sName: + reporter.log('Killing %s process: %s (%s)' % (sDesc, oProcess.iPid, sBase)); + utils.processKill(oProcess.iPid); + cKilled += 1; + + if fChildren: + for oChild in aoProcesses: + if oChild.iParentPid == oProcess.iPid and oChild.iParentPid is not None: + reporter.log('Killing %s child process: %s (%s)' % (sDesc, oChild.iPid, sBase)); + utils.processKill(oChild.iPid); + cKilled += 1; + return cKilled; + + def _terminateProcessesByNameAndArgSubstr(self, sName, sArg, sDesc): + """ + Terminates the named process using taskkill.exe, if any of its args + contains the passed string. + """ + cKilled = 0; + aoProcesses = utils.processListAll(); + for oProcess in aoProcesses: + sBase = oProcess.getBaseImageNameNoExeSuff(); + if sBase is not None and sBase.lower() == sName and any(sArg in s for s in oProcess.asArgs): + + reporter.log('Killing %s process: %s (%s)' % (sDesc, oProcess.iPid, sBase)); + self._executeSync(['taskkill.exe', '/pid', '%u' % (oProcess.iPid,)]); + cKilled += 1; + return cKilled; + + def _uninstallVBoxOnWindows(self, sMode): + """ + Uninstalls VBox on Windows, all installations we find to be on the safe side... + """ + assert sMode in ['install', 'uninstall',]; + + import win32com.client; # pylint: disable=import-error + win32com.client.gencache.EnsureModule('{000C1092-0000-0000-C000-000000000046}', 1033, 1, 0); + oInstaller = win32com.client.Dispatch('WindowsInstaller.Installer', + resultCLSID = '{000C1090-0000-0000-C000-000000000046}') + + # Search installed products for VirtualBox. + asProdCodes = []; + for sProdCode in oInstaller.Products: + try: + sProdName = oInstaller.ProductInfo(sProdCode, "ProductName"); + except: + reporter.logXcpt(); + continue; + #reporter.log('Info: %s=%s' % (sProdCode, sProdName)); + if sProdName.startswith('Oracle VM VirtualBox') \ + or sProdName.startswith('Sun VirtualBox'): + asProdCodes.append([sProdCode, sProdName]); + + # Before we start uninstalling anything, just ruthlessly kill any cdb, + # msiexec, drvinst and some rundll process we might find hanging around. + if self._isProcessPresent('rundll32'): + cTimes = 0; + while cTimes < 3: + cTimes += 1; + cKilled = self._terminateProcessesByNameAndArgSubstr('rundll32', 'InstallSecurityPromptRunDllW', + 'MSI driver installation'); + if cKilled <= 0: + break; + time.sleep(10); # Give related drvinst process a chance to clean up after we killed the verification dialog. + + if self._isProcessPresent('drvinst'): + time.sleep(15); # In the hope that it goes away. + cTimes = 0; + while cTimes < 4: + cTimes += 1; + cKilled = self._killProcessesByName('drvinst', 'MSI driver installation', True); + if cKilled <= 0: + break; + time.sleep(10); # Give related MSI process a chance to clean up after we killed the driver installer. + + if self._isProcessPresent('msiexec'): + cTimes = 0; + while cTimes < 3: + reporter.log('found running msiexec process, waiting a bit...'); + time.sleep(20) # In the hope that it goes away. + if not self._isProcessPresent('msiexec'): + break; + cTimes += 1; + ## @todo this could also be the msiexec system service, try to detect this case! + if cTimes >= 6: + cKilled = self._killProcessesByName('msiexec', 'MSI driver installation'); + if cKilled > 0: + time.sleep(16); # fudge. + + # cdb.exe sometimes stays running (from utils.getProcessInfo), blocking + # the scratch directory. No idea why. + if self._isProcessPresent('cdb'): + cTimes = 0; + while cTimes < 3: + cKilled = self._killProcessesByName('cdb', 'cdb.exe from getProcessInfo'); + if cKilled <= 0: + break; + time.sleep(2); # fudge. + + # Do the uninstalling. + fRc = True; + sLogFile = os.path.join(self.sScratchPath, 'VBoxUninstallLog.txt'); + for sProdCode, sProdName in asProdCodes: + reporter.log('Uninstalling %s (%s)...' % (sProdName, sProdCode)); + fRc2, iRc = self._sudoExecuteSync(['msiexec', '/uninstall', sProdCode, '/quiet', '/passive', '/norestart', + '/L*v', '%s' % (sLogFile), ]); + if fRc2 is False: + if iRc == 3010: # ERROR_SUCCESS_REBOOT_REQUIRED + reporter.error('Uninstaller required a reboot to complete uninstallation'); + else: + reporter.error('Uninstaller failed, exit code: %s' % (iRc,)); + fRc = False; + + self._waitForTestManagerConnectivity(30); + + # Upload the log on failure. Do it early if the extra cleanups below causes trouble. + if fRc is False and os.path.isfile(sLogFile): + reporter.addLogFile(sLogFile, 'log/uninstaller', "Verbose MSI uninstallation log file"); + sLogFile = None; + + # Log driver service states (should ls \Driver\VBox* and \Device\VBox*). + fHadLeftovers = False; + asLeftovers = []; + for sService in reversed(self.kasWindowsServices): + cTries = 0; + while True: + fRc2, _ = self._sudoExecuteSync(['sc.exe', 'query', sService]); + if not fRc2: + break; + fHadLeftovers = True; + + cTries += 1; + if cTries > 3: + asLeftovers.append(sService,); + break; + + # Get the status output. + try: + sOutput = utils.sudoProcessOutputChecked(['sc.exe', 'query', sService]); + except: + reporter.logXcpt(); + else: + if re.search(r'STATE\s+:\s*1\s*STOPPED', sOutput) is None: + reporter.log('Trying to stop %s...' % (sService,)); + fRc2, _ = self._sudoExecuteSync(['sc.exe', 'stop', sService]); + time.sleep(1); # fudge + + reporter.log('Trying to delete %s...' % (sService,)); + self._sudoExecuteSync(['sc.exe', 'delete', sService]); + + time.sleep(1); # fudge + + if asLeftovers: + reporter.log('Warning! Leftover VBox drivers: %s' % (', '.join(asLeftovers),)); + fRc = False; + + if fHadLeftovers: + self._waitForTestManagerConnectivity(30); + + # Upload the log if we have any leftovers and didn't upload it already. + if sLogFile is not None and (fRc is False or fHadLeftovers) and os.path.isfile(sLogFile): + reporter.addLogFile(sLogFile, 'log/uninstaller', "Verbose MSI uninstallation log file"); + + return fRc; + + + # + # Extension pack. + # + + def _getVBoxInstallPath(self, fFailIfNotFound): + """ Returns the default VBox installation path. """ + sHost = utils.getHostOs(); + if sHost == 'win': + sProgFiles = os.environ.get('ProgramFiles', 'C:\\Program Files'); + asLocs = [ + os.path.join(sProgFiles, 'Oracle', 'VirtualBox'), + os.path.join(sProgFiles, 'OracleVM', 'VirtualBox'), + os.path.join(sProgFiles, 'Sun', 'VirtualBox'), + ]; + elif sHost in ('linux', 'solaris',): + asLocs = [ '/opt/VirtualBox', '/opt/VirtualBox-3.2', '/opt/VirtualBox-3.1', '/opt/VirtualBox-3.0']; + elif sHost == 'darwin': + asLocs = [ '/Applications/VirtualBox.app/Contents/MacOS' ]; + else: + asLocs = [ '/opt/VirtualBox' ]; + if 'VBOX_INSTALL_PATH' in os.environ: + asLocs.insert(0, os.environ.get('VBOX_INSTALL_PATH', None)); + + for sLoc in asLocs: + if os.path.isdir(sLoc): + return sLoc; + if fFailIfNotFound: + reporter.error('Failed to locate VirtualBox installation: %s' % (asLocs,)); + else: + reporter.log2('Failed to locate VirtualBox installation: %s' % (asLocs,)); + return None; + + def _installExtPack(self): + """ Installs the extension pack. """ + sVBox = self._getVBoxInstallPath(fFailIfNotFound = True); + if sVBox is None: + return False; + sExtPackDir = os.path.join(sVBox, 'ExtensionPacks'); + + if self._uninstallAllExtPacks() is not True: + return False; + + sExtPack = self._findFile('Oracle_VM_VirtualBox_Extension_Pack.vbox-extpack'); + if sExtPack is None: + sExtPack = self._findFile('Oracle_VM_VirtualBox_Extension_Pack.*.vbox-extpack'); + if sExtPack is None: + return True; + + sDstDir = os.path.join(sExtPackDir, 'Oracle_VM_VirtualBox_Extension_Pack'); + reporter.log('Installing extension pack "%s" to "%s"...' % (sExtPack, sExtPackDir)); + fRc, _ = self._sudoExecuteSync([ self.getBinTool('vts_tar'), + '--extract', + '--verbose', + '--gzip', + '--file', sExtPack, + '--directory', sDstDir, + '--file-mode-and-mask', '0644', + '--file-mode-or-mask', '0644', + '--dir-mode-and-mask', '0755', + '--dir-mode-or-mask', '0755', + '--owner', '0', + '--group', '0', + ]); + return fRc; + + def _uninstallAllExtPacks(self): + """ Uninstalls all extension packs. """ + sVBox = self._getVBoxInstallPath(fFailIfNotFound = False); + if sVBox is None: + return True; + + sExtPackDir = os.path.join(sVBox, 'ExtensionPacks'); + if not os.path.exists(sExtPackDir): + return True; + + fRc, _ = self._sudoExecuteSync([self.getBinTool('vts_rm'), '-Rfv', '--', sExtPackDir]); + return fRc; + + + +if __name__ == '__main__': + sys.exit(VBoxInstallerTestDriver().main(sys.argv)); diff --git a/src/VBox/ValidationKit/testdriver/vboxtestfileset.py b/src/VBox/ValidationKit/testdriver/vboxtestfileset.py new file mode 100755 index 00000000..96ad0920 --- /dev/null +++ b/src/VBox/ValidationKit/testdriver/vboxtestfileset.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- +# $Id: vboxtestfileset.py $ +# pylint: disable=too-many-lines + +""" +Test File Set +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os; +import sys; + +# Validation Kit imports. +from common import utils; +from testdriver import reporter; +from testdriver import testfileset; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + xrange = range; # pylint: disable=redefined-builtin,invalid-name + + +class TestFileSet(testfileset.TestFileSet): + """ + A generated set of files and directories for uploading to a VM. + + The file and directory names are compatible with the host, so it is + possible to copy them to the host without changing any names. + + Uploaded as a tarball and expanded via TXS (if new enough) or uploaded vts_tar + utility from the validation kit. + """ + + def __init__(self, oTestVm, sBasePath, sSubDir, # pylint: disable=too-many-arguments + oRngFileSizes = xrange(0, 16384), + oRngManyFiles = xrange(128, 512), + oRngTreeFiles = xrange(128, 384), + oRngTreeDepth = xrange(92, 256), + oRngTreeDirs = xrange(2, 16), + cchMaxPath = 230, + cchMaxName = 230, + asCompatibleWith = None, + uSeed = None): + + asCompOses = [oTestVm.getGuestOs(), ]; + sHostOs = utils.getHostOs(); + if sHostOs not in asCompOses: + asCompOses.append(sHostOs); + + testfileset.TestFileSet.__init__(self, + fDosStyle = oTestVm.isWindows() or oTestVm.isOS2(), + asCompatibleWith = asCompOses, + sBasePath = sBasePath, + sSubDir = sSubDir, + oRngFileSizes = oRngFileSizes, + oRngManyFiles = oRngManyFiles, + oRngTreeFiles = oRngTreeFiles, + oRngTreeDepth = oRngTreeDepth, + oRngTreeDirs = oRngTreeDirs, + cchMaxPath = cchMaxPath, + cchMaxName = cchMaxName, + uSeed = uSeed); + self.oTestVm = oTestVm; + + def __uploadFallback(self, oTxsSession, sTarFileGst, oTstDrv): + """ + Fallback upload method. + """ + sVtsTarExe = 'vts_tar' + self.oTestVm.getGuestExeSuff(); + sVtsTarHst = os.path.join(oTstDrv.sVBoxValidationKit, self.oTestVm.getGuestOs(), + self.oTestVm.getGuestArch(), sVtsTarExe); + sVtsTarGst = self.oTestVm.pathJoin(self.sBasePath, sVtsTarExe); + + if oTxsSession.syncUploadFile(sVtsTarHst, sVtsTarGst) is not True: + return reporter.error('Failed to upload "%s" to the guest as "%s"!' % (sVtsTarHst, sVtsTarGst,)); + + fRc = oTxsSession.syncExec(sVtsTarGst, [sVtsTarGst, '-xzf', sTarFileGst, '-C', self.sBasePath,], fWithTestPipe = False); + if fRc is not True: + return reporter.error('vts_tar failed!'); + return True; + + def upload(self, oTxsSession, oTstDrv): + """ + Uploads the files into the guest via the given TXS session. + + Returns True / False. + """ + + # + # Create a tarball. + # + sTarFileHst = os.path.join(oTstDrv.sScratchPath, 'tdAddGuestCtrl-1-Stuff.tar.gz'); + sTarFileGst = self.oTestVm.pathJoin(self.sBasePath, 'tdAddGuestCtrl-1-Stuff.tar.gz'); + if self.createTarball(sTarFileHst) is not True: + return False; + + # + # Upload it. + # + reporter.log('Uploading tarball "%s" to the guest as "%s"...' % (sTarFileHst, sTarFileGst)); + if oTxsSession.syncUploadFile(sTarFileHst, sTarFileGst) is not True: + return reporter.error('Failed upload tarball "%s" as "%s"!' % (sTarFileHst, sTarFileGst,)); + + # + # Try unpack it. + # + reporter.log('Unpacking "%s" into "%s"...' % (sTarFileGst, self.sBasePath)); + if oTxsSession.syncUnpackFile(sTarFileGst, self.sBasePath, fIgnoreErrors = True) is not True: + reporter.log('Failed to expand tarball "%s" into "%s", falling back on individual directory and file creation...' + % (sTarFileGst, self.sBasePath,)); + if self.__uploadFallback(oTxsSession, sTarFileGst, oTstDrv) is not True: + return False; + reporter.log('Successfully placed test files and directories in the VM.'); + return True; + diff --git a/src/VBox/ValidationKit/testdriver/vboxtestvms.py b/src/VBox/ValidationKit/testdriver/vboxtestvms.py new file mode 100755 index 00000000..8a96f2cf --- /dev/null +++ b/src/VBox/ValidationKit/testdriver/vboxtestvms.py @@ -0,0 +1,2105 @@ +# -*- coding: utf-8 -*- +# $Id: vboxtestvms.py $ + +""" +VirtualBox Test VMs +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + +# Standard Python imports. +import copy; +import os; +import re; +import random; +import socket; +import string; +import uuid; + +# Validation Kit imports. +from common import pathutils; +from common import utils; +from testdriver import base; +from testdriver import reporter; +from testdriver import vboxcon; + + +# All virtualization modes. +g_asVirtModes = ['hwvirt', 'hwvirt-np', 'raw',]; +# All virtualization modes except for raw-mode. +g_asVirtModesNoRaw = ['hwvirt', 'hwvirt-np',]; +# Dictionary mapping the virtualization mode mnemonics to a little less cryptic +# strings used in test descriptions. +g_dsVirtModeDescs = { + 'raw' : 'Raw-mode', + 'hwvirt' : 'HwVirt', + 'hwvirt-np' : 'NestedPaging' +}; + +## @name VM grouping flags +## @{ +g_kfGrpSmoke = 0x0001; ##< Smoke test VM. +g_kfGrpStandard = 0x0002; ##< Standard test VM. +g_kfGrpStdSmoke = g_kfGrpSmoke | g_kfGrpStandard; ##< shorthand. +g_kfGrpWithGAs = 0x0004; ##< The VM has guest additions installed. +g_kfGrpNoTxs = 0x0008; ##< The VM lacks test execution service. +g_kfGrpAncient = 0x1000; ##< Ancient OS. +g_kfGrpExotic = 0x2000; ##< Exotic OS. +## @} + + +## @name Flags. +## @{ +g_k32 = 32; # pylint: disable=invalid-name +g_k64 = 64; # pylint: disable=invalid-name +g_k32_64 = 96; # pylint: disable=invalid-name +g_kiArchMask = 96; +g_kiNoRaw = 128; ##< No raw mode. +## @} + +# Array indexes. +g_iGuestOsType = 0; +g_iKind = 1; +g_iFlags = 2; +g_iMinCpu = 3; +g_iMaxCpu = 4; +g_iRegEx = 5; + +# Table translating from VM name core to a more detailed guest info. +# pylint: disable=line-too-long +## @todo what's the difference between the first two columns again? +g_aaNameToDetails = \ +[ + [ 'WindowsNT3x', 'WindowsNT3x', g_k32, 1, 32, ['nt3', 'nt3[0-9]*']], # max cpus?? + [ 'WindowsNT4', 'WindowsNT4', g_k32, 1, 32, ['nt4', 'nt4sp[0-9]']], # max cpus?? + [ 'Windows2000', 'Windows2000', g_k32, 1, 32, ['w2k', 'w2ksp[0-9]', 'win2k', 'win2ksp[0-9]']], # max cpus?? + [ 'WindowsXP', 'WindowsXP', g_k32, 1, 32, ['xp', 'xpsp[0-9]']], + [ 'WindowsXP_64', 'WindowsXP_64', g_k64, 1, 32, ['xp64', 'xp64sp[0-9]']], + [ 'Windows2003', 'Windows2003', g_k32, 1, 32, ['w2k3', 'w2k3sp[0-9]', 'win2k3', 'win2k3sp[0-9]']], + [ 'WindowsVista', 'WindowsVista', g_k32, 1, 32, ['vista', 'vistasp[0-9]']], + [ 'WindowsVista_64','WindowsVista_64', g_k64, 1, 64, ['vista-64', 'vistasp[0-9]-64',]], # max cpus/cores?? + [ 'Windows2008', 'Windows2008', g_k32, 1, 64, ['w2k8', 'w2k8sp[0-9]', 'win2k8', 'win2k8sp[0-9]']], # max cpus/cores?? + [ 'Windows2008_64', 'Windows2008_64', g_k64, 1, 64, ['w2k8r2', 'w2k8r2sp[0-9]', 'win2k8r2', 'win2k8r2sp[0-9]']], # max cpus/cores?? + [ 'Windows7', 'Windows7', g_k32, 1, 32, ['w7', 'w7sp[0-9]', 'win7',]], # max cpus/cores?? + [ 'Windows7_64', 'Windows7_64', g_k64, 1, 64, ['w7-64', 'w7sp[0-9]-64', 'win7-64',]], # max cpus/cores?? + [ 'Windows2012', 'Windows2012', g_k64, 1, 64, ['w2k12', 'w2k12sp[0-9]', 'win2k12', 'win2k12sp[0-9]',]], # max cpus/cores?? + [ 'Windows8', 'Windows8', g_k32 | g_kiNoRaw, 1, 32, ['w8', 'w8sp[0-9]', 'win8',]], # max cpus/cores?? + [ 'Windows8_64', 'Windows8_64', g_k64, 1, 64, ['w8-64', 'w8sp[0-9]-64', 'win8-64',]], # max cpus/cores?? + [ 'Windows81', 'Windows81', g_k32 | g_kiNoRaw, 1, 32, ['w81', 'w81sp[0-9]', 'win81',]], # max cpus/cores?? + [ 'Windows81_64', 'Windows81_64', g_k64, 1, 64, ['w81-64', 'w81sp[0-9]-64', 'win81-64',]], # max cpus/cores?? + [ 'Windows10', 'Windows10', g_k32 | g_kiNoRaw, 1, 32, ['w10', 'w10sp[0-9]', 'win10',]], # max cpus/cores?? + [ 'Windows10_64', 'Windows10_64', g_k64, 1, 64, ['w10-64', 'w10sp[0-9]-64', 'win10-64',]], # max cpus/cores?? + [ 'Windows2016', 'Windows2016', g_k64, 1, 64, ['w2k16', 'w2k16sp[0-9]', 'win2k16', 'win2k16sp[0-9]',]], # max cpus/cores?? + [ 'Windows2019', 'Windows2019', g_k64, 1, 64, ['w2k19', 'w2k19sp[0-9]', 'win2k19', 'win2k19sp[0-9]',]], # max cpus/cores?? + [ 'Windows2022', 'Windows2022', g_k64, 1, 64, ['w2k22', 'w2k22sp[0-9]', 'win2k22', 'win2k22sp[0-9]',]], # max cpus/cores?? + [ 'Windows11', 'Windows11', g_k64, 1, 64, ['w11', 'w11-64', 'w11sp[0-9]-64', 'win11', 'win11-64',]], # max cpus/cores?? + [ 'Linux', 'Debian', g_k32, 1, 256, ['deb[0-9]*', 'debian[0-9]*', ]], + [ 'Linux_64', 'Debian_64', g_k64, 1, 256, ['deb[0-9]*-64', 'debian[0-9]*-64', ]], + [ 'Linux', 'RedHat', g_k32, 1, 256, ['rhel', 'rhel[0-9]', 'rhel[0-9]u[0-9]']], + [ 'Linux', 'Fedora', g_k32, 1, 256, ['fedora', 'fedora[0-9]*', ]], + [ 'Linux_64', 'Fedora_64', g_k64, 1, 256, ['fedora-64', 'fedora[0-9]*-64', ]], + [ 'Linux', 'Oracle', g_k32, 1, 256, ['ols[0-9]*', 'oel[0-9]*', ]], + [ 'Linux_64', 'Oracle_64', g_k64, 1, 256, ['ols[0-9]*-64', 'oel[0-9]*-64', ]], + [ 'Linux', 'OpenSUSE', g_k32, 1, 256, ['opensuse[0-9]*', 'suse[0-9]*', ]], + [ 'Linux_64', 'OpenSUSE_64', g_k64, 1, 256, ['opensuse[0-9]*-64', 'suse[0-9]*-64', ]], + [ 'Linux', 'Ubuntu', g_k32, 1, 256, ['ubuntu[0-9]*', ]], + [ 'Linux_64', 'Ubuntu_64', g_k64, 1, 256, ['ubuntu[0-9]*-64', ]], + [ 'Linux', 'ArchLinux', g_k32, 1, 256, ['arch[0-9]*', ]], + [ 'Linux_64', 'ArchLinux_64', g_k64, 1, 256, ['arch[0-9]*-64', ]], + [ 'OS2Warp45', 'OS2Warp45', g_k32 | g_kiNoRaw, 1, 1, ['os2.*', 'acp.*','mcp.*', ]], # smp does busy spinning and unattended installer only does UNI at the momen. + [ 'Solaris', 'Solaris', g_k32, 1, 256, ['sol10', 'sol10u[0-9]']], + [ 'Solaris_64', 'Solaris_64', g_k64, 1, 256, ['sol10-64', 'sol10u-64[0-9]']], + [ 'Solaris_64', 'Solaris11_64', g_k64, 1, 256, ['sol11u1']], + [ 'BSD', 'FreeBSD_64', g_k32_64, 1, 1, ['bs-.*']], # boot sectors, wanted 64-bit type. + [ 'DOS', 'DOS', g_k32, 1, 1, ['bs-.*']], +]; + + +## @name Guest OS type string constants. +## @{ +g_ksGuestOsTypeDarwin = 'darwin'; +g_ksGuestOsTypeDOS = 'dos'; +g_ksGuestOsTypeFreeBSD = 'freebsd'; +g_ksGuestOsTypeLinux = 'linux'; +g_ksGuestOsTypeOS2 = 'os2'; +g_ksGuestOsTypeSolaris = 'solaris'; +g_ksGuestOsTypeWindows = 'windows'; +## @} + +## @name String constants for paravirtualization providers. +## @{ +g_ksParavirtProviderNone = 'none'; +g_ksParavirtProviderDefault = 'default'; +g_ksParavirtProviderLegacy = 'legacy'; +g_ksParavirtProviderMinimal = 'minimal'; +g_ksParavirtProviderHyperV = 'hyperv'; +g_ksParavirtProviderKVM = 'kvm'; +## @} + +## Valid paravirtualization providers. +g_kasParavirtProviders = ( g_ksParavirtProviderNone, g_ksParavirtProviderDefault, g_ksParavirtProviderLegacy, + g_ksParavirtProviderMinimal, g_ksParavirtProviderHyperV, g_ksParavirtProviderKVM ); + +# Mapping for support of paravirtualisation providers per guest OS. +#g_kdaParavirtProvidersSupported = { +# g_ksGuestOsTypeDarwin : ( g_ksParavirtProviderMinimal, ), +# g_ksGuestOsTypeFreeBSD : ( g_ksParavirtProviderNone, g_ksParavirtProviderMinimal, ), +# g_ksGuestOsTypeLinux : ( g_ksParavirtProviderNone, g_ksParavirtProviderMinimal, g_ksParavirtProviderHyperV, g_ksParavirtProviderKVM), +# g_ksGuestOsTypeOS2 : ( g_ksParavirtProviderNone, ), +# g_ksGuestOsTypeSolaris : ( g_ksParavirtProviderNone, ), +# g_ksGuestOsTypeWindows : ( g_ksParavirtProviderNone, g_ksParavirtProviderMinimal, g_ksParavirtProviderHyperV, ) +#} +# Temporary tweak: +# since for the most guests g_ksParavirtProviderNone is almost the same as g_ksParavirtProviderMinimal, +# g_ksParavirtProviderMinimal is removed from the list in order to get maximum number of unique choices +# during independent test runs when paravirt provider is taken randomly. +g_kdaParavirtProvidersSupported = { + g_ksGuestOsTypeDarwin : ( g_ksParavirtProviderMinimal, ), + g_ksGuestOsTypeDOS : ( g_ksParavirtProviderNone, ), + g_ksGuestOsTypeFreeBSD : ( g_ksParavirtProviderNone, ), + g_ksGuestOsTypeLinux : ( g_ksParavirtProviderNone, g_ksParavirtProviderHyperV, g_ksParavirtProviderKVM), + g_ksGuestOsTypeOS2 : ( g_ksParavirtProviderNone, ), + g_ksGuestOsTypeSolaris : ( g_ksParavirtProviderNone, ), + g_ksGuestOsTypeWindows : ( g_ksParavirtProviderNone, g_ksParavirtProviderHyperV, ) +} + + +# pylint: enable=line-too-long + +def _intersects(asSet1, asSet2): + """ + Checks if any of the strings in set 1 matches any of the regular + expressions in set 2. + """ + for sStr1 in asSet1: + for sRx2 in asSet2: + if re.match(sStr1, sRx2 + '$'): + return True; + return False; + + + +class BaseTestVm(object): + """ + Base class for Test VMs. + """ + + def __init__(self, # pylint: disable=too-many-arguments + sVmName, # type: str + fGrouping = 0, # type: int + oSet = None, # type: TestVmSet + sKind = None, # type: str + acCpusSup = None, # type: List[int] + asVirtModesSup = None, # type: List[str] + asParavirtModesSup = None, # type: List[str] + fRandomPvPModeCrap = False, # type: bool + fVmmDevTestingPart = None, # type: bool + fVmmDevTestingMmio = False, # type: bool + iGroup = 1, # type: int + ): + self.oSet = oSet # type: TestVmSet + self.sVmName = sVmName; + self.iGroup = iGroup; # Startup group (for MAC address uniqueness and non-NAT networking). + self.fGrouping = fGrouping; + self.sKind = sKind; # API Guest OS type. + self.acCpusSup = acCpusSup; + self.asVirtModesSup = asVirtModesSup; + self.asParavirtModesSup = asParavirtModesSup; + self.asParavirtModesSupOrg = asParavirtModesSup; # HACK ALERT! Trick to make the 'effing random mess not get in the + # way of actively selecting virtualization modes. + + self.fSkip = False; # All VMs are included in the configured set by default. + self.fSnapshotRestoreCurrent = False; # Whether to restore execution on the current snapshot. + + # VMMDev and serial (TXS++) settings: + self.fVmmDevTestingPart = fVmmDevTestingPart; + self.fVmmDevTestingMmio = fVmmDevTestingMmio; + self.fCom1RawFile = False; + + # Cached stuff (use getters): + self.__sCom1RawFile = None; # Set by createVmInner and getReconfiguredVm if fCom1RawFile is set. + self.__tHddCtrlPortDev = (None, None, None); # The HDD controller, port and device. + self.__tDvdCtrlPortDev = (None, None, None); # The DVD controller, port and device. + self.__cbHdd = -1; # The recommended HDD size. + + # Derived stuff: + self.aInfo = None; + self.sGuestOsType = None; # ksGuestOsTypeXxxx value, API GuestOS Type is in the sKind member. + ## @todo rename sGuestOsType + self._guessStuff(fRandomPvPModeCrap); + + def _mkCanonicalGuestOSType(self, sType): + """ + Convert guest OS type into constant representation. + Raise exception if specified @param sType is unknown. + """ + if sType.lower().startswith('darwin'): + return g_ksGuestOsTypeDarwin + if sType.lower().startswith('bsd'): + return g_ksGuestOsTypeFreeBSD + if sType.lower().startswith('dos'): + return g_ksGuestOsTypeDOS + if sType.lower().startswith('linux'): + return g_ksGuestOsTypeLinux + if sType.lower().startswith('os2'): + return g_ksGuestOsTypeOS2 + if sType.lower().startswith('solaris'): + return g_ksGuestOsTypeSolaris + if sType.lower().startswith('windows'): + return g_ksGuestOsTypeWindows + raise base.GenError(sWhat="unknown guest OS kind: %s" % str(sType)) + + def _guessStuff(self, fRandomPvPModeCrap): + """ + Used by the constructor to guess stuff. + """ + + sNm = self.sVmName.lower().strip(); + asSplit = sNm.replace('-', ' ').split(' '); + + if self.sKind is None: + # From name. + for aInfo in g_aaNameToDetails: + if _intersects(asSplit, aInfo[g_iRegEx]): + self.aInfo = aInfo; + self.sGuestOsType = self._mkCanonicalGuestOSType(aInfo[g_iGuestOsType]) + self.sKind = aInfo[g_iKind]; + break; + if self.sKind is None: + reporter.fatal('The OS of test VM "%s" cannot be guessed' % (self.sVmName,)); + + # Check for 64-bit, if required and supported. + if (self.aInfo[g_iFlags] & g_kiArchMask) == g_k32_64 and _intersects(asSplit, ['64', 'amd64']): + self.sKind = self.sKind + '_64'; + else: + # Lookup the kind. + for aInfo in g_aaNameToDetails: + if self.sKind == aInfo[g_iKind]: + self.aInfo = aInfo; + break; + if self.aInfo is None: + reporter.fatal('The OS of test VM "%s" with sKind="%s" cannot be guessed' % (self.sVmName, self.sKind)); + + # Translate sKind into sGuest OS Type. + if self.sGuestOsType is None: + if self.aInfo is not None: + self.sGuestOsType = self._mkCanonicalGuestOSType(self.aInfo[g_iGuestOsType]) + elif self.sKind.find("Windows") >= 0: + self.sGuestOsType = g_ksGuestOsTypeWindows + elif self.sKind.find("Linux") >= 0: + self.sGuestOsType = g_ksGuestOsTypeLinux; + elif self.sKind.find("Solaris") >= 0: + self.sGuestOsType = g_ksGuestOsTypeSolaris; + elif self.sKind.find("DOS") >= 0: + self.sGuestOsType = g_ksGuestOsTypeDOS; + else: + reporter.fatal('The OS of test VM "%s", sKind="%s" cannot be guessed' % (self.sVmName, self.sKind)); + + # Restrict modes and such depending on the OS. + if self.asVirtModesSup is None: + self.asVirtModesSup = list(g_asVirtModes); + if self.sGuestOsType in (g_ksGuestOsTypeOS2, g_ksGuestOsTypeDarwin) \ + or self.sKind.find('_64') > 0 \ + or (self.aInfo is not None and (self.aInfo[g_iFlags] & g_kiNoRaw)): + self.asVirtModesSup = [sVirtMode for sVirtMode in self.asVirtModesSup if sVirtMode != 'raw']; + # TEMPORARY HACK - START + sHostName = os.environ.get("COMPUTERNAME", None); + if sHostName: sHostName = sHostName.lower(); + else: sHostName = socket.getfqdn(); # Horribly slow on windows without IPv6 DNS/whatever. + if sHostName.startswith('testboxpile1'): + self.asVirtModesSup = [sVirtMode for sVirtMode in self.asVirtModesSup if sVirtMode != 'raw']; + # TEMPORARY HACK - END + + # Restrict the CPU count depending on the OS and/or percieved SMP readiness. + if self.acCpusSup is None: + if _intersects(asSplit, ['uni']): + self.acCpusSup = [1]; + elif self.aInfo is not None: + self.acCpusSup = list(range(self.aInfo[g_iMinCpu], self.aInfo[g_iMaxCpu] + 1)); + else: + self.acCpusSup = [1]; + + # Figure relevant PV modes based on the OS. + if self.asParavirtModesSup is None: + self.asParavirtModesSup = g_kdaParavirtProvidersSupported[self.sGuestOsType]; + ## @todo Remove this hack as soon as we've got around to explictly configure test variations + ## on the server side. Client side random is interesting but not the best option. + self.asParavirtModesSupOrg = self.asParavirtModesSup; + if fRandomPvPModeCrap: + random.seed(); + self.asParavirtModesSup = (random.choice(self.asParavirtModesSup),); + + return True; + + def _generateRawPortFilename(self, oTestDrv, sInfix, sSuffix): + """ Generates a raw port filename. """ + random.seed(); + sRandom = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(10)); + return os.path.join(oTestDrv.sScratchPath, self.sVmName + sInfix + sRandom + sSuffix); + + def _createVmPre(self, oTestDrv, eNic0AttachType, sDvdImage): + """ + Prepares for creating the VM. + + Returns True / False. + """ + _ = eNic0AttachType; _ = sDvdImage; + if self.fCom1RawFile: + self.__sCom1RawFile = self._generateRawPortFilename(oTestDrv, '-com1-', '.out'); + return True; + + def _createVmDoIt(self, oTestDrv, eNic0AttachType, sDvdImage): + """ + Creates the VM. + + The default implementation creates a VM with defaults, no disks created or attached. + + Returns Wrapped VM object on success, None on failure. + """ + return oTestDrv.createTestVmWithDefaults(self.sVmName, + iGroup = self.iGroup, + sKind = self.sKind, + eNic0AttachType = eNic0AttachType, + sDvdImage = sDvdImage, + fVmmDevTestingPart = self.fVmmDevTestingPart, + fVmmDevTestingMmio = self.fVmmDevTestingMmio, + sCom1RawFile = self.__sCom1RawFile if self.fCom1RawFile else None + ); + + def _createVmPost(self, oTestDrv, oVM, eNic0AttachType, sDvdImage): # type: (base.testdriver, Any, int, str) -> Any + """ + Returns same oVM on success, None on failure (createVm cleans up). + """ + _ = oTestDrv; _ = eNic0AttachType; _ = sDvdImage; + return oVM; + + def _skipVmTest(self, oTestDrv, oVM): + """ + Called by getReconfiguredVm to figure out whether to skip the VM or not. + + Returns True if the VM should be skipped, False otherwise. + """ + _ = oVM; + fHostSupports64bit = oTestDrv.hasHostLongMode(); + if self.is64bitRequired() and not fHostSupports64bit: + reporter.log('Skipping 64-bit VM on non-64 capable host.'); + elif self.isViaIncompatible() and oTestDrv.isHostCpuVia(): + reporter.log('Skipping VIA incompatible VM.'); + elif self.isShanghaiIncompatible() and oTestDrv.isHostCpuShanghai(): + reporter.log('Skipping Shanghai (Zhaoxin) incompatible VM.'); + elif self.isP4Incompatible() and oTestDrv.isHostCpuP4(): + reporter.log('Skipping P4 incompatible VM.'); + else: + return False; + return True; + + + def _childVmReconfig(self, oTestDrv, oVM, oSession): + """ + Hook into getReconfiguredVm() for children. + """ + _ = oTestDrv; _ = oVM; _ = oSession; + return True; + + def _storageCtrlAndBusToName(self, oVBoxMgr, oVM, eCtrl, eBus): + """ + Resolves the storage controller name given type and bus. + + Returns String on success, None on failure w/ errors logged. + """ + try: + aoControllers = oVBoxMgr.getArray(oVM, 'storageControllers'); + except: + reporter.errorXcpt(); + return None; + asSummary = []; + for oController in aoControllers: + try: + eCurCtrl = oController.controllerType; + eCurBus = oController.bus; + sName = oController.name; + except: + reporter.errorXcpt(); + return None; + if eCurCtrl == eCtrl and eCurBus == eBus: + return sName; + asSummary.append('%s-%s-%s' % (eCurCtrl, eCurBus, sName,)); + reporter.error('Unable to find controller of type %s and bus %s (searched: %s)' % (eCtrl, eBus, ', '.join(asSummary),)); + return None; + + + # + # Public interface. + # + + def getResourceSet(self): + """ + Resturns a list of reosurces that the VM needs. + """ + return []; + + def getMissingResources(self, sResourcePath): + """ + Returns a list of missing resources (paths, stuff) that the VM needs. + """ + asRet = []; + asResources = self.getResourceSet(); + for sPath in asResources: + if not os.path.isabs(sPath): + sPath = os.path.join(sResourcePath, sPath); + if not os.path.exists(sPath): + asRet.append(sPath); + return asRet; + + def skipCreatingVm(self, oTestDrv): + """ + Called before VM creation to determine whether the VM should be skipped + due to host incompatibility or something along those lines. + + returns True if it should be skipped, False if not. Caller updates fSkip. + + See also _skipVmTest(). + """ + _ = oTestDrv; + return False; + + + def createVm(self, oTestDrv, eNic0AttachType = None, sDvdImage = None): + """ + Creates the VM with defaults and the few tweaks as per the arguments. + + Returns same as vbox.TestDriver.createTestVM. + """ + reporter.log2(''); + reporter.log2('Creating %s...' % (self.sVmName,)) + oVM = None; + fRc = self._createVmPre(oTestDrv, eNic0AttachType, sDvdImage); + if fRc is True: + oVM = self._createVmDoIt(oTestDrv, eNic0AttachType, sDvdImage); + if oVM: + oVM = self._createVmPost(oTestDrv, oVM, eNic0AttachType, sDvdImage); + return oVM; + + def getReconfiguredVm(self, oTestDrv, cCpus, sVirtMode, sParavirtMode = None): + """ + actionExecute worker that finds and reconfigure a test VM. + + Returns (fRc, oVM) where fRc is True, None or False and oVM is a + VBox VM object that is only present when rc is True. + """ + + fRc = False; + oVM = oTestDrv.getVmByName(self.sVmName); + if oVM is not None: + if self.fSnapshotRestoreCurrent is True: + fRc = True; + else: + fHostSupports64bit = oTestDrv.hasHostLongMode(); + if self._skipVmTest(oTestDrv, oVM): + fRc = None; # Skip the test. + else: + oSession = oTestDrv.openSession(oVM); + if oSession is not None: + fRc = oSession.enableVirtEx(sVirtMode != 'raw'); + fRc = fRc and oSession.enableNestedPaging(sVirtMode == 'hwvirt-np'); + fRc = fRc and oSession.setCpuCount(cCpus); + if cCpus > 1: + fRc = fRc and oSession.enableIoApic(True); + + if sParavirtMode is not None and oSession.fpApiVer >= 5.0: + adParavirtProviders = { + g_ksParavirtProviderNone : vboxcon.ParavirtProvider_None, + g_ksParavirtProviderDefault: vboxcon.ParavirtProvider_Default, + g_ksParavirtProviderLegacy : vboxcon.ParavirtProvider_Legacy, + g_ksParavirtProviderMinimal: vboxcon.ParavirtProvider_Minimal, + g_ksParavirtProviderHyperV : vboxcon.ParavirtProvider_HyperV, + g_ksParavirtProviderKVM : vboxcon.ParavirtProvider_KVM, + }; + fRc = fRc and oSession.setParavirtProvider(adParavirtProviders[sParavirtMode]); + + fCfg64Bit = self.is64bitRequired() or (self.is64bit() and fHostSupports64bit and sVirtMode != 'raw'); + fRc = fRc and oSession.enableLongMode(fCfg64Bit); + if fCfg64Bit: # This is to avoid GUI pedantic warnings in the GUI. Sigh. + oOsType = oSession.getOsType(); + if oOsType is not None: + if oOsType.is64Bit and sVirtMode == 'raw': + assert(oOsType.id[-3:] == '_64'); + fRc = fRc and oSession.setOsType(oOsType.id[:-3]); + elif not oOsType.is64Bit and sVirtMode != 'raw': + fRc = fRc and oSession.setOsType(oOsType.id + '_64'); + + # New serial raw file. + if fRc and self.fCom1RawFile: + self.__sCom1RawFile = self._generateRawPortFilename(oTestDrv, '-com1-', '.out'); + utils.noxcptDeleteFile(self.__sCom1RawFile); + fRc = oSession.setupSerialToRawFile(0, self.__sCom1RawFile); + + # Make life simpler for child classes. + if fRc: + fRc = self._childVmReconfig(oTestDrv, oVM, oSession); + + fRc = fRc and oSession.saveSettings(); + if not oSession.close(): + fRc = False; + if fRc is True: + return (True, oVM); + return (fRc, None); + + def getNonCanonicalGuestOsType(self): + """ + Gets the non-canonical OS type (self.sGuestOsType is canonical). + """ + return self.sKind; #self.aInfo[g_iGuestOsType]; + + def getGuestArch(self): + """ Same as util.getHostArch. """ + return 'amd64' if self.sKind.find('_64') >= 0 else 'x86'; + + def getGuestOs(self): + """ Same as util.getHostOs. """ + if self.isWindows(): return 'win'; + if self.isOS2(): return 'os2'; + if self.isLinux(): return 'linux'; + reporter.error('getGuestOs does not what to return!'); + raise Exception(); + + def getGuestOsDotArch(self): + """ Same as util.getHostOsDotArch. """ + return self.getGuestOs() + '.' + self.getGuestArch(); + + def getGuestExeSuff(self): + """ The executable image suffix for the guest. """ + if self.isWindows() or self.isOS2(): + return '.exe'; + return ''; + + def isWindows(self): + """ Checks if it's a Windows VM. """ + return self.sGuestOsType == g_ksGuestOsTypeWindows; + + def isOS2(self): + """ Checks if it's an OS/2 VM. """ + return self.sGuestOsType == g_ksGuestOsTypeOS2; + + def isLinux(self): + """ Checks if it's an Linux VM. """ + return self.sGuestOsType == g_ksGuestOsTypeLinux; + + def is64bit(self): + """ Checks if it's a 64-bit VM. """ + return self.sKind.find('_64') >= 0; + + def is64bitRequired(self): + """ Check if 64-bit is required or not. """ + return (self.aInfo[g_iFlags] & g_k64) != 0; + + def isLoggedOntoDesktop(self): + """ Checks if the test VM is logging onto a graphical desktop by default. """ + if self.isWindows(): + return True; + if self.isOS2(): + return True; + if self.sVmName.find('-desktop'): + return True; + return False; + + def isViaIncompatible(self): + """ + Identifies VMs that doesn't work on VIA. + + Returns True if NOT supported on VIA, False if it IS supported. + """ + # Oracle linux doesn't like VIA in our experience + if self.aInfo[g_iKind] in ['Oracle', 'Oracle_64']: + return True; + # OS/2: "The system detected an internal processing error at location + # 0168:fff1da1f - 000e:ca1f. 0a8606fd + if self.isOS2(): + return True; + # Windows NT4 before SP4 won't work because of cmpxchg8b not being + # detected, leading to a STOP 3e(80,0,0,0). + if self.aInfo[g_iKind] == 'WindowsNT4': + if self.sVmName.find('sp') < 0: + return True; # no service pack. + if self.sVmName.find('sp0') >= 0 \ + or self.sVmName.find('sp1') >= 0 \ + or self.sVmName.find('sp2') >= 0 \ + or self.sVmName.find('sp3') >= 0: + return True; + # XP x64 on a physical VIA box hangs exactly like a VM. + if self.aInfo[g_iKind] in ['WindowsXP_64', 'Windows2003_64']: + return True; + # Vista 64 throws BSOD 0x5D (UNSUPPORTED_PROCESSOR) + if self.aInfo[g_iKind] in ['WindowsVista_64']: + return True; + # Solaris 11 hangs on VIA, tested on a physical box (testboxvqc) + if self.aInfo[g_iKind] in ['Solaris11_64']: + return True; + return False; + + def isShanghaiIncompatible(self): + """ + Identifies VMs that doesn't work on Shanghai. + + Returns True if NOT supported on Shanghai, False if it IS supported. + """ + # For now treat it just like VIA, to be adjusted later + return self.isViaIncompatible() + + def isP4Incompatible(self): + """ + Identifies VMs that doesn't work on Pentium 4 / Pentium D. + + Returns True if NOT supported on P4, False if it IS supported. + """ + # Stupid 1 kHz timer. Too much for antique CPUs. + if self.sVmName.find('rhel5') >= 0: + return True; + # Due to the boot animation the VM takes forever to boot. + if self.aInfo[g_iKind] == 'Windows2000': + return True; + return False; + + def isHostCpuAffectedByUbuntuNewAmdBug(self, oTestDrv): + """ + Checks if the host OS is affected by older ubuntu installers being very + picky about which families of AMD CPUs it would run on. + + The installer checks for family 15, later 16, later 20, and in 11.10 + they remove the family check for AMD CPUs. + """ + if not oTestDrv.isHostCpuAmd(): + return False; + try: + (uMaxExt, _, _, _) = oTestDrv.oVBox.host.getProcessorCPUIDLeaf(0, 0x80000000, 0); + (uFamilyModel, _, _, _) = oTestDrv.oVBox.host.getProcessorCPUIDLeaf(0, 0x80000001, 0); + except: + reporter.logXcpt(); + return False; + if uMaxExt < 0x80000001 or uMaxExt > 0x8000ffff: + return False; + + uFamily = (uFamilyModel >> 8) & 0xf + if uFamily == 0xf: + uFamily = ((uFamilyModel >> 20) & 0x7f) + 0xf; + ## @todo Break this down into which old ubuntu release supports exactly + ## which AMD family, if we care. + if uFamily <= 15: + return False; + reporter.log('Skipping "%s" because host CPU is a family %u AMD, which may cause trouble for the guest OS installer.' + % (self.sVmName, uFamily,)); + return True; + + def getTestUser(self): + """ + Gets the primary test user name. + """ + if self.isWindows(): + return 'Administrator'; + return 'vbox'; + + def getTestUserPassword(self, sUser = None): + """ + Gets the password for the primary user (or other specified one). + """ + if sUser == 'test': + return ''; + if sUser == 'vboxuser': # Default unattended installation user and password. + return 'changeme'; + return 'password'; + + def getCom1RawFile(self, oVM): + """ + Gets the name of the COM1 raw file. + + Returns string, None on failure or if not active. + + Note! Do not access __sCom1RawFile directly as it will not be set unless the + 'config' action was executed in the same run. + """ + if self.fCom1RawFile: + # Retrieve it from the IMachine object and cache the result if needed: + if self.__sCom1RawFile is None: + try: + oPort = oVM.machine.getSerialPort(0); + except: + reporter.errorXcpt('failed to get serial port #0'); + else: + try: + self.__sCom1RawFile = oPort.path; + except: + reporter.errorXcpt('failed to get the "path" property on serial port #0'); + return self.__sCom1RawFile; + + reporter.error('getCom1RawFile called when fCom1RawFile is False'); + return None; + + def getIGuestOSType(self, oVBoxWrapped): + """ + Gets the IGuestOSType object corresponding to self.sKind. + + Returns object on success, None on failure (logged as error). + """ + try: + return oVBoxWrapped.o.getGuestOSType(self.sKind); + except: + reporter.errorXcpt('sVmName=%s sKind=%s' % (self.sVmName, self.sKind,)); + return None; + + def getRecommendedHddSize(self, oVBoxWrapped): + """ + Gets the recommended HDD size from the IGuestOSType matching self.sKind. + + Returns size in bytes on success, -1 on failure. + """ + if self.__cbHdd < 0: + oGuestOSType = self.getIGuestOSType(oVBoxWrapped); + if oGuestOSType: + try: + self.__cbHdd = oGuestOSType.recommendedHDD; + except: + reporter.errorXcpt(); + return -1; + return self.__cbHdd; + + def getHddAddress(self, oVM, oVBoxWrapped): + """ + Gets the HDD attachment address. + + Returns (sController, iPort, iDevice) on success; (None, None, None) on failure. + + Note! Do not access the cached value directly! + """ + # Cached already? + if self.__tHddCtrlPortDev[0] is not None: + return self.__tHddCtrlPortDev; + + # First look for HDs attached to the VM: + try: + aoAttachments = oVBoxWrapped.oVBoxMgr.getArray(oVM, 'mediumAttachments') + except: + reporter.errorXcpt(); + else: + for oAtt in aoAttachments: + try: + sCtrl = oAtt.controller + iPort = oAtt.port; + iDev = oAtt.device; + eType = oAtt.type; + except: + reporter.errorXcpt(); + return self.__tHddCtrlPortDev; + if eType == vboxcon.DeviceType_HardDisk: + self.__tHddCtrlPortDev = (sCtrl, iPort, iDev); + reporter.log2('getHddAddress: %s, %s, %s' % self.__tHddCtrlPortDev); + return self.__tHddCtrlPortDev; + + # Then consult IGuestOSType: + oGuestOSType = self.getIGuestOSType(oVBoxWrapped); + if oGuestOSType: + try: + eCtrl = oGuestOSType.recommendedHDStorageController; + eBus = oGuestOSType.recommendedHDStorageBus; + except: + reporter.errorXcpt(); + else: + # ASSUMES port 0, device 0. + self.__tHddCtrlPortDev = (self._storageCtrlAndBusToName(oVBoxWrapped.oVBoxMgr, oVM, eCtrl, eBus), 0, 0); + reporter.log2('getHddAddress: %s, %s, %s [IGuestOSType]' % self.__tHddCtrlPortDev); + return self.__tHddCtrlPortDev; + + def getDvdAddress(self, oVM, oVBoxWrapped): + """ + Gets the DVD attachment address. + + Returns (sController, iPort, iDevice) on success; (None, None, None) on failure. + + Note! Do not access the cached value directly! + """ + # Cached already? + if self.__tDvdCtrlPortDev[0] is not None: + return self.__tDvdCtrlPortDev; + + # First look for DVD attached to the VM: + try: + aoAttachments = oVBoxWrapped.oVBoxMgr.getArray(oVM, 'mediumAttachments') + except: + reporter.errorXcpt(); + else: + for oAtt in aoAttachments: + try: + sCtrl = oAtt.controller + iPort = oAtt.port; + iDev = oAtt.device; + eType = oAtt.type; + except: + reporter.errorXcpt(); + return self.__tDvdCtrlPortDev; + if eType == vboxcon.DeviceType_DVD: + self.__tDvdCtrlPortDev = (sCtrl, iPort, iDev); + reporter.log2('getDvdAddress: %s, %s, %s' % self.__tDvdCtrlPortDev); + return self.__tDvdCtrlPortDev; + + # Then consult IGuestOSType: + oGuestOSType = self.getIGuestOSType(oVBoxWrapped); + if oGuestOSType: + try: + eCtrl = oGuestOSType.recommendedDVDStorageController; + eBus = oGuestOSType.recommendedDVDStorageBus; + except: + reporter.errorXcpt(); + else: + # ASSUMES port 1, device 0. + self.__tDvdCtrlPortDev = (self._storageCtrlAndBusToName(oVBoxWrapped.oVBoxMgr, oVM, eCtrl, eBus), 1, 0); + reporter.log2('getDvdAddress: %s, %s, %s [IGuestOSType]' % self.__tDvdCtrlPortDev); + return self.__tDvdCtrlPortDev; + + def recreateRecommendedHdd(self, oVM, oTestDrv, sHddPath = None): + """ + Detaches and delete any current hard disk and then ensures that a new + one with the recommended size is created and attached to the recommended + controller/port/device. + + Returns True/False (errors logged). + """ + # Generate a name if none was given: + if not sHddPath: + try: + sHddPath = oVM.settingsFilePath; + except: + return reporter.errorXcpt(); + sHddPath = os.path.join(os.path.dirname(sHddPath), '%s-%s.vdi' % (self.sVmName, uuid.uuid4(),)); + + fRc = False; + + # Get the hard disk specs first: + cbHdd = self.getRecommendedHddSize(oTestDrv.oVBox); + tHddAddress = self.getHddAddress(oVM, oTestDrv.oVBox); + assert len(tHddAddress) == 3; + if tHddAddress[0] and cbHdd > 0: + # Open an session so we can make changes: + oSession = oTestDrv.openSession(oVM); + if oSession is not None: + # Detach the old disk (this will succeed with oOldHd set to None the first time around). + (fRc, oOldHd) = oSession.detachHd(tHddAddress[0], tHddAddress[1], tHddAddress[2]); + if fRc: + # Create a new disk and attach it. + fRc = oSession.createAndAttachHd(sHddPath, + cb = cbHdd, + sController = tHddAddress[0], + iPort = tHddAddress[1], + iDevice = tHddAddress[2], + fImmutable = False); + if fRc: + # Save the changes. + fRc = oSession.saveSettings(); + + # Delete the old HD: + if fRc and oOldHd is not None: + fRc = fRc and oTestDrv.oVBox.deleteHdByMedium(oOldHd); + fRc = fRc and oSession.saveSettings(); # Necessary for media reg?? + else: + oSession.discardSettings(); + fRc = oSession.close() and fRc; + return fRc; + + def pathJoin(self, sBase, *asAppend): + """ See common.pathutils.joinEx(). """ + return pathutils.joinEx(self.isWindows() or self.isOS2(), sBase, *asAppend); + + def pathSep(self): + """ Returns the preferred paths separator for the guest OS. """ + return '\\' if self.isWindows() or self.isOS2() else '/'; + + +## @todo Inherit from BaseTestVm +class TestVm(object): + """ + A Test VM - name + VDI/whatever. + + This is just a data object. + """ + + def __init__(self, # pylint: disable=too-many-arguments + sVmName, # type: str + fGrouping = 0, # type: int + oSet = None, # type: TestVmSet + sHd = None, # type: str + sKind = None, # type: str + acCpusSup = None, # type: List[int] + asVirtModesSup = None, # type: List[str] + fIoApic = None, # type: bool + fNstHwVirt = False, # type: bool + fPae = None, # type: bool + sNic0AttachType = None, # type: str + sFloppy = None, # type: str + fVmmDevTestingPart = None, # type: bool + fVmmDevTestingMmio = False, # type: bool + asParavirtModesSup = None, # type: List[str] + fRandomPvPMode = False, # type: bool + sFirmwareType = 'bios', # type: str + sChipsetType = 'piix3', # type: str + sIommuType = 'none', # type: str + sHddControllerType = 'IDE Controller', # type: str + sDvdControllerType = 'IDE Controller' # type: str + ): + self.oSet = oSet; + self.sVmName = sVmName; + self.fGrouping = fGrouping; + self.sHd = sHd; # Relative to the testrsrc root. + self.acCpusSup = acCpusSup; + self.asVirtModesSup = asVirtModesSup; + self.asParavirtModesSup = asParavirtModesSup; + self.asParavirtModesSupOrg = asParavirtModesSup; # HACK ALERT! Trick to make the 'effing random mess not get in the + # way of actively selecting virtualization modes. + self.sKind = sKind; + self.sGuestOsType = None; + self.sDvdImage = None; # Relative to the testrsrc root. + self.sDvdControllerType = sDvdControllerType; + self.fIoApic = fIoApic; + self.fNstHwVirt = fNstHwVirt; + self.fPae = fPae; + self.sNic0AttachType = sNic0AttachType; + self.sHddControllerType = sHddControllerType; + self.sFloppy = sFloppy; # Relative to the testrsrc root, except when it isn't... + self.fVmmDevTestingPart = fVmmDevTestingPart; + self.fVmmDevTestingMmio = fVmmDevTestingMmio; + self.sFirmwareType = sFirmwareType; + self.sChipsetType = sChipsetType; + self.sIommuType = sIommuType; + self.fCom1RawFile = False; + + self.fSnapshotRestoreCurrent = False; # Whether to restore execution on the current snapshot. + self.fSkip = False; # All VMs are included in the configured set by default. + self.aInfo = None; + self.sCom1RawFile = None; # Set by createVmInner and getReconfiguredVm if fCom1RawFile is set. + self._guessStuff(fRandomPvPMode); + + def _mkCanonicalGuestOSType(self, sType): + """ + Convert guest OS type into constant representation. + Raise exception if specified @param sType is unknown. + """ + if sType.lower().startswith('darwin'): + return g_ksGuestOsTypeDarwin + if sType.lower().startswith('bsd'): + return g_ksGuestOsTypeFreeBSD + if sType.lower().startswith('dos'): + return g_ksGuestOsTypeDOS + if sType.lower().startswith('linux'): + return g_ksGuestOsTypeLinux + if sType.lower().startswith('os2'): + return g_ksGuestOsTypeOS2 + if sType.lower().startswith('solaris'): + return g_ksGuestOsTypeSolaris + if sType.lower().startswith('windows'): + return g_ksGuestOsTypeWindows + raise base.GenError(sWhat="unknown guest OS kind: %s" % str(sType)) + + def _guessStuff(self, fRandomPvPMode): + """ + Used by the constructor to guess stuff. + """ + + sNm = self.sVmName.lower().strip(); + asSplit = sNm.replace('-', ' ').split(' '); + + if self.sKind is None: + # From name. + for aInfo in g_aaNameToDetails: + if _intersects(asSplit, aInfo[g_iRegEx]): + self.aInfo = aInfo; + self.sGuestOsType = self._mkCanonicalGuestOSType(aInfo[g_iGuestOsType]) + self.sKind = aInfo[g_iKind]; + break; + if self.sKind is None: + reporter.fatal('The OS of test VM "%s" cannot be guessed' % (self.sVmName,)); + + # Check for 64-bit, if required and supported. + if (self.aInfo[g_iFlags] & g_kiArchMask) == g_k32_64 and _intersects(asSplit, ['64', 'amd64']): + self.sKind = self.sKind + '_64'; + else: + # Lookup the kind. + for aInfo in g_aaNameToDetails: + if self.sKind == aInfo[g_iKind]: + self.aInfo = aInfo; + break; + if self.aInfo is None: + reporter.fatal('The OS of test VM "%s" with sKind="%s" cannot be guessed' % (self.sVmName, self.sKind)); + + # Translate sKind into sGuest OS Type. + if self.sGuestOsType is None: + if self.aInfo is not None: + self.sGuestOsType = self._mkCanonicalGuestOSType(self.aInfo[g_iGuestOsType]) + elif self.sKind.find("Windows") >= 0: + self.sGuestOsType = g_ksGuestOsTypeWindows + elif self.sKind.find("Linux") >= 0: + self.sGuestOsType = g_ksGuestOsTypeLinux; + elif self.sKind.find("Solaris") >= 0: + self.sGuestOsType = g_ksGuestOsTypeSolaris; + elif self.sKind.find("DOS") >= 0: + self.sGuestOsType = g_ksGuestOsTypeDOS; + else: + reporter.fatal('The OS of test VM "%s", sKind="%s" cannot be guessed' % (self.sVmName, self.sKind)); + + # Restrict modes and such depending on the OS. + if self.asVirtModesSup is None: + self.asVirtModesSup = list(g_asVirtModes); + if self.sGuestOsType in (g_ksGuestOsTypeOS2, g_ksGuestOsTypeDarwin) \ + or self.sKind.find('_64') > 0 \ + or (self.aInfo is not None and (self.aInfo[g_iFlags] & g_kiNoRaw)): + self.asVirtModesSup = [sVirtMode for sVirtMode in self.asVirtModesSup if sVirtMode != 'raw']; + # TEMPORARY HACK - START + sHostName = os.environ.get("COMPUTERNAME", None); + if sHostName: sHostName = sHostName.lower(); + else: sHostName = socket.getfqdn(); # Horribly slow on windows without IPv6 DNS/whatever. + if sHostName.startswith('testboxpile1'): + self.asVirtModesSup = [sVirtMode for sVirtMode in self.asVirtModesSup if sVirtMode != 'raw']; + # TEMPORARY HACK - END + + # Restrict the CPU count depending on the OS and/or percieved SMP readiness. + if self.acCpusSup is None: + if _intersects(asSplit, ['uni']): + self.acCpusSup = [1]; + elif self.aInfo is not None: + self.acCpusSup = list(range(self.aInfo[g_iMinCpu], self.aInfo[g_iMaxCpu] + 1)); + else: + self.acCpusSup = [1]; + + # Figure relevant PV modes based on the OS. + if self.asParavirtModesSup is None: + self.asParavirtModesSup = g_kdaParavirtProvidersSupported[self.sGuestOsType]; + ## @todo Remove this hack as soon as we've got around to explictly configure test variations + ## on the server side. Client side random is interesting but not the best option. + self.asParavirtModesSupOrg = self.asParavirtModesSup; + if fRandomPvPMode: + random.seed(); + self.asParavirtModesSup = (random.choice(self.asParavirtModesSup),); + + return True; + + def getNonCanonicalGuestOsType(self): + """ + Gets the non-canonical OS type (self.sGuestOsType is canonical). + """ + return self.aInfo[g_iGuestOsType]; + + def getMissingResources(self, sTestRsrc): + """ + Returns a list of missing resources (paths, stuff) that the VM needs. + """ + asRet = []; + for sPath in [ self.sHd, self.sDvdImage, self.sFloppy]: + if sPath is not None: + if not os.path.isabs(sPath): + sPath = os.path.join(sTestRsrc, sPath); + if not os.path.exists(sPath): + asRet.append(sPath); + return asRet; + + def skipCreatingVm(self, oTestDrv): + """ + Called before VM creation to determine whether the VM should be skipped + due to host incompatibility or something along those lines. + + returns True if it should be skipped, False if not. + """ + if self.fNstHwVirt and not oTestDrv.hasHostNestedHwVirt(): + reporter.log('Ignoring VM %s (Nested hardware-virtualization not support on this host).' % (self.sVmName,)); + return True; + return False; + + def createVm(self, oTestDrv, eNic0AttachType = None, sDvdImage = None): + """ + Creates the VM with defaults and the few tweaks as per the arguments. + + Returns same as vbox.TestDriver.createTestVM. + """ + if sDvdImage is not None: + sMyDvdImage = sDvdImage; + else: + sMyDvdImage = self.sDvdImage; + + if eNic0AttachType is not None: + eMyNic0AttachType = eNic0AttachType; + elif self.sNic0AttachType is None: + eMyNic0AttachType = None; + elif self.sNic0AttachType == 'nat': + eMyNic0AttachType = vboxcon.NetworkAttachmentType_NAT; + elif self.sNic0AttachType == 'bridged': + eMyNic0AttachType = vboxcon.NetworkAttachmentType_Bridged; + else: + assert False, self.sNic0AttachType; + + return self.createVmInner(oTestDrv, eMyNic0AttachType, sMyDvdImage); + + def _generateRawPortFilename(self, oTestDrv, sInfix, sSuffix): + """ Generates a raw port filename. """ + random.seed(); + sRandom = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(10)); + return os.path.join(oTestDrv.sScratchPath, self.sVmName + sInfix + sRandom + sSuffix); + + def createVmInner(self, oTestDrv, eNic0AttachType, sDvdImage): + """ + Same as createVm but parameters resolved. + + Returns same as vbox.TestDriver.createTestVM. + """ + reporter.log2(''); + reporter.log2('Calling createTestVM on %s...' % (self.sVmName,)) + if self.fCom1RawFile: + self.sCom1RawFile = self._generateRawPortFilename(oTestDrv, '-com1-', '.out'); + return oTestDrv.createTestVM(self.sVmName, + 1, # iGroup + sHd = self.sHd, + sKind = self.sKind, + fIoApic = self.fIoApic, + fNstHwVirt = self.fNstHwVirt, + fPae = self.fPae, + eNic0AttachType = eNic0AttachType, + sDvdImage = sDvdImage, + sDvdControllerType = self.sDvdControllerType, + sHddControllerType = self.sHddControllerType, + sFloppy = self.sFloppy, + fVmmDevTestingPart = self.fVmmDevTestingPart, + fVmmDevTestingMmio = self.fVmmDevTestingMmio, + sFirmwareType = self.sFirmwareType, + sChipsetType = self.sChipsetType, + sIommuType = self.sIommuType, + sCom1RawFile = self.sCom1RawFile if self.fCom1RawFile else None + ); + + def getReconfiguredVm(self, oTestDrv, cCpus, sVirtMode, sParavirtMode = None): + """ + actionExecute worker that finds and reconfigure a test VM. + + Returns (fRc, oVM) where fRc is True, None or False and oVM is a + VBox VM object that is only present when rc is True. + """ + + fRc = False; + oVM = oTestDrv.getVmByName(self.sVmName); + if oVM is not None: + if self.fSnapshotRestoreCurrent is True: + fRc = True; + else: + fHostSupports64bit = oTestDrv.hasHostLongMode(); + if self.is64bitRequired() and not fHostSupports64bit: + fRc = None; # Skip the test. + elif self.isViaIncompatible() and oTestDrv.isHostCpuVia(): + fRc = None; # Skip the test. + elif self.isShanghaiIncompatible() and oTestDrv.isHostCpuShanghai(): + fRc = None; # Skip the test. + elif self.isP4Incompatible() and oTestDrv.isHostCpuP4(): + fRc = None; # Skip the test. + else: + oSession = oTestDrv.openSession(oVM); + if oSession is not None: + fRc = oSession.enableVirtEx(sVirtMode != 'raw'); + fRc = fRc and oSession.enableNestedPaging(sVirtMode == 'hwvirt-np'); + fRc = fRc and oSession.setCpuCount(cCpus); + if cCpus > 1: + fRc = fRc and oSession.enableIoApic(True); + + if sParavirtMode is not None and oSession.fpApiVer >= 5.0: + adParavirtProviders = { + g_ksParavirtProviderNone : vboxcon.ParavirtProvider_None, + g_ksParavirtProviderDefault: vboxcon.ParavirtProvider_Default, + g_ksParavirtProviderLegacy : vboxcon.ParavirtProvider_Legacy, + g_ksParavirtProviderMinimal: vboxcon.ParavirtProvider_Minimal, + g_ksParavirtProviderHyperV : vboxcon.ParavirtProvider_HyperV, + g_ksParavirtProviderKVM : vboxcon.ParavirtProvider_KVM, + }; + fRc = fRc and oSession.setParavirtProvider(adParavirtProviders[sParavirtMode]); + + fCfg64Bit = self.is64bitRequired() or (self.is64bit() and fHostSupports64bit and sVirtMode != 'raw'); + fRc = fRc and oSession.enableLongMode(fCfg64Bit); + if fCfg64Bit: # This is to avoid GUI pedantic warnings in the GUI. Sigh. + oOsType = oSession.getOsType(); + if oOsType is not None: + if oOsType.is64Bit and sVirtMode == 'raw': + assert(oOsType.id[-3:] == '_64'); + fRc = fRc and oSession.setOsType(oOsType.id[:-3]); + elif not oOsType.is64Bit and sVirtMode != 'raw': + fRc = fRc and oSession.setOsType(oOsType.id + '_64'); + + # New serial raw file. + if fRc and self.fCom1RawFile: + self.sCom1RawFile = self._generateRawPortFilename(oTestDrv, '-com1-', '.out'); + utils.noxcptDeleteFile(self.sCom1RawFile); + fRc = oSession.setupSerialToRawFile(0, self.sCom1RawFile); + + # Make life simpler for child classes. + if fRc: + fRc = self._childVmReconfig(oTestDrv, oVM, oSession); + + fRc = fRc and oSession.saveSettings(); + if not oSession.close(): + fRc = False; + if fRc is True: + return (True, oVM); + return (fRc, None); + + def _childVmReconfig(self, oTestDrv, oVM, oSession): + """ Hook into getReconfiguredVm() for children. """ + _ = oTestDrv; _ = oVM; _ = oSession; + return True; + + def getGuestArch(self): + """ Same as util.getHostArch. """ + return 'amd64' if self.sKind.find('_64') >= 0 else 'x86'; + + def getGuestOs(self): + """ Same as util.getHostOs. """ + if self.isWindows(): return 'win'; + if self.isOS2(): return 'os2'; + if self.isLinux(): return 'linux'; + reporter.error('getGuestOs does not what to return!'); + raise Exception(); + + def getGuestExeSuff(self): + """ The executable image suffix for the guest. """ + if self.isWindows() or self.isOS2(): + return '.exe'; + return ''; + + def getGuestOsDotArch(self): + """ Same as util.getHostOsDotArch.""" + return self.getGuestOs() + '.' + self.getGuestArch(); + + def isWindows(self): + """ Checks if it's a Windows VM. """ + return self.sGuestOsType == g_ksGuestOsTypeWindows; + + def isOS2(self): + """ Checks if it's an OS/2 VM. """ + return self.sGuestOsType == g_ksGuestOsTypeOS2; + + def isLinux(self): + """ Checks if it's an Linux VM. """ + return self.sGuestOsType == g_ksGuestOsTypeLinux; + + def is64bit(self): + """ Checks if it's a 64-bit VM. """ + return self.sKind.find('_64') >= 0; + + def is64bitRequired(self): + """ Check if 64-bit is required or not. """ + return (self.aInfo[g_iFlags] & g_k64) != 0; + + def isLoggedOntoDesktop(self): + """ Checks if the test VM is logging onto a graphical desktop by default. """ + if self.isWindows(): + return True; + if self.isOS2(): + return True; + if self.sVmName.find('-desktop'): + return True; + return False; + + def isViaIncompatible(self): + """ + Identifies VMs that doesn't work on VIA. + + Returns True if NOT supported on VIA, False if it IS supported. + """ + # Oracle linux doesn't like VIA in our experience + if self.aInfo[g_iKind] in ['Oracle', 'Oracle_64']: + return True; + # OS/2: "The system detected an internal processing error at location + # 0168:fff1da1f - 000e:ca1f. 0a8606fd + if self.isOS2(): + return True; + # Windows NT4 before SP4 won't work because of cmpxchg8b not being + # detected, leading to a STOP 3e(80,0,0,0). + if self.aInfo[g_iKind] == 'WindowsNT4': + if self.sVmName.find('sp') < 0: + return True; # no service pack. + if self.sVmName.find('sp0') >= 0 \ + or self.sVmName.find('sp1') >= 0 \ + or self.sVmName.find('sp2') >= 0 \ + or self.sVmName.find('sp3') >= 0: + return True; + # XP x64 on a physical VIA box hangs exactly like a VM. + if self.aInfo[g_iKind] in ['WindowsXP_64', 'Windows2003_64']: + return True; + # Vista 64 throws BSOD 0x5D (UNSUPPORTED_PROCESSOR) + if self.aInfo[g_iKind] in ['WindowsVista_64']: + return True; + # Solaris 11 hangs on VIA, tested on a physical box (testboxvqc) + if self.aInfo[g_iKind] in ['Solaris11_64']: + return True; + return False; + + def isShanghaiIncompatible(self): + """ + Identifies VMs that doesn't work on Shanghai. + + Returns True if NOT supported on Shanghai, False if it IS supported. + """ + # For now treat it just like VIA, to be adjusted later + return self.isViaIncompatible() + + def isP4Incompatible(self): + """ + Identifies VMs that doesn't work on Pentium 4 / Pentium D. + + Returns True if NOT supported on P4, False if it IS supported. + """ + # Stupid 1 kHz timer. Too much for antique CPUs. + if self.sVmName.find('rhel5') >= 0: + return True; + # Due to the boot animation the VM takes forever to boot. + if self.aInfo[g_iKind] == 'Windows2000': + return True; + return False; + + def isHostCpuAffectedByUbuntuNewAmdBug(self, oTestDrv): + """ + Checks if the host OS is affected by older ubuntu installers being very + picky about which families of AMD CPUs it would run on. + + The installer checks for family 15, later 16, later 20, and in 11.10 + they remove the family check for AMD CPUs. + """ + if not oTestDrv.isHostCpuAmd(): + return False; + try: + (uMaxExt, _, _, _) = oTestDrv.oVBox.host.getProcessorCPUIDLeaf(0, 0x80000000, 0); + (uFamilyModel, _, _, _) = oTestDrv.oVBox.host.getProcessorCPUIDLeaf(0, 0x80000001, 0); + except: + reporter.logXcpt(); + return False; + if uMaxExt < 0x80000001 or uMaxExt > 0x8000ffff: + return False; + + uFamily = (uFamilyModel >> 8) & 0xf + if uFamily == 0xf: + uFamily = ((uFamilyModel >> 20) & 0x7f) + 0xf; + ## @todo Break this down into which old ubuntu release supports exactly + ## which AMD family, if we care. + if uFamily <= 15: + return False; + reporter.log('Skipping "%s" because host CPU is a family %u AMD, which may cause trouble for the guest OS installer.' + % (self.sVmName, uFamily,)); + return True; + + def getTestUser(self): + """ + Gets the primary test user name. + """ + if self.isWindows(): + return 'Administrator'; + return 'vbox'; + + def getTestUserPassword(self, sUser = None): + """ + Gets the password for the primary user (or other specified one). + """ + if sUser == 'test': + return ''; + if sUser == 'vboxuser': # Default unattended installation user and password. + return 'changeme'; + return 'password'; + + def pathJoin(self, sBase, *asAppend): + """ See common.pathutils.joinEx(). """ + return pathutils.joinEx(self.isWindows() or self.isOS2(), sBase, *asAppend); + + def pathSep(self): + """ Returns the preferred paths separator for the guest OS. """ + return '\\' if self.isWindows() or self.isOS2() else '/'; + + +class BootSectorTestVm(TestVm): + """ + A Boot Sector Test VM. + """ + + def __init__(self, oSet, sVmName, sFloppy = None, asVirtModesSup = None, f64BitRequired = False): + self.f64BitRequired = f64BitRequired; + if asVirtModesSup is None: + asVirtModesSup = list(g_asVirtModes); + TestVm.__init__(self, sVmName, + oSet = oSet, + acCpusSup = [1,], + sFloppy = sFloppy, + asVirtModesSup = asVirtModesSup, + fPae = True, + fIoApic = True, + fVmmDevTestingPart = True, + fVmmDevTestingMmio = True, + ); + + def is64bitRequired(self): + return self.f64BitRequired; + + +class AncientTestVm(TestVm): + """ + A ancient Test VM, using the serial port for communicating results. + + We're looking for 'PASSED' and 'FAILED' lines in the COM1 output. + """ + + + def __init__(self, # pylint: disable=too-many-arguments + sVmName, # type: str + fGrouping = g_kfGrpAncient | g_kfGrpNoTxs, # type: int + sHd = None, # type: str + sKind = None, # type: str + acCpusSup = None, # type: List[int] + asVirtModesSup = None, # type: List[str] + sNic0AttachType = None, # type: str + sFloppy = None, # type: str + sFirmwareType = 'bios', # type: str + sChipsetType = 'piix3', # type: str + sHddControllerName = 'IDE Controller', # type: str + sDvdControllerName = 'IDE Controller', # type: str + cMBRamMax = None, # type: int + ): + TestVm.__init__(self, + sVmName, + fGrouping = fGrouping, + sHd = sHd, + sKind = sKind, + acCpusSup = [1] if acCpusSup is None else acCpusSup, + asVirtModesSup = asVirtModesSup, + sNic0AttachType = sNic0AttachType, + sFloppy = sFloppy, + sFirmwareType = sFirmwareType, + sChipsetType = sChipsetType, + sHddControllerType = sHddControllerName, + sDvdControllerType = sDvdControllerName, + asParavirtModesSup = (g_ksParavirtProviderNone,) + ); + self.fCom1RawFile = True; + self.cMBRamMax= cMBRamMax; + + + def _childVmReconfig(self, oTestDrv, oVM, oSession): + _ = oVM; _ = oTestDrv; + fRc = True; + + # DOS 4.01 doesn't like the default 32MB of memory. + if fRc and self.cMBRamMax is not None: + try: + cMBRam = oSession.o.machine.memorySize; + except: + cMBRam = self.cMBRamMax + 4; + if self.cMBRamMax < cMBRam: + fRc = oSession.setRamSize(self.cMBRamMax); + + return fRc; + + +class TestVmSet(object): + """ + A set of Test VMs. + """ + + def __init__(self, oTestVmManager = None, acCpus = None, asVirtModes = None, fIgnoreSkippedVm = False): + self.oTestVmManager = oTestVmManager; + if acCpus is None: + acCpus = [1, 2]; + self.acCpusDef = acCpus; + self.acCpus = acCpus; + if asVirtModes is None: + asVirtModes = list(g_asVirtModes); + self.asVirtModesDef = asVirtModes; + self.asVirtModes = asVirtModes; + self.aoTestVms = [] # type: list(BaseTestVm) + self.fIgnoreSkippedVm = fIgnoreSkippedVm; + self.asParavirtModes = None; ##< If None, use the first PV mode of the test VM, otherwise all modes in this list. + + def findTestVmByName(self, sVmName): + """ + Returns the TestVm object with the given name. + Returns None if not found. + """ + + # The 'tst-' prefix is optional. + sAltName = sVmName if sVmName.startswith('tst-') else 'tst-' + sVmName; + + for oTestVm in self.aoTestVms: + if oTestVm.sVmName in (sVmName, sAltName): + return oTestVm; + return None; + + def getAllVmNames(self, sSep = ':'): + """ + Returns names of all the test VMs in the set separated by + sSep (defaults to ':'). + """ + sVmNames = ''; + for oTestVm in self.aoTestVms: + sName = oTestVm.sVmName; + if sName.startswith('tst-'): + sName = sName[4:]; + if sVmNames == '': + sVmNames = sName; + else: + sVmNames = sVmNames + sSep + sName; + return sVmNames; + + def showUsage(self): + """ + Invoked by vbox.TestDriver. + """ + reporter.log(''); + reporter.log('Test VM selection and general config options:'); + reporter.log(' --virt-modes <m1[:m2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asVirtModesDef))); + reporter.log(' --skip-virt-modes <m1[:m2[:...]]>'); + reporter.log(' Use this to avoid hwvirt or hwvirt-np when not supported by the host'); + reporter.log(' since we cannot detect it using the main API. Use after --virt-modes.'); + reporter.log(' --cpu-counts <c1[:c2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(str(c) for c in self.acCpusDef))); + reporter.log(' --test-vms <vm1[:vm2[:...]]>'); + reporter.log(' Test the specified VMs in the given order. Use this to change'); + reporter.log(' the execution order or limit the choice of VMs'); + reporter.log(' Default: %s (all)' % (self.getAllVmNames(),)); + reporter.log(' --skip-vms <vm1[:vm2[:...]]>'); + reporter.log(' Skip the specified VMs when testing.'); + reporter.log(' --snapshot-restore-current'); + reporter.log(' Restores the current snapshot and resumes execution.'); + reporter.log(' --paravirt-modes <pv1[:pv2[:...]]>'); + reporter.log(' Set of paravirtualized providers (modes) to tests. Intersected with what the test VM supports.'); + reporter.log(' Default is the first PV mode the test VMs support, generally same as "legacy".'); + reporter.log(' --with-nested-hwvirt-only'); + reporter.log(' Test VMs using nested hardware-virtualization only.'); + reporter.log(' --without-nested-hwvirt-only'); + reporter.log(' Test VMs not using nested hardware-virtualization only.'); + ## @todo Add more options for controlling individual VMs. + return True; + + def parseOption(self, asArgs, iArg): + """ + Parses the set test vm set options (--test-vms and --skip-vms), modifying the set + Invoked by the testdriver method with the same name. + + Keyword arguments: + asArgs -- The argument vector. + iArg -- The index of the current argument. + + Returns iArg if the option was not recognized and the caller should handle it. + Returns the index of the next argument when something is consumed. + + In the event of a syntax error, a InvalidOption or QuietInvalidOption + is thrown. + """ + + if asArgs[iArg] == '--virt-modes': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--virt-modes" takes a colon separated list of modes'); + + self.asVirtModes = asArgs[iArg].split(':'); + for s in self.asVirtModes: + if s not in self.asVirtModesDef: + raise base.InvalidOption('The "--virt-modes" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asVirtModesDef))); + + elif asArgs[iArg] == '--skip-virt-modes': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--skip-virt-modes" takes a colon separated list of modes'); + + for s in asArgs[iArg].split(':'): + if s not in self.asVirtModesDef: + raise base.InvalidOption('The "--virt-modes" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asVirtModesDef))); + if s in self.asVirtModes: + self.asVirtModes.remove(s); + + elif asArgs[iArg] == '--cpu-counts': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--cpu-counts" takes a colon separated list of cpu counts'); + + self.acCpus = []; + for s in asArgs[iArg].split(':'): + try: c = int(s); + except: raise base.InvalidOption('The "--cpu-counts" value "%s" is not an integer' % (s,)); + if c <= 0: raise base.InvalidOption('The "--cpu-counts" value "%s" is zero or negative' % (s,)); + self.acCpus.append(c); + + elif asArgs[iArg] == '--test-vms': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--test-vms" takes colon separated list'); + + for oTestVm in self.aoTestVms: + oTestVm.fSkip = True; + + asTestVMs = asArgs[iArg].split(':'); + for s in asTestVMs: + oTestVm = self.findTestVmByName(s); + if oTestVm is None: + raise base.InvalidOption('The "--test-vms" value "%s" is not valid; valid values are: %s' \ + % (s, self.getAllVmNames(' '))); + oTestVm.fSkip = False; + + elif asArgs[iArg] == '--skip-vms': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--skip-vms" takes colon separated list'); + + asTestVMs = asArgs[iArg].split(':'); + for s in asTestVMs: + oTestVm = self.findTestVmByName(s); + if oTestVm is None: + reporter.log('warning: The "--test-vms" value "%s" does not specify any of our test VMs.' % (s,)); + else: + oTestVm.fSkip = True; + + elif asArgs[iArg] == '--snapshot-restore-current': + for oTestVm in self.aoTestVms: + if oTestVm.fSkip is False: + oTestVm.fSnapshotRestoreCurrent = True; + reporter.log('VM "%s" will be restored.' % (oTestVm.sVmName)); + + elif asArgs[iArg] == '--paravirt-modes': + iArg += 1 + if iArg >= len(asArgs): + raise base.InvalidOption('The "--paravirt-modes" takes a colon separated list of modes'); + + self.asParavirtModes = asArgs[iArg].split(':') + for sPvMode in self.asParavirtModes: + if sPvMode not in g_kasParavirtProviders: + raise base.InvalidOption('The "--paravirt-modes" value "%s" is not valid; valid values are: %s' + % (sPvMode, ', '.join(g_kasParavirtProviders),)); + if not self.asParavirtModes: + self.asParavirtModes = None; + + # HACK ALERT! Reset the random paravirt selection for members. + for oTestVm in self.aoTestVms: + oTestVm.asParavirtModesSup = oTestVm.asParavirtModesSupOrg; + + elif asArgs[iArg] == '--with-nested-hwvirt-only': + for oTestVm in self.aoTestVms: + if oTestVm.fNstHwVirt is False: + oTestVm.fSkip = True; + + elif asArgs[iArg] == '--without-nested-hwvirt-only': + for oTestVm in self.aoTestVms: + if oTestVm.fNstHwVirt is True: + oTestVm.fSkip = True; + + else: + return iArg; + return iArg + 1; + + def getResourceSet(self): + """ + Called vbox.TestDriver.getResourceSet and returns a list of paths of resources. + """ + asResources = []; + for oTestVm in self.aoTestVms: + if not oTestVm.fSkip: + if isinstance(oTestVm, BaseTestVm): # Temporarily... + asResources.extend(oTestVm.getResourceSet()); + else: + if oTestVm.sHd is not None: + asResources.append(oTestVm.sHd); + if oTestVm.sDvdImage is not None: + asResources.append(oTestVm.sDvdImage); + return asResources; + + def actionConfig(self, oTestDrv, eNic0AttachType = None, sDvdImage = None): + """ + For base.TestDriver.actionConfig. Configure the VMs with defaults and + a few tweaks as per arguments. + + Returns True if successful. + Returns False if not. + """ + + for oTestVm in self.aoTestVms: + if oTestVm.fSkip: + continue; + if oTestVm.skipCreatingVm(oTestDrv): + oTestVm.fSkip = True; + continue; + + if oTestVm.fSnapshotRestoreCurrent: + # If we want to restore a VM we don't need to create + # the machine anymore -- so just add it to the test VM list. + oVM = oTestDrv.addTestMachine(oTestVm.sVmName); + else: + oVM = oTestVm.createVm(oTestDrv, eNic0AttachType, sDvdImage); + if oVM is None: + return False; + + return True; + + def _removeUnsupportedVirtModes(self, oTestDrv): + """ + Removes unsupported virtualization modes. + """ + if 'hwvirt' in self.asVirtModes and not oTestDrv.hasHostHwVirt(): + reporter.log('Hardware assisted virtualization is not available on the host, skipping it.'); + self.asVirtModes.remove('hwvirt'); + + if 'hwvirt-np' in self.asVirtModes and not oTestDrv.hasHostNestedPaging(): + reporter.log('Nested paging not supported by the host, skipping it.'); + self.asVirtModes.remove('hwvirt-np'); + + if 'raw' in self.asVirtModes and not oTestDrv.hasRawModeSupport(): + reporter.log('Raw-mode virtualization is not available in this build (or perhaps for this host), skipping it.'); + self.asVirtModes.remove('raw'); + + return True; + + def actionExecute(self, oTestDrv, fnCallback): # pylint: disable=too-many-locals + """ + For base.TestDriver.actionExecute. Calls the callback function for + each of the VMs and basic configuration variations (virt-mode and cpu + count). + + Returns True if all fnCallback calls returned True, otherwise False. + + The callback can return True, False or None. The latter is for when the + test is skipped. (True is for success, False is for failure.) + """ + + self._removeUnsupportedVirtModes(oTestDrv); + cMaxCpus = oTestDrv.getHostCpuCount(); + + # + # The test loop. + # + fRc = True; + for oTestVm in self.aoTestVms: + if oTestVm.fSkip and self.fIgnoreSkippedVm: + reporter.log2('Ignoring VM %s (fSkip = True).' % (oTestVm.sVmName,)); + continue; + reporter.testStart(oTestVm.sVmName); + if oTestVm.fSkip: + reporter.testDone(fSkipped = True); + continue; + + # Intersect the supported modes and the ones being testing. + asVirtModesSup = [sMode for sMode in oTestVm.asVirtModesSup if sMode in self.asVirtModes]; + + # Ditto for CPUs. + acCpusSup = [cCpus for cCpus in oTestVm.acCpusSup if cCpus in self.acCpus]; + + # Ditto for paravirtualization modes, except if not specified we got a less obvious default. + if self.asParavirtModes is not None and oTestDrv.fpApiVer >= 5.0: + asParavirtModes = [sPvMode for sPvMode in oTestVm.asParavirtModesSup if sPvMode in self.asParavirtModes]; + assert None not in asParavirtModes; + elif oTestDrv.fpApiVer >= 5.0: + asParavirtModes = (oTestVm.asParavirtModesSup[0],); + assert asParavirtModes[0] is not None; + else: + asParavirtModes = (None,); + + for cCpus in acCpusSup: + if cCpus == 1: + reporter.testStart('1 cpu'); + else: + reporter.testStart('%u cpus' % (cCpus)); + if cCpus > cMaxCpus: + reporter.testDone(fSkipped = True); + continue; + + cTests = 0; + for sVirtMode in asVirtModesSup: + if sVirtMode == 'raw' and cCpus > 1: + continue; + reporter.testStart('%s' % ( g_dsVirtModeDescs[sVirtMode], ) ); + cStartTests = cTests; + + for sParavirtMode in asParavirtModes: + if sParavirtMode is not None: + assert oTestDrv.fpApiVer >= 5.0; + reporter.testStart('%s' % ( sParavirtMode, ) ); + + # Reconfigure the VM. + try: + (rc2, oVM) = oTestVm.getReconfiguredVm(oTestDrv, cCpus, sVirtMode, sParavirtMode = sParavirtMode); + except KeyboardInterrupt: + raise; + except: + reporter.errorXcpt(cFrames = 9); + rc2 = False; + if rc2 is True: + # Do the testing. + try: + rc2 = fnCallback(oVM, oTestVm); + except KeyboardInterrupt: + raise; + except: + reporter.errorXcpt(cFrames = 9); + rc2 = False; + if rc2 is False: + reporter.maybeErr(reporter.testErrorCount() == 0, 'fnCallback failed'); + elif rc2 is False: + reporter.log('getReconfiguredVm failed'); + if rc2 is False: + fRc = False; + + cTests = cTests + (rc2 is not None); + if sParavirtMode is not None: + reporter.testDone(fSkipped = (rc2 is None)); + + reporter.testDone(fSkipped = cTests == cStartTests); + + reporter.testDone(fSkipped = cTests == 0); + + _, cErrors = reporter.testDone(); + if cErrors > 0: + fRc = False; + return fRc; + + def enumerateTestVms(self, fnCallback): + """ + Enumerates all the 'active' VMs. + + Returns True if all fnCallback calls returned True. + Returns False if any returned False. + Returns None immediately if fnCallback returned None. + """ + fRc = True; + for oTestVm in self.aoTestVms: + if not oTestVm.fSkip: + fRc2 = fnCallback(oTestVm); + if fRc2 is None: + return fRc2; + fRc = fRc and fRc2; + return fRc; + + + +class TestVmManager(object): + """ + Test VM manager. + """ + + ## @name VM grouping flags + ## @{ + kfGrpSmoke = g_kfGrpSmoke; + kfGrpStandard = g_kfGrpStandard; + kfGrpStdSmoke = g_kfGrpStdSmoke; + kfGrpWithGAs = g_kfGrpWithGAs; + kfGrpNoTxs = g_kfGrpNoTxs; + kfGrpAncient = g_kfGrpAncient; + kfGrpExotic = g_kfGrpExotic; + ## @} + + kaTestVMs = ( + # Note: The images in the 6.1 folder all have been pre-configured to allow for Guest Additions installation + # (come with build essentials, kernel headers). + # Linux + TestVm('tst-ubuntu-18_04_3-64', kfGrpStdSmoke, sHd = '6.1/ubuntu-18_04_3-amd64-2.vdi', + sKind = 'Ubuntu_64', acCpusSup = range(1, 33), fIoApic = True, + asParavirtModesSup = [g_ksParavirtProviderKVM,]), + # Note: Deprecated; had SELinux + Screensaver (black screen) enabled. + #TestVm('tst-ol-8_1-64-efi', kfGrpStdSmoke, sHd = '6.1/efi/ol-8_1-efi-amd64.vdi', + # sKind = 'Oracle_64', acCpusSup = range(1, 33), fIoApic = True, sFirmwareType = 'efi', + # asParavirtModesSup = [g_ksParavirtProviderKVM,]), + TestVm('tst-ol-8_1-64-efi', kfGrpStdSmoke, sHd = '6.1/efi/ol-8_1-efi-amd64-2.vdi', + sKind = 'Oracle_64', acCpusSup = range(1, 33), fIoApic = True, sFirmwareType = 'efi', + asParavirtModesSup = [g_ksParavirtProviderKVM,]), + TestVm('tst-ol-6u2-32', kfGrpStdSmoke, sHd = '6.1/ol-6u2-x86.vdi', + sKind = 'Oracle', acCpusSup = range(1, 33), fIoApic = True, + asParavirtModesSup = [g_ksParavirtProviderKVM,]), + TestVm('tst-ubuntu-15_10-64-efi', kfGrpStdSmoke, sHd = '6.1/efi/ubuntu-15_10-efi-amd64-3.vdi', + sKind = 'Ubuntu_64', acCpusSup = range(1, 33), fIoApic = True, sFirmwareType = 'efi', + asParavirtModesSup = [g_ksParavirtProviderKVM,]), + # Note: Deprecated / buggy; use the one in the 6.1 folder. + #TestVm('tst-ubuntu-15_10-64-efi', kfGrpStdSmoke, sHd = '4.2/efi/ubuntu-15_10-efi-amd64.vdi', + # sKind = 'Ubuntu_64', acCpusSup = range(1, 33), fIoApic = True, sFirmwareType = 'efi', + # asParavirtModesSup = [g_ksParavirtProviderKVM,]), + TestVm('tst-rhel5', kfGrpSmoke, sHd = '3.0/tcp/rhel5.vdi', + sKind = 'RedHat', acCpusSup = range(1, 33), fIoApic = True, sNic0AttachType = 'nat'), + TestVm('tst-arch', kfGrpStandard, sHd = '4.2/usb/tst-arch.vdi', + sKind = 'ArchLinux_64', acCpusSup = range(1, 33), fIoApic = True, sNic0AttachType = 'nat'), + # disabled 2019-03-08 klaus - fails all over the place and pollutes the test results + #TestVm('tst-ubuntu-1804-64', kfGrpStdSmoke, sHd = '4.2/ubuntu-1804/t-ubuntu-1804-64.vdi', + # sKind = 'Ubuntu_64', acCpusSup = range(1, 33), fIoApic = True), + TestVm('tst-ol76-64', kfGrpStdSmoke, sHd = '4.2/ol76/t-ol76-64.vdi', + sKind = 'Oracle_64', acCpusSup = range(1, 33), fIoApic = True), + TestVm('tst-ubuntu-20_04-64-amdvi', kfGrpStdSmoke, sHd = '6.1/ubuntu-20_04-64.vdi', + sKind = 'Ubuntu_64', acCpusSup = range(1, 33), fIoApic = True, + asParavirtModesSup = [g_ksParavirtProviderKVM,], sNic0AttachType = 'nat', sChipsetType = 'ich9', + sIommuType = 'amd'), + TestVm('tst-ubuntu-20_04-64-vtd', kfGrpStdSmoke, sHd = '6.1/ubuntu-20_04-64.vdi', + sKind = 'Ubuntu_64', acCpusSup = range(1, 33), fIoApic = True, + asParavirtModesSup = [g_ksParavirtProviderKVM,], sNic0AttachType = 'nat', sChipsetType = 'ich9', + sIommuType = 'intel'), + + # Solaris + TestVm('tst-sol10', kfGrpSmoke, sHd = '3.0/tcp/solaris10.vdi', + sKind = 'Solaris', acCpusSup = range(1, 33), fPae = True, sNic0AttachType = 'bridged'), + TestVm('tst-sol10-64', kfGrpSmoke, sHd = '3.0/tcp/solaris10.vdi', + sKind = 'Solaris_64', acCpusSup = range(1, 33), sNic0AttachType = 'bridged'), + TestVm('tst-sol11u1', kfGrpSmoke, sHd = '4.2/nat/sol11u1/t-sol11u1.vdi', + sKind = 'Solaris11_64', acCpusSup = range(1, 33), sNic0AttachType = 'nat', fIoApic = True, + sHddControllerType = 'SATA Controller'), + #TestVm('tst-sol11u1-ich9', kfGrpSmoke, sHd = '4.2/nat/sol11u1/t-sol11u1.vdi', + # sKind = 'Solaris11_64', acCpusSup = range(1, 33), sNic0AttachType = 'nat', fIoApic = True, + # sHddControllerType = 'SATA Controller', sChipsetType = 'ich9'), + + # NT 3.x + TestVm('tst-nt310', kfGrpAncient, sHd = '5.2/great-old-ones/t-nt310/t-nt310.vdi', + sKind = 'WindowsNT3x', acCpusSup = [1], sHddControllerType = 'BusLogic SCSI Controller', + sDvdControllerType = 'BusLogic SCSI Controller'), + TestVm('tst-nt350', kfGrpAncient, sHd = '5.2/great-old-ones/t-nt350/t-nt350.vdi', + sKind = 'WindowsNT3x', acCpusSup = [1], sHddControllerType = 'BusLogic SCSI Controller', + sDvdControllerType = 'BusLogic SCSI Controller'), + TestVm('tst-nt351', kfGrpAncient, sHd = '5.2/great-old-ones/t-nt350/t-nt351.vdi', + sKind = 'WindowsNT3x', acCpusSup = [1], sHddControllerType = 'BusLogic SCSI Controller', + sDvdControllerType = 'BusLogic SCSI Controller'), + + # NT 4 + TestVm('tst-nt4sp1', kfGrpStdSmoke, sHd = '4.2/nat/nt4sp1/t-nt4sp1.vdi', + sKind = 'WindowsNT4', acCpusSup = [1], sNic0AttachType = 'nat'), + + TestVm('tst-nt4sp6', kfGrpStdSmoke, sHd = '4.2/nt4sp6/t-nt4sp6.vdi', + sKind = 'WindowsNT4', acCpusSup = range(1, 33)), + + # W2K + TestVm('tst-w2ksp4', kfGrpStdSmoke, sHd = '4.2/win2ksp4/t-win2ksp4.vdi', + sKind = 'Windows2000', acCpusSup = range(1, 33)), + + # XP + TestVm('tst-xppro', kfGrpStdSmoke, sHd = '4.2/nat/xppro/t-xppro.vdi', + sKind = 'WindowsXP', acCpusSup = range(1, 33), sNic0AttachType = 'nat'), + TestVm('tst-xpsp2', kfGrpStdSmoke, sHd = '4.2/xpsp2/t-winxpsp2.vdi', + sKind = 'WindowsXP', acCpusSup = range(1, 33), fIoApic = True), + TestVm('tst-xpsp2-halaacpi', kfGrpStdSmoke, sHd = '4.2/xpsp2/t-winxp-halaacpi.vdi', + sKind = 'WindowsXP', acCpusSup = range(1, 33), fIoApic = True), + TestVm('tst-xpsp2-halacpi', kfGrpStdSmoke, sHd = '4.2/xpsp2/t-winxp-halacpi.vdi', + sKind = 'WindowsXP', acCpusSup = range(1, 33), fIoApic = True), + TestVm('tst-xpsp2-halapic', kfGrpStdSmoke, sHd = '4.2/xpsp2/t-winxp-halapic.vdi', + sKind = 'WindowsXP', acCpusSup = range(1, 33), fIoApic = True), + TestVm('tst-xpsp2-halmacpi', kfGrpStdSmoke, sHd = '4.2/xpsp2/t-winxp-halmacpi.vdi', + sKind = 'WindowsXP', acCpusSup = range(2, 33), fIoApic = True), + TestVm('tst-xpsp2-halmps', kfGrpStdSmoke, sHd = '4.2/xpsp2/t-winxp-halmps.vdi', + sKind = 'WindowsXP', acCpusSup = range(2, 33), fIoApic = True), + + # W2K3 + TestVm('tst-win2k3ent', kfGrpSmoke, sHd = '3.0/tcp/win2k3ent-acpi.vdi', + sKind = 'Windows2003', acCpusSup = range(1, 33), fPae = True, sNic0AttachType = 'bridged'), + + # W7 + TestVm('tst-win7', kfGrpStdSmoke, sHd = '6.1/win7-32/t-win7-32-1.vdi', + sKind = 'Windows7', acCpusSup = range(1, 33), fIoApic = True), + # Note: Deprecated due to activation issues; use t-win7-32-1 instead. + #TestVm('tst-win7', kfGrpStdSmoke, sHd = '6.1/win7-32/t-win7-32.vdi', + # sKind = 'Windows7', acCpusSup = range(1, 33), fIoApic = True), + # Note: Deprecated; use the one in the 6.1 folder. + #TestVm('tst-win7', kfGrpStdSmoke, sHd = '4.2/win7-32/t-win7.vdi', + # sKind = 'Windows7', acCpusSup = range(1, 33), fIoApic = True), + + # W8 + TestVm('tst-win8-64', kfGrpStdSmoke, sHd = '4.2/win8-64/t-win8-64.vdi', + sKind = 'Windows8_64', acCpusSup = range(1, 33), fIoApic = True), + #TestVm('tst-win8-64-ich9', kfGrpStdSmoke, sHd = '4.2/win8-64/t-win8-64.vdi', + # sKind = 'Windows8_64', acCpusSup = range(1, 33), fIoApic = True, sChipsetType = 'ich9'), + + # W10 + TestVm('tst-win10-efi', kfGrpStdSmoke, sHd = '4.2/efi/win10-efi-x86.vdi', + sKind = 'Windows10', acCpusSup = range(1, 33), fIoApic = True, sFirmwareType = 'efi'), + TestVm('tst-win10-64-efi', kfGrpStdSmoke, sHd = '4.2/efi/win10-efi-amd64.vdi', + sKind = 'Windows10_64', acCpusSup = range(1, 33), fIoApic = True, sFirmwareType = 'efi'), + #TestVm('tst-win10-64-efi-ich9', kfGrpStdSmoke, sHd = '4.2/efi/win10-efi-amd64.vdi', + # sKind = 'Windows10_64', acCpusSup = range(1, 33), fIoApic = True, sFirmwareType = 'efi', sChipsetType = 'ich9'), + + # Nested hardware-virtualization + TestVm('tst-nsthwvirt-ubuntu-64', kfGrpStdSmoke, sHd = '5.3/nat/nsthwvirt-ubuntu64/t-nsthwvirt-ubuntu64.vdi', + sKind = 'Ubuntu_64', acCpusSup = range(1, 2), asVirtModesSup = ['hwvirt-np',], fIoApic = True, fNstHwVirt = True, + sNic0AttachType = 'nat'), + + # Audio testing. + TestVm('tst-audio-debian10-64', kfGrpStdSmoke, sHd = '6.1/audio/debian10-amd64-7.vdi', + sKind = 'Debian_64', acCpusSup = range(1, 33), fIoApic = True), + + # DOS and Old Windows. + AncientTestVm('tst-dos20', sKind = 'DOS', + sHd = '5.2/great-old-ones/t-dos20/t-dos20.vdi'), + AncientTestVm('tst-dos401-win30me', sKind = 'DOS', + sHd = '5.2/great-old-ones/t-dos401-win30me/t-dos401-win30me.vdi', cMBRamMax = 4), + AncientTestVm('tst-dos401-emm386-win30me', sKind = 'DOS', + sHd = '5.2/great-old-ones/t-dos401-emm386-win30me/t-dos401-emm386-win30me.vdi', cMBRamMax = 4), + AncientTestVm('tst-dos50-win31', sKind = 'DOS', + sHd = '5.2/great-old-ones/t-dos50-win31/t-dos50-win31.vdi'), + AncientTestVm('tst-dos50-emm386-win31', sKind = 'DOS', + sHd = '5.2/great-old-ones/t-dos50-emm386-win31/t-dos50-emm386-win31.vdi'), + AncientTestVm('tst-dos622', sKind = 'DOS', + sHd = '5.2/great-old-ones/t-dos622/t-dos622.vdi'), + AncientTestVm('tst-dos622-emm386', sKind = 'DOS', + sHd = '5.2/great-old-ones/t-dos622-emm386/t-dos622-emm386.vdi'), + AncientTestVm('tst-dos71', sKind = 'DOS', + sHd = '5.2/great-old-ones/t-dos71/t-dos71.vdi'), + + #AncientTestVm('tst-dos5-win311a', sKind = 'DOS', sHd = '5.2/great-old-ones/t-dos5-win311a/t-dos5-win311a.vdi'), + ); + + + def __init__(self, sResourcePath): + self.sResourcePath = sResourcePath; + + def selectSet(self, fGrouping, sTxsTransport = None, fCheckResources = True): + """ + Returns a VM set with the selected VMs. + """ + oSet = TestVmSet(oTestVmManager = self); + for oVm in self.kaTestVMs: + if oVm.fGrouping & fGrouping: + if sTxsTransport is None or oVm.sNic0AttachType is None or sTxsTransport == oVm.sNic0AttachType: + if not fCheckResources or not oVm.getMissingResources(self.sResourcePath): + oCopyVm = copy.deepcopy(oVm); + oCopyVm.oSet = oSet; + oSet.aoTestVms.append(oCopyVm); + return oSet; + + def getStandardVmSet(self, sTxsTransport): + """ + Gets the set of standard test VMs. + + This is supposed to do something seriously clever, like searching the + testrsrc tree for usable VMs, but for the moment it's all hard coded. :-) + """ + return self.selectSet(self.kfGrpStandard, sTxsTransport) + + def getSmokeVmSet(self, sTxsTransport = None): + """Gets a representative set of VMs for smoke testing. """ + return self.selectSet(self.kfGrpSmoke, sTxsTransport); + + def shutUpPyLint(self): + """ Shut up already! """ + return self.sResourcePath; diff --git a/src/VBox/ValidationKit/testdriver/vboxwrappers.py b/src/VBox/ValidationKit/testdriver/vboxwrappers.py new file mode 100755 index 00000000..075a19d2 --- /dev/null +++ b/src/VBox/ValidationKit/testdriver/vboxwrappers.py @@ -0,0 +1,3666 @@ +# -*- coding: utf-8 -*- +# $Id: vboxwrappers.py $ +# pylint: disable=too-many-lines + +""" +VirtualBox Wrapper Classes +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard Python imports. +import os; +import socket; +import sys; + +# Validation Kit imports. +from common import utils; +from common import netutils; +from testdriver import base; +from testdriver import reporter; +from testdriver import txsclient; +from testdriver import vboxcon; +from testdriver import vbox; +from testdriver.base import TdTaskBase; + + +def _ControllerNameToBusAndType(sController): + """ Translate a controller name to a storage bus. """ + if sController == "IDE Controller": + eBus = vboxcon.StorageBus_IDE; + eType = vboxcon.StorageControllerType_PIIX4; + elif sController == "SATA Controller": + eBus = vboxcon.StorageBus_SATA; + eType = vboxcon.StorageControllerType_IntelAhci; + elif sController == "Floppy Controller": + eType = vboxcon.StorageControllerType_I82078; + eBus = vboxcon.StorageBus_Floppy; + elif sController == "SAS Controller": + eBus = vboxcon.StorageBus_SAS; + eType = vboxcon.StorageControllerType_LsiLogicSas; + elif sController == "SCSI Controller": + eBus = vboxcon.StorageBus_SCSI; + eType = vboxcon.StorageControllerType_LsiLogic; + elif sController == "BusLogic SCSI Controller": + eBus = vboxcon.StorageBus_SCSI; + eType = vboxcon.StorageControllerType_BusLogic; + elif sController == "NVMe Controller": + eBus = vboxcon.StorageBus_PCIe; + eType = vboxcon.StorageControllerType_NVMe; + elif sController == "VirtIO SCSI Controller": + eBus = vboxcon.StorageBus_VirtioSCSI; + eType = vboxcon.StorageControllerType_VirtioSCSI; + else: + eBus = vboxcon.StorageBus_Null; + eType = vboxcon.StorageControllerType_Null; + return (eBus, eType); + + +def _nameMachineState(eState): + """ Gets the name (string) of a machine state.""" + if eState == vboxcon.MachineState_PoweredOff: return 'PoweredOff'; + if eState == vboxcon.MachineState_Saved: return 'Saved'; + if eState == vboxcon.MachineState_Teleported: return 'Teleported'; + if eState == vboxcon.MachineState_Aborted: return 'Aborted'; + if eState == vboxcon.MachineState_Running: return 'Running'; + if eState == vboxcon.MachineState_Paused: return 'Paused'; + if eState == vboxcon.MachineState_Stuck: return 'GuruMeditation'; + if eState == vboxcon.MachineState_Teleporting: return 'Teleporting'; + if eState == vboxcon.MachineState_LiveSnapshotting: return 'LiveSnapshotting'; + if eState == vboxcon.MachineState_Starting: return 'Starting'; + if eState == vboxcon.MachineState_Stopping: return 'Stopping'; + if eState == vboxcon.MachineState_Saving: return 'Saving'; + if eState == vboxcon.MachineState_Restoring: return 'Restoring'; + if eState == vboxcon.MachineState_TeleportingPausedVM: return 'TeleportingPausedVM'; + if eState == vboxcon.MachineState_TeleportingIn: return 'TeleportingIn'; + if eState == vboxcon.MachineState_DeletingSnapshotOnline: return 'DeletingSnapshotOnline'; + if eState == vboxcon.MachineState_DeletingSnapshotPaused: return 'DeletingSnapshotPaused'; + if eState == vboxcon.MachineState_RestoringSnapshot: return 'RestoringSnapshot'; + if eState == vboxcon.MachineState_DeletingSnapshot: return 'DeletingSnapshot'; + if eState == vboxcon.MachineState_SettingUp: return 'SettingUp'; + if hasattr(vboxcon, 'MachineState_FaultTolerantSyncing'): + if eState == vboxcon.MachineState_FaultTolerantSyncing: return 'FaultTolerantSyncing'; + if hasattr(vboxcon, 'MachineState_AbortedSaved'): # since r147033 / 7.0 + if eState == vboxcon.MachineState_AbortedSaved: return 'Aborted-Saved'; + return 'Unknown-%s' % (eState,); + + +class VirtualBoxWrapper(object): # pylint: disable=too-few-public-methods + """ + Wrapper around the IVirtualBox object that adds some (hopefully) useful + utility methods + + The real object can be accessed thru the o member. That said, members can + be accessed directly as well. + """ + + def __init__(self, oVBox, oVBoxMgr, fpApiVer, oTstDrv): + self.o = oVBox; + self.oVBoxMgr = oVBoxMgr; + self.fpApiVer = fpApiVer; + self.oTstDrv = oTstDrv; + + def __getattr__(self, sName): + # Try ourselves first. + try: + oAttr = self.__dict__[sName]; + except: + #try: + # oAttr = dir(self)[sName]; + #except AttributeError: + oAttr = getattr(self.o, sName); + return oAttr; + + # + # Utilities. + # + + def registerDerivedEventHandler(self, oSubClass, dArgs = None): + """ + Create an instance of the given VirtualBoxEventHandlerBase sub-class + and register it. + + The new instance is returned on success. None is returned on error. + """ + dArgsCopy = dArgs.copy() if dArgs is not None else {}; + dArgsCopy['oVBox'] = self; + return oSubClass.registerDerivedEventHandler(self.oVBoxMgr, self.fpApiVer, oSubClass, dArgsCopy, + self.o, 'IVirtualBox', 'IVirtualBoxCallback'); + + def deleteHdByLocation(self, sHdLocation): + """ + Deletes a disk image from the host, given it's location. + Returns True on success and False on failure. Error information is logged. + """ + try: + oIMedium = self.o.findHardDisk(sHdLocation); + except: + try: + if self.fpApiVer >= 4.1: + oIMedium = self.o.openMedium(sHdLocation, vboxcon.DeviceType_HardDisk, + vboxcon.AccessMode_ReadWrite, False); + elif self.fpApiVer >= 4.0: + oIMedium = self.o.openMedium(sHdLocation, vboxcon.DeviceType_HardDisk, + vboxcon.AccessMode_ReadWrite); + else: + oIMedium = self.o.openHardDisk(sHdLocation, vboxcon.AccessMode_ReadOnly, False, "", False, ""); + except: + return reporter.errorXcpt('failed to open hd "%s"' % (sHdLocation)); + return self.deleteHdByMedium(oIMedium) + + def deleteHdByMedium(self, oIMedium): + """ + Deletes a disk image from the host, given an IMedium reference. + Returns True on success and False on failure. Error information is logged. + """ + try: oProgressCom = oIMedium.deleteStorage(); + except: return reporter.errorXcpt('deleteStorage() for disk %s failed' % (oIMedium,)); + try: oProgress = ProgressWrapper(oProgressCom, self.oVBoxMgr, self.oTstDrv, 'delete disk %s' % (oIMedium.location)); + except: return reporter.errorXcpt(); + oProgress.wait(); + oProgress.logResult(); + return oProgress.isSuccess(); + + + +class ProgressWrapper(TdTaskBase): + """ + Wrapper around a progress object for making it a task and providing useful + utility methods. + The real progress object can be accessed thru the o member. + """ + + def __init__(self, oProgress, oVBoxMgr, oTstDrv, sName): + TdTaskBase.__init__(self, utils.getCallerName()); + self.o = oProgress; + self.oVBoxMgr = oVBoxMgr; + self.oTstDrv = oTstDrv; + self.sName = sName; + + def toString(self): + return '<%s sName=%s, oProgress=%s >' \ + % (TdTaskBase.toString(self), self.sName, self.o); + + # + # TdTaskBase overrides. + # + + def pollTask(self, fLocked = False): + """ + Overrides TdTaskBase.pollTask(). + + This method returns False until the progress object has completed. + """ + self.doQuickApiTest(); + try: + try: + if self.o.completed: + return True; + except: + pass; + finally: + self.oTstDrv.processPendingEvents(); + return False; + + def waitForTask(self, cMsTimeout = 0): + """ + Overrides TdTaskBase.waitForTask(). + Process XPCOM/COM events while waiting. + """ + msStart = base.timestampMilli(); + fState = self.pollTask(False); + while not fState: + cMsElapsed = base.timestampMilli() - msStart; + if cMsElapsed > cMsTimeout: + break; + cMsToWait = cMsTimeout - cMsElapsed; + cMsToWait = min(cMsToWait, 500); + try: + self.o.waitForCompletion(cMsToWait); + except KeyboardInterrupt: raise; + except: pass; + if self.fnProcessEvents: + self.fnProcessEvents(); + reporter.doPollWork('ProgressWrapper.waitForTask'); + fState = self.pollTask(False); + return fState; + + # + # Utility methods. + # + + def isSuccess(self): + """ + Tests if the progress object completed successfully. + Returns True on success, False on failure or incomplete. + """ + if not self.isCompleted(): + return False; + return self.getResult() >= 0; + + def isCompleted(self): + """ + Wrapper around IProgress.completed. + """ + return self.pollTask(); + + def isCancelable(self): + """ + Wrapper around IProgress.cancelable. + """ + try: + fRc = self.o.cancelable; + except: + reporter.logXcpt(); + fRc = False; + return fRc; + + def wasCanceled(self): + """ + Wrapper around IProgress.canceled. + """ + try: + fRc = self.o.canceled; + except: + reporter.logXcpt(self.sName); + fRc = False; + return fRc; + + def cancel(self): + """ + Wrapper around IProgress.cancel() + Returns True on success, False on failure (logged as error). + """ + try: + self.o.cancel(); + except: + reporter.errorXcpt(self.sName); + return False; + return True; + + def getResult(self): + """ + Wrapper around IProgress.resultCode. + """ + try: + iRc = self.o.resultCode; + except: + reporter.logXcpt(self.sName); + iRc = -1; + return iRc; + + def getErrInfoResultCode(self): + """ + Wrapper around IProgress.errorInfo.resultCode. + + Returns the string on success, -1 on bad objects (logged as error), and + -2 on missing errorInfo object. + """ + iRc = -1; + try: + oErrInfo = self.o.errorInfo; + except: + reporter.errorXcpt(self.sName); + else: + if oErrInfo is None: + iRc = -2; + else: + try: + iRc = oErrInfo.resultCode; + except: + reporter.errorXcpt(); + return iRc; + + def getErrInfoText(self): + """ + Wrapper around IProgress.errorInfo.text. + + Returns the string on success, None on failure. Missing errorInfo is + not logged as an error, all other failures are. + """ + sText = None; + try: + oErrInfo = self.o.errorInfo; + except: + reporter.log2Xcpt(self.sName); + else: + if oErrInfo is not None: + try: + sText = oErrInfo.text; + except: + reporter.errorXcpt(); + return sText; + + def stringifyErrorInfo(self): + """ + Formats IProgress.errorInfo into a string. + """ + try: + oErrInfo = self.o.errorInfo; + except: + reporter.logXcpt(self.sName); + sErr = 'no error info'; + else: + sErr = vbox.stringifyErrorInfo(oErrInfo); + return sErr; + + def stringifyResult(self): + """ + Stringify the result. + """ + if self.isCompleted(): + if self.wasCanceled(): + sRet = 'Progress %s: Canceled, hrc=%s' % (self.sName, vbox.ComError.toString(self.getResult())); + elif self.getResult() == 0: + sRet = 'Progress %s: Success' % (self.sName,); + elif self.getResult() > 0: + sRet = 'Progress %s: Success (hrc=%s)' % (self.sName, vbox.ComError.toString(self.getResult())); + else: + sRet = 'Progress %s: Failed! %s' % (self.sName, self.stringifyErrorInfo()); + else: + sRet = 'Progress %s: Not completed yet...' % (self.sName); + return sRet; + + def logResult(self, fIgnoreErrors = False): + """ + Logs the result, failure logged as error unless fIgnoreErrors is True. + Return True on success, False on failure (and fIgnoreErrors is false). + """ + sText = self.stringifyResult(); + if self.isCompleted() and self.getResult() < 0 and fIgnoreErrors is False: + return reporter.error(sText); + reporter.log(sText); + return True; + + def waitOnProgress(self, cMsInterval = 1000): + """ + See vbox.TestDriver.waitOnProgress. + """ + self.doQuickApiTest(); + return self.oTstDrv.waitOnProgress(self.o, cMsInterval); + + def wait(self, cMsTimeout = 60000, fErrorOnTimeout = True, cMsInterval = 1000): + """ + Wait on the progress object for a while. + + Returns the resultCode of the progress object if completed. + Returns -1 on timeout, logged as error if fErrorOnTimeout is set. + Returns -2 is the progress object is invalid or waitForCompletion + fails (logged as errors). + """ + msStart = base.timestampMilli(); + while True: + self.oTstDrv.processPendingEvents(); + self.doQuickApiTest(); + try: + if self.o.completed: + break; + except: + reporter.errorXcpt(self.sName); + return -2; + self.oTstDrv.processPendingEvents(); + + cMsElapsed = base.timestampMilli() - msStart; + if cMsElapsed > cMsTimeout: + if fErrorOnTimeout: + reporter.error('Timing out after waiting for %u s on "%s"' % (cMsTimeout / 1000, self.sName)) + return -1; + + try: + self.o.waitForCompletion(cMsInterval); + except: + reporter.errorXcpt(self.sName); + return -2; + reporter.doPollWork('ProgressWrapper.wait'); + + try: + rc = self.o.resultCode; + except: + rc = -2; + reporter.errorXcpt(self.sName); + self.oTstDrv.processPendingEvents(); + return rc; + + def waitForOperation(self, iOperation, cMsTimeout = 60000, fErrorOnTimeout = True, cMsInterval = 1000, \ + fIgnoreErrors = False): + """ + Wait for the completion of a operation. + + Negative iOperation values are relative to operationCount (this + property may changed at runtime). + + Returns 0 if the operation completed normally. + Returns -1 on timeout, logged as error if fErrorOnTimeout is set. + Returns -2 is the progress object is invalid or waitForCompletion + fails (logged as errors). + Returns -3 if if the operation completed with an error, this is logged + as an error. + """ + msStart = base.timestampMilli(); + while True: + self.oTstDrv.processPendingEvents(); + self.doQuickApiTest(); + try: + iCurrentOperation = self.o.operation; + cOperations = self.o.operationCount; + if iOperation >= 0: + iRealOperation = iOperation; + else: + iRealOperation = cOperations + iOperation; + + if iCurrentOperation > iRealOperation: + return 0; + if iCurrentOperation == iRealOperation \ + and iRealOperation >= cOperations - 1 \ + and self.o.completed: + if self.o.resultCode < 0: + self.logResult(fIgnoreErrors); + return -3; + return 0; + except: + if fIgnoreErrors: + reporter.logXcpt(); + else: + reporter.errorXcpt(); + return -2; + self.oTstDrv.processPendingEvents(); + + cMsElapsed = base.timestampMilli() - msStart; + if cMsElapsed > cMsTimeout: + if fErrorOnTimeout: + if fIgnoreErrors: + reporter.log('Timing out after waiting for %s s on "%s" operation %d' \ + % (cMsTimeout / 1000, self.sName, iOperation)) + else: + reporter.error('Timing out after waiting for %s s on "%s" operation %d' \ + % (cMsTimeout / 1000, self.sName, iOperation)) + return -1; + + try: + self.o.waitForOperationCompletion(iRealOperation, cMsInterval); + except: + if fIgnoreErrors: + reporter.logXcpt(self.sName); + else: + reporter.errorXcpt(self.sName); + return -2; + reporter.doPollWork('ProgressWrapper.waitForOperation'); + # Not reached. + return -3; # Make pylin happy (for now). + + def doQuickApiTest(self): + """ + Queries everything that is stable and easy to get at and checks that + they don't throw errors. + """ + if True is True: # pylint: disable=comparison-with-itself + try: + iPct = self.o.operationPercent; + sDesc = self.o.description; + fCancelable = self.o.cancelable; + cSecsRemain = self.o.timeRemaining; + fCanceled = self.o.canceled; + fCompleted = self.o.completed; + iOp = self.o.operation; + cOps = self.o.operationCount; + iOpPct = self.o.operationPercent; + sOpDesc = self.o.operationDescription; + except: + reporter.errorXcpt('%s: %s' % (self.sName, self.o,)); + return False; + try: + # Very noisy -- only enable for debugging purposes. + #reporter.log2('%s: op=%u/%u/%s: %u%%; total=%u%% cancel=%s/%s compl=%s rem=%us; desc=%s' \ + # % (self.sName, iOp, cOps, sOpDesc, iOpPct, iPct, fCanceled, fCancelable, fCompleted, \ + # cSecsRemain, sDesc)); + _ = iPct; _ = sDesc; _ = fCancelable; _ = cSecsRemain; _ = fCanceled; _ = fCompleted; _ = iOp; + _ = cOps; _ = iOpPct; _ = sOpDesc; + except: + reporter.errorXcpt(); + return False; + + return True; + + +class SessionWrapper(TdTaskBase): + """ + Wrapper around a machine session. The real session object can be accessed + thru the o member (short is good, right :-). + """ + + def __init__(self, oSession, oVM, oVBox, oVBoxMgr, oTstDrv, fRemoteSession, sFallbackName = None, sLogFile = None): + """ + Initializes the session wrapper. + """ + TdTaskBase.__init__(self, utils.getCallerName()); + self.o = oSession; + self.oVBox = oVBox; + self.oVBoxMgr = oVBoxMgr; + self.oVM = oVM; # Not the session machine. Useful backdoor... + self.oTstDrv = oTstDrv; + self.fpApiVer = oTstDrv.fpApiVer; + self.fRemoteSession = fRemoteSession; + self.sLogFile = sLogFile; + self.oConsoleEventHandler = None; + self.uPid = None; + self.fPidFile = True; + self.fHostMemoryLow = False; # see signalHostMemoryLow; read-only for outsiders. + + try: + self.sName = oSession.machine.name; + except: + if sFallbackName is not None: + self.sName = sFallbackName; + else: + try: self.sName = str(oSession.machine); + except: self.sName = 'is-this-vm-already-off' + + try: + self.sUuid = oSession.machine.id; + except: + self.sUuid = None; + + # Try cache the SessionPID. + self.getPid(); + + def __del__(self): + """ + Destructor that makes sure the callbacks are deregistered and + that the session is closed. + """ + self.deregisterEventHandlerForTask(); + + if self.o is not None: + try: + self.close(); + reporter.log('close session %s' % (self.o)); + except: + pass; + self.o = None; + + TdTaskBase.__del__(self); + + def toString(self): + return '<%s: sUuid=%s, sName=%s, uPid=%s, sDbgCreated=%s, fRemoteSession=%s, oSession=%s,' \ + ' oConsoleEventHandler=%s, oVM=%s >' \ + % (type(self).__name__, self.sUuid, self.sName, self.uPid, self.sDbgCreated, self.fRemoteSession, + self.o, self.oConsoleEventHandler, self.oVM,); + + def __str__(self): + return self.toString(); + + # + # TdTaskBase overrides. + # + + def __pollTask(self): + """ Internal poller """ + # Poll for events after doing the remote GetState call, otherwise we + # might end up sleepless because XPCOM queues a cleanup event. + try: + try: + eState = self.o.machine.state; + except Exception as oXcpt: + if vbox.ComError.notEqual(oXcpt, vbox.ComError.E_UNEXPECTED): + reporter.logXcpt(); + return True; + finally: + self.oTstDrv.processPendingEvents(); + + # Switch + if eState == vboxcon.MachineState_Running: + return False; + if eState == vboxcon.MachineState_Paused: + return False; + if eState == vboxcon.MachineState_Teleporting: + return False; + if eState == vboxcon.MachineState_LiveSnapshotting: + return False; + if eState == vboxcon.MachineState_Starting: + return False; + if eState == vboxcon.MachineState_Stopping: + return False; + if eState == vboxcon.MachineState_Saving: + return False; + if eState == vboxcon.MachineState_Restoring: + return False; + if eState == vboxcon.MachineState_TeleportingPausedVM: + return False; + if eState == vboxcon.MachineState_TeleportingIn: + return False; + + # *Beeep* fudge! + if self.fpApiVer < 3.2 \ + and eState == vboxcon.MachineState_PoweredOff \ + and self.getAgeAsMs() < 3000: + return False; + + reporter.log('SessionWrapper::pollTask: eState=%s' % (eState)); + return True; + + + def pollTask(self, fLocked = False): + """ + Overrides TdTaskBase.pollTask(). + + This method returns False while the VM is online and running normally. + """ + + # Call super to check if the task was signalled by runtime error or similar, + # if not then check the VM state via __pollTask. + fRc = super(SessionWrapper, self).pollTask(fLocked); + if not fRc: + fRc = self.__pollTask(); + + # HACK ALERT: Lazily try registering the console event handler if + # we're not ready. + if not fRc and self.oConsoleEventHandler is None: + self.registerEventHandlerForTask(); + + # HACK ALERT: Lazily try get the PID and add it to the PID file. + if not fRc and self.uPid is None: + self.getPid(); + + return fRc; + + def waitForTask(self, cMsTimeout = 0): + """ + Overrides TdTaskBase.waitForTask(). + Process XPCOM/COM events while waiting. + """ + msStart = base.timestampMilli(); + fState = self.pollTask(False); + while not fState: + cMsElapsed = base.timestampMilli() - msStart; + if cMsElapsed > cMsTimeout: + break; + cMsSleep = cMsTimeout - cMsElapsed; + cMsSleep = min(cMsSleep, 10000); + try: self.oVBoxMgr.waitForEvents(cMsSleep); + except KeyboardInterrupt: raise; + except: pass; + if self.fnProcessEvents: + self.fnProcessEvents(); + reporter.doPollWork('SessionWrapper.waitForTask'); + fState = self.pollTask(False); + return fState; + + def setTaskOwner(self, oOwner): + """ + HACK ALERT! + Overrides TdTaskBase.setTaskOwner() so we can try call + registerEventHandlerForTask() again when when the testdriver calls + addTask() after VM has been spawned. Related to pollTask() above. + + The testdriver must not add the task too early for this to work! + """ + if oOwner is not None: + self.registerEventHandlerForTask() + return TdTaskBase.setTaskOwner(self, oOwner); + + + # + # Task helpers. + # + + def registerEventHandlerForTask(self): + """ + Registers the console event handlers for working the task state. + """ + if self.oConsoleEventHandler is not None: + return True; + self.oConsoleEventHandler = self.registerDerivedEventHandler(vbox.SessionConsoleEventHandler, {}, False); + return self.oConsoleEventHandler is not None; + + def deregisterEventHandlerForTask(self): + """ + Deregisters the console event handlers. + """ + if self.oConsoleEventHandler is not None: + self.oConsoleEventHandler.unregister(); + self.oConsoleEventHandler = None; + + def signalHostMemoryLow(self): + """ + Used by a runtime error event handler to indicate that we're low on memory. + Signals the task. + """ + self.fHostMemoryLow = True; + self.signalTask(); + return True; + + def needsPoweringOff(self): + """ + Examins the machine state to see if the VM needs powering off. + """ + try: + try: + eState = self.o.machine.state; + except Exception as oXcpt: + if vbox.ComError.notEqual(oXcpt, vbox.ComError.E_UNEXPECTED): + reporter.logXcpt(); + return False; + finally: + self.oTstDrv.processPendingEvents(); + + # Switch + if eState == vboxcon.MachineState_Running: + return True; + if eState == vboxcon.MachineState_Paused: + return True; + if eState == vboxcon.MachineState_Stuck: + return True; + if eState == vboxcon.MachineState_Teleporting: + return True; + if eState == vboxcon.MachineState_LiveSnapshotting: + return True; + if eState == vboxcon.MachineState_Starting: + return True; + if eState == vboxcon.MachineState_Saving: + return True; + if eState == vboxcon.MachineState_Restoring: + return True; + if eState == vboxcon.MachineState_TeleportingPausedVM: + return True; + if eState == vboxcon.MachineState_TeleportingIn: + return True; + if hasattr(vboxcon, 'MachineState_FaultTolerantSyncing'): + if eState == vboxcon.MachineState_FaultTolerantSyncing: + return True; + return False; + + def assertPoweredOff(self): + """ + Asserts that the VM is powered off, reporting an error if not. + Returns True if powered off, False + error msg if not. + """ + try: + try: + eState = self.oVM.state; + except Exception: + reporter.errorXcpt(); + return True; + finally: + self.oTstDrv.processPendingEvents(); + + if eState == vboxcon.MachineState_PoweredOff: + return True; + reporter.error('Expected machine state "PoweredOff", machine is in the "%s" state instead.' + % (_nameMachineState(eState),)); + return False; + + def getMachineStateWithName(self): + """ + Gets the current machine state both as a constant number/whatever and + as a human readable string. On error, the constants will be set to + None and the string will be the error message. + """ + try: + eState = self.oVM.state; + except: + return (None, '[error getting state: %s]' % (self.oVBoxMgr.xcptToString(),)); + finally: + self.oTstDrv.processPendingEvents(); + return (eState, _nameMachineState(eState)); + + def reportPrematureTermination(self, sPrefix = ''): + """ + Reports a premature virtual machine termination. + Returns False to facilitate simpler error paths. + """ + + reporter.error(sPrefix + 'The virtual machine terminated prematurely!!'); + (enmState, sStateNm) = self.getMachineStateWithName(); + reporter.error(sPrefix + 'Machine state: %s' % (sStateNm,)); + + if enmState is not None \ + and enmState == vboxcon.MachineState_Aborted \ + and self.uPid is not None: + # + # Look for process crash info. + # + def addCrashFile(sLogFile, fBinary): + """ processCollectCrashInfo callback. """ + reporter.addLogFile(sLogFile, 'crash/dump/vm' if fBinary else 'crash/report/vm'); + utils.processCollectCrashInfo(self.uPid, reporter.log, addCrashFile); + + return False; + + + + # + # ISession / IMachine / ISomethingOrAnother wrappers. + # + + def close(self): + """ + Closes the session if it's open and removes it from the + vbox.TestDriver.aoRemoteSessions list. + Returns success indicator. + """ + fRc = True; + if self.o is not None: + # Get the pid in case we need to kill the process later on. + self.getPid(); + + # Try close it. + try: + if self.fpApiVer < 3.3: + self.o.close(); + else: + self.o.unlockMachine(); + self.o = None; + except KeyboardInterrupt: + raise; + except: + # Kludge to ignore VBoxSVC's closing of our session when the + # direct session closes / VM process terminates. Fun! + try: fIgnore = self.o.state == vboxcon.SessionState_Unlocked; + except: fIgnore = False; + if fIgnore: + self.o = None; # Must prevent a retry during GC. + else: + reporter.errorXcpt('ISession::unlockMachine failed on %s' % (self.o)); + fRc = False; + + # Remove it from the remote session list if applicable (not 100% clean). + if fRc and self.fRemoteSession: + try: + if self in self.oTstDrv.aoRemoteSessions: + reporter.log2('SessionWrapper::close: Removing myself from oTstDrv.aoRemoteSessions'); + self.oTstDrv.aoRemoteSessions.remove(self) + except: + reporter.logXcpt(); + + if self.uPid is not None and self.fPidFile: + self.oTstDrv.pidFileRemove(self.uPid); + self.fPidFile = False; + + # It's only logical to deregister the event handler after the session + # is closed. It also avoids circular references between the session + # and the listener, which causes trouble with garbage collection. + self.deregisterEventHandlerForTask(); + + self.oTstDrv.processPendingEvents(); + return fRc; + + def saveSettings(self, fClose = False): + """ + Saves the settings and optionally closes the session. + Returns success indicator. + """ + try: + try: + self.o.machine.saveSettings(); + except: + reporter.errorXcpt('saveSettings failed on %s' % (self.o)); + return False; + finally: + self.oTstDrv.processPendingEvents(); + if fClose: + return self.close(); + return True; + + def discardSettings(self, fClose = False): + """ + Discards the settings and optionally closes the session. + """ + try: + try: + self.o.machine.discardSettings(); + except: + reporter.errorXcpt('discardSettings failed on %s' % (self.o)); + return False; + finally: + self.oTstDrv.processPendingEvents(); + if fClose: + return self.close(); + return True; + + def enableVirtEx(self, fEnable): + """ + Enables or disables AMD-V/VT-x. + Returns True on success and False on failure. Error information is logged. + """ + # Enable/disable it. + fRc = True; + try: + self.o.machine.setHWVirtExProperty(vboxcon.HWVirtExPropertyType_Enabled, fEnable); + except: + reporter.errorXcpt('failed to set HWVirtExPropertyType_Enabled=%s for "%s"' % (fEnable, self.sName)); + fRc = False; + else: + reporter.log('set HWVirtExPropertyType_Enabled=%s for "%s"' % (fEnable, self.sName)); + + # Force/unforce it. + if fRc and hasattr(vboxcon, 'HWVirtExPropertyType_Force'): + try: + self.o.machine.setHWVirtExProperty(vboxcon.HWVirtExPropertyType_Force, fEnable); + except: + reporter.errorXcpt('failed to set HWVirtExPropertyType_Force=%s for "%s"' % (fEnable, self.sName)); + fRc = False; + else: + reporter.log('set HWVirtExPropertyType_Force=%s for "%s"' % (fEnable, self.sName)); + else: + reporter.log('Warning! vboxcon has no HWVirtExPropertyType_Force attribute.'); + ## @todo Modify CFGM to do the same for old VBox versions? + + self.oTstDrv.processPendingEvents(); + return fRc; + + def enableNestedPaging(self, fEnable): + """ + Enables or disables nested paging.. + Returns True on success and False on failure. Error information is logged. + """ + ## @todo Add/remove force CFGM thing, we don't want fallback logic when testing. + fRc = True; + try: + self.o.machine.setHWVirtExProperty(vboxcon.HWVirtExPropertyType_NestedPaging, fEnable); + except: + reporter.errorXcpt('failed to set HWVirtExPropertyType_NestedPaging=%s for "%s"' % (fEnable, self.sName)); + fRc = False; + else: + reporter.log('set HWVirtExPropertyType_NestedPaging=%s for "%s"' % (fEnable, self.sName)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def enableLongMode(self, fEnable): + """ + Enables or disables LongMode. + Returns True on success and False on failure. Error information is logged. + """ + # Supported. + if self.fpApiVer < 4.2 or not hasattr(vboxcon, 'HWVirtExPropertyType_LongMode'): + return True; + + # Enable/disable it. + fRc = True; + try: + self.o.machine.setCPUProperty(vboxcon.CPUPropertyType_LongMode, fEnable); + except: + reporter.errorXcpt('failed to set CPUPropertyType_LongMode=%s for "%s"' % (fEnable, self.sName)); + fRc = False; + else: + reporter.log('set CPUPropertyType_LongMode=%s for "%s"' % (fEnable, self.sName)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def enableNestedHwVirt(self, fEnable): + """ + Enables or disables Nested Hardware-Virtualization. + Returns True on success and False on failure. Error information is logged. + """ + # Supported. + if self.fpApiVer < 5.3 or not hasattr(vboxcon, 'CPUPropertyType_HWVirt'): + return True; + + # Enable/disable it. + fRc = True; + try: + self.o.machine.setCPUProperty(vboxcon.CPUPropertyType_HWVirt, fEnable); + except: + reporter.errorXcpt('failed to set CPUPropertyType_HWVirt=%s for "%s"' % (fEnable, self.sName)); + fRc = False; + else: + reporter.log('set CPUPropertyType_HWVirt=%s for "%s"' % (fEnable, self.sName)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def enablePae(self, fEnable): + """ + Enables or disables PAE + Returns True on success and False on failure. Error information is logged. + """ + fRc = True; + try: + if self.fpApiVer >= 3.2: # great, ain't it? + self.o.machine.setCPUProperty(vboxcon.CPUPropertyType_PAE, fEnable); + else: + self.o.machine.setCpuProperty(vboxcon.CpuPropertyType_PAE, fEnable); + except: + reporter.errorXcpt('failed to set CPUPropertyType_PAE=%s for "%s"' % (fEnable, self.sName)); + fRc = False; + else: + reporter.log('set CPUPropertyType_PAE=%s for "%s"' % (fEnable, self.sName)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def enableIoApic(self, fEnable): + """ + Enables or disables the IO-APIC + Returns True on success and False on failure. Error information is logged. + """ + fRc = True; + try: + self.o.machine.BIOSSettings.IOAPICEnabled = fEnable; + except: + reporter.errorXcpt('failed to set BIOSSettings.IOAPICEnabled=%s for "%s"' % (fEnable, self.sName)); + fRc = False; + else: + reporter.log('set BIOSSettings.IOAPICEnabled=%s for "%s"' % (fEnable, self.sName)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def enableHpet(self, fEnable): + """ + Enables or disables the HPET + Returns True on success and False on failure. Error information is logged. + """ + fRc = True; + try: + if self.fpApiVer >= 4.2: + self.o.machine.HPETEnabled = fEnable; + else: + self.o.machine.hpetEnabled = fEnable; + except: + reporter.errorXcpt('failed to set HpetEnabled=%s for "%s"' % (fEnable, self.sName)); + fRc = False; + else: + reporter.log('set HpetEnabled=%s for "%s"' % (fEnable, self.sName)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def enableUsbHid(self, fEnable): + """ + Enables or disables the USB HID + Returns True on success and False on failure. Error information is logged. + """ + fRc = True; + try: + if fEnable: + if self.fpApiVer >= 4.3: + cOhciCtls = self.o.machine.getUSBControllerCountByType(vboxcon.USBControllerType_OHCI); + if cOhciCtls == 0: + self.o.machine.addUSBController('OHCI', vboxcon.USBControllerType_OHCI); + else: + self.o.machine.usbController.enabled = True; + + if self.fpApiVer >= 4.2: + self.o.machine.pointingHIDType = vboxcon.PointingHIDType_ComboMouse; + self.o.machine.keyboardHIDType = vboxcon.KeyboardHIDType_ComboKeyboard; + else: + self.o.machine.pointingHidType = vboxcon.PointingHidType_ComboMouse; + self.o.machine.keyboardHidType = vboxcon.KeyboardHidType_ComboKeyboard; + else: + if self.fpApiVer >= 4.2: + self.o.machine.pointingHIDType = vboxcon.PointingHIDType_PS2Mouse; + self.o.machine.keyboardHIDType = vboxcon.KeyboardHIDType_PS2Keyboard; + else: + self.o.machine.pointingHidType = vboxcon.PointingHidType_PS2Mouse; + self.o.machine.keyboardHidType = vboxcon.KeyboardHidType_PS2Keyboard; + except: + reporter.errorXcpt('failed to change UsbHid to %s for "%s"' % (fEnable, self.sName)); + fRc = False; + else: + reporter.log('changed UsbHid to %s for "%s"' % (fEnable, self.sName)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def enableUsbOhci(self, fEnable): + """ + Enables or disables the USB OHCI controller + Returns True on success and False on failure. Error information is logged. + """ + fRc = True; + try: + if fEnable: + if self.fpApiVer >= 4.3: + cOhciCtls = self.o.machine.getUSBControllerCountByType(vboxcon.USBControllerType_OHCI); + if cOhciCtls == 0: + self.o.machine.addUSBController('OHCI', vboxcon.USBControllerType_OHCI); + else: + self.o.machine.usbController.enabled = True; + else: + if self.fpApiVer >= 4.3: + cOhciCtls = self.o.machine.getUSBControllerCountByType(vboxcon.USBControllerType_OHCI); + if cOhciCtls == 1: + self.o.machine.removeUSBController('OHCI'); + else: + self.o.machine.usbController.enabled = False; + except: + reporter.errorXcpt('failed to change OHCI to %s for "%s"' % (fEnable, self.sName)); + fRc = False; + else: + reporter.log('changed OHCI to %s for "%s"' % (fEnable, self.sName)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def enableUsbEhci(self, fEnable): + """ + Enables or disables the USB EHCI controller, enables also OHCI if it is still disabled. + Returns True on success and False on failure. Error information is logged. + """ + fRc = True; + try: + if fEnable: + if self.fpApiVer >= 4.3: + cOhciCtls = self.o.machine.getUSBControllerCountByType(vboxcon.USBControllerType_OHCI); + if cOhciCtls == 0: + self.o.machine.addUSBController('OHCI', vboxcon.USBControllerType_OHCI); + + cEhciCtls = self.o.machine.getUSBControllerCountByType(vboxcon.USBControllerType_EHCI); + if cEhciCtls == 0: + self.o.machine.addUSBController('EHCI', vboxcon.USBControllerType_EHCI); + else: + self.o.machine.usbController.enabled = True; + self.o.machine.usbController.enabledEHCI = True; + else: + if self.fpApiVer >= 4.3: + cEhciCtls = self.o.machine.getUSBControllerCountByType(vboxcon.USBControllerType_EHCI); + if cEhciCtls == 1: + self.o.machine.removeUSBController('EHCI'); + else: + self.o.machine.usbController.enabledEHCI = False; + except: + reporter.errorXcpt('failed to change EHCI to %s for "%s"' % (fEnable, self.sName)); + fRc = False; + else: + reporter.log('changed EHCI to %s for "%s"' % (fEnable, self.sName)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def enableUsbXhci(self, fEnable): + """ + Enables or disables the USB XHCI controller. Error information is logged. + """ + fRc = True; + try: + if fEnable: + cXhciCtls = self.o.machine.getUSBControllerCountByType(vboxcon.USBControllerType_XHCI); + if cXhciCtls == 0: + self.o.machine.addUSBController('XHCI', vboxcon.USBControllerType_XHCI); + else: + cXhciCtls = self.o.machine.getUSBControllerCountByType(vboxcon.USBControllerType_XHCI); + if cXhciCtls == 1: + self.o.machine.removeUSBController('XHCI'); + except: + reporter.errorXcpt('failed to change XHCI to %s for "%s"' % (fEnable, self.sName)); + fRc = False; + else: + reporter.log('changed XHCI to %s for "%s"' % (fEnable, self.sName)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def setFirmwareType(self, eType): + """ + Sets the firmware type. + Returns True on success and False on failure. Error information is logged. + """ + fRc = True; + try: + self.o.machine.firmwareType = eType; + except: + reporter.errorXcpt('failed to set firmwareType=%s for "%s"' % (eType, self.sName)); + fRc = False; + else: + reporter.log('set firmwareType=%s for "%s"' % (eType, self.sName)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def setChipsetType(self, eType): + """ + Sets the chipset type. + Returns True on success and False on failure. Error information is logged. + """ + fRc = True; + try: + self.o.machine.chipsetType = eType; + except: + reporter.errorXcpt('failed to set chipsetType=%s for "%s"' % (eType, self.sName)); + fRc = False; + else: + reporter.log('set chipsetType=%s for "%s"' % (eType, self.sName)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def setIommuType(self, eType): + """ + Sets the IOMMU type. + Returns True on success and False on failure. Error information is logged. + """ + # Supported. + if self.fpApiVer < 6.2 or not hasattr(vboxcon, 'IommuType_Intel') or not hasattr(vboxcon, 'IommuType_AMD'): + return True; + fRc = True; + try: + self.o.machine.iommuType = eType; + except: + reporter.errorXcpt('failed to set iommuType=%s for "%s"' % (eType, self.sName)); + fRc = False; + else: + reporter.log('set iommuType=%s for "%s"' % (eType, self.sName)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def setupBootLogo(self, fEnable, cMsLogoDisplay = 0): + """ + Sets up the boot logo. fEnable toggles the fade and boot menu + settings as well as the mode. + """ + fRc = True; + try: + self.o.machine.BIOSSettings.logoFadeIn = not fEnable; + self.o.machine.BIOSSettings.logoFadeOut = not fEnable; + self.o.machine.BIOSSettings.logoDisplayTime = cMsLogoDisplay; + if fEnable: + self.o.machine.BIOSSettings.bootMenuMode = vboxcon.BIOSBootMenuMode_Disabled; + else: + self.o.machine.BIOSSettings.bootMenuMode = vboxcon.BIOSBootMenuMode_MessageAndMenu; + except: + reporter.errorXcpt('failed to set logoFadeIn/logoFadeOut/bootMenuMode=%s for "%s"' % (fEnable, self.sName)); + fRc = False; + else: + reporter.log('set logoFadeIn/logoFadeOut/bootMenuMode=%s for "%s"' % (fEnable, self.sName)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def setupVrdp(self, fEnable, uPort = None): + """ + Configures VRDP. + """ + fRc = True; + try: + if self.fpApiVer >= 4.0: + self.o.machine.VRDEServer.enabled = fEnable; + else: + self.o.machine.VRDPServer.enabled = fEnable; + except: + reporter.errorXcpt('failed to set VRDEServer::enabled=%s for "%s"' % (fEnable, self.sName)); + fRc = False; + + if uPort is not None and fRc: + try: + if self.fpApiVer >= 4.0: + self.o.machine.VRDEServer.setVRDEProperty("TCP/Ports", str(uPort)); + else: + self.o.machine.VRDPServer.ports = str(uPort); + except: + reporter.errorXcpt('failed to set VRDEServer::ports=%s for "%s"' % (uPort, self.sName)); + fRc = False; + if fRc: + reporter.log('set VRDEServer.enabled/ports=%s/%s for "%s"' % (fEnable, uPort, self.sName)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def getNicDriverNameFromType(self, eNicType): + """ + Helper that translate the adapter type into a driver name. + """ + if eNicType in (vboxcon.NetworkAdapterType_Am79C970A, vboxcon.NetworkAdapterType_Am79C973): + sName = 'pcnet'; + elif eNicType in (vboxcon.NetworkAdapterType_I82540EM, + vboxcon.NetworkAdapterType_I82543GC, + vboxcon.NetworkAdapterType_I82545EM): + sName = 'e1000'; + elif eNicType == vboxcon.NetworkAdapterType_Virtio: + sName = 'virtio-net'; + else: + reporter.error('Unknown adapter type "%s" (VM: "%s")' % (eNicType, self.sName)); + sName = 'pcnet'; + return sName; + + def setupNatForwardingForTxs(self, iNic = 0, iHostPort = 5042): + """ + Sets up NAT forwarding for port 5042 if applicable, cleans up if not. + """ + try: + oNic = self.o.machine.getNetworkAdapter(iNic); + except: + reporter.errorXcpt('getNetworkAdapter(%s) failed for "%s"' % (iNic, self.sName)); + return False; + + # Nuke the old setup for all possible adapter types (in case we're + # called after it changed). + for sName in ('pcnet', 'e1000', 'virtio-net'): + for sConfig in ('VBoxInternal/Devices/%s/%u/LUN#0/AttachedDriver/Config' % (sName, iNic), \ + 'VBoxInternal/Devices/%s/%u/LUN#0/Config' % (sName, iNic)): + try: + self.o.machine.setExtraData('%s/txs/Protocol' % (sConfig), ''); + self.o.machine.setExtraData('%s/txs/HostPort' % (sConfig), ''); + self.o.machine.setExtraData('%s/txs/GuestPort' % (sConfig), ''); + except: + reporter.errorXcpt(); + + # Set up port forwarding if NAT attachment. + try: + eAttType = oNic.attachmentType; + except: + reporter.errorXcpt('attachmentType on %s failed for "%s"' % (iNic, self.sName)); + return False; + if eAttType != vboxcon.NetworkAttachmentType_NAT: + return True; + + try: + eNicType = oNic.adapterType; + fTraceEnabled = oNic.traceEnabled; + except: + reporter.errorXcpt('attachmentType/traceEnabled on %s failed for "%s"' % (iNic, self.sName)); + return False; + + if self.fpApiVer >= 4.1: + try: + if self.fpApiVer >= 4.2: + oNatEngine = oNic.NATEngine; + else: + oNatEngine = oNic.natDriver; + except: + reporter.errorXcpt('Failed to get INATEngine data on "%s"' % (self.sName)); + return False; + try: oNatEngine.removeRedirect('txs'); + except: pass; + try: + oNatEngine.addRedirect('txs', vboxcon.NATProtocol_TCP, '127.0.0.1', '%s' % (iHostPort), '', '5042'); + except: + reporter.errorXcpt('Failed to add a addRedirect redirect on "%s"' % (self.sName)); + return False; + + else: + sName = self.getNicDriverNameFromType(eNicType); + if fTraceEnabled: + sConfig = 'VBoxInternal/Devices/%s/%u/LUN#0/AttachedDriver/Config' % (sName, iNic) + else: + sConfig = 'VBoxInternal/Devices/%s/%u/LUN#0/Config' % (sName, iNic) + + try: + self.o.machine.setExtraData('%s/txs/Protocol' % (sConfig), 'TCP'); + self.o.machine.setExtraData('%s/txs/HostPort' % (sConfig), '%s' % (iHostPort)); + self.o.machine.setExtraData('%s/txs/GuestPort' % (sConfig), '5042'); + except: + reporter.errorXcpt('Failed to set NAT extra data on "%s"' % (self.sName)); + return False; + return True; + + def setNicType(self, eType, iNic = 0): + """ + Sets the NIC type of the specified NIC. + Returns True on success and False on failure. Error information is logged. + """ + try: + try: + oNic = self.o.machine.getNetworkAdapter(iNic); + except: + reporter.errorXcpt('getNetworkAdapter(%s) failed for "%s"' % (iNic, self.sName)); + return False; + try: + oNic.adapterType = eType; + except: + reporter.errorXcpt('failed to set NIC type on slot %s to %s for VM "%s"' % (iNic, eType, self.sName)); + return False; + finally: + self.oTstDrv.processPendingEvents(); + + if not self.setupNatForwardingForTxs(iNic): + return False; + reporter.log('set NIC type on slot %s to %s for VM "%s"' % (iNic, eType, self.sName)); + return True; + + def setNicTraceEnabled(self, fTraceEnabled, sTraceFile, iNic = 0): + """ + Sets the NIC trace enabled flag and file path. + Returns True on success and False on failure. Error information is logged. + """ + try: + try: + oNic = self.o.machine.getNetworkAdapter(iNic); + except: + reporter.errorXcpt('getNetworkAdapter(%s) failed for "%s"' % (iNic, self.sName)); + return False; + try: + oNic.traceEnabled = fTraceEnabled; + oNic.traceFile = sTraceFile; + except: + reporter.errorXcpt('failed to set NIC trace flag on slot %s to %s for VM "%s"' \ + % (iNic, fTraceEnabled, self.sName)); + return False; + finally: + self.oTstDrv.processPendingEvents(); + + if not self.setupNatForwardingForTxs(iNic): + return False; + reporter.log('set NIC trace on slot %s to "%s" (path "%s") for VM "%s"' % + (iNic, fTraceEnabled, sTraceFile, self.sName)); + return True; + + def getDefaultNicName(self, eAttachmentType): + """ + Return the default network / interface name for the NIC attachment type. + """ + sRetName = ''; + if eAttachmentType == vboxcon.NetworkAttachmentType_Bridged: + if self.oTstDrv.sDefBridgedNic is not None: + sRetName = self.oTstDrv.sDefBridgedNic; + else: + sRetName = 'eth0'; + try: + aoHostNics = self.oVBoxMgr.getArray(self.oVBox.host, 'networkInterfaces'); + for oHostNic in aoHostNics: + if oHostNic.interfaceType == vboxcon.HostNetworkInterfaceType_Bridged \ + and oHostNic.status == vboxcon.HostNetworkInterfaceStatus_Up: + sRetName = oHostNic.name; + break; + except: + reporter.errorXcpt(); + + elif eAttachmentType == vboxcon.NetworkAttachmentType_HostOnly: + try: + aoHostNics = self.oVBoxMgr.getArray(self.oVBox.host, 'networkInterfaces'); + for oHostNic in aoHostNics: + if oHostNic.interfaceType == vboxcon.HostNetworkInterfaceType_HostOnly: + if oHostNic.status == vboxcon.HostNetworkInterfaceStatus_Up: + sRetName = oHostNic.name; + break; + if sRetName == '': + sRetName = oHostNic.name; + except: + reporter.errorXcpt(); + if sRetName == '': + # Create a new host-only interface. + reporter.log("Creating host only NIC ..."); + try: + (oIProgress, oIHostOnly) = self.oVBox.host.createHostOnlyNetworkInterface(); + oProgress = ProgressWrapper(oIProgress, self.oVBoxMgr, self.oTstDrv, 'Create host only NIC'); + oProgress.wait(); + if oProgress.logResult() is False: + return ''; + sRetName = oIHostOnly.name; + except: + reporter.errorXcpt(); + return ''; + reporter.log("Created host only NIC: '%s'" % (sRetName,)); + + elif self.fpApiVer >= 7.0 and eAttachmentType == vboxcon.NetworkAttachmentType_HostOnlyNetwork: + aoHostNetworks = self.oVBoxMgr.getArray(self.oVBox, 'hostOnlyNetworks'); + if aoHostNetworks: + sRetName = aoHostNetworks[0].networkName; + else: + try: + oHostOnlyNet = self.oVBox.createHostOnlyNetwork('Host-only Test Network'); + oHostOnlyNet.lowerIP = '192.168.56.1'; + oHostOnlyNet.upperIP = '192.168.56.199'; + oHostOnlyNet.networkMask = '255.255.255.0'; + sRetName = oHostOnlyNet.networkName; + except: + reporter.errorXcpt(); + return ''; + + elif eAttachmentType == vboxcon.NetworkAttachmentType_Internal: + sRetName = 'VBoxTest'; + + elif eAttachmentType == vboxcon.NetworkAttachmentType_NAT: + sRetName = ''; + + else: ## @todo Support NetworkAttachmentType_NATNetwork + reporter.error('eAttachmentType=%s is not known' % (eAttachmentType)); + return sRetName; + + def setNicAttachment(self, eAttachmentType, sName = None, iNic = 0): + """ + Sets the attachment type of the specified NIC. + Returns True on success and False on failure. Error information is logged. + """ + try: + oNic = self.o.machine.getNetworkAdapter(iNic); + except: + reporter.errorXcpt('getNetworkAdapter(%s) failed for "%s"' % (iNic, self.sName)); + return False; + + try: + if eAttachmentType is not None: + try: + if self.fpApiVer >= 4.1: + oNic.attachmentType = eAttachmentType; + else: + if eAttachmentType == vboxcon.NetworkAttachmentType_NAT: + oNic.attachToNAT(); + elif eAttachmentType == vboxcon.NetworkAttachmentType_Bridged: + oNic.attachToBridgedInterface(); + elif eAttachmentType == vboxcon.NetworkAttachmentType_Internal: + oNic.attachToInternalNetwork(); + elif eAttachmentType == vboxcon.NetworkAttachmentType_HostOnly: + oNic.attachToHostOnlyInterface(); + else: + raise base.GenError("eAttachmentType=%s is invalid" % (eAttachmentType)); + except: + reporter.errorXcpt('failed to set the attachment type on slot %s to %s for VM "%s"' \ + % (iNic, eAttachmentType, self.sName)); + return False; + else: + try: + eAttachmentType = oNic.attachmentType; + except: + reporter.errorXcpt('failed to get the attachment type on slot %s for VM "%s"' % (iNic, self.sName)); + return False; + finally: + self.oTstDrv.processPendingEvents(); + + if sName is not None: + # Resolve the special 'default' name. + if sName == 'default': + sName = self.getDefaultNicName(eAttachmentType); + + # The name translate to different attributes depending on the + # attachment type. + try: + if eAttachmentType == vboxcon.NetworkAttachmentType_Bridged: + ## @todo check this out on windows, may have to do a + # translation of the name there or smth IIRC. + try: + if self.fpApiVer >= 4.1: + oNic.bridgedInterface = sName; + else: + oNic.hostInterface = sName; + except: + reporter.errorXcpt('failed to set the hostInterface property on slot %s to "%s" for VM "%s"' + % (iNic, sName, self.sName,)); + return False; + elif eAttachmentType == vboxcon.NetworkAttachmentType_HostOnly: + try: + if self.fpApiVer >= 4.1: + oNic.hostOnlyInterface = sName; + else: + oNic.hostInterface = sName; + except: + reporter.errorXcpt('failed to set the internalNetwork property on slot %s to "%s" for VM "%s"' + % (iNic, sName, self.sName,)); + return False; + elif self.fpApiVer >= 7.0 and eAttachmentType == vboxcon.NetworkAttachmentType_HostOnlyNetwork: + try: + oNic.hostOnlyNetwork = sName; + except: + reporter.errorXcpt('failed to set the hostOnlyNetwork property on slot %s to "%s" for VM "%s"' + % (iNic, sName, self.sName,)); + return False; + elif eAttachmentType == vboxcon.NetworkAttachmentType_Internal: + try: + oNic.internalNetwork = sName; + except: + reporter.errorXcpt('failed to set the internalNetwork property on slot %s to "%s" for VM "%s"' + % (iNic, sName, self.sName,)); + return False; + elif eAttachmentType == vboxcon.NetworkAttachmentType_NAT: + try: + oNic.NATNetwork = sName; + except: + reporter.errorXcpt('failed to set the NATNetwork property on slot %s to "%s" for VM "%s"' + % (iNic, sName, self.sName,)); + return False; + finally: + self.oTstDrv.processPendingEvents(); + + if not self.setupNatForwardingForTxs(iNic): + return False; + reporter.log('set NIC attachment type on slot %s to %s for VM "%s"' % (iNic, eAttachmentType, self.sName)); + return True; + + def setNicLocalhostReachable(self, fReachable, iNic = 0): + """ + Sets whether the specified NIC can reach the host or not. + Only affects (enabled) NICs configured to NAT at the moment. + + Returns True on success and False on failure. Error information is logged. + """ + try: + oNic = self.o.machine.getNetworkAdapter(iNic); + except: + return reporter.errorXcpt('getNetworkAdapter(%s) failed for "%s"' % (iNic, self.sName,)); + + try: + if not oNic.enabled: # NIC not enabled? Nothing to do here. + return True; + except: + return reporter.errorXcpt('NIC enabled status (%s) failed for "%s"' % (iNic, self.sName,)); + + reporter.log('Setting "LocalhostReachable" for network adapter in slot %d to %s' % (iNic, fReachable)); + + try: + oNatEngine = oNic.NATEngine; + except: + return reporter.errorXcpt('Getting NIC NAT engine (%s) failed for "%s"' % (iNic, self.sName,)); + + try: + if hasattr(oNatEngine, "localhostReachable"): + oNatEngine.localhostReachable = fReachable; + else: + oNatEngine.LocalhostReachable = fReachable; + except: + return reporter.errorXcpt('LocalhostReachable (%s) failed for "%s"' % (iNic, self.sName,)); + + return True; + + def setNicMacAddress(self, sMacAddr, iNic = 0): + """ + Sets the MAC address of the specified NIC. + + The sMacAddr parameter is a string supplying the tail end of the MAC + address, missing quads are supplied from a constant byte (2), the IPv4 + address of the host, and the NIC number. + + Returns True on success and False on failure. Error information is logged. + """ + + # Resolve missing MAC address prefix by feeding in the host IP address bytes. + cchMacAddr = len(sMacAddr); + if 0 < cchMacAddr < 12: + sHostIP = netutils.getPrimaryHostIp(); + abHostIP = socket.inet_aton(sHostIP); + if sys.version_info[0] < 3: + abHostIP = (ord(abHostIP[0]), ord(abHostIP[1]), ord(abHostIP[2]), ord(abHostIP[3])); + + if abHostIP[0] == 127 \ + or (abHostIP[0] == 169 and abHostIP[1] == 254) \ + or (abHostIP[0] == 192 and abHostIP[1] == 168 and abHostIP[2] == 56): + return reporter.error('host IP for "%s" is %s, most likely not unique.' % (netutils.getHostnameFqdn(), sHostIP,)); + + sDefaultMac = '%02X%02X%02X%02X%02X%02X' % (0x02, abHostIP[0], abHostIP[1], abHostIP[2], abHostIP[3], iNic); + sMacAddr = sDefaultMac[0:(12 - cchMacAddr)] + sMacAddr; + + # Get the NIC object and try set it address. + try: + oNic = self.o.machine.getNetworkAdapter(iNic); + except: + return reporter.errorXcpt('getNetworkAdapter(%s) failed for "%s"' % (iNic, self.sName,)); + + try: + oNic.MACAddress = sMacAddr; + except: + return reporter.errorXcpt('failed to set the MAC address on slot %s to "%s" for VM "%s"' + % (iNic, sMacAddr, self.sName)); + + reporter.log('set MAC address on slot %s to %s for VM "%s"' % (iNic, sMacAddr, self.sName,)); + return True; + + def setRamSize(self, cMB): + """ + Set the RAM size of the VM. + Returns True on success and False on failure. Error information is logged. + """ + fRc = True; + try: + self.o.machine.memorySize = cMB; + except: + reporter.errorXcpt('failed to set the RAM size of "%s" to %s' % (self.sName, cMB)); + fRc = False; + else: + reporter.log('set the RAM size of "%s" to %s' % (self.sName, cMB)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def setLargePages(self, fUseLargePages): + """ + Configures whether the VM should use large pages or not. + Returns True on success and False on failure. Error information is logged. + """ + fRc = True; + try: + self.o.machine.setHWVirtExProperty(vboxcon.HWVirtExPropertyType_LargePages, fUseLargePages); + except: + reporter.errorXcpt('failed to set large pages of "%s" to %s' % (self.sName, fUseLargePages)); + fRc = False; + else: + reporter.log('set the large pages of "%s" to %s' % (self.sName, fUseLargePages)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def setVRamSize(self, cMB): + """ + Set the RAM size of the VM. + Returns True on success and False on failure. Error information is logged. + """ + fRc = True; + try: + if self.fpApiVer >= 6.1 and hasattr(self.o.machine, 'graphicsAdapter'): + self.o.machine.graphicsAdapter.VRAMSize = cMB; + else: + self.o.machine.VRAMSize = cMB; + except: + reporter.errorXcpt('failed to set the VRAM size of "%s" to %s' % (self.sName, cMB)); + fRc = False; + else: + reporter.log('set the VRAM size of "%s" to %s' % (self.sName, cMB)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def setVideoControllerType(self, eControllerType): + """ + Set the video controller type of the VM. + Returns True on success and False on failure. Error information is logged. + """ + fRc = True; + try: + if self.fpApiVer >= 6.1 and hasattr(self.o.machine, 'graphicsAdapter'): + self.o.machine.graphicsAdapter.graphicsControllerType = eControllerType; + else: + self.o.machine.graphicsControllerType = eControllerType; + except: + reporter.errorXcpt('failed to set the video controller type of "%s" to %s' % (self.sName, eControllerType)); + fRc = False; + else: + reporter.log('set the video controller type of "%s" to %s' % (self.sName, eControllerType)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def setAccelerate3DEnabled(self, fEnabled): + """ + Set the video controller type of the VM. + Returns True on success and False on failure. Error information is logged. + """ + fRc = True; + try: + if self.fpApiVer >= 6.1 and hasattr(self.o.machine, 'graphicsAdapter'): + self.o.machine.graphicsAdapter.accelerate3DEnabled = fEnabled; + else: + self.o.machine.accelerate3DEnabled = fEnabled; + except: + reporter.errorXcpt('failed to set the accelerate3DEnabled of "%s" to %s' % (self.sName, fEnabled)); + fRc = False; + else: + reporter.log('set the accelerate3DEnabled of "%s" to %s' % (self.sName, fEnabled)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def setCpuCount(self, cCpus): + """ + Set the number of CPUs. + Returns True on success and False on failure. Error information is logged. + """ + fRc = True; + try: + self.o.machine.CPUCount = cCpus; + except: + reporter.errorXcpt('failed to set the CPU count of "%s" to %s' % (self.sName, cCpus)); + fRc = False; + else: + reporter.log('set the CPU count of "%s" to %s' % (self.sName, cCpus)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def getCpuCount(self): + """ + Returns the number of CPUs. + Returns the number of CPUs on success and 0 on failure. Error information is logged. + """ + cCpus = 0; + try: + cCpus = self.o.machine.CPUCount; + except: + reporter.errorXcpt('failed to get the CPU count of "%s"' % (self.sName,)); + + self.oTstDrv.processPendingEvents(); + return cCpus; + + def ensureControllerAttached(self, sController): + """ + Makes sure the specified controller is attached to the VM, attaching it + if necessary. + """ + try: + try: + self.o.machine.getStorageControllerByName(sController); + except: + (eBus, eType) = _ControllerNameToBusAndType(sController); + try: + oCtl = self.o.machine.addStorageController(sController, eBus); + except: + reporter.errorXcpt('addStorageController("%s",%s) failed on "%s"' % (sController, eBus, self.sName) ); + return False; + else: + try: + oCtl.controllerType = eType; + reporter.log('added storage controller "%s" (bus %s, type %s) to %s' + % (sController, eBus, eType, self.sName)); + except: + reporter.errorXcpt('controllerType = %s on ("%s" / %s) failed on "%s"' + % (eType, sController, eBus, self.sName) ); + return False; + finally: + self.oTstDrv.processPendingEvents(); + return True; + + def setStorageControllerPortCount(self, sController, iPortCount): + """ + Set maximum ports count for storage controller + """ + try: + oCtl = self.o.machine.getStorageControllerByName(sController) + oCtl.portCount = iPortCount + self.oTstDrv.processPendingEvents() + reporter.log('set controller "%s" port count to value %d' % (sController, iPortCount)) + return True + except: + reporter.log('unable to set storage controller "%s" ports count to %d' % (sController, iPortCount)) + + return False + + def setStorageControllerHostIoCache(self, sController, fUseHostIoCache): + """ + Set maximum ports count for storage controller + """ + try: + oCtl = self.o.machine.getStorageControllerByName(sController); + oCtl.useHostIOCache = fUseHostIoCache; + self.oTstDrv.processPendingEvents(); + reporter.log('set controller "%s" host I/O cache setting to %r' % (sController, fUseHostIoCache)); + return True; + except: + reporter.log('unable to set storage controller "%s" host I/O cache setting to %r' % (sController, fUseHostIoCache)); + + return False; + + def setBootOrder(self, iPosition, eType): + """ + Set guest boot order type + @param iPosition boot order position + @param eType device type (vboxcon.DeviceType_HardDisk, + vboxcon.DeviceType_DVD, vboxcon.DeviceType_Floppy) + """ + try: + self.o.machine.setBootOrder(iPosition, eType) + except: + return reporter.errorXcpt('Unable to set boot order.') + + reporter.log('Set boot order [%d] for device %s' % (iPosition, str(eType))) + self.oTstDrv.processPendingEvents(); + + return True + + def setStorageControllerType(self, eType, sController = "IDE Controller"): + """ + Similar to ensureControllerAttached, except it will change the type. + """ + try: + oCtl = self.o.machine.getStorageControllerByName(sController); + except: + (eBus, _) = _ControllerNameToBusAndType(sController); + try: + oCtl = self.o.machine.addStorageController(sController, eBus); + reporter.log('added storage controller "%s" (bus %s) to %s' % (sController, eBus, self.sName)); + except: + reporter.errorXcpt('addStorageController("%s",%s) failed on "%s"' % (sController, eBus, self.sName) ); + return False; + try: + oCtl.controllerType = eType; + except: + reporter.errorXcpt('failed to set controller type of "%s" on "%s" to %s' % (sController, self.sName, eType) ); + return False; + reporter.log('set controller type of "%s" on "%s" to %s' % (sController, self.sName, eType) ); + self.oTstDrv.processPendingEvents(); + return True; + + def attachDvd(self, sImage = None, sController = "IDE Controller", iPort = 1, iDevice = 0): + """ + Attaches a DVD drive to a VM, optionally with an ISO inserted. + Returns True on success and False on failure. Error information is logged. + """ + # Input validation. + if sImage is not None and not self.oTstDrv.isResourceFile(sImage)\ + and not os.path.isabs(sImage): ## fixme - testsuite unzip ++ + reporter.fatal('"%s" is not in the resource set' % (sImage)); + return None; + + if not self.ensureControllerAttached(sController): + return False; + + # Find/register the image if specified. + oImage = None; + sImageUuid = ""; + if sImage is not None: + sFullName = self.oTstDrv.getFullResourceName(sImage) + try: + oImage = self.oVBox.findDVDImage(sFullName); + except: + try: + if self.fpApiVer >= 4.1: + oImage = self.oVBox.openMedium(sFullName, vboxcon.DeviceType_DVD, vboxcon.AccessMode_ReadOnly, False); + elif self.fpApiVer >= 4.0: + oImage = self.oVBox.openMedium(sFullName, vboxcon.DeviceType_DVD, vboxcon.AccessMode_ReadOnly); + else: + oImage = self.oVBox.openDVDImage(sFullName, ""); + except vbox.ComException as oXcpt: + if oXcpt.errno != -1: + reporter.errorXcpt('failed to open DVD image "%s" xxx' % (sFullName)); + else: + reporter.errorXcpt('failed to open DVD image "%s" yyy' % (sFullName)); + return False; + except: + reporter.errorXcpt('failed to open DVD image "%s"' % (sFullName)); + return False; + try: + sImageUuid = oImage.id; + except: + reporter.errorXcpt('failed to get the UUID of "%s"' % (sFullName)); + return False; + + # Attach the DVD. + fRc = True; + try: + if self.fpApiVer >= 4.0: + self.o.machine.attachDevice(sController, iPort, iDevice, vboxcon.DeviceType_DVD, oImage); + else: + self.o.machine.attachDevice(sController, iPort, iDevice, vboxcon.DeviceType_DVD, sImageUuid); + except: + reporter.errorXcpt('attachDevice("%s",%s,%s,HardDisk,"%s") failed on "%s"' \ + % (sController, iPort, iDevice, sImageUuid, self.sName) ); + fRc = False; + else: + reporter.log('attached DVD to %s, image="%s"' % (self.sName, sImage)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def attachHd(self, sHd, sController = "IDE Controller", iPort = 0, iDevice = 0, fImmutable = True, fForceResource = True): + """ + Attaches a HD to a VM. + Returns True on success and False on failure. Error information is logged. + """ + # Input validation. + if fForceResource and not self.oTstDrv.isResourceFile(sHd): + reporter.fatal('"%s" is not in the resource set' % (sHd,)); + return None; + + if not self.ensureControllerAttached(sController): + return False; + + # Find the HD, registering it if necessary (as immutable). + if fForceResource: + sFullName = self.oTstDrv.getFullResourceName(sHd); + else: + sFullName = sHd; + try: + oHd = self.oVBox.findHardDisk(sFullName); + except: + try: + if self.fpApiVer >= 4.1: + oHd = self.oVBox.openMedium(sFullName, vboxcon.DeviceType_HardDisk, vboxcon.AccessMode_ReadOnly, False); + elif self.fpApiVer >= 4.0: + oHd = self.oVBox.openMedium(sFullName, vboxcon.DeviceType_HardDisk, vboxcon.AccessMode_ReadOnly); + else: + oHd = self.oVBox.openHardDisk(sFullName, vboxcon.AccessMode_ReadOnly, False, "", False, ""); + except: + reporter.errorXcpt('failed to open hd "%s"' % (sFullName)); + return False; + try: + if fImmutable: + oHd.type = vboxcon.MediumType_Immutable; + else: + oHd.type = vboxcon.MediumType_Normal; + except: + if fImmutable: + reporter.errorXcpt('failed to set hd "%s" immutable' % (sHd)); + else: + reporter.errorXcpt('failed to set hd "%s" normal' % (sHd)); + return False; + + # Attach it. + fRc = True; + try: + if self.fpApiVer >= 4.0: + self.o.machine.attachDevice(sController, iPort, iDevice, vboxcon.DeviceType_HardDisk, oHd); + else: + self.o.machine.attachDevice(sController, iPort, iDevice, vboxcon.DeviceType_HardDisk, oHd.id); + except: + reporter.errorXcpt('attachDevice("%s",%s,%s,HardDisk,"%s") failed on "%s"' \ + % (sController, iPort, iDevice, oHd.id, self.sName) ); + fRc = False; + else: + reporter.log('attached "%s" to %s' % (sHd, self.sName)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def createBaseHd(self, sHd, sFmt = "VDI", cb = 10*1024*1024*1024, cMsTimeout = 60000, tMediumVariant = None): + """ + Creates a base HD. + Returns Medium object on success and None on failure. Error information is logged. + """ + if tMediumVariant is None: + tMediumVariant = (vboxcon.MediumVariant_Standard, ); + + try: + if self.fpApiVer >= 5.0: + oHd = self.oVBox.createMedium(sFmt, sHd, vboxcon.AccessMode_ReadWrite, vboxcon.DeviceType_HardDisk); + else: + oHd = self.oVBox.createHardDisk(sFmt, sHd); + oProgressXpcom = oHd.createBaseStorage(cb, tMediumVariant); + oProgress = ProgressWrapper(oProgressXpcom, self.oVBoxMgr, self.oTstDrv, 'create base disk %s' % (sHd)); + oProgress.wait(cMsTimeout); + oProgress.logResult(); + except: + reporter.errorXcpt('failed to create base hd "%s"' % (sHd)); + oHd = None + + return oHd; + + def createDiffHd(self, oParentHd, sHd, sFmt = "VDI"): + """ + Creates a differencing HD. + Returns Medium object on success and None on failure. Error information is logged. + """ + # Detect the proper format if requested + if sFmt is None: + try: + oHdFmt = oParentHd.mediumFormat; + lstCaps = self.oVBoxMgr.getArray(oHdFmt, 'capabilities'); + if vboxcon.MediumFormatCapabilities_Differencing in lstCaps: + sFmt = oHdFmt.id; + else: + sFmt = 'VDI'; + except: + reporter.errorXcpt('failed to get preferred diff format for "%s"' % (sHd)); + return None; + try: + if self.fpApiVer >= 5.0: + oHd = self.oVBox.createMedium(sFmt, sHd, vboxcon.AccessMode_ReadWrite, vboxcon.DeviceType_HardDisk); + else: + oHd = self.oVBox.createHardDisk(sFmt, sHd); + oProgressXpcom = oParentHd.createDiffStorage(oHd, (vboxcon.MediumVariant_Standard, )) + oProgress = ProgressWrapper(oProgressXpcom, self.oVBoxMgr, self.oTstDrv, 'create diff disk %s' % (sHd)); + oProgress.wait(); + oProgress.logResult(); + except: + reporter.errorXcpt('failed to create diff hd "%s"' % (sHd)); + oHd = None + + return oHd; + + def createAndAttachHd(self, sHd, sFmt = "VDI", sController = "IDE Controller", cb = 10*1024*1024*1024, # pylint: disable=too-many-arguments + iPort = 0, iDevice = 0, fImmutable = True, cMsTimeout = 60000, tMediumVariant = None): + """ + Creates and attaches a HD to a VM. + Returns True on success and False on failure. Error information is logged. + """ + if not self.ensureControllerAttached(sController): + return False; + + oHd = self.createBaseHd(sHd, sFmt, cb, cMsTimeout, tMediumVariant); + if oHd is None: + return False; + + fRc = True; + try: + if fImmutable: + oHd.type = vboxcon.MediumType_Immutable; + else: + oHd.type = vboxcon.MediumType_Normal; + except: + if fImmutable: + reporter.errorXcpt('failed to set hd "%s" immutable' % (sHd)); + else: + reporter.errorXcpt('failed to set hd "%s" normal' % (sHd)); + fRc = False; + + # Attach it. + if fRc is True: + try: + if self.fpApiVer >= 4.0: + self.o.machine.attachDevice(sController, iPort, iDevice, vboxcon.DeviceType_HardDisk, oHd); + else: + self.o.machine.attachDevice(sController, iPort, iDevice, vboxcon.DeviceType_HardDisk, oHd.id); + except: + reporter.errorXcpt('attachDevice("%s",%s,%s,HardDisk,"%s") failed on "%s"' \ + % (sController, iPort, iDevice, oHd.id, self.sName) ); + fRc = False; + else: + reporter.log('attached "%s" to %s' % (sHd, self.sName)); + + # Delete disk in case of an error + if fRc is False: + try: + oProgressCom = oHd.deleteStorage(); + except: + reporter.errorXcpt('deleteStorage() for disk %s failed' % (sHd,)); + else: + oProgress = ProgressWrapper(oProgressCom, self.oVBoxMgr, self.oTstDrv, 'delete disk %s' % (sHd)); + oProgress.wait(); + oProgress.logResult(); + + self.oTstDrv.processPendingEvents(); + return fRc; + + def detachHd(self, sController = "IDE Controller", iPort = 0, iDevice = 0): + """ + Detaches a HD, if attached, and returns a reference to it (IMedium). + + In order to delete the detached medium, the caller must first save + the changes made in this session. + + Returns (fRc, oHd), where oHd is None unless fRc is True, and fRc is + your standard success indicator. Error information is logged. + """ + + # What's attached? + try: + oHd = self.o.machine.getMedium(sController, iPort, iDevice); + except: + if self.oVBoxMgr.xcptIsOurXcptKind() \ + and self.oVBoxMgr.xcptIsEqual(None, self.oVBoxMgr.constants.VBOX_E_OBJECT_NOT_FOUND): + reporter.log('No HD attached (to %s %s:%s)' % (sController, iPort, iDevice)); + return (True, None); + return (reporter.errorXcpt('Error getting media at port %s, device %s, on %s.' + % (iPort, iDevice, sController)), None); + # Detach it. + try: + self.o.machine.detachDevice(sController, iPort, iDevice); + except: + return (reporter.errorXcpt('detachDevice("%s",%s,%s) failed on "%s"' \ + % (sController, iPort, iDevice, self.sName) ), None); + reporter.log('detached HD ("%s",%s,%s) from %s' % (sController, iPort, iDevice, self.sName)); + return (True, oHd); + + def attachFloppy(self, sFloppy, sController = "Floppy Controller", iPort = 0, iDevice = 0): + """ + Attaches a floppy image to a VM. + Returns True on success and False on failure. Error information is logged. + """ + # Input validation. + ## @todo Fix this wrt to bootsector-xxx.img from the validationkit.zip. + ##if not self.oTstDrv.isResourceFile(sFloppy): + ## reporter.fatal('"%s" is not in the resource set' % (sFloppy)); + ## return None; + + if not self.ensureControllerAttached(sController): + return False; + + # Find the floppy image, registering it if necessary (as immutable). + sFullName = self.oTstDrv.getFullResourceName(sFloppy); + try: + oFloppy = self.oVBox.findFloppyImage(sFullName); + except: + try: + if self.fpApiVer >= 4.1: + oFloppy = self.oVBox.openMedium(sFullName, vboxcon.DeviceType_Floppy, vboxcon.AccessMode_ReadOnly, False); + elif self.fpApiVer >= 4.0: + oFloppy = self.oVBox.openMedium(sFullName, vboxcon.DeviceType_Floppy, vboxcon.AccessMode_ReadOnly); + else: + oFloppy = self.oVBox.openFloppyImage(sFullName, ""); + except: + reporter.errorXcpt('failed to open floppy "%s"' % (sFullName)); + return False; + ## @todo the following works but causes trouble below (asserts in main). + #try: + # oFloppy.type = vboxcon.MediumType_Immutable; + #except: + # reporter.errorXcpt('failed to make floppy "%s" immutable' % (sFullName)); + # return False; + + # Attach it. + fRc = True; + try: + if self.fpApiVer >= 4.0: + self.o.machine.attachDevice(sController, iPort, iDevice, vboxcon.DeviceType_Floppy, oFloppy); + else: + self.o.machine.attachDevice(sController, iPort, iDevice, vboxcon.DeviceType_Floppy, oFloppy.id); + except: + reporter.errorXcpt('attachDevice("%s",%s,%s,Floppy,"%s") failed on "%s"' \ + % (sController, iPort, iDevice, oFloppy.id, self.sName) ); + fRc = False; + else: + reporter.log('attached "%s" to %s' % (sFloppy, self.sName)); + self.oTstDrv.processPendingEvents(); + return fRc; + + def setupNic(self, sType, sXXX): + """ + Sets up a NIC to a VM. + Returns True on success and False on failure. Error information is logged. + """ + if sType == "PCNet": enmType = vboxcon.NetworkAdapterType_Am79C973; + elif sType == "PCNetOld": enmType = vboxcon.NetworkAdapterType_Am79C970A; + elif sType == "E1000": enmType = vboxcon.NetworkAdapterType_I82545EM; # MT Server + elif sType == "E1000Desk": enmType = vboxcon.NetworkAdapterType_I82540EM; # MT Desktop + elif sType == "E1000Srv2": enmType = vboxcon.NetworkAdapterType_I82543GC; # T Server + elif sType == "Virtio": enmType = vboxcon.NetworkAdapterType_Virtio; + else: + reporter.error('Invalid NIC type: "%s" (sXXX=%s)' % (sType, sXXX)); + return False; + ## @todo Implement me! + if enmType is not None: pass + return True; + + def setupAudio(self, eAudioControllerType, fEnable = True, fEnableIn = False, fEnableOut = True, eAudioDriverType = None): + """ + Sets up audio. + + :param eAudioControllerType: The audio controller type (vboxcon.AudioControllerType_XXX). + :param fEnable: Whether to enable or disable the audio controller (default enable). + :param fEnableIn: Whether to enable or disable audio input (default disable). + :param fEnableOut: Whether to enable or disable audio output (default enable). + :param eAudioDriverType: The audio driver type (vboxcon.AudioDriverType_XXX), picks something suitable + if None is passed (default). + """ + try: + if self.fpApiVer >= 7.0: + oAdapter = self.o.machine.audioSettings.adapter; + else: + oAdapter = self.o.machine.audioAdapter; + except: return reporter.errorXcpt('Failed to get the audio adapter.'); + + try: oAdapter.audioController = eAudioControllerType; + except: return reporter.errorXcpt('Failed to set the audio controller to %s.' % (eAudioControllerType,)); + + if eAudioDriverType is None: + sHost = utils.getHostOs() + if sHost == 'darwin': eAudioDriverType = vboxcon.AudioDriverType_CoreAudio; + elif sHost == 'win': eAudioDriverType = vboxcon.AudioDriverType_DirectSound; + elif sHost == 'linux': eAudioDriverType = vboxcon.AudioDriverType_Pulse; + elif sHost == 'solaris': eAudioDriverType = vboxcon.AudioDriverType_OSS; + else: + reporter.error('PORTME: Do not know which audio driver to pick for: %s!' % (sHost,)); + eAudioDriverType = vboxcon.AudioDriverType_Null; + + try: oAdapter.audioDriver = eAudioDriverType; + except: return reporter.errorXcpt('Failed to set the audio driver to %s.' % (eAudioDriverType,)) + + try: oAdapter.enabled = fEnable; + except: return reporter.errorXcpt('Failed to set the "enabled" property to %s.' % (fEnable,)); + + try: oAdapter.enabledIn = fEnableIn; + except: return reporter.errorXcpt('Failed to set the "enabledIn" property to %s.' % (fEnable,)); + + try: oAdapter.enabledOut = fEnableOut; + except: return reporter.errorXcpt('Failed to set the "enabledOut" property to %s.' % (fEnable,)); + + reporter.log('set audio adapter type to %d, driver to %d, and enabled to %s (input is %s, output is %s)' + % (eAudioControllerType, eAudioDriverType, fEnable, fEnableIn, fEnableOut,)); + self.oTstDrv.processPendingEvents(); + return True; + + def setupPreferredConfig(self): # pylint: disable=too-many-locals + """ + Configures the VM according to the preferences of the guest type. + """ + try: + sOsTypeId = self.o.machine.OSTypeId; + except: + reporter.errorXcpt('failed to obtain the OSTypeId for "%s"' % (self.sName)); + return False; + + try: + oOsType = self.oVBox.getGuestOSType(sOsTypeId); + except: + reporter.errorXcpt('getGuestOSType("%s") failed for "%s"' % (sOsTypeId, self.sName)); + return False; + + # get the attributes. + try: + #sFamilyId = oOsType.familyId; + #f64Bit = oOsType.is64Bit; + fIoApic = oOsType.recommendedIOAPIC; + fVirtEx = oOsType.recommendedVirtEx; + cMBRam = oOsType.recommendedRAM; + cMBVRam = oOsType.recommendedVRAM; + #cMBHdd = oOsType.recommendedHDD; + eNicType = oOsType.adapterType; + if self.fpApiVer >= 3.2: + if self.fpApiVer >= 4.2: + fPae = oOsType.recommendedPAE; + fUsbHid = oOsType.recommendedUSBHID; + fHpet = oOsType.recommendedHPET; + eStorCtlType = oOsType.recommendedHDStorageController; + else: + fPae = oOsType.recommendedPae; + fUsbHid = oOsType.recommendedUsbHid; + fHpet = oOsType.recommendedHpet; + eStorCtlType = oOsType.recommendedHdStorageController; + eFirmwareType = oOsType.recommendedFirmware; + else: + fPae = False; + fUsbHid = False; + fHpet = False; + eFirmwareType = -1; + eStorCtlType = vboxcon.StorageControllerType_PIIX4; + if self.fpApiVer >= 4.0: + eAudioCtlType = oOsType.recommendedAudioController; + except: + reporter.errorXcpt('exception reading IGuestOSType(%s) attribute' % (sOsTypeId)); + self.oTstDrv.processPendingEvents(); + return False; + self.oTstDrv.processPendingEvents(); + + # Do the setting. Continue applying settings on error in case the + # caller ignores the return code + fRc = True; + if not self.enableIoApic(fIoApic): fRc = False; + if not self.enableVirtEx(fVirtEx): fRc = False; + if not self.enablePae(fPae): fRc = False; + if not self.setRamSize(cMBRam): fRc = False; + if not self.setVRamSize(cMBVRam): fRc = False; + if not self.setNicType(eNicType, 0): fRc = False; + if self.fpApiVer >= 3.2: + if not self.setFirmwareType(eFirmwareType): fRc = False; + if not self.enableUsbHid(fUsbHid): fRc = False; + if not self.enableHpet(fHpet): fRc = False; + if eStorCtlType in (vboxcon.StorageControllerType_PIIX3, + vboxcon.StorageControllerType_PIIX4, + vboxcon.StorageControllerType_ICH6,): + if not self.setStorageControllerType(eStorCtlType, "IDE Controller"): + fRc = False; + if self.fpApiVer >= 4.0: + if not self.setupAudio(eAudioCtlType): fRc = False; + + return fRc; + + def addUsbDeviceFilter(self, sName, sVendorId = None, sProductId = None, sRevision = None, # pylint: disable=too-many-arguments + sManufacturer = None, sProduct = None, sSerialNumber = None, + sPort = None, sRemote = None): + """ + Creates a USB device filter and inserts it into the VM. + Returns True on success. + Returns False on failure (logged). + """ + fRc = True; + + try: + oUsbDevFilter = self.o.machine.USBDeviceFilters.createDeviceFilter(sName); + oUsbDevFilter.active = True; + if sVendorId is not None: + oUsbDevFilter.vendorId = sVendorId; + if sProductId is not None: + oUsbDevFilter.productId = sProductId; + if sRevision is not None: + oUsbDevFilter.revision = sRevision; + if sManufacturer is not None: + oUsbDevFilter.manufacturer = sManufacturer; + if sProduct is not None: + oUsbDevFilter.product = sProduct; + if sSerialNumber is not None: + oUsbDevFilter.serialnumber = sSerialNumber; + if sPort is not None: + oUsbDevFilter.port = sPort; + if sRemote is not None: + oUsbDevFilter.remote = sRemote; + try: + self.o.machine.USBDeviceFilters.insertDeviceFilter(0, oUsbDevFilter); + except: + reporter.errorXcpt('insertDeviceFilter(%s) failed on "%s"' \ + % (0, self.sName) ); + fRc = False; + else: + reporter.log('inserted USB device filter "%s" to %s' % (sName, self.sName)); + except: + reporter.errorXcpt('createDeviceFilter("%s") failed on "%s"' \ + % (sName, self.sName) ); + fRc = False; + return fRc; + + def getGuestPropertyValue(self, sName): + """ + Gets a guest property value. + Returns the value on success, None on failure (logged). + """ + try: + sValue = self.o.machine.getGuestPropertyValue(sName); + except: + reporter.errorXcpt('IMachine::getGuestPropertyValue("%s") failed' % (sName)); + return None; + return sValue; + + def setGuestPropertyValue(self, sName, sValue): + """ + Sets a guest property value. + Returns the True on success, False on failure (logged). + """ + try: + self.o.machine.setGuestPropertyValue(sName, sValue); + except: + reporter.errorXcpt('IMachine::setGuestPropertyValue("%s","%s") failed' % (sName, sValue)); + return False; + return True; + + def delGuestPropertyValue(self, sName): + """ + Deletes a guest property value. + Returns the True on success, False on failure (logged). + """ + try: + oMachine = self.o.machine; + if self.fpApiVer >= 4.2: + oMachine.deleteGuestProperty(sName); + else: + oMachine.setGuestPropertyValue(sName, ''); + except: + reporter.errorXcpt('Unable to delete guest property "%s"' % (sName,)); + return False; + return True; + + def setExtraData(self, sKey, sValue): + """ + Sets extra data. + Returns the True on success, False on failure (logged). + """ + try: + self.o.machine.setExtraData(sKey, sValue); + except: + reporter.errorXcpt('IMachine::setExtraData("%s","%s") failed' % (sKey, sValue)); + return False; + return True; + + def getExtraData(self, sKey): + """ + Gets extra data. + Returns value on success, None on failure. + """ + try: + sValue = self.o.machine.getExtraData(sKey) + except: + reporter.errorXcpt('IMachine::setExtraData("%s","%s") failed' % (sKey, sValue)) + return None + return sValue + + def setupTeleporter(self, fEnabled=True, uPort = 6500, sAddress = '', sPassword = ''): + """ + Sets up the teleporter for the VM. + Returns True on success, False on failure (logged). + """ + try: + self.o.machine.teleporterAddress = sAddress; + self.o.machine.teleporterPort = uPort; + self.o.machine.teleporterPassword = sPassword; + self.o.machine.teleporterEnabled = fEnabled; + except: + reporter.errorXcpt('setupTeleporter(%s, %s, %s, %s)' % (fEnabled, sPassword, uPort, sAddress)); + return False; + return True; + + def enableTeleporter(self, fEnable=True): + """ + Enables or disables the teleporter of the VM. + Returns True on success, False on failure (logged). + """ + try: + self.o.machine.teleporterEnabled = fEnable; + except: + reporter.errorXcpt('IMachine::teleporterEnabled=%s failed' % (fEnable)); + return False; + return True; + + def teleport(self, sHostname = 'localhost', uPort = 6500, sPassword = 'password', cMsMaxDowntime = 250): + """ + Wrapper around the IConsole::teleport() method. + Returns a progress object on success, None on failure (logged). + """ + reporter.log2('"%s"::teleport(%s,%s,%s,%s)...' % (self.sName, sHostname, uPort, sPassword, cMsMaxDowntime)); + try: + oProgress = self.o.console.teleport(sHostname, uPort, sPassword, cMsMaxDowntime) + except: + reporter.errorXcpt('IConsole::teleport(%s,%s,%s,%s) failed' % (sHostname, uPort, sPassword, cMsMaxDowntime)); + return None; + return ProgressWrapper(oProgress, self.oVBoxMgr, self.oTstDrv, 'teleport %s' % (self.sName,)); + + def getOsType(self): + """ + Gets the IGuestOSType interface for the machine. + + return IGuestOSType interface on success, None + errorXcpt on failure. + No exceptions raised. + """ + try: + sOsTypeId = self.o.machine.OSTypeId; + except: + reporter.errorXcpt('failed to obtain the OSTypeId for "%s"' % (self.sName)); + return None; + + try: + oOsType = self.oVBox.getGuestOSType(sOsTypeId); + except: + reporter.errorXcpt('getGuestOSType("%s") failed for "%s"' % (sOsTypeId, self.sName)); + return None; + + return oOsType; + + def setOsType(self, sNewTypeId): + """ + Changes the OS type. + + returns True on success, False + errorXcpt on failure. + No exceptions raised. + """ + try: + self.o.machine.OSTypeId = sNewTypeId; + except: + reporter.errorXcpt('failed to set the OSTypeId for "%s" to "%s"' % (self.sName, sNewTypeId)); + return False; + return True; + + + def setParavirtProvider(self, iProvider): + """ + Sets a paravirtualisation provider. + Returns the True on success, False on failure (logged). + """ + try: + self.o.machine.paravirtProvider = iProvider + except: + reporter.errorXcpt('Unable to set paravirtualisation provider "%s"' % (iProvider,)) + return False; + return True; + + + def setupSerialToRawFile(self, iSerialPort, sRawFile): + """ + Enables the given serial port (zero based) and redirects it to sRawFile. + Returns the True on success, False on failure (logged). + """ + try: + oPort = self.o.machine.getSerialPort(iSerialPort); + except: + fRc = reporter.errorXcpt('failed to get serial port #%u' % (iSerialPort,)); + else: + try: + oPort.path = sRawFile; + except: + fRc = reporter.errorXcpt('failed to set the "path" property on serial port #%u to "%s"' + % (iSerialPort, sRawFile)); + else: + try: + oPort.hostMode = vboxcon.PortMode_RawFile; + except: + fRc = reporter.errorXcpt('failed to set the "hostMode" property on serial port #%u to PortMode_RawFile' + % (iSerialPort,)); + else: + try: + oPort.enabled = True; + except: + fRc = reporter.errorXcpt('failed to set the "enable" property on serial port #%u to True' + % (iSerialPort,)); + else: + reporter.log('set SerialPort[%s].enabled/hostMode/path=True/RawFile/%s' % (iSerialPort, sRawFile,)); + fRc = True; + self.oTstDrv.processPendingEvents(); + return fRc; + + + def enableSerialPort(self, iSerialPort): + """ + Enables the given serial port setting the initial port mode to disconnected. + """ + try: + oPort = self.o.machine.getSerialPort(iSerialPort); + except: + fRc = reporter.errorXcpt('failed to get serial port #%u' % (iSerialPort,)); + else: + try: + oPort.hostMode = vboxcon.PortMode_Disconnected; + except: + fRc = reporter.errorXcpt('failed to set the "hostMode" property on serial port #%u to PortMode_Disconnected' + % (iSerialPort,)); + else: + try: + oPort.enabled = True; + except: + fRc = reporter.errorXcpt('failed to set the "enable" property on serial port #%u to True' + % (iSerialPort,)); + else: + reporter.log('set SerialPort[%s].enabled/hostMode/=True/Disconnected' % (iSerialPort,)); + fRc = True; + self.oTstDrv.processPendingEvents(); + return fRc; + + + def changeSerialPortAttachment(self, iSerialPort, ePortMode, sPath, fServer): + """ + Changes the attachment of the given serial port to the attachment config given. + """ + try: + oPort = self.o.machine.getSerialPort(iSerialPort); + except: + fRc = reporter.errorXcpt('failed to get serial port #%u' % (iSerialPort,)); + else: + try: + # Change port mode to disconnected first so changes get picked up by a potentially running VM. + oPort.hostMode = vboxcon.PortMode_Disconnected; + except: + fRc = reporter.errorXcpt('failed to set the "hostMode" property on serial port #%u to PortMode_Disconnected' + % (iSerialPort,)); + else: + try: + oPort.path = sPath; + oPort.server = fServer; + oPort.hostMode = ePortMode; + except: + fRc = reporter.errorXcpt('failed to configure the serial port'); + else: + reporter.log('set SerialPort[%s].hostMode/path/server=%s/%s/%s' + % (iSerialPort, ePortMode, sPath, fServer)); + fRc = True; + self.oTstDrv.processPendingEvents(); + return fRc; + + # + # IConsole wrappers. + # + + def powerOff(self, fFudgeOnFailure = True): + """ + Powers off the VM. + + Returns True on success. + Returns False on IConsole::powerDown() failure. + Returns None if the progress object returns failure. + """ + # + # Deregister event handler before we power off the VM, otherwise we're + # racing for VM process termination and cause misleading spurious + # error messages in the event handling code, because the event objects + # disappear. + # + # Note! Doing this before powerDown to try prevent numerous smoketest + # timeouts on XPCOM hosts. + # + self.deregisterEventHandlerForTask(); + + + # Try power if off. + try: + oProgress = self.o.console.powerDown(); + except: + reporter.logXcpt('IConsole::powerDown failed on %s' % (self.sName)); + if fFudgeOnFailure: + self.oTstDrv.waitOnDirectSessionClose(self.oVM, 5000); # fudge + self.waitForTask(1000); # fudge + return False; + + # Wait on power off operation to complete. + rc = self.oTstDrv.waitOnProgress(oProgress); + if rc < 0: + self.close(); + if fFudgeOnFailure: + vbox.reportError(oProgress, 'powerDown for "%s" failed' % (self.sName)); + self.oTstDrv.waitOnDirectSessionClose(self.oVM, 5000); # fudge + return None; + + # Wait for the VM to really power off or we'll fail to open a new session to it. + self.oTstDrv.waitOnDirectSessionClose(self.oVM, 5000); # fudge + return self.waitForTask(30 * 1000); # fudge + + def saveState(self, fPause = True): + """ + Saves state of the VM. + + Returns True on success. + Returns False on IConsole::saveState() failure. + Returns None if the progress object returns Failure. + """ + + if fPause is True \ + and self.oVM.state is vboxcon.MachineState_Running: + self.o.console.pause(); + if self.oVM.state is not vboxcon.MachineState_Paused: + reporter.error('pause for "%s" failed' % (self.sName)); + # Try saving state. + try: + if self.fpApiVer >= 5.0: + oProgress = self.o.machine.saveState() + else: + oProgress = self.o.console.saveState() + except: + reporter.logXcpt('IMachine::saveState failed on %s' % (self.sName)); + return False; + + # Wait for saving state operation to complete. + rc = self.oTstDrv.waitOnProgress(oProgress); + if rc < 0: + self.close(); + return None; + + # Wait for the VM to really terminate or we'll fail to open a new session to it. + self.oTstDrv.waitOnDirectSessionClose(self.oVM, 5000); # fudge + return self.waitForTask(30 * 1000); # fudge + + def discardSavedState(self, fRemove = True): + """ + Discards saved state of the VM. + + Returns True on success. + Returns False on IConsole::discardSaveState() failure. + """ + + try: + if self.fpApiVer >= 5.0: + self.o.machine.discardSavedState(fRemove) + else: + self.o.console.discardSavedState(fRemove) + except: + reporter.logXcpt('IMachine::discardSavedState failed on %s' % (self.sName)) + return False + return True + + def restoreSnapshot(self, oSnapshot, fFudgeOnFailure = True): + """ + Restores the given snapshot. + + Returns True on success. + Returns False on IMachine::restoreSnapshot() failure. + Returns None if the progress object returns failure. + """ + try: + if self.fpApiVer >= 5.0: + oProgress = self.o.machine.restoreSnapshot(oSnapshot); + else: + oProgress = self.o.console.restoreSnapshot(oSnapshot); + except: + reporter.logXcpt('IMachine::restoreSnapshot failed on %s' % (self.sName)); + if fFudgeOnFailure: + self.oTstDrv.waitOnDirectSessionClose(self.oVM, 5000); # fudge + self.waitForTask(1000); # fudge + return False; + + rc = self.oTstDrv.waitOnProgress(oProgress); + if rc < 0: + self.close(); + if fFudgeOnFailure: + vbox.reportError(oProgress, 'restoreSnapshot for "%s" failed' % (self.sName)); + return None; + + return self.waitForTask(30 * 1000); + + def deleteSnapshot(self, oSnapshot, fFudgeOnFailure = True, cMsTimeout = 30 * 1000): + """ + Deletes the given snapshot merging the diff image into the base. + + Returns True on success. + Returns False on IMachine::deleteSnapshot() failure. + """ + try: + if self.fpApiVer >= 5.0: + oProgressCom = self.o.machine.deleteSnapshot(oSnapshot); + else: + oProgressCom = self.o.console.deleteSnapshot(oSnapshot); + oProgress = ProgressWrapper(oProgressCom, self.oVBoxMgr, self.oTstDrv, 'Delete Snapshot %s' % (oSnapshot)); + oProgress.wait(cMsTimeout); + oProgress.logResult(); + except: + reporter.logXcpt('IMachine::deleteSnapshot failed on %s' % (self.sName)); + if fFudgeOnFailure: + self.oTstDrv.waitOnDirectSessionClose(self.oVM, 5000); # fudge + self.waitForTask(1000); # fudge + return False; + + return True; + + def takeSnapshot(self, sName, sDescription = '', fPause = True, fFudgeOnFailure = True, cMsTimeout = 30 * 1000): + """ + Takes a snapshot with the given name + + Returns True on success. + Returns False on IMachine::takeSnapshot() or VM state change failure. + """ + try: + if fPause is True \ + and self.oVM.state is vboxcon.MachineState_Running: + self.o.console.pause(); + if self.fpApiVer >= 5.0: + (oProgressCom, _) = self.o.machine.takeSnapshot(sName, sDescription, True); + else: + oProgressCom = self.o.console.takeSnapshot(sName, sDescription); + oProgress = ProgressWrapper(oProgressCom, self.oVBoxMgr, self.oTstDrv, 'Take Snapshot %s' % (sName)); + oProgress.wait(cMsTimeout); + oProgress.logResult(); + except: + reporter.logXcpt('IMachine::takeSnapshot failed on %s' % (self.sName)); + if fFudgeOnFailure: + self.oTstDrv.waitOnDirectSessionClose(self.oVM, 5000); # fudge + self.waitForTask(1000); # fudge + return False; + + if fPause is True \ + and self.oVM.state is vboxcon.MachineState_Paused: + self.o.console.resume(); + + return True; + + def findSnapshot(self, sName): + """ + Returns the snapshot object with the given name + + Returns snapshot object on success. + Returns None if there is no snapshot with the given name. + """ + return self.oVM.findSnapshot(sName); + + def takeScreenshot(self, sFilename, iScreenId=0): + """ + Take screenshot from the given display and save it to specified file. + + Returns True on success + Returns False on failure. + """ + try: + if self.fpApiVer >= 5.0: + iWidth, iHeight, _, _, _, _ = self.o.console.display.getScreenResolution(iScreenId) + aPngData = self.o.console.display.takeScreenShotToArray(iScreenId, iWidth, iHeight, + vboxcon.BitmapFormat_PNG) + else: + iWidth, iHeight, _, _, _ = self.o.console.display.getScreenResolution(iScreenId) + aPngData = self.o.console.display.takeScreenShotPNGToArray(iScreenId, iWidth, iHeight) + except: + reporter.logXcpt("Unable to take screenshot") + return False + + with open(sFilename, 'wb') as oFile: # pylint: disable=unspecified-encoding + oFile.write(aPngData) + + return True + + def attachUsbDevice(self, sUuid, sCaptureFilename = None): + """ + Attach given USB device UUID to the VM. + + Returns True on success + Returns False on failure. + """ + fRc = True; + try: + if sCaptureFilename is None: + self.o.console.attachUSBDevice(sUuid, ''); + else: + self.o.console.attachUSBDevice(sUuid, sCaptureFilename); + except: + reporter.logXcpt('Unable to attach USB device %s' % (sUuid,)); + fRc = False; + + return fRc; + + def detachUsbDevice(self, sUuid): + """ + Detach given USB device UUID from the VM. + + Returns True on success + Returns False on failure. + """ + fRc = True; + try: + _ = self.o.console.detachUSBDevice(sUuid); + except: + reporter.logXcpt('Unable to detach USB device %s' % (sUuid,)); + fRc = False; + + return fRc; + + + # + # IMachineDebugger wrappers. + # + + def queryOsKernelLog(self): + """ + Tries to get the OS kernel log using the VM debugger interface. + + Returns string containing the kernel log on success. + Returns None on failure. + """ + sOsKernelLog = None; + try: + self.o.console.debugger.loadPlugIn('all'); + except: + reporter.logXcpt('Unable to load debugger plugins'); + else: + try: + sOsDetected = self.o.console.debugger.detectOS(); + except: + reporter.logXcpt('Failed to detect the guest OS'); + else: + try: + sOsKernelLog = self.o.console.debugger.queryOSKernelLog(0); + except: + reporter.logXcpt('Unable to get the guest OS (%s) kernel log' % (sOsDetected,)); + return sOsKernelLog; + + def queryDbgInfo(self, sItem, sArg = '', sDefault = None): + """ + Simple wrapper around IMachineDebugger::info. + + Returns string on success, sDefault on failure (logged). + """ + try: + return self.o.console.debugger.info(sItem, sArg); + except: + reporter.logXcpt('Unable to query "%s" with arg "%s"' % (sItem, sArg,)); + return sDefault; + + def queryDbgInfoVgaText(self, sArg = 'all'): + """ + Tries to get the 'info vgatext' output, provided we're in next mode. + + Returns string containing text on success. + Returns None on failure or not text mode. + """ + sVgaText = None; + try: + sVgaText = self.o.console.debugger.info('vgatext', sArg); + if sVgaText.startswith('Not in text mode!'): + sVgaText = None; + except: + reporter.logXcpt('Unable to query vgatext with arg "%s"' % (sArg,)); + return sVgaText; + + def queryDbgGuestStack(self, iCpu = 0): + """ + Returns the guest stack for the given VCPU. + + Returns string containing the guest stack for the selected VCPU on success. + Returns None on failure. + """ + + # + # Load all plugins first and try to detect the OS so we can + # get nicer stack traces. + # + try: + self.o.console.debugger.loadPlugIn('all'); + except: + reporter.logXcpt('Unable to load debugger plugins'); + else: + try: + sOsDetected = self.o.console.debugger.detectOS(); + _ = sOsDetected; + except: + reporter.logXcpt('Failed to detect the guest OS'); + + sGuestStack = None; + try: + sGuestStack = self.o.console.debugger.dumpGuestStack(iCpu); + except: + reporter.logXcpt('Unable to query guest stack for CPU %s' % (iCpu, )); + + return sGuestStack; + + + # + # Other methods. + # + + def getPrimaryIp(self): + """ + Tries to obtain the primary IP address of the guest via the guest + properties. + + Returns IP address on success. + Returns empty string on failure. + """ + sIpAddr = self.getGuestPropertyValue('/VirtualBox/GuestInfo/Net/0/V4/IP'); + if vbox.isIpAddrValid(sIpAddr): + return sIpAddr; + return ''; + + def getPid(self): + """ + Gets the process ID for the direct session unless it's ourselves. + """ + if self.uPid is None and self.o is not None and self.fRemoteSession: + try: + if self.fpApiVer >= 4.2: + uPid = self.o.machine.sessionPID; + else: + uPid = self.o.machine.sessionPid; + if uPid != os.getpid() and uPid != 0xffffffff: + self.uPid = uPid; + except Exception as oXcpt: + if vbox.ComError.equal(oXcpt, vbox.ComError.E_UNEXPECTED): + try: + if self.fpApiVer >= 4.2: + uPid = self.oVM.sessionPID; + else: + uPid = self.oVM.sessionPid; + if uPid != os.getpid() and uPid != 0xffffffff: + self.uPid = uPid; + except: + reporter.log2Xcpt(); + else: + reporter.log2Xcpt(); + if self.uPid is not None: + reporter.log2('getPid: %u' % (self.uPid,)); + self.fPidFile = self.oTstDrv.pidFileAdd(self.uPid, 'vm_%s' % (self.sName,), # Set-uid-to-root is similar to SUDO. + fSudo = True); + return self.uPid; + + def addLogsToReport(self, cReleaseLogs = 1): + """ + Retrieves and adds the release and debug logs to the test report. + """ + fRc = True; + + # Add each of the requested release logs to the report. + for iLog in range(0, cReleaseLogs): + try: + if self.fpApiVer >= 3.2: + sLogFile = self.oVM.queryLogFilename(iLog); + elif iLog > 0: + sLogFile = '%s/VBox.log' % (self.oVM.logFolder,); + else: + sLogFile = '%s/VBox.log.%u' % (self.oVM.logFolder, iLog); + except: + reporter.logXcpt('iLog=%s' % (iLog,)); + fRc = False; + else: + if sLogFile is not None and sLogFile != '': # the None bit is for a 3.2.0 bug. + reporter.addLogFile(sLogFile, 'log/release/vm', '%s #%u' % (self.sName, iLog), + sAltName = '%s-%s' % (self.sName, os.path.basename(sLogFile),)); + + # Now for the hardened windows startup log. + try: + sLogFile = os.path.join(self.oVM.logFolder, 'VBoxHardening.log'); + except: + reporter.logXcpt(); + fRc = False; + else: + if os.path.isfile(sLogFile): + reporter.addLogFile(sLogFile, 'log/release/vm', '%s hardening log' % (self.sName, ), + sAltName = '%s-%s' % (self.sName, os.path.basename(sLogFile),)); + + # Now for the debug log. + if self.sLogFile is not None and os.path.isfile(self.sLogFile): + reporter.addLogFile(self.sLogFile, 'log/debug/vm', '%s debug' % (self.sName, ), + sAltName = '%s-%s' % (self.sName, os.path.basename(self.sLogFile),)); + + return fRc; + + def registerDerivedEventHandler(self, oSubClass, dArgs = None, fMustSucceed = True): + """ + Create an instance of the given ConsoleEventHandlerBase sub-class and + register it. + + The new instance is returned on success. None is returned on error. + """ + + # We need a console object. + try: + oConsole = self.o.console; + except Exception as oXcpt: + if fMustSucceed or vbox.ComError.notEqual(oXcpt, vbox.ComError.E_UNEXPECTED): + reporter.errorXcpt('Failed to get ISession::console for "%s"' % (self.sName, )); + return None; + + # Add the base class arguments. + dArgsCopy = dArgs.copy() if dArgs is not None else {}; + dArgsCopy['oSession'] = self; + dArgsCopy['oConsole'] = oConsole; + sLogSuffix = 'on %s' % (self.sName,) + return oSubClass.registerDerivedEventHandler(self.oVBoxMgr, self.fpApiVer, oSubClass, dArgsCopy, + oConsole, 'IConsole', 'IConsoleCallback', + fMustSucceed = fMustSucceed, sLogSuffix = sLogSuffix); + + def enableVmmDevTestingPart(self, fEnabled, fEnableMMIO = False): + """ + Enables the testing part of the VMMDev. + + Returns True on success and False on failure. Error information is logged. + """ + fRc = True; + try: + self.o.machine.setExtraData('VBoxInternal/Devices/VMMDev/0/Config/TestingEnabled', + '1' if fEnabled else ''); + self.o.machine.setExtraData('VBoxInternal/Devices/VMMDev/0/Config/TestingMMIO', + '1' if fEnableMMIO and fEnabled else ''); + except: + reporter.errorXcpt('VM name "%s", fEnabled=%s' % (self.sName, fEnabled)); + fRc = False; + else: + reporter.log('set VMMDevTesting=%s for "%s"' % (fEnabled, self.sName)); + self.oTstDrv.processPendingEvents(); + return fRc; + + # + # Test eXecution Service methods. + # + + def txsConnectViaTcp(self, cMsTimeout = 10*60000, sIpAddr = None, fNatForwardingForTxs = False): + """ + Connects to the TXS using TCP/IP as transport. If no IP or MAC is + addresses are specified, we'll get the IP from the guest additions. + + Returns a TxsConnectTask object on success, None + log on failure. + """ + # If the VM is configured with a NAT interface, connect to local host. + fReversedSetup = False; + fUseNatForTxs = False; + sMacAddr = None; + oIDhcpServer = None; + if sIpAddr is None: + try: + oNic = self.oVM.getNetworkAdapter(0); + enmAttachmentType = oNic.attachmentType; + if enmAttachmentType == vboxcon.NetworkAttachmentType_NAT: + fUseNatForTxs = True; + elif enmAttachmentType == vboxcon.NetworkAttachmentType_HostOnly and not sIpAddr: + # Get the MAC address and find the DHCP server. + sMacAddr = oNic.MACAddress; + sHostOnlyNIC = oNic.hostOnlyInterface; + oIHostOnlyIf = self.oVBox.host.findHostNetworkInterfaceByName(sHostOnlyNIC); + sHostOnlyNet = oIHostOnlyIf.networkName; + oIDhcpServer = self.oVBox.findDHCPServerByNetworkName(sHostOnlyNet); + except: + reporter.errorXcpt(); + return None; + + if fUseNatForTxs: + fReversedSetup = not fNatForwardingForTxs; + sIpAddr = '127.0.0.1'; + + # Kick off the task. + try: + oTask = TxsConnectTask(self, cMsTimeout, sIpAddr, sMacAddr, oIDhcpServer, fReversedSetup, + fnProcessEvents = self.oTstDrv.processPendingEvents); + except: + reporter.errorXcpt(); + oTask = None; + return oTask; + + def txsTryConnectViaTcp(self, cMsTimeout, sHostname, fReversed = False): + """ + Attempts to connect to a TXS instance. + + Returns True if a connection was established, False if not (only grave + failures are logged as errors). + + Note! The timeout is more of a guideline... + """ + + if sHostname is None or sHostname.strip() == '': + raise base.GenError('Empty sHostname is not implemented yet'); + + oTxsSession = txsclient.tryOpenTcpSession(cMsTimeout, sHostname, fReversedSetup = fReversed, + cMsIdleFudge = cMsTimeout // 2, + fnProcessEvents = self.oTstDrv.processPendingEvents); + if oTxsSession is None: + return False; + + # Wait for the connect task to time out. + self.oTstDrv.addTask(oTxsSession); + self.oTstDrv.processPendingEvents(); + oRc = self.oTstDrv.waitForTasks(cMsTimeout); + self.oTstDrv.removeTask(oTxsSession); + if oRc != oTxsSession: + if oRc is not None: + reporter.log('oRc=%s, expected %s' % (oRc, oTxsSession)); + self.oTstDrv.processPendingEvents(); + oTxsSession.cancelTask(); # this is synchronous + return False; + + # Check the status. + reporter.log2('TxsSession is ready, isSuccess() -> %s.' % (oTxsSession.isSuccess(),)); + if not oTxsSession.isSuccess(): + return False; + + reporter.log2('Disconnecting from TXS...'); + return oTxsSession.syncDisconnect(); + + + +class TxsConnectTask(TdTaskBase): + """ + Class that takes care of connecting to a VM. + """ + + class TxsConnectTaskVBoxCallback(vbox.VirtualBoxEventHandlerBase): + """ Class for looking for IPv4 address changes on interface 0.""" + def __init__(self, dArgs): + vbox.VirtualBoxEventHandlerBase.__init__(self, dArgs); + self.oParentTask = dArgs['oParentTask']; + self.sMachineId = dArgs['sMachineId']; + + def onGuestPropertyChange(self, sMachineId, sName, sValue, sFlags, fWasDeleted): + """Look for IP address.""" + reporter.log2('onGuestPropertyChange(,%s,%s,%s,%s,%s)' % (sMachineId, sName, sValue, sFlags, fWasDeleted)); + if sMachineId == self.sMachineId \ + and sName == '/VirtualBox/GuestInfo/Net/0/V4/IP': + oParentTask = self.oParentTask; + if oParentTask: + oParentTask._setIp(sValue); # pylint: disable=protected-access + + + def __init__(self, oSession, cMsTimeout, sIpAddr, sMacAddr, oIDhcpServer, fReversedSetup, fnProcessEvents = None): + TdTaskBase.__init__(self, utils.getCallerName(), fnProcessEvents = fnProcessEvents); + self.cMsTimeout = cMsTimeout; + self.fnProcessEvents = fnProcessEvents; + self.sIpAddr = None; + self.sNextIpAddr = None; + self.sMacAddr = sMacAddr; + self.oIDhcpServer = oIDhcpServer; + self.fReversedSetup = fReversedSetup; + self.oVBoxEventHandler = None; + self.oTxsSession = None; + + # Check that the input makes sense: + if (sMacAddr is None) != (oIDhcpServer is None) \ + or (sMacAddr and fReversedSetup) \ + or (sMacAddr and sIpAddr): + reporter.error('TxsConnectTask sMacAddr=%s oIDhcpServer=%s sIpAddr=%s fReversedSetup=%s' + % (sMacAddr, oIDhcpServer, sIpAddr, fReversedSetup,)); + raise base.GenError(); + + reporter.log2('TxsConnectTask: sIpAddr=%s fReversedSetup=%s' % (sIpAddr, fReversedSetup)) + if fReversedSetup is True: + self._openTcpSession(sIpAddr, fReversedSetup = True); + elif sIpAddr is not None and sIpAddr.strip() != '': + self._openTcpSession(sIpAddr, cMsIdleFudge = 5000); + else: + # + # If we've got no IP address, register callbacks that listens for + # the primary network adaptor of the VM to set a IPv4 guest prop. + # Note! The order in which things are done here is kind of important. + # + + # 0. The caller zaps the property before starting the VM. + #try: + # oSession.delGuestPropertyValue('/VirtualBox/GuestInfo/Net/0/V4/IP'); + #except: + # reporter.logXcpt(); + + # 1. Register the callback / event listener object. + dArgs = {'oParentTask':self, 'sMachineId':oSession.o.machine.id}; + self.oVBoxEventHandler = oSession.oVBox.registerDerivedEventHandler(self.TxsConnectTaskVBoxCallback, dArgs); + + # 2. Query the guest properties. + try: + sIpAddr = oSession.getGuestPropertyValue('/VirtualBox/GuestInfo/Net/0/V4/IP'); + except: + reporter.errorXcpt('IMachine::getGuestPropertyValue("/VirtualBox/GuestInfo/Net/0/V4/IP") failed'); + self._deregisterEventHandler(); + raise; + else: + if sIpAddr is not None: + self._setIp(sIpAddr); + + # + # If the network adapter of the VM is host-only we can talk poll IDHCPServer + # for the guest IP, allowing us to detect it for VMs without guest additions. + # This will when we're polled. + # + if sMacAddr is not None: + assert self.oIDhcpServer is not None; + + + # end __init__ + + def __del__(self): + """ Make sure we deregister the callback. """ + self._deregisterEventHandler(); + return TdTaskBase.__del__(self); + + def toString(self): + return '<%s cMsTimeout=%s, sIpAddr=%s, sNextIpAddr=%s, sMacAddr=%s, fReversedSetup=%s,' \ + ' oTxsSession=%s oVBoxEventHandler=%s>' \ + % (TdTaskBase.toString(self), self.cMsTimeout, self.sIpAddr, self.sNextIpAddr, self.sMacAddr, self.fReversedSetup, + self.oTxsSession, self.oVBoxEventHandler); + + def _deregisterEventHandler(self): + """Deregisters the event handler.""" + fRc = True; + oVBoxEventHandler = self.oVBoxEventHandler; + if oVBoxEventHandler is not None: + self.oVBoxEventHandler = None; + fRc = oVBoxEventHandler.unregister(); + oVBoxEventHandler.oParentTask = None; # Try avoid cylic deps. + return fRc; + + def _setIp(self, sIpAddr, fInitCall = False): + """Called when we get an IP. Will create a TXS session and signal the task.""" + sIpAddr = sIpAddr.strip(); + + if sIpAddr is not None \ + and sIpAddr != '': + if vbox.isIpAddrValid(sIpAddr) or fInitCall: + try: + for s in sIpAddr.split('.'): + i = int(s); + if str(i) != s: + raise Exception(); + except: + reporter.fatalXcpt(); + else: + reporter.log('TxsConnectTask: opening session to ip "%s"' % (sIpAddr)); + self._openTcpSession(sIpAddr, cMsIdleFudge = 5000); + return None; + + reporter.log('TxsConnectTask: Ignoring Bad ip "%s"' % (sIpAddr)); + else: + reporter.log2('TxsConnectTask: Ignoring empty ip "%s"' % (sIpAddr)); + return None; + + def _openTcpSession(self, sIpAddr, uPort = None, fReversedSetup = False, cMsIdleFudge = 0): + """ + Calls txsclient.openTcpSession and switches our task to reflect the + state of the subtask. + """ + self.oCv.acquire(); + if self.oTxsSession is None: + reporter.log2('_openTcpSession: sIpAddr=%s, uPort=%d, fReversedSetup=%s' % + (sIpAddr, uPort if uPort is not None else 0, fReversedSetup)); + self.sIpAddr = sIpAddr; + self.oTxsSession = txsclient.openTcpSession(self.cMsTimeout, sIpAddr, uPort, fReversedSetup, + cMsIdleFudge, fnProcessEvents = self.fnProcessEvents); + self.oTxsSession.setTaskOwner(self); + else: + self.sNextIpAddr = sIpAddr; + reporter.log2('_openTcpSession: sNextIpAddr=%s' % (sIpAddr,)); + self.oCv.release(); + return None; + + def notifyAboutReadyTask(self, oTxsSession): + """ + Called by the TXS session task when it's done. + + We'll signal the task completed or retry depending on the result. + """ + + self.oCv.acquire(); + + # Disassociate ourselves with the session (avoid cyclic ref) + oTxsSession.setTaskOwner(None); + fSuccess = oTxsSession.isSuccess(); + if self.oTxsSession is not None: + if not fSuccess: + self.oTxsSession = None; + if fSuccess and self.fReversedSetup: + self.sIpAddr = oTxsSession.oTransport.sHostname; + else: + fSuccess = False; + + # Signal done, or retry? + fDeregister = False; + if fSuccess \ + or self.fReversedSetup \ + or self.getAgeAsMs() >= self.cMsTimeout: + self.signalTaskLocked(); + fDeregister = True; + else: + sIpAddr = self.sNextIpAddr if self.sNextIpAddr is not None else self.sIpAddr; + self._openTcpSession(sIpAddr, cMsIdleFudge = 5000); + + self.oCv.release(); + + # If we're done, deregister the callback (w/o owning lock). It will + if fDeregister: + self._deregisterEventHandler(); + return True; + + def _pollDhcpServer(self): + """ + Polls the DHCP server by MAC address in host-only setups. + """ + + if self.sIpAddr: + return False; + + if self.oIDhcpServer is None or not self.sMacAddr: + return False; + + try: + (sIpAddr, sState, secIssued, secExpire) = self.oIDhcpServer.findLeaseByMAC(self.sMacAddr, 0); + except: + reporter.log4Xcpt('sMacAddr=%s' % (self.sMacAddr,)); + return False; + + secNow = utils.secondsSinceUnixEpoch(); + reporter.log2('dhcp poll: secNow=%s secExpire=%s secIssued=%s sState=%s sIpAddr=%s' + % (secNow, secExpire, secIssued, sState, sIpAddr,)); + if secNow > secExpire or sState != 'acked' or not sIpAddr: + return False; + + reporter.log('dhcp poll: sIpAddr=%s secExpire=%s (%s TTL) secIssued=%s (%s ago)' + % (sIpAddr, secExpire, secExpire - secNow, secIssued, secNow - secIssued,)); + self._setIp(sIpAddr); + return True; + + # + # Task methods + # + + def pollTask(self, fLocked = False): + """ + Overridden pollTask method. + """ + self._pollDhcpServer(); + return TdTaskBase.pollTask(self, fLocked); + + # + # Public methods + # + + def getResult(self): + """ + Returns the connected TXS session object on success. + Returns None on failure or if the task has not yet completed. + """ + self.oCv.acquire(); + oTxsSession = self.oTxsSession; + self.oCv.release(); + + if oTxsSession is not None and not oTxsSession.isSuccess(): + oTxsSession = None; + return oTxsSession; + + def cancelTask(self): + """ Cancels the task. """ + self._deregisterEventHandler(); # (make sure to avoid cyclic fun) + self.oCv.acquire(); + if not self.fSignalled: + oTxsSession = self.oTxsSession; + if oTxsSession is not None: + self.oCv.release(); + oTxsSession.setTaskOwner(None); + oTxsSession.cancelTask(); + oTxsSession.waitForTask(1000); + self.oCv.acquire(); + self.signalTaskLocked(); + self.oCv.release(); + return True; + + + +class AdditionsStatusTask(TdTaskBase): + """ + Class that takes care of waiting till the guest additions are in a given state. + """ + + class AdditionsStatusTaskCallback(vbox.EventHandlerBase): + """ Class for looking for IPv4 address changes on interface 0.""" + def __init__(self, dArgs): + self.oParentTask = dArgs['oParentTask']; + vbox.EventHandlerBase.__init__(self, dArgs, self.oParentTask.oSession.fpApiVer, + 'AdditionsStatusTaskCallback/%s' % (self.oParentTask.oSession.sName,)); + + def handleEvent(self, oEvt): + try: + enmType = oEvt.type; + except: + reporter.errorXcpt(); + else: + reporter.log2('AdditionsStatusTaskCallback:handleEvent: enmType=%s' % (enmType,)); + if enmType == vboxcon.VBoxEventType_OnGuestAdditionsStatusChanged: + oParentTask = self.oParentTask; + if oParentTask: + oParentTask.pollTask(); + + # end + + + def __init__(self, oSession, oIGuest, cMsTimeout = 120000, aenmWaitForRunLevels = None, aenmWaitForActive = None, + aenmWaitForInactive = None): + """ + aenmWaitForRunLevels - List of run level values to wait for (success if one matches). + aenmWaitForActive - List facilities (type values) that must be active. + aenmWaitForInactive - List facilities (type values) that must be inactive. + + The default is to wait for AdditionsRunLevelType_Userland if all three lists + are unspecified or empty. + """ + TdTaskBase.__init__(self, utils.getCallerName()); + self.oSession = oSession # type: vboxwrappers.SessionWrapper + self.oIGuest = oIGuest; + self.cMsTimeout = cMsTimeout; + self.fSucceeded = False; + self.oVBoxEventHandler = None; + self.aenmWaitForRunLevels = aenmWaitForRunLevels if aenmWaitForRunLevels else []; + self.aenmWaitForActive = aenmWaitForActive if aenmWaitForActive else []; + self.aenmWaitForInactive = aenmWaitForInactive if aenmWaitForInactive else []; + + # Provide a sensible default if nothing is given. + if not self.aenmWaitForRunLevels and not self.aenmWaitForActive and not self.aenmWaitForInactive: + self.aenmWaitForRunLevels = [vboxcon.AdditionsRunLevelType_Userland,]; + + # Register the event handler on hosts which has it: + if oSession.fpApiVer >= 6.1 or hasattr(vboxcon, 'VBoxEventType_OnGuestAdditionsStatusChanged'): + aenmEvents = (vboxcon.VBoxEventType_OnGuestAdditionsStatusChanged,); + dArgs = { + 'oParentTask': self, + }; + self.oVBoxEventHandler = vbox.EventHandlerBase.registerDerivedEventHandler(oSession.oVBoxMgr, + oSession.fpApiVer, + self.AdditionsStatusTaskCallback, + dArgs, + oIGuest, + 'IGuest', + 'AdditionsStatusTaskCallback', + aenmEvents = aenmEvents); + reporter.log2('AdditionsStatusTask: %s' % (self.toString(), )); + + def __del__(self): + """ Make sure we deregister the callback. """ + self._deregisterEventHandler(); + self.oIGuest = None; + return TdTaskBase.__del__(self); + + def toString(self): + return '<%s cMsTimeout=%s, fSucceeded=%s, aenmWaitForRunLevels=%s, aenmWaitForActive=%s, aenmWaitForInactive=%s, ' \ + 'oVBoxEventHandler=%s>' \ + % (TdTaskBase.toString(self), self.cMsTimeout, self.fSucceeded, self.aenmWaitForRunLevels, self.aenmWaitForActive, + self.aenmWaitForInactive, self.oVBoxEventHandler,); + + def _deregisterEventHandler(self): + """Deregisters the event handler.""" + fRc = True; + oVBoxEventHandler = self.oVBoxEventHandler; + if oVBoxEventHandler is not None: + self.oVBoxEventHandler = None; + fRc = oVBoxEventHandler.unregister(); + oVBoxEventHandler.oParentTask = None; # Try avoid cylic deps. + return fRc; + + def _poll(self): + """ + Internal worker for pollTask() that returns the new signalled state. + """ + + # + # Check if any of the runlevels we wait for have been reached: + # + if self.aenmWaitForRunLevels: + try: + enmRunLevel = self.oIGuest.additionsRunLevel; + except: + reporter.errorXcpt(); + return True; + if enmRunLevel not in self.aenmWaitForRunLevels: + reporter.log6('AdditionsStatusTask/poll: enmRunLevel=%s not in %s' % (enmRunLevel, self.aenmWaitForRunLevels,)); + return False; + reporter.log2('AdditionsStatusTask/poll: enmRunLevel=%s matched %s!' % (enmRunLevel, self.aenmWaitForRunLevels,)); + + + # + # Check for the facilities that must all be active. + # + for enmFacility in self.aenmWaitForActive: + try: + (enmStatus, _) = self.oIGuest.getFacilityStatus(enmFacility); + except: + reporter.errorXcpt('enmFacility=%s' % (enmFacility,)); + return True; + if enmStatus != vboxcon.AdditionsFacilityStatus_Active: + reporter.log2('AdditionsStatusTask/poll: enmFacility=%s not active: %s' % (enmFacility, enmStatus,)); + return False; + + # + # Check for the facilities that must all be inactive or terminated. + # + for enmFacility in self.aenmWaitForInactive: + try: + (enmStatus, _) = self.oIGuest.getFacilityStatus(enmFacility); + except: + reporter.errorXcpt('enmFacility=%s' % (enmFacility,)); + return True; + if enmStatus not in (vboxcon.AdditionsFacilityStatus_Inactive, + vboxcon.AdditionsFacilityStatus_Terminated): + reporter.log2('AdditionsStatusTask/poll: enmFacility=%s not inactive: %s' % (enmFacility, enmStatus,)); + return False; + + + reporter.log('AdditionsStatusTask: Poll succeeded, signalling...'); + self.fSucceeded = True; + return True; + + + # + # Task methods + # + + def pollTask(self, fLocked = False): + """ + Overridden pollTask method. + """ + if not fLocked: + self.lockTask(); + + fDeregister = False; + fRc = self.fSignalled; + if not fRc: + fRc = self._poll(); + if fRc or self.getAgeAsMs() >= self.cMsTimeout: + self.signalTaskLocked(); + fDeregister = True; + + if not fLocked: + self.unlockTask(); + + # If we're done, deregister the event callback (w/o owning lock). + if fDeregister: + self._deregisterEventHandler(); + return fRc; + + def getResult(self): + """ + Returns true if the we succeeded. + Returns false if not. If the task is signalled already, then we + encountered a problem while polling. + """ + return self.fSucceeded; + + def cancelTask(self): + """ + Cancels the task. + Just to actively disengage the event handler. + """ + self._deregisterEventHandler(); + return True; + diff --git a/src/VBox/ValidationKit/testdriver/win-vbox-net-drvstore-cleanup.ps1 b/src/VBox/ValidationKit/testdriver/win-vbox-net-drvstore-cleanup.ps1 new file mode 100644 index 00000000..054fc20c --- /dev/null +++ b/src/VBox/ValidationKit/testdriver/win-vbox-net-drvstore-cleanup.ps1 @@ -0,0 +1,71 @@ +# $Id: win-vbox-net-drvstore-cleanup.ps1 $ +## @file +# VirtualBox Validation Kit - network cleanup script (powershell). +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +param([switch]$confirm) + +Function AskForConfirmation ($title_text, $message_text, $yes_text, $no_text) +{ + if ($confirm) { + $title = $title_text + $message = $message_text + + $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", $yes_text + + $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", $no_text + + $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no) + + $result = $host.ui.PromptForChoice($title, $message, $options, 0) + } else { + $result = 0 + } + + return $result +} + +pnputil -e | ForEach-Object { if ($_ -match "Published name :.*(oem\d+\.inf)") {$inf=$matches[1]} elseif ($_ -match "Driver package provider :.*Oracle") {$inf + " " + $_} } + +$result = AskForConfirmation "Clean up the driver store" ` + "Do you want to delete all VirtualBox drivers from the driver store?" ` + "Deletes all VirtualBox drivers from the driver store." ` + "No modifications to the driver store will be made." + +switch ($result) + { + 0 {pnputil -e | ForEach-Object { if ($_ -match "Published name :.*(oem\d+\.inf)") {$inf=$matches[1]} elseif ($_ -match "Driver package provider :.*Oracle") {$inf} } | ForEach-Object { pnputil -d $inf } } + 1 {"Removal cancelled."} + } + diff --git a/src/VBox/ValidationKit/testdriver/win-vbox-net-uninstall.ps1 b/src/VBox/ValidationKit/testdriver/win-vbox-net-uninstall.ps1 new file mode 100644 index 00000000..8bef4731 --- /dev/null +++ b/src/VBox/ValidationKit/testdriver/win-vbox-net-uninstall.ps1 @@ -0,0 +1,253 @@ +# $Id: win-vbox-net-uninstall.ps1 $ +## @file +# VirtualBox Validation Kit - network cleanup script (powershell). +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +param([switch]$confirm) + +Function AskForConfirmation ($title_text, $message_text, $yes_text, $no_text) +{ + if ($confirm) { + $title = $title_text + $message = $message_text + + $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", $yes_text + + $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", $no_text + + $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no) + + $result = $host.ui.PromptForChoice($title, $message, $options, 0) + } else { + $result = 0 + } + + return $result +} + +Function DeleteUnmatchingKeys ($title_text, $reg_key) +{ + $ghostcon = @(Get-ChildItem ($reg_key) | Where-Object { !$connections.ContainsKey($_.PSChildName) } ) + if ($ghostcon.count -eq 0) { + Write-Host "`nNo ghost connections has been found -- nothing to do" + } else { + Write-Host "`nParameter keys for the following connections will be removed:" + Write-Host ($ghostcon | Out-String) + + $result = AskForConfirmation $title_text ` + "Do you want to delete the keys listed above?" ` + "Deletes all ghost connection keys from the registry." ` + "No modifications to the registry will be made." + + switch ($result) + { + 0 {$ghostcon.GetEnumerator() | ForEach-Object { Remove-Item -Path $_ -Recurse }} + 1 {"Removal cancelled."} + } + } +} + + +Push-Location +cd "Registry::" +Write-Host "Retrieving valid connections:" +$iftypes = @{} +$connections = @{} +$ghostcon_names = @{} +Get-Item ".\HKLM\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\0*" | ` + ForEach-Object { + $prop = (Get-ItemProperty $_.PSPath) + $conn = $null + if (Test-Path ("HKLM\SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\" + $prop.NetCfgInstanceId + "\Connection")) { + $conn = (Get-ItemProperty ("HKLM\SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\" + $prop.NetCfgInstanceId + "\Connection")) + } + $iftype = $prop."*IfType" + if ($iftypes.ContainsKey($iftype)) { + $iftypes[$iftype] = $iftypes[$iftype] + [Math]::pow(2,$prop.NetLuidIndex) + } else { + $iftypes[$iftype] = [Math]::pow(2,$prop.NetLuidIndex) + } + if ($conn -ne $null) { + $connections[$prop.NetCfgInstanceId] = $conn.Name + Write-Host $prop.NetCfgInstanceId $conn.Name "|" $prop."*IfType" $prop.NetLuidIndex $prop.DriverDesc + } else { + Write-Host $prop.NetCfgInstanceId [MISSING] "|" $prop."*IfType" $prop.NetLuidIndex $prop.DriverDesc + } + } + +# Someday we may want to process other types than Ethernet as well: $iftypes.GetEnumerator() | ForEach-Object { +if ($iftypes[6] -gt 9223372036854775808) { + Write-Host "Found more than 63 interfaces (mask=" $iftypes[6] ") -- bailing out" + exit +} +Write-Host "`nChecking if the used LUID index mask is correct:" +$correctmask = [BitConverter]::GetBytes([int64]($iftypes[6])) +$actualmask = (Get-ItemProperty -Path "HKLM\SYSTEM\CurrentControlSet\Services\NDIS\IfTypes\6" -Name "IfUsedNetLuidIndices").IfUsedNetLuidIndices +$needcorrection = $FALSE +$ai = 0 +$lastnonzero = 0 +for ($ci = 0; $ci -lt $correctmask.Length; $ci++) { + if ($ai -lt $actualmask.Length) { + $aval = $actualmask[$ai++] + } else { + $aval = 0 + } + if ($correctmask[$ci] -ne 0) { + $lastnonzero = $ci + } + if ($correctmask[$ci] -eq $aval) { + Write-Host "DEBUG: " $correctmask[$ci].ToString("X2") " == " $aval.ToString("X2") + } else { + Write-Host "DEBUG: " $correctmask[$ci].ToString("X2") " != " $aval.ToString("X2") + $needcorrection = $TRUE + } +} +if ($ai -lt $actualmask.Length) { + for (; $ai -lt $actualmask.Length; $ai++) { + if ($actualmask[$ai] -eq 0) { + Write-Host "DEBUG: 0 == 0" + } else { + Write-Host "DEBUG: " $actualmask[$ai].ToString("X2") " != 0" + $needcorrection = $TRUE + } + } +} +if ($needcorrection) { + Write-Host "Current mask is " ($actualmask|foreach {$_.ToString("X2")}) ", while it should be" ($correctmask|foreach {$_.ToString("X2")}) + if ($confirm) { + Set-ItemProperty -Path "HKLM\SYSTEM\CurrentControlSet\Services\NDIS\IfTypes\6" -Name "IfUsedNetLuidIndices" -Value $correctmask -Type Binary -Confirm + } else { + Set-ItemProperty -Path "HKLM\SYSTEM\CurrentControlSet\Services\NDIS\IfTypes\6" -Name "IfUsedNetLuidIndices" -Value $correctmask -Type Binary + } +} else { + Write-Host "The used LUID index mask is correct -- nothing to do" +} + +#Write-Host ($connections | Out-String) +$ghostcon = @(Get-ChildItem ("HKLM\SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}") | Where-Object { !$connections.ContainsKey($_.PSChildName) -and $_.PSChildName -ne "Descriptions" } ) +if ($ghostcon -eq $null) { + Write-Host "`nNo ghost connections has been found -- nothing to do" +} else { + Write-Host "`nThe following connections will be removed:" + #Write-Host ($ghostcon | Out-String) + + $ghostcon.GetEnumerator() | ForEach-Object { + $prop = (Get-ItemProperty "$_\Connection") + if ($prop.PnPInstanceId -eq $null) { + Write-Host "WARNING! PnPInstanceId does not exist for" $_.PSChildName + } elseif (!($prop.PnPInstanceId.ToString() -match "SUN_VBOXNETFLTMP")) { + Write-Host "WARNING! PnPInstanceId (" $prop.PnPInstanceId.ToString() ") does not match ROOT\SUN_VBOXNETFLTMP for" $_.PSChildName + } + if ($prop.Name -eq $null) { + Write-Host "WARNING! Name does not exist for" $_.PSChildName + } else { + $ghostcon_names.Add($_.PSChildName, $prop.Name) + Write-Host $_.PSChildName -nonewline + Write-Host " " -nonewline + Write-Host $prop.Name + } + } + + $result = AskForConfirmation "Delete Registry Keys" ` + "Do you want to delete the keys listed above?" ` + "Deletes all ghost connection keys from the registry." ` + "No modifications to the registry will be made." + + switch ($result) + { + 0 {$ghostcon.GetEnumerator() | ForEach-Object { Remove-Item -Path $_.PSPath -Recurse }} + 1 {"Removal cancelled."} + } +} + +# Delete WFPLWFS parameter keys +DeleteUnmatchingKeys "Delete WFPLWFS Parameter Keys (Adapter subkey)" "HKLM\SYSTEM\CurrentControlSet\Services\WFPLWFS\Parameters\Adapters" +DeleteUnmatchingKeys "Delete WFPLWFS Parameter Keys (NdisAdapter subkey)" "HKLM\SYSTEM\CurrentControlSet\Services\WFPLWFS\Parameters\NdisAdapters" +# Delete Psched parameter keys +DeleteUnmatchingKeys "Delete Psched Parameter Keys (Adapter subkey)" "HKLM\SYSTEM\CurrentControlSet\Services\Psched\Parameters\Adapters" +DeleteUnmatchingKeys "Delete Psched Parameter Keys (NdisAdapter subkey)" "HKLM\SYSTEM\CurrentControlSet\Services\Psched\Parameters\NdisAdapters" + +# Clean up NSI entries +$nsi_obsolete = New-Object System.Collections.ArrayList +$nsi_path = "HKLM\SYSTEM\CurrentControlSet\Control\Nsi\{EB004A11-9B1A-11D4-9123-0050047759BC}\10" +$nsi = (Get-Item $nsi_path) | Select-Object -ExpandProperty property +$nsi | ForEach-Object { + $value = (Get-ItemProperty -Path $nsi_path -Name $_).$_ + [byte[]]$guid_bytes = $value[1040..1055] + $guid = New-Object -TypeName System.Guid -ArgumentList (,$guid_bytes) + $guid_string = $guid.ToString("B").ToUpper() + $nsi_conn_name_last = 6 + $value[4] + $value[5]*256 + $nsi_conn_name = [Text.Encoding]::Unicode.GetString($value[6..$nsi_conn_name_last]) + $nsi_if_name_last = 522 + $value[520] + $value[521]*256 + $nsi_if_name = [Text.Encoding]::Unicode.GetString($value[522..$nsi_if_name_last]) + Write-Host $_ -nonewline + Write-Host " " -nonewline + Write-Host $guid_string -nonewline + Write-Host " " -nonewline + if ($connections.ContainsKey($guid_string)) { + Write-Host $nsi_if_name + } else { + [void] $nsi_obsolete.Add($_) + Write-Host "[OBSOLETE] " $nsi_if_name -foregroundcolor red + } +} + +$result = AskForConfirmation "Delete NSI Entries" ` + "Do you want to delete the entries marked in red above?" ` + "Deletes all marked entries from the NSI registry key." ` + "No modifications to the registry will be made." + +switch ($result) + { + 0 {$nsi_obsolete.GetEnumerator() | ForEach-Object { Remove-ItemProperty -Path $nsi_path -Name $_ }} + 1 {"Removal cancelled."} + } + +# Clean up uninstalled connections +if ( (Get-ChildItem "HKLM\SYSTEM\CurrentControlSet\Control\Network\Uninstalled" | Measure-Object).Count -gt 10 ) { + $result = AskForConfirmation "Delete Uninstalled Network Connection Registry Keys" ` + "There are over 10 uninstalled network connections accumulated in the registry. Do you want to delete them?" ` + "Deletes uninstalled connection keys from the registry." ` + "No modifications to the registry will be made." + + switch ($result) + { + 0 {Remove-Item -Path "HKLM\SYSTEM\CurrentControlSet\Control\Network\Uninstalled\*" -Recurse} + 1 {"Removal cancelled."} + } +} else { + Write-Host "Less than 10 uninstalled connections -- no action yet required." +} + +Pop-Location diff --git a/src/VBox/ValidationKit/testdriver/winbase.py b/src/VBox/ValidationKit/testdriver/winbase.py new file mode 100755 index 00000000..92deac1d --- /dev/null +++ b/src/VBox/ValidationKit/testdriver/winbase.py @@ -0,0 +1,336 @@ +# -*- coding: utf-8 -*- +# $Id: winbase.py $ + +""" +This module is here to externalize some Windows specifics that gives pychecker +a hard time when running on non-Windows systems. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import ctypes; +import os; +import sys; + +# Windows specific imports. +import pywintypes; # pylint: disable=import-error +import winerror; # pylint: disable=import-error +import win32con; # pylint: disable=import-error +import win32api; # pylint: disable=import-error +import win32console; # pylint: disable=import-error +import win32event; # pylint: disable=import-error +import win32process; # pylint: disable=import-error + +# Validation Kit imports. +from testdriver import reporter; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + + +# +# Windows specific implementation of base functions. +# + +def processInterrupt(uPid): + """ + The Windows version of base.processInterrupt + + Note! This doesn't work terribly well with a lot of processes. + """ + try: + # pylint: disable=no-member + win32console.GenerateConsoleCtrlEvent(win32con.CTRL_BREAK_EVENT, uPid); # pylint: disable=c-extension-no-member + #GenerateConsoleCtrlEvent = ctypes.windll.kernel32.GenerateConsoleCtrlEvent + #rc = GenerateConsoleCtrlEvent(1, uPid); + #reporter.log('GenerateConsoleCtrlEvent -> %s' % (rc,)); + fRc = True; + except: + reporter.logXcpt('uPid=%s' % (uPid,)); + fRc = False; + return fRc; + +def postThreadMesssageClose(uTid): + """ Posts a WM_CLOSE message to the specified thread.""" + fRc = False; + try: + win32api.PostThreadMessage(uTid, win32con.WM_CLOSE, 0, 0); # pylint: disable=no-member,c-extension-no-member + fRc = True; + except: + reporter.logXcpt('uTid=%s' % (uTid,)); + return fRc; + +def postThreadMesssageQuit(uTid): + """ Posts a WM_QUIT message to the specified thread.""" + fRc = False; + try: + win32api.PostThreadMessage(uTid, win32con.WM_QUIT, # pylint: disable=no-member,c-extension-no-member + 0x40010004, 0); # DBG_TERMINATE_PROCESS + fRc = True; + except: + reporter.logXcpt('uTid=%s' % (uTid,)); + return fRc; + +def processTerminate(uPid): + """ The Windows version of base.processTerminate """ + # pylint: disable=no-member + fRc = False; + try: + hProcess = win32api.OpenProcess(win32con.PROCESS_TERMINATE, # pylint: disable=no-member,c-extension-no-member + False, uPid); + except: + reporter.logXcpt('uPid=%s' % (uPid,)); + else: + try: + win32process.TerminateProcess(hProcess, # pylint: disable=no-member,c-extension-no-member + 0x40010004); # DBG_TERMINATE_PROCESS + fRc = True; + except: + reporter.logXcpt('uPid=%s' % (uPid,)); + hProcess.Close(); #win32api.CloseHandle(hProcess) + return fRc; + +def processKill(uPid): + """ The Windows version of base.processKill """ + return processTerminate(uPid); + +def processExists(uPid): + """ The Windows version of base.processExists """ + # We try open the process for waiting since this is generally only forbidden in a very few cases. + try: + hProcess = win32api.OpenProcess(win32con.SYNCHRONIZE, False, uPid); # pylint: disable=no-member,c-extension-no-member + except pywintypes.error as oXcpt: # pylint: disable=no-member + if oXcpt.winerror == winerror.ERROR_INVALID_PARAMETER: + return False; + if oXcpt.winerror != winerror.ERROR_ACCESS_DENIED: + reporter.logXcpt('uPid=%s oXcpt=%s' % (uPid, oXcpt)); + return False; + reporter.logXcpt('uPid=%s oXcpt=%s' % (uPid, oXcpt)); + except Exception as oXcpt: + reporter.logXcpt('uPid=%s' % (uPid,)); + return False; + else: + hProcess.Close(); #win32api.CloseHandle(hProcess) + return True; + +def processCheckPidAndName(uPid, sName): + """ The Windows version of base.processCheckPidAndName """ + fRc = processExists(uPid); + if fRc is True: + try: + from win32com.client import GetObject; # pylint: disable=import-error + oWmi = GetObject('winmgmts:'); + aoProcesses = oWmi.InstancesOf('Win32_Process'); + for oProcess in aoProcesses: + if long(oProcess.Properties_("ProcessId").Value) == uPid: + sCurName = oProcess.Properties_("Name").Value; + reporter.log2('uPid=%s sName=%s sCurName=%s' % (uPid, sName, sCurName)); + sName = sName.lower(); + sCurName = sCurName.lower(); + if os.path.basename(sName) == sName: + sCurName = os.path.basename(sCurName); + + if sCurName == sName \ + or sCurName + '.exe' == sName \ + or sCurName == sName + '.exe': + fRc = True; + break; + except: + reporter.logXcpt('uPid=%s sName=%s' % (uPid, sName)); + return fRc; + +# +# Some helper functions. +# +def processCreate(sName, asArgs): + """ + Returns a (pid, handle, tid) tuple on success. (-1, None) on failure (logged). + """ + + # Construct a command line. + sCmdLine = ''; + for sArg in asArgs: + if sCmdLine == '': + sCmdLine += '"'; + else: + sCmdLine += ' "'; + sCmdLine += sArg; + sCmdLine += '"'; + + # Try start the process. + # pylint: disable=no-member + dwCreationFlags = win32con.CREATE_NEW_PROCESS_GROUP; + oStartupInfo = win32process.STARTUPINFO(); # pylint: disable=c-extension-no-member + try: + (hProcess, hThread, uPid, uTid) = win32process.CreateProcess(sName, # pylint: disable=c-extension-no-member + sCmdLine, # CommandLine + None, # ProcessAttributes + None, # ThreadAttibutes + 1, # fInheritHandles + dwCreationFlags, + None, # Environment + None, # CurrentDirectory. + oStartupInfo); + except: + reporter.logXcpt('sName="%s" sCmdLine="%s"' % (sName, sCmdLine)); + return (-1, None, -1); + + # Dispense with the thread handle. + try: + hThread.Close(); # win32api.CloseHandle(hThread); + except: + reporter.logXcpt(); + + # Try get full access to the process. + try: + hProcessFullAccess = win32api.DuplicateHandle( # pylint: disable=c-extension-no-member + win32api.GetCurrentProcess(), # pylint: disable=c-extension-no-member + hProcess, + win32api.GetCurrentProcess(), # pylint: disable=c-extension-no-member + win32con.PROCESS_TERMINATE + | win32con.PROCESS_QUERY_INFORMATION + | win32con.SYNCHRONIZE + | win32con.DELETE, + False, + 0); + hProcess.Close(); # win32api.CloseHandle(hProcess); + hProcess = hProcessFullAccess; + except: + reporter.logXcpt(); + reporter.log2('processCreate -> %#x, hProcess=%s %#x' % (uPid, hProcess, hProcess.handle,)); + return (uPid, hProcess, uTid); + +def processPollByHandle(hProcess): + """ + Polls the process handle to see if it has finished (True) or not (False). + """ + try: + dwWait = win32event.WaitForSingleObject(hProcess, 0); # pylint: disable=no-member,c-extension-no-member + except: + reporter.logXcpt('hProcess=%s %#x' % (hProcess, hProcess.handle,)); + return True; + return dwWait != win32con.WAIT_TIMEOUT; #0x102; # + + +def processTerminateByHandle(hProcess): + """ + Terminates the process. + """ + try: + win32api.TerminateProcess(hProcess, # pylint: disable=no-member,c-extension-no-member + 0x40010004); # DBG_TERMINATE_PROCESS + except: + reporter.logXcpt('hProcess=%s %#x' % (hProcess, hProcess.handle,)); + return False; + return True; + +# +# Misc +# + +def logMemoryStats(): + """ + Logs windows memory stats. + """ + class MemoryStatusEx(ctypes.Structure): + """ MEMORYSTATUSEX """ + kaFields = [ + ( 'dwLength', ctypes.c_ulong ), + ( 'dwMemoryLoad', ctypes.c_ulong ), + ( 'ullTotalPhys', ctypes.c_ulonglong ), + ( 'ullAvailPhys', ctypes.c_ulonglong ), + ( 'ullTotalPageFile', ctypes.c_ulonglong ), + ( 'ullAvailPageFile', ctypes.c_ulonglong ), + ( 'ullTotalVirtual', ctypes.c_ulonglong ), + ( 'ullAvailVirtual', ctypes.c_ulonglong ), + ( 'ullAvailExtendedVirtual', ctypes.c_ulonglong ), + ]; + _fields_ = kaFields; # pylint: disable=invalid-name + + def __init__(self): + super(MemoryStatusEx, self).__init__(); + self.dwLength = ctypes.sizeof(self); + + try: + oStats = MemoryStatusEx(); + ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(oStats)); + except: + reporter.logXcpt(); + return False; + + reporter.log('Memory statistics:'); + for sField, _ in MemoryStatusEx.kaFields: + reporter.log(' %32s: %s' % (sField, getattr(oStats, sField))); + return True; + +def checkProcessHeap(): + """ + Calls HeapValidate(GetProcessHeap(), 0, NULL); + """ + + # Get the process heap. + try: + hHeap = ctypes.windll.kernel32.GetProcessHeap(); + except: + reporter.logXcpt(); + return False; + + # Check it. + try: + fIsOkay = ctypes.windll.kernel32.HeapValidate(hHeap, 0, None); + except: + reporter.logXcpt(); + return False; + + if fIsOkay == 0: + reporter.log('HeapValidate failed!'); + + # Try trigger a dump using c:\utils\procdump64.exe. + from common import utils; + + iPid = os.getpid(); + asArgs = [ 'e:\\utils\\procdump64.exe', '-ma', '%s' % (iPid,), 'c:\\CrashDumps\\python.exe-%u-heap.dmp' % (iPid,)]; + if utils.getHostArch() != 'amd64': + asArgs[0] = 'c:\\utils\\procdump.exe' + reporter.log('Trying to dump this process using: %s' % (asArgs,)); + utils.processCall(asArgs); + + # Generate a crash exception. + ctypes.windll.msvcrt.strcpy(None, None, 1024); + + return True; + diff --git a/src/VBox/ValidationKit/testmanager/Makefile.kmk b/src/VBox/ValidationKit/testmanager/Makefile.kmk new file mode 100644 index 00000000..5f3da1f6 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/Makefile.kmk @@ -0,0 +1,54 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +include $(PATH_SUB_CURRENT)/cgi/Makefile.kmk +include $(PATH_SUB_CURRENT)/core/Makefile.kmk +include $(PATH_SUB_CURRENT)/batch/Makefile.kmk +include $(PATH_SUB_CURRENT)/debug/Makefile.kmk +include $(PATH_SUB_CURRENT)/misc/Makefile.kmk +include $(PATH_SUB_CURRENT)/webui/Makefile.kmk + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(wildcard $(PATH_SUB_CURRENT)/*.py) +VBOX_VALIDATIONKIT_JS_SOURCES += $(wildcard $(PATH_SUB_CURRENT)/htdocs/js/*.js) + + +$(evalcall def_vbox_validationkit_process_python_sources) +$(evalcall def_vbox_validationkit_process_js_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/testmanager/__init__.py b/src/VBox/ValidationKit/testmanager/__init__.py new file mode 100644 index 00000000..6ef0a78c --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/__init__.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# $Id: __init__.py $ + +""" +Test Manager. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + diff --git a/src/VBox/ValidationKit/testmanager/apache-template-2.2.conf b/src/VBox/ValidationKit/testmanager/apache-template-2.2.conf new file mode 100644 index 00000000..ba97513f --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/apache-template-2.2.conf @@ -0,0 +1,87 @@ +# $Id: apache-template-2.2.conf $ +## @file +# Test Manager - Apache 2.2 configuration sample. +# +# Requires TestManagerRootDir to be set in the environment (envvars file for instance). +# + +# +# Copyright (C) 2012-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + + +<LocationMatch "^/testmanager/logout.py"> + AuthType Basic + AuthName "Test Manager" + AuthUserFile ${TestManagerRootDir}/misc/htpasswd-logout + Require user logout +</LocationMatch> + +<LocationMatch "^/testmanager/(?!(testboxdisp.py|logout.py|/*htdocs/downloads/.*))"> + AuthType Basic + AuthName "Test Manager" + AuthUserFile ${TestManagerRootDir}/misc/htpasswd-sample + Require valid-user +</LocationMatch> + +# These two directives are only for local testing! +Alias /testmanager/htdocs/downloads/VBoxValidationKit.zip ${VBoxBuildOutputDir}/VBoxValidationKit.zip +<Location /testmanager/htdocs/downloads/VBoxValidationKit.zip> + Options Indexes + Order allow,deny + Allow from all +</Location> + +Alias /testmanager/htdocs/ ${TestManagerRootDir}/htdocs/ +<Directory ${TestManagerRootDir}/htdocs/> + AllowOverride None + Options Indexes + Order allow,deny + Allow from all +</Directory> + +Alias /testmanager/logs/ /var/tmp/testmanager/ +<Directory /var/tmp/testmanager/> + AllowOverride None + Options Indexes + Order allow,deny + Allow from all +</Directory> + +Alias /testmanager/ ${TestManagerRootDir}/cgi/ +<Directory ${TestManagerRootDir}/cgi/> + AllowOverride None + Options Indexes ExecCGI + DirectoryIndex index.py + AddHandler cgi-script .py + Order allow,deny + Allow from all +</Directory> + diff --git a/src/VBox/ValidationKit/testmanager/apache-template-2.4.conf b/src/VBox/ValidationKit/testmanager/apache-template-2.4.conf new file mode 100644 index 00000000..dd16bc70 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/apache-template-2.4.conf @@ -0,0 +1,81 @@ +# $Id: apache-template-2.4.conf $ +## @file +# Test Manager - Apache 2.4 configuration sample. +# +# Use the new Define directive to define TestManagerRootDir and +# VBoxBuildOutputDir before including this file. +# + +# +# Copyright (C) 2012-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + + +<LocationMatch "^/testmanager/logout.py"> + AuthType Basic + AuthName "Test Manager" + AuthUserFile ${TestManagerRootDir}/misc/htpasswd-logout + Require user logout +</LocationMatch> + +<LocationMatch "^/testmanager/(?!(testboxdisp.py|logout.py|/*htdocs/downloads/.*))"> + AuthType Basic + AuthName "Test Manager" + AuthUserFile ${TestManagerRootDir}/misc/htpasswd-sample + Require valid-user +</LocationMatch> + +# These two directives are only for local testing! +Alias /testmanager/htdocs/downloads/VBoxValidationKit.zip ${VBoxBuildOutputDir}/VBoxValidationKit.zip +<Location /testmanager/htdocs/downloads/VBoxValidationKit.zip> + Options Indexes + Require all granted +</Location> + +Alias /testmanager/htdocs/ ${TestManagerRootDir}/htdocs/ +<Directory ${TestManagerRootDir}/htdocs/> + AllowOverride None + Options Indexes +</Directory> + +Alias /testmanager/logs/ /var/tmp/testmanager/ +<Directory /var/tmp/testmanager/> + AllowOverride None + Options Indexes +</Directory> + +Alias /testmanager/ ${TestManagerRootDir}/cgi/ +<Directory ${TestManagerRootDir}/cgi/> + AllowOverride None + Options Indexes ExecCGI + DirectoryIndex index.py + AddHandler cgi-script .py +</Directory> + diff --git a/src/VBox/ValidationKit/testmanager/batch/Makefile.kmk b/src/VBox/ValidationKit/testmanager/batch/Makefile.kmk new file mode 100644 index 00000000..1de2d1dc --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/batch/Makefile.kmk @@ -0,0 +1,46 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(wildcard $(PATH_SUB_CURRENT)/*.py) + +$(evalcall def_vbox_validationkit_process_python_sources) +$(evalcall def_vbox_validationkit_process_js_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/testmanager/batch/add_build.py b/src/VBox/ValidationKit/testmanager/batch/add_build.py new file mode 100755 index 00000000..3f2ea505 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/batch/add_build.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: add_build.py $ +# pylint: disable=line-too-long + +""" +Interface used by the tinderbox server side software to add a fresh build. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Standard python imports +import sys; +import os; +from optparse import OptionParser; # pylint: disable=deprecated-module + +# Add Test Manager's modules path +g_ksTestManagerDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksTestManagerDir); + +# Test Manager imports +from testmanager.core.db import TMDatabaseConnection; +from testmanager.core.build import BuildDataEx, BuildLogic, BuildCategoryData; + +class Build(object): # pylint: disable=too-few-public-methods + """ + Add build info into Test Manager database. + """ + + def __init__(self): + """ + Parse command line. + """ + + oParser = OptionParser(); + oParser.add_option('-q', '--quiet', dest = 'fQuiet', action = 'store_true', + help = 'Quiet execution'); + oParser.add_option('-b', '--branch', dest = 'sBranch', metavar = '<branch>', + help = 'branch name (default: trunk)', default = 'trunk'); + oParser.add_option('-p', '--product', dest = 'sProductName', metavar = '<name>', + help = 'The product name.'); + oParser.add_option('-r', '--revision', dest = 'iRevision', metavar = '<rev>', + help = 'revision number'); + oParser.add_option('-R', '--repository', dest = 'sRepository', metavar = '<repository>', + help = 'Version control repository name.'); + oParser.add_option('-t', '--type', dest = 'sBuildType', metavar = '<type>', + help = 'build type (debug, release etc.)'); + oParser.add_option('-v', '--version', dest = 'sProductVersion', metavar = '<ver>', + help = 'The product version number (suitable for RTStrVersionCompare)'); + oParser.add_option('-o', '--os-arch', dest = 'asTargetOsArches', metavar = '<os.arch>', action = 'append', + help = 'Target OS and architecture. This option can be repeated.'); + oParser.add_option('-l', '--log', dest = 'sBuildLogPath', metavar = '<url>', + help = 'URL to the build logs (optional).'); + oParser.add_option('-f', '--file', dest = 'asFiles', metavar = '<file|url>', action = 'append', + help = 'URLs or build share relative path to a build output file. This option can be repeated.'); + + (self.oConfig, _) = oParser.parse_args(); + + # Check command line + asMissing = []; + if self.oConfig.sBranch is None: asMissing.append('--branch'); + if self.oConfig.iRevision is None: asMissing.append('--revision'); + if self.oConfig.sProductVersion is None: asMissing.append('--version'); + if self.oConfig.sProductName is None: asMissing.append('--product'); + if self.oConfig.sBuildType is None: asMissing.append('--type'); + if self.oConfig.asTargetOsArches is None: asMissing.append('--os-arch'); + if self.oConfig.asFiles is None: asMissing.append('--file'); + if asMissing: + sys.stderr.write('syntax error: Missing: %s\n' % (asMissing,)); + sys.exit(1); + # Temporary default. + if self.oConfig.sRepository is None: + self.oConfig.sRepository = 'vbox'; + + def add(self): + """ + Add build data record into database. + """ + oDb = TMDatabaseConnection() + + # Assemble the build data. + oBuildData = BuildDataEx() + oBuildData.idBuildCategory = None; + oBuildData.iRevision = self.oConfig.iRevision + oBuildData.sVersion = self.oConfig.sProductVersion + oBuildData.sLogUrl = self.oConfig.sBuildLogPath + oBuildData.sBinaries = ','.join(self.oConfig.asFiles); + oBuildData.oCat = BuildCategoryData().initFromValues(sProduct = self.oConfig.sProductName, + sRepository = self.oConfig.sRepository, + sBranch = self.oConfig.sBranch, + sType = self.oConfig.sBuildType, + asOsArches = self.oConfig.asTargetOsArches); + + # Add record to database + try: + BuildLogic(oDb).addEntry(oBuildData, fCommit = True); + except: + if self.oConfig.fQuiet: + sys.exit(1); + raise; + oDb.close(); + return 0; + +if __name__ == '__main__': + sys.exit(Build().add()); + diff --git a/src/VBox/ValidationKit/testmanager/batch/check_for_deleted_builds.py b/src/VBox/ValidationKit/testmanager/batch/check_for_deleted_builds.py new file mode 100755 index 00000000..800fcaf0 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/batch/check_for_deleted_builds.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: check_for_deleted_builds.py $ +# pylint: disable=line-too-long + +""" +Admin job for checking detecting deleted builds. + +This is necessary when the tinderbox <-> test manager interface was +busted and the build info in is out of sync. The result is generally +a lot of skipped tests because of missing builds, typically during +bisecting problems. +""" + +from __future__ import print_function; + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Standard python imports +import sys; +import os; +from optparse import OptionParser; # pylint: disable=deprecated-module + +# Add Test Manager's modules path +g_ksTestManagerDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksTestManagerDir); + +# Test Manager imports +from testmanager.core.db import TMDatabaseConnection; +from testmanager.core.build import BuildLogic; + + + +class BuildChecker(object): # pylint: disable=too-few-public-methods + """ + Add build info into Test Manager database. + """ + + def __init__(self): + """ + Parse command line. + """ + + oParser = OptionParser(); + oParser.add_option('-q', '--quiet', dest = 'fQuiet', action = 'store_true', default = False, + help = 'Quiet execution'); + oParser.add_option('--dry-run', dest = 'fRealRun', action = 'store_false', default = False, + help = 'Dry run'); + oParser.add_option('--real-run', dest = 'fRealRun', action = 'store_true', default = False, + help = 'Real run'); + + (self.oConfig, _) = oParser.parse_args(); + if not self.oConfig.fQuiet: + if not self.oConfig.fRealRun: + print('Dry run.'); + else: + print('Real run! Will commit findings!'); + + + def checkBuilds(self): + """ + Add build data record into database. + """ + oDb = TMDatabaseConnection(); + oBuildLogic = BuildLogic(oDb); + + tsNow = oDb.getCurrentTimestamp(); + cMaxRows = 1024; + iStart = 0; + while True: + aoBuilds = oBuildLogic.fetchForListing(iStart, cMaxRows, tsNow); + if not self.oConfig.fQuiet and aoBuilds: + print('Processing builds #%s thru #%s' % (aoBuilds[0].idBuild, aoBuilds[-1].idBuild)); + + for oBuild in aoBuilds: + if oBuild.fBinariesDeleted is False: + rc = oBuild.areFilesStillThere(); + if rc is False: + if not self.oConfig.fQuiet: + print('missing files for build #%s / r%s / %s / %s / %s / %s / %s' + % (oBuild.idBuild, oBuild.iRevision, oBuild.sVersion, oBuild.oCat.sType, + oBuild.oCat.sBranch, oBuild.oCat.sProduct, oBuild.oCat.asOsArches,)); + print(' %s' % (oBuild.sBinaries,)); + if self.oConfig.fRealRun is True: + oBuild.fBinariesDeleted = True; + oBuildLogic.editEntry(oBuild, fCommit = True); + elif rc is True and not self.oConfig.fQuiet: + print('build #%s still have its files' % (oBuild.idBuild,)); + elif rc is None and not self.oConfig.fQuiet: + print('Unable to determine state of build #%s' % (oBuild.idBuild,)); + + # advance + if len(aoBuilds) < cMaxRows: + break; + iStart += len(aoBuilds); + + oDb.close(); + return 0; + +if __name__ == '__main__': + sys.exit(BuildChecker().checkBuilds()); + diff --git a/src/VBox/ValidationKit/testmanager/batch/close_orphaned_testsets.py b/src/VBox/ValidationKit/testmanager/batch/close_orphaned_testsets.py new file mode 100755 index 00000000..7243ae96 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/batch/close_orphaned_testsets.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: close_orphaned_testsets.py $ +# pylint: disable=line-too-long + +""" +Maintenance tool for closing orphaned testsets. +""" + +from __future__ import print_function; + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Standard python imports +import sys +import os +from optparse import OptionParser; # pylint: disable=deprecated-module + +# Add Test Manager's modules path +g_ksTestManagerDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(g_ksTestManagerDir) + +# Test Manager imports +from testmanager.core.db import TMDatabaseConnection +from testmanager.core.testset import TestSetLogic; + + +class CloseOrphanedTestSets(object): + """ + Finds and closes orphaned testsets. + """ + + def __init__(self): + """ + Parse command line + """ + oParser = OptionParser(); + oParser.add_option('-d', '--just-do-it', dest='fJustDoIt', action='store_true', + help='Do the database changes.'); + + + (self.oConfig, _) = oParser.parse_args(); + + + def main(self): + """ Main method. """ + oDb = TMDatabaseConnection(); + + # Get a list of orphans. + oLogic = TestSetLogic(oDb); + aoOrphans = oLogic.fetchOrphaned(); + if aoOrphans: + # Complete them. + if self.oConfig.fJustDoIt: + print('Completing %u test sets as abandoned:' % (len(aoOrphans),)); + for oTestSet in aoOrphans: + print('#%-7u: idTestBox=%-3u tsCreated=%s tsDone=%s' + % (oTestSet.idTestSet, oTestSet.idTestBox, oTestSet.tsCreated, oTestSet.tsDone)); + oLogic.completeAsAbandoned(oTestSet.idTestSet); + print('Committing...'); + oDb.commit(); + else: + for oTestSet in aoOrphans: + print('#%-7u: idTestBox=%-3u tsCreated=%s tsDone=%s' + % (oTestSet.idTestSet, oTestSet.idTestBox, oTestSet.tsCreated, oTestSet.tsDone)); + print('Not completing any testsets without seeing the --just-do-it option.'); + else: + print('No orphaned test sets.\n'); + return 0; + + +if __name__ == '__main__': + sys.exit(CloseOrphanedTestSets().main()) + diff --git a/src/VBox/ValidationKit/testmanager/batch/del_build.py b/src/VBox/ValidationKit/testmanager/batch/del_build.py new file mode 100755 index 00000000..7d2e9207 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/batch/del_build.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: del_build.py $ +# pylint: disable=line-too-long + +""" +Interface used by the tinderbox server side software to mark build binaries +deleted. +""" + +from __future__ import print_function; + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Standard python imports +import sys +import os +from optparse import OptionParser; # pylint: disable=deprecated-module + +# Add Test Manager's modules path +g_ksTestManagerDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(g_ksTestManagerDir) + +# Test Manager imports +from testmanager.core.db import TMDatabaseConnection +from testmanager.core.build import BuildLogic + + +def markBuildsDeleted(): + """ + Marks the builds using the specified binaries as deleted. + """ + + oParser = OptionParser() + oParser.add_option('-q', '--quiet', dest='fQuiet', action='store_true', + help='Quiet execution'); + + (oConfig, asArgs) = oParser.parse_args() + if not asArgs: + if not oConfig.fQuiet: + sys.stderr.write('syntax error: No builds binaries specified\n'); + return 1; + + + oDb = TMDatabaseConnection() + oLogic = BuildLogic(oDb) + + for sBuildBin in asArgs: + try: + cBuilds = oLogic.markDeletedByBinaries(sBuildBin, fCommit = True) + except: + if oConfig.fQuiet: + sys.exit(1); + raise; + else: + if not oConfig.fQuiet: + print("del_build.py: Marked %u builds associated with '%s' as deleted." % (cBuilds, sBuildBin,)); + + oDb.close() + return 0; + +if __name__ == '__main__': + sys.exit(markBuildsDeleted()) + diff --git a/src/VBox/ValidationKit/testmanager/batch/filearchiver.py b/src/VBox/ValidationKit/testmanager/batch/filearchiver.py new file mode 100755 index 00000000..13e0706a --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/batch/filearchiver.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: filearchiver.py $ +# pylint: disable=line-too-long + +""" +A cronjob that compresses logs and other files, moving them to the +g_ksZipFileAreaRootDir storage area. +""" + +from __future__ import print_function; + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Standard python imports +import sys +import os +from optparse import OptionParser; # pylint: disable=deprecated-module +import time; +import zipfile; + +# Add Test Manager's modules path +g_ksTestManagerDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(g_ksTestManagerDir) + +# Test Manager imports +from common import utils; +from testmanager import config; +from testmanager.core.db import TMDatabaseConnection; +from testmanager.core.testset import TestSetData, TestSetLogic; + + + +class FileArchiverBatchJob(object): # pylint: disable=too-few-public-methods + """ + Log+files comp + """ + + def __init__(self, oOptions): + """ + Parse command line + """ + self.fVerbose = oOptions.fVerbose; + self.sSrcDir = config.g_ksFileAreaRootDir; + self.sDstDir = config.g_ksZipFileAreaRootDir; + #self.oTestSetLogic = TestSetLogic(TMDatabaseConnection(self.dprint if self.fVerbose else None)); + self.oTestSetLogic = TestSetLogic(TMDatabaseConnection(None)); + self.fDryRun = oOptions.fDryRun; + + def dprint(self, sText): + """ Verbose output. """ + if self.fVerbose: + print(sText); + return True; + + def warning(self, sText): + """Prints a warning.""" + print(sText); + return True; + + def _processTestSet(self, idTestSet, asFiles, sCurDir): + """ + Worker for processDir. + Same return codes as processDir. + """ + + sBaseFilename = os.path.join(sCurDir, 'TestSet-%d' % (idTestSet,)); + if sBaseFilename[0:2] == ('.' + os.path.sep): + sBaseFilename = sBaseFilename[2:]; + sSrcFileBase = os.path.join(self.sSrcDir, sBaseFilename + '-'); + + # + # Skip the file if the test set is still running. + # But delete them if the testset is not found. + # + oTestSet = self.oTestSetLogic.tryFetch(idTestSet); + if oTestSet is not None and sBaseFilename != oTestSet.sBaseFilename: + self.warning('TestSet %d: Deleting because sBaseFilename differs: "%s" (disk) vs "%s" (db)' \ + % (idTestSet, sBaseFilename, oTestSet.sBaseFilename,)); + oTestSet = None; + + if oTestSet is not None: + if oTestSet.enmStatus == TestSetData.ksTestStatus_Running: + self.dprint('Skipping test set #%d, still running' % (idTestSet,)); + return True; + + # + # If we have a zip file already, don't try recreate it as we might + # have had trouble removing the source files. + # + sDstDirPath = os.path.join(self.sDstDir, sCurDir); + sZipFileNm = os.path.join(sDstDirPath, 'TestSet-%d.zip' % (idTestSet,)); + if not os.path.exists(sZipFileNm): + # + # Create zip file with all testset files as members. + # + self.dprint('TestSet %d: Creating %s...' % (idTestSet, sZipFileNm,)); + if not self.fDryRun: + + if not os.path.exists(sDstDirPath): + os.makedirs(sDstDirPath, 0o755); + + utils.noxcptDeleteFile(sZipFileNm + '.tmp'); + with zipfile.ZipFile(sZipFileNm + '.tmp', 'w', zipfile.ZIP_DEFLATED, allowZip64 = True) as oZipFile: + for sFile in asFiles: + sSuff = os.path.splitext(sFile)[1]; + if sSuff in [ '.png', '.webm', '.gz', '.bz2', '.zip', '.mov', '.avi', '.mpg', '.gif', '.jpg' ]: + ## @todo Consider storing these files outside the zip if they are a little largish. + self.dprint('TestSet %d: Storing %s...' % (idTestSet, sFile)); + oZipFile.write(sSrcFileBase + sFile, sFile, zipfile.ZIP_STORED); + else: + self.dprint('TestSet %d: Deflating %s...' % (idTestSet, sFile)); + oZipFile.write(sSrcFileBase + sFile, sFile, zipfile.ZIP_DEFLATED); + + # + # .zip.tmp -> .zip. + # + utils.noxcptDeleteFile(sZipFileNm); + os.rename(sZipFileNm + '.tmp', sZipFileNm); + + #else: Dry run. + else: + self.dprint('TestSet %d: zip file exists already (%s)' % (idTestSet, sZipFileNm,)); + + # + # Delete the files. + # + fRc = True; + if self.fVerbose: + self.dprint('TestSet %d: deleting file: %s' % (idTestSet, asFiles)); + if not self.fDryRun: + for sFile in asFiles: + if utils.noxcptDeleteFile(sSrcFileBase + sFile) is False: + self.warning('TestSet %d: Failed to delete "%s" (%s)' % (idTestSet, sFile, sSrcFileBase + sFile,)); + fRc = False; + + return fRc; + + + def processDir(self, sCurDir): + """ + Process the given directory (relative to sSrcDir and sDstDir). + Returns success indicator. + """ + if self.fVerbose: + self.dprint('processDir: %s' % (sCurDir,)); + + # + # Sift thought the directory content, collecting subdirectories and + # sort relevant files by test set. + # Generally there will either be subdirs or there will be files. + # + asSubDirs = []; + dTestSets = {}; + sCurPath = os.path.abspath(os.path.join(self.sSrcDir, sCurDir)); + for sFile in os.listdir(sCurPath): + if os.path.isdir(os.path.join(sCurPath, sFile)): + if sFile not in [ '.', '..' ]: + asSubDirs.append(sFile); + elif sFile.startswith('TestSet-'): + # Parse the file name. ASSUMES 'TestSet-%d-filename' format. + iSlash1 = sFile.find('-'); + iSlash2 = sFile.find('-', iSlash1 + 1); + if iSlash2 <= iSlash1: + self.warning('Bad filename (1): "%s"' % (sFile,)); + continue; + + try: idTestSet = int(sFile[(iSlash1 + 1):iSlash2]); + except: + self.warning('Bad filename (2): "%s"' % (sFile,)); + if self.fVerbose: + self.dprint('\n'.join(utils.getXcptInfo(4))); + continue; + + if idTestSet <= 0: + self.warning('Bad filename (3): "%s"' % (sFile,)); + continue; + + if iSlash2 + 2 >= len(sFile): + self.warning('Bad filename (4): "%s"' % (sFile,)); + continue; + sName = sFile[(iSlash2 + 1):]; + + # Add it. + if idTestSet not in dTestSets: + dTestSets[idTestSet] = []; + asTestSet = dTestSets[idTestSet]; + asTestSet.append(sName); + + # + # Test sets. + # + fRc = True; + for idTestSet, oTestSet in dTestSets.items(): + try: + if self._processTestSet(idTestSet, oTestSet, sCurDir) is not True: + fRc = False; + except: + self.warning('TestSet %d: Exception in _processTestSet:\n%s' % (idTestSet, '\n'.join(utils.getXcptInfo()),)); + fRc = False; + + # + # Sub dirs. + # + for sSubDir in asSubDirs: + if self.processDir(os.path.join(sCurDir, sSubDir)) is not True: + fRc = False; + + # + # Try Remove the directory iff it's not '.' and it's been unmodified + # for the last 24h (race protection). + # + if sCurDir != '.': + try: + fpModTime = float(os.path.getmtime(sCurPath)); + if fpModTime + (24*3600) <= time.time(): + if utils.noxcptRmDir(sCurPath) is True: + self.dprint('Removed "%s".' % (sCurPath,)); + except: + pass; + + return fRc; + + @staticmethod + def main(): + """ C-style main(). """ + # + # Parse options. + # + oParser = OptionParser(); + oParser.add_option('-v', '--verbose', dest = 'fVerbose', action = 'store_true', default = False, + help = 'Verbose output.'); + oParser.add_option('-q', '--quiet', dest = 'fVerbose', action = 'store_false', default = False, + help = 'Quiet operation.'); + oParser.add_option('-d', '--dry-run', dest = 'fDryRun', action = 'store_true', default = False, + help = 'Dry run, do not make any changes.'); + (oOptions, asArgs) = oParser.parse_args() + if asArgs != []: + oParser.print_help(); + return 1; + + # + # Do the work. + # + oBatchJob = FileArchiverBatchJob(oOptions); + fRc = oBatchJob.processDir('.'); + return 0 if fRc is True else 1; + +if __name__ == '__main__': + sys.exit(FileArchiverBatchJob.main()); + diff --git a/src/VBox/ValidationKit/testmanager/batch/quota.py b/src/VBox/ValidationKit/testmanager/batch/quota.py new file mode 100755 index 00000000..7b509a8a --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/batch/quota.py @@ -0,0 +1,319 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: quota.py $ +# pylint: disable=line-too-long + +""" +A cronjob that applies quotas to large files in testsets. +""" + +from __future__ import print_function; + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154103 $" + +# Standard python imports +import sys +import os +from optparse import OptionParser; # pylint: disable=deprecated-module +import shutil +import tempfile; +import zipfile; + +# Add Test Manager's modules path +g_ksTestManagerDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(g_ksTestManagerDir) + +# Test Manager imports +from testmanager import config; +from testmanager.core.db import TMDatabaseConnection; +from testmanager.core.testset import TestSetLogic; + + +class ArchiveDelFilesBatchJob(object): # pylint: disable=too-few-public-methods + """ + Log+files comp + """ + + def __init__(self, oOptions): + """ + Parse command line + """ + self.fDryRun = oOptions.fDryRun; + self.fVerbose = oOptions.fVerbose; + self.sTempDir = tempfile.gettempdir(); + + self.dprint('Connecting to DB ...'); + self.oTestSetLogic = TestSetLogic(TMDatabaseConnection(self.dprint if self.fVerbose else None)); + + ## Fetches (and handles) all testsets up to this age (in hours). + self.uHoursAgeToHandle = 24; + ## Always remove files with these extensions. + self.asRemoveFileExt = [ 'webm' ]; + ## Always remove files which are bigger than this limit. + # Set to 0 to disable. + self.cbRemoveBiggerThan = 128 * 1024 * 1024; + + def dprint(self, sText): + """ Verbose output. """ + if self.fVerbose: + print(sText); + return True; + + def warning(self, sText): + """Prints a warning.""" + print(sText); + return True; + + def _replaceFile(self, sDstFile, sSrcFile, fDryRun = False, fForce = False): + """ + Replaces / moves a file safely by backing up the existing destination file (if any). + + Returns success indicator. + """ + + fRc = True; + + # Rename the destination file first (if any). + sDstFileTmp = None; + if os.path.exists(sDstFile): + sDstFileTmp = sDstFile + ".bak"; + if os.path.exists(sDstFileTmp): + if not fForce: + print('Replace file: Warning: Temporary destination file "%s" already exists, skipping' % (sDstFileTmp,)); + fRc = False; + else: + try: + os.remove(sDstFileTmp); + except Exception as e: + print('Replace file: Error deleting old temporary destination file "%s": %s' % (sDstFileTmp, e)); + fRc = False; + try: + if not fDryRun: + shutil.move(sDstFile, sDstFileTmp); + except Exception as e: + print('Replace file: Error moving old destination file "%s" to temporary file "%s": %s' \ + % (sDstFile, sDstFileTmp, e)); + fRc = False; + + if not fRc: + return False; + + try: + if not fDryRun: + shutil.move(sSrcFile, sDstFile); + except Exception as e: + print('Replace file: Error moving source file "%s" to destination "%s": %s' % (sSrcFile, sDstFile, e,)); + fRc = False; + + if sDstFileTmp: + if fRc: # Move succeeded, remove backup. + try: + if not fDryRun: + os.remove(sDstFileTmp); + except Exception as e: + print('Replace file: Error deleting temporary destination file "%s": %s' % (sDstFileTmp, e)); + fRc = False; + else: # Final move failed, roll back. + try: + if not fDryRun: + shutil.move(sDstFileTmp, sDstFile); + except Exception as e: + print('Replace file: Error restoring old destination file "%s": %s' % (sDstFile, e)); + fRc = False; + return fRc; + + def _processTestSetZip(self, idTestSet, sSrcZipFileAbs): + """ + Worker for processOneTestSet, which processes the testset's ZIP file. + + Returns success indicator. + """ + _ = idTestSet + + with tempfile.NamedTemporaryFile(dir=self.sTempDir, delete=False) as tmpfile: + sDstZipFileAbs = tmpfile.name; + + fRc = True; + + try: + oSrcZipFile = zipfile.ZipFile(sSrcZipFileAbs, 'r'); # pylint: disable=consider-using-with + self.dprint('Processing ZIP archive "%s" ...' % (sSrcZipFileAbs)); + try: + if not self.fDryRun: + oDstZipFile = zipfile.ZipFile(sDstZipFileAbs, 'w'); # pylint: disable=consider-using-with + self.dprint('Using temporary ZIP archive "%s"' % (sDstZipFileAbs)); + try: + # + # First pass: Gather information if we need to do some re-packing. + # + fDoRepack = False; + aoFilesToRepack = []; + for oCurFile in oSrcZipFile.infolist(): + self.dprint('Handling File "%s" ...' % (oCurFile.filename)) + sFileExt = os.path.splitext(oCurFile.filename)[1]; + + if sFileExt \ + and sFileExt[1:] in self.asRemoveFileExt: + self.dprint('\tMatches excluded extensions') + fDoRepack = True; + elif self.cbRemoveBiggerThan \ + and oCurFile.file_size > self.cbRemoveBiggerThan: + self.dprint('\tIs bigger than %d bytes (%d bytes)' % (self.cbRemoveBiggerThan, oCurFile.file_size)) + fDoRepack = True; + else: + aoFilesToRepack.append(oCurFile); + + if not fDoRepack: + oSrcZipFile.close(); + self.dprint('No re-packing necessary, skipping ZIP archive'); + return True; + + # + # Second pass: Re-pack all needed files into our temporary ZIP archive. + # + for oCurFile in aoFilesToRepack: + self.dprint('Re-packing file "%s"' % (oCurFile.filename,)) + if not self.fDryRun: + oBuf = oSrcZipFile.read(oCurFile); + oDstZipFile.writestr(oCurFile, oBuf); + + if not self.fDryRun: + oDstZipFile.close(); + + except Exception as oXcpt4: + print('Error handling file "%s" of archive "%s": %s' % (oCurFile.filename, sSrcZipFileAbs, oXcpt4,)); + return False; + + oSrcZipFile.close(); + + if fRc: + self.dprint('Moving file "%s" to "%s"' % (sDstZipFileAbs, sSrcZipFileAbs)); + fRc = self._replaceFile(sSrcZipFileAbs, sDstZipFileAbs, self.fDryRun); + + except Exception as oXcpt3: + print('Error creating temporary ZIP archive "%s": %s' % (sDstZipFileAbs, oXcpt3,)); + return False; + + except Exception as oXcpt1: + # Construct a meaningful error message. + if os.path.exists(sSrcZipFileAbs): + print('Error: Opening file "%s" failed: %s' % (sSrcZipFileAbs, oXcpt1)); + else: + print('Error: File "%s" not found.' % (sSrcZipFileAbs,)); + return False; + + return fRc; + + + def processOneTestSet(self, idTestSet, sBasename): + """ + Processes one single testset. + + Returns success indicator. + """ + + fRc = True; + self.dprint('Processing testset %d' % (idTestSet,)); + + # Construct absolute ZIP file path. + # ZIP is hardcoded in config, so do here. + sSrcZipFileAbs = os.path.join(config.g_ksZipFileAreaRootDir, sBasename + '.zip'); + + if self._processTestSetZip(idTestSet, sSrcZipFileAbs) is not True: + fRc = False; + + return fRc; + + def processTestSets(self): + """ + Processes all testsets according to the set configuration. + + Returns success indicator. + """ + + aoTestSets = self.oTestSetLogic.fetchByAge(cHoursBack = self.uHoursAgeToHandle); + cTestSets = len(aoTestSets); + print('Found %d entries in DB' % cTestSets); + if not cTestSets: + return True; # Nothing to do (yet). + + fRc = True; + for oTestSet in aoTestSets: + fRc = self.processOneTestSet(oTestSet.idTestSet, oTestSet.sBaseFilename) and fRc; + # Keep going. + + return fRc; + + @staticmethod + def main(): + """ C-style main(). """ + # + # Parse options. + # + + oParser = OptionParser(); + + # Generic options. + oParser.add_option('-v', '--verbose', dest = 'fVerbose', action = 'store_true', default = False, + help = 'Verbose output.'); + oParser.add_option('-q', '--quiet', dest = 'fVerbose', action = 'store_false', default = False, + help = 'Quiet operation.'); + oParser.add_option('-d', '--dry-run', dest = 'fDryRun', action = 'store_true', default = False, + help = 'Dry run, do not make any changes.'); + + (oOptions, asArgs) = oParser.parse_args(sys.argv[1:]); + if asArgs != []: + oParser.print_help(); + return 1; + + if oOptions.fDryRun: + print('***********************************'); + print('*** DRY RUN - NO FILES MODIFIED ***'); + print('***********************************'); + + # + # Do the work. + # + fRc = False; + + oBatchJob = ArchiveDelFilesBatchJob(oOptions); + fRc = oBatchJob.processTestSets(); + + if oOptions.fVerbose: + print('SUCCESS' if fRc else 'FAILURE'); + + return 0 if fRc is True else 1; + +if __name__ == '__main__': + sys.exit(ArchiveDelFilesBatchJob.main()); diff --git a/src/VBox/ValidationKit/testmanager/batch/regen_sched_queues.py b/src/VBox/ValidationKit/testmanager/batch/regen_sched_queues.py new file mode 100755 index 00000000..47d8f101 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/batch/regen_sched_queues.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: regen_sched_queues.py $ +# pylint: disable=line-too-long + +""" +Interface used by the admin to regenerate scheduling queues. +""" + +from __future__ import print_function; + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Standard python imports +import sys; +import os; +from optparse import OptionParser; # pylint: disable=deprecated-module + +# Add Test Manager's modules path +g_ksTestManagerDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksTestManagerDir); + +# Test Manager imports +from testmanager.core.db import TMDatabaseConnection; +from testmanager.core.schedulerbase import SchedulerBase; +from testmanager.core.schedgroup import SchedGroupLogic; + + + +class RegenSchedQueues(object): # pylint: disable=too-few-public-methods + """ + Regenerates all the scheduling queues. + """ + + def __init__(self): + """ + Parse command line. + """ + + oParser = OptionParser(); + oParser.add_option('-q', '--quiet', dest = 'fQuiet', action = 'store_true', default = False, + help = 'Quiet execution'); + oParser.add_option('-u', '--uid', dest = 'uid', action = 'store', type = 'int', default = 1, + help = 'User ID to accredit with this job'); + oParser.add_option('--profile', dest = 'fProfile', action = 'store_true', default = False, + help = 'User ID to accredit with this job'); + + (self.oConfig, _) = oParser.parse_args(); + + + def doIt(self): + """ + Does the job. + """ + oDb = TMDatabaseConnection(); + + aoGroups = SchedGroupLogic(oDb).getAll(); + iRc = 0; + for oGroup in aoGroups: + if not self.oConfig.fQuiet: + print('%s (ID %#d):' % (oGroup.sName, oGroup.idSchedGroup,)); + try: + (aoErrors, asMessages) = SchedulerBase.recreateQueue(oDb, self.oConfig.uid, oGroup.idSchedGroup, 2); + except Exception as oXcpt: + oDb.rollback(); + print(' !!Hit exception processing "%s": %s' % (oGroup.sName, oXcpt,)); + else: + if not aoErrors: + if not self.oConfig.fQuiet: + print(' Successfully regenerated.'); + else: + iRc = 1; + print(' %d errors:' % (len(aoErrors,))); + for oError in aoErrors: + if oError[1] is None: + print(' !!%s' % (oError[0],)); + else: + print(' !!%s (%s)' % (oError[0], oError[1])); + if asMessages and not self.oConfig.fQuiet: + print(' %d messages:' % (len(asMessages),)); + for sMsg in asMessages: + print(' ##%s' % (sMsg,)); + return iRc; + + @staticmethod + def main(): + """ Main function. """ + oMain = RegenSchedQueues(); + if oMain.oConfig.fProfile is not True: + iRc = oMain.doIt(); + else: + import cProfile; + oProfiler = cProfile.Profile(); + iRc = oProfiler.runcall(oMain.doIt); + oProfiler.print_stats(sort = 'time'); + oProfiler = None; + return iRc; + +if __name__ == '__main__': + sys.exit(RegenSchedQueues().main()); + diff --git a/src/VBox/ValidationKit/testmanager/batch/vcs_import.py b/src/VBox/ValidationKit/testmanager/batch/vcs_import.py new file mode 100755 index 00000000..4a0ba5d4 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/batch/vcs_import.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: vcs_import.py $ + +""" +Cron job for importing revision history for a repository. +""" + +from __future__ import print_function; + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Standard python imports +import sys; +import os; +from optparse import OptionParser; # pylint: disable=deprecated-module +import xml.etree.ElementTree as ET; + +# Add Test Manager's modules path +g_ksTestManagerDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksTestManagerDir); + +# Test Manager imports +from testmanager.config import g_kdBugTrackers; +from testmanager.core.db import TMDatabaseConnection; +from testmanager.core.vcsrevisions import VcsRevisionData, VcsRevisionLogic; +from testmanager.core.vcsbugreference import VcsBugReferenceData, VcsBugReferenceLogic; +from common import utils; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + + +class VcsImport(object): # pylint: disable=too-few-public-methods + """ + Imports revision history from a VSC into the Test Manager database. + """ + + class BugTracker(object): + def __init__(self, sDbName, sTag): + self.sDbName = sDbName; + self.sTag = sTag; + + + def __init__(self): + """ + Parse command line. + """ + + oParser = OptionParser() + oParser.add_option('-b', '--only-bug-refs', dest = 'fBugRefsOnly', action = 'store_true', + help = 'Only do bug references, not revisions.'); + oParser.add_option('-e', '--extra-option', dest = 'asExtraOptions', metavar = 'vcsoption', action = 'append', + help = 'Adds a extra option to the command retrieving the log.'); + oParser.add_option('-f', '--full', dest = 'fFull', action = 'store_true', + help = 'Full revision history import.'); + oParser.add_option('-q', '--quiet', dest = 'fQuiet', action = 'store_true', + help = 'Quiet execution'); + oParser.add_option('-R', '--repository', dest = 'sRepository', metavar = '<repository>', + help = 'Version control repository name.'); + oParser.add_option('-s', '--start-revision', dest = 'iStartRevision', metavar = 'start-revision', + type = "int", default = 0, + help = 'The revision to start at when doing a full import.'); + oParser.add_option('-t', '--type', dest = 'sType', metavar = '<type>', + help = 'The VCS type (default: svn)', choices = [ 'svn', ], default = 'svn'); + oParser.add_option('-u', '--url', dest = 'sUrl', metavar = '<url>', + help = 'The VCS URL'); + + (self.oConfig, _) = oParser.parse_args(); + + # Check command line + asMissing = []; + if self.oConfig.sUrl is None: asMissing.append('--url'); + if self.oConfig.sRepository is None: asMissing.append('--repository'); + if asMissing: + sys.stderr.write('syntax error: Missing: %s\n' % (asMissing,)); + sys.exit(1); + + assert self.oConfig.sType == 'svn'; + + def main(self): + """ + Main function. + """ + oDb = TMDatabaseConnection(); + oLogic = VcsRevisionLogic(oDb); + oBugLogic = VcsBugReferenceLogic(oDb); + + # Where to start. + iStartRev = 0; + if not self.oConfig.fFull: + if not self.oConfig.fBugRefsOnly: + iStartRev = oLogic.getLastRevision(self.oConfig.sRepository); + else: + iStartRev = oBugLogic.getLastRevision(self.oConfig.sRepository); + if iStartRev == 0: + iStartRev = self.oConfig.iStartRevision; + + # Construct a command line. + os.environ['LC_ALL'] = 'en_US.utf-8'; + asArgs = [ + 'svn', + 'log', + '--xml', + '--revision', str(iStartRev) + ':HEAD', + ]; + if self.oConfig.asExtraOptions is not None: + asArgs.extend(self.oConfig.asExtraOptions); + asArgs.append(self.oConfig.sUrl); + if not self.oConfig.fQuiet: + print('Executing: %s' % (asArgs,)); + sLogXml = utils.processOutputChecked(asArgs); + + # Parse the XML and add the entries to the database. + oParser = ET.XMLParser(target = ET.TreeBuilder(), encoding = 'utf-8'); + oParser.feed(sLogXml.encode('utf-8')); # Does its own decoding; processOutputChecked always gives us decoded utf-8 now. + oRoot = oParser.close(); + + for oLogEntry in oRoot.findall('logentry'): + iRevision = int(oLogEntry.get('revision')); + sAuthor = oLogEntry.findtext('author', 'unspecified').strip(); # cvs2svn entries doesn't have an author. + sDate = oLogEntry.findtext('date').strip(); + sRawMsg = oLogEntry.findtext('msg', '').strip(); + sMessage = sRawMsg; + if sMessage == '': + sMessage = ' '; + elif len(sMessage) > VcsRevisionData.kcchMax_sMessage: + sMessage = sMessage[:VcsRevisionData.kcchMax_sMessage - 4] + ' ...'; + if not self.oConfig.fQuiet: + utils.printOut(u'sDate=%s iRev=%u sAuthor=%s sMsg[%s]=%s' + % (sDate, iRevision, sAuthor, type(sMessage).__name__, sMessage)); + + if not self.oConfig.fBugRefsOnly: + oData = VcsRevisionData().initFromValues(self.oConfig.sRepository, iRevision, sDate, sAuthor, sMessage); + oLogic.addVcsRevision(oData); + + # Analyze the raw message looking for bug tracker references. + for oBugTracker in g_kdBugTrackers.values(): + for sTag in oBugTracker.asCommitTags: + off = sRawMsg.find(sTag); + while off >= 0: + off += len(sTag); + while off < len(sRawMsg) and sRawMsg[off].isspace(): + off += 1; + + if off < len(sRawMsg) and sRawMsg[off].isdigit(): + offNum = off; + while off < len(sRawMsg) and sRawMsg[off].isdigit(): + off += 1; + try: + iBugNo = long(sRawMsg[offNum:off]); + except Exception as oXcpt: + utils.printErr(u'error! exception(r%s,"%s"): -> %s' % (iRevision, sRawMsg[offNum:off], oXcpt,)); + else: + if not self.oConfig.fQuiet: + utils.printOut(u' r%u -> sBugTracker=%s iBugNo=%s' + % (iRevision, oBugTracker.sDbId, iBugNo,)); + + oBugData = VcsBugReferenceData().initFromValues(self.oConfig.sRepository, iRevision, + oBugTracker.sDbId, iBugNo); + oBugLogic.addVcsBugReference(oBugData); + + # next + off = sRawMsg.find(sTag, off); + + oDb.commit(); + + oDb.close(); + return 0; + +if __name__ == '__main__': + sys.exit(VcsImport().main()); + diff --git a/src/VBox/ValidationKit/testmanager/batch/virtual_test_sheriff.py b/src/VBox/ValidationKit/testmanager/batch/virtual_test_sheriff.py new file mode 100755 index 00000000..19f26c39 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/batch/virtual_test_sheriff.py @@ -0,0 +1,1832 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: virtual_test_sheriff.py $ +# pylint: disable=line-too-long + +""" +Virtual Test Sheriff. + +Duties: + - Try to a assign failure reasons to recently failed tests. + - Reboot or disable bad test boxes. + +""" + +from __future__ import print_function; + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard python imports +import hashlib; +import os; +import re; +import smtplib; +#import subprocess; +import sys; +from email.mime.multipart import MIMEMultipart; +from email.mime.text import MIMEText; +from email.utils import COMMASPACE; + +if sys.version_info[0] >= 3: + from io import BytesIO as BytesIO; # pylint: disable=import-error,no-name-in-module,useless-import-alias +else: + from StringIO import StringIO as BytesIO; # pylint: disable=import-error,no-name-in-module,useless-import-alias +from optparse import OptionParser; # pylint: disable=deprecated-module +from PIL import Image; # pylint: disable=import-error + +# Add Test Manager's modules path +g_ksTestManagerDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksTestManagerDir); + +# Test Manager imports +from common import utils; +from testmanager.core.db import TMDatabaseConnection; +from testmanager.core.build import BuildDataEx; +from testmanager.core.failurereason import FailureReasonLogic; +from testmanager.core.testbox import TestBoxLogic, TestBoxData; +from testmanager.core.testcase import TestCaseDataEx; +from testmanager.core.testgroup import TestGroupData; +from testmanager.core.testset import TestSetLogic, TestSetData; +from testmanager.core.testresults import TestResultLogic, TestResultFileData; +from testmanager.core.testresultfailures import TestResultFailureLogic, TestResultFailureData; +from testmanager.core.useraccount import UserAccountLogic; +from testmanager.config import g_ksSmtpHost, g_kcSmtpPort, g_ksAlertFrom, \ + g_ksAlertSubject, g_asAlertList #, g_ksLomPassword; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + xrange = range; # pylint: disable=redefined-builtin,invalid-name + + +class VirtualTestSheriffCaseFile(object): + """ + A failure investigation case file. + + """ + + + ## Max log file we'll read into memory. (256 MB) + kcbMaxLogRead = 0x10000000; + + def __init__(self, oSheriff, oTestSet, oTree, oBuild, oTestBox, oTestGroup, oTestCase): + self.oSheriff = oSheriff; + self.oTestSet = oTestSet; # TestSetData + self.oTree = oTree; # TestResultDataEx + self.oBuild = oBuild; # BuildDataEx + self.oTestBox = oTestBox; # TestBoxData + self.oTestGroup = oTestGroup; # TestGroupData + self.oTestCase = oTestCase; # TestCaseDataEx + self.sMainLog = ''; # The main log file. Empty string if not accessible. + self.sSvcLog = ''; # The VBoxSVC log file. Empty string if not accessible. + + # Generate a case file name. + self.sName = '#%u: %s' % (self.oTestSet.idTestSet, self.oTestCase.sName,) + self.sLongName = '#%u: "%s" on "%s" running %s %s (%s), "%s" by %s, using %s %s %s r%u' \ + % ( self.oTestSet.idTestSet, + self.oTestCase.sName, + self.oTestBox.sName, + self.oTestBox.sOs, + self.oTestBox.sOsVersion, + self.oTestBox.sCpuArch, + self.oTestBox.sCpuName, + self.oTestBox.sCpuVendor, + self.oBuild.oCat.sProduct, + self.oBuild.oCat.sBranch, + self.oBuild.oCat.sType, + self.oBuild.iRevision, ); + + # Investigation notes. + self.tReason = None; # None or one of the ktReason_XXX constants. + self.dReasonForResultId = {}; # Reason assignments indexed by idTestResult. + self.dCommentForResultId = {}; # Comment assignments indexed by idTestResult. + + # + # Reason. + # + + def noteReason(self, tReason): + """ Notes down a possible reason. """ + self.oSheriff.dprint(u'noteReason: %s -> %s' % (self.tReason, tReason,)); + self.tReason = tReason; + return True; + + def noteReasonForId(self, tReason, idTestResult, sComment = None): + """ Notes down a possible reason for a specific test result. """ + self.oSheriff.dprint(u'noteReasonForId: %u: %s -> %s%s' + % (idTestResult, self.dReasonForResultId.get(idTestResult, None), tReason, + (u' (%s)' % (sComment,)) if sComment is not None else '')); + self.dReasonForResultId[idTestResult] = tReason; + if sComment is not None: + self.dCommentForResultId[idTestResult] = sComment; + return True; + + + # + # Test classification. + # + + def isVBoxTest(self): + """ Test classification: VirtualBox (using the build) """ + return self.oBuild.oCat.sProduct.lower() in [ 'virtualbox', 'vbox' ]; + + def isVBoxUnitTest(self): + """ Test case classification: The unit test doing all our testcase/*.cpp stuff. """ + return self.isVBoxTest() \ + and (self.oTestCase.sName.lower() == 'unit tests' or self.oTestCase.sName.lower().startswith('misc: unit tests')); + + def isVBoxInstallTest(self): + """ Test case classification: VirtualBox Guest installation test. """ + return self.isVBoxTest() \ + and self.oTestCase.sName.lower().startswith('install:'); + + def isVBoxUnattendedInstallTest(self): + """ Test case classification: VirtualBox Guest installation test. """ + return self.isVBoxTest() \ + and self.oTestCase.sName.lower().startswith('uinstall:'); + + def isVBoxUSBTest(self): + """ Test case classification: VirtualBox USB test. """ + return self.isVBoxTest() \ + and self.oTestCase.sName.lower().startswith('usb:'); + + def isVBoxStorageTest(self): + """ Test case classification: VirtualBox Storage test. """ + return self.isVBoxTest() \ + and self.oTestCase.sName.lower().startswith('storage:'); + + def isVBoxGAsTest(self): + """ Test case classification: VirtualBox Guest Additions test. """ + return self.isVBoxTest() \ + and ( self.oTestCase.sName.lower().startswith('guest additions') + or self.oTestCase.sName.lower().startswith('ga\'s tests')); + + def isVBoxAPITest(self): + """ Test case classification: VirtualBox API test. """ + return self.isVBoxTest() \ + and self.oTestCase.sName.lower().startswith('api:'); + + def isVBoxBenchmarkTest(self): + """ Test case classification: VirtualBox Benchmark test. """ + return self.isVBoxTest() \ + and self.oTestCase.sName.lower().startswith('benchmark:'); + + def isVBoxSmokeTest(self): + """ Test case classification: Smoke test. """ + return self.isVBoxTest() \ + and self.oTestCase.sName.lower().startswith('smoketest'); + + def isVBoxSerialTest(self): + """ Test case classification: Smoke test. """ + return self.isVBoxTest() \ + and self.oTestCase.sName.lower().startswith('serial:'); + + + # + # Utility methods. + # + + def getMainLog(self): + """ + Tries to read the main log file since this will be the first source of information. + """ + if self.sMainLog: + return self.sMainLog; + (oFile, oSizeOrError, _) = self.oTestSet.openFile('main.log', 'rb'); + if oFile is not None: + try: + self.sMainLog = oFile.read(min(self.kcbMaxLogRead, oSizeOrError)).decode('utf-8', 'replace'); + except Exception as oXcpt: + self.oSheriff.vprint(u'Error reading main log file: %s' % (oXcpt,)) + self.sMainLog = ''; + else: + self.oSheriff.vprint(u'Error opening main log file: %s' % (oSizeOrError,)); + return self.sMainLog; + + def getLogFile(self, oFile): + """ + Tries to read the given file as a utf-8 log file. + oFile is a TestFileDataEx instance. + Returns empty string if problems opening or reading the file. + """ + sContent = ''; + (oFile, oSizeOrError, _) = self.oTestSet.openFile(oFile.sFile, 'rb'); + if oFile is not None: + try: + sContent = oFile.read(min(self.kcbMaxLogRead, oSizeOrError)).decode('utf-8', 'replace'); + except Exception as oXcpt: + self.oSheriff.vprint(u'Error reading the "%s" log file: %s' % (oFile.sFile, oXcpt,)) + else: + self.oSheriff.vprint(u'Error opening the "%s" log file: %s' % (oFile.sFile, oSizeOrError,)); + return sContent; + + def getSvcLog(self): + """ + Tries to read the VBoxSVC log file as it typically not associated with a failing test result. + Note! Returns the first VBoxSVC log file we find. + """ + if not self.sSvcLog: + aoSvcLogFiles = self.oTree.getListOfLogFilesByKind(TestResultFileData.ksKind_LogReleaseSvc); + if aoSvcLogFiles: + self.sSvcLog = self.getLogFile(aoSvcLogFiles[0]); + return self.sSvcLog; + + def getScreenshotSha256(self, oFile): + """ + Tries to read the given screenshot file, uncompress it, and do SHA-2 + on the raw pixels. + Returns SHA-2 digest string on success, None on failure. + """ + (oImgFile, _, _) = self.oTestSet.openFile(oFile.sFile, 'rb'); + try: + abImageFile = oImgFile.read(); + except Exception as oXcpt: + self.oSheriff.vprint(u'Error reading the "%s" image file: %s' % (oFile.sFile, oXcpt,)) + else: + try: + oImage = Image.open(BytesIO(abImageFile)); + except Exception as oXcpt: + self.oSheriff.vprint(u'Error opening the "%s" image bytes using PIL.Image.open: %s' % (oFile.sFile, oXcpt,)) + else: + try: + oHash = hashlib.sha256(); + if hasattr(oImage, 'tobytes'): + oHash.update(oImage.tobytes()); + else: + oHash.update(oImage.tostring()); # pylint: disable=no-member + except Exception as oXcpt: + self.oSheriff.vprint(u'Error hashing the uncompressed image bytes for "%s": %s' % (oFile.sFile, oXcpt,)) + else: + return oHash.hexdigest(); + return None; + + + + def isSingleTestFailure(self): + """ + Figure out if this is a single test failing or if it's one of the + more complicated ones. + """ + if self.oTree.cErrors == 1: + return True; + if self.oTree.deepCountErrorContributers() <= 1: + return True; + return False; + + + +class VirtualTestSheriff(object): # pylint: disable=too-few-public-methods + """ + Add build info into Test Manager database. + """ + + ## The user account for the virtual sheriff. + ksLoginName = 'vsheriff'; + + def __init__(self): + """ + Parse command line. + """ + self.oDb = None; + self.tsNow = None; + self.oTestResultLogic = None; + self.oTestSetLogic = None; + self.oFailureReasonLogic = None; # FailureReasonLogic; + self.oTestResultFailureLogic = None; # TestResultFailureLogic + self.oLogin = None; + self.uidSelf = -1; + self.oLogFile = None; + self.asBsodReasons = []; + self.asUnitTestReasons = []; + + oParser = OptionParser(); + oParser.add_option('--start-hours-ago', dest = 'cStartHoursAgo', metavar = '<hours>', default = 0, type = 'int', + help = 'When to start specified as hours relative to current time. Defauls is right now.', ); + oParser.add_option('--hours-period', dest = 'cHoursBack', metavar = '<period-in-hours>', default = 2, type = 'int', + help = 'Work period specified in hours. Defauls is 2 hours.'); + oParser.add_option('--real-run-back', dest = 'fRealRun', action = 'store_true', default = False, + help = 'Whether to commit the findings to the database. Default is a dry run.'); + oParser.add_option('--testset', dest = 'aidTestSets', metavar = '<id>', default = [], type = 'int', action = 'append', + help = 'Only investigate this one. Accumulates IDs when repeated.'); + oParser.add_option('-q', '--quiet', dest = 'fQuiet', action = 'store_true', default = False, + help = 'Quiet execution'); + oParser.add_option('-l', '--log', dest = 'sLogFile', metavar = '<logfile>', default = None, + help = 'Where to log messages.'); + oParser.add_option('--debug', dest = 'fDebug', action = 'store_true', default = False, + help = 'Enables debug mode.'); + + (self.oConfig, _) = oParser.parse_args(); + + if self.oConfig.sLogFile: + self.oLogFile = open(self.oConfig.sLogFile, "a"); # pylint: disable=consider-using-with,unspecified-encoding + self.oLogFile.write('VirtualTestSheriff: $Revision: 154728 $ \n'); + + + def eprint(self, sText): + """ + Prints error messages. + Returns 1 (for exit code usage.) + """ + print('error: %s' % (sText,)); + if self.oLogFile is not None: + if sys.version_info[0] >= 3: + self.oLogFile.write(u'error: %s\n' % (sText,)); + else: + self.oLogFile.write((u'error: %s\n' % (sText,)).encode('utf-8')); + return 1; + + def dprint(self, sText): + """ + Prints debug info. + """ + if self.oConfig.fDebug: + if not self.oConfig.fQuiet: + print('debug: %s' % (sText, )); + if self.oLogFile is not None: + if sys.version_info[0] >= 3: + self.oLogFile.write(u'debug: %s\n' % (sText,)); + else: + self.oLogFile.write((u'debug: %s\n' % (sText,)).encode('utf-8')); + return 0; + + def vprint(self, sText): + """ + Prints verbose info. + """ + if not self.oConfig.fQuiet: + print('info: %s' % (sText,)); + if self.oLogFile is not None: + if sys.version_info[0] >= 3: + self.oLogFile.write(u'info: %s\n' % (sText,)); + else: + self.oLogFile.write((u'info: %s\n' % (sText,)).encode('utf-8')); + return 0; + + def getFailureReason(self, tReason): + """ Gets the failure reason object for tReason. """ + return self.oFailureReasonLogic.cachedLookupByNameAndCategory(tReason[1], tReason[0]); + + def selfCheck(self): + """ Does some self checks, looking up things we expect to be in the database and such. """ + rcExit = 0; + for sAttr in dir(self.__class__): + if sAttr.startswith('ktReason_'): + tReason = getattr(self.__class__, sAttr); + oFailureReason = self.getFailureReason(tReason); + if oFailureReason is None: + rcExit = self.eprint(u'Failed to find failure reason "%s" in category "%s" in the database!' + % (tReason[1], tReason[0],)); + + # Check the user account as well. + if self.oLogin is None: + oLogin = UserAccountLogic(self.oDb).tryFetchAccountByLoginName(VirtualTestSheriff.ksLoginName); + if oLogin is None: + rcExit = self.eprint(u'Cannot find my user account "%s"!' % (VirtualTestSheriff.ksLoginName,)); + return rcExit; + + def sendEmailAlert(self, uidAuthor, sBodyText): + """ + Sends email alert. + """ + + # Get author email + self.oDb.execute('SELECT sEmail FROM Users WHERE uid=%s', (uidAuthor,)); + sFrom = self.oDb.fetchOne(); + if sFrom is not None: + sFrom = sFrom[0]; + else: + sFrom = g_ksAlertFrom; + + # Gather recipient list. + asEmailList = []; + for sUser in g_asAlertList: + self.oDb.execute('SELECT sEmail FROM Users WHERE sUsername=%s', (sUser,)); + sEmail = self.oDb.fetchOne(); + if sEmail: + asEmailList.append(sEmail[0]); + if not asEmailList: + return self.eprint('No email addresses to send alter to!'); + + # Compose the message. + oMsg = MIMEMultipart(); + oMsg['From'] = sFrom; + oMsg['To'] = COMMASPACE.join(asEmailList); + oMsg['Subject'] = g_ksAlertSubject; + oMsg.attach(MIMEText(sBodyText, 'plain')) + + # Try send it. + try: + oSMTP = smtplib.SMTP(g_ksSmtpHost, g_kcSmtpPort); + oSMTP.sendmail(sFrom, asEmailList, oMsg.as_string()) + oSMTP.quit() + except smtplib.SMTPException as oXcpt: + return self.eprint('Failed to send mail: %s' % (oXcpt,)); + + return 0; + + def badTestBoxManagement(self): + """ + Looks for bad test boxes and first tries once to reboot them then disables them. + """ + rcExit = 0; + + # + # We skip this entirely if we're running in the past and not in harmless debug mode. + # + if self.oConfig.cStartHoursAgo != 0 \ + and (not self.oConfig.fDebug or self.oConfig.fRealRun): + return rcExit; + tsNow = self.tsNow if self.oConfig.fDebug else None; + cHoursBack = self.oConfig.cHoursBack if self.oConfig.fDebug else 2; + oTestBoxLogic = TestBoxLogic(self.oDb); + + # + # Generate a list of failures reasons we consider bad-testbox behavior. + # + aidFailureReasons = [ + self.getFailureReason(self.ktReason_Host_DriverNotLoaded).idFailureReason, + self.getFailureReason(self.ktReason_Host_DriverNotUnloading).idFailureReason, + self.getFailureReason(self.ktReason_Host_DriverNotCompilable).idFailureReason, + self.getFailureReason(self.ktReason_Host_InstallationFailed).idFailureReason, + ]; + + # + # Get list of bad test boxes for given period and check them out individually. + # + aidBadTestBoxes = self.oTestSetLogic.fetchBadTestBoxIds(cHoursBack = cHoursBack, tsNow = tsNow, + aidFailureReasons = aidFailureReasons); + for idTestBox in aidBadTestBoxes: + # Skip if the testbox is already disabled or has a pending reboot command. + try: + oTestBox = TestBoxData().initFromDbWithId(self.oDb, idTestBox); + except Exception as oXcpt: + rcExit = self.eprint('Failed to get data for test box #%u in badTestBoxManagement: %s' % (idTestBox, oXcpt,)); + continue; + if not oTestBox.fEnabled: + self.dprint(u'badTestBoxManagement: Skipping test box #%u (%s) as it has been disabled already.' + % ( idTestBox, oTestBox.sName, )); + continue; + if oTestBox.enmPendingCmd != TestBoxData.ksTestBoxCmd_None: + self.dprint(u'badTestBoxManagement: Skipping test box #%u (%s) as it has a command pending: %s' + % ( idTestBox, oTestBox.sName, oTestBox.enmPendingCmd)); + continue; + + # Get the most recent testsets for this box (descending on tsDone) and see how bad it is. + aoSets = self.oTestSetLogic.fetchSetsForTestBox(idTestBox, cHoursBack = cHoursBack, tsNow = tsNow); + cOkay = 0; + cBad = 0; + iFirstOkay = len(aoSets); + for iSet, oSet in enumerate(aoSets): + if oSet.enmStatus == TestSetData.ksTestStatus_BadTestBox: + cBad += 1; + else: + # Check for bad failure reasons. + oFailure = None; + if oSet.enmStatus in TestSetData.kasBadTestStatuses: + (oTree, _ ) = self.oTestResultLogic.fetchResultTree(oSet.idTestSet) + aoFailedResults = oTree.getListOfFailures(); + for oFailedResult in aoFailedResults: + oFailure = self.oTestResultFailureLogic.getById(oFailedResult.idTestResult); + if oFailure is not None and oFailure.idFailureReason in aidFailureReasons: + break; + oFailure = None; + if oFailure is not None: + cBad += 1; + else: + # This is an okay test result then. + ## @todo maybe check the elapsed time here, it could still be a bad run? + cOkay += 1; + iFirstOkay = min(iFirstOkay, iSet); + if iSet > 10: + break; + + # We react if there are two or more bad-testbox statuses at the head of the + # history and at least three in the last 10 results. + if iFirstOkay >= 2 and cBad > 2: + if oTestBoxLogic.hasTestBoxRecentlyBeenRebooted(idTestBox, cHoursBack = cHoursBack, tsNow = tsNow): + sComment = u'Disabling testbox #%u (%s) - iFirstOkay=%u cBad=%u cOkay=%u' \ + % (idTestBox, oTestBox.sName, iFirstOkay, cBad, cOkay); + self.vprint(sComment); + self.sendEmailAlert(self.uidSelf, sComment); + if self.oConfig.fRealRun is True: + try: + oTestBoxLogic.disableTestBox(idTestBox, self.uidSelf, fCommit = True, + sComment = 'Automatically disabled (iFirstOkay=%u cBad=%u cOkay=%u)' + % (iFirstOkay, cBad, cOkay),); + except Exception as oXcpt: + rcExit = self.eprint(u'Error disabling testbox #%u (%u): %s\n' % (idTestBox, oTestBox.sName, oXcpt,)); + else: + sComment = u'Rebooting testbox #%u (%s) - iFirstOkay=%u cBad=%u cOkay=%u' \ + % (idTestBox, oTestBox.sName, iFirstOkay, cBad, cOkay); + self.vprint(sComment); + self.sendEmailAlert(self.uidSelf, sComment); + if self.oConfig.fRealRun is True: + try: + oTestBoxLogic.rebootTestBox(idTestBox, self.uidSelf, fCommit = True, + sComment = 'Automatically rebooted (iFirstOkay=%u cBad=%u cOkay=%u)' + % (iFirstOkay, cBad, cOkay),); + except Exception as oXcpt: + rcExit = self.eprint(u'Error rebooting testbox #%u (%s): %s\n' % (idTestBox, oTestBox.sName, oXcpt,)); + else: + self.dprint(u'badTestBoxManagement: #%u (%s) looks ok: iFirstOkay=%u cBad=%u cOkay=%u' + % ( idTestBox, oTestBox.sName, iFirstOkay, cBad, cOkay)); + + ## @todo r=bird: review + rewrite; + ## - no selecting here, that belongs in the core/*.py files. + ## - preserve existing comments. + ## - doing way too much in the try/except block. + ## - No password quoting in the sshpass command that always fails (127). + ## - Timeout is way to low. testboxmem1 need more than 10 min to take a dump, ages to + ## get thru POST and another 5 just to time out in grub. Should be an hour or so. + ## Besides, it need to be constant elsewhere in the file, not a variable here. + ## + ## + ## Reset hanged testboxes + ## + #cStatusTimeoutMins = 10; + # + #self.oDb.execute('SELECT TestBoxStatuses.idTestBox\n' + # ' FROM TestBoxStatuses, TestBoxes\n' + # ' WHERE TestBoxStatuses.tsUpdated >= (CURRENT_TIMESTAMP - interval \'%s hours\')\n' + # ' AND TestBoxStatuses.tsUpdated < (CURRENT_TIMESTAMP - interval \'%s minutes\')\n' + # ' AND TestBoxStatuses.idTestBox = TestBoxes.idTestBox\n' + # ' AND Testboxes.tsExpire = \'infinity\'::timestamp', (cHoursBack,cStatusTimeoutMins)); + #for idTestBox in self.oDb.fetchAll(): + # idTestBox = idTestBox[0]; + # try: + # oTestBox = TestBoxData().initFromDbWithId(self.oDb, idTestBox); + # except Exception as oXcpt: + # rcExit = self.eprint('Failed to get data for test box #%u in badTestBoxManagement: %s' % (idTestBox, oXcpt,)); + # continue; + # # Skip if the testbox is already disabled, already reset or there's no iLOM + # if not oTestBox.fEnabled or oTestBox.ipLom is None or oTestBox.sComment is not None and oTestBox.sComment.find('Automatically reset') >= 0: + # self.dprint(u'badTestBoxManagement: Skipping test box #%u (%s) as it has been disabled already.' + # % ( idTestBox, oTestBox.sName, )); + # continue; + # ## @todo get iLOM credentials from a table? + # sCmd = 'sshpass -p%s ssh -oStrictHostKeyChecking=no root@%s show /SP && reset /SYS' % (g_ksLomPassword, oTestBox.ipLom,); + # try: + # oPs = subprocess.Popen(sCmd, stdout=subprocess.PIPE, shell=True); + # sStdout = oPs.communicate()[0]; + # iRC = oPs.wait(); + # + # oTestBox.sComment = 'Automatically reset (iRC=%u sStdout=%s)' % (iRC, sStdout,); + # oTestBoxLogic.editEntry(oTestBox, self.uidSelf, fCommit = True); + # + # sComment = u'Reset testbox #%u (%s) - iRC=%u sStduot=%s' % ( idTestBox, oTestBox.sName, iRC, sStdout); + # self.vprint(sComment); + # self.sendEmailAlert(self.uidSelf, sComment); + # + # except Exception as oXcpt: + # rcExit = self.eprint(u'Error resetting testbox #%u (%s): %s\n' % (idTestBox, oTestBox.sName, oXcpt,)); + # + return rcExit; + + + ## @name Failure reasons we know. + ## @{ + + ktReason_Add_Installer_Win_Failed = ( 'Additions', 'Win GA install' ); + ktReason_Add_ShFl_Automount = ( 'Additions', 'Automounting' ); + ktReason_Add_ShFl_FsPerf = ( 'Additions', 'FsPerf' ); + ktReason_Add_ShFl_FsPerf_Abend = ( 'Additions', 'FsPerf abend' ); + ktReason_Add_GstCtl_Preparations = ( 'Additions', 'GstCtl preparations' ); + ktReason_Add_GstCtl_SessionBasics = ( 'Additions', 'Session basics' ); + ktReason_Add_GstCtl_SessionProcRefs = ( 'Additions', 'Session process' ); + ktReason_Add_GstCtl_Session_Reboot = ( 'Additions', 'Session reboot' ); + ktReason_Add_GstCtl_CopyFromGuest_Timeout = ( 'Additions', 'CopyFromGuest timeout' ); + ktReason_Add_GstCtl_CopyToGuest_Timeout = ( 'Additions', 'CopyToGuest timeout' ); + ktReason_Add_GstCtl_CopyToGuest_DstEmpty = ( 'Additions', 'CopyToGuest dst empty' ); + ktReason_Add_GstCtl_CopyToGuest_DstExists = ( 'Additions', 'CopyToGuest dst exists' ); + ktReason_Add_FlushViewOfFile = ( 'Additions', 'FlushViewOfFile' ); + ktReason_Add_Mmap_Coherency = ( 'Additions', 'mmap coherency' ); + ktReason_BSOD_Recovery = ( 'BSOD', 'Recovery' ); + ktReason_BSOD_Automatic_Repair = ( 'BSOD', 'Automatic Repair' ); + ktReason_BSOD_0000007F = ( 'BSOD', '0x0000007F' ); + ktReason_BSOD_000000D1 = ( 'BSOD', '0x000000D1' ); + ktReason_BSOD_C0000225 = ( 'BSOD', '0xC0000225 (boot)' ); + ktReason_Guru_Generic = ( 'Guru Meditations', 'Generic Guru Meditation' ); + ktReason_Guru_VERR_IEM_INSTR_NOT_IMPLEMENTED = ( 'Guru Meditations', 'VERR_IEM_INSTR_NOT_IMPLEMENTED' ); + ktReason_Guru_VERR_IEM_ASPECT_NOT_IMPLEMENTED = ( 'Guru Meditations', 'VERR_IEM_ASPECT_NOT_IMPLEMENTED' ); + ktReason_Guru_VERR_TRPM_DONT_PANIC = ( 'Guru Meditations', 'VERR_TRPM_DONT_PANIC' ); + ktReason_Guru_VERR_PGM_PHYS_PAGE_RESERVED = ( 'Guru Meditations', 'VERR_PGM_PHYS_PAGE_RESERVED' ); + ktReason_Guru_VERR_VMX_INVALID_GUEST_STATE = ( 'Guru Meditations', 'VERR_VMX_INVALID_GUEST_STATE' ); + ktReason_Guru_VINF_EM_TRIPLE_FAULT = ( 'Guru Meditations', 'VINF_EM_TRIPLE_FAULT' ); + ktReason_Host_HostMemoryLow = ( 'Host', 'HostMemoryLow' ); + ktReason_Host_DriverNotLoaded = ( 'Host', 'Driver not loaded' ); + ktReason_Host_DriverNotUnloading = ( 'Host', 'Driver not unloading' ); + ktReason_Host_DriverNotCompilable = ( 'Host', 'Driver not compilable' ); + ktReason_Host_InstallationFailed = ( 'Host', 'Installation failed' ); + ktReason_Host_InstallationWantReboot = ( 'Host', 'Installation want reboot' ); + ktReason_Host_InvalidPackage = ( 'Host', 'ERROR_INSTALL_PACKAGE_INVALID' ); + ktReason_Host_InstallSourceAbsent = ( 'Host', 'ERROR_INSTALL_SOURCE_ABSENT' ); + ktReason_Host_NotSignedWithBuildCert = ( 'Host', 'Not signed with build cert' ); + ktReason_Host_DiskFull = ( 'Host', 'Host disk full' ); + ktReason_Host_DoubleFreeHeap = ( 'Host', 'Double free or corruption' ); + ktReason_Host_LeftoverService = ( 'Host', 'Leftover service' ); + ktReason_Host_win32com_gen_py = ( 'Host', 'win32com.gen_py' ); + ktReason_Host_Reboot_OSX_Watchdog_Timeout = ( 'Host Reboot', 'OSX Watchdog Timeout' ); + ktReason_Host_Modprobe_Failed = ( 'Host', 'Modprobe failed' ); + ktReason_Host_Install_Hang = ( 'Host', 'Install hang' ); + ktReason_Host_NetworkMisconfiguration = ( 'Host', 'Network misconfiguration' ); + ktReason_Host_TSTInfo_Accuracy_OOR = ( 'Host', 'TSTInfo accuracy out of range' ); + ktReason_Networking_Nonexistent_host_nic = ( 'Networking', 'Nonexistent host networking interface' ); + ktReason_Networking_VERR_INTNET_FLT_IF_NOT_FOUND = ( 'Networking', 'VERR_INTNET_FLT_IF_NOT_FOUND' ); + ktReason_OSInstall_GRUB_hang = ( 'O/S Install', 'GRUB hang' ); + ktReason_OSInstall_Udev_hang = ( 'O/S Install', 'udev hang' ); + ktReason_OSInstall_Sata_no_BM = ( 'O/S Install', 'SATA busmaster bit not set' ); + ktReason_Panic_BootManagerC000000F = ( 'Panic', 'Hardware Changed' ); + ktReason_Panic_MP_BIOS_IO_APIC = ( 'Panic', 'MP-BIOS/IO-APIC' ); + ktReason_Panic_HugeMemory = ( 'Panic', 'Huge memory assertion' ); + ktReason_Panic_IOAPICDoesntWork = ( 'Panic', 'IO-APIC and timer does not work' ); + ktReason_Panic_TxUnitHang = ( 'Panic', 'Tx Unit Hang' ); + ktReason_API_std_bad_alloc = ( 'API / (XP)COM', 'std::bad_alloc' ); + ktReason_API_Digest_Mismatch = ( 'API / (XP)COM', 'Digest mismatch' ); + ktReason_API_MoveVM_SharingViolation = ( 'API / (XP)COM', 'MoveVM sharing violation' ); + ktReason_API_MoveVM_InvalidParameter = ( 'API / (XP)COM', 'MoveVM invalid parameter' ); + ktReason_API_Open_Session_Failed = ( 'API / (XP)COM', 'Open session failed' ); + ktReason_XPCOM_Exit_Minus_11 = ( 'API / (XP)COM', 'exit -11' ); + ktReason_XPCOM_VBoxSVC_Hang = ( 'API / (XP)COM', 'VBoxSVC hang' ); + ktReason_XPCOM_VBoxSVC_Hang_Plus_Heap_Corruption = ( 'API / (XP)COM', 'VBoxSVC hang + heap corruption' ); + ktReason_XPCOM_NS_ERROR_CALL_FAILED = ( 'API / (XP)COM', 'NS_ERROR_CALL_FAILED' ); + ktReason_BootManager_Image_corrupt = ( 'Unknown', 'BOOTMGR Image corrupt' ); + ktReason_Unknown_Heap_Corruption = ( 'Unknown', 'Heap corruption' ); + ktReason_Unknown_Reboot_Loop = ( 'Unknown', 'Reboot loop' ); + ktReason_Unknown_File_Not_Found = ( 'Unknown', 'File not found' ); + ktReason_Unknown_HalReturnToFirmware = ( 'Unknown', 'HalReturnToFirmware' ); + ktReason_Unknown_VM_Crash = ( 'Unknown', 'VM crash' ); + ktReason_Unknown_VM_Terminated = ( 'Unknown', 'VM terminated' ); + ktReason_Unknown_VM_Start_Error = ( 'Unknown', 'VM Start Error' ); + ktReason_Unknown_VM_Runtime_Error = ( 'Unknown', 'VM Runtime Error' ); + ktReason_VMM_kvm_lock_spinning = ( 'VMM', 'kvm_lock_spinning' ); + ktReason_Ignore_Buggy_Test_Driver = ( 'Ignore', 'Buggy test driver' ); + ktReason_Ignore_Stale_Files = ( 'Ignore', 'Stale files' ); + ktReason_Buggy_Build_Broken_Build = ( 'Broken Build', 'Buggy build' ); + ktReason_GuestBug_CompizVBoxQt = ( 'Guest Bug', 'Compiz + VirtualBox Qt GUI crash' ); + ## @} + + ## BSOD category. + ksBsodCategory = 'BSOD'; + ## Special reason indicating that the flesh and blood sheriff has work to do. + ksBsodAddNew = 'Add new BSOD'; + + ## Unit test category. + ksUnitTestCategory = 'Unit'; + ## Special reason indicating that the flesh and blood sheriff has work to do. + ksUnitTestAddNew = 'Add new'; + + ## Used for indica that we shouldn't report anything for this test result ID and + ## consider promoting the previous error to test set level if it's the only one. + ktHarmless = ( 'Probably', 'Caused by previous error' ); + + + def caseClosed(self, oCaseFile): + """ + Reports the findings in the case and closes it. + """ + # + # Log it and create a dReasonForReasultId we can use below. + # + dCommentForResultId = oCaseFile.dCommentForResultId; + if oCaseFile.dReasonForResultId: + # Must weed out ktHarmless. + dReasonForResultId = {}; + for idKey, tReason in oCaseFile.dReasonForResultId.items(): + if tReason is not self.ktHarmless: + dReasonForResultId[idKey] = tReason; + if not dReasonForResultId: + self.vprint(u'TODO: Closing %s without a real reason, only %s.' + % (oCaseFile.sName, oCaseFile.dReasonForResultId)); + return False; + + # Try promote to single reason. + atValues = dReasonForResultId.values(); + fSingleReason = True; + if len(dReasonForResultId) == 1 and next(iter(dReasonForResultId.keys())) != oCaseFile.oTestSet.idTestResult: + self.dprint(u'Promoting single reason to whole set: %s' % (next(iter(atValues)),)); + elif len(dReasonForResultId) > 1 and len(atValues) == list(atValues).count(next(iter(atValues))): + self.dprint(u'Merged %d reasons to a single one: %s' % (len(atValues), next(iter(atValues)))); + else: + fSingleReason = False; + if fSingleReason: + dReasonForResultId = { oCaseFile.oTestSet.idTestResult: next(iter(atValues)), }; + if dCommentForResultId: + dCommentForResultId = { oCaseFile.oTestSet.idTestResult: next(iter(dCommentForResultId.values())), }; + elif oCaseFile.tReason is not None: + dReasonForResultId = { oCaseFile.oTestSet.idTestResult: oCaseFile.tReason, }; + else: + self.vprint(u'Closing %s without a reason - this should not happen!' % (oCaseFile.sName,)); + return False; + + self.vprint(u'Closing %s with following reason%s: %s' + % ( oCaseFile.sName, 's' if len(dReasonForResultId) > 1 else '', dReasonForResultId, )); + + # + # Add the test failure reason record(s). + # + for idTestResult, tReason in dReasonForResultId.items(): + oFailureReason = self.getFailureReason(tReason); + if oFailureReason is not None: + sComment = 'Set by $Revision: 154728 $' # Handy for reverting later. + if idTestResult in dCommentForResultId: + sComment += ': ' + dCommentForResultId[idTestResult]; + + oAdd = TestResultFailureData(); + oAdd.initFromValues(idTestResult = idTestResult, + idFailureReason = oFailureReason.idFailureReason, + uidAuthor = self.uidSelf, + idTestSet = oCaseFile.oTestSet.idTestSet, + sComment = sComment,); + if self.oConfig.fRealRun: + try: + self.oTestResultFailureLogic.addEntry(oAdd, self.uidSelf, fCommit = True); + except Exception as oXcpt: + self.eprint(u'caseClosed: Exception "%s" while adding reason %s for %s' + % (oXcpt, oAdd, oCaseFile.sLongName,)); + else: + self.eprint(u'caseClosed: Cannot locate failure reason: %s / %s' % ( tReason[0], tReason[1],)); + return True; + + # + # Tools for assiting log parsing. + # + + @staticmethod + def matchFollowedByLines(sStr, off, asFollowingLines): + """ Worker for isThisFollowedByTheseLines. """ + + # Advance off to the end of the line. + off = sStr.find('\n', off); + if off < 0: + return False; + off += 1; + + # Match each string with the subsequent lines. + for iLine, sLine in enumerate(asFollowingLines): + offEnd = sStr.find('\n', off); + if offEnd < 0: + return iLine + 1 == len(asFollowingLines) and sStr.find(sLine, off) < 0; + if sLine and sStr.find(sLine, off, offEnd) < 0: + return False; + + # next line. + off = offEnd + 1; + + return True; + + @staticmethod + def isThisFollowedByTheseLines(sStr, sFirst, asFollowingLines): + """ + Looks for a line contining sFirst which is then followed by lines + with the strings in asFollowingLines. (No newline chars anywhere!) + Returns True / False. + """ + off = sStr.find(sFirst, 0); + while off >= 0: + if VirtualTestSheriff.matchFollowedByLines(sStr, off, asFollowingLines): + return True; + off = sStr.find(sFirst, off + 1); + return False; + + @staticmethod + def findAndReturnRestOfLine(sHaystack, sNeedle): + """ + Looks for sNeedle in sHaystack. + Returns The text following the needle up to the end of the line. + Returns None if not found. + """ + if sHaystack is None: + return None; + off = sHaystack.find(sNeedle); + if off < 0: + return None; + off += len(sNeedle) + offEol = sHaystack.find('\n', off); + if offEol < 0: + offEol = len(sHaystack); + return sHaystack[off:offEol] + + @staticmethod + def findInAnyAndReturnRestOfLine(asHaystacks, sNeedle): + """ + Looks for sNeedle in zeroe or more haystacks (asHaystack). + Returns The text following the first needed found up to the end of the line. + Returns None if not found. + """ + for sHaystack in asHaystacks: + sRet = VirtualTestSheriff.findAndReturnRestOfLine(sHaystack, sNeedle); + if sRet is not None: + return sRet; + return None; + + + # + # The investigative units. + # + + katSimpleInstallUninstallMainLogReasons = [ + # ( Whether to stop on hit, reason tuple, needle text. ) + ( False, ktReason_Host_LeftoverService, + 'SERVICE_NAME: vbox' ), + ( False, ktReason_Host_LeftoverService, + 'Seems installation was skipped. Old version lurking behind? Not the fault of this build/test run!'), + ]; + + kdatSimpleInstallUninstallMainLogReasonsPerOs = { + 'darwin': [ + # ( Whether to stop on hit, reason tuple, needle text. ) + ( True, ktReason_Host_DriverNotUnloading, + 'Can\'t remove kext org.virtualbox.kext.VBoxDrv; services failed to terminate - 0xe00002c7' ), + ], + 'linux': [ + # ( Whether to stop on hit, reason tuple, needle text. ) + ( True, ktReason_Host_DriverNotCompilable, + 'This system is not currently set up to build kernel modules' ), + ( True, ktReason_Host_DriverNotCompilable, + 'This system is currently not set up to build kernel modules' ), + ( True, ktReason_Host_InstallationFailed, + 'vboxdrv.sh: failed: Look at /var/log/vbox-install.log to find out what went wrong.' ), + ( True, ktReason_Host_DriverNotUnloading, + 'Cannot unload module vboxdrv'), + ], + 'solaris': [ + # ( Whether to stop on hit, reason tuple, needle text. ) + ( True, ktReason_Host_DriverNotUnloading, 'can\'t unload the module: Device busy' ), + ( True, ktReason_Host_DriverNotUnloading, 'Unloading: Host module ...FAILED!' ), + ( True, ktReason_Host_DriverNotUnloading, 'Unloading: NetFilter (Crossbow) module ...FAILED!' ), + ( True, ktReason_Host_InstallationFailed, 'svcadm: Couldn\'t bind to svc.configd.' ), + ( True, ktReason_Host_InstallationFailed, 'pkgadd: ERROR: postinstall script did not complete successfully' ), + ], + 'win': [ + # ( Whether to stop on hit, reason tuple, needle text. ) + ( True, ktReason_Host_InstallationWantReboot, 'ERROR_SUCCESS_REBOOT_REQUIRED' ), + ( False, ktReason_Host_InstallationFailed, 'Installation error.' ), + ( True, ktReason_Host_InvalidPackage, 'Uninstaller failed, exit code: 1620' ), + ( True, ktReason_Host_InstallSourceAbsent, 'Uninstaller failed, exit code: 1612' ), + ], + }; + + + def investigateInstallUninstallFailure(self, oCaseFile, oFailedResult, sResultLog, fInstall): + """ + Investigates an install or uninstall failure. + + We lump the two together since the installation typically also performs + an uninstall first and will be seeing similar issues to the uninstall. + """ + self.dprint(u'%s + %s <<\n%s\n<<' % (oFailedResult.tsCreated, oFailedResult.tsElapsed, sResultLog,)); + + if fInstall and oFailedResult.enmStatus == TestSetData.ksTestStatus_TimedOut: + oCaseFile.noteReasonForId(self.ktReason_Host_Install_Hang, oFailedResult.idTestResult) + return True; + + atSimple = self.katSimpleInstallUninstallMainLogReasons; + if oCaseFile.oTestBox.sOs in self.kdatSimpleInstallUninstallMainLogReasonsPerOs: + atSimple = self.kdatSimpleInstallUninstallMainLogReasonsPerOs[oCaseFile.oTestBox.sOs] + atSimple; + + fFoundSomething = False; + for fStopOnHit, tReason, sNeedle in atSimple: + if sResultLog.find(sNeedle) > 0: + oCaseFile.noteReasonForId(tReason, oFailedResult.idTestResult); + if fStopOnHit: + return True; + fFoundSomething = True; + + return fFoundSomething if fFoundSomething else None; + + + def investigateBadTestBox(self, oCaseFile): + """ + Checks out bad-testbox statuses. + """ + _ = oCaseFile; + return False; + + + def investigateVBoxUnitTest(self, oCaseFile): + """ + Checks out a VBox unittest problem. + """ + + # + # Process simple test case failures first, using their name as reason. + # We do the reason management just like for BSODs. + # + cRelevantOnes = 0; + sMainLog = oCaseFile.getMainLog(); + aoFailedResults = oCaseFile.oTree.getListOfFailures(); + for oFailedResult in aoFailedResults: + if oFailedResult is oCaseFile.oTree: + self.vprint('TODO: toplevel failure'); + cRelevantOnes += 1 + + elif oFailedResult.sName == 'Installing VirtualBox': + sResultLog = TestSetData.extractLogSectionElapsed(sMainLog, oFailedResult.tsCreated, oFailedResult.tsElapsed); + self.investigateInstallUninstallFailure(oCaseFile, oFailedResult, sResultLog, fInstall = True) + cRelevantOnes += 1 + + elif oFailedResult.sName == 'Uninstalling VirtualBox': + sResultLog = TestSetData.extractLogSectionElapsed(sMainLog, oFailedResult.tsCreated, oFailedResult.tsElapsed); + self.investigateInstallUninstallFailure(oCaseFile, oFailedResult, sResultLog, fInstall = False) + cRelevantOnes += 1 + + elif oFailedResult.oParent is not None: + # Get the 2nd level node because that's where we'll find the unit test name. + while oFailedResult.oParent.oParent is not None: + oFailedResult = oFailedResult.oParent; + + # Only report a failure once. + if oFailedResult.idTestResult not in oCaseFile.dReasonForResultId: + sKey = oFailedResult.sName; + if sKey.startswith('testcase/'): + sKey = sKey[9:]; + if sKey in self.asUnitTestReasons: + tReason = ( self.ksUnitTestCategory, sKey ); + oCaseFile.noteReasonForId(tReason, oFailedResult.idTestResult); + else: + self.dprint(u'Unit test failure "%s" not found in %s;' % (sKey, self.asUnitTestReasons)); + tReason = ( self.ksUnitTestCategory, self.ksUnitTestAddNew ); + oCaseFile.noteReasonForId(tReason, oFailedResult.idTestResult, sComment = sKey); + cRelevantOnes += 1 + else: + self.vprint(u'Internal error: expected oParent to NOT be None for %s' % (oFailedResult,)); + + # + # If we've caught all the relevant ones by now, report the result. + # + if len(oCaseFile.dReasonForResultId) >= cRelevantOnes: + return self.caseClosed(oCaseFile); + return False; + + def extractGuestCpuStack(self, sInfoText): + """ + Extracts the guest CPU stacks from the input file. + + Returns a dictionary keyed by the CPU number, value being a list of + raw stack lines (no header). + Returns empty dictionary if no stacks where found. + """ + dRet = {}; + off = 0; + while True: + # Find the stack. + offStart = sInfoText.find('=== start guest stack VCPU ', off); + if offStart < 0: + break; + offEnd = sInfoText.find('=== end guest stack', offStart + 20); + if offEnd >= 0: + offEnd += 3; + else: + offEnd = sInfoText.find('=== start guest stack VCPU', offStart + 20); + if offEnd < 0: + offEnd = len(sInfoText); + + sStack = sInfoText[offStart : offEnd]; + sStack = sStack.replace('\r',''); # paranoia + asLines = sStack.split('\n'); + + # Figure the CPU. + asWords = asLines[0].split(); + if len(asWords) < 6 or not asWords[5].isdigit(): + break; + iCpu = int(asWords[5]); + + # Add it and advance. + dRet[iCpu] = [sLine.rstrip() for sLine in asLines[2:-1]] + off = offEnd; + return dRet; + + def investigateInfoKvmLockSpinning(self, oCaseFile, sInfoText, dLogs): + """ Investigates kvm_lock_spinning deadlocks """ + # + # Extract the stacks. We need more than one CPU to create a deadlock. + # + dStacks = self.extractGuestCpuStack(sInfoText); + self.dprint('kvm_lock_spinning: found %s stacks' % (len(dStacks),)); + if len(dStacks) >= 2: + # + # Examin each of the stacks. Each must have kvm_lock_spinning in + # one of the first three entries. + # + cHits = 0; + for asBacktrace in dStacks.values(): + for iFrame in xrange(min(3, len(asBacktrace))): + if asBacktrace[iFrame].find('kvm_lock_spinning') >= 0: + cHits += 1; + break; + self.dprint('kvm_lock_spinning: %s/%s hits' % (cHits, len(dStacks),)); + if cHits == len(dStacks): + return (True, self.ktReason_VMM_kvm_lock_spinning); + + _ = dLogs; _ = oCaseFile; + return (False, None); + + def investigateInfoHalReturnToFirmware(self, oCaseFile, sInfoText, dLogs): + """ Investigates HalReturnToFirmware hangs """ + del oCaseFile + del sInfoText + del dLogs + # hope that's sufficient + return (True, self.ktReason_Unknown_HalReturnToFirmware); + + ## Things we search a main or VM log for to figure out why something went bust. + ## @note DO NOT ADD MORE STUFF HERE! + ## Please use katSimpleMainLogReasons and katSimpleVmLogReasons instead! + katSimpleMainAndVmLogReasonsDeprecated = [ + # ( Whether to stop on hit, reason tuple, needle text. ) + ( False, ktReason_Guru_Generic, 'GuruMeditation' ), + ( False, ktReason_Guru_Generic, 'Guru Meditation' ), + ( True, ktReason_Guru_VERR_IEM_INSTR_NOT_IMPLEMENTED, 'VERR_IEM_INSTR_NOT_IMPLEMENTED' ), + ( True, ktReason_Guru_VERR_IEM_ASPECT_NOT_IMPLEMENTED, 'VERR_IEM_ASPECT_NOT_IMPLEMENTED' ), + ( True, ktReason_Guru_VERR_TRPM_DONT_PANIC, 'VERR_TRPM_DONT_PANIC' ), + ( True, ktReason_Guru_VERR_PGM_PHYS_PAGE_RESERVED, 'VERR_PGM_PHYS_PAGE_RESERVED' ), + ( True, ktReason_Guru_VERR_VMX_INVALID_GUEST_STATE, 'VERR_VMX_INVALID_GUEST_STATE' ), + ( True, ktReason_Guru_VINF_EM_TRIPLE_FAULT, 'VINF_EM_TRIPLE_FAULT' ), + ( True, ktReason_Networking_Nonexistent_host_nic, + 'rc=E_FAIL text="Nonexistent host networking interface, name \'eth0\' (VERR_INTERNAL_ERROR)"' ), + ( True, ktReason_Networking_VERR_INTNET_FLT_IF_NOT_FOUND, + 'Failed to attach the network LUN (VERR_INTNET_FLT_IF_NOT_FOUND)' ), + ( True, ktReason_Host_Reboot_OSX_Watchdog_Timeout, ': "OSX Watchdog Timeout: ' ), + ( False, ktReason_XPCOM_NS_ERROR_CALL_FAILED, + 'Exception: 0x800706be (Call to remote object failed (NS_ERROR_CALL_FAILED))' ), + ( True, ktReason_API_std_bad_alloc, 'Unexpected exception: std::bad_alloc' ), + ( True, ktReason_Host_HostMemoryLow, 'HostMemoryLow' ), + ( True, ktReason_Host_HostMemoryLow, 'Failed to procure handy pages; rc=VERR_NO_MEMORY' ), + ( True, ktReason_Unknown_File_Not_Found, + 'Error: failed to start machine. Error message: File not found. (VERR_FILE_NOT_FOUND)' ), + ( True, ktReason_Unknown_File_Not_Found, # lump it in with file-not-found for now. + 'Error: failed to start machine. Error message: Not supported. (VERR_NOT_SUPPORTED)' ), + ( False, ktReason_Unknown_VM_Crash, 'txsDoConnectViaTcp: Machine state: Aborted' ), + ( True, ktReason_Host_Modprobe_Failed, 'Kernel driver not installed' ), + ( True, ktReason_OSInstall_Sata_no_BM, 'PCHS=14128/14134/8224' ), + ( True, ktReason_Host_DoubleFreeHeap, 'double free or corruption' ), + #( False, ktReason_Unknown_VM_Start_Error, 'VMSetError: ' ), - false positives for stuff like: + # "VMSetError: VD: Backend 'VBoxIsoMaker' does not support async I/O" + ( False, ktReason_Unknown_VM_Start_Error, 'error: failed to open session for' ), + ( False, ktReason_Unknown_VM_Runtime_Error, 'Console: VM runtime error: fatal=true' ), + ]; + + ## This we search a main log for to figure out why something went bust. + katSimpleMainLogReasons = [ + # ( Whether to stop on hit, reason tuple, needle text. ) + ( False, ktReason_Host_win32com_gen_py, 'ModuleNotFoundError: No module named \'win32com.gen_py' ), + + ]; + + ## This we search a VM log for to figure out why something went bust. + katSimpleVmLogReasons = [ + # ( Whether to stop on hit, reason tuple, needle text. ) + # Note: Works for ATA and VD drivers. + ( False, ktReason_Host_DiskFull, '_DISKFULL' ), + ]; + + ## Things we search a VBoxHardening.log file for to figure out why something went bust. + katSimpleVBoxHardeningLogReasons = [ + # ( Whether to stop on hit, reason tuple, needle text. ) + ( True, ktReason_Host_DriverNotLoaded, 'Error opening VBoxDrvStub: STATUS_OBJECT_NAME_NOT_FOUND' ), + ( True, ktReason_Host_NotSignedWithBuildCert, 'Not signed with the build certificate' ), + ( True, ktReason_Host_TSTInfo_Accuracy_OOR, 'RTCRTSPTSTINFO::Accuracy::Millis: Out of range' ), + ( False, ktReason_Unknown_VM_Crash, 'Quitting: ExitCode=0xc0000005 (rcNtWait=' ), + ]; + + ## Things we search a kernel.log file for to figure out why something went bust. + katSimpleKernelLogReasons = [ + # ( Whether to stop on hit, reason tuple, needle text. ) + ( True, ktReason_Panic_HugeMemory, 'mm/huge_memory.c:1988' ), + ( True, ktReason_Panic_IOAPICDoesntWork, 'IO-APIC + timer doesn\'t work' ), + ( True, ktReason_Panic_TxUnitHang, 'Detected Tx Unit Hang' ), + ( True, ktReason_GuestBug_CompizVBoxQt, 'error 4 in libQt5CoreVBox' ), + ( True, ktReason_GuestBug_CompizVBoxQt, 'error 4 in libgtk-3' ), + ]; + + ## Things we search the _RIGHT_ _STRIPPED_ vgatext for. + katSimpleVgaTextReasons = [ + # ( Whether to stop on hit, reason tuple, needle text. ) + ( True, ktReason_Panic_MP_BIOS_IO_APIC, + "..MP-BIOS bug: 8254 timer not connected to IO-APIC\n\n" ), + ( True, ktReason_Panic_MP_BIOS_IO_APIC, + "..MP-BIOS bug: 8254 timer not connected to IO-APIC\n" + "...trying to set up timer (IRQ0) through the 8259A ... failed.\n" + "...trying to set up timer as Virtual Wire IRQ... failed.\n" + "...trying to set up timer as ExtINT IRQ... failed :(.\n" + "Kernel panic - not syncing: IO-APIC + timer doesn't work! Boot with apic=debug\n" + "and send a report. Then try booting with the 'noapic' option\n" + "\n" ), + ( True, ktReason_OSInstall_GRUB_hang, + "-----\nGRUB Loading stage2..\n\n\n\n" ), + ( True, ktReason_OSInstall_GRUB_hang, + "-----\nGRUB Loading stage2...\n\n\n\n" ), # the 3 dot hang appears to be less frequent + ( True, ktReason_OSInstall_GRUB_hang, + "-----\nGRUB Loading stage2....\n\n\n\n" ), # the 4 dot hang appears to be very infrequent + ( True, ktReason_OSInstall_GRUB_hang, + "-----\nGRUB Loading stage2.....\n\n\n\n" ), # the 5 dot hang appears to be more frequent again + ( True, ktReason_OSInstall_Udev_hang, + "\nStarting udev:\n\n\n\n" ), + ( True, ktReason_OSInstall_Udev_hang, + "\nStarting udev:\n------" ), + ( True, ktReason_Panic_BootManagerC000000F, + "Windows failed to start. A recent hardware or software change might be the" ), + ( True, ktReason_BootManager_Image_corrupt, + "BOOTMGR image is corrupt. The system cannot boot." ), + ]; + + ## Things we search for in the info.txt file. Require handlers for now. + katInfoTextHandlers = [ + # ( Trigger text, handler method ) + ( "kvm_lock_spinning", investigateInfoKvmLockSpinning ), + ( "HalReturnToFirmware", investigateInfoHalReturnToFirmware ), + ]; + + ## Mapping screenshot/failure SHA-256 hashes to failure reasons. + katSimpleScreenshotHashReasons = [ + # ( Whether to stop on hit, reason tuple, lowercased sha-256 of PIL.Image.tostring output ) + ( True, ktReason_BSOD_Recovery, '576f8e38d62b311cac7e3dc3436a0d0b9bd8cfd7fa9c43aafa95631520a45eac' ), + ( True, ktReason_BSOD_Automatic_Repair, 'c6a72076cc619937a7a39cfe9915b36d94cee0d4e3ce5ce061485792dcee2749' ), + ( True, ktReason_BSOD_Automatic_Repair, '26c4d8a724ff2c5e1051f3d5b650dbda7b5fdee0aa3e3c6059797f7484a515df' ), + ( True, ktReason_BSOD_0000007F, '57e1880619e13042a87100e7a38c8974b85ce3866501be621bea0cc696bb2c63' ), + ( True, ktReason_BSOD_000000D1, '134621281f00a3f8aeeb7660064bffbf6187ed56d5852142328d0bcb18ef0ede' ), + ( True, ktReason_BSOD_000000D1, '279f11258150c9d2fef041eca65501f3141da8df39256d8f6377e897e3b45a93' ), + ( True, ktReason_BSOD_C0000225, 'bd13a144be9dcdfb16bc863ff4c8f02a86e263c174f2cd5ffd27ca5f3aa31789' ), + ( True, ktReason_BSOD_C0000225, '8348b465e7ee9e59dd4e785880c57fd8677de05d11ac21e786bfde935307b42f' ), + ( True, ktReason_BSOD_C0000225, '1316e1fc818a73348412788e6910b8c016f237d8b4e15b20caf4a866f7a7840e' ), + ( True, ktReason_BSOD_C0000225, '54e0acbff365ce20a85abbe42bcd53647b8b9e80c68e45b2cd30e86bf177a0b5' ), + ( True, ktReason_BSOD_C0000225, '50fec50b5199923fa48b3f3e782687cc381e1c8a788ebda14e6a355fbe3bb1b3' ), + ]; + + + def scanLog(self, asLogs, atNeedles, oCaseFile, idTestResult): + """ + Scans for atNeedles in sLog. + + Returns True if a stop-on-hit neelde was found. + Returns None if a no-stop reason was found. + Returns False if no hit. + """ + fRet = False; + for fStopOnHit, tReason, oNeedle in atNeedles: + fMatch = False; + if utils.isString(oNeedle): + for sLog in asLogs: + if sLog: + fMatch |= sLog.find(oNeedle) > 0; + else: + for sLog in asLogs: + if sLog: + fMatch |= oNeedle.search(sLog) is not None; + if fMatch: + oCaseFile.noteReasonForId(tReason, idTestResult); + if fStopOnHit: + return True; + fRet = None; + return fRet; + + + def investigateGATest(self, oCaseFile, oFailedResult, sResultLog): + """ + Investigates a failed VM run. + """ + enmReason = None; + sParentName = oFailedResult.oParent.sName if oFailedResult.oParent else ''; + if oFailedResult.sName == 'VBoxWindowsAdditions.exe' or sResultLog.find('VBoxWindowsAdditions.exe" failed with') > 0: + enmReason = self.ktReason_Add_Installer_Win_Failed; + # guest control: + elif sParentName == 'Guest Control' and oFailedResult.sName == 'Preparations': + enmReason = self.ktReason_Add_GstCtl_Preparations; + elif oFailedResult.sName == 'Session Basics': + enmReason = self.ktReason_Add_GstCtl_SessionBasics; + elif oFailedResult.sName == 'Session Process References': + enmReason = self.ktReason_Add_GstCtl_SessionProcRefs; + elif oFailedResult.sName == 'Copy from guest': + if sResultLog.find('*** abort action ***') >= 0: + enmReason = self.ktReason_Add_GstCtl_CopyFromGuest_Timeout; + elif oFailedResult.sName == 'Copy to guest': + off = sResultLog.find('"Guest directory "'); + if off > 0 and sResultLog.find('" already exists"', off, off + 80): + enmReason = self.ktReason_Add_GstCtl_CopyToGuest_DstExists; + elif sResultLog.find('Guest destination must not be empty') >= 0: + enmReason = self.ktReason_Add_GstCtl_CopyToGuest_DstEmpty; + elif sResultLog.find('*** abort action ***') >= 0: + enmReason = self.ktReason_Add_GstCtl_CopyToGuest_Timeout; + elif oFailedResult.sName.find('Session w/ Guest Reboot') >= 0: + enmReason = self.ktReason_Add_GstCtl_Session_Reboot; + # shared folders: + elif sParentName == 'Shared Folders' and oFailedResult.sName == 'Automounting': + enmReason = self.ktReason_Add_ShFl_Automount; + elif oFailedResult.sName == 'mmap': + if sResultLog.find('FsPerf: Flush issue at offset ') >= 0: + enmReason = self.ktReason_Add_Mmap_Coherency; + elif sResultLog.find('FlushViewOfFile') >= 0: + enmReason = self.ktReason_Add_FlushViewOfFile; + elif sParentName == 'Shared Folders' and oFailedResult.sName == 'Running FsPerf': + enmReason = self.ktReason_Add_ShFl_FsPerf; ## Maybe it would be better to be more specific... + + if enmReason is not None: + return oCaseFile.noteReasonForId(enmReason, oFailedResult.idTestResult); + + self.vprint(u'TODO: Cannot place GA failure idTestResult=%u - %s' % (oFailedResult.idTestResult, oFailedResult.sName,)); + self.dprint(u'%s + %s <<\n%s\n<<' % (oFailedResult.tsCreated, oFailedResult.tsElapsed, sResultLog,)); + return False; + + def isResultFromGATest(self, oCaseFile, oFailedResult): + """ + Checks if this result and corresponding log snippet looks like a GA test run. + """ + while oFailedResult is not None: + if oFailedResult.sName in [ 'Guest Control', 'Shared Folders', 'FsPerf', 'VBoxWindowsAdditions.exe' ]: + return True; + if oCaseFile.oTestCase.sName == 'Guest Additions' and oFailedResult.sName in [ 'Install', ]: + return True; + oFailedResult = oFailedResult.oParent; + return False; + + + def investigateVMResult(self, oCaseFile, oFailedResult, sResultLog): + """ + Investigates a failed VM run. + """ + + def investigateLogSet(): + """ + Investigates the current set of VM related logs. + """ + self.dprint('investigateLogSet: log lengths: result %u, VM %u, kernel %u, vga text %u, info text %u, hard %u' + % ( len(sResultLog if sResultLog else ''), + len(sVMLog if sVMLog else ''), + len(sKrnlLog if sKrnlLog else ''), + len(sVgaText if sVgaText else ''), + len(sInfoText if sInfoText else ''), + len(sNtHardLog if sNtHardLog else ''),)); + + #self.dprint(u'main.log<<<\n%s\n<<<\n' % (sResultLog,)); + #self.dprint(u'vbox.log<<<\n%s\n<<<\n' % (sVMLog,)); + #self.dprint(u'krnl.log<<<\n%s\n<<<\n' % (sKrnlLog,)); + #self.dprint(u'vgatext.txt<<<\n%s\n<<<\n' % (sVgaText,)); + #self.dprint(u'info.txt<<<\n%s\n<<<\n' % (sInfoText,)); + #self.dprint(u'hard.txt<<<\n%s\n<<<\n' % (sNtHardLog,)); + + # TODO: more + + # + # Look for BSODs. Some stupid stupid inconsistencies in reason and log messages here, so don't try prettify this. + # + sDetails = self.findInAnyAndReturnRestOfLine([ sVMLog, sResultLog ], + 'GIM: HyperV: Guest indicates a fatal condition! P0='); + if sDetails is not None: + # P0=%#RX64 P1=%#RX64 P2=%#RX64 P3=%#RX64 P4=%#RX64 " + sKey = sDetails.split(' ', 1)[0]; + try: sKey = '0x%08X' % (int(sKey, 16),); + except: pass; + if sKey in self.asBsodReasons: + tReason = ( self.ksBsodCategory, sKey ); + elif sKey.lower() in self.asBsodReasons: # just in case. + tReason = ( self.ksBsodCategory, sKey.lower() ); + else: + self.dprint(u'BSOD "%s" not found in %s;' % (sKey, self.asBsodReasons)); + tReason = ( self.ksBsodCategory, self.ksBsodAddNew ); + return oCaseFile.noteReasonForId(tReason, oFailedResult.idTestResult, sComment = sDetails.strip()); + + fFoundSomething = False; + + # + # Look for linux panic. + # + if sKrnlLog is not None: + fRet = self.scanLog([sKrnlLog,], self.katSimpleKernelLogReasons, oCaseFile, oFailedResult.idTestResult); + if fRet is True: + return fRet; + fFoundSomething |= fRet is None; + + # + # Loop thru the simple stuff. + # + + # Main log. + fRet = self.scanLog([sResultLog,], self.katSimpleMainLogReasons, oCaseFile, oFailedResult.idTestResult); + if fRet is True: + return fRet; + fFoundSomething |= fRet is None; + + # VM log. + fRet = self.scanLog([sVMLog,], self.katSimpleVmLogReasons, oCaseFile, oFailedResult.idTestResult); + if fRet is True: + return fRet; + fFoundSomething |= fRet is None; + + # Old main + vm log. + fRet = self.scanLog([sResultLog, sVMLog], self.katSimpleMainAndVmLogReasonsDeprecated, + oCaseFile, oFailedResult.idTestResult); + if fRet is True: + return fRet; + fFoundSomething |= fRet is None; + + # Continue with vga text. + if sVgaText: + fRet = self.scanLog([sVgaText,], self.katSimpleVgaTextReasons, oCaseFile, oFailedResult.idTestResult); + if fRet is True: + return fRet; + fFoundSomething |= fRet is None; + + # Continue with screen hashes. + if sScreenHash is not None: + for fStopOnHit, tReason, sHash in self.katSimpleScreenshotHashReasons: + if sScreenHash == sHash: + oCaseFile.noteReasonForId(tReason, oFailedResult.idTestResult); + if fStopOnHit: + return True; + fFoundSomething = True; + + # Check VBoxHardening.log. + if sNtHardLog is not None: + fRet = self.scanLog([sNtHardLog,], self.katSimpleVBoxHardeningLogReasons, oCaseFile, oFailedResult.idTestResult); + if fRet is True: + return fRet; + fFoundSomething |= fRet is None; + + # + # Complicated stuff. + # + dLogs = { + 'sVMLog': sVMLog, + 'sNtHardLog': sNtHardLog, + 'sScreenHash': sScreenHash, + 'sKrnlLog': sKrnlLog, + 'sVgaText': sVgaText, + 'sInfoText': sInfoText, + }; + + # info.txt. + if sInfoText: + for sNeedle, fnHandler in self.katInfoTextHandlers: + if sInfoText.find(sNeedle) > 0: + (fStop, tReason) = fnHandler(self, oCaseFile, sInfoText, dLogs); + if tReason is not None: + oCaseFile.noteReasonForId(tReason, oFailedResult.idTestResult); + if fStop: + return True; + fFoundSomething = True; + + # + # Check for repeated reboots... + # + if sVMLog is not None: + cResets = sVMLog.count('Changing the VM state from \'RUNNING\' to \'RESETTING\''); + if cResets > 10: + return oCaseFile.noteReasonForId(self.ktReason_Unknown_Reboot_Loop, oFailedResult.idTestResult, + sComment = 'Counted %s reboots' % (cResets,)); + + return fFoundSomething; + + # + # Check if we got any VM or/and kernel logs. Treat them as sets in + # case we run multiple VMs here (this is of course ASSUMING they + # appear in the order that terminateVmBySession uploads them). + # + cTimes = 0; + sVMLog = None; + sNtHardLog = None; + sScreenHash = None; + sKrnlLog = None; + sVgaText = None; + sInfoText = None; + for oFile in oFailedResult.aoFiles: + if oFile.sKind == TestResultFileData.ksKind_LogReleaseVm: + if 'VBoxHardening.log' not in oFile.sFile: + if sVMLog is not None: + if investigateLogSet() is True: + return True; + cTimes += 1; + sInfoText = None; + sVgaText = None; + sKrnlLog = None; + sScreenHash = None; + sNtHardLog = None; + sVMLog = oCaseFile.getLogFile(oFile); + else: + sNtHardLog = oCaseFile.getLogFile(oFile); + elif oFile.sKind == TestResultFileData.ksKind_LogGuestKernel: + sKrnlLog = oCaseFile.getLogFile(oFile); + elif oFile.sKind == TestResultFileData.ksKind_InfoVgaText: + sVgaText = '\n'.join([sLine.rstrip() for sLine in oCaseFile.getLogFile(oFile).split('\n')]); + elif oFile.sKind == TestResultFileData.ksKind_InfoCollection: + sInfoText = oCaseFile.getLogFile(oFile); + elif oFile.sKind == TestResultFileData.ksKind_ScreenshotFailure: + sScreenHash = oCaseFile.getScreenshotSha256(oFile); + if sScreenHash is not None: + sScreenHash = sScreenHash.lower(); + self.vprint(u'%s %s' % ( sScreenHash, oFile.sFile,)); + + if ( sVMLog is not None \ + or sNtHardLog is not None \ + or cTimes == 0) \ + and investigateLogSet() is True: + return True; + + return None; + + def isResultFromVMRun(self, oFailedResult, sResultLog): + """ + Checks if this result and corresponding log snippet looks like a VM run. + """ + + # Look for startVmEx/ startVmAndConnectToTxsViaTcp and similar output in the log. + if sResultLog.find(' startVm') > 0: + return True; + + # Any other indicators? No? + _ = oFailedResult; + return False; + + + ## Things we search a VBoxSVC log for to figure out why something went bust. + katSimpleSvcLogReasons = [ + # ( Whether to stop on hit, reason tuple, needle text. ) + ( False, ktReason_Unknown_VM_Crash, re.compile(r'Reaper.* exited normally: -1073741819 \(0xc0000005\)') ), + ( False, ktReason_Unknown_VM_Crash, re.compile(r'Reaper.* was signalled: 11 \(0xb\)') ), # For VBox < 6.1. + ( False, ktReason_Unknown_VM_Crash, re.compile(r'Reaper.* was signalled: SIGABRT.*') ), # Since VBox 7.0. + ( False, ktReason_Unknown_VM_Crash, re.compile(r'Reaper.* was signalled: SIGSEGV.*') ), + ( False, ktReason_Unknown_VM_Terminated, re.compile(r'Reaper.* was signalled: SIGTERM.*') ), + ( False, ktReason_Unknown_VM_Terminated, re.compile(r'Reaper.* was signalled: SIGKILL.*') ), + ]; + + def investigateSvcLogForVMRun(self, oCaseFile, sSvcLog): + """ + Check the VBoxSVC log for a single VM run. + """ + if sSvcLog: + fRet = self.scanLog([sSvcLog,], self.katSimpleSvcLogReasons, oCaseFile, oCaseFile.oTree.idTestResult); + if fRet is True or fRet is None: + return True; + return False; + + def investigateNtHardLogForVMRun(self, oCaseFile): + """ + Check if the hardening log for a single VM run contains VM crash indications. + """ + aoLogFiles = oCaseFile.oTree.getListOfLogFilesByKind(TestResultFileData.ksKind_LogReleaseVm); + for oLogFile in aoLogFiles: + if oLogFile.sFile.find('VBoxHardening.log') >= 0: + sLog = oCaseFile.getLogFile(oLogFile); + if sLog.find('Quitting: ExitCode=0xc0000005') >= 0: + return oCaseFile.noteReasonForId(self.ktReason_Unknown_VM_Crash, oCaseFile.oTree.idTestResult); + return False; + + + def investigateVBoxVMTest(self, oCaseFile, fSingleVM): + """ + Checks out a VBox VM test. + + This is generic investigation of a test running one or more VMs, like + for example a smoke test or a guest installation test. + + The fSingleVM parameter is a hint, which probably won't come in useful. + """ + _ = fSingleVM; + + # + # Get a list of test result failures we should be looking into and the main log. + # + aoFailedResults = oCaseFile.oTree.getListOfFailures(); + sMainLog = oCaseFile.getMainLog(); + + # + # There are a set of errors ending up on the top level result record. + # Should deal with these first. + # + if len(aoFailedResults) == 1 and aoFailedResults[0] == oCaseFile.oTree: + # Check if we've just got that XPCOM client smoke test shutdown issue. This will currently always + # be reported on the top result because vboxinstall.py doesn't add an error for it. It is easy to + # ignore other failures in the test if we're not a little bit careful here. + if sMainLog.find('vboxinstaller: Exit code: -11 (') > 0: + oCaseFile.noteReason(self.ktReason_XPCOM_Exit_Minus_11); + return self.caseClosed(oCaseFile); + + # Hang after starting VBoxSVC (e.g. idTestSet=136307258) + if self.isThisFollowedByTheseLines(sMainLog, 'oVBoxMgr=<vboxapi.VirtualBoxManager object at', + (' Timeout: ', ' Attempting to abort child...',) ): + if sMainLog.find('*** glibc detected *** /') > 0: + oCaseFile.noteReason(self.ktReason_XPCOM_VBoxSVC_Hang_Plus_Heap_Corruption); + else: + oCaseFile.noteReason(self.ktReason_XPCOM_VBoxSVC_Hang); + return self.caseClosed(oCaseFile); + + # Look for heap corruption without visible hang. + if sMainLog.find('*** glibc detected *** /') > 0 \ + or sMainLog.find("-1073740940") > 0: # STATUS_HEAP_CORRUPTION / 0xc0000374 + oCaseFile.noteReason(self.ktReason_Unknown_Heap_Corruption); + return self.caseClosed(oCaseFile); + + # Out of memory w/ timeout. + if sMainLog.find('sErrId=HostMemoryLow') > 0: + oCaseFile.noteReason(self.ktReason_Host_HostMemoryLow); + return self.caseClosed(oCaseFile); + + # Stale files like vts_rm.exe (windows). + offEnd = sMainLog.rfind('*** The test driver exits successfully. ***'); + if offEnd > 0 and sMainLog.find('[Error 145] The directory is not empty: ', offEnd) > 0: + oCaseFile.noteReason(self.ktReason_Ignore_Stale_Files); + return self.caseClosed(oCaseFile); + + # + # XPCOM screwup + # + if sMainLog.find('AttributeError: \'NoneType\' object has no attribute \'addObserver\'') > 0: + oCaseFile.noteReason(self.ktReason_Buggy_Build_Broken_Build); + return self.caseClosed(oCaseFile); + + # + # Go thru each failed result. + # + for oFailedResult in aoFailedResults: + self.dprint(u'Looking at test result #%u - %s' % (oFailedResult.idTestResult, oFailedResult.getFullName(),)); + sResultLog = TestSetData.extractLogSectionElapsed(sMainLog, oFailedResult.tsCreated, oFailedResult.tsElapsed); + if oFailedResult.sName == 'Installing VirtualBox': + self.investigateInstallUninstallFailure(oCaseFile, oFailedResult, sResultLog, fInstall = True) + + elif oFailedResult.sName == 'Uninstalling VirtualBox': + self.investigateInstallUninstallFailure(oCaseFile, oFailedResult, sResultLog, fInstall = False) + + elif self.isResultFromVMRun(oFailedResult, sResultLog): + self.investigateVMResult(oCaseFile, oFailedResult, sResultLog); + + elif self.isResultFromGATest(oCaseFile, oFailedResult): + self.investigateGATest(oCaseFile, oFailedResult, sResultLog); + + elif sResultLog.find('most likely not unique') > 0: + oCaseFile.noteReasonForId(self.ktReason_Host_NetworkMisconfiguration, oFailedResult.idTestResult) + elif sResultLog.find('Exception: 0x800706be (Call to remote object failed (NS_ERROR_CALL_FAILED))') > 0: + oCaseFile.noteReasonForId(self.ktReason_XPCOM_NS_ERROR_CALL_FAILED, oFailedResult.idTestResult); + + elif sResultLog.find('The machine is not mutable (state is ') > 0: + self.vprint('Ignoring "machine not mutable" error as it is probably due to an earlier problem'); + oCaseFile.noteReasonForId(self.ktHarmless, oFailedResult.idTestResult); + + elif sResultLog.find('** error: no action was specified') > 0 \ + or sResultLog.find('(len(self._asXml, asText))') > 0: + oCaseFile.noteReasonForId(self.ktReason_Ignore_Buggy_Test_Driver, oFailedResult.idTestResult); + + else: + self.vprint(u'TODO: Cannot place idTestResult=%u - %s' % (oFailedResult.idTestResult, oFailedResult.sName,)); + self.dprint(u'%s + %s <<\n%s\n<<' % (oFailedResult.tsCreated, oFailedResult.tsElapsed, sResultLog,)); + + # + # Windows python/com screwup. + # + if sMainLog.find('ModuleNotFoundError: No module named \'win32com.gen_py') > 0: + oCaseFile.noteReason(self.ktReason_Host_win32com_gen_py); + return self.caseClosed(oCaseFile); + + # + # Check VBoxSVC.log and VBoxHardening.log for VM crashes if inconclusive on single VM runs. + # + if fSingleVM and len(oCaseFile.dReasonForResultId) < len(aoFailedResults): + self.dprint(u'Got %u out of %u - checking VBoxSVC.log...' + % (len(oCaseFile.dReasonForResultId), len(aoFailedResults))); + if self.investigateSvcLogForVMRun(oCaseFile, oCaseFile.getSvcLog()): + return self.caseClosed(oCaseFile); + if self.investigateNtHardLogForVMRun(oCaseFile): + return self.caseClosed(oCaseFile); + + # + # Report home and close the case if we got them all, otherwise log it. + # + if len(oCaseFile.dReasonForResultId) >= len(aoFailedResults): + return self.caseClosed(oCaseFile); + + if oCaseFile.dReasonForResultId: + self.vprint(u'TODO: Got %u out of %u - close, but no cigar. :-/' + % (len(oCaseFile.dReasonForResultId), len(aoFailedResults))); + else: + self.vprint(u'XXX: Could not figure out anything at all! :-('); + return False; + + + ## Things we search a main log for to figure out why something in the API test went bust. + katSimpleApiMainLogReasons = [ + # ( Whether to stop on hit, reason tuple, needle text. ) + ( True, ktReason_Networking_Nonexistent_host_nic, + 'rc=E_FAIL text="Nonexistent host networking interface, name \'eth0\' (VERR_INTERNAL_ERROR)"' ), + ( False, ktReason_XPCOM_NS_ERROR_CALL_FAILED, + 'Exception: 0x800706be (Call to remote object failed (NS_ERROR_CALL_FAILED))' ), + ( True, ktReason_API_std_bad_alloc, 'Unexpected exception: std::bad_alloc' ), + ( True, ktReason_API_Digest_Mismatch, 'Digest mismatch (VERR_NOT_EQUAL)' ), + ( True, ktReason_API_MoveVM_SharingViolation, 'rc=VBOX_E_IPRT_ERROR text="Could not copy the log file ' ), + ( True, ktReason_API_MoveVM_InvalidParameter, + 'rc=VBOX_E_IPRT_ERROR text="Could not copy the setting file ' ), + ( True, ktReason_API_Open_Session_Failed, 'error: failed to open session for' ), + ]; + + def investigateVBoxApiTest(self, oCaseFile): + """ + Checks out a VBox API test. + """ + + # + # Get a list of test result failures we should be looking into and the main log. + # + aoFailedResults = oCaseFile.oTree.getListOfFailures(); + sMainLog = oCaseFile.getMainLog(); + + # + # Go thru each failed result. + # + for oFailedResult in aoFailedResults: + self.dprint(u'Looking at test result #%u - %s' % (oFailedResult.idTestResult, oFailedResult.getFullName(),)); + sResultLog = TestSetData.extractLogSectionElapsed(sMainLog, oFailedResult.tsCreated, oFailedResult.tsElapsed); + if oFailedResult.sName == 'Installing VirtualBox': + self.investigateInstallUninstallFailure(oCaseFile, oFailedResult, sResultLog, fInstall = True) + + elif oFailedResult.sName == 'Uninstalling VirtualBox': + self.investigateInstallUninstallFailure(oCaseFile, oFailedResult, sResultLog, fInstall = False) + + elif sResultLog.find('Exception: 0x800706be (Call to remote object failed (NS_ERROR_CALL_FAILED))') > 0: + oCaseFile.noteReasonForId(self.ktReason_XPCOM_NS_ERROR_CALL_FAILED, oFailedResult.idTestResult); + + else: + fFoundSomething = False; + for fStopOnHit, tReason, sNeedle in self.katSimpleApiMainLogReasons: + if sResultLog.find(sNeedle) > 0: + oCaseFile.noteReasonForId(tReason, oFailedResult.idTestResult); + fFoundSomething = True; + if fStopOnHit: + break; + if fFoundSomething: + self.vprint(u'TODO: Cannot place idTestResult=%u - %s' % (oFailedResult.idTestResult, oFailedResult.sName,)); + self.dprint(u'%s + %s <<\n%s\n<<' % (oFailedResult.tsCreated, oFailedResult.tsElapsed, sResultLog,)); + + # + # Report home and close the case if we got them all, otherwise log it. + # + if len(oCaseFile.dReasonForResultId) >= len(aoFailedResults): + return self.caseClosed(oCaseFile); + + if oCaseFile.dReasonForResultId: + self.vprint(u'TODO: Got %u out of %u - close, but no cigar. :-/' + % (len(oCaseFile.dReasonForResultId), len(aoFailedResults))); + else: + self.vprint(u'XXX: Could not figure out anything at all! :-('); + return False; + + + def reasoningFailures(self): + """ + Guess the reason for failures. + """ + # + # Get a list of failed test sets without any assigned failure reason. + # + cGot = 0; + if not self.oConfig.aidTestSets: + aoTestSets = self.oTestSetLogic.fetchFailedSetsWithoutReason(cHoursBack = self.oConfig.cHoursBack, + tsNow = self.tsNow); + else: + aoTestSets = [self.oTestSetLogic.getById(idTestSet) for idTestSet in self.oConfig.aidTestSets]; + for oTestSet in aoTestSets: + self.dprint(u'----------------------------------- #%u, status %s -----------------------------------' + % ( oTestSet.idTestSet, oTestSet.enmStatus,)); + + # + # Open a case file and assign it to the right investigator. + # + (oTree, _ ) = self.oTestResultLogic.fetchResultTree(oTestSet.idTestSet); + oBuild = BuildDataEx().initFromDbWithId( self.oDb, oTestSet.idBuild, oTestSet.tsCreated); + oTestBox = TestBoxData().initFromDbWithGenId( self.oDb, oTestSet.idGenTestBox); + oTestGroup = TestGroupData().initFromDbWithId( self.oDb, oTestSet.idTestGroup, oTestSet.tsCreated); + oTestCase = TestCaseDataEx().initFromDbWithGenId( self.oDb, oTestSet.idGenTestCase, oTestSet.tsConfig); + + oCaseFile = VirtualTestSheriffCaseFile(self, oTestSet, oTree, oBuild, oTestBox, oTestGroup, oTestCase); + + if oTestSet.enmStatus == TestSetData.ksTestStatus_BadTestBox: + self.dprint(u'investigateBadTestBox is taking over %s.' % (oCaseFile.sLongName,)); + fRc = self.investigateBadTestBox(oCaseFile); + + elif oCaseFile.isVBoxUnitTest(): + self.dprint(u'investigateVBoxUnitTest is taking over %s.' % (oCaseFile.sLongName,)); + fRc = self.investigateVBoxUnitTest(oCaseFile); + + elif oCaseFile.isVBoxInstallTest() or oCaseFile.isVBoxUnattendedInstallTest(): + self.dprint(u'investigateVBoxVMTest is taking over %s.' % (oCaseFile.sLongName,)); + fRc = self.investigateVBoxVMTest(oCaseFile, fSingleVM = True); + + elif oCaseFile.isVBoxUSBTest(): + self.dprint(u'investigateVBoxVMTest is taking over %s.' % (oCaseFile.sLongName,)); + fRc = self.investigateVBoxVMTest(oCaseFile, fSingleVM = True); + + elif oCaseFile.isVBoxStorageTest(): + self.dprint(u'investigateVBoxVMTest is taking over %s.' % (oCaseFile.sLongName,)); + fRc = self.investigateVBoxVMTest(oCaseFile, fSingleVM = True); + + elif oCaseFile.isVBoxGAsTest(): + self.dprint(u'investigateVBoxVMTest is taking over %s.' % (oCaseFile.sLongName,)); + fRc = self.investigateVBoxVMTest(oCaseFile, fSingleVM = True); + + elif oCaseFile.isVBoxAPITest(): + self.dprint(u'investigateVBoxApiTest is taking over %s.' % (oCaseFile.sLongName,)); + fRc = self.investigateVBoxApiTest(oCaseFile); + + elif oCaseFile.isVBoxBenchmarkTest(): + self.dprint(u'investigateVBoxVMTest is taking over %s.' % (oCaseFile.sLongName,)); + fRc = self.investigateVBoxVMTest(oCaseFile, fSingleVM = False); + + elif oCaseFile.isVBoxSmokeTest(): + self.dprint(u'investigateVBoxVMTest is taking over %s.' % (oCaseFile.sLongName,)); + fRc = self.investigateVBoxVMTest(oCaseFile, fSingleVM = False); + + elif oCaseFile.isVBoxSerialTest(): + self.dprint(u'investigateVBoxVMTest is taking over %s.' % (oCaseFile.sLongName,)); + fRc = self.investigateVBoxVMTest(oCaseFile, fSingleVM = False); + + else: + self.vprint(u'reasoningFailures: Unable to classify test set: %s' % (oCaseFile.sLongName,)); + fRc = False; + cGot += fRc is True; + + self.vprint(u'reasoningFailures: Got %u out of %u' % (cGot, len(aoTestSets), )); + return 0; + + + def main(self): + """ + The 'main' function. + Return exit code (0, 1, etc). + """ + # Database stuff. + self.oDb = TMDatabaseConnection() + self.oTestResultLogic = TestResultLogic(self.oDb); + self.oTestSetLogic = TestSetLogic(self.oDb); + self.oFailureReasonLogic = FailureReasonLogic(self.oDb); + self.oTestResultFailureLogic = TestResultFailureLogic(self.oDb); + self.asBsodReasons = self.oFailureReasonLogic.fetchForSheriffByNamedCategory(self.ksBsodCategory); + self.asUnitTestReasons = self.oFailureReasonLogic.fetchForSheriffByNamedCategory(self.ksUnitTestCategory); + + # Get a fix on our 'now' before we do anything.. + self.oDb.execute('SELECT CURRENT_TIMESTAMP - interval \'%s hours\'', (self.oConfig.cStartHoursAgo,)); + self.tsNow = self.oDb.fetchOne(); + + # If we're suppost to commit anything we need to get our user ID. + rcExit = 0; + if self.oConfig.fRealRun: + self.oLogin = UserAccountLogic(self.oDb).tryFetchAccountByLoginName(VirtualTestSheriff.ksLoginName); + if self.oLogin is None: + rcExit = self.eprint('Cannot find my user account "%s"!' % (VirtualTestSheriff.ksLoginName,)); + else: + self.uidSelf = self.oLogin.uid; + + # + # Do the stuff. + # + if rcExit == 0: + rcExit = self.selfCheck(); + if rcExit == 0: + rcExit = self.badTestBoxManagement(); + rcExit2 = self.reasoningFailures(); + if rcExit == 0: + rcExit = rcExit2; + # Redo the bad testbox management after failure reasons have been assigned (got timing issues). + if rcExit == 0: + rcExit = self.badTestBoxManagement(); + + # Cleanup. + self.oFailureReasonLogic = None; + self.oTestResultFailureLogic = None; + self.oTestSetLogic = None; + self.oTestResultLogic = None; + self.oDb.close(); + self.oDb = None; + if self.oLogFile is not None: + self.oLogFile.close(); + self.oLogFile = None; + return rcExit; + +if __name__ == '__main__': + sys.exit(VirtualTestSheriff().main()); diff --git a/src/VBox/ValidationKit/testmanager/cgi/Makefile.kmk b/src/VBox/ValidationKit/testmanager/cgi/Makefile.kmk new file mode 100644 index 00000000..1de2d1dc --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/cgi/Makefile.kmk @@ -0,0 +1,46 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(wildcard $(PATH_SUB_CURRENT)/*.py) + +$(evalcall def_vbox_validationkit_process_python_sources) +$(evalcall def_vbox_validationkit_process_js_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/testmanager/cgi/admin.py b/src/VBox/ValidationKit/testmanager/cgi/admin.py new file mode 100755 index 00000000..128aed3d --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/cgi/admin.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: admin.py $ + +""" +CGI - Administrator Web-UI. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import os +import sys + +# Only the main script needs to modify the path. +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testmanager import config; +from testmanager.core.webservergluecgi import WebServerGlueCgi; +from testmanager.webui.wuiadmin import WuiAdmin; + +def main(): + """ + Main function a la C/C++. Returns exit code. + """ + + oSrvGlue = WebServerGlueCgi(g_ksValidationKitDir, fHtmlOutput = True); + try: + oWui = WuiAdmin(oSrvGlue); + oWui.dispatchRequest(); + oSrvGlue.flush(); + except Exception as oXcpt: + return oSrvGlue.errorPage('Internal error: %s' % (str(oXcpt),), sys.exc_info()); + + return 0; + +if __name__ == '__main__': + if config.g_kfProfileAdmin: + from testmanager.debug import cgiprofiling; + sys.exit(cgiprofiling.profileIt(main)); + else: + sys.exit(main()); + diff --git a/src/VBox/ValidationKit/testmanager/cgi/debuginfo.py b/src/VBox/ValidationKit/testmanager/cgi/debuginfo.py new file mode 100755 index 00000000..12bf14b8 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/cgi/debuginfo.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: debuginfo.py $ + +""" +CGI - Debug Info Page. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import os +import sys + +# Only the main script needs to modify the path. +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testmanager.core.webservergluecgi import WebServerGlueCgi; + + +def main(): + """ + Main function a la C/C++. Returns exit code. + """ + + oSrvGlue = WebServerGlueCgi(g_ksValidationKitDir, fHtmlOutput = True); + try: + oSrvGlue.debugInfoPage(); + oSrvGlue.flush(); + except Exception as oXcpt: + return oSrvGlue.errorPage('Internal error: %s' % (str(oXcpt),), sys.exc_info()); + + return 0; + +if __name__ == '__main__': + sys.exit(main()); + diff --git a/src/VBox/ValidationKit/testmanager/cgi/index.py b/src/VBox/ValidationKit/testmanager/cgi/index.py new file mode 100755 index 00000000..f4af43df --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/cgi/index.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: index.py $ + +""" +CGI - Web UI - Main (index) page. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import os +import sys + +# Only the main script needs to modify the path. +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testmanager import config; +from testmanager.core.webservergluecgi import WebServerGlueCgi; +from testmanager.webui.wuimain import WuiMain; + + +def main(): + """ + Main function a la C/C++. Returns exit code. + """ + + oSrvGlue = WebServerGlueCgi(g_ksValidationKitDir, fHtmlOutput = False); + try: + oWui = WuiMain(oSrvGlue); + oWui.dispatchRequest(); + oSrvGlue.flush(); + except Exception as oXcpt: + return oSrvGlue.errorPage('Internal error: %s' % (str(oXcpt),), sys.exc_info()); + + return 0; + +if __name__ == '__main__': + if config.g_kfProfileIndex: + from testmanager.debug import cgiprofiling; + sys.exit(cgiprofiling.profileIt(main)); + else: + sys.exit(main()); + diff --git a/src/VBox/ValidationKit/testmanager/cgi/logout.py b/src/VBox/ValidationKit/testmanager/cgi/logout.py new file mode 100755 index 00000000..ccd59359 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/cgi/logout.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: logout.py $ + +""" +VirtualBox Validation Kit - CGI - Log out page. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import os +import sys + +# Only the main script needs to modify the path. +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testmanager.core.webservergluecgi import WebServerGlueCgi + + +def main(): + """ + Main function a la C/C++. Returns exit code. + """ + + oSrvGlue = WebServerGlueCgi(g_ksValidationKitDir, fHtmlOutput = True) + sUser = oSrvGlue.getLoginName() + if sUser not in (oSrvGlue.ksUnknownUser, 'logout'): + oSrvGlue.write('<p>Broken apache config!\n' + 'The logout.py script should be configured with .htaccess-logout and require user logout!</p>') + else: + oSrvGlue.write('<p>Successfully logged out!</p>') + oSrvGlue.write('<p><a href="%sadmin.py">Log in</a> under another user name.</p>' % + (oSrvGlue.getBaseUrl(),)) + + + oSrvGlue.write('<hr/><p>debug info:</p>') + oSrvGlue.debugInfoPage() + oSrvGlue.flush() + + return 0 + +if __name__ == '__main__': + sys.exit(main()) + diff --git a/src/VBox/ValidationKit/testmanager/cgi/logout2.py b/src/VBox/ValidationKit/testmanager/cgi/logout2.py new file mode 100755 index 00000000..c39d126b --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/cgi/logout2.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: logout2.py $ + +""" +VirtualBox Validation Kit - CGI - Log out page for Safari. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import os +import sys + +# Only the main script needs to modify the path. +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testmanager.core.webservergluecgi import WebServerGlueCgi; + + +def main(): + """ + Main function a la C/C++. Returns exit code. + """ + + oSrvGlue = WebServerGlueCgi(g_ksValidationKitDir, fHtmlOutput = True); + sUserAgent = oSrvGlue.getUserAgent(); + oSrvGlue.setHeaderField('Status', '401 Unauthorized to access the document'); + oSrvGlue.setHeaderField('WWW-authenticate', 'Basic realm="Test Manager"'); + if sUserAgent.startswith('Mozilla/') and sUserAgent.find('AppleWebKit/') > 0: + oSrvGlue.write('<p>Attempting to log out an Apple browser...</p>'); + else: + oSrvGlue.write('<p>Sorry, not sure this will work...</p>'); + oSrvGlue.write('<p>User-Agent:' + sUserAgent + '</p>'); + + oSrvGlue.write('<p><a href="%sadmin.py">Log in</a> under another user name.</p>' % + (oSrvGlue.getBaseUrl(),)) + + oSrvGlue.write('<hr/><p>debug info:</p>'); + oSrvGlue.debugInfoPage(); + oSrvGlue.flush(); + + return 0; + +if __name__ == '__main__': + sys.exit(main()); + diff --git a/src/VBox/ValidationKit/testmanager/cgi/rest.py b/src/VBox/ValidationKit/testmanager/cgi/rest.py new file mode 100755 index 00000000..c970779c --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/cgi/rest.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: rest.py $ + +""" +CGI - REST - sPath=path variant. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import os +import sys + +# Only the main script needs to modify the path. +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testmanager import config; +from testmanager.core.webservergluecgi import WebServerGlueCgi; +from testmanager.core.restdispatcher import RestMain, RestDispException; + + +def main(): + """ + Main function a la C/C++. Returns exit code. + """ + + oSrvGlue = WebServerGlueCgi(g_ksValidationKitDir, fHtmlOutput = False); + try: + oMain = RestMain(oSrvGlue); + oMain.dispatchRequest(); + oSrvGlue.flush(); + except RestDispException as oXcpt: + oSrvGlue.setStatus(oXcpt.iStatus); + oSrvGlue.setHeaderField('tm-error-message', str(oXcpt)); + oSrvGlue.write('error: ' + str(oXcpt)); + oSrvGlue.flush(); + except Exception as oXcpt: + return oSrvGlue.errorPage('Internal error: %s' % (str(oXcpt),), + sys.exc_info(), + config.g_ksTestBoxDispXpctLog); + + return 0; + +if __name__ == '__main__': + sys.exit(main()); + diff --git a/src/VBox/ValidationKit/testmanager/cgi/status.py b/src/VBox/ValidationKit/testmanager/cgi/status.py new file mode 100755 index 00000000..544239f6 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/cgi/status.py @@ -0,0 +1,519 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: status.py $ + +""" +CGI - Administrator Web-UI. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154325 $" + + +# Standard python imports. +import os +import sys + +# Only the main script needs to modify the path. +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testmanager import config; +from testmanager.core.webservergluecgi import WebServerGlueCgi; + +from common import constants; +from testmanager.core.base import TMExceptionBase; +from testmanager.core.db import TMDatabaseConnection; + + + +def timeDeltaToHours(oTimeDelta): + return oTimeDelta.days * 24 + oTimeDelta.seconds // 3600 + + +def testbox_data_processing(oDb): + testboxes_dict = {} + while True: + line = oDb.fetchOne(); + if line is None: + break; + testbox_name = line[0] + test_result = line[1] + oTimeDeltaSinceStarted = line[2] + test_box_os = line[3] + test_sched_group = line[4] + + # idle testboxes might have an assigned testsets, skipping them + if test_result not in g_kdTestStatuses: + continue + + testboxes_dict = dict_update(testboxes_dict, testbox_name, test_result) + + if "testbox_os" not in testboxes_dict[testbox_name]: + testboxes_dict[testbox_name].update({"testbox_os": test_box_os}) + + if "sched_group" not in testboxes_dict[testbox_name]: + testboxes_dict[testbox_name].update({"sched_group": test_sched_group}) + elif test_sched_group not in testboxes_dict[testbox_name]["sched_group"]: + testboxes_dict[testbox_name]["sched_group"] += "," + test_sched_group + + if test_result == "running": + testboxes_dict[testbox_name].update({"hours_running": timeDeltaToHours(oTimeDeltaSinceStarted)}) + + return testboxes_dict; + + +def os_results_separating(vb_dict, test_name, testbox_os, test_result): + if testbox_os == "linux": + dict_update(vb_dict, test_name + " / linux", test_result) + elif testbox_os == "win": + dict_update(vb_dict, test_name + " / windows", test_result) + elif testbox_os == "darwin": + dict_update(vb_dict, test_name + " / darwin", test_result) + elif testbox_os == "solaris": + dict_update(vb_dict, test_name + " / solaris", test_result) + else: + dict_update(vb_dict, test_name + " / other", test_result) + + +# const/immutable. +g_kdTestStatuses = { + 'running': 0, + 'success': 0, + 'skipped': 0, + 'bad-testbox': 0, + 'aborted': 0, + 'failure': 0, + 'timed-out': 0, + 'rebooted': 0, +} + +def dict_update(target_dict, key_name, test_result): + if key_name not in target_dict: + target_dict.update({key_name: g_kdTestStatuses.copy()}) + if test_result in g_kdTestStatuses: + target_dict[key_name][test_result] += 1 + return target_dict + + +def formatDataEntry(sKey, dEntry): + # There are variations in the first and second "columns". + if "hours_running" in dEntry: + sRet = "%s;%s;%s | running: %s;%s" \ + % (sKey, dEntry["testbox_os"], dEntry["sched_group"], dEntry["running"], dEntry["hours_running"]); + else: + if "testbox_os" in dEntry: + sRet = "%s;%s;%s" % (sKey, dEntry["testbox_os"], dEntry["sched_group"],); + else: + sRet = sKey; + sRet += " | running: %s" % (dEntry["running"],) + + # The rest is currently identical: + sRet += " | success: %s | skipped: %s | bad-testbox: %s | aborted: %s | failure: %s | timed-out: %s | rebooted: %s | \n" \ + % (dEntry["success"], dEntry["skipped"], dEntry["bad-testbox"], dEntry["aborted"], + dEntry["failure"], dEntry["timed-out"], dEntry["rebooted"],); + return sRet; + + +def format_data(dData, fSorted): + sRet = ""; + if not fSorted: + for sKey in dData: + sRet += formatDataEntry(sKey, dData[sKey]); + else: + for sKey in sorted(dData.keys()): + sRet += formatDataEntry(sKey, dData[sKey]); + return sRet; + +###### + +class StatusDispatcherException(TMExceptionBase): + """ + Exception class for TestBoxController. + """ + pass; # pylint: disable=unnecessary-pass + + +class StatusDispatcher(object): # pylint: disable=too-few-public-methods + """ + Status dispatcher class. + """ + + + def __init__(self, oSrvGlue): + """ + Won't raise exceptions. + """ + self._oSrvGlue = oSrvGlue; + self._sAction = None; # _getStandardParams / dispatchRequest sets this later on. + self._dParams = None; # _getStandardParams / dispatchRequest sets this later on. + self._asCheckedParams = []; + self._dActions = \ + { + 'MagicMirrorTestResults': self._actionMagicMirrorTestResults, + 'MagicMirrorTestBoxes': self._actionMagicMirrorTestBoxes, + }; + + def _getStringParam(self, sName, asValidValues = None, fStrip = False, sDefValue = None): + """ + Gets a string parameter (stripped). + + Raises exception if not found and no default is provided, or if the + value isn't found in asValidValues. + """ + if sName not in self._dParams: + if sDefValue is None: + raise StatusDispatcherException('%s parameter %s is missing' % (self._sAction, sName)); + return sDefValue; + sValue = self._dParams[sName]; + if fStrip: + sValue = sValue.strip(); + + if sName not in self._asCheckedParams: + self._asCheckedParams.append(sName); + + if asValidValues is not None and sValue not in asValidValues: + raise StatusDispatcherException('%s parameter %s value "%s" not in %s ' + % (self._sAction, sName, sValue, asValidValues)); + return sValue; + + def _getIntParam(self, sName, iMin = None, iMax = None, iDefValue = None): + """ + Gets a string parameter. + Raises exception if not found, not a valid integer, or if the value + isn't in the range defined by iMin and iMax. + """ + if sName not in self._dParams: + if iDefValue is None: + raise StatusDispatcherException('%s parameter %s is missing' % (self._sAction, sName)); + return iDefValue; + sValue = self._dParams[sName]; + try: + iValue = int(sValue, 0); + except: + raise StatusDispatcherException('%s parameter %s value "%s" cannot be convert to an integer' + % (self._sAction, sName, sValue)); + if sName not in self._asCheckedParams: + self._asCheckedParams.append(sName); + + if (iMin is not None and iValue < iMin) \ + or (iMax is not None and iValue > iMax): + raise StatusDispatcherException('%s parameter %s value %d is out of range [%s..%s]' + % (self._sAction, sName, iValue, iMin, iMax)); + return iValue; + + def _getBoolParam(self, sName, fDefValue = None): + """ + Gets a boolean parameter. + + Raises exception if not found and no default is provided, or if not a + valid boolean. + """ + sValue = self._getStringParam(sName, [ 'True', 'true', '1', 'False', 'false', '0'], sDefValue = str(fDefValue)); + return sValue in ('True', 'true', '1',); + + def _checkForUnknownParameters(self): + """ + Check if we've handled all parameters, raises exception if anything + unknown was found. + """ + + if len(self._asCheckedParams) != len(self._dParams): + sUnknownParams = ''; + for sKey in self._dParams: + if sKey not in self._asCheckedParams: + sUnknownParams += ' ' + sKey + '=' + self._dParams[sKey]; + raise StatusDispatcherException('Unknown parameters: ' + sUnknownParams); + + return True; + + def _connectToDb(self): + """ + Connects to the database. + + Returns (TMDatabaseConnection, (more later perhaps) ) on success. + Returns (None, ) on failure after sending the box an appropriate response. + May raise exception on DB error. + """ + return (TMDatabaseConnection(self._oSrvGlue.dprint),); + + def _actionMagicMirrorTestBoxes(self): + """ + Produces test result status for the magic mirror dashboard + """ + + # + # Parse arguments and connect to the database. + # + cHoursBack = self._getIntParam('cHours', 1, 24*14, 12); + fSorted = self._getBoolParam('fSorted', False); + self._checkForUnknownParameters(); + + # + # Get the data. + # + # Note! We're not joining on TestBoxesWithStrings.idTestBox = + # TestSets.idGenTestBox here because of indexes. This is + # also more consistent with the rest of the query. + # Note! The original SQL is slow because of the 'OR TestSets.tsDone' + # part, using AND and UNION is significatly faster because + # it matches the TestSetsGraphBoxIdx (index). + # + (oDb,) = self._connectToDb(); + if oDb is None: + return False; + + # + # some comments regarding select below: + # first part is about fetching all finished tests for last cHoursBack hours + # second part is fetching all tests which isn't done + # both old (running more than cHoursBack) and fresh (less than cHoursBack) ones + # 'cause we want to know if there's a hanging tests together with currently running + # + # there's also testsets without status at all, likely because disabled testboxes still have an assigned testsets + # + oDb.execute(''' +( SELECT TestBoxesWithStrings.sName, + TestSets.enmStatus, + CURRENT_TIMESTAMP - TestSets.tsCreated, + TestBoxesWithStrings.sOS, + SchedGroupNames.sSchedGroupNames + FROM ( + SELECT TestBoxesInSchedGroups.idTestBox AS idTestBox, + STRING_AGG(SchedGroups.sName, ',') AS sSchedGroupNames + FROM TestBoxesInSchedGroups + INNER JOIN SchedGroups + ON SchedGroups.idSchedGroup = TestBoxesInSchedGroups.idSchedGroup + WHERE TestBoxesInSchedGroups.tsExpire = 'infinity'::TIMESTAMP + AND SchedGroups.tsExpire = 'infinity'::TIMESTAMP + GROUP BY TestBoxesInSchedGroups.idTestBox + ) AS SchedGroupNames, + TestBoxesWithStrings + LEFT OUTER JOIN TestSets + ON TestSets.idTestBox = TestBoxesWithStrings.idTestBox + AND TestSets.tsCreated >= (CURRENT_TIMESTAMP - '%s hours'::interval) + AND TestSets.tsDone IS NOT NULL + WHERE TestBoxesWithStrings.tsExpire = 'infinity'::TIMESTAMP + AND SchedGroupNames.idTestBox = TestBoxesWithStrings.idTestBox +) UNION ( + SELECT TestBoxesWithStrings.sName, + TestSets.enmStatus, + CURRENT_TIMESTAMP - TestSets.tsCreated, + TestBoxesWithStrings.sOS, + SchedGroupNames.sSchedGroupNames + FROM ( + SELECT TestBoxesInSchedGroups.idTestBox AS idTestBox, + STRING_AGG(SchedGroups.sName, ',') AS sSchedGroupNames + FROM TestBoxesInSchedGroups + INNER JOIN SchedGroups + ON SchedGroups.idSchedGroup = TestBoxesInSchedGroups.idSchedGroup + WHERE TestBoxesInSchedGroups.tsExpire = 'infinity'::TIMESTAMP + AND SchedGroups.tsExpire = 'infinity'::TIMESTAMP + GROUP BY TestBoxesInSchedGroups.idTestBox + ) AS SchedGroupNames, + TestBoxesWithStrings + LEFT OUTER JOIN TestSets + ON TestSets.idTestBox = TestBoxesWithStrings.idTestBox + AND TestSets.tsDone IS NULL + WHERE TestBoxesWithStrings.tsExpire = 'infinity'::TIMESTAMP + AND SchedGroupNames.idTestBox = TestBoxesWithStrings.idTestBox +) +''', (cHoursBack, cHoursBack,)); + + + # + # Process, format and output data. + # + dResult = testbox_data_processing(oDb); + self._oSrvGlue.setContentType('text/plain'); + self._oSrvGlue.write(format_data(dResult, fSorted)); + + return True; + + def _actionMagicMirrorTestResults(self): + """ + Produces test result status for the magic mirror dashboard + """ + + # + # Parse arguments and connect to the database. + # + sBranch = self._getStringParam('sBranch'); + cHoursBack = self._getIntParam('cHours', 1, 24*14, 6); ## @todo why 6 hours here and 12 for test boxes? + fSorted = self._getBoolParam('fSorted', False); + self._checkForUnknownParameters(); + + # + # Get the data. + # + # Note! These queries should be joining TestBoxesWithStrings and TestSets + # on idGenTestBox rather than on idTestBox and tsExpire=inf, but + # we don't have any index matching those. So, we'll ignore tests + # performed by deleted testboxes for the present as that doesn't + # happen often and we want the ~1000x speedup. + # + (oDb,) = self._connectToDb(); + if oDb is None: + return False; + + if sBranch == 'all': + oDb.execute(''' +SELECT TestSets.enmStatus, + TestCases.sName, + TestBoxesWithStrings.sOS +FROM TestSets +INNER JOIN TestCases + ON TestCases.idGenTestCase = TestSets.idGenTestCase +INNER JOIN TestBoxesWithStrings + ON TestBoxesWithStrings.idTestBox = TestSets.idTestBox + AND TestBoxesWithStrings.tsExpire = 'infinity'::TIMESTAMP +WHERE TestSets.tsCreated >= (CURRENT_TIMESTAMP - '%s hours'::interval) +''', (cHoursBack,)); + else: + oDb.execute(''' +SELECT TestSets.enmStatus, + TestCases.sName, + TestBoxesWithStrings.sOS +FROM TestSets +INNER JOIN BuildCategories + ON BuildCategories.idBuildCategory = TestSets.idBuildCategory + AND BuildCategories.sBranch = %s +INNER JOIN TestCases + ON TestCases.idGenTestCase = TestSets.idGenTestCase +INNER JOIN TestBoxesWithStrings + ON TestBoxesWithStrings.idTestBox = TestSets.idTestBox + AND TestBoxesWithStrings.tsExpire = 'infinity'::TIMESTAMP +WHERE TestSets.tsCreated >= (CURRENT_TIMESTAMP - '%s hours'::interval) +''', (sBranch, cHoursBack,)); + + # Process the data + dResult = {}; + while True: + aoRow = oDb.fetchOne(); + if aoRow is None: + break; + os_results_separating(dResult, aoRow[1], aoRow[2], aoRow[0]) # save all test results + + # Format and output it. + self._oSrvGlue.setContentType('text/plain'); + self._oSrvGlue.write(format_data(dResult, fSorted)); + + return True; + + def _getStandardParams(self, dParams): + """ + Gets the standard parameters and validates them. + + The parameters are returned as a tuple: sAction, (more later, maybe) + Note! the sTextBoxId can be None if it's a SIGNON request. + + Raises StatusDispatcherException on invalid input. + """ + # + # Get the action parameter and validate it. + # + if constants.tbreq.ALL_PARAM_ACTION not in dParams: + raise StatusDispatcherException('No "%s" parameter in request (params: %s)' + % (constants.tbreq.ALL_PARAM_ACTION, dParams,)); + sAction = dParams[constants.tbreq.ALL_PARAM_ACTION]; + + if sAction not in self._dActions: + raise StatusDispatcherException('Unknown action "%s" in request (params: %s; action: %s)' + % (sAction, dParams, self._dActions)); + # + # Update the list of checked parameters. + # + self._asCheckedParams.extend([constants.tbreq.ALL_PARAM_ACTION,]); + + return (sAction,); + + def dispatchRequest(self): + """ + Dispatches the incoming request. + + Will raise StatusDispatcherException on failure. + """ + + # + # Must be a GET request. + # + try: + sMethod = self._oSrvGlue.getMethod(); + except Exception as oXcpt: + raise StatusDispatcherException('Error retriving request method: %s' % (oXcpt,)); + if sMethod != 'GET': + raise StatusDispatcherException('Error expected POST request not "%s"' % (sMethod,)); + + # + # Get the parameters and checks for duplicates. + # + try: + dParams = self._oSrvGlue.getParameters(); + except Exception as oXcpt: + raise StatusDispatcherException('Error retriving parameters: %s' % (oXcpt,)); + for sKey in dParams.keys(): + if len(dParams[sKey]) > 1: + raise StatusDispatcherException('Parameter "%s" is given multiple times: %s' % (sKey, dParams[sKey])); + dParams[sKey] = dParams[sKey][0]; + self._dParams = dParams; + + # + # Get+validate the standard action parameters and dispatch the request. + # + (self._sAction, ) = self._getStandardParams(dParams); + return self._dActions[self._sAction](); + + +def main(): + """ + Main function a la C/C++. Returns exit code. + """ + + oSrvGlue = WebServerGlueCgi(g_ksValidationKitDir, fHtmlOutput = False); + try: + oDisp = StatusDispatcher(oSrvGlue); + oDisp.dispatchRequest(); + oSrvGlue.flush(); + except Exception as oXcpt: + return oSrvGlue.errorPage('Internal error: %s' % (str(oXcpt),), sys.exc_info()); + + return 0; + +if __name__ == '__main__': + if config.g_kfProfileAdmin: + from testmanager.debug import cgiprofiling; + sys.exit(cgiprofiling.profileIt(main)); + else: + sys.exit(main()); + diff --git a/src/VBox/ValidationKit/testmanager/cgi/testboxdisp.py b/src/VBox/ValidationKit/testmanager/cgi/testboxdisp.py new file mode 100755 index 00000000..c02d87bc --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/cgi/testboxdisp.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: testboxdisp.py $ + +""" +CGI - TestBox Interaction (see testboxscript or the other party). +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import os +import sys + +# Only the main script needs to modify the path. +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testmanager import config; +from testmanager.core.webservergluecgi import WebServerGlueCgi; +from testmanager.core.testboxcontroller import TestBoxController; + + +def main(): + """ + Main function a la C/C++. Returns exit code. + """ + + oSrvGlue = WebServerGlueCgi(g_ksValidationKitDir, fHtmlOutput = False); + oCtrl = TestBoxController(oSrvGlue); + try: + oCtrl.dispatchRequest() + oSrvGlue.flush(); + except Exception as oXcpt: + return oSrvGlue.errorPage('Internal error: %s' % (str(oXcpt),), + sys.exc_info(), + config.g_ksTestBoxDispXpctLog); + return 0; + +if __name__ == '__main__': + sys.exit(main()); + diff --git a/src/VBox/ValidationKit/testmanager/config.py b/src/VBox/ValidationKit/testmanager/config.py new file mode 100644 index 00000000..9c49c73f --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/config.py @@ -0,0 +1,261 @@ +# -*- coding: utf-8 -*- +# $Id: config.py $ + +""" +Test Manager Configuration. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154730 $" + +import os; + +## Test Manager version string. +g_ksVersion = 'v0.1.0'; +## Test Manager revision string. +g_ksRevision = ('$Revision: 154730 $')[11:-2]; + +## Enable VBox specific stuff. +g_kfVBoxSpecific = True; + + +## @name Used by the TMDatabaseConnection class. +# @{ +g_ksDatabaseName = 'testmanager'; +g_ksDatabaseAddress = None; +g_ksDatabasePort = None; +g_ksDatabaseUser = 'postgres'; +g_ksDatabasePassword = ''; +## @} + + +## @name User handling. +## @{ + +## Whether login names are case insensitive (True) or case sensitive (False). +## @note Implemented by inserting lower case names into DB and lower case +## bind variables in WHERE clauses. +g_kfLoginNameCaseInsensitive = True; + +## @} + + +## @name File locations +## @{ + +## The TestManager directory. +g_ksTestManagerDir = os.path.dirname(os.path.abspath(__file__)); +## The Validation Kit directory. +g_ksValidationKitDir = os.path.dirname(g_ksTestManagerDir); +## The TestManager htdoc directory. +g_ksTmHtDocDir = os.path.join(g_ksTestManagerDir, 'htdocs'); +## The TestManager download directory (under htdoc somewhere), for validationkit zips. +g_ksTmDownloadDir = os.path.join(g_ksTmHtDocDir, 'download'); +## The base URL relative path of the TM download directory (g_ksTmDownloadDir). +g_ksTmDownloadBaseUrlRel = 'htdocs/downloads'; +## The root of the file area (referred to as TM_FILE_DIR in database docs). +g_ksFileAreaRootDir = '/var/tmp/testmanager' +## The root of the file area with the zip files (best put on a big storage server). +g_ksZipFileAreaRootDir = '/var/tmp/testmanager2' +## URL prefix for trac log viewer. +g_ksTracLogUrlPrefix = 'https://linserv.de.oracle.com/vbox/log/' +## URL prefix for trac log viewer. +g_ksTracChangsetUrlFmt = 'https://linserv.de.oracle.com/%(sRepository)s/changeset/%(iRevision)s' +## URL prefix for unprefixed build logs. +g_ksBuildLogUrlPrefix = '' +## URL prefix for unprefixed build binaries. +g_ksBuildBinUrlPrefix = '/builds/' +## The local path prefix for unprefixed build binaries. (Host file system, not web server.) +g_ksBuildBinRootDir = '/mnt/builds/' +## File on the build binary share that can be used to check that it's mounted. +g_ksBuildBinRootFile = 'builds.txt' +## Template for paratial database dump output files. One argument: UID +g_ksTmDbDumpOutFileTmpl = '/var/tmp/tm-partial-db-dump-for-%u.zip' +## Template for paratial database dump temporary files. One argument: UID +g_ksTmDbDumpTmpFileTmpl = '/var/tmp/tm-partial-db-dump-for-%u.pgtxt' +## @} + + +## @name Scheduling parameters +## @{ + +## The time to wait for a gang to gather (in seconds). +g_kcSecGangGathering = 600; +## The max time allowed to spend looking for a new task (in seconds). +g_kcSecMaxNewTask = 60; +## Minimum time since last task started. +g_kcSecMinSinceLastTask = 120; # (2 min) +## Minimum time since last failed task. +g_kcSecMinSinceLastFailedTask = 180; # (3 min) + +## @} + + + +## @name Test result limits. +## In general, we will fail the test when reached and stop accepting further results. +## @{ + +## The max number of test results per test set. +g_kcMaxTestResultsPerTS = 4096; +## The max number of test results (children) per test result. +g_kcMaxTestResultsPerTR = 512; +## The max number of test result values per test set. +g_kcMaxTestValuesPerTS = 4096; +## The max number of test result values per test result. +g_kcMaxTestValuesPerTR = 256; +## The max number of test result message per test result. +g_kcMaxTestMsgsPerTR = 4; +## The max test result nesting depth. +g_kcMaxTestResultDepth = 10; + +## The max length of a test result name. +g_kcchMaxTestResultName = 64; +## The max length of a test result value name. +g_kcchMaxTestValueName = 56; +## The max length of a test result message. +g_kcchMaxTestMsg = 128; + +## The max size of the main log file. +g_kcMbMaxMainLog = 32; +## The max size of an uploaded file (individual). +g_kcMbMaxUploadSingle = 150; +## The max size of all uploaded file. +g_kcMbMaxUploadTotal = 200; +## The max number of files that can be uploaded. +g_kcMaxUploads = 256; +## @} + + +## @name Bug Trackers and VCS reference tags. +## @{ +class BugTrackerConfig(object): + """ Bug tracker config """ + def __init__(self, sDbId, sName, sBugUrl, asCommitTags): + assert len(sDbId) == 4; + self.sDbId = sDbId; + self.sName = sName; + self.sBugUrl = sBugUrl; + self.asCommitTags = asCommitTags; + +## The key is the database table +g_kdBugTrackers = { + 'xtrk': BugTrackerConfig('xtrk', 'xTracker', 'https://linserv.de.oracle.com/vbox/xTracker/index.php?bug=', + ['bugref:', '@bugref{', 'bugef:', 'bugrf:', ], ), + 'bgdb': BugTrackerConfig('bgdb', 'BugDB', 'https://bug.oraclecorp.com/pls/bug/webbug_edit.edit_info_top?rptno=', + ['bugdbref:', '@bugdbref{', 'bugdb:', ], ), + 'vorg': BugTrackerConfig('vorg', 'External Trac', 'https://www.virtualbox.org/ticket/', + ['ticketref:', '@ticketref{', 'ticket:', ], ), +}; +## @} + + + +## @name Virtual Sheriff email alerts +## @{ + +## SMTP server host name. +g_ksSmtpHost = 'internal-mail-router.oracle.com'; +## SMTP server port number. +g_kcSmtpPort = 25; +## Default email 'From' for email alert. +g_ksAlertFrom = 'vsheriff@oracle.com'; +## Subject for email alert. +g_ksAlertSubject = 'Virtual Test Sheriff Alert'; +## List of users to send alerts. +g_asAlertList = ['alertuser1', 'alertuser2']; +## iLOM password. +g_ksLomPassword = 'put_your_ILOM_password_here_if_applicable'; + +## @} + + +## @name Partial Database Dump +## @{ + +## Minimum number of day. Set higher than g_kcTmDbDumpMaxDays to disable. +g_kcTmDbDumpMinDays = 1; +## Maximum number of day. Keep low - consider space and runtime. +g_kcTmDbDumpMaxDays = 31; +## The default number of days. +g_kcTmDbDumpDefaultDays = 14; +## @} + + +## @name Debug Features +## @{ + +## Enables extra DB exception information. +g_kfDebugDbXcpt = True; + +## Where to write the glue debug. +# None indicates apache error log, string indicates a file. +#g_ksSrvGlueDebugLogDst = '/tmp/testmanager-srv-glue.log'; +g_ksSrvGlueDebugLogDst = None; +## Whether to enable CGI trace back in the server glue. +g_kfSrvGlueCgiTb = False; +## Enables glue debug output. +g_kfSrvGlueDebug = False; +## Timestamp and pid prefix the glue debug output. +g_kfSrvGlueDebugTS = True; +## Whether to dumping CGI environment variables. +g_kfSrvGlueCgiDumpEnv = False; +## Whether to dumping CGI script arguments. +g_kfSrvGlueCgiDumpArgs = False; +## Enables task scheduler debug output to g_ksSrvGlueDebugLogDst. +g_kfSrvGlueDebugScheduler = False; + +## Enables the SQL trace back. +g_kfWebUiSqlTrace = False; +## Enables the explain in the SQL trace back. +g_kfWebUiSqlTraceExplain = False; +## Whether the postgresql version supports the TIMING option on EXPLAIN (>= 9.2). +g_kfWebUiSqlTraceExplainTiming = False; +## Display time spent processing the page. +g_kfWebUiProcessedIn = True; +## Enables WebUI debug output. +g_kfWebUiDebug = False; +## Enables WebUI SQL debug output print() calls (requires g_kfWebUiDebug). +g_kfWebUiSqlDebug = False; +## Enables the debug panel at the bottom of the page. +g_kfWebUiDebugPanel = True; + +## Profile cgi/admin.py. +g_kfProfileAdmin = False; +## Profile cgi/index.py. +g_kfProfileIndex = False; + +## When not None, +g_ksTestBoxDispXpctLog = '/tmp/testmanager-testboxdisp-xcpt.log' +## @} + diff --git a/src/VBox/ValidationKit/testmanager/core/Makefile.kmk b/src/VBox/ValidationKit/testmanager/core/Makefile.kmk new file mode 100644 index 00000000..1de2d1dc --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/Makefile.kmk @@ -0,0 +1,46 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(wildcard $(PATH_SUB_CURRENT)/*.py) + +$(evalcall def_vbox_validationkit_process_python_sources) +$(evalcall def_vbox_validationkit_process_js_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/testmanager/core/__init__.py b/src/VBox/ValidationKit/testmanager/core/__init__.py new file mode 100644 index 00000000..6422d660 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/__init__.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# $Id: __init__.py $ + +""" +TestBox Script - Core Logic. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + diff --git a/src/VBox/ValidationKit/testmanager/core/base.py b/src/VBox/ValidationKit/testmanager/core/base.py new file mode 100755 index 00000000..14c3be2f --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/base.py @@ -0,0 +1,1514 @@ +# -*- coding: utf-8 -*- +# $Id: base.py $ +# pylint: disable=too-many-lines + +""" +Test Manager Core - Base Class(es). +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard python imports. +import copy; +import datetime; +import json; +import re; +import socket; +import sys; +import uuid; +import unittest; + +# Validation Kit imports. +from common import utils; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int # pylint: disable=redefined-builtin,invalid-name + + +class TMExceptionBase(Exception): + """ + For exceptions raised by any TestManager component. + """ + pass; # pylint: disable=unnecessary-pass + + +class TMTooManyRows(TMExceptionBase): + """ + Too many rows in the result. + Used by ModelLogicBase decendants. + """ + pass; # pylint: disable=unnecessary-pass + + +class TMRowNotFound(TMExceptionBase): + """ + Database row not found. + Used by ModelLogicBase decendants. + """ + pass; # pylint: disable=unnecessary-pass + + +class TMRowAlreadyExists(TMExceptionBase): + """ + Database row already exists (typically raised by addEntry). + Used by ModelLogicBase decendants. + """ + pass; # pylint: disable=unnecessary-pass + + +class TMInvalidData(TMExceptionBase): + """ + Data validation failed. + Used by ModelLogicBase decendants. + """ + pass; # pylint: disable=unnecessary-pass + + +class TMRowInUse(TMExceptionBase): + """ + Database row is in use and cannot be deleted. + Used by ModelLogicBase decendants. + """ + pass; # pylint: disable=unnecessary-pass + + +class TMInFligthCollision(TMExceptionBase): + """ + Database update failed because someone else had already made changes to + the data there. + Used by ModelLogicBase decendants. + """ + pass; # pylint: disable=unnecessary-pass + + +class ModelBase(object): # pylint: disable=too-few-public-methods + """ + Something all classes in the logical model inherits from. + + Not sure if 'logical model' is the right term here. + Will see if it has any purpose later on... + """ + + def __init__(self): + pass; + + +class ModelDataBase(ModelBase): # pylint: disable=too-few-public-methods + """ + Something all classes in the data classes in the logical model inherits from. + """ + + ## Child classes can use this to list array attributes which should use + # an empty array ([]) instead of None as database NULL value. + kasAltArrayNull = []; + + ## validate + ## @{ + ksValidateFor_Add = 'add'; + ksValidateFor_AddForeignId = 'add-foreign-id'; + ksValidateFor_Edit = 'edit'; + ksValidateFor_Other = 'other'; + ## @} + + + ## List of internal attributes which should be ignored by + ## getDataAttributes and related machinery + kasInternalAttributes = []; + + def __init__(self): + ModelBase.__init__(self); + + + # + # Standard methods implemented by combining python magic and hungarian prefixes. + # + + def getDataAttributes(self): + """ + Returns a list of data attributes. + """ + asRet = []; + asAttrs = dir(self); + for sAttr in asAttrs: + if sAttr[0] == '_' or sAttr[0] == 'k': + continue; + if sAttr in self.kasInternalAttributes: + continue; + oValue = getattr(self, sAttr); + if callable(oValue): + continue; + asRet.append(sAttr); + return asRet; + + def initFromOther(self, oOther): + """ + Initialize this object with the values from another instance (child + class instance is accepted). + + This serves as a kind of copy constructor. + + Returns self. May raise exception if the type of other object differs + or is damaged. + """ + for sAttr in self.getDataAttributes(): + setattr(self, sAttr, getattr(oOther, sAttr)); + return self; + + @staticmethod + def getHungarianPrefix(sName): + """ + Returns the hungarian prefix of the given name. + """ + for i, _ in enumerate(sName): + if sName[i] not in ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']: + assert re.search('^[A-Z][a-zA-Z0-9]*$', sName[i:]) is not None; + return sName[:i]; + return sName; + + def getAttributeParamNullValues(self, sAttr): + """ + Returns a list of parameter NULL values, with the preferred one being + the first element. + + Child classes can override this to handle one or more attributes specially. + """ + sPrefix = self.getHungarianPrefix(sAttr); + if sPrefix in ['id', 'uid', 'i', 'off', 'pct']: + return [-1, '', '-1',]; + if sPrefix in ['l', 'c',]: + return [long(-1), '', '-1',]; + if sPrefix == 'f': + return ['',]; + if sPrefix in ['enm', 'ip', 's', 'ts', 'uuid']: + return ['',]; + if sPrefix in ['ai', 'aid', 'al', 'as']: + return [[], '', None]; ## @todo ?? + if sPrefix == 'bm': + return ['', [],]; ## @todo bitmaps. + raise TMExceptionBase('Unable to classify "%s" (prefix %s)' % (sAttr, sPrefix)); + + def isAttributeNull(self, sAttr, oValue): + """ + Checks if the specified attribute value indicates NULL. + Return True/False. + + Note! This isn't entirely kosher actually. + """ + if oValue is None: + return True; + aoNilValues = self.getAttributeParamNullValues(sAttr); + return oValue in aoNilValues; + + def _convertAttributeFromParamNull(self, sAttr, oValue): + """ + Converts an attribute from parameter NULL to database NULL value. + Returns the new attribute value. + """ + aoNullValues = self.getAttributeParamNullValues(sAttr); + if oValue in aoNullValues: + oValue = None if sAttr not in self.kasAltArrayNull else []; + # + # Perform deep conversion on ModelDataBase object and lists of them. + # + elif isinstance(oValue, list) and oValue and isinstance(oValue[0], ModelDataBase): + oValue = copy.copy(oValue); + for i, _ in enumerate(oValue): + assert isinstance(oValue[i], ModelDataBase); + oValue[i] = copy.copy(oValue[i]); + oValue[i].convertFromParamNull(); + + elif isinstance(oValue, ModelDataBase): + oValue = copy.copy(oValue); + oValue.convertFromParamNull(); + + return oValue; + + def convertFromParamNull(self): + """ + Converts from parameter NULL values to database NULL values (None). + Returns self. + """ + for sAttr in self.getDataAttributes(): + oValue = getattr(self, sAttr); + oNewValue = self._convertAttributeFromParamNull(sAttr, oValue); + if oValue != oNewValue: + setattr(self, sAttr, oNewValue); + return self; + + def _convertAttributeToParamNull(self, sAttr, oValue): + """ + Converts an attribute from database NULL to a sepcial value we can pass + thru parameter list. + Returns the new attribute value. + """ + if oValue is None: + oValue = self.getAttributeParamNullValues(sAttr)[0]; + # + # Perform deep conversion on ModelDataBase object and lists of them. + # + elif isinstance(oValue, list) and oValue and isinstance(oValue[0], ModelDataBase): + oValue = copy.copy(oValue); + for i, _ in enumerate(oValue): + assert isinstance(oValue[i], ModelDataBase); + oValue[i] = copy.copy(oValue[i]); + oValue[i].convertToParamNull(); + + elif isinstance(oValue, ModelDataBase): + oValue = copy.copy(oValue); + oValue.convertToParamNull(); + + return oValue; + + def convertToParamNull(self): + """ + Converts from database NULL values (None) to special values we can + pass thru parameters list. + Returns self. + """ + for sAttr in self.getDataAttributes(): + oValue = getattr(self, sAttr); + oNewValue = self._convertAttributeToParamNull(sAttr, oValue); + if oValue != oNewValue: + setattr(self, sAttr, oNewValue); + return self; + + def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb): + """ + Validates and convert one attribute. + Returns the converted value. + + Child classes can override this to handle one or more attributes specially. + Note! oDb can be None. + """ + sPrefix = self.getHungarianPrefix(sAttr); + + if sPrefix in ['id', 'uid']: + (oNewValue, sError) = self.validateInt( oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull); + elif sPrefix in ['i', 'off', 'pct']: + (oNewValue, sError) = self.validateInt( oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull, + iMin = getattr(self, 'kiMin_' + sAttr, 0), + iMax = getattr(self, 'kiMax_' + sAttr, 0x7ffffffe)); + elif sPrefix in ['l', 'c']: + (oNewValue, sError) = self.validateLong(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull, + lMin = getattr(self, 'klMin_' + sAttr, 0), + lMax = getattr(self, 'klMax_' + sAttr, None)); + elif sPrefix == 'f': + if not oValue and not fAllowNull: oValue = '0'; # HACK ALERT! Checkboxes are only added when checked. + (oNewValue, sError) = self.validateBool(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull); + elif sPrefix == 'ts': + (oNewValue, sError) = self.validateTs( oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull); + elif sPrefix == 'ip': + (oNewValue, sError) = self.validateIp( oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull); + elif sPrefix == 'uuid': + (oNewValue, sError) = self.validateUuid(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull); + elif sPrefix == 'enm': + (oNewValue, sError) = self.validateWord(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull, + asValid = getattr(self, 'kasValidValues_' + sAttr)); # The list is required. + elif sPrefix == 's': + (oNewValue, sError) = self.validateStr( oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull, + cchMin = getattr(self, 'kcchMin_' + sAttr, 0), + cchMax = getattr(self, 'kcchMax_' + sAttr, 4096), + fAllowUnicodeSymbols = getattr(self, 'kfAllowUnicode_' + sAttr, False) ); + ## @todo al. + elif sPrefix == 'aid': + (oNewValue, sError) = self.validateListOfInts(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull, + iMin = 1, iMax = 0x7ffffffe); + elif sPrefix == 'as': + (oNewValue, sError) = self.validateListOfStr(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull, + asValidValues = getattr(self, 'kasValidValues_' + sAttr, None), + cchMin = getattr(self, 'kcchMin_' + sAttr, 0 if fAllowNull else 1), + cchMax = getattr(self, 'kcchMax_' + sAttr, 4096)); + + elif sPrefix == 'bm': + ## @todo figure out bitfields. + (oNewValue, sError) = self.validateListOfStr(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull); + else: + raise TMExceptionBase('Unable to classify "%s" (prefix %s)' % (sAttr, sPrefix)); + + _ = sParam; _ = oDb; + return (oNewValue, sError); + + def _validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor = ksValidateFor_Other): + """ + Worker for implementing validateAndConvert(). + """ + dErrors = {}; + for sAttr in self.getDataAttributes(): + oValue = getattr(self, sAttr); + sParam = getattr(self, 'ksParam_' + sAttr); + aoNilValues = self.getAttributeParamNullValues(sAttr); + aoNilValues.append(None); + + (oNewValue, sError) = self._validateAndConvertAttribute(sAttr, sParam, oValue, aoNilValues, + sAttr in asAllowNullAttributes, oDb); + if oValue != oNewValue: + setattr(self, sAttr, oNewValue); + if sError is not None: + dErrors[sParam] = sError; + + # Check the NULL requirements of the primary ID(s) for the 'add' and 'edit' actions. + if enmValidateFor in (ModelDataBase.ksValidateFor_Add, + ModelDataBase.ksValidateFor_AddForeignId, + ModelDataBase.ksValidateFor_Edit,): + fMustBeNull = enmValidateFor == ModelDataBase.ksValidateFor_Add; + sAttr = getattr(self, 'ksIdAttr', None); + if sAttr is not None: + oValue = getattr(self, sAttr); + if self.isAttributeNull(sAttr, oValue) != fMustBeNull: + sParam = getattr(self, 'ksParam_' + sAttr); + sErrMsg = 'Must be NULL!' if fMustBeNull else 'Must not be NULL!' + if sParam in dErrors: + dErrors[sParam] += ' ' + sErrMsg; + else: + dErrors[sParam] = sErrMsg; + + return dErrors; + + def validateAndConvert(self, oDb, enmValidateFor = ksValidateFor_Other): + """ + Validates the input and converts valid fields to their right type. + Returns a dictionary with per field reports, only invalid fields will + be returned, so an empty dictionary means that the data is valid. + + The dictionary keys are ksParam_*. + + Child classes can override _validateAndConvertAttribute to handle + selected fields specially. There are also a few class variables that + can be used to advice the validation: kcchMin_sAttr, kcchMax_sAttr, + kiMin_iAttr, kiMax_iAttr, klMin_lAttr, klMax_lAttr, + kasValidValues_enmAttr, and kasAllowNullAttributes. + """ + return self._validateAndConvertWorker(getattr(self, 'kasAllowNullAttributes', []), oDb, + enmValidateFor = enmValidateFor); + + def validateAndConvertEx(self, asAllowNullAttributes, oDb, enmValidateFor = ksValidateFor_Other): + """ + Same as validateAndConvert but with custom allow-null list. + """ + return self._validateAndConvertWorker(asAllowNullAttributes, oDb, enmValidateFor = enmValidateFor); + + def convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict): + """ + Calculate the attribute value when initialized from a parameter. + + Returns the new value, with parameter NULL values. Raises exception on + invalid parameter value. + + Child classes can override to do special parameter conversion jobs. + """ + sPrefix = self.getHungarianPrefix(sAttr); + asValidValues = getattr(self, 'kasValidValues_' + sAttr, None); + fAllowNull = sAttr in getattr(self, 'kasAllowNullAttributes', []); + if fStrict: + if sPrefix == 'f': + # HACK ALERT! Checkboxes are only present when checked, so we always have to provide a default. + oNewValue = oDisp.getStringParam(sParam, asValidValues, '0'); + elif sPrefix[0] == 'a': + # HACK ALERT! Lists are not present if empty. + oNewValue = oDisp.getListOfStrParams(sParam, []); + else: + oNewValue = oDisp.getStringParam(sParam, asValidValues, None, fAllowNull = fAllowNull); + else: + if sPrefix[0] == 'a': + oNewValue = oDisp.getListOfStrParams(sParam, []); + else: + assert oValue is not None, 'sAttr=%s' % (sAttr,); + oNewValue = oDisp.getStringParam(sParam, asValidValues, oValue, fAllowNull = fAllowNull); + return oNewValue; + + def initFromParams(self, oDisp, fStrict = True): + """ + Initialize the object from parameters. + The input is not validated at all, except that all parameters must be + present when fStrict is True. + + Returns self. Raises exception on invalid parameter value. + + Note! The returned object has parameter NULL values, not database ones! + """ + + self.convertToParamNull() + for sAttr in self.getDataAttributes(): + oValue = getattr(self, sAttr); + oNewValue = self.convertParamToAttribute(sAttr, getattr(self, 'ksParam_' + sAttr), oValue, oDisp, fStrict); + if oNewValue != oValue: + setattr(self, sAttr, oNewValue); + return self; + + def areAttributeValuesEqual(self, sAttr, sPrefix, oValue1, oValue2): + """ + Called to compare two attribute values and python thinks differs. + + Returns True/False. + + Child classes can override this to do special compares of things like arrays. + """ + # Just in case someone uses it directly. + if oValue1 == oValue2: + return True; + + # + # Timestamps can be both string (param) and object (db) + # depending on the data source. Compare string values to make + # sure we're doing the right thing here. + # + if sPrefix == 'ts': + return str(oValue1) == str(oValue2); + + # + # Some generic code handling ModelDataBase children. + # + if isinstance(oValue1, list) and isinstance(oValue2, list): + if len(oValue1) == len(oValue2): + for i, _ in enumerate(oValue1): + if not isinstance(oValue1[i], ModelDataBase) \ + or type(oValue1) is not type(oValue2): + return False; + if not oValue1[i].isEqual(oValue2[i]): + return False; + return True; + + elif isinstance(oValue1, ModelDataBase) \ + and type(oValue1) is type(oValue2): + return oValue1[i].isEqual(oValue2[i]); + + _ = sAttr; + return False; + + def isEqual(self, oOther): + """ Compares two instances. """ + for sAttr in self.getDataAttributes(): + if getattr(self, sAttr) != getattr(oOther, sAttr): + # Delegate the final decision to an overridable method. + if not self.areAttributeValuesEqual(sAttr, self.getHungarianPrefix(sAttr), + getattr(self, sAttr), getattr(oOther, sAttr)): + return False; + return True; + + def isEqualEx(self, oOther, asExcludeAttrs): + """ Compares two instances, omitting the given attributes. """ + for sAttr in self.getDataAttributes(): + if sAttr not in asExcludeAttrs \ + and getattr(self, sAttr) != getattr(oOther, sAttr): + # Delegate the final decision to an overridable method. + if not self.areAttributeValuesEqual(sAttr, self.getHungarianPrefix(sAttr), + getattr(self, sAttr), getattr(oOther, sAttr)): + return False; + return True; + + def reinitToNull(self): + """ + Reinitializes the object to (database) NULL values. + Returns self. + """ + for sAttr in self.getDataAttributes(): + setattr(self, sAttr, None); + return self; + + def toString(self): + """ + Stringifies the object. + Returns string representation. + """ + + sMembers = ''; + for sAttr in self.getDataAttributes(): + oValue = getattr(self, sAttr); + sMembers += ', %s=%s' % (sAttr, oValue); + + oClass = type(self); + if sMembers == '': + return '<%s>' % (oClass.__name__); + return '<%s: %s>' % (oClass.__name__, sMembers[2:]); + + def __str__(self): + return self.toString(); + + + + # + # New validation helpers. + # + # These all return (oValue, sError), where sError is None when the value + # is valid and an error message when not. On success and in case of + # range errors, oValue is converted into the requested type. + # + + @staticmethod + def validateInt(sValue, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([-1, None, '']), fAllowNull = True): + """ Validates an integer field. """ + if sValue in aoNilValues: + if fAllowNull: + return (None if sValue is None else aoNilValues[0], None); + return (sValue, 'Mandatory.'); + + try: + if utils.isString(sValue): + iValue = int(sValue, 0); + else: + iValue = int(sValue); + except: + return (sValue, 'Not an integer'); + + if iValue in aoNilValues: + return (aoNilValues[0], None if fAllowNull else 'Mandatory.'); + + if iValue < iMin: + return (iValue, 'Value too small (min %d)' % (iMin,)); + if iValue > iMax: + return (iValue, 'Value too high (max %d)' % (iMax,)); + return (iValue, None); + + @staticmethod + def validateLong(sValue, lMin = 0, lMax = None, aoNilValues = tuple([long(-1), None, '']), fAllowNull = True): + """ Validates an long integer field. """ + if sValue in aoNilValues: + if fAllowNull: + return (None if sValue is None else aoNilValues[0], None); + return (sValue, 'Mandatory.'); + try: + if utils.isString(sValue): + lValue = long(sValue, 0); + else: + lValue = long(sValue); + except: + return (sValue, 'Not a long integer'); + + if lValue in aoNilValues: + return (aoNilValues[0], None if fAllowNull else 'Mandatory.'); + + if lMin is not None and lValue < lMin: + return (lValue, 'Value too small (min %d)' % (lMin,)); + if lMax is not None and lValue > lMax: + return (lValue, 'Value too high (max %d)' % (lMax,)); + return (lValue, None); + + kdTimestampRegex = { + len('2012-10-08 01:54:06'): r'(\d{4})-([01]\d)-([0123]\d)[ Tt]([012]\d):[0-5]\d:([0-6]\d)$', + len('2012-10-08 01:54:06.00'): r'(\d{4})-([01]\d)-([0123]\d)[ Tt]([012]\d):[0-5]\d:([0-6]\d).\d{2}$', + len('2012-10-08 01:54:06.000'): r'(\d{4})-([01]\d)-([0123]\d)[ Tt]([012]\d):[0-5]\d:([0-6]\d).\d{3}$', + len('999999-12-31 00:00:00.00'): r'(\d{6})-([01]\d)-([0123]\d)[ Tt]([012]\d):[0-5]\d:([0-6]\d).\d{2}$', + len('9999-12-31 23:59:59.999999'): r'(\d{4})-([01]\d)-([0123]\d)[ Tt]([012]\d):[0-5]\d:([0-6]\d).\d{6}$', + len('9999-12-31T23:59:59.999999999'): r'(\d{4})-([01]\d)-([0123]\d)[ Tt]([012]\d):[0-5]\d:([0-6]\d).\d{9}$', + }; + + @staticmethod + def validateTs(sValue, aoNilValues = tuple([None, '']), fAllowNull = True, fRelative = False): + """ Validates a timestamp field. """ + if sValue in aoNilValues: + return (sValue, None if fAllowNull else 'Mandatory.'); + if not utils.isString(sValue): + return (sValue, None); + + # Validate and strip off the timezone stuff. + if sValue[-1] in 'Zz': + sStripped = sValue[:-1]; + sValue = sStripped + 'Z'; + elif len(sValue) >= 19 + 3: + oRes = re.match(r'^.*[+-](\d\d):(\d\d)$', sValue); + if oRes is not None: + if int(oRes.group(1)) > 12 or int(oRes.group(2)) >= 60: + return (sValue, 'Invalid timezone offset.'); + sStripped = sValue[:-6]; + else: + sStripped = sValue; + else: + sStripped = sValue; + + # Used the stripped value length to find regular expression for validating and parsing the timestamp. + sError = None; + sRegExp = ModelDataBase.kdTimestampRegex.get(len(sStripped), None); + if sRegExp: + oRes = re.match(sRegExp, sStripped); + if oRes is not None: + iYear = int(oRes.group(1)); + if iYear % 4 == 0 and (iYear % 100 != 0 or iYear % 400 == 0): + acDaysOfMonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + else: + acDaysOfMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + iMonth = int(oRes.group(2)); + iDay = int(oRes.group(3)); + iHour = int(oRes.group(4)); + iSec = int(oRes.group(5)); + if iMonth > 12 or (iMonth <= 0 and not fRelative): + sError = 'Invalid timestamp month.'; + elif iDay > acDaysOfMonth[iMonth - 1]: + sError = 'Invalid timestamp day-of-month (%02d has %d days).' % (iMonth, acDaysOfMonth[iMonth - 1]); + elif iHour > 23: + sError = 'Invalid timestamp hour.' + elif iSec >= 61: + sError = 'Invalid timestamp second.' + elif iSec >= 60: + sError = 'Invalid timestamp: no leap seconds, please.' + else: + sError = 'Invalid timestamp (validation regexp: %s).' % (sRegExp,); + else: + sError = 'Invalid timestamp length.'; + return (sValue, sError); + + @staticmethod + def validateIp(sValue, aoNilValues = tuple([None, '']), fAllowNull = True): + """ Validates an IP address field. """ + if sValue in aoNilValues: + return (sValue, None if fAllowNull else 'Mandatory.'); + + if sValue == '::1': + return (sValue, None); + + try: + socket.inet_pton(socket.AF_INET, sValue); # pylint: disable=no-member + except: + try: + socket.inet_pton(socket.AF_INET6, sValue); # pylint: disable=no-member + except: + return (sValue, 'Not a valid IP address.'); + + return (sValue, None); + + @staticmethod + def validateBool(sValue, aoNilValues = tuple([None, '']), fAllowNull = True): + """ Validates a boolean field. """ + if sValue in aoNilValues: + return (sValue, None if fAllowNull else 'Mandatory.'); + + if sValue in ('True', 'true', '1', True): + return (True, None); + if sValue in ('False', 'false', '0', False): + return (False, None); + return (sValue, 'Invalid boolean value.'); + + @staticmethod + def validateUuid(sValue, aoNilValues = tuple([None, '']), fAllowNull = True): + """ Validates an UUID field. """ + if sValue in aoNilValues: + return (sValue, None if fAllowNull else 'Mandatory.'); + + try: + sValue = str(uuid.UUID(sValue)); + except: + return (sValue, 'Invalid UUID value.'); + return (sValue, None); + + @staticmethod + def validateWord(sValue, cchMin = 1, cchMax = 64, asValid = None, aoNilValues = tuple([None, '']), fAllowNull = True): + """ Validates a word field. """ + if sValue in aoNilValues: + return (sValue, None if fAllowNull else 'Mandatory.'); + + if re.search('[^a-zA-Z0-9_-]', sValue) is not None: + sError = 'Single word ([a-zA-Z0-9_-]), please.'; + elif cchMin is not None and len(sValue) < cchMin: + sError = 'Too short, min %s chars' % (cchMin,); + elif cchMax is not None and len(sValue) > cchMax: + sError = 'Too long, max %s chars' % (cchMax,); + elif asValid is not None and sValue not in asValid: + sError = 'Invalid value "%s", must be one of: %s' % (sValue, asValid); + else: + sError = None; + return (sValue, sError); + + @staticmethod + def validateStr(sValue, cchMin = 0, cchMax = 4096, aoNilValues = tuple([None, '']), fAllowNull = True, + fAllowUnicodeSymbols = False): + """ Validates a string field. """ + if sValue in aoNilValues: + return (sValue, None if fAllowNull else 'Mandatory.'); + + if cchMin is not None and len(sValue) < cchMin: + sError = 'Too short, min %s chars' % (cchMin,); + elif cchMax is not None and len(sValue) > cchMax: + sError = 'Too long, max %s chars' % (cchMax,); + elif fAllowUnicodeSymbols is False and utils.hasNonAsciiCharacters(sValue): + sError = 'Non-ascii characters not allowed' + else: + sError = None; + return (sValue, sError); + + @staticmethod + def validateEmail(sValue, aoNilValues = tuple([None, '']), fAllowNull = True): + """ Validates a email field.""" + if sValue in aoNilValues: + return (sValue, None if fAllowNull else 'Mandatory.'); + + if re.match(r'.+@.+\..+', sValue) is None: + return (sValue,'Invalid e-mail format.'); + return (sValue, None); + + @staticmethod + def validateListOfSomething(asValues, aoNilValues = tuple([[], None]), fAllowNull = True): + """ Validate a list of some uniform values. Returns a copy of the list (if list it is). """ + if asValues in aoNilValues or (not asValues and not fAllowNull): + return (asValues, None if fAllowNull else 'Mandatory.') + + if not isinstance(asValues, list): + return (asValues, 'Invalid data type (%s).' % (type(asValues),)); + + asValues = list(asValues); # copy the list. + if asValues: + oType = type(asValues[0]); + for i in range(1, len(asValues)): + if type(asValues[i]) is not oType: # pylint: disable=unidiomatic-typecheck + return (asValues, 'Invalid entry data type ([0]=%s vs [%d]=%s).' % (oType, i, type(asValues[i])) ); + + return (asValues, None); + + @staticmethod + def validateListOfStr(asValues, cchMin = None, cchMax = None, asValidValues = None, + aoNilValues = tuple([[], None]), fAllowNull = True): + """ Validates a list of text items.""" + (asValues, sError) = ModelDataBase.validateListOfSomething(asValues, aoNilValues, fAllowNull); + + if sError is None and asValues not in aoNilValues and asValues: + if not utils.isString(asValues[0]): + return (asValues, 'Invalid item data type.'); + + if not fAllowNull and cchMin is None: + cchMin = 1; + + for sValue in asValues: + if asValidValues is not None and sValue not in asValidValues: + sThisErr = 'Invalid value "%s".' % (sValue,); + elif cchMin is not None and len(sValue) < cchMin: + sThisErr = 'Value "%s" is too short, min length is %u chars.' % (sValue, cchMin); + elif cchMax is not None and len(sValue) > cchMax: + sThisErr = 'Value "%s" is too long, max length is %u chars.' % (sValue, cchMax); + else: + continue; + + if sError is None: + sError = sThisErr; + else: + sError += ' ' + sThisErr; + + return (asValues, sError); + + @staticmethod + def validateListOfInts(asValues, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([[], None]), fAllowNull = True): + """ Validates a list of integer items.""" + (asValues, sError) = ModelDataBase.validateListOfSomething(asValues, aoNilValues, fAllowNull); + + if sError is None and asValues not in aoNilValues and asValues: + for i, _ in enumerate(asValues): + sValue = asValues[i]; + + sThisErr = ''; + try: + iValue = int(sValue); + except: + sThisErr = 'Invalid integer value "%s".' % (sValue,); + else: + asValues[i] = iValue; + if iValue < iMin: + sThisErr = 'Value %d is too small (min %d)' % (iValue, iMin,); + elif iValue > iMax: + sThisErr = 'Value %d is too high (max %d)' % (iValue, iMax,); + else: + continue; + + if sError is None: + sError = sThisErr; + else: + sError += ' ' + sThisErr; + + return (asValues, sError); + + + + # + # Old validation helpers. + # + + @staticmethod + def _validateInt(dErrors, sName, sValue, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([-1, None, ''])): + """ Validates an integer field. """ + (sValue, sError) = ModelDataBase.validateInt(sValue, iMin, iMax, aoNilValues, fAllowNull = True); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + @staticmethod + def _validateIntNN(dErrors, sName, sValue, iMin = 0, iMax = 0x7ffffffe, aoNilValues = tuple([-1, None, ''])): + """ Validates an integer field, not null. """ + (sValue, sError) = ModelDataBase.validateInt(sValue, iMin, iMax, aoNilValues, fAllowNull = False); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + @staticmethod + def _validateLong(dErrors, sName, sValue, lMin = 0, lMax = None, aoNilValues = tuple([long(-1), None, ''])): + """ Validates an long integer field. """ + (sValue, sError) = ModelDataBase.validateLong(sValue, lMin, lMax, aoNilValues, fAllowNull = False); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + @staticmethod + def _validateLongNN(dErrors, sName, sValue, lMin = 0, lMax = None, aoNilValues = tuple([long(-1), None, ''])): + """ Validates an long integer field, not null. """ + (sValue, sError) = ModelDataBase.validateLong(sValue, lMin, lMax, aoNilValues, fAllowNull = True); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + @staticmethod + def _validateTs(dErrors, sName, sValue): + """ Validates a timestamp field. """ + (sValue, sError) = ModelDataBase.validateTs(sValue, fAllowNull = True); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + @staticmethod + def _validateTsNN(dErrors, sName, sValue): + """ Validates a timestamp field, not null. """ + (sValue, sError) = ModelDataBase.validateTs(sValue, fAllowNull = False); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + @staticmethod + def _validateIp(dErrors, sName, sValue): + """ Validates an IP address field. """ + (sValue, sError) = ModelDataBase.validateIp(sValue, fAllowNull = True); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + @staticmethod + def _validateIpNN(dErrors, sName, sValue): + """ Validates an IP address field, not null. """ + (sValue, sError) = ModelDataBase.validateIp(sValue, fAllowNull = False); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + @staticmethod + def _validateBool(dErrors, sName, sValue): + """ Validates a boolean field. """ + (sValue, sError) = ModelDataBase.validateBool(sValue, fAllowNull = True); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + @staticmethod + def _validateBoolNN(dErrors, sName, sValue): + """ Validates a boolean field, not null. """ + (sValue, sError) = ModelDataBase.validateBool(sValue, fAllowNull = False); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + @staticmethod + def _validateUuid(dErrors, sName, sValue): + """ Validates an UUID field. """ + (sValue, sError) = ModelDataBase.validateUuid(sValue, fAllowNull = True); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + @staticmethod + def _validateUuidNN(dErrors, sName, sValue): + """ Validates an UUID field, not null. """ + (sValue, sError) = ModelDataBase.validateUuid(sValue, fAllowNull = False); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + @staticmethod + def _validateWord(dErrors, sName, sValue, cchMin = 1, cchMax = 64, asValid = None): + """ Validates a word field. """ + (sValue, sError) = ModelDataBase.validateWord(sValue, cchMin, cchMax, asValid, fAllowNull = True); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + @staticmethod + def _validateWordNN(dErrors, sName, sValue, cchMin = 1, cchMax = 64, asValid = None): + """ Validates a boolean field, not null. """ + (sValue, sError) = ModelDataBase.validateWord(sValue, cchMin, cchMax, asValid, fAllowNull = False); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + @staticmethod + def _validateStr(dErrors, sName, sValue, cchMin = 0, cchMax = 4096): + """ Validates a string field. """ + (sValue, sError) = ModelDataBase.validateStr(sValue, cchMin, cchMax, fAllowNull = True); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + @staticmethod + def _validateStrNN(dErrors, sName, sValue, cchMin = 0, cchMax = 4096): + """ Validates a string field, not null. """ + (sValue, sError) = ModelDataBase.validateStr(sValue, cchMin, cchMax, fAllowNull = False); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + @staticmethod + def _validateEmail(dErrors, sName, sValue): + """ Validates a email field.""" + (sValue, sError) = ModelDataBase.validateEmail(sValue, fAllowNull = True); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + @staticmethod + def _validateEmailNN(dErrors, sName, sValue): + """ Validates a email field.""" + (sValue, sError) = ModelDataBase.validateEmail(sValue, fAllowNull = False); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + @staticmethod + def _validateListOfStr(dErrors, sName, asValues, asValidValues = None): + """ Validates a list of text items.""" + (sValue, sError) = ModelDataBase.validateListOfStr(asValues, asValidValues = asValidValues, fAllowNull = True); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + @staticmethod + def _validateListOfStrNN(dErrors, sName, asValues, asValidValues = None): + """ Validates a list of text items, not null and len >= 1.""" + (sValue, sError) = ModelDataBase.validateListOfStr(asValues, asValidValues = asValidValues, fAllowNull = False); + if sError is not None: + dErrors[sName] = sError; + return sValue; + + # + # Various helpers. + # + + @staticmethod + def formatSimpleNowAndPeriod(oDb, tsNow = None, sPeriodBack = None, + sTablePrefix = '', sExpCol = 'tsExpire', sEffCol = 'tsEffective'): + """ + Formats a set of tsNow and sPeriodBack arguments for a standard testmanager + table. + + If sPeriodBack is given, the query is effective for the period + (tsNow - sPeriodBack) thru (tsNow). + + If tsNow isn't given, it defaults to current time. + + Returns the final portion of a WHERE query (start with AND) and maybe an + ORDER BY and LIMIT bit if sPeriodBack is given. + """ + if tsNow is not None: + if sPeriodBack is not None: + sRet = oDb.formatBindArgs(' AND ' + sTablePrefix + sExpCol + ' > (%s::timestamp - %s::interval)\n' + ' AND tsEffective <= %s\n' + 'ORDER BY ' + sTablePrefix + sExpCol + ' DESC\n' + 'LIMIT 1\n' + , ( tsNow, sPeriodBack, tsNow)); + else: + sRet = oDb.formatBindArgs(' AND ' + sTablePrefix + sExpCol + ' > %s\n' + ' AND ' + sTablePrefix + sEffCol + ' <= %s\n' + , ( tsNow, tsNow, )); + else: + if sPeriodBack is not None: + sRet = oDb.formatBindArgs(' AND ' + sTablePrefix + sExpCol + ' > (CURRENT_TIMESTAMP - %s::interval)\n' + ' AND ' + sTablePrefix + sEffCol + ' <= CURRENT_TIMESTAMP\n' + 'ORDER BY ' + sTablePrefix + sExpCol + ' DESC\n' + 'LIMIT 1\n' + , ( sPeriodBack, )); + else: + sRet = ' AND ' + sTablePrefix + sExpCol + ' = \'infinity\'::timestamp\n'; + return sRet; + + @staticmethod + def formatSimpleNowAndPeriodQuery(oDb, sQuery, aBindArgs, tsNow = None, sPeriodBack = None, + sTablePrefix = '', sExpCol = 'tsExpire', sEffCol = 'tsEffective'): + """ + Formats a simple query for a standard testmanager table with optional + tsNow and sPeriodBack arguments. + + The sQuery and sBindArgs are passed along to oDb.formatBindArgs to form + the first part of the query. Must end with an open WHERE statement as + we'll be adding the time part starting with 'AND something...'. + + See formatSimpleNowAndPeriod for tsNow and sPeriodBack description. + + Returns the final portion of a WHERE query (start with AND) and maybe an + ORDER BY and LIMIT bit if sPeriodBack is given. + + """ + return oDb.formatBindArgs(sQuery, aBindArgs) \ + + ModelDataBase.formatSimpleNowAndPeriod(oDb, tsNow, sPeriodBack, sTablePrefix, sExpCol, sEffCol); + + + # + # JSON + # + + @staticmethod + def stringToJson(sString): + """ Converts a string to a JSON value string. """ + if not utils.isString(sString): + sString = utils.toUnicode(sString); + if not utils.isString(sString): + sString = str(sString); + return json.dumps(sString); + + @staticmethod + def dictToJson(dDict, dOptions = None): + """ Converts a dictionary to a JSON string. """ + sJson = u'{ '; + for i, oKey in enumerate(dDict): + if i > 0: + sJson += ', '; + sJson += '%s: %s' % (ModelDataBase.stringToJson(oKey), + ModelDataBase.genericToJson(dDict[oKey], dOptions)); + return sJson + ' }'; + + @staticmethod + def listToJson(aoList, dOptions = None): + """ Converts list of something to a JSON string. """ + sJson = u'[ '; + for i, oValue in enumerate(aoList): + if i > 0: + sJson += u', '; + sJson += ModelDataBase.genericToJson(oValue, dOptions); + return sJson + u' ]'; + + @staticmethod + def datetimeToJson(oDateTime): + """ Converts a datetime instance to a JSON string. """ + return '"%s"' % (oDateTime,); + + + @staticmethod + def genericToJson(oValue, dOptions = None): + """ Converts a generic object to a JSON string. """ + if isinstance(oValue, ModelDataBase): + return oValue.toJson(); + if isinstance(oValue, dict): + return ModelDataBase.dictToJson(oValue, dOptions); + if isinstance(oValue, (list, tuple, set, frozenset)): + return ModelDataBase.listToJson(oValue, dOptions); + if isinstance(oValue, datetime.datetime): + return ModelDataBase.datetimeToJson(oValue) + return json.dumps(oValue); + + def attribValueToJson(self, sAttr, oValue, dOptions = None): + """ + Converts the attribute value to JSON. + Returns JSON (string). + """ + _ = sAttr; + return self.genericToJson(oValue, dOptions); + + def toJson(self, dOptions = None): + """ + Converts the object to JSON. + Returns JSON (string). + """ + sJson = u'{ '; + for iAttr, sAttr in enumerate(self.getDataAttributes()): + oValue = getattr(self, sAttr); + if iAttr > 0: + sJson += ', '; + sJson += u'"%s": ' % (sAttr,); + sJson += self.attribValueToJson(sAttr, oValue, dOptions); + return sJson + u' }'; + + + # + # Sub-classes. + # + + class DispWrapper(object): + """Proxy object.""" + def __init__(self, oDisp, sAttrFmt): + self.oDisp = oDisp; + self.sAttrFmt = sAttrFmt; + def getStringParam(self, sName, asValidValues = None, sDefault = None, fAllowNull = False): + """See WuiDispatcherBase.getStringParam.""" + return self.oDisp.getStringParam(self.sAttrFmt % (sName,), asValidValues, sDefault, fAllowNull = fAllowNull); + def getListOfStrParams(self, sName, asDefaults = None): + """See WuiDispatcherBase.getListOfStrParams.""" + return self.oDisp.getListOfStrParams(self.sAttrFmt % (sName,), asDefaults); + def getListOfIntParams(self, sName, iMin = None, iMax = None, aiDefaults = None): + """See WuiDispatcherBase.getListOfIntParams.""" + return self.oDisp.getListOfIntParams(self.sAttrFmt % (sName,), iMin, iMax, aiDefaults); + + + + +# pylint: disable=no-member,missing-docstring,too-few-public-methods +class ModelDataBaseTestCase(unittest.TestCase): + """ + Base testcase for ModelDataBase decendants. + Derive from this and override setUp. + """ + + def setUp(self): + """ + Override this! Don't call super! + The subclasses are expected to set aoSamples to an array of instance + samples. The first entry must be a default object, the subsequent ones + are optional and their contents freely choosen. + """ + self.aoSamples = [ModelDataBase(),]; + + def testEquality(self): + for oSample in self.aoSamples: + self.assertEqual(oSample.isEqual(copy.copy(oSample)), True); + self.assertIsNotNone(oSample.isEqual(self.aoSamples[0])); + + def testNullConversion(self): + if not self.aoSamples[0].getDataAttributes(): + return; + for oSample in self.aoSamples: + oCopy = copy.copy(oSample); + self.assertEqual(oCopy.convertToParamNull(), oCopy); + self.assertEqual(oCopy.isEqual(oSample), False); + self.assertEqual(oCopy.convertFromParamNull(), oCopy); + self.assertEqual(oCopy.isEqual(oSample), True, '\ngot : %s\nexpected: %s' % (oCopy, oSample,)); + + oCopy = copy.copy(oSample); + self.assertEqual(oCopy.convertToParamNull(), oCopy); + oCopy2 = copy.copy(oCopy); + self.assertEqual(oCopy.convertToParamNull(), oCopy); + self.assertEqual(oCopy.isEqual(oCopy2), True); + self.assertEqual(oCopy.convertToParamNull(), oCopy); + self.assertEqual(oCopy.isEqual(oCopy2), True); + + oCopy = copy.copy(oSample); + self.assertEqual(oCopy.convertFromParamNull(), oCopy); + oCopy2 = copy.copy(oCopy); + self.assertEqual(oCopy.convertFromParamNull(), oCopy); + self.assertEqual(oCopy.isEqual(oCopy2), True); + self.assertEqual(oCopy.convertFromParamNull(), oCopy); + self.assertEqual(oCopy.isEqual(oCopy2), True); + + def testReinitToNull(self): + oFirst = copy.copy(self.aoSamples[0]); + self.assertEqual(oFirst.reinitToNull(), oFirst); + for oSample in self.aoSamples: + oCopy = copy.copy(oSample); + self.assertEqual(oCopy.reinitToNull(), oCopy); + self.assertEqual(oCopy.isEqual(oFirst), True); + + def testValidateAndConvert(self): + for oSample in self.aoSamples: + oCopy = copy.copy(oSample); + oCopy.convertToParamNull(); + dError1 = oCopy.validateAndConvert(None); + + oCopy2 = copy.copy(oCopy); + self.assertEqual(oCopy.validateAndConvert(None), dError1); + self.assertEqual(oCopy.isEqual(oCopy2), True); + + def testInitFromParams(self): + class DummyDisp(object): + def getStringParam(self, sName, asValidValues = None, sDefault = None, fAllowNull = False): + _ = sName; _ = asValidValues; _ = fAllowNull; + return sDefault; + def getListOfStrParams(self, sName, asDefaults = None): + _ = sName; + return asDefaults; + def getListOfIntParams(self, sName, iMin = None, iMax = None, aiDefaults = None): + _ = sName; _ = iMin; _ = iMax; + return aiDefaults; + + for oSample in self.aoSamples: + oCopy = copy.copy(oSample); + self.assertEqual(oCopy.initFromParams(DummyDisp(), fStrict = False), oCopy); + + def testToString(self): + for oSample in self.aoSamples: + self.assertIsNotNone(oSample.toString()); + + +class FilterCriterionValueAndDescription(object): + """ + A filter criterion value and its description. + """ + + def __init__(self, oValue, sDesc, cTimes = None, sHover = None, fIrrelevant = False): + self.oValue = oValue; ##< Typically the ID of something in the database. + self.sDesc = sDesc; ##< What to display. + self.cTimes = cTimes; ##< Number of times the value occurs in the result set. None if not given. + self.sHover = sHover; ##< Optional hover/title string. + self.fIrrelevant = fIrrelevant; ##< Irrelevant filter option, only present because it's selected + self.aoSubs = []; ##< References to FilterCriterion.oSub.aoPossible. + + +class FilterCriterion(object): + """ + A filter criterion. + """ + + ## @name The state. + ## @{ + ksState_NotSelected = 'not-selected'; + ksState_Selected = 'selected'; + ## @} + + ## @name The kind of filtering. + ## @{ + ## 'Element of' by default, 'not an element of' when fInverted is False. + ksKind_ElementOfOrNot = 'element-of-or-not'; + ## The criterion is a special one and cannot be inverted. + ksKind_Special = 'special'; + ## @} + + ## @name The value type. + ## @{ + ksType_UInt = 'uint'; ##< unsigned integer value. + ksType_UIntNil = 'uint-nil'; ##< unsigned integer value, with nil. + ksType_String = 'string'; ##< string value. + ksType_Ranges = 'ranges'; ##< List of (unsigned) integer ranges. + ## @} + + def __init__(self, sName, sVarNm = None, sType = ksType_UInt, # pylint: disable=too-many-arguments + sState = ksState_NotSelected, sKind = ksKind_ElementOfOrNot, + sTable = None, sColumn = None, asTables = None, oSub = None): + assert len(sVarNm) == 2; # required by wuimain.py for filtering. + self.sName = sName; + self.sState = sState; + self.sType = sType; + self.sKind = sKind; + self.sVarNm = sVarNm; + self.aoSelected = []; ##< User input from sVarNm. Single value, type according to sType. + self.sInvVarNm = 'i' + sVarNm if sKind == self.ksKind_ElementOfOrNot else None; + self.fInverted = False; ##< User input from sInvVarNm. Inverts the operation (-> not an element of). + self.aoPossible = []; ##< type: list[FilterCriterionValueAndDescription] + assert (sTable is None and asTables is None) or ((sTable is not None) != (asTables is not None)), \ + '%s %s' % (sTable, asTables); + self.asTables = [sTable,] if sTable is not None else asTables; + assert sColumn is None or len(self.asTables) == 1, '%s %s' % (self.asTables, sColumn); + self.sColumn = sColumn; ##< Normally only applicable if one table. + self.fExpanded = None; ##< Tristate (None, False, True) + self.oSub = oSub; ##< type: FilterCriterion + + +class ModelFilterBase(ModelBase): + """ + Base class for filters. + + Filters are used to narrow down data that is displayed in a list or + report. This class differs a little from ModelDataBase in that it is not + tied to a database table, but one or more database queries that are + typically rather complicated. + + The filter object has two roles: + + 1. It is used by a ModelLogicBase descendant to store the available + filtering options for data begin displayed. + + 2. It decodes and stores the filtering options submitted by the user so + a ModeLogicBase descendant can use it to construct WHERE statements. + + The ModelFilterBase class is related to the ModelDataBase class in that it + decodes user parameters and stores data, however it is not a descendant. + + Note! In order to reduce URL lengths, we use very very brief parameter + names for the filters. + """ + + def __init__(self): + ModelBase.__init__(self); + self.aCriteria = [] # type: list[FilterCriterion] + + def _initFromParamsWorker(self, oDisp, oCriterion): # (,FilterCriterion) + """ Worker for initFromParams. """ + if oCriterion.sType == FilterCriterion.ksType_UInt: + oCriterion.aoSelected = oDisp.getListOfIntParams(oCriterion.sVarNm, iMin = 0, aiDefaults = []); + elif oCriterion.sType == FilterCriterion.ksType_UIntNil: + oCriterion.aoSelected = oDisp.getListOfIntParams(oCriterion.sVarNm, iMin = -1, aiDefaults = []); + elif oCriterion.sType == FilterCriterion.ksType_String: + oCriterion.aoSelected = oDisp.getListOfStrParams(oCriterion.sVarNm, asDefaults = []); + if len(oCriterion.aoSelected) > 100: + raise TMExceptionBase('Variable %s has %u value, max allowed is 100!' + % (oCriterion.sVarNm, len(oCriterion.aoSelected))); + for sValue in oCriterion.aoSelected: + if len(sValue) > 64 \ + or '\'' in sValue \ + or sValue[-1] == '\\': + raise TMExceptionBase('Variable %s has an illegal value "%s"!' % (oCriterion.sVarNm, sValue)); + elif oCriterion.sType == FilterCriterion.ksType_Ranges: + def convertRangeNumber(sValue): + """ Helper """ + sValue = sValue.strip(); + if sValue and sValue not in ('inf', 'Inf', 'INf', 'INF', 'InF', 'iNf', 'iNF', 'inF',): + try: return int(sValue); + except: pass; + return None; + + for sRange in oDisp.getStringParam(oCriterion.sVarNm, sDefault = '').split(','): + sRange = sRange.strip(); + if sRange and sRange != '-' and any(ch.isdigit() for ch in sRange): + asValues = sRange.split('-'); + if len(asValues) == 1: + asValues = [asValues[0], asValues[0]]; + elif len(asValues) > 2: + asValues = [asValues[0], asValues[-1]]; + tTuple = (convertRangeNumber(asValues[0]), convertRangeNumber(asValues[1])); + if tTuple[0] is not None and tTuple[1] is not None and tTuple[0] > tTuple[1]: + tTuple = (tTuple[1], tTuple[0]); + oCriterion.aoSelected.append(tTuple); + else: + assert False; + if oCriterion.aoSelected: + oCriterion.sState = FilterCriterion.ksState_Selected; + else: + oCriterion.sState = FilterCriterion.ksState_NotSelected; + + if oCriterion.sKind == FilterCriterion.ksKind_ElementOfOrNot: + oCriterion.fInverted = oDisp.getBoolParam(oCriterion.sInvVarNm, fDefault = False); + + if oCriterion.oSub is not None: + self._initFromParamsWorker(oDisp, oCriterion.oSub); + return; + + def initFromParams(self, oDisp): # type: (WuiDispatcherBase) -> self + """ + Initialize the object from parameters. + + Returns self. Raises exception on invalid parameter value. + """ + + for oCriterion in self.aCriteria: + self._initFromParamsWorker(oDisp, oCriterion); + return self; + + def strainParameters(self, dParams, aAdditionalParams = None): + """ Filters just the parameters relevant to this filter, returning a copy. """ + + # Collect the parameter names. + dWanted = {}; + for oCrit in self.aCriteria: + dWanted[oCrit.sVarNm] = 1; + if oCrit.sInvVarNm: + dWanted[oCrit.sInvVarNm] = 1; + + # Add additional stuff. + if aAdditionalParams: + for sParam in aAdditionalParams: + dWanted[sParam] = 1; + + # To the straining. + dRet = {}; + for sKey in dParams: + if sKey in dWanted: + dRet[sKey] = dParams[sKey]; + return dRet; + + +class ModelLogicBase(ModelBase): # pylint: disable=too-few-public-methods + """ + Something all classes in the logic classes the logical model inherits from. + """ + + def __init__(self, oDb): + ModelBase.__init__(self); + + # + # Note! Do not create a connection here if None, we need to DB share + # connection with all other logic objects so we can perform half + # complex transactions involving several logic objects. + # + self._oDb = oDb; + + def getDbConnection(self): + """ + Gets the database connection. + This should only be used for instantiating other ModelLogicBase children. + """ + return self._oDb; + + def _dbRowsToModelDataList(self, oModelDataType, aaoRows = None): + """ + Helper for conerting a simple fetch into a list of ModelDataType python objects. + + If aaoRows is None, we'll fetchAll from the database ourselves. + + The oModelDataType must be a class derived from ModelDataBase and implement + the initFormDbRow method. + + Returns a list of oModelDataType instances. + """ + assert issubclass(oModelDataType, ModelDataBase); + aoRet = []; + if aaoRows is None: + aaoRows = self._oDb.fetchAll(); + for aoRow in aaoRows: + aoRet.append(oModelDataType().initFromDbRow(aoRow)); + return aoRet; + + + +class AttributeChangeEntry(object): # pylint: disable=too-few-public-methods + """ + Data class representing the changes made to one attribute. + """ + + def __init__(self, sAttr, oNewRaw, oOldRaw, sNewText, sOldText): + self.sAttr = sAttr; + self.oNewRaw = oNewRaw; + self.oOldRaw = oOldRaw; + self.sNewText = sNewText; + self.sOldText = sOldText; + +class AttributeChangeEntryPre(AttributeChangeEntry): # pylint: disable=too-few-public-methods + """ + AttributeChangeEntry for preformatted values. + """ + + def __init__(self, sAttr, oNewRaw, oOldRaw, sNewText, sOldText): + AttributeChangeEntry.__init__(self, sAttr, oNewRaw, oOldRaw, sNewText, sOldText); + +class ChangeLogEntry(object): # pylint: disable=too-few-public-methods + """ + A change log entry returned by the fetchChangeLog method typically + implemented by ModelLogicBase child classes. + """ + + def __init__(self, uidAuthor, sAuthor, tsEffective, tsExpire, oNewRaw, oOldRaw, aoChanges): + self.uidAuthor = uidAuthor; + self.sAuthor = sAuthor; + self.tsEffective = tsEffective; + self.tsExpire = tsExpire; + self.oNewRaw = oNewRaw; + self.oOldRaw = oOldRaw; # Note! NULL for the last entry. + self.aoChanges = aoChanges; + diff --git a/src/VBox/ValidationKit/testmanager/core/build.py b/src/VBox/ValidationKit/testmanager/core/build.py new file mode 100755 index 00000000..c027641f --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/build.py @@ -0,0 +1,891 @@ +# -*- coding: utf-8 -*- +# $Id: build.py $ + +""" +Test Manager - Builds. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import os; +import unittest; + +# Validation Kit imports. +from testmanager import config; +from testmanager.core import coreconsts; +from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMExceptionBase, \ + TMTooManyRows, TMInvalidData, TMRowNotFound, TMRowInUse; + + +class BuildCategoryData(ModelDataBase): + """ + A build category. + """ + + ksIdAttr = 'idBuildCategory'; + + ksParam_idBuildCategory = 'BuildCategory_idBuildCategory'; + ksParam_sProduct = 'BuildCategory_sProduct'; + ksParam_sRepository = 'BuildCategory_sRepository'; + ksParam_sBranch = 'BuildCategory_sBranch'; + ksParam_sType = 'BuildCategory_sType'; + ksParam_asOsArches = 'BuildCategory_asOsArches'; + + kasAllowNullAttributes = ['idBuildCategory', ]; + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.idBuildCategory = None; + self.sProduct = None; + self.sRepository = None; + self.sBranch = None; + self.sType = None; + self.asOsArches = None; + + def initFromDbRow(self, aoRow): + """ + Re-initializes the object from a SELECT * FROM BuildCategories row. + Returns self. Raises exception if aoRow is None. + """ + if aoRow is None: + raise TMRowNotFound('BuildCategory not found.'); + + self.idBuildCategory = aoRow[0]; + self.sProduct = aoRow[1]; + self.sRepository = aoRow[2]; + self.sBranch = aoRow[3]; + self.sType = aoRow[4]; + self.asOsArches = sorted(aoRow[5]); + return self; + + def initFromDbWithId(self, oDb, idBuildCategory, tsNow = None, sPeriodBack = None): + """ + Initialize from the database, given the ID of a row. + """ + _ = tsNow; _ = sPeriodBack; # No history in this table. + oDb.execute('SELECT * FROM BuildCategories WHERE idBuildCategory = %s', (idBuildCategory,)); + aoRow = oDb.fetchOne() + if aoRow is None: + raise TMRowNotFound('idBuildCategory=%s not found' % (idBuildCategory, )); + return self.initFromDbRow(aoRow); + + def initFromValues(self, sProduct, sRepository, sBranch, sType, asOsArches, idBuildCategory = None): + """ + Reinitializes form a set of values. + return self. + """ + self.idBuildCategory = idBuildCategory; + self.sProduct = sProduct; + self.sRepository = sRepository; + self.sBranch = sBranch; + self.sType = sType; + self.asOsArches = asOsArches; + return self; + + def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb): + # Handle sType and asOsArches specially. + if sAttr == 'sType': + (oNewValue, sError) = ModelDataBase._validateAndConvertAttribute(self, sAttr, sParam, oValue, + aoNilValues, fAllowNull, oDb); + if sError is None and self.sType.lower() != self.sType: + sError = 'Invalid build type value'; + + elif sAttr == 'asOsArches': + (oNewValue, sError) = self.validateListOfStr(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull, + asValidValues = coreconsts.g_kasOsDotCpusAll); + if sError is not None and oNewValue is not None: + oNewValue = sorted(oNewValue); # Must be sorted! + + else: + return ModelDataBase._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb); + + return (oNewValue, sError); + + def matchesOsArch(self, sOs, sArch): + """ Checks if the build matches the given OS and architecture. """ + if sOs + '.' + sArch in self.asOsArches: + return True; + if sOs + '.noarch' in self.asOsArches: + return True; + if 'os-agnostic.' + sArch in self.asOsArches: + return True; + if 'os-agnostic.noarch' in self.asOsArches: + return True; + return False; + + +class BuildCategoryLogic(ModelLogicBase): # pylint: disable=too-few-public-methods + """ + Build categories database logic. + """ + + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb) + self.dCache = None; + + def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None): + """ + Fetches testboxes for listing. + + Returns an array (list) of UserAccountData items, empty list if none. + Raises exception on error. + """ + _ = tsNow; _ = aiSortColumns; + self._oDb.execute('SELECT *\n' + 'FROM BuildCategories\n' + 'ORDER BY sProduct, sRepository, sBranch, sType, idBuildCategory\n' + 'LIMIT %s OFFSET %s\n' + , (cMaxRows, iStart,)); + + aoRows = []; + for _ in range(self._oDb.getRowCount()): + aoRows.append(BuildCategoryData().initFromDbRow(self._oDb.fetchOne())); + return aoRows; + + def fetchForCombo(self): + """ + Gets the list of Build Categories for a combo box. + Returns an array of (value [idBuildCategory], drop-down-name [info], + hover-text [info]) tuples. + """ + self._oDb.execute('SELECT *\n' + 'FROM BuildCategories\n' + 'ORDER BY sProduct, sBranch, sType, asOsArches') + + aaoRows = self._oDb.fetchAll() + aoRet = [] + for aoRow in aaoRows: + oData = BuildCategoryData().initFromDbRow(aoRow) + + sInfo = '%s / %s / %s / %s' % \ + (oData.sProduct, + oData.sBranch, + oData.sType, + ', '.join(oData.asOsArches)) + + # Make short info string if necessary + sInfo = sInfo if len(sInfo) < 70 else (sInfo[:70] + '...') + + oInfoItem = (oData.idBuildCategory, sInfo, sInfo) + aoRet.append(oInfoItem) + + return aoRet + + def addEntry(self, oData, uidAuthor = None, fCommit = False): + """ + Standard method for adding a build category. + """ + + # Lazy bird warning! Reuse the soft addBuildCategory method. + self.addBuildCategory(oData, fCommit); + _ = uidAuthor; + return True; + + def removeEntry(self, uidAuthor, idBuildCategory, fCascade = False, fCommit = False): + """ + Tries to delete the build category. + Note! Does not implement cascading. This is intentional! + """ + + # + # Check that the build category isn't used by anyone. + # + self._oDb.execute('SELECT COUNT(idBuild)\n' + 'FROM Builds\n' + 'WHERE idBuildCategory = %s\n' + , (idBuildCategory,)); + cBuilds = self._oDb.fetchOne()[0]; + if cBuilds > 0: + raise TMRowInUse('Build category #%d is used by %d builds and can therefore not be deleted.' + % (idBuildCategory, cBuilds,)); + + # + # Ok, it's not used, so just delete it. + # (No history on this table. This code is for typos.) + # + self._oDb.execute('DELETE FROM Builds\n' + 'WHERE idBuildCategory = %s\n' + , (idBuildCategory,)); + + self._oDb.maybeCommit(fCommit); + _ = uidAuthor; _ = fCascade; + return True; + + def cachedLookup(self, idBuildCategory): + """ + Looks up the most recent BuildCategoryData object for idBuildCategory + via an object cache. + + Returns a shared BuildCategoryData object. None if not found. + Raises exception on DB error. + """ + if self.dCache is None: + self.dCache = self._oDb.getCache('BuildCategoryData'); + oEntry = self.dCache.get(idBuildCategory, None); + if oEntry is None: + self._oDb.execute('SELECT *\n' + 'FROM BuildCategories\n' + 'WHERE idBuildCategory = %s\n' + , (idBuildCategory, )); + if self._oDb.getRowCount() == 1: + aaoRow = self._oDb.fetchOne(); + oEntry = BuildCategoryData(); + oEntry.initFromDbRow(aaoRow); + self.dCache[idBuildCategory] = oEntry; + return oEntry; + + # + # Other methods. + # + + def tryFetch(self, idBuildCategory): + """ + Try fetch the build category with the given ID. + Returns BuildCategoryData instance if found, None if not found. + May raise exception on database error. + """ + self._oDb.execute('SELECT *\n' + 'FROM BuildCategories\n' + 'WHERE idBuildCategory = %s\n' + , (idBuildCategory,)) + aaoRows = self._oDb.fetchAll() + if not aaoRows: + return None; + if len(aaoRows) != 1: + raise self._oDb.integrityException('Duplicates in BuildCategories: %s' % (aaoRows,)); + return BuildCategoryData().initFromDbRow(aaoRows[0]) + + def tryFindByData(self, oData): + """ + Tries to find the matching build category from the sProduct, sBranch, + sType and asOsArches members of oData. + + Returns a valid build category ID and an updated oData object if found. + Returns None and unmodified oData object if not found. + May raise exception on database error. + """ + self._oDb.execute('SELECT *\n' + 'FROM BuildCategories\n' + 'WHERE sProduct = %s\n' + ' AND sRepository = %s\n' + ' AND sBranch = %s\n' + ' AND sType = %s\n' + ' AND asOsArches = %s\n' + , ( oData.sProduct, + oData.sRepository, + oData.sBranch, + oData.sType, + sorted(oData.asOsArches), + )); + aaoRows = self._oDb.fetchAll(); + if not aaoRows: + return None; + if len(aaoRows) > 1: + raise self._oDb.integrityException('Duplicates in BuildCategories: %s' % (aaoRows,)); + + oData.initFromDbRow(aaoRows[0]); + return oData.idBuildCategory; + + def addBuildCategory(self, oData, fCommit = False): + """ + Add Build Category record into the database if needed, returning updated oData. + Raises exception on input and database errors. + """ + + # Check BuildCategoryData before do anything + dDataErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Add); + if dDataErrors: + raise TMInvalidData('Invalid data passed to addBuildCategory(): %s' % (dDataErrors,)); + + # Does it already exist? + if self.tryFindByData(oData) is None: + # No, We'll have to add it. + self._oDb.execute('INSERT INTO BuildCategories (sProduct, sRepository, sBranch, sType, asOsArches)\n' + 'VALUES (%s, %s, %s, %s, %s)\n' + 'RETURNING idBuildCategory' + , ( oData.sProduct, + oData.sRepository, + oData.sBranch, + oData.sType, + sorted(oData.asOsArches), + )); + oData.idBuildCategory = self._oDb.fetchOne()[0]; + + self._oDb.maybeCommit(fCommit); + return oData; + + +class BuildData(ModelDataBase): + """ + A build. + """ + + ksIdAttr = 'idBuild'; + + ksParam_idBuild = 'Build_idBuild'; + ksParam_tsCreated = 'Build_tsCreated'; + ksParam_tsEffective = 'Build_tsEffective'; + ksParam_tsExpire = 'Build_tsExpire'; + ksParam_uidAuthor = 'Build_uidAuthor'; + ksParam_idBuildCategory = 'Build_idBuildCategory'; + ksParam_iRevision = 'Build_iRevision'; + ksParam_sVersion = 'Build_sVersion'; + ksParam_sLogUrl = 'Build_sLogUrl'; + ksParam_sBinaries = 'Build_sBinaries'; + ksParam_fBinariesDeleted = 'Build_fBinariesDeleted'; + + kasAllowNullAttributes = ['idBuild', 'tsCreated', 'tsEffective', 'tsExpire', 'uidAuthor', 'tsCreated', 'sLogUrl']; + + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.idBuild = None; + self.tsCreated = None; + self.tsEffective = None; + self.tsExpire = None; + self.uidAuthor = None; + self.idBuildCategory = None; + self.iRevision = None; + self.sVersion = None; + self.sLogUrl = None; + self.sBinaries = None; + self.fBinariesDeleted = False; + + def initFromDbRow(self, aoRow): + """ + Re-initializes the object from a SELECT * FROM Builds row. + Returns self. Raises exception if aoRow is None. + """ + if aoRow is None: + raise TMRowNotFound('Build not found.'); + + self.idBuild = aoRow[0]; + self.tsCreated = aoRow[1]; + self.tsEffective = aoRow[2]; + self.tsExpire = aoRow[3]; + self.uidAuthor = aoRow[4]; + self.idBuildCategory = aoRow[5]; + self.iRevision = aoRow[6]; + self.sVersion = aoRow[7]; + self.sLogUrl = aoRow[8]; + self.sBinaries = aoRow[9]; + self.fBinariesDeleted = aoRow[10]; + return self; + + def initFromDbWithId(self, oDb, idBuild, tsNow = None, sPeriodBack = None): + """ + Initialize from the database, given the ID of a row. + """ + oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb, + 'SELECT *\n' + 'FROM Builds\n' + 'WHERE idBuild = %s\n' + , ( idBuild,), tsNow, sPeriodBack)); + aoRow = oDb.fetchOne() + if aoRow is None: + raise TMRowNotFound('idBuild=%s not found (tsNow=%s sPeriodBack=%s)' % (idBuild, tsNow, sPeriodBack,)); + return self.initFromDbRow(aoRow); + + def areFilesStillThere(self): + """ + Try check if the build files are still there. + + Returns True if they are, None if we cannot tell, and False if one or + more are missing. + """ + if self.fBinariesDeleted: + return False; + + for sBinary in self.sBinaries.split(','): + sBinary = sBinary.strip(); + if not sBinary: + continue; + # Same URL tests as in webutils.downloadFile(). + if sBinary.startswith('http://') \ + or sBinary.startswith('https://') \ + or sBinary.startswith('ftp://'): + # URL - don't bother trying to verify that (we don't use it atm). + fRc = None; + else: + # File. + if config.g_ksBuildBinRootDir is not None: + sFullPath = os.path.join(config.g_ksBuildBinRootDir, sBinary); + fRc = os.path.isfile(sFullPath); + if not fRc \ + and not os.path.isfile(os.path.join(config.g_ksBuildBinRootDir, config.g_ksBuildBinRootFile)): + fRc = None; # Root file missing, so the share might not be mounted correctly. + else: + fRc = None; + if fRc is not True: + return fRc; + + return True; + + +class BuildDataEx(BuildData): + """ + Complete data set. + """ + + kasInternalAttributes = [ 'oCat', ]; + + def __init__(self): + BuildData.__init__(self); + self.oCat = None; + + def initFromDbRow(self, aoRow): + """ + Reinitialize from a SELECT Builds.*, BuildCategories.* FROM Builds, BuildCategories query. + Returns self. Raises exception if aoRow is None. + """ + if aoRow is None: + raise TMRowNotFound('Build not found.'); + BuildData.initFromDbRow(self, aoRow); + self.oCat = BuildCategoryData().initFromDbRow(aoRow[11:]); + return self; + + def initFromDbWithId(self, oDb, idBuild, tsNow = None, sPeriodBack = None): + """ + Reinitialize from database given a row ID. + Returns self. Raises exception on database error or if the ID is invalid. + """ + oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb, + 'SELECT Builds.*, BuildCategories.*\n' + 'FROM Builds, BuildCategories\n' + 'WHERE idBuild = %s\n' + ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n' + , ( idBuild,), tsNow, sPeriodBack, 'Builds.')); + aoRow = oDb.fetchOne() + if aoRow is None: + raise TMRowNotFound('idBuild=%s not found (tsNow=%s sPeriodBack=%s)' % (idBuild, tsNow, sPeriodBack,)); + return self.initFromDbRow(aoRow); + + def convertFromParamNull(self): + raise TMExceptionBase('Not implemented'); + + def isEqual(self, oOther): + raise TMExceptionBase('Not implemented'); + + + +class BuildLogic(ModelLogicBase): # pylint: disable=too-few-public-methods + """ + Build database logic (covers build categories as well as builds). + """ + + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb) + self.dCache = None; + + # + # Standard methods. + # + + def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None): + """ + Fetches builds for listing. + + Returns an array (list) of BuildDataEx items, empty list if none. + Raises exception on error. + """ + _ = aiSortColumns; + + if tsNow is None: + self._oDb.execute('SELECT *\n' + 'FROM Builds, BuildCategories\n' + 'WHERE Builds.idBuildCategory = BuildCategories.idBuildCategory\n' + ' AND Builds.tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY tsCreated DESC\n' + 'LIMIT %s OFFSET %s\n' + , (cMaxRows, iStart,)); + else: + self._oDb.execute('SELECT *\n' + 'FROM Builds, BuildCategories\n' + 'WHERE Builds.idBuildCategory = BuildCategories.idBuildCategory\n' + ' AND Builds.tsExpire > %s\n' + ' AND Builds.tsEffective <= %s\n' + 'ORDER BY tsCreated DESC\n' + 'LIMIT %s OFFSET %s\n' + , (tsNow, tsNow, cMaxRows, iStart,)); + + aoRows = []; + for _ in range(self._oDb.getRowCount()): + aoRows.append(BuildDataEx().initFromDbRow(self._oDb.fetchOne())); + return aoRows; + + def addEntry(self, oBuildData, uidAuthor = None, fCommit = False): + """ + Adds the build to the database, optionally adding the build category if + a BuildDataEx object used and it's necessary. + + Returns updated data object. Raises exception on failure. + """ + + # Find/Add the build category if specified. + if isinstance(oBuildData, BuildDataEx) \ + and oBuildData.idBuildCategory is None: + BuildCategoryLogic(self._oDb).addBuildCategory(oBuildData.oCat, fCommit = False); + oBuildData.idBuildCategory = oBuildData.oCat.idBuildCategory; + + # Add the build. + self._oDb.execute('INSERT INTO Builds (uidAuthor,\n' + ' idBuildCategory,\n' + ' iRevision,\n' + ' sVersion,\n' + ' sLogUrl,\n' + ' sBinaries,\n' + ' fBinariesDeleted)\n' + 'VALUES (%s, %s, %s, %s, %s, %s, %s)\n' + 'RETURNING idBuild, tsCreated\n' + , ( uidAuthor, + oBuildData.idBuildCategory, + oBuildData.iRevision, + oBuildData.sVersion, + oBuildData.sLogUrl, + oBuildData.sBinaries, + oBuildData.fBinariesDeleted, + )); + aoRow = self._oDb.fetchOne(); + oBuildData.idBuild = aoRow[0]; + oBuildData.tsCreated = aoRow[1]; + + self._oDb.maybeCommit(fCommit); + return oBuildData; + + def editEntry(self, oData, uidAuthor = None, fCommit = False): + """Modify database record""" + + # + # Validate input and get current data. + # + dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit); + if dErrors: + raise TMInvalidData('editEntry invalid input: %s' % (dErrors,)); + oOldData = BuildData().initFromDbWithId(self._oDb, oData.idBuild); + + # + # Do the work. + # + if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor' ]): + self._historizeBuild(oData.idBuild); + self._oDb.execute('INSERT INTO Builds (uidAuthor,\n' + ' idBuild,\n' + ' tsCreated,\n' + ' idBuildCategory,\n' + ' iRevision,\n' + ' sVersion,\n' + ' sLogUrl,\n' + ' sBinaries,\n' + ' fBinariesDeleted)\n' + 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)\n' + 'RETURNING idBuild, tsCreated\n' + , ( uidAuthor, + oData.idBuild, + oData.tsCreated, + oData.idBuildCategory, + oData.iRevision, + oData.sVersion, + oData.sLogUrl, + oData.sBinaries, + oData.fBinariesDeleted, + )); + + self._oDb.maybeCommit(fCommit); + return True; + + def removeEntry(self, uidAuthor, idBuild, fCascade = False, fCommit = False): + """ + Historize record + """ + + # + # No non-historic refs here, so just go ahead and expire the build. + # + _ = fCascade; + _ = uidAuthor; ## @todo record deleter. + + self._historizeBuild(idBuild, None); + + self._oDb.maybeCommit(fCommit); + return True; + + def cachedLookup(self, idBuild): + """ + Looks up the most recent BuildDataEx object for idBuild + via an object cache. + + Returns a shared BuildDataEx object. None if not found. + Raises exception on DB error. + """ + if self.dCache is None: + self.dCache = self._oDb.getCache('BuildDataEx'); + oEntry = self.dCache.get(idBuild, None); + if oEntry is None: + self._oDb.execute('SELECT Builds.*, BuildCategories.*\n' + 'FROM Builds, BuildCategories\n' + 'WHERE Builds.idBuild = %s\n' + ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idBuild, )); + if self._oDb.getRowCount() == 0: + # Maybe it was deleted, try get the last entry. + self._oDb.execute('SELECT Builds.*, BuildCategories.*\n' + 'FROM Builds, BuildCategories\n' + 'WHERE Builds.idBuild = %s\n' + ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n' + 'ORDER BY tsExpire DESC\n' + 'LIMIT 1\n' + , (idBuild, )); + elif self._oDb.getRowCount() > 1: + raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idBuild)); + + if self._oDb.getRowCount() == 1: + aaoRow = self._oDb.fetchOne(); + oEntry = BuildDataEx(); + oEntry.initFromDbRow(aaoRow); + self.dCache[idBuild] = oEntry; + return oEntry; + + + # + # Other methods. + # + + def tryFindSameBuildForOsArch(self, oBuildEx, sOs, sCpuArch): + """ + Attempts to find a matching build for the given OS.ARCH. May return + the input build if if matches. + + Returns BuildDataEx instance if found, None if none. May raise + exception on database error. + """ + + if oBuildEx.oCat.matchesOsArch(sOs, sCpuArch): + return oBuildEx; + + self._oDb.execute('SELECT Builds.*, BuildCategories.*\n' + 'FROM Builds, BuildCategories\n' + 'WHERE BuildCategories.sProduct = %s\n' + ' AND BuildCategories.sBranch = %s\n' + ' AND BuildCategories.sType = %s\n' + ' AND ( %s = ANY(BuildCategories.asOsArches)\n' + ' OR %s = ANY(BuildCategories.asOsArches)\n' + ' OR %s = ANY(BuildCategories.asOsArches))\n' + ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n' + ' AND Builds.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND Builds.iRevision = %s\n' + ' AND Builds.sRelease = %s\n' + ' AND Builds.fBinariesDeleted IS FALSE\n' + 'ORDER BY tsCreated DESC\n' + 'LIMIT 4096\n' # stay sane. + , (oBuildEx.oCat.sProduct, + oBuildEx.oCat.sBranch, + oBuildEx.oCat.sType, + '%s.%s' % (sOs, sCpuArch), + '%s.noarch' % (sOs,), + 'os-agnostic.%s' % (sCpuArch,), + 'os-agnostic.noarch', + oBuildEx.iRevision, + oBuildEx.sRelease, + ) ); + aaoRows = self._oDb.fetchAll(); + + for aoRow in aaoRows: + oBuildExRet = BuildDataEx().initFromDbRow(aoRow); + if not self.isBuildBlacklisted(oBuildExRet): + return oBuildExRet; + + return None; + + def isBuildBlacklisted(self, oBuildEx): + """ + Checks if the given build is blacklisted + Returns True/False. May raise exception on database error. + """ + + asOsAgnosticArch = []; + asOsNoArch = []; + for sOsArch in oBuildEx.oCat.asOsArches: + asParts = sOsArch.split('.'); + if len(asParts) != 2 or not asParts[0] or not asParts[1]: + raise self._oDb.integrityException('Bad build asOsArches value: %s (idBuild=%s idBuildCategory=%s)' + % (sOsArch, oBuildEx.idBuild, oBuildEx.idBuildCategory)); + asOsNoArch.append(asParts[0] + '.noarch'); + asOsNoArch.append('os-agnostic.' + asParts[1]); + + self._oDb.execute('SELECT COUNT(*)\n' + 'FROM BuildBlacklist\n' + 'WHERE BuildBlacklist.tsExpire > CURRENT_TIMESTAMP\n' + ' AND BuildBlacklist.tsEffective <= CURRENT_TIMESTAMP\n' + ' AND BuildBlacklist.sProduct = %s\n' + ' AND BuildBlacklist.sBranch = %s\n' + ' AND ( BuildBlacklist.asTypes is NULL\n' + ' OR %s = ANY(BuildBlacklist.asTypes))\n' + ' AND ( BuildBlacklist.asOsArches is NULL\n' + ' OR %s && BuildBlacklist.asOsArches\n' ## @todo check array rep! Need overload? + ' OR %s && BuildBlacklist.asOsArches\n' + ' OR %s && BuildBlacklist.asOsArches\n' + ' OR %s = ANY(BuildBlacklist.asOsArches))\n' + ' AND BuildBlacklist.iFirstRevision <= %s\n' + ' AND BuildBlacklist.iLastRevision >= %s\n' + , (oBuildEx.oCat.sProduct, + oBuildEx.oCat.sBranch, + oBuildEx.oCat.sType, + oBuildEx.oCat.asOsArches, + asOsAgnosticArch, + asOsNoArch, + 'os-agnostic.noarch', + oBuildEx.iRevision, + oBuildEx.iRevision, + ) ); + return self._oDb.fetchOne()[0] > 0; + + + def getById(self, idBuild): + """ + Get build record by its id + """ + self._oDb.execute('SELECT Builds.*, BuildCategories.*\n' + 'FROM Builds, BuildCategories\n' + 'WHERE Builds.idBuild=%s\n' + ' AND Builds.idBuildCategory=BuildCategories.idBuildCategory\n' + ' AND Builds.tsExpire = \'infinity\'::TIMESTAMP\n', (idBuild,)) + + aRows = self._oDb.fetchAll() + if len(aRows) not in (0, 1): + raise TMTooManyRows('Found more than one build with the same credentials. Database structure is corrupted.') + try: + return BuildDataEx().initFromDbRow(aRows[0]) + except IndexError: + return None + + + def getAll(self, tsEffective = None): + """ + Gets the list of all builds. + Returns an array of BuildDataEx instances. + """ + if tsEffective is None: + self._oDb.execute('SELECT Builds.*, BuildCategories.*\n' + 'FROM Builds, BuildCategories\n' + 'WHERE Builds.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND Builds.idBuildCategory=BuildCategories.idBuildCategory') + else: + self._oDb.execute('SELECT Builds.*, BuildCategories.*\n' + 'FROM Builds, BuildCategories\n' + 'WHERE Builds.tsExpire > %s\n' + ' AND Builds.tsEffective <= %s' + ' AND Builds.idBuildCategory=BuildCategories.idBuildCategory' + , (tsEffective, tsEffective)) + aoRet = [] + for aoRow in self._oDb.fetchAll(): + aoRet.append(BuildDataEx().initFromDbRow(aoRow)) + return aoRet + + + def markDeletedByBinaries(self, sBinaries, fCommit = False): + """ + Marks zero or more builds deleted given the build binaries. + + Returns the number of affected builds. + """ + # Fetch a list of affected build IDs (generally 1 build), and used the + # editEntry method to do the rest. This isn't 100% optimal, but it's + # short and simple, the main effort is anyway the first query. + self._oDb.execute('SELECT idBuild\n' + 'FROM Builds\n' + 'WHERE sBinaries = %s\n' + ' AND fBinariesDeleted = FALSE\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (sBinaries,)); + aaoRows = self._oDb.fetchAll(); + for aoRow in aaoRows: + oData = BuildData().initFromDbWithId(self._oDb, aoRow[0]); + assert not oData.fBinariesDeleted; + oData.fBinariesDeleted = True; + self.editEntry(oData, fCommit = False); + self._oDb.maybeCommit(fCommit); + return len(aaoRows); + + + + # + # Internal helpers. + # + + def _historizeBuild(self, idBuild, tsExpire = None): + """ Historizes the current entry for the specified build. """ + if tsExpire is None: + self._oDb.execute('UPDATE Builds\n' + 'SET tsExpire = CURRENT_TIMESTAMP\n' + 'WHERE idBuild = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idBuild,)); + else: + self._oDb.execute('UPDATE Builds\n' + 'SET tsExpire = %s\n' + 'WHERE idBuild = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (tsExpire, idBuild,)); + return True; + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +class BuildCategoryDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [BuildCategoryData(),]; + +class BuildDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [BuildData(),]; + +if __name__ == '__main__': + unittest.main(); + # not reached. + diff --git a/src/VBox/ValidationKit/testmanager/core/buildblacklist.py b/src/VBox/ValidationKit/testmanager/core/buildblacklist.py new file mode 100755 index 00000000..12a54ba7 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/buildblacklist.py @@ -0,0 +1,324 @@ +# -*- coding: utf-8 -*- +# $Id: buildblacklist.py $ + +""" +Test Manager - Builds Blacklist. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Validation Kit imports. +from testmanager.core.base import ModelDataBase, ModelLogicBase, TMInvalidData, TMRowNotFound; + + +class BuildBlacklistData(ModelDataBase): + """ + Build Blacklist Data. + """ + + ksIdAttr = 'idBlacklisting'; + + ksParam_idBlacklisting = 'BuildBlacklist_idBlacklisting' + ksParam_tsEffective = 'BuildBlacklist_tsEffective' + ksParam_tsExpire = 'BuildBlacklist_tsExpire' + ksParam_uidAuthor = 'BuildBlacklist_uidAuthor' + ksParam_idFailureReason = 'BuildBlacklist_idFailureReason' + ksParam_sProduct = 'BuildBlacklist_sProduct' + ksParam_sBranch = 'BuildBlacklist_sBranch' + ksParam_asTypes = 'BuildBlacklist_asTypes' + ksParam_asOsArches = 'BuildBlacklist_asOsArches' + ksParam_iFirstRevision = 'BuildBlacklist_iFirstRevision' + ksParam_iLastRevision = 'BuildBlacklist_iLastRevision' + + kasAllowNullAttributes = [ 'idBlacklisting', + 'tsEffective', + 'tsExpire', + 'uidAuthor', + 'asTypes', + 'asOsArches' ]; + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.idBlacklisting = None + self.tsEffective = None + self.tsExpire = None + self.uidAuthor = None + self.idFailureReason = None + self.sProduct = None + self.sBranch = None + self.asTypes = None + self.asOsArches = None + self.iFirstRevision = None + self.iLastRevision = None + + def initFromDbRow(self, aoRow): + """ + Re-initializes the data with a row from a SELECT * FROM BuildBlacklist. + + Returns self. Raises exception if the row is None or otherwise invalid. + """ + + if aoRow is None: + raise TMRowNotFound('Build Blacklist item not found.') + + self.idBlacklisting = aoRow[0] + self.tsEffective = aoRow[1] + self.tsExpire = aoRow[2] + self.uidAuthor = aoRow[3] + self.idFailureReason = aoRow[4] + self.sProduct = aoRow[5] + self.sBranch = aoRow[6] + self.asTypes = aoRow[7] + self.asOsArches = aoRow[8] + self.iFirstRevision = aoRow[9] + self.iLastRevision = aoRow[10] + + return self; + + def initFromDbWithId(self, oDb, idBlacklisting, tsNow = None, sPeriodBack = None): + """ + Initialize the object from the database. + """ + oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb, + 'SELECT *\n' + 'FROM BuildBlacklist\n' + 'WHERE idBlacklisting = %s\n' + , ( idBlacklisting,), tsNow, sPeriodBack)); + aoRow = oDb.fetchOne() + if aoRow is None: + raise TMRowNotFound('idBlacklisting=%s not found (tsNow=%s sPeriodBack=%s)' + % (idBlacklisting, tsNow, sPeriodBack,)); + return self.initFromDbRow(aoRow); + + +class BuildBlacklistLogic(ModelLogicBase): # pylint: disable=too-few-public-methods + """ + Build Back List logic. + """ + + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb) + self.dCache = None; + + def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None): + """ + Fetches Build Blacklist records. + + Returns an array (list) of BuildBlacklistData items, empty list if none. + Raises exception on error. + """ + _ = aiSortColumns; + + if tsNow is None: + self._oDb.execute('SELECT *\n' + 'FROM BuildBlacklist\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY idBlacklisting DESC\n' + 'LIMIT %s OFFSET %s\n' + , (cMaxRows, iStart,)); + else: + self._oDb.execute('SELECT *\n' + 'FROM BuildBlacklist\n' + 'WHERE tsExpire > %s\n' + ' AND tsEffective <= %s\n' + 'ORDER BY idBlacklisting DESC\n' + 'LIMIT %s OFFSET %s\n' + , (tsNow, tsNow, cMaxRows, iStart,)); + + aoRows = [] + for aoRow in self._oDb.fetchAll(): + aoRows.append(BuildBlacklistData().initFromDbRow(aoRow)) + return aoRows + + def addEntry(self, oData, uidAuthor, fCommit = False): + """ + Adds a blacklisting to the database. + """ + self._oDb.execute('INSERT INTO BuildBlacklist (\n' + ' uidAuthor,\n' + ' idFailureReason,\n' + ' sProduct,\n' + ' sBranch,\n' + ' asTypes,\n' + ' asOsArches,\n' + ' iFirstRevision,\n' + ' iLastRevision)\n' + 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s)' + , ( uidAuthor, + oData.idFailureReason, + oData.sProduct, + oData.sBranch, + oData.asTypes, + oData.asOsArches, + oData.iFirstRevision, + oData.iLastRevision,) ); + self._oDb.maybeCommit(fCommit); + return True + + def editEntry(self, oData, uidAuthor, fCommit = False): + """ + Modifies a blacklisting. + """ + + # + # Validate inputs and read in the old(/current) data. + # + assert isinstance(oData, BuildBlacklistData); + dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit); + if dErrors: + raise TMInvalidData('editEntry invalid input: %s' % (dErrors,)); + + oOldData = BuildBlacklistData().initFromDbWithId(self._oDb, oData.idBlacklisting); + + # + # Update the data that needs updating. + # + if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor', ]): + self._historizeEntry(oData.idBlacklisting, None); + self._readdEntry(uidAuthor, oData, None); + self._oDb.maybeCommit(fCommit); + return True; + + + def removeEntry(self, uidAuthor, idBlacklisting, fCascade = False, fCommit = False): + """ + Deletes a test group. + """ + _ = fCascade; # Not applicable. + + oData = BuildBlacklistData().initFromDbWithId(self._oDb, idBlacklisting); + + (tsCur, tsCurMinusOne) = self._oDb.getCurrentTimestamps(); + if oData.tsEffective not in (tsCur, tsCurMinusOne): + self._historizeEntry(idBlacklisting, tsCurMinusOne); + self._readdEntry(uidAuthor, oData, tsCurMinusOne); + self._historizeEntry(idBlacklisting); + self._oDb.execute('UPDATE BuildBlacklist\n' + 'SET tsExpire = CURRENT_TIMESTAMP\n' + 'WHERE idBlacklisting = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idBlacklisting,)); + self._oDb.maybeCommit(fCommit); + return True; + + + def cachedLookup(self, idBlacklisting): + """ + Looks up the most recent BuildBlacklistData object for idBlacklisting + via an object cache. + + Returns a shared BuildBlacklistData object. None if not found. + Raises exception on DB error. + """ + if self.dCache is None: + self.dCache = self._oDb.getCache('BuildBlacklistData'); + oEntry = self.dCache.get(idBlacklisting, None); + if oEntry is None: + self._oDb.execute('SELECT *\n' + 'FROM BuildBlacklist\n' + 'WHERE idBlacklisting = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idBlacklisting, )); + if self._oDb.getRowCount() == 0: + # Maybe it was deleted, try get the last entry. + self._oDb.execute('SELECT *\n' + 'FROM BuildBlacklist\n' + 'WHERE idBlacklisting = %s\n' + 'ORDER BY tsExpire DESC\n' + 'LIMIT 1\n' + , (idBlacklisting, )); + elif self._oDb.getRowCount() > 1: + raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idBlacklisting)); + + if self._oDb.getRowCount() == 1: + aaoRow = self._oDb.fetchOne(); + oEntry = BuildBlacklistData(); + oEntry.initFromDbRow(aaoRow); + self.dCache[idBlacklisting] = oEntry; + return oEntry; + + + # + # Helpers. + # + + def _historizeEntry(self, idBlacklisting, tsExpire = None): + """ + Historizes the current entry for the given backlisting. + """ + if tsExpire is None: + tsExpire = self._oDb.getCurrentTimestamp(); + self._oDb.execute('UPDATE BuildBlacklist\n' + 'SET tsExpire = %s\n' + 'WHERE idBlacklisting = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , ( tsExpire, idBlacklisting, )); + return True; + + def _readdEntry(self, uidAuthor, oData, tsEffective = None): + """ + Re-adds the BuildBlacklist entry. Used by editEntry and removeEntry. + """ + if tsEffective is None: + tsEffective = self._oDb.getCurrentTimestamp(); + self._oDb.execute('INSERT INTO BuildBlacklist (\n' + ' uidAuthor,\n' + ' tsEffective,\n' + ' idBlacklisting,\n' + ' idFailureReason,\n' + ' sProduct,\n' + ' sBranch,\n' + ' asTypes,\n' + ' asOsArches,\n' + ' iFirstRevision,\n' + ' iLastRevision)\n' + 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)\n' + , ( uidAuthor, + tsEffective, + oData.idBlacklisting, + oData.idFailureReason, + oData.sProduct, + oData.sBranch, + oData.asTypes, + oData.asOsArches, + oData.iFirstRevision, + oData.iLastRevision,) ); + return True; + diff --git a/src/VBox/ValidationKit/testmanager/core/buildsource.py b/src/VBox/ValidationKit/testmanager/core/buildsource.py new file mode 100755 index 00000000..df2f70e5 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/buildsource.py @@ -0,0 +1,524 @@ +# -*- coding: utf-8 -*- +# $Id: buildsource.py $ + +""" +Test Manager - Build Sources. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import unittest; + +# Validation Kit imports. +from common import utils; +from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMRowAlreadyExists, \ + TMRowInUse, TMInvalidData, TMRowNotFound; +from testmanager.core import coreconsts; + + +class BuildSourceData(ModelDataBase): + """ + A build source. + """ + + ksIdAttr = 'idBuildSrc'; + + ksParam_idBuildSrc = 'BuildSource_idBuildSrc'; + ksParam_tsEffective = 'BuildSource_tsEffective'; + ksParam_tsExpire = 'BuildSource_tsExpire'; + ksParam_uidAuthor = 'BuildSource_uidAuthor'; + ksParam_sName = 'BuildSource_sName'; + ksParam_sDescription = 'BuildSource_sDescription'; + ksParam_sProduct = 'BuildSource_sProduct'; + ksParam_sBranch = 'BuildSource_sBranch'; + ksParam_asTypes = 'BuildSource_asTypes'; + ksParam_asOsArches = 'BuildSource_asOsArches'; + ksParam_iFirstRevision = 'BuildSource_iFirstRevision'; + ksParam_iLastRevision = 'BuildSource_iLastRevision'; + ksParam_cSecMaxAge = 'BuildSource_cSecMaxAge'; + + kasAllowNullAttributes = [ 'idBuildSrc', 'tsEffective', 'tsExpire', 'uidAuthor', 'sDescription', 'asTypes', + 'asOsArches', 'iFirstRevision', 'iLastRevision', 'cSecMaxAge' ]; + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.idBuildSrc = None; + self.tsEffective = None; + self.tsExpire = None; + self.uidAuthor = None; + self.sName = None; + self.sDescription = None; + self.sProduct = None; + self.sBranch = None; + self.asTypes = None; + self.asOsArches = None; + self.iFirstRevision = None; + self.iLastRevision = None; + self.cSecMaxAge = None; + + def initFromDbRow(self, aoRow): + """ + Re-initializes the object from a SELECT * FROM BuildSources row. + Returns self. Raises exception if aoRow is None. + """ + if aoRow is None: + raise TMRowNotFound('Build source not found.'); + + self.idBuildSrc = aoRow[0]; + self.tsEffective = aoRow[1]; + self.tsExpire = aoRow[2]; + self.uidAuthor = aoRow[3]; + self.sName = aoRow[4]; + self.sDescription = aoRow[5]; + self.sProduct = aoRow[6]; + self.sBranch = aoRow[7]; + self.asTypes = aoRow[8]; + self.asOsArches = aoRow[9]; + self.iFirstRevision = aoRow[10]; + self.iLastRevision = aoRow[11]; + self.cSecMaxAge = aoRow[12]; + return self; + + def initFromDbWithId(self, oDb, idBuildSrc, tsNow = None, sPeriodBack = None): + """ + Initialize from the database, given the ID of a row. + """ + oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb, + 'SELECT *\n' + 'FROM BuildSources\n' + 'WHERE idBuildSrc = %s\n' + , ( idBuildSrc,), tsNow, sPeriodBack)); + aoRow = oDb.fetchOne() + if aoRow is None: + raise TMRowNotFound('idBuildSrc=%s not found (tsNow=%s sPeriodBack=%s)' % (idBuildSrc, tsNow, sPeriodBack,)); + return self.initFromDbRow(aoRow); + + def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb): + # Handle asType and asOsArches specially. + if sAttr == 'sType': + (oNewValue, sError) = ModelDataBase._validateAndConvertAttribute(self, sAttr, sParam, oValue, + aoNilValues, fAllowNull, oDb); + if sError is None: + if not self.asTypes: + oNewValue = None; + else: + for sType in oNewValue: + if len(sType) < 2 or sType.lower() != sType: + if sError is None: sError = ''; + else: sError += ', '; + sError += 'invalid value "%s"' % (sType,); + + elif sAttr == 'asOsArches': + (oNewValue, sError) = self.validateListOfStr(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull, + asValidValues = coreconsts.g_kasOsDotCpusAll); + if sError is not None and oNewValue is not None: + oNewValue = sorted(oNewValue); # Must be sorted! + + elif sAttr == 'cSecMaxAge' and oValue not in aoNilValues: # Allow human readable interval formats. + (oNewValue, sError) = utils.parseIntervalSeconds(oValue); + else: + return ModelDataBase._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb); + + return (oNewValue, sError); + +class BuildSourceLogic(ModelLogicBase): # pylint: disable=too-few-public-methods + """ + Build source database logic. + """ + + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb) + self.dCache = None; + + # + # Standard methods. + # + + def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None): + """ + Fetches build sources. + + Returns an array (list) of BuildSourceData items, empty list if none. + Raises exception on error. + """ + _ = aiSortColumns; + + if tsNow is None: + self._oDb.execute('SELECT *\n' + 'FROM BuildSources\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY idBuildSrc DESC\n' + 'LIMIT %s OFFSET %s\n' + , (cMaxRows, iStart,)); + else: + self._oDb.execute('SELECT *\n' + 'FROM BuildSources\n' + 'WHERE tsExpire > %s\n' + ' AND tsEffective <= %s\n' + 'ORDER BY idBuildSrc DESC\n' + 'LIMIT %s OFFSET %s\n' + , (tsNow, tsNow, cMaxRows, iStart,)); + + aoRows = [] + for aoRow in self._oDb.fetchAll(): + aoRows.append(BuildSourceData().initFromDbRow(aoRow)) + return aoRows + + def fetchForCombo(self): + """Fetch data which is aimed to be passed to HTML form""" + self._oDb.execute('SELECT idBuildSrc, sName, sProduct\n' + 'FROM BuildSources\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY idBuildSrc DESC\n') + asRet = self._oDb.fetchAll(); + asRet.insert(0, (-1, 'None', 'None')); + return asRet; + + + def addEntry(self, oData, uidAuthor, fCommit = False): + """ + Add a new build source to the database. + """ + + # + # Validate the input. + # + dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Add); + if dErrors: + raise TMInvalidData('addEntry invalid input: %s' % (dErrors,)); + self._assertUnique(oData, None); + + # + # Add it. + # + self._oDb.execute('INSERT INTO BuildSources (\n' + ' uidAuthor,\n' + ' sName,\n' + ' sDescription,\n' + ' sProduct,\n' + ' sBranch,\n' + ' asTypes,\n' + ' asOsArches,\n' + ' iFirstRevision,\n' + ' iLastRevision,\n' + ' cSecMaxAge)\n' + 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)\n' + , ( uidAuthor, + oData.sName, + oData.sDescription, + oData.sProduct, + oData.sBranch, + oData.asTypes, + oData.asOsArches, + oData.iFirstRevision, + oData.iLastRevision, + oData.cSecMaxAge, )); + + self._oDb.maybeCommit(fCommit); + return True; + + def editEntry(self, oData, uidAuthor, fCommit = False): + """ + Modifies a build source. + """ + + # + # Validate the input and read the old entry. + # + dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit); + if dErrors: + raise TMInvalidData('addEntry invalid input: %s' % (dErrors,)); + self._assertUnique(oData, oData.idBuildSrc); + oOldData = BuildSourceData().initFromDbWithId(self._oDb, oData.idBuildSrc); + + # + # Make the changes (if something actually changed). + # + if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor', ]): + self._historizeBuildSource(oData.idBuildSrc); + self._oDb.execute('INSERT INTO BuildSources (\n' + ' uidAuthor,\n' + ' idBuildSrc,\n' + ' sName,\n' + ' sDescription,\n' + ' sProduct,\n' + ' sBranch,\n' + ' asTypes,\n' + ' asOsArches,\n' + ' iFirstRevision,\n' + ' iLastRevision,\n' + ' cSecMaxAge)\n' + 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)\n' + , ( uidAuthor, + oData.idBuildSrc, + oData.sName, + oData.sDescription, + oData.sProduct, + oData.sBranch, + oData.asTypes, + oData.asOsArches, + oData.iFirstRevision, + oData.iLastRevision, + oData.cSecMaxAge, )); + self._oDb.maybeCommit(fCommit); + return True; + + def removeEntry(self, uidAuthor, idBuildSrc, fCascade = False, fCommit = False): + """ + Deletes a build sources. + """ + + # + # Check cascading. + # + if fCascade is not True: + self._oDb.execute('SELECT idSchedGroup, sName\n' + 'FROM SchedGroups\n' + 'WHERE idBuildSrc = %s\n' + ' OR idBuildSrcTestSuite = %s\n' + , (idBuildSrc, idBuildSrc,)); + if self._oDb.getRowCount() > 0: + asGroups = []; + for aoRow in self._oDb.fetchAll(): + asGroups.append('%s (#%d)' % (aoRow[1], aoRow[0])); + raise TMRowInUse('Build source #%d is used by one or more scheduling groups: %s' + % (idBuildSrc, ', '.join(asGroups),)); + else: + self._oDb.execute('UPDATE SchedGroups\n' + 'SET idBuildSrc = NULL\n' + 'WHERE idBuildSrc = %s' + , ( idBuildSrc,)); + self._oDb.execute('UPDATE SchedGroups\n' + 'SET idBuildSrcTestSuite = NULL\n' + 'WHERE idBuildSrcTestSuite = %s' + , ( idBuildSrc,)); + + # + # Do the job. + # + self._historizeBuildSource(idBuildSrc, None); + _ = uidAuthor; ## @todo record deleter. + + self._oDb.maybeCommit(fCommit); + return True; + + def cachedLookup(self, idBuildSrc): + """ + Looks up the most recent BuildSourceData object for idBuildSrc + via an object cache. + + Returns a shared BuildSourceData object. None if not found. + Raises exception on DB error. + """ + if self.dCache is None: + self.dCache = self._oDb.getCache('BuildSourceData'); + oEntry = self.dCache.get(idBuildSrc, None); + if oEntry is None: + self._oDb.execute('SELECT *\n' + 'FROM BuildSources\n' + 'WHERE idBuildSrc = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idBuildSrc, )); + if self._oDb.getRowCount() == 0: + # Maybe it was deleted, try get the last entry. + self._oDb.execute('SELECT *\n' + 'FROM BuildSources\n' + 'WHERE idBuildSrc = %s\n' + 'ORDER BY tsExpire DESC\n' + 'LIMIT 1\n' + , (idBuildSrc, )); + elif self._oDb.getRowCount() > 1: + raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idBuildSrc)); + + if self._oDb.getRowCount() == 1: + aaoRow = self._oDb.fetchOne(); + oEntry = BuildSourceData(); + oEntry.initFromDbRow(aaoRow); + self.dCache[idBuildSrc] = oEntry; + return oEntry; + + # + # Other methods. + # + + def openBuildCursor(self, oBuildSource, sOs, sCpuArch, tsNow): + """ + Opens a cursor (SELECT) using the criteria found in the build source + and the given OS.CPUARCH. + + Returns database cursor. May raise exception on bad input or logic error. + + Used by SchedulerBase. + """ + + oCursor = self._oDb.openCursor(); + + # + # Construct the extra conditionals. + # + sExtraConditions = ''; + + # Types + if oBuildSource.asTypes is not None and oBuildSource.asTypes: + if len(oBuildSource.asTypes) == 1: + sExtraConditions += oCursor.formatBindArgs(' AND BuildCategories.sType = %s', (oBuildSource.asTypes[0],)); + else: + sExtraConditions += oCursor.formatBindArgs(' AND BuildCategories.sType IN (%s', (oBuildSource.asTypes[0],)) + for i in range(1, len(oBuildSource.asTypes) - 1): + sExtraConditions += oCursor.formatBindArgs(', %s', (oBuildSource.asTypes[i],)); + sExtraConditions += oCursor.formatBindArgs(', %s)\n', (oBuildSource.asTypes[-1],)); + + # BuildSource OSes.ARCHes. (Paranoia: use a dictionary to avoid duplicate values.) + if oBuildSource.asOsArches is not None and oBuildSource.asOsArches: + sExtraConditions += oCursor.formatBindArgs(' AND BuildCategories.asOsArches && %s', (oBuildSource.asOsArches,)); + + # TestBox OSes.ARCHes. (Paranoia: use a dictionary to avoid duplicate values.) + dOsDotArches = {}; + dOsDotArches[sOs + '.' + sCpuArch] = 1; + dOsDotArches[sOs + '.' + coreconsts.g_ksCpuArchAgnostic] = 1; + dOsDotArches[coreconsts.g_ksOsAgnostic + '.' + sCpuArch] = 1; + dOsDotArches[coreconsts.g_ksOsDotArchAgnostic] = 1; + sExtraConditions += oCursor.formatBindArgs(' AND BuildCategories.asOsArches && %s', (list(dOsDotArches.keys()),)); + + # Revision range. + if oBuildSource.iFirstRevision is not None: + sExtraConditions += oCursor.formatBindArgs(' AND Builds.iRevision >= %s\n', (oBuildSource.iFirstRevision,)); + if oBuildSource.iLastRevision is not None: + sExtraConditions += oCursor.formatBindArgs(' AND Builds.iRevision <= %s\n', (oBuildSource.iLastRevision,)); + + # Max age. + if oBuildSource.cSecMaxAge is not None: + sExtraConditions += oCursor.formatBindArgs(' AND Builds.tsCreated >= (%s - \'%s seconds\'::INTERVAL)\n', + (tsNow, oBuildSource.cSecMaxAge,)); + + # + # Execute the query. + # + oCursor.execute('SELECT Builds.*, BuildCategories.*,\n' + ' EXISTS( SELECT tsExpire\n' + ' FROM BuildBlacklist\n' + ' WHERE BuildBlacklist.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND BuildBlacklist.sProduct = %s\n' + ' AND BuildBlacklist.sBranch = %s\n' + ' AND BuildBlacklist.iFirstRevision <= Builds.iRevision\n' + ' AND BuildBlacklist.iLastRevision >= Builds.iRevision ) AS fMaybeBlacklisted\n' + 'FROM Builds, BuildCategories\n' + 'WHERE Builds.idBuildCategory = BuildCategories.idBuildCategory\n' + ' AND Builds.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND Builds.tsEffective <= %s\n' + ' AND Builds.fBinariesDeleted is FALSE\n' + ' AND BuildCategories.sProduct = %s\n' + ' AND BuildCategories.sBranch = %s\n' + + sExtraConditions + + 'ORDER BY Builds.idBuild DESC\n' + 'LIMIT 256\n' + , ( oBuildSource.sProduct, oBuildSource.sBranch, + tsNow, oBuildSource.sProduct, oBuildSource.sBranch,)); + + return oCursor; + + + def getById(self, idBuildSrc): + """Get Build Source data by idBuildSrc""" + + self._oDb.execute('SELECT *\n' + 'FROM BuildSources\n' + 'WHERE tsExpire = \'infinity\'::timestamp\n' + ' AND idBuildSrc = %s;', (idBuildSrc,)) + aRows = self._oDb.fetchAll() + if len(aRows) not in (0, 1): + raise self._oDb.integrityException( + 'Found more than one build sources with the same credentials. Database structure is corrupted.') + try: + return BuildSourceData().initFromDbRow(aRows[0]) + except IndexError: + return None + + # + # Internal helpers. + # + + def _assertUnique(self, oData, idBuildSrcIgnore): + """ Checks that the build source name is unique, raises exception if it isn't. """ + self._oDb.execute('SELECT idBuildSrc\n' + 'FROM BuildSources\n' + 'WHERE sName = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + + ('' if idBuildSrcIgnore is None else ' AND idBuildSrc <> %d\n' % (idBuildSrcIgnore,)) + , ( oData.sName, )) + if self._oDb.getRowCount() > 0: + raise TMRowAlreadyExists('A build source with name "%s" already exist.' % (oData.sName,)); + return True; + + + def _historizeBuildSource(self, idBuildSrc, tsExpire = None): + """ Historizes the current build source entry. """ + if tsExpire is None: + self._oDb.execute('UPDATE BuildSources\n' + 'SET tsExpire = CURRENT_TIMESTAMP\n' + 'WHERE idBuildSrc = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , ( idBuildSrc, )); + else: + self._oDb.execute('UPDATE BuildSources\n' + 'SET tsExpire = %s\n' + 'WHERE idBuildSrc = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , ( tsExpire, idBuildSrc, )); + return True; + + + + + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +class BuildSourceDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [BuildSourceData(),]; + +if __name__ == '__main__': + unittest.main(); + # not reached. + diff --git a/src/VBox/ValidationKit/testmanager/core/coreconsts.py b/src/VBox/ValidationKit/testmanager/core/coreconsts.py new file mode 100644 index 00000000..ec6f56a7 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/coreconsts.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +# $Id: coreconsts.py $ + +""" +Test Manager - Test Manager Constants (without a more appropriate home). +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +## OS agnostic. +g_ksOsAgnostic = 'os-agnostic'; +## All known OSes, except the agnostic one. +# See KBUILD_OSES in kBuild/header.kmk for reference. +g_kasOses = ['darwin', 'dos', 'dragonfly', 'freebsd', 'haiku', 'l4', 'linux', 'netbsd', 'nt', 'openbsd', 'os2', + 'solaris', 'win']; +## All known OSes, including the agnostic one. +# See KBUILD_OSES in kBuild/header.kmk for reference. +g_kasOsesAll = g_kasOses + [g_ksOsAgnostic,]; + + +## Architecture agnostic. +g_ksCpuArchAgnostic = 'noarch'; +## All known CPU architectures, except the agnostic one. +# See KBUILD_ARCHES in kBuild/header.kmk for reference. +g_kasCpuArches = ['amd64', 'x86', 'sparc32', 'sparc64', 's390', 's390x', 'ppc32', 'ppc64', 'mips32', 'mips64', 'ia64', + 'hppa32', 'hppa64', 'arm', 'alpha']; +## All known CPU architectures, except the agnostic one. +# See KBUILD_ARCHES in kBuild/header.kmk for reference. +g_kasCpuArchesAll = g_kasCpuArches + [g_ksCpuArchAgnostic,]; + +## All known build types +# See KBUILD_TYPE in kBuild/header.kmk for reference. +# @note 'blessed' is a special type used for release builds that has been notarized +# or attestation signed by the OS vendor. +g_kasBuildTypesAll = [ 'release', 'strict', 'profile', 'debug', 'asan', 'blessed' ]; + +## OS and CPU architecture agnostic. +g_ksOsDotArchAgnostic = 'os-agnostic.noarch'; +## Combinations of all OSes and CPU architectures, except the two agnostic ones. +# We do some of them by hand to avoid offering too many choices. +g_kasOsDotCpus = \ +[ + 'darwin.amd64', 'darwin.x86', 'darwin.ppc32', 'darwin.ppc64', 'darwin.arm', + 'dos.x86', + 'dragonfly.amd64', 'dragonfly.x86', + 'freebsd.amd64', 'freebsd.x86', 'freebsd.sparc64', 'freebsd.ia64', 'freebsd.ppc32', 'freebsd.ppc64', 'freebsd.arm', + 'freebsd.mips32', 'freebsd.mips64', + 'haiku.amd64', 'haiku.x86', + 'l4.amd64', 'l4.x86', 'l4.ppc32', 'l4.ppc64', 'l4.arm', + 'nt.amd64', 'nt.x86', 'nt.arm', 'nt.ia64', 'nt.mips32', 'nt.ppc32', 'nt.alpha', + 'win.amd64', 'win.x86', 'win.arm', 'win.ia64', 'win.mips32', 'win.ppc32', 'win.alpha', + 'os2.x86', + 'solaris.amd64', 'solaris.x86', 'solaris.sparc32', 'solaris.sparc64', +]; +for sOs in g_kasOses: + if sOs not in ['darwin', 'dos', 'dragonfly', 'freebsd', 'haiku', 'l4', 'nt', 'win', 'os2', 'solaris']: + for sArch in g_kasCpuArches: + g_kasOsDotCpus.append(sOs + '.' + sArch); +g_kasOsDotCpus.sort(); + +## Combinations of all OSes and CPU architectures, including the two agnostic ones. +g_kasOsDotCpusAll = [g_ksOsDotArchAgnostic] +g_kasOsDotCpusAll.extend(g_kasOsDotCpus); +for sOs in g_kasOsesAll: + g_kasOsDotCpusAll.append(sOs + '.' + g_ksCpuArchAgnostic); +for sArch in g_kasCpuArchesAll: + g_kasOsDotCpusAll.append(g_ksOsAgnostic + '.' + sArch); +g_kasOsDotCpusAll.sort(); + diff --git a/src/VBox/ValidationKit/testmanager/core/db.py b/src/VBox/ValidationKit/testmanager/core/db.py new file mode 100755 index 00000000..ad3c943c --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/db.py @@ -0,0 +1,745 @@ +# -*- coding: utf-8 -*- +# $Id: db.py $ + +""" +Test Manager - Database Interface. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard python imports. +import datetime; +import os; +import sys; +import psycopg2; # pylint: disable=import-error +import psycopg2.extensions; # pylint: disable=import-error + +# Validation Kit imports. +from common import utils, webutils; +from testmanager import config; + +# Fix psycho unicode handling in psycopg2 with python 2.x. +if sys.version_info[0] < 3: + psycopg2.extensions.register_type(psycopg2.extensions.UNICODE); + psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY); +else: + unicode = str; # pylint: disable=redefined-builtin,invalid-name + + + +def isDbTimestampInfinity(tsValue): + """ + Checks if tsValue is an infinity timestamp. + """ + ## @todo improve this test... + return tsValue.year >= 9999; + +def isDbTimestamp(oValue): + """ + Checks if oValue is a DB timestamp object. + """ + if isinstance(oValue, datetime.datetime): + return True; + if utils.isString(oValue): + ## @todo detect strings as well. + return False; + return getattr(oValue, 'pydatetime', None) is not None; + +def dbTimestampToDatetime(oValue): + """ + Converts a database timestamp to a datetime instance. + """ + if isinstance(oValue, datetime.datetime): + return oValue; + if utils.isString(oValue): + return utils.parseIsoTimestamp(oValue); + return oValue.pydatetime(); + +def dbTimestampToZuluDatetime(oValue): + """ + Converts a database timestamp to a zulu datetime instance. + """ + tsValue = dbTimestampToDatetime(oValue); + + class UTC(datetime.tzinfo): + """UTC TZ Info Class""" + def utcoffset(self, _): + return datetime.timedelta(0); + def tzname(self, _): + return "UTC"; + def dst(self, _): + return datetime.timedelta(0); + if tsValue.tzinfo is not None: + tsValue = tsValue.astimezone(UTC()); + else: + tsValue = tsValue.replace(tzinfo=UTC()); + return tsValue; + +def dbTimestampPythonNow(): + """ + Gets the current python timestamp in a database compatible way. + """ + return dbTimestampToZuluDatetime(datetime.datetime.utcnow()); + +def dbOneTickIntervalString(): + """ + Returns the interval string for one tick. + + Mogrify the return value into the SQL: + "... %s::INTERVAL ..." + or + "INTERVAL %s" + The completed SQL will contain the necessary ticks. + """ + return '1 microsecond'; + +def dbTimestampMinusOneTick(oValue): + """ + Returns a new timestamp that's one tick before the given one. + """ + oValue = dbTimestampToZuluDatetime(oValue); + return oValue - datetime.timedelta(microseconds = 1); + +def dbTimestampPlusOneTick(oValue): + """ + Returns a new timestamp that's one tick after the given one. + """ + oValue = dbTimestampToZuluDatetime(oValue); + return oValue + datetime.timedelta(microseconds = 1); + +def isDbInterval(oValue): + """ + Checks if oValue is a DB interval object. + """ + if isinstance(oValue, datetime.timedelta): + return True; + return False; + + +class TMDatabaseIntegrityException(Exception): + """ + Herolds a database integrity error up the callstack. + + Do NOT use directly, only thru TMDatabaseConnection.integrityException. + Otherwise, we won't be able to log the issue. + """ + pass; # pylint: disable=unnecessary-pass + + +class TMDatabaseCursor(object): + """ Cursor wrapper class. """ + + def __init__(self, oDb, oCursor): + self._oDb = oDb; + self._oCursor = oCursor; + + def execute(self, sOperation, aoArgs = None): + """ See TMDatabaseConnection.execute()""" + return self._oDb.executeInternal(self._oCursor, sOperation, aoArgs, utils.getCallerName()); + + def callProc(self, sProcedure, aoArgs = None): + """ See TMDatabaseConnection.callProc()""" + return self._oDb.callProcInternal(self._oCursor, sProcedure, aoArgs, utils.getCallerName()); + + def insertList(self, sInsertSql, aoList, fnEntryFmt): + """ See TMDatabaseConnection.insertList. """ + return self._oDb.insertListInternal(self._oCursor, sInsertSql, aoList, fnEntryFmt, utils.getCallerName()); + + def fetchOne(self): + """Wrapper around Psycopg2.cursor.fetchone.""" + return self._oCursor.fetchone(); + + def fetchMany(self, cRows = None): + """Wrapper around Psycopg2.cursor.fetchmany.""" + return self._oCursor.fetchmany(cRows if cRows is not None else self._oCursor.arraysize); + + def fetchAll(self): + """Wrapper around Psycopg2.cursor.fetchall.""" + return self._oCursor.fetchall(); + + def getRowCount(self): + """Wrapper around Psycopg2.cursor.rowcount.""" + return self._oCursor.rowcount; + + def formatBindArgs(self, sStatement, aoArgs): + """Wrapper around Psycopg2.cursor.mogrify.""" + oRet = self._oCursor.mogrify(sStatement, aoArgs); + if sys.version_info[0] >= 3 and not isinstance(oRet, str): + oRet = oRet.decode('utf-8'); + return oRet; + + def copyExpert(self, sSqlCopyStmt, oFile, cbBuf = 8192): + """ See TMDatabaseConnection.copyExpert()""" + return self._oCursor.copy_expert(sSqlCopyStmt, oFile, cbBuf); + + @staticmethod + def isTsInfinity(tsValue): + """ Checks if tsValue is an infinity timestamp. """ + return isDbTimestampInfinity(tsValue); + + +class TMDatabaseConnection(object): + """ + Test Manager Database Access class. + + This class contains no logic, just raw access abstraction and utilities, + as well as some debug help and some statistics. + """ + + def __init__(self, fnDPrint = None, oSrvGlue = None): + """ + Database connection wrapper. + The fnDPrint is for debug logging of all database activity. + + Raises an exception on failure. + """ + + sAppName = '%s-%s' % (os.getpid(), os.path.basename(sys.argv[0]),) + if len(sAppName) >= 64: + sAppName = sAppName[:64]; + os.environ['PGAPPNAME'] = sAppName; + + dArgs = \ + { \ + 'database': config.g_ksDatabaseName, + 'user': config.g_ksDatabaseUser, + 'password': config.g_ksDatabasePassword, + # 'application_name': sAppName, - Darn stale debian! :/ + }; + if config.g_ksDatabaseAddress is not None: + dArgs['host'] = config.g_ksDatabaseAddress; + if config.g_ksDatabasePort is not None: + dArgs['port'] = config.g_ksDatabasePort; + self._oConn = psycopg2.connect(**dArgs); # pylint: disable=star-args + self._oConn.set_client_encoding('UTF-8'); + self._oCursor = self._oConn.cursor(); + self._oExplainConn = None; + self._oExplainCursor = None; + if config.g_kfWebUiSqlTraceExplain and config.g_kfWebUiSqlTrace: + self._oExplainConn = psycopg2.connect(**dArgs); # pylint: disable=star-args + self._oExplainConn.set_client_encoding('UTF-8'); + self._oExplainCursor = self._oExplainConn.cursor(); + self._fTransaction = False; + self._tsCurrent = None; + self._tsCurrentMinusOne = None; + + assert self.isAutoCommitting() is False; + + # Debug and introspection. + self._fnDPrint = fnDPrint; + self._aoTraceBack = []; + + # Exception class handles. + self.oXcptError = psycopg2.Error; + + if oSrvGlue is not None: + oSrvGlue.registerDebugInfoCallback(self.debugInfoCallback); + + # Object caches (used by database logic classes). + self.ddCaches = {}; + + def isAutoCommitting(self): + """ Work around missing autocommit attribute in older versions.""" + return getattr(self._oConn, 'autocommit', False); + + def close(self): + """ + Closes the connection and renders all cursors useless. + """ + if self._oCursor is not None: + self._oCursor.close(); + self._oCursor = None; + + if self._oConn is not None: + self._oConn.close(); + self._oConn = None; + + if self._oExplainCursor is not None: + self._oExplainCursor.close(); + self._oExplainCursor = None; + + if self._oExplainConn is not None: + self._oExplainConn.close(); + self._oExplainConn = None; + + + def _startedTransaction(self): + """ + Called to work the _fTransaction and related variables when starting + a transaction. + """ + self._fTransaction = True; + self._tsCurrent = None; + self._tsCurrentMinusOne = None; + return None; + + def _endedTransaction(self): + """ + Called to work the _fTransaction and related variables when ending + a transaction. + """ + self._fTransaction = False; + self._tsCurrent = None; + self._tsCurrentMinusOne = None; + return None; + + def begin(self): + """ + Currently just for marking where a transaction starts in the code. + """ + assert self._oConn is not None; + assert self.isAutoCommitting() is False; + self._aoTraceBack.append([utils.timestampNano(), 'START TRANSACTION', 0, 0, utils.getCallerName(), None]); + self._startedTransaction(); + return True; + + def commit(self, sCallerName = None): + """ Wrapper around Psycopg2.connection.commit.""" + assert self._fTransaction is True; + + nsStart = utils.timestampNano(); + oRc = self._oConn.commit(); + cNsElapsed = utils.timestampNano() - nsStart; + + if sCallerName is None: + sCallerName = utils.getCallerName(); + self._aoTraceBack.append([nsStart, 'COMMIT', cNsElapsed, 0, sCallerName, None]); + self._endedTransaction(); + return oRc; + + def maybeCommit(self, fCommit): + """ + Commits if fCommit is True. + Returns True if committed, False if not. + """ + if fCommit is True: + self.commit(utils.getCallerName()); + return True; + return False; + + def rollback(self): + """ Wrapper around Psycopg2.connection.rollback.""" + nsStart = utils.timestampNano(); + oRc = self._oConn.rollback(); + cNsElapsed = utils.timestampNano() - nsStart; + + self._aoTraceBack.append([nsStart, 'ROLLBACK', cNsElapsed, 0, utils.getCallerName(), None]); + self._endedTransaction(); + return oRc; + + # + # Internal cursor workers. + # + + def executeInternal(self, oCursor, sOperation, aoArgs, sCallerName): + """ + Execute a query or command. + + Mostly a wrapper around the psycopg2 cursor method with the same name, + but collect data for traceback. + """ + if aoArgs is not None: + sBound = oCursor.mogrify(unicode(sOperation), aoArgs); + elif sOperation.find('%') < 0: + sBound = oCursor.mogrify(unicode(sOperation), []); + else: + sBound = unicode(sOperation); + + if sys.version_info[0] >= 3 and not isinstance(sBound, str): + sBound = sBound.decode('utf-8'); # pylint: disable=redefined-variable-type + + aasExplain = None; + if self._oExplainCursor is not None and not sBound.startswith('DROP'): + try: + if config.g_kfWebUiSqlTraceExplainTiming: + self._oExplainCursor.execute('EXPLAIN (ANALYZE, BUFFERS, COSTS, VERBOSE, TIMING) ' + sBound); + else: + self._oExplainCursor.execute('EXPLAIN (ANALYZE, BUFFERS, COSTS, VERBOSE) ' + sBound); + except Exception as oXcpt: + aasExplain = [ ['Explain exception: '], [str(oXcpt)]]; + try: self._oExplainConn.rollback(); + except: pass; + else: + aasExplain = self._oExplainCursor.fetchall(); + + nsStart = utils.timestampNano(); + try: + oRc = oCursor.execute(sBound); + except Exception as oXcpt: + cNsElapsed = utils.timestampNano() - nsStart; + self._aoTraceBack.append([nsStart, 'oXcpt=%s; Statement: %s' % (oXcpt, sBound), cNsElapsed, 0, sCallerName, None]); + if self._fnDPrint is not None: + self._fnDPrint('db::execute %u ns, caller %s: oXcpt=%s; Statement: %s' + % (cNsElapsed, sCallerName, oXcpt, sBound)); + raise; + cNsElapsed = utils.timestampNano() - nsStart; + + if self._fTransaction is False and not self.isAutoCommitting(): # Even SELECTs starts transactions with psycopg2, see FAQ. + self._aoTraceBack.append([nsStart, '[START TRANSACTION]', 0, 0, sCallerName, None]); + self._startedTransaction(); + self._aoTraceBack.append([nsStart, sBound, cNsElapsed, oCursor.rowcount, sCallerName, aasExplain]); + if self._fnDPrint is not None: + self._fnDPrint('db::execute %u ns, caller %s: "\n%s"' % (cNsElapsed, sCallerName, sBound)); + if self.isAutoCommitting(): + self._aoTraceBack.append([nsStart, '[AUTO COMMIT]', 0, 0, sCallerName, None]); + + return oRc; + + def callProcInternal(self, oCursor, sProcedure, aoArgs, sCallerName): + """ + Call a stored procedure. + + Mostly a wrapper around the psycopg2 cursor method 'callproc', but + collect data for traceback. + """ + if aoArgs is None: + aoArgs = []; + + nsStart = utils.timestampNano(); + try: + oRc = oCursor.callproc(sProcedure, aoArgs); + except Exception as oXcpt: + cNsElapsed = utils.timestampNano() - nsStart; + self._aoTraceBack.append([nsStart, 'oXcpt=%s; Calling: %s(%s)' % (oXcpt, sProcedure, aoArgs), + cNsElapsed, 0, sCallerName, None]); + if self._fnDPrint is not None: + self._fnDPrint('db::callproc %u ns, caller %s: oXcpt=%s; Calling: %s(%s)' + % (cNsElapsed, sCallerName, oXcpt, sProcedure, aoArgs)); + raise; + cNsElapsed = utils.timestampNano() - nsStart; + + if self._fTransaction is False and not self.isAutoCommitting(): # Even SELECTs starts transactions with psycopg2, see FAQ. + self._aoTraceBack.append([nsStart, '[START TRANSACTION]', 0, 0, sCallerName, None]); + self._startedTransaction(); + self._aoTraceBack.append([nsStart, '%s(%s)' % (sProcedure, aoArgs), cNsElapsed, oCursor.rowcount, sCallerName, None]); + if self._fnDPrint is not None: + self._fnDPrint('db::callproc %u ns, caller %s: "%s(%s)"' % (cNsElapsed, sCallerName, sProcedure, aoArgs)); + if self.isAutoCommitting(): + self._aoTraceBack.append([nsStart, '[AUTO COMMIT]', 0, 0, sCallerName, sCallerName, None]); + + return oRc; + + def insertListInternal(self, oCursor, sInsertSql, aoList, fnEntryFmt, sCallerName): + """ + Optimizes the insertion of a list of values. + """ + oRc = None; + asValues = []; + for aoEntry in aoList: + asValues.append(fnEntryFmt(aoEntry)); + if len(asValues) > 256: + oRc = self.executeInternal(oCursor, sInsertSql + 'VALUES' + ', '.join(asValues), None, sCallerName); + asValues = []; + if asValues: + oRc = self.executeInternal(oCursor, sInsertSql + 'VALUES' + ', '.join(asValues), None, sCallerName); + return oRc + + def _fetchOne(self, oCursor): + """Wrapper around Psycopg2.cursor.fetchone.""" + oRow = oCursor.fetchone() + if self._fnDPrint is not None: + self._fnDPrint('db:fetchOne returns: %s' % (oRow,)); + return oRow; + + def _fetchMany(self, oCursor, cRows): + """Wrapper around Psycopg2.cursor.fetchmany.""" + return oCursor.fetchmany(cRows if cRows is not None else oCursor.arraysize); + + def _fetchAll(self, oCursor): + """Wrapper around Psycopg2.cursor.fetchall.""" + return oCursor.fetchall() + + def _getRowCountWorker(self, oCursor): + """Wrapper around Psycopg2.cursor.rowcount.""" + return oCursor.rowcount; + + + # + # Default cursor access. + # + + def execute(self, sOperation, aoArgs = None): + """ + Execute a query or command. + + Mostly a wrapper around the psycopg2 cursor method with the same name, + but collect data for traceback. + """ + return self.executeInternal(self._oCursor, sOperation, aoArgs, utils.getCallerName()); + + def callProc(self, sProcedure, aoArgs = None): + """ + Call a stored procedure. + + Mostly a wrapper around the psycopg2 cursor method 'callproc', but + collect data for traceback. + """ + return self.callProcInternal(self._oCursor, sProcedure, aoArgs, utils.getCallerName()); + + def insertList(self, sInsertSql, aoList, fnEntryFmt): + """ + Optimizes the insertion of a list of values. + """ + return self.insertListInternal(self._oCursor, sInsertSql, aoList, fnEntryFmt, utils.getCallerName()); + + def fetchOne(self): + """Wrapper around Psycopg2.cursor.fetchone.""" + return self._oCursor.fetchone(); + + def fetchMany(self, cRows = None): + """Wrapper around Psycopg2.cursor.fetchmany.""" + return self._oCursor.fetchmany(cRows if cRows is not None else self._oCursor.arraysize); + + def fetchAll(self): + """Wrapper around Psycopg2.cursor.fetchall.""" + return self._oCursor.fetchall(); + + def getRowCount(self): + """Wrapper around Psycopg2.cursor.rowcount.""" + return self._oCursor.rowcount; + + def formatBindArgs(self, sStatement, aoArgs): + """Wrapper around Psycopg2.cursor.mogrify.""" + oRet = self._oCursor.mogrify(sStatement, aoArgs); + if sys.version_info[0] >= 3 and not isinstance(oRet, str): + oRet = oRet.decode('utf-8'); + return oRet; + + def copyExpert(self, sSqlCopyStmt, oFile, cbBuf = 8192): + """ Wrapper around Psycopg2.cursor.copy_expert. """ + return self._oCursor.copy_expert(sSqlCopyStmt, oFile, cbBuf); + + def getCurrentTimestamps(self): + """ + Returns the current timestamp and the current timestamp minus one tick. + This will start a transaction if necessary. + """ + if self._tsCurrent is None: + self.execute('SELECT CURRENT_TIMESTAMP, CURRENT_TIMESTAMP - INTERVAL \'1 microsecond\''); + (self._tsCurrent, self._tsCurrentMinusOne) = self.fetchOne(); + return (self._tsCurrent, self._tsCurrentMinusOne); + + def getCurrentTimestamp(self): + """ + Returns the current timestamp. + This will start a transaction if necessary. + """ + if self._tsCurrent is None: + self.getCurrentTimestamps(); + return self._tsCurrent; + + def getCurrentTimestampMinusOne(self): + """ + Returns the current timestamp minus one tick. + This will start a transaction if necessary. + """ + if self._tsCurrentMinusOne is None: + self.getCurrentTimestamps(); + return self._tsCurrentMinusOne; + + + # + # Additional cursors. + # + def openCursor(self): + """ + Opens a new cursor (TMDatabaseCursor). + """ + oCursor = self._oConn.cursor(); + return TMDatabaseCursor(self, oCursor); + + # + # Cache support. + # + def getCache(self, sType): + """ Returns the cache dictionary for this data type. """ + dRet = self.ddCaches.get(sType, None); + if dRet is None: + dRet = {}; + self.ddCaches[sType] = dRet; + return dRet; + + + # + # Utilities. + # + + @staticmethod + def isTsInfinity(tsValue): + """ Checks if tsValue is an infinity timestamp. """ + return isDbTimestampInfinity(tsValue); + + # + # Error stuff. + # + def integrityException(self, sMessage): + """ + Database integrity reporter and exception factory. + Returns an TMDatabaseIntegrityException which the caller can raise. + """ + ## @todo Create a new database connection and log the issue in the SystemLog table. + ## Alternatively, rollback whatever is going on and do it using the current one. + return TMDatabaseIntegrityException(sMessage); + + + # + # Debugging. + # + + def dprint(self, sText): + """ + Debug output. + """ + if not self._fnDPrint: + return False; + self._fnDPrint(sText); + return True; + + def debugHtmlReport(self, tsStart = 0): + """ + Used to get a SQL activity dump as HTML, usually for WuiBase._sDebug. + """ + cNsElapsed = 0; + for aEntry in self._aoTraceBack: + cNsElapsed += aEntry[2]; + + sDebug = '<h3>SQL Debug Log (total time %s ns):</h3>\n' \ + '<table class="tmsqltable">\n' \ + ' <tr>\n' \ + ' <th>No.</th>\n' \ + ' <th>Timestamp (ns)</th>\n' \ + ' <th>Elapsed (ns)</th>\n' \ + ' <th>Rows Returned</th>\n' \ + ' <th>Command</th>\n' \ + ' <th>Caller</th>\n' \ + ' </tr>\n' \ + % (utils.formatNumber(cNsElapsed, ' '),); + + iEntry = 0; + for aEntry in self._aoTraceBack: + iEntry += 1; + sDebug += ' <tr>\n' \ + ' <td>%s</td>\n' \ + ' <td>%s</td>\n' \ + ' <td>%s</td>\n' \ + ' <td>%s</td>\n' \ + ' <td><pre>%s</pre></td>\n' \ + ' <td>%s</td>\n' \ + ' </tr>\n' \ + % (iEntry, + utils.formatNumber(aEntry[0] - tsStart, ' '), + utils.formatNumber(aEntry[2], ' '), + utils.formatNumber(aEntry[3], ' '), + webutils.escapeElem(aEntry[1]), + webutils.escapeElem(aEntry[4]), + ); + if aEntry[5] is not None: + sDebug += ' <tr>\n' \ + ' <td colspan="6"><pre style="white-space: pre-wrap;">%s</pre></td>\n' \ + ' </tr>\n' \ + % (webutils.escapeElem('\n'.join([aoRow[0] for aoRow in aEntry[5]])),); + + sDebug += '</table>'; + return sDebug; + + def debugTextReport(self, tsStart = 0): + """ + Used to get a SQL activity dump as text. + """ + cNsElapsed = 0; + for aEntry in self._aoTraceBack: + cNsElapsed += aEntry[2]; + + sHdr = 'SQL Debug Log (total time %s ns)' % (utils.formatNumber(cNsElapsed),); + sDebug = sHdr + '\n' + '-' * len(sHdr) + '\n'; + + iEntry = 0; + for aEntry in self._aoTraceBack: + iEntry += 1; + sHdr = 'Query #%s Timestamp: %s ns Elapsed: %s ns Rows: %s Caller: %s' \ + % ( iEntry, + utils.formatNumber(aEntry[0] - tsStart), + utils.formatNumber(aEntry[2]), + utils.formatNumber(aEntry[3]), + aEntry[4], ); + sDebug += '\n' + sHdr + '\n' + '-' * len(sHdr) + '\n'; + + sDebug += aEntry[1]; + if sDebug[-1] != '\n': + sDebug += '\n'; + + if aEntry[5] is not None: + sDebug += 'Explain:\n' \ + ' %s\n' \ + % ( '\n'.join([aoRow[0] for aoRow in aEntry[5]]),); + + return sDebug; + + def debugInfoCallback(self, oGlue, fHtml): + """ Called back by the glue code on error. """ + oGlue.write('\n'); + if not fHtml: oGlue.write(self.debugTextReport()); + else: oGlue.write(self.debugHtmlReport()); + oGlue.write('\n'); + return True; + + def debugEnableExplain(self): + """ Enabled explain. """ + if self._oExplainConn is None: + dArgs = \ + { \ + 'database': config.g_ksDatabaseName, + 'user': config.g_ksDatabaseUser, + 'password': config.g_ksDatabasePassword, + # 'application_name': sAppName, - Darn stale debian! :/ + }; + if config.g_ksDatabaseAddress is not None: + dArgs['host'] = config.g_ksDatabaseAddress; + if config.g_ksDatabasePort is not None: + dArgs['port'] = config.g_ksDatabasePort; + self._oExplainConn = psycopg2.connect(**dArgs); # pylint: disable=star-args + self._oExplainCursor = self._oExplainConn.cursor(); + return True; + + def debugDisableExplain(self): + """ Disables explain. """ + self._oExplainCursor = None; + self._oExplainConn = None + return True; + + def debugIsExplainEnabled(self): + """ Check if explaining of SQL statements is enabled. """ + return self._oExplainConn is not None; + diff --git a/src/VBox/ValidationKit/testmanager/core/dbobjcache.py b/src/VBox/ValidationKit/testmanager/core/dbobjcache.py new file mode 100755 index 00000000..adf0a88c --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/dbobjcache.py @@ -0,0 +1,200 @@ +# -*- coding: utf-8 -*- +# $Id: dbobjcache.py $ + +""" +Test Manager - Database object cache. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Validation Kit imports. +from testmanager.core.base import ModelLogicBase; + + +class DatabaseObjCache(ModelLogicBase): + """ + Database object cache. + + This is mainly for reports and test results where we wish to get further + information on a data series or similar. The cache should reduce database + lookups as well as pyhon memory footprint. + + Note! Dependecies are imported when needed to avoid potential cylic dependency issues. + """ + + ## @name Cache object types. + ## @{ + ksObjType_TestResultStrTab_idStrName = 0; + ksObjType_BuildCategory_idBuildCategory = 1; + ksObjType_TestBox_idTestBox = 2; + ksObjType_TestBox_idGenTestBox = 3; + ksObjType_TestCase_idTestCase = 4; + ksObjType_TestCase_idGenTestCase = 5; + ksObjType_TestCaseArgs_idTestCaseArgs = 6; + ksObjType_TestCaseArgs_idGenTestCaseArgs = 7; + ksObjType_VcsRevision_sRepository_iRevision = 8; + ksObjType_End = 9; + ## @} + + def __init__(self, oDb, tsNow = None, sPeriodBack = None, cHoursBack = None): + ModelLogicBase.__init__(self, oDb); + + self.tsNow = tsNow; + self.sPeriodBack = sPeriodBack; + if sPeriodBack is None and cHoursBack is not None: + self.sPeriodBack = '%u hours' % cHoursBack; + + self._adCache = ( + {}, {}, {}, {}, + {}, {}, {}, {}, + {}, + ); + assert(len(self._adCache) == self.ksObjType_End); + + def _handleDbException(self): + """ Deals with database exceptions. """ + #self._oDb.rollback(); + return False; + + def getTestResultString(self, idStrName): + """ Gets a string from the TestResultStrTab. """ + sRet = self._adCache[self.ksObjType_TestResultStrTab_idStrName].get(idStrName); + if sRet is None: + # Load cache entry. + self._oDb.execute('SELECT sValue FROM TestResultStrTab WHERE idStr = %s', (idStrName,)); + sRet = self._oDb.fetchOne()[0]; + self._adCache[self.ksObjType_TestResultStrTab_idStrName][idStrName] = sRet + return sRet; + + def getBuildCategory(self, idBuildCategory): + """ Gets the corresponding BuildCategoryData object. """ + oRet = self._adCache[self.ksObjType_BuildCategory_idBuildCategory].get(idBuildCategory); + if oRet is None: + # Load cache entry. + from testmanager.core.build import BuildCategoryData; + oRet = BuildCategoryData(); + try: oRet.initFromDbWithId(self._oDb, idBuildCategory); + except: self._handleDbException(); raise; + self._adCache[self.ksObjType_BuildCategory_idBuildCategory][idBuildCategory] = oRet; + return oRet; + + def getTestBox(self, idTestBox): + """ Gets the corresponding TestBoxData object. """ + oRet = self._adCache[self.ksObjType_TestBox_idTestBox].get(idTestBox); + if oRet is None: + # Load cache entry. + from testmanager.core.testbox import TestBoxData; + oRet = TestBoxData(); + try: oRet.initFromDbWithId(self._oDb, idTestBox, self.tsNow, self.sPeriodBack); + except: self._handleDbException(); raise; + else: self._adCache[self.ksObjType_TestBox_idGenTestBox][oRet.idGenTestBox] = oRet; + self._adCache[self.ksObjType_TestBox_idTestBox][idTestBox] = oRet; + return oRet; + + def getTestCase(self, idTestCase): + """ Gets the corresponding TestCaseData object. """ + oRet = self._adCache[self.ksObjType_TestCase_idTestCase].get(idTestCase); + if oRet is None: + # Load cache entry. + from testmanager.core.testcase import TestCaseData; + oRet = TestCaseData(); + try: oRet.initFromDbWithId(self._oDb, idTestCase, self.tsNow, self.sPeriodBack); + except: self._handleDbException(); raise; + else: self._adCache[self.ksObjType_TestCase_idGenTestCase][oRet.idGenTestCase] = oRet; + self._adCache[self.ksObjType_TestCase_idTestCase][idTestCase] = oRet; + return oRet; + + def getTestCaseArgs(self, idTestCaseArgs): + """ Gets the corresponding TestCaseArgsData object. """ + oRet = self._adCache[self.ksObjType_TestCaseArgs_idTestCaseArgs].get(idTestCaseArgs); + if oRet is None: + # Load cache entry. + from testmanager.core.testcaseargs import TestCaseArgsData; + oRet = TestCaseArgsData(); + try: oRet.initFromDbWithId(self._oDb, idTestCaseArgs, self.tsNow, self.sPeriodBack); + except: self._handleDbException(); raise; + else: self._adCache[self.ksObjType_TestCaseArgs_idGenTestCaseArgs][oRet.idGenTestCaseArgs] = oRet; + self._adCache[self.ksObjType_TestCaseArgs_idTestCaseArgs][idTestCaseArgs] = oRet; + return oRet; + + def preloadVcsRevInfo(self, sRepository, aiRevisions): + """ + Preloads VCS revision information. + ASSUMES aiRevisions does not contain duplicate keys. + """ + from testmanager.core.vcsrevisions import VcsRevisionData; + dRepo = self._adCache[self.ksObjType_VcsRevision_sRepository_iRevision].get(sRepository); + if dRepo is None: + dRepo = {}; + self._adCache[self.ksObjType_VcsRevision_sRepository_iRevision][sRepository] = dRepo; + aiFiltered = aiRevisions; + else: + aiFiltered = []; + for iRevision in aiRevisions: + if iRevision not in dRepo: + aiFiltered.append(iRevision); + if aiFiltered: + self._oDb.execute('SELECT *\n' + 'FROM VcsRevisions\n' + 'WHERE sRepository = %s\n' + ' AND iRevision IN (' + ','.join([str(i) for i in aiFiltered]) + ')' + , ( sRepository, )); + for aoRow in self._oDb.fetchAll(): + oInfo = VcsRevisionData().initFromDbRow(aoRow); + dRepo[oInfo.iRevision] = oInfo; + return True; + + def getVcsRevInfo(self, sRepository, iRevision): + """ + Gets the corresponding VcsRevisionData object. + May return a default (all NULLs) VcsRevisionData object if the revision + information isn't available in the database yet. + """ + dRepo = self._adCache[self.ksObjType_VcsRevision_sRepository_iRevision].get(sRepository); + if dRepo is not None: + oRet = dRepo.get(iRevision); + else: + dRepo = {}; + self._adCache[self.ksObjType_VcsRevision_sRepository_iRevision][sRepository] = dRepo; + oRet = None; + if oRet is None: + from testmanager.core.vcsrevisions import VcsRevisionLogic; + oRet = VcsRevisionLogic(self._oDb).tryFetch(sRepository, iRevision); + if oRet is None: + from testmanager.core.vcsrevisions import VcsRevisionData; + oRet = VcsRevisionData(); + dRepo[iRevision] = oRet; + return oRet; + diff --git a/src/VBox/ValidationKit/testmanager/core/failurecategory.py b/src/VBox/ValidationKit/testmanager/core/failurecategory.py new file mode 100755 index 00000000..ce0c9fca --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/failurecategory.py @@ -0,0 +1,392 @@ +# -*- coding: utf-8 -*- +# $Id: failurecategory.py $ + +""" +Test Manager - Failure Categories. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard Python imports. +import sys; + +# Validation Kit imports. +from testmanager.core.base import ModelDataBase, ModelLogicBase, TMRowInUse, TMInvalidData, TMRowNotFound, \ + ChangeLogEntry, AttributeChangeEntry; +from testmanager.core.useraccount import UserAccountLogic; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + xrange = range; # pylint: disable=redefined-builtin,invalid-name + + +class FailureCategoryData(ModelDataBase): + """ + Failure Category Data. + """ + + ksIdAttr = 'idFailureCategory'; + + ksParam_idFailureCategory = 'FailureCategory_idFailureCategory' + ksParam_tsEffective = 'FailureCategory_tsEffective' + ksParam_tsExpire = 'FailureCategory_tsExpire' + ksParam_uidAuthor = 'FailureCategory_uidAuthor' + ksParam_sShort = 'FailureCategory_sShort' + ksParam_sFull = 'FailureCategory_sFull' + + kasAllowNullAttributes = [ 'idFailureCategory', 'tsEffective', 'tsExpire', 'uidAuthor' ] + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + + self.idFailureCategory = None + self.tsEffective = None + self.tsExpire = None + self.uidAuthor = None + self.sShort = None + self.sFull = None + + def initFromDbRow(self, aoRow): + """ + Re-initializes the data with a row from a SELECT * FROM FailureCategoryes. + + Returns self. Raises exception if the row is None or otherwise invalid. + """ + + if aoRow is None: + raise TMRowNotFound('Failure Category not found.'); + + self.idFailureCategory = aoRow[0] + self.tsEffective = aoRow[1] + self.tsExpire = aoRow[2] + self.uidAuthor = aoRow[3] + self.sShort = aoRow[4] + self.sFull = aoRow[5] + + return self + + def initFromDbWithId(self, oDb, idFailureCategory, tsNow = None, sPeriodBack = None): + """ + Initialize from the database, given the ID of a row. + """ + oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb, + 'SELECT *\n' + 'FROM FailureCategories\n' + 'WHERE idFailureCategory = %s\n' + , ( idFailureCategory,), tsNow, sPeriodBack)); + aoRow = oDb.fetchOne() + if aoRow is None: + raise TMRowNotFound('idFailureCategory=%s not found (tsNow=%s sPeriodBack=%s)' + % (idFailureCategory, tsNow, sPeriodBack,)); + return self.initFromDbRow(aoRow); + + +class FailureCategoryLogic(ModelLogicBase): # pylint: disable=too-few-public-methods + """ + Failure Category logic. + """ + + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb) + self.dCache = None; + + def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None): + """ + Fetches Failure Category records. + + Returns an array (list) of FailureCategoryData items, empty list if none. + Raises exception on error. + """ + _ = aiSortColumns; + + if tsNow is None: + self._oDb.execute('SELECT *\n' + 'FROM FailureCategories\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY idFailureCategory ASC\n' + 'LIMIT %s OFFSET %s\n' + , (cMaxRows, iStart,)); + else: + self._oDb.execute('SELECT *\n' + 'FROM FailureCategories\n' + 'WHERE tsExpire > %s\n' + ' AND tsEffective <= %s\n' + 'ORDER BY idFailureCategory ASC\n' + 'LIMIT %s OFFSET %s\n' + , (tsNow, tsNow, cMaxRows, iStart,)); + + aoRows = [] + for aoRow in self._oDb.fetchAll(): + aoRows.append(FailureCategoryData().initFromDbRow(aoRow)) + return aoRows + + + def fetchForChangeLog(self, idFailureCategory, iStart, cMaxRows, tsNow): # pylint: disable=too-many-locals + """ + Fetches change log entries for a failure reason. + + Returns an array of ChangeLogEntry instance and an indicator whether + there are more entries. + Raises exception on error. + """ + if tsNow is None: + tsNow = self._oDb.getCurrentTimestamp(); + + # 1. Get a list of the relevant changes. + self._oDb.execute('SELECT * FROM FailureCategories WHERE idFailureCategory = %s AND tsEffective <= %s\n' + 'ORDER BY tsEffective DESC\n' + 'LIMIT %s OFFSET %s\n' + , ( idFailureCategory, tsNow, cMaxRows + 1, iStart, )); + aoRows = []; + for aoChange in self._oDb.fetchAll(): + aoRows.append(FailureCategoryData().initFromDbRow(aoChange)); + + # 2. Calculate the changes. + aoEntries = []; + for i in xrange(0, len(aoRows) - 1): + oNew = aoRows[i]; + oOld = aoRows[i + 1]; + + aoChanges = []; + for sAttr in oNew.getDataAttributes(): + if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', ]: + oOldAttr = getattr(oOld, sAttr); + oNewAttr = getattr(oNew, sAttr); + if oOldAttr != oNewAttr: + aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr))); + + aoEntries.append(ChangeLogEntry(oNew.uidAuthor, None, oNew.tsEffective, oNew.tsExpire, oNew, oOld, aoChanges)); + + # If we're at the end of the log, add the initial entry. + if len(aoRows) <= cMaxRows and aoRows: + oNew = aoRows[-1]; + aoEntries.append(ChangeLogEntry(oNew.uidAuthor, None, oNew.tsEffective, oNew.tsExpire, oNew, None, [])); + + return (UserAccountLogic(self._oDb).resolveChangeLogAuthors(aoEntries), len(aoRows) > cMaxRows); + + + def getFailureCategoriesForCombo(self, tsEffective = None): + """ + Gets the list of Failure Categories for a combo box. + Returns an array of (value [idFailureCategory], drop-down-name [sShort], + hover-text [sFull]) tuples. + """ + if tsEffective is None: + self._oDb.execute('SELECT idFailureCategory, sShort, sFull\n' + 'FROM FailureCategories\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY sShort') + else: + self._oDb.execute('SELECT idFailureCategory, sShort, sFull\n' + 'FROM FailureCategories\n' + 'WHERE tsExpire > %s\n' + ' AND tsEffective <= %s\n' + 'ORDER BY sShort' + , (tsEffective, tsEffective)) + return self._oDb.fetchAll() + + + def getById(self, idFailureCategory): + """Get Failure Category data by idFailureCategory""" + + self._oDb.execute('SELECT *\n' + 'FROM FailureCategories\n' + 'WHERE tsExpire = \'infinity\'::timestamp\n' + ' AND idFailureCategory = %s;', (idFailureCategory,)) + aRows = self._oDb.fetchAll() + if len(aRows) not in (0, 1): + raise self._oDb.integrityException( + 'Found more than one failure categories with the same credentials. Database structure is corrupted.') + try: + return FailureCategoryData().initFromDbRow(aRows[0]) + except IndexError: + return None + + + def addEntry(self, oData, uidAuthor, fCommit = False): + """ + Add a failure reason category. + """ + # + # Validate inputs and read in the old(/current) data. + # + assert isinstance(oData, FailureCategoryData); + dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Add); + if dErrors: + raise TMInvalidData('editEntry invalid input: %s' % (dErrors,)); + + # + # Add the record. + # + self._readdEntry(uidAuthor, oData); + self._oDb.maybeCommit(fCommit); + return True; + + + def editEntry(self, oData, uidAuthor, fCommit = False): + """ + Modifies a failure reason category. + """ + + # + # Validate inputs and read in the old(/current) data. + # + assert isinstance(oData, FailureCategoryData); + dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit); + if dErrors: + raise TMInvalidData('editEntry invalid input: %s' % (dErrors,)); + + oOldData = FailureCategoryData().initFromDbWithId(self._oDb, oData.idFailureCategory); + + # + # Update the data that needs updating. + # + if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor', ]): + self._historizeEntry(oData.idFailureCategory); + self._readdEntry(uidAuthor, oData); + self._oDb.maybeCommit(fCommit); + return True; + + + def removeEntry(self, uidAuthor, idFailureCategory, fCascade = False, fCommit = False): + """ + Deletes a failure reason category. + """ + _ = fCascade; # too complicated for now. + + # + # Check whether it's being used by other tables and bitch if it is . + # We currently do not implement cascading. + # + self._oDb.execute('SELECT CONCAT(idFailureReason, \' - \', sShort)\n' + 'FROM FailureReasons\n' + 'WHERE idFailureCategory = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idFailureCategory,)); + aaoRows = self._oDb.fetchAll(); + if aaoRows: + raise TMRowInUse('Cannot remove failure reason category %u because its being used by: %s' + % (idFailureCategory, ', '.join(aoRow[0] for aoRow in aaoRows),)); + + # + # Do the job. + # + oData = FailureCategoryData().initFromDbWithId(self._oDb, idFailureCategory); + (tsCur, tsCurMinusOne) = self._oDb.getCurrentTimestamps(); + if oData.tsEffective not in (tsCur, tsCurMinusOne): + self._historizeEntry(idFailureCategory, tsCurMinusOne); + self._readdEntry(uidAuthor, oData, tsCurMinusOne); + self._historizeEntry(idFailureCategory); + self._oDb.maybeCommit(fCommit); + return True; + + + def cachedLookup(self, idFailureCategory): + """ + Looks up the most recent FailureCategoryData object for idFailureCategory + via an object cache. + + Returns a shared FailureCategoryData object. None if not found. + Raises exception on DB error. + """ + if self.dCache is None: + self.dCache = self._oDb.getCache('FailureCategory'); + + oEntry = self.dCache.get(idFailureCategory, None); + if oEntry is None: + self._oDb.execute('SELECT *\n' + 'FROM FailureCategories\n' + 'WHERE idFailureCategory = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idFailureCategory, )); + if self._oDb.getRowCount() == 0: + # Maybe it was deleted, try get the last entry. + self._oDb.execute('SELECT *\n' + 'FROM FailureCategories\n' + 'WHERE idFailureCategory = %s\n' + 'ORDER BY tsExpire DESC\n' + 'LIMIT 1\n' + , (idFailureCategory, )); + elif self._oDb.getRowCount() > 1: + raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idFailureCategory)); + + if self._oDb.getRowCount() == 1: + oEntry = FailureCategoryData().initFromDbRow(self._oDb.fetchOne()); + self.dCache[idFailureCategory] = oEntry; + return oEntry; + + + # + # Helpers. + # + + def _readdEntry(self, uidAuthor, oData, tsEffective = None): + """ + Re-adds the FailureCategories entry. Used by addEntry, editEntry and removeEntry. + """ + if tsEffective is None: + tsEffective = self._oDb.getCurrentTimestamp(); + self._oDb.execute('INSERT INTO FailureCategories (\n' + ' uidAuthor,\n' + ' tsEffective,\n' + ' idFailureCategory,\n' + ' sShort,\n' + ' sFull)\n' + 'VALUES (%s, %s, ' + + ('DEFAULT' if oData.idFailureCategory is None else str(oData.idFailureCategory)) + + ', %s, %s)\n' + , ( uidAuthor, + tsEffective, + oData.sShort, + oData.sFull,) ); + return True; + + + def _historizeEntry(self, idFailureCategory, tsExpire = None): + """ Historizes the current entry. """ + if tsExpire is None: + tsExpire = self._oDb.getCurrentTimestamp(); + self._oDb.execute('UPDATE FailureCategories\n' + 'SET tsExpire = %s\n' + 'WHERE idFailureCategory = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (tsExpire, idFailureCategory,)); + return True; + diff --git a/src/VBox/ValidationKit/testmanager/core/failurereason.py b/src/VBox/ValidationKit/testmanager/core/failurereason.py new file mode 100755 index 00000000..fdf12cc5 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/failurereason.py @@ -0,0 +1,580 @@ +# -*- coding: utf-8 -*- +# $Id: failurereason.py $ + +""" +Test Manager - Failure Reasons. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard Python imports. +import sys; + +# Validation Kit imports. +from testmanager.core.base import ModelDataBase, ModelLogicBase, TMRowNotFound, TMInvalidData, TMRowInUse, \ + AttributeChangeEntry, ChangeLogEntry; +from testmanager.core.useraccount import UserAccountLogic; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + xrange = range; # pylint: disable=redefined-builtin,invalid-name + + +class FailureReasonData(ModelDataBase): + """ + Failure Reason Data. + """ + + ksIdAttr = 'idFailureReason'; + + ksParam_idFailureReason = 'FailureReasonData_idFailureReason' + ksParam_tsEffective = 'FailureReasonData_tsEffective' + ksParam_tsExpire = 'FailureReasonData_tsExpire' + ksParam_uidAuthor = 'FailureReasonData_uidAuthor' + ksParam_idFailureCategory = 'FailureReasonData_idFailureCategory' + ksParam_sShort = 'FailureReasonData_sShort' + ksParam_sFull = 'FailureReasonData_sFull' + ksParam_iTicket = 'FailureReasonData_iTicket' + ksParam_asUrls = 'FailureReasonData_asUrls' + + kasAllowNullAttributes = [ 'idFailureReason', 'tsEffective', 'tsExpire', + 'uidAuthor', 'iTicket', 'asUrls' ] + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + + self.idFailureReason = None + self.tsEffective = None + self.tsExpire = None + self.uidAuthor = None + self.idFailureCategory = None + self.sShort = None + self.sFull = None + self.iTicket = None + self.asUrls = None + + def initFromDbRow(self, aoRow): + """ + Re-initializes the data with a row from a SELECT * FROM FailureReasons. + + Returns self. Raises exception if the row is None or otherwise invalid. + """ + + if aoRow is None: + raise TMRowNotFound('Failure Reason not found.'); + + self.idFailureReason = aoRow[0] + self.tsEffective = aoRow[1] + self.tsExpire = aoRow[2] + self.uidAuthor = aoRow[3] + self.idFailureCategory = aoRow[4] + self.sShort = aoRow[5] + self.sFull = aoRow[6] + self.iTicket = aoRow[7] + self.asUrls = aoRow[8] + + return self; + + def initFromDbWithId(self, oDb, idFailureReason, tsNow = None, sPeriodBack = None): + """ + Initialize from the database, given the ID of a row. + """ + oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb, + 'SELECT *\n' + 'FROM FailureReasons\n' + 'WHERE idFailureReason = %s\n' + , ( idFailureReason,), tsNow, sPeriodBack)); + aoRow = oDb.fetchOne() + if aoRow is None: + raise TMRowNotFound('idFailureReason=%s not found (tsNow=%s sPeriodBack=%s)' + % (idFailureReason, tsNow, sPeriodBack,)); + return self.initFromDbRow(aoRow); + + +class FailureReasonDataEx(FailureReasonData): + """ + Failure Reason Data, extended version that includes the category. + """ + + def __init__(self): + FailureReasonData.__init__(self); + self.oCategory = None; + self.oAuthor = None; + + def initFromDbRowEx(self, aoRow, oCategoryLogic, oUserAccountLogic): + """ + Re-initializes the data with a row from a SELECT * FROM FailureReasons. + + Returns self. Raises exception if the row is None or otherwise invalid. + """ + + self.initFromDbRow(aoRow); + self.oCategory = oCategoryLogic.cachedLookup(self.idFailureCategory); + self.oAuthor = oUserAccountLogic.cachedLookup(self.uidAuthor); + + return self; + + +class FailureReasonLogic(ModelLogicBase): # pylint: disable=too-few-public-methods + """ + Failure Reason logic. + """ + + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb) + self.dCache = None; + self.dCacheNameAndCat = None; + self.oCategoryLogic = None; + self.oUserAccountLogic = None; + + def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None): + """ + Fetches Failure Category records. + + Returns an array (list) of FailureReasonDataEx items, empty list if none. + Raises exception on error. + """ + _ = aiSortColumns; + self._ensureCachesPresent(); + + if tsNow is None: + self._oDb.execute('SELECT FailureReasons.*,\n' + ' FailureCategories.sShort AS sCategory\n' + 'FROM FailureReasons,\n' + ' FailureCategories\n' + 'WHERE FailureReasons.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND FailureCategories.idFailureCategory = FailureReasons.idFailureCategory\n' + ' AND FailureCategories.tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY sCategory ASC, sShort ASC\n' + 'LIMIT %s OFFSET %s\n' + , (cMaxRows, iStart,)); + else: + self._oDb.execute('SELECT FailureReasons.*,\n' + ' FailureCategories.sShort AS sCategory\n' + 'FROM FailureReasons,\n' + ' FailureCategories\n' + 'WHERE FailureReasons.tsExpire > %s\n' + ' AND FailureReasons.tsEffective <= %s\n' + ' AND FailureCategories.idFailureCategory = FailureReasons.idFailureCategory\n' + ' AND FailureReasons.tsExpire > %s\n' + ' AND FailureReasons.tsEffective <= %s\n' + 'ORDER BY sCategory ASC, sShort ASC\n' + 'LIMIT %s OFFSET %s\n' + , (tsNow, tsNow, tsNow, tsNow, cMaxRows, iStart,)); + + aoRows = [] + for aoRow in self._oDb.fetchAll(): + aoRows.append(FailureReasonDataEx().initFromDbRowEx(aoRow, self.oCategoryLogic, self.oUserAccountLogic)); + return aoRows + + def fetchForListingInCategory(self, iStart, cMaxRows, tsNow, idFailureCategory, aiSortColumns = None): + """ + Fetches Failure Category records. + + Returns an array (list) of FailureReasonDataEx items, empty list if none. + Raises exception on error. + """ + _ = aiSortColumns; + self._ensureCachesPresent(); + + if tsNow is None: + self._oDb.execute('SELECT *\n' + 'FROM FailureReasons\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND idFailureCategory = %s\n' + 'ORDER BY sShort ASC\n' + 'LIMIT %s OFFSET %s\n' + , ( idFailureCategory, cMaxRows, iStart,)); + else: + self._oDb.execute('SELECT *\n' + 'FROM FailureReasons\n' + 'WHERE idFailureCategory = %s\n' + ' AND tsExpire > %s\n' + ' AND tsEffective <= %s\n' + 'ORDER BY sShort ASC\n' + 'LIMIT %s OFFSET %s\n' + , ( idFailureCategory, tsNow, tsNow, cMaxRows, iStart,)); + + aoRows = [] + for aoRow in self._oDb.fetchAll(): + aoRows.append(FailureReasonDataEx().initFromDbRowEx(aoRow, self.oCategoryLogic, self.oUserAccountLogic)); + return aoRows + + + def fetchForSheriffByNamedCategory(self, sFailureCategory): + """ + Fetches the short names of the reasons in the named category. + + Returns array of strings. + Raises exception on error. + """ + self._oDb.execute('SELECT FailureReasons.sShort\n' + 'FROM FailureReasons,\n' + ' FailureCategories\n' + 'WHERE FailureReasons.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND FailureReasons.idFailureCategory = FailureCategories.idFailureCategory\n' + ' AND FailureCategories.sShort = %s\n' + 'ORDER BY FailureReasons.sShort ASC\n' + , ( sFailureCategory,)); + return [aoRow[0] for aoRow in self._oDb.fetchAll()]; + + + def fetchForCombo(self, sFirstEntry = 'Select a failure reason', tsEffective = None): + """ + Gets the list of Failure Reasons for a combo box. + Returns an array of (value [idFailureReason], drop-down-name [sShort], + hover-text [sFull]) tuples. + """ + if tsEffective is None: + self._oDb.execute('SELECT fr.idFailureReason, CONCAT(fc.sShort, \' / \', fr.sShort) as sComboText, fr.sFull\n' + 'FROM FailureReasons fr,\n' + ' FailureCategories fc\n' + 'WHERE fr.idFailureCategory = fc.idFailureCategory\n' + ' AND fr.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND fc.tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY sComboText') + else: + self._oDb.execute('SELECT fr.idFailureReason, CONCAT(fc.sShort, \' / \', fr.sShort) as sComboText, fr.sFull\n' + 'FROM FailureReasons fr,\n' + ' FailureCategories fc\n' + 'WHERE fr.idFailureCategory = fc.idFailureCategory\n' + ' AND fr.tsExpire > %s\n' + ' AND fr.tsEffective <= %s\n' + ' AND fc.tsExpire > %s\n' + ' AND fc.tsEffective <= %s\n' + 'ORDER BY sComboText' + , (tsEffective, tsEffective, tsEffective, tsEffective)); + aoRows = self._oDb.fetchAll(); + return [(-1, sFirstEntry, '')] + aoRows; + + + def fetchForChangeLog(self, idFailureReason, iStart, cMaxRows, tsNow): # pylint: disable=too-many-locals + """ + Fetches change log entries for a failure reason. + + Returns an array of ChangeLogEntry instance and an indicator whether + there are more entries. + Raises exception on error. + """ + self._ensureCachesPresent(); + + if tsNow is None: + tsNow = self._oDb.getCurrentTimestamp(); + + # 1. Get a list of the relevant changes. + self._oDb.execute('SELECT * FROM FailureReasons WHERE idFailureReason = %s AND tsEffective <= %s\n' + 'ORDER BY tsEffective DESC\n' + 'LIMIT %s OFFSET %s\n' + , ( idFailureReason, tsNow, cMaxRows + 1, iStart, )); + aoRows = []; + for aoChange in self._oDb.fetchAll(): + aoRows.append(FailureReasonData().initFromDbRow(aoChange)); + + # 2. Calculate the changes. + aoEntries = []; + for i in xrange(0, len(aoRows) - 1): + oNew = aoRows[i]; + oOld = aoRows[i + 1]; + + aoChanges = []; + for sAttr in oNew.getDataAttributes(): + if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', ]: + oOldAttr = getattr(oOld, sAttr); + oNewAttr = getattr(oNew, sAttr); + if oOldAttr != oNewAttr: + if sAttr == 'idFailureCategory': + oCat = self.oCategoryLogic.cachedLookup(oOldAttr); + if oCat is not None: + oOldAttr = '%s (%s)' % (oOldAttr, oCat.sShort, ); + oCat = self.oCategoryLogic.cachedLookup(oNewAttr); + if oCat is not None: + oNewAttr = '%s (%s)' % (oNewAttr, oCat.sShort, ); + aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr))); + + aoEntries.append(ChangeLogEntry(oNew.uidAuthor, None, oNew.tsEffective, oNew.tsExpire, oNew, oOld, aoChanges)); + + # If we're at the end of the log, add the initial entry. + if len(aoRows) <= cMaxRows and aoRows: + oNew = aoRows[-1]; + aoEntries.append(ChangeLogEntry(oNew.uidAuthor, None, oNew.tsEffective, oNew.tsExpire, oNew, None, [])); + + return (UserAccountLogic(self._oDb).resolveChangeLogAuthors(aoEntries), len(aoRows) > cMaxRows); + + + def getById(self, idFailureReason): + """Get Failure Reason data by idFailureReason""" + + self._oDb.execute('SELECT *\n' + 'FROM FailureReasons\n' + 'WHERE tsExpire = \'infinity\'::timestamp\n' + ' AND idFailureReason = %s;', (idFailureReason,)) + aRows = self._oDb.fetchAll() + if len(aRows) not in (0, 1): + raise self._oDb.integrityException( + 'Found more than one failure reasons with the same credentials. Database structure is corrupted.') + try: + return FailureReasonData().initFromDbRow(aRows[0]) + except IndexError: + return None + + + def addEntry(self, oData, uidAuthor, fCommit = False): + """ + Add a failure reason. + """ + # + # Validate. + # + dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Add); + if dErrors: + raise TMInvalidData('addEntry invalid input: %s' % (dErrors,)); + + # + # Add the record. + # + self._readdEntry(uidAuthor, oData); + self._oDb.maybeCommit(fCommit); + return True; + + + def editEntry(self, oData, uidAuthor, fCommit = False): + """ + Modifies a failure reason. + """ + + # + # Validate inputs and read in the old(/current) data. + # + assert isinstance(oData, FailureReasonData); + dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit); + if dErrors: + raise TMInvalidData('editEntry invalid input: %s' % (dErrors,)); + + oOldData = FailureReasonData().initFromDbWithId(self._oDb, oData.idFailureReason); + + # + # Update the data that needs updating. + # + if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor', ]): + self._historizeEntry(oData.idFailureReason); + self._readdEntry(uidAuthor, oData); + self._oDb.maybeCommit(fCommit); + return True; + + + def removeEntry(self, uidAuthor, idFailureReason, fCascade = False, fCommit = False): + """ + Deletes a failure reason. + """ + _ = fCascade; # too complicated for now. + + # + # Check whether it's being used by other tables and bitch if it is . + # We currently do not implement cascading. + # + self._oDb.execute('SELECT CONCAT(idBlacklisting, \' - blacklisting\')\n' + 'FROM BuildBlacklist\n' + 'WHERE idFailureReason = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + 'UNION\n' + 'SELECT CONCAT(idTestResult, \' - test result failure reason\')\n' + 'FROM TestResultFailures\n' + 'WHERE idFailureReason = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idFailureReason, idFailureReason,)); + aaoRows = self._oDb.fetchAll(); + if aaoRows: + raise TMRowInUse('Cannot remove failure reason %u because its being used by: %s' + % (idFailureReason, ', '.join(aoRow[0] for aoRow in aaoRows),)); + + # + # Do the job. + # + oData = FailureReasonData().initFromDbWithId(self._oDb, idFailureReason); + assert oData.idFailureReason == idFailureReason; + (tsCur, tsCurMinusOne) = self._oDb.getCurrentTimestamps(); + if oData.tsEffective not in (tsCur, tsCurMinusOne): + self._historizeEntry(idFailureReason, tsCurMinusOne); + self._readdEntry(uidAuthor, oData, tsCurMinusOne); + self._historizeEntry(idFailureReason); + self._oDb.maybeCommit(fCommit); + return True; + + + def cachedLookup(self, idFailureReason): + """ + Looks up the most recent FailureReasonDataEx object for idFailureReason + via an object cache. + + Returns a shared FailureReasonData object. None if not found. + Raises exception on DB error. + """ + if self.dCache is None: + self.dCache = self._oDb.getCache('FailureReasonDataEx'); + oEntry = self.dCache.get(idFailureReason, None); + if oEntry is None: + self._oDb.execute('SELECT *\n' + 'FROM FailureReasons\n' + 'WHERE idFailureReason = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idFailureReason, )); + if self._oDb.getRowCount() == 0: + # Maybe it was deleted, try get the last entry. + self._oDb.execute('SELECT *\n' + 'FROM FailureReasons\n' + 'WHERE idFailureReason = %s\n' + 'ORDER BY tsExpire DESC\n' + 'LIMIT 1\n' + , (idFailureReason, )); + elif self._oDb.getRowCount() > 1: + raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idFailureReason)); + + if self._oDb.getRowCount() == 1: + self._ensureCachesPresent(); + oEntry = FailureReasonDataEx().initFromDbRowEx(self._oDb.fetchOne(), self.oCategoryLogic, + self.oUserAccountLogic); + self.dCache[idFailureReason] = oEntry; + return oEntry; + + + def cachedLookupByNameAndCategory(self, sName, sCategory): + """ + Looks up a failure reason by it's name and category. + + Should the request be ambigiuos, we'll return the oldest one. + + Returns a shared FailureReasonData object. None if not found. + Raises exception on DB error. + """ + if self.dCacheNameAndCat is None: + self.dCacheNameAndCat = self._oDb.getCache('FailureReasonDataEx-By-Name-And-Category'); + sKey = '%s:::%s' % (sName, sCategory,); + oEntry = self.dCacheNameAndCat.get(sKey, None); + if oEntry is None: + self._oDb.execute('SELECT *\n' + 'FROM FailureReasons,\n' + ' FailureCategories\n' + 'WHERE FailureReasons.sShort = %s\n' + ' AND FailureReasons.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND FailureReasons.idFailureCategory = FailureCategories.idFailureCategory ' + ' AND FailureCategories.sShort = %s\n' + ' AND FailureCategories.tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY FailureReasons.tsEffective\n' + , ( sName, sCategory)); + if self._oDb.getRowCount() == 0: + sLikeSucks = self._oDb.formatBindArgs( + 'SELECT *\n' + 'FROM FailureReasons,\n' + ' FailureCategories\n' + 'WHERE ( FailureReasons.sShort ILIKE @@@@@@@! %s !@@@@@@@\n' + ' OR FailureReasons.sFull ILIKE @@@@@@@! %s !@@@@@@@)\n' + ' AND FailureCategories.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND FailureReasons.idFailureCategory = FailureCategories.idFailureCategory\n' + ' AND ( FailureCategories.sShort = %s\n' + ' OR FailureCategories.sFull = %s)\n' + ' AND FailureReasons.tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY FailureReasons.tsEffective\n' + , ( sName, sName, sCategory, sCategory )); + sLikeSucks = sLikeSucks.replace('LIKE @@@@@@@! \'', 'LIKE \'%').replace('\' !@@@@@@@', '%\''); + self._oDb.execute(sLikeSucks); + if self._oDb.getRowCount() > 0: + self._ensureCachesPresent(); + oEntry = FailureReasonDataEx().initFromDbRowEx(self._oDb.fetchOne(), self.oCategoryLogic, + self.oUserAccountLogic); + self.dCacheNameAndCat[sKey] = oEntry; + if sName != oEntry.sShort or sCategory != oEntry.oCategory.sShort: + sKey2 = '%s:::%s' % (oEntry.sShort, oEntry.oCategory.sShort,); + self.dCacheNameAndCat[sKey2] = oEntry; + return oEntry; + + + # + # Helpers. + # + + def _readdEntry(self, uidAuthor, oData, tsEffective = None): + """ + Re-adds the FailureReasons entry. Used by addEntry, editEntry and removeEntry. + """ + if tsEffective is None: + tsEffective = self._oDb.getCurrentTimestamp(); + self._oDb.execute('INSERT INTO FailureReasons (\n' + ' uidAuthor,\n' + ' tsEffective,\n' + ' idFailureReason,\n' + ' idFailureCategory,\n' + ' sShort,\n' + ' sFull,\n' + ' iTicket,\n' + ' asUrls)\n' + 'VALUES (%s, %s, ' + + ( 'DEFAULT' if oData.idFailureReason is None else str(oData.idFailureReason) ) + + ', %s, %s, %s, %s, %s)\n' + , ( uidAuthor, + tsEffective, + oData.idFailureCategory, + oData.sShort, + oData.sFull, + oData.iTicket, + oData.asUrls,) ); + return True; + + + def _historizeEntry(self, idFailureReason, tsExpire = None): + """ Historizes the current entry. """ + if tsExpire is None: + tsExpire = self._oDb.getCurrentTimestamp(); + self._oDb.execute('UPDATE FailureReasons\n' + 'SET tsExpire = %s\n' + 'WHERE idFailureReason = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (tsExpire, idFailureReason,)); + return True; + + + def _ensureCachesPresent(self): + """ Ensures we've got the cache references resolved. """ + if self.oCategoryLogic is None: + from testmanager.core.failurecategory import FailureCategoryLogic; + self.oCategoryLogic = FailureCategoryLogic(self._oDb); + if self.oUserAccountLogic is None: + self.oUserAccountLogic = UserAccountLogic(self._oDb); + return True; + diff --git a/src/VBox/ValidationKit/testmanager/core/globalresource.pgsql b/src/VBox/ValidationKit/testmanager/core/globalresource.pgsql new file mode 100644 index 00000000..6caf01b3 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/globalresource.pgsql @@ -0,0 +1,118 @@ +-- $Id: globalresource.pgsql $ +--- @file +-- VBox Test Manager Database Stored Procedures. +-- + +-- +-- Copyright (C) 2006-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + +\set ON_ERROR_STOP 1 +\connect testmanager; + +-- Args: uidAuthor, sName, sDescription, fEnabled +CREATE OR REPLACE function add_globalresource(integer, text, text, bool) RETURNS integer AS $$ + DECLARE + _idGlobalRsrc integer; + _uidAuthor ALIAS FOR $1; + _sName ALIAS FOR $2; + _sDescription ALIAS FOR $3; + _fEnabled ALIAS FOR $4; + BEGIN + -- Check if Global Resource name is unique + IF EXISTS(SELECT * FROM GlobalResources + WHERE sName=_sName AND + tsExpire='infinity'::timestamp) THEN + RAISE EXCEPTION 'Duplicate Global Resource name'; + END IF; + INSERT INTO GlobalResources (uidAuthor, sName, sDescription, fEnabled) + VALUES (_uidAuthor, _sName, _sDescription, _fEnabled) RETURNING idGlobalRsrc INTO _idGlobalRsrc; + RETURN _idGlobalRsrc; + END; +$$ LANGUAGE plpgsql; + +-- Args: uidAuthor, idGlobalRsrc +CREATE OR REPLACE function del_globalresource(integer, integer) RETURNS VOID AS $$ + DECLARE + _uidAuthor ALIAS FOR $1; + _idGlobalRsrc ALIAS FOR $2; + BEGIN + + -- Check if record exist + IF NOT EXISTS(SELECT * FROM GlobalResources WHERE idGlobalRsrc=_idGlobalRsrc AND tsExpire='infinity'::timestamp) THEN + RAISE EXCEPTION 'Global resource (%) does not exist', _idGlobalRsrc; + END IF; + + -- Historize record: GlobalResources + UPDATE GlobalResources + SET tsExpire=CURRENT_TIMESTAMP, + uidAuthor=_uidAuthor + WHERE idGlobalRsrc=_idGlobalRsrc AND + tsExpire='infinity'::timestamp; + + + -- Delete record: GlobalResourceStatuses + DELETE FROM GlobalResourceStatuses WHERE idGlobalRsrc=_idGlobalRsrc; + + -- Historize record: TestCaseGlobalRsrcDeps + UPDATE TestCaseGlobalRsrcDeps + SET tsExpire=CURRENT_TIMESTAMP, + uidAuthor=_uidAuthor + WHERE idGlobalRsrc=_idGlobalRsrc AND + tsExpire='infinity'::timestamp; + + END; +$$ LANGUAGE plpgsql; + +-- Args: uidAuthor, idGlobalRsrc, sName, sDescription, fEnabled +CREATE OR REPLACE function update_globalresource(integer, integer, text, text, bool) RETURNS VOID AS $$ + DECLARE + _uidAuthor ALIAS FOR $1; + _idGlobalRsrc ALIAS FOR $2; + _sName ALIAS FOR $3; + _sDescription ALIAS FOR $4; + _fEnabled ALIAS FOR $5; + BEGIN + -- Hostorize record + UPDATE GlobalResources + SET tsExpire=CURRENT_TIMESTAMP + WHERE idGlobalRsrc=_idGlobalRsrc AND + tsExpire='infinity'::timestamp; + -- Check if Global Resource name is unique + IF EXISTS(SELECT * FROM GlobalResources + WHERE sName=_sName AND + tsExpire='infinity'::timestamp) THEN + RAISE EXCEPTION 'Duplicate Global Resource name'; + END IF; + -- Add new record + INSERT INTO GlobalResources(uidAuthor, idGlobalRsrc, sName, sDescription, fEnabled) + VALUES (_uidAuthor, _idGlobalRsrc, _sName, _sDescription, _fEnabled); + END; +$$ LANGUAGE plpgsql; diff --git a/src/VBox/ValidationKit/testmanager/core/globalresource.py b/src/VBox/ValidationKit/testmanager/core/globalresource.py new file mode 100755 index 00000000..d9caacf9 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/globalresource.py @@ -0,0 +1,328 @@ +# -*- coding: utf-8 -*- +# $Id: globalresource.py $ + +""" +Test Manager - Global Resources. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import unittest; + +# Validation Kit imports. +from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMRowNotFound; + + +class GlobalResourceData(ModelDataBase): + """ + Global resource data + """ + + ksIdAttr = 'idGlobalRsrc'; + + ksParam_idGlobalRsrc = 'GlobalResource_idGlobalRsrc' + ksParam_tsEffective = 'GlobalResource_tsEffective' + ksParam_tsExpire = 'GlobalResource_tsExpire' + ksParam_uidAuthor = 'GlobalResource_uidAuthor' + ksParam_sName = 'GlobalResource_sName' + ksParam_sDescription = 'GlobalResource_sDescription' + ksParam_fEnabled = 'GlobalResource_fEnabled' + + kasAllowNullAttributes = ['idGlobalRsrc', 'tsEffective', 'tsExpire', 'uidAuthor', 'sDescription' ]; + kcchMin_sName = 2; + kcchMax_sName = 64; + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.idGlobalRsrc = None; + self.tsEffective = None; + self.tsExpire = None; + self.uidAuthor = None; + self.sName = None; + self.sDescription = None; + self.fEnabled = False + + def initFromDbRow(self, aoRow): + """ + Reinitialize from a SELECT * FROM GlobalResources row. + Returns self. Raises exception if no row. + """ + if aoRow is None: + raise TMRowNotFound('Global resource not found.') + + self.idGlobalRsrc = aoRow[0] + self.tsEffective = aoRow[1] + self.tsExpire = aoRow[2] + self.uidAuthor = aoRow[3] + self.sName = aoRow[4] + self.sDescription = aoRow[5] + self.fEnabled = aoRow[6] + return self + + def initFromDbWithId(self, oDb, idGlobalRsrc, tsNow = None, sPeriodBack = None): + """ + Initialize the object from the database. + """ + oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb, + 'SELECT *\n' + 'FROM GlobalResources\n' + 'WHERE idGlobalRsrc = %s\n' + , ( idGlobalRsrc,), tsNow, sPeriodBack)); + aoRow = oDb.fetchOne() + if aoRow is None: + raise TMRowNotFound('idGlobalRsrc=%s not found (tsNow=%s sPeriodBack=%s)' % (idGlobalRsrc, tsNow, sPeriodBack,)); + return self.initFromDbRow(aoRow); + + def isEqual(self, oOther): + """ + Compares two instances. + """ + return self.idGlobalRsrc == oOther.idGlobalRsrc \ + and str(self.tsEffective) == str(oOther.tsEffective) \ + and str(self.tsExpire) == str(oOther.tsExpire) \ + and self.uidAuthor == oOther.uidAuthor \ + and self.sName == oOther.sName \ + and self.sDescription == oOther.sDescription \ + and self.fEnabled == oOther.fEnabled + + +class GlobalResourceLogic(ModelLogicBase): + """ + Global resource logic. + """ + + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb) + self.dCache = None; + + def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None): + """ + Returns an array (list) of FailureReasonData items, empty list if none. + Raises exception on error. + """ + _ = aiSortColumns; + + if tsNow is None: + self._oDb.execute('SELECT *\n' + 'FROM GlobalResources\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY idGlobalRsrc DESC\n' + 'LIMIT %s OFFSET %s\n' + , (cMaxRows, iStart,)); + else: + self._oDb.execute('SELECT *\n' + 'FROM GlobalResources\n' + 'WHERE tsExpire > %s\n' + ' AND tsEffective <= %s\n' + 'ORDER BY idGlobalRsrc DESC\n' + 'LIMIT %s OFFSET %s\n' + , (tsNow, tsNow, cMaxRows, iStart,)) + + aoRows = [] + for aoRow in self._oDb.fetchAll(): + aoRows.append(GlobalResourceData().initFromDbRow(aoRow)) + return aoRows + + + def cachedLookup(self, idGlobalRsrc): + """ + Looks up the most recent GlobalResourceData object for idGlobalRsrc + via an object cache. + + Returns a shared GlobalResourceData object. None if not found. + Raises exception on DB error. + """ + if self.dCache is None: + self.dCache = self._oDb.getCache('GlobalResourceData'); + oEntry = self.dCache.get(idGlobalRsrc, None); + if oEntry is None: + self._oDb.execute('SELECT *\n' + 'FROM GlobalResources\n' + 'WHERE idGlobalRsrc = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idGlobalRsrc, )); + if self._oDb.getRowCount() == 0: + # Maybe it was deleted, try get the last entry. + self._oDb.execute('SELECT *\n' + 'FROM GlobalResources\n' + 'WHERE idGlobalRsrc = %s\n' + 'ORDER BY tsExpire DESC\n' + 'LIMIT 1\n' + , (idGlobalRsrc, )); + elif self._oDb.getRowCount() > 1: + raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idGlobalRsrc)); + + if self._oDb.getRowCount() == 1: + aaoRow = self._oDb.fetchOne(); + oEntry = GlobalResourceData(); + oEntry.initFromDbRow(aaoRow); + self.dCache[idGlobalRsrc] = oEntry; + return oEntry; + + + def getAll(self, tsEffective = None): + """ + Gets all global resources. + + Returns an array of GlobalResourceData instances on success (can be + empty). Raises exception on database error. + """ + if tsEffective is not None: + self._oDb.execute('SELECT *\n' + 'FROM GlobalResources\n' + 'WHERE tsExpire > %s\n' + ' AND tsEffective <= %s\n' + , (tsEffective, tsEffective)); + else: + self._oDb.execute('SELECT *\n' + 'FROM GlobalResources\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'); + aaoRows = self._oDb.fetchAll(); + aoRet = []; + for aoRow in aaoRows: + aoRet.append(GlobalResourceData().initFromDbRow(aoRow)); + + return aoRet; + + def addGlobalResource(self, uidAuthor, oData): + """Add Global Resource DB record""" + self._oDb.execute('SELECT * FROM add_globalresource(%s, %s, %s, %s);', + (uidAuthor, + oData.sName, + oData.sDescription, + oData.fEnabled)) + self._oDb.commit() + return True + + def editGlobalResource(self, uidAuthor, idGlobalRsrc, oData): + """Modify Global Resource DB record""" + # Check if anything has been changed + oGlobalResourcesDataOld = self.getById(idGlobalRsrc) + if oGlobalResourcesDataOld.isEqual(oData): + # Nothing has been changed, do nothing + return True + + self._oDb.execute('SELECT * FROM update_globalresource(%s, %s, %s, %s, %s);', + (uidAuthor, + idGlobalRsrc, + oData.sName, + oData.sDescription, + oData.fEnabled)) + self._oDb.commit() + return True + + def remove(self, uidAuthor, idGlobalRsrc): + """Delete Global Resource DB record""" + self._oDb.execute('SELECT * FROM del_globalresource(%s, %s);', + (uidAuthor, idGlobalRsrc)) + self._oDb.commit() + return True + + def getById(self, idGlobalRsrc): + """ + Get global resource record by its id + """ + self._oDb.execute('SELECT *\n' + 'FROM GlobalResources\n' + 'WHERE tsExpire = \'infinity\'::timestamp\n' + ' AND idGlobalRsrc=%s;', (idGlobalRsrc,)) + + aRows = self._oDb.fetchAll() + if len(aRows) not in (0, 1): + raise self._oDb.integrityException('Duplicate global resource entry with ID %u (current)' % (idGlobalRsrc,)); + try: + return GlobalResourceData().initFromDbRow(aRows[0]) + except IndexError: + raise TMRowNotFound('Global resource not found.') + + def allocateResources(self, idTestBox, aoGlobalRsrcs, fCommit = False): + """ + Allocates the given global resource. + + Returns True of successfully allocated the resources, False if not. + May raise exception on DB error. + """ + # Quit quickly if there is nothing to alloocate. + if not aoGlobalRsrcs: + return True; + + # + # Note! Someone else might have allocated the resources since the + # scheduler check that they were available. In such case we + # need too quietly rollback and return FALSE. + # + self._oDb.execute('SAVEPOINT allocateResources'); + + for oGlobalRsrc in aoGlobalRsrcs: + try: + self._oDb.execute('INSERT INTO GlobalResourceStatuses (idGlobalRsrc, idTestBox)\n' + 'VALUES (%s, %s)', (oGlobalRsrc.idGlobalRsrc, idTestBox, ) ); + except self._oDb.oXcptError: + self._oDb.execute('ROLLBACK TO SAVEPOINT allocateResources'); + return False; + + self._oDb.execute('RELEASE SAVEPOINT allocateResources'); + self._oDb.maybeCommit(fCommit); + return True; + + def freeGlobalResourcesByTestBox(self, idTestBox, fCommit = False): + """ + Frees all global resources own by the given testbox. + Returns True. May raise exception on DB error. + """ + self._oDb.execute('DELETE FROM GlobalResourceStatuses\n' + 'WHERE idTestBox = %s\n', (idTestBox, ) ); + self._oDb.maybeCommit(fCommit); + return True; + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +class GlobalResourceDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [GlobalResourceData(),]; + +if __name__ == '__main__': + unittest.main(); + # not reached. + diff --git a/src/VBox/ValidationKit/testmanager/core/report.py b/src/VBox/ValidationKit/testmanager/core/report.py new file mode 100755 index 00000000..2d5ac087 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/report.py @@ -0,0 +1,1307 @@ +# -*- coding: utf-8 -*- +# $Id: report.py $ + +""" +Test Manager - Report models. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import sys; + +# Validation Kit imports. +from testmanager.core.base import ModelLogicBase, TMExceptionBase; +from testmanager.core.build import BuildCategoryData; +from testmanager.core.dbobjcache import DatabaseObjCache; +from testmanager.core.failurereason import FailureReasonLogic; +from testmanager.core.testbox import TestBoxLogic, TestBoxData; +from testmanager.core.testcase import TestCaseLogic; +from testmanager.core.testcaseargs import TestCaseArgsLogic; +from testmanager.core.testresults import TestResultLogic, TestResultFilter; +from common import constants; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + xrange = range; # pylint: disable=redefined-builtin,invalid-name + + + +class ReportFilter(TestResultFilter): + """ + Same as TestResultFilter for now. + """ + + def __init__(self): + TestResultFilter.__init__(self); + + + +class ReportModelBase(ModelLogicBase): # pylint: disable=too-few-public-methods + """ + Something all report logic(/miner) classes inherit from. + """ + + ## @name Report subjects + ## @{ + ksSubEverything = 'Everything'; + ksSubSchedGroup = 'SchedGroup'; + ksSubTestGroup = 'TestGroup'; + ksSubTestCase = 'TestCase'; + ksSubTestCaseArgs = 'TestCaseArgs'; + ksSubTestBox = 'TestBox'; + ksSubBuild = 'Build'; + ## @} + kasSubjects = [ ksSubEverything, ksSubSchedGroup, ksSubTestGroup, ksSubTestCase, ksSubTestBox, ksSubBuild, ]; + + + ## @name TestStatus_T + # @{ + ksTestStatus_Running = 'running'; + ksTestStatus_Success = 'success'; + ksTestStatus_Skipped = 'skipped'; + ksTestStatus_BadTestBox = 'bad-testbox'; + ksTestStatus_Aborted = 'aborted'; + ksTestStatus_Failure = 'failure'; + ksTestStatus_TimedOut = 'timed-out'; + ksTestStatus_Rebooted = 'rebooted'; + ## @} + + + def __init__(self, oDb, tsNow, cPeriods, cHoursPerPeriod, sSubject, aidSubjects, oFilter): + ModelLogicBase.__init__(self, oDb); + # Public so the report generator can easily access them. + self.tsNow = tsNow; # (Can be None.) + self.__tsNowDateTime = None; + self.cPeriods = cPeriods; + self.cHoursPerPeriod = cHoursPerPeriod; + self.sSubject = sSubject; + self.aidSubjects = aidSubjects; + self.oFilter = oFilter; + if self.oFilter is None: + class DummyFilter(object): + """ Dummy """ + def getTableJoins(self, sExtraIndent = '', iOmit = -1, dOmitTables = None): + """ Dummy """ + _ = sExtraIndent; _ = iOmit; _ = dOmitTables; # pylint: disable=redefined-variable-type + return ''; + def getWhereConditions(self, sExtraIndent = '', iOmit = -1): + """ Dummy """ + _ = sExtraIndent; _ = iOmit; # pylint: disable=redefined-variable-type + return ''; + def isJoiningWithTable(self, sTable): + """ Dummy """; + _ = sTable; + return False; + self.oFilter = DummyFilter(); + + def getExtraSubjectTables(self): + """ + Returns a list of additional tables needed by the subject. + """ + return []; + + def getExtraSubjectWhereExpr(self): + """ + Returns additional WHERE expression relating to the report subject. It starts + with an AND so that it can simply be appended to the WHERE clause. + """ + if self.sSubject == self.ksSubEverything: + return ''; + + if self.sSubject == self.ksSubSchedGroup: + sWhere = ' AND TestSets.idSchedGroup'; + elif self.sSubject == self.ksSubTestGroup: + sWhere = ' AND TestSets.idTestGroup'; + elif self.sSubject == self.ksSubTestCase: + sWhere = ' AND TestSets.idTestCase'; + elif self.sSubject == self.ksSubTestCaseArgs: + sWhere = ' AND TestSets.idTestCaseArgs'; + elif self.sSubject == self.ksSubTestBox: + sWhere = ' AND TestSets.idTestBox'; + elif self.sSubject == self.ksSubBuild: + sWhere = ' AND TestSets.idBuild'; + else: + raise TMExceptionBase(self.sSubject); + + if len(self.aidSubjects) == 1: + sWhere += self._oDb.formatBindArgs(' = %s\n', (self.aidSubjects[0],)); + else: + assert self.aidSubjects; + sWhere += self._oDb.formatBindArgs(' IN (%s', (self.aidSubjects[0],)); + for i in range(1, len(self.aidSubjects)): + sWhere += self._oDb.formatBindArgs(', %s', (self.aidSubjects[i],)); + sWhere += ')\n'; + + return sWhere; + + def getNowAsDateTime(self): + """ Returns a datetime instance corresponding to tsNow. """ + if self.__tsNowDateTime is None: + if self.tsNow is None: + self.__tsNowDateTime = self._oDb.getCurrentTimestamp(); + else: + self._oDb.execute('SELECT %s::TIMESTAMP WITH TIME ZONE', (self.tsNow,)); + self.__tsNowDateTime = self._oDb.fetchOne()[0]; + return self.__tsNowDateTime; + + def getPeriodStart(self, iPeriod): + """ Gets the python timestamp for the start of the given period. """ + from datetime import timedelta; + cHoursStart = (self.cPeriods - iPeriod ) * self.cHoursPerPeriod; + return self.getNowAsDateTime() - timedelta(hours = cHoursStart); + + def getPeriodEnd(self, iPeriod): + """ Gets the python timestamp for the end of the given period. """ + from datetime import timedelta; + cHoursEnd = (self.cPeriods - iPeriod - 1) * self.cHoursPerPeriod; + return self.getNowAsDateTime() - timedelta(hours = cHoursEnd); + + def getExtraWhereExprForPeriod(self, iPeriod): + """ + Returns additional WHERE expression for getting test sets for the + specified period. It starts with an AND so that it can simply be + appended to the WHERE clause. + """ + if self.tsNow is None: + sNow = 'CURRENT_TIMESTAMP'; + else: + sNow = self._oDb.formatBindArgs('%s::TIMESTAMP', (self.tsNow,)); + + cHoursStart = (self.cPeriods - iPeriod ) * self.cHoursPerPeriod; + cHoursEnd = (self.cPeriods - iPeriod - 1) * self.cHoursPerPeriod; + if cHoursEnd == 0: + return ' AND TestSets.tsDone >= (%s - interval \'%u hours\')\n' \ + ' AND TestSets.tsDone < %s\n' \ + % (sNow, cHoursStart, sNow); + return ' AND TestSets.tsDone >= (%s - interval \'%u hours\')\n' \ + ' AND TestSets.tsDone < (%s - interval \'%u hours\')\n' \ + % (sNow, cHoursStart, sNow, cHoursEnd); + + def getPeriodDesc(self, iPeriod): + """ + Returns the period description, usually for graph data. + """ + if iPeriod == 0: + return 'now' if self.tsNow is None else 'then'; + sTerm = 'ago' if self.tsNow is None else 'earlier'; + if self.cHoursPerPeriod == 24: + return '%dd %s' % (iPeriod, sTerm, ); + if (iPeriod * self.cHoursPerPeriod) % 24 == 0: + return '%dd %s' % (iPeriod * self.cHoursPerPeriod / 24, sTerm, ); + return '%dh %s' % (iPeriod * self.cHoursPerPeriod, sTerm); + + def getStraightPeriodDesc(self, iPeriod): + """ + Returns the period description, usually for graph data. + """ + iWickedPeriod = self.cPeriods - iPeriod - 1; + return self.getPeriodDesc(iWickedPeriod); + + +# +# Data structures produced and returned by the ReportLazyModel. +# + +class ReportTransientBase(object): + """ Details on the test where a problem was first/last seen. """ + def __init__(self, idBuild, iRevision, sRepository, idTestSet, idTestResult, tsDone, # pylint: disable=too-many-arguments + iPeriod, fEnter, idSubject, oSubject): + self.idBuild = idBuild; # Build ID. + self.iRevision = iRevision; # SVN revision for build. + self.sRepository = sRepository; # SVN repository for build. + self.idTestSet = idTestSet; # Test set. + self.idTestResult = idTestResult; # Test result. + self.tsDone = tsDone; # When the test set was done. + self.iPeriod = iPeriod; # Data set period. + self.fEnter = fEnter; # True if enter event, False if leave event. + self.idSubject = idSubject; + self.oSubject = oSubject; + +class ReportFailureReasonTransient(ReportTransientBase): + """ Details on the test where a failure reason was first/last seen. """ + def __init__(self, idBuild, iRevision, sRepository, idTestSet, idTestResult, tsDone, # pylint: disable=too-many-arguments + iPeriod, fEnter, oReason): + ReportTransientBase.__init__(self, idBuild, iRevision, sRepository, idTestSet, idTestResult, tsDone, iPeriod, fEnter, + oReason.idFailureReason, oReason); + self.oReason = oReason; # FailureReasonDataEx + + +class ReportHitRowBase(object): + """ A row in a period. """ + def __init__(self, idSubject, oSubject, cHits, tsMin = None, tsMax = None): + self.idSubject = idSubject; + self.oSubject = oSubject; + self.cHits = cHits; + self.tsMin = tsMin; + self.tsMax = tsMax; + +class ReportHitRowWithTotalBase(ReportHitRowBase): + """ A row in a period. """ + def __init__(self, idSubject, oSubject, cHits, cTotal, tsMin = None, tsMax = None): + ReportHitRowBase.__init__(self, idSubject, oSubject, cHits, tsMin, tsMax) + self.cTotal = cTotal; + self.uPct = cHits * 100 / cTotal; + +class ReportFailureReasonRow(ReportHitRowBase): + """ The account of one failure reason for a period. """ + def __init__(self, aoRow, oReason): + ReportHitRowBase.__init__(self, aoRow[0], oReason, aoRow[1], aoRow[2], aoRow[3]); + self.idFailureReason = aoRow[0]; + self.oReason = oReason; # FailureReasonDataEx + + +class ReportPeriodBase(object): + """ A period in ReportFailureReasonSet. """ + def __init__(self, oSet, iPeriod, sDesc, tsFrom, tsTo): + self.oSet = oSet # Reference to the parent ReportSetBase derived object. + self.iPeriod = iPeriod; # Period number in the set. + self.sDesc = sDesc; # Short period description. + self.tsStart = tsFrom; # Start of the period. + self.tsEnd = tsTo; # End of the period (exclusive). + self.tsMin = tsTo; # The earlierst hit of the period (only valid for cHits > 0). + self.tsMax = tsFrom; # The latest hit of the period (only valid for cHits > 0). + self.aoRows = []; # Rows in order the database returned them (ReportHitRowBase descendant). + self.dRowsById = {}; # Same as aoRows but indexed by object ID (see ReportSetBase::sIdAttr). + self.dFirst = {}; # The subjects seen for the first time - data object, keyed by ID. + self.dLast = {}; # The subjects seen for the last time - data object, keyed by ID. + self.cHits = 0; # Total number of hits in this period. + self.cMaxHits = 0; # Max hits in a row. + self.cMinHits = 99999999; # Min hits in a row (only valid for cHits > 0). + + def appendRow(self, oRow, idRow, oData): + """ Adds a row. """ + assert isinstance(oRow, ReportHitRowBase); + self.aoRows.append(oRow); + self.dRowsById[idRow] = oRow; + if idRow not in self.oSet.dSubjects: + self.oSet.dSubjects[idRow] = oData; + self._doStatsForRow(oRow, idRow, oData); + + def _doStatsForRow(self, oRow, idRow, oData): + """ Does the statistics for a row. Helper for appendRow as well as helpRecalcStats. """ + if oRow.tsMin is not None and oRow.tsMin < self.tsMin: + self.tsMin = oRow.tsMin; + if oRow.tsMax is not None and oRow.tsMax < self.tsMax: + self.tsMax = oRow.tsMax; + + self.cHits += oRow.cHits; + if oRow.cHits > self.cMaxHits: + self.cMaxHits = oRow.cHits; + if oRow.cHits < self.cMinHits: + self.cMinHits = oRow.cHits; + + if idRow in self.oSet.dcHitsPerId: + self.oSet.dcHitsPerId[idRow] += oRow.cHits; + else: + self.oSet.dcHitsPerId[idRow] = oRow.cHits; + + if oRow.cHits > 0: + if idRow not in self.oSet.diPeriodFirst: + self.dFirst[idRow] = oData; + self.oSet.diPeriodFirst[idRow] = self.iPeriod; + self.oSet.diPeriodLast[idRow] = self.iPeriod; + + def helperSetRecalcStats(self): + """ Recalc the statistics (do resetStats first on set). """ + for idRow, oRow in self.dRowsById.items(): + self._doStatsForRow(oRow, idRow, self.oSet.dSubjects[idRow]); + + def helperSetResetStats(self): + """ Resets the statistics. """ + self.tsMin = self.tsEnd; + self.tsMax = self.tsStart; + self.cHits = 0; + self.cMaxHits = 0; + self.cMinHits = 99999999; + self.dFirst = {}; + self.dLast = {}; + + def helperSetDeleteKeyFromSet(self, idKey): + """ Helper for ReportPeriodSetBase::deleteKey """ + if idKey in self.dRowsById: + oRow = self.dRowsById[idKey]; + self.aoRows.remove(oRow); + del self.dRowsById[idKey] + self.cHits -= oRow.cHits; + if idKey in self.dFirst: + del self.dFirst[idKey]; + if idKey in self.dLast: + del self.dLast[idKey]; + +class ReportPeriodWithTotalBase(ReportPeriodBase): + """ In addition to the cHits, we also have a total to relate it too. """ + def __init__(self, oSet, iPeriod, sDesc, tsFrom, tsTo): + ReportPeriodBase.__init__(self, oSet, iPeriod, sDesc, tsFrom, tsTo); + self.cTotal = 0; + self.cMaxTotal = 0; + self.cMinTotal = 99999999; + self.uMaxPct = 0; # Max percentage in a row (100 = 100%). + + def _doStatsForRow(self, oRow, idRow, oData): + assert isinstance(oRow, ReportHitRowWithTotalBase); + super(ReportPeriodWithTotalBase, self)._doStatsForRow(oRow, idRow, oData); + self.cTotal += oRow.cTotal; + if oRow.cTotal > self.cMaxTotal: + self.cMaxTotal = oRow.cTotal; + if oRow.cTotal < self.cMinTotal: + self.cMinTotal = oRow.cTotal; + + if oRow.uPct > self.uMaxPct: + self.uMaxPct = oRow.uPct; + + if idRow in self.oSet.dcTotalPerId: + self.oSet.dcTotalPerId[idRow] += oRow.cTotal; + else: + self.oSet.dcTotalPerId[idRow] = oRow.cTotal; + + def helperSetResetStats(self): + super(ReportPeriodWithTotalBase, self).helperSetResetStats(); + self.cTotal = 0; + self.cMaxTotal = 0; + self.cMinTotal = 99999999; + self.uMaxPct = 0; + +class ReportFailureReasonPeriod(ReportPeriodBase): + """ A period in ReportFailureReasonSet. """ + def __init__(self, oSet, iPeriod, sDesc, tsFrom, tsTo): + ReportPeriodBase.__init__(self, oSet, iPeriod, sDesc, tsFrom, tsTo); + self.cWithoutReason = 0; # Number of failed test sets without any assigned reason. + + + +class ReportPeriodSetBase(object): + """ Period data set base class. """ + def __init__(self, sIdAttr): + self.sIdAttr = sIdAttr; # The name of the key attribute. Mainly for documentation purposes. + self.aoPeriods = []; # Periods (ReportPeriodBase descendant) in ascending order (time wise). + self.dSubjects = {}; # The subject data objects, keyed by the subject ID. + self.dcHitsPerId = {}; # Sum hits per subject ID (key). + self.cHits = 0; # Sum number of hits in all periods and all reasons. + self.cMaxHits = 0; # Max hits in a row. + self.cMinHits = 99999999; # Min hits in a row. + self.cMaxRows = 0; # Max number of rows in a period. + self.cMinRows = 99999999; # Min number of rows in a period. + self.diPeriodFirst = {}; # The period number a reason was first seen (keyed by subject ID). + self.diPeriodLast = {}; # The period number a reason was last seen (keyed by subject ID). + self.aoEnterInfo = []; # Array of ReportTransientBase children order by iRevision. Excludes + # the first period of course. (Child class populates this.) + self.aoLeaveInfo = []; # Array of ReportTransientBase children order in descending order by + # iRevision. Excludes the last priod. (Child class populates this.) + + def appendPeriod(self, oPeriod): + """ Appends a period to the set. """ + assert isinstance(oPeriod, ReportPeriodBase); + self.aoPeriods.append(oPeriod); + self._doStatsForPeriod(oPeriod); + + def _doStatsForPeriod(self, oPeriod): + """ Worker for appendPeriod and recalcStats. """ + self.cHits += oPeriod.cHits; + if oPeriod.cMaxHits > self.cMaxHits: + self.cMaxHits = oPeriod.cMaxHits; + if oPeriod.cMinHits < self.cMinHits: + self.cMinHits = oPeriod.cMinHits; + + if len(oPeriod.aoRows) > self.cMaxRows: + self.cMaxRows = len(oPeriod.aoRows); + if len(oPeriod.aoRows) < self.cMinRows: + self.cMinRows = len(oPeriod.aoRows); + + def recalcStats(self): + """ Recalculates the statistics. ASSUMES finalizePass1 hasn't been done yet. """ + self.cHits = 0; + self.cMaxHits = 0; + self.cMinHits = 99999999; + self.cMaxRows = 0; + self.cMinRows = 99999999; + self.diPeriodFirst = {}; + self.diPeriodLast = {}; + self.dcHitsPerId = {}; + for oPeriod in self.aoPeriods: + oPeriod.helperSetResetStats(); + + for oPeriod in self.aoPeriods: + oPeriod.helperSetRecalcStats(); + self._doStatsForPeriod(oPeriod); + + def deleteKey(self, idKey): + """ Deletes a key from the set. May leave cMaxHits and cMinHits with outdated values. """ + self.cHits -= self.dcHitsPerId[idKey]; + del self.dcHitsPerId[idKey]; + if idKey in self.diPeriodFirst: + del self.diPeriodFirst[idKey]; + if idKey in self.diPeriodLast: + del self.diPeriodLast[idKey]; + if idKey in self.aoEnterInfo: + del self.aoEnterInfo[idKey]; + if idKey in self.aoLeaveInfo: + del self.aoLeaveInfo[idKey]; + del self.dSubjects[idKey]; + for oPeriod in self.aoPeriods: + oPeriod.helperSetDeleteKeyFromSet(idKey); + + def pruneRowsWithZeroSumHits(self): + """ Discards rows with zero sum hits across all periods. Works around lazy selects counting both totals and hits. """ + cDeleted = 0; + aidKeys = list(self.dcHitsPerId); + for idKey in aidKeys: + if self.dcHitsPerId[idKey] == 0: + self.deleteKey(idKey); + cDeleted += 1; + if cDeleted > 0: + self.recalcStats(); + return cDeleted; + + def finalizePass1(self): + """ Finished all but aoEnterInfo and aoLeaveInfo. """ + # All we need to do here is to populate the dLast members. + for idKey, iPeriod in self.diPeriodLast.items(): + self.aoPeriods[iPeriod].dLast[idKey] = self.dSubjects[idKey]; + return self; + + def finalizePass2(self): + """ Called after aoEnterInfo and aoLeaveInfo has been populated to sort them. """ + self.aoEnterInfo = sorted(self.aoEnterInfo, key = lambda oTrans: oTrans.iRevision); + self.aoLeaveInfo = sorted(self.aoLeaveInfo, key = lambda oTrans: oTrans.iRevision, reverse = True); + return self; + +class ReportPeriodSetWithTotalBase(ReportPeriodSetBase): + """ In addition to the cHits, we also have a total to relate it too. """ + def __init__(self, sIdAttr): + ReportPeriodSetBase.__init__(self, sIdAttr); + self.dcTotalPerId = {}; # Sum total per subject ID (key). + self.cTotal = 0; # Sum number of total in all periods and all reasons. + self.cMaxTotal = 0; # Max total in a row. + self.cMinTotal = 0; # Min total in a row. + self.uMaxPct = 0; # Max percentage in a row (100 = 100%). + + def _doStatsForPeriod(self, oPeriod): + assert isinstance(oPeriod, ReportPeriodWithTotalBase); + super(ReportPeriodSetWithTotalBase, self)._doStatsForPeriod(oPeriod); + self.cTotal += oPeriod.cTotal; + if oPeriod.cMaxTotal > self.cMaxTotal: + self.cMaxTotal = oPeriod.cMaxTotal; + if oPeriod.cMinTotal < self.cMinTotal: + self.cMinTotal = oPeriod.cMinTotal; + + if oPeriod.uMaxPct > self.uMaxPct: + self.uMaxPct = oPeriod.uMaxPct; + + def recalcStats(self): + self.dcTotalPerId = {}; + self.cTotal = 0; + self.cMaxTotal = 0; + self.cMinTotal = 0; + self.uMaxPct = 0; + super(ReportPeriodSetWithTotalBase, self).recalcStats(); + + def deleteKey(self, idKey): + self.cTotal -= self.dcTotalPerId[idKey]; + del self.dcTotalPerId[idKey]; + super(ReportPeriodSetWithTotalBase, self).deleteKey(idKey); + +class ReportFailureReasonSet(ReportPeriodSetBase): + """ What ReportLazyModel.getFailureReasons returns. """ + def __init__(self): + ReportPeriodSetBase.__init__(self, 'idFailureReason'); + + + +class ReportLazyModel(ReportModelBase): # pylint: disable=too-few-public-methods + """ + The 'lazy bird' report model class. + + We may want to have several classes, maybe one for each report even. But, + I'm thinking that's a bit overkill so we'll start with this and split it + if/when it becomes necessary. + """ + + kdsStatusSimplificationMap = { + ReportModelBase.ksTestStatus_Running: ReportModelBase.ksTestStatus_Running, + ReportModelBase.ksTestStatus_Success: ReportModelBase.ksTestStatus_Success, + ReportModelBase.ksTestStatus_Skipped: ReportModelBase.ksTestStatus_Skipped, + ReportModelBase.ksTestStatus_BadTestBox: ReportModelBase.ksTestStatus_Skipped, + ReportModelBase.ksTestStatus_Aborted: ReportModelBase.ksTestStatus_Skipped, + ReportModelBase.ksTestStatus_Failure: ReportModelBase.ksTestStatus_Failure, + ReportModelBase.ksTestStatus_TimedOut: ReportModelBase.ksTestStatus_Failure, + ReportModelBase.ksTestStatus_Rebooted: ReportModelBase.ksTestStatus_Failure, + }; + + def getSuccessRates(self): + """ + Gets the success rates of the subject in the specified period. + + Returns an array of data per period (0 is the oldes, self.cPeriods-1 is + the latest) where each entry is a status (TestStatus_T) dictionary with + the number of occurences of each final status (i.e. not running). + """ + + sBaseQuery = 'SELECT TestSets.enmStatus, COUNT(TestSets.idTestSet)\n' \ + 'FROM TestSets\n' \ + + self.oFilter.getTableJoins(); + for sTable in self.getExtraSubjectTables(): + sBaseQuery = sBaseQuery[:-1] + ',\n ' + sTable + '\n'; + sBaseQuery += 'WHERE enmStatus <> \'running\'\n' \ + + self.oFilter.getWhereConditions() \ + + self.getExtraSubjectWhereExpr(); + + adPeriods = []; + for iPeriod in xrange(self.cPeriods): + self._oDb.execute(sBaseQuery + self.getExtraWhereExprForPeriod(iPeriod) + 'GROUP BY enmStatus\n'); + + dRet = \ + { + self.ksTestStatus_Skipped: 0, + self.ksTestStatus_Failure: 0, + self.ksTestStatus_Success: 0, + }; + + for aoRow in self._oDb.fetchAll(): + sKey = self.kdsStatusSimplificationMap[aoRow[0]] + if sKey in dRet: + dRet[sKey] += aoRow[1]; + else: + dRet[sKey] = aoRow[1]; + + assert len(dRet) == 3; + + adPeriods.insert(0, dRet); + + return adPeriods; + + + def getFailureReasons(self): + """ + Gets the failure reasons of the subject in the specified period. + + Returns a ReportFailureReasonSet instance. + """ + + oFailureReasonLogic = FailureReasonLogic(self._oDb); + + # + # Create a temporary table + # + sTsNow = 'CURRENT_TIMESTAMP' if self.tsNow is None else self._oDb.formatBindArgs('%s::TIMESTAMP', (self.tsNow,)); + sTsFirst = '(%s - interval \'%s hours\')' \ + % (sTsNow, self.cHoursPerPeriod * self.cPeriods,); + sQuery = 'CREATE TEMPORARY TABLE TmpReasons ON COMMIT DROP AS\n' \ + 'SELECT TestResultFailures.idFailureReason AS idFailureReason,\n' \ + ' TestResultFailures.idTestResult AS idTestResult,\n' \ + ' TestSets.idTestSet AS idTestSet,\n' \ + ' TestSets.tsDone AS tsDone,\n' \ + ' TestSets.tsCreated AS tsCreated,\n' \ + ' TestSets.idBuild AS idBuild\n' \ + 'FROM TestResultFailures,\n' \ + ' TestResults,\n' \ + ' TestSets\n' \ + + self.oFilter.getTableJoins(dOmitTables = {'TestResults': True, 'TestResultFailures': True}); + for sTable in self.getExtraSubjectTables(): + if sTable not in [ 'TestResults', 'TestResultFailures' ] and not self.oFilter.isJoiningWithTable(sTable): + sQuery = sQuery[:-1] + ',\n ' + sTable + '\n'; + sQuery += 'WHERE TestResultFailures.idTestResult = TestResults.idTestResult\n' \ + ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP\n' \ + ' AND TestResultFailures.tsEffective >= ' + sTsFirst + '\n' \ + ' AND TestResults.enmStatus <> \'running\'\n' \ + ' AND TestResults.enmStatus <> \'success\'\n' \ + ' AND TestResults.tsCreated >= ' + sTsFirst + '\n' \ + ' AND TestResults.tsCreated < ' + sTsNow + '\n' \ + ' AND TestResults.idTestSet = TestSets.idTestSet\n' \ + ' AND TestSets.tsDone >= ' + sTsFirst + '\n' \ + ' AND TestSets.tsDone < ' + sTsNow + '\n' \ + + self.oFilter.getWhereConditions() \ + + self.getExtraSubjectWhereExpr(); + self._oDb.execute(sQuery); + self._oDb.execute('SELECT idFailureReason FROM TmpReasons;'); + + # + # Retrieve the period results. + # + oSet = ReportFailureReasonSet(); + for iPeriod in xrange(self.cPeriods): + self._oDb.execute('SELECT idFailureReason,\n' + ' COUNT(idTestResult),\n' + ' MIN(tsDone),\n' + ' MAX(tsDone)\n' + 'FROM TmpReasons\n' + 'WHERE TRUE\n' + + self.getExtraWhereExprForPeriod(iPeriod).replace('TestSets.', '') + + 'GROUP BY idFailureReason\n'); + aaoRows = self._oDb.fetchAll() + + oPeriod = ReportFailureReasonPeriod(oSet, iPeriod, self.getStraightPeriodDesc(iPeriod), + self.getPeriodStart(iPeriod), self.getPeriodEnd(iPeriod)); + + for aoRow in aaoRows: + oReason = oFailureReasonLogic.cachedLookup(aoRow[0]); + oPeriodRow = ReportFailureReasonRow(aoRow, oReason); + oPeriod.appendRow(oPeriodRow, oReason.idFailureReason, oReason); + + # Count how many test sets we've got without any reason associated with them. + self._oDb.execute('SELECT COUNT(TestSets.idTestSet)\n' + 'FROM TestSets\n' + ' LEFT OUTER JOIN TestResultFailures\n' + ' ON TestSets.idTestSet = TestResultFailures.idTestSet\n' + ' AND TestResultFailures.tsEffective = \'infinity\'::TIMESTAMP\n' + 'WHERE TestSets.enmStatus <> \'running\'\n' + ' AND TestSets.enmStatus <> \'success\'\n' + + self.getExtraWhereExprForPeriod(iPeriod) + + ' AND TestResultFailures.idTestSet IS NULL\n'); + oPeriod.cWithoutReason = self._oDb.fetchOne()[0]; + + oSet.appendPeriod(oPeriod); + + + # + # For reasons entering after the first period, look up the build and + # test set it first occured with. + # + oSet.finalizePass1(); + + for iPeriod in xrange(1, self.cPeriods): + oPeriod = oSet.aoPeriods[iPeriod]; + for oReason in oPeriod.dFirst.values(): + oSet.aoEnterInfo.append(self._getEdgeFailureReasonOccurence(oReason, iPeriod, fEnter = True)); + + # Ditto for reasons leaving before the last. + for iPeriod in xrange(self.cPeriods - 1): + oPeriod = oSet.aoPeriods[iPeriod]; + for oReason in oPeriod.dLast.values(): + oSet.aoLeaveInfo.append(self._getEdgeFailureReasonOccurence(oReason, iPeriod, fEnter = False)); + + oSet.finalizePass2(); + + self._oDb.execute('DROP TABLE TmpReasons\n'); + return oSet; + + + def _getEdgeFailureReasonOccurence(self, oReason, iPeriod, fEnter = True): + """ + Helper for the failure reason report that finds the oldest or newest build + (SVN rev) and test set (start time) it occured with. + + If fEnter is set the oldest occurence is return, if fEnter clear the newest + is is returned. + + Returns ReportFailureReasonTransient instant. + + """ + + + sSorting = 'ASC' if fEnter else 'DESC'; + self._oDb.execute('SELECT TmpReasons.idTestResult,\n' + ' TmpReasons.idTestSet,\n' + ' TmpReasons.tsDone,\n' + ' TmpReasons.idBuild,\n' + ' Builds.iRevision,\n' + ' BuildCategories.sRepository\n' + 'FROM TmpReasons,\n' + ' Builds,\n' + ' BuildCategories\n' + 'WHERE TmpReasons.idFailureReason = %s\n' + ' AND TmpReasons.idBuild = Builds.idBuild\n' + ' AND Builds.tsExpire > TmpReasons.tsCreated\n' + ' AND Builds.tsEffective <= TmpReasons.tsCreated\n' + ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n' + 'ORDER BY Builds.iRevision ' + sSorting + ',\n' + ' TmpReasons.tsCreated ' + sSorting + '\n' + 'LIMIT 1\n' + , ( oReason.idFailureReason, )); + aoRow = self._oDb.fetchOne(); + if aoRow is None: + return ReportFailureReasonTransient(-1, -1, 'internal-error', -1, -1, self._oDb.getCurrentTimestamp(), + iPeriod, fEnter, oReason); + return ReportFailureReasonTransient(idBuild = aoRow[3], iRevision = aoRow[4], sRepository = aoRow[5], + idTestSet = aoRow[1], idTestResult = aoRow[0], tsDone = aoRow[2], + iPeriod = iPeriod, fEnter = fEnter, oReason = oReason); + + + def getTestCaseFailures(self): + """ + Gets the test case failures of the subject in the specified period. + + Returns a ReportPeriodSetWithTotalBase instance. + + """ + return self._getSimpleFailures('idTestCase', TestCaseLogic); + + + def getTestCaseVariationFailures(self): + """ + Gets the test case failures of the subject in the specified period. + + Returns a ReportPeriodSetWithTotalBase instance. + + """ + return self._getSimpleFailures('idTestCaseArgs', TestCaseArgsLogic); + + + def getTestBoxFailures(self): + """ + Gets the test box failures of the subject in the specified period. + + Returns a ReportPeriodSetWithTotalBase instance. + + """ + return self._getSimpleFailures('idTestBox', TestBoxLogic); + + + def _getSimpleFailures(self, sIdColumn, oCacheLogicType, sIdAttr = None): + """ + Gets the test box failures of the subject in the specified period. + + Returns a ReportPeriodSetWithTotalBase instance. + + """ + + oLogic = oCacheLogicType(self._oDb); + oSet = ReportPeriodSetWithTotalBase(sIdColumn if sIdAttr is None else sIdAttr); + + # Construct base query. + sBaseQuery = 'SELECT TestSets.' + sIdColumn + ',\n' \ + ' COUNT(CASE WHEN TestSets.enmStatus >= \'failure\' THEN 1 END),\n' \ + ' MIN(TestSets.tsDone),\n' \ + ' MAX(TestSets.tsDone),\n' \ + ' COUNT(TestSets.idTestResult)\n' \ + 'FROM TestSets\n' \ + + self.oFilter.getTableJoins(); + for sTable in self.getExtraSubjectTables(): + sBaseQuery = sBaseQuery[:-1] + ',\n ' + sTable + '\n'; + sBaseQuery += 'WHERE TRUE\n' \ + + self.oFilter.getWhereConditions() \ + + self.getExtraSubjectWhereExpr() + '\n'; + + # Retrieve the period results. + for iPeriod in xrange(self.cPeriods): + self._oDb.execute(sBaseQuery + self.getExtraWhereExprForPeriod(iPeriod) + 'GROUP BY TestSets.' + sIdColumn + '\n'); + aaoRows = self._oDb.fetchAll() + + oPeriod = ReportPeriodWithTotalBase(oSet, iPeriod, self.getStraightPeriodDesc(iPeriod), + self.getPeriodStart(iPeriod), self.getPeriodEnd(iPeriod)); + + for aoRow in aaoRows: + oSubject = oLogic.cachedLookup(aoRow[0]); + oPeriodRow = ReportHitRowWithTotalBase(aoRow[0], oSubject, aoRow[1], aoRow[4], aoRow[2], aoRow[3]); + oPeriod.appendRow(oPeriodRow, aoRow[0], oSubject); + + oSet.appendPeriod(oPeriod); + oSet.pruneRowsWithZeroSumHits(); + + + + # + # For reasons entering after the first period, look up the build and + # test set it first occured with. + # + oSet.finalizePass1(); + + for iPeriod in xrange(1, self.cPeriods): + oPeriod = oSet.aoPeriods[iPeriod]; + for idSubject, oSubject in oPeriod.dFirst.items(): + oSet.aoEnterInfo.append(self._getEdgeSimpleFailureOccurence(idSubject, sIdColumn, oSubject, + iPeriod, fEnter = True)); + + # Ditto for reasons leaving before the last. + for iPeriod in xrange(self.cPeriods - 1): + oPeriod = oSet.aoPeriods[iPeriod]; + for idSubject, oSubject in oPeriod.dLast.items(): + oSet.aoLeaveInfo.append(self._getEdgeSimpleFailureOccurence(idSubject, sIdColumn, oSubject, + iPeriod, fEnter = False)); + + oSet.finalizePass2(); + + return oSet; + + def _getEdgeSimpleFailureOccurence(self, idSubject, sIdColumn, oSubject, iPeriod, fEnter = True): + """ + Helper for the failure reason report that finds the oldest or newest build + (SVN rev) and test set (start time) it occured with. + + If fEnter is set the oldest occurence is return, if fEnter clear the newest + is is returned. + + Returns ReportTransientBase instant. + + """ + sSorting = 'ASC' if fEnter else 'DESC'; + sQuery = 'SELECT TestSets.idTestResult,\n' \ + ' TestSets.idTestSet,\n' \ + ' TestSets.tsDone,\n' \ + ' TestSets.idBuild,\n' \ + ' Builds.iRevision,\n' \ + ' BuildCategories.sRepository\n' \ + 'FROM TestSets\n' \ + + self.oFilter.getTableJoins(dOmitTables = {'Builds': True, 'BuildCategories': True}); + sQuery = sQuery[:-1] + ',\n' \ + ' Builds,\n' \ + ' BuildCategories\n'; + for sTable in self.getExtraSubjectTables(): + if sTable not in [ 'Builds', 'BuildCategories' ] and not self.oFilter.isJoiningWithTable(sTable): + sQuery = sQuery[:-1] + ',\n ' + sTable + '\n'; + sQuery += 'WHERE TestSets.' + sIdColumn + ' = ' + str(idSubject) + '\n' \ + ' AND TestSets.idBuild = Builds.idBuild\n' \ + ' AND TestSets.enmStatus >= \'failure\'\n' \ + + self.getExtraWhereExprForPeriod(iPeriod) + \ + ' AND Builds.tsExpire > TestSets.tsCreated\n' \ + ' AND Builds.tsEffective <= TestSets.tsCreated\n' \ + ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n' \ + + self.oFilter.getWhereConditions() \ + + self.getExtraSubjectWhereExpr() + '\n' \ + 'ORDER BY Builds.iRevision ' + sSorting + ',\n' \ + ' TestSets.tsCreated ' + sSorting + '\n' \ + 'LIMIT 1\n'; + self._oDb.execute(sQuery); + aoRow = self._oDb.fetchOne(); + if aoRow is None: + return ReportTransientBase(-1, -1, 'internal-error', -1, -1, self._oDb.getCurrentTimestamp(), + iPeriod, fEnter, idSubject, oSubject); + return ReportTransientBase(idBuild = aoRow[3], iRevision = aoRow[4], sRepository = aoRow[5], + idTestSet = aoRow[1], idTestResult = aoRow[0], tsDone = aoRow[2], + iPeriod = iPeriod, fEnter = fEnter, idSubject = idSubject, oSubject = oSubject); + + def fetchPossibleFilterOptions(self, oFilter, tsNow, sPeriod): + """ + Fetches possible filtering options. + """ + return TestResultLogic(self._oDb).fetchPossibleFilterOptions(oFilter, tsNow, sPeriod, oReportModel = self); + + + +class ReportGraphModel(ReportModelBase): # pylint: disable=too-few-public-methods + """ + Extended report model used when generating the more complicated graphs + detailing results, time elapsed and values over time. + """ + + ## @name Subject ID types. + ## These prefix the values in the aidSubjects array. The prefix is + ## followed by a colon and then a list of string IDs. Following the prefix + ## is one or more string table IDs separated by colons. These are used to + ## drill down the exact test result we're looking for, by matching against + ## TestResult::idStrName (in the db). + ## @{ + ksTypeResult = 'result'; + ksTypeElapsed = 'elapsed'; + ## The last string table ID gives the name of the value. + ksTypeValue = 'value'; + ## List of types. + kasTypes = (ksTypeResult, ksTypeElapsed, ksTypeValue); + ## @} + + class SampleSource(object): + """ A sample source. """ + def __init__(self, sType, aidStrTests, idStrValue): + self.sType = sType; + self.aidStrTests = aidStrTests; + self.idStrValue = idStrValue; + + def getTestResultTables(self): + """ Retrieves the list of TestResults tables to join with.""" + sRet = ''; + for i in range(len(self.aidStrTests)): + sRet += ' TestResults TR%u,\n' % (i,); + return sRet; + + def getTestResultConditions(self): + """ Retrieves the join conditions for the TestResults tables.""" + sRet = ''; + cItems = len(self.aidStrTests); + for i in range(cItems - 1): + sRet += ' AND TR%u.idStrName = %u\n' \ + ' AND TR%u.idTestResultParent = TR%u.idTestResult\n' \ + % ( i, self.aidStrTests[cItems - i - 1], i, i + 1 ); + sRet += ' AND TR%u.idStrName = %u\n' % (cItems - 1, self.aidStrTests[0]); + return sRet; + + class DataSeries(object): + """ A data series. """ + def __init__(self, oCache, idBuildCategory, idTestBox, idTestCase, idTestCaseArgs, iUnit): + _ = oCache; + self.idBuildCategory = idBuildCategory; + self.oBuildCategory = oCache.getBuildCategory(idBuildCategory); + self.idTestBox = idTestBox; + self.oTestBox = oCache.getTestBox(idTestBox); + self.idTestCase = idTestCase; + self.idTestCaseArgs = idTestCaseArgs; + if idTestCase is not None: + self.oTestCase = oCache.getTestCase(idTestCase); + self.oTestCaseArgs = None; + else: + self.oTestCaseArgs = oCache.getTestCaseArgs(idTestCaseArgs); + self.oTestCase = oCache.getTestCase(self.oTestCaseArgs.idTestCase); + self.iUnit = iUnit; + # Six parallel arrays. + self.aiRevisions = []; # The X values. + self.aiValues = []; # The Y values. + self.aiErrorBarBelow = []; # The Y value minimum errorbars, relative to the Y value (positive). + self.aiErrorBarAbove = []; # The Y value maximum errorbars, relative to the Y value (positive). + self.acSamples = []; # The number of samples at this X value. + self.aoRevInfo = []; # VcsRevisionData objects for each revision. Empty/SQL-NULL objects if no info. + + class DataSeriesCollection(object): + """ A collection of data series corresponding to one input sample source. """ + def __init__(self, oSampleSrc, asTests, sValue = None): + self.sType = oSampleSrc.sType; + self.aidStrTests = oSampleSrc.aidStrTests; + self.asTests = list(asTests); + self.idStrValue = oSampleSrc.idStrValue; + self.sValue = sValue; + self.aoSeries = []; + + def addDataSeries(self, oDataSeries): + """ Appends a data series to the collection. """ + self.aoSeries.append(oDataSeries); + return oDataSeries; + + + def __init__(self, oDb, tsNow, cPeriods, cHoursPerPeriod, sSubject, aidSubjects, # pylint: disable=too-many-arguments + aidTestBoxes, aidBuildCats, aidTestCases, fSepTestVars): + assert(sSubject == self.ksSubEverything); # dummy + ReportModelBase.__init__(self, oDb, tsNow, cPeriods, cHoursPerPeriod, sSubject, aidSubjects, oFilter = None); + self.aidTestBoxes = aidTestBoxes; + self.aidBuildCats = aidBuildCats; + self.aidTestCases = aidTestCases; + self.fOnTestCase = not fSepTestVars; # (Separates testcase variations into separate data series.) + self.oCache = DatabaseObjCache(self._oDb, self.tsNow, None, self.cPeriods * self.cHoursPerPeriod); + + + # Quickly validate and convert the subject "IDs". + self.aoLookups = []; + for sCur in self.aidSubjects: + asParts = sCur.split(':'); + if len(asParts) < 2: + raise TMExceptionBase('Invalid graph value "%s"' % (sCur,)); + + sType = asParts[0]; + if sType not in ReportGraphModel.kasTypes: + raise TMExceptionBase('Invalid graph value type "%s" (full: "%s")' % (sType, sCur,)); + + aidStrTests = []; + for sIdStr in asParts[1:]: + try: idStr = int(sIdStr); + except: raise TMExceptionBase('Invalid graph value id "%s" (full: "%s")' % (sIdStr, sCur,)); + if idStr < 0: + raise TMExceptionBase('Invalid graph value id "%u" (full: "%s")' % (idStr, sCur,)); + aidStrTests.append(idStr); + + idStrValue = None; + if sType == ReportGraphModel.ksTypeValue: + idStrValue = aidStrTests.pop(); + self.aoLookups.append(ReportGraphModel.SampleSource(sType, aidStrTests, idStrValue)); + + # done + + + def getExtraWhereExprForTotalPeriod(self, sTimestampField): + """ + Returns additional WHERE expression for getting test sets for the + specified period. It starts with an AND so that it can simply be + appended to the WHERE clause. + """ + return self.getExtraWhereExprForTotalPeriodEx(sTimestampField, sTimestampField, True); + + def getExtraWhereExprForTotalPeriodEx(self, sStartField = 'tsCreated', sEndField = 'tsDone', fLeadingAnd = True): + """ + Returns additional WHERE expression for getting test sets for the + specified period. + """ + if self.tsNow is None: + sNow = 'CURRENT_TIMESTAMP'; + else: + sNow = self._oDb.formatBindArgs('%s::TIMESTAMP', (self.tsNow,)); + + sRet = ' AND %s >= (%s - interval \'%u hours\')\n' \ + ' AND %s <= %s\n' \ + % ( sStartField, sNow, self.cPeriods * self.cHoursPerPeriod, + sEndField, sNow); + + if not fLeadingAnd: + assert sRet[8] == ' ' and sRet[7] == 'D'; + return sRet[9:]; + return sRet; + + def _getEligibleTestSetPeriod(self, sPrefix = 'TestSets.', fLeadingAnd = False): + """ + Returns additional WHERE expression for getting TestSets rows + potentially relevant for the selected period. + """ + if self.tsNow is None: + sNow = 'CURRENT_TIMESTAMP'; + else: + sNow = self._oDb.formatBindArgs('%s::TIMESTAMP', (self.tsNow,)); + + # The 2nd line is a performance hack on TestSets. It nudges postgresql + # into useing the TestSetsCreatedDoneIdx index instead of doing a table + # scan when we look for eligible bits there. + # ASSUMES no relevant test runs longer than 7 days! + sRet = ' AND %stsCreated <= %s\n' \ + ' AND %stsCreated >= (%s - interval \'%u hours\' - interval \'%u days\')\n' \ + ' AND %stsDone >= (%s - interval \'%u hours\')\n' \ + % ( sPrefix, sNow, + sPrefix, sNow, self.cPeriods * self.cHoursPerPeriod, 7, + sPrefix, sNow, self.cPeriods * self.cHoursPerPeriod, ); + + if not fLeadingAnd: + assert sRet[8] == ' ' and sRet[7] == 'D'; + return sRet[9:]; + return sRet; + + + def _getNameStrings(self, aidStrTests): + """ Returns an array of names corresponding to the array of string table entries. """ + return [self.oCache.getTestResultString(idStr) for idStr in aidStrTests]; + + def fetchGraphData(self): + """ returns data """ + sWantedTestCaseId = 'idTestCase' if self.fOnTestCase else 'idTestCaseArgs'; + + aoRet = []; + for oLookup in self.aoLookups: + # + # Set up the result collection. + # + if oLookup.sType == self.ksTypeValue: + oCollection = self.DataSeriesCollection(oLookup, self._getNameStrings(oLookup.aidStrTests), + self.oCache.getTestResultString(oLookup.idStrValue)); + else: + oCollection = self.DataSeriesCollection(oLookup, self._getNameStrings(oLookup.aidStrTests)); + + # + # Construct the query. + # + sQuery = 'SELECT Builds.iRevision,\n' \ + ' TestSets.idBuildCategory,\n' \ + ' TestSets.idTestBox,\n' \ + ' TestSets.' + sWantedTestCaseId + ',\n'; + if oLookup.sType == self.ksTypeValue: + sQuery += ' TestResultValues.iUnit as iUnit,\n' \ + ' MIN(TestResultValues.lValue),\n' \ + ' CAST(ROUND(AVG(TestResultValues.lValue)) AS BIGINT),\n' \ + ' MAX(TestResultValues.lValue),\n' \ + ' COUNT(TestResultValues.lValue)\n'; + elif oLookup.sType == self.ksTypeElapsed: + sQuery += ' %u as iUnit,\n' \ + ' CAST((EXTRACT(EPOCH FROM MIN(TR0.tsElapsed)) * 1000) AS INTEGER),\n' \ + ' CAST((EXTRACT(EPOCH FROM AVG(TR0.tsElapsed)) * 1000) AS INTEGER),\n' \ + ' CAST((EXTRACT(EPOCH FROM MAX(TR0.tsElapsed)) * 1000) AS INTEGER),\n' \ + ' COUNT(TR0.tsElapsed)\n' \ + % (constants.valueunit.MS,); + else: + sQuery += ' %u as iUnit,\n'\ + ' MIN(TR0.cErrors),\n' \ + ' CAST(ROUND(AVG(TR0.cErrors)) AS INTEGER),\n' \ + ' MAX(TR0.cErrors),\n' \ + ' COUNT(TR0.cErrors)\n' \ + % (constants.valueunit.OCCURRENCES,); + + if oLookup.sType == self.ksTypeValue: + sQuery += 'FROM TestResultValues,\n'; + sQuery += ' TestSets,\n' + sQuery += oLookup.getTestResultTables(); + else: + sQuery += 'FROM ' + oLookup.getTestResultTables().lstrip(); + sQuery += ' TestSets,\n'; + sQuery += ' Builds\n'; + + if oLookup.sType == self.ksTypeValue: + sQuery += 'WHERE TestResultValues.idStrName = %u\n' % ( oLookup.idStrValue, ); + sQuery += self.getExtraWhereExprForTotalPeriod('TestResultValues.tsCreated'); + sQuery += ' AND TestResultValues.idTestSet = TestSets.idTestSet\n'; + sQuery += self._getEligibleTestSetPeriod(fLeadingAnd = True); + else: + sQuery += 'WHERE ' + (self.getExtraWhereExprForTotalPeriod('TR0.tsCreated').lstrip()[4:]).lstrip(); + sQuery += ' AND TR0.idTestSet = TestSets.idTestSet\n'; + + if len(self.aidTestBoxes) == 1: + sQuery += ' AND TestSets.idTestBox = %u\n' % (self.aidTestBoxes[0],); + elif self.aidTestBoxes: + sQuery += ' AND TestSets.idTestBox IN (' + ','.join([str(i) for i in self.aidTestBoxes]) + ')\n'; + + if len(self.aidBuildCats) == 1: + sQuery += ' AND TestSets.idBuildCategory = %u\n' % (self.aidBuildCats[0],); + elif self.aidBuildCats: + sQuery += ' AND TestSets.idBuildCategory IN (' + ','.join([str(i) for i in self.aidBuildCats]) + ')\n'; + + if len(self.aidTestCases) == 1: + sQuery += ' AND TestSets.idTestCase = %u\n' % (self.aidTestCases[0],); + elif self.aidTestCases: + sQuery += ' AND TestSets.idTestCase IN (' + ','.join([str(i) for i in self.aidTestCases]) + ')\n'; + + if oLookup.sType == self.ksTypeElapsed: + sQuery += ' AND TestSets.enmStatus = \'%s\'::TestStatus_T\n' % (self.ksTestStatus_Success,); + + if oLookup.sType == self.ksTypeValue: + sQuery += ' AND TestResultValues.idTestResult = TR0.idTestResult\n' + sQuery += self.getExtraWhereExprForTotalPeriod('TR0.tsCreated'); # For better index matching in some cases. + + if oLookup.sType != self.ksTypeResult: + sQuery += ' AND TR0.enmStatus = \'%s\'::TestStatus_T\n' % (self.ksTestStatus_Success,); + + sQuery += oLookup.getTestResultConditions(); + sQuery += ' AND TestSets.idBuild = Builds.idBuild\n'; + + sQuery += 'GROUP BY TestSets.idBuildCategory,\n' \ + ' TestSets.idTestBox,\n' \ + ' TestSets.' + sWantedTestCaseId + ',\n' \ + ' iUnit,\n' \ + ' Builds.iRevision\n'; + sQuery += 'ORDER BY TestSets.idBuildCategory,\n' \ + ' TestSets.idTestBox,\n' \ + ' TestSets.' + sWantedTestCaseId + ',\n' \ + ' iUnit,\n' \ + ' Builds.iRevision\n'; + + # + # Execute it and collect the result. + # + sCurRepository = None; + dRevisions = {}; + oLastSeries = None; + idLastBuildCat = -1; + idLastTestBox = -1; + idLastTestCase = -1; + iLastUnit = -1; + self._oDb.execute(sQuery); + for aoRow in self._oDb.fetchAll(): # Fetching all here so we can make cache queries below. + if aoRow[1] != idLastBuildCat \ + or aoRow[2] != idLastTestBox \ + or aoRow[3] != idLastTestCase \ + or aoRow[4] != iLastUnit: + idLastBuildCat = aoRow[1]; + idLastTestBox = aoRow[2]; + idLastTestCase = aoRow[3]; + iLastUnit = aoRow[4]; + if self.fOnTestCase: + oLastSeries = self.DataSeries(self.oCache, idLastBuildCat, idLastTestBox, + idLastTestCase, None, iLastUnit); + else: + oLastSeries = self.DataSeries(self.oCache, idLastBuildCat, idLastTestBox, + None, idLastTestCase, iLastUnit); + oCollection.addDataSeries(oLastSeries); + if oLastSeries.oBuildCategory.sRepository != sCurRepository: + if sCurRepository is not None: + self.oCache.preloadVcsRevInfo(sCurRepository, dRevisions.keys()); + sCurRepository = oLastSeries.oBuildCategory.sRepository + dRevisions = {}; + oLastSeries.aiRevisions.append(aoRow[0]); + oLastSeries.aiValues.append(aoRow[6]); + oLastSeries.aiErrorBarBelow.append(aoRow[6] - aoRow[5]); + oLastSeries.aiErrorBarAbove.append(aoRow[7] - aoRow[6]); + oLastSeries.acSamples.append(aoRow[8]); + dRevisions[aoRow[0]] = 1; + + if sCurRepository is not None: + self.oCache.preloadVcsRevInfo(sCurRepository, dRevisions.keys()); + del dRevisions; + + # + # Look up the VCS revision details. + # + for oSeries in oCollection.aoSeries: + for iRevision in oSeries.aiRevisions: + oSeries.aoRevInfo.append(self.oCache.getVcsRevInfo(sCurRepository, iRevision)); + aoRet.append(oCollection); + + return aoRet; + + def getEligibleTestBoxes(self): + """ + Returns a list of TestBoxData objects with eligible testboxes for + the total period of time defined for this graph. + """ + + # Taking the simple way out now, getting all active testboxes at the + # time without filtering out on sample sources. + + # 1. Collect the relevant testbox generation IDs. + self._oDb.execute('SELECT DISTINCT idTestBox, idGenTestBox\n' + 'FROM TestSets\n' + 'WHERE ' + self._getEligibleTestSetPeriod(fLeadingAnd = False) + + 'ORDER BY idTestBox, idGenTestBox DESC'); + idPrevTestBox = -1; + asIdGenTestBoxes = []; + for _ in range(self._oDb.getRowCount()): + aoRow = self._oDb.fetchOne(); + if aoRow[0] != idPrevTestBox: + idPrevTestBox = aoRow[0]; + asIdGenTestBoxes.append(str(aoRow[1])); + + # 2. Query all the testbox data in one go. + aoRet = []; + if asIdGenTestBoxes: + self._oDb.execute('SELECT *\n' + 'FROM TestBoxesWithStrings\n' + 'WHERE idGenTestBox IN (' + ','.join(asIdGenTestBoxes) + ')\n' + 'ORDER BY sName'); + for _ in range(self._oDb.getRowCount()): + aoRet.append(TestBoxData().initFromDbRow(self._oDb.fetchOne())); + + return aoRet; + + def getEligibleBuildCategories(self): + """ + Returns a list of BuildCategoryData objects with eligible build + categories for the total period of time defined for this graph. In + addition it will add any currently selected categories that aren't + really relevant to the period, just to simplify the WUI code. + + """ + + # Taking the simple way out now, getting all used build cat without + # any testbox or testcase filtering. + + sSelectedBuildCats = ''; + if self.aidBuildCats: + sSelectedBuildCats = ' OR idBuildCategory IN (' + ','.join([str(i) for i in self.aidBuildCats]) + ')\n'; + + self._oDb.execute('SELECT DISTINCT *\n' + 'FROM BuildCategories\n' + 'WHERE idBuildCategory IN (\n' + ' SELECT DISTINCT idBuildCategory\n' + ' FROM TestSets\n' + ' WHERE ' + self._getEligibleTestSetPeriod(fLeadingAnd = False) + + ')\n' + + sSelectedBuildCats + + 'ORDER BY sProduct,\n' + ' sBranch,\n' + ' asOsArches,\n' + ' sType\n'); + aoRet = []; + for _ in range(self._oDb.getRowCount()): + aoRet.append(BuildCategoryData().initFromDbRow(self._oDb.fetchOne())); + + return aoRet; + diff --git a/src/VBox/ValidationKit/testmanager/core/restdispatcher.py b/src/VBox/ValidationKit/testmanager/core/restdispatcher.py new file mode 100755 index 00000000..69c2d63d --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/restdispatcher.py @@ -0,0 +1,455 @@ +# -*- coding: utf-8 -*- +# $Id: restdispatcher.py $ + +""" +Test Manager Core - REST cgi handler. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import os; +import sys; + +# Validation Kit imports. +#from common import constants; +from common import utils; +from testmanager import config; +#from testmanager.core import coreconsts; +from testmanager.core.db import TMDatabaseConnection; +from testmanager.core.base import TMExceptionBase, ModelDataBase; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + + +# +# Exceptions +# + +class RestDispException(TMExceptionBase): + """ + Exception class for the REST dispatcher. + """ + def __init__(self, sMsg, iStatus): + TMExceptionBase.__init__(self, sMsg); + self.iStatus = iStatus; + +# 400 +class RestDispException400(RestDispException): + """ A 400 error """ + def __init__(self, sMsg): + RestDispException.__init__(self, sMsg, 400); + +class RestUnknownParameters(RestDispException400): + """ Unknown parameter(s). """ + pass; # pylint: disable=unnecessary-pass + +# 404 +class RestDispException404(RestDispException): + """ A 404 error """ + def __init__(self, sMsg): + RestDispException.__init__(self, sMsg, 404); + +class RestBadPathException(RestDispException404): + """ We've got a bad path. """ + pass; # pylint: disable=unnecessary-pass + +class RestBadParameter(RestDispException404): + """ Bad parameter. """ + pass; # pylint: disable=unnecessary-pass + +class RestMissingParameter(RestDispException404): + """ Missing parameter. """ + pass; # pylint: disable=unnecessary-pass + + + +class RestMain(object): # pylint: disable=too-few-public-methods + """ + REST main dispatcher class. + """ + + ksParam_sPath = 'sPath'; + + + def __init__(self, oSrvGlue): + self._oSrvGlue = oSrvGlue; + self._oDb = TMDatabaseConnection(oSrvGlue.dprint); + self._iFirstHandlerPath = 0; + self._iNextHandlerPath = 0; + self._sPath = None; # _getStandardParams / dispatchRequest sets this later on. + self._asPath = None; # _getStandardParams / dispatchRequest sets this later on. + self._sMethod = None; # _getStandardParams / dispatchRequest sets this later on. + self._dParams = None; # _getStandardParams / dispatchRequest sets this later on. + self._asCheckedParams = []; + self._dGetTree = { + 'vcs': { + 'changelog': self._handleVcsChangelog_Get, + 'bugreferences': self._handleVcsBugReferences_Get, + }, + }; + self._dMethodTrees = { + 'GET': self._dGetTree, + } + + # + # Helpers. + # + + def _getStringParam(self, sName, asValidValues = None, fStrip = False, sDefValue = None): + """ + Gets a string parameter (stripped). + + Raises exception if not found and no default is provided, or if the + value isn't found in asValidValues. + """ + if sName not in self._dParams: + if sDefValue is None: + raise RestMissingParameter('%s parameter %s is missing' % (self._sPath, sName)); + return sDefValue; + sValue = self._dParams[sName]; + if isinstance(sValue, list): + if len(sValue) == 1: + sValue = sValue[0]; + else: + raise RestBadParameter('%s parameter %s value is not a string but list: %s' + % (self._sPath, sName, sValue)); + if fStrip: + sValue = sValue.strip(); + + if sName not in self._asCheckedParams: + self._asCheckedParams.append(sName); + + if asValidValues is not None and sValue not in asValidValues: + raise RestBadParameter('%s parameter %s value "%s" not in %s ' + % (self._sPath, sName, sValue, asValidValues)); + return sValue; + + def _getBoolParam(self, sName, fDefValue = None): + """ + Gets a boolean parameter. + + Raises exception if not found and no default is provided, or if not a + valid boolean. + """ + sValue = self._getStringParam(sName, [ 'True', 'true', '1', 'False', 'false', '0'], sDefValue = str(fDefValue)); + return sValue in ('True', 'true', '1',); + + def _getIntParam(self, sName, iMin = None, iMax = None): + """ + Gets a string parameter. + Raises exception if not found, not a valid integer, or if the value + isn't in the range defined by iMin and iMax. + """ + sValue = self._getStringParam(sName); + try: + iValue = int(sValue, 0); + except: + raise RestBadParameter('%s parameter %s value "%s" cannot be convert to an integer' + % (self._sPath, sName, sValue)); + + if (iMin is not None and iValue < iMin) \ + or (iMax is not None and iValue > iMax): + raise RestBadParameter('%s parameter %s value %d is out of range [%s..%s]' + % (self._sPath, sName, iValue, iMin, iMax)); + return iValue; + + def _getLongParam(self, sName, lMin = None, lMax = None, lDefValue = None): + """ + Gets a string parameter. + Raises exception if not found, not a valid long integer, or if the value + isn't in the range defined by lMin and lMax. + """ + sValue = self._getStringParam(sName, sDefValue = (str(lDefValue) if lDefValue is not None else None)); + try: + lValue = long(sValue, 0); + except Exception as oXcpt: + raise RestBadParameter('%s parameter %s value "%s" cannot be convert to an integer (%s)' + % (self._sPath, sName, sValue, oXcpt)); + + if (lMin is not None and lValue < lMin) \ + or (lMax is not None and lValue > lMax): + raise RestBadParameter('%s parameter %s value %d is out of range [%s..%s]' + % (self._sPath, sName, lValue, lMin, lMax)); + return lValue; + + def _checkForUnknownParameters(self): + """ + Check if we've handled all parameters, raises exception if anything + unknown was found. + """ + + if len(self._asCheckedParams) != len(self._dParams): + sUnknownParams = ''; + for sKey in self._dParams: + if sKey not in self._asCheckedParams: + sUnknownParams += ' ' + sKey + '=' + self._dParams[sKey]; + raise RestUnknownParameters('Unknown parameters: ' + sUnknownParams); + + return True; + + def writeToMainLog(self, oTestSet, sText, fIgnoreSizeCheck = False): + """ Writes the text to the main log file. """ + + # Calc the file name and open the file. + sFile = os.path.join(config.g_ksFileAreaRootDir, oTestSet.sBaseFilename + '-main.log'); + if not os.path.exists(os.path.dirname(sFile)): + os.makedirs(os.path.dirname(sFile), 0o755); + + with open(sFile, 'ab') as oFile: + # Check the size. + fSizeOk = True; + if not fIgnoreSizeCheck: + oStat = os.fstat(oFile.fileno()); + fSizeOk = oStat.st_size / (1024 * 1024) < config.g_kcMbMaxMainLog; + + # Write the text. + if fSizeOk: + if sys.version_info[0] >= 3: + oFile.write(bytes(sText, 'utf-8')); + else: + oFile.write(sText); + + return fSizeOk; + + def _getNextPathElementString(self, sName, oDefault = None): + """ + Gets the next handler specific path element. + Returns unprocessed string. + Throws exception + """ + i = self._iNextHandlerPath; + if i < len(self._asPath): + self._iNextHandlerPath = i + 1; + return self._asPath[i]; + if oDefault is None: + raise RestBadPathException('Requires a "%s" element after "%s"' % (sName, self._sPath,)); + return oDefault; + + def _getNextPathElementInt(self, sName, iDefault = None, iMin = None, iMax = None): + """ + Gets the next handle specific path element as an integer. + Returns integer value. + Throws exception if not found or not a valid integer. + """ + sValue = self._getNextPathElementString(sName, oDefault = iDefault); + try: + iValue = int(sValue); + except: + raise RestBadPathException('Not an integer "%s" (%s)' % (sValue, sName,)); + if iMin is not None and iValue < iMin: + raise RestBadPathException('Integer "%s" value (%s) is too small, min %s' % (sValue, sName, iMin)); + if iMax is not None and iValue > iMax: + raise RestBadPathException('Integer "%s" value (%s) is too large, max %s' % (sValue, sName, iMax)); + return iValue; + + def _getNextPathElementLong(self, sName, iDefault = None, iMin = None, iMax = None): + """ + Gets the next handle specific path element as a long integer. + Returns integer value. + Throws exception if not found or not a valid integer. + """ + sValue = self._getNextPathElementString(sName, oDefault = iDefault); + try: + iValue = long(sValue); + except: + raise RestBadPathException('Not an integer "%s" (%s)' % (sValue, sName,)); + if iMin is not None and iValue < iMin: + raise RestBadPathException('Integer "%s" value (%s) is too small, min %s' % (sValue, sName, iMin)); + if iMax is not None and iValue > iMax: + raise RestBadPathException('Integer "%s" value (%s) is too large, max %s' % (sValue, sName, iMax)); + return iValue; + + def _checkNoMorePathElements(self): + """ + Checks that there are no more path elements. + Throws exception if there are. + """ + i = self._iNextHandlerPath; + if i < len(self._asPath): + raise RestBadPathException('Unknown subpath "%s" below "%s"' % + ('/'.join(self._asPath[i:]), '/'.join(self._asPath[:i]),)); + return True; + + def _doneParsingArguments(self): + """ + Checks that there are no more path elements or unhandled parameters. + Throws exception if there are. + """ + self._checkNoMorePathElements(); + self._checkForUnknownParameters(); + return True; + + def _dataArrayToJsonReply(self, aoData, sName = 'aoData', dExtraFields = None, iStatus = 200): + """ + Converts aoData into an array objects + return True. + """ + self._oSrvGlue.setContentType('application/json'); + self._oSrvGlue.setStatus(iStatus); + self._oSrvGlue.write(u'{\n'); + if dExtraFields: + for sKey in dExtraFields: + self._oSrvGlue.write(u' "%s": %s,\n' % (sKey, ModelDataBase.genericToJson(dExtraFields[sKey]),)); + self._oSrvGlue.write(u' "c%s": %u,\n' % (sName[2:],len(aoData),)); + self._oSrvGlue.write(u' "%s": [\n' % (sName,)); + for i, oData in enumerate(aoData): + if i > 0: + self._oSrvGlue.write(u',\n'); + self._oSrvGlue.write(ModelDataBase.genericToJson(oData)); + self._oSrvGlue.write(u' ]\n'); + ## @todo if config.g_kfWebUiSqlDebug: + self._oSrvGlue.write(u'}\n'); + self._oSrvGlue.flush(); + return True; + + + # + # Handlers. + # + + def _handleVcsChangelog_Get(self): + """ GET /vcs/changelog/{sRepository}/{iStartRev}[/{cEntriesBack}] """ + # Parse arguments + sRepository = self._getNextPathElementString('sRepository'); + iStartRev = self._getNextPathElementInt('iStartRev', iMin = 0); + cEntriesBack = self._getNextPathElementInt('cEntriesBack', iDefault = 32, iMin = 0, iMax = 8192); + self._checkNoMorePathElements(); + self._checkForUnknownParameters(); + + # Execute it. + from testmanager.core.vcsrevisions import VcsRevisionLogic; + oLogic = VcsRevisionLogic(self._oDb); + return self._dataArrayToJsonReply(oLogic.fetchTimeline(sRepository, iStartRev, cEntriesBack), 'aoCommits', + { 'sTracChangesetUrlFmt': + config.g_ksTracChangsetUrlFmt.replace('%(sRepository)s', sRepository), } ); + + def _handleVcsBugReferences_Get(self): + """ GET /vcs/bugreferences/{sTrackerId}/{lBugId} """ + # Parse arguments + sTrackerId = self._getNextPathElementString('sTrackerId'); + lBugId = self._getNextPathElementLong('lBugId', iMin = 0); + self._checkNoMorePathElements(); + self._checkForUnknownParameters(); + + # Execute it. + from testmanager.core.vcsbugreference import VcsBugReferenceLogic; + oLogic = VcsBugReferenceLogic(self._oDb); + oLogic.fetchForBug(sTrackerId, lBugId) + return self._dataArrayToJsonReply(oLogic.fetchForBug(sTrackerId, lBugId), 'aoCommits', + { 'sTracChangesetUrlFmt': config.g_ksTracChangsetUrlFmt, } ); + + + # + # Dispatching. + # + + def _dispatchRequestCommon(self): + """ + Dispatches the incoming request after have gotten the path and parameters. + + Will raise RestDispException on failure. + """ + + # + # Split up the path. + # + asPath = self._sPath.split('/'); + self._asPath = asPath; + + # + # Get the method and the corresponding handler tree. + # + try: + sMethod = self._oSrvGlue.getMethod(); + except Exception as oXcpt: + raise RestDispException('Error retriving request method: %s' % (oXcpt,), 400); + self._sMethod = sMethod; + + try: + dTree = self._dMethodTrees[sMethod]; + except KeyError: + raise RestDispException('Unsupported method %s' % (sMethod,), 405); + + # + # Walk the path till we find a handler for it. + # + iPath = 0; + while iPath < len(asPath): + try: + oTreeOrHandler = dTree[asPath[iPath]]; + except KeyError: + raise RestBadPathException('Path element #%u "%s" not found (path="%s")' % (iPath, asPath[iPath], self._sPath)); + iPath += 1; + if isinstance(oTreeOrHandler, dict): + dTree = oTreeOrHandler; + else: + # + # Call the handler. + # + self._iFirstHandlerPath = iPath; + self._iNextHandlerPath = iPath; + return oTreeOrHandler(); + + raise RestBadPathException('Empty path (%s)' % (self._sPath,)); + + def dispatchRequest(self): + """ + Dispatches the incoming request where the path is given as an argument. + + Will raise RestDispException on failure. + """ + + # + # Get the parameters. + # + try: + dParams = self._oSrvGlue.getParameters(); + except Exception as oXcpt: + raise RestDispException('Error retriving parameters: %s' % (oXcpt,), 500); + self._dParams = dParams; + + # + # Get the path parameter. + # + if self.ksParam_sPath not in dParams: + raise RestDispException('No "%s" parameter in request (params: %s)' % (self.ksParam_sPath, dParams,), 500); + self._sPath = self._getStringParam(self.ksParam_sPath); + assert utils.isString(self._sPath); + + return self._dispatchRequestCommon(); + diff --git a/src/VBox/ValidationKit/testmanager/core/schedgroup.py b/src/VBox/ValidationKit/testmanager/core/schedgroup.py new file mode 100755 index 00000000..2bb43ae5 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/schedgroup.py @@ -0,0 +1,1352 @@ +# -*- coding: utf-8 -*- +# $Id: schedgroup.py $ + +""" +Test Manager - Scheduling Group. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard python imports. +import unittest; + +# Validation Kit imports. +from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMExceptionBase, \ + TMRowInUse, TMInvalidData, TMRowAlreadyExists, TMRowNotFound, \ + ChangeLogEntry, AttributeChangeEntry, AttributeChangeEntryPre; +from testmanager.core.buildsource import BuildSourceData; +from testmanager.core import db; +from testmanager.core.testcase import TestCaseData; +from testmanager.core.testcaseargs import TestCaseArgsData; +from testmanager.core.testbox import TestBoxLogic, TestBoxDataForSchedGroup; +from testmanager.core.testgroup import TestGroupData; +from testmanager.core.useraccount import UserAccountLogic; + + + +class SchedGroupMemberData(ModelDataBase): + """ + SchedGroupMember Data. + """ + + ksIdAttr = 'idSchedGroup'; + + ksParam_idSchedGroup = 'SchedGroupMember_idSchedGroup'; + ksParam_idTestGroup = 'SchedGroupMember_idTestGroup'; + ksParam_tsEffective = 'SchedGroupMember_tsEffective'; + ksParam_tsExpire = 'SchedGroupMember_tsExpire'; + ksParam_uidAuthor = 'SchedGroupMember_uidAuthor'; + ksParam_iSchedPriority = 'SchedGroupMember_iSchedPriority'; + ksParam_bmHourlySchedule = 'SchedGroupMember_bmHourlySchedule'; + ksParam_idTestGroupPreReq = 'SchedGroupMember_idTestGroupPreReq'; + + kasAllowNullAttributes = [ 'idSchedGroup', 'idTestGroup', 'tsEffective', 'tsExpire', + 'uidAuthor', 'bmHourlySchedule', 'idTestGroupPreReq' ]; + kiMin_iSchedPriority = 0; + kiMax_iSchedPriority = 32; + + kcDbColumns = 8 + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.idSchedGroup = None; + self.idTestGroup = None; + self.tsEffective = None; + self.tsExpire = None; + self.uidAuthor = None; + self.iSchedPriority = 16; + self.bmHourlySchedule = None; + self.idTestGroupPreReq = None; + + def initFromDbRow(self, aoRow): + """ + Re-initializes the data with a row from a SELECT * FROM SchedGroupMembers. + + Returns self. Raises exception if the row is None or otherwise invalid. + """ + + if aoRow is None: + raise TMRowNotFound('SchedGroupMember not found.'); + + self.idSchedGroup = aoRow[0]; + self.idTestGroup = aoRow[1]; + self.tsEffective = aoRow[2]; + self.tsExpire = aoRow[3]; + self.uidAuthor = aoRow[4]; + self.iSchedPriority = aoRow[5]; + self.bmHourlySchedule = aoRow[6]; ## @todo figure out how bitmaps are returned... + self.idTestGroupPreReq = aoRow[7]; + return self; + + +class SchedGroupMemberDataEx(SchedGroupMemberData): + """ + Extended SchedGroupMember data class. + This adds the testgroups. + """ + + def __init__(self): + SchedGroupMemberData.__init__(self); + self.oTestGroup = None; + + def initFromDbRow(self, aoRow): + """ + Re-initializes the data with a row from a query like this: + + SELECT SchedGroupMembers.*, TestGroups.* + FROM SchedGroupMembers + JOIN TestGroups + ON (SchedGroupMembers.idTestGroup = TestGroups.idTestGroup); + + Returns self. Raises exception if the row is None or otherwise invalid. + """ + SchedGroupMemberData.initFromDbRow(self, aoRow); + self.oTestGroup = TestGroupData().initFromDbRow(aoRow[SchedGroupMemberData.kcDbColumns:]); + return self; + + def getDataAttributes(self): + asAttributes = SchedGroupMemberData.getDataAttributes(self); + asAttributes.remove('oTestGroup'); + return asAttributes; + + def _validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor = ModelDataBase.ksValidateFor_Other): + dErrors = SchedGroupMemberData._validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor); + if self.ksParam_idTestGroup not in dErrors: + self.oTestGroup = TestGroupData(); + try: + self.oTestGroup.initFromDbWithId(oDb, self.idTestGroup); + except Exception as oXcpt: + self.oTestGroup = TestGroupData() + dErrors[self.ksParam_idTestGroup] = str(oXcpt); + return dErrors; + + + + +class SchedGroupData(ModelDataBase): + """ + SchedGroup Data. + """ + + ## @name TestBoxState_T + # @{ + ksScheduler_BestEffortContinuousIntegration = 'bestEffortContinousItegration'; # sic*2 + ksScheduler_Reserved = 'reserved'; + ## @} + + + ksIdAttr = 'idSchedGroup'; + + ksParam_idSchedGroup = 'SchedGroup_idSchedGroup'; + ksParam_tsEffective = 'SchedGroup_tsEffective'; + ksParam_tsExpire = 'SchedGroup_tsExpire'; + ksParam_uidAuthor = 'SchedGroup_uidAuthor'; + ksParam_sName = 'SchedGroup_sName'; + ksParam_sDescription = 'SchedGroup_sDescription'; + ksParam_fEnabled = 'SchedGroup_fEnabled'; + ksParam_enmScheduler = 'SchedGroup_enmScheduler'; + ksParam_idBuildSrc = 'SchedGroup_idBuildSrc'; + ksParam_idBuildSrcTestSuite = 'SchedGroup_idBuildSrcTestSuite'; + ksParam_sComment = 'SchedGroup_sComment'; + + kasAllowNullAttributes = ['idSchedGroup', 'tsEffective', 'tsExpire', 'uidAuthor', 'sDescription', + 'idBuildSrc', 'idBuildSrcTestSuite', 'sComment' ]; + kasValidValues_enmScheduler = [ ksScheduler_BestEffortContinuousIntegration, ]; + + kcDbColumns = 11; + + # Scheduler types + kasSchedulerDesc = \ + [ + ( ksScheduler_BestEffortContinuousIntegration, 'Best-Effort-Continuous-Integration (BECI) scheduler.', ''), + ] + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.idSchedGroup = None; + self.tsEffective = None; + self.tsExpire = None; + self.uidAuthor = None; + self.sName = None; + self.sDescription = None; + self.fEnabled = None; + self.enmScheduler = SchedGroupData.ksScheduler_BestEffortContinuousIntegration; + self.idBuildSrc = None; + self.idBuildSrcTestSuite = None; + self.sComment = None; + + def initFromDbRow(self, aoRow): + """ + Re-initializes the data with a row from a SELECT * FROM SchedGroups. + + Returns self. Raises exception if the row is None or otherwise invalid. + """ + + if aoRow is None: + raise TMRowNotFound('SchedGroup not found.'); + + self.idSchedGroup = aoRow[0]; + self.tsEffective = aoRow[1]; + self.tsExpire = aoRow[2]; + self.uidAuthor = aoRow[3]; + self.sName = aoRow[4]; + self.sDescription = aoRow[5]; + self.fEnabled = aoRow[6]; + self.enmScheduler = aoRow[7]; + self.idBuildSrc = aoRow[8]; + self.idBuildSrcTestSuite = aoRow[9]; + self.sComment = aoRow[10]; + return self; + + def initFromDbWithId(self, oDb, idSchedGroup, tsNow = None, sPeriodBack = None): + """ + Initialize the object from the database. + """ + oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb, + 'SELECT *\n' + 'FROM SchedGroups\n' + 'WHERE idSchedGroup = %s\n' + , ( idSchedGroup,), tsNow, sPeriodBack)); + aoRow = oDb.fetchOne() + if aoRow is None: + raise TMRowNotFound('idSchedGroup=%s not found (tsNow=%s, sPeriodBack=%s)' % (idSchedGroup, tsNow, sPeriodBack)); + return self.initFromDbRow(aoRow); + + +class SchedGroupDataEx(SchedGroupData): + """ + Extended scheduling group data. + + Note! Similar to TestGroupDataEx. + """ + + ksParam_aoMembers = 'SchedGroup_aoMembers'; + ksParam_aoTestBoxes = 'SchedGroup_aoTestboxes'; + kasAltArrayNull = [ 'aoMembers', 'aoTestboxes' ]; + + ## Helper parameter containing the comma separated list with the IDs of + # potential members found in the parameters. + ksParam_aidTestGroups = 'TestGroupDataEx_aidTestGroups'; + ## Ditto for testbox meembers. + ksParam_aidTestBoxes = 'TestGroupDataEx_aidTestBoxes'; + + + def __init__(self): + SchedGroupData.__init__(self); + self.aoMembers = [] # type: list[SchedGroupMemberDataEx] + self.aoTestBoxes = [] # type: list[TestBoxDataForSchedGroup] + + # The two build sources for the sake of convenience. + self.oBuildSrc = None # type: BuildSourceData + self.oBuildSrcValidationKit = None # type: BuildSourceData + + def _initExtraMembersFromDb(self, oDb, tsNow = None, sPeriodBack = None): + """ + Worker shared by the initFromDb* methods. + Returns self. Raises exception if no row or database error. + """ + # + # Clear all members upfront so the object has some kind of consistency + # if anything below raises exceptions. + # + self.oBuildSrc = None; + self.oBuildSrcValidationKit = None; + self.aoTestBoxes = []; + self.aoMembers = []; + + # + # Build source. + # + if self.idBuildSrc: + self.oBuildSrc = BuildSourceData().initFromDbWithId(oDb, self.idBuildSrc, tsNow, sPeriodBack); + + if self.idBuildSrcTestSuite: + self.oBuildSrcValidationKit = BuildSourceData().initFromDbWithId(oDb, self.idBuildSrcTestSuite, + tsNow, sPeriodBack); + + # + # Test Boxes. + # + self.aoTestBoxes = TestBoxLogic(oDb).fetchForSchedGroup(self.idSchedGroup, tsNow); + + # + # Test groups. + # The fetchForChangeLog method makes ASSUMPTIONS about sorting! + # + oDb.execute('SELECT SchedGroupMembers.*, TestGroups.*\n' + 'FROM SchedGroupMembers\n' + 'LEFT OUTER JOIN TestGroups ON (SchedGroupMembers.idTestGroup = TestGroups.idTestGroup)\n' + 'WHERE SchedGroupMembers.idSchedGroup = %s\n' + + self.formatSimpleNowAndPeriod(oDb, tsNow, sPeriodBack, sTablePrefix = 'SchedGroupMembers.') + + self.formatSimpleNowAndPeriod(oDb, tsNow, sPeriodBack, sTablePrefix = 'TestGroups.') + + 'ORDER BY SchedGroupMembers.idTestGroupPreReq ASC NULLS FIRST,\n' + ' TestGroups.sName,\n' + ' SchedGroupMembers.idTestGroup\n' + , (self.idSchedGroup,)); + for aoRow in oDb.fetchAll(): + self.aoMembers.append(SchedGroupMemberDataEx().initFromDbRow(aoRow)); + return self; + + def initFromDbRowEx(self, aoRow, oDb, tsNow = None): + """ + Reinitialize from a SELECT * FROM SchedGroups row. Will query the + necessary additional data from oDb using tsNow. + Returns self. Raises exception if no row or database error. + """ + SchedGroupData.initFromDbRow(self, aoRow); + return self._initExtraMembersFromDb(oDb, tsNow); + + def initFromDbWithId(self, oDb, idSchedGroup, tsNow = None, sPeriodBack = None): + """ + Initialize the object from the database. + """ + SchedGroupData.initFromDbWithId(self, oDb, idSchedGroup, tsNow, sPeriodBack); + return self._initExtraMembersFromDb(oDb, tsNow, sPeriodBack); + + def getDataAttributes(self): + asAttributes = SchedGroupData.getDataAttributes(self); + asAttributes.remove('oBuildSrc'); + asAttributes.remove('oBuildSrcValidationKit'); + return asAttributes; + + def getAttributeParamNullValues(self, sAttr): + if sAttr not in [ 'aoMembers', 'aoTestBoxes' ]: + return SchedGroupData.getAttributeParamNullValues(self, sAttr); + return ['', [], None]; + + def convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict): + aoNewValue = []; + if sAttr == 'aoMembers': + aidSelected = oDisp.getListOfIntParams(sParam, iMin = 1, iMax = 0x7ffffffe, aiDefaults = []) + sIds = oDisp.getStringParam(self.ksParam_aidTestGroups, sDefault = ''); + for idTestGroup in sIds.split(','): + try: idTestGroup = int(idTestGroup); + except: pass; + oDispWrapper = self.DispWrapper(oDisp, '%s[%s][%%s]' % (SchedGroupDataEx.ksParam_aoMembers, idTestGroup,)) + oMember = SchedGroupMemberDataEx().initFromParams(oDispWrapper, fStrict = False); + if idTestGroup in aidSelected: + oMember.idTestGroup = idTestGroup; + aoNewValue.append(oMember); + elif sAttr == 'aoTestBoxes': + aidSelected = oDisp.getListOfIntParams(sParam, iMin = 1, iMax = 0x7ffffffe, aiDefaults = []) + sIds = oDisp.getStringParam(self.ksParam_aidTestBoxes, sDefault = ''); + for idTestBox in sIds.split(','): + try: idTestBox = int(idTestBox); + except: pass; + oDispWrapper = self.DispWrapper(oDisp, '%s[%s][%%s]' % (SchedGroupDataEx.ksParam_aoTestBoxes, idTestBox,)) + oBoxInGrp = TestBoxDataForSchedGroup().initFromParams(oDispWrapper, fStrict = False); + if idTestBox in aidSelected: + oBoxInGrp.idTestBox = idTestBox; + aoNewValue.append(oBoxInGrp); + else: + return SchedGroupData.convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict); + return aoNewValue; + + def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb): + if sAttr not in [ 'aoMembers', 'aoTestBoxes' ]: + return SchedGroupData._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb); + + if oValue in aoNilValues: + return ([], None); + + asErrors = []; + aoNewMembers = []; + if sAttr == 'aoMembers': + asAllowNulls = ['bmHourlySchedule', 'idTestGroupPreReq', 'tsEffective', 'tsExpire', 'uidAuthor', ]; + if self.idSchedGroup in [None, '-1', -1]: + asAllowNulls.append('idSchedGroup'); # Probably new group, so allow null scheduling group. + + for oOldMember in oValue: + oNewMember = SchedGroupMemberDataEx().initFromOther(oOldMember); + aoNewMembers.append(oNewMember); + + dErrors = oNewMember.validateAndConvertEx(asAllowNulls, oDb, ModelDataBase.ksValidateFor_Other); + if dErrors: + asErrors.append(str(dErrors)); + + if not asErrors: + for i, _ in enumerate(aoNewMembers): + idTestGroup = aoNewMembers[i]; + for j in range(i + 1, len(aoNewMembers)): + if aoNewMembers[j].idTestGroup == idTestGroup: + asErrors.append('Duplicate test group #%d!' % (idTestGroup, )); + break; + else: + asAllowNulls = list(TestBoxDataForSchedGroup.kasAllowNullAttributes); + if self.idSchedGroup in [None, '-1', -1]: + asAllowNulls.append('idSchedGroup'); # Probably new group, so allow null scheduling group. + + for oOldMember in oValue: + oNewMember = TestBoxDataForSchedGroup().initFromOther(oOldMember); + aoNewMembers.append(oNewMember); + + dErrors = oNewMember.validateAndConvertEx(asAllowNulls, oDb, ModelDataBase.ksValidateFor_Other); + if dErrors: + asErrors.append(str(dErrors)); + + if not asErrors: + for i, _ in enumerate(aoNewMembers): + idTestBox = aoNewMembers[i]; + for j in range(i + 1, len(aoNewMembers)): + if aoNewMembers[j].idTestBox == idTestBox: + asErrors.append('Duplicate test box #%d!' % (idTestBox, )); + break; + + return (aoNewMembers, None if not asErrors else '<br>\n'.join(asErrors)); + + def _validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor = ModelDataBase.ksValidateFor_Other): + dErrors = SchedGroupData._validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor); + + # + # Fetch the extended build source bits. + # + if self.ksParam_idBuildSrc not in dErrors: + if self.idBuildSrc in self.getAttributeParamNullValues('idBuildSrc') \ + or self.idBuildSrc is None: + self.oBuildSrc = None; + else: + try: + self.oBuildSrc = BuildSourceData().initFromDbWithId(oDb, self.idBuildSrc); + except Exception as oXcpt: + self.oBuildSrc = BuildSourceData(); + dErrors[self.ksParam_idBuildSrc] = str(oXcpt); + + if self.ksParam_idBuildSrcTestSuite not in dErrors: + if self.idBuildSrcTestSuite in self.getAttributeParamNullValues('idBuildSrcTestSuite') \ + or self.idBuildSrcTestSuite is None: + self.oBuildSrcValidationKit = None; + else: + try: + self.oBuildSrcValidationKit = BuildSourceData().initFromDbWithId(oDb, self.idBuildSrcTestSuite); + except Exception as oXcpt: + self.oBuildSrcValidationKit = BuildSourceData(); + dErrors[self.ksParam_idBuildSrcTestSuite] = str(oXcpt); + + return dErrors; + + + +class SchedGroupLogic(ModelLogicBase): # pylint: disable=too-few-public-methods + """ + SchedGroup logic. + """ + + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb); + self.dCache = None; + + # + # Standard methods. + # + + def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None): + """ + Fetches build sources. + + Returns an array (list) of BuildSourceData items, empty list if none. + Raises exception on error. + """ + _ = aiSortColumns; + + if tsNow is None: + self._oDb.execute('SELECT *\n' + 'FROM SchedGroups\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY fEnabled DESC, sName DESC\n' + 'LIMIT %s OFFSET %s\n' + , (cMaxRows, iStart,)); + else: + self._oDb.execute('SELECT *\n' + 'FROM SchedGroups\n' + 'WHERE tsExpire > %s\n' + ' AND tsEffective <= %s\n' + 'ORDER BY fEnabled DESC, sName DESC\n' + 'LIMIT %s OFFSET %s\n' + , (tsNow, tsNow, cMaxRows, iStart,)); + + aoRet = []; + for aoRow in self._oDb.fetchAll(): + aoRet.append(SchedGroupDataEx().initFromDbRowEx(aoRow, self._oDb, tsNow)); + return aoRet; + + def fetchForChangeLog(self, idSchedGroup, iStart, cMaxRows, tsNow): # pylint: disable=too-many-locals,too-many-statements + """ + Fetches change log entries for a scheduling group. + + Returns an array of ChangeLogEntry instance and an indicator whether + there are more entries. + Raises exception on error. + """ + + ## @todo calc changes to scheduler group! + + if tsNow is None: + tsNow = self._oDb.getCurrentTimestamp(); + + # + # First gather the change log timeline using the effective dates. + # (ASSUMES that we'll always have a separate delete entry, rather + # than just setting tsExpire.) + # + self._oDb.execute(''' +( +SELECT tsEffective, + uidAuthor +FROM SchedGroups +WHERE idSchedGroup = %s + AND tsEffective <= %s +ORDER BY tsEffective DESC +) UNION ( +SELECT CASE WHEN tsEffective + %s::INTERVAL = tsExpire THEN tsExpire ELSE tsEffective END, + uidAuthor +FROM SchedGroupMembers +WHERE idSchedGroup = %s + AND tsEffective <= %s +ORDER BY tsEffective DESC +) UNION ( +SELECT CASE WHEN tsEffective + %s::INTERVAL = tsExpire THEN tsExpire ELSE tsEffective END, + uidAuthor +FROM TestBoxesInSchedGroups +WHERE idSchedGroup = %s + AND tsEffective <= %s +ORDER BY tsEffective DESC +) +ORDER BY tsEffective DESC +LIMIT %s OFFSET %s +''', (idSchedGroup, tsNow, + db.dbOneTickIntervalString(), idSchedGroup, tsNow, + db.dbOneTickIntervalString(), idSchedGroup, tsNow, + cMaxRows + 1, iStart, )); + + aoEntries = [] # type: list[ChangeLogEntry] + tsPrevious = tsNow; + for aoDbRow in self._oDb.fetchAll(): + (tsEffective, uidAuthor) = aoDbRow; + aoEntries.append(ChangeLogEntry(uidAuthor, None, tsEffective, tsPrevious, None, None, [])); + tsPrevious = db.dbTimestampPlusOneTick(tsEffective); + + if True: # pylint: disable=using-constant-test + # + # Fetch data for each for each change log entry point. + # + # We add one tick to the timestamp here to skip past delete records + # that only there to record the user doing the deletion. + # + for iEntry, oEntry in enumerate(aoEntries): + oEntry.oNewRaw = SchedGroupDataEx().initFromDbWithId(self._oDb, idSchedGroup, oEntry.tsEffective); + if iEntry > 0: + aoEntries[iEntry - 1].oOldRaw = oEntry.oNewRaw; + + # Chop off the +1 entry, if any. + fMore = len(aoEntries) > cMaxRows; + if fMore: + aoEntries = aoEntries[:-1]; + + # Figure out the changes. + for oEntry in aoEntries: + oOld = oEntry.oOldRaw; + if not oOld: + break; + oNew = oEntry.oNewRaw; + aoChanges = oEntry.aoChanges; + for sAttr in oNew.getDataAttributes(): + if sAttr in [ 'tsEffective', 'tsExpire', 'uidAuthor', ]: + continue; + oOldAttr = getattr(oOld, sAttr); + oNewAttr = getattr(oNew, sAttr); + if oOldAttr == oNewAttr: + continue; + if sAttr in [ 'aoMembers', 'aoTestBoxes', ]: + iNew = 0; + iOld = 0; + asNewAttr = []; + asOldAttr = []; + if sAttr == 'aoMembers': + # ASSUMES aoMembers is sorted by idTestGroupPreReq (nulls first), oTestGroup.sName, idTestGroup! + while iNew < len(oNewAttr) and iOld < len(oOldAttr): + if oNewAttr[iNew].idTestGroup == oOldAttr[iOld].idTestGroup: + if oNewAttr[iNew].idTestGroupPreReq != oOldAttr[iOld].idTestGroupPreReq: + if oNewAttr[iNew].idTestGroupPreReq is None: + asOldAttr.append('Dropped test group #%s (%s) dependency on #%s' + % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName, + oOldAttr[iOld].idTestGroupPreReq)); + elif oOldAttr[iOld].idTestGroupPreReq is None: + asNewAttr.append('Added test group #%s (%s) dependency on #%s' + % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName, + oNewAttr[iOld].idTestGroupPreReq)); + else: + asNewAttr.append('Test group #%s (%s) dependency on #%s' + % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName, + oNewAttr[iNew].idTestGroupPreReq)); + asOldAttr.append('Test group #%s (%s) dependency on #%s' + % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName, + oOldAttr[iOld].idTestGroupPreReq)); + if oNewAttr[iNew].iSchedPriority != oOldAttr[iOld].iSchedPriority: + asNewAttr.append('Test group #%s (%s) priority %s' + % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName, + oNewAttr[iNew].iSchedPriority)); + asOldAttr.append('Test group #%s (%s) priority %s' + % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName, + oOldAttr[iOld].iSchedPriority)); + iNew += 1; + iOld += 1; + elif oNewAttr[iNew].oTestGroup.sName < oOldAttr[iOld].oTestGroup.sName \ + or ( oNewAttr[iNew].oTestGroup.sName == oOldAttr[iOld].oTestGroup.sName + and oNewAttr[iNew].idTestGroup < oOldAttr[iOld].idTestGroup): + asNewAttr.append('New test group #%s - %s' + % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName)); + iNew += 1; + else: + asOldAttr.append('Removed test group #%s - %s' + % (oOldAttr[iOld].idTestGroup, oOldAttr[iOld].oTestGroup.sName)); + iOld += 1; + while iNew < len(oNewAttr): + asNewAttr.append('New test group #%s - %s' + % (oNewAttr[iNew].idTestGroup, oNewAttr[iNew].oTestGroup.sName)); + iNew += 1; + while iOld < len(oOldAttr): + asOldAttr.append('Removed test group #%s - %s' + % (oOldAttr[iOld].idTestGroup, oOldAttr[iOld].oTestGroup.sName)); + iOld += 1; + else: + dNewIds = { oBoxInGrp.idTestBox: oBoxInGrp for oBoxInGrp in oNewAttr }; + dOldIds = { oBoxInGrp.idTestBox: oBoxInGrp for oBoxInGrp in oOldAttr }; + hCommonIds = set(dNewIds.keys()) & set(dOldIds.keys()); + for idTestBox in hCommonIds: + oNewBoxInGrp = dNewIds[idTestBox]; + oOldBoxInGrp = dOldIds[idTestBox]; + if oNewBoxInGrp.iSchedPriority != oOldBoxInGrp.iSchedPriority: + asNewAttr.append('Test box \'%s\' (#%s) priority %s' + % (getattr(oNewBoxInGrp.oTestBox, 'sName', '[Partial DB]'), + oNewBoxInGrp.idTestBox, oNewBoxInGrp.iSchedPriority)); + asOldAttr.append('Test box \'%s\' (#%s) priority %s' + % (getattr(oOldBoxInGrp.oTestBox, 'sName', '[Partial DB]'), + oOldBoxInGrp.idTestBox, oOldBoxInGrp.iSchedPriority)); + asNewAttr = sorted(asNewAttr); + asOldAttr = sorted(asOldAttr); + for idTestBox in set(dNewIds.keys()) - hCommonIds: + oNewBoxInGrp = dNewIds[idTestBox]; + asNewAttr.append('New test box \'%s\' (#%s) priority %s' + % (getattr(oNewBoxInGrp.oTestBox, 'sName', '[Partial DB]'), + oNewBoxInGrp.idTestBox, oNewBoxInGrp.iSchedPriority)); + for idTestBox in set(dOldIds.keys()) - hCommonIds: + oOldBoxInGrp = dOldIds[idTestBox]; + asOldAttr.append('Removed test box \'%s\' (#%s) priority %s' + % (getattr(oOldBoxInGrp.oTestBox, 'sName', '[Partial DB]'), + oOldBoxInGrp.idTestBox, oOldBoxInGrp.iSchedPriority)); + + if asNewAttr or asOldAttr: + aoChanges.append(AttributeChangeEntryPre(sAttr, oNewAttr, oOldAttr, + '\n'.join(asNewAttr), '\n'.join(asOldAttr))); + else: + aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr))); + + else: + ## + ## @todo Incomplete: A more complicate apporach, probably faster though. + ## + def findEntry(tsEffective, iPrev = 0): + """ Find entry with matching effective + expiration time """ + self._oDb.dprint('findEntry: iPrev=%s len(aoEntries)=%s tsEffective=%s' % (iPrev, len(aoEntries), tsEffective)); + while iPrev < len(aoEntries): + self._oDb.dprint('%s iPrev=%u' % (aoEntries[iPrev].tsEffective, iPrev, )); + if aoEntries[iPrev].tsEffective > tsEffective: + iPrev += 1; + elif aoEntries[iPrev].tsEffective == tsEffective: + self._oDb.dprint('hit %u' % (iPrev,)); + return iPrev; + else: + break; + self._oDb.dprint('%s not found!' % (tsEffective,)); + return -1; + + fMore = True; + + # + # Track scheduling group changes. Not terribly efficient for large cMaxRows + # values, but not in the mood for figure out if there is any way to optimize that. + # + self._oDb.execute(''' +SELECT * +FROM SchedGroups +WHERE idSchedGroup = %s + AND tsEffective <= %s +ORDER BY tsEffective DESC +LIMIT %s''', (idSchedGroup, aoEntries[0].tsEffective, cMaxRows + 1,)); + + iEntry = 0; + aaoRows = self._oDb.fetchAll(); + for iRow, oRow in enumerate(aaoRows): + oNew = SchedGroupDataEx().initFromDbRow(oRow); + iEntry = findEntry(oNew.tsEffective, iEntry); + self._oDb.dprint('iRow=%s iEntry=%s' % (iRow, iEntry)); + if iEntry < 0: + break; + oEntry = aoEntries[iEntry]; + aoChanges = oEntry.aoChanges; + oEntry.oNewRaw = oNew; + if iRow + 1 < len(aaoRows): + oOld = SchedGroupDataEx().initFromDbRow(aaoRows[iRow + 1]); + self._oDb.dprint('oOld=%s' % (oOld,)); + for sAttr in oNew.getDataAttributes(): + if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', ]: + oOldAttr = getattr(oOld, sAttr); + oNewAttr = getattr(oNew, sAttr); + if oOldAttr != oNewAttr: + aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr))); + else: + self._oDb.dprint('New'); + + # + # ... + # + + # FInally + UserAccountLogic(self._oDb).resolveChangeLogAuthors(aoEntries); + return (aoEntries, fMore); + + + def addEntry(self, oData, uidAuthor, fCommit = False): + """Add Scheduling Group record""" + + # + # Validate. + # + dDataErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Add); + if dDataErrors: + raise TMInvalidData('Invalid data passed to addEntry: %s' % (dDataErrors,)); + if self.exists(oData.sName): + raise TMRowAlreadyExists('Scheduling group "%s" already exists.' % (oData.sName,)); + + # + # Add it. + # + self._oDb.execute('INSERT INTO SchedGroups (\n' + ' uidAuthor,\n' + ' sName,\n' + ' sDescription,\n' + ' fEnabled,\n' + ' enmScheduler,\n' + ' idBuildSrc,\n' + ' idBuildSrcTestSuite,\n' + ' sComment)\n' + 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s)\n' + 'RETURNING idSchedGroup\n' + , ( uidAuthor, + oData.sName, + oData.sDescription, + oData.fEnabled, + oData.enmScheduler, + oData.idBuildSrc, + oData.idBuildSrcTestSuite, + oData.sComment )); + idSchedGroup = self._oDb.fetchOne()[0]; + oData.idSchedGroup = idSchedGroup; + + for oBoxInGrp in oData.aoTestBoxes: + oBoxInGrp.idSchedGroup = idSchedGroup; + self._addSchedGroupTestBox(uidAuthor, oBoxInGrp); + + for oMember in oData.aoMembers: + oMember.idSchedGroup = idSchedGroup; + self._addSchedGroupMember(uidAuthor, oMember); + + self._oDb.maybeCommit(fCommit); + return True; + + def editEntry(self, oData, uidAuthor, fCommit = False): + """Edit Scheduling Group record""" + + # + # Validate input and retrieve the old data. + # + dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit); + if dErrors: + raise TMInvalidData('editEntry got invalid data: %s' % (dErrors,)); + self._assertUnique(oData.sName, oData.idSchedGroup); + oOldData = SchedGroupDataEx().initFromDbWithId(self._oDb, oData.idSchedGroup); + + # + # Make the changes. + # + if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor', 'aoMembers', 'aoTestBoxes', + 'oBuildSrc', 'oBuildSrcValidationKit', ]): + self._historizeEntry(oData.idSchedGroup); + self._readdEntry(uidAuthor, oData); + + # Remove groups. + for oOld in oOldData.aoMembers: + fRemove = True; + for oNew in oData.aoMembers: + if oNew.idTestGroup == oOld.idTestGroup: + fRemove = False; + break; + if fRemove: + self._removeSchedGroupMember(uidAuthor, oOld); + + # Add / modify groups. + for oMember in oData.aoMembers: + oOldMember = None; + for oOld in oOldData.aoMembers: + if oOld.idTestGroup == oMember.idTestGroup: + oOldMember = oOld; + break; + + oMember.idSchedGroup = oData.idSchedGroup; + if oOldMember is None: + self._addSchedGroupMember(uidAuthor, oMember); + elif not oMember.isEqualEx(oOldMember, ['tsEffective', 'tsExpire', 'uidAuthor', 'oTestGroup']): + self._historizeSchedGroupMember(oMember); + self._addSchedGroupMember(uidAuthor, oMember); + + # Remove testboxes. + for oOld in oOldData.aoTestBoxes: + fRemove = True; + for oNew in oData.aoTestBoxes: + if oNew.idTestBox == oOld.idTestBox: + fRemove = False; + break; + if fRemove: + self._removeSchedGroupTestBox(uidAuthor, oOld); + + # Add / modify testboxes. + for oBoxInGrp in oData.aoTestBoxes: + oOldBoxInGrp = None; + for oOld in oOldData.aoTestBoxes: + if oOld.idTestBox == oBoxInGrp.idTestBox: + oOldBoxInGrp = oOld; + break; + + oBoxInGrp.idSchedGroup = oData.idSchedGroup; + if oOldBoxInGrp is None: + self._addSchedGroupTestBox(uidAuthor, oBoxInGrp); + elif not oBoxInGrp.isEqualEx(oOldBoxInGrp, ['tsEffective', 'tsExpire', 'uidAuthor', 'oTestBox']): + self._historizeSchedGroupTestBox(oBoxInGrp); + self._addSchedGroupTestBox(uidAuthor, oBoxInGrp); + + self._oDb.maybeCommit(fCommit); + return True; + + def removeEntry(self, uidAuthor, idSchedGroup, fCascade = False, fCommit = False): + """ + Deletes a scheduling group. + """ + _ = fCascade; + + # + # Input validation and retrival of current data. + # + if idSchedGroup == 1: + raise TMRowInUse('Cannot remove the default scheduling group (id 1).'); + oData = SchedGroupDataEx().initFromDbWithId(self._oDb, idSchedGroup); + + # + # Remove the test box member records. + # + for oBoxInGrp in oData.aoTestBoxes: + self._removeSchedGroupTestBox(uidAuthor, oBoxInGrp); + self._oDb.execute('UPDATE TestBoxesInSchedGroups\n' + 'SET tsExpire = CURRENT_TIMESTAMP\n' + 'WHERE idSchedGroup = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idSchedGroup,)); + + # + # Remove the test group member records. + # + for oMember in oData.aoMembers: + self._removeSchedGroupMember(uidAuthor, oMember); + self._oDb.execute('UPDATE SchedGroupMembers\n' + 'SET tsExpire = CURRENT_TIMESTAMP\n' + 'WHERE idSchedGroup = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idSchedGroup,)); + + # + # Now the SchedGroups entry. + # + (tsCur, tsCurMinusOne) = self._oDb.getCurrentTimestamps(); + if oData.tsEffective not in (tsCur, tsCurMinusOne): + self._historizeEntry(idSchedGroup, tsCurMinusOne); + self._readdEntry(uidAuthor, oData, tsCurMinusOne); + self._historizeEntry(idSchedGroup); + self._oDb.execute('UPDATE SchedGroups\n' + 'SET tsExpire = CURRENT_TIMESTAMP\n' + 'WHERE idSchedGroup = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idSchedGroup,)) + + self._oDb.maybeCommit(fCommit) + return True; + + + def cachedLookup(self, idSchedGroup): + """ + Looks up the most recent SchedGroupData object for idSchedGroup + via an object cache. + + Returns a shared SchedGroupData object. None if not found. + Raises exception on DB error. + """ + if self.dCache is None: + self.dCache = self._oDb.getCache('SchedGroup'); + + oEntry = self.dCache.get(idSchedGroup, None); + if oEntry is None: + self._oDb.execute('SELECT *\n' + 'FROM SchedGroups\n' + 'WHERE idSchedGroup = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idSchedGroup, )); + if self._oDb.getRowCount() == 0: + # Maybe it was deleted, try get the last entry. + self._oDb.execute('SELECT *\n' + 'FROM SchedGroups\n' + 'WHERE idSchedGroup = %s\n' + 'ORDER BY tsExpire DESC\n' + 'LIMIT 1\n' + , (idSchedGroup, )); + elif self._oDb.getRowCount() > 1: + raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idSchedGroup)); + + if self._oDb.getRowCount() == 1: + oEntry = SchedGroupData().initFromDbRow(self._oDb.fetchOne()); + self.dCache[idSchedGroup] = oEntry; + return oEntry; + + + # + # Other methods. + # + + def fetchOrderedByName(self, tsNow = None): + """ + Return list of objects of type SchedGroups ordered by name. + May raise exception on database error. + """ + if tsNow is None: + self._oDb.execute('SELECT *\n' + 'FROM SchedGroups\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY sName ASC\n'); + else: + self._oDb.execute('SELECT *\n' + 'FROM SchedGroups\n' + 'WHERE tsExpire > %s\n' + ' AND tsEffective <= %s\n' + 'ORDER BY sName ASC\n' + , (tsNow, tsNow,)); + aoRet = [] + for _ in range(self._oDb.getRowCount()): + aoRet.append(SchedGroupData().initFromDbRow(self._oDb.fetchOne())); + return aoRet; + + + def getAll(self, tsEffective = None): + """ + Gets the list of all scheduling groups. + Returns an array of SchedGroupData instances. + """ + if tsEffective is None: + self._oDb.execute('SELECT *\n' + 'FROM SchedGroups\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n'); + else: + self._oDb.execute('SELECT *\n' + 'FROM SchedGroups\n' + 'WHERE tsExpire > %s\n' + ' AND tsEffective <= %s\n' + , (tsEffective, tsEffective)); + aoRet = []; + for aoRow in self._oDb.fetchAll(): + aoRet.append(SchedGroupData().initFromDbRow(aoRow)); + return aoRet; + + def getSchedGroupsForCombo(self, tsEffective = None): + """ + Gets the list of active scheduling groups for a combo box. + Returns an array of (value [idSchedGroup], drop-down-name [sName], + hover-text [sDescription]) tuples. + """ + if tsEffective is None: + self._oDb.execute('SELECT idSchedGroup, sName, sDescription\n' + 'FROM SchedGroups\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY sName'); + else: + self._oDb.execute('SELECT idSchedGroup, sName, sDescription\n' + 'FROM SchedGroups\n' + 'WHERE tsExpire > %s\n' + ' AND tsEffective <= %s\n' + 'ORDER BY sName' + , (tsEffective, tsEffective)); + return self._oDb.fetchAll(); + + + def getMembers(self, idSchedGroup, tsEffective = None): + """ + Gets the scheduling groups members for the given scheduling group. + + Returns an array of SchedGroupMemberDataEx instances (sorted by + priority (descending) and idTestGroup). May raise exception DB error. + """ + + if tsEffective is None: + self._oDb.execute('SELECT *\n' + 'FROM SchedGroupMembers, TestGroups\n' + 'WHERE SchedGroupMembers.idSchedGroup = %s\n' + ' AND SchedGroupMembers.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND TestGroups.idTestGroup = SchedGroupMembers.idTestGroup\n' + ' AND TestGroups.tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY SchedGroupMembers.iSchedPriority DESC, SchedGroupMembers.idTestGroup\n' + , (idSchedGroup,)); + else: + self._oDb.execute('SELECT *\n' + 'FROM SchedGroupMembers, TestGroups\n' + 'WHERE SchedGroupMembers.idSchedGroup = %s\n' + ' AND SchedGroupMembers.tsExpire < %s\n' + ' AND SchedGroupMembers.tsEffective >= %s\n' + ' AND TestGroups.idTestGroup = SchedGroupMembers.idTestGroup\n' + ' AND TestGroups.tsExpire < %s\n' + ' AND TestGroups.tsEffective >= %s\n' + 'ORDER BY SchedGroupMembers.iSchedPriority DESC, SchedGroupMembers.idTestGroup\n' + , (idSchedGroup, tsEffective, tsEffective, tsEffective, tsEffective, )); + aaoRows = self._oDb.fetchAll(); + aoRet = []; + for aoRow in aaoRows: + aoRet.append(SchedGroupMemberDataEx().initFromDbRow(aoRow)); + return aoRet; + + def getTestCasesForGroup(self, idSchedGroup, cMax = None): + """ + Gets the enabled testcases w/ testgroup+priority for the given scheduling group. + + Returns an array of TestCaseData instances (ordered by group id, descending + testcase priority, and testcase IDs) with an extra iSchedPriority member. + May raise exception on DB error or if the result exceeds cMax. + """ + + self._oDb.execute('SELECT TestGroupMembers.idTestGroup, TestGroupMembers.iSchedPriority, TestCases.*\n' + 'FROM SchedGroupMembers, TestGroups, TestGroupMembers, TestCases\n' + 'WHERE SchedGroupMembers.idSchedGroup = %s\n' + ' AND SchedGroupMembers.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND TestGroups.idTestGroup = SchedGroupMembers.idTestGroup\n' + ' AND TestGroups.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND TestGroupMembers.idTestGroup = TestGroups.idTestGroup\n' + ' AND TestGroupMembers.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND TestCases.idTestCase = TestGroupMembers.idTestCase\n' + ' AND TestCases.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND TestCases.fEnabled = TRUE\n' + 'ORDER BY TestGroupMembers.idTestGroup, TestGroupMembers.iSchedPriority DESC, TestCases.idTestCase\n' + , (idSchedGroup,)); + + if cMax is not None and self._oDb.getRowCount() > cMax: + raise TMExceptionBase('Too many testcases for scheduling group %s: %s, max %s' + % (idSchedGroup, cMax, self._oDb.getRowCount(),)); + + aoRet = []; + for aoRow in self._oDb.fetchAll(): + oTestCase = TestCaseData().initFromDbRow(aoRow[2:]); + oTestCase.idTestGroup = aoRow[0]; + oTestCase.iSchedPriority = aoRow[1]; + aoRet.append(oTestCase); + return aoRet; + + def getTestCaseArgsForGroup(self, idSchedGroup, cMax = None): + """ + Gets the testcase argument variation w/ testgroup+priority for the given scheduling group. + + Returns an array TestCaseArgsData instance (sorted by group and + variation id) with an extra iSchedPriority member. + May raise exception on DB error or if the result exceeds cMax. + """ + + self._oDb.execute('SELECT TestGroupMembers.idTestGroup, TestGroupMembers.iSchedPriority, TestCaseArgs.*\n' + 'FROM SchedGroupMembers, TestGroups, TestGroupMembers, TestCaseArgs, TestCases\n' + 'WHERE SchedGroupMembers.idSchedGroup = %s\n' + ' AND SchedGroupMembers.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND TestGroups.idTestGroup = SchedGroupMembers.idTestGroup\n' + ' AND TestGroups.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND TestGroupMembers.idTestGroup = TestGroups.idTestGroup\n' + ' AND TestGroupMembers.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND TestCaseArgs.idTestCase = TestGroupMembers.idTestCase\n' + ' AND TestCaseArgs.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND ( TestGroupMembers.aidTestCaseArgs is NULL\n' + ' OR TestCaseArgs.idTestCaseArgs = ANY(TestGroupMembers.aidTestCaseArgs) )\n' + ' AND TestCases.idTestCase = TestCaseArgs.idTestCase\n' + ' AND TestCases.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND TestCases.fEnabled = TRUE\n' + 'ORDER BY TestGroupMembers.idTestGroup, TestGroupMembers.idTestCase, TestCaseArgs.idTestCaseArgs\n' + , (idSchedGroup,)); + + if cMax is not None and self._oDb.getRowCount() > cMax: + raise TMExceptionBase('Too many argument variations for scheduling group %s: %s, max %s' + % (idSchedGroup, cMax, self._oDb.getRowCount(),)); + + aoRet = []; + for aoRow in self._oDb.fetchAll(): + oVariation = TestCaseArgsData().initFromDbRow(aoRow[2:]); + oVariation.idTestGroup = aoRow[0]; + oVariation.iSchedPriority = aoRow[1]; + aoRet.append(oVariation); + return aoRet; + + def exists(self, sName): + """Checks if a group with the given name exists.""" + self._oDb.execute('SELECT idSchedGroup\n' + 'FROM SchedGroups\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND sName = %s\n' + 'LIMIT 1\n' + , (sName,)); + return self._oDb.getRowCount() > 0; + + def getById(self, idSchedGroup): + """Get Scheduling Group data by idSchedGroup""" + self._oDb.execute('SELECT *\n' + 'FROM SchedGroups\n' + 'WHERE tsExpire = \'infinity\'::timestamp\n' + ' AND idSchedGroup = %s;', (idSchedGroup,)) + aRows = self._oDb.fetchAll() + if len(aRows) not in (0, 1): + raise self._oDb.integrityException( + 'Found more than one scheduling groups with the same credentials. Database structure is corrupted.') + try: + return SchedGroupData().initFromDbRow(aRows[0]) + except IndexError: + return None + + + # + # Internal helpers. + # + + def _assertUnique(self, sName, idSchedGroupIgnore = None): + """ + Checks that the scheduling group name is unique. + Raises exception if the name is already in use. + """ + if idSchedGroupIgnore is None: + self._oDb.execute('SELECT idSchedGroup\n' + 'FROM SchedGroups\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND sName = %s\n' + , ( sName, ) ); + else: + self._oDb.execute('SELECT idSchedGroup\n' + 'FROM SchedGroups\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND sName = %s\n' + ' AND idSchedGroup <> %s\n' + , ( sName, idSchedGroupIgnore, ) ); + if self._oDb.getRowCount() > 0: + raise TMRowInUse('Scheduling group name (%s) is already in use.' % (sName,)); + return True; + + def _readdEntry(self, uidAuthor, oData, tsEffective = None): + """ + Re-adds the SchedGroups entry. Used by editEntry and removeEntry. + """ + if tsEffective is None: + tsEffective = self._oDb.getCurrentTimestamp(); + self._oDb.execute('INSERT INTO SchedGroups (\n' + ' uidAuthor,\n' + ' tsEffective,\n' + ' idSchedGroup,\n' + ' sName,\n' + ' sDescription,\n' + ' fEnabled,\n' + ' enmScheduler,\n' + ' idBuildSrc,\n' + ' idBuildSrcTestSuite,\n' + ' sComment )\n' + 'VALUES ( %s, %s, %s, %s, %s, %s, %s, %s, %s, %s )\n' + , ( uidAuthor, + tsEffective, + oData.idSchedGroup, + oData.sName, + oData.sDescription, + oData.fEnabled, + oData.enmScheduler, + oData.idBuildSrc, + oData.idBuildSrcTestSuite, + oData.sComment, )); + return True; + + def _historizeEntry(self, idSchedGroup, tsExpire = None): + """ + Historizes the current entry for the given scheduling group. + """ + if tsExpire is None: + tsExpire = self._oDb.getCurrentTimestamp(); + self._oDb.execute('UPDATE SchedGroups\n' + 'SET tsExpire = %s\n' + 'WHERE idSchedGroup = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , ( tsExpire, idSchedGroup, )); + return True; + + def _addSchedGroupMember(self, uidAuthor, oMember, tsEffective = None): + """ + addEntry worker for adding a scheduling group member. + """ + if tsEffective is None: + tsEffective = self._oDb.getCurrentTimestamp(); + self._oDb.execute('INSERT INTO SchedGroupMembers(\n' + ' idSchedGroup,\n' + ' idTestGroup,\n' + ' tsEffective,\n' + ' uidAuthor,\n' + ' iSchedPriority,\n' + ' bmHourlySchedule,\n' + ' idTestGroupPreReq)\n' + 'VALUES (%s, %s, %s, %s, %s, %s, %s)\n' + , ( oMember.idSchedGroup, + oMember.idTestGroup, + tsEffective, + uidAuthor, + oMember.iSchedPriority, + oMember.bmHourlySchedule, + oMember.idTestGroupPreReq, )); + return True; + + def _removeSchedGroupMember(self, uidAuthor, oMember): + """ + Removes a scheduling group member. + """ + + # Try record who removed it by adding an dummy entry that expires immediately. + (tsCur, tsCurMinusOne) = self._oDb.getCurrentTimestamps(); + if oMember.tsEffective not in (tsCur, tsCurMinusOne): + self._historizeSchedGroupMember(oMember, tsCurMinusOne); + self._addSchedGroupMember(uidAuthor, oMember, tsCurMinusOne); # lazy bird. + self._historizeSchedGroupMember(oMember); + else: + self._historizeSchedGroupMember(oMember); + return True; + + def _historizeSchedGroupMember(self, oMember, tsExpire = None): + """ + Historizes the current entry for the given scheduling group. + """ + if tsExpire is None: + tsExpire = self._oDb.getCurrentTimestamp(); + self._oDb.execute('UPDATE SchedGroupMembers\n' + 'SET tsExpire = %s\n' + 'WHERE idSchedGroup = %s\n' + ' AND idTestGroup = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , ( tsExpire, oMember.idSchedGroup, oMember.idTestGroup, )); + return True; + + # + def _addSchedGroupTestBox(self, uidAuthor, oBoxInGroup, tsEffective = None): + """ + addEntry worker for adding a test box to a scheduling group. + """ + if tsEffective is None: + tsEffective = self._oDb.getCurrentTimestamp(); + self._oDb.execute('INSERT INTO TestBoxesInSchedGroups(\n' + ' idSchedGroup,\n' + ' idTestBox,\n' + ' tsEffective,\n' + ' uidAuthor,\n' + ' iSchedPriority)\n' + 'VALUES (%s, %s, %s, %s, %s)\n' + , ( oBoxInGroup.idSchedGroup, + oBoxInGroup.idTestBox, + tsEffective, + uidAuthor, + oBoxInGroup.iSchedPriority, )); + return True; + + def _removeSchedGroupTestBox(self, uidAuthor, oBoxInGroup): + """ + Removes a testbox from a scheduling group. + """ + + # Try record who removed it by adding an dummy entry that expires immediately. + (tsCur, tsCurMinusOne) = self._oDb.getCurrentTimestamps(); + if oBoxInGroup.tsEffective not in (tsCur, tsCurMinusOne): + self._historizeSchedGroupTestBox(oBoxInGroup, tsCurMinusOne); + self._addSchedGroupTestBox(uidAuthor, oBoxInGroup, tsCurMinusOne); # lazy bird. + self._historizeSchedGroupTestBox(oBoxInGroup); + else: + self._historizeSchedGroupTestBox(oBoxInGroup); + return True; + + def _historizeSchedGroupTestBox(self, oBoxInGroup, tsExpire = None): + """ + Historizes the current entry for the given scheduling group. + """ + if tsExpire is None: + tsExpire = self._oDb.getCurrentTimestamp(); + self._oDb.execute('UPDATE TestBoxesInSchedGroups\n' + 'SET tsExpire = %s\n' + 'WHERE idSchedGroup = %s\n' + ' AND idTestBox = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , ( tsExpire, oBoxInGroup.idSchedGroup, oBoxInGroup.idTestBox, )); + return True; + + + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +class SchedGroupMemberDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [SchedGroupMemberData(),]; + +class SchedGroupDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [SchedGroupData(),]; + +if __name__ == '__main__': + unittest.main(); + # not reached. + diff --git a/src/VBox/ValidationKit/testmanager/core/schedqueue.py b/src/VBox/ValidationKit/testmanager/core/schedqueue.py new file mode 100755 index 00000000..33357106 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/schedqueue.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- +# "$Id: schedqueue.py $" + +""" +Test Manager - Test Case Queue. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +## Standard python imports. +#import unittest + +from testmanager.core.base import ModelDataBase, ModelLogicBase, TMExceptionBase #, ModelDataBaseTestCase + + +class SchedQueueEntry(ModelDataBase): + """ + SchedQueue listing entry + + Note! This could be turned into a SchedQueueDataEx class if we start + fetching all the fields from the scheduing queue. + """ + + def __init__(self): + ModelDataBase.__init__(self) + + self.idItem = None + self.tsLastScheduled = None + self.sSchedGroup = None + self.sTestGroup = None + self.sTestCase = None + self.fUpToDate = None + self.iPerSchedGroupRowNumber = None; + + def initFromDbRow(self, aoRow): + """ + Re-initializes the object from a SchedQueueLogic::fetchForListing select. + Returns self. Raises exception if aoRow is None. + """ + if aoRow is None: + raise TMExceptionBase('TestCaseQueue row not found.') + + self.idItem = aoRow[0] + self.tsLastScheduled = aoRow[1] + self.sSchedGroup = aoRow[2] + self.sTestGroup = aoRow[3] + self.sTestCase = aoRow[4] + self.fUpToDate = aoRow[5] + self.iPerSchedGroupRowNumber = aoRow[6]; + return self + + +class SchedQueueLogic(ModelLogicBase): + """ + SchedQueues logic. + """ + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb) + + def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None): + """ + Fetches SchedQueues entries. + + Returns an array (list) of SchedQueueEntry items, empty list if none. + Raises exception on error. + """ + _, _ = tsNow, aiSortColumns + self._oDb.execute(''' +SELECT SchedQueues.idItem, + SchedQueues.tsLastScheduled, + SchedGroups.sName, + TestGroups.sName, + TestCases.sName, + SchedGroups.tsExpire = 'infinity'::TIMESTAMP + AND TestGroups.tsExpire = 'infinity'::TIMESTAMP + AND TestGroups.tsExpire = 'infinity'::TIMESTAMP + AND TestCaseArgs.tsExpire = 'infinity'::TIMESTAMP + AND TestCases.tsExpire = 'infinity'::TIMESTAMP AS fUpToDate, + ROW_NUMBER() OVER (PARTITION BY SchedQueues.idSchedGroup + ORDER BY SchedQueues.tsLastScheduled, + SchedQueues.idItem) AS iPerSchedGroupRowNumber +FROM SchedQueues + INNER JOIN SchedGroups + ON SchedGroups.idSchedGroup = SchedQueues.idSchedGroup + AND SchedGroups.tsExpire > SchedQueues.tsConfig + AND SchedGroups.tsEffective <= SchedQueues.tsConfig + INNER JOIN TestGroups + ON TestGroups.idTestGroup = SchedQueues.idTestGroup + AND TestGroups.tsExpire > SchedQueues.tsConfig + AND TestGroups.tsEffective <= SchedQueues.tsConfig + INNER JOIN TestCaseArgs + ON TestCaseArgs.idGenTestCaseArgs = SchedQueues.idGenTestCaseArgs + INNER JOIN TestCases + ON TestCases.idTestCase = TestCaseArgs.idTestCase + AND TestCases.tsExpire > SchedQueues.tsConfig + AND TestCases.tsEffective <= SchedQueues.tsConfig +ORDER BY iPerSchedGroupRowNumber, + SchedGroups.sName DESC +LIMIT %s OFFSET %s''' % (cMaxRows, iStart,)) + aoRows = [] + for _ in range(self._oDb.getRowCount()): + aoRows.append(SchedQueueEntry().initFromDbRow(self._oDb.fetchOne())) + return aoRows + +# +# Unit testing. +# + +## @todo SchedQueueEntry isn't a typical ModelDataBase child (not fetching all +## fields; is an extended data class mixing data from multiple tables), so +## this won't work yet. +# +## pylint: disable=missing-docstring +#class TestCaseQueueDataTestCase(ModelDataBaseTestCase): +# def setUp(self): +# self.aoSamples = [SchedQueueEntry(),] +# +# +#if __name__ == '__main__': +# unittest.main() +# # not reached. +# diff --git a/src/VBox/ValidationKit/testmanager/core/schedulerbase.py b/src/VBox/ValidationKit/testmanager/core/schedulerbase.py new file mode 100755 index 00000000..e6cb09d6 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/schedulerbase.py @@ -0,0 +1,1570 @@ +# -*- coding: utf-8 -*- +# $Id: schedulerbase.py $ +# pylint: disable=too-many-lines + + +""" +Test Manager - Base class and utilities for the schedulers. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard python imports. +import sys; +import unittest; + +# Validation Kit imports. +from common import utils, constants; +from testmanager import config; +from testmanager.core.build import BuildDataEx, BuildLogic; +from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, TMExceptionBase; +from testmanager.core.buildsource import BuildSourceData, BuildSourceLogic; +from testmanager.core.globalresource import GlobalResourceLogic; +from testmanager.core.schedgroup import SchedGroupData, SchedGroupLogic; +from testmanager.core.systemlog import SystemLogData, SystemLogLogic; +from testmanager.core.testbox import TestBoxData, TestBoxDataEx; +from testmanager.core.testboxstatus import TestBoxStatusData, TestBoxStatusLogic; +from testmanager.core.testcase import TestCaseLogic; +from testmanager.core.testcaseargs import TestCaseArgsDataEx, TestCaseArgsLogic; +from testmanager.core.testset import TestSetData, TestSetLogic; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + xrange = range; # pylint: disable=redefined-builtin,invalid-name + + + +class ReCreateQueueData(object): + """ + Data object for recreating a scheduling queue. + + It's mostly a storage object, but has a few data checking operation + associated with it. + """ + + def __init__(self, oDb, idSchedGroup): + # + # Load data from the database. + # + oSchedGroupLogic = SchedGroupLogic(oDb); + self.oSchedGroup = oSchedGroupLogic.cachedLookup(idSchedGroup); + + # Will extend the entries with aoTestCases and dTestCases members + # further down (SchedGroupMemberDataEx). checkForGroupDepCycles + # will add aidTestGroupPreReqs. + self.aoTestGroups = oSchedGroupLogic.getMembers(idSchedGroup); + + # aoTestCases entries are TestCaseData instance with iSchedPriority + # and idTestGroup added for our purposes. + # We will add oTestGroup and aoArgsVariations members to each further down. + self.aoTestCases = oSchedGroupLogic.getTestCasesForGroup(idSchedGroup, cMax = 4096); + + # Load dependencies. + oTestCaseLogic = TestCaseLogic(oDb) + for oTestCase in self.aoTestCases: + oTestCase.aidPreReqs = oTestCaseLogic.getTestCasePreReqIds(oTestCase.idTestCase, cMax = 4096); + + # aoTestCases entries are TestCaseArgsData instance with iSchedPriority + # and idTestGroup added for our purposes. + # We will add oTestGroup and oTestCase members to each further down. + self.aoArgsVariations = oSchedGroupLogic.getTestCaseArgsForGroup(idSchedGroup, cMax = 65536); + + # + # Generate global lookups. + # + + # Generate a testcase lookup dictionary for use when working on + # argument variations. + self.dTestCases = {}; + for oTestCase in self.aoTestCases: + self.dTestCases[oTestCase.idTestCase] = oTestCase; + assert len(self.dTestCases) <= len(self.aoTestCases); # Note! Can be shorter! + + # Generate a testgroup lookup dictionary. + self.dTestGroups = {}; + for oTestGroup in self.aoTestGroups: + self.dTestGroups[oTestGroup.idTestGroup] = oTestGroup; + assert len(self.dTestGroups) == len(self.aoTestGroups); + + # + # Associate extra members with the base data. + # + if self.aoTestGroups: + # Prep the test groups. + for oTestGroup in self.aoTestGroups: + oTestGroup.aoTestCases = []; + oTestGroup.dTestCases = {}; + + # Link testcases to their group, both directions. Prep testcases for + # argument varation association. + oTestGroup = self.aoTestGroups[0]; + for oTestCase in self.aoTestCases: + if oTestGroup.idTestGroup != oTestCase.idTestGroup: + oTestGroup = self.dTestGroups[oTestCase.idTestGroup]; + + assert oTestCase.idTestCase not in oTestGroup.dTestCases; + oTestGroup.dTestCases[oTestCase.idTestCase] = oTestCase; + oTestGroup.aoTestCases.append(oTestCase); + oTestCase.oTestGroup = oTestGroup; + oTestCase.aoArgsVariations = []; + + # Associate testcase argument variations with their testcases (group) + # in both directions. + oTestGroup = self.aoTestGroups[0]; + oTestCase = self.aoTestCases[0] if self.aoTestCases else None; + for oArgVariation in self.aoArgsVariations: + if oTestGroup.idTestGroup != oArgVariation.idTestGroup: + oTestGroup = self.dTestGroups[oArgVariation.idTestGroup]; + if oTestCase.idTestCase != oArgVariation.idTestCase or oTestCase.idTestGroup != oArgVariation.idTestGroup: + oTestCase = oTestGroup.dTestCases[oArgVariation.idTestCase]; + + oTestCase.aoArgsVariations.append(oArgVariation); + oArgVariation.oTestCase = oTestCase; + oArgVariation.oTestGroup = oTestGroup; + + else: + assert not self.aoTestCases; + assert not self.aoArgsVariations; + # done. + + @staticmethod + def _addPreReqError(aoErrors, aidChain, oObj, sMsg): + """ Returns a chain of IDs error entry. """ + + sMsg += ' Dependency chain: %s' % (aidChain[0],); + for i in range(1, len(aidChain)): + sMsg += ' -> %s' % (aidChain[i],); + + aoErrors.append([sMsg, oObj]); + return aoErrors; + + def checkForGroupDepCycles(self): + """ + Checks for testgroup depencency cycles and any missing testgroup + dependencies. + Returns array of errors (see SchedulderBase.recreateQueue()). + """ + aoErrors = []; + for oTestGroup in self.aoTestGroups: + idPreReq = oTestGroup.idTestGroupPreReq; + if idPreReq is None: + oTestGroup.aidTestGroupPreReqs = []; + continue; + + aidChain = [oTestGroup.idTestGroup,]; + while idPreReq is not None: + aidChain.append(idPreReq); + if len(aidChain) >= 10: + self._addPreReqError(aoErrors, aidChain, oTestGroup, + 'TestGroup #%s prerequisite chain is too long!' + % (oTestGroup.idTestGroup,)); + break; + + oDep = self.dTestGroups.get(idPreReq, None); + if oDep is None: + self._addPreReqError(aoErrors, aidChain, oTestGroup, + 'TestGroup #%s prerequisite #%s is not in the scheduling group!' + % (oTestGroup.idTestGroup, idPreReq,)); + break; + + idPreReq = oDep.idTestGroupPreReq; + oTestGroup.aidTestGroupPreReqs = aidChain[1:]; + + return aoErrors; + + + def checkForMissingTestCaseDeps(self): + """ + Checks that testcase dependencies stays within bounds. We do not allow + dependencies outside a testgroup, no dependency cycles or even remotely + long dependency chains. + + Returns array of errors (see SchedulderBase.recreateQueue()). + """ + aoErrors = []; + for oTestGroup in self.aoTestGroups: + for oTestCase in oTestGroup.aoTestCases: + if not oTestCase.aidPreReqs: + continue; + + # Stupid recursion code using special stack(s). + aiIndexes = [[oTestCase, 0], ]; + aidChain = [oTestCase.idTestGroup,]; + while aiIndexes: + (oCur, i) = aiIndexes[-1]; + if i >= len(oCur.aidPreReqs): + aiIndexes.pop(); + aidChain.pop(); + else: + aiIndexes[-1][1] = i + 1; # whatever happens, we'll advance on the current level. + + idPreReq = oTestCase.aidPreReqs[i]; + oDep = oTestGroup.dTestCases.get(idPreReq, None); + if oDep is None: + self._addPreReqError(aoErrors, aidChain, oTestCase, + 'TestCase #%s prerequisite #%s is not in the scheduling group!' + % (oTestCase.idTestCase, idPreReq)); + elif idPreReq in aidChain: + self._addPreReqError(aoErrors, aidChain, oTestCase, + 'TestCase #%s prerequisite #%s creates a cycle!' + % (oTestCase.idTestCase, idPreReq)); + elif not oDep.aiPreReqs: + pass; + elif len(aidChain) >= 10: + self._addPreReqError(aoErrors, aidChain, oTestCase, + 'TestCase #%s prerequisite chain is too long!' % (oTestCase.idTestCase,)); + else: + aiIndexes.append([oDep, 0]); + aidChain.append(idPreReq); + + return aoErrors; + + def deepTestGroupSort(self): + """ + Sorts the testgroups and their testcases by priority and dependencies. + Note! Don't call this before checking for dependency cycles! + """ + if not self.aoTestGroups: + return; + + # + # ASSUMES groups as well as testcases are sorted by priority by the + # database. So we only have to concern ourselves with the dependency + # sorting. + # + iGrpPrio = self.aoTestGroups[0].iSchedPriority; + for iTestGroup, oTestGroup in enumerate(self.aoTestGroups): + if oTestGroup.iSchedPriority > iGrpPrio: + raise TMExceptionBase('Incorrectly sorted testgroups returned by database: iTestGroup=%s prio=%s %s' + % ( iTestGroup, iGrpPrio, + ', '.join(['(%s: %s)' % (oCur.idTestGroup, oCur.iSchedPriority) + for oCur in self.aoTestGroups]), ) ); + iGrpPrio = oTestGroup.iSchedPriority; + + if oTestGroup.aoTestCases: + iTstPrio = oTestGroup.aoTestCases[0].iSchedPriority; + for iTestCase, oTestCase in enumerate(oTestGroup.aoTestCases): + if oTestCase.iSchedPriority > iTstPrio: + raise TMExceptionBase('Incorrectly sorted testcases returned by database: i=%s prio=%s idGrp=%s %s' + % ( iTestCase, iTstPrio, oTestGroup.idTestGroup, + ', '.join(['(%s: %s)' % (oCur.idTestCase, oCur.iSchedPriority) + for oCur in oTestGroup.aoTestCases]),)); + + # + # Sort the testgroups by dependencies. + # + i = 0; + while i < len(self.aoTestGroups): + oTestGroup = self.aoTestGroups[i]; + if oTestGroup.idTestGroupPreReq is not None: + iPreReq = self.aoTestGroups.index(self.dTestGroups[oTestGroup.idTestGroupPreReq]); + if iPreReq > i: + # The prerequisite is after the current entry. Move the + # current entry so that it's following it's prereq entry. + self.aoTestGroups.insert(iPreReq + 1, oTestGroup); + self.aoTestGroups.pop(i); + continue; + assert iPreReq < i; + i += 1; # Advance. + + # + # Sort the testcases by dependencies. + # Same algorithm as above, just more prerequisites. + # + for oTestGroup in self.aoTestGroups: + i = 0; + while i < len(oTestGroup.aoTestCases): + oTestCase = oTestGroup.aoTestCases[i]; + if oTestCase.aidPreReqs: + for idPreReq in oTestCase.aidPreReqs: + iPreReq = oTestGroup.aoTestCases.index(oTestGroup.dTestCases[idPreReq]); + if iPreReq > i: + # The prerequisite is after the current entry. Move the + # current entry so that it's following it's prereq entry. + oTestGroup.aoTestGroups.insert(iPreReq + 1, oTestCase); + oTestGroup.aoTestGroups.pop(i); + i -= 1; # Don't advance. + break; + assert iPreReq < i; + i += 1; # Advance. + + + +class SchedQueueData(ModelDataBase): + """ + Scheduling queue data item. + """ + + ksIdAttr = 'idSchedGroup'; + + ksParam_idSchedGroup = 'SchedQueueData_idSchedGroup'; + ksParam_idItem = 'SchedQueueData_idItem'; + ksParam_offQueue = 'SchedQueueData_offQueue'; + ksParam_idGenTestCaseArgs = 'SchedQueueData_idGenTestCaseArgs'; + ksParam_idTestGroup = 'SchedQueueData_idTestGroup'; + ksParam_aidTestGroupPreReqs = 'SchedQueueData_aidTestGroupPreReqs'; + ksParam_bmHourlySchedule = 'SchedQueueData_bmHourlySchedule'; + ksParam_tsConfig = 'SchedQueueData_tsConfig'; + ksParam_tsLastScheduled = 'SchedQueueData_tsLastScheduled'; + ksParam_idTestSetGangLeader = 'SchedQueueData_idTestSetGangLeader'; + ksParam_cMissingGangMembers = 'SchedQueueData_cMissingGangMembers'; + + kasAllowNullAttributes = [ 'idItem', 'offQueue', 'aidTestGroupPreReqs', 'bmHourlySchedule', 'idTestSetGangLeader', + 'tsConfig', 'tsLastScheduled' ]; + + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.idSchedGroup = None; + self.idItem = None; + self.offQueue = None; + self.idGenTestCaseArgs = None; + self.idTestGroup = None; + self.aidTestGroupPreReqs = None; + self.bmHourlySchedule = None; + self.tsConfig = None; + self.tsLastScheduled = None; + self.idTestSetGangLeader = None; + self.cMissingGangMembers = 1; + + def initFromValues(self, idSchedGroup, idGenTestCaseArgs, idTestGroup, aidTestGroupPreReqs, # pylint: disable=too-many-arguments + bmHourlySchedule, cMissingGangMembers, + idItem = None, offQueue = None, tsConfig = None, tsLastScheduled = None, idTestSetGangLeader = None): + """ + Reinitialize with all attributes potentially given as inputs. + Return self. + """ + self.idSchedGroup = idSchedGroup; + self.idItem = idItem; + self.offQueue = offQueue; + self.idGenTestCaseArgs = idGenTestCaseArgs; + self.idTestGroup = idTestGroup; + self.aidTestGroupPreReqs = aidTestGroupPreReqs; + self.bmHourlySchedule = bmHourlySchedule; + self.tsConfig = tsConfig; + self.tsLastScheduled = tsLastScheduled; + self.idTestSetGangLeader = idTestSetGangLeader; + self.cMissingGangMembers = cMissingGangMembers; + return self; + + def initFromDbRow(self, aoRow): + """ + Initialize from database row (SELECT * FROM SchedQueues). + Returns self. + Raises exception if no row is specfied. + """ + if aoRow is None: + raise TMExceptionBase('SchedQueueData not found.'); + + self.idSchedGroup = aoRow[0]; + self.idItem = aoRow[1]; + self.offQueue = aoRow[2]; + self.idGenTestCaseArgs = aoRow[3]; + self.idTestGroup = aoRow[4]; + self.aidTestGroupPreReqs = aoRow[5]; + self.bmHourlySchedule = aoRow[6]; + self.tsConfig = aoRow[7]; + self.tsLastScheduled = aoRow[8]; + self.idTestSetGangLeader = aoRow[9]; + self.cMissingGangMembers = aoRow[10]; + return self; + + + + + + +class SchedulerBase(object): + """ + The scheduler base class. + + The scheduler classes have two functions: + 1. Recreate the scheduling queue. + 2. Pick the next task from the queue. + + The first is scheduler specific, the latter isn't. + """ + + class BuildCache(object): + """ Build cache. """ + + class BuildCacheIterator(object): + """ Build class iterator. """ + def __init__(self, oCache): + self.oCache = oCache; + self.iCur = 0; + + def __iter__(self): + """Returns self, required by the language.""" + return self; + + def __next__(self): + """Returns the next build, raises StopIteration when the end has been reached.""" + while True: + if self.iCur >= len(self.oCache.aoEntries): + oEntry = self.oCache.fetchFromCursor(); + if oEntry is None: + raise StopIteration; + else: + oEntry = self.oCache.aoEntries[self.iCur]; + self.iCur += 1; + if not oEntry.fRemoved: + return oEntry; + return None; # not reached, but make pylint happy (for now). + + def next(self): + """ For python 2.x. """ + return self.__next__(); + + class BuildCacheEntry(object): + """ Build cache entry. """ + + def __init__(self, oBuild, fMaybeBlacklisted): + self.oBuild = oBuild; + self._fBlacklisted = None if fMaybeBlacklisted is True else False; + self.fRemoved = False; + self._dPreReqDecisions = {}; + + def remove(self): + """ + Marks the cache entry as removed. + This doesn't actually remove it from the cache array, only marks + it as removed. It has no effect on open iterators. + """ + self.fRemoved = True; + + def getPreReqDecision(self, sPreReqSet): + """ + Retrieves a cached prerequisite decision. + Returns boolean if found, None if not. + """ + return self._dPreReqDecisions.get(sPreReqSet); + + def setPreReqDecision(self, sPreReqSet, fDecision): + """ + Caches a prerequistie decision. + """ + self._dPreReqDecisions[sPreReqSet] = fDecision; + return fDecision; + + def isBlacklisted(self, oDb): + """ Checks if the build is blacklisted. """ + if self._fBlacklisted is None: + self._fBlacklisted = BuildLogic(oDb).isBuildBlacklisted(self.oBuild); + return self._fBlacklisted; + + + def __init__(self): + self.aoEntries = []; + self.oCursor = None; + + def setupSource(self, oDb, idBuildSrc, sOs, sCpuArch, tsNow): + """ Configures the build cursor for the cache. """ + if not self.aoEntries and self.oCursor is None: + oBuildSource = BuildSourceData().initFromDbWithId(oDb, idBuildSrc, tsNow); + self.oCursor = BuildSourceLogic(oDb).openBuildCursor(oBuildSource, sOs, sCpuArch, tsNow); + return True; + + def __iter__(self): + """Return an iterator.""" + return self.BuildCacheIterator(self); + + def fetchFromCursor(self): + """ Fetches a build from the cursor and adds it to the cache.""" + if self.oCursor is None: + return None; + + try: + aoRow = self.oCursor.fetchOne(); + except: + return None; + if aoRow is None: + return None; + + oBuild = BuildDataEx().initFromDbRow(aoRow); + oEntry = self.BuildCacheEntry(oBuild, aoRow[-1]); + self.aoEntries.append(oEntry); + return oEntry; + + def __init__(self, oDb, oSchedGrpData, iVerbosity = 0, tsSecStart = None): + self._oDb = oDb; + self._oSchedGrpData = oSchedGrpData; + self._iVerbosity = iVerbosity; + self._asMessages = []; + self._tsSecStart = tsSecStart if tsSecStart is not None else utils.timestampSecond(); + self.oBuildCache = self.BuildCache(); + self.dTestGroupMembers = {}; + + @staticmethod + def _instantiate(oDb, oSchedGrpData, iVerbosity = 0, tsSecStart = None): + """ + Instantiate the scheduler specified by the scheduling group. + Returns scheduler child class instance. May raise exception if + the input is invalid. + """ + if oSchedGrpData.enmScheduler == SchedGroupData.ksScheduler_BestEffortContinuousIntegration: + from testmanager.core.schedulerbeci import SchdulerBeci; + oScheduler = SchdulerBeci(oDb, oSchedGrpData, iVerbosity, tsSecStart); + else: + raise oDb.integrityException('Invalid scheduler "%s", idSchedGroup=%d' \ + % (oSchedGrpData.enmScheduler, oSchedGrpData.idSchedGroup)); + return oScheduler; + + + # + # Misc. + # + + def msgDebug(self, sText): + """Debug printing.""" + if self._iVerbosity > 1: + self._asMessages.append('debug:' + sText); + return None; + + def msgInfo(self, sText): + """Info printing.""" + if self._iVerbosity > 1: + self._asMessages.append('info: ' + sText); + return None; + + def dprint(self, sMsg): + """Prints a debug message to the srv glue log (see config.py). """ + if config.g_kfSrvGlueDebugScheduler: + self._oDb.dprint(sMsg); + return None; + + def getElapsedSecs(self): + """ Returns the number of seconds this scheduling task has been running. """ + tsSecNow = utils.timestampSecond(); + if tsSecNow < self._tsSecStart: # paranoia + self._tsSecStart = tsSecNow; + return tsSecNow - self._tsSecStart; + + + # + # Create schedule. + # + + def _recreateQueueCancelGatherings(self): + """ + Cancels all pending gang gatherings on the current queue. + """ + self._oDb.execute('SELECT idTestSetGangLeader\n' + 'FROM SchedQueues\n' + 'WHERE idSchedGroup = %s\n' + ' AND idTestSetGangLeader is not NULL\n' + , (self._oSchedGrpData.idSchedGroup,)); + if self._oDb.getRowCount() > 0: + oTBStatusLogic = TestBoxStatusLogic(self._oDb); + for aoRow in self._oDb.fetchAll(): + idTestSetGangLeader = aoRow[0]; + oTBStatusLogic.updateGangStatus(idTestSetGangLeader, + TestBoxStatusData.ksTestBoxState_GangGatheringTimedOut, + fCommit = False); + return True; + + def _recreateQueueItems(self, oData): + """ + Returns an array of queue items (SchedQueueData). + Child classes must override this. + """ + _ = oData; + return []; + + def recreateQueueWorker(self): + """ + Worker for recreateQueue. + """ + + # + # Collect the necessary data and validate it. + # + oData = ReCreateQueueData(self._oDb, self._oSchedGrpData.idSchedGroup); + aoErrors = oData.checkForGroupDepCycles(); + aoErrors.extend(oData.checkForMissingTestCaseDeps()); + if not aoErrors: + oData.deepTestGroupSort(); + + # + # The creation of the scheduling queue is done by the child class. + # + # We will try guess where in queue we're currently at and rotate + # the items such that we will resume execution in the approximately + # same position. The goal of the scheduler is to provide a 100% + # deterministic result so that if we regenerate the queue when there + # are no changes to the testcases, testgroups or scheduling groups + # involved, test execution will be unchanged (save for maybe just a + # little for gang gathering). + # + aoItems = []; + if not oData.oSchedGroup.fEnabled: + self.msgInfo('Disabled.'); + elif not oData.aoArgsVariations: + self.msgInfo('Found no test case argument variations.'); + else: + aoItems = self._recreateQueueItems(oData); + self.msgDebug('len(aoItems)=%s' % (len(aoItems),)); + #for i in range(len(aoItems)): + # self.msgDebug('aoItems[%2d]=%s' % (i, aoItems[i])); + if aoItems: + self._oDb.execute('SELECT offQueue FROM SchedQueues WHERE idSchedGroup = %s ORDER BY idItem LIMIT 1' + , (self._oSchedGrpData.idSchedGroup,)); + if self._oDb.getRowCount() > 0: + offQueue = self._oDb.fetchOne()[0]; + self._oDb.execute('SELECT COUNT(*) FROM SchedQueues WHERE idSchedGroup = %s' + , (self._oSchedGrpData.idSchedGroup,)); + cItems = self._oDb.fetchOne()[0]; + offQueueNew = (offQueue * cItems) // len(aoItems); + if offQueueNew != 0: + aoItems = aoItems[offQueueNew:] + aoItems[:offQueueNew]; + + # + # Replace the scheduling queue. + # Care need to be take to first timeout/abort any gangs in the + # gathering state since these use the queue to set up the date. + # + self._recreateQueueCancelGatherings(); + self._oDb.execute('DELETE FROM SchedQueues WHERE idSchedGroup = %s\n', (self._oSchedGrpData.idSchedGroup,)); + if aoItems: + self._oDb.insertList('INSERT INTO SchedQueues (\n' + ' idSchedGroup,\n' + ' offQueue,\n' + ' idGenTestCaseArgs,\n' + ' idTestGroup,\n' + ' aidTestGroupPreReqs,\n' + ' bmHourlySchedule,\n' + ' cMissingGangMembers )\n', + aoItems, self._formatItemForInsert); + return (aoErrors, self._asMessages); + + def _formatItemForInsert(self, oItem): + """ + Used by recreateQueueWorker together with TMDatabaseConnect::insertList + """ + return self._oDb.formatBindArgs('(%s,%s,%s,%s,%s,%s,%s)' + , ( oItem.idSchedGroup, + oItem.offQueue, + oItem.idGenTestCaseArgs, + oItem.idTestGroup, + oItem.aidTestGroupPreReqs if oItem.aidTestGroupPreReqs else None, + oItem.bmHourlySchedule, + oItem.cMissingGangMembers + )); + + @staticmethod + def recreateQueue(oDb, uidAuthor, idSchedGroup, iVerbosity = 1): + """ + (Re-)creates the scheduling queue for the given group. + + Returns (asMessages, asMessages). On success the array with the error + will be empty, on failure it will contain (sError, oRelatedObject) + entries. The messages is for debugging and are simple strings. + + Raises exception database error. + """ + + aoExtraMsgs = []; + if oDb.debugIsExplainEnabled(): + aoExtraMsgs += ['Warning! Disabling SQL explain to avoid deadlocking against locked tables.']; + oDb.debugDisableExplain(); + + aoErrors = []; + asMessages = []; + try: + # + # To avoid concurrency issues (SchedQueues) and inconsistent data (*), + # we lock quite a few tables while doing this work. We access more + # data than scheduleNewTask so we lock some additional tables. + # + oDb.rollback(); + oDb.begin(); + oDb.execute('LOCK TABLE SchedGroups, SchedGroupMembers, TestGroups, TestGroupMembers IN SHARE MODE'); + oDb.execute('LOCK TABLE TestBoxes, TestCaseArgs, TestCases IN SHARE MODE'); + oDb.execute('LOCK TABLE TestBoxStatuses, SchedQueues IN EXCLUSIVE MODE'); + + # + # Instantiate the scheduler and call the worker function. + # + oSchedGrpData = SchedGroupData().initFromDbWithId(oDb, idSchedGroup); + oScheduler = SchedulerBase._instantiate(oDb, oSchedGrpData, iVerbosity); + + (aoErrors, asMessages) = oScheduler.recreateQueueWorker(); + if not aoErrors: + SystemLogLogic(oDb).addEntry(SystemLogData.ksEvent_SchedQueueRecreate, + 'User #%d recreated sched queue #%d.' % (uidAuthor, idSchedGroup,)); + oDb.commit(); + else: + oDb.rollback(); + + except: + oDb.rollback(); + raise; + + return (aoErrors, aoExtraMsgs + asMessages); + + + @staticmethod + def cleanUpOrphanedQueues(oDb): + """ + Removes orphan scheduling queues from the SchedQueues table. + + Queues becomes orphaned when the scheduling group they belongs to has been deleted. + + Returns number of orphaned queues. + Raises exception database error. + """ + cRet = 0; + try: + oDb.rollback(); + oDb.begin(); + oDb.execute(''' +SELECT SchedQueues.idSchedGroup +FROM SchedQueues + LEFT OUTER JOIN SchedGroups + ON SchedGroups.idSchedGroup = SchedQueues.idSchedGroup + AND SchedGroups.tsExpire = 'infinity'::TIMESTAMP +WHERE SchedGroups.idSchedGroup is NULL +GROUP BY SchedQueues.idSchedGroup'''); + aaoOrphanRows = oDb.fetchAll(); + cRet = len(aaoOrphanRows); + if cRet > 0: + oDb.execute('DELETE FROM SchedQueues WHERE idSchedGroup IN (%s)' + % (','.join([str(aoRow[0]) for aoRow in aaoOrphanRows]),)); + oDb.commit(); + except: + oDb.rollback(); + raise; + return cRet; + + + # + # Schedule Task. + # + + def _composeGangArguments(self, idTestSet): + """ + Composes the gang specific testdriver arguments. + Returns command line string, including a leading space. + """ + + oTestSet = TestSetData().initFromDbWithId(self._oDb, idTestSet); + aoGangMembers = TestSetLogic(self._oDb).getGang(oTestSet.idTestSetGangLeader); + + sArgs = ' --gang-member-no %s --gang-members %s' % (oTestSet.iGangMemberNo, len(aoGangMembers)); + for i, _ in enumerate(aoGangMembers): + sArgs = ' --gang-ipv4-%s %s' % (i, aoGangMembers[i].ip); ## @todo IPv6 + + return sArgs; + + + def composeExecResponseWorker(self, idTestSet, oTestEx, oTestBox, oBuild, oValidationKitBuild, sBaseUrl): + """ + Given all the bits of data, compose an EXEC command response to the testbox. + """ + sScriptZips = oTestEx.oTestCase.sValidationKitZips; + if sScriptZips is None or sScriptZips.find('@VALIDATIONKIT_ZIP@') >= 0: + assert oValidationKitBuild; + if sScriptZips is None: + sScriptZips = oValidationKitBuild.sBinaries; + else: + sScriptZips = sScriptZips.replace('@VALIDATIONKIT_ZIP@', oValidationKitBuild.sBinaries); + sScriptZips = sScriptZips.replace('@DOWNLOAD_BASE_URL@', sBaseUrl + config.g_ksTmDownloadBaseUrlRel); + + sCmdLine = oTestEx.oTestCase.sBaseCmd + ' ' + oTestEx.sArgs; + sCmdLine = sCmdLine.replace('@BUILD_BINARIES@', oBuild.sBinaries); + sCmdLine = sCmdLine.strip(); + if oTestEx.cGangMembers > 1: + sCmdLine += ' ' + self._composeGangArguments(idTestSet); + + cSecTimeout = oTestEx.cSecTimeout if oTestEx.cSecTimeout is not None else oTestEx.oTestCase.cSecTimeout; + cSecTimeout = cSecTimeout * oTestBox.pctScaleTimeout // 100; + + dResponse = \ + { + constants.tbresp.ALL_PARAM_RESULT: constants.tbresp.CMD_EXEC, + constants.tbresp.EXEC_PARAM_RESULT_ID: idTestSet, + constants.tbresp.EXEC_PARAM_SCRIPT_ZIPS: sScriptZips, + constants.tbresp.EXEC_PARAM_SCRIPT_CMD_LINE: sCmdLine, + constants.tbresp.EXEC_PARAM_TIMEOUT: cSecTimeout, + }; + return dResponse; + + @staticmethod + def composeExecResponse(oDb, idTestSet, sBaseUrl, iVerbosity = 0): + """ + Composes an EXEC response for a gang member (other than the last). + Returns a EXEC response or raises an exception (DB/input error). + """ + # + # Gather the necessary data. + # + oTestSet = TestSetData().initFromDbWithId(oDb, idTestSet); + oTestBox = TestBoxData().initFromDbWithGenId(oDb, oTestSet.idGenTestBox); + oTestEx = TestCaseArgsDataEx().initFromDbWithGenIdEx(oDb, oTestSet.idGenTestCaseArgs, + tsConfigEff = oTestSet.tsConfig, + tsRsrcEff = oTestSet.tsConfig); + oBuild = BuildDataEx().initFromDbWithId(oDb, oTestSet.idBuild); + oValidationKitBuild = None; + if oTestSet.idBuildTestSuite is not None: + oValidationKitBuild = BuildDataEx().initFromDbWithId(oDb, oTestSet.idBuildTestSuite); + + # + # Instantiate the specified scheduler and let it do the rest. + # + oSchedGrpData = SchedGroupData().initFromDbWithId(oDb, oTestSet.idSchedGroup, oTestSet.tsCreated); + assert oSchedGrpData.fEnabled is True; + assert oSchedGrpData.idBuildSrc is not None; + oScheduler = SchedulerBase._instantiate(oDb, oSchedGrpData, iVerbosity); + + return oScheduler.composeExecResponseWorker(idTestSet, oTestEx, oTestBox, oBuild, oValidationKitBuild, sBaseUrl); + + + def _updateTask(self, oTask, tsNow): + """ + Updates a gang schedule task. + """ + assert oTask.cMissingGangMembers >= 1; + assert oTask.idTestSetGangLeader is not None; + assert oTask.idTestSetGangLeader >= 1; + if tsNow is not None: + self._oDb.execute('UPDATE SchedQueues\n' + ' SET idTestSetGangLeader = %s,\n' + ' cMissingGangMembers = %s,\n' + ' tsLastScheduled = %s\n' + 'WHERE idItem = %s\n' + , (oTask.idTestSetGangLeader, oTask.cMissingGangMembers, tsNow, oTask.idItem,) ); + else: + self._oDb.execute('UPDATE SchedQueues\n' + ' SET cMissingGangMembers = %s\n' + 'WHERE idItem = %s\n' + , (oTask.cMissingGangMembers, oTask.idItem,) ); + return True; + + def _moveTaskToEndOfQueue(self, oTask, cGangMembers, tsNow): + """ + The task has been scheduled successfully, reset it's data move it to + the end of the queue. + """ + if cGangMembers > 1: + self._oDb.execute('UPDATE SchedQueues\n' + ' SET idItem = NEXTVAL(\'SchedQueueItemIdSeq\'),\n' + ' idTestSetGangLeader = NULL,\n' + ' cMissingGangMembers = %s\n' + 'WHERE idItem = %s\n' + , (cGangMembers, oTask.idItem,) ); + else: + self._oDb.execute('UPDATE SchedQueues\n' + ' SET idItem = NEXTVAL(\'SchedQueueItemIdSeq\'),\n' + ' idTestSetGangLeader = NULL,\n' + ' cMissingGangMembers = 1,\n' + ' tsLastScheduled = %s\n' + 'WHERE idItem = %s\n' + , (tsNow, oTask.idItem,) ); + return True; + + + + + def _createTestSet(self, oTask, oTestEx, oTestBoxData, oBuild, oValidationKitBuild, tsNow): + # type: (SchedQueueData, TestCaseArgsDataEx, TestBoxData, BuildDataEx, BuildDataEx, datetime.datetime) -> int + """ + Creates a test set for using the given data. + Will not commit, someone up the callstack will that later on. + + Returns the test set ID, may raise an exception on database error. + """ + # Lazy bird doesn't want to write testset.py and does it all here. + + # + # We're getting the TestSet ID first in order to include it in the base + # file name (that way we can directly relate files on the disk to the + # test set when doing batch work), and also for idTesetSetGangLeader. + # + self._oDb.execute('SELECT NEXTVAL(\'TestSetIdSeq\')'); + idTestSet = self._oDb.fetchOne()[0]; + + sBaseFilename = '%04d/%02d/%02d/%02d/TestSet-%s' \ + % (tsNow.year, tsNow.month, tsNow.day, (tsNow.hour // 6) * 6, idTestSet); + + # + # Gang scheduling parameters. Changes the oTask data for updating by caller. + # + iGangMemberNo = 0; + + if oTestEx.cGangMembers <= 1: + assert oTask.idTestSetGangLeader is None; + assert oTask.cMissingGangMembers <= 1; + elif oTask.idTestSetGangLeader is None: + assert oTask.cMissingGangMembers == oTestEx.cGangMembers; + oTask.cMissingGangMembers = oTestEx.cGangMembers - 1; + oTask.idTestSetGangLeader = idTestSet; + else: + assert oTask.cMissingGangMembers > 0 and oTask.cMissingGangMembers < oTestEx.cGangMembers; + oTask.cMissingGangMembers -= 1; + + # + # Do the database stuff. + # + self._oDb.execute('INSERT INTO TestSets (\n' + ' idTestSet,\n' + ' tsConfig,\n' + ' tsCreated,\n' + ' idBuild,\n' + ' idBuildCategory,\n' + ' idBuildTestSuite,\n' + ' idGenTestBox,\n' + ' idTestBox,\n' + ' idSchedGroup,\n' + ' idTestGroup,\n' + ' idGenTestCase,\n' + ' idTestCase,\n' + ' idGenTestCaseArgs,\n' + ' idTestCaseArgs,\n' + ' sBaseFilename,\n' + ' iGangMemberNo,\n' + ' idTestSetGangLeader )\n' + 'VALUES ( %s,\n' # idTestSet + ' %s,\n' # tsConfig + ' %s,\n' # tsCreated + ' %s,\n' # idBuild + ' %s,\n' # idBuildCategory + ' %s,\n' # idBuildTestSuite + ' %s,\n' # idGenTestBox + ' %s,\n' # idTestBox + ' %s,\n' # idSchedGroup + ' %s,\n' # idTestGroup + ' %s,\n' # idGenTestCase + ' %s,\n' # idTestCase + ' %s,\n' # idGenTestCaseArgs + ' %s,\n' # idTestCaseArgs + ' %s,\n' # sBaseFilename + ' %s,\n' # iGangMemberNo + ' %s)\n' # idTestSetGangLeader + , ( idTestSet, + oTask.tsConfig, + tsNow, + oBuild.idBuild, + oBuild.idBuildCategory, + oValidationKitBuild.idBuild if oValidationKitBuild is not None else None, + oTestBoxData.idGenTestBox, + oTestBoxData.idTestBox, + oTask.idSchedGroup, + oTask.idTestGroup, + oTestEx.oTestCase.idGenTestCase, + oTestEx.oTestCase.idTestCase, + oTestEx.idGenTestCaseArgs, + oTestEx.idTestCaseArgs, + sBaseFilename, + iGangMemberNo, + oTask.idTestSetGangLeader, + )); + + self._oDb.execute('INSERT INTO TestResults (\n' + ' idTestResultParent,\n' + ' idTestSet,\n' + ' tsCreated,\n' + ' idStrName,\n' + ' cErrors,\n' + ' enmStatus,\n' + ' iNestingDepth)\n' + 'VALUES ( NULL,\n' # idTestResultParent + ' %s,\n' # idTestSet + ' %s,\n' # tsCreated + ' 0,\n' # idStrName + ' 0,\n' # cErrors + ' \'running\'::TestStatus_T,\n' + ' 0)\n' # iNestingDepth + 'RETURNING idTestResult' + , ( idTestSet, tsNow, )); + idTestResult = self._oDb.fetchOne()[0]; + + self._oDb.execute('UPDATE TestSets\n' + ' SET idTestResult = %s\n' + 'WHERE idTestSet = %s\n' + , (idTestResult, idTestSet, )); + + return idTestSet; + + def _tryFindValidationKitBit(self, oTestBoxData, tsNow): + """ + Tries to find the most recent validation kit build suitable for the given testbox. + Returns BuildDataEx or None. Raise exception on database error. + + Can be overridden by child classes to change the default build requirements. + """ + oBuildLogic = BuildLogic(self._oDb); + oBuildSource = BuildSourceData().initFromDbWithId(self._oDb, self._oSchedGrpData.idBuildSrcTestSuite, tsNow); + oCursor = BuildSourceLogic(self._oDb).openBuildCursor(oBuildSource, oTestBoxData.sOs, oTestBoxData.sCpuArch, tsNow); + for _ in range(oCursor.getRowCount()): + oBuild = BuildDataEx().initFromDbRow(oCursor.fetchOne()); + if not oBuildLogic.isBuildBlacklisted(oBuild): + return oBuild; + return None; + + def _tryFindBuild(self, oTask, oTestEx, oTestBoxData, tsNow): + """ + Tries to find a fitting build. + Returns BuildDataEx or None. Raise exception on database error. + + Can be overridden by child classes to change the default build requirements. + """ + + # + # Gather the set of prerequisites we have and turn them into a value + # set for use in the loop below. + # + # Note! We're scheduling on testcase level and ignoring argument variation + # selections in TestGroupMembers is intentional. + # + dPreReqs = {}; + + # Direct prerequisites. We assume they're all enabled as this can be + # checked at queue creation time. + for oPreReq in oTestEx.aoTestCasePreReqs: + dPreReqs[oPreReq.idTestCase] = 1; + + # Testgroup dependencies from the scheduling group config. + if oTask.aidTestGroupPreReqs is not None: + for iTestGroup in oTask.aidTestGroupPreReqs: + # Make sure the _active_ test group members are in the cache. + if iTestGroup not in self.dTestGroupMembers: + self._oDb.execute('SELECT DISTINCT TestGroupMembers.idTestCase\n' + 'FROM TestGroupMembers, TestCases\n' + 'WHERE TestGroupMembers.idTestGroup = %s\n' + ' AND TestGroupMembers.tsExpire > %s\n' + ' AND TestGroupMembers.tsEffective <= %s\n' + ' AND TestCases.idTestCase = TestGroupMembers.idTestCase\n' + ' AND TestCases.tsExpire > %s\n' + ' AND TestCases.tsEffective <= %s\n' + ' AND TestCases.fEnabled is TRUE\n' + , (iTestGroup, oTask.tsConfig, oTask.tsConfig, oTask.tsConfig, oTask.tsConfig,)); + aidTestCases = []; + for aoRow in self._oDb.fetchAll(): + aidTestCases.append(aoRow[0]); + self.dTestGroupMembers[iTestGroup] = aidTestCases; + + # Add the testgroup members to the prerequisites. + for idTestCase in self.dTestGroupMembers[iTestGroup]: + dPreReqs[idTestCase] = 1; + + # Create a SQL values table out of them. + sPreReqSet = '' + if dPreReqs: + for idPreReq in sorted(dPreReqs): + sPreReqSet += ', (' + str(idPreReq) + ')'; + sPreReqSet = sPreReqSet[2:]; # drop the leading ', '. + + # + # Try the builds. + # + self.oBuildCache.setupSource(self._oDb, self._oSchedGrpData.idBuildSrc, oTestBoxData.sOs, oTestBoxData.sCpuArch, tsNow); + for oEntry in self.oBuildCache: + # + # Check build requirements set by the test. + # + if not oTestEx.matchesBuildProps(oEntry.oBuild): + continue; + + if oEntry.isBlacklisted(self._oDb): + oEntry.remove(); + continue; + + # + # Check prerequisites. The default scheduler is satisfied if one + # argument variation has been executed successfully. It is not + # satisfied if there are any failure runs. + # + if sPreReqSet: + fDecision = oEntry.getPreReqDecision(sPreReqSet); + if fDecision is None: + # Check for missing prereqs. + self._oDb.execute('SELECT COUNT(*)\n' + 'FROM (VALUES ' + sPreReqSet + ') AS PreReqs(idTestCase)\n' + 'LEFT OUTER JOIN (SELECT idTestSet\n' + ' FROM TestSets\n' + ' WHERE enmStatus IN (%s, %s)\n' + ' AND idBuild = %s\n' + ' ) AS TestSets\n' + ' ON (PreReqs.idTestCase = TestSets.idTestCase)\n' + 'WHERE TestSets.idTestSet is NULL\n' + , ( TestSetData.ksTestStatus_Success, TestSetData.ksTestStatus_Skipped, + oEntry.oBuild.idBuild, )); + cMissingPreReqs = self._oDb.fetchOne()[0]; + if cMissingPreReqs > 0: + self.dprint('build %s is missing %u prerequisites (out of %s)' + % (oEntry.oBuild.idBuild, cMissingPreReqs, sPreReqSet,)); + oEntry.setPreReqDecision(sPreReqSet, False); + continue; + + # Check for failed prereq runs. + self._oDb.execute('SELECT COUNT(*)\n' + 'FROM (VALUES ' + sPreReqSet + ') AS PreReqs(idTestCase),\n' + ' TestSets\n' + 'WHERE PreReqs.idTestCase = TestSets.idTestCase\n' + ' AND TestSets.idBuild = %s\n' + ' AND TestSets.enmStatus IN (%s, %s, %s)\n' + , ( oEntry.oBuild.idBuild, + TestSetData.ksTestStatus_Failure, + TestSetData.ksTestStatus_TimedOut, + TestSetData.ksTestStatus_Rebooted, + ) + ); + cFailedPreReqs = self._oDb.fetchOne()[0]; + if cFailedPreReqs > 0: + self.dprint('build %s is has %u prerequisite failures (out of %s)' + % (oEntry.oBuild.idBuild, cFailedPreReqs, sPreReqSet,)); + oEntry.setPreReqDecision(sPreReqSet, False); + continue; + + oEntry.setPreReqDecision(sPreReqSet, True); + elif not fDecision: + continue; + + # + # If we can, check if the build files still exist. + # + if oEntry.oBuild.areFilesStillThere() is False: + self.dprint('build %s no longer exists' % (oEntry.oBuild.idBuild,)); + oEntry.remove(); + continue; + + self.dprint('found oBuild=%s' % (oEntry.oBuild,)); + return oEntry.oBuild; + return None; + + def _tryFindMatchingBuild(self, oLeaderBuild, oTestBoxData, idBuildSrc): + """ + Tries to find a matching build for gang scheduling. + Returns BuildDataEx or None. Raise exception on database error. + + Can be overridden by child classes to change the default build requirements. + """ + # + # Note! Should probably check build prerequisites if we get a different + # build back, so that we don't use a build which hasn't passed + # the smoke test. + # + _ = idBuildSrc; + return BuildLogic(self._oDb).tryFindSameBuildForOsArch(oLeaderBuild, oTestBoxData.sOs, oTestBoxData.sCpuArch); + + + def _tryAsLeader(self, oTask, oTestEx, oTestBoxData, tsNow, sBaseUrl): + """ + Try schedule the task as a gang leader (can be a gang of one). + Returns response or None. May raise exception on DB error. + """ + + # We don't wait for busy resources, we just try the next test. + oTestArgsLogic = TestCaseArgsLogic(self._oDb); + if not oTestArgsLogic.areResourcesFree(oTestEx): + self.dprint('Cannot get global test resources!'); + return None; + + # + # Find a matching build (this is the difficult bit). + # + oBuild = self._tryFindBuild(oTask, oTestEx, oTestBoxData, tsNow); + if oBuild is None: + self.dprint('No build!'); + return None; + if oTestEx.oTestCase.needValidationKitBit(): + oValidationKitBuild = self._tryFindValidationKitBit(oTestBoxData, tsNow); + if oValidationKitBuild is None: + self.dprint('No validation kit build!'); + return None; + else: + oValidationKitBuild = None; + + # + # Create a testset, allocate the resources and update the state. + # Note! Since resource allocation may still fail, we create a nested + # transaction so we can roll back. (Heed lock warning in docs!) + # + self._oDb.execute('SAVEPOINT tryAsLeader'); + idTestSet = self._createTestSet(oTask, oTestEx, oTestBoxData, oBuild, oValidationKitBuild, tsNow); + + if GlobalResourceLogic(self._oDb).allocateResources(oTestBoxData.idTestBox, oTestEx.aoGlobalRsrc, fCommit = False) \ + is not True: + self._oDb.execute('ROLLBACK TO SAVEPOINT tryAsLeader'); + self.dprint('Failed to allocate global resources!'); + return False; + + if oTestEx.cGangMembers <= 1: + # We're alone, put the task back at the end of the queue and issue EXEC cmd. + self._moveTaskToEndOfQueue(oTask, oTestEx.cGangMembers, tsNow); + dResponse = self.composeExecResponseWorker(idTestSet, oTestEx, oTestBoxData, oBuild, oValidationKitBuild, sBaseUrl); + sTBState = TestBoxStatusData.ksTestBoxState_Testing; + else: + # We're missing gang members, issue WAIT cmd. + self._updateTask(oTask, tsNow if idTestSet == oTask.idTestSetGangLeader else None); + dResponse = { constants.tbresp.ALL_PARAM_RESULT: constants.tbresp.CMD_WAIT, }; + sTBState = TestBoxStatusData.ksTestBoxState_GangGathering; + + TestBoxStatusLogic(self._oDb).updateState(oTestBoxData.idTestBox, sTBState, idTestSet, fCommit = False); + self._oDb.execute('RELEASE SAVEPOINT tryAsLeader'); + return dResponse; + + def _tryAsGangMember(self, oTask, oTestEx, oTestBoxData, tsNow, sBaseUrl): + """ + Try schedule the task as a gang member. + Returns response or None. May raise exception on DB error. + """ + + # + # The leader has choosen a build, we need to find a matching one for our platform. + # (It's up to the scheduler decide upon how strict dependencies are to be enforced + # upon subordinate group members.) + # + oLeaderTestSet = TestSetData().initFromDbWithId(self._oDb, oTestBoxData.idTestSetGangLeader); + + oLeaderBuild = BuildDataEx().initFromDbWithId(self._oDb, oLeaderTestSet.idBuild); + oBuild = self._tryFindMatchingBuild(oLeaderBuild, oTestBoxData, self._oSchedGrpData.idBuildSrc); + if oBuild is None: + return None; + + oValidationKitBuild = None; + if oLeaderTestSet.idBuildTestSuite is not None: + oLeaderValidationKitBit = BuildDataEx().initFromDbWithId(self._oDb, oLeaderTestSet.idBuildTestSuite); + oValidationKitBuild = self._tryFindMatchingBuild(oLeaderValidationKitBit, oTestBoxData, + self._oSchedGrpData.idBuildSrcTestSuite); + + # + # Create a testset and update the state(s). + # + idTestSet = self._createTestSet(oTask, oTestEx, oTestBoxData, oBuild, oValidationKitBuild, tsNow); + + oTBStatusLogic = TestBoxStatusLogic(self._oDb); + if oTask.cMissingGangMembers < 1: + # The whole gang is there, move the task to the end of the queue + # and update the status on the other gang members. + self._moveTaskToEndOfQueue(oTask, oTestEx.cGangMembers, tsNow); + dResponse = self.composeExecResponseWorker(idTestSet, oTestEx, oTestBoxData, oBuild, oValidationKitBuild, sBaseUrl); + sTBState = TestBoxStatusData.ksTestBoxState_GangTesting; + oTBStatusLogic.updateGangStatus(oTask.idTestSetGangLeader, sTBState, fCommit = False); + else: + # We're still missing some gang members, issue WAIT cmd. + self._updateTask(oTask, tsNow if idTestSet == oTask.idTestSetGangLeader else None); + dResponse = { constants.tbresp.ALL_PARAM_RESULT: constants.tbresp.CMD_WAIT, }; + sTBState = TestBoxStatusData.ksTestBoxState_GangGathering; + + oTBStatusLogic.updateState(oTestBoxData.idTestBox, sTBState, idTestSet, fCommit = False); + return dResponse; + + + def scheduleNewTaskWorker(self, oTestBoxData, tsNow, sBaseUrl): + """ + Worker for schduling a new task. + """ + + # + # Iterate the scheduler queue (fetch all to avoid having to concurrent + # queries), trying out each task to see if the testbox can execute it. + # + dRejected = {}; # variations we've already checked out and rejected. + self._oDb.execute('SELECT *\n' + 'FROM SchedQueues\n' + 'WHERE idSchedGroup = %s\n' + ' AND ( bmHourlySchedule IS NULL\n' + ' OR get_bit(bmHourlySchedule, %s) = 1 )\n' + 'ORDER BY idItem ASC\n' + , (self._oSchedGrpData.idSchedGroup, utils.getLocalHourOfWeek()) ); + aaoRows = self._oDb.fetchAll(); + for aoRow in aaoRows: + # Don't loop forever. + if self.getElapsedSecs() >= config.g_kcSecMaxNewTask: + break; + + # Unpack the data and check if we've rejected the testcasevar/group variation already (they repeat). + oTask = SchedQueueData().initFromDbRow(aoRow); + if config.g_kfSrvGlueDebugScheduler: + self.dprint('** Considering: idItem=%s idGenTestCaseArgs=%s idTestGroup=%s Deps=%s last=%s cfg=%s\n' + % ( oTask.idItem, oTask.idGenTestCaseArgs, oTask.idTestGroup, oTask.aidTestGroupPreReqs, + oTask.tsLastScheduled, oTask.tsConfig,)); + + sRejectNm = '%s:%s' % (oTask.idGenTestCaseArgs, oTask.idTestGroup,); + if sRejectNm in dRejected: + self.dprint('Duplicate, already rejected! (%s)' % (sRejectNm,)); + continue; + dRejected[sRejectNm] = 1; + + # Fetch all the test case info (too much, but who cares right now). + oTestEx = TestCaseArgsDataEx().initFromDbWithGenIdEx(self._oDb, oTask.idGenTestCaseArgs, + tsConfigEff = oTask.tsConfig, + tsRsrcEff = oTask.tsConfig); + if config.g_kfSrvGlueDebugScheduler: + self.dprint('TestCase "%s": %s %s' % (oTestEx.oTestCase.sName, oTestEx.oTestCase.sBaseCmd, oTestEx.sArgs,)); + + # This shouldn't happen, but just in case it does... + if oTestEx.oTestCase.fEnabled is not True: + self.dprint('Testcase is not enabled!!'); + continue; + + # Check if the testbox properties matches the test. + if not oTestEx.matchesTestBoxProps(oTestBoxData): + self.dprint('Testbox mismatch!'); + continue; + + # Try schedule it. + if oTask.idTestSetGangLeader is None or oTestEx.cGangMembers <= 1: + dResponse = self._tryAsLeader(oTask, oTestEx, oTestBoxData, tsNow, sBaseUrl); + elif oTask.cMissingGangMembers > 1: + dResponse = self._tryAsGangMember(oTask, oTestEx, oTestBoxData, tsNow, sBaseUrl); + else: + dResponse = None; # Shouldn't happen! + if dResponse is not None: + self.dprint('Found a task! dResponse=%s' % (dResponse,)); + return dResponse; + + # Found no suitable task. + return None; + + @staticmethod + def _pickSchedGroup(oTestBoxDataEx, iWorkItem, dIgnoreSchedGroupIds): + """ + Picks the next scheduling group for the given testbox. + """ + if len(oTestBoxDataEx.aoInSchedGroups) == 1: + oSchedGroup = oTestBoxDataEx.aoInSchedGroups[0].oSchedGroup; + if oSchedGroup.fEnabled \ + and oSchedGroup.idBuildSrc is not None \ + and oSchedGroup.idSchedGroup not in dIgnoreSchedGroupIds: + return (oSchedGroup, 0); + iWorkItem = 0; + + elif oTestBoxDataEx.aoInSchedGroups: + # Construct priority table of currently enabled scheduling groups. + aaoList1 = []; + for oInGroup in oTestBoxDataEx.aoInSchedGroups: + oSchedGroup = oInGroup.oSchedGroup; + if oSchedGroup.fEnabled and oSchedGroup.idBuildSrc is not None: + iSchedPriority = oInGroup.iSchedPriority; + if iSchedPriority > 31: # paranoia + iSchedPriority = 31; + elif iSchedPriority < 0: # paranoia + iSchedPriority = 0; + + for iSchedPriority in xrange(min(iSchedPriority, len(aaoList1))): + aaoList1[iSchedPriority].append(oSchedGroup); + while len(aaoList1) <= iSchedPriority: + aaoList1.append([oSchedGroup,]); + + # Flatten it into a single list, mixing the priorities a little so it doesn't + # take forever before low priority stuff is executed. + aoFlat = []; + iLo = 0; + iHi = len(aaoList1) - 1; + while iHi >= iLo: + aoFlat += aaoList1[iHi]; + if iLo < iHi: + aoFlat += aaoList1[iLo]; + iLo += 1; + iHi -= 1; + + # Pick the next one. + cLeft = len(aoFlat); + while cLeft > 0: + cLeft -= 1; + iWorkItem += 1; + if iWorkItem >= len(aoFlat) or iWorkItem < 0: + iWorkItem = 0; + if aoFlat[iWorkItem].idSchedGroup not in dIgnoreSchedGroupIds: + return (aoFlat[iWorkItem], iWorkItem); + else: + iWorkItem = 0; + + # No active group. + return (None, iWorkItem); + + @staticmethod + def scheduleNewTask(oDb, oTestBoxData, iWorkItem, sBaseUrl, iVerbosity = 0): + # type: (TMDatabaseConnection, TestBoxData, int, str, int) -> None + """ + Schedules a new task for a testbox. + """ + oTBStatusLogic = TestBoxStatusLogic(oDb); + + try: + # + # To avoid concurrency issues in SchedQueues we lock all the rows + # related to our scheduling queue. Also, since this is a very + # expensive operation we lock the testbox status row to fend of + # repeated retires by faulty testbox scripts. + # + tsSecStart = utils.timestampSecond(); + oDb.rollback(); + oDb.begin(); + oDb.execute('SELECT idTestBox FROM TestBoxStatuses WHERE idTestBox = %s FOR UPDATE NOWAIT' + % (oTestBoxData.idTestBox,)); + oDb.execute('SELECT SchedQueues.idSchedGroup\n' + ' FROM SchedQueues, TestBoxesInSchedGroups\n' + 'WHERE TestBoxesInSchedGroups.idTestBox = %s\n' + ' AND TestBoxesInSchedGroups.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND TestBoxesInSchedGroups.idSchedGroup = SchedQueues.idSchedGroup\n' + ' FOR UPDATE' + % (oTestBoxData.idTestBox,)); + + # We need the current timestamp. + tsNow = oDb.getCurrentTimestamp(); + + # Re-read the testbox data with scheduling group relations. + oTestBoxDataEx = TestBoxDataEx().initFromDbWithId(oDb, oTestBoxData.idTestBox, tsNow); + if oTestBoxDataEx.fEnabled \ + and oTestBoxDataEx.idGenTestBox == oTestBoxData.idGenTestBox: + + # We may have to skip scheduling groups that are out of work (e.g. 'No build'). + iInitialWorkItem = iWorkItem; + dIgnoreSchedGroupIds = {}; + while True: + # Now, pick the scheduling group. + (oSchedGroup, iWorkItem) = SchedulerBase._pickSchedGroup(oTestBoxDataEx, iWorkItem, dIgnoreSchedGroupIds); + if oSchedGroup is None: + break; + assert oSchedGroup.fEnabled and oSchedGroup.idBuildSrc is not None; + + # Instantiate the specified scheduler and let it do the rest. + oScheduler = SchedulerBase._instantiate(oDb, oSchedGroup, iVerbosity, tsSecStart); + dResponse = oScheduler.scheduleNewTaskWorker(oTestBoxDataEx, tsNow, sBaseUrl); + if dResponse is not None: + oTBStatusLogic.updateWorkItem(oTestBoxDataEx.idTestBox, iWorkItem); + oDb.commit(); + return dResponse; + + # Check out the next work item? + if oScheduler.getElapsedSecs() > config.g_kcSecMaxNewTask: + break; + dIgnoreSchedGroupIds[oSchedGroup.idSchedGroup] = oSchedGroup; + + # No luck, but best if we update the work item if we've made progress. + # Note! In case of a config.g_kcSecMaxNewTask timeout, this may accidentally skip + # a work item with actually work to do. But that's a small price to pay. + if iWorkItem != iInitialWorkItem: + oTBStatusLogic.updateWorkItem(oTestBoxDataEx.idTestBox, iWorkItem); + oDb.commit(); + return None; + except: + oDb.rollback(); + raise; + + # Not enabled, rollback and return no task. + oDb.rollback(); + return None; + + @staticmethod + def tryCancelGangGathering(oDb, oStatusData): + """ + Try canceling a gang gathering. + + Returns True if successfully cancelled. + Returns False if not (someone raced us to the SchedQueue table). + + Note! oStatusData is re-initialized. + """ + assert oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangGathering; + try: + # + # Lock the tables we're updating so we don't run into concurrency + # issues (we're racing both scheduleNewTask and other callers of + # this method). + # + oDb.rollback(); + oDb.begin(); + oDb.execute('LOCK TABLE TestBoxStatuses, SchedQueues IN EXCLUSIVE MODE'); + + # + # Re-read the testbox data and check that we're still in the same state. + # + oStatusData.initFromDbWithId(oDb, oStatusData.idTestBox); + if oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangGathering: + # + # Get the leader thru the test set and change the state of the whole gang. + # + oTestSetData = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet); + + oTBStatusLogic = TestBoxStatusLogic(oDb); + oTBStatusLogic.updateGangStatus(oTestSetData.idTestSetGangLeader, + TestBoxStatusData.ksTestBoxState_GangGatheringTimedOut, + fCommit = False); + + # + # Move the scheduling queue item to the end. + # + oDb.execute('SELECT *\n' + 'FROM SchedQueues\n' + 'WHERE idTestSetGangLeader = %s\n' + , (oTestSetData.idTestSetGangLeader,) ); + oTask = SchedQueueData().initFromDbRow(oDb.fetchOne()); + oTestEx = TestCaseArgsDataEx().initFromDbWithGenIdEx(oDb, oTask.idGenTestCaseArgs, + tsConfigEff = oTask.tsConfig, + tsRsrcEff = oTask.tsConfig); + oDb.execute('UPDATE SchedQueues\n' + ' SET idItem = NEXTVAL(\'SchedQueueItemIdSeq\'),\n' + ' idTestSetGangLeader = NULL,\n' + ' cMissingGangMembers = %s\n' + 'WHERE idItem = %s\n' + , (oTestEx.cGangMembers, oTask.idItem,) ); + + oDb.commit(); + return True; + + if oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangGatheringTimedOut: + oDb.rollback(); + return True; + except: + oDb.rollback(); + raise; + + # Not enabled, rollback and return no task. + oDb.rollback(); + return False; + + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +class SchedQueueDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [SchedQueueData(),]; + +if __name__ == '__main__': + unittest.main(); + # not reached. + diff --git a/src/VBox/ValidationKit/testmanager/core/schedulerbeci.py b/src/VBox/ValidationKit/testmanager/core/schedulerbeci.py new file mode 100755 index 00000000..f33a4dcc --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/schedulerbeci.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- +# $Id: schedulerbeci.py $ + +""" +Test Manager - Best-Effort-Continuous-Integration (BECI) scheduler. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Validation Kit imports. +from testmanager.core.schedulerbase import SchedulerBase, SchedQueueData; + + +class SchdulerBeci(SchedulerBase): # pylint: disable=too-few-public-methods + """ + The best-effort-continuous-integration scheduler, BECI for short. + """ + + def __init__(self, oDb, oSchedGrpData, iVerbosity, tsSecStart): + SchedulerBase.__init__(self, oDb, oSchedGrpData, iVerbosity, tsSecStart); + + def _recreateQueueItems(self, oData): + # + # Prepare the input data for the loop below. We compress the priority + # to reduce the number of loops we need to executes below. + # + # Note! For BECI test group priority only applies to the ordering of + # test groups, which has been resolved by the done sorting in the + # base class. + # + iMinPriority = 0x7fff; + iMaxPriority = 0; + for oTestGroup in oData.aoTestGroups: + for oTestCase in oTestGroup.aoTestCases: + iPrio = oTestCase.iSchedPriority; + assert iPrio in range(32); + iPrio = iPrio // 4; + assert iPrio in range(8); + if iPrio > iMaxPriority: + iMaxPriority = iPrio; + if iPrio < iMinPriority: + iMinPriority = iPrio; + + oTestCase.iBeciPrio = iPrio; + oTestCase.iNextVariation = -1; + + assert iMinPriority in range(8); + assert iMaxPriority in range(8); + assert iMinPriority <= iMaxPriority; + + # + # Generate the + # + cMaxItems = len(oData.aoArgsVariations) * 64; + cMaxItems = min(cMaxItems, 1048576); + + aoItems = []; + cNotAtEnd = len(oData.aoTestCases); + while len(aoItems) < cMaxItems: + self.msgDebug('outer loop: %s items' % (len(aoItems),)); + for iPrio in range(iMaxPriority, iMinPriority - 1, -1): + #self.msgDebug('prio loop: %s' % (iPrio,)); + for oTestGroup in oData.aoTestGroups: + #self.msgDebug('testgroup loop: %s' % (oTestGroup,)); + for oTestCase in oTestGroup.aoTestCases: + #self.msgDebug('testcase loop: idTestCase=%s' % (oTestCase.idTestCase,)); + if iPrio <= oTestCase.iBeciPrio and oTestCase.aoArgsVariations: + # Get variation. + iNext = oTestCase.iNextVariation; + if iNext != 0: + if iNext == -1: iNext = 0; + cNotAtEnd -= 1; + oArgsVariation = oTestCase.aoArgsVariations[iNext]; + + # Update next variation. + iNext = (iNext + 1) % len(oTestCase.aoArgsVariations); + cNotAtEnd += iNext != 0; + oTestCase.iNextVariation = iNext; + + # Create queue item and append it. + oItem = SchedQueueData(); + oItem.initFromValues(idSchedGroup = self._oSchedGrpData.idSchedGroup, + idGenTestCaseArgs = oArgsVariation.idGenTestCaseArgs, + idTestGroup = oTestGroup.idTestGroup, + aidTestGroupPreReqs = oTestGroup.aidTestGroupPreReqs, + bmHourlySchedule = oTestGroup.bmHourlySchedule, + cMissingGangMembers = oArgsVariation.cGangMembers, + offQueue = len(aoItems)); + aoItems.append(oItem); + + # Done? + if cNotAtEnd == 0: + self.msgDebug('returns %s items' % (len(aoItems),)); + return aoItems; + return aoItems; + diff --git a/src/VBox/ValidationKit/testmanager/core/systemchangelog.py b/src/VBox/ValidationKit/testmanager/core/systemchangelog.py new file mode 100755 index 00000000..991ff6c9 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/systemchangelog.py @@ -0,0 +1,202 @@ +# -*- coding: utf-8 -*- +# $Id: systemchangelog.py $ + +""" +Test Manager - System changelog compilation. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Validation Kit imports. +from testmanager.core.base import ModelLogicBase; +from testmanager.core.useraccount import UserAccountLogic; +from testmanager.core.systemlog import SystemLogData; + + +class SystemChangelogEntry(object): # pylint: disable=too-many-instance-attributes + """ + System changelog entry. + """ + + def __init__(self, tsEffective, oAuthor, sEvent, idWhat, sDesc): + self.tsEffective = tsEffective; + self.oAuthor = oAuthor; + self.sEvent = sEvent; + self.idWhat = idWhat; + self.sDesc = sDesc; + + +class SystemChangelogLogic(ModelLogicBase): + """ + System changelog compilation logic. + """ + + ## @name What kind of change. + ## @{ + ksWhat_TestBox = 'chlog::TestBox'; + ksWhat_TestCase = 'chlog::TestCase'; + ksWhat_Blacklisting = 'chlog::Blacklisting'; + ksWhat_Build = 'chlog::Build'; + ksWhat_BuildSource = 'chlog::BuildSource'; + ksWhat_FailureCategory = 'chlog::FailureCategory'; + ksWhat_FailureReason = 'chlog::FailureReason'; + ksWhat_GlobalRsrc = 'chlog::GlobalRsrc'; + ksWhat_SchedGroup = 'chlog::SchedGroup'; + ksWhat_TestGroup = 'chlog::TestGroup'; + ksWhat_User = 'chlog::User'; + ksWhat_TestResult = 'chlog::TestResult'; + ## @} + + ## Mapping a changelog entry kind to a table, key and clue. + kdWhatToTable = dict({ # pylint: disable=star-args + ksWhat_TestBox: ( 'TestBoxes', 'idTestBox', None, ), + ksWhat_TestCase: ( 'TestCasees', 'idTestCase', None, ), + ksWhat_Blacklisting: ( 'Blacklist', 'idBlacklisting', None, ), + ksWhat_Build: ( 'Builds', 'idBuild', None, ), + ksWhat_BuildSource: ( 'BuildSources', 'idBuildSrc', None, ), + ksWhat_FailureCategory: ( 'FailureCategories', 'idFailureCategory', None, ), + ksWhat_FailureReason: ( 'FailureReasons', 'idFailureReason', None, ), + ksWhat_GlobalRsrc: ( 'GlobalResources', 'idGlobalRsrc', None, ), + ksWhat_SchedGroup: ( 'SchedGroups', 'idSchedGroup', None, ), + ksWhat_TestGroup: ( 'TestGroups', 'idTestGroup', None, ), + ksWhat_User: ( 'Users', 'idUser', None, ), + ksWhat_TestResult: ( 'TestResults', 'idTestResult', None, ), + }, **{sEvent: ( 'SystemLog', 'tsCreated', 'TimestampId', ) for sEvent in SystemLogData.kasEvents}); + + ## The table key is the effective timestamp. (Can't be used above for some weird scoping reason.) + ksClue_TimestampId = 'TimestampId'; + + ## @todo move to config.py? + ksVSheriffLoginName = 'vsheriff'; + + + ## @name for kaasChangelogTables + ## @internal + ## @{ + ksTweak_None = ''; + ksTweak_NotNullAuthor = 'uidAuthorNotNull'; + ksTweak_NotNullAuthorOrVSheriff = 'uidAuthorNotNullOrVSheriff'; + ## @} + + ## @internal + kaasChangelogTables = ( + # [0]: change name, [1]: Table name, [2]: key column, [3]:later, [4]: tweak + ( ksWhat_TestBox, 'TestBoxes', 'idTestBox', None, ksTweak_NotNullAuthor, ), + ( ksWhat_TestBox, 'TestBoxesInSchedGroups', 'idTestBox', None, ksTweak_None, ), + ( ksWhat_TestCase, 'TestCases', 'idTestCase', None, ksTweak_None, ), + ( ksWhat_TestCase, 'TestCaseArgs', 'idTestCase', None, ksTweak_None, ), + ( ksWhat_TestCase, 'TestCaseDeps', 'idTestCase', None, ksTweak_None, ), + ( ksWhat_TestCase, 'TestCaseGlobalRsrcDeps', 'idTestCase', None, ksTweak_None, ), + ( ksWhat_Blacklisting, 'BuildBlacklist', 'idBlacklisting', None, ksTweak_None, ), + ( ksWhat_Build, 'Builds', 'idBuild', None, ksTweak_NotNullAuthor, ), + ( ksWhat_BuildSource, 'BuildSources', 'idBuildSrc', None, ksTweak_None, ), + ( ksWhat_FailureCategory, 'FailureCategories', 'idFailureCategory', None, ksTweak_None, ), + ( ksWhat_FailureReason, 'FailureReasons', 'idFailureReason', None, ksTweak_None, ), + ( ksWhat_GlobalRsrc, 'GlobalResources', 'idGlobalRsrc', None, ksTweak_None, ), + ( ksWhat_SchedGroup, 'SchedGroups', 'idSchedGroup', None, ksTweak_None, ), + ( ksWhat_SchedGroup, 'SchedGroupMembers', 'idSchedGroup', None, ksTweak_None, ), + ( ksWhat_TestGroup, 'TestGroups', 'idTestGroup', None, ksTweak_None, ), + ( ksWhat_TestGroup, 'TestGroupMembers', 'idTestGroup', None, ksTweak_None, ), + ( ksWhat_User, 'Users', 'uid', None, ksTweak_None, ), + ( ksWhat_TestResult, 'TestResultFailures', 'idTestResult', None, ksTweak_NotNullAuthorOrVSheriff, ), + ); + + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb); + + + def fetchForListingEx(self, iStart, cMaxRows, tsNow, cDaysBack, aiSortColumns = None): + """ + Fetches SystemLog entries. + + Returns an array (list) of SystemLogData items, empty list if none. + Raises exception on error. + """ + _ = aiSortColumns; + + # + # Construct the query. + # + oUserAccountLogic = UserAccountLogic(self._oDb); + oVSheriff = oUserAccountLogic.tryFetchAccountByLoginName(self.ksVSheriffLoginName); + uidVSheriff = oVSheriff.uid if oVSheriff is not None else -1; + + if tsNow is None: + sWhereTime = self._oDb.formatBindArgs(' WHERE tsEffective >= CURRENT_TIMESTAMP - \'%s days\'::interval\n', + (cDaysBack,)); + else: + sWhereTime = self._oDb.formatBindArgs(' WHERE tsEffective >= (%s::timestamptz - \'%s days\'::interval)\n' + ' AND tsEffective <= %s\n', + (tsNow, cDaysBack, tsNow)); + + # Special entry for the system log. + sQuery = '(\n' + sQuery += ' SELECT NULL AS uidAuthor,\n'; + sQuery += ' tsCreated AS tsEffective,\n'; + sQuery += ' sEvent AS sEvent,\n'; + sQuery += ' NULL AS idWhat,\n'; + sQuery += ' sLogText AS sDesc\n'; + sQuery += ' FROM SystemLog\n'; + sQuery += sWhereTime.replace('tsEffective', 'tsCreated'); + sQuery += ' ORDER BY tsCreated DESC\n' + sQuery += ')' + + for asEntry in self.kaasChangelogTables: + sQuery += ' UNION (\n' + sQuery += ' SELECT uidAuthor, tsEffective, \'' + asEntry[0] + '\', ' + asEntry[2] + ', \'\'\n'; + sQuery += ' FROM ' + asEntry[1] + '\n' + sQuery += sWhereTime; + if asEntry[4] == self.ksTweak_NotNullAuthor or asEntry[4] == self.ksTweak_NotNullAuthorOrVSheriff: + sQuery += ' AND uidAuthor IS NOT NULL\n'; + if asEntry[4] == self.ksTweak_NotNullAuthorOrVSheriff: + sQuery += ' AND uidAuthor <> %u\n' % (uidVSheriff,); + sQuery += ' ORDER BY tsEffective DESC\n' + sQuery += ')'; + sQuery += ' ORDER BY 2 DESC\n'; + sQuery += ' LIMIT %u OFFSET %u\n' % (cMaxRows, iStart, ); + + + # + # Execute the query and construct the return data. + # + self._oDb.execute(sQuery); + aoRows = []; + for aoRow in self._oDb.fetchAll(): + aoRows.append(SystemChangelogEntry(aoRow[1], oUserAccountLogic.cachedLookup(aoRow[0]), + aoRow[2], aoRow[3], aoRow[4])); + + + return aoRows; + diff --git a/src/VBox/ValidationKit/testmanager/core/systemlog.py b/src/VBox/ValidationKit/testmanager/core/systemlog.py new file mode 100755 index 00000000..b0953de4 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/systemlog.py @@ -0,0 +1,186 @@ +# -*- coding: utf-8 -*- +# $Id: systemlog.py $ + +""" +Test Manager - SystemLog. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import unittest; + +# Validation Kit imports. +from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMExceptionBase; + + +class SystemLogData(ModelDataBase): # pylint: disable=too-many-instance-attributes + """ + SystemLog Data. + """ + + ## @name Event Constants + # @{ + ksEvent_CmdNacked = 'CmdNack '; + ksEvent_TestBoxUnknown = 'TBoxUnkn'; + ksEvent_TestSetAbandoned = 'TSetAbdd'; + ksEvent_UserAccountUnknown = 'TAccUnkn'; + ksEvent_XmlResultMalformed = 'XmlRMalf'; + ksEvent_SchedQueueRecreate = 'SchQRecr'; + ## @} + + ## Valid event types. + kasEvents = \ + [ \ + ksEvent_CmdNacked, + ksEvent_TestBoxUnknown, + ksEvent_TestSetAbandoned, + ksEvent_UserAccountUnknown, + ksEvent_XmlResultMalformed, + ksEvent_SchedQueueRecreate, + ]; + + ksParam_tsCreated = 'tsCreated'; + ksParam_sEvent = 'sEvent'; + ksParam_sLogText = 'sLogText'; + + kasValidValues_sEvent = kasEvents; + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.tsCreated = None; + self.sEvent = None; + self.sLogText = None; + + def initFromDbRow(self, aoRow): + """ + Internal worker for initFromDbWithId and initFromDbWithGenId as well as + SystemLogLogic. + """ + + if aoRow is None: + raise TMExceptionBase('SystemLog row not found.'); + + self.tsCreated = aoRow[0]; + self.sEvent = aoRow[1]; + self.sLogText = aoRow[2]; + return self; + + +class SystemLogLogic(ModelLogicBase): + """ + SystemLog logic. + """ + + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb); + + def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None): + """ + Fetches SystemLog entries. + + Returns an array (list) of SystemLogData items, empty list if none. + Raises exception on error. + """ + _ = aiSortColumns; + if tsNow is None: + self._oDb.execute('SELECT *\n' + 'FROM SystemLog\n' + 'ORDER BY tsCreated DESC\n' + 'LIMIT %s OFFSET %s\n', + (cMaxRows, iStart)); + else: + self._oDb.execute('SELECT *\n' + 'FROM SystemLog\n' + 'WHERE tsCreated <= %s\n' + 'ORDER BY tsCreated DESC\n' + 'LIMIT %s OFFSET %s\n', + (tsNow, cMaxRows, iStart)); + aoRows = []; + for _ in range(self._oDb.getRowCount()): + oData = SystemLogData(); + oData.initFromDbRow(self._oDb.fetchOne()); + aoRows.append(oData); + return aoRows; + + def addEntry(self, sEvent, sLogText, cHoursRepeat = 0, fCommit = False): + """ + Adds an entry to the SystemLog table. + Raises exception on problem. + """ + if sEvent not in SystemLogData.kasEvents: + raise TMExceptionBase('Unknown event type "%s"' % (sEvent,)); + + # Check the repeat restriction first. + if cHoursRepeat > 0: + self._oDb.execute('SELECT COUNT(*) as Stuff\n' + 'FROM SystemLog\n' + 'WHERE tsCreated >= (current_timestamp - interval \'%s hours\')\n' + ' AND sEvent = %s\n' + ' AND sLogText = %s\n', + (cHoursRepeat, + sEvent, + sLogText)); + aRow = self._oDb.fetchOne(); + if aRow[0] > 0: + return None; + + # Insert it. + self._oDb.execute('INSERT INTO SystemLog (sEvent, sLogText)\n' + 'VALUES (%s, %s)\n', + (sEvent, sLogText)); + + if fCommit: + self._oDb.commit(); + return True; + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +class SystemLogDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [SystemLogData(),]; + +if __name__ == '__main__': + unittest.main(); + # not reached. + diff --git a/src/VBox/ValidationKit/testmanager/core/testbox.pgsql b/src/VBox/ValidationKit/testmanager/core/testbox.pgsql new file mode 100644 index 00000000..63eb53b6 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/testbox.pgsql @@ -0,0 +1,635 @@ +-- $Id: testbox.pgsql $ +--- @file +-- VBox Test Manager Database Stored Procedures - TestBoxes. +-- + +-- +-- Copyright (C) 2012-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + +-- +-- Old type signatures. +-- +DROP FUNCTION IF EXISTS TestBoxLogic_addEntry(a_uidAuthor INTEGER, + a_ip inet, + a_uuidSystem uuid, + a_sName TEXT, + a_sDescription TEXT, + a_idSchedGroup INTEGER, + a_fEnabled BOOLEAN, + a_enmLomKind LomKind_T, + a_ipLom inet, + a_pctScaleTimeout INTEGER, -- Actually smallint, but default typing fun. + a_sComment TEXT, + a_enmPendingCmd TestBoxCmd_T, + OUT r_idTestBox INTEGER, + OUT r_idGenTestBox INTEGER, + OUT r_tsEffective TIMESTAMP WITH TIME ZONE); +DROP FUNCTION IF EXISTS TestBoxLogic_editEntry(a_uidAuthor INTEGER, + a_idTestBox INTEGER, + a_ip inet, + a_uuidSystem uuid, + a_sName TEXT, + a_sDescription TEXT, + a_idSchedGroup INTEGER, + a_fEnabled BOOLEAN, + a_enmLomKind LomKind_T, + a_ipLom inet, + a_pctScaleTimeout INTEGER, -- Actually smallint, but default typing fun. + a_sComment TEXT, + a_enmPendingCmd TestBoxCmd_T, + OUT r_idGenTestBox INTEGER, + OUT r_tsEffective TIMESTAMP WITH TIME ZONE); +DROP FUNCTION IF EXISTS TestBoxLogic_removeEntry(INTEGER, INTEGER, BOOLEAN); +DROP FUNCTION IF EXISTS TestBoxLogic_addGroupEntry(a_uidAuthor INTEGER, + a_idTestBox INTEGER, + a_idSchedGroup INTEGER, + a_iSchedPriority INTEGER, + OUT r_tsEffective TIMESTAMP WITH TIME ZONE); +DROP FUNCTION IF EXISTS TestBoxLogic_editGroupEntry(a_uidAuthor INTEGER, + a_idTestBox INTEGER, + a_idSchedGroup INTEGER, + a_iSchedPriority INTEGER, + OUT r_tsEffective INTEGER); + + +--- +-- Checks if the test box name is unique, ignoring a_idTestCaseIgnore. +-- Raises exception if duplicates are found. +-- +-- @internal +-- +CREATE OR REPLACE FUNCTION TestBoxLogic_checkUniqueName(a_sName TEXT, a_idTestBoxIgnore INTEGER) + RETURNS VOID AS $$ + DECLARE + v_cRows INTEGER; + BEGIN + SELECT COUNT(*) INTO v_cRows + FROM TestBoxes + WHERE sName = a_sName + AND tsExpire = 'infinity'::TIMESTAMP + AND idTestBox <> a_idTestBoxIgnore; + IF v_cRows <> 0 THEN + RAISE EXCEPTION 'Duplicate test box name "%" (% times)', a_sName, v_cRows; + END IF; + END; +$$ LANGUAGE plpgsql; + + +--- +-- Checks that the given scheduling group exists. +-- Raises exception if it doesn't. +-- +-- @internal +-- +CREATE OR REPLACE FUNCTION TestBoxLogic_checkSchedGroupExists(a_idSchedGroup INTEGER) + RETURNS VOID AS $$ + DECLARE + v_cRows INTEGER; + BEGIN + SELECT COUNT(*) INTO v_cRows + FROM SchedGroups + WHERE idSchedGroup = a_idSchedGroup + AND tsExpire = 'infinity'::TIMESTAMP; + IF v_cRows <> 1 THEN + IF v_cRows = 0 THEN + RAISE EXCEPTION 'Scheduling group with ID % was not found', a_idSchedGroup; + END IF; + RAISE EXCEPTION 'Integrity error in SchedGroups: % current rows with idSchedGroup=%', v_cRows, a_idSchedGroup; + END IF; + END; +$$ LANGUAGE plpgsql; + + +--- +-- Checks that the given testbxo + scheduling group pair does not currently exists. +-- Raises exception if it does. +-- +-- @internal +-- +CREATE OR REPLACE FUNCTION TestBoxLogic_checkTestBoxNotInSchedGroup(a_idTestBox INTEGER, a_idSchedGroup INTEGER) + RETURNS VOID AS $$ + DECLARE + v_cRows INTEGER; + BEGIN + SELECT COUNT(*) INTO v_cRows + FROM TestBoxesInSchedGroups + WHERE idTestBox = a_idTestBox + AND idSchedGroup = a_idSchedGroup + AND tsExpire = 'infinity'::TIMESTAMP; + IF v_cRows <> 0 THEN + RAISE EXCEPTION 'TestBox % is already a member of scheduling group %', a_idTestBox, a_idSchedGroup; + END IF; + END; +$$ LANGUAGE plpgsql; + + +--- +-- Historize a row. +-- @internal +-- +CREATE OR REPLACE FUNCTION TestBoxLogic_historizeEntry(a_idGenTestBox INTEGER, a_tsExpire TIMESTAMP WITH TIME ZONE) + RETURNS VOID AS $$ + DECLARE + v_cUpdatedRows INTEGER; + BEGIN + UPDATE TestBoxes + SET tsExpire = a_tsExpire + WHERE idGenTestBox = a_idGenTestBox + AND tsExpire = 'infinity'::TIMESTAMP; + GET DIAGNOSTICS v_cUpdatedRows = ROW_COUNT; + IF v_cUpdatedRows <> 1 THEN + IF v_cUpdatedRows = 0 THEN + RAISE EXCEPTION 'Test box generation ID % is no longer valid', a_idGenTestBox; + END IF; + RAISE EXCEPTION 'Integrity error in TestBoxes: % current rows with idGenTestBox=%', v_cUpdatedRows, a_idGenTestBox; + END IF; + END; +$$ LANGUAGE plpgsql; + + +--- +-- Historize a in-scheduling-group row. +-- @internal +-- +CREATE OR REPLACE FUNCTION TestBoxLogic_historizeGroupEntry(a_idTestBox INTEGER, + a_idSchedGroup INTEGER, + a_tsExpire TIMESTAMP WITH TIME ZONE) + RETURNS VOID AS $$ + DECLARE + v_cUpdatedRows INTEGER; + BEGIN + UPDATE TestBoxesInSchedGroups + SET tsExpire = a_tsExpire + WHERE idTestBox = a_idTestBox + AND idSchedGroup = a_idSchedGroup + AND tsExpire = 'infinity'::TIMESTAMP; + GET DIAGNOSTICS v_cUpdatedRows = ROW_COUNT; + IF v_cUpdatedRows <> 1 THEN + IF v_cUpdatedRows = 0 THEN + RAISE EXCEPTION 'TestBox ID % / SchedGroup ID % is no longer a valid combination', a_idTestBox, a_idSchedGroup; + END IF; + RAISE EXCEPTION 'Integrity error in TestBoxesInSchedGroups: % current rows for % / %', + v_cUpdatedRows, a_idTestBox, a_idSchedGroup; + END IF; + END; +$$ LANGUAGE plpgsql; + + +--- +-- Translate string via the string table. +-- +-- @returns NULL if a_sValue is NULL, otherwise a string ID. +-- +CREATE OR REPLACE FUNCTION TestBoxLogic_lookupOrFindString(a_sValue TEXT) + RETURNS INTEGER AS $$ + DECLARE + v_idStr INTEGER; + v_cRows INTEGER; + BEGIN + IF a_sValue IS NULL THEN + RETURN NULL; + END IF; + + SELECT idStr + INTO v_idStr + FROM TestBoxStrTab + WHERE sValue = a_sValue; + GET DIAGNOSTICS v_cRows = ROW_COUNT; + IF v_cRows = 0 THEN + INSERT INTO TestBoxStrTab (sValue) + VALUES (a_sValue) + RETURNING idStr INTO v_idStr; + END IF; + RETURN v_idStr; + END; +$$ LANGUAGE plpgsql; + + +--- +-- Only adds the user settable parts of the row, i.e. not what TestBoxLogic_updateOnSignOn touches. +-- +CREATE OR REPLACE function TestBoxLogic_addEntry(a_uidAuthor INTEGER, + a_ip inet, + a_uuidSystem uuid, + a_sName TEXT, + a_sDescription TEXT, + a_fEnabled BOOLEAN, + a_enmLomKind LomKind_T, + a_ipLom inet, + a_pctScaleTimeout INTEGER, -- Actually smallint, but default typing fun. + a_sComment TEXT, + a_enmPendingCmd TestBoxCmd_T, + OUT r_idTestBox INTEGER, + OUT r_idGenTestBox INTEGER, + OUT r_tsEffective TIMESTAMP WITH TIME ZONE + ) AS $$ + DECLARE + v_idStrDescription INTEGER; + v_idStrComment INTEGER; + BEGIN + PERFORM TestBoxLogic_checkUniqueName(a_sName, -1); + + SELECT TestBoxLogic_lookupOrFindString(a_sDescription) INTO v_idStrDescription; + SELECT TestBoxLogic_lookupOrFindString(a_sComment) INTO v_idStrComment; + + INSERT INTO TestBoxes ( + tsEffective, -- 1 + uidAuthor, -- 2 + ip, -- 3 + uuidSystem, -- 4 + sName, -- 5 + idStrDescription, -- 6 + fEnabled, -- 7 + enmLomKind, -- 8 + ipLom, -- 9 + pctScaleTimeout, -- 10 + idStrComment, -- 11 + enmPendingCmd ) -- 12 + VALUES (CURRENT_TIMESTAMP, -- 1 + a_uidAuthor, -- 2 + a_ip, -- 3 + a_uuidSystem, -- 4 + a_sName, -- 5 + v_idStrDescription, -- 6 + a_fEnabled, -- 7 + a_enmLomKind, -- 8 + a_ipLom, -- 9 + a_pctScaleTimeout, -- 10 + v_idStrComment, -- 11 + a_enmPendingCmd ) -- 12 + RETURNING idTestBox, idGenTestBox, tsEffective INTO r_idTestBox, r_idGenTestBox, r_tsEffective; + END; +$$ LANGUAGE plpgsql; + + +CREATE OR REPLACE function TestBoxLogic_addGroupEntry(a_uidAuthor INTEGER, + a_idTestBox INTEGER, + a_idSchedGroup INTEGER, + a_iSchedPriority INTEGER, + OUT r_tsEffective TIMESTAMP WITH TIME ZONE + ) AS $$ + BEGIN + PERFORM TestBoxLogic_checkSchedGroupExists(a_idSchedGroup); + PERFORM TestBoxLogic_checkTestBoxNotInSchedGroup(a_idTestBox, a_idSchedGroup); + + INSERT INTO TestBoxesInSchedGroups ( + idTestBox, + idSchedGroup, + tsEffective, + tsExpire, + uidAuthor, + iSchedPriority) + VALUES (a_idTestBox, + a_idSchedGroup, + CURRENT_TIMESTAMP, + 'infinity'::TIMESTAMP, + a_uidAuthor, + a_iSchedPriority) + RETURNING tsEffective INTO r_tsEffective; + END; +$$ LANGUAGE plpgsql; + + +--- +-- Only adds the user settable parts of the row, i.e. not what TestBoxLogic_updateOnSignOn touches. +-- +CREATE OR REPLACE function TestBoxLogic_editEntry(a_uidAuthor INTEGER, + a_idTestBox INTEGER, + a_ip inet, + a_uuidSystem uuid, + a_sName TEXT, + a_sDescription TEXT, + a_fEnabled BOOLEAN, + a_enmLomKind LomKind_T, + a_ipLom inet, + a_pctScaleTimeout INTEGER, -- Actually smallint, but default typing fun. + a_sComment TEXT, + a_enmPendingCmd TestBoxCmd_T, + OUT r_idGenTestBox INTEGER, + OUT r_tsEffective TIMESTAMP WITH TIME ZONE + ) AS $$ + DECLARE + v_Row TestBoxes%ROWTYPE; + v_idStrDescription INTEGER; + v_idStrComment INTEGER; + BEGIN + PERFORM TestBoxLogic_checkUniqueName(a_sName, a_idTestBox); + + SELECT TestBoxLogic_lookupOrFindString(a_sDescription) INTO v_idStrDescription; + SELECT TestBoxLogic_lookupOrFindString(a_sComment) INTO v_idStrComment; + + -- Fetch and historize the current row - there must be one. + UPDATE TestBoxes + SET tsExpire = CURRENT_TIMESTAMP + WHERE idTestBox = a_idTestBox + AND tsExpire = 'infinity'::TIMESTAMP + RETURNING * INTO STRICT v_Row; + + -- Modify the row with the new data. + v_Row.uidAuthor := a_uidAuthor; + v_Row.ip := a_ip; + v_Row.uuidSystem := a_uuidSystem; + v_Row.sName := a_sName; + v_Row.idStrDescription := v_idStrDescription; + v_Row.fEnabled := a_fEnabled; + v_Row.enmLomKind := a_enmLomKind; + v_Row.ipLom := a_ipLom; + v_Row.pctScaleTimeout := a_pctScaleTimeout; + v_Row.idStrComment := v_idStrComment; + v_Row.enmPendingCmd := a_enmPendingCmd; + v_Row.tsEffective := v_Row.tsExpire; + r_tsEffective := v_Row.tsExpire; + v_Row.tsExpire := 'infinity'::TIMESTAMP; + + -- Get a new generation ID. + SELECT NEXTVAL('TestBoxGenIdSeq') INTO v_Row.idGenTestBox; + r_idGenTestBox := v_Row.idGenTestBox; + + -- Insert the modified row. + INSERT INTO TestBoxes VALUES (v_Row.*); + END; +$$ LANGUAGE plpgsql; + + +CREATE OR REPLACE function TestBoxLogic_editGroupEntry(a_uidAuthor INTEGER, + a_idTestBox INTEGER, + a_idSchedGroup INTEGER, + a_iSchedPriority INTEGER, + OUT r_tsEffective TIMESTAMP WITH TIME ZONE + ) AS $$ + DECLARE + v_Row TestBoxesInSchedGroups%ROWTYPE; + v_idStrDescription INTEGER; + v_idStrComment INTEGER; + BEGIN + PERFORM TestBoxLogic_checkSchedGroupExists(a_idSchedGroup); + + -- Fetch and historize the current row - there must be one. + UPDATE TestBoxesInSchedGroups + SET tsExpire = CURRENT_TIMESTAMP + WHERE idTestBox = a_idTestBox + AND idSchedGroup = a_idSchedGroup + AND tsExpire = 'infinity'::TIMESTAMP + RETURNING * INTO STRICT v_Row; + + -- Modify the row with the new data. + v_Row.uidAuthor := a_uidAuthor; + v_Row.iSchedPriority := a_iSchedPriority; + v_Row.tsEffective := v_Row.tsExpire; + r_tsEffective := v_Row.tsExpire; + v_Row.tsExpire := 'infinity'::TIMESTAMP; + + -- Insert the modified row. + INSERT INTO TestBoxesInSchedGroups VALUES (v_Row.*); + END; +$$ LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION TestBoxLogic_removeEntry(a_uidAuthor INTEGER, a_idTestBox INTEGER, a_fCascade BOOLEAN) + RETURNS VOID AS $$ + DECLARE + v_Row TestBoxes%ROWTYPE; + v_tsEffective TIMESTAMP WITH TIME ZONE; + v_Rec RECORD; + v_sErrors TEXT; + BEGIN + -- + -- Check preconditions. + -- + IF a_fCascade <> TRUE THEN + -- @todo implement checks which throws useful exceptions. + ELSE + RAISE EXCEPTION 'CASCADE test box deletion is not implemented'; + END IF; + + -- + -- Delete all current groups, skipping history since we're also deleting the testbox. + -- + UPDATE TestBoxesInSchedGroups + SET tsExpire = CURRENT_TIMESTAMP + WHERE idTestBox = a_idTestBox + AND tsExpire = 'infinity'::TIMESTAMP; + + -- + -- To preserve the information about who deleted the record, we try to + -- add a dummy record which expires immediately. I say try because of + -- the primary key, we must let the new record be valid for 1 us. :-( + -- + SELECT * INTO STRICT v_Row + FROM TestBoxes + WHERE idTestBox = a_idTestBox + AND tsExpire = 'infinity'::TIMESTAMP; + + v_tsEffective := CURRENT_TIMESTAMP - INTERVAL '1 microsecond'; + IF v_Row.tsEffective < v_tsEffective THEN + PERFORM TestBoxLogic_historizeEntry(v_Row.idGenTestBox, v_tsEffective); + + v_Row.tsEffective := v_tsEffective; + v_Row.tsExpire := CURRENT_TIMESTAMP; + v_Row.uidAuthor := a_uidAuthor; + SELECT NEXTVAL('TestBoxGenIdSeq') INTO v_Row.idGenTestBox; + INSERT INTO TestBoxes VALUES (v_Row.*); + ELSE + PERFORM TestBoxLogic_historizeEntry(v_Row.idGenTestBox, CURRENT_TIMESTAMP); + END IF; + + EXCEPTION + WHEN NO_DATA_FOUND THEN + RAISE EXCEPTION 'Test box with ID % does not currently exist', a_idTestBox; + WHEN TOO_MANY_ROWS THEN + RAISE EXCEPTION 'Integrity error in TestBoxes: Too many current rows for %', a_idTestBox; + END; +$$ LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION TestBoxLogic_removeGroupEntry(a_uidAuthor INTEGER, a_idTestBox INTEGER, a_idSchedGroup INTEGER) + RETURNS VOID AS $$ + DECLARE + v_Row TestBoxesInSchedGroups%ROWTYPE; + v_tsEffective TIMESTAMP WITH TIME ZONE; + BEGIN + -- + -- To preserve the information about who deleted the record, we try to + -- add a dummy record which expires immediately. I say try because of + -- the primary key, we must let the new record be valid for 1 us. :-( + -- + SELECT * INTO STRICT v_Row + FROM TestBoxesInSchedGroups + WHERE idTestBox = a_idTestBox + AND idSchedGroup = a_idSchedGroup + AND tsExpire = 'infinity'::TIMESTAMP; + + v_tsEffective := CURRENT_TIMESTAMP - INTERVAL '1 microsecond'; + IF v_Row.tsEffective < v_tsEffective THEN + PERFORM TestBoxLogic_historizeGroupEntry(a_idTestBox, a_idSchedGroup, v_tsEffective); + + v_Row.tsEffective := v_tsEffective; + v_Row.tsExpire := CURRENT_TIMESTAMP; + v_Row.uidAuthor := a_uidAuthor; + INSERT INTO TestBoxesInSchedGroups VALUES (v_Row.*); + ELSE + PERFORM TestBoxLogic_historizeGroupEntry(a_idTestBox, a_idSchedGroup, CURRENT_TIMESTAMP); + END IF; + + EXCEPTION + WHEN NO_DATA_FOUND THEN + RAISE EXCEPTION 'TestBox #% does is not currently a member of scheduling group #%', a_idTestBox, a_idSchedGroup; + WHEN TOO_MANY_ROWS THEN + RAISE EXCEPTION 'Integrity error in TestBoxesInSchedGroups: Too many current rows for % / %', + a_idTestBox, a_idSchedGroup; + END; +$$ LANGUAGE plpgsql; + + +--- +-- Sign on update +-- +CREATE OR REPLACE function TestBoxLogic_updateOnSignOn(a_idTestBox INTEGER, + a_ip inet, + a_sOs TEXT, + a_sOsVersion TEXT, + a_sCpuVendor TEXT, + a_sCpuArch TEXT, + a_sCpuName TEXT, + a_lCpuRevision bigint, + a_cCpus INTEGER, -- Actually smallint, but default typing fun. + a_fCpuHwVirt boolean, + a_fCpuNestedPaging boolean, + a_fCpu64BitGuest boolean, + a_fChipsetIoMmu boolean, + a_fRawMode boolean, + a_cMbMemory bigint, + a_cMbScratch bigint, + a_sReport TEXT, + a_iTestBoxScriptRev INTEGER, + a_iPythonHexVersion INTEGER, + OUT r_idGenTestBox INTEGER + ) AS $$ + DECLARE + v_Row TestBoxes%ROWTYPE; + v_idStrOs INTEGER; + v_idStrOsVersion INTEGER; + v_idStrCpuVendor INTEGER; + v_idStrCpuArch INTEGER; + v_idStrCpuName INTEGER; + v_idStrReport INTEGER; + BEGIN + SELECT TestBoxLogic_lookupOrFindString(a_sOs) INTO v_idStrOs; + SELECT TestBoxLogic_lookupOrFindString(a_sOsVersion) INTO v_idStrOsVersion; + SELECT TestBoxLogic_lookupOrFindString(a_sCpuVendor) INTO v_idStrCpuVendor; + SELECT TestBoxLogic_lookupOrFindString(a_sCpuArch) INTO v_idStrCpuArch; + SELECT TestBoxLogic_lookupOrFindString(a_sCpuName) INTO v_idStrCpuName; + SELECT TestBoxLogic_lookupOrFindString(a_sReport) INTO v_idStrReport; + + -- Fetch and historize the current row - there must be one. + UPDATE TestBoxes + SET tsExpire = CURRENT_TIMESTAMP + WHERE idTestBox = a_idTestBox + AND tsExpire = 'infinity'::TIMESTAMP + RETURNING * INTO STRICT v_Row; + + -- Modify the row with the new data. + v_Row.uidAuthor := NULL; + v_Row.ip := a_ip; + v_Row.idStrOs := v_idStrOs; + v_Row.idStrOsVersion := v_idStrOsVersion; + v_Row.idStrCpuVendor := v_idStrCpuVendor; + v_Row.idStrCpuArch := v_idStrCpuArch; + v_Row.idStrCpuName := v_idStrCpuName; + v_Row.lCpuRevision := a_lCpuRevision; + v_Row.cCpus := a_cCpus; + v_Row.fCpuHwVirt := a_fCpuHwVirt; + v_Row.fCpuNestedPaging := a_fCpuNestedPaging; + v_Row.fCpu64BitGuest := a_fCpu64BitGuest; + v_Row.fChipsetIoMmu := a_fChipsetIoMmu; + v_Row.fRawMode := a_fRawMode; + v_Row.cMbMemory := a_cMbMemory; + v_Row.cMbScratch := a_cMbScratch; + v_Row.idStrReport := v_idStrReport; + v_Row.iTestBoxScriptRev := a_iTestBoxScriptRev; + v_Row.iPythonHexVersion := a_iPythonHexVersion; + v_Row.tsEffective := v_Row.tsExpire; + v_Row.tsExpire := 'infinity'::TIMESTAMP; + + -- Get a new generation ID. + SELECT NEXTVAL('TestBoxGenIdSeq') INTO v_Row.idGenTestBox; + r_idGenTestBox := v_Row.idGenTestBox; + + -- Insert the modified row. + INSERT INTO TestBoxes VALUES (v_Row.*); + END; +$$ LANGUAGE plpgsql; + + +--- +-- Set new command. +-- +CREATE OR REPLACE function TestBoxLogic_setCommand(a_uidAuthor INTEGER, + a_idTestBox INTEGER, + a_enmOldCmd TestBoxCmd_T, + a_enmNewCmd TestBoxCmd_T, + a_sComment TEXT, + OUT r_idGenTestBox INTEGER, + OUT r_tsEffective TIMESTAMP WITH TIME ZONE + ) AS $$ + DECLARE + v_Row TestBoxes%ROWTYPE; + v_idStrComment INTEGER; + BEGIN + SELECT TestBoxLogic_lookupOrFindString(a_sComment) INTO v_idStrComment; + + -- Fetch and historize the current row - there must be one. + UPDATE TestBoxes + SET tsExpire = CURRENT_TIMESTAMP + WHERE idTestBox = a_idTestBox + AND tsExpire = 'infinity'::TIMESTAMP + AND enmPendingCmd = a_enmOldCmd + RETURNING * INTO STRICT v_Row; + + -- Modify the row with the new data. + v_Row.enmPendingCmd := a_enmNewCmd; + IF v_idStrComment IS NOT NULL THEN + v_Row.idStrComment := v_idStrComment; + END IF; + v_Row.tsEffective := v_Row.tsExpire; + r_tsEffective := v_Row.tsExpire; + v_Row.tsExpire := 'infinity'::TIMESTAMP; + + -- Get a new generation ID. + SELECT NEXTVAL('TestBoxGenIdSeq') INTO v_Row.idGenTestBox; + r_idGenTestBox := v_Row.idGenTestBox; + + -- Insert the modified row. + INSERT INTO TestBoxes VALUES (v_Row.*); + END; +$$ LANGUAGE plpgsql; + diff --git a/src/VBox/ValidationKit/testmanager/core/testbox.py b/src/VBox/ValidationKit/testmanager/core/testbox.py new file mode 100755 index 00000000..49dff40e --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/testbox.py @@ -0,0 +1,1286 @@ +# -*- coding: utf-8 -*- +# $Id: testbox.py $ + +""" +Test Manager - TestBox. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import copy; +import sys; +import unittest; + +# Validation Kit imports. +from testmanager.core import db; +from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMInFligthCollision, \ + TMInvalidData, TMTooManyRows, TMRowNotFound, \ + ChangeLogEntry, AttributeChangeEntry, AttributeChangeEntryPre; +from testmanager.core.useraccount import UserAccountLogic; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + xrange = range; # pylint: disable=redefined-builtin,invalid-name + + +class TestBoxInSchedGroupData(ModelDataBase): + """ + TestBox in SchedGroup data. + """ + + ksParam_idTestBox = 'TestBoxInSchedGroup_idTestBox'; + ksParam_idSchedGroup = 'TestBoxInSchedGroup_idSchedGroup'; + ksParam_tsEffective = 'TestBoxInSchedGroup_tsEffective'; + ksParam_tsExpire = 'TestBoxInSchedGroup_tsExpire'; + ksParam_uidAuthor = 'TestBoxInSchedGroup_uidAuthor'; + ksParam_iSchedPriority = 'TestBoxInSchedGroup_iSchedPriority'; + + kasAllowNullAttributes = [ 'tsEffective', 'tsExpire', 'uidAuthor', ] + + kiMin_iSchedPriority = 0; + kiMax_iSchedPriority = 32; + + kcDbColumns = 6; + + def __init__(self): + ModelDataBase.__init__(self); + self.idTestBox = None; + self.idSchedGroup = None; + self.tsEffective = None; + self.tsExpire = None; + self.uidAuthor = None; + self.iSchedPriority = 16; + + def initFromDbRow(self, aoRow): + """ + Expecting the result from a query like this: + SELECT * FROM TestBoxesInSchedGroups + """ + if aoRow is None: + raise TMRowNotFound('TestBox/SchedGroup not found.'); + + self.idTestBox = aoRow[0]; + self.idSchedGroup = aoRow[1]; + self.tsEffective = aoRow[2]; + self.tsExpire = aoRow[3]; + self.uidAuthor = aoRow[4]; + self.iSchedPriority = aoRow[5]; + + return self; + +class TestBoxInSchedGroupDataEx(TestBoxInSchedGroupData): + """ + Extended version of TestBoxInSchedGroupData that contains the scheduling group. + """ + + def __init__(self): + TestBoxInSchedGroupData.__init__(self); + self.oSchedGroup = None # type: SchedGroupData + + def initFromDbRowEx(self, aoRow, oDb, tsNow = None, sPeriodBack = None): + """ + Extended version of initFromDbRow that fills in the rest from the database. + """ + from testmanager.core.schedgroup import SchedGroupData; + self.initFromDbRow(aoRow); + self.oSchedGroup = SchedGroupData().initFromDbWithId(oDb, self.idSchedGroup, tsNow, sPeriodBack); + return self; + +class TestBoxDataForSchedGroup(TestBoxInSchedGroupData): + """ + Extended version of TestBoxInSchedGroupData that adds the testbox data (if available). + Used by TestBoxLogic.fetchForSchedGroup + """ + + def __init__(self): + TestBoxInSchedGroupData.__init__(self); + self.oTestBox = None # type: TestBoxData + + def initFromDbRow(self, aoRow): + """ + The row is: TestBoxesInSchedGroups.*, TestBoxesWithStrings.* + """ + TestBoxInSchedGroupData.initFromDbRow(self, aoRow); + if aoRow[self.kcDbColumns]: + self.oTestBox = TestBoxData().initFromDbRow(aoRow[self.kcDbColumns:]); + else: + self.oTestBox = None; + return self; + + def getDataAttributes(self): + asAttributes = TestBoxInSchedGroupData.getDataAttributes(self); + asAttributes.remove('oTestBox'); + return asAttributes; + + def _validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor = ModelDataBase.ksValidateFor_Other): + dErrors = TestBoxInSchedGroupData._validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor); + if self.ksParam_idTestBox not in dErrors: + self.oTestBox = TestBoxData(); + try: + self.oTestBox.initFromDbWithId(oDb, self.idTestBox); + except Exception as oXcpt: + self.oTestBox = TestBoxData() + dErrors[self.ksParam_idTestBox] = str(oXcpt); + return dErrors; + + +# pylint: disable=invalid-name +class TestBoxData(ModelDataBase): # pylint: disable=too-many-instance-attributes + """ + TestBox Data. + """ + + ## LomKind_T + ksLomKind_None = 'none'; + ksLomKind_ILOM = 'ilom'; + ksLomKind_ELOM = 'elom'; + ksLomKind_AppleXserveLom = 'apple-xserver-lom'; + kasLomKindValues = [ ksLomKind_None, ksLomKind_ILOM, ksLomKind_ELOM, ksLomKind_AppleXserveLom]; + kaoLomKindDescs = \ + [ + ( ksLomKind_None, 'None', ''), + ( ksLomKind_ILOM, 'ILOM', ''), + ( ksLomKind_ELOM, 'ELOM', ''), + ( ksLomKind_AppleXserveLom, 'Apple Xserve LOM', ''), + ]; + + + ## TestBoxCmd_T + ksTestBoxCmd_None = 'none'; + ksTestBoxCmd_Abort = 'abort'; + ksTestBoxCmd_Reboot = 'reboot'; + ksTestBoxCmd_Upgrade = 'upgrade'; + ksTestBoxCmd_UpgradeAndReboot = 'upgrade-and-reboot'; + ksTestBoxCmd_Special = 'special'; + kasTestBoxCmdValues = [ ksTestBoxCmd_None, ksTestBoxCmd_Abort, ksTestBoxCmd_Reboot, ksTestBoxCmd_Upgrade, + ksTestBoxCmd_UpgradeAndReboot, ksTestBoxCmd_Special]; + kaoTestBoxCmdDescs = \ + [ + ( ksTestBoxCmd_None, 'None', ''), + ( ksTestBoxCmd_Abort, 'Abort current test', ''), + ( ksTestBoxCmd_Reboot, 'Reboot TestBox', ''), + ( ksTestBoxCmd_Upgrade, 'Upgrade TestBox Script', ''), + ( ksTestBoxCmd_UpgradeAndReboot, 'Upgrade TestBox Script and reboot', ''), + ( ksTestBoxCmd_Special, 'Special (reserved)', ''), + ]; + + + ksIdAttr = 'idTestBox'; + ksIdGenAttr = 'idGenTestBox'; + + ksParam_idTestBox = 'TestBox_idTestBox'; + ksParam_tsEffective = 'TestBox_tsEffective'; + ksParam_tsExpire = 'TestBox_tsExpire'; + ksParam_uidAuthor = 'TestBox_uidAuthor'; + ksParam_idGenTestBox = 'TestBox_idGenTestBox'; + ksParam_ip = 'TestBox_ip'; + ksParam_uuidSystem = 'TestBox_uuidSystem'; + ksParam_sName = 'TestBox_sName'; + ksParam_sDescription = 'TestBox_sDescription'; + ksParam_fEnabled = 'TestBox_fEnabled'; + ksParam_enmLomKind = 'TestBox_enmLomKind'; + ksParam_ipLom = 'TestBox_ipLom'; + ksParam_pctScaleTimeout = 'TestBox_pctScaleTimeout'; + ksParam_sComment = 'TestBox_sComment'; + ksParam_sOs = 'TestBox_sOs'; + ksParam_sOsVersion = 'TestBox_sOsVersion'; + ksParam_sCpuVendor = 'TestBox_sCpuVendor'; + ksParam_sCpuArch = 'TestBox_sCpuArch'; + ksParam_sCpuName = 'TestBox_sCpuName'; + ksParam_lCpuRevision = 'TestBox_lCpuRevision'; + ksParam_cCpus = 'TestBox_cCpus'; + ksParam_fCpuHwVirt = 'TestBox_fCpuHwVirt'; + ksParam_fCpuNestedPaging = 'TestBox_fCpuNestedPaging'; + ksParam_fCpu64BitGuest = 'TestBox_fCpu64BitGuest'; + ksParam_fChipsetIoMmu = 'TestBox_fChipsetIoMmu'; + ksParam_fRawMode = 'TestBox_fRawMode'; + ksParam_cMbMemory = 'TestBox_cMbMemory'; + ksParam_cMbScratch = 'TestBox_cMbScratch'; + ksParam_sReport = 'TestBox_sReport'; + ksParam_iTestBoxScriptRev = 'TestBox_iTestBoxScriptRev'; + ksParam_iPythonHexVersion = 'TestBox_iPythonHexVersion'; + ksParam_enmPendingCmd = 'TestBox_enmPendingCmd'; + + kasInternalAttributes = [ 'idStrDescription', 'idStrComment', 'idStrOs', 'idStrOsVersion', 'idStrCpuVendor', + 'idStrCpuArch', 'idStrCpuName', 'idStrReport', ]; + kasMachineSettableOnly = [ 'sOs', 'sOsVersion', 'sCpuVendor', 'sCpuArch', 'sCpuName', 'lCpuRevision', 'cCpus', + 'fCpuHwVirt', 'fCpuNestedPaging', 'fCpu64BitGuest', 'fChipsetIoMmu', 'fRawMode', + 'cMbMemory', 'cMbScratch', 'sReport', 'iTestBoxScriptRev', 'iPythonHexVersion', ]; + kasAllowNullAttributes = ['idTestBox', 'tsEffective', 'tsExpire', 'uidAuthor', 'idGenTestBox', 'sDescription', + 'ipLom', 'sComment', ] + kasMachineSettableOnly + kasInternalAttributes; + + kasValidValues_enmLomKind = kasLomKindValues; + kasValidValues_enmPendingCmd = kasTestBoxCmdValues; + kiMin_pctScaleTimeout = 11; + kiMax_pctScaleTimeout = 19999; + kcchMax_sReport = 65535; + + kcDbColumns = 40; # including the 7 string joins columns + + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.idTestBox = None; + self.tsEffective = None; + self.tsExpire = None; + self.uidAuthor = None; + self.idGenTestBox = None; + self.ip = None; + self.uuidSystem = None; + self.sName = None; + self.idStrDescription = None; + self.fEnabled = False; + self.enmLomKind = self.ksLomKind_None; + self.ipLom = None; + self.pctScaleTimeout = 100; + self.idStrComment = None; + self.idStrOs = None; + self.idStrOsVersion = None; + self.idStrCpuVendor = None; + self.idStrCpuArch = None; + self.idStrCpuName = None; + self.lCpuRevision = None; + self.cCpus = 1; + self.fCpuHwVirt = False; + self.fCpuNestedPaging = False; + self.fCpu64BitGuest = False; + self.fChipsetIoMmu = False; + self.fRawMode = None; + self.cMbMemory = 1; + self.cMbScratch = 0; + self.idStrReport = None; + self.iTestBoxScriptRev = 0; + self.iPythonHexVersion = 0; + self.enmPendingCmd = self.ksTestBoxCmd_None; + # String table values. + self.sDescription = None; + self.sComment = None; + self.sOs = None; + self.sOsVersion = None; + self.sCpuVendor = None; + self.sCpuArch = None; + self.sCpuName = None; + self.sReport = None; + + def initFromDbRow(self, aoRow): + """ + Internal worker for initFromDbWithId and initFromDbWithGenId as well as + from TestBoxLogic. Expecting the result from a query like this: + SELECT TestBoxesWithStrings.* FROM TestBoxesWithStrings + """ + if aoRow is None: + raise TMRowNotFound('TestBox not found.'); + + self.idTestBox = aoRow[0]; + self.tsEffective = aoRow[1]; + self.tsExpire = aoRow[2]; + self.uidAuthor = aoRow[3]; + self.idGenTestBox = aoRow[4]; + self.ip = aoRow[5]; + self.uuidSystem = aoRow[6]; + self.sName = aoRow[7]; + self.idStrDescription = aoRow[8]; + self.fEnabled = aoRow[9]; + self.enmLomKind = aoRow[10]; + self.ipLom = aoRow[11]; + self.pctScaleTimeout = aoRow[12]; + self.idStrComment = aoRow[13]; + self.idStrOs = aoRow[14]; + self.idStrOsVersion = aoRow[15]; + self.idStrCpuVendor = aoRow[16]; + self.idStrCpuArch = aoRow[17]; + self.idStrCpuName = aoRow[18]; + self.lCpuRevision = aoRow[19]; + self.cCpus = aoRow[20]; + self.fCpuHwVirt = aoRow[21]; + self.fCpuNestedPaging = aoRow[22]; + self.fCpu64BitGuest = aoRow[23]; + self.fChipsetIoMmu = aoRow[24]; + self.fRawMode = aoRow[25]; + self.cMbMemory = aoRow[26]; + self.cMbScratch = aoRow[27]; + self.idStrReport = aoRow[28]; + self.iTestBoxScriptRev = aoRow[29]; + self.iPythonHexVersion = aoRow[30]; + self.enmPendingCmd = aoRow[31]; + + # String table values. + if len(aoRow) > 32: + self.sDescription = aoRow[32]; + self.sComment = aoRow[33]; + self.sOs = aoRow[34]; + self.sOsVersion = aoRow[35]; + self.sCpuVendor = aoRow[36]; + self.sCpuArch = aoRow[37]; + self.sCpuName = aoRow[38]; + self.sReport = aoRow[39]; + + return self; + + def initFromDbWithId(self, oDb, idTestBox, tsNow = None, sPeriodBack = None): + """ + Initialize the object from the database. + """ + oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb, + 'SELECT TestBoxesWithStrings.*\n' + 'FROM TestBoxesWithStrings\n' + 'WHERE idTestBox = %s\n' + , ( idTestBox, ), tsNow, sPeriodBack)); + aoRow = oDb.fetchOne() + if aoRow is None: + raise TMRowNotFound('idTestBox=%s not found (tsNow=%s sPeriodBack=%s)' % (idTestBox, tsNow, sPeriodBack,)); + return self.initFromDbRow(aoRow); + + def initFromDbWithGenId(self, oDb, idGenTestBox, tsNow = None): + """ + Initialize the object from the database. + """ + _ = tsNow; # Only useful for extended data classes. + oDb.execute('SELECT TestBoxesWithStrings.*\n' + 'FROM TestBoxesWithStrings\n' + 'WHERE idGenTestBox = %s\n' + , (idGenTestBox, ) ); + return self.initFromDbRow(oDb.fetchOne()); + + def _validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor = ModelDataBase.ksValidateFor_Other): + # Override to do extra ipLom checks. + dErrors = ModelDataBase._validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor); + if self.ksParam_ipLom not in dErrors \ + and self.ksParam_enmLomKind not in dErrors \ + and self.enmLomKind != self.ksLomKind_None \ + and self.ipLom is None: + dErrors[self.ksParam_ipLom] = 'Light-out-management IP is mandatory and a LOM is selected.' + return dErrors; + + @staticmethod + def formatPythonVersionEx(iPythonHexVersion): + """ Unbuttons the version number and formats it as a version string. """ + if iPythonHexVersion is None: + return 'N/A'; + return 'v%d.%d.%d.%d' \ + % ( iPythonHexVersion >> 24, + (iPythonHexVersion >> 16) & 0xff, + (iPythonHexVersion >> 8) & 0xff, + iPythonHexVersion & 0xff); + + def formatPythonVersion(self): + """ Unbuttons the version number and formats it as a version string. """ + return self.formatPythonVersionEx(self.iPythonHexVersion); + + + @staticmethod + def getCpuFamilyEx(lCpuRevision): + """ Returns the CPU family for a x86 or amd64 testboxes.""" + if lCpuRevision is None: + return 0; + return (lCpuRevision >> 24 & 0xff); + + def getCpuFamily(self): + """ Returns the CPU family for a x86 or amd64 testboxes.""" + return self.getCpuFamilyEx(self.lCpuRevision); + + @staticmethod + def getCpuModelEx(lCpuRevision): + """ Returns the CPU model for a x86 or amd64 testboxes.""" + if lCpuRevision is None: + return 0; + return (lCpuRevision >> 8 & 0xffff); + + def getCpuModel(self): + """ Returns the CPU model for a x86 or amd64 testboxes.""" + return self.getCpuModelEx(self.lCpuRevision); + + @staticmethod + def getCpuSteppingEx(lCpuRevision): + """ Returns the CPU stepping for a x86 or amd64 testboxes.""" + if lCpuRevision is None: + return 0; + return (lCpuRevision & 0xff); + + def getCpuStepping(self): + """ Returns the CPU stepping for a x86 or amd64 testboxes.""" + return self.getCpuSteppingEx(self.lCpuRevision); + + + # The following is a translation of the g_aenmIntelFamily06 array in CPUMR3CpuId.cpp: + kdIntelFamily06 = { + 0x00: 'P6', + 0x01: 'P6', + 0x03: 'P6_II', + 0x05: 'P6_II', + 0x06: 'P6_II', + 0x07: 'P6_III', + 0x08: 'P6_III', + 0x09: 'P6_M_Banias', + 0x0a: 'P6_III', + 0x0b: 'P6_III', + 0x0d: 'P6_M_Dothan', + 0x0e: 'Core_Yonah', + 0x0f: 'Core2_Merom', + 0x15: 'P6_M_Dothan', + 0x16: 'Core2_Merom', + 0x17: 'Core2_Penryn', + 0x1a: 'Core7_Nehalem', + 0x1c: 'Atom_Bonnell', + 0x1d: 'Core2_Penryn', + 0x1e: 'Core7_Nehalem', + 0x1f: 'Core7_Nehalem', + 0x25: 'Core7_Westmere', + 0x26: 'Atom_Lincroft', + 0x27: 'Atom_Saltwell', + 0x2a: 'Core7_SandyBridge', + 0x2c: 'Core7_Westmere', + 0x2d: 'Core7_SandyBridge', + 0x2e: 'Core7_Nehalem', + 0x2f: 'Core7_Westmere', + 0x35: 'Atom_Saltwell', + 0x36: 'Atom_Saltwell', + 0x37: 'Atom_Silvermont', + 0x3a: 'Core7_IvyBridge', + 0x3c: 'Core7_Haswell', + 0x3d: 'Core7_Broadwell', + 0x3e: 'Core7_IvyBridge', + 0x3f: 'Core7_Haswell', + 0x45: 'Core7_Haswell', + 0x46: 'Core7_Haswell', + 0x47: 'Core7_Broadwell', + 0x4a: 'Atom_Silvermont', + 0x4c: 'Atom_Airmount', + 0x4d: 'Atom_Silvermont', + 0x4e: 'Core7_Skylake', + 0x4f: 'Core7_Broadwell', + 0x55: 'Core7_Skylake', + 0x56: 'Core7_Broadwell', + 0x5a: 'Atom_Silvermont', + 0x5c: 'Atom_Goldmont', + 0x5d: 'Atom_Silvermont', + 0x5e: 'Core7_Skylake', + 0x66: 'Core7_Cannonlake', + }; + # Also from CPUMR3CpuId.cpp, but the switch. + kdIntelFamily15 = { + 0x00: 'NB_Willamette', + 0x01: 'NB_Willamette', + 0x02: 'NB_Northwood', + 0x03: 'NB_Prescott', + 0x04: 'NB_Prescott2M', + 0x05: 'NB_Unknown', + 0x06: 'NB_CedarMill', + 0x07: 'NB_Gallatin', + }; + + @staticmethod + def queryCpuMicroarchEx(lCpuRevision, sCpuVendor): + """ Try guess the microarch name for the cpu. Returns None if we cannot. """ + if lCpuRevision is None or sCpuVendor is None: + return None; + uFam = TestBoxData.getCpuFamilyEx(lCpuRevision); + uMod = TestBoxData.getCpuModelEx(lCpuRevision); + if sCpuVendor == 'GenuineIntel': + if uFam == 6: + return TestBoxData.kdIntelFamily06.get(uMod, None); + if uFam == 15: + return TestBoxData.kdIntelFamily15.get(uMod, None); + elif sCpuVendor == 'AuthenticAMD': + if uFam == 0xf: + if uMod < 0x10: return 'K8_130nm'; + if 0x60 <= uMod < 0x80: return 'K8_65nm'; + if uMod >= 0x40: return 'K8_90nm_AMDV'; + if uMod in [0x21, 0x23, 0x2b, 0x37, 0x3f]: return 'K8_90nm_DualCore'; + return 'AMD_K8_90nm'; + if uFam == 0x10: return 'K10'; + if uFam == 0x11: return 'K10_Lion'; + if uFam == 0x12: return 'K10_Llano'; + if uFam == 0x14: return 'Bobcat'; + if uFam == 0x15: + if uMod <= 0x01: return 'Bulldozer'; + if uMod in [0x02, 0x10, 0x13]: return 'Piledriver'; + return None; + if uFam == 0x16: + return 'Jaguar'; + elif sCpuVendor == 'CentaurHauls': + if uFam == 0x05: + if uMod == 0x01: return 'Centaur_C6'; + if uMod == 0x04: return 'Centaur_C6'; + if uMod == 0x08: return 'Centaur_C2'; + if uMod == 0x09: return 'Centaur_C3'; + if uFam == 0x06: + if uMod == 0x05: return 'VIA_C3_M2'; + if uMod == 0x06: return 'VIA_C3_C5A'; + if uMod == 0x07: return 'VIA_C3_C5B' if TestBoxData.getCpuSteppingEx(lCpuRevision) < 8 else 'VIA_C3_C5C'; + if uMod == 0x08: return 'VIA_C3_C5N'; + if uMod == 0x09: return 'VIA_C3_C5XL' if TestBoxData.getCpuSteppingEx(lCpuRevision) < 8 else 'VIA_C3_C5P'; + if uMod == 0x0a: return 'VIA_C7_C5J'; + if uMod == 0x0f: return 'VIA_Isaiah'; + elif sCpuVendor == ' Shanghai ': + if uFam == 0x07: + if uMod == 0x0b: return 'Shanghai_KX-5000'; + return None; + + def queryCpuMicroarch(self): + """ Try guess the microarch name for the cpu. Returns None if we cannot. """ + return self.queryCpuMicroarchEx(self.lCpuRevision, self.sCpuVendor); + + @staticmethod + def getPrettyCpuVersionEx(lCpuRevision, sCpuVendor): + """ Pretty formatting of the family/model/stepping with microarch optimizations. """ + if lCpuRevision is None or sCpuVendor is None: + return u'<none>'; + sMarch = TestBoxData.queryCpuMicroarchEx(lCpuRevision, sCpuVendor); + if sMarch is not None: + return '%s %02x:%x' \ + % (sMarch, TestBoxData.getCpuModelEx(lCpuRevision), TestBoxData.getCpuSteppingEx(lCpuRevision)); + return 'fam%02X m%02X s%02X' \ + % ( TestBoxData.getCpuFamilyEx(lCpuRevision), TestBoxData.getCpuModelEx(lCpuRevision), + TestBoxData.getCpuSteppingEx(lCpuRevision)); + + def getPrettyCpuVersion(self): + """ Pretty formatting of the family/model/stepping with microarch optimizations. """ + return self.getPrettyCpuVersionEx(self.lCpuRevision, self.sCpuVendor); + + def getArchBitString(self): + """ Returns 32-bit, 64-bit, <none>, or sCpuArch. """ + if self.sCpuArch is None: + return '<none>'; + if self.sCpuArch in [ 'x86',]: + return '32-bit'; + if self.sCpuArch in [ 'amd64',]: + return '64-bit'; + return self.sCpuArch; + + def getPrettyCpuVendor(self): + """ Pretty vendor name.""" + if self.sCpuVendor is None: + return '<none>'; + if self.sCpuVendor == 'GenuineIntel': return 'Intel'; + if self.sCpuVendor == 'AuthenticAMD': return 'AMD'; + if self.sCpuVendor == 'CentaurHauls': return 'VIA'; + if self.sCpuVendor == ' Shanghai ': return 'Shanghai'; + return self.sCpuVendor; + + +class TestBoxDataEx(TestBoxData): + """ + TestBox data. + """ + + ksParam_aoInSchedGroups = 'TestBox_aoInSchedGroups'; + + # Use [] instead of None. + kasAltArrayNull = [ 'aoInSchedGroups', ]; + + ## Helper parameter containing the comma separated list with the IDs of + # potential members found in the parameters. + ksParam_aidSchedGroups = 'TestBoxDataEx_aidSchedGroups'; + + def __init__(self): + TestBoxData.__init__(self); + self.aoInSchedGroups = [] # type: list[TestBoxInSchedGroupData] + + def _initExtraMembersFromDb(self, oDb, tsNow = None, sPeriodBack = None): + """ + Worker shared by the initFromDb* methods. + Returns self. Raises exception if no row or database error. + """ + oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb, + 'SELECT *\n' + 'FROM TestBoxesInSchedGroups\n' + 'WHERE idTestBox = %s\n' + , (self.idTestBox,), tsNow, sPeriodBack) + + 'ORDER BY idSchedGroup\n' ); + self.aoInSchedGroups = []; + for aoRow in oDb.fetchAll(): + self.aoInSchedGroups.append(TestBoxInSchedGroupDataEx().initFromDbRowEx(aoRow, oDb, tsNow, sPeriodBack)); + return self; + + def initFromDbRowEx(self, aoRow, oDb, tsNow = None): + """ + Reinitialize from a SELECT * FROM TestBoxesWithStrings row. Will query the + necessary additional data from oDb using tsNow. + Returns self. Raises exception if no row or database error. + """ + TestBoxData.initFromDbRow(self, aoRow); + return self._initExtraMembersFromDb(oDb, tsNow); + + def initFromDbWithId(self, oDb, idTestBox, tsNow = None, sPeriodBack = None): + """ + Initialize the object from the database. + """ + TestBoxData.initFromDbWithId(self, oDb, idTestBox, tsNow, sPeriodBack); + return self._initExtraMembersFromDb(oDb, tsNow, sPeriodBack); + + def initFromDbWithGenId(self, oDb, idGenTestBox, tsNow = None): + """ + Initialize the object from the database. + """ + TestBoxData.initFromDbWithGenId(self, oDb, idGenTestBox); + if tsNow is None and not oDb.isTsInfinity(self.tsExpire): + tsNow = self.tsEffective; + return self._initExtraMembersFromDb(oDb, tsNow); + + def getAttributeParamNullValues(self, sAttr): # Necessary? + if sAttr in ['aoInSchedGroups', ]: + return [[], '']; + return TestBoxData.getAttributeParamNullValues(self, sAttr); + + def convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict): + """ + For dealing with the in-scheduling-group list. + """ + if sAttr != 'aoInSchedGroups': + return TestBoxData.convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict); + + aoNewValues = []; + aidSelected = oDisp.getListOfIntParams(sParam, iMin = 1, iMax = 0x7ffffffe, aiDefaults = []); + asIds = oDisp.getStringParam(self.ksParam_aidSchedGroups, sDefault = '').split(','); + for idSchedGroup in asIds: + try: idSchedGroup = int(idSchedGroup); + except: pass; + oDispWrapper = self.DispWrapper(oDisp, '%s[%s][%%s]' % (TestBoxDataEx.ksParam_aoInSchedGroups, idSchedGroup,)) + oMember = TestBoxInSchedGroupData().initFromParams(oDispWrapper, fStrict = False); + if idSchedGroup in aidSelected: + aoNewValues.append(oMember); + return aoNewValues; + + def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb): # pylint: disable=too-many-locals + """ + Validate special arrays and requirement expressions. + + Some special needs for the in-scheduling-group list. + """ + if sAttr != 'aoInSchedGroups': + return TestBoxData._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb); + + asErrors = []; + aoNewValues = []; + + # Note! We'll be returning an error dictionary instead of an string here. + dErrors = {}; + + # HACK ALERT! idTestBox might not have been validated and converted yet, but we need detect + # adding so we can ignore idTestBox being NIL when validating group memberships. + ## @todo make base.py pass us the ksValidateFor_Xxxx value. + fIsAdding = bool(self.idTestBox in [ None, -1, '-1', 'None', '' ]) + + for iInGrp, oInSchedGroup in enumerate(self.aoInSchedGroups): + oInSchedGroup = copy.copy(oInSchedGroup); + oInSchedGroup.idTestBox = self.idTestBox; + if fIsAdding: + dCurErrors = oInSchedGroup.validateAndConvertEx(['idTestBox',] + oInSchedGroup.kasAllowNullAttributes, + oDb, ModelDataBase.ksValidateFor_Add); + else: + dCurErrors = oInSchedGroup.validateAndConvert(oDb, ModelDataBase.ksValidateFor_Other); + if not dCurErrors: + pass; ## @todo figure out the ID? + else: + asErrors = []; + for sKey in dCurErrors: + asErrors.append('%s: %s' % (sKey[len('TestBoxInSchedGroup_'):], + dCurErrors[sKey] + ('{%s}' % self.idTestBox))) + dErrors[iInGrp] = '<br>\n'.join(asErrors) + aoNewValues.append(oInSchedGroup); + + for iInGrp, oInSchedGroup in enumerate(self.aoInSchedGroups): + for iInGrp2 in xrange(iInGrp + 1, len(self.aoInSchedGroups)): + if self.aoInSchedGroups[iInGrp2].idSchedGroup == oInSchedGroup.idSchedGroup: + sMsg = 'Duplicate scheduling group #%s".' % (oInSchedGroup.idSchedGroup,); + if iInGrp in dErrors: dErrors[iInGrp] += '<br>\n' + sMsg; + else: dErrors[iInGrp] = sMsg; + if iInGrp2 in dErrors: dErrors[iInGrp2] += '<br>\n' + sMsg; + else: dErrors[iInGrp2] = sMsg; + break; + + return (aoNewValues, dErrors if dErrors else None); + + +class TestBoxLogic(ModelLogicBase): + """ + TestBox logic. + """ + + kiSortColumn_sName = 1; + kiSortColumn_sOs = 2; + kiSortColumn_sOsVersion = 3; + kiSortColumn_sCpuVendor = 4; + kiSortColumn_sCpuArch = 5; + kiSortColumn_lCpuRevision = 6; + kiSortColumn_cCpus = 7; + kiSortColumn_cMbMemory = 8; + kiSortColumn_cMbScratch = 9; + kiSortColumn_fCpuNestedPaging = 10; + kiSortColumn_iTestBoxScriptRev = 11; + kiSortColumn_iPythonHexVersion = 12; + kiSortColumn_enmPendingCmd = 13; + kiSortColumn_fEnabled = 14; + kiSortColumn_enmState = 15; + kiSortColumn_tsUpdated = 16; + kcMaxSortColumns = 17; + kdSortColumnMap = { + 0: 'TestBoxesWithStrings.sName', + kiSortColumn_sName: "regexp_replace(TestBoxesWithStrings.sName,'[0-9]*', '', 'g'), " \ + "RIGHT(CONCAT(regexp_replace(TestBoxesWithStrings.sName,'[^0-9]*','', 'g'),'0'),8)::int", + -kiSortColumn_sName: "regexp_replace(TestBoxesWithStrings.sName,'[0-9]*', '', 'g') DESC, " \ + "RIGHT(CONCAT(regexp_replace(TestBoxesWithStrings.sName,'[^0-9]*','', 'g'),'0'),8)::int DESC", + kiSortColumn_sOs: 'TestBoxesWithStrings.sOs', + -kiSortColumn_sOs: 'TestBoxesWithStrings.sOs DESC', + kiSortColumn_sOsVersion: 'TestBoxesWithStrings.sOsVersion', + -kiSortColumn_sOsVersion: 'TestBoxesWithStrings.sOsVersion DESC', + kiSortColumn_sCpuVendor: 'TestBoxesWithStrings.sCpuVendor', + -kiSortColumn_sCpuVendor: 'TestBoxesWithStrings.sCpuVendor DESC', + kiSortColumn_sCpuArch: 'TestBoxesWithStrings.sCpuArch', + -kiSortColumn_sCpuArch: 'TestBoxesWithStrings.sCpuArch DESC', + kiSortColumn_lCpuRevision: 'TestBoxesWithStrings.lCpuRevision', + -kiSortColumn_lCpuRevision: 'TestBoxesWithStrings.lCpuRevision DESC', + kiSortColumn_cCpus: 'TestBoxesWithStrings.cCpus', + -kiSortColumn_cCpus: 'TestBoxesWithStrings.cCpus DESC', + kiSortColumn_cMbMemory: 'TestBoxesWithStrings.cMbMemory', + -kiSortColumn_cMbMemory: 'TestBoxesWithStrings.cMbMemory DESC', + kiSortColumn_cMbScratch: 'TestBoxesWithStrings.cMbScratch', + -kiSortColumn_cMbScratch: 'TestBoxesWithStrings.cMbScratch DESC', + kiSortColumn_fCpuNestedPaging: 'TestBoxesWithStrings.fCpuNestedPaging', + -kiSortColumn_fCpuNestedPaging: 'TestBoxesWithStrings.fCpuNestedPaging DESC', + kiSortColumn_iTestBoxScriptRev: 'TestBoxesWithStrings.iTestBoxScriptRev', + -kiSortColumn_iTestBoxScriptRev: 'TestBoxesWithStrings.iTestBoxScriptRev DESC', + kiSortColumn_iPythonHexVersion: 'TestBoxesWithStrings.iPythonHexVersion', + -kiSortColumn_iPythonHexVersion: 'TestBoxesWithStrings.iPythonHexVersion DESC', + kiSortColumn_enmPendingCmd: 'TestBoxesWithStrings.enmPendingCmd', + -kiSortColumn_enmPendingCmd: 'TestBoxesWithStrings.enmPendingCmd DESC', + kiSortColumn_fEnabled: 'TestBoxesWithStrings.fEnabled', + -kiSortColumn_fEnabled: 'TestBoxesWithStrings.fEnabled DESC', + kiSortColumn_enmState: 'TestBoxStatuses.enmState', + -kiSortColumn_enmState: 'TestBoxStatuses.enmState DESC', + kiSortColumn_tsUpdated: 'TestBoxStatuses.tsUpdated', + -kiSortColumn_tsUpdated: 'TestBoxStatuses.tsUpdated DESC', + }; + + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb); + self.dCache = None; + + def tryFetchTestBoxByUuid(self, sTestBoxUuid): + """ + Tries to fetch a testbox by its UUID alone. + """ + self._oDb.execute('SELECT TestBoxesWithStrings.*\n' + 'FROM TestBoxesWithStrings\n' + 'WHERE uuidSystem = %s\n' + ' AND tsExpire = \'infinity\'::timestamp\n' + 'ORDER BY tsEffective DESC\n', + (sTestBoxUuid,)); + if self._oDb.getRowCount() == 0: + return None; + if self._oDb.getRowCount() != 1: + raise TMTooManyRows('Database integrity error: %u hits' % (self._oDb.getRowCount(),)); + oData = TestBoxData(); + oData.initFromDbRow(self._oDb.fetchOne()); + return oData; + + def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None): + """ + Fetches testboxes for listing. + + Returns an array (list) of TestBoxDataForListing items, empty list if none. + The TestBoxDataForListing instances are just TestBoxData with two extra + members, an extra oStatus member that is either None or a TestBoxStatusData + instance, and a member tsCurrent holding CURRENT_TIMESTAMP. + + Raises exception on error. + """ + class TestBoxDataForListing(TestBoxDataEx): + """ We add two members for the listing. """ + def __init__(self): + TestBoxDataEx.__init__(self); + self.tsCurrent = None; # CURRENT_TIMESTAMP + self.oStatus = None # type: TestBoxStatusData + + from testmanager.core.testboxstatus import TestBoxStatusData; + + if not aiSortColumns: + aiSortColumns = [self.kiSortColumn_sName,]; + + if tsNow is None: + self._oDb.execute('SELECT TestBoxesWithStrings.*,\n' + ' TestBoxStatuses.*\n' + 'FROM TestBoxesWithStrings\n' + ' LEFT OUTER JOIN TestBoxStatuses\n' + ' ON TestBoxStatuses.idTestBox = TestBoxesWithStrings.idTestBox\n' + 'WHERE TestBoxesWithStrings.tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY ' + (', '.join([self.kdSortColumnMap[i] for i in aiSortColumns])) + '\n' + 'LIMIT %s OFFSET %s\n' + , (cMaxRows, iStart,)); + else: + self._oDb.execute('SELECT TestBoxesWithStrings.*,\n' + ' TestBoxStatuses.*\n' + 'FROM TestBoxesWithStrings\n' + ' LEFT OUTER JOIN TestBoxStatuses\n' + ' ON TestBoxStatuses.idTestBox = TestBoxesWithStrings.idTestBox\n' + 'WHERE tsExpire > %s\n' + ' AND tsEffective <= %s\n' + 'ORDER BY ' + (', '.join([self.kdSortColumnMap[i] for i in aiSortColumns])) + '\n' + 'LIMIT %s OFFSET %s\n' + , ( tsNow, tsNow, cMaxRows, iStart,)); + + aoRows = []; + for aoOne in self._oDb.fetchAll(): + oTestBox = TestBoxDataForListing().initFromDbRowEx(aoOne, self._oDb, tsNow); + oTestBox.tsCurrent = self._oDb.getCurrentTimestamp(); + if aoOne[TestBoxData.kcDbColumns] is not None: + oTestBox.oStatus = TestBoxStatusData().initFromDbRow(aoOne[TestBoxData.kcDbColumns:]); + aoRows.append(oTestBox); + return aoRows; + + def fetchForSchedGroup(self, idSchedGroup, tsNow, aiSortColumns = None): + """ + Fetches testboxes for listing. + + Returns an array (list) of TestBoxDataForSchedGroup items, empty list if none. + + Raises exception on error. + """ + if not aiSortColumns: + aiSortColumns = [self.kiSortColumn_sName,]; + asSortColumns = [self.kdSortColumnMap[i] for i in aiSortColumns]; + asSortColumns.append('TestBoxesInSchedGroups.idTestBox'); + + if tsNow is None: + self._oDb.execute(''' +SELECT TestBoxesInSchedGroups.*, + TestBoxesWithStrings.* +FROM TestBoxesInSchedGroups + LEFT OUTER JOIN TestBoxesWithStrings + ON TestBoxesWithStrings.idTestBox = TestBoxesInSchedGroups.idTestBox + AND TestBoxesWithStrings.tsExpire = 'infinity'::TIMESTAMP +WHERE TestBoxesInSchedGroups.idSchedGroup = %s + AND TestBoxesInSchedGroups.tsExpire = 'infinity'::TIMESTAMP +ORDER BY ''' + ', '.join(asSortColumns), (idSchedGroup, )); + else: + self._oDb.execute(''' +SELECT TestBoxesInSchedGroups.*, + TestBoxesWithStrings.* +FROM TestBoxesInSchedGroups + LEFT OUTER JOIN TestBoxesWithStrings + ON TestBoxesWithStrings.idTestBox = TestBoxesInSchedGroups.idTestBox + AND TestBoxesWithStrings.tsExpire > %s + AND TestBoxesWithStrings.tsEffective <= %s +WHERE TestBoxesInSchedGroups.idSchedGroup = %s + AND TestBoxesInSchedGroups.tsExpire > %s + AND TestBoxesInSchedGroups.tsEffective <= %s +ORDER BY ''' + ', '.join(asSortColumns), (tsNow, tsNow, idSchedGroup, tsNow, tsNow, )); + + aoRows = []; + for aoOne in self._oDb.fetchAll(): + aoRows.append(TestBoxDataForSchedGroup().initFromDbRow(aoOne)); + return aoRows; + + def fetchForChangeLog(self, idTestBox, iStart, cMaxRows, tsNow): # pylint: disable=too-many-locals + """ + Fetches change log entries for a testbox. + + Returns an array of ChangeLogEntry instance and an indicator whether + there are more entries. + Raises exception on error. + """ + + ## @todo calc changes to scheduler group! + + if tsNow is None: + tsNow = self._oDb.getCurrentTimestamp(); + + self._oDb.execute('SELECT TestBoxesWithStrings.*\n' + 'FROM TestBoxesWithStrings\n' + 'WHERE TestBoxesWithStrings.tsEffective <= %s\n' + ' AND TestBoxesWithStrings.idTestBox = %s\n' + 'ORDER BY TestBoxesWithStrings.tsExpire DESC\n' + 'LIMIT %s OFFSET %s\n' + , (tsNow, idTestBox, cMaxRows + 1, iStart,)); + + aoRows = []; + for aoDbRow in self._oDb.fetchAll(): + aoRows.append(TestBoxData().initFromDbRow(aoDbRow)); + + # Calculate the changes. + aoEntries = []; + for i in xrange(0, len(aoRows) - 1): + oNew = aoRows[i]; + oOld = aoRows[i + 1]; + aoChanges = []; + for sAttr in oNew.getDataAttributes(): + if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', ]: + oOldAttr = getattr(oOld, sAttr); + oNewAttr = getattr(oNew, sAttr); + if oOldAttr != oNewAttr: + if sAttr == 'sReport': + aoChanges.append(AttributeChangeEntryPre(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr))); + else: + aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr))); + aoEntries.append(ChangeLogEntry(oNew.uidAuthor, None, oNew.tsEffective, oNew.tsExpire, oNew, oOld, aoChanges)); + + # If we're at the end of the log, add the initial entry. + if len(aoRows) <= cMaxRows and aoRows: + oNew = aoRows[-1]; + aoEntries.append(ChangeLogEntry(oNew.uidAuthor, None, oNew.tsEffective, oNew.tsExpire, oNew, None, [])); + + UserAccountLogic(self._oDb).resolveChangeLogAuthors(aoEntries); + return (aoEntries, len(aoRows) > cMaxRows); + + def _validateAndConvertData(self, oData, enmValidateFor): + # type: (TestBoxDataEx, str) -> None + """ + Helper for addEntry and editEntry that validates the scheduling group IDs in + addtion to what's covered by the default validateAndConvert of the data object. + + Raises exception on invalid input. + """ + dDataErrors = oData.validateAndConvert(self._oDb, enmValidateFor); + if dDataErrors: + raise TMInvalidData('TestBoxLogic.addEntry: %s' % (dDataErrors,)); + if isinstance(oData, TestBoxDataEx): + if oData.aoInSchedGroups: + sSchedGrps = ', '.join('(%s)' % oCur.idSchedGroup for oCur in oData.aoInSchedGroups); + self._oDb.execute('SELECT SchedGroupIDs.idSchedGroup\n' + 'FROM (VALUES ' + sSchedGrps + ' ) AS SchedGroupIDs(idSchedGroup)\n' + ' LEFT OUTER JOIN SchedGroups\n' + ' ON SchedGroupIDs.idSchedGroup = SchedGroups.idSchedGroup\n' + ' AND SchedGroups.tsExpire = \'infinity\'::TIMESTAMP\n' + 'WHERE SchedGroups.idSchedGroup IS NULL\n'); + aaoRows = self._oDb.fetchAll(); + if aaoRows: + raise TMInvalidData('TestBoxLogic.addEntry missing scheduling groups: %s' + % (', '.join(str(aoRow[0]) for aoRow in aaoRows),)); + return None; + + def addEntry(self, oData, uidAuthor, fCommit = False): + # type: (TestBoxDataEx, int, bool) -> (int, int, datetime.datetime) + """ + Creates a testbox in the database. + Returns the testbox ID, testbox generation ID and effective timestamp + of the created testbox on success. Throws error on failure. + """ + + # + # Validate. Extra work because of missing foreign key (due to history). + # + self._validateAndConvertData(oData, oData.ksValidateFor_Add); + + # + # Do it. + # + self._oDb.callProc('TestBoxLogic_addEntry' + , ( uidAuthor, + oData.ip, # Should we allow setting the IP? + oData.uuidSystem, + oData.sName, + oData.sDescription, + oData.fEnabled, + oData.enmLomKind, + oData.ipLom, + oData.pctScaleTimeout, + oData.sComment, + oData.enmPendingCmd, ) ); + (idTestBox, idGenTestBox, tsEffective) = self._oDb.fetchOne(); + + for oInSchedGrp in oData.aoInSchedGroups: + self._oDb.callProc('TestBoxLogic_addGroupEntry', + ( uidAuthor, idTestBox, oInSchedGrp.idSchedGroup, oInSchedGrp.iSchedPriority,) ); + + self._oDb.maybeCommit(fCommit); + return (idTestBox, idGenTestBox, tsEffective); + + + def editEntry(self, oData, uidAuthor, fCommit = False): + """ + Data edit update, web UI is the primary user. + + oData is either TestBoxDataEx or TestBoxData. The latter is for enabling + Returns the new generation ID and effective date. + """ + + # + # Validate. + # + self._validateAndConvertData(oData, oData.ksValidateFor_Edit); + + # + # Get current data. + # + oOldData = TestBoxDataEx().initFromDbWithId(self._oDb, oData.idTestBox); + + # + # Do it. + # + if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor', 'aoInSchedGroups', ] + + TestBoxData.kasMachineSettableOnly ): + self._oDb.callProc('TestBoxLogic_editEntry' + , ( uidAuthor, + oData.idTestBox, + oData.ip, # Should we allow setting the IP? + oData.uuidSystem, + oData.sName, + oData.sDescription, + oData.fEnabled, + oData.enmLomKind, + oData.ipLom, + oData.pctScaleTimeout, + oData.sComment, + oData.enmPendingCmd, )); + (idGenTestBox, tsEffective) = self._oDb.fetchOne(); + else: + idGenTestBox = oOldData.idGenTestBox; + tsEffective = oOldData.tsEffective; + + if isinstance(oData, TestBoxDataEx): + # Calc in-group changes. + aoRemoved = list(oOldData.aoInSchedGroups); + aoNew = []; + aoUpdated = []; + for oNewInGroup in oData.aoInSchedGroups: + oOldInGroup = None; + for iCur, oCur in enumerate(aoRemoved): + if oCur.idSchedGroup == oNewInGroup.idSchedGroup: + oOldInGroup = aoRemoved.pop(iCur); + break; + if oOldInGroup is None: + aoNew.append(oNewInGroup); + elif oNewInGroup.iSchedPriority != oOldInGroup.iSchedPriority: + aoUpdated.append(oNewInGroup); + + # Remove in-groups. + for oInGroup in aoRemoved: + self._oDb.callProc('TestBoxLogic_removeGroupEntry', (uidAuthor, oData.idTestBox, oInGroup.idSchedGroup, )); + + # Add new ones. + for oInGroup in aoNew: + self._oDb.callProc('TestBoxLogic_addGroupEntry', + ( uidAuthor, oData.idTestBox, oInGroup.idSchedGroup, oInGroup.iSchedPriority, ) ); + + # Edit existing ones. + for oInGroup in aoUpdated: + self._oDb.callProc('TestBoxLogic_editGroupEntry', + ( uidAuthor, oData.idTestBox, oInGroup.idSchedGroup, oInGroup.iSchedPriority, ) ); + else: + assert isinstance(oData, TestBoxData); + + self._oDb.maybeCommit(fCommit); + return (idGenTestBox, tsEffective); + + + def removeEntry(self, uidAuthor, idTestBox, fCascade = False, fCommit = False): + """ + Delete test box and scheduling group associations. + """ + self._oDb.callProc('TestBoxLogic_removeEntry' + , ( uidAuthor, idTestBox, fCascade,)); + self._oDb.maybeCommit(fCommit); + return True; + + + def updateOnSignOn(self, idTestBox, idGenTestBox, sTestBoxAddr, sOs, sOsVersion, # pylint: disable=too-many-arguments,too-many-locals + sCpuVendor, sCpuArch, sCpuName, lCpuRevision, cCpus, fCpuHwVirt, fCpuNestedPaging, fCpu64BitGuest, + fChipsetIoMmu, fRawMode, cMbMemory, cMbScratch, sReport, iTestBoxScriptRev, iPythonHexVersion): + """ + Update the testbox attributes automatically on behalf of the testbox script. + Returns the new generation id on success, raises an exception on failure. + """ + _ = idGenTestBox; + self._oDb.callProc('TestBoxLogic_updateOnSignOn' + , ( idTestBox, + sTestBoxAddr, + sOs, + sOsVersion, + sCpuVendor, + sCpuArch, + sCpuName, + lCpuRevision, + cCpus, + fCpuHwVirt, + fCpuNestedPaging, + fCpu64BitGuest, + fChipsetIoMmu, + fRawMode, + cMbMemory, + cMbScratch, + sReport, + iTestBoxScriptRev, + iPythonHexVersion,)); + return self._oDb.fetchOne()[0]; + + + def setCommand(self, idTestBox, sOldCommand, sNewCommand, uidAuthor = None, fCommit = False, sComment = None): + """ + Sets or resets the pending command on a testbox. + Returns (idGenTestBox, tsEffective) of the new row. + """ + ## @todo throw TMInFligthCollision again... + self._oDb.callProc('TestBoxLogic_setCommand' + , ( uidAuthor, idTestBox, sOldCommand, sNewCommand, sComment,)); + aoRow = self._oDb.fetchOne(); + self._oDb.maybeCommit(fCommit); + return (aoRow[0], aoRow[1]); + + + def getAll(self): + """ + Retrieve list of all registered Test Box records from DB. + """ + self._oDb.execute('SELECT *\n' + 'FROM TestBoxesWithStrings\n' + 'WHERE tsExpire=\'infinity\'::timestamp\n' + 'ORDER BY sName') + + aaoRows = self._oDb.fetchAll() + aoRet = [] + for aoRow in aaoRows: + aoRet.append(TestBoxData().initFromDbRow(aoRow)) + return aoRet + + + def cachedLookup(self, idTestBox): + # type: (int) -> TestBoxDataEx + """ + Looks up the most recent TestBoxData object for idTestBox via + an object cache. + + Returns a shared TestBoxDataEx object. None if not found. + Raises exception on DB error. + """ + if self.dCache is None: + self.dCache = self._oDb.getCache('TestBoxData'); + oEntry = self.dCache.get(idTestBox, None); + if oEntry is None: + fNeedNow = False; + self._oDb.execute('SELECT TestBoxesWithStrings.*\n' + 'FROM TestBoxesWithStrings\n' + 'WHERE idTestBox = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idTestBox, )); + if self._oDb.getRowCount() == 0: + # Maybe it was deleted, try get the last entry. + self._oDb.execute('SELECT TestBoxesWithStrings.*\n' + 'FROM TestBoxesWithStrings\n' + 'WHERE idTestBox = %s\n' + 'ORDER BY tsExpire DESC\n' + 'LIMIT 1\n' + , (idTestBox, )); + fNeedNow = True; + elif self._oDb.getRowCount() > 1: + raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idTestBox)); + + if self._oDb.getRowCount() == 1: + aaoRow = self._oDb.fetchOne(); + if not fNeedNow: + oEntry = TestBoxDataEx().initFromDbRowEx(aaoRow, self._oDb); + else: + oEntry = TestBoxDataEx().initFromDbRow(aaoRow); + oEntry.initFromDbRowEx(aaoRow, self._oDb, tsNow = db.dbTimestampMinusOneTick(oEntry.tsExpire)); + self.dCache[idTestBox] = oEntry; + return oEntry; + + + + # + # The virtual test sheriff interface. + # + + def hasTestBoxRecentlyBeenRebooted(self, idTestBox, cHoursBack = 2, tsNow = None): + """ + Checks if the testbox has been rebooted in the specified time period. + + This does not include already pending reboots, though under some + circumstances it may. These being the test box entry being edited for + other reasons. + + Returns True / False. + """ + if tsNow is None: + tsNow = self._oDb.getCurrentTimestamp(); + self._oDb.execute('SELECT COUNT(idTestBox)\n' + 'FROM TestBoxes\n' + 'WHERE idTestBox = %s\n' + ' AND tsExpire < %s\n' + ' AND tsExpire >= %s - interval \'%s hours\'\n' + ' AND enmPendingCmd IN (%s, %s)\n' + , ( idTestBox, tsNow, tsNow, cHoursBack, + TestBoxData.ksTestBoxCmd_Reboot, TestBoxData.ksTestBoxCmd_UpgradeAndReboot, )); + return self._oDb.fetchOne()[0] > 0; + + + def rebootTestBox(self, idTestBox, uidAuthor, sComment, sOldCommand = TestBoxData.ksTestBoxCmd_None, fCommit = False): + """ + Issues a reboot command for the given test box. + Return True on succes, False on in-flight collision. + May raise DB exception on other trouble. + """ + try: + self.setCommand(idTestBox, sOldCommand, TestBoxData.ksTestBoxCmd_Reboot, + uidAuthor = uidAuthor, fCommit = fCommit, sComment = sComment); + except TMInFligthCollision: + return False; + return True; + + + def disableTestBox(self, idTestBox, uidAuthor, sComment, fCommit = False): + """ + Disables the given test box. + + Raises exception on trouble, without rollback. + """ + oTestBox = TestBoxData().initFromDbWithId(self._oDb, idTestBox); + if oTestBox.fEnabled: + oTestBox.fEnabled = False; + if sComment is not None: + oTestBox.sComment = sComment; + self.editEntry(oTestBox, uidAuthor = uidAuthor, fCommit = fCommit); + return None; + + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +class TestBoxDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [TestBoxData(),]; + +if __name__ == '__main__': + unittest.main(); + # not reached. + diff --git a/src/VBox/ValidationKit/testmanager/core/testboxcontroller.py b/src/VBox/ValidationKit/testmanager/core/testboxcontroller.py new file mode 100755 index 00000000..dd67ac7a --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/testboxcontroller.py @@ -0,0 +1,954 @@ +# -*- coding: utf-8 -*- +# $Id: testboxcontroller.py $ + +""" +Test Manager Core - Web Server Abstraction Base Class. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154070 $" + + +# Standard python imports. +import re; +import os; +import string; # pylint: disable=deprecated-module +import sys; +import uuid; + +# Validation Kit imports. +from common import constants; +from testmanager import config; +from testmanager.core import coreconsts; +from testmanager.core.db import TMDatabaseConnection; +from testmanager.core.base import TMExceptionBase; +from testmanager.core.globalresource import GlobalResourceLogic; +from testmanager.core.testboxstatus import TestBoxStatusData, TestBoxStatusLogic; +from testmanager.core.testbox import TestBoxData, TestBoxLogic; +from testmanager.core.testresults import TestResultLogic, TestResultFileData; +from testmanager.core.testset import TestSetData, TestSetLogic; +from testmanager.core.systemlog import SystemLogData, SystemLogLogic; +from testmanager.core.schedulerbase import SchedulerBase; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + + +class TestBoxControllerException(TMExceptionBase): + """ + Exception class for TestBoxController. + """ + pass; # pylint: disable=unnecessary-pass + + +class TestBoxController(object): # pylint: disable=too-few-public-methods + """ + TestBox Controller class. + """ + + ## Applicable testbox commands to an idle TestBox. + kasIdleCmds = [TestBoxData.ksTestBoxCmd_Reboot, + TestBoxData.ksTestBoxCmd_Upgrade, + TestBoxData.ksTestBoxCmd_UpgradeAndReboot, + TestBoxData.ksTestBoxCmd_Special]; + ## Applicable testbox commands to a busy TestBox. + kasBusyCmds = [TestBoxData.ksTestBoxCmd_Abort, TestBoxData.ksTestBoxCmd_Reboot]; + ## Commands that can be ACK'ed. + kasAckableCmds = [constants.tbresp.CMD_EXEC, constants.tbresp.CMD_ABORT, constants.tbresp.CMD_REBOOT, + constants.tbresp.CMD_UPGRADE, constants.tbresp.CMD_UPGRADE_AND_REBOOT, constants.tbresp.CMD_SPECIAL]; + ## Commands that can be NACK'ed or NOTSUP'ed. + kasNackableCmds = kasAckableCmds + [kasAckableCmds, constants.tbresp.CMD_IDLE, constants.tbresp.CMD_WAIT]; + + ## Mapping from TestBoxCmd_T to TestBoxState_T + kdCmdToState = \ + { \ + TestBoxData.ksTestBoxCmd_Abort: None, + TestBoxData.ksTestBoxCmd_Reboot: TestBoxStatusData.ksTestBoxState_Rebooting, + TestBoxData.ksTestBoxCmd_Upgrade: TestBoxStatusData.ksTestBoxState_Upgrading, + TestBoxData.ksTestBoxCmd_UpgradeAndReboot: TestBoxStatusData.ksTestBoxState_UpgradingAndRebooting, + TestBoxData.ksTestBoxCmd_Special: TestBoxStatusData.ksTestBoxState_DoingSpecialCmd, + }; + + ## Mapping from TestBoxCmd_T to TestBox responses commands. + kdCmdToTbRespCmd = \ + { + TestBoxData.ksTestBoxCmd_Abort: constants.tbresp.CMD_ABORT, + TestBoxData.ksTestBoxCmd_Reboot: constants.tbresp.CMD_REBOOT, + TestBoxData.ksTestBoxCmd_Upgrade: constants.tbresp.CMD_UPGRADE, + TestBoxData.ksTestBoxCmd_UpgradeAndReboot: constants.tbresp.CMD_UPGRADE_AND_REBOOT, + TestBoxData.ksTestBoxCmd_Special: constants.tbresp.CMD_SPECIAL, + }; + + ## Mapping from TestBox responses to TestBoxCmd_T commands. + kdTbRespCmdToCmd = \ + { + constants.tbresp.CMD_IDLE: None, + constants.tbresp.CMD_WAIT: None, + constants.tbresp.CMD_EXEC: None, + constants.tbresp.CMD_ABORT: TestBoxData.ksTestBoxCmd_Abort, + constants.tbresp.CMD_REBOOT: TestBoxData.ksTestBoxCmd_Reboot, + constants.tbresp.CMD_UPGRADE: TestBoxData.ksTestBoxCmd_Upgrade, + constants.tbresp.CMD_UPGRADE_AND_REBOOT: TestBoxData.ksTestBoxCmd_UpgradeAndReboot, + constants.tbresp.CMD_SPECIAL: TestBoxData.ksTestBoxCmd_Special, + }; + + + ## The path to the upgrade zip, relative WebServerGlueBase.getBaseUrl(). + ksUpgradeZip = 'htdocs/upgrade/VBoxTestBoxScript.zip'; + + ## Valid TestBox result values. + kasValidResults = list(constants.result.g_kasValidResults); + ## Mapping TestBox result values to TestStatus_T values. + kadTbResultToStatus = \ + { + constants.result.PASSED: TestSetData.ksTestStatus_Success, + constants.result.SKIPPED: TestSetData.ksTestStatus_Skipped, + constants.result.ABORTED: TestSetData.ksTestStatus_Aborted, + constants.result.BAD_TESTBOX: TestSetData.ksTestStatus_BadTestBox, + constants.result.FAILED: TestSetData.ksTestStatus_Failure, + constants.result.TIMED_OUT: TestSetData.ksTestStatus_TimedOut, + constants.result.REBOOTED: TestSetData.ksTestStatus_Rebooted, + }; + + + def __init__(self, oSrvGlue): + """ + Won't raise exceptions. + """ + self._oSrvGlue = oSrvGlue; + self._sAction = None; # _getStandardParams / dispatchRequest sets this later on. + self._idTestBox = None; # _getStandardParams / dispatchRequest sets this later on. + self._sTestBoxUuid = None; # _getStandardParams / dispatchRequest sets this later on. + self._sTestBoxAddr = None; # _getStandardParams / dispatchRequest sets this later on. + self._idTestSet = None; # _getStandardParams / dispatchRequest sets this later on. + self._dParams = None; # _getStandardParams / dispatchRequest sets this later on. + self._asCheckedParams = []; + self._dActions = \ + { \ + constants.tbreq.SIGNON : self._actionSignOn, + constants.tbreq.REQUEST_COMMAND_BUSY: self._actionRequestCommandBusy, + constants.tbreq.REQUEST_COMMAND_IDLE: self._actionRequestCommandIdle, + constants.tbreq.COMMAND_ACK : self._actionCommandAck, + constants.tbreq.COMMAND_NACK : self._actionCommandNack, + constants.tbreq.COMMAND_NOTSUP : self._actionCommandNotSup, + constants.tbreq.LOG_MAIN : self._actionLogMain, + constants.tbreq.UPLOAD : self._actionUpload, + constants.tbreq.XML_RESULTS : self._actionXmlResults, + constants.tbreq.EXEC_COMPLETED : self._actionExecCompleted, + }; + + def _getStringParam(self, sName, asValidValues = None, fStrip = False, sDefValue = None): + """ + Gets a string parameter (stripped). + + Raises exception if not found and no default is provided, or if the + value isn't found in asValidValues. + """ + if sName not in self._dParams: + if sDefValue is None: + raise TestBoxControllerException('%s parameter %s is missing' % (self._sAction, sName)); + return sDefValue; + sValue = self._dParams[sName]; + if fStrip: + sValue = sValue.strip(); + + if sName not in self._asCheckedParams: + self._asCheckedParams.append(sName); + + if asValidValues is not None and sValue not in asValidValues: + raise TestBoxControllerException('%s parameter %s value "%s" not in %s ' \ + % (self._sAction, sName, sValue, asValidValues)); + return sValue; + + def _getBoolParam(self, sName, fDefValue = None): + """ + Gets a boolean parameter. + + Raises exception if not found and no default is provided, or if not a + valid boolean. + """ + sValue = self._getStringParam(sName, [ 'True', 'true', '1', 'False', 'false', '0'], sDefValue = str(fDefValue)); + return sValue in ('True', 'true', '1',); + + def _getIntParam(self, sName, iMin = None, iMax = None): + """ + Gets a string parameter. + Raises exception if not found, not a valid integer, or if the value + isn't in the range defined by iMin and iMax. + """ + sValue = self._getStringParam(sName); + try: + iValue = int(sValue, 0); + except: + raise TestBoxControllerException('%s parameter %s value "%s" cannot be convert to an integer' \ + % (self._sAction, sName, sValue)); + + if (iMin is not None and iValue < iMin) \ + or (iMax is not None and iValue > iMax): + raise TestBoxControllerException('%s parameter %s value %d is out of range [%s..%s]' \ + % (self._sAction, sName, iValue, iMin, iMax)); + return iValue; + + def _getLongParam(self, sName, lMin = None, lMax = None, lDefValue = None): + """ + Gets a string parameter. + Raises exception if not found, not a valid long integer, or if the value + isn't in the range defined by lMin and lMax. + """ + sValue = self._getStringParam(sName, sDefValue = (str(lDefValue) if lDefValue is not None else None)); + try: + lValue = long(sValue, 0); + except Exception as oXcpt: + raise TestBoxControllerException('%s parameter %s value "%s" cannot be convert to an integer (%s)' \ + % (self._sAction, sName, sValue, oXcpt)); + + if (lMin is not None and lValue < lMin) \ + or (lMax is not None and lValue > lMax): + raise TestBoxControllerException('%s parameter %s value %d is out of range [%s..%s]' \ + % (self._sAction, sName, lValue, lMin, lMax)); + return lValue; + + def _checkForUnknownParameters(self): + """ + Check if we've handled all parameters, raises exception if anything + unknown was found. + """ + + if len(self._asCheckedParams) != len(self._dParams): + sUnknownParams = ''; + for sKey in self._dParams: + if sKey not in self._asCheckedParams: + sUnknownParams += ' ' + sKey + '=' + self._dParams[sKey]; + raise TestBoxControllerException('Unknown parameters: ' + sUnknownParams); + + return True; + + def _writeResponse(self, dParams): + """ + Makes a reply to the testbox script. + Will raise exception on failure. + """ + self._oSrvGlue.writeParams(dParams); + self._oSrvGlue.flush(); + return True; + + def _resultResponse(self, sResultValue): + """ + Makes a simple reply to the testbox script. + Will raise exception on failure. + """ + return self._writeResponse({constants.tbresp.ALL_PARAM_RESULT: sResultValue}); + + + def _idleResponse(self): + """ + Makes an IDLE reply to the testbox script. + Will raise exception on failure. + """ + return self._writeResponse({ constants.tbresp.ALL_PARAM_RESULT: constants.tbresp.CMD_IDLE }); + + def _cleanupOldTest(self, oDb, oStatusData): + """ + Cleans up any old test set that may be left behind and changes the + state to 'idle'. See scenario #9: + file://../../docs/AutomaticTestingRevamp.html#cleaning-up-abandoned-testcase + + Note. oStatusData.enmState is set to idle, but tsUpdated is not changed. + """ + + # Cleanup any abandoned test. + if oStatusData.idTestSet is not None: + SystemLogLogic(oDb).addEntry(SystemLogData.ksEvent_TestSetAbandoned, + "idTestSet=%u idTestBox=%u enmState=%s %s" + % (oStatusData.idTestSet, oStatusData.idTestBox, + oStatusData.enmState, self._sAction), + fCommit = False); + TestSetLogic(oDb).completeAsAbandoned(oStatusData.idTestSet, fCommit = False); + GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox, fCommit = False); + + # Change to idle status + if oStatusData.enmState != TestBoxStatusData.ksTestBoxState_Idle: + TestBoxStatusLogic(oDb).updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False); + oStatusData.tsUpdated = oDb.getCurrentTimestamp(); + oStatusData.enmState = TestBoxStatusData.ksTestBoxState_Idle; + + # Commit. + oDb.commit(); + + return True; + + def _connectToDbAndValidateTb(self, asValidStates = None): + """ + Connects to the database and validates the testbox. + + Returns (TMDatabaseConnection, TestBoxStatusData, TestBoxData) on success. + Returns (None, None, None) on failure after sending the box an appropriate response. + May raise exception on DB error. + """ + oDb = TMDatabaseConnection(self._oSrvGlue.dprint); + oLogic = TestBoxStatusLogic(oDb); + (oStatusData, oTestBoxData) = oLogic.tryFetchStatusAndConfig(self._idTestBox, self._sTestBoxUuid, self._sTestBoxAddr); + if oStatusData is None: + self._resultResponse(constants.tbresp.STATUS_DEAD); + elif asValidStates is not None and oStatusData.enmState not in asValidStates: + self._resultResponse(constants.tbresp.STATUS_NACK); + elif self._idTestSet is not None and self._idTestSet != oStatusData.idTestSet: + self._resultResponse(constants.tbresp.STATUS_NACK); + else: + return (oDb, oStatusData, oTestBoxData); + return (None, None, None); + + def writeToMainLog(self, oTestSet, sText, fIgnoreSizeCheck = False): + """ Writes the text to the main log file. """ + + # Calc the file name and open the file. + sFile = os.path.join(config.g_ksFileAreaRootDir, oTestSet.sBaseFilename + '-main.log'); + if not os.path.exists(os.path.dirname(sFile)): + os.makedirs(os.path.dirname(sFile), 0o755); + + with open(sFile, 'ab') as oFile: + # Check the size. + fSizeOk = True; + if not fIgnoreSizeCheck: + oStat = os.fstat(oFile.fileno()); + fSizeOk = oStat.st_size / (1024 * 1024) < config.g_kcMbMaxMainLog; + + # Write the text. + if fSizeOk: + if sys.version_info[0] >= 3: + oFile.write(bytes(sText, 'utf-8')); + else: + oFile.write(sText); + + return fSizeOk; + + def _actionSignOn(self): # pylint: disable=too-many-locals + """ Implement sign-on """ + + # + # Validate parameters (raises exception on failure). + # + sOs = self._getStringParam(constants.tbreq.SIGNON_PARAM_OS, coreconsts.g_kasOses); + sOsVersion = self._getStringParam(constants.tbreq.SIGNON_PARAM_OS_VERSION); + sCpuVendor = self._getStringParam(constants.tbreq.SIGNON_PARAM_CPU_VENDOR); + sCpuArch = self._getStringParam(constants.tbreq.SIGNON_PARAM_CPU_ARCH, coreconsts.g_kasCpuArches); + sCpuName = self._getStringParam(constants.tbreq.SIGNON_PARAM_CPU_NAME, fStrip = True, sDefValue = ''); # new + lCpuRevision = self._getLongParam( constants.tbreq.SIGNON_PARAM_CPU_REVISION, lMin = 0, lDefValue = 0); # new + cCpus = self._getIntParam( constants.tbreq.SIGNON_PARAM_CPU_COUNT, 1, 16384); + fCpuHwVirt = self._getBoolParam( constants.tbreq.SIGNON_PARAM_HAS_HW_VIRT); + fCpuNestedPaging = self._getBoolParam( constants.tbreq.SIGNON_PARAM_HAS_NESTED_PAGING); + fCpu64BitGuest = self._getBoolParam( constants.tbreq.SIGNON_PARAM_HAS_64_BIT_GUEST, fDefValue = True); + fChipsetIoMmu = self._getBoolParam( constants.tbreq.SIGNON_PARAM_HAS_IOMMU); + fRawMode = self._getBoolParam( constants.tbreq.SIGNON_PARAM_WITH_RAW_MODE, fDefValue = None); + cMbMemory = self._getLongParam( constants.tbreq.SIGNON_PARAM_MEM_SIZE, 8, 1073741823); # 8MB..1PB + cMbScratch = self._getLongParam( constants.tbreq.SIGNON_PARAM_SCRATCH_SIZE, 0, 1073741823); # 0..1PB + sReport = self._getStringParam(constants.tbreq.SIGNON_PARAM_REPORT, fStrip = True, sDefValue = ''); # new + iTestBoxScriptRev = self._getIntParam( constants.tbreq.SIGNON_PARAM_SCRIPT_REV, 1, 100000000); + iPythonHexVersion = self._getIntParam( constants.tbreq.SIGNON_PARAM_PYTHON_VERSION, 0x020300f0, 0x030f00f0); + self._checkForUnknownParameters(); + + # Null conversions for new parameters. + if not sReport: + sReport = None; + if not sCpuName: + sCpuName = None; + if lCpuRevision <= 0: + lCpuRevision = None; + + # + # Connect to the database and validate the testbox. + # + oDb = TMDatabaseConnection(self._oSrvGlue.dprint); + oTestBoxLogic = TestBoxLogic(oDb); + oTestBox = oTestBoxLogic.tryFetchTestBoxByUuid(self._sTestBoxUuid); + if oTestBox is None: + oSystemLogLogic = SystemLogLogic(oDb); + oSystemLogLogic.addEntry(SystemLogData.ksEvent_TestBoxUnknown, + 'addr=%s uuid=%s os=%s %d cpus' \ + % (self._sTestBoxAddr, self._sTestBoxUuid, sOs, cCpus), + 24, fCommit = True); + return self._resultResponse(constants.tbresp.STATUS_NACK); + + # + # Update the row in TestBoxes if something changed. + # + if oTestBox.cMbScratch is not None and oTestBox.cMbScratch != 0: + cPctScratchDiff = (cMbScratch - oTestBox.cMbScratch) * 100 / oTestBox.cMbScratch; + else: + cPctScratchDiff = 100; + + # pylint: disable=too-many-boolean-expressions + if self._sTestBoxAddr != oTestBox.ip \ + or sOs != oTestBox.sOs \ + or sOsVersion != oTestBox.sOsVersion \ + or sCpuVendor != oTestBox.sCpuVendor \ + or sCpuArch != oTestBox.sCpuArch \ + or sCpuName != oTestBox.sCpuName \ + or lCpuRevision != oTestBox.lCpuRevision \ + or cCpus != oTestBox.cCpus \ + or fCpuHwVirt != oTestBox.fCpuHwVirt \ + or fCpuNestedPaging != oTestBox.fCpuNestedPaging \ + or fCpu64BitGuest != oTestBox.fCpu64BitGuest \ + or fChipsetIoMmu != oTestBox.fChipsetIoMmu \ + or fRawMode != oTestBox.fRawMode \ + or cMbMemory != oTestBox.cMbMemory \ + or abs(cPctScratchDiff) >= min(4 + cMbScratch / 10240, 12) \ + or sReport != oTestBox.sReport \ + or iTestBoxScriptRev != oTestBox.iTestBoxScriptRev \ + or iPythonHexVersion != oTestBox.iPythonHexVersion: + oTestBoxLogic.updateOnSignOn(oTestBox.idTestBox, + oTestBox.idGenTestBox, + sTestBoxAddr = self._sTestBoxAddr, + sOs = sOs, + sOsVersion = sOsVersion, + sCpuVendor = sCpuVendor, + sCpuArch = sCpuArch, + sCpuName = sCpuName, + lCpuRevision = lCpuRevision, + cCpus = cCpus, + fCpuHwVirt = fCpuHwVirt, + fCpuNestedPaging = fCpuNestedPaging, + fCpu64BitGuest = fCpu64BitGuest, + fChipsetIoMmu = fChipsetIoMmu, + fRawMode = fRawMode, + cMbMemory = cMbMemory, + cMbScratch = cMbScratch, + sReport = sReport, + iTestBoxScriptRev = iTestBoxScriptRev, + iPythonHexVersion = iPythonHexVersion); + + # + # Update the testbox status, making sure there is a status. + # + oStatusLogic = TestBoxStatusLogic(oDb); + oStatusData = oStatusLogic.tryFetchStatus(oTestBox.idTestBox); + if oStatusData is not None: + self._cleanupOldTest(oDb, oStatusData); + else: + oStatusLogic.insertIdleStatus(oTestBox.idTestBox, oTestBox.idGenTestBox, fCommit = True); + + # + # ACK the request. + # + dResponse = \ + { + constants.tbresp.ALL_PARAM_RESULT: constants.tbresp.STATUS_ACK, + constants.tbresp.SIGNON_PARAM_ID: oTestBox.idTestBox, + constants.tbresp.SIGNON_PARAM_NAME: oTestBox.sName, + } + return self._writeResponse(dResponse); + + def _doGangCleanup(self, oDb, oStatusData): + """ + _doRequestCommand worker for handling a box in gang-cleanup. + This will check if all testboxes has completed their run, pretending to + be busy until that happens. Once all are completed, resources will be + freed and the testbox returns to idle state (we update oStatusData). + """ + oStatusLogic = TestBoxStatusLogic(oDb) + oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet); + if oStatusLogic.isWholeGangDoneTesting(oTestSet.idTestSetGangLeader): + oDb.begin(); + + GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox, fCommit = False); + TestBoxStatusLogic(oDb).updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False); + + oStatusData.tsUpdated = oDb.getCurrentTimestamp(); + oStatusData.enmState = TestBoxStatusData.ksTestBoxState_Idle; + + oDb.commit(); + return None; + + def _doGangGatheringTimedOut(self, oDb, oStatusData): + """ + _doRequestCommand worker for handling a box in gang-gathering-timed-out state. + This will do clean-ups similar to _cleanupOldTest and update the state likewise. + """ + oDb.begin(); + + TestSetLogic(oDb).completeAsGangGatheringTimeout(oStatusData.idTestSet, fCommit = False); + GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox, fCommit = False); + TestBoxStatusLogic(oDb).updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False); + + oStatusData.tsUpdated = oDb.getCurrentTimestamp(); + oStatusData.enmState = TestBoxStatusData.ksTestBoxState_Idle; + + oDb.commit(); + return None; + + def _doGangGathering(self, oDb, oStatusData): + """ + _doRequestCommand worker for handling a box in gang-gathering state. + This only checks for timeout. It will update the oStatusData if a + timeout is detected, so that the box will be idle upon return. + """ + oStatusLogic = TestBoxStatusLogic(oDb); + if oStatusLogic.timeSinceLastChangeInSecs(oStatusData) > config.g_kcSecGangGathering \ + and SchedulerBase.tryCancelGangGathering(oDb, oStatusData): # <-- Updates oStatusData. + self._doGangGatheringTimedOut(oDb, oStatusData); + return None; + + def _doRequestCommand(self, fIdle): + """ + Common code for handling command request. + """ + + (oDb, oStatusData, oTestBoxData) = self._connectToDbAndValidateTb(); + if oDb is None: + return False; + + # + # Status clean up. + # + # Only when BUSY will the TestBox Script request and execute commands + # concurrently. So, it must be idle when sending REQUEST_COMMAND_IDLE. + # + if fIdle: + if oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangGathering: + self._doGangGathering(oDb, oStatusData); + elif oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangGatheringTimedOut: + self._doGangGatheringTimedOut(oDb, oStatusData); + elif oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangTesting: + dResponse = SchedulerBase.composeExecResponse(oDb, oTestBoxData.idTestBox, self._oSrvGlue.getBaseUrl()); + if dResponse is not None: + return dResponse; + elif oStatusData.enmState == TestBoxStatusData.ksTestBoxState_GangCleanup: + self._doGangCleanup(oDb, oStatusData); + elif oStatusData.enmState != TestBoxStatusData.ksTestBoxState_Idle: # (includes ksTestBoxState_GangGatheringTimedOut) + self._cleanupOldTest(oDb, oStatusData); + + # + # Check for pending command. + # + if oTestBoxData.enmPendingCmd != TestBoxData.ksTestBoxCmd_None: + asValidCmds = TestBoxController.kasIdleCmds if fIdle else TestBoxController.kasBusyCmds; + if oTestBoxData.enmPendingCmd in asValidCmds: + dResponse = { constants.tbresp.ALL_PARAM_RESULT: TestBoxController.kdCmdToTbRespCmd[oTestBoxData.enmPendingCmd] }; + if oTestBoxData.enmPendingCmd in [TestBoxData.ksTestBoxCmd_Upgrade, TestBoxData.ksTestBoxCmd_UpgradeAndReboot]: + dResponse[constants.tbresp.UPGRADE_PARAM_URL] = self._oSrvGlue.getBaseUrl() + TestBoxController.ksUpgradeZip; + return self._writeResponse(dResponse); + + if oTestBoxData.enmPendingCmd == TestBoxData.ksTestBoxCmd_Abort and fIdle: + TestBoxLogic(oDb).setCommand(self._idTestBox, sOldCommand = oTestBoxData.enmPendingCmd, + sNewCommand = TestBoxData.ksTestBoxCmd_None, fCommit = True); + + # + # If doing gang stuff, return 'CMD_WAIT'. + # + ## @todo r=bird: Why is GangTesting included here? Figure out when testing gang testing. + if oStatusData.enmState in [TestBoxStatusData.ksTestBoxState_GangGathering, + TestBoxStatusData.ksTestBoxState_GangTesting, + TestBoxStatusData.ksTestBoxState_GangCleanup]: + return self._resultResponse(constants.tbresp.CMD_WAIT); + + # + # If idling and enabled try schedule a new task. + # + if fIdle \ + and oTestBoxData.fEnabled \ + and not TestSetLogic(oDb).isTestBoxExecutingTooRapidly(oTestBoxData.idTestBox) \ + and oStatusData.enmState == TestBoxStatusData.ksTestBoxState_Idle: # (paranoia) + dResponse = SchedulerBase.scheduleNewTask(oDb, oTestBoxData, oStatusData.iWorkItem, self._oSrvGlue.getBaseUrl()); + if dResponse is not None: + return self._writeResponse(dResponse); + + # + # Touch the status row every couple of mins so we can tell that the box is alive. + # + oStatusLogic = TestBoxStatusLogic(oDb); + if oStatusData.enmState != TestBoxStatusData.ksTestBoxState_GangGathering \ + and oStatusLogic.timeSinceLastChangeInSecs(oStatusData) >= TestBoxStatusLogic.kcSecIdleTouchStatus: + oStatusLogic.touchStatus(oTestBoxData.idTestBox, fCommit = True); + + return self._idleResponse(); + + def _actionRequestCommandBusy(self): + """ Implement request for command. """ + self._checkForUnknownParameters(); + return self._doRequestCommand(False); + + def _actionRequestCommandIdle(self): + """ Implement request for command. """ + self._checkForUnknownParameters(); + return self._doRequestCommand(True); + + def _doCommandAckNck(self, sCmd): + """ Implements ACK, NACK and NACK(ENOTSUP). """ + + (oDb, _, _) = self._connectToDbAndValidateTb(); + if oDb is None: + return False; + + # + # If the command maps to a TestBoxCmd_T value, it means we have to + # check and update TestBoxes. If it's an ACK, the testbox status will + # need updating as well. + # + sPendingCmd = TestBoxController.kdTbRespCmdToCmd[sCmd]; + if sPendingCmd is not None: + oTestBoxLogic = TestBoxLogic(oDb) + oTestBoxLogic.setCommand(self._idTestBox, sOldCommand = sPendingCmd, + sNewCommand = TestBoxData.ksTestBoxCmd_None, fCommit = False); + + if self._sAction == constants.tbreq.COMMAND_ACK \ + and TestBoxController.kdCmdToState[sPendingCmd] is not None: + oStatusLogic = TestBoxStatusLogic(oDb); + oStatusLogic.updateState(self._idTestBox, TestBoxController.kdCmdToState[sPendingCmd], fCommit = False); + + # Commit the two updates. + oDb.commit(); + + # + # Log NACKs. + # + if self._sAction != constants.tbreq.COMMAND_ACK: + oSysLogLogic = SystemLogLogic(oDb); + oSysLogLogic.addEntry(SystemLogData.ksEvent_CmdNacked, + 'idTestBox=%s sCmd=%s' % (self._idTestBox, sPendingCmd), + 24, fCommit = True); + + return self._resultResponse(constants.tbresp.STATUS_ACK); + + def _actionCommandAck(self): + """ Implement command ACK'ing """ + sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasAckableCmds); + self._checkForUnknownParameters(); + return self._doCommandAckNck(sCmd); + + def _actionCommandNack(self): + """ Implement command NACK'ing """ + sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasNackableCmds); + self._checkForUnknownParameters(); + return self._doCommandAckNck(sCmd); + + def _actionCommandNotSup(self): + """ Implement command NACK(ENOTSUP)'ing """ + sCmd = self._getStringParam(constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME, TestBoxController.kasNackableCmds); + self._checkForUnknownParameters(); + return self._doCommandAckNck(sCmd); + + def _actionLogMain(self): + """ Implement submitting log entries to the main log file. """ + # + # Parameter validation. + # + sBody = self._getStringParam(constants.tbreq.LOG_PARAM_BODY, fStrip = False); + if not sBody: + return self._resultResponse(constants.tbresp.STATUS_NACK); + self._checkForUnknownParameters(); + + (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing, + TestBoxStatusData.ksTestBoxState_GangTesting]); + if oStatusData is None: + return False; + + # + # Write the text to the log file. + # + oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet); + self.writeToMainLog(oTestSet, sBody); + ## @todo Overflow is a hanging offence, need to note it and fail whatever is going on... + + # Done. + return self._resultResponse(constants.tbresp.STATUS_ACK); + + def _actionUpload(self): + """ Implement uploading of files. """ + # + # Parameter validation. + # + sName = self._getStringParam(constants.tbreq.UPLOAD_PARAM_NAME); + sMime = self._getStringParam(constants.tbreq.UPLOAD_PARAM_MIME); + sKind = self._getStringParam(constants.tbreq.UPLOAD_PARAM_KIND); + sDesc = self._getStringParam(constants.tbreq.UPLOAD_PARAM_DESC); + self._checkForUnknownParameters(); + + (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing, + TestBoxStatusData.ksTestBoxState_GangTesting]); + if oStatusData is None: + return False; + + if len(sName) > 128 or len(sName) < 3: + raise TestBoxControllerException('Invalid file name "%s"' % (sName,)); + if re.match(r'^[a-zA-Z0-9_\-(){}#@+,.=]*$', sName) is None: + raise TestBoxControllerException('Invalid file name "%s"' % (sName,)); + + if sMime not in [ 'text/plain', #'text/html', 'text/xml', + 'application/octet-stream', + 'image/png', #'image/gif', 'image/jpeg', + 'video/webm', #'video/mpeg', 'video/mpeg4-generic', + ]: + raise TestBoxControllerException('Invalid MIME type "%s"' % (sMime,)); + + if sKind not in TestResultFileData.kasKinds: + raise TestBoxControllerException('Invalid kind "%s"' % (sKind,)); + + if len(sDesc) > 256: + raise TestBoxControllerException('Invalid description "%s"' % (sDesc,)); + if not set(sDesc).issubset(set(string.printable)): + raise TestBoxControllerException('Invalid description "%s"' % (sDesc,)); + + if ('application/octet-stream', {}) != self._oSrvGlue.getContentType(): + raise TestBoxControllerException('Unexpected content type: %s; %s' % self._oSrvGlue.getContentType()); + + cbFile = self._oSrvGlue.getContentLength(); + if cbFile <= 0: + raise TestBoxControllerException('File "%s" is empty or negative in size (%s)' % (sName, cbFile)); + if (cbFile + 1048575) / 1048576 > config.g_kcMbMaxUploadSingle: + raise TestBoxControllerException('File "%s" is too big %u bytes (max %u MiB)' + % (sName, cbFile, config.g_kcMbMaxUploadSingle,)); + + # + # Write the text to the log file. + # + oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet); + oDstFile = TestSetLogic(oDb).createFile(oTestSet, sName = sName, sMime = sMime, sKind = sKind, sDesc = sDesc, + cbFile = cbFile, fCommit = True); + + offFile = 0; + oSrcFile = self._oSrvGlue.getBodyIoStreamBinary(); + while offFile < cbFile: + cbToRead = cbFile - offFile; + if cbToRead > 256*1024: + cbToRead = 256*1024; + offFile += cbToRead; + + abBuf = oSrcFile.read(cbToRead); + oDstFile.write(abBuf); # pylint: disable=maybe-no-member + del abBuf; + + oDstFile.close(); # pylint: disable=maybe-no-member + + # Done. + return self._resultResponse(constants.tbresp.STATUS_ACK); + + def _actionXmlResults(self): + """ Implement submitting "XML" like test result stream. """ + # + # Parameter validation. + # + sXml = self._getStringParam(constants.tbreq.XML_RESULT_PARAM_BODY, fStrip = False); + self._checkForUnknownParameters(); + if not sXml: # Used for link check by vboxinstaller.py on Windows. + return self._resultResponse(constants.tbresp.STATUS_ACK); + + (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing, + TestBoxStatusData.ksTestBoxState_GangTesting]); + if oStatusData is None: + return False; + + # + # Process the XML. + # + (sError, fUnforgivable) = TestResultLogic(oDb).processXmlStream(sXml, self._idTestSet); + if sError is not None: + oTestSet = TestSetData().initFromDbWithId(oDb, oStatusData.idTestSet); + self.writeToMainLog(oTestSet, '\n!!XML error: %s\n%s\n\n' % (sError, sXml,)); + if fUnforgivable: + return self._resultResponse(constants.tbresp.STATUS_NACK); + return self._resultResponse(constants.tbresp.STATUS_ACK); + + + def _actionExecCompleted(self): + """ + Implement EXEC completion. + + Because the action is request by the worker thread of the testbox + script we cannot pass pending commands back to it like originally + planned. So, we just complete the test set and update the status. + """ + # + # Parameter validation. + # + sStatus = self._getStringParam(constants.tbreq.EXEC_COMPLETED_PARAM_RESULT, TestBoxController.kasValidResults); + self._checkForUnknownParameters(); + + (oDb, oStatusData, _) = self._connectToDbAndValidateTb([TestBoxStatusData.ksTestBoxState_Testing, + TestBoxStatusData.ksTestBoxState_GangTesting]); + if oStatusData is None: + return False; + + # + # Complete the status. + # + oDb.rollback(); + oDb.begin(); + oTestSetLogic = TestSetLogic(oDb); + idTestSetGangLeader = oTestSetLogic.complete(oStatusData.idTestSet, self.kadTbResultToStatus[sStatus], fCommit = False); + + oStatusLogic = TestBoxStatusLogic(oDb); + if oStatusData.enmState == TestBoxStatusData.ksTestBoxState_Testing: + assert idTestSetGangLeader is None; + GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox); + oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False); + else: + assert idTestSetGangLeader is not None; + oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_GangCleanup, oStatusData.idTestSet, + fCommit = False); + if oStatusLogic.isWholeGangDoneTesting(idTestSetGangLeader): + GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox); + oStatusLogic.updateState(self._idTestBox, TestBoxStatusData.ksTestBoxState_Idle, fCommit = False); + + oDb.commit(); + return self._resultResponse(constants.tbresp.STATUS_ACK); + + + + def _getStandardParams(self, dParams): + """ + Gets the standard parameters and validates them. + + The parameters are returned as a tuple: sAction, idTestBox, sTestBoxUuid. + Note! the sTextBoxId can be None if it's a SIGNON request. + + Raises TestBoxControllerException on invalid input. + """ + # + # Get the action parameter and validate it. + # + if constants.tbreq.ALL_PARAM_ACTION not in dParams: + raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \ + % (constants.tbreq.ALL_PARAM_ACTION, dParams,)); + sAction = dParams[constants.tbreq.ALL_PARAM_ACTION]; + + if sAction not in self._dActions: + raise TestBoxControllerException('Unknown action "%s" in request (params: %s; action: %s)' \ + % (sAction, dParams, self._dActions)); + + # + # TestBox UUID. + # + if constants.tbreq.ALL_PARAM_TESTBOX_UUID not in dParams: + raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \ + % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, dParams,)); + sTestBoxUuid = dParams[constants.tbreq.ALL_PARAM_TESTBOX_UUID]; + try: + sTestBoxUuid = str(uuid.UUID(sTestBoxUuid)); + except Exception as oXcpt: + raise TestBoxControllerException('Invalid %s parameter value "%s": %s ' \ + % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, sTestBoxUuid, oXcpt)); + if sTestBoxUuid == '00000000-0000-0000-0000-000000000000': + raise TestBoxControllerException('Invalid %s parameter value "%s": NULL UUID not allowed.' \ + % (constants.tbreq.ALL_PARAM_TESTBOX_UUID, sTestBoxUuid)); + + # + # TestBox ID. + # + if constants.tbreq.ALL_PARAM_TESTBOX_ID in dParams: + sTestBoxId = dParams[constants.tbreq.ALL_PARAM_TESTBOX_ID]; + try: + idTestBox = int(sTestBoxId); + if idTestBox <= 0 or idTestBox >= 0x7fffffff: + raise Exception; + except: + raise TestBoxControllerException('Bad value for "%s": "%s"' \ + % (constants.tbreq.ALL_PARAM_TESTBOX_ID, sTestBoxId)); + elif sAction == constants.tbreq.SIGNON: + idTestBox = None; + else: + raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \ + % (constants.tbreq.ALL_PARAM_TESTBOX_ID, dParams,)); + + # + # Test Set ID. + # + if constants.tbreq.RESULT_PARAM_TEST_SET_ID in dParams: + sTestSetId = dParams[constants.tbreq.RESULT_PARAM_TEST_SET_ID]; + try: + idTestSet = int(sTestSetId); + if idTestSet <= 0 or idTestSet >= 0x7fffffff: + raise Exception; + except: + raise TestBoxControllerException('Bad value for "%s": "%s"' \ + % (constants.tbreq.RESULT_PARAM_TEST_SET_ID, sTestSetId)); + elif sAction not in [ constants.tbreq.XML_RESULTS, ]: ## More later. + idTestSet = None; + else: + raise TestBoxControllerException('No "%s" parameter in request (params: %s)' \ + % (constants.tbreq.RESULT_PARAM_TEST_SET_ID, dParams,)); + + # + # The testbox address. + # + sTestBoxAddr = self._oSrvGlue.getClientAddr(); + if sTestBoxAddr is None or sTestBoxAddr.strip() == '': + raise TestBoxControllerException('Invalid client address "%s"' % (sTestBoxAddr,)); + + # + # Update the list of checked parameters. + # + self._asCheckedParams.extend([constants.tbreq.ALL_PARAM_TESTBOX_UUID, constants.tbreq.ALL_PARAM_ACTION]); + if idTestBox is not None: + self._asCheckedParams.append(constants.tbreq.ALL_PARAM_TESTBOX_ID); + if idTestSet is not None: + self._asCheckedParams.append(constants.tbreq.RESULT_PARAM_TEST_SET_ID); + + return (sAction, idTestBox, sTestBoxUuid, sTestBoxAddr, idTestSet); + + def dispatchRequest(self): + """ + Dispatches the incoming request. + + Will raise TestBoxControllerException on failure. + """ + + # + # Must be a POST request. + # + try: + sMethod = self._oSrvGlue.getMethod(); + except Exception as oXcpt: + raise TestBoxControllerException('Error retriving request method: %s' % (oXcpt,)); + if sMethod != 'POST': + raise TestBoxControllerException('Error expected POST request not "%s"' % (sMethod,)); + + # + # Get the parameters and checks for duplicates. + # + try: + dParams = self._oSrvGlue.getParameters(); + except Exception as oXcpt: + raise TestBoxControllerException('Error retriving parameters: %s' % (oXcpt,)); + for sKey in dParams.keys(): + if len(dParams[sKey]) > 1: + raise TestBoxControllerException('Parameter "%s" is given multiple times: %s' % (sKey, dParams[sKey])); + dParams[sKey] = dParams[sKey][0]; + self._dParams = dParams; + + # + # Get+validate the standard action parameters and dispatch the request. + # + (self._sAction, self._idTestBox, self._sTestBoxUuid, self._sTestBoxAddr, self._idTestSet) = \ + self._getStandardParams(dParams); + return self._dActions[self._sAction](); diff --git a/src/VBox/ValidationKit/testmanager/core/testboxstatus.py b/src/VBox/ValidationKit/testmanager/core/testboxstatus.py new file mode 100755 index 00000000..75ba0ea9 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/testboxstatus.py @@ -0,0 +1,317 @@ +# -*- coding: utf-8 -*- +# $Id: testboxstatus.py $ + +""" +Test Manager - TestBoxStatus. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import unittest; + +# Validation Kit imports. +from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMTooManyRows, TMRowNotFound; +from testmanager.core.testbox import TestBoxData; + + +class TestBoxStatusData(ModelDataBase): + """ + TestBoxStatus Data. + """ + + ## @name TestBoxState_T + # @{ + ksTestBoxState_Idle = 'idle'; + ksTestBoxState_Testing = 'testing'; + ksTestBoxState_GangGathering = 'gang-gathering'; + ksTestBoxState_GangGatheringTimedOut = 'gang-gathering-timedout'; + ksTestBoxState_GangTesting = 'gang-testing'; + ksTestBoxState_GangCleanup = 'gang-cleanup'; + ksTestBoxState_Rebooting = 'rebooting'; + ksTestBoxState_Upgrading = 'upgrading'; + ksTestBoxState_UpgradingAndRebooting = 'upgrading-and-rebooting'; + ksTestBoxState_DoingSpecialCmd = 'doing-special-cmd'; + ## @} + + ksParam_idTestBox = 'TestBoxStatus_idTestBox'; + ksParam_idGenTestBox = 'TestBoxStatus_idGenTestBox' + ksParam_tsUpdated = 'TestBoxStatus_tsUpdated'; + ksParam_enmState = 'TestBoxStatus_enmState'; + ksParam_idTestSet = 'TestBoxStatus_idTestSet'; + ksParam_iWorkItem = 'TestBoxStatus_iWorkItem'; + + kasAllowNullAttributes = ['idTestSet', ]; + kasValidValues_enmState = \ + [ + ksTestBoxState_Idle, ksTestBoxState_Testing, ksTestBoxState_GangGathering, + ksTestBoxState_GangGatheringTimedOut, ksTestBoxState_GangTesting, ksTestBoxState_GangCleanup, + ksTestBoxState_Rebooting, ksTestBoxState_Upgrading, ksTestBoxState_UpgradingAndRebooting, + ksTestBoxState_DoingSpecialCmd, + ]; + + kcDbColumns = 6; + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.idTestBox = None; + self.idGenTestBox = None; + self.tsUpdated = None; + self.enmState = self.ksTestBoxState_Idle; + self.idTestSet = None; + self.iWorkItem = None; + + def initFromDbRow(self, aoRow): + """ + Internal worker for initFromDbWithId and initFromDbWithGenId as well as + TestBoxStatusLogic. + """ + + if aoRow is None: + raise TMRowNotFound('TestBoxStatus not found.'); + + self.idTestBox = aoRow[0]; + self.idGenTestBox = aoRow[1]; + self.tsUpdated = aoRow[2]; + self.enmState = aoRow[3]; + self.idTestSet = aoRow[4]; + self.iWorkItem = aoRow[5]; + return self; + + def initFromDbWithId(self, oDb, idTestBox): + """ + Initialize the object from the database. + """ + oDb.execute('SELECT *\n' + 'FROM TestBoxStatuses\n' + 'WHERE idTestBox = %s\n' + , (idTestBox, ) ); + return self.initFromDbRow(oDb.fetchOne()); + + def initFromDbWithGenId(self, oDb, idGenTestBox): + """ + Initialize the object from the database. + """ + oDb.execute('SELECT *\n' + 'FROM TestBoxStatuses\n' + 'WHERE idGenTestBox = %s\n' + , (idGenTestBox, ) ); + return self.initFromDbRow(oDb.fetchOne()); + + +class TestBoxStatusLogic(ModelLogicBase): + """ + TestBoxStatus logic. + """ + + ## The number of seconds between each time to call touchStatus() when + # returning CMD_IDLE. + kcSecIdleTouchStatus = 120; + + + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb); + + + def tryFetchStatus(self, idTestBox): + """ + Attempts to fetch the status of the given testbox. + + Returns a TestBoxStatusData object on success. + Returns None if no status was found. + Raises exception on other errors. + """ + self._oDb.execute('SELECT *\n' + 'FROM TestBoxStatuses\n' + 'WHERE idTestBox = %s\n', + (idTestBox,)); + if self._oDb.getRowCount() == 0: + return None; + oStatus = TestBoxStatusData(); + return oStatus.initFromDbRow(self._oDb.fetchOne()); + + def tryFetchStatusAndConfig(self, idTestBox, sTestBoxUuid, sTestBoxAddr): + """ + Tries to fetch the testbox status and current testbox config. + + Returns (TestBoxStatusData, TestBoxData) on success, (None, None) if + not found. May throw an exception on database error. + """ + self._oDb.execute('SELECT TestBoxStatuses.*,\n' + ' TestBoxesWithStrings.*\n' + 'FROM TestBoxStatuses,\n' + ' TestBoxesWithStrings\n' + 'WHERE TestBoxStatuses.idTestBox = %s\n' + ' AND TestBoxesWithStrings.idTestBox = %s\n' + ' AND TestBoxesWithStrings.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND TestBoxesWithStrings.uuidSystem = %s\n' + ' AND TestBoxesWithStrings.ip = %s\n' + , ( idTestBox, + idTestBox, + sTestBoxUuid, + sTestBoxAddr,) ); + cRows = self._oDb.getRowCount(); + if cRows != 1: + if cRows != 0: + raise TMTooManyRows('tryFetchStatusForCommandReq got %s rows for idTestBox=%s' % (cRows, idTestBox)); + return (None, None); + aoRow = self._oDb.fetchOne(); + return (TestBoxStatusData().initFromDbRow(aoRow[:TestBoxStatusData.kcDbColumns]), + TestBoxData().initFromDbRow(aoRow[TestBoxStatusData.kcDbColumns:])); + + + def insertIdleStatus(self, idTestBox, idGenTestBox, fCommit = False): + """ + Inserts an idle status for the specified testbox. + """ + self._oDb.execute('INSERT INTO TestBoxStatuses (\n' + ' idTestBox,\n' + ' idGenTestBox,\n' + ' enmState,\n' + ' idTestSet,\n' + ' iWorkItem)\n' + 'VALUES ( %s,\n' + ' %s,\n' + ' \'idle\'::TestBoxState_T,\n' + ' NULL,\n' + ' 0)\n' + , (idTestBox, idGenTestBox) ); + self._oDb.maybeCommit(fCommit); + return True; + + def touchStatus(self, idTestBox, fCommit = False): + """ + Touches the testbox status row, i.e. sets tsUpdated to the current time. + """ + self._oDb.execute('UPDATE TestBoxStatuses\n' + 'SET tsUpdated = CURRENT_TIMESTAMP\n' + 'WHERE idTestBox = %s\n' + , (idTestBox,)); + self._oDb.maybeCommit(fCommit); + return True; + + def updateState(self, idTestBox, sNewState, idTestSet = None, fCommit = False): + """ + Updates the testbox state. + """ + self._oDb.execute('UPDATE TestBoxStatuses\n' + 'SET enmState = %s,\n' + ' idTestSet = %s,\n' + ' tsUpdated = CURRENT_TIMESTAMP\n' + 'WHERE idTestBox = %s\n', + (sNewState, idTestSet, idTestBox)); + self._oDb.maybeCommit(fCommit); + return True; + + def updateGangStatus(self, idTestSetGangLeader, sNewState, fCommit = False): + """ + Update the state of all members of a gang. + """ + self._oDb.execute('UPDATE TestBoxStatuses\n' + 'SET enmState = %s,\n' + ' tsUpdated = CURRENT_TIMESTAMP\n' + 'WHERE idTestBox IN (SELECT idTestBox\n' + ' FROM TestSets\n' + ' WHERE idTestSetGangLeader = %s)\n' + , (sNewState, idTestSetGangLeader,) ); + self._oDb.maybeCommit(fCommit); + return True; + + def updateWorkItem(self, idTestBox, iWorkItem, fCommit = False): + """ + Updates the testbox state. + """ + self._oDb.execute('UPDATE TestBoxStatuses\n' + 'SET iWorkItem = %s\n' + 'WHERE idTestBox = %s\n' + , ( iWorkItem, idTestBox,)); + self._oDb.maybeCommit(fCommit); + return True; + + def isWholeGangDoneTesting(self, idTestSetGangLeader): + """ + Checks if the whole gang is done testing. + """ + self._oDb.execute('SELECT COUNT(*)\n' + 'FROM TestBoxStatuses, TestSets\n' + 'WHERE TestBoxStatuses.idTestSet = TestSets.idTestSet\n' + ' AND TestSets.idTestSetGangLeader = %s\n' + ' AND TestBoxStatuses.enmState IN (%s, %s)\n' + , ( idTestSetGangLeader, + TestBoxStatusData.ksTestBoxState_GangGathering, + TestBoxStatusData.ksTestBoxState_GangTesting)); + return self._oDb.fetchOne()[0] == 0; + + def isTheWholeGangThere(self, idTestSetGangLeader): + """ + Checks if the whole gang is done testing. + """ + self._oDb.execute('SELECT COUNT(*)\n' + 'FROM TestBoxStatuses, TestSets\n' + 'WHERE TestBoxStatuses.idTestSet = TestSets.idTestSet\n' + ' AND TestSets.idTestSetGangLeader = %s\n' + ' AND TestBoxStatuses.enmState IN (%s, %s)\n' + , ( idTestSetGangLeader, + TestBoxStatusData.ksTestBoxState_GangGathering, + TestBoxStatusData.ksTestBoxState_GangTesting)); + return self._oDb.fetchOne()[0] == 0; + + def timeSinceLastChangeInSecs(self, oStatusData): + """ + Figures the time since the last status change. + """ + tsNow = self._oDb.getCurrentTimestamp(); + oDelta = tsNow - oStatusData.tsUpdated; + return oDelta.seconds + oDelta.days * 24 * 3600; + + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +class TestBoxStatusDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [TestBoxStatusData(),]; + +if __name__ == '__main__': + unittest.main(); + # not reached. + diff --git a/src/VBox/ValidationKit/testmanager/core/testcase.pgsql b/src/VBox/ValidationKit/testmanager/core/testcase.pgsql new file mode 100644 index 00000000..aea81449 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/testcase.pgsql @@ -0,0 +1,275 @@ +-- $Id: testcase.pgsql $ +--- @file +-- VBox Test Manager Database Stored Procedures - TestCases. +-- + +-- +-- Copyright (C) 2012-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + +\set ON_ERROR_STOP 1 +\connect testmanager; + +DROP FUNCTION IF EXISTS add_testcase(INTEGER, TEXT, TEXT, BOOLEAN, INTEGER, TEXT, TEXT); +DROP FUNCTION IF EXISTS edit_testcase(INTEGER, INTEGER, TEXT, TEXT, BOOLEAN, INTEGER, TEXT, TEXT); +DROP FUNCTION IF EXISTS del_testcase(INTEGER); +DROP FUNCTION IF EXISTS TestCaseLogic_delEntry(INTEGER, INTEGER); +DROP FUNCTION IF EXISTS TestCaseLogic_addEntry(a_uidAuthor INTEGER, a_sName TEXT, a_sDescription TEXT, + a_fEnabled BOOL, a_cSecTimeout INTEGER, a_sTestBoxReqExpr TEXT, + a_sBuildReqExpr TEXT, a_sBaseCmd TEXT, a_sTestSuiteZips TEXT); +DROP FUNCTION IF EXISTS TestCaseLogic_editEntry(a_uidAuthor INTEGER, a_idTestCase INTEGER, a_sName TEXT, a_sDescription TEXT, + a_fEnabled BOOL, a_cSecTimeout INTEGER, a_sTestBoxReqExpr TEXT, + a_sBuildReqExpr TEXT, a_sBaseCmd TEXT, a_sTestSuiteZips TEXT); + +--- +-- Checks if the test case name is unique, ignoring a_idTestCaseIgnore. +-- Raises exception if duplicates are found. +-- +-- @internal +-- +CREATE OR REPLACE FUNCTION TestCaseLogic_checkUniqueName(a_sName TEXT, a_idTestCaseIgnore INTEGER) + RETURNS VOID AS $$ + DECLARE + v_cRows INTEGER; + BEGIN + SELECT COUNT(*) INTO v_cRows + FROM TestCases + WHERE sName = a_sName + AND tsExpire = 'infinity'::TIMESTAMP + AND idTestCase <> a_idTestCaseIgnore; + IF v_cRows <> 0 THEN + RAISE EXCEPTION 'Duplicate test case name "%" (% times)', a_sName, v_cRows; + END IF; + END; +$$ LANGUAGE plpgsql; + +--- +-- Check that the test case exists. +-- Raises exception if it doesn't. +-- +-- @internal +-- +CREATE OR REPLACE FUNCTION TestCaseLogic_checkExists(a_idTestCase INTEGER) RETURNS VOID AS $$ + BEGIN + IF NOT EXISTS( SELECT * + FROM TestCases + WHERE idTestCase = a_idTestCase + AND tsExpire = 'infinity'::TIMESTAMP ) THEN + RAISE EXCEPTION 'Test case with ID % does not currently exist', a_idTestCase; + END IF; + END; +$$ LANGUAGE plpgsql; + + +--- +-- Historize a row. +-- @internal +-- +CREATE OR REPLACE FUNCTION TestCaseLogic_historizeEntry(a_idTestCase INTEGER, a_tsExpire TIMESTAMP WITH TIME ZONE) + RETURNS VOID AS $$ + DECLARE + v_cUpdatedRows INTEGER; + BEGIN + UPDATE TestCases + SET tsExpire = a_tsExpire + WHERE idTestcase = a_idTestCase + AND tsExpire = 'infinity'::TIMESTAMP; + GET DIAGNOSTICS v_cUpdatedRows = ROW_COUNT; + IF v_cUpdatedRows <> 1 THEN + IF v_cUpdatedRows = 0 THEN + RAISE EXCEPTION 'Test case ID % does not currently exist', a_idTestCase; + END IF; + RAISE EXCEPTION 'Integrity error in TestCases: % current rows with idTestCase=%d', v_cUpdatedRows, a_idTestCase; + END IF; + END; +$$ LANGUAGE plpgsql; + + +CREATE OR REPLACE function TestCaseLogic_addEntry(a_uidAuthor INTEGER, a_sName TEXT, a_sDescription TEXT, + a_fEnabled BOOL, a_cSecTimeout INTEGER, a_sTestBoxReqExpr TEXT, + a_sBuildReqExpr TEXT, a_sBaseCmd TEXT, a_sTestSuiteZips TEXT, + a_sComment TEXT) + RETURNS INTEGER AS $$ + DECLARE + v_idTestCase INTEGER; + BEGIN + PERFORM TestCaseLogic_checkUniqueName(a_sName, -1); + + INSERT INTO TestCases (uidAuthor, sName, sDescription, fEnabled, cSecTimeout, + sTestBoxReqExpr, sBuildReqExpr, sBaseCmd, sTestSuiteZips, sComment) + VALUES (a_uidAuthor, a_sName, a_sDescription, a_fEnabled, a_cSecTimeout, + a_sTestBoxReqExpr, a_sBuildReqExpr, a_sBaseCmd, a_sTestSuiteZips, a_sComment) + RETURNING idTestcase INTO v_idTestCase; + RETURN v_idTestCase; + END; +$$ LANGUAGE plpgsql; + + +CREATE OR REPLACE function TestCaseLogic_editEntry(a_uidAuthor INTEGER, a_idTestCase INTEGER, a_sName TEXT, a_sDescription TEXT, + a_fEnabled BOOL, a_cSecTimeout INTEGER, a_sTestBoxReqExpr TEXT, + a_sBuildReqExpr TEXT, a_sBaseCmd TEXT, a_sTestSuiteZips TEXT, + a_sComment TEXT) + RETURNS INTEGER AS $$ + DECLARE + v_idGenTestCase INTEGER; + BEGIN + PERFORM TestCaseLogic_checkExists(a_idTestCase); + PERFORM TestCaseLogic_checkUniqueName(a_sName, a_idTestCase); + + PERFORM TestCaseLogic_historizeEntry(a_idTestCase, CURRENT_TIMESTAMP); + INSERT INTO TestCases (idTestCase, uidAuthor, sName, sDescription, fEnabled, cSecTimeout, + sTestBoxReqExpr, sBuildReqExpr, sBaseCmd, sTestSuiteZips, sComment) + VALUES (a_idTestCase, a_uidAuthor, a_sName, a_sDescription, a_fEnabled, a_cSecTimeout, + a_sTestBoxReqExpr, a_sBuildReqExpr, a_sBaseCmd, a_sTestSuiteZips, a_sComment) + RETURNING idGenTestCase INTO v_idGenTestCase; + RETURN v_idGenTestCase; + END; +$$ LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION TestCaseLogic_delEntry(a_uidAuthor INTEGER, a_idTestCase INTEGER, a_fCascade BOOLEAN) + RETURNS VOID AS $$ + DECLARE + v_Row TestCases%ROWTYPE; + v_tsEffective TIMESTAMP WITH TIME ZONE; + v_Rec RECORD; + v_sErrors TEXT; + BEGIN + -- + -- Check preconditions. + -- + IF a_fCascade <> TRUE THEN + IF EXISTS( SELECT * + FROM TestCaseDeps + WHERE idTestCasePreReq = a_idTestCase + AND tsExpire = 'infinity'::TIMESTAMP ) THEN + v_sErrors := ''; + FOR v_Rec IN + SELECT TestCases.idTestCase AS idTestCase, + TestCases.sName AS sName + FROM TestCaseDeps, TestCases + WHERE TestCaseDeps.idTestCasePreReq = a_idTestCase + AND TestCaseDeps.tsExpire = 'infinity'::TIMESTAMP + AND TestCases.idTestCase = TestCaseDeps.idTestCase + AND TestCases.tsExpire = 'infinity'::TIMESTAMP + LOOP + IF v_sErrors <> '' THEN + v_sErrors := v_sErrors || ', '; + END IF; + v_sErrors := v_sErrors || v_Rec.sName || ' (idTestCase=' || v_Rec.idTestCase || ')'; + END LOOP; + RAISE EXCEPTION 'Other test cases depends on test case with ID %: % ', a_idTestCase, v_sErrors; + END IF; + + IF EXISTS( SELECT * + FROM TestGroupMembers + WHERE idTestCase = a_idTestCase + AND tsExpire = 'infinity'::TIMESTAMP ) THEN + v_sErrors := ''; + FOR v_Rec IN + SELECT TestGroups.idTestGroup AS idTestGroup, + TestGroups.sName AS sName + FROM TestGroupMembers, TestGroups + WHERE TestGroupMembers.idTestCase = a_idTestCase + AND TestGroupMembers.tsExpire = 'infinity'::TIMESTAMP + AND TestGroupMembers.idTestGroup = TestGroups.idTestGroup + AND TestGroups.tsExpire = 'infinity'::TIMESTAMP + LOOP + IF v_sErrors <> '' THEN + v_sErrors := v_sErrors || ', '; + END IF; + v_sErrors := v_sErrors || v_Rec.sName || ' (idTestGroup=' || v_Rec.idTestGroup || ')'; + END LOOP; + RAISE EXCEPTION 'Test case with ID % is member of the following test group(s): % ', a_idTestCase, v_sErrors; + END IF; + END IF; + + -- + -- To preserve the information about who deleted the record, we try to + -- add a dummy record which expires immediately. I say try because of + -- the primary key, we must let the new record be valid for 1 us. :-( + -- + SELECT * INTO STRICT v_Row + FROM TestCases + WHERE idTestCase = a_idTestCase + AND tsExpire = 'infinity'::TIMESTAMP; + + v_tsEffective := CURRENT_TIMESTAMP - INTERVAL '1 microsecond'; + IF v_Row.tsEffective < v_tsEffective THEN + PERFORM TestCaseLogic_historizeEntry(a_idTestCase, v_tsEffective); + v_Row.tsEffective := v_tsEffective; + v_Row.tsExpire := CURRENT_TIMESTAMP; + v_Row.uidAuthor := a_uidAuthor; + SELECT NEXTVAL('TestCaseGenIdSeq') INTO v_Row.idGenTestCase; + INSERT INTO TestCases VALUES (v_Row.*); + ELSE + PERFORM TestCaseLogic_historizeEntry(a_idTestCase, CURRENT_TIMESTAMP); + END IF; + + -- + -- Delete arguments, test case dependencies and resource dependencies. + -- (We don't bother recording who deleted the records here since it's + -- a lot of work and sufficiently covered in the TestCases table.) + -- + UPDATE TestCaseArgs + SET tsExpire = CURRENT_TIMESTAMP + WHERE idTestCase = a_idTestCase + AND tsExpire = 'infinity'::TIMESTAMP; + + UPDATE TestCaseDeps + SET tsExpire = CURRENT_TIMESTAMP + WHERE idTestCase = a_idTestCase + AND tsExpire = 'infinity'::TIMESTAMP; + + UPDATE TestCaseGlobalRsrcDeps + SET tsExpire = CURRENT_TIMESTAMP + WHERE idTestCase = a_idTestCase + AND tsExpire = 'infinity'::TIMESTAMP; + + IF a_fCascade = TRUE THEN + UPDATE TestCaseDeps + SET tsExpire = CURRENT_TIMESTAMP + WHERE idTestCasePreReq = a_idTestCase + AND tsExpire = 'infinity'::TIMESTAMP; + + UPDATE TestGroupMembers + SET tsExpire = CURRENT_TIMESTAMP + WHERE idTestCase = a_idTestCase + AND tsExpire = 'infinity'::TIMESTAMP; + END IF; + + EXCEPTION + WHEN NO_DATA_FOUND THEN + RAISE EXCEPTION 'Test case with ID % does not currently exist', a_idTestCase; + WHEN TOO_MANY_ROWS THEN + RAISE EXCEPTION 'Integrity error in TestCases: Too many current rows for %', a_idTestCase; + END; +$$ LANGUAGE plpgsql; + diff --git a/src/VBox/ValidationKit/testmanager/core/testcase.py b/src/VBox/ValidationKit/testmanager/core/testcase.py new file mode 100755 index 00000000..e1176f80 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/testcase.py @@ -0,0 +1,1467 @@ +# -*- coding: utf-8 -*- +# $Id: testcase.py $ +# pylint: disable=too-many-lines + +""" +Test Manager - Test Case. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard python imports. +import copy; +import sys; +import unittest; + +# Validation Kit imports. +from common import utils; +from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMExceptionBase, \ + TMInvalidData, TMRowNotFound, ChangeLogEntry, AttributeChangeEntry; +from testmanager.core.globalresource import GlobalResourceData; +from testmanager.core.useraccount import UserAccountLogic; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + + + +class TestCaseGlobalRsrcDepData(ModelDataBase): + """ + Test case dependency on a global resource - data. + """ + + ksParam_idTestCase = 'TestCaseDependency_idTestCase'; + ksParam_idGlobalRsrc = 'TestCaseDependency_idGlobalRsrc'; + ksParam_tsEffective = 'TestCaseDependency_tsEffective'; + ksParam_tsExpire = 'TestCaseDependency_tsExpire'; + ksParam_uidAuthor = 'TestCaseDependency_uidAuthor'; + + kasAllowNullAttributes = ['idTestSet', ]; + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.idTestCase = None; + self.idGlobalRsrc = None; + self.tsEffective = None; + self.tsExpire = None; + self.uidAuthor = None; + + def initFromDbRow(self, aoRow): + """ + Reinitialize from a SELECT * FROM TestCaseDeps row. + """ + if aoRow is None: + raise TMRowNotFound('Test case not found.'); + + self.idTestCase = aoRow[0]; + self.idGlobalRsrc = aoRow[1]; + self.tsEffective = aoRow[2]; + self.tsExpire = aoRow[3]; + self.uidAuthor = aoRow[4]; + return self; + + +class TestCaseGlobalRsrcDepLogic(ModelLogicBase): + """ + Test case dependency on a global resources - logic. + """ + + def getTestCaseDeps(self, idTestCase, tsNow = None): + """ + Returns an array of (TestCaseGlobalRsrcDepData, GlobalResourceData) + with the global resources required by idTestCase. + Returns empty array if none found. Raises exception on database error. + + Note! Maybe a bit overkill... + """ + ## @todo This code isn't entirely kosher... Should use a DataEx with a oGlobalRsrc = GlobalResourceData(). + if tsNow is not None: + self._oDb.execute('SELECT *\n' + 'FROM TestCaseGlobalRsrcDeps, GlobalResources\n' + 'WHERE TestCaseGlobalRsrcDeps.idTestCase = %s\n' + ' AND TestCaseGlobalRsrcDeps.tsExpire > %s\n' + ' AND TestCaseGlobalRsrcDeps.tsEffective <= %s\n' + ' AND GlobalResources.idGlobalRsrc = TestCaseGlobalRsrcDeps.idGlobalRsrc\n' + ' AND GlobalResources.tsExpire > %s\n' + ' AND GlobalResources.tsEffective <= %s\n' + , (idTestCase, tsNow, tsNow, tsNow, tsNow) ); + else: + self._oDb.execute('SELECT *\n' + 'FROM TestCaseGlobalRsrcDeps, GlobalResources\n' + 'WHERE TestCaseGlobalRsrcDeps.idTestCase = %s\n' + ' AND GlobalResources.idGlobalRsrc = TestCaseGlobalRsrcDeps.idGlobalRsrc\n' + ' AND TestCaseGlobalRsrcDeps.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND GlobalResources.tsExpire = \'infinity\'::TIMESTAMP\n' + , (idTestCase,)) + aaoRows = self._oDb.fetchAll(); + aoRet = [] + for aoRow in aaoRows: + oItem = [TestCaseDependencyData().initFromDbRow(aoRow), + GlobalResourceData().initFromDbRow(aoRow[5:])]; + aoRet.append(oItem); + + return aoRet + + def getTestCaseDepsIds(self, idTestCase, tsNow = None): + """ + Returns an array of global resources that idTestCase require. + Returns empty array if none found. Raises exception on database error. + """ + if tsNow is not None: + self._oDb.execute('SELECT idGlobalRsrc\n' + 'FROM TestCaseGlobalRsrcDeps\n' + 'WHERE TestCaseGlobalRsrcDeps.idTestCase = %s\n' + ' AND TestCaseGlobalRsrcDeps.tsExpire > %s\n' + ' AND TestCaseGlobalRsrcDeps.tsEffective <= %s\n' + , (idTestCase, tsNow, tsNow, ) ); + else: + self._oDb.execute('SELECT idGlobalRsrc\n' + 'FROM TestCaseGlobalRsrcDeps\n' + 'WHERE TestCaseGlobalRsrcDeps.idTestCase = %s\n' + ' AND TestCaseGlobalRsrcDeps.tsExpire = \'infinity\'::TIMESTAMP\n' + , (idTestCase,)) + aidGlobalRsrcs = [] + for aoRow in self._oDb.fetchAll(): + aidGlobalRsrcs.append(aoRow[0]); + return aidGlobalRsrcs; + + + def getDepGlobalResourceData(self, idTestCase, tsNow = None): + """ + Returns an array of objects of type GlobalResourceData on which the + specified test case depends on. + """ + if tsNow is None : + self._oDb.execute('SELECT GlobalResources.*\n' + 'FROM TestCaseGlobalRsrcDeps, GlobalResources\n' + 'WHERE TestCaseGlobalRsrcDeps.idTestCase = %s\n' + ' AND GlobalResources.idGlobalRsrc = TestCaseGlobalRsrcDeps.idGlobalRsrc\n' + ' AND TestCaseGlobalRsrcDeps.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND GlobalResources.tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY GlobalResources.idGlobalRsrc\n' + , (idTestCase,)) + else: + self._oDb.execute('SELECT GlobalResources.*\n' + 'FROM TestCaseGlobalRsrcDeps, GlobalResources\n' + 'WHERE TestCaseGlobalRsrcDeps.idTestCase = %s\n' + ' AND GlobalResources.idGlobalRsrc = TestCaseGlobalRsrcDeps.idGlobalRsrc\n' + ' AND TestCaseGlobalRsrcDeps.tsExpire > %s\n' + ' AND TestCaseGlobalRsrcDeps.tsExpire <= %s\n' + ' AND GlobalResources.tsExpire > %s\n' + ' AND GlobalResources.tsEffective <= %s\n' + 'ORDER BY GlobalResources.idGlobalRsrc\n' + , (idTestCase, tsNow, tsNow, tsNow, tsNow)); + + aaoRows = self._oDb.fetchAll() + aoRet = [] + for aoRow in aaoRows: + aoRet.append(GlobalResourceData().initFromDbRow(aoRow)); + + return aoRet + + +class TestCaseDependencyData(ModelDataBase): + """ + Test case dependency data + """ + + ksParam_idTestCase = 'TestCaseDependency_idTestCase'; + ksParam_idTestCasePreReq = 'TestCaseDependency_idTestCasePreReq'; + ksParam_tsEffective = 'TestCaseDependency_tsEffective'; + ksParam_tsExpire = 'TestCaseDependency_tsExpire'; + ksParam_uidAuthor = 'TestCaseDependency_uidAuthor'; + + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.idTestCase = None; + self.idTestCasePreReq = None; + self.tsEffective = None; + self.tsExpire = None; + self.uidAuthor = None; + + def initFromDbRow(self, aoRow): + """ + Reinitialize from a SELECT * FROM TestCaseDeps row. + """ + if aoRow is None: + raise TMRowNotFound('Test case not found.'); + + self.idTestCase = aoRow[0]; + self.idTestCasePreReq = aoRow[1]; + self.tsEffective = aoRow[2]; + self.tsExpire = aoRow[3]; + self.uidAuthor = aoRow[4]; + return self; + + def initFromParams(self, oDisp, fStrict=True): + """ + Initialize the object from parameters. + The input is not validated at all, except that all parameters must be + present when fStrict is True. + Note! Returns parameter NULL values, not database ones. + """ + + self.convertToParamNull(); + fn = oDisp.getStringParam; # Shorter... + + self.idTestCase = fn(self.ksParam_idTestCase, None, None if fStrict else self.idTestCase); + self.idTestCasePreReq = fn(self.ksParam_idTestCasePreReq, None, None if fStrict else self.idTestCasePreReq); + self.tsEffective = fn(self.ksParam_tsEffective, None, None if fStrict else self.tsEffective); + self.tsExpire = fn(self.ksParam_tsExpire, None, None if fStrict else self.tsExpire); + self.uidAuthor = fn(self.ksParam_uidAuthor, None, None if fStrict else self.uidAuthor); + + return True + + def validateAndConvert(self, oDb = None, enmValidateFor = ModelDataBase.ksValidateFor_Other): + """ + Validates the input and converts valid fields to their right type. + Returns a dictionary with per field reports, only invalid fields will + be returned, so an empty dictionary means that the data is valid. + + The dictionary keys are ksParam_*. + """ + dErrors = {} + + self.idTestCase = self._validateInt( dErrors, self.ksParam_idTestCase, self.idTestCase); + self.idTestCasePreReq = self._validateInt( dErrors, self.ksParam_idTestCasePreReq, self.idTestCasePreReq); + self.tsEffective = self._validateTs( dErrors, self.ksParam_tsEffective, self.tsEffective); + self.tsExpire = self._validateTs( dErrors, self.ksParam_tsExpire, self.tsExpire); + self.uidAuthor = self._validateInt( dErrors, self.ksParam_uidAuthor, self.uidAuthor); + + _ = oDb; + _ = enmValidateFor; + return dErrors + + def convertFromParamNull(self): + """ + Converts from parameter NULL values to database NULL values (None). + """ + if self.idTestCase in [-1, '']: self.idTestCase = None; + if self.idTestCasePreReq in [-1, '']: self.idTestCasePreReq = None; + if self.tsEffective == '': self.tsEffective = None; + if self.tsExpire == '': self.tsExpire = None; + if self.uidAuthor in [-1, '']: self.uidAuthor = None; + return True; + + def convertToParamNull(self): + """ + Converts from database NULL values (None) to special values we can + pass thru parameters list. + """ + if self.idTestCase is None: self.idTestCase = -1; + if self.idTestCasePreReq is None: self.idTestCasePreReq = -1; + if self.tsEffective is None: self.tsEffective = ''; + if self.tsExpire is None: self.tsExpire = ''; + if self.uidAuthor is None: self.uidAuthor = -1; + return True; + + def isEqual(self, oOther): + """ Compares two instances. """ + return self.idTestCase == oOther.idTestCase \ + and self.idTestCasePreReq == oOther.idTestCasePreReq \ + and self.tsEffective == oOther.tsEffective \ + and self.tsExpire == oOther.tsExpire \ + and self.uidAuthor == oOther.uidAuthor; + + def getTestCasePreReqIds(self, aTestCaseDependencyData): + """ + Get list of Test Case IDs which current + Test Case depends on + """ + if not aTestCaseDependencyData: + return [] + + aoRet = [] + for oTestCaseDependencyData in aTestCaseDependencyData: + aoRet.append(oTestCaseDependencyData.idTestCasePreReq) + + return aoRet + +class TestCaseDependencyLogic(ModelLogicBase): + """Test case dependency management logic""" + + def getTestCaseDeps(self, idTestCase, tsEffective = None): + """ + Returns an array of TestCaseDependencyData with the prerequisites of + idTestCase. + Returns empty array if none found. Raises exception on database error. + """ + if tsEffective is not None: + self._oDb.execute('SELECT *\n' + 'FROM TestCaseDeps\n' + 'WHERE idTestCase = %s\n' + ' AND tsExpire > %s\n' + ' AND tsEffective <= %s\n' + , (idTestCase, tsEffective, tsEffective, ) ); + else: + self._oDb.execute('SELECT *\n' + 'FROM TestCaseDeps\n' + 'WHERE idTestCase = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idTestCase, ) ); + aaoRows = self._oDb.fetchAll(); + aoRet = []; + for aoRow in aaoRows: + aoRet.append(TestCaseDependencyData().initFromDbRow(aoRow)); + + return aoRet + + def getTestCaseDepsIds(self, idTestCase, tsNow = None): + """ + Returns an array of test case IDs of the prerequisites of idTestCase. + Returns empty array if none found. Raises exception on database error. + """ + if tsNow is not None: + self._oDb.execute('SELECT idTestCase\n' + 'FROM TestCaseDeps\n' + 'WHERE idTestCase = %s\n' + ' AND tsExpire > %s\n' + ' AND tsEffective <= %s\n' + , (idTestCase, tsNow, tsNow, ) ); + else: + self._oDb.execute('SELECT idTestCase\n' + 'FROM TestCaseDeps\n' + 'WHERE idTestCase = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idTestCase, ) ); + aidPreReqs = []; + for aoRow in self._oDb.fetchAll(): + aidPreReqs.append(aoRow[0]); + return aidPreReqs; + + + def getDepTestCaseData(self, idTestCase, tsNow = None): + """ + Returns an array of objects of type TestCaseData2 on which + specified test case depends on + """ + if tsNow is None: + self._oDb.execute('SELECT TestCases.*\n' + 'FROM TestCases, TestCaseDeps\n' + 'WHERE TestCaseDeps.idTestCase = %s\n' + ' AND TestCaseDeps.idTestCasePreReq = TestCases.idTestCase\n' + ' AND TestCaseDeps.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND TestCases.tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY TestCases.idTestCase\n' + , (idTestCase, ) ); + else: + self._oDb.execute('SELECT TestCases.*\n' + 'FROM TestCases, TestCaseDeps\n' + 'WHERE TestCaseDeps.idTestCase = %s\n' + ' AND TestCaseDeps.idTestCasePreReq = TestCases.idTestCase\n' + ' AND TestCaseDeps.tsExpire > %s\n' + ' AND TestCaseDeps.tsEffective <= %s\n' + ' AND TestCases.tsExpire > %s\n' + ' AND TestCases.tsEffective <= %s\n' + 'ORDER BY TestCases.idTestCase\n' + , (idTestCase, tsNow, tsNow, tsNow, tsNow, ) ); + + aaoRows = self._oDb.fetchAll() + aoRet = [] + for aoRow in aaoRows: + aoRet.append(TestCaseData().initFromDbRow(aoRow)); + + return aoRet + + def getApplicableDepTestCaseData(self, idTestCase): + """ + Returns an array of objects of type TestCaseData on which + specified test case might depends on (all test + cases except the specified one and those testcases which are + depend on idTestCase) + """ + self._oDb.execute('SELECT *\n' + 'FROM TestCases\n' + 'WHERE idTestCase <> %s\n' + ' AND idTestCase NOT IN (SELECT idTestCase\n' + ' FROM TestCaseDeps\n' + ' WHERE idTestCasePreReq=%s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP)\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idTestCase, idTestCase) ) + + aaoRows = self._oDb.fetchAll() + aoRet = [] + for aoRow in aaoRows: + aoRet.append(TestCaseData().initFromDbRow(aoRow)); + + return aoRet + +class TestCaseData(ModelDataBase): + """ + Test case data + """ + + ksIdAttr = 'idTestCase'; + ksIdGenAttr = 'idGenTestCase'; + + ksParam_idTestCase = 'TestCase_idTestCase' + ksParam_tsEffective = 'TestCase_tsEffective' + ksParam_tsExpire = 'TestCase_tsExpire' + ksParam_uidAuthor = 'TestCase_uidAuthor' + ksParam_idGenTestCase = 'TestCase_idGenTestCase' + ksParam_sName = 'TestCase_sName' + ksParam_sDescription = 'TestCase_sDescription' + ksParam_fEnabled = 'TestCase_fEnabled' + ksParam_cSecTimeout = 'TestCase_cSecTimeout' + ksParam_sTestBoxReqExpr = 'TestCase_sTestBoxReqExpr'; + ksParam_sBuildReqExpr = 'TestCase_sBuildReqExpr'; + ksParam_sBaseCmd = 'TestCase_sBaseCmd' + ksParam_sValidationKitZips = 'TestCase_sValidationKitZips' + ksParam_sComment = 'TestCase_sComment' + + kasAllowNullAttributes = [ 'idTestCase', 'tsEffective', 'tsExpire', 'uidAuthor', 'idGenTestCase', 'sDescription', + 'sTestBoxReqExpr', 'sBuildReqExpr', 'sValidationKitZips', 'sComment' ]; + + kcDbColumns = 14; + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.idTestCase = None; + self.tsEffective = None; + self.tsExpire = None; + self.uidAuthor = None; + self.idGenTestCase = None; + self.sName = None; + self.sDescription = None; + self.fEnabled = False; + self.cSecTimeout = 10; # Init with minimum timeout value + self.sTestBoxReqExpr = None; + self.sBuildReqExpr = None; + self.sBaseCmd = None; + self.sValidationKitZips = None; + self.sComment = None; + + def initFromDbRow(self, aoRow): + """ + Reinitialize from a SELECT * FROM TestCases row. + Returns self. Raises exception if no row. + """ + if aoRow is None: + raise TMRowNotFound('Test case not found.'); + + self.idTestCase = aoRow[0]; + self.tsEffective = aoRow[1]; + self.tsExpire = aoRow[2]; + self.uidAuthor = aoRow[3]; + self.idGenTestCase = aoRow[4]; + self.sName = aoRow[5]; + self.sDescription = aoRow[6]; + self.fEnabled = aoRow[7]; + self.cSecTimeout = aoRow[8]; + self.sTestBoxReqExpr = aoRow[9]; + self.sBuildReqExpr = aoRow[10]; + self.sBaseCmd = aoRow[11]; + self.sValidationKitZips = aoRow[12]; + self.sComment = aoRow[13]; + return self; + + def initFromDbWithId(self, oDb, idTestCase, tsNow = None, sPeriodBack = None): + """ + Initialize the object from the database. + """ + oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb, + 'SELECT *\n' + 'FROM TestCases\n' + 'WHERE idTestCase = %s\n' + , ( idTestCase,), tsNow, sPeriodBack)); + aoRow = oDb.fetchOne() + if aoRow is None: + raise TMRowNotFound('idTestCase=%s not found (tsNow=%s sPeriodBack=%s)' % (idTestCase, tsNow, sPeriodBack,)); + return self.initFromDbRow(aoRow); + + def initFromDbWithGenId(self, oDb, idGenTestCase, tsNow = None): + """ + Initialize the object from the database. + """ + _ = tsNow; # For relevant for the TestCaseDataEx version only. + oDb.execute('SELECT *\n' + 'FROM TestCases\n' + 'WHERE idGenTestCase = %s\n' + , (idGenTestCase, ) ); + return self.initFromDbRow(oDb.fetchOne()); + + def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb): + if sAttr == 'cSecTimeout' and oValue not in aoNilValues: # Allow human readable interval formats. + return utils.parseIntervalSeconds(oValue); + + (oValue, sError) = ModelDataBase._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb); + if sError is None: + if sAttr == 'sTestBoxReqExpr': + sError = TestCaseData.validateTestBoxReqExpr(oValue); + elif sAttr == 'sBuildReqExpr': + sError = TestCaseData.validateBuildReqExpr(oValue); + elif sAttr == 'sBaseCmd': + _, sError = TestCaseData.validateStr(oValue, fAllowUnicodeSymbols=False); + return (oValue, sError); + + + # + # Misc. + # + + def needValidationKitBit(self): + """ + Predicate method for checking whether a validation kit build is required. + """ + return self.sValidationKitZips is None \ + or self.sValidationKitZips.find('@VALIDATIONKIT_ZIP@') >= 0; + + def matchesTestBoxProps(self, oTestBoxData): + """ + Checks if the all of the testbox related test requirements matches the + given testbox. + + Returns True or False according to the expression, None on exception or + non-boolean expression result. + """ + return TestCaseData.matchesTestBoxPropsEx(oTestBoxData, self.sTestBoxReqExpr); + + def matchesBuildProps(self, oBuildDataEx): + """ + Checks if the all of the build related test requirements matches the + given build. + + Returns True or False according to the expression, None on exception or + non-boolean expression result. + """ + return TestCaseData.matchesBuildPropsEx(oBuildDataEx, self.sBuildReqExpr); + + + # + # Expression validation code shared with TestCaseArgsDataEx. + # + @staticmethod + def _safelyEvalExpr(sExpr, dLocals, fMayRaiseXcpt = False): + """ + Safely evaluate requirment expression given a set of locals. + + Returns True or False according to the expression. If the expression + causes an exception to be raised or does not return a boolean result, + None will be returned. + """ + if sExpr is None or sExpr == '': + return True; + + dGlobals = \ + { + '__builtins__': None, + 'long': long, + 'int': int, + 'bool': bool, + 'True': True, + 'False': False, + 'len': len, + 'isinstance': isinstance, + 'type': type, + 'dict': dict, + 'dir': dir, + 'list': list, + 'versionCompare': utils.versionCompare, + }; + + try: + fRc = eval(sExpr, dGlobals, dLocals); + except: + if fMayRaiseXcpt: + raise; + return None; + + if not isinstance(fRc, bool): + if fMayRaiseXcpt: + raise Exception('not a boolean result: "%s" - %s' % (fRc, type(fRc)) ); + return None; + + return fRc; + + @staticmethod + def _safelyValidateReqExpr(sExpr, adLocals): + """ + Validates a requirement expression using the given sets of locals, + returning None on success and an error string on failure. + """ + for dLocals in adLocals: + try: + TestCaseData._safelyEvalExpr(sExpr, dLocals, True); + except Exception as oXcpt: + return str(oXcpt); + return None; + + @staticmethod + def validateTestBoxReqExpr(sExpr): + """ + Validates a testbox expression, returning None on success and an error + string on failure. + """ + adTestBoxes = \ + [ + { + 'sOs': 'win', + 'sOsVersion': '3.1', + 'sCpuVendor': 'VirtualBox', + 'sCpuArch': 'x86', + 'cCpus': 1, + 'fCpuHwVirt': False, + 'fCpuNestedPaging': False, + 'fCpu64BitGuest': False, + 'fChipsetIoMmu': False, + 'fRawMode': False, + 'cMbMemory': 985034, + 'cMbScratch': 1234089, + 'iTestBoxScriptRev': 1, + 'sName': 'emanon', + 'uuidSystem': '8FF81BE5-3901-4AB1-8A65-B48D511C0321', + }, + { + 'sOs': 'linux', + 'sOsVersion': '3.1', + 'sCpuVendor': 'VirtualBox', + 'sCpuArch': 'amd64', + 'cCpus': 8191, + 'fCpuHwVirt': True, + 'fCpuNestedPaging': True, + 'fCpu64BitGuest': True, + 'fChipsetIoMmu': True, + 'fRawMode': True, + 'cMbMemory': 9999999999, + 'cMbScratch': 9999999999999, + 'iTestBoxScriptRev': 9999999, + 'sName': 'emanon', + 'uuidSystem': '00000000-0000-0000-0000-000000000000', + }, + ]; + return TestCaseData._safelyValidateReqExpr(sExpr, adTestBoxes); + + @staticmethod + def matchesTestBoxPropsEx(oTestBoxData, sExpr): + """ Worker for TestCaseData.matchesTestBoxProps and TestCaseArgsDataEx.matchesTestBoxProps. """ + if sExpr is None: + return True; + dLocals = \ + { + 'sOs': oTestBoxData.sOs, + 'sOsVersion': oTestBoxData.sOsVersion, + 'sCpuVendor': oTestBoxData.sCpuVendor, + 'sCpuArch': oTestBoxData.sCpuArch, + 'iCpuFamily': oTestBoxData.getCpuFamily(), + 'iCpuModel': oTestBoxData.getCpuModel(), + 'cCpus': oTestBoxData.cCpus, + 'fCpuHwVirt': oTestBoxData.fCpuHwVirt, + 'fCpuNestedPaging': oTestBoxData.fCpuNestedPaging, + 'fCpu64BitGuest': oTestBoxData.fCpu64BitGuest, + 'fChipsetIoMmu': oTestBoxData.fChipsetIoMmu, + 'fRawMode': oTestBoxData.fRawMode, + 'cMbMemory': oTestBoxData.cMbMemory, + 'cMbScratch': oTestBoxData.cMbScratch, + 'iTestBoxScriptRev': oTestBoxData.iTestBoxScriptRev, + 'iPythonHexVersion': oTestBoxData.iPythonHexVersion, + 'sName': oTestBoxData.sName, + 'uuidSystem': oTestBoxData.uuidSystem, + }; + return TestCaseData._safelyEvalExpr(sExpr, dLocals); + + @staticmethod + def validateBuildReqExpr(sExpr): + """ + Validates a testbox expression, returning None on success and an error + string on failure. + """ + adBuilds = \ + [ + { + 'sProduct': 'VirtualBox', + 'sBranch': 'trunk', + 'sType': 'release', + 'asOsArches': ['win.amd64', 'win.x86'], + 'sVersion': '1.0', + 'iRevision': 1234, + 'uidAuthor': None, + 'idBuild': 953, + }, + { + 'sProduct': 'VirtualBox', + 'sBranch': 'VBox-4.1', + 'sType': 'release', + 'asOsArches': ['linux.x86',], + 'sVersion': '4.2.15', + 'iRevision': 89876, + 'uidAuthor': None, + 'idBuild': 945689, + }, + { + 'sProduct': 'VirtualBox', + 'sBranch': 'VBox-4.1', + 'sType': 'strict', + 'asOsArches': ['solaris.x86', 'solaris.amd64',], + 'sVersion': '4.3.0_RC3', + 'iRevision': 97939, + 'uidAuthor': 33, + 'idBuild': 9456893, + }, + ]; + return TestCaseData._safelyValidateReqExpr(sExpr, adBuilds); + + @staticmethod + def matchesBuildPropsEx(oBuildDataEx, sExpr): + """ + Checks if the all of the build related test requirements matches the + given build. + """ + if sExpr is None: + return True; + dLocals = \ + { + 'sProduct': oBuildDataEx.oCat.sProduct, + 'sBranch': oBuildDataEx.oCat.sBranch, + 'sType': oBuildDataEx.oCat.sType, + 'asOsArches': oBuildDataEx.oCat.asOsArches, + 'sVersion': oBuildDataEx.sVersion, + 'iRevision': oBuildDataEx.iRevision, + 'uidAuthor': oBuildDataEx.uidAuthor, + 'idBuild': oBuildDataEx.idBuild, + }; + return TestCaseData._safelyEvalExpr(sExpr, dLocals); + + + + +class TestCaseDataEx(TestCaseData): + """ + Test case data. + """ + + ksParam_aoTestCaseArgs = 'TestCase_aoTestCaseArgs'; + ksParam_aoDepTestCases = 'TestCase_aoDepTestCases'; + ksParam_aoDepGlobalResources = 'TestCase_aoDepGlobalResources'; + + # Use [] instead of None. + kasAltArrayNull = [ 'aoTestCaseArgs', 'aoDepTestCases', 'aoDepGlobalResources' ]; + + + def __init__(self): + TestCaseData.__init__(self); + + # List of objects of type TestCaseData (or TestCaseDataEx, we don't + # care) on which current Test Case depends. + self.aoDepTestCases = []; + + # List of objects of type GlobalResourceData on which current Test Case depends. + self.aoDepGlobalResources = []; + + # List of objects of type TestCaseArgsData. + self.aoTestCaseArgs = []; + + def _initExtraMembersFromDb(self, oDb, tsNow = None, sPeriodBack = None): + """ + Worker shared by the initFromDb* methods. + Returns self. Raises exception if no row or database error. + """ + _ = sPeriodBack; ## @todo sPeriodBack + from testmanager.core.testcaseargs import TestCaseArgsLogic; + self.aoDepTestCases = TestCaseDependencyLogic(oDb).getDepTestCaseData(self.idTestCase, tsNow); + self.aoDepGlobalResources = TestCaseGlobalRsrcDepLogic(oDb).getDepGlobalResourceData(self.idTestCase, tsNow); + self.aoTestCaseArgs = TestCaseArgsLogic(oDb).getTestCaseArgs(self.idTestCase, tsNow); + # Note! The above arrays are sorted by their relvant IDs for fetchForChangeLog's sake. + return self; + + def initFromDbRowEx(self, aoRow, oDb, tsNow = None): + """ + Reinitialize from a SELECT * FROM TestCases row. Will query the + necessary additional data from oDb using tsNow. + Returns self. Raises exception if no row or database error. + """ + TestCaseData.initFromDbRow(self, aoRow); + return self._initExtraMembersFromDb(oDb, tsNow); + + def initFromDbWithId(self, oDb, idTestCase, tsNow = None, sPeriodBack = None): + """ + Initialize the object from the database. + """ + TestCaseData.initFromDbWithId(self, oDb, idTestCase, tsNow, sPeriodBack); + return self._initExtraMembersFromDb(oDb, tsNow, sPeriodBack); + + def initFromDbWithGenId(self, oDb, idGenTestCase, tsNow = None): + """ + Initialize the object from the database. + """ + TestCaseData.initFromDbWithGenId(self, oDb, idGenTestCase); + if tsNow is None and not oDb.isTsInfinity(self.tsExpire): + tsNow = self.tsEffective; + return self._initExtraMembersFromDb(oDb, tsNow); + + def getAttributeParamNullValues(self, sAttr): + if sAttr in ['aoDepTestCases', 'aoDepGlobalResources', 'aoTestCaseArgs']: + return [[], '']; + return TestCaseData.getAttributeParamNullValues(self, sAttr); + + def convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict): + """For dealing with the arrays.""" + if sAttr not in ['aoDepTestCases', 'aoDepGlobalResources', 'aoTestCaseArgs']: + return TestCaseData.convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict); + + aoNewValues = []; + if sAttr == 'aoDepTestCases': + for idTestCase in oDisp.getListOfIntParams(sParam, 1, 0x7ffffffe, []): + oDep = TestCaseData(); + oDep.idTestCase = str(idTestCase); + aoNewValues.append(oDep); + + elif sAttr == 'aoDepGlobalResources': + for idGlobalRsrc in oDisp.getListOfIntParams(sParam, 1, 0x7ffffffe, []): + oGlobalRsrc = GlobalResourceData(); + oGlobalRsrc.idGlobalRsrc = str(idGlobalRsrc); + aoNewValues.append(oGlobalRsrc); + + elif sAttr == 'aoTestCaseArgs': + from testmanager.core.testcaseargs import TestCaseArgsData; + for sArgKey in oDisp.getStringParam(TestCaseDataEx.ksParam_aoTestCaseArgs, sDefault = '').split(','): + oDispWrapper = self.DispWrapper(oDisp, '%s[%s][%%s]' % (TestCaseDataEx.ksParam_aoTestCaseArgs, sArgKey,)) + aoNewValues.append(TestCaseArgsData().initFromParams(oDispWrapper, fStrict = False)); + return aoNewValues; + + def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb): # pylint: disable=too-many-locals + """ + Validate special arrays and requirement expressions. + + For the two dependency arrays we have to supply missing bits by + looking them up in the database. In the argument variation case we + need to validate each item. + """ + if sAttr not in ['aoDepTestCases', 'aoDepGlobalResources', 'aoTestCaseArgs']: + return TestCaseData._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb); + + asErrors = []; + aoNewValues = []; + if sAttr == 'aoDepTestCases': + for oTestCase in self.aoDepTestCases: + if utils.isString(oTestCase.idTestCase): # Stored as string convertParamToAttribute. + oTestCase = copy.copy(oTestCase); + try: + oTestCase.idTestCase = int(oTestCase.idTestCase); + oTestCase.initFromDbWithId(oDb, oTestCase.idTestCase); + except Exception as oXcpt: + asErrors.append('Test case dependency #%s: %s' % (oTestCase.idTestCase, oXcpt)); + aoNewValues.append(oTestCase); + + elif sAttr == 'aoDepGlobalResources': + for oGlobalRsrc in self.aoDepGlobalResources: + if utils.isString(oGlobalRsrc.idGlobalRsrc): # Stored as string convertParamToAttribute. + oGlobalRsrc = copy.copy(oGlobalRsrc); + try: + oGlobalRsrc.idTestCase = int(oGlobalRsrc.idGlobalRsrc); + oGlobalRsrc.initFromDbWithId(oDb, oGlobalRsrc.idGlobalRsrc); + except Exception as oXcpt: + asErrors.append('Resource dependency #%s: %s' % (oGlobalRsrc.idGlobalRsrc, oXcpt)); + aoNewValues.append(oGlobalRsrc); + + else: + assert sAttr == 'aoTestCaseArgs'; + if not self.aoTestCaseArgs: + return (None, 'The testcase requires at least one argument variation to be valid.'); + + # Note! We'll be returning an error dictionary instead of an string here. + dErrors = {}; + + for iVar, oVar in enumerate(self.aoTestCaseArgs): + oVar = copy.copy(oVar); + oVar.idTestCase = self.idTestCase; + dCurErrors = oVar.validateAndConvert(oDb, ModelDataBase.ksValidateFor_Other); + if not dCurErrors: + pass; ## @todo figure out the ID? + else: + asErrors = []; + for sKey in dCurErrors: + asErrors.append('%s: %s' % (sKey[len('TestCaseArgs_'):], dCurErrors[sKey])); + dErrors[iVar] = '<br>\n'.join(asErrors) + aoNewValues.append(oVar); + + for iVar, oVar in enumerate(self.aoTestCaseArgs): + sArgs = oVar.sArgs; + for iVar2 in range(iVar + 1, len(self.aoTestCaseArgs)): + if self.aoTestCaseArgs[iVar2].sArgs == sArgs: + sMsg = 'Duplicate argument variation "%s".' % (sArgs); + if iVar in dErrors: dErrors[iVar] += '<br>\n' + sMsg; + else: dErrors[iVar] = sMsg; + if iVar2 in dErrors: dErrors[iVar2] += '<br>\n' + sMsg; + else: dErrors[iVar2] = sMsg; + break; + + return (aoNewValues, dErrors if dErrors else None); + + return (aoNewValues, None if not asErrors else ' <br>'.join(asErrors)); + + def _validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor = ModelDataBase.ksValidateFor_Other): + dErrors = TestCaseData._validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor); + + # Validate dependencies a wee bit for paranoid reasons. The scheduler + # queue generation code does the real validation here! + if not dErrors and self.idTestCase is not None: + for oDep in self.aoDepTestCases: + if oDep.idTestCase == self.idTestCase: + if self.ksParam_aoDepTestCases in dErrors: + dErrors[self.ksParam_aoDepTestCases] += ' Depending on itself!'; + else: + dErrors[self.ksParam_aoDepTestCases] = 'Depending on itself!'; + return dErrors; + + + + + +class TestCaseLogic(ModelLogicBase): + """ + Test case management logic. + """ + + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb) + self.dCache = None; + + def getAll(self): + """ + Fetches all test case records from DB (TestCaseData). + """ + self._oDb.execute('SELECT *\n' + 'FROM TestCases\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY idTestCase ASC;') + + aaoRows = self._oDb.fetchAll() + aoRet = []; + for aoRow in aaoRows: + aoRet.append(TestCaseData().initFromDbRow(aoRow)) + return aoRet + + def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None): + """ + Fetches test cases. + + Returns an array (list) of TestCaseDataEx items, empty list if none. + Raises exception on error. + """ + _ = aiSortColumns; + if tsNow is None: + self._oDb.execute('SELECT *\n' + 'FROM TestCases\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY sName ASC\n' + 'LIMIT %s OFFSET %s\n' + , (cMaxRows, iStart, )); + else: + self._oDb.execute('SELECT *\n' + 'FROM TestCases\n' + 'WHERE tsExpire > %s\n' + ' AND tsEffective <= %s\n' + 'ORDER BY sName ASC\n' + 'LIMIT %s OFFSET %s\n' + , (tsNow, tsNow, cMaxRows, iStart, )); + + aoRows = []; + for aoRow in self._oDb.fetchAll(): + aoRows.append(TestCaseDataEx().initFromDbRowEx(aoRow, self._oDb, tsNow)); + return aoRows; + + def fetchForChangeLog(self, idTestCase, iStart, cMaxRows, tsNow): # pylint: disable=too-many-locals + """ + Fetches change log entries for a testbox. + + Returns an array of ChangeLogEntry instance and an indicator whether + there are more entries. + Raises exception on error. + """ + + if tsNow is None: + tsNow = self._oDb.getCurrentTimestamp(); + + # 1. Get a list of the relevant change times. + self._oDb.execute('( SELECT tsEffective, uidAuthor FROM TestCases WHERE idTestCase = %s AND tsEffective <= %s )\n' + 'UNION\n' + '( SELECT tsEffective, uidAuthor FROM TestCaseArgs WHERE idTestCase = %s AND tsEffective <= %s )\n' + 'UNION\n' + '( SELECT tsEffective, uidAuthor FROM TestCaseDeps WHERE idTestCase = %s AND tsEffective <= %s )\n' + 'UNION\n' + '( SELECT tsEffective, uidAuthor FROM TestCaseGlobalRsrcDeps \n' \ + ' WHERE idTestCase = %s AND tsEffective <= %s )\n' + 'ORDER BY tsEffective DESC\n' + 'LIMIT %s OFFSET %s\n' + , ( idTestCase, tsNow, + idTestCase, tsNow, + idTestCase, tsNow, + idTestCase, tsNow, + cMaxRows + 1, iStart, )); + aaoChanges = self._oDb.fetchAll(); + + # 2. Collect data sets for each of those points. + # (Doing it the lazy + inefficient way for now.) + aoRows = []; + for aoChange in aaoChanges: + aoRows.append(TestCaseDataEx().initFromDbWithId(self._oDb, idTestCase, aoChange[0])); + + # 3. Calculate the changes. + aoEntries = []; + for i in range(0, len(aoRows) - 1): + oNew = aoRows[i]; + oOld = aoRows[i + 1]; + (tsEffective, uidAuthor) = aaoChanges[i]; + (tsExpire, _) = aaoChanges[i - 1] if i > 0 else (oNew.tsExpire, None) + assert self._oDb.isTsInfinity(tsEffective) != self._oDb.isTsInfinity(tsExpire) or tsEffective < tsExpire, \ + '%s vs %s' % (tsEffective, tsExpire); + + aoChanges = []; + + # The testcase object. + if oNew.tsEffective != oOld.tsEffective: + for sAttr in oNew.getDataAttributes(): + if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', \ + 'aoTestCaseArgs', 'aoDepTestCases', 'aoDepGlobalResources']: + oOldAttr = getattr(oOld, sAttr); + oNewAttr = getattr(oNew, sAttr); + if oOldAttr != oNewAttr: + aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr))); + + # The argument variations. + iChildOld = 0; + for oChildNew in oNew.aoTestCaseArgs: + # Locate the old entry, emitting removed markers for old items we have to skip. + while iChildOld < len(oOld.aoTestCaseArgs) \ + and oOld.aoTestCaseArgs[iChildOld].idTestCaseArgs < oChildNew.idTestCaseArgs: + oChildOld = oOld.aoTestCaseArgs[iChildOld]; + aoChanges.append(AttributeChangeEntry('Variation #%s' % (oChildOld.idTestCaseArgs,), + None, oChildOld, 'Removed', str(oChildOld))); + iChildOld += 1; + + if iChildOld < len(oOld.aoTestCaseArgs) \ + and oOld.aoTestCaseArgs[iChildOld].idTestCaseArgs == oChildNew.idTestCaseArgs: + oChildOld = oOld.aoTestCaseArgs[iChildOld]; + if oChildNew.tsEffective != oChildOld.tsEffective: + for sAttr in oChildNew.getDataAttributes(): + if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', 'idGenTestCase', ]: + oOldAttr = getattr(oChildOld, sAttr); + oNewAttr = getattr(oChildNew, sAttr); + if oOldAttr != oNewAttr: + aoChanges.append(AttributeChangeEntry('Variation[#%s].%s' + % (oChildOld.idTestCaseArgs, sAttr,), + oNewAttr, oOldAttr, + str(oNewAttr), str(oOldAttr))); + iChildOld += 1; + else: + aoChanges.append(AttributeChangeEntry('Variation #%s' % (oChildNew.idTestCaseArgs,), + oChildNew, None, + str(oChildNew), 'Did not exist')); + + # The testcase dependencies. + iChildOld = 0; + for oChildNew in oNew.aoDepTestCases: + # Locate the old entry, emitting removed markers for old items we have to skip. + while iChildOld < len(oOld.aoDepTestCases) \ + and oOld.aoDepTestCases[iChildOld].idTestCase < oChildNew.idTestCase: + oChildOld = oOld.aoDepTestCases[iChildOld]; + aoChanges.append(AttributeChangeEntry('Dependency #%s' % (oChildOld.idTestCase,), + None, oChildOld, 'Removed', + '%s (#%u)' % (oChildOld.sName, oChildOld.idTestCase,))); + iChildOld += 1; + if iChildOld < len(oOld.aoDepTestCases) \ + and oOld.aoDepTestCases[iChildOld].idTestCase == oChildNew.idTestCase: + iChildOld += 1; + else: + aoChanges.append(AttributeChangeEntry('Dependency #%s' % (oChildNew.idTestCase,), + oChildNew, None, + '%s (#%u)' % (oChildNew.sName, oChildNew.idTestCase,), + 'Did not exist')); + + # The global resource dependencies. + iChildOld = 0; + for oChildNew in oNew.aoDepGlobalResources: + # Locate the old entry, emitting removed markers for old items we have to skip. + while iChildOld < len(oOld.aoDepGlobalResources) \ + and oOld.aoDepGlobalResources[iChildOld].idGlobalRsrc < oChildNew.idGlobalRsrc: + oChildOld = oOld.aoDepGlobalResources[iChildOld]; + aoChanges.append(AttributeChangeEntry('Global Resource #%s' % (oChildOld.idGlobalRsrc,), + None, oChildOld, 'Removed', + '%s (#%u)' % (oChildOld.sName, oChildOld.idGlobalRsrc,))); + iChildOld += 1; + if iChildOld < len(oOld.aoDepGlobalResources) \ + and oOld.aoDepGlobalResources[iChildOld].idGlobalRsrc == oChildNew.idGlobalRsrc: + iChildOld += 1; + else: + aoChanges.append(AttributeChangeEntry('Global Resource #%s' % (oChildNew.idGlobalRsrc,), + oChildNew, None, + '%s (#%u)' % (oChildNew.sName, oChildNew.idGlobalRsrc,), + 'Did not exist')); + + # Done. + aoEntries.append(ChangeLogEntry(uidAuthor, None, tsEffective, tsExpire, oNew, oOld, aoChanges)); + + # If we're at the end of the log, add the initial entry. + if len(aoRows) <= cMaxRows and aoRows: + oNew = aoRows[-1]; + aoEntries.append(ChangeLogEntry(oNew.uidAuthor, None, + aaoChanges[-1][0], aaoChanges[-2][0] if len(aaoChanges) > 1 else oNew.tsExpire, + oNew, None, [])); + + return (UserAccountLogic(self._oDb).resolveChangeLogAuthors(aoEntries), len(aoRows) > cMaxRows); + + + def addEntry(self, oData, uidAuthor, fCommit = False): + """ + Add a new testcase to the DB. + """ + + # + # Validate the input first. + # + assert isinstance(oData, TestCaseDataEx); + dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Add); + if dErrors: + raise TMInvalidData('Invalid input data: %s' % (dErrors,)); + + # + # Add the testcase. + # + self._oDb.callProc('TestCaseLogic_addEntry', + ( uidAuthor, oData.sName, oData.sDescription, oData.fEnabled, oData.cSecTimeout, + oData.sTestBoxReqExpr, oData.sBuildReqExpr, oData.sBaseCmd, oData.sValidationKitZips, + oData.sComment )); + oData.idTestCase = self._oDb.fetchOne()[0]; + + # Add testcase dependencies. + for oDep in oData.aoDepTestCases: + self._oDb.execute('INSERT INTO TestCaseDeps (idTestCase, idTestCasePreReq, uidAuthor) VALUES (%s, %s, %s)' + , (oData.idTestCase, oDep.idTestCase, uidAuthor)) + + # Add global resource dependencies. + for oDep in oData.aoDepGlobalResources: + self._oDb.execute('INSERT INTO TestCaseGlobalRsrcDeps (idTestCase, idGlobalRsrc, uidAuthor) VALUES (%s, %s, %s)' + , (oData.idTestCase, oDep.idGlobalRsrc, uidAuthor)) + + # Set Test Case Arguments variations + for oVar in oData.aoTestCaseArgs: + self._oDb.execute('INSERT INTO TestCaseArgs (\n' + ' idTestCase, uidAuthor, sArgs, cSecTimeout,\n' + ' sTestBoxReqExpr, sBuildReqExpr, cGangMembers, sSubName)\n' + 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s)' + , ( oData.idTestCase, uidAuthor, oVar.sArgs, oVar.cSecTimeout, + oVar.sTestBoxReqExpr, oVar.sBuildReqExpr, oVar.cGangMembers, oVar.sSubName, )); + + self._oDb.maybeCommit(fCommit); + return True; + + def editEntry(self, oData, uidAuthor, fCommit = False): # pylint: disable=too-many-locals + """ + Edit a testcase entry (extended). + Caller is expected to rollback the database transactions on exception. + """ + + # + # Validate the input. + # + assert isinstance(oData, TestCaseDataEx); + dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit); + if dErrors: + raise TMInvalidData('Invalid input data: %s' % (dErrors,)); + + # + # Did anything change? If not return straight away. + # + oOldDataEx = TestCaseDataEx().initFromDbWithId(self._oDb, oData.idTestCase); + if oOldDataEx.isEqual(oData): + self._oDb.maybeCommit(fCommit); + return True; + + # + # Make the necessary changes. + # + + # The test case itself. + if not TestCaseData().initFromOther(oOldDataEx).isEqual(oData): + self._oDb.callProc('TestCaseLogic_editEntry', ( uidAuthor, oData.idTestCase, oData.sName, oData.sDescription, + oData.fEnabled, oData.cSecTimeout, oData.sTestBoxReqExpr, + oData.sBuildReqExpr, oData.sBaseCmd, oData.sValidationKitZips, + oData.sComment )); + oData.idGenTestCase = self._oDb.fetchOne()[0]; + + # + # Its dependencies on other testcases. + # + aidNewDeps = [oDep.idTestCase for oDep in oData.aoDepTestCases]; + aidOldDeps = [oDep.idTestCase for oDep in oOldDataEx.aoDepTestCases]; + + sQuery = self._oDb.formatBindArgs('UPDATE TestCaseDeps\n' + 'SET tsExpire = CURRENT_TIMESTAMP\n' + 'WHERE idTestCase = %s\n' + ' AND tsExpire = \'infinity\'::timestamp\n' + , (oData.idTestCase,)); + asKeepers = []; + for idDep in aidOldDeps: + if idDep in aidNewDeps: + asKeepers.append(str(idDep)); + if asKeepers: + sQuery += ' AND idTestCasePreReq NOT IN (' + ', '.join(asKeepers) + ')\n'; + self._oDb.execute(sQuery); + + for idDep in aidNewDeps: + if idDep not in aidOldDeps: + self._oDb.execute('INSERT INTO TestCaseDeps (idTestCase, idTestCasePreReq, uidAuthor)\n' + 'VALUES (%s, %s, %s)\n' + , (oData.idTestCase, idDep, uidAuthor) ); + + # + # Its dependencies on global resources. + # + aidNewDeps = [oDep.idGlobalRsrc for oDep in oData.aoDepGlobalResources]; + aidOldDeps = [oDep.idGlobalRsrc for oDep in oOldDataEx.aoDepGlobalResources]; + + sQuery = self._oDb.formatBindArgs('UPDATE TestCaseGlobalRsrcDeps\n' + 'SET tsExpire = CURRENT_TIMESTAMP\n' + 'WHERE idTestCase = %s\n' + ' AND tsExpire = \'infinity\'::timestamp\n' + , (oData.idTestCase,)); + asKeepers = []; + for idDep in aidOldDeps: + if idDep in aidNewDeps: + asKeepers.append(str(idDep)); + if asKeepers: + sQuery = ' AND idGlobalRsrc NOT IN (' + ', '.join(asKeepers) + ')\n'; + self._oDb.execute(sQuery); + + for idDep in aidNewDeps: + if idDep not in aidOldDeps: + self._oDb.execute('INSERT INTO TestCaseGlobalRsrcDeps (idTestCase, idGlobalRsrc, uidAuthor)\n' + 'VALUES (%s, %s, %s)\n' + , (oData.idTestCase, idDep, uidAuthor) ); + + # + # Update Test Case Args + # Note! Primary key is idTestCase, tsExpire, sArgs. + # + + # Historize rows that have been removed. + sQuery = self._oDb.formatBindArgs('UPDATE TestCaseArgs\n' + 'SET tsExpire = CURRENT_TIMESTAMP\n' + 'WHERE idTestCase = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP' + , (oData.idTestCase, )); + for oNewVar in oData.aoTestCaseArgs: + asKeepers.append(self._oDb.formatBindArgs('%s', (oNewVar.sArgs,))); + if asKeepers: + sQuery += ' AND sArgs NOT IN (' + ', '.join(asKeepers) + ')\n'; + self._oDb.execute(sQuery); + + # Add new TestCaseArgs records if necessary, reusing old IDs when possible. + from testmanager.core.testcaseargs import TestCaseArgsData; + for oNewVar in oData.aoTestCaseArgs: + self._oDb.execute('SELECT *\n' + 'FROM TestCaseArgs\n' + 'WHERE idTestCase = %s\n' + ' AND sArgs = %s\n' + 'ORDER BY tsExpire DESC\n' + 'LIMIT 1\n' + , (oData.idTestCase, oNewVar.sArgs,)); + aoRow = self._oDb.fetchOne(); + if aoRow is None: + # New + self._oDb.execute('INSERT INTO TestCaseArgs (\n' + ' idTestCase, uidAuthor, sArgs, cSecTimeout,\n' + ' sTestBoxReqExpr, sBuildReqExpr, cGangMembers, sSubName)\n' + 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s)' + , ( oData.idTestCase, uidAuthor, oNewVar.sArgs, oNewVar.cSecTimeout, + oNewVar.sTestBoxReqExpr, oNewVar.sBuildReqExpr, oNewVar.cGangMembers, oNewVar.sSubName)); + else: + oCurVar = TestCaseArgsData().initFromDbRow(aoRow); + if self._oDb.isTsInfinity(oCurVar.tsExpire): + # Existing current entry, updated if changed. + if oNewVar.cSecTimeout == oCurVar.cSecTimeout \ + and oNewVar.sTestBoxReqExpr == oCurVar.sTestBoxReqExpr \ + and oNewVar.sBuildReqExpr == oCurVar.sBuildReqExpr \ + and oNewVar.cGangMembers == oCurVar.cGangMembers \ + and oNewVar.sSubName == oCurVar.sSubName: + oNewVar.idTestCaseArgs = oCurVar.idTestCaseArgs; + oNewVar.idGenTestCaseArgs = oCurVar.idGenTestCaseArgs; + continue; # Unchanged. + self._oDb.execute('UPDATE TestCaseArgs SET tsExpire = CURRENT_TIMESTAMP WHERE idGenTestCaseArgs = %s\n' + , (oCurVar.idGenTestCaseArgs, )); + else: + # Existing old entry, re-use the ID. + pass; + self._oDb.execute('INSERT INTO TestCaseArgs (\n' + ' idTestCaseArgs, idTestCase, uidAuthor, sArgs, cSecTimeout,\n' + ' sTestBoxReqExpr, sBuildReqExpr, cGangMembers, sSubName)\n' + 'VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)\n' + 'RETURNING idGenTestCaseArgs\n' + , ( oCurVar.idTestCaseArgs, oData.idTestCase, uidAuthor, oNewVar.sArgs, oNewVar.cSecTimeout, + oNewVar.sTestBoxReqExpr, oNewVar.sBuildReqExpr, oNewVar.cGangMembers, oNewVar.sSubName)); + oNewVar.idGenTestCaseArgs = self._oDb.fetchOne()[0]; + + self._oDb.maybeCommit(fCommit); + return True; + + def removeEntry(self, uidAuthor, idTestCase, fCascade = False, fCommit = False): + """ Deletes the test case if possible. """ + self._oDb.callProc('TestCaseLogic_delEntry', (uidAuthor, idTestCase, fCascade)); + self._oDb.maybeCommit(fCommit); + return True + + + def getTestCasePreReqIds(self, idTestCase, tsEffective = None, cMax = None): + """ + Returns an array of prerequisite testcases (IDs) for the given testcase. + May raise exception on database error or if the result exceeds cMax. + """ + if tsEffective is None: + self._oDb.execute('SELECT idTestCasePreReq\n' + 'FROM TestCaseDeps\n' + 'WHERE idTestCase = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY idTestCasePreReq\n' + , (idTestCase,) ); + else: + self._oDb.execute('SELECT idTestCasePreReq\n' + 'FROM TestCaseDeps\n' + 'WHERE idTestCase = %s\n' + ' AND tsExpire > %s\n' + ' AND tsEffective <= %s\n' + 'ORDER BY idTestCasePreReq\n' + , (idTestCase, tsEffective, tsEffective) ); + + + if cMax is not None and self._oDb.getRowCount() > cMax: + raise TMExceptionBase('Too many prerequisites for testcase %s: %s, max %s' + % (idTestCase, cMax, self._oDb.getRowCount(),)); + + aidPreReqs = []; + for aoRow in self._oDb.fetchAll(): + aidPreReqs.append(aoRow[0]); + return aidPreReqs; + + + def cachedLookup(self, idTestCase): + """ + Looks up the most recent TestCaseDataEx object for idTestCase + via an object cache. + + Returns a shared TestCaseDataEx object. None if not found. + Raises exception on DB error. + """ + if self.dCache is None: + self.dCache = self._oDb.getCache('TestCaseDataEx'); + oEntry = self.dCache.get(idTestCase, None); + if oEntry is None: + fNeedTsNow = False; + self._oDb.execute('SELECT *\n' + 'FROM TestCases\n' + 'WHERE idTestCase = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idTestCase, )); + if self._oDb.getRowCount() == 0: + # Maybe it was deleted, try get the last entry. + self._oDb.execute('SELECT *\n' + 'FROM TestCases\n' + 'WHERE idTestCase = %s\n' + 'ORDER BY tsExpire DESC\n' + 'LIMIT 1\n' + , (idTestCase, )); + fNeedTsNow = True; + elif self._oDb.getRowCount() > 1: + raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idTestCase)); + + if self._oDb.getRowCount() == 1: + aaoRow = self._oDb.fetchOne(); + oEntry = TestCaseDataEx(); + tsNow = oEntry.initFromDbRow(aaoRow).tsEffective if fNeedTsNow else None; + oEntry.initFromDbRowEx(aaoRow, self._oDb, tsNow); + self.dCache[idTestCase] = oEntry; + return oEntry; + + + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +class TestCaseGlobalRsrcDepDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [TestCaseGlobalRsrcDepData(),]; + +class TestCaseDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [TestCaseData(),]; + + def testEmptyExpr(self): + self.assertEqual(TestCaseData.validateTestBoxReqExpr(None), None); + self.assertEqual(TestCaseData.validateTestBoxReqExpr(''), None); + + def testSimpleExpr(self): + self.assertEqual(TestCaseData.validateTestBoxReqExpr('cMbMemory > 10'), None); + self.assertEqual(TestCaseData.validateTestBoxReqExpr('cMbScratch < 10'), None); + self.assertEqual(TestCaseData.validateTestBoxReqExpr('fChipsetIoMmu'), None); + self.assertEqual(TestCaseData.validateTestBoxReqExpr('fChipsetIoMmu is True'), None); + self.assertEqual(TestCaseData.validateTestBoxReqExpr('fChipsetIoMmu is False'), None); + self.assertEqual(TestCaseData.validateTestBoxReqExpr('fChipsetIoMmu is None'), None); + self.assertEqual(TestCaseData.validateTestBoxReqExpr('isinstance(fChipsetIoMmu, bool)'), None); + self.assertEqual(TestCaseData.validateTestBoxReqExpr('isinstance(iTestBoxScriptRev, int)'), None); + self.assertEqual(TestCaseData.validateTestBoxReqExpr('isinstance(cMbScratch, long)'), None); + + def testBadExpr(self): + self.assertNotEqual(TestCaseData.validateTestBoxReqExpr('this is an bad expression, surely it must be'), None); + self.assertNotEqual(TestCaseData.validateTestBoxReqExpr('x = 1 + 1'), None); + self.assertNotEqual(TestCaseData.validateTestBoxReqExpr('__import__(\'os\').unlink(\'/tmp/no/such/file\')'), None); + self.assertNotEqual(TestCaseData.validateTestBoxReqExpr('print "foobar"'), None); + +class TestCaseDataExTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [TestCaseDataEx(),]; + +if __name__ == '__main__': + unittest.main(); + # not reached. + diff --git a/src/VBox/ValidationKit/testmanager/core/testcaseargs.py b/src/VBox/ValidationKit/testmanager/core/testcaseargs.py new file mode 100755 index 00000000..68ca84fb --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/testcaseargs.py @@ -0,0 +1,416 @@ +# -*- coding: utf-8 -*- +# $Id: testcaseargs.py $ + +""" +Test Manager - Test Case Arguments Variations. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import unittest; +import sys; + +# Validation Kit imports. +from common import utils; +from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMExceptionBase, \ + TMRowNotFound; +from testmanager.core.testcase import TestCaseData, TestCaseDependencyLogic, TestCaseGlobalRsrcDepLogic; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + + +class TestCaseArgsData(ModelDataBase): + """ + Test case argument variation. + """ + + ksIdAttr = 'idTestCaseArgs'; + ksIdGenAttr = 'idGenTestCaseArgs'; + + ksParam_idTestCase = 'TestCaseArgs_idTestCase'; + ksParam_idTestCaseArgs = 'TestCaseArgs_idTestCaseArgs'; + ksParam_tsEffective = 'TestCaseArgs_tsEffective'; + ksParam_tsExpire = 'TestCaseArgs_tsExpire'; + ksParam_uidAuthor = 'TestCaseArgs_uidAuthor'; + ksParam_idGenTestCaseArgs = 'TestCaseArgs_idGenTestCaseArgs'; + ksParam_sArgs = 'TestCaseArgs_sArgs'; + ksParam_cSecTimeout = 'TestCaseArgs_cSecTimeout'; + ksParam_sTestBoxReqExpr = 'TestCaseArgs_sTestBoxReqExpr'; + ksParam_sBuildReqExpr = 'TestCaseArgs_sBuildReqExpr'; + ksParam_cGangMembers = 'TestCaseArgs_cGangMembers'; + ksParam_sSubName = 'TestCaseArgs_sSubName'; + + kcDbColumns = 12; + + kasAllowNullAttributes = [ 'idTestCase', 'idTestCaseArgs', 'tsEffective', 'tsExpire', 'uidAuthor', 'idGenTestCaseArgs', + 'cSecTimeout', 'sTestBoxReqExpr', 'sBuildReqExpr', 'sSubName', ]; + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.idTestCase = None; + self.idTestCaseArgs = None; + self.tsEffective = None; + self.tsExpire = None; + self.uidAuthor = None; + self.idGenTestCaseArgs = None; + self.sArgs = ''; + self.cSecTimeout = None; + self.sTestBoxReqExpr = None; + self.sBuildReqExpr = None; + self.cGangMembers = 1; + self.sSubName = None; + + def initFromDbRow(self, aoRow): + """ + Re-initializes the object from a SELECT * FROM TestCaseArgs row. + Returns self. Raises exception if aoRow is None. + """ + if aoRow is None: + raise TMRowNotFound('TestBoxStatus not found.'); + + self.idTestCase = aoRow[0]; + self.idTestCaseArgs = aoRow[1]; + self.tsEffective = aoRow[2]; + self.tsExpire = aoRow[3]; + self.uidAuthor = aoRow[4]; + self.idGenTestCaseArgs = aoRow[5]; + self.sArgs = aoRow[6]; + self.cSecTimeout = aoRow[7]; + self.sTestBoxReqExpr = aoRow[8]; + self.sBuildReqExpr = aoRow[9]; + self.cGangMembers = aoRow[10]; + self.sSubName = aoRow[11]; + return self; + + def initFromDbWithId(self, oDb, idTestCaseArgs, tsNow = None, sPeriodBack = None): + """ + Initialize from the database. + """ + oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb, + 'SELECT *\n' + 'FROM TestCaseArgs\n' + 'WHERE idTestCaseArgs = %s\n' + , ( idTestCaseArgs,), tsNow, sPeriodBack)); + aoRow = oDb.fetchOne() + if aoRow is None: + raise TMRowNotFound('idTestCaseArgs=%s not found (tsNow=%s sPeriodBack=%s)' + % (idTestCaseArgs, tsNow, sPeriodBack,)); + return self.initFromDbRow(aoRow); + + def initFromDbWithGenId(self, oDb, idGenTestCaseArgs): + """ + Initialize from the database, given the generation ID of a row. + """ + oDb.execute('SELECT * FROM TestCaseArgs WHERE idGenTestCaseArgs = %s', (idGenTestCaseArgs,)); + return self.initFromDbRow(oDb.fetchOne()); + + def initFromValues(self, sArgs, cSecTimeout = None, sTestBoxReqExpr = None, sBuildReqExpr = None, # pylint: disable=too-many-arguments + cGangMembers = 1, idTestCase = None, idTestCaseArgs = None, tsEffective = None, tsExpire = None, + uidAuthor = None, idGenTestCaseArgs = None, sSubName = None): + """ + Reinitialize from values. + Returns self. + """ + self.idTestCase = idTestCase; + self.idTestCaseArgs = idTestCaseArgs; + self.tsEffective = tsEffective; + self.tsExpire = tsExpire; + self.uidAuthor = uidAuthor; + self.idGenTestCaseArgs = idGenTestCaseArgs; + self.sArgs = sArgs; + self.cSecTimeout = utils.parseIntervalSeconds(cSecTimeout); + self.sTestBoxReqExpr = sTestBoxReqExpr; + self.sBuildReqExpr = sBuildReqExpr; + self.cGangMembers = cGangMembers; + self.sSubName = sSubName; + return self; + + def getAttributeParamNullValues(self, sAttr): + aoNilValues = ModelDataBase.getAttributeParamNullValues(self, sAttr); + if sAttr == 'cSecTimeout': + aoNilValues.insert(0, ''); # Prettier NULL value for cSecTimeout. + elif sAttr == 'sArgs': + aoNilValues = []; # No NULL value here, thank you. + return aoNilValues; + + def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb): + if sAttr == 'cSecTimeout' and oValue not in aoNilValues: # Allow human readable interval formats. + return utils.parseIntervalSeconds(oValue); + + (oValue, sError) = ModelDataBase._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb); + if sError is None: + if sAttr == 'sTestBoxReqExpr': + sError = TestCaseData.validateTestBoxReqExpr(oValue); + elif sAttr == 'sBuildReqExpr': + sError = TestCaseData.validateBuildReqExpr(oValue); + return (oValue, sError); + + + + +class TestCaseArgsDataEx(TestCaseArgsData): + """ + Complete data set. + """ + + def __init__(self): + TestCaseArgsData.__init__(self); + self.oTestCase = None; + self.aoTestCasePreReqs = []; + self.aoGlobalRsrc = []; + + def initFromDbRow(self, aoRow): + raise TMExceptionBase('Do not call me: %s' % (aoRow,)) + + def initFromDbRowEx(self, aoRow, oDb, tsConfigEff = None, tsRsrcEff = None): + """ + Extended version of initFromDbRow that fills in the rest from the database. + """ + TestCaseArgsData.initFromDbRow(self, aoRow); + + if tsConfigEff is None: tsConfigEff = oDb.getCurrentTimestamp(); + if tsRsrcEff is None: tsRsrcEff = oDb.getCurrentTimestamp(); + + self.oTestCase = TestCaseData().initFromDbWithId(oDb, self.idTestCase, tsConfigEff); + self.aoTestCasePreReqs = TestCaseDependencyLogic(oDb).getTestCaseDeps(self.idTestCase, tsConfigEff); + self.aoGlobalRsrc = TestCaseGlobalRsrcDepLogic(oDb).getTestCaseDeps(self.idTestCase, tsRsrcEff); + + return self; + + def initFromDbWithId(self, oDb, idTestCaseArgs, tsNow = None, sPeriodBack = None): + _ = oDb; _ = idTestCaseArgs; _ = tsNow; _ = sPeriodBack; + raise TMExceptionBase('Not supported.'); + + def initFromDbWithGenId(self, oDb, idGenTestCaseArgs): + _ = oDb; _ = idGenTestCaseArgs; + raise TMExceptionBase('Use initFromDbWithGenIdEx...'); + + def initFromDbWithGenIdEx(self, oDb, idGenTestCaseArgs, tsConfigEff = None, tsRsrcEff = None): + """ + Initialize from the database, given the ID of a row. + """ + oDb.execute('SELECT *, CURRENT_TIMESTAMP FROM TestCaseArgs WHERE idGenTestCaseArgs = %s', (idGenTestCaseArgs,)); + aoRow = oDb.fetchOne(); + return self.initFromDbRowEx(aoRow, oDb, tsConfigEff, tsRsrcEff); + + def convertFromParamNull(self): + raise TMExceptionBase('Not implemented'); + + def convertToParamNull(self): + raise TMExceptionBase('Not implemented'); + + def isEqual(self, oOther): + raise TMExceptionBase('Not implemented'); + + def matchesTestBoxProps(self, oTestBoxData): + """ + Checks if the all of the testbox related test requirements matches the + given testbox. + + Returns True or False according to the expression, None on exception or + non-boolean expression result. + """ + return TestCaseData.matchesTestBoxPropsEx(oTestBoxData, self.oTestCase.sTestBoxReqExpr) \ + and TestCaseData.matchesTestBoxPropsEx(oTestBoxData, self.sTestBoxReqExpr); + + def matchesBuildProps(self, oBuildDataEx): + """ + Checks if the all of the build related test requirements matches the + given build. + + Returns True or False according to the expression, None on exception or + non-boolean expression result. + """ + return TestCaseData.matchesBuildPropsEx(oBuildDataEx, self.oTestCase.sBuildReqExpr) \ + and TestCaseData.matchesBuildPropsEx(oBuildDataEx, self.sBuildReqExpr); + + +class TestCaseArgsLogic(ModelLogicBase): + """ + TestCaseArgs database logic. + """ + + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb); + self.dCache = None; + + + def areResourcesFree(self, oDataEx): + """ + Checks if all global resources are currently still in existance and free. + Returns True/False. May raise exception on database error. + """ + + # Create a set of global resource IDs. + if not oDataEx.aoGlobalRsrc: + return True; + asIdRsrcs = [str(oDep.idGlobalRsrc) for oDep, _ in oDataEx.aoGlobalRsrc]; + + # A record in the resource status table means it's allocated. + self._oDb.execute('SELECT COUNT(*)\n' + 'FROM GlobalResourceStatuses\n' + 'WHERE GlobalResourceStatuses.idGlobalRsrc IN (' + ', '.join(asIdRsrcs) + ')\n'); + if self._oDb.fetchOne()[0] == 0: + # Check for disabled or deleted resources (we cannot allocate them). + self._oDb.execute('SELECT COUNT(*)\n' + 'FROM GlobalResources\n' + 'WHERE GlobalResources.idGlobalRsrc IN (' + ', '.join(asIdRsrcs) + ')\n' + ' AND GlobalResources.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND GlobalResources.fEnabled = TRUE\n'); + if self._oDb.fetchOne()[0] == len(oDataEx.aoGlobalRsrc): + return True; + return False; + + def getAll(self): + """Get list of objects of type TestCaseArgsData""" + self._oDb.execute('SELECT *\n' + 'FROM TestCaseArgs\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP') + aaoRows = self._oDb.fetchAll() + aoRet = [] + for aoRow in aaoRows: + aoRet.append(TestCaseArgsData().initFromDbRow(aoRow)) + + return aoRet + + def getTestCaseArgs(self, idTestCase, tsNow = None, aiWhiteList = None): + """Get list of testcase's arguments variations""" + if aiWhiteList is None: + if tsNow is None: + self._oDb.execute('SELECT *\n' + 'FROM TestCaseArgs\n' + 'WHERE idTestCase = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY TestCaseArgs.idTestCaseArgs\n' + , (idTestCase,)); + else: + self._oDb.execute('SELECT *\n' + 'FROM TestCaseArgs\n' + 'WHERE idTestCase = %s\n' + ' AND tsExpire > %s\n' + ' AND tsEffective <= %s\n' + 'ORDER BY TestCaseArgs.idTestCaseArgs\n' + , (idTestCase, tsNow, tsNow)); + else: + sWhiteList = ','.join((str(x) for x in aiWhiteList)); + if tsNow is None: + self._oDb.execute('SELECT *\n' + 'FROM TestCaseArgs\n' + 'WHERE idTestCase = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND idTestCaseArgs IN (' + sWhiteList + ')\n' + 'ORDER BY TestCaseArgs.idTestCaseArgs\n' + , (idTestCase,)); + else: + self._oDb.execute('SELECT *\n' + 'FROM TestCaseArgs\n' + 'WHERE idTestCase = %s\n' + ' AND tsExpire > %s\n' + ' AND tsEffective <= %s\n' + ' AND idTestCaseArgs IN (' + sWhiteList + ')\n' + 'ORDER BY TestCaseArgs.idTestCaseArgs\n' + , (idTestCase, tsNow, tsNow)); + + aaoRows = self._oDb.fetchAll() + aoRet = [] + for aoRow in aaoRows: + aoRet.append(TestCaseArgsData().initFromDbRow(aoRow)) + + return aoRet + + def addTestCaseArgs(self, oTestCaseArgsData): + """Add Test Case Args record into DB""" + pass; # pylint: disable=unnecessary-pass + + def cachedLookup(self, idTestCaseArgs): + """ + Looks up the most recent TestCaseArgsDataEx object for idTestCaseArg + via in an object cache. + + Returns a shared TestCaseArgDataEx object. None if not found. + Raises exception on DB error. + """ + if self.dCache is None: + self.dCache = self._oDb.getCache('TestCaseArgsDataEx'); + oEntry = self.dCache.get(idTestCaseArgs, None); + if oEntry is None: + fNeedTsNow = False; + self._oDb.execute('SELECT *\n' + 'FROM TestCaseArgs\n' + 'WHERE idTestCaseArgs = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idTestCaseArgs, )); + if self._oDb.getRowCount() == 0: + # Maybe it was deleted, try get the last entry. + self._oDb.execute('SELECT *\n' + 'FROM TestCaseArgs\n' + 'WHERE idTestCaseArgs = %s\n' + 'ORDER BY tsExpire DESC\n' + 'LIMIT 1\n' + , (idTestCaseArgs, )); + fNeedTsNow = True; + elif self._oDb.getRowCount() > 1: + raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idTestCaseArgs)); + + if self._oDb.getRowCount() == 1: + aaoRow = self._oDb.fetchOne(); + oEntry = TestCaseArgsDataEx(); + tsNow = TestCaseArgsData().initFromDbRow(aaoRow).tsEffective if fNeedTsNow else None; + oEntry.initFromDbRowEx(aaoRow, self._oDb, tsNow, tsNow); + self.dCache[idTestCaseArgs] = oEntry; + return oEntry; + + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +class TestCaseArgsDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [TestCaseArgsData(),]; + +if __name__ == '__main__': + unittest.main(); + # not reached. + diff --git a/src/VBox/ValidationKit/testmanager/core/testgroup.py b/src/VBox/ValidationKit/testmanager/core/testgroup.py new file mode 100755 index 00000000..b6d2e158 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/testgroup.py @@ -0,0 +1,771 @@ +# -*- coding: utf-8 -*- +# $Id: testgroup.py $ + +""" +Test Manager - Test groups management. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import unittest; + +# Validation Kit imports. +from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMRowInUse, \ + TMTooManyRows, TMInvalidData, TMRowNotFound, TMRowAlreadyExists; +from testmanager.core.testcase import TestCaseData, TestCaseDataEx; + + +class TestGroupMemberData(ModelDataBase): + """Representation of a test group member database row.""" + + ksParam_idTestGroup = 'TestGroupMember_idTestGroup'; + ksParam_idTestCase = 'TestGroupMember_idTestCase'; + ksParam_tsEffective = 'TestGroupMember_tsEffective'; + ksParam_tsExpire = 'TestGroupMember_tsExpire'; + ksParam_uidAuthor = 'TestGroupMember_uidAuthor'; + ksParam_iSchedPriority = 'TestGroupMember_iSchedPriority'; + ksParam_aidTestCaseArgs = 'TestGroupMember_aidTestCaseArgs'; + + kasAllowNullAttributes = ['idTestGroup', 'idTestCase', 'tsEffective', 'tsExpire', 'uidAuthor', 'aidTestCaseArgs' ]; + kiMin_iSchedPriority = 0; + kiMax_iSchedPriority = 31; + + kcDbColumns = 7; + + def __init__(self): + ModelDataBase.__init__(self) + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.idTestGroup = None; + self.idTestCase = None; + self.tsEffective = None; + self.tsExpire = None; + self.uidAuthor = None; + self.iSchedPriority = 16; + self.aidTestCaseArgs = None; + + def initFromDbRow(self, aoRow): + """ + Reinitialize from a SELECT * FROM TestCaseGroupMembers. + Return self. Raises exception if no row. + """ + if aoRow is None: + raise TMRowNotFound('Test group member not found.') + + self.idTestGroup = aoRow[0]; + self.idTestCase = aoRow[1]; + self.tsEffective = aoRow[2]; + self.tsExpire = aoRow[3]; + self.uidAuthor = aoRow[4]; + self.iSchedPriority = aoRow[5]; + self.aidTestCaseArgs = aoRow[6]; + return self + + + def getAttributeParamNullValues(self, sAttr): + # Arrays default to [] as NULL currently. That doesn't work for us. + if sAttr == 'aidTestCaseArgs': + aoNilValues = [None, '-1']; + else: + aoNilValues = ModelDataBase.getAttributeParamNullValues(self, sAttr); + return aoNilValues; + + def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb): + if sAttr != 'aidTestCaseArgs': + return ModelDataBase._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb); + + # -1 is a special value, which when present make the whole thing NULL (None). + (aidVariations, sError) = self.validateListOfInts(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull, + iMin = -1, iMax = 0x7ffffffe); + if sError is None: + if aidVariations is None: + pass; + elif -1 in aidVariations: + aidVariations = None; + elif 0 in aidVariations: + sError = 'Invalid test case varation ID #0.'; + else: + aidVariations = sorted(aidVariations); + return (aidVariations, sError); + + + +class TestGroupMemberDataEx(TestGroupMemberData): + """Extended representation of a test group member.""" + + def __init__(self): + """Extend parent class""" + TestGroupMemberData.__init__(self) + self.oTestCase = None; # TestCaseDataEx. + + def initFromDbRowEx(self, aoRow, oDb, tsNow = None): + """ + Reinitialize from a SELECT * FROM TestGroupMembers, TestCases row. + Will query the necessary additional data from oDb using tsNow. + + Returns self. Raises exception if no row or database error. + """ + TestGroupMemberData.initFromDbRow(self, aoRow); + self.oTestCase = TestCaseDataEx(); + self.oTestCase.initFromDbRowEx(aoRow[TestGroupMemberData.kcDbColumns:], oDb, tsNow); + return self; + + def initFromParams(self, oDisp, fStrict = True): + self.oTestCase = None; + return TestGroupMemberData.initFromParams(self, oDisp, fStrict); + + def getDataAttributes(self): + asAttributes = TestGroupMemberData.getDataAttributes(self); + asAttributes.remove('oTestCase'); + return asAttributes; + + def _validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor = ModelDataBase.ksValidateFor_Other): + dErrors = TestGroupMemberData._validateAndConvertWorker(self, asAllowNullAttributes, oDb, enmValidateFor); + if self.ksParam_idTestCase not in dErrors: + self.oTestCase = TestCaseDataEx() + try: + self.oTestCase.initFromDbWithId(oDb, self.idTestCase); + except Exception as oXcpt: + self.oTestCase = TestCaseDataEx() + dErrors[self.ksParam_idTestCase] = str(oXcpt); + return dErrors; + + +class TestGroupMemberData2(TestCaseData): + """Special representation of a Test Group Member item""" + + def __init__(self): + """Extend parent class""" + TestCaseData.__init__(self) + self.idTestGroup = None + self.aidTestCaseArgs = [] + + def initFromDbRowEx(self, aoRow): + """ + Reinitialize from this query: + + SELECT TestCases.*, + TestGroupMembers.idTestGroup, + TestGroupMembers.aidTestCaseArgs + FROM TestCases, TestGroupMembers + WHERE TestCases.idTestCase = TestGroupMembers.idTestCase + + Represents complete test group member (test case) info. + Returns object of type TestGroupMemberData2. Raises exception if no row. + """ + TestCaseData.initFromDbRow(self, aoRow); + self.idTestGroup = aoRow[-2] + self.aidTestCaseArgs = aoRow[-1] + return self; + + +class TestGroupData(ModelDataBase): + """ + Test group data. + """ + + ksIdAttr = 'idTestGroup'; + + ksParam_idTestGroup = 'TestGroup_idTestGroup' + ksParam_tsEffective = 'TestGroup_tsEffective' + ksParam_tsExpire = 'TestGroup_tsExpire' + ksParam_uidAuthor = 'TestGroup_uidAuthor' + ksParam_sName = 'TestGroup_sName' + ksParam_sDescription = 'TestGroup_sDescription' + ksParam_sComment = 'TestGroup_sComment' + + kasAllowNullAttributes = ['idTestGroup', 'tsEffective', 'tsExpire', 'uidAuthor', 'sDescription', 'sComment' ]; + + kcDbColumns = 7; + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.idTestGroup = None + self.tsEffective = None + self.tsExpire = None + self.uidAuthor = None + self.sName = None + self.sDescription = None + self.sComment = None + + def initFromDbRow(self, aoRow): + """ + Reinitialize from a SELECT * FROM TestGroups row. + Returns object of type TestGroupData. Raises exception if no row. + """ + if aoRow is None: + raise TMRowNotFound('Test group not found.') + + self.idTestGroup = aoRow[0] + self.tsEffective = aoRow[1] + self.tsExpire = aoRow[2] + self.uidAuthor = aoRow[3] + self.sName = aoRow[4] + self.sDescription = aoRow[5] + self.sComment = aoRow[6] + return self + + def initFromDbWithId(self, oDb, idTestGroup, tsNow = None, sPeriodBack = None): + """ + Initialize the object from the database. + """ + oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb, + 'SELECT *\n' + 'FROM TestGroups\n' + 'WHERE idTestGroup = %s\n' + , ( idTestGroup,), tsNow, sPeriodBack)); + aoRow = oDb.fetchOne() + if aoRow is None: + raise TMRowNotFound('idTestGroup=%s not found (tsNow=%s sPeriodBack=%s)' % (idTestGroup, tsNow, sPeriodBack,)); + return self.initFromDbRow(aoRow); + + +class TestGroupDataEx(TestGroupData): + """ + Extended test group data. + """ + + ksParam_aoMembers = 'TestGroupDataEx_aoMembers'; + kasAltArrayNull = [ 'aoMembers', ]; + + ## Helper parameter containing the comma separated list with the IDs of + # potential members found in the parameters. + ksParam_aidTestCases = 'TestGroupDataEx_aidTestCases'; + + + def __init__(self): + TestGroupData.__init__(self); + self.aoMembers = []; # TestGroupMemberDataEx. + + def _initExtraMembersFromDb(self, oDb, tsNow = None, sPeriodBack = None): + """ + Worker shared by the initFromDb* methods. + Returns self. Raises exception if no row or database error. + """ + self.aoMembers = []; + _ = sPeriodBack; ## @todo sPeriodBack + + if tsNow is None: + oDb.execute('SELECT TestGroupMembers.*, TestCases.*\n' + 'FROM TestGroupMembers\n' + 'LEFT OUTER JOIN TestCases ON (\n' + ' TestGroupMembers.idTestCase = TestCases.idTestCase\n' + ' AND TestCases.tsExpire = \'infinity\'::TIMESTAMP)\n' + 'WHERE TestGroupMembers.idTestGroup = %s\n' + ' AND TestGroupMembers.tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY TestCases.sName, TestCases.idTestCase\n' + , (self.idTestGroup,)); + else: + oDb.execute('SELECT TestGroupMembers.*, TestCases.*\n' + 'FROM TestGroupMembers\n' + 'LEFT OUTER JOIN TestCases ON (\n' + ' TestGroupMembers.idTestCase = TestCases.idTestCase\n' + ' AND TestCases.tsExpire > %s\n' + ' AND TestCases.tsEffective <= %s)\n' + 'WHERE TestGroupMembers.idTestGroup = %s\n' + ' AND TestGroupMembers.tsExpire > %s\n' + ' AND TestGroupMembers.tsEffective <= %s\n' + 'ORDER BY TestCases.sName, TestCases.idTestCase\n' + , (tsNow, tsNow, self.idTestGroup, tsNow, tsNow)); + + for aoRow in oDb.fetchAll(): + self.aoMembers.append(TestGroupMemberDataEx().initFromDbRowEx(aoRow, oDb, tsNow)); + return self; + + def initFromDbRowEx(self, aoRow, oDb, tsNow = None, sPeriodBack = None): + """ + Reinitialize from a SELECT * FROM TestGroups row. Will query the + necessary additional data from oDb using tsNow. + Returns self. Raises exception if no row or database error. + """ + TestGroupData.initFromDbRow(self, aoRow); + return self._initExtraMembersFromDb(oDb, tsNow, sPeriodBack); + + def initFromDbWithId(self, oDb, idTestGroup, tsNow = None, sPeriodBack = None): + """ + Initialize the object from the database. + """ + TestGroupData.initFromDbWithId(self, oDb, idTestGroup, tsNow, sPeriodBack); + return self._initExtraMembersFromDb(oDb, tsNow, sPeriodBack); + + + def getAttributeParamNullValues(self, sAttr): + if sAttr != 'aoMembers': + return TestGroupData.getAttributeParamNullValues(self, sAttr); + return ['', [], None]; + + def convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict): + if sAttr != 'aoMembers': + return TestGroupData.convertParamToAttribute(self, sAttr, sParam, oValue, oDisp, fStrict); + + aoNewValue = []; + aidSelected = oDisp.getListOfIntParams(sParam, iMin = 1, iMax = 0x7ffffffe, aiDefaults = []) + sIds = oDisp.getStringParam(self.ksParam_aidTestCases, sDefault = ''); + for idTestCase in sIds.split(','): + try: idTestCase = int(idTestCase); + except: pass; + oDispWrapper = self.DispWrapper(oDisp, '%s[%s][%%s]' % (TestGroupDataEx.ksParam_aoMembers, idTestCase,)) + oMember = TestGroupMemberDataEx().initFromParams(oDispWrapper, fStrict = False); + if idTestCase in aidSelected: + aoNewValue.append(oMember); + return aoNewValue; + + def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb): + if sAttr != 'aoMembers': + return TestGroupData._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb); + + asErrors = []; + aoNewMembers = []; + for oOldMember in oValue: + oNewMember = TestGroupMemberDataEx().initFromOther(oOldMember); + aoNewMembers.append(oNewMember); + + dErrors = oNewMember.validateAndConvert(oDb, ModelDataBase.ksValidateFor_Other); + if dErrors: + asErrors.append(str(dErrors)); + + if not asErrors: + for i, _ in enumerate(aoNewMembers): + idTestCase = aoNewMembers[i]; + for j in range(i + 1, len(aoNewMembers)): + if aoNewMembers[j].idTestCase == idTestCase: + asErrors.append('Duplicate testcase #%d!' % (idTestCase, )); + break; + + return (aoNewMembers, None if not asErrors else '<br>\n'.join(asErrors)); + + +class TestGroupLogic(ModelLogicBase): + """ + Test case management logic. + """ + + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb) + self.dCache = None; + + # + # Standard methods. + # + + def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None): + """ + Fetches test groups. + + Returns an array (list) of TestGroupDataEx items, empty list if none. + Raises exception on error. + """ + _ = aiSortColumns; + if tsNow is None: + self._oDb.execute('SELECT *\n' + 'FROM TestGroups\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY sName ASC\n' + 'LIMIT %s OFFSET %s\n' + , (cMaxRows, iStart,)); + else: + self._oDb.execute('SELECT *\n' + 'FROM TestGroups\n' + 'WHERE tsExpire > %s\n' + ' AND tsEffective <= %s\n' + 'ORDER BY sName ASC\n' + 'LIMIT %s OFFSET %s\n' + , (tsNow, tsNow, cMaxRows, iStart,)); + + aoRet = []; + for aoRow in self._oDb.fetchAll(): + aoRet.append(TestGroupDataEx().initFromDbRowEx(aoRow, self._oDb, tsNow)); + return aoRet; + + def addEntry(self, oData, uidAuthor, fCommit = False): + """ + Adds a testgroup to the database. + """ + + # + # Validate inputs. + # + assert isinstance(oData, TestGroupDataEx); + dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Add); + if dErrors: + raise TMInvalidData('addEntry invalid input: %s' % (dErrors,)); + self._assertUniq(oData, None); + + # + # Do the job. + # + self._oDb.execute('INSERT INTO TestGroups (uidAuthor, sName, sDescription, sComment)\n' + 'VALUES (%s, %s, %s, %s)\n' + 'RETURNING idTestGroup\n' + , ( uidAuthor, + oData.sName, + oData.sDescription, + oData.sComment )); + idTestGroup = self._oDb.fetchOne()[0]; + oData.idTestGroup = idTestGroup; + + for oMember in oData.aoMembers: + oMember.idTestGroup = idTestGroup; + self._insertTestGroupMember(uidAuthor, oMember) + + self._oDb.maybeCommit(fCommit); + return True; + + def editEntry(self, oData, uidAuthor, fCommit = False): + """ + Modifies a test group. + """ + + # + # Validate inputs and read in the old(/current) data. + # + assert isinstance(oData, TestGroupDataEx); + dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit); + if dErrors: + raise TMInvalidData('editEntry invalid input: %s' % (dErrors,)); + self._assertUniq(oData, oData.idTestGroup); + + oOldData = TestGroupDataEx().initFromDbWithId(self._oDb, oData.idTestGroup); + + # + # Update the data that needs updating. + # + + if not oData.isEqualEx(oOldData, [ 'aoMembers', 'tsEffective', 'tsExpire', 'uidAuthor', ]): + self._historizeTestGroup(oData.idTestGroup); + self._oDb.execute('INSERT INTO TestGroups\n' + ' (uidAuthor, idTestGroup, sName, sDescription, sComment)\n' + 'VALUES (%s, %s, %s, %s, %s)\n' + , ( uidAuthor, + oData.idTestGroup, + oData.sName, + oData.sDescription, + oData.sComment )); + + # Create a lookup dictionary for old entries. + dOld = {}; + for oOld in oOldData.aoMembers: + dOld[oOld.idTestCase] = oOld; + assert len(dOld) == len(oOldData.aoMembers); + + # Add new members, updated existing ones. + dNew = {}; + for oNewMember in oData.aoMembers: + oNewMember.idTestGroup = oData.idTestGroup; + if oNewMember.idTestCase in dNew: + raise TMRowAlreadyExists('Duplicate test group member: idTestCase=%d (%s / %s)' + % (oNewMember.idTestCase, oNewMember, dNew[oNewMember.idTestCase],)); + dNew[oNewMember.idTestCase] = oNewMember; + + oOldMember = dOld.get(oNewMember.idTestCase, None); + if oOldMember is not None: + if oNewMember.isEqualEx(oOldMember, [ 'uidAuthor', 'tsEffective', 'tsExpire' ]): + continue; # Skip, nothing changed. + self._historizeTestGroupMember(oData.idTestGroup, oNewMember.idTestCase); + self._insertTestGroupMember(uidAuthor, oNewMember); + + # Expire members that have been removed. + sQuery = self._oDb.formatBindArgs('UPDATE TestGroupMembers\n' + 'SET tsExpire = CURRENT_TIMESTAMP\n' + 'WHERE idTestGroup = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , ( oData.idTestGroup, )); + if dNew: + sQuery += ' AND idTestCase NOT IN (%s)' % (', '.join([str(iKey) for iKey in dNew]),); + self._oDb.execute(sQuery); + + self._oDb.maybeCommit(fCommit); + return True; + + def removeEntry(self, uidAuthor, idTestGroup, fCascade = False, fCommit = False): + """ + Deletes a test group. + """ + _ = uidAuthor; ## @todo record uidAuthor. + + # + # Cascade. + # + if fCascade is not True: + self._oDb.execute('SELECT SchedGroups.idSchedGroup, SchedGroups.sName\n' + 'FROM SchedGroupMembers, SchedGroups\n' + 'WHERE SchedGroupMembers.idTestGroup = %s\n' + ' AND SchedGroupMembers.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND SchedGroups.idSchedGroup = SchedGroupMembers.idSchedGroup\n' + ' AND SchedGroups.tsExpire = \'infinity\'::TIMESTAMP\n' + , ( idTestGroup, )); + aoGroups = self._oDb.fetchAll(); + if aoGroups: + asGroups = ['%s (#%d)' % (sName, idSchedGroup) for idSchedGroup, sName in aoGroups]; + raise TMRowInUse('Test group #%d is member of one or more scheduling groups: %s' + % (idTestGroup, ', '.join(asGroups),)); + else: + self._oDb.execute('UPDATE SchedGroupMembers\n' + 'SET tsExpire = CURRENT_TIMESTAMP\n' + 'WHERE idTestGroup = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , ( idTestGroup, )); + + # + # Remove the group. + # + self._oDb.execute('UPDATE TestGroupMembers\n' + 'SET tsExpire = CURRENT_TIMESTAMP\n' + 'WHERE idTestGroup = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idTestGroup,)) + self._oDb.execute('UPDATE TestGroups\n' + 'SET tsExpire = CURRENT_TIMESTAMP\n' + 'WHERE idTestGroup = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idTestGroup,)) + + self._oDb.maybeCommit(fCommit) + return True; + + def cachedLookup(self, idTestGroup): + """ + Looks up the most recent TestGroupDataEx object for idTestGroup + via an object cache. + + Returns a shared TestGroupDataEx object. None if not found. + Raises exception on DB error. + """ + if self.dCache is None: + self.dCache = self._oDb.getCache('TestGroupDataEx'); + oEntry = self.dCache.get(idTestGroup, None); + if oEntry is None: + fNeedTsNow = False; + self._oDb.execute('SELECT *\n' + 'FROM TestGroups\n' + 'WHERE idTestGroup = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idTestGroup, )); + if self._oDb.getRowCount() == 0: + # Maybe it was deleted, try get the last entry. + self._oDb.execute('SELECT *\n' + 'FROM TestGroups\n' + 'WHERE idTestGroup = %s\n' + 'ORDER BY tsExpire DESC\n' + 'LIMIT 1\n' + , (idTestGroup, )); + fNeedTsNow = True; + elif self._oDb.getRowCount() > 1: + raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idTestGroup)); + + if self._oDb.getRowCount() == 1: + aaoRow = self._oDb.fetchOne(); + oEntry = TestGroupDataEx(); + tsNow = oEntry.initFromDbRow(aaoRow).tsEffective if fNeedTsNow else None; + oEntry.initFromDbRowEx(aaoRow, self._oDb, tsNow); + self.dCache[idTestGroup] = oEntry; + return oEntry; + + + # + # Other methods. + # + + def fetchOrderedByName(self, tsNow = None): + """ + Return list of objects of type TestGroupData ordered by name. + May raise exception on database error. + """ + if tsNow is None: + self._oDb.execute('SELECT *\n' + 'FROM TestGroups\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY sName ASC\n'); + else: + self._oDb.execute('SELECT *\n' + 'FROM TestGroups\n' + 'WHERE tsExpire > %s\n' + ' AND tsEffective <= %s\n' + 'ORDER BY sName ASC\n' + , (tsNow, tsNow,)); + aoRet = [] + for _ in range(self._oDb.getRowCount()): + aoRet.append(TestGroupData().initFromDbRow(self._oDb.fetchOne())); + return aoRet; + + def getMembers(self, idTestGroup): + """ + Fetches all test case records from DB which are + belong to current Test Group. + Returns list of objects of type TestGroupMemberData2 (!). + """ + self._oDb.execute('SELECT TestCases.*,\n' + ' TestGroupMembers.idTestGroup,\n' + ' TestGroupMembers.aidTestCaseArgs\n' + 'FROM TestCases, TestGroupMembers\n' + 'WHERE TestCases.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND TestGroupMembers.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND TestGroupMembers.idTestCase = TestCases.idTestCase\n' + ' AND TestGroupMembers.idTestGroup = %s\n' + 'ORDER BY TestCases.idTestCase ASC;', + (idTestGroup,)) + + aaoRows = self._oDb.fetchAll() + aoRet = [] + for aoRow in aaoRows: + aoRet.append(TestGroupMemberData2().initFromDbRowEx(aoRow)) + + return aoRet + + def getAll(self, tsNow=None): + """Return list of objects of type TestGroupData""" + + if tsNow is None: + self._oDb.execute('SELECT *\n' + 'FROM TestGroups\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY idTestGroup ASC;') + else: + self._oDb.execute('SELECT *\n' + 'FROM TestGroups\n' + 'WHERE tsExpire > %s\n' + ' AND tsEffective <= %s\n' + 'ORDER BY idTestGroup ASC;', + (tsNow, tsNow)) + + aaoRows = self._oDb.fetchAll() + aoRet = [] + for aoRow in aaoRows: + aoRet.append(TestGroupData().initFromDbRow(aoRow)) + + return aoRet + + def getById(self, idTestGroup, tsNow=None): + """Get Test Group data by its ID""" + + if tsNow is None: + self._oDb.execute('SELECT *\n' + 'FROM TestGroups\n' + 'WHERE tsExpire = \'infinity\'::timestamp\n' + ' AND idTestGroup = %s\n' + 'ORDER BY idTestGroup ASC;' + , (idTestGroup,)) + else: + self._oDb.execute('SELECT *\n' + 'FROM TestGroups\n' + 'WHERE tsExpire > %s\n' + ' AND tsEffective <= %s\n' + ' AND idTestGroup = %s\n' + 'ORDER BY idTestGroup ASC;' + , (tsNow, tsNow, idTestGroup)) + + aRows = self._oDb.fetchAll() + if len(aRows) not in (0, 1): + raise TMTooManyRows('Found more than one test groups with the same credentials. Database structure is corrupted.') + try: + return TestGroupData().initFromDbRow(aRows[0]) + except IndexError: + return None + + # + # Helpers. + # + + def _assertUniq(self, oData, idTestGroupIgnore): + """ Checks that the test group name is unique, raises exception if it isn't. """ + self._oDb.execute('SELECT idTestGroup\n' + 'FROM TestGroups\n' + 'WHERE sName = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + + ('' if idTestGroupIgnore is None else ' AND idTestGroup <> %d\n' % (idTestGroupIgnore,)) + , ( oData.sName, )) + if self._oDb.getRowCount() > 0: + raise TMRowAlreadyExists('A Test group with name "%s" already exist.' % (oData.sName,)); + return True; + + def _historizeTestGroup(self, idTestGroup): + """ Historize Test Group record. """ + self._oDb.execute('UPDATE TestGroups\n' + 'SET tsExpire = CURRENT_TIMESTAMP\n' + 'WHERE idTestGroup = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , ( idTestGroup, )); + return True; + + def _historizeTestGroupMember(self, idTestGroup, idTestCase): + """ Historize Test Group Member record. """ + self._oDb.execute('UPDATE TestGroupMembers\n' + 'SET tsExpire = CURRENT_TIMESTAMP\n' + 'WHERE idTestGroup = %s\n' + ' AND idTestCase = %s\n' + ' AND tsExpire = \'infinity\'::timestamp\n' + , (idTestGroup, idTestCase,)); + return True; + + def _insertTestGroupMember(self, uidAuthor, oMember): + """ Inserts a test group member. """ + self._oDb.execute('INSERT INTO TestGroupMembers\n' + ' (uidAuthor, idTestGroup, idTestCase, iSchedPriority, aidTestCaseArgs)\n' + 'VALUES (%s, %s, %s, %s, %s)\n' + , ( uidAuthor, + oMember.idTestGroup, + oMember.idTestCase, + oMember.iSchedPriority, + oMember.aidTestCaseArgs, )); + return True; + + + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +class TestGroupMemberDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [TestGroupMemberData(),]; + +class TestGroupDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [TestGroupData(),]; + +if __name__ == '__main__': + unittest.main(); + # not reached. + diff --git a/src/VBox/ValidationKit/testmanager/core/testresultfailures.py b/src/VBox/ValidationKit/testmanager/core/testresultfailures.py new file mode 100755 index 00000000..cd551ec5 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/testresultfailures.py @@ -0,0 +1,529 @@ +# -*- coding: utf-8 -*- +# $Id: testresultfailures.py $ +# pylint: disable=too-many-lines + +## @todo Rename this file to testresult.py! + +""" +Test Manager - Test result failures. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + +# Standard python imports. +import sys; +import unittest; + +# Validation Kit imports. +from testmanager.core.base import ModelDataBase, ModelLogicBase, ModelDataBaseTestCase, TMInvalidData, TMRowNotFound, \ + TMRowAlreadyExists, ChangeLogEntry, AttributeChangeEntry; +from testmanager.core.failurereason import FailureReasonData; +from testmanager.core.useraccount import UserAccountLogic; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + xrange = range; # pylint: disable=redefined-builtin,invalid-name + + +class TestResultFailureData(ModelDataBase): + """ + Test result failure reason data. + """ + + ksIdAttr = 'idTestResult'; + kfIdAttrIsForForeign = True; # Modifies the 'add' validation. + + ksParam_idTestResult = 'TestResultFailure_idTestResult'; + ksParam_tsEffective = 'TestResultFailure_tsEffective'; + ksParam_tsExpire = 'TestResultFailure_tsExpire'; + ksParam_uidAuthor = 'TestResultFailure_uidAuthor'; + ksParam_idTestSet = 'TestResultFailure_idTestSet'; + ksParam_idFailureReason = 'TestResultFailure_idFailureReason'; + ksParam_sComment = 'TestResultFailure_sComment'; + + kasAllowNullAttributes = ['tsEffective', 'tsExpire', 'uidAuthor', 'sComment', 'idTestSet' ]; + + kcDbColumns = 7; + + def __init__(self): + ModelDataBase.__init__(self) + self.idTestResult = None; + self.tsEffective = None; + self.tsExpire = None; + self.uidAuthor = None; + self.idTestSet = None; + self.idFailureReason = None; + self.sComment = None; + + def initFromDbRow(self, aoRow): + """ + Reinitialize from a SELECT * FROM TestResultFailures. + Return self. Raises exception if no row. + """ + if aoRow is None: + raise TMRowNotFound('Test result file record not found.') + + self.idTestResult = aoRow[0]; + self.tsEffective = aoRow[1]; + self.tsExpire = aoRow[2]; + self.uidAuthor = aoRow[3]; + self.idTestSet = aoRow[4]; + self.idFailureReason = aoRow[5]; + self.sComment = aoRow[6]; + return self; + + def initFromDbWithId(self, oDb, idTestResult, tsNow = None, sPeriodBack = None): + """ + Initialize the object from the database. + """ + oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb, + 'SELECT *\n' + 'FROM TestResultFailures\n' + 'WHERE idTestResult = %s\n' + , ( idTestResult,), tsNow, sPeriodBack)); + aoRow = oDb.fetchOne() + if aoRow is None: + raise TMRowNotFound('idTestResult=%s not found (tsNow=%s, sPeriodBack=%s)' % (idTestResult, tsNow, sPeriodBack)); + assert len(aoRow) == self.kcDbColumns; + return self.initFromDbRow(aoRow); + + def initFromValues(self, idTestResult, idFailureReason, uidAuthor, + tsExpire = None, tsEffective = None, idTestSet = None, sComment = None): + """ + Initialize from values. + """ + self.idTestResult = idTestResult; + self.tsEffective = tsEffective; + self.tsExpire = tsExpire; + self.uidAuthor = uidAuthor; + self.idTestSet = idTestSet; + self.idFailureReason = idFailureReason; + self.sComment = sComment; + return self; + + + +class TestResultFailureDataEx(TestResultFailureData): + """ + Extends TestResultFailureData by resolving reasons and user. + """ + + def __init__(self): + TestResultFailureData.__init__(self); + self.oFailureReason = None; + self.oAuthor = None; + + def initFromDbRowEx(self, aoRow, oFailureReasonLogic, oUserAccountLogic): + """ + Reinitialize from a SELECT * FROM TestResultFailures. + Return self. Raises exception if no row. + """ + self.initFromDbRow(aoRow); + self.oFailureReason = oFailureReasonLogic.cachedLookup(self.idFailureReason); + self.oAuthor = oUserAccountLogic.cachedLookup(self.uidAuthor); + return self; + + +class TestResultListingData(ModelDataBase): # pylint: disable=too-many-instance-attributes + """ + Test case result data representation for table listing + """ + + def __init__(self): + """Initialize""" + ModelDataBase.__init__(self) + + self.idTestSet = None + + self.idBuildCategory = None; + self.sProduct = None + self.sRepository = None; + self.sBranch = None + self.sType = None + self.idBuild = None; + self.sVersion = None; + self.iRevision = None + + self.sOs = None; + self.sOsVersion = None; + self.sArch = None; + self.sCpuVendor = None; + self.sCpuName = None; + self.cCpus = None; + self.fCpuHwVirt = None; + self.fCpuNestedPaging = None; + self.fCpu64BitGuest = None; + self.idTestBox = None + self.sTestBoxName = None + + self.tsCreated = None + self.tsElapsed = None + self.enmStatus = None + self.cErrors = None; + + self.idTestCase = None + self.sTestCaseName = None + self.sBaseCmd = None + self.sArgs = None + self.sSubName = None; + + self.idBuildTestSuite = None; + self.iRevisionTestSuite = None; + + self.oFailureReason = None; + self.oFailureReasonAssigner = None; + self.tsFailureReasonAssigned = None; + self.sFailureReasonComment = None; + + def initFromDbRowEx(self, aoRow, oFailureReasonLogic, oUserAccountLogic): + """ + Reinitialize from a database query. + Return self. Raises exception if no row. + """ + if aoRow is None: + raise TMRowNotFound('Test result record not found.') + + self.idTestSet = aoRow[0]; + + self.idBuildCategory = aoRow[1]; + self.sProduct = aoRow[2]; + self.sRepository = aoRow[3]; + self.sBranch = aoRow[4]; + self.sType = aoRow[5]; + self.idBuild = aoRow[6]; + self.sVersion = aoRow[7]; + self.iRevision = aoRow[8]; + + self.sOs = aoRow[9]; + self.sOsVersion = aoRow[10]; + self.sArch = aoRow[11]; + self.sCpuVendor = aoRow[12]; + self.sCpuName = aoRow[13]; + self.cCpus = aoRow[14]; + self.fCpuHwVirt = aoRow[15]; + self.fCpuNestedPaging = aoRow[16]; + self.fCpu64BitGuest = aoRow[17]; + self.idTestBox = aoRow[18]; + self.sTestBoxName = aoRow[19]; + + self.tsCreated = aoRow[20]; + self.tsElapsed = aoRow[21]; + self.enmStatus = aoRow[22]; + self.cErrors = aoRow[23]; + + self.idTestCase = aoRow[24]; + self.sTestCaseName = aoRow[25]; + self.sBaseCmd = aoRow[26]; + self.sArgs = aoRow[27]; + self.sSubName = aoRow[28]; + + self.idBuildTestSuite = aoRow[29]; + self.iRevisionTestSuite = aoRow[30]; + + self.oFailureReason = None; + if aoRow[31] is not None: + self.oFailureReason = oFailureReasonLogic.cachedLookup(aoRow[31]); + self.oFailureReasonAssigner = None; + if aoRow[32] is not None: + self.oFailureReasonAssigner = oUserAccountLogic.cachedLookup(aoRow[32]); + self.tsFailureReasonAssigned = aoRow[33]; + self.sFailureReasonComment = aoRow[34]; + + return self + + + +class TestResultFailureLogic(ModelLogicBase): # pylint: disable=too-few-public-methods + """ + Test result failure reason logic. + """ + + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb) + + def fetchForChangeLog(self, idTestResult, iStart, cMaxRows, tsNow): # pylint: disable=too-many-locals + """ + Fetches change log entries for a failure reason. + + Returns an array of ChangeLogEntry instance and an indicator whether + there are more entries. + Raises exception on error. + """ + + if tsNow is None: + tsNow = self._oDb.getCurrentTimestamp(); + + # 1. Get a list of the changes from both TestResultFailures and assoicated + # FailureReasons. The latter is useful since the failure reason + # description may evolve along side the invidiual failure analysis. + self._oDb.execute('( SELECT trf.tsEffective AS tsEffectiveChangeLog,\n' + ' trf.uidAuthor AS uidAuthorChangeLog,\n' + ' trf.*,\n' + ' fr.*\n' + ' FROM TestResultFailures trf,\n' + ' FailureReasons fr\n' + ' WHERE trf.idTestResult = %s\n' + ' AND trf.tsEffective <= %s\n' + ' AND trf.idFailureReason = fr.idFailureReason\n' + ' AND fr.tsEffective <= trf.tsEffective\n' + ' AND fr.tsExpire > trf.tsEffective\n' + ')\n' + 'UNION\n' + '( SELECT fr.tsEffective AS tsEffectiveChangeLog,\n' + ' fr.uidAuthor AS uidAuthorChangeLog,\n' + ' trf.*,\n' + ' fr.*\n' + ' FROM TestResultFailures trf,\n' + ' FailureReasons fr\n' + ' WHERE trf.idTestResult = %s\n' + ' AND trf.tsEffective <= %s\n' + ' AND trf.idFailureReason = fr.idFailureReason\n' + ' AND fr.tsEffective > trf.tsEffective\n' + ' AND fr.tsEffective < trf.tsExpire\n' + ')\n' + 'ORDER BY tsEffectiveChangeLog DESC\n' + 'LIMIT %s OFFSET %s\n' + , ( idTestResult, tsNow, idTestResult, tsNow, cMaxRows + 1, iStart, )); + + aaoRows = []; + for aoChange in self._oDb.fetchAll(): + oTrf = TestResultFailureDataEx().initFromDbRow(aoChange[2:]); + oFr = FailureReasonData().initFromDbRow(aoChange[(2+TestResultFailureData.kcDbColumns):]); + oTrf.oFailureReason = oFr; + aaoRows.append([aoChange[0], aoChange[1], oTrf, oFr]); + + # 2. Calculate the changes. + oFailureCategoryLogic = None; + aoEntries = []; + for i in xrange(0, len(aaoRows) - 1): + aoNew = aaoRows[i]; + aoOld = aaoRows[i + 1]; + + aoChanges = []; + oNew = aoNew[2]; + oOld = aoOld[2]; + for sAttr in oNew.getDataAttributes(): + if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', 'oFailureReason', 'oAuthor' ]: + oOldAttr = getattr(oOld, sAttr); + oNewAttr = getattr(oNew, sAttr); + if oOldAttr != oNewAttr: + if sAttr == 'idFailureReason': + oNewAttr = '%s (%s)' % (oNewAttr, oNew.oFailureReason.sShort, ); + oOldAttr = '%s (%s)' % (oOldAttr, oOld.oFailureReason.sShort, ); + aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr))); + if oOld.idFailureReason == oNew.idFailureReason: + oNew = aoNew[3]; + oOld = aoOld[3]; + for sAttr in oNew.getDataAttributes(): + if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', ]: + oOldAttr = getattr(oOld, sAttr); + oNewAttr = getattr(oNew, sAttr); + if oOldAttr != oNewAttr: + if sAttr == 'idFailureCategory': + if oFailureCategoryLogic is None: + from testmanager.core.failurecategory import FailureCategoryLogic; + oFailureCategoryLogic = FailureCategoryLogic(self._oDb); + oCat = oFailureCategoryLogic.cachedLookup(oNewAttr); + if oCat is not None: + oNewAttr = '%s (%s)' % (oNewAttr, oCat.sShort, ); + oCat = oFailureCategoryLogic.cachedLookup(oOldAttr); + if oCat is not None: + oOldAttr = '%s (%s)' % (oOldAttr, oCat.sShort, ); + aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr))); + + + tsExpire = aaoRows[i - 1][0] if i > 0 else aoNew[2].tsExpire; + aoEntries.append(ChangeLogEntry(aoNew[1], None, aoNew[0], tsExpire, aoNew[2], aoOld[2], aoChanges)); + + # If we're at the end of the log, add the initial entry. + if len(aaoRows) <= cMaxRows and aaoRows: + aoNew = aaoRows[-1]; + tsExpire = aaoRows[-1 - 1][0] if len(aaoRows) > 1 else aoNew[2].tsExpire; + aoEntries.append(ChangeLogEntry(aoNew[1], None, aoNew[0], tsExpire, aoNew[2], None, [])); + + return (UserAccountLogic(self._oDb).resolveChangeLogAuthors(aoEntries), len(aaoRows) > cMaxRows); + + + def getById(self, idTestResult): + """Get Test result failure reason data by idTestResult""" + + self._oDb.execute('SELECT *\n' + 'FROM TestResultFailures\n' + 'WHERE tsExpire = \'infinity\'::timestamp\n' + ' AND idTestResult = %s;', (idTestResult,)) + aRows = self._oDb.fetchAll() + if len(aRows) not in (0, 1): + raise self._oDb.integrityException( + 'Found more than one failure reasons with the same credentials. Database structure is corrupted.') + try: + return TestResultFailureData().initFromDbRow(aRows[0]) + except IndexError: + return None + + def addEntry(self, oData, uidAuthor, fCommit = False): + """ + Add a test result failure reason record. + """ + + # + # Validate inputs and read in the old(/current) data. + # + assert isinstance(oData, TestResultFailureData); + dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_AddForeignId); + if dErrors: + raise TMInvalidData('editEntry invalid input: %s' % (dErrors,)); + + # Check if it exist first (we're adding, not editing, collisions not allowed). + oOldData = self.getById(oData.idTestResult); + if oOldData is not None: + raise TMRowAlreadyExists('TestResult %d already have a failure reason associated with it:' + '%s\n' + 'Perhaps someone else beat you to it? Or did you try resubmit?' + % (oData.idTestResult, oOldData)); + oData = self._resolveSetTestIdIfMissing(oData); + + # + # Add record. + # + self._readdEntry(uidAuthor, oData); + self._oDb.maybeCommit(fCommit); + return True; + + def editEntry(self, oData, uidAuthor, fCommit = False): + """ + Modifies a test result failure reason. + """ + + # + # Validate inputs and read in the old(/current) data. + # + assert isinstance(oData, TestResultFailureData); + dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit); + if dErrors: + raise TMInvalidData('editEntry invalid input: %s' % (dErrors,)); + + oOldData = self.getById(oData.idTestResult) + oData.idTestSet = oOldData.idTestSet; + + # + # Update the data that needs updating. + # + if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor', ]): + self._historizeEntry(oData.idTestResult); + self._readdEntry(uidAuthor, oData); + self._oDb.maybeCommit(fCommit); + return True; + + + def removeEntry(self, uidAuthor, idTestResult, fCascade = False, fCommit = False): + """ + Deletes a test result failure reason. + """ + _ = fCascade; # Not applicable. + + oData = self.getById(idTestResult) + (tsCur, tsCurMinusOne) = self._oDb.getCurrentTimestamps(); + if oData.tsEffective not in (tsCur, tsCurMinusOne): + self._historizeEntry(idTestResult, tsCurMinusOne); + self._readdEntry(uidAuthor, oData, tsCurMinusOne); + self._historizeEntry(idTestResult); + self._oDb.execute('UPDATE TestResultFailures\n' + 'SET tsExpire = CURRENT_TIMESTAMP\n' + 'WHERE idTestResult = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (idTestResult,)); + self._oDb.maybeCommit(fCommit); + return True; + + # + # Helpers. + # + + def _readdEntry(self, uidAuthor, oData, tsEffective = None): + """ + Re-adds the TestResultFailure entry. Used by addEntry, editEntry and removeEntry. + """ + if tsEffective is None: + tsEffective = self._oDb.getCurrentTimestamp(); + self._oDb.execute('INSERT INTO TestResultFailures (\n' + ' uidAuthor,\n' + ' tsEffective,\n' + ' idTestResult,\n' + ' idTestSet,\n' + ' idFailureReason,\n' + ' sComment)\n' + 'VALUES (%s, %s, %s, %s, %s, %s)\n' + , ( uidAuthor, + tsEffective, + oData.idTestResult, + oData.idTestSet, + oData.idFailureReason, + oData.sComment,) ); + return True; + + + def _historizeEntry(self, idTestResult, tsExpire = None): + """ Historizes the current entry. """ + if tsExpire is None: + tsExpire = self._oDb.getCurrentTimestamp(); + self._oDb.execute('UPDATE TestResultFailures\n' + 'SET tsExpire = %s\n' + 'WHERE idTestResult = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (tsExpire, idTestResult,)); + return True; + + + def _resolveSetTestIdIfMissing(self, oData): + """ Resolve any missing idTestSet reference (it's a duplicate for speed efficiency). """ + if oData.idTestSet is None and oData.idTestResult is not None: + self._oDb.execute('SELECT idTestSet FROM TestResults WHERE idTestResult = %s', (oData.idTestResult,)); + oData.idTestSet = self._oDb.fetchOne()[0]; + return oData; + + + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +class TestResultFailureDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [TestResultFailureData(),]; + +if __name__ == '__main__': + unittest.main(); + # not reached. + diff --git a/src/VBox/ValidationKit/testmanager/core/testresults.py b/src/VBox/ValidationKit/testmanager/core/testresults.py new file mode 100755 index 00000000..a5259c11 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/testresults.py @@ -0,0 +1,2926 @@ +# -*- coding: utf-8 -*- +# $Id: testresults.py $ +# pylint: disable=too-many-lines + +## @todo Rename this file to testresult.py! + +""" +Test Manager - Fetch test results. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153425 $" + + +# Standard python imports. +import sys; +import unittest; + +# Validation Kit imports. +from common import constants; +from testmanager import config; +from testmanager.core.base import ModelDataBase, ModelLogicBase, ModelDataBaseTestCase, ModelFilterBase, \ + FilterCriterion, FilterCriterionValueAndDescription, \ + TMExceptionBase, TMTooManyRows, TMRowNotFound; +from testmanager.core.testgroup import TestGroupData; +from testmanager.core.build import BuildDataEx, BuildCategoryData; +from testmanager.core.failurereason import FailureReasonLogic; +from testmanager.core.testbox import TestBoxData, TestBoxLogic; +from testmanager.core.testcase import TestCaseData; +from testmanager.core.schedgroup import SchedGroupData, SchedGroupLogic; +from testmanager.core.systemlog import SystemLogData, SystemLogLogic; +from testmanager.core.testresultfailures import TestResultFailureDataEx; +from testmanager.core.useraccount import UserAccountLogic; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + + +class TestResultData(ModelDataBase): + """ + Test case execution result data + """ + + ## @name TestStatus_T + # @{ + ksTestStatus_Running = 'running'; + ksTestStatus_Success = 'success'; + ksTestStatus_Skipped = 'skipped'; + ksTestStatus_BadTestBox = 'bad-testbox'; + ksTestStatus_Aborted = 'aborted'; + ksTestStatus_Failure = 'failure'; + ksTestStatus_TimedOut = 'timed-out'; + ksTestStatus_Rebooted = 'rebooted'; + ## @} + + ## List of relatively harmless (to testgroup/case) statuses. + kasHarmlessTestStatuses = [ ksTestStatus_Skipped, ksTestStatus_BadTestBox, ksTestStatus_Aborted, ]; + ## List of bad statuses. + kasBadTestStatuses = [ ksTestStatus_Failure, ksTestStatus_TimedOut, ksTestStatus_Rebooted, ]; + + + ksIdAttr = 'idTestResult'; + + ksParam_idTestResult = 'TestResultData_idTestResult'; + ksParam_idTestResultParent = 'TestResultData_idTestResultParent'; + ksParam_idTestSet = 'TestResultData_idTestSet'; + ksParam_tsCreated = 'TestResultData_tsCreated'; + ksParam_tsElapsed = 'TestResultData_tsElapsed'; + ksParam_idStrName = 'TestResultData_idStrName'; + ksParam_cErrors = 'TestResultData_cErrors'; + ksParam_enmStatus = 'TestResultData_enmStatus'; + ksParam_iNestingDepth = 'TestResultData_iNestingDepth'; + kasValidValues_enmStatus = [ + ksTestStatus_Running, + ksTestStatus_Success, + ksTestStatus_Skipped, + ksTestStatus_BadTestBox, + ksTestStatus_Aborted, + ksTestStatus_Failure, + ksTestStatus_TimedOut, + ksTestStatus_Rebooted + ]; + + + def __init__(self): + ModelDataBase.__init__(self) + self.idTestResult = None + self.idTestResultParent = None + self.idTestSet = None + self.tsCreated = None + self.tsElapsed = None + self.idStrName = None + self.cErrors = 0; + self.enmStatus = None + self.iNestingDepth = None + + def initFromDbRow(self, aoRow): + """ + Reinitialize from a SELECT * FROM TestResults. + Return self. Raises exception if no row. + """ + if aoRow is None: + raise TMRowNotFound('Test result record not found.') + + self.idTestResult = aoRow[0] + self.idTestResultParent = aoRow[1] + self.idTestSet = aoRow[2] + self.tsCreated = aoRow[3] + self.tsElapsed = aoRow[4] + self.idStrName = aoRow[5] + self.cErrors = aoRow[6] + self.enmStatus = aoRow[7] + self.iNestingDepth = aoRow[8] + return self; + + def initFromDbWithId(self, oDb, idTestResult, tsNow = None, sPeriodBack = None): + """ + Initialize from the database, given the ID of a row. + """ + _ = tsNow; + _ = sPeriodBack; + oDb.execute('SELECT *\n' + 'FROM TestResults\n' + 'WHERE idTestResult = %s\n' + , ( idTestResult,)); + aoRow = oDb.fetchOne() + if aoRow is None: + raise TMRowNotFound('idTestResult=%s not found' % (idTestResult,)); + return self.initFromDbRow(aoRow); + + def isFailure(self): + """ Check if it's a real failure. """ + return self.enmStatus in self.kasBadTestStatuses; + + +class TestResultDataEx(TestResultData): + """ + Extended test result data class. + + This is intended for use as a node in a result tree. This is not intended + for serialization to parameters or vice versa. Use TestResultLogic to + construct the tree. + """ + + def __init__(self): + TestResultData.__init__(self) + self.sName = None; # idStrName resolved. + self.oParent = None; # idTestResultParent within the tree. + + self.aoChildren = []; # TestResultDataEx; + self.aoValues = []; # TestResultValueDataEx; + self.aoMsgs = []; # TestResultMsgDataEx; + self.aoFiles = []; # TestResultFileDataEx; + self.oReason = None; # TestResultReasonDataEx; + + def initFromDbRow(self, aoRow): + """ + Initialize from a query like this: + SELECT TestResults.*, TestResultStrTab.sValue + FROM TestResults, TestResultStrTab + WHERE TestResultStrTab.idStr = TestResults.idStrName + + Note! The caller is expected to fetch children, values, failure + details, and files. + """ + self.sName = None; + self.oParent = None; + self.aoChildren = []; + self.aoValues = []; + self.aoMsgs = []; + self.aoFiles = []; + self.oReason = None; + + TestResultData.initFromDbRow(self, aoRow); + + self.sName = aoRow[9]; + return self; + + def deepCountErrorContributers(self): + """ + Counts how many test result instances actually contributed to cErrors. + """ + + # Check each child (if any). + cChanges = 0; + cChildErrors = 0; + for oChild in self.aoChildren: + if oChild.cErrors > 0: + cChildErrors += oChild.cErrors; + cChanges += oChild.deepCountErrorContributers(); + + # Did we contribute as well? + if self.cErrors > cChildErrors: + cChanges += 1; + return cChanges; + + def getListOfFailures(self): + """ + Get a list of test results instances actually contributing to cErrors. + + Returns a list of TestResultDataEx instances from this tree. (shared!) + """ + # Check each child (if any). + aoRet = []; + cChildErrors = 0; + for oChild in self.aoChildren: + if oChild.cErrors > 0: + cChildErrors += oChild.cErrors; + aoRet.extend(oChild.getListOfFailures()); + + # Did we contribute as well? + if self.cErrors > cChildErrors: + aoRet.append(self); + + return aoRet; + + def getListOfLogFilesByKind(self, asKinds): + """ + Get a list of test results instances actually contributing to cErrors. + + Returns a list of TestResultFileDataEx instances from this tree. (shared!) + """ + aoRet = []; + + # Check the children first. + for oChild in self.aoChildren: + aoRet.extend(oChild.getListOfLogFilesByKind(asKinds)); + + # Check our own files next. + for oFile in self.aoFiles: + if oFile.sKind in asKinds: + aoRet.append(oFile); + + return aoRet; + + def getFullName(self): + """ Constructs the full name of this test result. """ + if self.oParent is None: + return self.sName; + return self.oParent.getFullName() + ' / ' + self.sName; + + + +class TestResultValueData(ModelDataBase): + """ + Test result value data. + """ + + ksIdAttr = 'idTestResultValue'; + + ksParam_idTestResultValue = 'TestResultValue_idTestResultValue'; + ksParam_idTestResult = 'TestResultValue_idTestResult'; + ksParam_idTestSet = 'TestResultValue_idTestSet'; + ksParam_tsCreated = 'TestResultValue_tsCreated'; + ksParam_idStrName = 'TestResultValue_idStrName'; + ksParam_lValue = 'TestResultValue_lValue'; + ksParam_iUnit = 'TestResultValue_iUnit'; + + kasAllowNullAttributes = [ 'idTestSet', ]; + + def __init__(self): + ModelDataBase.__init__(self) + self.idTestResultValue = None; + self.idTestResult = None; + self.idTestSet = None; + self.tsCreated = None; + self.idStrName = None; + self.lValue = None; + self.iUnit = 0; + + def initFromDbRow(self, aoRow): + """ + Reinitialize from a SELECT * FROM TestResultValues. + Return self. Raises exception if no row. + """ + if aoRow is None: + raise TMRowNotFound('Test result value record not found.') + + self.idTestResultValue = aoRow[0]; + self.idTestResult = aoRow[1]; + self.idTestSet = aoRow[2]; + self.tsCreated = aoRow[3]; + self.idStrName = aoRow[4]; + self.lValue = aoRow[5]; + self.iUnit = aoRow[6]; + return self; + + +class TestResultValueDataEx(TestResultValueData): + """ + Extends TestResultValue by resolving the value name and unit string. + """ + + def __init__(self): + TestResultValueData.__init__(self) + self.sName = None; + self.sUnit = ''; + + def initFromDbRow(self, aoRow): + """ + Reinitialize from a query like this: + SELECT TestResultValues.*, TestResultStrTab.sValue + FROM TestResultValues, TestResultStrTab + WHERE TestResultStrTab.idStr = TestResultValues.idStrName + + Return self. Raises exception if no row. + """ + TestResultValueData.initFromDbRow(self, aoRow); + self.sName = aoRow[7]; + if self.iUnit < len(constants.valueunit.g_asNames): + self.sUnit = constants.valueunit.g_asNames[self.iUnit]; + else: + self.sUnit = '<%d>' % (self.iUnit,); + return self; + +class TestResultMsgData(ModelDataBase): + """ + Test result message data. + """ + + ksIdAttr = 'idTestResultMsg'; + + ksParam_idTestResultMsg = 'TestResultValue_idTestResultMsg'; + ksParam_idTestResult = 'TestResultValue_idTestResult'; + ksParam_idTestSet = 'TestResultValue_idTestSet'; + ksParam_tsCreated = 'TestResultValue_tsCreated'; + ksParam_idStrMsg = 'TestResultValue_idStrMsg'; + ksParam_enmLevel = 'TestResultValue_enmLevel'; + + kasAllowNullAttributes = [ 'idTestSet', ]; + + kcDbColumns = 6 + + def __init__(self): + ModelDataBase.__init__(self) + self.idTestResultMsg = None; + self.idTestResult = None; + self.idTestSet = None; + self.tsCreated = None; + self.idStrMsg = None; + self.enmLevel = None; + + def initFromDbRow(self, aoRow): + """ + Reinitialize from a SELECT * FROM TestResultMsgs. + Return self. Raises exception if no row. + """ + if aoRow is None: + raise TMRowNotFound('Test result value record not found.') + + self.idTestResultMsg = aoRow[0]; + self.idTestResult = aoRow[1]; + self.idTestSet = aoRow[2]; + self.tsCreated = aoRow[3]; + self.idStrMsg = aoRow[4]; + self.enmLevel = aoRow[5]; + return self; + +class TestResultMsgDataEx(TestResultMsgData): + """ + Extends TestResultMsg by resolving the message string. + """ + + def __init__(self): + TestResultMsgData.__init__(self) + self.sMsg = None; + + def initFromDbRow(self, aoRow): + """ + Reinitialize from a query like this: + SELECT TestResultMsg.*, TestResultStrTab.sValue + FROM TestResultMsg, TestResultStrTab + WHERE TestResultStrTab.idStr = TestResultMsgs.idStrName + + Return self. Raises exception if no row. + """ + TestResultMsgData.initFromDbRow(self, aoRow); + self.sMsg = aoRow[self.kcDbColumns]; + return self; + + +class TestResultFileData(ModelDataBase): + """ + Test result message data. + """ + + ksIdAttr = 'idTestResultFile'; + + ksParam_idTestResultFile = 'TestResultFile_idTestResultFile'; + ksParam_idTestResult = 'TestResultFile_idTestResult'; + ksParam_tsCreated = 'TestResultFile_tsCreated'; + ksParam_idStrFile = 'TestResultFile_idStrFile'; + ksParam_idStrDescription = 'TestResultFile_idStrDescription'; + ksParam_idStrKind = 'TestResultFile_idStrKind'; + ksParam_idStrMime = 'TestResultFile_idStrMime'; + + ## @name Kind of files. + ## @{ + ksKind_LogReleaseVm = 'log/release/vm'; + ksKind_LogDebugVm = 'log/debug/vm'; + ksKind_LogReleaseSvc = 'log/release/svc'; + ksKind_LogDebugSvc = 'log/debug/svc'; + ksKind_LogReleaseClient = 'log/release/client'; + ksKind_LogDebugClient = 'log/debug/client'; + ksKind_LogInstaller = 'log/installer'; + ksKind_LogUninstaller = 'log/uninstaller'; + ksKind_LogGuestKernel = 'log/guest/kernel'; + ksKind_ProcessReportVm = 'process/report/vm'; + ksKind_CrashReportVm = 'crash/report/vm'; + ksKind_CrashDumpVm = 'crash/dump/vm'; + ksKind_CrashReportSvc = 'crash/report/svc'; + ksKind_CrashDumpSvc = 'crash/dump/svc'; + ksKind_CrashReportClient = 'crash/report/client'; + ksKind_CrashDumpClient = 'crash/dump/client'; + ksKind_InfoCollection = 'info/collection'; + ksKind_InfoVgaText = 'info/vgatext'; + ksKind_MiscOther = 'misc/other'; + ksKind_ScreenshotFailure = 'screenshot/failure'; + ksKind_ScreenshotSuccesss = 'screenshot/success'; + ksKind_ScreenRecordingFailure = 'screenrecording/failure'; + ksKind_ScreenRecordingSuccess = 'screenrecording/success'; + ## @} + + kasKinds = [ + ksKind_LogReleaseVm, + ksKind_LogDebugVm, + ksKind_LogReleaseSvc, + ksKind_LogDebugSvc, + ksKind_LogReleaseClient, + ksKind_LogDebugClient, + ksKind_LogInstaller, + ksKind_LogUninstaller, + ksKind_LogGuestKernel, + ksKind_ProcessReportVm, + ksKind_CrashReportVm, + ksKind_CrashDumpVm, + ksKind_CrashReportSvc, + ksKind_CrashDumpSvc, + ksKind_CrashReportClient, + ksKind_CrashDumpClient, + ksKind_InfoCollection, + ksKind_InfoVgaText, + ksKind_MiscOther, + ksKind_ScreenshotFailure, + ksKind_ScreenshotSuccesss, + ksKind_ScreenRecordingFailure, + ksKind_ScreenRecordingSuccess, + ]; + + kasAllowNullAttributes = [ 'idTestSet', ]; + + kcDbColumns = 8 + + def __init__(self): + ModelDataBase.__init__(self) + self.idTestResultFile = None; + self.idTestResult = None; + self.idTestSet = None; + self.tsCreated = None; + self.idStrFile = None; + self.idStrDescription = None; + self.idStrKind = None; + self.idStrMime = None; + + def initFromDbRow(self, aoRow): + """ + Reinitialize from a SELECT * FROM TestResultFiles. + Return self. Raises exception if no row. + """ + if aoRow is None: + raise TMRowNotFound('Test result file record not found.') + + self.idTestResultFile = aoRow[0]; + self.idTestResult = aoRow[1]; + self.idTestSet = aoRow[2]; + self.tsCreated = aoRow[3]; + self.idStrFile = aoRow[4]; + self.idStrDescription = aoRow[5]; + self.idStrKind = aoRow[6]; + self.idStrMime = aoRow[7]; + return self; + +class TestResultFileDataEx(TestResultFileData): + """ + Extends TestResultFile by resolving the strings. + """ + + def __init__(self): + TestResultFileData.__init__(self) + self.sFile = None; + self.sDescription = None; + self.sKind = None; + self.sMime = None; + + def initFromDbRow(self, aoRow): + """ + Reinitialize from a query like this: + SELECT TestResultFiles.*, + StrTabFile.sValue AS sFile, + StrTabDesc.sValue AS sDescription + StrTabKind.sValue AS sKind, + StrTabMime.sValue AS sMime, + FROM ... + + Return self. Raises exception if no row. + """ + TestResultFileData.initFromDbRow(self, aoRow); + self.sFile = aoRow[self.kcDbColumns]; + self.sDescription = aoRow[self.kcDbColumns + 1]; + self.sKind = aoRow[self.kcDbColumns + 2]; + self.sMime = aoRow[self.kcDbColumns + 3]; + return self; + + def initFakeMainLog(self, oTestSet): + """ + Reinitializes to represent the main.log object (not in DB). + + Returns self. + """ + self.idTestResultFile = 0; + self.idTestResult = oTestSet.idTestResult; + self.tsCreated = oTestSet.tsCreated; + self.idStrFile = None; + self.idStrDescription = None; + self.idStrKind = None; + self.idStrMime = None; + + self.sFile = 'main.log'; + self.sDescription = ''; + self.sKind = 'log/main'; + self.sMime = 'text/plain'; + return self; + + def isProbablyUtf8Encoded(self): + """ + Checks if the file is likely to be UTF-8 encoded. + """ + if self.sMime in [ 'text/plain', 'text/html' ]: + return True; + return False; + + def getMimeWithEncoding(self): + """ + Gets the MIME type with encoding if likely to be UTF-8. + """ + if self.isProbablyUtf8Encoded(): + return '%s; charset=utf-8' % (self.sMime,); + return self.sMime; + + + +class TestResultListingData(ModelDataBase): # pylint: disable=too-many-instance-attributes + """ + Test case result data representation for table listing + """ + + class FailureReasonListingData(object): + """ Failure reason listing data """ + def __init__(self): + self.oFailureReason = None; + self.oFailureReasonAssigner = None; + self.tsFailureReasonAssigned = None; + self.sFailureReasonComment = None; + + def __init__(self): + """Initialize""" + ModelDataBase.__init__(self) + + self.idTestSet = None + + self.idBuildCategory = None; + self.sProduct = None + self.sRepository = None; + self.sBranch = None + self.sType = None + self.idBuild = None; + self.sVersion = None; + self.iRevision = None + + self.sOs = None; + self.sOsVersion = None; + self.sArch = None; + self.sCpuVendor = None; + self.sCpuName = None; + self.cCpus = None; + self.fCpuHwVirt = None; + self.fCpuNestedPaging = None; + self.fCpu64BitGuest = None; + self.idTestBox = None + self.sTestBoxName = None + + self.tsCreated = None + self.tsElapsed = None + self.enmStatus = None + self.cErrors = None; + + self.idTestCase = None + self.sTestCaseName = None + self.sBaseCmd = None + self.sArgs = None + self.sSubName = None; + + self.idBuildTestSuite = None; + self.iRevisionTestSuite = None; + + self.aoFailureReasons = []; + + def initFromDbRowEx(self, aoRow, oFailureReasonLogic, oUserAccountLogic): + """ + Reinitialize from a database query. + Return self. Raises exception if no row. + """ + if aoRow is None: + raise TMRowNotFound('Test result record not found.') + + self.idTestSet = aoRow[0]; + + self.idBuildCategory = aoRow[1]; + self.sProduct = aoRow[2]; + self.sRepository = aoRow[3]; + self.sBranch = aoRow[4]; + self.sType = aoRow[5]; + self.idBuild = aoRow[6]; + self.sVersion = aoRow[7]; + self.iRevision = aoRow[8]; + + self.sOs = aoRow[9]; + self.sOsVersion = aoRow[10]; + self.sArch = aoRow[11]; + self.sCpuVendor = aoRow[12]; + self.sCpuName = aoRow[13]; + self.cCpus = aoRow[14]; + self.fCpuHwVirt = aoRow[15]; + self.fCpuNestedPaging = aoRow[16]; + self.fCpu64BitGuest = aoRow[17]; + self.idTestBox = aoRow[18]; + self.sTestBoxName = aoRow[19]; + + self.tsCreated = aoRow[20]; + self.tsElapsed = aoRow[21]; + self.enmStatus = aoRow[22]; + self.cErrors = aoRow[23]; + + self.idTestCase = aoRow[24]; + self.sTestCaseName = aoRow[25]; + self.sBaseCmd = aoRow[26]; + self.sArgs = aoRow[27]; + self.sSubName = aoRow[28]; + + self.idBuildTestSuite = aoRow[29]; + self.iRevisionTestSuite = aoRow[30]; + + self.aoFailureReasons = []; + for i, _ in enumerate(aoRow[31]): + if aoRow[31][i] is not None \ + or aoRow[32][i] is not None \ + or aoRow[33][i] is not None \ + or aoRow[34][i] is not None: + oReason = self.FailureReasonListingData(); + if aoRow[31][i] is not None: + oReason.oFailureReason = oFailureReasonLogic.cachedLookup(aoRow[31][i]); + if aoRow[32][i] is not None: + oReason.oFailureReasonAssigner = oUserAccountLogic.cachedLookup(aoRow[32][i]); + oReason.tsFailureReasonAssigned = aoRow[33][i]; + oReason.sFailureReasonComment = aoRow[34][i]; + self.aoFailureReasons.append(oReason); + + return self + + +class TestResultHangingOffence(TMExceptionBase): + """Hanging offence committed by test case.""" + pass; # pylint: disable=unnecessary-pass + + +class TestResultFilter(ModelFilterBase): + """ + Test result filter. + """ + + kiTestStatus = 0; + kiErrorCounts = 1; + kiBranches = 2; + kiBuildTypes = 3; + kiRevisions = 4; + kiRevisionRange = 5; + kiFailReasons = 6; + kiTestCases = 7; + kiTestCaseMisc = 8; + kiTestBoxes = 9; + kiOses = 10; + kiCpuArches = 11; + kiCpuVendors = 12; + kiCpuCounts = 13; + kiMemory = 14; + kiTestboxMisc = 15; + kiPythonVersions = 16; + kiSchedGroups = 17; + + ## Misc test case / variation name filters. + ## Presented in table order. The first sub element is the presistent ID. + kaTcMisc = ( + ( 1, 'x86', ), + ( 2, 'amd64', ), + ( 3, 'uni', ), + ( 4, 'smp', ), + ( 5, 'raw', ), + ( 6, 'hw', ), + ( 7, 'np', ), + ( 8, 'Install', ), + ( 20, 'UInstall', ), # NB. out of order. + ( 9, 'Benchmark', ), + ( 18, 'smoke', ), # NB. out of order. + ( 19, 'unit', ), # NB. out of order. + ( 10, 'USB', ), + ( 11, 'Debian', ), + ( 12, 'Fedora', ), + ( 13, 'Oracle', ), + ( 14, 'RHEL', ), + ( 15, 'SUSE', ), + ( 16, 'Ubuntu', ), + ( 17, 'Win', ), + ); + + kiTbMisc_NestedPaging = 0; + kiTbMisc_NoNestedPaging = 1; + kiTbMisc_RawMode = 2; + kiTbMisc_NoRawMode = 3; + kiTbMisc_64BitGuest = 4; + kiTbMisc_No64BitGuest = 5; + kiTbMisc_HwVirt = 6; + kiTbMisc_NoHwVirt = 7; + kiTbMisc_IoMmu = 8; + kiTbMisc_NoIoMmu = 9; + + def __init__(self): + ModelFilterBase.__init__(self); + + # Test statuses + oCrit = FilterCriterion('Test statuses', sVarNm = 'ts', sType = FilterCriterion.ksType_String, + sTable = 'TestSets', sColumn = 'enmStatus'); + self.aCriteria.append(oCrit); + assert self.aCriteria[self.kiTestStatus] is oCrit; + + # Error counts + oCrit = FilterCriterion('Error counts', sVarNm = 'ec', sTable = 'TestResults', sColumn = 'cErrors'); + self.aCriteria.append(oCrit); + assert self.aCriteria[self.kiErrorCounts] is oCrit; + + # Branches + oCrit = FilterCriterion('Branches', sVarNm = 'br', sType = FilterCriterion.ksType_String, + sTable = 'BuildCategories', sColumn = 'sBranch'); + self.aCriteria.append(oCrit); + assert self.aCriteria[self.kiBranches] is oCrit; + + # Build types + oCrit = FilterCriterion('Build types', sVarNm = 'bt', sType = FilterCriterion.ksType_String, + sTable = 'BuildCategories', sColumn = 'sType'); + self.aCriteria.append(oCrit); + assert self.aCriteria[self.kiBuildTypes] is oCrit; + + # Revisions + oCrit = FilterCriterion('Revisions', sVarNm = 'rv', sTable = 'Builds', sColumn = 'iRevision'); + self.aCriteria.append(oCrit); + assert self.aCriteria[self.kiRevisions] is oCrit; + + # Revision Range + oCrit = FilterCriterion('Revision Range', sVarNm = 'rr', sType = FilterCriterion.ksType_Ranges, + sKind = FilterCriterion.ksKind_ElementOfOrNot, sTable = 'Builds', sColumn = 'iRevision'); + self.aCriteria.append(oCrit); + assert self.aCriteria[self.kiRevisionRange] is oCrit; + + # Failure reasons + oCrit = FilterCriterion('Failure reasons', sVarNm = 'fr', sType = FilterCriterion.ksType_UIntNil, + sTable = 'TestResultFailures', sColumn = 'idFailureReason'); + self.aCriteria.append(oCrit); + assert self.aCriteria[self.kiFailReasons] is oCrit; + + # Test cases and variations. + oCrit = FilterCriterion('Test case / var', sVarNm = 'tc', sTable = 'TestSets', sColumn = 'idTestCase', + oSub = FilterCriterion('Test variations', sVarNm = 'tv', + sTable = 'TestSets', sColumn = 'idTestCaseArgs')); + self.aCriteria.append(oCrit); + assert self.aCriteria[self.kiTestCases] is oCrit; + + # Special test case and varation name sub string matching. + oCrit = FilterCriterion('Test case name', sVarNm = 'cm', sKind = FilterCriterion.ksKind_Special, + asTables = ('TestCases', 'TestCaseArgs')); + oCrit.aoPossible = [ + FilterCriterionValueAndDescription(aoCur[0], 'Include %s' % (aoCur[1],)) for aoCur in self.kaTcMisc + ]; + oCrit.aoPossible.extend([ + FilterCriterionValueAndDescription(aoCur[0] + 32, 'Exclude %s' % (aoCur[1],)) for aoCur in self.kaTcMisc + ]); + self.aCriteria.append(oCrit); + assert self.aCriteria[self.kiTestCaseMisc] is oCrit; + + # Testboxes + oCrit = FilterCriterion('Testboxes', sVarNm = 'tb', sTable = 'TestSets', sColumn = 'idTestBox'); + self.aCriteria.append(oCrit); + assert self.aCriteria[self.kiTestBoxes] is oCrit; + + # Testbox OS and OS version. + oCrit = FilterCriterion('OS / version', sVarNm = 'os', sTable = 'TestBoxesWithStrings', sColumn = 'idStrOs', + oSub = FilterCriterion('OS Versions', sVarNm = 'ov', + sTable = 'TestBoxesWithStrings', sColumn = 'idStrOsVersion')); + self.aCriteria.append(oCrit); + assert self.aCriteria[self.kiOses] is oCrit; + + # Testbox CPU architectures. + oCrit = FilterCriterion('CPU arches', sVarNm = 'ca', sTable = 'TestBoxesWithStrings', sColumn = 'idStrCpuArch'); + self.aCriteria.append(oCrit); + assert self.aCriteria[self.kiCpuArches] is oCrit; + + # Testbox CPU vendors and revisions. + oCrit = FilterCriterion('CPU vendor / rev', sVarNm = 'cv', sTable = 'TestBoxesWithStrings', sColumn = 'idStrCpuVendor', + oSub = FilterCriterion('CPU revisions', sVarNm = 'cr', + sTable = 'TestBoxesWithStrings', sColumn = 'lCpuRevision')); + self.aCriteria.append(oCrit); + assert self.aCriteria[self.kiCpuVendors] is oCrit; + + # Testbox CPU (thread) count + oCrit = FilterCriterion('CPU counts', sVarNm = 'cc', sTable = 'TestBoxesWithStrings', sColumn = 'cCpus'); + self.aCriteria.append(oCrit); + assert self.aCriteria[self.kiCpuCounts] is oCrit; + + # Testbox memory sizes. + oCrit = FilterCriterion('Memory', sVarNm = 'mb', sTable = 'TestBoxesWithStrings', sColumn = 'cMbMemory'); + self.aCriteria.append(oCrit); + assert self.aCriteria[self.kiMemory] is oCrit; + + # Testbox features. + oCrit = FilterCriterion('Testbox features', sVarNm = 'tm', sKind = FilterCriterion.ksKind_Special, + sTable = 'TestBoxesWithStrings'); + oCrit.aoPossible = [ + FilterCriterionValueAndDescription(self.kiTbMisc_NestedPaging, "req nested paging"), + FilterCriterionValueAndDescription(self.kiTbMisc_NoNestedPaging, "w/o nested paging"), + #FilterCriterionValueAndDescription(self.kiTbMisc_RawMode, "req raw-mode"), - not implemented yet. + #FilterCriterionValueAndDescription(self.kiTbMisc_NoRawMode, "w/o raw-mode"), - not implemented yet. + FilterCriterionValueAndDescription(self.kiTbMisc_64BitGuest, "req 64-bit guests"), + FilterCriterionValueAndDescription(self.kiTbMisc_No64BitGuest, "w/o 64-bit guests"), + FilterCriterionValueAndDescription(self.kiTbMisc_HwVirt, "req VT-x / AMD-V"), + FilterCriterionValueAndDescription(self.kiTbMisc_NoHwVirt, "w/o VT-x / AMD-V"), + #FilterCriterionValueAndDescription(self.kiTbMisc_IoMmu, "req I/O MMU"), - not implemented yet. + #FilterCriterionValueAndDescription(self.kiTbMisc_NoIoMmu, "w/o I/O MMU"), - not implemented yet. + ]; + self.aCriteria.append(oCrit); + assert self.aCriteria[self.kiTestboxMisc] is oCrit; + + # Testbox python versions. + oCrit = FilterCriterion('Python', sVarNm = 'py', sTable = 'TestBoxesWithStrings', sColumn = 'iPythonHexVersion'); + self.aCriteria.append(oCrit); + assert self.aCriteria[self.kiPythonVersions] is oCrit; + + # Scheduling groups. + oCrit = FilterCriterion('Sched groups', sVarNm = 'sg', sTable = 'TestSets', sColumn = 'idSchedGroup'); + self.aCriteria.append(oCrit); + assert self.aCriteria[self.kiSchedGroups] is oCrit; + + + kdTbMiscConditions = { + kiTbMisc_NestedPaging: 'TestBoxesWithStrings.fCpuNestedPaging IS TRUE', + kiTbMisc_NoNestedPaging: 'TestBoxesWithStrings.fCpuNestedPaging IS FALSE', + kiTbMisc_RawMode: 'TestBoxesWithStrings.fRawMode IS TRUE', + kiTbMisc_NoRawMode: 'TestBoxesWithStrings.fRawMode IS NOT TRUE', + kiTbMisc_64BitGuest: 'TestBoxesWithStrings.fCpu64BitGuest IS TRUE', + kiTbMisc_No64BitGuest: 'TestBoxesWithStrings.fCpu64BitGuest IS FALSE', + kiTbMisc_HwVirt: 'TestBoxesWithStrings.fCpuHwVirt IS TRUE', + kiTbMisc_NoHwVirt: 'TestBoxesWithStrings.fCpuHwVirt IS FALSE', + kiTbMisc_IoMmu: 'TestBoxesWithStrings.fChipsetIoMmu IS TRUE', + kiTbMisc_NoIoMmu: 'TestBoxesWithStrings.fChipsetIoMmu IS FALSE', + }; + + def _getWhereWorker(self, iCrit, oCrit, sExtraIndent, iOmit): + """ Formats one - main or sub. """ + sQuery = ''; + if oCrit.sState == FilterCriterion.ksState_Selected and iCrit != iOmit: + if iCrit == self.kiTestCaseMisc: + for iValue, sLike in self.kaTcMisc: + if iValue in oCrit.aoSelected: sNot = ''; + elif iValue + 32 in oCrit.aoSelected: sNot = 'NOT '; + else: continue; + sQuery += '%s AND %s (' % (sExtraIndent, sNot,); + if len(sLike) <= 3: # do word matching for small substrings (hw, np, smp, uni, ++). + sQuery += 'TestCases.sName ~ \'.*\\y%s\\y.*\' ' \ + 'OR COALESCE(TestCaseArgs.sSubName, \'\') ~ \'.*\\y%s\\y.*\')\n' \ + % ( sLike, sLike,); + else: + sQuery += 'TestCases.sName LIKE \'%%%s%%\' ' \ + 'OR COALESCE(TestCaseArgs.sSubName, \'\') LIKE \'%%%s%%\')\n' \ + % ( sLike, sLike,); + elif iCrit == self.kiTestboxMisc: + dConditions = self.kdTbMiscConditions; + for iValue in oCrit.aoSelected: + if iValue in dConditions: + sQuery += '%s AND %s\n' % (sExtraIndent, dConditions[iValue],); + elif oCrit.sType == FilterCriterion.ksType_Ranges: + assert not oCrit.aoPossible; + if oCrit.aoSelected: + asConditions = []; + for tRange in oCrit.aoSelected: + if tRange[0] == tRange[1]: + asConditions.append('%s.%s = %s' % (oCrit.asTables[0], oCrit.sColumn, tRange[0])); + elif tRange[1] is None: # 9999- + asConditions.append('%s.%s >= %s' % (oCrit.asTables[0], oCrit.sColumn, tRange[0])); + elif tRange[0] is None: # -9999 + asConditions.append('%s.%s <= %s' % (oCrit.asTables[0], oCrit.sColumn, tRange[1])); + else: + asConditions.append('%s.%s BETWEEN %s AND %s' % (oCrit.asTables[0], oCrit.sColumn, + tRange[0], tRange[1])); + if not oCrit.fInverted: + sQuery += '%s AND (%s)\n' % (sExtraIndent, ' OR '.join(asConditions)); + else: + sQuery += '%s AND NOT (%s)\n' % (sExtraIndent, ' OR '.join(asConditions)); + else: + assert len(oCrit.asTables) == 1; + sQuery += '%s AND (' % (sExtraIndent,); + + if oCrit.sType != FilterCriterion.ksType_UIntNil or max(oCrit.aoSelected) != -1: + if iCrit == self.kiMemory: + sQuery += '(%s.%s / 1024)' % (oCrit.asTables[0], oCrit.sColumn,); + else: + sQuery += '%s.%s' % (oCrit.asTables[0], oCrit.sColumn,); + if not oCrit.fInverted: + sQuery += ' IN ('; + else: + sQuery += ' NOT IN ('; + if oCrit.sType == FilterCriterion.ksType_String: + sQuery += ', '.join('\'%s\'' % (sValue,) for sValue in oCrit.aoSelected) + ')'; + else: + sQuery += ', '.join(str(iValue) for iValue in oCrit.aoSelected if iValue != -1) + ')'; + + if oCrit.sType == FilterCriterion.ksType_UIntNil \ + and -1 in oCrit.aoSelected: + if sQuery[-1] != '(': sQuery += ' OR '; + sQuery += '%s.%s IS NULL' % (oCrit.asTables[0], oCrit.sColumn,); + + if iCrit == self.kiFailReasons: + if oCrit.fInverted: + sQuery += '%s OR TestResultFailures.idFailureReason IS NULL\n' % (sExtraIndent,); + else: + sQuery += '%s AND TestSets.enmStatus >= \'failure\'::TestStatus_T\n' % (sExtraIndent,); + sQuery += ')\n'; + if oCrit.oSub is not None: + sQuery += self._getWhereWorker(iCrit | (((iCrit >> 8) + 1) << 8), oCrit.oSub, sExtraIndent, iOmit); + return sQuery; + + def getWhereConditions(self, sExtraIndent = '', iOmit = -1): + """ + Construct the WHERE conditions for the filter, optionally omitting one + criterion. + """ + sQuery = ''; + for iCrit, oCrit in enumerate(self.aCriteria): + sQuery += self._getWhereWorker(iCrit, oCrit, sExtraIndent, iOmit); + return sQuery; + + def getTableJoins(self, sExtraIndent = '', iOmit = -1, dOmitTables = None): + """ + Construct the WHERE conditions for the filter, optionally omitting one + criterion. + """ + afDone = { 'TestSets': True, }; + if dOmitTables is not None: + afDone.update(dOmitTables); + + sQuery = ''; + for iCrit, oCrit in enumerate(self.aCriteria): + if oCrit.sState == FilterCriterion.ksState_Selected \ + and iCrit != iOmit: + for sTable in oCrit.asTables: + if sTable not in afDone: + afDone[sTable] = True; + if sTable == 'Builds': + sQuery += '%sINNER JOIN Builds\n' \ + '%s ON Builds.idBuild = TestSets.idBuild\n' \ + '%s AND Builds.tsExpire > TestSets.tsCreated\n' \ + '%s AND Builds.tsEffective <= TestSets.tsCreated\n' \ + % ( sExtraIndent, sExtraIndent, sExtraIndent, sExtraIndent, ); + elif sTable == 'BuildCategories': + sQuery += '%sINNER JOIN BuildCategories\n' \ + '%s ON BuildCategories.idBuildCategory = TestSets.idBuildCategory\n' \ + % ( sExtraIndent, sExtraIndent, ); + elif sTable == 'TestBoxesWithStrings': + sQuery += '%sLEFT OUTER JOIN TestBoxesWithStrings\n' \ + '%s ON TestBoxesWithStrings.idGenTestBox = TestSets.idGenTestBox\n' \ + % ( sExtraIndent, sExtraIndent, ); + elif sTable == 'TestCases': + sQuery += '%sINNER JOIN TestCases\n' \ + '%s ON TestCases.idGenTestCase = TestSets.idGenTestCase\n' \ + % ( sExtraIndent, sExtraIndent, ); + elif sTable == 'TestCaseArgs': + sQuery += '%sINNER JOIN TestCaseArgs\n' \ + '%s ON TestCaseArgs.idGenTestCaseArgs = TestSets.idGenTestCaseArgs\n' \ + % ( sExtraIndent, sExtraIndent, ); + elif sTable == 'TestResults': + sQuery += '%sINNER JOIN TestResults\n' \ + '%s ON TestResults.idTestResult = TestSets.idTestResult\n' \ + % ( sExtraIndent, sExtraIndent, ); + elif sTable == 'TestResultFailures': + sQuery += '%sLEFT OUTER JOIN TestResultFailures\n' \ + '%s ON TestResultFailures.idTestSet = TestSets.idTestSet\n' \ + '%s AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP\n' \ + % ( sExtraIndent, sExtraIndent, sExtraIndent, ); + else: + assert False, sTable; + return sQuery; + + def isJoiningWithTable(self, sTable): + """ Checks whether getTableJoins already joins with TestResultFailures. """ + for oCrit in self.aCriteria: + if oCrit.sState == FilterCriterion.ksState_Selected and sTable in oCrit.asTables: + return True; + return False + + + +class TestResultLogic(ModelLogicBase): # pylint: disable=too-few-public-methods + """ + Results grouped by scheduling group. + """ + + # + # Result grinding for displaying in the WUI. + # + + ksResultsGroupingTypeNone = 'ResultsGroupingTypeNone'; + ksResultsGroupingTypeTestGroup = 'ResultsGroupingTypeTestGroup'; + ksResultsGroupingTypeBuildCat = 'ResultsGroupingTypeBuildCat'; + ksResultsGroupingTypeBuildRev = 'ResultsGroupingTypeBuildRev'; + ksResultsGroupingTypeTestBox = 'ResultsGroupingTypeTestBox'; + ksResultsGroupingTypeTestCase = 'ResultsGroupingTypeTestCase'; + ksResultsGroupingTypeOS = 'ResultsGroupingTypeOS'; + ksResultsGroupingTypeArch = 'ResultsGroupingTypeArch'; + ksResultsGroupingTypeSchedGroup = 'ResultsGroupingTypeSchedGroup'; + + ## @name Result sorting options. + ## @{ + ksResultsSortByRunningAndStart = 'ResultsSortByRunningAndStart'; ##< Default + ksResultsSortByBuildRevision = 'ResultsSortByBuildRevision'; + ksResultsSortByTestBoxName = 'ResultsSortByTestBoxName'; + ksResultsSortByTestBoxOs = 'ResultsSortByTestBoxOs'; + ksResultsSortByTestBoxOsVersion = 'ResultsSortByTestBoxOsVersion'; + ksResultsSortByTestBoxOsArch = 'ResultsSortByTestBoxOsArch'; + ksResultsSortByTestBoxArch = 'ResultsSortByTestBoxArch'; + ksResultsSortByTestBoxCpuVendor = 'ResultsSortByTestBoxCpuVendor'; + ksResultsSortByTestBoxCpuName = 'ResultsSortByTestBoxCpuName'; + ksResultsSortByTestBoxCpuRev = 'ResultsSortByTestBoxCpuRev'; + ksResultsSortByTestBoxCpuFeatures = 'ResultsSortByTestBoxCpuFeatures'; + ksResultsSortByTestCaseName = 'ResultsSortByTestCaseName'; + ksResultsSortByFailureReason = 'ResultsSortByFailureReason'; + kasResultsSortBy = { + ksResultsSortByRunningAndStart, + ksResultsSortByBuildRevision, + ksResultsSortByTestBoxName, + ksResultsSortByTestBoxOs, + ksResultsSortByTestBoxOsVersion, + ksResultsSortByTestBoxOsArch, + ksResultsSortByTestBoxArch, + ksResultsSortByTestBoxCpuVendor, + ksResultsSortByTestBoxCpuName, + ksResultsSortByTestBoxCpuRev, + ksResultsSortByTestBoxCpuFeatures, + ksResultsSortByTestCaseName, + ksResultsSortByFailureReason, + }; + ## Used by the WUI for generating the drop down. + kaasResultsSortByTitles = ( + ( ksResultsSortByRunningAndStart, 'Running & Start TS' ), + ( ksResultsSortByBuildRevision, 'Build Revision' ), + ( ksResultsSortByTestBoxName, 'TestBox Name' ), + ( ksResultsSortByTestBoxOs, 'O/S' ), + ( ksResultsSortByTestBoxOsVersion, 'O/S Version' ), + ( ksResultsSortByTestBoxOsArch, 'O/S & Architecture' ), + ( ksResultsSortByTestBoxArch, 'Architecture' ), + ( ksResultsSortByTestBoxCpuVendor, 'CPU Vendor' ), + ( ksResultsSortByTestBoxCpuName, 'CPU Vendor & Name' ), + ( ksResultsSortByTestBoxCpuRev, 'CPU Vendor & Revision' ), + ( ksResultsSortByTestBoxCpuFeatures, 'CPU Features' ), + ( ksResultsSortByTestCaseName, 'Test Case Name' ), + ( ksResultsSortByFailureReason, 'Failure Reason' ), + ); + ## @} + + ## Default sort by map. + kdResultSortByMap = { + ksResultsSortByRunningAndStart: ( (), None, None, '', '' ), + ksResultsSortByBuildRevision: ( + # Sorting tables. + ('Builds',), + # Sorting table join(s). + ' AND TestSets.idBuild = Builds.idBuild' + ' AND Builds.tsExpire >= TestSets.tsCreated' + ' AND Builds.tsEffective <= TestSets.tsCreated', + # Start of ORDER BY statement. + ' Builds.iRevision DESC', + # Extra columns to fetch for the above ORDER BY to work in a SELECT DISTINCT statement. + '', + # Columns for the GROUP BY + ''), + ksResultsSortByTestBoxName: ( + ('TestBoxes',), + ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox', + ' TestBoxes.sName DESC', + '', '' ), + ksResultsSortByTestBoxOsArch: ( + ('TestBoxesWithStrings',), + ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox', + ' TestBoxesWithStrings.sOs, TestBoxesWithStrings.sCpuArch', + '', '' ), + ksResultsSortByTestBoxOs: ( + ('TestBoxesWithStrings',), + ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox', + ' TestBoxesWithStrings.sOs', + '', '' ), + ksResultsSortByTestBoxOsVersion: ( + ('TestBoxesWithStrings',), + ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox', + ' TestBoxesWithStrings.sOs, TestBoxesWithStrings.sOsVersion DESC', + '', '' ), + ksResultsSortByTestBoxArch: ( + ('TestBoxesWithStrings',), + ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox', + ' TestBoxesWithStrings.sCpuArch', + '', '' ), + ksResultsSortByTestBoxCpuVendor: ( + ('TestBoxesWithStrings',), + ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox', + ' TestBoxesWithStrings.sCpuVendor', + '', '' ), + ksResultsSortByTestBoxCpuName: ( + ('TestBoxesWithStrings',), + ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox', + ' TestBoxesWithStrings.sCpuVendor, TestBoxesWithStrings.sCpuName', + '', '' ), + ksResultsSortByTestBoxCpuRev: ( + ('TestBoxesWithStrings',), + ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox', + ' TestBoxesWithStrings.sCpuVendor, TestBoxesWithStrings.lCpuRevision DESC', + ', TestBoxesWithStrings.lCpuRevision', + ', TestBoxesWithStrings.lCpuRevision' ), + ksResultsSortByTestBoxCpuFeatures: ( + ('TestBoxes',), + ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox', + ' TestBoxes.fCpuHwVirt DESC, TestBoxes.fCpuNestedPaging DESC, TestBoxes.fCpu64BitGuest DESC, TestBoxes.cCpus DESC', + '', + '' ), + ksResultsSortByTestCaseName: ( + ('TestCases',), + ' AND TestSets.idGenTestCase = TestCases.idGenTestCase', + ' TestCases.sName', + '', '' ), + ksResultsSortByFailureReason: ( + (), '', + 'asSortByFailureReason ASC', + ', array_agg(FailureReasons.sShort ORDER BY TestResultFailures.idTestResult) AS asSortByFailureReason', + '' ), + }; + + kdResultGroupingMap = { + ksResultsGroupingTypeNone: ( + # Grouping tables; + (), + # Grouping field; + None, + # Grouping where addition. + None, + # Sort by overrides. + {}, + ), + ksResultsGroupingTypeTestGroup: ('', 'TestSets.idTestGroup', None, {},), + ksResultsGroupingTypeTestBox: ('', 'TestSets.idTestBox', None, {},), + ksResultsGroupingTypeTestCase: ('', 'TestSets.idTestCase', None, {},), + ksResultsGroupingTypeOS: ( + ('TestBoxes',), + 'TestBoxes.idStrOs', + ' AND TestBoxes.idGenTestBox = TestSets.idGenTestBox', + {}, + ), + ksResultsGroupingTypeArch: ( + ('TestBoxes',), + 'TestBoxes.idStrCpuArch', + ' AND TestBoxes.idGenTestBox = TestSets.idGenTestBox', + {}, + ), + ksResultsGroupingTypeBuildCat: ('', 'TestSets.idBuildCategory', None, {},), + ksResultsGroupingTypeBuildRev: ( + ('Builds',), + 'Builds.iRevision', + ' AND Builds.idBuild = TestSets.idBuild' + ' AND Builds.tsExpire > TestSets.tsCreated' + ' AND Builds.tsEffective <= TestSets.tsCreated', + { ksResultsSortByBuildRevision: ( (), None, ' Builds.iRevision DESC' ), } + ), + ksResultsGroupingTypeSchedGroup: ( '', 'TestSets.idSchedGroup', None, {},), + }; + + + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb) + self.oFailureReasonLogic = None; + self.oUserAccountLogic = None; + + def _getTimePeriodQueryPart(self, tsNow, sInterval, sExtraIndent = ''): + """ + Get part of SQL query responsible for SELECT data within + specified period of time. + """ + assert sInterval is not None; # too many rows. + + cMonthsMourningPeriod = 2; # Stop reminding everyone about testboxes after 2 months. (May also speed up the query.) + if tsNow is None: + sRet = '(TestSets.tsDone IS NULL OR TestSets.tsDone >= (CURRENT_TIMESTAMP - \'%s\'::interval))\n' \ + '%s AND TestSets.tsCreated >= (CURRENT_TIMESTAMP - \'%s\'::interval - \'%u months\'::interval)\n' \ + % ( sInterval, + sExtraIndent, sInterval, cMonthsMourningPeriod); + else: + sTsNow = '\'%s\'::TIMESTAMP' % (tsNow,); # It's actually a string already. duh. + sRet = 'TestSets.tsCreated <= %s\n' \ + '%s AND TestSets.tsCreated >= (%s - \'%s\'::interval - \'%u months\'::interval)\n' \ + '%s AND (TestSets.tsDone IS NULL OR TestSets.tsDone >= (%s - \'%s\'::interval))\n' \ + % ( sTsNow, + sExtraIndent, sTsNow, sInterval, cMonthsMourningPeriod, + sExtraIndent, sTsNow, sInterval ); + return sRet + + def fetchResultsForListing(self, iStart, cMaxRows, tsNow, sInterval, oFilter, enmResultSortBy, # pylint: disable=too-many-arguments + enmResultsGroupingType, iResultsGroupingValue, fOnlyFailures, fOnlyNeedingReason): + """ + Fetches TestResults table content. + + If @param enmResultsGroupingType and @param iResultsGroupingValue + are not None, then resulting (returned) list contains only records + that match specified @param enmResultsGroupingType. + + If @param enmResultsGroupingType is None, then + @param iResultsGroupingValue is ignored. + + Returns an array (list) of TestResultData items, empty list if none. + Raises exception on error. + """ + + _ = oFilter; + + # + # Get SQL query parameters + # + if enmResultsGroupingType is None or enmResultsGroupingType not in self.kdResultGroupingMap: + raise TMExceptionBase('Unknown grouping type'); + if enmResultSortBy is None or enmResultSortBy not in self.kasResultsSortBy: + raise TMExceptionBase('Unknown sorting'); + asGroupingTables, sGroupingField, sGroupingCondition, dSortOverrides = self.kdResultGroupingMap[enmResultsGroupingType]; + if enmResultSortBy in dSortOverrides: + asSortTables, sSortWhere, sSortOrderBy, sSortColumns, sSortGroupBy = dSortOverrides[enmResultSortBy]; + else: + asSortTables, sSortWhere, sSortOrderBy, sSortColumns, sSortGroupBy = self.kdResultSortByMap[enmResultSortBy]; + + # + # Construct the query. + # + sQuery = 'SELECT DISTINCT TestSets.idTestSet,\n' \ + ' BuildCategories.idBuildCategory,\n' \ + ' BuildCategories.sProduct,\n' \ + ' BuildCategories.sRepository,\n' \ + ' BuildCategories.sBranch,\n' \ + ' BuildCategories.sType,\n' \ + ' Builds.idBuild,\n' \ + ' Builds.sVersion,\n' \ + ' Builds.iRevision,\n' \ + ' TestBoxesWithStrings.sOs,\n' \ + ' TestBoxesWithStrings.sOsVersion,\n' \ + ' TestBoxesWithStrings.sCpuArch,\n' \ + ' TestBoxesWithStrings.sCpuVendor,\n' \ + ' TestBoxesWithStrings.sCpuName,\n' \ + ' TestBoxesWithStrings.cCpus,\n' \ + ' TestBoxesWithStrings.fCpuHwVirt,\n' \ + ' TestBoxesWithStrings.fCpuNestedPaging,\n' \ + ' TestBoxesWithStrings.fCpu64BitGuest,\n' \ + ' TestBoxesWithStrings.idTestBox,\n' \ + ' TestBoxesWithStrings.sName,\n' \ + ' TestResults.tsCreated,\n' \ + ' COALESCE(TestResults.tsElapsed, CURRENT_TIMESTAMP - TestResults.tsCreated) AS tsElapsedTestResult,\n' \ + ' TestSets.enmStatus,\n' \ + ' TestResults.cErrors,\n' \ + ' TestCases.idTestCase,\n' \ + ' TestCases.sName,\n' \ + ' TestCases.sBaseCmd,\n' \ + ' TestCaseArgs.sArgs,\n' \ + ' TestCaseArgs.sSubName,\n' \ + ' TestSuiteBits.idBuild AS idBuildTestSuite,\n' \ + ' TestSuiteBits.iRevision AS iRevisionTestSuite,\n' \ + ' array_agg(TestResultFailures.idFailureReason ORDER BY TestResultFailures.idTestResult),\n' \ + ' array_agg(TestResultFailures.uidAuthor ORDER BY TestResultFailures.idTestResult),\n' \ + ' array_agg(TestResultFailures.tsEffective ORDER BY TestResultFailures.idTestResult),\n' \ + ' array_agg(TestResultFailures.sComment ORDER BY TestResultFailures.idTestResult),\n' \ + ' (TestSets.tsDone IS NULL) SortRunningFirst' + sSortColumns + '\n' \ + 'FROM ( SELECT TestSets.idTestSet AS idTestSet,\n' \ + ' TestSets.tsDone AS tsDone,\n' \ + ' TestSets.tsCreated AS tsCreated,\n' \ + ' TestSets.enmStatus AS enmStatus,\n' \ + ' TestSets.idBuild AS idBuild,\n' \ + ' TestSets.idBuildTestSuite AS idBuildTestSuite,\n' \ + ' TestSets.idGenTestBox AS idGenTestBox,\n' \ + ' TestSets.idGenTestCase AS idGenTestCase,\n' \ + ' TestSets.idGenTestCaseArgs AS idGenTestCaseArgs\n' \ + ' FROM TestSets\n'; + sQuery += oFilter.getTableJoins(' '); + if fOnlyNeedingReason and not oFilter.isJoiningWithTable('TestResultFailures'): + sQuery += '\n' \ + ' LEFT OUTER JOIN TestResultFailures\n' \ + ' ON TestSets.idTestSet = TestResultFailures.idTestSet\n' \ + ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP'; + for asTables in [asGroupingTables, asSortTables]: + for sTable in asTables: + if not oFilter.isJoiningWithTable(sTable): + sQuery = sQuery[:-1] + ',\n ' + sTable + '\n'; + + sQuery += ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sInterval, ' ') + \ + oFilter.getWhereConditions(' '); + if fOnlyFailures or fOnlyNeedingReason: + sQuery += ' AND TestSets.enmStatus != \'success\'::TestStatus_T\n' \ + ' AND TestSets.enmStatus != \'running\'::TestStatus_T\n'; + if fOnlyNeedingReason: + sQuery += ' AND TestResultFailures.idTestSet IS NULL\n'; + if sGroupingField is not None: + sQuery += ' AND %s = %d\n' % (sGroupingField, iResultsGroupingValue,); + if sGroupingCondition is not None: + sQuery += sGroupingCondition.replace(' AND ', ' AND '); + if sSortWhere is not None: + sQuery += sSortWhere.replace(' AND ', ' AND '); + sQuery += ' ORDER BY '; + if sSortOrderBy is not None and sSortOrderBy.find('FailureReason') < 0: + sQuery += sSortOrderBy + ',\n '; + sQuery += '(TestSets.tsDone IS NULL) DESC, TestSets.idTestSet DESC\n' \ + ' LIMIT %s OFFSET %s\n' % (cMaxRows, iStart,); + + # Note! INNER JOIN TestBoxesWithStrings performs miserable compared to LEFT OUTER JOIN. Doesn't matter for the result + # because TestSets.idGenTestBox is a foreign key and unique in TestBoxes. So, let's do what ever is faster. + sQuery += ' ) AS TestSets\n' \ + ' LEFT OUTER JOIN TestBoxesWithStrings\n' \ + ' ON TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox' \ + ' LEFT OUTER JOIN Builds AS TestSuiteBits\n' \ + ' ON TestSuiteBits.idBuild = TestSets.idBuildTestSuite\n' \ + ' AND TestSuiteBits.tsExpire > TestSets.tsCreated\n' \ + ' AND TestSuiteBits.tsEffective <= TestSets.tsCreated\n' \ + ' LEFT OUTER JOIN TestResultFailures\n' \ + ' ON TestSets.idTestSet = TestResultFailures.idTestSet\n' \ + ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP'; + if sSortOrderBy is not None and sSortOrderBy.find('FailureReason') >= 0: + sQuery += '\n' \ + ' LEFT OUTER JOIN FailureReasons\n' \ + ' ON TestResultFailures.idFailureReason = FailureReasons.idFailureReason\n' \ + ' AND FailureReasons.tsExpire = \'infinity\'::TIMESTAMP'; + sQuery += ',\n' \ + ' BuildCategories,\n' \ + ' Builds,\n' \ + ' TestResults,\n' \ + ' TestCases,\n' \ + ' TestCaseArgs\n'; + sQuery += 'WHERE TestSets.idTestSet = TestResults.idTestSet\n' \ + ' AND TestResults.idTestResultParent is NULL\n' \ + ' AND TestSets.idBuild = Builds.idBuild\n' \ + ' AND Builds.tsExpire > TestSets.tsCreated\n' \ + ' AND Builds.tsEffective <= TestSets.tsCreated\n' \ + ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n' \ + ' AND TestSets.idGenTestCase = TestCases.idGenTestCase\n' \ + ' AND TestSets.idGenTestCaseArgs = TestCaseArgs.idGenTestCaseArgs\n'; + sQuery += 'GROUP BY TestSets.idTestSet,\n' \ + ' BuildCategories.idBuildCategory,\n' \ + ' BuildCategories.sProduct,\n' \ + ' BuildCategories.sRepository,\n' \ + ' BuildCategories.sBranch,\n' \ + ' BuildCategories.sType,\n' \ + ' Builds.idBuild,\n' \ + ' Builds.sVersion,\n' \ + ' Builds.iRevision,\n' \ + ' TestBoxesWithStrings.sOs,\n' \ + ' TestBoxesWithStrings.sOsVersion,\n' \ + ' TestBoxesWithStrings.sCpuArch,\n' \ + ' TestBoxesWithStrings.sCpuVendor,\n' \ + ' TestBoxesWithStrings.sCpuName,\n' \ + ' TestBoxesWithStrings.cCpus,\n' \ + ' TestBoxesWithStrings.fCpuHwVirt,\n' \ + ' TestBoxesWithStrings.fCpuNestedPaging,\n' \ + ' TestBoxesWithStrings.fCpu64BitGuest,\n' \ + ' TestBoxesWithStrings.idTestBox,\n' \ + ' TestBoxesWithStrings.sName,\n' \ + ' TestResults.tsCreated,\n' \ + ' tsElapsedTestResult,\n' \ + ' TestSets.enmStatus,\n' \ + ' TestResults.cErrors,\n' \ + ' TestCases.idTestCase,\n' \ + ' TestCases.sName,\n' \ + ' TestCases.sBaseCmd,\n' \ + ' TestCaseArgs.sArgs,\n' \ + ' TestCaseArgs.sSubName,\n' \ + ' TestSuiteBits.idBuild,\n' \ + ' TestSuiteBits.iRevision,\n' \ + ' SortRunningFirst' + sSortGroupBy + '\n'; + sQuery += 'ORDER BY '; + if sSortOrderBy is not None: + sQuery += sSortOrderBy.replace('TestBoxes.', 'TestBoxesWithStrings.') + ',\n '; + sQuery += '(TestSets.tsDone IS NULL) DESC, TestSets.idTestSet DESC\n'; + + # + # Execute the query and return the wrapped results. + # + self._oDb.execute(sQuery); + + if self.oFailureReasonLogic is None: + self.oFailureReasonLogic = FailureReasonLogic(self._oDb); + if self.oUserAccountLogic is None: + self.oUserAccountLogic = UserAccountLogic(self._oDb); + + aoRows = []; + for aoRow in self._oDb.fetchAll(): + aoRows.append(TestResultListingData().initFromDbRowEx(aoRow, self.oFailureReasonLogic, self.oUserAccountLogic)); + + return aoRows + + + def fetchTimestampsForLogViewer(self, idTestSet): + """ + Returns an ordered list with all the test result timestamps, both start + and end. + + The log viewer create anchors in the log text so we can jump directly to + the log lines relevant for a test event. + """ + self._oDb.execute('(\n' + 'SELECT tsCreated\n' + 'FROM TestResults\n' + 'WHERE idTestSet = %s\n' + ') UNION (\n' + 'SELECT tsCreated + tsElapsed\n' + 'FROM TestResults\n' + 'WHERE idTestSet = %s\n' + ' AND tsElapsed IS NOT NULL\n' + ') UNION (\n' + 'SELECT TestResultFiles.tsCreated\n' + 'FROM TestResultFiles\n' + 'WHERE idTestSet = %s\n' + ') UNION (\n' + 'SELECT tsCreated\n' + 'FROM TestResultValues\n' + 'WHERE idTestSet = %s\n' + ') UNION (\n' + 'SELECT TestResultMsgs.tsCreated\n' + 'FROM TestResultMsgs\n' + 'WHERE idTestSet = %s\n' + ') ORDER by 1' + , ( idTestSet, idTestSet, idTestSet, idTestSet, idTestSet, )); + return [aoRow[0] for aoRow in self._oDb.fetchAll()]; + + + def getEntriesCount(self, tsNow, sInterval, oFilter, enmResultsGroupingType, iResultsGroupingValue, + fOnlyFailures, fOnlyNeedingReason): + """ + Get number of table records. + + If @param enmResultsGroupingType and @param iResultsGroupingValue + are not None, then we count only only those records + that match specified @param enmResultsGroupingType. + + If @param enmResultsGroupingType is None, then + @param iResultsGroupingValue is ignored. + """ + _ = oFilter; + + # + # Get SQL query parameters + # + if enmResultsGroupingType is None: + raise TMExceptionBase('Unknown grouping type') + + if enmResultsGroupingType not in self.kdResultGroupingMap: + raise TMExceptionBase('Unknown grouping type') + asGroupingTables, sGroupingField, sGroupingCondition, _ = self.kdResultGroupingMap[enmResultsGroupingType]; + + # + # Construct the query. + # + sQuery = 'SELECT COUNT(TestSets.idTestSet)\n' \ + 'FROM TestSets\n'; + sQuery += oFilter.getTableJoins(); + if fOnlyNeedingReason and not oFilter.isJoiningWithTable('TestResultFailures'): + sQuery += ' LEFT OUTER JOIN TestResultFailures\n' \ + ' ON TestSets.idTestSet = TestResultFailures.idTestSet\n' \ + ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP\n'; + for sTable in asGroupingTables: + if not oFilter.isJoiningWithTable(sTable): + sQuery = sQuery[:-1] + ',\n ' + sTable + '\n'; + sQuery += 'WHERE ' + self._getTimePeriodQueryPart(tsNow, sInterval) + \ + oFilter.getWhereConditions(); + if fOnlyFailures or fOnlyNeedingReason: + sQuery += ' AND TestSets.enmStatus != \'success\'::TestStatus_T\n' \ + ' AND TestSets.enmStatus != \'running\'::TestStatus_T\n'; + if fOnlyNeedingReason: + sQuery += ' AND TestResultFailures.idTestSet IS NULL\n'; + if sGroupingField is not None: + sQuery += ' AND %s = %d\n' % (sGroupingField, iResultsGroupingValue,); + if sGroupingCondition is not None: + sQuery += sGroupingCondition.replace(' AND ', ' AND '); + + # + # Execute the query and return the result. + # + self._oDb.execute(sQuery) + return self._oDb.fetchOne()[0] + + def getTestGroups(self, tsNow, sPeriod): + """ + Get list of uniq TestGroupData objects which + found in all test results. + """ + + self._oDb.execute('SELECT DISTINCT TestGroups.*\n' + 'FROM TestGroups, TestSets\n' + 'WHERE TestSets.idTestGroup = TestGroups.idTestGroup\n' + ' AND TestGroups.tsExpire > TestSets.tsCreated\n' + ' AND TestGroups.tsEffective <= TestSets.tsCreated' + ' AND ' + self._getTimePeriodQueryPart(tsNow, sPeriod)) + aaoRows = self._oDb.fetchAll() + aoRet = [] + for aoRow in aaoRows: + aoRet.append(TestGroupData().initFromDbRow(aoRow)) + return aoRet + + def getBuilds(self, tsNow, sPeriod): + """ + Get list of uniq BuildDataEx objects which + found in all test results. + """ + + self._oDb.execute('SELECT DISTINCT Builds.*, BuildCategories.*\n' + 'FROM Builds, BuildCategories, TestSets\n' + 'WHERE TestSets.idBuild = Builds.idBuild\n' + ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n' + ' AND Builds.tsExpire > TestSets.tsCreated\n' + ' AND Builds.tsEffective <= TestSets.tsCreated' + ' AND ' + self._getTimePeriodQueryPart(tsNow, sPeriod)) + aaoRows = self._oDb.fetchAll() + aoRet = [] + for aoRow in aaoRows: + aoRet.append(BuildDataEx().initFromDbRow(aoRow)) + return aoRet + + def getTestBoxes(self, tsNow, sPeriod): + """ + Get list of uniq TestBoxData objects which + found in all test results. + """ + # Note! INNER JOIN TestBoxesWithStrings performs miserable compared to LEFT OUTER JOIN. Doesn't matter for the result + # because TestSets.idGenTestBox is a foreign key and unique in TestBoxes. So, let's do what ever is faster. + self._oDb.execute('SELECT TestBoxesWithStrings.*\n' + 'FROM ( SELECT idTestBox AS idTestBox,\n' + ' MAX(idGenTestBox) AS idGenTestBox\n' + ' FROM TestSets\n' + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + ' GROUP BY idTestBox\n' + ' ) AS TestBoxIDs\n' + ' LEFT OUTER JOIN TestBoxesWithStrings\n' + ' ON TestBoxesWithStrings.idGenTestBox = TestBoxIDs.idGenTestBox\n' + 'ORDER BY TestBoxesWithStrings.sName\n' ); + aoRet = [] + for aoRow in self._oDb.fetchAll(): + aoRet.append(TestBoxData().initFromDbRow(aoRow)); + return aoRet + + def getTestCases(self, tsNow, sPeriod): + """ + Get a list of unique TestCaseData objects which is appears in the test + specified result period. + """ + + # Using LEFT OUTER JOIN instead of INNER JOIN in case it performs better, doesn't matter for the result. + self._oDb.execute('SELECT TestCases.*\n' + 'FROM ( SELECT idTestCase AS idTestCase,\n' + ' MAX(idGenTestCase) AS idGenTestCase\n' + ' FROM TestSets\n' + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + ' GROUP BY idTestCase\n' + ' ) AS TestCasesIDs\n' + ' LEFT OUTER JOIN TestCases ON TestCases.idGenTestCase = TestCasesIDs.idGenTestCase\n' + 'ORDER BY TestCases.sName\n' ); + + aoRet = []; + for aoRow in self._oDb.fetchAll(): + aoRet.append(TestCaseData().initFromDbRow(aoRow)); + return aoRet + + def getOSes(self, tsNow, sPeriod): + """ + Get a list of [idStrOs, sOs] tuples of the OSes that appears in the specified result period. + """ + + # Note! INNER JOIN TestBoxesWithStrings performs miserable compared to LEFT OUTER JOIN. Doesn't matter for the result + # because TestSets.idGenTestBox is a foreign key and unique in TestBoxes. So, let's do what ever is faster. + self._oDb.execute('SELECT DISTINCT TestBoxesWithStrings.idStrOs, TestBoxesWithStrings.sOs\n' + 'FROM ( SELECT idTestBox AS idTestBox,\n' + ' MAX(idGenTestBox) AS idGenTestBox\n' + ' FROM TestSets\n' + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + ' GROUP BY idTestBox\n' + ' ) AS TestBoxIDs\n' + ' LEFT OUTER JOIN TestBoxesWithStrings\n' + ' ON TestBoxesWithStrings.idGenTestBox = TestBoxIDs.idGenTestBox\n' + 'ORDER BY TestBoxesWithStrings.sOs\n' ); + return self._oDb.fetchAll(); + + def getArchitectures(self, tsNow, sPeriod): + """ + Get a list of [idStrCpuArch, sCpuArch] tuples of the architecutres + that appears in the specified result period. + """ + + # Note! INNER JOIN TestBoxesWithStrings performs miserable compared to LEFT OUTER JOIN. Doesn't matter for the result + # because TestSets.idGenTestBox is a foreign key and unique in TestBoxes. So, let's do what ever is faster. + self._oDb.execute('SELECT DISTINCT TestBoxesWithStrings.idStrCpuArch, TestBoxesWithStrings.sCpuArch\n' + 'FROM ( SELECT idTestBox AS idTestBox,\n' + ' MAX(idGenTestBox) AS idGenTestBox\n' + ' FROM TestSets\n' + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + ' GROUP BY idTestBox\n' + ' ) AS TestBoxIDs\n' + ' LEFT OUTER JOIN TestBoxesWithStrings\n' + ' ON TestBoxesWithStrings.idGenTestBox = TestBoxIDs.idGenTestBox\n' + 'ORDER BY TestBoxesWithStrings.sCpuArch\n' ); + return self._oDb.fetchAll(); + + def getBuildCategories(self, tsNow, sPeriod): + """ + Get a list of BuildCategoryData that appears in the specified result period. + """ + + self._oDb.execute('SELECT DISTINCT BuildCategories.*\n' + 'FROM ( SELECT DISTINCT idBuildCategory AS idBuildCategory\n' + ' FROM TestSets\n' + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + ' ) AS BuildCategoryIDs\n' + ' LEFT OUTER JOIN BuildCategories\n' + ' ON BuildCategories.idBuildCategory = BuildCategoryIDs.idBuildCategory\n' + 'ORDER BY BuildCategories.sProduct, BuildCategories.sBranch, BuildCategories.sType\n'); + aoRet = []; + for aoRow in self._oDb.fetchAll(): + aoRet.append(BuildCategoryData().initFromDbRow(aoRow)); + return aoRet; + + def getSchedGroups(self, tsNow, sPeriod): + """ + Get list of uniq SchedGroupData objects which + found in all test results. + """ + + self._oDb.execute('SELECT SchedGroups.*\n' + 'FROM ( SELECT idSchedGroup,\n' + ' MAX(TestSets.tsCreated) AS tsNow\n' + ' FROM TestSets\n' + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + ' GROUP BY idSchedGroup\n' + ' ) AS SchedGroupIDs\n' + ' INNER JOIN SchedGroups\n' + ' ON SchedGroups.idSchedGroup = SchedGroupIDs.idSchedGroup\n' + ' AND SchedGroups.tsExpire > SchedGroupIDs.tsNow\n' + ' AND SchedGroups.tsEffective <= SchedGroupIDs.tsNow\n' + 'ORDER BY SchedGroups.sName\n' ); + aoRet = [] + for aoRow in self._oDb.fetchAll(): + aoRet.append(SchedGroupData().initFromDbRow(aoRow)); + return aoRet + + def getById(self, idTestResult): + """ + Get build record by its id + """ + self._oDb.execute('SELECT *\n' + 'FROM TestResults\n' + 'WHERE idTestResult = %s\n', + (idTestResult,)) + + aRows = self._oDb.fetchAll() + if len(aRows) not in (0, 1): + raise TMTooManyRows('Found more than one test result with the same credentials. Database structure is corrupted.') + try: + return TestResultData().initFromDbRow(aRows[0]) + except IndexError: + return None + + def fetchPossibleFilterOptions(self, oFilter, tsNow, sPeriod, oReportModel = None): + """ + Fetches the available filter criteria, given the current filtering. + + Returns oFilter. + """ + assert isinstance(oFilter, TestResultFilter); + + # Hack to avoid lot's of conditionals or duplicate this code. + if oReportModel is None: + class DummyReportModel(object): + """ Dummy """ + def getExtraSubjectTables(self): + """ Dummy """ + return []; + def getExtraSubjectWhereExpr(self): + """ Dummy """ + return ''; + oReportModel = DummyReportModel(); + + def workerDoFetch(oMissingLogicType, sNameAttr = 'sName', fIdIsName = False, idxHover = -1, + idNull = -1, sNullDesc = '<NULL>'): + """ Does the tedious result fetching and handling of missing bits. """ + dLeft = { oValue: 1 for oValue in oCrit.aoSelected }; + oCrit.aoPossible = []; + for aoRow in self._oDb.fetchAll(): + oCrit.aoPossible.append(FilterCriterionValueAndDescription(aoRow[0] if aoRow[0] is not None else idNull, + aoRow[1] if aoRow[1] is not None else sNullDesc, + aoRow[2], + aoRow[idxHover] if idxHover >= 0 else None)); + if aoRow[0] in dLeft: + del dLeft[aoRow[0]]; + if dLeft: + if fIdIsName: + for idMissing in dLeft: + oCrit.aoPossible.append(FilterCriterionValueAndDescription(idMissing, str(idMissing), + fIrrelevant = True)); + else: + oMissingLogic = oMissingLogicType(self._oDb); + for idMissing in dLeft: + oMissing = oMissingLogic.cachedLookup(idMissing); + if oMissing is not None: + oCrit.aoPossible.append(FilterCriterionValueAndDescription(idMissing, + getattr(oMissing, sNameAttr), + fIrrelevant = True)); + + def workerDoFetchNested(): + """ Does the tedious result fetching and handling of missing bits. """ + oCrit.aoPossible = []; + oCrit.oSub.aoPossible = []; + dLeft = { oValue: 1 for oValue in oCrit.aoSelected }; + dSubLeft = { oValue: 1 for oValue in oCrit.oSub.aoSelected }; + oMain = None; + for aoRow in self._oDb.fetchAll(): + if oMain is None or oMain.oValue != aoRow[0]: + oMain = FilterCriterionValueAndDescription(aoRow[0], aoRow[1], 0); + oCrit.aoPossible.append(oMain); + if aoRow[0] in dLeft: + del dLeft[aoRow[0]]; + oCurSub = FilterCriterionValueAndDescription(aoRow[2], aoRow[3], aoRow[4]); + oCrit.oSub.aoPossible.append(oCurSub); + if aoRow[2] in dSubLeft: + del dSubLeft[aoRow[2]]; + + oMain.aoSubs.append(oCurSub); + oMain.cTimes += aoRow[4]; + + if dLeft: + pass; ## @todo + + # Statuses. + oCrit = oFilter.aCriteria[TestResultFilter.kiTestStatus]; + self._oDb.execute('SELECT TestSets.enmStatus, TestSets.enmStatus, COUNT(TestSets.idTestSet)\n' + 'FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiTestStatus) + + ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) + + 'WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod) + + oFilter.getWhereConditions(iOmit = TestResultFilter.kiTestStatus) + + oReportModel.getExtraSubjectWhereExpr() + + 'GROUP BY TestSets.enmStatus\n' + 'ORDER BY TestSets.enmStatus\n'); + workerDoFetch(None, fIdIsName = True); + + # Scheduling groups (see getSchedGroups). + oCrit = oFilter.aCriteria[TestResultFilter.kiSchedGroups]; + self._oDb.execute('SELECT SchedGroups.idSchedGroup, SchedGroups.sName, SchedGroupIDs.cTimes\n' + 'FROM ( SELECT TestSets.idSchedGroup,\n' + ' MAX(TestSets.tsCreated) AS tsNow,\n' + ' COUNT(TestSets.idTestSet) AS cTimes\n' + ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiSchedGroups) + + ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) + + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + oFilter.getWhereConditions(iOmit = TestResultFilter.kiSchedGroups) + + oReportModel.getExtraSubjectWhereExpr() + + ' GROUP BY TestSets.idSchedGroup\n' + ' ) AS SchedGroupIDs\n' + ' INNER JOIN SchedGroups\n' + ' ON SchedGroups.idSchedGroup = SchedGroupIDs.idSchedGroup\n' + ' AND SchedGroups.tsExpire > SchedGroupIDs.tsNow\n' + ' AND SchedGroups.tsEffective <= SchedGroupIDs.tsNow\n' + 'ORDER BY SchedGroups.sName\n' ); + workerDoFetch(SchedGroupLogic); + + # Testboxes (see getTestBoxes). + oCrit = oFilter.aCriteria[TestResultFilter.kiTestBoxes]; + self._oDb.execute('SELECT TestBoxesWithStrings.idTestBox,\n' + ' TestBoxesWithStrings.sName,\n' + ' TestBoxIDs.cTimes\n' + 'FROM ( SELECT TestSets.idTestBox AS idTestBox,\n' + ' MAX(TestSets.idGenTestBox) AS idGenTestBox,\n' + ' COUNT(TestSets.idTestSet) AS cTimes\n' + ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiTestBoxes) + + ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) + + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + oFilter.getWhereConditions(iOmit = TestResultFilter.kiTestBoxes) + + oReportModel.getExtraSubjectWhereExpr() + + ' GROUP BY TestSets.idTestBox\n' + ' ) AS TestBoxIDs\n' + ' LEFT OUTER JOIN TestBoxesWithStrings\n' + ' ON TestBoxesWithStrings.idGenTestBox = TestBoxIDs.idGenTestBox\n' + 'ORDER BY TestBoxesWithStrings.sName\n' ); + workerDoFetch(TestBoxLogic); + + # Testbox OSes and versions. + oCrit = oFilter.aCriteria[TestResultFilter.kiOses]; + self._oDb.execute('SELECT TestBoxesWithStrings.idStrOs,\n' + ' TestBoxesWithStrings.sOs,\n' + ' TestBoxesWithStrings.idStrOsVersion,\n' + ' TestBoxesWithStrings.sOsVersion,\n' + ' SUM(TestBoxGenIDs.cTimes)\n' + 'FROM ( SELECT TestSets.idGenTestBox,\n' + ' COUNT(TestSets.idTestSet) AS cTimes\n' + ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiOses) + + ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) + + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + oFilter.getWhereConditions(iOmit = TestResultFilter.kiOses) + + oReportModel.getExtraSubjectWhereExpr() + + ' GROUP BY TestSets.idGenTestBox\n' + ' ) AS TestBoxGenIDs\n' + ' LEFT OUTER JOIN TestBoxesWithStrings\n' + ' ON TestBoxesWithStrings.idGenTestBox = TestBoxGenIDs.idGenTestBox\n' + 'GROUP BY TestBoxesWithStrings.idStrOs,\n' + ' TestBoxesWithStrings.sOs,\n' + ' TestBoxesWithStrings.idStrOsVersion,\n' + ' TestBoxesWithStrings.sOsVersion\n' + 'ORDER BY TestBoxesWithStrings.sOs,\n' + ' TestBoxesWithStrings.sOs = \'win\' AND TestBoxesWithStrings.sOsVersion = \'10\' DESC,\n' + ' TestBoxesWithStrings.sOsVersion DESC\n' + ); + workerDoFetchNested(); + + # Testbox CPU(/OS) architectures. + oCrit = oFilter.aCriteria[TestResultFilter.kiCpuArches]; + self._oDb.execute('SELECT TestBoxesWithStrings.idStrCpuArch,\n' + ' TestBoxesWithStrings.sCpuArch,\n' + ' SUM(TestBoxGenIDs.cTimes)\n' + 'FROM ( SELECT TestSets.idGenTestBox,\n' + ' COUNT(TestSets.idTestSet) AS cTimes\n' + ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiCpuArches) + + ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) + + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + oFilter.getWhereConditions(iOmit = TestResultFilter.kiCpuArches) + + oReportModel.getExtraSubjectWhereExpr() + + ' GROUP BY TestSets.idGenTestBox\n' + ' ) AS TestBoxGenIDs\n' + ' LEFT OUTER JOIN TestBoxesWithStrings\n' + ' ON TestBoxesWithStrings.idGenTestBox = TestBoxGenIDs.idGenTestBox\n' + 'GROUP BY TestBoxesWithStrings.idStrCpuArch, TestBoxesWithStrings.sCpuArch\n' + 'ORDER BY TestBoxesWithStrings.sCpuArch\n' ); + workerDoFetch(None, fIdIsName = True); + + # Testbox CPU revisions. + oCrit = oFilter.aCriteria[TestResultFilter.kiCpuVendors]; + self._oDb.execute('SELECT TestBoxesWithStrings.idStrCpuVendor,\n' + ' TestBoxesWithStrings.sCpuVendor,\n' + ' TestBoxesWithStrings.lCpuRevision,\n' + ' TestBoxesWithStrings.sCpuVendor,\n' + ' SUM(TestBoxGenIDs.cTimes)\n' + 'FROM ( SELECT TestSets.idGenTestBox,\n' + ' COUNT(TestSets.idTestSet) AS cTimes\n' + ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiCpuVendors) + + ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) + + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + oFilter.getWhereConditions(iOmit = TestResultFilter.kiCpuVendors) + + oReportModel.getExtraSubjectWhereExpr() + + ' GROUP BY TestSets.idGenTestBox' + ' ) AS TestBoxGenIDs\n' + ' LEFT OUTER JOIN TestBoxesWithStrings\n' + ' ON TestBoxesWithStrings.idGenTestBox = TestBoxGenIDs.idGenTestBox\n' + 'GROUP BY TestBoxesWithStrings.idStrCpuVendor,\n' + ' TestBoxesWithStrings.sCpuVendor,\n' + ' TestBoxesWithStrings.lCpuRevision,\n' + ' TestBoxesWithStrings.sCpuVendor\n' + 'ORDER BY TestBoxesWithStrings.sCpuVendor DESC,\n' + ' TestBoxesWithStrings.sCpuVendor = \'GenuineIntel\'\n' + ' AND (TestBoxesWithStrings.lCpuRevision >> 24) = 15,\n' # P4 at the bottom is a start... + ' TestBoxesWithStrings.lCpuRevision DESC\n' + ); + workerDoFetchNested(); + for oCur in oCrit.oSub.aoPossible: + oCur.sDesc = TestBoxData.getPrettyCpuVersionEx(oCur.oValue, oCur.sDesc).replace('_', ' '); + + # Testbox CPU core/thread counts. + oCrit = oFilter.aCriteria[TestResultFilter.kiCpuCounts]; + self._oDb.execute('SELECT TestBoxesWithStrings.cCpus,\n' + ' CAST(TestBoxesWithStrings.cCpus AS TEXT),\n' + ' SUM(TestBoxGenIDs.cTimes)\n' + 'FROM ( SELECT TestSets.idGenTestBox,\n' + ' COUNT(TestSets.idTestSet) AS cTimes\n' + ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiCpuCounts) + + ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) + + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + oFilter.getWhereConditions(iOmit = TestResultFilter.kiCpuCounts) + + oReportModel.getExtraSubjectWhereExpr() + + ' GROUP BY TestSets.idGenTestBox' + ' ) AS TestBoxGenIDs\n' + ' LEFT OUTER JOIN TestBoxesWithStrings\n' + ' ON TestBoxesWithStrings.idGenTestBox = TestBoxGenIDs.idGenTestBox\n' + 'GROUP BY TestBoxesWithStrings.cCpus\n' + 'ORDER BY TestBoxesWithStrings.cCpus\n' ); + workerDoFetch(None, fIdIsName = True); + + # Testbox memory. + oCrit = oFilter.aCriteria[TestResultFilter.kiMemory]; + self._oDb.execute('SELECT TestBoxesWithStrings.cMbMemory / 1024,\n' + ' NULL,\n' + ' SUM(TestBoxGenIDs.cTimes)\n' + 'FROM ( SELECT TestSets.idGenTestBox,\n' + ' COUNT(TestSets.idTestSet) AS cTimes\n' + ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiMemory) + + ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) + + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + oFilter.getWhereConditions(iOmit = TestResultFilter.kiMemory) + + oReportModel.getExtraSubjectWhereExpr() + + ' GROUP BY TestSets.idGenTestBox' + ' ) AS TestBoxGenIDs\n' + ' LEFT OUTER JOIN TestBoxesWithStrings\n' + ' ON TestBoxesWithStrings.idGenTestBox = TestBoxGenIDs.idGenTestBox\n' + 'GROUP BY TestBoxesWithStrings.cMbMemory / 1024\n' + 'ORDER BY 1\n' ); + workerDoFetch(None, fIdIsName = True); + for oCur in oCrit.aoPossible: + oCur.sDesc = '%u GB' % (oCur.oValue,); + + # Testbox python versions . + oCrit = oFilter.aCriteria[TestResultFilter.kiPythonVersions]; + self._oDb.execute('SELECT TestBoxesWithStrings.iPythonHexVersion,\n' + ' NULL,\n' + ' SUM(TestBoxGenIDs.cTimes)\n' + 'FROM ( SELECT TestSets.idGenTestBox AS idGenTestBox,\n' + ' COUNT(TestSets.idTestSet) AS cTimes\n' + ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiPythonVersions) + + ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) + + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + oFilter.getWhereConditions(iOmit = TestResultFilter.kiPythonVersions) + + oReportModel.getExtraSubjectWhereExpr() + + ' GROUP BY TestSets.idGenTestBox\n' + ' ) AS TestBoxGenIDs\n' + ' LEFT OUTER JOIN TestBoxesWithStrings\n' + ' ON TestBoxesWithStrings.idGenTestBox = TestBoxGenIDs.idGenTestBox\n' + 'GROUP BY TestBoxesWithStrings.iPythonHexVersion\n' + 'ORDER BY TestBoxesWithStrings.iPythonHexVersion\n' ); + workerDoFetch(None, fIdIsName = True); + for oCur in oCrit.aoPossible: + oCur.sDesc = TestBoxData.formatPythonVersionEx(oCur.oValue); # pylint: disable=redefined-variable-type + + # Testcase with variation. + oCrit = oFilter.aCriteria[TestResultFilter.kiTestCases]; + self._oDb.execute('SELECT TestCaseArgsIDs.idTestCase,\n' + ' TestCases.sName,\n' + ' TestCaseArgsIDs.idTestCaseArgs,\n' + ' CASE WHEN TestCaseArgs.sSubName IS NULL OR TestCaseArgs.sSubName = \'\' THEN\n' + ' CONCAT(\'/ #\', TestCaseArgs.idTestCaseArgs)\n' + ' ELSE\n' + ' TestCaseArgs.sSubName\n' + ' END,' + ' TestCaseArgsIDs.cTimes\n' + 'FROM ( SELECT TestSets.idTestCase AS idTestCase,\n' + ' TestSets.idTestCaseArgs AS idTestCaseArgs,\n' + ' MAX(TestSets.idGenTestCase) AS idGenTestCase,\n' + ' MAX(TestSets.idGenTestCaseArgs) AS idGenTestCaseArgs,\n' + ' COUNT(TestSets.idTestSet) AS cTimes\n' + ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiTestCases) + + ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) + + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + oFilter.getWhereConditions(iOmit = TestResultFilter.kiTestCases) + + oReportModel.getExtraSubjectWhereExpr() + + ' GROUP BY TestSets.idTestCase, TestSets.idTestCaseArgs\n' + ' ) AS TestCaseArgsIDs\n' + ' LEFT OUTER JOIN TestCases ON TestCases.idGenTestCase = TestCaseArgsIDs.idGenTestCase\n' + ' LEFT OUTER JOIN TestCaseArgs\n' + ' ON TestCaseArgs.idGenTestCaseArgs = TestCaseArgsIDs.idGenTestCaseArgs\n' + 'ORDER BY TestCases.sName, 4\n' ); + workerDoFetchNested(); + + # Build revisions. + oCrit = oFilter.aCriteria[TestResultFilter.kiRevisions]; + self._oDb.execute('SELECT Builds.iRevision, CONCAT(\'r\', Builds.iRevision), SUM(BuildIDs.cTimes)\n' + 'FROM ( SELECT TestSets.idBuild AS idBuild,\n' + ' MAX(TestSets.tsCreated) AS tsNow,\n' + ' COUNT(TestSets.idBuild) AS cTimes\n' + ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiRevisions) + + ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) + + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + oFilter.getWhereConditions(iOmit = TestResultFilter.kiRevisions) + + oReportModel.getExtraSubjectWhereExpr() + + ' GROUP BY TestSets.idBuild\n' + ' ) AS BuildIDs\n' + ' INNER JOIN Builds\n' + ' ON Builds.idBuild = BuildIDs.idBuild\n' + ' AND Builds.tsExpire > BuildIDs.tsNow\n' + ' AND Builds.tsEffective <= BuildIDs.tsNow\n' + 'GROUP BY Builds.iRevision\n' + 'ORDER BY Builds.iRevision DESC\n' ); + workerDoFetch(None, fIdIsName = True); + + # Build branches. + oCrit = oFilter.aCriteria[TestResultFilter.kiBranches]; + self._oDb.execute('SELECT BuildCategories.sBranch, BuildCategories.sBranch, SUM(BuildCategoryIDs.cTimes)\n' + 'FROM ( SELECT TestSets.idBuildCategory,\n' + ' COUNT(TestSets.idTestSet) AS cTimes\n' + ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiBranches) + + ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) + + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + oFilter.getWhereConditions(iOmit = TestResultFilter.kiBranches) + + oReportModel.getExtraSubjectWhereExpr() + + ' GROUP BY TestSets.idBuildCategory\n' + ' ) AS BuildCategoryIDs\n' + ' INNER JOIN BuildCategories\n' + ' ON BuildCategories.idBuildCategory = BuildCategoryIDs.idBuildCategory\n' + 'GROUP BY BuildCategories.sBranch\n' + 'ORDER BY BuildCategories.sBranch DESC\n' ); + workerDoFetch(None, fIdIsName = True); + + # Build types. + oCrit = oFilter.aCriteria[TestResultFilter.kiBuildTypes]; + self._oDb.execute('SELECT BuildCategories.sType, BuildCategories.sType, SUM(BuildCategoryIDs.cTimes)\n' + 'FROM ( SELECT TestSets.idBuildCategory,\n' + ' COUNT(TestSets.idTestSet) AS cTimes\n' + ' FROM TestSets\n' + oFilter.getTableJoins(iOmit = TestResultFilter.kiBuildTypes) + + ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) + + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + oFilter.getWhereConditions(iOmit = TestResultFilter.kiBuildTypes) + + oReportModel.getExtraSubjectWhereExpr() + + ' GROUP BY TestSets.idBuildCategory\n' + ' ) AS BuildCategoryIDs\n' + ' INNER JOIN BuildCategories\n' + ' ON BuildCategories.idBuildCategory = BuildCategoryIDs.idBuildCategory\n' + 'GROUP BY BuildCategories.sType\n' + 'ORDER BY BuildCategories.sType DESC\n' ); + workerDoFetch(None, fIdIsName = True); + + # Failure reasons. + oCrit = oFilter.aCriteria[TestResultFilter.kiFailReasons]; + self._oDb.execute('SELECT FailureReasons.idFailureReason, FailureReasons.sShort, FailureReasonIDs.cTimes\n' + 'FROM ( SELECT TestResultFailures.idFailureReason,\n' + ' COUNT(TestSets.idTestSet) as cTimes\n' + ' FROM TestSets\n' + ' LEFT OUTER JOIN TestResultFailures\n' + ' ON TestResultFailures.idTestSet = TestSets.idTestSet\n' + ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP\n' + + oFilter.getTableJoins(iOmit = TestResultFilter.kiFailReasons) + + ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) + + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + ' AND TestSets.enmStatus >= \'failure\'::TestStatus_T\n' + + oFilter.getWhereConditions(iOmit = TestResultFilter.kiFailReasons) + + oReportModel.getExtraSubjectWhereExpr() + + ' GROUP BY TestResultFailures.idFailureReason\n' + ' ) AS FailureReasonIDs\n' + ' LEFT OUTER JOIN FailureReasons\n' + ' ON FailureReasons.idFailureReason = FailureReasonIDs.idFailureReason\n' + ' AND FailureReasons.tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY FailureReasons.idFailureReason IS NULL DESC,\n' + ' FailureReasons.sShort\n' ); + workerDoFetch(FailureReasonLogic, 'sShort', sNullDesc = 'Not given'); + + # Error counts. + oCrit = oFilter.aCriteria[TestResultFilter.kiErrorCounts]; + self._oDb.execute('SELECT TestResults.cErrors, CAST(TestResults.cErrors AS TEXT), COUNT(TestResults.idTestResult)\n' + 'FROM ( SELECT TestSets.idTestResult AS idTestResult\n' + ' FROM TestSets\n' + + oFilter.getTableJoins(iOmit = TestResultFilter.kiFailReasons) + + ''.join(' , %s\n' % (sTable,) for sTable in oReportModel.getExtraSubjectTables()) + + ' WHERE ' + self._getTimePeriodQueryPart(tsNow, sPeriod, ' ') + + oFilter.getWhereConditions(iOmit = TestResultFilter.kiFailReasons) + + oReportModel.getExtraSubjectWhereExpr() + + ' ) AS TestSetIDs\n' + ' INNER JOIN TestResults\n' + ' ON TestResults.idTestResult = TestSetIDs.idTestResult\n' + 'GROUP BY TestResults.cErrors\n' + 'ORDER BY TestResults.cErrors\n'); + + workerDoFetch(None, fIdIsName = True); + + return oFilter; + + + # + # Details view and interface. + # + + def fetchResultTree(self, idTestSet, cMaxDepth = None): + """ + Fetches the result tree for the given test set. + + Returns a tree of TestResultDataEx nodes. + Raises exception on invalid input and database issues. + """ + # Depth first, i.e. just like the XML added them. + ## @todo this still isn't performing extremely well, consider optimizations. + sQuery = self._oDb.formatBindArgs( + 'SELECT TestResults.*,\n' + ' TestResultStrTab.sValue,\n' + ' EXISTS ( SELECT idTestResultValue\n' + ' FROM TestResultValues\n' + ' WHERE TestResultValues.idTestResult = TestResults.idTestResult ) AS fHasValues,\n' + ' EXISTS ( SELECT idTestResultMsg\n' + ' FROM TestResultMsgs\n' + ' WHERE TestResultMsgs.idTestResult = TestResults.idTestResult ) AS fHasMsgs,\n' + ' EXISTS ( SELECT idTestResultFile\n' + ' FROM TestResultFiles\n' + ' WHERE TestResultFiles.idTestResult = TestResults.idTestResult ) AS fHasFiles,\n' + ' EXISTS ( SELECT idTestResult\n' + ' FROM TestResultFailures\n' + ' WHERE TestResultFailures.idTestResult = TestResults.idTestResult ) AS fHasReasons\n' + 'FROM TestResults, TestResultStrTab\n' + 'WHERE TestResults.idTestSet = %s\n' + ' AND TestResults.idStrName = TestResultStrTab.idStr\n' + , ( idTestSet, )); + if cMaxDepth is not None: + sQuery += self._oDb.formatBindArgs(' AND TestResults.iNestingDepth <= %s\n', (cMaxDepth,)); + sQuery += 'ORDER BY idTestResult ASC\n' + + self._oDb.execute(sQuery); + cRows = self._oDb.getRowCount(); + if cRows > 65536: + raise TMTooManyRows('Too many rows returned for idTestSet=%d: %d' % (idTestSet, cRows,)); + + aaoRows = self._oDb.fetchAll(); + if not aaoRows: + raise TMRowNotFound('No test results for idTestSet=%d.' % (idTestSet,)); + + # Set up the root node first. + aoRow = aaoRows[0]; + oRoot = TestResultDataEx().initFromDbRow(aoRow); + if oRoot.idTestResultParent is not None: + raise self._oDb.integrityException('The root TestResult (#%s) has a parent (#%s)!' + % (oRoot.idTestResult, oRoot.idTestResultParent)); + self._fetchResultTreeNodeExtras(oRoot, aoRow[-4], aoRow[-3], aoRow[-2], aoRow[-1]); + + # The children (if any). + dLookup = { oRoot.idTestResult: oRoot }; + oParent = oRoot; + for iRow in range(1, len(aaoRows)): + aoRow = aaoRows[iRow]; + oCur = TestResultDataEx().initFromDbRow(aoRow); + self._fetchResultTreeNodeExtras(oCur, aoRow[-4], aoRow[-3], aoRow[-2], aoRow[-1]); + + # Figure out and vet the parent. + if oParent.idTestResult != oCur.idTestResultParent: + oParent = dLookup.get(oCur.idTestResultParent, None); + if oParent is None: + raise self._oDb.integrityException('TestResult #%d is orphaned from its parent #%s.' + % (oCur.idTestResult, oCur.idTestResultParent,)); + if oParent.iNestingDepth + 1 != oCur.iNestingDepth: + raise self._oDb.integrityException('TestResult #%d has incorrect nesting depth (%d instead of %d)' + % (oCur.idTestResult, oCur.iNestingDepth, oParent.iNestingDepth + 1,)); + + # Link it up. + oCur.oParent = oParent; + oParent.aoChildren.append(oCur); + dLookup[oCur.idTestResult] = oCur; + + return (oRoot, dLookup); + + def _fetchResultTreeNodeExtras(self, oCurNode, fHasValues, fHasMsgs, fHasFiles, fHasReasons): + """ + fetchResultTree worker that fetches values, message and files for the + specified node. + """ + assert(oCurNode.aoValues == []); + assert(oCurNode.aoMsgs == []); + assert(oCurNode.aoFiles == []); + assert(oCurNode.oReason is None); + + if fHasValues: + self._oDb.execute('SELECT TestResultValues.*,\n' + ' TestResultStrTab.sValue\n' + 'FROM TestResultValues, TestResultStrTab\n' + 'WHERE TestResultValues.idTestResult = %s\n' + ' AND TestResultValues.idStrName = TestResultStrTab.idStr\n' + 'ORDER BY idTestResultValue ASC\n' + , ( oCurNode.idTestResult, )); + for aoRow in self._oDb.fetchAll(): + oCurNode.aoValues.append(TestResultValueDataEx().initFromDbRow(aoRow)); + + if fHasMsgs: + self._oDb.execute('SELECT TestResultMsgs.*,\n' + ' TestResultStrTab.sValue\n' + 'FROM TestResultMsgs, TestResultStrTab\n' + 'WHERE TestResultMsgs.idTestResult = %s\n' + ' AND TestResultMsgs.idStrMsg = TestResultStrTab.idStr\n' + 'ORDER BY idTestResultMsg ASC\n' + , ( oCurNode.idTestResult, )); + for aoRow in self._oDb.fetchAll(): + oCurNode.aoMsgs.append(TestResultMsgDataEx().initFromDbRow(aoRow)); + + if fHasFiles: + self._oDb.execute('SELECT TestResultFiles.*,\n' + ' StrTabFile.sValue AS sFile,\n' + ' StrTabDesc.sValue AS sDescription,\n' + ' StrTabKind.sValue AS sKind,\n' + ' StrTabMime.sValue AS sMime\n' + 'FROM TestResultFiles,\n' + ' TestResultStrTab AS StrTabFile,\n' + ' TestResultStrTab AS StrTabDesc,\n' + ' TestResultStrTab AS StrTabKind,\n' + ' TestResultStrTab AS StrTabMime\n' + 'WHERE TestResultFiles.idTestResult = %s\n' + ' AND TestResultFiles.idStrFile = StrTabFile.idStr\n' + ' AND TestResultFiles.idStrDescription = StrTabDesc.idStr\n' + ' AND TestResultFiles.idStrKind = StrTabKind.idStr\n' + ' AND TestResultFiles.idStrMime = StrTabMime.idStr\n' + 'ORDER BY idTestResultFile ASC\n' + , ( oCurNode.idTestResult, )); + for aoRow in self._oDb.fetchAll(): + oCurNode.aoFiles.append(TestResultFileDataEx().initFromDbRow(aoRow)); + + if fHasReasons: + if self.oFailureReasonLogic is None: + self.oFailureReasonLogic = FailureReasonLogic(self._oDb); + if self.oUserAccountLogic is None: + self.oUserAccountLogic = UserAccountLogic(self._oDb); + self._oDb.execute('SELECT *\n' + 'FROM TestResultFailures\n' + 'WHERE idTestResult = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , ( oCurNode.idTestResult, )); + if self._oDb.getRowCount() > 0: + oCurNode.oReason = TestResultFailureDataEx().initFromDbRowEx(self._oDb.fetchOne(), self.oFailureReasonLogic, + self.oUserAccountLogic); + + return True; + + + + # + # TestBoxController interface(s). + # + + def _inhumeTestResults(self, aoStack, idTestSet, sError): + """ + The test produces too much output, kill and bury it. + + Note! We leave the test set open, only the test result records are + completed. Thus, _getResultStack will return an empty stack and + cause XML processing to fail immediately, while we can still + record when it actually completed in the test set the normal way. + """ + self._oDb.dprint('** _inhumeTestResults: idTestSet=%d\n%s' % (idTestSet, self._stringifyStack(aoStack),)); + + # + # First add a message. + # + self._newFailureDetails(aoStack[0].idTestResult, idTestSet, sError, None); + + # + # The complete all open test results. + # + for oTestResult in aoStack: + oTestResult.cErrors += 1; + self._completeTestResults(oTestResult, None, TestResultData.ksTestStatus_Failure, oTestResult.cErrors); + + # A bit of paranoia. + self._oDb.execute('UPDATE TestResults\n' + 'SET cErrors = cErrors + 1,\n' + ' enmStatus = \'failure\'::TestStatus_T,\n' + ' tsElapsed = CURRENT_TIMESTAMP - tsCreated\n' + 'WHERE idTestSet = %s\n' + ' AND enmStatus = \'running\'::TestStatus_T\n' + , ( idTestSet, )); + self._oDb.commit(); + + return None; + + def strTabString(self, sString, fCommit = False): + """ + Gets the string table id for the given string, adding it if new. + + Note! A copy of this code is also in TestSetLogic. + """ + ## @todo move this and make a stored procedure for it. + self._oDb.execute('SELECT idStr\n' + 'FROM TestResultStrTab\n' + 'WHERE sValue = %s' + , (sString,)); + if self._oDb.getRowCount() == 0: + self._oDb.execute('INSERT INTO TestResultStrTab (sValue)\n' + 'VALUES (%s)\n' + 'RETURNING idStr\n' + , (sString,)); + if fCommit: + self._oDb.commit(); + return self._oDb.fetchOne()[0]; + + @staticmethod + def _stringifyStack(aoStack): + """Returns a string rep of the stack.""" + sRet = ''; + for i, _ in enumerate(aoStack): + sRet += 'aoStack[%d]=%s\n' % (i, aoStack[i]); + return sRet; + + def _getResultStack(self, idTestSet): + """ + Gets the current stack of result sets. + """ + self._oDb.execute('SELECT *\n' + 'FROM TestResults\n' + 'WHERE idTestSet = %s\n' + ' AND enmStatus = \'running\'::TestStatus_T\n' + 'ORDER BY idTestResult DESC' + , ( idTestSet, )); + aoStack = []; + for aoRow in self._oDb.fetchAll(): + aoStack.append(TestResultData().initFromDbRow(aoRow)); + + for i, _ in enumerate(aoStack): + assert aoStack[i].iNestingDepth == len(aoStack) - i - 1, self._stringifyStack(aoStack); + + return aoStack; + + def _newTestResult(self, idTestResultParent, idTestSet, iNestingDepth, tsCreated, sName, dCounts, fCommit = False): + """ + Creates a new test result. + Returns the TestResultData object for the new record. + May raise exception on database error. + """ + assert idTestResultParent is not None; + assert idTestResultParent > 1; + + # + # This isn't necessarily very efficient, but it's necessary to prevent + # a wild test or testbox from filling up the database. + # + sCountName = 'cTestResults'; + if sCountName not in dCounts: + self._oDb.execute('SELECT COUNT(idTestResult)\n' + 'FROM TestResults\n' + 'WHERE idTestSet = %s\n' + , ( idTestSet,)); + dCounts[sCountName] = self._oDb.fetchOne()[0]; + dCounts[sCountName] += 1; + if dCounts[sCountName] > config.g_kcMaxTestResultsPerTS: + raise TestResultHangingOffence('Too many sub-tests in total!'); + + sCountName = 'cTestResultsIn%d' % (idTestResultParent,); + if sCountName not in dCounts: + self._oDb.execute('SELECT COUNT(idTestResult)\n' + 'FROM TestResults\n' + 'WHERE idTestResultParent = %s\n' + , ( idTestResultParent,)); + dCounts[sCountName] = self._oDb.fetchOne()[0]; + dCounts[sCountName] += 1; + if dCounts[sCountName] > config.g_kcMaxTestResultsPerTR: + raise TestResultHangingOffence('Too many immediate sub-tests!'); + + # This is also a hanging offence. + if iNestingDepth > config.g_kcMaxTestResultDepth: + raise TestResultHangingOffence('To deep sub-test nesting!'); + + # Ditto. + if len(sName) > config.g_kcchMaxTestResultName: + raise TestResultHangingOffence('Test name is too long: %d chars - "%s"' % (len(sName), sName)); + + # + # Within bounds, do the job. + # + idStrName = self.strTabString(sName, fCommit); + self._oDb.execute('INSERT INTO TestResults (\n' + ' idTestResultParent,\n' + ' idTestSet,\n' + ' tsCreated,\n' + ' idStrName,\n' + ' iNestingDepth )\n' + 'VALUES (%s, %s, TIMESTAMP WITH TIME ZONE %s, %s, %s)\n' + 'RETURNING *\n' + , ( idTestResultParent, idTestSet, tsCreated, idStrName, iNestingDepth) ) + oData = TestResultData().initFromDbRow(self._oDb.fetchOne()); + + self._oDb.maybeCommit(fCommit); + return oData; + + def _newTestValue(self, idTestResult, idTestSet, sName, lValue, sUnit, dCounts, tsCreated = None, fCommit = False): + """ + Creates a test value. + May raise exception on database error. + """ + + # + # Bounds checking. + # + sCountName = 'cTestValues'; + if sCountName not in dCounts: + self._oDb.execute('SELECT COUNT(idTestResultValue)\n' + 'FROM TestResultValues, TestResults\n' + 'WHERE TestResultValues.idTestResult = TestResults.idTestResult\n' + ' AND TestResults.idTestSet = %s\n' + , ( idTestSet,)); + dCounts[sCountName] = self._oDb.fetchOne()[0]; + dCounts[sCountName] += 1; + if dCounts[sCountName] > config.g_kcMaxTestValuesPerTS: + raise TestResultHangingOffence('Too many values in total!'); + + sCountName = 'cTestValuesIn%d' % (idTestResult,); + if sCountName not in dCounts: + self._oDb.execute('SELECT COUNT(idTestResultValue)\n' + 'FROM TestResultValues\n' + 'WHERE idTestResult = %s\n' + , ( idTestResult,)); + dCounts[sCountName] = self._oDb.fetchOne()[0]; + dCounts[sCountName] += 1; + if dCounts[sCountName] > config.g_kcMaxTestValuesPerTR: + raise TestResultHangingOffence('Too many immediate values for one test result!'); + + if len(sName) > config.g_kcchMaxTestValueName: + raise TestResultHangingOffence('Value name is too long: %d chars - "%s"' % (len(sName), sName)); + + # + # Do the job. + # + iUnit = constants.valueunit.g_kdNameToConst.get(sUnit, constants.valueunit.NONE); + + idStrName = self.strTabString(sName, fCommit); + if tsCreated is None: + self._oDb.execute('INSERT INTO TestResultValues (\n' + ' idTestResult,\n' + ' idTestSet,\n' + ' idStrName,\n' + ' lValue,\n' + ' iUnit)\n' + 'VALUES ( %s, %s, %s, %s, %s )\n' + , ( idTestResult, idTestSet, idStrName, lValue, iUnit,) ); + else: + self._oDb.execute('INSERT INTO TestResultValues (\n' + ' idTestResult,\n' + ' idTestSet,\n' + ' tsCreated,\n' + ' idStrName,\n' + ' lValue,\n' + ' iUnit)\n' + 'VALUES ( %s, %s, TIMESTAMP WITH TIME ZONE %s, %s, %s, %s )\n' + , ( idTestResult, idTestSet, tsCreated, idStrName, lValue, iUnit,) ); + self._oDb.maybeCommit(fCommit); + return True; + + def _newFailureDetails(self, idTestResult, idTestSet, sText, dCounts, tsCreated = None, fCommit = False): + """ + Creates a record detailing cause of failure. + May raise exception on database error. + """ + + # + # Overflow protection. + # + if dCounts is not None: + sCountName = 'cTestMsgsIn%d' % (idTestResult,); + if sCountName not in dCounts: + self._oDb.execute('SELECT COUNT(idTestResultMsg)\n' + 'FROM TestResultMsgs\n' + 'WHERE idTestResult = %s\n' + , ( idTestResult,)); + dCounts[sCountName] = self._oDb.fetchOne()[0]; + dCounts[sCountName] += 1; + if dCounts[sCountName] > config.g_kcMaxTestMsgsPerTR: + raise TestResultHangingOffence('Too many messages under for one test result!'); + + if len(sText) > config.g_kcchMaxTestMsg: + raise TestResultHangingOffence('Failure details message is too long: %d chars - "%s"' % (len(sText), sText)); + + # + # Do the job. + # + idStrMsg = self.strTabString(sText, fCommit); + if tsCreated is None: + self._oDb.execute('INSERT INTO TestResultMsgs (\n' + ' idTestResult,\n' + ' idTestSet,\n' + ' idStrMsg,\n' + ' enmLevel)\n' + 'VALUES ( %s, %s, %s, %s)\n' + , ( idTestResult, idTestSet, idStrMsg, 'failure',) ); + else: + self._oDb.execute('INSERT INTO TestResultMsgs (\n' + ' idTestResult,\n' + ' idTestSet,\n' + ' tsCreated,\n' + ' idStrMsg,\n' + ' enmLevel)\n' + 'VALUES ( %s, %s, TIMESTAMP WITH TIME ZONE %s, %s, %s)\n' + , ( idTestResult, idTestSet, tsCreated, idStrMsg, 'failure',) ); + + self._oDb.maybeCommit(fCommit); + return True; + + + def _completeTestResults(self, oTestResult, tsDone, enmStatus, cErrors = 0, fCommit = False): + """ + Completes a test result. Updates the oTestResult object. + May raise exception on database error. + """ + self._oDb.dprint('** _completeTestResults: cErrors=%s tsDone=%s enmStatus=%s oTestResults=\n%s' + % (cErrors, tsDone, enmStatus, oTestResult,)); + + # + # Sanity check: No open sub tests (aoStack should make sure about this!). + # + self._oDb.execute('SELECT COUNT(idTestResult)\n' + 'FROM TestResults\n' + 'WHERE idTestResultParent = %s\n' + ' AND enmStatus = %s\n' + , ( oTestResult.idTestResult, TestResultData.ksTestStatus_Running,)); + cOpenSubTest = self._oDb.fetchOne()[0]; + assert cOpenSubTest == 0, 'cOpenSubTest=%d - %s' % (cOpenSubTest, oTestResult,); + assert oTestResult.enmStatus == TestResultData.ksTestStatus_Running; + + # + # Make sure the reporter isn't lying about successes or error counts. + # + self._oDb.execute('SELECT COALESCE(SUM(cErrors), 0)\n' + 'FROM TestResults\n' + 'WHERE idTestResultParent = %s\n' + , ( oTestResult.idTestResult, )); + cMinErrors = self._oDb.fetchOne()[0] + oTestResult.cErrors; + cErrors = max(cErrors, cMinErrors); + if cErrors > 0 and enmStatus == TestResultData.ksTestStatus_Success: + enmStatus = TestResultData.ksTestStatus_Failure + + # + # Do the update. + # + if tsDone is None: + self._oDb.execute('UPDATE TestResults\n' + 'SET cErrors = %s,\n' + ' enmStatus = %s,\n' + ' tsElapsed = CURRENT_TIMESTAMP - tsCreated\n' + 'WHERE idTestResult = %s\n' + 'RETURNING tsElapsed' + , ( cErrors, enmStatus, oTestResult.idTestResult,) ); + else: + self._oDb.execute('UPDATE TestResults\n' + 'SET cErrors = %s,\n' + ' enmStatus = %s,\n' + ' tsElapsed = TIMESTAMP WITH TIME ZONE %s - tsCreated\n' + 'WHERE idTestResult = %s\n' + 'RETURNING tsElapsed' + , ( cErrors, enmStatus, tsDone, oTestResult.idTestResult,) ); + + oTestResult.tsElapsed = self._oDb.fetchOne()[0]; + oTestResult.enmStatus = enmStatus; + oTestResult.cErrors = cErrors; + + self._oDb.maybeCommit(fCommit); + return None; + + def _doPopHint(self, aoStack, cStackEntries, dCounts, idTestSet): + """ Executes a PopHint. """ + assert cStackEntries >= 0; + while len(aoStack) > cStackEntries: + if aoStack[0].enmStatus == TestResultData.ksTestStatus_Running: + self._newFailureDetails(aoStack[0].idTestResult, idTestSet, 'XML error: Missing </Test>', dCounts); + self._completeTestResults(aoStack[0], tsDone = None, cErrors = 1, + enmStatus = TestResultData.ksTestStatus_Failure, fCommit = True); + aoStack.pop(0); + return True; + + + @staticmethod + def _validateElement(sName, dAttribs, fClosed): + """ + Validates an element and its attributes. + """ + + # + # Validate attributes by name. + # + + # Validate integer attributes. + for sAttr in [ 'errors', 'testdepth' ]: + if sAttr in dAttribs: + try: + _ = int(dAttribs[sAttr]); + except: + return 'Element %s has an invalid %s attribute value: %s.' % (sName, sAttr, dAttribs[sAttr],); + + # Validate long attributes. + for sAttr in [ 'value', ]: + if sAttr in dAttribs: + try: + _ = long(dAttribs[sAttr]); # pylint: disable=redefined-variable-type + except: + return 'Element %s has an invalid %s attribute value: %s.' % (sName, sAttr, dAttribs[sAttr],); + + # Validate string attributes. + for sAttr in [ 'name', 'text' ]: # 'unit' can be zero length. + if sAttr in dAttribs and not dAttribs[sAttr]: + return 'Element %s has an empty %s attribute value.' % (sName, sAttr,); + + # Validate the timestamp attribute. + if 'timestamp' in dAttribs: + (dAttribs['timestamp'], sError) = ModelDataBase.validateTs(dAttribs['timestamp'], fAllowNull = False); + if sError is not None: + return 'Element %s has an invalid timestamp ("%s"): %s' % (sName, dAttribs['timestamp'], sError,); + + + # + # Check that attributes that are required are present. + # We ignore extra attributes. + # + dElementAttribs = \ + { + 'Test': [ 'timestamp', 'name', ], + 'Value': [ 'timestamp', 'name', 'unit', 'value', ], + 'FailureDetails': [ 'timestamp', 'text', ], + 'Passed': [ 'timestamp', ], + 'Skipped': [ 'timestamp', ], + 'Failed': [ 'timestamp', 'errors', ], + 'TimedOut': [ 'timestamp', 'errors', ], + 'End': [ 'timestamp', ], + 'PushHint': [ 'testdepth', ], + 'PopHint': [ 'testdepth', ], + }; + if sName not in dElementAttribs: + return 'Unknown element "%s".' % (sName,); + for sAttr in dElementAttribs[sName]: + if sAttr not in dAttribs: + return 'Element %s requires attribute "%s".' % (sName, sAttr); + + # + # Only the Test element can (and must) remain open. + # + if sName == 'Test' and fClosed: + return '<Test/> is not allowed.'; + if sName != 'Test' and not fClosed: + return 'All elements except <Test> must be closed.'; + + return None; + + @staticmethod + def _parseElement(sElement): + """ + Parses an element. + + """ + # + # Element level bits. + # + sName = sElement.split()[0]; + sElement = sElement[len(sName):]; + + fClosed = sElement[-1] == '/'; + if fClosed: + sElement = sElement[:-1]; + + # + # Attributes. + # + sError = None; + dAttribs = {}; + sElement = sElement.strip(); + while sElement: + # Extract attribute name. + off = sElement.find('='); + if off < 0 or not sElement[:off].isalnum(): + sError = 'Attributes shall have alpha numberical names and have values.'; + break; + sAttr = sElement[:off]; + + # Extract attribute value. + if off + 2 >= len(sElement) or sElement[off + 1] != '"': + sError = 'Attribute (%s) value is missing or not in double quotes.' % (sAttr,); + break; + off += 2; + offEndQuote = sElement.find('"', off); + if offEndQuote < 0: + sError = 'Attribute (%s) value is missing end quotation mark.' % (sAttr,); + break; + sValue = sElement[off:offEndQuote]; + + # Check for duplicates. + if sAttr in dAttribs: + sError = 'Attribute "%s" appears more than once.' % (sAttr,); + break; + + # Unescape the value. + sValue = sValue.replace('<', '<'); + sValue = sValue.replace('>', '>'); + sValue = sValue.replace(''', '\''); + sValue = sValue.replace('"', '"'); + sValue = sValue.replace('
', '\n'); + sValue = sValue.replace('
', '\r'); + sValue = sValue.replace('&', '&'); # last + + # Done. + dAttribs[sAttr] = sValue; + + # advance + sElement = sElement[offEndQuote + 1:]; + sElement = sElement.lstrip(); + + # + # Validate the element before we return. + # + if sError is None: + sError = TestResultLogic._validateElement(sName, dAttribs, fClosed); + + return (sName, dAttribs, sError) + + def _handleElement(self, sName, dAttribs, idTestSet, aoStack, aaiHints, dCounts): + """ + Worker for processXmlStream that handles one element. + + Returns None on success, error string on bad XML or similar. + Raises exception on hanging offence and on database error. + """ + if sName == 'Test': + iNestingDepth = aoStack[0].iNestingDepth + 1 if aoStack else 0; + aoStack.insert(0, self._newTestResult(idTestResultParent = aoStack[0].idTestResult, idTestSet = idTestSet, + tsCreated = dAttribs['timestamp'], sName = dAttribs['name'], + iNestingDepth = iNestingDepth, dCounts = dCounts, fCommit = True) ); + + elif sName == 'Value': + self._newTestValue(idTestResult = aoStack[0].idTestResult, idTestSet = idTestSet, tsCreated = dAttribs['timestamp'], + sName = dAttribs['name'], sUnit = dAttribs['unit'], lValue = long(dAttribs['value']), + dCounts = dCounts, fCommit = True); + + elif sName == 'FailureDetails': + self._newFailureDetails(idTestResult = aoStack[0].idTestResult, idTestSet = idTestSet, + tsCreated = dAttribs['timestamp'], sText = dAttribs['text'], dCounts = dCounts, + fCommit = True); + + elif sName == 'Passed': + self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'], + enmStatus = TestResultData.ksTestStatus_Success, fCommit = True); + + elif sName == 'Skipped': + self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'], + enmStatus = TestResultData.ksTestStatus_Skipped, fCommit = True); + + elif sName == 'Failed': + self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'], cErrors = int(dAttribs['errors']), + enmStatus = TestResultData.ksTestStatus_Failure, fCommit = True); + + elif sName == 'TimedOut': + self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'], cErrors = int(dAttribs['errors']), + enmStatus = TestResultData.ksTestStatus_TimedOut, fCommit = True); + + elif sName == 'End': + self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'], + cErrors = int(dAttribs.get('errors', '1')), + enmStatus = TestResultData.ksTestStatus_Success, fCommit = True); + + elif sName == 'PushHint': + if len(aaiHints) > 1: + return 'PushHint cannot be nested.' + + aaiHints.insert(0, [len(aoStack), int(dAttribs['testdepth'])]); + + elif sName == 'PopHint': + if not aaiHints: + return 'No hint to pop.' + + iDesiredTestDepth = int(dAttribs['testdepth']); + cStackEntries, iTestDepth = aaiHints.pop(0); + self._doPopHint(aoStack, cStackEntries, dCounts, idTestSet); # Fake the necessary '<End/></Test>' tags. + if iDesiredTestDepth != iTestDepth: + return 'PopHint tag has different testdepth: %d, on stack %d.' % (iDesiredTestDepth, iTestDepth); + else: + return 'Unexpected element "%s".' % (sName,); + return None; + + + def processXmlStream(self, sXml, idTestSet): + """ + Processes the "XML" stream section given in sXml. + + The sXml isn't a complete XML document, even should we save up all sXml + for a given set, they may not form a complete and well formed XML + document since the test may be aborted, abend or simply be buggy. We + therefore do our own parsing and treat the XML tags as commands more + than anything else. + + Returns (sError, fUnforgivable), where sError is None on success. + May raise database exception. + """ + aoStack = self._getResultStack(idTestSet); # [0] == top; [-1] == bottom. + if not aoStack: + return ('No open results', True); + self._oDb.dprint('** processXmlStream len(aoStack)=%s' % (len(aoStack),)); + #self._oDb.dprint('processXmlStream: %s' % (self._stringifyStack(aoStack),)); + #self._oDb.dprint('processXmlStream: sXml=%s' % (sXml,)); + + dCounts = {}; + aaiHints = []; + sError = None; + + fExpectCloseTest = False; + sXml = sXml.strip(); + while sXml: + if sXml.startswith('</Test>'): # Only closing tag. + offNext = len('</Test>'); + if len(aoStack) <= 1: + sError = 'Trying to close the top test results.' + break; + # ASSUMES that we've just seen an <End/>, <Passed/>, <Failed/>, + # <TimedOut/> or <Skipped/> tag earlier in this call! + if aoStack[0].enmStatus == TestResultData.ksTestStatus_Running or not fExpectCloseTest: + sError = 'Missing <End/>, <Passed/>, <Failed/>, <TimedOut/> or <Skipped/> tag.'; + break; + aoStack.pop(0); + fExpectCloseTest = False; + + elif fExpectCloseTest: + sError = 'Expected </Test>.' + break; + + elif sXml.startswith('<?xml '): # Ignore (included files). + offNext = sXml.find('?>'); + if offNext < 0: + sError = 'Unterminated <?xml ?> element.'; + break; + offNext += 2; + + elif sXml[0] == '<': + # Parse and check the tag. + if not sXml[1].isalpha(): + sError = 'Malformed element.'; + break; + offNext = sXml.find('>') + if offNext < 0: + sError = 'Unterminated element.'; + break; + (sName, dAttribs, sError) = self._parseElement(sXml[1:offNext]); + offNext += 1; + if sError is not None: + break; + + # Handle it. + try: + sError = self._handleElement(sName, dAttribs, idTestSet, aoStack, aaiHints, dCounts); + except TestResultHangingOffence as oXcpt: + self._inhumeTestResults(aoStack, idTestSet, str(oXcpt)); + return (str(oXcpt), True); + + + fExpectCloseTest = sName in [ 'End', 'Passed', 'Failed', 'TimedOut', 'Skipped', ]; + else: + sError = 'Unexpected content.'; + break; + + # Advance. + sXml = sXml[offNext:]; + sXml = sXml.lstrip(); + + # + # Post processing checks. + # + if sError is None and fExpectCloseTest: + sError = 'Expected </Test> before the end of the XML section.' + elif sError is None and aaiHints: + sError = 'Expected </PopHint> before the end of the XML section.' + if aaiHints: + self._doPopHint(aoStack, aaiHints[-1][0], dCounts, idTestSet); + + # + # Log the error. + # + if sError is not None: + SystemLogLogic(self._oDb).addEntry(SystemLogData.ksEvent_XmlResultMalformed, + 'idTestSet=%s idTestResult=%s XML="%s" %s' + % ( idTestSet, + aoStack[0].idTestResult if aoStack else -1, + sXml[:min(len(sXml), 30)], + sError, ), + cHoursRepeat = 6, fCommit = True); + return (sError, False); + + + + + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +class TestResultDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [TestResultData(),]; + +class TestResultValueDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [TestResultValueData(),]; + +if __name__ == '__main__': + unittest.main(); + # not reached. + diff --git a/src/VBox/ValidationKit/testmanager/core/testset.py b/src/VBox/ValidationKit/testmanager/core/testset.py new file mode 100755 index 00000000..7079b215 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/testset.py @@ -0,0 +1,869 @@ +# -*- coding: utf-8 -*- +# $Id: testset.py $ + +""" +Test Manager - TestSet. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard python imports. +import os; +import zipfile; +import unittest; + +# Validation Kit imports. +from common import utils; +from testmanager import config; +from testmanager.core import db; +from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, \ + TMExceptionBase, TMTooManyRows, TMRowNotFound; +from testmanager.core.testbox import TestBoxData; +from testmanager.core.testresults import TestResultFileDataEx; + + +class TestSetData(ModelDataBase): + """ + TestSet Data. + """ + + ## @name TestStatus_T + # @{ + ksTestStatus_Running = 'running'; + ksTestStatus_Success = 'success'; + ksTestStatus_Skipped = 'skipped'; + ksTestStatus_BadTestBox = 'bad-testbox'; + ksTestStatus_Aborted = 'aborted'; + ksTestStatus_Failure = 'failure'; + ksTestStatus_TimedOut = 'timed-out'; + ksTestStatus_Rebooted = 'rebooted'; + ## @} + + ## List of relatively harmless (to testgroup/case) statuses. + kasHarmlessTestStatuses = [ ksTestStatus_Skipped, ksTestStatus_BadTestBox, ksTestStatus_Aborted, ]; + ## List of bad statuses. + kasBadTestStatuses = [ ksTestStatus_Failure, ksTestStatus_TimedOut, ksTestStatus_Rebooted, ]; + + ksIdAttr = 'idTestSet'; + + ksParam_idTestSet = 'TestSet_idTestSet'; + ksParam_tsConfig = 'TestSet_tsConfig'; + ksParam_tsCreated = 'TestSet_tsCreated'; + ksParam_tsDone = 'TestSet_tsDone'; + ksParam_enmStatus = 'TestSet_enmStatus'; + ksParam_idBuild = 'TestSet_idBuild'; + ksParam_idBuildCategory = 'TestSet_idBuildCategory'; + ksParam_idBuildTestSuite = 'TestSet_idBuildTestSuite'; + ksParam_idGenTestBox = 'TestSet_idGenTestBox'; + ksParam_idTestBox = 'TestSet_idTestBox'; + ksParam_idSchedGroup = 'TestSet_idSchedGroup'; + ksParam_idTestGroup = 'TestSet_idTestGroup'; + ksParam_idGenTestCase = 'TestSet_idGenTestCase'; + ksParam_idTestCase = 'TestSet_idTestCase'; + ksParam_idGenTestCaseArgs = 'TestSet_idGenTestCaseArgs'; + ksParam_idTestCaseArgs = 'TestSet_idTestCaseArgs'; + ksParam_idTestResult = 'TestSet_idTestResult'; + ksParam_sBaseFilename = 'TestSet_sBaseFilename'; + ksParam_iGangMemberNo = 'TestSet_iGangMemberNo'; + ksParam_idTestSetGangLeader = 'TestSet_idTestSetGangLeader'; + + kasAllowNullAttributes = [ 'tsDone', 'idBuildTestSuite', 'idTestSetGangLeader' ]; + kasValidValues_enmStatus = [ + ksTestStatus_Running, + ksTestStatus_Success, + ksTestStatus_Skipped, + ksTestStatus_BadTestBox, + ksTestStatus_Aborted, + ksTestStatus_Failure, + ksTestStatus_TimedOut, + ksTestStatus_Rebooted, + ]; + kiMin_iGangMemberNo = 0; + kiMax_iGangMemberNo = 1023; + + + kcDbColumns = 20; + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.idTestSet = None; + self.tsConfig = None; + self.tsCreated = None; + self.tsDone = None; + self.enmStatus = 'running'; + self.idBuild = None; + self.idBuildCategory = None; + self.idBuildTestSuite = None; + self.idGenTestBox = None; + self.idTestBox = None; + self.idSchedGroup = None; + self.idTestGroup = None; + self.idGenTestCase = None; + self.idTestCase = None; + self.idGenTestCaseArgs = None; + self.idTestCaseArgs = None; + self.idTestResult = None; + self.sBaseFilename = None; + self.iGangMemberNo = 0; + self.idTestSetGangLeader = None; + + def initFromDbRow(self, aoRow): + """ + Internal worker for initFromDbWithId and initFromDbWithGenId as well as + TestBoxSetLogic. + """ + + if aoRow is None: + raise TMRowNotFound('TestSet not found.'); + + self.idTestSet = aoRow[0]; + self.tsConfig = aoRow[1]; + self.tsCreated = aoRow[2]; + self.tsDone = aoRow[3]; + self.enmStatus = aoRow[4]; + self.idBuild = aoRow[5]; + self.idBuildCategory = aoRow[6]; + self.idBuildTestSuite = aoRow[7]; + self.idGenTestBox = aoRow[8]; + self.idTestBox = aoRow[9]; + self.idSchedGroup = aoRow[10]; + self.idTestGroup = aoRow[11]; + self.idGenTestCase = aoRow[12]; + self.idTestCase = aoRow[13]; + self.idGenTestCaseArgs = aoRow[14]; + self.idTestCaseArgs = aoRow[15]; + self.idTestResult = aoRow[16]; + self.sBaseFilename = aoRow[17]; + self.iGangMemberNo = aoRow[18]; + self.idTestSetGangLeader = aoRow[19]; + return self; + + + def initFromDbWithId(self, oDb, idTestSet): + """ + Initialize the object from the database. + """ + oDb.execute('SELECT *\n' + 'FROM TestSets\n' + 'WHERE idTestSet = %s\n' + , (idTestSet, ) ); + aoRow = oDb.fetchOne() + if aoRow is None: + raise TMRowNotFound('idTestSet=%s not found' % (idTestSet,)); + return self.initFromDbRow(aoRow); + + + def openFile(self, sFilename, sMode = 'rb'): + """ + Opens a file. + + Returns (oFile, cbFile, fIsStream) on success. + Returns (None, sErrorMsg, None) on failure. + Will not raise exceptions, unless the class instance is invalid. + """ + assert sMode in [ 'rb', 'r', 'rU' ]; + + # Try raw file first. + sFile1 = os.path.join(config.g_ksFileAreaRootDir, self.sBaseFilename + '-' + sFilename); + try: + oFile = open(sFile1, sMode); # pylint: disable=consider-using-with,unspecified-encoding + return (oFile, os.fstat(oFile.fileno()).st_size, False); + except Exception as oXcpt1: + # Try the zip archive next. + sFile2 = os.path.join(config.g_ksZipFileAreaRootDir, self.sBaseFilename + '.zip'); + try: + oZipFile = zipfile.ZipFile(sFile2, 'r'); # pylint: disable=consider-using-with + oFile = oZipFile.open(sFilename, sMode if sMode != 'rb' else 'r'); # pylint: disable=consider-using-with + cbFile = oZipFile.getinfo(sFilename).file_size; + return (oFile, cbFile, True); + except Exception as oXcpt2: + # Construct a meaningful error message. + try: + if os.path.exists(sFile1): + return (None, 'Error opening "%s": %s' % (sFile1, oXcpt1), None); + if not os.path.exists(sFile2): + return (None, 'File "%s" not found. [%s, %s]' % (sFilename, sFile1, sFile2,), None); + return (None, 'Error opening "%s" inside "%s": %s' % (sFilename, sFile2, oXcpt2), None); + except Exception as oXcpt3: + return (None, 'OMG! %s; %s; %s' % (oXcpt1, oXcpt2, oXcpt3,), None); + return (None, 'Code not reachable!', None); + + def createFile(self, sFilename, sMode = 'wb'): + """ + Creates a new file. + + Returns oFile on success. + Returns sErrorMsg on failure. + """ + assert sMode in [ 'wb', 'w', 'wU' ]; + + # Try raw file first. + sFile1 = os.path.join(config.g_ksFileAreaRootDir, self.sBaseFilename + '-' + sFilename); + try: + if not os.path.exists(os.path.dirname(sFile1)): + os.makedirs(os.path.dirname(sFile1), 0o755); + oFile = open(sFile1, sMode); # pylint: disable=consider-using-with,unspecified-encoding + except Exception as oXcpt1: + return str(oXcpt1); + return oFile; + + @staticmethod + def findLogOffsetForTimestamp(sLogContent, tsTimestamp, offStart = 0, fAfter = False): + """ + Log parsing utility function for finding the offset for the given timestamp. + + We ASSUME the log lines are prefixed with UTC timestamps on the format + '09:43:55.789353'. + + Return index into the sLogContent string, 0 if not found. + """ + # Turn tsTimestamp into a string compatible with what we expect to find in the log. + oTsZulu = db.dbTimestampToZuluDatetime(tsTimestamp); + sWantedTs = oTsZulu.strftime('%H:%M:%S.%f'); + assert len(sWantedTs) == 15; + + # Now loop thru the string, line by line. + offRet = offStart; + off = offStart; + while True: + sThisTs = sLogContent[off : off + 15]; + if len(sThisTs) >= 15 \ + and sThisTs[2] == ':' \ + and sThisTs[5] == ':' \ + and sThisTs[8] == '.' \ + and sThisTs[14] in '0123456789': + if sThisTs < sWantedTs: + offRet = off; + elif sThisTs == sWantedTs: + if not fAfter: + return off; + offRet = off; + else: + if fAfter: + offRet = off; + break; + + # next line. + off = sLogContent.find('\n', off); + if off < 0: + if fAfter: + offRet = len(sLogContent); + break; + off += 1; + + return offRet; + + @staticmethod + def extractLogSection(sLogContent, tsStart, tsLast): + """ + Returns log section from tsStart to tsLast (or all if we cannot make sense of it). + """ + offStart = TestSetData.findLogOffsetForTimestamp(sLogContent, tsStart); + offEnd = TestSetData.findLogOffsetForTimestamp(sLogContent, tsLast, offStart, fAfter = True); + return sLogContent[offStart : offEnd]; + + @staticmethod + def extractLogSectionElapsed(sLogContent, tsStart, tsElapsed): + """ + Returns log section from tsStart and tsElapsed forward (or all if we cannot make sense of it). + """ + tsStart = db.dbTimestampToZuluDatetime(tsStart); + tsLast = tsStart + tsElapsed; + return TestSetData.extractLogSection(sLogContent, tsStart, tsLast); + + + +class TestSetLogic(ModelLogicBase): + """ + TestSet logic. + """ + + + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb); + + + def tryFetch(self, idTestSet): + """ + Attempts to fetch a test set. + + Returns a TestSetData object on success. + Returns None if no status was found. + Raises exception on other errors. + """ + self._oDb.execute('SELECT *\n' + 'FROM TestSets\n' + 'WHERE idTestSet = %s\n', + (idTestSet,)); + if self._oDb.getRowCount() == 0: + return None; + oData = TestSetData(); + return oData.initFromDbRow(self._oDb.fetchOne()); + + def strTabString(self, sString, fCommit = False): + """ + Gets the string table id for the given string, adding it if new. + """ + ## @todo move this and make a stored procedure for it. + self._oDb.execute('SELECT idStr\n' + 'FROM TestResultStrTab\n' + 'WHERE sValue = %s' + , (sString,)); + if self._oDb.getRowCount() == 0: + self._oDb.execute('INSERT INTO TestResultStrTab (sValue)\n' + 'VALUES (%s)\n' + 'RETURNING idStr\n' + , (sString,)); + if fCommit: + self._oDb.commit(); + return self._oDb.fetchOne()[0]; + + def complete(self, idTestSet, sStatus, fCommit = False): + """ + Completes the testset. + Returns the test set ID of the gang leader, None if no gang involvement. + Raises exceptions on database errors and invalid input. + """ + + assert sStatus != TestSetData.ksTestStatus_Running; + + # + # Get the basic test set data and check if there is anything to do here. + # + oData = TestSetData().initFromDbWithId(self._oDb, idTestSet); + if oData.enmStatus != TestSetData.ksTestStatus_Running: + raise TMExceptionBase('TestSet %s is already completed as %s.' % (idTestSet, oData.enmStatus)); + if oData.idTestResult is None: + raise self._oDb.integrityException('idTestResult is NULL for TestSet %u' % (idTestSet,)); + + # + # Close open sub test results, count these as errors. + # Note! No need to propagate error counts here. Only one tree line will + # have open sets, and it will go all the way to the root. + # + self._oDb.execute('SELECT idTestResult\n' + 'FROM TestResults\n' + 'WHERE idTestSet = %s\n' + ' AND enmStatus = %s\n' + ' AND idTestResult <> %s\n' + 'ORDER BY idTestResult DESC\n' + , (idTestSet, TestSetData.ksTestStatus_Running, oData.idTestResult)); + aaoRows = self._oDb.fetchAll(); + if aaoRows: + idStr = self.strTabString('Unclosed test result', fCommit = fCommit); + for aoRow in aaoRows: + self._oDb.execute('UPDATE TestResults\n' + 'SET enmStatus = \'failure\',\n' + ' tsElapsed = CURRENT_TIMESTAMP - tsCreated,\n' + ' cErrors = cErrors + 1\n' + 'WHERE idTestResult = %s\n' + , (aoRow[0],)); + self._oDb.execute('INSERT INTO TestResultMsgs (idTestResult, idTestSet, idStrMsg, enmLevel)\n' + 'VALUES ( %s, %s, %s, \'failure\'::TestResultMsgLevel_T)\n' + , (aoRow[0], idTestSet, idStr,)); + + # + # If it's a success result, check it against error counters. + # + if sStatus not in TestSetData.kasBadTestStatuses: + self._oDb.execute('SELECT COUNT(*)\n' + 'FROM TestResults\n' + 'WHERE idTestSet = %s\n' + ' AND cErrors > 0\n' + , (idTestSet,)); + cErrors = self._oDb.fetchOne()[0]; + if cErrors > 0: + sStatus = TestSetData.ksTestStatus_Failure; + + # + # If it's an pure 'failure', check for timeouts and propagate it. + # + if sStatus == TestSetData.ksTestStatus_Failure: + self._oDb.execute('SELECT COUNT(*)\n' + 'FROM TestResults\n' + 'WHERE idTestSet = %s\n' + ' AND enmStatus = %s\n' + , ( idTestSet, TestSetData.ksTestStatus_TimedOut, )); + if self._oDb.fetchOne()[0] > 0: + sStatus = TestSetData.ksTestStatus_TimedOut; + + # + # Complete the top level test result and then the test set. + # + self._oDb.execute('UPDATE TestResults\n' + 'SET cErrors = (SELECT COALESCE(SUM(cErrors), 0)\n' + ' FROM TestResults\n' + ' WHERE idTestResultParent = %s)\n' + 'WHERE idTestResult = %s\n' + 'RETURNING cErrors\n' + , (oData.idTestResult, oData.idTestResult)); + cErrors = self._oDb.fetchOne()[0]; + if cErrors == 0 and sStatus in TestSetData.kasBadTestStatuses: + self._oDb.execute('UPDATE TestResults\n' + 'SET cErrors = 1\n' + 'WHERE idTestResult = %s\n' + , (oData.idTestResult,)); + elif cErrors > 0 and sStatus not in TestSetData.kasBadTestStatuses: + sStatus = TestSetData.ksTestStatus_Failure; # Impossible. + self._oDb.execute('UPDATE TestResults\n' + 'SET enmStatus = %s,\n' + ' tsElapsed = CURRENT_TIMESTAMP - tsCreated\n' + 'WHERE idTestResult = %s\n' + , (sStatus, oData.idTestResult,)); + + self._oDb.execute('UPDATE TestSets\n' + 'SET enmStatus = %s,\n' + ' tsDone = CURRENT_TIMESTAMP\n' + 'WHERE idTestSet = %s\n' + , (sStatus, idTestSet,)); + + self._oDb.maybeCommit(fCommit); + return oData.idTestSetGangLeader; + + def completeAsAbandoned(self, idTestSet, fCommit = False): + """ + Completes the testset as abandoned if necessary. + + See scenario #9: + file://../../docs/AutomaticTestingRevamp.html#cleaning-up-abandond-testcase + + Returns True if successfully completed as abandond, False if it's already + completed, and raises exceptions under exceptional circumstances. + """ + + # + # Get the basic test set data and check if there is anything to do here. + # + oData = self.tryFetch(idTestSet); + if oData is None: + return False; + if oData.enmStatus != TestSetData.ksTestStatus_Running: + return False; + + if oData.idTestResult is not None: + # + # Clean up test results, adding a message why they failed. + # + self._oDb.execute('UPDATE TestResults\n' + 'SET enmStatus = \'failure\',\n' + ' tsElapsed = CURRENT_TIMESTAMP - tsCreated,\n' + ' cErrors = cErrors + 1\n' + 'WHERE idTestSet = %s\n' + ' AND enmStatus = \'running\'::TestStatus_T\n' + , (idTestSet,)); + + idStr = self.strTabString('The test was abandond by the testbox', fCommit = fCommit); + self._oDb.execute('INSERT INTO TestResultMsgs (idTestResult, idTestSet, idStrMsg, enmLevel)\n' + 'VALUES ( %s, %s, %s, \'failure\'::TestResultMsgLevel_T)\n' + , (oData.idTestResult, idTestSet, idStr,)); + + # + # Complete the testset. + # + self._oDb.execute('UPDATE TestSets\n' + 'SET enmStatus = \'failure\',\n' + ' tsDone = CURRENT_TIMESTAMP\n' + 'WHERE idTestSet = %s\n' + ' AND enmStatus = \'running\'::TestStatus_T\n' + , (idTestSet,)); + + self._oDb.maybeCommit(fCommit); + return True; + + def completeAsGangGatheringTimeout(self, idTestSet, fCommit = False): + """ + Completes the testset with a gang-gathering timeout. + Raises exceptions on database errors and invalid input. + """ + # + # Get the basic test set data and check if there is anything to do here. + # + oData = TestSetData().initFromDbWithId(self._oDb, idTestSet); + if oData.enmStatus != TestSetData.ksTestStatus_Running: + raise TMExceptionBase('TestSet %s is already completed as %s.' % (idTestSet, oData.enmStatus)); + if oData.idTestResult is None: + raise self._oDb.integrityException('idTestResult is NULL for TestSet %u' % (idTestSet,)); + + # + # Complete the top level test result and then the test set. + # + self._oDb.execute('UPDATE TestResults\n' + 'SET enmStatus = \'failure\',\n' + ' tsElapsed = CURRENT_TIMESTAMP - tsCreated,\n' + ' cErrors = cErrors + 1\n' + 'WHERE idTestSet = %s\n' + ' AND enmStatus = \'running\'::TestStatus_T\n' + , (idTestSet,)); + + idStr = self.strTabString('Gang gathering timed out', fCommit = fCommit); + self._oDb.execute('INSERT INTO TestResultMsgs (idTestResult, idTestSet, idStrMsg, enmLevel)\n' + 'VALUES ( %s, %s, %s, \'failure\'::TestResultMsgLevel_T)\n' + , (oData.idTestResult, idTestSet, idStr,)); + + self._oDb.execute('UPDATE TestSets\n' + 'SET enmStatus = \'failure\',\n' + ' tsDone = CURRENT_TIMESTAMP\n' + 'WHERE idTestSet = %s\n' + , (idTestSet,)); + + self._oDb.maybeCommit(fCommit); + return True; + + def createFile(self, oTestSet, sName, sMime, sKind, sDesc, cbFile, fCommit = False): # pylint: disable=too-many-locals + """ + Creates a file and associating with the current test result record in + the test set. + + Returns file object that the file content can be written to. + Raises exception on database error, I/O errors, if there are too many + files in the test set or if they take up too much disk space. + + The caller (testboxdisp.py) is expected to do basic input validation, + so we skip that and get on with the bits only we can do. + """ + + # + # Furhter input and limit checks. + # + if oTestSet.enmStatus != TestSetData.ksTestStatus_Running: + raise TMExceptionBase('Cannot create files on a test set with status "%s".' % (oTestSet.enmStatus,)); + + self._oDb.execute('SELECT TestResultStrTab.sValue\n' + 'FROM TestResultFiles,\n' + ' TestResults,\n' + ' TestResultStrTab\n' + 'WHERE TestResults.idTestSet = %s\n' + ' AND TestResultFiles.idTestResult = TestResults.idTestResult\n' + ' AND TestResultStrTab.idStr = TestResultFiles.idStrFile\n' + , ( oTestSet.idTestSet,)); + if self._oDb.getRowCount() + 1 > config.g_kcMaxUploads: + raise TMExceptionBase('Uploaded too many files already (%d).' % (self._oDb.getRowCount(),)); + + dFiles = {} + cbTotalFiles = 0; + for aoRow in self._oDb.fetchAll(): + dFiles[aoRow[0].lower()] = 1; # For determining a unique filename further down. + sFile = os.path.join(config.g_ksFileAreaRootDir, oTestSet.sBaseFilename + '-' + aoRow[0]); + try: + cbTotalFiles += os.path.getsize(sFile); + except: + cbTotalFiles += config.g_kcMbMaxUploadSingle * 1048576; + if (cbTotalFiles + cbFile + 1048575) / 1048576 > config.g_kcMbMaxUploadTotal: + raise TMExceptionBase('Will exceed total upload limit: %u bytes + %u bytes > %s MiB.' \ + % (cbTotalFiles, cbFile, config.g_kcMbMaxUploadTotal)); + + # + # Create a new file. + # + self._oDb.execute('SELECT idTestResult\n' + 'FROM TestResults\n' + 'WHERE idTestSet = %s\n' + ' AND enmStatus = \'running\'::TestStatus_T\n' + 'ORDER BY idTestResult DESC\n' + 'LIMIT 1\n' + % ( oTestSet.idTestSet, )); + if self._oDb.getRowCount() < 1: + raise TMExceptionBase('No open test results - someone committed a capital offence or we ran into a race.'); + idTestResult = self._oDb.fetchOne()[0]; + + if sName.lower() in dFiles: + # Note! There is in theory a race here, but that's something the + # test driver doing parallel upload with non-unique names + # should worry about. The TD should always avoid this path. + sOrgName = sName; + for i in range(2, config.g_kcMaxUploads + 6): + sName = '%s-%s' % (i, sName,); + if sName not in dFiles: + break; + sName = None; + if sName is None: + raise TMExceptionBase('Failed to find unique name for %s.' % (sOrgName,)); + + self._oDb.execute('INSERT INTO TestResultFiles(idTestResult, idTestSet, idStrFile, idStrDescription,\n' + ' idStrKind, idStrMime)\n' + 'VALUES (%s, %s, %s, %s, %s, %s)\n' + , ( idTestResult, + oTestSet.idTestSet, + self.strTabString(sName), + self.strTabString(sDesc), + self.strTabString(sKind), + self.strTabString(sMime), + )); + + oFile = oTestSet.createFile(sName, 'wb'); + if utils.isString(oFile): + raise TMExceptionBase('Error creating "%s": %s' % (sName, oFile)); + self._oDb.maybeCommit(fCommit); + return oFile; + + def getGang(self, idTestSetGangLeader): + """ + Returns an array of TestBoxData object representing the gang for the given testset. + """ + self._oDb.execute('SELECT TestBoxesWithStrings.*\n' + 'FROM TestBoxesWithStrings,\n' + ' TestSets' + 'WHERE TestSets.idTestSetGangLeader = %s\n' + ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox\n' + 'ORDER BY iGangMemberNo ASC\n' + , ( idTestSetGangLeader,)); + aaoRows = self._oDb.fetchAll(); + aoTestBoxes = []; + for aoRow in aaoRows: + aoTestBoxes.append(TestBoxData().initFromDbRow(aoRow)); + return aoTestBoxes; + + def getFile(self, idTestSet, idTestResultFile): + """ + Gets the TestResultFileEx corresponding to idTestResultFile. + + Raises an exception if the file wasn't found, doesn't belong to + idTestSet, and on DB error. + """ + self._oDb.execute('SELECT TestResultFiles.*,\n' + ' StrTabFile.sValue AS sFile,\n' + ' StrTabDesc.sValue AS sDescription,\n' + ' StrTabKind.sValue AS sKind,\n' + ' StrTabMime.sValue AS sMime\n' + 'FROM TestResultFiles,\n' + ' TestResultStrTab AS StrTabFile,\n' + ' TestResultStrTab AS StrTabDesc,\n' + ' TestResultStrTab AS StrTabKind,\n' + ' TestResultStrTab AS StrTabMime,\n' + ' TestResults\n' + 'WHERE TestResultFiles.idTestResultFile = %s\n' + ' AND TestResultFiles.idStrFile = StrTabFile.idStr\n' + ' AND TestResultFiles.idStrDescription = StrTabDesc.idStr\n' + ' AND TestResultFiles.idStrKind = StrTabKind.idStr\n' + ' AND TestResultFiles.idStrMime = StrTabMime.idStr\n' + ' AND TestResults.idTestResult = TestResultFiles.idTestResult\n' + ' AND TestResults.idTestSet = %s\n' + , ( idTestResultFile, idTestSet, )); + return TestResultFileDataEx().initFromDbRow(self._oDb.fetchOne()); + + + def getById(self, idTestSet): + """ + Get TestSet table record by its id + """ + self._oDb.execute('SELECT *\n' + 'FROM TestSets\n' + 'WHERE idTestSet=%s\n', + (idTestSet,)) + + aRows = self._oDb.fetchAll() + if len(aRows) not in (0, 1): + raise TMTooManyRows('Found more than one test sets with the same credentials. Database structure is corrupted.') + try: + return TestSetData().initFromDbRow(aRows[0]) + except IndexError: + return None + + + def fetchOrphaned(self): + """ + Returns a list of TestSetData objects of orphaned test sets. + + A test set is orphaned if tsDone is NULL and the testbox has created + one or more newer testsets. + """ + + self._oDb.execute('SELECT TestSets.*\n' + 'FROM TestSets,\n' + ' (SELECT idTestSet, idTestBox FROM TestSets WHERE tsDone is NULL) AS t\n' + 'WHERE TestSets.idTestSet = t.idTestSet\n' + ' AND EXISTS(SELECT 1 FROM TestSets st\n' + ' WHERE st.idTestBox = t.idTestBox AND st.idTestSet > t.idTestSet)\n' + ' AND NOT EXISTS(SELECT 1 FROM TestBoxStatuses tbs\n' + ' WHERE tbs.idTestBox = t.idTestBox AND tbs.idTestSet = t.idTestSet)\n' + 'ORDER by TestSets.idTestBox, TestSets.idTestSet' + ); + aoRet = []; + for aoRow in self._oDb.fetchAll(): + aoRet.append(TestSetData().initFromDbRow(aoRow)); + return aoRet; + + def fetchByAge(self, tsNow = None, cHoursBack = 24): + """ + Returns a list of TestSetData objects of a given time period (default is 24 hours). + + Returns None if no testsets stored, + Returns an empty list if no testsets found with given criteria. + """ + if tsNow is None: + tsNow = self._oDb.getCurrentTimestamp(); + + if self._oDb.getRowCount() == 0: + return None; + + self._oDb.execute('(SELECT *\n' + ' FROM TestSets\n' + ' WHERE tsDone <= %s\n' + ' AND tsDone > (%s - interval \'%s hours\')\n' + ')\n' + , ( tsNow, tsNow, cHoursBack, )); + + aoRet = []; + for aoRow in self._oDb.fetchAll(): + aoRet.append(TestSetData().initFromDbRow(aoRow)); + return aoRet; + + def isTestBoxExecutingTooRapidly(self, idTestBox): ## s/To/Too/ + """ + Checks whether the specified test box is executing tests too rapidly. + + The parameters defining too rapid execution are defined in config.py. + + Returns True if it does, False if it doesn't. + May raise database problems. + """ + + self._oDb.execute('(\n' + 'SELECT tsCreated\n' + 'FROM TestSets\n' + 'WHERE idTestBox = %s\n' + ' AND tsCreated >= (CURRENT_TIMESTAMP - interval \'%s seconds\')\n' + ') UNION (\n' + 'SELECT tsCreated\n' + 'FROM TestSets\n' + 'WHERE idTestBox = %s\n' + ' AND tsCreated >= (CURRENT_TIMESTAMP - interval \'%s seconds\')\n' + ' AND enmStatus >= \'failure\'\n' + ')' + , ( idTestBox, config.g_kcSecMinSinceLastTask, + idTestBox, config.g_kcSecMinSinceLastFailedTask, )); + return self._oDb.getRowCount() > 0; + + + # + # The virtual test sheriff interface. + # + + def fetchBadTestBoxIds(self, cHoursBack = 2, tsNow = None, aidFailureReasons = None): + """ + Fetches a list of test box IDs which returned bad-testbox statuses in the + given period (tsDone). + """ + if tsNow is None: + tsNow = self._oDb.getCurrentTimestamp(); + if aidFailureReasons is None: + aidFailureReasons = [ -1, ]; + self._oDb.execute('(SELECT idTestBox\n' + ' FROM TestSets\n' + ' WHERE TestSets.enmStatus = \'bad-testbox\'\n' + ' AND tsDone <= %s\n' + ' AND tsDone > (%s - interval \'%s hours\')\n' + ') UNION (\n' + ' SELECT TestSets.idTestBox\n' + ' FROM TestSets,\n' + ' TestResultFailures\n' + ' WHERE TestSets.tsDone <= %s\n' + ' AND TestSets.tsDone > (%s - interval \'%s hours\')\n' + ' AND TestSets.enmStatus >= \'failure\'::TestStatus_T\n' + ' AND TestSets.idTestSet = TestResultFailures.idTestSet\n' + ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND TestResultFailures.idFailureReason IN (' + + ', '.join([str(i) for i in aidFailureReasons]) + ')\n' + ')\n' + , ( tsNow, tsNow, cHoursBack, + tsNow, tsNow, cHoursBack, )); + return [aoRow[0] for aoRow in self._oDb.fetchAll()]; + + def fetchSetsForTestBox(self, idTestBox, cHoursBack = 2, tsNow = None): + """ + Fetches the TestSet rows for idTestBox for the given period (tsDone), w/o running ones. + + Returns list of TestSetData sorted by tsDone in descending order. + """ + if tsNow is None: + tsNow = self._oDb.getCurrentTimestamp(); + self._oDb.execute('SELECT *\n' + 'FROM TestSets\n' + 'WHERE TestSets.idTestBox = %s\n' + ' AND tsDone IS NOT NULL\n' + ' AND tsDone <= %s\n' + ' AND tsDone > (%s - interval \'%s hours\')\n' + 'ORDER by tsDone DESC\n' + , ( idTestBox, tsNow, tsNow, cHoursBack,)); + return self._dbRowsToModelDataList(TestSetData); + + def fetchFailedSetsWithoutReason(self, cHoursBack = 2, tsNow = None): + """ + Fetches the TestSet failure rows without any currently (CURRENT_TIMESTAMP + not tsNow) assigned failure reason. + + Returns list of TestSetData sorted by tsDone in descending order. + + Note! Includes bad-testbox sets too as it can be useful to analyze these + too even if we normally count them in the 'skipped' category. + """ + if tsNow is None: + tsNow = self._oDb.getCurrentTimestamp(); + self._oDb.execute('SELECT TestSets.*\n' + 'FROM TestSets\n' + ' LEFT OUTER JOIN TestResultFailures\n' + ' ON TestResultFailures.idTestSet = TestSets.idTestSet\n' + ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP\n' + 'WHERE TestSets.tsDone IS NOT NULL\n' + ' AND TestSets.enmStatus IN ( %s, %s, %s, %s )\n' + ' AND TestSets.tsDone <= %s\n' + ' AND TestSets.tsDone > (%s - interval \'%s hours\')\n' + ' AND TestResultFailures.idTestSet IS NULL\n' + 'ORDER by tsDone DESC\n' + , ( TestSetData.ksTestStatus_Failure, TestSetData.ksTestStatus_TimedOut, + TestSetData.ksTestStatus_Rebooted, TestSetData.ksTestStatus_BadTestBox, + tsNow, + tsNow, cHoursBack,)); + return self._dbRowsToModelDataList(TestSetData); + + + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +class TestSetDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [TestSetData(),]; + +if __name__ == '__main__': + unittest.main(); + # not reached. diff --git a/src/VBox/ValidationKit/testmanager/core/useraccount.pgsql b/src/VBox/ValidationKit/testmanager/core/useraccount.pgsql new file mode 100644 index 00000000..1e76e648 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/useraccount.pgsql @@ -0,0 +1,178 @@ +-- $Id: useraccount.pgsql $ +--- @file +-- VBox Test Manager Database Stored Procedures - UserAccounts. +-- + +-- +-- Copyright (C) 2012-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + +\set ON_ERROR_STOP 1 +\connect testmanager; + +--- +-- Checks if the user name and login name are unique, ignoring a_uidIgnore. +-- Raises exception if duplicates are found. +-- +-- @internal +-- +CREATE OR REPLACE FUNCTION UserAccountLogic_checkUniqueUser(a_sUsername TEXT, a_sLoginName TEXT, a_uidIgnore INTEGER) + RETURNS VOID AS $$ + DECLARE + v_cRows INTEGER; + BEGIN + -- sUserName + SELECT COUNT(*) INTO v_cRows + FROM Users + WHERE sUsername = a_sUsername + AND tsExpire = 'infinity'::TIMESTAMP + AND uid <> a_uidIgnore; + IF v_cRows <> 0 THEN + RAISE EXCEPTION 'Duplicate user name "%" (% times)', a_sUsername, v_cRows; + END IF; + + -- sLoginName + SELECT COUNT(*) INTO v_cRows + FROM Users + WHERE sLoginName = a_sLoginName + AND tsExpire = 'infinity'::TIMESTAMP + AND uid <> a_uidIgnore; + IF v_cRows <> 0 THEN + RAISE EXCEPTION 'Duplicate login name "%" (% times)', a_sUsername, v_cRows; + END IF; + END; +$$ LANGUAGE plpgsql; + +--- +-- Check that the user account exists. +-- Raises exception if it doesn't. +-- +-- @internal +-- +CREATE OR REPLACE FUNCTION UserAccountLogic_checkExists(a_uid INTEGER) RETURNS VOID AS $$ + DECLARE + v_cUpdatedRows INTEGER; + BEGIN + IF NOT EXISTS( SELECT * + FROM Users + WHERE uid = a_uid + AND tsExpire = 'infinity'::TIMESTAMP ) THEN + RAISE EXCEPTION 'User with ID % does not currently exist', a_uid; + END IF; + END; +$$ LANGUAGE plpgsql; + +--- +-- Historize a row. +-- @internal +-- +CREATE OR REPLACE FUNCTION UserAccountLogic_historizeEntry(a_uid INTEGER, a_tsExpire TIMESTAMP WITH TIME ZONE) + RETURNS VOID AS $$ + DECLARE + v_cUpdatedRows INTEGER; + BEGIN + UPDATE Users + SET tsExpire = a_tsExpire + WHERE uid = a_uid + AND tsExpire = 'infinity'::TIMESTAMP; + GET DIAGNOSTICS v_cUpdatedRows = ROW_COUNT; + IF v_cUpdatedRows <> 1 THEN + IF v_cUpdatedRows = 0 THEN + RAISE EXCEPTION 'User with ID % does not currently exist', a_uid; + END IF; + RAISE EXCEPTION 'Integrity error in UserAccounts: % current rows with uid=%d', v_cUpdatedRows, a_uid; + END IF; + END; +$$ LANGUAGE plpgsql; + + +--- +-- Adds a new user. +-- +CREATE OR REPLACE FUNCTION UserAccountLogic_addEntry(a_uidAuthor INTEGER, a_sUsername TEXT, a_sEmail TEXT, a_sFullName TEXT, + a_sLoginName TEXT, a_fReadOnly BOOLEAN) + RETURNS VOID AS $$ + DECLARE + v_cRows INTEGER; + BEGIN + PERFORM UserAccountLogic_checkUniqueUser(a_sUsername, a_sLoginName, -1); + INSERT INTO Users(uidAuthor, sUsername, sEmail, sFullName, sLoginName) + VALUES (a_uidAuthor, a_sUsername, a_sEmail, a_sFullName, a_sLoginName); + END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION UserAccountLogic_editEntry(a_uidAuthor INTEGER, a_uid INTEGER, a_sUsername TEXT, a_sEmail TEXT, + a_sFullName TEXT, a_sLoginName TEXT, a_fReadOnly BOOLEAN) + RETURNS VOID AS $$ + BEGIN + PERFORM UserAccountLogic_checkExists(a_uid); + PERFORM UserAccountLogic_checkUniqueUser(a_sUsername, a_sLoginName, a_uid); + + PERFORM UserAccountLogic_historizeEntry(a_uid, CURRENT_TIMESTAMP); + INSERT INTO Users (uid, uidAuthor, sUsername, sEmail, sFullName, sLoginName, fReadOnly) + VALUES (a_uid, a_uidAuthor, a_sUsername, a_sEmail, a_sFullName, a_sLoginName, a_fReadOnly); + END; +$$ LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION UserAccountLogic_delEntry(a_uidAuthor INTEGER, a_uid INTEGER) RETURNS VOID AS $$ + DECLARE + v_Row Users%ROWTYPE; + v_tsEffective TIMESTAMP WITH TIME ZONE; + BEGIN + -- + -- To preserve the information about who deleted the record, we try to + -- add a dummy record which expires immediately. I say try because of + -- the primary key, we must let the new record be valid for 1 us. :-( + -- + + SELECT * INTO STRICT v_Row + FROM Users + WHERE uid = a_uid + AND tsExpire = 'infinity'::TIMESTAMP; + + v_tsEffective := CURRENT_TIMESTAMP - INTERVAL '1 microsecond'; + IF v_Row.tsEffective < v_tsEffective THEN + PERFORM UserAccountLogic_historizeEntry(a_uid, v_tsEffective); + v_Row.tsEffective = v_tsEffective; + v_Row.tsExpire = CURRENT_TIMESTAMP; + INSERT INTO Users VALUES (v_Row.*); + ELSE + PERFORM UserAccountLogic_historizeEntry(a_uid, CURRENT_TIMESTAMP); + END IF; + + EXCEPTION + WHEN NO_DATA_FOUND THEN + RAISE EXCEPTION 'User with ID % does not currently exist', a_uid; + WHEN TOO_MANY_ROWS THEN + RAISE EXCEPTION 'Integrity error in UserAccounts: Too many current rows for %', a_uid; + END; +$$ LANGUAGE plpgsql; + diff --git a/src/VBox/ValidationKit/testmanager/core/useraccount.py b/src/VBox/ValidationKit/testmanager/core/useraccount.py new file mode 100755 index 00000000..db8f8099 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/useraccount.py @@ -0,0 +1,302 @@ +# -*- coding: utf-8 -*- +# $Id: useraccount.py $ + +""" +Test Manager - User DB records management. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import unittest; + +# Validation Kit imports. +from testmanager import config; +from testmanager.core.base import ModelDataBase, ModelLogicBase, ModelDataBaseTestCase, TMTooManyRows, TMRowNotFound; + + +class UserAccountData(ModelDataBase): + """ + User account data + """ + + ksIdAttr = 'uid'; + + ksParam_uid = 'UserAccount_uid' + ksParam_tsExpire = 'UserAccount_tsExpire' + ksParam_tsEffective = 'UserAccount_tsEffective' + ksParam_uidAuthor = 'UserAccount_uidAuthor' + ksParam_sLoginName = 'UserAccount_sLoginName' + ksParam_sUsername = 'UserAccount_sUsername' + ksParam_sEmail = 'UserAccount_sEmail' + ksParam_sFullName = 'UserAccount_sFullName' + ksParam_fReadOnly = 'UserAccount_fReadOnly' + + kasAllowNullAttributes = ['uid', 'tsEffective', 'tsExpire', 'uidAuthor']; + + + def __init__(self): + """Init parameters""" + ModelDataBase.__init__(self); + self.uid = None; + self.tsEffective = None; + self.tsExpire = None; + self.uidAuthor = None; + self.sUsername = None; + self.sEmail = None; + self.sFullName = None; + self.sLoginName = None; + self.fReadOnly = None; + + def initFromDbRow(self, aoRow): + """ + Init from database table row + Returns self. Raises exception of the row is None. + """ + if aoRow is None: + raise TMRowNotFound('User not found.'); + + self.uid = aoRow[0]; + self.tsEffective = aoRow[1]; + self.tsExpire = aoRow[2]; + self.uidAuthor = aoRow[3]; + self.sUsername = aoRow[4]; + self.sEmail = aoRow[5]; + self.sFullName = aoRow[6]; + self.sLoginName = aoRow[7]; + self.fReadOnly = aoRow[8]; + return self; + + def initFromDbWithId(self, oDb, uid, tsNow = None, sPeriodBack = None): + """ + Initialize the object from the database. + """ + oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb, + 'SELECT *\n' + 'FROM Users\n' + 'WHERE uid = %s\n' + , ( uid, ), tsNow, sPeriodBack)); + aoRow = oDb.fetchOne() + if aoRow is None: + raise TMRowNotFound('uid=%s not found (tsNow=%s sPeriodBack=%s)' % (uid, tsNow, sPeriodBack,)); + return self.initFromDbRow(aoRow); + + def _validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb): + # Custom handling of the email field. + if sAttr == 'sEmail': + return ModelDataBase.validateEmail(oValue, aoNilValues = aoNilValues, fAllowNull = fAllowNull); + + # Automatically lowercase the login name if we're supposed to do case + # insensitive matching. (The feature assumes lower case in DB.) + if sAttr == 'sLoginName' and oValue is not None and config.g_kfLoginNameCaseInsensitive: + oValue = oValue.lower(); + + return ModelDataBase._validateAndConvertAttribute(self, sAttr, sParam, oValue, aoNilValues, fAllowNull, oDb); + + +class UserAccountLogic(ModelLogicBase): + """ + User account logic (for the Users table). + """ + + def __init__(self, oDb): + ModelLogicBase.__init__(self, oDb) + self.dCache = None; + + def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None): + """ + Fetches user accounts. + + Returns an array (list) of UserAccountData items, empty list if none. + Raises exception on error. + """ + _ = aiSortColumns; + if tsNow is None: + self._oDb.execute('SELECT *\n' + 'FROM Users\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + 'ORDER BY sUsername DESC\n' + 'LIMIT %s OFFSET %s\n' + , (cMaxRows, iStart,)); + else: + self._oDb.execute('SELECT *\n' + 'FROM Users\n' + 'WHERE tsExpire > %s\n' + ' AND tsEffective <= %s\n' + 'ORDER BY sUsername DESC\n' + 'LIMIT %s OFFSET %s\n' + , (tsNow, tsNow, cMaxRows, iStart,)); + + aoRows = []; + for _ in range(self._oDb.getRowCount()): + aoRows.append(UserAccountData().initFromDbRow(self._oDb.fetchOne())); + return aoRows; + + def addEntry(self, oData, uidAuthor, fCommit = False): + """ + Add user account entry to the DB. + """ + self._oDb.callProc('UserAccountLogic_addEntry', + (uidAuthor, oData.sUsername, oData.sEmail, oData.sFullName, oData.sLoginName, oData.fReadOnly)); + self._oDb.maybeCommit(fCommit); + return True; + + def editEntry(self, oData, uidAuthor, fCommit = False): + """ + Modify user account. + """ + self._oDb.callProc('UserAccountLogic_editEntry', + ( uidAuthor, oData.uid, oData.sUsername, oData.sEmail, + oData.sFullName, oData.sLoginName, oData.fReadOnly)); + self._oDb.maybeCommit(fCommit); + return True; + + def removeEntry(self, uidAuthor, uid, fCascade = False, fCommit = False): + """ + Delete user account + """ + self._oDb.callProc('UserAccountLogic_delEntry', (uidAuthor, uid)); + self._oDb.maybeCommit(fCommit); + _ = fCascade; + return True; + + def _getByField(self, sField, sValue): + """ + Get user account record by its field value + """ + self._oDb.execute('SELECT *\n' + 'FROM Users\n' + 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' + ' AND ' + sField + ' = %s' + , (sValue,)) + + aRows = self._oDb.fetchAll() + if len(aRows) not in (0, 1): + raise TMTooManyRows('Found more than one user account with the same credentials. Database structure is corrupted.') + + try: + return aRows[0] + except IndexError: + return [] + + def getById(self, idUserId): + """ + Get user account information by ID. + """ + return self._getByField('uid', idUserId) + + def tryFetchAccountByLoginName(self, sLoginName): + """ + Try get user account information by login name. + + Returns UserAccountData if found, None if not. + Raises exception on DB error. + """ + if config.g_kfLoginNameCaseInsensitive: + sLoginName = sLoginName.lower(); + + self._oDb.execute('SELECT *\n' + 'FROM Users\n' + 'WHERE sLoginName = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (sLoginName, )); + if self._oDb.getRowCount() != 1: + if self._oDb.getRowCount() != 0: + raise self._oDb.integrityException('%u rows in Users with sLoginName="%s"' + % (self._oDb.getRowCount(), sLoginName)); + return None; + return UserAccountData().initFromDbRow(self._oDb.fetchOne()); + + def cachedLookup(self, uid): + """ + Looks up the current UserAccountData object for uid via an object cache. + + Returns a shared UserAccountData object. None if not found. + Raises exception on DB error. + """ + if self.dCache is None: + self.dCache = self._oDb.getCache('UserAccount'); + + oUser = self.dCache.get(uid, None); + if oUser is None: + self._oDb.execute('SELECT *\n' + 'FROM Users\n' + 'WHERE uid = %s\n' + ' AND tsExpire = \'infinity\'::TIMESTAMP\n' + , (uid, )); + if self._oDb.getRowCount() == 0: + # Maybe it was deleted, try get the last entry. + self._oDb.execute('SELECT *\n' + 'FROM Users\n' + 'WHERE uid = %s\n' + 'ORDER BY tsExpire DESC\n' + 'LIMIT 1\n' + , (uid, )); + elif self._oDb.getRowCount() > 1: + raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), uid)); + + if self._oDb.getRowCount() == 1: + oUser = UserAccountData().initFromDbRow(self._oDb.fetchOne()); + self.dCache[uid] = oUser; + return oUser; + + def resolveChangeLogAuthors(self, aoEntries): + """ + Given an array of ChangeLogEntry instances, set sAuthor to whatever + uidAuthor resolves to. + + Returns aoEntries. + Raises exception on DB error. + """ + for oEntry in aoEntries: + oUser = self.cachedLookup(oEntry.uidAuthor) + if oUser is not None: + oEntry.sAuthor = oUser.sUsername; + return aoEntries; + + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +class UserAccountDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [UserAccountData(),]; + +if __name__ == '__main__': + unittest.main(); + # not reached. + diff --git a/src/VBox/ValidationKit/testmanager/core/vcsbugreference.py b/src/VBox/ValidationKit/testmanager/core/vcsbugreference.py new file mode 100755 index 00000000..52f05521 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/vcsbugreference.py @@ -0,0 +1,251 @@ +# -*- coding: utf-8 -*- +# $Id: vcsbugreference.py $ + +""" +Test Manager - VcsBugReferences +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import unittest; + +# Validation Kit imports. +from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMExceptionBase; + + +class VcsBugReferenceData(ModelDataBase): + """ + A version control system (VCS) bug tracker reference (commit message tag). + """ + + #kasIdAttr = ['sRepository','iRevision', 'sBugTracker', 'iBugNo']; + + ksParam_sRepository = 'VcsBugReference_sRepository'; + ksParam_iRevision = 'VcsBugReference_iRevision'; + ksParam_sBugTracker = 'VcsBugReference_sBugTracker'; + ksParam_lBugNo = 'VcsBugReference_lBugNo'; + + kasAllowNullAttributes = [ ]; + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.sRepository = None; + self.iRevision = None; + self.sBugTracker = None; + self.lBugNo = None; + + def initFromDbRow(self, aoRow): + """ + Re-initializes the object from a SELECT * FROM VcsBugReferences row. + Returns self. Raises exception if aoRow is None. + """ + if aoRow is None: + raise TMExceptionBase('VcsBugReference not found.'); + + self.sRepository = aoRow[0]; + self.iRevision = aoRow[1]; + self.sBugTracker = aoRow[2]; + self.lBugNo = aoRow[3]; + return self; + + def initFromValues(self, sRepository, iRevision, sBugTracker, lBugNo): + """ + Reinitializes form a set of values. + return self. + """ + self.sRepository = sRepository; + self.iRevision = iRevision; + self.sBugTracker = sBugTracker; + self.lBugNo = lBugNo; + return self; + + +class VcsBugReferenceDataEx(VcsBugReferenceData): + """ + Extended version of VcsBugReferenceData that includes the commit details. + """ + def __init__(self): + VcsBugReferenceData.__init__(self); + self.tsCreated = None; + self.sAuthor = None; + self.sMessage = None; + + def initFromDbRow(self, aoRow): + VcsBugReferenceData.initFromDbRow(self, aoRow); + self.tsCreated = aoRow[4]; + self.sAuthor = aoRow[5]; + self.sMessage = aoRow[6]; + return self; + + +class VcsBugReferenceLogic(ModelLogicBase): # pylint: disable=too-few-public-methods + """ + VCS revision <-> bug tracker references database logic. + """ + + # + # Standard methods. + # + + def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None): + """ + Fetches VCS revisions for listing. + + Returns an array (list) of VcsBugReferenceData items, empty list if none. + Raises exception on error. + """ + _ = tsNow; _ = aiSortColumns; + self._oDb.execute(''' +SELECT * +FROM VcsBugReferences +ORDER BY sRepository, iRevision, sBugTracker, lBugNo +LIMIT %s OFFSET %s +''', (cMaxRows, iStart,)); + + aoRows = []; + for _ in range(self._oDb.getRowCount()): + aoRows.append(VcsBugReferenceData().initFromDbRow(self._oDb.fetchOne())); + return aoRows; + + def exists(self, oData): + """ + Checks if the data is already present in the DB. + Returns True / False. + Raises exception on input and database errors. + """ + self._oDb.execute(''' +SELECT COUNT(*) +FROM VcsBugReferences +WHERE sRepository = %s + AND iRevision = %s + AND sBugTracker = %s + AND lBugNo = %s +''', ( oData.sRepository, oData.iRevision, oData.sBugTracker, oData.lBugNo)); + cRows = self._oDb.fetchOne()[0]; + if cRows < 0 or cRows > 1: + raise TMExceptionBase('VcsBugReferences has a primary key problem: %u duplicates' % (cRows,)); + return cRows != 0; + + + # + # Other methods. + # + + def addVcsBugReference(self, oData, fCommit = False): + """ + Adds (or updates) a tree revision record. + Raises exception on input and database errors. + """ + + # Check VcsBugReferenceData before do anything + dDataErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Add); + if dDataErrors: + raise TMExceptionBase('Invalid data passed to addVcsBugReference(): %s' % (dDataErrors,)); + + # Does it already exist? + if not self.exists(oData): + # New row. + self._oDb.execute('INSERT INTO VcsBugReferences (sRepository, iRevision, sBugTracker, lBugNo)\n' + 'VALUES (%s, %s, %s, %s)\n' + , ( oData.sRepository, + oData.iRevision, + oData.sBugTracker, + oData.lBugNo, + )); + + self._oDb.maybeCommit(fCommit); + return oData; + + def getLastRevision(self, sRepository): + """ + Get the last known revision number for the given repository, returns 0 + if the repository is not known to us: + """ + self._oDb.execute(''' +SELECT iRevision +FROM VcsBugReferences +WHERE sRepository = %s +ORDER BY iRevision DESC +LIMIT 1 +''', ( sRepository, )); + if self._oDb.getRowCount() == 0: + return 0; + return self._oDb.fetchOne()[0]; + + def fetchForBug(self, sBugTracker, lBugNo): + """ + Fetches VCS revisions for a bug. + + Returns an array (list) of VcsBugReferenceDataEx items, empty list if none. + Raises exception on error. + """ + self._oDb.execute(''' +SELECT VcsBugReferences.*, + VcsRevisions.tsCreated, + VcsRevisions.sAuthor, + VcsRevisions.sMessage +FROM VcsBugReferences +LEFT OUTER JOIN VcsRevisions ON ( VcsRevisions.sRepository = VcsBugReferences.sRepository + AND VcsRevisions.iRevision = VcsBugReferences.iRevision ) +WHERE sBugTracker = %s + AND lBugNo = %s +ORDER BY VcsRevisions.tsCreated, VcsBugReferences.sRepository, VcsBugReferences.iRevision +''', (sBugTracker, lBugNo,)); + + aoRows = []; + for _ in range(self._oDb.getRowCount()): + aoRows.append(VcsBugReferenceDataEx().initFromDbRow(self._oDb.fetchOne())); + return aoRows; + + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +class VcsBugReferenceDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [VcsBugReferenceData(),]; + +if __name__ == '__main__': + unittest.main(); + # not reached. + diff --git a/src/VBox/ValidationKit/testmanager/core/vcsrevisions.py b/src/VBox/ValidationKit/testmanager/core/vcsrevisions.py new file mode 100755 index 00000000..ddfc02ca --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/vcsrevisions.py @@ -0,0 +1,254 @@ +# -*- coding: utf-8 -*- +# $Id: vcsrevisions.py $ + +""" +Test Manager - VcsRevisions +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import unittest; + +# Validation Kit imports. +from testmanager.core.base import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase, TMExceptionBase; + + +class VcsRevisionData(ModelDataBase): + """ + A version control system (VCS) revision. + """ + + #kasIdAttr = ['sRepository',iRevision]; + + ksParam_sRepository = 'VcsRevision_sRepository'; + ksParam_iRevision = 'VcsRevision_iRevision'; + ksParam_tsCreated = 'VcsRevision_tsCreated'; + ksParam_sAuthor = 'VcsRevision_sAuthor'; + ksParam_sMessage = 'VcsRevision_sMessage'; + + kasAllowNullAttributes = [ ]; + kfAllowUnicode_sMessage = True; + kcchMax_sMessage = 8192; + + def __init__(self): + ModelDataBase.__init__(self); + + # + # Initialize with defaults. + # See the database for explanations of each of these fields. + # + self.sRepository = None; + self.iRevision = None; + self.tsCreated = None; + self.sAuthor = None; + self.sMessage = None; + + def initFromDbRow(self, aoRow): + """ + Re-initializes the object from a SELECT * FROM VcsRevisions row. + Returns self. Raises exception if aoRow is None. + """ + if aoRow is None: + raise TMExceptionBase('VcsRevision not found.'); + + self.sRepository = aoRow[0]; + self.iRevision = aoRow[1]; + self.tsCreated = aoRow[2]; + self.sAuthor = aoRow[3]; + self.sMessage = aoRow[4]; + return self; + + def initFromDbWithRepoAndRev(self, oDb, sRepository, iRevision): + """ + Initialize from the database, given the tree and revision of a row. + """ + oDb.execute('SELECT * FROM VcsRevisions WHERE sRepository = %s AND iRevision = %u', (sRepository, iRevision,)); + aoRow = oDb.fetchOne() + if aoRow is None: + raise TMExceptionBase('sRepository = %s iRevision = %u not found' % (sRepository, iRevision, )); + return self.initFromDbRow(aoRow); + + def initFromValues(self, sRepository, iRevision, tsCreated, sAuthor, sMessage): + """ + Reinitializes form a set of values. + return self. + """ + self.sRepository = sRepository; + self.iRevision = iRevision; + self.tsCreated = tsCreated; + self.sAuthor = sAuthor; + self.sMessage = sMessage; + return self; + + +class VcsRevisionLogic(ModelLogicBase): # pylint: disable=too-few-public-methods + """ + VCS revisions database logic. + """ + + # + # Standard methods. + # + + def fetchForListing(self, iStart, cMaxRows, tsNow, aiSortColumns = None): + """ + Fetches VCS revisions for listing. + + Returns an array (list) of VcsRevisionData items, empty list if none. + Raises exception on error. + """ + _ = tsNow; _ = aiSortColumns; + self._oDb.execute('SELECT *\n' + 'FROM VcsRevisions\n' + 'ORDER BY tsCreated, sRepository, iRevision\n' + 'LIMIT %s OFFSET %s\n' + , (cMaxRows, iStart,)); + + aoRows = []; + for _ in range(self._oDb.getRowCount()): + aoRows.append(VcsRevisionData().initFromDbRow(self._oDb.fetchOne())); + return aoRows; + + def tryFetch(self, sRepository, iRevision): + """ + Tries to fetch the specified tree revision record. + Returns VcsRevisionData instance if found, None if not found. + Raises exception on input and database errors. + """ + self._oDb.execute('SELECT * FROM VcsRevisions WHERE sRepository = %s AND iRevision = %s', + ( sRepository, iRevision, )); + aaoRows = self._oDb.fetchAll(); + if len(aaoRows) == 1: + return VcsRevisionData().initFromDbRow(aaoRows[0]); + if aaoRows: + raise TMExceptionBase('VcsRevisions has a primary key problem: %u duplicates' % (len(aaoRows),)); + return None + + + # + # Other methods. + # + + def addVcsRevision(self, oData, fCommit = False): + """ + Adds (or updates) a tree revision record. + Raises exception on input and database errors. + """ + + # Check VcsRevisionData before do anything + dDataErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Add); + if dDataErrors: + raise TMExceptionBase('Invalid data passed to addVcsRevision(): %s' % (dDataErrors,)); + + # Does it already exist? + oOldData = self.tryFetch(oData.sRepository, oData.iRevision); + if oOldData is None: + # New row. + self._oDb.execute('INSERT INTO VcsRevisions (sRepository, iRevision, tsCreated, sAuthor, sMessage)\n' + 'VALUES (%s, %s, %s, %s, %s)\n' + , ( oData.sRepository, + oData.iRevision, + oData.tsCreated, + oData.sAuthor, + oData.sMessage, + )); + elif not oOldData.isEqual(oData): + # Update old row. + self._oDb.execute('UPDATE VcsRevisions\n' + ' SET tsCreated = %s,\n' + ' sAuthor = %s,\n' + ' sMessage = %s\n' + 'WHERE sRepository = %s\n' + ' AND iRevision = %s' + , ( oData.tsCreated, + oData.sAuthor, + oData.sMessage, + oData.sRepository, + oData.iRevision, + )); + + self._oDb.maybeCommit(fCommit); + return oData; + + def getLastRevision(self, sRepository): + """ + Get the last known revision number for the given repository, returns 0 + if the repository is not known to us: + """ + self._oDb.execute('SELECT iRevision\n' + 'FROM VcsRevisions\n' + 'WHERE sRepository = %s\n' + 'ORDER BY iRevision DESC\n' + 'LIMIT 1\n' + , ( sRepository, )); + if self._oDb.getRowCount() == 0: + return 0; + return self._oDb.fetchOne()[0]; + + def fetchTimeline(self, sRepository, iRevision, cEntriesBack): + """ + Fetches a VCS timeline portion for a repository. + + Returns an array (list) of VcsRevisionData items, empty list if none. + Raises exception on error. + """ + self._oDb.execute('SELECT *\n' + 'FROM VcsRevisions\n' + 'WHERE sRepository = %s\n' + ' AND iRevision > %s\n' + ' AND iRevision <= %s\n' + 'ORDER BY iRevision DESC\n' + 'LIMIT %s\n' + , ( sRepository, iRevision - cEntriesBack*2 + 1, iRevision, cEntriesBack)); + aoRows = []; + for _ in range(self._oDb.getRowCount()): + aoRows.append(VcsRevisionData().initFromDbRow(self._oDb.fetchOne())); + return aoRows; + + +# +# Unit testing. +# + +# pylint: disable=missing-docstring +class VcsRevisionDataTestCase(ModelDataBaseTestCase): + def setUp(self): + self.aoSamples = [VcsRevisionData(),]; + +if __name__ == '__main__': + unittest.main(); + # not reached. + diff --git a/src/VBox/ValidationKit/testmanager/core/webservergluebase.py b/src/VBox/ValidationKit/testmanager/core/webservergluebase.py new file mode 100755 index 00000000..842f6793 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/webservergluebase.py @@ -0,0 +1,717 @@ +# -*- coding: utf-8 -*- +# $Id: webservergluebase.py $ + +""" +Test Manager Core - Web Server Abstraction Base Class. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard python imports. +import cgitb +import codecs; +import os +import sys + +# Validation Kit imports. +from common import webutils, utils; +from testmanager import config; + + +class WebServerGlueException(Exception): + """ + For exceptions raised by glue code. + """ + pass; # pylint: disable=unnecessary-pass + + +class WebServerGlueBase(object): + """ + Web server interface abstraction and some HTML utils. + """ + + ## Enables more debug output. + kfDebugInfoEnabled = True; + + ## The maximum number of characters to cache. + kcchMaxCached = 65536; + + ## Special getUserName return value. + ksUnknownUser = 'Unknown User'; + + ## HTTP status codes and their messages. + kdStatusMsgs = { + 100: 'Continue', + 101: 'Switching Protocols', + 102: 'Processing', + 103: 'Early Hints', + 200: 'OK', + 201: 'Created', + 202: 'Accepted', + 203: 'Non-Authoritative Information', + 204: 'No Content', + 205: 'Reset Content', + 206: 'Partial Content', + 207: 'Multi-Status', + 208: 'Already Reported', + 226: 'IM Used', + 300: 'Multiple Choices', + 301: 'Moved Permantently', + 302: 'Found', + 303: 'See Other', + 304: 'Not Modified', + 305: 'Use Proxy', + 306: 'Switch Proxy', + 307: 'Temporary Redirect', + 308: 'Permanent Redirect', + 400: 'Bad Request', + 401: 'Unauthorized', + 402: 'Payment Required', + 403: 'Forbidden', + 404: 'Not Found', + 405: 'Method Not Allowed', + 406: 'Not Acceptable', + 407: 'Proxy Authentication Required', + 408: 'Request Timeout', + 409: 'Conflict', + 410: 'Gone', + 411: 'Length Required', + 412: 'Precondition Failed', + 413: 'Payload Too Large', + 414: 'URI Too Long', + 415: 'Unsupported Media Type', + 416: 'Range Not Satisfiable', + 417: 'Expectation Failed', + 418: 'I\'m a teapot', + 421: 'Misdirection Request', + 422: 'Unprocessable Entity', + 423: 'Locked', + 424: 'Failed Dependency', + 425: 'Too Early', + 426: 'Upgrade Required', + 428: 'Precondition Required', + 429: 'Too Many Requests', + 431: 'Request Header Fields Too Large', + 451: 'Unavailable For Legal Reasons', + 500: 'Internal Server Error', + 501: 'Not Implemented', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + 504: 'Gateway Timeout', + 505: 'HTTP Version Not Supported', + 506: 'Variant Also Negotiates', + 507: 'Insufficient Storage', + 508: 'Loop Detected', + 510: 'Not Extended', + 511: 'Network Authentication Required', + }; + + + def __init__(self, sValidationKitDir, fHtmlDebugOutput = True): + self._sValidationKitDir = sValidationKitDir; + + # Debug + self.tsStart = utils.timestampNano(); + self._fHtmlDebugOutput = fHtmlDebugOutput; # For trace + self._oDbgFile = sys.stderr; + if config.g_ksSrvGlueDebugLogDst is not None and config.g_kfSrvGlueDebug is True: + self._oDbgFile = open(config.g_ksSrvGlueDebugLogDst, 'a'); # pylint: disable=consider-using-with,unspecified-encoding + if config.g_kfSrvGlueCgiDumpArgs: + self._oDbgFile.write('Arguments: %s\nEnvironment:\n' % (sys.argv,)); + if config.g_kfSrvGlueCgiDumpEnv: + for sVar in sorted(os.environ): + self._oDbgFile.write(' %s=\'%s\' \\\n' % (sVar, os.environ[sVar],)); + + self._afnDebugInfo = []; + + # HTTP header. + self._fHeaderWrittenOut = False; + self._dHeaderFields = \ + { \ + 'Content-Type': 'text/html; charset=utf-8', + }; + + # Body. + self._sBodyType = None; + self._dParams = {}; + self._sHtmlBody = ''; + self._cchCached = 0; + self._cchBodyWrittenOut = 0; + + # Output. + if sys.version_info[0] >= 3: + self.oOutputRaw = sys.stdout.detach(); # pylint: disable=no-member + sys.stdout = None; # Prevents flush_std_files() from complaining on stderr during sys.exit(). + else: + self.oOutputRaw = sys.stdout; + self.oOutputText = codecs.getwriter('utf-8')(self.oOutputRaw); + + + # + # Get stuff. + # + + def getParameters(self): + """ + Returns a dictionary with the query parameters. + + The parameter name is the key, the values are given as lists. If a + parameter is given more than once, the value is appended to the + existing dictionary entry. + """ + return {}; + + def getClientAddr(self): + """ + Returns the client address, as a string. + """ + raise WebServerGlueException('getClientAddr is not implemented'); + + def getMethod(self): + """ + Gets the HTTP request method. + """ + return 'POST'; + + def getLoginName(self): + """ + Gets login name provided by Apache. + Returns kUnknownUser if not logged on. + """ + return WebServerGlueBase.ksUnknownUser; + + def getUrlScheme(self): + """ + Gets scheme name (aka. access protocol) from request URL, i.e. 'http' or 'https'. + See also urlparse.scheme. + """ + return 'http'; + + def getUrlNetLoc(self): + """ + Gets the network location (server host name / ip) from the request URL. + See also urlparse.netloc. + """ + raise WebServerGlueException('getUrlNetLoc is not implemented'); + + def getUrlPath(self): + """ + Gets the hirarchical path (relative to server) from the request URL. + See also urlparse.path. + Note! This includes the leading slash. + """ + raise WebServerGlueException('getUrlPath is not implemented'); + + def getUrlBasePath(self): + """ + Gets the hirarchical base path (relative to server) from the request URL. + Note! This includes both a leading an trailing slash. + """ + sPath = self.getUrlPath(); # virtual method # pylint: disable=assignment-from-no-return + iLastSlash = sPath.rfind('/'); + if iLastSlash >= 0: + sPath = sPath[:iLastSlash]; + sPath = sPath.rstrip('/'); + return sPath + '/'; + + def getUrl(self): + """ + Gets the URL being accessed, sans parameters. + For instance this will return, "http://localhost/testmanager/admin.cgi" + when "http://localhost/testmanager/admin.cgi?blah=blah" is being access. + """ + return '%s://%s%s' % (self.getUrlScheme(), self.getUrlNetLoc(), self.getUrlPath()); + + def getBaseUrl(self): + """ + Gets the base URL (with trailing slash). + For instance this will return, "http://localhost/testmanager/" when + "http://localhost/testmanager/admin.cgi?blah=blah" is being access. + """ + return '%s://%s%s' % (self.getUrlScheme(), self.getUrlNetLoc(), self.getUrlBasePath()); + + def getUserAgent(self): + """ + Gets the User-Agent field of the HTTP header, returning empty string + if not present. + """ + return ''; + + def getContentType(self): + """ + Gets the Content-Type field of the HTTP header, parsed into a type + string and a dictionary. + """ + return ('text/html', {}); + + def getContentLength(self): + """ + Gets the content length. + Returns int. + """ + return 0; + + def getBodyIoStream(self): + """ + Returns file object for reading the HTML body. + """ + raise WebServerGlueException('getUrlPath is not implemented'); + + def getBodyIoStreamBinary(self): + """ + Returns file object for reading the binary HTML body. + """ + raise WebServerGlueException('getBodyIoStreamBinary is not implemented'); + + # + # Output stuff. + # + + def _writeHeader(self, sHeaderLine): + """ + Worker function which child classes can override. + """ + sys.stderr.write('_writeHeader: cch=%s "%s..."\n' % (len(sHeaderLine), sHeaderLine[0:10],)) + self.oOutputText.write(sHeaderLine); + return True; + + def flushHeader(self): + """ + Flushes the HTTP header. + """ + if self._fHeaderWrittenOut is False: + for sKey, sValue in self._dHeaderFields.items(): + self._writeHeader('%s: %s\n' % (sKey, sValue,)); + self._fHeaderWrittenOut = True; + self._writeHeader('\n'); # End of header indicator. + return None; + + def setHeaderField(self, sField, sValue): + """ + Sets a header field. + """ + assert self._fHeaderWrittenOut is False; + self._dHeaderFields[sField] = sValue; + return True; + + def setRedirect(self, sLocation, iCode = 302): + """ + Sets up redirection of the page. + Raises an exception if called too late. + """ + if self._fHeaderWrittenOut is True: + raise WebServerGlueException('setRedirect called after the header was written'); + if iCode != 302: + raise WebServerGlueException('Redirection code %d is not supported' % (iCode,)); + + self.setHeaderField('Location', sLocation); + self.setHeaderField('Status', '302 Found'); + return True; + + def setStatus(self, iStatus, sMsg = None): + """ Sets the status code. """ + if not sMsg: + sMsg = self.kdStatusMsgs[iStatus]; + return self.setHeaderField('Status', '%u %s' % (iStatus, sMsg)); + + def setContentType(self, sType): + """ Sets the content type header field. """ + return self.setHeaderField('Content-Type', sType); + + def _writeWorker(self, sChunkOfHtml): + """ + Worker function which child classes can override. + """ + sys.stderr.write('_writeWorker: cch=%s "%s..."\n' % (len(sChunkOfHtml), sChunkOfHtml[0:10],)) + self.oOutputText.write(sChunkOfHtml); + return True; + + def write(self, sChunkOfHtml): + """ + Writes chunk of HTML, making sure the HTTP header is flushed first. + """ + if self._sBodyType is None: + self._sBodyType = 'html'; + elif self._sBodyType != 'html': + raise WebServerGlueException('Cannot use writeParameter when body type is "%s"' % (self._sBodyType, )); + + self._sHtmlBody += sChunkOfHtml; + self._cchCached += len(sChunkOfHtml); + + if self._cchCached > self.kcchMaxCached: + self.flush(); + return True; + + def writeRaw(self, abChunk): + """ + Writes a raw chunk the document. Can be binary or any encoding. + No caching. + """ + if self._sBodyType is None: + self._sBodyType = 'raw'; + elif self._sBodyType != 'raw': + raise WebServerGlueException('Cannot use writeRaw when body type is "%s"' % (self._sBodyType, )); + + self.flushHeader(); + if self._cchCached > 0: + self.flush(); + + sys.stderr.write('writeRaw: cb=%s\n' % (len(abChunk),)) + self.oOutputRaw.write(abChunk); + return True; + + def writeParams(self, dParams): + """ + Writes one or more reply parameters in a form style response. The names + and values in dParams are unencoded, this method takes care of that. + + Note! This automatically changes the content type to + 'application/x-www-form-urlencoded', if the header hasn't been flushed + already. + """ + if self._sBodyType is None: + if not self._fHeaderWrittenOut: + self.setHeaderField('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8'); + elif self._dHeaderFields['Content-Type'] != 'application/x-www-form-urlencoded; charset=utf-8': + raise WebServerGlueException('Cannot use writeParams when content-type is "%s"' % \ + (self._dHeaderFields['Content-Type'],)); + self._sBodyType = 'form'; + + elif self._sBodyType != 'form': + raise WebServerGlueException('Cannot use writeParams when body type is "%s"' % (self._sBodyType, )); + + for sKey in dParams: + sValue = str(dParams[sKey]); + self._dParams[sKey] = sValue; + self._cchCached += len(sKey) + len(sValue); + + if self._cchCached > self.kcchMaxCached: + self.flush(); + + return True; + + def flush(self): + """ + Flush the output. + """ + self.flushHeader(); + + if self._sBodyType == 'form': + sBody = webutils.encodeUrlParams(self._dParams); + self._writeWorker(sBody); + + self._dParams = {}; + self._cchBodyWrittenOut += self._cchCached; + + elif self._sBodyType == 'html': + self._writeWorker(self._sHtmlBody); + + self._sHtmlBody = ''; + self._cchBodyWrittenOut += self._cchCached; + + self._cchCached = 0; + return None; + + # + # Paths. + # + + def pathTmWebUI(self): + """ + Gets the path to the TM 'webui' directory. + """ + return os.path.join(self._sValidationKitDir, 'testmanager', 'webui'); + + # + # Error stuff & Debugging. + # + + def errorLog(self, sError, aXcptInfo, sLogFile): + """ + Writes the error to a log file. + """ + # Easy solution for log file size: Only one report. + try: os.unlink(sLogFile); + except: pass; + + # Try write the log file. + fRc = True; + fSaved = self._fHtmlDebugOutput; + + try: + with open(sLogFile, 'w') as oFile: # pylint: disable=unspecified-encoding + oFile.write(sError + '\n\n'); + if aXcptInfo[0] is not None: + oFile.write(' B a c k t r a c e\n'); + oFile.write('===================\n'); + oFile.write(cgitb.text(aXcptInfo, 5)); + oFile.write('\n\n'); + + oFile.write(' D e b u g I n f o\n'); + oFile.write('=====================\n\n'); + self._fHtmlDebugOutput = False; + self.debugDumpStuff(oFile.write); + except: + fRc = False; + + self._fHtmlDebugOutput = fSaved; + return fRc; + + def errorPage(self, sError, aXcptInfo, sLogFile = None): + """ + Displays a page with an error message. + """ + if sLogFile is not None: + self.errorLog(sError, aXcptInfo, sLogFile); + + # Reset buffering, hoping that nothing was flushed yet. + self._sBodyType = None; + self._sHtmlBody = ''; + self._cchCached = 0; + if not self._fHeaderWrittenOut: + if self._fHtmlDebugOutput: + self.setHeaderField('Content-Type', 'text/html; charset=utf-8'); + else: + self.setHeaderField('Content-Type', 'text/plain; charset=utf-8'); + + # Write the error page. + if self._fHtmlDebugOutput: + self.write('<html><head><title>Test Manage Error</title></head>\n' + + '<body><h1>Test Manager Error:</h1>\n' + + '<p>' + sError + '</p>\n'); + else: + self.write(' Test Manage Error\n' + '===================\n' + '\n' + '' + sError + '\n\n'); + + if aXcptInfo[0] is not None: + if self._fHtmlDebugOutput: + self.write('<h1>Backtrace:</h1>\n'); + self.write(cgitb.html(aXcptInfo, 5)); + else: + self.write('Backtrace\n' + '---------\n' + '\n'); + self.write(cgitb.text(aXcptInfo, 5)); + self.write('\n\n'); + + if self.kfDebugInfoEnabled: + if self._fHtmlDebugOutput: + self.write('<h1>Debug Info:</h1>\n'); + else: + self.write('Debug Info\n' + '----------\n' + '\n'); + self.debugDumpStuff(); + + for fn in self._afnDebugInfo: + try: + fn(self, self._fHtmlDebugOutput); + except Exception as oXcpt: + self.write('\nDebug info callback %s raised exception: %s\n' % (fn, oXcpt)); + + if self._fHtmlDebugOutput: + self.write('</body></html>'); + + self.flush(); + + def debugInfoPage(self, fnWrite = None): + """ + Dumps useful debug info. + """ + if fnWrite is None: + fnWrite = self.write; + + fnWrite('<html><head><title>Test Manage Debug Info</title></head>\n<body>\n'); + self.debugDumpStuff(fnWrite = fnWrite); + fnWrite('</body></html>'); + self.flush(); + + def debugDumpDict(self, sName, dDict, fSorted = True, fnWrite = None): + """ + Dumps dictionary. + """ + if fnWrite is None: + fnWrite = self.write; + + asKeys = list(dDict.keys()); + if fSorted: + asKeys.sort(); + + if self._fHtmlDebugOutput: + fnWrite('<h2>%s</h2>\n' + '<table border="1"><tr><th>name</th><th>value</th></tr>\n' % (sName,)); + for sKey in asKeys: + fnWrite(' <tr><td>' + webutils.escapeElem(sKey) + '</td><td>' \ + + webutils.escapeElem(str(dDict.get(sKey))) \ + + '</td></tr>\n'); + fnWrite('</table>\n'); + else: + for i in range(len(sName) - 1): + fnWrite('%s ' % (sName[i],)); + fnWrite('%s\n\n' % (sName[-1],)); + + fnWrite('%28s Value\n' % ('Name',)); + fnWrite('------------------------------------------------------------------------\n'); + for sKey in asKeys: + fnWrite('%28s: %s\n' % (sKey, dDict.get(sKey),)); + fnWrite('\n'); + + return True; + + def debugDumpList(self, sName, aoStuff, fnWrite = None): + """ + Dumps array. + """ + if fnWrite is None: + fnWrite = self.write; + + if self._fHtmlDebugOutput: + fnWrite('<h2>%s</h2>\n' + '<table border="1"><tr><th>index</th><th>value</th></tr>\n' % (sName,)); + for i, _ in enumerate(aoStuff): + fnWrite(' <tr><td>' + str(i) + '</td><td>' + webutils.escapeElem(str(aoStuff[i])) + '</td></tr>\n'); + fnWrite('</table>\n'); + else: + for ch in sName[:-1]: + fnWrite('%s ' % (ch,)); + fnWrite('%s\n\n' % (sName[-1],)); + + fnWrite('Index Value\n'); + fnWrite('------------------------------------------------------------------------\n'); + for i, oStuff in enumerate(aoStuff): + fnWrite('%5u %s\n' % (i, str(oStuff))); + fnWrite('\n'); + + return True; + + def debugDumpParameters(self, fnWrite): + """ Dumps request parameters. """ + if fnWrite is None: + fnWrite = self.write; + + try: + dParams = self.getParameters(); + return self.debugDumpDict('Parameters', dParams); + except Exception as oXcpt: + if self._fHtmlDebugOutput: + fnWrite('<p>Exception %s while retriving parameters.</p>\n' % (oXcpt,)) + else: + fnWrite('Exception %s while retriving parameters.\n' % (oXcpt,)) + return False; + + def debugDumpEnv(self, fnWrite = None): + """ Dumps os.environ. """ + return self.debugDumpDict('Environment (os.environ)', os.environ, fnWrite = fnWrite); + + def debugDumpArgv(self, fnWrite = None): + """ Dumps sys.argv. """ + return self.debugDumpList('Arguments (sys.argv)', sys.argv, fnWrite = fnWrite); + + def debugDumpPython(self, fnWrite = None): + """ + Dump python info. + """ + dInfo = {}; + dInfo['sys.version'] = sys.version; + dInfo['sys.hexversion'] = sys.hexversion; + dInfo['sys.api_version'] = sys.api_version; + if hasattr(sys, 'subversion'): + dInfo['sys.subversion'] = sys.subversion; # pylint: disable=no-member + dInfo['sys.platform'] = sys.platform; + dInfo['sys.executable'] = sys.executable; + dInfo['sys.copyright'] = sys.copyright; + dInfo['sys.byteorder'] = sys.byteorder; + dInfo['sys.exec_prefix'] = sys.exec_prefix; + dInfo['sys.prefix'] = sys.prefix; + dInfo['sys.path'] = sys.path; + dInfo['sys.builtin_module_names'] = sys.builtin_module_names; + dInfo['sys.flags'] = sys.flags; + + return self.debugDumpDict('Python Info', dInfo, fnWrite = fnWrite); + + + def debugDumpStuff(self, fnWrite = None): + """ + Dumps stuff to the error page and debug info page. + Should be extended by child classes when possible. + """ + self.debugDumpParameters(fnWrite); + self.debugDumpEnv(fnWrite); + self.debugDumpArgv(fnWrite); + self.debugDumpPython(fnWrite); + return True; + + def dprint(self, sMessage): + """ + Prints to debug log (usually apache error log). + """ + if config.g_kfSrvGlueDebug is True: + if config.g_kfSrvGlueDebugTS is False: + self._oDbgFile.write(sMessage); + if not sMessage.endswith('\n'): + self._oDbgFile.write('\n'); + else: + tsNow = utils.timestampMilli(); + tsReq = tsNow - (self.tsStart / 1000000); + iPid = os.getpid(); + for sLine in sMessage.split('\n'): + self._oDbgFile.write('%s/%03u,pid=%04x: %s\n' % (tsNow, tsReq, iPid, sLine,)); + + return True; + + def registerDebugInfoCallback(self, fnDebugInfo): + """ + Registers a debug info method for calling when the error page is shown. + + The fnDebugInfo function takes two parameters. The first is this + object, the second is a boolean indicating html (True) or text (False) + output. The return value is ignored. + """ + if self.kfDebugInfoEnabled: + self._afnDebugInfo.append(fnDebugInfo); + return True; + + def unregisterDebugInfoCallback(self, fnDebugInfo): + """ + Unregisters a debug info method previously registered by + registerDebugInfoCallback. + """ + if self.kfDebugInfoEnabled: + try: self._afnDebugInfo.remove(fnDebugInfo); + except: pass; + return True; + diff --git a/src/VBox/ValidationKit/testmanager/core/webservergluecgi.py b/src/VBox/ValidationKit/testmanager/core/webservergluecgi.py new file mode 100755 index 00000000..730455c5 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/core/webservergluecgi.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +# $Id: webservergluecgi.py $ + +""" +Test Manager Core - Web Server Abstraction Base Class. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import cgitb; +import os; +import sys; +import cgi; + +# Validation Kit imports. +from testmanager.core.webservergluebase import WebServerGlueBase; +from testmanager import config; + + +class WebServerGlueCgi(WebServerGlueBase): + """ + CGI glue. + """ + def __init__(self, sValidationKitDir, fHtmlOutput=True): + WebServerGlueBase.__init__(self, sValidationKitDir, fHtmlOutput); + + if config.g_kfSrvGlueCgiTb is True: + cgitb.enable(format=('html' if fHtmlOutput else 'text')); + + def getParameters(self): + return cgi.parse(keep_blank_values=True); + + def getClientAddr(self): + return os.environ.get('REMOTE_ADDR'); + + def getMethod(self): + return os.environ.get('REQUEST_METHOD', 'POST'); + + def getLoginName(self): + return os.environ.get('REMOTE_USER', WebServerGlueBase.ksUnknownUser); + + def getUrlScheme(self): + return 'https' if 'HTTPS' in os.environ else 'http'; + + def getUrlNetLoc(self): + return os.environ['HTTP_HOST']; + + def getUrlPath(self): + return os.environ['REQUEST_URI']; + + def getUserAgent(self): + return os.getenv('HTTP_USER_AGENT', ''); + + def getContentType(self): + return cgi.parse_header(os.environ.get('CONTENT_TYPE', 'text/html')); + + def getContentLength(self): + return int(os.environ.get('CONTENT_LENGTH', 0)); + + def getBodyIoStream(self): + return sys.stdin; + + def getBodyIoStreamBinary(self): + # Python 3: sys.stdin.read() returns a string. To get untranslated + # binary data we use the sys.stdin.buffer object instead. + return getattr(sys.stdin, 'buffer', sys.stdin); + diff --git a/src/VBox/ValidationKit/testmanager/db/Makefile.kmk b/src/VBox/ValidationKit/testmanager/db/Makefile.kmk new file mode 100644 index 00000000..c73cbfca --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/Makefile.kmk @@ -0,0 +1,98 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Makefile for generating .html from .txt. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + + +# Need proper shell on windows. +DEPTH = ../../../../.. +ifneq ($(wildcard $(DEPTH)/Config.kmk),) + include $(KBUILD_PATH)/header.kmk +else + VBOX_BLD_PYTHON ?= python +endif + + +GENERATED_FILES = TestManagerDatabaseComments.pgsql +PSQL := $(firstword $(which $(foreach pgver, 16 15 14 13 12 10 11 95 94 93 92,psql$(pgver)) ) psql) +ifeq ($(PSQL_DB_HOST),) + PSQL_DB_HOST := localhost # Use localhost if nothing else is set. +endif +ifeq ($(PSQL_DB_PORT),) + PSQL_DB_PORT := 5432 # Same for the port; use the default. +endif +ifeq ($(PSQL_DB_USER),) + PSQL_DB_USER := postgres +endif +PSQL_OPTS = --user=$(PSQL_DB_USER) --set=ON_ERROR_STOP=1 --host=$(PSQL_DB_HOST) --port=$(PSQL_DB_PORT) + +all: $(GENERATED_FILES) + +clean: + kmk_builtin_rm -f -- $(GENERATED_FILES) + + +TestManagerDatabaseComments.pgsql: TestManagerDatabaseInit.pgsql gen-sql-comments.py + LC_ALL=C $(VBOX_BLD_PYTHON) gen-sql-comments.py $< > $@ + + +load-testmanager-db: \ + TestManagerDatabaseInit.pgsql \ + TestManagerDatabaseComments.pgsql \ + ../core/useraccount.pgsql \ + ../core/testcase.pgsql \ + ../core/testbox.pgsql \ + ../core/globalresource.pgsql + @kmk_builtin_echo "Creating testmanager database: For script verification only!" + $(PSQL) $(PSQL_OPTS) -f TestManagerDatabaseInit.pgsql + $(PSQL) $(PSQL_OPTS) -d testmanager -f TestManagerDatabaseComments.pgsql + $(PSQL) $(PSQL_OPTS) -d testmanager -f ../core/useraccount.pgsql + $(PSQL) $(PSQL_OPTS) -d testmanager -f ../core/testcase.pgsql + $(PSQL) $(PSQL_OPTS) -d testmanager -f ../core/testbox.pgsql + $(PSQL) $(PSQL_OPTS) -d testmanager -f ../core/globalresource.pgsql + $(PSQL) $(PSQL_OPTS) -d testmanager -f TestManagerDatabaseDefaultUserAccounts.pgsql + +reload-testmanager-db-functions: \ + ../core/useraccount.pgsql \ + ../core/testcase.pgsql \ + ../core/testbox.pgsql \ + ../core/globalresource.pgsql + @kmk_builtin_echo "Reloading testmanager database functions" + $(PSQL) $(PSQL_OPTS) -d testmanager -f ../core/useraccount.pgsql + $(PSQL) $(PSQL_OPTS) -d testmanager -f ../core/testcase.pgsql + $(PSQL) $(PSQL_OPTS) -d testmanager -f ../core/testbox.pgsql + $(PSQL) $(PSQL_OPTS) -d testmanager -f ../core/globalresource.pgsql + +# Only for prettier graphs: +# $(PSQL) $(PSQL_OPTS) -d testmanager -f TestManagerDatabaseForeignKeyErHacks.pgsql diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase.dmd b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase.dmd new file mode 100644 index 00000000..cf35f3fb --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase.dmd @@ -0,0 +1,8 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<OSDM_Design class="oracle.dbtools.crest.model.design.Design" name="TestManagerDatabase" id="99299876-6D97-026B-55F9-DF582D334681" version="3.5"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 21:58:45 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<capitalNames>false</capitalNames> +<designId>99299876-6D97-026B-55F9-DF582D334681</designId> +</OSDM_Design>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/datatypes/DataTypes.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/datatypes/DataTypes.xml new file mode 100644 index 00000000..9b86b6dd --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/datatypes/DataTypes.xml @@ -0,0 +1,15 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<DataTypesDesign class="oracle.dbtools.crest.model.design.datatypes.DataTypesDesign" name="DataTypes" id="E0EE53BE-07B1-7CE9-B0DA-5D939EA4A3C9" mainViewID="E9476B45-3C62-EE27-4705-6F1EFAD11B74"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 21:58:45 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<shouldBeOpen>false</shouldBeOpen> +<collectionOfRefsPrefix>array_ref_</collectionOfRefsPrefix> +<collectionPrefix>array_</collectionPrefix> +<defaultArrayLimit>10</defaultArrayLimit> +<defaultCollectionType_Kind>ARRAY</defaultCollectionType_Kind> +<defaultCollectionType_Suffix>_Array</defaultCollectionType_Suffix> +<embeddedStructuredTypePrefix>inst_</embeddedStructuredTypePrefix> +<referencePrefix>ref_</referencePrefix> +<useRoleInAssociationEndAsName>true</useRoleInAssociationEndAsName> +</DataTypesDesign>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/datatypes/structuredtype/seg_0/47E390DE-0671-C4B1-8428-0F45CBEE18F8.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/datatypes/structuredtype/seg_0/47E390DE-0671-C4B1-8428-0F45CBEE18F8.xml new file mode 100644 index 00000000..e274e153 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/datatypes/structuredtype/seg_0/47E390DE-0671-C4B1-8428-0F45CBEE18F8.xml @@ -0,0 +1,37 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<StructuredType class="oracle.dbtools.crest.model.design.datatypes.StructuredType" name="SDO_GEOMETRY" id="47E390DE-0671-C4B1-8428-0F45CBEE18F8" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 21:58:45 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<visible>false</visible> +<predefined>true</predefined> +<final>false</final> +<instantiable>true</instantiable> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16777056</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Method</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Instantiable</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Mandatory</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +</fonts> +</StructuredType>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/datatypes/structuredtype/seg_0/F72C39E0-D1CA-8821-2AD7-A1E95A37D3D1.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/datatypes/structuredtype/seg_0/F72C39E0-D1CA-8821-2AD7-A1E95A37D3D1.xml new file mode 100644 index 00000000..5ff7c301 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/datatypes/structuredtype/seg_0/F72C39E0-D1CA-8821-2AD7-A1E95A37D3D1.xml @@ -0,0 +1,37 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<StructuredType class="oracle.dbtools.crest.model.design.datatypes.StructuredType" name="XMLTYPE" id="F72C39E0-D1CA-8821-2AD7-A1E95A37D3D1" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 21:58:45 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<visible>false</visible> +<predefined>true</predefined> +<final>false</final> +<instantiable>true</instantiable> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16777056</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Method</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Instantiable</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Mandatory</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +</fonts> +</StructuredType>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/datatypes/subviews/E9476B45-3C62-EE27-4705-6F1EFAD11B74.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/datatypes/subviews/E9476B45-3C62-EE27-4705-6F1EFAD11B74.xml new file mode 100644 index 00000000..d09e8a61 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/datatypes/subviews/E9476B45-3C62-EE27-4705-6F1EFAD11B74.xml @@ -0,0 +1,21 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Diagram class="oracle.dbtools.crest.swingui.datatypes.DPVDataTypes" id="E9476B45-3C62-EE27-4705-6F1EFAD11B74"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 21:58:45 UTC</createdTime> +<autoRoute>false</autoRoute> +<boxInbox>true</boxInbox> +<showLegend>false</showLegend> +<showLabels>false</showLabels> +<showGrid>false</showGrid> +<diagramColor>-1</diagramColor> +<display>false</display> +<notation>0</notation> +<objectViews> +<OView class="oracle.dbtools.crest.swingui.datatypes.TVStructuredType" oid="47E390DE-0671-C4B1-8428-0F45CBEE18F8" otype="StructuredType" vid="48CB0B19-5276-2CC9-FC10-6C17D5E5FAC6"> +<bounds x="20" y="20" width="100" height="100"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.datatypes.TVStructuredType" oid="F72C39E0-D1CA-8821-2AD7-A1E95A37D3D1" otype="StructuredType" vid="5CEA75E2-B53E-AD72-1C75-F8820307529C"> +<bounds x="20" y="20" width="100" height="100"/> +</OView> +</objectViews> +</Diagram>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/defaultRDBMSSites.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/defaultRDBMSSites.xml new file mode 100644 index 00000000..07122f85 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/defaultRDBMSSites.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<sites version="1.0"> + <site name="Oracle Database 11g" type="9" oid="32076570-2523-435C-2E92-BF29817DFF70" pathid="1" /> + <site name="Oracle Database 10g" type="8" oid="D9582E4E-79E2-319F-387A-2ED963CB9D32" pathid="2" /> + <site name="Oracle9i" type="7" oid="9807C1FA-0550-772D-1F14-16B19CA63681" pathid="3" /> + <site name="SQL Server 2005" type="5" oid="B0943E51-0387-1F2A-CED9-5FB738BA5A0C" pathid="4" /> + <site name="SQL Server 2000" type="4" oid="3424E3DB-6FE1-14EB-9311-F76EF3096E76" pathid="5" /> + <site name="DB2/390 8" type="1" oid="CC7FDCE5-F5A5-F2C0-C9A7-0C07C92C898D" pathid="6" /> + <site name="DB2/390 7" type="0" oid="26535E02-9B31-3EDE-24D5-4E3188C99288" pathid="7" /> + <site name="DB2/UDB 8.1" type="3" oid="2BAE410E-5CEB-5134-8F33-CCB20E003569" pathid="8" /> + <site name="DB2/UDB 7.1" type="2" oid="BA6252DC-29CE-184D-7701-48F55E3954D4" pathid="9" /> +</sites>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/defaultdomains.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/defaultdomains.xml new file mode 100644 index 00000000..d858b5c4 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/defaultdomains.xml @@ -0,0 +1,13 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<DomainFile class="oracle.dbtools.crest.model.design.DomainFileWrapper" fileName="defaultdomains"> + <domains> + <Domain class="oracle.dbtools.crest.model.design.Domain" name="Unknown" id="DOM3000004"> + <createdBy>bird</createdBy> + <createdTime>2012-08-20 21:58:45 UTC</createdTime> + <ownerDesignName>System</ownerDesignName> + <avTSortOrder>0</avTSortOrder> + <fileName>defaultdomains</fileName> + <logicalDatatype>LOGDT017</logicalDatatype> + </Domain> + </domains> +</DomainFile>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/dl_settings.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/dl_settings.xml new file mode 100644 index 00000000..6c426008 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/dl_settings.xml @@ -0,0 +1,288 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<settings> + <logical_type_for_domain_presentation value="false" /> + <automatic_pk_generation value="false" /> + <automatic_uk_generation value="false" /> + <automatic_fk_generation value="false" /> + <substitution_patterns> + </substitution_patterns> + <classification_types> + <type name="Fact" color="-7482" prefix="" id="1" /> + <type name="Dimension" color="-1781507" prefix="" id="2" /> + <type name="Logging" color="-1776412" prefix="" id="3" /> + <type name="Summary" color="-3148598" prefix="" id="4" /> + <type name="Temporary" color="-1" prefix="" id="5" /> + </classification_types> + <default_fonts_and_colors> + <fc_object classname="Entity" background="-5971457" foreground="-16776961"> + <fonts> + <font_object fo_type="Title" font_color="-16776961" font_name="Dialog" font_size="10" font_style="1"/> + <font_object fo_type="Attribute" font_color="-16777216" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Datatype" font_color="-16744448" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="PK Element" font_color="-16776961" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="FK Element" font_color="-16777216" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="UK Element" font_color="-16776961" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Not Null" font_color="-65536" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Key" font_color="-16777216" font_name="Dialog" font_size="10" font_style="0"/> + </fonts> + </fc_object> + <fc_object classname="Logical View" background="-25750" foreground="-16776961"> + <fonts> + <font_object fo_type="Title" font_color="-16776961" font_name="Dialog" font_size="10" font_style="1"/> + <font_object fo_type="Attribute" font_color="-16777216" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Datatype" font_color="-16744448" font_name="Dialog" font_size="10" font_style="0"/> + </fonts> + </fc_object> + <fc_object classname="Table" background="-76" foreground="-16776961"> + <fonts> + <font_object fo_type="Title" font_color="-16776961" font_name="Dialog" font_size="10" font_style="1"/> + <font_object fo_type="Column" font_color="-16777216" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Datatype" font_color="-16744448" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="PK Element" font_color="-16776961" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="FK Element" font_color="-16777216" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="UK Element" font_color="-16776961" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Not Null" font_color="-65536" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Key" font_color="-16777216" font_name="Dialog" font_size="10" font_style="0"/> + </fonts> + </fc_object> + <fc_object classname="Relational View" background="-6881386" foreground="-16776961"> + <fonts> + <font_object fo_type="Title" font_color="-16776961" font_name="Dialog" font_size="10" font_style="1"/> + <font_object fo_type="Column" font_color="-16777216" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Datatype" font_color="-16744448" font_name="Dialog" font_size="10" font_style="0"/> + </fonts> + </fc_object> + <fc_object classname="Structured Type" background="-7537956" foreground="-16777216"> + <fonts> + <font_object fo_type="Title" font_color="-16776961" font_name="Dialog" font_size="10" font_style="1"/> + <font_object fo_type="Attribute" font_color="-16777216" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Datatype" font_color="-16777056" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Method" font_color="-16777216" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Not Instantiable" font_color="-65536" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Mandatory" font_color="-65536" font_name="Dialog" font_size="10" font_style="0"/> + </fonts> + </fc_object> + <fc_object classname="Cube" background="-7482" foreground="-16777216"> + <fonts> + <font_object fo_type="Title" font_color="-16776961" font_name="Dialog" font_size="10" font_style="1"/> + <font_object fo_type="Fact Entities" font_color="-16776961" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Measure Type" font_color="-16776961" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Measure" font_color="-16777216" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Function" font_color="-16777056" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Formula" font_color="-65536" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Child to Parent Attributes" font_color="-16777216" font_name="Dialog" font_size="10" font_style="0"/> + </fonts> + </fc_object> + <fc_object classname="Dimension" background="-16713196" foreground="-16777216"> + <fonts> + <font_object fo_type="Title" font_color="-16776961" font_name="Dialog" font_size="10" font_style="1"/> + </fonts> + </fc_object> + <fc_object classname="Level" background="-1781507" foreground="-16777216"> + <fonts> + <font_object fo_type="Title" font_color="-16776961" font_name="Dialog" font_size="10" font_style="1"/> + <font_object fo_type="Level Entity" font_color="-16776961" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Type" font_color="-16776961" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Attribute" font_color="-16777216" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Function" font_color="-16777216" font_name="Dialog" font_size="10" font_style="0"/> + </fonts> + </fc_object> + <fc_object classname="Process" background="-106" foreground="-16777216"> + <fonts> + <font_object fo_type="Title" font_color="-16776961" font_name="Dialog" font_size="10" font_style="1"/> + <font_object fo_type="Process Number" font_color="-65536" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Transformation Task" font_color="-16777216" font_name="Dialog" font_size="10" font_style="0"/> + </fonts> + </fc_object> + <fc_object classname="External Agent" background="-5570646" foreground="-16777216"> + <fonts> + <font_object fo_type="Title" font_color="-16776961" font_name="Dialog" font_size="10" font_style="1"/> + </fonts> + </fc_object> + <fc_object classname="Information Store" background="-10170881" foreground="-16777216"> + <fonts> + <font_object fo_type="Title" font_color="-16776961" font_name="Dialog" font_size="10" font_style="1"/> + <font_object fo_type="Number" font_color="-1" font_name="Dialog" font_size="10" font_style="1"/> + </fonts> + </fc_object> + <fc_object classname="In-Out Parameters" background="-328966" foreground="-16777216"> + <fonts> + <font_object fo_type="Title" font_color="-16777216" font_name="Dialog" font_size="10" font_style="1"/> + <font_object fo_type="Parameters" font_color="-16777216" font_name="Dialog" font_size="10" font_style="0"/> + <font_object fo_type="Datatype" font_color="-65536" font_name="Dialog" font_size="10" font_style="0"/> + </fonts> + </fc_object> + <fc_object classname="Transformation" background="-43" foreground="-16777216"> + <fonts> + <font_object fo_type="Title" font_color="-16777216" font_name="Dialog" font_size="10" font_style="1"/> + <font_object fo_type="Process Number" font_color="-65536" font_name="Dialog" font_size="10" font_style="0"/> + </fonts> + </fc_object> + <fc_object classname="Note" background="-4144960" foreground="-16777216"> + <fonts> + <font_object fo_type="Title" font_color="-16777216" font_name="Dialog" font_size="10" font_style="0"/> + </fonts> + </fc_object> + <fc_object classname="Label" background="-1" foreground="-16777216"> + <fonts> + <font_object fo_type="Text" font_color="-16777216" font_name="Dialog" font_size="10" font_style="0"/> + </fonts> + </fc_object> + <fc_object classname="Legend" background="-1" foreground="-16777216"> + <fonts> + <font_object fo_type="Text" font_color="-16777216" font_name="Dialog" font_size="10" font_style="0"/> + </fonts> + </fc_object> + </default_fonts_and_colors> + <default_line_widths_and_colors> + <lwc_object classname="Logical Relation" color="-16777216" width="1"> + </lwc_object> + <lwc_object classname="Logical Inheritance" color="-65536" width="1"> + </lwc_object> + <lwc_object classname="Relational Foreign Key" color="-16777216" width="1"> + </lwc_object> + <lwc_object classname="Type Substitution" color="-16725996" width="1"> + </lwc_object> + <lwc_object classname="Datatype Reference" color="-16776961" width="1"> + </lwc_object> + <lwc_object classname="Datatype Inheritance" color="-65536" width="1"> + </lwc_object> + <lwc_object classname="Multidimentional Link" color="-16776961" width="1"> + </lwc_object> + <lwc_object classname="Multidimensional Hierarchy" color="-16725996" width="1"> + </lwc_object> + <lwc_object classname="Process Flow" color="-65536" width="1"> + </lwc_object> + </default_line_widths_and_colors> + <naming_standard_rules> + <logical> + <separator value= "Title Case" char=" "/> + <entity> + </entity> + <attribute> + </attribute> + </logical> + <relational> + <separator value= "_" abbreviated_only="false"/> + <table> + </table> + <column> + </column> + </relational> + <domains> + <separator value= " "/> + <domain> + </domain> + </domains> + <constraints> + <pk value="{table}_PK"/> + <fk value="{child}_{parent}_FK"/> + <ck value="{table}_CK"/> + <un value="{table}_{column}_UN"/> + <idx value="{table}_{column}_IDX"/> + <colck value="CK_{table}_{column}"/> + <column_foreign_key value="{ref table}_{ref column}"/> + <ui value="{entity} PK"/> + <relation_attribute value="{ref entity}_{ref attribute}"/> + </constraints> + <glossaries> + </glossaries> + </naming_standard_rules> +<comparemapping> +</comparemapping> + <engineering_params> + <delete_without_origin value="false"/> + <engineer_coordinates value="true"/> + <engineer_generated value="false"/> + <show_engineering_intree value="false"/> + <apply_naming_std value="false"/> + <use_pref_abbreviation value="true"/> + <upload_directory value=""/> + <date_format value="YYYY/MM/DD HH24:MI:SS"/> + <timestamp_format value="YYYY/MM/DD HH24:MI:SS.FF"/> + <timestamp_tz_format value="YYYY/MM/DD HH24:MI:SS.FFTZH:TZM"/> + </engineering_params> + <eng_compare show_sel_prop_only="true" not_apply_for_new_objects="true" exclude_from_tree="false"> + <entity_table> + <property name="Name" selected="true"/> + <property name="Short Name / Abbreviation" selected="true"/> + <property name="Comment" selected="true"/> + <property name="Comment in RDBMS" selected="true"/> + <property name="Notes" selected="true"/> + <property name="Temporary Table Scope" selected="true"/> + <property name="Table Type" selected="true"/> + <property name="Structured Type" selected="true"/> + <property name="Type Substitution (Super-Type Object)" selected="true"/> + <property name="Min Volumes" selected="true"/> + <property name="Expected Volumes" selected="true"/> + <property name="Max Volumes" selected="true"/> + <property name="Growth Percent" selected="true"/> + <property name="Growth Type" selected="true"/> + <property name="Normal Form" selected="true"/> + <property name="Adequately Normalized" selected="true"/> + </entity_table> + <attribute_column> + <property name="Name" selected="true"/> + <property name="Data Type" selected="true"/> + <property name="Data Type Kind" selected="true"/> + <property name="Mandatory" selected="true"/> + <property name="Default Value" selected="true"/> + <property name="Check Constraint Name" selected="true"/> + <property name="Use Domain Constraint" selected="true"/> + <property name="Check Constraint" selected="true"/> + <property name="Range Constraint" selected="true"/> + <property name="LOV Constraint" selected="true"/> + <property name="Comment" selected="true"/> + <property name="Comment in RDBMS" selected="true"/> + <property name="Notes" selected="true"/> + <property name="Source Type" selected="true"/> + <property name="Formula Description" selected="true"/> + <property name="Type Substitution" selected="true"/> + <property name="Scope" selected="true"/> + </attribute_column> + <key_index> + <property name="Name" selected="true"/> + <property name="Comment" selected="true"/> + <property name="Comment in RDBMS" selected="true"/> + <property name="Notes" selected="true"/> + <property name="Primary Key" selected="true"/> + <property name="Attributes/Columns" selected="true"/> + </key_index> + <relation_fk> + <property name="Name" selected="true"/> + <property name="Delete Rule" selected="true"/> + <property name="Comment" selected="true"/> + <property name="Comment in RDBMS" selected="true"/> + <property name="Notes" selected="true"/> + </relation_fk> + <entityview_view> + <property name="Name" selected="true"/> + <property name="Comment" selected="true"/> + <property name="Comment in RDBMS" selected="true"/> + <property name="Notes" selected="true"/> + <property name="Structured Type" selected="true"/> + <property name="Where" selected="true"/> + <property name="Having" selected="true"/> + <property name="User Defined SQL" selected="true"/> + </entityview_view> + </eng_compare> + <naming_options> + <model_options objectid="B082B14A-BEA8-D8A7-D661-197F34766ED3"> + <naming_option class_name="oracle.dbtools.crest.model.design.relational.Table" max_name_length="30" case_type="2" valid_characters="" all_valid="true" /> + <naming_option class_name="oracle.dbtools.crest.model.design.relational.Column" max_name_length="30" case_type="2" valid_characters="" all_valid="true" /> + <naming_option class_name="oracle.dbtools.crest.model.design.relational.TableView" max_name_length="30" case_type="2" valid_characters="" all_valid="true" /> + <naming_option class_name="oracle.dbtools.crest.model.design.constraint.TableLevelConstraint" max_name_length="30" case_type="2" valid_characters="" all_valid="true" /> + <naming_option class_name="oracle.dbtools.crest.model.design.relational.FKIndexAssociation" max_name_length="30" case_type="2" valid_characters="" all_valid="true" /> + <naming_option class_name="oracle.dbtools.crest.model.design.relational.Index" max_name_length="30" case_type="2" valid_characters="" all_valid="true" /> + </model_options> + <model_options objectid="E3665D68-35D3-8757-63ED-30AEFB972A2C"> + <naming_option class_name="oracle.dbtools.crest.model.design.logical.Entity" max_name_length="254" case_type="2" valid_characters="[a-z][A-Z][0-9]" all_valid="false" /> + <naming_option class_name="oracle.dbtools.crest.model.design.logical.Attribute" max_name_length="254" case_type="2" valid_characters="[a-z][A-Z][0-9] " all_valid="false" /> + <naming_option class_name="oracle.dbtools.crest.model.design.logical.EntityView" max_name_length="254" case_type="2" valid_characters="[a-z][A-Z][0-9] " all_valid="false" /> + </model_options> + </naming_options> + <merge_conflicts> + </merge_conflicts> + <deleted_files> + </deleted_files> +</settings>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/dr_custom_scripts.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/dr_custom_scripts.xml new file mode 100644 index 00000000..3580f0f0 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/dr_custom_scripts.xml @@ -0,0 +1,360 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<dr_custom_scripts> + <scr id="D36CE536-D575-BE5C-625F-23DE23913C6B" name="Complex rule - check comments demo" object="Table" engine="Mozilla Rhino" type="Warning" var="table" library="my first library" method="checkcomments" purpose="validation" > + <script> + <![CDATA[var ruleMessage; +var errType; +var table; +function checkcomments(object){ + result = true; + ruleMessage=""; + if(table.getCommentInRDBMS().equals("")){ + ruleMessage="no comments in RDBMS defined"; + errType="Problem:"; + result = false; + } + if(table.getComment().equals("")){ + if(ruleMessage.equals("")){ + ruleMessage="no comments defined"; + }else{ + ruleMessage= ruleMessage +" , no comments defined"; + } + errType="Error"; + return false; + } + return result; +}]]> + </script> + </scr> + <scr id="0BAA564F-AB5F-D776-2E4F-31FDB3047F69" name="Tables to lower case - Rhino" object="relational" engine="Mozilla Rhino" type="" var="model" library="" method="" purpose="transformation" > + <script> + <![CDATA[tables = model.getTableSet().toArray(); +for (var t = 0; t<tables.length;t++){ + table = tables[t]; + name = table.getName().toLowerCase(); + table.setName(name); + columns = table.getElements(); + size = table.getElementsCollection().size(); + for (var i = 0; i < size; i++) { + column = columns[i]; + cname = column.getName().toLowerCase(); + column.setName(cname); + } + table.setDirty(true); + keys = table.getKeys(); + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + if(!key.isFK()){ + kname = key.getName().toLowerCase(); + key.setName(kname); + }else{ + kname = key.getFKAssociation().getName().toLowerCase(); + key.getFKAssociation().setName(kname); + key.getFKAssociation().setDirty(true); + } + } +}]]> + </script> + </scr> + <scr id="B673F271-4836-DD48-15AC-487DDECCAF49" name="Tables to upper case - JRuby" object="relational" engine="JSR 223 JRuby Engine" type="" var="model" library="" method="" purpose="transformation" > + <script> + <![CDATA[tables =$model.getTableSet().toArray() +for t in 0..tables.length-1 + table = tables[t] + name = table.getName().upcase + table.setName(name) + columns = table.getElements() + size = table.getElementsCollection().size()-1 + for i in 0..size + column = columns[i] + cname = column.getName().upcase + column.setName(cname) + end + keys = table.getKeys() + for i in 0..keys.length-1 + key = keys[i] + kname = key.getName().upcase + key.setName(kname) + end +end]]> + </script> + </scr> + <scr id="3E7C4F9E-9FCB-56C7-086F-F976F9A66384" name="Tables to upper case - JRuby - library usage" object="relational" engine="JSR 223 JRuby Engine" type="" var="model" library="Jruby lib" method="tables_up" purpose="transformation" > + <script> + <![CDATA[def tables_up(model) +tables = model.getTableSet().toArray() +for t in 0..tables.length-1 + table = tables[t] + name = table.getName().upcase + table.setName(name) + columns = table.getElements() + size = table.getElementsCollection().size()-1 + for i in 0..size + column = columns[i] + cname = column.getName().upcase + column.setName(cname) + end + keys = table.getKeys() + for i in 0..keys.length-1 + key = keys[i] + kname = key.getName().upcase + key.setName(kname) + end +end +return true +end]]> + </script> + </scr> + <scr id="E60A5A28-BB9B-3787-10E7-259DF900B9E6" name="Table abbreviation to column" object="relational" engine="Mozilla Rhino" type="" var="model" library="" method="" purpose="transformation" > + <script> + <![CDATA[tables = model.getTableSet().toArray(); +for (var t = 0; t<tables.length;t++){ + table = tables[t]; + abbr = table.getAbbreviation()+"_"; + if(!"_".equals(abbr)){ + columns = table.getElements(); + for (var i = 0; i < columns.length; i++) { + column = columns[i]; + cname = column.getName(); + if(!cname.startsWith(abbr)){ + column.setName(abbr+cname); + } + } + } +}]]> + </script> + </scr> + <scr id="9BE4E26C-36D8-A92C-ADEA-F183327DC239" name="Remove Table abbr from column" object="relational" engine="Mozilla Rhino" type="" var="model" library="" method="" purpose="transformation" > + <script> + <![CDATA[tables = model.getTableSet().toArray(); +for (var t = 0; t<tables.length;t++){ + table = tables[t]; + abbr = table.getAbbreviation()+"_"; + count = table.getAbbreviation().length()+1; + if(!"_".equals(abbr)){ + columns = table.getElements(); + for (var i = 0; i < columns.length; i++) { + column = columns[i]; + cname = column.getName(); + if(cname.startsWith(abbr)){ + column.setName(cname.substring(count)); + table.setDirty(true); + } + } + } +}]]> + </script> + </scr> + <scr id="23BE8827-D732-72B0-C6E6-266EFE116EDD" name="Table template" object="relational" engine="Mozilla Rhino" type="" var="model" library="" method="" purpose="transformation" > + <script> + <![CDATA[var t_name = "table_template"; +var p_name = "ctemplateID"; +template = model.getTableSet().getByName(t_name); +if(template!=null){ + tcolumns = template.getElements(); + tables = model.getTableSet().toArray(); + for (var t = 0; t<tables.length;t++){ + table = tables[t]; + // compare name ignoring the case + if(!table.getName().equalsIgnoreCase(t_name)){ + for (var i = 0; i < tcolumns.length; i++) { + column = tcolumns[i]; + col = table.getColumnByProperty(p_name,column.getObjectID()); + if(col==null){ + col = table.createColumn(); + } + column.copy(col); + //set property after copy otherwise it'll be cleared + col.setProperty(p_name,column.getObjectID()); + table.setDirty(true); + } + } + } +}]]> + </script> + </scr> + <scr id="5A8A151A-13FD-4B0A-E233-E3C5126BA02C" name="Tables to upper case - Rhino" object="relational" engine="Mozilla Rhino" type="" var="model" library="" method="" purpose="transformation" > + <script> + <![CDATA[tables = model.getTableSet().toArray(); +for (var t = 0; t<tables.length;t++){ + table = tables[t]; + name = table.getName().toUpperCase(); + table.setName(name); + columns = table.getElements(); + size = table.getElementsCollection().size(); + for (var i = 0; i < size; i++) { + column = columns[i]; + cname = column.getName().toUpperCase(); + column.setName(cname); + } + table.setDirty(true); + keys = table.getKeys(); + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + if(!key.isFK()){ + kname = key.getName().toUpperCase(); + key.setName(kname); + }else{ + kname = key.getFKAssociation().getName().toUpperCase(); + key.getFKAssociation().setName(kname); + key.getFKAssociation().setDirty(true); + } + } +}]]> + </script> + </scr> + <scr id="0528C35C-F29B-E7BB-57AC-37BA2780A98D" name="Table template - uses column name" object="relational" engine="Mozilla Rhino" type="" var="model" library="" method="" purpose="transformation" > + <script> + <![CDATA[// version without usage of dynamic properties, columns are found by column name +// this allow reuse of already existing columns +var t_name = "table_template"; +template = model.getTableSet().getByName(t_name); +if(template!=null){ + tcolumns = template.getElements(); + tables = model.getTableSet().toArray(); + for (var t = 0; t<tables.length;t++){ + table = tables[t]; + // compare name ignoring the case + if(!table.getName().equalsIgnoreCase(t_name)){ + for (var i = 0; i < tcolumns.length; i++) { + column = tcolumns[i]; + col = table.getElementByName(column.getName()); + if(col==null){ + col = table.createColumn(); + } + column.copy(col); + table.setDirty(true); + } + } + } +}]]> + </script> + </scr> + <scr id="6279C414-90DD-A52B-4CEB-8D49AB31DC10" name="Copy Comments to Comments in RDBMS" object="relational" engine="Mozilla Rhino" type="" var="model" library="" method="" purpose="transformation" > + <script> + <![CDATA[max_length = 4000; +function copyComments(object){ + if(object.getCommentInRDBMS().equals("")){ + if(!object.getComment().equals("")){ + if(object.getComment().length()>max_length){ + object.setCommentInRDBMS(object.getComment().substring(0, max_length)); + }else{ + object.setCommentInRDBMS(object.getComment()); + } + object.setDirty(true); + } + } +} + +tables = model.getTableSet().toArray(); +for (var t = 0; t<tables.length;t++){ + table = tables[t] + copyComments(table); + columns = table.getElements(); + size = table.getElementsCollection().size(); + for (var i = 0; i < columns.length; i++) { + column = columns[i]; + copyComments(column); + } + keys = table.getKeys(); + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + if(!key.isFK()){ + copyComments(key); + }else{ + copyComments(key.getFKAssociation()); + } + } +}]]> + </script> + </scr> + <scr id="7C4EDFC0-26EA-859C-DBD9-AC9345DEAF98" name="Create index on FK" object="relational" engine="Mozilla Rhino" type="" var="model" library="" method="" purpose="transformation" > + <script> + <![CDATA[function getIndex(tab,cols){ + keys = tab.getKeys(); + for (var i = 0; i < keys.length; i++) { + index = keys[i]; + if(!(index.isPK() || index.isUnique()) && !index.isFK() && index.isIndexForColumns(cols)){ + return index + } + } + return null; +} + +tables = model.getTableSet().toArray(); +for (var t = 0; t<tables.length;t++){ + table = tables[t]; + indexes = table.getKeys(); + for (var i = 0; i < indexes.length; i++) { + index = indexes[i]; + if(index.isFK()){ + columns = index.getColumns(); + if(columns.length>0){ + newIndex = getIndex(table,columns); + if(newIndex==null){ + newIndex = table.createIndex() + table.setDirty(true); + for (var k = 0; k < columns.length; k++){ + newIndex.add(columns[k]); + } + } + } + } + } +}]]> + </script> + </scr> + + <lib id="B310E434-78AE-6AED-EA94-6808B0262483" name="my first library" engine="Mozilla Rhino" methods="checkcomments" > + <script> + <![CDATA[var ruleMessage; +var errType; +var table; +function checkcomments(object){ + result = true; + ruleMessage=""; + if(table.getCommentInRDBMS().equals("")){ + ruleMessage="no comments in RDBMS defined"; + errType="Problem:"; + result = false; + } + if(table.getComment().equals("")){ + if(ruleMessage.equals("")){ + ruleMessage="no comments defined"; + }else{ + ruleMessage= ruleMessage +" , no comments defined"; + } + errType="Error"; + return false; + } + return result; +}]]> + </script> + </lib> + <lib id="2518F33A-DE50-9E1D-7216-DD2A0FD6B84C" name="Jruby lib" engine="JRuby Engine" methods="tables_up" > + <script> + <![CDATA[def tables_up(model) +tables = model.getTableSet().toArray() +for t in 0..tables.length-1 + table = tables[t] + name = table.getName().upcase + table.setName(name) + columns = table.getElements() + size = table.getElementsCollection().size()-1 + for i in 0..size + column = columns[i] + cname = column.getName().upcase + column.setName(cname) + end + keys = table.getKeys() + for i in 0..keys.length-1 + key = keys[i] + kname = key.getName().upcase + key.setName(kname) + end +end +return true +end]]> + </script> + </lib> +</dr_custom_scripts>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/Logical.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/Logical.xml new file mode 100644 index 00000000..0403a605 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/Logical.xml @@ -0,0 +1,7 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<LogicalDesign class="oracle.dbtools.crest.model.design.logical.LogicalDesign" name="Logical" id="E3665D68-35D3-8757-63ED-30AEFB972A2C" mainViewID="AFCEF013-4CF2-4A5A-79A3-31521C1CA20A"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 21:58:45 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<shouldBeOpen>false</shouldBeOpen> +</LogicalDesign>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/16464F5A-64BE-D2ED-91E0-BCBD0AA34680.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/16464F5A-64BE-D2ED-91E0-BCBD0AA34680.xml new file mode 100644 index 00000000..6904bb54 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/16464F5A-64BE-D2ED-91E0-BCBD0AA34680.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="16464F5A-64BE-D2ED-91E0-BCBD0AA34680" directorySegmentName="seg_0" name="TestResults"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:11:26 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/1BEAB532-23CA-8628-0C97-7CAD39119A4E.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/1BEAB532-23CA-8628-0C97-7CAD39119A4E.xml new file mode 100644 index 00000000..9f54c6cd --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/1BEAB532-23CA-8628-0C97-7CAD39119A4E.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="1BEAB532-23CA-8628-0C97-7CAD39119A4E" directorySegmentName="seg_0" name="TestCaseArgs"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:38:18 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/24150FB1-B00F-4F69-6F77-49ECB58F0F66.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/24150FB1-B00F-4F69-6F77-49ECB58F0F66.xml new file mode 100644 index 00000000..3a02553a --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/24150FB1-B00F-4F69-6F77-49ECB58F0F66.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="24150FB1-B00F-4F69-6F77-49ECB58F0F66" directorySegmentName="seg_0" name="BuildSources"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 08:54:55 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/28DD93CF-D058-7343-CD47-E9B435E1AC16.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/28DD93CF-D058-7343-CD47-E9B435E1AC16.xml new file mode 100644 index 00000000..3a9992c9 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/28DD93CF-D058-7343-CD47-E9B435E1AC16.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="28DD93CF-D058-7343-CD47-E9B435E1AC16" directorySegmentName="seg_0" name="TestResultFiles"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:12:51 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/2F6ACC6D-3D17-537D-8ADF-F8424395B345.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/2F6ACC6D-3D17-537D-8ADF-F8424395B345.xml new file mode 100644 index 00000000..4ea40fc7 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/2F6ACC6D-3D17-537D-8ADF-F8424395B345.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="2F6ACC6D-3D17-537D-8ADF-F8424395B345" directorySegmentName="seg_0" name="GlobalRsrcStatuses"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:17:42 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/44FFF5E9-0C2F-7BAC-B5B7-73CA3A230B39.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/44FFF5E9-0C2F-7BAC-B5B7-73CA3A230B39.xml new file mode 100644 index 00000000..e3300354 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/44FFF5E9-0C2F-7BAC-B5B7-73CA3A230B39.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="44FFF5E9-0C2F-7BAC-B5B7-73CA3A230B39" directorySegmentName="seg_0" name="FailureReasons"> +<createdBy>bird</createdBy> +<createdTime>2012-08-22 11:47:11 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/4579B792-2F35-D72A-1A3B-C7E53C41A766.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/4579B792-2F35-D72A-1A3B-C7E53C41A766.xml new file mode 100644 index 00000000..e35d8bc0 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/4579B792-2F35-D72A-1A3B-C7E53C41A766.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="4579B792-2F35-D72A-1A3B-C7E53C41A766" directorySegmentName="seg_0" name="TestResultMsgs"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:13:03 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/4D937E7C-3A28-E52D-89C0-EC8804C62367.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/4D937E7C-3A28-E52D-89C0-EC8804C62367.xml new file mode 100644 index 00000000..7b2d1d01 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/4D937E7C-3A28-E52D-89C0-EC8804C62367.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="4D937E7C-3A28-E52D-89C0-EC8804C62367" directorySegmentName="seg_0" name="FailureCategories"> +<createdBy>bird</createdBy> +<createdTime>2012-08-22 11:47:19 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/504221DA-1B57-4EAD-39DB-40FD553E9FA2.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/504221DA-1B57-4EAD-39DB-40FD553E9FA2.xml new file mode 100644 index 00000000..e536867c --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/504221DA-1B57-4EAD-39DB-40FD553E9FA2.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="504221DA-1B57-4EAD-39DB-40FD553E9FA2" directorySegmentName="seg_0" name="Builds"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 08:52:15 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/6A886CEE-579B-48FF-63F6-0FB03393FBF6.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/6A886CEE-579B-48FF-63F6-0FB03393FBF6.xml new file mode 100644 index 00000000..20424c7c --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/6A886CEE-579B-48FF-63F6-0FB03393FBF6.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="6A886CEE-579B-48FF-63F6-0FB03393FBF6" directorySegmentName="seg_0" name="SchedGroups"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:16:15 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/7AE36CC1-A030-63E5-6EF3-72FCD04815EE.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/7AE36CC1-A030-63E5-6EF3-72FCD04815EE.xml new file mode 100644 index 00000000..9475385d --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/7AE36CC1-A030-63E5-6EF3-72FCD04815EE.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="7AE36CC1-A030-63E5-6EF3-72FCD04815EE" directorySegmentName="seg_0" name="TestBoxes"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:34:30 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/90367AFB-BA2D-A918-46B9-1E5DE53ACC48.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/90367AFB-BA2D-A918-46B9-1E5DE53ACC48.xml new file mode 100644 index 00000000..96b815e1 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/90367AFB-BA2D-A918-46B9-1E5DE53ACC48.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="90367AFB-BA2D-A918-46B9-1E5DE53ACC48" directorySegmentName="seg_0" name="BuildBlacklist"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 08:59:31 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/90F477EE-35D6-21A7-B693-E5724FB07476.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/90F477EE-35D6-21A7-B693-E5724FB07476.xml new file mode 100644 index 00000000..6bcf734c --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/90F477EE-35D6-21A7-B693-E5724FB07476.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="90F477EE-35D6-21A7-B693-E5724FB07476" directorySegmentName="seg_0" name="TestSets"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:11:20 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/9F78B73C-056D-DDEF-8C50-A9DA76B9E724.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/9F78B73C-056D-DDEF-8C50-A9DA76B9E724.xml new file mode 100644 index 00000000..d672b27e --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/9F78B73C-056D-DDEF-8C50-A9DA76B9E724.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="9F78B73C-056D-DDEF-8C50-A9DA76B9E724" directorySegmentName="seg_0" name="BuildTypes"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 08:52:32 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/A352A20F-310D-E285-FBC9-90DD0DA7BB9B.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/A352A20F-310D-E285-FBC9-90DD0DA7BB9B.xml new file mode 100644 index 00000000..301a3f28 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/A352A20F-310D-E285-FBC9-90DD0DA7BB9B.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="A352A20F-310D-E285-FBC9-90DD0DA7BB9B" directorySegmentName="seg_0" name="TestBoxStatuses"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:09:55 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/A6A5F317-479C-A0DD-CAAE-9DCB56B29D40.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/A6A5F317-479C-A0DD-CAAE-9DCB56B29D40.xml new file mode 100644 index 00000000..a6f31387 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/A6A5F317-479C-A0DD-CAAE-9DCB56B29D40.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="A6A5F317-479C-A0DD-CAAE-9DCB56B29D40" directorySegmentName="seg_0" name="RequirementSets"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:14:04 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/B36A186B-CDB3-7851-8C38-12EA8D50EAEB.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/B36A186B-CDB3-7851-8C38-12EA8D50EAEB.xml new file mode 100644 index 00000000..7e22bcc2 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/B36A186B-CDB3-7851-8C38-12EA8D50EAEB.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="B36A186B-CDB3-7851-8C38-12EA8D50EAEB" directorySegmentName="seg_0" name="RequirementsNum"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:14:37 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/B82DAF9A-6F99-5CF6-4D99-A391BAD66192.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/B82DAF9A-6F99-5CF6-4D99-A391BAD66192.xml new file mode 100644 index 00000000..aa84dcf3 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/B82DAF9A-6F99-5CF6-4D99-A391BAD66192.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="B82DAF9A-6F99-5CF6-4D99-A391BAD66192" directorySegmentName="seg_0" name="TestCases"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:34:30 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/C332E3D7-638B-6CA8-24BF-383CA8659A3A.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/C332E3D7-638B-6CA8-24BF-383CA8659A3A.xml new file mode 100644 index 00000000..f093d805 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/C332E3D7-638B-6CA8-24BF-383CA8659A3A.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="C332E3D7-638B-6CA8-24BF-383CA8659A3A" directorySegmentName="seg_0" name="SchedQueues"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:09:44 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/C79482B8-771B-FAD8-0337-163E3A45003A.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/C79482B8-771B-FAD8-0337-163E3A45003A.xml new file mode 100644 index 00000000..3550b18c --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/C79482B8-771B-FAD8-0337-163E3A45003A.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="C79482B8-771B-FAD8-0337-163E3A45003A" directorySegmentName="seg_0" name="GlobalResources"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:13:16 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/D09E0DE5-99D6-2991-032A-A8A124F6ACBA.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/D09E0DE5-99D6-2991-032A-A8A124F6ACBA.xml new file mode 100644 index 00000000..1e10ffb7 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/D09E0DE5-99D6-2991-032A-A8A124F6ACBA.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="D09E0DE5-99D6-2991-032A-A8A124F6ACBA" directorySegmentName="seg_0" name="TestResultValues"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:11:32 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/DCC79294-5434-1DED-298C-6473DEE59FBA.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/DCC79294-5434-1DED-298C-6473DEE59FBA.xml new file mode 100644 index 00000000..7891dab7 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/DCC79294-5434-1DED-298C-6473DEE59FBA.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="DCC79294-5434-1DED-298C-6473DEE59FBA" directorySegmentName="seg_0" name="TestResultFailures"> +<createdBy>bird</createdBy> +<createdTime>2012-08-22 11:46:51 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/DE366053-6F7A-7F42-ABA3-00E583098C37.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/DE366053-6F7A-7F42-ABA3-00E583098C37.xml new file mode 100644 index 00000000..145b2c76 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/DE366053-6F7A-7F42-ABA3-00E583098C37.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="DE366053-6F7A-7F42-ABA3-00E583098C37" directorySegmentName="seg_0" name="TestGroups"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:34:30 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/E93BBF08-067B-A665-39F3-CF488A6547B2.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/E93BBF08-067B-A665-39F3-CF488A6547B2.xml new file mode 100644 index 00000000..c8632bf7 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/entity/seg_0/E93BBF08-067B-A665-39F3-CF488A6547B2.xml @@ -0,0 +1,52 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Entity class="oracle.dbtools.crest.model.design.logical.Entity" id="E93BBF08-067B-A665-39F3-CF488A6547B2" directorySegmentName="seg_0" name="RequirementsText"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:14:21 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<adequatelyNormalized>NO</adequatelyNormalized> +<expectedVolumes>0</expectedVolumes> +<fwdEngineeringStrategyName>Single Table</fwdEngineeringStrategyName> +<growthPercent>0</growthPercent> +<growthType>Year</growthType> +<maxVolumes>9999999</maxVolumes> +<minVolumes>0</minVolumes> +<normalForm>Third</normalForm> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<fontStyle>1</fontStyle> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Attribute</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Datatype</foType> +<colorRGB>-16744448</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>PK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>FK Element</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>UK Element</foType> +<colorRGB>-16776961</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Not Null</foType> +<colorRGB>-65536</colorRGB> +</FontObject> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Key</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Entity>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/note/seg_0/876CB767-80BA-6C8E-AACA-F1CCC95C445E.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/note/seg_0/876CB767-80BA-6C8E-AACA-F1CCC95C445E.xml new file mode 100644 index 00000000..31ddc417 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/note/seg_0/876CB767-80BA-6C8E-AACA-F1CCC95C445E.xml @@ -0,0 +1,16 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Note class="oracle.dbtools.crest.model.design.Note" name="Note_1" id="876CB767-80BA-6C8E-AACA-F1CCC95C445E" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 08:43:49 UTC</createdTime> +<comment>Priority, scheduling time, and testgroup dependencies are associated with SchedGroup membership.</comment> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Note>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/note/seg_0/D487AFDC-4027-F824-EA29-5C6D0ABB9E1E.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/note/seg_0/D487AFDC-4027-F824-EA29-5C6D0ABB9E1E.xml new file mode 100644 index 00000000..9152a7c6 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/note/seg_0/D487AFDC-4027-F824-EA29-5C6D0ABB9E1E.xml @@ -0,0 +1,16 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Note class="oracle.dbtools.crest.model.design.Note" name="Note_3" id="D487AFDC-4027-F824-EA29-5C6D0ABB9E1E" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 08:57:21 UTC</createdTime> +<comment>Testsuite and build sources.</comment> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<fonts> +<FontObject class="oracle.dbtools.crest.model.design.FontObjectWr"> +<foType>Title</foType> +<colorRGB>-16777216</colorRGB> +</FontObject> +</fonts> +</Note>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/01537211-CCFB-0A1E-B43B-E8C641B69471.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/01537211-CCFB-0A1E-B43B-E8C641B69471.xml new file mode 100644 index 00000000..e8b317cd --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/01537211-CCFB-0A1E-B43B-E8C641B69471.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="WhichTestcaseArgs" id="01537211-CCFB-0A1E-B43B-E8C641B69471" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:57:18 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>false</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>*</sourceCardinality> +<sourceEntity>90F477EE-35D6-21A7-B693-E5724FB07476</sourceEntity> +<targetCardinalityString>1</targetCardinalityString> +<targetEntity>1BEAB532-23CA-8628-0C97-7CAD39119A4E</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/02096BBB-0795-1759-1E26-2877BE36BB59.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/02096BBB-0795-1759-1E26-2877BE36BB59.xml new file mode 100644 index 00000000..48df0a07 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/02096BBB-0795-1759-1E26-2877BE36BB59.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="NestedTestResults" id="02096BBB-0795-1759-1E26-2877BE36BB59" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:16:26 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>true</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>16464F5A-64BE-D2ED-91E0-BCBD0AA34680</sourceEntity> +<targetCardinalityString>*</targetCardinalityString> +<targetEntity>16464F5A-64BE-D2ED-91E0-BCBD0AA34680</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/0CCF1DE3-7916-9054-BEA6-C601FF564DB2.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/0CCF1DE3-7916-9054-BEA6-C601FF564DB2.xml new file mode 100644 index 00000000..e5304e1d --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/0CCF1DE3-7916-9054-BEA6-C601FF564DB2.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="TestBoxGrouping" id="0CCF1DE3-7916-9054-BEA6-C601FF564DB2" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:35:28 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>true</identifying> +<optionalSource>false</optionalSource> +<optionalTarget>false</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>7AE36CC1-A030-63E5-6EF3-72FCD04815EE</sourceEntity> +<targetCardinalityString>*</targetCardinalityString> +<targetEntity>6A886CEE-579B-48FF-63F6-0FB03393FBF6</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/10867E70-94CE-FDAF-6B6E-2742D3A49E57.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/10867E70-94CE-FDAF-6B6E-2742D3A49E57.xml new file mode 100644 index 00000000..ed642271 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/10867E70-94CE-FDAF-6B6E-2742D3A49E57.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="ReasonForBlacklisting" id="10867E70-94CE-FDAF-6B6E-2742D3A49E57" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-22 11:56:22 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>false</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>90367AFB-BA2D-A918-46B9-1E5DE53ACC48</sourceEntity> +<targetCardinalityString>1</targetCardinalityString> +<targetEntity>44FFF5E9-0C2F-7BAC-B5B7-73CA3A230B39</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/11710A55-6423-1904-841A-C7D2AB8CEEBF.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/11710A55-6423-1904-841A-C7D2AB8CEEBF.xml new file mode 100644 index 00000000..4c37ff79 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/11710A55-6423-1904-841A-C7D2AB8CEEBF.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="TestResultValues\" id="11710A55-6423-1904-841A-C7D2AB8CEEBF" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:17:15 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>true</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>16464F5A-64BE-D2ED-91E0-BCBD0AA34680</sourceEntity> +<targetCardinalityString>*</targetCardinalityString> +<targetEntity>D09E0DE5-99D6-2991-032A-A8A124F6ACBA</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/1C189437-742B-B999-C955-7754C8ADB089.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/1C189437-742B-B999-C955-7754C8ADB089.xml new file mode 100644 index 00000000..ee340833 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/1C189437-742B-B999-C955-7754C8ADB089.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="SchedTestGroupMembership" id="1C189437-742B-B999-C955-7754C8ADB089" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:46:08 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>true</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>*</sourceCardinality> +<sourceEntity>6A886CEE-579B-48FF-63F6-0FB03393FBF6</sourceEntity> +<targetCardinalityString>*</targetCardinalityString> +<targetEntity>DE366053-6F7A-7F42-ABA3-00E583098C37</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/34733942-1305-4CA1-47EB-ACE724B04E69.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/34733942-1305-4CA1-47EB-ACE724B04E69.xml new file mode 100644 index 00000000..bde14e2c --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/34733942-1305-4CA1-47EB-ACE724B04E69.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="TestResultFiles" id="34733942-1305-4CA1-47EB-ACE724B04E69" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:16:58 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>true</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>16464F5A-64BE-D2ED-91E0-BCBD0AA34680</sourceEntity> +<targetCardinalityString>*</targetCardinalityString> +<targetEntity>28DD93CF-D058-7343-CD47-E9B435E1AC16</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/3563C940-E524-7F96-7AE0-DAC3C1C17AFC.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/3563C940-E524-7F96-7AE0-DAC3C1C17AFC.xml new file mode 100644 index 00000000..0d924eae --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/3563C940-E524-7F96-7AE0-DAC3C1C17AFC.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="TestedBuild" id="3563C940-E524-7F96-7AE0-DAC3C1C17AFC" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 10:14:03 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>true</identifying> +<optionalSource>true</optionalSource> +<optionalTarget>false</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>504221DA-1B57-4EAD-39DB-40FD553E9FA2</sourceEntity> +<targetCardinalityString>*</targetCardinalityString> +<targetEntity>90F477EE-35D6-21A7-B693-E5724FB07476</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/3983F50A-EBB9-E4DE-1958-60EA4EDD6D6C.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/3983F50A-EBB9-E4DE-1958-60EA4EDD6D6C.xml new file mode 100644 index 00000000..f0a22501 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/3983F50A-EBB9-E4DE-1958-60EA4EDD6D6C.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="BuildSource" id="3983F50A-EBB9-E4DE-1958-60EA4EDD6D6C" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 08:55:43 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>true</optionalSource> +<optionalTarget>false</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>24150FB1-B00F-4F69-6F77-49ECB58F0F66</sourceEntity> +<targetCardinalityString>*</targetCardinalityString> +<targetEntity>6A886CEE-579B-48FF-63F6-0FB03393FBF6</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/3B7C8913-EB6A-47B1-27D0-E2C85EE9048B.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/3B7C8913-EB6A-47B1-27D0-E2C85EE9048B.xml new file mode 100644 index 00000000..9a95a66a --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/3B7C8913-EB6A-47B1-27D0-E2C85EE9048B.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="NumericalRequirement" id="3B7C8913-EB6A-47B1-27D0-E2C85EE9048B" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:41:40 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>true</identifying> +<optionalSource>true</optionalSource> +<optionalTarget>false</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>A6A5F317-479C-A0DD-CAAE-9DCB56B29D40</sourceEntity> +<targetCardinalityString>*</targetCardinalityString> +<targetEntity>B36A186B-CDB3-7851-8C38-12EA8D50EAEB</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/518CE489-97B4-C05C-07A2-E3DBF14EE267.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/518CE489-97B4-C05C-07A2-E3DBF14EE267.xml new file mode 100644 index 00000000..7987194b --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/518CE489-97B4-C05C-07A2-E3DBF14EE267.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="TestResultFailureReason" id="518CE489-97B4-C05C-07A2-E3DBF14EE267" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-22 11:58:35 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>false</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>DCC79294-5434-1DED-298C-6473DEE59FBA</sourceEntity> +<targetCardinalityString>1</targetCardinalityString> +<targetEntity>44FFF5E9-0C2F-7BAC-B5B7-73CA3A230B39</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/68A0C3E1-0FA1-8414-A361-33B08A8EDB39.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/68A0C3E1-0FA1-8414-A361-33B08A8EDB39.xml new file mode 100644 index 00000000..bf2200dc --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/68A0C3E1-0FA1-8414-A361-33B08A8EDB39.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="FailureRegardingTestResult" id="68A0C3E1-0FA1-8414-A361-33B08A8EDB39" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-22 11:48:45 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>false</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>DCC79294-5434-1DED-298C-6473DEE59FBA</sourceEntity> +<targetCardinalityString>1</targetCardinalityString> +<targetEntity>16464F5A-64BE-D2ED-91E0-BCBD0AA34680</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/7497D76B-781B-3BDD-D797-FFBDB974F772.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/7497D76B-781B-3BDD-D797-FFBDB974F772.xml new file mode 100644 index 00000000..43673229 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/7497D76B-781B-3BDD-D797-FFBDB974F772.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="GlobalResourceDependencies" id="7497D76B-781B-3BDD-D797-FFBDB974F772" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:42:25 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>true</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>*</sourceCardinality> +<sourceEntity>B82DAF9A-6F99-5CF6-4D99-A391BAD66192</sourceEntity> +<targetCardinalityString>*</targetCardinalityString> +<targetEntity>C79482B8-771B-FAD8-0337-163E3A45003A</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/7DA9DD83-A52E-CA1E-FCBF-FC9CE71AF635.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/7DA9DD83-A52E-CA1E-FCBF-FC9CE71AF635.xml new file mode 100644 index 00000000..dd75d4cb --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/7DA9DD83-A52E-CA1E-FCBF-FC9CE71AF635.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="TestResultMessages" id="7DA9DD83-A52E-CA1E-FCBF-FC9CE71AF635" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:17:23 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>true</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>16464F5A-64BE-D2ED-91E0-BCBD0AA34680</sourceEntity> +<targetCardinalityString>*</targetCardinalityString> +<targetEntity>4579B792-2F35-D72A-1A3B-C7E53C41A766</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/89A83E25-364B-6B73-0613-FEAD875EF9FB.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/89A83E25-364B-6B73-0613-FEAD875EF9FB.xml new file mode 100644 index 00000000..e8a4730c --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/89A83E25-364B-6B73-0613-FEAD875EF9FB.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="TestcaseArguments" id="89A83E25-364B-6B73-0613-FEAD875EF9FB" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:40:39 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>false</optionalSource> +<optionalTarget>false</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>B82DAF9A-6F99-5CF6-4D99-A391BAD66192</sourceEntity> +<targetCardinalityString>*</targetCardinalityString> +<targetEntity>1BEAB532-23CA-8628-0C97-7CAD39119A4E</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/8E5018CC-34E3-9AFC-D6D1-31E2BC4E9FE2.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/8E5018CC-34E3-9AFC-D6D1-31E2BC4E9FE2.xml new file mode 100644 index 00000000..9d086559 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/8E5018CC-34E3-9AFC-D6D1-31E2BC4E9FE2.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="WhatToRun" id="8E5018CC-34E3-9AFC-D6D1-31E2BC4E9FE2" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:41:56 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>false</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>*</sourceCardinality> +<sourceEntity>C332E3D7-638B-6CA8-24BF-383CA8659A3A</sourceEntity> +<targetCardinalityString>*</targetCardinalityString> +<targetEntity>1BEAB532-23CA-8628-0C97-7CAD39119A4E</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/9B1FE0CF-B2AD-EED0-22FC-461A7D46DE51.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/9B1FE0CF-B2AD-EED0-22FC-461A7D46DE51.xml new file mode 100644 index 00000000..b50ed32a --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/9B1FE0CF-B2AD-EED0-22FC-461A7D46DE51.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="WhichResource" id="9B1FE0CF-B2AD-EED0-22FC-461A7D46DE51" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:52:20 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>false</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>2F6ACC6D-3D17-537D-8ADF-F8424395B345</sourceEntity> +<targetCardinalityString>1</targetCardinalityString> +<targetEntity>C79482B8-771B-FAD8-0337-163E3A45003A</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/A182A65A-47AE-5D00-9A30-BC20AB050BF2.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/A182A65A-47AE-5D00-9A30-BC20AB050BF2.xml new file mode 100644 index 00000000..b29652bd --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/A182A65A-47AE-5D00-9A30-BC20AB050BF2.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="TestSetResult" id="A182A65A-47AE-5D00-9A30-BC20AB050BF2" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:15:48 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>true</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>90F477EE-35D6-21A7-B693-E5724FB07476</sourceEntity> +<targetCardinalityString>1</targetCardinalityString> +<targetEntity>16464F5A-64BE-D2ED-91E0-BCBD0AA34680</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/B346381F-48FE-E495-01A7-E22EC26AEE8A.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/B346381F-48FE-E495-01A7-E22EC26AEE8A.xml new file mode 100644 index 00000000..ba60f398 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/B346381F-48FE-E495-01A7-E22EC26AEE8A.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="TestGroupMember" id="B346381F-48FE-E495-01A7-E22EC26AEE8A" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:37:24 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>true</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>*</sourceCardinality> +<sourceEntity>B82DAF9A-6F99-5CF6-4D99-A391BAD66192</sourceEntity> +<targetCardinalityString>*</targetCardinalityString> +<targetEntity>DE366053-6F7A-7F42-ABA3-00E583098C37</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/B3596116-540F-6397-ECE4-58A386644E15.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/B3596116-540F-6397-ECE4-58A386644E15.xml new file mode 100644 index 00000000..d4f9edd8 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/B3596116-540F-6397-ECE4-58A386644E15.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="TestcaseDependencies" id="B3596116-540F-6397-ECE4-58A386644E15" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:39:51 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>true</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>*</sourceCardinality> +<sourceEntity>B82DAF9A-6F99-5CF6-4D99-A391BAD66192</sourceEntity> +<targetCardinalityString>1</targetCardinalityString> +<targetEntity>B82DAF9A-6F99-5CF6-4D99-A391BAD66192</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/BAD8EC05-6F14-4E38-366C-B4B660C6F38A.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/BAD8EC05-6F14-4E38-366C-B4B660C6F38A.xml new file mode 100644 index 00000000..da1e2a8f --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/BAD8EC05-6F14-4E38-366C-B4B660C6F38A.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="InFailureCategory" id="BAD8EC05-6F14-4E38-366C-B4B660C6F38A" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-22 11:57:18 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>true</identifying> +<optionalSource>false</optionalSource> +<optionalTarget>false</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>44FFF5E9-0C2F-7BAC-B5B7-73CA3A230B39</sourceEntity> +<targetCardinalityString>*</targetCardinalityString> +<targetEntity>4D937E7C-3A28-E52D-89C0-EC8804C62367</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/C5B67DD4-FA4F-EF9F-1FF5-0445D51B32EE.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/C5B67DD4-FA4F-EF9F-1FF5-0445D51B32EE.xml new file mode 100644 index 00000000..d75c9a0a --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/C5B67DD4-FA4F-EF9F-1FF5-0445D51B32EE.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="WhichTestBox" id="C5B67DD4-FA4F-EF9F-1FF5-0445D51B32EE" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:59:42 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>false</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>*</sourceCardinality> +<sourceEntity>90F477EE-35D6-21A7-B693-E5724FB07476</sourceEntity> +<targetCardinalityString>1</targetCardinalityString> +<targetEntity>7AE36CC1-A030-63E5-6EF3-72FCD04815EE</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/CCD38E11-8557-EB34-2651-07EB29E83FA6.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/CCD38E11-8557-EB34-2651-07EB29E83FA6.xml new file mode 100644 index 00000000..bf216b5d --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/CCD38E11-8557-EB34-2651-07EB29E83FA6.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="TestSuiteSource" id="CCD38E11-8557-EB34-2651-07EB29E83FA6" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 08:56:11 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>true</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>24150FB1-B00F-4F69-6F77-49ECB58F0F66</sourceEntity> +<targetCardinalityString>*</targetCardinalityString> +<targetEntity>6A886CEE-579B-48FF-63F6-0FB03393FBF6</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/E2A47942-ED55-E81D-4C71-9A134C49C147.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/E2A47942-ED55-E81D-4C71-9A134C49C147.xml new file mode 100644 index 00000000..5164076c --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/E2A47942-ED55-E81D-4C71-9A134C49C147.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="TestBox" id="E2A47942-ED55-E81D-4C71-9A134C49C147" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:43:14 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>false</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>7AE36CC1-A030-63E5-6EF3-72FCD04815EE</sourceEntity> +<targetCardinalityString>*</targetCardinalityString> +<targetEntity>A352A20F-310D-E285-FBC9-90DD0DA7BB9B</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/E4FE88E9-EE21-B43B-B0FE-A153E38246F9.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/E4FE88E9-EE21-B43B-B0FE-A153E38246F9.xml new file mode 100644 index 00000000..fc0ec020 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/E4FE88E9-EE21-B43B-B0FE-A153E38246F9.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="TestcaseRequirements" id="E4FE88E9-EE21-B43B-B0FE-A153E38246F9" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:38:38 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>true</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>*</sourceCardinality> +<sourceEntity>B82DAF9A-6F99-5CF6-4D99-A391BAD66192</sourceEntity> +<targetCardinalityString>1</targetCardinalityString> +<targetEntity>A6A5F317-479C-A0DD-CAAE-9DCB56B29D40</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/E62AE7DF-49EE-9280-B328-A867CBD273AE.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/E62AE7DF-49EE-9280-B328-A867CBD273AE.xml new file mode 100644 index 00000000..3121966f --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/E62AE7DF-49EE-9280-B328-A867CBD273AE.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="CurrentTestSet" id="E62AE7DF-49EE-9280-B328-A867CBD273AE" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:48:53 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>true</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>A352A20F-310D-E285-FBC9-90DD0DA7BB9B</sourceEntity> +<targetCardinalityString>1</targetCardinalityString> +<targetEntity>90F477EE-35D6-21A7-B693-E5724FB07476</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/E74406B5-20F1-4323-DC99-6E45982CB606.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/E74406B5-20F1-4323-DC99-6E45982CB606.xml new file mode 100644 index 00000000..498ce1fb --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/E74406B5-20F1-4323-DC99-6E45982CB606.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="TextRequirements" id="E74406B5-20F1-4323-DC99-6E45982CB606" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:41:57 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>true</identifying> +<optionalSource>true</optionalSource> +<optionalTarget>false</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>A6A5F317-479C-A0DD-CAAE-9DCB56B29D40</sourceEntity> +<targetCardinalityString>*</targetCardinalityString> +<targetEntity>E93BBF08-067B-A665-39F3-CF488A6547B2</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/EC4EB506-3DBE-7F36-6451-F31920EDAB52.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/EC4EB506-3DBE-7F36-6451-F31920EDAB52.xml new file mode 100644 index 00000000..18840e25 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/EC4EB506-3DBE-7F36-6451-F31920EDAB52.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="AllocatedBy" id="EC4EB506-3DBE-7F36-6451-F31920EDAB52" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:44:47 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>true</optionalSource> +<optionalTarget>true</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>7AE36CC1-A030-63E5-6EF3-72FCD04815EE</sourceEntity> +<targetCardinalityString>*</targetCardinalityString> +<targetEntity>2F6ACC6D-3D17-537D-8ADF-F8424395B345</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/EE1D98EF-6AEA-2790-D9B9-DBC2ED21D880.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/EE1D98EF-6AEA-2790-D9B9-DBC2ED21D880.xml new file mode 100644 index 00000000..6fcc7e2b --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/relation/seg_0/EE1D98EF-6AEA-2790-D9B9-DBC2ED21D880.xml @@ -0,0 +1,17 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Relation class="oracle.dbtools.crest.model.design.logical.Relation" name="BuildToType" id="EE1D98EF-6AEA-2790-D9B9-DBC2ED21D880" directorySegmentName="seg_0"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 08:53:25 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<engineerTo> +<item key="B082B14A-BEA8-D8A7-D661-197F34766ED3" value="true"/> +</engineerTo> +<identifying>false</identifying> +<optionalSource>true</optionalSource> +<optionalTarget>false</optionalTarget> +<sourceCardinality>1</sourceCardinality> +<sourceEntity>9F78B73C-056D-DDEF-8C50-A9DA76B9E724</sourceEntity> +<targetCardinalityString>*</targetCardinalityString> +<targetEntity>504221DA-1B57-4EAD-39DB-40FD553E9FA2</targetEntity> +<transferable>true</transferable> +</Relation>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/subviews/016BA1CF-6EA4-9CA4-CDF7-3AAA507EF6EF.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/subviews/016BA1CF-6EA4-9CA4-CDF7-3AAA507EF6EF.xml new file mode 100644 index 00000000..e947c03a --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/subviews/016BA1CF-6EA4-9CA4-CDF7-3AAA507EF6EF.xml @@ -0,0 +1,40 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Diagram class="oracle.dbtools.crest.swingui.logical.DPVLogicalSubView" name="Failure Tracking" id="016BA1CF-6EA4-9CA4-CDF7-3AAA507EF6EF"> +<createdBy>bird</createdBy> +<createdTime>2012-08-22 12:01:22 UTC</createdTime> +<autoRoute>false</autoRoute> +<boxInbox>true</boxInbox> +<showLegend>false</showLegend> +<showLabels>false</showLabels> +<showGrid>false</showGrid> +<diagramColor>-1</diagramColor> +<display>false</display> +<notation>0</notation> +<objectViews> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="44FFF5E9-0C2F-7BAC-B5B7-73CA3A230B39" otype="Entity" vid="D1B4D1DF-E3AB-F84A-F479-87FB68F0A2D2"> +<bounds x="1270" y="448" width="151" height="41"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="4D937E7C-3A28-E52D-89C0-EC8804C62367" otype="Entity" vid="37DED3CC-443D-FC8B-A30D-07BF0D742C62"> +<bounds x="1270" y="522" width="152" height="43"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="DCC79294-5434-1DED-298C-6473DEE59FBA" otype="Entity" vid="95A5D57E-9986-0942-BCE8-4B9F5F46AE30"> +<bounds x="1087" y="460" width="157" height="51"/> +</OView> +</objectViews> +<connectors> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="518CE489-97B4-C05C-07A2-E3DBF14EE267" otype="Relation" vid_source="95A5D57E-9986-0942-BCE8-4B9F5F46AE30" vid_target="D1B4D1DF-E3AB-F84A-F479-87FB68F0A2D2"> +<lineWidth>1</lineWidth> +<points> +<point x="1244" y="474"/> +<point x="1270" y="474"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="BAD8EC05-6F14-4E38-366C-B4B660C6F38A" otype="Relation" vid_source="D1B4D1DF-E3AB-F84A-F479-87FB68F0A2D2" vid_target="37DED3CC-443D-FC8B-A30D-07BF0D742C62"> +<lineWidth>1</lineWidth> +<points> +<point x="1345" y="489"/> +<point x="1345" y="522"/> +</points> +</Connector> +</connectors> +</Diagram>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/subviews/32D718B4-250F-95DC-37F0-C0A817F69020.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/subviews/32D718B4-250F-95DC-37F0-C0A817F69020.xml new file mode 100644 index 00000000..6493425b --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/subviews/32D718B4-250F-95DC-37F0-C0A817F69020.xml @@ -0,0 +1,70 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Diagram class="oracle.dbtools.crest.swingui.logical.DPVLogicalSubView" name="Outputs" id="32D718B4-250F-95DC-37F0-C0A817F69020"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:19:53 UTC</createdTime> +<autoRoute>false</autoRoute> +<boxInbox>true</boxInbox> +<showLegend>false</showLegend> +<showLabels>false</showLabels> +<showGrid>false</showGrid> +<diagramColor>-1</diagramColor> +<display>false</display> +<notation>0</notation> +<objectViews> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="16464F5A-64BE-D2ED-91E0-BCBD0AA34680" otype="Entity" vid="636E76B2-6F21-38E5-BF29-D4C078AC8F61"> +<bounds x="1014" y="625" width="121" height="102"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="28DD93CF-D058-7343-CD47-E9B435E1AC16" otype="Entity" vid="89BDF7A8-D79D-A869-BE57-BD2E1C2B290C"> +<bounds x="1190" y="610" width="131" height="41"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="4579B792-2F35-D72A-1A3B-C7E53C41A766" otype="Entity" vid="D72D72DA-F9C0-CE9C-E6A6-7A44DA7656DC"> +<bounds x="1190" y="710" width="131" height="41"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="90F477EE-35D6-21A7-B693-E5724FB07476" otype="Entity" vid="0A09F0EB-AF09-D080-F1B5-EC4E3693C1C5"> +<bounds x="824" y="652" width="141" height="51"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="D09E0DE5-99D6-2991-032A-A8A124F6ACBA" otype="Entity" vid="239CADB1-5F1D-1286-1C79-0DCD91157E84"> +<bounds x="1190" y="662" width="131" height="39"/> +</OView> +</objectViews> +<connectors> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="02096BBB-0795-1759-1E26-2877BE36BB59" otype="Relation" vid_source="636E76B2-6F21-38E5-BF29-D4C078AC8F61" vid_target="636E76B2-6F21-38E5-BF29-D4C078AC8F61"> +<lineWidth>1</lineWidth> +<points> +<point x="1135" y="676"/> +<point x="1150" y="676"/> +<point x="1150" y="742"/> +<point x="1074" y="742"/> +<point x="1074" y="727"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="11710A55-6423-1904-841A-C7D2AB8CEEBF" otype="Relation" vid_source="636E76B2-6F21-38E5-BF29-D4C078AC8F61" vid_target="239CADB1-5F1D-1286-1C79-0DCD91157E84"> +<lineWidth>1</lineWidth> +<points> +<point x="1135" y="691"/> +<point x="1190" y="691"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="34733942-1305-4CA1-47EB-ACE724B04E69" otype="Relation" vid_source="636E76B2-6F21-38E5-BF29-D4C078AC8F61" vid_target="89BDF7A8-D79D-A869-BE57-BD2E1C2B290C"> +<lineWidth>1</lineWidth> +<points> +<point x="1135" y="638"/> +<point x="1190" y="638"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="7DA9DD83-A52E-CA1E-FCBF-FC9CE71AF635" otype="Relation" vid_source="636E76B2-6F21-38E5-BF29-D4C078AC8F61" vid_target="D72D72DA-F9C0-CE9C-E6A6-7A44DA7656DC"> +<lineWidth>1</lineWidth> +<points> +<point x="1135" y="718"/> +<point x="1190" y="718"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="A182A65A-47AE-5D00-9A30-BC20AB050BF2" otype="Relation" vid_source="0A09F0EB-AF09-D080-F1B5-EC4E3693C1C5" vid_target="636E76B2-6F21-38E5-BF29-D4C078AC8F61"> +<lineWidth>1</lineWidth> +<points> +<point x="965" y="677"/> +<point x="1014" y="677"/> +</points> +</Connector> +</connectors> +</Diagram>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/subviews/571DBBAF-CDDA-1C46-4220-D1319C0EEC00.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/subviews/571DBBAF-CDDA-1C46-4220-D1319C0EEC00.xml new file mode 100644 index 00000000..25df5afc --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/subviews/571DBBAF-CDDA-1C46-4220-D1319C0EEC00.xml @@ -0,0 +1,24 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Diagram class="oracle.dbtools.crest.swingui.logical.DPVLogicalSubView" name="Persistent Test Manager Data" id="571DBBAF-CDDA-1C46-4220-D1319C0EEC00"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:19:18 UTC</createdTime> +<autoRoute>false</autoRoute> +<boxInbox>true</boxInbox> +<showLegend>false</showLegend> +<showLabels>false</showLabels> +<showGrid>false</showGrid> +<diagramColor>-1</diagramColor> +<display>false</display> +<notation>0</notation> +<objectViews> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="2F6ACC6D-3D17-537D-8ADF-F8424395B345" otype="Entity" vid="B4E5F358-5BC8-9B06-4A13-EDF705ED9089"> +<bounds x="110" y="570" width="151" height="61"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="A352A20F-310D-E285-FBC9-90DD0DA7BB9B" otype="Entity" vid="8747577F-8999-3CBF-1376-1DD291702774"> +<bounds x="300" y="570" width="151" height="61"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="C332E3D7-638B-6CA8-24BF-383CA8659A3A" otype="Entity" vid="F053C992-CB30-88B3-66FF-F4E522C60155"> +<bounds x="499" y="570" width="136" height="61"/> +</OView> +</objectViews> +</Diagram>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/subviews/65FA5BA0-CC9C-C108-BB1B-AC9E13F5BC83.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/subviews/65FA5BA0-CC9C-C108-BB1B-AC9E13F5BC83.xml new file mode 100644 index 00000000..c248a58e --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/subviews/65FA5BA0-CC9C-C108-BB1B-AC9E13F5BC83.xml @@ -0,0 +1,127 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Diagram class="oracle.dbtools.crest.swingui.logical.DPVLogicalSubView" name="Configuration" id="65FA5BA0-CC9C-C108-BB1B-AC9E13F5BC83"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 08:58:45 UTC</createdTime> +<autoRoute>false</autoRoute> +<boxInbox>true</boxInbox> +<showLegend>false</showLegend> +<showLabels>false</showLabels> +<showGrid>false</showGrid> +<diagramColor>-1</diagramColor> +<display>false</display> +<notation>0</notation> +<objectViews> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="1BEAB532-23CA-8628-0C97-7CAD39119A4E" otype="Entity" vid="459DD9CF-0825-0BAE-7BBA-FADAA3B895BB"> +<bounds x="680" y="419" width="161" height="52"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="24150FB1-B00F-4F69-6F77-49ECB58F0F66" otype="Entity" vid="398E8687-F10E-D31E-DD4E-EA0A6A7868A3"> +<bounds x="273" y="96" width="138" height="61"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="6A886CEE-579B-48FF-63F6-0FB03393FBF6" otype="Entity" vid="E301FF23-DE18-19FB-9A6A-9F170D26B939"> +<bounds x="180" y="250" width="131" height="71"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="7AE36CC1-A030-63E5-6EF3-72FCD04815EE" otype="Entity" vid="B06DA0BE-1DA3-3AB7-06CD-E7EA9FDC0B3E"> +<bounds x="101" y="95" width="131" height="61"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="A6A5F317-479C-A0DD-CAAE-9DCB56B29D40" otype="Entity" vid="49F6288A-70A0-788D-3FEE-BE0053D8D44C"> +<bounds x="680" y="130" width="161" height="41"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="B36A186B-CDB3-7851-8C38-12EA8D50EAEB" otype="Entity" vid="9E4B525D-2B00-0B76-39EE-0C0F74693333"> +<bounds x="600" y="30" width="141" height="31"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="B82DAF9A-6F99-5CF6-4D99-A391BAD66192" otype="Entity" vid="2C49F347-32B8-CA7C-2646-4F16FDDA087E"> +<bounds x="680" y="250" width="161" height="71"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="C79482B8-771B-FAD8-0337-163E3A45003A" otype="Entity" vid="8FAC087B-6133-162A-207B-3FAFB7B41E98"> +<bounds x="908" y="250" width="153" height="31"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="DE366053-6F7A-7F42-ABA3-00E583098C37" otype="Entity" vid="61150DED-91F4-1AE3-BD02-4EDC4CC0D98F"> +<bounds x="430" y="250" width="131" height="71"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="E93BBF08-067B-A665-39F3-CF488A6547B2" otype="Entity" vid="C41DA40C-A50A-BDCC-4DA0-2DCA7874C1A2"> +<bounds x="789" y="30" width="132" height="31"/> +</OView> +</objectViews> +<connectors> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="0CCF1DE3-7916-9054-BEA6-C601FF564DB2" otype="Relation" vid_source="B06DA0BE-1DA3-3AB7-06CD-E7EA9FDC0B3E" vid_target="E301FF23-DE18-19FB-9A6A-9F170D26B939"> +<lineWidth>1</lineWidth> +<points> +<point x="206" y="156"/> +<point x="206" y="250"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="1C189437-742B-B999-C955-7754C8ADB089" otype="Relation" vid_source="E301FF23-DE18-19FB-9A6A-9F170D26B939" vid_target="61150DED-91F4-1AE3-BD02-4EDC4CC0D98F"> +<lineWidth>1</lineWidth> +<points> +<point x="311" y="285"/> +<point x="430" y="285"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="3983F50A-EBB9-E4DE-1958-60EA4EDD6D6C" otype="Relation" vid_source="398E8687-F10E-D31E-DD4E-EA0A6A7868A3" vid_target="E301FF23-DE18-19FB-9A6A-9F170D26B939"> +<lineWidth>1</lineWidth> +<points> +<point x="292" y="157"/> +<point x="292" y="250"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="3B7C8913-EB6A-47B1-27D0-E2C85EE9048B" otype="Relation" vid_source="49F6288A-70A0-788D-3FEE-BE0053D8D44C" vid_target="9E4B525D-2B00-0B76-39EE-0C0F74693333"> +<lineWidth>1</lineWidth> +<points> +<point x="710" y="130"/> +<point x="710" y="61"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="7497D76B-781B-3BDD-D797-FFBDB974F772" otype="Relation" vid_source="2C49F347-32B8-CA7C-2646-4F16FDDA087E" vid_target="8FAC087B-6133-162A-207B-3FAFB7B41E98"> +<lineWidth>1</lineWidth> +<points> +<point x="841" y="265"/> +<point x="908" y="265"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="89A83E25-364B-6B73-0613-FEAD875EF9FB" otype="Relation" vid_source="2C49F347-32B8-CA7C-2646-4F16FDDA087E" vid_target="459DD9CF-0825-0BAE-7BBA-FADAA3B895BB"> +<lineWidth>1</lineWidth> +<points> +<point x="760" y="321"/> +<point x="760" y="419"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="B346381F-48FE-E495-01A7-E22EC26AEE8A" otype="Relation" vid_source="2C49F347-32B8-CA7C-2646-4F16FDDA087E" vid_target="61150DED-91F4-1AE3-BD02-4EDC4CC0D98F"> +<lineWidth>1</lineWidth> +<points> +<point x="680" y="285"/> +<point x="561" y="285"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="B3596116-540F-6397-ECE4-58A386644E15" otype="Relation" vid_source="2C49F347-32B8-CA7C-2646-4F16FDDA087E" vid_target="2C49F347-32B8-CA7C-2646-4F16FDDA087E"> +<lineWidth>1</lineWidth> +<points> +<point x="841" y="285"/> +<point x="856" y="285"/> +<point x="856" y="336"/> +<point x="760" y="336"/> +<point x="760" y="321"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="CCD38E11-8557-EB34-2651-07EB29E83FA6" otype="Relation" vid_source="398E8687-F10E-D31E-DD4E-EA0A6A7868A3" vid_target="E301FF23-DE18-19FB-9A6A-9F170D26B939"> +<lineWidth>1</lineWidth> +<points> +<point x="302" y="157"/> +<point x="302" y="250"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="E4FE88E9-EE21-B43B-B0FE-A153E38246F9" otype="Relation" vid_source="2C49F347-32B8-CA7C-2646-4F16FDDA087E" vid_target="49F6288A-70A0-788D-3FEE-BE0053D8D44C"> +<lineWidth>1</lineWidth> +<points> +<point x="760" y="250"/> +<point x="760" y="171"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="E74406B5-20F1-4323-DC99-6E45982CB606" otype="Relation" vid_source="49F6288A-70A0-788D-3FEE-BE0053D8D44C" vid_target="C41DA40C-A50A-BDCC-4DA0-2DCA7874C1A2"> +<lineWidth>1</lineWidth> +<points> +<point x="815" y="130"/> +<point x="815" y="61"/> +</points> +</Connector> +</connectors> +</Diagram>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/subviews/AFCEF013-4CF2-4A5A-79A3-31521C1CA20A.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/subviews/AFCEF013-4CF2-4A5A-79A3-31521C1CA20A.xml new file mode 100644 index 00000000..14a7566f --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/subviews/AFCEF013-4CF2-4A5A-79A3-31521C1CA20A.xml @@ -0,0 +1,306 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Diagram class="oracle.dbtools.crest.swingui.logical.DPVLogical" name="Logical" id="AFCEF013-4CF2-4A5A-79A3-31521C1CA20A"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:02:17 UTC</createdTime> +<autoRoute>false</autoRoute> +<boxInbox>true</boxInbox> +<showLegend>false</showLegend> +<showLabels>true</showLabels> +<showGrid>true</showGrid> +<diagramColor>-1</diagramColor> +<legendPosX>265</legendPosX> +<legendPosY>490</legendPosY> +<display>false</display> +<notation>0</notation> +<objectViews> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="16464F5A-64BE-D2ED-91E0-BCBD0AA34680" otype="Entity" vid="5B100733-B921-D478-15B5-3BE9A7747A87"> +<bounds x="1014" y="625" width="121" height="102"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="1BEAB532-23CA-8628-0C97-7CAD39119A4E" otype="Entity" vid="62F579AD-F97F-1F92-7C5F-525AE1A2F26C"> +<bounds x="680" y="419" width="161" height="52"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="24150FB1-B00F-4F69-6F77-49ECB58F0F66" otype="Entity" vid="B3D29C8C-8482-D7AF-BE58-122AB07FB853"> +<bounds x="273" y="96" width="138" height="61"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="28DD93CF-D058-7343-CD47-E9B435E1AC16" otype="Entity" vid="ABB72A58-23E7-DF85-4B01-74F467F60284"> +<bounds x="1190" y="610" width="131" height="41"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="2F6ACC6D-3D17-537D-8ADF-F8424395B345" otype="Entity" vid="40AB3AA2-7D9F-7BA7-AB96-050F27CF81AB"> +<bounds x="110" y="570" width="151" height="51"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="44FFF5E9-0C2F-7BAC-B5B7-73CA3A230B39" otype="Entity" vid="BE78445F-B005-8F1A-E390-120DCC587063"> +<bounds x="1270" y="448" width="151" height="41"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="4579B792-2F35-D72A-1A3B-C7E53C41A766" otype="Entity" vid="BA629852-B837-F348-59DD-12899B260C79"> +<bounds x="1190" y="710" width="131" height="41"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="4D937E7C-3A28-E52D-89C0-EC8804C62367" otype="Entity" vid="109E2A3F-B942-1D32-CB1C-4F60260ACF5C"> +<bounds x="1270" y="522" width="152" height="43"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="504221DA-1B57-4EAD-39DB-40FD553E9FA2" otype="Entity" vid="F4CED71A-65B7-151C-3ADC-26F25043F168"> +<bounds x="1092" y="301" width="151" height="70"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="6A886CEE-579B-48FF-63F6-0FB03393FBF6" otype="Entity" vid="81A8E233-0690-CBFE-6102-F71A991903FC"> +<bounds x="180" y="250" width="131" height="71"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="7AE36CC1-A030-63E5-6EF3-72FCD04815EE" otype="Entity" vid="C8DAF849-7026-3615-7FC8-4397BFC6CA14"> +<bounds x="101" y="95" width="131" height="61"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.TVNote" oid="876CB767-80BA-6C8E-AACA-F1CCC95C445E" otype="Note" vid="593FF096-DB74-2562-91B0-A4F1423FEBA7"> +<bounds x="292" y="336" width="149" height="61"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="90367AFB-BA2D-A918-46B9-1E5DE53ACC48" otype="Entity" vid="5A1E3970-E7C2-5B4A-B4FC-A4224370E349"> +<bounds x="1270" y="300" width="145" height="72"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="90F477EE-35D6-21A7-B693-E5724FB07476" otype="Entity" vid="B6946DC3-6424-2A37-D668-5BD36839859C"> +<bounds x="824" y="652" width="141" height="51"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="9F78B73C-056D-DDEF-8C50-A9DA76B9E724" otype="Entity" vid="EEE8DCBD-05DB-E390-AE27-14DFF3B0DD56"> +<bounds x="1091" y="205" width="151" height="63"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="A352A20F-310D-E285-FBC9-90DD0DA7BB9B" otype="Entity" vid="27BF1041-8402-6396-1A77-2223122117A1"> +<bounds x="292" y="570" width="148" height="51"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="A6A5F317-479C-A0DD-CAAE-9DCB56B29D40" otype="Entity" vid="AB9AED98-F420-DDD6-02BA-ABA20D05AFB3"> +<bounds x="680" y="130" width="161" height="41"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="B36A186B-CDB3-7851-8C38-12EA8D50EAEB" otype="Entity" vid="8B654282-58D6-084A-69E2-3C8D7E390802"> +<bounds x="600" y="30" width="141" height="31"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="B82DAF9A-6F99-5CF6-4D99-A391BAD66192" otype="Entity" vid="2F2EDF15-4992-FE58-E928-D09AF0373D9E"> +<bounds x="680" y="250" width="161" height="71"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="C332E3D7-638B-6CA8-24BF-383CA8659A3A" otype="Entity" vid="03B42717-C78B-007E-11B3-EEA11AABA415"> +<bounds x="472" y="570" width="136" height="51"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="C79482B8-771B-FAD8-0337-163E3A45003A" otype="Entity" vid="8D1A1E0A-0651-0364-F81D-EC5D599DF29A"> +<bounds x="909" y="251" width="132" height="51"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="D09E0DE5-99D6-2991-032A-A8A124F6ACBA" otype="Entity" vid="2446BDB4-EEEF-A6B8-6F46-4C1208EDECC2"> +<bounds x="1190" y="662" width="131" height="39"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.TVNote" oid="D487AFDC-4027-F824-EA29-5C6D0ABB9E1E" otype="Note" vid="583B257A-5AD8-026F-84FF-AB3956387595"> +<bounds x="322" y="179" width="89" height="40"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="DCC79294-5434-1DED-298C-6473DEE59FBA" otype="Entity" vid="8689850E-1426-9DCF-EF62-4753AFEE7BE6"> +<bounds x="1087" y="460" width="157" height="51"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="DE366053-6F7A-7F42-ABA3-00E583098C37" otype="Entity" vid="CAF127DE-45F6-6BCE-8FAB-7BAE679347E1"> +<bounds x="430" y="250" width="131" height="71"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="E93BBF08-067B-A665-39F3-CF488A6547B2" otype="Entity" vid="2862D2B6-5340-9024-1DF2-E4408EA96B6E"> +<bounds x="789" y="30" width="132" height="31"/> +</OView> +</objectViews> +<connectors> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="01537211-CCFB-0A1E-B43B-E8C641B69471" otype="Relation" vid_source="B6946DC3-6424-2A37-D668-5BD36839859C" vid_target="62F579AD-F97F-1F92-7C5F-525AE1A2F26C"> +<lineWidth>1</lineWidth> +<points> +<point x="832" y="652"/> +<point x="832" y="471"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="02096BBB-0795-1759-1E26-2877BE36BB59" otype="Relation" vid_source="5B100733-B921-D478-15B5-3BE9A7747A87" vid_target="5B100733-B921-D478-15B5-3BE9A7747A87"> +<lineWidth>1</lineWidth> +<points> +<point x="1135" y="676"/> +<point x="1150" y="676"/> +<point x="1150" y="742"/> +<point x="1074" y="742"/> +<point x="1074" y="727"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="0CCF1DE3-7916-9054-BEA6-C601FF564DB2" otype="Relation" vid_source="C8DAF849-7026-3615-7FC8-4397BFC6CA14" vid_target="81A8E233-0690-CBFE-6102-F71A991903FC"> +<lineWidth>1</lineWidth> +<points> +<point x="206" y="156"/> +<point x="206" y="250"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="10867E70-94CE-FDAF-6B6E-2742D3A49E57" otype="Relation" vid_source="5A1E3970-E7C2-5B4A-B4FC-A4224370E349" vid_target="BE78445F-B005-8F1A-E390-120DCC587063"> +<lineWidth>1</lineWidth> +<points> +<point x="1342" y="372"/> +<point x="1342" y="448"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="11710A55-6423-1904-841A-C7D2AB8CEEBF" otype="Relation" vid_source="5B100733-B921-D478-15B5-3BE9A7747A87" vid_target="2446BDB4-EEEF-A6B8-6F46-4C1208EDECC2"> +<lineWidth>1</lineWidth> +<points> +<point x="1135" y="690"/> +<point x="1190" y="690"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="1C189437-742B-B999-C955-7754C8ADB089" otype="Relation" vid_source="81A8E233-0690-CBFE-6102-F71A991903FC" vid_target="CAF127DE-45F6-6BCE-8FAB-7BAE679347E1"> +<lineWidth>1</lineWidth> +<points> +<point x="311" y="285"/> +<point x="430" y="285"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="34733942-1305-4CA1-47EB-ACE724B04E69" otype="Relation" vid_source="5B100733-B921-D478-15B5-3BE9A7747A87" vid_target="ABB72A58-23E7-DF85-4B01-74F467F60284"> +<lineWidth>1</lineWidth> +<points> +<point x="1135" y="638"/> +<point x="1190" y="638"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="3563C940-E524-7F96-7AE0-DAC3C1C17AFC" otype="Relation" vid_source="F4CED71A-65B7-151C-3ADC-26F25043F168" vid_target="B6946DC3-6424-2A37-D668-5BD36839859C"> +<lineWidth>1</lineWidth> +<points> +<point x="1167" y="371"/> +<point x="894" y="652"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="3983F50A-EBB9-E4DE-1958-60EA4EDD6D6C" otype="Relation" vid_source="B3D29C8C-8482-D7AF-BE58-122AB07FB853" vid_target="81A8E233-0690-CBFE-6102-F71A991903FC"> +<lineWidth>1</lineWidth> +<points> +<point x="300" y="157"/> +<point x="300" y="250"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="3B7C8913-EB6A-47B1-27D0-E2C85EE9048B" otype="Relation" vid_source="AB9AED98-F420-DDD6-02BA-ABA20D05AFB3" vid_target="8B654282-58D6-084A-69E2-3C8D7E390802"> +<lineWidth>1</lineWidth> +<points> +<point x="710" y="130"/> +<point x="710" y="61"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="518CE489-97B4-C05C-07A2-E3DBF14EE267" otype="Relation" vid_source="8689850E-1426-9DCF-EF62-4753AFEE7BE6" vid_target="BE78445F-B005-8F1A-E390-120DCC587063"> +<lineWidth>1</lineWidth> +<points> +<point x="1244" y="474"/> +<point x="1270" y="474"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="68A0C3E1-0FA1-8414-A361-33B08A8EDB39" otype="Relation" vid_source="8689850E-1426-9DCF-EF62-4753AFEE7BE6" vid_target="5B100733-B921-D478-15B5-3BE9A7747A87"> +<lineWidth>1</lineWidth> +<points> +<point x="1111" y="511"/> +<point x="1111" y="625"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="7497D76B-781B-3BDD-D797-FFBDB974F772" otype="Relation" vid_source="2F2EDF15-4992-FE58-E928-D09AF0373D9E" vid_target="8D1A1E0A-0651-0364-F81D-EC5D599DF29A"> +<lineWidth>1</lineWidth> +<points> +<point x="841" y="266"/> +<point x="909" y="266"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="7DA9DD83-A52E-CA1E-FCBF-FC9CE71AF635" otype="Relation" vid_source="5B100733-B921-D478-15B5-3BE9A7747A87" vid_target="BA629852-B837-F348-59DD-12899B260C79"> +<lineWidth>1</lineWidth> +<points> +<point x="1135" y="718"/> +<point x="1190" y="718"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="89A83E25-364B-6B73-0613-FEAD875EF9FB" otype="Relation" vid_source="2F2EDF15-4992-FE58-E928-D09AF0373D9E" vid_target="62F579AD-F97F-1F92-7C5F-525AE1A2F26C"> +<lineWidth>1</lineWidth> +<points> +<point x="750" y="321"/> +<point x="750" y="419"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="8E5018CC-34E3-9AFC-D6D1-31E2BC4E9FE2" otype="Relation" vid_source="03B42717-C78B-007E-11B3-EEA11AABA415" vid_target="62F579AD-F97F-1F92-7C5F-525AE1A2F26C"> +<lineWidth>1</lineWidth> +<points> +<point x="540" y="570"/> +<point x="760" y="471"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="9B1FE0CF-B2AD-EED0-22FC-461A7D46DE51" otype="Relation" vid_source="40AB3AA2-7D9F-7BA7-AB96-050F27CF81AB" vid_target="8D1A1E0A-0651-0364-F81D-EC5D599DF29A"> +<lineWidth>1</lineWidth> +<points> +<point x="185" y="570"/> +<point x="985" y="302"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="A182A65A-47AE-5D00-9A30-BC20AB050BF2" otype="Relation" vid_source="B6946DC3-6424-2A37-D668-5BD36839859C" vid_target="5B100733-B921-D478-15B5-3BE9A7747A87"> +<lineWidth>1</lineWidth> +<points> +<point x="965" y="677"/> +<point x="1014" y="677"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="B346381F-48FE-E495-01A7-E22EC26AEE8A" otype="Relation" vid_source="2F2EDF15-4992-FE58-E928-D09AF0373D9E" vid_target="CAF127DE-45F6-6BCE-8FAB-7BAE679347E1"> +<lineWidth>1</lineWidth> +<points> +<point x="680" y="285"/> +<point x="561" y="285"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="B3596116-540F-6397-ECE4-58A386644E15" otype="Relation" vid_source="2F2EDF15-4992-FE58-E928-D09AF0373D9E" vid_target="2F2EDF15-4992-FE58-E928-D09AF0373D9E"> +<lineWidth>1</lineWidth> +<points> +<point x="841" y="285"/> +<point x="856" y="285"/> +<point x="856" y="336"/> +<point x="760" y="336"/> +<point x="760" y="321"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="BAD8EC05-6F14-4E38-366C-B4B660C6F38A" otype="Relation" vid_source="BE78445F-B005-8F1A-E390-120DCC587063" vid_target="109E2A3F-B942-1D32-CB1C-4F60260ACF5C"> +<lineWidth>1</lineWidth> +<points> +<point x="1345" y="489"/> +<point x="1345" y="522"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="C5B67DD4-FA4F-EF9F-1FF5-0445D51B32EE" otype="Relation" vid_source="B6946DC3-6424-2A37-D668-5BD36839859C" vid_target="C8DAF849-7026-3615-7FC8-4397BFC6CA14"> +<lineWidth>1</lineWidth> +<points> +<point x="894" y="652"/> +<point x="166" y="156"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="CCD38E11-8557-EB34-2651-07EB29E83FA6" otype="Relation" vid_source="B3D29C8C-8482-D7AF-BE58-122AB07FB853" vid_target="81A8E233-0690-CBFE-6102-F71A991903FC"> +<lineWidth>1</lineWidth> +<points> +<point x="280" y="157"/> +<point x="280" y="250"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="E2A47942-ED55-E81D-4C71-9A134C49C147" otype="Relation" vid_source="C8DAF849-7026-3615-7FC8-4397BFC6CA14" vid_target="27BF1041-8402-6396-1A77-2223122117A1"> +<lineWidth>1</lineWidth> +<points> +<point x="166" y="156"/> +<point x="330" y="570"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="E4FE88E9-EE21-B43B-B0FE-A153E38246F9" otype="Relation" vid_source="2F2EDF15-4992-FE58-E928-D09AF0373D9E" vid_target="AB9AED98-F420-DDD6-02BA-ABA20D05AFB3"> +<lineWidth>1</lineWidth> +<points> +<point x="760" y="250"/> +<point x="760" y="171"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="E62AE7DF-49EE-9280-B328-A867CBD273AE" otype="Relation" vid_source="27BF1041-8402-6396-1A77-2223122117A1" vid_target="B6946DC3-6424-2A37-D668-5BD36839859C"> +<lineWidth>1</lineWidth> +<points> +<point x="360" y="621"/> +<point x="824" y="677"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="E74406B5-20F1-4323-DC99-6E45982CB606" otype="Relation" vid_source="AB9AED98-F420-DDD6-02BA-ABA20D05AFB3" vid_target="2862D2B6-5340-9024-1DF2-E4408EA96B6E"> +<lineWidth>1</lineWidth> +<points> +<point x="815" y="130"/> +<point x="815" y="61"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="EC4EB506-3DBE-7F36-6451-F31920EDAB52" otype="Relation" vid_source="C8DAF849-7026-3615-7FC8-4397BFC6CA14" vid_target="40AB3AA2-7D9F-7BA7-AB96-050F27CF81AB"> +<lineWidth>1</lineWidth> +<points> +<point x="130" y="156"/> +<point x="130" y="570"/> +</points> +</Connector> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="EE1D98EF-6AEA-2790-D9B9-DBC2ED21D880" otype="Relation" vid_source="EEE8DCBD-05DB-E390-AE27-14DFF3B0DD56" vid_target="F4CED71A-65B7-151C-3ADC-26F25043F168"> +<lineWidth>1</lineWidth> +<points> +<point x="1166" y="243"/> +<point x="1167" y="301"/> +</points> +</Connector> +</connectors> +</Diagram>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/subviews/F936BE6D-7A74-1B57-7564-41C1E13B973B.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/subviews/F936BE6D-7A74-1B57-7564-41C1E13B973B.xml new file mode 100644 index 00000000..bcc0009f --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/logical/subviews/F936BE6D-7A74-1B57-7564-41C1E13B973B.xml @@ -0,0 +1,33 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Diagram class="oracle.dbtools.crest.swingui.logical.DPVLogicalSubView" name="Inputs" id="F936BE6D-7A74-1B57-7564-41C1E13B973B"> +<createdBy>bird</createdBy> +<createdTime>2012-08-21 09:08:50 UTC</createdTime> +<autoRoute>false</autoRoute> +<boxInbox>true</boxInbox> +<showLegend>false</showLegend> +<showLabels>false</showLabels> +<showGrid>false</showGrid> +<diagramColor>-1</diagramColor> +<display>false</display> +<notation>0</notation> +<objectViews> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="504221DA-1B57-4EAD-39DB-40FD553E9FA2" otype="Entity" vid="EA3885E3-FEE4-031B-1751-1C6351610836"> +<bounds x="1091" y="476" width="151" height="70"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="90367AFB-BA2D-A918-46B9-1E5DE53ACC48" otype="Entity" vid="86784B28-925D-6EAF-24D8-27DE22A0A93B"> +<bounds x="1090" y="376" width="151" height="68"/> +</OView> +<OView class="oracle.dbtools.crest.swingui.logical.TVEntity" oid="9F78B73C-056D-DDEF-8C50-A9DA76B9E724" otype="Entity" vid="1B62E962-0DFC-D5AE-0AC4-33E14F65E825"> +<bounds x="1297" y="477" width="151" height="71"/> +</OView> +</objectViews> +<connectors> +<Connector class="oracle.dbtools.crest.swingui.logical.TVRelation" oid="EE1D98EF-6AEA-2790-D9B9-DBC2ED21D880" otype="Relation" vid_source="1B62E962-0DFC-D5AE-0AC4-33E14F65E825" vid_target="EA3885E3-FEE4-031B-1751-1C6351610836"> +<lineWidth>1</lineWidth> +<points> +<point x="1297" y="511"/> +<point x="1242" y="511"/> +</points> +</Connector> +</connectors> +</Diagram>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/mapping/ExtendedMap.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/mapping/ExtendedMap.xml new file mode 100644 index 00000000..6811f63f --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/mapping/ExtendedMap.xml @@ -0,0 +1,3 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<ExtendedMap class="oracle.dbtools.crest.model.xtdmapping.ExtendedMap"> +</ExtendedMap>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/mapping/ExtendedMap_RMB082B14A-BEA8-D8A7-D661-197F34766ED3.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/mapping/ExtendedMap_RMB082B14A-BEA8-D8A7-D661-197F34766ED3.xml new file mode 100644 index 00000000..7ea5df08 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/mapping/ExtendedMap_RMB082B14A-BEA8-D8A7-D661-197F34766ED3.xml @@ -0,0 +1,3 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<RMExtendedMap class="oracle.dbtools.crest.model.xtdmapping.RMExtendedMap"> +</RMExtendedMap>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/rdbms/TestManagerDatabase_RDBMSSites.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/rdbms/TestManagerDatabase_RDBMSSites.xml new file mode 100644 index 00000000..e0c5dad0 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/rdbms/TestManagerDatabase_RDBMSSites.xml @@ -0,0 +1,2 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<metadatadoc version="2.0"/>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/rel/B082B14A-197F34766ED3.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/rel/B082B14A-197F34766ED3.xml new file mode 100644 index 00000000..76bdad85 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/rel/B082B14A-197F34766ED3.xml @@ -0,0 +1,8 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<relationalModel class="oracle.dbtools.crest.model.design.relational.RelationalDesign" name="Relational_1" id="B082B14A-BEA8-D8A7-D661-197F34766ED3" mainViewID="6CEC5843-B4DD-D9B0-54D4-2845569D5E9F"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 21:58:45 UTC</createdTime> +<ownerDesignName>TestManagerDatabase</ownerDesignName> +<shouldBeOpen>false</shouldBeOpen> +<selectedRDBMSSite>32076570-2523-435C-2E92-BF29817DFF70</selectedRDBMSSite> +</relationalModel>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/rel/B082B14A-197F34766ED3/subviews/6CEC5843-B4DD-D9B0-54D4-2845569D5E9F.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/rel/B082B14A-197F34766ED3/subviews/6CEC5843-B4DD-D9B0-54D4-2845569D5E9F.xml new file mode 100644 index 00000000..44b040be --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/rel/B082B14A-197F34766ED3/subviews/6CEC5843-B4DD-D9B0-54D4-2845569D5E9F.xml @@ -0,0 +1,13 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<Diagram class="oracle.dbtools.crest.swingui.relational.DPVRelational" name="Relational_1" id="6CEC5843-B4DD-D9B0-54D4-2845569D5E9F"> +<createdBy>bird</createdBy> +<createdTime>2012-08-20 22:02:17 UTC</createdTime> +<autoRoute>false</autoRoute> +<boxInbox>true</boxInbox> +<showLegend>false</showLegend> +<showLabels>false</showLabels> +<showGrid>false</showGrid> +<diagramColor>-1</diagramColor> +<display>false</display> +<notation>0</notation> +</Diagram>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/types.xml b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/types.xml new file mode 100644 index 00000000..64fa7ab8 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabase/types.xml @@ -0,0 +1,933 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<logtypes> + <logicaltype name="Audio" objectid="LOGDT005"> + <mapping rdbms="Oracle Database 11g">BLOB</mapping> + <mapping rdbms="Oracle Database 10g">BLOB</mapping> + <mapping rdbms="Oracle9i">BLOB</mapping> + <mapping rdbms="SQL Server 2005">BINARY, size</mapping> + <mapping rdbms="SQL Server 2000">BINARY, size</mapping> + <mapping rdbms="DB2/390 8">BLOB, size</mapping> + <mapping rdbms="DB2/390 7">BLOB, size</mapping> + <mapping rdbms="DB2/UDB 8.1">BLOB, size</mapping> + <mapping rdbms="DB2/UDB 7.1">BLOB, size</mapping> + </logicaltype> + <logicaltype name="BFile" objectid="LogDes-7DD553FD-11E8-61FA-399D-2E531FB621D0@LOGDT10034"> + <mapping rdbms="Oracle Database 11g">BFILE</mapping> + <mapping rdbms="Oracle Database 10g">BFILE</mapping> + <mapping rdbms="Oracle9i">BFILE</mapping> + <mapping rdbms="SQL Server 2005">VARCHAR, size</mapping> + <mapping rdbms="SQL Server 2000">VARCHAR, size</mapping> + <mapping rdbms="DB2/390 8">VARCHAR, size</mapping> + <mapping rdbms="DB2/390 7">VARCHAR, size</mapping> + <mapping rdbms="DB2/UDB 8.1">DATALINK</mapping> + <mapping rdbms="DB2/UDB 7.1">DATALINK</mapping> + </logicaltype> + <logicaltype name="BIGINT" objectid="LOGDT027"> + <mapping rdbms="Oracle Database 11g">INTEGER</mapping> + <mapping rdbms="Oracle Database 10g">INTEGER</mapping> + <mapping rdbms="Oracle9i">INTEGER</mapping> + <mapping rdbms="SQL Server 2005">BIGINT</mapping> + <mapping rdbms="SQL Server 2000">BIGINT</mapping> + <mapping rdbms="DB2/390 8">INTEGER</mapping> + <mapping rdbms="DB2/390 7">INTEGER</mapping> + <mapping rdbms="DB2/UDB 8.1">INTEGER</mapping> + <mapping rdbms="DB2/UDB 7.1">INTEGER</mapping> + </logicaltype> + <logicaltype name="BINARY" objectid="LOGDT033"> + <mapping rdbms="Oracle Database 11g">BLOB</mapping> + <mapping rdbms="Oracle Database 10g">BLOB</mapping> + <mapping rdbms="Oracle9i">BLOB</mapping> + <mapping rdbms="SQL Server 2005">BINARY, size</mapping> + <mapping rdbms="SQL Server 2000">BINARY, size</mapping> + <mapping rdbms="DB2/390 8">BLOB, size</mapping> + <mapping rdbms="DB2/390 7">BLOB, size</mapping> + <mapping rdbms="DB2/UDB 8.1">BLOB, size</mapping> + <mapping rdbms="DB2/UDB 7.1">BLOB, size</mapping> + </logicaltype> + <logicaltype name="BINARY DOUBLE" objectid="LOGDT056"> + <mapping rdbms="Oracle Database 11g">BINARY_DOUBLE</mapping> + <mapping rdbms="Oracle Database 10g">BINARY_DOUBLE</mapping> + <mapping rdbms="Oracle9i">NUMBER</mapping> + <mapping rdbms="SQL Server 2005">FLOAT</mapping> + <mapping rdbms="SQL Server 2000">FLOAT</mapping> + <mapping rdbms="DB2/390 8">DOUBLE</mapping> + <mapping rdbms="DB2/390 7">DOUBLE</mapping> + <mapping rdbms="DB2/UDB 8.1">DOUBLE</mapping> + <mapping rdbms="DB2/UDB 7.1">DOUBLE</mapping> + </logicaltype> + <logicaltype name="BINARY FLOAT" objectid="LOGDT055"> + <mapping rdbms="Oracle Database 11g">BINARY_FLOAT</mapping> + <mapping rdbms="Oracle Database 10g">BINARY_FLOAT</mapping> + <mapping rdbms="Oracle9i">NUMBER</mapping> + <mapping rdbms="SQL Server 2005">REAL</mapping> + <mapping rdbms="SQL Server 2000">REAL</mapping> + <mapping rdbms="DB2/390 8">REAL</mapping> + <mapping rdbms="DB2/390 7">REAL</mapping> + <mapping rdbms="DB2/UDB 8.1">REAL</mapping> + <mapping rdbms="DB2/UDB 7.1">REAL</mapping> + </logicaltype> + <logicaltype name="BIT" objectid="LOGDT034"> + <mapping rdbms="Oracle Database 11g">CHAR</mapping> + <mapping rdbms="Oracle Database 10g">CHAR</mapping> + <mapping rdbms="Oracle9i">CHAR</mapping> + <mapping rdbms="SQL Server 2005">BIT</mapping> + <mapping rdbms="SQL Server 2000">BIT</mapping> + <mapping rdbms="DB2/390 8">CHAR, size</mapping> + <mapping rdbms="DB2/390 7">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 8.1">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 7.1">CHAR, size</mapping> + </logicaltype> + <logicaltype name="BLOB" objectid="LOGDT029"> + <mapping rdbms="Oracle Database 11g">BLOB</mapping> + <mapping rdbms="Oracle Database 10g">BLOB</mapping> + <mapping rdbms="Oracle9i">BLOB</mapping> + <mapping rdbms="SQL Server 2005">IMAGE</mapping> + <mapping rdbms="SQL Server 2000">IMAGE</mapping> + <mapping rdbms="DB2/390 8">BLOB, size</mapping> + <mapping rdbms="DB2/390 7">BLOB, size</mapping> + <mapping rdbms="DB2/UDB 8.1">BLOB, size</mapping> + <mapping rdbms="DB2/UDB 7.1">BLOB, size</mapping> + </logicaltype> + <logicaltype name="Boolean" objectid="LOGDT006"> + <mapping rdbms="Oracle Database 11g">CHAR</mapping> + <mapping rdbms="Oracle Database 10g">CHAR</mapping> + <mapping rdbms="Oracle9i">CHAR</mapping> + <mapping rdbms="SQL Server 2005">BIT</mapping> + <mapping rdbms="SQL Server 2000">BIT</mapping> + <mapping rdbms="DB2/390 8">CHAR</mapping> + <mapping rdbms="DB2/390 7">CHAR</mapping> + <mapping rdbms="DB2/UDB 8.1">CHAR</mapping> + <mapping rdbms="DB2/UDB 7.1">CHAR</mapping> + </logicaltype> + <logicaltype name="CHAR" objectid="LOGDT025"> + <mapping rdbms="Oracle Database 11g">CHAR, size</mapping> + <mapping rdbms="Oracle Database 10g">CHAR, size</mapping> + <mapping rdbms="Oracle9i">CHAR, size</mapping> + <mapping rdbms="SQL Server 2005">CHAR, size</mapping> + <mapping rdbms="SQL Server 2000">CHAR, size</mapping> + <mapping rdbms="DB2/390 8">CHAR, size</mapping> + <mapping rdbms="DB2/390 7">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 8.1">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 7.1">CHAR, size</mapping> + </logicaltype> + <logicaltype name="CLOB" objectid="LOGDT028"> + <mapping rdbms="Oracle Database 11g">CLOB</mapping> + <mapping rdbms="Oracle Database 10g">CLOB</mapping> + <mapping rdbms="Oracle9i">CLOB</mapping> + <mapping rdbms="SQL Server 2005" size_default_value="max">VARCHAR, size</mapping> + <mapping rdbms="SQL Server 2000">TEXT</mapping> + <mapping rdbms="DB2/390 8">CLOB, size</mapping> + <mapping rdbms="DB2/390 7">CLOB, size</mapping> + <mapping rdbms="DB2/UDB 8.1">CLOB, size</mapping> + <mapping rdbms="DB2/UDB 7.1">CLOB, size</mapping> + </logicaltype> + <logicaltype name="DATALINK" objectid="LOGDT030"> + <mapping rdbms="Oracle Database 11g">BLOB</mapping> + <mapping rdbms="Oracle Database 10g">BLOB</mapping> + <mapping rdbms="Oracle9i">BLOB</mapping> + <mapping rdbms="SQL Server 2005">BINARY</mapping> + <mapping rdbms="SQL Server 2000">BINARY</mapping> + <mapping rdbms="DB2/390 8">VARCHAR, size</mapping> + <mapping rdbms="DB2/390 7">VARCHAR, size</mapping> + <mapping rdbms="DB2/UDB 8.1">DATALINK</mapping> + <mapping rdbms="DB2/UDB 7.1">DATALINK</mapping> + </logicaltype> + <logicaltype name="DBURIType" objectid="LOGDT054"> + <mapping rdbms="Oracle Database 11g">DBURITYPE</mapping> + <mapping rdbms="Oracle Database 10g">DBURITYPE</mapping> + <mapping rdbms="Oracle9i">DBURITYPE</mapping> + <mapping rdbms="SQL Server 2005">CHAR, size</mapping> + <mapping rdbms="SQL Server 2000">CHAR, size</mapping> + <mapping rdbms="DB2/390 8">CHAR, size</mapping> + <mapping rdbms="DB2/390 7">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 8.1">DATALINK</mapping> + <mapping rdbms="DB2/UDB 7.1">DATALINK</mapping> + </logicaltype> + <logicaltype name="DECIMAL" objectid="LOGDT026"> + <mapping rdbms="Oracle Database 11g">NUMBER, precision, scale</mapping> + <mapping rdbms="Oracle Database 10g">NUMBER, precision, scale</mapping> + <mapping rdbms="Oracle9i">NUMBER, precision, scale</mapping> + <mapping rdbms="SQL Server 2005">DECIMAL, precision, scale</mapping> + <mapping rdbms="SQL Server 2000">DECIMAL, precision, scale</mapping> + <mapping rdbms="DB2/390 8">DECIMAL, precision, scale</mapping> + <mapping rdbms="DB2/390 7">DECIMAL, precision, scale</mapping> + <mapping rdbms="DB2/UDB 8.1">DECIMAL, precision, scale</mapping> + <mapping rdbms="DB2/UDB 7.1">DECIMAL, precision, scale</mapping> + </logicaltype> + <logicaltype name="DOUBLE" objectid="LOGDT020"> + <mapping rdbms="Oracle Database 11g">NUMBER</mapping> + <mapping rdbms="Oracle Database 10g">NUMBER</mapping> + <mapping rdbms="Oracle9i">NUMBER</mapping> + <mapping rdbms="SQL Server 2005">BIGINT</mapping> + <mapping rdbms="SQL Server 2000">BIGINT</mapping> + <mapping rdbms="DB2/390 8">DOUBLE</mapping> + <mapping rdbms="DB2/390 7">DOUBLE</mapping> + <mapping rdbms="DB2/UDB 8.1">DOUBLE</mapping> + <mapping rdbms="DB2/UDB 7.1">DOUBLE</mapping> + </logicaltype> + <logicaltype name="Date" objectid="LOGDT007"> + <mapping rdbms="Oracle Database 11g">DATE</mapping> + <mapping rdbms="Oracle Database 10g">DATE</mapping> + <mapping rdbms="Oracle9i">DATE</mapping> + <mapping rdbms="SQL Server 2005">DATETIME</mapping> + <mapping rdbms="SQL Server 2000">DATETIME</mapping> + <mapping rdbms="DB2/390 8">DATE</mapping> + <mapping rdbms="DB2/390 7">DATE</mapping> + <mapping rdbms="DB2/UDB 8.1">DATE</mapping> + <mapping rdbms="DB2/UDB 7.1">DATE</mapping> + </logicaltype> + <logicaltype name="Datetime" objectid="LOGDT008"> + <mapping rdbms="Oracle Database 11g">DATE</mapping> + <mapping rdbms="Oracle Database 10g">DATE</mapping> + <mapping rdbms="Oracle9i">DATE</mapping> + <mapping rdbms="SQL Server 2005">DATETIME</mapping> + <mapping rdbms="SQL Server 2000">DATETIME</mapping> + <mapping rdbms="DB2/390 8">TIMESTAMP</mapping> + <mapping rdbms="DB2/390 7">TIMESTAMP</mapping> + <mapping rdbms="DB2/UDB 8.1">TIMESTAMP</mapping> + <mapping rdbms="DB2/UDB 7.1">TIMESTAMP</mapping> + </logicaltype> + <logicaltype name="FLOAT" objectid="LOGDT021"> + <mapping rdbms="Oracle Database 11g">FLOAT, precision</mapping> + <mapping rdbms="Oracle Database 10g">FLOAT, precision</mapping> + <mapping rdbms="Oracle9i">FLOAT, precision</mapping> + <mapping rdbms="SQL Server 2005">FLOAT, precision</mapping> + <mapping rdbms="SQL Server 2000">FLOAT, precision</mapping> + <mapping rdbms="DB2/390 8">FLOAT, precision</mapping> + <mapping rdbms="DB2/390 7">FLOAT, precision</mapping> + <mapping rdbms="DB2/UDB 8.1">FLOAT, precision</mapping> + <mapping rdbms="DB2/UDB 7.1">FLOAT, precision</mapping> + </logicaltype> + <logicaltype name="GRAPHIC" objectid="LOGDT031"> + <mapping rdbms="Oracle Database 11g">BLOB</mapping> + <mapping rdbms="Oracle Database 10g">BLOB</mapping> + <mapping rdbms="Oracle9i">BLOB</mapping> + <mapping rdbms="SQL Server 2005">BINARY</mapping> + <mapping rdbms="SQL Server 2000">BINARY</mapping> + <mapping rdbms="DB2/390 8">GRAPHIC, size</mapping> + <mapping rdbms="DB2/390 7">GRAPHIC, size</mapping> + <mapping rdbms="DB2/UDB 8.1">GRAPHIC, size</mapping> + <mapping rdbms="DB2/UDB 7.1">GRAPHIC, size</mapping> + </logicaltype> + <logicaltype name="HTTPURIType" objectid="LOGDT052"> + <mapping rdbms="Oracle Database 11g">HTTPURITYPE</mapping> + <mapping rdbms="Oracle Database 10g">HTTPURITYPE</mapping> + <mapping rdbms="Oracle9i">HTTPURITYPE</mapping> + <mapping rdbms="SQL Server 2005">CHAR, size</mapping> + <mapping rdbms="SQL Server 2000">CHAR, size</mapping> + <mapping rdbms="DB2/390 8">CHAR, size</mapping> + <mapping rdbms="DB2/390 7">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 8.1">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 7.1">CHAR, size</mapping> + </logicaltype> + <logicaltype name="INTERVAL DAY TO SECOND" objectid="LOGDT049"> + <mapping rdbms="Oracle Database 11g">INTERVAL DAY TO SECOND, precision, scale</mapping> + <mapping rdbms="Oracle Database 10g">INTERVAL DAY TO SECOND, precision, scale</mapping> + <mapping rdbms="Oracle9i">INTERVAL DAY TO SECOND, precision, scale</mapping> + <mapping rdbms="SQL Server 2005">CHAR, size</mapping> + <mapping rdbms="SQL Server 2000">CHAR, size</mapping> + <mapping rdbms="DB2/390 8">CHAR, size</mapping> + <mapping rdbms="DB2/390 7">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 8.1">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 7.1">CHAR, size</mapping> + </logicaltype> + <logicaltype name="INTERVAL YEAR TO MONTH" objectid="LOGDT048"> + <mapping rdbms="Oracle Database 11g">INTERVAL YEAR TO MONTH, precision</mapping> + <mapping rdbms="Oracle Database 10g">INTERVAL YEAR TO MONTH, precision</mapping> + <mapping rdbms="Oracle9i">INTERVAL YEAR TO MONTH, precision</mapping> + <mapping rdbms="SQL Server 2005">CHAR, size</mapping> + <mapping rdbms="SQL Server 2000">CHAR, size</mapping> + <mapping rdbms="DB2/390 8">CHAR, size</mapping> + <mapping rdbms="DB2/390 7">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 8.1">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 7.1">CHAR, size</mapping> + </logicaltype> + <logicaltype name="Image" objectid="LOGDT010"> + <mapping rdbms="Oracle Database 11g">BLOB</mapping> + <mapping rdbms="Oracle Database 10g">BLOB</mapping> + <mapping rdbms="Oracle9i">BLOB</mapping> + <mapping rdbms="SQL Server 2005">IMAGE</mapping> + <mapping rdbms="SQL Server 2000">IMAGE</mapping> + <mapping rdbms="DB2/390 8">BLOB, size</mapping> + <mapping rdbms="DB2/390 7">BLOB, size</mapping> + <mapping rdbms="DB2/UDB 8.1">BLOB, size</mapping> + <mapping rdbms="DB2/UDB 7.1">BLOB, size</mapping> + </logicaltype> + <logicaltype name="Integer" objectid="LOGDT011"> + <mapping rdbms="Oracle Database 11g">INTEGER</mapping> + <mapping rdbms="Oracle Database 10g">INTEGER</mapping> + <mapping rdbms="Oracle9i">INTEGER</mapping> + <mapping rdbms="SQL Server 2005">INTEGER</mapping> + <mapping rdbms="SQL Server 2000">INTEGER</mapping> + <mapping rdbms="DB2/390 8">INTEGER</mapping> + <mapping rdbms="DB2/390 7">INTEGER</mapping> + <mapping rdbms="DB2/UDB 8.1">INTEGER</mapping> + <mapping rdbms="DB2/UDB 7.1">INTEGER</mapping> + </logicaltype> + <logicaltype name="Long Char" objectid="LogDes-1768A872-F385-FDBA-D95E-0CB63F5908E2@LOGDT10045"> + <mapping rdbms="Oracle Database 11g">LONG</mapping> + <mapping rdbms="Oracle Database 10g">LONG</mapping> + <mapping rdbms="Oracle9i">LONG</mapping> + <mapping rdbms="SQL Server 2005">VARCHAR, size</mapping> + <mapping rdbms="SQL Server 2000">VARCHAR, size</mapping> + <mapping rdbms="DB2/390 8">CLOB, size</mapping> + <mapping rdbms="DB2/390 7">CLOB, size</mapping> + <mapping rdbms="DB2/UDB 8.1">CLOB, size</mapping> + <mapping rdbms="DB2/UDB 7.1">CLOB, size</mapping> + </logicaltype> + <logicaltype name="Long_Raw" objectid="LogDes-7DD553FD-11E8-61FA-399D-2E531FB621D0@LOGDT10036"> + <mapping rdbms="Oracle Database 11g">LONG RAW</mapping> + <mapping rdbms="Oracle Database 10g">LONG RAW</mapping> + <mapping rdbms="Oracle9i">LONG RAW</mapping> + <mapping rdbms="SQL Server 2005">VARBINARY, size</mapping> + <mapping rdbms="SQL Server 2000">VARBINARY, size</mapping> + <mapping rdbms="DB2/390 8">BLOB, size</mapping> + <mapping rdbms="DB2/390 7">BLOB, size</mapping> + <mapping rdbms="DB2/UDB 8.1">BLOB, size</mapping> + <mapping rdbms="DB2/UDB 7.1">BLOB, size</mapping> + </logicaltype> + <logicaltype name="MONEY" objectid="LOGDT043"> + <mapping rdbms="Oracle Database 11g">NUMBER, precision, scale</mapping> + <mapping rdbms="Oracle Database 10g">NUMBER, precision, scale</mapping> + <mapping rdbms="Oracle9i">NUMBER, precision, scale</mapping> + <mapping rdbms="SQL Server 2005">MONEY</mapping> + <mapping rdbms="SQL Server 2000">MONEY</mapping> + <mapping rdbms="DB2/390 8">DOUBLE</mapping> + <mapping rdbms="DB2/390 7">DOUBLE</mapping> + <mapping rdbms="DB2/UDB 8.1">DOUBLE</mapping> + <mapping rdbms="DB2/UDB 7.1">DOUBLE</mapping> + </logicaltype> + <logicaltype name="NCHAR" objectid="LOGDT035"> + <mapping rdbms="Oracle Database 11g">NCHAR, size</mapping> + <mapping rdbms="Oracle Database 10g">NCHAR, size</mapping> + <mapping rdbms="Oracle9i">NCHAR, size</mapping> + <mapping rdbms="SQL Server 2005">NCHAR, size</mapping> + <mapping rdbms="SQL Server 2000">NCHAR, size</mapping> + <mapping rdbms="DB2/390 8">CHAR, size</mapping> + <mapping rdbms="DB2/390 7">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 8.1">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 7.1">CHAR, size</mapping> + </logicaltype> + <logicaltype name="NClob" objectid="LogDes-7DD553FD-11E8-61FA-399D-2E531FB621D0@LOGDT10035"> + <mapping rdbms="Oracle Database 11g">NCLOB</mapping> + <mapping rdbms="Oracle Database 10g">NCLOB</mapping> + <mapping rdbms="Oracle9i">NCLOB</mapping> + <mapping rdbms="SQL Server 2005">NTEXT</mapping> + <mapping rdbms="SQL Server 2000">NTEXT</mapping> + <mapping rdbms="DB2/390 8">CLOB, size</mapping> + <mapping rdbms="DB2/390 7">CLOB, size</mapping> + <mapping rdbms="DB2/UDB 8.1">CLOB, size</mapping> + <mapping rdbms="DB2/UDB 7.1">CLOB, size</mapping> + </logicaltype> + <logicaltype name="NTEXT" objectid="LOGDT036"> + <mapping rdbms="Oracle Database 11g">NCLOB</mapping> + <mapping rdbms="Oracle Database 10g">NCLOB</mapping> + <mapping rdbms="Oracle9i">NCLOB</mapping> + <mapping rdbms="SQL Server 2005">NTEXT</mapping> + <mapping rdbms="SQL Server 2000">NTEXT</mapping> + <mapping rdbms="DB2/390 8">CLOB, size</mapping> + <mapping rdbms="DB2/390 7">CLOB, size</mapping> + <mapping rdbms="DB2/UDB 8.1">CLOB, size</mapping> + <mapping rdbms="DB2/UDB 7.1">CLOB, size</mapping> + </logicaltype> + <logicaltype name="NUMERIC" objectid="LOGDT019"> + <mapping rdbms="Oracle Database 11g">NUMBER, precision, scale</mapping> + <mapping rdbms="Oracle Database 10g">NUMBER, precision, scale</mapping> + <mapping rdbms="Oracle9i">NUMBER, precision, scale</mapping> + <mapping rdbms="SQL Server 2005">NUMERIC, precision, scale</mapping> + <mapping rdbms="SQL Server 2000">NUMERIC, precision, scale</mapping> + <mapping rdbms="DB2/390 8">NUMERIC, precision, scale</mapping> + <mapping rdbms="DB2/390 7">NUMERIC, precision, scale</mapping> + <mapping rdbms="DB2/UDB 8.1">NUMERIC, precision, scale</mapping> + <mapping rdbms="DB2/UDB 7.1">NUMERIC, precision, scale</mapping> + </logicaltype> + <logicaltype name="NVARCHAR" objectid="LOGDT037"> + <mapping rdbms="Oracle Database 11g">NVARCHAR2, size</mapping> + <mapping rdbms="Oracle Database 10g">NVARCHAR2, size</mapping> + <mapping rdbms="Oracle9i">NVARCHAR2, size</mapping> + <mapping rdbms="SQL Server 2005">NVARCHAR, size</mapping> + <mapping rdbms="SQL Server 2000">NVARCHAR, size</mapping> + <mapping rdbms="DB2/390 8">VARCHAR, size</mapping> + <mapping rdbms="DB2/390 7">VARCHAR, size</mapping> + <mapping rdbms="DB2/UDB 8.1">VARCHAR, size</mapping> + <mapping rdbms="DB2/UDB 7.1">VARCHAR, size</mapping> + </logicaltype> + <logicaltype name="ORDAUDIO" objectid="LogDes-4972B6D2-6F93-8AE5-6E24-3599E65A7CFE@LOGDT10005"> + <mapping rdbms="Oracle Database 11g">ORDSYS.ORDAudio</mapping> + <mapping rdbms="Oracle Database 10g">ORDSYS.ORDAudio</mapping> + <mapping rdbms="Oracle9i">ORDSYS.ORDAudio</mapping> + <mapping rdbms="SQL Server 2005">UNKNOWN</mapping> + <mapping rdbms="SQL Server 2000">UNKNOWN</mapping> + <mapping rdbms="DB2/390 8">UNKNOWN</mapping> + <mapping rdbms="DB2/390 7">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 8.1">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 7.1">UNKNOWN</mapping> + </logicaltype> + <logicaltype name="ORDDOC" objectid="LogDes-4972B6D2-6F93-8AE5-6E24-3599E65A7CFE@LOGDT10009"> + <mapping rdbms="Oracle Database 11g">ORDSYS.ORDDoc</mapping> + <mapping rdbms="Oracle Database 10g">ORDSYS.ORDDoc</mapping> + <mapping rdbms="Oracle9i">ORDSYS.ORDDoc</mapping> + <mapping rdbms="SQL Server 2005">UNKNOWN</mapping> + <mapping rdbms="SQL Server 2000">UNKNOWN</mapping> + <mapping rdbms="DB2/390 8">UNKNOWN</mapping> + <mapping rdbms="DB2/390 7">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 8.1">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 7.1">UNKNOWN</mapping> + </logicaltype> + <logicaltype name="ORDIMAGE" objectid="LogDes-4972B6D2-6F93-8AE5-6E24-3599E65A7CFE@LOGDT10006"> + <mapping rdbms="Oracle Database 11g">ORDSYS.ORDImage</mapping> + <mapping rdbms="Oracle Database 10g">ORDSYS.ORDImage</mapping> + <mapping rdbms="Oracle9i">ORDSYS.ORDImage</mapping> + <mapping rdbms="SQL Server 2005">UNKNOWN</mapping> + <mapping rdbms="SQL Server 2000">UNKNOWN</mapping> + <mapping rdbms="DB2/390 8">UNKNOWN</mapping> + <mapping rdbms="DB2/390 7">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 8.1">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 7.1">UNKNOWN</mapping> + </logicaltype> + <logicaltype name="ORDIMAGE_SIGNATURE" objectid="LogDes-4972B6D2-6F93-8AE5-6E24-3599E65A7CFE@LOGDT10007"> + <mapping rdbms="Oracle Database 11g">ORDSYS.ORDImageSignature</mapping> + <mapping rdbms="Oracle Database 10g">ORDSYS.ORDImageSignature</mapping> + <mapping rdbms="Oracle9i">ORDSYS.ORDImageSignature</mapping> + <mapping rdbms="SQL Server 2005">UNKNOWN</mapping> + <mapping rdbms="SQL Server 2000">UNKNOWN</mapping> + <mapping rdbms="DB2/390 8">UNKNOWN</mapping> + <mapping rdbms="DB2/390 7">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 8.1">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 7.1">UNKNOWN</mapping> + </logicaltype> + <logicaltype name="ORDVIDEO" objectid="LogDes-4972B6D2-6F93-8AE5-6E24-3599E65A7CFE@LOGDT10008"> + <mapping rdbms="Oracle Database 11g">ORDSYS.ORDVideo</mapping> + <mapping rdbms="Oracle Database 10g">ORDSYS.ORDVideo</mapping> + <mapping rdbms="Oracle9i">ORDSYS.ORDVideo</mapping> + <mapping rdbms="SQL Server 2005">UNKNOWN</mapping> + <mapping rdbms="SQL Server 2000">UNKNOWN</mapping> + <mapping rdbms="DB2/390 8">UNKNOWN</mapping> + <mapping rdbms="DB2/390 7">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 8.1">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 7.1">UNKNOWN</mapping> + </logicaltype> + <logicaltype name="REAL" objectid="LOGDT022"> + <mapping rdbms="Oracle Database 11g">REAL</mapping> + <mapping rdbms="Oracle Database 10g">REAL</mapping> + <mapping rdbms="Oracle9i">REAL</mapping> + <mapping rdbms="SQL Server 2005">REAL, precision</mapping> + <mapping rdbms="SQL Server 2000">REAL, precision</mapping> + <mapping rdbms="DB2/390 8">REAL</mapping> + <mapping rdbms="DB2/390 7">REAL</mapping> + <mapping rdbms="DB2/UDB 8.1">REAL</mapping> + <mapping rdbms="DB2/UDB 7.1">REAL</mapping> + </logicaltype> + <logicaltype name="ROWID" objectid="LOGDT032"> + <mapping rdbms="Oracle Database 11g">ROWID</mapping> + <mapping rdbms="Oracle Database 10g">ROWID</mapping> + <mapping rdbms="Oracle9i">ROWID</mapping> + <mapping rdbms="SQL Server 2005">CHAR, size</mapping> + <mapping rdbms="SQL Server 2000">CHAR, size</mapping> + <mapping rdbms="DB2/390 8">ROWID</mapping> + <mapping rdbms="DB2/390 7">ROWID</mapping> + <mapping rdbms="DB2/UDB 8.1">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 7.1">CHAR, size</mapping> + </logicaltype> + <logicaltype name="Raw" objectid="LogDes-4BABEC65-108B-2A3C-F7C4-84AC47D292B0@LOGDT10040"> + <mapping rdbms="Oracle Database 11g">RAW, size</mapping> + <mapping rdbms="Oracle Database 10g">RAW, size</mapping> + <mapping rdbms="Oracle9i">RAW, size</mapping> + <mapping rdbms="SQL Server 2005">VARBINARY, size</mapping> + <mapping rdbms="SQL Server 2000">VARBINARY, size</mapping> + <mapping rdbms="DB2/390 8">VARGRAPHIC, size</mapping> + <mapping rdbms="DB2/390 7">VARGRAPHIC, size</mapping> + <mapping rdbms="DB2/UDB 8.1">VARGRAPHIC, size</mapping> + <mapping rdbms="DB2/UDB 7.1">VARGRAPHIC, size</mapping> + </logicaltype> + <logicaltype name="SMALLDATETIME" objectid="LOGDT038"> + <mapping rdbms="Oracle Database 11g">DATE</mapping> + <mapping rdbms="Oracle Database 10g">DATE</mapping> + <mapping rdbms="Oracle9i">DATE</mapping> + <mapping rdbms="SQL Server 2005">SMALLDATETIME</mapping> + <mapping rdbms="SQL Server 2000">SMALLDATETIME</mapping> + <mapping rdbms="DB2/390 8">TIMESTAMP</mapping> + <mapping rdbms="DB2/390 7">TIMESTAMP</mapping> + <mapping rdbms="DB2/UDB 8.1">TIMESTAMP</mapping> + <mapping rdbms="DB2/UDB 7.1">TIMESTAMP</mapping> + </logicaltype> + <logicaltype name="SMALLINT" objectid="LOGDT018"> + <mapping rdbms="Oracle Database 11g">SMALLINT</mapping> + <mapping rdbms="Oracle Database 10g">SMALLINT</mapping> + <mapping rdbms="Oracle9i">SMALLINT</mapping> + <mapping rdbms="SQL Server 2005">SMALLINT</mapping> + <mapping rdbms="SQL Server 2000">SMALLINT</mapping> + <mapping rdbms="DB2/390 8">SMALLINT</mapping> + <mapping rdbms="DB2/390 7">SMALLINT</mapping> + <mapping rdbms="DB2/UDB 8.1">SMALLINT</mapping> + <mapping rdbms="DB2/UDB 7.1">SMALLINT</mapping> + </logicaltype> + <logicaltype name="SMALLMONEY" objectid="LOGDT044"> + <mapping rdbms="Oracle Database 11g">NUMBER, precision, scale</mapping> + <mapping rdbms="Oracle Database 10g">NUMBER, precision, scale</mapping> + <mapping rdbms="Oracle9i">NUMBER, precision, scale</mapping> + <mapping rdbms="SQL Server 2005">SMALLMONEY</mapping> + <mapping rdbms="SQL Server 2000">SMALLMONEY</mapping> + <mapping rdbms="DB2/390 8">REAL</mapping> + <mapping rdbms="DB2/390 7">REAL</mapping> + <mapping rdbms="DB2/UDB 8.1">REAL</mapping> + <mapping rdbms="DB2/UDB 7.1">REAL</mapping> + </logicaltype> + <logicaltype name="SQL_VARIANT" objectid="LOGDT045"> + <mapping rdbms="Oracle Database 11g">SYS.ANYDATA</mapping> + <mapping rdbms="Oracle Database 10g">SYS.ANYDATA</mapping> + <mapping rdbms="Oracle9i">SYS.ANYDATA</mapping> + <mapping rdbms="SQL Server 2005">SQL_VARIANT</mapping> + <mapping rdbms="SQL Server 2000">SQL_VARIANT</mapping> + <mapping rdbms="DB2/390 8">UNKNOWN</mapping> + <mapping rdbms="DB2/390 7">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 8.1">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 7.1">UNKNOWN</mapping> + </logicaltype> + <logicaltype name="SYSNAME" objectid="LOGDT039"> + <mapping rdbms="Oracle Database 11g">VARCHAR2, size</mapping> + <mapping rdbms="Oracle Database 10g">VARCHAR2, size</mapping> + <mapping rdbms="Oracle9i">VARCHAR2, size</mapping> + <mapping rdbms="SQL Server 2005">SYSNAME</mapping> + <mapping rdbms="SQL Server 2000">SYSNAME</mapping> + <mapping rdbms="DB2/390 8">VARCHAR, size</mapping> + <mapping rdbms="DB2/390 7">VARCHAR, size</mapping> + <mapping rdbms="DB2/UDB 8.1">VARCHAR, size</mapping> + <mapping rdbms="DB2/UDB 7.1">VARCHAR, size</mapping> + </logicaltype> + <logicaltype name="SYS_ANYDATA" objectid="LogDes-F046B719-7D91-3873-3302-38C441683842@LOGDT10010"> + <mapping rdbms="Oracle Database 11g">SYS.ANYDATA</mapping> + <mapping rdbms="Oracle Database 10g">SYS.ANYDATA</mapping> + <mapping rdbms="Oracle9i">SYS.ANYDATA</mapping> + <mapping rdbms="SQL Server 2005">SQL_VARIANT</mapping> + <mapping rdbms="SQL Server 2000">SQL_VARIANT</mapping> + <mapping rdbms="DB2/390 8">UNKNOWN</mapping> + <mapping rdbms="DB2/390 7">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 8.1">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 7.1">UNKNOWN</mapping> + </logicaltype> + <logicaltype name="SYS_ANYDATASET" objectid="LogDes-22E251EB-9F6C-8137-56B2-DD4B87DC1E33@LOGDT10030"> + <mapping rdbms="Oracle Database 11g">SYS.ANYDATASET</mapping> + <mapping rdbms="Oracle Database 10g">SYS.ANYDATASET</mapping> + <mapping rdbms="Oracle9i">SYS.ANYDATASET</mapping> + <mapping rdbms="SQL Server 2005">UNKNOWN</mapping> + <mapping rdbms="SQL Server 2000">UNKNOWN</mapping> + <mapping rdbms="DB2/390 8">UNKNOWN</mapping> + <mapping rdbms="DB2/390 7">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 8.1">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 7.1">UNKNOWN</mapping> + </logicaltype> + <logicaltype name="SYS_ANYTYPE" objectid="LogDes-F046B719-7D91-3873-3302-38C441683842@LOGDT10011"> + <mapping rdbms="Oracle Database 11g">SYS.ANYTYPE</mapping> + <mapping rdbms="Oracle Database 10g">SYS.ANYTYPE</mapping> + <mapping rdbms="Oracle9i">SYS.ANYTYPE</mapping> + <mapping rdbms="SQL Server 2005">UNKNOWN</mapping> + <mapping rdbms="SQL Server 2000">UNKNOWN</mapping> + <mapping rdbms="DB2/390 8">UNKNOWN</mapping> + <mapping rdbms="DB2/390 7">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 8.1">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 7.1">UNKNOWN</mapping> + </logicaltype> + <logicaltype name="TEXT" objectid="LOGDT040"> + <mapping rdbms="Oracle Database 11g">CLOB</mapping> + <mapping rdbms="Oracle Database 10g">CLOB</mapping> + <mapping rdbms="Oracle9i">CLOB</mapping> + <mapping rdbms="SQL Server 2005">TEXT</mapping> + <mapping rdbms="SQL Server 2000">TEXT</mapping> + <mapping rdbms="DB2/390 8">VARCHAR, size</mapping> + <mapping rdbms="DB2/390 7">VARCHAR, size</mapping> + <mapping rdbms="DB2/UDB 8.1">VARCHAR, size</mapping> + <mapping rdbms="DB2/UDB 7.1">VARCHAR, size</mapping> + </logicaltype> + <logicaltype name="TIMESTAMP WITH LOCAL TIME ZONE" objectid="LOGDT047"> + <mapping rdbms="Oracle Database 11g">TIMESTAMP WITH LOCAL TIME ZONE, precision</mapping> + <mapping rdbms="Oracle Database 10g">TIMESTAMP WITH LOCAL TIME ZONE, precision</mapping> + <mapping rdbms="Oracle9i">TIMESTAMP WITH LOCAL TIME ZONE, precision</mapping> + <mapping rdbms="SQL Server 2005">DATETIME</mapping> + <mapping rdbms="SQL Server 2000">DATETIME</mapping> + <mapping rdbms="DB2/390 8">TIMESTAMP</mapping> + <mapping rdbms="DB2/390 7">TIMESTAMP</mapping> + <mapping rdbms="DB2/UDB 8.1">TIMESTAMP</mapping> + <mapping rdbms="DB2/UDB 7.1">TIMESTAMP</mapping> + </logicaltype> + <logicaltype name="TIMESTAMP WITH TIME ZONE" objectid="LOGDT046"> + <mapping rdbms="Oracle Database 11g">TIMESTAMP WITH TIME ZONE, precision</mapping> + <mapping rdbms="Oracle Database 10g">TIMESTAMP WITH TIME ZONE, precision</mapping> + <mapping rdbms="Oracle9i">TIMESTAMP WITH TIME ZONE, precision</mapping> + <mapping rdbms="SQL Server 2005">DATETIME</mapping> + <mapping rdbms="SQL Server 2000">DATETIME</mapping> + <mapping rdbms="DB2/390 8">TIMESTAMP</mapping> + <mapping rdbms="DB2/390 7">TIMESTAMP</mapping> + <mapping rdbms="DB2/UDB 8.1">TIMESTAMP</mapping> + <mapping rdbms="DB2/UDB 7.1">TIMESTAMP</mapping> + </logicaltype> + <logicaltype name="TINYINT" objectid="LOGDT042"> + <mapping rdbms="Oracle Database 11g">SMALLINT</mapping> + <mapping rdbms="Oracle Database 10g">SMALLINT</mapping> + <mapping rdbms="Oracle9i">SMALLINT</mapping> + <mapping rdbms="SQL Server 2005">TINYINT</mapping> + <mapping rdbms="SQL Server 2000">TINYINT</mapping> + <mapping rdbms="DB2/390 8">SMALLINT</mapping> + <mapping rdbms="DB2/390 7">SMALLINT</mapping> + <mapping rdbms="DB2/UDB 8.1">SMALLINT</mapping> + <mapping rdbms="DB2/UDB 7.1">SMALLINT</mapping> + </logicaltype> + <logicaltype name="Time" objectid="LOGDT014"> + <mapping rdbms="Oracle Database 11g">DATE</mapping> + <mapping rdbms="Oracle Database 10g">DATE</mapping> + <mapping rdbms="Oracle9i">DATE</mapping> + <mapping rdbms="SQL Server 2005">DATETIME</mapping> + <mapping rdbms="SQL Server 2000">DATETIME</mapping> + <mapping rdbms="DB2/390 8">TIME</mapping> + <mapping rdbms="DB2/390 7">TIME</mapping> + <mapping rdbms="DB2/UDB 8.1">TIME</mapping> + <mapping rdbms="DB2/UDB 7.1">TIME</mapping> + </logicaltype> + <logicaltype name="Timestamp" objectid="LOGDT015"> + <mapping rdbms="Oracle Database 11g">TIMESTAMP, precision</mapping> + <mapping rdbms="Oracle Database 10g">TIMESTAMP, precision</mapping> + <mapping rdbms="Oracle9i">TIMESTAMP, precision</mapping> + <mapping rdbms="SQL Server 2005">DATETIME</mapping> + <mapping rdbms="SQL Server 2000">DATETIME</mapping> + <mapping rdbms="DB2/390 8">TIMESTAMP</mapping> + <mapping rdbms="DB2/390 7">TIMESTAMP</mapping> + <mapping rdbms="DB2/UDB 8.1">TIMESTAMP</mapping> + <mapping rdbms="DB2/UDB 7.1">TIMESTAMP</mapping> + </logicaltype> + <logicaltype name="UNIQUEIDENTIFIER" objectid="LOGDT057"> + <mapping rdbms="Oracle Database 11g">CHAR, size</mapping> + <mapping rdbms="Oracle Database 10g">CHAR, size</mapping> + <mapping rdbms="Oracle9i">CHAR, size</mapping> + <mapping rdbms="SQL Server 2005">UNIQUEIDENTIFIER</mapping> + <mapping rdbms="SQL Server 2000">UNIQUEIDENTIFIER</mapping> + <mapping rdbms="DB2/390 8">CHAR, size</mapping> + <mapping rdbms="DB2/390 7">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 8.1">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 7.1">CHAR, size</mapping> + </logicaltype> + <logicaltype name="URIType" objectid="LOGDT051"> + <mapping rdbms="Oracle Database 11g">URITYPE</mapping> + <mapping rdbms="Oracle Database 10g">URITYPE</mapping> + <mapping rdbms="Oracle9i">URITYPE</mapping> + <mapping rdbms="SQL Server 2005">CHAR, size</mapping> + <mapping rdbms="SQL Server 2000">CHAR, size</mapping> + <mapping rdbms="DB2/390 8">CHAR, size</mapping> + <mapping rdbms="DB2/390 7">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 8.1">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 7.1">CHAR, size</mapping> + </logicaltype> + <logicaltype name="URowID" objectid="LogDes-4BABEC65-108B-2A3C-F7C4-84AC47D292B0@LOGDT10041"> + <mapping rdbms="Oracle Database 11g">UROWID, size</mapping> + <mapping rdbms="Oracle Database 10g">UROWID, size</mapping> + <mapping rdbms="Oracle9i">UROWID, size</mapping> + <mapping rdbms="SQL Server 2005">VARCHAR, size</mapping> + <mapping rdbms="SQL Server 2000">VARCHAR, size</mapping> + <mapping rdbms="DB2/390 8">VARCHAR, size</mapping> + <mapping rdbms="DB2/390 7">VARCHAR, size</mapping> + <mapping rdbms="DB2/UDB 8.1">VARCHAR, size</mapping> + <mapping rdbms="DB2/UDB 7.1">VARCHAR, size</mapping> + </logicaltype> + <logicaltype name="VARBINARY" objectid="LOGDT041"> + <mapping rdbms="Oracle Database 11g">BLOB</mapping> + <mapping rdbms="Oracle Database 10g">BLOB</mapping> + <mapping rdbms="Oracle9i">BLOB</mapping> + <mapping rdbms="SQL Server 2005">VARBINARY, size</mapping> + <mapping rdbms="SQL Server 2000">VARBINARY, size</mapping> + <mapping rdbms="DB2/390 8">BLOB, size</mapping> + <mapping rdbms="DB2/390 7">BLOB, size</mapping> + <mapping rdbms="DB2/UDB 8.1">BLOB, size</mapping> + <mapping rdbms="DB2/UDB 7.1">BLOB, size</mapping> + </logicaltype> + <logicaltype name="VARCHAR" objectid="LOGDT024"> + <mapping rdbms="Oracle Database 11g">VARCHAR2, size</mapping> + <mapping rdbms="Oracle Database 10g">VARCHAR2, size</mapping> + <mapping rdbms="Oracle9i">VARCHAR2, size</mapping> + <mapping rdbms="SQL Server 2005">VARCHAR, size</mapping> + <mapping rdbms="SQL Server 2000">VARCHAR, size</mapping> + <mapping rdbms="DB2/390 8">VARCHAR, size</mapping> + <mapping rdbms="DB2/390 7">VARCHAR, size</mapping> + <mapping rdbms="DB2/UDB 8.1">VARCHAR, size</mapping> + <mapping rdbms="DB2/UDB 7.1">VARCHAR, size</mapping> + </logicaltype> + <logicaltype name="VARGRAPHIC" objectid="LOGDT023"> + <mapping rdbms="Oracle Database 11g">BLOB</mapping> + <mapping rdbms="Oracle Database 10g">BLOB</mapping> + <mapping rdbms="Oracle9i">BLOB</mapping> + <mapping rdbms="SQL Server 2005">VARBINARY, size</mapping> + <mapping rdbms="SQL Server 2000">VARBINARY, size</mapping> + <mapping rdbms="DB2/390 8">VARGRAPHIC, size</mapping> + <mapping rdbms="DB2/390 7">VARGRAPHIC, size</mapping> + <mapping rdbms="DB2/UDB 8.1">VARGRAPHIC, size</mapping> + <mapping rdbms="DB2/UDB 7.1">VARGRAPHIC, size</mapping> + </logicaltype> + <logicaltype name="Video" objectid="LOGDT016"> + <mapping rdbms="Oracle Database 11g">BLOB</mapping> + <mapping rdbms="Oracle Database 10g">BLOB</mapping> + <mapping rdbms="Oracle9i">BLOB</mapping> + <mapping rdbms="SQL Server 2005">IMAGE</mapping> + <mapping rdbms="SQL Server 2000">IMAGE</mapping> + <mapping rdbms="DB2/390 8">VARGRAPHIC, size</mapping> + <mapping rdbms="DB2/390 7">VARGRAPHIC, size</mapping> + <mapping rdbms="DB2/UDB 8.1">BLOB</mapping> + <mapping rdbms="DB2/UDB 7.1">BLOB</mapping> + </logicaltype> + <logicaltype name="XDBURIType" objectid="LOGDT053"> + <mapping rdbms="Oracle Database 11g">XDBURITYPE</mapping> + <mapping rdbms="Oracle Database 10g">XDBURITYPE</mapping> + <mapping rdbms="Oracle9i">XDBURITYPE</mapping> + <mapping rdbms="SQL Server 2005">CHAR, size</mapping> + <mapping rdbms="SQL Server 2000">CHAR, size</mapping> + <mapping rdbms="DB2/390 8">CHAR, size</mapping> + <mapping rdbms="DB2/390 7">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 8.1">CHAR, size</mapping> + <mapping rdbms="DB2/UDB 7.1">CHAR, size</mapping> + </logicaltype> + <logicaltype name="XMLType" objectid="LOGDT050"> + <mapping rdbms="Oracle Database 11g">XMLTYPE</mapping> + <mapping rdbms="Oracle Database 10g">XMLTYPE</mapping> + <mapping rdbms="Oracle9i">XMLTYPE</mapping> + <mapping rdbms="SQL Server 2005">XML</mapping> + <mapping rdbms="SQL Server 2000">TEXT</mapping> + <mapping rdbms="DB2/390 8">CLOB, size</mapping> + <mapping rdbms="DB2/390 7">CLOB, size</mapping> + <mapping rdbms="DB2/UDB 8.1">XML</mapping> + <mapping rdbms="DB2/UDB 7.1">CLOB, size</mapping> + </logicaltype> + <logicaltype name="unknown" objectid="LOGDT017" default="true"> + <mapping rdbms="Oracle Database 11g">UNKNOWN</mapping> + <mapping rdbms="Oracle Database 10g">UNKNOWN</mapping> + <mapping rdbms="Oracle9i">UNKNOWN</mapping> + <mapping rdbms="SQL Server 2005">UNKNOWN</mapping> + <mapping rdbms="SQL Server 2000">UNKNOWN</mapping> + <mapping rdbms="DB2/390 8">UNKNOWN</mapping> + <mapping rdbms="DB2/390 7">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 8.1">UNKNOWN</mapping> + <mapping rdbms="DB2/UDB 7.1">UNKNOWN</mapping> + </logicaltype> + <native_to_logical_mappings> + <mappings_for_RDBMS_type rdbms_type="Oracle Database 11g"> + <mapping native_type="BFILE" logicaltype="BFile" log_type_id="LogDes-7DD553FD-11E8-61FA-399D-2E531FB621D0@LOGDT10034" /> + <mapping native_type="BINARY_DOUBLE" logicaltype="BINARY DOUBLE" log_type_id="LOGDT056" /> + <mapping native_type="BINARY_FLOAT" logicaltype="BINARY FLOAT" log_type_id="LOGDT055" /> + <mapping native_type="BLOB" logicaltype="BLOB" log_type_id="LOGDT029" /> + <mapping native_type="CHAR" logicaltype="CHAR" log_type_id="LOGDT025" /> + <mapping native_type="CHAR VARYING" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + <mapping native_type="CHARACTER" logicaltype="CHAR" log_type_id="LOGDT025" /> + <mapping native_type="CHARACTER VARYING" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + <mapping native_type="CLOB" logicaltype="CLOB" log_type_id="LOGDT028" /> + <mapping native_type="DATE" logicaltype="Date" log_type_id="LOGDT007" /> + <mapping native_type="DECIMAL" logicaltype="DECIMAL" log_type_id="LOGDT026" /> + <mapping native_type="DOUBLE" logicaltype="DOUBLE" log_type_id="LOGDT020" /> + <mapping native_type="FLOAT" logicaltype="FLOAT" log_type_id="LOGDT021" /> + <mapping native_type="INTEGER" logicaltype="Integer" log_type_id="LOGDT011" /> + <mapping native_type="LONG" logicaltype="Long Char" log_type_id="LogDes-1768A872-F385-FDBA-D95E-0CB63F5908E2@LOGDT10045" /> + <mapping native_type="LONG RAW" logicaltype="Long_Raw" log_type_id="LogDes-7DD553FD-11E8-61FA-399D-2E531FB621D0@LOGDT10036" /> + <mapping native_type="LONG ROW" logicaltype="Long_Raw" log_type_id="LogDes-7DD553FD-11E8-61FA-399D-2E531FB621D0@LOGDT10036" /> + <mapping native_type="LONGROW" logicaltype="Long_Raw" log_type_id="LogDes-7DD553FD-11E8-61FA-399D-2E531FB621D0@LOGDT10036" /> + <mapping native_type="NATIONAL CHAR" logicaltype="NCHAR" log_type_id="LOGDT035" /> + <mapping native_type="NATIONAL CHAR VARYING" logicaltype="NVARCHAR" log_type_id="LOGDT037" /> + <mapping native_type="NATIONAL CHARACTER" logicaltype="NCHAR" log_type_id="LOGDT035" /> + <mapping native_type="NATIONAL CHARACTER VARYING" logicaltype="NVARCHAR" log_type_id="LOGDT037" /> + <mapping native_type="NCHAR" logicaltype="NCHAR" log_type_id="LOGDT035" /> + <mapping native_type="NCHAR VARYING" logicaltype="NVARCHAR" log_type_id="LOGDT037" /> + <mapping native_type="NCLOB" logicaltype="NClob" log_type_id="LogDes-7DD553FD-11E8-61FA-399D-2E531FB621D0@LOGDT10035" /> + <mapping native_type="NUMBER" logicaltype="NUMERIC" log_type_id="LOGDT019" /> + <mapping native_type="NUMERIC" logicaltype="NUMERIC" log_type_id="LOGDT019" /> + <mapping native_type="RAW" logicaltype="Raw" log_type_id="LogDes-4BABEC65-108B-2A3C-F7C4-84AC47D292B0@LOGDT10040" /> + <mapping native_type="REAL" logicaltype="REAL" log_type_id="LOGDT022" /> + <mapping native_type="ROWID" logicaltype="ROWID" log_type_id="LOGDT032" /> + <mapping native_type="SMALLINT" logicaltype="SMALLINT" log_type_id="LOGDT018" /> + <mapping native_type="UROWID" logicaltype="URowID" log_type_id="LogDes-4BABEC65-108B-2A3C-F7C4-84AC47D292B0@LOGDT10041" /> + <mapping native_type="VARCHAR" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + <mapping native_type="VARCHAR2" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + </mappings_for_RDBMS_type> + <mappings_for_RDBMS_type rdbms_type="Oracle Database 10g"> + <mapping native_type="BFILE" logicaltype="BFile" log_type_id="LogDes-7DD553FD-11E8-61FA-399D-2E531FB621D0@LOGDT10034" /> + <mapping native_type="BINARY_DOUBLE" logicaltype="BINARY DOUBLE" log_type_id="LOGDT056" /> + <mapping native_type="BINARY_FLOAT" logicaltype="BINARY FLOAT" log_type_id="LOGDT055" /> + <mapping native_type="BLOB" logicaltype="BLOB" log_type_id="LOGDT029" /> + <mapping native_type="CHAR" logicaltype="CHAR" log_type_id="LOGDT025" /> + <mapping native_type="CHAR VARYING" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + <mapping native_type="CHARACTER" logicaltype="CHAR" log_type_id="LOGDT025" /> + <mapping native_type="CHARACTER VARYING" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + <mapping native_type="CLOB" logicaltype="CLOB" log_type_id="LOGDT028" /> + <mapping native_type="DATE" logicaltype="Date" log_type_id="LOGDT007" /> + <mapping native_type="DECIMAL" logicaltype="DECIMAL" log_type_id="LOGDT026" /> + <mapping native_type="DOUBLE" logicaltype="DOUBLE" log_type_id="LOGDT020" /> + <mapping native_type="FLOAT" logicaltype="FLOAT" log_type_id="LOGDT021" /> + <mapping native_type="INTEGER" logicaltype="Integer" log_type_id="LOGDT011" /> + <mapping native_type="LONG" logicaltype="Long Char" log_type_id="LogDes-1768A872-F385-FDBA-D95E-0CB63F5908E2@LOGDT10045" /> + <mapping native_type="LONG RAW" logicaltype="Long_Raw" log_type_id="LogDes-7DD553FD-11E8-61FA-399D-2E531FB621D0@LOGDT10036" /> + <mapping native_type="LONG ROW" logicaltype="Long_Raw" log_type_id="LogDes-7DD553FD-11E8-61FA-399D-2E531FB621D0@LOGDT10036" /> + <mapping native_type="LONGROW" logicaltype="Long_Raw" log_type_id="LogDes-7DD553FD-11E8-61FA-399D-2E531FB621D0@LOGDT10036" /> + <mapping native_type="NATIONAL CHAR" logicaltype="NCHAR" log_type_id="LOGDT035" /> + <mapping native_type="NATIONAL CHAR VARYING" logicaltype="NVARCHAR" log_type_id="LOGDT037" /> + <mapping native_type="NATIONAL CHARACTER" logicaltype="NCHAR" log_type_id="LOGDT035" /> + <mapping native_type="NATIONAL CHARACTER VARYING" logicaltype="NVARCHAR" log_type_id="LOGDT037" /> + <mapping native_type="NCHAR" logicaltype="NCHAR" log_type_id="LOGDT035" /> + <mapping native_type="NCHAR VARYING" logicaltype="NVARCHAR" log_type_id="LOGDT037" /> + <mapping native_type="NCLOB" logicaltype="NClob" log_type_id="LogDes-7DD553FD-11E8-61FA-399D-2E531FB621D0@LOGDT10035" /> + <mapping native_type="NUMBER" logicaltype="NUMERIC" log_type_id="LOGDT019" /> + <mapping native_type="NUMERIC" logicaltype="NUMERIC" log_type_id="LOGDT019" /> + <mapping native_type="RAW" logicaltype="Raw" log_type_id="LogDes-4BABEC65-108B-2A3C-F7C4-84AC47D292B0@LOGDT10040" /> + <mapping native_type="REAL" logicaltype="REAL" log_type_id="LOGDT022" /> + <mapping native_type="ROWID" logicaltype="ROWID" log_type_id="LOGDT032" /> + <mapping native_type="SMALLINT" logicaltype="SMALLINT" log_type_id="LOGDT018" /> + <mapping native_type="UROWID" logicaltype="URowID" log_type_id="LogDes-4BABEC65-108B-2A3C-F7C4-84AC47D292B0@LOGDT10041" /> + <mapping native_type="VARCHAR" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + <mapping native_type="VARCHAR2" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + </mappings_for_RDBMS_type> + <mappings_for_RDBMS_type rdbms_type="Oracle9i"> + <mapping native_type="BFILE" logicaltype="BFile" log_type_id="LogDes-7DD553FD-11E8-61FA-399D-2E531FB621D0@LOGDT10034" /> + <mapping native_type="BLOB" logicaltype="BLOB" log_type_id="LOGDT029" /> + <mapping native_type="CHAR" logicaltype="CHAR" log_type_id="LOGDT025" /> + <mapping native_type="CHAR VARYING" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + <mapping native_type="CHARACTER" logicaltype="CHAR" log_type_id="LOGDT025" /> + <mapping native_type="CHARACTER VARYING" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + <mapping native_type="CLOB" logicaltype="CLOB" log_type_id="LOGDT028" /> + <mapping native_type="DATE" logicaltype="Date" log_type_id="LOGDT007" /> + <mapping native_type="DBURITYPE" logicaltype="DBURIType" log_type_id="LOGDT054" /> + <mapping native_type="DECIMAL" logicaltype="DECIMAL" log_type_id="LOGDT026" /> + <mapping native_type="DOUBLE" logicaltype="DOUBLE" log_type_id="LOGDT020" /> + <mapping native_type="FLOAT" logicaltype="FLOAT" log_type_id="LOGDT021" /> + <mapping native_type="HTTPURITYPE" logicaltype="HTTPURIType" log_type_id="LOGDT052" /> + <mapping native_type="INTEGER" logicaltype="Integer" log_type_id="LOGDT011" /> + <mapping native_type="INTERVAL DAY TO SECOND" logicaltype="INTERVAL DAY TO SECOND" log_type_id="LOGDT049" /> + <mapping native_type="INTERVAL YEAR TO MONTH" logicaltype="INTERVAL YEAR TO MONTH" log_type_id="LOGDT048" /> + <mapping native_type="LONG" logicaltype="Long Char" log_type_id="LogDes-1768A872-F385-FDBA-D95E-0CB63F5908E2@LOGDT10045" /> + <mapping native_type="LONG RAW" logicaltype="Long_Raw" log_type_id="LogDes-7DD553FD-11E8-61FA-399D-2E531FB621D0@LOGDT10036" /> + <mapping native_type="LONG ROW" logicaltype="Long_Raw" log_type_id="LogDes-7DD553FD-11E8-61FA-399D-2E531FB621D0@LOGDT10036" /> + <mapping native_type="LONGROW" logicaltype="Long_Raw" log_type_id="LogDes-7DD553FD-11E8-61FA-399D-2E531FB621D0@LOGDT10036" /> + <mapping native_type="NATIONAL CHAR" logicaltype="NCHAR" log_type_id="LOGDT035" /> + <mapping native_type="NATIONAL CHAR VARYING" logicaltype="NVARCHAR" log_type_id="LOGDT037" /> + <mapping native_type="NATIONAL CHARACTER" logicaltype="NCHAR" log_type_id="LOGDT035" /> + <mapping native_type="NATIONAL CHARACTER VARYING" logicaltype="NVARCHAR" log_type_id="LOGDT037" /> + <mapping native_type="NCHAR" logicaltype="NCHAR" log_type_id="LOGDT035" /> + <mapping native_type="NCHAR VARYING" logicaltype="NVARCHAR" log_type_id="LOGDT037" /> + <mapping native_type="NCLOB" logicaltype="NClob" log_type_id="LogDes-7DD553FD-11E8-61FA-399D-2E531FB621D0@LOGDT10035" /> + <mapping native_type="NUMBER" logicaltype="NUMERIC" log_type_id="LOGDT019" /> + <mapping native_type="NUMERIC" logicaltype="NUMERIC" log_type_id="LOGDT019" /> + <mapping native_type="NVARCHAR2" logicaltype="NVARCHAR" log_type_id="LOGDT037" /> + <mapping native_type="ORDSYS.ORDAudio" logicaltype="ORDAUDIO" log_type_id="LogDes-4972B6D2-6F93-8AE5-6E24-3599E65A7CFE@LOGDT10005" /> + <mapping native_type="ORDSYS.ORDDoc" logicaltype="ORDDOC" log_type_id="LogDes-4972B6D2-6F93-8AE5-6E24-3599E65A7CFE@LOGDT10009" /> + <mapping native_type="ORDSYS.ORDImage" logicaltype="ORDIMAGE" log_type_id="LogDes-4972B6D2-6F93-8AE5-6E24-3599E65A7CFE@LOGDT10006" /> + <mapping native_type="ORDSYS.ORDImageSignature" logicaltype="ORDIMAGE_SIGNATURE" log_type_id="LogDes-4972B6D2-6F93-8AE5-6E24-3599E65A7CFE@LOGDT10007" /> + <mapping native_type="ORDSYS.ORDVideo" logicaltype="ORDVIDEO" log_type_id="LogDes-4972B6D2-6F93-8AE5-6E24-3599E65A7CFE@LOGDT10008" /> + <mapping native_type="RAW" logicaltype="Raw" log_type_id="LogDes-4BABEC65-108B-2A3C-F7C4-84AC47D292B0@LOGDT10040" /> + <mapping native_type="REAL" logicaltype="REAL" log_type_id="LOGDT022" /> + <mapping native_type="ROWID" logicaltype="ROWID" log_type_id="LOGDT032" /> + <mapping native_type="SMALLINT" logicaltype="SMALLINT" log_type_id="LOGDT018" /> + <mapping native_type="SYS.ANYDATA" logicaltype="SYS_ANYDATA" log_type_id="LogDes-F046B719-7D91-3873-3302-38C441683842@LOGDT10010" /> + <mapping native_type="SYS.ANYDATASET" logicaltype="SYS_ANYDATASET" log_type_id="LogDes-22E251EB-9F6C-8137-56B2-DD4B87DC1E33@LOGDT10030" /> + <mapping native_type="SYS.ANYTYPE" logicaltype="SYS_ANYTYPE" log_type_id="LogDes-F046B719-7D91-3873-3302-38C441683842@LOGDT10011" /> + <mapping native_type="TIMESTAMP" logicaltype="Timestamp" log_type_id="LOGDT015" /> + <mapping native_type="TIMESTAMP WITH LOCAL TIME ZONE" logicaltype="TIMESTAMP WITH LOCAL TIME ZONE" log_type_id="LOGDT047" /> + <mapping native_type="TIMESTAMP WITH TIME ZONE" logicaltype="TIMESTAMP WITH TIME ZONE" log_type_id="LOGDT046" /> + <mapping native_type="URITYPE" logicaltype="URIType" log_type_id="LOGDT051" /> + <mapping native_type="UROWID" logicaltype="URowID" log_type_id="LogDes-4BABEC65-108B-2A3C-F7C4-84AC47D292B0@LOGDT10041" /> + <mapping native_type="VARCHAR" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + <mapping native_type="VARCHAR2" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + <mapping native_type="XDBURITYPE" logicaltype="XDBURIType" log_type_id="LOGDT053" /> + <mapping native_type="XMLTYPE" logicaltype="XMLType" log_type_id="LOGDT050" /> + </mappings_for_RDBMS_type> + <mappings_for_RDBMS_type rdbms_type="SQL Server 2005"> + <mapping native_type="DATE" logicaltype="Date" log_type_id="LOGDT007" /> + <mapping native_type="DOUBLE" logicaltype="DOUBLE" log_type_id="LOGDT020" /> + <mapping native_type="XML" logicaltype="XMLType" log_type_id="LOGDT050" /> + </mappings_for_RDBMS_type> + <mappings_for_RDBMS_type rdbms_type="SQL Server 2000"> + <mapping native_type="BIGINT" logicaltype="BIGINT" log_type_id="LOGDT027" /> + <mapping native_type="BINARY" logicaltype="BINARY" log_type_id="LOGDT033" /> + <mapping native_type="BIT" logicaltype="BIT" log_type_id="LOGDT034" /> + <mapping native_type="CHAR" logicaltype="CHAR" log_type_id="LOGDT025" /> + <mapping native_type="DATE" logicaltype="Date" log_type_id="LOGDT007" /> + <mapping native_type="DATETIME" logicaltype="Datetime" log_type_id="LOGDT008" /> + <mapping native_type="DECIMAL" logicaltype="DECIMAL" log_type_id="LOGDT026" /> + <mapping native_type="DOUBLE" logicaltype="DOUBLE" log_type_id="LOGDT020" /> + <mapping native_type="FLOAT" logicaltype="FLOAT" log_type_id="LOGDT021" /> + <mapping native_type="IMAGE" logicaltype="Image" log_type_id="LOGDT010" /> + <mapping native_type="INT" logicaltype="Integer" log_type_id="LOGDT011" /> + <mapping native_type="INTEGER" logicaltype="Integer" log_type_id="LOGDT011" /> + <mapping native_type="MONEY" logicaltype="MONEY" log_type_id="LOGDT043" /> + <mapping native_type="NCHAR" logicaltype="NCHAR" log_type_id="LOGDT035" /> + <mapping native_type="NTEXT" logicaltype="NTEXT" log_type_id="LOGDT036" /> + <mapping native_type="NUMERIC" logicaltype="NUMERIC" log_type_id="LOGDT019" /> + <mapping native_type="NVARCHAR" logicaltype="NVARCHAR" log_type_id="LOGDT037" /> + <mapping native_type="REAL" logicaltype="REAL" log_type_id="LOGDT022" /> + <mapping native_type="ROWID" logicaltype="ROWID" log_type_id="LOGDT032" /> + <mapping native_type="SMALLDATETIME" logicaltype="SMALLDATETIME" log_type_id="LOGDT038" /> + <mapping native_type="SMALLINT" logicaltype="SMALLINT" log_type_id="LOGDT018" /> + <mapping native_type="SMALLMONEY" logicaltype="SMALLMONEY" log_type_id="LOGDT044" /> + <mapping native_type="SQL_VARIANT" logicaltype="SQL_VARIANT" log_type_id="LOGDT045" /> + <mapping native_type="SYSNAME" logicaltype="SYSNAME" log_type_id="LOGDT039" /> + <mapping native_type="TEXT" logicaltype="TEXT" log_type_id="LOGDT040" /> + <mapping native_type="TIMESTAMP" logicaltype="Timestamp" log_type_id="LOGDT015" /> + <mapping native_type="TINYINT" logicaltype="TINYINT" log_type_id="LOGDT042" /> + <mapping native_type="UNIQUEIDENTIFIER" logicaltype="UNIQUEIDENTIFIER" log_type_id="LOGDT057" /> + <mapping native_type="VARBINARY" logicaltype="VARBINARY" log_type_id="LOGDT041" /> + <mapping native_type="VARCHAR" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + </mappings_for_RDBMS_type> + <mappings_for_RDBMS_type rdbms_type="DB2/390 8"> + <mapping native_type="GRAPHIC" logicaltype="GRAPHIC" log_type_id="LOGDT031" /> + </mappings_for_RDBMS_type> + <mappings_for_RDBMS_type rdbms_type="DB2/390 7"> + <mapping native_type="BINARY LARGE OBJECT" logicaltype="BLOB" log_type_id="LOGDT029" /> + <mapping native_type="BLOB" logicaltype="BLOB" log_type_id="LOGDT029" /> + <mapping native_type="CHAR" logicaltype="CHAR" log_type_id="LOGDT025" /> + <mapping native_type="CHAR LARGE OBJECT" logicaltype="CLOB" log_type_id="LOGDT028" /> + <mapping native_type="CHAR VARYING" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + <mapping native_type="CHARACTER" logicaltype="CHAR" log_type_id="LOGDT025" /> + <mapping native_type="CHARACTER LARGE OBJECT" logicaltype="CLOB" log_type_id="LOGDT028" /> + <mapping native_type="CHARACTER VARYING" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + <mapping native_type="CLOB" logicaltype="CLOB" log_type_id="LOGDT028" /> + <mapping native_type="DATE" logicaltype="Date" log_type_id="LOGDT007" /> + <mapping native_type="DBCLOB" logicaltype="CLOB" log_type_id="LOGDT028" /> + <mapping native_type="DECIMAL" logicaltype="DECIMAL" log_type_id="LOGDT026" /> + <mapping native_type="DOUBLE" logicaltype="DOUBLE" log_type_id="LOGDT020" /> + <mapping native_type="FLOAT" logicaltype="FLOAT" log_type_id="LOGDT021" /> + <mapping native_type="GRAPHIC" logicaltype="GRAPHIC" log_type_id="LOGDT031" /> + <mapping native_type="INTEGER" logicaltype="Integer" log_type_id="LOGDT011" /> + <mapping native_type="LONG VARCHAR" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + <mapping native_type="LONG VARGRAPHIC" logicaltype="VARGRAPHIC" log_type_id="LOGDT023" /> + <mapping native_type="NUMERIC" logicaltype="NUMERIC" log_type_id="LOGDT019" /> + <mapping native_type="REAL" logicaltype="REAL" log_type_id="LOGDT022" /> + <mapping native_type="ROWID" logicaltype="ROWID" log_type_id="LOGDT032" /> + <mapping native_type="SMALLINT" logicaltype="SMALLINT" log_type_id="LOGDT018" /> + <mapping native_type="TIME" logicaltype="Time" log_type_id="LOGDT014" /> + <mapping native_type="TIMESTAMP" logicaltype="Timestamp" log_type_id="LOGDT015" /> + <mapping native_type="VARCHAR" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + <mapping native_type="VARGRAPHIC" logicaltype="VARGRAPHIC" log_type_id="LOGDT023" /> + </mappings_for_RDBMS_type> + <mappings_for_RDBMS_type rdbms_type="DB2/UDB 8.1"> + <mapping native_type="GRAPHIC" logicaltype="GRAPHIC" log_type_id="LOGDT031" /> + <mapping native_type="XML" logicaltype="XMLType" log_type_id="LOGDT050" /> + </mappings_for_RDBMS_type> + <mappings_for_RDBMS_type rdbms_type="DB2/UDB 7.1"> + <mapping native_type="BIGINT" logicaltype="BIGINT" log_type_id="LOGDT027" /> + <mapping native_type="BLOB" logicaltype="BLOB" log_type_id="LOGDT029" /> + <mapping native_type="CHAR" logicaltype="CHAR" log_type_id="LOGDT025" /> + <mapping native_type="CHAR VARYING" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + <mapping native_type="CHARACTER" logicaltype="CHAR" log_type_id="LOGDT025" /> + <mapping native_type="CHARACTER VARYING" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + <mapping native_type="CLOB" logicaltype="CLOB" log_type_id="LOGDT028" /> + <mapping native_type="DATALINK" logicaltype="DATALINK" log_type_id="LOGDT030" /> + <mapping native_type="DATE" logicaltype="Date" log_type_id="LOGDT007" /> + <mapping native_type="DBCLOB" logicaltype="CLOB" log_type_id="LOGDT028" /> + <mapping native_type="DECIMAL" logicaltype="DECIMAL" log_type_id="LOGDT026" /> + <mapping native_type="DOUBLE" logicaltype="DOUBLE" log_type_id="LOGDT020" /> + <mapping native_type="FLOAT" logicaltype="FLOAT" log_type_id="LOGDT021" /> + <mapping native_type="GRAPHIC" logicaltype="GRAPHIC" log_type_id="LOGDT031" /> + <mapping native_type="INTEGER" logicaltype="Integer" log_type_id="LOGDT011" /> + <mapping native_type="LONG VARCHAR" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + <mapping native_type="LONG VARGRAPHIC" logicaltype="VARGRAPHIC" log_type_id="LOGDT023" /> + <mapping native_type="NUMERIC" logicaltype="NUMERIC" log_type_id="LOGDT019" /> + <mapping native_type="REAL" logicaltype="REAL" log_type_id="LOGDT022" /> + <mapping native_type="SMALLINT" logicaltype="SMALLINT" log_type_id="LOGDT018" /> + <mapping native_type="TIME" logicaltype="Time" log_type_id="LOGDT014" /> + <mapping native_type="TIMESTAMP" logicaltype="Timestamp" log_type_id="LOGDT015" /> + <mapping native_type="VARCHAR" logicaltype="VARCHAR" log_type_id="LOGDT024" /> + <mapping native_type="VARGRAPHIC" logicaltype="VARGRAPHIC" log_type_id="LOGDT023" /> + </mappings_for_RDBMS_type> + </native_to_logical_mappings> + <ud_native_db_types /> +</logtypes>
\ No newline at end of file diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseComments.pgsql b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseComments.pgsql new file mode 100644 index 00000000..91978e37 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseComments.pgsql @@ -0,0 +1,1193 @@ +-- $Id: TestManagerDatabaseComments.pgsql $ +--- @file +-- Autogenerated from TestManagerDatabaseInit.pgsql. Do not edit! +-- + +-- +-- Copyright (C) 2012-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + +COMMENT ON COLUMN SystemLog.tsCreated IS + 'When this was logged.'; + +COMMENT ON COLUMN SystemLog.sEvent IS + 'The event type. +This is a 8 character string identifier so that we don''t need to change +some enum type everytime we introduce a new event type.'; + +COMMENT ON COLUMN SystemLog.sLogText IS + 'The log text.'; + +COMMENT ON TABLE Users IS + 'Test manager users. + +This is mainly for doing simple access checks before permitting access to +the test manager. This needs to be coordinated with +apache/ldap/Oracle-Single-Sign-On. + +The main purpose, though, is for tracing who changed the test config and +analysis data. + +@remarks This table stores history. Never update or delete anything. The + equivalent of deleting is done by setting the ''tsExpire'' field to + current_timestamp.'; + +COMMENT ON COLUMN Users.tsEffective IS + 'When this row starts taking effect (inclusive).'; + +COMMENT ON COLUMN Users.tsExpire IS + 'When this row stops being tsEffective (exclusive).'; + +COMMENT ON COLUMN Users.uidAuthor IS + 'The user id of the one who created/modified this entry. +Non-unique foreign key: Users(uid)'; + +COMMENT ON COLUMN Users.sUsername IS + 'User name.'; + +COMMENT ON COLUMN Users.sEmail IS + 'The email address of the user.'; + +COMMENT ON COLUMN Users.sFullName IS + 'The full name.'; + +COMMENT ON COLUMN Users.sLoginName IS + 'The login name used by apache.'; + +COMMENT ON COLUMN Users.fReadOnly IS + 'Read access only.'; + +COMMENT ON TABLE GlobalResources IS + 'Global resource configuration. + +For example an iSCSI target. + +@remarks This table stores history. Never update or delete anything. The + equivalent of deleting is done by setting the ''tsExpire'' field to + current_timestamp.'; + +COMMENT ON COLUMN GlobalResources.tsEffective IS + 'When this row starts taking effect (inclusive).'; + +COMMENT ON COLUMN GlobalResources.tsExpire IS + 'When this row stops being tsEffective (exclusive).'; + +COMMENT ON COLUMN GlobalResources.uidAuthor IS + 'The user id of the one who created/modified this entry. +Non-unique foreign key: Users(uid)'; + +COMMENT ON COLUMN GlobalResources.sName IS + 'The name of the resource.'; + +COMMENT ON COLUMN GlobalResources.sDescription IS + 'Optional resource description.'; + +COMMENT ON COLUMN GlobalResources.fEnabled IS + 'Indicates whether this resource is currently enabled (online).'; + +COMMENT ON TABLE BuildSources IS + 'Build sources. + +This is used by a scheduling group to select builds and the default +Validation Kit from the Builds table. + +@remarks This table stores history. Never update or delete anything. The + equivalent of deleting is done by setting the ''tsExpire'' field to + current_timestamp. + +@todo Any better way of representing this so we could more easily + join/whatever when searching for builds?'; + +COMMENT ON COLUMN BuildSources.tsEffective IS + 'When this row starts taking effect (inclusive).'; + +COMMENT ON COLUMN BuildSources.tsExpire IS + 'When this row stops being tsEffective (exclusive).'; + +COMMENT ON COLUMN BuildSources.uidAuthor IS + 'The user id of the one who created/modified this entry. +Non-unique foreign key: Users(uid)'; + +COMMENT ON COLUMN BuildSources.sName IS + 'The name of the build source.'; + +COMMENT ON COLUMN BuildSources.sDescription IS + 'Description.'; + +COMMENT ON COLUMN BuildSources.sProduct IS + 'Which product. +ASSUME that it is okay to limit a build source to a single product.'; + +COMMENT ON COLUMN BuildSources.sBranch IS + 'Which branch. +ASSUME that it is okay to limit a build source to a branch.'; + +COMMENT ON COLUMN BuildSources.asTypes IS + 'Build types to include, all matches if NULL. +@todo Weighting the types would be nice in a later version.'; + +COMMENT ON COLUMN BuildSources.asOsArches IS + 'Array of the ''sOs.sCpuArch'' to match, all matches if NULL. +See KBUILD_OSES in kBuild for a list of standard target OSes, and +KBUILD_ARCHES for a list of standard architectures. + +@remarks See marks on ''os-agnostic'' and ''noarch'' in BuildCategories.'; + +COMMENT ON COLUMN BuildSources.iFirstRevision IS + 'The first subversion tree revision to match, no lower limit if NULL.'; + +COMMENT ON COLUMN BuildSources.iLastRevision IS + 'The last subversion tree revision to match, no upper limit if NULL.'; + +COMMENT ON COLUMN BuildSources.cSecMaxAge IS + 'The maximum age of the builds in seconds, unlimited if NULL.'; + +COMMENT ON TABLE TestCases IS + 'Test case configuration. + +@remarks This table stores history. Never update or delete anything. The + equivalent of deleting is done by setting the ''tsExpire'' field to + current_timestamp.'; + +COMMENT ON COLUMN TestCases.tsEffective IS + 'When this row starts taking effect (inclusive).'; + +COMMENT ON COLUMN TestCases.tsExpire IS + 'When this row stops being tsEffective (exclusive).'; + +COMMENT ON COLUMN TestCases.uidAuthor IS + 'The user id of the one who created/modified this entry. +Non-unique foreign key: Users(uid)'; + +COMMENT ON COLUMN TestCases.sName IS + 'The name of the test case.'; + +COMMENT ON COLUMN TestCases.sDescription IS + 'Optional test case description.'; + +COMMENT ON COLUMN TestCases.fEnabled IS + 'Indicates whether this test case is currently enabled.'; + +COMMENT ON COLUMN TestCases.cSecTimeout IS + 'Default test case timeout given in seconds.'; + +COMMENT ON COLUMN TestCases.sTestBoxReqExpr IS + 'Default TestBox requirement expression (python boolean expression). +All the scheduler properties are available for use with the same names +as in that table. +If NULL everything matches.'; + +COMMENT ON COLUMN TestCases.sBuildReqExpr IS + 'Default build requirement expression (python boolean expression). +The following build properties are available: sProduct, sBranch, +sType, asOsArches, sVersion, iRevision, uidAuthor and idBuild. +If NULL everything matches.'; + +COMMENT ON COLUMN TestCases.sBaseCmd IS + 'The base command. +String suitable for executing in bourne shell with space as separator +(IFS). References to @BUILD_BINARIES@ will be replaced WITH the content +of the Builds(sBinaries) field.'; + +COMMENT ON COLUMN TestCases.sTestSuiteZips IS + 'Comma separated list of test suite zips (or tars) that the testbox will +need to download and expand prior to testing. +If NULL the current test suite of the scheduling group will be used (the +scheduling group will have an optional test suite build queue associated +with it). The current test suite can also be referenced by +@VALIDATIONKIT_ZIP@ in case more downloads are required. Files may also be +uploaded to the test manager download area, in which case the +@DOWNLOAD_BASE_URL@ prefix can be used to refer to this area.'; + +COMMENT ON TABLE TestCaseArgs IS + 'Test case argument list variations. + +For example, we have a test case that does a set of tests on a virtual +machine. To get better code/feature coverage of this testcase we wish to +run it with different guest hardware configuration. The test case may do +the same stuff, but the guest OS as well as the VMM may react differently to +the hardware configurations and uncover issues in the VMM, device emulation +or other places. + +Typical hardware variations are: + - guest memory size (RAM), + - guest video memory size (VRAM), + - virtual CPUs / cores / threads, + - virtual chipset + - virtual network interface card (NIC) + - USB 1.1, USB 2.0, no USB + +The TM web UI will help the user create a reasonable set of permutations +of these parameters, the user specifies a maximum and the TM uses certain +rules together with random selection to generate the desired number. The +UI will also help suggest fitting testbox requirements according to the +RAM/VRAM sizes and the virtual CPU counts. The user may then make +adjustments to the suggestions before commit them. + +Alternatively, the user may also enter all the permutations without any +help from the UI. + +Note! All test cases has at least one entry in this table, even if it is +empty, because testbox requirements are specified thru this. + +Querying the valid parameter lists for a testase this way: + SELECT * ... WHERE idTestCase = TestCases.idTestCase + AND tsExpire > <when> + AND tsEffective <= <when>; + +Querying the valid parameter list for the latest generation can be +simplified by just checking tsExpire date: + SELECT * ... WHERE idTestCase = TestCases.idTestCase + AND tsExpire == TIMESTAMP WITH TIME ZONE ''infinity''; + +@remarks This table stores history. Never update or delete anything. The + equivalent of deleting is done by setting the ''tsExpire'' field to + current_timestamp.'; + +COMMENT ON COLUMN TestCaseArgs.tsEffective IS + 'When this row starts taking effect (inclusive).'; + +COMMENT ON COLUMN TestCaseArgs.tsExpire IS + 'When this row stops being tsEffective (exclusive).'; + +COMMENT ON COLUMN TestCaseArgs.uidAuthor IS + 'The user id of the one who created/modified this entry. +Non-unique foreign key: Users(uid)'; + +COMMENT ON COLUMN TestCaseArgs.sArgs IS + 'The additional arguments. +String suitable for bourne shell style argument parsing with space as +separator (IFS). References to @BUILD_BINARIES@ will be replaced with +the content of the Builds(sBinaries) field.'; + +COMMENT ON COLUMN TestCaseArgs.cSecTimeout IS + 'Optional test case timeout given in seconds. +If NULL, the TestCases.cSecTimeout field is used instead.'; + +COMMENT ON COLUMN TestCaseArgs.sTestBoxReqExpr IS + 'Additional TestBox requirement expression (python boolean expression). +All the scheduler properties are available for use with the same names +as in that table. This is checked after first checking the requirements +in the TestCases.sTestBoxReqExpr field.'; + +COMMENT ON COLUMN TestCaseArgs.sBuildReqExpr IS + 'Additional build requirement expression (python boolean expression). +The following build properties are available: sProduct, sBranch, +sType, asOsArches, sVersion, iRevision, uidAuthor and idBuild. This is +checked after first checking the requirements in the +TestCases.sBuildReqExpr field.'; + +COMMENT ON COLUMN TestCaseArgs.cGangMembers IS + 'Number of testboxes required (gang scheduling).'; + +COMMENT ON COLUMN TestCaseArgs.sSubName IS + 'Optional variation sub-name.'; + +COMMENT ON INDEX TestCaseArgsLookupIdx IS + 'The arguments are part of the primary key for several reasons. +No duplicate argument lists (makes no sense - if you want to prioritize +argument lists, we add that explicitly). This may hopefully enable us +to more easily check coverage later on, even when the test case is +reconfigured with more/less permutations.'; + +COMMENT ON TABLE TestCaseDeps IS + 'Test case dependencies (N:M) + +This effect build selection. The build must have passed all runs of the +given prerequisite testcase (idTestCasePreReq) and executed at a minimum one +argument list variation. + +This should also affect scheduling order, if possible at least one +prerequisite testcase variation should be place before the specific testcase +in the scheduling queue. + +@remarks This table stores history. Never update or delete anything. The + equivalent of deleting is done by setting the ''tsExpire'' field to + current_timestamp. To select the currently valid entries use + tsExpire = TIMESTAMP WITH TIME ZONE ''infinity''.'; + +COMMENT ON COLUMN TestCaseDeps.tsEffective IS + 'When this row starts taking effect (inclusive).'; + +COMMENT ON COLUMN TestCaseDeps.tsExpire IS + 'When this row stops being tsEffective (exclusive).'; + +COMMENT ON COLUMN TestCaseDeps.uidAuthor IS + 'The user id of the one who created/modified this entry. +Non-unique foreign key: Users(uid)'; + +COMMENT ON TABLE TestCaseGlobalRsrcDeps IS + 'Test case dependencies on global resources (N:M) + +@remarks This table stores history. Never update or delete anything. The + equivalent of deleting is done by setting the ''tsExpire'' field to + current_timestamp. To select the currently valid entries use + tsExpire = TIMESTAMP WITH TIME ZONE ''infinity''.'; + +COMMENT ON COLUMN TestCaseGlobalRsrcDeps.tsEffective IS + 'When this row starts taking effect (inclusive).'; + +COMMENT ON COLUMN TestCaseGlobalRsrcDeps.tsExpire IS + 'When this row stops being tsEffective (exclusive).'; + +COMMENT ON COLUMN TestCaseGlobalRsrcDeps.uidAuthor IS + 'The user id of the one who created/modified this entry. +Non-unique foreign key: Users(uid)'; + +COMMENT ON TABLE TestGroups IS + 'Test Group - A collection of test cases. + +This is for simplifying test configuration by working with a few groups +instead of a herd of individual testcases. It may also be used for creating +test suites for certain areas (like guest additions) or tasks (like +performance measurements). + +A test case can be member of any number of test groups. + +@remarks This table stores history. Never update or delete anything. The + equivalent of deleting is done by setting the ''tsExpire'' field to + current_timestamp. To select the currently valid entries use + tsExpire = TIMESTAMP WITH TIME ZONE ''infinity''.'; + +COMMENT ON COLUMN TestGroups.tsEffective IS + 'When this row starts taking effect (inclusive).'; + +COMMENT ON COLUMN TestGroups.tsExpire IS + 'When this row stops being tsEffective (exclusive).'; + +COMMENT ON COLUMN TestGroups.uidAuthor IS + 'The user id of the one who created/modified this entry. +Non-unique foreign key: Users(uid)'; + +COMMENT ON COLUMN TestGroups.sName IS + 'The name of the scheduling group.'; + +COMMENT ON COLUMN TestGroups.sDescription IS + 'Optional group description.'; + +COMMENT ON TABLE TestGroupMembers IS + 'The N:M relationship between test case configurations and test groups. + +@remarks This table stores history. Never update or delete anything. The + equivalent of deleting is done by setting the ''tsExpire'' field to + current_timestamp. To select the currently valid entries use + tsExpire = TIMESTAMP WITH TIME ZONE ''infinity''.'; + +COMMENT ON COLUMN TestGroupMembers.tsEffective IS + 'When this row starts taking effect (inclusive).'; + +COMMENT ON COLUMN TestGroupMembers.tsExpire IS + 'When this row stops being tsEffective (exclusive).'; + +COMMENT ON COLUMN TestGroupMembers.uidAuthor IS + 'The user id of the one who created/modified this entry. +Non-unique foreign key: Users(uid)'; + +COMMENT ON COLUMN TestGroupMembers.iSchedPriority IS + 'Test case scheduling priority. +Higher number causes the test case to be run more frequently. +@sa SchedGroupMembers.iSchedPriority, TestBoxesInSchedGroups.iSchedPriority +@todo Not sure we want to keep this...'; + +COMMENT ON TABLE SchedGroups IS + 'Scheduling group (aka. testbox partitioning) configuration. + +A testbox is associated with exactly one scheduling group. This association +can be changed, of course. If we (want to) retire a group which still has +testboxes associated with it, these will be moved to the ''default'' group. + +The TM web UI will make sure that a testbox is always in a group and that +the default group cannot be deleted. + +A scheduling group combines several things: + - A selection of builds to test (via idBuildSrc). + - A collection of test groups to test with (via SchedGroupMembers). + - A set of testboxes to test on (via TestBoxes.idSchedGroup). + +In additions there is an optional source of fresh test suite builds (think +VBoxTestSuite) as well as scheduling options. + +@remarks This table stores history. Never update or delete anything. The + equivalent of deleting is done by setting the ''tsExpire'' field to + current_timestamp. To select the currently valid entries use + tsExpire = TIMESTAMP WITH TIME ZONE ''infinity''.'; + +COMMENT ON COLUMN SchedGroups.tsEffective IS + 'When this row starts taking effect (inclusive).'; + +COMMENT ON COLUMN SchedGroups.tsExpire IS + 'When this row stops being tsEffective (exclusive).'; + +COMMENT ON COLUMN SchedGroups.uidAuthor IS + 'The user id of the one who created/modified this entry. +Non-unique foreign key: Users(uid) +@note This is NULL for the default group.'; + +COMMENT ON COLUMN SchedGroups.sName IS + 'The name of the scheduling group.'; + +COMMENT ON COLUMN SchedGroups.sDescription IS + 'Optional group description.'; + +COMMENT ON COLUMN SchedGroups.fEnabled IS + 'Indicates whether this group is currently enabled.'; + +COMMENT ON COLUMN SchedGroups.enmScheduler IS + 'The scheduler to use. +This is for when we later desire different scheduling that the best +effort stuff provided by the initial implementation.'; + +COMMENT ON COLUMN SchedGroups.sComment IS + 'The Validation Kit build source (@VALIDATIONKIT_ZIP@). +Non-unique foreign key: BuildSources(idBuildSrc)'; + +COMMENT ON TABLE SchedGroupMembers IS + 'N:M relationship between scheduling groups and test groups. + +Several scheduling parameters are associated with this relationship. + +The test group dependency (idTestGroupPreReq) can be used in the same way as +TestCaseDeps.idTestCasePreReq, only here on test group level. This means it +affects the build selection. The builds needs to have passed all test runs +the prerequisite test group and done at least one argument variation of each +test case in it. + +@remarks This table stores history. Never update or delete anything. The + equivalent of deleting is done by setting the ''tsExpire'' field to + current_timestamp. To select the currently valid entries use + tsExpire = TIMESTAMP WITH TIME ZONE ''infinity''.'; + +COMMENT ON COLUMN SchedGroupMembers.tsEffective IS + 'When this row starts taking effect (inclusive).'; + +COMMENT ON COLUMN SchedGroupMembers.tsExpire IS + 'When this row stops being tsEffective (exclusive).'; + +COMMENT ON COLUMN SchedGroupMembers.uidAuthor IS + 'The user id of the one who created/modified this entry. +Non-unique foreign key: Users(uid)'; + +COMMENT ON COLUMN SchedGroupMembers.iSchedPriority IS + 'The scheduling priority of the test group. +Higher number causes the test case to be run more frequently. +@sa TestGroupMembers.iSchedPriority, TestBoxesInSchedGroups.iSchedPriority'; + +COMMENT ON COLUMN SchedGroupMembers.bmHourlySchedule IS + 'When during the week this group is allowed to start running, NULL means +there are no constraints. +Each bit in the bitstring represents one hour, with bit 0 indicating the +midnight hour on a monday.'; + +COMMENT ON TABLE TestBoxStrTab IS + 'String table for the test boxes. + +This is a string cache for all string members in TestBoxes except the name. +The rational is to avoid duplicating large strings like sReport when the +testbox reports a new cMbScratch value or the box when the test sheriff +sends a reboot command or similar. + +At the time this table was introduced, we had 400558 TestBoxes rows, where +the SUM(LENGTH(sReport)) was 993MB. There were really just 1066 distinct +sReport values, with a total length of 0x3 MB. + +Nothing is ever deleted from this table. + +@note Should use a stored procedure to query/insert a string. + + +TestBox stats prior to conversion: + SELECT COUNT(*) FROM TestBoxes: 400558 rows + SELECT pg_total_relation_size(''TestBoxes''): 740794368 bytes (706 MB) + Average row cost: 740794368 / 400558 = 1849 bytes/row + +After conversion: + SELECT COUNT(*) FROM TestBoxes: 400558 rows + SELECT pg_total_relation_size(''TestBoxes''): 144375808 bytes (138 MB) + SELECT COUNT(idStr) FROM TestBoxStrTab: 1292 rows + SELECT pg_total_relation_size(''TestBoxStrTab''): 5709824 bytes (5.5 MB) + (144375808 + 5709824) / 740794368 = 20 % + Average row cost boxes: 144375808 / 400558 = 360 bytes/row + Average row cost strings: 5709824 / 1292 = 4420 bytes/row'; + +COMMENT ON COLUMN TestBoxStrTab.sValue IS + 'The string value.'; + +COMMENT ON COLUMN TestBoxStrTab.tsCreated IS + 'Creation time stamp.'; + +COMMENT ON TYPE TestBoxCmd_T IS + 'Testbox commands.'; + +COMMENT ON TYPE LomKind_T IS + 'The kind of lights out management on a testbox.'; + +COMMENT ON TABLE TestBoxes IS + 'Testbox configurations. + +The testboxes are identified by IP and the system UUID if available. Should +the IP change, the testbox will be refused at sign on and the testbox +sheriff will have to update it''s IP. + +@todo Implement the UUID stuff. Get it from DMI, UEFI or whereever. + Mismatching needs to be logged somewhere... + +To query the currently valid configuration: + SELECT ... WHERE id = idTestBox AND tsExpire = TIMESTAMP WITH TIME ZONE ''infinity''; + +@remarks This table stores history. Never update or delete anything. The + equivalent of deleting is done by setting the ''tsExpire'' field to + current_timestamp. To select the currently valid entries use + tsExpire = TIMESTAMP WITH TIME ZONE ''infinity''.'; + +COMMENT ON COLUMN TestBoxes.tsEffective IS + 'When this row starts taking effect (inclusive).'; + +COMMENT ON COLUMN TestBoxes.tsExpire IS + 'When this row stops being tsEffective (exclusive).'; + +COMMENT ON COLUMN TestBoxes.uidAuthor IS + 'The user id of the one who created/modified this entry. +When modified automatically by the testbox, NULL is used. +Non-unique foreign key: Users(uid)'; + +COMMENT ON COLUMN TestBoxes.uuidSystem IS + 'The system or firmware UUID. +This uniquely identifies the testbox when talking to the server. After +SIGNON though, the testbox will also provide idTestBox and ip to +establish its identity beyond doubt.'; + +COMMENT ON COLUMN TestBoxes.sName IS + 'The testbox name. +Usually similar to the DNS name.'; + +COMMENT ON COLUMN TestBoxes.fEnabled IS + 'Indicates whether this testbox is enabled. +A testbox gets disabled when we''re doing maintenance, debugging a issue +that happens only on that testbox, or some similar stuff. This is an +alternative to deleting the testbox.'; + +COMMENT ON COLUMN TestBoxes.enmLomKind IS + 'The kind of lights-out-management.'; + +COMMENT ON COLUMN TestBoxes.lCpuRevision IS + 'Number identifying the CPU family/model/stepping/whatever. +For x86 and AMD64 type CPUs, this will on the following format: + (EffFamily << 24) | (EffModel << 8) | Stepping.'; + +COMMENT ON COLUMN TestBoxes.cCpus IS + 'Number of CPUs, CPU cores and CPU threads.'; + +COMMENT ON COLUMN TestBoxes.fCpuHwVirt IS + 'Set if capable of hardware virtualization.'; + +COMMENT ON COLUMN TestBoxes.fCpuNestedPaging IS + 'Set if capable of nested paging.'; + +COMMENT ON COLUMN TestBoxes.fCpu64BitGuest IS + 'Set if CPU capable of 64-bit (VBox) guests.'; + +COMMENT ON COLUMN TestBoxes.fChipsetIoMmu IS + 'Set if chipset with usable IOMMU (VT-d / AMD-Vi).'; + +COMMENT ON COLUMN TestBoxes.fRawMode IS + 'Set if the test box does raw-mode tests.'; + +COMMENT ON COLUMN TestBoxes.cMbMemory IS + 'The (approximate) memory size in megabytes (rounded down to nearest 4 MB).'; + +COMMENT ON COLUMN TestBoxes.cMbScratch IS + 'The amount of scratch space in megabytes (rounded down to nearest 64 MB).'; + +COMMENT ON COLUMN TestBoxes.iTestBoxScriptRev IS + 'The testbox script revision number, serves the purpose of a version number. +Probably good to have when scheduling upgrades as well for status purposes.'; + +COMMENT ON COLUMN TestBoxes.iPythonHexVersion IS + 'The python sys.hexversion (layed out as of 2.7). +Good to know which python versions we need to support.'; + +COMMENT ON COLUMN TestBoxes.enmPendingCmd IS + 'Pending command. +@note We put it here instead of in TestBoxStatuses to get history.'; + +COMMENT ON INDEX TestBoxesUuidIdx IS + 'Nested paging requires hardware virtualization.'; + +COMMENT ON TABLE TestBoxesInSchedGroups IS + 'N:M relationship between test boxes and scheduling groups. + +We associate a priority with this relationship. + +@remarks This table stores history. Never update or delete anything. The + equivalent of deleting is done by setting the ''tsExpire'' field to + current_timestamp. To select the currently valid entries use + tsExpire = TIMESTAMP WITH TIME ZONE ''infinity''.'; + +COMMENT ON COLUMN TestBoxesInSchedGroups.tsEffective IS + 'When this row starts taking effect (inclusive).'; + +COMMENT ON COLUMN TestBoxesInSchedGroups.tsExpire IS + 'When this row stops being tsEffective (exclusive).'; + +COMMENT ON COLUMN TestBoxesInSchedGroups.uidAuthor IS + 'The user id of the one who created/modified this entry. +Non-unique foreign key: Users(uid)'; + +COMMENT ON COLUMN TestBoxesInSchedGroups.iSchedPriority IS + 'The scheduling priority of the scheduling group for the test box. +Higher number causes the scheduling group to be serviced more frequently. +@sa TestGroupMembers.iSchedPriority, SchedGroups.iSchedPriority'; + +COMMENT ON TABLE FailureCategories IS + 'Failure categories. + +This is for organizing the failure reasons. + +@remarks This table stores history. Never update or delete anything. The + equivalent of deleting is done by setting the ''tsExpire'' field to + current_timestamp. To select the currently valid entries use + tsExpire = TIMESTAMP WITH TIME ZONE ''infinity''.'; + +COMMENT ON COLUMN FailureCategories.tsEffective IS + 'When this row starts taking effect (inclusive).'; + +COMMENT ON COLUMN FailureCategories.tsExpire IS + 'When this row stops being tsEffective (exclusive).'; + +COMMENT ON COLUMN FailureCategories.uidAuthor IS + 'The user id of the one who created/modified this entry. +Non-unique foreign key: Users(uid)'; + +COMMENT ON COLUMN FailureCategories.sShort IS + 'The short category description. +For combo boxes and other selection lists.'; + +COMMENT ON COLUMN FailureCategories.sFull IS + 'Full description +For cursor-over-poppups for instance.'; + +COMMENT ON TABLE FailureReasons IS + 'Failure reasons. + +When analysing a test failure, the testbox sheriff will try assign a fitting +reason for the failure. This table is here to help the sheriff in his/hers +job as well as developers looking checking if their changes affected the +test results in any way. + +@remarks This table stores history. Never update or delete anything. The + equivalent of deleting is done by setting the ''tsExpire'' field to + current_timestamp. To select the currently valid entries use + tsExpire = TIMESTAMP WITH TIME ZONE ''infinity''.'; + +COMMENT ON COLUMN FailureReasons.tsEffective IS + 'When this row starts taking effect (inclusive).'; + +COMMENT ON COLUMN FailureReasons.tsExpire IS + 'When this row stops being tsEffective (exclusive).'; + +COMMENT ON COLUMN FailureReasons.uidAuthor IS + 'The user id of the one who created/modified this entry. +Non-unique foreign key: Users(uid)'; + +COMMENT ON COLUMN FailureReasons.sShort IS + 'The short failure description. +For combo boxes and other selection lists.'; + +COMMENT ON COLUMN FailureReasons.sFull IS + 'Full failure description.'; + +COMMENT ON COLUMN FailureReasons.iTicket IS + 'Ticket number in the primary bugtracker.'; + +COMMENT ON COLUMN FailureReasons.asUrls IS + 'Other URLs to reports or discussions of the observed symptoms.'; + +COMMENT ON TABLE TestResultFailures IS + 'This is for tracking/discussing test result failures. + +The rational for putting this is a separate table is that we need history on +this while TestResults does not. + +@remarks This table stores history. Never update or delete anything. The + equivalent of deleting is done by setting the ''tsExpire'' field to + current_timestamp. To select the currently valid entries use + tsExpire = TIMESTAMP WITH TIME ZONE ''infinity''.'; + +COMMENT ON COLUMN TestResultFailures.tsEffective IS + 'When this row starts taking effect (inclusive).'; + +COMMENT ON COLUMN TestResultFailures.tsExpire IS + 'When this row stops being tsEffective (exclusive).'; + +COMMENT ON COLUMN TestResultFailures.uidAuthor IS + 'The user id of the one who created/modified this entry. +Non-unique foreign key: Users(uid)'; + +COMMENT ON COLUMN TestResultFailures.sComment IS + 'Optional comment.'; + +COMMENT ON TABLE BuildBlacklist IS + 'Table used to blacklist sets of builds. + +The best usage example is a VMM developer realizing that a change causes the +host to panic, hang, or otherwise misbehave. To prevent the testbox sheriff +from repeatedly having to reboot testboxes, the builds gets blacklisted +until there is a working build again. This may mean adding an open ended +blacklist spec and then updating it with the final revision number once the +fix has been committed. + +@remarks This table stores history. Never update or delete anything. The + equivalent of deleting is done by setting the ''tsExpire'' field to + current_timestamp. To select the currently valid entries use + tsExpire = TIMESTAMP WITH TIME ZONE ''infinity''. + +@todo Would be nice if we could replace the text strings below with a set of + BuildCategories, or sore it in any other way which would enable us to + do a negative join with build category... The way it is specified + now, it looks like we have to open a cursor of prospecitve builds and + filter then thru this table one by one. + + Any better representation is welcome, but this is low prioirty for + now, as it''s relatively easy to change this later one.'; + +COMMENT ON COLUMN BuildBlacklist.tsEffective IS + 'When this row starts taking effect (inclusive).'; + +COMMENT ON COLUMN BuildBlacklist.tsExpire IS + 'When this row stops being tsEffective (exclusive).'; + +COMMENT ON COLUMN BuildBlacklist.uidAuthor IS + 'The user id of the one who created/modified this entry. +Non-unique foreign key: Users(uid)'; + +COMMENT ON COLUMN BuildBlacklist.sProduct IS + 'Which product. +ASSUME that it is okay to limit a blacklisting to a single product.'; + +COMMENT ON COLUMN BuildBlacklist.sBranch IS + 'Which branch. +ASSUME that it is okay to limit a blacklisting to a branch.'; + +COMMENT ON COLUMN BuildBlacklist.asTypes IS + 'Build types to include, all matches if NULL.'; + +COMMENT ON COLUMN BuildBlacklist.asOsArches IS + 'Array of the ''sOs.sCpuArch'' to match, all matches if NULL. +See KBUILD_OSES in kBuild for a list of standard target OSes, and +KBUILD_ARCHES for a list of standard architectures. + +@remarks See marks on ''os-agnostic'' and ''noarch'' in BuildCategories.'; + +COMMENT ON COLUMN BuildBlacklist.iFirstRevision IS + 'The first subversion tree revision to blacklist.'; + +COMMENT ON COLUMN BuildBlacklist.iLastRevision IS + 'The last subversion tree revision to blacklist, no upper limit if NULL.'; + +COMMENT ON TABLE BuildCategories IS + 'Build categories. + +The purpose of this table is saving space in the Builds table and hopefully +speed things up when selecting builds as well (compared to selecting on 4 +text fields in the much larger Builds table). + +Insert only table, no update, no delete. History is not needed.'; + +COMMENT ON COLUMN BuildCategories.sProduct IS + 'Product. +The product name. For instance ''VBox'' or ''VBoxTestSuite''.'; + +COMMENT ON COLUMN BuildCategories.sRepository IS + 'The version control repository name.'; + +COMMENT ON COLUMN BuildCategories.sBranch IS + 'The branch name (in the version control system).'; + +COMMENT ON COLUMN BuildCategories.sType IS + 'The build type. +See KBUILD_BLD_TYPES in kBuild for a list of standard build types.'; + +COMMENT ON COLUMN BuildCategories.asOsArches IS + 'Array of the ''sOs.sCpuArch'' supported by the build. +See KBUILD_OSES in kBuild for a list of standard target OSes, and +KBUILD_ARCHES for a list of standard architectures. + +@remarks ''os-agnostic'' is used if the build doesn''t really target any + specific OS or if it targets all applicable OSes. + ''noarch'' is used if the build is architecture independent or if + all applicable architectures are handled. + Thus, ''os-agnostic.noarch'' will run on all build boxes. + +@note The array shall be sorted ascendingly to prevent unnecessary duplicates!'; + +COMMENT ON TABLE Builds IS + 'The builds table contains builds from the tinderboxes and oaccasionally from +developers. + +The tinderbox side could be fed by a batch job enumerating the build output +directories every so often, looking for new builds. Or we could query them +from the tinderbox database. Yet another alternative is making the +tinderbox server or client side software inform us about all new builds. + +The developer builds are entered manually thru the TM web UI. They are used +for subjecting new code to some larger scale testing before commiting, +enabling, or merging a private branch. + +The builds are being selected from this table by the via the build source +specification that SchedGroups.idBuildSrc and +SchedGroups.idBuildSrcTestSuite links to. + +@remarks This table stores history. Never update or delete anything. The + equivalent of deleting is done by setting the ''tsExpire'' field to + current_timestamp. To select the currently valid entries use + tsExpire = TIMESTAMP WITH TIME ZONE ''infinity''.'; + +COMMENT ON COLUMN Builds.tsCreated IS + 'When this build was created or entered into the database. +This remains unchanged'; + +COMMENT ON COLUMN Builds.tsEffective IS + 'When this row starts taking effect (inclusive).'; + +COMMENT ON COLUMN Builds.tsExpire IS + 'When this row stops being tsEffective (exclusive).'; + +COMMENT ON COLUMN Builds.uidAuthor IS + 'The user id of the one who created/modified this entry. +Non-unique foreign key: Users(uid) +@note This is NULL if added by a batch job / tinderbox.'; + +COMMENT ON COLUMN Builds.iRevision IS + 'The subversion tree revision of the build.'; + +COMMENT ON COLUMN Builds.sVersion IS + 'The product version number (suitable for RTStrVersionCompare).'; + +COMMENT ON COLUMN Builds.sLogUrl IS + 'The link to the tinderbox log of this build.'; + +COMMENT ON COLUMN Builds.sBinaries IS + 'Comma separated list of binaries. +The binaries have paths relative to the TESTBOX_PATH_BUILDS or full URLs.'; + +COMMENT ON COLUMN Builds.fBinariesDeleted IS + 'Set when the binaries gets deleted by the build quota script.'; + +COMMENT ON TABLE VcsRevisions IS + 'This table is for translating build revisions into commit details. + +For graphs and test results, it would be useful to translate revisions into +dates and maybe provide commit message and the committer. + +Data is entered exclusively thru one or more batch jobs, so no internal +authorship needed. Also, since we''re mirroring data from external sources +here, the batch job is allowed to update/replace existing records. + +@todo We we could collect more info from the version control systems, if we + believe it''s useful and can be presented in a reasonable manner. + Getting a list of affected files would be simple (requires + a separate table with a M:1 relationship to this table), or try + associate a commit to a branch.'; + +COMMENT ON COLUMN VcsRevisions.sRepository IS + 'The version control tree name.'; + +COMMENT ON COLUMN VcsRevisions.iRevision IS + 'The version control tree revision number.'; + +COMMENT ON COLUMN VcsRevisions.tsCreated IS + 'When the revision was created (committed).'; + +COMMENT ON COLUMN VcsRevisions.sAuthor IS + 'The name of the committer. +@note Not to be confused with uidAuthor and test manager users.'; + +COMMENT ON COLUMN VcsRevisions.sMessage IS + 'The commit message.'; + +COMMENT ON TABLE TestResultStrTab IS + 'String table for the test results. + +This is a string cache for value names, test names and possible more, that +is frequently repated in the test results record for each test run. The +purpose is not only to save space, but to make datamining queries faster by +giving them integer fields to work on instead of text fields. There may +possibly be some benefits on INSERT as well as there are only integer +indexes. + +Nothing is ever deleted from this table. + +@note Should use a stored procedure to query/insert a string.'; + +COMMENT ON COLUMN TestResultStrTab.sValue IS + 'The string value.'; + +COMMENT ON COLUMN TestResultStrTab.tsCreated IS + 'Creation time stamp.'; + +COMMENT ON TYPE TestStatus_T IS + 'The status of a test (set / result).'; + +COMMENT ON TABLE TestResults IS + 'Test results - a recursive bundle of joy! + +A test case will be created when the testdriver calls reporter.testStart and +concluded with reporter.testDone. The testdriver (or it subordinates) can +use these methods to create nested test results. For IPRT based test cases, +RTTestCreate, RTTestInitAndCreate and RTTestSub will both create new test +result records, where as RTTestSubDone, RTTestSummaryAndDestroy and +RTTestDestroy will conclude records. + +By concluding is meant updating the status. When the test driver reports +success, we check it against reported results. (paranoia strikes again!) + +Nothing is ever deleted from this table. + +@note As seen below, several other tables associate data with a + test result, and the top most test result is referenced by the + test set.'; + +COMMENT ON COLUMN TestResults.tsCreated IS + 'Creation time stamp. This may also be the timestamp of when the test started.'; + +COMMENT ON COLUMN TestResults.tsElapsed IS + 'The elapsed time for this test. +This is either reported by the directly (with some sanity checking) or +calculated (current_timestamp - created_ts). +@todo maybe use a nanosecond field here, check with what'; + +COMMENT ON COLUMN TestResults.cErrors IS + 'The error count.'; + +COMMENT ON COLUMN TestResults.enmStatus IS + 'The test status.'; + +COMMENT ON COLUMN TestResults.iNestingDepth IS + 'Nesting depth.'; + +COMMENT ON TABLE TestResultValues IS + 'Test result values. + +A testdriver or subordinate may report a test value via +reporter.testValue(), while IPRT based test will use RTTestValue and +associates. + +This is an insert only table, no deletes, no updates.'; + +COMMENT ON COLUMN TestResultValues.tsCreated IS + 'Creation time stamp.'; + +COMMENT ON COLUMN TestResultValues.lValue IS + 'The value.'; + +COMMENT ON COLUMN TestResultValues.iUnit IS + 'The unit. +@todo This is currently not defined properly. Will fix/correlate this + with the other places we use unit (IPRT/testdriver/VMMDev).'; + +COMMENT ON TABLE TestResultFiles IS + 'Test result files. + +A testdriver or subordinate may report a file by using +reporter.addFile() or reporter.addLogFile(). + +The files stored here as well as the primary log file will be processed by a +batch job and compressed if considered compressable. Thus, TM will look for +files with a .gz/.bz2 suffix first and then without a suffix. + +This is an insert only table, no deletes, no updates.'; + +COMMENT ON COLUMN TestResultFiles.tsCreated IS + 'Creation time stamp.'; + +COMMENT ON INDEX TestResultFilesIdx IS + 'The mime type for the file. +For instance: ''text/plain'', + ''image/png'', + ''video/webm'', + ''text/xml'''; + +COMMENT ON TABLE TestResultMsgs IS + 'Test result message. + +A testdriver or subordinate may report a message via the sDetails parameter +of the reporter.testFailure() method, while IPRT test cases will use +RTTestFailed, RTTestPrintf and their friends. For RTTestPrintf, we will +ignore the more verbose message levels since these can also be found in one +of the logs. + +This is an insert only table, no deletes, no updates.'; + +COMMENT ON COLUMN TestResultMsgs.tsCreated IS + 'Creation time stamp.'; + +COMMENT ON COLUMN TestResultMsgs.enmLevel IS + 'The message level.'; + +COMMENT ON TABLE TestSets IS + 'Test sets / Test case runs. + +This is where we collect data about test runs. + +@todo Not entirely sure where the ''test set'' term came from. Consider + finding something more appropriate.'; + +COMMENT ON COLUMN TestSets.tsConfig IS + 'The test config timestamp, used when reading test config.'; + +COMMENT ON COLUMN TestSets.tsCreated IS + 'When this test set was scheduled. +idGenTestBox is valid at this point.'; + +COMMENT ON COLUMN TestSets.tsDone IS + 'When this test completed, i.e. testing stopped. This should only be set once.'; + +COMMENT ON COLUMN TestSets.enmStatus IS + 'The current status.'; + +COMMENT ON COLUMN TestSets.sBaseFilename IS + 'The base filename used for storing files related to this test set. +This is a path relative to wherever TM is dumping log files. In order +to not become a file system test case, we will try not to put too many +hundred thousand files in a directory. A simple first approach would +be to just use the current date (tsCreated) like this: + TM_FILE_DIR/year/month/day/TestSets.idTestSet + +The primary log file for the test is this name suffixed by ''.log''. + +The files in the testresultfile table gets their full names like this: + TM_FILE_DIR/sBaseFilename-testresultfile.id-TestResultStrTab(testresultfile.idStrFilename) + +@remarks We store this explicitly in case we change the directly layout + at some later point.'; + +COMMENT ON COLUMN TestSets.iGangMemberNo IS + 'The gang member number number, 0 is the leader.'; + +COMMENT ON INDEX TestSetsGangIdx IS + 'The test set of the gang leader, NULL if no gang involved. +@note This is set by the gang leader as well, so that we can find all + gang members by WHERE idTestSetGangLeader = :id.'; + +COMMENT ON INDEX TestSetsDoneCreatedBuildCatIdx IS + 'The TestSetsDoneCreatedBuildCatIdx is for testbox results, graph options and such.'; + +COMMENT ON INDEX TestSetsGraphBoxIdx IS + 'For graphs.'; + +COMMENT ON TYPE TestBoxState_T IS + 'TestBox state. + +@todo Consider drawing a state diagram for this.'; + +COMMENT ON TABLE TestBoxStatuses IS + 'Testbox status table. + +History is not planned on this table.'; + +COMMENT ON COLUMN TestBoxStatuses.tsUpdated IS + 'When this status was last updated. +This is updated everytime the testbox talks to the test manager, thus it +can easily be used to find testboxes which has stopped responding. + +This is used for timeout calculation during gang-gathering, so in that +scenario it won''t be updated until the gang is gathered or we time out.'; + +COMMENT ON COLUMN TestBoxStatuses.enmState IS + 'The current state.'; + +COMMENT ON COLUMN TestBoxStatuses.iWorkItem IS + 'Interal work item number. +This is used to pick and prioritize between multiple scheduling groups.'; + +COMMENT ON TABLE GlobalResourceStatuses IS + 'Global resource status, tracks which test set resources are allocated by. + +History is not planned on this table.'; + +COMMENT ON COLUMN GlobalResourceStatuses.tsAllocated IS + 'When the allocation took place.'; + +COMMENT ON TABLE SchedQueues IS + 'Scheduler queue. + +The queues are currently associated with a scheduling group, it could +alternative be changed to hook on to a testbox instead. It depends on what +kind of scheduling method we prefer. The former method aims at test case +thruput, making sacrifices in the hardware distribution area. The latter is +more like the old buildbox style testing, making sure that each test case is +executed on each testbox. + +When there are configuration changes, TM will regenerate the scheduling +queue for the affected scheduling groups. We do not concern ourselves with +trying to continue at the approximately same queue position, we simply take +it from the top. + +When a testbox ask for work, we will open a cursor on the queue and take the +first test in the queue that can be executed on that testbox. The test will +be moved to the end of the queue (getting a new item_id). + +If a test is manually changed to the head of the queue, the item will get a +item_id which is 1 lower than the head of the queue. Unless someone does +this a couple of billion times, we shouldn''t have any trouble running out of +number space. :-) + +Manually moving a test to the end of the queue is easy, just get a new +''item_id''. + +History is not planned on this table.'; + +COMMENT ON COLUMN SchedQueues.bmHourlySchedule IS + 'The scheduling time constraints (see SchedGroupMembers.bmHourlySchedule).'; + +COMMENT ON COLUMN SchedQueues.tsConfig IS + 'When the queue entry was created and for which config is valid. +This is the timestamp that should be used when reading config info.'; + +COMMENT ON COLUMN SchedQueues.tsLastScheduled IS + 'When this status was last scheduled. +This is set to current_timestamp when moving the entry to the end of the +queue. It''s initial value is unix-epoch. Not entirely sure if it''s +useful beyond introspection and non-unique foreign key hacking.'; + +COMMENT ON COLUMN SchedQueues.cMissingGangMembers IS + 'The number of gang members still missing. + +This saves calculating the number of missing members via selects like: + SELECT COUNT(*) FROM TestSets WHERE idTestSetGangLeader = :idGang; +and + SELECT cGangMembers FROM TestCaseArgs WHERE idGenTestCaseArgs = :idTest; +to figure out whether to remain in ''gather-gang''::TestBoxState_T.'; + +COMMENT ON INDEX SchedQueuesItemIdx IS + 'The number of times this has been considered for scheduling. +cConsidered SMALLINT DEFAULT 0 NOT NULL,'; + diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseDefaultUserAccounts.pgsql b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseDefaultUserAccounts.pgsql new file mode 100644 index 00000000..992cab06 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseDefaultUserAccounts.pgsql @@ -0,0 +1,43 @@ +-- $Id: TestManagerDatabaseDefaultUserAccounts.pgsql $ +--- @file +-- VBox Test Manager default user account records creation script. +-- + +-- +-- Copyright (C) 2012-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + +\set ON_ERROR_STOP 1 +\connect testmanager; + +-- Add record for user 'admin' +INSERT INTO Users (sUsername, sEmail, sFullName, sLoginName) + VALUES ('root', 'admin@example.org', 'Administrator', 'admin'); + diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseForeignKeyErHacks.pgsql b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseForeignKeyErHacks.pgsql new file mode 100644 index 00000000..5c567147 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseForeignKeyErHacks.pgsql @@ -0,0 +1,90 @@ +-- $Id: TestManagerDatabaseForeignKeyErHacks.pgsql $ +--- @file +-- VBox Test Manager Database Addendum that adds non-unique foreign keys. +-- +-- This is for getting better visualization in reverse engeering ER tools, +-- it is not for production databases. +-- + +-- +-- Copyright (C) 2012-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + +\set ON_ERROR_STOP 1 +\connect testmanager + +ALTER TABLE TestCaseArgs + ADD CONSTRAINT non_unique_fk1 FOREIGN KEY (idTestCase, tsExpire) REFERENCES TestCases(idTestCase, tsExpire) MATCH FULL; + +ALTER TABLE TestcaseDeps + ADD CONSTRAINT non_unique_fk1 FOREIGN KEY (idTestCase, tsExpire) REFERENCES TestCases(idTestCase, tsExpire) MATCH FULL; +ALTER TABLE TestcaseDeps + ADD CONSTRAINT non_unique_fk2 FOREIGN KEY (idTestCasePreReq,tsExpire) REFERENCES TestCases(idTestCase, tsExpire) MATCH FULL; + +ALTER TABLE TestCaseGlobalRsrcDeps + ADD CONSTRAINT non_unique_fk1 FOREIGN KEY (idTestCase, tsExpire) REFERENCES TestCases(idTestCase, tsExpire) MATCH FULL; +ALTER TABLE TestCaseGlobalRsrcDeps + ADD CONSTRAINT non_unique_fk2 FOREIGN KEY (idGlobalRsrc, tsExpire) REFERENCES GlobalResources(idGlobalRsrc, tsExpire) MATCH FULL; + +ALTER TABLE TestGroupMembers + ADD CONSTRAINT non_unique_fk1 FOREIGN KEY (idTestGroup, tsExpire) REFERENCES TestGroups(idTestGroup, tsExpire) MATCH FULL; +ALTER TABLE TestGroupMembers + ADD CONSTRAINT non_unique_fk2 FOREIGN KEY (idTestCase, tsExpire) REFERENCES TestCases(idTestCase, tsExpire) MATCH FULL; + +ALTER TABLE SchedGroups + ADD CONSTRAINT non_unique_fk1 FOREIGN KEY (idBuildSrc, tsExpire) REFERENCES BuildSources(idBuildSrc, tsExpire) MATCH SIMPLE; +ALTER TABLE SchedGroups + ADD CONSTRAINT non_unique_fk2 FOREIGN KEY (idBuildSrcTestSuite, tsExpire) REFERENCES BuildSources(idBuildSrc, tsExpire) MATCH SIMPLE; + +ALTER TABLE SchedGroupMembers + ADD CONSTRAINT non_unique_fk1 FOREIGN KEY (idSchedGroup, tsExpire) REFERENCES SchedGroups(idSchedGroup, tsExpire) MATCH FULL; +ALTER TABLE SchedGroupMembers + ADD CONSTRAINT non_unique_fk2 FOREIGN KEY (idTestGroup, tsExpire) REFERENCES TestGroups(idTestGroup, tsExpire) MATCH FULL; +ALTER TABLE SchedGroupMembers + ADD CONSTRAINT non_unique_fk3 FOREIGN KEY (idTestGroupPreReq, tsExpire) REFERENCES TestGroups(idTestGroup, tsExpire) MATCH FULL; + +ALTER TABLE TestBoxes + ADD CONSTRAINT non_unique_fk1 FOREIGN KEY (idSchedGroup, tsExpire) REFERENCES SchedGroups(idSchedGroup, tsExpire) MATCH FULL; + +ALTER TABLE FailureReasons + ADD CONSTRAINT non_unique_fk1 FOREIGN KEY (idFailureCategory, tsExpire) REFERENCES FailureCategories(idFailureCategory, tsExpire) MATCH FULL; + +ALTER TABLE TestResultFailures + ADD CONSTRAINT non_unique_fk1 FOREIGN KEY (idFailureReason, tsExpire) REFERENCES FailureReasons(idFailureReason, tsExpire) MATCH FULL; + +ALTER TABLE BuildBlacklist + ADD CONSTRAINT non_unique_fk1 FOREIGN KEY (idFailureReason, tsExpire) REFERENCES FailureReasons(idFailureReason, tsExpire) MATCH FULL; + +ALTER TABLE GlobalResourceStatuses + ADD CONSTRAINT non_unique_fk1 FOREIGN KEY (idGlobalRsrc, tsAllocated) REFERENCES GlobalResources(idGlobalRsrc, tsExpire) MATCH FULL; + +ALTER TABLE SchedQueues + ADD CONSTRAINT non_unique_fk1 FOREIGN KEY (idSchedGroup, tsLastScheduled) REFERENCES SchedGroups(idSchedGroup, tsExpire) MATCH FULL; + diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseForeignKeyErHacks2.pgsql b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseForeignKeyErHacks2.pgsql new file mode 100644 index 00000000..f81a941c --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseForeignKeyErHacks2.pgsql @@ -0,0 +1,77 @@ +-- $Id: TestManagerDatabaseForeignKeyErHacks2.pgsql $ +--- @file +-- VBox Test Manager Database Addendum that adds non-unique foreign keys to Users. +-- +-- This is for getting better visualization in reverse engeering ER tools, +-- it is not for production databases. +-- + +-- +-- Copyright (C) 2012-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + +\set ON_ERROR_STOP 1 +\connect testmanager + +ALTER TABLE GlobalResources + ADD CONSTRAINT non_unique_fk9 FOREIGN KEY (uidAuthor, tsExpire) REFERENCES Users(uid, tsExpire) MATCH FULL; +ALTER TABLE BuildSources + ADD CONSTRAINT non_unique_fk9 FOREIGN KEY (uidAuthor, tsExpire) REFERENCES Users(uid, tsExpire) MATCH FULL; +ALTER TABLE RequirementSets + ADD CONSTRAINT non_unique_fk9 FOREIGN KEY (uidAuthor, tsCreated) REFERENCES Users(uid, tsExpire) MATCH FULL; +ALTER TABLE TestCases + ADD CONSTRAINT non_unique_fk9 FOREIGN KEY (uidAuthor, tsExpire) REFERENCES Users(uid, tsExpire) MATCH FULL; +ALTER TABLE TestCaseArgs + ADD CONSTRAINT non_unique_fk9 FOREIGN KEY (uidAuthor, tsExpire) REFERENCES Users(uid, tsExpire) MATCH FULL; +ALTER TABLE TestcaseDeps + ADD CONSTRAINT non_unique_fk9 FOREIGN KEY (uidAuthor, tsExpire) REFERENCES Users(uid, tsExpire) MATCH FULL; +ALTER TABLE TestCaseGlobalRsrcDeps + ADD CONSTRAINT non_unique_fk9 FOREIGN KEY (uidAuthor, tsExpire) REFERENCES Users(uid, tsExpire) MATCH FULL; +ALTER TABLE TestGroups + ADD CONSTRAINT non_unique_fk9 FOREIGN KEY (uidAuthor, tsExpire) REFERENCES Users(uid, tsExpire) MATCH FULL; +ALTER TABLE TestGroupMembers + ADD CONSTRAINT non_unique_fk9 FOREIGN KEY (uidAuthor, tsExpire) REFERENCES Users(uid, tsExpire) MATCH FULL; +ALTER TABLE SchedGroups + ADD CONSTRAINT non_unique_fk9 FOREIGN KEY (uidAuthor, tsExpire) REFERENCES Users(uid, tsExpire) MATCH SIMPLE; +ALTER TABLE SchedGroupMembers + ADD CONSTRAINT non_unique_fk9 FOREIGN KEY (uidAuthor, tsExpire) REFERENCES Users(uid, tsExpire) MATCH FULL; +ALTER TABLE TestBoxes + ADD CONSTRAINT non_unique_fk9 FOREIGN KEY (uidAuthor, tsExpire) REFERENCES Users(uid, tsExpire) MATCH FULL; +ALTER TABLE FailureCategories + ADD CONSTRAINT non_unique_fk9 FOREIGN KEY (uidAuthor, tsExpire) REFERENCES Users(uid, tsExpire) MATCH FULL; +ALTER TABLE FailureReasons + ADD CONSTRAINT non_unique_fk9 FOREIGN KEY (uidAuthor, tsExpire) REFERENCES Users(uid, tsExpire) MATCH FULL; +ALTER TABLE TestResultFailures + ADD CONSTRAINT non_unique_fk9 FOREIGN KEY (uidAuthor, tsExpire) REFERENCES Users(uid, tsExpire) MATCH FULL; +ALTER TABLE BuildBlacklist + ADD CONSTRAINT non_unique_fk9 FOREIGN KEY (uidAuthor, tsExpire) REFERENCES Users(uid, tsExpire) MATCH FULL; +ALTER TABLE Builds + ADD CONSTRAINT non_unique_fk9 FOREIGN KEY (uidAuthor, tsCreated) REFERENCES Users(uid, tsExpire) MATCH FULL; + diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseInit.pgsql b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseInit.pgsql new file mode 100644 index 00000000..0c245aa4 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseInit.pgsql @@ -0,0 +1,1950 @@ +-- $Id: TestManagerDatabaseInit.pgsql $ +--- @file +-- VBox Test Manager Database Creation script. +-- + +-- +-- Copyright (C) 2012-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + +-- +-- Declaimer: +-- +-- The guys working on this design are not database experts, web +-- programming experts or similar, rather we are low level guys +-- who's main job is x86 & AMD64 virtualization. So, please don't +-- be too hard on us. :-) +-- +-- + + +-- D R O P D A T A B A S E t e s t m a n a g e r - - you do this now. +\set ON_ERROR_STOP 1 +CREATE DATABASE testmanager; +\connect testmanager; + + +-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +-- +-- S y s t e m +-- +-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + +--- +-- Log table for a few important events. +-- +-- Currently, two events are planned to be logged: +-- - Sign on of an unknown testbox, including the IP and System UUID. +-- This will be restricted to one entry per 24h or something like that: +-- SELECT COUNT(*) +-- FROM SystemLog +-- WHERE tsCreated >= (current_timestamp - interval '24 hours') +-- AND sEvent = 'TBoxUnkn' +-- AND sLogText = :sNewLogText; +-- - When cleaning up an abandoned testcase (scenario #9), log which +-- testbox abandoned which testset. +-- +-- The Web UI will have some way of displaying the log. +-- +-- A batch job should regularly clean out old log messages, like for instance +-- > 64 days. +-- +CREATE TABLE SystemLog ( + --- When this was logged. + tsCreated TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- The event type. + -- This is a 8 character string identifier so that we don't need to change + -- some enum type everytime we introduce a new event type. + sEvent CHAR(8) NOT NULL, + --- The log text. + sLogText text NOT NULL, + + PRIMARY KEY (tsCreated, sEvent) +); + + +-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +-- +-- C o n f i g u r a t i o n +-- +-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + +--- @table Users +-- Test manager users. +-- +-- This is mainly for doing simple access checks before permitting access to +-- the test manager. This needs to be coordinated with +-- apache/ldap/Oracle-Single-Sign-On. +-- +-- The main purpose, though, is for tracing who changed the test config and +-- analysis data. +-- +-- @remarks This table stores history. Never update or delete anything. The +-- equivalent of deleting is done by setting the 'tsExpire' field to +-- current_timestamp. +-- +CREATE SEQUENCE UserIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE Users ( + --- The user id. + uid INTEGER DEFAULT NEXTVAL('UserIdSeq') NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER DEFAULT NULL, + --- User name. + sUsername text NOT NULL, + --- The email address of the user. + sEmail text NOT NULL, + --- The full name. + sFullName text NOT NULL, + --- The login name used by apache. + sLoginName text NOT NULL, + --- Read access only. + fReadOnly BOOLEAN NOT NULL DEFAULT FALSE, + + PRIMARY KEY (uid, tsExpire) +); +CREATE INDEX UsersLoginNameIdx ON Users (sLoginName, tsExpire DESC); + + +--- @table GlobalResources +-- Global resource configuration. +-- +-- For example an iSCSI target. +-- +-- @remarks This table stores history. Never update or delete anything. The +-- equivalent of deleting is done by setting the 'tsExpire' field to +-- current_timestamp. +-- +CREATE SEQUENCE GlobalResourceIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE GlobalResources ( + --- The global resource ID. + -- This stays the same thru updates. + idGlobalRsrc INTEGER DEFAULT NEXTVAL('GlobalResourceIdSeq') NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER NOT NULL, + --- The name of the resource. + sName text NOT NULL, + --- Optional resource description. + sDescription text, + --- Indicates whether this resource is currently enabled (online). + fEnabled boolean DEFAULT FALSE NOT NULL, + + PRIMARY KEY (idGlobalRsrc, tsExpire) +); + + +--- @table BuildSources +-- Build sources. +-- +-- This is used by a scheduling group to select builds and the default +-- Validation Kit from the Builds table. +-- +-- @remarks This table stores history. Never update or delete anything. The +-- equivalent of deleting is done by setting the 'tsExpire' field to +-- current_timestamp. +-- +-- @todo Any better way of representing this so we could more easily +-- join/whatever when searching for builds? +-- +CREATE SEQUENCE BuildSourceIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE BuildSources ( + --- The build source identifier. + -- This stays constant over time. + idBuildSrc INTEGER DEFAULT NEXTVAL('BuildSourceIdSeq') NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER NOT NULL, + + --- The name of the build source. + sName TEXT NOT NULL, + --- Description. + sDescription TEXT DEFAULT NULL, + + --- Which product. + -- ASSUME that it is okay to limit a build source to a single product. + sProduct text NOT NULL, + --- Which branch. + -- ASSUME that it is okay to limit a build source to a branch. + sBranch text NOT NULL, + + --- Build types to include, all matches if NULL. + -- @todo Weighting the types would be nice in a later version. + asTypes text ARRAY DEFAULT NULL, + --- Array of the 'sOs.sCpuArch' to match, all matches if NULL. + -- See KBUILD_OSES in kBuild for a list of standard target OSes, and + -- KBUILD_ARCHES for a list of standard architectures. + -- + -- @remarks See marks on 'os-agnostic' and 'noarch' in BuildCategories. + asOsArches text ARRAY DEFAULT NULL, + + --- The first subversion tree revision to match, no lower limit if NULL. + iFirstRevision INTEGER DEFAULT NULL, + --- The last subversion tree revision to match, no upper limit if NULL. + iLastRevision INTEGER DEFAULT NULL, + + --- The maximum age of the builds in seconds, unlimited if NULL. + cSecMaxAge INTEGER DEFAULT NULL, + + PRIMARY KEY (idBuildSrc, tsExpire) +); + + +--- @table TestCases +-- Test case configuration. +-- +-- @remarks This table stores history. Never update or delete anything. The +-- equivalent of deleting is done by setting the 'tsExpire' field to +-- current_timestamp. +-- +CREATE SEQUENCE TestCaseIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE SEQUENCE TestCaseGenIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE TestCases ( + --- The fixed test case ID. + -- This is assigned when the test case is created and will never change. + idTestCase INTEGER DEFAULT NEXTVAL('TestCaseIdSeq') NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER NOT NULL, + --- Generation ID for this row, a truly unique identifier. + -- This is primarily for referencing by TestSets. + idGenTestCase INTEGER UNIQUE DEFAULT NEXTVAL('TestCaseGenIdSeq') NOT NULL, + + --- The name of the test case. + sName TEXT NOT NULL, + --- Optional test case description. + sDescription TEXT DEFAULT NULL, + --- Indicates whether this test case is currently enabled. + fEnabled BOOLEAN DEFAULT FALSE NOT NULL, + --- Default test case timeout given in seconds. + cSecTimeout INTEGER NOT NULL CHECK (cSecTimeout > 0), + --- Default TestBox requirement expression (python boolean expression). + -- All the scheduler properties are available for use with the same names + -- as in that table. + -- If NULL everything matches. + sTestBoxReqExpr TEXT DEFAULT NULL, + --- Default build requirement expression (python boolean expression). + -- The following build properties are available: sProduct, sBranch, + -- sType, asOsArches, sVersion, iRevision, uidAuthor and idBuild. + -- If NULL everything matches. + sBuildReqExpr TEXT DEFAULT NULL, + + --- The base command. + -- String suitable for executing in bourne shell with space as separator + -- (IFS). References to @BUILD_BINARIES@ will be replaced WITH the content + -- of the Builds(sBinaries) field. + sBaseCmd TEXT NOT NULL, + + --- Comma separated list of test suite zips (or tars) that the testbox will + -- need to download and expand prior to testing. + -- If NULL the current test suite of the scheduling group will be used (the + -- scheduling group will have an optional test suite build queue associated + -- with it). The current test suite can also be referenced by + -- @VALIDATIONKIT_ZIP@ in case more downloads are required. Files may also be + -- uploaded to the test manager download area, in which case the + -- @DOWNLOAD_BASE_URL@ prefix can be used to refer to this area. + sTestSuiteZips TEXT DEFAULT NULL, + + -- Comment regarding a change or something. + sComment TEXT DEFAULT NULL, + + PRIMARY KEY (idTestCase, tsExpire) +); + + +--- @table TestCaseArgs +-- Test case argument list variations. +-- +-- For example, we have a test case that does a set of tests on a virtual +-- machine. To get better code/feature coverage of this testcase we wish to +-- run it with different guest hardware configuration. The test case may do +-- the same stuff, but the guest OS as well as the VMM may react differently to +-- the hardware configurations and uncover issues in the VMM, device emulation +-- or other places. +-- +-- Typical hardware variations are: +-- - guest memory size (RAM), +-- - guest video memory size (VRAM), +-- - virtual CPUs / cores / threads, +-- - virtual chipset +-- - virtual network interface card (NIC) +-- - USB 1.1, USB 2.0, no USB +-- +-- The TM web UI will help the user create a reasonable set of permutations +-- of these parameters, the user specifies a maximum and the TM uses certain +-- rules together with random selection to generate the desired number. The +-- UI will also help suggest fitting testbox requirements according to the +-- RAM/VRAM sizes and the virtual CPU counts. The user may then make +-- adjustments to the suggestions before commit them. +-- +-- Alternatively, the user may also enter all the permutations without any +-- help from the UI. +-- +-- Note! All test cases has at least one entry in this table, even if it is +-- empty, because testbox requirements are specified thru this. +-- +-- Querying the valid parameter lists for a testase this way: +-- SELECT * ... WHERE idTestCase = TestCases.idTestCase +-- AND tsExpire > <when> +-- AND tsEffective <= <when>; +-- +-- Querying the valid parameter list for the latest generation can be +-- simplified by just checking tsExpire date: +-- SELECT * ... WHERE idTestCase = TestCases.idTestCase +-- AND tsExpire == TIMESTAMP WITH TIME ZONE 'infinity'; +-- +-- @remarks This table stores history. Never update or delete anything. The +-- equivalent of deleting is done by setting the 'tsExpire' field to +-- current_timestamp. +-- +CREATE SEQUENCE TestCaseArgsIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE SEQUENCE TestCaseArgsGenIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE TestCaseArgs ( + --- The test case ID. + -- Non-unique foreign key: TestCases(idTestCase). + idTestCase INTEGER NOT NULL, + --- The testcase argument variation ID (fixed). + -- This is primarily for TestGroupMembers.aidTestCaseArgs. + idTestCaseArgs INTEGER DEFAULT NEXTVAL('TestCaseArgsIdSeq') NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER NOT NULL, + --- Generation ID for this row. + -- This is primarily for efficient referencing by TestSets and SchedQueues. + idGenTestCaseArgs INTEGER UNIQUE DEFAULT NEXTVAL('TestCaseArgsGenIdSeq') NOT NULL, + + --- The additional arguments. + -- String suitable for bourne shell style argument parsing with space as + -- separator (IFS). References to @BUILD_BINARIES@ will be replaced with + -- the content of the Builds(sBinaries) field. + sArgs TEXT NOT NULL, + --- Optional test case timeout given in seconds. + -- If NULL, the TestCases.cSecTimeout field is used instead. + cSecTimeout INTEGER DEFAULT NULL CHECK (cSecTimeout IS NULL OR cSecTimeout > 0), + --- Additional TestBox requirement expression (python boolean expression). + -- All the scheduler properties are available for use with the same names + -- as in that table. This is checked after first checking the requirements + -- in the TestCases.sTestBoxReqExpr field. + sTestBoxReqExpr TEXT DEFAULT NULL, + --- Additional build requirement expression (python boolean expression). + -- The following build properties are available: sProduct, sBranch, + -- sType, asOsArches, sVersion, iRevision, uidAuthor and idBuild. This is + -- checked after first checking the requirements in the + -- TestCases.sBuildReqExpr field. + sBuildReqExpr TEXT DEFAULT NULL, + --- Number of testboxes required (gang scheduling). + cGangMembers SMALLINT DEFAULT 1 NOT NULL CHECK (cGangMembers > 0 AND cGangMembers < 1024), + --- Optional variation sub-name. + sSubName TEXT DEFAULT NULL, + + --- The arguments are part of the primary key for several reasons. + -- No duplicate argument lists (makes no sense - if you want to prioritize + -- argument lists, we add that explicitly). This may hopefully enable us + -- to more easily check coverage later on, even when the test case is + -- reconfigured with more/less permutations. + PRIMARY KEY (idTestCase, tsExpire, sArgs) +); +CREATE INDEX TestCaseArgsLookupIdx ON TestCaseArgs (idTestCase, tsExpire DESC, tsEffective ASC); + + +--- @table TestCaseDeps +-- Test case dependencies (N:M) +-- +-- This effect build selection. The build must have passed all runs of the +-- given prerequisite testcase (idTestCasePreReq) and executed at a minimum one +-- argument list variation. +-- +-- This should also affect scheduling order, if possible at least one +-- prerequisite testcase variation should be place before the specific testcase +-- in the scheduling queue. +-- +-- @remarks This table stores history. Never update or delete anything. The +-- equivalent of deleting is done by setting the 'tsExpire' field to +-- current_timestamp. To select the currently valid entries use +-- tsExpire = TIMESTAMP WITH TIME ZONE 'infinity'. +-- +CREATE TABLE TestCaseDeps ( + --- The test case that depends on someone. + -- Non-unique foreign key: TestCases(idTestCase). + idTestCase INTEGER NOT NULL, + --- The prerequisite test case ID. + -- Non-unique foreign key: TestCases(idTestCase). + idTestCasePreReq INTEGER NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER NOT NULL, + + PRIMARY KEY (idTestCase, idTestCasePreReq, tsExpire) +); + + +--- @table TestCaseGlobalRsrcDeps +-- Test case dependencies on global resources (N:M) +-- +-- @remarks This table stores history. Never update or delete anything. The +-- equivalent of deleting is done by setting the 'tsExpire' field to +-- current_timestamp. To select the currently valid entries use +-- tsExpire = TIMESTAMP WITH TIME ZONE 'infinity'. +-- +CREATE TABLE TestCaseGlobalRsrcDeps ( + --- The test case that depends on someone. + -- Non-unique foreign key: TestCases(idTestCase). + idTestCase INTEGER NOT NULL, + --- The prerequisite resource ID. + -- Non-unique foreign key: GlobalResources(idGlobalRsrc). + idGlobalRsrc INTEGER NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER NOT NULL, + + PRIMARY KEY (idTestCase, idGlobalRsrc, tsExpire) +); + + +--- @table TestGroups +-- Test Group - A collection of test cases. +-- +-- This is for simplifying test configuration by working with a few groups +-- instead of a herd of individual testcases. It may also be used for creating +-- test suites for certain areas (like guest additions) or tasks (like +-- performance measurements). +-- +-- A test case can be member of any number of test groups. +-- +-- @remarks This table stores history. Never update or delete anything. The +-- equivalent of deleting is done by setting the 'tsExpire' field to +-- current_timestamp. To select the currently valid entries use +-- tsExpire = TIMESTAMP WITH TIME ZONE 'infinity'. +-- +CREATE SEQUENCE TestGroupIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE TestGroups ( + --- The fixed scheduling group ID. + -- This is assigned when the group is created and will never change. + idTestGroup INTEGER DEFAULT NEXTVAL('TestGroupIdSeq') NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER NOT NULL, + + --- The name of the scheduling group. + sName TEXT NOT NULL, + --- Optional group description. + sDescription TEXT, + -- Comment regarding a change or something. + sComment TEXT DEFAULT NULL, + + PRIMARY KEY (idTestGroup, tsExpire) +); +CREATE INDEX TestGroups_id_index ON TestGroups (idTestGroup, tsExpire DESC, tsEffective ASC); + + +--- @table TestGroupMembers +-- The N:M relationship between test case configurations and test groups. +-- +-- @remarks This table stores history. Never update or delete anything. The +-- equivalent of deleting is done by setting the 'tsExpire' field to +-- current_timestamp. To select the currently valid entries use +-- tsExpire = TIMESTAMP WITH TIME ZONE 'infinity'. +-- +CREATE TABLE TestGroupMembers ( + --- The group ID. + -- Non-unique foreign key: TestGroups(idTestGroup). + idTestGroup INTEGER NOT NULL, + --- The test case ID. + -- Non-unique foreign key: TestCases(idTestCase). + idTestCase INTEGER NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER NOT NULL, + + --- Test case scheduling priority. + -- Higher number causes the test case to be run more frequently. + -- @sa SchedGroupMembers.iSchedPriority, TestBoxesInSchedGroups.iSchedPriority + -- @todo Not sure we want to keep this... + iSchedPriority INTEGER DEFAULT 16 CHECK (iSchedPriority >= 0 AND iSchedPriority < 32) NOT NULL, + + --- Limit the memberships to the given argument variations. + -- Non-unique foreign key: TestCaseArgs(idTestCase, idTestCaseArgs). + aidTestCaseArgs INTEGER ARRAY DEFAULT NULL, + + PRIMARY KEY (idTestGroup, idTestCase, tsExpire) +); + + +--- @table SchedGroups +-- Scheduling group (aka. testbox partitioning) configuration. +-- +-- A testbox is associated with exactly one scheduling group. This association +-- can be changed, of course. If we (want to) retire a group which still has +-- testboxes associated with it, these will be moved to the 'default' group. +-- +-- The TM web UI will make sure that a testbox is always in a group and that +-- the default group cannot be deleted. +-- +-- A scheduling group combines several things: +-- - A selection of builds to test (via idBuildSrc). +-- - A collection of test groups to test with (via SchedGroupMembers). +-- - A set of testboxes to test on (via TestBoxes.idSchedGroup). +-- +-- In additions there is an optional source of fresh test suite builds (think +-- VBoxTestSuite) as well as scheduling options. +-- +-- @remarks This table stores history. Never update or delete anything. The +-- equivalent of deleting is done by setting the 'tsExpire' field to +-- current_timestamp. To select the currently valid entries use +-- tsExpire = TIMESTAMP WITH TIME ZONE 'infinity'. +-- +CREATE TYPE Scheduler_T AS ENUM ( + 'bestEffortContinousItegration', + 'reserved' +); +CREATE SEQUENCE SchedGroupIdSeq + START 2 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE SchedGroups ( + --- The fixed scheduling group ID. + -- This is assigned when the group is created and will never change. + idSchedGroup INTEGER DEFAULT NEXTVAL('SchedGroupIdSeq') NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- Non-unique foreign key: Users(uid) + -- @note This is NULL for the default group. + uidAuthor INTEGER DEFAULT NULL, + + --- The name of the scheduling group. + sName TEXT NOT NULL, + --- Optional group description. + sDescription TEXT, + --- Indicates whether this group is currently enabled. + fEnabled boolean NOT NULL, + --- The scheduler to use. + -- This is for when we later desire different scheduling that the best + -- effort stuff provided by the initial implementation. + enmScheduler Scheduler_T DEFAULT 'bestEffortContinousItegration'::Scheduler_T NOT NULL, + --- The build source. + -- Non-unique foreign key: BuildSources(idBuildSrc) + idBuildSrc INTEGER DEFAULT NULL, + --- The Validation Kit build source (@VALIDATIONKIT_ZIP@). + -- Non-unique foreign key: BuildSources(idBuildSrc) + idBuildSrcTestSuite INTEGER DEFAULT NULL, + -- Comment regarding a change or something. + sComment TEXT DEFAULT NULL, + + PRIMARY KEY (idSchedGroup, tsExpire) +); + +-- Special default group. +INSERT INTO SchedGroups (idSchedGroup, tsEffective, tsExpire, sName, sDescription, fEnabled) + VALUES (1, TIMESTAMP WITH TIME ZONE 'epoch', TIMESTAMP WITH TIME ZONE 'infinity', 'default', 'default group', FALSE); + + +--- @table SchedGroupMembers +-- N:M relationship between scheduling groups and test groups. +-- +-- Several scheduling parameters are associated with this relationship. +-- +-- The test group dependency (idTestGroupPreReq) can be used in the same way as +-- TestCaseDeps.idTestCasePreReq, only here on test group level. This means it +-- affects the build selection. The builds needs to have passed all test runs +-- the prerequisite test group and done at least one argument variation of each +-- test case in it. +-- +-- @remarks This table stores history. Never update or delete anything. The +-- equivalent of deleting is done by setting the 'tsExpire' field to +-- current_timestamp. To select the currently valid entries use +-- tsExpire = TIMESTAMP WITH TIME ZONE 'infinity'. +-- +CREATE TABLE SchedGroupMembers ( + --- Scheduling ID. + -- Non-unique foreign key: SchedGroups(idSchedGroup). + idSchedGroup INTEGER NOT NULL, + --- Testgroup ID. + -- Non-unique foreign key: TestGroups(idTestGroup). + idTestGroup INTEGER NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER NOT NULL, + + --- The scheduling priority of the test group. + -- Higher number causes the test case to be run more frequently. + -- @sa TestGroupMembers.iSchedPriority, TestBoxesInSchedGroups.iSchedPriority + iSchedPriority INTEGER DEFAULT 16 CHECK (iSchedPriority >= 0 AND iSchedPriority < 32) NOT NULL, + --- When during the week this group is allowed to start running, NULL means + -- there are no constraints. + -- Each bit in the bitstring represents one hour, with bit 0 indicating the + -- midnight hour on a monday. + bmHourlySchedule bit(168) DEFAULT NULL, + --- Optional test group dependency. + -- Non-unique foreign key: TestGroups(idTestGroup). + -- This is for requiring that a build has been subject to smoke tests + -- before bothering to subject it to longer tests. + -- @todo Not entirely sure this should be here, but I'm not so keen on yet + -- another table as the only use case is smoketests. + idTestGroupPreReq INTEGER DEFAULT NULL, + + PRIMARY KEY (idSchedGroup, idTestGroup, tsExpire) +); + + +--- @table TestBoxStrTab +-- String table for the test boxes. +-- +-- This is a string cache for all string members in TestBoxes except the name. +-- The rational is to avoid duplicating large strings like sReport when the +-- testbox reports a new cMbScratch value or the box when the test sheriff +-- sends a reboot command or similar. +-- +-- At the time this table was introduced, we had 400558 TestBoxes rows, where +-- the SUM(LENGTH(sReport)) was 993MB. There were really just 1066 distinct +-- sReport values, with a total length of 0x3 MB. +-- +-- Nothing is ever deleted from this table. +-- +-- @note Should use a stored procedure to query/insert a string. +-- +-- +-- TestBox stats prior to conversion: +-- SELECT COUNT(*) FROM TestBoxes: 400558 rows +-- SELECT pg_total_relation_size('TestBoxes'): 740794368 bytes (706 MB) +-- Average row cost: 740794368 / 400558 = 1849 bytes/row +-- +-- After conversion: +-- SELECT COUNT(*) FROM TestBoxes: 400558 rows +-- SELECT pg_total_relation_size('TestBoxes'): 144375808 bytes (138 MB) +-- SELECT COUNT(idStr) FROM TestBoxStrTab: 1292 rows +-- SELECT pg_total_relation_size('TestBoxStrTab'): 5709824 bytes (5.5 MB) +-- (144375808 + 5709824) / 740794368 = 20 % +-- Average row cost boxes: 144375808 / 400558 = 360 bytes/row +-- Average row cost strings: 5709824 / 1292 = 4420 bytes/row +-- +CREATE SEQUENCE TestBoxStrTabIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE TestBoxStrTab ( + --- The ID of this string. + idStr INTEGER PRIMARY KEY DEFAULT NEXTVAL('TestBoxStrTabIdSeq'), + --- The string value. + sValue text NOT NULL, + --- Creation time stamp. + tsCreated TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL +); +-- Note! Must use hash index as the sReport strings are too long for regular indexing. +CREATE INDEX TestBoxStrTabNameIdx ON TestBoxStrTab USING hash (sValue); + +--- Empty string with ID 0. +INSERT INTO TestBoxStrTab (idStr, sValue) VALUES (0, ''); + + +--- @type TestBoxCmd_T +-- Testbox commands. +CREATE TYPE TestBoxCmd_T AS ENUM ( + 'none', + 'abort', + 'reboot', --< This implies abort. Status changes when reaching 'idle'. + 'upgrade', --< This is only handled when asking for work. + 'upgrade-and-reboot', --< Ditto. + 'special' --< Similar to upgrade, reserved for the future. +); + + +--- @type LomKind_T +-- The kind of lights out management on a testbox. +CREATE TYPE LomKind_T AS ENUM ( + 'none', + 'ilom', + 'elom', + 'apple-xserve-lom' +); + + +--- @table TestBoxes +-- Testbox configurations. +-- +-- The testboxes are identified by IP and the system UUID if available. Should +-- the IP change, the testbox will be refused at sign on and the testbox +-- sheriff will have to update it's IP. +-- +-- @todo Implement the UUID stuff. Get it from DMI, UEFI or whereever. +-- Mismatching needs to be logged somewhere... +-- +-- To query the currently valid configuration: +-- SELECT ... WHERE id = idTestBox AND tsExpire = TIMESTAMP WITH TIME ZONE 'infinity'; +-- +-- @remarks This table stores history. Never update or delete anything. The +-- equivalent of deleting is done by setting the 'tsExpire' field to +-- current_timestamp. To select the currently valid entries use +-- tsExpire = TIMESTAMP WITH TIME ZONE 'infinity'. +-- +CREATE SEQUENCE TestBoxIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE SEQUENCE TestBoxGenIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE TestBoxes ( + --- The fixed testbox ID. + -- This is assigned when the testbox is created and will never change. + idTestBox INTEGER DEFAULT NEXTVAL('TestBoxIdSeq') NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- When modified automatically by the testbox, NULL is used. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER DEFAULT NULL, + --- Generation ID for this row. + -- This is primarily for referencing by TestSets. + idGenTestBox INTEGER UNIQUE DEFAULT NEXTVAL('TestBoxGenIdSeq') NOT NULL, + + --- The testbox IP. + -- This is from the webserver point of view and automatically updated on + -- SIGNON. The test setup doesn't permit for IP addresses to change while + -- the testbox is operational, because this will break gang tests. + ip inet NOT NULL, + --- The system or firmware UUID. + -- This uniquely identifies the testbox when talking to the server. After + -- SIGNON though, the testbox will also provide idTestBox and ip to + -- establish its identity beyond doubt. + uuidSystem uuid NOT NULL, + --- The testbox name. + -- Usually similar to the DNS name. + sName text NOT NULL, + --- Optional testbox description. + -- Intended for describing the box as well as making other relevant notes. + idStrDescription INTEGER REFERENCES TestBoxStrTab(idStr) DEFAULT NULL, + + --- Indicates whether this testbox is enabled. + -- A testbox gets disabled when we're doing maintenance, debugging a issue + -- that happens only on that testbox, or some similar stuff. This is an + -- alternative to deleting the testbox. + fEnabled BOOLEAN DEFAULT NULL, + + --- The kind of lights-out-management. + enmLomKind LomKind_T DEFAULT 'none'::LomKind_T NOT NULL, + --- The IP adress of the lights-out-management. + -- This can be NULL if enmLomKind is 'none', otherwise it must contain a valid address. + ipLom inet DEFAULT NULL, + + --- Timeout scale factor, given as a percent. + -- This is a crude adjustment of the test case timeout for slower hardware. + pctScaleTimeout smallint DEFAULT 100 NOT NULL CHECK (pctScaleTimeout > 10 AND pctScaleTimeout < 20000), + + --- Change comment or similar. + idStrComment INTEGER REFERENCES TestBoxStrTab(idStr) DEFAULT NULL, + + --- @name Scheduling properties (reported by testbox script). + -- @{ + --- Same abbrieviations as kBuild, see KBUILD_OSES. + idStrOs INTEGER REFERENCES TestBoxStrTab(idStr) DEFAULT NULL, + --- Informational, no fixed format. + idStrOsVersion INTEGER REFERENCES TestBoxStrTab(idStr) DEFAULT NULL, + --- Same as CPUID reports (GenuineIntel, AuthenticAMD, CentaurHauls, ...). + idStrCpuVendor INTEGER REFERENCES TestBoxStrTab(idStr) DEFAULT NULL, + --- Same as kBuild - x86, amd64, ... See KBUILD_ARCHES. + idStrCpuArch INTEGER REFERENCES TestBoxStrTab(idStr) DEFAULT NULL, + --- The CPU name if available. + idStrCpuName INTEGER REFERENCES TestBoxStrTab(idStr) DEFAULT NULL, + --- Number identifying the CPU family/model/stepping/whatever. + -- For x86 and AMD64 type CPUs, this will on the following format: + -- (EffFamily << 24) | (EffModel << 8) | Stepping. + lCpuRevision bigint DEFAULT NULL, + --- Number of CPUs, CPU cores and CPU threads. + cCpus smallint DEFAULT NULL CHECK (cCpus IS NULL OR cCpus > 0), + --- Set if capable of hardware virtualization. + fCpuHwVirt boolean DEFAULT NULL, + --- Set if capable of nested paging. + fCpuNestedPaging boolean DEFAULT NULL, + --- Set if CPU capable of 64-bit (VBox) guests. + fCpu64BitGuest boolean DEFAULT NULL, + --- Set if chipset with usable IOMMU (VT-d / AMD-Vi). + fChipsetIoMmu boolean DEFAULT NULL, + --- Set if the test box does raw-mode tests. + fRawMode boolean DEFAULT NULL, + --- The (approximate) memory size in megabytes (rounded down to nearest 4 MB). + cMbMemory bigint DEFAULT NULL CHECK (cMbMemory IS NULL OR cMbMemory > 0), + --- The amount of scratch space in megabytes (rounded down to nearest 64 MB). + cMbScratch bigint DEFAULT NULL CHECK (cMbScratch IS NULL OR cMbScratch >= 0), + --- Free form hardware and software report field. + idStrReport INTEGER REFERENCES TestBoxStrTab(idStr) DEFAULT NULL, + --- @} + + --- The testbox script revision number, serves the purpose of a version number. + -- Probably good to have when scheduling upgrades as well for status purposes. + iTestBoxScriptRev INTEGER DEFAULT 0 NOT NULL, + --- The python sys.hexversion (layed out as of 2.7). + -- Good to know which python versions we need to support. + iPythonHexVersion INTEGER DEFAULT NULL, + + --- Pending command. + -- @note We put it here instead of in TestBoxStatuses to get history. + enmPendingCmd TestBoxCmd_T DEFAULT 'none'::TestBoxCmd_T NOT NULL, + + PRIMARY KEY (idTestBox, tsExpire), + + --- Nested paging requires hardware virtualization. + CHECK (fCpuNestedPaging IS NULL OR (fCpuNestedPaging <> TRUE OR fCpuHwVirt = TRUE)) +); +CREATE UNIQUE INDEX TestBoxesUuidIdx ON TestBoxes (uuidSystem, tsExpire DESC); +CREATE INDEX TestBoxesExpireEffectiveIdx ON TestBoxes (tsExpire DESC, tsEffective ASC); + + +-- +-- Create a view for TestBoxes where the strings are resolved. +-- +CREATE VIEW TestBoxesWithStrings AS + SELECT TestBoxes.*, + Str1.sValue AS sDescription, + Str2.sValue AS sComment, + Str3.sValue AS sOs, + Str4.sValue AS sOsVersion, + Str5.sValue AS sCpuVendor, + Str6.sValue AS sCpuArch, + Str7.sValue AS sCpuName, + Str8.sValue AS sReport + FROM TestBoxes + LEFT OUTER JOIN TestBoxStrTab Str1 ON idStrDescription = Str1.idStr + LEFT OUTER JOIN TestBoxStrTab Str2 ON idStrComment = Str2.idStr + LEFT OUTER JOIN TestBoxStrTab Str3 ON idStrOs = Str3.idStr + LEFT OUTER JOIN TestBoxStrTab Str4 ON idStrOsVersion = Str4.idStr + LEFT OUTER JOIN TestBoxStrTab Str5 ON idStrCpuVendor = Str5.idStr + LEFT OUTER JOIN TestBoxStrTab Str6 ON idStrCpuArch = Str6.idStr + LEFT OUTER JOIN TestBoxStrTab Str7 ON idStrCpuName = Str7.idStr + LEFT OUTER JOIN TestBoxStrTab Str8 ON idStrReport = Str8.idStr; + + +--- @table TestBoxesInSchedGroups +-- N:M relationship between test boxes and scheduling groups. +-- +-- We associate a priority with this relationship. +-- +-- @remarks This table stores history. Never update or delete anything. The +-- equivalent of deleting is done by setting the 'tsExpire' field to +-- current_timestamp. To select the currently valid entries use +-- tsExpire = TIMESTAMP WITH TIME ZONE 'infinity'. +-- +CREATE TABLE TestBoxesInSchedGroups ( + --- TestBox ID. + -- Non-unique foreign key: TestBoxes(idTestBox). + idTestBox INTEGER NOT NULL, + --- Scheduling ID. + -- Non-unique foreign key: SchedGroups(idSchedGroup). + idSchedGroup INTEGER NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER NOT NULL, + + --- The scheduling priority of the scheduling group for the test box. + -- Higher number causes the scheduling group to be serviced more frequently. + -- @sa TestGroupMembers.iSchedPriority, SchedGroups.iSchedPriority + iSchedPriority INTEGER DEFAULT 16 CHECK (iSchedPriority >= 0 AND iSchedPriority < 32) NOT NULL, + + PRIMARY KEY (idTestBox, idSchedGroup, tsExpire) +); + + +-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +-- +-- F a i l u r e T r a c k i n g +-- +-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + + +--- @table FailureCategories +-- Failure categories. +-- +-- This is for organizing the failure reasons. +-- +-- @remarks This table stores history. Never update or delete anything. The +-- equivalent of deleting is done by setting the 'tsExpire' field to +-- current_timestamp. To select the currently valid entries use +-- tsExpire = TIMESTAMP WITH TIME ZONE 'infinity'. +-- +CREATE SEQUENCE FailureCategoryIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE FailureCategories ( + --- The identifier of this failure category (once assigned, it will never change). + idFailureCategory INTEGER DEFAULT NEXTVAL('FailureCategoryIdSeq') NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER NOT NULL, + --- The short category description. + -- For combo boxes and other selection lists. + sShort text NOT NULL, + --- Full description + -- For cursor-over-poppups for instance. + sFull text NOT NULL, + + PRIMARY KEY (idFailureCategory, tsExpire) +); + + +--- @table FailureReasons +-- Failure reasons. +-- +-- When analysing a test failure, the testbox sheriff will try assign a fitting +-- reason for the failure. This table is here to help the sheriff in his/hers +-- job as well as developers looking checking if their changes affected the +-- test results in any way. +-- +-- @remarks This table stores history. Never update or delete anything. The +-- equivalent of deleting is done by setting the 'tsExpire' field to +-- current_timestamp. To select the currently valid entries use +-- tsExpire = TIMESTAMP WITH TIME ZONE 'infinity'. +-- +CREATE SEQUENCE FailureReasonIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE FailureReasons ( + --- The identifier of this failure reason (once assigned, it will never change). + idFailureReason INTEGER DEFAULT NEXTVAL('FailureReasonIdSeq') NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER NOT NULL, + + --- The failure category this reason belongs to. + -- Non-unique foreign key: FailureCategories(idFailureCategory) + idFailureCategory INTEGER NOT NULL, + --- The short failure description. + -- For combo boxes and other selection lists. + sShort text NOT NULL, + --- Full failure description. + sFull text NOT NULL, + --- Ticket number in the primary bugtracker. + iTicket INTEGER DEFAULT NULL, + --- Other URLs to reports or discussions of the observed symptoms. + asUrls text ARRAY DEFAULT NULL, + + PRIMARY KEY (idFailureReason, tsExpire) +); +CREATE INDEX FailureReasonsCategoryIdx ON FailureReasons (idFailureCategory, idFailureReason); + + + +--- @table TestResultFailures +-- This is for tracking/discussing test result failures. +-- +-- The rational for putting this is a separate table is that we need history on +-- this while TestResults does not. +-- +-- @remarks This table stores history. Never update or delete anything. The +-- equivalent of deleting is done by setting the 'tsExpire' field to +-- current_timestamp. To select the currently valid entries use +-- tsExpire = TIMESTAMP WITH TIME ZONE 'infinity'. +-- +CREATE TABLE TestResultFailures ( + --- The test result we're disucssing. + -- @note The foreign key is declared after TestResults (further down). + idTestResult INTEGER NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER NOT NULL, + --- The testsest this result is a part of. + -- This is mainly an aid for bypassing the enormous TestResults table. + -- Note! This is a foreign key, but we have to add it after TestSets has + -- been created, see further down. + idTestSet INTEGER NOT NULL, + + --- The suggested failure reason. + -- Non-unique foreign key: FailureReasons(idFailureReason) + idFailureReason INTEGER NOT NULL, + --- Optional comment. + sComment text DEFAULT NULL, + + PRIMARY KEY (idTestResult, tsExpire) +); +CREATE INDEX TestResultFailureIdx ON TestResultFailures (idTestSet, tsExpire DESC, tsEffective ASC); +CREATE INDEX TestResultFailureIdx2 ON TestResultFailures (idTestResult, tsExpire DESC, tsEffective ASC); +CREATE INDEX TestResultFailureIdx3 ON TestResultFailures (idFailureReason, idTestResult, tsExpire DESC, tsEffective ASC); + + + + +-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +-- +-- T e s t I n p u t +-- +-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + + +--- @table BuildBlacklist +-- Table used to blacklist sets of builds. +-- +-- The best usage example is a VMM developer realizing that a change causes the +-- host to panic, hang, or otherwise misbehave. To prevent the testbox sheriff +-- from repeatedly having to reboot testboxes, the builds gets blacklisted +-- until there is a working build again. This may mean adding an open ended +-- blacklist spec and then updating it with the final revision number once the +-- fix has been committed. +-- +-- @remarks This table stores history. Never update or delete anything. The +-- equivalent of deleting is done by setting the 'tsExpire' field to +-- current_timestamp. To select the currently valid entries use +-- tsExpire = TIMESTAMP WITH TIME ZONE 'infinity'. +-- +-- @todo Would be nice if we could replace the text strings below with a set of +-- BuildCategories, or sore it in any other way which would enable us to +-- do a negative join with build category... The way it is specified +-- now, it looks like we have to open a cursor of prospecitve builds and +-- filter then thru this table one by one. +-- +-- Any better representation is welcome, but this is low prioirty for +-- now, as it's relatively easy to change this later one. +-- +CREATE SEQUENCE BuildBlacklistIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE BuildBlacklist ( + --- The blacklist entry id. + -- This stays constant over time. + idBlacklisting INTEGER DEFAULT NEXTVAL('BuildBlacklistIdSeq') NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER NOT NULL, + + --- The reason for the blacklisting. + -- Non-unique foreign key: FailureReasons(idFailureReason) + idFailureReason INTEGER NOT NULL, + + --- Which product. + -- ASSUME that it is okay to limit a blacklisting to a single product. + sProduct text NOT NULL, + --- Which branch. + -- ASSUME that it is okay to limit a blacklisting to a branch. + sBranch text NOT NULL, + + --- Build types to include, all matches if NULL. + asTypes text ARRAY DEFAULT NULL, + --- Array of the 'sOs.sCpuArch' to match, all matches if NULL. + -- See KBUILD_OSES in kBuild for a list of standard target OSes, and + -- KBUILD_ARCHES for a list of standard architectures. + -- + -- @remarks See marks on 'os-agnostic' and 'noarch' in BuildCategories. + asOsArches text ARRAY DEFAULT NULL, + + --- The first subversion tree revision to blacklist. + iFirstRevision INTEGER NOT NULL, + --- The last subversion tree revision to blacklist, no upper limit if NULL. + iLastRevision INTEGER NOT NULL, + + PRIMARY KEY (idBlacklisting, tsExpire) +); +CREATE INDEX BuildBlacklistIdx ON BuildBlacklist (iLastRevision DESC, iFirstRevision ASC, sProduct, sBranch, + tsExpire DESC, tsEffective ASC); + +--- @table BuildCategories +-- Build categories. +-- +-- The purpose of this table is saving space in the Builds table and hopefully +-- speed things up when selecting builds as well (compared to selecting on 4 +-- text fields in the much larger Builds table). +-- +-- Insert only table, no update, no delete. History is not needed. +-- +CREATE SEQUENCE BuildCategoryIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE BuildCategories ( + --- The build type identifier. + idBuildCategory INTEGER PRIMARY KEY DEFAULT NEXTVAL('BuildCategoryIdSeq') NOT NULL, + --- Product. + -- The product name. For instance 'VBox' or 'VBoxTestSuite'. + sProduct TEXT NOT NULL, + --- The version control repository name. + sRepository TEXT NOT NULL, + --- The branch name (in the version control system). + sBranch TEXT NOT NULL, + --- The build type. + -- See KBUILD_BLD_TYPES in kBuild for a list of standard build types. + sType TEXT NOT NULL, + --- Array of the 'sOs.sCpuArch' supported by the build. + -- See KBUILD_OSES in kBuild for a list of standard target OSes, and + -- KBUILD_ARCHES for a list of standard architectures. + -- + -- @remarks 'os-agnostic' is used if the build doesn't really target any + -- specific OS or if it targets all applicable OSes. + -- 'noarch' is used if the build is architecture independent or if + -- all applicable architectures are handled. + -- Thus, 'os-agnostic.noarch' will run on all build boxes. + -- + -- @note The array shall be sorted ascendingly to prevent unnecessary duplicates! + -- + asOsArches TEXT ARRAY NOT NULL, + + UNIQUE (sProduct, sRepository, sBranch, sType, asOsArches) +); + + +--- @table Builds +-- The builds table contains builds from the tinderboxes and oaccasionally from +-- developers. +-- +-- The tinderbox side could be fed by a batch job enumerating the build output +-- directories every so often, looking for new builds. Or we could query them +-- from the tinderbox database. Yet another alternative is making the +-- tinderbox server or client side software inform us about all new builds. +-- +-- The developer builds are entered manually thru the TM web UI. They are used +-- for subjecting new code to some larger scale testing before commiting, +-- enabling, or merging a private branch. +-- +-- The builds are being selected from this table by the via the build source +-- specification that SchedGroups.idBuildSrc and +-- SchedGroups.idBuildSrcTestSuite links to. +-- +-- @remarks This table stores history. Never update or delete anything. The +-- equivalent of deleting is done by setting the 'tsExpire' field to +-- current_timestamp. To select the currently valid entries use +-- tsExpire = TIMESTAMP WITH TIME ZONE 'infinity'. +-- +CREATE SEQUENCE BuildIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE Builds ( + --- The build identifier. + -- This remains unchanged + idBuild INTEGER DEFAULT NEXTVAL('BuildIdSeq') NOT NULL, + --- When this build was created or entered into the database. + -- This remains unchanged + tsCreated TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- Non-unique foreign key: Users(uid) + -- @note This is NULL if added by a batch job / tinderbox. + uidAuthor INTEGER DEFAULT NULL, + --- The build category. + idBuildCategory INTEGER REFERENCES BuildCategories(idBuildCategory) NOT NULL, + --- The subversion tree revision of the build. + iRevision INTEGER NOT NULL, + --- The product version number (suitable for RTStrVersionCompare). + sVersion TEXT NOT NULL, + --- The link to the tinderbox log of this build. + sLogUrl TEXT, + --- Comma separated list of binaries. + -- The binaries have paths relative to the TESTBOX_PATH_BUILDS or full URLs. + sBinaries TEXT NOT NULL, + --- Set when the binaries gets deleted by the build quota script. + fBinariesDeleted BOOLEAN DEFAULT FALSE NOT NULL, + + UNIQUE (idBuild, tsExpire) +); +CREATE INDEX BuildsLookupIdx ON Builds (idBuildCategory, iRevision); + + +--- @table VcsRevisions +-- This table is for translating build revisions into commit details. +-- +-- For graphs and test results, it would be useful to translate revisions into +-- dates and maybe provide commit message and the committer. +-- +-- Data is entered exclusively thru one or more batch jobs, so no internal +-- authorship needed. Also, since we're mirroring data from external sources +-- here, the batch job is allowed to update/replace existing records. +-- +-- @todo We we could collect more info from the version control systems, if we +-- believe it's useful and can be presented in a reasonable manner. +-- Getting a list of affected files would be simple (requires +-- a separate table with a M:1 relationship to this table), or try +-- associate a commit to a branch. +-- +CREATE TABLE VcsRevisions ( + --- The version control tree name. + sRepository TEXT NOT NULL, + --- The version control tree revision number. + iRevision INTEGER NOT NULL, + --- When the revision was created (committed). + tsCreated TIMESTAMP WITH TIME ZONE NOT NULL, + --- The name of the committer. + -- @note Not to be confused with uidAuthor and test manager users. + sAuthor TEXT, + --- The commit message. + sMessage TEXT, + + UNIQUE (sRepository, iRevision) +); +CREATE INDEX VcsRevisionsByDate ON VcsRevisions (tsCreated DESC); + + +--- @table VcsBugReferences +-- This is for relating commits to a bug and vice versa. +-- +-- This feature isn't so much for the test manager as a cheap way of extending +-- bug trackers without VCS integration. We just need to parse the commit +-- messages when inserting them into the VcsRevisions table. +-- +-- Same input, updating and history considerations as VcsRevisions. +-- +CREATE TABLE VcsBugReferences ( + --- The version control tree name. + sRepository TEXT NOT NULL, + --- The version control tree revision number. + iRevision INTEGER NOT NULL, + --- The bug tracker identifier - see g_kdBugTrackers in config.py. + sBugTracker CHAR(4) NOT NULL, + --- The bug number in the bug tracker. + lBugNo BIGINT NOT NULL, + + UNIQUE (sRepository, iRevision, sBugTracker, lBugNo) +); +CREATE INDEX VcsBugReferencesLookupIdx ON VcsBugReferences (sBugTracker, lBugNo); + + + + +-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +-- +-- T e s t R e s u l t s +-- +-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + + +--- @table TestResultStrTab +-- String table for the test results. +-- +-- This is a string cache for value names, test names and possible more, that +-- is frequently repated in the test results record for each test run. The +-- purpose is not only to save space, but to make datamining queries faster by +-- giving them integer fields to work on instead of text fields. There may +-- possibly be some benefits on INSERT as well as there are only integer +-- indexes. +-- +-- Nothing is ever deleted from this table. +-- +-- @note Should use a stored procedure to query/insert a string. +-- +CREATE SEQUENCE TestResultStrTabIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE TestResultStrTab ( + --- The ID of this string. + idStr INTEGER PRIMARY KEY DEFAULT NEXTVAL('TestResultStrTabIdSeq'), + --- The string value. + sValue text NOT NULL, + --- Creation time stamp. + tsCreated TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL +); +CREATE UNIQUE INDEX TestResultStrTabNameIdx ON TestResultStrTab (sValue); + +--- Empty string with ID 0. +INSERT INTO TestResultStrTab (idStr, sValue) VALUES (0, ''); + + +--- @type TestStatus_T +-- The status of a test (set / result). +-- +CREATE TYPE TestStatus_T AS ENUM ( + -- Initial status: + 'running', + -- Final statuses: + 'success', + -- Final status: Test didn't fail as such, it was something else. + 'skipped', + 'bad-testbox', + 'aborted', + -- Final status: Test failed. + 'failure', + 'timed-out', + 'rebooted' +); + + +--- @table TestResults +-- Test results - a recursive bundle of joy! +-- +-- A test case will be created when the testdriver calls reporter.testStart and +-- concluded with reporter.testDone. The testdriver (or it subordinates) can +-- use these methods to create nested test results. For IPRT based test cases, +-- RTTestCreate, RTTestInitAndCreate and RTTestSub will both create new test +-- result records, where as RTTestSubDone, RTTestSummaryAndDestroy and +-- RTTestDestroy will conclude records. +-- +-- By concluding is meant updating the status. When the test driver reports +-- success, we check it against reported results. (paranoia strikes again!) +-- +-- Nothing is ever deleted from this table. +-- +-- @note As seen below, several other tables associate data with a +-- test result, and the top most test result is referenced by the +-- test set. +-- +CREATE SEQUENCE TestResultIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE TestResults ( + --- The ID of this test result. + idTestResult INTEGER PRIMARY KEY DEFAULT NEXTVAL('TestResultIdSeq'), + --- The parent test result. + -- This is NULL for the top test result. + idTestResultParent INTEGER REFERENCES TestResults(idTestResult), + --- The test set this result is a part of. + -- Note! This is a foreign key, but we have to add it after TestSets has + -- been created, see further down. + idTestSet INTEGER NOT NULL, + --- Creation time stamp. This may also be the timestamp of when the test started. + tsCreated TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- The elapsed time for this test. + -- This is either reported by the directly (with some sanity checking) or + -- calculated (current_timestamp - created_ts). + -- @todo maybe use a nanosecond field here, check with what + tsElapsed interval DEFAULT NULL, + --- The test name. + idStrName INTEGER REFERENCES TestResultStrTab(idStr) NOT NULL, + --- The error count. + cErrors INTEGER DEFAULT 0 NOT NULL, + --- The test status. + enmStatus TestStatus_T DEFAULT 'running'::TestStatus_T NOT NULL, + --- Nesting depth. + iNestingDepth smallint NOT NULL CHECK (iNestingDepth >= 0 AND iNestingDepth < 16), + -- Make sure errors and status match up. + CONSTRAINT CheckStatusMatchesErrors + CHECK ( (cErrors > 0 AND enmStatus IN ('running'::TestStatus_T, + 'failure'::TestStatus_T, 'timed-out'::TestStatus_T, 'rebooted'::TestStatus_T )) + OR (cErrors = 0 AND enmStatus IN ('running'::TestStatus_T, 'success'::TestStatus_T, + 'skipped'::TestStatus_T, 'aborted'::TestStatus_T, 'bad-testbox'::TestStatus_T)) + ), + -- The following is for the TestResultFailures foreign key. + -- Note! This was added with the name TestResults_idTestResult_idTestSet_key in the tmdb-r16 update script. + UNIQUE (idTestResult, idTestSet) +); + +CREATE INDEX TestResultsSetIdx ON TestResults (idTestSet, idStrName, idTestResult); +CREATE INDEX TestResultsParentIdx ON TestResults (idTestResultParent); +-- The TestResultsNameIdx and TestResultsNameIdx2 are for speeding up the result graph & reporting code. +CREATE INDEX TestResultsNameIdx ON TestResults (idStrName, tsCreated DESC); +CREATE INDEX TestResultsNameIdx2 ON TestResults (idTestResult, idStrName); + +ALTER TABLE TestResultFailures ADD CONSTRAINT TestResultFailures_idTestResult_idTestSet_fkey + FOREIGN KEY (idTestResult, idTestSet) REFERENCES TestResults(idTestResult, idTestSet) MATCH FULL; + + +--- @table TestResultValues +-- Test result values. +-- +-- A testdriver or subordinate may report a test value via +-- reporter.testValue(), while IPRT based test will use RTTestValue and +-- associates. +-- +-- This is an insert only table, no deletes, no updates. +-- +CREATE SEQUENCE TestResultValueIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE TestResultValues ( + --- The ID of this value. + idTestResultValue INTEGER PRIMARY KEY DEFAULT NEXTVAL('TestResultValueIdSeq'), + --- The test result it was reported within. + idTestResult INTEGER REFERENCES TestResults(idTestResult) NOT NULL, + --- The test set this value is a part of (for avoiding joining thru TestResults). + -- Note! This is a foreign key, but we have to add it after TestSets has + -- been created, see further down. + idTestSet INTEGER NOT NULL, + --- Creation time stamp. + tsCreated TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- The name. + idStrName INTEGER REFERENCES TestResultStrTab(idStr) NOT NULL, + --- The value. + lValue bigint NOT NULL, + --- The unit. + -- @todo This is currently not defined properly. Will fix/correlate this + -- with the other places we use unit (IPRT/testdriver/VMMDev). + iUnit smallint NOT NULL CHECK (iUnit >= 0 AND iUnit < 1024) +); + +CREATE INDEX TestResultValuesIdx ON TestResultValues(idTestResult); +-- The TestResultValuesGraphIdx is for speeding up the result graph & reporting code. +CREATE INDEX TestResultValuesGraphIdx ON TestResultValues(idStrName, tsCreated); +-- The TestResultValuesLogIdx is for speeding up the log viewer. +CREATE INDEX TestResultValuesLogIdx ON TestResultValues(idTestSet, tsCreated); + + +--- @table TestResultFiles +-- Test result files. +-- +-- A testdriver or subordinate may report a file by using +-- reporter.addFile() or reporter.addLogFile(). +-- +-- The files stored here as well as the primary log file will be processed by a +-- batch job and compressed if considered compressable. Thus, TM will look for +-- files with a .gz/.bz2 suffix first and then without a suffix. +-- +-- This is an insert only table, no deletes, no updates. +-- +CREATE SEQUENCE TestResultFileId + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE TestResultFiles ( + --- The ID of this file. + idTestResultFile INTEGER PRIMARY KEY DEFAULT NEXTVAL('TestResultFileId'), + --- The test result it was reported within. + idTestResult INTEGER REFERENCES TestResults(idTestResult) NOT NULL, + --- The test set this file is a part of (for avoiding joining thru TestResults). + -- Note! This is a foreign key, but we have to add it after TestSets has + -- been created, see further down. + idTestSet INTEGER NOT NULL, + --- Creation time stamp. + tsCreated TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- The filename relative to TestSets(sBaseFilename) + '-'. + -- The set of valid filename characters should be very limited so that no + -- file system issues can occure either on the TM side or the user when + -- loading the files. Tests trying to use other characters will fail. + -- Valid character regular expession: '^[a-zA-Z0-9_-(){}#@+,.=]*$' + idStrFile INTEGER REFERENCES TestResultStrTab(idStr) NOT NULL, + --- The description. + idStrDescription INTEGER REFERENCES TestResultStrTab(idStr) NOT NULL, + --- The kind of file. + -- For instance: 'log/release/vm', + -- 'screenshot/failure', + -- 'screencapture/failure', + -- 'xmllog/somestuff' + idStrKind INTEGER REFERENCES TestResultStrTab(idStr) NOT NULL, + --- The mime type for the file. + -- For instance: 'text/plain', + -- 'image/png', + -- 'video/webm', + -- 'text/xml' + idStrMime INTEGER REFERENCES TestResultStrTab(idStr) NOT NULL +); + +CREATE INDEX TestResultFilesIdx ON TestResultFiles(idTestResult); +CREATE INDEX TestResultFilesIdx2 ON TestResultFiles(idTestSet, tsCreated DESC); + + +--- @table TestResultMsgs +-- Test result message. +-- +-- A testdriver or subordinate may report a message via the sDetails parameter +-- of the reporter.testFailure() method, while IPRT test cases will use +-- RTTestFailed, RTTestPrintf and their friends. For RTTestPrintf, we will +-- ignore the more verbose message levels since these can also be found in one +-- of the logs. +-- +-- This is an insert only table, no deletes, no updates. +-- +CREATE TYPE TestResultMsgLevel_T AS ENUM ( + 'failure', + 'info' +); +CREATE SEQUENCE TestResultMsgIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE TestResultMsgs ( + --- The ID of this file. + idTestResultMsg INTEGER PRIMARY KEY DEFAULT NEXTVAL('TestResultMsgIdSeq'), + --- The test result it was reported within. + idTestResult INTEGER REFERENCES TestResults(idTestResult) NOT NULL, + --- The test set this file is a part of (for avoiding joining thru TestResults). + -- Note! This is a foreign key, but we have to add it after TestSets has + -- been created, see further down. + idTestSet INTEGER NOT NULL, + --- Creation time stamp. + tsCreated TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- The message string. + idStrMsg INTEGER REFERENCES TestResultStrTab(idStr) NOT NULL, + --- The message level. + enmLevel TestResultMsgLevel_T NOT NULL +); + +CREATE INDEX TestResultMsgsIdx ON TestResultMsgs(idTestResult); +CREATE INDEX TestResultMsgsIdx2 ON TestResultMsgs(idTestSet, tsCreated DESC); + + +--- @table TestSets +-- Test sets / Test case runs. +-- +-- This is where we collect data about test runs. +-- +-- @todo Not entirely sure where the 'test set' term came from. Consider +-- finding something more appropriate. +-- +CREATE SEQUENCE TestSetIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE TestSets ( + --- The ID of this test set. + idTestSet INTEGER PRIMARY KEY DEFAULT NEXTVAL('TestSetIdSeq') NOT NULL, + + --- The test config timestamp, used when reading test config. + tsConfig TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + --- When this test set was scheduled. + -- idGenTestBox is valid at this point. + tsCreated TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + --- When this test completed, i.e. testing stopped. This should only be set once. + tsDone TIMESTAMP WITH TIME ZONE DEFAULT NULL, + --- The current status. + enmStatus TestStatus_T DEFAULT 'running'::TestStatus_T NOT NULL, + + --- The build we're testing. + -- Non-unique foreign key: Builds(idBuild) + idBuild INTEGER NOT NULL, + --- The build category of idBuild when the test started. + -- This is for speeding up graph data collection, i.e. avoid idBuild + -- the WHERE part of the selection. + idBuildCategory INTEGER REFERENCES BuildCategories(idBuildCategory) NOT NULL, + --- The test suite build we're using to do the testing. + -- This is NULL if the test suite zip wasn't referred or if a test suite + -- build source wasn't configured. + -- Non-unique foreign key: Builds(idBuild) + idBuildTestSuite INTEGER DEFAULT NULL, + + --- The exact testbox configuration. + idGenTestBox INTEGER REFERENCES TestBoxes(idGenTestBox) NOT NULL, + --- The testbox ID for joining with (valid: tsStarted). + -- Non-unique foreign key: TestBoxes(idTestBox) + idTestBox INTEGER NOT NULL, + --- The scheduling group ID the test was scheduled thru (valid: tsStarted). + -- Non-unique foreign key: SchedGroups(idSchedGroup) + idSchedGroup INTEGER NOT NULL, + + --- The testgroup (valid: tsConfig). + -- Non-unique foreign key: TestBoxes(idTestGroup) + -- Note! This also gives the member ship entry, since a testcase can only + -- have one membership per test group. + idTestGroup INTEGER NOT NULL, + + --- The exact test case config we executed in this test run. + idGenTestCase INTEGER REFERENCES TestCases(idGenTestCase) NOT NULL, + --- The test case ID for joining with (valid: tsConfig). + -- Non-unique foreign key: TestBoxes(idTestCase) + idTestCase INTEGER NOT NULL, + + --- The arguments (and requirements++) we executed this test case with. + idGenTestCaseArgs INTEGER REFERENCES TestCaseArgs(idGenTestCaseArgs) NOT NULL, + --- The argument variation ID (valid: tsConfig). + -- Non-unique foreign key: TestCaseArgs(idTestCaseArgs) + idTestCaseArgs INTEGER NOT NULL, + + --- The root of the test result tree. + -- @note This will only be NULL early in the transaction setting up the testset. + -- @note If the test reports more than one top level test result, we'll + -- fail the whole test run and let the test developer fix it. + idTestResult INTEGER REFERENCES TestResults(idTestResult) DEFAULT NULL, + + --- The base filename used for storing files related to this test set. + -- This is a path relative to wherever TM is dumping log files. In order + -- to not become a file system test case, we will try not to put too many + -- hundred thousand files in a directory. A simple first approach would + -- be to just use the current date (tsCreated) like this: + -- TM_FILE_DIR/year/month/day/TestSets.idTestSet + -- + -- The primary log file for the test is this name suffixed by '.log'. + -- + -- The files in the testresultfile table gets their full names like this: + -- TM_FILE_DIR/sBaseFilename-testresultfile.id-TestResultStrTab(testresultfile.idStrFilename) + -- + -- @remarks We store this explicitly in case we change the directly layout + -- at some later point. + sBaseFilename text UNIQUE NOT NULL, + + --- The gang member number number, 0 is the leader. + iGangMemberNo SMALLINT DEFAULT 0 NOT NULL CHECK (iGangMemberNo >= 0 AND iGangMemberNo < 1024), + --- The test set of the gang leader, NULL if no gang involved. + -- @note This is set by the gang leader as well, so that we can find all + -- gang members by WHERE idTestSetGangLeader = :id. + idTestSetGangLeader INTEGER REFERENCES TestSets(idTestSet) DEFAULT NULL + +); +CREATE INDEX TestSetsGangIdx ON TestSets (idTestSetGangLeader); +CREATE INDEX TestSetsBoxIdx ON TestSets (idTestBox, idTestResult); +CREATE INDEX TestSetsBuildIdx ON TestSets (idBuild, idTestResult); +CREATE INDEX TestSetsTestCaseIdx ON TestSets (idTestCase, idTestResult); +CREATE INDEX TestSetsTestVarIdx ON TestSets (idTestCaseArgs, idTestResult); +--- The TestSetsDoneCreatedBuildCatIdx is for testbox results, graph options and such. +CREATE INDEX TestSetsDoneCreatedBuildCatIdx ON TestSets (tsDone DESC NULLS FIRST, tsCreated ASC, idBuildCategory); +--- For graphs. +CREATE INDEX TestSetsGraphBoxIdx ON TestSets (idTestBox, tsCreated DESC, tsDone ASC NULLS LAST, idBuildCategory, idTestCase); + +ALTER TABLE TestResults ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL; +ALTER TABLE TestResultValues ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL; +ALTER TABLE TestResultFiles ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL; +ALTER TABLE TestResultMsgs ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL; +ALTER TABLE TestResultFailures ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL; + + + + +-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +-- +-- T e s t M a n g e r P e r s i s t e n t S t o r a g e +-- +-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + +--- @type TestBoxState_T +-- TestBox state. +-- +-- @todo Consider drawing a state diagram for this. +-- +CREATE TYPE TestBoxState_T AS ENUM ( + --- Nothing to do. + -- Prev: testing, gang-cleanup, rebooting, upgrading, + -- upgrading-and-rebooting, doing-special-cmd. + -- Next: testing, gang-gathering, rebooting, upgrading, + -- upgrading-and-rebooting, doing-special-cmd. + 'idle', + --- Executing a test. + -- Prev: idle + -- Next: idle + 'testing', + + -- Gang scheduling statuses: + --- The gathering of a gang. + -- Prev: idle + -- Next: gang-gathering-timedout, gang-testing + 'gang-gathering', + --- The gathering timed out, the testbox needs to cleanup and move on. + -- Prev: gang-gathering + -- Next: idle + -- This is set on all gathered members by the testbox who triggers the + -- timeout. + 'gang-gathering-timedout', + --- The gang scheduling equivalent of 'testing'. + -- Prev: gang-gathering + -- Next: gang-cleanup + 'gang-testing', + --- Waiting for the other gang members to stop testing so that cleanups + -- can be performed and members safely rescheduled. + -- Prev: gang-testing + -- Next: idle + -- + -- There are two resource clean up issues being targeted here: + -- 1. Global resources will be allocated by the leader when he enters the + -- 'gang-gathering' state. If the leader quits and frees the resource + -- while someone is still using it, bad things will happen. Imagine a + -- global resource without any access checks and relies exclusivly on + -- the TM doing its job. + -- 2. TestBox resource accessed by other gang members may also be used in + -- other tests. Should a gang member leave early and embark on a + -- testcase using the same resources, bad things will happen. Example: + -- Live migration. One partner leaves early because it detected some + -- fatal failure, the other one is still trying to connect to him. + -- The testbox is scheduled again on the same live migration testcase, + -- only with different arguments (VM config), it will try migrate using + -- the same TCP ports. Confusion ensues. + -- + -- To figure out whether to remain in this status because someone is + -- still testing: + -- SELECT COUNT(*) FROM TestBoxStatuses, TestSets + -- WHERE TestSets.idTestSetGangLeader = :idGangLeader + -- AND TestSets.idTestBox = TestBoxStatuses.idTestBox + -- AND TestSets.idTestSet = TestBoxStatuses.idTestSet + -- AND TestBoxStatuses.enmState = 'gang-testing'::TestBoxState_T; + 'gang-cleanup', + + -- Command related statuses (all command status changes comes from 'idle' + -- and goes back to 'idle'): + 'rebooting', + 'upgrading', + 'upgrading-and-rebooting', + 'doing-special-cmd' +); + +--- @table TestBoxStatuses +-- Testbox status table. +-- +-- History is not planned on this table. +-- +CREATE TABLE TestBoxStatuses ( + --- The testbox. + idTestBox INTEGER PRIMARY KEY NOT NULL, + --- The testbox generation ID. + idGenTestBox INTEGER REFERENCES TestBoxes(idGenTestBox) NOT NULL, + --- When this status was last updated. + -- This is updated everytime the testbox talks to the test manager, thus it + -- can easily be used to find testboxes which has stopped responding. + -- + -- This is used for timeout calculation during gang-gathering, so in that + -- scenario it won't be updated until the gang is gathered or we time out. + tsUpdated TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- The current state. + enmState TestBoxState_T DEFAULT 'idle'::TestBoxState_T NOT NULL, + --- Reference to the test set + idTestSet INTEGER REFERENCES TestSets(idTestSet), + --- Interal work item number. + -- This is used to pick and prioritize between multiple scheduling groups. + iWorkItem INTEGER DEFAULT 0 NOT NULL +); + + +--- @table GlobalResourceStatuses +-- Global resource status, tracks which test set resources are allocated by. +-- +-- History is not planned on this table. +-- +CREATE TABLE GlobalResourceStatuses ( + --- The resource ID. + -- Non-unique foreign key: GlobalResources(idGlobalRsrc). + idGlobalRsrc INTEGER PRIMARY KEY NOT NULL, + --- The resource owner. + -- @note This is going thru testboxstatus to be able to use the testbox ID + -- as a foreign key. + idTestBox INTEGER REFERENCES TestBoxStatuses(idTestBox) NOT NULL, + --- When the allocation took place. + tsAllocated TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL +); + + +--- @table SchedQueues +-- Scheduler queue. +-- +-- The queues are currently associated with a scheduling group, it could +-- alternative be changed to hook on to a testbox instead. It depends on what +-- kind of scheduling method we prefer. The former method aims at test case +-- thruput, making sacrifices in the hardware distribution area. The latter is +-- more like the old buildbox style testing, making sure that each test case is +-- executed on each testbox. +-- +-- When there are configuration changes, TM will regenerate the scheduling +-- queue for the affected scheduling groups. We do not concern ourselves with +-- trying to continue at the approximately same queue position, we simply take +-- it from the top. +-- +-- When a testbox ask for work, we will open a cursor on the queue and take the +-- first test in the queue that can be executed on that testbox. The test will +-- be moved to the end of the queue (getting a new item_id). +-- +-- If a test is manually changed to the head of the queue, the item will get a +-- item_id which is 1 lower than the head of the queue. Unless someone does +-- this a couple of billion times, we shouldn't have any trouble running out of +-- number space. :-) +-- +-- Manually moving a test to the end of the queue is easy, just get a new +-- 'item_id'. +-- +-- History is not planned on this table. +-- +CREATE SEQUENCE SchedQueueItemIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE SchedQueues ( + --- The scheduling queue (one queue per scheduling group). + -- Non-unique foreign key: SchedGroups(idSchedGroup) + idSchedGroup INTEGER NOT NULL, + --- The scheduler queue entry ID. + -- Lower numbers means early queue position. + idItem INTEGER DEFAULT NEXTVAL('SchedQueueItemIdSeq') NOT NULL, + --- The queue offset. + -- This is used for repositining the queue when recreating it. It can also + -- be used to figure out how jumbled the queue gets after real life has had + -- it's effect on it. + offQueue INTEGER NOT NULL, + --- The test case argument variation to execute. + idGenTestCaseArgs INTEGER REFERENCES TestCaseArgs(idGenTestCaseArgs) NOT NULL, + --- The relevant testgroup. + -- Non-unique foreign key: TestGroups(idTestGroup). + idTestGroup INTEGER NOT NULL, + --- Aggregated test group dependencies (NULL if none). + -- Non-unique foreign key: TestGroups(idTestGroup). + -- See also comments on SchedGroupMembers.idTestGroupPreReq. + aidTestGroupPreReqs INTEGER ARRAY DEFAULT NULL, + --- The scheduling time constraints (see SchedGroupMembers.bmHourlySchedule). + bmHourlySchedule bit(168) DEFAULT NULL, + --- When the queue entry was created and for which config is valid. + -- This is the timestamp that should be used when reading config info. + tsConfig TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + --- When this status was last scheduled. + -- This is set to current_timestamp when moving the entry to the end of the + -- queue. It's initial value is unix-epoch. Not entirely sure if it's + -- useful beyond introspection and non-unique foreign key hacking. + tsLastScheduled TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'epoch' NOT NULL, + + --- This is used in gang scheduling. + idTestSetGangLeader INTEGER REFERENCES TestSets(idTestSet) DEFAULT NULL UNIQUE, + --- The number of gang members still missing. + -- + -- This saves calculating the number of missing members via selects like: + -- SELECT COUNT(*) FROM TestSets WHERE idTestSetGangLeader = :idGang; + -- and + -- SELECT cGangMembers FROM TestCaseArgs WHERE idGenTestCaseArgs = :idTest; + -- to figure out whether to remain in 'gather-gang'::TestBoxState_T. + -- + cMissingGangMembers smallint DEFAULT 1 NOT NULL, + + --- @todo + --- The number of times this has been considered for scheduling. + -- cConsidered SMALLINT DEFAULT 0 NOT NULL, + + PRIMARY KEY (idSchedGroup, idItem) +); +CREATE INDEX SchedQueuesItemIdx ON SchedQueues(idItem); +CREATE INDEX SchedQueuesSchedGroupIdx ON SchedQueues(idSchedGroup); + diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseMap.png b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseMap.png Binary files differnew file mode 100644 index 00000000..861a407d --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseMap.png diff --git a/src/VBox/ValidationKit/testmanager/db/TestManagerVBoxPilot-1.pgsql b/src/VBox/ValidationKit/testmanager/db/TestManagerVBoxPilot-1.pgsql new file mode 100644 index 00000000..3e7a36b3 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/TestManagerVBoxPilot-1.pgsql @@ -0,0 +1,101 @@ +-- $Id: TestManagerVBoxPilot-1.pgsql $ +--- @file +-- VBox Test Manager - Setup for the 1st VBox Pilot. +-- + +-- +-- Copyright (C) 2012-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + + +\set ON_ERROR_STOP 1 +\connect testmanager; + +BEGIN WORK; + +-- +-- The user we assign all the changes too. +-- +INSERT INTO Users (sUsername, sEmail, sFullName, sLoginName) + VALUES ('vbox-pilot-config', 'pilot1@example.org', 'VBox Pilot Configurator', 'vbox-pilot-config'); +\set idUserQuery '(SELECT uid FROM Users WHERE sUsername = \'vbox-pilot-config\')' + +-- +-- Configure a scheduling group with build sources. +-- +INSERT INTO BuildSources (uidAuthor, sName, sProduct, sBranch, asTypes, asOsArches) + VALUES (:idUserQuery, 'VBox trunk builds', 'VirtualBox', 'trunk', ARRAY['release', 'strict'], NULL); + +INSERT INTO BuildSources (uidAuthor, sName, sProduct, sBranch, asTypes, asOsArches) + VALUES (:idUserQuery, 'VBox TestSuite trunk builds', 'VBox TestSuite', 'trunk', ARRAY['release'], NULL); + +INSERT INTO SchedGroups (sName, sDescription, fEnabled, idBuildSrc, idBuildSrcTestSuite) + VALUES ('VirtualBox Trunk', NULL, TRUE, + (SELECT idBuildSrc FROM BuildSources WHERE sName = 'VBox trunk builds'), + (SELECT idBuildSrc FROM BuildSources WHERE sName = 'VBox TestSuite trunk builds') ); +\set idSchedGroupQuery '(SELECT idSchedGroup FROM SchedGroups WHERE sName = \'VirtualBox Trunk\')' + +-- +-- Configure three test groups. +-- +INSERT INTO TestGroups (uidAuthor, sName) + VALUES (:idUserQuery, 'VBox smoketests'); +\set idGrpSmokeQuery '(SELECT idTestGroup FROM TestGroups WHERE sName = \'VBox smoketests\')' +INSERT INTO SchedGroupMembers (idSchedGroup, idTestGroup, uidAuthor, idTestGroupPreReq) + VALUES (:idSchedGroupQuery, :idGrpSmokeQuery, :idUserQuery, NULL); + +INSERT INTO TestGroups (uidAuthor, sName) + VALUES (:idUserQuery, 'VBox general'); +\set idGrpGeneralQuery '(SELECT idTestGroup FROM TestGroups WHERE sName = \'VBox general\')' +INSERT INTO SchedGroupMembers (idSchedGroup, idTestGroup, uidAuthor, idTestGroupPreReq) + VALUES (:idSchedGroupQuery, :idGrpGeneralQuery, :idUserQuery, :idGrpSmokeQuery); + +INSERT INTO TestGroups (uidAuthor, sName) + VALUES (:idUserQuery, 'VBox benchmarks'); +\set idGrpBenchmarksQuery '(SELECT idTestGroup FROM TestGroups WHERE sName = \'VBox benchmarks\')' +INSERT INTO SchedGroupMembers (idSchedGroup, idTestGroup, uidAuthor, idTestGroupPreReq) + VALUES (:idSchedGroupQuery, :idGrpBenchmarksQuery, :idUserQuery, :idGrpGeneralQuery); + + +-- +-- Testcases +-- +INSERT INTO TestCases (uidAuthor, sName, fEnabled, cSecTimeout, sBaseCmd, sTestSuiteZips) + VALUES (:idUserQuery, 'VBox install', TRUE, 600, + 'validationkit/testdriver/vboxinstaller.py --vbox-build @BUILD_BINARIES@ @ACTION@ -- testdriver/base.py @ACTION@', + '@VALIDATIONKIT_ZIP@'); +INSERT INTO TestCaseArgs (idTestCase, uidAuthor, sArgs) + VALUES ((SELECT idTestCase FROM TestCases WHERE sName = 'VBox install'), :idUserQuery, ''); +INSERT INTO TestGroupMembers (idTestGroup, idTestCase, uidAuthor) + VALUES (:idGrpSmokeQuery, (SELECT idTestCase FROM TestCases WHERE sName = 'VBox install'), :idUserQuery); + +COMMIT WORK; + diff --git a/src/VBox/ValidationKit/testmanager/db/gen-sql-comments.py b/src/VBox/ValidationKit/testmanager/db/gen-sql-comments.py new file mode 100755 index 00000000..51e76368 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/gen-sql-comments.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: gen-sql-comments.py $ + +""" +Converts doxygen style comments in SQL script to COMMENT ON statements. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" + +import sys; +import re; + + +def errorMsg(sMsg): + sys.stderr.write('error: %s\n' % (sMsg,)); + return 1; + +class SqlDox(object): + """ + Class for parsing relevant comments out of a pgsql file + and emit COMMENT ON statements from it. + """ + + def __init__(self, oFile, sFilename): + self.oFile = oFile; + self.sFilename = sFilename; + self.iLine = 0; # The current input line number. + self.sComment = None; # The current comment. + self.fCommentComplete = False; # Indicates that the comment has ended. + self.sCommentSqlObj = None; # SQL object indicated by the comment (@table). + self.sOuterSqlObj = None; # Like 'table yyyy' or 'type zzzz'. + self.sPrevSqlObj = None; # Like 'table xxxx'. + + + def error(self, sMsg): + return errorMsg('%s(%d): %s' % (self.sFilename, self.iLine, sMsg,)); + + def dprint(self, sMsg): + sys.stderr.write('debug: %s\n' % (sMsg,)); + return True; + + def resetComment(self): + self.sComment = None; + self.fCommentComplete = False; + self.sCommentSqlObj = None; + + def quoteSqlString(self, s): + return s.replace("'", "''"); + + def commitComment2(self, sSqlObj): + if self.sComment is not None and sSqlObj is not None: + print("COMMENT ON %s IS\n '%s';\n" % (sSqlObj, self.quoteSqlString(self.sComment.strip()))); + self.resetComment(); + return True; + + def commitComment(self): + return self.commitComment2(self.sCommentSqlObj); + + def process(self): + for sLine in self.oFile: + self.iLine += 1; + + sLine = sLine.strip(); + self.dprint('line %d: %s\n' % (self.iLine, sLine)); + if sLine.startswith('--'): + if sLine.startswith('--- '): + # + # New comment. + # The first list may have a @table, @type or similar that we're interested in. + # + self.commitComment(); + + sLine = sLine.lstrip('- '); + if sLine.startswith('@table '): + self.sCommentSqlObj = 'TABLE ' + (sLine[7:]).rstrip(); + self.sComment = ''; + elif sLine.startswith('@type '): + self.sCommentSqlObj = 'TYPE ' + (sLine[6:]).rstrip(); + self.sComment = ''; + elif sLine.startswith('@todo') \ + or sLine.startswith('@file') \ + or sLine.startswith('@page') \ + or sLine.startswith('@name') \ + or sLine.startswith('@{') \ + or sLine.startswith('@}'): + # Ignore. + pass; + elif sLine.startswith('@'): + return self.error('Unknown tag: %s' % (sLine,)); + else: + self.sComment = sLine; + + elif (sLine.startswith('-- ') or sLine == '--') \ + and self.sComment is not None and self.fCommentComplete is False: + # + # Append line to comment. + # + if sLine == '--': + sLine = ''; + else: + sLine = (sLine[3:]); + if self.sComment == '': + self.sComment = sLine; + else: + self.sComment += "\n" + sLine; + + elif sLine.startswith('--< '): + # + # Comment that starts on the same line as the object it describes. + # + sLine = (sLine[4:]).rstrip(); + # => Later/never. + else: + # + # Not a comment that interests us. So, complete any open + # comment and commit it if we know which SQL object it + # applies to. + # + self.fCommentComplete = True; + if self.sCommentSqlObj is not None: + self.commitComment(); + else: + # + # Not a comment. As above, we complete and optionally commit + # any open comment. + # + self.fCommentComplete = True; + if self.sCommentSqlObj is not None: + self.commitComment(); + + # + # Check for SQL (very fuzzy and bad). + # + asWords = sLine.split(' '); + if len(asWords) >= 3 \ + and asWords[0] == 'CREATE': + # CREATE statement. + sType = asWords[1]; + sName = asWords[2]; + if sType == 'UNIQUE' and sName == 'INDEX' and len(asWords) >= 4: + sType = asWords[2]; + sName = asWords[3]; + if sType in ('TABLE', 'TYPE', 'INDEX', 'VIEW'): + self.sOuterSqlObj = sType + ' ' + sName; + self.sPrevSqlObj = self.sOuterSqlObj; + self.dprint('%s' % (self.sOuterSqlObj,)); + self.commitComment2(self.sOuterSqlObj); + elif len(asWords) >= 1 \ + and self.sOuterSqlObj is not None \ + and self.sOuterSqlObj.startswith('TABLE ') \ + and re.search("^(as|al|bm|c|enm|f|i|l|s|ts|uid|uuid)[A-Z][a-zA-Z0-9]*$", asWords[0]) is not None: + # Possibly a column name. + self.sPrevSqlObj = 'COLUMN ' + self.sOuterSqlObj[6:] + '.' + asWords[0]; + self.dprint('column? %s' % (self.sPrevSqlObj)); + self.commitComment2(self.sPrevSqlObj); + + # + # Check for semicolon. + # + if sLine.find(");") >= 0: + self.sOuterSqlObj = None; + + return 0; + + +def usage(): + sys.stderr.write('usage: gen-sql-comments.py <filename.pgsql>\n' + '\n' + 'The output goes to stdout.\n'); + return 0; + + +def main(asArgs): + # Parse the argument. :-) + sInput = None; + if (len(asArgs) != 2): + sys.stderr.write('syntax error: expected exactly 1 argument, a psql file\n'); + usage(); + return 2; + sInput = asArgs[1]; + + # Do the job, outputting to standard output. + try: + oFile = open(sInput, 'r'); + except: + return errorMsg("failed to open '%s' for reading" % (sInput,)); + + # header. + print("-- $" "Id" "$"); + print("--- @file"); + print("-- Autogenerated from %s. Do not edit!" % (sInput,)); + print("--"); + print(""); + for sLine in __copyright__.split('\n'): + if len(sLine) > 0: + print("-- %s" % (sLine,)); + else: + print("--"); + print(""); + print(""); + me = SqlDox(oFile, sInput); + return me.process(); + +sys.exit(main(sys.argv)); + diff --git a/src/VBox/ValidationKit/testmanager/db/partial-db-dump.py b/src/VBox/ValidationKit/testmanager/db/partial-db-dump.py new file mode 100755 index 00000000..73d745db --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/partial-db-dump.py @@ -0,0 +1,392 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: partial-db-dump.py $ +# pylint: disable=line-too-long + +""" +Utility for dumping the last X days of data. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154084 $" + +# Standard python imports +import sys; +import os; +import zipfile; +from optparse import OptionParser; +import xml.etree.ElementTree as ET; + +# Add Test Manager's modules path +g_ksTestManagerDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksTestManagerDir); + +# Test Manager imports +from testmanager.core.db import TMDatabaseConnection; +from common import utils; + + +class PartialDbDump(object): # pylint: disable=too-few-public-methods + """ + Dumps or loads the last X days of database data. + + This is a useful tool when hacking on the test manager locally. You can get + a small sample from the last few days from the production test manager server + without spending hours dumping, downloading, and loading the whole database + (because it is gigantic). + + """ + + def __init__(self): + """ + Parse command line. + """ + + oParser = OptionParser() + oParser.add_option('-q', '--quiet', dest = 'fQuiet', action = 'store_true', + help = 'Quiet execution'); + oParser.add_option('-f', '--filename', dest = 'sFilename', metavar = '<filename>', + default = 'partial-db-dump.zip', help = 'The name of the partial database zip file to write/load.'); + + oParser.add_option('-t', '--tmp-file', dest = 'sTempFile', metavar = '<temp-file>', + default = '/tmp/tm-partial-db-dump.pgtxt', + help = 'Name of temporary file for duping tables. Must be absolute'); + oParser.add_option('--days-to-dump', dest = 'cDays', metavar = '<days>', type = 'int', default = 14, + help = 'How many days to dump (counting backward from current date).'); + oParser.add_option('--load-dump-into-database', dest = 'fLoadDumpIntoDatabase', action = 'store_true', + default = False, help = 'For loading instead of dumping.'); + oParser.add_option('--store', dest = 'fStore', action = 'store_true', + default = False, help = 'Do not compress the zip file.'); + + (self.oConfig, _) = oParser.parse_args(); + + + ## + # Tables dumped in full because they're either needed in full or they normally + # aren't large enough to bother reducing. + kasTablesToDumpInFull = [ + 'Users', + 'BuildBlacklist', + 'BuildCategories', + 'BuildSources', + 'FailureCategories', + 'FailureReasons', + 'GlobalResources', + 'Testcases', + 'TestcaseArgs', + 'TestcaseDeps', + 'TestcaseGlobalRsrcDeps', + 'TestGroups', + 'TestGroupMembers', + 'SchedGroups', + 'SchedGroupMembers', # ? + 'TestBoxesInSchedGroups', # ? + 'SchedQueues', + 'TestResultStrTab', # 36K rows, never mind complicated then. + ]; + + ## + # Tables where we only dump partial info (the TestResult* tables are rather + # gigantic). + kasTablesToPartiallyDump = [ + 'TestBoxes', # 2016-05-25: ca. 641 MB + 'TestSets', # 2016-05-25: ca. 525 MB + 'TestResults', # 2016-05-25: ca. 13 GB + 'TestResultFiles', # 2016-05-25: ca. 87 MB + 'TestResultMsgs', # 2016-05-25: ca. 29 MB + 'TestResultValues', # 2016-05-25: ca. 3728 MB + 'TestResultFailures', + 'Builds', + 'TestBoxStrTab', + 'SystemLog', + 'VcsRevisions', + ]; + + def _doCopyTo(self, sTable, oZipFile, oDb, sSql, aoArgs = None): + """ Does one COPY TO job. """ + print('Dumping %s...' % (sTable,)); + + if aoArgs is not None: + sSql = oDb.formatBindArgs(sSql, aoArgs); + + oFile = open(self.oConfig.sTempFile, 'w'); + oDb.copyExpert(sSql, oFile); + cRows = oDb.getRowCount(); + oFile.close(); + print('... %s rows.' % (cRows,)); + + oZipFile.write(self.oConfig.sTempFile, sTable); + return True; + + def _doDump(self, oDb): + """ Does the dumping of the database. """ + + enmCompression = zipfile.ZIP_DEFLATED; + if self.oConfig.fStore: + enmCompression = zipfile.ZIP_STORED; + oZipFile = zipfile.ZipFile(self.oConfig.sFilename, 'w', enmCompression); + + oDb.begin(); + + # Dumping full tables is simple. + for sTable in self.kasTablesToDumpInFull: + self._doCopyTo(sTable, oZipFile, oDb, 'COPY ' + sTable + ' TO STDOUT WITH (FORMAT TEXT)'); + + # Figure out how far back we need to go. + oDb.execute('SELECT CURRENT_TIMESTAMP - INTERVAL \'%s days\'' % (self.oConfig.cDays,)); + tsEffective = oDb.fetchOne()[0]; + oDb.execute('SELECT CURRENT_TIMESTAMP - INTERVAL \'%s days\'' % (self.oConfig.cDays + 2,)); + tsEffectiveSafe = oDb.fetchOne()[0]; + print('Going back to: %s (safe: %s)' % (tsEffective, tsEffectiveSafe)); + + # We dump test boxes back to the safe timestamp because the test sets may + # use slightly dated test box references and we don't wish to have dangling + # references when loading. + for sTable in [ 'TestBoxes', ]: + self._doCopyTo(sTable, oZipFile, oDb, + 'COPY (SELECT * FROM ' + sTable + ' WHERE tsExpire >= %s) TO STDOUT WITH (FORMAT TEXT)', + (tsEffectiveSafe,)); + + # The test results needs to start with test sets and then dump everything + # releated to them. So, figure the lowest (oldest) test set ID we'll be + # dumping first. + oDb.execute('SELECT idTestSet FROM TestSets WHERE tsCreated >= %s', (tsEffective, )); + idFirstTestSet = 0; + if oDb.getRowCount() > 0: + idFirstTestSet = oDb.fetchOne()[0]; + print('First test set ID: %s' % (idFirstTestSet,)); + + oDb.execute('SELECT MAX(idTestSet) FROM TestSets WHERE tsCreated >= %s', (tsEffective, )); + idLastTestSet = 0; + if oDb.getRowCount() > 0: + idLastTestSet = oDb.fetchOne()[0]; + print('Last test set ID: %s' % (idLastTestSet,)); + + oDb.execute('SELECT MAX(idTestResult) FROM TestResults WHERE tsCreated >= %s', (tsEffective, )); + idLastTestResult = 0; + if oDb.getRowCount() > 0: + idLastTestResult = oDb.fetchOne()[0]; + print('Last test result ID: %s' % (idLastTestResult,)); + + # Tables with idTestSet member. + for sTable in [ 'TestSets', 'TestResults', 'TestResultValues' ]: + self._doCopyTo(sTable, oZipFile, oDb, + 'COPY (SELECT *\n' + ' FROM ' + sTable + '\n' + ' WHERE idTestSet >= %s\n' + ' AND idTestSet <= %s\n' + ' AND idTestResult <= %s\n' + ') TO STDOUT WITH (FORMAT TEXT)' + , ( idFirstTestSet, idLastTestSet, idLastTestResult,)); + + # Tables where we have to go via TestResult. + for sTable in [ 'TestResultFiles', 'TestResultMsgs', 'TestResultFailures' ]: + self._doCopyTo(sTable, oZipFile, oDb, + 'COPY (SELECT it.*\n' + ' FROM ' + sTable + ' it, TestResults tr\n' + ' WHERE tr.idTestSet >= %s\n' + ' AND tr.idTestSet <= %s\n' + ' AND tr.idTestResult <= %s\n' + ' AND tr.tsCreated >= %s\n' # performance hack. + ' AND it.idTestResult = tr.idTestResult\n' + ') TO STDOUT WITH (FORMAT TEXT)' + , ( idFirstTestSet, idLastTestSet, idLastTestResult, tsEffective,)); + + # Tables which goes exclusively by tsCreated using tsEffectiveSafe. + for sTable in [ 'SystemLog', 'VcsRevisions' ]: + self._doCopyTo(sTable, oZipFile, oDb, + 'COPY (SELECT * FROM ' + sTable + ' WHERE tsCreated >= %s) TO STDOUT WITH (FORMAT TEXT)', + (tsEffectiveSafe,)); + + # The builds table. + oDb.execute('SELECT MIN(idBuild), MIN(idBuildTestSuite) FROM TestSets WHERE idTestSet >= %s', (idFirstTestSet,)); + idFirstBuild = 0; + if oDb.getRowCount() > 0: + idFirstBuild = min(oDb.fetchOne()); + print('First build ID: %s' % (idFirstBuild,)); + for sTable in [ 'Builds', ]: + self._doCopyTo(sTable, oZipFile, oDb, + 'COPY (SELECT * FROM ' + sTable + ' WHERE idBuild >= %s) TO STDOUT WITH (FORMAT TEXT)', + (idFirstBuild,)); + + # The test box string table. + self._doCopyTo('TestBoxStrTab', oZipFile, oDb, ''' +COPY (SELECT * FROM TestBoxStrTab WHERE idStr IN ( + ( SELECT 0 + ) UNION ( SELECT idStrComment FROM TestBoxes WHERE tsExpire >= %s + ) UNION ( SELECT idStrCpuArch FROM TestBoxes WHERE tsExpire >= %s + ) UNION ( SELECT idStrCpuName FROM TestBoxes WHERE tsExpire >= %s + ) UNION ( SELECT idStrCpuVendor FROM TestBoxes WHERE tsExpire >= %s + ) UNION ( SELECT idStrDescription FROM TestBoxes WHERE tsExpire >= %s + ) UNION ( SELECT idStrOS FROM TestBoxes WHERE tsExpire >= %s + ) UNION ( SELECT idStrOsVersion FROM TestBoxes WHERE tsExpire >= %s + ) UNION ( SELECT idStrReport FROM TestBoxes WHERE tsExpire >= %s + ) ) ) TO STDOUT WITH (FORMAT TEXT) +''', (tsEffectiveSafe, tsEffectiveSafe, tsEffectiveSafe, tsEffectiveSafe, + tsEffectiveSafe, tsEffectiveSafe, tsEffectiveSafe, tsEffectiveSafe,)); + + oZipFile.close(); + print('Done!'); + return 0; + + def _doLoad(self, oDb): + """ Does the loading of the dumped data into the database. """ + + try: + oZipFile = zipfile.ZipFile(self.oConfig.sFilename, 'r'); + except: + print('error: Dump file "%s" cannot be opened! Use "-f <file>" to specify a file.' % (self.oConfig.sFilename,)); + return 1; + + asTablesInLoadOrder = [ + 'Users', + 'BuildBlacklist', + 'BuildCategories', + 'BuildSources', + 'FailureCategories', + 'FailureReasons', + 'GlobalResources', + 'Testcases', + 'TestcaseArgs', + 'TestcaseDeps', + 'TestcaseGlobalRsrcDeps', + 'TestGroups', + 'TestGroupMembers', + 'SchedGroups', + 'TestBoxStrTab', + 'TestBoxes', + 'SchedGroupMembers', + 'TestBoxesInSchedGroups', + 'SchedQueues', + 'Builds', + 'SystemLog', + 'VcsRevisions', + 'TestResultStrTab', + 'TestSets', + 'TestResults', + 'TestResultFiles', + 'TestResultMsgs', + 'TestResultValues', + 'TestResultFailures', + ]; + assert len(asTablesInLoadOrder) == len(self.kasTablesToDumpInFull) + len(self.kasTablesToPartiallyDump); + + oDb.begin(); + oDb.execute('SET CONSTRAINTS ALL DEFERRED;'); + + print('Checking if the database looks empty...\n'); + for sTable in asTablesInLoadOrder + [ 'TestBoxStatuses', 'GlobalResourceStatuses' ]: + oDb.execute('SELECT COUNT(*) FROM ' + sTable); + cRows = oDb.fetchOne()[0]; + cMaxRows = 0; + if sTable in [ 'SchedGroups', 'TestBoxStrTab', 'TestResultStrTab', 'Users' ]: cMaxRows = 1; + if cRows > cMaxRows: + print('error: Table %s has %u rows which is more than %u - refusing to delete and load.' + % (sTable, cRows, cMaxRows,)); + print('info: Please drop and recreate the database before loading!'); + return 1; + + print('Dropping default table content...\n'); + for sTable in [ 'SchedGroups', 'TestBoxStrTab', 'TestResultStrTab', 'Users']: + oDb.execute('DELETE FROM ' + sTable); + + oDb.execute('ALTER TABLE TestSets DROP CONSTRAINT IF EXISTS TestSets_idTestResult_fkey'); + + for sTable in asTablesInLoadOrder: + print('Loading %s...' % (sTable,)); + oFile = oZipFile.open(sTable); + oDb.copyExpert('COPY ' + sTable + ' FROM STDIN WITH (FORMAT TEXT)', oFile); + cRows = oDb.getRowCount(); + print('... %s rows.' % (cRows,)); + + oDb.execute('ALTER TABLE TestSets ADD FOREIGN KEY (idTestResult) REFERENCES TestResults(idTestResult)'); + oDb.commit(); + + # Correct sequences. + atSequences = [ + ( 'UserIdSeq', 'Users', 'uid' ), + ( 'GlobalResourceIdSeq', 'GlobalResources', 'idGlobalRsrc' ), + ( 'BuildSourceIdSeq', 'BuildSources', 'idBuildSrc' ), + ( 'TestCaseIdSeq', 'TestCases', 'idTestCase' ), + ( 'TestCaseGenIdSeq', 'TestCases', 'idGenTestCase' ), + ( 'TestCaseArgsIdSeq', 'TestCaseArgs', 'idTestCaseArgs' ), + ( 'TestCaseArgsGenIdSeq', 'TestCaseArgs', 'idGenTestCaseArgs' ), + ( 'TestGroupIdSeq', 'TestGroups', 'idTestGroup' ), + ( 'SchedGroupIdSeq', 'SchedGroups', 'idSchedGroup' ), + ( 'TestBoxStrTabIdSeq', 'TestBoxStrTab', 'idStr' ), + ( 'TestBoxIdSeq', 'TestBoxes', 'idTestBox' ), + ( 'TestBoxGenIdSeq', 'TestBoxes', 'idGenTestBox' ), + ( 'FailureCategoryIdSeq', 'FailureCategories', 'idFailureCategory' ), + ( 'FailureReasonIdSeq', 'FailureReasons', 'idFailureReason' ), + ( 'BuildBlacklistIdSeq', 'BuildBlacklist', 'idBlacklisting' ), + ( 'BuildCategoryIdSeq', 'BuildCategories', 'idBuildCategory' ), + ( 'BuildIdSeq', 'Builds', 'idBuild' ), + ( 'TestResultStrTabIdSeq', 'TestResultStrTab', 'idStr' ), + ( 'TestResultIdSeq', 'TestResults', 'idTestResult' ), + ( 'TestResultValueIdSeq', 'TestResultValues', 'idTestResultValue' ), + ( 'TestResultFileId', 'TestResultFiles', 'idTestResultFile' ), + ( 'TestResultMsgIdSeq', 'TestResultMsgs', 'idTestResultMsg' ), + ( 'TestSetIdSeq', 'TestSets', 'idTestSet' ), + ( 'SchedQueueItemIdSeq', 'SchedQueues', 'idItem' ), + ]; + for (sSeq, sTab, sCol) in atSequences: + oDb.execute('SELECT MAX(%s) FROM %s' % (sCol, sTab,)); + idMax = oDb.fetchOne()[0]; + print('%s: idMax=%s' % (sSeq, idMax)); + if idMax is not None: + oDb.execute('SELECT setval(\'%s\', %s)' % (sSeq, idMax)); + + # Last step. + print('Analyzing...'); + oDb.execute('ANALYZE'); + oDb.commit(); + + print('Done!'); + return 0; + + def main(self): + """ + Main function. + """ + oDb = TMDatabaseConnection(); + + if self.oConfig.fLoadDumpIntoDatabase is not True: + rc = self._doDump(oDb); + else: + rc = self._doLoad(oDb); + + oDb.close(); + return 0; + +if __name__ == '__main__': + sys.exit(PartialDbDump().main()); diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r01-builds-1.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r01-builds-1.pgsql new file mode 100644 index 00000000..721d05d8 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r01-builds-1.pgsql @@ -0,0 +1,91 @@ +-- $Id: tmdb-r01-builds-1.pgsql $ +--- @file +-- VBox Test Manager Database - Changed Builds to be historized. +-- + +-- +-- Copyright (C) 2012-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + +DROP TABLE OldBuilds; +DROP TABLE NewBuilds; +DROP INDEX BuildsLookupIdx; + +\set ON_ERROR_STOP 1 + +-- +-- idBuild won't be unique, so it cannot be used directly as a foreign key +-- by TestSets. +-- +ALTER TABLE TestSets + DROP CONSTRAINT TestSets_idBuild_fkey; +ALTER TABLE TestSets + DROP CONSTRAINT TestSets_idBuildTestSuite_fkey; + + +-- +-- Create the table, filling it with the current Builds content. +-- +CREATE TABLE NewBuilds ( + idBuild INTEGER DEFAULT NEXTVAL('BuildIdSeq') NOT NULL, + tsCreated TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + uidAuthor INTEGER DEFAULT NULL, + idBuildCategory INTEGER REFERENCES BuildCategories(idBuildCategory) NOT NULL, + iRevision INTEGER NOT NULL, + sVersion TEXT NOT NULL, + sLogUrl TEXT, + sBinaries TEXT NOT NULL, + fBinariesDeleted BOOLEAN DEFAULT FALSE NOT NULL, + UNIQUE (idBuild, tsExpire) +); + +INSERT INTO NewBuilds (idBuild, tsCreated, tsEffective, uidAuthor, idBuildCategory, iRevision, sVersion, sLogUrl, sBinaries) + SELECT idBuild, tsCreated, tsCreated, uidAuthor, idBuildCategory, iRevision, sVersion, sLogUrl, sBinaries + FROM Builds; +COMMIT; + +-- Switch the tables. +ALTER TABLE Builds RENAME TO OldBuilds; +ALTER TABLE NewBuilds RENAME TO Builds; +COMMIT; + +-- Finally index the table. +CREATE INDEX BuildsLookupIdx ON Builds (idBuildCategory, iRevision); +COMMIT; + +DROP TABLE OldBuilds; +COMMIT; + +-- Fix implicit index name. +ALTER INDEX newbuilds_idbuild_tsexpire_key RENAME TO builds_idbuild_tsexpire_key; + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r02-testboxes-1.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r02-testboxes-1.pgsql new file mode 100644 index 00000000..447a671a --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r02-testboxes-1.pgsql @@ -0,0 +1,194 @@ +-- $Id: tmdb-r02-testboxes-1.pgsql $ +--- @file +-- VBox Test Manager Database - Adds fCpu64BitGuest to TestBoxes +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + +DROP TABLE OldTestBoxes; +DROP TABLE NewTestBoxes; + +\d TestBoxes; + +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 0 + +LOCK TABLE TestBoxStatuses IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestSets IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestBoxes IN ACCESS EXCLUSIVE MODE; + +DROP INDEX TestBoxesUuidIdx; + +-- +-- Rename the original table, drop constrains and foreign key references so we +-- get the right name automatic when creating the new one. +-- +ALTER TABLE TestBoxes RENAME TO OldTestBoxes; + +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_ccpus_check; +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_check; +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_cmbmemory_check; +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_cmbscratch_check; +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_pctscaletimeout_check; + +ALTER TABLE TestBoxStatuses DROP CONSTRAINT TestBoxStatuses_idGenTestBox_fkey; +ALTER TABLE TestSets DROP CONSTRAINT TestSets_idGenTestBox_fkey; + +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_pkey; +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_idgentestbox_key; + +-- +-- Create the new table, filling it with the current TestBoxes content. +-- +CREATE TABLE TestBoxes ( + --- The fixed testbox ID. + -- This is assigned when the testbox is created and will never change. + idTestBox INTEGER DEFAULT NEXTVAL('TestBoxIdSeq') NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- When modified automatically by the testbox, NULL is used. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER DEFAULT NULL, + --- Generation ID for this row. + -- This is primarily for referencing by TestSets. + idGenTestBox INTEGER UNIQUE DEFAULT NEXTVAL('TestBoxGenIdSeq') NOT NULL, + + --- The testbox IP. + -- This is from the webserver point of view and automatically updated on + -- SIGNON. The test setup doesn't permit for IP addresses to change while + -- the testbox is operational, because this will break gang tests. + ip inet NOT NULL, + --- The system or firmware UUID. + -- This uniquely identifies the testbox when talking to the server. After + -- SIGNON though, the testbox will also provide idTestBox and ip to + -- establish its identity beyond doubt. + uuidSystem uuid NOT NULL, + --- The testbox name. + -- Usually similar to the DNS name. + sName text NOT NULL, + --- Optional testbox description. + -- Intended for describing the box as well as making other relevant notes. + sDescription text DEFAULT NULL, + + --- Reference to the scheduling group that this testbox is a member of. + -- Non-unique foreign key: SchedGroups(idSchedGroup) + -- A testbox is always part of a group, the default one nothing else. + idSchedGroup INTEGER DEFAULT 1 NOT NULL, + + --- Indicates whether this testbox is enabled. + -- A testbox gets disabled when we're doing maintenance, debugging a issue + -- that happens only on that testbox, or some similar stuff. This is an + -- alternative to deleting the testbox. + fEnabled BOOLEAN DEFAULT NULL, + + --- The kind of lights-out-management. + enmLomKind LomKind_T DEFAULT 'none'::LomKind_T NOT NULL, + --- The IP adress of the lights-out-management. + -- This can be NULL if enmLomKind is 'none', otherwise it must contain a valid address. + ipLom inet DEFAULT NULL, + + --- Timeout scale factor, given as a percent. + -- This is a crude adjustment of the test case timeout for slower hardware. + pctScaleTimeout smallint DEFAULT 100 NOT NULL CHECK (pctScaleTimeout > 10 AND pctScaleTimeout < 20000), + + --- @name Scheduling properties (reported by testbox script). + -- @{ + --- Same abbrieviations as kBuild, see KBUILD_OSES. + sOs text DEFAULT NULL, + --- Informational, no fixed format. + sOsVersion text DEFAULT NULL, + --- Same as CPUID reports (GenuineIntel, AuthenticAMD, CentaurHauls, ...). + sCpuVendor text DEFAULT NULL, + --- Same as kBuild - x86, amd64, ... See KBUILD_ARCHES. + sCpuArch text DEFAULT NULL, + --- Number of CPUs, CPU cores and CPU threads. + cCpus smallint DEFAULT NULL CHECK (cCpus IS NULL OR cCpus > 0), + --- Set if capable of hardware virtualization. + fCpuHwVirt boolean DEFAULT NULL, + --- Set if capable of nested paging. + fCpuNestedPaging boolean DEFAULT NULL, + --- Set if CPU capable of 64-bit (VBox) guests. + fCpu64BitGuest boolean DEFAULT NULL, + --- Set if chipset with usable IOMMU (VT-d / AMD-Vi). + fChipsetIoMmu boolean DEFAULT NULL, + --- The (approximate) memory size in megabytes (rounded down to nearest 4 MB). + cMbMemory bigint DEFAULT NULL CHECK (cMbMemory IS NULL OR cMbMemory > 0), + --- The amount of scratch space in megabytes (rounded down to nearest 64 MB). + cMbScratch bigint DEFAULT NULL CHECK (cMbScratch IS NULL OR cMbScratch >= 0), + --- @} + + --- The testbox script revision number, serves the purpose of a version number. + -- Probably good to have when scheduling upgrades as well for status purposes. + iTestBoxScriptRev INTEGER DEFAULT 0 NOT NULL, + --- The python sys.hexversion (layed out as of 2.7). + -- Good to know which python versions we need to support. + iPythonHexVersion INTEGER DEFAULT NULL, + + --- Pending command. + -- @note We put it here instead of in TestBoxStatuses to get history. + enmPendingCmd TestBoxCmd_T DEFAULT 'none'::TestBoxCmd_T NOT NULL, + + PRIMARY KEY (idTestBox, tsExpire), + + --- Nested paging requires hardware virtualization. + CHECK (fCpuNestedPaging IS NULL OR (fCpuNestedPaging <> TRUE OR fCpuHwVirt = TRUE)) +); + + +INSERT INTO TestBoxes ( idTestBox, tsEffective, tsExpire, uidAuthor, idGenTestBox, ip, uuidSystem, sName, sDescription, + idSchedGroup, fEnabled, enmLomKind, ipLom, pctScaleTimeout, sOs, sOsVersion, sCpuVendor, sCpuArch, cCpus, fCpuHwVirt, + fCpuNestedPaging, fCpu64BitGuest, fChipsetIoMmu, cMbMemory, cMbScratch, iTestBoxScriptRev, iPythonHexVersion, + enmPendingCmd ) + SELECT idTestBox, tsEffective, tsExpire, uidAuthor, idGenTestBox, ip, uuidSystem, sName, sDescription, + idSchedGroup, fEnabled, enmLomKind, ipLom, pctScaleTimeout, sOs, sOsVersion, sCpuVendor, sCpuArch, cCpus, fCpuHwVirt, + fCpuNestedPaging, TRUE, fChipsetIoMmu, cMbMemory, cMbScratch, iTestBoxScriptRev, iPythonHexVersion, + enmPendingCmd + FROM OldTestBoxes; + +-- Add index. +CREATE UNIQUE INDEX TestBoxesUuidIdx ON TestBoxes (uuidSystem, tsExpire); + +-- Restore foreign key references to the table. +ALTER TABLE TestBoxStatuses ADD CONSTRAINT TestBoxStatuses_idGenTestBox_fkey FOREIGN KEY (idGenTestBox) REFERENCES TestBoxes(idGenTestBox); +ALTER TABLE TestSets ADD CONSTRAINT TestSets_idGenTestBox_fkey FOREIGN KEY (idGenTestBox) REFERENCES TestBoxes(idGenTestBox); + +-- Drop the old table. +DROP TABLE OldTestBoxes; + +COMMIT; + +\d TestBoxes; + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r03-teststatus-1.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r03-teststatus-1.pgsql new file mode 100644 index 00000000..760666e3 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r03-teststatus-1.pgsql @@ -0,0 +1,48 @@ +-- $Id: tmdb-r03-teststatus-1.pgsql $ +--- @file +-- VBox Test Manager Database - Adds 'bad-testbox', 'aborted', and 'timeout' to TestStatus_T. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 1 + +\dT+ TestStatus_T + +ALTER TYPE TestStatus_T ADD VALUE 'bad-testbox' BEFORE 'failure'; +ALTER TYPE TestStatus_T ADD VALUE 'aborted' BEFORE 'failure'; +ALTER TYPE TestStatus_T ADD VALUE 'timed-out' AFTER 'failure'; + +\dT+ TestStatus_T + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r04-teststatus-2.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r04-teststatus-2.pgsql new file mode 100644 index 00000000..91aa9c73 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r04-teststatus-2.pgsql @@ -0,0 +1,46 @@ +-- $Id: tmdb-r04-teststatus-2.pgsql $ +--- @file +-- VBox Test Manager Database - Adds 'rebooted' to TestStatus_T. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 1 + +\dT+ TestStatus_T + +ALTER TYPE TestStatus_T ADD VALUE 'rebooted' AFTER 'timed-out'; + +\dT+ TestStatus_T + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r05-teststatus-3.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r05-teststatus-3.pgsql new file mode 100644 index 00000000..02b57df7 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r05-teststatus-3.pgsql @@ -0,0 +1,54 @@ +-- $Id: tmdb-r05-teststatus-3.pgsql $ +--- @file +-- VBox Test Manager Database - Adds 'rebooted' to TestStatus_T. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 0 + +\d+ TestResults + +ALTER TABLE TestResults + DROP CONSTRAINT CheckStatusMatchesErrors; +ALTER TABLE TestResults + ADD CONSTRAINT CheckStatusMatchesErrors + CHECK ( (cErrors > 0 AND enmStatus IN ('running'::TestStatus_T, + 'failure'::TestStatus_T, 'timed-out'::TestStatus_T, 'rebooted'::TestStatus_T )) + OR (cErrors = 0 AND enmStatus IN ('running'::TestStatus_T, 'success'::TestStatus_T, + 'skipped'::TestStatus_T, 'aborted'::TestStatus_T, 'bad-testbox'::TestStatus_T)) + ); +COMMIT; +\d+ TestResults + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r06-buildsources-1.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r06-buildsources-1.pgsql new file mode 100644 index 00000000..9db6350a --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r06-buildsources-1.pgsql @@ -0,0 +1,46 @@ +-- $Id: tmdb-r06-buildsources-1.pgsql $ +--- @file +-- VBox Test Manager Database - Adds cMaxSecondsOld to BuildSources. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 1 + +\d+ buildsources + +ALTER TABLE BuildSources ADD COLUMN cSecMaxAge INTEGER DEFAULT NULL; + +\d+ buildsources + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r07-testresults-1.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r07-testresults-1.pgsql new file mode 100644 index 00000000..9194be88 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r07-testresults-1.pgsql @@ -0,0 +1,47 @@ +-- $Id: tmdb-r07-testresults-1.pgsql $ +--- @file +-- VBox Test Manager Database - Adds an index to TestResults. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 0 + +\d+ TestResults + +CREATE INDEX TestResultsNameIdx ON TestResults (idStrName, idTestResult, tsCreated); +COMMIT; + +\d+ TestResults + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r08-testresultvalues-1.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r08-testresultvalues-1.pgsql new file mode 100644 index 00000000..c24d030d --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r08-testresultvalues-1.pgsql @@ -0,0 +1,47 @@ +-- $Id: tmdb-r08-testresultvalues-1.pgsql $ +--- @file +-- VBox Test Manager Database - Adds an index to TestResultValues. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 0 + +\d+ TestResultValues + +CREATE INDEX TestResultValuesNameIdx ON TestResultValues (idStrName, tsCreated); +COMMIT; + +\d+ TestResultValues + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r09-testsets-1.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r09-testsets-1.pgsql new file mode 100644 index 00000000..cd4f06e0 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r09-testsets-1.pgsql @@ -0,0 +1,48 @@ +-- $Id: tmdb-r09-testsets-1.pgsql $ +--- @file +-- VBox Test Manager Database - Adds two indexes to TestSets. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 0 + +\d+ TestSets + +CREATE INDEX TestSetsCreated ON TestSets (tsCreated); +CREATE INDEX TestSetsDone ON TestSets (tsDone); +COMMIT; + +\d+ TestSets + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r10-testresultvalues-2.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r10-testresultvalues-2.pgsql new file mode 100644 index 00000000..b18b02b9 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r10-testresultvalues-2.pgsql @@ -0,0 +1,111 @@ +-- $Id: tmdb-r10-testresultvalues-2.pgsql $ +--- @file +-- VBox Test Manager Database - Adds an idTestSet to TestResultValues. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + +-- +-- Cleanup after failed runs. +-- +DROP TABLE NewTestResultValues; + +-- +-- Drop all indexes (might already be dropped). +-- +DROP INDEX TestResultValuesIdx; +DROP INDEX TestResultValuesNameIdx; + +-- Die on error from now on. +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 0 + +\d+ TestResultValues; + +-- +-- Create the new version of the table and filling with the content of the old. +-- +CREATE TABLE NewTestResultValues ( + --- The ID of this value. + idTestResultValue INTEGER DEFAULT NEXTVAL('TestResultValueIdSeq'), -- PRIMARY KEY + --- The test result it was reported within. + idTestResult INTEGER NOT NULL, -- REFERENCES TestResults(idTestResult) NOT NULL, + --- The test result it was reported within. + idTestSet INTEGER NOT NULL, -- REFERENCES TestSets(idTestSet) NOT NULL, + --- Creation time stamp. + tsCreated TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- The name. + idStrName INTEGER NOT NULL, -- REFERENCES TestResultStrTab(idStr) NOT NULL, + --- The value. + lValue bigint NOT NULL, + --- The unit. + -- @todo This is currently not defined properly. Will fix/correlate this + -- with the other places we use unit (IPRT/testdriver/VMMDev). + iUnit smallint NOT NULL --CHECK (iUnit >= 0 AND iUnit < 1024) +); +COMMIT; +\d+ NewTestResultValues + +-- Note! Using left out join here to speed up things (no hashing). +SELECT COUNT(*) FROM TestResultValues a LEFT OUTER JOIN TestResults b ON (a.idTestResult = b.idTestResult); +SELECT COUNT(*) FROM TestResultValues; + +INSERT INTO NewTestResultValues (idTestResultValue, idTestResult, idTestSet, tsCreated, idStrName, lValue, iUnit) + SELECT a.idTestResultValue, a.idTestResult, b.idTestSet, a.tsCreated, a.idStrName, a.lValue, a.iUnit + FROM TestResultValues a LEFT OUTER JOIN TestResults b ON (a.idTestResult = b.idTestResult); +COMMIT; +SELECT COUNT(*) FROM NewTestResultValues; + +-- Switch the tables. +ALTER TABLE TestResultValues RENAME TO OldTestResultValues; +ALTER TABLE NewTestResultValues RENAME TO TestResultValues; +COMMIT; + +-- Index the table. +CREATE INDEX TestResultValuesIdx ON TestResultValues(idTestResult); +CREATE INDEX TestResultValuesNameIdx ON TestResultValues(idStrName, tsCreated); +COMMIT; + +-- Drop the old table. +DROP TABLE OldTestResultValues; +COMMIT; + +-- Add the constraints constraint. +ALTER TABLE TestResultValues ADD CONSTRAINT TestResultValues_iUnit_Check CHECK (iUnit >= 0 AND iUnit < 1024); +ALTER TABLE TestResultValues ADD PRIMARY KEY (idTestResultValue); +ALTER TABLE TestResultValues ADD FOREIGN KEY (idStrName) REFERENCES TestResultstrtab(idStr); +ALTER TABLE TestResultValues ADD FOREIGN KEY (idTestResult) REFERENCES TestResults(idTestResult); +ALTER TABLE TestResultValues ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet); +COMMIT; + +\d+ TestResultValues; + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r11-testsets-2.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r11-testsets-2.pgsql new file mode 100644 index 00000000..4c6e7b6d --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r11-testsets-2.pgsql @@ -0,0 +1,214 @@ +-- $Id: tmdb-r11-testsets-2.pgsql $ +--- @file +-- VBox Test Manager Database - Adds an idBuildCategories to TestSets. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + +-- +-- Drop all indexes (might already be dropped). +-- +DROP INDEX TestSetsGangIdx; +DROP INDEX TestSetsBoxIdx; +DROP INDEX TestSetsBuildIdx; +DROP INDEX TestSetsTestCaseIdx; +DROP INDEX TestSetsTestVarIdx; +DROP INDEX TestSetsCreated; +DROP INDEX TestSetsDone; + +-- +-- Drop foreign keys on this table. +-- +ALTER TABLE SchedQueues DROP CONSTRAINT SchedQueues_idTestSetGangLeader_fkey; +ALTER TABLE TestBoxStatuses DROP CONSTRAINT TestBoxStatuses_idTestSet_fkey; +ALTER TABLE TestResults DROP CONSTRAINT idTestSetFk; -- old name +ALTER TABLE TestResults DROP CONSTRAINT TestResults_idTestSet_fkey; +ALTER TABLE TestResultValues DROP CONSTRAINT TestResultValues_idTestSet_fkey; + +-- +-- Cleanup after failed runs. +-- +DROP TABLE NewTestSets; +DROP TABLE OldTestSets; + +-- Die on error from now on. +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 0 + +\d+ TestSets; + +-- +-- Create the new version of the table and filling with the content of the old. +-- +CREATE TABLE NewTestSets ( + --- The ID of this test set. + idTestSet INTEGER DEFAULT NEXTVAL('TestSetIdSeq') NOT NULL, -- PRIMARY KEY + + --- The test config timestamp, used when reading test config. + tsConfig TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + --- When this test set was scheduled. + -- idGenTestBox is valid at this point. + tsCreated TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + --- When this test completed, i.e. testing stopped. This should only be set once. + tsDone TIMESTAMP WITH TIME ZONE DEFAULT NULL, + --- The current status. + enmStatus TestStatus_T DEFAULT 'running'::TestStatus_T NOT NULL, + + --- The build we're testing. + -- Non-unique foreign key: Builds(idBuild) + idBuild INTEGER NOT NULL, + --- The build category of idBuild when the test started. + -- This is for speeding up graph data collection, i.e. avoid idBuild + -- the WHERE part of the selection. + idBuildCategory INTEGER , -- NOT NULL REFERENCES BuildCategories(idBuildCategory) + --- The test suite build we're using to do the testing. + -- This is NULL if the test suite zip wasn't referred or if a test suite + -- build source wasn't configured. + -- Non-unique foreign key: Builds(idBuild) + idBuildTestSuite INTEGER DEFAULT NULL, + + --- The exact testbox configuration. + idGenTestBox INTEGER NOT NULL, -- REFERENCES TestBoxes(idGenTestBox) + --- The testbox ID for joining with (valid: tsStarted). + -- Non-unique foreign key: TestBoxes(idTestBox) + idTestBox INTEGER NOT NULL, + + --- The testgroup (valid: tsConfig). + -- Non-unique foreign key: TestBoxes(idTestGroup) + -- Note! This also gives the member ship entry, since a testcase can only + -- have one membership per test group. + idTestGroup INTEGER NOT NULL, + + --- The exact test case config we executed in this test run. + idGenTestCase INTEGER NOT NULL, -- REFERENCES TestCases(idGenTestCase) + --- The test case ID for joining with (valid: tsConfig). + -- Non-unique foreign key: TestBoxes(idTestCase) + idTestCase INTEGER NOT NULL, + + --- The arguments (and requirements++) we executed this test case with. + idGenTestCaseArgs INTEGER NOT NULL, -- REFERENCES TestCaseArgs(idGenTestCaseArgs) + --- The argument variation ID (valid: tsConfig). + -- Non-unique foreign key: TestCaseArgs(idTestCaseArgs) + idTestCaseArgs INTEGER NOT NULL, + + --- The root of the test result tree. + -- @note This will only be NULL early in the transaction setting up the testset. + -- @note If the test reports more than one top level test result, we'll + -- fail the whole test run and let the test developer fix it. + idTestResult INTEGER DEFAULT NULL, -- REFERENCES TestResults(idTestResult) + + --- The base filename used for storing files related to this test set. + -- This is a path relative to wherever TM is dumping log files. In order + -- to not become a file system test case, we will try not to put too many + -- hundred thousand files in a directory. A simple first approach would + -- be to just use the current date (tsCreated) like this: + -- TM_FILE_DIR/year/month/day/TestSets.idTestSet + -- + -- The primary log file for the test is this name suffixed by '.log'. + -- + -- The files in the testresultfile table gets their full names like this: + -- TM_FILE_DIR/sBaseFilename-testresultfile.id-TestResultStrTab(testresultfile.idStrFilename) + -- + -- @remarks We store this explicitly in case we change the directly layout + -- at some later point. + sBaseFilename text UNIQUE NOT NULL, + + --- The gang member number number, 0 is the leader. + iGangMemberNo SMALLINT DEFAULT 0 NOT NULL, --CHECK (iGangMemberNo >= 0 AND iGangMemberNo < 1024), + --- The test set of the gang leader, NULL if no gang involved. + -- @note This is set by the gang leader as well, so that we can find all + -- gang members by WHERE idTestSetGangLeader = :id. + idTestSetGangLeader INTEGER DEFAULT NULL -- REFERENCES TestSets(idTestSet) + +); +COMMIT; +\d+ NewTestSets + +-- Note! Using left out join here to speed up things (no hashing). +SELECT COUNT(*) FROM TestSets a LEFT OUTER JOIN Builds b ON (a.idBuild = b.idBuild AND b.tsExpire = 'infinity'::TIMESTAMP); +SELECT COUNT(*) FROM TestSets; + +INSERT INTO NewTestSets (idTestSet, tsConfig, tsCreated, tsDone, enmStatus, idBuild, idBuildCategory, idBuildTestSuite, + idGenTestBox, idTestBox, idTestGroup, idGenTestCase, idTestCase, idGenTestCaseArgs, idTestCaseArgs, + idTestResult, sBaseFilename, iGangMemberNo, idTestSetGangLeader ) + SELECT a.idTestSet, a.tsConfig, a.tsCreated, tsDone, a.enmStatus, a.idBuild, b.idBuildCategory, a.idBuildTestSuite, + a.idGenTestBox, a.idTestBox, a.idTestGroup, a.idGenTestCase, a.idTestCase, a.idGenTestCaseArgs, a.idTestCaseArgs, + a.idTestResult, a.sBaseFilename, a.iGangMemberNo, a.idTestSetGangLeader + FROM TestSets a LEFT OUTER JOIN Builds b ON (a.idBuild = b.idBuild AND b.tsExpire = 'infinity'::TIMESTAMP); +COMMIT; +SELECT COUNT(*) FROM NewTestSets; + +-- Note! 2-3 builds are missing from the Builds table, so fudge it. +UPDATE NewTestSets + SET idBuildCategory = 1 + WHERE idBuildCategory IS NULL; + +-- Switch the tables. +ALTER TABLE TestSets RENAME TO OldTestSets; +ALTER TABLE NewTestSets RENAME TO TestSets; +COMMIT; + +-- Index the table. +CREATE INDEX TestSetsGangIdx ON TestSets (idTestSetGangLeader); +CREATE INDEX TestSetsBoxIdx ON TestSets (idTestBox, idTestResult); +CREATE INDEX TestSetsBuildIdx ON TestSets (idBuild, idTestResult); +CREATE INDEX TestSetsTestCaseIdx ON TestSets (idTestCase, idTestResult); +CREATE INDEX TestSetsTestVarIdx ON TestSets (idTestCaseArgs, idTestResult); +CREATE INDEX TestSetsCreated ON TestSets (tsCreated); +CREATE INDEX TestSetsDone ON TestSets (tsDone); +COMMIT; + +-- Drop the old table. +DROP TABLE OldTestSets; +COMMIT; + +-- Add the constraints constraint. +ALTER TABLE TestSets ADD CONSTRAINT TestSets_iGangMemberNo_Check CHECK (iGangMemberNo >= 0 AND iGangMemberNo < 1024); +ALTER TABLE TestSets ADD PRIMARY KEY (idTestSet); +ALTER TABLE TestSets ADD FOREIGN KEY (idBuildCategory) REFERENCES BuildCategories(idBuildCategory); +ALTER TABLE TestSets ADD FOREIGN KEY (idGenTestBox) REFERENCES TestBoxes(idGenTestBox); +ALTER TABLE TestSets ADD FOREIGN KEY (idGenTestCase) REFERENCES TestCases(idGenTestCase); +ALTER TABLE TestSets ADD FOREIGN KEY (idGenTestCaseArgs) REFERENCES TestCaseArgs(idGenTestCaseArgs); +ALTER TABLE TestSets ADD FOREIGN KEY (idTestResult) REFERENCES TestResults(idTestResult); +ALTER TABLE TestSets ADD FOREIGN KEY (idTestSetGangLeader) REFERENCES TestSets(idTestSet); +COMMIT; + +-- Restore foreign keys. +LOCK TABLE SchedQueues, TestBoxStatuses, TestResults, TestResultValues IN EXCLUSIVE MODE; +ALTER TABLE SchedQueues ADD FOREIGN KEY (idTestSetGangLeader) REFERENCES TestSets(idTestSet) MATCH FULL; +ALTER TABLE TestBoxStatuses ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL; +ALTER TABLE TestResults ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL; +ALTER TABLE TestResultValues ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL; +COMMIT; + +\d+ TestSets; + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r12-testresultvalues-3-testsets-3.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r12-testresultvalues-3-testsets-3.pgsql new file mode 100644 index 00000000..ef375340 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r12-testresultvalues-3-testsets-3.pgsql @@ -0,0 +1,58 @@ +-- $Id: tmdb-r12-testresultvalues-3-testsets-3.pgsql $ +--- @file +-- VBox Test Manager Database - Graph related optimizations for TestResultValues and TestSets. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 0 + +\d+ TestResultValues + +-- Rename index to better show it's purpose. +ALTER INDEX TestResultValuesNameIdx RENAME TO TestResultValuesGraphIdx; +COMMIT; + +-- Combine the tsCreated and tsDone indexes. +DROP INDEX TestSetsCreated; +DROP INDEX TestSetsDone; +CREATE INDEX TestSetsCreatedDoneIdx ON TestSets (tsCreated, tsDone); +COMMIT; + +-- Create index for graph. +CREATE INDEX TestSetsGraphBoxIdx ON TestSets (idTestBox, tsCreated, tsDone, idBuildCategory, idTestCase); +COMMIT; + +\d+ TestResultValues + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r13-buildcategories-1-vcsrevisions-1.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r13-buildcategories-1-vcsrevisions-1.pgsql new file mode 100644 index 00000000..e00c548a --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r13-buildcategories-1-vcsrevisions-1.pgsql @@ -0,0 +1,134 @@ +-- $Id: tmdb-r13-buildcategories-1-vcsrevisions-1.pgsql $ +--- @file +-- VBox Test Manager Database - Adds an sRepository to Builds and creates a new VcsRepositories table. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + +-- +-- Cleanup after failed runs. +-- +DROP TABLE NewBuildCategories; +DROP TABLE OldBuildCategories; + +-- +-- Drop foreign keys on this table. +-- +ALTER TABLE Builds DROP CONSTRAINT NewBuilds_idBuildCategory_fkey; +ALTER TABLE Builds DROP CONSTRAINT Builds_idBuildCategory_fkey; +ALTER TABLE TestSets DROP CONSTRAINT TestSets_idBuildCategory_fkey; + +-- Die on error from now on. +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 0 + +\d+ BuildCategories; + +-- +-- Create the new version of the table and filling with the content of the old. +-- +CREATE TABLE NewBuildCategories ( + --- The build type identifier. + idBuildCategory INTEGER PRIMARY KEY DEFAULT NEXTVAL('BuildCategoryIdSeq') NOT NULL, + --- Product. + -- The product name. For instance 'VBox' or 'VBoxTestSuite'. + sProduct TEXT NOT NULL, + --- The version control repository name. + sRepository TEXT NOT NULL, + --- The branch name (in the version control system). + sBranch TEXT NOT NULL, + --- The build type. + -- See KBUILD_BLD_TYPES in kBuild for a list of standard build types. + sType TEXT NOT NULL, + --- Array of the 'sOs.sCpuArch' supported by the build. + -- See KBUILD_OSES in kBuild for a list of standard target OSes, and + -- KBUILD_ARCHES for a list of standard architectures. + -- + -- @remarks 'os-agnostic' is used if the build doesn't really target any + -- specific OS or if it targets all applicable OSes. + -- 'noarch' is used if the build is architecture independent or if + -- all applicable architectures are handled. + -- Thus, 'os-agnostic.noarch' will run on all build boxes. + -- + -- @note The array shall be sorted ascendingly to prevent unnecessary duplicates! + -- + asOsArches TEXT ARRAY NOT NULL, + + UNIQUE (sProduct, sRepository, sBranch, sType, asOsArches) +); +COMMIT; +\d+ NewBuildCategories + +INSERT INTO NewBuildCategories (idBuildCategory, sProduct, sRepository, sBranch, sType, asOsArches) + SELECT idBuildCategory, sProduct, 'vbox', sBranch, sType, asOsArches + FROM BuildCategories +COMMIT; + +-- Switch the tables. +ALTER TABLE BuildCategories RENAME TO OldBuildCategories; +ALTER TABLE NewBuildCategories RENAME TO BuildCategories; +COMMIT; + +-- Drop the old table. +DROP TABLE OldBuildCategories; +COMMIT; + +-- Restore foreign keys. +LOCK TABLE Builds, TestSets; +ALTER TABLE Builds ADD FOREIGN KEY (idBuildCategory) REFERENCES BuildCategories(idBuildCategory); +ALTER TABLE TestSets ADD FOREIGN KEY (idBuildCategory) REFERENCES BuildCategories(idBuildCategory); +COMMIT; + +\d+ BuildCategories; + + +-- +-- Create the new VcsRevisions table. +-- +CREATE TABLE VcsRevisions ( + --- The version control tree name. + sRepository TEXT NOT NULL, + --- The version control tree revision number. + iRevision INTEGER NOT NULL, + --- When the revision was created (committed). + tsCreated TIMESTAMP WITH TIME ZONE NOT NULL, + --- The name of the committer. + -- @note Not to be confused with uidAuthor and test manager users. + sAuthor TEXT, + --- The commit message. + sMessage TEXT, + + UNIQUE (sRepository, iRevision) +); +COMMIT; +\d+ VcsRevisions; + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r14-testboxes-2.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r14-testboxes-2.pgsql new file mode 100644 index 00000000..26a05b66 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r14-testboxes-2.pgsql @@ -0,0 +1,201 @@ +-- $Id: tmdb-r14-testboxes-2.pgsql $ +--- @file +-- VBox Test Manager Database - Adds sCpuName, lCpuRevision and sReport to TestBoxes. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + +DROP TABLE OldTestBoxes; +DROP TABLE NewTestBoxes; + +\d TestBoxes; + +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 0 + +LOCK TABLE TestBoxStatuses IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestSets IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestBoxes IN ACCESS EXCLUSIVE MODE; + +DROP INDEX TestBoxesUuidIdx; + +-- +-- Rename the original table, drop constrains and foreign key references so we +-- get the right name automatic when creating the new one. +-- +ALTER TABLE TestBoxes RENAME TO OldTestBoxes; + +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_ccpus_check; +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_check; +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_cmbmemory_check; +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_cmbscratch_check; +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_pctscaletimeout_check; + +ALTER TABLE TestBoxStatuses DROP CONSTRAINT TestBoxStatuses_idGenTestBox_fkey; +ALTER TABLE TestSets DROP CONSTRAINT TestSets_idGenTestBox_fkey; + +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_pkey; +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_idgentestbox_key; + +-- +-- Create the new table, filling it with the current TestBoxes content. +-- +CREATE TABLE TestBoxes ( + --- The fixed testbox ID. + -- This is assigned when the testbox is created and will never change. + idTestBox INTEGER DEFAULT NEXTVAL('TestBoxIdSeq') NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- When modified automatically by the testbox, NULL is used. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER DEFAULT NULL, + --- Generation ID for this row. + -- This is primarily for referencing by TestSets. + idGenTestBox INTEGER UNIQUE DEFAULT NEXTVAL('TestBoxGenIdSeq') NOT NULL, + + --- The testbox IP. + -- This is from the webserver point of view and automatically updated on + -- SIGNON. The test setup doesn't permit for IP addresses to change while + -- the testbox is operational, because this will break gang tests. + ip inet NOT NULL, + --- The system or firmware UUID. + -- This uniquely identifies the testbox when talking to the server. After + -- SIGNON though, the testbox will also provide idTestBox and ip to + -- establish its identity beyond doubt. + uuidSystem uuid NOT NULL, + --- The testbox name. + -- Usually similar to the DNS name. + sName text NOT NULL, + --- Optional testbox description. + -- Intended for describing the box as well as making other relevant notes. + sDescription text DEFAULT NULL, + + --- Reference to the scheduling group that this testbox is a member of. + -- Non-unique foreign key: SchedGroups(idSchedGroup) + -- A testbox is always part of a group, the default one nothing else. + idSchedGroup INTEGER DEFAULT 1 NOT NULL, + + --- Indicates whether this testbox is enabled. + -- A testbox gets disabled when we're doing maintenance, debugging a issue + -- that happens only on that testbox, or some similar stuff. This is an + -- alternative to deleting the testbox. + fEnabled BOOLEAN DEFAULT NULL, + + --- The kind of lights-out-management. + enmLomKind LomKind_T DEFAULT 'none'::LomKind_T NOT NULL, + --- The IP adress of the lights-out-management. + -- This can be NULL if enmLomKind is 'none', otherwise it must contain a valid address. + ipLom inet DEFAULT NULL, + + --- Timeout scale factor, given as a percent. + -- This is a crude adjustment of the test case timeout for slower hardware. + pctScaleTimeout smallint DEFAULT 100 NOT NULL CHECK (pctScaleTimeout > 10 AND pctScaleTimeout < 20000), + + --- @name Scheduling properties (reported by testbox script). + -- @{ + --- Same abbrieviations as kBuild, see KBUILD_OSES. + sOs text DEFAULT NULL, + --- Informational, no fixed format. + sOsVersion text DEFAULT NULL, + --- Same as CPUID reports (GenuineIntel, AuthenticAMD, CentaurHauls, ...). + sCpuVendor text DEFAULT NULL, + --- Same as kBuild - x86, amd64, ... See KBUILD_ARCHES. + sCpuArch text DEFAULT NULL, + --- The CPU name if available. + sCpuName text DEFAULT NULL, + --- Number identifying the CPU family/model/stepping/whatever. + -- For x86 and AMD64 type CPUs, this will on the following format: + -- (EffFamily << 24) | (EffModel << 8) | Stepping. + lCpuRevision bigint DEFAULT NULL, + --- Number of CPUs, CPU cores and CPU threads. + cCpus smallint DEFAULT NULL CHECK (cCpus IS NULL OR cCpus > 0), + --- Set if capable of hardware virtualization. + fCpuHwVirt boolean DEFAULT NULL, + --- Set if capable of nested paging. + fCpuNestedPaging boolean DEFAULT NULL, + --- Set if CPU capable of 64-bit (VBox) guests. + fCpu64BitGuest boolean DEFAULT NULL, + --- Set if chipset with usable IOMMU (VT-d / AMD-Vi). + fChipsetIoMmu boolean DEFAULT NULL, + --- The (approximate) memory size in megabytes (rounded down to nearest 4 MB). + cMbMemory bigint DEFAULT NULL CHECK (cMbMemory IS NULL OR cMbMemory > 0), + --- The amount of scratch space in megabytes (rounded down to nearest 64 MB). + cMbScratch bigint DEFAULT NULL CHECK (cMbScratch IS NULL OR cMbScratch >= 0), + --- Free form hardware and software report field. + sReport text DEFAULT NULL, + --- @} + + --- The testbox script revision number, serves the purpose of a version number. + -- Probably good to have when scheduling upgrades as well for status purposes. + iTestBoxScriptRev INTEGER DEFAULT 0 NOT NULL, + --- The python sys.hexversion (layed out as of 2.7). + -- Good to know which python versions we need to support. + iPythonHexVersion INTEGER DEFAULT NULL, + + --- Pending command. + -- @note We put it here instead of in TestBoxStatuses to get history. + enmPendingCmd TestBoxCmd_T DEFAULT 'none'::TestBoxCmd_T NOT NULL, + + PRIMARY KEY (idTestBox, tsExpire), + + --- Nested paging requires hardware virtualization. + CHECK (fCpuNestedPaging IS NULL OR (fCpuNestedPaging <> TRUE OR fCpuHwVirt = TRUE)) +); + +INSERT INTO TestBoxes ( idTestBox, tsEffective, tsExpire, uidAuthor, idGenTestBox, ip, uuidSystem, sName, sDescription, + idSchedGroup, fEnabled, enmLomKind, ipLom, pctScaleTimeout, sOs, sOsVersion, sCpuVendor, sCpuArch, sCpuName, + lCpuRevision, cCpus, fCpuHwVirt, fCpuNestedPaging, fCpu64BitGuest, fChipsetIoMmu, cMbMemory, cMbScratch, sReport, + iTestBoxScriptRev, iPythonHexVersion, enmPendingCmd ) + SELECT idTestBox, tsEffective, tsExpire, uidAuthor, idGenTestBox, ip, uuidSystem, sName, sDescription, + idSchedGroup, fEnabled, enmLomKind, ipLom, pctScaleTimeout, sOs, sOsVersion, sCpuVendor, sCpuArch, NULL, + NULL, cCpus, fCpuHwVirt, fCpuNestedPaging, fCpu64BitGuest, fChipsetIoMmu, cMbMemory, cMbScratch, NULL, + iTestBoxScriptRev, iPythonHexVersion, enmPendingCmd + FROM OldTestBoxes; + +-- Add index. +CREATE UNIQUE INDEX TestBoxesUuidIdx ON TestBoxes (uuidSystem, tsExpire); + +-- Restore foreign key references to the table. +ALTER TABLE TestBoxStatuses ADD CONSTRAINT TestBoxStatuses_idGenTestBox_fkey FOREIGN KEY (idGenTestBox) REFERENCES TestBoxes(idGenTestBox); +ALTER TABLE TestSets ADD CONSTRAINT TestSets_idGenTestBox_fkey FOREIGN KEY (idGenTestBox) REFERENCES TestBoxes(idGenTestBox); + +-- Drop the old table. +DROP TABLE OldTestBoxes; + +COMMIT; + +\d TestBoxes; + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r15-index-sorting.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r15-index-sorting.pgsql new file mode 100644 index 00000000..e182d648 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r15-index-sorting.pgsql @@ -0,0 +1,108 @@ +-- $Id: tmdb-r15-index-sorting.pgsql $ +--- @file +-- VBox Test Manager Database - Index tuning effort. +-- + +-- +-- Copyright (C) 2015-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + +-- +-- Reordered, modified and new indexes. +-- +\d UsersLoginNameIdx; +DROP INDEX UsersLoginNameIdx; +CREATE INDEX UsersLoginNameIdx ON Users (sLoginName, tsExpire DESC); +\d UsersLoginNameIdx; +ANALYZE VERBOSE Users; + + +\d TestCaseArgsLookupIdx; +DROP INDEX TestCaseArgsLookupIdx; +CREATE INDEX TestCaseArgsLookupIdx ON TestCaseArgs (idTestCase, tsExpire DESC, tsEffective ASC); +\d TestCaseArgsLookupIdx; +ANALYZE VERBOSE TestCaseArgs; + + +\d TestGroups_id_index; +DROP INDEX TestGroups_id_index; +CREATE INDEX TestGroups_id_index ON TestGroups (idTestGroup, tsExpire DESC, tsEffective ASC); +\d TestGroups_id_index; +ANALYZE VERBOSE TestGroups; + + +\d TestBoxesUuidIdx; +DROP INDEX TestBoxesUuidIdx; +CREATE UNIQUE INDEX TestBoxesUuidIdx ON TestBoxes (uuidSystem, tsExpire DESC); +\d TestBoxesUuidIdx; +DROP INDEX IF EXISTS TestBoxesExpireEffectiveIdx; +CREATE INDEX TestBoxesExpireEffectiveIdx ON TestBoxes (tsExpire DESC, tsEffective ASC); +\d TestBoxesExpireEffectiveIdx; +ANALYZE VERBOSE TestBoxes; + + +DROP INDEX IF EXISTS BuildBlacklistIdx; +CREATE INDEX BuildBlacklistIdx ON BuildBlacklist (iLastRevision DESC, iFirstRevision ASC, sProduct, sBranch, + tsExpire DESC, tsEffective ASC); +\d BuildBlacklist; +ANALYZE VERBOSE BuildBlacklist; + + +\d TestResultsNameIdx; +DROP INDEX TestResultsNameIdx; +CREATE INDEX TestResultsNameIdx ON TestResults (idStrName, tsCreated DESC); +\d TestResultsNameIdx; +DROP INDEX IF EXISTS TestResultsNameIdx2; +CREATE INDEX TestResultsNameIdx2 ON TestResults (idTestResult, idStrName); +\d TestResultsNameIdx2; +ANALYZE VERBOSE TestResults; + + +\d TestSetsCreatedDoneIdx; +DROP INDEX TestSetsCreatedDoneIdx; +DROP INDEX IF EXISTS TestSetsDoneCreatedBuildCatIdx; +CREATE INDEX TestSetsDoneCreatedBuildCatIdx ON TestSets (tsDone DESC NULLS FIRST, tsCreated ASC, idBuildCategory); +\d TestSetsDoneCreatedBuildCatIdx; +\d TestSetsGraphBoxIdx; +DROP INDEX TestSetsGraphBoxIdx; +CREATE INDEX TestSetsGraphBoxIdx ON TestSets (idTestBox, tsCreated DESC, tsDone ASC NULLS LAST, idBuildCategory, idTestCase); +\d TestSetsGraphBoxIdx; +ANALYZE VERBOSE TestSets; + + +DROP INDEX IF EXISTS SchedQueuesItemIdx; +CREATE INDEX SchedQueuesItemIdx ON SchedQueues(idItem); +\d SchedQueuesItemIdx; +DROP INDEX IF EXISTS SchedQueuesSchedGroupIdx; +CREATE INDEX SchedQueuesSchedGroupIdx ON SchedQueues(idSchedGroup); +\d SchedQueuesSchedGroupIdx; +ANALYZE VERBOSE SchedQueues; + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r16-testcaseargs-1-testresultfailures-1.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r16-testcaseargs-1-testresultfailures-1.pgsql new file mode 100644 index 00000000..cc00d8db --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r16-testcaseargs-1-testresultfailures-1.pgsql @@ -0,0 +1,122 @@ +-- $Id: tmdb-r16-testcaseargs-1-testresultfailures-1.pgsql $ +--- @file +-- VBox Test Manager Database - Adds sName to TestCaseArgs, idTestSet +-- to TestResultFailures and add some indexes to the latter as well. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + +DROP TABLE OldTestCaseArgs; +DROP TABLE NewTestCaseArgs; + + +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 0 + +LOCK TABLE TestBoxStatuses IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestSets IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestCaseArgs IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestResultFailures IN ACCESS EXCLUSIVE MODE; + +-- +-- TestCaseArgs is simple and we can use ALTER TABLE for a change. +-- +\d TestCaseArgs; +ALTER TABLE TestCaseArgs ADD COLUMN sSubName text DEFAULT NULL; +\d TestCaseArgs; + + +-- +-- Rename the original table, drop constrains and foreign key references so we +-- get the right name automatic when creating the new one. +-- +\d TestResultFailures; +ALTER TABLE TestResultFailures DROP CONSTRAINT idTestResultFk; +ALTER TABLE TestResultFailures RENAME TO OldTestResultFailures; + +DROP INDEX IF EXISTS TestResultFailureIdx; +DROP INDEX IF EXISTS TestResultFailureIdx2; +DROP INDEX IF EXISTS TestResultFailureIdx3; + + +CREATE TABLE TestResultFailures ( + --- The test result we're disucssing. + -- @note The foreign key is declared after TestResults (further down). + idTestResult INTEGER NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER NOT NULL, + --- The testsest this result is a part of. + -- This is mainly an aid for bypassing the enormous TestResults table. + -- Note! This is a foreign key, but we have to add it after TestSets has + -- been created, see further down. + idTestSet INTEGER NOT NULL, + + --- The suggested failure reason. + -- Non-unique foreign key: FailureReasons(idFailureReason) + idFailureReason INTEGER NOT NULL, + --- Optional comment. + sComment text DEFAULT NULL, + + PRIMARY KEY (idTestResult, tsExpire) +); + +INSERT INTO TestResultFailures ( idTestResult, tsEffective, tsExpire, uidAuthor, idTestSet, idFailureReason, sComment ) + SELECT o.idTestResult, o.tsEffective, o.tsExpire, o.uidAuthor, tr.idTestSet, o.idFailureReason, sComment + FROM OldTestResultFailures o, + TestResults tr + WHERE o.idTestResult = tr.idTestResult; + +-- Add unique constraint to TestResult for our new foreign key. +ALTER TABLE TestResults ADD CONSTRAINT TestResults_idTestResult_idTestSet_key UNIQUE (idTestResult, idTestSet); + +-- Restore foreign key. +ALTER TABLE TestResultFailures ADD CONSTRAINT TestResultFailures_idTestResult_idTestSet_fkey + FOREIGN KEY (idTestResult, idTestSet) REFERENCES TestResults(idTestResult, idTestSet) MATCH FULL; + +-- Add new indexes. +CREATE INDEX TestResultFailureIdx ON TestResultFailures (idTestSet, tsExpire DESC, tsEffective ASC); +CREATE INDEX TestResultFailureIdx2 ON TestResultFailures (idTestResult, tsExpire DESC, tsEffective ASC); +CREATE INDEX TestResultFailureIdx3 ON TestResultFailures (idFailureReason, idTestResult, tsExpire DESC, tsEffective ASC); + +-- Drop the old table. +DROP TABLE OldTestResultFailures; + +COMMIT; + +\d TestResultFailures; + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r17-testresultvalues-4.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r17-testresultvalues-4.pgsql new file mode 100644 index 00000000..fbe40a5c --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r17-testresultvalues-4.pgsql @@ -0,0 +1,48 @@ +-- $Id: tmdb-r17-testresultvalues-4.pgsql $ +--- @file +-- VBox Test Manager Database - Log viewer related optimizations for TestResultValues. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 0 + +\d+ TestResultValues + +-- Create index for the log viewer +CREATE INDEX TestResultValuesLogIdx ON TestResultValues(idTestSet, tsCreated); +COMMIT; + +\d+ TestResultValues + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r18-testresultfiles-1-testresultmsgs-1.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r18-testresultfiles-1-testresultmsgs-1.pgsql new file mode 100644 index 00000000..9adb15d0 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r18-testresultfiles-1-testresultmsgs-1.pgsql @@ -0,0 +1,171 @@ +-- $Id: tmdb-r18-testresultfiles-1-testresultmsgs-1.pgsql $ +--- @file +-- VBox Test Manager Database - Adds an idTestSet to TestResultFiles and TestResultMsgs. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + +-- +-- Cleanup after failed runs. +-- +DROP TABLE IF EXISTS NewTestResultFiles; +DROP TABLE IF EXISTS OldTestResultFiles; +DROP TABLE IF EXISTS NewTestResultMsgs; +DROP TABLE IF EXISTS OldTestResultMsgs; + +-- Die on error from now on. +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 0 + + +-- +-- Rename the original table, drop constrains and foreign key references so we +-- get the right name automatic when creating the new one. +-- +\d+ TestResultFiles; +ALTER TABLE TestResultFiles RENAME TO OldTestResultFiles; + +DROP INDEX IF EXISTS TestResultFilesIdx; +DROP INDEX IF EXISTS TestResultFilesIdx2; + +-- +-- Create the new version of the table and filling with the content of the old. +-- +CREATE TABLE TestResultFiles ( + --- The ID of this file. + idTestResultFile INTEGER PRIMARY KEY DEFAULT NEXTVAL('TestResultFileId'), + --- The test result it was reported within. + idTestResult INTEGER NOT NULL, + --- The test set this file is a part of (for avoiding joining thru TestResults). + idTestSet INTEGER NOT NULL, + --- Creation time stamp. + tsCreated TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- The filename relative to TestSets(sBaseFilename) + '-'. + -- The set of valid filename characters should be very limited so that no + -- file system issues can occure either on the TM side or the user when + -- loading the files. Tests trying to use other characters will fail. + -- Valid character regular expession: '^[a-zA-Z0-9_-(){}#@+,.=]*$' + idStrFile INTEGER NOT NULL, + --- The description. + idStrDescription INTEGER NOT NULL, + --- The kind of file. + -- For instance: 'log/release/vm', + -- 'screenshot/failure', + -- 'screencapture/failure', + -- 'xmllog/somestuff' + idStrKind INTEGER NOT NULL, + --- The mime type for the file. + -- For instance: 'text/plain', + -- 'image/png', + -- 'video/webm', + -- 'text/xml' + idStrMime INTEGER NOT NULL +); + +INSERT INTO TestResultFiles ( idTestResultFile, idTestResult, idTestSet, tsCreated, idStrFile, idStrDescription, + idStrKind, idStrMime) + SELECT o.idTestResultFile, o.idTestResult, tr.idTestSet, o.tsCreated, o.idStrFile, o.idStrDescription, + o.idStrKind, o.idStrMime + FROM OldTestResultFiles o, + TestResults tr + WHERE o.idTestResult = tr.idTestResult; + +-- Add new indexes. +CREATE INDEX TestResultFilesIdx ON TestResultFiles(idTestResult); +CREATE INDEX TestResultFilesIdx2 ON TestResultFiles(idTestSet, tsCreated DESC); + +-- Restore foreign keys. +ALTER TABLE TestResultFiles ADD CONSTRAINT TestResultFiles_idTestResult_fkey FOREIGN KEY(idTestResult) REFERENCES TestResults(idTestResult); +ALTER TABLE TestResultFiles ADD CONSTRAINT TestResultFiles_idTestSet_fkey FOREIGN KEY(idTestSet) REFERENCES TestSets(idTestSet); +ALTER TABLE TestResultFiles ADD CONSTRAINT TestResultFiles_idStrFile_fkey FOREIGN KEY(idStrFile) REFERENCES TestResultStrTab(idStr); +ALTER TABLE TestResultFiles ADD CONSTRAINT TestResultFiles_idStrDescription_fkey FOREIGN KEY(idStrDescription) REFERENCES TestResultStrTab(idStr); +ALTER TABLE TestResultFiles ADD CONSTRAINT TestResultFiles_idStrKind_fkey FOREIGN KEY(idStrKind) REFERENCES TestResultStrTab(idStr); +ALTER TABLE TestResultFiles ADD CONSTRAINT TestResultFiles_idStrMime_fkey FOREIGN KEY(idStrMime) REFERENCES TestResultStrTab(idStr); + +\d TestResultFiles; + + +-- +-- Rename the original table, drop constrains and foreign key references so we +-- get the right name automatic when creating the new one. +-- +\d+ TestResultMsgs; +ALTER TABLE TestResultMsgs RENAME TO OldTestResultMsgs; + +DROP INDEX IF EXISTS TestResultMsgsIdx; +DROP INDEX IF EXISTS TestResultMsgsIdx2; + +-- +-- Create the new version of the table and filling with the content of the old. +-- +CREATE TABLE TestResultMsgs ( + --- The ID of this file. + idTestResultMsg INTEGER PRIMARY KEY DEFAULT NEXTVAL('TestResultMsgIdSeq'), + --- The test result it was reported within. + idTestResult INTEGER NOT NULL, + --- The test set this file is a part of (for avoiding joining thru TestResults). + idTestSet INTEGER NOT NULL, + --- Creation time stamp. + tsCreated TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- The message string. + idStrMsg INTEGER NOT NULL, + --- The message level. + enmLevel TestResultMsgLevel_T NOT NULL +); + +INSERT INTO TestResultMsgs ( idTestResultMsg, idTestResult, idTestSet, tsCreated, idStrMsg, enmLevel) + SELECT o.idTestResultMsg, o.idTestResult, tr.idTestSet, o.tsCreated, o.idStrMsg, o.enmLevel + FROM OldTestResultMsgs o, + TestResults tr + WHERE o.idTestResult = tr.idTestResult; + +-- Add new indexes. +CREATE INDEX TestResultMsgsIdx ON TestResultMsgs(idTestResult); +CREATE INDEX TestResultMsgsIdx2 ON TestResultMsgs(idTestSet, tsCreated DESC); + +-- Restore foreign keys. +ALTER TABLE TestResultMsgs ADD CONSTRAINT TestResultMsgs_idTestResult_fkey FOREIGN KEY(idTestResult) REFERENCES TestResults(idTestResult); +ALTER TABLE TestResultMsgs ADD CONSTRAINT TestResultMsgs_idTestSet_fkey FOREIGN KEY(idTestSet) REFERENCES TestSets(idTestSet); +ALTER TABLE TestResultMsgs ADD CONSTRAINT TestResultMsgs_idStrMsg_fkey FOREIGN KEY(idStrMsg) REFERENCES TestResultStrTab(idStr); + + +\d TestResultMsgs; + + +-- +-- Drop the old tables and commit. +-- +DROP TABLE OldTestResultFiles; +DROP TABLE OldTestResultMsgs; + +COMMIT; + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r19-testboxes-3.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r19-testboxes-3.pgsql new file mode 100644 index 00000000..3d3cfd8e --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r19-testboxes-3.pgsql @@ -0,0 +1,356 @@ +-- $Id: tmdb-r19-testboxes-3.pgsql $ +--- @file +-- VBox Test Manager Database - Adds sComment and fRawMode to TestBoxes and +-- moves the strings to separate table. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + +-- +-- Cleanup after failed runs. +-- +DROP TABLE IF EXISTS OldTestBoxes; + +-- Die on error from now on. +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 0 + +-- Sanity check that we haven't already run this script. +SELECT 'done conversion already?', COUNT(sReport) FROM TestBoxes WHERE tsExpire = 'infinity'::TIMESTAMP; + +-- Total grid lock. +LOCK TABLE TestBoxStatuses IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestSets IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestBoxes IN ACCESS EXCLUSIVE MODE; +LOCK TABLE SchedGroupMembers IN ACCESS EXCLUSIVE MODE; + +\d+ TestBoxes; + +-- +-- Rename the table, drop foreign keys refering to it, and drop constrains +-- within the table itself. The latter is mostly for naming and we do it +-- up front in case the database we're running against has different names +-- due to previous conversions. +-- +ALTER TABLE TestBoxes RENAME TO OldTestBoxes; + +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_ccpus_check; +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_check; +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_cmbmemory_check; +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_cmbscratch_check; +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_pctscaletimeout_check; + +ALTER TABLE TestBoxStatuses DROP CONSTRAINT TestBoxStatuses_idGenTestBox_fkey; +ALTER TABLE TestSets DROP CONSTRAINT TestSets_idGenTestBox_fkey; + +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_pkey; +ALTER TABLE OldTestBoxes DROP CONSTRAINT testboxes_idgentestbox_key; + +DROP INDEX IF EXISTS TestBoxesUuidIdx; +DROP INDEX IF EXISTS TestBoxesExpireEffectiveIdx; + +-- This output should be free of index, constraints and references from other tables. +\d+ OldTestBoxes; + +-- +-- Create the two new tables before starting data migration (don't want to spend time +-- on converting strings just to find a typo in the TestBoxes create table syntax). +-- +CREATE SEQUENCE TestBoxStrTabIdSeq + START 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; +CREATE TABLE TestBoxStrTab ( + --- The ID of this string. + idStr INTEGER PRIMARY KEY DEFAULT NEXTVAL('TestBoxStrTabIdSeq'), + --- The string value. + sValue text NOT NULL, + --- Creation time stamp. + tsCreated TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL +); + +CREATE TABLE TestBoxes ( + --- The fixed testbox ID. + -- This is assigned when the testbox is created and will never change. + idTestBox INTEGER DEFAULT NEXTVAL('TestBoxIdSeq') NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- When modified automatically by the testbox, NULL is used. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER DEFAULT NULL, + --- Generation ID for this row. + -- This is primarily for referencing by TestSets. + idGenTestBox INTEGER UNIQUE DEFAULT NEXTVAL('TestBoxGenIdSeq') NOT NULL, + + --- The testbox IP. + -- This is from the webserver point of view and automatically updated on + -- SIGNON. The test setup doesn't permit for IP addresses to change while + -- the testbox is operational, because this will break gang tests. + ip inet NOT NULL, + --- The system or firmware UUID. + -- This uniquely identifies the testbox when talking to the server. After + -- SIGNON though, the testbox will also provide idTestBox and ip to + -- establish its identity beyond doubt. + uuidSystem uuid NOT NULL, + --- The testbox name. + -- Usually similar to the DNS name. + sName text NOT NULL, + --- Optional testbox description. + -- Intended for describing the box as well as making other relevant notes. + idStrDescription INTEGER REFERENCES TestBoxStrTab(idStr) DEFAULT NULL, + + --- Reference to the scheduling group that this testbox is a member of. + -- Non-unique foreign key: SchedGroups(idSchedGroup) + -- A testbox is always part of a group, the default one nothing else. + idSchedGroup INTEGER DEFAULT 1 NOT NULL, + + --- Indicates whether this testbox is enabled. + -- A testbox gets disabled when we're doing maintenance, debugging a issue + -- that happens only on that testbox, or some similar stuff. This is an + -- alternative to deleting the testbox. + fEnabled BOOLEAN DEFAULT NULL, + + --- The kind of lights-out-management. + enmLomKind LomKind_T DEFAULT 'none'::LomKind_T NOT NULL, + --- The IP adress of the lights-out-management. + -- This can be NULL if enmLomKind is 'none', otherwise it must contain a valid address. + ipLom inet DEFAULT NULL, + + --- Timeout scale factor, given as a percent. + -- This is a crude adjustment of the test case timeout for slower hardware. + pctScaleTimeout smallint DEFAULT 100 NOT NULL CHECK (pctScaleTimeout > 10 AND pctScaleTimeout < 20000), + + --- Change comment or similar. + idStrComment INTEGER REFERENCES TestBoxStrTab(idStr) DEFAULT NULL, + + --- @name Scheduling properties (reported by testbox script). + -- @{ + --- Same abbrieviations as kBuild, see KBUILD_OSES. + idStrOs INTEGER REFERENCES TestBoxStrTab(idStr) DEFAULT NULL, + --- Informational, no fixed format. + idStrOsVersion INTEGER REFERENCES TestBoxStrTab(idStr) DEFAULT NULL, + --- Same as CPUID reports (GenuineIntel, AuthenticAMD, CentaurHauls, ...). + idStrCpuVendor INTEGER REFERENCES TestBoxStrTab(idStr) DEFAULT NULL, + --- Same as kBuild - x86, amd64, ... See KBUILD_ARCHES. + idStrCpuArch INTEGER REFERENCES TestBoxStrTab(idStr) DEFAULT NULL, + --- The CPU name if available. + idStrCpuName INTEGER REFERENCES TestBoxStrTab(idStr) DEFAULT NULL, + --- Number identifying the CPU family/model/stepping/whatever. + -- For x86 and AMD64 type CPUs, this will on the following format: + -- (EffFamily << 24) | (EffModel << 8) | Stepping. + lCpuRevision bigint DEFAULT NULL, + --- Number of CPUs, CPU cores and CPU threads. + cCpus smallint DEFAULT NULL CHECK (cCpus IS NULL OR cCpus > 0), + --- Set if capable of hardware virtualization. + fCpuHwVirt boolean DEFAULT NULL, + --- Set if capable of nested paging. + fCpuNestedPaging boolean DEFAULT NULL, + --- Set if CPU capable of 64-bit (VBox) guests. + fCpu64BitGuest boolean DEFAULT NULL, + --- Set if chipset with usable IOMMU (VT-d / AMD-Vi). + fChipsetIoMmu boolean DEFAULT NULL, + --- Set if the test box does raw-mode tests. + fRawMode boolean DEFAULT NULL, + --- The (approximate) memory size in megabytes (rounded down to nearest 4 MB). + cMbMemory bigint DEFAULT NULL CHECK (cMbMemory IS NULL OR cMbMemory > 0), + --- The amount of scratch space in megabytes (rounded down to nearest 64 MB). + cMbScratch bigint DEFAULT NULL CHECK (cMbScratch IS NULL OR cMbScratch >= 0), + --- Free form hardware and software report field. + idStrReport INTEGER REFERENCES TestBoxStrTab(idStr) DEFAULT NULL, + --- @} + + --- The testbox script revision number, serves the purpose of a version number. + -- Probably good to have when scheduling upgrades as well for status purposes. + iTestBoxScriptRev INTEGER DEFAULT 0 NOT NULL, + --- The python sys.hexversion (layed out as of 2.7). + -- Good to know which python versions we need to support. + iPythonHexVersion INTEGER DEFAULT NULL, + + --- Pending command. + -- @note We put it here instead of in TestBoxStatuses to get history. + enmPendingCmd TestBoxCmd_T DEFAULT 'none'::TestBoxCmd_T NOT NULL, + + PRIMARY KEY (idTestBox, tsExpire), + + --- Nested paging requires hardware virtualization. + CHECK (fCpuNestedPaging IS NULL OR (fCpuNestedPaging <> TRUE OR fCpuHwVirt = TRUE)) +); + +-- Convenience view that simplifies querying a lot. +CREATE VIEW TestBoxesWithStrings AS + SELECT TestBoxes.*, + Str1.sValue AS sDescription, + Str2.sValue AS sComment, + Str3.sValue AS sOs, + Str4.sValue AS sOsVersion, + Str5.sValue AS sCpuVendor, + Str6.sValue AS sCpuArch, + Str7.sValue AS sCpuName, + Str8.sValue AS sReport + FROM TestBoxes + LEFT OUTER JOIN TestBoxStrTab Str1 ON idStrDescription = Str1.idStr + LEFT OUTER JOIN TestBoxStrTab Str2 ON idStrComment = Str2.idStr + LEFT OUTER JOIN TestBoxStrTab Str3 ON idStrOs = Str3.idStr + LEFT OUTER JOIN TestBoxStrTab Str4 ON idStrOsVersion = Str4.idStr + LEFT OUTER JOIN TestBoxStrTab Str5 ON idStrCpuVendor = Str5.idStr + LEFT OUTER JOIN TestBoxStrTab Str6 ON idStrCpuArch = Str6.idStr + LEFT OUTER JOIN TestBoxStrTab Str7 ON idStrCpuName = Str7.idStr + LEFT OUTER JOIN TestBoxStrTab Str8 ON idStrReport = Str8.idStr; + + +-- +-- Populate the string table. +-- + +--- Empty string with ID 0. +INSERT INTO TestBoxStrTab (idStr, sValue) VALUES (0, ''); + +INSERT INTO TestBoxStrTab (sValue) +( SELECT DISTINCT sDescription FROM OldTestBoxes WHERE sDescription IS NOT NULL +) UNION ( SELECT DISTINCT sOs FROM OldTestBoxes WHERE sOs IS NOT NULL +) UNION ( SELECT DISTINCT sOsVersion FROM OldTestBoxes WHERE sOsVersion IS NOT NULL +) UNION ( SELECT DISTINCT sCpuVendor FROM OldTestBoxes WHERE sCpuVendor IS NOT NULL +) UNION ( SELECT DISTINCT sCpuArch FROM OldTestBoxes WHERE sCpuArch IS NOT NULL +) UNION ( SELECT DISTINCT sCpuName FROM OldTestBoxes WHERE sCpuName IS NOT NULL +) UNION ( SELECT DISTINCT sReport FROM OldTestBoxes WHERE sReport IS NOT NULL ); + +-- Index and analyze the string table as we'll be using it a lot below already. +CREATE INDEX TestBoxStrTabNameIdx ON TestBoxStrTab USING hash (sValue); +ANALYZE VERBOSE TestBoxStrTab; + +SELECT MAX(idStr) FROM TestBoxStrTab; +SELECT pg_total_relation_size('TestBoxStrTab'); + + +-- +-- Populate the test box table. +-- + +INSERT INTO TestBoxes ( + idTestBox, -- 0 + tsEffective, -- 1 + tsExpire, -- 2 + uidAuthor, -- 3 + idGenTestBox, -- 4 + ip, -- 5 + uuidSystem, -- 6 + sName, -- 7 + idStrDescription, -- 8 + idSchedGroup, -- 9 + fEnabled, -- 10 + enmLomKind, -- 11 + ipLom, -- 12 + pctScaleTimeout, -- 13 + idStrComment, -- 14 + idStrOs, -- 15 + idStrOsVersion, -- 16 + idStrCpuVendor, -- 17 + idStrCpuArch, -- 18 + idStrCpuName, -- 19 + lCpuRevision, -- 20 + cCpus, -- 21 + fCpuHwVirt, -- 22 + fCpuNestedPaging, -- 23 + fCpu64BitGuest, -- 24 + fChipsetIoMmu, -- 25 + fRawMode, -- 26 + cMbMemory, -- 27 + cMbScratch, -- 28 + idStrReport, -- 29 + iTestBoxScriptRev, -- 30 + iPythonHexVersion, -- 31 + enmPendingCmd -- 32 + ) +SELECT idTestBox, + tsEffective, + tsExpire, + uidAuthor, + idGenTestBox, + ip, + uuidSystem, + sName, + st1.idStr, + idSchedGroup, + fEnabled, + enmLomKind, + ipLom, + pctScaleTimeout, + NULL, + st2.idStr, + st3.idStr, + st4.idStr, + st5.idStr, + st6.idStr, + lCpuRevision, + cCpus, + fCpuHwVirt, + fCpuNestedPaging, + fCpu64BitGuest, + fChipsetIoMmu, + NULL, + cMbMemory, + cMbScratch, + st7.idStr, + iTestBoxScriptRev, + iPythonHexVersion, + enmPendingCmd +FROM OldTestBoxes + LEFT OUTER JOIN TestBoxStrTab st1 ON sDescription = st1.sValue + LEFT OUTER JOIN TestBoxStrTab st2 ON sOs = st2.sValue + LEFT OUTER JOIN TestBoxStrTab st3 ON sOsVersion = st3.sValue + LEFT OUTER JOIN TestBoxStrTab st4 ON sCpuVendor = st4.sValue + LEFT OUTER JOIN TestBoxStrTab st5 ON sCpuArch = st5.sValue + LEFT OUTER JOIN TestBoxStrTab st6 ON sCpuName = st6.sValue + LEFT OUTER JOIN TestBoxStrTab st7 ON sReport = st7.sValue; + +-- Restore indexes. +CREATE UNIQUE INDEX TestBoxesUuidIdx ON TestBoxes (uuidSystem, tsExpire DESC); +CREATE INDEX TestBoxesExpireEffectiveIdx ON TestBoxes (tsExpire DESC, tsEffective ASC); + +-- Restore foreign key references to the table. +ALTER TABLE TestBoxStatuses ADD CONSTRAINT TestBoxStatuses_idGenTestBox_fkey + FOREIGN KEY (idGenTestBox) REFERENCES TestBoxes(idGenTestBox); +ALTER TABLE TestSets ADD CONSTRAINT TestSets_idGenTestBox_fkey + FOREIGN KEY (idGenTestBox) REFERENCES TestBoxes(idGenTestBox); + +-- Drop the old table. +DROP TABLE OldTestBoxes; + +COMMIT; + +\d TestBoxes; + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r20-testcases-1-testgroups-1-schedgroups-1.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r20-testcases-1-testgroups-1-schedgroups-1.pgsql new file mode 100644 index 00000000..fcda544c --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r20-testcases-1-testgroups-1-schedgroups-1.pgsql @@ -0,0 +1,67 @@ +-- $Id: tmdb-r20-testcases-1-testgroups-1-schedgroups-1.pgsql $ +--- @file +-- VBox Test Manager Database - Adds sComment to TestCases, TestGroups +-- and SchedGroups. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + + +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 0 + +LOCK TABLE TestBoxes IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestBoxStatuses IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestCases IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestGroups IN ACCESS EXCLUSIVE MODE; +LOCK TABLE SchedGroups IN ACCESS EXCLUSIVE MODE; + +-- +-- All the changes are rather simple and we'll just add the sComment column last. +-- +\d TestCases; +\d TestGroups; +\d SchedGroups; + +ALTER TABLE TestCases ADD COLUMN sComment TEXT DEFAULT NULL; +ALTER TABLE TestGroups ADD COLUMN sComment TEXT DEFAULT NULL; +ALTER TABLE SchedGroups ADD COLUMN sComment TEXT DEFAULT NULL; + +\d TestCases; +\d TestGroups; +\d SchedGroups; + +\prompt "Update python files while everything is locked. Hurry!" dummy + +COMMIT; + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r21-testsets-4.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r21-testsets-4.pgsql new file mode 100644 index 00000000..ec280e77 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r21-testsets-4.pgsql @@ -0,0 +1,290 @@ +-- $Id: tmdb-r21-testsets-4.pgsql $ +--- @file +-- VBox Test Manager Database - Adds an idSchedGroup to TestSets in +-- preparation for testboxes belonging to multiple scheduling queues. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + +-- +-- Cleanup after failed runs. +-- +DROP TABLE IF EXISTS OldTestSets; + +-- +-- Die on error from now on. +-- +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 0 + + +-- Total grid lock (don't want to deadlock below). +LOCK TABLE TestBoxStatuses IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestSets IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestBoxes IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestResults IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestResultFailures IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestResultFiles IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestResultMsgs IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestResultValues IN ACCESS EXCLUSIVE MODE; +LOCK TABLE SchedGroups IN ACCESS EXCLUSIVE MODE; +LOCK TABLE SchedQueues IN ACCESS EXCLUSIVE MODE; +LOCK TABLE SchedGroupMembers IN ACCESS EXCLUSIVE MODE; + +\d+ TestSets; + +-- +-- Rename the table, drop foreign keys refering to it, and drop constrains +-- within the table itself. The latter is mostly for naming and we do it +-- up front in case the database we're running against has different names +-- due to previous conversions. +-- +ALTER TABLE TestSets RENAME TO OldTestSets; + +ALTER TABLE TestResultFailures DROP CONSTRAINT IF EXISTS idtestsetfk; +ALTER TABLE TestResultFailures DROP CONSTRAINT IF EXISTS TestResultFailures_idTestSet_fkey; +ALTER TABLE SchedQueues DROP CONSTRAINT IF EXISTS SchedQueues_idTestSetGangLeader_fkey; +ALTER TABLE TestBoxStatuses DROP CONSTRAINT IF EXISTS TestBoxStatuses_idTestSet_fkey; +ALTER TABLE TestResultFiles DROP CONSTRAINT IF EXISTS TestResultFiles_idTestSet_fkey; +ALTER TABLE TestResultMsgs DROP CONSTRAINT IF EXISTS TestResultMsgs_idTestSet_fkey; +ALTER TABLE TestResults DROP CONSTRAINT IF EXISTS TestResults_idTestSet_fkey; +ALTER TABLE TestResultValues DROP CONSTRAINT IF EXISTS TestResultValues_idTestSet_fkey; +ALTER TABLE TestResultValues DROP CONSTRAINT IF EXISTS TestResultValues_idTestSet_fkey1; + +ALTER TABLE OldTestSets DROP CONSTRAINT testsets_igangmemberno_check; + +ALTER TABLE OldTestSets DROP CONSTRAINT TestSets_idBuildCategory_fkey; +ALTER TABLE OldTestSets DROP CONSTRAINT TestSets_idGenTestBox_fkey; +ALTER TABLE OldTestSets DROP CONSTRAINT TestSets_idGenTestCase_fkey; +ALTER TABLE OldTestSets DROP CONSTRAINT TestSets_idGenTestCaseArgs_fkey; +ALTER TABLE OldTestSets DROP CONSTRAINT TestSets_idTestResult_fkey; +ALTER TABLE OldTestSets DROP CONSTRAINT TestSets_idTestSetGangLeader_fkey; + +ALTER TABLE OldTestSets DROP CONSTRAINT IF EXISTS TestSets_sBaseFilename_key; +ALTER TABLE OldTestSets DROP CONSTRAINT IF EXISTS NewTestSets_sBaseFilename_key; +ALTER TABLE OldTestSets DROP CONSTRAINT TestSets_pkey; + +DROP INDEX IF EXISTS TestSetsGangIdx; +DROP INDEX IF EXISTS TestSetsBoxIdx; +DROP INDEX IF EXISTS TestSetsBuildIdx; +DROP INDEX IF EXISTS TestSetsTestCaseIdx; +DROP INDEX IF EXISTS TestSetsTestVarIdx; +DROP INDEX IF EXISTS TestSetsDoneCreatedBuildCatIdx; +DROP INDEX IF EXISTS TestSetsGraphBoxIdx; + + +-- This output should be free of indexes, constraints and references from other tables. +\d+ OldTestSets; + +\prompt "Is the above table completely free of indexes, constraints and references? Ctrl-C if not." dummy + +-- +-- Create the new table (no foreign keys). +-- +CREATE TABLE TestSets ( + --- The ID of this test set. + idTestSet INTEGER DEFAULT NEXTVAL('TestSetIdSeq') NOT NULL, + + --- The test config timestamp, used when reading test config. + tsConfig TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + --- When this test set was scheduled. + -- idGenTestBox is valid at this point. + tsCreated TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + --- When this test completed, i.e. testing stopped. This should only be set once. + tsDone TIMESTAMP WITH TIME ZONE DEFAULT NULL, + --- The current status. + enmStatus TestStatus_T DEFAULT 'running'::TestStatus_T NOT NULL, + + --- The build we're testing. + -- Non-unique foreign key: Builds(idBuild) + idBuild INTEGER NOT NULL, + --- The build category of idBuild when the test started. + -- This is for speeding up graph data collection, i.e. avoid idBuild + -- the WHERE part of the selection. + idBuildCategory INTEGER NOT NULL, + --- The test suite build we're using to do the testing. + -- This is NULL if the test suite zip wasn't referred or if a test suite + -- build source wasn't configured. + -- Non-unique foreign key: Builds(idBuild) + idBuildTestSuite INTEGER DEFAULT NULL, + + --- The exact testbox configuration. + idGenTestBox INTEGER NOT NULL, + --- The testbox ID for joining with (valid: tsStarted). + -- Non-unique foreign key: TestBoxes(idTestBox) + idTestBox INTEGER NOT NULL, + --- The scheduling group ID the test was scheduled thru (valid: tsStarted). + -- Non-unique foreign key: SchedGroups(idSchedGroup) + idSchedGroup INTEGER NOT NULL, + + --- The testgroup (valid: tsConfig). + -- Non-unique foreign key: TestBoxes(idTestGroup) + -- Note! This also gives the member ship entry, since a testcase can only + -- have one membership per test group. + idTestGroup INTEGER NOT NULL, + + --- The exact test case config we executed in this test run. + idGenTestCase INTEGER NOT NULL, + --- The test case ID for joining with (valid: tsConfig). + -- Non-unique foreign key: TestBoxes(idTestCase) + idTestCase INTEGER NOT NULL, + + --- The arguments (and requirements++) we executed this test case with. + idGenTestCaseArgs INTEGER NOT NULL, + --- The argument variation ID (valid: tsConfig). + -- Non-unique foreign key: TestCaseArgs(idTestCaseArgs) + idTestCaseArgs INTEGER NOT NULL, + + --- The root of the test result tree. + -- @note This will only be NULL early in the transaction setting up the testset. + -- @note If the test reports more than one top level test result, we'll + -- fail the whole test run and let the test developer fix it. + idTestResult INTEGER DEFAULT NULL, + + --- The base filename used for storing files related to this test set. + -- This is a path relative to wherever TM is dumping log files. In order + -- to not become a file system test case, we will try not to put too many + -- hundred thousand files in a directory. A simple first approach would + -- be to just use the current date (tsCreated) like this: + -- TM_FILE_DIR/year/month/day/TestSets.idTestSet + -- + -- The primary log file for the test is this name suffixed by '.log'. + -- + -- The files in the testresultfile table gets their full names like this: + -- TM_FILE_DIR/sBaseFilename-testresultfile.id-TestResultStrTab(testresultfile.idStrFilename) + -- + -- @remarks We store this explicitly in case we change the directly layout + -- at some later point. + sBaseFilename text NOT NULL, + + --- The gang member number number, 0 is the leader. + iGangMemberNo SMALLINT DEFAULT 0 NOT NULL, -- CHECK (iGangMemberNo >= 0 AND iGangMemberNo < 1024), + --- The test set of the gang leader, NULL if no gang involved. + -- @note This is set by the gang leader as well, so that we can find all + -- gang members by WHERE idTestSetGangLeader = :id. + idTestSetGangLeader INTEGER DEFAULT NULL + +); + +-- Convert the data. +INSERT INTO TestSets ( + idTestSet, + tsConfig, + tsCreated, + tsDone, + enmStatus, + idBuild, + idBuildCategory, + idBuildTestSuite, + idGenTestBox, + idTestBox, + idSchedGroup, + idTestGroup, + idGenTestCase, + idTestCase, + idGenTestCaseArgs, + idTestCaseArgs, + idTestResult, + sBaseFilename, + iGangMemberNo, + idTestSetGangLeader + ) +SELECT OldTestSets.idTestSet, + OldTestSets.tsConfig, + OldTestSets.tsCreated, + OldTestSets.tsDone, + OldTestSets.enmStatus, + OldTestSets.idBuild, + OldTestSets.idBuildCategory, + OldTestSets.idBuildTestSuite, + OldTestSets.idGenTestBox, + OldTestSets.idTestBox, + TestBoxes.idSchedGroup, + OldTestSets.idTestGroup, + OldTestSets.idGenTestCase, + OldTestSets.idTestCase, + OldTestSets.idGenTestCaseArgs, + OldTestSets.idTestCaseArgs, + OldTestSets.idTestResult, + OldTestSets.sBaseFilename, + OldTestSets.iGangMemberNo, + OldTestSets.idTestSetGangLeader +FROM OldTestSets + INNER JOIN TestBoxes + ON OldTestSets.idGenTestBox = TestBoxes.idGenTestBox; + +-- Restore the primary key and unique constraints. +ALTER TABLE TestSets ADD PRIMARY KEY (idTestSet); +ALTER TABLE TestSets ADD UNIQUE (sBaseFilename); + +-- Restore check constraints. +ALTER TABLE TestSets ADD CONSTRAINT TestSets_iGangMemberNo_Check CHECK (iGangMemberNo >= 0 AND iGangMemberNo < 1024); + +-- Restore foreign keys in the table. +ALTER TABLE TestSets ADD FOREIGN KEY (idBuildCategory) REFERENCES BuildCategories(idBuildCategory); +ALTER TABLE TestSets ADD FOREIGN KEY (idGenTestBox) REFERENCES TestBoxes(idGenTestBox); +ALTER TABLE TestSets ADD FOREIGN KEY (idGenTestCase) REFERENCES TestCases(idGenTestCase); +ALTER TABLE TestSets ADD FOREIGN KEY (idGenTestCaseArgs) REFERENCES TestCaseArgs(idGenTestCaseArgs); +ALTER TABLE TestSets ADD FOREIGN KEY (idTestResult) REFERENCES TestResults(idTestResult); +ALTER TABLE TestSets ADD FOREIGN KEY (idTestSetGangLeader) REFERENCES TestSets(idTestSet); + +-- Restore indexes. +CREATE INDEX TestSetsGangIdx ON TestSets (idTestSetGangLeader); +CREATE INDEX TestSetsBoxIdx ON TestSets (idTestBox, idTestResult); +CREATE INDEX TestSetsBuildIdx ON TestSets (idBuild, idTestResult); +CREATE INDEX TestSetsTestCaseIdx ON TestSets (idTestCase, idTestResult); +CREATE INDEX TestSetsTestVarIdx ON TestSets (idTestCaseArgs, idTestResult); +CREATE INDEX TestSetsDoneCreatedBuildCatIdx ON TestSets (tsDone DESC NULLS FIRST, tsCreated ASC, idBuildCategory); +CREATE INDEX TestSetsGraphBoxIdx ON TestSets (idTestBox, tsCreated DESC, tsDone ASC NULLS LAST, idBuildCategory, idTestCase); + +-- Restore foreign key references to the table. +ALTER TABLE TestResults ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL; +ALTER TABLE TestResultValues ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL; +ALTER TABLE TestResultFiles ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL; +ALTER TABLE TestResultMsgs ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL; +ALTER TABLE TestResultFailures ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL; + +ALTER TABLE TestBoxStatuses ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL; +ALTER TABLE SchedQueues ADD FOREIGN KEY (idTestSetGangLeader) REFERENCES TestSets(idTestSet) MATCH FULL; + +-- Drop the old table. +DROP TABLE OldTestSets; + +\prompt "Update python files while everything is locked. Hurry!" dummy + +-- Grant access to the new table. +GRANT ALL PRIVILEGES ON TABLE TestSets TO testmanager; + +COMMIT; + +\d TestSets; + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r22-testboxes-3-teststatus-4-testboxinschedgroups-1.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r22-testboxes-3-teststatus-4-testboxinschedgroups-1.pgsql new file mode 100644 index 00000000..199aa29b --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r22-testboxes-3-teststatus-4-testboxinschedgroups-1.pgsql @@ -0,0 +1,181 @@ +-- $Id: tmdb-r22-testboxes-3-teststatus-4-testboxinschedgroups-1.pgsql $ +--- @file +-- VBox Test Manager Database - Turns idSchedGroup column in TestBoxes +-- into an N:M relationship with a priority via the new table +-- TestBoxesInSchedGroups. Adds an internal scheduling table index to +-- TestBoxStatuses to implement testboxes switching between groups. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + +-- +-- Cleanup after failed runs. +-- +DROP TABLE IF EXISTS OldTestBoxes; + +-- +-- Die on error from now on. +-- +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 0 + + +-- Total grid lock. +LOCK TABLE TestBoxStatuses IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestSets IN ACCESS EXCLUSIVE MODE; +LOCK TABLE TestBoxes IN ACCESS EXCLUSIVE MODE; +LOCK TABLE SchedGroups IN ACCESS EXCLUSIVE MODE; +LOCK TABLE SchedGroupMembers IN ACCESS EXCLUSIVE MODE; + +\d+ TestBoxes; + +-- +-- We'll only be doing simple alterations so, no need to drop constraints +-- and stuff like we usually do first. +-- + +-- +-- Create the new table and populate it. +-- + +CREATE TABLE TestBoxesInSchedGroups ( + --- TestBox ID. + -- Non-unique foreign key: TestBoxes(idTestBox). + idTestBox INTEGER NOT NULL, + --- Scheduling ID. + -- Non-unique foreign key: SchedGroups(idSchedGroup). + idSchedGroup INTEGER NOT NULL, + --- When this row starts taking effect (inclusive). + tsEffective TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp NOT NULL, + --- When this row stops being tsEffective (exclusive). + tsExpire TIMESTAMP WITH TIME ZONE DEFAULT TIMESTAMP WITH TIME ZONE 'infinity' NOT NULL, + --- The user id of the one who created/modified this entry. + -- Non-unique foreign key: Users(uid) + uidAuthor INTEGER NOT NULL, + + --- The scheduling priority of the scheduling group for the test box. + -- Higher number causes the scheduling group to be serviced more frequently. + -- @sa TestGroupMembers.iSchedPriority, SchedGroups.iSchedPriority + iSchedPriority INTEGER DEFAULT 16 CHECK (iSchedPriority >= 0 AND iSchedPriority < 32) NOT NULL, + + PRIMARY KEY (idTestBox, idSchedGroup, tsExpire) +); + +GRANT ALL PRIVILEGES ON TABLE TestBoxesInSchedGroups TO testmanager; + +CREATE OR REPLACE FUNCTION TestBoxesInSchedGroups_ConvertedOneBox(a_idTestBox INTEGER) + RETURNS VOID AS $$ + DECLARE + v_Row RECORD; + v_idSchedGroup INTEGER; + v_uidAuthor INTEGER; + v_tsEffective TIMESTAMP WITH TIME ZONE; + v_tsExpire TIMESTAMP WITH TIME ZONE; + BEGIN + FOR v_Row IN + SELECT idTestBox, + idSchedGroup, + tsEffective, + tsExpire, + uidAuthor + FROM TestBoxes + WHERE idTestBox = a_idTestBox + ORDER BY tsEffective, tsExpire + LOOP + IF v_idSchedGroup IS NOT NULL THEN + IF (v_idSchedGroup != v_Row.idSchedGroup) OR (v_Row.tsEffective <> v_tsExpire) THEN + INSERT INTO TestBoxesInSchedGroups (idTestBox, idSchedGroup, tsEffective, tsExpire, uidAuthor) + VALUES (a_idTestBox, v_idSchedGroup, v_tsEffective, v_tsExpire, v_uidAuthor); + v_idSchedGroup := NULL; + END IF; + END IF; + + IF v_idSchedGroup IS NULL THEN + v_idSchedGroup := v_Row.idSchedGroup; + v_tsEffective := v_Row.tsEffective; + END IF; + IF v_Row.uidAuthor IS NOT NULL THEN + v_uidAuthor := v_Row.uidAuthor; + END IF; + v_tsExpire := v_Row.tsExpire; + END LOOP; + + IF v_idSchedGroup != -1 THEN + INSERT INTO TestBoxesInSchedGroups (idTestBox, idSchedGroup, tsEffective, tsExpire, uidAuthor) + VALUES (a_idTestBox, v_idSchedGroup, v_tsEffective, v_tsExpire, v_uidAuthor); + END IF; + END; +$$ LANGUAGE plpgsql; + +SELECT TestBoxesInSchedGroups_ConvertedOneBox(TestBoxIDs.idTestBox) +FROM ( SELECT DISTINCT idTestBox FROM TestBoxes ) AS TestBoxIDs; + +DROP FUNCTION TestBoxesInSchedGroups_ConvertedOneBox(INTEGER); + +-- +-- Do the other two modifications. +-- +ALTER TABLE TestBoxStatuses ADD COLUMN iWorkItem INTEGER DEFAULT 0 NOT NULL; + +DROP VIEW TestBoxesWithStrings; +ALTER TABLE TestBoxes DROP COLUMN idSchedGroup; +CREATE VIEW TestBoxesWithStrings AS + SELECT TestBoxes.*, + Str1.sValue AS sDescription, + Str2.sValue AS sComment, + Str3.sValue AS sOs, + Str4.sValue AS sOsVersion, + Str5.sValue AS sCpuVendor, + Str6.sValue AS sCpuArch, + Str7.sValue AS sCpuName, + Str8.sValue AS sReport + FROM TestBoxes + LEFT OUTER JOIN TestBoxStrTab Str1 ON idStrDescription = Str1.idStr + LEFT OUTER JOIN TestBoxStrTab Str2 ON idStrComment = Str2.idStr + LEFT OUTER JOIN TestBoxStrTab Str3 ON idStrOs = Str3.idStr + LEFT OUTER JOIN TestBoxStrTab Str4 ON idStrOsVersion = Str4.idStr + LEFT OUTER JOIN TestBoxStrTab Str5 ON idStrCpuVendor = Str5.idStr + LEFT OUTER JOIN TestBoxStrTab Str6 ON idStrCpuArch = Str6.idStr + LEFT OUTER JOIN TestBoxStrTab Str7 ON idStrCpuName = Str7.idStr + LEFT OUTER JOIN TestBoxStrTab Str8 ON idStrReport = Str8.idStr; + +GRANT ALL PRIVILEGES ON TABLE TestBoxesWithStrings TO testmanager; + +\prompt "Update python files while everything is locked. Hurry!" dummy + +COMMIT; + +\d TestBoxesInSchedGroups; +\d TestBoxStatuses; +\d TestBoxes; +ANALYZE VERBOSE TestBoxesInSchedGroups; + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r23-users-2.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r23-users-2.pgsql new file mode 100644 index 00000000..cbc3d1bd --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r23-users-2.pgsql @@ -0,0 +1,60 @@ +-- $Id: tmdb-r23-users-2.pgsql $ +--- @file +-- VBox Test Manager Database - Adds fReadOnly column to Users. +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + +-- +-- Cleanup after failed runs. +-- +DROP TABLE IF EXISTS OldTestBoxes; + +-- +-- Die on error from now on. +-- +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 0 + + + +-- This change can be implemented using ALTER TABLE. Yeah! +\d+ Users; + +ALTER TABLE Users + ADD COLUMN fReadOnly BOOLEAN NOT NULL DEFAULT FALSE; + +COMMIT; + +\d Users; +ANALYZE VERBOSE Users; + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r24-vcsbugreferences-1.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r24-vcsbugreferences-1.pgsql new file mode 100644 index 00000000..8a6947cd --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r24-vcsbugreferences-1.pgsql @@ -0,0 +1,59 @@ +-- $Id: tmdb-r24-vcsbugreferences-1.pgsql $ +--- @file +-- VBox Test Manager Database - Creates a new VcsBugReferences table. +-- + +-- +-- Copyright (C) 2020-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + +-- Die on error from now on. +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 0 + +-- +-- Create the new VcsBugReferences table. +-- +CREATE TABLE VcsBugReferences ( + --- The version control tree name. + sRepository TEXT NOT NULL, + --- The version control tree revision number. + iRevision INTEGER NOT NULL, + --- The bug tracker identifier - see g_kdBugTrackers in config.py. + sBugTracker CHAR(4) NOT NULL, + --- The bug number in the bug tracker. + lBugNo BIGINT NOT NULL, + + UNIQUE (sRepository, iRevision, sBugTracker, lBugNo) +); +CREATE INDEX VcsBugReferencesLookupIdx ON VcsBugReferences (sBugTracker, lBugNo); +COMMIT; +\d+ VcsBugReferences; + diff --git a/src/VBox/ValidationKit/testmanager/db/tmdb-r25-vcsrevisions-2.pgsql b/src/VBox/ValidationKit/testmanager/db/tmdb-r25-vcsrevisions-2.pgsql new file mode 100644 index 00000000..f4370472 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/db/tmdb-r25-vcsrevisions-2.pgsql @@ -0,0 +1,45 @@ +-- $Id: tmdb-r25-vcsrevisions-2.pgsql $ +--- @file +-- VBox Test Manager Database - Creates a new index on VcsRevisions +-- + +-- +-- Copyright (C) 2013-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + +-- +-- Die on error from now on. +-- +\set ON_ERROR_STOP 1 +\set AUTOCOMMIT 1 + + +CREATE INDEX VcsRevisionsByDate ON VcsRevisions (tsCreated DESC); + diff --git a/src/VBox/ValidationKit/testmanager/debug/Makefile.kmk b/src/VBox/ValidationKit/testmanager/debug/Makefile.kmk new file mode 100644 index 00000000..1de2d1dc --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/debug/Makefile.kmk @@ -0,0 +1,46 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(wildcard $(PATH_SUB_CURRENT)/*.py) + +$(evalcall def_vbox_validationkit_process_python_sources) +$(evalcall def_vbox_validationkit_process_js_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/testmanager/debug/__init__.py b/src/VBox/ValidationKit/testmanager/debug/__init__.py new file mode 100644 index 00000000..66354dd2 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/debug/__init__.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# $Id: __init__.py $ + +""" +Test Manager - Debug Utilities. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + diff --git a/src/VBox/ValidationKit/testmanager/debug/add_testbox.pgsql b/src/VBox/ValidationKit/testmanager/debug/add_testbox.pgsql new file mode 100644 index 00000000..196d1fac --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/debug/add_testbox.pgsql @@ -0,0 +1,76 @@ +-- $Id: add_testbox.pgsql $ +--- @file +-- Test data. +-- + +-- +-- Copyright (C) 2012-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + +\connect testmanager; + +INSERT INTO users (sUsername, sNickname, sFullName, sLoginName) + VALUES ('testmanager', 'testmanager', 'testmanager', 'testmanager'); + +INSERT INTO testboxes (uidAuthor, + ip, + uuidSystem, + sName, + fEnabled, + sOs, + sOsVersion, + sCpuVendor, + sCpuArch, + cCpus, + fCpuHwVirt, + fCpuNestedPaging, + fCpu64BitGuest, + fChipsetIoMmu, + cMbMemory, + cMbScratch) + + VALUES (1, + '127.0.0.1', + '9394c36a-cb2f-3c7f-b987-9f54a4519bbc', + 'localhost', + TRUE, + 'LINUX', + '1.0', + 'Intel', + 'AMD64', + 2, + TRUE, + TRUE, + TRUE, + TRUE, + 1024, + 1024); + diff --git a/src/VBox/ValidationKit/testmanager/debug/cgiprofiling.py b/src/VBox/ValidationKit/testmanager/debug/cgiprofiling.py new file mode 100755 index 00000000..b52c581a --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/debug/cgiprofiling.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: cgiprofiling.py $ + +""" +Debug - CGI Profiling. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +def profileIt(fnMain, sAppendToElement = 'main', sSort = 'time'): + """ + Profiles a main() type function call (no parameters, returns int) and + outputs a hacky HTML section. + """ + + # + # Execute it. + # + import cProfile; + oProfiler = cProfile.Profile(); + rc = oProfiler.runcall(fnMain); + + # + # Output HTML to stdout (CGI assumption). + # + print('<div id="debug2"><br>\n' # Lazy BR-layouting!! + ' <h2>Profiler Output</h2>\n' + ' <pre>'); + try: + oProfiler.print_stats(sort = sSort); + except Exception as oXcpt: + print('<p><pre>%s</pre></p>\n' % (oXcpt,)); + else: + print('</pre>\n'); + oProfiler = None; + print('</div>\n'); + + # + # Trick to move the section in under the SQL trace. + # + print('<script lang="script/javascript">\n' + 'var oMain = document.getElementById(\'%s\');\n' + 'if (oMain) {\n' + ' oMain.appendChild(document.getElementById(\'debug2\'));\n' + '}\n' + '</script>\n' + % (sAppendToElement, ) ); + + return rc; + diff --git a/src/VBox/ValidationKit/testmanager/debug/functions.pgsql b/src/VBox/ValidationKit/testmanager/debug/functions.pgsql new file mode 100644 index 00000000..7a9755eb --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/debug/functions.pgsql @@ -0,0 +1,82 @@ +-- $Id: functions.pgsql $ +--- @file +-- ????????????????????????? +-- + +-- +-- Copyright (C) 2012-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + +\connect testmanager; + +DROP FUNCTION authenticate_testbox(inet, uuid); +DROP FUNCTION testbox_status_set(integer, TestBoxState_T); + +-- Authenticate Test Box record by IP and UUID and set its state to IDLE +-- Args: IP, UUID +CREATE OR REPLACE FUNCTION authenticate_testbox(inet, uuid) RETURNS testboxes AS $$ + DECLARE + _ip ALIAS FOR $1; + _uuidSystem ALIAS FOR $2; + _box TestBoxes; + BEGIN + -- Find Test Box record + SELECT * + FROM testboxes + WHERE ip=_ip AND uuidSystem=_uuidSystem INTO _box; + IF FOUND THEN + -- Update Test Box status if exists + UPDATE TestBoxStatuses SET enmState='idle' WHERE idTestBox=_box.idTestBox; + IF NOT FOUND THEN + -- Otherwise, add new record to TestBoxStatuses table + INSERT + INTO TestBoxStatuses(idTestBox, idGenTestBox, enmState) + VALUES (_box.idTestBox, _box.idGenTestBox, 'idle'); + END IF; + END IF; + return _box; + END; +$$ LANGUAGE plpgsql; + +-- Set Test Box status and make sure if it has been set +-- Args: Test Box ID, new status +CREATE OR REPLACE FUNCTION testbox_status_set(integer, TestBoxState_T) RETURNS VOID AS $$ + DECLARE + _box ALIAS FOR $1; + _status ALIAS FOR $2; + BEGIN + -- Update Test Box status if exists + UPDATE TestBoxStatuses SET enmState=_status WHERE idTestBox=_box; + IF NOT FOUND THEN + RAISE EXCEPTION 'Test Box (#%) was not found in database', _box; + END IF; + END; +$$ LANGUAGE plpgsql; + diff --git a/src/VBox/ValidationKit/testmanager/htdocs/Makefile.kup b/src/VBox/ValidationKit/testmanager/htdocs/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/htdocs/Makefile.kup diff --git a/src/VBox/ValidationKit/testmanager/htdocs/css/common.css b/src/VBox/ValidationKit/testmanager/htdocs/css/common.css new file mode 100644 index 00000000..28ce6a10 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/htdocs/css/common.css @@ -0,0 +1,1183 @@ +/* $Id: common.css $ */ +/** @file + * Test Manager - Common CSS. + */ + +/* + * Copyright (C) 2012-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +@charset "UTF-8"; + +/* + * Basic HTML elements. + */ +* { + margin: 0; + padding: 0; +} + +html, body { + height: 100%; +} + +body { + background: #f9f9f9 repeat-y center; + font-family: Georgia, "Times New Roman", Times, serif; + font-family: Arial, Helvetica, sans-serif; + font-size: 0.8em; + color: #2f2f2f; +} + +p, ul, ol { + margin-top: 0; +} + +div { + margin: 0; + padding: 0; +} + +h1, h2, h3 { + margin: 0px 0 10px 0; + padding: 0; + font-weight: normal; + color: #2f2f2f; + line-height: 180%; +} +h1 { + font-size: 2.4em; +} +h2 { + font-size: 2.0em; +} +h3 { + font-size: 1.5em; +} + +dl { + margin-bottom: 10px; +} + + +/* + * Misc class stuff. + */ +.clear { + clear: both; +} + +.left { + float: left; +} + +.right { + float: right; +} + + + +/* + * The general layout. + * + * Note! Not quite sure if something like this will work well everywhere... + * Will get back to that when the logic and content is all there, not + * worth wasting more time on CSS now. + */ + +html, body { + height: 100%; +} + +#wrap { + position: relative; + width: 100%; + height: 100%; +} + +#head-wrap { + position: fixed; + top: 0; + left: 0; + height: 74px; /**< header + top-menu. */ + width: 100%; + background: #f9f9f9; +} + +#logo { + width: 42px; + height: 46px; + top: 0; + left: 0; + right: 0; + bottom: auto; + /* Center the image in both directions. */ + display: flex; + align-items: center; + justify-content: center; + justify-content: flex-end; +} + +#logo img { + height: 36px; + width: 36px; +} + +#header { + position: fixed; + width: 100%; /** @todo this is too wide, darn! */ + height: 46px; + left: 42px; + top: 0; + right: 0; + bottom: auto; + margin-top: 0px; + margin-left: 0px; + text-align: left; + /* Center the h1 child vertically: */ + display: flex; + align-items: center; +} + +#login { + position: absolute; + top: 0; + left: auto; + right: 2px; + bottom: auto; + height: auto; +} + +#top-menu { + position: fixed; + padding: 0px; + width: 99%; + height: auto; + max-height: 22px; + top: 46px; + left: 0px; + right: 0px; + bottom: auto; +} + +body.tm-wide-side-menu #side-menu-wrap { + width: 300px; +} +#side-menu-wrap { + position: fixed; + top: 0px; + left: 0; + right: auto; + bottom: auto; + + width: 164px; + height: 100vh; + min-height: 100vh; + max-height: 100vh; + + display: flex; +} + +#side-menu { + margin-top: 46px; + margin-top: 70px; + padding-top: 6px + height: auto; + max-height: 100%; + width: 95%; + width: calc(100% - 8px); /* CSS3 */ + + display: flex; + flex-direction: column; + justify-content: space-between; +} + +#side-menu-body { + display: block; + max-height: 100%; + overflow: auto; +} + +body.tm-wide-side-menu #main { + margin-left: 300px; +} +#main { + height: 100%; + margin-top: 74px; /**< header + top-menu + padding. */ + margin-left: 164px; + padding-left: 2px; + padding-right: 2px; + padding-top: 2px; + padding-bottom: 2px; +} + + +/* + * Header and logo specifics. + */ +#header h1 { + margin-left: 8px; + margin-top: 0px; + margin-right: 0px; + margin-bottom: 0px; + font-weight: bold; + font-size: 2.2em; + font-family: Times New, Times, serif; +} + +#login p { + line-height: 100%; +} + + +/* + * Navigation menus (common). + */ +#top-menu, #side-menu { + font-weight: bold; + font-size: 1em; + font-family: Arial, Helvetica, sans-serif; + background-color: #c0d0e0; + padding: 2px 2px 2px 2px; +} + +#top-menu.tm-top-menu-wo-side { + border-radius: 12px; +} +#top-menu { + border-radius: 12px 12px 12px 0px; +} + +#side-menu { + border-radius: 0px 0px 12px 12px; +} + +#head-wrap { + line-height: 180%; +} + +#top-menu ul li a, #side-menu ul li a { + text-decoration: none; + color: #000000; + font-weight: bold; + font-size: 1em; + font-family: Arial, Helvetica, sans-serif; +} + +#top-menu a:hover, #top-menu .current_page_item a, #side-menu a:hover, #side-menu .current_page_item a { + text-decoration: none; + color: #b23c1c; +} + + +/* + * Navigation in on the left side. + */ + + +/* Side menu: */ +#side-menu { + /* margin-top and padding-top are set up in layout !*/ + margin-right: 3px; + margin-left: 3px; + margin-bottom: 3px; +} + +#side-menu p { + margin-right: 3px; + margin-left: 3px; +} + +#side-menu ul { + list-style: none; + margin-left: 3px; + margin-right: 3px; +} + +#side-menu li { + padding-top: 0.3em; + padding-bottom: 0.3em; + line-height: 1.0em; + text-align: left; +} + +#side-menu .subheader_item { + font-style: italic; + font-size: 1.1em; + text-decoration: underline; +} + +.subheader_item:not(:first-child) { + margin-top: 0.5em; +} + +/* The following is for the element of / not element of checkbox, supplying text and hiding the actual box. */ +input.tm-side-filter-union-input { + display: none; +} +input.tm-side-filter-union-input + label { + vertical-align: middle; +} +input.tm-side-filter-union-input[type=checkbox]:checked + label::after { + content: '∉'; /* U+2209: not an element of. */ +} +input.tm-side-filter-union-input[type=checkbox] + label::after { + content: '∈'; /* U+2208: element of. */ +} + +/* Webkit: Pretty scroll bars on the menu body as well as inside filter criteria. */ +#side-menu ::-webkit-scrollbar { + width: 8px; +} +#side-menu ::-webkit-scrollbar-track { + -webkit-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.3); + -webkit-border-radius: 4px; + border-radius: 4px; +} +#side-menu ::-webkit-scrollbar-thumb { + -webkit-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.5); + -webkit-border-radius: 4px; + border-radius: 4px; + background: rgba(112, 128, 144, 0.9); +} +#side-menu ::-webkit-scrollbar-thumb:window-inactive { + background: rgba(112, 128, 144, 0.7); +} + +/* Filters: */ +.tm-side-filter-title-buttons { + float: right; +} +body.tm-wide-side-menu .tm-side-filter-title-buttons input { + display: none; +} +.tm-side-filter-title-buttons input { + display: inline; +} +.tm-side-filter-title-buttons input { + font-size: 0.6em; +} +.tm-side-filter-dt-buttons input { + font-size: 0.6em; +} +body.tm-wide-side-menu .tm-side-filter-dt-buttons input[type=submit] { + display: inline; +} +.tm-side-filter-dt-buttons input[type=submit] { + display: none; +} +.tm-side-filter-dt-buttons { + float: right; +} + +#side-filters p:first-child { + margin-top: 0.5em; + font-style: italic; + font-size: 1.1em; + text-decoration: underline; +} + +#side-filters dd.sf-collapsible { + display: block; +} + +#side-filters dd.sf-expandable { + display: none; +} + +#side-filters a { + text-decoration: none; + color: #000000; +} + +#side-filters dt { + margin-top: 0.4em; +} + +#side-filters dd { + font-size: 0.82em; + font-family: "Arial Narrow", Arial, sans-serif; + font-weight: normal; + clear: both; /* cancel .tm-side-filter-dt-buttons */ +} + +#side-filters li, #side-filters input[type=checkbox], #side-filters p { + line-height: 0.9em; + vertical-align: text-bottom; +} + +#side-filters input[type=checkbox] { + margin-right: 0.20em; + width: 1.0em; + height: 1.0em; +} +@supports(-moz-appearance:meterbar) { + #side-filters input[type=checkbox] { + /* not currently used */ + } +} +@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { /* IE 10+ specific tweaks */ + #side-filters input[type=checkbox] { + width: 1.1em; + height: 1.1em; + } +} + +#side-filters dd > ul { + max-height: 22em; + overflow: auto; +} + +#side-filters ul ul { + margin-left: 1.4em; +} + +#side-filters li { + padding-top: 1px; + padding-bottom: 1px; + overflow-wrap: break-word; +} + +ul.sf-checkbox-collapsible { + display: block; +} + +ul.sf-checkbox-expandable { + display: none; +} + +.side-filter-irrelevant { + font-style: italic; + font-weight: normal; +} +.side-filter-count { + font-size: smaller; + vertical-align: text-top; +} + +/* Footer: */ +#side-footer { + width: 100%; + margin-left: 2px; + margin-right: 2px; + margin-top: 1em; + padding-top: 1em; + padding-bottom: 0.8em; + border-top: thin white ridge; +} + +#side-footer p { + margin-left: 3px; + margin-right: 3px; + margin-bottom: 0.5em; + font-family: Times New, Times, serif; + font-size: 0.86em; + font-style: normal; + font-weight: normal; + line-height: 1.2em; + text-align: center; +} + + +/* + * Navigation in the header. + */ +#top-menu { + margin-right: 3px; /* same as #side-menu! */ + margin-left: 3px; +} + +#top-menu ul li a { + padding: .1em 1em; +} + +#top-menu ul li { + display: inline; +} + +#top-menu ul { + margin: 0; + padding: 0; + list-style: none; + list-style-type: none; + text-align: center; +} + +#top-menu a { + border: none; +} + +#top-menu .current_page_item a { +} + +/* + * Time navigation forms on a line with some padding between them. + */ +.tmtimenav form { + display: inline-block; +} + +.tmtimenav form + form { + padding-left: 0.6em; +} + +/* + * Items per page and next. + */ +.tmnextanditemsperpage form { + display: inline-block; + padding-left: 1em; +} + +/* + * Error message (typically a paragraph in the body). + */ +.tmerrormsg { + color: #ff0000; + white-space: pre; + font-family: Monospace, "Lucida Console", "Courier New", "Courier"; + display: block; + border: 1px solid; + margin: 1em; + padding: 0.6em; +} + + +/* + * Generic odd/even row and sub-row attribs. + */ +.tmeven { + background-color: #ececec; +} + +.tmodd { + background-color: #fcfcfc; +} + +/** @todo adjust the sub row colors (see change logs for examples). */ +.tmeveneven { + background-color: #d8e0f8; +} + +.tmevenodd { + background-color: #e8f0ff; +} + +.tmoddeven { + background-color: #d8e0f8; +} + +.tmoddodd { + background-color: #e8f0ff; +} + +/* + * Multi color row/item coloring, 0..7. + */ +.tmshade0 { background-color: #ececec; } +.tmshade1 { background-color: #fbfbfb; } +.tmshade2 { background-color: #e4e4e4; } +.tmshade3 { background-color: #f4f4f4; } +.tmshade4 { background-color: #e0e0e0; } +.tmshade5 { background-color: #f0f0f0; } +.tmshade6 { background-color: #dcdcdc; } +.tmshade7 { background-color: #fdfdfd; } + + +/* + * Generic thead class (first-child doesn't work for multiple header rows). + */ +.tmheader { + background-color: #d0d0d0; + color: black; +} + +/* + * Generic class for div elements wrapping pre inside a table. This prevents + * the <pre> from taking up way more screen space that available. + */ +.tdpre { + display: table; + table-layout: fixed; + width: 100%; +} +.tdpre pre { + overflow: auto; +} + + +/* + * A typical table. + */ +/* table.tmtable th { + background-color: #d0d0d0; + color: black; +} */ + +table.tmtable caption { + text-align: left; +} + +table.tmtable { + width: 100%; + border-spacing: 0px; +} + +table.tmtable th { + font-size: 1.3em; + text-align: center; +} + +table.tmtable, table.tmtable tr, table.tmtable td, table.tmtable th { + vertical-align: top; +} + +table.tmtable { + border-left: 1px solid black; + border-top: 1px solid black; + border-right: none; + border-bottom: none; +} + +table.tmtable td, table.tmtable th { + border-left: none; + border-top: none; + border-right: 1px solid black; + border-bottom: 1px solid black; +} + +table.tmtable td { + padding-left: 3px; + padding-right: 3px; + padding-top: 3px; + padding-bottom: 3px; +} + +table.tmtable th { + padding-left: 3px; + padding-right: 3px; + padding-top: 6px; + padding-bottom: 6px; +} + +.tmtable td { +} + +tr.tmseparator td { + border-bottom: 2px solid black; + font-size: 0; + padding-top: 0; + padding-bottom: 0; +} + + + +/* + * Table placed inside of a big table used to display *all* stuff of a category. + */ + +table.tminnertbl tr:nth-child(odd) { + background-color: #e8e8e8; +} +table.tminnertbl tr:nth-child(even) { + background-color: #f8f8f8; +} +table.tminnertbl tr:first-child { + background-color: #d0d0d0; + color: black; +} + +table.tminnertbl { + border-style: dashed; + border-spacing: 1px; + border-width: 1px; + border-color: gray; + border-collapse: separate; +} + +table.tminnertbl th, table.tminnertbl td { + font-size: 1em; + text-align: center; + border-style: none; + padding: 1px; + border-width: 1px; + border-color: #FFFFF0; +} + +/* + * Table placed inside a form. + */ +table.tmformtbl { + border-style: none; + border-spacing: 1px; + border-width: 1px; + border-collapse: separate; +} + +table.tmformtbl th, table.tmformtbl td { + font-size: 1em; + padding-left: 0.5em; + padding-right: 0.5em; + padding-bottom: 1px; + padding-top: 1px; + border-width: 1px; +} + +table.tmformtbl th, table.tmformtbl thead { + background-color: #d0d0d0; + font-size: 1em; + font-weight: bold; +} + +table.tmformtbl tr.tmodd { + background: #e2e2e2; +} + +table.tmformtblschedgroupmembers tr td:nth-child(3), +table.tmformtblschedgroupmembers tr td:nth-child(4) { + text-align: center; +} + + +/* + * Change log table (used with tmtable). + */ +table.tmchangelog > tbody { + font-size: 1em; +} + +table.tmchangelog tr.tmodd td:nth-child(1), +table.tmchangelog tr.tmeven td:nth-child(1), +table.tmchangelog tr.tmodd td:nth-child(2), +table.tmchangelog tr.tmeven td:nth-child(2) { + min-width: 5em; + max-width: 10em; /* futile */ +} + +table.tmchangelog tr.tmeven { + background-color: #e8f0ff; +} + +table.tmchangelog tr.tmodd { + background-color: #d8e0f8; +} + +table.tmchangelog tr.tmoddeven, table.tmchangelog tr.tmeveneven { + background-color: #fcfcfc; +} + +table.tmchangelog tr.tmoddodd, table.tmchangelog tr.tmevenodd { + background-color: #ececec; +} + +table.tmchangelog tr.tmoddeven, table.tmchangelog tr.tmeveneven, table.tmchangelog tr.tmoddodd, table.tmchangelog tr.tmevenodd { + font-size: 0.86em; +} + +.tmsyschlogattr { + font-size: 0.80em; +} + +.tmsyschlogspacer { + width: 0.8em; +} + +td.tmsyschlogspacer:not(:last-child) { + width: 1.8em; + border-bottom: 0px solid green !important; +} + +.tmsyschlogevent { + border-bottom: 0px solid green !important; +} + +.tmsyschlogspacerrowabove { + height: 0.22em; +} + +.tmsyschlogspacerrowbelow { + height: 0.80em; +} + + +/* + * Elements to be shows on *Show All* pages. + */ + +ul.tmshowall { + margin-left: 15px; + margin-right: 15px; +} + +li.tmshowall { + margin-left: 5px; + margin-right: 5px; +} + + +/* + * List navigation table + */ +table.tmlistnavtab { + width: 100%; +} + +table.tmlistnavtab tr td:nth-child(1) { + text-align: left; +} + +table.tmlistnavtab tr td:nth-child(2) { + text-align: right; +} + + +/* + * A typical form. + * + * Note! This _has_ to be redone. It sucks for the wide fields and such. + */ +.tmform ul { + list-style: none; + list-style-type: none; +} + +.tmform li { + line-height: 160%; +} + + +.tmform-field { + display: block; + clear: both; +} + +.tmform-field label { + float: left; + text-align: right; + width: 20%; + min-width: 10em; + max-width: 16em; + padding-right: 0.9em; +} + +.tmform-error-desc { + display: block; + color: #ff0000; + font-style: italic; +} + +.tmform-button { + float: left; + padding-top: 0.8em; +} + +.tmform-field input { +} + +.tmform-field-tiny-int input { + width: 2em; +} + +.tmform-field-int input { + width: 6em; +} + +.tmform-field-long input { + width: 9em; +} + +.tmform-field-submit input { +} + +.tmform-field-string input { + width: 24em; +} + +.tmform-field-subname input { + width: 10em; +} + +.tmform-field-timestamp input { + width: 20em; +} + +.tmform-field-uuid input { + width: 24em; +} + +.tmform-field-wide input { + width: 78%; + overflow: hidden; +} + +.tmform-field-wide100 input { + width: 100%; + overflow: hidden; +} + +.tmform-field-list { + padding-top: 2px; + padding-bottom: 2px; +} + +.tmform-checkboxes-container { + padding: 3px; + overflow: auto; + border: 1px dotted #cccccc; +} + +.tmform-checkbox-holder { + float: left; + min-width: 20em; +} + +#tmform-checkbox-list-os-arches .tmform-checkbox-holder { + min-width: 11em; +} + +#tmform-checkbox-list-build-types .tmform-checkbox-holder { + min-width: 6em; +} + +.tmform-input-readonly { + background: #ADD8EF; + color: #ffffff; +} + +/* (Test case argument variation.) */ + +table.tmform-innertbl { + border-style: none; + border-spacing: 1px; + border-width: 1px; + border-collapse: separate; + width: 78%; +} + +table.tmform-innertbl caption { + text-align: left; +} + +table.tmform-innertbl th, table.tmform-innertbl td { + font-size: 1em; + text-align: center; + border-style: none; + /* padding-top: 1px;*/ + /*padding-bottom: 1px;*/ + padding-left: 2px; + padding-right: 2px; + border-width: 1px; + border-color: #FFFFF0; + background-color: #f9f9f9; +} + +.tmform-inntertbl-td-wide input { + width: 100%; + overflow: hidden; +} + +.tmform-inntertbl-td-wide { + width: 100%; +} + + +/* + * The test case argument variation table. + */ +table.tmform-testcasevars { + border-style: none; + border-spacing: 0px; + border-width: 0px; + border-collapse: collapse; + width: 78%; +} + +table.tmform-testcasevars tbody { + border-style: solid; + border-spacing: 1px; + border-width: 1px; + margin: 2px; +} + +table.tmform-testcasevars td { + padding-right: 3px; + padding-left: 3px; +} + +table.tmform-testcasevars td:first-child, table.tmform-testcasevars td:nth-child(3) { + width: 8em; + text-align: right; +} +table.tmform-testcasevars td:nth-child(5) { + width: 4em; + text-align: left; +} + + +.tmform-testcasevars caption { + text-align: left; +} + +tr.tmform-testcasevars-first-row td { + padding-top: 0px; + padding-bottom: 0px; + background-color: #e3e3ec; +} + +.tmform-testcasevars-inner-row td { + padding-top: 0px; + padding-bottom: 0px; +} + +tr.tmform-testcasevars-final-row td { + padding-top: 0px; + padding-bottom: 1px; +} + +td.tmform-testcasevars-stupid-border-column { + /* Stupid hack. */ + min-width: 2px; + width: 0.1%; +} + + + +/* + * Log viewer. + */ +.tmlog a[href] { + background-color: #e0e0e0; + padding-left: 0.8em; + padding-right: 0.8em; +} + +.tmlog pre { + background-color: #000000; + color: #00ff00; + font-family: "Monospace", "Lucida Console", "Courier New", "Courier"; +} + + +/* + * Debug SQL traceback. + */ +#debug, #debug h1, #debug h2, #debug h3, +#debug2, #debug2 h1, #debug2 h2, #debug2 h3 { + color: #00009f; +} + +table.tmsqltable { + border-collapse: collapse; +} + +table.tmsqltable, table.tmsqltable tr, table.tmsqltable td, table.tmsqltable th { + border: 1px solid; + vertical-align: middle; + padding: 0.1ex 0.5ex; +} + +table.tmsqltable pre { + text-align: left; +} + +table.tmsqltable tr td { + text-align: left; +} + +table.tmsqltable tr td:nth-child(1), +table.tmsqltable tr td:nth-child(2), +table.tmsqltable tr td:nth-child(3), +table.tmsqltable tr td:nth-child(4) { + text-align: right; +} + + + +/* + * Various more or less common span classes. + */ +.tmspan-offline { + color: #f08020; + font-size: 0.75em; +} + +.tmspan-online { + font-size: 0.75em; +} + +.tmspan-name, .tmspan-osarch { + font-weight: bold; +} + +.tmspan-osver1 { + font-style: italic; +} + +.tmspan-osver2 { + font-style: normal; +} + + +/* + * Subversion tooltip. + */ +.tmvcstooltip { + padding: 0px; + min-width: 50em; + overflow: hidden; + border: 0px none; +} + +.tmvcstooltip iframe { + padding: 0px; + margin: 0px; + border: 0px none; + width: 100%; + //overflow: auto; + overflow: hidden; +} + +.tmvcstooltipnew { + padding: 0px; + min-width: 50em; + overflow: hidden; + border: 0px none; + background-color: #f9f9f9; +} + + +/* + * Workaround for flickering tooltips in the column bar graphs (see + * https://github.com/google/google-visualization-issues/issues/2162). + */ +.google-visualization-tooltip { + pointer-events: none; +} + diff --git a/src/VBox/ValidationKit/testmanager/htdocs/css/details.css b/src/VBox/ValidationKit/testmanager/htdocs/css/details.css new file mode 100644 index 00000000..0e3c367b --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/htdocs/css/details.css @@ -0,0 +1,216 @@ +/* $Id: details.css $ */ +/** @file + * Test Manager - Test Details CSS. + */ + +/* + * Copyright (C) 2012-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + + +/* + * The test details page has no side menu, so adjust the top-menu and main + * sections so they start at the left border. + */ + +#top-menu, #main { + left: 0; +} +#main { + margin-left: 0px; +} + +.tmtbl-events { + +} + +.tmstatusrow-failure, .tmstatusrow-timed-out, .tmstatusrow-rebooted { + color: #e80000; +} + +.tmstatusrow-skipped, .tmstatusrow-aborted, .tmstatusrow-bad-testbox { + color: #0000f0; +} + + +/* + * Test results. + */ + +/* + * Details table on the individual test result page. + */ +table.tmtbl-testresult-details { + border-style: dashed; + border-spacing: 1px; + border-width: 1px; + border-color: gray; + border-collapse: separate; +} + +table.tmtbl-testresult-details caption { + text-align: left; + font-weight: bold; + font-size: 1.2em; +} + +table.tmtbl-testresult-details td, table.tmtbl-testresult-details th { + font-size: 1em; + border-style: none; + padding-bottom: 3px; + padding-top: 3px; + padding-left: 2px; + padding-right: 2px; + border-width: 1px; +} + +table.tmtbl-testresult-details th { + text-align: left; +} + +.tmtbl-result-details-caption { + font-size: 1.2em; + font-weight: bold; + text-align: center; + background-color: #c0d0e0; +} + +.tmtbl-result-details-subcaption { + text-align: center; +} + + +/* + * Event log on the individual test result page. + */ +.tmtbl-events td { + padding-bottom: 1px; + padding-top: 1px; + padding-left: 1px; + padding-right: 1px; + vertical-align: top; +} + +.tmtbl-events th { + font-size: 1.3em; + text-align: center; +} + +table.tmtbl-events, table.tmtbl-events tr, table.tmtbl-events td, table.tmtbl-events th { + border-collapse: collapse; +} + +tr.tmtbl-events-leaf { +} + +tr.tmtbl-events-first { + border-top: 1px dotted; +} + +tr.tmtbl-events-value { +} + +tr.tmtbl-events-final { + border-bottom: 1px dotted; +} + + +tr.tmtbl-events-lvl0 td { + padding-top: 8px; + padding-bottom: 8px; +} + +tr.tmtbl-events-lvl1 td { + padding-top: 6px; + padding-bottom: 6px; +} + +tr.tmtbl-events-lvl2 td { + padding-top: 4px; + padding-bottom: 4px; +} + +tr.tmtbl-events-lvl3 td { + padding-top: 2px; + padding-bottom: 2px; +} + +tr.tmtbl-events-lvl4 td { + padding-top: 1px; + padding-bottom: 1px; +} + +tr.tmtbl-events-lvl5 td, +tr.tmtbl-events-lvl6 td, +tr.tmtbl-events-lvl7 td, +tr.tmtbl-events-lvl8 td, +tr.tmtbl-events-lvl9 td, +tr.tmtbl-events-lvl10 td { + padding-top: 0px; + padding-bottom: 0px; +} + +td.tmtbl-events-number { + text-align: right; +} + +td.tmtbl-events-number, td.tmtbl-events-unit { +} + +tr.tmtbl-events-value td:nth-child(3), +tr.tmtbl-events-file td:nth-child(3), +tr.tmtbl-events-message td:nth-child(3) { + padding-left: 2em; +} + +tr.tmtbl-events-value td:nth-child(3), +tr.tmtbl-events-message td:nth-child(3) { + font-style: italic; +} + + +/* + * Status coloring. (move to common.css?) + */ +.tmspan-status-success { + color: green; +} +.tmspan-status-skipped { + color: blue; +} +.tmspan-status-failure { + color: red; +} +.tmspan-status-success, .tmspan-status-skipped, .tmspan-status-failure { + font-weight: bold; + text-transform: uppercase; +} + diff --git a/src/VBox/ValidationKit/testmanager/htdocs/css/graphwiz.css b/src/VBox/ValidationKit/testmanager/htdocs/css/graphwiz.css new file mode 100644 index 00000000..69612bfb --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/htdocs/css/graphwiz.css @@ -0,0 +1,237 @@ +/* $Id: graphwiz.css $ */ +/** @file + * Test Manager - Graph Wizard CSS. + */ + +/* + * Copyright (C) 2012-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + + +/* + * The graph wizard page currently has no side menu, so adjust the top-menu + * and main sections so they start at the left border. + */ + +#main { + margin-left: 0; +} + +.tmtbl-events { + +} + +/* + * Let the top navigation and end selection inputs look alike. + */ +#graphwiz-nav, #graphwiz-end-selection { + background-color: #c0cbd6; + padding-left: 3px; + padding-right: 3px; + padding-top: 3px; + padding-bottom: 3px; + margin-left: 1px; + margin-right: 1px; + margin-top: 3px; + margin-bottom: 3px; + width: 100%; +} + + +/* + * Navigation and it's inputs. + */ + +#graphwiz-nav { + min-height: 4.2em; +} + +#graphwiz-top-1, #graphwiz-top-2 { + clear: both; +} + +#graphwiz-time, #graphwiz-top-options-1, #graphwiz-top-submit, #graphwiz-top-options-2 { + display: block; +} + +#graphwiz-time, #graphwiz-top-submit { + margin-left: 1em; + margin-right: 2em; + float: left; +} + +#graphwiz-top-options-1, #graphwiz-top-options-2 { + margin-left: 2em; + margin-right: 1em; + float: right; +} + +.graphwiz-pixel-input, .graphwiz-dpi-input, .graphwiz-time-input, .graphwiz-period-input { + margin-top: 0.2em; + margin-bottom: 0.2em; +} + +.graphwiz-pixel-input { + width: 3em; + text-align: right +} + +.graphwiz-dpi-input { + width: 2em; + text-align: right +} + +.graphwiz-time-input { + width: 18em; + text-align: left +} + +.graphwiz-period-input { + width: 4em; + text-align: right +} + +.graphwiz-maxerrorbar-input { + width: 2em; + text-align: right; +} + +.graphwiz-fontsize-input { + width: 2em; + text-align: right; +} + +.graphwiz-maxpergraph-input { + width: 2em; + text-align: right; +} + +/* + * The graphs. + */ +#graphwiz-graphs { + margin-top: 0.5em; +} + +.graphwiz-collection { + margin-top: 1em; + background-color: #f0f0f0; + padding-bottom: 1em; +} + +.graphwiz-src-select { + margin-left: 0.2em; + margin-right: 0.2em; + margin-top: 0.2em; + margin-bottom: 0.2em; + padding-left: 0.3em; + padding-top: 0.3em; + padding-bottom: 0.3em; + padding-right: 0.3em; + font-size: 1.4em; +} + +.graphwiz-graph { + margin-left: 1em; + margin-right: 1em; +} + +.graphwiz-graph svg { + width: 100%; +} + +/* + * Table data. + */ +table.graphwiz-tab { + width: auto; +} + +.graphwiz-tab td { + text-align: right; +} + +/* + * The end selection. + */ +#graphwiz-end-selection { + margin-top: 1em; +} + +.graphwiz-end-selection-group { + clear: both; + display: block; +} + +.graphwiz-end-selection-group li { + display: block; + width: 25%; + float: left; +} + +#graphwiz-buildcategories li, #graphwiz-testcase-variations li { + width: 50%; +} + +.graphwiz-end-selection-group label { + margin-left: 0.3em; + vertical-align: middle; +} + +.graphwiz-end-selection-group input { + vertical-align: middle; +} + +.graphwiz-end-selection-group h3 { + font-size: 1.2em; + font-style: italic; + font-weight: bold; + margin-bottom: 0.26em; +} + +#graphwiz-buildcategories h3, #graphwiz-testcase-variations h3, #graphwiz-end-submit { + padding-top: 1em; +} + +#graphwiz-end-submit { + clear: both; + display: block; +} + + + +/* + * Tool tip tables. + */ +table.graphwiz-tt td:nth-child(1) { + font-weight: bold; +} + diff --git a/src/VBox/ValidationKit/testmanager/htdocs/css/tooltip.css b/src/VBox/ValidationKit/testmanager/htdocs/css/tooltip.css new file mode 100644 index 00000000..c714c36b --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/htdocs/css/tooltip.css @@ -0,0 +1,132 @@ +/* $Id: tooltip.css $ */ +/** @file + * Test Manager - Tooltip content (via iframe). + */ + +/* + * Copyright (C) 2012-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/* + * Form the main divs in template-tooltip.html. + */ +.tooltip-main { + width: 100%; +} + +.tooltip-inner { + clear: both; + border: 2px solid black; + padding-left: 2px; + padding-right: 2px; + padding-top: 2px; + padding-bottom: 2px +} + +/* + * Timeline tooltip. + */ +.tmtimelinetooltip { + font-size: 1em; +} + +/* + * Relative stuff that could also be used for a non-tooltip VCS timeline. + */ +.tmvcstimeline-highlighted { + background: #f0f0f0; +} + +.tmvcstimeline h2 { + clear: both; + font-size: 120%; + background: #e8e8e8; + border-bottom: 1px solid #c8c8c8; + border-radius: 3px; + margin-left: 0.2em; + margin-right: 0.2em; + margin-top: 0.2em; + margin-bottom: 0.4em; + padding-left: 0.2em; + padding-right: 0.2em; + padding-top: 0.2em; + padding-bottom: 0.2em; +} + +.tmvcstimeline dl { + margin-left: 0.8em; + margin-right: 0.2em; + margin-top: 0.2em; + margin-bottom: 0.8em; +} + +.tmvcstimeline dt { + font-size: 118%; + padding-left: 0.2em; + margin-top: 0.1em; + margin-bottom: 0.0em; +} + +.tmvcstimeline dt, .tmvcstimeline :link, .tmvcstimeline :link:visited, .tmvcstimeline :link:hover { + color: black; + text-decoration: none; +} + +.tmvcstimeline :link:hover { + border: 1px dotted black; +} + +.tmvcstimeline-time { + font-size: 88%; + margin-right: 0.2em; +} + +.tmvcstimeline-time, .tmvcstimeline-author { + color: #5858a0; +} + +.tmvcstimeline-rev { + color: #0000ee; +} + +.tmvcstimeline dd { + padding-left: 2em; + margin-top: 0.0em; + margin-bottom: 0.4em; + color: #424250; +} + +/* This helps highlighting the revision we're showing the tooltip for. */ +.tmvcstimeline-highlight, .tmvcstimeline :target, .tmvcstimeline :target + dd { + background-color: #d8e8ff; + padding-top: 0.2em; + padding-bottom: 0.2em; +} + diff --git a/src/VBox/ValidationKit/testmanager/htdocs/images/VirtualBox.svg b/src/VBox/ValidationKit/testmanager/htdocs/images/VirtualBox.svg new file mode 100644 index 00000000..2369828b --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/htdocs/images/VirtualBox.svg @@ -0,0 +1,806 @@ +<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="256px" height="256px" viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve">
+<g>
+ <rect fill="none" width="256" height="256"/>
+ <g>
+
+ <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-180.1821" y1="784.8389" x2="-56.9487" y2="784.8389" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0" style="stop-color:#0A2B4D"/>
+ <stop offset="0.0423" style="stop-color:#7DA5DC"/>
+ <stop offset="0.0441" style="stop-color:#83A9DE"/>
+ <stop offset="0.0553" style="stop-color:#A1BFE8"/>
+ <stop offset="0.067" style="stop-color:#B9D0F0"/>
+ <stop offset="0.0794" style="stop-color:#CADCF6"/>
+ <stop offset="0.093" style="stop-color:#D4E4F9"/>
+ <stop offset="0.1099" style="stop-color:#D7E6FA"/>
+ <stop offset="0.254" style="stop-color:#D0DCFA"/>
+ <stop offset="0.42" style="stop-color:#85A0C8"/>
+ <stop offset="0.57" style="stop-color:#2E4573"/>
+ <stop offset="0.6978" style="stop-color:#14335E"/>
+ <stop offset="0.7692" style="stop-color:#1C3866"/>
+ <stop offset="0.8462" style="stop-color:#4F73AA"/>
+ <stop offset="0.9153" style="stop-color:#6487B9"/>
+ <stop offset="1" style="stop-color:#1A3B61"/>
+ </linearGradient>
+ <path fill="url(#SVGID_1_)" d="M129.117,141.783c33.426,0,60.732,23.482,60.732,52.32c0,1.838,0,4.744,0,6.578
+ c0,28.838-27.308,51.803-60.732,51.803c-33.503,0-60.811-22.965-60.811-51.803c0-1.834,0-4.74,0-6.578
+ C68.307,165.266,95.614,141.783,129.117,141.783z"/>
+
+ <linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="-150.8486" y1="822.7695" x2="-84.4476" y2="740.7712" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0" style="stop-color:#E1E6FA"/>
+ <stop offset="0.07" style="stop-color:#B4C3E1"/>
+ <stop offset="0.11" style="stop-color:#8293B8"/>
+ <stop offset="0.1551" style="stop-color:#2D4173"/>
+ <stop offset="0.2888" style="stop-color:#1B2F61"/>
+ <stop offset="0.5" style="stop-color:#14285A"/>
+ <stop offset="0.7" style="stop-color:#14285A"/>
+ <stop offset="0.8663" style="stop-color:#213970"/>
+ <stop offset="1" style="stop-color:#4164A5"/>
+ </linearGradient>
+ <ellipse fill="url(#SVGID_2_)" cx="129.041" cy="194.065" rx="57.889" ry="49.1"/>
+ <defs>
+ <filter id="Adobe_OpacityMaskFilter" filterUnits="userSpaceOnUse" x="70.895" y="144.746" width="116.292" height="98.638">
+ <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
+ </filter>
+ </defs>
+ <mask maskUnits="userSpaceOnUse" x="70.895" y="144.746" width="116.292" height="98.638" id="SVGID_3_">
+ <g filter="url(#Adobe_OpacityMaskFilter)">
+
+ <linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="-85.9404" y1="823.8486" x2="-149.3573" y2="739.6917" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0" style="stop-color:#CCCCCC"/>
+ <stop offset="0.15" style="stop-color:#000000"/>
+ </linearGradient>
+ <ellipse fill="url(#SVGID_4_)" cx="129.041" cy="194.065" rx="58.146" ry="49.319"/>
+ </g>
+ </mask>
+
+ <radialGradient id="SVGID_5_" cx="-148.8311" cy="824.0654" r="100.2425" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#DCE6FA"/>
+ <stop offset="0.4" style="stop-color:#AAC3EB"/>
+ <stop offset="1" style="stop-color:#AAC3EB"/>
+ </radialGradient>
+ <ellipse mask="url(#SVGID_3_)" fill="url(#SVGID_5_)" cx="129.041" cy="194.065" rx="58.146" ry="49.319"/>
+ <defs>
+ <filter id="Adobe_OpacityMaskFilter_1_" filterUnits="userSpaceOnUse" x="70.895" y="144.746" width="116.292" height="98.638">
+ <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
+ </filter>
+ </defs>
+ <mask maskUnits="userSpaceOnUse" x="70.895" y="144.746" width="116.292" height="98.638" id="SVGID_6_">
+ <g filter="url(#Adobe_OpacityMaskFilter_1_)">
+
+ <linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="-117.6489" y1="831.0889" x2="-117.6489" y2="732.4512" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.05" style="stop-color:#000000"/>
+ </linearGradient>
+ <ellipse fill="url(#SVGID_7_)" cx="129.041" cy="194.065" rx="58.146" ry="49.319"/>
+ </g>
+ </mask>
+
+ <radialGradient id="SVGID_8_" cx="-148.8311" cy="824.0654" r="100.2425" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#DCE6FA"/>
+ <stop offset="0.4" style="stop-color:#AAC3EB"/>
+ <stop offset="1" style="stop-color:#AAC3EB"/>
+ </radialGradient>
+ <ellipse mask="url(#SVGID_6_)" fill="url(#SVGID_8_)" cx="129.041" cy="194.065" rx="58.146" ry="49.319"/>
+ <defs>
+ <filter id="Adobe_OpacityMaskFilter_2_" filterUnits="userSpaceOnUse" x="70.895" y="144.746" width="116.292" height="98.638">
+ <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
+ </filter>
+ </defs>
+ <mask maskUnits="userSpaceOnUse" x="70.895" y="144.746" width="116.292" height="98.638" id="SVGID_9_">
+ <g filter="url(#Adobe_OpacityMaskFilter_2_)">
+
+ <linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="-100.396" y1="829.1719" x2="-134.902" y2="734.3676" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0" style="stop-color:#595959"/>
+ <stop offset="0.0535" style="stop-color:#282828"/>
+ <stop offset="0.1" style="stop-color:#000000"/>
+ </linearGradient>
+ <ellipse fill="url(#SVGID_10_)" cx="129.041" cy="194.065" rx="58.146" ry="49.319"/>
+ </g>
+ </mask>
+
+ <radialGradient id="SVGID_11_" cx="-148.8311" cy="824.0654" r="100.2425" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#DCE6FA"/>
+ <stop offset="0.4" style="stop-color:#AAC3EB"/>
+ <stop offset="1" style="stop-color:#AAC3EB"/>
+ </radialGradient>
+ <ellipse mask="url(#SVGID_9_)" fill="url(#SVGID_11_)" cx="129.041" cy="194.065" rx="58.146" ry="49.319"/>
+ <defs>
+ <filter id="Adobe_OpacityMaskFilter_3_" filterUnits="userSpaceOnUse" x="70.895" y="144.746" width="116.292" height="98.638">
+ <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
+ </filter>
+ </defs>
+ <mask maskUnits="userSpaceOnUse" x="70.895" y="144.746" width="116.292" height="98.638" id="SVGID_12_">
+ <g filter="url(#Adobe_OpacityMaskFilter_3_)">
+
+ <linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="-134.9023" y1="829.1719" x2="-100.3965" y2="734.368" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0" style="stop-color:#FFFFFF"/>
+ <stop offset="0.05" style="stop-color:#1A1A1A"/>
+ <stop offset="0.09" style="stop-color:#000000"/>
+ </linearGradient>
+ <ellipse fill="url(#SVGID_13_)" cx="129.041" cy="194.065" rx="58.146" ry="49.319"/>
+ </g>
+ </mask>
+
+ <radialGradient id="SVGID_14_" cx="-148.8311" cy="824.0654" r="100.2425" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#DCE6FA"/>
+ <stop offset="0.4" style="stop-color:#AAC3EB"/>
+ <stop offset="1" style="stop-color:#AAC3EB"/>
+ </radialGradient>
+ <ellipse mask="url(#SVGID_12_)" fill="url(#SVGID_14_)" cx="129.041" cy="194.065" rx="58.146" ry="49.319"/>
+
+ <linearGradient id="SVGID_15_" gradientUnits="userSpaceOnUse" x1="-178.3818" y1="813.666" x2="-56.8384" y2="813.666" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0.0055" style="stop-color:#3C5A78"/>
+ <stop offset="0.033" style="stop-color:#6E8CBE"/>
+ <stop offset="0.123" style="stop-color:#A5BEE1"/>
+ <stop offset="0.25" style="stop-color:#96AFD2"/>
+ <stop offset="0.52" style="stop-color:#3C5A87"/>
+ <stop offset="0.72" style="stop-color:#2B486D"/>
+ <stop offset="0.9" style="stop-color:#466491"/>
+ <stop offset="1" style="stop-color:#3C5082"/>
+ </linearGradient>
+ <path fill="url(#SVGID_15_)" d="M189.852,198.923c0,0.61,0,1.222,0,1.759c0,28.838-27.309,52.318-60.733,52.318
+ c-33.501,0-60.81-23.48-60.81-52.318c0-0.537,0-1.146,0-1.759c0,28.761,27.308,52.243,60.81,52.243
+ C162.543,251.166,189.852,227.684,189.852,198.923z"/>
+
+ <linearGradient id="SVGID_16_" gradientUnits="userSpaceOnUse" x1="-178.1328" y1="776.4775" x2="-57.1652" y2="787.0609" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0.0053" style="stop-color:#9BBEE1"/>
+ <stop offset="0.0802" style="stop-color:#D2E6FA"/>
+ <stop offset="0.1337" style="stop-color:#F0F5FF"/>
+ <stop offset="0.1979" style="stop-color:#F0F5FF"/>
+ <stop offset="0.35" style="stop-color:#DEE6F0"/>
+ <stop offset="0.55" style="stop-color:#AFBEDC"/>
+ <stop offset="0.7" style="stop-color:#96AFD7"/>
+ <stop offset="0.9091" style="stop-color:#A0B4DC"/>
+ <stop offset="1" style="stop-color:#6487AF"/>
+ </linearGradient>
+ <path fill="url(#SVGID_16_)" d="M129.041,141.693c-33.563,0-60.771,23.449-60.771,52.371s27.208,52.371,60.771,52.371
+ c33.564,0,60.771-23.449,60.771-52.371C189.813,165.145,162.604,141.693,129.041,141.693z M129.041,243.165
+ c-31.971,0-57.887-21.983-57.887-49.101c0-27.115,25.916-49.104,57.887-49.104c31.973,0,57.889,21.986,57.889,49.104
+ S161.014,243.165,129.041,243.165z"/>
+ <defs>
+ <filter id="Adobe_OpacityMaskFilter_4_" filterUnits="userSpaceOnUse" x="68.27" y="141.693" width="121.542" height="104.742">
+ <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
+ </filter>
+ </defs>
+ <mask maskUnits="userSpaceOnUse" x="68.27" y="141.693" width="121.542" height="104.742" id="SVGID_17_">
+ <g filter="url(#Adobe_OpacityMaskFilter_4_)">
+
+ <linearGradient id="SVGID_18_" gradientUnits="userSpaceOnUse" x1="-117.6489" y1="834.1406" x2="-117.6489" y2="729.4004" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0.35" style="stop-color:#000000"/>
+ <stop offset="0.6" style="stop-color:#FFFFFF"/>
+ </linearGradient>
+ <ellipse fill="url(#SVGID_18_)" cx="129.041" cy="194.065" rx="60.771" ry="52.37"/>
+ </g>
+ </mask>
+ <path mask="url(#SVGID_17_)" fill="#142355" d="M129.041,141.693c-33.563,0-60.771,23.449-60.771,52.371
+ s27.208,52.371,60.771,52.371c33.564,0,60.771-23.449,60.771-52.371C189.813,165.145,162.604,141.693,129.041,141.693z
+ M129.041,243.165c-31.971,0-57.887-21.983-57.887-49.101c0-27.115,25.916-49.104,57.887-49.104
+ c31.973,0,57.889,21.986,57.889,49.104S161.014,243.165,129.041,243.165z"/>
+ <defs>
+ <filter id="Adobe_OpacityMaskFilter_5_" filterUnits="userSpaceOnUse" x="68.27" y="141.693" width="121.542" height="104.742">
+ <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
+ </filter>
+ </defs>
+ <mask maskUnits="userSpaceOnUse" x="68.27" y="141.693" width="121.542" height="104.742" id="SVGID_19_">
+ <g filter="url(#Adobe_OpacityMaskFilter_5_)">
+
+ <linearGradient id="SVGID_20_" gradientUnits="userSpaceOnUse" x1="-135.9253" y1="831.9824" x2="-99.3735" y2="731.5574" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0.3" style="stop-color:#000000"/>
+ <stop offset="1" style="stop-color:#CCCCCC"/>
+ </linearGradient>
+ <ellipse fill="url(#SVGID_20_)" cx="129.041" cy="194.065" rx="60.771" ry="52.37"/>
+ </g>
+ </mask>
+ <path mask="url(#SVGID_19_)" fill="#142355" d="M129.041,141.693c-33.563,0-60.771,23.449-60.771,52.371
+ s27.208,52.371,60.771,52.371c33.564,0,60.771-23.449,60.771-52.371C189.813,165.145,162.604,141.693,129.041,141.693z
+ M129.041,243.165c-31.971,0-57.887-21.983-57.887-49.101c0-27.115,25.916-49.104,57.887-49.104
+ c31.973,0,57.889,21.986,57.889,49.104S161.014,243.165,129.041,243.165z"/>
+
+ <radialGradient id="SVGID_21_" cx="-242.0352" cy="-534.5098" r="111.9119" gradientTransform="matrix(1 0 0 -1 370.0347 -414.4775)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#FFFFFF"/>
+ <stop offset="1" style="stop-color:#C3D2F0"/>
+ </radialGradient>
+ <path fill="url(#SVGID_21_)" d="M234.529,52.965l-8.549,108.286c-0.113,1.454-0.854,2.812-2.012,3.699l-92.826,71.046
+ c-0.933,0.714-2.037,1.068-3.144,1.068c-1.105,0-2.21-0.354-3.14-1.068l-92.832-71.045c-1.157-0.889-1.897-2.246-2.011-3.7
+ L21.47,52.965c-0.173-2.195,1.062-4.258,3.078-5.141L125.929,3.433c1.318-0.577,2.827-0.577,4.147,0l101.375,44.393
+ C233.467,48.708,234.703,50.771,234.529,52.965z"/>
+
+ <linearGradient id="SVGID_22_" gradientUnits="userSpaceOnUse" x1="-121.1333" y1="710.2393" x2="-217.5764" y2="629.314" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0" style="stop-color:#FFFFFF"/>
+ <stop offset="0.9" style="stop-color:#F0F5FF"/>
+ <stop offset="0.96" style="stop-color:#DCE6F8"/>
+ <stop offset="0.99" style="stop-color:#C3D2F0"/>
+ </linearGradient>
+ <path fill="url(#SVGID_22_)" d="M22.205,49.877c-1.1,1.812,2.384,3.694,2.678,3.872l99.186,60.164
+ c1.081,0.655,1.748,1.842,1.752,3.109l4.361-0.001c0.004-1.266,0.672-2.454,1.752-3.109l-1.959-3.612
+ c-0.578,0.348-1.229,0.521-1.882,0.521c-0.652,0-1.305-0.173-1.883-0.521L26.503,50.528c-0.292-0.175-0.46-0.499-0.439-0.837
+ C26.063,49.691,23.283,48.1,22.205,49.877z"/>
+
+ <linearGradient id="SVGID_23_" gradientUnits="userSpaceOnUse" x1="-106.9102" y1="716.4375" x2="-29.1729" y2="623.7938" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0" style="stop-color:#FFFFFF"/>
+ <stop offset="0.6" style="stop-color:#F5FAFF"/>
+ <stop offset="0.92" style="stop-color:#E1E6FA"/>
+ <stop offset="0.97" style="stop-color:#CFDAF4"/>
+ <stop offset="1" style="stop-color:#C0CFEE"/>
+ </linearGradient>
+ <path fill="url(#SVGID_23_)" d="M233.797,49.876c1.1,1.812-2.385,3.694-2.678,3.872l-99.188,60.163
+ c-1.08,0.655-1.748,1.842-1.752,3.109l-4.36-0.001c-0.005-1.266-0.672-2.454-1.752-3.109l1.958-3.612
+ c0.579,0.348,1.231,0.521,1.882,0.521c0.652,0,1.304-0.172,1.882-0.521l99.707-59.771c0.291-0.175,0.461-0.499,0.438-0.837
+ C229.938,49.69,232.719,48.099,233.797,49.876z"/>
+
+ <linearGradient id="SVGID_24_" gradientUnits="userSpaceOnUse" x1="-118.689" y1="698.002" x2="-118.689" y2="824.7695" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0" style="stop-color:#FFFFFF"/>
+ <stop offset="0.9" style="stop-color:#DCE6FF"/>
+ <stop offset="1" style="stop-color:#C2D4F0"/>
+ </linearGradient>
+ <path fill="url(#SVGID_24_)" d="M128,237.064c-2.653,0-2.922-3.642-2.922-3.642c0.138,0,0.278-0.03,0.405-0.096
+ c0.31-0.155,0.507-0.474,0.507-0.819l-0.17-115.49c-0.005-1.267-0.672-2.454-1.752-3.109l2.142-3.612
+ c0.579,0.348,1.231,0.52,1.883,0.52c0.65,0,1.304-0.172,1.882-0.52l1.959,3.611c-1.082,0.655-1.748,1.843-1.752,3.109
+ l-0.17,115.49c0,0.349,0.194,0.665,0.506,0.819c0.127,0.063,0.268,0.095,0.403,0.095C130.922,233.423,130.654,237.064,128,237.064
+ z"/>
+ <defs>
+ <filter id="Adobe_OpacityMaskFilter_6_" filterUnits="userSpaceOnUse" x="21.454" y="3" width="213.092" height="234.064">
+ <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
+ </filter>
+ </defs>
+ <mask maskUnits="userSpaceOnUse" x="21.454" y="3" width="213.092" height="234.064" id="SVGID_25_">
+ <g filter="url(#Adobe_OpacityMaskFilter_6_)">
+
+ <linearGradient id="SVGID_26_" gradientUnits="userSpaceOnUse" x1="-225.665" y1="639.6367" x2="-23.757" y2="755.2705" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0.78" style="stop-color:#000000"/>
+ <stop offset="0.8" style="stop-color:#FFFFFF"/>
+ </linearGradient>
+ <path fill="url(#SVGID_26_)" d="M234.529,52.965l-8.549,108.286c-0.113,1.454-0.854,2.812-2.012,3.699l-92.826,71.046
+ c-0.933,0.714-2.037,1.068-3.144,1.068c-1.105,0-2.21-0.354-3.14-1.068l-92.832-71.045c-1.157-0.889-1.897-2.246-2.011-3.7
+ L21.47,52.965c-0.173-2.195,1.062-4.258,3.078-5.141L125.929,3.433c1.318-0.577,2.827-0.577,4.147,0l101.375,44.393
+ C233.467,48.708,234.703,50.771,234.529,52.965z"/>
+ </g>
+ </mask>
+
+ <linearGradient id="SVGID_27_" gradientUnits="userSpaceOnUse" x1="-213.625" y1="755.2715" x2="-11.7157" y2="639.637" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0.2" style="stop-color:#C3D2F0"/>
+ <stop offset="0.22" style="stop-color:#7DA0D2"/>
+ <stop offset="0.2674" style="stop-color:#6E91CD"/>
+ <stop offset="0.7" style="stop-color:#7396D2"/>
+ <stop offset="1" style="stop-color:#5A78B4"/>
+ </linearGradient>
+ <path mask="url(#SVGID_25_)" fill="url(#SVGID_27_)" d="M234.529,52.965l-8.549,108.286c-0.113,1.454-0.854,2.812-2.012,3.699
+ l-92.826,71.046c-0.933,0.714-2.037,1.068-3.144,1.068c-1.105,0-2.21-0.354-3.14-1.068l-92.832-71.045
+ c-1.157-0.889-1.897-2.246-2.011-3.7L21.47,52.965c-0.173-2.195,1.062-4.258,3.078-5.141L125.929,3.433
+ c1.318-0.577,2.827-0.577,4.147,0l101.375,44.393C233.467,48.708,234.703,50.771,234.529,52.965z"/>
+ <defs>
+ <filter id="Adobe_OpacityMaskFilter_7_" filterUnits="userSpaceOnUse" x="21.454" y="3" width="213.092" height="234.064">
+ <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
+ </filter>
+ </defs>
+ <mask maskUnits="userSpaceOnUse" x="21.454" y="3" width="213.092" height="234.064" id="SVGID_28_">
+ <g filter="url(#Adobe_OpacityMaskFilter_7_)">
+
+ <linearGradient id="SVGID_29_" gradientUnits="userSpaceOnUse" x1="-213.522" y1="755.417" x2="-12.0263" y2="639.0833" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0.2" style="stop-color:#FFFFFF"/>
+ <stop offset="0.22" style="stop-color:#000000"/>
+ </linearGradient>
+ <path fill="url(#SVGID_29_)" d="M234.529,52.965l-8.549,108.286c-0.113,1.454-0.854,2.812-2.012,3.699l-92.826,71.046
+ c-0.933,0.714-2.037,1.068-3.144,1.068c-1.105,0-2.21-0.354-3.14-1.068l-92.832-71.045c-1.157-0.889-1.897-2.246-2.011-3.7
+ L21.47,52.965c-0.173-2.195,1.062-4.258,3.078-5.141L125.929,3.433c1.318-0.577,2.827-0.577,4.147,0l101.375,44.393
+ C233.467,48.708,234.703,50.771,234.529,52.965z"/>
+ </g>
+ </mask>
+
+ <linearGradient id="SVGID_30_" gradientUnits="userSpaceOnUse" x1="-225.354" y1="639.084" x2="-23.8602" y2="755.4165" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0" style="stop-color:#B9C8DC"/>
+ <stop offset="0.3" style="stop-color:#D7D5EB"/>
+ <stop offset="0.5" style="stop-color:#D7DCEB"/>
+ <stop offset="0.78" style="stop-color:#DCDCEB"/>
+ <stop offset="0.8" style="stop-color:#C3D2F0"/>
+ </linearGradient>
+ <path mask="url(#SVGID_28_)" fill="url(#SVGID_30_)" d="M234.529,52.965l-8.549,108.286c-0.113,1.454-0.854,2.812-2.012,3.699
+ l-92.826,71.046c-0.933,0.714-2.037,1.068-3.144,1.068c-1.105,0-2.21-0.354-3.14-1.068l-92.832-71.045
+ c-1.157-0.889-1.897-2.246-2.011-3.7L21.47,52.965c-0.173-2.195,1.062-4.258,3.078-5.141L125.929,3.433
+ c1.318-0.577,2.827-0.577,4.147,0l101.375,44.393C233.467,48.708,234.703,50.771,234.529,52.965z"/>
+ <defs>
+ <filter id="Adobe_OpacityMaskFilter_8_" filterUnits="userSpaceOnUse" x="21.454" y="3" width="213.092" height="234.064">
+ <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
+ </filter>
+ </defs>
+ <mask maskUnits="userSpaceOnUse" x="21.454" y="3" width="213.092" height="234.064" id="SVGID_31_">
+ <g filter="url(#Adobe_OpacityMaskFilter_8_)">
+
+ <radialGradient id="SVGID_32_" cx="-214.9419" cy="621.7891" r="31.5227" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#595959"/>
+ <stop offset="1" style="stop-color:#000000"/>
+ </radialGradient>
+ <path fill="url(#SVGID_32_)" d="M234.529,52.965l-8.549,108.286c-0.113,1.454-0.854,2.812-2.012,3.699l-92.826,71.046
+ c-0.933,0.714-2.037,1.068-3.144,1.068c-1.105,0-2.21-0.354-3.14-1.068l-92.832-71.045c-1.157-0.889-1.897-2.246-2.011-3.7
+ L21.47,52.965c-0.173-2.195,1.062-4.258,3.078-5.141L125.929,3.433c1.318-0.577,2.827-0.577,4.147,0l101.375,44.393
+ C233.467,48.708,234.703,50.771,234.529,52.965z"/>
+ </g>
+ </mask>
+ <path mask="url(#SVGID_31_)" fill="#E1F5FF" d="M234.529,52.965l-8.549,108.286c-0.113,1.454-0.854,2.812-2.012,3.699
+ l-92.826,71.046c-0.933,0.714-2.037,1.068-3.144,1.068c-1.105,0-2.21-0.354-3.14-1.068l-92.832-71.045
+ c-1.157-0.889-1.897-2.246-2.011-3.7L21.47,52.965c-0.173-2.195,1.062-4.258,3.078-5.141L125.929,3.433
+ c1.318-0.577,2.827-0.577,4.147,0l101.375,44.393C233.467,48.708,234.703,50.771,234.529,52.965z"/>
+ <g>
+
+ <radialGradient id="SVGID_33_" cx="-118.6724" cy="642.2432" r="64.9423" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#19416E"/>
+ <stop offset="1" style="stop-color:#0A2D64"/>
+ </radialGradient>
+ <path fill="url(#SVGID_33_)" d="M209.039,50.425l-79.438,46.227c-0.477,0.275-1.002,0.412-1.528,0.412
+ c-0.527,0-1.053-0.136-1.528-0.412L46.999,50.426c-0.25-0.145-0.398-0.417-0.384-0.705c0.014-0.288,0.188-0.545,0.449-0.666
+ l79.834-36.784c0.746-0.346,1.604-0.346,2.354,0l79.721,36.783c0.262,0.122,0.435,0.377,0.447,0.667
+ C209.436,50.008,209.287,50.28,209.039,50.425z"/>
+
+ <radialGradient id="SVGID_34_" cx="-172.187" cy="727.8721" r="56.6285" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#19416E"/>
+ <stop offset="1" style="stop-color:#0A2D64"/>
+ </radialGradient>
+ <path fill="url(#SVGID_34_)" d="M114.98,123.905l0.871,83.976c0.002,0.327-0.178,0.628-0.471,0.775
+ c-0.123,0.063-0.257,0.096-0.391,0.096c-0.18,0-0.361-0.057-0.512-0.169l-74.515-54.917c-0.674-0.497-1.095-1.257-1.153-2.092
+ l-5.652-79.07c-0.024-0.324,0.138-0.634,0.417-0.8c0.277-0.166,0.626-0.162,0.902,0.008l79.063,49.616
+ C114.428,121.886,114.969,122.855,114.98,123.905z"/>
+
+ <radialGradient id="SVGID_35_" cx="-1001.7246" cy="782.7354" r="61.9365" gradientTransform="matrix(-0.9143 0 0 0.9143 -734.343 -575.489)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#19416E"/>
+ <stop offset="1" style="stop-color:#0A2D64"/>
+ </radialGradient>
+ <path fill="url(#SVGID_35_)" d="M141.057,123.904l-0.871,83.977c-0.002,0.326,0.179,0.627,0.472,0.775
+ c0.122,0.063,0.258,0.095,0.391,0.095c0.181,0,0.361-0.058,0.513-0.168l74.516-54.917c0.674-0.498,1.096-1.257,1.152-2.093
+ l5.651-79.07c0.022-0.324-0.139-0.634-0.416-0.8c-0.276-0.166-0.627-0.163-0.901,0.008L142.5,121.327
+ C141.609,121.885,141.068,122.854,141.057,123.904z"/>
+ </g>
+
+ <linearGradient id="SVGID_36_" gradientUnits="userSpaceOnUse" x1="-199.5181" y1="619.627" x2="-37.8291" y2="619.627" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0" style="stop-color:#234B82"/>
+ <stop offset="0.489" style="stop-color:#3C5A8C"/>
+ <stop offset="0.5112" style="stop-color:#E6F0FF"/>
+ <stop offset="0.7697" style="stop-color:#FFFFFF"/>
+ <stop offset="1" style="stop-color:#F0F5FF"/>
+ </linearGradient>
+ <path fill="url(#SVGID_36_)" d="M208.004,51.354c0.525-0.314,0.885-0.917,0.855-1.54c-0.027-0.646-0.422-1.267-1.006-1.537
+ l-78.097-36.02c-0.534-0.248-1.11-0.371-1.687-0.371s-1.153,0.123-1.688,0.373l-78.2,36.018c-0.586,0.27-1.01,0.89-1.01,1.525
+ c0,0.621,0.328,1.245,0.863,1.557l1.186,0.6c-0.243-0.141-0.387-0.405-0.373-0.686c0.014-0.279,0.182-0.53,0.437-0.647
+ l77.641-35.774c0.726-0.335,1.561-0.335,2.289,0l77.533,35.772c0.254,0.119,0.422,0.367,0.436,0.649
+ c0.015,0.279-0.131,0.544-0.371,0.685L208.004,51.354z"/>
+
+ <linearGradient id="SVGID_37_" gradientUnits="userSpaceOnUse" x1="-114.0732" y1="786.165" x2="-6.8749" y2="678.9667" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0" style="stop-color:#AFC3D7"/>
+ <stop offset="0.2033" style="stop-color:#BED2E6"/>
+ <stop offset="0.5879" style="stop-color:#BED7EB"/>
+ <stop offset="0.6154" style="stop-color:#3C5A8C"/>
+ <stop offset="1" style="stop-color:#234B82"/>
+ </linearGradient>
+ <path fill="url(#SVGID_37_)" d="M140.545,205.538l-0.002,0.062c-0.002,0.713,0.405,1.371,1.039,1.696
+ c0.271,0.142,0.572,0.212,0.871,0.212c0.393,0,0.795-0.121,1.137-0.375l71.861-52.972c0.945-0.697,1.549-1.777,1.627-2.957
+ l5.451-76.295c0.003-0.051,0.006-0.087,0.006-0.139c-0.003-0.675-0.354-1.291-0.93-1.635c-0.299-0.178-0.64-0.271-0.979-0.271
+ c-0.346,0-0.701,0.094-1.021,0.296l-0.818,0.53c0.266-0.164,0.6-0.167,0.865-0.008c0.266,0.159,0.424,0.457,0.398,0.767
+ l-5.425,75.887c-0.058,0.803-0.461,1.53-1.106,2.008l-71.517,52.707c-0.146,0.108-0.317,0.161-0.489,0.161
+ c-0.13,0-0.259-0.03-0.375-0.091c-0.281-0.143-0.455-0.43-0.453-0.744L140.545,205.538z"/>
+
+ <linearGradient id="SVGID_38_" gradientUnits="userSpaceOnUse" x1="-230.4624" y1="678.9688" x2="-123.2632" y2="786.1679" gradientTransform="matrix(1 0 0 1 246.6899 -587.7051)">
+ <stop offset="0" style="stop-color:#234B82"/>
+ <stop offset="0.3846" style="stop-color:#3C5A8C"/>
+ <stop offset="0.4045" style="stop-color:#F0F5FF"/>
+ <stop offset="0.691" style="stop-color:#FFFFFF"/>
+ <stop offset="1" style="stop-color:#FFFFFF"/>
+ </linearGradient>
+ <path fill="url(#SVGID_38_)" d="M115.354,204.379c0.003,0.314-0.171,0.604-0.451,0.744c-0.119,0.061-0.247,0.091-0.376,0.091
+ c-0.173,0-0.346-0.053-0.49-0.161L42.52,152.346c-0.646-0.478-1.05-1.205-1.106-2.008l-5.425-75.887
+ c-0.023-0.311,0.133-0.608,0.4-0.767c0.266-0.159,0.601-0.156,0.865,0.008l-0.818-0.53c-0.318-0.203-0.675-0.296-1.02-0.296
+ c-0.341,0-0.681,0.092-0.98,0.271c-0.576,0.344-0.926,0.96-0.928,1.635c0,0.052,0.002,0.087,0.004,0.139l5.452,76.295
+ c0.079,1.18,0.682,2.26,1.628,2.957l71.861,52.972c0.341,0.254,0.743,0.375,1.137,0.375c0.298,0,0.599-0.07,0.871-0.212
+ c0.633-0.325,1.04-0.983,1.038-1.696l-0.002-0.062L115.354,204.379z"/>
+
+ <linearGradient id="SVGID_39_" gradientUnits="userSpaceOnUse" x1="-242.0366" y1="-525.2964" x2="-242.0366" y2="-418.9517" gradientTransform="matrix(1 0 0 -1 370.0347 -414.4775)">
+ <stop offset="0" style="stop-color:#FFFFFF"/>
+ <stop offset="1" style="stop-color:#BEDCFA"/>
+ </linearGradient>
+ <path fill="url(#SVGID_39_)" d="M229.395,48.914l-99.83-44.127c-0.938-0.417-2.003-0.417-2.947,0L26.605,48.914
+ c-0.312,0.138-0.521,0.437-0.542,0.777c-0.021,0.339,0.147,0.662,0.439,0.837l99.707,59.771c0.578,0.348,1.231,0.52,1.883,0.52
+ c0.652,0,1.303-0.172,1.883-0.52l99.52-59.771c0.293-0.175,0.463-0.499,0.439-0.837C229.912,49.351,229.705,49.051,229.395,48.914
+ z M206.813,50.538l-77.256,44.957c-0.461,0.267-0.976,0.4-1.486,0.4c-0.513,0-1.024-0.132-1.486-0.4L49.224,50.539
+ c-0.243-0.141-0.387-0.405-0.373-0.686c0.013-0.279,0.182-0.53,0.437-0.647l77.641-35.774c0.726-0.335,1.561-0.335,2.29,0
+ l77.531,35.772c0.256,0.119,0.422,0.367,0.438,0.649C207.199,50.133,207.057,50.397,206.813,50.538z"/>
+ <defs>
+ <filter id="Adobe_OpacityMaskFilter_9_" filterUnits="userSpaceOnUse" x="26.061" y="4.475" width="203.875" height="106.344">
+ <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
+ </filter>
+ </defs>
+ <mask maskUnits="userSpaceOnUse" x="26.061" y="4.475" width="203.875" height="106.344" id="SVGID_40_">
+ <g filter="url(#Adobe_OpacityMaskFilter_9_)">
+
+ <linearGradient id="SVGID_41_" gradientUnits="userSpaceOnUse" x1="-318.0947" y1="-509.2944" x2="-164.9766" y2="-420.8915" gradientTransform="matrix(1 0 0 -1 370.0347 -414.4775)">
+ <stop offset="0.6" style="stop-color:#000000"/>
+ <stop offset="1" style="stop-color:#FFFFFF"/>
+ </linearGradient>
+ <path fill="url(#SVGID_41_)" d="M229.395,48.914l-99.83-44.127c-0.938-0.417-2.003-0.417-2.947,0L26.605,48.914
+ c-0.312,0.138-0.521,0.437-0.542,0.777c-0.021,0.339,0.147,0.662,0.439,0.837l99.707,59.771c0.578,0.348,1.231,0.52,1.883,0.52
+ c0.652,0,1.303-0.172,1.883-0.52l99.52-59.771c0.293-0.175,0.463-0.499,0.439-0.837
+ C229.912,49.351,229.705,49.051,229.395,48.914z M206.813,50.538l-77.256,44.957c-0.461,0.267-0.976,0.4-1.486,0.4
+ c-0.513,0-1.024-0.132-1.486-0.4L49.224,50.539c-0.243-0.141-0.387-0.405-0.373-0.686c0.013-0.279,0.182-0.53,0.437-0.647
+ l77.641-35.774c0.726-0.335,1.561-0.335,2.29,0l77.531,35.772c0.256,0.119,0.422,0.367,0.438,0.649
+ C207.199,50.133,207.057,50.397,206.813,50.538z"/>
+ </g>
+ </mask>
+ <path mask="url(#SVGID_40_)" fill="#C3DCFA" d="M229.395,48.914l-99.83-44.127c-0.938-0.417-2.003-0.417-2.947,0L26.605,48.914
+ c-0.312,0.138-0.521,0.437-0.542,0.777c-0.021,0.339,0.147,0.662,0.439,0.837l99.707,59.771c0.578,0.348,1.231,0.52,1.883,0.52
+ c0.652,0,1.303-0.172,1.883-0.52l99.52-59.771c0.293-0.175,0.463-0.499,0.439-0.837C229.912,49.351,229.705,49.051,229.395,48.914
+ z M206.813,50.538l-77.256,44.957c-0.461,0.267-0.976,0.4-1.486,0.4c-0.513,0-1.024-0.132-1.486-0.4L49.224,50.539
+ c-0.243-0.141-0.387-0.405-0.373-0.686c0.013-0.279,0.182-0.53,0.437-0.647l77.641-35.774c0.726-0.335,1.561-0.335,2.29,0
+ l77.531,35.772c0.256,0.119,0.422,0.367,0.438,0.649C207.199,50.133,207.057,50.397,206.813,50.538z"/>
+ <defs>
+ <filter id="Adobe_OpacityMaskFilter_10_" filterUnits="userSpaceOnUse" x="26.061" y="4.475" width="203.875" height="106.344">
+ <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
+ </filter>
+ </defs>
+ <mask maskUnits="userSpaceOnUse" x="26.061" y="4.475" width="203.875" height="106.344" id="SVGID_42_">
+ <g filter="url(#Adobe_OpacityMaskFilter_10_)">
+
+ <linearGradient id="SVGID_43_" gradientUnits="userSpaceOnUse" x1="-284.3638" y1="-514.6699" x2="-199.7078" y2="-413.781" gradientTransform="matrix(1 0 0 -1 370.0347 -414.4775)">
+ <stop offset="0" style="stop-color:#7A7A7A"/>
+ <stop offset="0.3048" style="stop-color:#000000"/>
+ </linearGradient>
+ <path fill="url(#SVGID_43_)" d="M229.395,48.914l-99.83-44.127c-0.938-0.417-2.003-0.417-2.947,0L26.605,48.914
+ c-0.312,0.138-0.521,0.437-0.542,0.777c-0.021,0.339,0.147,0.662,0.439,0.837l99.707,59.771c0.578,0.348,1.231,0.52,1.883,0.52
+ c0.652,0,1.303-0.172,1.883-0.52l99.52-59.771c0.293-0.175,0.463-0.499,0.439-0.837
+ C229.912,49.351,229.705,49.051,229.395,48.914z M206.813,50.538l-77.256,44.957c-0.461,0.267-0.976,0.4-1.486,0.4
+ c-0.513,0-1.024-0.132-1.486-0.4L49.224,50.539c-0.243-0.141-0.387-0.405-0.373-0.686c0.013-0.279,0.182-0.53,0.437-0.647
+ l77.641-35.774c0.726-0.335,1.561-0.335,2.29,0l77.531,35.772c0.256,0.119,0.422,0.367,0.438,0.649
+ C207.199,50.133,207.057,50.397,206.813,50.538z"/>
+ </g>
+ </mask>
+
+ <linearGradient id="SVGID_44_" gradientUnits="userSpaceOnUse" x1="-268.7026" y1="-510.4141" x2="-216.3228" y2="-419.6894" gradientTransform="matrix(1 0 0 -1 370.0347 -414.4775)">
+ <stop offset="0.0053" style="stop-color:#FFF5F0"/>
+ <stop offset="0.25" style="stop-color:#FFFFFF"/>
+ </linearGradient>
+ <path mask="url(#SVGID_42_)" fill="url(#SVGID_44_)" d="M229.395,48.914l-99.83-44.127c-0.938-0.417-2.003-0.417-2.947,0
+ L26.605,48.914c-0.312,0.138-0.521,0.437-0.542,0.777c-0.021,0.339,0.147,0.662,0.439,0.837l99.707,59.771
+ c0.578,0.348,1.231,0.52,1.883,0.52c0.652,0,1.303-0.172,1.883-0.52l99.52-59.771c0.293-0.175,0.463-0.499,0.439-0.837
+ C229.912,49.351,229.705,49.051,229.395,48.914z M206.813,50.538l-77.256,44.957c-0.461,0.267-0.976,0.4-1.486,0.4
+ c-0.513,0-1.024-0.132-1.486-0.4L49.224,50.539c-0.243-0.141-0.387-0.405-0.373-0.686c0.013-0.279,0.182-0.53,0.437-0.647
+ l77.641-35.774c0.726-0.335,1.561-0.335,2.29,0l77.531,35.772c0.256,0.119,0.422,0.367,0.438,0.649
+ C207.199,50.133,207.057,50.397,206.813,50.538z"/>
+ <defs>
+ <filter id="Adobe_OpacityMaskFilter_11_" filterUnits="userSpaceOnUse" x="26.061" y="4.475" width="203.875" height="106.344">
+ <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
+ </filter>
+ </defs>
+ <mask maskUnits="userSpaceOnUse" x="26.061" y="4.475" width="203.875" height="106.344" id="SVGID_45_">
+ <g filter="url(#Adobe_OpacityMaskFilter_11_)">
+
+ <linearGradient id="SVGID_46_" gradientUnits="userSpaceOnUse" x1="-302.0186" y1="-413.8936" x2="-182.0531" y2="-514.5565" gradientTransform="matrix(1 0 0 -1 370.0347 -414.4775)">
+ <stop offset="0" style="stop-color:#141414"/>
+ <stop offset="0.25" style="stop-color:#000000"/>
+ </linearGradient>
+ <path fill="url(#SVGID_46_)" d="M229.395,48.914l-99.83-44.127c-0.938-0.417-2.003-0.417-2.947,0L26.605,48.914
+ c-0.312,0.138-0.521,0.437-0.542,0.777c-0.021,0.339,0.147,0.662,0.439,0.837l99.707,59.771c0.578,0.348,1.231,0.52,1.883,0.52
+ c0.652,0,1.303-0.172,1.883-0.52l99.52-59.771c0.293-0.175,0.463-0.499,0.439-0.837
+ C229.912,49.351,229.705,49.051,229.395,48.914z M206.813,50.538l-77.256,44.957c-0.461,0.267-0.976,0.4-1.486,0.4
+ c-0.513,0-1.024-0.132-1.486-0.4L49.224,50.539c-0.243-0.141-0.387-0.405-0.373-0.686c0.013-0.279,0.182-0.53,0.437-0.647
+ l77.641-35.774c0.726-0.335,1.561-0.335,2.29,0l77.531,35.772c0.256,0.119,0.422,0.367,0.438,0.649
+ C207.199,50.133,207.057,50.397,206.813,50.538z"/>
+ </g>
+ </mask>
+ <path mask="url(#SVGID_45_)" fill="#2B388F" d="M229.395,48.914l-99.83-44.127c-0.938-0.417-2.003-0.417-2.947,0L26.605,48.914
+ c-0.312,0.138-0.521,0.437-0.542,0.777c-0.021,0.339,0.147,0.662,0.439,0.837l99.707,59.771c0.578,0.348,1.231,0.52,1.883,0.52
+ c0.652,0,1.303-0.172,1.883-0.52l99.52-59.771c0.293-0.175,0.463-0.499,0.439-0.837C229.912,49.351,229.705,49.051,229.395,48.914
+ z M206.813,50.538l-77.256,44.957c-0.461,0.267-0.976,0.4-1.486,0.4c-0.513,0-1.024-0.132-1.486-0.4L49.224,50.539
+ c-0.243-0.141-0.387-0.405-0.373-0.686c0.013-0.279,0.182-0.53,0.437-0.647l77.641-35.774c0.726-0.335,1.561-0.335,2.29,0
+ l77.531,35.772c0.256,0.119,0.422,0.367,0.438,0.649C207.199,50.133,207.057,50.397,206.813,50.538z"/>
+
+ <linearGradient id="SVGID_47_" gradientUnits="userSpaceOnUse" x1="-321.6987" y1="-603.7334" x2="-268.8858" y2="-512.2586" gradientTransform="matrix(1 0 0 -1 370.0347 -414.4775)">
+ <stop offset="0.0053" style="stop-color:#E6E1E1"/>
+ <stop offset="0.5" style="stop-color:#E3E0DC"/>
+ <stop offset="1" style="stop-color:#EEE9E1"/>
+ </linearGradient>
+ <path fill="url(#SVGID_47_)" d="M125.82,117.02c-0.005-1.267-0.672-2.453-1.752-3.109L24.881,53.748
+ c-0.294-0.178-0.658-0.178-0.951,0.001c-0.292,0.179-0.459,0.505-0.432,0.847l8.144,106.359c0.079,1.038,0.599,1.994,1.429,2.624
+ l91.455,69.657c0.161,0.123,0.357,0.188,0.552,0.188c0.138,0,0.278-0.031,0.405-0.095c0.311-0.155,0.508-0.474,0.507-0.819
+ L125.82,117.02z M113.735,205.9c-0.118,0.061-0.247,0.091-0.375,0.091c-0.174,0-0.347-0.054-0.492-0.161l-71.516-52.707
+ c-0.646-0.479-1.051-1.206-1.107-2.008L34.82,75.227c-0.023-0.311,0.133-0.608,0.4-0.768c0.266-0.159,0.601-0.156,0.865,0.008
+ l75.882,47.62c0.853,0.535,1.372,1.466,1.384,2.473l0.835,80.597C114.19,205.471,114.018,205.758,113.735,205.9z"/>
+ <defs>
+ <filter id="Adobe_OpacityMaskFilter_12_" filterUnits="userSpaceOnUse" x="23.495" y="53.614" width="102.495" height="179.81">
+ <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
+ </filter>
+ </defs>
+ <mask maskUnits="userSpaceOnUse" x="23.495" y="53.614" width="102.495" height="179.81" id="SVGID_48_">
+ <g filter="url(#Adobe_OpacityMaskFilter_12_)">
+
+ <linearGradient id="SVGID_49_" gradientUnits="userSpaceOnUse" x1="-218.2163" y1="-602.4976" x2="-372.3692" y2="-513.4973" gradientTransform="matrix(1 0 0 -1 370.0347 -414.4775)">
+ <stop offset="0.2" style="stop-color:#000000"/>
+ <stop offset="0.9" style="stop-color:#FFFFFF"/>
+ </linearGradient>
+ <path fill="url(#SVGID_49_)" d="M125.82,117.02c-0.005-1.267-0.672-2.453-1.752-3.109L24.881,53.748
+ c-0.294-0.178-0.658-0.178-0.951,0.001c-0.292,0.179-0.459,0.505-0.432,0.847l8.144,106.359
+ c0.079,1.038,0.599,1.994,1.429,2.624l91.455,69.657c0.161,0.123,0.357,0.188,0.552,0.188c0.138,0,0.278-0.031,0.405-0.095
+ c0.311-0.155,0.508-0.474,0.507-0.819L125.82,117.02z M113.735,205.9c-0.118,0.061-0.247,0.091-0.375,0.091
+ c-0.174,0-0.347-0.054-0.492-0.161l-71.516-52.707c-0.646-0.479-1.051-1.206-1.107-2.008L34.82,75.227
+ c-0.023-0.311,0.133-0.608,0.4-0.768c0.266-0.159,0.601-0.156,0.865,0.008l75.882,47.62c0.853,0.535,1.372,1.466,1.384,2.473
+ l0.835,80.597C114.19,205.471,114.018,205.758,113.735,205.9z"/>
+ </g>
+ </mask>
+
+ <linearGradient id="SVGID_50_" gradientUnits="userSpaceOnUse" x1="-225.4541" y1="-631.8145" x2="-355.862" y2="-476.4005" gradientTransform="matrix(1 0 0 -1 370.0347 -414.4775)">
+ <stop offset="0" style="stop-color:#E6D2F0"/>
+ <stop offset="1" style="stop-color:#C3C8DC"/>
+ </linearGradient>
+ <path mask="url(#SVGID_48_)" fill="url(#SVGID_50_)" d="M125.82,117.02c-0.005-1.267-0.672-2.453-1.752-3.109L24.881,53.748
+ c-0.294-0.178-0.658-0.178-0.951,0.001c-0.292,0.179-0.459,0.505-0.432,0.847l8.144,106.359c0.079,1.038,0.599,1.994,1.429,2.624
+ l91.455,69.657c0.161,0.123,0.357,0.188,0.552,0.188c0.138,0,0.278-0.031,0.405-0.095c0.311-0.155,0.508-0.474,0.507-0.819
+ L125.82,117.02z M113.735,205.9c-0.118,0.061-0.247,0.091-0.375,0.091c-0.174,0-0.347-0.054-0.492-0.161l-71.516-52.707
+ c-0.646-0.479-1.051-1.206-1.107-2.008L34.82,75.227c-0.023-0.311,0.133-0.608,0.4-0.768c0.266-0.159,0.601-0.156,0.865,0.008
+ l75.882,47.62c0.853,0.535,1.372,1.466,1.384,2.473l0.835,80.597C114.19,205.471,114.018,205.758,113.735,205.9z"/>
+
+ <linearGradient id="SVGID_51_" gradientUnits="userSpaceOnUse" x1="-162.3706" y1="-603.7349" x2="-215.1844" y2="-512.2589" gradientTransform="matrix(1 0 0 -1 370.0347 -414.4775)">
+ <stop offset="0" style="stop-color:#7896BE"/>
+ <stop offset="1" style="stop-color:#4164A5"/>
+ </linearGradient>
+ <path fill="url(#SVGID_51_)" d="M232.068,53.75c-0.293-0.18-0.654-0.18-0.951-0.001l-99.186,60.162
+ c-1.08,0.655-1.748,1.843-1.752,3.109l-0.17,115.49c0,0.348,0.196,0.665,0.506,0.819c0.129,0.063,0.268,0.095,0.405,0.095
+ c0.195,0,0.392-0.063,0.554-0.188l91.457-69.657c0.828-0.629,1.348-1.585,1.428-2.623l8.145-106.359
+ C232.527,54.254,232.361,53.93,232.068,53.75z M215.791,151.115c-0.057,0.802-0.459,1.529-1.104,2.008l-71.52,52.707
+ c-0.145,0.107-0.318,0.161-0.491,0.161c-0.128,0-0.257-0.03-0.375-0.091c-0.28-0.145-0.454-0.433-0.45-0.744l0.834-80.597
+ c0.014-1.007,0.531-1.938,1.383-2.473l75.886-47.62c0.264-0.165,0.599-0.167,0.862-0.008c0.269,0.159,0.425,0.456,0.4,0.768
+ L215.791,151.115z"/>
+ <defs>
+ <filter id="Adobe_OpacityMaskFilter_13_" filterUnits="userSpaceOnUse" x="130.01" y="53.615" width="102.496" height="179.809">
+ <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
+ </filter>
+ </defs>
+ <mask maskUnits="userSpaceOnUse" x="130.01" y="53.615" width="102.496" height="179.809" id="SVGID_52_">
+ <g filter="url(#Adobe_OpacityMaskFilter_13_)">
+
+ <linearGradient id="SVGID_53_" gradientUnits="userSpaceOnUse" x1="-148.5908" y1="-581.1997" x2="-238.9791" y2="-529.0141" gradientTransform="matrix(1 0 0 -1 370.0347 -414.4775)">
+ <stop offset="0.0053" style="stop-color:#808080"/>
+ <stop offset="0.6" style="stop-color:#000000"/>
+ </linearGradient>
+ <path fill="url(#SVGID_53_)" d="M232.068,53.75c-0.293-0.18-0.654-0.18-0.951-0.001l-99.186,60.162
+ c-1.08,0.655-1.748,1.843-1.752,3.109l-0.17,115.49c0,0.348,0.196,0.665,0.506,0.819c0.129,0.063,0.268,0.095,0.405,0.095
+ c0.195,0,0.392-0.063,0.554-0.188l91.457-69.657c0.828-0.629,1.348-1.585,1.428-2.623l8.145-106.359
+ C232.527,54.254,232.361,53.93,232.068,53.75z M215.791,151.115c-0.057,0.802-0.459,1.529-1.104,2.008l-71.52,52.707
+ c-0.145,0.107-0.318,0.161-0.491,0.161c-0.128,0-0.257-0.03-0.375-0.091c-0.28-0.145-0.454-0.433-0.45-0.744l0.834-80.597
+ c0.014-1.007,0.531-1.938,1.383-2.473l75.886-47.62c0.264-0.165,0.599-0.167,0.862-0.008c0.269,0.159,0.425,0.456,0.4,0.768
+ L215.791,151.115z"/>
+ </g>
+ </mask>
+ <path mask="url(#SVGID_52_)" fill="#78A0E6" d="M232.068,53.75c-0.293-0.18-0.654-0.18-0.951-0.001l-99.186,60.162
+ c-1.08,0.655-1.748,1.843-1.752,3.109l-0.17,115.49c0,0.348,0.196,0.665,0.506,0.819c0.129,0.063,0.268,0.095,0.405,0.095
+ c0.195,0,0.392-0.063,0.554-0.188l91.457-69.657c0.828-0.629,1.348-1.585,1.428-2.623l8.145-106.359
+ C232.527,54.254,232.361,53.93,232.068,53.75z M215.791,151.115c-0.057,0.802-0.459,1.529-1.104,2.008l-71.52,52.707
+ c-0.145,0.107-0.318,0.161-0.491,0.161c-0.128,0-0.257-0.03-0.375-0.091c-0.28-0.145-0.454-0.433-0.45-0.744l0.834-80.597
+ c0.014-1.007,0.531-1.938,1.383-2.473l75.886-47.62c0.264-0.165,0.599-0.167,0.862-0.008c0.269,0.159,0.425,0.456,0.4,0.768
+ L215.791,151.115z"/>
+ <defs>
+ <filter id="Adobe_OpacityMaskFilter_14_" filterUnits="userSpaceOnUse" x="130.01" y="53.615" width="102.496" height="179.809">
+ <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
+ </filter>
+ </defs>
+ <mask maskUnits="userSpaceOnUse" x="130.01" y="53.615" width="102.496" height="179.809" id="SVGID_54_">
+ <g filter="url(#Adobe_OpacityMaskFilter_14_)">
+
+ <linearGradient id="SVGID_55_" gradientUnits="userSpaceOnUse" x1="-210.332" y1="-652.9912" x2="-176.5425" y2="-461.3605" gradientTransform="matrix(1 0 0 -1 370.0347 -414.4775)">
+ <stop offset="0.0053" style="stop-color:#808080"/>
+ <stop offset="0.4" style="stop-color:#000000"/>
+ </linearGradient>
+ <path fill="url(#SVGID_55_)" d="M232.068,53.75c-0.293-0.18-0.654-0.18-0.951-0.001l-99.186,60.162
+ c-1.08,0.655-1.748,1.843-1.752,3.109l-0.17,115.49c0,0.348,0.196,0.665,0.506,0.819c0.129,0.063,0.268,0.095,0.405,0.095
+ c0.195,0,0.392-0.063,0.554-0.188l91.457-69.657c0.828-0.629,1.348-1.585,1.428-2.623l8.145-106.359
+ C232.527,54.254,232.361,53.93,232.068,53.75z M215.791,151.115c-0.057,0.802-0.459,1.529-1.104,2.008l-71.52,52.707
+ c-0.145,0.107-0.318,0.161-0.491,0.161c-0.128,0-0.257-0.03-0.375-0.091c-0.28-0.145-0.454-0.433-0.45-0.744l0.834-80.597
+ c0.014-1.007,0.531-1.938,1.383-2.473l75.886-47.62c0.264-0.165,0.599-0.167,0.862-0.008c0.269,0.159,0.425,0.456,0.4,0.768
+ L215.791,151.115z"/>
+ </g>
+ </mask>
+ <path mask="url(#SVGID_54_)" fill="#7896BE" d="M232.068,53.75c-0.293-0.18-0.654-0.18-0.951-0.001l-99.186,60.162
+ c-1.08,0.655-1.748,1.843-1.752,3.109l-0.17,115.49c0,0.348,0.196,0.665,0.506,0.819c0.129,0.063,0.268,0.095,0.405,0.095
+ c0.195,0,0.392-0.063,0.554-0.188l91.457-69.657c0.828-0.629,1.348-1.585,1.428-2.623l8.145-106.359
+ C232.527,54.254,232.361,53.93,232.068,53.75z M215.791,151.115c-0.057,0.802-0.459,1.529-1.104,2.008l-71.52,52.707
+ c-0.145,0.107-0.318,0.161-0.491,0.161c-0.128,0-0.257-0.03-0.375-0.091c-0.28-0.145-0.454-0.433-0.45-0.744l0.834-80.597
+ c0.014-1.007,0.531-1.938,1.383-2.473l75.886-47.62c0.264-0.165,0.599-0.167,0.862-0.008c0.269,0.159,0.425,0.456,0.4,0.768
+ L215.791,151.115z"/>
+ <g id="text_3_">
+ <g>
+ <path fill="#FFFFFF" d="M49.103,122.684c-0.36-0.256-0.717-0.627-1.054-1.077c-0.337-0.45-0.654-0.981-0.936-1.557
+ c-0.281-0.577-0.526-1.199-0.719-1.834c-0.192-0.634-0.332-1.281-0.403-1.907c-0.07-0.625-0.064-1.169,0.006-1.617
+ c0.07-0.449,0.206-0.8,0.394-1.043c0.189-0.242,0.432-0.373,0.718-0.38c0.285-0.008,0.613,0.109,0.973,0.367l4.473,3.189
+ c0.36,0.256,0.718,0.628,1.056,1.08c0.337,0.452,0.657,0.984,0.939,1.562c0.282,0.579,0.528,1.203,0.721,1.84
+ c0.193,0.636,0.334,1.285,0.404,1.911c0.07,0.626,0.064,1.169-0.007,1.617c-0.071,0.447-0.208,0.797-0.398,1.037
+ c-0.19,0.24-0.435,0.369-0.721,0.375c-0.287,0.005-0.616-0.113-0.976-0.371L49.103,122.684 M52.382,114.425l-4.682-3.337
+ c-0.555-0.397-1.063-0.577-1.504-0.567c-0.441,0.012-0.817,0.216-1.109,0.591c-0.293,0.374-0.502,0.919-0.611,1.611
+ c-0.109,0.693-0.117,1.533-0.008,2.5c0.108,0.963,0.325,1.963,0.622,2.943c0.298,0.98,0.677,1.943,1.112,2.835
+ c0.435,0.891,0.926,1.712,1.447,2.409c0.521,0.697,1.073,1.271,1.628,1.667l4.68,3.342c0.557,0.397,1.065,0.581,1.506,0.57
+ c0.442-0.009,0.819-0.212,1.112-0.583c0.293-0.372,0.503-0.915,0.613-1.604c0.11-0.69,0.119-1.529,0.01-2.493
+ c-0.108-0.968-0.324-1.97-0.623-2.954c-0.298-0.983-0.678-1.949-1.115-2.844c-0.435-0.895-0.927-1.717-1.449-2.416
+ C53.492,115.396,52.938,114.821,52.382,114.425 M64.026,122.726l-6.789-4.84l1.575,13.998l1.548,1.105l-1.295-11.521
+ l5.131,3.659c0.18,0.129,0.36,0.314,0.528,0.54c0.169,0.225,0.328,0.491,0.469,0.778c0.141,0.289,0.262,0.6,0.359,0.917
+ c0.095,0.317,0.165,0.642,0.2,0.955c0.034,0.313,0.032,0.585-0.002,0.811c-0.036,0.226-0.103,0.402-0.197,0.522
+ c-0.095,0.123-0.216,0.188-0.359,0.192c-0.143,0.003-0.308-0.055-0.489-0.184l-4.374-3.13l5.418,10.305l2.253,1.607
+ l-3.628-6.758l0.713,0.51c0.376,0.269,0.72,0.393,1.02,0.386c0.299-0.006,0.555-0.142,0.753-0.394
+ c0.199-0.252,0.342-0.619,0.417-1.086c0.075-0.469,0.082-1.035,0.008-1.689c-0.073-0.653-0.219-1.329-0.42-1.994
+ c-0.203-0.666-0.459-1.32-0.755-1.925c-0.295-0.606-0.628-1.164-0.981-1.637C64.775,123.383,64.401,122.994,64.026,122.726
+ M87.361,139.361l-5.595-3.986c-0.558-0.398-1.068-0.581-1.511-0.57c-0.444,0.01-0.823,0.213-1.118,0.588
+ c-0.295,0.373-0.507,0.918-0.619,1.611c-0.111,0.691-0.122,1.536-0.013,2.503c0.109,0.966,0.325,1.968,0.624,2.95
+ c0.298,0.982,0.679,1.949,1.116,2.844c0.438,0.896,0.931,1.719,1.454,2.417c0.523,0.699,1.077,1.274,1.635,1.673l4.802,3.43
+ l0.645-1.813l-5.616-4.009c-0.362-0.258-0.721-0.63-1.059-1.083c-0.339-0.452-0.659-0.984-0.942-1.563
+ c-0.283-0.58-0.529-1.205-0.723-1.84c-0.193-0.639-0.333-1.285-0.403-1.912c-0.07-0.627-0.063-1.173,0.009-1.621
+ c0.072-0.447,0.209-0.802,0.4-1.041c0.192-0.242,0.438-0.373,0.725-0.379c0.288-0.008,0.618,0.112,0.98,0.371l4.571,3.26
+ L87.361,139.361 M89.758,141.07l-1.557-1.109l1.415,12.684c0.01,0.088,0.024,0.177,0.043,0.268
+ c0.018,0.092,0.041,0.184,0.068,0.273c0.028,0.092,0.06,0.186,0.097,0.278c0.037,0.093,0.078,0.187,0.124,0.279
+ c0.044,0.089,0.089,0.174,0.138,0.256c0.048,0.079,0.099,0.153,0.15,0.224c0.052,0.067,0.105,0.131,0.16,0.187
+ c0.054,0.057,0.108,0.103,0.164,0.146l7.112,5.076l0.64-1.816l-7.267-5.188L89.758,141.07 M106.35,152.898l-5.617-4.004
+ c-0.561-0.399-1.073-0.586-1.519-0.576c-0.447,0.011-0.826,0.214-1.123,0.589c-0.297,0.373-0.508,0.919-0.62,1.612
+ c-0.112,0.694-0.123,1.535-0.015,2.506c0.107,0.969,0.324,1.971,0.622,2.953c0.298,0.983,0.679,1.953,1.116,2.849
+ c0.438,0.896,0.932,1.719,1.457,2.421c0.525,0.701,1.081,1.279,1.642,1.679l4.817,3.44l0.643-1.818l-5.633-4.021
+ c-0.298-0.213-0.596-0.506-0.882-0.855c-0.285-0.354-0.561-0.762-0.815-1.215c-0.254-0.451-0.488-0.941-0.691-1.455
+ c-0.203-0.512-0.374-1.046-0.506-1.582l6.679,4.766l0.644-1.817l-7.597-5.419c0.025-0.42,0.098-0.776,0.212-1.063
+ c0.113-0.286,0.268-0.5,0.457-0.635c0.19-0.135,0.412-0.19,0.664-0.161c0.25,0.031,0.529,0.15,0.828,0.362l4.584,3.271
+ L106.35,152.898 M72.648,128.752c-0.1-0.07-0.194-0.119-0.282-0.145c-0.087-0.027-0.169-0.032-0.242-0.018
+ c-0.075,0.016-0.14,0.053-0.198,0.107c-0.058,0.057-0.107,0.132-0.146,0.227l-3.404,9.787l1.829,1.307l2.79-8.179l2.878,7.683
+ l-4.148-2.959l1.189,3.117l4.199,2.998l1.145,3.088l1.831,1.308l-6.404-16.808c-0.068-0.17-0.142-0.33-0.222-0.483
+ c-0.08-0.152-0.166-0.294-0.254-0.425s-0.181-0.248-0.275-0.352C72.838,128.906,72.743,128.82,72.648,128.752"/>
+ </g>
+ <path fill="#FFFFFF" d="M127.647,21.402L64.145,50.701l63.045,36.036l64.158-36.036L127.647,21.402z M127.253,81.504
+ L72.909,50.768l35.185-16.416l8.502,3.661L94.557,59.727l37.996-15.501l-13.538,15.697l24.135-10.007l-12.231,15.239
+ l12.886,7.128L127.253,81.504z M136.936,63.977l18.508-23.021l-23.217,9.484l12.947-15.172L110.25,49.59l12.425-12.426
+ l-10.397-4.773l15.369-7.194l55.001,25.571l-34.4,19.096L136.936,63.977z"/>
+ <g>
+ <path fill="#FFFFFF" d="M155.916,147.47l-1.908,1.384c-0.084,0.539-0.174,1.1-0.269,1.681c-0.099,0.577-0.198,1.18-0.306,1.803
+ c-0.105,0.622-0.222,1.267-0.338,1.929c-0.121,0.664-0.244,1.351-0.373,2.055c-0.134,0.705-0.263,1.388-0.388,2.045
+ c-0.123,0.658-0.246,1.295-0.364,1.908c-0.119,0.612-0.236,1.203-0.351,1.771c-0.111,0.565-0.224,1.112-0.33,1.634
+ c-0.06-0.505-0.114-1.013-0.17-1.523c-0.058-0.514-0.108-1.027-0.162-1.551c-0.053-0.52-0.104-1.045-0.151-1.574
+ c-0.052-0.529-0.099-1.063-0.144-1.604c-0.047-0.541-0.093-1.068-0.133-1.583c-0.039-0.515-0.074-1.015-0.109-1.502
+ c-0.03-0.489-0.062-0.963-0.088-1.424c-0.026-0.463-0.051-0.914-0.069-1.351l-2.175,1.576c0.046,0.646,0.097,1.284,0.146,1.92
+ c0.054,0.633,0.106,1.26,0.162,1.883c0.058,0.623,0.117,1.238,0.179,1.85c0.063,0.609,0.127,1.217,0.194,1.818
+ c0.064,0.599,0.138,1.205,0.209,1.816c0.068,0.612,0.146,1.229,0.228,1.854c0.078,0.625,0.16,1.255,0.246,1.888
+ c0.086,0.637,0.174,1.273,0.267,1.922l2.434-1.775c0.186-0.83,0.365-1.653,0.541-2.469c0.182-0.815,0.354-1.623,0.524-2.426
+ c0.169-0.8,0.337-1.593,0.499-2.375c0.161-0.783,0.319-1.563,0.475-2.33c0.154-0.771,0.309-1.537,0.457-2.308
+ c0.146-0.771,0.293-1.539,0.438-2.31c0.142-0.771,0.281-1.541,0.422-2.313C155.646,149.02,155.783,148.244,155.916,147.47"/>
+ <path fill="#FFFFFF" d="M159.123,149.271l-1.807,1.311l-0.031,0.525l-0.771,12.022l1.805-1.317L159.123,149.271 M158.57,144.455
+ c-0.094,0.064-0.178,0.135-0.256,0.208c-0.08,0.071-0.15,0.149-0.221,0.229c-0.063,0.08-0.125,0.164-0.181,0.25
+ c-0.056,0.088-0.103,0.18-0.146,0.272c-0.041,0.094-0.08,0.194-0.113,0.305c-0.032,0.11-0.063,0.229-0.09,0.354
+ s-0.049,0.259-0.063,0.4c-0.021,0.144-0.032,0.291-0.045,0.451c-0.009,0.162-0.015,0.311-0.015,0.442
+ c0,0.131,0.007,0.25,0.019,0.35c0.013,0.101,0.026,0.188,0.052,0.259c0.021,0.07,0.051,0.127,0.084,0.166
+ c0.032,0.039,0.071,0.065,0.12,0.078c0.05,0.012,0.104,0.012,0.166-0.002c0.062-0.015,0.134-0.042,0.209-0.082
+ c0.076-0.041,0.16-0.094,0.25-0.162c0.095-0.063,0.181-0.135,0.259-0.209c0.08-0.072,0.151-0.149,0.221-0.229
+ c0.066-0.081,0.129-0.164,0.182-0.25c0.055-0.086,0.104-0.178,0.145-0.27c0.041-0.096,0.08-0.197,0.113-0.31
+ c0.031-0.11,0.063-0.229,0.09-0.358c0.025-0.127,0.051-0.267,0.068-0.41c0.016-0.146,0.029-0.299,0.043-0.461
+ c0.01-0.164,0.014-0.311,0.014-0.44c0-0.132-0.008-0.248-0.018-0.349c-0.012-0.102-0.025-0.188-0.047-0.254
+ c-0.021-0.068-0.051-0.123-0.082-0.16c-0.031-0.039-0.072-0.064-0.121-0.076c-0.047-0.012-0.104-0.012-0.166,0.005
+ c-0.063,0.015-0.133,0.043-0.211,0.085C158.75,144.333,158.664,144.386,158.57,144.455"/>
+ <path fill="#FFFFFF" d="M165.184,144.634c-0.069,0.052-0.143,0.113-0.209,0.187c-0.067,0.07-0.139,0.154-0.203,0.248
+ c-0.069,0.095-0.137,0.197-0.203,0.313c-0.063,0.116-0.129,0.242-0.192,0.378c-0.063,0.137-0.135,0.289-0.211,0.463
+ c-0.074,0.174-0.153,0.366-0.24,0.577c-0.086,0.211-0.178,0.443-0.274,0.695c-0.099,0.251-0.199,0.521-0.31,0.813l-0.125-2.01
+ l-1.358,0.987l-0.808,12.532l1.813-1.32l0.516-8.008c0.078-0.208,0.151-0.404,0.223-0.588c0.072-0.184,0.141-0.354,0.203-0.514
+ c0.064-0.158,0.127-0.306,0.184-0.438c0.06-0.137,0.111-0.26,0.162-0.369c0.05-0.105,0.099-0.207,0.146-0.296
+ c0.051-0.089,0.096-0.168,0.145-0.239c0.046-0.068,0.091-0.131,0.136-0.182c0.043-0.053,0.088-0.094,0.131-0.123
+ c0.021-0.016,0.041-0.028,0.065-0.042c0.021-0.015,0.047-0.029,0.07-0.042c0.025-0.014,0.053-0.024,0.08-0.037
+ c0.025-0.014,0.057-0.023,0.086-0.036c0.031-0.008,0.063-0.019,0.092-0.022c0.027-0.012,0.06-0.021,0.092-0.027
+ c0.029-0.008,0.062-0.018,0.091-0.024c0.03-0.008,0.063-0.015,0.094-0.021l0.418-3.135c-0.034,0.011-0.067,0.021-0.101,0.029
+ c-0.03,0.012-0.063,0.021-0.09,0.031c-0.03,0.01-0.061,0.021-0.084,0.033c-0.026,0.011-0.053,0.021-0.076,0.033
+ c-0.022,0.012-0.049,0.021-0.069,0.033s-0.045,0.024-0.067,0.036c-0.021,0.015-0.041,0.026-0.063,0.04
+ C165.223,144.607,165.201,144.621,165.184,144.634"/>
+ <path fill="#FFFFFF" d="M169.717,137.949l-1.076,0.779c-0.07,0.399-0.145,0.791-0.221,1.172c-0.074,0.38-0.154,0.75-0.234,1.11
+ c-0.082,0.361-0.164,0.714-0.252,1.058c-0.084,0.342-0.176,0.676-0.268,0.998l-0.922,0.67l-0.143,2.169l0.91-0.662l-0.435,6.753
+ c-0.022,0.335-0.035,0.639-0.035,0.914c-0.002,0.273,0.006,0.52,0.024,0.736c0.019,0.215,0.05,0.401,0.084,0.563
+ c0.041,0.16,0.089,0.289,0.146,0.393c0.059,0.103,0.133,0.176,0.217,0.225c0.084,0.045,0.182,0.064,0.291,0.056
+ c0.109-0.007,0.23-0.043,0.365-0.104c0.139-0.063,0.283-0.152,0.445-0.271c0.074-0.053,0.15-0.112,0.227-0.177
+ c0.074-0.063,0.152-0.13,0.229-0.204c0.073-0.069,0.153-0.146,0.229-0.229c0.076-0.08,0.156-0.166,0.234-0.254
+ c0.08-0.088,0.154-0.179,0.23-0.271c0.073-0.09,0.146-0.183,0.217-0.274c0.07-0.093,0.139-0.188,0.203-0.281
+ c0.063-0.096,0.129-0.188,0.19-0.286l0.022-1.794c-0.043,0.044-0.086,0.087-0.125,0.125c-0.043,0.039-0.084,0.078-0.123,0.115
+ c-0.041,0.039-0.08,0.074-0.118,0.108c-0.037,0.036-0.078,0.067-0.115,0.101c-0.037,0.03-0.074,0.063-0.108,0.09
+ c-0.041,0.029-0.078,0.062-0.113,0.091c-0.037,0.026-0.074,0.057-0.111,0.087c-0.037,0.025-0.074,0.054-0.111,0.082
+ c-0.055,0.04-0.106,0.068-0.154,0.087c-0.047,0.021-0.092,0.024-0.131,0.021c-0.039-0.008-0.076-0.021-0.104-0.049
+ c-0.033-0.029-0.062-0.068-0.084-0.119s-0.043-0.106-0.062-0.173c-0.014-0.063-0.026-0.137-0.033-0.214
+ c-0.012-0.078-0.014-0.164-0.016-0.258c0-0.094,0.004-0.193,0.01-0.301l0.429-6.604l1.522-1.108l0.142-2.164l-1.523,1.104
+ L169.717,137.949"/>
+ <path fill="#FFFFFF" d="M178.785,134.994l-1.791,1.302l-0.615,9.468c-0.111,0.174-0.225,0.338-0.334,0.487
+ c-0.107,0.149-0.215,0.287-0.32,0.409c-0.104,0.125-0.209,0.234-0.309,0.333c-0.104,0.098-0.201,0.182-0.299,0.252
+ c-0.076,0.057-0.147,0.093-0.213,0.11c-0.063,0.018-0.121,0.014-0.174-0.008s-0.099-0.063-0.14-0.123
+ c-0.04-0.062-0.073-0.142-0.103-0.238c-0.026-0.104-0.049-0.235-0.063-0.402c-0.015-0.167-0.023-0.367-0.025-0.6
+ c-0.002-0.233,0.002-0.498,0.014-0.797s0.027-0.631,0.054-0.996l0.456-6.396l-1.797,1.306c-0.01,0.138-0.021,0.313-0.037,0.531
+ c-0.014,0.219-0.031,0.479-0.055,0.776c-0.021,0.3-0.047,0.64-0.076,1.02c-0.025,0.382-0.061,0.804-0.096,1.265
+ c-0.033,0.462-0.063,0.882-0.092,1.26c-0.025,0.379-0.053,0.716-0.073,1.013c-0.021,0.298-0.04,0.552-0.054,0.767
+ c-0.019,0.215-0.027,0.389-0.037,0.521c-0.031,0.512-0.053,0.979-0.063,1.402c-0.008,0.421-0.002,0.799,0.017,1.131
+ c0.016,0.332,0.045,0.62,0.088,0.863c0.043,0.242,0.1,0.438,0.166,0.595c0.065,0.153,0.146,0.272,0.238,0.356
+ c0.094,0.086,0.196,0.135,0.313,0.149c0.118,0.019,0.245-0.004,0.389-0.056c0.144-0.053,0.298-0.142,0.463-0.264
+ c0.091-0.063,0.181-0.14,0.271-0.224c0.091-0.085,0.185-0.183,0.278-0.285c0.094-0.104,0.188-0.222,0.287-0.348
+ c0.096-0.126,0.194-0.263,0.295-0.409c0.102-0.146,0.196-0.296,0.297-0.452c0.096-0.151,0.188-0.313,0.285-0.479
+ c0.092-0.162,0.186-0.33,0.276-0.504c0.093-0.172,0.185-0.352,0.272-0.533l0.104,1.271l1.389-1.014L178.785,134.994"/>
+ <path fill="#FFFFFF" d="M182.768,141.832c-0.063,0.05-0.127,0.087-0.188,0.115c-0.057,0.027-0.113,0.042-0.162,0.048
+ c-0.053,0.005-0.1,0-0.139-0.017c-0.043-0.019-0.078-0.047-0.113-0.086s-0.063-0.086-0.084-0.145
+ c-0.021-0.061-0.043-0.128-0.055-0.207c-0.014-0.08-0.021-0.168-0.023-0.27c-0.002-0.1,0-0.209,0.008-0.326
+ c0.01-0.143,0.025-0.28,0.051-0.418c0.021-0.137,0.049-0.27,0.086-0.399c0.035-0.131,0.078-0.26,0.125-0.386
+ c0.052-0.126,0.105-0.251,0.17-0.374c0.063-0.123,0.138-0.246,0.224-0.367c0.082-0.119,0.183-0.237,0.285-0.354
+ c0.106-0.115,0.226-0.23,0.354-0.344s0.268-0.224,0.416-0.334l0.521-0.382l-0.166,2.56c-0.059,0.104-0.113,0.203-0.17,0.3
+ c-0.053,0.095-0.109,0.186-0.162,0.271c-0.059,0.085-0.107,0.167-0.162,0.244c-0.052,0.077-0.104,0.146-0.154,0.215
+ c-0.053,0.067-0.104,0.133-0.158,0.191c-0.053,0.063-0.106,0.117-0.162,0.174c-0.055,0.054-0.108,0.104-0.166,0.154
+ C182.885,141.745,182.826,141.791,182.768,141.832 M184.02,130.961c-0.102,0.071-0.199,0.148-0.305,0.232
+ c-0.102,0.084-0.203,0.173-0.311,0.269c-0.105,0.097-0.213,0.196-0.322,0.306c-0.107,0.105-0.219,0.222-0.33,0.34
+ c-0.111,0.117-0.229,0.248-0.354,0.394s-0.254,0.301-0.39,0.472c-0.137,0.172-0.278,0.354-0.426,0.553
+ c-0.147,0.194-0.304,0.406-0.461,0.632l0.067,1.792c0.095-0.117,0.188-0.229,0.277-0.338c0.092-0.108,0.18-0.213,0.27-0.313
+ c0.089-0.1,0.173-0.196,0.257-0.288c0.084-0.091,0.161-0.179,0.241-0.261c0.08-0.083,0.158-0.164,0.24-0.242
+ c0.08-0.079,0.164-0.153,0.246-0.228c0.084-0.071,0.168-0.146,0.254-0.213c0.086-0.068,0.174-0.135,0.262-0.198
+ c0.099-0.072,0.191-0.132,0.281-0.181c0.084-0.047,0.168-0.082,0.246-0.104c0.076-0.022,0.148-0.033,0.215-0.031
+ c0.067,0.002,0.127,0.017,0.185,0.044c0.055,0.026,0.103,0.068,0.142,0.127c0.036,0.058,0.069,0.133,0.092,0.226
+ c0.023,0.092,0.035,0.198,0.041,0.323c0.008,0.123,0.006,0.266-0.006,0.422l-0.056,0.845l-0.483,0.353
+ c-0.295,0.215-0.57,0.439-0.832,0.676c-0.256,0.236-0.5,0.481-0.724,0.74c-0.224,0.258-0.43,0.524-0.618,0.805
+ c-0.187,0.277-0.355,0.565-0.513,0.867c-0.151,0.301-0.287,0.604-0.406,0.907c-0.12,0.306-0.223,0.611-0.312,0.919
+ c-0.086,0.309-0.153,0.615-0.209,0.926c-0.055,0.312-0.092,0.623-0.11,0.935c-0.039,0.601-0.028,1.093,0.031,1.479
+ c0.057,0.385,0.166,0.662,0.321,0.832c0.156,0.17,0.365,0.233,0.621,0.189c0.252-0.043,0.558-0.193,0.908-0.452
+ c0.098-0.067,0.189-0.147,0.287-0.235c0.094-0.088,0.188-0.187,0.283-0.293c0.098-0.106,0.189-0.225,0.284-0.349
+ c0.099-0.125,0.19-0.259,0.288-0.401c0.096-0.146,0.188-0.293,0.281-0.445c0.092-0.15,0.181-0.305,0.268-0.463
+ c0.086-0.157,0.17-0.318,0.252-0.481c0.082-0.164,0.16-0.33,0.236-0.498l0.104,1.269l1.311-0.957l0.566-8.646
+ c0.021-0.337,0.029-0.645,0.021-0.921c-0.002-0.276-0.021-0.524-0.053-0.742c-0.035-0.217-0.08-0.403-0.14-0.561
+ c-0.062-0.157-0.133-0.283-0.22-0.38c-0.086-0.099-0.19-0.161-0.309-0.195c-0.119-0.033-0.254-0.036-0.404-0.005
+ c-0.149,0.028-0.317,0.092-0.5,0.185C184.432,130.682,184.232,130.807,184.02,130.961"/>
+ <path fill="#FFFFFF" d="M191.021,121.232l-1.761,1.275l-0.987,15.075c-0.019,0.257-0.023,0.488-0.023,0.693
+ s0.011,0.387,0.025,0.543c0.02,0.156,0.045,0.287,0.078,0.393s0.074,0.188,0.127,0.246c0.051,0.057,0.108,0.093,0.18,0.113
+ c0.068,0.02,0.146,0.021,0.234,0.004c0.088-0.016,0.184-0.053,0.289-0.105c0.104-0.052,0.221-0.125,0.344-0.215
+ c0.104-0.077,0.212-0.162,0.32-0.256c0.106-0.094,0.219-0.196,0.33-0.307c0.112-0.11,0.227-0.23,0.342-0.358
+ c0.116-0.128,0.229-0.265,0.352-0.409l0.027-1.81l-0.426,0.332c-0.046,0.032-0.082,0.056-0.119,0.069s-0.07,0.018-0.099,0.012
+ c-0.028-0.004-0.053-0.019-0.073-0.041c-0.021-0.022-0.041-0.055-0.056-0.094c-0.015-0.041-0.022-0.099-0.03-0.168
+ c-0.007-0.07-0.013-0.156-0.015-0.259c-0.002-0.101,0-0.217,0.006-0.347c0.002-0.131,0.009-0.277,0.021-0.438L191.021,121.232"
+ />
+ <path fill="#FFFFFF" d="M195.492,132.34l-0.818,0.523l0.33-5.009l0.77-0.559c0.14-0.101,0.269-0.181,0.39-0.24
+ c0.121-0.06,0.229-0.098,0.332-0.114c0.101-0.017,0.19-0.013,0.271,0.012c0.084,0.024,0.152,0.072,0.215,0.139
+ c0.062,0.069,0.115,0.148,0.158,0.243c0.041,0.094,0.076,0.201,0.104,0.322c0.023,0.12,0.041,0.254,0.047,0.402
+ c0.006,0.149,0.006,0.31-0.006,0.485c-0.016,0.205-0.035,0.403-0.063,0.597c-0.029,0.192-0.063,0.379-0.107,0.563
+ c-0.045,0.185-0.094,0.36-0.153,0.534c-0.058,0.173-0.119,0.342-0.194,0.506c-0.07,0.164-0.151,0.318-0.242,0.469
+ c-0.088,0.148-0.187,0.289-0.293,0.422c-0.105,0.135-0.219,0.26-0.342,0.376C195.764,132.129,195.633,132.238,195.492,132.34
+ M195.164,125.433l0.277-4.249l0.852-0.618c0.098-0.069,0.186-0.122,0.27-0.158c0.082-0.036,0.158-0.056,0.228-0.06
+ c0.069-0.004,0.133,0.008,0.188,0.037c0.059,0.028,0.106,0.071,0.149,0.131c0.041,0.062,0.078,0.134,0.104,0.219
+ c0.029,0.085,0.056,0.18,0.068,0.287c0.018,0.107,0.027,0.226,0.029,0.355c0.004,0.129,0,0.27-0.012,0.423
+ c-0.01,0.155-0.025,0.309-0.051,0.461c-0.023,0.153-0.058,0.305-0.095,0.457c-0.038,0.15-0.084,0.301-0.137,0.45
+ c-0.054,0.149-0.115,0.298-0.183,0.445c-0.067,0.146-0.143,0.286-0.222,0.418c-0.081,0.132-0.167,0.257-0.261,0.374
+ c-0.094,0.116-0.191,0.225-0.299,0.326c-0.105,0.101-0.219,0.195-0.338,0.282L195.164,125.433 M196.744,117.89l-2.961,2.146
+ l-1.102,16.726l2.411-1.762c0.334-0.243,0.646-0.501,0.938-0.773c0.292-0.271,0.563-0.559,0.813-0.858
+ c0.25-0.302,0.479-0.617,0.688-0.948c0.206-0.331,0.395-0.676,0.561-1.037c0.164-0.357,0.313-0.72,0.443-1.086
+ c0.131-0.363,0.244-0.732,0.34-1.105c0.097-0.373,0.174-0.75,0.233-1.129c0.062-0.379,0.104-0.763,0.13-1.15
+ c0.014-0.209,0.018-0.409,0.01-0.598c-0.008-0.19-0.026-0.368-0.06-0.537c-0.026-0.168-0.067-0.326-0.119-0.474
+ c-0.053-0.147-0.114-0.285-0.188-0.412c-0.069-0.127-0.149-0.23-0.233-0.312c-0.086-0.082-0.176-0.14-0.275-0.176
+ c-0.1-0.036-0.201-0.05-0.313-0.04c-0.111,0.009-0.229,0.042-0.353,0.097c0.119-0.177,0.229-0.354,0.334-0.531
+ c0.104-0.177,0.201-0.353,0.291-0.53c0.09-0.177,0.172-0.354,0.248-0.533c0.075-0.177,0.146-0.356,0.207-0.535
+ c0.062-0.176,0.117-0.36,0.164-0.551c0.053-0.19,0.096-0.388,0.135-0.592c0.037-0.203,0.069-0.413,0.101-0.63
+ c0.024-0.217,0.045-0.438,0.063-0.668c0.021-0.313,0.023-0.598,0.016-0.856c-0.014-0.258-0.037-0.487-0.08-0.69
+ c-0.041-0.203-0.1-0.377-0.172-0.526c-0.074-0.148-0.164-0.269-0.268-0.362c-0.104-0.092-0.225-0.15-0.359-0.179
+ c-0.137-0.027-0.287-0.024-0.453,0.012s-0.348,0.104-0.545,0.205C197.188,117.591,196.973,117.724,196.744,117.89"/>
+ <path fill="#FFFFFF" d="M203.377,126.843c-0.111,0.082-0.217,0.139-0.311,0.17c-0.096,0.033-0.181,0.041-0.26,0.024
+ c-0.076-0.017-0.146-0.058-0.207-0.124c-0.063-0.066-0.115-0.157-0.16-0.273c-0.043-0.117-0.078-0.271-0.104-0.46
+ c-0.025-0.19-0.043-0.417-0.053-0.681c-0.008-0.263-0.008-0.564,0.002-0.902c0.01-0.337,0.027-0.712,0.057-1.124
+ c0.021-0.324,0.047-0.631,0.08-0.92c0.031-0.29,0.07-0.562,0.113-0.817c0.045-0.255,0.096-0.495,0.15-0.717
+ c0.057-0.221,0.115-0.425,0.184-0.614c0.064-0.188,0.137-0.358,0.213-0.517c0.076-0.159,0.158-0.305,0.246-0.438
+ c0.086-0.131,0.176-0.249,0.271-0.354c0.1-0.105,0.198-0.196,0.309-0.274c0.104-0.075,0.197-0.127,0.287-0.154
+ c0.092-0.026,0.172-0.028,0.246-0.005c0.072,0.023,0.139,0.071,0.197,0.145c0.057,0.072,0.106,0.17,0.149,0.293
+ c0.043,0.124,0.076,0.282,0.103,0.477c0.022,0.195,0.041,0.426,0.047,0.693c0.01,0.267,0.008,0.569-0.002,0.908
+ c-0.011,0.339-0.027,0.713-0.056,1.125c-0.022,0.323-0.049,0.629-0.081,0.916c-0.031,0.288-0.07,0.558-0.113,0.81
+ c-0.043,0.251-0.09,0.485-0.146,0.7c-0.054,0.217-0.113,0.414-0.179,0.593c-0.063,0.182-0.135,0.35-0.209,0.503
+ c-0.071,0.154-0.149,0.293-0.231,0.419c-0.082,0.125-0.17,0.239-0.26,0.338C203.57,126.685,203.477,126.771,203.377,126.843
+ M204.135,116.359c-0.248,0.18-0.484,0.385-0.709,0.616c-0.229,0.231-0.443,0.488-0.646,0.771
+ c-0.204,0.284-0.397,0.593-0.581,0.929c-0.185,0.335-0.357,0.698-0.521,1.087c-0.161,0.386-0.308,0.792-0.438,1.22
+ c-0.13,0.428-0.244,0.876-0.345,1.345c-0.1,0.469-0.186,0.958-0.25,1.469c-0.065,0.511-0.121,1.041-0.156,1.592
+ c-0.034,0.547-0.053,1.044-0.053,1.49c0,0.447,0.019,0.845,0.056,1.191c0.036,0.347,0.092,0.644,0.165,0.892
+ c0.074,0.247,0.164,0.444,0.275,0.594c0.109,0.146,0.238,0.254,0.383,0.318c0.146,0.066,0.308,0.09,0.484,0.074
+ c0.18-0.016,0.375-0.074,0.59-0.172c0.213-0.1,0.441-0.238,0.689-0.418c0.25-0.185,0.488-0.394,0.717-0.627
+ c0.229-0.234,0.447-0.498,0.652-0.785c0.207-0.287,0.402-0.6,0.59-0.94c0.186-0.34,0.359-0.707,0.523-1.101
+ c0.164-0.393,0.313-0.806,0.444-1.237c0.136-0.433,0.248-0.884,0.351-1.355c0.1-0.472,0.184-0.962,0.25-1.472
+ c0.067-0.511,0.123-1.041,0.158-1.59c0.034-0.516,0.049-0.986,0.047-1.412c-0.004-0.426-0.023-0.807-0.064-1.143
+ c-0.039-0.335-0.1-0.625-0.178-0.871c-0.076-0.246-0.174-0.445-0.287-0.6c-0.113-0.155-0.244-0.27-0.393-0.341
+ c-0.146-0.073-0.31-0.103-0.488-0.09c-0.18,0.01-0.375,0.063-0.584,0.157C204.604,116.047,204.377,116.183,204.135,116.359"/>
+ <path fill="#FFFFFF" d="M213.489,109.8l-1.823,1.325c-0.043,0.158-0.084,0.317-0.127,0.478c-0.043,0.16-0.084,0.321-0.129,0.482
+ c-0.043,0.162-0.088,0.324-0.133,0.487c-0.047,0.162-0.094,0.326-0.139,0.49c-0.046,0.163-0.091,0.322-0.138,0.478
+ c-0.045,0.156-0.09,0.31-0.135,0.458c-0.046,0.148-0.091,0.292-0.136,0.433s-0.086,0.277-0.129,0.409l-0.664-2.463l-2.035,1.479
+ l1.427,4.602l-2.521,7.942l1.875-1.37c0.053-0.206,0.105-0.413,0.16-0.619c0.053-0.207,0.107-0.414,0.162-0.621
+ c0.059-0.208,0.113-0.417,0.17-0.626c0.061-0.209,0.117-0.419,0.178-0.631c0.06-0.211,0.117-0.419,0.177-0.623
+ c0.059-0.204,0.116-0.404,0.176-0.601c0.06-0.196,0.116-0.389,0.174-0.579c0.058-0.189,0.115-0.375,0.172-0.558l0.857,3.234
+ l2.096-1.531l-1.696-5.256L213.489,109.8"/>
+ </g>
+ </g>
+ </g>
+</g>
+</svg>
diff --git a/src/VBox/ValidationKit/testmanager/htdocs/images/VirtualBox_64px.png b/src/VBox/ValidationKit/testmanager/htdocs/images/VirtualBox_64px.png Binary files differnew file mode 100644 index 00000000..d8849bdd --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/htdocs/images/VirtualBox_64px.png diff --git a/src/VBox/ValidationKit/testmanager/htdocs/images/tmfavicon.ico b/src/VBox/ValidationKit/testmanager/htdocs/images/tmfavicon.ico Binary files differnew file mode 100644 index 00000000..72f7032d --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/htdocs/images/tmfavicon.ico diff --git a/src/VBox/ValidationKit/testmanager/htdocs/js/Makefile.kup b/src/VBox/ValidationKit/testmanager/htdocs/js/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/htdocs/js/Makefile.kup diff --git a/src/VBox/ValidationKit/testmanager/htdocs/js/common.js b/src/VBox/ValidationKit/testmanager/htdocs/js/common.js new file mode 100644 index 00000000..043e1f00 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/htdocs/js/common.js @@ -0,0 +1,1926 @@ +/* $Id: common.js $ */ +/** @file + * Common JavaScript functions + */ + +/* + * Copyright (C) 2012-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Same as WuiDispatcherBase.ksParamRedirectTo. */ +var g_ksParamRedirectTo = 'RedirectTo'; + +/** Days of the week in Date() style with Sunday first. */ +var g_kasDaysOfTheWeek = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ]; + + +/** + * Detects the firefox browser. + */ +function isBrowserFirefox() +{ + return typeof InstallTrigger !== 'undefined'; +} + +/** + * Detects the google chrome browser. + * @note Might be confused with edge chromium + */ +function isBrowserChrome() +{ + var oChrome = window.chrome; + if (!oChrome) + return false; + return !!oChrome.runtime || !oChrome.webstore; +} + +/** + * Detects the chromium-based edge browser. + */ +function isBrowserEdgeChromium() +{ + if (!isBrowserChrome()) + return false; + return navigation.userAgent.indexOf('Edg') >= 0 +} + +/** + * Detects the chromium-based edge browser. + */ +function isBrowserInternetExplorer() +{ + /* documentMode is an IE only property. Values are 5,7,8,9,10 or 11 + according to google results. */ + if (typeof document.documentMode !== 'undefined') + { + if (document.documentMode) + return true; + } + /* IE only conditional compiling feature. Here, the 'true || ' part + will be included in the if when executing in IE: */ + if (/*@cc_on true || @*/false) + return true; + return false; +} + +/** + * Detects the safari browser (v3+). + */ +function isBrowserSafari() +{ + /* Check if window.HTMLElement is a function named 'HTMLElementConstructor()'? + Should work for older safari versions. */ + var sStr = window.HTMLElement.toString(); + if (/constructor/i.test(sStr)) + return true; + + /* Check the class name of window.safari.pushNotification. This works for current. */ + var oSafari = window['safari']; + if (oSafari) + { + if (typeof oSafari !== 'undefined') + { + var oPushNotify = oSafari.pushNotification; + if (oPushNotify) + { + sStr = oPushNotify.toString(); + if (/\[object Safari.*Notification\]/.test(sStr)) + return true; + } + } + } + return false; +} + +/** + * Checks if the given value is a decimal integer value. + * + * @returns true if it is, false if it's isn't. + * @param sValue The value to inspect. + */ +function isInteger(sValue) +{ + if (typeof sValue != 'undefined') + { + var intRegex = /^\d+$/; + if (intRegex.test(sValue)) + { + return true; + } + } + return false; +} + +/** + * Checks if @a oMemmber is present in aoArray. + * + * @returns true/false. + * @param aoArray The array to check. + * @param oMember The member to check for. + */ +function isMemberOfArray(aoArray, oMember) +{ + var i; + for (i = 0; i < aoArray.length; i++) + if (aoArray[i] == oMember) + return true; + return false; +} + +/** + * Parses a typical ISO timestamp, returing a Date object, reasonably + * forgiving, but will throw weird indexing/conversion errors if the input + * is malformed. + * + * @returns Date object. + * @param sTs The timestamp to parse. + * @sa parseIsoTimestamp() in utils.py. + */ +function parseIsoTimestamp(sTs) +{ + /* YYYY-MM-DD */ + var iYear = parseInt(sTs.substring(0, 4), 10); + console.assert(sTs.charAt(4) == '-'); + var iMonth = parseInt(sTs.substring(5, 7), 10); + console.assert(sTs.charAt(7) == '-'); + var iDay = parseInt(sTs.substring(8, 10), 10); + + /* Skip separator */ + var sTime = sTs.substring(10); + while ('Tt \t\n\r'.includes(sTime.charAt(0))) { + sTime = sTime.substring(1); + } + + /* HH:MM[:SS[.fraction] */ + var iHour = parseInt(sTime.substring(0, 2), 10); + console.assert(sTime.charAt(2) == ':'); + var iMin = parseInt(sTime.substring(3, 5), 10); + var iSec = 0; + var iMicroseconds = 0; + var offTime = 5; + if (sTime.charAt(5) == ':') + { + iSec = parseInt(sTime.substring(6, 8), 10); + + /* Fraction? */ + offTime = 8; + if (offTime < sTime.length && '.,'.includes(sTime.charAt(offTime))) + { + offTime += 1; + var cchFraction = 0; + while (offTime + cchFraction < sTime.length && '0123456789'.includes(sTime.charAt(offTime + cchFraction))) + cchFraction += 1; + if (cchFraction > 0) + { + iMicroseconds = parseInt(sTime.substring(offTime, offTime + cchFraction), 10); + offTime += cchFraction; + while (cchFraction < 6) + { + iMicroseconds *= 10; + cchFraction += 1; + } + while (cchFraction > 6) + { + iMicroseconds = iMicroseconds / 10; + cchFraction -= 1; + } + } + } + } + var iMilliseconds = (iMicroseconds + 499) / 1000; + + /* Naive? */ + var oDate = new Date(Date.UTC(iYear, iMonth - 1, iDay, iHour, iMin, iSec, iMilliseconds)); + if (offTime >= sTime.length) + return oDate; + + /* Zulu? */ + if (offTime >= sTime.length || 'Zz'.includes(sTime.charAt(offTime))) + return oDate; + + /* Some kind of offset afterwards. */ + var chSign = sTime.charAt(offTime); + if ('+-'.includes(chSign)) + { + offTime += 1; + var cMinTz = parseInt(sTime.substring(offTime, offTime + 2), 10) * 60; + offTime += 2; + if (offTime < sTime.length && sTime.charAt(offTime) == ':') + offTime += 1; + if (offTime + 2 <= sTime.length) + { + cMinTz += parseInt(sTime.substring(offTime, offTime + 2), 10); + offTime += 2; + } + console.assert(offTime == sTime.length); + if (chSign == '-') + cMinTz = -cMinTz; + + return new Date(oDate.getTime() - cMinTz * 60000); + } + console.assert(false); + return oDate; +} + +/** + * @param oDate Date object. + */ +function formatTimeHHMM(oDate, fNbsp) +{ + var sTime = oDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit'} ); + if (fNbsp === true) + sTime = sTime.replace(' ', '\u00a0'); + + /* Workaround for single digit hours in firefox with en_US (minutes works fine): */ + var iHours = oDate.getHours(); + if ((iHours % 12) < 10) + { + var ch1 = sTime.substr(0, 1); + var ch2 = sTime.substr(1, 1); + if ( ch1 == (iHours % 12).toString() + && !(ch2 >= '0' && ch2 <= '9')) + sTime = '0' + sTime; + } + return sTime; +} + +/** + * Escapes special characters to HTML-safe sequences, for element use. + * + * @returns Escaped string suitable for HTML. + * @param sText Plain text to escape. + */ +function escapeElem(sText) +{ + sText = sText.replace(/&/g, '&'); + sText = sText.replace(/>/g, '<'); + return sText.replace(/</g, '>'); +} + +/** + * Escapes special characters to HTML-safe sequences, for double quoted + * attribute use. + * + * @returns Escaped string suitable for HTML. + * @param sText Plain text to escape. + */ +function escapeAttr(sText) +{ + sText = sText.replace(/&/g, '&'); + sText = sText.replace(/</g, '<'); + sText = sText.replace(/>/g, '>'); + return sText.replace(/"/g, '"'); +} + +/** + * Removes the element with the specified ID. + */ +function removeHtmlNode(sContainerId) +{ + var oElement = document.getElementById(sContainerId); + if (oElement) + { + oElement.parentNode.removeChild(oElement); + } +} + +/** + * Sets the value of the element with id @a sInputId to the keys of aoItems + * (comma separated). + */ +function setElementValueToKeyList(sInputId, aoItems) +{ + var sKey; + var oElement = document.getElementById(sInputId); + oElement.value = ''; + + for (sKey in aoItems) + { + if (oElement.value.length > 0) + { + oElement.value += ','; + } + + oElement.value += sKey; + } +} + +/** + * Get the Window.devicePixelRatio in a safe way. + * + * @returns Floating point ratio. 1.0 means it's a 1:1 ratio. + */ +function getDevicePixelRatio() +{ + var fpRatio = 1.0; + if (window.devicePixelRatio) + { + fpRatio = window.devicePixelRatio; + if (fpRatio < 0.5 || fpRatio > 10.0) + fpRatio = 1.0; + } + return fpRatio; +} + +/** + * Tries to figure out the DPI of the device in the X direction. + * + * @returns DPI on success, null on failure. + */ +function getDeviceXDotsPerInch() +{ + if (window.deviceXDPI && window.deviceXDPI > 48 && window.deviceXDPI < 2048) + { + return window.deviceXDPI; + } + else if (window.devicePixelRatio && window.devicePixelRatio >= 0.5 && window.devicePixelRatio <= 10.0) + { + cDotsPerInch = Math.round(96 * window.devicePixelRatio); + } + else + { + cDotsPerInch = null; + } + return cDotsPerInch; +} + +/** + * Gets the width of the given element (downscaled). + * + * Useful when using the element to figure the size of a image + * or similar. + * + * @returns Number of pixels. null if oElement is bad. + * @param oElement The element (not ID). + */ +function getElementWidth(oElement) +{ + if (oElement && oElement.offsetWidth) + return oElement.offsetWidth; + return null; +} + +/** By element ID version of getElementWidth. */ +function getElementWidthById(sElementId) +{ + return getElementWidth(document.getElementById(sElementId)); +} + +/** + * Gets the real unscaled width of the given element. + * + * Useful when using the element to figure the size of a image + * or similar. + * + * @returns Number of screen pixels. null if oElement is bad. + * @param oElement The element (not ID). + */ +function getUnscaledElementWidth(oElement) +{ + if (oElement && oElement.offsetWidth) + return Math.round(oElement.offsetWidth * getDevicePixelRatio()); + return null; +} + +/** By element ID version of getUnscaledElementWidth. */ +function getUnscaledElementWidthById(sElementId) +{ + return getUnscaledElementWidth(document.getElementById(sElementId)); +} + +/** + * Gets the part of the URL needed for a RedirectTo parameter. + * + * @returns URL string. + */ +function getCurrentBrowerUrlPartForRedirectTo() +{ + var sWhere = window.location.href; + var offTmp; + var offPathKeep; + + /* Find the end of that URL 'path' component. */ + var offPathEnd = sWhere.indexOf('?'); + if (offPathEnd < 0) + offPathEnd = sWhere.indexOf('#'); + if (offPathEnd < 0) + offPathEnd = sWhere.length; + + /* Go backwards from the end of the and find the start of the last component. */ + offPathKeep = sWhere.lastIndexOf("/", offPathEnd); + offTmp = sWhere.lastIndexOf(":", offPathEnd); + if (offPathKeep < offTmp) + offPathKeep = offTmp; + offTmp = sWhere.lastIndexOf("\\", offPathEnd); + if (offPathKeep < offTmp) + offPathKeep = offTmp; + + return sWhere.substring(offPathKeep + 1); +} + +/** + * Adds the given sorting options to the URL and reloads. + * + * This will preserve previous sorting columns except for those + * given in @a aiColumns. + * + * @param sParam Sorting parameter. + * @param aiColumns Array of sorting columns. + */ +function ahrefActionSortByColumns(sParam, aiColumns) +{ + var sWhere = window.location.href; + + var offHash = sWhere.indexOf('#'); + if (offHash < 0) + offHash = sWhere.length; + + var offQm = sWhere.indexOf('?'); + if (offQm > offHash) + offQm = -1; + + var sNew = ''; + if (offQm > 0) + sNew = sWhere.substring(0, offQm); + + sNew += '?' + sParam + '=' + aiColumns[0]; + var i; + for (i = 1; i < aiColumns.length; i++) + sNew += '&' + sParam + '=' + aiColumns[i]; + + if (offQm >= 0 && offQm + 1 < offHash) + { + var sArgs = '&' + sWhere.substring(offQm + 1, offHash); + var off = 0; + while (off < sArgs.length) + { + var offMatch = sArgs.indexOf('&' + sParam + '=', off); + if (offMatch >= 0) + { + if (off < offMatch) + sNew += sArgs.substring(off, offMatch); + + var offValue = offMatch + 1 + sParam.length + 1; + offEnd = sArgs.indexOf('&', offValue); + if (offEnd < offValue) + offEnd = sArgs.length; + + var iColumn = parseInt(sArgs.substring(offValue, offEnd)); + if (!isMemberOfArray(aiColumns, iColumn) && !isMemberOfArray(aiColumns, -iColumn)) + sNew += sArgs.substring(offMatch, offEnd); + + off = offEnd; + } + else + { + sNew += sArgs.substring(off); + break; + } + } + } + + if (offHash < sWhere.length) + sNew = sWhere.substr(offHash); + + window.location.href = sNew; +} + +/** + * Sets the value of an input field element (give by ID). + * + * @returns Returns success indicator (true/false). + * @param sFieldId The field ID (required for updating). + * @param sValue The field value. + */ +function setInputFieldValue(sFieldId, sValue) +{ + var oInputElement = document.getElementById(sFieldId); + if (oInputElement) + { + oInputElement.value = sValue; + return true; + } + return false; +} + +/** + * Adds a hidden input field to a form. + * + * @returns The new input field element. + * @param oFormElement The form to append it to. + * @param sName The field name. + * @param sValue The field value. + * @param sFieldId The field ID (optional). + */ +function addHiddenInputFieldToForm(oFormElement, sName, sValue, sFieldId) +{ + var oNew = document.createElement('input'); + oNew.type = 'hidden'; + oNew.name = sName; + oNew.value = sValue; + if (sFieldId) + oNew.id = sFieldId; + oFormElement.appendChild(oNew); + return oNew; +} + +/** By element ID version of addHiddenInputFieldToForm. */ +function addHiddenInputFieldToFormById(sFormId, sName, sValue, sFieldId) +{ + return addHiddenInputFieldToForm(document.getElementById(sFormId), sName, sValue, sFieldId); +} + +/** + * Adds or updates a hidden input field to/on a form. + * + * @returns The new input field element. + * @param sFormId The ID of the form to amend. + * @param sName The field name. + * @param sValue The field value. + * @param sFieldId The field ID (required for updating). + */ +function addUpdateHiddenInputFieldToFormById(sFormId, sName, sValue, sFieldId) +{ + var oInputElement = null; + if (sFieldId) + { + oInputElement = document.getElementById(sFieldId); + } + if (oInputElement) + { + oInputElement.name = sName; + oInputElement.value = sValue; + } + else + { + oInputElement = addHiddenInputFieldToFormById(sFormId, sName, sValue, sFieldId); + } + return oInputElement; +} + +/** + * Adds a width and a dpi input to the given form element if possible to + * determine the values. + * + * This is normally employed in an onlick hook, but then you must specify IDs or + * the browser may end up adding it several times. + * + * @param sFormId The ID of the form to amend. + * @param sWidthSrcId The ID of the element to calculate the width + * value from. + * @param sWidthName The name of the width value. + * @param sDpiName The name of the dpi value. + */ +function addDynamicGraphInputs(sFormId, sWidthSrcId, sWidthName, sDpiName) +{ + var cx = getUnscaledElementWidthById(sWidthSrcId); + var cDotsPerInch = getDeviceXDotsPerInch(); + + if (cx) + { + addUpdateHiddenInputFieldToFormById(sFormId, sWidthName, cx, sFormId + '-' + sWidthName + '-id'); + } + + if (cDotsPerInch) + { + addUpdateHiddenInputFieldToFormById(sFormId, sDpiName, cDotsPerInch, sFormId + '-' + sDpiName + '-id'); + } + +} + +/** + * Adds the RedirecTo field with the current URL to the form. + * + * This is a 'onsubmit' action. + * + * @returns Returns success indicator (true/false). + * @param oForm The form being submitted. + */ +function addRedirectToInputFieldWithCurrentUrl(oForm) +{ + /* Constant used here is duplicated in WuiDispatcherBase.ksParamRedirectTo */ + return addHiddenInputFieldToForm(oForm, 'RedirectTo', getCurrentBrowerUrlPartForRedirectTo(), null); +} + +/** + * Adds the RedirecTo parameter to the href of the given anchor. + * + * This is a 'onclick' action. + * + * @returns Returns success indicator (true/false). + * @param oAnchor The anchor element being clicked on. + */ +function addRedirectToAnchorHref(oAnchor) +{ + var sRedirectToParam = g_ksParamRedirectTo + '=' + encodeURIComponent(getCurrentBrowerUrlPartForRedirectTo()); + var sHref = oAnchor.href; + if (sHref.indexOf(sRedirectToParam) < 0) + { + var sHash; + var offHash = sHref.indexOf('#'); + if (offHash >= 0) + sHash = sHref.substring(offHash); + else + { + sHash = ''; + offHash = sHref.length; + } + sHref = sHref.substring(0, offHash) + if (sHref.indexOf('?') >= 0) + sHref += '&'; + else + sHref += '?'; + sHref += sRedirectToParam; + sHref += sHash; + oAnchor.href = sHref; + } + return true; +} + + + +/** + * Clears one input element. + * + * @param oInput The input to clear. + */ +function resetInput(oInput) +{ + switch (oInput.type) + { + case 'checkbox': + case 'radio': + oInput.checked = false; + break; + + case 'text': + oInput.value = 0; + break; + } +} + + +/** + * Clears a form. + * + * @param sIdForm The ID of the form + */ +function clearForm(sIdForm) +{ + var oForm = document.getElementById(sIdForm); + if (oForm) + { + var aoInputs = oForm.getElementsByTagName('INPUT'); + var i; + for (i = 0; i < aoInputs.length; i++) + resetInput(aoInputs[i]) + + /* HTML5 allows inputs outside <form>, so scan the document. */ + aoInputs = document.getElementsByTagName('INPUT'); + for (i = 0; i < aoInputs.length; i++) + if (aoInputs.hasOwnProperty("form")) + if (aoInputs.form == sIdForm) + resetInput(aoInputs[i]) + } + + return true; +} + + +/** + * Used by the time navigation to update the hidden efficient date field when + * either of the date or time fields changes. + * + * @param oForm The form. + */ +function timeNavigationUpdateHiddenEffDate(oForm, sIdSuffix) +{ + var sDate = document.getElementById('EffDate' + sIdSuffix).value; + var sTime = document.getElementById('EffTime' + sIdSuffix).value; + + var oField = document.getElementById('EffDateTime' + sIdSuffix); + oField.value = sDate + 'T' + sTime + '.00Z'; +} + + +/** @name Collapsible / Expandable items + * @{ + */ + + +/** + * Toggles the collapsible / expandable state of a parent DD and DT uncle. + * + * @returns true + * @param oAnchor The anchor object. + */ +function toggleCollapsibleDtDd(oAnchor) +{ + var oParent = oAnchor.parentElement; + var sClass = oParent.className; + + /* Find the DD sibling tag */ + var oDdElement = oParent.nextSibling; + while (oDdElement != null && oDdElement.tagName != 'DD') + oDdElement = oDdElement.nextSibling; + + /* Determin the new class and arrow char. */ + var sNewClass; + var sNewChar; + if ( sClass.substr(-11) == 'collapsible') + { + sNewClass = sClass.substr(0, sClass.length - 11) + 'expandable'; + sNewChar = '\u25B6'; /* black right-pointing triangle */ + } + else if (sClass.substr(-10) == 'expandable') + { + sNewClass = sClass.substr(0, sClass.length - 10) + 'collapsible'; + sNewChar = '\u25BC'; /* black down-pointing triangle */ + } + else + { + console.log('toggleCollapsibleParent: Invalid class: ' + sClass); + return true; + } + + /* Update the parent (DT) class and anchor text. */ + oParent.className = sNewClass; + oAnchor.firstChild.textContent = sNewChar + oAnchor.firstChild.textContent.substr(1); + + /* Update the uncle (DD) class. */ + if (oDdElement) + oDdElement.className = sNewClass; + return true; +} + +/** + * Shows/hides a sub-category UL according to checkbox status. + * + * The checkbox is expected to be within a label element or something. + * + * @returns true + * @param oInput The input checkbox. + */ +function toggleCollapsibleCheckbox(oInput) +{ + var oParent = oInput.parentElement; + + /* Find the UL sibling element. */ + var oUlElement = oParent.nextSibling; + while (oUlElement != null && oUlElement.tagName != 'UL') + oUlElement = oUlElement.nextSibling; + + /* Change the visibility. */ + if (oInput.checked) + oUlElement.className = oUlElement.className.replace('expandable', 'collapsible'); + else + { + oUlElement.className = oUlElement.className.replace('collapsible', 'expandable'); + + /* Make sure all sub-checkboxes are now unchecked. */ + var aoSubInputs = oUlElement.getElementsByTagName('input'); + var i; + for (i = 0; i < aoSubInputs.length; i++) + aoSubInputs[i].checked = false; + } + return true; +} + +/** + * Toggles the sidebar size so filters can more easily manipulated. + */ +function toggleSidebarSize() +{ + var sLinkText; + if (document.body.className != 'tm-wide-side-menu') + { + document.body.className = 'tm-wide-side-menu'; + sLinkText = '\u00ab\u00ab'; + } + else + { + document.body.className = ''; + sLinkText = '\u00bb\u00bb'; + } + + var aoToggleLink = document.getElementsByClassName('tm-sidebar-size-link'); + var i; + for (i = 0; i < aoToggleLink.length; i++) + if ( aoToggleLink[i].textContent.indexOf('\u00bb') >= 0 + || aoToggleLink[i].textContent.indexOf('\u00ab') >= 0) + aoToggleLink[i].textContent = sLinkText; +} + +/** @} */ + + +/** @name Custom Tooltips + * @{ + */ + +/** Enables non-iframe tooltip code. */ +var g_fNewTooltips = true; + +/** Where we keep tooltip elements when not displayed. */ +var g_dTooltips = {}; +var g_oCurrentTooltip = null; +var g_idTooltipShowTimer = null; +var g_idTooltipHideTimer = null; +var g_cTooltipSvnRevisions = 12; + +/** + * Cancel showing/replacing/repositing a tooltip. + */ +function tooltipResetShowTimer() +{ + if (g_idTooltipShowTimer) + { + clearTimeout(g_idTooltipShowTimer); + g_idTooltipShowTimer = null; + } +} + +/** + * Cancel hiding of the current tooltip. + */ +function tooltipResetHideTimer() +{ + if (g_idTooltipHideTimer) + { + clearTimeout(g_idTooltipHideTimer); + g_idTooltipHideTimer = null; + } +} + +/** + * Really hide the tooltip. + */ +function tooltipReallyHide() +{ + if (g_oCurrentTooltip) + { + //console.log('tooltipReallyHide: ' + g_oCurrentTooltip); + g_oCurrentTooltip.oElm.style.display = 'none'; + g_oCurrentTooltip = null; + } +} + +/** + * Schedule the tooltip for hiding. + */ +function tooltipHide() +{ + function tooltipDelayedHide() + { + tooltipResetHideTimer(); + tooltipReallyHide(); + } + + /* + * Cancel any pending show and schedule hiding if necessary. + */ + tooltipResetShowTimer(); + if (g_oCurrentTooltip && !g_idTooltipHideTimer) + { + g_idTooltipHideTimer = setTimeout(tooltipDelayedHide, 700); + } + + return true; +} + +/** + * Function that is repositions the tooltip when it's shown. + * + * Used directly, via onload, and hackish timers to catch all browsers and + * whatnot. + * + * Will set several tooltip member variables related to position and space. + */ +function tooltipRepositionOnLoad() +{ + //console.log('tooltipRepositionOnLoad'); + if (g_oCurrentTooltip) + { + var oRelToRect = g_oCurrentTooltip.oRelToRect; + var cxNeeded = g_oCurrentTooltip.oElm.offsetWidth + 8; + var cyNeeded = g_oCurrentTooltip.oElm.offsetHeight + 8; + + var cyWindow = window.innerHeight; + var yScroll = window.pageYOffset || document.documentElement.scrollTop; + var yScrollBottom = yScroll + cyWindow; + var cxWindow = window.innerWidth; + var xScroll = window.pageXOffset || document.documentElement.scrollLeft; + var xScrollRight = xScroll + cxWindow; + + var cyAbove = Math.max(oRelToRect.top, 0); + var cyBelow = Math.max(cyWindow - oRelToRect.bottom, 0); + var cxLeft = Math.max(oRelToRect.left, 0); + var cxRight = Math.max(cxWindow - oRelToRect.right, 0); + + var xPos; + var yPos; + + //console.log('tooltipRepositionOnLoad: rect: x,y=' + oRelToRect.x + ',' + oRelToRect.y + // + ' cx,cy=' + oRelToRect.width + ',' + oRelToRect.height + ' top=' + oRelToRect.top + // + ' bottom=' + oRelToRect.bottom + ' left=' + oRelToRect.left + ' right=' + oRelToRect.right); + //console.log('tooltipRepositionOnLoad: yScroll=' + yScroll + ' yScrollBottom=' + yScrollBottom); + //console.log('tooltipRepositionOnLoad: cyAbove=' + cyAbove + ' cyBelow=' + cyBelow + ' cyNeeded=' + cyNeeded); + //console.log('tooltipRepositionOnLoad: xScroll=' + xScroll + ' xScrollRight=' + xScrollRight); + //console.log('tooltipRepositionOnLoad: cxLeft=' + cxLeft + ' cxRight=' + cxRight + ' cxNeeded=' + cxNeeded); + + /* + * Decide where to put the thing. + */ + if (cyNeeded < cyBelow) + { + yPos = yScroll + oRelToRect.top; + g_oCurrentTooltip.cyMax = cyBelow; + //console.log('tooltipRepositionOnLoad: #1'); + } + else if (cyBelow >= cyAbove) + { + yPos = yScrollBottom - cyNeeded; + g_oCurrentTooltip.cyMax = yScrollBottom - yPos; + //console.log('tooltipRepositionOnLoad: #2'); + } + else + { + yPos = yScroll + oRelToRect.bottom - cyNeeded; + g_oCurrentTooltip.cyMax = yScrollBottom - yPos; + //console.log('tooltipRepositionOnLoad: #3'); + } + if (yPos < yScroll) + { + yPos = yScroll; + g_oCurrentTooltip.cyMax = yScrollBottom - yPos; + //console.log('tooltipRepositionOnLoad: #4'); + } + g_oCurrentTooltip.yPos = yPos; + g_oCurrentTooltip.yScroll = yScroll; + g_oCurrentTooltip.cyMaxUp = yPos - yScroll; + //console.log('tooltipRepositionOnLoad: yPos=' + yPos + ' yScroll=' + yScroll + ' cyMaxUp=' + g_oCurrentTooltip.cyMaxUp); + + if (cxNeeded < cxRight) + { + xPos = xScroll + oRelToRect.right; + g_oCurrentTooltip.cxMax = cxRight; + //console.log('tooltipRepositionOnLoad: #5'); + } + else + { + xPos = xScroll + oRelToRect.left - cxNeeded; + if (xPos < xScroll) + xPos = xScroll; + g_oCurrentTooltip.cxMax = cxNeeded; + //console.log('tooltipRepositionOnLoad: #6'); + } + g_oCurrentTooltip.xPos = xPos; + g_oCurrentTooltip.xScroll = xScroll; + //console.log('tooltipRepositionOnLoad: xPos=' + xPos + ' xScroll=' + xScroll); + + g_oCurrentTooltip.oElm.style.top = yPos + 'px'; + g_oCurrentTooltip.oElm.style.left = xPos + 'px'; + } + return true; +} + + +/** + * Really show the tooltip. + * + * @param oTooltip The tooltip object. + * @param oRelTo What to put the tooltip adjecent to. + */ +function tooltipReallyShow(oTooltip, oRelTo) +{ + var oRect; + + tooltipResetShowTimer(); + tooltipResetHideTimer(); + + if (g_oCurrentTooltip == oTooltip) + { + //console.log('moving tooltip'); + } + else if (g_oCurrentTooltip) + { + //console.log('removing current tooltip and showing new'); + tooltipReallyHide(); + } + else + { + //console.log('showing tooltip'); + } + + //oTooltip.oElm.setAttribute('style', 'display: block; position: absolute;'); + oTooltip.oElm.style.position = 'absolute'; + oTooltip.oElm.style.display = 'block'; + oRect = oRelTo.getBoundingClientRect(); + oTooltip.oRelToRect = oRect; + + g_oCurrentTooltip = oTooltip; + + /* + * Do repositioning (again). + */ + tooltipRepositionOnLoad(); +} + +/** + * Tooltip onmouseenter handler . + */ +function tooltipElementOnMouseEnter() +{ + /*console.log('tooltipElementOnMouseEnter: arguments.length='+arguments.length+' [0]='+arguments[0]); + console.log('ENT: currentTarget='+arguments[0].currentTarget+' id='+arguments[0].currentTarget.id+' class='+arguments[0].currentTarget.className); */ + tooltipResetShowTimer(); + tooltipResetHideTimer(); + return true; +} + +/** + * Tooltip onmouseout handler. + * + * @remarks We only use this and onmouseenter for one tooltip element (iframe + * for svn, because chrome is sending onmouseout events after + * onmouseneter for the next element, which would confuse this simple + * code. + */ +function tooltipElementOnMouseOut() +{ + var oEvt = arguments[0]; + /*console.log('tooltipElementOnMouseOut: arguments.length='+arguments.length+' [0]='+oEvt); + console.log('OUT: currentTarget='+oEvt.currentTarget+' id='+oEvt.currentTarget.id+' class='+oEvt.currentTarget.className);*/ + + /* Ignore the event if leaving to a child element. */ + var oElm = oEvt.toElement || oEvt.relatedTarget; + if (oElm != this && oElm) + { + for (;;) + { + oElm = oElm.parentNode; + if (!oElm || oElm == window) + break; + if (oElm == this) + { + console.log('OUT: was to child! - ignore'); + return false; + } + } + } + + tooltipHide(); + return true; +} + +/** + * iframe.onload hook that repositions and resizes the tooltip. + * + * This is a little hacky and we're calling it one or three times too many to + * work around various browser differences too. + */ +function svnHistoryTooltipOldOnLoad() +{ + //console.log('svnHistoryTooltipOldOnLoad'); + + /* + * Resize the tooltip to better fit the content. + */ + tooltipRepositionOnLoad(); /* Sets cxMax and cyMax. */ + if (g_oCurrentTooltip && g_oCurrentTooltip.oIFrame.contentWindow) + { + var oIFrameElement = g_oCurrentTooltip.oIFrame; + var cxSpace = Math.max(oIFrameElement.offsetLeft * 2, 0); /* simplified */ + var cySpace = Math.max(oIFrameElement.offsetTop * 2, 0); /* simplified */ + var cxNeeded = oIFrameElement.contentWindow.document.body.scrollWidth + cxSpace; + var cyNeeded = oIFrameElement.contentWindow.document.body.scrollHeight + cySpace; + var cx = Math.min(cxNeeded, g_oCurrentTooltip.cxMax); + var cy; + + g_oCurrentTooltip.oElm.width = cx + 'px'; + oIFrameElement.width = (cx - cxSpace) + 'px'; + if (cx >= cxNeeded) + { + //console.log('svnHistoryTooltipOldOnLoad: overflowX -> hidden'); + oIFrameElement.style.overflowX = 'hidden'; + } + else + { + oIFrameElement.style.overflowX = 'scroll'; + } + + cy = Math.min(cyNeeded, g_oCurrentTooltip.cyMax); + if (cyNeeded > g_oCurrentTooltip.cyMax && g_oCurrentTooltip.cyMaxUp > 0) + { + var cyMove = Math.min(cyNeeded - g_oCurrentTooltip.cyMax, g_oCurrentTooltip.cyMaxUp); + g_oCurrentTooltip.cyMax += cyMove; + g_oCurrentTooltip.yPos -= cyMove; + g_oCurrentTooltip.oElm.style.top = g_oCurrentTooltip.yPos + 'px'; + cy = Math.min(cyNeeded, g_oCurrentTooltip.cyMax); + } + + g_oCurrentTooltip.oElm.height = cy + 'px'; + oIFrameElement.height = (cy - cySpace) + 'px'; + if (cy >= cyNeeded) + { + //console.log('svnHistoryTooltipOldOnLoad: overflowY -> hidden'); + oIFrameElement.style.overflowY = 'hidden'; + } + else + { + oIFrameElement.style.overflowY = 'scroll'; + } + + //console.log('cyNeeded='+cyNeeded+' cyMax='+g_oCurrentTooltip.cyMax+' cySpace='+cySpace+' cy='+cy); + //console.log('oIFrameElement.offsetTop='+oIFrameElement.offsetTop); + //console.log('svnHistoryTooltipOldOnLoad: cx='+cx+'cxMax='+g_oCurrentTooltip.cxMax+' cxNeeded='+cxNeeded+' cy='+cy+' cyMax='+g_oCurrentTooltip.cyMax); + + tooltipRepositionOnLoad(); + } + return true; +} + +/** + * iframe.onload hook that repositions and resizes the tooltip. + * + * This is a little hacky and we're calling it one or three times too many to + * work around various browser differences too. + */ +function svnHistoryTooltipNewOnLoad() +{ + //console.log('svnHistoryTooltipNewOnLoad'); + + /* + * Resize the tooltip to better fit the content. + */ + tooltipRepositionOnLoad(); /* Sets cxMax and cyMax. */ + oTooltip = g_oCurrentTooltip; + if (oTooltip) + { + var oElmInner = oTooltip.oInnerElm; + var cxSpace = Math.max(oElmInner.offsetLeft * 2, 0); /* simplified */ + var cySpace = Math.max(oElmInner.offsetTop * 2, 0); /* simplified */ + var cxNeeded = oElmInner.scrollWidth + cxSpace; + var cyNeeded = oElmInner.scrollHeight + cySpace; + var cx = Math.min(cxNeeded, oTooltip.cxMax); + + oTooltip.oElm.width = cx + 'px'; + oElmInner.width = (cx - cxSpace) + 'px'; + if (cx >= cxNeeded) + { + //console.log('svnHistoryTooltipNewOnLoad: overflowX -> hidden'); + oElmInner.style.overflowX = 'hidden'; + } + else + { + oElmInner.style.overflowX = 'scroll'; + } + + var cy = Math.min(cyNeeded, oTooltip.cyMax); + if (cyNeeded > oTooltip.cyMax && oTooltip.cyMaxUp > 0) + { + var cyMove = Math.min(cyNeeded - oTooltip.cyMax, oTooltip.cyMaxUp); + oTooltip.cyMax += cyMove; + oTooltip.yPos -= cyMove; + oTooltip.oElm.style.top = oTooltip.yPos + 'px'; + cy = Math.min(cyNeeded, oTooltip.cyMax); + } + + oTooltip.oElm.height = cy + 'px'; + oElmInner.height = (cy - cySpace) + 'px'; + if (cy >= cyNeeded) + { + //console.log('svnHistoryTooltipNewOnLoad: overflowY -> hidden'); + oElmInner.style.overflowY = 'hidden'; + } + else + { + oElmInner.style.overflowY = 'scroll'; + } + + //console.log('cyNeeded='+cyNeeded+' cyMax='+oTooltip.cyMax+' cySpace='+cySpace+' cy='+cy); + //console.log('oElmInner.offsetTop='+oElmInner.offsetTop); + //console.log('svnHistoryTooltipNewOnLoad: cx='+cx+'cxMax='+oTooltip.cxMax+' cxNeeded='+cxNeeded+' cy='+cy+' cyMax='+oTooltip.cyMax); + + tooltipRepositionOnLoad(); + } + return true; +} + + +function svnHistoryTooltipNewOnReadState(oTooltip, oRestReq, oParent) +{ + /*console.log('svnHistoryTooltipNewOnReadState: status=' + oRestReq.status + ' readyState=' + oRestReq.readyState);*/ + if (oRestReq.readyState != oRestReq.DONE) + { + oTooltip.oInnerElm.innerHTML = '<p>Loading ...(' + oRestReq.readyState + ')</p>'; + return true; + } + + /* + * Check the result and translate it to a javascript object (oResp). + */ + var oResp = null; + var sHtml; + if (oRestReq.status != 200) + { + console.log('svnHistoryTooltipNewOnReadState: status=' + oRestReq.status); + sHtml = '<p>error: status=' + oRestReq.status + '</p>'; + } + else + { + try + { + oResp = JSON.parse(oRestReq.responseText); + } + catch (oEx) + { + console.log('JSON.parse threw: ' + oEx.toString()); + console.log(oRestReq.responseText); + sHtml = '<p>error: JSON.parse threw: ' + oEx.toString() + '</p>'; + } + } + + /* + * Generate the HTML. + * + * Note! Make sure the highlighting code in svnHistoryTooltipNewDelayedShow + * continues to work after modifying this code. + */ + if (oResp) + { + sHtml = '<div class="tmvcstimeline tmvcstimelinetooltip">\n'; + + var aoCommits = oResp.aoCommits; + var cCommits = oResp.aoCommits.length; + var iCurDay = null; + var i; + for (i = 0; i < cCommits; i++) + { + var oCommit = aoCommits[i]; + var tsCreated = parseIsoTimestamp(oCommit.tsCreated); + var iCommitDay = Math.floor((tsCreated.getTime() + tsCreated.getTimezoneOffset()) / (24 * 60 * 60 * 1000)); + if (iCurDay === null || iCurDay != iCommitDay) + { + if (iCurDay !== null) + sHtml += ' </dl>\n'; + iCurDay = iCommitDay; + sHtml += ' <h2>' + tsCreated.toISOString().split('T')[0] + ' ' + g_kasDaysOfTheWeek[tsCreated.getDay()] + '</h2>\n'; + sHtml += ' <dl>\n'; + } + Date + + var sHighligh = ''; + if (oCommit.iRevision == oTooltip.iRevision) + sHighligh += ' class="tmvcstimeline-highlight"'; + + sHtml += ' <dt id="r' + oCommit.iRevision + '"' + sHighligh + '>'; + sHtml += '<a href="' + oResp.sTracChangesetUrlFmt.replace('%(iRevision)s', oCommit.iRevision.toString()); + sHtml += '" target="_blank">'; + sHtml += '<span class="tmvcstimeline-time">' + escapeElem(formatTimeHHMM(tsCreated, true)) + '</span>' + sHtml += ' Changeset <span class="tmvcstimeline-rev">[' + oCommit.iRevision + ']</span>'; + sHtml += ' by <span class="tmvcstimeline-author">' + escapeElem(oCommit.sAuthor) + '</span>'; + sHtml += '</a></dt>\n'; + sHtml += ' <dd' + sHighligh + '>' + escapeElem(oCommit.sMessage) + '</dd>\n'; + } + + if (iCurDay !== null) + sHtml += ' </dl>\n'; + sHtml += '</div>'; + } + + /*console.log('svnHistoryTooltipNewOnReadState: sHtml=' + sHtml);*/ + oTooltip.oInnerElm.innerHTML = sHtml; + + tooltipReallyShow(oTooltip, oParent); + svnHistoryTooltipNewOnLoad(); +} + +/** + * Calculates the last revision to get when showing a tooltip for @a iRevision. + * + * A tooltip covers several change log entries, both to limit the number of + * tooltips to load and to give context. The exact number is defined by + * g_cTooltipSvnRevisions. + * + * @returns Last revision in a tooltip. + * @param iRevision The revision number. + */ +function svnHistoryTooltipCalcLastRevision(iRevision) +{ + var iFirstRev = Math.floor(iRevision / g_cTooltipSvnRevisions) * g_cTooltipSvnRevisions; + return iFirstRev + g_cTooltipSvnRevisions - 1; +} + +/** + * Calculates a unique ID for the tooltip element. + * + * This is also used as dictionary index. + * + * @returns tooltip ID value (string). + * @param sRepository The repository name. + * @param iRevision The revision number. + */ +function svnHistoryTooltipCalcId(sRepository, iRevision) +{ + return 'svnHistoryTooltip_' + sRepository + '_' + svnHistoryTooltipCalcLastRevision(iRevision); +} + +/** + * The onmouseenter event handler for creating the tooltip. + * + * @param oEvt The event. + * @param sRepository The repository name. + * @param iRevision The revision number. + * @param sUrlPrefix URL prefix for non-testmanager use. + * + * @remarks onmouseout must be set to call tooltipHide. + */ +function svnHistoryTooltipShowEx(oEvt, sRepository, iRevision, sUrlPrefix) +{ + var sKey = svnHistoryTooltipCalcId(sRepository, iRevision); + var oParent = oEvt.currentTarget; + //console.log('svnHistoryTooltipShow ' + sRepository); + + function svnHistoryTooltipOldDelayedShow() + { + var sSrc; + + var oTooltip = g_dTooltips[sKey]; + //console.log('svnHistoryTooltipOldDelayedShow ' + sRepository + ' ' + oTooltip); + if (!oTooltip) + { + /* + * Create a new tooltip element. + */ + //console.log('creating ' + sKey); + oTooltip = {}; + oTooltip.oElm = document.createElement('div'); + oTooltip.oElm.setAttribute('id', sKey); + oTooltip.oElm.className = 'tmvcstooltip'; + //oTooltip.oElm.setAttribute('style', 'display:none; position: absolute;'); + oTooltip.oElm.style.display = 'none'; /* Note! Must stay hidden till loaded, or parent jumps with #rXXXX.*/ + oTooltip.oElm.style.position = 'absolute'; + oTooltip.oElm.style.zIndex = 6001; + oTooltip.xPos = 0; + oTooltip.yPos = 0; + oTooltip.cxMax = 0; + oTooltip.cyMax = 0; + oTooltip.cyMaxUp = 0; + oTooltip.xScroll = 0; + oTooltip.yScroll = 0; + oTooltip.iRevision = iRevision; /**< For :target/highlighting */ + + var oIFrameElement = document.createElement('iframe'); + oIFrameElement.setAttribute('id', sKey + '_iframe'); + oIFrameElement.style.position = 'relative'; + oIFrameElement.onmouseenter = tooltipElementOnMouseEnter; + //oIFrameElement.onmouseout = tooltipElementOnMouseOut; + oTooltip.oElm.appendChild(oIFrameElement); + oTooltip.oIFrame = oIFrameElement; + g_dTooltips[sKey] = oTooltip; + + document.body.appendChild(oTooltip.oElm); + + oIFrameElement.onload = function() { /* A slight delay here to give time for #rXXXX scrolling before we show it. */ + setTimeout(function(){ + /*console.log('iframe/onload');*/ + tooltipReallyShow(oTooltip, oParent); + svnHistoryTooltipOldOnLoad(); + }, isBrowserInternetExplorer() ? 256 : 128); + }; + + var sUrl = sUrlPrefix + 'index.py?Action=VcsHistoryTooltip&repo=' + sRepository + + '&rev=' + svnHistoryTooltipCalcLastRevision(iRevision) + + '&cEntries=' + g_cTooltipSvnRevisions + + '#r' + iRevision; + oIFrameElement.src = sUrl; + } + else + { + /* + * Show the existing one, possibly with different :target/highlighting. + */ + if (oTooltip.iRevision != iRevision) + { + //console.log('Changing revision ' + oTooltip.iRevision + ' -> ' + iRevision); + oTooltip.oIFrame.contentWindow.location.hash = '#r' + iRevision; + if (!isBrowserFirefox()) /* Chrome updates stuff like expected; Firefox OTOH doesn't change anything. */ + { + setTimeout(function() { /* Slight delay to make sure it scrolls before it's shown. */ + tooltipReallyShow(oTooltip, oParent); + svnHistoryTooltipOldOnLoad(); + }, isBrowserInternetExplorer() ? 256 : 64); + } + else + oTooltip.oIFrame.contentWindow.location.reload(); + } + else + { + tooltipReallyShow(oTooltip, oParent); + svnHistoryTooltipOldOnLoad(); + } + } + } + + function svnHistoryTooltipNewDelayedShow() + { + var sSrc; + + var oTooltip = g_dTooltips[sKey]; + /*console.log('svnHistoryTooltipNewDelayedShow: ' + sRepository + ' ' + oTooltip);*/ + if (!oTooltip) + { + /* + * Create a new tooltip element. + */ + /*console.log('creating ' + sKey);*/ + + var oElm = document.createElement('div'); + oElm.setAttribute('id', sKey); + oElm.className = 'tmvcstooltipnew'; + //oElm.setAttribute('style', 'display:none; position: absolute;'); + oElm.style.display = 'none'; /* Note! Must stay hidden till loaded, or parent jumps with #rXXXX.*/ + oElm.style.position = 'absolute'; + oElm.style.zIndex = 6001; + oElm.onmouseenter = tooltipElementOnMouseEnter; + oElm.onmouseout = tooltipElementOnMouseOut; + + var oInnerElm = document.createElement('div'); + oInnerElm.className = 'tooltip-inner'; + oElm.appendChild(oInnerElm); + + oTooltip = {}; + oTooltip.oElm = oElm; + oTooltip.oInnerElm = oInnerElm; + oTooltip.xPos = 0; + oTooltip.yPos = 0; + oTooltip.cxMax = 0; + oTooltip.cyMax = 0; + oTooltip.cyMaxUp = 0; + oTooltip.xScroll = 0; + oTooltip.yScroll = 0; + oTooltip.iRevision = iRevision; /**< For :target/highlighting */ + + oRestReq = new XMLHttpRequest(); + oRestReq.onreadystatechange = function() { svnHistoryTooltipNewOnReadState(oTooltip, this, oParent); } + oRestReq.open('GET', sUrlPrefix + 'rest.py?sPath=vcs/changelog/' + sRepository + + '/' + svnHistoryTooltipCalcLastRevision(iRevision) + '/' + g_cTooltipSvnRevisions); + oRestReq.setRequestHeader('Content-type', 'application/json'); + + document.body.appendChild(oTooltip.oElm); + g_dTooltips[sKey] = oTooltip; + + oRestReq.send(''); + } + else + { + /* + * Show the existing one, possibly with different highlighting. + * Note! Update this code when changing svnHistoryTooltipNewOnReadState. + */ + if (oTooltip.iRevision != iRevision) + { + //console.log('Changing revision ' + oTooltip.iRevision + ' -> ' + iRevision); + var oElmTimelineDiv = oTooltip.oInnerElm.firstElementChild; + var i; + for (i = 0; i < oElmTimelineDiv.children.length; i++) + { + var oElm = oElmTimelineDiv.children[i]; + //console.log('oElm='+oElm+' id='+oElm.id+' nodeName='+oElm.nodeName); + if (oElm.nodeName == 'DL') + { + var iCurRev = iRevision - 64; + var j; + for (j = 0; i < oElm.children.length; i++) + { + var oDlSubElm = oElm.children[i]; + //console.log(' oDlSubElm='+oDlSubElm+' id='+oDlSubElm.id+' nodeName='+oDlSubElm.nodeName+' className='+oDlSubElm.className); + if (oDlSubElm.id.length > 2) + iCurRev = parseInt(oDlSubElm.id.substring(1), 10); + if (iCurRev == iRevision) + oDlSubElm.className = 'tmvcstimeline-highlight'; + else + oDlSubElm.className = ''; + } + } + } + oTooltip.iRevision = iRevision; + } + + tooltipReallyShow(oTooltip, oParent); + svnHistoryTooltipNewOnLoad(); + } + } + + + /* + * Delay the change (in case the mouse moves on). + */ + tooltipResetShowTimer(); + if (g_fNewTooltips) + g_idTooltipShowTimer = setTimeout(svnHistoryTooltipNewDelayedShow, 512); + else + g_idTooltipShowTimer = setTimeout(svnHistoryTooltipOldDelayedShow, 512); +} + +/** + * The onmouseenter event handler for creating the tooltip. + * + * @param oEvt The event. + * @param sRepository The repository name. + * @param iRevision The revision number. + * + * @remarks onmouseout must be set to call tooltipHide. + */ +function svnHistoryTooltipShow(oEvt, sRepository, iRevision) +{ + return svnHistoryTooltipShowEx(oEvt, sRepository, iRevision, ''); +} + +/** @} */ + + +/** @name Debugging and Introspection + * @{ + */ + +/** + * Python-like dir() implementation. + * + * @returns Array of names associated with oObj. + * @param oObj The object under inspection. If not specified we'll + * look at the window object. + */ +function pythonlikeDir(oObj, fDeep) +{ + var aRet = []; + var dTmp = {}; + + if (!oObj) + { + oObj = window; + } + + for (var oCur = oObj; oCur; oCur = Object.getPrototypeOf(oCur)) + { + var aThis = Object.getOwnPropertyNames(oCur); + for (var i = 0; i < aThis.length; i++) + { + if (!(aThis[i] in dTmp)) + { + dTmp[aThis[i]] = 1; + aRet.push(aThis[i]); + } + } + } + + return aRet; +} + + +/** + * Python-like dir() implementation, shallow version. + * + * @returns Array of names associated with oObj. + * @param oObj The object under inspection. If not specified we'll + * look at the window object. + */ +function pythonlikeShallowDir(oObj, fDeep) +{ + var aRet = []; + var dTmp = {}; + + if (oObj) + { + for (var i in oObj) + { + aRet.push(i); + } + } + + return aRet; +} + + + +function dbgGetObjType(oObj) +{ + var sType = typeof oObj; + if (sType == "object" && oObj !== null) + { + if (oObj.constructor && oObj.constructor.name) + { + sType = oObj.constructor.name; + } + else + { + var fnToString = Object.prototype.toString; + var sTmp = fnToString.call(oObj); + if (sTmp.indexOf('[object ') === 0) + { + sType = sTmp.substring(8, sTmp.length); + } + } + } + return sType; +} + + +/** + * Dumps the given object to the console. + * + * @param oObj The object under inspection. + * @param sPrefix What to prefix the log output with. + */ +function dbgDumpObj(oObj, sName, sPrefix) +{ + var aMembers; + var sType; + + /* + * Defaults + */ + if (!oObj) + { + oObj = window; + } + + if (!sPrefix) + { + if (sName) + { + sPrefix = sName + ':'; + } + else + { + sPrefix = 'dbgDumpObj:'; + } + } + + if (!sName) + { + sName = ''; + } + + /* + * The object itself. + */ + sPrefix = sPrefix + ' '; + console.log(sPrefix + sName + ' ' + dbgGetObjType(oObj)); + + /* + * The members. + */ + sPrefix = sPrefix + ' '; + aMembers = pythonlikeDir(oObj); + for (i = 0; i < aMembers.length; i++) + { + console.log(sPrefix + aMembers[i]); + } + + return true; +} + +function dbgDumpObjWorker(sType, sName, oObj, sPrefix) +{ + var sRet; + switch (sType) + { + case 'function': + { + sRet = sPrefix + 'function ' + sName + '()' + '\n'; + break; + } + + case 'object': + { + sRet = sPrefix + 'var ' + sName + '(' + dbgGetObjType(oObj) + ') ='; + if (oObj !== null) + { + sRet += '\n'; + } + else + { + sRet += ' null\n'; + } + break; + } + + case 'string': + { + sRet = sPrefix + 'var ' + sName + '(string, ' + oObj.length + ')'; + if (oObj.length < 80) + { + sRet += ' = "' + oObj + '"\n'; + } + else + { + sRet += '\n'; + } + break; + } + + case 'Oops!': + sRet = sPrefix + sName + '(??)\n'; + break; + + default: + sRet = sPrefix + 'var ' + sName + '(' + sType + ')\n'; + break; + } + return sRet; +} + + +function dbgObjInArray(aoObjs, oObj) +{ + var i = aoObjs.length; + while (i > 0) + { + i--; + if (aoObjs[i] === oObj) + { + return true; + } + } + return false; +} + +function dbgDumpObjTreeWorker(oObj, sPrefix, aParentObjs, cMaxDepth) +{ + var sRet = ''; + var aMembers = pythonlikeShallowDir(oObj); + var i; + + for (i = 0; i < aMembers.length; i++) + { + //var sName = i; + var sName = aMembers[i]; + var oMember; + var sType; + var oEx; + + try + { + oMember = oObj[sName]; + sType = typeof oObj[sName]; + } + catch (oEx) + { + oMember = null; + sType = 'Oops!'; + } + + //sRet += '[' + i + '/' + aMembers.length + ']'; + sRet += dbgDumpObjWorker(sType, sName, oMember, sPrefix); + + if ( sType == 'object' + && oObj !== null) + { + + if (dbgObjInArray(aParentObjs, oMember)) + { + sRet += sPrefix + '! parent recursion\n'; + } + else if ( sName == 'previousSibling' + || sName == 'previousElement' + || sName == 'lastChild' + || sName == 'firstElementChild' + || sName == 'lastElementChild' + || sName == 'nextElementSibling' + || sName == 'prevElementSibling' + || sName == 'parentElement' + || sName == 'ownerDocument') + { + sRet += sPrefix + '! potentially dangerous element name\n'; + } + else if (aParentObjs.length >= cMaxDepth) + { + sRet = sRet.substring(0, sRet.length - 1); + sRet += ' <too deep>!\n'; + } + else + { + + aParentObjs.push(oMember); + if (i + 1 < aMembers.length) + { + sRet += dbgDumpObjTreeWorker(oMember, sPrefix + '| ', aParentObjs, cMaxDepth); + } + else + { + sRet += dbgDumpObjTreeWorker(oMember, sPrefix.substring(0, sPrefix.length - 2) + ' | ', aParentObjs, cMaxDepth); + } + aParentObjs.pop(); + } + } + } + return sRet; +} + +/** + * Dumps the given object and all it's subobjects to the console. + * + * @returns String dump of the object. + * @param oObj The object under inspection. + * @param sName The object name (optional). + * @param sPrefix What to prefix the log output with (optional). + * @param cMaxDepth The max depth, optional. + */ +function dbgDumpObjTree(oObj, sName, sPrefix, cMaxDepth) +{ + var sType; + var sRet; + var oEx; + + /* + * Defaults + */ + if (!sPrefix) + { + sPrefix = ''; + } + + if (!sName) + { + sName = '??'; + } + + if (!cMaxDepth) + { + cMaxDepth = 2; + } + + /* + * The object itself. + */ + try + { + sType = typeof oObj; + } + catch (oEx) + { + sType = 'Oops!'; + } + sRet = dbgDumpObjWorker(sType, sName, oObj, sPrefix); + if (sType == 'object' && oObj !== null) + { + var aParentObjs = Array(); + aParentObjs.push(oObj); + sRet += dbgDumpObjTreeWorker(oObj, sPrefix + '| ', aParentObjs, cMaxDepth); + } + + return sRet; +} + +function dbgLogString(sLongString) +{ + var aStrings = sLongString.split("\n"); + var i; + for (i = 0; i < aStrings.length; i++) + { + console.log(aStrings[i]); + } + console.log('dbgLogString - end - ' + aStrings.length + '/' + sLongString.length); + return true; +} + +function dbgLogObjTree(oObj, sName, sPrefix, cMaxDepth) +{ + return dbgLogString(dbgDumpObjTree(oObj, sName, sPrefix, cMaxDepth)); +} + +/** @} */ + diff --git a/src/VBox/ValidationKit/testmanager/htdocs/js/graphwiz.js b/src/VBox/ValidationKit/testmanager/htdocs/js/graphwiz.js new file mode 100644 index 00000000..28c1e86a --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/htdocs/js/graphwiz.js @@ -0,0 +1,126 @@ +/* $Id: graphwiz.js $ */ +/** @file + * JavaScript functions for the Graph Wizard. + */ + +/* + * Copyright (C) 2012-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/******************************************************************************* +* Global Variables * +*******************************************************************************/ +/** The previous width of the div element that we measure. */ +var g_cxPreviousWidth = 0; + + +/** + * onload function that sets g_cxPreviousWidth to the width of @a sWidthSrcId. + * + * @returns true. + * @param sWidthSrcId The ID of the element which width we should measure. + */ +function graphwizOnLoadRememberWidth(sWidthSrcId) +{ + var cx = getUnscaledElementWidthById(sWidthSrcId); + if (cx) + { + g_cxPreviousWidth = cx; + } + return true; +} + + +/** + * onresize callback function that scales the given graph width input field + * value according to the resized element. + * + * @returns true. + * @param sWidthSrcId The ID of the element which width we should measure + * the resize effect on. + * @param sWidthInputId The ID of the input field which values should be + * scaled. + * + * @remarks Since we're likely to get several resize calls as part of one user + * resize operation, we're likely to suffer from some rounding + * artifacts. So, should the user abort or undo the resizing, the + * width value is unlikely to be restored to the exact value it had + * prior to the resizing. + */ +function graphwizOnResizeRecalcWidth(sWidthSrcId, sWidthInputId) +{ + var cx = getUnscaledElementWidthById(sWidthSrcId); + if (cx) + { + var oElement = document.getElementById(sWidthInputId); + if (oElement && g_cxPreviousWidth) + { + var cxOld = oElement.value; + if (isInteger(cxOld)) + { + var fpRatio = cxOld / g_cxPreviousWidth; + oElement.value = Math.round(cx * fpRatio); + } + } + g_cxPreviousWidth = cx; + } + + return true; +} + +/** + * Fills thegraph size (cx, cy) and dpi fields with default values. + * + * @returns false (for onclick). + * @param sWidthSrcId The ID of the element which width we should measure. + * @param sWidthInputId The ID of the graph width field (cx). + * @param sHeightInputId The ID of the graph height field (cy). + * @param sDpiInputId The ID of the graph DPI field. + */ +function graphwizSetDefaultSizeValues(sWidthSrcId, sWidthInputId, sHeightInputId, sDpiInputId) +{ + var cx = getUnscaledElementWidthById(sWidthSrcId); + var cDotsPerInch = getDeviceXDotsPerInch(); + + if (cx) + { + setInputFieldValue(sWidthInputId, cx); + setInputFieldValue(sHeightInputId, Math.round(cx * 5 / 16)); /* See wuimain.py. */ + } + + if (cDotsPerInch) + { + setInputFieldValue(sDpiInputId, cDotsPerInch); + } + + return false; +} + diff --git a/src/VBox/ValidationKit/testmanager/htdocs/js/vcsrevisions.js b/src/VBox/ValidationKit/testmanager/htdocs/js/vcsrevisions.js new file mode 100644 index 00000000..c82c3b57 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/htdocs/js/vcsrevisions.js @@ -0,0 +1,237 @@ +/* $Id: vcsrevisions.js $ */ +/** @file + * Common JavaScript functions + */ + +/* + * Copyright (C) 2012-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/** + * @internal. + */ +function vcsRevisionFormatDate(tsDate) +{ + /*return tsDate.toLocaleDateString();*/ + return tsDate.toISOString().split('T')[0]; +} + +/** + * @internal. + */ +function vcsRevisionFormatTime(tsDate) +{ + return formatTimeHHMM(tsDate, true /*fNbsp*/); +} + +/** + * Called 'onclick' for the link/button used to show the detailed VCS + * revisions. + * @internal. + */ +function vcsRevisionShowDetails(oElmSource) +{ + document.getElementById('vcsrevisions-detailed').style.display = 'block'; + document.getElementById('vcsrevisions-brief').style.display = 'none'; + oElmSource.style.display = 'none'; + return false; +} + +/** + * Called when we've got the revision data. + * @internal + */ +function vcsRevisionsRender(sTestMgr, oElmDst, sBugTracker, oRestReq, sUrl) +{ + console.log('vcsRevisionsRender: status=' + oRestReq.status + ' readyState=' + oRestReq.readyState + ' url=' + sUrl); + if (oRestReq.readyState != oRestReq.DONE) + { + oElmDst.innerHTML = '<p>' + oRestReq.readyState + '</p>'; + return true; + } + + + /* + * Check the result and translate it to a javascript object (oResp). + */ + var oResp = null; + var sHtml; + if (oRestReq.status != 200) + { + /** @todo figure why this doesn't work (sPath to something random). */ + var sMsg = oRestReq.getResponseHeader('tm-error-message'); + console.log('vcsRevisionsRender: status=' + oRestReq.status + ' readyState=' + oRestReq.readyState + ' url=' + sUrl + ' msg=' + sMsg); + sHtml = '<p>error: status=' + oRestReq.status + 'readyState=' + oRestReq.readyState + ' url=' + sUrl; + if (sMsg) + sHtml += ' msg=' + sMsg; + sHtml += '</p>'; + } + else + { + try + { + oResp = JSON.parse(oRestReq.responseText); + } + catch (oEx) + { + console.log('JSON.parse threw: ' + oEx.toString()); + console.log(oRestReq.responseText); + sHtml = '<p>error: JSON.parse threw: ' + oEx.toString() + '</p>'; + } + } + + /* + * Do the rendering. + */ + if (oResp) + { + if (oResp.cCommits == 0) + { + sHtml = '<p>None.</p>'; + } + else + { + var aoCommits = oResp.aoCommits; + var cCommits = oResp.aoCommits.length; + var i; + + sHtml = ''; + /*sHtml = '<a href="#" onclick="return vcsRevisionShowDetails(this);" class="vcsrevisions-show-details">Show full VCS details...</a>\n';*/ + /*sHtml = '<button onclick="vcsRevisionShowDetails(this);" class="vcsrevisions-show-details">Show full VCS details...</button>\n';*/ + + /* Brief view (the default): */ + sHtml += '<p id="vcsrevisions-brief">'; + for (i = 0; i < cCommits; i++) + { + var oCommit = aoCommits[i]; + var sUrl = oResp.sTracChangesetUrlFmt.replace('%(sRepository)s', oCommit.sRepository).replace('%(iRevision)s', oCommit.iRevision.toString()); + var sTitle = oCommit.sAuthor + ': ' + oCommit.sMessage; + sHtml += ' <a href="' + escapeElem(sUrl) + '" title="' + escapeElem(sTitle) + '">r' + oCommit.iRevision + '</a> \n'; + } + sHtml += '</p>'; + sHtml += '<a href="#" onclick="return vcsRevisionShowDetails(this);" class="vcsrevisions-show-details-bottom">Show full VCS details...</a>\n'; + + /* Details view: */ + sHtml += '<div id="vcsrevisions-detailed" style="display:none;">\n'; + var iCurDay = null; + if (0) + { + /* Changelog variant: */ + for (i = 0; i < cCommits; i++) + { + var oCommit = aoCommits[i]; + var tsCreated = parseIsoTimestamp(oCommit.tsCreated); + var sUrl = oResp.sTracChangesetUrlFmt.replace('%(sRepository)s', oCommit.sRepository).replace('%(iRevision)s', oCommit.iRevision.toString()); + var iCommitDay = Math.floor((tsCreated.getTime() + tsCreated.getTimezoneOffset()) / (24 * 60 * 60 * 1000)); + if (iCurDay === null || iCurDay != iCommitDay) + { + if (iCurDay !== null) + sHtml += ' </dl>\n'; + iCurDay = iCommitDay; + sHtml += ' <h3>' + vcsRevisionFormatDate(tsCreated) + ' ' + g_kasDaysOfTheWeek[tsCreated.getDay()] + '</h3>\n'; + sHtml += ' <dl>\n'; + } + + sHtml += ' <dt id="r' + oCommit.iRevision + '">'; + sHtml += '<a href="' + oResp.sTracChangesetUrlFmt.replace('%(iRevision)s', oCommit.iRevision.toString()) + '">'; + /*sHtml += '<span class="vcsrevisions-time">' + escapeElem(vcsRevisionFormatTime(tsCreated)) + '</span>' + sHtml += ' Changeset <span class="vcsrevisions-rev">r' + oCommit.iRevision + '</span>'; + sHtml += ' by <span class="vcsrevisions-author">' + escapeElem(oCommit.sAuthor) + '</span>'; */ + sHtml += '<span class="vcsrevisions-time">' + escapeElem(vcsRevisionFormatTime(tsCreated)) + '</span>'; + sHtml += ' - <span class="vcsrevisions-rev">r' + oCommit.iRevision + '</span>'; + sHtml += ' - <span class="vcsrevisions-author">' + escapeElem(oCommit.sAuthor) + '</span>'; + sHtml += '</a></dt>\n'; + sHtml += ' <dd>' + escapeElem(oCommit.sMessage) + '</dd>\n'; + } + + if (iCurDay !== null) + sHtml += ' </dl>\n'; + } + else + { /* TABLE variant: */ + sHtml += '<table class="vcsrevisions-table">'; + var iAlt = 0; + for (i = 0; i < cCommits; i++) + { + var oCommit = aoCommits[i]; + var tsCreated = parseIsoTimestamp(oCommit.tsCreated); + var sUrl = oResp.sTracChangesetUrlFmt.replace('%(sRepository)s', oCommit.sRepository).replace('%(iRevision)s', oCommit.iRevision.toString()); + var iCommitDay = Math.floor((tsCreated.getTime() + tsCreated.getTimezoneOffset()) / (24 * 60 * 60 * 1000)); + if (iCurDay === null || iCurDay != iCommitDay) + { + iCurDay = iCommitDay; + sHtml += '<tr id="r' + oCommit.iRevision + '"><td colspan="4" class="vcsrevisions-tab-date">'; + sHtml += vcsRevisionFormatDate(tsCreated) + ' ' + g_kasDaysOfTheWeek[tsCreated.getDay()]; + sHtml += '</td></tr>\n'; + sHtml += '<tr>'; + iAlt = 0; + } + else + sHtml += '<tr id="r' + oCommit.iRevision + '">'; + var sAltCls = ''; + var sAltClsStmt = ''; + iAlt += 1; + if (iAlt & 1) + { + sAltCls = ' alt'; + sAltClsStmt = ' class="alt"'; + } + sHtml += '<td class="vcsrevisions-tab-time'+sAltCls+'"><a href="' + sUrl + '">' + + escapeElem(vcsRevisionFormatTime(tsCreated)) + '</a></td>'; + sHtml += '<td'+sAltClsStmt+'><a href="' + sUrl + '" class="vcsrevisions-rev' + sAltCls + '">r' + + oCommit.iRevision + '</a></td>'; + sHtml += '<td'+sAltClsStmt+'><a href="' + sUrl + '" class="vcsrevisions-author' + sAltCls + '">' + + escapeElem(oCommit.sAuthor) + '<a></td>'; + sHtml += '<td'+sAltClsStmt+'>' + escapeElem(oCommit.sMessage) + '</td></tr>\n'; + } + sHtml += '</table>\n'; + } + sHtml += '</div>\n'; + } + } + + oElmDst.innerHTML = sHtml; +} + +/** Called by the xtracker bugdetails page. */ +function VcsRevisionsLoad(sTestMgr, oElmDst, sBugTracker, lBugNo) +{ + oElmDst.innerHTML = '<p>Loading VCS revisions...</p>'; + + var sUrl = sTestMgr + 'rest.py?sPath=vcs/bugreferences/' + sBugTracker + '/' + lBugNo; + var oRestReq = new XMLHttpRequest(); + oRestReq.onreadystatechange = function() { vcsRevisionsRender(sTestMgr, oElmDst, sBugTracker, this, sUrl); } + oRestReq.open('GET', sUrl); + oRestReq.withCredentials = true; + /*oRestReq.setRequestHeader('Content-type', 'application/json'); - Causes CORS trouble. */ + oRestReq.send(); +} + diff --git a/src/VBox/ValidationKit/testmanager/misc/Makefile.kmk b/src/VBox/ValidationKit/testmanager/misc/Makefile.kmk new file mode 100644 index 00000000..1de2d1dc --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/misc/Makefile.kmk @@ -0,0 +1,46 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(wildcard $(PATH_SUB_CURRENT)/*.py) + +$(evalcall def_vbox_validationkit_process_python_sources) +$(evalcall def_vbox_validationkit_process_js_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/testmanager/misc/htpasswd-logout b/src/VBox/ValidationKit/testmanager/misc/htpasswd-logout new file mode 100644 index 00000000..8a36998b --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/misc/htpasswd-logout @@ -0,0 +1 @@ +logout:$apr1$OqiMc/Uv$XylAjnIPla7gb57UMW0TK. diff --git a/src/VBox/ValidationKit/testmanager/misc/htpasswd-sample b/src/VBox/ValidationKit/testmanager/misc/htpasswd-sample new file mode 100644 index 00000000..6b6c1b33 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/misc/htpasswd-sample @@ -0,0 +1,2 @@ +admin:ZXHvyrLs.vCmw +test:ClO2uu6/D7jDg diff --git a/src/VBox/ValidationKit/testmanager/readme.txt b/src/VBox/ValidationKit/testmanager/readme.txt new file mode 100644 index 00000000..7211c7b6 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/readme.txt @@ -0,0 +1,125 @@ +$Id: readme.txt $ + +Directory descriptions: + ./ The Test Manager. + ./batch/ Batch scripts to be run via cron. + ./cgi/ CGI scripts (we'll use standard CGI at first). + ./core/ The core Test Manager logic (model). + ./htdocs/ Files to be served directly by the web server. + ./htdocs/css/ Style sheets. + ./htdocs/images/ Graphics. + ./webui/ The Web User Interface (WUI) bits. (Not sure if we will + do model-view-controller stuff, though. Time will show.) + +I. Running a Test Manager instance with Docker: + + - This way should be preferred to get a local Test Manager instance running + and is NOT meant for production use! + + - Install docker-ce and docker-compose on your Linux host (not tested on + Windows yet). Your user must be able to run the Docker CLI (see Docker documentation). + + - Type "kmk" to get the containers built, "kmk start|stop" to start/stop them + respectively. To start over, use "kmk clean". For having a peek into the container + logs, use "kmk logs". + + To administrate / develop the database, an Adminer instance is running at + http://localhost:8080 + + To access the actual Test Manager instance, go to http://localhost:8080/testmanager/ + + - There are two ways of doing development with this setup: + + a. The Test Manager source is stored inside a separate data volume called + "docker_vbox-testmgr-web". The source will be checked out automatically on + container initialization. Development then can take part within that data + container. The initialization script will automatically pull the sources + from the public OSE tree, so make sure this is what you want! + + b. Edit the (hidden) .env file in this directory and change VBOX_TESTMGR_DATA + to point to your checked out VBox root, e.g. VBOX_TESTMGR_DATA=/path/to/VBox/trunk + + +II. Steps for manually setting up a local Test Manager instance for development: + + - Install apache, postgresql, python, psycopg2 (python) and pylint. + + - Create the database by executing 'kmk load-testmanager-db' in + the './db/' subdirectory. The default psql parameters there + requies pg_hba.conf to specify 'trust' instead of 'peer' as the + authentication method for local connections. + + - Use ./db/partial-db-dump.py on the production system to extract a + partial database dump (last 14 days). + + - Use ./db/partial-db-dump.py with the --load-dump-into-database + parameter on the development box to load the dump. + + - Configure apache using the ./apache-template-2.4.conf (see top of + file for details), for example: + + Define TestManagerRootDir "/mnt/scratch/vbox/svn/trunk/src/VBox/ValidationKit/testmanager" + Define VBoxBuildOutputDir "/tmp" + Include "${TestManagerRootDir}/apache-template-2.4.conf" + + Make sure to enable cgi (a2enmod cgi && systemctl restart apache2). + + - Default htpasswd file has users a user 'admin' with password 'admin' and a + 'test' user with password 'test'. This isn't going to get you far if + you've loaded something from the production server as there is typically + no 'admin' user in the 'Users' table there. So, you will need to add your + user and a throwaway password to 'misc/htpasswd-sample' using the htpasswd + utility. + + - Try http://localhost/testmanager/ in a browser and see if it works. + + +III. OS X version of the above manual setup using MacPorts: + + - sudo ports install apache2 postgresql12 postgresql12-server py38-psycopg2 py38-pylint + sudo port select --set python python38 + sudo port select --set python3 python38 + sudo port select --set pylint pylint38 + + Note! Replace the python 38 with the most recent one you want to use. Same + for the 12 in relation to postgresql. + + - Do what the postgresql12-server notes says, at the time of writing: + sudo mkdir -p /opt/local/var/db/postgresql12/defaultdb + sudo chown postgres:postgres /opt/local/var/db/postgresql12/defaultdb + sudo su postgres -c 'cd /opt/local/var/db/postgresql12 && /opt/local/lib/postgresql12/bin/initdb -D /opt/local/var/db/postgresql12/defaultdb' + sudo port load postgresql12-server + + Note! The postgresql12-server's config is 'trust' already, so no need to + edit /opt/local/var/db/postgresql12/defaultdb/pg_hba.conf there. If + you use a different version, please check it. + + - kmk load-testmanager-db + + - Creating and loading a partial database dump as detailed above. + + - Configure apache: + - sudo joe /opt/local/etc/apache2/httpd.conf: + - Uncomment the line "LoadModule cgi_module...". + - At the end of the file add (edit paths): + Define TestManagerRootDir "/Users/bird/coding/vbox/svn/trunk/src/VBox/ValidationKit/testmanager" + Define VBoxBuildOutputDir "/tmp" + Include "${TestManagerRootDir}/apache-template-2.4.conf" + - Test the config: + /opt/local/sbin/apachectl -t + - So apache will find the right python add the following to + /opt/local/sbin/envvars: + PATH=/opt/local/bin:/opt/local/sbin:$PATH + export PATH + - Load the apache service (or reload it): + sudo port load apache2 + - Give apache access to read everything under TestManagerRootDir: + chmod -R a:rX /Users/bird/coding/vbox/svn/trunk/src/VBox/ValidationKit/testmanager + MYDIR=/Users/bird/coding/vbox/svn/trunk/src/VBox/ValidationKit; while [ '!' "$MYDIR" '<' "$HOME" ]; do \ + chmod a+x "$MYDIR"; MYDIR=`dirname $MYDIR`; done + + - Fix htpasswd file as detailed above and try the url (also above). + + +N.B. For developing tests (../tests/), setting up a local test manager will be + a complete waste of time. Just run the test drivers locally. diff --git a/src/VBox/ValidationKit/testmanager/selftest/st1-load.pgsql b/src/VBox/ValidationKit/testmanager/selftest/st1-load.pgsql new file mode 100644 index 00000000..a5c2668f --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/selftest/st1-load.pgsql @@ -0,0 +1,164 @@ +-- $Id: st1-load.pgsql $ +--- @file +-- VBox Test Manager - Self Test #1 Database Load File. +-- + +-- +-- Copyright (C) 2012-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + + +\set ON_ERROR_STOP 1 +\connect testmanager; + +BEGIN WORK; + + +INSERT INTO Users (uid, sUsername, sEmail, sFullName, sLoginName) + VALUES (1112223331, 'st1', 'st1@example.org', 'self test #1', 'st1'); + +INSERT INTO TestCases (uidAuthor, sName, fEnabled, cSecTimeout, sBaseCmd, sTestSuiteZips) + VALUES (1112223331, 'st1-test1', TRUE, 3600, 'validationkit/tests/selftests/tdSelfTest1.py', '@DOWNLOAD_BASE_URL@/VBoxValidationKit.zip'); + +INSERT INTO TestCaseArgs (idTestCase, uidAuthor, sArgs) + VALUES ((SELECT idTestCase FROM TestCases WHERE sName = 'st1-test1'), 1112223331, ''); + +INSERT INTO TestGroups (uidAuthor, sName) + VALUES (1112223331, 'st1-testgroup'); + +INSERT INTO TestGroupMembers (idTestGroup, idTestCase, uidAuthor) + VALUES ((SELECT idTestGroup FROM TestGroups WHERE sName = 'st1-testgroup'), + (SELECT idTestCase FROM TestCases WHERE sName = 'st1-test1'), + 1112223331); + +INSERT INTO BuildSources (uidAuthor, sName, sProduct, sBranch, asTypes, asOsArches) + VALUES (1112223331, 'st1-src', 'st1', 'trunk', + ARRAY['release', 'strict'], + ARRAY['win.x86', 'linux.noarch', 'solaris.amd64', 'os-agnostic.sparc64', 'os-agnostic.noarch']); + +INSERT INTO BuildCategories (sProduct, sBranch, sType, asOsArches) + VALUES ('st1', 'trunk', 'release', ARRAY['os-agnostic.noarch']); + +INSERT INTO Builds (uidAuthor, idBuildCategory, iRevision, sVersion, sBinaries) + VALUES (1112223331, + (SELECT idBuildCategory FROM BuildCategories WHERE sProduct = 'st1' AND sBranch = 'trunk'), + 1234, '1.0', ''); + +INSERT INTO SchedGroups (uidAuthor, sName, sDescription, fEnabled, idBuildSrc) + VALUES (1112223331, 'st1-group', 'test test #1', TRUE, + (SELECT idBuildSrc FROM BuildSources WHERE sName = 'st1-src') ); + +INSERT INTO SchedGroupMembers (idSchedGroup, idTestGroup, uidAuthor) + VALUES ((SELECT idSchedGroup FROM SchedGroups WHERE sName = 'st1-group'), + (SELECT idTestGroup FROM TestGroups WHERE sName = 'st1-testgroup'), + 1112223331); + + +-- The second test + +INSERT INTO TestCases (uidAuthor, sName, fEnabled, cSecTimeout, sBaseCmd, sTestSuiteZips) + VALUES (1112223331, 'st1-test2', TRUE, 3600, 'validationkit/tests/selftests/tdSelfTest2.py', '@DOWNLOAD_BASE_URL@/VBoxValidationKit.zip'); + +INSERT INTO TestCaseArgs (idTestCase, uidAuthor, sArgs) + VALUES ((SELECT idTestCase FROM TestCases WHERE sName = 'st1-test2'), 1112223331, ''); + +INSERT INTO TestGroupMembers (idTestGroup, idTestCase, uidAuthor) + VALUES ((SELECT idTestGroup FROM TestGroups WHERE sName = 'st1-testgroup'), + (SELECT idTestCase FROM TestCases WHERE sName = 'st1-test2'), + 1112223331); + +-- The third test + +INSERT INTO TestCases (uidAuthor, sName, fEnabled, cSecTimeout, sBaseCmd, sTestSuiteZips) + VALUES (1112223331, 'st1-test3', TRUE, 3600, 'validationkit/tests/selftests/tdSelfTest3.py', '@DOWNLOAD_BASE_URL@/VBoxValidationKit.zip'); + +INSERT INTO TestCaseArgs (idTestCase, uidAuthor, sArgs) + VALUES ((SELECT idTestCase FROM TestCases WHERE sName = 'st1-test3'), 1112223331, ''); + +INSERT INTO TestGroupMembers (idTestGroup, idTestCase, uidAuthor) + VALUES ((SELECT idTestGroup FROM TestGroups WHERE sName = 'st1-testgroup'), + (SELECT idTestCase FROM TestCases WHERE sName = 'st1-test3'), + 1112223331); + +-- The fourth thru eight tests + +INSERT INTO TestCases (uidAuthor, sName, fEnabled, cSecTimeout, sBaseCmd, sTestSuiteZips) + VALUES (1112223331, 'st1-test4-neg', TRUE, 3600, 'validationkit/tests/selftests/tdSelfTest4.py --test immediate-sub-tests', + '@DOWNLOAD_BASE_URL@/VBoxValidationKit.zip'); +INSERT INTO TestCaseArgs (idTestCase, uidAuthor, sArgs) + VALUES ((SELECT idTestCase FROM TestCases WHERE sName = 'st1-test4-neg'), 1112223331, ''); +INSERT INTO TestGroupMembers (idTestGroup, idTestCase, uidAuthor) + VALUES ((SELECT idTestGroup FROM TestGroups WHERE sName = 'st1-testgroup'), + (SELECT idTestCase FROM TestCases WHERE sName = 'st1-test4-neg'), + 1112223331); + +INSERT INTO TestCases (uidAuthor, sName, fEnabled, cSecTimeout, sBaseCmd, sTestSuiteZips) + VALUES (1112223331, 'st1-test5-neg', TRUE, 3600, 'validationkit/tests/selftests/tdSelfTest4.py --test total-sub-tests', + '@DOWNLOAD_BASE_URL@/VBoxValidationKit.zip'); +INSERT INTO TestCaseArgs (idTestCase, uidAuthor, sArgs) + VALUES ((SELECT idTestCase FROM TestCases WHERE sName = 'st1-test5-neg'), 1112223331, ''); +INSERT INTO TestGroupMembers (idTestGroup, idTestCase, uidAuthor) + VALUES ((SELECT idTestGroup FROM TestGroups WHERE sName = 'st1-testgroup'), + (SELECT idTestCase FROM TestCases WHERE sName = 'st1-test5-neg'), + 1112223331); + +INSERT INTO TestCases (uidAuthor, sName, fEnabled, cSecTimeout, sBaseCmd, sTestSuiteZips) + VALUES (1112223331, 'st1-test6-neg', TRUE, 3600, 'validationkit/tests/selftests/tdSelfTest4.py --test immediate-values', + '@DOWNLOAD_BASE_URL@/VBoxValidationKit.zip'); +INSERT INTO TestCaseArgs (idTestCase, uidAuthor, sArgs) + VALUES ((SELECT idTestCase FROM TestCases WHERE sName = 'st1-test6-neg'), 1112223331, ''); +INSERT INTO TestGroupMembers (idTestGroup, idTestCase, uidAuthor) + VALUES ((SELECT idTestGroup FROM TestGroups WHERE sName = 'st1-testgroup'), + (SELECT idTestCase FROM TestCases WHERE sName = 'st1-test6-neg'), + 1112223331); + +INSERT INTO TestCases (uidAuthor, sName, fEnabled, cSecTimeout, sBaseCmd, sTestSuiteZips) + VALUES (1112223331, 'st1-test7-neg', TRUE, 3600, 'validationkit/tests/selftests/tdSelfTest4.py --test total-values', + '@DOWNLOAD_BASE_URL@/VBoxValidationKit.zip'); +INSERT INTO TestCaseArgs (idTestCase, uidAuthor, sArgs) + VALUES ((SELECT idTestCase FROM TestCases WHERE sName = 'st1-test7-neg'), 1112223331, ''); +INSERT INTO TestGroupMembers (idTestGroup, idTestCase, uidAuthor) + VALUES ((SELECT idTestGroup FROM TestGroups WHERE sName = 'st1-testgroup'), + (SELECT idTestCase FROM TestCases WHERE sName = 'st1-test7-neg'), + 1112223331); + +INSERT INTO TestCases (uidAuthor, sName, fEnabled, cSecTimeout, sBaseCmd, sTestSuiteZips) + VALUES (1112223331, 'st1-test8-neg', TRUE, 3600, 'validationkit/tests/selftests/tdSelfTest4.py --test immediate-messages', + '@DOWNLOAD_BASE_URL@/VBoxValidationKit.zip'); +INSERT INTO TestCaseArgs (idTestCase, uidAuthor, sArgs) + VALUES ((SELECT idTestCase FROM TestCases WHERE sName = 'st1-test8-neg'), 1112223331, ''); +INSERT INTO TestGroupMembers (idTestGroup, idTestCase, uidAuthor) + VALUES ((SELECT idTestGroup FROM TestGroups WHERE sName = 'st1-testgroup'), + (SELECT idTestCase FROM TestCases WHERE sName = 'st1-test8-neg'), + 1112223331); + +COMMIT WORK; + diff --git a/src/VBox/ValidationKit/testmanager/selftest/st1-unload.pgsql b/src/VBox/ValidationKit/testmanager/selftest/st1-unload.pgsql new file mode 100644 index 00000000..1f1518aa --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/selftest/st1-unload.pgsql @@ -0,0 +1,87 @@ +-- $Id: st1-unload.pgsql $ +--- @file +-- VBox Test Manager - Self Test #1 Database Unload File. +-- + +-- +-- Copyright (C) 2012-2022 Oracle and/or its affiliates. +-- +-- This file is part of VirtualBox base platform packages, as +-- available from https://www.virtualbox.org. +-- +-- This program is free software; you can redistribute it and/or +-- modify it under the terms of the GNU General Public License +-- as published by the Free Software Foundation, in version 3 of the +-- License. +-- +-- This program is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, see <https://www.gnu.org/licenses>. +-- +-- The contents of this file may alternatively be used under the terms +-- of the Common Development and Distribution License Version 1.0 +-- (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +-- in the VirtualBox distribution, in which case the provisions of the +-- CDDL are applicable instead of those of the GPL. +-- +-- You may elect to license modified versions of this file under the +-- terms and conditions of either the GPL or the CDDL or both. +-- +-- SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +-- + + + +\set ON_ERROR_STOP 1 +\connect testmanager; + +BEGIN WORK; + +DELETE FROM TestBoxStatuses; +DELETE FROM SchedQueues; + +DELETE FROM SchedGroupMembers WHERE uidAuthor = 1112223331; +UPDATE TestBoxes SET idSchedGroup = 1 WHERE idSchedGroup IN ( SELECT idSchedGroup FROM SchedGroups WHERE uidAuthor = 1112223331 ); +DELETE FROM SchedGroups WHERE uidAuthor = 1112223331 OR sName = 'st1-group'; + +UPDATE TestSets SET idTestResult = NULL + WHERE idTestCase IN ( SELECT idTestCase FROM TestCases WHERE uidAuthor = 1112223331 ); + +DELETE FROM TestResultValues + WHERE idTestResult IN ( SELECT idTestResult FROM TestResults + WHERE idTestSet IN ( SELECT idTestSet FROM TestSets + WHERE idTestCase IN ( SELECT idTestCase FROM TestCases + WHERE uidAuthor = 1112223331 ) ) ); +DELETE FROM TestResultFiles + WHERE idTestResult IN ( SELECT idTestResult FROM TestResults + WHERE idTestSet IN ( SELECT idTestSet FROM TestSets + WHERE idTestCase IN ( SELECT idTestCase FROM TestCases + WHERE uidAuthor = 1112223331 ) ) ); +DELETE FROM TestResultMsgs + WHERE idTestResult IN ( SELECT idTestResult FROM TestResults + WHERE idTestSet IN ( SELECT idTestSet FROM TestSets + WHERE idTestCase IN ( SELECT idTestCase FROM TestCases + WHERE uidAuthor = 1112223331 ) ) ); +DELETE FROM TestResults + WHERE idTestSet IN ( SELECT idTestSet FROM TestSets + WHERE idTestCase IN ( SELECT idTestCase FROM TestCases WHERE uidAuthor = 1112223331 ) ); +DELETE FROM TestSets + WHERE idTestCase IN ( SELECT idTestCase FROM TestCases WHERE uidAuthor = 1112223331 ); + +DELETE FROM TestCases WHERE uidAuthor = 1112223331; +DELETE FROM TestCaseArgs WHERE uidAuthor = 1112223331; +DELETE FROM TestGroups WHERE uidAuthor = 1112223331 OR sName = 'st1-testgroup'; +DELETE FROM TestGroupMembers WHERE uidAuthor = 1112223331; + +DELETE FROM BuildSources WHERE uidAuthor = 1112223331; +DELETE FROM Builds WHERE uidAuthor = 1112223331; +DELETE FROM BuildCategories WHERE sProduct = 'st1'; + +DELETE FROM Users WHERE uid = 1112223331; + +COMMIT WORK; + diff --git a/src/VBox/ValidationKit/testmanager/webui/Makefile.kmk b/src/VBox/ValidationKit/testmanager/webui/Makefile.kmk new file mode 100644 index 00000000..b5a25c16 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/Makefile.kmk @@ -0,0 +1,47 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(wildcard $(PATH_SUB_CURRENT)/*.py) +VBOX_VALIDATIONKIT_PYUNITTEST_EXCLUDE += $(PATH_SUB_CURRENT)/wuihlpgraphmatplotlib.py + +$(evalcall def_vbox_validationkit_process_python_sources) +$(evalcall def_vbox_validationkit_process_js_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/testmanager/webui/__init__.py b/src/VBox/ValidationKit/testmanager/webui/__init__.py new file mode 100644 index 00000000..5117dd53 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/__init__.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# $Id: __init__.py $ + +""" +TestBox Script - WUI Presentation. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + diff --git a/src/VBox/ValidationKit/testmanager/webui/template-details.html b/src/VBox/ValidationKit/testmanager/webui/template-details.html new file mode 100644 index 00000000..e7e16c0f --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/template-details.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html lang="en"> + <head> + <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> + <meta http-equiv="content-language" content="en" /> + <meta name="language" content="en" /> + <link href="htdocs/images/tmfavicon.ico" rel="shortcut icon" type="image/x-icon" /> + <link href="htdocs/images/tmfavicon.ico" rel="icon" type="image/x-icon" /> + <link href="htdocs/css/common.css" rel="stylesheet" type="text/css" media="screen" /> + <link href="htdocs/css/tooltip.css" rel="stylesheet" type="text/css" media="screen" /> + <link href="htdocs/css/details.css" rel="stylesheet" type="text/css" media="screen" /> + <script type="text/javascript" src="htdocs/js/common.js"></script> + <title>@@PAGE_TITLE@@</title> + </head> + + <body> + <div id="wrap"> + <div id="head-wrap"> + <div id="logo"> + <img alt ="VirtualBox" src="htdocs/images/VirtualBox.svg"> + </div> + <div id="header"> + <h1>@@PAGE_TITLE@@</h1> + </div> + <div id="top-menu" class="tm-top-menu-wo-side"> + <ul> + @@TOP_MENU_ITEMS@@ + </ul> + </div> + <div id="login"> + <p><small> + Logged in as <b>@@USER_NAME@@</b>@@LOG_OUT@@ + </small></p> + </div> + </div> + + <div id="main"> + @@PAGE_BODY@@ + + @@DEBUG@@ + </div> + </div> + </body> +</html> + diff --git a/src/VBox/ValidationKit/testmanager/webui/template-graphwiz.html b/src/VBox/ValidationKit/testmanager/webui/template-graphwiz.html new file mode 100644 index 00000000..4e1dc0c8 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/template-graphwiz.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html lang="en"> + <head> + <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> + <meta http-equiv="content-language" content="en" /> + <meta name="language" content="en" /> + <link href="htdocs/images/tmfavicon.ico" rel="shortcut icon" type="image/x-icon" /> + <link href="htdocs/images/tmfavicon.ico" rel="icon" type="image/x-icon" /> + <link href="htdocs/css/common.css" rel="stylesheet" type="text/css" media="screen" /> + <link href="htdocs/css/tooltip.css" rel="stylesheet" type="text/css" media="screen" /> + <link href="htdocs/css/graphwiz.css" rel="stylesheet" type="text/css" media="screen" /> + <script type="text/javascript" src="htdocs/js/common.js"></script> + <script type="text/javascript" src="htdocs/js/graphwiz.js"></script> + <title>@@PAGE_TITLE@@</title> + </head> + + <body> + <div id="wrap"> + <div id="head-wrap"> + <div id="logo"> + <img alt ="VirtualBox" src="htdocs/images/VirtualBox.svg"> + </div> + <div id="header"> + <h1>@@PAGE_TITLE@@</h1> + </div> + <div id="top-menu" class="tm-top-menu-wo-side"> + <ul> + @@TOP_MENU_ITEMS@@ + </ul> + </div> + <div id="login"> + <p><small> + Logged in as <b>@@USER_NAME@@</b>@@LOG_OUT@@ + </small></p> + </div> + </div> + + <div id="main"> + @@PAGE_BODY@@ + + @@DEBUG@@ + </div> + </div> + </body> +</html> + diff --git a/src/VBox/ValidationKit/testmanager/webui/template-tooltip.html b/src/VBox/ValidationKit/testmanager/webui/template-tooltip.html new file mode 100644 index 00000000..7aa95d71 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/template-tooltip.html @@ -0,0 +1,20 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html lang="en"> +<head> + <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> + <meta http-equiv="content-language" content="en" /> + <meta name="language" content="en" /> + <link href="htdocs/css/common.css" rel="stylesheet" type="text/css" media="screen" /> + <link href="htdocs/css/tooltip.css" rel="stylesheet" type="text/css" media="screen" /> + <title>@@PAGE_TITLE@@</title> +</head> + +<body scroll="no"> +<div id="tooltip" class="tooltip-main"> +<div id="tooltip-inner" class="tooltip-inner"> +@@PAGE_BODY@@ +</div> +</div> +</body> +</html> + diff --git a/src/VBox/ValidationKit/testmanager/webui/template.html b/src/VBox/ValidationKit/testmanager/webui/template.html new file mode 100644 index 00000000..ea28c24e --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/template.html @@ -0,0 +1,65 @@ +<!DOCTYPE HTML> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> + <meta http-equiv="content-language" content="en" /> + <meta name="language" content="en" /> + <link href="htdocs/images/tmfavicon.ico" rel="shortcut icon" /> + <link href="htdocs/images/tmfavicon.ico" rel="icon" type="image/x-icon" /> + <link href="htdocs/css/common.css" rel="stylesheet" type="text/css" media="screen" /> + <link href="htdocs/css/tooltip.css" rel="stylesheet" type="text/css" media="screen" /> + <script type="text/javascript" src="htdocs/js/common.js"></script> + <title>@@PAGE_TITLE@@</title> + </head> + + <body> + <div id="wrap"> + <div id="head-wrap"> + <div id="logo"> + <img alt ="VirtualBox" src="htdocs/images/VirtualBox.svg"> + </div> + <div id="header"> + <h1>@@PAGE_TITLE@@</h1> + </div> + <div id="login"> + <p><small> + Logged in as <b>@@USER_NAME@@</b>@@LOG_OUT@@ + </small></p> + </div> + <div id="top-menu"> + <ul> + @@TOP_MENU_ITEMS@@ + </ul> + </div> + </div> + + <div id="side-menu-wrap"> + <div id="side-menu"> + <div id="side-menu-body"> + <form id="side-menu-form" @@SIDE_MENU_FORM_ATTRS@@> + <ul> + @@SIDE_MENU_ITEMS@@ + </ul> + @@SIDE_FILTER_CONTROL@@ + </form> + </div> + <!-- justify-content: space-between --> + <div id="side-footer"> + <p> + VBox Test Manager<br/>@@TESTMANAGER_VERSION@@r@@TESTMANAGER_REVISION@@ + </p> + <p>Copyright © 2012-2020 Oracle Corporation</p> + </div> + </div> + </div> + + <div id="main"> + @@PAGE_BODY@@ + + @@DEBUG@@ + </div> + </div> + </body> +</html> + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuiadmin.py b/src/VBox/ValidationKit/testmanager/webui/wuiadmin.py new file mode 100755 index 00000000..a9a7d67a --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuiadmin.py @@ -0,0 +1,1270 @@ +# -*- coding: utf-8 -*- +# $Id: wuiadmin.py $ + +""" +Test Manager Core - WUI - Admin Main page. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard python imports. +import cgitb; +import sys; + +# Validation Kit imports. +from common import utils, webutils; +from testmanager import config; +from testmanager.webui.wuibase import WuiDispatcherBase, WuiException + + +class WuiAdmin(WuiDispatcherBase): + """ + WUI Admin main page. + """ + + ## The name of the script. + ksScriptName = 'admin.py' + + ## Number of days back. + ksParamDaysBack = 'cDaysBack'; + + ## @name Actions + ## @{ + ksActionSystemLogList = 'SystemLogList' + ksActionSystemChangelogList = 'SystemChangelogList' + ksActionSystemDbDump = 'SystemDbDump' + ksActionSystemDbDumpDownload = 'SystemDbDumpDownload' + + ksActionUserList = 'UserList' + ksActionUserAdd = 'UserAdd' + ksActionUserAddPost = 'UserAddPost' + ksActionUserEdit = 'UserEdit' + ksActionUserEditPost = 'UserEditPost' + ksActionUserDelPost = 'UserDelPost' + ksActionUserDetails = 'UserDetails' + + ksActionTestBoxList = 'TestBoxList' + ksActionTestBoxListPost = 'TestBoxListPost' + ksActionTestBoxAdd = 'TestBoxAdd' + ksActionTestBoxAddPost = 'TestBoxAddPost' + ksActionTestBoxEdit = 'TestBoxEdit' + ksActionTestBoxEditPost = 'TestBoxEditPost' + ksActionTestBoxDetails = 'TestBoxDetails' + ksActionTestBoxRemovePost = 'TestBoxRemove' + ksActionTestBoxesRegenQueues = 'TestBoxesRegenQueues'; + + ksActionTestCaseList = 'TestCaseList' + ksActionTestCaseAdd = 'TestCaseAdd' + ksActionTestCaseAddPost = 'TestCaseAddPost' + ksActionTestCaseClone = 'TestCaseClone' + ksActionTestCaseDetails = 'TestCaseDetails' + ksActionTestCaseEdit = 'TestCaseEdit' + ksActionTestCaseEditPost = 'TestCaseEditPost' + ksActionTestCaseDoRemove = 'TestCaseDoRemove' + + ksActionGlobalRsrcShowAll = 'GlobalRsrcShowAll' + ksActionGlobalRsrcShowAdd = 'GlobalRsrcShowAdd' + ksActionGlobalRsrcShowEdit = 'GlobalRsrcShowEdit' + ksActionGlobalRsrcAdd = 'GlobalRsrcAddPost' + ksActionGlobalRsrcEdit = 'GlobalRsrcEditPost' + ksActionGlobalRsrcDel = 'GlobalRsrcDelPost' + + ksActionBuildList = 'BuildList' + ksActionBuildAdd = 'BuildAdd' + ksActionBuildAddPost = 'BuildAddPost' + ksActionBuildClone = 'BuildClone' + ksActionBuildDetails = 'BuildDetails' + ksActionBuildDoRemove = 'BuildDoRemove' + ksActionBuildEdit = 'BuildEdit' + ksActionBuildEditPost = 'BuildEditPost' + + ksActionBuildBlacklist = 'BuildBlacklist'; + ksActionBuildBlacklistAdd = 'BuildBlacklistAdd'; + ksActionBuildBlacklistAddPost = 'BuildBlacklistAddPost'; + ksActionBuildBlacklistClone = 'BuildBlacklistClone'; + ksActionBuildBlacklistDetails = 'BuildBlacklistDetails'; + ksActionBuildBlacklistDoRemove = 'BuildBlacklistDoRemove'; + ksActionBuildBlacklistEdit = 'BuildBlacklistEdit'; + ksActionBuildBlacklistEditPost = 'BuildBlacklistEditPost'; + + ksActionFailureCategoryList = 'FailureCategoryList'; + ksActionFailureCategoryAdd = 'FailureCategoryAdd'; + ksActionFailureCategoryAddPost = 'FailureCategoryAddPost'; + ksActionFailureCategoryDetails = 'FailureCategoryDetails'; + ksActionFailureCategoryDoRemove = 'FailureCategoryDoRemove'; + ksActionFailureCategoryEdit = 'FailureCategoryEdit'; + ksActionFailureCategoryEditPost = 'FailureCategoryEditPost'; + + ksActionFailureReasonList = 'FailureReasonList' + ksActionFailureReasonAdd = 'FailureReasonAdd' + ksActionFailureReasonAddPost = 'FailureReasonAddPost' + ksActionFailureReasonDetails = 'FailureReasonDetails' + ksActionFailureReasonDoRemove = 'FailureReasonDoRemove' + ksActionFailureReasonEdit = 'FailureReasonEdit' + ksActionFailureReasonEditPost = 'FailureReasonEditPost' + + ksActionBuildSrcList = 'BuildSrcList' + ksActionBuildSrcAdd = 'BuildSrcAdd' + ksActionBuildSrcAddPost = 'BuildSrcAddPost' + ksActionBuildSrcClone = 'BuildSrcClone' + ksActionBuildSrcDetails = 'BuildSrcDetails' + ksActionBuildSrcEdit = 'BuildSrcEdit' + ksActionBuildSrcEditPost = 'BuildSrcEditPost' + ksActionBuildSrcDoRemove = 'BuildSrcDoRemove' + + ksActionBuildCategoryList = 'BuildCategoryList' + ksActionBuildCategoryAdd = 'BuildCategoryAdd' + ksActionBuildCategoryAddPost = 'BuildCategoryAddPost' + ksActionBuildCategoryClone = 'BuildCategoryClone'; + ksActionBuildCategoryDetails = 'BuildCategoryDetails'; + ksActionBuildCategoryDoRemove = 'BuildCategoryDoRemove'; + + ksActionTestGroupList = 'TestGroupList' + ksActionTestGroupAdd = 'TestGroupAdd' + ksActionTestGroupAddPost = 'TestGroupAddPost' + ksActionTestGroupClone = 'TestGroupClone' + ksActionTestGroupDetails = 'TestGroupDetails' + ksActionTestGroupDoRemove = 'TestGroupDoRemove' + ksActionTestGroupEdit = 'TestGroupEdit' + ksActionTestGroupEditPost = 'TestGroupEditPost' + ksActionTestCfgRegenQueues = 'TestCfgRegenQueues' + + ksActionSchedGroupList = 'SchedGroupList' + ksActionSchedGroupAdd = 'SchedGroupAdd'; + ksActionSchedGroupAddPost = 'SchedGroupAddPost'; + ksActionSchedGroupClone = 'SchedGroupClone'; + ksActionSchedGroupDetails = 'SchedGroupDetails'; + ksActionSchedGroupDoRemove = 'SchedGroupDel'; + ksActionSchedGroupEdit = 'SchedGroupEdit'; + ksActionSchedGroupEditPost = 'SchedGroupEditPost'; + ksActionSchedQueueList = 'SchedQueueList'; + ## @} + + def __init__(self, oSrvGlue): # pylint: disable=too-many-locals,too-many-statements + WuiDispatcherBase.__init__(self, oSrvGlue, self.ksScriptName); + self._sTemplate = 'template.html'; + + + # + # System actions. + # + self._dDispatch[self.ksActionSystemChangelogList] = self._actionSystemChangelogList; + self._dDispatch[self.ksActionSystemLogList] = self._actionSystemLogList; + self._dDispatch[self.ksActionSystemDbDump] = self._actionSystemDbDump; + self._dDispatch[self.ksActionSystemDbDumpDownload] = self._actionSystemDbDumpDownload; + + # + # User Account actions. + # + self._dDispatch[self.ksActionUserList] = self._actionUserList; + self._dDispatch[self.ksActionUserAdd] = self._actionUserAdd; + self._dDispatch[self.ksActionUserEdit] = self._actionUserEdit; + self._dDispatch[self.ksActionUserAddPost] = self._actionUserAddPost; + self._dDispatch[self.ksActionUserEditPost] = self._actionUserEditPost; + self._dDispatch[self.ksActionUserDetails] = self._actionUserDetails; + self._dDispatch[self.ksActionUserDelPost] = self._actionUserDelPost; + + # + # TestBox actions. + # + self._dDispatch[self.ksActionTestBoxList] = self._actionTestBoxList; + self._dDispatch[self.ksActionTestBoxListPost] = self._actionTestBoxListPost; + self._dDispatch[self.ksActionTestBoxAdd] = self._actionTestBoxAdd; + self._dDispatch[self.ksActionTestBoxAddPost] = self._actionTestBoxAddPost; + self._dDispatch[self.ksActionTestBoxDetails] = self._actionTestBoxDetails; + self._dDispatch[self.ksActionTestBoxEdit] = self._actionTestBoxEdit; + self._dDispatch[self.ksActionTestBoxEditPost] = self._actionTestBoxEditPost; + self._dDispatch[self.ksActionTestBoxRemovePost] = self._actionTestBoxRemovePost; + self._dDispatch[self.ksActionTestBoxesRegenQueues] = self._actionRegenQueuesCommon; + + # + # Test Case actions. + # + self._dDispatch[self.ksActionTestCaseList] = self._actionTestCaseList; + self._dDispatch[self.ksActionTestCaseAdd] = self._actionTestCaseAdd; + self._dDispatch[self.ksActionTestCaseAddPost] = self._actionTestCaseAddPost; + self._dDispatch[self.ksActionTestCaseClone] = self._actionTestCaseClone; + self._dDispatch[self.ksActionTestCaseDetails] = self._actionTestCaseDetails; + self._dDispatch[self.ksActionTestCaseEdit] = self._actionTestCaseEdit; + self._dDispatch[self.ksActionTestCaseEditPost] = self._actionTestCaseEditPost; + self._dDispatch[self.ksActionTestCaseDoRemove] = self._actionTestCaseDoRemove; + + # + # Global Resource actions + # + self._dDispatch[self.ksActionGlobalRsrcShowAll] = self._actionGlobalRsrcShowAll; + self._dDispatch[self.ksActionGlobalRsrcShowAdd] = self._actionGlobalRsrcShowAdd; + self._dDispatch[self.ksActionGlobalRsrcShowEdit] = self._actionGlobalRsrcShowEdit; + self._dDispatch[self.ksActionGlobalRsrcAdd] = self._actionGlobalRsrcAdd; + self._dDispatch[self.ksActionGlobalRsrcEdit] = self._actionGlobalRsrcEdit; + self._dDispatch[self.ksActionGlobalRsrcDel] = self._actionGlobalRsrcDel; + + # + # Build Source actions + # + self._dDispatch[self.ksActionBuildSrcList] = self._actionBuildSrcList; + self._dDispatch[self.ksActionBuildSrcAdd] = self._actionBuildSrcAdd; + self._dDispatch[self.ksActionBuildSrcAddPost] = self._actionBuildSrcAddPost; + self._dDispatch[self.ksActionBuildSrcClone] = self._actionBuildSrcClone; + self._dDispatch[self.ksActionBuildSrcDetails] = self._actionBuildSrcDetails; + self._dDispatch[self.ksActionBuildSrcDoRemove] = self._actionBuildSrcDoRemove; + self._dDispatch[self.ksActionBuildSrcEdit] = self._actionBuildSrcEdit; + self._dDispatch[self.ksActionBuildSrcEditPost] = self._actionBuildSrcEditPost; + + # + # Build actions + # + self._dDispatch[self.ksActionBuildList] = self._actionBuildList; + self._dDispatch[self.ksActionBuildAdd] = self._actionBuildAdd; + self._dDispatch[self.ksActionBuildAddPost] = self._actionBuildAddPost; + self._dDispatch[self.ksActionBuildClone] = self._actionBuildClone; + self._dDispatch[self.ksActionBuildDetails] = self._actionBuildDetails; + self._dDispatch[self.ksActionBuildDoRemove] = self._actionBuildDoRemove; + self._dDispatch[self.ksActionBuildEdit] = self._actionBuildEdit; + self._dDispatch[self.ksActionBuildEditPost] = self._actionBuildEditPost; + + # + # Build Black List actions + # + self._dDispatch[self.ksActionBuildBlacklist] = self._actionBuildBlacklist; + self._dDispatch[self.ksActionBuildBlacklistAdd] = self._actionBuildBlacklistAdd; + self._dDispatch[self.ksActionBuildBlacklistAddPost] = self._actionBuildBlacklistAddPost; + self._dDispatch[self.ksActionBuildBlacklistClone] = self._actionBuildBlacklistClone; + self._dDispatch[self.ksActionBuildBlacklistDetails] = self._actionBuildBlacklistDetails; + self._dDispatch[self.ksActionBuildBlacklistDoRemove] = self._actionBuildBlacklistDoRemove; + self._dDispatch[self.ksActionBuildBlacklistEdit] = self._actionBuildBlacklistEdit; + self._dDispatch[self.ksActionBuildBlacklistEditPost] = self._actionBuildBlacklistEditPost; + + # + # Failure Category actions + # + self._dDispatch[self.ksActionFailureCategoryList] = self._actionFailureCategoryList; + self._dDispatch[self.ksActionFailureCategoryAdd] = self._actionFailureCategoryAdd; + self._dDispatch[self.ksActionFailureCategoryAddPost] = self._actionFailureCategoryAddPost; + self._dDispatch[self.ksActionFailureCategoryDetails] = self._actionFailureCategoryDetails; + self._dDispatch[self.ksActionFailureCategoryDoRemove] = self._actionFailureCategoryDoRemove; + self._dDispatch[self.ksActionFailureCategoryEdit] = self._actionFailureCategoryEdit; + self._dDispatch[self.ksActionFailureCategoryEditPost] = self._actionFailureCategoryEditPost; + + # + # Failure Reason actions + # + self._dDispatch[self.ksActionFailureReasonList] = self._actionFailureReasonList; + self._dDispatch[self.ksActionFailureReasonAdd] = self._actionFailureReasonAdd; + self._dDispatch[self.ksActionFailureReasonAddPost] = self._actionFailureReasonAddPost; + self._dDispatch[self.ksActionFailureReasonDetails] = self._actionFailureReasonDetails; + self._dDispatch[self.ksActionFailureReasonDoRemove] = self._actionFailureReasonDoRemove; + self._dDispatch[self.ksActionFailureReasonEdit] = self._actionFailureReasonEdit; + self._dDispatch[self.ksActionFailureReasonEditPost] = self._actionFailureReasonEditPost; + + # + # Build Category actions + # + self._dDispatch[self.ksActionBuildCategoryList] = self._actionBuildCategoryList; + self._dDispatch[self.ksActionBuildCategoryAdd] = self._actionBuildCategoryAdd; + self._dDispatch[self.ksActionBuildCategoryAddPost] = self._actionBuildCategoryAddPost; + self._dDispatch[self.ksActionBuildCategoryClone] = self._actionBuildCategoryClone; + self._dDispatch[self.ksActionBuildCategoryDetails] = self._actionBuildCategoryDetails; + self._dDispatch[self.ksActionBuildCategoryDoRemove] = self._actionBuildCategoryDoRemove; + + # + # Test Group actions + # + self._dDispatch[self.ksActionTestGroupList] = self._actionTestGroupList; + self._dDispatch[self.ksActionTestGroupAdd] = self._actionTestGroupAdd; + self._dDispatch[self.ksActionTestGroupAddPost] = self._actionTestGroupAddPost; + self._dDispatch[self.ksActionTestGroupClone] = self._actionTestGroupClone; + self._dDispatch[self.ksActionTestGroupDetails] = self._actionTestGroupDetails; + self._dDispatch[self.ksActionTestGroupEdit] = self._actionTestGroupEdit; + self._dDispatch[self.ksActionTestGroupEditPost] = self._actionTestGroupEditPost; + self._dDispatch[self.ksActionTestGroupDoRemove] = self._actionTestGroupDoRemove; + self._dDispatch[self.ksActionTestCfgRegenQueues] = self._actionRegenQueuesCommon; + + # + # Scheduling Group and Queue actions + # + self._dDispatch[self.ksActionSchedGroupList] = self._actionSchedGroupList; + self._dDispatch[self.ksActionSchedGroupAdd] = self._actionSchedGroupAdd; + self._dDispatch[self.ksActionSchedGroupClone] = self._actionSchedGroupClone; + self._dDispatch[self.ksActionSchedGroupDetails] = self._actionSchedGroupDetails; + self._dDispatch[self.ksActionSchedGroupEdit] = self._actionSchedGroupEdit; + self._dDispatch[self.ksActionSchedGroupAddPost] = self._actionSchedGroupAddPost; + self._dDispatch[self.ksActionSchedGroupEditPost] = self._actionSchedGroupEditPost; + self._dDispatch[self.ksActionSchedGroupDoRemove] = self._actionSchedGroupDoRemove; + self._dDispatch[self.ksActionSchedQueueList] = self._actionSchedQueueList; + + + # + # Menus + # + self._aaoMenus = \ + [ + [ + 'Builds', self._sActionUrlBase + self.ksActionBuildList, + [ + [ 'Builds', self._sActionUrlBase + self.ksActionBuildList, False ], + [ 'Blacklist', self._sActionUrlBase + self.ksActionBuildBlacklist, False ], + [ 'Build sources', self._sActionUrlBase + self.ksActionBuildSrcList, False ], + [ 'Build categories', self._sActionUrlBase + self.ksActionBuildCategoryList, False ], + [ 'New build', self._sActionUrlBase + self.ksActionBuildAdd, True ], + [ 'New blacklisting', self._sActionUrlBase + self.ksActionBuildBlacklistAdd, True ], + [ 'New build source', self._sActionUrlBase + self.ksActionBuildSrcAdd, True ], + [ 'New build category', self._sActionUrlBase + self.ksActionBuildCategoryAdd, True ], + ] + ], + [ + 'Failure Reasons', self._sActionUrlBase + self.ksActionFailureReasonList, + [ + [ 'Failure categories', self._sActionUrlBase + self.ksActionFailureCategoryList, False ], + [ 'Failure reasons', self._sActionUrlBase + self.ksActionFailureReasonList, False ], + [ 'New failure category', self._sActionUrlBase + self.ksActionFailureCategoryAdd, True ], + [ 'New failure reason', self._sActionUrlBase + self.ksActionFailureReasonAdd, True ], + ] + ], + [ + 'System', self._sActionUrlBase + self.ksActionSystemChangelogList, + [ + [ 'Changelog', self._sActionUrlBase + self.ksActionSystemChangelogList, False ], + [ 'System log', self._sActionUrlBase + self.ksActionSystemLogList, False ], + [ 'Partial DB Dump', self._sActionUrlBase + self.ksActionSystemDbDump, False ], + [ 'User accounts', self._sActionUrlBase + self.ksActionUserList, False ], + [ 'New user', self._sActionUrlBase + self.ksActionUserAdd, True ], + ] + ], + [ + 'Testboxes', self._sActionUrlBase + self.ksActionTestBoxList, + [ + [ 'Testboxes', self._sActionUrlBase + self.ksActionTestBoxList, False ], + [ 'Scheduling groups', self._sActionUrlBase + self.ksActionSchedGroupList, False ], + [ 'New testbox', self._sActionUrlBase + self.ksActionTestBoxAdd, True ], + [ 'New scheduling group', self._sActionUrlBase + self.ksActionSchedGroupAdd, True ], + [ 'View scheduling queues', self._sActionUrlBase + self.ksActionSchedQueueList, False ], + [ 'Regenerate all scheduling queues', self._sActionUrlBase + self.ksActionTestBoxesRegenQueues, True ], + ] + ], + [ + 'Test Config', self._sActionUrlBase + self.ksActionTestGroupList, + [ + [ 'Test cases', self._sActionUrlBase + self.ksActionTestCaseList, False ], + [ 'Test groups', self._sActionUrlBase + self.ksActionTestGroupList, False ], + [ 'Global resources', self._sActionUrlBase + self.ksActionGlobalRsrcShowAll, False ], + [ 'New test case', self._sActionUrlBase + self.ksActionTestCaseAdd, True ], + [ 'New test group', self._sActionUrlBase + self.ksActionTestGroupAdd, True ], + [ 'New global resource', self._sActionUrlBase + self.ksActionGlobalRsrcShowAdd, True ], + [ 'Regenerate all scheduling queues', self._sActionUrlBase + self.ksActionTestCfgRegenQueues, True ], + ] + ], + [ + '> Test Results', 'index.py?' + webutils.encodeUrlParams(self._dDbgParams), [] + ], + ]; + + + def _actionDefault(self): + """Show the default admin page.""" + self._sAction = self.ksActionTestBoxList; + from testmanager.core.testbox import TestBoxLogic; + from testmanager.webui.wuiadmintestbox import WuiTestBoxList; + return self._actionGenericListing(TestBoxLogic, WuiTestBoxList); + + def _actionGenericDoDelOld(self, oCoreObjectLogic, sCoreObjectIdFieldName, sRedirectAction): + """ + Delete entry (using oLogicType.remove). + + @param oCoreObjectLogic A *Logic class + + @param sCoreObjectIdFieldName Name of HTTP POST variable that + contains object ID information + + @param sRedirectAction An action for redirect user to + in case of operation success + """ + iCoreDataObjectId = self.getStringParam(sCoreObjectIdFieldName) # STRING?!?! + self._checkForUnknownParameters() + + try: + self._sPageTitle = None + self._sPageBody = None + self._sRedirectTo = self._sActionUrlBase + sRedirectAction + return oCoreObjectLogic(self._oDb).remove(self._oCurUser.uid, iCoreDataObjectId) + except Exception as oXcpt: + self._oDb.rollback(); + self._sPageTitle = 'Unable to delete record' + self._sPageBody = str(oXcpt); + if config.g_kfDebugDbXcpt: + self._sPageBody += cgitb.html(sys.exc_info()); + self._sRedirectTo = None + + return False + + + # + # System Category. + # + + # System wide changelog actions. + + def _actionSystemChangelogList(self): + """ Action handler. """ + from testmanager.core.systemchangelog import SystemChangelogLogic; + from testmanager.webui.wuiadminsystemchangelog import WuiAdminSystemChangelogList; + + tsEffective = self.getEffectiveDateParam(); + cItemsPerPage = self.getIntParam(self.ksParamItemsPerPage, iMin = 2, iMax = 9999, iDefault = 384); + iPage = self.getIntParam(self.ksParamPageNo, iMin = 0, iMax = 999999, iDefault = 0); + cDaysBack = self.getIntParam(self.ksParamDaysBack, iMin = 1, iMax = 366, iDefault = 14); + self._checkForUnknownParameters(); + + aoEntries = SystemChangelogLogic(self._oDb).fetchForListingEx(iPage * cItemsPerPage, cItemsPerPage + 1, + tsEffective, cDaysBack); + oContent = WuiAdminSystemChangelogList(aoEntries, iPage, cItemsPerPage, tsEffective, + cDaysBack = cDaysBack, fnDPrint = self._oSrvGlue.dprint, oDisp = self); + (self._sPageTitle, self._sPageBody) = oContent.show(); + return True; + + # System Log actions. + + def _actionSystemLogList(self): + """ Action wrapper. """ + from testmanager.core.systemlog import SystemLogLogic; + from testmanager.webui.wuiadminsystemlog import WuiAdminSystemLogList; + return self._actionGenericListing(SystemLogLogic, WuiAdminSystemLogList) + + def _actionSystemDbDump(self): + """ Action handler. """ + from testmanager.webui.wuiadminsystemdbdump import WuiAdminSystemDbDumpForm; + + cDaysBack = self.getIntParam(self.ksParamDaysBack, iMin = config.g_kcTmDbDumpMinDays, + iMax = config.g_kcTmDbDumpMaxDays, iDefault = config.g_kcTmDbDumpDefaultDays); + self._checkForUnknownParameters(); + + oContent = WuiAdminSystemDbDumpForm(cDaysBack, oDisp = self); + (self._sPageTitle, self._sPageBody) = oContent.showForm(); + return True; + + def _actionSystemDbDumpDownload(self): + """ Action handler. """ + import datetime; + import os; + + cDaysBack = self.getIntParam(self.ksParamDaysBack, iMin = config.g_kcTmDbDumpMinDays, + iMax = config.g_kcTmDbDumpMaxDays, iDefault = config.g_kcTmDbDumpDefaultDays); + self._checkForUnknownParameters(); + + # + # Generate the dump. + # + # We generate a file name that's unique to a user is smart enough to only + # issue one of these requests at the time. This also makes sure we won't + # waste too much space should this code get interrupted and rerun. + # + oFile = None; + oNow = datetime.datetime.utcnow(); + sOutFile = config.g_ksTmDbDumpOutFileTmpl % (self._oCurUser.uid,); + sTmpFile = config.g_ksTmDbDumpTmpFileTmpl % (self._oCurUser.uid,); + sScript = os.path.join(config.g_ksTestManagerDir, 'db', 'partial-db-dump.py'); + try: + (iExitCode, sStdOut, sStdErr) = utils.processOutputUnchecked([ sScript, + '--days-to-dump', str(cDaysBack), + '-f', sOutFile, + '-t', sTmpFile, + ]); + if iExitCode != 0: + raise Exception('iExitCode=%s\n--- stderr ---\n%s\n--- stdout ---\n%s' % (iExitCode, sStdOut, sStdErr,)); + + # + # Open and send the dump. + # + oFile = open(sOutFile, 'rb'); # pylint: disable=consider-using-with + cbFile = os.fstat(oFile.fileno()).st_size; + + self._oSrvGlue.setHeaderField('Content-Type', 'application/zip'); + self._oSrvGlue.setHeaderField('Content-Disposition', + oNow.strftime('attachment; filename="partial-db-dump-%Y-%m-%dT%H-%M-%S.zip"')); + self._oSrvGlue.setHeaderField('Content-Length', str(cbFile)); + + while True: + abChunk = oFile.read(262144); + if not abChunk: + break; + self._oSrvGlue.writeRaw(abChunk); + + finally: + # Delete the file to save space. + if oFile: + try: oFile.close(); + except: pass; + utils.noxcptDeleteFile(sOutFile); + utils.noxcptDeleteFile(sTmpFile); + return self.ksDispatchRcAllDone; + + + # User Account actions. + + def _actionUserList(self): + """ Action wrapper. """ + from testmanager.core.useraccount import UserAccountLogic; + from testmanager.webui.wuiadminuseraccount import WuiUserAccountList; + return self._actionGenericListing(UserAccountLogic, WuiUserAccountList) + + def _actionUserAdd(self): + """ Action wrapper. """ + from testmanager.core.useraccount import UserAccountData; + from testmanager.webui.wuiadminuseraccount import WuiUserAccount; + return self._actionGenericFormAdd(UserAccountData, WuiUserAccount) + + def _actionUserDetails(self): + """ Action wrapper. """ + from testmanager.core.useraccount import UserAccountData, UserAccountLogic; + from testmanager.webui.wuiadminuseraccount import WuiUserAccount; + return self._actionGenericFormDetails(UserAccountData, UserAccountLogic, WuiUserAccount, 'uid'); + + def _actionUserEdit(self): + """ Action wrapper. """ + from testmanager.core.useraccount import UserAccountData; + from testmanager.webui.wuiadminuseraccount import WuiUserAccount; + return self._actionGenericFormEdit(UserAccountData, WuiUserAccount, UserAccountData.ksParam_uid); + + def _actionUserAddPost(self): + """ Action wrapper. """ + from testmanager.core.useraccount import UserAccountData, UserAccountLogic; + from testmanager.webui.wuiadminuseraccount import WuiUserAccount; + return self._actionGenericFormAddPost(UserAccountData, UserAccountLogic, WuiUserAccount, self.ksActionUserList) + + def _actionUserEditPost(self): + """ Action wrapper. """ + from testmanager.core.useraccount import UserAccountData, UserAccountLogic; + from testmanager.webui.wuiadminuseraccount import WuiUserAccount; + return self._actionGenericFormEditPost(UserAccountData, UserAccountLogic, WuiUserAccount, self.ksActionUserList) + + def _actionUserDelPost(self): + """ Action wrapper. """ + from testmanager.core.useraccount import UserAccountData, UserAccountLogic; + return self._actionGenericDoRemove(UserAccountLogic, UserAccountData.ksParam_uid, self.ksActionUserList) + + + # + # TestBox & Scheduling Category. + # + + def _actionTestBoxList(self): + """ Action wrapper. """ + from testmanager.core.testbox import TestBoxLogic + from testmanager.webui.wuiadmintestbox import WuiTestBoxList; + return self._actionGenericListing(TestBoxLogic, WuiTestBoxList); + + def _actionTestBoxListPost(self): + """Actions on a list of testboxes.""" + from testmanager.core.testbox import TestBoxData, TestBoxLogic + from testmanager.webui.wuiadmintestbox import WuiTestBoxList; + + # Parameters. + aidTestBoxes = self.getListOfIntParams(TestBoxData.ksParam_idTestBox, iMin = 1, aiDefaults = []); + sListAction = self.getStringParam(self.ksParamListAction); + if sListAction in [asDesc[0] for asDesc in WuiTestBoxList.kasTestBoxActionDescs]: + idAction = None; + else: + asActionPrefixes = [ 'setgroup-', ]; + i = 0; + while i < len(asActionPrefixes) and not sListAction.startswith(asActionPrefixes[i]): + i += 1; + if i >= len(asActionPrefixes): + raise WuiException('Parameter "%s" has an invalid value: "%s"' % (self.ksParamListAction, sListAction,)); + idAction = sListAction[len(asActionPrefixes[i]):]; + if not idAction.isdigit(): + raise WuiException('Parameter "%s" has an invalid value: "%s"' % (self.ksParamListAction, sListAction,)); + idAction = int(idAction); + sListAction = sListAction[:len(asActionPrefixes[i]) - 1]; + self._checkForUnknownParameters(); + + + # Take action. + if sListAction == 'none': + pass; + else: + oLogic = TestBoxLogic(self._oDb); + aoTestBoxes = [] + for idTestBox in aidTestBoxes: + aoTestBoxes.append(TestBoxData().initFromDbWithId(self._oDb, idTestBox)); + + if sListAction in [ 'enable', 'disable' ]: + fEnable = sListAction == 'enable'; + for oTestBox in aoTestBoxes: + if oTestBox.fEnabled != fEnable: + oTestBox.fEnabled = fEnable; + oLogic.editEntry(oTestBox, self._oCurUser.uid, fCommit = False); + else: + for oTestBox in aoTestBoxes: + if oTestBox.enmPendingCmd != sListAction: + oLogic.setCommand(oTestBox.idTestBox, oTestBox.enmPendingCmd, sListAction, self._oCurUser.uid, + fCommit = False); + self._oDb.commit(); + + # Re-display the list. + self._sPageTitle = None; + self._sPageBody = None; + self._sRedirectTo = self._sActionUrlBase + self.ksActionTestBoxList; + return True; + + def _actionTestBoxAdd(self): + """ Action wrapper. """ + from testmanager.core.testbox import TestBoxDataEx; + from testmanager.webui.wuiadmintestbox import WuiTestBox; + return self._actionGenericFormAdd(TestBoxDataEx, WuiTestBox); + + def _actionTestBoxAddPost(self): + """ Action wrapper. """ + from testmanager.core.testbox import TestBoxDataEx, TestBoxLogic; + from testmanager.webui.wuiadmintestbox import WuiTestBox; + return self._actionGenericFormAddPost(TestBoxDataEx, TestBoxLogic, WuiTestBox, self.ksActionTestBoxList); + + def _actionTestBoxDetails(self): + """ Action wrapper. """ + from testmanager.core.testbox import TestBoxDataEx, TestBoxLogic; + from testmanager.webui.wuiadmintestbox import WuiTestBox; + return self._actionGenericFormDetails(TestBoxDataEx, TestBoxLogic, WuiTestBox, 'idTestBox', 'idGenTestBox'); + + def _actionTestBoxEdit(self): + """ Action wrapper. """ + from testmanager.core.testbox import TestBoxDataEx; + from testmanager.webui.wuiadmintestbox import WuiTestBox; + return self._actionGenericFormEdit(TestBoxDataEx, WuiTestBox, TestBoxDataEx.ksParam_idTestBox); + + def _actionTestBoxEditPost(self): + """ Action wrapper. """ + from testmanager.core.testbox import TestBoxDataEx, TestBoxLogic; + from testmanager.webui.wuiadmintestbox import WuiTestBox; + return self._actionGenericFormEditPost(TestBoxDataEx, TestBoxLogic,WuiTestBox, self.ksActionTestBoxList); + + def _actionTestBoxRemovePost(self): + """ Action wrapper. """ + from testmanager.core.testbox import TestBoxData, TestBoxLogic; + return self._actionGenericDoRemove(TestBoxLogic, TestBoxData.ksParam_idTestBox, self.ksActionTestBoxList); + + + # Scheduling Group actions + + def _actionSchedGroupList(self): + """ Action wrapper. """ + from testmanager.core.schedgroup import SchedGroupLogic; + from testmanager.webui.wuiadminschedgroup import WuiAdminSchedGroupList; + return self._actionGenericListing(SchedGroupLogic, WuiAdminSchedGroupList); + + def _actionSchedGroupAdd(self): + """ Action wrapper. """ + from testmanager.core.schedgroup import SchedGroupDataEx; + from testmanager.webui.wuiadminschedgroup import WuiSchedGroup; + return self._actionGenericFormAdd(SchedGroupDataEx, WuiSchedGroup); + + def _actionSchedGroupClone(self): + """ Action wrapper. """ + from testmanager.core.schedgroup import SchedGroupDataEx; + from testmanager.webui.wuiadminschedgroup import WuiSchedGroup; + return self._actionGenericFormClone( SchedGroupDataEx, WuiSchedGroup, 'idSchedGroup'); + + def _actionSchedGroupDetails(self): + """ Action wrapper. """ + from testmanager.core.schedgroup import SchedGroupDataEx, SchedGroupLogic; + from testmanager.webui.wuiadminschedgroup import WuiSchedGroup; + return self._actionGenericFormDetails(SchedGroupDataEx, SchedGroupLogic, WuiSchedGroup, 'idSchedGroup'); + + def _actionSchedGroupEdit(self): + """ Action wrapper. """ + from testmanager.core.schedgroup import SchedGroupDataEx; + from testmanager.webui.wuiadminschedgroup import WuiSchedGroup; + return self._actionGenericFormEdit(SchedGroupDataEx, WuiSchedGroup, SchedGroupDataEx.ksParam_idSchedGroup); + + def _actionSchedGroupAddPost(self): + """ Action wrapper. """ + from testmanager.core.schedgroup import SchedGroupDataEx, SchedGroupLogic; + from testmanager.webui.wuiadminschedgroup import WuiSchedGroup; + return self._actionGenericFormAddPost(SchedGroupDataEx, SchedGroupLogic, WuiSchedGroup, self.ksActionSchedGroupList); + + def _actionSchedGroupEditPost(self): + """ Action wrapper. """ + from testmanager.core.schedgroup import SchedGroupDataEx, SchedGroupLogic; + from testmanager.webui.wuiadminschedgroup import WuiSchedGroup; + return self._actionGenericFormEditPost(SchedGroupDataEx, SchedGroupLogic, WuiSchedGroup, self.ksActionSchedGroupList); + + def _actionSchedGroupDoRemove(self): + """ Action wrapper. """ + from testmanager.core.schedgroup import SchedGroupData, SchedGroupLogic; + return self._actionGenericDoRemove(SchedGroupLogic, SchedGroupData.ksParam_idSchedGroup, self.ksActionSchedGroupList) + + def _actionSchedQueueList(self): + """ Action wrapper. """ + from testmanager.core.schedqueue import SchedQueueLogic; + from testmanager.webui.wuiadminschedqueue import WuiAdminSchedQueueList; + return self._actionGenericListing(SchedQueueLogic, WuiAdminSchedQueueList); + + def _actionRegenQueuesCommon(self): + """ + Common code for ksActionTestBoxesRegenQueues and ksActionTestCfgRegenQueues. + + Too lazy to put this in some separate place right now. + """ + from testmanager.core.schedgroup import SchedGroupLogic; + from testmanager.core.schedulerbase import SchedulerBase; + + self._checkForUnknownParameters(); + ## @todo should also be changed to a POST with a confirmation dialog preceeding it. + + self._sPageTitle = 'Regenerate All Scheduling Queues'; + if not self.isReadOnlyUser(): + self._sPageBody = ''; + aoGroups = SchedGroupLogic(self._oDb).getAll(); + for oGroup in aoGroups: + self._sPageBody += '<h3>%s (ID %#d)</h3>' % (webutils.escapeElem(oGroup.sName), oGroup.idSchedGroup); + try: + (aoErrors, asMessages) = SchedulerBase.recreateQueue(self._oDb, self._oCurUser.uid, oGroup.idSchedGroup, 2); + except Exception as oXcpt: + self._oDb.rollback(); + self._sPageBody += '<p>SchedulerBase.recreateQueue threw an exception: %s</p>' \ + % (webutils.escapeElem(str(oXcpt)),); + self._sPageBody += cgitb.html(sys.exc_info()); + else: + if not aoErrors: + self._sPageBody += '<p>Successfully regenerated.</p>'; + else: + for oError in aoErrors: + if oError[1] is None: + self._sPageBody += '<p>%s.</p>' % (webutils.escapeElem(oError[0]),); + ## @todo links. + #elif isinstance(oError[1], TestGroupData): + # self._sPageBody += '<p>%s.</p>' % (webutils.escapeElem(oError[0]),); + #elif isinstance(oError[1], TestGroupCase): + # self._sPageBody += '<p>%s.</p>' % (webutils.escapeElem(oError[0]),); + else: + self._sPageBody += '<p>%s. [Cannot link to %s]</p>' \ + % (webutils.escapeElem(oError[0]), webutils.escapeElem(str(oError[1])),); + for sMsg in asMessages: + self._sPageBody += '<p>%s<p>\n' % (webutils.escapeElem(sMsg),); + + # Remove leftovers from deleted scheduling groups. + self._sPageBody += '<h3>Cleanups</h3>\n'; + cOrphans = SchedulerBase.cleanUpOrphanedQueues(self._oDb); + self._sPageBody += '<p>Removed %s orphaned (deleted) queue%s.<p>\n' % (cOrphans, '' if cOrphans == 1 else 's', ); + else: + self._sPageBody = webutils.escapeElem('%s is a read only user and may not regenerate the scheduling queues!' + % (self._oCurUser.sUsername,)); + return True; + + + + # + # Test Config Category. + # + + # Test Cases + + def _actionTestCaseList(self): + """ Action wrapper. """ + from testmanager.core.testcase import TestCaseLogic; + from testmanager.webui.wuiadmintestcase import WuiTestCaseList; + return self._actionGenericListing(TestCaseLogic, WuiTestCaseList); + + def _actionTestCaseAdd(self): + """ Action wrapper. """ + from testmanager.core.testcase import TestCaseDataEx; + from testmanager.webui.wuiadmintestcase import WuiTestCase; + return self._actionGenericFormAdd(TestCaseDataEx, WuiTestCase); + + def _actionTestCaseAddPost(self): + """ Action wrapper. """ + from testmanager.core.testcase import TestCaseDataEx, TestCaseLogic; + from testmanager.webui.wuiadmintestcase import WuiTestCase; + return self._actionGenericFormAddPost(TestCaseDataEx, TestCaseLogic, WuiTestCase, self.ksActionTestCaseList); + + def _actionTestCaseClone(self): + """ Action wrapper. """ + from testmanager.core.testcase import TestCaseDataEx; + from testmanager.webui.wuiadmintestcase import WuiTestCase; + return self._actionGenericFormClone( TestCaseDataEx, WuiTestCase, 'idTestCase', 'idGenTestCase'); + + def _actionTestCaseDetails(self): + """ Action wrapper. """ + from testmanager.core.testcase import TestCaseDataEx, TestCaseLogic; + from testmanager.webui.wuiadmintestcase import WuiTestCase; + return self._actionGenericFormDetails(TestCaseDataEx, TestCaseLogic, WuiTestCase, 'idTestCase', 'idGenTestCase'); + + def _actionTestCaseEdit(self): + """ Action wrapper. """ + from testmanager.core.testcase import TestCaseDataEx; + from testmanager.webui.wuiadmintestcase import WuiTestCase; + return self._actionGenericFormEdit(TestCaseDataEx, WuiTestCase, TestCaseDataEx.ksParam_idTestCase); + + def _actionTestCaseEditPost(self): + """ Action wrapper. """ + from testmanager.core.testcase import TestCaseDataEx, TestCaseLogic; + from testmanager.webui.wuiadmintestcase import WuiTestCase; + return self._actionGenericFormEditPost(TestCaseDataEx, TestCaseLogic, WuiTestCase, self.ksActionTestCaseList); + + def _actionTestCaseDoRemove(self): + """ Action wrapper. """ + from testmanager.core.testcase import TestCaseData, TestCaseLogic; + return self._actionGenericDoRemove(TestCaseLogic, TestCaseData.ksParam_idTestCase, self.ksActionTestCaseList); + + # Test Group actions + + def _actionTestGroupList(self): + """ Action wrapper. """ + from testmanager.core.testgroup import TestGroupLogic; + from testmanager.webui.wuiadmintestgroup import WuiTestGroupList; + return self._actionGenericListing(TestGroupLogic, WuiTestGroupList); + def _actionTestGroupAdd(self): + """ Action wrapper. """ + from testmanager.core.testgroup import TestGroupDataEx; + from testmanager.webui.wuiadmintestgroup import WuiTestGroup; + return self._actionGenericFormAdd(TestGroupDataEx, WuiTestGroup); + def _actionTestGroupAddPost(self): + """ Action wrapper. """ + from testmanager.core.testgroup import TestGroupDataEx, TestGroupLogic; + from testmanager.webui.wuiadmintestgroup import WuiTestGroup; + return self._actionGenericFormAddPost(TestGroupDataEx, TestGroupLogic, WuiTestGroup, self.ksActionTestGroupList); + def _actionTestGroupClone(self): + """ Action wrapper. """ + from testmanager.core.testgroup import TestGroupDataEx; + from testmanager.webui.wuiadmintestgroup import WuiTestGroup; + return self._actionGenericFormClone(TestGroupDataEx, WuiTestGroup, 'idTestGroup'); + def _actionTestGroupDetails(self): + """ Action wrapper. """ + from testmanager.core.testgroup import TestGroupDataEx, TestGroupLogic; + from testmanager.webui.wuiadmintestgroup import WuiTestGroup; + return self._actionGenericFormDetails(TestGroupDataEx, TestGroupLogic, WuiTestGroup, 'idTestGroup'); + def _actionTestGroupEdit(self): + """ Action wrapper. """ + from testmanager.core.testgroup import TestGroupDataEx; + from testmanager.webui.wuiadmintestgroup import WuiTestGroup; + return self._actionGenericFormEdit(TestGroupDataEx, WuiTestGroup, TestGroupDataEx.ksParam_idTestGroup); + def _actionTestGroupEditPost(self): + """ Action wrapper. """ + from testmanager.core.testgroup import TestGroupDataEx, TestGroupLogic; + from testmanager.webui.wuiadmintestgroup import WuiTestGroup; + return self._actionGenericFormEditPost(TestGroupDataEx, TestGroupLogic, WuiTestGroup, self.ksActionTestGroupList); + def _actionTestGroupDoRemove(self): + """ Action wrapper. """ + from testmanager.core.testgroup import TestGroupDataEx, TestGroupLogic; + return self._actionGenericDoRemove(TestGroupLogic, TestGroupDataEx.ksParam_idTestGroup, self.ksActionTestGroupList) + + + # Global Resources + + def _actionGlobalRsrcShowAll(self): + """ Action wrapper. """ + from testmanager.core.globalresource import GlobalResourceLogic; + from testmanager.webui.wuiadminglobalrsrc import WuiGlobalResourceList; + return self._actionGenericListing(GlobalResourceLogic, WuiGlobalResourceList); + + def _actionGlobalRsrcShowAdd(self): + """ Action wrapper. """ + return self._actionGlobalRsrcShowAddEdit(WuiAdmin.ksActionGlobalRsrcAdd); + + def _actionGlobalRsrcShowEdit(self): + """ Action wrapper. """ + return self._actionGlobalRsrcShowAddEdit(WuiAdmin.ksActionGlobalRsrcEdit); + + def _actionGlobalRsrcAdd(self): + """ Action wrapper. """ + return self._actionGlobalRsrcAddEdit(WuiAdmin.ksActionGlobalRsrcAdd); + + def _actionGlobalRsrcEdit(self): + """ Action wrapper. """ + return self._actionGlobalRsrcAddEdit(WuiAdmin.ksActionGlobalRsrcEdit); + + def _actionGlobalRsrcDel(self): + """ Action wrapper. """ + from testmanager.core.globalresource import GlobalResourceData, GlobalResourceLogic; + return self._actionGenericDoDelOld(GlobalResourceLogic, GlobalResourceData.ksParam_idGlobalRsrc, + self.ksActionGlobalRsrcShowAll); + + def _actionGlobalRsrcShowAddEdit(self, sAction): # pylint: disable=invalid-name + """Show Global Resource creation or edit dialog""" + from testmanager.core.globalresource import GlobalResourceLogic, GlobalResourceData; + from testmanager.webui.wuiadminglobalrsrc import WuiGlobalResource; + + oGlobalResourceLogic = GlobalResourceLogic(self._oDb) + if sAction == WuiAdmin.ksActionGlobalRsrcEdit: + idGlobalRsrc = self.getIntParam(GlobalResourceData.ksParam_idGlobalRsrc, iDefault = -1) + oData = oGlobalResourceLogic.getById(idGlobalRsrc) + else: + oData = GlobalResourceData() + oData.convertToParamNull() + + self._checkForUnknownParameters() + + oContent = WuiGlobalResource(oData) + (self._sPageTitle, self._sPageBody) = oContent.showAddModifyPage(sAction) + + return True + + def _actionGlobalRsrcAddEdit(self, sAction): + """Add or modify Global Resource record""" + from testmanager.core.globalresource import GlobalResourceLogic, GlobalResourceData; + from testmanager.webui.wuiadminglobalrsrc import WuiGlobalResource; + + oData = GlobalResourceData() + oData.initFromParams(self, fStrict=True) + + self._checkForUnknownParameters() + + if self._oSrvGlue.getMethod() != 'POST': + raise WuiException('Expected "POST" request, got "%s"' % (self._oSrvGlue.getMethod(),)) + + oGlobalResourceLogic = GlobalResourceLogic(self._oDb) + dErrors = oData.validateAndConvert(self._oDb); + if not dErrors: + if sAction == WuiAdmin.ksActionGlobalRsrcAdd: + oGlobalResourceLogic.addGlobalResource(self._oCurUser.uid, oData) + elif sAction == WuiAdmin.ksActionGlobalRsrcEdit: + idGlobalRsrc = self.getStringParam(GlobalResourceData.ksParam_idGlobalRsrc) + oGlobalResourceLogic.editGlobalResource(self._oCurUser.uid, idGlobalRsrc, oData) + else: + raise WuiException('Invalid parameter.') + self._sPageTitle = None; + self._sPageBody = None; + self._sRedirectTo = self._sActionUrlBase + self.ksActionGlobalRsrcShowAll; + else: + oContent = WuiGlobalResource(oData) + (self._sPageTitle, self._sPageBody) = oContent.showAddModifyPage(sAction, dErrors=dErrors) + + return True + + + # + # Build Source actions + # + + def _actionBuildSrcList(self): + """ Action wrapper. """ + from testmanager.core.buildsource import BuildSourceLogic; + from testmanager.webui.wuiadminbuildsource import WuiAdminBuildSrcList; + return self._actionGenericListing(BuildSourceLogic, WuiAdminBuildSrcList); + + def _actionBuildSrcAdd(self): + """ Action wrapper. """ + from testmanager.core.buildsource import BuildSourceData; + from testmanager.webui.wuiadminbuildsource import WuiAdminBuildSrc; + return self._actionGenericFormAdd(BuildSourceData, WuiAdminBuildSrc); + + def _actionBuildSrcAddPost(self): + """ Action wrapper. """ + from testmanager.core.buildsource import BuildSourceData, BuildSourceLogic; + from testmanager.webui.wuiadminbuildsource import WuiAdminBuildSrc; + return self._actionGenericFormAddPost(BuildSourceData, BuildSourceLogic, WuiAdminBuildSrc, self.ksActionBuildSrcList); + + def _actionBuildSrcClone(self): + """ Action wrapper. """ + from testmanager.core.buildsource import BuildSourceData; + from testmanager.webui.wuiadminbuildsource import WuiAdminBuildSrc; + return self._actionGenericFormClone( BuildSourceData, WuiAdminBuildSrc, 'idBuildSrc'); + + def _actionBuildSrcDetails(self): + """ Action wrapper. """ + from testmanager.core.buildsource import BuildSourceData, BuildSourceLogic; + from testmanager.webui.wuiadminbuildsource import WuiAdminBuildSrc; + return self._actionGenericFormDetails(BuildSourceData, BuildSourceLogic, WuiAdminBuildSrc, 'idBuildSrc'); + + def _actionBuildSrcDoRemove(self): + """ Action wrapper. """ + from testmanager.core.buildsource import BuildSourceData, BuildSourceLogic; + return self._actionGenericDoRemove(BuildSourceLogic, BuildSourceData.ksParam_idBuildSrc, self.ksActionBuildSrcList); + + def _actionBuildSrcEdit(self): + """ Action wrapper. """ + from testmanager.core.buildsource import BuildSourceData; + from testmanager.webui.wuiadminbuildsource import WuiAdminBuildSrc; + return self._actionGenericFormEdit(BuildSourceData, WuiAdminBuildSrc, BuildSourceData.ksParam_idBuildSrc); + + def _actionBuildSrcEditPost(self): + """ Action wrapper. """ + from testmanager.core.buildsource import BuildSourceData, BuildSourceLogic; + from testmanager.webui.wuiadminbuildsource import WuiAdminBuildSrc; + return self._actionGenericFormEditPost(BuildSourceData, BuildSourceLogic, WuiAdminBuildSrc, self.ksActionBuildSrcList); + + + # + # Build actions + # + def _actionBuildList(self): + """ Action wrapper. """ + from testmanager.core.build import BuildLogic; + from testmanager.webui.wuiadminbuild import WuiAdminBuildList; + return self._actionGenericListing(BuildLogic, WuiAdminBuildList); + + def _actionBuildAdd(self): + """ Action wrapper. """ + from testmanager.core.build import BuildData; + from testmanager.webui.wuiadminbuild import WuiAdminBuild; + return self._actionGenericFormAdd(BuildData, WuiAdminBuild); + + def _actionBuildAddPost(self): + """ Action wrapper. """ + from testmanager.core.build import BuildData, BuildLogic; + from testmanager.webui.wuiadminbuild import WuiAdminBuild; + return self._actionGenericFormAddPost(BuildData, BuildLogic, WuiAdminBuild, self.ksActionBuildList); + + def _actionBuildClone(self): + """ Action wrapper. """ + from testmanager.core.build import BuildData; + from testmanager.webui.wuiadminbuild import WuiAdminBuild; + return self._actionGenericFormClone( BuildData, WuiAdminBuild, 'idBuild'); + + def _actionBuildDetails(self): + """ Action wrapper. """ + from testmanager.core.build import BuildData, BuildLogic; + from testmanager.webui.wuiadminbuild import WuiAdminBuild; + return self._actionGenericFormDetails(BuildData, BuildLogic, WuiAdminBuild, 'idBuild'); + + def _actionBuildDoRemove(self): + """ Action wrapper. """ + from testmanager.core.build import BuildData, BuildLogic; + return self._actionGenericDoRemove(BuildLogic, BuildData.ksParam_idBuild, self.ksActionBuildList); + + def _actionBuildEdit(self): + """ Action wrapper. """ + from testmanager.core.build import BuildData; + from testmanager.webui.wuiadminbuild import WuiAdminBuild; + return self._actionGenericFormEdit(BuildData, WuiAdminBuild, BuildData.ksParam_idBuild); + + def _actionBuildEditPost(self): + """ Action wrapper. """ + from testmanager.core.build import BuildData, BuildLogic; + from testmanager.webui.wuiadminbuild import WuiAdminBuild; + return self._actionGenericFormEditPost(BuildData, BuildLogic, WuiAdminBuild, self.ksActionBuildList) + + + # + # Build Category actions + # + def _actionBuildCategoryList(self): + """ Action wrapper. """ + from testmanager.core.build import BuildCategoryLogic; + from testmanager.webui.wuiadminbuildcategory import WuiAdminBuildCatList; + return self._actionGenericListing(BuildCategoryLogic, WuiAdminBuildCatList); + + def _actionBuildCategoryAdd(self): + """ Action wrapper. """ + from testmanager.core.build import BuildCategoryData; + from testmanager.webui.wuiadminbuildcategory import WuiAdminBuildCat; + return self._actionGenericFormAdd(BuildCategoryData, WuiAdminBuildCat); + + def _actionBuildCategoryAddPost(self): + """ Action wrapper. """ + from testmanager.core.build import BuildCategoryData, BuildCategoryLogic; + from testmanager.webui.wuiadminbuildcategory import WuiAdminBuildCat; + return self._actionGenericFormAddPost(BuildCategoryData, BuildCategoryLogic, WuiAdminBuildCat, + self.ksActionBuildCategoryList); + + def _actionBuildCategoryClone(self): + """ Action wrapper. """ + from testmanager.core.build import BuildCategoryData; + from testmanager.webui.wuiadminbuildcategory import WuiAdminBuildCat; + return self._actionGenericFormClone(BuildCategoryData, WuiAdminBuildCat, 'idBuildCategory'); + + def _actionBuildCategoryDetails(self): + """ Action wrapper. """ + from testmanager.core.build import BuildCategoryData, BuildCategoryLogic; + from testmanager.webui.wuiadminbuildcategory import WuiAdminBuildCat; + return self._actionGenericFormDetails(BuildCategoryData, BuildCategoryLogic, WuiAdminBuildCat, 'idBuildCategory'); + + def _actionBuildCategoryDoRemove(self): + """ Action wrapper. """ + from testmanager.core.build import BuildCategoryData, BuildCategoryLogic; + return self._actionGenericDoRemove(BuildCategoryLogic, BuildCategoryData.ksParam_idBuildCategory, + self.ksActionBuildCategoryList) + + + # + # Build Black List actions + # + def _actionBuildBlacklist(self): + """ Action wrapper. """ + from testmanager.core.buildblacklist import BuildBlacklistLogic; + from testmanager.webui.wuiadminbuildblacklist import WuiAdminListOfBlacklistItems; + return self._actionGenericListing(BuildBlacklistLogic, WuiAdminListOfBlacklistItems); + + def _actionBuildBlacklistAdd(self): + """ Action wrapper. """ + from testmanager.core.buildblacklist import BuildBlacklistData; + from testmanager.webui.wuiadminbuildblacklist import WuiAdminBuildBlacklist; + return self._actionGenericFormAdd(BuildBlacklistData, WuiAdminBuildBlacklist); + + def _actionBuildBlacklistAddPost(self): + """ Action wrapper. """ + from testmanager.core.buildblacklist import BuildBlacklistData, BuildBlacklistLogic; + from testmanager.webui.wuiadminbuildblacklist import WuiAdminBuildBlacklist; + return self._actionGenericFormAddPost(BuildBlacklistData, BuildBlacklistLogic, + WuiAdminBuildBlacklist, self.ksActionBuildBlacklist); + + def _actionBuildBlacklistClone(self): + """ Action wrapper. """ + from testmanager.core.buildblacklist import BuildBlacklistData; + from testmanager.webui.wuiadminbuildblacklist import WuiAdminBuildBlacklist; + return self._actionGenericFormClone(BuildBlacklistData, WuiAdminBuildBlacklist, 'idBlacklisting'); + + def _actionBuildBlacklistDetails(self): + """ Action wrapper. """ + from testmanager.core.buildblacklist import BuildBlacklistData, BuildBlacklistLogic; + from testmanager.webui.wuiadminbuildblacklist import WuiAdminBuildBlacklist; + return self._actionGenericFormDetails(BuildBlacklistData, BuildBlacklistLogic, WuiAdminBuildBlacklist, 'idBlacklisting'); + + def _actionBuildBlacklistDoRemove(self): + """ Action wrapper. """ + from testmanager.core.buildblacklist import BuildBlacklistData, BuildBlacklistLogic; + return self._actionGenericDoRemove(BuildBlacklistLogic, BuildBlacklistData.ksParam_idBlacklisting, + self.ksActionBuildBlacklist); + + def _actionBuildBlacklistEdit(self): + """ Action wrapper. """ + from testmanager.core.buildblacklist import BuildBlacklistData; + from testmanager.webui.wuiadminbuildblacklist import WuiAdminBuildBlacklist; + return self._actionGenericFormEdit(BuildBlacklistData, WuiAdminBuildBlacklist, BuildBlacklistData.ksParam_idBlacklisting); + + def _actionBuildBlacklistEditPost(self): + """ Action wrapper. """ + from testmanager.core.buildblacklist import BuildBlacklistData, BuildBlacklistLogic; + from testmanager.webui.wuiadminbuildblacklist import WuiAdminBuildBlacklist; + return self._actionGenericFormEditPost(BuildBlacklistData, BuildBlacklistLogic, WuiAdminBuildBlacklist, + self.ksActionBuildBlacklist) + + + # + # Failure Category actions + # + def _actionFailureCategoryList(self): + """ Action wrapper. """ + from testmanager.core.failurecategory import FailureCategoryLogic; + from testmanager.webui.wuiadminfailurecategory import WuiFailureCategoryList; + return self._actionGenericListing(FailureCategoryLogic, WuiFailureCategoryList); + + def _actionFailureCategoryAdd(self): + """ Action wrapper. """ + from testmanager.core.failurecategory import FailureCategoryData; + from testmanager.webui.wuiadminfailurecategory import WuiFailureCategory; + return self._actionGenericFormAdd(FailureCategoryData, WuiFailureCategory); + + def _actionFailureCategoryAddPost(self): + """ Action wrapper. """ + from testmanager.core.failurecategory import FailureCategoryData, FailureCategoryLogic; + from testmanager.webui.wuiadminfailurecategory import WuiFailureCategory; + return self._actionGenericFormAddPost(FailureCategoryData, FailureCategoryLogic, WuiFailureCategory, + self.ksActionFailureCategoryList) + + def _actionFailureCategoryDetails(self): + """ Action wrapper. """ + from testmanager.core.failurecategory import FailureCategoryData, FailureCategoryLogic; + from testmanager.webui.wuiadminfailurecategory import WuiFailureCategory; + return self._actionGenericFormDetails(FailureCategoryData, FailureCategoryLogic, WuiFailureCategory); + + + def _actionFailureCategoryDoRemove(self): + """ Action wrapper. """ + from testmanager.core.failurecategory import FailureCategoryData, FailureCategoryLogic; + return self._actionGenericDoRemove(FailureCategoryLogic, FailureCategoryData.ksParam_idFailureCategory, + self.ksActionFailureCategoryList); + + def _actionFailureCategoryEdit(self): + """ Action wrapper. """ + from testmanager.core.failurecategory import FailureCategoryData; + from testmanager.webui.wuiadminfailurecategory import WuiFailureCategory; + return self._actionGenericFormEdit(FailureCategoryData, WuiFailureCategory, + FailureCategoryData.ksParam_idFailureCategory); + + def _actionFailureCategoryEditPost(self): + """ Action wrapper. """ + from testmanager.core.failurecategory import FailureCategoryData, FailureCategoryLogic; + from testmanager.webui.wuiadminfailurecategory import WuiFailureCategory; + return self._actionGenericFormEditPost(FailureCategoryData, FailureCategoryLogic, WuiFailureCategory, + self.ksActionFailureCategoryList); + + # + # Failure Reason actions + # + def _actionFailureReasonList(self): + """ Action wrapper. """ + from testmanager.core.failurereason import FailureReasonLogic; + from testmanager.webui.wuiadminfailurereason import WuiAdminFailureReasonList; + return self._actionGenericListing(FailureReasonLogic, WuiAdminFailureReasonList) + + def _actionFailureReasonAdd(self): + """ Action wrapper. """ + from testmanager.core.failurereason import FailureReasonData; + from testmanager.webui.wuiadminfailurereason import WuiAdminFailureReason; + return self._actionGenericFormAdd(FailureReasonData, WuiAdminFailureReason); + + def _actionFailureReasonAddPost(self): + """ Action wrapper. """ + from testmanager.core.failurereason import FailureReasonData, FailureReasonLogic; + from testmanager.webui.wuiadminfailurereason import WuiAdminFailureReason; + return self._actionGenericFormAddPost(FailureReasonData, FailureReasonLogic, WuiAdminFailureReason, + self.ksActionFailureReasonList); + + def _actionFailureReasonDetails(self): + """ Action wrapper. """ + from testmanager.core.failurereason import FailureReasonData, FailureReasonLogic; + from testmanager.webui.wuiadminfailurereason import WuiAdminFailureReason; + return self._actionGenericFormDetails(FailureReasonData, FailureReasonLogic, WuiAdminFailureReason); + + def _actionFailureReasonDoRemove(self): + """ Action wrapper. """ + from testmanager.core.failurereason import FailureReasonData, FailureReasonLogic; + return self._actionGenericDoRemove(FailureReasonLogic, FailureReasonData.ksParam_idFailureReason, + self.ksActionFailureReasonList); + + def _actionFailureReasonEdit(self): + """ Action wrapper. """ + from testmanager.core.failurereason import FailureReasonData; + from testmanager.webui.wuiadminfailurereason import WuiAdminFailureReason; + return self._actionGenericFormEdit(FailureReasonData, WuiAdminFailureReason); + + + def _actionFailureReasonEditPost(self): + """ Action wrapper. """ + from testmanager.core.failurereason import FailureReasonData, FailureReasonLogic; + from testmanager.webui.wuiadminfailurereason import WuiAdminFailureReason; + return self._actionGenericFormEditPost(FailureReasonData, FailureReasonLogic, WuiAdminFailureReason, + self.ksActionFailureReasonList) + + + # + # Overrides. + # + + def _generatePage(self): + """Override parent handler in order to change page titte""" + if self._sPageTitle is not None: + self._sPageTitle = 'Test Manager Admin - ' + self._sPageTitle + + return WuiDispatcherBase._generatePage(self) diff --git a/src/VBox/ValidationKit/testmanager/webui/wuiadminbuild.py b/src/VBox/ValidationKit/testmanager/webui/wuiadminbuild.py new file mode 100755 index 00000000..3d9d322d --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuiadminbuild.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +# $Id: wuiadminbuild.py $ + +""" +Test Manager WUI - Builds. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154471 $" + + +# Validation Kit imports. +from testmanager.webui.wuicontentbase import WuiFormContentBase, WuiListContentBase, WuiTmLink, WuiBuildLogLink, \ + WuiSvnLinkWithTooltip; +from testmanager.core.build import BuildData, BuildCategoryLogic; +from testmanager.core.buildblacklist import BuildBlacklistData; +from testmanager.core.db import isDbTimestampInfinity; + + +class WuiAdminBuild(WuiFormContentBase): + """ + WUI Build HTML content generator. + """ + + def __init__(self, oData, sMode, oDisp): + if sMode == WuiFormContentBase.ksMode_Add: + sTitle = 'Add Build' + elif sMode == WuiFormContentBase.ksMode_Edit: + sTitle = 'Modify Build - #%s' % (oData.idBuild,); + else: + assert sMode == WuiFormContentBase.ksMode_Show; + sTitle = 'Build - #%s' % (oData.idBuild,); + WuiFormContentBase.__init__(self, oData, sMode, 'Build', oDisp, sTitle); + + def _populateForm(self, oForm, oData): + oForm.addIntRO (BuildData.ksParam_idBuild, oData.idBuild, 'Build ID') + oForm.addTimestampRO(BuildData.ksParam_tsCreated, oData.tsCreated, 'Created') + oForm.addTimestampRO(BuildData.ksParam_tsEffective, oData.tsEffective, 'Last changed') + oForm.addTimestampRO(BuildData.ksParam_tsExpire, oData.tsExpire, 'Expires (excl)') + oForm.addIntRO (BuildData.ksParam_uidAuthor, oData.uidAuthor, 'Changed by UID') + + oForm.addComboBox (BuildData.ksParam_idBuildCategory, oData.idBuildCategory, 'Build category', + BuildCategoryLogic(self._oDisp.getDb()).fetchForCombo()); + + oForm.addInt (BuildData.ksParam_iRevision, oData.iRevision, 'Revision') + oForm.addText (BuildData.ksParam_sVersion, oData.sVersion, 'Version') + oForm.addWideText (BuildData.ksParam_sLogUrl, oData.sLogUrl, 'Log URL') + oForm.addWideText (BuildData.ksParam_sBinaries, oData.sBinaries, 'Binaries') + oForm.addCheckBox (BuildData.ksParam_fBinariesDeleted, oData.fBinariesDeleted, 'Binaries deleted') + + oForm.addSubmit() + return True; + + +class WuiAdminBuildList(WuiListContentBase): + """ + WUI Admin Build List Content Generator. + """ + + ksResultsSortByOs_Darwin = '' + def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, aiSelectedSortColumns = None): + WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, + sTitle = 'Builds', sId = 'builds', fnDPrint = fnDPrint, oDisp = oDisp, + aiSelectedSortColumns = aiSelectedSortColumns); + + self._asColumnHeaders = ['ID', 'Product', 'Branch', 'Version', + 'Type', 'OS(es)', 'Author', 'Added', + 'Files', 'Action' ]; + self._asColumnAttribs = ['align="right"', 'align="center"', 'align="center"', 'align="center"', + 'align="center"', 'align="center"', 'align="center"', 'align="center"', + '', 'align="center"']; + + def _formatListEntry(self, iEntry): + from testmanager.webui.wuiadmin import WuiAdmin + oEntry = self._aoEntries[iEntry]; + + aoActions = []; + if oEntry.sLogUrl is not None: + aoActions.append(WuiBuildLogLink(oEntry.sLogUrl, 'Build Log')); + + dParams = { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildBlacklistAdd, + BuildBlacklistData.ksParam_sProduct: oEntry.oCat.sProduct, + BuildBlacklistData.ksParam_sBranch: oEntry.oCat.sBranch, + BuildBlacklistData.ksParam_asTypes: oEntry.oCat.sType, + BuildBlacklistData.ksParam_asOsArches: oEntry.oCat.asOsArches, + BuildBlacklistData.ksParam_iFirstRevision: oEntry.iRevision, + BuildBlacklistData.ksParam_iLastRevision: oEntry.iRevision } + + if self._oDisp is None or not self._oDisp.isReadOnlyUser(): + aoActions += [ + WuiTmLink('Blacklist', WuiAdmin.ksScriptName, dParams), + WuiTmLink('Details', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildDetails, + BuildData.ksParam_idBuild: oEntry.idBuild, + WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, }), + WuiTmLink('Clone', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildClone, + BuildData.ksParam_idBuild: oEntry.idBuild, + WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, }), + ]; + if isDbTimestampInfinity(oEntry.tsExpire): + aoActions += [ + WuiTmLink('Modify', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildEdit, + BuildData.ksParam_idBuild: oEntry.idBuild }), + WuiTmLink('Remove', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildDoRemove, + BuildData.ksParam_idBuild: oEntry.idBuild }, + sConfirm = 'Are you sure you want to remove build #%d?' % (oEntry.idBuild,) ), + ]; + + return [ oEntry.idBuild, + oEntry.oCat.sProduct, + oEntry.oCat.sBranch, + WuiSvnLinkWithTooltip(oEntry.iRevision, oEntry.oCat.sRepository, + sName = '%s r%s' % (oEntry.sVersion, oEntry.iRevision,)), + oEntry.oCat.sType, + ' '.join(oEntry.oCat.asOsArches), + 'batch' if oEntry.uidAuthor is None else oEntry.uidAuthor, + self.formatTsShort(oEntry.tsCreated), + oEntry.sBinaries if not oEntry.fBinariesDeleted else '<Deleted>', + aoActions, + ]; + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuiadminbuildblacklist.py b/src/VBox/ValidationKit/testmanager/webui/wuiadminbuildblacklist.py new file mode 100755 index 00000000..fe93801c --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuiadminbuildblacklist.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- +# $Id: wuiadminbuildblacklist.py $ + +""" +Test Manager WUI - Build Blacklist. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Validation Kit imports. +from testmanager.webui.wuibase import WuiException +from testmanager.webui.wuicontentbase import WuiFormContentBase, WuiListContentBase, WuiTmLink +from testmanager.core.buildblacklist import BuildBlacklistData +from testmanager.core.failurereason import FailureReasonLogic +from testmanager.core.db import TMDatabaseConnection +from testmanager.core import coreconsts + + +class WuiAdminBuildBlacklist(WuiFormContentBase): + """ + WUI Build Black List Form. + """ + + def __init__(self, oData, sMode, oDisp): + """ + Prepare & initialize parent + """ + + if sMode == WuiFormContentBase.ksMode_Add: + sTitle = 'Add Build Blacklist Entry' + elif sMode == WuiFormContentBase.ksMode_Edit: + sTitle = 'Edit Build Blacklist Entry' + else: + assert sMode == WuiFormContentBase.ksMode_Show; + sTitle = 'Build Black'; + WuiFormContentBase.__init__(self, oData, sMode, 'BuildBlacklist', oDisp, sTitle); + + # + # Additional data. + # + self.asTypes = coreconsts.g_kasBuildTypesAll + self.asOsArches = coreconsts.g_kasOsDotCpusAll + + def _populateForm(self, oForm, oData): + """ + Construct an HTML form + """ + + aoFailureReasons = FailureReasonLogic(self._oDisp.getDb()).fetchForCombo() + if not aoFailureReasons: + from testmanager.webui.wuiadmin import WuiAdmin + raise WuiException('Please <a href="%s?%s=%s">add</a> some Failure Reasons first.' + % (WuiAdmin.ksScriptName, WuiAdmin.ksParamAction, WuiAdmin.ksActionFailureReasonAdd)); + + asTypes = self.getListOfItems(self.asTypes, oData.asTypes) + asOsArches = self.getListOfItems(self.asOsArches, oData.asOsArches) + + oForm.addIntRO (BuildBlacklistData.ksParam_idBlacklisting, oData.idBlacklisting, 'Blacklist item ID') + oForm.addTimestampRO(BuildBlacklistData.ksParam_tsEffective, oData.tsEffective, 'Last changed') + oForm.addTimestampRO(BuildBlacklistData.ksParam_tsExpire, oData.tsExpire, 'Expires (excl)') + oForm.addIntRO (BuildBlacklistData.ksParam_uidAuthor, oData.uidAuthor, 'Changed by UID') + + oForm.addComboBox (BuildBlacklistData.ksParam_idFailureReason, oData.idFailureReason, 'Failure Reason', + aoFailureReasons) + + oForm.addText (BuildBlacklistData.ksParam_sProduct, oData.sProduct, 'Product') + oForm.addText (BuildBlacklistData.ksParam_sBranch, oData.sBranch, 'Branch') + oForm.addListOfTypes(BuildBlacklistData.ksParam_asTypes, asTypes, 'Build types') + oForm.addListOfOsArches(BuildBlacklistData.ksParam_asOsArches, asOsArches, 'Target architectures') + oForm.addInt (BuildBlacklistData.ksParam_iFirstRevision, oData.iFirstRevision, 'First revision') + oForm.addInt (BuildBlacklistData.ksParam_iLastRevision, oData.iLastRevision, 'Last revision (incl)') + + oForm.addSubmit(); + + return True; + + +class WuiAdminListOfBlacklistItems(WuiListContentBase): + """ + WUI Admin Build Blacklist Content Generator. + """ + + def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, aiSelectedSortColumns = None): + WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, + sTitle = 'Build Blacklist', sId = 'buildsBlacklist', + fnDPrint = fnDPrint, oDisp = oDisp, aiSelectedSortColumns = aiSelectedSortColumns); + + self._asColumnHeaders = ['ID', 'Failure Reason', + 'Product', 'Branch', 'Type', + 'OS(es)', 'First Revision', 'Last Revision', + 'Actions' ] + self._asColumnAttribs = ['align="right"', 'align="center"', 'align="center"', 'align="center"', + 'align="center"', 'align="center"', 'align="center"', 'align="center"', + 'align="center"', 'align="center"', 'align="center"', 'align="center"', + 'align="center"' ] + + def _formatListEntry(self, iEntry): + from testmanager.webui.wuiadmin import WuiAdmin + oEntry = self._aoEntries[iEntry] + + sShortFailReason = FailureReasonLogic(TMDatabaseConnection()).getById(oEntry.idFailureReason).sShort + + aoActions = [ + WuiTmLink('Details', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildBlacklistDetails, + BuildBlacklistData.ksParam_idBlacklisting: oEntry.idBlacklisting }), + ]; + if self._oDisp is None or not self._oDisp.isReadOnlyUser(): + aoActions += [ + WuiTmLink('Edit', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildBlacklistEdit, + BuildBlacklistData.ksParam_idBlacklisting: oEntry.idBlacklisting }), + WuiTmLink('Clone', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildBlacklistClone, + BuildBlacklistData.ksParam_idBlacklisting: oEntry.idBlacklisting, + WuiAdmin.ksParamEffectiveDate: oEntry.tsEffective, }), + WuiTmLink('Remove', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildBlacklistDoRemove, + BuildBlacklistData.ksParam_idBlacklisting: oEntry.idBlacklisting }, + sConfirm = 'Are you sure you want to remove black list entry #%d?' % (oEntry.idBlacklisting,)), + ]; + + return [ oEntry.idBlacklisting, + sShortFailReason, + oEntry.sProduct, + oEntry.sBranch, + oEntry.asTypes, + oEntry.asOsArches, + oEntry.iFirstRevision, + oEntry.iLastRevision, + aoActions + ]; diff --git a/src/VBox/ValidationKit/testmanager/webui/wuiadminbuildcategory.py b/src/VBox/ValidationKit/testmanager/webui/wuiadminbuildcategory.py new file mode 100755 index 00000000..09a91d04 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuiadminbuildcategory.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +# $Id: wuiadminbuildcategory.py $ + +""" +Test Manager WUI - Build categories. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Validation Kit imports. +from common import webutils; +from testmanager.webui.wuicontentbase import WuiListContentBase, WuiFormContentBase, WuiRawHtml, WuiTmLink; +from testmanager.core.build import BuildCategoryData +from testmanager.core import coreconsts; + + +class WuiAdminBuildCatList(WuiListContentBase): + """ + WUI Build Category List Content Generator. + """ + + def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, aiSelectedSortColumns = None): + WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, + sTitle = 'Build Categories', sId = 'buildcategories', + fnDPrint = fnDPrint, oDisp = oDisp, aiSelectedSortColumns = aiSelectedSortColumns); + self._asColumnHeaders = ([ 'ID', 'Product', 'Repository', 'Branch', 'Build Type', 'OS/Architectures', 'Actions' ]); + self._asColumnAttribs = (['align="right"', '', '', '', '', 'align="center"' ]); + + def _formatListEntry(self, iEntry): + from testmanager.webui.wuiadmin import WuiAdmin; + oEntry = self._aoEntries[iEntry]; + + aoActions = [ + WuiTmLink('Details', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildCategoryDetails, + BuildCategoryData.ksParam_idBuildCategory: oEntry.idBuildCategory, }), + ]; + if self._oDisp is None or not self._oDisp.isReadOnlyUser(): + aoActions += [ + WuiTmLink('Clone', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildCategoryClone, + BuildCategoryData.ksParam_idBuildCategory: oEntry.idBuildCategory, }), + WuiTmLink('Try Remove', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildCategoryDoRemove, + BuildCategoryData.ksParam_idBuildCategory: oEntry.idBuildCategory, }), + ]; + + sHtml = '<ul class="tmshowall">\n'; + for sOsArch in oEntry.asOsArches: + sHtml += ' <li class="tmshowall">%s</li>\n' % (webutils.escapeElem(sOsArch),); + sHtml += '</ul>\n' + + return [ oEntry.idBuildCategory, + oEntry.sRepository, + oEntry.sProduct, + oEntry.sBranch, + oEntry.sType, + WuiRawHtml(sHtml), + aoActions, + ]; + + +class WuiAdminBuildCat(WuiFormContentBase): + """ + WUI Build Category Form Content Generator. + """ + def __init__(self, oData, sMode, oDisp): + if sMode == WuiFormContentBase.ksMode_Add: + sTitle = 'Create Build Category'; + elif sMode == WuiFormContentBase.ksMode_Edit: + assert False, 'not possible' + else: + assert sMode == WuiFormContentBase.ksMode_Show; + sTitle = 'Build Category- %s' % (oData.idBuildCategory,); + WuiFormContentBase.__init__(self, oData, sMode, 'BuildCategory', oDisp, sTitle, fEditable = False); + + def _populateForm(self, oForm, oData): + oForm.addIntRO( BuildCategoryData.ksParam_idBuildCategory, oData.idBuildCategory, 'Build Category ID') + oForm.addText( BuildCategoryData.ksParam_sRepository, oData.sRepository, 'VCS repository name'); + oForm.addText( BuildCategoryData.ksParam_sProduct, oData.sProduct, 'Product name') + oForm.addText( BuildCategoryData.ksParam_sBranch, oData.sBranch, 'Branch name') + oForm.addText( BuildCategoryData.ksParam_sType, oData.sType, 'Build type') + + aoOsArches = [[sOsArch, sOsArch in oData.asOsArches, sOsArch] for sOsArch in coreconsts.g_kasOsDotCpusAll]; + oForm.addListOfOsArches(BuildCategoryData.ksParam_asOsArches, aoOsArches, 'Target architectures'); + + if self._sMode != WuiFormContentBase.ksMode_Show: + oForm.addSubmit(); + return True; + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuiadminbuildsource.py b/src/VBox/ValidationKit/testmanager/webui/wuiadminbuildsource.py new file mode 100755 index 00000000..e42c12c5 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuiadminbuildsource.py @@ -0,0 +1,160 @@ +# -*- coding: utf-8 -*- +# $Id: wuiadminbuildsource.py $ + +""" +Test Manager WUI - Build Sources. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Validation Kit imports. +from common import utils, webutils; +from testmanager.webui.wuicontentbase import WuiFormContentBase, WuiListContentBase, WuiTmLink, WuiRawHtml; +from testmanager.core import coreconsts; +from testmanager.core.db import isDbTimestampInfinity; +from testmanager.core.buildsource import BuildSourceData; + + +class WuiAdminBuildSrc(WuiFormContentBase): + """ + WUI Build Sources HTML content generator. + """ + + def __init__(self, oData, sMode, oDisp): + assert isinstance(oData, BuildSourceData); + if sMode == WuiFormContentBase.ksMode_Add: + sTitle = 'New Build Source'; + elif sMode == WuiFormContentBase.ksMode_Edit: + sTitle = 'Edit Build Source - %s (#%s)' % (oData.sName, oData.idBuildSrc,); + else: + assert sMode == WuiFormContentBase.ksMode_Show; + sTitle = 'Build Source - %s (#%s)' % (oData.sName, oData.idBuildSrc,); + WuiFormContentBase.__init__(self, oData, sMode, 'BuildSrc', oDisp, sTitle); + + def _populateForm(self, oForm, oData): + oForm.addIntRO (BuildSourceData.ksParam_idBuildSrc, oData.idBuildSrc, 'Build Source item ID') + oForm.addTimestampRO(BuildSourceData.ksParam_tsEffective, oData.tsEffective, 'Last changed') + oForm.addTimestampRO(BuildSourceData.ksParam_tsExpire, oData.tsExpire, 'Expires (excl)') + oForm.addIntRO (BuildSourceData.ksParam_uidAuthor, oData.uidAuthor, 'Changed by UID') + oForm.addText (BuildSourceData.ksParam_sName, oData.sName, 'Name') + oForm.addText (BuildSourceData.ksParam_sDescription, oData.sDescription, 'Description') + oForm.addText (BuildSourceData.ksParam_sProduct, oData.sProduct, 'Product') + oForm.addText (BuildSourceData.ksParam_sBranch, oData.sBranch, 'Branch') + asTypes = self.getListOfItems(coreconsts.g_kasBuildTypesAll, oData.asTypes); + oForm.addListOfTypes(BuildSourceData.ksParam_asTypes, asTypes, 'Build types') + asOsArches = self.getListOfItems(coreconsts.g_kasOsDotCpusAll, oData.asOsArches); + oForm.addListOfOsArches(BuildSourceData.ksParam_asOsArches, asOsArches, 'Target architectures') + oForm.addInt (BuildSourceData.ksParam_iFirstRevision, oData.iFirstRevision, 'Starting from revision') + oForm.addInt (BuildSourceData.ksParam_iLastRevision, oData.iLastRevision, 'Ending by revision') + oForm.addLong (BuildSourceData.ksParam_cSecMaxAge, + utils.formatIntervalSeconds2(oData.cSecMaxAge) if oData.cSecMaxAge not in [-1, '', None] else '', + 'Max age in seconds'); + oForm.addSubmit(); + return True; + +class WuiAdminBuildSrcList(WuiListContentBase): + """ + WUI Build Source content generator. + """ + + def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, aiSelectedSortColumns = None): + WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, + sTitle = 'Registered Build Sources', sId = 'build sources', + fnDPrint = fnDPrint, oDisp = oDisp, aiSelectedSortColumns = aiSelectedSortColumns); + self._asColumnHeaders = ['ID', 'Name', 'Description', 'Product', + 'Branch', 'Build Types', 'OS/ARCH', 'First Revision', 'Last Revision', 'Max Age', + 'Actions' ]; + self._asColumnAttribs = ['align="center"', 'align="center"', 'align="center"', 'align="center"', 'align="center"', + 'align="left"', 'align="left"', 'align="center"', 'align="center"', 'align="center"', + 'align="center"' ]; + + def _getSubList(self, aList): + """ + Convert pythonic list into HTML list + """ + if aList not in (None, []): + sHtml = ' <ul class="tmshowall">\n' + for sTmp in aList: + sHtml += ' <li class="tmshowall">%s</a></li>\n' % (webutils.escapeElem(sTmp),); + sHtml += ' </ul>\n'; + else: + sHtml = '<ul class="tmshowall"><li class="tmshowall">Any</li></ul>\n'; + + return WuiRawHtml(sHtml); + + def _formatListEntry(self, iEntry): + """ + Format *show all* table entry + """ + + from testmanager.webui.wuiadmin import WuiAdmin + oEntry = self._aoEntries[iEntry] + + aoActions = [ + WuiTmLink('Details', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildSrcDetails, + BuildSourceData.ksParam_idBuildSrc: oEntry.idBuildSrc, + WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, }), + ]; + if self._oDisp is None or not self._oDisp.isReadOnlyUser(): + aoActions += [ + WuiTmLink('Clone', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildSrcClone, + BuildSourceData.ksParam_idBuildSrc: oEntry.idBuildSrc, + WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, }), + ]; + if isDbTimestampInfinity(oEntry.tsExpire): + aoActions += [ + WuiTmLink('Modify', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildSrcEdit, + BuildSourceData.ksParam_idBuildSrc: oEntry.idBuildSrc } ), + WuiTmLink('Remove', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildSrcDoRemove, + BuildSourceData.ksParam_idBuildSrc: oEntry.idBuildSrc }, + sConfirm = 'Are you sure you want to remove build source #%d?' % (oEntry.idBuildSrc,) ) + ]; + + return [ oEntry.idBuildSrc, + oEntry.sName, + oEntry.sDescription, + oEntry.sProduct, + oEntry.sBranch, + self._getSubList(oEntry.asTypes), + self._getSubList(oEntry.asOsArches), + oEntry.iFirstRevision, + oEntry.iLastRevision, + utils.formatIntervalSeconds2(oEntry.cSecMaxAge) if oEntry.cSecMaxAge is not None else None, + aoActions, + ] diff --git a/src/VBox/ValidationKit/testmanager/webui/wuiadminfailurecategory.py b/src/VBox/ValidationKit/testmanager/webui/wuiadminfailurecategory.py new file mode 100755 index 00000000..6da1aa7d --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuiadminfailurecategory.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +# $Id: wuiadminfailurecategory.py $ + +""" +Test Manager WUI - Failure Categories Web content generator. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Validation Kit imports. +from testmanager.webui.wuicontentbase import WuiFormContentBase, WuiContentBase, WuiListContentBase, WuiTmLink; +from testmanager.webui.wuiadminfailurereason import WuiAdminFailureReasonList; +from testmanager.core.failurecategory import FailureCategoryData; +from testmanager.core.failurereason import FailureReasonLogic; + + +class WuiFailureReasonCategoryLink(WuiTmLink): + """ Link to a failure category. """ + def __init__(self, idFailureCategory, sName = WuiContentBase.ksShortDetailsLink, sTitle = None, fBracketed = None): + if fBracketed is None: + fBracketed = len(sName) > 2; + from testmanager.webui.wuiadmin import WuiAdmin; + WuiTmLink.__init__(self, sName = sName, + sUrlBase = WuiAdmin.ksScriptName, + dParams = { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureCategoryDetails, + FailureCategoryData.ksParam_idFailureCategory: idFailureCategory, }, + fBracketed = fBracketed, + sTitle = sTitle); + self.idFailureCategory = idFailureCategory; + + + +class WuiFailureCategory(WuiFormContentBase): + """ + WUI Failure Category HTML content generator. + """ + + def __init__(self, oData, sMode, oDisp): + """ + Prepare & initialize parent + """ + + sTitle = 'Failure Category'; + if sMode == WuiFormContentBase.ksMode_Add: + sTitle = 'Add ' + sTitle; + elif sMode == WuiFormContentBase.ksMode_Edit: + sTitle = 'Edit ' + sTitle; + else: + assert sMode == WuiFormContentBase.ksMode_Show; + + WuiFormContentBase.__init__(self, oData, sMode, 'FailureCategory', oDisp, sTitle); + + def _populateForm(self, oForm, oData): + """ + Construct an HTML form + """ + + oForm.addIntRO (FailureCategoryData.ksParam_idFailureCategory, oData.idFailureCategory, 'Failure Category ID') + oForm.addTimestampRO(FailureCategoryData.ksParam_tsEffective, oData.tsEffective, 'Last changed') + oForm.addTimestampRO(FailureCategoryData.ksParam_tsExpire, oData.tsExpire, 'Expires (excl)') + oForm.addIntRO (FailureCategoryData.ksParam_uidAuthor, oData.uidAuthor, 'Changed by UID') + oForm.addText (FailureCategoryData.ksParam_sShort, oData.sShort, 'Short Description') + oForm.addText (FailureCategoryData.ksParam_sFull, oData.sFull, 'Full Description') + + oForm.addSubmit() + + return True; + + def _generatePostFormContent(self, oData): + """ + Adds a table with the category members below the form. + """ + if oData.idFailureCategory is not None and oData.idFailureCategory >= 0: + oLogic = FailureReasonLogic(self._oDisp.getDb()); + tsNow = self._oDisp.getNow(); + cMax = 4096; + aoEntries = oLogic.fetchForListingInCategory(0, cMax, tsNow, oData.idFailureCategory) + if aoEntries: + oList = WuiAdminFailureReasonList(aoEntries, 0, cMax, tsNow, fnDPrint = None, oDisp = self._oDisp); + return [ [ 'Members', oList.show(fShowNavigation = False)[1]], ]; + return []; + + + +class WuiFailureCategoryList(WuiListContentBase): + """ + WUI Admin Failure Category Content Generator. + """ + + def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, aiSelectedSortColumns = None): + WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, + sTitle = 'Failure Categories', sId = 'failureCategories', + fnDPrint = fnDPrint, oDisp = oDisp, aiSelectedSortColumns = aiSelectedSortColumns); + + self._asColumnHeaders = ['ID', 'Short Description', 'Full Description', 'Actions' ] + self._asColumnAttribs = ['align="right"', 'align="center"', 'align="center"', 'align="center"'] + + def _formatListEntry(self, iEntry): + from testmanager.webui.wuiadmin import WuiAdmin + oEntry = self._aoEntries[iEntry] + + aoActions = [ + WuiTmLink('Details', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureCategoryDetails, + FailureCategoryData.ksParam_idFailureCategory: oEntry.idFailureCategory }), + ]; + if self._oDisp is None or not self._oDisp.isReadOnlyUser(): + aoActions += [ + WuiTmLink('Modify', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureCategoryEdit, + FailureCategoryData.ksParam_idFailureCategory: oEntry.idFailureCategory }), + WuiTmLink('Remove', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureCategoryDoRemove, + FailureCategoryData.ksParam_idFailureCategory: oEntry.idFailureCategory }, + sConfirm = 'Do you really want to remove failure cateogry #%d?' % (oEntry.idFailureCategory,)), + ] + + return [ oEntry.idFailureCategory, + oEntry.sShort, + oEntry.sFull, + aoActions, + ]; + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuiadminfailurereason.py b/src/VBox/ValidationKit/testmanager/webui/wuiadminfailurereason.py new file mode 100755 index 00000000..b885e651 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuiadminfailurereason.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- +# $Id: wuiadminfailurereason.py $ + +""" +Test Manager WUI - Failure Reasons Web content generator. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Validation Kit imports. +from testmanager.webui.wuibase import WuiException +from testmanager.webui.wuicontentbase import WuiFormContentBase, WuiListContentBase, WuiContentBase, WuiTmLink; +from testmanager.core.failurereason import FailureReasonData; +from testmanager.core.failurecategory import FailureCategoryLogic; +from testmanager.core.db import TMDatabaseConnection; + + + +class WuiFailureReasonDetailsLink(WuiTmLink): + """ Short link to a failure reason. """ + def __init__(self, idFailureReason, sName = WuiContentBase.ksShortDetailsLink, sTitle = None, fBracketed = None): + if fBracketed is None: + fBracketed = len(sName) > 2; + from testmanager.webui.wuiadmin import WuiAdmin; + WuiTmLink.__init__(self, sName = sName, + sUrlBase = WuiAdmin.ksScriptName, + dParams = { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureReasonDetails, + FailureReasonData.ksParam_idFailureReason: idFailureReason, }, + fBracketed = fBracketed); + self.idFailureReason = idFailureReason; + + + +class WuiFailureReasonAddLink(WuiTmLink): + """ Link for adding a failure reason. """ + def __init__(self, sName = WuiContentBase.ksShortAddLink, sTitle = None, fBracketed = None): + if fBracketed is None: + fBracketed = len(sName) > 2; + from testmanager.webui.wuiadmin import WuiAdmin; + WuiTmLink.__init__(self, sName = sName, + sUrlBase = WuiAdmin.ksScriptName, + dParams = { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureReasonAdd, }, + fBracketed = fBracketed); + + + +class WuiAdminFailureReason(WuiFormContentBase): + """ + WUI Failure Reason HTML content generator. + """ + + def __init__(self, oFailureReasonData, sMode, oDisp): + """ + Prepare & initialize parent + """ + + sTitle = 'Failure Reason'; + if sMode == WuiFormContentBase.ksMode_Add: + sTitle = 'Add' + sTitle; + elif sMode == WuiFormContentBase.ksMode_Edit: + sTitle = 'Edit' + sTitle; + else: + assert sMode == WuiFormContentBase.ksMode_Show; + + WuiFormContentBase.__init__(self, oFailureReasonData, sMode, 'FailureReason', oDisp, sTitle); + + def _populateForm(self, oForm, oData): + """ + Construct an HTML form + """ + + aoFailureCategories = FailureCategoryLogic(TMDatabaseConnection()).getFailureCategoriesForCombo() + if not aoFailureCategories: + from testmanager.webui.wuiadmin import WuiAdmin + sExceptionMsg = 'Please <a href="%s?%s=%s">add</a> Failure Category first.' % \ + (WuiAdmin.ksScriptName, WuiAdmin.ksParamAction, WuiAdmin.ksActionFailureCategoryAdd) + + raise WuiException(sExceptionMsg) + + oForm.addIntRO (FailureReasonData.ksParam_idFailureReason, oData.idFailureReason, 'Failure Reason ID') + oForm.addTimestampRO (FailureReasonData.ksParam_tsEffective, oData.tsEffective, 'Last changed') + oForm.addTimestampRO (FailureReasonData.ksParam_tsExpire, oData.tsExpire, 'Expires (excl)') + oForm.addIntRO (FailureReasonData.ksParam_uidAuthor, oData.uidAuthor, 'Changed by UID') + + oForm.addComboBox (FailureReasonData.ksParam_idFailureCategory, oData.idFailureCategory, 'Failure Category', + aoFailureCategories) + + oForm.addText (FailureReasonData.ksParam_sShort, oData.sShort, 'Short Description') + oForm.addText (FailureReasonData.ksParam_sFull, oData.sFull, 'Full Description') + oForm.addInt (FailureReasonData.ksParam_iTicket, oData.iTicket, 'Ticket Number') + oForm.addMultilineText(FailureReasonData.ksParam_asUrls, oData.asUrls, 'Other URLs to reports ' + 'or discussions of the ' + 'observed symptoms') + oForm.addSubmit() + + return True + + +class WuiAdminFailureReasonList(WuiListContentBase): + """ + WUI Admin Failure Reasons Content Generator. + """ + + def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, aiSelectedSortColumns = None): + WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, + sTitle = 'Failure Reasons', sId = 'failureReasons', + fnDPrint = fnDPrint, oDisp = oDisp, aiSelectedSortColumns = aiSelectedSortColumns); + + self._asColumnHeaders = ['ID', 'Category', 'Short Description', + 'Full Description', 'Ticket', 'External References', 'Actions' ] + + self._asColumnAttribs = ['align="right"', 'align="center"', 'align="center"', + 'align="center"',' align="center"', 'align="center"', 'align="center"'] + + def _formatListEntry(self, iEntry): + from testmanager.webui.wuiadmin import WuiAdmin + from testmanager.webui.wuiadminfailurecategory import WuiFailureReasonCategoryLink; + oEntry = self._aoEntries[iEntry] + + aoActions = [ + WuiTmLink('Details', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureReasonDetails, + FailureReasonData.ksParam_idFailureReason: oEntry.idFailureReason } ), + ]; + if self._oDisp is None or not self._oDisp.isReadOnlyUser(): + aoActions += [ + WuiTmLink('Modify', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureReasonEdit, + FailureReasonData.ksParam_idFailureReason: oEntry.idFailureReason } ), + WuiTmLink('Remove', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureReasonDoRemove, + FailureReasonData.ksParam_idFailureReason: oEntry.idFailureReason }, + sConfirm = 'Are you sure you want to remove failure reason #%d?' % (oEntry.idFailureReason,)), + ]; + + return [ oEntry.idFailureReason, + WuiFailureReasonCategoryLink(oEntry.idFailureCategory, sName = oEntry.oCategory.sShort, fBracketed = False), + oEntry.sShort, + oEntry.sFull, + oEntry.iTicket, + oEntry.asUrls, + aoActions, + ] diff --git a/src/VBox/ValidationKit/testmanager/webui/wuiadminglobalrsrc.py b/src/VBox/ValidationKit/testmanager/webui/wuiadminglobalrsrc.py new file mode 100755 index 00000000..76deaf24 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuiadminglobalrsrc.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- +# $Id: wuiadminglobalrsrc.py $ + +""" +Test Manager WUI - Global resources. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + +# Validation Kit imports. +from testmanager.webui.wuibase import WuiException +from testmanager.webui.wuicontentbase import WuiContentBase +from testmanager.webui.wuihlpform import WuiHlpForm +from testmanager.core.globalresource import GlobalResourceData +from testmanager.webui.wuicontentbase import WuiListContentBase, WuiTmLink + + +class WuiGlobalResource(WuiContentBase): + """ + WUI global resources content generator. + """ + + def __init__(self, oData, fnDPrint = None): + """ + Do necessary initializations + """ + WuiContentBase.__init__(self, fnDPrint) + self._oData = oData + + def showAddModifyPage(self, sAction, dErrors = None): + """ + Render add global resource HTML form. + """ + from testmanager.webui.wuiadmin import WuiAdmin + + sFormActionUrl = '%s?%s=%s' % (WuiAdmin.ksScriptName, + WuiAdmin.ksParamAction, sAction) + if sAction == WuiAdmin.ksActionGlobalRsrcAdd: + sTitle = 'Add Global Resource' + elif sAction == WuiAdmin.ksActionGlobalRsrcEdit: + sTitle = 'Modify Global Resource' + sFormActionUrl += '&%s=%s' % (GlobalResourceData.ksParam_idGlobalRsrc, self._oData.idGlobalRsrc) + else: + raise WuiException('Invalid paraemter "%s"' % (sAction,)) + + oForm = WuiHlpForm('globalresourceform', + sFormActionUrl, + dErrors if dErrors is not None else {}) + + if sAction == WuiAdmin.ksActionGlobalRsrcAdd: + oForm.addIntRO (GlobalResourceData.ksParam_idGlobalRsrc, self._oData.idGlobalRsrc, 'Global Resource ID') + oForm.addTimestampRO(GlobalResourceData.ksParam_tsEffective, self._oData.tsEffective, 'Last changed') + oForm.addTimestampRO(GlobalResourceData.ksParam_tsExpire, self._oData.tsExpire, 'Expires (excl)') + oForm.addIntRO (GlobalResourceData.ksParam_uidAuthor, self._oData.uidAuthor, 'Changed by UID') + oForm.addText (GlobalResourceData.ksParam_sName, self._oData.sName, 'Name') + oForm.addText (GlobalResourceData.ksParam_sDescription, self._oData.sDescription, 'Description') + oForm.addCheckBox (GlobalResourceData.ksParam_fEnabled, self._oData.fEnabled, 'Enabled') + + oForm.addSubmit('Submit') + + return (sTitle, oForm.finalize()) + + +class WuiGlobalResourceList(WuiListContentBase): + """ + WUI Content Generator. + """ + + def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, aiSelectedSortColumns = None): + WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, + sTitle = 'Global Resources', sId = 'globalResources', + fnDPrint = fnDPrint, oDisp = oDisp, aiSelectedSortColumns = aiSelectedSortColumns); + + self._asColumnHeaders = ['ID', 'Name', 'Description', 'Enabled', 'Actions' ] + self._asColumnAttribs = ['align="right"', 'align="center"', 'align="center"', + 'align="center"', 'align="center"'] + + def _formatListEntry(self, iEntry): + from testmanager.webui.wuiadmin import WuiAdmin + oEntry = self._aoEntries[iEntry] + + aoActions = [ ]; + if self._oDisp is None or not self._oDisp.isReadOnlyUser(): + aoActions += [ + WuiTmLink('Modify', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionGlobalRsrcShowEdit, + GlobalResourceData.ksParam_idGlobalRsrc: oEntry.idGlobalRsrc }), + WuiTmLink('Remove', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionGlobalRsrcDel, + GlobalResourceData.ksParam_idGlobalRsrc: oEntry.idGlobalRsrc }, + sConfirm = 'Are you sure you want to remove global resource #%d?' % (oEntry.idGlobalRsrc,)), + ]; + + return [ oEntry.idGlobalRsrc, + oEntry.sName, + oEntry.sDescription, + oEntry.fEnabled, + aoActions, ]; + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuiadminschedgroup.py b/src/VBox/ValidationKit/testmanager/webui/wuiadminschedgroup.py new file mode 100755 index 00000000..b74a946e --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuiadminschedgroup.py @@ -0,0 +1,205 @@ +# -*- coding: utf-8 -*- +# $Id: wuiadminschedgroup.py $ + +""" +Test Manager WUI - Scheduling groups. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Validation Kit imports. +from testmanager.core.buildsource import BuildSourceData, BuildSourceLogic; +from testmanager.core.db import isDbTimestampInfinity; +from testmanager.core.schedgroup import SchedGroupData, SchedGroupDataEx; +from testmanager.core.testgroup import TestGroupData, TestGroupLogic; +from testmanager.core.testbox import TestBoxLogic; +from testmanager.webui.wuicontentbase import WuiFormContentBase, WuiListContentBase, WuiTmLink, WuiRawHtml; +from testmanager.webui.wuiadmintestbox import WuiTestBoxDetailsLink; + + +class WuiSchedGroup(WuiFormContentBase): + """ + WUI Scheduling Groups HTML content generator. + """ + + def __init__(self, oData, sMode, oDisp): + assert isinstance(oData, SchedGroupData); + if sMode == WuiFormContentBase.ksMode_Add: + sTitle = 'New Scheduling Group'; + elif sMode == WuiFormContentBase.ksMode_Edit: + sTitle = 'Edit Scheduling Group' + else: + assert sMode == WuiFormContentBase.ksMode_Show; + sTitle = 'Scheduling Group'; + WuiFormContentBase.__init__(self, oData, sMode, 'SchedGroup', oDisp, sTitle); + + # Read additional bits form the DB, unless we're in + if sMode != WuiFormContentBase.ksMode_Show: + self._aoAllRelevantTestGroups = TestGroupLogic(oDisp.getDb()).getAll(); + self._aoAllRelevantTestBoxes = TestBoxLogic(oDisp.getDb()).getAll(); + else: + self._aoAllRelevantTestGroups = [oMember.oTestGroup for oMember in oData.aoMembers]; + self._aoAllRelevantTestBoxes = [oMember.oTestBox for oMember in oData.aoTestBoxes]; + + def _populateForm(self, oForm, oData): # type: (WuiHlpForm, SchedGroupDataEx) -> bool + """ + Construct an HTML form + """ + + oForm.addIntRO( SchedGroupData.ksParam_idSchedGroup, oData.idSchedGroup, 'ID') + oForm.addTimestampRO(SchedGroupData.ksParam_tsEffective, oData.tsEffective, 'Last changed') + oForm.addTimestampRO(SchedGroupData.ksParam_tsExpire, oData.tsExpire, 'Expires (excl)') + oForm.addIntRO( SchedGroupData.ksParam_uidAuthor, oData.uidAuthor, 'Changed by UID') + oForm.addText( SchedGroupData.ksParam_sName, oData.sName, 'Name') + oForm.addText( SchedGroupData.ksParam_sDescription, oData.sDescription, 'Description') + oForm.addCheckBox( SchedGroupData.ksParam_fEnabled, oData.fEnabled, 'Enabled') + + oForm.addComboBox( SchedGroupData.ksParam_enmScheduler, oData.enmScheduler, 'Scheduler type', + SchedGroupData.kasSchedulerDesc) + + aoBuildSrcIds = BuildSourceLogic(self._oDisp.getDb()).fetchForCombo(); + oForm.addComboBox( SchedGroupData.ksParam_idBuildSrc, oData.idBuildSrc, 'Build source', aoBuildSrcIds); + oForm.addComboBox( SchedGroupData.ksParam_idBuildSrcTestSuite, + oData.idBuildSrcTestSuite, 'Test suite', aoBuildSrcIds); + + oForm.addListOfSchedGroupMembers(SchedGroupDataEx.ksParam_aoMembers, + oData.aoMembers, self._aoAllRelevantTestGroups, 'Test groups', + oData.idSchedGroup, fReadOnly = self._sMode == WuiFormContentBase.ksMode_Show); + + oForm.addListOfSchedGroupBoxes(SchedGroupDataEx.ksParam_aoTestBoxes, + oData.aoTestBoxes, self._aoAllRelevantTestBoxes, 'Test boxes', + oData.idSchedGroup, fReadOnly = self._sMode == WuiFormContentBase.ksMode_Show); + + oForm.addMultilineText(SchedGroupData.ksParam_sComment, oData.sComment, 'Comment'); + oForm.addSubmit() + + return True; + +class WuiAdminSchedGroupList(WuiListContentBase): + """ + Content generator for the schedule group listing. + """ + + def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, aiSelectedSortColumns = None): + WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, + sTitle = 'Registered Scheduling Groups', sId = 'schedgroups', + fnDPrint = fnDPrint, oDisp = oDisp, aiSelectedSortColumns = aiSelectedSortColumns); + + self._asColumnHeaders = [ + 'ID', 'Name', 'Enabled', 'Scheduler Type', + 'Build Source', 'Validation Kit Source', 'Test Groups', 'TestBoxes', 'Note', 'Actions', + ]; + + self._asColumnAttribs = [ + 'align="right"', 'align="center"', 'align="center"', 'align="center"', + 'align="center"', 'align="center"', '', '', 'align="center"', 'align="center"', + ]; + + def _formatListEntry(self, iEntry): + """ + Format *show all* table entry + """ + from testmanager.webui.wuiadmin import WuiAdmin + oEntry = self._aoEntries[iEntry] # type: SchedGroupDataEx + + oBuildSrc = None; + if oEntry.idBuildSrc is not None: + oBuildSrc = WuiTmLink(oEntry.oBuildSrc.sName if oEntry.oBuildSrc else str(oEntry.idBuildSrc), + WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildSrcDetails, + BuildSourceData.ksParam_idBuildSrc: oEntry.idBuildSrc, }); + + oValidationKitSrc = None; + if oEntry.idBuildSrcTestSuite is not None: + oValidationKitSrc = WuiTmLink(oEntry.oBuildSrcValidationKit.sName if oEntry.oBuildSrcValidationKit + else str(oEntry.idBuildSrcTestSuite), + WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildSrcDetails, + BuildSourceData.ksParam_idBuildSrc: oEntry.idBuildSrcTestSuite, }); + + # Test groups + aoMembers = []; + for oMember in oEntry.aoMembers: + aoMembers.append(WuiTmLink(oMember.oTestGroup.sName, WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestGroupDetails, + TestGroupData.ksParam_idTestGroup: oMember.idTestGroup, + WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, }, + sTitle = '#%s' % (oMember.idTestGroup,) if oMember.oTestGroup.sDescription is None + else '#%s - %s' % (oMember.idTestGroup, oMember.oTestGroup.sDescription,) )); + + # Test boxes. + aoTestBoxes = []; + for oRelation in oEntry.aoTestBoxes: + oTestBox = oRelation.oTestBox; + if oTestBox: + aoTestBoxes.append(WuiTestBoxDetailsLink(oTestBox, fBracketed = True, tsNow = self._tsEffectiveDate)); + else: + aoTestBoxes.append(WuiRawHtml('#%s' % (oRelation.idTestBox,))); + + # Actions + aoActions = [ WuiTmLink('Details', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionSchedGroupDetails, + SchedGroupData.ksParam_idSchedGroup: oEntry.idSchedGroup, + WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, } ),]; + if self._oDisp is None or not self._oDisp.isReadOnlyUser(): + + if isDbTimestampInfinity(oEntry.tsExpire): + aoActions.append(WuiTmLink('Modify', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionSchedGroupEdit, + SchedGroupData.ksParam_idSchedGroup: oEntry.idSchedGroup } )); + aoActions.append(WuiTmLink('Clone', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionSchedGroupClone, + SchedGroupData.ksParam_idSchedGroup: oEntry.idSchedGroup, + WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, } )); + if isDbTimestampInfinity(oEntry.tsExpire): + aoActions.append(WuiTmLink('Remove', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionSchedGroupDoRemove, + SchedGroupData.ksParam_idSchedGroup: oEntry.idSchedGroup }, + sConfirm = 'Are you sure you want to remove scheduling group #%d?' + % (oEntry.idSchedGroup,))); + + return [ + oEntry.idSchedGroup, + oEntry.sName, + oEntry.fEnabled, + oEntry.enmScheduler, + oBuildSrc, + oValidationKitSrc, + aoMembers, + aoTestBoxes, + self._formatCommentCell(oEntry.sComment), + aoActions, + ]; + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuiadminschedqueue.py b/src/VBox/ValidationKit/testmanager/webui/wuiadminschedqueue.py new file mode 100755 index 00000000..55c6f87a --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuiadminschedqueue.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# "$Id: wuiadminschedqueue.py $" + +""" +Test Manager WUI - Admin - Scheduling Queue. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Validation Kit imports +from testmanager.webui.wuicontentbase import WuiListContentBase + + +class WuiAdminSchedQueueList(WuiListContentBase): + """ + WUI Scheduling Queue Content Generator. + """ + def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, aiSelectedSortColumns = None): + tsEffective = None; # Not relevant, no history on the scheduling queue. + WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, 'Scheduling Queue', + fnDPrint = fnDPrint, oDisp = oDisp, aiSelectedSortColumns = aiSelectedSortColumns, + fTimeNavigation = False); + self._asColumnHeaders = [ + 'Last Run', 'Scheduling Group', 'Test Group', 'Test Case', 'Config State', 'Item ID' + ]; + self._asColumnAttribs = [ + 'align="center"', 'align="center"', 'align="center"', 'align="center"', 'align="center"', 'align="center"' + ]; + self._iPrevPerSchedGroupRowNumber = 0; + + def _formatListEntry(self, iEntry): + oEntry = self._aoEntries[iEntry] # type: SchedQueueEntry + sState = 'up-to-date' if oEntry.fUpToDate else 'outdated'; + return [ oEntry.tsLastScheduled, oEntry.sSchedGroup, oEntry.sTestGroup, oEntry.sTestCase, sState, oEntry.idItem ]; + + def _formatListEntryHtml(self, iEntry): + sHtml = WuiListContentBase._formatListEntryHtml(self, iEntry); + + # Insert separator row? + if iEntry < len(self._aoEntries): + oEntry = self._aoEntries[iEntry] # type: SchedQueueEntry + if oEntry.iPerSchedGroupRowNumber != self._iPrevPerSchedGroupRowNumber: + if iEntry > 0 and iEntry + 1 < min(len(self._aoEntries), self._cItemsPerPage): + sHtml += '<tr class="tmseparator"><td colspan=%s> </td></tr>\n' % (len(self._asColumnHeaders),); + self._iPrevPerSchedGroupRowNumber = oEntry.iPerSchedGroupRowNumber; + return sHtml; + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuiadminsystemchangelog.py b/src/VBox/ValidationKit/testmanager/webui/wuiadminsystemchangelog.py new file mode 100755 index 00000000..3ad182e2 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuiadminsystemchangelog.py @@ -0,0 +1,447 @@ +# -*- coding: utf-8 -*- +# $Id: wuiadminsystemchangelog.py $ + +""" +Test Manager WUI - Admin - System changelog. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +from common import webutils; + +# Validation Kit imports. +from testmanager.webui.wuicontentbase import WuiListContentBase, WuiHtmlKeeper, WuiAdminLink, \ + WuiMainLink, WuiElementText, WuiHtmlBase; + +from testmanager.core.base import AttributeChangeEntryPre; +from testmanager.core.buildblacklist import BuildBlacklistLogic, BuildBlacklistData; +from testmanager.core.build import BuildLogic, BuildData; +from testmanager.core.buildsource import BuildSourceLogic, BuildSourceData; +from testmanager.core.globalresource import GlobalResourceLogic, GlobalResourceData; +from testmanager.core.failurecategory import FailureCategoryLogic, FailureCategoryData; +from testmanager.core.failurereason import FailureReasonLogic, FailureReasonData; +from testmanager.core.systemlog import SystemLogData; +from testmanager.core.systemchangelog import SystemChangelogLogic; +from testmanager.core.schedgroup import SchedGroupLogic, SchedGroupData; +from testmanager.core.testbox import TestBoxLogic, TestBoxData; +from testmanager.core.testcase import TestCaseLogic, TestCaseData; +from testmanager.core.testgroup import TestGroupLogic, TestGroupData; +from testmanager.core.testset import TestSetData; +from testmanager.core.useraccount import UserAccountLogic, UserAccountData; + + +class WuiAdminSystemChangelogList(WuiListContentBase): + """ + WUI System Changelog Content Generator. + """ + + def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, cDaysBack, aiSelectedSortColumns = None): + WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, 'System Changelog', + fnDPrint = fnDPrint, oDisp = oDisp, aiSelectedSortColumns = aiSelectedSortColumns); + self._asColumnHeaders = [ 'When', 'User', 'Event', 'Details' ]; + self._asColumnAttribs = [ 'align="center"', 'align="center"', '', '' ]; + self._oBuildBlacklistLogic = BuildBlacklistLogic(oDisp.getDb()); + self._oBuildLogic = BuildLogic(oDisp.getDb()); + self._oBuildSourceLogic = BuildSourceLogic(oDisp.getDb()); + self._oFailureCategoryLogic = FailureCategoryLogic(oDisp.getDb()); + self._oFailureReasonLogic = FailureReasonLogic(oDisp.getDb()); + self._oGlobalResourceLogic = GlobalResourceLogic(oDisp.getDb()); + self._oSchedGroupLogic = SchedGroupLogic(oDisp.getDb()); + self._oTestBoxLogic = TestBoxLogic(oDisp.getDb()); + self._oTestCaseLogic = TestCaseLogic(oDisp.getDb()); + self._oTestGroupLogic = TestGroupLogic(oDisp.getDb()); + self._oUserAccountLogic = UserAccountLogic(oDisp.getDb()); + self._sPrevDate = ''; + _ = cDaysBack; + + # oDetails = self._createBlacklistingDetailsLink(oEntry.idWhat, oEntry.tsEffective); + def _createBlacklistingDetailsLink(self, idBlacklisting, tsEffective): + """ Creates a link to the build source details. """ + oBlacklisting = self._oBuildBlacklistLogic.cachedLookup(idBlacklisting); + if oBlacklisting is not None: + from testmanager.webui.wuiadmin import WuiAdmin; + return WuiAdminLink('Blacklisting #%u' % (oBlacklisting.idBlacklisting,), + WuiAdmin.ksActionBuildBlacklistDetails, tsEffective, + { BuildBlacklistData.ksParam_idBlacklisting: oBlacklisting.idBlacklisting }, + fBracketed = False); + return WuiElementText('[blacklisting #%u not found]' % (idBlacklisting,)); + + def _createBuildDetailsLink(self, idBuild, tsEffective): + """ Creates a link to the build details. """ + oBuild = self._oBuildLogic.cachedLookup(idBuild); + if oBuild is not None: + from testmanager.webui.wuiadmin import WuiAdmin; + return WuiAdminLink('%s %sr%u' % ( oBuild.oCat.sProduct, oBuild.sVersion, oBuild.iRevision), + WuiAdmin.ksActionBuildDetails, tsEffective, + { BuildData.ksParam_idBuild: oBuild.idBuild }, + fBracketed = False, + sTitle = 'build #%u for %s, type %s' + % (oBuild.idBuild, ' & '.join(oBuild.oCat.asOsArches), oBuild.oCat.sType)); + return WuiElementText('[build #%u not found]' % (idBuild,)); + + def _createBuildSourceDetailsLink(self, idBuildSrc, tsEffective): + """ Creates a link to the build source details. """ + oBuildSource = self._oBuildSourceLogic.cachedLookup(idBuildSrc); + if oBuildSource is not None: + from testmanager.webui.wuiadmin import WuiAdmin; + return WuiAdminLink(oBuildSource.sName, WuiAdmin.ksActionBuildSrcDetails, tsEffective, + { BuildSourceData.ksParam_idBuildSrc: oBuildSource.idBuildSrc }, + fBracketed = False, + sTitle = 'Build source #%u' % (oBuildSource.idBuildSrc,)); + return WuiElementText('[build source #%u not found]' % (idBuildSrc,)); + + def _createFailureCategoryDetailsLink(self, idFailureCategory, tsEffective): + """ Creates a link to the failure category details. """ + oFailureCategory = self._oFailureCategoryLogic.cachedLookup(idFailureCategory); + if oFailureCategory is not None: + from testmanager.webui.wuiadmin import WuiAdmin; + return WuiAdminLink(oFailureCategory.sShort, WuiAdmin.ksActionFailureCategoryDetails, tsEffective, + { FailureCategoryData.ksParam_idFailureCategory: oFailureCategory.idFailureCategory }, + fBracketed = False, + sTitle = 'Failure category #%u' % (oFailureCategory.idFailureCategory,)); + return WuiElementText('[failure category #%u not found]' % (idFailureCategory,)); + + def _createFailureReasonDetailsLink(self, idFailureReason, tsEffective): + """ Creates a link to the failure reason details. """ + oFailureReason = self._oFailureReasonLogic.cachedLookup(idFailureReason); + if oFailureReason is not None: + from testmanager.webui.wuiadmin import WuiAdmin; + return WuiAdminLink(oFailureReason.sShort, WuiAdmin.ksActionFailureReasonDetails, tsEffective, + { FailureReasonData.ksParam_idFailureReason: oFailureReason.idFailureReason }, + fBracketed = False, + sTitle = 'Failure reason #%u, category %s' + % (oFailureReason.idFailureReason, oFailureReason.oCategory.sShort)); + return WuiElementText('[failure reason #%u not found]' % (idFailureReason,)); + + def _createGlobalResourceDetailsLink(self, idGlobalRsrc, tsEffective): + """ Creates a link to the global resource details. """ + oGlobalResource = self._oGlobalResourceLogic.cachedLookup(idGlobalRsrc); + if oGlobalResource is not None: + return WuiAdminLink(oGlobalResource.sName, '@todo', tsEffective, + { GlobalResourceData.ksParam_idGlobalRsrc: oGlobalResource.idGlobalRsrc }, + fBracketed = False, + sTitle = 'Global resource #%u' % (oGlobalResource.idGlobalRsrc,)); + return WuiElementText('[global resource #%u not found]' % (idGlobalRsrc,)); + + def _createSchedGroupDetailsLink(self, idSchedGroup, tsEffective): + """ Creates a link to the scheduling group details. """ + oSchedGroup = self._oSchedGroupLogic.cachedLookup(idSchedGroup); + if oSchedGroup is not None: + from testmanager.webui.wuiadmin import WuiAdmin; + return WuiAdminLink(oSchedGroup.sName, WuiAdmin.ksActionSchedGroupDetails, tsEffective, + { SchedGroupData.ksParam_idSchedGroup: oSchedGroup.idSchedGroup }, + fBracketed = False, + sTitle = 'Scheduling group #%u' % (oSchedGroup.idSchedGroup,)); + return WuiElementText('[scheduling group #%u not found]' % (idSchedGroup,)); + + def _createTestBoxDetailsLink(self, idTestBox, tsEffective): + """ Creates a link to the testbox details. """ + oTestBox = self._oTestBoxLogic.cachedLookup(idTestBox); + if oTestBox is not None: + from testmanager.webui.wuiadmin import WuiAdmin; + return WuiAdminLink(oTestBox.sName, WuiAdmin.ksActionTestBoxDetails, tsEffective, + { TestBoxData.ksParam_idTestBox: oTestBox.idTestBox }, + fBracketed = False, sTitle = 'Testbox #%u' % (oTestBox.idTestBox,)); + return WuiElementText('[testbox #%u not found]' % (idTestBox,)); + + def _createTestCaseDetailsLink(self, idTestCase, tsEffective): + """ Creates a link to the test case details. """ + oTestCase = self._oTestCaseLogic.cachedLookup(idTestCase); + if oTestCase is not None: + from testmanager.webui.wuiadmin import WuiAdmin; + return WuiAdminLink(oTestCase.sName, WuiAdmin.ksActionTestCaseDetails, tsEffective, + { TestCaseData.ksParam_idTestCase: oTestCase.idTestCase }, + fBracketed = False, sTitle = 'Test case #%u' % (oTestCase.idTestCase,)); + return WuiElementText('[test case #%u not found]' % (idTestCase,)); + + def _createTestGroupDetailsLink(self, idTestGroup, tsEffective): + """ Creates a link to the test group details. """ + oTestGroup = self._oTestGroupLogic.cachedLookup(idTestGroup); + if oTestGroup is not None: + from testmanager.webui.wuiadmin import WuiAdmin; + return WuiAdminLink(oTestGroup.sName, WuiAdmin.ksActionTestGroupDetails, tsEffective, + { TestGroupData.ksParam_idTestGroup: oTestGroup.idTestGroup }, + fBracketed = False, sTitle = 'Test group #%u' % (oTestGroup.idTestGroup,)); + return WuiElementText('[test group #%u not found]' % (idTestGroup,)); + + def _createTestSetResultsDetailsLink(self, idTestSet, tsEffective): + """ Creates a link to the test set results. """ + _ = tsEffective; + from testmanager.webui.wuimain import WuiMain; + return WuiMainLink('test set #%u' % idTestSet, WuiMain.ksActionTestSetDetails, + { TestSetData.ksParam_idTestSet: idTestSet }, fBracketed = False); + + def _createTestSetDetailsLinkByResult(self, idTestResult, tsEffective): + """ Creates a link to the test set results. """ + _ = tsEffective; + from testmanager.webui.wuimain import WuiMain; + return WuiMainLink('test result #%u' % idTestResult, WuiMain.ksActionTestSetDetailsFromResult, + { TestSetData.ksParam_idTestResult: idTestResult }, fBracketed = False); + + def _createUserAccountDetailsLink(self, uid, tsEffective): + """ Creates a link to the user account details. """ + oUser = self._oUserAccountLogic.cachedLookup(uid); + if oUser is not None: + return WuiAdminLink(oUser.sUsername, '@todo', tsEffective, { UserAccountData.ksParam_uid: oUser.uid }, + fBracketed = False, sTitle = '%s (#%u)' % (oUser.sFullName, oUser.uid)); + return WuiElementText('[user #%u not found]' % (uid,)); + + def _formatDescGeneric(self, sDesc, oEntry): + """ + Generically format system log the description. + """ + oRet = WuiHtmlKeeper(); + asWords = sDesc.split(); + for sWord in asWords: + offEqual = sWord.find('='); + if offEqual > 0: + sKey = sWord[:offEqual]; + try: idValue = int(sWord[offEqual+1:].rstrip('.,')); + except: pass; + else: + if sKey == 'idTestSet': + oRet.append(self._createTestSetResultsDetailsLink(idValue, oEntry.tsEffective)); + continue; + if sKey == 'idTestBox': + oRet.append(self._createTestBoxDetailsLink(idValue, oEntry.tsEffective)); + continue; + if sKey == 'idSchedGroup': + oRet.append(self._createSchedGroupDetailsLink(idValue, oEntry.tsEffective)); + continue; + + oRet.append(WuiElementText(sWord)); + return oRet; + + def _formatListEntryHtml(self, iEntry): # pylint: disable=too-many-statements + """ + Overridden parent method. + """ + oEntry = self._aoEntries[iEntry]; + sRowClass = 'tmodd' if (iEntry + 1) & 1 else 'tmeven'; + sHtml = u''; + + # + # Format the timestamp. + # + sDate = self.formatTsShort(oEntry.tsEffective); + if sDate[:10] != self._sPrevDate: + self._sPrevDate = sDate[:10]; + sHtml += ' <tr class="%s tmdaterow" align="left"><td colspan="7">%s</td></tr>\n' % (sRowClass, sDate[:10],); + sDate = sDate[11:] + + # + # System log events. + # pylint: disable=redefined-variable-type + # + aoChanges = None; + if oEntry.sEvent == SystemLogData.ksEvent_CmdNacked: + sEvent = 'Command not acknowleged'; + oDetails = oEntry.sDesc; + + elif oEntry.sEvent == SystemLogData.ksEvent_TestBoxUnknown: + sEvent = 'Unknown testbox'; + oDetails = oEntry.sDesc; + + elif oEntry.sEvent == SystemLogData.ksEvent_TestSetAbandoned: + sEvent = 'Abandoned ' if oEntry.sDesc.startswith('idTestSet') else 'Abandoned test set'; + oDetails = self._formatDescGeneric(oEntry.sDesc, oEntry); + + elif oEntry.sEvent == SystemLogData.ksEvent_UserAccountUnknown: + sEvent = 'Unknown user account'; + oDetails = oEntry.sDesc; + + elif oEntry.sEvent == SystemLogData.ksEvent_XmlResultMalformed: + sEvent = 'Malformed XML result'; + oDetails = oEntry.sDesc; + + elif oEntry.sEvent == SystemLogData.ksEvent_SchedQueueRecreate: + sEvent = 'Recreating scheduling queue'; + asWords = oEntry.sDesc.split(); + if len(asWords) > 3 and asWords[0] == 'User' and asWords[1][0] == '#': + try: idAuthor = int(asWords[1][1:]); + except: pass; + else: + oEntry.oAuthor = self._oUserAccountLogic.cachedLookup(idAuthor); + if oEntry.oAuthor is not None: + i = 2; + if asWords[i] == 'recreated': i += 1; + oEntry.sDesc = ' '.join(asWords[i:]); + oDetails = self._formatDescGeneric(oEntry.sDesc.replace('sched queue #', 'for scheduling group idSchedGroup='), + oEntry); + # + # System changelog events. + # + elif oEntry.sEvent == SystemChangelogLogic.ksWhat_Blacklisting: + sEvent = 'Modified blacklisting'; + oDetails = self._createBlacklistingDetailsLink(oEntry.idWhat, oEntry.tsEffective); + + elif oEntry.sEvent == SystemChangelogLogic.ksWhat_Build: + sEvent = 'Modified build'; + oDetails = self._createBuildDetailsLink(oEntry.idWhat, oEntry.tsEffective); + + elif oEntry.sEvent == SystemChangelogLogic.ksWhat_BuildSource: + sEvent = 'Modified build source'; + oDetails = self._createBuildSourceDetailsLink(oEntry.idWhat, oEntry.tsEffective); + + elif oEntry.sEvent == SystemChangelogLogic.ksWhat_GlobalRsrc: + sEvent = 'Modified global resource'; + oDetails = self._createGlobalResourceDetailsLink(oEntry.idWhat, oEntry.tsEffective); + + elif oEntry.sEvent == SystemChangelogLogic.ksWhat_FailureCategory: + sEvent = 'Modified failure category'; + oDetails = self._createFailureCategoryDetailsLink(oEntry.idWhat, oEntry.tsEffective); + (aoChanges, _) = self._oFailureCategoryLogic.fetchForChangeLog(oEntry.idWhat, 0, 1, oEntry.tsEffective); + + elif oEntry.sEvent == SystemChangelogLogic.ksWhat_FailureReason: + sEvent = 'Modified failure reason'; + oDetails = self._createFailureReasonDetailsLink(oEntry.idWhat, oEntry.tsEffective); + (aoChanges, _) = self._oFailureReasonLogic.fetchForChangeLog(oEntry.idWhat, 0, 1, oEntry.tsEffective); + + elif oEntry.sEvent == SystemChangelogLogic.ksWhat_SchedGroup: + sEvent = 'Modified scheduling group'; + oDetails = self._createSchedGroupDetailsLink(oEntry.idWhat, oEntry.tsEffective); + + elif oEntry.sEvent == SystemChangelogLogic.ksWhat_TestBox: + sEvent = 'Modified testbox'; + oDetails = self._createTestBoxDetailsLink(oEntry.idWhat, oEntry.tsEffective); + (aoChanges, _) = self._oTestBoxLogic.fetchForChangeLog(oEntry.idWhat, 0, 1, oEntry.tsEffective); + + elif oEntry.sEvent == SystemChangelogLogic.ksWhat_TestCase: + sEvent = 'Modified test case'; + oDetails = self._createTestCaseDetailsLink(oEntry.idWhat, oEntry.tsEffective); + (aoChanges, _) = self._oTestCaseLogic.fetchForChangeLog(oEntry.idWhat, 0, 1, oEntry.tsEffective); + + elif oEntry.sEvent == SystemChangelogLogic.ksWhat_TestGroup: + sEvent = 'Modified test group'; + oDetails = self._createTestGroupDetailsLink(oEntry.idWhat, oEntry.tsEffective); + + elif oEntry.sEvent == SystemChangelogLogic.ksWhat_TestResult: + sEvent = 'Modified test failure reason'; + oDetails = self._createTestSetDetailsLinkByResult(oEntry.idWhat, oEntry.tsEffective); + + elif oEntry.sEvent == SystemChangelogLogic.ksWhat_User: + sEvent = 'Modified user account'; + oDetails = self._createUserAccountDetailsLink(oEntry.idWhat, oEntry.tsEffective); + + else: + sEvent = '%s(%s)' % (oEntry.sEvent, oEntry.idWhat,); + oDetails = '!Unknown event!' + (oEntry.sDesc if oEntry.sDesc else ''); + + # + # Do the formatting. + # + + if aoChanges: + oChangeEntry = aoChanges[0]; + cAttribsChanged = len(oChangeEntry.aoChanges) + 1; + if oChangeEntry.oOldRaw is None and sEvent.startswith('Modified '): + sEvent = 'Created ' + sEvent[9:]; + + else: + oChangeEntry = None; + cAttribsChanged = -1; + + sHtml += u' <tr class="%s">\n' \ + u' <td rowspan="%d" align="center" >%s</td>\n' \ + u' <td rowspan="%d" align="center" >%s</td>\n' \ + u' <td colspan="5" class="%s%s">%s %s</td>\n' \ + u' </tr>\n' \ + % ( sRowClass, + 1 + cAttribsChanged + 1, sDate, + 1 + cAttribsChanged + 1, webutils.escapeElem(oEntry.oAuthor.sUsername if oEntry.oAuthor is not None else ''), + sRowClass, ' tmsyschlogevent' if oChangeEntry is not None else '', webutils.escapeElem(sEvent), + oDetails.toHtml() if isinstance(oDetails, WuiHtmlBase) else oDetails, + ); + + if oChangeEntry is not None: + sHtml += u' <tr class="%s tmsyschlogspacerrowabove">\n' \ + u' <td xrowspan="%d" style="border-right: 0px; border-bottom: 0px;"></td>\n' \ + u' <td colspan="3" style="border-right: 0px;"></td>\n' \ + u' <td rowspan="%d" class="%s tmsyschlogspacer"></td>\n' \ + u' </tr>\n' \ + % (sRowClass, cAttribsChanged + 1, cAttribsChanged + 1, sRowClass); + for j, oChange in enumerate(oChangeEntry.aoChanges): + fLastRow = j + 1 == len(oChangeEntry.aoChanges); + sHtml += u' <tr class="%s%s tmsyschlogattr%s">\n' \ + % ( sRowClass, 'odd' if j & 1 else 'even', ' tmsyschlogattrfinal' if fLastRow else '',); + if j == 0: + sHtml += u' <td class="%s tmsyschlogspacer" rowspan="%d"></td>\n' % (sRowClass, cAttribsChanged - 1,); + + if isinstance(oChange, AttributeChangeEntryPre): + sHtml += u' <td class="%s%s">%s</td>\n' \ + u' <td><div class="tdpre"><pre>%s</pre></div></td>\n' \ + u' <td class="%s%s"><div class="tdpre"><pre>%s</pre></div></td>\n' \ + % ( ' tmtopleft' if j == 0 else '', ' tmbottomleft' if fLastRow else '', + webutils.escapeElem(oChange.sAttr), + webutils.escapeElem(oChange.sOldText), + ' tmtopright' if j == 0 else '', ' tmbottomright' if fLastRow else '', + webutils.escapeElem(oChange.sNewText), ); + else: + sHtml += u' <td class="%s%s">%s</td>\n' \ + u' <td>%s</td>\n' \ + u' <td class="%s%s">%s</td>\n' \ + % ( ' tmtopleft' if j == 0 else '', ' tmbottomleft' if fLastRow else '', + webutils.escapeElem(oChange.sAttr), + webutils.escapeElem(oChange.sOldText), + ' tmtopright' if j == 0 else '', ' tmbottomright' if fLastRow else '', + webutils.escapeElem(oChange.sNewText), ); + sHtml += u' </tr>\n'; + + if oChangeEntry is not None: + sHtml += u' <tr class="%s tmsyschlogspacerrowbelow "><td colspan="5"></td></tr>\n\n' % (sRowClass,); + return sHtml; + + + def _generateTableHeaders(self): + """ + Overridden parent method. + """ + + sHtml = u'<thead class="tmheader">\n' \ + u' <tr>\n' \ + u' <th rowspan="2">When</th>\n' \ + u' <th rowspan="2">Who</th>\n' \ + u' <th colspan="5">Event</th>\n' \ + u' </tr>\n' \ + u' <tr>\n' \ + u' <th style="border-right: 0px;"></th>\n' \ + u' <th>Attribute</th>\n' \ + u' <th>Old</th>\n' \ + u' <th style="border-right: 0px;">New</th>\n' \ + u' <th></th>\n' \ + u' </tr>\n' \ + u'</thead>\n'; + return sHtml; + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuiadminsystemdbdump.py b/src/VBox/ValidationKit/testmanager/webui/wuiadminsystemdbdump.py new file mode 100755 index 00000000..15a9d270 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuiadminsystemdbdump.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# $Id: wuiadminsystemdbdump.py $ + +""" +Test Manager WUI - System DB - Partial Dumping +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Validation Kit imports. +from testmanager.core.base import ModelDataBase; +from testmanager.webui.wuicontentbase import WuiFormContentBase; + + +class WuiAdminSystemDbDumpForm(WuiFormContentBase): + """ + WUI Partial DB Dump HTML content generator. + """ + + def __init__(self, cDaysBack, oDisp): + WuiFormContentBase.__init__(self, ModelDataBase(), + WuiFormContentBase.ksMode_Edit, 'DbDump', oDisp, 'Partial DB Dump', + sSubmitAction = oDisp.ksActionSystemDbDumpDownload); + self._cDaysBack = cDaysBack; + + def _generateTopRowFormActions(self, oData): + _ = oData; + return []; + + def _populateForm(self, oForm, oData): # type: (WuiHlpForm, SchedGroupDataEx) -> bool + """ + Construct an HTML form + """ + _ = oData; + + oForm.addInt(self._oDisp.ksParamDaysBack, self._cDaysBack, 'How many days back to dump'); + oForm.addSubmit('Produce & Download'); + + return True; + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuiadminsystemlog.py b/src/VBox/ValidationKit/testmanager/webui/wuiadminsystemlog.py new file mode 100755 index 00000000..9ee58df6 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuiadminsystemlog.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +# $Id: wuiadminsystemlog.py $ + +""" +Test Manager WUI - Admin - System Log. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Validation Kit imports. +from testmanager.webui.wuicontentbase import WuiListContentBase, WuiTmLink; +from testmanager.core.testbox import TestBoxData; +from testmanager.core.systemlog import SystemLogData; +from testmanager.core.useraccount import UserAccountData; + + +class WuiAdminSystemLogList(WuiListContentBase): + """ + WUI System Log Content Generator. + """ + + def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, aiSelectedSortColumns = None): + WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, 'System Log', + fnDPrint = fnDPrint, oDisp = oDisp, aiSelectedSortColumns = aiSelectedSortColumns); + self._asColumnHeaders = ['Date', 'Event', 'Message', 'Action']; + self._asColumnAttribs = ['', '', '', 'align="center"']; + + def _formatListEntry(self, iEntry): + from testmanager.webui.wuiadmin import WuiAdmin; + oEntry = self._aoEntries[iEntry]; + + oAction = None + + if self._oDisp is None or not self._oDisp.isReadOnlyUser(): + if oEntry.sEvent == SystemLogData.ksEvent_TestBoxUnknown \ + and oEntry.sLogText.find('addr=') >= 0 \ + and oEntry.sLogText.find('uuid=') >= 0: + sUuid = (oEntry.sLogText[(oEntry.sLogText.find('uuid=') + 5):])[:36]; + sAddr = (oEntry.sLogText[(oEntry.sLogText.find('addr=') + 5):]).split(' ')[0]; + oAction = WuiTmLink('Add TestBox', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxAdd, + TestBoxData.ksParam_uuidSystem: sUuid, + TestBoxData.ksParam_ip: sAddr }); + + elif oEntry.sEvent == SystemLogData.ksEvent_UserAccountUnknown: + sUserName = oEntry.sLogText[oEntry.sLogText.find('(') + 1: + oEntry.sLogText.find(')')] + oAction = WuiTmLink('Add User', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionUserAdd, + UserAccountData.ksParam_sLoginName: sUserName }); + + return [oEntry.tsCreated, oEntry.sEvent, oEntry.sLogText, oAction]; + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuiadmintestbox.py b/src/VBox/ValidationKit/testmanager/webui/wuiadmintestbox.py new file mode 100755 index 00000000..c8eb4766 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuiadmintestbox.py @@ -0,0 +1,490 @@ +# -*- coding: utf-8 -*- +# $Id: wuiadmintestbox.py $ + +""" +Test Manager WUI - TestBox. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard python imports. +import socket; + +# Validation Kit imports. +from common import utils, webutils; +from testmanager.webui.wuicontentbase import WuiContentBase, WuiListContentWithActionBase, WuiFormContentBase, WuiLinkBase, \ + WuiSvnLink, WuiTmLink, WuiSpanText, WuiRawHtml; +from testmanager.core.db import TMDatabaseConnection; +from testmanager.core.schedgroup import SchedGroupLogic, SchedGroupData; +from testmanager.core.testbox import TestBoxData, TestBoxDataEx, TestBoxLogic; +from testmanager.core.testset import TestSetData; +from testmanager.core.db import isDbTimestampInfinity; + + + +class WuiTestBoxDetailsLinkById(WuiTmLink): + """ Test box details link by ID. """ + + def __init__(self, idTestBox, sName = WuiContentBase.ksShortDetailsLink, fBracketed = False, tsNow = None, sTitle = None): + from testmanager.webui.wuiadmin import WuiAdmin; + dParams = { + WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxDetails, + TestBoxData.ksParam_idTestBox: idTestBox, + }; + if tsNow is not None: + dParams[WuiAdmin.ksParamEffectiveDate] = tsNow; ## ?? + WuiTmLink.__init__(self, sName, WuiAdmin.ksScriptName, dParams, fBracketed = fBracketed, sTitle = sTitle); + self.idTestBox = idTestBox; + + +class WuiTestBoxDetailsLink(WuiTestBoxDetailsLinkById): + """ Test box details link by TestBoxData instance. """ + + def __init__(self, oTestBox, sName = None, fBracketed = False, tsNow = None): # (TestBoxData, str, bool, Any) -> None + WuiTestBoxDetailsLinkById.__init__(self, oTestBox.idTestBox, + sName if sName else oTestBox.sName, + fBracketed = fBracketed, + tsNow = tsNow, + sTitle = self.formatTitleText(oTestBox)); + self.oTestBox = oTestBox; + + @staticmethod + def formatTitleText(oTestBox): # (TestBoxData) -> str + """ + Formats the title text for a TestBoxData object. + """ + + # Note! Somewhat similar code is found in testresults.py + + # + # Collect field/value tuples. + # + aasTestBoxTitle = [ + (u'Identifier:', '#%u' % (oTestBox.idTestBox,),), + (u'Name:', oTestBox.sName,), + ]; + if oTestBox.sCpuVendor: + aasTestBoxTitle.append((u'CPU\u00a0vendor:', oTestBox.sCpuVendor, )); + if oTestBox.sCpuName: + aasTestBoxTitle.append((u'CPU\u00a0name:', u'\u00a0'.join(oTestBox.sCpuName.split()),)); + if oTestBox.cCpus: + aasTestBoxTitle.append((u'CPU\u00a0threads:', u'%s' % ( oTestBox.cCpus, ),)); + + asFeatures = []; + if oTestBox.fCpuHwVirt is True: + if oTestBox.sCpuVendor is None: + asFeatures.append(u'HW\u2011Virt'); + elif oTestBox.sCpuVendor in ['AuthenticAMD',]: + asFeatures.append(u'HW\u2011Virt(AMD\u2011V)'); + else: + asFeatures.append(u'HW\u2011Virt(VT\u2011x)'); + if oTestBox.fCpuNestedPaging is True: asFeatures.append(u'Nested\u2011Paging'); + if oTestBox.fCpu64BitGuest is True: asFeatures.append(u'64\u2011bit\u2011Guest'); + if oTestBox.fChipsetIoMmu is True: asFeatures.append(u'I/O\u2011MMU'); + aasTestBoxTitle.append((u'CPU\u00a0features:', u',\u00a0'.join(asFeatures),)); + + if oTestBox.cMbMemory: + aasTestBoxTitle.append((u'System\u00a0RAM:', u'%s MiB' % ( oTestBox.cMbMemory, ),)); + if oTestBox.sOs: + aasTestBoxTitle.append((u'OS:', oTestBox.sOs, )); + if oTestBox.sCpuArch: + aasTestBoxTitle.append((u'OS\u00a0arch:', oTestBox.sCpuArch,)); + if oTestBox.sOsVersion: + aasTestBoxTitle.append((u'OS\u00a0version:', u'\u00a0'.join(oTestBox.sOsVersion.split()),)); + if oTestBox.ip: + aasTestBoxTitle.append((u'IP\u00a0address:', u'%s' % ( oTestBox.ip, ),)); + + # + # Do a guestimation of the max field name width and pad short + # names when constructing the title text lines. + # + cchMaxWidth = 0; + for sEntry, _ in aasTestBoxTitle: + cchMaxWidth = max(WuiTestBoxDetailsLink.estimateStringWidth(sEntry), cchMaxWidth); + asTestBoxTitle = []; + for sEntry, sValue in aasTestBoxTitle: + asTestBoxTitle.append(u'%s%s\t\t%s' + % (sEntry, WuiTestBoxDetailsLink.getStringWidthPadding(sEntry, cchMaxWidth), sValue)); + + return u'\n'.join(asTestBoxTitle); + + +class WuiTestBoxDetailsLinkShort(WuiTestBoxDetailsLink): + """ Test box details link by TestBoxData instance, but with ksShortDetailsLink as default name. """ + + def __init__(self, oTestBox, sName = WuiContentBase.ksShortDetailsLink, fBracketed = False, + tsNow = None): # (TestBoxData, str, bool, Any) -> None + WuiTestBoxDetailsLink.__init__(self, oTestBox, sName = sName, fBracketed = fBracketed, tsNow = tsNow); + + +class WuiTestBox(WuiFormContentBase): + """ + WUI TestBox Form Content Generator. + """ + + def __init__(self, oData, sMode, oDisp): + if sMode == WuiFormContentBase.ksMode_Add: + sTitle = 'Create TextBox'; + if oData.uuidSystem is not None and len(oData.uuidSystem) > 10: + sTitle += ' - ' + oData.uuidSystem; + elif sMode == WuiFormContentBase.ksMode_Edit: + sTitle = 'Edit TestBox - %s (#%s)' % (oData.sName, oData.idTestBox); + else: + assert sMode == WuiFormContentBase.ksMode_Show; + sTitle = 'TestBox - %s (#%s)' % (oData.sName, oData.idTestBox); + WuiFormContentBase.__init__(self, oData, sMode, 'TestBox', oDisp, sTitle); + + # Try enter sName as hostname (no domain) when creating the testbox. + if sMode == WuiFormContentBase.ksMode_Add \ + and self._oData.sName in [None, ''] \ + and self._oData.ip not in [None, '']: + try: + (self._oData.sName, _, _) = socket.gethostbyaddr(self._oData.ip); + except: + pass; + offDot = self._oData.sName.find('.'); + if offDot > 0: + self._oData.sName = self._oData.sName[:offDot]; + + + def _populateForm(self, oForm, oData): + oForm.addIntRO( TestBoxData.ksParam_idTestBox, oData.idTestBox, 'TestBox ID'); + oForm.addIntRO( TestBoxData.ksParam_idGenTestBox, oData.idGenTestBox, 'TestBox generation ID'); + oForm.addTimestampRO(TestBoxData.ksParam_tsEffective, oData.tsEffective, 'Last changed'); + oForm.addTimestampRO(TestBoxData.ksParam_tsExpire, oData.tsExpire, 'Expires (excl)'); + oForm.addIntRO( TestBoxData.ksParam_uidAuthor, oData.uidAuthor, 'Changed by UID'); + + oForm.addText( TestBoxData.ksParam_ip, oData.ip, 'TestBox IP Address'); ## make read only?? + oForm.addUuid( TestBoxData.ksParam_uuidSystem, oData.uuidSystem, 'TestBox System/Firmware UUID'); + oForm.addText( TestBoxData.ksParam_sName, oData.sName, 'TestBox Name'); + oForm.addText( TestBoxData.ksParam_sDescription, oData.sDescription, 'TestBox Description'); + oForm.addCheckBox( TestBoxData.ksParam_fEnabled, oData.fEnabled, 'Enabled'); + oForm.addComboBox( TestBoxData.ksParam_enmLomKind, oData.enmLomKind, 'Lights-out-management', + TestBoxData.kaoLomKindDescs); + oForm.addText( TestBoxData.ksParam_ipLom, oData.ipLom, 'Lights-out-management IP Address'); + oForm.addInt( TestBoxData.ksParam_pctScaleTimeout, oData.pctScaleTimeout, 'Timeout scale factor (%)'); + + oForm.addListOfSchedGroupsForTestBox(TestBoxDataEx.ksParam_aoInSchedGroups, + oData.aoInSchedGroups, + SchedGroupLogic(TMDatabaseConnection()).fetchOrderedByName(), + 'Scheduling Group', oData.idTestBox); + # Command, comment and submit button. + if self._sMode == WuiFormContentBase.ksMode_Edit: + oForm.addComboBox(TestBoxData.ksParam_enmPendingCmd, oData.enmPendingCmd, 'Pending command', + TestBoxData.kaoTestBoxCmdDescs); + else: + oForm.addComboBoxRO(TestBoxData.ksParam_enmPendingCmd, oData.enmPendingCmd, 'Pending command', + TestBoxData.kaoTestBoxCmdDescs); + oForm.addMultilineText(TestBoxData.ksParam_sComment, oData.sComment, 'Comment'); + if self._sMode != WuiFormContentBase.ksMode_Show: + oForm.addSubmit('Create TestBox' if self._sMode == WuiFormContentBase.ksMode_Add else 'Change TestBox'); + + return True; + + + def _generatePostFormContent(self, oData): + from testmanager.webui.wuihlpform import WuiHlpForm; + + oForm = WuiHlpForm('testbox-machine-settable', '', fReadOnly = True); + oForm.addTextRO( TestBoxData.ksParam_sOs, oData.sOs, 'TestBox OS'); + oForm.addTextRO( TestBoxData.ksParam_sOsVersion, oData.sOsVersion, 'TestBox OS version'); + oForm.addTextRO( TestBoxData.ksParam_sCpuArch, oData.sCpuArch, 'TestBox OS kernel architecture'); + oForm.addTextRO( TestBoxData.ksParam_sCpuVendor, oData.sCpuVendor, 'TestBox CPU vendor'); + oForm.addTextRO( TestBoxData.ksParam_sCpuName, oData.sCpuName, 'TestBox CPU name'); + if oData.lCpuRevision: + oForm.addTextRO( TestBoxData.ksParam_lCpuRevision, '%#x' % (oData.lCpuRevision,), 'TestBox CPU revision', + sPostHtml = ' (family=%#x model=%#x stepping=%#x)' + % (oData.getCpuFamily(), oData.getCpuModel(), oData.getCpuStepping(),), + sSubClass = 'long'); + else: + oForm.addLongRO( TestBoxData.ksParam_lCpuRevision, oData.lCpuRevision, 'TestBox CPU revision'); + oForm.addIntRO( TestBoxData.ksParam_cCpus, oData.cCpus, 'Number of CPUs, cores and threads'); + oForm.addCheckBoxRO( TestBoxData.ksParam_fCpuHwVirt, oData.fCpuHwVirt, 'VT-x or AMD-V supported'); + oForm.addCheckBoxRO( TestBoxData.ksParam_fCpuNestedPaging, oData.fCpuNestedPaging, 'Nested paging supported'); + oForm.addCheckBoxRO( TestBoxData.ksParam_fCpu64BitGuest, oData.fCpu64BitGuest, '64-bit guest supported'); + oForm.addCheckBoxRO( TestBoxData.ksParam_fChipsetIoMmu, oData.fChipsetIoMmu, 'I/O MMU supported'); + oForm.addMultilineTextRO(TestBoxData.ksParam_sReport, oData.sReport, 'Hardware/software report'); + oForm.addLongRO( TestBoxData.ksParam_cMbMemory, oData.cMbMemory, 'Installed RAM size (MB)'); + oForm.addLongRO( TestBoxData.ksParam_cMbScratch, oData.cMbScratch, 'Available scratch space (MB)'); + oForm.addIntRO( TestBoxData.ksParam_iTestBoxScriptRev, oData.iTestBoxScriptRev, + 'TestBox Script SVN revision'); + sHexVer = oData.formatPythonVersion(); + oForm.addIntRO( TestBoxData.ksParam_iPythonHexVersion, oData.iPythonHexVersion, + 'Python version (hex)', sPostHtml = webutils.escapeElem(sHexVer)); + return [('Machine Only Settables', oForm.finalize()),]; + + + +class WuiTestBoxList(WuiListContentWithActionBase): + """ + WUI TestBox List Content Generator. + """ + + ## Descriptors for the combo box. + kasTestBoxActionDescs = \ + [ \ + [ 'none', 'Select an action...', '' ], + [ 'enable', 'Enable', '' ], + [ 'disable', 'Disable', '' ], + TestBoxData.kaoTestBoxCmdDescs[1], + TestBoxData.kaoTestBoxCmdDescs[2], + TestBoxData.kaoTestBoxCmdDescs[3], + TestBoxData.kaoTestBoxCmdDescs[4], + TestBoxData.kaoTestBoxCmdDescs[5], + ]; + + ## Boxes which doesn't report in for more than 15 min are considered dead. + kcSecMaxStatusDeltaAlive = 15*60 + + def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, aiSelectedSortColumns = None): + # type: (list[TestBoxDataForListing], int, int, datetime.datetime, ignore, WuiAdmin) -> None + WuiListContentWithActionBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, + sTitle = 'TestBoxes', sId = 'users', fnDPrint = fnDPrint, oDisp = oDisp, + aiSelectedSortColumns = aiSelectedSortColumns); + self._asColumnHeaders.extend([ 'Name', 'LOM', 'Status', 'Cmd', + 'Note', 'Script', 'Python', 'Group', + 'OS', 'CPU', 'Features', 'CPUs', 'RAM', 'Scratch', + 'Actions' ]); + self._asColumnAttribs.extend([ 'align="center"', 'align="center"', 'align="center"', 'align="center"' + 'align="center"', 'align="center"', 'align="center"', 'align="center"', + '', '', '', 'align="left"', 'align="right"', 'align="right"', 'align="right"', + 'align="center"' ]); + self._aaiColumnSorting.extend([ + (TestBoxLogic.kiSortColumn_sName,), + None, # LOM + (-TestBoxLogic.kiSortColumn_fEnabled, TestBoxLogic.kiSortColumn_enmState, -TestBoxLogic.kiSortColumn_tsUpdated,), + (TestBoxLogic.kiSortColumn_enmPendingCmd,), + None, # Note + (TestBoxLogic.kiSortColumn_iTestBoxScriptRev,), + (TestBoxLogic.kiSortColumn_iPythonHexVersion,), + None, # Group + (TestBoxLogic.kiSortColumn_sOs, TestBoxLogic.kiSortColumn_sOsVersion, TestBoxLogic.kiSortColumn_sCpuArch,), + (TestBoxLogic.kiSortColumn_sCpuVendor, TestBoxLogic.kiSortColumn_lCpuRevision,), + (TestBoxLogic.kiSortColumn_fCpuNestedPaging,), + (TestBoxLogic.kiSortColumn_cCpus,), + (TestBoxLogic.kiSortColumn_cMbMemory,), + (TestBoxLogic.kiSortColumn_cMbScratch,), + None, # Actions + ]); + assert len(self._aaiColumnSorting) == len(self._asColumnHeaders); + self._aoActions = list(self.kasTestBoxActionDescs); + self._sAction = oDisp.ksActionTestBoxListPost; + self._sCheckboxName = TestBoxData.ksParam_idTestBox; + + def show(self, fShowNavigation = True): + """ Adds some stats at the bottom of the page """ + (sTitle, sBody) = super(WuiTestBoxList, self).show(fShowNavigation); + + # Count boxes in interesting states. + if self._aoEntries: + cActive = 0; + cDead = 0; + for oTestBox in self._aoEntries: + if oTestBox.oStatus is not None: + oDelta = oTestBox.tsCurrent - oTestBox.oStatus.tsUpdated; + if oDelta.days <= 0 and oDelta.seconds <= self.kcSecMaxStatusDeltaAlive: + if oTestBox.fEnabled: + cActive += 1; + else: + cDead += 1; + else: + cDead += 1; + sBody += '<div id="testboxsummary"><p>\n' \ + '%s testboxes of which %s are active and %s dead' \ + '</p></div>\n' \ + % (len(self._aoEntries), cActive, cDead,) + return (sTitle, sBody); + + def _formatListEntry(self, iEntry): # pylint: disable=too-many-locals + from testmanager.webui.wuiadmin import WuiAdmin; + oEntry = self._aoEntries[iEntry]; + + # Lights outs managment. + if oEntry.enmLomKind == TestBoxData.ksLomKind_ILOM: + aoLom = [ WuiLinkBase('ILOM', 'https://%s/' % (oEntry.ipLom,), fBracketed = False), ]; + elif oEntry.enmLomKind == TestBoxData.ksLomKind_ELOM: + aoLom = [ WuiLinkBase('ELOM', 'http://%s/' % (oEntry.ipLom,), fBracketed = False), ]; + elif oEntry.enmLomKind == TestBoxData.ksLomKind_AppleXserveLom: + aoLom = [ 'Apple LOM' ]; + elif oEntry.enmLomKind == TestBoxData.ksLomKind_None: + aoLom = [ 'none' ]; + else: + aoLom = [ 'Unexpected enmLomKind value "%s"' % (oEntry.enmLomKind,) ]; + if oEntry.ipLom is not None: + if oEntry.enmLomKind in [ TestBoxData.ksLomKind_ILOM, TestBoxData.ksLomKind_ELOM ]: + aoLom += [ WuiLinkBase('(ssh)', 'ssh://%s' % (oEntry.ipLom,), fBracketed = False) ]; + aoLom += [ WuiRawHtml('<br>'), '%s' % (oEntry.ipLom,) ]; + + # State and Last seen. + if oEntry.oStatus is None: + oSeen = WuiSpanText('tmspan-offline', 'Never'); + oState = ''; + else: + oDelta = oEntry.tsCurrent - oEntry.oStatus.tsUpdated; + if oDelta.days <= 0 and oDelta.seconds <= self.kcSecMaxStatusDeltaAlive: + oSeen = WuiSpanText('tmspan-online', u'%s\u00a0s\u00a0ago' % (oDelta.days * 24 * 3600 + oDelta.seconds,)); + else: + oSeen = WuiSpanText('tmspan-offline', u'%s' % (self.formatTsShort(oEntry.oStatus.tsUpdated),)); + + if oEntry.oStatus.idTestSet is None: + oState = str(oEntry.oStatus.enmState); + else: + from testmanager.webui.wuimain import WuiMain; + oState = WuiTmLink(oEntry.oStatus.enmState, WuiMain.ksScriptName, # pylint: disable=redefined-variable-type + { WuiMain.ksParamAction: WuiMain.ksActionTestResultDetails, + TestSetData.ksParam_idTestSet: oEntry.oStatus.idTestSet, }, + sTitle = '#%u' % (oEntry.oStatus.idTestSet,), + fBracketed = False); + # Comment + oComment = self._formatCommentCell(oEntry.sComment); + + # Group links. + aoGroups = []; + for oInGroup in oEntry.aoInSchedGroups: + oSchedGroup = oInGroup.oSchedGroup; + aoGroups.append(WuiTmLink(oSchedGroup.sName, WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionSchedGroupEdit, + SchedGroupData.ksParam_idSchedGroup: oSchedGroup.idSchedGroup, }, + sTitle = '#%u' % (oSchedGroup.idSchedGroup,), + fBracketed = len(oEntry.aoInSchedGroups) > 1)); + + # Reformat the OS version to take less space. + aoOs = [ 'N/A' ]; + if oEntry.sOs is not None and oEntry.sOsVersion is not None and oEntry.sCpuArch: + sOsVersion = oEntry.sOsVersion; + if sOsVersion[0] not in [ 'v', 'V', 'r', 'R'] \ + and sOsVersion[0].isdigit() \ + and sOsVersion.find('.') in range(4) \ + and oEntry.sOs in [ 'linux', 'solaris', 'darwin', ]: + sOsVersion = 'v' + sOsVersion; + + sVer1 = sOsVersion; + sVer2 = None; + if oEntry.sOs in ('linux', 'darwin'): + iSep = sOsVersion.find(' / '); + if iSep > 0: + sVer1 = sOsVersion[:iSep].strip(); + sVer2 = sOsVersion[iSep + 3:].strip(); + sVer2 = sVer2.replace('Red Hat Enterprise Linux Server', 'RHEL'); + sVer2 = sVer2.replace('Oracle Linux Server', 'OL'); + elif oEntry.sOs == 'solaris': + iSep = sOsVersion.find(' ('); + if iSep > 0 and sOsVersion[-1] == ')': + sVer1 = sOsVersion[:iSep].strip(); + sVer2 = sOsVersion[iSep + 2:-1].strip(); + elif oEntry.sOs == 'win': + iSep = sOsVersion.find('build'); + if iSep > 0: + sVer1 = sOsVersion[:iSep].strip(); + sVer2 = 'B' + sOsVersion[iSep + 1:].strip(); + aoOs = [ + WuiSpanText('tmspan-osarch', u'%s.%s' % (oEntry.sOs, oEntry.sCpuArch,)), + WuiSpanText('tmspan-osver1', sVer1.replace('-', u'\u2011'),), + ]; + if sVer2 is not None: + aoOs += [ WuiRawHtml('<br>'), WuiSpanText('tmspan-osver2', sVer2.replace('-', u'\u2011')), ]; + + # Format the CPU revision. + oCpu = None; + if oEntry.lCpuRevision is not None and oEntry.sCpuVendor is not None and oEntry.sCpuName is not None: + oCpu = [ + u'%s (fam:%xh\u00a0m:%xh\u00a0s:%xh)' + % (oEntry.sCpuVendor, oEntry.getCpuFamily(), oEntry.getCpuModel(), oEntry.getCpuStepping(),), + WuiRawHtml('<br>'), + oEntry.sCpuName, + ]; + else: + oCpu = []; + if oEntry.sCpuVendor is not None: + oCpu.append(oEntry.sCpuVendor); + if oEntry.lCpuRevision is not None: + oCpu.append('%#x' % (oEntry.lCpuRevision,)); + if oEntry.sCpuName is not None: + oCpu.append(oEntry.sCpuName); + + # Stuff cpu vendor and cpu/box features into one field. + asFeatures = [] + if oEntry.fCpuHwVirt is True: asFeatures.append(u'HW\u2011Virt'); + if oEntry.fCpuNestedPaging is True: asFeatures.append(u'Nested\u2011Paging'); + if oEntry.fCpu64BitGuest is True: asFeatures.append(u'64\u2011bit\u2011Guest'); + if oEntry.fChipsetIoMmu is True: asFeatures.append(u'I/O\u2011MMU'); + sFeatures = u' '.join(asFeatures) if asFeatures else u''; + + # Collection applicable actions. + aoActions = [ + WuiTmLink('Details', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxDetails, + TestBoxData.ksParam_idTestBox: oEntry.idTestBox, + WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, } ), + ] + + if self._oDisp is None or not self._oDisp.isReadOnlyUser(): + if isDbTimestampInfinity(oEntry.tsExpire): + aoActions += [ + WuiTmLink('Edit', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxEdit, + TestBoxData.ksParam_idTestBox: oEntry.idTestBox, } ), + WuiTmLink('Remove', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxRemovePost, + TestBoxData.ksParam_idTestBox: oEntry.idTestBox }, + sConfirm = 'Are you sure that you want to remove %s (%s)?' % (oEntry.sName, oEntry.ip) ), + ] + + if oEntry.sOs not in [ 'win', 'os2', ] and oEntry.ip is not None: + aoActions.append(WuiLinkBase('ssh', 'ssh://vbox@%s' % (oEntry.ip,),)); + + return [ self._getCheckBoxColumn(iEntry, oEntry.idTestBox), + [ WuiSpanText('tmspan-name', oEntry.sName), WuiRawHtml('<br>'), '%s' % (oEntry.ip,),], + aoLom, + [ + '' if oEntry.fEnabled else 'disabled / ', + oState, + WuiRawHtml('<br>'), + oSeen, + ], + oEntry.enmPendingCmd, + oComment, + WuiSvnLink(oEntry.iTestBoxScriptRev), + oEntry.formatPythonVersion(), + aoGroups, + aoOs, + oCpu, + sFeatures, + oEntry.cCpus if oEntry.cCpus is not None else 'N/A', + utils.formatNumberNbsp(oEntry.cMbMemory) + u'\u00a0MB' if oEntry.cMbMemory is not None else 'N/A', + utils.formatNumberNbsp(oEntry.cMbScratch) + u'\u00a0MB' if oEntry.cMbScratch is not None else 'N/A', + aoActions, + ]; + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuiadmintestcase.py b/src/VBox/ValidationKit/testmanager/webui/wuiadmintestcase.py new file mode 100755 index 00000000..2e2a2082 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuiadmintestcase.py @@ -0,0 +1,258 @@ +# -*- coding: utf-8 -*- +# $Id: wuiadmintestcase.py $ + +""" +Test Manager WUI - Test Cases. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Validation Kit imports. +from common import utils, webutils; +from testmanager.webui.wuicontentbase import WuiFormContentBase, WuiListContentBase, WuiContentBase, WuiTmLink, WuiRawHtml; +from testmanager.core.db import isDbTimestampInfinity; +from testmanager.core.testcase import TestCaseDataEx, TestCaseData, TestCaseDependencyLogic; +from testmanager.core.globalresource import GlobalResourceData, GlobalResourceLogic; + + + +class WuiTestCaseDetailsLink(WuiTmLink): + """ Test case details link by ID. """ + + def __init__(self, idTestCase, sName = WuiContentBase.ksShortDetailsLink, fBracketed = False, tsNow = None): + from testmanager.webui.wuiadmin import WuiAdmin; + dParams = { + WuiAdmin.ksParamAction: WuiAdmin.ksActionTestCaseDetails, + TestCaseData.ksParam_idTestCase: idTestCase, + }; + if tsNow is not None: + dParams[WuiAdmin.ksParamEffectiveDate] = tsNow; ## ?? + WuiTmLink.__init__(self, sName, WuiAdmin.ksScriptName, dParams, fBracketed = fBracketed); + self.idTestCase = idTestCase; + + +class WuiTestCaseList(WuiListContentBase): + """ + WUI test case list content generator. + """ + + def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, aiSelectedSortColumns = None): + WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, sTitle = 'Test Cases', + fnDPrint = fnDPrint, oDisp = oDisp, aiSelectedSortColumns = aiSelectedSortColumns); + self._asColumnHeaders = \ + [ + 'Name', 'Active', 'Timeout', 'Base Command / Variations', 'Validation Kit Files', + 'Test Case Prereqs', 'Global Rsrces', 'Note', 'Actions' + ]; + self._asColumnAttribs = \ + [ + '', '', 'align="center"', '', '', + 'valign="top"', 'valign="top"', 'align="center"', 'align="center"' + ]; + + def _formatListEntry(self, iEntry): + oEntry = self._aoEntries[iEntry]; + from testmanager.webui.wuiadmin import WuiAdmin; + + aoRet = \ + [ + oEntry.sName.replace('-', u'\u2011'), + 'Enabled' if oEntry.fEnabled else 'Disabled', + utils.formatIntervalSeconds(oEntry.cSecTimeout), + ]; + + # Base command and variations. + fNoGang = True; + fNoSubName = True; + fAllDefaultTimeouts = True; + for oVar in oEntry.aoTestCaseArgs: + if fNoSubName and oVar.sSubName is not None and oVar.sSubName.strip(): + fNoSubName = False; + if oVar.cGangMembers > 1: + fNoGang = False; + if oVar.cSecTimeout is not None: + fAllDefaultTimeouts = False; + + sHtml = ' <table class="tminnertbl" width=100%>\n' \ + ' <tr>\n' \ + ' '; + if not fNoSubName: + sHtml += '<th class="tmtcasubname">Sub-name</th>'; + if not fNoGang: + sHtml += '<th class="tmtcagangsize">Gang Size</th>'; + if not fAllDefaultTimeouts: + sHtml += '<th class="tmtcatimeout">Timeout</th>'; + sHtml += '<th>Additional Arguments</b></th>\n' \ + ' </tr>\n' + for oTmp in oEntry.aoTestCaseArgs: + sHtml += '<tr>'; + if not fNoSubName: + sHtml += '<td>%s</td>' % (webutils.escapeElem(oTmp.sSubName) if oTmp.sSubName is not None else ''); + if not fNoGang: + sHtml += '<td>%d</td>' % (oTmp.cGangMembers,) + if not fAllDefaultTimeouts: + sHtml += '<td>%s</td>' \ + % (utils.formatIntervalSeconds(oTmp.cSecTimeout) if oTmp.cSecTimeout is not None else 'Default',) + sHtml += u'<td>%s</td></tr>' \ + % ( webutils.escapeElem(oTmp.sArgs.replace('-', u'\u2011')) if oTmp.sArgs else u'\u2011',); + sHtml += '</tr>\n'; + sHtml += ' </table>' + + aoRet.append([oEntry.sBaseCmd.replace('-', u'\u2011'), WuiRawHtml(sHtml)]); + + # Next. + aoRet += [ oEntry.sValidationKitZips if oEntry.sValidationKitZips is not None else '', ]; + + # Show dependency on other testcases + if oEntry.aoDepTestCases not in (None, []): + sHtml = ' <ul class="tmshowall">\n' + for sTmp in oEntry.aoDepTestCases: + sHtml += ' <li class="tmshowall"><a href="%s?%s=%s&%s=%s">%s</a></li>\n' \ + % (WuiAdmin.ksScriptName, + WuiAdmin.ksParamAction, WuiAdmin.ksActionTestCaseEdit, + TestCaseData.ksParam_idTestCase, sTmp.idTestCase, + sTmp.sName) + sHtml += ' </ul>\n' + else: + sHtml = '<ul class="tmshowall"><li class="tmshowall">None</li></ul>\n' + aoRet.append(WuiRawHtml(sHtml)); + + # Show dependency on global resources + if oEntry.aoDepGlobalResources not in (None, []): + sHtml = ' <ul class="tmshowall">\n' + for sTmp in oEntry.aoDepGlobalResources: + sHtml += ' <li class="tmshowall"><a href="%s?%s=%s&%s=%s">%s</a></li>\n' \ + % (WuiAdmin.ksScriptName, + WuiAdmin.ksParamAction, WuiAdmin.ksActionGlobalRsrcShowEdit, + GlobalResourceData.ksParam_idGlobalRsrc, sTmp.idGlobalRsrc, + sTmp.sName) + sHtml += ' </ul>\n' + else: + sHtml = '<ul class="tmshowall"><li class="tmshowall">None</li></ul>\n' + aoRet.append(WuiRawHtml(sHtml)); + + # Comment (note). + aoRet.append(self._formatCommentCell(oEntry.sComment)); + + # Show actions that can be taken. + aoActions = [ WuiTmLink('Details', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestCaseDetails, + TestCaseData.ksParam_idGenTestCase: oEntry.idGenTestCase }), ]; + if self._oDisp is None or not self._oDisp.isReadOnlyUser(): + if isDbTimestampInfinity(oEntry.tsExpire): + aoActions.append(WuiTmLink('Modify', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestCaseEdit, + TestCaseData.ksParam_idTestCase: oEntry.idTestCase })); + aoActions.append(WuiTmLink('Clone', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestCaseClone, + TestCaseData.ksParam_idGenTestCase: oEntry.idGenTestCase })); + if isDbTimestampInfinity(oEntry.tsExpire): + aoActions.append(WuiTmLink('Remove', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestCaseDoRemove, + TestCaseData.ksParam_idTestCase: oEntry.idTestCase }, + sConfirm = 'Are you sure you want to remove test case #%d?' % (oEntry.idTestCase,))); + aoRet.append(aoActions); + + return aoRet; + + +class WuiTestCase(WuiFormContentBase): + """ + WUI user account content generator. + """ + + def __init__(self, oData, sMode, oDisp): + assert isinstance(oData, TestCaseDataEx); + + if sMode == WuiFormContentBase.ksMode_Add: + sTitle = 'New Test Case'; + elif sMode == WuiFormContentBase.ksMode_Edit: + sTitle = 'Edit Test Case - %s (#%s)' % (oData.sName, oData.idTestCase); + else: + assert sMode == WuiFormContentBase.ksMode_Show; + sTitle = 'Test Case - %s (#%s)' % (oData.sName, oData.idTestCase); + WuiFormContentBase.__init__(self, oData, sMode, 'TestCase', oDisp, sTitle); + + # Read additional bits form the DB. + oDepLogic = TestCaseDependencyLogic(oDisp.getDb()); + self._aoAllTestCases = oDepLogic.getApplicableDepTestCaseData(-1 if oData.idTestCase is None else oData.idTestCase); + self._aoAllGlobalRsrcs = GlobalResourceLogic(oDisp.getDb()).getAll(); + + def _populateForm(self, oForm, oData): + oForm.addIntRO (TestCaseData.ksParam_idTestCase, oData.idTestCase, 'Test Case ID') + oForm.addTimestampRO(TestCaseData.ksParam_tsEffective, oData.tsEffective, 'Last changed') + oForm.addTimestampRO(TestCaseData.ksParam_tsExpire, oData.tsExpire, 'Expires (excl)') + oForm.addIntRO (TestCaseData.ksParam_uidAuthor, oData.uidAuthor, 'Changed by UID') + oForm.addIntRO (TestCaseData.ksParam_idGenTestCase, oData.idGenTestCase, 'Test Case generation ID') + oForm.addText (TestCaseData.ksParam_sName, oData.sName, 'Name') + oForm.addText (TestCaseData.ksParam_sDescription, oData.sDescription, 'Description') + oForm.addCheckBox (TestCaseData.ksParam_fEnabled, oData.fEnabled, 'Enabled') + oForm.addLong (TestCaseData.ksParam_cSecTimeout, + utils.formatIntervalSeconds2(oData.cSecTimeout), 'Default timeout') + oForm.addWideText (TestCaseData.ksParam_sTestBoxReqExpr, oData.sTestBoxReqExpr, 'TestBox requirements (python)'); + oForm.addWideText (TestCaseData.ksParam_sBuildReqExpr, oData.sBuildReqExpr, 'Build requirement (python)'); + oForm.addWideText (TestCaseData.ksParam_sBaseCmd, oData.sBaseCmd, 'Base command') + oForm.addText (TestCaseData.ksParam_sValidationKitZips, oData.sValidationKitZips, 'Test suite files') + + oForm.addListOfTestCaseArgs(TestCaseDataEx.ksParam_aoTestCaseArgs, oData.aoTestCaseArgs, 'Argument variations') + + aoTestCaseDeps = []; + for oTestCase in self._aoAllTestCases: + if oTestCase.idTestCase == oData.idTestCase: + continue; + fSelected = False; + for oDep in oData.aoDepTestCases: + if oDep.idTestCase == oTestCase.idTestCase: + fSelected = True; + break; + aoTestCaseDeps.append([oTestCase.idTestCase, fSelected, oTestCase.sName]); + oForm.addListOfTestCases(TestCaseDataEx.ksParam_aoDepTestCases, aoTestCaseDeps, 'Depends on test cases') + + aoGlobalResrcDeps = []; + for oGlobalRsrc in self._aoAllGlobalRsrcs: + fSelected = False; + for oDep in oData.aoDepGlobalResources: + if oDep.idGlobalRsrc == oGlobalRsrc.idGlobalRsrc: + fSelected = True; + break; + aoGlobalResrcDeps.append([oGlobalRsrc.idGlobalRsrc, fSelected, oGlobalRsrc.sName]); + oForm.addListOfResources(TestCaseDataEx.ksParam_aoDepGlobalResources, aoGlobalResrcDeps, 'Depends on resources') + + oForm.addMultilineText(TestCaseDataEx.ksParam_sComment, oData.sComment, 'Comment'); + + oForm.addSubmit(); + + return True; + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuiadmintestgroup.py b/src/VBox/ValidationKit/testmanager/webui/wuiadmintestgroup.py new file mode 100755 index 00000000..5451c48e --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuiadmintestgroup.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- +# $Id: wuiadmintestgroup.py $ + +""" +Test Manager WUI - Test Groups. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Validation Kit imports. +from common import utils, webutils; +from testmanager.webui.wuicontentbase import WuiFormContentBase, WuiListContentBase, WuiTmLink, WuiRawHtml; +from testmanager.core.db import isDbTimestampInfinity; +from testmanager.core.testgroup import TestGroupData, TestGroupDataEx; +from testmanager.core.testcase import TestCaseData, TestCaseLogic; + + +class WuiTestGroup(WuiFormContentBase): + """ + WUI test group content generator. + """ + + def __init__(self, oData, sMode, oDisp): + assert isinstance(oData, TestGroupDataEx); + + if sMode == WuiFormContentBase.ksMode_Add: + sTitle = 'Add Test Group'; + elif sMode == WuiFormContentBase.ksMode_Edit: + sTitle = 'Modify Test Group'; + else: + assert sMode == WuiFormContentBase.ksMode_Show; + sTitle = 'Test Group'; + WuiFormContentBase.__init__(self, oData, sMode, 'TestGroup', oDisp, sTitle); + + # + # Fetch additional data. + # + if sMode in [WuiFormContentBase.ksMode_Add, WuiFormContentBase.ksMode_Edit]: + self.aoAllTestCases = TestCaseLogic(oDisp.getDb()).fetchForListing(0, 0x7fff, None); + else: + self.aoAllTestCases = [oMember.oTestCase for oMember in oData.aoMembers]; + + def _populateForm(self, oForm, oData): + oForm.addIntRO (TestGroupData.ksParam_idTestGroup, self._oData.idTestGroup, 'Test Group ID') + oForm.addTimestampRO (TestGroupData.ksParam_tsEffective, self._oData.tsEffective, 'Last changed') + oForm.addTimestampRO (TestGroupData.ksParam_tsExpire, self._oData.tsExpire, 'Expires (excl)') + oForm.addIntRO (TestGroupData.ksParam_uidAuthor, self._oData.uidAuthor, 'Changed by UID') + oForm.addText (TestGroupData.ksParam_sName, self._oData.sName, 'Name') + oForm.addText (TestGroupData.ksParam_sDescription, self._oData.sDescription, 'Description') + + oForm.addListOfTestGroupMembers(TestGroupDataEx.ksParam_aoMembers, + oData.aoMembers, self.aoAllTestCases, 'Test Case List', + fReadOnly = self._sMode == WuiFormContentBase.ksMode_Show); + + oForm.addMultilineText (TestGroupData.ksParam_sComment, self._oData.sComment, 'Comment'); + oForm.addSubmit(); + return True; + + +class WuiTestGroupList(WuiListContentBase): + """ + WUI test group list content generator. + """ + + def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, aiSelectedSortColumns = None): + assert not aoEntries or isinstance(aoEntries[0], TestGroupDataEx) + + WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, sTitle = 'Test Groups', + fnDPrint = fnDPrint, oDisp = oDisp, aiSelectedSortColumns = aiSelectedSortColumns); + self._asColumnHeaders = [ 'ID', 'Name', 'Description', 'Test Cases', 'Note', 'Actions' ]; + self._asColumnAttribs = [ 'align="right"', '', '', '', 'align="center"', 'align="center"' ]; + + + def _formatListEntry(self, iEntry): + oEntry = self._aoEntries[iEntry]; + from testmanager.webui.wuiadmin import WuiAdmin; + + # + # Test case list. + # + sHtml = ''; + if oEntry.aoMembers: + for oMember in oEntry.aoMembers: + sHtml += '<dl>\n' \ + ' <dd><strong>%s</strong> (priority: %d) %s %s</dd>\n' \ + % ( webutils.escapeElem(oMember.oTestCase.sName), + oMember.iSchedPriority, + WuiTmLink('Details', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestCaseDetails, + TestCaseData.ksParam_idGenTestCase: oMember.oTestCase.idGenTestCase, } ).toHtml(), + WuiTmLink('Edit', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestCaseEdit, + TestCaseData.ksParam_idTestCase: oMember.oTestCase.idTestCase, } ).toHtml() + if isDbTimestampInfinity(oMember.oTestCase.tsExpire) + and self._oDisp is not None + and not self._oDisp.isReadOnlyUser() else '', + ); + + sHtml += ' <dt>\n'; + + fNoGang = True; + for oVar in oMember.oTestCase.aoTestCaseArgs: + if oVar.cGangMembers > 1: + fNoGang = False + break; + + sHtml += ' <table class="tminnertbl" width="100%">\n' + if fNoGang: + sHtml += ' <tr><th>Timeout</th><th>Arguments</th></tr>\n'; + else: + sHtml += ' <tr><th>Gang Size</th><th>Timeout</th><th style="text-align:left;">Arguments</th></tr>\n'; + + cArgsIncluded = 0; + for oVar in oMember.oTestCase.aoTestCaseArgs: + if oMember.aidTestCaseArgs is None or oVar.idTestCaseArgs in oMember.aidTestCaseArgs: + cArgsIncluded += 1; + if fNoGang: + sHtml += ' <tr>'; + else: + sHtml += ' <tr><td>%s</td>' % (oVar.cGangMembers,); + sHtml += '<td>%s</td><td>%s</td></tr>\n' \ + % ( utils.formatIntervalSeconds(oMember.oTestCase.cSecTimeout if oVar.cSecTimeout is None + else oVar.cSecTimeout), + webutils.escapeElem(oVar.sArgs), ); + if cArgsIncluded == 0: + sHtml += ' <tr><td colspan="%u">No arguments selected.</td></tr>\n' % ( 2 if fNoGang else 3, ); + sHtml += ' </table>\n' \ + ' </dl>\n'; + oTestCases = WuiRawHtml(sHtml); + + # + # Actions. + # + aoActions = [ WuiTmLink('Details', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestGroupDetails, + TestGroupData.ksParam_idTestGroup: oEntry.idTestGroup, + WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, }) ]; + if self._oDisp is None or not self._oDisp.isReadOnlyUser(): + + if isDbTimestampInfinity(oEntry.tsExpire): + aoActions.append(WuiTmLink('Modify', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestGroupEdit, + TestGroupData.ksParam_idTestGroup: oEntry.idTestGroup })); + aoActions.append(WuiTmLink('Clone', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestGroupClone, + TestGroupData.ksParam_idTestGroup: oEntry.idTestGroup, + WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, })); + aoActions.append(WuiTmLink('Remove', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestGroupDoRemove, + TestGroupData.ksParam_idTestGroup: oEntry.idTestGroup }, + sConfirm = 'Do you really want to remove test group #%d?' % (oEntry.idTestGroup,))); + else: + aoActions.append(WuiTmLink('Clone', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestGroupClone, + TestGroupData.ksParam_idTestGroup: oEntry.idTestGroup, + WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, })); + + + + return [ oEntry.idTestGroup, + oEntry.sName, + oEntry.sDescription if oEntry.sDescription is not None else '', + oTestCases, + self._formatCommentCell(oEntry.sComment, cMaxLines = max(3, len(oEntry.aoMembers) * 2)), + aoActions ]; + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuiadminuseraccount.py b/src/VBox/ValidationKit/testmanager/webui/wuiadminuseraccount.py new file mode 100755 index 00000000..c9ce5f4c --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuiadminuseraccount.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +# $Id: wuiadminuseraccount.py $ + +""" +Test Manager WUI - User accounts. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Validation Kit imports. +from testmanager.webui.wuicontentbase import WuiFormContentBase, WuiListContentBase, WuiTmLink; +from testmanager.core.useraccount import UserAccountData + + +class WuiUserAccount(WuiFormContentBase): + """ + WUI user account content generator. + """ + def __init__(self, oData, sMode, oDisp): + if sMode == WuiFormContentBase.ksMode_Add: + sTitle = 'Add User Account'; + elif sMode == WuiFormContentBase.ksMode_Edit: + sTitle = 'Modify User Account'; + else: + assert sMode == WuiFormContentBase.ksMode_Show; + sTitle = 'User Account'; + WuiFormContentBase.__init__(self, oData, sMode, 'User', oDisp, sTitle); + + def _populateForm(self, oForm, oData): + oForm.addIntRO( UserAccountData.ksParam_uid, oData.uid, 'User ID'); + oForm.addTimestampRO(UserAccountData.ksParam_tsEffective, oData.tsEffective, 'Effective Date'); + oForm.addTimestampRO(UserAccountData.ksParam_tsExpire, oData.tsExpire, 'Effective Date'); + oForm.addIntRO( UserAccountData.ksParam_uidAuthor, oData.uidAuthor, 'Changed by UID'); + oForm.addText( UserAccountData.ksParam_sLoginName, oData.sLoginName, 'Login name') + oForm.addText( UserAccountData.ksParam_sUsername, oData.sUsername, 'User name') + oForm.addText( UserAccountData.ksParam_sFullName, oData.sFullName, 'Full name') + oForm.addText( UserAccountData.ksParam_sEmail, oData.sEmail, 'E-mail') + oForm.addCheckBox( UserAccountData.ksParam_fReadOnly, oData.fReadOnly, 'Only read access') + if self._sMode != WuiFormContentBase.ksMode_Show: + oForm.addSubmit('Add User' if self._sMode == WuiFormContentBase.ksMode_Add else 'Change User'); + return True; + + +class WuiUserAccountList(WuiListContentBase): + """ + WUI user account list content generator. + """ + + def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, aiSelectedSortColumns = None): + WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, + sTitle = 'Registered User Accounts', sId = 'users', fnDPrint = fnDPrint, oDisp = oDisp, + aiSelectedSortColumns = aiSelectedSortColumns); + self._asColumnHeaders = ['User ID', 'Name', 'E-mail', 'Full Name', 'Login Name', 'Access', 'Actions']; + self._asColumnAttribs = ['align="center"', 'align="center"', 'align="center"', 'align="center"', 'align="center"', + 'align="center"', 'align="center"', ]; + + def _formatListEntry(self, iEntry): + from testmanager.webui.wuiadmin import WuiAdmin; + oEntry = self._aoEntries[iEntry]; + aoActions = [ + WuiTmLink('Details', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionUserDetails, + UserAccountData.ksParam_uid: oEntry.uid } ), + ]; + if self._oDisp is None or not self._oDisp.isReadOnlyUser(): + aoActions += [ + WuiTmLink('Modify', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionUserEdit, + UserAccountData.ksParam_uid: oEntry.uid } ), + WuiTmLink('Remove', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionUserDelPost, + UserAccountData.ksParam_uid: oEntry.uid }, + sConfirm = 'Are you sure you want to remove user #%d?' % (oEntry.uid,)), + ]; + + return [ oEntry.uid, oEntry.sUsername, oEntry.sEmail, oEntry.sFullName, oEntry.sLoginName, + 'read only' if oEntry.fReadOnly else 'full', + aoActions, ]; + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuibase.py b/src/VBox/ValidationKit/testmanager/webui/wuibase.py new file mode 100755 index 00000000..e1e3908d --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuibase.py @@ -0,0 +1,1245 @@ +# -*- coding: utf-8 -*- +# $Id: wuibase.py $ + +""" +Test Manager Web-UI - Base Classes. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard python imports. +import os; +import sys; +import string; + +# Validation Kit imports. +from common import webutils, utils; +from testmanager import config; +from testmanager.core.base import ModelDataBase, ModelLogicBase, TMExceptionBase; +from testmanager.core.db import TMDatabaseConnection; +from testmanager.core.systemlog import SystemLogLogic, SystemLogData; +from testmanager.core.useraccount import UserAccountLogic + +# Python 3 hacks: +if sys.version_info[0] >= 3: + unicode = str; # pylint: disable=redefined-builtin,invalid-name + long = int; # pylint: disable=redefined-builtin,invalid-name + + +class WuiException(TMExceptionBase): + """ + For exceptions raised by Web UI code. + """ + pass; # pylint: disable=unnecessary-pass + + +class WuiDispatcherBase(object): + """ + Base class for the Web User Interface (WUI) dispatchers. + + The dispatcher class defines the basics of the page (like base template, + menu items, action). It is also responsible for parsing requests and + dispatching them to action (POST) or/and content generators (GET+POST). + The content returned by the generator is merged into the template and sent + back to the webserver glue. + """ + + ## @todo possible that this should all go into presentation. + + ## The action parameter. + ksParamAction = 'Action'; + ## The name of the default action. + ksActionDefault = 'default'; + + ## The name of the current page number parameter used when displaying lists. + ksParamPageNo = 'PageNo'; + ## The name of the page length (list items) parameter when displaying lists. + ksParamItemsPerPage = 'ItemsPerPage'; + + ## The name of the effective date (timestamp) parameter. + ksParamEffectiveDate = 'EffectiveDate'; + + ## The name of the redirect-to (test manager relative url) parameter. + ksParamRedirectTo = 'RedirectTo'; + + ## The name of the list-action parameter (WuiListContentWithActionBase). + ksParamListAction = 'ListAction'; + + ## One or more columns to sort by. + ksParamSortColumns = 'SortBy'; + + ## The name of the change log enabled/disabled parameter. + ksParamChangeLogEnabled = 'ChangeLogEnabled'; + ## The name of the parmaeter indicating the change log page number. + ksParamChangeLogPageNo = 'ChangeLogPageNo'; + ## The name of the parameter indicate number of change log entries per page. + ksParamChangeLogEntriesPerPage = 'ChangeLogEntriesPerPage'; + ## The change log related parameters. + kasChangeLogParams = (ksParamChangeLogEnabled, ksParamChangeLogPageNo, ksParamChangeLogEntriesPerPage,); + + ## @name Dispatcher debugging parameters. + ## {@ + ksParamDbgSqlTrace = 'DbgSqlTrace'; + ksParamDbgSqlExplain = 'DbgSqlExplain'; + ## List of all debugging parameters. + kasDbgParams = (ksParamDbgSqlTrace, ksParamDbgSqlExplain,); + ## @} + + ## Special action return code for skipping _generatePage. Useful for + # download pages and the like that messes with the HTTP header and more. + ksDispatchRcAllDone = 'Done - Page has been rendered already'; + + + def __init__(self, oSrvGlue, sScriptName): + self._oSrvGlue = oSrvGlue; + self._oDb = TMDatabaseConnection(self.dprint if config.g_kfWebUiSqlDebug else None, oSrvGlue = oSrvGlue); + self._tsNow = None; # Set by getEffectiveDateParam. + self._asCheckedParams = []; + self._dParams = None; # Set by dispatchRequest. + self._sAction = None; # Set by dispatchRequest. + self._dDispatch = { self.ksActionDefault: self._actionDefault, }; + + # Template bits. + self._sTemplate = 'template-default.html'; + self._sPageTitle = '$$TODO$$'; # The page title. + self._aaoMenus = []; # List of [sName, sLink, [ [sSideName, sLink], .. ] tuples. + self._sPageFilter = ''; # The filter controls (optional). + self._sPageBody = '$$TODO$$'; # The body text. + self._dSideMenuFormAttrs = {}; # key/value with attributes for the side menu <form> tag. + self._sRedirectTo = None; + self._sDebug = ''; + + # Debugger bits. + self._fDbgSqlTrace = False; + self._fDbgSqlExplain = False; + self._dDbgParams = {}; + for sKey, sValue in oSrvGlue.getParameters().items(): + if sKey in self.kasDbgParams: + self._dDbgParams[sKey] = sValue; + if self._dDbgParams: + from testmanager.webui.wuicontentbase import WuiTmLink; + WuiTmLink.kdDbgParams = self._dDbgParams; + + # Determine currently logged in user credentials + self._oCurUser = UserAccountLogic(self._oDb).tryFetchAccountByLoginName(oSrvGlue.getLoginName()); + + # Calc a couple of URL base strings for this dispatcher. + self._sUrlBase = sScriptName + '?'; + if self._dDbgParams: + self._sUrlBase += webutils.encodeUrlParams(self._dDbgParams) + '&'; + self._sActionUrlBase = self._sUrlBase + self.ksParamAction + '='; + + + def _redirectPage(self): + """ + Redirects the page to the URL given in self._sRedirectTo. + """ + assert self._sRedirectTo; + assert self._sPageBody is None; + assert self._sPageTitle is None; + + self._oSrvGlue.setRedirect(self._sRedirectTo); + return True; + + def _isMenuMatch(self, sMenuUrl, sActionParam): + """ Overridable menu matcher. """ + return sMenuUrl is not None and sMenuUrl.find(sActionParam) > 0; + + def _isSideMenuMatch(self, sSideMenuUrl, sActionParam): + """ Overridable side menu matcher. """ + return sSideMenuUrl is not None and sSideMenuUrl.find(sActionParam) > 0; + + def _generateMenus(self): + """ + Generates the two menus, returning them as (sTopMenuItems, sSideMenuItems). + """ + fReadOnly = self.isReadOnlyUser(); + + # + # We use the action to locate the side menu. + # + aasSideMenu = None; + for cchAction in range(len(self._sAction), 1, -1): + sActionParam = '%s=%s' % (self.ksParamAction, self._sAction[:cchAction]); + for aoItem in self._aaoMenus: + if self._isMenuMatch(aoItem[1], sActionParam): + aasSideMenu = aoItem[2]; + break; + for asSubItem in aoItem[2]: + if self._isMenuMatch(asSubItem[1], sActionParam): + aasSideMenu = aoItem[2]; + break; + if aasSideMenu is not None: + break; + + # + # Top menu first. + # + sTopMenuItems = ''; + for aoItem in self._aaoMenus: + if aasSideMenu is aoItem[2]: + sTopMenuItems += '<li class="current_page_item">'; + else: + sTopMenuItems += '<li>'; + sTopMenuItems += '<a href="' + webutils.escapeAttr(aoItem[1]) + '">' \ + + webutils.escapeElem(aoItem[0]) + '</a></li>\n'; + + # + # Side menu (if found). + # + sActionParam = '%s=%s' % (self.ksParamAction, self._sAction); + sSideMenuItems = ''; + if aasSideMenu is not None: + for asSubItem in aasSideMenu: + if asSubItem[1] is not None: + if not asSubItem[2] or not fReadOnly: + if self._isSideMenuMatch(asSubItem[1], sActionParam): + sSideMenuItems += '<li class="current_page_item">'; + else: + sSideMenuItems += '<li>'; + sSideMenuItems += '<a href="' + webutils.escapeAttr(asSubItem[1]) + '">' \ + + webutils.escapeElem(asSubItem[0]) + '</a></li>\n'; + else: + sSideMenuItems += '<li class="subheader_item">' + webutils.escapeElem(asSubItem[0]) + '</li>'; + return (sTopMenuItems, sSideMenuItems); + + def _generatePage(self): + """ + Generates the page using _sTemplate, _sPageTitle, _aaoMenus, and _sPageBody. + """ + assert self._sRedirectTo is None; + + # + # Build the replacement string dictionary. + # + + # Provide basic auth log out for browsers that supports it. + sUserAgent = self._oSrvGlue.getUserAgent(); + if sUserAgent.startswith('Mozilla/') and sUserAgent.find('Firefox') > 0: + # Log in as the logout user in the same realm, the browser forgets + # the old login and the job is done. (see apache sample conf) + sLogOut = ' (<a href="%s://logout:logout@%s%slogout.py">logout</a>)' \ + % (self._oSrvGlue.getUrlScheme(), self._oSrvGlue.getUrlNetLoc(), self._oSrvGlue.getUrlBasePath()); + elif sUserAgent.startswith('Mozilla/') and sUserAgent.find('Safari') > 0: + # For a 401, causing the browser to forget the old login. Works + # with safari as well as the two above. Since safari consider the + # above method a phishing attempt and displays a warning to that + # effect, which when taken seriously aborts the logout, this method + # is preferable, even if it throws logon boxes in the user's face + # till he/she/it hits escape, because it always works. + sLogOut = ' (<a href="logout2.py">logout</a>)' + elif (sUserAgent.startswith('Mozilla/') and sUserAgent.find('MSIE') > 0) \ + or (sUserAgent.startswith('Mozilla/') and sUserAgent.find('Chrome') > 0): + ## There doesn't seem to be any way to make IE really log out + # without using a cookie and systematically 401 accesses based on + # some logout state associated with it. Not sure how secure that + # can be made and we really want to avoid cookies. So, perhaps, + # just avoid IE for now. :-) + ## Chrome/21.0 doesn't want to log out either. + sLogOut = '' + else: + sLogOut = '' + + # Prep Menus. + (sTopMenuItems, sSideMenuItems) = self._generateMenus(); + + # The dictionary (max variable length is 28 chars (see further down)). + dReplacements = { + '@@PAGE_TITLE@@': self._sPageTitle, + '@@LOG_OUT@@': sLogOut, + '@@TESTMANAGER_VERSION@@': config.g_ksVersion, + '@@TESTMANAGER_REVISION@@': config.g_ksRevision, + '@@BASE_URL@@': self._oSrvGlue.getBaseUrl(), + '@@TOP_MENU_ITEMS@@': sTopMenuItems, + '@@SIDE_MENU_ITEMS@@': sSideMenuItems, + '@@SIDE_FILTER_CONTROL@@': self._sPageFilter, + '@@SIDE_MENU_FORM_ATTRS@@': '', + '@@PAGE_BODY@@': self._sPageBody, + '@@DEBUG@@': '', + }; + + # Side menu form attributes. + if self._dSideMenuFormAttrs: + dReplacements['@@SIDE_MENU_FORM_ATTRS@@'] = ' '.join(['%s="%s"' % (sKey, webutils.escapeAttr(sValue)) + for sKey, sValue in self._dSideMenuFormAttrs.items()]); + + # Special current user handling. + if self._oCurUser is not None: + dReplacements['@@USER_NAME@@'] = self._oCurUser.sUsername; + else: + dReplacements['@@USER_NAME@@'] = 'unauthorized user "' + self._oSrvGlue.getLoginName() + '"'; + + # Prep debug section. + if self._sDebug == '': + if config.g_kfWebUiSqlTrace or self._fDbgSqlTrace or self._fDbgSqlExplain: + self._sDebug = '<h3>Processed in %s ns.</h3>\n%s\n' \ + % ( utils.formatNumber(utils.timestampNano() - self._oSrvGlue.tsStart,), + self._oDb.debugHtmlReport(self._oSrvGlue.tsStart)); + elif config.g_kfWebUiProcessedIn: + self._sDebug = '<h3>Processed in %s ns.</h3>\n' \ + % ( utils.formatNumber(utils.timestampNano() - self._oSrvGlue.tsStart,), ); + if config.g_kfWebUiDebugPanel: + self._sDebug += self._debugRenderPanel(); + if self._sDebug != '': + dReplacements['@@DEBUG@@'] = u'<div id="debug"><br><br><hr/>' \ + + (utils.toUnicode(self._sDebug, errors='ignore') if isinstance(self._sDebug, str) + else self._sDebug) \ + + u'</div>\n'; + + # + # Load the template. + # + with open(os.path.join(self._oSrvGlue.pathTmWebUI(), self._sTemplate)) as oFile: # pylint: disable=unspecified-encoding + sTmpl = oFile.read(); + + # + # Process the template, outputting each part we process. + # + offStart = 0; + offCur = 0; + while offCur < len(sTmpl): + # Look for a replacement variable. + offAtAt = sTmpl.find('@@', offCur); + if offAtAt < 0: + break; + offCur = offAtAt + 2; + if sTmpl[offCur] not in string.ascii_uppercase: + continue; + offEnd = sTmpl.find('@@', offCur, offCur+28); + if offEnd <= 0: + continue; + offCur = offEnd; + sReplacement = sTmpl[offAtAt:offEnd+2]; + if sReplacement in dReplacements: + # Got a match! Write out the previous chunk followed by the replacement text. + if offStart < offAtAt: + self._oSrvGlue.write(sTmpl[offStart:offAtAt]); + self._oSrvGlue.write(dReplacements[sReplacement]); + # Advance past the replacement point in the template. + offCur += 2; + offStart = offCur; + else: + assert False, 'Unknown replacement "%s" at offset %s in %s' % (sReplacement, offAtAt, self._sTemplate ); + + # The final chunk. + if offStart < len(sTmpl): + self._oSrvGlue.write(sTmpl[offStart:]); + + return True; + + # + # Interface for WuiContentBase classes. + # + + def getUrlNoParams(self): + """ + Returns the base URL without any parameters (no trailing '?' or &). + """ + return self._sUrlBase[:self._sUrlBase.rindex('?')]; + + def getUrlBase(self): + """ + Returns the base URL, ending with '?' or '&'. + This may already include some debug parameters. + """ + return self._sUrlBase; + + def getParameters(self): + """ + Returns a (shallow) copy of the request parameter dictionary. + """ + return self._dParams.copy(); + + def getDb(self): + """ + Returns the database connection. + """ + return self._oDb; + + def getNow(self): + """ + Returns the effective date. + """ + return self._tsNow; + + + # + # Parameter handling. + # + + def getStringParam(self, sName, asValidValues = None, sDefault = None, fAllowNull = False): + """ + Gets a string parameter. + Raises exception if not found and sDefault is None. + """ + if sName in self._dParams: + if sName not in self._asCheckedParams: + self._asCheckedParams.append(sName); + sValue = self._dParams[sName]; + if isinstance(sValue, list): + raise WuiException('%s parameter "%s" is given multiple times: "%s"' % (self._sAction, sName, sValue)); + sValue = sValue.strip(); + elif sDefault is None and fAllowNull is not True: + raise WuiException('%s is missing parameters: "%s"' % (self._sAction, sName,)); + else: + sValue = sDefault; + + if asValidValues is not None and sValue not in asValidValues: + raise WuiException('%s parameter %s value "%s" not in %s ' + % (self._sAction, sName, sValue, asValidValues)); + return sValue; + + def getBoolParam(self, sName, fDefault = None): + """ + Gets a boolean parameter. + Raises exception if not found and fDefault is None, or if not a valid boolean. + """ + sValue = self.getStringParam(sName, [ 'True', 'true', '1', 'False', 'false', '0'], + '0' if fDefault is None else str(fDefault)); + # HACK: Checkboxes doesn't return a value when unchecked, so we always + # provide a default when dealing with boolean parameters. + return sValue in ('True', 'true', '1',); + + def getIntParam(self, sName, iMin = None, iMax = None, iDefault = None): + """ + Gets a integer parameter. + Raises exception if not found and iDefault is None, if not a valid int, + or if outside the range defined by iMin and iMax. + """ + if iDefault is not None and sName not in self._dParams: + return iDefault; + + sValue = self.getStringParam(sName, None, None if iDefault is None else str(iDefault)); + try: + iValue = int(sValue); + except: + raise WuiException('%s parameter %s value "%s" cannot be convert to an integer' + % (self._sAction, sName, sValue)); + + if (iMin is not None and iValue < iMin) \ + or (iMax is not None and iValue > iMax): + raise WuiException('%s parameter %s value %d is out of range [%s..%s]' + % (self._sAction, sName, iValue, iMin, iMax)); + return iValue; + + def getLongParam(self, sName, lMin = None, lMax = None, lDefault = None): + """ + Gets a long integer parameter. + Raises exception if not found and lDefault is None, if not a valid long, + or if outside the range defined by lMin and lMax. + """ + if lDefault is not None and sName not in self._dParams: + return lDefault; + + sValue = self.getStringParam(sName, None, None if lDefault is None else str(lDefault)); + try: + lValue = long(sValue); + except: + raise WuiException('%s parameter %s value "%s" cannot be convert to an integer' + % (self._sAction, sName, sValue)); + + if (lMin is not None and lValue < lMin) \ + or (lMax is not None and lValue > lMax): + raise WuiException('%s parameter %s value %d is out of range [%s..%s]' + % (self._sAction, sName, lValue, lMin, lMax)); + return lValue; + + def getTsParam(self, sName, tsDefault = None, fRequired = True): + """ + Gets a timestamp parameter. + Raises exception if not found and fRequired is True. + """ + if fRequired is False and sName not in self._dParams: + return tsDefault; + + sValue = self.getStringParam(sName, None, None if tsDefault is None else str(tsDefault)); + (sValue, sError) = ModelDataBase.validateTs(sValue); + if sError is not None: + raise WuiException('%s parameter %s value "%s": %s' + % (self._sAction, sName, sValue, sError)); + return sValue; + + def getListOfIntParams(self, sName, iMin = None, iMax = None, aiDefaults = None): + """ + Gets parameter list. + Raises exception if not found and aiDefaults is None, or if any of the + values are not valid integers or outside the range defined by iMin and iMax. + """ + if sName in self._dParams: + if sName not in self._asCheckedParams: + self._asCheckedParams.append(sName); + + if isinstance(self._dParams[sName], list): + asValues = self._dParams[sName]; + else: + asValues = [self._dParams[sName],]; + aiValues = []; + for sValue in asValues: + try: + iValue = int(sValue); + except: + raise WuiException('%s parameter %s value "%s" cannot be convert to an integer' + % (self._sAction, sName, sValue)); + + if (iMin is not None and iValue < iMin) \ + or (iMax is not None and iValue > iMax): + raise WuiException('%s parameter %s value %d is out of range [%s..%s]' + % (self._sAction, sName, iValue, iMin, iMax)); + aiValues.append(iValue); + else: + aiValues = aiDefaults; + + return aiValues; + + def getListOfStrParams(self, sName, asDefaults = None): + """ + Gets parameter list. + Raises exception if not found and asDefaults is None. + """ + if sName in self._dParams: + if sName not in self._asCheckedParams: + self._asCheckedParams.append(sName); + + if isinstance(self._dParams[sName], list): + asValues = [str(s).strip() for s in self._dParams[sName]]; + else: + asValues = [str(self._dParams[sName]).strip(), ]; + elif asDefaults is None: + raise WuiException('%s is missing parameters: "%s"' % (self._sAction, sName,)); + else: + asValues = asDefaults; + + return asValues; + + def getListOfTestCasesParam(self, sName, asDefaults = None): # too many local vars - pylint: disable=too-many-locals + """Get list of test cases and their parameters""" + if sName in self._dParams: + if sName not in self._asCheckedParams: + self._asCheckedParams.append(sName) + + aoListOfTestCases = [] + + aiSelectedTestCaseIds = self.getListOfIntParams('%s[asCheckedTestCases]' % sName, aiDefaults=[]) + aiAllTestCases = self.getListOfIntParams('%s[asAllTestCases]' % sName, aiDefaults=[]) + + for idTestCase in aiAllTestCases: + aiCheckedTestCaseArgs = \ + self.getListOfIntParams( + '%s[%d][asCheckedTestCaseArgs]' % (sName, idTestCase), + aiDefaults=[]) + + aiAllTestCaseArgs = \ + self.getListOfIntParams( + '%s[%d][asAllTestCaseArgs]' % (sName, idTestCase), + aiDefaults=[]) + + oListEntryTestCaseArgs = [] + for idTestCaseArgs in aiAllTestCaseArgs: + fArgsChecked = idTestCaseArgs in aiCheckedTestCaseArgs; + + # Dry run + sPrefix = '%s[%d][%d]' % (sName, idTestCase, idTestCaseArgs,); + self.getIntParam(sPrefix + '[idTestCaseArgs]', iDefault = -1,) + + sArgs = self.getStringParam(sPrefix + '[sArgs]', sDefault = '') + cSecTimeout = self.getStringParam(sPrefix + '[cSecTimeout]', sDefault = '') + cGangMembers = self.getStringParam(sPrefix + '[cGangMembers]', sDefault = '') + cGangMembers = self.getStringParam(sPrefix + '[cGangMembers]', sDefault = '') + + oListEntryTestCaseArgs.append((fArgsChecked, idTestCaseArgs, sArgs, cSecTimeout, cGangMembers)) + + sTestCaseName = self.getStringParam('%s[%d][sName]' % (sName, idTestCase), sDefault='') + + oListEntryTestCase = ( + idTestCase, + idTestCase in aiSelectedTestCaseIds, + sTestCaseName, + oListEntryTestCaseArgs + ); + + aoListOfTestCases.append(oListEntryTestCase) + + if not aoListOfTestCases: + if asDefaults is None: + raise WuiException('%s is missing parameters: "%s"' % (self._sAction, sName)) + aoListOfTestCases = asDefaults + + return aoListOfTestCases + + def getEffectiveDateParam(self, sParamName = None): + """ + Gets the effective date parameter. + + Returns a timestamp suitable for database and url parameters. + Returns None if not found or empty. + + The first call with sParamName set to None will set the internal _tsNow + value upon successfull return. + """ + + sName = sParamName if sParamName is not None else WuiDispatcherBase.ksParamEffectiveDate + + if sName not in self._dParams: + return None; + + if sName not in self._asCheckedParams: + self._asCheckedParams.append(sName); + + sValue = self._dParams[sName]; + if isinstance(sValue, list): + raise WuiException('%s parameter "%s" is given multiple times: %s' % (self._sAction, sName, sValue)); + sValue = sValue.strip(); + if sValue == '': + return None; + + # + # Timestamp, just validate it and return. + # + if sValue[0] not in ['-', '+']: + (sValue, sError) = ModelDataBase.validateTs(sValue); + if sError is not None: + raise WuiException('%s parameter "%s" ("%s") is invalid: %s' % (self._sAction, sName, sValue, sError)); + if sParamName is None and self._tsNow is None: + self._tsNow = sValue; + return sValue; + + # + # Relative timestamp. Validate and convert it to a fixed timestamp. + # + chSign = sValue[0]; + (sValue, sError) = ModelDataBase.validateTs(sValue[1:], fRelative = True); + if sError is not None: + raise WuiException('%s parameter "%s" ("%s") is invalid: %s' % (self._sAction, sName, sValue, sError)); + if sValue[-6] in ['-', '+']: + raise WuiException('%s parameter "%s" ("%s") is a relative timestamp but incorrectly includes a time zone.' + % (self._sAction, sName, sValue)); + offTime = 11; + if sValue[offTime - 1] != ' ': + raise WuiException('%s parameter "%s" ("%s") incorrect format.' % (self._sAction, sName, sValue)); + sInterval = 'P' + sValue[:(offTime - 1)] + 'T' + sValue[offTime:]; + + self._oDb.execute('SELECT CURRENT_TIMESTAMP ' + chSign + ' \'' + sInterval + '\'::INTERVAL'); + oDate = self._oDb.fetchOne()[0]; + + sValue = str(oDate); + if sParamName is None and self._tsNow is None: + self._tsNow = sValue; + return sValue; + + def getRedirectToParameter(self, sDefault = None): + """ + Gets the special redirect to parameter if it exists, will Return default + if not, with None being a valid default. + + Makes sure the it doesn't got offsite. + Raises exception if invalid. + """ + if sDefault is not None or self.ksParamRedirectTo in self._dParams: + sValue = self.getStringParam(self.ksParamRedirectTo, sDefault = sDefault); + cch = sValue.find("?"); + if cch < 0: + cch = sValue.find("#"); + if cch < 0: + cch = len(sValue); + for ch in (':', '/', '\\', '..'): + if sValue.find(ch, 0, cch) >= 0: + raise WuiException('Invalid character (%c) in redirect-to url: %s' % (ch, sValue,)); + else: + sValue = None; + return sValue; + + + def _checkForUnknownParameters(self): + """ + Check if we've handled all parameters, raises exception if anything + unknown was found. + """ + + if len(self._asCheckedParams) != len(self._dParams): + sUnknownParams = ''; + for sKey in self._dParams: + if sKey not in self._asCheckedParams: + sUnknownParams += ' ' + sKey + '=' + str(self._dParams[sKey]); + raise WuiException('Unknown parameters: ' + sUnknownParams); + + return True; + + def _assertPostRequest(self): + """ + Makes sure that the request we're dispatching is a POST request. + Raises an exception of not. + """ + if self._oSrvGlue.getMethod() != 'POST': + raise WuiException('Expected "POST" request, got "%s"' % (self._oSrvGlue.getMethod(),)) + return True; + + # + # Client browser type. + # + + ## @name Browser types. + ## @{ + ksBrowserFamily_Unknown = 0; + ksBrowserFamily_Gecko = 1; + ksBrowserFamily_Webkit = 2; + ksBrowserFamily_Trident = 3; + ## @} + + ## @name Browser types. + ## @{ + ksBrowserType_FamilyMask = 0xff; + ksBrowserType_Unknown = 0; + ksBrowserType_Firefox = (1 << 8) | ksBrowserFamily_Gecko; + ksBrowserType_Chrome = (2 << 8) | ksBrowserFamily_Webkit; + ksBrowserType_Safari = (3 << 8) | ksBrowserFamily_Webkit; + ksBrowserType_IE = (4 << 8) | ksBrowserFamily_Trident; + ## @} + + def getBrowserType(self): + """ + Gets the browser type. + The browser family can be extracted from this using ksBrowserType_FamilyMask. + """ + sAgent = self._oSrvGlue.getUserAgent(); + if sAgent.find('AppleWebKit/') > 0: + if sAgent.find('Chrome/') > 0: + return self.ksBrowserType_Chrome; + if sAgent.find('Safari/') > 0: + return self.ksBrowserType_Safari; + return self.ksBrowserType_Unknown | self.ksBrowserFamily_Webkit; + if sAgent.find('Gecko/') > 0: + if sAgent.find('Firefox/') > 0: + return self.ksBrowserType_Firefox; + return self.ksBrowserType_Unknown | self.ksBrowserFamily_Gecko; + return self.ksBrowserType_Unknown | self.ksBrowserFamily_Unknown; + + def isBrowserGecko(self, sMinVersion = None): + """ Returns true if it's a gecko based browser. """ + if (self.getBrowserType() & self.ksBrowserType_FamilyMask) != self.ksBrowserFamily_Gecko: + return False; + if sMinVersion is not None: + sAgent = self._oSrvGlue.getUserAgent(); + sVersion = sAgent[sAgent.find('Gecko/')+6:].split()[0]; + if sVersion < sMinVersion: + return False; + return True; + + # + # User related stuff. + # + + def isReadOnlyUser(self): + """ Returns true if the logged in user is read-only or if no user is logged in. """ + return self._oCurUser is None or self._oCurUser.fReadOnly; + + + # + # Debugging + # + + def _debugProcessDispatch(self): + """ + Processes any debugging parameters in the request and adds them to + _asCheckedParams so they won't cause trouble in the action handler. + """ + + self._fDbgSqlTrace = self.getBoolParam(self.ksParamDbgSqlTrace, False); + self._fDbgSqlExplain = self.getBoolParam(self.ksParamDbgSqlExplain, False); + + if self._fDbgSqlExplain: + self._oDb.debugEnableExplain(); + + return True; + + def _debugRenderPanel(self): + """ + Renders a simple form for controlling WUI debugging. + + Returns the HTML for it. + """ + + sHtml = '<div id="debug-panel">\n' \ + ' <form id="debug-panel-form" method="get" action="#">\n'; + + for sKey, oValue in self._dParams.items(): + if sKey not in self.kasDbgParams: + if hasattr(oValue, 'startswith'): + sHtml += ' <input type="hidden" name="%s" value="%s"/>\n' \ + % (webutils.escapeAttr(sKey), webutils.escapeAttrToStr(oValue),); + else: + for oSubValue in oValue: + sHtml += ' <input type="hidden" name="%s" value="%s"/>\n' \ + % (webutils.escapeAttr(sKey), webutils.escapeAttrToStr(oSubValue),); + + for aoCheckBox in ( + [self.ksParamDbgSqlTrace, self._fDbgSqlTrace, 'SQL trace'], + [self.ksParamDbgSqlExplain, self._fDbgSqlExplain, 'SQL explain'], ): + sHtml += ' <input type="checkbox" name="%s" value="1"%s />%s\n' \ + % (aoCheckBox[0], ' checked' if aoCheckBox[1] else '', aoCheckBox[2]); + + sHtml += ' <button type="submit">Apply</button>\n'; + sHtml += ' </form>\n' \ + '</div>\n'; + return sHtml; + + + def _debugGetParameters(self): + """ + Gets a dictionary with the debug parameters. + + For use when links are constructed from scratch instead of self._dParams. + """ + return self._dDbgParams; + + # + # Dispatching + # + + def _actionDefault(self): + """The default action handler, always overridden. """ + raise WuiException('The child class shall override WuiBase.actionDefault().') + + def _actionGenericListing(self, oLogicType, oListContentType): + """ + Generic listing action. + + oLogicType implements fetchForListing. + oListContentType is a child of WuiListContentBase. + """ + tsEffective = self.getEffectiveDateParam(); + cItemsPerPage = self.getIntParam(self.ksParamItemsPerPage, iMin = 2, iMax = 9999, iDefault = 384); + iPage = self.getIntParam(self.ksParamPageNo, iMin = 0, iMax = 999999, iDefault = 0); + aiSortColumnsDup = self.getListOfIntParams(self.ksParamSortColumns, + iMin = -getattr(oLogicType, 'kcMaxSortColumns', 0) + 1, + iMax = getattr(oLogicType, 'kcMaxSortColumns', 0), aiDefaults = []); + aiSortColumns = []; + for iSortColumn in aiSortColumnsDup: + if iSortColumn not in aiSortColumns: + aiSortColumns.append(iSortColumn); + self._checkForUnknownParameters(); + + ## @todo fetchForListing could be made more useful if it returned a tuple + # that includes the total number of entries, thus making paging more user + # friendly (known number of pages). So, the return should be: + # (aoEntries, cAvailableEntries) + # + # In addition, we could add a new parameter to include deleted entries, + # making it easier to find old deleted testboxes/testcases/whatever and + # clone them back to life. The temporal navigation is pretty usless here. + # + aoEntries = oLogicType(self._oDb).fetchForListing(iPage * cItemsPerPage, cItemsPerPage + 1, tsEffective, aiSortColumns); + oContent = oListContentType(aoEntries, iPage, cItemsPerPage, tsEffective, + fnDPrint = self._oSrvGlue.dprint, oDisp = self, aiSelectedSortColumns = aiSortColumns); + (self._sPageTitle, self._sPageBody) = oContent.show(); + return True; + + def _actionGenericFormAdd(self, oDataType, oFormType, sRedirectTo = None): + """ + Generic add something form display request handler. + + oDataType is a ModelDataBase child class. + oFormType is a WuiFormContentBase child class. + """ + assert issubclass(oDataType, ModelDataBase); + from testmanager.webui.wuicontentbase import WuiFormContentBase; + assert issubclass(oFormType, WuiFormContentBase); + + oData = oDataType().initFromParams(oDisp = self, fStrict = False); + sRedirectTo = self.getRedirectToParameter(sRedirectTo); + self._checkForUnknownParameters(); + + oForm = oFormType(oData, oFormType.ksMode_Add, oDisp = self); + oForm.setRedirectTo(sRedirectTo); + (self._sPageTitle, self._sPageBody) = oForm.showForm(); + return True + + def _actionGenericFormDetails(self, oDataType, oLogicType, oFormType, sIdAttr = None, sGenIdAttr = None): # pylint: disable=too-many-locals + """ + Generic handler for showing a details form/page. + + oDataType is a ModelDataBase child class. + oLogicType may implement fetchForChangeLog. + oFormType is a WuiFormContentBase child class. + sIdParamName is the name of the ID parameter (not idGen!). + """ + # Input. + assert issubclass(oDataType, ModelDataBase); + assert issubclass(oLogicType, ModelLogicBase); + from testmanager.webui.wuicontentbase import WuiFormContentBase; + assert issubclass(oFormType, WuiFormContentBase); + + if sIdAttr is None: + sIdAttr = oDataType.ksIdAttr; + if sGenIdAttr is None: + sGenIdAttr = getattr(oDataType, 'ksGenIdAttr', None); + + # Parameters. + idGenObject = -1; + if sGenIdAttr is not None: + idGenObject = self.getIntParam(getattr(oDataType, 'ksParam_' + sGenIdAttr), 0, 0x7ffffffe, -1); + if idGenObject != -1: + idObject = tsNow = None; + else: + idObject = self.getIntParam(getattr(oDataType, 'ksParam_' + sIdAttr), 0, 0x7ffffffe, -1); + tsNow = self.getEffectiveDateParam(); + fChangeLog = self.getBoolParam(WuiDispatcherBase.ksParamChangeLogEnabled, True); + iChangeLogPageNo = self.getIntParam(WuiDispatcherBase.ksParamChangeLogPageNo, 0, 9999, 0); + cChangeLogEntriesPerPage = self.getIntParam(WuiDispatcherBase.ksParamChangeLogEntriesPerPage, 2, 9999, 4); + self._checkForUnknownParameters(); + + # Fetch item and display it. + if idGenObject == -1: + oData = oDataType().initFromDbWithId(self._oDb, idObject, tsNow); + else: + oData = oDataType().initFromDbWithGenId(self._oDb, idGenObject); + + oContent = oFormType(oData, oFormType.ksMode_Show, oDisp = self); + (self._sPageTitle, self._sPageBody) = oContent.showForm(); + + # Add change log if supported. + if fChangeLog and hasattr(oLogicType, 'fetchForChangeLog'): + (aoEntries, fMore) = oLogicType(self._oDb).fetchForChangeLog(getattr(oData, sIdAttr), + iChangeLogPageNo * cChangeLogEntriesPerPage, + cChangeLogEntriesPerPage , + tsNow); + self._sPageBody += oContent.showChangeLog(aoEntries, fMore, iChangeLogPageNo, cChangeLogEntriesPerPage, tsNow); + return True + + def _actionGenericDoRemove(self, oLogicType, sParamId, sRedirAction): + """ + Delete entry (using oLogicType.removeEntry). + + oLogicType is a class that implements addEntry. + + sParamId is the name (ksParam_...) of the HTTP variable hold the ID of + the database entry to delete. + + sRedirAction is what action to redirect to on success. + """ + import cgitb; + + idEntry = self.getIntParam(sParamId, iMin = 1, iMax = 0x7ffffffe) + fCascade = self.getBoolParam('fCascadeDelete', False); + sRedirectTo = self.getRedirectToParameter(self._sActionUrlBase + sRedirAction); + self._checkForUnknownParameters() + + try: + if self.isReadOnlyUser(): + raise Exception('"%s" is a read only user!' % (self._oCurUser.sUsername,)); + self._sPageTitle = None + self._sPageBody = None + self._sRedirectTo = sRedirectTo; + return oLogicType(self._oDb).removeEntry(self._oCurUser.uid, idEntry, fCascade = fCascade, fCommit = True); + except Exception as oXcpt: + self._oDb.rollback(); + self._sPageTitle = 'Unable to delete entry'; + self._sPageBody = str(oXcpt); + if config.g_kfDebugDbXcpt: + self._sPageBody += cgitb.html(sys.exc_info()); + self._sRedirectTo = None; + return False; + + def _actionGenericFormEdit(self, oDataType, oFormType, sIdParamName = None, sRedirectTo = None): + """ + Generic edit something form display request handler. + + oDataType is a ModelDataBase child class. + oFormType is a WuiFormContentBase child class. + sIdParamName is the name of the ID parameter (not idGen!). + """ + assert issubclass(oDataType, ModelDataBase); + from testmanager.webui.wuicontentbase import WuiFormContentBase; + assert issubclass(oFormType, WuiFormContentBase); + + if sIdParamName is None: + sIdParamName = getattr(oDataType, 'ksParam_' + oDataType.ksIdAttr); + assert len(sIdParamName) > 1; + + tsNow = self.getEffectiveDateParam(); + idObject = self.getIntParam(sIdParamName, 0, 0x7ffffffe); + sRedirectTo = self.getRedirectToParameter(sRedirectTo); + self._checkForUnknownParameters(); + oData = oDataType().initFromDbWithId(self._oDb, idObject, tsNow = tsNow); + + oContent = oFormType(oData, oFormType.ksMode_Edit, oDisp = self); + oContent.setRedirectTo(sRedirectTo); + (self._sPageTitle, self._sPageBody) = oContent.showForm(); + return True + + def _actionGenericFormEditL(self, oCoreObjectLogic, sCoreObjectIdFieldName, oWuiObjectLogic): + """ + Generic modify something form display request handler. + + @param oCoreObjectLogic A *Logic class + + @param sCoreObjectIdFieldName Name of HTTP POST variable that + contains object ID information + + @param oWuiObjectLogic Web interface renderer class + """ + + iCoreDataObjectId = self.getIntParam(sCoreObjectIdFieldName, 0, 0x7ffffffe, -1) + self._checkForUnknownParameters(); + + ## @todo r=bird: This will return a None object if the object wasn't found... Crash bang in the content generator + # code (that's not logic code btw.). + oData = oCoreObjectLogic(self._oDb).getById(iCoreDataObjectId) + + # Instantiate and render the MODIFY dialog form + oContent = oWuiObjectLogic(oData, oWuiObjectLogic.ksMode_Edit, oDisp=self) + + (self._sPageTitle, self._sPageBody) = oContent.showForm() + + return True + + def _actionGenericFormClone(self, oDataType, oFormType, sIdAttr, sGenIdAttr = None): + """ + Generic clone something form display request handler. + + oDataType is a ModelDataBase child class. + oFormType is a WuiFormContentBase child class. + sIdParamName is the name of the ID parameter. + sGenIdParamName is the name of the generation ID parameter, None if not applicable. + """ + # Input. + assert issubclass(oDataType, ModelDataBase); + from testmanager.webui.wuicontentbase import WuiFormContentBase; + assert issubclass(oFormType, WuiFormContentBase); + + # Parameters. + idGenObject = -1; + if sGenIdAttr is not None: + idGenObject = self.getIntParam(getattr(oDataType, 'ksParam_' + sGenIdAttr), 0, 0x7ffffffe, -1); + if idGenObject != -1: + idObject = tsNow = None; + else: + idObject = self.getIntParam(getattr(oDataType, 'ksParam_' + sIdAttr), 0, 0x7ffffffe, -1); + tsNow = self.getEffectiveDateParam(); + self._checkForUnknownParameters(); + + # Fetch data and clear identifying attributes not relevant to the clone. + if idGenObject != -1: + oData = oDataType().initFromDbWithGenId(self._oDb, idGenObject); + else: + oData = oDataType().initFromDbWithId(self._oDb, idObject, tsNow); + + setattr(oData, sIdAttr, None); + if sGenIdAttr is not None: + setattr(oData, sGenIdAttr, None); + oData.tsEffective = None; + oData.tsExpire = None; + + # Display form. + oContent = oFormType(oData, oFormType.ksMode_Add, oDisp = self); + (self._sPageTitle, self._sPageBody) = oContent.showForm() + return True + + + def _actionGenericFormPost(self, sMode, fnLogicAction, oDataType, oFormType, sRedirectTo, fStrict = True): + """ + Generic POST request handling from a WuiFormContentBase child. + + oDataType is a ModelDataBase child class. + oFormType is a WuiFormContentBase child class. + fnLogicAction is a method taking a oDataType instance and uidAuthor as arguments. + """ + assert issubclass(oDataType, ModelDataBase); + from testmanager.webui.wuicontentbase import WuiFormContentBase; + assert issubclass(oFormType, WuiFormContentBase); + + # + # Read and validate parameters. + # + oData = oDataType().initFromParams(oDisp = self, fStrict = fStrict); + sRedirectTo = self.getRedirectToParameter(sRedirectTo); + self._checkForUnknownParameters(); + self._assertPostRequest(); + if sMode == WuiFormContentBase.ksMode_Add and getattr(oData, 'kfIdAttrIsForForeign', False): + enmValidateFor = oData.ksValidateFor_AddForeignId; + elif sMode == WuiFormContentBase.ksMode_Add: + enmValidateFor = oData.ksValidateFor_Add; + else: + enmValidateFor = oData.ksValidateFor_Edit; + dErrors = oData.validateAndConvert(self._oDb, enmValidateFor); + + # Check that the user can do this. + sErrorMsg = None; + assert self._oCurUser is not None; + if self.isReadOnlyUser(): + sErrorMsg = 'User %s is not allowed to modify anything!' % (self._oCurUser.sUsername,) + + if not dErrors and not sErrorMsg: + oData.convertFromParamNull(); + + # + # Try do the job. + # + try: + fnLogicAction(oData, self._oCurUser.uid, fCommit = True); + except Exception as oXcpt: + self._oDb.rollback(); + oForm = oFormType(oData, sMode, oDisp = self); + oForm.setRedirectTo(sRedirectTo); + sErrorMsg = str(oXcpt) if not config.g_kfDebugDbXcpt else '\n'.join(utils.getXcptInfo(4)); + (self._sPageTitle, self._sPageBody) = oForm.showForm(sErrorMsg = sErrorMsg); + else: + # + # Worked, redirect to the specified page. + # + self._sPageTitle = None; + self._sPageBody = None; + self._sRedirectTo = sRedirectTo; + else: + oForm = oFormType(oData, sMode, oDisp = self); + oForm.setRedirectTo(sRedirectTo); + (self._sPageTitle, self._sPageBody) = oForm.showForm(dErrors = dErrors, sErrorMsg = sErrorMsg); + return True; + + def _actionGenericFormAddPost(self, oDataType, oLogicType, oFormType, sRedirAction, fStrict=True): + """ + Generic add entry POST request handling from a WuiFormContentBase child. + + oDataType is a ModelDataBase child class. + oLogicType is a class that implements addEntry. + oFormType is a WuiFormContentBase child class. + sRedirAction is what action to redirect to on success. + """ + assert issubclass(oDataType, ModelDataBase); + assert issubclass(oLogicType, ModelLogicBase); + from testmanager.webui.wuicontentbase import WuiFormContentBase; + assert issubclass(oFormType, WuiFormContentBase); + + oLogic = oLogicType(self._oDb); + return self._actionGenericFormPost(WuiFormContentBase.ksMode_Add, oLogic.addEntry, oDataType, oFormType, + '?' + webutils.encodeUrlParams({self.ksParamAction: sRedirAction}), fStrict=fStrict) + + def _actionGenericFormEditPost(self, oDataType, oLogicType, oFormType, sRedirAction, fStrict = True): + """ + Generic edit POST request handling from a WuiFormContentBase child. + + oDataType is a ModelDataBase child class. + oLogicType is a class that implements addEntry. + oFormType is a WuiFormContentBase child class. + sRedirAction is what action to redirect to on success. + """ + assert issubclass(oDataType, ModelDataBase); + assert issubclass(oLogicType, ModelLogicBase); + from testmanager.webui.wuicontentbase import WuiFormContentBase; + assert issubclass(oFormType, WuiFormContentBase); + + oLogic = oLogicType(self._oDb); + return self._actionGenericFormPost(WuiFormContentBase.ksMode_Edit, oLogic.editEntry, oDataType, oFormType, + '?' + webutils.encodeUrlParams({self.ksParamAction: sRedirAction}), + fStrict = fStrict); + + def _unauthorizedUser(self): + """ + Displays the unauthorized user message (corresponding record is not + present in DB). + """ + + sLoginName = self._oSrvGlue.getLoginName(); + + # Report to system log + oSystemLogLogic = SystemLogLogic(self._oDb); + oSystemLogLogic.addEntry(SystemLogData.ksEvent_UserAccountUnknown, + 'Unknown user (%s) attempts to access from %s' + % (sLoginName, self._oSrvGlue.getClientAddr()), + 24, fCommit = True) + + # Display message. + self._sPageTitle = 'User not authorized' + self._sPageBody = """ + <p>Access denied for user <b>%s</b>. + Please contact an admin user to set up your access.</p> + """ % (sLoginName,) + return True; + + def dispatchRequest(self): + """ + Dispatches a request. + """ + + # + # Get the parameters and checks for duplicates. + # + try: + dParams = self._oSrvGlue.getParameters(); + except Exception as oXcpt: + raise WuiException('Error retriving parameters: %s' % (oXcpt,)); + + for sKey in dParams.keys(): + + # Take care about strings which may contain unicode characters: convert percent-encoded symbols back to unicode. + for idxItem, _ in enumerate(dParams[sKey]): + dParams[sKey][idxItem] = utils.toUnicode(dParams[sKey][idxItem], 'utf-8'); + + if not len(dParams[sKey]) > 1: + dParams[sKey] = dParams[sKey][0]; + self._dParams = dParams; + + # + # Figure out the requested action and validate it. + # + if self.ksParamAction in self._dParams: + self._sAction = self._dParams[self.ksParamAction]; + self._asCheckedParams.append(self.ksParamAction); + else: + self._sAction = self.ksActionDefault; + + if isinstance(self._sAction, list) or self._sAction not in self._dDispatch: + raise WuiException('Unknown action "%s" requested' % (self._sAction,)); + + # + # Call action handler and generate the page (if necessary). + # + if self._oCurUser is not None: + self._debugProcessDispatch(); + if self._dDispatch[self._sAction]() is self.ksDispatchRcAllDone: + return True; + else: + self._unauthorizedUser(); + + if self._sRedirectTo is None: + self._generatePage(); + else: + self._redirectPage(); + return True; + + + def dprint(self, sText): + """ Debug printing. """ + if config.g_kfWebUiDebug: + self._oSrvGlue.dprint(sText); diff --git a/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py b/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py new file mode 100755 index 00000000..b2022a3b --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py @@ -0,0 +1,1290 @@ +# -*- coding: utf-8 -*- +# $Id: wuicontentbase.py $ + +""" +Test Manager Web-UI - Content Base Classes. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard python imports. +import copy; +import sys; + +# Validation Kit imports. +from common import utils, webutils; +from testmanager import config; +from testmanager.webui.wuibase import WuiDispatcherBase, WuiException; +from testmanager.webui.wuihlpform import WuiHlpForm; +from testmanager.core import db; +from testmanager.core.base import AttributeChangeEntryPre; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + unicode = str; # pylint: disable=redefined-builtin,invalid-name + + +class WuiHtmlBase(object): # pylint: disable=too-few-public-methods + """ + Base class for HTML objects. + """ + + def __init__(self): + """Dummy init to shut up pylint.""" + pass; # pylint: disable=unnecessary-pass + + def toHtml(self): + + """ + Must be overridden by sub-classes. + """ + assert False; + return ''; + + def __str__(self): + """ String representation is HTML, simplifying formatting and such. """ + return self.toHtml(); + + +class WuiLinkBase(WuiHtmlBase): # pylint: disable=too-few-public-methods + """ + For passing links from WuiListContentBase._formatListEntry. + """ + + def __init__(self, sName, sUrlBase, dParams = None, sConfirm = None, sTitle = None, + sFragmentId = None, fBracketed = True, sExtraAttrs = ''): + WuiHtmlBase.__init__(self); + self.sName = sName + self.sUrl = sUrlBase + self.sConfirm = sConfirm; + self.sTitle = sTitle; + self.fBracketed = fBracketed; + self.sExtraAttrs = sExtraAttrs; + + if dParams: + # Do some massaging of None arguments. + dParams = dict(dParams); + for sKey in dParams: + if dParams[sKey] is None: + dParams[sKey] = ''; + self.sUrl += '?' + webutils.encodeUrlParams(dParams); + + if sFragmentId is not None: + self.sUrl += '#' + sFragmentId; + + def setBracketed(self, fBracketed): + """Changes the bracketing style.""" + self.fBracketed = fBracketed; + return True; + + def toHtml(self): + """ + Returns a simple HTML anchor element. + """ + sExtraAttrs = self.sExtraAttrs; + if self.sConfirm is not None: + sExtraAttrs += 'onclick=\'return confirm("%s");\' ' % (webutils.escapeAttr(self.sConfirm),); + if self.sTitle is not None: + sExtraAttrs += 'title="%s" ' % (webutils.escapeAttr(self.sTitle),); + if sExtraAttrs and sExtraAttrs[-1] != ' ': + sExtraAttrs += ' '; + + sFmt = '[<a %shref="%s">%s</a>]'; + if not self.fBracketed: + sFmt = '<a %shref="%s">%s</a>'; + return sFmt % (sExtraAttrs, webutils.escapeAttr(self.sUrl), webutils.escapeElem(self.sName)); + + @staticmethod + def estimateStringWidth(sString): + """ + Takes a string and estimate it's width so the caller can pad with + U+2002 before tab in a title text. This is very very rough. + """ + cchWidth = 0; + for ch in sString: + if ch.isupper() or ch in u'wm\u2007\u2003\u2001\u3000': + cchWidth += 2; + else: + cchWidth += 1; + return cchWidth; + + @staticmethod + def getStringWidthPaddingEx(cchWidth, cchMaxWidth): + """ Works with estiamteStringWidth(). """ + if cchWidth + 2 <= cchMaxWidth: + return u'\u2002' * ((cchMaxWidth - cchWidth) * 2 // 3) + return u''; + + @staticmethod + def getStringWidthPadding(sString, cchMaxWidth): + """ Works with estiamteStringWidth(). """ + return WuiLinkBase.getStringWidthPaddingEx(WuiLinkBase.estimateStringWidth(sString), cchMaxWidth); + + @staticmethod + def padStringToWidth(sString, cchMaxWidth): + """ Works with estimateStringWidth. """ + cchWidth = WuiLinkBase.estimateStringWidth(sString); + if cchWidth < cchMaxWidth: + return sString + WuiLinkBase.getStringWidthPaddingEx(cchWidth, cchMaxWidth); + return sString; + + +class WuiTmLink(WuiLinkBase): # pylint: disable=too-few-public-methods + """ Local link to the test manager. """ + + kdDbgParams = []; + + def __init__(self, sName, sUrlBase, dParams = None, sConfirm = None, sTitle = None, + sFragmentId = None, fBracketed = True): + + # Add debug parameters if necessary. + if self.kdDbgParams: + if not dParams: + dParams = dict(self.kdDbgParams); + else: + dParams = dict(dParams); + for sKey in self.kdDbgParams: + if sKey not in dParams: + dParams[sKey] = self.kdDbgParams[sKey]; + + WuiLinkBase.__init__(self, sName, sUrlBase, dParams, sConfirm, sTitle, sFragmentId, fBracketed); + + +class WuiAdminLink(WuiTmLink): # pylint: disable=too-few-public-methods + """ Local link to the test manager's admin portion. """ + + def __init__(self, sName, sAction, tsEffectiveDate = None, dParams = None, sConfirm = None, sTitle = None, + sFragmentId = None, fBracketed = True): + from testmanager.webui.wuiadmin import WuiAdmin; + if not dParams: + dParams = {}; + else: + dParams = dict(dParams); + if sAction is not None: + dParams[WuiAdmin.ksParamAction] = sAction; + if tsEffectiveDate is not None: + dParams[WuiAdmin.ksParamEffectiveDate] = tsEffectiveDate; + WuiTmLink.__init__(self, sName, WuiAdmin.ksScriptName, dParams = dParams, sConfirm = sConfirm, sTitle = sTitle, + sFragmentId = sFragmentId, fBracketed = fBracketed); + +class WuiMainLink(WuiTmLink): # pylint: disable=too-few-public-methods + """ Local link to the test manager's main portion. """ + + def __init__(self, sName, sAction, dParams = None, sConfirm = None, sTitle = None, sFragmentId = None, fBracketed = True): + if not dParams: + dParams = {}; + else: + dParams = dict(dParams); + from testmanager.webui.wuimain import WuiMain; + if sAction is not None: + dParams[WuiMain.ksParamAction] = sAction; + WuiTmLink.__init__(self, sName, WuiMain.ksScriptName, dParams = dParams, sConfirm = sConfirm, sTitle = sTitle, + sFragmentId = sFragmentId, fBracketed = fBracketed); + +class WuiSvnLink(WuiLinkBase): # pylint: disable=too-few-public-methods + """ + For linking to a SVN revision. + """ + def __init__(self, iRevision, sName = None, fBracketed = True, sExtraAttrs = ''): + if sName is None: + sName = 'r%s' % (iRevision,); + WuiLinkBase.__init__(self, sName, config.g_ksTracLogUrlPrefix, { 'rev': iRevision,}, + fBracketed = fBracketed, sExtraAttrs = sExtraAttrs); + +class WuiSvnLinkWithTooltip(WuiSvnLink): # pylint: disable=too-few-public-methods + """ + For linking to a SVN revision with changelog tooltip. + """ + def __init__(self, iRevision, sRepository, sName = None, fBracketed = True): + sExtraAttrs = ' onmouseover="return svnHistoryTooltipShow(event,\'%s\',%s);" onmouseout="return tooltipHide();"' \ + % ( sRepository, iRevision, ); + WuiSvnLink.__init__(self, iRevision, sName = sName, fBracketed = fBracketed, sExtraAttrs = sExtraAttrs); + +class WuiBuildLogLink(WuiLinkBase): + """ + For linking to a build log. + """ + def __init__(self, sUrl, sName = None, fBracketed = True): + assert sUrl; + if sName is None: + sName = 'Build log'; + if not webutils.hasSchema(sUrl): + WuiLinkBase.__init__(self, sName, config.g_ksBuildLogUrlPrefix + sUrl, fBracketed = fBracketed); + else: + WuiLinkBase.__init__(self, sName, sUrl, fBracketed = fBracketed); + +class WuiRawHtml(WuiHtmlBase): # pylint: disable=too-few-public-methods + """ + For passing raw html from WuiListContentBase._formatListEntry. + """ + def __init__(self, sHtml): + self.sHtml = sHtml; + WuiHtmlBase.__init__(self); + + def toHtml(self): + return self.sHtml; + +class WuiHtmlKeeper(WuiHtmlBase): # pylint: disable=too-few-public-methods + """ + For keeping a list of elements, concatenating their toHtml output together. + """ + def __init__(self, aoInitial = None, sSep = ' '): + WuiHtmlBase.__init__(self); + self.sSep = sSep; + self.aoKept = []; + if aoInitial is not None: + if isinstance(aoInitial, WuiHtmlBase): + self.aoKept.append(aoInitial); + else: + self.aoKept.extend(aoInitial); + + def append(self, oObject): + """ Appends one objects. """ + self.aoKept.append(oObject); + + def extend(self, aoObjects): + """ Appends a list of objects. """ + self.aoKept.extend(aoObjects); + + def toHtml(self): + return self.sSep.join(oObj.toHtml() for oObj in self.aoKept); + +class WuiSpanText(WuiRawHtml): # pylint: disable=too-few-public-methods + """ + Outputs the given text within a span of the given CSS class. + """ + def __init__(self, sSpanClass, sText, sTitle = None): + if sTitle is None: + WuiRawHtml.__init__(self, + u'<span class="%s">%s</span>' + % ( webutils.escapeAttr(sSpanClass), webutils.escapeElem(sText),)); + else: + WuiRawHtml.__init__(self, + u'<span class="%s" title="%s">%s</span>' + % ( webutils.escapeAttr(sSpanClass), webutils.escapeAttr(sTitle), webutils.escapeElem(sText),)); + +class WuiElementText(WuiRawHtml): # pylint: disable=too-few-public-methods + """ + Outputs the given element text. + """ + def __init__(self, sText): + WuiRawHtml.__init__(self, webutils.escapeElem(sText)); + + +class WuiContentBase(object): # pylint: disable=too-few-public-methods + """ + Base for the content classes. + """ + + ## The text/symbol for a very short add link. + ksShortAddLink = u'\u2795' + ## HTML hex entity string for ksShortAddLink. + ksShortAddLinkHtml = '➕;' + ## The text/symbol for a very short edit link. + ksShortEditLink = u'\u270D' + ## HTML hex entity string for ksShortDetailsLink. + ksShortEditLinkHtml = '✍' + ## The text/symbol for a very short details link. + ksShortDetailsLink = u'\U0001f6c8\ufe0e' + ## HTML hex entity string for ksShortDetailsLink. + ksShortDetailsLinkHtml = '🛈;︎' + ## The text/symbol for a very short change log / details / previous page link. + ksShortChangeLogLink = u'\u2397' + ## HTML hex entity string for ksShortDetailsLink. + ksShortChangeLogLinkHtml = '⎗' + ## The text/symbol for a very short reports link. + ksShortReportLink = u'\U0001f4ca\ufe0e' + ## HTML hex entity string for ksShortReportLink. + ksShortReportLinkHtml = '📊︎' + ## The text/symbol for a very short test results link. + ksShortTestResultsLink = u'\U0001f5d0\ufe0e' + + + def __init__(self, fnDPrint = None, oDisp = None): + self._oDisp = oDisp; # WuiDispatcherBase. + self._fnDPrint = fnDPrint; + if fnDPrint is None and oDisp is not None: + self._fnDPrint = oDisp.dprint; + + def dprint(self, sText): + """ Debug printing. """ + if self._fnDPrint: + self._fnDPrint(sText); + + @staticmethod + def formatTsShort(oTs): + """ + Formats a timestamp (db rep) into a short form. + """ + oTsZulu = db.dbTimestampToZuluDatetime(oTs); + sTs = oTsZulu.strftime('%Y-%m-%d %H:%M:%SZ'); + return unicode(sTs).replace('-', u'\u2011').replace(' ', u'\u00a0'); + + def getNowTs(self): + """ Gets a database compatible current timestamp from python. See db.dbTimestampPythonNow(). """ + return db.dbTimestampPythonNow(); + + def formatIntervalShort(self, oInterval): + """ + Formats an interval (db rep) into a short form. + """ + # default formatting for negative intervals. + if oInterval.days < 0: + return str(oInterval); + + # Figure the hour, min and sec counts. + cHours = oInterval.seconds // 3600; + cMinutes = (oInterval.seconds % 3600) // 60; + cSeconds = oInterval.seconds - cHours * 3600 - cMinutes * 60; + + # Tailor formatting to the interval length. + if oInterval.days > 0: + if oInterval.days > 1: + return '%d days, %d:%02d:%02d' % (oInterval.days, cHours, cMinutes, cSeconds); + return '1 day, %d:%02d:%02d' % (cHours, cMinutes, cSeconds); + if cMinutes > 0 or cSeconds >= 30 or cHours > 0: + return '%d:%02d:%02d' % (cHours, cMinutes, cSeconds); + if cSeconds >= 10: + return '%d.%ds' % (cSeconds, oInterval.microseconds // 100000); + if cSeconds > 0: + return '%d.%02ds' % (cSeconds, oInterval.microseconds // 10000); + return '%d ms' % (oInterval.microseconds // 1000,); + + @staticmethod + def genericPageWalker(iCurItem, cItems, sHrefFmt, cWidth = 11, iBase = 1, sItemName = 'page'): + """ + Generic page walker generator. + + sHrefFmt has three %s sequences: + 1. The first is the page number link parameter (0-based). + 2. The title text, iBase-based number or text. + 3. The link text, iBase-based number or text. + """ + + # Calc display range. + iStart = 0 if iCurItem - cWidth // 2 <= cWidth // 4 else iCurItem - cWidth // 2; + iEnd = iStart + cWidth; + if iEnd > cItems: + iEnd = cItems; + if cItems > cWidth: + iStart = cItems - cWidth; + + sHtml = u''; + + # Previous page (using << >> because « and » are too tiny). + if iCurItem > 0: + sHtml += '%s ' % sHrefFmt % (iCurItem - 1, 'previous ' + sItemName, '<<'); + else: + sHtml += '<< '; + + # 1 2 3 4... + if iStart > 0: + sHtml += '%s ... \n' % (sHrefFmt % (0, 'first %s' % (sItemName,), 0 + iBase),); + + sHtml += ' \n'.join(sHrefFmt % (i, '%s %d' % (sItemName, i + iBase), i + iBase) if i != iCurItem + else unicode(i + iBase) + for i in range(iStart, iEnd)); + if iEnd < cItems: + sHtml += ' ... %s\n' % (sHrefFmt % (cItems - 1, 'last %s' % (sItemName,), cItems - 1 + iBase)); + + # Next page. + if iCurItem + 1 < cItems: + sHtml += ' %s' % sHrefFmt % (iCurItem + 1, 'next ' + sItemName, '>>'); + else: + sHtml += ' >>'; + + return sHtml; + +class WuiSingleContentBase(WuiContentBase): # pylint: disable=too-few-public-methods + """ + Base for the content classes working on a single data object (oData). + """ + def __init__(self, oData, oDisp = None, fnDPrint = None): + WuiContentBase.__init__(self, oDisp = oDisp, fnDPrint = fnDPrint); + self._oData = oData; # Usually ModelDataBase. + + +class WuiFormContentBase(WuiSingleContentBase): # pylint: disable=too-few-public-methods + """ + Base class for simple input form content classes (single data object). + """ + + ## @name Form mode. + ## @{ + ksMode_Add = 'add'; + ksMode_Edit = 'edit'; + ksMode_Show = 'show'; + ## @} + + ## Default action mappings. + kdSubmitActionMappings = { + ksMode_Add: 'AddPost', + ksMode_Edit: 'EditPost', + }; + + def __init__(self, oData, sMode, sCoreName, oDisp, sTitle, sId = None, fEditable = True, sSubmitAction = None): + WuiSingleContentBase.__init__(self, copy.copy(oData), oDisp); + assert sMode in [self.ksMode_Add, self.ksMode_Edit, self.ksMode_Show]; + assert len(sTitle) > 1; + assert sId is None or sId; + + self._sMode = sMode; + self._sCoreName = sCoreName; + self._sActionBase = 'ksAction' + sCoreName; + self._sTitle = sTitle; + self._sId = sId if sId is not None else (type(oData).__name__.lower() + 'form'); + self._fEditable = fEditable and (oDisp is None or not oDisp.isReadOnlyUser()) + self._sSubmitAction = sSubmitAction; + if sSubmitAction is None and sMode != self.ksMode_Show: + self._sSubmitAction = getattr(oDisp, self._sActionBase + self.kdSubmitActionMappings[sMode]); + self._sRedirectTo = None; + + + def _populateForm(self, oForm, oData): + """ + Populates the form. oData has parameter NULL values. + This must be reimplemented by the child. + """ + _ = oForm; _ = oData; + raise Exception('Reimplement me!'); + + def _generatePostFormContent(self, oData): + """ + Generate optional content that comes below the form. + Returns a list of tuples, where the first tuple element is the title + and the second the content. I.e. similar to show() output. + """ + _ = oData; + return []; + + @staticmethod + def _calcChangeLogEntryLinks(aoEntries, iEntry): + """ + Returns an array of links to go with the change log entry. + """ + _ = aoEntries; _ = iEntry; + ## @todo detect deletion and recreation. + ## @todo view details link. + ## @todo restore link (need new action) + ## @todo clone link. + return []; + + @staticmethod + def _guessChangeLogEntryDescription(aoEntries, iEntry): + """ + Guesses the action + author that caused the change log entry. + Returns descriptive string. + """ + oEntry = aoEntries[iEntry]; + + # Figure the author of the change. + if oEntry.sAuthor is not None: + sAuthor = '%s (#%s)' % (oEntry.sAuthor, oEntry.uidAuthor,); + elif oEntry.uidAuthor is not None: + sAuthor = '#%d (??)' % (oEntry.uidAuthor,); + else: + sAuthor = None; + + # Figure the action. + if oEntry.oOldRaw is None: + if sAuthor is None: + return 'Created by batch job.'; + return 'Created by %s.' % (sAuthor,); + + if sAuthor is None: + return 'Automatically updated.' + return 'Modified by %s.' % (sAuthor,); + + @staticmethod + def formatChangeLogEntry(aoEntries, iEntry, sUrl, dParams): + """ + Formats one change log entry into one or more HTML table rows. + + The sUrl and dParams arguments are used to construct links to historical + data using the tsEffective value. If no links wanted, they'll both be None. + + Note! The parameters are given as array + index in case someone wishes + to access adjacent entries later in order to generate better + change descriptions. + """ + oEntry = aoEntries[iEntry]; + + # Turn the effective date into a URL if we can: + if sUrl: + dParams[WuiDispatcherBase.ksParamEffectiveDate] = oEntry.tsEffective; + sEffective = WuiLinkBase(WuiFormContentBase.formatTsShort(oEntry.tsEffective), sUrl, + dParams, fBracketed = False).toHtml(); + else: + sEffective = webutils.escapeElem(WuiFormContentBase.formatTsShort(oEntry.tsEffective)) + + # The primary row. + sRowClass = 'tmodd' if (iEntry + 1) & 1 else 'tmeven'; + sContent = ' <tr class="%s">\n' \ + ' <td rowspan="%d">%s</td>\n' \ + ' <td rowspan="%d">%s</td>\n' \ + ' <td colspan="3">%s%s</td>\n' \ + ' </tr>\n' \ + % ( sRowClass, + len(oEntry.aoChanges) + 1, sEffective, + len(oEntry.aoChanges) + 1, webutils.escapeElem(WuiFormContentBase.formatTsShort(oEntry.tsExpire)), + WuiFormContentBase._guessChangeLogEntryDescription(aoEntries, iEntry), + ' '.join(oLink.toHtml() for oLink in WuiFormContentBase._calcChangeLogEntryLinks(aoEntries, iEntry)),); + + # Additional rows for each changed attribute. + j = 0; + for oChange in oEntry.aoChanges: + if isinstance(oChange, AttributeChangeEntryPre): + sContent += ' <tr class="%s%s"><td>%s</td>'\ + '<td><div class="tdpre">%s%s%s</div></td>' \ + '<td><div class="tdpre">%s%s%s</div></td></tr>\n' \ + % ( sRowClass, 'odd' if j & 1 else 'even', + webutils.escapeElem(oChange.sAttr), + '<pre>' if oChange.sOldText else '', + webutils.escapeElem(oChange.sOldText), + '</pre>' if oChange.sOldText else '', + '<pre>' if oChange.sNewText else '', + webutils.escapeElem(oChange.sNewText), + '</pre>' if oChange.sNewText else '', ); + else: + sContent += ' <tr class="%s%s"><td>%s</td><td>%s</td><td>%s</td></tr>\n' \ + % ( sRowClass, 'odd' if j & 1 else 'even', + webutils.escapeElem(oChange.sAttr), + webutils.escapeElem(oChange.sOldText), + webutils.escapeElem(oChange.sNewText), ); + j += 1; + + return sContent; + + def _showChangeLogNavi(self, fMoreEntries, iPageNo, cEntriesPerPage, tsNow, sWhere): + """ + Returns the HTML for the change log navigator. + Note! See also _generateNavigation. + """ + sNavigation = '<div class="tmlistnav-%s">\n' % sWhere; + sNavigation += ' <table class="tmlistnavtab">\n' \ + ' <tr>\n'; + dParams = self._oDisp.getParameters(); + dParams[WuiDispatcherBase.ksParamChangeLogEntriesPerPage] = cEntriesPerPage; + dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo; + if tsNow is not None: + dParams[WuiDispatcherBase.ksParamEffectiveDate] = tsNow; + + # Prev and combo box in one cell. Both inside the form for formatting reasons. + sNavigation += ' <td>\n' \ + ' <form name="ChangeLogEntriesPerPageForm" method="GET">\n' + + # Prev + if iPageNo > 0: + dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo - 1; + sNavigation += '<a href="?%s#tmchangelog">Previous</a>\n' \ + % (webutils.encodeUrlParams(dParams),); + dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo; + else: + sNavigation += 'Previous\n'; + + # Entries per page selector. + del dParams[WuiDispatcherBase.ksParamChangeLogEntriesPerPage]; + sNavigation += ' \n' \ + ' <select name="%s" onchange="window.location=\'?%s&%s=\' + ' \ + 'this.options[this.selectedIndex].value + \'#tmchangelog\';" ' \ + 'title="Max change log entries per page">\n' \ + % (WuiDispatcherBase.ksParamChangeLogEntriesPerPage, + webutils.encodeUrlParams(dParams), + WuiDispatcherBase.ksParamChangeLogEntriesPerPage); + dParams[WuiDispatcherBase.ksParamChangeLogEntriesPerPage] = cEntriesPerPage; + + for iEntriesPerPage in [2, 4, 8, 16, 32, 64, 128, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 8192]: + sNavigation += ' <option value="%d" %s>%d entries per page</option>\n' \ + % ( iEntriesPerPage, + 'selected="selected"' if iEntriesPerPage == cEntriesPerPage else '', + iEntriesPerPage ); + sNavigation += ' </select>\n'; + + # End of cell (and form). + sNavigation += ' </form>\n' \ + ' </td>\n'; + + # Next + if fMoreEntries: + dParams[WuiDispatcherBase.ksParamChangeLogPageNo] = iPageNo + 1; + sNavigation += ' <td><a href="?%s#tmchangelog">Next</a></td>\n' \ + % (webutils.encodeUrlParams(dParams),); + else: + sNavigation += ' <td>Next</td>\n'; + + sNavigation += ' </tr>\n' \ + ' </table>\n' \ + '</div>\n'; + return sNavigation; + + def setRedirectTo(self, sRedirectTo): + """ + For setting the hidden redirect-to field. + """ + self._sRedirectTo = sRedirectTo; + return True; + + def showChangeLog(self, aoEntries, fMoreEntries, iPageNo, cEntriesPerPage, tsNow, fShowNavigation = True): + """ + Render the change log, returning raw HTML. + aoEntries is an array of ChangeLogEntry. + """ + sContent = '\n' \ + '<hr>\n' \ + '<div id="tmchangelog">\n' \ + ' <h3>Change Log </h3>\n'; + if fShowNavigation: + sContent += self._showChangeLogNavi(fMoreEntries, iPageNo, cEntriesPerPage, tsNow, 'top'); + sContent += ' <table class="tmtable tmchangelog">\n' \ + ' <thead class="tmheader">' \ + ' <tr>' \ + ' <th rowspan="2">When</th>\n' \ + ' <th rowspan="2">Expire (excl)</th>\n' \ + ' <th colspan="3">Changes</th>\n' \ + ' </tr>\n' \ + ' <tr>\n' \ + ' <th>Attribute</th>\n' \ + ' <th>Old value</th>\n' \ + ' <th>New value</th>\n' \ + ' </tr>\n' \ + ' </thead>\n' \ + ' <tbody>\n'; + + if self._sMode == self.ksMode_Show: + sUrl = self._oDisp.getUrlNoParams(); + dParams = self._oDisp.getParameters(); + else: + sUrl = None; + dParams = None; + + for iEntry, _ in enumerate(aoEntries): + sContent += self.formatChangeLogEntry(aoEntries, iEntry, sUrl, dParams); + + sContent += ' </tbody>\n' \ + ' </table>\n'; + if fShowNavigation and len(aoEntries) >= 8: + sContent += self._showChangeLogNavi(fMoreEntries, iPageNo, cEntriesPerPage, tsNow, 'bottom'); + sContent += '</div>\n\n'; + return sContent; + + def _generateTopRowFormActions(self, oData): + """ + Returns a list of WuiTmLinks. + """ + aoActions = []; + if self._sMode == self.ksMode_Show and self._fEditable: + # Remove _idGen and effective date since we're always editing the current data, + # and make sure the primary ID is present. Also remove change log stuff. + dParams = self._oDisp.getParameters(); + if hasattr(oData, 'ksIdGenAttr'): + sIdGenParam = getattr(oData, 'ksParam_' + oData.ksIdGenAttr); + if sIdGenParam in dParams: + del dParams[sIdGenParam]; + for sParam in [ WuiDispatcherBase.ksParamEffectiveDate, ] + list(WuiDispatcherBase.kasChangeLogParams): + if sParam in dParams: + del dParams[sParam]; + dParams[getattr(oData, 'ksParam_' + oData.ksIdAttr)] = getattr(oData, oData.ksIdAttr); + + dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'Edit'); + aoActions.append(WuiTmLink('Edit', '', dParams)); + + # Add clone operation if available. This uses the same data selection as for showing details. No change log. + if hasattr(self._oDisp, self._sActionBase + 'Clone'): + dParams = self._oDisp.getParameters(); + for sParam in WuiDispatcherBase.kasChangeLogParams: + if sParam in dParams: + del dParams[sParam]; + dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'Clone'); + aoActions.append(WuiTmLink('Clone', '', dParams)); + + elif self._sMode == self.ksMode_Edit: + # Details views the details at a given time, so we need either idGen or an effecive date + regular id. + dParams = {}; + if hasattr(oData, 'ksIdGenAttr'): + sIdGenParam = getattr(oData, 'ksParam_' + oData.ksIdGenAttr); + dParams[sIdGenParam] = getattr(oData, oData.ksIdGenAttr); + elif hasattr(oData, 'tsEffective'): + dParams[WuiDispatcherBase.ksParamEffectiveDate] = oData.tsEffective; + dParams[getattr(oData, 'ksParam_' + oData.ksIdAttr)] = getattr(oData, oData.ksIdAttr); + dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'Details'); + aoActions.append(WuiTmLink('Details', '', dParams)); + + # Add delete operation if available. + if hasattr(self._oDisp, self._sActionBase + 'DoRemove'): + dParams = self._oDisp.getParameters(); + dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'DoRemove'); + dParams[getattr(oData, 'ksParam_' + oData.ksIdAttr)] = getattr(oData, oData.ksIdAttr); + aoActions.append(WuiTmLink('Delete', '', dParams, sConfirm = "Are you absolutely sure?")); + + return aoActions; + + def showForm(self, dErrors = None, sErrorMsg = None): + """ + Render the form. + """ + oForm = WuiHlpForm(self._sId, + '?' + webutils.encodeUrlParams({WuiDispatcherBase.ksParamAction: self._sSubmitAction}), + dErrors if dErrors is not None else {}, + fReadOnly = self._sMode == self.ksMode_Show); + + self._oData.convertToParamNull(); + + # If form cannot be constructed due to some reason we + # need to show this reason + try: + self._populateForm(oForm, self._oData); + if self._sRedirectTo is not None: + oForm.addTextHidden(self._oDisp.ksParamRedirectTo, self._sRedirectTo); + except WuiException as oXcpt: + sContent = unicode(oXcpt) + else: + sContent = oForm.finalize(); + + # Add any post form content. + atPostFormContent = self._generatePostFormContent(self._oData); + if atPostFormContent: + for iSection, tSection in enumerate(atPostFormContent): + (sSectionTitle, sSectionContent) = tSection; + sContent += u'<div id="postform-%d" class="tmformpostsection">\n' % (iSection,); + if sSectionTitle: + sContent += '<h3 class="tmformpostheader">%s</h3>\n' % (webutils.escapeElem(sSectionTitle),); + sContent += u' <div id="postform-%d-content" class="tmformpostcontent">\n' % (iSection,); + sContent += sSectionContent; + sContent += u' </div>\n' \ + u'</div>\n'; + + # Add action to the top. + aoActions = self._generateTopRowFormActions(self._oData); + if aoActions: + sActionLinks = '<p>%s</p>' % (' '.join(unicode(oLink) for oLink in aoActions)); + sContent = sActionLinks + sContent; + + # Add error info to the top. + if sErrorMsg is not None: + sContent = '<p class="tmerrormsg">' + webutils.escapeElem(sErrorMsg) + '</p>\n' + sContent; + + return (self._sTitle, sContent); + + def getListOfItems(self, asListItems = tuple(), asSelectedItems = tuple()): + """ + Format generic list which should be used by HTML form + """ + aoRet = [] + for sListItem in asListItems: + fEnabled = sListItem in asSelectedItems; + aoRet.append((sListItem, fEnabled, sListItem)) + return aoRet + + +class WuiListContentBase(WuiContentBase): + """ + Base for the list content classes. + """ + + def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffectiveDate, sTitle, # pylint: disable=too-many-arguments + sId = None, fnDPrint = None, oDisp = None, aiSelectedSortColumns = None, fTimeNavigation = True): + WuiContentBase.__init__(self, fnDPrint = fnDPrint, oDisp = oDisp); + self._aoEntries = aoEntries; ## @todo should replace this with a Logic object and define methods for querying. + self._iPage = iPage; + self._cItemsPerPage = cItemsPerPage; + self._tsEffectiveDate = tsEffectiveDate; + self._fTimeNavigation = fTimeNavigation; + self._sTitle = sTitle; assert len(sTitle) > 1; + if sId is None: + sId = sTitle.strip().replace(' ', '').lower(); + assert sId.strip(); + self._sId = sId; + self._asColumnHeaders = []; + self._asColumnAttribs = []; + self._aaiColumnSorting = []; ##< list of list of integers + self._aiSelectedSortColumns = aiSelectedSortColumns; ##< list of integers + + def _formatCommentCell(self, sComment, cMaxLines = 3, cchMaxLine = 63): + """ + Helper functions for formatting comment cell. + Returns None or WuiRawHtml instance. + """ + # Nothing to do for empty comments. + if sComment is None: + return None; + sComment = sComment.strip(); + if not sComment: + return None; + + # Restrict the text if necessary, making the whole text available thru mouse-over. + ## @todo this would be better done by java script or smth, so it could automatically adjust to the table size. + if len(sComment) > cchMaxLine or sComment.count('\n') >= cMaxLines: + sShortHtml = ''; + for iLine, sLine in enumerate(sComment.split('\n')): + if iLine >= cMaxLines: + break; + if iLine > 0: + sShortHtml += '<br>\n'; + if len(sLine) > cchMaxLine: + sShortHtml += webutils.escapeElem(sLine[:(cchMaxLine - 3)]); + sShortHtml += '...'; + else: + sShortHtml += webutils.escapeElem(sLine); + return WuiRawHtml('<span class="tmcomment" title="%s">%s</span>' % (webutils.escapeAttr(sComment), sShortHtml,)); + + return WuiRawHtml('<span class="tmcomment">%s</span>' % (webutils.escapeElem(sComment).replace('\n', '<br>'),)); + + def _formatListEntry(self, iEntry): + """ + Formats the specified list entry as a list of column values. + Returns HTML for a table row. + + The child class really need to override this! + """ + # ASSUMES ModelDataBase children. + asRet = []; + for sAttr in self._aoEntries[0].getDataAttributes(): + asRet.append(getattr(self._aoEntries[iEntry], sAttr)); + return asRet; + + def _formatListEntryHtml(self, iEntry): + """ + Formats the specified list entry as HTML. + Returns HTML for a table row. + + The child class can override this to + """ + if (iEntry + 1) & 1: + sRow = u' <tr class="tmodd">\n'; + else: + sRow = u' <tr class="tmeven">\n'; + + aoValues = self._formatListEntry(iEntry); + assert len(aoValues) == len(self._asColumnHeaders), '%s vs %s' % (len(aoValues), len(self._asColumnHeaders)); + + for i, _ in enumerate(aoValues): + if i < len(self._asColumnAttribs) and self._asColumnAttribs[i]: + sRow += u' <td ' + self._asColumnAttribs[i] + '>'; + else: + sRow += u' <td>'; + + if isinstance(aoValues[i], WuiHtmlBase): + sRow += aoValues[i].toHtml(); + elif isinstance(aoValues[i], list): + if aoValues[i]: + for oElement in aoValues[i]: + if isinstance(oElement, WuiHtmlBase): + sRow += oElement.toHtml(); + elif db.isDbTimestamp(oElement): + sRow += webutils.escapeElem(self.formatTsShort(oElement)); + else: + sRow += webutils.escapeElem(unicode(oElement)); + sRow += ' '; + elif db.isDbTimestamp(aoValues[i]): + sRow += webutils.escapeElem(self.formatTsShort(aoValues[i])); + elif db.isDbInterval(aoValues[i]): + sRow += webutils.escapeElem(self.formatIntervalShort(aoValues[i])); + elif aoValues[i] is not None: + sRow += webutils.escapeElem(unicode(aoValues[i])); + + sRow += u'</td>\n'; + + return sRow + u' </tr>\n'; + + @staticmethod + def generateTimeNavigationComboBox(sWhere, dParams, tsEffective): + """ + Generates the HTML for the xxxx ago combo box form. + """ + sNavigation = '<form name="TmTimeNavCombo-%s" method="GET">\n' % (sWhere,); + sNavigation += ' <select name="%s" onchange="window.location=' % (WuiDispatcherBase.ksParamEffectiveDate); + sNavigation += '\'?%s&%s=\' + ' % (webutils.encodeUrlParams(dParams), WuiDispatcherBase.ksParamEffectiveDate) + sNavigation += 'this.options[this.selectedIndex].value;" title="Effective date">\n'; + + aoWayBackPoints = [ + ('+0000-00-00 00:00:00.00', 'Now', ' title="Present Day. Present Time."'), # lain :) + + ('-0000-00-00 01:00:00.00', '1 hour ago', ''), + ('-0000-00-00 02:00:00.00', '2 hours ago', ''), + ('-0000-00-00 03:00:00.00', '3 hours ago', ''), + + ('-0000-00-01 00:00:00.00', '1 day ago', ''), + ('-0000-00-02 00:00:00.00', '2 days ago', ''), + ('-0000-00-03 00:00:00.00', '3 days ago', ''), + + ('-0000-00-07 00:00:00.00', '1 week ago', ''), + ('-0000-00-14 00:00:00.00', '2 weeks ago', ''), + ('-0000-00-21 00:00:00.00', '3 weeks ago', ''), + + ('-0000-01-00 00:00:00.00', '1 month ago', ''), + ('-0000-02-00 00:00:00.00', '2 months ago', ''), + ('-0000-03-00 00:00:00.00', '3 months ago', ''), + ('-0000-04-00 00:00:00.00', '4 months ago', ''), + ('-0000-05-00 00:00:00.00', '5 months ago', ''), + ('-0000-06-00 00:00:00.00', 'Half a year ago', ''), + + ('-0001-00-00 00:00:00.00', '1 year ago', ''), + ] + fSelected = False; + for sTimestamp, sWayBackPointCaption, sExtraAttrs in aoWayBackPoints: + if sTimestamp == tsEffective: + fSelected = True; + sNavigation += ' <option value="%s"%s%s>%s</option>\n' \ + % (webutils.quoteUrl(sTimestamp), + ' selected="selected"' if sTimestamp == tsEffective else '', + sExtraAttrs, sWayBackPointCaption, ); + if not fSelected and tsEffective != '': + sNavigation += ' <option value="%s" selected>%s</option>\n' \ + % (webutils.quoteUrl(tsEffective), WuiContentBase.formatTsShort(tsEffective)) + + sNavigation += ' </select>\n' \ + '</form>\n'; + return sNavigation; + + @staticmethod + def generateTimeNavigationDateTime(sWhere, dParams, sNow): + """ + Generates HTML for a form with date + time input fields. + + Note! Modifies dParams! + """ + + # + # Date + time input fields. We use a java script helper to combine the two + # into a hidden field as there is no portable datetime input field type. + # + sNavigation = '<form method="get" action="?" onchange="timeNavigationUpdateHiddenEffDate(this,\'%s\')">' % (sWhere,); + if sNow is None: + sNow = utils.getIsoTimestamp(); + else: + sNow = utils.normalizeIsoTimestampToZulu(sNow); + asSplit = sNow.split('T'); + sNavigation += ' <input type="date" value="%s" id="EffDate%s"/> ' % (asSplit[0], sWhere, ); + sNavigation += ' <input type="time" value="%s" id="EffTime%s"/> ' % (asSplit[1][:8], sWhere,); + sNavigation += ' <input type="hidden" name="%s" value="%s" id="EffDateTime%s"/>' \ + % (WuiDispatcherBase.ksParamEffectiveDate, webutils.escapeAttr(sNow), sWhere); + for sKey in dParams: + sNavigation += ' <input type="hidden" name="%s" value="%s"/>' \ + % (webutils.escapeAttr(sKey), webutils.escapeAttrToStr(dParams[sKey])); + sNavigation += ' <input type="submit" value="Set"/>\n' \ + '</form>\n'; + return sNavigation; + + ## @todo move to better place! WuiMain uses it. + @staticmethod + def generateTimeNavigation(sWhere, dParams, tsEffectiveAbs, sPreamble = '', sPostamble = '', fKeepPageNo = False): + """ + Returns HTML for time navigation. + + Note! Modifies dParams! + Note! Views without a need for a timescale just stubs this method. + """ + sNavigation = '<div class="tmtimenav-%s tmtimenav">%s' % (sWhere, sPreamble,); + + # + # Prepare the URL parameters. + # + if WuiDispatcherBase.ksParamPageNo in dParams: # Forget about page No when changing a period + del dParams[WuiDispatcherBase.ksParamPageNo] + if not fKeepPageNo and WuiDispatcherBase.ksParamEffectiveDate in dParams: + tsEffectiveParam = dParams[WuiDispatcherBase.ksParamEffectiveDate]; + del dParams[WuiDispatcherBase.ksParamEffectiveDate]; + else: + tsEffectiveParam = '' + + # + # Generate the individual parts. + # + sNavigation += WuiListContentBase.generateTimeNavigationDateTime(sWhere, dParams, tsEffectiveAbs); + sNavigation += WuiListContentBase.generateTimeNavigationComboBox(sWhere, dParams, tsEffectiveParam); + + sNavigation += '%s</div>' % (sPostamble,); + return sNavigation; + + def _generateTimeNavigation(self, sWhere, sPreamble = '', sPostamble = ''): + """ + Returns HTML for time navigation. + + Note! Views without a need for a timescale just stubs this method. + """ + return self.generateTimeNavigation(sWhere, self._oDisp.getParameters(), self._oDisp.getEffectiveDateParam(), + sPreamble, sPostamble) + + @staticmethod + def generateItemPerPageSelector(sWhere, dParams, cCurItemsPerPage): + """ + Generate HTML code for items per page selector. + Note! Modifies dParams! + """ + + # Drop the current page count parameter. + if WuiDispatcherBase.ksParamItemsPerPage in dParams: + del dParams[WuiDispatcherBase.ksParamItemsPerPage]; + + # Remove the current page number. + if WuiDispatcherBase.ksParamPageNo in dParams: + del dParams[WuiDispatcherBase.ksParamPageNo]; + + sHtmlItemsPerPageSelector = '<form name="TmItemsPerPageForm-%s" method="GET" class="tmitemsperpage-%s tmitemsperpage">\n'\ + ' <select name="%s" onchange="window.location=\'?%s&%s=\' + ' \ + 'this.options[this.selectedIndex].value;" title="Max items per page">\n' \ + % (sWhere, WuiDispatcherBase.ksParamItemsPerPage, sWhere, + webutils.encodeUrlParams(dParams), + WuiDispatcherBase.ksParamItemsPerPage) + + acItemsPerPage = [16, 32, 64, 128, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096]; + for cItemsPerPage in acItemsPerPage: + sHtmlItemsPerPageSelector += ' <option value="%d" %s>%d per page</option>\n' \ + % (cItemsPerPage, + 'selected="selected"' if cItemsPerPage == cCurItemsPerPage else '', + cItemsPerPage) + sHtmlItemsPerPageSelector += ' </select>\n' \ + '</form>\n'; + + return sHtmlItemsPerPageSelector + + + def _generateNavigation(self, sWhere): + """ + Return HTML for navigation. + """ + + # + # ASSUMES the dispatcher/controller code fetches one entry more than + # needed to fill the page to indicate further records. + # + sNavigation = '<div class="tmlistnav-%s">\n' % sWhere; + sNavigation += ' <table class="tmlistnavtab">\n' \ + ' <tr>\n'; + dParams = self._oDisp.getParameters(); + dParams[WuiDispatcherBase.ksParamItemsPerPage] = self._cItemsPerPage; + dParams[WuiDispatcherBase.ksParamPageNo] = self._iPage; + if self._tsEffectiveDate is not None: + dParams[WuiDispatcherBase.ksParamEffectiveDate] = self._tsEffectiveDate; + + # Prev + if self._iPage > 0: + dParams[WuiDispatcherBase.ksParamPageNo] = self._iPage - 1; + sNavigation += ' <td align="left"><a href="?%s">Previous</a></td>\n' % (webutils.encodeUrlParams(dParams),); + else: + sNavigation += ' <td></td>\n'; + + # Time scale. + if self._fTimeNavigation: + sNavigation += '<td align="center" class="tmtimenav">'; + sNavigation += self._generateTimeNavigation(sWhere); + sNavigation += '</td>'; + + # page count and next. + sNavigation += '<td align="right" class="tmnextanditemsperpage">\n'; + + if len(self._aoEntries) > self._cItemsPerPage: + dParams[WuiDispatcherBase.ksParamPageNo] = self._iPage + 1; + sNavigation += ' <a href="?%s">Next</a>\n' % (webutils.encodeUrlParams(dParams),); + sNavigation += self.generateItemPerPageSelector(sWhere, dParams, self._cItemsPerPage); + sNavigation += '</td>\n'; + sNavigation += ' </tr>\n' \ + ' </table>\n' \ + '</div>\n'; + return sNavigation; + + def _checkSortingByColumnAscending(self, aiColumns): + """ + Checks if we're sorting by this column. + + Returns 0 if not sorting by this, negative if descending, positive if ascending. The + value indicates the priority (nearer to 0 is higher). + """ + if len(aiColumns) <= len(self._aiSelectedSortColumns): + aiColumns = list(aiColumns); + aiNegColumns = list([-i for i in aiColumns]); # pylint: disable=consider-using-generator + i = 0; + while i + len(aiColumns) <= len(self._aiSelectedSortColumns): + aiSub = list(self._aiSelectedSortColumns[i : i + len(aiColumns)]); + if aiSub == aiColumns: + return 1 + i; + if aiSub == aiNegColumns: + return -1 - i; + i += 1; + return 0; + + def _generateTableHeaders(self): + """ + Generate table headers. + Returns raw html string. + Overridable. + """ + + sHtml = ' <thead class="tmheader"><tr>'; + for iHeader, oHeader in enumerate(self._asColumnHeaders): + if isinstance(oHeader, WuiHtmlBase): + sHtml += '<th>' + oHeader.toHtml() + '</th>'; + elif iHeader < len(self._aaiColumnSorting) and self._aaiColumnSorting[iHeader] is not None: + sHtml += '<th>' + iSorting = self._checkSortingByColumnAscending(self._aaiColumnSorting[iHeader]); + if iSorting > 0: + sDirection = ' ▴' if iSorting == 1 else '<small> ▵</small>'; + sSortParams = ','.join([str(-i) for i in self._aaiColumnSorting[iHeader]]); + else: + sDirection = ''; + if iSorting < 0: + sDirection = ' ▾' if iSorting == -1 else '<small> ▿</small>' + sSortParams = ','.join([str(i) for i in self._aaiColumnSorting[iHeader]]); + sHtml += '<a href="javascript:ahrefActionSortByColumns(\'%s\',[%s]);">' \ + % (WuiDispatcherBase.ksParamSortColumns, sSortParams); + sHtml += webutils.escapeElem(oHeader) + '</a>' + sDirection + '</th>'; + else: + sHtml += '<th>' + webutils.escapeElem(oHeader) + '</th>'; + sHtml += '</tr><thead>\n'; + return sHtml + + def _generateTable(self): + """ + show worker that just generates the table. + """ + + # + # Create a table. + # If no colum headers are provided, fall back on database field + # names, ASSUMING that the entries are ModelDataBase children. + # Note! the cellspacing is for IE8. + # + sPageBody = '<table class="tmtable" id="' + self._sId + '" cellspacing="0">\n'; + + if not self._asColumnHeaders: + self._asColumnHeaders = self._aoEntries[0].getDataAttributes(); + + sPageBody += self._generateTableHeaders(); + + # + # Format the body and close the table. + # + sPageBody += ' <tbody>\n'; + for iEntry in range(min(len(self._aoEntries), self._cItemsPerPage)): + sPageBody += self._formatListEntryHtml(iEntry); + sPageBody += ' </tbody>\n' \ + '</table>\n'; + return sPageBody; + + def _composeTitle(self): + """Composes the title string (return value).""" + sTitle = self._sTitle; + if self._iPage != 0: + sTitle += ' (page ' + unicode(self._iPage + 1) + ')' + if self._tsEffectiveDate is not None: + sTitle += ' as per ' + unicode(self.formatTsShort(self._tsEffectiveDate)); + return sTitle; + + + def show(self, fShowNavigation = True): + """ + Displays the list. + Returns (Title, HTML) on success, raises exception on error. + """ + + sPageBody = '' + if fShowNavigation: + sPageBody += self._generateNavigation('top'); + + if self._aoEntries: + sPageBody += self._generateTable(); + if fShowNavigation: + sPageBody += self._generateNavigation('bottom'); + else: + sPageBody += '<p>No entries.</p>' + + return (self._composeTitle(), sPageBody); + + +class WuiListContentWithActionBase(WuiListContentBase): + """ + Base for the list content with action classes. + """ + + def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffectiveDate, sTitle, # pylint: disable=too-many-arguments + sId = None, fnDPrint = None, oDisp = None, aiSelectedSortColumns = None): + WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffectiveDate, sTitle, sId = sId, + fnDPrint = fnDPrint, oDisp = oDisp, aiSelectedSortColumns = aiSelectedSortColumns); + self._aoActions = None; # List of [ oValue, sText, sHover ] provided by the child class. + self._sAction = None; # Set by the child class. + self._sCheckboxName = None; # Set by the child class. + self._asColumnHeaders = [ WuiRawHtml('<input type="checkbox" onClick="toggle%s(this)">' + % ('' if sId is None else sId)), ]; + self._asColumnAttribs = [ 'align="center"', ]; + self._aaiColumnSorting = [ None, ]; + + def _getCheckBoxColumn(self, iEntry, sValue): + """ + Used by _formatListEntry implementations, returns a WuiRawHtmlBase object. + """ + _ = iEntry; + return WuiRawHtml('<input type="checkbox" name="%s" value="%s">' + % (webutils.escapeAttr(self._sCheckboxName), webutils.escapeAttr(unicode(sValue)))); + + def show(self, fShowNavigation=True): + """ + Displays the list. + Returns (Title, HTML) on success, raises exception on error. + """ + assert self._aoActions is not None; + assert self._sAction is not None; + + sPageBody = '<script language="JavaScript">\n' \ + 'function toggle%s(oSource) {\n' \ + ' aoCheckboxes = document.getElementsByName(\'%s\');\n' \ + ' for(var i in aoCheckboxes)\n' \ + ' aoCheckboxes[i].checked = oSource.checked;\n' \ + '}\n' \ + '</script>\n' \ + % ('' if self._sId is None else self._sId, self._sCheckboxName,); + if fShowNavigation: + sPageBody += self._generateNavigation('top'); + if self._aoEntries: + + sPageBody += '<form action="?%s" method="post" class="tmlistactionform">\n' \ + % (webutils.encodeUrlParams({WuiDispatcherBase.ksParamAction: self._sAction,}),); + sPageBody += self._generateTable(); + + sPageBody += ' <label>Actions</label>\n' \ + ' <select name="%s" id="%s-action-combo" class="tmlistactionform-combo">\n' \ + % (webutils.escapeAttr(WuiDispatcherBase.ksParamListAction), webutils.escapeAttr(self._sId),); + for oValue, sText, _ in self._aoActions: + sPageBody += ' <option value="%s">%s</option>\n' \ + % (webutils.escapeAttr(unicode(oValue)), webutils.escapeElem(sText), ); + sPageBody += ' </select>\n'; + sPageBody += ' <input type="submit"></input>\n'; + sPageBody += '</form>\n'; + if fShowNavigation: + sPageBody += self._generateNavigation('bottom'); + else: + sPageBody += '<p>No entries.</p>' + + return (self._composeTitle(), sPageBody); + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuigraphwiz.py b/src/VBox/ValidationKit/testmanager/webui/wuigraphwiz.py new file mode 100755 index 00000000..1ea7a637 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuigraphwiz.py @@ -0,0 +1,660 @@ +# -*- coding: utf-8 -*- +# $Id: wuigraphwiz.py $ + +""" +Test Manager WUI - Graph Wizard +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + +# Python imports. +import functools; + +# Validation Kit imports. +from testmanager.webui.wuimain import WuiMain; +from testmanager.webui.wuihlpgraph import WuiHlpLineGraphErrorbarY, WuiHlpGraphDataTableEx; +from testmanager.webui.wuireport import WuiReportBase; + +from common import utils, webutils; +from common import constants; + + +class WuiGraphWiz(WuiReportBase): + """Construct a graph for analyzing test results (values) across builds and testboxes.""" + + ## @name Series name parts. + ## @{ + kfSeriesName_TestBox = 1; + kfSeriesName_Product = 2; + kfSeriesName_Branch = 4; + kfSeriesName_BuildType = 8; + kfSeriesName_OsArchs = 16; + kfSeriesName_TestCase = 32; + kfSeriesName_TestCaseArgs = 64; + kfSeriesName_All = 127; + ## @} + + + def __init__(self, oModel, dParams, fSubReport = False, fnDPrint = None, oDisp = None): + WuiReportBase.__init__(self, oModel, dParams, fSubReport = fSubReport, fnDPrint = fnDPrint, oDisp = oDisp); + + # Select graph implementation. + if dParams[WuiMain.ksParamGraphWizImpl] == 'charts': + from testmanager.webui.wuihlpgraphgooglechart import WuiHlpLineGraphErrorbarY as MyGraph; + self.oGraphClass = MyGraph; + elif dParams[WuiMain.ksParamGraphWizImpl] == 'matplotlib': + from testmanager.webui.wuihlpgraphmatplotlib import WuiHlpLineGraphErrorbarY as MyGraph; + self.oGraphClass = MyGraph; + else: + self.oGraphClass = WuiHlpLineGraphErrorbarY; + + + # + def _figureSeriesNameBits(self, aoSeries): + """ Figures out the method (bitmask) to use when naming series. """ + if len(aoSeries) <= 1: + return WuiGraphWiz.kfSeriesName_TestBox; + + # Start with all and drop unnecessary specs one-by-one. + fRet = WuiGraphWiz.kfSeriesName_All; + + if [oSrs.idTestBox for oSrs in aoSeries].count(aoSeries[0].idTestBox) == len(aoSeries): + fRet &= ~WuiGraphWiz.kfSeriesName_TestBox; + + if [oSrs.idBuildCategory for oSrs in aoSeries].count(aoSeries[0].idBuildCategory) == len(aoSeries): + fRet &= ~WuiGraphWiz.kfSeriesName_Product; + fRet &= ~WuiGraphWiz.kfSeriesName_Branch; + fRet &= ~WuiGraphWiz.kfSeriesName_BuildType; + fRet &= ~WuiGraphWiz.kfSeriesName_OsArchs; + else: + if [oSrs.oBuildCategory.sProduct for oSrs in aoSeries].count(aoSeries[0].oBuildCategory.sProduct) == len(aoSeries): + fRet &= ~WuiGraphWiz.kfSeriesName_Product; + if [oSrs.oBuildCategory.sBranch for oSrs in aoSeries].count(aoSeries[0].oBuildCategory.sBranch) == len(aoSeries): + fRet &= ~WuiGraphWiz.kfSeriesName_Branch; + if [oSrs.oBuildCategory.sType for oSrs in aoSeries].count(aoSeries[0].oBuildCategory.sType) == len(aoSeries): + fRet &= ~WuiGraphWiz.kfSeriesName_BuildType; + + # Complicated. + fRet &= ~WuiGraphWiz.kfSeriesName_OsArchs; + daTestBoxes = {}; + for oSeries in aoSeries: + if oSeries.idTestBox in daTestBoxes: + daTestBoxes[oSeries.idTestBox].append(oSeries); + else: + daTestBoxes[oSeries.idTestBox] = [oSeries,]; + for aoSeriesPerTestBox in daTestBoxes.values(): + if len(aoSeriesPerTestBox) >= 0: + asOsArches = aoSeriesPerTestBox[0].oBuildCategory.asOsArches; + for i in range(1, len(aoSeriesPerTestBox)): + if aoSeriesPerTestBox[i].oBuildCategory.asOsArches != asOsArches: + fRet |= WuiGraphWiz.kfSeriesName_OsArchs; + break; + + if aoSeries[0].oTestCaseArgs is None: + fRet &= ~WuiGraphWiz.kfSeriesName_TestCaseArgs; + if [oSrs.idTestCase for oSrs in aoSeries].count(aoSeries[0].idTestCase) == len(aoSeries): + fRet &= ~WuiGraphWiz.kfSeriesName_TestCase; + else: + fRet &= ~WuiGraphWiz.kfSeriesName_TestCase; + if [oSrs.idTestCaseArgs for oSrs in aoSeries].count(aoSeries[0].idTestCaseArgs) == len(aoSeries): + fRet &= ~WuiGraphWiz.kfSeriesName_TestCaseArgs; + + return fRet; + + def _getSeriesNameFromBits(self, oSeries, fBits): + """ Creates a series name from bits (kfSeriesName_xxx). """ + assert fBits != 0; + sName = ''; + + if fBits & WuiGraphWiz.kfSeriesName_Product: + if sName: sName += ' / '; + sName += oSeries.oBuildCategory.sProduct; + + if fBits & WuiGraphWiz.kfSeriesName_Branch: + if sName: sName += ' / '; + sName += oSeries.oBuildCategory.sBranch; + + if fBits & WuiGraphWiz.kfSeriesName_BuildType: + if sName: sName += ' / '; + sName += oSeries.oBuildCategory.sType; + + if fBits & WuiGraphWiz.kfSeriesName_OsArchs: + if sName: sName += ' / '; + sName += ' & '.join(oSeries.oBuildCategory.asOsArches); + + if fBits & WuiGraphWiz.kfSeriesName_TestCaseArgs: + if sName: sName += ' / '; + if oSeries.idTestCaseArgs is not None: + sName += oSeries.oTestCase.sName + ':#' + str(oSeries.idTestCaseArgs); + else: + sName += oSeries.oTestCase.sName; + elif fBits & WuiGraphWiz.kfSeriesName_TestCase: + if sName: sName += ' / '; + sName += oSeries.oTestCase.sName; + + if fBits & WuiGraphWiz.kfSeriesName_TestBox: + if sName: sName += ' / '; + sName += oSeries.oTestBox.sName; + + return sName; + + def _calcGraphName(self, oSeries, fSeriesName, sSampleName): + """ Constructs a name for the graph. """ + fGraphName = ~fSeriesName & ( WuiGraphWiz.kfSeriesName_TestBox + | WuiGraphWiz.kfSeriesName_Product + | WuiGraphWiz.kfSeriesName_Branch + | WuiGraphWiz.kfSeriesName_BuildType + ); + sName = self._getSeriesNameFromBits(oSeries, fGraphName); + if sName: sName += ' - '; + sName += sSampleName; + return sName; + + def _calcSampleName(self, oCollection): + """ Constructs a name for a sample source (collection). """ + if oCollection.sValue is not None: + asSampleName = [oCollection.sValue, 'in',]; + elif oCollection.sType == self._oModel.ksTypeElapsed: + asSampleName = ['Elapsed time', 'for', ]; + elif oCollection.sType == self._oModel.ksTypeResult: + asSampleName = ['Error count', 'for',]; + else: + return 'Invalid collection type: "%s"' % (oCollection.sType,); + + sTestName = ', '.join(oCollection.asTests if oCollection.asTests[0] else oCollection.asTests[1:]); + if sTestName == '': + # Use the testcase name if there is only one for all series. + if not oCollection.aoSeries: + return asSampleName[0]; + if len(oCollection.aoSeries) > 1: + idTestCase = oCollection.aoSeries[0].idTestCase; + for oSeries in oCollection.aoSeries: + if oSeries.idTestCase != idTestCase: + return asSampleName[0]; + sTestName = oCollection.aoSeries[0].oTestCase.sName; + return ' '.join(asSampleName) + ' ' + sTestName; + + + def _splitSeries(self, aoSeries): + """ + Splits the data series (ReportGraphModel.DataSeries) into one or more graphs. + + Returns an array of data series arrays. + """ + # Must be at least two series for something to be splittable. + if len(aoSeries) <= 1: + if not aoSeries: + return []; + return [aoSeries,]; + + # Split on unit. + dUnitSeries = {}; + for oSeries in aoSeries: + if oSeries.iUnit not in dUnitSeries: + dUnitSeries[oSeries.iUnit] = []; + dUnitSeries[oSeries.iUnit].append(oSeries); + + # Sort the per-unit series since the build category was only sorted by ID. + for iUnit in dUnitSeries: + def mycmp(oSelf, oOther): + """ __cmp__ like function. """ + iCmp = utils.stricmp(oSelf.oBuildCategory.sProduct, oOther.oBuildCategory.sProduct); + if iCmp != 0: + return iCmp; + iCmp = utils.stricmp(oSelf.oBuildCategory.sBranch, oOther.oBuildCategory.sBranch); + if iCmp != 0: + return iCmp; + iCmp = utils.stricmp(oSelf.oBuildCategory.sType, oOther.oBuildCategory.sType); + if iCmp != 0: + return iCmp; + iCmp = utils.stricmp(oSelf.oTestBox.sName, oOther.oTestBox.sName); + if iCmp != 0: + return iCmp; + return 0; + dUnitSeries[iUnit] = sorted(dUnitSeries[iUnit], key = functools.cmp_to_key(mycmp)); + + # Split the per-unit series up if necessary. + cMaxPerGraph = self._dParams[WuiMain.ksParamGraphWizMaxPerGraph]; + aaoRet = []; + for aoUnitSeries in dUnitSeries.values(): + while len(aoUnitSeries) > cMaxPerGraph: + aaoRet.append(aoUnitSeries[:cMaxPerGraph]); + aoUnitSeries = aoUnitSeries[cMaxPerGraph:]; + if aoUnitSeries: + aaoRet.append(aoUnitSeries); + + return aaoRet; + + def _configureGraph(self, oGraph): + """ + Configures oGraph according to user parameters and other config settings. + + Returns oGraph. + """ + oGraph.setWidth(self._dParams[WuiMain.ksParamGraphWizWidth]) + oGraph.setHeight(self._dParams[WuiMain.ksParamGraphWizHeight]) + oGraph.setDpi(self._dParams[WuiMain.ksParamGraphWizDpi]) + oGraph.setErrorBarY(self._dParams[WuiMain.ksParamGraphWizErrorBarY]); + oGraph.setFontSize(self._dParams[WuiMain.ksParamGraphWizFontSize]); + if hasattr(oGraph, 'setXkcdStyle'): + oGraph.setXkcdStyle(self._dParams[WuiMain.ksParamGraphWizXkcdStyle]); + + return oGraph; + + def _generateInteractiveForm(self): + """ + Generates the HTML for the interactive form. + Returns (sTopOfForm, sEndOfForm) + """ + + # + # The top of the form. + # + sTop = '<form action="#" method="get" id="graphwiz-form">\n' \ + ' <input type="hidden" name="%s" value="%s"/>\n' \ + ' <input type="hidden" name="%s" value="%u"/>\n' \ + % ( WuiMain.ksParamAction, WuiMain.ksActionGraphWiz, + WuiMain.ksParamGraphWizSrcTestSetId, self._dParams[WuiMain.ksParamGraphWizSrcTestSetId], + ); + + sTop += ' <div id="graphwiz-nav">\n'; + sTop += ' <script type="text/javascript">\n' \ + ' window.onresize = function(){ return graphwizOnResizeRecalcWidth("graphwiz-nav", "%s"); }\n' \ + ' window.onload = function(){ return graphwizOnLoadRememberWidth("graphwiz-nav"); }\n' \ + ' </script>\n' \ + % ( WuiMain.ksParamGraphWizWidth, ); + + # + # Top: First row. + # + sTop += ' <div id="graphwiz-top-1">\n'; + + # time. + sNow = self._dParams[WuiMain.ksParamEffectiveDate]; + if sNow is None: sNow = ''; + sTop += ' <div id="graphwiz-time">\n'; + sTop += ' <label for="%s">Starting:</label>\n' \ + ' <input type="text" name="%s" id="%s" value="%s" class="graphwiz-time-input"/>\n' \ + % ( WuiMain.ksParamEffectiveDate, + WuiMain.ksParamEffectiveDate, WuiMain.ksParamEffectiveDate, sNow, ); + + sTop += ' <input type="hidden" name="%s" value="%u"/>\n' % ( WuiMain.ksParamReportPeriods, 1, ); + sTop += ' <label for="%s"> Going back:\n' \ + ' <input type="text" name="%s" id="%s" value="%s" class="graphwiz-period-input"/>\n' \ + % ( WuiMain.ksParamReportPeriodInHours, + WuiMain.ksParamReportPeriodInHours, WuiMain.ksParamReportPeriodInHours, + utils.formatIntervalHours(self._dParams[WuiMain.ksParamReportPeriodInHours]) ); + sTop += ' </div>\n'; + + # Graph options top row. + sTop += ' <div id="graphwiz-top-options-1">\n'; + + # graph type. + sTop += ' <label for="%s">Graph:</label>\n' \ + ' <select name="%s" id="%s">\n' \ + % ( WuiMain.ksParamGraphWizImpl, WuiMain.ksParamGraphWizImpl, WuiMain.ksParamGraphWizImpl, ); + for (sImpl, sDesc) in WuiMain.kaasGraphWizImplCombo: + sTop += ' <option value="%s"%s>%s</option>\n' \ + % (sImpl, ' selected' if sImpl == self._dParams[WuiMain.ksParamGraphWizImpl] else '', sDesc); + sTop += ' </select>\n'; + + # graph size. + sTop += ' <label for="%s">Graph size:</label>\n' \ + ' <input type="text" name="%s" id="%s" value="%s" class="graphwiz-pixel-input"> x\n' \ + ' <input type="text" name="%s" id="%s" value="%s" class="graphwiz-pixel-input">\n' \ + ' <label for="%s">Dpi:</label>'\ + ' <input type="text" name="%s" id="%s" value="%s" class="graphwiz-dpi-input">\n' \ + ' <button type="button" onclick="%s">Defaults</button>\n' \ + % ( WuiMain.ksParamGraphWizWidth, + WuiMain.ksParamGraphWizWidth, WuiMain.ksParamGraphWizWidth, self._dParams[WuiMain.ksParamGraphWizWidth], + WuiMain.ksParamGraphWizHeight, WuiMain.ksParamGraphWizHeight, self._dParams[WuiMain.ksParamGraphWizHeight], + WuiMain.ksParamGraphWizDpi, + WuiMain.ksParamGraphWizDpi, WuiMain.ksParamGraphWizDpi, self._dParams[WuiMain.ksParamGraphWizDpi], + webutils.escapeAttr('return graphwizSetDefaultSizeValues("graphwiz-nav", "%s", "%s", "%s");' + % ( WuiMain.ksParamGraphWizWidth, WuiMain.ksParamGraphWizHeight, + WuiMain.ksParamGraphWizDpi )), + ); + + sTop += ' </div>\n'; # (options row 1) + + sTop += ' </div>\n'; # (end of row 1) + + # + # Top: Second row. + # + sTop += ' <div id="graphwiz-top-2">\n'; + + # Submit + sFormButton = '<button type="submit">Refresh</button>\n'; + sTop += ' <div id="graphwiz-top-submit">' + sFormButton + '</div>\n'; + + + # Options. + sTop += ' <div id="graphwiz-top-options-2">\n'; + + sTop += ' <input type="checkbox" name="%s" id="%s" value="1"%s/>\n' \ + ' <label for="%s">Tabular data</label>\n' \ + % ( WuiMain.ksParamGraphWizTabular, WuiMain.ksParamGraphWizTabular, + ' checked' if self._dParams[WuiMain.ksParamGraphWizTabular] else '', + WuiMain.ksParamGraphWizTabular); + + if hasattr(self.oGraphClass, 'setXkcdStyle'): + sTop += ' <input type="checkbox" name="%s" id="%s" value="1"%s/>\n' \ + ' <label for="%s">xkcd-style</label>\n' \ + % ( WuiMain.ksParamGraphWizXkcdStyle, WuiMain.ksParamGraphWizXkcdStyle, + ' checked' if self._dParams[WuiMain.ksParamGraphWizXkcdStyle] else '', + WuiMain.ksParamGraphWizXkcdStyle); + elif self._dParams[WuiMain.ksParamGraphWizXkcdStyle]: + sTop += ' <input type="hidden" name="%s" id="%s" value="1"/>\n' \ + % ( WuiMain.ksParamGraphWizXkcdStyle, WuiMain.ksParamGraphWizXkcdStyle, ); + + if not hasattr(self.oGraphClass, 'kfNoErrorBarsSupport'): + sTop += ' <input type="checkbox" name="%s" id="%s" value="1"%s title="%s"/>\n' \ + ' <label for="%s">Error bars,</label>\n' \ + ' <label for="%s">max: </label>\n' \ + ' <input type="text" name="%s" id="%s" value="%s" class="graphwiz-maxerrorbar-input" title="%s"/>\n' \ + % ( WuiMain.ksParamGraphWizErrorBarY, WuiMain.ksParamGraphWizErrorBarY, + ' checked' if self._dParams[WuiMain.ksParamGraphWizErrorBarY] else '', + 'Error bars shows some of the max and min results on the Y-axis.', + WuiMain.ksParamGraphWizErrorBarY, + WuiMain.ksParamGraphWizMaxErrorBarY, + WuiMain.ksParamGraphWizMaxErrorBarY, WuiMain.ksParamGraphWizMaxErrorBarY, + self._dParams[WuiMain.ksParamGraphWizMaxErrorBarY], + 'Maximum number of Y-axis error bar per graph. (Too many makes it unreadable.)' + ); + else: + if self._dParams[WuiMain.ksParamGraphWizErrorBarY]: + sTop += '<input type="hidden" name="%s" id="%s" value="1">\n' \ + % ( WuiMain.ksParamGraphWizErrorBarY, WuiMain.ksParamGraphWizErrorBarY, ); + sTop += '<input type="hidden" name="%s" id="%s" value="%u">\n' \ + % ( WuiMain.ksParamGraphWizMaxErrorBarY, WuiMain.ksParamGraphWizMaxErrorBarY, + self._dParams[WuiMain.ksParamGraphWizMaxErrorBarY], ); + + sTop += ' <label for="%s">Font size: </label>\n' \ + ' <input type="text" name="%s" id="%s" value="%s" class="graphwiz-fontsize-input"/>\n' \ + % ( WuiMain.ksParamGraphWizFontSize, + WuiMain.ksParamGraphWizFontSize, WuiMain.ksParamGraphWizFontSize, + self._dParams[WuiMain.ksParamGraphWizFontSize], ); + + sTop += ' <label for="%s">Data series: </label>\n' \ + ' <input type="text" name="%s" id="%s" value="%s" class="graphwiz-maxpergraph-input" title="%s"/>\n' \ + % ( WuiMain.ksParamGraphWizMaxPerGraph, + WuiMain.ksParamGraphWizMaxPerGraph, WuiMain.ksParamGraphWizMaxPerGraph, + self._dParams[WuiMain.ksParamGraphWizMaxPerGraph], + 'Max data series per graph.' ); + + sTop += ' </div>\n'; # (options row 2) + + sTop += ' </div>\n'; # (end of row 2) + + sTop += ' </div>\n'; # end of top. + + # + # The end of the page selection. + # + sEnd = ' <div id="graphwiz-end-selection">\n'; + + # + # Testbox selection + # + aidTestBoxes = list(self._dParams[WuiMain.ksParamGraphWizTestBoxIds]); + sEnd += ' <div id="graphwiz-testboxes" class="graphwiz-end-selection-group">\n' \ + ' <h3>TestBox Selection:</h3>\n' \ + ' <ol class="tmgraph-testboxes">\n'; + + # Get a list of eligible testboxes from the DB. + for oTestBox in self._oModel.getEligibleTestBoxes(): + try: aidTestBoxes.remove(oTestBox.idTestBox); + except: sChecked = ''; + else: sChecked = ' checked'; + sEnd += ' <li><input type="checkbox" name="%s" value="%s" id="gw-tb-%u"%s/>' \ + '<label for="gw-tb-%u">%s</label></li>\n' \ + % ( WuiMain.ksParamGraphWizTestBoxIds, oTestBox.idTestBox, oTestBox.idTestBox, sChecked, + oTestBox.idTestBox, oTestBox.sName); + + # List testboxes that have been checked in a different period or something. + for idTestBox in aidTestBoxes: + oTestBox = self._oModel.oCache.getTestBox(idTestBox); + sEnd += ' <li><input type="checkbox" name="%s" value="%s" id="gw-tb-%u" checked/>' \ + '<label for="gw-tb-%u">%s</label></li>\n' \ + % ( WuiMain.ksParamGraphWizTestBoxIds, oTestBox.idTestBox, oTestBox.idTestBox, + oTestBox.idTestBox, oTestBox.sName); + + sEnd += ' </ol>\n' \ + ' </div>\n'; + + # + # Build category selection. + # + aidBuildCategories = list(self._dParams[WuiMain.ksParamGraphWizBuildCatIds]); + sEnd += ' <div id="graphwiz-buildcategories" class="graphwiz-end-selection-group">\n' \ + ' <h3>Build Category Selection:</h3>\n' \ + ' <ol class="tmgraph-buildcategories">\n'; + for oBuildCat in self._oModel.getEligibleBuildCategories(): + try: aidBuildCategories.remove(oBuildCat.idBuildCategory); + except: sChecked = ''; + else: sChecked = ' checked'; + sEnd += ' <li><input type="checkbox" name="%s" value="%s" id="gw-bc-%u" %s/>' \ + '<label for="gw-bc-%u">%s / %s / %s / %s</label></li>\n' \ + % ( WuiMain.ksParamGraphWizBuildCatIds, oBuildCat.idBuildCategory, oBuildCat.idBuildCategory, sChecked, + oBuildCat.idBuildCategory, + oBuildCat.sProduct, oBuildCat.sBranch, oBuildCat.sType, ' & '.join(oBuildCat.asOsArches) ); + assert not aidBuildCategories; # SQL should return all currently selected. + + sEnd += ' </ol>\n' \ + ' </div>\n'; + + # + # Testcase variations. + # + sEnd += ' <div id="graphwiz-testcase-variations" class="graphwiz-end-selection-group">\n' \ + ' <h3>Miscellaneous:</h3>\n' \ + ' <ol>'; + + sEnd += ' <li>\n' \ + ' <input type="checkbox" id="%s" name="%s" value="1"%s/>\n' \ + ' <label for="%s">Separate by testcase variation.</label>\n' \ + ' </li>\n' \ + % ( WuiMain.ksParamGraphWizSepTestVars, WuiMain.ksParamGraphWizSepTestVars, + ' checked' if self._dParams[WuiMain.ksParamGraphWizSepTestVars] else '', + WuiMain.ksParamGraphWizSepTestVars ); + + + sEnd += ' <li>\n' \ + ' <lable for="%s">Test case ID:</label>\n' \ + ' <input type="text" id="%s" name="%s" value="%s" readonly/>\n' \ + ' </li>\n' \ + % ( WuiMain.ksParamGraphWizTestCaseIds, + WuiMain.ksParamGraphWizTestCaseIds, WuiMain.ksParamGraphWizTestCaseIds, + ','.join([str(i) for i in self._dParams[WuiMain.ksParamGraphWizTestCaseIds]]), ); + + sEnd += ' </ol>\n' \ + ' </div>\n'; + + #sEnd += ' <h3> </h3>\n'; + + # + # Finish up the form. + # + sEnd += ' <div id="graphwiz-end-submit"><p>' + sFormButton + '</p></div>\n'; + sEnd += ' </div>\n' \ + '</form>\n'; + + return (sTop, sEnd); + + def generateReportBody(self): + fInteractive = not self._fSubReport; + + # Quick mockup. + self._sTitle = 'Graph Wizzard'; + + sHtml = ''; + sHtml += '<h2>Incomplete code - no complaints yet, thank you!!</h2>\n'; + + # + # Create a form for altering the data we're working with. + # + if fInteractive: + (sTopOfForm, sEndOfForm) = self._generateInteractiveForm(); + sHtml += sTopOfForm; + del sTopOfForm; + + # + # Emit the graphs. At least one per sample source. + # + sHtml += ' <div id="graphwiz-graphs">\n'; + iGraph = 0; + aoCollections = self._oModel.fetchGraphData(); + for iCollection, oCollection in enumerate(aoCollections): + # Name the graph and add a checkbox for removing it. + sSampleName = self._calcSampleName(oCollection); + sHtml += ' <div class="graphwiz-collection" id="graphwiz-source-%u">\n' % (iCollection,); + if fInteractive: + sHtml += ' <div class="graphwiz-src-select">\n' \ + ' <input type="checkbox" name="%s" id="%s" value="%s:%s%s" checked class="graphwiz-src-input">\n' \ + ' <label for="%s">%s</label>\n' \ + ' </div>\n' \ + % ( WuiMain.ksParamReportSubjectIds, WuiMain.ksParamReportSubjectIds, oCollection.sType, + ':'.join([str(idStr) for idStr in oCollection.aidStrTests]), + ':%u' % oCollection.idStrValue if oCollection.idStrValue else '', + WuiMain.ksParamReportSubjectIds, sSampleName ); + + if oCollection.aoSeries: + # + # Split the series into sub-graphs as needed and produce SVGs. + # + aaoSeries = self._splitSeries(oCollection.aoSeries); + for aoSeries in aaoSeries: + # Gather the data for this graph. (Most big stuff is passed by + # reference, so there shouldn't be any large memory penalty for + # repacking the data here.) + sYUnit = None; + if aoSeries[0].iUnit < len(constants.valueunit.g_asNames) and aoSeries[0].iUnit > 0: + sYUnit = constants.valueunit.g_asNames[aoSeries[0].iUnit]; + oData = WuiHlpGraphDataTableEx(sXUnit = 'Build revision', sYUnit = sYUnit); + + fSeriesName = self._figureSeriesNameBits(aoSeries); + for oSeries in aoSeries: + sSeriesName = self._getSeriesNameFromBits(oSeries, fSeriesName); + asHtmlTooltips = None; + if len(oSeries.aoRevInfo) == len(oSeries.aiRevisions): + asHtmlTooltips = []; + for i, oRevInfo in enumerate(oSeries.aoRevInfo): + sPlusMinus = ''; + if oSeries.acSamples[i] > 1: + sPlusMinus = ' (+%s/-%s; %u samples)' \ + % ( utils.formatNumber(oSeries.aiErrorBarAbove[i]), + utils.formatNumber(oSeries.aiErrorBarBelow[i]), + oSeries.acSamples[i]) + sTooltip = '<table class=\'graphwiz-tt\'><tr><td>%s:</td><td>%s %s %s</td></tr>'\ + '<tr><td>Rev:</td><td>r%s</td></tr>' \ + % ( sSeriesName, + utils.formatNumber(oSeries.aiValues[i]), + sYUnit, sPlusMinus, + oSeries.aiRevisions[i], + ); + if oRevInfo.sAuthor is not None: + sMsg = oRevInfo.sMessage[:80].strip(); + #if sMsg.find('\n') >= 0: + # sMsg = sMsg[:sMsg.find('\n')].strip(); + sTooltip += '<tr><td>Author:</td><td>%s</td></tr>' \ + '<tr><td>Date:</td><td>%s</td><tr>' \ + '<tr><td>Message:</td><td>%s%s</td></tr>' \ + % ( oRevInfo.sAuthor, + self.formatTsShort(oRevInfo.tsCreated), + sMsg, '...' if len(oRevInfo.sMessage) > len(sMsg) else ''); + sTooltip += '</table>'; + asHtmlTooltips.append(sTooltip); + oData.addDataSeries(sSeriesName, oSeries.aiRevisions, oSeries.aiValues, asHtmlTooltips, + oSeries.aiErrorBarBelow, oSeries.aiErrorBarAbove); + # Render the data into a graph. + oGraph = self.oGraphClass('tmgraph-%u' % (iGraph,), oData, self._oDisp); + self._configureGraph(oGraph); + + oGraph.setTitle(self._calcGraphName(aoSeries[0], fSeriesName, sSampleName)); + sHtml += ' <div class="graphwiz-graph" id="graphwiz-graph-%u">\n' % (iGraph,); + sHtml += oGraph.renderGraph(); + sHtml += '\n </div>\n'; + iGraph += 1; + + # + # Emit raw tabular data if requested. + # + if self._dParams[WuiMain.ksParamGraphWizTabular]: + sHtml += ' <div class="graphwiz-tab-div" id="graphwiz-tab-%u">\n' \ + ' <table class="tmtable graphwiz-tab">\n' \ + % (iCollection, ); + for aoSeries in aaoSeries: + if aoSeries[0].iUnit < len(constants.valueunit.g_asNames) and aoSeries[0].iUnit > 0: + sUnit = constants.valueunit.g_asNames[aoSeries[0].iUnit]; + else: + sUnit = str(aoSeries[0].iUnit); + + for iSeries, oSeries in enumerate(aoSeries): + sColor = self.oGraphClass.calcSeriesColor(iSeries); + + sHtml += '<thead class="tmheader">\n' \ + ' <tr class="graphwiz-tab graphwiz-tab-new-series-row">\n' \ + ' <th colspan="5"><span style="background-color:%s;"> </span> %s</th>\n' \ + ' </tr>\n' \ + ' <tr class="graphwiz-tab graphwiz-tab-col-hdr-row">\n' \ + ' <th>Revision</th><th>Value (%s)</th><th>Δmax</th><th>Δmin</th>' \ + '<th>Samples</th>\n' \ + ' </tr>\n' \ + '</thead>\n' \ + % ( sColor, + self._getSeriesNameFromBits(oSeries, self.kfSeriesName_All & ~self.kfSeriesName_OsArchs), + sUnit ); + + for i, iRevision in enumerate(oSeries.aiRevisions): + sHtml += ' <tr class="%s"><td>r%s</td><td>%s</td><td>+%s</td><td>-%s</td><td>%s</td></tr>\n' \ + % ( 'tmodd' if i & 1 else 'tmeven', + iRevision, oSeries.aiValues[i], + oSeries.aiErrorBarAbove[i], oSeries.aiErrorBarBelow[i], + oSeries.acSamples[i]); + sHtml += ' </table>\n' \ + ' </div>\n'; + else: + sHtml += '<i>No results.</i>\n'; + sHtml += ' </div>\n' + sHtml += ' </div>\n'; + + # + # Finish the form. + # + if fInteractive: + sHtml += sEndOfForm; + + return sHtml; + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuihlpform.py b/src/VBox/ValidationKit/testmanager/webui/wuihlpform.py new file mode 100755 index 00000000..ce55c84d --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuihlpform.py @@ -0,0 +1,1111 @@ +# -*- coding: utf-8 -*- +# $Id: wuihlpform.py $ + +""" +Test Manager Web-UI - Form Helpers. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + +# Standard python imports. +import copy; +import sys; + +# Validation Kit imports. +from common import utils; +from common.webutils import escapeAttr, escapeElem; +from testmanager import config; +from testmanager.core.schedgroup import SchedGroupMemberData, SchedGroupDataEx; +from testmanager.core.testcaseargs import TestCaseArgsData; +from testmanager.core.testgroup import TestGroupMemberData, TestGroupDataEx; +from testmanager.core.testbox import TestBoxDataForSchedGroup; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + unicode = str; # pylint: disable=redefined-builtin,invalid-name + + +class WuiHlpForm(object): + """ + Helper for constructing a form. + """ + + ksItemsList = 'ksItemsList' + + ksOnSubmit_AddReturnToFieldWithCurrentUrl = '+AddReturnToFieldWithCurrentUrl+'; + + def __init__(self, sId, sAction, dErrors = None, fReadOnly = False, sOnSubmit = None): + self._fFinalized = False; + self._fReadOnly = fReadOnly; + self._dErrors = dErrors if dErrors is not None else {}; + + if sOnSubmit == self.ksOnSubmit_AddReturnToFieldWithCurrentUrl: + sOnSubmit = u'return addRedirectToInputFieldWithCurrentUrl(this)'; + if sOnSubmit is None: sOnSubmit = u''; + else: sOnSubmit = u' onsubmit=\"%s\"' % (escapeAttr(sOnSubmit),); + + self._sBody = u'\n' \ + u'<div id="%s" class="tmform">\n' \ + u' <form action="%s" method="post"%s>\n' \ + u' <ul>\n' \ + % (sId, sAction, sOnSubmit); + + def _add(self, sText): + """Internal worker for appending text to the body.""" + assert not self._fFinalized; + if not self._fFinalized: + self._sBody += utils.toUnicode(sText, errors='ignore'); + return True; + return False; + + def _escapeErrorText(self, sText): + """Escapes error text, preserving some predefined HTML tags.""" + if sText.find('<br>') >= 0: + asParts = sText.split('<br>'); + for i, _ in enumerate(asParts): + asParts[i] = escapeElem(asParts[i].strip()); + sText = '<br>\n'.join(asParts); + else: + sText = escapeElem(sText); + return sText; + + def _addLabel(self, sName, sLabel, sDivSubClass = 'normal'): + """Internal worker for adding a label.""" + if sName in self._dErrors: + sError = self._dErrors[sName]; + if utils.isString(sError): # List error trick (it's an associative array). + return self._add(u' <li>\n' + u' <div class="tmform-field"><div class="tmform-field-%s">\n' + u' <label for="%s" class="tmform-error-label">%s\n' + u' <span class="tmform-error-desc">%s</span>\n' + u' </label>\n' + % (escapeAttr(sDivSubClass), escapeAttr(sName), escapeElem(sLabel), + self._escapeErrorText(sError), ) ); + return self._add(u' <li>\n' + u' <div class="tmform-field"><div class="tmform-field-%s">\n' + u' <label for="%s">%s</label>\n' + % (escapeAttr(sDivSubClass), escapeAttr(sName), escapeElem(sLabel)) ); + + + def finalize(self): + """ + Finalizes the form and returns the body. + """ + if not self._fFinalized: + self._add(u' </ul>\n' + u' </form>\n' + u'</div>\n' + u'<div class="clear"></div>\n' ); + return self._sBody; + + def addTextHidden(self, sName, sValue, sExtraAttribs = ''): + """Adds a hidden text input.""" + return self._add(u' <div class="tmform-field-hidden">\n' + u' <input name="%s" id="%s" type="text" hidden%s value="%s" class="tmform-hidden">\n' + u' </div>\n' + u' </li>\n' + % ( escapeAttr(sName), escapeAttr(sName), sExtraAttribs, escapeElem(str(sValue)) )); + # + # Non-input stuff. + # + def addNonText(self, sValue, sLabel, sName = 'non-text', sPostHtml = ''): + """Adds a read-only text input.""" + self._addLabel(sName, sLabel, 'string'); + if sValue is None: sValue = ''; + return self._add(u' <p>%s%s</p>\n' + u' </div></div>\n' + u' </li>\n' + % (escapeElem(unicode(sValue)), sPostHtml )); + + def addRawHtml(self, sRawHtml, sLabel, sName = 'raw-html'): + """Adds a read-only text input.""" + self._addLabel(sName, sLabel, 'string'); + self._add(sRawHtml); + return self._add(u' </div></div>\n' + u' </li>\n'); + + + # + # Text input fields. + # + def addText(self, sName, sValue, sLabel, sSubClass = 'string', sExtraAttribs = '', sPostHtml = ''): + """Adds a text input.""" + if self._fReadOnly: + return self.addTextRO(sName, sValue, sLabel, sSubClass, sExtraAttribs); + if sSubClass not in ('int', 'long', 'string', 'uuid', 'timestamp', 'wide'): raise Exception(sSubClass); + self._addLabel(sName, sLabel, sSubClass); + if sValue is None: sValue = ''; + return self._add(u' <input name="%s" id="%s" type="text"%s value="%s">%s\n' + u' </div></div>\n' + u' </li>\n' + % ( escapeAttr(sName), escapeAttr(sName), sExtraAttribs, escapeAttr(unicode(sValue)), sPostHtml )); + + def addTextRO(self, sName, sValue, sLabel, sSubClass = 'string', sExtraAttribs = '', sPostHtml = ''): + """Adds a read-only text input.""" + if sSubClass not in ('int', 'long', 'string', 'uuid', 'timestamp', 'wide'): raise Exception(sSubClass); + self._addLabel(sName, sLabel, sSubClass); + if sValue is None: sValue = ''; + return self._add(u' <input name="%s" id="%s" type="text" readonly%s value="%s" class="tmform-input-readonly">' + u'%s\n' + u' </div></div>\n' + u' </li>\n' + % ( escapeAttr(sName), escapeAttr(sName), sExtraAttribs, escapeAttr(unicode(sValue)), sPostHtml )); + + def addWideText(self, sName, sValue, sLabel, sExtraAttribs = '', sPostHtml = ''): + """Adds a wide text input.""" + return self.addText(sName, sValue, sLabel, 'wide', sExtraAttribs, sPostHtml = sPostHtml); + + def addWideTextRO(self, sName, sValue, sLabel, sExtraAttribs = '', sPostHtml = ''): + """Adds a wide read-only text input.""" + return self.addTextRO(sName, sValue, sLabel, 'wide', sExtraAttribs, sPostHtml = sPostHtml); + + def _adjustMultilineTextAttribs(self, sExtraAttribs, sValue): + """ Internal helper for setting good default sizes for textarea based on content.""" + if sExtraAttribs.find('cols') < 0 and sExtraAttribs.find('width') < 0: + sExtraAttribs = 'cols="96%" ' + sExtraAttribs; + + if sExtraAttribs.find('rows') < 0 and sExtraAttribs.find('width') < 0: + if sValue is None: sValue = ''; + else: sValue = sValue.strip(); + + cRows = sValue.count('\n') + (not sValue.endswith('\n')); + if cRows * 80 < len(sValue): + cRows += 2; + cRows = max(min(cRows, 16), 2); + sExtraAttribs = ('rows="%s" ' % (cRows,)) + sExtraAttribs; + + return sExtraAttribs; + + def addMultilineText(self, sName, sValue, sLabel, sSubClass = 'string', sExtraAttribs = ''): + """Adds a multiline text input.""" + if self._fReadOnly: + return self.addMultilineTextRO(sName, sValue, sLabel, sSubClass, sExtraAttribs); + if sSubClass not in ('int', 'long', 'string', 'uuid', 'timestamp'): raise Exception(sSubClass) + self._addLabel(sName, sLabel, sSubClass) + if sValue is None: sValue = ''; + sNewValue = unicode(sValue) if not isinstance(sValue, list) else '\n'.join(sValue) + return self._add(u' <textarea name="%s" id="%s" %s>%s</textarea>\n' + u' </div></div>\n' + u' </li>\n' + % ( escapeAttr(sName), escapeAttr(sName), self._adjustMultilineTextAttribs(sExtraAttribs, sNewValue), + escapeElem(sNewValue))) + + def addMultilineTextRO(self, sName, sValue, sLabel, sSubClass = 'string', sExtraAttribs = ''): + """Adds a multiline read-only text input.""" + if sSubClass not in ('int', 'long', 'string', 'uuid', 'timestamp'): raise Exception(sSubClass) + self._addLabel(sName, sLabel, sSubClass) + if sValue is None: sValue = ''; + sNewValue = unicode(sValue) if not isinstance(sValue, list) else '\n'.join(sValue) + return self._add(u' <textarea name="%s" id="%s" readonly %s>%s</textarea>\n' + u' </div></div>\n' + u' </li>\n' + % ( escapeAttr(sName), escapeAttr(sName), self._adjustMultilineTextAttribs(sExtraAttribs, sNewValue), + escapeElem(sNewValue))) + + def addInt(self, sName, iValue, sLabel, sExtraAttribs = '', sPostHtml = ''): + """Adds an integer input.""" + return self.addText(sName, unicode(iValue), sLabel, 'int', sExtraAttribs, sPostHtml = sPostHtml); + + def addIntRO(self, sName, iValue, sLabel, sExtraAttribs = '', sPostHtml = ''): + """Adds an integer input.""" + return self.addTextRO(sName, unicode(iValue), sLabel, 'int', sExtraAttribs, sPostHtml = sPostHtml); + + def addLong(self, sName, lValue, sLabel, sExtraAttribs = '', sPostHtml = ''): + """Adds a long input.""" + return self.addText(sName, unicode(lValue), sLabel, 'long', sExtraAttribs, sPostHtml = sPostHtml); + + def addLongRO(self, sName, lValue, sLabel, sExtraAttribs = '', sPostHtml = ''): + """Adds a long input.""" + return self.addTextRO(sName, unicode(lValue), sLabel, 'long', sExtraAttribs, sPostHtml = sPostHtml); + + def addUuid(self, sName, uuidValue, sLabel, sExtraAttribs = '', sPostHtml = ''): + """Adds an UUID input.""" + return self.addText(sName, unicode(uuidValue), sLabel, 'uuid', sExtraAttribs, sPostHtml = sPostHtml); + + def addUuidRO(self, sName, uuidValue, sLabel, sExtraAttribs = '', sPostHtml = ''): + """Adds a read-only UUID input.""" + return self.addTextRO(sName, unicode(uuidValue), sLabel, 'uuid', sExtraAttribs, sPostHtml = sPostHtml); + + def addTimestampRO(self, sName, sTimestamp, sLabel, sExtraAttribs = '', sPostHtml = ''): + """Adds a read-only database string timstamp input.""" + return self.addTextRO(sName, sTimestamp, sLabel, 'timestamp', sExtraAttribs, sPostHtml = sPostHtml); + + + # + # Text areas. + # + + + # + # Combo boxes. + # + def addComboBox(self, sName, sSelected, sLabel, aoOptions, sExtraAttribs = '', sPostHtml = ''): + """Adds a combo box.""" + if self._fReadOnly: + return self.addComboBoxRO(sName, sSelected, sLabel, aoOptions, sExtraAttribs, sPostHtml); + self._addLabel(sName, sLabel, 'combobox'); + self._add(' <select name="%s" id="%s" class="tmform-combobox"%s>\n' + % (escapeAttr(sName), escapeAttr(sName), sExtraAttribs)); + sSelected = unicode(sSelected); + for iValue, sText, _ in aoOptions: + sValue = unicode(iValue); + self._add(' <option value="%s"%s>%s</option>\n' + % (escapeAttr(sValue), ' selected' if sValue == sSelected else '', + escapeElem(sText))); + return self._add(u' </select>' + sPostHtml + '\n' + u' </div></div>\n' + u' </li>\n'); + + def addComboBoxRO(self, sName, sSelected, sLabel, aoOptions, sExtraAttribs = '', sPostHtml = ''): + """Adds a read-only combo box.""" + self.addTextHidden(sName, sSelected); + self._addLabel(sName, sLabel, 'combobox-readonly'); + self._add(u' <select name="%s" id="%s" disabled class="tmform-combobox"%s>\n' + % (escapeAttr(sName), escapeAttr(sName), sExtraAttribs)); + sSelected = unicode(sSelected); + for iValue, sText, _ in aoOptions: + sValue = unicode(iValue); + self._add(' <option value="%s"%s>%s</option>\n' + % (escapeAttr(sValue), ' selected' if sValue == sSelected else '', + escapeElem(sText))); + return self._add(u' </select>' + sPostHtml + '\n' + u' </div></div>\n' + u' </li>\n'); + + # + # Check boxes. + # + @staticmethod + def _reinterpretBool(fValue): + """Reinterprets a value as a boolean type.""" + if fValue is not type(True): + if fValue is None: + fValue = False; + elif str(fValue) in ('True', 'true', '1'): + fValue = True; + else: + fValue = False; + return fValue; + + def addCheckBox(self, sName, fChecked, sLabel, sExtraAttribs = ''): + """Adds an check box.""" + if self._fReadOnly: + return self.addCheckBoxRO(sName, fChecked, sLabel, sExtraAttribs); + self._addLabel(sName, sLabel, 'checkbox'); + fChecked = self._reinterpretBool(fChecked); + return self._add(u' <input name="%s" id="%s" type="checkbox"%s%s value="1" class="tmform-checkbox">\n' + u' </div></div>\n' + u' </li>\n' + % (escapeAttr(sName), escapeAttr(sName), ' checked' if fChecked else '', sExtraAttribs)); + + def addCheckBoxRO(self, sName, fChecked, sLabel, sExtraAttribs = ''): + """Adds an readonly check box.""" + self._addLabel(sName, sLabel, 'checkbox'); + fChecked = self._reinterpretBool(fChecked); + # Hack Alert! The onclick and onkeydown are for preventing editing and fake readonly/disabled. + return self._add(u' <input name="%s" id="%s" type="checkbox"%s readonly%s value="1" class="readonly"\n' + u' onclick="return false" onkeydown="return false">\n' + u' </div></div>\n' + u' </li>\n' + % (escapeAttr(sName), escapeAttr(sName), ' checked' if fChecked else '', sExtraAttribs)); + + # + # List of items to check + # + def _addList(self, sName, aoRows, sLabel, fUseTable = False, sId = 'dummy', sExtraAttribs = ''): + """ + Adds a list of items to check. + + @param sName Name of HTML form element + @param aoRows List of [sValue, fChecked, sName] sub-arrays. + @param sLabel Label of HTML form element + """ + fReadOnly = self._fReadOnly; ## @todo add this as a parameter. + if fReadOnly: + sExtraAttribs += ' readonly onclick="return false" onkeydown="return false"'; + + self._addLabel(sName, sLabel, 'list'); + if not aoRows: + return self._add('No items</div></div></li>') + sNameEscaped = escapeAttr(sName); + + self._add(' <div class="tmform-checkboxes-container" id="%s">\n' % (escapeAttr(sId),)); + if fUseTable: + self._add(' <table>\n'); + for asRow in aoRows: + assert len(asRow) == 3; # Don't allow sloppy input data! + fChecked = self._reinterpretBool(asRow[1]) + self._add(u' <tr>\n' + u' <td><input type="checkbox" name="%s" value="%s"%s%s></td>\n' + u' <td>%s</td>\n' + u' </tr>\n' + % ( sNameEscaped, escapeAttr(unicode(asRow[0])), ' checked' if fChecked else '', sExtraAttribs, + escapeElem(unicode(asRow[2])), )); + self._add(u' </table>\n'); + else: + for asRow in aoRows: + assert len(asRow) == 3; # Don't allow sloppy input data! + fChecked = self._reinterpretBool(asRow[1]) + self._add(u' <div class="tmform-checkbox-holder">' + u'<input type="checkbox" name="%s" value="%s"%s%s> %s</input></div>\n' + % ( sNameEscaped, escapeAttr(unicode(asRow[0])), ' checked' if fChecked else '', sExtraAttribs, + escapeElem(unicode(asRow[2])),)); + return self._add(u' </div></div></div>\n' + u' </li>\n'); + + + def addListOfOsArches(self, sName, aoOsArches, sLabel, sExtraAttribs = ''): + """ + List of checkboxes for OS/ARCH selection. + asOsArches is a list of [sValue, fChecked, sName] sub-arrays. + """ + return self._addList(sName, aoOsArches, sLabel, fUseTable = False, sId = 'tmform-checkbox-list-os-arches', + sExtraAttribs = sExtraAttribs); + + def addListOfTypes(self, sName, aoTypes, sLabel, sExtraAttribs = ''): + """ + List of checkboxes for build type selection. + aoTypes is a list of [sValue, fChecked, sName] sub-arrays. + """ + return self._addList(sName, aoTypes, sLabel, fUseTable = False, sId = 'tmform-checkbox-list-build-types', + sExtraAttribs = sExtraAttribs); + + def addListOfTestCases(self, sName, aoTestCases, sLabel, sExtraAttribs = ''): + """ + List of checkboxes for test box (dependency) selection. + aoTestCases is a list of [sValue, fChecked, sName] sub-arrays. + """ + return self._addList(sName, aoTestCases, sLabel, fUseTable = False, sId = 'tmform-checkbox-list-testcases', + sExtraAttribs = sExtraAttribs); + + def addListOfResources(self, sName, aoTestCases, sLabel, sExtraAttribs = ''): + """ + List of checkboxes for resource selection. + aoTestCases is a list of [sValue, fChecked, sName] sub-arrays. + """ + return self._addList(sName, aoTestCases, sLabel, fUseTable = False, sId = 'tmform-checkbox-list-resources', + sExtraAttribs = sExtraAttribs); + + def addListOfTestGroups(self, sName, aoTestGroups, sLabel, sExtraAttribs = ''): + """ + List of checkboxes for test group selection. + aoTestGroups is a list of [sValue, fChecked, sName] sub-arrays. + """ + return self._addList(sName, aoTestGroups, sLabel, fUseTable = False, sId = 'tmform-checkbox-list-testgroups', + sExtraAttribs = sExtraAttribs); + + def addListOfTestCaseArgs(self, sName, aoVariations, sLabel): # pylint: disable=too-many-statements + """ + Adds a list of test case argument variations to the form. + + @param sName Name of HTML form element + @param aoVariations List of TestCaseArgsData instances. + @param sLabel Label of HTML form element + """ + self._addLabel(sName, sLabel); + + sTableId = u'TestArgsExtendingListRoot'; + fReadOnly = self._fReadOnly; ## @todo argument? + sReadOnlyAttr = u' readonly class="tmform-input-readonly"' if fReadOnly else ''; + + sHtml = u'<li>\n' + + # + # Define javascript function for extending the list of test case + # variations. Doing it here so we can use the python constants. This + # also permits multiple argument lists on one page should that ever be + # required... + # + if not fReadOnly: + sHtml += u'<script type="text/javascript">\n' + sHtml += u'\n'; + sHtml += u'g_%s_aItems = { %s };\n' % (sName, ', '.join(('%s: 1' % (i,)) for i in range(len(aoVariations))),); + sHtml += u'g_%s_cItems = %s;\n' % (sName, len(aoVariations),); + sHtml += u'g_%s_iIdMod = %s;\n' % (sName, len(aoVariations) + 32); + sHtml += u'\n'; + sHtml += u'function %s_removeEntry(sId)\n' % (sName,); + sHtml += u'{\n'; + sHtml += u' if (g_%s_cItems > 1)\n' % (sName,); + sHtml += u' {\n'; + sHtml += u' g_%s_cItems--;\n' % (sName,); + sHtml += u' delete g_%s_aItems[sId];\n' % (sName,); + sHtml += u' setElementValueToKeyList(\'%s\', g_%s_aItems);\n' % (sName, sName); + sHtml += u'\n'; + for iInput in range(8): + sHtml += u' removeHtmlNode(\'%s[\' + sId + \'][%s]\');\n' % (sName, iInput,); + sHtml += u' }\n'; + sHtml += u'}\n'; + sHtml += u'\n'; + sHtml += u'function %s_extendListEx(sSubName, cGangMembers, cSecTimeout, sArgs, sTestBoxReqExpr, sBuildReqExpr)\n' \ + % (sName,); + sHtml += u'{\n'; + sHtml += u' var oElement = document.getElementById(\'%s\');\n' % (sTableId,); + sHtml += u' var oTBody = document.createElement(\'tbody\');\n'; + sHtml += u' var sHtml = \'\';\n'; + sHtml += u' var sId;\n'; + sHtml += u'\n'; + sHtml += u' g_%s_iIdMod += 1;\n' % (sName,); + sHtml += u' sId = g_%s_iIdMod.toString();\n' % (sName,); + + oVarDefaults = TestCaseArgsData(); + oVarDefaults.convertToParamNull(); + sHtml += u'\n'; + sHtml += u' sHtml += \'<tr class="tmform-testcasevars-first-row">\';\n'; + sHtml += u' sHtml += \' <td>Sub-Name:</td>\';\n'; + sHtml += u' sHtml += \' <td class="tmform-field-subname">' \ + '<input name="%s[\' + sId + \'][%s]" id="%s[\' + sId + \'][0]" value="\' + sSubName + \'"></td>\';\n' \ + % (sName, TestCaseArgsData.ksParam_sSubName, sName,); + sHtml += u' sHtml += \' <td>Gang Members:</td>\';\n'; + sHtml += u' sHtml += \' <td class="tmform-field-tiny-int">' \ + '<input name="%s[\' + sId + \'][%s]" id="%s[\' + sId + \'][0]" value="\' + cGangMembers + \'"></td>\';\n' \ + % (sName, TestCaseArgsData.ksParam_cGangMembers, sName,); + sHtml += u' sHtml += \' <td>Timeout:</td>\';\n'; + sHtml += u' sHtml += \' <td class="tmform-field-int">' \ + u'<input name="%s[\' + sId + \'][%s]" id="%s[\' + sId + \'][1]" value="\'+ cSecTimeout + \'"></td>\';\n' \ + % (sName, TestCaseArgsData.ksParam_cSecTimeout, sName,); + sHtml += u' sHtml += \' <td><a href="#" onclick="%s_removeEntry(\\\'\' + sId + \'\\\');"> Remove</a></td>\';\n' \ + % (sName, ); + sHtml += u' sHtml += \' <td></td>\';\n'; + sHtml += u' sHtml += \'</tr>\';\n' + sHtml += u'\n'; + sHtml += u' sHtml += \'<tr class="tmform-testcasevars-inner-row">\';\n'; + sHtml += u' sHtml += \' <td>Arguments:</td>\';\n'; + sHtml += u' sHtml += \' <td class="tmform-field-wide100" colspan="6">' \ + u'<input name="%s[\' + sId + \'][%s]" id="%s[\' + sId + \'][2]" value="\' + sArgs + \'"></td>\';\n' \ + % (sName, TestCaseArgsData.ksParam_sArgs, sName,); + sHtml += u' sHtml += \' <td></td>\';\n'; + sHtml += u' sHtml += \'</tr>\';\n' + sHtml += u'\n'; + sHtml += u' sHtml += \'<tr class="tmform-testcasevars-inner-row">\';\n'; + sHtml += u' sHtml += \' <td>TestBox Reqs:</td>\';\n'; + sHtml += u' sHtml += \' <td class="tmform-field-wide100" colspan="6">' \ + u'<input name="%s[\' + sId + \'][%s]" id="%s[\' + sId + \'][2]" value="\' + sTestBoxReqExpr' \ + u' + \'"></td>\';\n' \ + % (sName, TestCaseArgsData.ksParam_sTestBoxReqExpr, sName,); + sHtml += u' sHtml += \' <td></td>\';\n'; + sHtml += u' sHtml += \'</tr>\';\n' + sHtml += u'\n'; + sHtml += u' sHtml += \'<tr class="tmform-testcasevars-final-row">\';\n'; + sHtml += u' sHtml += \' <td>Build Reqs:</td>\';\n'; + sHtml += u' sHtml += \' <td class="tmform-field-wide100" colspan="6">' \ + u'<input name="%s[\' + sId + \'][%s]" id="%s[\' + sId + \'][2]" value="\' + sBuildReqExpr + \'"></td>\';\n' \ + % (sName, TestCaseArgsData.ksParam_sBuildReqExpr, sName,); + sHtml += u' sHtml += \' <td></td>\';\n'; + sHtml += u' sHtml += \'</tr>\';\n' + sHtml += u'\n'; + sHtml += u' oTBody.id = \'%s[\' + sId + \'][6]\';\n' % (sName,); + sHtml += u' oTBody.innerHTML = sHtml;\n'; + sHtml += u'\n'; + sHtml += u' oElement.appendChild(oTBody);\n'; + sHtml += u'\n'; + sHtml += u' g_%s_aItems[sId] = 1;\n' % (sName,); + sHtml += u' g_%s_cItems++;\n' % (sName,); + sHtml += u' setElementValueToKeyList(\'%s\', g_%s_aItems);\n' % (sName, sName); + sHtml += u'}\n'; + sHtml += u'function %s_extendList()\n' % (sName,); + sHtml += u'{\n'; + sHtml += u' %s_extendListEx("%s", "%s", "%s", "%s", "%s", "%s");\n' % (sName, + escapeAttr(unicode(oVarDefaults.sSubName)), escapeAttr(unicode(oVarDefaults.cGangMembers)), + escapeAttr(unicode(oVarDefaults.cSecTimeout)), escapeAttr(oVarDefaults.sArgs), + escapeAttr(oVarDefaults.sTestBoxReqExpr), escapeAttr(oVarDefaults.sBuildReqExpr), ); + sHtml += u'}\n'; + if config.g_kfVBoxSpecific: + sSecTimeoutDef = escapeAttr(unicode(oVarDefaults.cSecTimeout)); + sHtml += u'function vbox_%s_add_uni()\n' % (sName,); + sHtml += u'{\n'; + sHtml += u' %s_extendListEx("1-raw", "1", "%s", "--cpu-counts 1 --virt-modes raw", ' \ + u' "", "");\n' % (sName, sSecTimeoutDef); + sHtml += u' %s_extendListEx("1-hw", "1", "%s", "--cpu-counts 1 --virt-modes hwvirt", ' \ + u' "fCpuHwVirt is True", "");\n' % (sName, sSecTimeoutDef); + sHtml += u' %s_extendListEx("1-np", "1", "%s", "--cpu-counts 1 --virt-modes hwvirt-np", ' \ + u' "fCpuNestedPaging is True", "");\n' % (sName, sSecTimeoutDef); + sHtml += u'}\n'; + sHtml += u'function vbox_%s_add_uni_amd64()\n' % (sName,); + sHtml += u'{\n'; + sHtml += u' %s_extendListEx("1-hw", "1", "%s", "--cpu-counts 1 --virt-modes hwvirt", ' \ + u' "fCpuHwVirt is True", "");\n' % (sName, sSecTimeoutDef); + sHtml += u' %s_extendListEx("1-np", "%s", "--cpu-counts 1 --virt-modes hwvirt-np", ' \ + u' "fCpuNestedPaging is True", "");\n' % (sName, sSecTimeoutDef); + sHtml += u'}\n'; + sHtml += u'function vbox_%s_add_smp()\n' % (sName,); + sHtml += u'{\n'; + sHtml += u' %s_extendListEx("2-hw", "1", "%s", "--cpu-counts 2 --virt-modes hwvirt",' \ + u' "fCpuHwVirt is True and cCpus >= 2", "");\n' % (sName, sSecTimeoutDef); + sHtml += u' %s_extendListEx("2-np", "1", "%s", "--cpu-counts 2 --virt-modes hwvirt-np",' \ + u' "fCpuNestedPaging is True and cCpus >= 2", "");\n' % (sName, sSecTimeoutDef); + sHtml += u' %s_extendListEx("3-hw", "1", "%s", "--cpu-counts 3 --virt-modes hwvirt",' \ + u' "fCpuHwVirt is True and cCpus >= 3", "");\n' % (sName, sSecTimeoutDef); + sHtml += u' %s_extendListEx("4-np", "1", "%s", "--cpu-counts 4 --virt-modes hwvirt-np ",' \ + u' "fCpuNestedPaging is True and cCpus >= 4", "");\n' % (sName, sSecTimeoutDef); + #sHtml += u' %s_extendListEx("6-hw", "1", "%s", "--cpu-counts 6 --virt-modes hwvirt",' \ + # u' "fCpuHwVirt is True and cCpus >= 6", "");\n' % (sName, sSecTimeoutDef); + #sHtml += u' %s_extendListEx("8-np", "1", "%s", "--cpu-counts 8 --virt-modes hwvirt-np",' \ + # u' "fCpuNestedPaging is True and cCpus >= 8", "");\n' % (sName, sSecTimeoutDef); + sHtml += u'}\n'; + sHtml += u'</script>\n'; + + + # + # List current entries. + # + sHtml += u'<input type="hidden" name="%s" id="%s" value="%s">\n' \ + % (sName, sName, ','.join(unicode(i) for i in range(len(aoVariations))), ); + sHtml += u' <table id="%s" class="tmform-testcasevars">\n' % (sTableId,) + if not fReadOnly: + sHtml += u' <caption>\n' \ + u' <a href="#" onClick="%s_extendList()">Add</a>\n' % (sName,); + if config.g_kfVBoxSpecific: + sHtml += u' [<a href="#" onClick="vbox_%s_add_uni()">Single CPU Variations</a>\n' % (sName,); + sHtml += u' <a href="#" onClick="vbox_%s_add_uni_amd64()">amd64</a>]\n' % (sName,); + sHtml += u' [<a href="#" onClick="vbox_%s_add_smp()">SMP Variations</a>]\n' % (sName,); + sHtml += u' </caption>\n'; + + dSubErrors = {}; + if sName in self._dErrors and isinstance(self._dErrors[sName], dict): + dSubErrors = self._dErrors[sName]; + + for iVar, _ in enumerate(aoVariations): + oVar = copy.copy(aoVariations[iVar]); + oVar.convertToParamNull(); + + sHtml += u'<tbody id="%s[%s][6]">\n' % (sName, iVar,) + sHtml += u' <tr class="tmform-testcasevars-first-row">\n' \ + u' <td>Sub-name:</td>' \ + u' <td class="tmform-field-subname"><input name="%s[%s][%s]" id="%s[%s][1]" value="%s"%s></td>\n' \ + u' <td>Gang Members:</td>' \ + u' <td class="tmform-field-tiny-int"><input name="%s[%s][%s]" id="%s[%s][1]" value="%s"%s></td>\n' \ + u' <td>Timeout:</td>' \ + u' <td class="tmform-field-int"><input name="%s[%s][%s]" id="%s[%s][2]" value="%s"%s></td>\n' \ + % ( sName, iVar, TestCaseArgsData.ksParam_sSubName, sName, iVar, oVar.sSubName, sReadOnlyAttr, + sName, iVar, TestCaseArgsData.ksParam_cGangMembers, sName, iVar, oVar.cGangMembers, sReadOnlyAttr, + sName, iVar, TestCaseArgsData.ksParam_cSecTimeout, sName, iVar, + utils.formatIntervalSeconds2(oVar.cSecTimeout), sReadOnlyAttr, ); + if not fReadOnly: + sHtml += u' <td><a href="#" onclick="%s_removeEntry(\'%s\');">Remove</a></td>\n' \ + % (sName, iVar); + else: + sHtml += u' <td></td>\n'; + sHtml += u' <td class="tmform-testcasevars-stupid-border-column"></td>\n' \ + u' </tr>\n'; + + sHtml += u' <tr class="tmform-testcasevars-inner-row">\n' \ + u' <td>Arguments:</td>' \ + u' <td class="tmform-field-wide100" colspan="6">' \ + u'<input name="%s[%s][%s]" id="%s[%s][3]" value="%s"%s></td>\n' \ + u' <td></td>\n' \ + u' </tr>\n' \ + % ( sName, iVar, TestCaseArgsData.ksParam_sArgs, sName, iVar, escapeAttr(oVar.sArgs), sReadOnlyAttr) + + sHtml += u' <tr class="tmform-testcasevars-inner-row">\n' \ + u' <td>TestBox Reqs:</td>' \ + u' <td class="tmform-field-wide100" colspan="6">' \ + u'<input name="%s[%s][%s]" id="%s[%s][4]" value="%s"%s></td>\n' \ + u' <td></td>\n' \ + u' </tr>\n' \ + % ( sName, iVar, TestCaseArgsData.ksParam_sTestBoxReqExpr, sName, iVar, + escapeAttr(oVar.sTestBoxReqExpr), sReadOnlyAttr) + + sHtml += u' <tr class="tmform-testcasevars-final-row">\n' \ + u' <td>Build Reqs:</td>' \ + u' <td class="tmform-field-wide100" colspan="6">' \ + u'<input name="%s[%s][%s]" id="%s[%s][5]" value="%s"%s></td>\n' \ + u' <td></td>\n' \ + u' </tr>\n' \ + % ( sName, iVar, TestCaseArgsData.ksParam_sBuildReqExpr, sName, iVar, + escapeAttr(oVar.sBuildReqExpr), sReadOnlyAttr) + + + if iVar in dSubErrors: + sHtml += u' <tr><td colspan="4"><p align="left" class="tmform-error-desc">%s</p></td></tr>\n' \ + % (self._escapeErrorText(dSubErrors[iVar]),); + + sHtml += u'</tbody>\n'; + sHtml += u' </table>\n' + sHtml += u'</li>\n' + + return self._add(sHtml) + + def addListOfTestGroupMembers(self, sName, aoTestGroupMembers, aoAllTestCases, sLabel, # pylint: disable=too-many-locals + fReadOnly = True): + """ + For WuiTestGroup. + """ + assert len(aoTestGroupMembers) <= len(aoAllTestCases); + self._addLabel(sName, sLabel); + if not aoAllTestCases: + return self._add('<li>No testcases.</li>\n') + + self._add(u'<input name="%s" type="hidden" value="%s">\n' + % ( TestGroupDataEx.ksParam_aidTestCases, + ','.join([unicode(oTestCase.idTestCase) for oTestCase in aoAllTestCases]), )); + + self._add(u'<table class="tmformtbl">\n' + u' <thead>\n' + u' <tr>\n' + u' <th rowspan="2"></th>\n' + u' <th rowspan="2">Test Case</th>\n' + u' <th rowspan="2">All Vars</th>\n' + u' <th rowspan="2">Priority [0..31]</th>\n' + u' <th colspan="4" align="center">Variations</th>\n' + u' </tr>\n' + u' <tr>\n' + u' <th>Included</th>\n' + u' <th>Gang size</th>\n' + u' <th>Timeout</th>\n' + u' <th>Arguments</th>\n' + u' </tr>\n' + u' </thead>\n' + u' <tbody>\n' + ); + + if self._fReadOnly: + fReadOnly = True; + sCheckBoxAttr = ' readonly onclick="return false" onkeydown="return false"' if fReadOnly else ''; + + oDefMember = TestGroupMemberData(); + aoTestGroupMembers = list(aoTestGroupMembers); # Copy it so we can pop. + for iTestCase, _ in enumerate(aoAllTestCases): + oTestCase = aoAllTestCases[iTestCase]; + + # Is it a member? + oMember = None; + for i, _ in enumerate(aoTestGroupMembers): + if aoTestGroupMembers[i].oTestCase.idTestCase == oTestCase.idTestCase: + oMember = aoTestGroupMembers.pop(i); + break; + + # Start on the rows... + sPrefix = u'%s[%d]' % (sName, oTestCase.idTestCase,); + self._add(u' <tr class="%s">\n' + u' <td rowspan="%d">\n' + u' <input name="%s[%s]" type="hidden" value="%s">\n' # idTestCase + u' <input name="%s[%s]" type="hidden" value="%s">\n' # idTestGroup + u' <input name="%s[%s]" type="hidden" value="%s">\n' # tsExpire + u' <input name="%s[%s]" type="hidden" value="%s">\n' # tsEffective + u' <input name="%s[%s]" type="hidden" value="%s">\n' # uidAuthor + u' <input name="%s" type="checkbox"%s%s value="%d" class="tmform-checkbox" title="#%d - %s">\n' #(list) + u' </td>\n' + % ( 'tmodd' if iTestCase & 1 else 'tmeven', + len(oTestCase.aoTestCaseArgs), + sPrefix, TestGroupMemberData.ksParam_idTestCase, oTestCase.idTestCase, + sPrefix, TestGroupMemberData.ksParam_idTestGroup, -1 if oMember is None else oMember.idTestGroup, + sPrefix, TestGroupMemberData.ksParam_tsExpire, '' if oMember is None else oMember.tsExpire, + sPrefix, TestGroupMemberData.ksParam_tsEffective, '' if oMember is None else oMember.tsEffective, + sPrefix, TestGroupMemberData.ksParam_uidAuthor, '' if oMember is None else oMember.uidAuthor, + TestGroupDataEx.ksParam_aoMembers, '' if oMember is None else ' checked', sCheckBoxAttr, + oTestCase.idTestCase, oTestCase.idTestCase, escapeElem(oTestCase.sName), + )); + self._add(u' <td rowspan="%d" align="left">%s</td>\n' + % ( len(oTestCase.aoTestCaseArgs), escapeElem(oTestCase.sName), )); + + self._add(u' <td rowspan="%d" title="Include all variations (checked) or choose a set?">\n' + u' <input name="%s[%s]" type="checkbox"%s%s value="-1">\n' + u' </td>\n' + % ( len(oTestCase.aoTestCaseArgs), + sPrefix, TestGroupMemberData.ksParam_aidTestCaseArgs, + ' checked' if oMember is None or oMember.aidTestCaseArgs is None else '', sCheckBoxAttr, )); + + self._add(u' <td rowspan="%d" align="center">\n' + u' <input name="%s[%s]" type="text" value="%s" style="max-width:3em;" %s>\n' + u' </td>\n' + % ( len(oTestCase.aoTestCaseArgs), + sPrefix, TestGroupMemberData.ksParam_iSchedPriority, + (oMember if oMember is not None else oDefMember).iSchedPriority, + ' readonly class="tmform-input-readonly"' if fReadOnly else '', )); + + # Argument variations. + aidTestCaseArgs = [] if oMember is None or oMember.aidTestCaseArgs is None else oMember.aidTestCaseArgs; + for iVar, oVar in enumerate(oTestCase.aoTestCaseArgs): + if iVar > 0: + self._add(' <tr class="%s">\n' % ('tmodd' if iTestCase & 1 else 'tmeven',)); + self._add(u' <td align="center">\n' + u' <input name="%s[%s]" type="checkbox"%s%s value="%d">' + u' </td>\n' + % ( sPrefix, TestGroupMemberData.ksParam_aidTestCaseArgs, + ' checked' if oVar.idTestCaseArgs in aidTestCaseArgs else '', sCheckBoxAttr, oVar.idTestCaseArgs, + )); + self._add(u' <td align="center">%s</td>\n' + u' <td align="center">%s</td>\n' + u' <td align="left">%s</td>\n' + % ( oVar.cGangMembers, + 'Default' if oVar.cSecTimeout is None else oVar.cSecTimeout, + escapeElem(oVar.sArgs) )); + + self._add(u' </tr>\n'); + + + + if not oTestCase.aoTestCaseArgs: + self._add(u' <td></td> <td></td> <td></td> <td></td>\n' + u' </tr>\n'); + return self._add(u' </tbody>\n' + u'</table>\n'); + + def addListOfSchedGroupMembers(self, sName, aoSchedGroupMembers, aoAllRelevantTestGroups, # pylint: disable=too-many-locals + sLabel, idSchedGroup, fReadOnly = True): + """ + For WuiAdminSchedGroup. + """ + if fReadOnly is None or self._fReadOnly: + fReadOnly = self._fReadOnly; + assert len(aoSchedGroupMembers) <= len(aoAllRelevantTestGroups); + self._addLabel(sName, sLabel); + if not aoAllRelevantTestGroups: + return self._add(u'<li>No test groups.</li>\n') + + self._add(u'<input name="%s" type="hidden" value="%s">\n' + % ( SchedGroupDataEx.ksParam_aidTestGroups, + ','.join([unicode(oTestGroup.idTestGroup) for oTestGroup in aoAllRelevantTestGroups]), )); + + self._add(u'<table class="tmformtbl tmformtblschedgroupmembers">\n' + u' <thead>\n' + u' <tr>\n' + u' <th></th>\n' + u' <th>Test Group</th>\n' + u' <th>Priority [0..31]</th>\n' + u' <th>Prerequisite Test Group</th>\n' + u' <th>Weekly schedule</th>\n' + u' </tr>\n' + u' </thead>\n' + u' <tbody>\n' + ); + + sCheckBoxAttr = u' readonly onclick="return false" onkeydown="return false"' if fReadOnly else ''; + sComboBoxAttr = u' disabled' if fReadOnly else ''; + + oDefMember = SchedGroupMemberData(); + aoSchedGroupMembers = list(aoSchedGroupMembers); # Copy it so we can pop. + for iTestGroup, _ in enumerate(aoAllRelevantTestGroups): + oTestGroup = aoAllRelevantTestGroups[iTestGroup]; + + # Is it a member? + oMember = None; + for i, _ in enumerate(aoSchedGroupMembers): + if aoSchedGroupMembers[i].oTestGroup.idTestGroup == oTestGroup.idTestGroup: + oMember = aoSchedGroupMembers.pop(i); + break; + + # Start on the rows... + sPrefix = u'%s[%d]' % (sName, oTestGroup.idTestGroup,); + self._add(u' <tr class="%s">\n' + u' <td>\n' + u' <input name="%s[%s]" type="hidden" value="%s">\n' # idTestGroup + u' <input name="%s[%s]" type="hidden" value="%s">\n' # idSchedGroup + u' <input name="%s[%s]" type="hidden" value="%s">\n' # tsExpire + u' <input name="%s[%s]" type="hidden" value="%s">\n' # tsEffective + u' <input name="%s[%s]" type="hidden" value="%s">\n' # uidAuthor + u' <input name="%s" type="checkbox"%s%s value="%d" class="tmform-checkbox" title="#%d - %s">\n' #(list) + u' </td>\n' + % ( 'tmodd' if iTestGroup & 1 else 'tmeven', + sPrefix, SchedGroupMemberData.ksParam_idTestGroup, oTestGroup.idTestGroup, + sPrefix, SchedGroupMemberData.ksParam_idSchedGroup, idSchedGroup, + sPrefix, SchedGroupMemberData.ksParam_tsExpire, '' if oMember is None else oMember.tsExpire, + sPrefix, SchedGroupMemberData.ksParam_tsEffective, '' if oMember is None else oMember.tsEffective, + sPrefix, SchedGroupMemberData.ksParam_uidAuthor, '' if oMember is None else oMember.uidAuthor, + SchedGroupDataEx.ksParam_aoMembers, '' if oMember is None else ' checked', sCheckBoxAttr, + oTestGroup.idTestGroup, oTestGroup.idTestGroup, escapeElem(oTestGroup.sName), + )); + self._add(u' <td>%s</td>\n' % ( escapeElem(oTestGroup.sName), )); + + self._add(u' <td>\n' + u' <input name="%s[%s]" type="text" value="%s" style="max-width:3em;" %s>\n' + u' </td>\n' + % ( sPrefix, SchedGroupMemberData.ksParam_iSchedPriority, + (oMember if oMember is not None else oDefMember).iSchedPriority, + ' readonly class="tmform-input-readonly"' if fReadOnly else '', )); + + self._add(u' <td>\n' + u' <select name="%s[%s]" id="%s[%s]" class="tmform-combobox"%s>\n' + u' <option value="-1"%s>None</option>\n' + % ( sPrefix, SchedGroupMemberData.ksParam_idTestGroupPreReq, + sPrefix, SchedGroupMemberData.ksParam_idTestGroupPreReq, + sComboBoxAttr, + ' selected' if oMember is None or oMember.idTestGroupPreReq is None else '', + )); + for oTestGroup2 in aoAllRelevantTestGroups: + if oTestGroup2 != oTestGroup: + fSelected = oMember is not None and oTestGroup2.idTestGroup == oMember.idTestGroupPreReq; + self._add(' <option value="%s"%s>%s</option>\n' + % ( oTestGroup2.idTestGroup, ' selected' if fSelected else '', escapeElem(oTestGroup2.sName), )); + self._add(u' </select>\n' + u' </td>\n'); + + self._add(u' <td>\n' + u' Todo<input name="%s[%s]" type="hidden" value="%s">\n' + u' </td>\n' + % ( sPrefix, SchedGroupMemberData.ksParam_bmHourlySchedule, + '' if oMember is None else oMember.bmHourlySchedule, )); + + self._add(u' </tr>\n'); + return self._add(u' </tbody>\n' + u'</table>\n'); + + def addListOfSchedGroupBoxes(self, sName, aoSchedGroupBoxes, # pylint: disable=too-many-locals + aoAllRelevantTestBoxes, sLabel, idSchedGroup, fReadOnly = True, + fUseTable = False): # (str, list[TestBoxDataEx], list[TestBoxDataEx], str, bool, bool) -> str + """ + For WuiAdminSchedGroup. + """ + if fReadOnly is None or self._fReadOnly: + fReadOnly = self._fReadOnly; + assert len(aoSchedGroupBoxes) <= len(aoAllRelevantTestBoxes); + self._addLabel(sName, sLabel); + if not aoAllRelevantTestBoxes: + return self._add(u'<li>No test boxes.</li>\n') + + self._add(u'<input name="%s" type="hidden" value="%s">\n' + % ( SchedGroupDataEx.ksParam_aidTestBoxes, + ','.join([unicode(oTestBox.idTestBox) for oTestBox in aoAllRelevantTestBoxes]), )); + + sCheckBoxAttr = u' readonly onclick="return false" onkeydown="return false"' if fReadOnly else ''; + oDefMember = TestBoxDataForSchedGroup(); + aoSchedGroupBoxes = list(aoSchedGroupBoxes); # Copy it so we can pop. + + from testmanager.webui.wuiadmintestbox import WuiTestBoxDetailsLink; + + if not fUseTable: + # + # Non-table version (see also addListOfOsArches). + # + self._add(' <div class="tmform-checkboxes-container">\n'); + + for iTestBox, oTestBox in enumerate(aoAllRelevantTestBoxes): + # Is it a member? + oMember = None; + for i, _ in enumerate(aoSchedGroupBoxes): + if aoSchedGroupBoxes[i].oTestBox and aoSchedGroupBoxes[i].oTestBox.idTestBox == oTestBox.idTestBox: + oMember = aoSchedGroupBoxes.pop(i); + break; + + # Start on the rows... + sPrf = u'%s[%d]' % (sName, oTestBox.idTestBox,); + self._add(u' <div class="tmform-checkbox-holder tmshade%u">\n' + u' <input name="%s[%s]" type="hidden" value="%s">\n' # idTestBox + u' <input name="%s[%s]" type="hidden" value="%s">\n' # idSchedGroup + u' <input name="%s[%s]" type="hidden" value="%s">\n' # tsExpire + u' <input name="%s[%s]" type="hidden" value="%s">\n' # tsEffective + u' <input name="%s[%s]" type="hidden" value="%s">\n' # uidAuthor + u' <input name="%s" type="checkbox"%s%s value="%d" class="tmform-checkbox" title="#%d - %s">\n' #(list) + % ( iTestBox & 7, + sPrf, TestBoxDataForSchedGroup.ksParam_idTestBox, oTestBox.idTestBox, + sPrf, TestBoxDataForSchedGroup.ksParam_idSchedGroup, idSchedGroup, + sPrf, TestBoxDataForSchedGroup.ksParam_tsExpire, '' if oMember is None else oMember.tsExpire, + sPrf, TestBoxDataForSchedGroup.ksParam_tsEffective, '' if oMember is None else oMember.tsEffective, + sPrf, TestBoxDataForSchedGroup.ksParam_uidAuthor, '' if oMember is None else oMember.uidAuthor, + SchedGroupDataEx.ksParam_aoTestBoxes, '' if oMember is None else ' checked', sCheckBoxAttr, + oTestBox.idTestBox, oTestBox.idTestBox, escapeElem(oTestBox.sName), + )); + + self._add(u' <span class="tmform-priority tmform-testbox-priority">' + u'<input name="%s[%s]" type="text" value="%s" style="max-width:3em;" %s title="%s"></span>\n' + % ( sPrf, TestBoxDataForSchedGroup.ksParam_iSchedPriority, + (oMember if oMember is not None else oDefMember).iSchedPriority, + ' readonly class="tmform-input-readonly"' if fReadOnly else '', + escapeAttr("Priority [0..31]. Higher value means run more often.") )); + + self._add(u' <span class="tmform-testbox-name">%s</span>\n' + % ( WuiTestBoxDetailsLink(oTestBox, sName = '%s (%s)' % (oTestBox.sName, oTestBox.sOs,)),)); + self._add(u' </div>\n'); + return self._add(u' </div></div></div>\n' + u' </li>\n'); + + # + # Table version. + # + self._add(u'<table class="tmformtbl">\n' + u' <thead>\n' + u' <tr>\n' + u' <th></th>\n' + u' <th>Test Box</th>\n' + u' <th>Priority [0..31]</th>\n' + u' </tr>\n' + u' </thead>\n' + u' <tbody>\n' + ); + + for iTestBox, oTestBox in enumerate(aoAllRelevantTestBoxes): + # Is it a member? + oMember = None; + for i, _ in enumerate(aoSchedGroupBoxes): + if aoSchedGroupBoxes[i].oTestBox and aoSchedGroupBoxes[i].oTestBox.idTestBox == oTestBox.idTestBox: + oMember = aoSchedGroupBoxes.pop(i); + break; + + # Start on the rows... + sPrefix = u'%s[%d]' % (sName, oTestBox.idTestBox,); + self._add(u' <tr class="%s">\n' + u' <td>\n' + u' <input name="%s[%s]" type="hidden" value="%s">\n' # idTestBox + u' <input name="%s[%s]" type="hidden" value="%s">\n' # idSchedGroup + u' <input name="%s[%s]" type="hidden" value="%s">\n' # tsExpire + u' <input name="%s[%s]" type="hidden" value="%s">\n' # tsEffective + u' <input name="%s[%s]" type="hidden" value="%s">\n' # uidAuthor + u' <input name="%s" type="checkbox"%s%s value="%d" class="tmform-checkbox" title="#%d - %s">\n' #(list) + u' </td>\n' + % ( 'tmodd' if iTestBox & 1 else 'tmeven', + sPrefix, TestBoxDataForSchedGroup.ksParam_idTestBox, oTestBox.idTestBox, + sPrefix, TestBoxDataForSchedGroup.ksParam_idSchedGroup, idSchedGroup, + sPrefix, TestBoxDataForSchedGroup.ksParam_tsExpire, '' if oMember is None else oMember.tsExpire, + sPrefix, TestBoxDataForSchedGroup.ksParam_tsEffective, '' if oMember is None else oMember.tsEffective, + sPrefix, TestBoxDataForSchedGroup.ksParam_uidAuthor, '' if oMember is None else oMember.uidAuthor, + SchedGroupDataEx.ksParam_aoTestBoxes, '' if oMember is None else ' checked', sCheckBoxAttr, + oTestBox.idTestBox, oTestBox.idTestBox, escapeElem(oTestBox.sName), + )); + self._add(u' <td align="left">%s</td>\n' % ( escapeElem(oTestBox.sName), )); + + self._add(u' <td align="center">\n' + u' <input name="%s[%s]" type="text" value="%s" style="max-width:3em;" %s>\n' + u' </td>\n' + % ( sPrefix, + TestBoxDataForSchedGroup.ksParam_iSchedPriority, + (oMember if oMember is not None else oDefMember).iSchedPriority, + ' readonly class="tmform-input-readonly"' if fReadOnly else '', )); + + self._add(u' </tr>\n'); + return self._add(u' </tbody>\n' + u'</table>\n'); + + def addListOfSchedGroupsForTestBox(self, sName, aoInSchedGroups, aoAllSchedGroups, sLabel, # pylint: disable=too-many-locals + idTestBox, fReadOnly = None): + # type: (str, TestBoxInSchedGroupDataEx, SchedGroupData, str, bool) -> str + """ + For WuiTestGroup. + """ + from testmanager.core.testbox import TestBoxInSchedGroupData, TestBoxDataEx; + + if fReadOnly is None or self._fReadOnly: + fReadOnly = self._fReadOnly; + assert len(aoInSchedGroups) <= len(aoAllSchedGroups); + + # Only show selected groups in read-only mode. + if fReadOnly: + aoAllSchedGroups = [oCur.oSchedGroup for oCur in aoInSchedGroups] + + self._addLabel(sName, sLabel); + if not aoAllSchedGroups: + return self._add('<li>No scheduling groups.</li>\n') + + # Add special parameter with all the scheduling group IDs in the form. + self._add(u'<input name="%s" type="hidden" value="%s">\n' + % ( TestBoxDataEx.ksParam_aidSchedGroups, + ','.join([unicode(oSchedGroup.idSchedGroup) for oSchedGroup in aoAllSchedGroups]), )); + + # Table header. + self._add(u'<table class="tmformtbl">\n' + u' <thead>\n' + u' <tr>\n' + u' <th rowspan="2"></th>\n' + u' <th rowspan="2">Schedulding Group</th>\n' + u' <th rowspan="2">Priority [0..31]</th>\n' + u' </tr>\n' + u' </thead>\n' + u' <tbody>\n' + ); + + # Table body. + if self._fReadOnly: + fReadOnly = True; + sCheckBoxAttr = ' readonly onclick="return false" onkeydown="return false"' if fReadOnly else ''; + + oDefMember = TestBoxInSchedGroupData(); + aoInSchedGroups = list(aoInSchedGroups); # Copy it so we can pop. + for iSchedGroup, oSchedGroup in enumerate(aoAllSchedGroups): + + # Is it a member? + oMember = None; + for i, _ in enumerate(aoInSchedGroups): + if aoInSchedGroups[i].idSchedGroup == oSchedGroup.idSchedGroup: + oMember = aoInSchedGroups.pop(i); + break; + + # Start on the rows... + sPrefix = u'%s[%d]' % (sName, oSchedGroup.idSchedGroup,); + self._add(u' <tr class="%s">\n' + u' <td>\n' + u' <input name="%s[%s]" type="hidden" value="%s">\n' # idSchedGroup + u' <input name="%s[%s]" type="hidden" value="%s">\n' # idTestBox + u' <input name="%s[%s]" type="hidden" value="%s">\n' # tsExpire + u' <input name="%s[%s]" type="hidden" value="%s">\n' # tsEffective + u' <input name="%s[%s]" type="hidden" value="%s">\n' # uidAuthor + u' <input name="%s" type="checkbox"%s%s value="%d" class="tmform-checkbox" title="#%d - %s">\n' #(list) + u' </td>\n' + % ( 'tmodd' if iSchedGroup & 1 else 'tmeven', + sPrefix, TestBoxInSchedGroupData.ksParam_idSchedGroup, oSchedGroup.idSchedGroup, + sPrefix, TestBoxInSchedGroupData.ksParam_idTestBox, idTestBox, + sPrefix, TestBoxInSchedGroupData.ksParam_tsExpire, '' if oMember is None else oMember.tsExpire, + sPrefix, TestBoxInSchedGroupData.ksParam_tsEffective, '' if oMember is None else oMember.tsEffective, + sPrefix, TestBoxInSchedGroupData.ksParam_uidAuthor, '' if oMember is None else oMember.uidAuthor, + TestBoxDataEx.ksParam_aoInSchedGroups, '' if oMember is None else ' checked', sCheckBoxAttr, + oSchedGroup.idSchedGroup, oSchedGroup.idSchedGroup, escapeElem(oSchedGroup.sName), + )); + self._add(u' <td align="left">%s</td>\n' % ( escapeElem(oSchedGroup.sName), )); + + self._add(u' <td align="center">\n' + u' <input name="%s[%s]" type="text" value="%s" style="max-width:3em;" %s>\n' + u' </td>\n' + % ( sPrefix, TestBoxInSchedGroupData.ksParam_iSchedPriority, + (oMember if oMember is not None else oDefMember).iSchedPriority, + ' readonly class="tmform-input-readonly"' if fReadOnly else '', )); + self._add(u' </tr>\n'); + + return self._add(u' </tbody>\n' + u'</table>\n'); + + + # + # Buttons. + # + def addSubmit(self, sLabel = 'Submit'): + """Adds the submit button to the form.""" + if self._fReadOnly: + return True; + return self._add(u' <li>\n' + u' <br>\n' + u' <div class="tmform-field"><div class="tmform-field-submit">\n' + u' <label> </label>\n' + u' <input type="submit" value="%s">\n' + u' </div></div>\n' + u' </li>\n' + % (escapeElem(sLabel),)); + + def addReset(self): + """Adds a reset button to the form.""" + if self._fReadOnly: + return True; + return self._add(u' <li>\n' + u' <div class="tmform-button"><div class="tmform-button-reset">\n' + u' <input type="reset" value="%s">\n' + u' </div></div>\n' + u' </li>\n'); + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuihlpgraph.py b/src/VBox/ValidationKit/testmanager/webui/wuihlpgraph.py new file mode 100755 index 00000000..87080b4a --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuihlpgraph.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- +# $Id: wuihlpgraph.py $ + +""" +Test Manager Web-UI - Graph Helpers. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +class WuiHlpGraphDataTable(object): # pylint: disable=too-few-public-methods + """ + Data table container. + """ + + class Row(object): # pylint: disable=too-few-public-methods + """A row.""" + def __init__(self, sGroup, aoValues, asValues = None): + self.sName = sGroup; + self.aoValues = aoValues; + if asValues is None: + self.asValues = [str(oVal) for oVal in aoValues]; + else: + assert len(asValues) == len(aoValues); + self.asValues = asValues; + + def __init__(self, sGroupLable, asMemberLabels): + self.aoTable = [ WuiHlpGraphDataTable.Row(sGroupLable, asMemberLabels), ]; + self.fHasStringValues = False; + + def addRow(self, sGroup, aoValues, asValues = None): + """Adds a row to the data table.""" + if asValues: + self.fHasStringValues = True; + self.aoTable.append(WuiHlpGraphDataTable.Row(sGroup, aoValues, asValues)); + return True; + + def getGroupCount(self): + """Gets the number of data groups (rows).""" + return len(self.aoTable) - 1; + + +class WuiHlpGraphDataTableEx(object): # pylint: disable=too-few-public-methods + """ + Data container for an table/graph with optional error bars on the Y values. + """ + + class DataSeries(object): # pylint: disable=too-few-public-methods + """ + A data series. + + The aoXValues, aoYValues and aoYErrorBars are parallel arrays, making a + series of (X,Y,Y-err-above-delta,Y-err-below-delta) points. + + The error bars are optional. + """ + def __init__(self, sName, aoXValues, aoYValues, asHtmlTooltips = None, aoYErrorBarBelow = None, aoYErrorBarAbove = None): + self.sName = sName; + self.aoXValues = aoXValues; + self.aoYValues = aoYValues; + self.asHtmlTooltips = asHtmlTooltips; + self.aoYErrorBarBelow = aoYErrorBarBelow; + self.aoYErrorBarAbove = aoYErrorBarAbove; + + def __init__(self, sXUnit, sYUnit): + self.sXUnit = sXUnit; + self.sYUnit = sYUnit; + self.aoSeries = []; + + def addDataSeries(self, sName, aoXValues, aoYValues, asHtmlTooltips = None, aoYErrorBarBelow = None, aoYErrorBarAbove = None): + """Adds an data series to the table.""" + self.aoSeries.append(WuiHlpGraphDataTableEx.DataSeries(sName, aoXValues, aoYValues, asHtmlTooltips, + aoYErrorBarBelow, aoYErrorBarAbove)); + return True; + + def getDataSeriesCount(self): + """Gets the number of data series.""" + return len(self.aoSeries); + + +# +# Dynamically choose implementation. +# +if True: # pylint: disable=using-constant-test + from testmanager.webui import wuihlpgraphgooglechart as GraphImplementation; +else: + try: + import matplotlib; # pylint: disable=unused-import,import-error,import-error,wrong-import-order + from testmanager.webui import wuihlpgraphmatplotlib as GraphImplementation; # pylint: disable=ungrouped-imports + except: + from testmanager.webui import wuihlpgraphsimple as GraphImplementation; + +# pylint: disable=invalid-name +WuiHlpBarGraph = GraphImplementation.WuiHlpBarGraph; +WuiHlpLineGraph = GraphImplementation.WuiHlpLineGraph; +WuiHlpLineGraphErrorbarY = GraphImplementation.WuiHlpLineGraphErrorbarY; + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuihlpgraphbase.py b/src/VBox/ValidationKit/testmanager/webui/wuihlpgraphbase.py new file mode 100755 index 00000000..d2e52271 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuihlpgraphbase.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +# $Id: wuihlpgraphbase.py $ + +""" +Test Manager Web-UI - Graph Helpers - Base Class. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +class WuiHlpGraphBase(object): + """ + Base class for the Graph helpers. + """ + + ## Set of colors that can be used by child classes to color data series. + kasColors = \ + [ + '#0000ff', # Blue + '#00ff00', # Green + '#ff0000', # Red + '#000000', # Black + + '#00ffff', # Cyan/Aqua + '#ff00ff', # Magenta/Fuchsia + '#ffff00', # Yellow + '#8b4513', # SaddleBrown + + '#7b68ee', # MediumSlateBlue + '#ffc0cb', # Pink + '#bdb76b', # DarkKhaki + '#008080', # Teal + + '#bc8f8f', # RosyBrown + '#000080', # Navy(Blue) + '#dc143c', # Crimson + '#800080', # Purple + + '#daa520', # Goldenrod + '#40e0d0', # Turquoise + '#00bfff', # DeepSkyBlue + '#c0c0c0', # Silver + ]; + + + def __init__(self, sId, oData, oDisp): + self._sId = sId; + self._oData = oData; + self._oDisp = oDisp; + # Graph output dimensions. + self._cxGraph = 1024; + self._cyGraph = 448; + self._cDpiGraph = 96; + # Other graph attributes + self._sTitle = None; + self._cPtFont = 8; + + def headerContent(self): + """ + Returns content that goes into the HTML header. + """ + return ''; + + def renderGraph(self): + """ + Renders the graph. + Returning HTML. + """ + return '<p>renderGraph needs to be overridden by the child class!</p>'; + + def setTitle(self, sTitle): + """ Sets the graph title. """ + self._sTitle = sTitle; + return True; + + def setWidth(self, cx): + """ Sets the graph width. """ + self._cxGraph = cx; + return True; + + def setHeight(self, cy): + """ Sets the graph height. """ + self._cyGraph = cy; + return True; + + def setDpi(self, cDotsPerInch): + """ Sets the graph DPI. """ + self._cDpiGraph = cDotsPerInch; + return True; + + def setFontSize(self, cPtFont): + """ Sets the default font size. """ + self._cPtFont = cPtFont; + return True; + + + @staticmethod + def calcSeriesColor(iSeries): + """ Returns a #rrggbb color code for the given series. """ + return WuiHlpGraphBase.kasColors[iSeries % len(WuiHlpGraphBase.kasColors)]; diff --git a/src/VBox/ValidationKit/testmanager/webui/wuihlpgraphgooglechart.py b/src/VBox/ValidationKit/testmanager/webui/wuihlpgraphgooglechart.py new file mode 100755 index 00000000..36e17808 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuihlpgraphgooglechart.py @@ -0,0 +1,376 @@ +# -*- coding: utf-8 -*- +# $Id: wuihlpgraphgooglechart.py $ + +""" +Test Manager Web-UI - Graph Helpers - Implemented using Google Charts. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Validation Kit imports. +from common import utils, webutils; +from testmanager.webui.wuihlpgraphbase import WuiHlpGraphBase; + + +#******************************************************************************* +#* Global Variables * +#******************************************************************************* +g_cGraphs = 0; + +class WuiHlpGraphGoogleChartsBase(WuiHlpGraphBase): + """ Base class for the Google Charts graphs. """ + pass; # pylint: disable=unnecessary-pass + + +class WuiHlpBarGraph(WuiHlpGraphGoogleChartsBase): + """ + Bar graph. + """ + + def __init__(self, sId, oData, oDisp = None): + WuiHlpGraphGoogleChartsBase.__init__(self, sId, oData, oDisp); + self.fpMax = None; + self.fpMin = 0.0; + self.fYInverted = False; + + def setRangeMax(self, fpMax): + """ Sets the max range.""" + self.fpMax = float(fpMax); + return None; + + def invertYDirection(self): + """ Inverts the direction of the Y-axis direction. """ + self.fYInverted = True; + return None; + + def renderGraph(self): + aoTable = self._oData.aoTable # type: WuiHlpGraphDataTable + + # Seems material (google.charts.Bar) cannot change the direction on the Y-axis, + # so we cannot get bars growing downwards from the top like we want for the + # reports. The classic charts OTOH cannot put X-axis labels on the top, but + # we just drop them all together instead, saving a little space. + fUseMaterial = False; + + # Unique on load function. + global g_cGraphs; + iGraph = g_cGraphs; + g_cGraphs += 1; + + sHtml = '<div id="%s">\n' % ( self._sId, ); + sHtml += '<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>\n' \ + '<script type="text/javascript">\n' \ + 'google.charts.load("current", { packages: ["corechart", "bar"] });\n' \ + 'google.setOnLoadCallback(tmDrawBarGraph%u);\n' \ + 'function tmDrawBarGraph%u()\n' \ + '{\n' \ + ' var oGraph;\n' \ + ' var dGraphOptions = \n' \ + ' {\n' \ + ' "title": "%s",\n' \ + ' "hAxis": {\n' \ + ' "title": "%s",\n' \ + ' },\n' \ + ' "vAxis": {\n' \ + ' "direction": %s,\n' \ + ' },\n' \ + % ( iGraph, + iGraph, + webutils.escapeAttrJavaScriptStringDQ(self._sTitle) if self._sTitle is not None else '', + webutils.escapeAttrJavaScriptStringDQ(aoTable[0].sName) if aoTable and aoTable[0].sName else '', + '-1' if self.fYInverted else '1', + ); + if fUseMaterial and self.fYInverted: + sHtml += ' "axes": { "x": { 0: { "side": "top" } }, "y": { "0": { "direction": -1, }, }, },\n'; + sHtml += ' };\n'; + + # The data. + if self._oData.fHasStringValues and len(aoTable) > 1: + sHtml += ' var oData = new google.visualization.DataTable();\n'; + # Column definitions. + sHtml += ' oData.addColumn("string", "%s");\n' \ + % (webutils.escapeAttrJavaScriptStringDQ(aoTable[0].sName) if aoTable[0].sName else '',); + for iValue, oValue in enumerate(aoTable[0].aoValues): + oSampleValue = aoTable[1].aoValues[iValue]; + if utils.isString(oSampleValue): + sHtml += ' oData.addColumn("string", "%s");\n' % (webutils.escapeAttrJavaScriptStringDQ(oValue),); + else: + sHtml += ' oData.addColumn("number", "%s");\n' % (webutils.escapeAttrJavaScriptStringDQ(oValue),); + sHtml += ' oData.addColumn({type: "string", role: "annotation"});\n'; + # The data rows. + sHtml += ' oData.addRows([\n'; + for oRow in aoTable[1:]: + if oRow.sName: + sRow = ' [ "%s"' % (webutils.escapeAttrJavaScriptStringDQ(oRow.sName),); + else: + sRow = ' [ null'; + for iValue, oValue in enumerate(oRow.aoValues): + if not utils.isString(oValue): + sRow += ', %s' % (oValue,); + else: + sRow += ', "%s"' % (webutils.escapeAttrJavaScriptStringDQ(oValue),); + if oRow.asValues[iValue]: + sRow += ', "%s"' % (webutils.escapeAttrJavaScriptStringDQ(oRow.asValues[iValue]),); + else: + sRow += ', null'; + sHtml += sRow + '],\n'; + sHtml += ' ]);\n'; + else: + sHtml += ' var oData = google.visualization.arrayToDataTable([\n'; + for oRow in aoTable: + sRow = ' [ "%s"' % (webutils.escapeAttrJavaScriptStringDQ(oRow.sName),); + for oValue in oRow.aoValues: + if utils.isString(oValue): + sRow += ', "%s"' % (webutils.escapeAttrJavaScriptStringDQ(oValue),); + else: + sRow += ', %s' % (oValue,); + sHtml += sRow + '],\n'; + sHtml += ' ]);\n'; + + # Create and draw. + if not fUseMaterial: + sHtml += ' oGraph = new google.visualization.ColumnChart(document.getElementById("%s"));\n' \ + ' oGraph.draw(oData, dGraphOptions);\n' \ + % ( self._sId, ); + else: + sHtml += ' oGraph = new google.charts.Bar(document.getElementById("%s"));\n' \ + ' oGraph.draw(oData, google.charts.Bar.convertOptions(dGraphOptions));\n' \ + % ( self._sId, ); + + # clean and return. + sHtml += ' oData = null;\n' \ + ' return true;\n' \ + '};\n'; + + sHtml += '</script>\n' \ + '</div>\n'; + return sHtml; + + +class WuiHlpLineGraph(WuiHlpGraphGoogleChartsBase): + """ + Line graph. + """ + + ## @todo implement error bars. + kfNoErrorBarsSupport = True; + + def __init__(self, sId, oData, oDisp = None, fErrorBarY = False): + # oData must be a WuiHlpGraphDataTableEx like object. + WuiHlpGraphGoogleChartsBase.__init__(self, sId, oData, oDisp); + self._cMaxErrorBars = 12; + self._fErrorBarY = fErrorBarY; + + def setErrorBarY(self, fEnable): + """ Enables or Disables error bars, making this work like a line graph. """ + self._fErrorBarY = fEnable; + return True; + + def renderGraph(self): # pylint: disable=too-many-locals + fSlideFilter = True; + + # Tooltips? + cTooltips = 0; + for oSeries in self._oData.aoSeries: + cTooltips += oSeries.asHtmlTooltips is not None; + + # Unique on load function. + global g_cGraphs; + iGraph = g_cGraphs; + g_cGraphs += 1; + + sHtml = '<div id="%s">\n' % ( self._sId, ); + if fSlideFilter: + sHtml += ' <table><tr><td><div id="%s_graph"/></td></tr><tr><td><div id="%s_filter"/></td></tr></table>\n' \ + % ( self._sId, self._sId, ); + + sHtml += '<script type="text/javascript" src="https://www.google.com/jsapi"></script>\n' \ + '<script type="text/javascript">\n' \ + 'google.load("visualization", "1.0", { packages: ["corechart"%s] });\n' \ + 'google.setOnLoadCallback(tmDrawLineGraph%u);\n' \ + 'function tmDrawLineGraph%u()\n' \ + '{\n' \ + ' var fnResize;\n' \ + ' var fnRedraw;\n' \ + ' var idRedrawTimer = null;\n' \ + ' var cxCur = getElementWidthById("%s") - 20;\n' \ + ' var oGraph;\n' \ + ' var oData = new google.visualization.DataTable();\n' \ + ' var fpXYRatio = %u / %u;\n' \ + ' var dGraphOptions = \n' \ + ' {\n' \ + ' "title": "%s",\n' \ + ' "width": cxCur,\n' \ + ' "height": Math.round(cxCur / fpXYRatio),\n' \ + ' "pointSize": 2,\n' \ + ' "fontSize": %u,\n' \ + ' "hAxis": { "title": "%s", "minorGridlines": { count: 5 }},\n' \ + ' "vAxis": { "title": "%s", "minorGridlines": { count: 5 }},\n' \ + ' "theme": "maximized",\n' \ + ' "tooltip": { "isHtml": %s }\n' \ + ' };\n' \ + % ( ', "controls"' if fSlideFilter else '', + iGraph, + iGraph, + self._sId, + self._cxGraph, self._cyGraph, + self._sTitle if self._sTitle is not None else '', + self._cPtFont * self._cDpiGraph / 72, # fudge + self._oData.sXUnit if self._oData.sXUnit else '', + self._oData.sYUnit if self._oData.sYUnit else '', + 'true' if cTooltips > 0 else 'false', + ); + if fSlideFilter: + sHtml += ' var oDashboard = new google.visualization.Dashboard(document.getElementById("%s"));\n' \ + ' var oSlide = new google.visualization.ControlWrapper({\n' \ + ' "controlType": "NumberRangeFilter",\n' \ + ' "containerId": "%s_filter",\n' \ + ' "options": {\n' \ + ' "filterColumnIndex": 0,\n' \ + ' "ui": { "width": getElementWidthById("%s") / 2 }, \n' \ + ' }\n' \ + ' });\n' \ + % ( self._sId, + self._sId, + self._sId,); + + # Data variables. + for iSeries, oSeries in enumerate(self._oData.aoSeries): + sHtml += ' var aSeries%u = [\n' % (iSeries,); + if oSeries.asHtmlTooltips is None: + sHtml += '[%s,%s]' % ( oSeries.aoXValues[0], oSeries.aoYValues[0],); + for i in range(1, len(oSeries.aoXValues)): + if (i & 16) == 0: sHtml += '\n'; + sHtml += ',[%s,%s]' % ( oSeries.aoXValues[i], oSeries.aoYValues[i], ); + else: + sHtml += '[%s,%s,"%s"]' \ + % ( oSeries.aoXValues[0], oSeries.aoYValues[0], + webutils.escapeAttrJavaScriptStringDQ(oSeries.asHtmlTooltips[0]),); + for i in range(1, len(oSeries.aoXValues)): + if (i & 16) == 0: sHtml += '\n'; + sHtml += ',[%s,%s,"%s"]' \ + % ( oSeries.aoXValues[i], oSeries.aoYValues[i], + webutils.escapeAttrJavaScriptStringDQ(oSeries.asHtmlTooltips[i]),); + + sHtml += '];\n' + + sHtml += ' oData.addColumn("number", "%s");\n' % (self._oData.sXUnit if self._oData.sXUnit else '',); + cVColumns = 0; + for oSeries in self._oData.aoSeries: + sHtml += ' oData.addColumn("number", "%s");\n' % (oSeries.sName,); + if oSeries.asHtmlTooltips: + sHtml += ' oData.addColumn({"type": "string", "role": "tooltip", "p": {"html": true}});\n'; + cVColumns += 1; + cVColumns += 1; + sHtml += 'var i;\n' + + cVColumsDone = 0; + for iSeries, oSeries in enumerate(self._oData.aoSeries): + sVar = 'aSeries%u' % (iSeries,); + sHtml += ' for (i = 0; i < %s.length; i++)\n' \ + ' {\n' \ + ' oData.addRow([%s[i][0]%s,%s[i][1]%s%s]);\n' \ + % ( sVar, + sVar, + ',null' * cVColumsDone, + sVar, + '' if oSeries.asHtmlTooltips is None else ',%s[i][2]' % (sVar,), + ',null' * (cVColumns - cVColumsDone - 1 - (oSeries.asHtmlTooltips is not None)), + ); + sHtml += ' }\n' \ + ' %s = null\n' \ + % (sVar,); + cVColumsDone += 1 + (oSeries.asHtmlTooltips is not None); + + # Create and draw. + if fSlideFilter: + sHtml += ' oGraph = new google.visualization.ChartWrapper({\n' \ + ' "chartType": "LineChart",\n' \ + ' "containerId": "%s_graph",\n' \ + ' "options": dGraphOptions\n' \ + ' });\n' \ + ' oDashboard.bind(oSlide, oGraph);\n' \ + ' oDashboard.draw(oData);\n' \ + % ( self._sId, ); + else: + sHtml += ' oGraph = new google.visualization.LineChart(document.getElementById("%s"));\n' \ + ' oGraph.draw(oData, dGraphOptions);\n' \ + % ( self._sId, ); + + # Register a resize handler for redrawing the graph, using a timer to delay it. + sHtml += ' fnRedraw = function() {\n' \ + ' var cxNew = getElementWidthById("%s") - 6;\n' \ + ' if (Math.abs(cxNew - cxCur) > 8)\n' \ + ' {\n' \ + ' cxCur = cxNew;\n' \ + ' dGraphOptions["width"] = cxNew;\n' \ + ' dGraphOptions["height"] = Math.round(cxNew / fpXYRatio);\n' \ + ' oGraph.draw(oData, dGraphOptions);\n' \ + ' }\n' \ + ' clearTimeout(idRedrawTimer);\n' \ + ' idRedrawTimer = null;\n' \ + ' return true;\n' \ + ' };\n' \ + ' fnResize = function() {\n' \ + ' if (idRedrawTimer != null) { clearTimeout(idRedrawTimer); } \n' \ + ' idRedrawTimer = setTimeout(fnRedraw, 512);\n' \ + ' return true;\n' \ + ' };\n' \ + ' if (window.attachEvent)\n' \ + ' { window.attachEvent("onresize", fnResize); }\n' \ + ' else if (window.addEventListener)\n' \ + ' { window.addEventListener("resize", fnResize, true); }\n' \ + % ( self._sId, ); + + # clean up what the callbacks don't need. + sHtml += ' oData = null;\n' \ + ' aaaSeries = null;\n'; + + # done; + sHtml += ' return true;\n' \ + '};\n'; + + sHtml += '</script>\n' \ + '</div>\n'; + return sHtml; + + +class WuiHlpLineGraphErrorbarY(WuiHlpLineGraph): + """ + Line graph with an errorbar for the Y axis. + """ + + def __init__(self, sId, oData, oDisp = None): + WuiHlpLineGraph.__init__(self, sId, oData, fErrorBarY = True); + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuihlpgraphmatplotlib.py b/src/VBox/ValidationKit/testmanager/webui/wuihlpgraphmatplotlib.py new file mode 100755 index 00000000..e96176db --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuihlpgraphmatplotlib.py @@ -0,0 +1,341 @@ +# -*- coding: utf-8 -*- +# $Id: wuihlpgraphmatplotlib.py $ + +""" +Test Manager Web-UI - Graph Helpers - Implemented using matplotlib. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + +# Standard Python Import and extensions installed on the system. +import re; +import sys; +if sys.version_info[0] >= 3: + from io import StringIO as StringIO; # pylint: disable=import-error,no-name-in-module,useless-import-alias +else: + from StringIO import StringIO as StringIO; # pylint: disable=import-error,no-name-in-module,useless-import-alias + +import matplotlib; # pylint: disable=import-error +matplotlib.use('Agg'); # Force backend. +import matplotlib.pyplot; # pylint: disable=import-error +from numpy import arange as numpy_arange; # pylint: disable=no-name-in-module,import-error,wrong-import-order + +# Validation Kit imports. +from testmanager.webui.wuihlpgraphbase import WuiHlpGraphBase; + + +class WuiHlpGraphMatplotlibBase(WuiHlpGraphBase): + """ Base class for the matplotlib graphs. """ + + def __init__(self, sId, oData, oDisp): + WuiHlpGraphBase.__init__(self, sId, oData, oDisp); + self._fXkcdStyle = True; + + def setXkcdStyle(self, fEnabled = True): + """ Enables xkcd style graphs for implementations that supports it. """ + self._fXkcdStyle = fEnabled; + return True; + + def _createFigure(self): + """ + Wrapper around matplotlib.pyplot.figure that feeds the figure the + basic graph configuration. + """ + if self._fXkcdStyle and matplotlib.__version__ > '1.2.9': + matplotlib.pyplot.xkcd(); # pylint: disable=no-member + matplotlib.rcParams.update({'font.size': self._cPtFont}); + + oFigure = matplotlib.pyplot.figure(figsize = (float(self._cxGraph) / self._cDpiGraph, + float(self._cyGraph) / self._cDpiGraph), + dpi = self._cDpiGraph); + return oFigure; + + def _produceSvg(self, oFigure, fTightLayout = True): + """ Creates an SVG string from the given figure. """ + oOutput = StringIO(); + if fTightLayout: + oFigure.tight_layout(); + oFigure.savefig(oOutput, format = 'svg'); + + if self._oDisp and self._oDisp.isBrowserGecko('20100101'): + # This browser will stretch images to fit if no size or width is given. + sSubstitute = r'\1 \3 reserveAspectRatio="xMidYMin meet"'; + else: + # Chrome and IE likes to have the sizes as well as the viewBox. + sSubstitute = r'\1 \3 reserveAspectRatio="xMidYMin meet" \2 \4'; + return re.sub(r'(<svg) (height="\d+pt") (version="\d+.\d+" viewBox="\d+ \d+ \d+ \d+") (width="\d+pt")', + sSubstitute, + oOutput.getvalue().decode('utf8'), + count = 1); + +class WuiHlpBarGraph(WuiHlpGraphMatplotlibBase): + """ + Bar graph. + """ + + def __init__(self, sId, oData, oDisp = None): + WuiHlpGraphMatplotlibBase.__init__(self, sId, oData, oDisp); + self.fpMax = None; + self.fpMin = 0.0; + self.cxBarWidth = None; + + def setRangeMax(self, fpMax): + """ Sets the max range.""" + self.fpMax = float(fpMax); + return None; + + def invertYDirection(self): + """ Inverts the direction of the Y-axis direction. """ + ## @todo self.fYInverted = True; + return None; + + def renderGraph(self): # pylint: disable=too-many-locals + aoTable = self._oData.aoTable; + + # + # Extract/structure the required data. + # + aoSeries = []; + for j in range(len(aoTable[1].aoValues)): + aoSeries.append([]); + asNames = []; + oXRange = numpy_arange(self._oData.getGroupCount()); + fpMin = self.fpMin; + fpMax = self.fpMax; + if self.fpMax is None: + fpMax = float(aoTable[1].aoValues[0]); + + for i in range(1, len(aoTable)): + asNames.append(aoTable[i].sName); + for j, oValue in enumerate(aoTable[i].aoValues): + fpValue = float(oValue); + aoSeries[j].append(fpValue); + if fpValue < fpMin: + fpMin = fpValue; + if fpValue > fpMax: + fpMax = fpValue; + + fpMid = fpMin + (fpMax - fpMin) / 2.0; + + if self.cxBarWidth is None: + self.cxBarWidth = 1.0 / (len(aoTable[0].asValues) + 1.1); + + # Render the PNG. + oFigure = self._createFigure(); + oSubPlot = oFigure.add_subplot(1, 1, 1); + + aoBars = []; + for i, _ in enumerate(aoSeries): + sColor = self.calcSeriesColor(i); + aoBars.append(oSubPlot.bar(oXRange + self.cxBarWidth * i, + aoSeries[i], + self.cxBarWidth, + color = sColor, + align = 'edge')); + + #oSubPlot.set_title('Title') + #oSubPlot.set_xlabel('X-axis') + #oSubPlot.set_xticks(oXRange + self.cxBarWidth); + oSubPlot.set_xticks(oXRange); + oLegend = oSubPlot.legend(aoTable[0].asValues, loc = 'best', fancybox = True); + oLegend.get_frame().set_alpha(0.5); + oSubPlot.set_xticklabels(asNames, ha = "left"); + #oSubPlot.set_ylabel('Y-axis') + oSubPlot.set_yticks(numpy_arange(fpMin, fpMax + (fpMax - fpMin) / 10 * 0, fpMax / 10)); + oSubPlot.grid(True); + fpPadding = (fpMax - fpMin) * 0.02; + for i, _ in enumerate(aoBars): + aoRects = aoBars[i] + for j, _ in enumerate(aoRects): + oRect = aoRects[j]; + fpValue = float(aoTable[j + 1].aoValues[i]); + if fpValue <= fpMid: + oSubPlot.text(oRect.get_x() + oRect.get_width() / 2.0, + oRect.get_height() + fpPadding, + aoTable[j + 1].asValues[i], + ha = 'center', va = 'bottom', rotation = 'vertical', alpha = 0.6, fontsize = 'small'); + else: + oSubPlot.text(oRect.get_x() + oRect.get_width() / 2.0, + oRect.get_height() - fpPadding, + aoTable[j + 1].asValues[i], + ha = 'center', va = 'top', rotation = 'vertical', alpha = 0.6, fontsize = 'small'); + + return self._produceSvg(oFigure); + + + + +class WuiHlpLineGraph(WuiHlpGraphMatplotlibBase): + """ + Line graph. + """ + + def __init__(self, sId, oData, oDisp = None, fErrorBarY = False): + # oData must be a WuiHlpGraphDataTableEx like object. + WuiHlpGraphMatplotlibBase.__init__(self, sId, oData, oDisp); + self._cMaxErrorBars = 12; + self._fErrorBarY = fErrorBarY; + + def setErrorBarY(self, fEnable): + """ Enables or Disables error bars, making this work like a line graph. """ + self._fErrorBarY = fEnable; + return True; + + def renderGraph(self): # pylint: disable=too-many-locals + aoSeries = self._oData.aoSeries; + + oFigure = self._createFigure(); + oSubPlot = oFigure.add_subplot(1, 1, 1); + if self._oData.sYUnit is not None: + oSubPlot.set_ylabel(self._oData.sYUnit); + if self._oData.sXUnit is not None: + oSubPlot.set_xlabel(self._oData.sXUnit); + + cSeriesNames = 0; + cYMin = 1000; + cYMax = 0; + for iSeries, oSeries in enumerate(aoSeries): + sColor = self.calcSeriesColor(iSeries); + cYMin = min(cYMin, min(oSeries.aoYValues)); + cYMax = max(cYMax, max(oSeries.aoYValues)); + if not self._fErrorBarY: + oSubPlot.errorbar(oSeries.aoXValues, oSeries.aoYValues, color = sColor); + elif len(oSeries.aoXValues) > self._cMaxErrorBars: + if matplotlib.__version__ < '1.3.0': + oSubPlot.errorbar(oSeries.aoXValues, oSeries.aoYValues, color = sColor); + else: + oSubPlot.errorbar(oSeries.aoXValues, oSeries.aoYValues, + yerr = [oSeries.aoYErrorBarBelow, oSeries.aoYErrorBarAbove], + errorevery = len(oSeries.aoXValues) / self._cMaxErrorBars, + color = sColor ); + else: + oSubPlot.errorbar(oSeries.aoXValues, oSeries.aoYValues, + yerr = [oSeries.aoYErrorBarBelow, oSeries.aoYErrorBarAbove], + color = sColor); + cSeriesNames += oSeries.sName is not None; + + if cYMin != 0 or cYMax != 0: + oSubPlot.set_ylim(bottom = 0); + + if cSeriesNames > 0: + oLegend = oSubPlot.legend([oSeries.sName for oSeries in aoSeries], loc = 'best', fancybox = True); + oLegend.get_frame().set_alpha(0.5); + + if self._sTitle is not None: + oSubPlot.set_title(self._sTitle); + + if self._cxGraph >= 256: + oSubPlot.minorticks_on(); + oSubPlot.grid(True, 'major', axis = 'both'); + oSubPlot.grid(True, 'both', axis = 'x'); + + if True: # pylint: disable=using-constant-test + # oSubPlot.axis('off'); + #oSubPlot.grid(True, 'major', axis = 'none'); + #oSubPlot.grid(True, 'both', axis = 'none'); + matplotlib.pyplot.setp(oSubPlot, xticks = [], yticks = []); + + return self._produceSvg(oFigure); + + +class WuiHlpLineGraphErrorbarY(WuiHlpLineGraph): + """ + Line graph with an errorbar for the Y axis. + """ + + def __init__(self, sId, oData, oDisp = None): + WuiHlpLineGraph.__init__(self, sId, oData, fErrorBarY = True); + + +class WuiHlpMiniSuccessRateGraph(WuiHlpGraphMatplotlibBase): + """ + Mini rate graph. + """ + + def __init__(self, sId, oData, oDisp = None): + """ + oData must be a WuiHlpGraphDataTableEx like object, but only aoSeries, + aoSeries[].aoXValues, and aoSeries[].aoYValues will be used. The + values are expected to be a percentage, i.e. values between 0 and 100. + """ + WuiHlpGraphMatplotlibBase.__init__(self, sId, oData, oDisp); + self.setFontSize(6); + + def renderGraph(self): # pylint: disable=too-many-locals + assert len(self._oData.aoSeries) == 1; + oSeries = self._oData.aoSeries[0]; + + # hacking + #self.setWidth(512); + #self.setHeight(128); + # end + + oFigure = self._createFigure(); + from mpl_toolkits.axes_grid.axislines import SubplotZero; # pylint: disable=import-error + oAxis = SubplotZero(oFigure, 111); + oFigure.add_subplot(oAxis); + + # Disable all the normal axis. + oAxis.axis['right'].set_visible(False) + oAxis.axis['top'].set_visible(False) + oAxis.axis['bottom'].set_visible(False) + oAxis.axis['left'].set_visible(False) + + # Use the zero axis instead. + oAxis.axis['yzero'].set_axisline_style('-|>'); + oAxis.axis['yzero'].set_visible(True); + oAxis.axis['xzero'].set_axisline_style('-|>'); + oAxis.axis['xzero'].set_visible(True); + + if oSeries.aoYValues[-1] == 100: + sColor = 'green'; + elif oSeries.aoYValues[-1] > 75: + sColor = 'yellow'; + else: + sColor = 'red'; + oAxis.plot(oSeries.aoXValues, oSeries.aoYValues, '.-', color = sColor, linewidth = 3); + oAxis.fill_between(oSeries.aoXValues, oSeries.aoYValues, facecolor = sColor, alpha = 0.5) + + oAxis.set_xlim(left = -0.01); + oAxis.set_xticklabels([]); + oAxis.set_xmargin(1); + + oAxis.set_ylim(bottom = 0, top = 100); + oAxis.set_yticks([0, 50, 100]); + oAxis.set_ylabel('%'); + #oAxis.set_yticklabels([]); + oAxis.set_yticklabels(['', '%', '']); + + return self._produceSvg(oFigure, False); + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuihlpgraphsimple.py b/src/VBox/ValidationKit/testmanager/webui/wuihlpgraphsimple.py new file mode 100755 index 00000000..f31de131 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuihlpgraphsimple.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +# $Id: wuihlpgraphsimple.py $ + +""" +Test Manager Web-UI - Graph Helpers - Simple/Stub Implementation. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Validation Kit imports. +from common.webutils import escapeAttr, escapeElem; +from testmanager.webui.wuihlpgraphbase import WuiHlpGraphBase; + + + +class WuiHlpBarGraph(WuiHlpGraphBase): + """ + Bar graph. + """ + + def __init__(self, sId, oData, oDisp = None): + WuiHlpGraphBase.__init__(self, sId, oData, oDisp); + self.cxMaxBar = 480; + self.fpMax = None; + self.fpMin = 0.0; + + def setRangeMax(self, fpMax): + """ Sets the max range.""" + self.fpMax = float(fpMax); + return None; + + def invertYDirection(self): + """ Not supported. """ + return None; + + def renderGraph(self): + aoTable = self._oData.aoTable; + sReport = '<div class="tmbargraph">\n'; + + # Figure the range. + fpMin = self.fpMin; + fpMax = self.fpMax; + if self.fpMax is None: + fpMax = float(aoTable[1].aoValues[0]); + for i in range(1, len(aoTable)): + for oValue in aoTable[i].aoValues: + fpValue = float(oValue); + if fpValue < fpMin: + fpMin = fpValue; + if fpValue > fpMax: + fpMax = fpValue; + assert fpMin >= 0; + + # Format the data. + sReport += '<table class="tmbargraphl1" border="1" id="%s">\n' % (escapeAttr(self._sId),); + for i in range(1, len(aoTable)): + oRow = aoTable[i]; + sReport += ' <tr>\n' \ + ' <td>%s</td>\n' \ + ' <td height="100%%" width="%spx">\n' \ + ' <table class="tmbargraphl2" height="100%%" width="100%%" ' \ + 'border="0" cellspacing="0" cellpadding="0">\n' \ + % (escapeElem(oRow.sName), escapeAttr(str(self.cxMaxBar + 2))); + for j, oValue in enumerate(oRow.aoValues): + cPct = int(float(oValue) * 100 / fpMax); + cxBar = int(float(oValue) * self.cxMaxBar / fpMax); + sValue = escapeElem(oRow.asValues[j]); + sColor = self.kasColors[j % len(self.kasColors)]; + sInvColor = 'white'; + if sColor[0] == '#' and len(sColor) == 7: + sInvColor = '#%06x' % (~int(sColor[1:],16) & 0xffffff,); + + sReport += ' <tr><td>\n' \ + ' <table class="tmbargraphl3" height="100%%" border="0" cellspacing="0" cellpadding="0">\n' \ + ' <tr>\n'; + if cPct >= 99: + sReport += ' <td width="%spx" nowrap bgcolor="%s" align="right" style="color:%s;">' \ + '%s </td>\n' \ + % (cxBar, sColor, sInvColor, sValue); + elif cPct < 1: + sReport += ' <td width="%spx" nowrap style="color:%s;">%s</td>\n' \ + % (self.cxMaxBar - cxBar, sColor, sValue); + elif cPct >= 50: + sReport += ' <td width="%spx" nowrap bgcolor="%s" align="right" style="color:%s;">' \ + '%s </td>\n' \ + ' <td width="%spx" nowrap><div> </div></td>\n' \ + % (cxBar, sColor, sInvColor, sValue, self.cxMaxBar - cxBar); + else: + sReport += ' <td width="%spx" nowrap bgcolor="%s"></td>\n' \ + ' <td width="%spx" nowrap> %s</td>\n' \ + % (cxBar, sColor, self.cxMaxBar - cxBar, sValue); + sReport += ' </tr>\n' \ + ' </table>\n' \ + ' </td></tr>\n' + sReport += ' </table>\n' \ + ' </td>\n' \ + ' </tr>\n'; + if i + 1 < len(aoTable) and len(oRow.aoValues) > 1: + sReport += ' <tr></tr>\n' + + sReport += '</table>\n'; + + sReport += '<div class="tmgraphlegend">\n' \ + ' <p>Legend:\n'; + for j, sValue in enumerate(aoTable[0].asValues): + sColor = self.kasColors[j % len(self.kasColors)]; + sReport += ' <font color="%s">■ %s</font>\n' % (sColor, escapeElem(sValue),); + sReport += ' </p>\n' \ + '</div>\n'; + + sReport += '</div>\n'; + return sReport; + + + + +class WuiHlpLineGraph(WuiHlpGraphBase): + """ + Line graph. + """ + + def __init__(self, sId, oData, oDisp): + WuiHlpGraphBase.__init__(self, sId, oData, oDisp); + + +class WuiHlpLineGraphErrorbarY(WuiHlpLineGraph): + """ + Line graph with an errorbar for the Y axis. + """ + + pass; # pylint: disable=unnecessary-pass + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuilogviewer.py b/src/VBox/ValidationKit/testmanager/webui/wuilogviewer.py new file mode 100755 index 00000000..79dfe46d --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuilogviewer.py @@ -0,0 +1,251 @@ +# -*- coding: utf-8 -*- +# $Id: wuilogviewer.py $ + +""" +Test Manager WUI - Log viewer +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Validation Kit imports. +from common import webutils; +from testmanager.core.testset import TestSetData; +from testmanager.webui.wuicontentbase import WuiContentBase, WuiTmLink; +from testmanager.webui.wuimain import WuiMain; + + +class WuiLogViewer(WuiContentBase): + """Log viewer.""" + + def __init__(self, oTestSet, oLogFile, cbChunk, iChunk, aoTimestamps, oDisp = None, fnDPrint = None): + WuiContentBase.__init__(self, oDisp = oDisp, fnDPrint = fnDPrint); + self._oTestSet = oTestSet; + self._oLogFile = oLogFile; + self._cbChunk = cbChunk; + self._iChunk = iChunk; + self._aoTimestamps = aoTimestamps; + + def _generateNavigation(self, cbFile): + """Generate the HTML for the log navigation.""" + + dParams = { + WuiMain.ksParamAction: WuiMain.ksActionViewLog, + WuiMain.ksParamLogSetId: self._oTestSet.idTestSet, + WuiMain.ksParamLogFileId: self._oLogFile.idTestResultFile, + WuiMain.ksParamLogChunkSize: self._cbChunk, + WuiMain.ksParamLogChunkNo: self._iChunk, + }; + + # + # The page walker. + # + dParams2 = dict(dParams); + del dParams2[WuiMain.ksParamLogChunkNo]; + sHrefFmt = '<a href="?%s&%s=%%s" title="%%s">%%s</a>' \ + % (webutils.encodeUrlParams(dParams2).replace('%', '%%'), WuiMain.ksParamLogChunkNo,); + sHtmlWalker = self.genericPageWalker(self._iChunk, (cbFile + self._cbChunk - 1) // self._cbChunk, + sHrefFmt, 11, 0, 'chunk'); + + # + # The chunk size selector. + # + + dParams2 = dict(dParams); + del dParams2[WuiMain.ksParamLogChunkSize]; + sHtmlSize = '<form name="ChunkSizeForm" method="GET">\n' \ + ' Max <select name="%s" onchange="window.location=\'?%s&%s=\' + ' \ + 'this.options[this.selectedIndex].value;" title="Max items per page">\n' \ + % ( WuiMain.ksParamLogChunkSize, webutils.encodeUrlParams(dParams2), WuiMain.ksParamLogChunkSize,); + + for cbChunk in [ 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, + 4194304, 8388608, 16777216 ]: + sHtmlSize += ' <option value="%d" %s>%d bytes</option>\n' \ + % (cbChunk, 'selected="selected"' if cbChunk == self._cbChunk else '', cbChunk); + sHtmlSize += ' </select> per page\n' \ + '</form>\n' + + # + # Download links. + # + oRawLink = WuiTmLink('View Raw', '', + { WuiMain.ksParamAction: WuiMain.ksActionGetFile, + WuiMain.ksParamGetFileSetId: self._oTestSet.idTestSet, + WuiMain.ksParamGetFileId: self._oLogFile.idTestResultFile, + WuiMain.ksParamGetFileDownloadIt: False, + }, + sTitle = '%u MiB' % ((cbFile + 1048576 - 1) // 1048576,) ); + oDownloadLink = WuiTmLink('Download Log', '', + { WuiMain.ksParamAction: WuiMain.ksActionGetFile, + WuiMain.ksParamGetFileSetId: self._oTestSet.idTestSet, + WuiMain.ksParamGetFileId: self._oLogFile.idTestResultFile, + WuiMain.ksParamGetFileDownloadIt: True, + }, + sTitle = '%u MiB' % ((cbFile + 1048576 - 1) // 1048576,) ); + oTestSetLink = WuiTmLink('Test Set', '', + { WuiMain.ksParamAction: WuiMain.ksActionTestResultDetails, + TestSetData.ksParam_idTestSet: self._oTestSet.idTestSet, + }); + + + # + # Combine the elements and return. + # + return '<div class="tmlogviewernavi">\n' \ + ' <table width=100%>\n' \ + ' <tr>\n' \ + ' <td width=20%>\n' \ + ' ' + oTestSetLink.toHtml() + '\n' \ + ' ' + oRawLink.toHtml() + '\n' \ + ' ' + oDownloadLink.toHtml() + '\n' \ + ' </td>\n' \ + ' <td width=60% align=center>' + sHtmlWalker + '</td>' \ + ' <td width=20% align=right>' + sHtmlSize + '</td>\n' \ + ' </tr>\n' \ + ' </table>\n' \ + '</div>\n'; + + def _displayLog(self, oFile, offFile, cbFile, aoTimestamps): + """Displays the current section of the log file.""" + from testmanager.core import db; + + def prepCurTs(): + """ Formats the current timestamp. """ + if iCurTs < len(aoTimestamps): + oTsZulu = db.dbTimestampToZuluDatetime(aoTimestamps[iCurTs]); + return (oTsZulu.strftime('%H:%M:%S.%f'), oTsZulu.strftime('%H_%M_%S_%f')); + return ('~~|~~|~~|~~~~~~', '~~|~~|~~|~~~~~~'); # ASCII chars with high values. Limit hits. + + def isCurLineAtOrAfterCurTs(): + """ Checks if the current line starts with a timestamp that is after the current one. """ + if len(sLine) >= 15 \ + and sLine[2] == ':' \ + and sLine[5] == ':' \ + and sLine[8] == '.' \ + and sLine[14] in '0123456789': + if sLine[:15] >= sCurTs and iCurTs < len(aoTimestamps): + return True; + return False; + + # Figure the end offset. + offEnd = offFile + self._cbChunk; + offEnd = min(offEnd, cbFile); + + # + # Here is an annoying thing, we cannot seek in zip file members. So, + # since we have to read from the start, we can just as well count line + # numbers while we're at it. + # + iCurTs = 0; + (sCurTs, sCurId) = prepCurTs(); + offCur = 0; + iLine = 0; + while True: + sLine = oFile.readline().decode('utf-8', 'replace'); + offLine = offCur; + iLine += 1; + offCur += len(sLine); + if offCur >= offFile or not sLine: + break; + while isCurLineAtOrAfterCurTs(): + iCurTs += 1; + (sCurTs, sCurId) = prepCurTs(); + + # + # Got to where we wanted, format the chunk. + # + asLines = ['\n<div class="tmlog">\n<pre>\n', ]; + while True: + # The timestamp IDs. + sPrevTs = ''; + while isCurLineAtOrAfterCurTs(): + if sPrevTs != sCurTs: + asLines.append('<a id="%s"></a>' % (sCurId,)); + iCurTs += 1; + (sCurTs, sCurId) = prepCurTs(); + + # The line. + asLines.append('<a id="L%d" href="#L%d">%05d</a><a id="O%d"></a>%s\n' \ + % (iLine, iLine, iLine, offLine, webutils.escapeElem(sLine.rstrip()))); + + # next + if offCur >= offEnd: + break; + sLine = oFile.readline().decode('utf-8', 'replace'); + offLine = offCur; + iLine += 1; + offCur += len(sLine); + if not sLine: + break; + asLines.append('<pre/></div>\n'); + return ''.join(asLines); + + + def show(self): + """Shows the log.""" + + if self._oLogFile.sDescription not in [ '', None ]: + sTitle = '%s - %s' % (self._oLogFile.sFile, self._oLogFile.sDescription); + else: + sTitle = '%s' % (self._oLogFile.sFile,); + + # + # Open the log file. No universal line endings here. + # + (oFile, oSizeOrError, _) = self._oTestSet.openFile(self._oLogFile.sFile, 'rb'); + if oFile is None: + return (sTitle, '<p>%s</p>\n' % (webutils.escapeElem(oSizeOrError),),); + cbFile = oSizeOrError; + + # + # Generate the page. + # + + # Start with a focus hack. + sHtml = '<div id="tmlogoutdiv" tabindex="0">\n' \ + '<script lang="text/javascript">\n' \ + 'document.getElementById(\'tmlogoutdiv\').focus();\n' \ + '</script>\n'; + + sNaviHtml = self._generateNavigation(cbFile); + sHtml += sNaviHtml; + + offFile = self._iChunk * self._cbChunk; + if offFile < cbFile: + sHtml += self._displayLog(oFile, offFile, cbFile, self._aoTimestamps); + sHtml += sNaviHtml; + else: + sHtml += '<p>End Of File</p>'; + + return (sTitle, sHtml); + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuimain.py b/src/VBox/ValidationKit/testmanager/webui/wuimain.py new file mode 100755 index 00000000..ebeb9bb6 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuimain.py @@ -0,0 +1,1344 @@ +# -*- coding: utf-8 -*- +# $Id: wuimain.py $ + +""" +Test Manager Core - WUI - The Main page. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Standard Python imports. + +# Validation Kit imports. +from testmanager import config; +from testmanager.core.base import TMExceptionBase, TMTooManyRows; +from testmanager.webui.wuibase import WuiDispatcherBase, WuiException; +from testmanager.webui.wuicontentbase import WuiTmLink; +from common import webutils, utils; + + + +class WuiMain(WuiDispatcherBase): + """ + WUI Main page. + + Note! All cylic dependency avoiance stuff goes here in the dispatcher code, + not in the action specific code. This keeps the uglyness in one place + and reduces load time dependencies in the more critical code path. + """ + + ## The name of the script. + ksScriptName = 'index.py' + + ## @name Actions + ## @{ + ksActionResultsUnGrouped = 'ResultsUnGrouped' + ksActionResultsGroupedBySchedGroup = 'ResultsGroupedBySchedGroup' + ksActionResultsGroupedByTestGroup = 'ResultsGroupedByTestGroup' + ksActionResultsGroupedByBuildRev = 'ResultsGroupedByBuildRev' + ksActionResultsGroupedByBuildCat = 'ResultsGroupedByBuildCat' + ksActionResultsGroupedByTestBox = 'ResultsGroupedByTestBox' + ksActionResultsGroupedByTestCase = 'ResultsGroupedByTestCase' + ksActionResultsGroupedByOS = 'ResultsGroupedByOS' + ksActionResultsGroupedByArch = 'ResultsGroupedByArch' + ksActionTestSetDetails = 'TestSetDetails'; + ksActionTestResultDetails = ksActionTestSetDetails; + ksActionTestSetDetailsFromResult = 'TestSetDetailsFromResult' + ksActionTestResultFailureDetails = 'TestResultFailureDetails' + ksActionTestResultFailureAdd = 'TestResultFailureAdd' + ksActionTestResultFailureAddPost = 'TestResultFailureAddPost' + ksActionTestResultFailureEdit = 'TestResultFailureEdit' + ksActionTestResultFailureEditPost = 'TestResultFailureEditPost' + ksActionTestResultFailureDoRemove = 'TestResultFailureDoRemove' + ksActionViewLog = 'ViewLog' + ksActionGetFile = 'GetFile' + ksActionReportSummary = 'ReportSummary'; + ksActionReportRate = 'ReportRate'; + ksActionReportTestCaseFailures = 'ReportTestCaseFailures'; + ksActionReportTestBoxFailures = 'ReportTestBoxFailures'; + ksActionReportFailureReasons = 'ReportFailureReasons'; + ksActionGraphWiz = 'GraphWiz'; + ksActionVcsHistoryTooltip = 'VcsHistoryTooltip'; ##< Hardcoded in common.js. + ## @} + + ## @name Standard report parameters + ## @{ + ksParamReportPeriods = 'cPeriods'; + ksParamReportPeriodInHours = 'cHoursPerPeriod'; + ksParamReportSubject = 'sSubject'; + ksParamReportSubjectIds = 'SubjectIds'; + ## @} + + ## @name Graph Wizard parameters + ## Common parameters: ksParamReportPeriods, ksParamReportPeriodInHours, ksParamReportSubjectIds, + ## ksParamReportSubject, ksParamEffectivePeriod, and ksParamEffectiveDate. + ## @{ + ksParamGraphWizTestBoxIds = 'aidTestBoxes'; + ksParamGraphWizBuildCatIds = 'aidBuildCats'; + ksParamGraphWizTestCaseIds = 'aidTestCases'; + ksParamGraphWizSepTestVars = 'fSepTestVars'; + ksParamGraphWizImpl = 'enmImpl'; + ksParamGraphWizWidth = 'cx'; + ksParamGraphWizHeight = 'cy'; + ksParamGraphWizDpi = 'dpi'; + ksParamGraphWizFontSize = 'cPtFont'; + ksParamGraphWizErrorBarY = 'fErrorBarY'; + ksParamGraphWizMaxErrorBarY = 'cMaxErrorBarY'; + ksParamGraphWizMaxPerGraph = 'cMaxPerGraph'; + ksParamGraphWizXkcdStyle = 'fXkcdStyle'; + ksParamGraphWizTabular = 'fTabular'; + ksParamGraphWizSrcTestSetId = 'idSrcTestSet'; + ## @} + + ## @name Graph implementations values for ksParamGraphWizImpl. + ## @{ + ksGraphWizImpl_Default = 'default'; + ksGraphWizImpl_Matplotlib = 'matplotlib'; + ksGraphWizImpl_Charts = 'charts'; + kasGraphWizImplValid = [ ksGraphWizImpl_Default, ksGraphWizImpl_Matplotlib, ksGraphWizImpl_Charts]; + kaasGraphWizImplCombo = [ + ( ksGraphWizImpl_Default, 'Default' ), + ( ksGraphWizImpl_Matplotlib, 'Matplotlib (server)' ), + ( ksGraphWizImpl_Charts, 'Google Charts (client)'), + ]; + ## @} + + ## @name Log Viewer parameters. + ## @{ + ksParamLogSetId = 'LogViewer_idTestSet'; + ksParamLogFileId = 'LogViewer_idFile'; + ksParamLogChunkSize = 'LogViewer_cbChunk'; + ksParamLogChunkNo = 'LogViewer_iChunk'; + ## @} + + ## @name File getter parameters. + ## @{ + ksParamGetFileSetId = 'GetFile_idTestSet'; + ksParamGetFileId = 'GetFile_idFile'; + ksParamGetFileDownloadIt = 'GetFile_fDownloadIt'; + ## @} + + ## @name VCS history parameters. + ## @{ + ksParamVcsHistoryRepository = 'repo'; + ksParamVcsHistoryRevision = 'rev'; + ksParamVcsHistoryEntries = 'cEntries'; + ## @} + + ## @name Test result listing parameters. + ## @{ + ## If this param is specified, then show only results for this member when results grouped by some parameter. + ksParamGroupMemberId = 'GroupMemberId' + ## Optional parameter for indicating whether to restrict the listing to failures only. + ksParamOnlyFailures = 'OnlyFailures'; + ## The sheriff parameter for getting failures needing a reason or two assigned to them. + ksParamOnlyNeedingReason = 'OnlyNeedingReason'; + ## Result listing sorting. + ksParamTestResultsSortBy = 'enmSortBy' + ## @} + + ## Effective time period. one of the first column values in kaoResultPeriods. + ksParamEffectivePeriod = 'sEffectivePeriod' + + ## Test result period values. + kaoResultPeriods = [ + ( '1 hour', '1 hour', 1 ), + ( '2 hours', '2 hours', 2 ), + ( '3 hours', '3 hours', 3 ), + ( '6 hours', '6 hours', 6 ), + ( '12 hours', '12 hours', 12 ), + + ( '1 day', '1 day', 24 ), + ( '2 days', '2 days', 48 ), + ( '3 days', '3 days', 72 ), + + ( '1 week', '1 week', 168 ), + ( '2 weeks', '2 weeks', 336 ), + ( '3 weeks', '3 weeks', 504 ), + + ( '1 month', '1 month', 31 * 24 ), # The approx hour count varies with the start date. + ( '2 months', '2 months', (31 + 31) * 24 ), # Using maximum values. + ( '3 months', '3 months', (31 + 30 + 31) * 24 ), + + ( '6 months', '6 months', (31 + 31 + 30 + 31 + 30 + 31) * 24 ), + + ( '1 year', '1 year', 365 * 24 ), + ]; + ## The default test result period. + ksResultPeriodDefault = '6 hours'; + + + + def __init__(self, oSrvGlue): + WuiDispatcherBase.__init__(self, oSrvGlue, self.ksScriptName); + self._sTemplate = 'template.html' + + # + # Populate the action dispatcher dictionary. + # Lambda is forbidden because of readability, speed and reducing number of imports. + # + self._dDispatch[self.ksActionResultsUnGrouped] = self._actionResultsUnGrouped; + self._dDispatch[self.ksActionResultsGroupedByTestGroup] = self._actionResultsGroupedByTestGroup; + self._dDispatch[self.ksActionResultsGroupedByBuildRev] = self._actionResultsGroupedByBuildRev; + self._dDispatch[self.ksActionResultsGroupedByBuildCat] = self._actionResultsGroupedByBuildCat; + self._dDispatch[self.ksActionResultsGroupedByTestBox] = self._actionResultsGroupedByTestBox; + self._dDispatch[self.ksActionResultsGroupedByTestCase] = self._actionResultsGroupedByTestCase; + self._dDispatch[self.ksActionResultsGroupedByOS] = self._actionResultsGroupedByOS; + self._dDispatch[self.ksActionResultsGroupedByArch] = self._actionResultsGroupedByArch; + self._dDispatch[self.ksActionResultsGroupedBySchedGroup] = self._actionResultsGroupedBySchedGroup; + + self._dDispatch[self.ksActionTestSetDetails] = self._actionTestSetDetails; + self._dDispatch[self.ksActionTestSetDetailsFromResult] = self._actionTestSetDetailsFromResult; + + self._dDispatch[self.ksActionTestResultFailureAdd] = self._actionTestResultFailureAdd; + self._dDispatch[self.ksActionTestResultFailureAddPost] = self._actionTestResultFailureAddPost; + self._dDispatch[self.ksActionTestResultFailureDetails] = self._actionTestResultFailureDetails; + self._dDispatch[self.ksActionTestResultFailureDoRemove] = self._actionTestResultFailureDoRemove; + self._dDispatch[self.ksActionTestResultFailureEdit] = self._actionTestResultFailureEdit; + self._dDispatch[self.ksActionTestResultFailureEditPost] = self._actionTestResultFailureEditPost; + + self._dDispatch[self.ksActionViewLog] = self._actionViewLog; + self._dDispatch[self.ksActionGetFile] = self._actionGetFile; + + self._dDispatch[self.ksActionReportSummary] = self._actionReportSummary; + self._dDispatch[self.ksActionReportRate] = self._actionReportRate; + self._dDispatch[self.ksActionReportTestCaseFailures] = self._actionReportTestCaseFailures; + self._dDispatch[self.ksActionReportFailureReasons] = self._actionReportFailureReasons; + self._dDispatch[self.ksActionGraphWiz] = self._actionGraphWiz; + + self._dDispatch[self.ksActionVcsHistoryTooltip] = self._actionVcsHistoryTooltip; + + # Legacy. + self._dDispatch['TestResultDetails'] = self._dDispatch[self.ksActionTestSetDetails]; + + + # + # Popupate the menus. + # + + # Additional URL parameters keeping for time navigation. + sExtraTimeNav = '' + dCurParams = oSrvGlue.getParameters() + if dCurParams is not None: + for sExtraParam in [ self.ksParamItemsPerPage, self.ksParamEffectiveDate, self.ksParamEffectivePeriod, ]: + if sExtraParam in dCurParams: + sExtraTimeNav += '&%s' % (webutils.encodeUrlParams({sExtraParam: dCurParams[sExtraParam]}),) + + # Additional URL parameters for reports + sExtraReports = ''; + if dCurParams is not None: + for sExtraParam in [ self.ksParamReportPeriods, self.ksParamReportPeriodInHours, self.ksParamEffectiveDate, ]: + if sExtraParam in dCurParams: + sExtraReports += '&%s' % (webutils.encodeUrlParams({sExtraParam: dCurParams[sExtraParam]}),) + + # Shorthand to keep within margins. + sActUrlBase = self._sActionUrlBase; + sOnlyFailures = '&%s%s' % ( webutils.encodeUrlParams({self.ksParamOnlyFailures: True}), sExtraTimeNav, ); + sSheriff = '&%s%s' % ( webutils.encodeUrlParams({self.ksParamOnlyNeedingReason: True}), sExtraTimeNav, ); + + self._aaoMenus = \ + [ + [ + 'Sheriff', sActUrlBase + self.ksActionResultsUnGrouped + sSheriff, + [ + [ 'Grouped by', None ], + [ 'Ungrouped', sActUrlBase + self.ksActionResultsUnGrouped + sSheriff, False ], + [ 'Sched group', sActUrlBase + self.ksActionResultsGroupedBySchedGroup + sSheriff, False ], + [ 'Test group', sActUrlBase + self.ksActionResultsGroupedByTestGroup + sSheriff, False ], + [ 'Test case', sActUrlBase + self.ksActionResultsGroupedByTestCase + sSheriff, False ], + [ 'Testbox', sActUrlBase + self.ksActionResultsGroupedByTestBox + sSheriff, False ], + [ 'OS', sActUrlBase + self.ksActionResultsGroupedByOS + sSheriff, False ], + [ 'Architecture', sActUrlBase + self.ksActionResultsGroupedByArch + sSheriff, False ], + [ 'Revision', sActUrlBase + self.ksActionResultsGroupedByBuildRev + sSheriff, False ], + [ 'Build category', sActUrlBase + self.ksActionResultsGroupedByBuildCat + sSheriff, False ], + ] + ], + [ + 'Reports', sActUrlBase + self.ksActionReportSummary, + [ + [ 'Summary', sActUrlBase + self.ksActionReportSummary + sExtraReports, False ], + [ 'Success rate', sActUrlBase + self.ksActionReportRate + sExtraReports, False ], + [ 'Test case failures', sActUrlBase + self.ksActionReportTestCaseFailures + sExtraReports, False ], + [ 'Testbox failures', sActUrlBase + self.ksActionReportTestBoxFailures + sExtraReports, False ], + [ 'Failure reasons', sActUrlBase + self.ksActionReportFailureReasons + sExtraReports, False ], + ] + ], + [ + 'Test Results', sActUrlBase + self.ksActionResultsUnGrouped + sExtraTimeNav, + [ + [ 'Grouped by', None ], + [ 'Ungrouped', sActUrlBase + self.ksActionResultsUnGrouped + sExtraTimeNav, False ], + [ 'Sched group', sActUrlBase + self.ksActionResultsGroupedBySchedGroup + sExtraTimeNav, False ], + [ 'Test group', sActUrlBase + self.ksActionResultsGroupedByTestGroup + sExtraTimeNav, False ], + [ 'Test case', sActUrlBase + self.ksActionResultsGroupedByTestCase + sExtraTimeNav, False ], + [ 'Testbox', sActUrlBase + self.ksActionResultsGroupedByTestBox + sExtraTimeNav, False ], + [ 'OS', sActUrlBase + self.ksActionResultsGroupedByOS + sExtraTimeNav, False ], + [ 'Architecture', sActUrlBase + self.ksActionResultsGroupedByArch + sExtraTimeNav, False ], + [ 'Revision', sActUrlBase + self.ksActionResultsGroupedByBuildRev + sExtraTimeNav, False ], + [ 'Build category', sActUrlBase + self.ksActionResultsGroupedByBuildCat + sExtraTimeNav, False ], + ] + ], + [ + 'Test Failures', sActUrlBase + self.ksActionResultsUnGrouped + sOnlyFailures, + [ + [ 'Grouped by', None ], + [ 'Ungrouped', sActUrlBase + self.ksActionResultsUnGrouped + sOnlyFailures, False ], + [ 'Sched group', sActUrlBase + self.ksActionResultsGroupedBySchedGroup + sOnlyFailures, False ], + [ 'Test group', sActUrlBase + self.ksActionResultsGroupedByTestGroup + sOnlyFailures, False ], + [ 'Test case', sActUrlBase + self.ksActionResultsGroupedByTestCase + sOnlyFailures, False ], + [ 'Testbox', sActUrlBase + self.ksActionResultsGroupedByTestBox + sOnlyFailures, False ], + [ 'OS', sActUrlBase + self.ksActionResultsGroupedByOS + sOnlyFailures, False ], + [ 'Architecture', sActUrlBase + self.ksActionResultsGroupedByArch + sOnlyFailures, False ], + [ 'Revision', sActUrlBase + self.ksActionResultsGroupedByBuildRev + sOnlyFailures, False ], + [ 'Build category', sActUrlBase + self.ksActionResultsGroupedByBuildCat + sOnlyFailures, False ], + ] + ], + [ + '> Admin', 'admin.py?' + webutils.encodeUrlParams(self._dDbgParams), [] + ], + ]; + + + # + # Overriding parent methods. + # + + def _generatePage(self): + """Override parent handler in order to change page title.""" + if self._sPageTitle is not None: + self._sPageTitle = 'Test Results - ' + self._sPageTitle + + return WuiDispatcherBase._generatePage(self) + + def _actionDefault(self): + """Show the default admin page.""" + from testmanager.webui.wuitestresult import WuiGroupedResultList; + from testmanager.core.testresults import TestResultLogic, TestResultFilter; + self._sAction = self.ksActionResultsUnGrouped + return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeNone, + TestResultLogic, TestResultFilter, WuiGroupedResultList); + + def _isMenuMatch(self, sMenuUrl, sActionParam): + if super(WuiMain, self)._isMenuMatch(sMenuUrl, sActionParam): + fOnlyNeedingReason = self.getBoolParam(self.ksParamOnlyNeedingReason, fDefault = False); + if fOnlyNeedingReason: + return (sMenuUrl.find(self.ksParamOnlyNeedingReason) > 0); + fOnlyFailures = self.getBoolParam(self.ksParamOnlyFailures, fDefault = False); + return (sMenuUrl.find(self.ksParamOnlyFailures) > 0) == fOnlyFailures \ + and sMenuUrl.find(self.ksParamOnlyNeedingReason) < 0; + return False; + + + # + # Navigation bar stuff + # + + def _generateSortBySelector(self, dParams, sPreamble, sPostamble): + """ + Generate HTML code for the sort by selector. + """ + from testmanager.core.testresults import TestResultLogic; + + if self.ksParamTestResultsSortBy in dParams: + enmResultSortBy = dParams[self.ksParamTestResultsSortBy]; + del dParams[self.ksParamTestResultsSortBy]; + else: + enmResultSortBy = TestResultLogic.ksResultsSortByRunningAndStart; + + sHtmlSortBy = '<form name="TimeForm" method="GET"> Sort by\n'; + sHtmlSortBy += sPreamble; + sHtmlSortBy += '\n <select name="%s" onchange="window.location=' % (self.ksParamTestResultsSortBy,); + sHtmlSortBy += '\'?%s&%s=\' + ' % (webutils.encodeUrlParams(dParams), self.ksParamTestResultsSortBy) + sHtmlSortBy += 'this.options[this.selectedIndex].value;" title="Sorting by">\n' + + fSelected = False; + for enmCode, sTitle in TestResultLogic.kaasResultsSortByTitles: + if enmCode == enmResultSortBy: + fSelected = True; + sHtmlSortBy += ' <option value="%s"%s>%s</option>\n' \ + % (enmCode, ' selected="selected"' if enmCode == enmResultSortBy else '', sTitle,); + assert fSelected; + sHtmlSortBy += ' </select>\n'; + sHtmlSortBy += sPostamble; + sHtmlSortBy += '\n</form>\n' + return sHtmlSortBy; + + def _generateStatusSelector(self, dParams, fOnlyFailures): + """ + Generate HTML code for the status code selector. Currently very simple. + """ + dParams[self.ksParamOnlyFailures] = not fOnlyFailures; + return WuiTmLink('Show all results' if fOnlyFailures else 'Only show failed tests', '', dParams, + fBracketed = False).toHtml(); + + def _generateTimeWalker(self, dParams, tsEffective, sCurPeriod): + """ + Generates HTML code for walking back and forth in time. + """ + # Have to do some math here. :-/ + if tsEffective is None: + self._oDb.execute('SELECT CURRENT_TIMESTAMP - \'' + sCurPeriod + '\'::interval'); + tsNext = None; + tsPrev = self._oDb.fetchOne()[0]; + else: + self._oDb.execute('SELECT %s::TIMESTAMP - \'' + sCurPeriod + '\'::interval,\n' + ' %s::TIMESTAMP + \'' + sCurPeriod + '\'::interval', + (tsEffective, tsEffective,)); + tsPrev, tsNext = self._oDb.fetchOne(); + + # Forget about page No when changing a period + if WuiDispatcherBase.ksParamPageNo in dParams: + del dParams[WuiDispatcherBase.ksParamPageNo] + + # Format. + dParams[WuiDispatcherBase.ksParamEffectiveDate] = str(tsPrev); + sPrev = '<a href="?%s" title="One period earlier"><<</a> ' \ + % (webutils.encodeUrlParams(dParams),); + + if tsNext is not None: + dParams[WuiDispatcherBase.ksParamEffectiveDate] = str(tsNext); + sNext = ' <a href="?%s" title="One period later">>></a>' \ + % (webutils.encodeUrlParams(dParams),); + else: + sNext = ' >>'; + + from testmanager.webui.wuicontentbase import WuiListContentBase; ## @todo move to better place. + return WuiListContentBase.generateTimeNavigation('top', self.getParameters(), self.getEffectiveDateParam(), + sPrev, sNext, False); + + def _generateResultPeriodSelector(self, dParams, sCurPeriod): + """ + Generate HTML code for result period selector. + """ + + if self.ksParamEffectivePeriod in dParams: + del dParams[self.ksParamEffectivePeriod]; + + # Forget about page No when changing a period + if WuiDispatcherBase.ksParamPageNo in dParams: + del dParams[WuiDispatcherBase.ksParamPageNo] + + sHtmlPeriodSelector = '<form name="PeriodForm" method="GET">\n' + sHtmlPeriodSelector += ' Period is\n' + sHtmlPeriodSelector += ' <select name="%s" onchange="window.location=' % self.ksParamEffectivePeriod + sHtmlPeriodSelector += '\'?%s&%s=\' + ' % (webutils.encodeUrlParams(dParams), self.ksParamEffectivePeriod) + sHtmlPeriodSelector += 'this.options[this.selectedIndex].value;">\n' + + for sPeriodValue, sPeriodCaption, _ in self.kaoResultPeriods: + sHtmlPeriodSelector += ' <option value="%s"%s>%s</option>\n' \ + % (webutils.quoteUrl(sPeriodValue), + ' selected="selected"' if sPeriodValue == sCurPeriod else '', + sPeriodCaption) + + sHtmlPeriodSelector += ' </select>\n' \ + '</form>\n' + + return sHtmlPeriodSelector + + def _generateGroupContentSelector(self, aoGroupMembers, iCurrentMember, sAltAction): + """ + Generate HTML code for group content selector. + """ + + dParams = self.getParameters() + + if self.ksParamGroupMemberId in dParams: + del dParams[self.ksParamGroupMemberId] + + if sAltAction is not None: + if self.ksParamAction in dParams: + del dParams[self.ksParamAction]; + dParams[self.ksParamAction] = sAltAction; + + sHtmlSelector = '<form name="GroupContentForm" method="GET">\n' + sHtmlSelector += ' <select name="%s" onchange="window.location=' % self.ksParamGroupMemberId + sHtmlSelector += '\'?%s&%s=\' + ' % (webutils.encodeUrlParams(dParams), self.ksParamGroupMemberId) + sHtmlSelector += 'this.options[this.selectedIndex].value;">\n' + + sHtmlSelector += '<option value="-1">All</option>\n' + + for iGroupMemberId, sGroupMemberName in aoGroupMembers: + if iGroupMemberId is not None: + sHtmlSelector += ' <option value="%s"%s>%s</option>\n' \ + % (iGroupMemberId, + ' selected="selected"' if iGroupMemberId == iCurrentMember else '', + sGroupMemberName) + + sHtmlSelector += ' </select>\n' \ + '</form>\n' + + return sHtmlSelector + + def _generatePagesSelector(self, dParams, cItems, cItemsPerPage, iPage): + """ + Generate HTML code for pages (1, 2, 3 ... N) selector + """ + + if WuiDispatcherBase.ksParamPageNo in dParams: + del dParams[WuiDispatcherBase.ksParamPageNo] + + sHrefPtr = '<a href="?%s&%s=' % (webutils.encodeUrlParams(dParams).replace('%', '%%'), + WuiDispatcherBase.ksParamPageNo) + sHrefPtr += '%d">%s</a>' + + cNumOfPages = (cItems + cItemsPerPage - 1) // cItemsPerPage; + cPagesToDisplay = 10 + cPagesRangeStart = iPage - cPagesToDisplay // 2 \ + if not iPage - cPagesToDisplay / 2 < 0 else 0 + cPagesRangeEnd = cPagesRangeStart + cPagesToDisplay \ + if not cPagesRangeStart + cPagesToDisplay > cNumOfPages else cNumOfPages + # Adjust pages range + if cNumOfPages < cPagesToDisplay: + cPagesRangeStart = 0 + cPagesRangeEnd = cNumOfPages + + # 1 2 3 4... + sHtmlPager = ' \n'.join(sHrefPtr % (x, str(x + 1)) if x != iPage else str(x + 1) + for x in range(cPagesRangeStart, cPagesRangeEnd)) + if cPagesRangeStart > 0: + sHtmlPager = '%s ... \n' % (sHrefPtr % (0, str(1))) + sHtmlPager + if cPagesRangeEnd < cNumOfPages: + sHtmlPager += ' ... %s\n' % (sHrefPtr % (cNumOfPages, str(cNumOfPages + 1))) + + # Prev/Next (using << >> because « and » are too tiny). + if iPage > 0: + dParams[WuiDispatcherBase.ksParamPageNo] = iPage - 1 + sHtmlPager = ('<a title="Previous page" href="?%s"><<</a> \n' + % (webutils.encodeUrlParams(dParams), )) \ + + sHtmlPager; + else: + sHtmlPager = '<< \n' + sHtmlPager + + if iPage + 1 < cNumOfPages: + dParams[WuiDispatcherBase.ksParamPageNo] = iPage + 1 + sHtmlPager += '\n <a title="Next page" href="?%s">>></a>\n' % (webutils.encodeUrlParams(dParams),) + else: + sHtmlPager += '\n >>\n' + + return sHtmlPager + + def _generateItemPerPageSelector(self, dParams, cItemsPerPage): + """ + Generate HTML code for items per page selector + Note! Modifies dParams! + """ + + from testmanager.webui.wuicontentbase import WuiListContentBase; ## @todo move to better place. + return WuiListContentBase.generateItemPerPageSelector('top', dParams, cItemsPerPage); + + def _generateResultNavigation(self, cItems, cItemsPerPage, iPage, tsEffective, sCurPeriod, fOnlyFailures, + sHtmlMemberSelector): + """ Make custom time navigation bar for the results. """ + + # Generate the elements. + sHtmlStatusSelector = self._generateStatusSelector(self.getParameters(), fOnlyFailures); + sHtmlSortBySelector = self._generateSortBySelector(self.getParameters(), '', sHtmlStatusSelector); + sHtmlPeriodSelector = self._generateResultPeriodSelector(self.getParameters(), sCurPeriod) + sHtmlTimeWalker = self._generateTimeWalker(self.getParameters(), tsEffective, sCurPeriod); + + if cItems > 0: + sHtmlPager = self._generatePagesSelector(self.getParameters(), cItems, cItemsPerPage, iPage) + sHtmlItemsPerPageSelector = self._generateItemPerPageSelector(self.getParameters(), cItemsPerPage) + else: + sHtmlPager = '' + sHtmlItemsPerPageSelector = '' + + # Generate navigation bar + sHtml = '<table width=100%>\n' \ + '<tr>\n' \ + ' <td width=30%>' + sHtmlMemberSelector + '</td>\n' \ + ' <td width=40% align=center>' + sHtmlTimeWalker + '</td>' \ + ' <td width=30% align=right>\n' + sHtmlPeriodSelector + '</td>\n' \ + '</tr>\n' \ + '<tr>\n' \ + ' <td width=30%>' + sHtmlSortBySelector + '</td>\n' \ + ' <td width=40% align=center>\n' + sHtmlPager + '</td>\n' \ + ' <td width=30% align=right>\n' + sHtmlItemsPerPageSelector + '</td>\n'\ + '</tr>\n' \ + '</table>\n' + + return sHtml + + def _generateReportNavigation(self, tsEffective, cHoursPerPeriod, cPeriods): + """ Make time navigation bar for the reports. """ + + # The period length selector. + dParams = self.getParameters(); + if WuiMain.ksParamReportPeriodInHours in dParams: + del dParams[WuiMain.ksParamReportPeriodInHours]; + sHtmlPeriodLength = ''; + sHtmlPeriodLength += '<form name="ReportPeriodInHoursForm" method="GET">\n' \ + ' Period length <select name="%s" onchange="window.location=\'?%s&%s=\' + ' \ + 'this.options[this.selectedIndex].value;" title="Statistics period length in hours.">\n' \ + % (WuiMain.ksParamReportPeriodInHours, + webutils.encodeUrlParams(dParams), + WuiMain.ksParamReportPeriodInHours) + for cHours in [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 18, 24, 48, 72, 96, 120, 144, 168 ]: + sHtmlPeriodLength += ' <option value="%d"%s>%d hour%s</option>\n' \ + % (cHours, 'selected="selected"' if cHours == cHoursPerPeriod else '', cHours, + 's' if cHours > 1 else ''); + sHtmlPeriodLength += ' </select>\n' \ + '</form>\n' + + # The period count selector. + dParams = self.getParameters(); + if WuiMain.ksParamReportPeriods in dParams: + del dParams[WuiMain.ksParamReportPeriods]; + sHtmlCountOfPeriods = ''; + sHtmlCountOfPeriods += '<form name="ReportPeriodsForm" method="GET">\n' \ + ' Periods <select name="%s" onchange="window.location=\'?%s&%s=\' + ' \ + 'this.options[this.selectedIndex].value;" title="Statistics periods to report.">\n' \ + % (WuiMain.ksParamReportPeriods, + webutils.encodeUrlParams(dParams), + WuiMain.ksParamReportPeriods) + for cCurPeriods in range(2, 43): + sHtmlCountOfPeriods += ' <option value="%d"%s>%d</option>\n' \ + % (cCurPeriods, 'selected="selected"' if cCurPeriods == cPeriods else '', cCurPeriods); + sHtmlCountOfPeriods += ' </select>\n' \ + '</form>\n' + + # The time walker. + sHtmlTimeWalker = self._generateTimeWalker(self.getParameters(), tsEffective, '%d hours' % (cHoursPerPeriod)); + + # Combine them all. + sHtml = '<table width=100%>\n' \ + ' <tr>\n' \ + ' <td width=30% align="center">\n' + sHtmlPeriodLength + '</td>\n' \ + ' <td width=40% align="center">\n' + sHtmlTimeWalker + '</td>' \ + ' <td width=30% align="center">\n' + sHtmlCountOfPeriods + '</td>\n' \ + ' </tr>\n' \ + '</table>\n'; + return sHtml; + + + # + # The rest of stuff + # + + def _actionGroupedResultsListing( #pylint: disable=too-many-locals + self, + enmResultsGroupingType, + oResultsLogicType, + oResultFilterType, + oResultsListContentType): + """ + Override generic listing action. + + oResultsLogicType implements getEntriesCount, fetchResultsForListing and more. + oResultFilterType is a child of ModelFilterBase. + oResultsListContentType is a child of WuiListContentBase. + """ + from testmanager.core.testresults import TestResultLogic; + + cItemsPerPage = self.getIntParam(self.ksParamItemsPerPage, iMin = 2, iMax = 9999, iDefault = 128); + iPage = self.getIntParam(self.ksParamPageNo, iMin = 0, iMax = 999999, iDefault = 0); + tsEffective = self.getEffectiveDateParam(); + iGroupMemberId = self.getIntParam(self.ksParamGroupMemberId, iMin = -1, iMax = 999999, iDefault = -1); + fOnlyFailures = self.getBoolParam(self.ksParamOnlyFailures, fDefault = False); + fOnlyNeedingReason = self.getBoolParam(self.ksParamOnlyNeedingReason, fDefault = False); + enmResultSortBy = self.getStringParam(self.ksParamTestResultsSortBy, + asValidValues = TestResultLogic.kasResultsSortBy, + sDefault = TestResultLogic.ksResultsSortByRunningAndStart); + oFilter = oResultFilterType().initFromParams(self); + + # Get testing results period and validate it + asValidValues = [x for (x, _, _) in self.kaoResultPeriods] + sCurPeriod = self.getStringParam(self.ksParamEffectivePeriod, asValidValues = asValidValues, + sDefault = self.ksResultPeriodDefault) + assert sCurPeriod != ''; # Impossible! + + self._checkForUnknownParameters() + + # + # Fetch the group members. + # + # If no grouping is selected, we'll fill the grouping combo with + # testboxes just to avoid having completely useless combo box. + # + oTrLogic = TestResultLogic(self._oDb); + sAltSelectorAction = None; + if enmResultsGroupingType in (TestResultLogic.ksResultsGroupingTypeNone, TestResultLogic.ksResultsGroupingTypeTestBox,): + aoTmp = oTrLogic.getTestBoxes(tsNow = tsEffective, sPeriod = sCurPeriod) + aoGroupMembers = sorted(list({(x.idTestBox, '%s (%s)' % (x.sName, str(x.ip))) for x in aoTmp }), + reverse = False, key = lambda asData: asData[1]) + + if enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeTestBox: + self._sPageTitle = 'Grouped by Test Box'; + else: + self._sPageTitle = 'Ungrouped results'; + sAltSelectorAction = self.ksActionResultsGroupedByTestBox; + aoGroupMembers.insert(0, [None, None]); # The "All" member. + + elif enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeTestGroup: + aoTmp = oTrLogic.getTestGroups(tsNow = tsEffective, sPeriod = sCurPeriod); + aoGroupMembers = sorted(list({ (x.idTestGroup, x.sName ) for x in aoTmp }), + reverse = False, key = lambda asData: asData[1]) + self._sPageTitle = 'Grouped by Test Group' + + elif enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeBuildRev: + aoTmp = oTrLogic.getBuilds(tsNow = tsEffective, sPeriod = sCurPeriod) + aoGroupMembers = sorted(list({ (x.iRevision, '%s.%d' % (x.oCat.sBranch, x.iRevision)) for x in aoTmp }), + reverse = True, key = lambda asData: asData[0]) + self._sPageTitle = 'Grouped by Build' + + elif enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeBuildCat: + aoTmp = oTrLogic.getBuildCategories(tsNow = tsEffective, sPeriod = sCurPeriod) + aoGroupMembers = sorted(list({ (x.idBuildCategory, + '%s / %s / %s / %s' % ( x.sProduct, x.sBranch, ', '.join(x.asOsArches), x.sType) ) + for x in aoTmp }), + reverse = True, key = lambda asData: asData[1]); + self._sPageTitle = 'Grouped by Build Category' + + elif enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeTestCase: + aoTmp = oTrLogic.getTestCases(tsNow = tsEffective, sPeriod = sCurPeriod) + aoGroupMembers = sorted(list({ (x.idTestCase, '%s' % x.sName) for x in aoTmp }), + reverse = False, key = lambda asData: asData[1]) + self._sPageTitle = 'Grouped by Test Case' + + elif enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeOS: + aoTmp = oTrLogic.getOSes(tsNow = tsEffective, sPeriod = sCurPeriod) + aoGroupMembers = sorted(list(set(aoTmp)), reverse = False, key = lambda asData: asData[1]); + self._sPageTitle = 'Grouped by OS' + + elif enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeArch: + aoTmp = oTrLogic.getArchitectures(tsNow = tsEffective, sPeriod = sCurPeriod) + aoGroupMembers = sorted(list(set(aoTmp)), reverse = False, key = lambda asData: asData[1]); + self._sPageTitle = 'Grouped by Architecture' + + elif enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeSchedGroup: + aoTmp = oTrLogic.getSchedGroups(tsNow = tsEffective, sPeriod = sCurPeriod) + aoGroupMembers = sorted(list({ (x.idSchedGroup, '%s' % x.sName) for x in aoTmp }), + reverse = False, key = lambda asData: asData[1]) + self._sPageTitle = 'Grouped by Scheduling Group' + + else: + raise TMExceptionBase('Unknown grouping type') + + _sPageBody = '' + oContent = None + cEntriesMax = 0 + _dParams = self.getParameters() + oResultLogic = oResultsLogicType(self._oDb); + for idMember, sMemberName in aoGroupMembers: + # + # Count and fetch entries to be displayed. + # + + # Skip group members that were not specified. + if idMember != iGroupMemberId \ + and ( (idMember is not None and enmResultsGroupingType == TestResultLogic.ksResultsGroupingTypeNone) + or (iGroupMemberId > 0 and enmResultsGroupingType != TestResultLogic.ksResultsGroupingTypeNone) ): + continue + + cEntries = oResultLogic.getEntriesCount(tsNow = tsEffective, + sInterval = sCurPeriod, + oFilter = oFilter, + enmResultsGroupingType = enmResultsGroupingType, + iResultsGroupingValue = idMember, + fOnlyFailures = fOnlyFailures, + fOnlyNeedingReason = fOnlyNeedingReason); + if cEntries == 0: # Do not display empty groups + continue + aoEntries = oResultLogic.fetchResultsForListing(iPage * cItemsPerPage, + cItemsPerPage, + tsNow = tsEffective, + sInterval = sCurPeriod, + oFilter = oFilter, + enmResultSortBy = enmResultSortBy, + enmResultsGroupingType = enmResultsGroupingType, + iResultsGroupingValue = idMember, + fOnlyFailures = fOnlyFailures, + fOnlyNeedingReason = fOnlyNeedingReason); + cEntriesMax = max(cEntriesMax, cEntries) + + # + # Format them. + # + oContent = oResultsListContentType(aoEntries, + cEntries, + iPage, + cItemsPerPage, + tsEffective, + fnDPrint = self._oSrvGlue.dprint, + oDisp = self) + + (_, sHtml) = oContent.show(fShowNavigation = False) + if sMemberName is not None: + _sPageBody += '<table width=100%><tr><td>' + + _dParams[self.ksParamGroupMemberId] = idMember + sLink = WuiTmLink(sMemberName, '', _dParams, fBracketed = False).toHtml() + + _sPageBody += '<h2>%s (%d)</h2></td>' % (sLink, cEntries) + _sPageBody += '<td><br></td>' + _sPageBody += '</tr></table>' + _sPageBody += sHtml + _sPageBody += '<br>' + + # + # Complete the page by slapping navigation controls at the top and + # bottom of it. + # + sHtmlNavigation = self._generateResultNavigation(cEntriesMax, cItemsPerPage, iPage, + tsEffective, sCurPeriod, fOnlyFailures, + self._generateGroupContentSelector(aoGroupMembers, iGroupMemberId, + sAltSelectorAction)); + if cEntriesMax > 0: + self._sPageBody = sHtmlNavigation + _sPageBody + sHtmlNavigation; + else: + self._sPageBody = sHtmlNavigation + '<p align="center"><i>No data to display</i></p>\n'; + + # + # Now, generate a filter control panel for the side bar. + # + if hasattr(oFilter, 'kiBranches'): + oFilter.aCriteria[oFilter.kiBranches].fExpanded = True; + if hasattr(oFilter, 'kiTestStatus'): + oFilter.aCriteria[oFilter.kiTestStatus].fExpanded = True; + self._sPageFilter = self._generateResultFilter(oFilter, oResultLogic, tsEffective, sCurPeriod, + enmResultsGroupingType = enmResultsGroupingType, + aoGroupMembers = aoGroupMembers, + fOnlyFailures = fOnlyFailures, + fOnlyNeedingReason = fOnlyNeedingReason); + return True; + + def _generateResultFilter(self, oFilter, oResultLogic, tsNow, sPeriod, enmResultsGroupingType = None, aoGroupMembers = None, + fOnlyFailures = False, fOnlyNeedingReason = False): + """ + Generates the result filter for the left hand side. + """ + _ = enmResultsGroupingType; _ = aoGroupMembers; _ = fOnlyFailures; _ = fOnlyNeedingReason; + oResultLogic.fetchPossibleFilterOptions(oFilter, tsNow, sPeriod) + + # Add non-filter parameters as hidden fields so we can use 'GET' and have URLs to bookmark. + self._dSideMenuFormAttrs['method'] = 'GET'; + sHtml = u''; + for sKey, oValue in self._oSrvGlue.getParameters().items(): + if len(sKey) > 3: + if hasattr(oValue, 'startswith'): + sHtml += u'<input type="hidden" name="%s" value="%s"/>\n' \ + % (webutils.escapeAttr(sKey), webutils.escapeAttr(oValue),); + else: + for oSubValue in oValue: + sHtml += u'<input type="hidden" name="%s" value="%s"/>\n' \ + % (webutils.escapeAttr(sKey), webutils.escapeAttr(oSubValue),); + + # Generate the filter panel. + sHtml += u'<div id="side-filters">\n' \ + u' <p>Filters' \ + u' <span class="tm-side-filter-title-buttons"><input type="submit" value="Apply" />\n' \ + u' <a href="javascript:toggleSidebarSize();" class="tm-sidebar-size-link">»»</a></span></p>\n'; + sHtml += u' <dl>\n'; + for oCrit in oFilter.aCriteria: + if oCrit.aoPossible or oCrit.sType == oCrit.ksType_Ranges: + if ( oCrit.oSub is None \ + and ( oCrit.sState == oCrit.ksState_Selected \ + or (len(oCrit.aoPossible) <= 2 and oCrit.sType != oCrit.ksType_Ranges))) \ + or ( oCrit.oSub is not None \ + and ( oCrit.sState == oCrit.ksState_Selected \ + or oCrit.oSub.sState == oCrit.ksState_Selected \ + or (len(oCrit.aoPossible) <= 2 and len(oCrit.oSub.aoPossible) <= 2))) \ + or oCrit.fExpanded is True: + sClass = 'sf-collapsible'; + sChar = '▼'; + else: + sClass = 'sf-expandable'; + sChar = '▶'; + + sHtml += u' <dt class="%s"><a href="javascript:void(0)" onclick="toggleCollapsibleDtDd(this);">%s %s</a> ' \ + % (sClass, sChar, webutils.escapeElem(oCrit.sName),); + sHtml += u'<span class="tm-side-filter-dt-buttons">'; + if oCrit.sInvVarNm is not None: + sHtml += u'<input id="sf-union-%s" class="tm-side-filter-union-input" ' \ + u'name="%s" value="1" type="checkbox"%s />' \ + u'<label for="sf-union-%s" class="tm-side-filter-union-input"></label>' \ + % ( oCrit.sInvVarNm, oCrit.sInvVarNm, ' checked' if oCrit.fInverted else '', oCrit.sInvVarNm,); + sHtml += u' <input type="submit" value="Apply" />'; + sHtml += u'</span>'; + sHtml += u'</dt>\n' \ + u' <dd class="%s">\n' \ + u' <ul>\n' \ + % (sClass); + + if oCrit.sType == oCrit.ksType_Ranges: + assert not oCrit.oSub; + assert not oCrit.aoPossible; + asValues = []; + for tRange in oCrit.aoSelected: + if tRange[0] == tRange[1]: + asValues.append('%s' % (tRange[0],)); + else: + asValues.append('%s-%s' % (tRange[0] if tRange[0] is not None else 'inf', + tRange[1] if tRange[1] is not None else 'inf')); + sHtml += u' <li title="%s"><input type="text" name="%s" value="%s"/></li>\n' \ + % ( webutils.escapeAttr('comma separate list of numerical ranges'), oCrit.sVarNm, + ', '.join(asValues), ); + else: + for oDesc in oCrit.aoPossible: + fChecked = oDesc.oValue in oCrit.aoSelected; + sHtml += u' <li%s%s><label><input type="checkbox" name="%s" value="%s"%s%s/>%s%s</label>\n' \ + % ( ' class="side-filter-irrelevant"' if oDesc.fIrrelevant else '', + (' title="%s"' % (webutils.escapeAttr(oDesc.sHover,)) if oDesc.sHover is not None else ''), + oCrit.sVarNm, + oDesc.oValue, + ' checked' if fChecked else '', + ' onclick="toggleCollapsibleCheckbox(this);"' if oDesc.aoSubs is not None else '', + webutils.escapeElem(oDesc.sDesc), + '<span class="side-filter-count"> [%u]</span>' % (oDesc.cTimes) if oDesc.cTimes is not None + else '', ); + if oDesc.aoSubs is not None: + sHtml += u' <ul class="sf-checkbox-%s">\n' % ('collapsible' if fChecked else 'expandable', ); + for oSubDesc in oDesc.aoSubs: + fSubChecked = oSubDesc.oValue in oCrit.oSub.aoSelected; + sHtml += u' <li%s%s><label><input type="checkbox" name="%s" value="%s"%s/>%s%s</label>\n' \ + % ( ' class="side-filter-irrelevant"' if oSubDesc.fIrrelevant else '', + ' title="%s"' % ( webutils.escapeAttr(oSubDesc.sHover,) if oSubDesc.sHover is not None + else ''), + oCrit.oSub.sVarNm, oSubDesc.oValue, ' checked' if fSubChecked else '', + webutils.escapeElem(oSubDesc.sDesc), + '<span class="side-filter-count"> [%u]</span>' % (oSubDesc.cTimes) + if oSubDesc.cTimes is not None else '', ); + + sHtml += u' </ul>\n'; + sHtml += u' </li>'; + + sHtml += u' </ul>\n' \ + u' </dd>\n'; + + sHtml += u' </dl>\n'; + sHtml += u' <input type="submit" value="Apply"/>\n'; + sHtml += u' <input type="reset" value="Reset"/>\n'; + sHtml += u' <button type="button" onclick="clearForm(\'side-menu-form\');">Clear</button>\n'; + sHtml += u'</div>\n'; + return sHtml; + + def _actionResultsUnGrouped(self): + """ Action wrapper. """ + from testmanager.webui.wuitestresult import WuiGroupedResultList; + from testmanager.core.testresults import TestResultLogic, TestResultFilter; + #return self._actionResultsListing(TestResultLogic, WuiGroupedResultList)? + return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeNone, + TestResultLogic, TestResultFilter, WuiGroupedResultList); + + def _actionResultsGroupedByTestGroup(self): + """ Action wrapper. """ + from testmanager.webui.wuitestresult import WuiGroupedResultList; + from testmanager.core.testresults import TestResultLogic, TestResultFilter; + return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeTestGroup, + TestResultLogic, TestResultFilter, WuiGroupedResultList); + + def _actionResultsGroupedByBuildRev(self): + """ Action wrapper. """ + from testmanager.webui.wuitestresult import WuiGroupedResultList; + from testmanager.core.testresults import TestResultLogic, TestResultFilter; + return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeBuildRev, + TestResultLogic, TestResultFilter, WuiGroupedResultList); + + def _actionResultsGroupedByBuildCat(self): + """ Action wrapper. """ + from testmanager.webui.wuitestresult import WuiGroupedResultList; + from testmanager.core.testresults import TestResultLogic, TestResultFilter; + return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeBuildCat, + TestResultLogic, TestResultFilter, WuiGroupedResultList); + + def _actionResultsGroupedByTestBox(self): + """ Action wrapper. """ + from testmanager.webui.wuitestresult import WuiGroupedResultList; + from testmanager.core.testresults import TestResultLogic, TestResultFilter; + return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeTestBox, + TestResultLogic, TestResultFilter, WuiGroupedResultList); + + def _actionResultsGroupedByTestCase(self): + """ Action wrapper. """ + from testmanager.webui.wuitestresult import WuiGroupedResultList; + from testmanager.core.testresults import TestResultLogic, TestResultFilter; + return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeTestCase, + TestResultLogic, TestResultFilter, WuiGroupedResultList); + + def _actionResultsGroupedByOS(self): + """ Action wrapper. """ + from testmanager.webui.wuitestresult import WuiGroupedResultList; + from testmanager.core.testresults import TestResultLogic, TestResultFilter; + return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeOS, + TestResultLogic, TestResultFilter, WuiGroupedResultList); + + def _actionResultsGroupedByArch(self): + """ Action wrapper. """ + from testmanager.webui.wuitestresult import WuiGroupedResultList; + from testmanager.core.testresults import TestResultLogic, TestResultFilter; + return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeArch, + TestResultLogic, TestResultFilter, WuiGroupedResultList); + + def _actionResultsGroupedBySchedGroup(self): + """ Action wrapper. """ + from testmanager.webui.wuitestresult import WuiGroupedResultList; + from testmanager.core.testresults import TestResultLogic, TestResultFilter; + return self._actionGroupedResultsListing(TestResultLogic.ksResultsGroupingTypeSchedGroup, + TestResultLogic, TestResultFilter, WuiGroupedResultList); + + + def _actionTestSetDetailsCommon(self, idTestSet): + """Show test case execution result details.""" + from testmanager.core.build import BuildDataEx; + from testmanager.core.testbox import TestBoxData; + from testmanager.core.testcase import TestCaseDataEx; + from testmanager.core.testcaseargs import TestCaseArgsDataEx; + from testmanager.core.testgroup import TestGroupData; + from testmanager.core.testresults import TestResultLogic; + from testmanager.core.testset import TestSetData; + from testmanager.webui.wuitestresult import WuiTestResult; + + self._sTemplate = 'template-details.html'; + self._checkForUnknownParameters() + + oTestSetData = TestSetData().initFromDbWithId(self._oDb, idTestSet); + try: + (oTestResultTree, _) = TestResultLogic(self._oDb).fetchResultTree(idTestSet); + except TMTooManyRows: + (oTestResultTree, _) = TestResultLogic(self._oDb).fetchResultTree(idTestSet, 2); + oBuildDataEx = BuildDataEx().initFromDbWithId(self._oDb, oTestSetData.idBuild, oTestSetData.tsCreated); + try: oBuildValidationKitDataEx = BuildDataEx().initFromDbWithId(self._oDb, oTestSetData.idBuildTestSuite, + oTestSetData.tsCreated); + except: oBuildValidationKitDataEx = None; + oTestBoxData = TestBoxData().initFromDbWithGenId(self._oDb, oTestSetData.idGenTestBox); + oTestGroupData = TestGroupData().initFromDbWithId(self._oDb, ## @todo This bogus time wise. Bad DB design? + oTestSetData.idTestGroup, oTestSetData.tsCreated); + oTestCaseDataEx = TestCaseDataEx().initFromDbWithGenId(self._oDb, oTestSetData.idGenTestCase, + oTestSetData.tsConfig); + oTestCaseArgsDataEx = TestCaseArgsDataEx().initFromDbWithGenIdEx(self._oDb, oTestSetData.idGenTestCaseArgs, + oTestSetData.tsConfig); + + oContent = WuiTestResult(oDisp = self, fnDPrint = self._oSrvGlue.dprint); + (self._sPageTitle, self._sPageBody) = oContent.showTestCaseResultDetails(oTestResultTree, + oTestSetData, + oBuildDataEx, + oBuildValidationKitDataEx, + oTestBoxData, + oTestGroupData, + oTestCaseDataEx, + oTestCaseArgsDataEx); + return True + + def _actionTestSetDetails(self): + """Show test case execution result details.""" + from testmanager.core.testset import TestSetData; + + idTestSet = self.getIntParam(TestSetData.ksParam_idTestSet); + return self._actionTestSetDetailsCommon(idTestSet); + + def _actionTestSetDetailsFromResult(self): + """Show test case execution result details.""" + from testmanager.core.testresults import TestResultData; + from testmanager.core.testset import TestSetData; + idTestResult = self.getIntParam(TestSetData.ksParam_idTestResult); + oTestResultData = TestResultData().initFromDbWithId(self._oDb, idTestResult); + return self._actionTestSetDetailsCommon(oTestResultData.idTestSet); + + + def _actionTestResultFailureAdd(self): + """ Pro forma. """ + from testmanager.core.testresultfailures import TestResultFailureData; + from testmanager.webui.wuitestresultfailure import WuiTestResultFailure; + return self._actionGenericFormAdd(TestResultFailureData, WuiTestResultFailure); + + def _actionTestResultFailureAddPost(self): + """Add test result failure result""" + from testmanager.core.testresultfailures import TestResultFailureLogic, TestResultFailureData; + from testmanager.webui.wuitestresultfailure import WuiTestResultFailure; + if self.ksParamRedirectTo not in self._dParams: + raise WuiException('Missing parameter ' + self.ksParamRedirectTo); + + return self._actionGenericFormAddPost(TestResultFailureData, TestResultFailureLogic, + WuiTestResultFailure, self.ksActionResultsUnGrouped); + + def _actionTestResultFailureDoRemove(self): + """ Action wrapper. """ + from testmanager.core.testresultfailures import TestResultFailureData, TestResultFailureLogic; + return self._actionGenericDoRemove(TestResultFailureLogic, TestResultFailureData.ksParam_idTestResult, + self.ksActionResultsUnGrouped); + + def _actionTestResultFailureDetails(self): + """ Pro forma. """ + from testmanager.core.testresultfailures import TestResultFailureLogic, TestResultFailureData; + from testmanager.webui.wuitestresultfailure import WuiTestResultFailure; + return self._actionGenericFormDetails(TestResultFailureData, TestResultFailureLogic, + WuiTestResultFailure, 'idTestResult'); + + def _actionTestResultFailureEdit(self): + """ Pro forma. """ + from testmanager.core.testresultfailures import TestResultFailureData; + from testmanager.webui.wuitestresultfailure import WuiTestResultFailure; + return self._actionGenericFormEdit(TestResultFailureData, WuiTestResultFailure, + TestResultFailureData.ksParam_idTestResult); + + def _actionTestResultFailureEditPost(self): + """Edit test result failure result""" + from testmanager.core.testresultfailures import TestResultFailureLogic, TestResultFailureData; + from testmanager.webui.wuitestresultfailure import WuiTestResultFailure; + return self._actionGenericFormEditPost(TestResultFailureData, TestResultFailureLogic, + WuiTestResultFailure, self.ksActionResultsUnGrouped); + + def _actionViewLog(self): + """ + Log viewer action. + """ + from testmanager.core.testresults import TestResultLogic, TestResultFileDataEx; + from testmanager.core.testset import TestSetData, TestSetLogic; + from testmanager.webui.wuilogviewer import WuiLogViewer; + + self._sTemplate = 'template-details.html'; ## @todo create new template (background color, etc) + idTestSet = self.getIntParam(self.ksParamLogSetId, iMin = 1); + idLogFile = self.getIntParam(self.ksParamLogFileId, iMin = 0, iDefault = 0); + cbChunk = self.getIntParam(self.ksParamLogChunkSize, iMin = 256, iMax = 16777216, iDefault = 1024*1024); + iChunk = self.getIntParam(self.ksParamLogChunkNo, iMin = 0, + iMax = config.g_kcMbMaxMainLog * 1048576 / cbChunk, iDefault = 0); + self._checkForUnknownParameters(); + + oTestSet = TestSetData().initFromDbWithId(self._oDb, idTestSet); + if idLogFile == 0: + oTestFile = TestResultFileDataEx().initFakeMainLog(oTestSet); + aoTimestamps = TestResultLogic(self._oDb).fetchTimestampsForLogViewer(idTestSet); + else: + oTestFile = TestSetLogic(self._oDb).getFile(idTestSet, idLogFile); + aoTimestamps = []; + if oTestFile.sMime not in [ 'text/plain',]: + raise WuiException('The log view does not display files of type: %s' % (oTestFile.sMime,)); + + oContent = WuiLogViewer(oTestSet, oTestFile, cbChunk, iChunk, aoTimestamps, + oDisp = self, fnDPrint = self._oSrvGlue.dprint); + (self._sPageTitle, self._sPageBody) = oContent.show(); + return True; + + def _actionGetFile(self): + """ + Get file action. + """ + from testmanager.core.testset import TestSetData, TestSetLogic; + from testmanager.core.testresults import TestResultFileDataEx; + + idTestSet = self.getIntParam(self.ksParamGetFileSetId, iMin = 1); + idFile = self.getIntParam(self.ksParamGetFileId, iMin = 0, iDefault = 0); + fDownloadIt = self.getBoolParam(self.ksParamGetFileDownloadIt, fDefault = True); + self._checkForUnknownParameters(); + + # + # Get the file info and open it. + # + oTestSet = TestSetData().initFromDbWithId(self._oDb, idTestSet); + if idFile == 0: + oTestFile = TestResultFileDataEx().initFakeMainLog(oTestSet); + else: + oTestFile = TestSetLogic(self._oDb).getFile(idTestSet, idFile); + + (oFile, oSizeOrError, _) = oTestSet.openFile(oTestFile.sFile, 'rb'); + if oFile is None: + raise Exception(oSizeOrError); + + # + # Send the file. + # + self._oSrvGlue.setHeaderField('Content-Type', oTestFile.getMimeWithEncoding()); + if fDownloadIt: + self._oSrvGlue.setHeaderField('Content-Disposition', 'attachment; filename="TestSet-%d-%s"' + % (idTestSet, oTestFile.sFile,)); + while True: + abChunk = oFile.read(262144); + if not abChunk: + break; + self._oSrvGlue.writeRaw(abChunk); + return self.ksDispatchRcAllDone; + + def _actionGenericReport(self, oModelType, oFilterType, oReportType): + """ + Generic report action. + oReportType is a child of WuiReportContentBase. + oFilterType is a child of ModelFilterBase. + oModelType is a child of ReportModelBase. + """ + from testmanager.core.report import ReportModelBase; + + tsEffective = self.getEffectiveDateParam(); + cPeriods = self.getIntParam(self.ksParamReportPeriods, iMin = 2, iMax = 99, iDefault = 7); + cHoursPerPeriod = self.getIntParam(self.ksParamReportPeriodInHours, iMin = 1, iMax = 168, iDefault = 24); + sSubject = self.getStringParam(self.ksParamReportSubject, ReportModelBase.kasSubjects, + ReportModelBase.ksSubEverything); + if sSubject == ReportModelBase.ksSubEverything: + aidSubjects = self.getListOfIntParams(self.ksParamReportSubjectIds, aiDefaults = []); + else: + aidSubjects = self.getListOfIntParams(self.ksParamReportSubjectIds, iMin = 1); + if aidSubjects is None: + raise WuiException('Missing parameter %s' % (self.ksParamReportSubjectIds,)); + + aiSortColumnsDup = self.getListOfIntParams(self.ksParamSortColumns, + iMin = -getattr(oReportType, 'kcMaxSortColumns', cPeriods) + 1, + iMax = getattr(oReportType, 'kcMaxSortColumns', cPeriods), aiDefaults = []); + aiSortColumns = []; + for iSortColumn in aiSortColumnsDup: + if iSortColumn not in aiSortColumns: + aiSortColumns.append(iSortColumn); + + oFilter = oFilterType().initFromParams(self); + self._checkForUnknownParameters(); + + dParams = \ + { + self.ksParamEffectiveDate: tsEffective, + self.ksParamReportPeriods: cPeriods, + self.ksParamReportPeriodInHours: cHoursPerPeriod, + self.ksParamReportSubject: sSubject, + self.ksParamReportSubjectIds: aidSubjects, + }; + ## @todo oFilter. + + oModel = oModelType(self._oDb, tsEffective, cPeriods, cHoursPerPeriod, sSubject, aidSubjects, oFilter); + oContent = oReportType(oModel, dParams, fSubReport = False, aiSortColumns = aiSortColumns, + fnDPrint = self._oSrvGlue.dprint, oDisp = self); + (self._sPageTitle, self._sPageBody) = oContent.show(); + sNavi = self._generateReportNavigation(tsEffective, cHoursPerPeriod, cPeriods); + self._sPageBody = sNavi + self._sPageBody; + + if hasattr(oFilter, 'kiBranches'): + oFilter.aCriteria[oFilter.kiBranches].fExpanded = True; + self._sPageFilter = self._generateResultFilter(oFilter, oModel, tsEffective, '%s hours' % (cHoursPerPeriod * cPeriods,)); + return True; + + def _actionReportSummary(self): + """ Action wrapper. """ + from testmanager.core.report import ReportLazyModel, ReportFilter; + from testmanager.webui.wuireport import WuiReportSummary; + return self._actionGenericReport(ReportLazyModel, ReportFilter, WuiReportSummary); + + def _actionReportRate(self): + """ Action wrapper. """ + from testmanager.core.report import ReportLazyModel, ReportFilter; + from testmanager.webui.wuireport import WuiReportSuccessRate; + return self._actionGenericReport(ReportLazyModel, ReportFilter, WuiReportSuccessRate); + + def _actionReportTestCaseFailures(self): + """ Action wrapper. """ + from testmanager.core.report import ReportLazyModel, ReportFilter; + from testmanager.webui.wuireport import WuiReportTestCaseFailures; + return self._actionGenericReport(ReportLazyModel, ReportFilter, WuiReportTestCaseFailures); + + def _actionReportFailureReasons(self): + """ Action wrapper. """ + from testmanager.core.report import ReportLazyModel, ReportFilter; + from testmanager.webui.wuireport import WuiReportFailureReasons; + return self._actionGenericReport(ReportLazyModel, ReportFilter, WuiReportFailureReasons); + + def _actionGraphWiz(self): + """ + Graph wizard action. + """ + from testmanager.core.report import ReportModelBase, ReportGraphModel; + from testmanager.webui.wuigraphwiz import WuiGraphWiz; + self._sTemplate = 'template-graphwiz.html'; + + tsEffective = self.getEffectiveDateParam(); + cPeriods = self.getIntParam(self.ksParamReportPeriods, iMin = 1, iMax = 1, iDefault = 1); # Not needed yet. + sTmp = self.getStringParam(self.ksParamReportPeriodInHours, sDefault = '3 weeks'); + (cHoursPerPeriod, sError) = utils.parseIntervalHours(sTmp); + if sError is not None: raise WuiException(sError); + asSubjectIds = self.getListOfStrParams(self.ksParamReportSubjectIds); + sSubject = self.getStringParam(self.ksParamReportSubject, [ReportModelBase.ksSubEverything], + ReportModelBase.ksSubEverything); # dummy + aidTestBoxes = self.getListOfIntParams(self.ksParamGraphWizTestBoxIds, iMin = 1, aiDefaults = []); + aidBuildCats = self.getListOfIntParams(self.ksParamGraphWizBuildCatIds, iMin = 1, aiDefaults = []); + aidTestCases = self.getListOfIntParams(self.ksParamGraphWizTestCaseIds, iMin = 1, aiDefaults = []); + fSepTestVars = self.getBoolParam(self.ksParamGraphWizSepTestVars, fDefault = False); + + enmGraphImpl = self.getStringParam(self.ksParamGraphWizImpl, asValidValues = self.kasGraphWizImplValid, + sDefault = self.ksGraphWizImpl_Default); + cx = self.getIntParam(self.ksParamGraphWizWidth, iMin = 128, iMax = 8192, iDefault = 1280); + cy = self.getIntParam(self.ksParamGraphWizHeight, iMin = 128, iMax = 8192, iDefault = int(cx * 5 / 16) ); + cDotsPerInch = self.getIntParam(self.ksParamGraphWizDpi, iMin = 64, iMax = 512, iDefault = 96); + cPtFont = self.getIntParam(self.ksParamGraphWizFontSize, iMin = 6, iMax = 32, iDefault = 8); + fErrorBarY = self.getBoolParam(self.ksParamGraphWizErrorBarY, fDefault = False); + cMaxErrorBarY = self.getIntParam(self.ksParamGraphWizMaxErrorBarY, iMin = 8, iMax = 9999999, iDefault = 18); + cMaxPerGraph = self.getIntParam(self.ksParamGraphWizMaxPerGraph, iMin = 1, iMax = 24, iDefault = 8); + fXkcdStyle = self.getBoolParam(self.ksParamGraphWizXkcdStyle, fDefault = False); + fTabular = self.getBoolParam(self.ksParamGraphWizTabular, fDefault = False); + idSrcTestSet = self.getIntParam(self.ksParamGraphWizSrcTestSetId, iDefault = None); + self._checkForUnknownParameters(); + + dParams = \ + { + self.ksParamEffectiveDate: tsEffective, + self.ksParamReportPeriods: cPeriods, + self.ksParamReportPeriodInHours: cHoursPerPeriod, + self.ksParamReportSubject: sSubject, + self.ksParamReportSubjectIds: asSubjectIds, + self.ksParamGraphWizTestBoxIds: aidTestBoxes, + self.ksParamGraphWizBuildCatIds: aidBuildCats, + self.ksParamGraphWizTestCaseIds: aidTestCases, + self.ksParamGraphWizSepTestVars: fSepTestVars, + + self.ksParamGraphWizImpl: enmGraphImpl, + self.ksParamGraphWizWidth: cx, + self.ksParamGraphWizHeight: cy, + self.ksParamGraphWizDpi: cDotsPerInch, + self.ksParamGraphWizFontSize: cPtFont, + self.ksParamGraphWizErrorBarY: fErrorBarY, + self.ksParamGraphWizMaxErrorBarY: cMaxErrorBarY, + self.ksParamGraphWizMaxPerGraph: cMaxPerGraph, + self.ksParamGraphWizXkcdStyle: fXkcdStyle, + self.ksParamGraphWizTabular: fTabular, + self.ksParamGraphWizSrcTestSetId: idSrcTestSet, + }; + + oModel = ReportGraphModel(self._oDb, tsEffective, cPeriods, cHoursPerPeriod, sSubject, asSubjectIds, + aidTestBoxes, aidBuildCats, aidTestCases, fSepTestVars); + oContent = WuiGraphWiz(oModel, dParams, fSubReport = False, fnDPrint = self._oSrvGlue.dprint, oDisp = self); + (self._sPageTitle, self._sPageBody) = oContent.show(); + return True; + + def _actionVcsHistoryTooltip(self): + """ + Version control system history. + """ + from testmanager.webui.wuivcshistory import WuiVcsHistoryTooltip; + from testmanager.core.vcsrevisions import VcsRevisionLogic; + + self._sTemplate = 'template-tooltip.html'; + iRevision = self.getIntParam(self.ksParamVcsHistoryRevision, iMin = 0, iMax = 999999999); + sRepository = self.getStringParam(self.ksParamVcsHistoryRepository); + cEntries = self.getIntParam(self.ksParamVcsHistoryEntries, iMin = 1, iMax = 1024, iDefault = 8); + self._checkForUnknownParameters(); + + aoEntries = VcsRevisionLogic(self._oDb).fetchTimeline(sRepository, iRevision, cEntries); + oContent = WuiVcsHistoryTooltip(aoEntries, sRepository, iRevision, cEntries, + fnDPrint = self._oSrvGlue.dprint, oDisp = self); + (self._sPageTitle, self._sPageBody) = oContent.show(); + return True; + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuireport.py b/src/VBox/ValidationKit/testmanager/webui/wuireport.py new file mode 100755 index 00000000..aa538551 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuireport.py @@ -0,0 +1,817 @@ +# -*- coding: utf-8 -*- +# $Id: wuireport.py $ + +""" +Test Manager WUI - Reports. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Validation Kit imports. +from common import webutils; +from testmanager.webui.wuicontentbase import WuiContentBase, WuiTmLink, WuiSvnLinkWithTooltip; +from testmanager.webui.wuihlpgraph import WuiHlpGraphDataTable, WuiHlpBarGraph; +from testmanager.webui.wuitestresult import WuiTestSetLink, WuiTestResultsForTestCaseLink, WuiTestResultsForTestBoxLink; +from testmanager.webui.wuiadmintestcase import WuiTestCaseDetailsLink; +from testmanager.webui.wuiadmintestbox import WuiTestBoxDetailsLinkShort; +from testmanager.core.report import ReportModelBase, ReportFilter; +from testmanager.core.testresults import TestResultFilter; + + +class WuiReportSummaryLink(WuiTmLink): + """ Generic report summary link. """ + + def __init__(self, sSubject, aIdSubjects, sName = WuiContentBase.ksShortReportLink, + tsNow = None, cPeriods = None, cHoursPerPeriod = None, fBracketed = False, dExtraParams = None): + from testmanager.webui.wuimain import WuiMain; + dParams = { + WuiMain.ksParamAction: WuiMain.ksActionReportSummary, + WuiMain.ksParamReportSubject: sSubject, + WuiMain.ksParamReportSubjectIds: aIdSubjects, + }; + if dExtraParams is not None: + dParams.update(dExtraParams); + if tsNow is not None: + dParams[WuiMain.ksParamEffectiveDate] = tsNow; + if cPeriods is not None: + dParams[WuiMain.ksParamReportPeriods] = cPeriods; + if cPeriods is not None: + dParams[WuiMain.ksParamReportPeriodInHours] = cHoursPerPeriod; + WuiTmLink.__init__(self, sName, WuiMain.ksScriptName, dParams, fBracketed = fBracketed); + + +class WuiReportBase(WuiContentBase): + """ + Base class for the reports. + """ + + def __init__(self, oModel, dParams, fSubReport = False, aiSortColumns = None, fnDPrint = None, oDisp = None): + WuiContentBase.__init__(self, fnDPrint = fnDPrint, oDisp = oDisp); + self._oModel = oModel; + self._dParams = dParams; + self._fSubReport = fSubReport; + self._sTitle = None; + self._aiSortColumns = aiSortColumns; + + # Additional URL parameters for reports: + from testmanager.webui.wuimain import WuiMain; + self._dExtraParams = ReportFilter().strainParameters({} if oDisp is None else oDisp.getParameters(), + (WuiMain.ksParamReportPeriods, + WuiMain.ksParamReportPeriodInHours, + WuiMain.ksParamEffectiveDate,)); + # Additional URL parameters for test results: + self._dExtraTestResultsParams = TestResultFilter().strainParameters(oDisp.getParameters(), + (WuiMain.ksParamEffectiveDate,)); + self._dExtraTestResultsParams[WuiMain.ksParamEffectivePeriod] = self.getPeriodForTestResults(); + + + def generateNavigator(self, sWhere): + """ + Generates the navigator (manipulate _dParams). + Returns HTML. + """ + assert sWhere in ('top', 'bottom',); + + return ''; + + def generateReportBody(self): + """ + This is overridden by the child class to generate the report. + Returns HTML. + """ + return '<h3>Must override generateReportBody!</h3>'; + + def show(self): + """ + Generate the report. + Returns (sTitle, HTML). + """ + + sTitle = self._sTitle if self._sTitle is not None else type(self).__name__; + sReport = self.generateReportBody(); + if not self._fSubReport: + sReport = self.generateNavigator('top') + sReport + self.generateNavigator('bottom'); + sTitle = self._oModel.sSubject + ' - ' + sTitle; ## @todo add subject to title in a proper way! + + sReport += '\n\n<!-- HEYYOU: sSubject=%s aidSubjects=%s -->\n\n' % (self._oModel.sSubject, self._oModel.aidSubjects); + return (sTitle, sReport); + + # + # Utility methods + # + + def getPeriodForTestResults(self): + """ + Takes the report period length and count and translates it into a + reasonable test result period (value). + """ + from testmanager.webui.wuimain import WuiMain; + cHours = self._oModel.cPeriods * self._oModel.cHoursPerPeriod; + if cHours > 7*24: + cHours = cHours // 2; + for sPeriodValue, _, cPeriodHours in WuiMain.kaoResultPeriods: + sPeriod = sPeriodValue; + if cPeriodHours >= cHours: + return sPeriod; + return sPeriod; + + @staticmethod + def fmtPct(cHits, cTotal): + """ + Formats a percent number. + Returns a string. + """ + uPct = cHits * 100 // cTotal; + if uPct >= 10 and (uPct > 103 or uPct <= 95): + return '%s%%' % (uPct,); + return '%.1f%%' % (cHits * 100.0 / cTotal,); + + @staticmethod + def fmtPctWithHits(cHits, cTotal): + """ + Formats a percent number with total in parentheses. + Returns a string. + """ + return '%s (%s)' % (WuiReportBase.fmtPct(cHits, cTotal), cHits); + + @staticmethod + def fmtPctWithHitsAndTotal(cHits, cTotal): + """ + Formats a percent number with total in parentheses. + Returns a string. + """ + return '%s (%s/%s)' % (WuiReportBase.fmtPct(cHits, cTotal), cHits, cTotal); + + + +class WuiReportSuccessRate(WuiReportBase): + """ + Generates a report displaying the success rate over time. + """ + + def generateReportBody(self): + self._sTitle = 'Success rate'; + fTailoredForGoogleCharts = True; + + # + # Get the data and check if we have anything in the 'skipped' category. + # + adPeriods = self._oModel.getSuccessRates(); + + cTotalSkipped = 0; + for dStatuses in adPeriods: + cTotalSkipped += dStatuses[ReportModelBase.ksTestStatus_Skipped]; + + # + # Output some general stats before the graphs. + # + cTotalNow = adPeriods[0][ReportModelBase.ksTestStatus_Success]; + cTotalNow += adPeriods[0][ReportModelBase.ksTestStatus_Skipped]; + cSuccessNow = cTotalNow; + cTotalNow += adPeriods[0][ReportModelBase.ksTestStatus_Failure]; + + sReport = '<p>Current success rate: '; + if cTotalNow > 0: + cSkippedNow = adPeriods[0][ReportModelBase.ksTestStatus_Skipped]; + if cSkippedNow > 0: + sReport += '%s (thereof %s skipped)</p>\n' \ + % (self.fmtPct(cSuccessNow, cTotalNow), self.fmtPct(cSkippedNow, cTotalNow),); + else: + sReport += '%s (none skipped)</p>\n' % (self.fmtPct(cSuccessNow, cTotalNow),); + else: + sReport += 'N/A</p>\n' + + # + # Create the data table. + # + if fTailoredForGoogleCharts: + if cTotalSkipped > 0: + oTable = WuiHlpGraphDataTable(None, [ 'Succeeded', 'Skipped', 'Failed' ]); + else: + oTable = WuiHlpGraphDataTable(None, [ 'Succeeded', 'Failed' ]); + else: + if cTotalSkipped > 0: + oTable = WuiHlpGraphDataTable('When', [ 'Succeeded', 'Skipped', 'Failed' ]); + else: + oTable = WuiHlpGraphDataTable('When', [ 'Succeeded', 'Failed' ]); + + for i, dStatuses in enumerate(adPeriods): + cSuccesses = dStatuses[ReportModelBase.ksTestStatus_Success]; + cFailures = dStatuses[ReportModelBase.ksTestStatus_Failure]; + cSkipped = dStatuses[ReportModelBase.ksTestStatus_Skipped]; + + cSuccess = cSuccesses + cSkipped; + cTotal = cSuccess + cFailures; + sPeriod = self._oModel.getPeriodDesc(i); + if fTailoredForGoogleCharts: + if cTotalSkipped > 0: + oTable.addRow(sPeriod, + [ cSuccesses * 100 // cTotal if cTotal else 0, + cSkipped * 100 // cTotal if cTotal else 0, + cFailures * 100 // cTotal if cTotal else 0, ], + [ self.fmtPct(cSuccesses, cTotal) if cSuccesses else None, + self.fmtPct(cSkipped, cTotal) if cSkipped else None, + self.fmtPct(cFailures, cTotal) if cFailures else None, ]); + else: + oTable.addRow(sPeriod, + [ cSuccesses * 100 // cTotal if cTotal else 0, + cFailures * 100 // cTotal if cTotal else 0, ], + [ self.fmtPct(cSuccesses, cTotal) if cSuccesses else None, + self.fmtPct(cFailures, cTotal) if cFailures else None, ]); + elif cTotal > 0: + if cTotalSkipped > 0: + oTable.addRow(sPeriod, + [ cSuccesses * 100 // cTotal, + cSkipped * 100 // cTotal, + cFailures * 100 // cTotal, ], + [ self.fmtPctWithHits(cSuccesses, cTotal), + self.fmtPctWithHits(cSkipped, cTotal), + self.fmtPctWithHits(cFailures, cTotal), ]); + else: + oTable.addRow(sPeriod, + [ cSuccesses * 100 // cTotal, + cFailures * 100 // cTotal, ], + [ self.fmtPctWithHits(cSuccesses, cTotal), + self.fmtPctWithHits(cFailures, cTotal), ]); + elif cTotalSkipped > 0: + oTable.addRow(sPeriod, [ 0, 0, 0 ], [ '0%', '0%', '0%' ]); + else: + oTable.addRow(sPeriod, [ 0, 0 ], [ '0%', '0%' ]); + + # + # Render the graph. + # + oGraph = WuiHlpBarGraph('success-rate', oTable, self._oDisp); + oGraph.setRangeMax(100); + sReport += oGraph.renderGraph(); + + # + # Graph with absolute counts. + # + if fTailoredForGoogleCharts: + if cTotalSkipped > 0: + oTable = WuiHlpGraphDataTable(None, [ 'Succeeded', 'Skipped', 'Failed' ]); + else: + oTable = WuiHlpGraphDataTable(None, [ 'Succeeded', 'Failed' ]); + for i, dStatuses in enumerate(adPeriods): + cSuccesses = dStatuses[ReportModelBase.ksTestStatus_Success]; + cFailures = dStatuses[ReportModelBase.ksTestStatus_Failure]; + cSkipped = dStatuses[ReportModelBase.ksTestStatus_Skipped]; + + if cTotalSkipped > 0: + oTable.addRow(None, #self._oModel.getPeriodDesc(i), + [ cSuccesses, cSkipped, cFailures, ], + [ str(cSuccesses) if cSuccesses > 0 else None, + str(cSkipped) if cSkipped > 0 else None, + str(cFailures) if cFailures > 0 else None, ]); + else: + oTable.addRow(None, #self._oModel.getPeriodDesc(i), + [ cSuccesses, cFailures, ], + [ str(cSuccesses) if cSuccesses > 0 else None, + str(cFailures) if cFailures > 0 else None, ]); + oGraph = WuiHlpBarGraph('success-numbers', oTable, self._oDisp); + oGraph.invertYDirection(); + sReport += oGraph.renderGraph(); + + return sReport; + + +class WuiReportFailuresBase(WuiReportBase): + """ + Common parent of WuiReportFailureReasons and WuiReportTestCaseFailures. + """ + + def _splitSeriesIntoMultipleGraphs(self, aidSorted, cMaxSeriesPerGraph = 8): + """ + Splits the ID array into one or more arrays, making sure we don't + have too many series per graph. + Returns array of ID arrays. + """ + if len(aidSorted) <= cMaxSeriesPerGraph + 2: + return [aidSorted,]; + cGraphs = len(aidSorted) // cMaxSeriesPerGraph + (len(aidSorted) % cMaxSeriesPerGraph != 0); + cPerGraph = len(aidSorted) // cGraphs + (len(aidSorted) % cGraphs != 0); + + aaoRet = []; + cLeft = len(aidSorted); + iSrc = 0; + while cLeft > 0: + cThis = cPerGraph; + if cLeft <= cPerGraph + 2: + cThis = cLeft; + elif cLeft <= cPerGraph * 2 + 4: + cThis = cLeft // 2; + aaoRet.append(aidSorted[iSrc : iSrc + cThis]); + iSrc += cThis; + cLeft -= cThis; + return aaoRet; + + def _formatEdgeOccurenceSubject(self, oTransient): + """ + Worker for _formatEdgeOccurence that child classes overrides to format + their type of subject data in the best possible way. + """ + _ = oTransient; + assert False; + return ''; + + def _formatEdgeOccurence(self, oTransient): + """ + Helper for formatting the transients. + oTransient is of type ReportFailureReasonTransient or ReportTestCaseFailureTransient. + """ + sHtml = u'<li>'; + if oTransient.fEnter: sHtml += 'Since '; + else: sHtml += 'Until '; + sHtml += WuiSvnLinkWithTooltip(oTransient.iRevision, oTransient.sRepository, fBracketed = 'False').toHtml(); + sHtml += u', %s: ' % (WuiTestSetLink(oTransient.idTestSet, self.formatTsShort(oTransient.tsDone), + fBracketed = False).toHtml(), ) + sHtml += self._formatEdgeOccurenceSubject(oTransient); + sHtml += u'</li>\n'; + return sHtml; + + def _generateTransitionList(self, oSet): + """ + Generates the enter and leave lists. + """ + # Skip this if we're looking at builds. + if self._oModel.sSubject in [self._oModel.ksSubBuild,] and len(self._oModel.aidSubjects) in [1, 2]: + return u''; + + sHtml = u'<h4>Movements:</h4>\n' \ + u'<ul>\n'; + if not oSet.aoEnterInfo and not oSet.aoLeaveInfo: + sHtml += u'<li>No changes</li>\n'; + else: + for oTransient in oSet.aoEnterInfo: + sHtml += self._formatEdgeOccurence(oTransient); + for oTransient in oSet.aoLeaveInfo: + sHtml += self._formatEdgeOccurence(oTransient); + sHtml += u'</ul>\n'; + + return sHtml; + + + def _formatSeriesNameColumnHeadersForTable(self): + """ Formats the series name column for the HTML table. """ + return '<th>Subject Name</th>'; + + def _formatSeriesNameForTable(self, oSet, idKey): + """ Formats the series name for the HTML table. """ + _ = oSet; + return '<td>%d</td>' % (idKey,); + + def _formatRowValueForTable(self, oRow, oPeriod, cColsPerSeries): + """ Formats a row value for the HTML table. """ + _ = oPeriod; + if oRow is None: + return u'<td colspan="%d"> </td>' % (cColsPerSeries,); + if cColsPerSeries == 2: + return u'<td align="right">%u%%</td><td align="center">%u / %u</td>' \ + % (oRow.cHits * 100 // oRow.cTotal, oRow.cHits, oRow.cTotal); + return u'<td align="center">%u</td>' % (oRow.cHits,); + + def _formatSeriesTotalForTable(self, oSet, idKey, cColsPerSeries): + """ Formats the totals cell for a data series in the HTML table. """ + dcTotalPerId = getattr(oSet, 'dcTotalPerId', None); + if cColsPerSeries == 2: + return u'<td align="right">%u%%</td><td align="center">%u/%u</td>' \ + % (oSet.dcHitsPerId[idKey] * 100 // dcTotalPerId[idKey], oSet.dcHitsPerId[idKey], dcTotalPerId[idKey]); + return u'<td align="center">%u</td>' % (oSet.dcHitsPerId[idKey],); + + def _generateTableForSet(self, oSet, aidSorted = None, iSortColumn = 0, + fWithTotals = True, cColsPerSeries = None): + """ + Turns the set into a table. + + Returns raw html. + """ + sHtml = u'<table class="tmtbl-report-set" width="100%%">\n'; + if cColsPerSeries is None: + cColsPerSeries = 2 if hasattr(oSet, 'dcTotalPerId') else 1; + + # Header row. + sHtml += u' <tr><thead><th>#</th>'; + sHtml += self._formatSeriesNameColumnHeadersForTable(); + for iPeriod, oPeriod in enumerate(reversed(oSet.aoPeriods)): + sHtml += u'<th colspan="%d"><a href="javascript:ahrefActionSortByColumns(\'%s\',[%s]);">%s</a>%s</th>' \ + % ( cColsPerSeries, self._oDisp.ksParamSortColumns, iPeriod, webutils.escapeElem(oPeriod.sDesc), + '▼' if iPeriod == iSortColumn else ''); + if fWithTotals: + sHtml += u'<th colspan="%d"><a href="javascript:ahrefActionSortByColumns(\'%s\',[%s]);">Total</a>%s</th>' \ + % ( cColsPerSeries, self._oDisp.ksParamSortColumns, len(oSet.aoPeriods), + '▼' if iSortColumn == len(oSet.aoPeriods) else ''); + sHtml += u'</thead></td>\n'; + + # Each data series. + if aidSorted is None: + aidSorted = oSet.dSubjects.keys(); + sHtml += u' <tbody>\n'; + for iRow, idKey in enumerate(aidSorted): + sHtml += u' <tr class="%s">' % ('tmodd' if iRow & 1 else 'tmeven',); + sHtml += u'<td align="left">#%u</td>' % (iRow + 1,); + sHtml += self._formatSeriesNameForTable(oSet, idKey); + for oPeriod in reversed(oSet.aoPeriods): + oRow = oPeriod.dRowsById.get(idKey, None); + sHtml += self._formatRowValueForTable(oRow, oPeriod, cColsPerSeries); + if fWithTotals: + sHtml += self._formatSeriesTotalForTable(oSet, idKey, cColsPerSeries); + sHtml += u' </tr>\n'; + sHtml += u' </tbody>\n'; + sHtml += u'</table>\n'; + return sHtml; + + +class WuiReportFailuresWithTotalBase(WuiReportFailuresBase): + """ + For ReportPeriodSetWithTotalBase. + """ + + def _formatSeriedNameForGraph(self, oSubject): + """ + Format the subject name for the graph. + """ + return str(oSubject); + + def _getSortedIds(self, oSet): + """ + Get default sorted subject IDs and which column. + """ + + # Figure the sorting column. + if self._aiSortColumns is not None \ + and self._aiSortColumns \ + and abs(self._aiSortColumns[0]) <= len(oSet.aoPeriods): + iSortColumn = abs(self._aiSortColumns[0]); + fByTotal = iSortColumn >= len(oSet.aoPeriods); # pylint: disable=unused-variable + elif oSet.cMaxTotal < 10: + iSortColumn = len(oSet.aoPeriods); + else: + iSortColumn = 0; + + if iSortColumn >= len(oSet.aoPeriods): + # Sort the total. + aidSortedRaw = sorted(oSet.dSubjects, + key = lambda idKey: oSet.dcHitsPerId[idKey] * 10000 // oSet.dcTotalPerId[idKey], + reverse = True); + else: + # Sort by NOW column. + dTmp = {}; + for idKey in oSet.dSubjects: + oRow = oSet.aoPeriods[-1 - iSortColumn].dRowsById.get(idKey, None); + if oRow is None: dTmp[idKey] = 0; + else: dTmp[idKey] = oRow.cHits * 10000 // max(1, oRow.cTotal); + aidSortedRaw = sorted(dTmp, key = lambda idKey: dTmp[idKey], reverse = True); + return (aidSortedRaw, iSortColumn); + + def _generateGraph(self, oSet, sIdBase, aidSortedRaw): + """ + Generates graph. + """ + sHtml = u''; + fGenerateGraph = len(aidSortedRaw) <= 6 and len(aidSortedRaw) > 0; ## Make this configurable. + if fGenerateGraph: + # Figure the graph width for all of them. + uPctMax = max(oSet.uMaxPct, oSet.cMaxHits * 100 // oSet.cMaxTotal); + uPctMax = max(uPctMax + 2, 10); + + for _, aidSorted in enumerate(self._splitSeriesIntoMultipleGraphs(aidSortedRaw, 8)): + asNames = []; + for idKey in aidSorted: + oSubject = oSet.dSubjects[idKey]; + asNames.append(self._formatSeriedNameForGraph(oSubject)); + + oTable = WuiHlpGraphDataTable('Period', asNames); + + for _, oPeriod in enumerate(reversed(oSet.aoPeriods)): + aiValues = []; + asValues = []; + + for idKey in aidSorted: + oRow = oPeriod.dRowsById.get(idKey, None); + if oRow is not None: + aiValues.append(oRow.cHits * 100 // oRow.cTotal); + asValues.append(self.fmtPctWithHitsAndTotal(oRow.cHits, oRow.cTotal)); + else: + aiValues.append(0); + asValues.append('0'); + + oTable.addRow(oPeriod.sDesc, aiValues, asValues); + + if True: # pylint: disable=using-constant-test + aiValues = []; + asValues = []; + for idKey in aidSorted: + uPct = oSet.dcHitsPerId[idKey] * 100 // oSet.dcTotalPerId[idKey]; + aiValues.append(uPct); + asValues.append(self.fmtPctWithHitsAndTotal(oSet.dcHitsPerId[idKey], oSet.dcTotalPerId[idKey])); + oTable.addRow('Totals', aiValues, asValues); + + oGraph = WuiHlpBarGraph(sIdBase, oTable, self._oDisp); + oGraph.setRangeMax(uPctMax); + sHtml += '<br>\n'; + sHtml += oGraph.renderGraph(); + return sHtml; + + + +class WuiReportFailureReasons(WuiReportFailuresBase): + """ + Generates a report displaying the failure reasons over time. + """ + + def _formatEdgeOccurenceSubject(self, oTransient): + return u'%s / %s' % ( webutils.escapeElem(oTransient.oReason.oCategory.sShort), + webutils.escapeElem(oTransient.oReason.sShort),); + + def _formatSeriesNameColumnHeadersForTable(self): + return '<th>Failure Reason</th>'; + + def _formatSeriesNameForTable(self, oSet, idKey): + oReason = oSet.dSubjects[idKey]; + sHtml = u'<td>'; + sHtml += u'%s / %s' % ( webutils.escapeElem(oReason.oCategory.sShort), webutils.escapeElem(oReason.sShort),); + sHtml += u'</td>'; + return sHtml; + + + def generateReportBody(self): + self._sTitle = 'Failure reasons'; + + # + # Get the data and sort the data series in descending order of badness. + # + oSet = self._oModel.getFailureReasons(); + aidSortedRaw = sorted(oSet.dSubjects, key = lambda idReason: oSet.dcHitsPerId[idReason], reverse = True); + + # + # Generate table and transition list. These are the most useful ones with the current graph machinery. + # + sHtml = self._generateTableForSet(oSet, aidSortedRaw, len(oSet.aoPeriods)); + sHtml += self._generateTransitionList(oSet); + + # + # Check if most of the stuff is without any assign reason, if so, skip + # that part of the graph so it doesn't offset the interesting bits. + # + fIncludeWithoutReason = True; + for oPeriod in reversed(oSet.aoPeriods): + if oPeriod.cWithoutReason > oSet.cMaxHits * 4: + fIncludeWithoutReason = False; + sHtml += '<p>Warning: Many failures without assigned reason!</p>\n'; + break; + + # + # Generate the graph. + # + fGenerateGraph = len(aidSortedRaw) <= 9 and len(aidSortedRaw) > 0; ## Make this configurable. + if fGenerateGraph: + aidSorted = aidSortedRaw; + + asNames = []; + for idReason in aidSorted: + oReason = oSet.dSubjects[idReason]; + asNames.append('%s / %s' % (oReason.oCategory.sShort, oReason.sShort,) ) + if fIncludeWithoutReason: + asNames.append('No reason'); + + oTable = WuiHlpGraphDataTable('Period', asNames); + + cMax = oSet.cMaxHits; + for _, oPeriod in enumerate(reversed(oSet.aoPeriods)): + aiValues = []; + + for idReason in aidSorted: + oRow = oPeriod.dRowsById.get(idReason, None); + iValue = oRow.cHits if oRow is not None else 0; + aiValues.append(iValue); + + if fIncludeWithoutReason: + aiValues.append(oPeriod.cWithoutReason); + if oPeriod.cWithoutReason > cMax: + cMax = oPeriod.cWithoutReason; + + oTable.addRow(oPeriod.sDesc, aiValues); + + oGraph = WuiHlpBarGraph('failure-reason', oTable, self._oDisp); + oGraph.setRangeMax(max(cMax + 1, 3)); + sHtml += oGraph.renderGraph(); + return sHtml; + + +class WuiReportTestCaseFailures(WuiReportFailuresWithTotalBase): + """ + Generates a report displaying the failure reasons over time. + """ + + def _formatEdgeOccurenceSubject(self, oTransient): + sHtml = u'%s ' % ( webutils.escapeElem(oTransient.oSubject.sName),); + sHtml += WuiTestCaseDetailsLink(oTransient.oSubject.idTestCase, fBracketed = False).toHtml(); + return sHtml; + + def _formatSeriesNameColumnHeadersForTable(self): + return '<th>Test Case</th>'; + + def _formatSeriesNameForTable(self, oSet, idKey): + oTestCase = oSet.dSubjects[idKey]; + return u'<td>%s %s %s</td>' % \ + ( WuiTestResultsForTestCaseLink(idKey, oTestCase.sName, self._dExtraTestResultsParams).toHtml(), + WuiTestCaseDetailsLink(oTestCase.idTestCase).toHtml(), + WuiReportSummaryLink(ReportModelBase.ksSubTestCase, oTestCase.idTestCase, + dExtraParams = self._dExtraParams).toHtml(),); + + def _formatSeriedNameForGraph(self, oSubject): + return oSubject.sName; + + def generateReportBody(self): + self._sTitle = 'Test Case Failures'; + oSet = self._oModel.getTestCaseFailures(); + (aidSortedRaw, iSortColumn) = self._getSortedIds(oSet); + + sHtml = self._generateTableForSet(oSet, aidSortedRaw, iSortColumn); + sHtml += self._generateTransitionList(oSet); + sHtml += self._generateGraph(oSet, 'testcase-graph', aidSortedRaw); + return sHtml; + + +class WuiReportTestCaseArgsFailures(WuiReportFailuresWithTotalBase): + """ + Generates a report displaying the failure reasons over time. + """ + + def __init__(self, oModel, dParams, fSubReport = False, aiSortColumns = None, fnDPrint = None, oDisp = None): + WuiReportFailuresWithTotalBase.__init__(self, oModel, dParams, fSubReport = fSubReport, + aiSortColumns = aiSortColumns, fnDPrint = fnDPrint, oDisp = oDisp); + self.oTestCaseCrit = TestResultFilter().aCriteria[TestResultFilter.kiTestCases] # type: FilterCriterion + + @staticmethod + def _formatName(oTestCaseArgs): + """ Internal helper for formatting the testcase name. """ + if oTestCaseArgs.sSubName: + sName = u'%s / %s' % ( oTestCaseArgs.oTestCase.sName, oTestCaseArgs.sSubName, ); + else: + sName = u'%s / #%u' % ( oTestCaseArgs.oTestCase.sName, oTestCaseArgs.idTestCaseArgs, ); + return sName; + + def _formatEdgeOccurenceSubject(self, oTransient): + sHtml = u'%s ' % ( webutils.escapeElem(self._formatName(oTransient.oSubject)),); + sHtml += WuiTestCaseDetailsLink(oTransient.oSubject.idTestCase, fBracketed = False).toHtml(); + return sHtml; + + def _formatSeriesNameColumnHeadersForTable(self): + return '<th>Test Case / Variation</th>'; + + def _formatSeriesNameForTable(self, oSet, idKey): + oTestCaseArgs = oSet.dSubjects[idKey]; + sHtml = u'<td>'; + dParams = dict(self._dExtraTestResultsParams); + dParams[self.oTestCaseCrit.sVarNm] = oTestCaseArgs.idTestCase; + dParams[self.oTestCaseCrit.oSub.sVarNm] = idKey; + sHtml += WuiTestResultsForTestCaseLink(oTestCaseArgs.idTestCase, self._formatName(oTestCaseArgs), dParams).toHtml(); + sHtml += u' '; + sHtml += WuiTestCaseDetailsLink(oTestCaseArgs.idTestCase).toHtml(); + #sHtml += u' '; + #sHtml += WuiReportSummaryLink(ReportModelBase.ksSubTestCaseArgs, oTestCaseArgs.idTestCaseArgs, + # sName = self._formatName(oTestCaseArgs), dExtraParams = self._dExtraParams).toHtml(); + sHtml += u'</td>'; + return sHtml; + + def _formatSeriedNameForGraph(self, oSubject): + return self._formatName(oSubject); + + def generateReportBody(self): + self._sTitle = 'Test Case Variation Failures'; + oSet = self._oModel.getTestCaseVariationFailures(); + (aidSortedRaw, iSortColumn) = self._getSortedIds(oSet); + + sHtml = self._generateTableForSet(oSet, aidSortedRaw, iSortColumn); + sHtml += self._generateTransitionList(oSet); + sHtml += self._generateGraph(oSet, 'testcasearg-graph', aidSortedRaw); + return sHtml; + + + +class WuiReportTestBoxFailures(WuiReportFailuresWithTotalBase): + """ + Generates a report displaying the failure reasons over time. + """ + + def _formatEdgeOccurenceSubject(self, oTransient): + sHtml = u'%s ' % ( webutils.escapeElem(oTransient.oSubject.sName),); + sHtml += WuiTestBoxDetailsLinkShort(oTransient.oSubject).toHtml(); + return sHtml; + + def _formatSeriesNameColumnHeadersForTable(self): + return '<th colspan="5">Test Box</th>'; + + def _formatSeriesNameForTable(self, oSet, idKey): + oTestBox = oSet.dSubjects[idKey]; + sHtml = u'<td>'; + sHtml += WuiTestResultsForTestBoxLink(idKey, oTestBox.sName, self._dExtraTestResultsParams).toHtml() + sHtml += u' '; + sHtml += WuiTestBoxDetailsLinkShort(oTestBox).toHtml(); + sHtml += u' '; + sHtml += WuiReportSummaryLink(ReportModelBase.ksSubTestBox, oTestBox.idTestBox, + dExtraParams = self._dExtraParams).toHtml(); + sHtml += u'</td>'; + sOsAndVer = '%s %s' % (oTestBox.sOs, oTestBox.sOsVersion.strip(),); + if len(sOsAndVer) < 22: + sHtml += u'<td>%s</td>' % (webutils.escapeElem(sOsAndVer),); + else: # wonder if td.title works.. + sHtml += u'<td title="%s" width="1%%" style="white-space:nowrap;">%s...</td>' \ + % (webutils.escapeAttr(sOsAndVer), webutils.escapeElem(sOsAndVer[:20])); + sHtml += u'<td>%s</td>' % (webutils.escapeElem(oTestBox.getArchBitString()),); + sHtml += u'<td>%s</td>' % (webutils.escapeElem(oTestBox.getPrettyCpuVendor()),); + sHtml += u'<td>%s' % (oTestBox.getPrettyCpuVersion(),); + if oTestBox.fCpuNestedPaging: sHtml += u', np'; + elif oTestBox.fCpuHwVirt: sHtml += u', hw'; + else: sHtml += u', raw'; + if oTestBox.fCpu64BitGuest: sHtml += u', 64'; + sHtml += u'</td>'; + return sHtml; + + def _formatSeriedNameForGraph(self, oSubject): + return oSubject.sName; + + def generateReportBody(self): + self._sTitle = 'Test Box Failures'; + oSet = self._oModel.getTestBoxFailures(); + (aidSortedRaw, iSortColumn) = self._getSortedIds(oSet); + + sHtml = self._generateTableForSet(oSet, aidSortedRaw, iSortColumn); + sHtml += self._generateTransitionList(oSet); + sHtml += self._generateGraph(oSet, 'testbox-graph', aidSortedRaw); + return sHtml; + + +class WuiReportSummary(WuiReportBase): + """ + Summary report. + """ + + def generateReportBody(self): + self._sTitle = 'Summary'; + sHtml = '<p>This will display several reports and listings useful to get an overview of %s (id=%s).</p>' \ + % (self._oModel.sSubject, self._oModel.aidSubjects,); + + aoReports = []; + + aoReports.append(WuiReportSuccessRate( self._oModel, self._dParams, fSubReport = True, + aiSortColumns = self._aiSortColumns, + fnDPrint = self._fnDPrint, oDisp = self._oDisp)); + aoReports.append(WuiReportTestCaseFailures(self._oModel, self._dParams, fSubReport = True, + aiSortColumns = self._aiSortColumns, + fnDPrint = self._fnDPrint, oDisp = self._oDisp)); + if self._oModel.sSubject == ReportModelBase.ksSubTestCase: + aoReports.append(WuiReportTestCaseArgsFailures(self._oModel, self._dParams, fSubReport = True, + aiSortColumns = self._aiSortColumns, + fnDPrint = self._fnDPrint, oDisp = self._oDisp)); + aoReports.append(WuiReportTestBoxFailures( self._oModel, self._dParams, fSubReport = True, + aiSortColumns = self._aiSortColumns, + fnDPrint = self._fnDPrint, oDisp = self._oDisp)); + aoReports.append(WuiReportFailureReasons( self._oModel, self._dParams, fSubReport = True, + aiSortColumns = self._aiSortColumns, + fnDPrint = self._fnDPrint, oDisp = self._oDisp)); + + for oReport in aoReports: + (sTitle, sContent) = oReport.show(); + sHtml += '<br>'; # drop this layout hack + sHtml += '<div>'; + sHtml += '<h3>%s</h3>\n' % (webutils.escapeElem(sTitle),); + sHtml += sContent; + sHtml += '</div>'; + + return sHtml; + diff --git a/src/VBox/ValidationKit/testmanager/webui/wuitestresult.py b/src/VBox/ValidationKit/testmanager/webui/wuitestresult.py new file mode 100755 index 00000000..4f08b5aa --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuitestresult.py @@ -0,0 +1,965 @@ +# -*- coding: utf-8 -*- +# $Id: wuitestresult.py $ + +""" +Test Manager WUI - Test Results. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + +# Python imports. +import datetime; + +# Validation Kit imports. +from testmanager.webui.wuicontentbase import WuiContentBase, WuiListContentBase, WuiHtmlBase, WuiTmLink, WuiLinkBase, \ + WuiSvnLink, WuiSvnLinkWithTooltip, WuiBuildLogLink, WuiRawHtml, \ + WuiHtmlKeeper; +from testmanager.webui.wuimain import WuiMain; +from testmanager.webui.wuihlpform import WuiHlpForm; +from testmanager.webui.wuiadminfailurereason import WuiFailureReasonAddLink, WuiFailureReasonDetailsLink; +from testmanager.webui.wuitestresultfailure import WuiTestResultFailureDetailsLink; +from testmanager.core.failurereason import FailureReasonData, FailureReasonLogic; +from testmanager.core.report import ReportGraphModel, ReportModelBase; +from testmanager.core.testbox import TestBoxData; +from testmanager.core.testcase import TestCaseData; +from testmanager.core.testset import TestSetData; +from testmanager.core.testgroup import TestGroupData; +from testmanager.core.testresultfailures import TestResultFailureData; +from testmanager.core.build import BuildData; +from testmanager.core import db; +from testmanager import config; +from common import webutils, utils; + + +class WuiTestSetLink(WuiTmLink): + """ Test set link. """ + + def __init__(self, idTestSet, sName = WuiContentBase.ksShortDetailsLink, fBracketed = False): + WuiTmLink.__init__(self, sName, WuiMain.ksScriptName, + { WuiMain.ksParamAction: WuiMain.ksActionTestResultDetails, + TestSetData.ksParam_idTestSet: idTestSet, }, fBracketed = fBracketed); + self.idTestSet = idTestSet; + +class WuiTestResultsForSomethingLink(WuiTmLink): + """ Test results link for a grouping. """ + + def __init__(self, sGroupedBy, idGroupMember, sName = WuiContentBase.ksShortTestResultsLink, + dExtraParams = None, fBracketed = False): + dParams = dict(dExtraParams) if dExtraParams else {}; + dParams[WuiMain.ksParamAction] = sGroupedBy; + dParams[WuiMain.ksParamGroupMemberId] = idGroupMember; + WuiTmLink.__init__(self, sName, WuiMain.ksScriptName, dParams, fBracketed = fBracketed); + + +class WuiTestResultsForTestBoxLink(WuiTestResultsForSomethingLink): + """ Test results link for a given testbox. """ + + def __init__(self, idTestBox, sName = WuiContentBase.ksShortTestResultsLink, dExtraParams = None, fBracketed = False): + WuiTestResultsForSomethingLink.__init__(self, WuiMain.ksActionResultsGroupedByTestBox, idTestBox, + sName = sName, dExtraParams = dExtraParams, fBracketed = fBracketed); + + +class WuiTestResultsForTestCaseLink(WuiTestResultsForSomethingLink): + """ Test results link for a given testcase. """ + + def __init__(self, idTestCase, sName = WuiContentBase.ksShortTestResultsLink, dExtraParams = None, fBracketed = False): + WuiTestResultsForSomethingLink.__init__(self, WuiMain.ksActionResultsGroupedByTestCase, idTestCase, + sName = sName, dExtraParams = dExtraParams, fBracketed = fBracketed); + + +class WuiTestResultsForBuildRevLink(WuiTestResultsForSomethingLink): + """ Test results link for a given build revision. """ + + def __init__(self, iRevision, sName = WuiContentBase.ksShortTestResultsLink, dExtraParams = None, fBracketed = False): + WuiTestResultsForSomethingLink.__init__(self, WuiMain.ksActionResultsGroupedByBuildRev, iRevision, + sName = sName, dExtraParams = dExtraParams, fBracketed = fBracketed); + + +class WuiTestResult(WuiContentBase): + """Display test case result""" + + def __init__(self, fnDPrint = None, oDisp = None): + WuiContentBase.__init__(self, fnDPrint = fnDPrint, oDisp = oDisp); + + # Cyclic import hacks. + from testmanager.webui.wuiadmin import WuiAdmin; + self.oWuiAdmin = WuiAdmin; + + def _toHtml(self, oObject): + """Translate some object to HTML.""" + if isinstance(oObject, WuiHtmlBase): + return oObject.toHtml(); + if db.isDbTimestamp(oObject): + return webutils.escapeElem(self.formatTsShort(oObject)); + if db.isDbInterval(oObject): + return webutils.escapeElem(self.formatIntervalShort(oObject)); + if utils.isString(oObject): + return webutils.escapeElem(oObject); + return webutils.escapeElem(str(oObject)); + + def _htmlTable(self, aoTableContent): + """Generate HTML code for table""" + sHtml = u' <table class="tmtbl-testresult-details" width="100%%">\n'; + + for aoSubRows in aoTableContent: + if not aoSubRows: + continue; # Can happen if there is no testsuit. + oCaption = aoSubRows[0]; + sHtml += u' \n' \ + u' <tr class="tmtbl-result-details-caption">\n' \ + u' <td colspan="2">%s</td>\n' \ + u' </tr>\n' \ + % (self._toHtml(oCaption),); + + iRow = 0; + for aoRow in aoSubRows[1:]: + iRow += 1; + sHtml += u' <tr class="%s">\n' % ('tmodd' if iRow & 1 else 'tmeven',); + if len(aoRow) == 1: + sHtml += u' <td class="tmtbl-result-details-subcaption" colspan="2">%s</td>\n' \ + % (self._toHtml(aoRow[0]),); + else: + sHtml += u' <th scope="row">%s</th>\n' % (webutils.escapeElem(aoRow[0]),); + if len(aoRow) > 2: + sHtml += u' <td>%s</td>\n' % (aoRow[2](aoRow[1]),); + else: + sHtml += u' <td>%s</td>\n' % (self._toHtml(aoRow[1]),); + sHtml += u' </tr>\n'; + + sHtml += u' </table>\n'; + + return sHtml + + def _highlightStatus(self, sStatus): + """Return sStatus string surrounded by HTML highlight code """ + sTmp = '<font color=%s><b>%s</b></font>' \ + % ('red' if sStatus == 'failure' else 'green', webutils.escapeElem(sStatus.upper())) + return sTmp + + def _anchorAndAppendBinaries(self, sBinaries, aoRows): + """ Formats each binary (if any) into a row with a download link. """ + if sBinaries is not None: + for sBinary in sBinaries.split(','): + if not webutils.hasSchema(sBinary): + sBinary = config.g_ksBuildBinUrlPrefix + sBinary; + aoRows.append([WuiLinkBase(webutils.getFilename(sBinary), sBinary, fBracketed = False),]); + return aoRows; + + + def _formatEventTimestampHtml(self, tsEvent, tsLog, idEvent, oTestSet): + """ Formats an event timestamp with a main log link. """ + tsEvent = db.dbTimestampToZuluDatetime(tsEvent); + #sFormattedTimestamp = u'%04u\u2011%02u\u2011%02u\u00a0%02u:%02u:%02uZ' \ + # % ( tsEvent.year, tsEvent.month, tsEvent.day, + # tsEvent.hour, tsEvent.minute, tsEvent.second,); + sFormattedTimestamp = u'%02u:%02u:%02uZ' \ + % ( tsEvent.hour, tsEvent.minute, tsEvent.second,); + sTitle = u'#%u - %04u\u2011%02u\u2011%02u\u00a0%02u:%02u:%02u.%06uZ' \ + % ( idEvent, tsEvent.year, tsEvent.month, tsEvent.day, + tsEvent.hour, tsEvent.minute, tsEvent.second, tsEvent.microsecond, ); + tsLog = db.dbTimestampToZuluDatetime(tsLog); + sFragment = u'%02u_%02u_%02u_%06u' % ( tsLog.hour, tsLog.minute, tsLog.second, tsLog.microsecond); + return WuiTmLink(sFormattedTimestamp, '', + { WuiMain.ksParamAction: WuiMain.ksActionViewLog, + WuiMain.ksParamLogSetId: oTestSet.idTestSet, }, + sFragmentId = sFragment, sTitle = sTitle, fBracketed = False, ).toHtml(); + + def _recursivelyGenerateEvents(self, oTestResult, sParentName, sLineage, iRow, + iFailure, oTestSet, iDepth): # pylint: disable=too-many-locals + """ + Recursively generate event table rows for the result set. + + oTestResult is an object of the type TestResultDataEx. + """ + # Hack: Replace empty outer test result name with (pretty) command line. + if iRow == 1: + sName = ''; + sDisplayName = sParentName; + else: + sName = oTestResult.sName if sParentName == '' else '%s, %s' % (sParentName, oTestResult.sName,); + sDisplayName = webutils.escapeElem(sName); + + # Format error count. + sErrCnt = ''; + if oTestResult.cErrors > 0: + sErrCnt = ' (1 error)' if oTestResult.cErrors == 1 else ' (%d errors)' % oTestResult.cErrors; + + # Format bits for adding or editing the failure reason. Level 0 is handled at the top of the page. + sChangeReason = ''; + if oTestResult.cErrors > 0 and iDepth > 0 and self._oDisp is not None and not self._oDisp.isReadOnlyUser(): + dTmp = { + self._oDisp.ksParamAction: self._oDisp.ksActionTestResultFailureAdd if oTestResult.oReason is None else + self._oDisp.ksActionTestResultFailureEdit, + TestResultFailureData.ksParam_idTestResult: oTestResult.idTestResult, + }; + sChangeReason = ' <a href="?%s" class="tmtbl-edit-reason" onclick="addRedirectToAnchorHref(this)">%s</a> ' \ + % ( webutils.encodeUrlParams(dTmp), WuiContentBase.ksShortEditLinkHtml ); + + # Format the include in graph checkboxes. + sLineage += ':%u' % (oTestResult.idStrName,); + sResultGraph = '<input type="checkbox" name="%s" value="%s%s" title="Include result in graph."/>' \ + % (WuiMain.ksParamReportSubjectIds, ReportGraphModel.ksTypeResult, sLineage,); + sElapsedGraph = ''; + if oTestResult.tsElapsed is not None: + sElapsedGraph = '<input type="checkbox" name="%s" value="%s%s" title="Include elapsed time in graph."/>' \ + % ( WuiMain.ksParamReportSubjectIds, ReportGraphModel.ksTypeElapsed, sLineage); + + + if not oTestResult.aoChildren \ + and len(oTestResult.aoValues) + len(oTestResult.aoMsgs) + len(oTestResult.aoFiles) == 0: + # Leaf - single row. + tsEvent = oTestResult.tsCreated; + if oTestResult.tsElapsed is not None: + tsEvent += oTestResult.tsElapsed; + sHtml = ' <tr class="%s tmtbl-events-leaf tmtbl-events-lvl%s tmstatusrow-%s" id="S%u">\n' \ + ' <td id="E%u">%s</td>\n' \ + ' <td>%s</td>\n' \ + ' <td>%s</td>\n' \ + ' <td>%s</td>\n' \ + ' <td colspan="2"%s>%s%s%s</td>\n' \ + ' <td>%s</td>\n' \ + ' </tr>\n' \ + % ( 'tmodd' if iRow & 1 else 'tmeven', iDepth, oTestResult.enmStatus, oTestResult.idTestResult, + oTestResult.idTestResult, + self._formatEventTimestampHtml(tsEvent, oTestResult.tsCreated, oTestResult.idTestResult, oTestSet), + sElapsedGraph, + webutils.escapeElem(self.formatIntervalShort(oTestResult.tsElapsed)) if oTestResult.tsElapsed is not None + else '', + sDisplayName, + ' id="failure-%u"' % (iFailure,) if oTestResult.isFailure() else '', + webutils.escapeElem(oTestResult.enmStatus), webutils.escapeElem(sErrCnt), + sChangeReason if oTestResult.oReason is None else '', + sResultGraph ); + iRow += 1; + else: + # Multiple rows. + sHtml = ' <tr class="%s tmtbl-events-first tmtbl-events-lvl%s ">\n' \ + ' <td>%s</td>\n' \ + ' <td></td>\n' \ + ' <td></td>\n' \ + ' <td>%s</td>\n' \ + ' <td colspan="2">%s</td>\n' \ + ' <td></td>\n' \ + ' </tr>\n' \ + % ( 'tmodd' if iRow & 1 else 'tmeven', iDepth, + self._formatEventTimestampHtml(oTestResult.tsCreated, oTestResult.tsCreated, + oTestResult.idTestResult, oTestSet), + sDisplayName, + 'running' if oTestResult.tsElapsed is None else '', ); + iRow += 1; + + # Depth. Check if our error count is just reflecting the one of our children. + cErrorsBelow = 0; + for oChild in oTestResult.aoChildren: + (sChildHtml, iRow, iFailure) = self._recursivelyGenerateEvents(oChild, sName, sLineage, + iRow, iFailure, oTestSet, iDepth + 1); + sHtml += sChildHtml; + cErrorsBelow += oChild.cErrors; + + # Messages. + for oMsg in oTestResult.aoMsgs: + sHtml += ' <tr class="%s tmtbl-events-message tmtbl-events-lvl%s">\n' \ + ' <td>%s</td>\n' \ + ' <td></td>\n' \ + ' <td></td>\n' \ + ' <td colspan="3">%s: %s</td>\n' \ + ' <td></td>\n' \ + ' </tr>\n' \ + % ( 'tmodd' if iRow & 1 else 'tmeven', iDepth, + self._formatEventTimestampHtml(oMsg.tsCreated, oMsg.tsCreated, oMsg.idTestResultMsg, oTestSet), + webutils.escapeElem(oMsg.enmLevel), + webutils.escapeElem(oMsg.sMsg), ); + iRow += 1; + + # Values. + for oValue in oTestResult.aoValues: + sHtml += ' <tr class="%s tmtbl-events-value tmtbl-events-lvl%s">\n' \ + ' <td>%s</td>\n' \ + ' <td></td>\n' \ + ' <td></td>\n' \ + ' <td>%s</td>\n' \ + ' <td class="tmtbl-events-number">%s</td>\n' \ + ' <td class="tmtbl-events-unit">%s</td>\n' \ + ' <td><input type="checkbox" name="%s" value="%s%s:%u" title="Include value in graph."></td>\n' \ + ' </tr>\n' \ + % ( 'tmodd' if iRow & 1 else 'tmeven', iDepth, + self._formatEventTimestampHtml(oValue.tsCreated, oValue.tsCreated, oValue.idTestResultValue, oTestSet), + webutils.escapeElem(oValue.sName), + utils.formatNumber(oValue.lValue).replace(' ', ' '), + webutils.escapeElem(oValue.sUnit), + WuiMain.ksParamReportSubjectIds, ReportGraphModel.ksTypeValue, sLineage, oValue.idStrName, ); + iRow += 1; + + # Files. + for oFile in oTestResult.aoFiles: + if oFile.sMime in [ 'text/plain', ]: + aoLinks = [ + WuiTmLink('%s (%s)' % (oFile.sFile, oFile.sKind), '', + { self._oDisp.ksParamAction: self._oDisp.ksActionViewLog, + self._oDisp.ksParamLogSetId: oTestSet.idTestSet, + self._oDisp.ksParamLogFileId: oFile.idTestResultFile, }, + sTitle = oFile.sDescription), + WuiTmLink('View Raw', '', + { self._oDisp.ksParamAction: self._oDisp.ksActionGetFile, + self._oDisp.ksParamGetFileSetId: oTestSet.idTestSet, + self._oDisp.ksParamGetFileId: oFile.idTestResultFile, + self._oDisp.ksParamGetFileDownloadIt: False, }, + sTitle = oFile.sDescription), + ] + else: + aoLinks = [ + WuiTmLink('%s (%s)' % (oFile.sFile, oFile.sKind), '', + { self._oDisp.ksParamAction: self._oDisp.ksActionGetFile, + self._oDisp.ksParamGetFileSetId: oTestSet.idTestSet, + self._oDisp.ksParamGetFileId: oFile.idTestResultFile, + self._oDisp.ksParamGetFileDownloadIt: False, }, + sTitle = oFile.sDescription), + ] + aoLinks.append(WuiTmLink('Download', '', + { self._oDisp.ksParamAction: self._oDisp.ksActionGetFile, + self._oDisp.ksParamGetFileSetId: oTestSet.idTestSet, + self._oDisp.ksParamGetFileId: oFile.idTestResultFile, + self._oDisp.ksParamGetFileDownloadIt: True, }, + sTitle = oFile.sDescription)); + + sHtml += ' <tr class="%s tmtbl-events-file tmtbl-events-lvl%s">\n' \ + ' <td>%s</td>\n' \ + ' <td></td>\n' \ + ' <td></td>\n' \ + ' <td>%s</td>\n' \ + ' <td></td>\n' \ + ' <td></td>\n' \ + ' <td></td>\n' \ + ' </tr>\n' \ + % ( 'tmodd' if iRow & 1 else 'tmeven', iDepth, + self._formatEventTimestampHtml(oFile.tsCreated, oFile.tsCreated, oFile.idTestResultFile, oTestSet), + '\n'.join(oLink.toHtml() for oLink in aoLinks),); + iRow += 1; + + # Done? + if oTestResult.tsElapsed is not None: + tsEvent = oTestResult.tsCreated + oTestResult.tsElapsed; + sHtml += ' <tr class="%s tmtbl-events-final tmtbl-events-lvl%s tmstatusrow-%s" id="E%d">\n' \ + ' <td>%s</td>\n' \ + ' <td>%s</td>\n' \ + ' <td>%s</td>\n' \ + ' <td>%s</td>\n' \ + ' <td colspan="2"%s>%s%s%s</td>\n' \ + ' <td>%s</td>\n' \ + ' </tr>\n' \ + % ( 'tmodd' if iRow & 1 else 'tmeven', iDepth, oTestResult.enmStatus, oTestResult.idTestResult, + self._formatEventTimestampHtml(tsEvent, tsEvent, oTestResult.idTestResult, oTestSet), + sElapsedGraph, + webutils.escapeElem(self.formatIntervalShort(oTestResult.tsElapsed)), + sDisplayName, + ' id="failure-%u"' % (iFailure,) if oTestResult.isFailure() else '', + webutils.escapeElem(oTestResult.enmStatus), webutils.escapeElem(sErrCnt), + sChangeReason if cErrorsBelow < oTestResult.cErrors and oTestResult.oReason is None else '', + sResultGraph); + iRow += 1; + + # Failure reason. + if oTestResult.oReason is not None: + sReasonText = '%s / %s' % ( oTestResult.oReason.oFailureReason.oCategory.sShort, + oTestResult.oReason.oFailureReason.sShort, ); + sCommentHtml = ''; + if oTestResult.oReason.sComment and oTestResult.oReason.sComment.strip(): + sCommentHtml = '<br>' + webutils.escapeElem(oTestResult.oReason.sComment.strip()); + sCommentHtml = sCommentHtml.replace('\n', '<br>'); + + sDetailedReason = ' <a href="?%s" class="tmtbl-show-reason">%s</a>' \ + % ( webutils.encodeUrlParams({ self._oDisp.ksParamAction: + self._oDisp.ksActionTestResultFailureDetails, + TestResultFailureData.ksParam_idTestResult: + oTestResult.idTestResult,}), + WuiContentBase.ksShortDetailsLinkHtml,); + + sHtml += ' <tr class="%s tmtbl-events-reason tmtbl-events-lvl%s">\n' \ + ' <td>%s</td>\n' \ + ' <td colspan="2">%s</td>\n' \ + ' <td colspan="3">%s%s%s%s</td>\n' \ + ' <td>%s</td>\n' \ + ' </tr>\n' \ + % ( 'tmodd' if iRow & 1 else 'tmeven', iDepth, + webutils.escapeElem(self.formatTsShort(oTestResult.oReason.tsEffective)), + oTestResult.oReason.oAuthor.sUsername, + webutils.escapeElem(sReasonText), sDetailedReason, sChangeReason, + sCommentHtml, + 'todo'); + iRow += 1; + + if oTestResult.isFailure(): + iFailure += 1; + + return (sHtml, iRow, iFailure); + + + def _generateMainReason(self, oTestResultTree, oTestSet): + """ + Generates the form for displaying and updating the main failure reason. + + oTestResultTree is an instance TestResultDataEx. + oTestSet is an instance of TestSetData. + + """ + _ = oTestSet; + sHtml = ' '; + + if oTestResultTree.isFailure() or oTestResultTree.cErrors > 0: + sHtml += ' <h2>Failure Reason:</h2>\n'; + oData = oTestResultTree.oReason; + + # We need the failure reasons for the combobox. + aoFailureReasons = FailureReasonLogic(self._oDisp.getDb()).fetchForCombo('Test Sheriff, you figure out why!'); + assert aoFailureReasons; + + # For now we'll use the standard form helper. + sFormActionUrl = '%s?%s=%s' % ( self._oDisp.ksScriptName, self._oDisp.ksParamAction, + WuiMain.ksActionTestResultFailureAddPost if oData is None else + WuiMain.ksActionTestResultFailureEditPost ) + fReadOnly = not self._oDisp or self._oDisp.isReadOnlyUser(); + oForm = WuiHlpForm('failure-reason', sFormActionUrl, + sOnSubmit = WuiHlpForm.ksOnSubmit_AddReturnToFieldWithCurrentUrl, fReadOnly = fReadOnly); + oForm.addTextHidden(TestResultFailureData.ksParam_idTestResult, oTestResultTree.idTestResult); + oForm.addTextHidden(TestResultFailureData.ksParam_idTestSet, oTestSet.idTestSet); + if oData is not None: + oForm.addComboBox(TestResultFailureData.ksParam_idFailureReason, oData.idFailureReason, 'Reason', + aoFailureReasons, + sPostHtml = u' ' + WuiFailureReasonDetailsLink(oData.idFailureReason).toHtml() + + (u' ' + WuiFailureReasonAddLink('New', fBracketed = False).toHtml() + if not fReadOnly else u'')); + oForm.addMultilineText(TestResultFailureData.ksParam_sComment, oData.sComment, 'Comment') + + oForm.addNonText(u'%s (%s), %s' + % ( oData.oAuthor.sUsername, oData.oAuthor.sUsername, + self.formatTsShort(oData.tsEffective),), + 'Sheriff', + sPostHtml = ' ' + WuiTestResultFailureDetailsLink(oData.idTestResult, "Show Details").toHtml() ) + + oForm.addTextHidden(TestResultFailureData.ksParam_tsEffective, oData.tsEffective); + oForm.addTextHidden(TestResultFailureData.ksParam_tsExpire, oData.tsExpire); + oForm.addTextHidden(TestResultFailureData.ksParam_uidAuthor, oData.uidAuthor); + oForm.addSubmit('Change Reason'); + else: + oForm.addComboBox(TestResultFailureData.ksParam_idFailureReason, -1, 'Reason', aoFailureReasons, + sPostHtml = ' ' + WuiFailureReasonAddLink('New').toHtml() if not fReadOnly else ''); + oForm.addMultilineText(TestResultFailureData.ksParam_sComment, '', 'Comment'); + oForm.addTextHidden(TestResultFailureData.ksParam_tsEffective, ''); + oForm.addTextHidden(TestResultFailureData.ksParam_tsExpire, ''); + oForm.addTextHidden(TestResultFailureData.ksParam_uidAuthor, ''); + oForm.addSubmit('Add Reason'); + + sHtml += oForm.finalize(); + return sHtml; + + + def showTestCaseResultDetails(self, # pylint: disable=too-many-locals,too-many-statements + oTestResultTree, + oTestSet, + oBuildEx, + oValidationKitEx, + oTestBox, + oTestGroup, + oTestCaseEx, + oTestVarEx): + """Show detailed result""" + def getTcDepsHtmlList(aoTestCaseData): + """Get HTML <ul> list of Test Case name items""" + if aoTestCaseData: + sTmp = '<ul>' + for oTestCaseData in aoTestCaseData: + sTmp += '<li>%s</li>' % (webutils.escapeElem(oTestCaseData.sName),); + sTmp += '</ul>' + else: + sTmp = 'No items' + return sTmp + + def getGrDepsHtmlList(aoGlobalResourceData): + """Get HTML <ul> list of Global Resource name items""" + if aoGlobalResourceData: + sTmp = '<ul>' + for oGlobalResourceData in aoGlobalResourceData: + sTmp += '<li>%s</li>' % (webutils.escapeElem(oGlobalResourceData.sName),); + sTmp += '</ul>' + else: + sTmp = 'No items' + return sTmp + + + asHtml = [] + + from testmanager.webui.wuireport import WuiReportSummaryLink; + tsReportEffectiveDate = None; + if oTestSet.tsDone is not None: + tsReportEffectiveDate = oTestSet.tsDone + datetime.timedelta(days = 4); + if tsReportEffectiveDate >= self.getNowTs(): + tsReportEffectiveDate = None; + + # Test result + test set details. + aoResultRows = [ + WuiHtmlKeeper([ WuiTmLink(oTestCaseEx.sName, self.oWuiAdmin.ksScriptName, + { self.oWuiAdmin.ksParamAction: self.oWuiAdmin.ksActionTestCaseDetails, + TestCaseData.ksParam_idTestCase: oTestCaseEx.idTestCase, + self.oWuiAdmin.ksParamEffectiveDate: oTestSet.tsConfig, }, + fBracketed = False), + WuiTestResultsForTestCaseLink(oTestCaseEx.idTestCase), + WuiReportSummaryLink(ReportModelBase.ksSubTestCase, oTestCaseEx.idTestCase, + tsNow = tsReportEffectiveDate, fBracketed = False), + ]), + ]; + if oTestCaseEx.sDescription: + aoResultRows.append([oTestCaseEx.sDescription,]); + aoResultRows.append([ 'Status:', WuiRawHtml('<span class="tmspan-status-%s">%s</span>' + % (oTestResultTree.enmStatus, oTestResultTree.enmStatus,))]); + if oTestResultTree.cErrors > 0: + aoResultRows.append(( 'Errors:', oTestResultTree.cErrors )); + aoResultRows.append([ 'Elapsed:', oTestResultTree.tsElapsed ]); + cSecCfgTimeout = oTestCaseEx.cSecTimeout if oTestVarEx.cSecTimeout is None else oTestVarEx.cSecTimeout; + cSecEffTimeout = cSecCfgTimeout * oTestBox.pctScaleTimeout / 100; + aoResultRows.append([ 'Timeout:', + '%s (%s sec)' % (utils.formatIntervalSeconds2(cSecEffTimeout), cSecEffTimeout,) ]); + if cSecEffTimeout != cSecCfgTimeout: + aoResultRows.append([ 'Cfg Timeout:', + '%s (%s sec)' % (utils.formatIntervalSeconds(cSecCfgTimeout), cSecCfgTimeout,) ]); + aoResultRows += [ + ( 'Started:', WuiTmLink(self.formatTsShort(oTestSet.tsCreated), WuiMain.ksScriptName, + { WuiMain.ksParamAction: WuiMain.ksActionResultsUnGrouped, + WuiMain.ksParamEffectiveDate: oTestSet.tsCreated, }, + fBracketed = False) ), + ]; + if oTestSet.tsDone is not None: + aoResultRows += [ ( 'Done:', + WuiTmLink(self.formatTsShort(oTestSet.tsDone), WuiMain.ksScriptName, + { WuiMain.ksParamAction: WuiMain.ksActionResultsUnGrouped, + WuiMain.ksParamEffectiveDate: oTestSet.tsDone, }, + fBracketed = False) ) ]; + else: + aoResultRows += [( 'Done:', 'Still running...')]; + aoResultRows += [( 'Config:', oTestSet.tsConfig )]; + if oTestVarEx.cGangMembers > 1: + aoResultRows.append([ 'Member No:', '#%s (of %s)' % (oTestSet.iGangMemberNo, oTestVarEx.cGangMembers) ]); + + aoResultRows += [ + ( 'Test Group:', + WuiHtmlKeeper([ WuiTmLink(oTestGroup.sName, self.oWuiAdmin.ksScriptName, + { self.oWuiAdmin.ksParamAction: self.oWuiAdmin.ksActionTestGroupDetails, + TestGroupData.ksParam_idTestGroup: oTestGroup.idTestGroup, + self.oWuiAdmin.ksParamEffectiveDate: oTestSet.tsConfig, }, + fBracketed = False), + WuiReportSummaryLink(ReportModelBase.ksSubTestGroup, oTestGroup.idTestGroup, + tsNow = tsReportEffectiveDate, fBracketed = False), + ]), ), + ]; + if oTestVarEx.sTestBoxReqExpr is not None: + aoResultRows.append([ 'TestBox reqs:', oTestVarEx.sTestBoxReqExpr ]); + elif oTestCaseEx.sTestBoxReqExpr is not None or oTestVarEx.sTestBoxReqExpr is not None: + aoResultRows.append([ 'TestBox reqs:', oTestCaseEx.sTestBoxReqExpr ]); + if oTestVarEx.sBuildReqExpr is not None: + aoResultRows.append([ 'Build reqs:', oTestVarEx.sBuildReqExpr ]); + elif oTestCaseEx.sBuildReqExpr is not None or oTestVarEx.sBuildReqExpr is not None: + aoResultRows.append([ 'Build reqs:', oTestCaseEx.sBuildReqExpr ]); + if oTestCaseEx.sValidationKitZips is not None and oTestCaseEx.sValidationKitZips != '@VALIDATIONKIT_ZIP@': + aoResultRows.append([ 'Validation Kit:', oTestCaseEx.sValidationKitZips ]); + if oTestCaseEx.aoDepTestCases: + aoResultRows.append([ 'Prereq. Test Cases:', oTestCaseEx.aoDepTestCases, getTcDepsHtmlList ]); + if oTestCaseEx.aoDepGlobalResources: + aoResultRows.append([ 'Global Resources:', oTestCaseEx.aoDepGlobalResources, getGrDepsHtmlList ]); + + # Builds. + aoBuildRows = []; + if oBuildEx is not None: + aoBuildRows += [ + WuiHtmlKeeper([ WuiTmLink('Build', self.oWuiAdmin.ksScriptName, + { self.oWuiAdmin.ksParamAction: self.oWuiAdmin.ksActionBuildDetails, + BuildData.ksParam_idBuild: oBuildEx.idBuild, + self.oWuiAdmin.ksParamEffectiveDate: oTestSet.tsCreated, }, + fBracketed = False), + WuiTestResultsForBuildRevLink(oBuildEx.iRevision), + WuiReportSummaryLink(ReportModelBase.ksSubBuild, oBuildEx.idBuild, + tsNow = tsReportEffectiveDate, fBracketed = False), ]), + ]; + self._anchorAndAppendBinaries(oBuildEx.sBinaries, aoBuildRows); + aoBuildRows += [ + ( 'Revision:', WuiSvnLinkWithTooltip(oBuildEx.iRevision, oBuildEx.oCat.sRepository, + fBracketed = False) ), + ( 'Product:', oBuildEx.oCat.sProduct ), + ( 'Branch:', oBuildEx.oCat.sBranch ), + ( 'Type:', oBuildEx.oCat.sType ), + ( 'Version:', oBuildEx.sVersion ), + ( 'Created:', oBuildEx.tsCreated ), + ]; + if oBuildEx.uidAuthor is not None: + aoBuildRows += [ ( 'Author ID:', oBuildEx.uidAuthor ), ]; + if oBuildEx.sLogUrl is not None: + aoBuildRows += [ ( 'Log:', WuiBuildLogLink(oBuildEx.sLogUrl, fBracketed = False) ), ]; + + aoValidationKitRows = []; + if oValidationKitEx is not None: + aoValidationKitRows += [ + WuiTmLink('Validation Kit', self.oWuiAdmin.ksScriptName, + { self.oWuiAdmin.ksParamAction: self.oWuiAdmin.ksActionBuildDetails, + BuildData.ksParam_idBuild: oValidationKitEx.idBuild, + self.oWuiAdmin.ksParamEffectiveDate: oTestSet.tsCreated, }, + fBracketed = False), + ]; + self._anchorAndAppendBinaries(oValidationKitEx.sBinaries, aoValidationKitRows); + aoValidationKitRows += [ ( 'Revision:', WuiSvnLink(oValidationKitEx.iRevision, fBracketed = False) ) ]; + if oValidationKitEx.oCat.sProduct != 'VBox TestSuite': + aoValidationKitRows += [ ( 'Product:', oValidationKitEx.oCat.sProduct ), ]; + if oValidationKitEx.oCat.sBranch != 'trunk': + aoValidationKitRows += [ ( 'Product:', oValidationKitEx.oCat.sBranch ), ]; + if oValidationKitEx.oCat.sType != 'release': + aoValidationKitRows += [ ( 'Type:', oValidationKitEx.oCat.sType), ]; + if oValidationKitEx.sVersion != '0.0.0': + aoValidationKitRows += [ ( 'Version:', oValidationKitEx.sVersion ), ]; + aoValidationKitRows += [ + ( 'Created:', oValidationKitEx.tsCreated ), + ]; + if oValidationKitEx.uidAuthor is not None: + aoValidationKitRows += [ ( 'Author ID:', oValidationKitEx.uidAuthor ), ]; + if oValidationKitEx.sLogUrl is not None: + aoValidationKitRows += [ ( 'Log:', WuiBuildLogLink(oValidationKitEx.sLogUrl, fBracketed = False) ), ]; + + # TestBox. + aoTestBoxRows = [ + WuiHtmlKeeper([ WuiTmLink(oTestBox.sName, self.oWuiAdmin.ksScriptName, + { self.oWuiAdmin.ksParamAction: self.oWuiAdmin.ksActionTestBoxDetails, + TestBoxData.ksParam_idGenTestBox: oTestSet.idGenTestBox, }, + fBracketed = False), + WuiTestResultsForTestBoxLink(oTestBox.idTestBox), + WuiReportSummaryLink(ReportModelBase.ksSubTestBox, oTestSet.idTestBox, + tsNow = tsReportEffectiveDate, fBracketed = False), + ]), + ]; + if oTestBox.sDescription: + aoTestBoxRows.append([oTestBox.sDescription, ]); + aoTestBoxRows += [ + ( 'IP:', oTestBox.ip ), + #( 'UUID:', oTestBox.uuidSystem ), + #( 'Enabled:', oTestBox.fEnabled ), + #( 'Lom Kind:', oTestBox.enmLomKind ), + #( 'Lom IP:', oTestBox.ipLom ), + ( 'OS/Arch:', '%s.%s' % (oTestBox.sOs, oTestBox.sCpuArch) ), + ( 'OS Version:', oTestBox.sOsVersion ), + ( 'CPUs:', oTestBox.cCpus ), + ]; + if oTestBox.sCpuName is not None: + aoTestBoxRows.append(['CPU Name', oTestBox.sCpuName.replace(' ', ' ')]); + if oTestBox.lCpuRevision is not None: + sMarch = oTestBox.queryCpuMicroarch(); + if sMarch is not None: + aoTestBoxRows.append( ('CPU Microarch', sMarch) ); + uFamily = oTestBox.getCpuFamily(); + uModel = oTestBox.getCpuModel(); + uStepping = oTestBox.getCpuStepping(); + aoTestBoxRows += [ + ( 'CPU Family', '%u (%#x)' % ( uFamily, uFamily, ) ), + ( 'CPU Model', '%u (%#x)' % ( uModel, uModel, ) ), + ( 'CPU Stepping', '%u (%#x)' % ( uStepping, uStepping, ) ), + ]; + asFeatures = [ oTestBox.sCpuVendor, ]; + if oTestBox.fCpuHwVirt is True: asFeatures.append(u'HW\u2011Virt'); + if oTestBox.fCpuNestedPaging is True: asFeatures.append(u'Nested\u2011Paging'); + if oTestBox.fCpu64BitGuest is True: asFeatures.append(u'64\u2011bit\u2011Guest'); + if oTestBox.fChipsetIoMmu is True: asFeatures.append(u'I/O\u2011MMU'); + aoTestBoxRows += [ + ( 'Features:', u' '.join(asFeatures) ), + ( 'RAM size:', '%s MB' % (oTestBox.cMbMemory,) ), + ( 'Scratch Size:', '%s MB' % (oTestBox.cMbScratch,) ), + ( 'Scale Timeout:', '%s%%' % (oTestBox.pctScaleTimeout,) ), + ( 'Script Rev:', WuiSvnLink(oTestBox.iTestBoxScriptRev, fBracketed = False) ), + ( 'Python:', oTestBox.formatPythonVersion() ), + ( 'Pending Command:', oTestBox.enmPendingCmd ), + ]; + + aoRows = [ + aoResultRows, + aoBuildRows, + aoValidationKitRows, + aoTestBoxRows, + ]; + + asHtml.append(self._htmlTable(aoRows)); + + # + # Convert the tree to a list of events, values, message and files. + # + sHtmlEvents = ''; + sHtmlEvents += '<table class="tmtbl-events" id="tmtbl-events" width="100%">\n'; + sHtmlEvents += ' <tr class="tmheader">\n' \ + ' <th>When</th>\n' \ + ' <th></th>\n' \ + ' <th>Elapsed</th>\n' \ + ' <th>Event name</th>\n' \ + ' <th colspan="2">Value (status)</th>' \ + ' <th></th>\n' \ + ' </tr>\n'; + sPrettyCmdLine = ' \\<br> \n'.join(webutils.escapeElem(oTestCaseEx.sBaseCmd + + ' ' + + oTestVarEx.sArgs).split() ); + (sTmp, _, cFailures) = self._recursivelyGenerateEvents(oTestResultTree, sPrettyCmdLine, '', 1, 0, oTestSet, 0); + sHtmlEvents += sTmp; + + sHtmlEvents += '</table>\n' + + # + # Put it all together. + # + sHtml = '<table class="tmtbl-testresult-details-base" width="100%">\n'; + sHtml += ' <tr>\n' + sHtml += ' <td valign="top" width="20%%">\n%s\n</td>\n' % ' <br>\n'.join(asHtml); + + sHtml += ' <td valign="top" width="80%" style="padding-left:6px">\n'; + sHtml += self._generateMainReason(oTestResultTree, oTestSet); + + sHtml += ' <h2>Events:</h2>\n'; + sHtml += ' <form action="#" method="get" id="graph-form">\n' \ + ' <input type="hidden" name="%s" value="%s"/>\n' \ + ' <input type="hidden" name="%s" value="%u"/>\n' \ + ' <input type="hidden" name="%s" value="%u"/>\n' \ + ' <input type="hidden" name="%s" value="%u"/>\n' \ + ' <input type="hidden" name="%s" value="%u"/>\n' \ + % ( WuiMain.ksParamAction, WuiMain.ksActionGraphWiz, + WuiMain.ksParamGraphWizTestBoxIds, oTestBox.idTestBox, + WuiMain.ksParamGraphWizBuildCatIds, oBuildEx.idBuildCategory, + WuiMain.ksParamGraphWizTestCaseIds, oTestSet.idTestCase, + WuiMain.ksParamGraphWizSrcTestSetId, oTestSet.idTestSet, + ); + if oTestSet.tsDone is not None: + sHtml += ' <input type="hidden" name="%s" value="%s"/>\n' \ + % ( WuiMain.ksParamEffectiveDate, oTestSet.tsDone, ); + sHtml += ' <p>\n'; + sFormButton = '<button type="submit" onclick="%s">Show graphs</button>' \ + % ( webutils.escapeAttr('addDynamicGraphInputs("graph-form", "main", "%s", "%s");' + % (WuiMain.ksParamGraphWizWidth, WuiMain.ksParamGraphWizDpi, )) ); + sHtml += ' ' + sFormButton + '\n'; + sHtml += ' %s %s %s\n' \ + % ( WuiTmLink('Log File', '', + { WuiMain.ksParamAction: WuiMain.ksActionViewLog, + WuiMain.ksParamLogSetId: oTestSet.idTestSet, + }), + WuiTmLink('Raw Log', '', + { WuiMain.ksParamAction: WuiMain.ksActionGetFile, + WuiMain.ksParamGetFileSetId: oTestSet.idTestSet, + WuiMain.ksParamGetFileDownloadIt: False, + }), + WuiTmLink('Download Log', '', + { WuiMain.ksParamAction: WuiMain.ksActionGetFile, + WuiMain.ksParamGetFileSetId: oTestSet.idTestSet, + WuiMain.ksParamGetFileDownloadIt: True, + }), + ); + sHtml += ' </p>\n'; + if cFailures == 1: + sHtml += ' <p>%s</p>\n' % ( WuiTmLink('Jump to failure', '#failure-0'), ) + elif cFailures > 1: + sHtml += ' <p>Jump to failure: '; + if cFailures <= 13: + for iFailure in range(0, cFailures): + sHtml += ' ' + WuiTmLink('#%u' % (iFailure,), '#failure-%u' % (iFailure,)).toHtml(); + else: + for iFailure in range(0, 6): + sHtml += ' ' + WuiTmLink('#%u' % (iFailure,), '#failure-%u' % (iFailure,)).toHtml(); + sHtml += ' ... '; + for iFailure in range(cFailures - 6, cFailures): + sHtml += ' ' + WuiTmLink('#%u' % (iFailure,), '#failure-%u' % (iFailure,)).toHtml(); + sHtml += ' </p>\n'; + + sHtml += sHtmlEvents; + sHtml += ' <p>' + sFormButton + '</p>\n'; + sHtml += ' </form>\n'; + sHtml += ' </td>\n'; + + sHtml += ' </tr>\n'; + sHtml += '</table>\n'; + + return ('Test Case result details', sHtml) + + +class WuiGroupedResultList(WuiListContentBase): + """ + WUI results content generator. + """ + + def __init__(self, aoEntries, cEntriesCount, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, + aiSelectedSortColumns = None): + """Override initialization""" + WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, + sTitle = 'Ungrouped (%d)' % cEntriesCount, sId = 'results', + fnDPrint = fnDPrint, oDisp = oDisp, aiSelectedSortColumns = aiSelectedSortColumns); + + self._cEntriesCount = cEntriesCount + + self._asColumnHeaders = [ + 'Start', + 'Product Build', + 'Kit', + 'Box', + 'OS.Arch', + 'Test Case', + 'Elapsed', + 'Result', + 'Reason', + ]; + self._asColumnAttribs = ['align="center"', 'align="center"', 'align="center"', + 'align="center"', 'align="center"', 'align="center"', + 'align="center"', 'align="center"', 'align="center"', + 'align="center"', 'align="center"', 'align="center"', + 'align="center"', ]; + + + # Prepare parameter lists. + self._dTestBoxLinkParams = self._oDisp.getParameters(); + self._dTestBoxLinkParams[WuiMain.ksParamAction] = WuiMain.ksActionResultsGroupedByTestBox; + + self._dTestCaseLinkParams = self._oDisp.getParameters(); + self._dTestCaseLinkParams[WuiMain.ksParamAction] = WuiMain.ksActionResultsGroupedByTestCase; + + self._dRevLinkParams = self._oDisp.getParameters(); + self._dRevLinkParams[WuiMain.ksParamAction] = WuiMain.ksActionResultsGroupedByBuildRev; + + + + def _formatListEntry(self, iEntry): + """ + Format *show all* table entry + """ + oEntry = self._aoEntries[iEntry]; + + from testmanager.webui.wuiadmin import WuiAdmin; + from testmanager.webui.wuireport import WuiReportSummaryLink; + + oValidationKit = None; + if oEntry.idBuildTestSuite is not None: + oValidationKit = WuiTmLink('r%s' % (oEntry.iRevisionTestSuite,), + WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildDetails, + BuildData.ksParam_idBuild: oEntry.idBuildTestSuite }, + fBracketed = False); + + aoTestSetLinks = []; + aoTestSetLinks.append(WuiTmLink(oEntry.enmStatus, + WuiMain.ksScriptName, + { WuiMain.ksParamAction: WuiMain.ksActionTestResultDetails, + TestSetData.ksParam_idTestSet: oEntry.idTestSet }, + fBracketed = False)); + if oEntry.cErrors > 0: + aoTestSetLinks.append(WuiRawHtml('-')); + aoTestSetLinks.append(WuiTmLink('%d error%s' % (oEntry.cErrors, '' if oEntry.cErrors == 1 else 's', ), + WuiMain.ksScriptName, + { WuiMain.ksParamAction: WuiMain.ksActionTestResultDetails, + TestSetData.ksParam_idTestSet: oEntry.idTestSet }, + sFragmentId = 'failure-0', fBracketed = False)); + + + self._dTestBoxLinkParams[WuiMain.ksParamGroupMemberId] = oEntry.idTestBox; + self._dTestCaseLinkParams[WuiMain.ksParamGroupMemberId] = oEntry.idTestCase; + self._dRevLinkParams[WuiMain.ksParamGroupMemberId] = oEntry.iRevision; + + sTestBoxTitle = u''; + if oEntry.sCpuVendor is not None: + sTestBoxTitle += 'CPU vendor:\t%s\n' % ( oEntry.sCpuVendor, ); + if oEntry.sCpuName is not None: + sTestBoxTitle += 'CPU name:\t%s\n' % ( ' '.join(oEntry.sCpuName.split()), ); + if oEntry.sOsVersion is not None: + sTestBoxTitle += 'OS version:\t%s\n' % ( oEntry.sOsVersion, ); + asFeatures = []; + if oEntry.fCpuHwVirt is True: + if oEntry.sCpuVendor is None: + asFeatures.append(u'HW\u2011Virt'); + elif oEntry.sCpuVendor in ['AuthenticAMD',]: + asFeatures.append(u'HW\u2011Virt(AMD\u2011V)'); + else: + asFeatures.append(u'HW\u2011Virt(VT\u2011x)'); + if oEntry.fCpuNestedPaging is True: asFeatures.append(u'Nested\u2011Paging'); + if oEntry.fCpu64BitGuest is True: asFeatures.append(u'64\u2011bit\u2011Guest'); + #if oEntry.fChipsetIoMmu is True: asFeatures.append(u'I/O\u2011MMU'); + sTestBoxTitle += u'CPU features:\t' + u', '.join(asFeatures); + + # Testcase + if oEntry.sSubName: + sTestCaseName = '%s / %s' % (oEntry.sTestCaseName, oEntry.sSubName,); + else: + sTestCaseName = oEntry.sTestCaseName; + + # Reason: + aoReasons = []; + for oIt in oEntry.aoFailureReasons: + sReasonTitle = 'Reason: \t%s\n' % ( oIt.oFailureReason.sShort, ); + sReasonTitle += 'Category:\t%s\n' % ( oIt.oFailureReason.oCategory.sShort, ); + sReasonTitle += 'Assigned:\t%s\n' % ( self.formatTsShort(oIt.tsFailureReasonAssigned), ); + sReasonTitle += 'By User: \t%s\n' % ( oIt.oFailureReasonAssigner.sUsername, ); + if oIt.sFailureReasonComment: + sReasonTitle += 'Comment: \t%s\n' % ( oIt.sFailureReasonComment, ); + if oIt.oFailureReason.iTicket is not None and oIt.oFailureReason.iTicket > 0: + sReasonTitle += 'xTracker:\t#%s\n' % ( oIt.oFailureReason.iTicket, ); + for i, sUrl in enumerate(oIt.oFailureReason.asUrls): + sUrl = sUrl.strip(); + if sUrl: + sReasonTitle += 'URL#%u: \t%s\n' % ( i, sUrl, ); + aoReasons.append(WuiTmLink(oIt.oFailureReason.sShort, WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureReasonDetails, + FailureReasonData.ksParam_idFailureReason: oIt.oFailureReason.idFailureReason }, + sTitle = sReasonTitle)); + + return [ + oEntry.tsCreated, + [ WuiTmLink('%s %s (%s)' % (oEntry.sProduct, oEntry.sVersion, oEntry.sType,), + WuiMain.ksScriptName, self._dRevLinkParams, sTitle = '%s' % (oEntry.sBranch,), fBracketed = False), + WuiSvnLinkWithTooltip(oEntry.iRevision, 'vbox'), ## @todo add sRepository TestResultListingData + WuiTmLink(self.ksShortDetailsLink, WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildDetails, + BuildData.ksParam_idBuild: oEntry.idBuild }, + fBracketed = False), + ], + oValidationKit, + [ WuiTmLink(oEntry.sTestBoxName, WuiMain.ksScriptName, self._dTestBoxLinkParams, fBracketed = False, + sTitle = sTestBoxTitle), + WuiTmLink(self.ksShortDetailsLink, WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxDetails, + TestBoxData.ksParam_idTestBox: oEntry.idTestBox }, + fBracketed = False), + WuiReportSummaryLink(ReportModelBase.ksSubTestBox, oEntry.idTestBox, fBracketed = False), ], + '%s.%s' % (oEntry.sOs, oEntry.sArch), + [ WuiTmLink(sTestCaseName, WuiMain.ksScriptName, self._dTestCaseLinkParams, fBracketed = False, + sTitle = (oEntry.sBaseCmd + ' ' + oEntry.sArgs) if oEntry.sArgs else oEntry.sBaseCmd), + WuiTmLink(self.ksShortDetailsLink, WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestCaseDetails, + TestCaseData.ksParam_idTestCase: oEntry.idTestCase }, + fBracketed = False), + WuiReportSummaryLink(ReportModelBase.ksSubTestCase, oEntry.idTestCase, fBracketed = False), ], + oEntry.tsElapsed, + aoTestSetLinks, + aoReasons + ]; diff --git a/src/VBox/ValidationKit/testmanager/webui/wuitestresultfailure.py b/src/VBox/ValidationKit/testmanager/webui/wuitestresultfailure.py new file mode 100755 index 00000000..07d8db42 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuitestresultfailure.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +# $Id: wuitestresultfailure.py $ + +""" +Test Manager WUI - Dummy Test Result Failure Reason Edit Dialog - just for error handling! +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Validation Kit imports. +from testmanager.webui.wuicontentbase import WuiFormContentBase, WuiContentBase, WuiTmLink; +from testmanager.webui.wuimain import WuiMain; +from testmanager.webui.wuiadminfailurereason import WuiFailureReasonDetailsLink, WuiFailureReasonAddLink; +from testmanager.core.testresultfailures import TestResultFailureData; +from testmanager.core.testset import TestSetData; +from testmanager.core.failurereason import FailureReasonLogic; + + + +class WuiTestResultFailureDetailsLink(WuiTmLink): + """ Link for adding a failure reason. """ + def __init__(self, idTestResult, sName = WuiContentBase.ksShortDetailsLink, sTitle = None, fBracketed = None): + if fBracketed is None: + fBracketed = len(sName) > 2; + WuiTmLink.__init__(self, sName = sName, + sUrlBase = WuiMain.ksScriptName, + dParams = { WuiMain.ksParamAction: WuiMain.ksActionTestResultFailureDetails, + TestResultFailureData.ksParam_idTestResult: idTestResult, }, + fBracketed = fBracketed, + sTitle = sTitle); + self.idTestResult = idTestResult; + + + +class WuiTestResultFailure(WuiFormContentBase): + """ + WUI test result failure error form generator. + """ + def __init__(self, oData, sMode, oDisp): + if sMode == WuiFormContentBase.ksMode_Add: + sTitle = 'Add Test Result Failure Reason'; + elif sMode == WuiFormContentBase.ksMode_Edit: + sTitle = 'Modify Test Result Failure Reason'; + else: + assert sMode == WuiFormContentBase.ksMode_Show; + sTitle = 'Test Result Failure Reason'; + ## submit access. + WuiFormContentBase.__init__(self, oData, sMode, 'TestResultFailure', oDisp, sTitle); + + def _populateForm(self, oForm, oData): + + aoFailureReasons = FailureReasonLogic(self._oDisp.getDb()).fetchForCombo('Todo: Figure out why'); + sPostHtml = ''; + if oData.idFailureReason is not None and oData.idFailureReason >= 0: + sPostHtml += u' ' + WuiFailureReasonDetailsLink(oData.idFailureReason).toHtml(); + sPostHtml += u' ' + WuiFailureReasonAddLink('New', fBracketed = False).toHtml(); + oForm.addComboBox(TestResultFailureData.ksParam_idFailureReason, oData.idFailureReason, + 'Reason', aoFailureReasons, sPostHtml = sPostHtml); + oForm.addMultilineText(TestResultFailureData.ksParam_sComment, oData.sComment, 'Comment'); + oForm.addIntRO( TestResultFailureData.ksParam_idTestResult, oData.idTestResult, 'Test Result ID'); + oForm.addIntRO( TestResultFailureData.ksParam_idTestSet, oData.idTestSet, 'Test Set ID'); + oForm.addTimestampRO(TestResultFailureData.ksParam_tsEffective, oData.tsEffective, 'Effective Date'); + oForm.addTimestampRO(TestResultFailureData.ksParam_tsExpire, oData.tsExpire, 'Expire (excl)'); + oForm.addIntRO( TestResultFailureData.ksParam_uidAuthor, oData.uidAuthor, 'Changed by UID'); + if self._sMode != WuiFormContentBase.ksMode_Show: + oForm.addSubmit('Add' if self._sMode == WuiFormContentBase.ksMode_Add else 'Modify'); + return True; + + def _generateTopRowFormActions(self, oData): + """ + We add a way to get back to the test set to the actions. + """ + aoActions = super(WuiTestResultFailure, self)._generateTopRowFormActions(oData); + if oData and oData.idTestResult and int(oData.idTestResult) > 0: + aoActions.append(WuiTmLink('Associated Test Set', WuiMain.ksScriptName, + { WuiMain.ksParamAction: WuiMain.ksActionTestSetDetailsFromResult, + TestSetData.ksParam_idTestResult: oData.idTestResult } + )); + return aoActions; diff --git a/src/VBox/ValidationKit/testmanager/webui/wuivcshistory.py b/src/VBox/ValidationKit/testmanager/webui/wuivcshistory.py new file mode 100755 index 00000000..44820e73 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuivcshistory.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +# $Id: wuivcshistory.py $ + +""" +Test Manager WUI - VCS history +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Python imports. +#import datetime; + +# Validation Kit imports. +from testmanager import config; +from testmanager.core import db; +from testmanager.webui.wuicontentbase import WuiContentBase; +from common import webutils; + +class WuiVcsHistoryTooltip(WuiContentBase): + """ + WUI VCS history tooltip generator. + """ + + def __init__(self, aoEntries, sRepository, iRevision, cEntries, fnDPrint, oDisp): + """Override initialization""" + WuiContentBase.__init__(self, fnDPrint = fnDPrint, oDisp = oDisp); + self.aoEntries = aoEntries; + self.sRepository = sRepository; + self.iRevision = iRevision; + self.cEntries = cEntries; + + + def show(self): + """ + Generates the tooltip. + Returns (sTitle, HTML). + """ + sHtml = '<div class="tmvcstimeline tmvcstimelinetooltip">\n'; + + oCurDate = None; + for oEntry in self.aoEntries: + oTsZulu = db.dbTimestampToZuluDatetime(oEntry.tsCreated); + if oCurDate is None or oCurDate != oTsZulu.date(): + if oCurDate is not None: + sHtml += ' </dl>\n' + oCurDate = oTsZulu.date(); + sHtml += ' <h2>%s:</h2>\n' \ + ' <dl>\n' \ + % (oTsZulu.strftime('%Y-%m-%d'),); + + sEntry = ' <dt id="r%s">' % (oEntry.iRevision, ); + sEntry += '<a href="%s" target="_blank">' \ + % ( webutils.escapeAttr(config.g_ksTracChangsetUrlFmt + % { 'iRevision': oEntry.iRevision, 'sRepository': oEntry.sRepository,}), ); + + sEntry += '<span class="tmvcstimeline-time">%s</span>' % ( oTsZulu.strftime('%H:%MZ'), ); + sEntry += ' Changeset <span class="tmvcstimeline-rev">[%s]</span>' % ( oEntry.iRevision, ); + sEntry += ' by <span class="tmvcstimeline-author">%s</span>' % ( webutils.escapeElem(oEntry.sAuthor), ); + sEntry += '</a>\n'; + sEntry += '</dt>\n'; + sEntry += ' <dd>%s</dd>\n' % ( webutils.escapeElem(oEntry.sMessage), ); + + sHtml += sEntry; + + if oCurDate is not None: + sHtml += ' </dl>\n'; + sHtml += '</div>\n'; + + return ('VCS History Tooltip', sHtml); + diff --git a/src/VBox/ValidationKit/tests/Makefile.kmk b/src/VBox/ValidationKit/tests/Makefile.kmk new file mode 100644 index 00000000..84654bde --- /dev/null +++ b/src/VBox/ValidationKit/tests/Makefile.kmk @@ -0,0 +1,60 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Include sub-makefiles. +# +include $(PATH_SUB_CURRENT)/additions/Makefile.kmk +include $(PATH_SUB_CURRENT)/api/Makefile.kmk +include $(PATH_SUB_CURRENT)/audio/Makefile.kmk +include $(PATH_SUB_CURRENT)/autostart/Makefile.kmk +include $(PATH_SUB_CURRENT)/benchmarks/Makefile.kmk +include $(PATH_SUB_CURRENT)/cpu/Makefile.kmk +include $(PATH_SUB_CURRENT)/network/Makefile.kmk +include $(PATH_SUB_CURRENT)/selftests/Makefile.kmk +include $(PATH_SUB_CURRENT)/smoketests/Makefile.kmk +include $(PATH_SUB_CURRENT)/storage/Makefile.kmk +include $(PATH_SUB_CURRENT)/teleportation/Makefile.kmk +include $(PATH_SUB_CURRENT)/unittests/Makefile.kmk +include $(PATH_SUB_CURRENT)/installation/Makefile.kmk +include $(PATH_SUB_CURRENT)/usb/Makefile.kmk +include $(PATH_SUB_CURRENT)/serial/Makefile.kmk + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/ValidationKit/tests/__init__.py b/src/VBox/ValidationKit/tests/__init__.py new file mode 100644 index 00000000..1c93cdf1 --- /dev/null +++ b/src/VBox/ValidationKit/tests/__init__.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# $Id: __init__.py $ + +""" +Just to make python 2.x happy. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + diff --git a/src/VBox/ValidationKit/tests/additions/Makefile.kmk b/src/VBox/ValidationKit/tests/additions/Makefile.kmk new file mode 100644 index 00000000..a7e5cd29 --- /dev/null +++ b/src/VBox/ValidationKit/tests/additions/Makefile.kmk @@ -0,0 +1,54 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Additions Tests. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +INSTALLS += ValidationKitTestsAdditions +ValidationKitTestsAdditions_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsAdditions_INST = $(INST_VALIDATIONKIT)tests/additions/ +ValidationKitTestsAdditions_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdAddBasic1.py \ + $(PATH_SUB_CURRENT)/tdAddGuestCtrl.py \ + $(PATH_SUB_CURRENT)/tdAddSharedFolders1.py + + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsAdditions_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/additions/tdAddBasic1.py b/src/VBox/ValidationKit/tests/additions/tdAddBasic1.py new file mode 100755 index 00000000..a5812a5b --- /dev/null +++ b/src/VBox/ValidationKit/tests/additions/tdAddBasic1.py @@ -0,0 +1,649 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdAddBasic1.py $ + +""" +VirtualBox Validation Kit - Additions Basics #1. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + +# Standard Python imports. +import os; +import sys; +import uuid; + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testdriver import reporter; +from testdriver import base; +from testdriver import vbox; +from testdriver import vboxcon; + +# Sub-test driver imports. +sys.path.append(os.path.dirname(os.path.abspath(__file__))); # For sub-test drivers. +from tdAddGuestCtrl import SubTstDrvAddGuestCtrl; +from tdAddSharedFolders1 import SubTstDrvAddSharedFolders1; + + +class tdAddBasic1(vbox.TestDriver): # pylint: disable=too-many-instance-attributes + """ + Additions Basics #1. + """ + ## @todo + # - More of the settings stuff can be and need to be generalized! + # + + def __init__(self): + vbox.TestDriver.__init__(self); + self.oTestVmSet = self.oTestVmManager.getSmokeVmSet('nat'); + self.asTestsDef = ['install', 'guestprops', 'stdguestprops', 'guestcontrol', 'sharedfolders']; + self.asTests = self.asTestsDef; + self.asRsrcs = None + # The file we're going to use as a beacon to wait if the Guest Additions CD-ROM is ready. + self.sFileCdWait = ''; + # Path pointing to the Guest Additions on the (V)ISO file. + self.sGstPathGaPrefix = ''; + + self.addSubTestDriver(SubTstDrvAddGuestCtrl(self)); + self.addSubTestDriver(SubTstDrvAddSharedFolders1(self)); + + # + # Overridden methods. + # + def showUsage(self): + """ Shows this driver's command line options. """ + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdAddBasic1 Options:'); + reporter.log(' --tests <s1[:s2[:]]>'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestsDef))); + reporter.log(' --quick'); + reporter.log(' Same as --virt-modes hwvirt --cpu-counts 1.'); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--tests': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--tests" takes a colon separated list of tests'); + self.asTests = asArgs[iArg].split(':'); + for s in self.asTests: + if s not in self.asTestsDef: + raise base.InvalidOption('The "--tests" value "%s" is not valid; valid values are: %s' + % (s, ' '.join(self.asTestsDef),)); + + elif asArgs[iArg] == '--quick': + self.parseOption(['--virt-modes', 'hwvirt'], 0); + self.parseOption(['--cpu-counts', '1'], 0); + + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def getResourceSet(self): + if self.asRsrcs is None: + self.asRsrcs = [] + for oSubTstDrv in self.aoSubTstDrvs: + self.asRsrcs.extend(oSubTstDrv.asRsrcs) + self.asRsrcs.extend(self.oTestVmSet.getResourceSet()) + return self.asRsrcs + + def actionConfig(self): + if not self.importVBoxApi(): # So we can use the constant below. + return False; + + eNic0AttachType = vboxcon.NetworkAttachmentType_NAT; + sGaIso = self.getGuestAdditionsIso(); + + # On 6.0 we merge the GAs with the ValidationKit so we can get at FsPerf. + # + # Note1: Not possible to do a double import as both images an '/OS2' dir. + # So, using same dir as with unattended VISOs for the valkit. + # + # Note2: We need to make sure that we don't change the location of the + # ValidationKit bits of the combined VISO, as this will break TXS' (TestExecService) + # automatic updating mechanism (uses hardcoded paths, e.g. "{CDROM}/linux/amd64/TestExecService"). + # + ## @todo Find a solution for testing the automatic Guest Additions updates, which also looks at {CDROM}s root. + if self.fpApiVer >= 6.0: + sGaViso = os.path.join(self.sScratchPath, 'AdditionsAndValKit.viso'); + ## @todo encode as bash cmd line: + sVisoContent = '--iprt-iso-maker-file-marker-bourne-sh %s ' \ + '--import-iso \'%s\' ' \ + '--push-iso \'%s\' ' \ + '/vboxadditions=/ ' \ + '--pop ' \ + % (uuid.uuid4(), self.sVBoxValidationKitIso, sGaIso); + reporter.log2('Using VISO combining ValKit and GAs "%s": %s' % (sVisoContent, sGaViso)); + with open(sGaViso, 'w') as oGaViso: # pylint: disable=unspecified-encoding + oGaViso.write(sVisoContent); + sGaIso = sGaViso; + + self.sGstPathGaPrefix = 'vboxadditions'; + else: + self.sGstPathGaPrefix = ''; + + + reporter.log2('Path to Guest Additions on ISO is "%s"' % self.sGstPathGaPrefix); + + return self.oTestVmSet.actionConfig(self, eNic0AttachType = eNic0AttachType, sDvdImage = sGaIso); + + def actionExecute(self): + return self.oTestVmSet.actionExecute(self, self.testOneCfg); + + + # + # Test execution helpers. + # + + def testOneCfg(self, oVM, oTestVm): + """ + Runs the specified VM thru the tests. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + fRc = False; + + self.logVmInfo(oVM); + + # We skip Linux Guest Additions testing for VBox < 6.1 for now. + fVersionIgnored = oTestVm.isLinux() and self.fpApiVer < 6.1; + + if fVersionIgnored: + reporter.log('Skipping testing for "%s" because VBox version %s is ignored' % (oTestVm.sKind, self.fpApiVer,)); + fRc = True; + else: + reporter.testStart('Waiting for TXS'); + if oTestVm.isWindows(): + self.sFileCdWait = ('%s/VBoxWindowsAdditions.exe' % (self.sGstPathGaPrefix,)); + elif oTestVm.isLinux(): + self.sFileCdWait = ('%s/VBoxLinuxAdditions.run' % (self.sGstPathGaPrefix,)); + + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, fCdWait = True, + cMsCdWait = 5 * 60 * 1000, + sFileCdWait = self.sFileCdWait); + reporter.testDone(); + + if oSession is not None \ + and oTxsSession is not None: + self.addTask(oTxsSession); + # Do the testing. + fSkip = 'install' not in self.asTests; + reporter.testStart('Install'); + if not fSkip: + fRc, oTxsSession = self.testInstallAdditions(oSession, oTxsSession, oTestVm); + reporter.testDone(fSkip); + + if not fSkip \ + and not fRc: + reporter.log('Skipping following tests as Guest Additions were not installed successfully'); + else: + fSkip = 'guestprops' not in self.asTests; + reporter.testStart('Guest Properties'); + if not fSkip: + fRc = self.testGuestProperties(oSession, oTxsSession, oTestVm) and fRc; + reporter.testDone(fSkip); + + fSkip = 'guestcontrol' not in self.asTests; + reporter.testStart('Guest Control'); + if not fSkip: + fRc, oTxsSession = self.aoSubTstDrvs[0].testIt(oTestVm, oSession, oTxsSession); + reporter.testDone(fSkip); + + fSkip = 'sharedfolders' not in self.asTests; + reporter.testStart('Shared Folders'); + if not fSkip: + fRc, oTxsSession = self.aoSubTstDrvs[1].testIt(oTestVm, oSession, oTxsSession); + reporter.testDone(fSkip or fRc is None); + + ## @todo Save and restore test. + + ## @todo Reset tests. + + ## @todo Final test: Uninstallation. + + # Download the TxS (Test Execution Service) log. This is not fatal when not being present. + if not fRc: + self.txsDownloadFiles(oSession, oTxsSession, + [ (oTestVm.pathJoin(self.getGuestTempDir(oTestVm), 'vbox-txs-release.log'), + 'vbox-txs-%s.log' % oTestVm.sVmName) ], + fIgnoreErrors = True); + + # Cleanup. + self.removeTask(oTxsSession); + self.terminateVmBySession(oSession); + + return fRc; + + def testInstallAdditions(self, oSession, oTxsSession, oTestVm): + """ + Tests installing the guest additions + """ + if oTestVm.isWindows(): + (fRc, oTxsSession) = self.testWindowsInstallAdditions(oSession, oTxsSession, oTestVm); + elif oTestVm.isLinux(): + (fRc, oTxsSession) = self.testLinuxInstallAdditions(oSession, oTxsSession, oTestVm); + else: + reporter.error('Guest Additions installation not implemented for %s yet! (%s)' % + (oTestVm.sKind, oTestVm.sVmName,)); + fRc = False; + + # + # Verify installation of Guest Additions using commmon bits. + # + if fRc: + # + # Check if the additions are operational. + # + try: oGuest = oSession.o.console.guest; + except: + reporter.errorXcpt('Getting IGuest failed.'); + return (False, oTxsSession); + + # Wait for the GAs to come up. + reporter.testStart('IGuest::additionsRunLevel'); + fRc = self.testIGuest_additionsRunLevel(oSession, oTestVm, oGuest); + reporter.testDone(); + + # Check the additionsVersion attribute. It must not be empty. + reporter.testStart('IGuest::additionsVersion'); + fRc = self.testIGuest_additionsVersion(oGuest) and fRc; + reporter.testDone(); + + # Check Guest Additions facilities + reporter.testStart('IGuest::getFacilityStatus'); + fRc = self.testIGuest_getFacilityStatus(oTestVm, oGuest) and fRc; + reporter.testDone(); + + # Do a bit of diagnosis on error. + if not fRc: + if oTestVm.isLinux(): + reporter.log('Boot log:'); + sCmdJournalCtl = oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), 'journalctl'); + oTxsSession.syncExec(sCmdJournalCtl, (sCmdJournalCtl, '-b'), fIgnoreErrors = True); + reporter.log('Loaded processes:'); + sCmdPs = oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), 'ps'); + oTxsSession.syncExec(sCmdPs, (sCmdPs, '-a', '-u', '-x'), fIgnoreErrors = True); + reporter.log('Kernel messages:'); + sCmdDmesg = oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), 'dmesg'); + oTxsSession.syncExec(sCmdDmesg, (sCmdDmesg), fIgnoreErrors = True); + reporter.log('Loaded modules:'); + sCmdLsMod = oTestVm.pathJoin(self.getGuestSystemAdminDir(oTestVm), 'lsmod'); + oTxsSession.syncExec(sCmdLsMod, (sCmdLsMod), fIgnoreErrors = True); + elif oTestVm.isWindows() or oTestVm.isOS2(): + sShell = self.getGuestSystemShell(oTestVm); + sShellOpt = '/C' if oTestVm.isWindows() or oTestVm.isOS2() else '-c'; + reporter.log('Loaded processes:'); + oTxsSession.syncExec(sShell, (sShell, sShellOpt, "tasklist.exe", "/FO", "CSV"), fIgnoreErrors = True); + reporter.log('Listing autostart entries:'); + oTxsSession.syncExec(sShell, (sShell, sShellOpt, "wmic.exe", "startup", "get"), fIgnoreErrors = True); + reporter.log('Listing autostart entries:'); + oTxsSession.syncExec(sShell, (sShell, sShellOpt, "dir", + oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), 'VBox*')), + fIgnoreErrors = True); + reporter.log('Downloading logs ...'); + self.txsDownloadFiles(oSession, oTxsSession, + [ ( self.getGuestVBoxTrayClientLogFile(oTestVm), + 'ga-vboxtrayclient-%s.log' % (oTestVm.sVmName,),), + ( "C:\\Documents and Settings\\All Users\\Application Data\\Microsoft\\Dr Watson\\drwtsn32.log", + 'ga-drwatson-%s.log' % (oTestVm.sVmName,), ), + ], + fIgnoreErrors = True); + + return (fRc, oTxsSession); + + def getGuestVBoxTrayClientLogFile(self, oTestVm): + """ Gets the path on the guest for the (release) log file of VBoxTray / VBoxClient. """ + if oTestVm.isWindows(): + return oTestVm.pathJoin(self.getGuestTempDir(oTestVm), 'VBoxTray.log'); + + return oTestVm.pathJoin(self.getGuestTempDir(oTestVm), 'VBoxClient.log'); + + def setGuestEnvVar(self, oSession, oTxsSession, oTestVm, sName, sValue): + """ Sets a system-wide environment variable on the guest. Only supports Windows guests so far. """ + _ = oSession; + if oTestVm.isWindows(): + sPathRegExe = oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), 'reg.exe'); + self.txsRunTest(oTxsSession, ('Set env var \"%s\"' % (sName,)), + 30 * 1000, sPathRegExe, + (sPathRegExe, 'add', + '"HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"', '/v', + sName, '/t', 'REG_EXPAND_SZ', '/d', sValue, '/f')); + + def testWindowsInstallAdditions(self, oSession, oTxsSession, oTestVm): + """ + Installs the Windows guest additions using the test execution service. + Since this involves rebooting the guest, we will have to create a new TXS session. + """ + + # Set system-wide env vars to enable release logging on some applications. + self.setGuestEnvVar(oSession, oTxsSession, oTestVm, 'VBOXTRAY_RELEASE_LOG', 'all.e.l.l2.l3.f'); + self.setGuestEnvVar(oSession, oTxsSession, oTestVm, 'VBOXTRAY_RELEASE_LOG_FLAGS', 'time thread group append'); + self.setGuestEnvVar(oSession, oTxsSession, oTestVm, 'VBOXTRAY_RELEASE_LOG_DEST', + ('file=%s' % (self.getGuestVBoxTrayClientLogFile(oTestVm),))); + + # + # Install the public signing key. + # + if oTestVm.sKind not in ('WindowsNT4', 'Windows2000', 'WindowsXP', 'Windows2003'): + fRc = self.txsRunTest(oTxsSession, 'VBoxCertUtil.exe', 1 * 60 * 1000, + '${CDROM}/%s/cert/VBoxCertUtil.exe' % self.sGstPathGaPrefix, + ('${CDROM}/%s/cert/VBoxCertUtil.exe' % self.sGstPathGaPrefix, 'add-trusted-publisher', + '${CDROM}/%s/cert/vbox-sha1.cer' % self.sGstPathGaPrefix), + fCheckSessionStatus = True); + if not fRc: + reporter.error('Error installing SHA1 certificate'); + else: + fRc = self.txsRunTest(oTxsSession, 'VBoxCertUtil.exe', 1 * 60 * 1000, + '${CDROM}/%s/cert/VBoxCertUtil.exe' % self.sGstPathGaPrefix, + ('${CDROM}/%s/cert/VBoxCertUtil.exe' % self.sGstPathGaPrefix, 'add-trusted-publisher', + '${CDROM}/%s/cert/vbox-sha256.cer' % self.sGstPathGaPrefix), fCheckSessionStatus = True); + if not fRc: + reporter.error('Error installing SHA256 certificate'); + + # + # Delete relevant log files. + # + # Note! On some guests the files in question still can be locked by the OS, so ignore + # deletion errors from the guest side (e.g. sharing violations) and just continue. + # + sWinDir = self.getGuestWinDir(oTestVm); + aasLogFiles = [ + ( oTestVm.pathJoin(sWinDir, 'setupapi.log'), 'ga-setupapi-%s.log' % (oTestVm.sVmName,), ), + ( oTestVm.pathJoin(sWinDir, 'setupact.log'), 'ga-setupact-%s.log' % (oTestVm.sVmName,), ), + ( oTestVm.pathJoin(sWinDir, 'setuperr.log'), 'ga-setuperr-%s.log' % (oTestVm.sVmName,), ), + ]; + + # Apply The SetupAPI logging level so that we also get the (most verbose) setupapi.dev.log file. + ## @todo !!! HACK ALERT !!! Add the value directly into the testing source image. Later. + sRegExe = oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), 'reg.exe'); + fHaveSetupApiDevLog = self.txsRunTest(oTxsSession, 'Enabling setupapi.dev.log', 30 * 1000, + sRegExe, + (sRegExe, 'add', + '"HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Setup"', + '/v', 'LogLevel', '/t', 'REG_DWORD', '/d', '0xFF'), + fCheckSessionStatus = True); + + for sGstFile, _ in aasLogFiles: + self.txsRmFile(oSession, oTxsSession, sGstFile, 10 * 1000, fIgnoreErrors = True); + + # Enable installing the optional auto-logon modules (VBoxGINA/VBoxCredProv). + # Also tell the installer to produce the appropriate log files. + sExe = '${CDROM}/%s/VBoxWindowsAdditions.exe' % self.sGstPathGaPrefix; + asArgs = [ sExe, '/S', '/l', '/with_autologon' ]; + + # Determine if we need to force installing the legacy timestamp CA to make testing succeed. + # Note: Don't force installing when the Guest Additions installer should do this automatically, + # i.e, only force it for Windows Server 2016 and up. + fForceInstallTimeStampCA = False; + if self.fpApiVer >= 6.1 \ + and oTestVm.getNonCanonicalGuestOsType() \ + in [ 'Windows2016', 'Windows2019', 'Windows2022', 'Windows11' ]: + fForceInstallTimeStampCA = True; + + # As we don't have a console command line to parse for the Guest Additions installer (only a message box) and + # unknown / unsupported parameters get ignored with silent installs anyway, we safely can add the following parameter(s) + # even if older Guest Addition installers might not support those. + if fForceInstallTimeStampCA: + asArgs.extend([ '/install_timestamp_ca' ]); + + # + # Do the actual install. + # + fRc = self.txsRunTest(oTxsSession, 'VBoxWindowsAdditions.exe', 5 * 60 * 1000, + sExe, asArgs, fCheckSessionStatus = True); + + # Add the Windows Guest Additions installer files to the files we want to download + # from the guest. Note: There won't be a install_ui.log because of the silent installation. + sGuestAddsDir = 'C:\\Program Files\\Oracle\\VirtualBox Guest Additions\\'; + aasLogFiles.append((sGuestAddsDir + 'install.log', 'ga-install-%s.log' % (oTestVm.sVmName,),)); + aasLogFiles.append((sGuestAddsDir + 'install_drivers.log', 'ga-install_drivers-%s.log' % (oTestVm.sVmName,),)); + aasLogFiles.append(('C:\\Windows\\setupapi.log', 'ga-setupapi-%s.log' % (oTestVm.sVmName,),)); + + # Note: setupapi.dev.log only is available since Windows 2000. + if fHaveSetupApiDevLog: + aasLogFiles.append(('C:\\Windows\\setupapi.dev.log', 'ga-setupapi.dev-%s.log' % (oTestVm.sVmName,),)); + + # + # Download log files. + # Ignore errors as all files above might not be present (or in different locations) + # on different Windows guests. + # + self.txsDownloadFiles(oSession, oTxsSession, aasLogFiles, fIgnoreErrors = True); + + # + # Reboot the VM and reconnect the TXS session. + # + if fRc: + reporter.testStart('Rebooting guest w/ updated Guest Additions active'); + (fRc, oTxsSession) = self.txsRebootAndReconnectViaTcp(oSession, oTxsSession, cMsTimeout = 15 * 60 * 1000); + if fRc: + pass; + else: + reporter.testFailure('Rebooting and reconnecting to TXS service failed'); + reporter.testDone(); + else: + reporter.error('Error installing Windows Guest Additions (installer returned with exit code <> 0)') + + return (fRc, oTxsSession); + + def getAdditionsInstallerResult(self, oTxsSession): + """ + Extracts the Guest Additions installer exit code from a run before. + Assumes that nothing else has been run on the same TXS session in the meantime. + """ + iRc = 0; + (_, sOpcode, abPayload) = oTxsSession.getLastReply(); + if sOpcode.startswith('PROC NOK '): # Extract process rc + iRc = abPayload[0]; # ASSUMES 8-bit rc for now. + ## @todo Parse more statuses here. + return iRc; + + def testLinuxInstallAdditions(self, oSession, oTxsSession, oTestVm): + # + # The actual install. + # Also tell the installer to produce the appropriate log files. + # + # Make sure to add "--nox11" to the makeself wrapper in order to not getting any blocking + # xterm window spawned. + fRc = self.txsRunTest(oTxsSession, 'VBoxLinuxAdditions.run', 30 * 60 * 1000, + self.getGuestSystemShell(oTestVm), + (self.getGuestSystemShell(oTestVm), + '${CDROM}/%s/VBoxLinuxAdditions.run' % self.sGstPathGaPrefix, '--nox11')); + if not fRc: + iRc = self.getAdditionsInstallerResult(oTxsSession); + # Check for rc == 0 just for completeness. + if iRc in (0, 2): # Can happen if the GA installer has detected older VBox kernel modules running and needs a reboot. + reporter.log('Guest has old(er) VBox kernel modules still running; requires a reboot'); + fRc = True; + + if not fRc: + reporter.error('Installing Linux Additions failed (isSuccess=%s, lastReply=%s, see log file for details)' + % (oTxsSession.isSuccess(), oTxsSession.getLastReply())); + + # + # Download log files. + # Ignore errors as all files above might not be present for whatever reason. + # + self.txsDownloadFiles(oSession, oTxsSession, + [('/var/log/vboxadd-install.log', 'vboxadd-install-%s.log' % oTestVm.sVmName), ], + fIgnoreErrors = True); + + # Do the final reboot to get the just installed Guest Additions up and running. + if fRc: + reporter.testStart('Rebooting guest w/ updated Guest Additions active'); + (fRc, oTxsSession) = self.txsRebootAndReconnectViaTcp(oSession, oTxsSession, cMsTimeout = 15 * 60 * 1000); + if fRc: + pass + else: + reporter.testFailure('Rebooting and reconnecting to TXS service failed'); + reporter.testDone(); + + return (fRc, oTxsSession); + + def testIGuest_additionsRunLevel(self, oSession, oTestVm, oGuest): + """ + Do run level tests. + """ + + _ = oGuest; + + if oTestVm.isWindows(): + if oTestVm.isLoggedOntoDesktop(): + eExpectedRunLevel = vboxcon.AdditionsRunLevelType_Desktop; + else: + eExpectedRunLevel = vboxcon.AdditionsRunLevelType_Userland; + else: + ## @todo VBoxClient does not have facility statuses implemented yet. + eExpectedRunLevel = vboxcon.AdditionsRunLevelType_Userland; + + return self.waitForGAs(oSession, aenmWaitForRunLevels = [ eExpectedRunLevel ]); + + def testIGuest_additionsVersion(self, oGuest): + """ + Returns False if no version string could be obtained, otherwise True + even though errors are logged. + """ + try: + sVer = oGuest.additionsVersion; + except: + reporter.errorXcpt('Getting the additions version failed.'); + return False; + reporter.log('IGuest::additionsVersion="%s"' % (sVer,)); + + if sVer.strip() == '': + reporter.error('IGuest::additionsVersion is empty.'); + return False; + + if sVer != sVer.strip(): + reporter.error('IGuest::additionsVersion is contains spaces: "%s".' % (sVer,)); + + asBits = sVer.split('.'); + if len(asBits) < 3: + reporter.error('IGuest::additionsVersion does not contain at least tree dot separated fields: "%s" (%d).' + % (sVer, len(asBits))); + + ## @todo verify the format. + return True; + + def checkFacilityStatus(self, oGuest, eFacilityType, sDesc, fMustSucceed = True): + """ + Prints the current status of a Guest Additions facility. + + Return success status. + """ + + fRc = True; + + try: + eStatus, tsLastUpdatedMs = oGuest.getFacilityStatus(eFacilityType); + except: + if fMustSucceed: + reporter.errorXcpt('Getting facility status for "%s" failed' % (sDesc,)); + fRc = False; + else: + if eStatus == vboxcon.AdditionsFacilityStatus_Inactive: + sStatus = "INACTIVE"; + elif eStatus == vboxcon.AdditionsFacilityStatus_Paused: + sStatus = "PAUSED"; + elif eStatus == vboxcon.AdditionsFacilityStatus_PreInit: + sStatus = "PREINIT"; + elif eStatus == vboxcon.AdditionsFacilityStatus_Init: + sStatus = "INIT"; + elif eStatus == vboxcon.AdditionsFacilityStatus_Active: + sStatus = "ACTIVE"; + elif eStatus == vboxcon.AdditionsFacilityStatus_Terminating: + sStatus = "TERMINATING"; + fRc = not fMustSucceed; + elif eStatus == vboxcon.AdditionsFacilityStatus_Terminated: + sStatus = "TERMINATED"; + fRc = not fMustSucceed; + elif eStatus == vboxcon.AdditionsFacilityStatus_Failed: + sStatus = "FAILED"; + fRc = not fMustSucceed; + elif eStatus == vboxcon.AdditionsFacilityStatus_Unknown: + sStatus = "UNKNOWN"; + fRc = not fMustSucceed; + else: + sStatus = "???"; + fRc = not fMustSucceed; + + reporter.log('Guest Additions facility "%s": %s (last updated: %sms)' % (sDesc, sStatus, str(tsLastUpdatedMs))); + if fMustSucceed \ + and not fRc: + reporter.error('Guest Additions facility "%s" did not report expected status (is "%s")' % (sDesc, sStatus)); + + return fRc; + + def testIGuest_getFacilityStatus(self, oTestVm, oGuest): + """ + Checks Guest Additions facilities for their status. + + Returns success status. + """ + + reporter.testStart('Status VBoxGuest Driver'); + fRc = self.checkFacilityStatus(oGuest, vboxcon.AdditionsFacilityType_VBoxGuestDriver, "VBoxGuest Driver"); + reporter.testDone(); + + reporter.testStart('Status VBoxService'); + fRc = self.checkFacilityStatus(oGuest, vboxcon.AdditionsFacilityType_VBoxService, "VBoxService") and fRc; + reporter.testDone(); + + if oTestVm.isWindows(): + if oTestVm.isLoggedOntoDesktop(): + ## @todo VBoxClient does not have facility statuses implemented yet. + reporter.testStart('Status VBoxTray / VBoxClient'); + fRc = self.checkFacilityStatus(oGuest, vboxcon.AdditionsFacilityType_VBoxTrayClient, + "VBoxTray / VBoxClient") and fRc; + reporter.testDone(); + ## @todo Add more. + + return fRc; + + def testGuestProperties(self, oSession, oTxsSession, oTestVm): + """ + Test guest properties. + """ + _ = oSession; _ = oTxsSession; _ = oTestVm; + return True; + +if __name__ == '__main__': + sys.exit(tdAddBasic1().main(sys.argv)); diff --git a/src/VBox/ValidationKit/tests/additions/tdAddGuestCtrl.py b/src/VBox/ValidationKit/tests/additions/tdAddGuestCtrl.py new file mode 100755 index 00000000..26eb7306 --- /dev/null +++ b/src/VBox/ValidationKit/tests/additions/tdAddGuestCtrl.py @@ -0,0 +1,5516 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# pylint: disable=too-many-lines + +""" +VirtualBox Validation Kit - Guest Control Tests. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154746 $" + +# Standard Python imports. +import errno +import os +import random +import string +import struct +import sys +import threading +import time + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testdriver import reporter; +from testdriver import base; +from testdriver import testfileset; +from testdriver import vbox; +from testdriver import vboxcon; +from testdriver import vboxtestfileset; +from testdriver import vboxwrappers; +from common import utils; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int # pylint: disable=redefined-builtin,invalid-name + xrange = range; # pylint: disable=redefined-builtin,invalid-name + +def limitString(sString, cLimit = 128): + """ + Returns a string with ellipsis ("...") when exceeding the specified limit. + Useful for toning down logging. By default strings will be shortened at 128 characters. + """ + if not isinstance(sString, str): + sString = str(sString); + cLen = len(sString); + if not cLen: + return ''; + return (sString[:cLimit] + '...[%d more]' % (cLen - cLimit)) if cLen > cLimit else sString; + +class GuestStream(bytearray): + """ + Class for handling a guest process input/output stream. + + @todo write stdout/stderr tests. + """ + def appendStream(self, stream, convertTo = '<b'): + """ + Appends and converts a byte sequence to this object; + handy for displaying a guest stream. + """ + self.extend(struct.pack(convertTo, stream)); + + +class tdCtxCreds(object): + """ + Provides credentials to pass to the guest. + """ + def __init__(self, sUser = None, sPassword = None, sDomain = None): + self.oTestVm = None; + self.sUser = sUser; + self.sPassword = sPassword; + self.sDomain = sDomain; + + def applyDefaultsIfNotSet(self, oTestVm): + """ + Applies credential defaults, based on the test VM (guest OS), if + no credentials were set yet. + + Returns success status. + """ + self.oTestVm = oTestVm; + if not self.oTestVm: + reporter.log('VM object is invalid -- did VBoxSVC or a client crash?'); + return False; + + if self.sUser is None: + self.sUser = self.oTestVm.getTestUser(); + + if self.sPassword is None: + self.sPassword = self.oTestVm.getTestUserPassword(self.sUser); + + if self.sDomain is None: + self.sDomain = ''; + + return True; + +class tdTestGuestCtrlBase(object): + """ + Base class for all guest control tests. + + Note: This test ASSUMES that working Guest Additions + were installed and running on the guest to be tested. + """ + def __init__(self, oCreds = None): + self.oGuest = None; ##< IGuest. + self.oTestVm = None; + self.oCreds = oCreds ##< type: tdCtxCreds + self.timeoutMS = 30 * 1000; ##< 30s timeout + self.oGuestSession = None; ##< IGuestSession reference or None. + + def setEnvironment(self, oSession, oTxsSession, oTestVm): + """ + Sets the test environment required for this test. + + Returns success status. + """ + _ = oTxsSession; + + fRc = True; + try: + self.oGuest = oSession.o.console.guest; + self.oTestVm = oTestVm; + except: + fRc = reporter.errorXcpt(); + + if self.oCreds is None: + self.oCreds = tdCtxCreds(); + + fRc = fRc and self.oCreds.applyDefaultsIfNotSet(self.oTestVm); + + if not fRc: + reporter.log('Error setting up Guest Control testing environment!'); + + return fRc; + + def uploadLogData(self, oTstDrv, aData, sFileName, sDesc): + """ + Uploads (binary) data to a log file for manual (later) inspection. + """ + reporter.log('Creating + uploading log data file "%s"' % sFileName); + sHstFileName = os.path.join(oTstDrv.sScratchPath, sFileName); + try: + with open(sHstFileName, "wb") as oCurTestFile: + oCurTestFile.write(aData); + except: + return reporter.error('Unable to create temporary file for "%s"' % (sDesc,)); + return reporter.addLogFile(sHstFileName, 'misc/other', sDesc); + + def createSession(self, sName, fIsError = True): + """ + Creates (opens) a guest session. + Returns (True, IGuestSession) on success or (False, None) on failure. + """ + if self.oGuestSession is None: + if sName is None: + sName = "<untitled>"; + + reporter.log('Creating session "%s" ...' % (sName,)); + try: + self.oGuestSession = self.oGuest.createSession(self.oCreds.sUser, + self.oCreds.sPassword, + self.oCreds.sDomain, + sName); + except: + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErrXcpt(fIsError, 'Creating a guest session "%s" failed; sUser="%s", pw="%s", sDomain="%s":' + % (sName, self.oCreds.sUser, self.oCreds.sPassword, self.oCreds.sDomain)); + return (False, None); + + tsStartMs = base.timestampMilli(); + while base.timestampMilli() - tsStartMs < self.timeoutMS: + reporter.log('Waiting for session "%s" to start within %dms...' % (sName, self.timeoutMS)); + aeWaitFor = [ vboxcon.GuestSessionWaitForFlag_Start, ]; + try: + waitResult = self.oGuestSession.waitForArray(aeWaitFor, self.timeoutMS); + + # Log session status changes. + if waitResult is vboxcon.GuestSessionWaitResult_Status: + reporter.log('Session "%s" indicated status change (status is now %d)' \ + % (sName, self.oGuestSession.status)); + if self.oGuestSession.status is vboxcon.GuestSessionStatus_Started: + # We indicate an error here, as we intentionally waited for the session start + # in the wait call above and got back a status change instead. + reporter.error('Session "%s" successfully started (thru status change)' % (sName,)); + break; + continue; # Continue waiting for the session to start. + + # + # Be nice to Guest Additions < 4.3: They don't support session handling and + # therefore return WaitFlagNotSupported. + # + if waitResult not in (vboxcon.GuestSessionWaitResult_Start, \ + vboxcon.GuestSessionWaitResult_WaitFlagNotSupported): + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErr(fIsError, 'Session did not start successfully, returned wait result: %d' \ + % (waitResult,)); + return (False, None); + reporter.log('Session "%s" successfully started' % (sName,)); + + # + # Make sure that the test VM configuration and Guest Control use the same path separator style for the guest. + # + sGstSep = '\\' if self.oGuestSession.pathStyle is vboxcon.PathStyle_DOS else '/'; + if self.oTestVm.pathSep() != sGstSep: + reporter.error('Configured test VM uses a different path style (%s) than Guest Control (%s)' \ + % (self.oTestVm.pathSep(), sGstSep)); + break; + except: + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErrXcpt(fIsError, 'Waiting for guest session "%s" (usr=%s;pw=%s;dom=%s) to start failed:' + % (sName, self.oCreds.sUser, self.oCreds.sPassword, self.oCreds.sDomain,)); + return (False, None); + else: + reporter.log('Warning: Session already set; this is probably not what you want'); + return (True, self.oGuestSession); + + def setSession(self, oGuestSession): + """ + Sets the current guest session and closes + an old one if necessary. + """ + if self.oGuestSession is not None: + self.closeSession(); + self.oGuestSession = oGuestSession; + return self.oGuestSession; + + def closeSession(self, fIsError = True): + """ + Closes the guest session. + """ + if self.oGuestSession is not None: + try: + sName = self.oGuestSession.name; + except: + return reporter.errorXcpt(); + + reporter.log('Closing session "%s" ...' % (sName,)); + try: + self.oGuestSession.close(); + self.oGuestSession = None; + except: + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErrXcpt(fIsError, 'Closing guest session "%s" failed:' % (sName,)); + return False; + return True; + +class tdTestCopyFrom(tdTestGuestCtrlBase): + """ + Test for copying files from the guest to the host. + """ + def __init__(self, sSrc = "", sDst = "", oCreds = None, afFlags = None, oSrc = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sSrc = sSrc; + self.sDst = sDst; + self.afFlags = afFlags; + self.oSrc = oSrc # type: testfileset.TestFsObj + if oSrc and not sSrc: + self.sSrc = oSrc.sPath; + +class tdTestCopyFromDir(tdTestCopyFrom): + + def __init__(self, sSrc = "", sDst = "", oCreds = None, afFlags = None, oSrc = None, fIntoDst = False): + tdTestCopyFrom.__init__(self, sSrc, sDst, oCreds, afFlags, oSrc); + self.fIntoDst = fIntoDst; # hint to the verification code that sDst == oSrc, rather than sDst+oSrc.sNAme == oSrc. + +class tdTestCopyFromFile(tdTestCopyFrom): + pass; + +class tdTestRemoveHostDir(object): + """ + Test step that removes a host directory tree. + """ + def __init__(self, sDir): + self.sDir = sDir; + + def execute(self, oTstDrv, oVmSession, oTxsSession, oTestVm, sMsgPrefix): + _ = oTstDrv; _ = oVmSession; _ = oTxsSession; _ = oTestVm; _ = sMsgPrefix; + if os.path.exists(self.sDir): + if base.wipeDirectory(self.sDir) != 0: + return False; + try: + os.rmdir(self.sDir); + except: + return reporter.errorXcpt('%s: sDir=%s' % (sMsgPrefix, self.sDir,)); + return True; + + + +class tdTestCopyTo(tdTestGuestCtrlBase): + """ + Test for copying files from the host to the guest. + """ + def __init__(self, sSrc = "", sDst = "", oCreds = None, afFlags = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sSrc = sSrc; + self.sDst = sDst; + self.afFlags = afFlags; + +class tdTestCopyToFile(tdTestCopyTo): + pass; + +class tdTestCopyToDir(tdTestCopyTo): + pass; + +class tdTestDirCreate(tdTestGuestCtrlBase): + """ + Test for directoryCreate call. + """ + def __init__(self, sDirectory = "", oCreds = None, fMode = 0, afFlags = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sDirectory = sDirectory; + self.fMode = fMode; + self.afFlags = afFlags; + +class tdTestDirCreateTemp(tdTestGuestCtrlBase): + """ + Test for the directoryCreateTemp call. + """ + def __init__(self, sDirectory = "", sTemplate = "", oCreds = None, fMode = 0, fSecure = False): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sDirectory = sDirectory; + self.sTemplate = sTemplate; + self.fMode = fMode; + self.fSecure = fSecure; + +class tdTestDirOpen(tdTestGuestCtrlBase): + """ + Test for the directoryOpen call. + """ + def __init__(self, sDirectory = "", oCreds = None, sFilter = "", afFlags = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sDirectory = sDirectory; + self.sFilter = sFilter; + self.afFlags = afFlags or []; + +class tdTestDirRead(tdTestDirOpen): + """ + Test for the opening, reading and closing a certain directory. + """ + def __init__(self, sDirectory = "", oCreds = None, sFilter = "", afFlags = None): + tdTestDirOpen.__init__(self, sDirectory, oCreds, sFilter, afFlags); + +class tdTestExec(tdTestGuestCtrlBase): + """ + Specifies exactly one guest control execution test. + Has a default timeout of 5 minutes (for safety). + """ + def __init__(self, sCmd = "", asArgs = None, aEnv = None, afFlags = None, # pylint: disable=too-many-arguments + timeoutMS = 5 * 60 * 1000, oCreds = None, fWaitForExit = True): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sCmd = sCmd; + self.asArgs = asArgs if asArgs is not None else [sCmd,]; + self.aEnv = aEnv; + self.afFlags = afFlags or []; + self.timeoutMS = timeoutMS; + self.fWaitForExit = fWaitForExit; + self.uExitStatus = 0; + self.iExitCode = 0; + self.cbStdOut = 0; + self.cbStdErr = 0; + self.sBuf = ''; + +class tdTestFileExists(tdTestGuestCtrlBase): + """ + Test for the file exists API call (fileExists). + """ + def __init__(self, sFile = "", oCreds = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sFile = sFile; + +class tdTestFileRemove(tdTestGuestCtrlBase): + """ + Test querying guest file information. + """ + def __init__(self, sFile = "", oCreds = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sFile = sFile; + +class tdTestRemoveBase(tdTestGuestCtrlBase): + """ + Removal base. + """ + def __init__(self, sPath, fRcExpect = True, oCreds = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sPath = sPath; + self.fRcExpect = fRcExpect; + + def execute(self, oSubTstDrv): + """ + Executes the test, returns True/False. + """ + _ = oSubTstDrv; + return True; + + def checkRemoved(self, sType): + """ Check that the object was removed using fObjExists. """ + try: + fExists = self.oGuestSession.fsObjExists(self.sPath, False); + except: + return reporter.errorXcpt('fsObjExists failed on "%s" after deletion (type: %s)' % (self.sPath, sType)); + if fExists: + return reporter.error('fsObjExists says "%s" still exists after deletion (type: %s)!' % (self.sPath, sType)); + return True; + +class tdTestRemoveFile(tdTestRemoveBase): + """ + Remove a single file. + """ + def __init__(self, sPath, fRcExpect = True, oCreds = None): + tdTestRemoveBase.__init__(self, sPath, fRcExpect, oCreds); + + def execute(self, oSubTstDrv): + reporter.log2('Deleting file "%s" ...' % (limitString(self.sPath),)); + try: + if oSubTstDrv.oTstDrv.fpApiVer >= 5.0: + self.oGuestSession.fsObjRemove(self.sPath); + else: + self.oGuestSession.fileRemove(self.sPath); + except: + reporter.maybeErrXcpt(self.fRcExpect, 'Removing "%s" failed' % (self.sPath,)); + return not self.fRcExpect; + if not self.fRcExpect: + return reporter.error('Expected removing "%s" to failed, but it succeeded' % (self.sPath,)); + + return self.checkRemoved('file'); + +class tdTestRemoveDir(tdTestRemoveBase): + """ + Remove a single directory if empty. + """ + def __init__(self, sPath, fRcExpect = True, oCreds = None): + tdTestRemoveBase.__init__(self, sPath, fRcExpect, oCreds); + + def execute(self, oSubTstDrv): + _ = oSubTstDrv; + reporter.log2('Deleting directory "%s" ...' % (limitString(self.sPath),)); + try: + self.oGuestSession.directoryRemove(self.sPath); + except: + reporter.maybeErrXcpt(self.fRcExpect, 'Removing "%s" (as a directory) failed' % (self.sPath,)); + return not self.fRcExpect; + if not self.fRcExpect: + return reporter.error('Expected removing "%s" (dir) to failed, but it succeeded' % (self.sPath,)); + + return self.checkRemoved('directory'); + +class tdTestRemoveTree(tdTestRemoveBase): + """ + Recursively remove a directory tree. + """ + def __init__(self, sPath, afFlags = None, fRcExpect = True, fNotExist = False, oCreds = None): + tdTestRemoveBase.__init__(self, sPath, fRcExpect, oCreds = None); + self.afFlags = afFlags if afFlags is not None else []; + self.fNotExist = fNotExist; # Hack for the ContentOnly scenario where the dir does not exist. + + def execute(self, oSubTstDrv): + reporter.log2('Deleting tree "%s" ...' % (limitString(self.sPath),)); + try: + oProgress = self.oGuestSession.directoryRemoveRecursive(self.sPath, self.afFlags); + except: + reporter.maybeErrXcpt(self.fRcExpect, 'Removing directory tree "%s" failed (afFlags=%s)' + % (self.sPath, self.afFlags)); + return not self.fRcExpect; + + oWrappedProgress = vboxwrappers.ProgressWrapper(oProgress, oSubTstDrv.oTstDrv.oVBoxMgr, oSubTstDrv.oTstDrv, + "remove-tree: %s" % (self.sPath,)); + oWrappedProgress.wait(); + if not oWrappedProgress.isSuccess(): + oWrappedProgress.logResult(fIgnoreErrors = not self.fRcExpect); + return not self.fRcExpect; + if not self.fRcExpect: + return reporter.error('Expected removing "%s" (tree) to failed, but it succeeded' % (self.sPath,)); + + if vboxcon.DirectoryRemoveRecFlag_ContentAndDir not in self.afFlags and not self.fNotExist: + # Cannot use directoryExists here as it is buggy. + try: + if oSubTstDrv.oTstDrv.fpApiVer >= 5.0: + oFsObjInfo = self.oGuestSession.fsObjQueryInfo(self.sPath, False); + else: + oFsObjInfo = self.oGuestSession.fileQueryInfo(self.sPath); + eType = oFsObjInfo.type; + except: + return reporter.errorXcpt('sPath=%s' % (self.sPath,)); + if eType != vboxcon.FsObjType_Directory: + return reporter.error('Found file type %d, expected directory (%d) for %s after rmtree/OnlyContent' + % (eType, vboxcon.FsObjType_Directory, self.sPath,)); + return True; + + return self.checkRemoved('tree'); + + +class tdTestFileStat(tdTestGuestCtrlBase): + """ + Test querying guest file information. + """ + def __init__(self, sFile = "", oCreds = None, cbSize = 0, eFileType = 0): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sFile = sFile; + self.cbSize = cbSize; + self.eFileType = eFileType; + +class tdTestFileIO(tdTestGuestCtrlBase): + """ + Test for the IGuestFile object. + """ + def __init__(self, sFile = "", oCreds = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sFile = sFile; + +class tdTestFileQuerySize(tdTestGuestCtrlBase): + """ + Test for the file size query API call (fileQuerySize). + """ + def __init__(self, sFile = "", oCreds = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sFile = sFile; + +class tdTestFileOpen(tdTestGuestCtrlBase): + """ + Tests opening a guest files. + """ + def __init__(self, sFile = "", eAccessMode = None, eAction = None, eSharing = None, + fCreationMode = 0o660, oCreds = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sFile = sFile; + self.eAccessMode = eAccessMode if eAccessMode is not None else vboxcon.FileAccessMode_ReadOnly; + self.eAction = eAction if eAction is not None else vboxcon.FileOpenAction_OpenExisting; + self.eSharing = eSharing if eSharing is not None else vboxcon.FileSharingMode_All; + self.fCreationMode = fCreationMode; + self.afOpenFlags = []; + self.oOpenedFile = None; + + def toString(self): + """ Get a summary string. """ + return 'eAccessMode=%s eAction=%s sFile=%s' % (self.eAccessMode, self.eAction, self.sFile); + + def doOpenStep(self, fExpectSuccess): + """ + Does the open step, putting the resulting file in oOpenedFile. + """ + try: + self.oOpenedFile = self.oGuestSession.fileOpenEx(self.sFile, self.eAccessMode, self.eAction, + self.eSharing, self.fCreationMode, self.afOpenFlags); + except: + reporter.maybeErrXcpt(fExpectSuccess, 'fileOpenEx(%s, %s, %s, %s, %s, %s)' + % (self.sFile, self.eAccessMode, self.eAction, self.eSharing, + self.fCreationMode, self.afOpenFlags,)); + return False; + return True; + + def doStepsOnOpenedFile(self, fExpectSuccess, oSubTst): + """ Overridden by children to do more testing. """ + _ = fExpectSuccess; _ = oSubTst; + return True; + + def doCloseStep(self): + """ Closes the file. """ + if self.oOpenedFile: + try: + self.oOpenedFile.close(); + except: + return reporter.errorXcpt('close([%s, %s, %s, %s, %s, %s])' + % (self.sFile, self.eAccessMode, self.eAction, self.eSharing, + self.fCreationMode, self.afOpenFlags,)); + self.oOpenedFile = None; + return True; + + def doSteps(self, fExpectSuccess, oSubTst): + """ Do the tests. """ + fRc = self.doOpenStep(fExpectSuccess); + if fRc is True: + fRc = self.doStepsOnOpenedFile(fExpectSuccess, oSubTst); + if self.oOpenedFile: + fRc = self.doCloseStep() and fRc; + return fRc; + + +class tdTestFileOpenCheckSize(tdTestFileOpen): + """ + Opens a file and checks the size. + """ + def __init__(self, sFile = "", eAccessMode = None, eAction = None, eSharing = None, + fCreationMode = 0o660, cbOpenExpected = 0, oCreds = None): + tdTestFileOpen.__init__(self, sFile, eAccessMode, eAction, eSharing, fCreationMode, oCreds); + self.cbOpenExpected = cbOpenExpected; + + def toString(self): + return 'cbOpenExpected=%s %s' % (self.cbOpenExpected, tdTestFileOpen.toString(self),); + + def doStepsOnOpenedFile(self, fExpectSuccess, oSubTst): + # + # Call parent. + # + fRc = tdTestFileOpen.doStepsOnOpenedFile(self, fExpectSuccess, oSubTst); + + # + # Check the size. Requires 6.0 or later (E_NOTIMPL in 5.2). + # + if oSubTst.oTstDrv.fpApiVer >= 6.0: + try: + oFsObjInfo = self.oOpenedFile.queryInfo(); + except: + return reporter.errorXcpt('queryInfo([%s, %s, %s, %s, %s, %s])' + % (self.sFile, self.eAccessMode, self.eAction, self.eSharing, + self.fCreationMode, self.afOpenFlags,)); + if oFsObjInfo is None: + return reporter.error('IGuestFile::queryInfo returned None'); + try: + cbFile = oFsObjInfo.objectSize; + except: + return reporter.errorXcpt(); + if cbFile != self.cbOpenExpected: + return reporter.error('Wrong file size after open (%d): %s, expected %s (file %s) (#1)' + % (self.eAction, cbFile, self.cbOpenExpected, self.sFile)); + + try: + cbFile = self.oOpenedFile.querySize(); + except: + return reporter.errorXcpt('querySize([%s, %s, %s, %s, %s, %s])' + % (self.sFile, self.eAccessMode, self.eAction, self.eSharing, + self.fCreationMode, self.afOpenFlags,)); + if cbFile != self.cbOpenExpected: + return reporter.error('Wrong file size after open (%d): %s, expected %s (file %s) (#2)' + % (self.eAction, cbFile, self.cbOpenExpected, self.sFile)); + + return fRc; + + +class tdTestFileOpenAndWrite(tdTestFileOpen): + """ + Opens the file and writes one or more chunks to it. + + The chunks are a list of tuples(offset, bytes), where offset can be None + if no seeking should be performed. + """ + def __init__(self, sFile = "", eAccessMode = None, eAction = None, eSharing = None, # pylint: disable=too-many-arguments + fCreationMode = 0o660, atChunks = None, fUseAtApi = False, abContent = None, oCreds = None): + tdTestFileOpen.__init__(self, sFile, eAccessMode if eAccessMode is not None else vboxcon.FileAccessMode_WriteOnly, + eAction, eSharing, fCreationMode, oCreds); + assert atChunks is not None; + self.atChunks = atChunks # type: list(tuple(int,bytearray)) + self.fUseAtApi = fUseAtApi; + self.fAppend = ( eAccessMode in (vboxcon.FileAccessMode_AppendOnly, vboxcon.FileAccessMode_AppendRead) + or eAction == vboxcon.FileOpenAction_AppendOrCreate); + self.abContent = abContent # type: bytearray + + def toString(self): + sChunks = ', '.join('%s LB %s' % (tChunk[0], len(tChunk[1]),) for tChunk in self.atChunks); + sApi = 'writeAt' if self.fUseAtApi else 'write'; + return '%s [%s] %s' % (sApi, sChunks, tdTestFileOpen.toString(self),); + + def doStepsOnOpenedFile(self, fExpectSuccess, oSubTst): + # + # Call parent. + # + fRc = tdTestFileOpen.doStepsOnOpenedFile(self, fExpectSuccess, oSubTst); + + # + # Do the writing. + # + for offFile, abBuf in self.atChunks: + if self.fUseAtApi: + # + # writeAt: + # + assert offFile is not None; + reporter.log2('writeAt(%s, %s bytes)' % (offFile, len(abBuf),)); + if self.fAppend: + if self.abContent is not None: # Try avoid seek as it updates the cached offset in GuestFileImpl. + offExpectAfter = len(self.abContent); + else: + try: + offSave = self.oOpenedFile.seek(0, vboxcon.FileSeekOrigin_Current); + offExpectAfter = self.oOpenedFile.seek(0, vboxcon.FileSeekOrigin_End); + self.oOpenedFile.seek(offSave, vboxcon.FileSeekOrigin_Begin); + except: + return reporter.errorXcpt(); + offExpectAfter += len(abBuf); + else: + offExpectAfter = offFile + len(abBuf); + + try: + cbWritten = self.oOpenedFile.writeAt(offFile, abBuf, 30*1000); + except: + return reporter.errorXcpt('writeAt(%s, %s bytes)' % (offFile, len(abBuf),)); + + else: + # + # write: + # + if self.fAppend: + if self.abContent is not None: # Try avoid seek as it updates the cached offset in GuestFileImpl. + offExpectAfter = len(self.abContent); + else: + try: + offSave = self.oOpenedFile.seek(0, vboxcon.FileSeekOrigin_Current); + offExpectAfter = self.oOpenedFile.seek(0, vboxcon.FileSeekOrigin_End); + self.oOpenedFile.seek(offSave, vboxcon.FileSeekOrigin_Begin); + except: + return reporter.errorXcpt('seek(0,End)'); + if offFile is not None: + try: + self.oOpenedFile.seek(offFile, vboxcon.FileSeekOrigin_Begin); + except: + return reporter.errorXcpt('seek(%s,Begin)' % (offFile,)); + else: + try: + offFile = self.oOpenedFile.seek(0, vboxcon.FileSeekOrigin_Current); + except: + return reporter.errorXcpt(); + if not self.fAppend: + offExpectAfter = offFile; + offExpectAfter += len(abBuf); + + reporter.log2('write(%s bytes @ %s)' % (len(abBuf), offFile,)); + try: + cbWritten = self.oOpenedFile.write(abBuf, 30*1000); + except: + return reporter.errorXcpt('write(%s bytes @ %s)' % (len(abBuf), offFile)); + + # + # Check how much was written, ASSUMING nothing we push thru here is too big: + # + if cbWritten != len(abBuf): + fRc = reporter.errorXcpt('Wrote less than expected: %s out of %s, expected all to be written' + % (cbWritten, len(abBuf),)); + if not self.fAppend: + offExpectAfter -= len(abBuf) - cbWritten; + + # + # Update the file content tracker if we've got one and can: + # + if self.abContent is not None: + if cbWritten < len(abBuf): + abBuf = abBuf[:cbWritten]; + + # + # In append mode, the current file offset shall be disregarded and the + # write always goes to the end of the file, regardless of writeAt or write. + # Note that RTFileWriteAt only naturally behaves this way on linux and + # (probably) windows, so VBoxService makes that behaviour generic across + # all OSes. + # + if self.fAppend: + reporter.log2('len(self.abContent)=%s + %s' % (len(self.abContent), cbWritten, )); + self.abContent.extend(abBuf); + else: + if offFile is None: + offFile = offExpectAfter - cbWritten; + reporter.log2('len(self.abContent)=%s + %s @ %s' % (len(self.abContent), cbWritten, offFile, )); + if offFile > len(self.abContent): + self.abContent.extend(bytearray(offFile - len(self.abContent))); + self.abContent[offFile:offFile + cbWritten] = abBuf; + reporter.log2('len(self.abContent)=%s' % (len(self.abContent),)); + + # + # Check the resulting file offset with IGuestFile::offset. + # + try: + offApi = self.oOpenedFile.offset; # Must be gotten first! + offSeek = self.oOpenedFile.seek(0, vboxcon.FileSeekOrigin_Current); + except: + fRc = reporter.errorXcpt(); + else: + reporter.log2('offApi=%s offSeek=%s offExpectAfter=%s' % (offApi, offSeek, offExpectAfter,)); + if offSeek != offExpectAfter: + fRc = reporter.error('Seek offset is %s, expected %s after %s bytes write @ %s (offApi=%s)' + % (offSeek, offExpectAfter, len(abBuf), offFile, offApi,)); + if offApi != offExpectAfter: + fRc = reporter.error('IGuestFile::offset is %s, expected %s after %s bytes write @ %s (offSeek=%s)' + % (offApi, offExpectAfter, len(abBuf), offFile, offSeek,)); + # for each chunk - end + return fRc; + + +class tdTestFileOpenAndCheckContent(tdTestFileOpen): + """ + Opens the file and checks the content using the read API. + """ + def __init__(self, sFile = "", eSharing = None, abContent = None, cbContentExpected = None, oCreds = None): + tdTestFileOpen.__init__(self, sFile = sFile, eSharing = eSharing, oCreds = oCreds); + self.abContent = abContent # type: bytearray + self.cbContentExpected = cbContentExpected; + + def toString(self): + return 'check content %s (%s) %s' % (len(self.abContent), self.cbContentExpected, tdTestFileOpen.toString(self),); + + def doStepsOnOpenedFile(self, fExpectSuccess, oSubTst): + # + # Call parent. + # + fRc = tdTestFileOpen.doStepsOnOpenedFile(self, fExpectSuccess, oSubTst); + + # + # Check the expected content size. + # + if self.cbContentExpected is not None: + if len(self.abContent) != self.cbContentExpected: + fRc = reporter.error('Incorrect abContent size: %s, expected %s' + % (len(self.abContent), self.cbContentExpected,)); + + # + # Read the file and compare it with the content. + # + offFile = 0; + while True: + try: + abChunk = self.oOpenedFile.read(512*1024, 30*1000); + except: + return reporter.errorXcpt('read(512KB) @ %s' % (offFile,)); + cbChunk = len(abChunk); + if cbChunk == 0: + if offFile != len(self.abContent): + fRc = reporter.error('Unexpected EOF @ %s, len(abContent)=%s' % (offFile, len(self.abContent),)); + break; + if offFile + cbChunk > len(self.abContent): + fRc = reporter.error('File is larger than expected: at least %s bytes, expected %s bytes' + % (offFile + cbChunk, len(self.abContent),)); + elif not utils.areBytesEqual(abChunk, self.abContent[offFile:(offFile + cbChunk)]): + fRc = reporter.error('Mismatch in range %s LB %s!' % (offFile, cbChunk,)); + offFile += cbChunk; + + return fRc; + + +class tdTestSession(tdTestGuestCtrlBase): + """ + Test the guest session handling. + """ + def __init__(self, sUser = None, sPassword = None, sDomain = None, sSessionName = ""): + tdTestGuestCtrlBase.__init__(self, oCreds = tdCtxCreds(sUser, sPassword, sDomain)); + self.sSessionName = sSessionName; + + def getSessionCount(self, oVBoxMgr): + """ + Helper for returning the number of currently + opened guest sessions of a VM. + """ + if self.oGuest is None: + return 0; + try: + aoSession = oVBoxMgr.getArray(self.oGuest, 'sessions') + except: + reporter.errorXcpt('sSessionName: %s' % (self.sSessionName,)); + return 0; + return len(aoSession); + + +class tdTestSessionEx(tdTestGuestCtrlBase): + """ + Test the guest session. + """ + def __init__(self, aoSteps = None, enmUser = None): + tdTestGuestCtrlBase.__init__(self); + assert enmUser is None; # For later. + self.enmUser = enmUser; + self.aoSteps = aoSteps if aoSteps is not None else []; + + def execute(self, oTstDrv, oVmSession, oTxsSession, oTestVm, sMsgPrefix): + """ + Executes the test. + """ + # + # Create a session. + # + assert self.enmUser is None; # For later. + self.oCreds = tdCtxCreds(); + fRc = self.setEnvironment(oVmSession, oTxsSession, oTestVm); + if not fRc: + return False; + reporter.log2('%s: %s steps' % (sMsgPrefix, len(self.aoSteps),)); + fRc, oCurSession = self.createSession(sMsgPrefix); + if fRc is True: + # + # Execute the tests. + # + try: + fRc = self.executeSteps(oTstDrv, oCurSession, sMsgPrefix); + except: + fRc = reporter.errorXcpt('%s: Unexpected exception executing test steps' % (sMsgPrefix,)); + + # + # Close the session. + # + fRc2 = self.closeSession(); + if fRc2 is False: + fRc = reporter.error('%s: Session could not be closed' % (sMsgPrefix,)); + else: + fRc = reporter.error('%s: Session creation failed' % (sMsgPrefix,)); + return fRc; + + def executeSteps(self, oTstDrv, oGstCtrlSession, sMsgPrefix): + """ + Executes just the steps. + Returns True on success, False on test failure. + """ + fRc = True; + for (i, oStep) in enumerate(self.aoSteps): + fRc2 = oStep.execute(oTstDrv, oGstCtrlSession, sMsgPrefix + ', step #%d' % i); + if fRc2 is True: + pass; + elif fRc2 is None: + reporter.log('%s: skipping remaining %d steps' % (sMsgPrefix, len(self.aoSteps) - i - 1,)); + break; + else: + fRc = False; + return fRc; + + @staticmethod + def executeListTestSessions(aoTests, oTstDrv, oVmSession, oTxsSession, oTestVm, sMsgPrefix): + """ + Works thru a list of tdTestSessionEx object. + """ + fRc = True; + for (i, oCurTest) in enumerate(aoTests): + try: + fRc2 = oCurTest.execute(oTstDrv, oVmSession, oTxsSession, oTestVm, '%s / %#d' % (sMsgPrefix, i,)); + if fRc2 is not True: + fRc = False; + except: + fRc = reporter.errorXcpt('%s: Unexpected exception executing test #%d' % (sMsgPrefix, i ,)); + + return (fRc, oTxsSession); + + +class tdSessionStepBase(object): + """ + Base class for the guest control session test steps. + """ + + def execute(self, oTstDrv, oGstCtrlSession, sMsgPrefix): + """ + Executes the test step. + + Returns True on success. + Returns False on failure (must be reported as error). + Returns None if to skip the remaining steps. + """ + _ = oTstDrv; + _ = oGstCtrlSession; + return reporter.error('%s: Missing execute implementation: %s' % (sMsgPrefix, self,)); + + +class tdStepRequireMinimumApiVer(tdSessionStepBase): + """ + Special test step which will cause executeSteps to skip the remaining step + if the VBox API is too old: + """ + def __init__(self, fpMinApiVer): + self.fpMinApiVer = fpMinApiVer; + + def execute(self, oTstDrv, oGstCtrlSession, sMsgPrefix): + """ Returns None if API version is too old, otherwise True. """ + if oTstDrv.fpApiVer >= self.fpMinApiVer: + return True; + _ = oGstCtrlSession; + _ = sMsgPrefix; + return None; # Special return value. Don't use elsewhere. + + +# +# Scheduling Environment Changes with the Guest Control Session. +# + +class tdStepSessionSetEnv(tdSessionStepBase): + """ + Guest session environment: schedule putenv + """ + def __init__(self, sVar, sValue, hrcExpected = 0): + self.sVar = sVar; + self.sValue = sValue; + self.hrcExpected = hrcExpected; + + def execute(self, oTstDrv, oGstCtrlSession, sMsgPrefix): + """ + Executes the step. + Returns True on success, False on test failure. + """ + reporter.log2('tdStepSessionSetEnv: sVar=%s sValue=%s hrcExpected=%#x' % (self.sVar, self.sValue, self.hrcExpected,)); + try: + if oTstDrv.fpApiVer >= 5.0: + oGstCtrlSession.environmentScheduleSet(self.sVar, self.sValue); + else: + oGstCtrlSession.environmentSet(self.sVar, self.sValue); + except vbox.ComException as oXcpt: + # Is this an expected failure? + if vbox.ComError.equal(oXcpt, self.hrcExpected): + return True; + return reporter.errorXcpt('%s: Expected hrc=%#x (%s) got %#x (%s) instead (setenv %s=%s)' + % (sMsgPrefix, self.hrcExpected, vbox.ComError.toString(self.hrcExpected), + vbox.ComError.getXcptResult(oXcpt), + vbox.ComError.toString(vbox.ComError.getXcptResult(oXcpt)), + self.sVar, self.sValue,)); + except: + return reporter.errorXcpt('%s: Unexpected exception in tdStepSessionSetEnv::execute (%s=%s)' + % (sMsgPrefix, self.sVar, self.sValue,)); + + # Should we succeed? + if self.hrcExpected != 0: + return reporter.error('%s: Expected hrcExpected=%#x, got S_OK (putenv %s=%s)' + % (sMsgPrefix, self.hrcExpected, self.sVar, self.sValue,)); + return True; + +class tdStepSessionUnsetEnv(tdSessionStepBase): + """ + Guest session environment: schedule unset. + """ + def __init__(self, sVar, hrcExpected = 0): + self.sVar = sVar; + self.hrcExpected = hrcExpected; + + def execute(self, oTstDrv, oGstCtrlSession, sMsgPrefix): + """ + Executes the step. + Returns True on success, False on test failure. + """ + reporter.log2('tdStepSessionUnsetEnv: sVar=%s hrcExpected=%#x' % (self.sVar, self.hrcExpected,)); + try: + if oTstDrv.fpApiVer >= 5.0: + oGstCtrlSession.environmentScheduleUnset(self.sVar); + else: + oGstCtrlSession.environmentUnset(self.sVar); + except vbox.ComException as oXcpt: + # Is this an expected failure? + if vbox.ComError.equal(oXcpt, self.hrcExpected): + return True; + return reporter.errorXcpt('%s: Expected hrc=%#x (%s) got %#x (%s) instead (unsetenv %s)' + % (sMsgPrefix, self.hrcExpected, vbox.ComError.toString(self.hrcExpected), + vbox.ComError.getXcptResult(oXcpt), + vbox.ComError.toString(vbox.ComError.getXcptResult(oXcpt)), + self.sVar,)); + except: + return reporter.errorXcpt('%s: Unexpected exception in tdStepSessionUnsetEnv::execute (%s)' + % (sMsgPrefix, self.sVar,)); + + # Should we succeed? + if self.hrcExpected != 0: + return reporter.error('%s: Expected hrcExpected=%#x, got S_OK (unsetenv %s)' + % (sMsgPrefix, self.hrcExpected, self.sVar,)); + return True; + +class tdStepSessionBulkEnv(tdSessionStepBase): + """ + Guest session environment: Bulk environment changes. + """ + def __init__(self, asEnv = None, hrcExpected = 0): + self.asEnv = asEnv if asEnv is not None else []; + self.hrcExpected = hrcExpected; + + def execute(self, oTstDrv, oGstCtrlSession, sMsgPrefix): + """ + Executes the step. + Returns True on success, False on test failure. + """ + reporter.log2('tdStepSessionBulkEnv: asEnv=%s hrcExpected=%#x' % (self.asEnv, self.hrcExpected,)); + try: + if oTstDrv.fpApiVer >= 5.0: + oTstDrv.oVBoxMgr.setArray(oGstCtrlSession, 'environmentChanges', self.asEnv); + else: + oTstDrv.oVBoxMgr.setArray(oGstCtrlSession, 'environment', self.asEnv); + except vbox.ComException as oXcpt: + # Is this an expected failure? + if vbox.ComError.equal(oXcpt, self.hrcExpected): + return True; + return reporter.errorXcpt('%s: Expected hrc=%#x (%s) got %#x (%s) instead (asEnv=%s)' + % (sMsgPrefix, self.hrcExpected, vbox.ComError.toString(self.hrcExpected), + vbox.ComError.getXcptResult(oXcpt), + vbox.ComError.toString(vbox.ComError.getXcptResult(oXcpt)), + self.asEnv,)); + except: + return reporter.errorXcpt('%s: Unexpected exception writing the environmentChanges property (asEnv=%s).' + % (sMsgPrefix, self.asEnv)); + return True; + +class tdStepSessionClearEnv(tdStepSessionBulkEnv): + """ + Guest session environment: clears the scheduled environment changes. + """ + def __init__(self): + tdStepSessionBulkEnv.__init__(self); + + +class tdStepSessionCheckEnv(tdSessionStepBase): + """ + Check the currently scheduled environment changes of a guest control session. + """ + def __init__(self, asEnv = None): + self.asEnv = asEnv if asEnv is not None else []; + + def execute(self, oTstDrv, oGstCtrlSession, sMsgPrefix): + """ + Executes the step. + Returns True on success, False on test failure. + """ + reporter.log2('tdStepSessionCheckEnv: asEnv=%s' % (self.asEnv,)); + + # + # Get the environment change list. + # + try: + if oTstDrv.fpApiVer >= 5.0: + asCurEnv = oTstDrv.oVBoxMgr.getArray(oGstCtrlSession, 'environmentChanges'); + else: + asCurEnv = oTstDrv.oVBoxMgr.getArray(oGstCtrlSession, 'environment'); + except: + return reporter.errorXcpt('%s: Unexpected exception reading the environmentChanges property.' % (sMsgPrefix,)); + + # + # Compare it with the expected one by trying to remove each expected value + # and the list anything unexpected. + # + fRc = True; + asCopy = list(asCurEnv); # just in case asCurEnv is immutable + for sExpected in self.asEnv: + try: + asCopy.remove(sExpected); + except: + fRc = reporter.error('%s: Expected "%s" to be in the resulting environment' % (sMsgPrefix, sExpected,)); + for sUnexpected in asCopy: + fRc = reporter.error('%s: Unexpected "%s" in the resulting environment' % (sMsgPrefix, sUnexpected,)); + + if fRc is not True: + reporter.log2('%s: Current environment: %s' % (sMsgPrefix, asCurEnv)); + return fRc; + + +# +# File system object statistics (i.e. stat()). +# + +class tdStepStat(tdSessionStepBase): + """ + Stats a file system object. + """ + def __init__(self, sPath, hrcExpected = 0, fFound = True, fFollowLinks = True, enmType = None, oTestFsObj = None): + self.sPath = sPath; + self.hrcExpected = hrcExpected; + self.fFound = fFound; + self.fFollowLinks = fFollowLinks; + self.enmType = enmType if enmType is not None else vboxcon.FsObjType_File; + self.cbExactSize = None; + self.cbMinSize = None; + self.oTestFsObj = oTestFsObj # type: testfileset.TestFsObj + + def execute(self, oTstDrv, oGstCtrlSession, sMsgPrefix): + """ + Execute the test step. + """ + reporter.log2('tdStepStat: sPath=%s enmType=%s hrcExpected=%s fFound=%s fFollowLinks=%s' + % (limitString(self.sPath), self.enmType, self.hrcExpected, self.fFound, self.fFollowLinks,)); + + # Don't execute non-file tests on older VBox version. + if oTstDrv.fpApiVer >= 5.0 or self.enmType == vboxcon.FsObjType_File or not self.fFound: + # + # Call the API. + # + try: + if oTstDrv.fpApiVer >= 5.0: + oFsInfo = oGstCtrlSession.fsObjQueryInfo(self.sPath, self.fFollowLinks); + else: + oFsInfo = oGstCtrlSession.fileQueryInfo(self.sPath); + except vbox.ComException as oXcpt: + ## @todo: The error reporting in the API just plain sucks! Most of the errors are + ## VBOX_E_IPRT_ERROR and there seems to be no way to distinguish between + ## non-existing files/path and a lot of other errors. Fix API and test! + if not self.fFound: + return True; + if vbox.ComError.equal(oXcpt, self.hrcExpected): # Is this an expected failure? + return True; + return reporter.errorXcpt('%s: Unexpected exception for exiting path "%s" (enmType=%s, hrcExpected=%s):' + % (sMsgPrefix, self.sPath, self.enmType, self.hrcExpected,)); + except: + return reporter.errorXcpt('%s: Unexpected exception in tdStepStat::execute (%s)' + % (sMsgPrefix, self.sPath,)); + if oFsInfo is None: + return reporter.error('%s: "%s" got None instead of IFsObjInfo instance!' % (sMsgPrefix, self.sPath,)); + + # + # Check type expectations. + # + try: + enmType = oFsInfo.type; + except: + return reporter.errorXcpt('%s: Unexpected exception in reading "IFsObjInfo::type"' % (sMsgPrefix,)); + if enmType != self.enmType: + return reporter.error('%s: "%s" has type %s, expected %s' + % (sMsgPrefix, self.sPath, enmType, self.enmType)); + + # + # Check size expectations. + # Note! This is unicode string here on windows, for some reason. + # long long mapping perhaps? + # + try: + cbObject = long(oFsInfo.objectSize); + except: + return reporter.errorXcpt('%s: Unexpected exception in reading "IFsObjInfo::objectSize"' + % (sMsgPrefix,)); + if self.cbExactSize is not None \ + and cbObject != self.cbExactSize: + return reporter.error('%s: "%s" has size %s bytes, expected %s bytes' + % (sMsgPrefix, self.sPath, cbObject, self.cbExactSize)); + if self.cbMinSize is not None \ + and cbObject < self.cbMinSize: + return reporter.error('%s: "%s" has size %s bytes, expected as least %s bytes' + % (sMsgPrefix, self.sPath, cbObject, self.cbMinSize)); + return True; + +class tdStepStatDir(tdStepStat): + """ Checks for an existing directory. """ + def __init__(self, sDirPath, oTestDir = None): + tdStepStat.__init__(self, sPath = sDirPath, enmType = vboxcon.FsObjType_Directory, oTestFsObj = oTestDir); + +class tdStepStatDirEx(tdStepStatDir): + """ Checks for an existing directory given a TestDir object. """ + def __init__(self, oTestDir): # type: (testfileset.TestDir) + tdStepStatDir.__init__(self, oTestDir.sPath, oTestDir); + +class tdStepStatFile(tdStepStat): + """ Checks for an existing file """ + def __init__(self, sFilePath = None, oTestFile = None): + tdStepStat.__init__(self, sPath = sFilePath, enmType = vboxcon.FsObjType_File, oTestFsObj = oTestFile); + +class tdStepStatFileEx(tdStepStatFile): + """ Checks for an existing file given a TestFile object. """ + def __init__(self, oTestFile): # type: (testfileset.TestFile) + tdStepStatFile.__init__(self, oTestFile.sPath, oTestFile); + +class tdStepStatFileSize(tdStepStat): + """ Checks for an existing file of a given expected size.. """ + def __init__(self, sFilePath, cbExactSize = 0): + tdStepStat.__init__(self, sPath = sFilePath, enmType = vboxcon.FsObjType_File); + self.cbExactSize = cbExactSize; + +class tdStepStatFileNotFound(tdStepStat): + """ Checks for an existing directory. """ + def __init__(self, sPath): + tdStepStat.__init__(self, sPath = sPath, fFound = False); + +class tdStepStatPathNotFound(tdStepStat): + """ Checks for an existing directory. """ + def __init__(self, sPath): + tdStepStat.__init__(self, sPath = sPath, fFound = False); + + +# +# +# + +class tdTestSessionFileRefs(tdTestGuestCtrlBase): + """ + Tests session file (IGuestFile) reference counting. + """ + def __init__(self, cRefs = 0): + tdTestGuestCtrlBase.__init__(self); + self.cRefs = cRefs; + +class tdTestSessionDirRefs(tdTestGuestCtrlBase): + """ + Tests session directory (IGuestDirectory) reference counting. + """ + def __init__(self, cRefs = 0): + tdTestGuestCtrlBase.__init__(self); + self.cRefs = cRefs; + +class tdTestSessionProcRefs(tdTestGuestCtrlBase): + """ + Tests session process (IGuestProcess) reference counting. + """ + def __init__(self, cRefs = 0): + tdTestGuestCtrlBase.__init__(self); + self.cRefs = cRefs; + +class tdTestUpdateAdditions(tdTestGuestCtrlBase): + """ + Test updating the Guest Additions inside the guest. + """ + def __init__(self, sSrc = "", asArgs = None, afFlags = None, oCreds = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sSrc = sSrc; + self.asArgs = asArgs; + self.afFlags = afFlags; + +class tdTestResult(object): + """ + Base class for test results. + """ + def __init__(self, fRc = False): + ## The overall test result. + self.fRc = fRc; + +class tdTestResultFailure(tdTestResult): + """ + Base class for test results. + """ + def __init__(self): + tdTestResult.__init__(self, fRc = False); + +class tdTestResultSuccess(tdTestResult): + """ + Base class for test results. + """ + def __init__(self): + tdTestResult.__init__(self, fRc = True); + +class tdTestResultDirRead(tdTestResult): + """ + Test result for reading guest directories. + """ + def __init__(self, fRc = False, cFiles = 0, cDirs = 0, cOthers = None): + tdTestResult.__init__(self, fRc = fRc); + self.cFiles = cFiles; + self.cDirs = cDirs; + self.cOthers = cOthers; + +class tdTestResultExec(tdTestResult): + """ + Holds a guest process execution test result, + including the exit code, status + afFlags. + """ + def __init__(self, fRc = False, uExitStatus = 500, iExitCode = 0, sBuf = None, cbBuf = 0, cbStdOut = None, cbStdErr = None): + tdTestResult.__init__(self); + ## The overall test result. + self.fRc = fRc; + ## Process exit stuff. + self.uExitStatus = uExitStatus; + self.iExitCode = iExitCode; + ## Desired buffer length returned back from stdout/stderr. + self.cbBuf = cbBuf; + ## Desired buffer result from stdout/stderr. Use with caution! + self.sBuf = sBuf; + self.cbStdOut = cbStdOut; + self.cbStdErr = cbStdErr; + +class tdTestResultFileStat(tdTestResult): + """ + Test result for stat'ing guest files. + """ + def __init__(self, fRc = False, + cbSize = 0, eFileType = 0): + tdTestResult.__init__(self, fRc = fRc); + self.cbSize = cbSize; + self.eFileType = eFileType; + ## @todo Add more information. + +class tdTestResultFileReadWrite(tdTestResult): + """ + Test result for reading + writing guest directories. + """ + def __init__(self, fRc = False, + cbProcessed = 0, offFile = 0, abBuf = None): + tdTestResult.__init__(self, fRc = fRc); + self.cbProcessed = cbProcessed; + self.offFile = offFile; + self.abBuf = abBuf; + +class tdTestResultSession(tdTestResult): + """ + Test result for guest session counts. + """ + def __init__(self, fRc = False, cNumSessions = 0): + tdTestResult.__init__(self, fRc = fRc); + self.cNumSessions = cNumSessions; + +class tdDebugSettings(object): + """ + Contains local test debug settings. + """ + def __init__(self, sVBoxServiceExeHst = None): + self.sVBoxServiceExeHst = sVBoxServiceExeHst; + self.sGstVBoxServiceLogPath = ''; + self.fNoExit = False; + +class SubTstDrvAddGuestCtrl(base.SubTestDriverBase): + """ + Sub-test driver for executing guest control (VBoxService, IGuest) tests. + """ + + def __init__(self, oTstDrv): + base.SubTestDriverBase.__init__(self, oTstDrv, 'add-guest-ctrl', 'Guest Control'); + + ## @todo base.TestBase. + self.asTestsDef = [ + 'debug', + 'session_basic', 'session_env', 'session_file_ref', 'session_dir_ref', 'session_proc_ref', 'session_reboot', + 'exec_basic', 'exec_timeout', + 'dir_create', 'dir_create_temp', 'dir_read', + 'file_open', 'file_remove', 'file_stat', 'file_read', 'file_write', + 'copy_to', 'copy_from', + 'update_additions' + ]; + self.asTests = self.asTestsDef; + self.fSkipKnownBugs = False; + self.oTestFiles = None # type: vboxtestfileset.TestFileSet + self.oDebug = tdDebugSettings(); + self.sPathVBoxServiceExeGst = ''; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--add-guest-ctrl-tests': + iArg += 1; + iNext = self.oTstDrv.requireMoreArgs(1, asArgs, iArg); + if asArgs[iArg] == 'all': # Nice for debugging scripts. + self.asTests = self.asTestsDef; + else: + self.asTests = asArgs[iArg].split(':'); + for s in self.asTests: + if s not in self.asTestsDef: + raise base.InvalidOption('The "--add-guest-ctrl-tests" value "%s" is not valid; valid values are: %s' + % (s, ' '.join(self.asTestsDef))); + return iNext; + if asArgs[iArg] == '--add-guest-ctrl-skip-known-bugs': + self.fSkipKnownBugs = True; + return iArg + 1; + if asArgs[iArg] == '--no-add-guest-ctrl-skip-known-bugs': + self.fSkipKnownBugs = False; + return iArg + 1; + if asArgs[iArg] == '--add-guest-ctrl-debug-img': + iArg += 1; + iNext = self.oTstDrv.requireMoreArgs(1, asArgs, iArg); + self.oDebug.sVBoxServiceExeHst = asArgs[iArg]; + return iNext; + if asArgs[iArg] == '--add-guest-ctrl-debug-no-exit': + self.oDebug.fNoExit = True; + return iArg + 1; + return iArg; + + def showUsage(self): + base.SubTestDriverBase.showUsage(self); + reporter.log(' --add-guest-ctrl-tests <s1[:s2[:]]>'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestsDef))); + reporter.log(' --add-guest-ctrl-skip-known-bugs'); + reporter.log(' Skips known bugs. Default: --no-add-guest-ctrl-skip-known-bugs'); + reporter.log('Debugging:'); + reporter.log(' --add-guest-ctrl-debug-img'); + reporter.log(' Sets VBoxService image to deploy for debugging'); + reporter.log(' --add-guest-ctrl-debug-no-exit'); + reporter.log(' Does not tear down and exit the test driver after running the tests'); + return True; + + def testIt(self, oTestVm, oSession, oTxsSession): + """ + Executes the test. + + Returns fRc, oTxsSession. The latter may have changed. + """ + + self.sPathVBoxServiceExeGst = oTestVm.pathJoin(self.oTstDrv.getGuestSystemAdminDir(oTestVm), 'VBoxService') \ + + base.exeSuff(); + + reporter.log("Active tests: %s" % (self.asTests,)); + + # The tests. Must-succeed tests should be first. + atTests = [ + ( True, self.prepareGuestForDebugging, None, 'Manual Debugging',), + ( True, self.prepareGuestForTesting, None, 'Preparations',), + ( True, self.testGuestCtrlSession, 'session_basic', 'Session Basics',), + ( True, self.testGuestCtrlExec, 'exec_basic', 'Execution',), + ( False, self.testGuestCtrlExecTimeout, 'exec_timeout', 'Execution Timeouts',), + ( False, self.testGuestCtrlSessionEnvironment, 'session_env', 'Session Environment',), + ( False, self.testGuestCtrlSessionFileRefs, 'session_file_ref', 'Session File References',), + #( False, self.testGuestCtrlSessionDirRefs, 'session_dir_ref', 'Session Directory References',), + ( False, self.testGuestCtrlSessionProcRefs, 'session_proc_ref', 'Session Process References',), + ( False, self.testGuestCtrlDirCreate, 'dir_create', 'Creating directories',), + ( False, self.testGuestCtrlDirCreateTemp, 'dir_create_temp', 'Creating temporary directories',), + ( False, self.testGuestCtrlDirRead, 'dir_read', 'Reading directories',), + ( False, self.testGuestCtrlCopyTo, 'copy_to', 'Copy to guest',), + ( False, self.testGuestCtrlCopyFrom, 'copy_from', 'Copy from guest',), + ( False, self.testGuestCtrlFileStat, 'file_stat', 'Querying file information (stat)',), + ( False, self.testGuestCtrlFileOpen, 'file_open', 'File open',), + ( False, self.testGuestCtrlFileRead, 'file_read', 'File read',), + ( False, self.testGuestCtrlFileWrite, 'file_write', 'File write',), + ( False, self.testGuestCtrlFileRemove, 'file_remove', 'Removing files',), # Destroys prepped files. + ( False, self.testGuestCtrlUpdateAdditions, 'update_additions', 'Updating Guest Additions',), + ]; + + if not self.fSkipKnownBugs: + atTests.extend([ + ## @todo Seems to (mainly?) fail on Linux guests, primarily running with systemd as service supervisor. + # Needs to be investigated and fixed. + ( False, self.testGuestCtrlSessionReboot, 'session_reboot', 'Session w/ Guest Reboot',), # May zap /tmp. + ]); + + fRc = True; + for fMustSucceed, fnHandler, sShortNm, sTestNm in atTests: + + # If for whatever reason the VM object became invalid, bail out. + if not oTestVm: + reporter.error('Test VM object invalid (VBoxSVC or client process crashed?), aborting tests'); + fRc = False; + break; + + reporter.testStart(sTestNm); + if sShortNm is None or sShortNm in self.asTests: + # Returns (fRc, oTxsSession, oSession) - but only the first one is mandatory. + aoResult = fnHandler(oSession, oTxsSession, oTestVm); + if aoResult is None or isinstance(aoResult, bool): + fRcTest = aoResult; + else: + fRcTest = aoResult[0]; + if len(aoResult) > 1: + oTxsSession = aoResult[1]; + if len(aoResult) > 2: + oSession = aoResult[2]; + assert len(aoResult) == 3; + else: + fRcTest = None; + + if fRcTest is False and reporter.testErrorCount() == 0: + fRcTest = reporter.error('Buggy test! Returned False w/o logging the error!'); + if reporter.testDone(fRcTest is None)[1] != 0: + fRcTest = False; + fRc = False; + + # Stop execution if this is a must-succeed test and it failed. + if fRcTest is False and fMustSucceed is True: + reporter.log('Skipping any remaining tests since the previous one failed.'); + break; + + # Upload VBoxService logs on failure. + if reporter.testErrorCount() > 0 \ + and self.oDebug.sGstVBoxServiceLogPath: + sVBoxServiceLogsTarGz = 'ga-vboxservice-logs-%s.tar.gz' % oTestVm.sVmName; + sGstVBoxServiceLogsTarGz = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), sVBoxServiceLogsTarGz); + if self.oTstDrv.txsPackFile(oSession, oTxsSession, \ + sGstVBoxServiceLogsTarGz, self.oDebug.sGstVBoxServiceLogPath, fIgnoreErrors = True): + self.oTstDrv.txsDownloadFiles(oSession, oTxsSession, [ (sGstVBoxServiceLogsTarGz, sVBoxServiceLogsTarGz) ], \ + fIgnoreErrors = True); + + return (fRc, oTxsSession); + + def prepareGuestForDebugging(self, oSession, oTxsSession, oTestVm): # pylint: disable=unused-argument + """ + Prepares a guest for (manual) debugging. + + This involves copying over and invoking a the locally built VBoxService binary. + """ + + if self.oDebug.sVBoxServiceExeHst is None: # If no debugging enabled, bail out. + reporter.log('Skipping debugging'); + return True; + + reporter.log('Preparing for debugging ...'); + + try: + self.vboxServiceControl(oTxsSession, oTestVm, fStart = False); + + self.oTstDrv.sleep(5); # Fudge factor -- wait until the service stopped. + + reporter.log('Uploading "%s" to "%s" ...' % (self.oDebug.sVBoxServiceExeHst, self.sPathVBoxServiceExeGst)); + oTxsSession.syncUploadFile(self.oDebug.sVBoxServiceExeHst, self.sPathVBoxServiceExeGst); + + if oTestVm.isLinux(): + oTxsSession.syncChMod(self.sPathVBoxServiceExeGst, 0o755); + + self.vboxServiceControl(oTxsSession, oTestVm, fStart = True); + + self.oTstDrv.sleep(5); # Fudge factor -- wait until the service is ready. + + except: + return reporter.errorXcpt('Unable to prepare for debugging'); + + return True; + + # + # VBoxService handling. + # + def vboxServiceControl(self, oTxsSession, oTestVm, fStart): + """ + Controls VBoxService on the guest by starting or stopping the service. + Returns success indicator. + """ + + fRc = True; + + if oTestVm.isWindows(): + sPathSC = os.path.join(self.oTstDrv.getGuestSystemDir(oTestVm), 'sc.exe'); + if fStart is True: + fRc = self.oTstDrv.txsRunTest(oTxsSession, 'Starting VBoxService', 30 * 1000, \ + sPathSC, (sPathSC, 'start', 'VBoxService')); + else: + fRc = self.oTstDrv.txsRunTest(oTxsSession, 'Stopping VBoxService', 30 * 1000, \ + sPathSC, (sPathSC, 'stop', 'VBoxService')); + elif oTestVm.isLinux(): + sPathService = "/sbin/rcvboxadd-service"; + if fStart is True: + fRc = self.oTstDrv.txsRunTest(oTxsSession, 'Starting VBoxService', 30 * 1000, \ + sPathService, (sPathService, 'start')); + else: + fRc = self.oTstDrv.txsRunTest(oTxsSession, 'Stopping VBoxService', 30 * 1000, \ + sPathService, (sPathService, 'stop')); + else: + reporter.log('Controlling VBoxService not supported for this guest yet'); + + return fRc; + + def waitForGuestFacility(self, oSession, eFacilityType, sDesc, + eFacilityStatus, cMsTimeout = 30 * 1000): + """ + Waits for a guest facility to enter a certain status. + By default the "Active" status is being used. + + Returns success status. + """ + + reporter.log('Waiting for Guest Additions facility "%s" to change to status %s (%dms timeout)...' + % (sDesc, str(eFacilityStatus), cMsTimeout)); + + fRc = False; + + eStatusOld = None; + tsStart = base.timestampMilli(); + while base.timestampMilli() - tsStart < cMsTimeout: + try: + eStatus, _ = oSession.o.console.guest.getFacilityStatus(eFacilityType); + reporter.log('Current status is %s' % (str(eStatus))); + if eStatusOld is None: + eStatusOld = eStatus; + except: + reporter.errorXcpt('Getting facility status failed'); + break; + if eStatus != eStatusOld: + reporter.log('Status changed to %s' % (str(eStatus))); + eStatusOld = eStatus; + if eStatus == eFacilityStatus: + fRc = True; + break; + self.oTstDrv.sleep(5); # Do some busy waiting. + + if not fRc: + reporter.error('Waiting for Guest Additions facility "%s" timed out' % (sDesc)); + else: + reporter.log('Guest Additions facility "%s" reached requested status %s after %dms' + % (sDesc, str(eFacilityStatus), base.timestampMilli() - tsStart)); + + return fRc; + + # + # Guest test files. + # + + def prepareGuestForTesting(self, oSession, oTxsSession, oTestVm): + """ + Prepares the VM for testing, uploading a bunch of files and stuff via TXS. + Returns success indicator. + """ + _ = oSession; + + # + # Make sure the temporary directory exists. + # + for sDir in [self.oTstDrv.getGuestTempDir(oTestVm), ]: + if oTxsSession.syncMkDirPath(sDir, 0o777) is not True: + return reporter.error('Failed to create directory "%s"!' % (sDir,)); + + # Query the TestExecService (TXS) version first to find out on what we run. + fGotTxsVer = self.oTstDrv.txsVer(oSession, oTxsSession, 30 * 100, fIgnoreErrors = True); + + # Whether to enable verbose logging for VBoxService. + fEnableVerboseLogging = False; + + # On Windows guests we always can enable verbose logging. + if oTestVm.isWindows(): + fEnableVerboseLogging = True; + + # Old TxS versions had a bug which caused an infinite loop when executing stuff containing "$xxx", + # so check if we got the version here first and skip enabling verbose logging nonetheless if needed. + if not fGotTxsVer: + reporter.log('Too old TxS service running') + fEnableVerboseLogging = False; + + # + # Enable VBoxService verbose logging. + # + reporter.log('Enabling verbose VBoxService logging: %s' % (fEnableVerboseLogging)); + if fEnableVerboseLogging: + self.oDebug.sGstVBoxServiceLogPath = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), "VBoxService"); + if oTxsSession.syncMkDirPath(self.oDebug.sGstVBoxServiceLogPath, 0o777) is not True: + return reporter.error('Failed to create directory "%s"!' % (self.oDebug.sGstVBoxServiceLogPath,)); + sPathLogFile = oTestVm.pathJoin(self.oDebug.sGstVBoxServiceLogPath, 'VBoxService.log'); + + reporter.log('VBoxService logs will be stored in "%s"' % (self.oDebug.sGstVBoxServiceLogPath,)); + + fRestartVBoxService = False; + if oTestVm.isWindows(): + sPathRegExe = oTestVm.pathJoin(self.oTstDrv.getGuestSystemDir(oTestVm), 'reg.exe'); + sImagePath = '%s -vvvv --logfile %s' % (self.sPathVBoxServiceExeGst, sPathLogFile); + fRestartVBoxService = self.oTstDrv.txsRunTest(oTxsSession, 'Enabling VBoxService verbose logging (via registry)', + 30 * 1000, + sPathRegExe, + (sPathRegExe, 'add', + 'HKLM\\SYSTEM\\CurrentControlSet\\Services\\VBoxService', + '/v', 'ImagePath', '/t', 'REG_SZ', '/d', sImagePath, '/f')); + elif oTestVm.isLinux(): + sPathSed = oTestVm.pathJoin(self.oTstDrv.getGuestSystemDir(oTestVm), 'sed'); + fRestartVBoxService = self.oTstDrv.txsRunTest(oTxsSession, 'Enabling VBoxService verbose logging', 30 * 1000, + sPathSed, + (sPathSed, '-i', '-e', 's/' + '\\$2 \\$3' + '/' + '\\$2 \\$3 -vvvv --logfile \\/var\\/tmp\\/VBoxService\\/VBoxService.log' + '/g', + '/sbin/rcvboxadd-service')); + else: + reporter.log('Verbose logging for VBoxService not supported for this guest yet'); + + if fRestartVBoxService: + self.vboxServiceControl(oTxsSession, oTestVm, fStart = False); + self.oTstDrv.sleep(5); + self.vboxServiceControl(oTxsSession, oTestVm, fStart = True); + else: + reporter.testStart('Waiting for VBoxService to get started'); + fRc = self.waitForGuestFacility(oSession, vboxcon.AdditionsFacilityType_VBoxService, "VBoxService", + vboxcon.AdditionsFacilityStatus_Active); + reporter.testDone(); + if not fRc: + return False; + + # + # Generate and upload some random files and dirs to the guest. + # Note! Make sure we don't run into too-long-path issues when using + # the test files on the host if. + # + cchGst = len(self.oTstDrv.getGuestTempDir(oTestVm)) + 1 + len('addgst-1') + 1; + cchHst = len(self.oTstDrv.sScratchPath) + 1 + len('copyto/addgst-1') + 1; + cchMaxPath = 230; + if cchHst > cchGst: + cchMaxPath -= cchHst - cchGst; + reporter.log('cchMaxPath=%s (cchHst=%s, cchGst=%s)' % (cchMaxPath, cchHst, cchGst,)); + self.oTestFiles = vboxtestfileset.TestFileSet(oTestVm, + self.oTstDrv.getGuestTempDir(oTestVm), 'addgst-1', + # Make sure that we use a lowest common denominator across all supported + # platforms, to make testing the randomly generated file paths work + # reliably. + cchMaxPath = cchMaxPath, asCompatibleWith = [ ('cross') ]); + return self.oTestFiles.upload(oTxsSession, self.oTstDrv); + + + # + # gctrlXxxx stuff. + # + + def gctrlCopyFileFrom(self, oGuestSession, oTest, fExpected): + """ + Helper function to copy a single file from the guest to the host. + """ + + # As we pass-in randomly generated file names, the source sometimes can be empty, which + # in turn will result in a (correct) error by the API. Simply skip this function then. + if not oTest.sSrc: + reporter.log2('Skipping guest file "%s"' % (limitString(oTest.sSrc))); + return fExpected; + + # + # Do the copying. + # + reporter.log2('Copying guest file "%s" to host "%s"' % (limitString(oTest.sSrc), limitString(oTest.sDst))); + try: + if self.oTstDrv.fpApiVer >= 5.0: + oCurProgress = oGuestSession.fileCopyFromGuest(oTest.sSrc, oTest.sDst, oTest.afFlags); + else: + oCurProgress = oGuestSession.copyFrom(oTest.sSrc, oTest.sDst, oTest.afFlags); + except: + reporter.maybeErrXcpt(fExpected, 'Copy from exception for sSrc="%s", sDst="%s":' % (oTest.sSrc, oTest.sDst,)); + return False; + if oCurProgress is None: + return reporter.error('No progress object returned'); + oProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, self.oTstDrv, "gctrlFileCopyFrom"); + oProgress.wait(); + if not oProgress.isSuccess(): + oProgress.logResult(fIgnoreErrors = not fExpected); + return False; + + # + # Check the result if we can. + # + if oTest.oSrc: + assert isinstance(oTest.oSrc, testfileset.TestFile); + sDst = oTest.sDst; + if os.path.isdir(sDst): + sDst = os.path.join(sDst, oTest.oSrc.sName); + try: + oFile = open(sDst, 'rb'); # pylint: disable=consider-using-with + except: + # Don't report expected non-existing paths / files as an error. + return reporter.maybeErrXcpt(fExpected, 'open(%s) failed during verfication (file / path not found)' % (sDst,)); + fEqual = oTest.oSrc.equalFile(oFile); + oFile.close(); + if not fEqual: + return reporter.error('Content differs for "%s"' % (sDst,)); + + return True; + + def __compareTestDir(self, oDir, sHostPath): # type: (testfileset.TestDir, str) -> bool + """ + Recursively compare the content of oDir and sHostPath. + + Returns True on success, False + error logging on failure. + + Note! This ASSUMES that nothing else was copied to sHostPath! + """ + # + # First check out all the entries and files in the directory. + # + dLeftUpper = dict(oDir.dChildrenUpper); + try: + asEntries = os.listdir(sHostPath); + except: + return reporter.errorXcpt('os.listdir(%s) failed' % (sHostPath,)); + + fRc = True; + for sEntry in asEntries: + sEntryUpper = sEntry.upper(); + if sEntryUpper not in dLeftUpper: + fRc = reporter.error('Unexpected entry "%s" in "%s"' % (sEntry, sHostPath,)); + else: + oFsObj = dLeftUpper[sEntryUpper]; + del dLeftUpper[sEntryUpper]; + + if isinstance(oFsObj, testfileset.TestFile): + sFilePath = os.path.join(sHostPath, oFsObj.sName); + try: + oFile = open(sFilePath, 'rb'); # pylint: disable=consider-using-with + except: + fRc = reporter.errorXcpt('open(%s) failed during verfication' % (sFilePath,)); + else: + fEqual = oFsObj.equalFile(oFile); + oFile.close(); + if not fEqual: + fRc = reporter.error('Content differs for "%s"' % (sFilePath,)); + + # List missing entries: + for sKey in dLeftUpper: + oEntry = dLeftUpper[sKey]; + fRc = reporter.error('%s: Missing %s "%s" (src path: %s)' + % (sHostPath, oEntry.sName, + 'file' if isinstance(oEntry, testfileset.TestFile) else 'directory', oEntry.sPath)); + + # + # Recurse into subdirectories. + # + for oFsObj in oDir.aoChildren: + if isinstance(oFsObj, testfileset.TestDir): + fRc = self.__compareTestDir(oFsObj, os.path.join(sHostPath, oFsObj.sName)) and fRc; + return fRc; + + def gctrlCopyDirFrom(self, oGuestSession, oTest, fExpected): + """ + Helper function to copy a directory from the guest to the host. + """ + + # As we pass-in randomly generated directories, the source sometimes can be empty, which + # in turn will result in a (correct) error by the API. Simply skip this function then. + if not oTest.sSrc: + reporter.log2('Skipping guest dir "%s"' % (limitString(oTest.sSrc))); + return fExpected; + + # + # Do the copying. + # + reporter.log2('Copying guest dir "%s" to host "%s"' % (limitString(oTest.sSrc), limitString(oTest.sDst))); + try: + if self.oTstDrv.fpApiVer >= 7.0: + ## @todo Make the following new flags implicit for 7.0 for now. Develop dedicated tests for this later and remove. + if not oTest.afFlags: + oTest.afFlags = [ vboxcon.DirectoryCopyFlag_Recursive, ]; + elif vboxcon.DirectoryCopyFlag_Recursive not in oTest.afFlags: + oTest.afFlags.append(vboxcon.DirectoryCopyFlag_Recursive); + ## @todo Ditto. + if not oTest.afFlags: + oTest.afFlags = [ vboxcon.DirectoryCopyFlag_FollowLinks, ]; + elif vboxcon.DirectoryCopyFlag_FollowLinks not in oTest.afFlags: + oTest.afFlags.append(vboxcon.DirectoryCopyFlag_FollowLinks); + oCurProgress = oGuestSession.directoryCopyFromGuest(oTest.sSrc, oTest.sDst, oTest.afFlags); + except: + reporter.maybeErrXcpt(fExpected, 'Copy dir from exception for sSrc="%s", sDst="%s":' % (oTest.sSrc, oTest.sDst,)); + return False; + if oCurProgress is None: + return reporter.error('No progress object returned'); + + oProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, self.oTstDrv, "gctrlDirCopyFrom"); + oProgress.wait(); + if not oProgress.isSuccess(): + oProgress.logResult(fIgnoreErrors = not fExpected); + return False; + + # + # Check the result if we can. + # + if oTest.oSrc: + assert isinstance(oTest.oSrc, testfileset.TestDir); + sDst = oTest.sDst; + if oTest.fIntoDst: + return self.__compareTestDir(oTest.oSrc, os.path.join(sDst, oTest.oSrc.sName)); + oDummy = testfileset.TestDir(None, 'dummy'); + oDummy.aoChildren = [oTest.oSrc,] + oDummy.dChildrenUpper = { oTest.oSrc.sName.upper(): oTest.oSrc, }; + return self.__compareTestDir(oDummy, sDst); + return True; + + def gctrlCopyFileTo(self, oGuestSession, sSrc, sDst, afFlags, fIsError): + """ + Helper function to copy a single file from the host to the guest. + + afFlags is either None or an array of vboxcon.DirectoryCopyFlag_Xxxx values. + """ + reporter.log2('Copying host file "%s" to guest "%s" (flags %s)' % (limitString(sSrc), limitString(sDst), afFlags)); + try: + if self.oTstDrv.fpApiVer >= 5.0: + oCurProgress = oGuestSession.fileCopyToGuest(sSrc, sDst, afFlags); + else: + oCurProgress = oGuestSession.copyTo(sSrc, sDst, afFlags); + except: + reporter.maybeErrXcpt(fIsError, 'sSrc=%s sDst=%s' % (sSrc, sDst,)); + return False; + + if oCurProgress is None: + return reporter.error('No progress object returned'); + oProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, self.oTstDrv, "gctrlCopyFileTo"); + + try: + oProgress.wait(); + if not oProgress.isSuccess(): + oProgress.logResult(fIgnoreErrors = not fIsError); + return False; + except: + reporter.maybeErrXcpt(fIsError, 'Wait exception for sSrc="%s", sDst="%s":' % (sSrc, sDst)); + return False; + return True; + + def gctrlCopyDirTo(self, oGuestSession, sSrc, sDst, afFlags, fIsError): + """ + Helper function to copy a directory (tree) from the host to the guest. + + afFlags is either None or an array of vboxcon.DirectoryCopyFlag_Xxxx values. + """ + reporter.log2('Copying host directory "%s" to guest "%s" (flags %s)' % (limitString(sSrc), limitString(sDst), afFlags)); + try: + if self.oTstDrv.fpApiVer >= 7.0: + ## @todo Make the following new flags implicit for 7.0 for now. Develop dedicated tests for this later and remove. + if not afFlags: + afFlags = [ vboxcon.DirectoryCopyFlag_Recursive, ]; + elif vboxcon.DirectoryCopyFlag_Recursive not in afFlags: + afFlags.append(vboxcon.DirectoryCopyFlag_Recursive); + ## @todo Ditto. + if not afFlags: + afFlags = [vboxcon.DirectoryCopyFlag_FollowLinks,]; + elif vboxcon.DirectoryCopyFlag_FollowLinks not in afFlags: + afFlags.append(vboxcon.DirectoryCopyFlag_FollowLinks); + oCurProgress = oGuestSession.directoryCopyToGuest(sSrc, sDst, afFlags); + except: + reporter.maybeErrXcpt(fIsError, 'sSrc=%s sDst=%s' % (sSrc, sDst,)); + return False; + + if oCurProgress is None: + return reporter.error('No progress object returned'); + oProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, self.oTstDrv, "gctrlCopyFileTo"); + + try: + oProgress.wait(); + if not oProgress.isSuccess(): + oProgress.logResult(fIgnoreErrors = not fIsError); + return False; + except: + reporter.maybeErrXcpt(fIsError, 'Wait exception for sSrc="%s", sDst="%s":' % (sSrc, sDst)); + return False; + return True; + + def gctrlCreateDir(self, oTest, oRes, oGuestSession): + """ + Helper function to create a guest directory specified in the current test. + """ + reporter.log2('Creating directory "%s"' % (limitString(oTest.sDirectory),)); + try: + oGuestSession.directoryCreate(oTest.sDirectory, oTest.fMode, oTest.afFlags); + except: + reporter.maybeErrXcpt(oRes.fRc, 'Failed to create "%s" fMode=%o afFlags=%s' + % (oTest.sDirectory, oTest.fMode, oTest.afFlags,)); + return not oRes.fRc; + if oRes.fRc is not True: + return reporter.error('Did not expect to create directory "%s"!' % (oTest.sDirectory,)); + + # Check if the directory now exists. + try: + if self.oTstDrv.fpApiVer >= 5.0: + fDirExists = oGuestSession.directoryExists(oTest.sDirectory, False); + else: + fDirExists = oGuestSession.directoryExists(oTest.sDirectory); + except: + return reporter.errorXcpt('directoryExists failed on "%s"!' % (oTest.sDirectory,)); + if not fDirExists: + return reporter.errorXcpt('directoryExists returned False on "%s" after directoryCreate succeeded!' + % (oTest.sDirectory,)); + return True; + + def gctrlReadDirTree(self, oTest, oGuestSession, fIsError, sSubDir = None): + """ + Helper function to recursively read a guest directory tree specified in the current test. + """ + sDir = oTest.sDirectory; + sFilter = oTest.sFilter; + afFlags = oTest.afFlags; + oTestVm = oTest.oCreds.oTestVm; + sCurDir = oTestVm.pathJoin(sDir, sSubDir) if sSubDir else sDir; + + fRc = True; # Be optimistic. + cDirs = 0; # Number of directories read. + cFiles = 0; # Number of files read. + cOthers = 0; # Other files. + + # Open the directory: + reporter.log2('Directory="%s", filter="%s", afFlags="%s"' % (limitString(sCurDir), sFilter, afFlags)); + try: + oCurDir = oGuestSession.directoryOpen(sCurDir, sFilter, afFlags); + except: + reporter.maybeErrXcpt(fIsError, 'sCurDir=%s sFilter=%s afFlags=%s' % (sCurDir, sFilter, afFlags,)) + return (False, 0, 0, 0); + + # Read the directory. + while fRc is True: + try: + oFsObjInfo = oCurDir.read(); + except Exception as oXcpt: + if vbox.ComError.notEqual(oXcpt, vbox.ComError.VBOX_E_OBJECT_NOT_FOUND): + if self.oTstDrv.fpApiVer > 5.2: + reporter.errorXcpt('Error reading directory "%s":' % (sCurDir,)); + else: + # Unlike fileOpen, directoryOpen will not fail if the directory does not exist. + reporter.maybeErrXcpt(fIsError, 'Error reading directory "%s":' % (sCurDir,)); + fRc = False; + else: + reporter.log2('\tNo more directory entries for "%s"' % (limitString(sCurDir),)); + break; + + try: + sName = oFsObjInfo.name; + eType = oFsObjInfo.type; + except: + fRc = reporter.errorXcpt(); + break; + + if sName in ('.', '..', ): + if eType != vboxcon.FsObjType_Directory: + fRc = reporter.error('Wrong type for "%s": %d, expected %d (Directory)' + % (sName, eType, vboxcon.FsObjType_Directory)); + elif eType == vboxcon.FsObjType_Directory: + reporter.log2(' Directory "%s"' % limitString(oFsObjInfo.name)); + aSubResult = self.gctrlReadDirTree(oTest, oGuestSession, fIsError, + oTestVm.pathJoin(sSubDir, sName) if sSubDir else sName); + fRc = aSubResult[0]; + cDirs += aSubResult[1] + 1; + cFiles += aSubResult[2]; + cOthers += aSubResult[3]; + elif eType is vboxcon.FsObjType_File: + reporter.log4(' File "%s"' % oFsObjInfo.name); + cFiles += 1; + elif eType is vboxcon.FsObjType_Symlink: + reporter.log4(' Symlink "%s" -- not tested yet' % oFsObjInfo.name); + cOthers += 1; + elif oTestVm.isWindows() \ + or oTestVm.isOS2() \ + or eType not in (vboxcon.FsObjType_Fifo, vboxcon.FsObjType_DevChar, vboxcon.FsObjType_DevBlock, + vboxcon.FsObjType_Socket, vboxcon.FsObjType_WhiteOut): + fRc = reporter.error('Directory "%s" contains invalid directory entry "%s" (type %d)' % + (sCurDir, oFsObjInfo.name, oFsObjInfo.type,)); + else: + cOthers += 1; + + # Close the directory + try: + oCurDir.close(); + except: + fRc = reporter.errorXcpt('sCurDir=%s' % (sCurDir)); + + return (fRc, cDirs, cFiles, cOthers); + + def gctrlReadDirTree2(self, oGuestSession, oDir): # type: (testfileset.TestDir) -> bool + """ + Helper function to recursively read a guest directory tree specified in the current test. + """ + + # + # Process the directory. + # + + # Open the directory: + try: + oCurDir = oGuestSession.directoryOpen(oDir.sPath, '', None); + except: + return reporter.errorXcpt('sPath=%s' % (oDir.sPath,)); + + # Read the directory. + dLeftUpper = dict(oDir.dChildrenUpper); + cDot = 0; + cDotDot = 0; + fRc = True; + while True: + try: + oFsObjInfo = oCurDir.read(); + except Exception as oXcpt: + if vbox.ComError.notEqual(oXcpt, vbox.ComError.VBOX_E_OBJECT_NOT_FOUND): + fRc = reporter.errorXcpt('Error reading directory "%s":' % (oDir.sPath,)); + break; + + try: + sName = oFsObjInfo.name; + eType = oFsObjInfo.type; + cbFile = oFsObjInfo.objectSize; + ## @todo check further attributes. + except: + fRc = reporter.errorXcpt(); + break; + + # '.' and '..' entries are not present in oDir.aoChildren, so special treatment: + if sName in ('.', '..', ): + if eType != vboxcon.FsObjType_Directory: + fRc = reporter.error('Wrong type for "%s": %d, expected %d (Directory)' + % (sName, eType, vboxcon.FsObjType_Directory)); + if sName == '.': cDot += 1; + else: cDotDot += 1; + else: + # Find the child and remove it from the dictionary. + sNameUpper = sName.upper(); + oFsObj = dLeftUpper.get(sNameUpper); + if oFsObj is None: + fRc = reporter.error('Unknown object "%s" found in "%s" (type %s, size %s)!' + % (sName, oDir.sPath, eType, cbFile,)); + else: + del dLeftUpper[sNameUpper]; + + # Check type + if isinstance(oFsObj, testfileset.TestDir): + if eType != vboxcon.FsObjType_Directory: + fRc = reporter.error('%s: expected directory (%d), got eType=%d!' + % (oFsObj.sPath, vboxcon.FsObjType_Directory, eType,)); + elif isinstance(oFsObj, testfileset.TestFile): + if eType != vboxcon.FsObjType_File: + fRc = reporter.error('%s: expected file (%d), got eType=%d!' + % (oFsObj.sPath, vboxcon.FsObjType_File, eType,)); + else: + fRc = reporter.error('%s: WTF? type=%s' % (oFsObj.sPath, type(oFsObj),)); + + # Check the name. + if oFsObj.sName != sName: + fRc = reporter.error('%s: expected name "%s", got "%s" instead!' % (oFsObj.sPath, oFsObj.sName, sName,)); + + # Check the size if a file. + if isinstance(oFsObj, testfileset.TestFile) and cbFile != oFsObj.cbContent: + fRc = reporter.error('%s: expected size %s, got %s instead!' % (oFsObj.sPath, oFsObj.cbContent, cbFile,)); + + ## @todo check timestamps and attributes. + + # Close the directory + try: + oCurDir.close(); + except: + fRc = reporter.errorXcpt('oDir.sPath=%s' % (oDir.sPath,)); + + # Any files left over? + for sKey in dLeftUpper: + oFsObj = dLeftUpper[sKey]; + fRc = reporter.error('%s: Was not returned! (%s)' % (oFsObj.sPath, type(oFsObj),)); + + # Check the dot and dot-dot counts. + if cDot != 1: + fRc = reporter.error('%s: Found %s "." entries, expected exactly 1!' % (oDir.sPath, cDot,)); + if cDotDot != 1: + fRc = reporter.error('%s: Found %s ".." entries, expected exactly 1!' % (oDir.sPath, cDotDot,)); + + # + # Recurse into subdirectories using info from oDir. + # + for oFsObj in oDir.aoChildren: + if isinstance(oFsObj, testfileset.TestDir): + fRc = self.gctrlReadDirTree2(oGuestSession, oFsObj) and fRc; + + return fRc; + + def gctrlExecDoTest(self, i, oTest, oRes, oGuestSession): + """ + Wrapper function around gctrlExecute to provide more sanity checking + when needed in actual execution tests. + """ + reporter.log('Testing #%d, cmd="%s" ...' % (i, oTest.sCmd)); + fRcExec = self.gctrlExecute(oTest, oGuestSession, oRes.fRc); + if fRcExec == oRes.fRc: + fRc = True; + if fRcExec is True: + # Compare exit status / code on successful process execution. + if oTest.uExitStatus != oRes.uExitStatus \ + or oTest.iExitCode != oRes.iExitCode: + fRc = reporter.error('Test #%d (%s) failed: Got exit status + code %d,%d, expected %d,%d' + % (i, oTest.asArgs, oTest.uExitStatus, oTest.iExitCode, + oRes.uExitStatus, oRes.iExitCode)); + + # Compare test / result buffers on successful process execution. + if oTest.sBuf is not None and oRes.sBuf is not None: + if not utils.areBytesEqual(oTest.sBuf, oRes.sBuf): + fRc = reporter.error('Test #%d (%s) failed: Got buffer\n%s (%d bytes), expected\n%s (%d bytes)' + % (i, oTest.asArgs, + map(hex, map(ord, oTest.sBuf)), len(oTest.sBuf), + map(hex, map(ord, oRes.sBuf)), len(oRes.sBuf))); + reporter.log2('Test #%d passed: Buffers match (%d bytes)' % (i, len(oRes.sBuf))); + elif oRes.sBuf and not oTest.sBuf: + fRc = reporter.error('Test #%d (%s) failed: Got no buffer data, expected\n%s (%dbytes)' % + (i, oTest.asArgs, map(hex, map(ord, oRes.sBuf)), len(oRes.sBuf),)); + + if oRes.cbStdOut is not None and oRes.cbStdOut != oTest.cbStdOut: + fRc = reporter.error('Test #%d (%s) failed: Got %d bytes of stdout data, expected %d' + % (i, oTest.asArgs, oTest.cbStdOut, oRes.cbStdOut)); + if oRes.cbStdErr is not None and oRes.cbStdErr != oTest.cbStdErr: + fRc = reporter.error('Test #%d (%s) failed: Got %d bytes of stderr data, expected %d' + % (i, oTest.asArgs, oTest.cbStdErr, oRes.cbStdErr)); + else: + fRc = reporter.error('Test #%d (%s) failed: Got %s, expected %s' % (i, oTest.asArgs, fRcExec, oRes.fRc)); + return fRc; + + def gctrlExecute(self, oTest, oGuestSession, fIsError): # pylint: disable=too-many-statements + """ + Helper function to execute a program on a guest, specified in the current test. + + Note! This weirdo returns results (process exitcode and status) in oTest. + """ + fRc = True; # Be optimistic. + + # Reset the weird result stuff: + oTest.cbStdOut = 0; + oTest.cbStdErr = 0; + oTest.sBuf = ''; + oTest.uExitStatus = 0; + oTest.iExitCode = 0; + + ## @todo Compare execution timeouts! + #tsStart = base.timestampMilli(); + + try: + reporter.log2('Using session user=%s, sDomain=%s, name=%s, timeout=%d' + % (oGuestSession.user, oGuestSession.domain, oGuestSession.name, oGuestSession.timeout,)); + except: + return reporter.errorXcpt(); + + # + # Start the process: + # + reporter.log2('Executing sCmd=%s, afFlags=%s, timeoutMS=%d, asArgs=%s, asEnv=%s' + % (oTest.sCmd, oTest.afFlags, oTest.timeoutMS, limitString(oTest.asArgs), limitString(oTest.aEnv),)); + try: + oProcess = oGuestSession.processCreate(oTest.sCmd, + oTest.asArgs if self.oTstDrv.fpApiVer >= 5.0 else oTest.asArgs[1:], + oTest.aEnv, oTest.afFlags, oTest.timeoutMS); + except: + reporter.maybeErrXcpt(fIsError, 'type=%s, asArgs=%s' % (type(oTest.asArgs), oTest.asArgs,)); + return False; + if oProcess is None: + return reporter.error('oProcess is None! (%s)' % (oTest.asArgs,)); + + #time.sleep(5); # try this if you want to see races here. + + # Wait for the process to start properly: + reporter.log2('Process start requested, waiting for start (%dms) ...' % (oTest.timeoutMS,)); + iPid = -1; + aeWaitFor = [ vboxcon.ProcessWaitForFlag_Start, ]; + try: + eWaitResult = oProcess.waitForArray(aeWaitFor, oTest.timeoutMS); + except: + reporter.maybeErrXcpt(fIsError, 'waitforArray failed for asArgs=%s' % (oTest.asArgs,)); + fRc = False; + else: + try: + eStatus = oProcess.status; + iPid = oProcess.PID; + except: + fRc = reporter.errorXcpt('asArgs=%s' % (oTest.asArgs,)); + else: + reporter.log2('Wait result returned: %d, current process status is: %d' % (eWaitResult, eStatus,)); + + # + # Wait for the process to run to completion if necessary. + # + # Note! The above eWaitResult return value can be ignored as it will + # (mostly) reflect the process status anyway. + # + if eStatus == vboxcon.ProcessStatus_Started: + + # What to wait for: + aeWaitFor = [ vboxcon.ProcessWaitForFlag_Terminate, ]; + if vboxcon.ProcessCreateFlag_WaitForStdOut in oTest.afFlags: + aeWaitFor.append(vboxcon.ProcessWaitForFlag_StdOut); + if vboxcon.ProcessCreateFlag_WaitForStdErr in oTest.afFlags: + aeWaitFor.append(vboxcon.ProcessWaitForFlag_StdErr); + ## @todo Add vboxcon.ProcessWaitForFlag_StdIn. + + reporter.log2('Process (PID %d) started, waiting for termination (%dms), aeWaitFor=%s ...' + % (iPid, oTest.timeoutMS, aeWaitFor)); + acbFdOut = [0,0,0]; + while True: + try: + eWaitResult = oProcess.waitForArray(aeWaitFor, oTest.timeoutMS); + except KeyboardInterrupt: # Not sure how helpful this is, but whatever. + reporter.error('Process (PID %d) execution interrupted' % (iPid,)); + try: oProcess.close(); + except: pass; + break; + except: + fRc = reporter.errorXcpt('asArgs=%s' % (oTest.asArgs,)); + break; + #reporter.log2('Wait returned: %d' % (eWaitResult,)); + + # Process output: + for eFdResult, iFd, sFdNm in [ (vboxcon.ProcessWaitResult_StdOut, 1, 'stdout'), + (vboxcon.ProcessWaitResult_StdErr, 2, 'stderr'), ]: + if eWaitResult in (eFdResult, vboxcon.ProcessWaitResult_WaitFlagNotSupported): + try: + abBuf = oProcess.read(iFd, 64 * 1024, oTest.timeoutMS); + except KeyboardInterrupt: # Not sure how helpful this is, but whatever. + reporter.error('Process (PID %d) execution interrupted' % (iPid,)); + try: oProcess.close(); + except: pass; + except: + reporter.maybeErrXcpt(fIsError, 'asArgs=%s' % (oTest.asArgs,)); + else: + if abBuf: + reporter.log2('Process (PID %d) got %d bytes of %s data (type: %s)' + % (iPid, len(abBuf), sFdNm, type(abBuf))); + if reporter.getVerbosity() >= 4: + sBuf = ''; + if sys.version_info >= (2, 7): + if isinstance(abBuf, memoryview): ## @todo Why is this happening? + abBuf = abBuf.tobytes(); + sBuf = abBuf.decode("utf-8"); + if sys.version_info <= (2, 7): + if isinstance(abBuf, buffer): # (for 3.0+) pylint: disable=undefined-variable + sBuf = str(abBuf); + for sLine in sBuf.splitlines(): + reporter.log4('%s: %s' % (sFdNm, sLine)); + acbFdOut[iFd] += len(abBuf); + oTest.sBuf = abBuf; ## @todo Figure out how to uniform + append! + + ## Process input (todo): + #if eWaitResult in (vboxcon.ProcessWaitResult_StdIn, vboxcon.ProcessWaitResult_WaitFlagNotSupported): + # reporter.log2('Process (PID %d) needs stdin data' % (iPid,)); + + # Termination or error? + if eWaitResult in (vboxcon.ProcessWaitResult_Terminate, + vboxcon.ProcessWaitResult_Error, + vboxcon.ProcessWaitResult_Timeout,): + try: eStatus = oProcess.status; + except: fRc = reporter.errorXcpt('asArgs=%s' % (oTest.asArgs,)); + reporter.log2('Process (PID %d) reported terminate/error/timeout: %d, status: %d' + % (iPid, eWaitResult, eStatus,)); + break; + + # End of the wait loop. + _, oTest.cbStdOut, oTest.cbStdErr = acbFdOut; + + try: eStatus = oProcess.status; + except: fRc = reporter.errorXcpt('asArgs=%s' % (oTest.asArgs,)); + reporter.log2('Final process status (PID %d) is: %d' % (iPid, eStatus)); + reporter.log2('Process (PID %d) %d stdout, %d stderr' % (iPid, oTest.cbStdOut, oTest.cbStdErr)); + + # + # Get the final status and exit code of the process. + # + try: + oTest.uExitStatus = oProcess.status; + oTest.iExitCode = oProcess.exitCode; + except: + fRc = reporter.errorXcpt('asArgs=%s' % (oTest.asArgs,)); + reporter.log2('Process (PID %d) has exit code: %d; status: %d ' % (iPid, oTest.iExitCode, oTest.uExitStatus)); + return fRc; + + def testGuestCtrlSessionEnvironment(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals + """ + Tests the guest session environment changes. + """ + aoTests = [ + # Check basic operations. + tdTestSessionEx([ # Initial environment is empty. + tdStepSessionCheckEnv(), + # Check clearing empty env. + tdStepSessionClearEnv(), + tdStepSessionCheckEnv(), + # Check set. + tdStepSessionSetEnv('FOO', 'BAR'), + tdStepSessionCheckEnv(['FOO=BAR',]), + tdStepRequireMinimumApiVer(5.0), # 4.3 can't cope with the remainder. + tdStepSessionClearEnv(), + tdStepSessionCheckEnv(), + # Check unset. + tdStepSessionUnsetEnv('BAR'), + tdStepSessionCheckEnv(['BAR']), + tdStepSessionClearEnv(), + tdStepSessionCheckEnv(), + # Set + unset. + tdStepSessionSetEnv('FOO', 'BAR'), + tdStepSessionCheckEnv(['FOO=BAR',]), + tdStepSessionUnsetEnv('FOO'), + tdStepSessionCheckEnv(['FOO']), + # Bulk environment changes (via attrib) (shall replace existing 'FOO'). + tdStepSessionBulkEnv( ['PATH=/bin:/usr/bin', 'TMPDIR=/var/tmp', 'USER=root']), + tdStepSessionCheckEnv(['PATH=/bin:/usr/bin', 'TMPDIR=/var/tmp', 'USER=root']), + ]), + tdTestSessionEx([ # Check that setting the same value several times works. + tdStepSessionSetEnv('FOO','BAR'), + tdStepSessionCheckEnv([ 'FOO=BAR',]), + tdStepSessionSetEnv('FOO','BAR2'), + tdStepSessionCheckEnv([ 'FOO=BAR2',]), + tdStepSessionSetEnv('FOO','BAR3'), + tdStepSessionCheckEnv([ 'FOO=BAR3',]), + tdStepRequireMinimumApiVer(5.0), # 4.3 can't cope with the remainder. + # Add a little unsetting to the mix. + tdStepSessionSetEnv('BAR', 'BEAR'), + tdStepSessionCheckEnv([ 'FOO=BAR3', 'BAR=BEAR',]), + tdStepSessionUnsetEnv('FOO'), + tdStepSessionCheckEnv([ 'FOO', 'BAR=BEAR',]), + tdStepSessionSetEnv('FOO','BAR4'), + tdStepSessionCheckEnv([ 'FOO=BAR4', 'BAR=BEAR',]), + # The environment is case sensitive. + tdStepSessionSetEnv('foo','BAR5'), + tdStepSessionCheckEnv([ 'FOO=BAR4', 'BAR=BEAR', 'foo=BAR5']), + tdStepSessionUnsetEnv('foo'), + tdStepSessionCheckEnv([ 'FOO=BAR4', 'BAR=BEAR', 'foo']), + ]), + tdTestSessionEx([ # Bulk settings merges stuff, last entry standing. + tdStepSessionBulkEnv(['FOO=bar', 'foo=bar', 'FOO=doofus', 'TMPDIR=/tmp', 'foo=bar2']), + tdStepSessionCheckEnv(['FOO=doofus', 'TMPDIR=/tmp', 'foo=bar2']), + tdStepRequireMinimumApiVer(5.0), # 4.3 is buggy! + tdStepSessionBulkEnv(['2=1+1', 'FOO=doofus2', ]), + tdStepSessionCheckEnv(['2=1+1', 'FOO=doofus2' ]), + ]), + # Invalid variable names. + tdTestSessionEx([ + tdStepSessionSetEnv('', 'FOO', vbox.ComError.E_INVALIDARG), + tdStepSessionCheckEnv(), + tdStepRequireMinimumApiVer(5.0), # 4.3 is too relaxed checking input! + tdStepSessionBulkEnv(['', 'foo=bar'], vbox.ComError.E_INVALIDARG), + tdStepSessionCheckEnv(), + tdStepSessionSetEnv('FOO=', 'BAR', vbox.ComError.E_INVALIDARG), + tdStepSessionCheckEnv(), + ]), + # A bit more weird keys/values. + tdTestSessionEx([ tdStepSessionSetEnv('$$$', ''), + tdStepSessionCheckEnv([ '$$$=',]), ]), + tdTestSessionEx([ tdStepSessionSetEnv('$$$', '%%%'), + tdStepSessionCheckEnv([ '$$$=%%%',]), + ]), + tdTestSessionEx([ tdStepRequireMinimumApiVer(5.0), # 4.3 is buggy! + tdStepSessionSetEnv(u'ß$%ß&', ''), + tdStepSessionCheckEnv([ u'ß$%ß&=',]), + ]), + # Misc stuff. + tdTestSessionEx([ tdStepSessionSetEnv('FOO', ''), + tdStepSessionCheckEnv(['FOO=',]), + ]), + tdTestSessionEx([ tdStepSessionSetEnv('FOO', 'BAR'), + tdStepSessionCheckEnv(['FOO=BAR',]) + ],), + tdTestSessionEx([ tdStepSessionSetEnv('FOO', 'BAR'), + tdStepSessionSetEnv('BAR', 'BAZ'), + tdStepSessionCheckEnv([ 'FOO=BAR', 'BAR=BAZ',]), + ]), + ]; + # Leading '=' in the name is okay for windows guests in 6.1 and later (for driver letter CWDs). + if (self.oTstDrv.fpApiVer < 6.1 and self.oTstDrv.fpApiVer >= 5.0) or not oTestVm.isWindows(): + aoTests.append(tdTestSessionEx([tdStepSessionSetEnv('=', '===', vbox.ComError.E_INVALIDARG), + tdStepSessionCheckEnv(), + tdStepSessionSetEnv('=FOO', 'BAR', vbox.ComError.E_INVALIDARG), + tdStepSessionCheckEnv(), + tdStepSessionBulkEnv(['=', 'foo=bar'], vbox.ComError.E_INVALIDARG), + tdStepSessionCheckEnv(), + tdStepSessionBulkEnv(['=FOO', 'foo=bar'], vbox.ComError.E_INVALIDARG), + tdStepSessionCheckEnv(), + tdStepSessionBulkEnv(['=D:=D:/tmp', 'foo=bar'], vbox.ComError.E_INVALIDARG), + tdStepSessionCheckEnv(), + tdStepSessionSetEnv('=D:', 'D:/temp', vbox.ComError.E_INVALIDARG), + tdStepSessionCheckEnv(), + ])); + elif self.oTstDrv.fpApiVer >= 6.1 and oTestVm.isWindows(): + aoTests.append(tdTestSessionEx([tdStepSessionSetEnv('=D:', 'D:/tmp'), + tdStepSessionCheckEnv(['=D:=D:/tmp',]), + tdStepSessionBulkEnv(['=D:=D:/temp', '=FOO', 'foo=bar']), + tdStepSessionCheckEnv(['=D:=D:/temp', '=FOO', 'foo=bar']), + tdStepSessionUnsetEnv('=D:'), + tdStepSessionCheckEnv(['=D:', '=FOO', 'foo=bar']), + ])); + + return tdTestSessionEx.executeListTestSessions(aoTests, self.oTstDrv, oSession, oTxsSession, oTestVm, 'SessionEnv'); + + def testGuestCtrlSession(self, oSession, oTxsSession, oTestVm): + """ + Tests the guest session handling. + """ + + # + # Tests: + # + atTests = [ + # Invalid parameters. + [ tdTestSession(sUser = ''), tdTestResultSession() ], + # User account without a passwort - forbidden. + [ tdTestSession(sPassword = "" ), tdTestResultSession() ], + # Various wrong credentials. + # Note! Only windows cares about sDomain, the other guests ignores it. + # Note! On Guest Additions < 4.3 this always succeeds because these don't + # support creating dedicated sessions. Instead, guest process creation + # then will fail. See note below. + [ tdTestSession(sPassword = 'bar'), tdTestResultSession() ], + [ tdTestSession(sUser = 'foo', sPassword = 'bar'), tdTestResultSession() ], + [ tdTestSession(sPassword = 'bar', sDomain = 'boo'), tdTestResultSession() ], + [ tdTestSession(sUser = 'foo', sPassword = 'bar', sDomain = 'boo'), tdTestResultSession() ], + ]; + if oTestVm.isWindows(): # domain is ignored elsewhere. + atTests.append([ tdTestSession(sDomain = 'boo'), tdTestResultSession() ]); + + # Finally, correct credentials. + atTests.append([ tdTestSession(), tdTestResultSession(fRc = True, cNumSessions = 1) ]); + + # + # Run the tests. + # + fRc = True; + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0] # type: tdTestSession + oCurRes = tTest[1] # type: tdTestResult + + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + reporter.log('Testing #%d, user="%s", sPassword="%s", sDomain="%s" ...' + % (i, oCurTest.oCreds.sUser, oCurTest.oCreds.sPassword, oCurTest.oCreds.sDomain)); + sCurGuestSessionName = 'testGuestCtrlSession: Test #%d' % (i,); + fRc2, oCurGuestSession = oCurTest.createSession(sCurGuestSessionName, fIsError = oCurRes.fRc); + + # See note about < 4.3 Guest Additions above. + uProtocolVersion = 2; + if oCurGuestSession is not None: + try: + uProtocolVersion = oCurGuestSession.protocolVersion; + except: + fRc = reporter.errorXcpt('Test #%d' % (i,)); + + if uProtocolVersion >= 2 and fRc2 is not oCurRes.fRc: + fRc = reporter.error('Test #%d failed: Session creation failed: Got %s, expected %s' % (i, fRc2, oCurRes.fRc,)); + + if fRc2 and oCurGuestSession is None: + fRc = reporter.error('Test #%d failed: no session object' % (i,)); + fRc2 = False; + + if fRc2: + if uProtocolVersion >= 2: # For Guest Additions < 4.3 getSessionCount() always will return 1. + cCurSessions = oCurTest.getSessionCount(self.oTstDrv.oVBoxMgr); + if cCurSessions != oCurRes.cNumSessions: + fRc = reporter.error('Test #%d failed: Session count does not match: Got %d, expected %d' + % (i, cCurSessions, oCurRes.cNumSessions)); + try: + sObjName = oCurGuestSession.name; + except: + fRc = reporter.errorXcpt('Test #%d' % (i,)); + else: + if sObjName != sCurGuestSessionName: + fRc = reporter.error('Test #%d failed: Session name does not match: Got "%s", expected "%s"' + % (i, sObjName, sCurGuestSessionName)); + fRc2 = oCurTest.closeSession(); + if fRc2 is False: + fRc = reporter.error('Test #%d failed: Session could not be closed' % (i,)); + + if fRc is False: + return (False, oTxsSession); + + # + # Multiple sessions. + # + cMaxGuestSessions = 31; # Maximum number of concurrent guest session allowed. + # Actually, this is 32, but we don't test session 0. + aoMultiSessions = {}; + reporter.log2('Opening multiple guest tsessions at once ...'); + for i in xrange(cMaxGuestSessions + 1): + aoMultiSessions[i] = tdTestSession(sSessionName = 'MultiSession #%d' % (i,)); + fRc = aoMultiSessions[i].setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + + cCurSessions = aoMultiSessions[i].getSessionCount(self.oTstDrv.oVBoxMgr); + reporter.log2('MultiSession test #%d count is %d' % (i, cCurSessions)); + if cCurSessions != i: + return (reporter.error('MultiSession count is %d, expected %d' % (cCurSessions, i)), oTxsSession); + fRc2, _ = aoMultiSessions[i].createSession('MultiSession #%d' % (i,), i < cMaxGuestSessions); + if fRc2 is not True: + if i < cMaxGuestSessions: + return (reporter.error('MultiSession #%d test failed' % (i,)), oTxsSession); + reporter.log('MultiSession #%d exceeded concurrent guest session count, good' % (i,)); + break; + + cCurSessions = aoMultiSessions[i].getSessionCount(self.oTstDrv.oVBoxMgr); + if cCurSessions is not cMaxGuestSessions: + return (reporter.error('Final session count %d, expected %d ' % (cCurSessions, cMaxGuestSessions,)), oTxsSession); + + reporter.log2('Closing MultiSessions ...'); + for i in xrange(cMaxGuestSessions): + # Close this session: + oClosedGuestSession = aoMultiSessions[i].oGuestSession; + fRc2 = aoMultiSessions[i].closeSession(); + cCurSessions = aoMultiSessions[i].getSessionCount(self.oTstDrv.oVBoxMgr) + reporter.log2('MultiSession #%d count is %d' % (i, cCurSessions,)); + if fRc2 is False: + fRc = reporter.error('Closing MultiSession #%d failed' % (i,)); + elif cCurSessions != cMaxGuestSessions - (i + 1): + fRc = reporter.error('Expected %d session after closing #%d, got %d instead' + % (cMaxGuestSessions - (i + 1), cCurSessions, i,)); + assert aoMultiSessions[i].oGuestSession is None or not fRc2; + ## @todo any way to check that the session is closed other than the 'sessions' attribute? + + # Try check that none of the remaining sessions got closed. + try: + aoGuestSessions = self.oTstDrv.oVBoxMgr.getArray(atTests[0][0].oGuest, 'sessions'); + except: + return (reporter.errorXcpt('i=%d/%d' % (i, cMaxGuestSessions,)), oTxsSession); + if oClosedGuestSession in aoGuestSessions: + fRc = reporter.error('i=%d/%d: %s should not be in %s' + % (i, cMaxGuestSessions, oClosedGuestSession, aoGuestSessions)); + if i + 1 < cMaxGuestSessions: # Not sure what xrange(2,2) does... + for j in xrange(i + 1, cMaxGuestSessions): + if aoMultiSessions[j].oGuestSession not in aoGuestSessions: + fRc = reporter.error('i=%d/j=%d/%d: %s should be in %s' + % (i, j, cMaxGuestSessions, aoMultiSessions[j].oGuestSession, aoGuestSessions)); + ## @todo any way to check that they work? + + ## @todo Test session timeouts. + + return (fRc, oTxsSession); + + def testGuestCtrlSessionFileRefs(self, oSession, oTxsSession, oTestVm): + """ + Tests the guest session file reference handling. + """ + + # Find a file to play around with: + sFile = self.oTstDrv.getGuestSystemFileForReading(oTestVm); + + # Use credential defaults. + oCreds = tdCtxCreds(); + oCreds.applyDefaultsIfNotSet(oTestVm); + + # Number of stale guest files to create. + cStaleFiles = 10; + + # + # Start a session. + # + aeWaitFor = [ vboxcon.GuestSessionWaitForFlag_Start ]; + try: + oGuest = oSession.o.console.guest; + oGuestSession = oGuest.createSession(oCreds.sUser, oCreds.sPassword, oCreds.sDomain, "testGuestCtrlSessionFileRefs"); + eWaitResult = oGuestSession.waitForArray(aeWaitFor, 30 * 1000); + except: + return (reporter.errorXcpt(), oTxsSession); + + # Be nice to Guest Additions < 4.3: They don't support session handling and therefore return WaitFlagNotSupported. + if eWaitResult not in (vboxcon.GuestSessionWaitResult_Start, vboxcon.GuestSessionWaitResult_WaitFlagNotSupported): + return (reporter.error('Session did not start successfully - wait error: %d' % (eWaitResult,)), oTxsSession); + reporter.log('Session successfully started'); + + # + # Open guest files and "forget" them (stale entries). + # For them we don't have any references anymore intentionally. + # + reporter.log2('Opening stale files'); + fRc = True; + for i in xrange(0, cStaleFiles): + try: + if self.oTstDrv.fpApiVer >= 5.0: + oGuestSession.fileOpen(sFile, vboxcon.FileAccessMode_ReadOnly, vboxcon.FileOpenAction_OpenExisting, 0); + else: + oGuestSession.fileOpen(sFile, "r", "oe", 0); + # Note: Use a timeout in the call above for not letting the stale processes + # hanging around forever. This can happen if the installed Guest Additions + # do not support terminating guest processes. + except: + fRc = reporter.errorXcpt('Opening stale file #%d failed:' % (i,)); + break; + + try: cFiles = len(self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'files')); + except: fRc = reporter.errorXcpt(); + else: + if cFiles != cStaleFiles: + fRc = reporter.error('Got %d stale files, expected %d' % (cFiles, cStaleFiles)); + + if fRc is True: + # + # Open non-stale files and close them again. + # + reporter.log2('Opening non-stale files'); + aoFiles = []; + for i in xrange(0, cStaleFiles): + try: + if self.oTstDrv.fpApiVer >= 5.0: + oCurFile = oGuestSession.fileOpen(sFile, vboxcon.FileAccessMode_ReadOnly, + vboxcon.FileOpenAction_OpenExisting, 0); + else: + oCurFile = oGuestSession.fileOpen(sFile, "r", "oe", 0); + aoFiles.append(oCurFile); + except: + fRc = reporter.errorXcpt('Opening non-stale file #%d failed:' % (i,)); + break; + + # Check the count. + try: cFiles = len(self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'files')); + except: fRc = reporter.errorXcpt(); + else: + if cFiles != cStaleFiles * 2: + fRc = reporter.error('Got %d total files, expected %d' % (cFiles, cStaleFiles * 2)); + + # Close them. + reporter.log2('Closing all non-stale files again ...'); + for i, oFile in enumerate(aoFiles): + try: + oFile.close(); + except: + fRc = reporter.errorXcpt('Closing non-stale file #%d failed:' % (i,)); + + # Check the count again. + try: cFiles = len(self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'files')); + except: fRc = reporter.errorXcpt(); + # Here we count the stale files (that is, files we don't have a reference + # anymore for) and the opened and then closed non-stale files (that we still keep + # a reference in aoFiles[] for). + if cFiles != cStaleFiles: + fRc = reporter.error('Got %d total files, expected %d' % (cFiles, cStaleFiles)); + + # + # Check that all (referenced) non-stale files are now in the "closed" state. + # + reporter.log2('Checking statuses of all non-stale files ...'); + for i, oFile in enumerate(aoFiles): + try: + eFileStatus = aoFiles[i].status; + except: + fRc = reporter.errorXcpt('Checking status of file #%d failed:' % (i,)); + else: + if eFileStatus != vboxcon.FileStatus_Closed: + fRc = reporter.error('Non-stale file #%d has status %d, expected %d' + % (i, eFileStatus, vboxcon.FileStatus_Closed)); + + if fRc is True: + reporter.log2('All non-stale files closed'); + + try: cFiles = len(self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'files')); + except: fRc = reporter.errorXcpt(); + else: reporter.log2('Final guest session file count: %d' % (cFiles,)); + + # + # Now try to close the session and see what happens. + # Note! Session closing is why we've been doing all the 'if fRc is True' stuff above rather than returning. + # + reporter.log2('Closing guest session ...'); + try: + oGuestSession.close(); + except: + fRc = reporter.errorXcpt('Testing for stale processes failed:'); + + return (fRc, oTxsSession); + + #def testGuestCtrlSessionDirRefs(self, oSession, oTxsSession, oTestVm): + # """ + # Tests the guest session directory reference handling. + # """ + + # fRc = True; + # return (fRc, oTxsSession); + + def testGuestCtrlSessionProcRefs(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals,too-many-statements + """ + Tests the guest session process reference handling. + """ + + sShell = self.oTstDrv.getGuestSystemShell(oTestVm); + asArgs = [sShell,]; + + # Use credential defaults. + oCreds = tdCtxCreds(); + oCreds.applyDefaultsIfNotSet(oTestVm); + + # Number of guest processes per group to create. + cProcsPerGroup = 10; + + # + # Start a session. + # + aeWaitFor = [ vboxcon.GuestSessionWaitForFlag_Start ]; + try: + oGuest = oSession.o.console.guest; + oGuestSession = oGuest.createSession(oCreds.sUser, oCreds.sPassword, oCreds.sDomain, "testGuestCtrlSessionProcRefs"); + eWaitResult = oGuestSession.waitForArray(aeWaitFor, 30 * 1000); + except: + return (reporter.errorXcpt(), oTxsSession); + + # Be nice to Guest Additions < 4.3: They don't support session handling and therefore return WaitFlagNotSupported. + if eWaitResult not in (vboxcon.GuestSessionWaitResult_Start, vboxcon.GuestSessionWaitResult_WaitFlagNotSupported): + return (reporter.error('Session did not start successfully - wait error: %d' % (eWaitResult,)), oTxsSession); + reporter.log('Session successfully started'); + + # + # Fire off forever-running processes and "forget" them (stale entries). + # For them we don't have any references anymore intentionally. + # + reporter.log('Starting stale processes...'); + fRc = True; + for i in xrange(0, cProcsPerGroup): + try: + reporter.log2('Starting stale process #%d...' % (i)); + oGuestSession.processCreate(sShell, + asArgs if self.oTstDrv.fpApiVer >= 5.0 else asArgs[1:], [], + [ vboxcon.ProcessCreateFlag_WaitForStdOut ], 30 * 1000); + # Note: Not keeping a process reference from the created process above is intentional and part of the test! + + # Note: Use a timeout in the call above for not letting the stale processes + # hanging around forever. This can happen if the installed Guest Additions + # do not support terminating guest processes. + except: + fRc = reporter.errorXcpt('Creating stale process #%d failed:' % (i,)); + break; + + if fRc: + reporter.log2('Starting stale processes successful'); + try: cProcs = len(self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'processes')); + except: fRc = reporter.errorXcpt(); + else: + reporter.log2('Proccess count is: %d' % (cProcs)); + if cProcs != cProcsPerGroup: + fRc = reporter.error('Got %d stale processes, expected %d (stale)' % (cProcs, cProcsPerGroup)); + + if fRc: + # + # Fire off non-stale processes and wait for termination. + # + if oTestVm.isWindows() or oTestVm.isOS2(): + asArgs = [ sShell, '/C', 'dir', '/S', self.oTstDrv.getGuestSystemDir(oTestVm), ]; + else: + asArgs = [ sShell, '-c', 'ls -la ' + self.oTstDrv.getGuestSystemDir(oTestVm), ]; + reporter.log('Starting non-stale processes...'); + aoProcs = []; + for i in xrange(0, cProcsPerGroup): + try: + reporter.log2('Starting non-stale process #%d...' % (i)); + oCurProc = oGuestSession.processCreate(sShell, asArgs if self.oTstDrv.fpApiVer >= 5.0 else asArgs[1:], + [], [], 0); # Infinite timeout. + aoProcs.append(oCurProc); + except: + fRc = reporter.errorXcpt('Creating non-stale process #%d failed:' % (i,)); + break; + + try: cProcs = len(self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'processes')); + except: fRc = reporter.errorXcpt(); + else: + reporter.log2('Proccess count is: %d' % (cProcs)); + + reporter.log('Waiting for non-stale processes to terminate...'); + for i, oProcess in enumerate(aoProcs): + try: + reporter.log('Waiting for non-stale process #%d...' % (i)); + eWaitResult = oProcess.waitForArray([ vboxcon.ProcessWaitForFlag_Terminate, ], 30 * 1000); + eProcessStatus = oProcess.status; + except: + fRc = reporter.errorXcpt('Waiting for non-stale process #%d failed:' % (i,)); + else: + if eProcessStatus != vboxcon.ProcessStatus_TerminatedNormally: + fRc = reporter.error('Waiting for non-stale processes #%d resulted in status %d, expected %d (wr=%d)' + % (i, eProcessStatus, vboxcon.ProcessStatus_TerminatedNormally, eWaitResult)); + if fRc: + reporter.log('All non-stale processes ended successfully'); + + try: cProcs = len(self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'processes')); + except: fRc = reporter.errorXcpt(); + else: + reporter.log2('Proccess count is: %d' % (cProcs)); + + # Here we count the stale processes (that is, processes we don't have a reference + # anymore for) and the started + ended non-stale processes (that we still keep + # a reference in aoProcesses[] for). + cProcsExpected = cProcsPerGroup * 2; + if cProcs != cProcsExpected: + fRc = reporter.error('Got %d total processes, expected %d (stale vs. non-stale)' \ + % (cProcs, cProcsExpected)); + # + # Fire off non-stale blocking processes which are terminated via terminate(). + # + if oTestVm.isWindows() or oTestVm.isOS2(): + sCmd = sShell; + asArgs = [ sCmd, '/C', 'pause']; + else: + sCmd = '/usr/bin/yes'; + asArgs = [ sCmd ]; + reporter.log('Starting blocking processes...'); + aoProcs = []; + for i in xrange(0, cProcsPerGroup): + try: + reporter.log2('Starting blocking process #%d...' % (i)); + oCurProc = oGuestSession.processCreate(sCmd, asArgs if self.oTstDrv.fpApiVer >= 5.0 else asArgs[1:], + [], [], 30 * 1000); + # Note: Use a timeout in the call above for not letting the stale processes + # hanging around forever. This can happen if the installed Guest Additions + # do not support terminating guest processes. + try: + reporter.log('Waiting for blocking process #%d getting started...' % (i)); + eWaitResult = oCurProc.waitForArray([ vboxcon.ProcessWaitForFlag_Start, ], 30 * 1000); + eProcessStatus = oCurProc.status; + except: + fRc = reporter.errorXcpt('Waiting for blocking process #%d failed:' % (i,)); + else: + if eProcessStatus != vboxcon.ProcessStatus_Started: + fRc = reporter.error('Waiting for blocking processes #%d resulted in status %d, expected %d (wr=%d)' + % (i, eProcessStatus, vboxcon.ProcessStatus_Started, eWaitResult)); + aoProcs.append(oCurProc); + except: + fRc = reporter.errorXcpt('Creating blocking process #%d failed:' % (i,)); + break; + + if fRc: + reporter.log2('Starting blocking processes successful'); + + reporter.log2('Terminating blocking processes...'); + for i, oProcess in enumerate(aoProcs): + try: + reporter.log('Terminating blocking process #%d...' % (i)); + oProcess.terminate(); + except: # Termination might not be supported, just skip and log it. + reporter.logXcpt('Termination of blocking process #%d failed, skipped:' % (i,)); + if self.oTstDrv.fpApiVer >= 6.1: # Termination is supported since 5.2 or so. + fRc = False; + if fRc: + reporter.log('All blocking processes were terminated successfully'); + + try: cProcs = len(self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'processes')); + except: fRc = reporter.errorXcpt(); + else: + # There still should be 20 processes because we just terminated the 10 blocking ones above. + cProcsExpected = cProcsPerGroup * 2; + if cProcs != cProcsExpected: + fRc = reporter.error('Got %d total processes, expected %d (final)' % (cProcs, cProcsExpected)); + reporter.log2('Final guest session processes count: %d' % (cProcs,)); + + if not fRc: + aoProcs = self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'processes'); + for i, oProc in enumerate(aoProcs): + try: + aoArgs = self.oTstDrv.oVBoxMgr.getArray(oProc, 'arguments'); + reporter.log('Process %d (\'%s\') still around, status is %d' \ + % (i, ' '.join([str(x) for x in aoArgs]), oProc.status)); + except: + reporter.errorXcpt('Process lookup failed:'); + # + # Now try to close the session and see what happens. + # + reporter.log('Closing guest session ...'); + try: + oGuestSession.close(); + except: + fRc = reporter.errorXcpt('Closing session for testing process references failed:'); + + return (fRc, oTxsSession); + + def testGuestCtrlExec(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals,too-many-statements + """ + Tests the basic execution feature. + """ + + # Paths: + sVBoxControl = None; ## @todo Get path of installed Guest Additions. Later. + sShell = self.oTstDrv.getGuestSystemShell(oTestVm); + sShellOpt = '/C' if oTestVm.isWindows() or oTestVm.isOS2() else '-c'; + sSystemDir = self.oTstDrv.getGuestSystemDir(oTestVm); + sFileForReading = self.oTstDrv.getGuestSystemFileForReading(oTestVm); + if oTestVm.isWindows() or oTestVm.isOS2(): + sImageOut = self.oTstDrv.getGuestSystemShell(oTestVm); + if oTestVm.isWindows(): + sVBoxControl = "C:\\Program Files\\Oracle\\VirtualBox Guest Additions\\VBoxControl.exe"; + else: + sImageOut = oTestVm.pathJoin(self.oTstDrv.getGuestSystemDir(oTestVm), 'ls'); + if oTestVm.isLinux(): ## @todo check solaris and darwin. + sVBoxControl = "/usr/bin/VBoxControl"; # Symlink + + # Use credential defaults. + oCreds = tdCtxCreds(); + oCreds.applyDefaultsIfNotSet(oTestVm); + + atInvalid = [ + # Invalid parameters. + [ tdTestExec(), tdTestResultExec() ], + # Non-existent / invalid image. + [ tdTestExec(sCmd = "non-existent"), tdTestResultExec() ], + [ tdTestExec(sCmd = "non-existent2"), tdTestResultExec() ], + # Use an invalid format string. + [ tdTestExec(sCmd = "%$%%%&"), tdTestResultExec() ], + # More stuff. + [ tdTestExec(sCmd = u"ƒ‰‹ˆ÷‹¸"), tdTestResultExec() ], + [ tdTestExec(sCmd = "???://!!!"), tdTestResultExec() ], + [ tdTestExec(sCmd = "<>!\\"), tdTestResultExec() ], + # Enable as soon as ERROR_BAD_DEVICE is implemented. + #[ tdTestExec(sCmd = "CON", tdTestResultExec() ], + ]; + + atExec = []; + if oTestVm.isWindows() or oTestVm.isOS2(): + atExec += [ + # Basic execution. + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', sSystemDir ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', sFileForReading ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', sSystemDir + '\\nonexist.dll' ]), + tdTestResultExec(fRc = True, iExitCode = 1) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', '/wrongparam' ]), + tdTestResultExec(fRc = True, iExitCode = 1) ], + [ tdTestExec(sCmd = sShell, asArgs = [ sShell, sShellOpt, 'wrongcommand' ]), + tdTestResultExec(fRc = True, iExitCode = 1) ], + # StdOut. + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', sSystemDir ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', 'stdout-non-existing' ]), + tdTestResultExec(fRc = True, iExitCode = 1) ], + # StdErr. + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', sSystemDir ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', 'stderr-non-existing' ]), + tdTestResultExec(fRc = True, iExitCode = 1) ], + # StdOut + StdErr. + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', sSystemDir ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', 'stdouterr-non-existing' ]), + tdTestResultExec(fRc = True, iExitCode = 1) ], + ]; + # atExec.extend([ + # FIXME: Failing tests. + # Environment variables. + # [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'set', 'TEST_NONEXIST' ], + # tdTestResultExec(fRc = True, iExitCode = 1) ] + # [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'set', 'windir' ], + # afFlags = [ vboxcon.ProcessCreateFlag_WaitForStdOut, vboxcon.ProcessCreateFlag_WaitForStdErr ]), + # tdTestResultExec(fRc = True, sBuf = 'windir=C:\\WINDOWS\r\n') ], + # [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'set', 'TEST_FOO' ], + # aEnv = [ 'TEST_FOO=BAR' ], + # afFlags = [ vboxcon.ProcessCreateFlag_WaitForStdOut, vboxcon.ProcessCreateFlag_WaitForStdErr ]), + # tdTestResultExec(fRc = True, sBuf = 'TEST_FOO=BAR\r\n') ], + # [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'set', 'TEST_FOO' ], + # aEnv = [ 'TEST_FOO=BAR', 'TEST_BAZ=BAR' ], + # afFlags = [ vboxcon.ProcessCreateFlag_WaitForStdOut, vboxcon.ProcessCreateFlag_WaitForStdErr ]), + # tdTestResultExec(fRc = True, sBuf = 'TEST_FOO=BAR\r\n') ] + + ## @todo Create some files (or get files) we know the output size of to validate output length! + ## @todo Add task which gets killed at some random time while letting the guest output something. + #]; + else: + atExec += [ + # Basic execution. + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '-R', sSystemDir ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, sFileForReading ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '--wrong-parameter' ]), + tdTestResultExec(fRc = True, iExitCode = 2) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/non/existent' ]), + tdTestResultExec(fRc = True, iExitCode = 2) ], + [ tdTestExec(sCmd = sShell, asArgs = [ sShell, sShellOpt, 'wrongcommand' ]), + tdTestResultExec(fRc = True, iExitCode = 127) ], + # StdOut. + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, sSystemDir ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, 'stdout-non-existing' ]), + tdTestResultExec(fRc = True, iExitCode = 2) ], + # StdErr. + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, sSystemDir ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, 'stderr-non-existing' ]), + tdTestResultExec(fRc = True, iExitCode = 2) ], + # StdOut + StdErr. + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, sSystemDir ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, 'stdouterr-non-existing' ]), + tdTestResultExec(fRc = True, iExitCode = 2) ], + ]; + # atExec.extend([ + # FIXME: Failing tests. + # Environment variables. + # [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'set', 'TEST_NONEXIST' ], + # tdTestResultExec(fRc = True, iExitCode = 1) ] + # [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'set', 'windir' ], + # + # afFlags = [ vboxcon.ProcessCreateFlag_WaitForStdOut, vboxcon.ProcessCreateFlag_WaitForStdErr ]), + # tdTestResultExec(fRc = True, sBuf = 'windir=C:\\WINDOWS\r\n') ], + # [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'set', 'TEST_FOO' ], + # aEnv = [ 'TEST_FOO=BAR' ], + # afFlags = [ vboxcon.ProcessCreateFlag_WaitForStdOut, vboxcon.ProcessCreateFlag_WaitForStdErr ]), + # tdTestResultExec(fRc = True, sBuf = 'TEST_FOO=BAR\r\n') ], + # [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'set', 'TEST_FOO' ], + # aEnv = [ 'TEST_FOO=BAR', 'TEST_BAZ=BAR' ], + # afFlags = [ vboxcon.ProcessCreateFlag_WaitForStdOut, vboxcon.ProcessCreateFlag_WaitForStdErr ]), + # tdTestResultExec(fRc = True, sBuf = 'TEST_FOO=BAR\r\n') ] + + ## @todo Create some files (or get files) we know the output size of to validate output length! + ## @todo Add task which gets killed at some random time while letting the guest output something. + #]; + + # + #for iExitCode in xrange(0, 127): + # atExec.append([ tdTestExec(sCmd = sShell, asArgs = [ sShell, sShellOpt, 'exit %s' % iExitCode ]), + # tdTestResultExec(fRc = True, iExitCode = iExitCode) ]); + + if sVBoxControl \ + and self.oTstDrv.fpApiVer >= 6.0: # Investigate with this doesn't work on (<) 5.2. + # Paths with spaces on windows. + atExec.append([ tdTestExec(sCmd = sVBoxControl, asArgs = [ sVBoxControl, 'version' ], + afFlags = [ vboxcon.ProcessCreateFlag_WaitForStdOut, + vboxcon.ProcessCreateFlag_WaitForStdErr ]), + tdTestResultExec(fRc = True) ]); + + # Test very long arguments. Be careful when tweaking this to not break the tests. + # Regarding paths: + # - We have RTPATH_BIG_MAX (64K) + # - MSDN says 32K for CreateFileW() + # - On Windows, each path component must not be longer than MAX_PATH (260), see + # https://docs.microsoft.com/en-us/windows/win32/fileio/filesystem-functionality-comparison#limits + # + # Old(er) Windows OSes tend to crash in cmd.exe, so skip this on these OSes. + if self.oTstDrv.fpApiVer >= 6.1 \ + and oTestVm.sKind not in ('WindowsNT4', 'Windows2000', 'WindowsXP', 'Windows2003'): + sEndMarker = '--end-marker'; + if oTestVm.isWindows() \ + or oTestVm.isOS2(): + sCmd = sShell; + else: + sCmd = oTestVm.pathJoin(self.oTstDrv.getGuestSystemDir(oTestVm), 'echo'); + + for _ in xrange(0, 16): + if oTestVm.isWindows() \ + or oTestVm.isOS2(): + asArgs = [ sShell, sShellOpt, "echo" ]; + else: + asArgs = [ sCmd ]; + + # Append a random number of arguments with random length. + for _ in xrange(0, self.oTestFiles.oRandom.randrange(1, 64)): + asArgs.append(''.join(random.choice(string.ascii_lowercase) + for _ in range(self.oTestFiles.oRandom.randrange(1, 196)))); + + asArgs.append(sEndMarker); + + reporter.log2('asArgs=%s (%d args), type=%s' % (limitString(asArgs), len(asArgs), type(asArgs))); + + ## @todo Check limits; on Ubuntu with 256KB IPRT returns VERR_NOT_IMPLEMENTED. + # Use a higher timeout (15 min) than usual for these long checks. + atExec.append([ tdTestExec(sCmd, asArgs, + afFlags = [ vboxcon.ProcessCreateFlag_WaitForStdOut, + vboxcon.ProcessCreateFlag_WaitForStdErr ], + timeoutMS = 15 * 60 * 1000), + tdTestResultExec(fRc = True) ]); + + # Build up the final test array for the first batch. + atTests = atInvalid + atExec; + + # + # First batch: One session per guest process. + # + reporter.log('One session per guest process ...'); + fRc = True; + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0] # type: tdTestExec + oCurRes = tTest[1] # type: tdTestResultExec + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc2, oCurGuestSession = oCurTest.createSession('testGuestCtrlExec: Test #%d' % (i,)); + if fRc2 is not True: + fRc = reporter.error('Test #%d failed: Could not create session' % (i,)); + break; + fRc = self.gctrlExecDoTest(i, oCurTest, oCurRes, oCurGuestSession) and fRc; + fRc = oCurTest.closeSession() and fRc; + + reporter.log('Execution of all tests done, checking for stale sessions'); + + # No sessions left? + try: + aSessions = self.oTstDrv.oVBoxMgr.getArray(oSession.o.console.guest, 'sessions'); + except: + fRc = reporter.errorXcpt(); + else: + cSessions = len(aSessions); + if cSessions != 0: + fRc = reporter.error('Found %d stale session(s), expected 0:' % (cSessions,)); + for (i, aSession) in enumerate(aSessions): + try: reporter.log(' Stale session #%d ("%s")' % (aSession.id, aSession.name)); + except: reporter.errorXcpt(); + + if fRc is not True: + return (fRc, oTxsSession); + + reporter.log('Now using one guest session for all tests ...'); + + # + # Second batch: One session for *all* guest processes. + # + + # Create session. + reporter.log('Creating session for all tests ...'); + aeWaitFor = [ vboxcon.GuestSessionWaitForFlag_Start, ]; + try: + oGuest = oSession.o.console.guest; + oCurGuestSession = oGuest.createSession(oCreds.sUser, oCreds.sPassword, oCreds.sDomain, + 'testGuestCtrlExec: One session for all tests'); + except: + return (reporter.errorXcpt(), oTxsSession); + + try: + eWaitResult = oCurGuestSession.waitForArray(aeWaitFor, 30 * 1000); + except: + fRc = reporter.errorXcpt('Waiting for guest session to start failed:'); + else: + if eWaitResult not in (vboxcon.GuestSessionWaitResult_Start, vboxcon.GuestSessionWaitResult_WaitFlagNotSupported): + fRc = reporter.error('Session did not start successfully, returned wait result: %d' % (eWaitResult,)); + else: + reporter.log('Session successfully started'); + + # Do the tests within this session. + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0] # type: tdTestExec + oCurRes = tTest[1] # type: tdTestResultExec + + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc = self.gctrlExecDoTest(i, oCurTest, oCurRes, oCurGuestSession); + if fRc is False: + break; + + # Close the session. + reporter.log2('Closing guest session ...'); + try: + oCurGuestSession.close(); + oCurGuestSession = None; + except: + fRc = reporter.errorXcpt('Closing guest session failed:'); + + # No sessions left? + reporter.log('Execution of all tests done, checking for stale sessions again'); + try: cSessions = len(self.oTstDrv.oVBoxMgr.getArray(oSession.o.console.guest, 'sessions')); + except: fRc = reporter.errorXcpt(); + else: + if cSessions != 0: + fRc = reporter.error('Found %d stale session(s), expected 0' % (cSessions,)); + return (fRc, oTxsSession); + + def threadForTestGuestCtrlSessionReboot(self, oGuestProcess): + """ + Thread routine which waits for the stale guest process getting terminated (or some error) + while the main test routine reboots the guest. It then compares the expected guest process result + and logs an error if appropriate. + """ + reporter.log('Waiting for process to get terminated at reboot ...'); + try: + eWaitResult = oGuestProcess.waitForArray([ vboxcon.ProcessWaitForFlag_Terminate ], 5 * 60 * 1000); + except: + return reporter.errorXcpt('waitForArray failed'); + try: + eStatus = oGuestProcess.status + except: + return reporter.errorXcpt('failed to get status (wait result %d)' % (eWaitResult,)); + + if eWaitResult == vboxcon.ProcessWaitResult_Terminate and eStatus == vboxcon.ProcessStatus_Down: + reporter.log('Stale process was correctly terminated (status: down)'); + return True; + + return reporter.error('Process wait across reboot failed: eWaitResult=%d, expected %d; eStatus=%d, expected %d' + % (eWaitResult, vboxcon.ProcessWaitResult_Terminate, eStatus, vboxcon.ProcessStatus_Down,)); + + def testGuestCtrlSessionReboot(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals + """ + Tests guest object notifications when a guest gets rebooted / shutdown. + + These notifications gets sent from the guest sessions in order to make API clients + aware of guest session changes. + + To test that we create a stale guest process and trigger a reboot of the guest. + """ + + ## @todo backport fixes to 6.0 and maybe 5.2 + if self.oTstDrv.fpApiVer <= 6.0: + reporter.log('Skipping: Required fixes not yet backported!'); + return None; + + # Use credential defaults. + oCreds = tdCtxCreds(); + oCreds.applyDefaultsIfNotSet(oTestVm); + + fRebooted = False; + fRc = True; + + # + # Start a session. + # + aeWaitFor = [ vboxcon.GuestSessionWaitForFlag_Start ]; + try: + oGuest = oSession.o.console.guest; + oGuestSession = oGuest.createSession(oCreds.sUser, oCreds.sPassword, oCreds.sDomain, "testGuestCtrlSessionReboot"); + eWaitResult = oGuestSession.waitForArray(aeWaitFor, 30 * 1000); + except: + return (reporter.errorXcpt(), oTxsSession); + + # Be nice to Guest Additions < 4.3: They don't support session handling and therefore return WaitFlagNotSupported. + if eWaitResult not in (vboxcon.GuestSessionWaitResult_Start, vboxcon.GuestSessionWaitResult_WaitFlagNotSupported): + return (reporter.error('Session did not start successfully - wait error: %d' % (eWaitResult,)), oTxsSession); + reporter.log('Session successfully started'); + + # + # Create a process. + # + # That process will also be used later to see if the session cleanup worked upon reboot. + # + sImage = self.oTstDrv.getGuestSystemShell(oTestVm); + asArgs = [ sImage, ]; + aEnv = []; + afFlags = []; + try: + oGuestProcess = oGuestSession.processCreate(sImage, + asArgs if self.oTstDrv.fpApiVer >= 5.0 else asArgs[1:], aEnv, afFlags, + 30 * 1000); + except: + fRc = reporter.error('Failed to start shell process (%s)' % (sImage,)); + else: + try: + eWaitResult = oGuestProcess.waitForArray([ vboxcon.ProcessWaitForFlag_Start ], 30 * 1000); + except: + fRc = reporter.errorXcpt('Waiting for shell process (%s) to start failed' % (sImage,)); + else: + # Check the result and state: + try: eStatus = oGuestProcess.status; + except: fRc = reporter.errorXcpt('Waiting for shell process (%s) to start failed' % (sImage,)); + else: + reporter.log2('Starting process wait result returned: %d; Process status is: %d' % (eWaitResult, eStatus,)); + if eWaitResult != vboxcon.ProcessWaitResult_Start: + fRc = reporter.error('wait for ProcessWaitForFlag_Start failed: %d, expected %d (Start)' + % (eWaitResult, vboxcon.ProcessWaitResult_Start,)); + elif eStatus != vboxcon.ProcessStatus_Started: + fRc = reporter.error('Unexpected process status after startup: %d, wanted %d (Started)' + % (eStatus, vboxcon.ProcessStatus_Started,)); + else: + # Create a thread that waits on the process to terminate + reporter.log('Creating reboot thread ...'); + oThreadReboot = threading.Thread(target = self.threadForTestGuestCtrlSessionReboot, + args = (oGuestProcess,), + name = ('threadForTestGuestCtrlSessionReboot')); + oThreadReboot.setDaemon(True); # pylint: disable=deprecated-method + oThreadReboot.start(); + + # Do the reboot. + reporter.log('Rebooting guest and reconnecting TXS ...'); + (oSession, oTxsSession) = self.oTstDrv.txsRebootAndReconnectViaTcp(oSession, oTxsSession, + cMsTimeout = 3 * 60000); + if oSession \ + and oTxsSession: + # Set reboot flag (needed later for session closing). + fRebooted = True; + else: + reporter.error('Rebooting via TXS failed'); + try: oGuestProcess.terminate(); + except: reporter.logXcpt(); + fRc = False; + + reporter.log('Waiting for thread to finish ...'); + oThreadReboot.join(); + + # Check that the guest session now still has the formerly guest process object created above, + # but with the "down" status now (because of guest reboot). + try: + aoGuestProcs = self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'processes'); + if len(aoGuestProcs) == 1: + enmProcSts = aoGuestProcs[0].status; + if enmProcSts != vboxcon.ProcessStatus_Down: + fRc = reporter.error('Old guest process (before reboot) has status %d, expected %s' \ + % (enmProcSts, vboxcon.ProcessStatus_Down)); + else: + fRc = reporter.error('Old guest session (before reboot) has %d processes registered, expected 1' \ + % (len(aoGuestProcs))); + except: + fRc = reporter.errorXcpt(); + # + # Try make sure we don't leave with a stale process on failure. + # + try: oGuestProcess.terminate(); + except: reporter.logXcpt(); + + # + # Close the session. + # + reporter.log2('Closing guest session ...'); + try: + oGuestSession.close(); + except: + # Closing the guest session will fail when the guest reboot has been triggered, + # as the session object will be cleared on a guest reboot. + if fRebooted: + reporter.logXcpt('Closing guest session failed, good (guest rebooted)'); + else: # ... otherwise this (still) should succeed. Report so if it doesn't. + reporter.errorXcpt('Closing guest session failed'); + + return (fRc, oTxsSession); + + def testGuestCtrlExecTimeout(self, oSession, oTxsSession, oTestVm): + """ + Tests handling of timeouts of started guest processes. + """ + + sShell = self.oTstDrv.getGuestSystemShell(oTestVm); + + # Use credential defaults. + oCreds = tdCtxCreds(); + oCreds.applyDefaultsIfNotSet(oTestVm); + + # + # Create a session. + # + try: + oGuest = oSession.o.console.guest; + oGuestSession = oGuest.createSession(oCreds.sUser, oCreds.sPassword, oCreds.sDomain, "testGuestCtrlExecTimeout"); + eWaitResult = oGuestSession.waitForArray([ vboxcon.GuestSessionWaitForFlag_Start, ], 30 * 1000); + except: + return (reporter.errorXcpt(), oTxsSession); + + # Be nice to Guest Additions < 4.3: They don't support session handling and therefore return WaitFlagNotSupported. + if eWaitResult not in (vboxcon.GuestSessionWaitResult_Start, vboxcon.GuestSessionWaitResult_WaitFlagNotSupported): + return (reporter.error('Session did not start successfully - wait error: %d' % (eWaitResult,)), oTxsSession); + reporter.log('Session successfully started'); + + # + # Create a process which never terminates and should timeout when + # waiting for termination. + # + fRc = True; + try: + oCurProcess = oGuestSession.processCreate(sShell, [sShell,] if self.oTstDrv.fpApiVer >= 5.0 else [], + [], [], 30 * 1000); + except: + fRc = reporter.errorXcpt(); + else: + reporter.log('Waiting for process 1 being started ...'); + try: + eWaitResult = oCurProcess.waitForArray([ vboxcon.ProcessWaitForFlag_Start ], 30 * 1000); + except: + fRc = reporter.errorXcpt(); + else: + if eWaitResult != vboxcon.ProcessWaitResult_Start: + fRc = reporter.error('Waiting for process 1 to start failed, got status %d' % (eWaitResult,)); + else: + for msWait in (1, 32, 2000,): + reporter.log('Waiting for process 1 to time out within %sms ...' % (msWait,)); + try: + eWaitResult = oCurProcess.waitForArray([ vboxcon.ProcessWaitForFlag_Terminate, ], msWait); + except: + fRc = reporter.errorXcpt(); + break; + if eWaitResult != vboxcon.ProcessWaitResult_Timeout: + fRc = reporter.error('Waiting for process 1 did not time out in %sms as expected: %d' + % (msWait, eWaitResult,)); + break; + reporter.log('Waiting for process 1 timed out in %u ms, good' % (msWait,)); + + try: + oCurProcess.terminate(); + except: + reporter.errorXcpt(); + oCurProcess = None; + + # + # Create another process that doesn't terminate, but which will be killed by VBoxService + # because it ran out of execution time (3 seconds). + # + try: + oCurProcess = oGuestSession.processCreate(sShell, [sShell,] if self.oTstDrv.fpApiVer >= 5.0 else [], + [], [], 3 * 1000); + except: + fRc = reporter.errorXcpt(); + else: + reporter.log('Waiting for process 2 being started ...'); + try: + eWaitResult = oCurProcess.waitForArray([ vboxcon.ProcessWaitForFlag_Start ], 30 * 1000); + except: + fRc = reporter.errorXcpt(); + else: + if eWaitResult != vboxcon.ProcessWaitResult_Start: + fRc = reporter.error('Waiting for process 2 to start failed, got status %d' % (eWaitResult,)); + else: + reporter.log('Waiting for process 2 to get killed for running out of execution time ...'); + try: + eWaitResult = oCurProcess.waitForArray([ vboxcon.ProcessWaitForFlag_Terminate, ], 15 * 1000); + except: + fRc = reporter.errorXcpt(); + else: + if eWaitResult != vboxcon.ProcessWaitResult_Timeout: + fRc = reporter.error('Waiting for process 2 did not time out when it should, got wait result %d' + % (eWaitResult,)); + else: + reporter.log('Waiting for process 2 did not time out, good: %s' % (eWaitResult,)); + try: + eStatus = oCurProcess.status; + except: + fRc = reporter.errorXcpt(); + else: + if eStatus != vboxcon.ProcessStatus_TimedOutKilled: + fRc = reporter.error('Status of process 2 wrong; excepted %d, got %d' + % (vboxcon.ProcessStatus_TimedOutKilled, eStatus)); + else: + reporter.log('Status of process 2 is TimedOutKilled (%d) is it should be.' + % (vboxcon.ProcessStatus_TimedOutKilled,)); + try: + oCurProcess.terminate(); + except: + reporter.logXcpt(); + oCurProcess = None; + + # + # Clean up the session. + # + try: + oGuestSession.close(); + except: + fRc = reporter.errorXcpt(); + + return (fRc, oTxsSession); + + def testGuestCtrlDirCreate(self, oSession, oTxsSession, oTestVm): + """ + Tests creation of guest directories. + """ + + sScratch = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), 'testGuestCtrlDirCreate'); + + atTests = [ + # Invalid stuff. + [ tdTestDirCreate(sDirectory = '' ), tdTestResultFailure() ], + # More unusual stuff. + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin('..', '.') ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin('..', '..') ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = '..' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = '../' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = '../../' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = '/' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = '/..' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = '/../' ), tdTestResultFailure() ], + ]; + if oTestVm.isWindows() or oTestVm.isOS2(): + atTests.extend([ + [ tdTestDirCreate(sDirectory = 'C:\\' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = 'C:\\..' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = 'C:\\..\\' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = 'C:/' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = 'C:/.' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = 'C:/./' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = 'C:/..' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = 'C:/../' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = '\\\\uncrulez\\foo' ), tdTestResultFailure() ], + ]); + atTests.extend([ + # Existing directories and files. + [ tdTestDirCreate(sDirectory = self.oTstDrv.getGuestSystemDir(oTestVm) ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = self.oTstDrv.getGuestSystemShell(oTestVm) ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = self.oTstDrv.getGuestSystemFileForReading(oTestVm) ), tdTestResultFailure() ], + # Creating directories. + [ tdTestDirCreate(sDirectory = sScratch ), tdTestResultSuccess() ], + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin(sScratch, 'foo', 'bar', 'baz'), + afFlags = (vboxcon.DirectoryCreateFlag_Parents,) ), tdTestResultSuccess() ], + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin(sScratch, 'foo', 'bar', 'baz'), + afFlags = (vboxcon.DirectoryCreateFlag_Parents,) ), tdTestResultSuccess() ], + # Try format strings as directories. + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin(sScratch, 'foo%sbar%sbaz%d' )), tdTestResultSuccess() ], + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin(sScratch, '%f%%boo%%bar%RI32' )), tdTestResultSuccess() ], + # Long random names. + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin(sScratch, self.oTestFiles.generateFilenameEx(36, 28))), + tdTestResultSuccess() ], + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin(sScratch, self.oTestFiles.generateFilenameEx(140, 116))), + tdTestResultSuccess() ], + # Too long names. ASSUMES a guests has a 255 filename length limitation. + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin(sScratch, self.oTestFiles.generateFilenameEx(2048, 256))), + tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin(sScratch, self.oTestFiles.generateFilenameEx(2048, 256))), + tdTestResultFailure() ], + # Missing directory in path. + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin(sScratch, 'foo1', 'bar') ), tdTestResultFailure() ], + ]); + + fRc = True; + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0] # type: tdTestDirCreate + oCurRes = tTest[1] # type: tdTestResult + reporter.log('Testing #%d, sDirectory="%s" ...' % (i, limitString(oCurTest.sDirectory))); + + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc, oCurGuestSession = oCurTest.createSession('testGuestCtrlDirCreate: Test #%d' % (i,)); + if fRc is False: + return reporter.error('Test #%d failed: Could not create session' % (i,)); + + fRc = self.gctrlCreateDir(oCurTest, oCurRes, oCurGuestSession); + + fRc = oCurTest.closeSession() and fRc; + if fRc is False: + fRc = reporter.error('Test #%d failed' % (i,)); + + return (fRc, oTxsSession); + + def testGuestCtrlDirCreateTemp(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals + """ + Tests creation of temporary directories. + """ + + sSystemDir = self.oTstDrv.getGuestSystemDir(oTestVm); + atTests = [ + # Invalid stuff (template must have one or more trailin 'X'es (upper case only), or a cluster of three or more). + [ tdTestDirCreateTemp(sDirectory = ''), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sDirectory = sSystemDir, fMode = 1234), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'xXx', sDirectory = sSystemDir, fMode = 0o700), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'xxx', sDirectory = sSystemDir, fMode = 0o700), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XXx', sDirectory = sSystemDir, fMode = 0o700), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'bar', sDirectory = 'whatever', fMode = 0o700), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'foo', sDirectory = 'it is not used', fMode = 0o700), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'X,so', sDirectory = 'pointless test', fMode = 0o700), tdTestResultFailure() ], + # Non-existing stuff. + [ tdTestDirCreateTemp(sTemplate = 'XXXXXXX', + sDirectory = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), 'non', 'existing')), + tdTestResultFailure() ], + # Working stuff: + [ tdTestDirCreateTemp(sTemplate = 'X', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm)), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm)), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XXX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm)), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XXXXXXX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm)), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'tmpXXXtst', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm)), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'tmpXXXtst', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm)), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'tmpXXXtst', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm)), + tdTestResultFailure() ], + ]; + + if self.oTstDrv.fpApiVer >= 7.0: + # Weird mode set. + atTests.extend([ + [ tdTestDirCreateTemp(sTemplate = 'XXX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fMode = 0o42333), + tdTestResultFailure() ] + ]); + # Same as working stuff above, but with a different mode set. + atTests.extend([ + [ tdTestDirCreateTemp(sTemplate = 'X', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fMode = 0o777), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fMode = 0o777), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XXX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fMode = 0o777), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XXXXXXX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fMode = 0o777), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'tmpXXXtst', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fMode = 0o777), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'tmpXXXtst', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fMode = 0o777), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'tmpXXXtst', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fMode = 0o777), + tdTestResultFailure() ] + ]); + # Same as working stuff above, but with secure mode set. + atTests.extend([ + [ tdTestDirCreateTemp(sTemplate = 'X', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fSecure = True), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fSecure = True), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XXX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fSecure = True), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XXXXXXX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fSecure = True), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'tmpXXXtst', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), + fSecure = True), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'tmpXXXtst', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), + fSecure = True), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'tmpXXXtst', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), + fSecure = True), + tdTestResultFailure() ] + ]); + + fRc = True; + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0] # type: tdTestDirCreateTemp + oCurRes = tTest[1] # type: tdTestResult + reporter.log('Testing #%d, sTemplate="%s", fMode=%#o, path="%s", secure="%s" ...' % + (i, oCurTest.sTemplate, oCurTest.fMode, oCurTest.sDirectory, oCurTest.fSecure)); + + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc, oCurGuestSession = oCurTest.createSession('testGuestCtrlDirCreateTemp: Test #%d' % (i,)); + if fRc is False: + fRc = reporter.error('Test #%d failed: Could not create session' % (i,)); + break; + + sDirTemp = ''; + try: + sDirTemp = oCurGuestSession.directoryCreateTemp(oCurTest.sTemplate, oCurTest.fMode, + oCurTest.sDirectory, oCurTest.fSecure); + except: + if oCurRes.fRc is True: + fRc = reporter.errorXcpt('Creating temp directory "%s" failed:' % (oCurTest.sDirectory,)); + else: + reporter.logXcpt('Creating temp directory "%s" failed expectedly, skipping:' % (oCurTest.sDirectory,)); + else: + reporter.log2('Temporary directory is: "%s"' % (limitString(sDirTemp),)); + if not sDirTemp: + fRc = reporter.error('Resulting directory is empty!'); + else: + ## @todo This does not work for some unknown reason. + #try: + # if self.oTstDrv.fpApiVer >= 5.0: + # fExists = oCurGuestSession.directoryExists(sDirTemp, False); + # else: + # fExists = oCurGuestSession.directoryExists(sDirTemp); + #except: + # fRc = reporter.errorXcpt('sDirTemp=%s' % (sDirTemp,)); + #else: + # if fExists is not True: + # fRc = reporter.error('Test #%d failed: Temporary directory "%s" does not exists (%s)' + # % (i, sDirTemp, fExists)); + try: + oFsObjInfo = oCurGuestSession.fsObjQueryInfo(sDirTemp, False); + eType = oFsObjInfo.type; + except: + fRc = reporter.errorXcpt('sDirTemp="%s"' % (sDirTemp,)); + else: + reporter.log2('%s: eType=%s (dir=%d)' % (limitString(sDirTemp), eType, vboxcon.FsObjType_Directory,)); + if eType != vboxcon.FsObjType_Directory: + fRc = reporter.error('Temporary directory "%s" not created as a directory: eType=%d' + % (sDirTemp, eType)); + fRc = oCurTest.closeSession() and fRc; + return (fRc, oTxsSession); + + def testGuestCtrlDirRead(self, oSession, oTxsSession, oTestVm): + """ + Tests opening and reading (enumerating) guest directories. + """ + + sSystemDir = self.oTstDrv.getGuestSystemDir(oTestVm); + atTests = [ + # Invalid stuff. + [ tdTestDirRead(sDirectory = ''), tdTestResultDirRead() ], + [ tdTestDirRead(sDirectory = sSystemDir, afFlags = [ 1234 ]), tdTestResultDirRead() ], + [ tdTestDirRead(sDirectory = sSystemDir, sFilter = '*.foo'), tdTestResultDirRead() ], + # Non-existing stuff. + [ tdTestDirRead(sDirectory = oTestVm.pathJoin(sSystemDir, 'really-no-such-subdir')), tdTestResultDirRead() ], + [ tdTestDirRead(sDirectory = oTestVm.pathJoin(sSystemDir, 'non', 'existing')), tdTestResultDirRead() ], + ]; + + if oTestVm.isWindows() or oTestVm.isOS2(): + atTests.extend([ + # More unusual stuff. + [ tdTestDirRead(sDirectory = 'z:\\'), tdTestResultDirRead() ], + [ tdTestDirRead(sDirectory = '\\\\uncrulez\\foo'), tdTestResultDirRead() ], + ]); + + # Read the system directory (ASSUMES at least 5 files in it): + # Windows 7+ has inaccessible system32/com/dmp directory that screws up this test, so skip it on windows: + if not oTestVm.isWindows(): + atTests.append([ tdTestDirRead(sDirectory = sSystemDir), + tdTestResultDirRead(fRc = True, cFiles = -5, cDirs = None) ]); + ## @todo trailing slash + + # Read from the test file set. + atTests.extend([ + [ tdTestDirRead(sDirectory = self.oTestFiles.oEmptyDir.sPath), + tdTestResultDirRead(fRc = True, cFiles = 0, cDirs = 0, cOthers = 0) ], + [ tdTestDirRead(sDirectory = self.oTestFiles.oManyDir.sPath), + tdTestResultDirRead(fRc = True, cFiles = len(self.oTestFiles.oManyDir.aoChildren), cDirs = 0, cOthers = 0) ], + [ tdTestDirRead(sDirectory = self.oTestFiles.oTreeDir.sPath), + tdTestResultDirRead(fRc = True, cFiles = self.oTestFiles.cTreeFiles, cDirs = self.oTestFiles.cTreeDirs, + cOthers = self.oTestFiles.cTreeOthers) ], + ]); + + + fRc = True; + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0] # type: tdTestExec + oCurRes = tTest[1] # type: tdTestResultDirRead + + reporter.log('Testing #%d, dir="%s" ...' % (i, oCurTest.sDirectory)); + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc, oCurGuestSession = oCurTest.createSession('testGuestCtrlDirRead: Test #%d' % (i,)); + if fRc is not True: + break; + (fRc2, cDirs, cFiles, cOthers) = self.gctrlReadDirTree(oCurTest, oCurGuestSession, oCurRes.fRc); + fRc = oCurTest.closeSession() and fRc; + + reporter.log2('Test #%d: Returned %d directories, %d files total' % (i, cDirs, cFiles)); + if fRc2 is oCurRes.fRc: + if fRc2 is True: + if oCurRes.cFiles is None: + pass; # ignore + elif oCurRes.cFiles >= 0 and cFiles != oCurRes.cFiles: + fRc = reporter.error('Test #%d failed: Got %d files, expected %d' % (i, cFiles, oCurRes.cFiles)); + elif oCurRes.cFiles < 0 and cFiles < -oCurRes.cFiles: + fRc = reporter.error('Test #%d failed: Got %d files, expected at least %d' + % (i, cFiles, -oCurRes.cFiles)); + if oCurRes.cDirs is None: + pass; # ignore + elif oCurRes.cDirs >= 0 and cDirs != oCurRes.cDirs: + fRc = reporter.error('Test #%d failed: Got %d directories, expected %d' % (i, cDirs, oCurRes.cDirs)); + elif oCurRes.cDirs < 0 and cDirs < -oCurRes.cDirs: + fRc = reporter.error('Test #%d failed: Got %d directories, expected at least %d' + % (i, cDirs, -oCurRes.cDirs)); + if oCurRes.cOthers is None: + pass; # ignore + elif oCurRes.cOthers >= 0 and cOthers != oCurRes.cOthers: + fRc = reporter.error('Test #%d failed: Got %d other types, expected %d' % (i, cOthers, oCurRes.cOthers)); + elif oCurRes.cOthers < 0 and cOthers < -oCurRes.cOthers: + fRc = reporter.error('Test #%d failed: Got %d other types, expected at least %d' + % (i, cOthers, -oCurRes.cOthers)); + + else: + fRc = reporter.error('Test #%d failed: Got %s, expected %s' % (i, fRc2, oCurRes.fRc)); + + + # + # Go over a few directories in the test file set and compare names, + # types and sizes rather than just the counts like we did above. + # + if fRc is True: + oCurTest = tdTestDirRead(); + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if fRc: + fRc, oCurGuestSession = oCurTest.createSession('testGuestCtrlDirRead: gctrlReadDirTree2'); + if fRc is True: + for oDir in (self.oTestFiles.oEmptyDir, self.oTestFiles.oManyDir, self.oTestFiles.oTreeDir): + reporter.log('Checking "%s" ...' % (oDir.sPath,)); + fRc = self.gctrlReadDirTree2(oCurGuestSession, oDir) and fRc; + fRc = oCurTest.closeSession() and fRc; + + return (fRc, oTxsSession); + + + def testGuestCtrlFileRemove(self, oSession, oTxsSession, oTestVm): + """ + Tests removing guest files. + """ + + # + # Create a directory with a few files in it using TXS that we'll use for the initial tests. + # + asTestDirs = [ + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-1'), # [0] + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-1', 'subdir-1'), # [1] + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-1', 'subdir-1', 'subsubdir-1'), # [2] + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-2'), # [3] + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-2', 'subdir-2'), # [4] + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-2', 'subdir-2', 'subsbudir-2'), # [5] + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-3'), # [6] + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-4'), # [7] + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-5'), # [8] + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-5', 'subdir-5'), # [9] + ] + asTestFiles = [ + oTestVm.pathJoin(asTestDirs[0], 'file-0'), # [0] + oTestVm.pathJoin(asTestDirs[0], 'file-1'), # [1] + oTestVm.pathJoin(asTestDirs[0], 'file-2'), # [2] + oTestVm.pathJoin(asTestDirs[1], 'file-3'), # [3] - subdir-1 + oTestVm.pathJoin(asTestDirs[1], 'file-4'), # [4] - subdir-1 + oTestVm.pathJoin(asTestDirs[2], 'file-5'), # [5] - subsubdir-1 + oTestVm.pathJoin(asTestDirs[3], 'file-6'), # [6] - rmtestdir-2 + oTestVm.pathJoin(asTestDirs[4], 'file-7'), # [7] - subdir-2 + oTestVm.pathJoin(asTestDirs[5], 'file-8'), # [8] - subsubdir-2 + ]; + for sDir in asTestDirs: + if oTxsSession.syncMkDir(sDir, 0o777) is not True: + return reporter.error('Failed to create test dir "%s"!' % (sDir,)); + for sFile in asTestFiles: + if oTxsSession.syncUploadString(sFile, sFile, 0o666) is not True: + return reporter.error('Failed to create test file "%s"!' % (sFile,)); + + # + # Tear down the directories and files. + # + aoTests = [ + # Negative tests first: + tdTestRemoveFile(asTestDirs[0], fRcExpect = False), + tdTestRemoveDir(asTestDirs[0], fRcExpect = False), + tdTestRemoveDir(asTestFiles[0], fRcExpect = False), + tdTestRemoveFile(oTestVm.pathJoin(self.oTestFiles.oEmptyDir.sPath, 'no-such-file'), fRcExpect = False), + tdTestRemoveDir(oTestVm.pathJoin(self.oTestFiles.oEmptyDir.sPath, 'no-such-dir'), fRcExpect = False), + tdTestRemoveFile(oTestVm.pathJoin(self.oTestFiles.oEmptyDir.sPath, 'no-such-dir', 'no-file'), fRcExpect = False), + tdTestRemoveDir(oTestVm.pathJoin(self.oTestFiles.oEmptyDir.sPath, 'no-such-dir', 'no-subdir'), fRcExpect = False), + tdTestRemoveTree(asTestDirs[0], afFlags = [], fRcExpect = False), # Only removes empty dirs, this isn't empty. + tdTestRemoveTree(asTestDirs[0], afFlags = [vboxcon.DirectoryRemoveRecFlag_None,], fRcExpect = False), # ditto + # Empty paths: + tdTestRemoveFile('', fRcExpect = False), + tdTestRemoveDir('', fRcExpect = False), + tdTestRemoveTree('', fRcExpect = False), + # Now actually remove stuff: + tdTestRemoveDir(asTestDirs[7], fRcExpect = True), + tdTestRemoveFile(asTestDirs[6], fRcExpect = False), + tdTestRemoveDir(asTestDirs[6], fRcExpect = True), + tdTestRemoveFile(asTestFiles[0], fRcExpect = True), + tdTestRemoveFile(asTestFiles[0], fRcExpect = False), + # 17: + tdTestRemoveTree(asTestDirs[8], fRcExpect = True), # Removes empty subdirs and leaves the dir itself. + tdTestRemoveDir(asTestDirs[8], fRcExpect = True), + tdTestRemoveTree(asTestDirs[3], fRcExpect = False), # Have subdirs & files, + tdTestRemoveTree(asTestDirs[3], afFlags = [vboxcon.DirectoryRemoveRecFlag_ContentOnly,], fRcExpect = True), + tdTestRemoveDir(asTestDirs[3], fRcExpect = True), + tdTestRemoveTree(asTestDirs[0], afFlags = [vboxcon.DirectoryRemoveRecFlag_ContentAndDir,], fRcExpect = True), + # No error if already delete (RTDirRemoveRecursive artifact). + tdTestRemoveTree(asTestDirs[0], afFlags = [vboxcon.DirectoryRemoveRecFlag_ContentAndDir,], fRcExpect = True), + tdTestRemoveTree(asTestDirs[0], afFlags = [vboxcon.DirectoryRemoveRecFlag_ContentOnly,], + fNotExist = True, fRcExpect = True), + tdTestRemoveTree(asTestDirs[0], afFlags = [vboxcon.DirectoryRemoveRecFlag_None,], fNotExist = True, fRcExpect = True), + ]; + + # + # Execution loop + # + fRc = True; + for (i, oTest) in enumerate(aoTests): # int, tdTestRemoveBase + reporter.log('Testing #%d, path="%s" %s ...' % (i, oTest.sPath, oTest.__class__.__name__)); + fRc = oTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc, _ = oTest.createSession('testGuestCtrlFileRemove: Test #%d' % (i,)); + if fRc is False: + fRc = reporter.error('Test #%d failed: Could not create session' % (i,)); + break; + fRc = oTest.execute(self) and fRc; + fRc = oTest.closeSession() and fRc; + + if fRc is True: + oCurTest = tdTestDirRead(); + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if fRc: + fRc, oCurGuestSession = oCurTest.createSession('remove final'); + if fRc is True: + + # + # Delete all the files in the many subdir of the test set. + # + reporter.log('Deleting the file in "%s" ...' % (self.oTestFiles.oManyDir.sPath,)); + for oFile in self.oTestFiles.oManyDir.aoChildren: + reporter.log2('"%s"' % (limitString(oFile.sPath),)); + try: + if self.oTstDrv.fpApiVer >= 5.0: + oCurGuestSession.fsObjRemove(oFile.sPath); + else: + oCurGuestSession.fileRemove(oFile.sPath); + except: + fRc = reporter.errorXcpt('Removing "%s" failed' % (oFile.sPath,)); + + # Remove the directory itself to verify that we've removed all the files in it: + reporter.log('Removing the directory "%s" ...' % (self.oTestFiles.oManyDir.sPath,)); + try: + oCurGuestSession.directoryRemove(self.oTestFiles.oManyDir.sPath); + except: + fRc = reporter.errorXcpt('Removing directory "%s" failed' % (self.oTestFiles.oManyDir.sPath,)); + + # + # Recursively delete the entire test file tree from the root up. + # + # Note! On unix we cannot delete the root dir itself since it is residing + # in /var/tmp where only the owner may delete it. Root is the owner. + # + if oTestVm.isWindows() or oTestVm.isOS2(): + afFlags = [vboxcon.DirectoryRemoveRecFlag_ContentAndDir,]; + else: + afFlags = [vboxcon.DirectoryRemoveRecFlag_ContentOnly,]; + try: + oProgress = oCurGuestSession.directoryRemoveRecursive(self.oTestFiles.oRoot.sPath, afFlags); + except: + fRc = reporter.errorXcpt('Removing tree "%s" failed' % (self.oTestFiles.oRoot.sPath,)); + else: + oWrappedProgress = vboxwrappers.ProgressWrapper(oProgress, self.oTstDrv.oVBoxMgr, self.oTstDrv, + "remove-tree-root: %s" % (self.oTestFiles.oRoot.sPath,)); + reporter.log2('waiting ...') + oWrappedProgress.wait(); + reporter.log2('isSuccess=%s' % (oWrappedProgress.isSuccess(),)); + if not oWrappedProgress.isSuccess(): + fRc = oWrappedProgress.logResult(); + + fRc = oCurTest.closeSession() and fRc; + + return (fRc, oTxsSession); + + + def testGuestCtrlFileStat(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals + """ + Tests querying file information through stat. + """ + + # Basic stuff, existing stuff. + aoTests = [ + tdTestSessionEx([ + tdStepStatDir('.'), + tdStepStatDir('..'), + tdStepStatDir(self.oTstDrv.getGuestTempDir(oTestVm)), + tdStepStatDir(self.oTstDrv.getGuestSystemDir(oTestVm)), + tdStepStatDirEx(self.oTestFiles.oRoot), + tdStepStatDirEx(self.oTestFiles.oEmptyDir), + tdStepStatDirEx(self.oTestFiles.oTreeDir), + tdStepStatDirEx(self.oTestFiles.chooseRandomDirFromTree()), + tdStepStatDirEx(self.oTestFiles.chooseRandomDirFromTree()), + tdStepStatDirEx(self.oTestFiles.chooseRandomDirFromTree()), + tdStepStatDirEx(self.oTestFiles.chooseRandomDirFromTree()), + tdStepStatDirEx(self.oTestFiles.chooseRandomDirFromTree()), + tdStepStatDirEx(self.oTestFiles.chooseRandomDirFromTree()), + tdStepStatFile(self.oTstDrv.getGuestSystemFileForReading(oTestVm)), + tdStepStatFile(self.oTstDrv.getGuestSystemShell(oTestVm)), + tdStepStatFileEx(self.oTestFiles.chooseRandomFile()), + tdStepStatFileEx(self.oTestFiles.chooseRandomFile()), + tdStepStatFileEx(self.oTestFiles.chooseRandomFile()), + tdStepStatFileEx(self.oTestFiles.chooseRandomFile()), + tdStepStatFileEx(self.oTestFiles.chooseRandomFile()), + tdStepStatFileEx(self.oTestFiles.chooseRandomFile()), + ]), + ]; + + # None existing stuff. + sSysDir = self.oTstDrv.getGuestSystemDir(oTestVm); + sSep = oTestVm.pathSep(); + aoTests += [ + tdTestSessionEx([ + tdStepStatFileNotFound(oTestVm.pathJoin(sSysDir, 'NoSuchFileOrDirectory')), + tdStepStatPathNotFound(oTestVm.pathJoin(sSysDir, 'NoSuchFileOrDirectory') + sSep), + tdStepStatPathNotFound(oTestVm.pathJoin(sSysDir, 'NoSuchFileOrDirectory', '.')), + tdStepStatPathNotFound(oTestVm.pathJoin(sSysDir, 'NoSuchFileOrDirectory', 'NoSuchFileOrSubDirectory')), + tdStepStatPathNotFound(oTestVm.pathJoin(sSysDir, 'NoSuchFileOrDirectory', 'NoSuchFileOrSubDirectory') + sSep), + tdStepStatPathNotFound(oTestVm.pathJoin(sSysDir, 'NoSuchFileOrDirectory', 'NoSuchFileOrSubDirectory', '.')), + #tdStepStatPathNotFound('N:\\'), # ASSUMES nothing mounted on N:! + #tdStepStatPathNotFound('\\\\NoSuchUncServerName\\NoSuchShare'), + ]), + ]; + # Invalid parameter check. + aoTests += [ tdTestSessionEx([ tdStepStat('', vbox.ComError.E_INVALIDARG), ]), ]; + + # + # Execute the tests. + # + fRc, oTxsSession = tdTestSessionEx.executeListTestSessions(aoTests, self.oTstDrv, oSession, oTxsSession, + oTestVm, 'FsStat'); + # + # Test the full test file set. + # + if self.oTstDrv.fpApiVer < 5.0: + return (fRc, oTxsSession); + + oTest = tdTestGuestCtrlBase(); + fRc = oTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + return (False, oTxsSession); + fRc2, oGuestSession = oTest.createSession('FsStat on TestFileSet'); + if fRc2 is not True: + return (False, oTxsSession); + + for oFsObj in self.oTestFiles.dPaths.values(): + reporter.log2('testGuestCtrlFileStat: %s sPath=%s' + % ('file' if isinstance(oFsObj, testfileset.TestFile) else 'dir ', limitString(oFsObj.sPath),)); + + # Query the information: + try: + oFsInfo = oGuestSession.fsObjQueryInfo(oFsObj.sPath, False); + except: + fRc = reporter.errorXcpt('sPath=%s type=%s: fsObjQueryInfo trouble!' % (oFsObj.sPath, type(oFsObj),)); + continue; + if oFsInfo is None: + fRc = reporter.error('sPath=%s type=%s: No info object returned!' % (oFsObj.sPath, type(oFsObj),)); + continue; + + # Check attributes: + try: + eType = oFsInfo.type; + cbObject = oFsInfo.objectSize; + except: + fRc = reporter.errorXcpt('sPath=%s type=%s: attribute access trouble!' % (oFsObj.sPath, type(oFsObj),)); + continue; + + if isinstance(oFsObj, testfileset.TestFile): + if eType != vboxcon.FsObjType_File: + fRc = reporter.error('sPath=%s type=file: eType=%s, expected %s!' + % (oFsObj.sPath, eType, vboxcon.FsObjType_File)); + if cbObject != oFsObj.cbContent: + fRc = reporter.error('sPath=%s type=file: cbObject=%s, expected %s!' + % (oFsObj.sPath, cbObject, oFsObj.cbContent)); + fFileExists = True; + fDirExists = False; + elif isinstance(oFsObj, testfileset.TestDir): + if eType != vboxcon.FsObjType_Directory: + fRc = reporter.error('sPath=%s type=dir: eType=%s, expected %s!' + % (oFsObj.sPath, eType, vboxcon.FsObjType_Directory)); + fFileExists = False; + fDirExists = True; + else: + fRc = reporter.error('sPath=%s type=%s: Unexpected oFsObj type!' % (oFsObj.sPath, type(oFsObj),)); + continue; + + # Check the directoryExists and fileExists results too. + try: + fExistsResult = oGuestSession.fileExists(oFsObj.sPath, False); + except: + fRc = reporter.errorXcpt('sPath=%s type=%s: fileExists trouble!' % (oFsObj.sPath, type(oFsObj),)); + else: + if fExistsResult != fFileExists: + fRc = reporter.error('sPath=%s type=%s: fileExists returned %s, expected %s!' + % (oFsObj.sPath, type(oFsObj), fExistsResult, fFileExists)); + try: + fExistsResult = oGuestSession.directoryExists(oFsObj.sPath, False); + except: + fRc = reporter.errorXcpt('sPath=%s type=%s: directoryExists trouble!' % (oFsObj.sPath, type(oFsObj),)); + else: + if fExistsResult != fDirExists: + fRc = reporter.error('sPath=%s type=%s: directoryExists returned %s, expected %s!' + % (oFsObj.sPath, type(oFsObj), fExistsResult, fDirExists)); + + fRc = oTest.closeSession() and fRc; + return (fRc, oTxsSession); + + def testGuestCtrlFileOpen(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals + """ + Tests opening guest files. + """ + if self.oTstDrv.fpApiVer < 5.0: + reporter.log('Skipping because of pre 5.0 API'); + return None; + + # + # Paths. + # + sTempDir = self.oTstDrv.getGuestTempDir(oTestVm); + sFileForReading = self.oTstDrv.getGuestSystemFileForReading(oTestVm); + asFiles = [ + oTestVm.pathJoin(sTempDir, 'file-open-0'), + oTestVm.pathJoin(sTempDir, 'file-open-1'), + oTestVm.pathJoin(sTempDir, 'file-open-2'), + oTestVm.pathJoin(sTempDir, 'file-open-3'), + oTestVm.pathJoin(sTempDir, 'file-open-4'), + ]; + asNonEmptyFiles = [ + oTestVm.pathJoin(sTempDir, 'file-open-10'), + oTestVm.pathJoin(sTempDir, 'file-open-11'), + oTestVm.pathJoin(sTempDir, 'file-open-12'), + oTestVm.pathJoin(sTempDir, 'file-open-13'), + ]; + sContent = 'abcdefghijklmnopqrstuvwxyz0123456789'; + for sFile in asNonEmptyFiles: + if oTxsSession.syncUploadString(sContent, sFile, 0o666) is not True: + return reporter.error('Failed to create "%s" via TXS' % (sFile,)); + + # + # The tests. + # + atTests = [ + # Invalid stuff. + [ tdTestFileOpen(sFile = ''), tdTestResultFailure() ], + # Wrong open mode. + [ tdTestFileOpen(sFile = sFileForReading, eAccessMode = -1), tdTestResultFailure() ], + # Wrong disposition. + [ tdTestFileOpen(sFile = sFileForReading, eAction = -1), tdTestResultFailure() ], + # Non-existing file or path. + [ tdTestFileOpen(sFile = oTestVm.pathJoin(sTempDir, 'no-such-file-or-dir')), tdTestResultFailure() ], + [ tdTestFileOpen(sFile = oTestVm.pathJoin(sTempDir, 'no-such-file-or-dir'), + eAction = vboxcon.FileOpenAction_OpenExistingTruncated), tdTestResultFailure() ], + [ tdTestFileOpen(sFile = oTestVm.pathJoin(sTempDir, 'no-such-file-or-dir'), + eAccessMode = vboxcon.FileAccessMode_WriteOnly, + eAction = vboxcon.FileOpenAction_OpenExistingTruncated), tdTestResultFailure() ], + [ tdTestFileOpen(sFile = oTestVm.pathJoin(sTempDir, 'no-such-file-or-dir'), + eAccessMode = vboxcon.FileAccessMode_ReadWrite, + eAction = vboxcon.FileOpenAction_OpenExistingTruncated), tdTestResultFailure() ], + [ tdTestFileOpen(sFile = oTestVm.pathJoin(sTempDir, 'no-such-dir', 'no-such-file')), tdTestResultFailure() ], + ]; + if self.oTstDrv.fpApiVer > 5.2: # Fixed since 6.0. + atTests.extend([ + # Wrong type: + [ tdTestFileOpen(sFile = self.oTstDrv.getGuestTempDir(oTestVm)), tdTestResultFailure() ], + [ tdTestFileOpen(sFile = self.oTstDrv.getGuestSystemDir(oTestVm)), tdTestResultFailure() ], + ]); + atTests.extend([ + # O_EXCL and such: + [ tdTestFileOpen(sFile = sFileForReading, eAction = vboxcon.FileOpenAction_CreateNew, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultFailure() ], + [ tdTestFileOpen(sFile = sFileForReading, eAction = vboxcon.FileOpenAction_CreateNew), tdTestResultFailure() ], + # Open a file. + [ tdTestFileOpen(sFile = sFileForReading), tdTestResultSuccess() ], + [ tdTestFileOpen(sFile = sFileForReading, + eAction = vboxcon.FileOpenAction_OpenOrCreate), tdTestResultSuccess() ], + # Create a new file. + [ tdTestFileOpenCheckSize(sFile = asFiles[0], eAction = vboxcon.FileOpenAction_CreateNew, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asFiles[0], eAction = vboxcon.FileOpenAction_CreateNew, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultFailure() ], + [ tdTestFileOpenCheckSize(sFile = asFiles[0], eAction = vboxcon.FileOpenAction_OpenExisting, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asFiles[0], eAction = vboxcon.FileOpenAction_CreateOrReplace, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asFiles[0], eAction = vboxcon.FileOpenAction_OpenOrCreate, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asFiles[0], eAction = vboxcon.FileOpenAction_OpenExistingTruncated, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asFiles[0], eAction = vboxcon.FileOpenAction_AppendOrCreate, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + # Open or create a new file. + [ tdTestFileOpenCheckSize(sFile = asFiles[1], eAction = vboxcon.FileOpenAction_OpenOrCreate, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + # Create or replace a new file. + [ tdTestFileOpenCheckSize(sFile = asFiles[2], eAction = vboxcon.FileOpenAction_CreateOrReplace, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + # Create and append to file (weird stuff). + [ tdTestFileOpenCheckSize(sFile = asFiles[3], eAction = vboxcon.FileOpenAction_AppendOrCreate, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asFiles[4], eAction = vboxcon.FileOpenAction_AppendOrCreate, + eAccessMode = vboxcon.FileAccessMode_WriteOnly), tdTestResultSuccess() ], + # Open the non-empty files in non-destructive modes. + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[0], cbOpenExpected = len(sContent)), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[1], cbOpenExpected = len(sContent), + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[2], cbOpenExpected = len(sContent), + eAccessMode = vboxcon.FileAccessMode_WriteOnly), tdTestResultSuccess() ], + + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[0], cbOpenExpected = len(sContent), + eAction = vboxcon.FileOpenAction_OpenOrCreate, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[1], cbOpenExpected = len(sContent), + eAction = vboxcon.FileOpenAction_OpenOrCreate, + eAccessMode = vboxcon.FileAccessMode_WriteOnly), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[2], cbOpenExpected = len(sContent), + eAction = vboxcon.FileOpenAction_OpenOrCreate, + eAccessMode = vboxcon.FileAccessMode_WriteOnly), tdTestResultSuccess() ], + + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[0], cbOpenExpected = len(sContent), + eAction = vboxcon.FileOpenAction_AppendOrCreate, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[1], cbOpenExpected = len(sContent), + eAction = vboxcon.FileOpenAction_AppendOrCreate, + eAccessMode = vboxcon.FileAccessMode_WriteOnly), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[2], cbOpenExpected = len(sContent), + eAction = vboxcon.FileOpenAction_AppendOrCreate, + eAccessMode = vboxcon.FileAccessMode_WriteOnly), tdTestResultSuccess() ], + + # Now the destructive stuff: + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[0], eAccessMode = vboxcon.FileAccessMode_WriteOnly, + eAction = vboxcon.FileOpenAction_OpenExistingTruncated), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[1], eAccessMode = vboxcon.FileAccessMode_WriteOnly, + eAction = vboxcon.FileOpenAction_CreateOrReplace), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[2], eAction = vboxcon.FileOpenAction_CreateOrReplace, + eAccessMode = vboxcon.FileAccessMode_WriteOnly), tdTestResultSuccess() ], + ]); + + # + # Do the testing. + # + fRc = True; + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0] # type: tdTestFileOpen + oCurRes = tTest[1] # type: tdTestResult + + reporter.log('Testing #%d: %s - sFile="%s", eAccessMode=%d, eAction=%d, (%s, %s, %s) ...' + % (i, oCurTest.__class__.__name__, oCurTest.sFile, oCurTest.eAccessMode, oCurTest.eAction, + oCurTest.eSharing, oCurTest.fCreationMode, oCurTest.afOpenFlags,)); + + oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc, _ = oCurTest.createSession('testGuestCtrlFileOpen: Test #%d' % (i,)); + if fRc is not True: + fRc = reporter.error('Test #%d failed: Could not create session' % (i,)); + break; + + fRc2 = oCurTest.doSteps(oCurRes.fRc, self); + if fRc2 != oCurRes.fRc: + fRc = reporter.error('Test #%d result mismatch: Got %s, expected %s' % (i, fRc2, oCurRes.fRc,)); + + fRc = oCurTest.closeSession() and fRc; + + return (fRc, oTxsSession); + + + def testGuestCtrlFileRead(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-branches,too-many-statements + """ + Tests reading from guest files. + """ + if self.oTstDrv.fpApiVer < 5.0: + reporter.log('Skipping because of pre 5.0 API'); + return None; + + # + # Do everything in one session. + # + oTest = tdTestGuestCtrlBase(); + fRc = oTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + return (False, oTxsSession); + fRc2, oGuestSession = oTest.createSession('FsStat on TestFileSet'); + if fRc2 is not True: + return (False, oTxsSession); + + # + # Create a really big zero filled, up to 1 GiB, adding it to the list of + # files from the set. + # + # Note! This code sucks a bit because we don't have a working setSize nor + # any way to figure out how much free space there is in the guest. + # + aoExtraFiles = []; + sBigName = self.oTestFiles.generateFilenameEx(); + sBigPath = oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, sBigName); + fRc = True; + try: + oFile = oGuestSession.fileOpenEx(sBigPath, vboxcon.FileAccessMode_ReadWrite, vboxcon.FileOpenAction_CreateOrReplace, + vboxcon.FileSharingMode_All, 0, []); + except: + fRc = reporter.errorXcpt('sBigName=%s' % (sBigPath,)); + else: + # Does setSize work now? + fUseFallback = True; + try: + oFile.setSize(0); + oFile.setSize(64); + fUseFallback = False; + except: + reporter.logXcpt(); + + # Grow the file till we hit trouble, typical VERR_DISK_FULL, then + # reduce the file size if we have a working setSize. + cbBigFile = 0; + while cbBigFile < (1024 + 32)*1024*1024: + if not fUseFallback: + cbBigFile += 16*1024*1024; + try: + oFile.setSize(cbBigFile); + except Exception: + reporter.logXcpt('cbBigFile=%s' % (sBigPath,)); + try: + cbBigFile -= 16*1024*1024; + oFile.setSize(cbBigFile); + except: + reporter.logXcpt('cbBigFile=%s' % (sBigPath,)); + break; + else: + cbBigFile += 32*1024*1024; + try: + oFile.seek(cbBigFile, vboxcon.FileSeekOrigin_Begin); + oFile.write(bytearray(1), 60*1000); + except: + reporter.logXcpt('cbBigFile=%s' % (sBigPath,)); + break; + try: + cbBigFile = oFile.seek(0, vboxcon.FileSeekOrigin_End); + except: + fRc = reporter.errorXcpt('sBigName=%s' % (sBigPath,)); + try: + oFile.close(); + except: + fRc = reporter.errorXcpt('sBigName=%s' % (sBigPath,)); + if fRc is True: + reporter.log('Big file: %s bytes: %s' % (cbBigFile, sBigPath,)); + aoExtraFiles.append(testfileset.TestFileZeroFilled(None, sBigPath, cbBigFile)); + else: + try: + oGuestSession.fsObjRemove(sBigPath); + except: + reporter.errorXcpt('fsObjRemove(sBigName=%s)' % (sBigPath,)); + + # + # Open and read all the files in the test file set. + # + for oTestFile in aoExtraFiles + self.oTestFiles.aoFiles: # type: testfileset.TestFile + reporter.log2('Test file: %s bytes, "%s" ...' % (oTestFile.cbContent, limitString(oTestFile.sPath),)); + + # + # Open it: + # + try: + oFile = oGuestSession.fileOpenEx(oTestFile.sPath, vboxcon.FileAccessMode_ReadOnly, + vboxcon.FileOpenAction_OpenExisting, vboxcon.FileSharingMode_All, 0, []); + except: + fRc = reporter.errorXcpt('sPath=%s' % (oTestFile.sPath, )); + continue; + + # + # Read the file in different sized chunks: + # + if oTestFile.cbContent < 128: + acbChunks = xrange(1,128); + elif oTestFile.cbContent < 1024: + acbChunks = (2048, 127, 63, 32, 29, 17, 16, 15, 9); + elif oTestFile.cbContent < 8*1024*1024: + acbChunks = (128*1024, 32*1024, 8191, 255); + else: + acbChunks = (768*1024, 128*1024); + + reporter.log2('Chunked reads'); + + for cbChunk in acbChunks: + # Read the whole file straight thru: + #if oTestFile.cbContent >= 1024*1024: reporter.log2('... cbChunk=%s' % (cbChunk,)); + offFile = 0; + cReads = 0; + while offFile <= oTestFile.cbContent: + try: + abRead = oFile.read(cbChunk, 30*1000); + except: + fRc = reporter.errorXcpt('%s: offFile=%s cbChunk=%s cbContent=%s' + % (oTestFile.sPath, offFile, cbChunk, oTestFile.cbContent)); + break; + cbRead = len(abRead); + if cbRead == 0 and offFile == oTestFile.cbContent: + break; + if cbRead <= 0: + fRc = reporter.error('%s @%s: cbRead=%s, cbContent=%s' + % (oTestFile.sPath, offFile, cbRead, oTestFile.cbContent)); + break; + if not oTestFile.equalMemory(abRead, offFile): + fRc = reporter.error('%s: read mismatch @ %s LB %s' % (oTestFile.sPath, offFile, cbRead)); + break; + offFile += cbRead; + cReads += 1; + if cReads > 8192: + break; + + # Seek to start of file. + try: + offFile = oFile.seek(0, vboxcon.FileSeekOrigin_Begin); + except: + fRc = reporter.errorXcpt('%s: error seeking to start of file' % (oTestFile.sPath,)); + break; + if offFile != 0: + fRc = reporter.error('%s: seek to start of file returned %u, expected 0' % (oTestFile.sPath, offFile)); + break; + + # + # Random reads. + # + reporter.log2('Random reads (seek)'); + for _ in xrange(8): + offFile = self.oTestFiles.oRandom.randrange(0, oTestFile.cbContent + 1024); + cbToRead = self.oTestFiles.oRandom.randrange(1, min(oTestFile.cbContent + 256, 768*1024)); + #if oTestFile.cbContent >= 1024*1024: reporter.log2('... %s LB %s' % (offFile, cbToRead,)); + + try: + offActual = oFile.seek(offFile, vboxcon.FileSeekOrigin_Begin); + except: + fRc = reporter.errorXcpt('%s: error seeking to %s' % (oTestFile.sPath, offFile)); + break; + if offActual != offFile: + fRc = reporter.error('%s: seek(%s,Begin) -> %s, expected %s' + % (oTestFile.sPath, offFile, offActual, offFile)); + break; + + try: + abRead = oFile.read(cbToRead, 30*1000); + except: + fRc = reporter.errorXcpt('%s: offFile=%s cbToRead=%s cbContent=%s' + % (oTestFile.sPath, offFile, cbToRead, oTestFile.cbContent)); + cbRead = 0; + else: + cbRead = len(abRead); + if not oTestFile.equalMemory(abRead, offFile): + fRc = reporter.error('%s: random read mismatch @ %s LB %s' % (oTestFile.sPath, offFile, cbRead,)); + + try: + offActual = oFile.offset; + except: + fRc = reporter.errorXcpt('%s: offFile=%s cbToRead=%s cbContent=%s (#1)' + % (oTestFile.sPath, offFile, cbToRead, oTestFile.cbContent)); + else: + if offActual != offFile + cbRead: + fRc = reporter.error('%s: IFile.offset is %s, expected %s (offFile=%s cbToRead=%s cbRead=%s) (#1)' + % (oTestFile.sPath, offActual, offFile + cbRead, offFile, cbToRead, cbRead)); + try: + offActual = oFile.seek(0, vboxcon.FileSeekOrigin_Current); + except: + fRc = reporter.errorXcpt('%s: offFile=%s cbToRead=%s cbContent=%s (#1)' + % (oTestFile.sPath, offFile, cbToRead, oTestFile.cbContent)); + else: + if offActual != offFile + cbRead: + fRc = reporter.error('%s: seek(0,cur) -> %s, expected %s (offFile=%s cbToRead=%s cbRead=%s) (#1)' + % (oTestFile.sPath, offActual, offFile + cbRead, offFile, cbToRead, cbRead)); + + # + # Random reads using readAt. + # + reporter.log2('Random reads (readAt)'); + for _ in xrange(12): + offFile = self.oTestFiles.oRandom.randrange(0, oTestFile.cbContent + 1024); + cbToRead = self.oTestFiles.oRandom.randrange(1, min(oTestFile.cbContent + 256, 768*1024)); + #if oTestFile.cbContent >= 1024*1024: reporter.log2('... %s LB %s (readAt)' % (offFile, cbToRead,)); + + try: + abRead = oFile.readAt(offFile, cbToRead, 30*1000); + except: + fRc = reporter.errorXcpt('%s: offFile=%s cbToRead=%s cbContent=%s' + % (oTestFile.sPath, offFile, cbToRead, oTestFile.cbContent)); + cbRead = 0; + else: + cbRead = len(abRead); + if not oTestFile.equalMemory(abRead, offFile): + fRc = reporter.error('%s: random readAt mismatch @ %s LB %s' % (oTestFile.sPath, offFile, cbRead,)); + + try: + offActual = oFile.offset; + except: + fRc = reporter.errorXcpt('%s: offFile=%s cbToRead=%s cbContent=%s (#2)' + % (oTestFile.sPath, offFile, cbToRead, oTestFile.cbContent)); + else: + if offActual != offFile + cbRead: + fRc = reporter.error('%s: IFile.offset is %s, expected %s (offFile=%s cbToRead=%s cbRead=%s) (#2)' + % (oTestFile.sPath, offActual, offFile + cbRead, offFile, cbToRead, cbRead)); + + try: + offActual = oFile.seek(0, vboxcon.FileSeekOrigin_Current); + except: + fRc = reporter.errorXcpt('%s: offFile=%s cbToRead=%s cbContent=%s (#2)' + % (oTestFile.sPath, offFile, cbToRead, oTestFile.cbContent)); + else: + if offActual != offFile + cbRead: + fRc = reporter.error('%s: seek(0,cur) -> %s, expected %s (offFile=%s cbToRead=%s cbRead=%s) (#2)' + % (oTestFile.sPath, offActual, offFile + cbRead, offFile, cbToRead, cbRead)); + + # + # A few negative things. + # + + # Zero byte reads -> E_INVALIDARG. + reporter.log2('Zero byte reads'); + try: + abRead = oFile.read(0, 30*1000); + except Exception as oXcpt: + if vbox.ComError.notEqual(oXcpt, vbox.ComError.E_INVALIDARG): + fRc = reporter.errorXcpt('read(0,30s) did not raise E_INVALIDARG as expected!'); + else: + fRc = reporter.error('read(0,30s) did not fail!'); + + try: + abRead = oFile.readAt(0, 0, 30*1000); + except Exception as oXcpt: + if vbox.ComError.notEqual(oXcpt, vbox.ComError.E_INVALIDARG): + fRc = reporter.errorXcpt('readAt(0,0,30s) did not raise E_INVALIDARG as expected!'); + else: + fRc = reporter.error('readAt(0,0,30s) did not fail!'); + + # See what happens when we read 1GiB. We should get a max of 1MiB back. + ## @todo Document this behaviour in VirtualBox.xidl. + reporter.log2('1GB reads'); + try: + oFile.seek(0, vboxcon.FileSeekOrigin_Begin); + except: + fRc = reporter.error('seek(0)'); + try: + abRead = oFile.read(1024*1024*1024, 30*1000); + except: + fRc = reporter.errorXcpt('read(1GiB,30s)'); + else: + if len(abRead) != min(oTestFile.cbContent, 1024*1024): + fRc = reporter.error('Expected read(1GiB,30s) to return %s bytes, got %s bytes instead' + % (min(oTestFile.cbContent, 1024*1024), len(abRead),)); + + try: + abRead = oFile.readAt(0, 1024*1024*1024, 30*1000); + except: + fRc = reporter.errorXcpt('readAt(0,1GiB,30s)'); + else: + if len(abRead) != min(oTestFile.cbContent, 1024*1024): + reporter.error('Expected readAt(0, 1GiB,30s) to return %s bytes, got %s bytes instead' + % (min(oTestFile.cbContent, 1024*1024), len(abRead),)); + + # + # Check stat info on the file as well as querySize. + # + if self.oTstDrv.fpApiVer > 5.2: + try: + oFsObjInfo = oFile.queryInfo(); + except: + fRc = reporter.errorXcpt('%s: queryInfo()' % (oTestFile.sPath,)); + else: + if oFsObjInfo is None: + fRc = reporter.error('IGuestFile::queryInfo returned None'); + else: + try: + cbFile = oFsObjInfo.objectSize; + except: + fRc = reporter.errorXcpt(); + else: + if cbFile != oTestFile.cbContent: + fRc = reporter.error('%s: queryInfo returned incorrect file size: %s, expected %s' + % (oTestFile.sPath, cbFile, oTestFile.cbContent)); + + try: + cbFile = oFile.querySize(); + except: + fRc = reporter.errorXcpt('%s: querySize()' % (oTestFile.sPath,)); + else: + if cbFile != oTestFile.cbContent: + fRc = reporter.error('%s: querySize returned incorrect file size: %s, expected %s' + % (oTestFile.sPath, cbFile, oTestFile.cbContent)); + + # + # Use seek to test the file size and do a few other end-relative seeks. + # + try: + cbFile = oFile.seek(0, vboxcon.FileSeekOrigin_End); + except: + fRc = reporter.errorXcpt('%s: seek(0,End)' % (oTestFile.sPath,)); + else: + if cbFile != oTestFile.cbContent: + fRc = reporter.error('%s: seek(0,End) returned incorrect file size: %s, expected %s' + % (oTestFile.sPath, cbFile, oTestFile.cbContent)); + if oTestFile.cbContent > 0: + for _ in xrange(5): + offSeek = self.oTestFiles.oRandom.randrange(oTestFile.cbContent + 1); + try: + offFile = oFile.seek(-offSeek, vboxcon.FileSeekOrigin_End); + except: + fRc = reporter.errorXcpt('%s: seek(%s,End)' % (oTestFile.sPath, -offSeek,)); + else: + if offFile != oTestFile.cbContent - offSeek: + fRc = reporter.error('%s: seek(%s,End) returned incorrect offset: %s, expected %s (cbContent=%s)' + % (oTestFile.sPath, -offSeek, offSeek, oTestFile.cbContent - offSeek, + oTestFile.cbContent,)); + + # + # Close it and we're done with this file. + # + try: + oFile.close(); + except: + fRc = reporter.errorXcpt('%s: error closing the file' % (oTestFile.sPath,)); + + # + # Clean up. + # + for oTestFile in aoExtraFiles: + try: + oGuestSession.fsObjRemove(sBigPath); + except: + fRc = reporter.errorXcpt('fsObjRemove(%s)' % (sBigPath,)); + + fRc = oTest.closeSession() and fRc; + + return (fRc, oTxsSession); + + + def testGuestCtrlFileWrite(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals + """ + Tests writing to guest files. + """ + if self.oTstDrv.fpApiVer < 5.0: + reporter.log('Skipping because of pre 5.0 API'); + return None; + + # + # The test file and its content. + # + sFile = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), 'gctrl-write-1'); + abContent = bytearray(0); + + # + # The tests. + # + def randBytes(cbHowMany): + """ Returns an bytearray of random bytes. """ + return bytearray(self.oTestFiles.oRandom.getrandbits(8) for _ in xrange(cbHowMany)); + + aoTests = [ + # Write at end: + tdTestFileOpenAndWrite(sFile = sFile, eAction = vboxcon.FileOpenAction_CreateNew, abContent = abContent, + atChunks = [(None, randBytes(1)), (None, randBytes(77)), (None, randBytes(98)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 1+77+98), # 176 + # Appending: + tdTestFileOpenAndWrite(sFile = sFile, eAction = vboxcon.FileOpenAction_AppendOrCreate, abContent = abContent, + atChunks = [(None, randBytes(255)), (None, randBytes(33)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 176 + 255+33), # 464 + tdTestFileOpenAndWrite(sFile = sFile, eAction = vboxcon.FileOpenAction_AppendOrCreate, abContent = abContent, + atChunks = [(10, randBytes(44)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 464 + 44), # 508 + # Write within existing: + tdTestFileOpenAndWrite(sFile = sFile, eAction = vboxcon.FileOpenAction_OpenExisting, abContent = abContent, + atChunks = [(0, randBytes(1)), (50, randBytes(77)), (255, randBytes(199)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 508), + # Writing around and over the end: + tdTestFileOpenAndWrite(sFile = sFile, abContent = abContent, + atChunks = [(500, randBytes(9)), (508, randBytes(15)), (512, randBytes(12)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 512+12), + + # writeAt appending: + tdTestFileOpenAndWrite(sFile = sFile, abContent = abContent, fUseAtApi = True, + atChunks = [(0, randBytes(23)), (6, randBytes(1018)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 6+1018), # 1024 + # writeAt within existing: + tdTestFileOpenAndWrite(sFile = sFile, abContent = abContent, fUseAtApi = True, + atChunks = [(1000, randBytes(23)), (1, randBytes(990)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 1024), + # writeAt around and over the end: + tdTestFileOpenAndWrite(sFile = sFile, abContent = abContent, fUseAtApi = True, + atChunks = [(1024, randBytes(63)), (1080, randBytes(968)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 1080+968), # 2048 + + # writeAt beyond the end (gap is filled with zeros): + tdTestFileOpenAndWrite(sFile = sFile, abContent = abContent, fUseAtApi = True, atChunks = [(3070, randBytes(2)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 3072), + # write beyond the end (gap is filled with zeros): + tdTestFileOpenAndWrite(sFile = sFile, abContent = abContent, atChunks = [(4090, randBytes(6)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 4096), + ]; + + for (i, oCurTest) in enumerate(aoTests): + reporter.log('Testing #%d: %s ...' % (i, oCurTest.toString(),)); + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc, _ = oCurTest.createSession('testGuestCtrlFileWrite: Test #%d' % (i,)); + if fRc is not True: + fRc = reporter.error('Test #%d failed: Could not create session' % (i,)); + break; + + fRc2 = oCurTest.doSteps(True, self); + if fRc2 is not True: + fRc = reporter.error('Test #%d failed!' % (i,)); + + fRc = oCurTest.closeSession() and fRc; + + # + # Cleanup + # + if oTxsSession.syncRmFile(sFile) is not True: + fRc = reporter.error('Failed to remove write-test file: %s' % (sFile, )); + + return (fRc, oTxsSession); + + @staticmethod + def __generateFile(sName, cbFile): + """ Helper for generating a file with a given size. """ + with open(sName, 'wb') as oFile: + while cbFile > 0: + cb = cbFile if cbFile < 256*1024 else 256*1024; + oFile.write(bytearray(random.getrandbits(8) for _ in xrange(cb))); + cbFile -= cb; + return True; + + def testGuestCtrlCopyTo(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals + """ + Tests copying files from host to the guest. + """ + + # + # Paths and test files. + # + sScratchHst = os.path.join(self.oTstDrv.sScratchPath, 'copyto'); + sScratchTestFilesHst = os.path.join(sScratchHst, self.oTestFiles.sSubDir); + sScratchEmptyDirHst = os.path.join(sScratchTestFilesHst, self.oTestFiles.oEmptyDir.sName); + sScratchNonEmptyDirHst = self.oTestFiles.chooseRandomDirFromTree().buildPath(sScratchHst, os.path.sep); + sScratchTreeDirHst = os.path.join(sScratchTestFilesHst, self.oTestFiles.oTreeDir.sName); + + sScratchGst = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), 'copyto'); + sScratchDstDir1Gst = oTestVm.pathJoin(sScratchGst, 'dstdir1'); + sScratchDstDir2Gst = oTestVm.pathJoin(sScratchGst, 'dstdir2'); + sScratchDstDir3Gst = oTestVm.pathJoin(sScratchGst, 'dstdir3'); + sScratchDstDir4Gst = oTestVm.pathJoin(sScratchGst, 'dstdir4'); + sScratchDotDotDirGst = oTestVm.pathJoin(sScratchGst, '..'); + #sScratchGstNotExist = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), 'no-such-file-or-directory'); + sScratchHstNotExist = os.path.join(self.oTstDrv.sScratchPath, 'no-such-file-or-directory'); + sScratchGstPathNotFound = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), 'no-such-directory', 'or-file'); + #sScratchHstPathNotFound = os.path.join(self.oTstDrv.sScratchPath, 'no-such-directory', 'or-file'); + + if oTestVm.isWindows() or oTestVm.isOS2(): + sScratchGstInvalid = "?*|<invalid-name>"; + else: + sScratchGstInvalid = None; + if utils.getHostOs() in ('win', 'os2'): + sScratchHstInvalid = "?*|<invalid-name>"; + else: + sScratchHstInvalid = None; + + for sDir in (sScratchGst, sScratchDstDir1Gst, sScratchDstDir2Gst, sScratchDstDir3Gst, sScratchDstDir4Gst): + if oTxsSession.syncMkDir(sDir, 0o777) is not True: + return reporter.error('TXS failed to create directory "%s"!' % (sDir,)); + + # Put the test file set under sScratchHst. + if os.path.exists(sScratchHst): + if base.wipeDirectory(sScratchHst) != 0: + return reporter.error('Failed to wipe "%s"' % (sScratchHst,)); + else: + try: + os.mkdir(sScratchHst); + except: + return reporter.errorXcpt('os.mkdir(%s)' % (sScratchHst, )); + if self.oTestFiles.writeToDisk(sScratchHst) is not True: + return reporter.error('Filed to write test files to "%s" on the host!' % (sScratchHst,)); + + # If for whatever reason the directory tree does not exist on the host, let us know. + # Copying an non-existing tree *will* fail the tests which otherwise should succeed! + assert os.path.exists(sScratchTreeDirHst); + + # Generate a test file in 32MB to 64 MB range. + sBigFileHst = os.path.join(self.oTstDrv.sScratchPath, 'gctrl-random.data'); + cbBigFileHst = random.randrange(32*1024*1024, 64*1024*1024); + reporter.log('cbBigFileHst=%s' % (cbBigFileHst,)); + cbLeft = cbBigFileHst; + try: + self.__generateFile(sBigFileHst, cbBigFileHst); + except: + return reporter.errorXcpt('sBigFileHst=%s cbBigFileHst=%s cbLeft=%s' % (sBigFileHst, cbBigFileHst, cbLeft,)); + reporter.log('cbBigFileHst=%s' % (cbBigFileHst,)); + + # Generate an empty file on the host that we can use to save space in the guest. + sEmptyFileHst = os.path.join(self.oTstDrv.sScratchPath, 'gctrl-empty.data'); + try: + open(sEmptyFileHst, "wb").close(); # pylint: disable=consider-using-with + except: + return reporter.errorXcpt('sEmptyFileHst=%s' % (sEmptyFileHst,)); + + # os.path.join() is too clever for "..", so we just build up the path here ourselves. + sScratchDotDotFileHst = sScratchHst + os.path.sep + '..' + os.path.sep + 'gctrl-empty.data'; + + # + # Tests. + # + atTests = [ + # Nothing given: + [ tdTestCopyToFile(), tdTestResultFailure() ], + [ tdTestCopyToDir(), tdTestResultFailure() ], + # Only source given: + [ tdTestCopyToFile(sSrc = sBigFileHst), tdTestResultFailure() ], + [ tdTestCopyToDir( sSrc = sScratchEmptyDirHst), tdTestResultFailure() ], + # Only destination given: + [ tdTestCopyToFile(sDst = oTestVm.pathJoin(sScratchGst, 'dstfile')), tdTestResultFailure() ], + [ tdTestCopyToDir( sDst = sScratchGst), tdTestResultFailure() ], + # Both given, but invalid flags. + [ tdTestCopyToFile(sSrc = sBigFileHst, sDst = sScratchGst, afFlags = [ 0x40000000, ] ), tdTestResultFailure() ], + [ tdTestCopyToDir( sSrc = sScratchEmptyDirHst, sDst = sScratchGst, afFlags = [ 0x40000000, ] ), + tdTestResultFailure() ], + ]; + atTests.extend([ + # Non-existing source, but no destination: + [ tdTestCopyToFile(sSrc = sScratchHstNotExist), tdTestResultFailure() ], + [ tdTestCopyToDir( sSrc = sScratchHstNotExist), tdTestResultFailure() ], + # Valid sources, but destination path not found: + [ tdTestCopyToFile(sSrc = sBigFileHst, sDst = sScratchGstPathNotFound), tdTestResultFailure() ], + [ tdTestCopyToDir( sSrc = sScratchEmptyDirHst, sDst = sScratchGstPathNotFound), tdTestResultFailure() ], + # Valid destination, but source file/dir not found: + [ tdTestCopyToFile(sSrc = sScratchHstNotExist, sDst = oTestVm.pathJoin(sScratchGst, 'dstfile')), + tdTestResultFailure() ], + [ tdTestCopyToDir( sSrc = sScratchHstNotExist, sDst = sScratchGst), tdTestResultFailure() ], + # Wrong type: + [ tdTestCopyToFile(sSrc = sScratchEmptyDirHst, sDst = oTestVm.pathJoin(sScratchGst, 'dstfile')), + tdTestResultFailure() ], + [ tdTestCopyToDir( sSrc = sBigFileHst, sDst = sScratchGst), tdTestResultFailure() ], + ]); + # Invalid characters in destination or source path: + if sScratchGstInvalid is not None: + atTests.extend([ + [ tdTestCopyToFile(sSrc = sBigFileHst, sDst = oTestVm.pathJoin(sScratchGst, sScratchGstInvalid)), + tdTestResultFailure() ], + [ tdTestCopyToDir( sSrc = sScratchEmptyDirHst, sDst = oTestVm.pathJoin(sScratchGst, sScratchGstInvalid)), + tdTestResultFailure() ], + ]); + if sScratchHstInvalid is not None: + atTests.extend([ + [ tdTestCopyToFile(sSrc = os.path.join(self.oTstDrv.sScratchPath, sScratchHstInvalid), sDst = sScratchGst), + tdTestResultFailure() ], + [ tdTestCopyToDir( sSrc = os.path.join(self.oTstDrv.sScratchPath, sScratchHstInvalid), sDst = sScratchGst), + tdTestResultFailure() ], + ]); + + # + # Single file handling. + # + atTests.extend([ + [ tdTestCopyToFile(sSrc = sBigFileHst, sDst = oTestVm.pathJoin(sScratchGst, 'HostGABig.dat')), + tdTestResultSuccess() ], + [ tdTestCopyToFile(sSrc = sBigFileHst, sDst = oTestVm.pathJoin(sScratchGst, 'HostGABig.dat')), # Overwrite + tdTestResultSuccess() ], + [ tdTestCopyToFile(sSrc = sEmptyFileHst, sDst = oTestVm.pathJoin(sScratchGst, 'HostGABig.dat')), # Overwrite + tdTestResultSuccess() ], + ]); + if self.oTstDrv.fpApiVer > 5.2: # Copying files into directories via Main is supported only 6.0 and later. + atTests.extend([ + # Should succeed, as the file isn't there yet on the destination. + [ tdTestCopyToFile(sSrc = sBigFileHst, sDst = sScratchGst + oTestVm.pathSep()), tdTestResultSuccess() ], + # Overwrite the existing file. + [ tdTestCopyToFile(sSrc = sBigFileHst, sDst = sScratchGst + oTestVm.pathSep()), tdTestResultSuccess() ], + # Same file, but with a different name on the destination. + [ tdTestCopyToFile(sSrc = sEmptyFileHst, sDst = oTestVm.pathJoin(sScratchGst, os.path.split(sBigFileHst)[1])), + tdTestResultSuccess() ], # Overwrite + ]); + + if oTestVm.isWindows(): + # Copy to a Windows alternative data stream (ADS). + atTests.extend([ + [ tdTestCopyToFile(sSrc = sBigFileHst, sDst = oTestVm.pathJoin(sScratchGst, 'HostGABig.dat:ADS-Test')), + tdTestResultSuccess() ], + [ tdTestCopyToFile(sSrc = sEmptyFileHst, sDst = oTestVm.pathJoin(sScratchGst, 'HostGABig.dat:ADS-Test')), + tdTestResultSuccess() ], + ]); + + # + # Directory handling. + # + if self.oTstDrv.fpApiVer > 5.2: # Copying directories via Main is supported only in versions > 5.2. + atTests.extend([ + # Without a trailing slash added to the destination this should fail, + # as the destination directory already exists. + [ tdTestCopyToDir(sSrc = sScratchEmptyDirHst, sDst = sScratchDstDir1Gst), tdTestResultFailure() ], + # Same existing host directory, but this time with DirectoryCopyFlag_CopyIntoExisting set. + # This should copy the contents of oEmptyDirGst to sScratchDstDir1Gst (empty, but anyway). + [ tdTestCopyToDir(sSrc = sScratchEmptyDirHst, sDst = sScratchDstDir1Gst, + afFlags = [vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + # Try again. + [ tdTestCopyToDir(sSrc = sScratchEmptyDirHst, sDst = sScratchDstDir1Gst, + afFlags = [vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + # With a trailing slash added to the destination, copy the empty guest directory + # (should end up as sScratchDstDir2Gst/empty): + [ tdTestCopyToDir(sSrc = sScratchEmptyDirHst, sDst = sScratchDstDir2Gst + oTestVm.pathSep()), + tdTestResultSuccess() ], + # Repeat -- this time it should fail, as the destination directory already exists (and + # DirectoryCopyFlag_CopyIntoExisting is not specified): + [ tdTestCopyToDir(sSrc = sScratchEmptyDirHst, sDst = sScratchDstDir2Gst + oTestVm.pathSep()), + tdTestResultFailure() ], + # Add the DirectoryCopyFlag_CopyIntoExisting flag being set and it should work (again). + [ tdTestCopyToDir(sSrc = sScratchEmptyDirHst, sDst = sScratchDstDir2Gst + oTestVm.pathSep(), + afFlags = [ vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + # Copy with a different destination name just for the heck of it: + [ tdTestCopyToDir(sSrc = sScratchEmptyDirHst, sDst = oTestVm.pathJoin(sScratchDstDir2Gst, 'empty2')), + tdTestResultSuccess() ], + ]); + atTests.extend([ + # Now the same using a directory with files in it: + [ tdTestCopyToDir(sSrc = sScratchNonEmptyDirHst, sDst = sScratchDstDir3Gst, + afFlags = [vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + # Again. + [ tdTestCopyToDir(sSrc = sScratchNonEmptyDirHst, sDst = sScratchDstDir3Gst, + afFlags = [vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + ]); + atTests.extend([ + # Copy the entire test tree: + [ tdTestCopyToDir(sSrc = sScratchTreeDirHst, sDst = sScratchDstDir4Gst + oTestVm.pathSep()), + tdTestResultSuccess() ], + # Again, should fail this time. + [ tdTestCopyToDir(sSrc = sScratchTreeDirHst, sDst = sScratchDstDir4Gst + oTestVm.pathSep()), + tdTestResultFailure() ], + # Works again, as DirectoryCopyFlag_CopyIntoExisting is specified. + [ tdTestCopyToDir(sSrc = sScratchTreeDirHst, sDst = sScratchDstDir4Gst + oTestVm.pathSep(), + afFlags = [ vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + ]); + # + # Dotdot path handling. + # + if self.oTstDrv.fpApiVer >= 6.1: + atTests.extend([ + # Test if copying stuff from a host dotdot ".." directory works. + [ tdTestCopyToFile(sSrc = sScratchDotDotFileHst, sDst = sScratchDstDir1Gst + oTestVm.pathSep()), + tdTestResultSuccess() ], + # Test if copying stuff from the host to a guest's dotdot ".." directory works. + # That should fail on destinations. + [ tdTestCopyToFile(sSrc = sEmptyFileHst, sDst = sScratchDotDotDirGst), tdTestResultFailure() ], + ]); + + fRc = True; + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0]; # tdTestCopyTo + oCurRes = tTest[1]; # tdTestResult + reporter.log('Testing #%d, sSrc=%s, sDst=%s, afFlags=%s ...' + % (i, limitString(oCurTest.sSrc), limitString(oCurTest.sDst), oCurTest.afFlags)); + + oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc, oCurGuestSession = oCurTest.createSession('testGuestCtrlCopyTo: Test #%d' % (i,)); + if fRc is not True: + fRc = reporter.error('Test #%d failed: Could not create session' % (i,)); + break; + + fRc2 = False; + if isinstance(oCurTest, tdTestCopyToFile): + fRc2 = self.gctrlCopyFileTo(oCurGuestSession, oCurTest.sSrc, oCurTest.sDst, oCurTest.afFlags, oCurRes.fRc); + else: + fRc2 = self.gctrlCopyDirTo(oCurGuestSession, oCurTest.sSrc, oCurTest.sDst, oCurTest.afFlags, oCurRes.fRc); + if fRc2 is not oCurRes.fRc: + fRc = reporter.error('Test #%d failed: Got %s, expected %s' % (i, fRc2, oCurRes.fRc)); + + fRc = oCurTest.closeSession() and fRc; + + return (fRc, oTxsSession); + + def testGuestCtrlCopyFrom(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals + """ + Tests copying files from guest to the host. + """ + + reporter.log2('Entered'); + + # + # Paths. + # + sScratchHst = os.path.join(self.oTstDrv.sScratchPath, "testGctrlCopyFrom"); + sScratchDstDir1Hst = os.path.join(sScratchHst, "dstdir1"); + sScratchDstDir2Hst = os.path.join(sScratchHst, "dstdir2"); + sScratchDstDir3Hst = os.path.join(sScratchHst, "dstdir3"); + sScratchDstDir4Hst = os.path.join(sScratchHst, "dstdir4"); + # os.path.join() is too clever for "..", so we just build up the path here ourselves. + sScratchDotDotDirHst = sScratchHst + os.path.sep + '..' + os.path.sep; + oExistingFileGst = self.oTestFiles.chooseRandomFile(); + oNonEmptyDirGst = self.oTestFiles.chooseRandomDirFromTree(fNonEmpty = True); + oTreeDirGst = self.oTestFiles.oTreeDir; + oEmptyDirGst = self.oTestFiles.oEmptyDir; + + if oTestVm.isWindows() or oTestVm.isOS2(): + sScratchGstInvalid = "?*|<invalid-name>"; + else: + sScratchGstInvalid = None; + if utils.getHostOs() in ('win', 'os2'): + sScratchHstInvalid = "?*|<invalid-name>"; + else: + sScratchHstInvalid = None; + + sScratchDotDotDirGst = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), '..'); + + if os.path.exists(sScratchHst): + if base.wipeDirectory(sScratchHst) != 0: + return reporter.error('Failed to wipe "%s"' % (sScratchHst,)); + else: + try: + os.mkdir(sScratchHst); + except: + return reporter.errorXcpt('os.mkdir(%s)' % (sScratchHst, )); + + reporter.log2('Creating host sub dirs ...'); + + for sSubDir in (sScratchDstDir1Hst, sScratchDstDir2Hst, sScratchDstDir3Hst, sScratchDstDir4Hst): + try: + os.mkdir(sSubDir); + except: + return reporter.errorXcpt('os.mkdir(%s)' % (sSubDir, )); + + reporter.log2('Defining tests ...'); + + # + # Bad parameter tests. + # + atTests = [ + # Missing both source and destination: + [ tdTestCopyFromFile(), tdTestResultFailure() ], + [ tdTestCopyFromDir(), tdTestResultFailure() ], + # Missing source. + [ tdTestCopyFromFile(sDst = os.path.join(sScratchHst, 'somefile')), tdTestResultFailure() ], + [ tdTestCopyFromDir( sDst = sScratchHst), tdTestResultFailure() ], + # Missing destination. + [ tdTestCopyFromFile(oSrc = oExistingFileGst), tdTestResultFailure() ], + [ tdTestCopyFromDir( sSrc = self.oTestFiles.oManyDir.sPath), tdTestResultFailure() ], + # Invalid flags: + [ tdTestCopyFromFile(oSrc = oExistingFileGst, sDst = os.path.join(sScratchHst, 'somefile'), afFlags = [0x40000000]), + tdTestResultFailure() ], + [ tdTestCopyFromDir( oSrc = oEmptyDirGst, sDst = os.path.join(sScratchHst, 'somedir'), afFlags = [ 0x40000000] ), + tdTestResultFailure() ], + # Non-existing sources: + [ tdTestCopyFromFile(sSrc = oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'no-such-file-or-directory'), + sDst = os.path.join(sScratchHst, 'somefile')), tdTestResultFailure() ], + [ tdTestCopyFromDir( sSrc = oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'no-such-file-or-directory'), + sDst = os.path.join(sScratchHst, 'somedir')), tdTestResultFailure() ], + [ tdTestCopyFromFile(sSrc = oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'no-such-directory', 'no-such-file'), + sDst = os.path.join(sScratchHst, 'somefile')), tdTestResultFailure() ], + [ tdTestCopyFromDir( sSrc = oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'no-such-directory', 'no-such-subdir'), + sDst = os.path.join(sScratchHst, 'somedir')), tdTestResultFailure() ], + # Non-existing destinations: + [ tdTestCopyFromFile(oSrc = oExistingFileGst, + sDst = os.path.join(sScratchHst, 'no-such-directory', 'somefile') ), tdTestResultFailure() ], + [ tdTestCopyFromDir( oSrc = oEmptyDirGst, sDst = os.path.join(sScratchHst, 'no-such-directory', 'somedir') ), + tdTestResultFailure() ], + [ tdTestCopyFromFile(oSrc = oExistingFileGst, + sDst = os.path.join(sScratchHst, 'no-such-directory-slash' + os.path.sep)), + tdTestResultFailure() ], + # Wrong source type: + [ tdTestCopyFromFile(oSrc = oNonEmptyDirGst, sDst = os.path.join(sScratchHst, 'somefile') ), tdTestResultFailure() ], + [ tdTestCopyFromDir(oSrc = oExistingFileGst, sDst = os.path.join(sScratchHst, 'somedir') ), tdTestResultFailure() ], + ]; + # Bogus names: + if sScratchHstInvalid: + atTests.extend([ + [ tdTestCopyFromFile(oSrc = oExistingFileGst, sDst = os.path.join(sScratchHst, sScratchHstInvalid)), + tdTestResultFailure() ], + [ tdTestCopyFromDir( sSrc = self.oTestFiles.oManyDir.sPath, sDst = os.path.join(sScratchHst, sScratchHstInvalid)), + tdTestResultFailure() ], + ]); + if sScratchGstInvalid: + atTests.extend([ + [ tdTestCopyFromFile(sSrc = oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, sScratchGstInvalid), + sDst = os.path.join(sScratchHst, 'somefile')), tdTestResultFailure() ], + [ tdTestCopyFromDir( sSrc = oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, sScratchGstInvalid), + sDst = os.path.join(sScratchHst, 'somedir')), tdTestResultFailure() ], + ]); + + # + # Single file copying. + # + atTests.extend([ + # Should succeed, as the file isn't there yet on the destination. + [ tdTestCopyFromFile(oSrc = oExistingFileGst, sDst = os.path.join(sScratchHst, 'copyfile1')), tdTestResultSuccess() ], + # Overwrite the existing file. + [ tdTestCopyFromFile(oSrc = oExistingFileGst, sDst = os.path.join(sScratchHst, 'copyfile1')), tdTestResultSuccess() ], + # Same file, but with a different name on the destination. + [ tdTestCopyFromFile(oSrc = oExistingFileGst, sDst = os.path.join(sScratchHst, 'copyfile2')), tdTestResultSuccess() ], + ]); + + if self.oTstDrv.fpApiVer > 5.2: # Copying files into directories via Main is supported only 6.0 and later. + # Copy into a directory. + atTests.extend([ + # This should fail, as sScratchHst exists and is a directory. + [ tdTestCopyFromFile(oSrc = oExistingFileGst, sDst = sScratchHst), tdTestResultFailure() ], + # Same existing host directory, but this time with a trailing slash. + # This should succeed, as the file isn't there yet on the destination. + [ tdTestCopyFromFile(oSrc = oExistingFileGst, sDst = sScratchHst + os.path.sep), tdTestResultSuccess() ], + # Overwrite the existing file. + [ tdTestCopyFromFile(oSrc = oExistingFileGst, sDst = sScratchHst + os.path.sep), tdTestResultSuccess() ], + ]); + + # + # Directory handling. + # + if self.oTstDrv.fpApiVer > 5.2: # Copying directories via Main is supported only in versions > 5.2. + atTests.extend([ + # Without a trailing slash added to the destination this should fail, + # as the destination directory already exist. + [ tdTestCopyFromDir(sSrc = oEmptyDirGst.sPath, sDst = sScratchDstDir1Hst), tdTestResultFailure() ], + # Same existing host directory, but this time with DirectoryCopyFlag_CopyIntoExisting set. + # This should copy the contents of oEmptyDirGst to sScratchDstDir1Hst (empty, but anyway). + [ tdTestCopyFromDir(sSrc = oEmptyDirGst.sPath, sDst = sScratchDstDir1Hst, + afFlags = [ vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + # Try again. + [ tdTestCopyFromDir(sSrc = oEmptyDirGst.sPath, sDst = sScratchDstDir1Hst, + afFlags = [ vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + # With a trailing slash added to the destination, copy the empty guest directory + # (should end up as sScratchHst/empty): + [ tdTestCopyFromDir(oSrc = oEmptyDirGst, sDst = sScratchDstDir2Hst + os.path.sep), tdTestResultSuccess() ], + # Repeat -- this time it should fail, as the destination directory already exists (and + # DirectoryCopyFlag_CopyIntoExisting is not specified): + [ tdTestCopyFromDir(oSrc = oEmptyDirGst, sDst = sScratchDstDir2Hst + os.path.sep), tdTestResultFailure() ], + # Add the DirectoryCopyFlag_CopyIntoExisting flag being set and it should work (again). + [ tdTestCopyFromDir(oSrc = oEmptyDirGst, sDst = sScratchDstDir2Hst + os.path.sep, + afFlags = [ vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + # Copy with a different destination name just for the heck of it: + [ tdTestCopyFromDir(sSrc = oEmptyDirGst.sPath, sDst = os.path.join(sScratchDstDir2Hst, 'empty2'), + fIntoDst = True), + tdTestResultSuccess() ], + ]); + atTests.extend([ + # Now the same using a directory with files in it: + [ tdTestCopyFromDir(oSrc = oNonEmptyDirGst, sDst = sScratchDstDir3Hst + os.path.sep), tdTestResultSuccess() ], + # Again. + [ tdTestCopyFromDir(oSrc = oNonEmptyDirGst, sDst = sScratchDstDir3Hst, fIntoDst = True, + afFlags = [ vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + ]); + atTests.extend([ + # Copy the entire test tree: + [ tdTestCopyFromDir(oSrc = oTreeDirGst, sDst = sScratchDstDir4Hst + os.path.sep), tdTestResultSuccess() ], + # Again, should fail this time. + [ tdTestCopyFromDir(oSrc = oTreeDirGst, sDst = sScratchDstDir4Hst + os.path.sep), tdTestResultFailure() ], + # Works again, as DirectoryCopyFlag_CopyIntoExisting is specified. + [ tdTestCopyFromDir(oSrc = oTreeDirGst, sDst = sScratchDstDir4Hst + os.path.sep, + afFlags = [ vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + ]); + # + # Dotdot path handling. + # + if self.oTstDrv.fpApiVer >= 6.1: + atTests.extend([ + # Test if copying stuff from a guest dotdot ".." directory works. + [ tdTestCopyFromDir(sSrc = sScratchDotDotDirGst, sDst = sScratchDstDir1Hst + os.path.sep, + afFlags = [ vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), + tdTestResultFailure() ], + # Test if copying stuff from the guest to a host's dotdot ".." directory works. + # That should fail on destinations. + [ tdTestCopyFromFile(oSrc = oExistingFileGst, sDst = sScratchDotDotDirHst), tdTestResultFailure() ], + ]); + + reporter.log2('Executing tests ...'); + + # + # Execute the tests. + # + fRc = True; + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0] + oCurRes = tTest[1] # type: tdTestResult + if isinstance(oCurTest, tdTestCopyFrom): + reporter.log('Testing #%d, %s: sSrc="%s", sDst="%s", afFlags="%s" ...' + % (i, "directory" if isinstance(oCurTest, tdTestCopyFromDir) else "file", + limitString(oCurTest.sSrc), limitString(oCurTest.sDst), oCurTest.afFlags,)); + else: + reporter.log('Testing #%d, tdTestRemoveHostDir "%s" ...' % (i, oCurTest.sDir,)); + if isinstance(oCurTest, tdTestCopyFromDir) and self.oTstDrv.fpApiVer < 6.0: + reporter.log('Skipping directoryCopyFromGuest test, not implemented in %s' % (self.oTstDrv.fpApiVer,)); + continue; + + if isinstance(oCurTest, tdTestRemoveHostDir): + fRc = oCurTest.execute(self.oTstDrv, oSession, oTxsSession, oTestVm, 'testing #%d' % (i,)); + else: + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc2, oCurGuestSession = oCurTest.createSession('testGuestCtrlCopyFrom: Test #%d' % (i,)); + if fRc2 is not True: + fRc = reporter.error('Test #%d failed: Could not create session' % (i,)); + break; + + if isinstance(oCurTest, tdTestCopyFromFile): + fRc2 = self.gctrlCopyFileFrom(oCurGuestSession, oCurTest, oCurRes.fRc); + else: + fRc2 = self.gctrlCopyDirFrom(oCurGuestSession, oCurTest, oCurRes.fRc); + + if fRc2 != oCurRes.fRc: + fRc = reporter.error('Test #%d failed: Got %s, expected %s' % (i, fRc2, oCurRes.fRc)); + + fRc = oCurTest.closeSession() and fRc; + + return (fRc, oTxsSession); + + def testGuestCtrlUpdateAdditions(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals + """ + Tests updating the Guest Additions inside the guest. + + """ + + ## @todo currently disabled everywhere. + if self.oTstDrv.fpApiVer < 100.0: + reporter.log("Skipping updating GAs everywhere for now..."); + return None; + + # Skip test for updating Guest Additions if we run on a too old (Windows) guest. + ## + ## @todo make it work everywhere! + ## + if oTestVm.sKind in ('WindowsNT4', 'Windows2000', 'WindowsXP', 'Windows2003'): + reporter.log("Skipping updating GAs on old windows vm (sKind=%s)" % (oTestVm.sKind,)); + return (None, oTxsSession); + if oTestVm.isOS2(): + reporter.log("Skipping updating GAs on OS/2 guest"); + return (None, oTxsSession); + + sVBoxValidationKitIso = self.oTstDrv.sVBoxValidationKitIso; + if not os.path.isfile(sVBoxValidationKitIso): + return reporter.log('Validation Kit .ISO not found at "%s"' % (sVBoxValidationKitIso,)); + + sScratch = os.path.join(self.oTstDrv.sScratchPath, "testGctrlUpdateAdditions"); + try: + os.makedirs(sScratch); + except OSError as e: + if e.errno != errno.EEXIST: + return reporter.error('Failed: Unable to create scratch directory \"%s\"' % (sScratch,)); + reporter.log('Scratch path is: %s' % (sScratch,)); + + atTests = []; + if oTestVm.isWindows(): + atTests.extend([ + # Source is missing. + [ tdTestUpdateAdditions(sSrc = ''), tdTestResultFailure() ], + + # Wrong flags. + [ tdTestUpdateAdditions(sSrc = self.oTstDrv.getGuestAdditionsIso(), + afFlags = [ 1234 ]), tdTestResultFailure() ], + + # Non-existing .ISO. + [ tdTestUpdateAdditions(sSrc = "non-existing.iso"), tdTestResultFailure() ], + + # Wrong .ISO. + [ tdTestUpdateAdditions(sSrc = sVBoxValidationKitIso), tdTestResultFailure() ], + + # The real thing. + [ tdTestUpdateAdditions(sSrc = self.oTstDrv.getGuestAdditionsIso()), + tdTestResultSuccess() ], + # Test the (optional) installer arguments. This will extract the + # installer into our guest's scratch directory. + [ tdTestUpdateAdditions(sSrc = self.oTstDrv.getGuestAdditionsIso(), + asArgs = [ '/extract', '/D=' + sScratch ]), + tdTestResultSuccess() ] + # Some debg ISO. Only enable locally. + #[ tdTestUpdateAdditions( + # sSrc = "V:\\Downloads\\VBoxGuestAdditions-r80354.iso"), + # tdTestResultSuccess() ] + ]); + else: + reporter.log('No OS-specific tests for non-Windows yet!'); + + fRc = True; + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0] # type: tdTestUpdateAdditions + oCurRes = tTest[1] # type: tdTestResult + reporter.log('Testing #%d, sSrc="%s", afFlags="%s" ...' % (i, oCurTest.sSrc, oCurTest.afFlags,)); + + oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc, _ = oCurTest.createSession('Test #%d' % (i,)); + if fRc is not True: + fRc = reporter.error('Test #%d failed: Could not create session' % (i,)); + break; + + try: + oCurProgress = oCurTest.oGuest.updateGuestAdditions(oCurTest.sSrc, oCurTest.asArgs, oCurTest.afFlags); + except: + reporter.maybeErrXcpt(oCurRes.fRc, 'Updating Guest Additions exception for sSrc="%s", afFlags="%s":' + % (oCurTest.sSrc, oCurTest.afFlags,)); + fRc = False; + else: + if oCurProgress is not None: + oWrapperProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, + self.oTstDrv, "gctrlUpGA"); + oWrapperProgress.wait(); + if not oWrapperProgress.isSuccess(): + oWrapperProgress.logResult(fIgnoreErrors = not oCurRes.fRc); + fRc = False; + else: + fRc = reporter.error('No progress object returned'); + + oCurTest.closeSession(); + if fRc is oCurRes.fRc: + if fRc: + ## @todo Verify if Guest Additions were really updated (build, revision, ...). + ## @todo r=bird: Not possible since you're installing the same GAs as before... + ## Maybe check creation dates on certain .sys/.dll/.exe files? + pass; + else: + fRc = reporter.error('Test #%d failed: Got %s, expected %s' % (i, fRc, oCurRes.fRc)); + break; + + return (fRc, oTxsSession); + + + +class tdAddGuestCtrl(vbox.TestDriver): # pylint: disable=too-many-instance-attributes,too-many-public-methods + """ + Guest control using VBoxService on the guest. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + self.oTestVmSet = self.oTestVmManager.getSmokeVmSet('nat'); + self.asRsrcs = None; + self.fQuick = False; # Don't skip lengthly tests by default. + self.addSubTestDriver(SubTstDrvAddGuestCtrl(self)); + + # + # Overridden methods. + # + def showUsage(self): + """ + Shows the testdriver usage. + """ + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdAddGuestCtrl Options:'); + reporter.log(' --quick'); + reporter.log(' Same as --virt-modes hwvirt --cpu-counts 1.'); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + """ + Parses the testdriver arguments from the command line. + """ + if asArgs[iArg] == '--quick': + self.parseOption(['--virt-modes', 'hwvirt'], 0); + self.parseOption(['--cpu-counts', '1'], 0); + self.fQuick = True; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def actionConfig(self): + if not self.importVBoxApi(): # So we can use the constant below. + return False; + + eNic0AttachType = vboxcon.NetworkAttachmentType_NAT; + sGaIso = self.getGuestAdditionsIso(); + return self.oTestVmSet.actionConfig(self, eNic0AttachType = eNic0AttachType, sDvdImage = sGaIso); + + def actionExecute(self): + return self.oTestVmSet.actionExecute(self, self.testOneCfg); + + # + # Test execution helpers. + # + def testOneCfg(self, oVM, oTestVm): # pylint: disable=too-many-statements + """ + Runs the specified VM thru the tests. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + + self.logVmInfo(oVM); + + fRc = True; + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, fCdWait = False); + reporter.log("TxsSession: %s" % (oTxsSession,)); + if oSession is not None: + + fRc, oTxsSession = self.aoSubTstDrvs[0].testIt(oTestVm, oSession, oTxsSession); + + if self.aoSubTstDrvs[0].oDebug.fNoExit: + self.sleep(60 * 60 * 1000); # Leave the VM session open for manual inspection / debugging. + else: + self.terminateVmBySession(oSession); + else: + fRc = False; + return fRc; + + def onExit(self, iRc): + if self.aoSubTstDrvs[0].oDebug.fNoExit: + return True + return vbox.TestDriver.onExit(self, iRc); + + def gctrlReportError(self, progress): + """ + Helper function to report an error of a + given progress object. + """ + if progress is None: + reporter.log('No progress object to print error for'); + else: + errInfo = progress.errorInfo; + if errInfo: + reporter.log('%s' % (errInfo.text,)); + return False; + + def gctrlGetRemainingTime(self, msTimeout, msStart): + """ + Helper function to return the remaining time (in ms) + based from a timeout value and the start time (both in ms). + """ + if msTimeout == 0: + return 0xFFFFFFFE; # Wait forever. + msElapsed = base.timestampMilli() - msStart; + if msElapsed > msTimeout: + return 0; # No time left. + return msTimeout - msElapsed; + + def testGuestCtrlManual(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals,too-many-statements,unused-argument,unused-variable + """ + For manually testing certain bits. + """ + + reporter.log('Manual testing ...'); + fRc = True; + + sUser = 'Administrator'; + sPassword = 'password'; + + oGuest = oSession.o.console.guest; + oGuestSession = oGuest.createSession(sUser, + sPassword, + "", "Manual Test"); + + aWaitFor = [ vboxcon.GuestSessionWaitForFlag_Start ]; + _ = oGuestSession.waitForArray(aWaitFor, 30 * 1000); + + sCmd = self.getGuestSystemShell(oTestVm); + asArgs = [ sCmd, '/C', 'dir', '/S', 'c:\\windows' ]; + aEnv = []; + afFlags = []; + + for _ in xrange(100): + oProc = oGuestSession.processCreate(sCmd, asArgs if self.fpApiVer >= 5.0 else asArgs[1:], + aEnv, afFlags, 30 * 1000); + + aWaitFor = [ vboxcon.ProcessWaitForFlag_Terminate ]; + _ = oProc.waitForArray(aWaitFor, 30 * 1000); + + oGuestSession.close(); + oGuestSession = None; + + time.sleep(5); + + oSession.o.console.PowerDown(); + + return (fRc, oTxsSession); + +if __name__ == '__main__': + sys.exit(tdAddGuestCtrl().main(sys.argv)); diff --git a/src/VBox/ValidationKit/tests/additions/tdAddSharedFolders1.py b/src/VBox/ValidationKit/tests/additions/tdAddSharedFolders1.py new file mode 100755 index 00000000..e5657f7c --- /dev/null +++ b/src/VBox/ValidationKit/tests/additions/tdAddSharedFolders1.py @@ -0,0 +1,361 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +VirtualBox Validation Kit - Shared Folders #1. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Standard Python imports. +import os +import shutil +import sys + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testdriver import reporter; +from testdriver import base; +from common import utils; + + +class SubTstDrvAddSharedFolders1(base.SubTestDriverBase): + """ + Sub-test driver for executing shared folders tests. + """ + + def __init__(self, oTstDrv): + base.SubTestDriverBase.__init__(self, oTstDrv, 'add-shared-folders', 'Shared Folders'); + + self.asTestsDef = [ 'fsperf', ]; + self.asTests = self.asTestsDef; + self.asExtraArgs = []; + self.asGstFsPerfPaths = [ + '${CDROM}/vboxvalidationkit/${OS/ARCH}/FsPerf${EXESUFF}', + '${CDROM}/${OS/ARCH}/FsPerf${EXESUFF}', + '${TXSDIR}/${OS/ARCH}/FsPerf${EXESUFF}', + '${TXSDIR}/FsPerf${EXESUFF}', + 'E:/vboxvalidationkit/${OS/ARCH}/FsPerf${EXESUFF}', + ]; + self.sGuestSlash = ''; + + def parseOption(self, asArgs, iArg): + if asArgs[iArg] == '--add-shared-folders-tests': # 'add' as in 'additions', not the verb. + iArg += 1; + iNext = self.oTstDrv.requireMoreArgs(1, asArgs, iArg); + if asArgs[iArg] == 'all': + self.asTests = self.asTestsDef; + else: + self.asTests = asArgs[iArg].split(':'); + for s in self.asTests: + if s not in self.asTestsDef: + raise base.InvalidOption('The "--add-shared-folders-tests" value "%s" is not valid; valid values are: %s' + % (s, ' '.join(self.asTestsDef))); + return iNext; + if asArgs[iArg] == '--add-shared-folders-extra-arg': + iArg += 1; + iNext = self.oTstDrv.requireMoreArgs(1, asArgs, iArg); + self.asExtraArgs.append(asArgs[iArg]); + return iNext; + return iArg; + + def showUsage(self): + base.SubTestDriverBase.showUsage(self); + reporter.log(' --add-shared-folders-tests <t1[:t2[:]]>'); + reporter.log(' Default: all (%s)' % (':'.join(self.asTestsDef))); + reporter.log(' --add-shared-folders-extra-arg <fsperf-arg>'); + reporter.log(' Adds an extra FsPerf argument. Can be repeated.'); + + return True; + + def mountShareEx(self, oSession, oTxsSession, sShareName, sHostPath, sGuestMountPoint, fMustSucceed): + """ + Automount a shared folder in the guest, extended version. + + Returns success status, based on fMustSucceed. + """ + reporter.testStart('Automounting "%s"' % (sShareName,)); + + reporter.log2('Creating shared folder "%s" at "%s" ...' % (sShareName, sGuestMountPoint)); + try: + oConsole = oSession.o.console; + oConsole.createSharedFolder(sShareName, sHostPath, True, True, sGuestMountPoint); + except: + if fMustSucceed: + reporter.errorXcpt('createSharedFolder(%s,%s,True,True,%s)' % (sShareName, sHostPath, sGuestMountPoint)); + else: + reporter.log('createSharedFolder(%s,%s,True,True,%s) failed, good' % (sShareName, sHostPath, sGuestMountPoint)); + reporter.testDone(); + return False is fMustSucceed; + + # Check whether we can see the shared folder now. Retry for 30 seconds. + msStart = base.timestampMilli(); + while True: + fRc = oTxsSession.syncIsDir(sGuestMountPoint + self.sGuestSlash + 'candle.dir'); + reporter.log2('candle.dir check -> %s' % (fRc,)); + if fRc is fMustSucceed: + break; + if base.timestampMilli() - msStart > 30000: + reporter.error('Shared folder mounting timed out!'); + break; + self.oTstDrv.sleep(1); + + reporter.testDone(); + + return fRc == fMustSucceed; + + def mountShare(self, oSession, oTxsSession, sShareName, sHostPath, sGuestMountPoint): + """ + Automount a shared folder in the guest. + + Returns success status. + """ + return self.mountShareEx(oSession, oTxsSession, sShareName, sHostPath, sGuestMountPoint, fMustSucceed = True); + + def unmountShareEx(self, oSession, oTxsSession, sShareName, sGuestMountPoint, fMustSucceed): + """ + Unmounts a shared folder in the guest. + + Returns success status, based on fMustSucceed. + """ + reporter.log2('Autounmount'); + try: + oConsole = oSession.o.console; + oConsole.removeSharedFolder(sShareName); + except: + if fMustSucceed: + reporter.errorXcpt('removeSharedFolder(%s)' % (sShareName,)); + else: + reporter.log('removeSharedFolder(%s)' % (sShareName,)); + reporter.testDone(); + return False is fMustSucceed; + + # Check whether the shared folder is gone on the guest now. Retry for 30 seconds. + msStart = base.timestampMilli(); + while True: + fRc = oTxsSession.syncIsDir(sGuestMountPoint + self.sGuestSlash + 'candle.dir'); + reporter.log2('candle.dir check -> %s' % (fRc,)); + if fRc is not fMustSucceed: + break; + if base.timestampMilli() - msStart > 30000: + reporter.error('Shared folder unmounting timed out!'); + fRc = False; + break; + self.oTstDrv.sleep(1); + + reporter.testDone(); + + return fRc is not fMustSucceed; + + def unmountShare(self, oSession, oTxsSession, sShareName, sGuestMountPoint): + """ + Unmounts a shared folder in the guest, extended version. + + Returns success status, based on fMustSucceed. + """ + return self.unmountShareEx(oSession, oTxsSession, sShareName, sGuestMountPoint, fMustSucceed = True); + + def testIt(self, oTestVm, oSession, oTxsSession): + """ + Executes the test. + + Returns fRc, oTxsSession. The latter may have changed. + """ + reporter.log("Active tests: %s" % (self.asTests,)); + + # + # Skip the test if before 6.0 + # + if self.oTstDrv.fpApiVer < 6.0: + reporter.log('Requires 6.0 or later (for now)'); + return (None, oTxsSession); + + # Guess a free mount point inside the guest. + if oTestVm.isWindows() or oTestVm.isOS2(): + self.sGuestSlash = '\\'; + else: + self.sGuestSlash = '/'; + + # + # Create the host directory to share. Empty except for a 'candle.dir' subdir + # that we use to check that it mounted correctly. + # + sShareName1 = 'shfl1'; + sShareHostPath1 = os.path.join(self.oTstDrv.sScratchPath, sShareName1); + reporter.log2('Creating shared host folder "%s"...' % (sShareHostPath1,)); + if os.path.exists(sShareHostPath1): + try: shutil.rmtree(sShareHostPath1); + except: return (reporter.errorXcpt('shutil.rmtree(%s)' % (sShareHostPath1,)), oTxsSession); + try: os.mkdir(sShareHostPath1); + except: return (reporter.errorXcpt('os.mkdir(%s)' % (sShareHostPath1,)), oTxsSession); + try: os.mkdir(os.path.join(sShareHostPath1, 'candle.dir')); + except: return (reporter.errorXcpt('os.mkdir(%s)' % (sShareHostPath1,)), oTxsSession); + + # Guess a free mount point inside the guest. + if oTestVm.isWindows() or oTestVm.isOS2(): + sMountPoint1 = 'V:'; + else: + sMountPoint1 = '/mnt/' + sShareName1; + + fRc = self.mountShare(oSession, oTxsSession, sShareName1, sShareHostPath1, sMountPoint1); + if fRc is not True: + return (False, oTxsSession); # skip the remainder if we cannot auto mount the folder. + + # + # Run FsPerf inside the guest. + # + fSkip = 'fsperf' not in self.asTests; + if fSkip is False: + cMbFree = utils.getDiskUsage(sShareHostPath1); + if cMbFree >= 16: + reporter.log2('Free space: %u MBs' % (cMbFree,)); + else: + reporter.log('Skipping FsPerf because only %u MB free on %s' % (cMbFree, sShareHostPath1,)); + fSkip = True; + if fSkip is False: + # Common arguments: + asArgs = ['FsPerf', '-d', sMountPoint1 + self.sGuestSlash + 'fstestdir-1', '-s8']; + + # Skip part of mmap on older windows systems without CcCoherencyFlushAndPurgeCache (>= w7). + reporter.log2('oTestVm.sGuestOsType=%s' % (oTestVm.sGuestOsType,)); + if oTestVm.getNonCanonicalGuestOsType() \ + in [ 'WindowsNT3x', 'WindowsNT4', 'Windows2000', 'WindowsXP', 'WindowsXP_64', 'Windows2003', + 'Windows2003_64', 'WindowsVista', 'WindowsVista_64', 'Windows2008', 'Windows2008_64']: + asArgs.append('--no-mmap-coherency'); + + # Configure I/O block sizes according to guest memory size: + cbMbRam = 128; + try: cbMbRam = oSession.o.machine.memorySize; + except: reporter.errorXcpt(); + reporter.log2('cbMbRam=%s' % (cbMbRam,)); + asArgs.append('--set-block-size=1'); + asArgs.append('--add-block-size=512'); + asArgs.append('--add-block-size=4096'); + asArgs.append('--add-block-size=16384'); + asArgs.append('--add-block-size=65536'); + asArgs.append('--add-block-size=1048576'); # 1 MiB + if cbMbRam >= 512: + asArgs.append('--add-block-size=33554432'); # 32 MiB + if cbMbRam >= 768: + asArgs.append('--add-block-size=134217728'); # 128 MiB + + # Putting lots (10000) of files in a single directory causes issues on OS X + # (HFS+ presumably, though could be slow disks) and some linuxes (slow disks, + # maybe ext2/3?). So, generally reduce the file count to 4096 everywhere + # since we're not here to test the host file systems, and 3072 on macs. + if utils.getHostOs() in [ 'darwin', ]: + asArgs.append('--many-files=3072'); + elif utils.getHostOs() in [ 'linux', ]: + asArgs.append('--many-files=4096'); + + # Add the extra arguments from the command line and kick it off: + asArgs.extend(self.asExtraArgs); + + # Run FsPerf: + reporter.log2('Starting guest FsPerf (%s)...' % (asArgs,)); + sFsPerfPath = self._locateGstFsPerf(oTxsSession); + + ## @todo For some odd reason the combined GA/VaKit .ISO (by IPRT/fs/isomakercmd) + # sometimes (?) contains FsPerf as non-executable (-r--r--r-- 1 root root) on Linux. + # + # So work around this for now by copying the desired FsPerf binary to the temp directory, + # make it executable and execute it from there. + fISOMakerCmdIsBuggy = oTestVm.isLinux(); + if fISOMakerCmdIsBuggy: + sFsPerfPathTemp = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), 'FsPerf${EXESUFF}'); + if oTestVm.isWindows() \ + or oTestVm.isOS2(): + sCopy = self.oTstDrv.getGuestSystemShell(); + sCopyArgs = ( sCopy, "/C", "copy", "/Y", sFsPerfPath, sFsPerfPathTemp ); + else: + sCopy = oTestVm.pathJoin(self.oTstDrv.getGuestSystemDir(oTestVm), 'cp'); + sCopyArgs = ( sCopy, "-a", "-v", sFsPerfPath, sFsPerfPathTemp ); + fRc = self.oTstDrv.txsRunTest(oTxsSession, 'Copying FsPerf', 60 * 1000, + sCopy, sCopyArgs, fCheckSessionStatus = True); + fRc = fRc and oTxsSession.syncChMod(sFsPerfPathTemp, 0o755); + if fRc: + sFsPerfPath = sFsPerfPathTemp; + + fRc = self.oTstDrv.txsRunTest(oTxsSession, 'Running FsPerf', 90 * 60 * 1000, sFsPerfPath, asArgs, + fCheckSessionStatus = True); + reporter.log2('FsPerf -> %s' % (fRc,)); + if fRc: + # Do a bit of diagnosis to find out why this failed. + if not oTestVm.isWindows() \ + and not oTestVm.isOS2(): + sCmdLs = oTestVm.pathJoin(self.oTstDrv.getGuestSystemDir(oTestVm), 'ls'); + oTxsSession.syncExec(sCmdLs, (sCmdLs, "-al", sFsPerfPath), fIgnoreErrors = True); + oTxsSession.syncExec(sCmdLs, (sCmdLs, "-al", "-R", "/opt"), fIgnoreErrors = True); + oTxsSession.syncExec(sCmdLs, (sCmdLs, "-al", "-R", "/media/cdrom"), fIgnoreErrors = True); + + sTestDir = os.path.join(sShareHostPath1, 'fstestdir-1'); + if os.path.exists(sTestDir): + fRc = reporter.errorXcpt('test directory lingers: %s' % (sTestDir,)); + try: shutil.rmtree(sTestDir); + except: fRc = reporter.errorXcpt('shutil.rmtree(%s)' % (sTestDir,)); + else: + reporter.testStart('FsPerf'); + reporter.testDone(fSkip or fRc is None); + + # + # Check if auto-unmounting works. + # + if fRc is True: + fRc = self.unmountShare(oSession, oTxsSession, sShareName1, sMountPoint1); + + ## @todo Add tests for multiple automount shares, random unmounting, reboot test. + + return (fRc, oTxsSession); + + def _locateGstFsPerf(self, oTxsSession): + """ + Returns guest side path to FsPerf. + """ + for sFsPerfPath in self.asGstFsPerfPaths: + if oTxsSession.syncIsFile(sFsPerfPath): + reporter.log('Using FsPerf at "%s"' % (sFsPerfPath,)); + return sFsPerfPath; + reporter.log('Unable to find guest FsPerf in any of these places: %s' % ('\n '.join(self.asGstFsPerfPaths),)); + return self.asGstFsPerfPaths[0]; + + + +if __name__ == '__main__': + reporter.error('Cannot run standalone, use tdAddBasic1.py'); + sys.exit(1); diff --git a/src/VBox/ValidationKit/tests/api/Makefile.kmk b/src/VBox/ValidationKit/tests/api/Makefile.kmk new file mode 100644 index 00000000..b202989a --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/Makefile.kmk @@ -0,0 +1,72 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - API Tests. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +INSTALLS += ValidationKitTestsApi +ValidationKitTestsApi_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsApi_INST = $(INST_VALIDATIONKIT)tests/api/ +ValidationKitTestsApi_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdApi1.py \ + $(PATH_SUB_CURRENT)/tdAppliance1.py \ + $(PATH_SUB_CURRENT)/tdCreateVMWithDefaults1.py \ + $(PATH_SUB_CURRENT)/tdMoveMedium1.py \ + $(PATH_SUB_CURRENT)/tdMoveVm1.py \ + $(PATH_SUB_CURRENT)/tdPython1.py \ + $(PATH_SUB_CURRENT)/tdTreeDepth1.py + +ifndef VBOX_OSE + ValidationKitTestsApi_EXEC_SOURCES += \ + $(PATH_SUB_CURRENT)/tdCloud1.py \ + $(PATH_SUB_CURRENT)/tdOciConnection1.py \ + $(PATH_SUB_CURRENT)/tdOciExport1.py \ + $(PATH_SUB_CURRENT)/tdOciImage1.py \ + $(PATH_SUB_CURRENT)/tdOciImport1.py \ + $(PATH_SUB_CURRENT)/tdOciInstance1.py \ + $(PATH_SUB_CURRENT)/tdOciProfile1.py +endif +ValidationKitTestsApi_SOURCES := \ + $(wildcard \ + $(PATH_SUB_CURRENT)/*.ova \ + ) + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsApi_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/api/__init__.py b/src/VBox/ValidationKit/tests/api/__init__.py new file mode 100644 index 00000000..5e96b424 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/__init__.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# $Id: __init__.py $ + +""" +Just to make python 2.x happy. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" diff --git a/src/VBox/ValidationKit/tests/api/tdApi1.py b/src/VBox/ValidationKit/tests/api/tdApi1.py new file mode 100755 index 00000000..012c6757 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdApi1.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdApi1.py $ + +""" +VirtualBox Validation Kit - API Test wrapper #1 combining all API sub-tests +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 155025 $" + + +# Standard Python imports. +import os +import sys + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0] +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(g_ksValidationKitDir) + +# Validation Kit imports. +from testdriver import vbox + + +class tdApi1(vbox.TestDriver): + """ + API Test wrapper #1. + """ + + def __init__(self, aoSubTestDriverClasses = None): + vbox.TestDriver.__init__(self) + for oSubTestDriverClass in aoSubTestDriverClasses: + self.addSubTestDriver(oSubTestDriverClass(self)); + + # + # Overridden methods. + # + + def actionConfig(self): + """ + Import the API. + """ + if not self.importVBoxApi(): + return False + return True + + def actionExecute(self): + """ + Execute the testcase, i.e. all sub-tests. + """ + fRc = True; + for oSubTstDrv in self.aoSubTstDrvs: + if oSubTstDrv.fEnabled: + fRc = oSubTstDrv.testIt() and fRc; + return fRc; + + +if __name__ == '__main__': + sys.path.append(os.path.dirname(os.path.abspath(__file__))) + from tdPython1 import SubTstDrvPython1; # pylint: disable=relative-import + from tdAppliance1 import SubTstDrvAppliance1; # pylint: disable=relative-import + from tdMoveMedium1 import SubTstDrvMoveMedium1; # pylint: disable=relative-import + from tdTreeDepth1 import SubTstDrvTreeDepth1; # pylint: disable=relative-import + from tdMoveVm1 import SubTstDrvMoveVm1; # pylint: disable=relative-import + from tdCloneMedium1 import SubTstDrvCloneMedium1;# pylint: disable=relative-import + sys.exit(tdApi1([SubTstDrvPython1, SubTstDrvAppliance1, SubTstDrvMoveMedium1, + SubTstDrvTreeDepth1, SubTstDrvMoveVm1, SubTstDrvCloneMedium1]).main(sys.argv)) diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t1.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t1.ova Binary files differnew file mode 100644 index 00000000..aba10dbb --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t1.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t2-ovftool-4.1.0.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t2-ovftool-4.1.0.ova Binary files differnew file mode 100644 index 00000000..19c2c4b9 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t2-ovftool-4.1.0.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t2.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t2.ova Binary files differnew file mode 100644 index 00000000..66a173aa --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t2.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t3-ovftool-4.1.0.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t3-ovftool-4.1.0.ova Binary files differnew file mode 100644 index 00000000..8027ce64 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t3-ovftool-4.1.0.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t3.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t3.ova Binary files differnew file mode 100644 index 00000000..7fbf44ee --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t3.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t4-ovftool-4.1.0.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t4-ovftool-4.1.0.ova Binary files differnew file mode 100644 index 00000000..2434f37e --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t4-ovftool-4.1.0.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t4.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t4.ova Binary files differnew file mode 100644 index 00000000..a6549b15 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t4.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t4.pem b/src/VBox/ValidationKit/tests/api/tdAppliance1-t4.pem new file mode 100644 index 00000000..c155d659 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t4.pem @@ -0,0 +1,74 @@ +-----BEGIN PRIVATE KEY----- +MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALRXOpwrjjFzFZtb +aTtB+3kNrgrqYkye6CqvrOODZwXsljr/pduYj6bYh5OdznAFqgeeCpkqEf/2p5mD +XlqE4cDoL2opLk7LrdmVDqX9rUkGcGf6hwTp1EAllESBIUt6LdSbL7Y5goYq1E++ +/fKSYRIniCbWp9ahTLH8hjDcw48tAgMBAAECgYAiNl4vHHA4X13dAEWBcW4UtAyt +k3Ocl5Tx7Cv/aYFU9WI2xSMg+ttdyrxFu+1bASgVk9zs27dYeOGo1OxEfesZzQkT +mbzvYCdYk9wAWKXQwpp78HZyEsKVipIxO+riH9ph7SFQBzB5NoADPoqwahOmeQQW +sE3oTJRa9O+JR3muXQJBAOOt4dj+1Rwmdy83j9uOLLfO75l2pJd/hq5gN7+eNFks +R68cbhFGkrOU13dVLquyqxAoaKc2PgZS+RfGRrohjm8CQQDKxeuqYXyrYBU4jNAS +TgcR4lbb8HiC1AyQrdiJVtH4qLpqk92M7muHXZQGOXRizHQMrRDY+PcgLoFAnRzJ +j0ojAkEA3rqL5ivlbtRyY86G/NHpDSdzXT2jZlFq/8tAvkOWEmYu+i9lvaC8gtFo +t2Stc2olzni5aFq38pfY9lkRd6S8IQJBAJe3LJPnqwrish4Epa38eae07P5U1yY0 +GE6r9EcWEbZ2MDyL9Al9XjEDIDzkAiPmC7JsTx24cda/VPAOXbqlnncCQQCCevzg +rrnTz0/3iLWkxowiKCE1EvbVALDxPwHi0zvjXbGZs8BodLxeChpJzFImbxRVAIrp +WvZOuLqhAKjL7OOT +-----END PRIVATE KEY----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 14180722474914962380 (0xc4cc0bf54fb45bcc) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=DE, ST=Example, L=For Instance, O=Beispiel GmbH, CN=beispiel.example.org + Validity + Not Before: Feb 1 01:45:27 2016 GMT + Not After : Jan 24 01:45:27 2046 GMT + Subject: C=DE, ST=Example, L=For Instance, O=Beispiel GmbH, CN=beispiel.example.org + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:b4:57:3a:9c:2b:8e:31:73:15:9b:5b:69:3b:41: + fb:79:0d:ae:0a:ea:62:4c:9e:e8:2a:af:ac:e3:83: + 67:05:ec:96:3a:ff:a5:db:98:8f:a6:d8:87:93:9d: + ce:70:05:aa:07:9e:0a:99:2a:11:ff:f6:a7:99:83: + 5e:5a:84:e1:c0:e8:2f:6a:29:2e:4e:cb:ad:d9:95: + 0e:a5:fd:ad:49:06:70:67:fa:87:04:e9:d4:40:25: + 94:44:81:21:4b:7a:2d:d4:9b:2f:b6:39:82:86:2a: + d4:4f:be:fd:f2:92:61:12:27:88:26:d6:a7:d6:a1: + 4c:b1:fc:86:30:dc:c3:8f:2d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 02:0A:B0:BC:21:63:C1:50:16:1E:8D:B7:F4:B0:1C:48:D8:E1:0A:2A + X509v3 Authority Key Identifier: + keyid:02:0A:B0:BC:21:63:C1:50:16:1E:8D:B7:F4:B0:1C:48:D8:E1:0A:2A + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha256WithRSAEncryption + 13:f1:33:5f:c7:d9:6c:20:a4:eb:f2:e5:e2:b5:e0:e8:b6:9c: + c7:62:4a:39:53:83:11:98:cf:11:3d:58:09:d8:38:78:71:16: + d4:24:cc:c8:2e:5a:2b:d3:94:6a:dc:ae:62:e7:81:6a:5f:04: + 84:ba:55:8c:dc:6b:ff:aa:78:4f:37:8e:fd:ba:b5:d1:27:83: + 47:29:30:92:63:85:53:f0:b1:b9:f4:c7:a8:b1:48:44:4e:30: + 6f:50:d3:35:14:87:59:d0:f8:ed:da:07:60:6c:de:6d:53:53: + 3d:d7:03:97:1f:6b:13:ce:92:49:20:57:4f:b0:87:30:76:66: + d3:43 +-----BEGIN CERTIFICATE----- +MIICqDCCAhGgAwIBAgIJAMTMC/VPtFvMMA0GCSqGSIb3DQEBCwUAMG0xCzAJBgNV +BAYTAkRFMRAwDgYDVQQIDAdFeGFtcGxlMRUwEwYDVQQHDAxGb3IgSW5zdGFuY2Ux +FjAUBgNVBAoMDUJlaXNwaWVsIEdtYkgxHTAbBgNVBAMMFGJlaXNwaWVsLmV4YW1w +bGUub3JnMB4XDTE2MDIwMTAxNDUyN1oXDTQ2MDEyNDAxNDUyN1owbTELMAkGA1UE +BhMCREUxEDAOBgNVBAgMB0V4YW1wbGUxFTATBgNVBAcMDEZvciBJbnN0YW5jZTEW +MBQGA1UECgwNQmVpc3BpZWwgR21iSDEdMBsGA1UEAwwUYmVpc3BpZWwuZXhhbXBs +ZS5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALRXOpwrjjFzFZtbaTtB ++3kNrgrqYkye6CqvrOODZwXsljr/pduYj6bYh5OdznAFqgeeCpkqEf/2p5mDXlqE +4cDoL2opLk7LrdmVDqX9rUkGcGf6hwTp1EAllESBIUt6LdSbL7Y5goYq1E++/fKS +YRIniCbWp9ahTLH8hjDcw48tAgMBAAGjUDBOMB0GA1UdDgQWBBQCCrC8IWPBUBYe +jbf0sBxI2OEKKjAfBgNVHSMEGDAWgBQCCrC8IWPBUBYejbf0sBxI2OEKKjAMBgNV +HRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBABPxM1/H2WwgpOvy5eK14Oi2nMdi +SjlTgxGYzxE9WAnYOHhxFtQkzMguWivTlGrcrmLngWpfBIS6VYzca/+qeE83jv26 +tdEng0cpMJJjhVPwsbn0x6ixSEROMG9Q0zUUh1nQ+O3aB2Bs3m1TUz3XA5cfaxPO +kkkgV0+whzB2ZtND +-----END CERTIFICATE----- diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t5-ovftool-4.1.0.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t5-ovftool-4.1.0.ova Binary files differnew file mode 100644 index 00000000..6a21a19b --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t5-ovftool-4.1.0.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t5.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t5.ova Binary files differnew file mode 100644 index 00000000..8e6e2e2e --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t5.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t6-ovftool-4.1.0.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t6-ovftool-4.1.0.ova Binary files differnew file mode 100644 index 00000000..d5a92eb2 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t6-ovftool-4.1.0.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t6.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t6.ova Binary files differnew file mode 100644 index 00000000..6480e6d5 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t6.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t6.pem b/src/VBox/ValidationKit/tests/api/tdAppliance1-t6.pem new file mode 100644 index 00000000..1b682b0d --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t6.pem @@ -0,0 +1,134 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEAzYlueHZlbSqlLrVAnCyQx9xgnO7jSjK6wXqlbxkl0QZLyDnh +vNowZYnu7YZI3V+RuVzYbczlEr1qZpUeO8NF04mK1kFPJRinRiuiOtih+WwvDQd7 +7RZJhjoAcfEeogt1OzOZg8QKF1AMGlUgk5zWma4VIS75ZM6G1b77qS3rge6L9NwZ +gthyQ1PvddYue4MNEpK+2ekZZq/GGZvd+yNP0Wq7kzcmydCgCF9EdLCZwt4BHmOS +ZM6BUxKv+PINZgO7Wp2tBBGigg9Mf/NXofjMHp7RI16xa2W9Q/Ct2A6rb5SAB10U +ddrtxiI+F+Jv7lNkHOCLbju7cEs+Y6XIbt1l7aVqBFpgo6cwFZ7DdLPHZkgb50hx +dnNdwncOR1syspTF544hbmkcb/8+weq+qgsD/57b/ZGrwb8ruByDz0LZiXzxxBRe +w9OhHZGFnuC0D1ciIvpifKe4R/kLcEgVW7yYp4W1BAZgiBJPgXArJTlUBrxamzsQ +m6N7GxlP+vTCny7IJsQ1KWu5BqkPv0T1+zD5vUT9/lAa+XBdDHhYVm0adr4dXIef +6ws+0zbpz8DyaD/Bg9KvRKdnziVc+pKwkWOVrFvZ6NvBex0i8Bgh4FrpkdjmrZMZ +uuCdex0CEayuYexgUqyMg9m0GwGqVNG6mVB4cCOg0rayaqEKrbtUSxNaD/MCAwEA +AQKCAgAnjPGQBqBf0Fv2z/P92WmGu/ZvXFyqU3aycmpRJZKsVTzR66lvkMDNWSx7 +0mJFDvXYqHAROOM/pulJkho+P8Y4/XeU4P5c0hCmJRFTp4oLl/C53h3PsoE1bgXV +5yMQ5YmKedRpkZirgcDCdG0PWpfE/MWeHA7rgf5aNSTyGh7+YqvV02CpWAMsx4MF +ttA8/ivOziQhhIRZySsilGazw8jBMHulyXASV63jzok6txzvbY7jjR+HfGFQXgE0 +s0c2wTMVLdA0Pzx5MH51BJtxVJHato7h8n/LfclcwHyDXddJYlb8k8GzKAynGsG+ +ENmfD7btA5xw+teHtULtI+KcysepCeVXW7Fr7PFJe1XOx1c24WrFpjeGdnG3PdqA +Y9YbjMCD/GpCUICla3N6iRDG3imY+DUWcExVjUo6TUbQ4ofQT4WF7+2Yzg6MRqYu +41jgF+voCXS4BH8e+ngEjd8VLHma7FPuWujodCERsvxZ7aLXBcZCH1Lje9Y8a79x +RGX83pVHTWFRyo4wKBpRUz+hwH7MjYdfE2Q+Zl153pXw3au9jX8PUMhx9swWPGRw +CvLPwtTwrezzdFeXg4wfJwWnvqhKIViery4z3rdjh5Zke/9+7gXmFbZ8w+QUkXXq +cnBEJySTvpI4B9abpcEI4OO84L/A6ENDO10W4ZGfm5aD2m0vIQKCAQEA6nZAmsSr +T1CcgrWNcY6civ8o5cMF8fM9ib5QvibRlJN9dpcZPr6+Pp2UPCkjVowk/FybP1Kw +sqA0aXOL3VR03YpLrqdUDpe9YiFCa3ttDX0pTOdU9QW2RbOrNQVurKhzNETd37cJ +0RYBOHJY/RxA0F8l16wSr1jTO/SRbWkFJYZS/OaH1B6gCxz6H1IgfKJddrvFZsd8 +6PnLtqsPnsXuxugqHu3ENddRqiiSNyGnqpGu0jQpPBrQEQQN5o8/X9rfNfAobChH +Nii0va7MHudwaTTo2ytUzEV+v0kueMNU9YXgJgxzSzf1/VQC+HoNI4Vbs9XCbOWV +z2FVBwXM+0cIMQKCAQEA4Gr1pQAymJq/9p2fiWSR8TVdrYCwvAnBu8QSqSzweNZC +SRt7Nkhhljm7M2DuUOioMaFAT9CnyD2nJyKSAqqOngb0vo7C53UzgFu+QFMtUx/Z +t/C96WcLYrErXfnB/sTq0Gp4tpoo1S3FZecabhlZyv1O08WXiNCcEX1VMQz2+iF9 ++n2PjFuEomeWHxBWCwfB9/S0pKVHP/7833sQ8Dfsm2EwtPy3v58ln3UguaUDIe5V +r0TmKr8kDrYgbw1ZpBOd9Wbbvj3vIN34OPoSIrcar67DCp7qYq77LYAhngfdQGed +MUMsbU1dODWf0kwP7mncTr3mPByQUHa3ImjvJPv1YwKCAQBYa04Dz8U2/RR46pSz +zW9Vr9Ixi7GTRALiDkaO3z7MRC7daTAZDH/cRzre0TjFa8aK8TWO1NVUF7yMRAnr +5uzHm17dN7coZasC9b4BoKNIofnQSbEtUgEiGhanwSuyqzf+7zWpJ3LpSd4d9ml+ +0ofSzP8NbZQCUoIeqyWo2CEbvKNRQnLY2M/MQRpGc4dS2TxcCYXxM6v0hDeB5NLY +MpbQpj80OMB0+YWPoQs7BVMgrR37obYnN4ld0WSYnU7uDDF/OtlTqIDqeMFogyHx +SaCH3G8wMBAjlNWut59x5WAF033re2iDZlA7P9J6+DQ6QBGMKUHQJWiws2kIY/Sg +knIRAoIBAQCzlkR/Rxo2LthhZR/PFfEIQrl1Z9+GipRDSxPX2AOT33nqARjnhqK5 +UfexlOcBTj2SgcTyWjp6LoQ9+Bc6FPzODyj5+UqVaJ/PHxuvZCCIPZu/6+I+Dlz5 +HGhk6sJIu5JhOGLjVZhJiDhIZNkstBK8M1tKcvvh23aZNF/hQcu+vOCQfLxMCMyq +HhTvROZmK04Yu/V3MGBFISuBN32FjmtEqFEO9JGiwZuc8GFAzoEkPRLKkGtUV+Nl +9m8cD2XlvGESib5djjh3Z8oE5nFu4HJ1lne0XxmX4QlWDwxX51kx+fi7/FJoIZnw +qlD8PCwfkQ1g4eyFvCHskiPZYHnHce2bAoIBACW40wFIPiDRhjNywEi4miN6P+Xs +rlM9csmxw2GG6Z4c8H/Z4SNBRQmnj/F6PHsar6SGy9WlR9mNeR2XBn4Pyf/cNjTQ +bKzV5wPRm6P81SQjhIz4Mxdx1S30AeF1LdagWFiq0on7oRTH2SeKQspdpNeiTDbC +sBw4SVDKmGZYGZcuT3BdvvZFEW4qncSYuUM7l9bTmsbzid/v8zn/XDQrpdPYnptD +ljJETKQzlyrJLtTbyFlo3Osf1N4408u3rqhpw2SgKdyMiHndhxkF869Vycll+VMz +SzPU0wI62BIPWHDBJLnxGBTa+4kUSxP+oDvCfVYCmDcfDRew3MWCK9emJnU= +-----END RSA PRIVATE KEY----- +Certificate: + Data: + Version: 1 (0x0) + Serial Number: 1 (0x1) + Signature Algorithm: sha512WithRSAEncryption + Issuer: C=DE, ST=Example, L=For Instance, O=Beispiel GmbH, CN=beispiel.example.org + Validity + Not Before: Feb 15 14:27:56 2016 GMT + Not After : Feb 2 14:27:56 2066 GMT + Subject: C=DE, ST=Instance-Example, L=Examplecity, O=For Instance GmbH, OU=The Example Unit, CN=subcert.example.org/emailAddress=subcert@example.org + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (4096 bit) + Modulus: + 00:cd:89:6e:78:76:65:6d:2a:a5:2e:b5:40:9c:2c: + 90:c7:dc:60:9c:ee:e3:4a:32:ba:c1:7a:a5:6f:19: + 25:d1:06:4b:c8:39:e1:bc:da:30:65:89:ee:ed:86: + 48:dd:5f:91:b9:5c:d8:6d:cc:e5:12:bd:6a:66:95: + 1e:3b:c3:45:d3:89:8a:d6:41:4f:25:18:a7:46:2b: + a2:3a:d8:a1:f9:6c:2f:0d:07:7b:ed:16:49:86:3a: + 00:71:f1:1e:a2:0b:75:3b:33:99:83:c4:0a:17:50: + 0c:1a:55:20:93:9c:d6:99:ae:15:21:2e:f9:64:ce: + 86:d5:be:fb:a9:2d:eb:81:ee:8b:f4:dc:19:82:d8: + 72:43:53:ef:75:d6:2e:7b:83:0d:12:92:be:d9:e9: + 19:66:af:c6:19:9b:dd:fb:23:4f:d1:6a:bb:93:37: + 26:c9:d0:a0:08:5f:44:74:b0:99:c2:de:01:1e:63: + 92:64:ce:81:53:12:af:f8:f2:0d:66:03:bb:5a:9d: + ad:04:11:a2:82:0f:4c:7f:f3:57:a1:f8:cc:1e:9e: + d1:23:5e:b1:6b:65:bd:43:f0:ad:d8:0e:ab:6f:94: + 80:07:5d:14:75:da:ed:c6:22:3e:17:e2:6f:ee:53: + 64:1c:e0:8b:6e:3b:bb:70:4b:3e:63:a5:c8:6e:dd: + 65:ed:a5:6a:04:5a:60:a3:a7:30:15:9e:c3:74:b3: + c7:66:48:1b:e7:48:71:76:73:5d:c2:77:0e:47:5b: + 32:b2:94:c5:e7:8e:21:6e:69:1c:6f:ff:3e:c1:ea: + be:aa:0b:03:ff:9e:db:fd:91:ab:c1:bf:2b:b8:1c: + 83:cf:42:d9:89:7c:f1:c4:14:5e:c3:d3:a1:1d:91: + 85:9e:e0:b4:0f:57:22:22:fa:62:7c:a7:b8:47:f9: + 0b:70:48:15:5b:bc:98:a7:85:b5:04:06:60:88:12: + 4f:81:70:2b:25:39:54:06:bc:5a:9b:3b:10:9b:a3: + 7b:1b:19:4f:fa:f4:c2:9f:2e:c8:26:c4:35:29:6b: + b9:06:a9:0f:bf:44:f5:fb:30:f9:bd:44:fd:fe:50: + 1a:f9:70:5d:0c:78:58:56:6d:1a:76:be:1d:5c:87: + 9f:eb:0b:3e:d3:36:e9:cf:c0:f2:68:3f:c1:83:d2: + af:44:a7:67:ce:25:5c:fa:92:b0:91:63:95:ac:5b: + d9:e8:db:c1:7b:1d:22:f0:18:21:e0:5a:e9:91:d8: + e6:ad:93:19:ba:e0:9d:7b:1d:02:11:ac:ae:61:ec: + 60:52:ac:8c:83:d9:b4:1b:01:aa:54:d1:ba:99:50: + 78:70:23:a0:d2:b6:b2:6a:a1:0a:ad:bb:54:4b:13: + 5a:0f:f3 + Exponent: 65537 (0x10001) + Signature Algorithm: sha512WithRSAEncryption + 2d:1c:49:41:1a:4f:dc:d8:5c:b1:fa:ca:53:38:86:26:6e:56: + a5:6d:2e:1d:0d:74:64:0e:89:c3:3a:c7:1d:01:6e:d1:93:b2: + c9:37:01:6a:ae:31:42:96:05:d7:df:fd:01:f8:bc:f3:f3:4c: + cd:75:ae:16:00:61:78:f2:67:c5:b1:76:76:16:39:ba:d2:6b: + 09:ad:99:2b:22:ce:56:89:4d:08:ca:8c:76:4c:50:6b:83:c9: + 46:9b:f5:9f:2d:e2:7f:e5:72:aa:76:56:c4:67:83:45:26:b7: + e2:ae:f7:1e:61:c9:aa:2e:8d:b8:59:42:84:37:25:c8:16:92: + d6:d5 +-----BEGIN CERTIFICATE----- +MIIEGjCCA4MCAQEwDQYJKoZIhvcNAQENBQAwbTELMAkGA1UEBhMCREUxEDAOBgNV +BAgMB0V4YW1wbGUxFTATBgNVBAcMDEZvciBJbnN0YW5jZTEWMBQGA1UECgwNQmVp +c3BpZWwgR21iSDEdMBsGA1UEAwwUYmVpc3BpZWwuZXhhbXBsZS5vcmcwIBcNMTYw +MjE1MTQyNzU2WhgPMjA2NjAyMDIxNDI3NTZaMIG3MQswCQYDVQQGEwJERTEZMBcG +A1UECAwQSW5zdGFuY2UtRXhhbXBsZTEUMBIGA1UEBwwLRXhhbXBsZWNpdHkxGjAY +BgNVBAoMEUZvciBJbnN0YW5jZSBHbWJIMRkwFwYDVQQLDBBUaGUgRXhhbXBsZSBV +bml0MRwwGgYDVQQDDBNzdWJjZXJ0LmV4YW1wbGUub3JnMSIwIAYJKoZIhvcNAQkB +FhNzdWJjZXJ0QGV4YW1wbGUub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAzYlueHZlbSqlLrVAnCyQx9xgnO7jSjK6wXqlbxkl0QZLyDnhvNowZYnu +7YZI3V+RuVzYbczlEr1qZpUeO8NF04mK1kFPJRinRiuiOtih+WwvDQd77RZJhjoA +cfEeogt1OzOZg8QKF1AMGlUgk5zWma4VIS75ZM6G1b77qS3rge6L9NwZgthyQ1Pv +ddYue4MNEpK+2ekZZq/GGZvd+yNP0Wq7kzcmydCgCF9EdLCZwt4BHmOSZM6BUxKv ++PINZgO7Wp2tBBGigg9Mf/NXofjMHp7RI16xa2W9Q/Ct2A6rb5SAB10UddrtxiI+ +F+Jv7lNkHOCLbju7cEs+Y6XIbt1l7aVqBFpgo6cwFZ7DdLPHZkgb50hxdnNdwncO +R1syspTF544hbmkcb/8+weq+qgsD/57b/ZGrwb8ruByDz0LZiXzxxBRew9OhHZGF +nuC0D1ciIvpifKe4R/kLcEgVW7yYp4W1BAZgiBJPgXArJTlUBrxamzsQm6N7GxlP ++vTCny7IJsQ1KWu5BqkPv0T1+zD5vUT9/lAa+XBdDHhYVm0adr4dXIef6ws+0zbp +z8DyaD/Bg9KvRKdnziVc+pKwkWOVrFvZ6NvBex0i8Bgh4FrpkdjmrZMZuuCdex0C +EayuYexgUqyMg9m0GwGqVNG6mVB4cCOg0rayaqEKrbtUSxNaD/MCAwEAATANBgkq +hkiG9w0BAQ0FAAOBgQAtHElBGk/c2Fyx+spTOIYmblalbS4dDXRkDonDOscdAW7R +k7LJNwFqrjFClgXX3/0B+Lzz80zNda4WAGF48mfFsXZ2Fjm60msJrZkrIs5WiU0I +yox2TFBrg8lGm/WfLeJ/5XKqdlbEZ4NFJrfirvceYcmqLo24WUKENyXIFpLW1Q== +-----END CERTIFICATE----- diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t7-bad-instance.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t7-bad-instance.ova Binary files differnew file mode 100644 index 00000000..48e49059 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t7-bad-instance.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1.py b/src/VBox/ValidationKit/tests/api/tdAppliance1.py new file mode 100755 index 00000000..c630bb3a --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdAppliance1.py $ + +""" +VirtualBox Validation Kit - IAppliance Test #1 +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os +import sys +import tarfile + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0] +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(g_ksValidationKitDir) + +# Validation Kit imports. +from testdriver import base; +from testdriver import reporter; +from testdriver import vboxwrappers; + + +class SubTstDrvAppliance1(base.SubTestDriverBase): + """ + Sub-test driver for IAppliance Test #1. + """ + + def __init__(self, oTstDrv): + base.SubTestDriverBase.__init__(self, oTstDrv, 'appliance', 'Applicance'); + + def testIt(self): + """ + Execute the sub-testcase. + """ + fRc = True; + + # Import a set of simple OVAs. + # Note! Manifests generated by ovftool 4.0.0 does not include the ovf, while the ones b 4.1.0 does. + for sOva in ( + # t1 is a plain VM without any disks, ovftool 4.0 export from fusion + 'tdAppliance1-t1.ova', + # t2 is a plain VM with one disk. Both 4.0 and 4.1.0 exports. + 'tdAppliance1-t2.ova', + 'tdAppliance1-t2-ovftool-4.1.0.ova', + # t3 is a VM with one gzipped disk and selecting SHA256 on the ovftool cmdline (--compress=9 --shaAlgorithm=sha256). + 'tdAppliance1-t3.ova', + 'tdAppliance1-t3-ovftool-4.1.0.ova', + # t4 is a VM with with two gzipped disk, SHA256 and a (self) signed manifest (--privateKey=./tdAppliance1-t4.pem). + 'tdAppliance1-t4.ova', + 'tdAppliance1-t4-ovftool-4.1.0.ova', + # t5 is a VM with with one gzipped disk, SHA1 and a manifest signed by a valid (2016) DigiCert code signing cert. + 'tdAppliance1-t5.ova', + 'tdAppliance1-t5-ovftool-4.1.0.ova', + # t6 is a VM with with one gzipped disk, SHA1 and a manifest signed by a certificate issued by the t4 certificate, + # thus it should be impossible to establish a trusted path to a root CA. + 'tdAppliance1-t6.ova', + 'tdAppliance1-t6-ovftool-4.1.0.ova', + # t7 is based on tdAppliance1-t2-ovftool-4.1.0.ova and has modified to have an invalid InstanceID as well as an + # extra readme file. It was tarred up using bsdtar 2.4.12 on windows, so it uses a slightly different tar format and + # have different file attributes. + 'tdAppliance1-t7-bad-instance.ova', + ): + reporter.testStart(sOva); + try: + fRc = self.testImportOva(os.path.join(g_ksValidationKitDir, 'tests', 'api', sOva)) and fRc; + fRc = self.testImportOvaAsOvf(os.path.join(g_ksValidationKitDir, 'tests', 'api', sOva)) and fRc; + except: + reporter.errorXcpt(); + fRc = False; + fRc = reporter.testDone() and fRc; + + ## @todo more stuff + return fRc; + + # + # Test execution helpers. + # + + def testImportOva(self, sOva): + """ xxx """ + oVirtualBox = self.oTstDrv.oVBoxMgr.getVirtualBox(); + + # + # Import it as OVA. + # + try: + oAppliance = oVirtualBox.createAppliance(); + except: + return reporter.errorXcpt('IVirtualBox::createAppliance failed'); + + try: + oProgress = vboxwrappers.ProgressWrapper(oAppliance.read(sOva), self.oTstDrv.oVBoxMgr, self.oTstDrv, + 'read "%s"' % (sOva,)); + except: + return reporter.errorXcpt('IAppliance::read("%s") failed' % (sOva,)); + oProgress.wait(); + if oProgress.logResult() is False: + return False; + + try: + oAppliance.interpret(); + except: + return reporter.errorXcpt('IAppliance::interpret() failed on "%s"' % (sOva,)); + + # + try: + oProgress = vboxwrappers.ProgressWrapper(oAppliance.importMachines([]), + self.oTstDrv.oVBoxMgr, self.oTstDrv, 'importMachines "%s"' % (sOva,)); + except: + return reporter.errorXcpt('IAppliance::importMachines failed on "%s"' % (sOva,)); + oProgress.wait(); + if oProgress.logResult() is False: + return False; + + # + # Export the + # + ## @todo do more with this OVA. Like untaring it and loading it as an OVF. Export it and import it again. + + return True; + + def testImportOvaAsOvf(self, sOva): + """ + Unpacks the OVA into a subdirectory in the scratch area and imports it as an OVF. + """ + oVirtualBox = self.oTstDrv.oVBoxMgr.getVirtualBox(); + + sTmpDir = os.path.join(self.oTstDrv.sScratchPath, os.path.split(sOva)[1] + '-ovf'); + sOvf = os.path.join(sTmpDir, os.path.splitext(os.path.split(sOva)[1])[0] + '.ovf'); + + # + # Unpack + # + try: + os.mkdir(sTmpDir, 0o755); + oTarFile = tarfile.open(sOva, 'r:*'); # No 'with' support in 2.6. pylint: disable=consider-using-with + oTarFile.extractall(sTmpDir); + oTarFile.close(); + except: + return reporter.errorXcpt('Unpacking "%s" to "%s" for OVF style importing failed' % (sOvf, sTmpDir,)); + + # + # Import. + # + try: + oAppliance2 = oVirtualBox.createAppliance(); + except: + return reporter.errorXcpt('IVirtualBox::createAppliance failed (#2)'); + + try: + oProgress = vboxwrappers.ProgressWrapper(oAppliance2.read(sOvf), self.oTstDrv.oVBoxMgr, self.oTstDrv, + 'read "%s"' % (sOvf,)); + except: + return reporter.errorXcpt('IAppliance::read("%s") failed' % (sOvf,)); + oProgress.wait(); + if oProgress.logResult() is False: + return False; + + try: + oAppliance2.interpret(); + except: + return reporter.errorXcpt('IAppliance::interpret() failed on "%s"' % (sOvf,)); + + try: + oProgress = vboxwrappers.ProgressWrapper(oAppliance2.importMachines([]), + self.oTstDrv.oVBoxMgr, self.oTstDrv, 'importMachines "%s"' % (sOvf,)); + except: + return reporter.errorXcpt('IAppliance::importMachines failed on "%s"' % (sOvf,)); + oProgress.wait(); + if oProgress.logResult() is False: + return False; + + return True; + + +if __name__ == '__main__': + sys.path.append(os.path.dirname(os.path.abspath(__file__))) + from tests.api.tdApi1 import tdApi1; + sys.exit(tdApi1([SubTstDrvAppliance1]).main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/api/tdCloneMedium1.py b/src/VBox/ValidationKit/tests/api/tdCloneMedium1.py new file mode 100755 index 00000000..3a2f17d8 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdCloneMedium1.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdCloneMedium1.py $ + +""" +VirtualBox Validation Kit - Clone Medium Test #1 +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 155026 $" + + +# Standard Python imports. +import os +from subprocess import call +import sys + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0] +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(g_ksValidationKitDir) + +# Validation Kit imports. +from testdriver import base +from testdriver import reporter +from testdriver import vboxcon +from testdriver import vboxwrappers + + +class SubTstDrvCloneMedium1(base.SubTestDriverBase): + """ + Sub-test driver for Clone Medium Test #1. + """ + + def __init__(self, oTstDrv): + base.SubTestDriverBase.__init__(self, oTstDrv, 'clone-medium', 'Move Medium'); + + def testIt(self): + """ + Execute the sub-testcase. + """ + + return self.testAll() + + # + # Test execution helpers. + # + + def createTestMedium(self, oVM, sPathSuffix, sFmt = 'VDI', cbSize = 1024*1024, data = []): + assert oVM is not None + + oSession = self.oTstDrv.openSession(oVM) + + if oSession is None: + return False + + # + # Create Medium Object + # + + sBaseHdd1Path = os.path.join(self.oTstDrv.sScratchPath, sPathSuffix) + sBaseHdd1Fmt = sFmt + cbBaseHdd1Size = cbSize + + try: + oBaseHdd1 = oSession.createBaseHd(sBaseHdd1Path, sBaseHdd1Fmt, cbBaseHdd1Size) + except: + return reporter.errorXcpt('createBaseHd failed') + + oMediumIOBaseHdd1 = oBaseHdd1.openForIO(True, "") + + if(len(data) > 0): + cbWritten = oMediumIOBaseHdd1.write(0, data) + + if cbWritten != 1: + return reporter.error("Failed writing to test hdd.") + + oMediumIOBaseHdd1.close() + + return oBaseHdd1 + + def cloneMedium(self, oSrcHd, oTgtHd): + """ + Clones medium into target medium. + """ + try: + oProgressCom = oSrcHd.cloneTo(oTgtHd, (vboxcon.MediumVariant_Standard, ), None); + except: + reporter.errorXcpt('failed to clone medium %s to %s' % (oSrcHd.name, oTgtHd.name)); + return False; + oProgress = vboxwrappers.ProgressWrapper(oProgressCom, self.oTstDrv.oVBoxMgr, self.oTstDrv, + 'clone base disk %s to %s' % (oSrcHd.name, oTgtHd.name)); + oProgress.wait(cMsTimeout = 15*60*1000); # 15 min + oProgress.logResult(); + return True; + + def resizeAndCloneMedium(self, oSrcHd, oTgtHd, cbTgtSize): + """ + Clones medium into target medium. + """ + + try: + oProgressCom = oSrcHd.resizeAndCloneTo(oTgtHd, cbTgtSize, (vboxcon.MediumVariant_Standard, ), None); + except: + reporter.errorXcpt('failed to resize and clone medium %s to %s and to size %d' % (oSrcHd.name, oTgtHd.name, cbTgtSize)); + return False; + oProgress = vboxwrappers.ProgressWrapper(oProgressCom, self.oTstDrv.oVBoxMgr, self.oTstDrv, + 'resize and clone base disk %s to %s and to size %d' % (oSrcHd.name, oTgtHd.name, cbTgtSize)); + oProgress.wait(cMsTimeout = 15*60*1000); # 15 min + oProgress.logResult(); + return True; + + def deleteVM(self, oVM): + try: + oVM.unregister(vboxcon.CleanupMode_DetachAllReturnNone); + except: + reporter.logXcpt(); + + try: + oProgressCom = oVM.deleteConfig([]); + except: + reporter.logXcpt(); + else: + oProgress = vboxwrappers.ProgressWrapper(oProgressCom, self.oTstDrv.oVBoxMgr, self.oTstDrv, + 'Delete VM %s' % (oVM.name)); + oProgress.wait(cMsTimeout = 15*60*1000); # 15 min + oProgress.logResult(); + + return None; + + # + # Tests + # + + def testCloneOnly(self): + """ + Tests cloning mediums only. No resize. + """ + + reporter.testStart("testCloneOnly") + + oVM = self.oTstDrv.createTestVM('test-medium-clone-only', 1, None, 4) + + hd1 = self.createTestMedium(oVM, "hd1-cloneonly", data=[0xdeadbeef]) + hd2 = self.createTestMedium(oVM, "hd2-cloneonly") + + if not self.cloneMedium(hd1, hd2): + return False + + oMediumIOhd1 = hd1.openForIO(True, "") + dataHd1 = oMediumIOhd1.read(0, 4) + oMediumIOhd1.close() + + oMediumIOhd2 = hd2.openForIO(True, "") + dataHd2 = oMediumIOhd2.read(0, 4) + oMediumIOhd2.close() + + if dataHd1 != dataHd2: + reporter.testFailure("Data read is unexpected.") + + self.deleteVM(oVM) + + reporter.testDone() + return True + + def testResizeAndClone(self): + """ + Tests resizing and cloning mediums only. + """ + + reporter.testStart("testResizeAndClone") + + oVM = self.oTstDrv.createTestVM('test-medium-clone-only', 1, None, 4) + + hd1 = self.createTestMedium(oVM, "hd1-resizeandclone", data=[0xdeadbeef]) + hd2 = self.createTestMedium(oVM, "hd2-resizeandclone") + + if not (hasattr(hd1, "resizeAndCloneTo") and callable(getattr(hd1, "resizeAndCloneTo"))): + self.deleteVM(oVM) + reporter.testDone() + return True + + if not self.resizeAndCloneMedium(hd1, hd2, 1024*1024*2): + return False + + oMediumIOhd1 = hd1.openForIO(True, "") + dataHd1 = oMediumIOhd1.read(0, 4) + oMediumIOhd1.close() + + oMediumIOhd2 = hd2.openForIO(True, "") + dataHd2 = oMediumIOhd2.read(0, 4) + oMediumIOhd2.close() + + if dataHd1 != dataHd2: + reporter.testFailure("Data read is unexpected.") + + if hd1.logicalSize != hd2.logicalSize and hd2.logicalSize != 1024*1024*2: + reporter.testFailure("Target medium did not resize.") + + self.deleteVM(oVM) + + reporter.testDone() + return True + + def testAll(self): + return (self.testCloneOnly() & self.testResizeAndClone()) + +if __name__ == '__main__': + sys.path.append(os.path.dirname(os.path.abspath(__file__))) + from tdApi1 import tdApi1; # pylint: disable=relative-import + sys.exit(tdApi1([SubTstDrvCloneMedium1]).main(sys.argv)) diff --git a/src/VBox/ValidationKit/tests/api/tdCreateVMWithDefaults1.py b/src/VBox/ValidationKit/tests/api/tdCreateVMWithDefaults1.py new file mode 100755 index 00000000..9890451c --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdCreateVMWithDefaults1.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdCreateVMWithDefaults1.py $ + +""" +VirtualBox Validation Kit - Create VM with IMachine::applyDefaults() Test +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os +import sys + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0] +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(g_ksValidationKitDir) + +# Validation Kit imports. +from testdriver import base +from testdriver import reporter; +from testdriver import vboxcon; + + +class SubTstDrvCreateVMWithDefaults1(base.SubTestDriverBase): + """ + Sub-test driver for VM Move Test #1. + """ + + def __init__(self, oTstDrv): + base.SubTestDriverBase.__init__(self, oTstDrv, 'create-vm-with-defaults', 'Create VMs with defaults'); + + def testIt(self): + """ + Execute the sub-testcase. + """ + reporter.log('ValidationKit folder is "%s"' % (g_ksValidationKitDir,)) + reporter.testStart(self.sTestName); + fRc = self.testCreateVMWithDefaults(); + reporter.testDone(); + return fRc; + + + def createVMWithDefaults(self, sGuestType): + sName = 'testvm_%s' % (sGuestType) + # create VM manually, because the self.createTestVM() makes registration inside + # the method, but IMachine::applyDefault() must be called before machine is registered + try: + if self.oTstDrv.fpApiVer >= 4.2: # Introduces grouping (third parameter, empty for now). + oVM = self.oTstDrv.oVBox.createMachine("", sName, [], + self.oTstDrv.tryFindGuestOsId(sGuestType), + "") + elif self.oTstDrv.fpApiVer >= 4.0: + oVM = self.oTstDrv.oVBox.createMachine("", sName, + self.oTstDrv.tryFindGuestOsId(sGuestType), + "", False) + elif self.oTstDrv.fpApiVer >= 3.2: + oVM = self.oTstDrv.oVBox.createMachine(sName, + self.oTstDrv.tryFindGuestOsId(sGuestType), + "", "", False) + else: + oVM = self.oTstDrv.oVBox.createMachine(sName, + self.oTstDrv.tryFindGuestOsId(sGuestType), + "", "") + try: + oVM.saveSettings() + except: + reporter.logXcpt() + if self.oTstDrv.fpApiVer >= 4.0: + try: + if self.oTstDrv.fpApiVer >= 4.3: + oProgress = oVM.deleteConfig([]) + else: + oProgress = oVM.delete(None); + self.oTstDrv.waitOnProgress(oProgress) + except: + reporter.logXcpt() + else: + try: oVM.deleteSettings() + except: reporter.logXcpt() + raise + except: + reporter.errorXcpt('failed to create vm "%s"' % (sName)) + return None + + if oVM is None: + return False + + # apply settings + fRc = True + try: + if self.oTstDrv.fpApiVer >= 6.1: + oVM.applyDefaults('') + oVM.saveSettings(); + self.oTstDrv.oVBox.registerMachine(oVM) + except: + reporter.logXcpt() + fRc = False + + # Some errors from applyDefaults can be observed only after further settings saving. + # Change and save the size of the VM RAM as simple setting change. + oSession = self.oTstDrv.openSession(oVM) + if oSession is None: + fRc = False + + if fRc: + try: + oSession.memorySize = 4096 + oSession.saveSettings(True) + except: + reporter.logXcpt() + fRc = False + + # delete VM + try: + oVM.unregister(vboxcon.CleanupMode_DetachAllReturnNone) + except: + reporter.logXcpt() + + if self.oTstDrv.fpApiVer >= 4.0: + try: + if self.oTstDrv.fpApiVer >= 4.3: + oProgress = oVM.deleteConfig([]) + else: + oProgress = oVM.delete(None) + self.oTstDrv.waitOnProgress(oProgress) + + except: + reporter.logXcpt() + + else: + try: oVM.deleteSettings() + except: reporter.logXcpt() + + return fRc + + def testCreateVMWithDefaults(self): + """ + Test create VM with defaults. + """ + if not self.oTstDrv.importVBoxApi(): + return reporter.error('importVBoxApi'); + + # Get the guest OS types. + try: + aoGuestTypes = self.oTstDrv.oVBoxMgr.getArray(self.oTstDrv.oVBox, 'guestOSTypes') + if aoGuestTypes is None or not aoGuestTypes: + return reporter.error('No guest OS types'); + except: + return reporter.errorXcpt(); + + # Create VMs with defaults for each of the guest types. + fRc = True + for oGuestType in aoGuestTypes: + try: + sGuestType = oGuestType.id; + except: + fRc = reporter.errorXcpt(); + else: + reporter.testStart(sGuestType); + fRc = self.createVMWithDefaults(sGuestType) and fRc; + reporter.testDone(); + return fRc + +if __name__ == '__main__': + sys.path.append(os.path.dirname(os.path.abspath(__file__))) + from tdApi1 import tdApi1; # pylint: disable=relative-import + sys.exit(tdApi1([SubTstDrvCreateVMWithDefaults1]).main(sys.argv)) + diff --git a/src/VBox/ValidationKit/tests/api/tdMoveMedium1.py b/src/VBox/ValidationKit/tests/api/tdMoveMedium1.py new file mode 100755 index 00000000..4c912617 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdMoveMedium1.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdMoveMedium1.py $ + +""" +VirtualBox Validation Kit - Medium Move Test #1 +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os +import sys + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0] +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(g_ksValidationKitDir) + +# Validation Kit imports. +from testdriver import base +from testdriver import reporter +from testdriver import vboxcon +from testdriver import vboxwrappers + + +class SubTstDrvMoveMedium1(base.SubTestDriverBase): + """ + Sub-test driver for Medium Move Test #1. + """ + + def __init__(self, oTstDrv): + base.SubTestDriverBase.__init__(self, oTstDrv, 'move-medium', 'Move Medium'); + + def testIt(self): + """ + Execute the sub-testcase. + """ + return self.testMediumMove() + + # + # Test execution helpers. + # + + def moveTo(self, sLocation, aoMediumAttachments): + for oAttachment in aoMediumAttachments: + try: + oMedium = oAttachment.medium + reporter.log('Move medium "%s" to "%s"' % (oMedium.name, sLocation,)) + except: + reporter.errorXcpt('failed to get the medium from the IMediumAttachment "%s"' % (oAttachment)) + + if self.oTstDrv.fpApiVer >= 5.3 and self.oTstDrv.uRevision > 124748: + try: + oProgress = vboxwrappers.ProgressWrapper(oMedium.moveTo(sLocation), self.oTstDrv.oVBoxMgr, self.oTstDrv, + 'move "%s"' % (oMedium.name,)); + except: + return reporter.errorXcpt('Medium::moveTo("%s") for medium "%s" failed' % (sLocation, oMedium.name,)); + else: + try: + oProgress = vboxwrappers.ProgressWrapper(oMedium.setLocation(sLocation), self.oTstDrv.oVBoxMgr, self.oTstDrv, + 'move "%s"' % (oMedium.name,)); + except: + return reporter.errorXcpt('Medium::setLocation("%s") for medium "%s" failed' % (sLocation, oMedium.name,)); + + + oProgress.wait() + if oProgress.logResult() is False: + return False + return True + + def checkLocation(self, sLocation, aoMediumAttachments, asFiles): + fRc = True + for oAttachment in aoMediumAttachments: + sFilePath = os.path.join(sLocation, asFiles[oAttachment.port]) + sActualFilePath = oAttachment.medium.location + if os.path.abspath(sFilePath) != os.path.abspath(sActualFilePath): + reporter.log('medium location expected to be "%s" but is "%s"' % (sFilePath, sActualFilePath)) + fRc = False; + if not os.path.exists(sFilePath): + reporter.log('medium file does not exist at "%s"' % (sFilePath,)) + fRc = False; + return fRc + + def testMediumMove(self): + """ + Test medium moving. + """ + reporter.testStart('medium moving') + + try: ## @todo r=bird: Bad 'ing style. + oVM = self.oTstDrv.createTestVM('test-medium-move', 1, None, 4) + assert oVM is not None + + # create hard disk images, one for each file-based backend, using the first applicable extension + fRc = True + oSession = self.oTstDrv.openSession(oVM) + aoDskFmts = self.oTstDrv.oVBoxMgr.getArray(self.oTstDrv.oVBox.systemProperties, 'mediumFormats') + asFiles = [] + for oDskFmt in aoDskFmts: + aoDskFmtCaps = self.oTstDrv.oVBoxMgr.getArray(oDskFmt, 'capabilities') + if vboxcon.MediumFormatCapabilities_File not in aoDskFmtCaps \ + or vboxcon.MediumFormatCapabilities_CreateDynamic not in aoDskFmtCaps: + continue + (asExts, aTypes) = oDskFmt.describeFileExtensions() + for i in range(0, len(asExts)): #pylint: disable=consider-using-enumerate + if aTypes[i] is vboxcon.DeviceType_HardDisk: + sExt = '.' + asExts[i] + break + if sExt is None: + fRc = False + break + sFile = 'Test' + str(len(asFiles)) + sExt + sHddPath = os.path.join(self.oTstDrv.sScratchPath, sFile) + oHd = oSession.createBaseHd(sHddPath, sFmt=oDskFmt.id, cb=1024*1024) + if oHd is None: + fRc = False + break + + # attach HDD, IDE controller exists by default, but we use SATA just in case + sController='SATA Controller' + fRc = fRc and oSession.attachHd(sHddPath, sController, iPort = len(asFiles), + fImmutable=False, fForceResource=False) + if fRc: + asFiles.append(sFile) + + fRc = fRc and oSession.saveSettings() + + #create temporary subdirectory in the current working directory + sOrigLoc = self.oTstDrv.sScratchPath + sNewLoc = os.path.join(sOrigLoc, 'newLocation') + os.mkdir(sNewLoc, 0o775) + + aoMediumAttachments = oVM.getMediumAttachmentsOfController(sController) + #case 1. Only path without file name, with trailing separator + fRc = self.moveTo(sNewLoc + os.sep, aoMediumAttachments) and fRc + fRc = self.checkLocation(sNewLoc, aoMediumAttachments, asFiles) and fRc + + #case 2. Only path without file name, without trailing separator + fRc = self.moveTo(sOrigLoc, aoMediumAttachments) and fRc + fRc = self.checkLocation(sOrigLoc, aoMediumAttachments, asFiles) and fRc + + #case 3. Path with file name + #The case supposes that user has passed a destination path with a file name but hasn't added an extension/suffix + #to this destination file. User supposes that the extension would be added automatically and to be the same as + #for the original file. Difficult case, apparently this case should follow mv(1) logic + #and the file name is processed as folder name (aka mv(1) logic). + #Be discussed. + fRc = self.moveTo(os.path.join(sNewLoc, 'newName'), aoMediumAttachments) and fRc + asNewFiles = ['newName' + os.path.splitext(s)[1] for s in asFiles] + fRc = self.checkLocation(os.path.join(sNewLoc, 'newName'), aoMediumAttachments, asFiles) and fRc + + #after the case the destination path must be corrected + sNewLoc = os.path.join(sNewLoc, 'newName') + + #case 4. Only file name + fRc = self.moveTo('onlyMediumName', aoMediumAttachments) and fRc + asNewFiles = ['onlyMediumName' + os.path.splitext(s)[1] for s in asFiles] + if self.oTstDrv.fpApiVer >= 5.3: + fRc = self.checkLocation(sNewLoc, aoMediumAttachments, asNewFiles) and fRc + else: + fRc = self.checkLocation(sNewLoc, aoMediumAttachments, + [s.replace('.hdd', '.parallels') for s in asNewFiles]) and fRc + + #case 5. Move all files from a snapshot + fRc = fRc and oSession.takeSnapshot('Snapshot1') + if fRc: + aoMediumAttachments = oVM.getMediumAttachmentsOfController(sController) + asSnapFiles = [os.path.basename(o.medium.name) for o in aoMediumAttachments] + fRc = self.moveTo(sOrigLoc, aoMediumAttachments) and fRc + fRc = self.checkLocation(sOrigLoc, aoMediumAttachments, asSnapFiles) and fRc + + fRc = oSession.close() and fRc + except: + reporter.errorXcpt() + + return reporter.testDone()[1] == 0 + + +if __name__ == '__main__': + sys.path.append(os.path.dirname(os.path.abspath(__file__))) + from tdApi1 import tdApi1; # pylint: disable=relative-import + sys.exit(tdApi1([SubTstDrvMoveMedium1]).main(sys.argv)) + diff --git a/src/VBox/ValidationKit/tests/api/tdMoveVm1.py b/src/VBox/ValidationKit/tests/api/tdMoveVm1.py new file mode 100755 index 00000000..db76af6b --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdMoveVm1.py @@ -0,0 +1,768 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# "$Id: tdMoveVm1.py $" + +""" +VirtualBox Validation Kit - VM Move Test #1 +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Standard Python imports. +import os +import sys +import shutil +from collections import defaultdict + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0] +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(g_ksValidationKitDir) + +# Validation Kit imports. +from testdriver import base +from testdriver import reporter +from testdriver import vboxcon +from testdriver import vboxwrappers +from tdMoveMedium1 import SubTstDrvMoveMedium1; # pylint: disable=relative-import + + +# @todo r=aeichner: The whole path handling/checking needs fixing to also work on Windows +# The current quick workaround is to spill os.path.normcase() all over the place when +# constructing paths. I'll leave the real fix to the original author... +class SubTstDrvMoveVm1(base.SubTestDriverBase): + """ + Sub-test driver for VM Move Test #1. + """ + + def __init__(self, oTstDrv): + base.SubTestDriverBase.__init__(self, oTstDrv, 'move-vm', 'Move VM'); + + # Note! Hardcoded indexing in test code further down. + self.asRsrcs = [ + os.path.join('5.3','isos','tdMoveVM1.iso'), + os.path.join('5.3','floppy','tdMoveVM1.img') + ]; + + self.asImagesNames = [] + self.dsKeys = { + 'StandardImage': 'SATA Controller', + 'ISOImage': 'IDE Controller', + 'FloppyImage': 'Floppy Controller', + 'SettingsFile': 'Settings File', + 'LogFile': 'Log File', + 'SavedStateFile': 'Saved State File', + 'SnapshotFile': 'Snapshot File' + }; + + def testIt(self): + """ + Execute the sub-testcase. + """ + reporter.testStart(self.sTestName); + reporter.log('ValidationKit folder is "%s"' % (g_ksValidationKitDir,)) + fRc = self.testVMMove(); + return reporter.testDone(fRc is None)[1] == 0; + + # + # Test execution helpers. + # + + def createTestMachine(self): + """ + Document me here, not with hashes above. + """ + oVM = self.oTstDrv.createTestVM('test-vm-move', 1, None, 4) + if oVM is None: + return None + + # create hard disk images, one for each file-based backend, using the first applicable extension + fRc = True + oSession = self.oTstDrv.openSession(oVM) + aoDskFmts = self.oTstDrv.oVBoxMgr.getArray(self.oTstDrv.oVBox.systemProperties, 'mediumFormats') + + for oDskFmt in aoDskFmts: + aoDskFmtCaps = self.oTstDrv.oVBoxMgr.getArray(oDskFmt, 'capabilities') + if vboxcon.MediumFormatCapabilities_File not in aoDskFmtCaps \ + or vboxcon.MediumFormatCapabilities_CreateDynamic not in aoDskFmtCaps: + continue + (asExts, aTypes) = oDskFmt.describeFileExtensions() + for i in range(0, len(asExts)): # pylint: disable=consider-using-enumerate + if aTypes[i] is vboxcon.DeviceType_HardDisk: + sExt = '.' + asExts[i] + break + if sExt is None: + fRc = False + break + sFile = 'test-vm-move' + str(len(self.asImagesNames)) + sExt + sHddPath = os.path.join(self.oTstDrv.sScratchPath, sFile) + oHd = oSession.createBaseHd(sHddPath, sFmt=oDskFmt.id, cb=1024*1024) + if oHd is None: + fRc = False + break + + # attach HDD, IDE controller exists by default, but we use SATA just in case + sController = self.dsKeys['StandardImage'] + fRc = fRc and oSession.attachHd(sHddPath, sController, iPort = len(self.asImagesNames), + fImmutable=False, fForceResource=False) + if fRc: + self.asImagesNames.append(sFile) + + fRc = fRc and oSession.saveSettings() + fRc = oSession.close() and fRc + + if fRc is False: + oVM = None + + return oVM + + def moveVMToLocation(self, sLocation, oVM): + """ + Document me here, not with hashes above. + """ + fRc = True + try: + + ## @todo r=bird: Too much unncessary crap inside try clause. Only oVM.moveTo needs to be here. + ## Though, you could make an argument for oVM.name too, perhaps. + + # move machine + reporter.log('Moving machine "%s" to the "%s"' % (oVM.name, sLocation)) + sType = 'basic' + oProgress = vboxwrappers.ProgressWrapper(oVM.moveTo(sLocation, sType), self.oTstDrv.oVBoxMgr, self.oTstDrv, + 'moving machine "%s"' % (oVM.name,)) + + except: + return reporter.errorXcpt('Machine::moveTo("%s") for machine "%s" failed' % (sLocation, oVM.name,)) + + oProgress.wait() + if oProgress.logResult() is False: + fRc = False + reporter.log('Progress object returned False') + else: + fRc = True + + return fRc + + def checkLocation(self, oMachine, dsReferenceFiles): + """ + Document me. + + Prerequisites: + 1. All standard images are attached to SATA controller + 2. All ISO images are attached to IDE controller + 3. All floppy images are attached to Floppy controller + 4. The type defaultdict from collection is used here (some sort of multimap data structure) + 5. The dsReferenceFiles parameter here is the structure defaultdict(set): + [ + ('StandardImage': ['somedisk.vdi', 'somedisk.vmdk',...]), + ('ISOImage': ['somedisk_1.iso','somedisk_2.iso',...]), + ('FloppyImage': ['somedisk_1.img','somedisk_2.img',...]), + ('SnapshotFile': ['snapshot file 1','snapshot file 2', ...]), + ('SettingsFile', ['setting file',...]), + ('SavedStateFile': ['state file 1','state file 2',...]), + ('LogFile': ['log file 1','log file 2',...]), + ] + """ + + fRc = True + + for sKey, sValue in self.dsKeys.items(): + aActuals = set() + aReferences = set() + + # Check standard images locations, ISO files locations, floppy images locations, snapshots files locations + if sKey in ('StandardImage', 'ISOImage', 'FloppyImage',): + aReferences = dsReferenceFiles[sKey] + if aReferences: + aoMediumAttachments = oMachine.getMediumAttachmentsOfController(sValue) ##@todo r=bird: API call, try-except! + for oAttachment in aoMediumAttachments: + aActuals.add(os.path.normcase(oAttachment.medium.location)) + + elif sKey == 'SnapshotFile': + aReferences = dsReferenceFiles[sKey] + if aReferences: + aActuals = self.__getSnapshotsFiles(oMachine) + + # Check setting file location + elif sKey == 'SettingsFile': + aReferences = dsReferenceFiles[sKey] + if aReferences: + aActuals.add(os.path.normcase(oMachine.settingsFilePath)) + + # Check saved state files location + elif sKey == 'SavedStateFile': + aReferences = dsReferenceFiles[sKey] + if aReferences: + aActuals = self.__getStatesFiles(oMachine) + + # Check log files location + elif sKey == 'LogFile': + aReferences = dsReferenceFiles[sKey] + if aReferences: + aActuals = self.__getLogFiles(oMachine) + + if aActuals: + reporter.log('Check %s' % (sKey)) + intersection = aReferences.intersection(aActuals) + for eachItem in intersection: + reporter.log('Item location "%s" is correct' % (eachItem)) + + difference = aReferences.difference(aActuals) + for eachItem in difference: + reporter.log('Item location "%s" isn\'t correct' % (eachItem)) + + reporter.log('####### Reference locations: #######') + for eachItem in aActuals: + reporter.log(' "%s"' % (eachItem)) + + if len(intersection) != len(aActuals): + reporter.log('Not all items in the right location. Check it.') + fRc = False + + return fRc + + def checkAPIVersion(self): + return self.oTstDrv.fpApiVer >= 5.3; + + @staticmethod + def __safeListDir(sDir): + """ Wrapper around os.listdir that returns empty array instead of exceptions. """ + try: + return os.listdir(sDir); + except: + return []; + + def __getStatesFiles(self, oMachine, fPrint = False): + asStateFilesList = set() + sFolder = oMachine.snapshotFolder; + for sFile in self.__safeListDir(sFolder): + if sFile.endswith(".sav"): + sFullPath = os.path.normcase(os.path.join(sFolder, sFile)); + asStateFilesList.add(sFullPath) + if fPrint is True: + reporter.log("State file is %s" % (sFullPath)) + return asStateFilesList + + def __getSnapshotsFiles(self, oMachine, fPrint = False): + asSnapshotsFilesList = set() + sFolder = oMachine.snapshotFolder + for sFile in self.__safeListDir(sFolder): + if sFile.endswith(".sav") is False: + sFullPath = os.path.normcase(os.path.join(sFolder, sFile)); + asSnapshotsFilesList.add(sFullPath) + if fPrint is True: + reporter.log("Snapshot file is %s" % (sFullPath)) + return asSnapshotsFilesList + + def __getLogFiles(self, oMachine, fPrint = False): + asLogFilesList = set() + sFolder = oMachine.logFolder + for sFile in self.__safeListDir(sFolder): + if sFile.endswith(".log"): + sFullPath = os.path.normcase(os.path.join(sFolder, sFile)); + asLogFilesList.add(sFullPath) + if fPrint is True: + reporter.log("Log file is %s" % (sFullPath)) + return asLogFilesList + + + def __testScenario_2(self, oSession, oMachine, sNewLoc, sOldLoc): + """ + All disks attached to VM are located inside the VM's folder. + There are no any snapshots and logs. + """ + + sController = self.dsKeys['StandardImage'] + aoMediumAttachments = oMachine.getMediumAttachmentsOfController(sController) + oSubTstDrvMoveMedium1Instance = SubTstDrvMoveMedium1(self.oTstDrv) + oSubTstDrvMoveMedium1Instance.moveTo(sOldLoc, aoMediumAttachments) + + del oSubTstDrvMoveMedium1Instance + + dsReferenceFiles = defaultdict(set) + + for s in self.asImagesNames: + reporter.log('"%s"' % (s,)) + dsReferenceFiles['StandardImage'].add(os.path.normcase(sNewLoc + os.sep + oMachine.name + os.sep + s)) + + sSettingFile = os.path.join(sNewLoc, os.path.join(oMachine.name, oMachine.name + '.vbox')) + dsReferenceFiles['SettingsFile'].add(os.path.normcase(sSettingFile)) + + fRc = self.moveVMToLocation(sNewLoc, oSession.o.machine) + + if fRc is True: + fRc = self.checkLocation(oSession.o.machine, dsReferenceFiles) + if fRc is False: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 2nd scenario: Check locations failed... !!!!!!!!!!!!!!!!!!') + else: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 2nd scenario: Move VM failed... !!!!!!!!!!!!!!!!!!') + + fRes = oSession.saveSettings() + if fRes is False: + reporter.log('2nd scenario: Couldn\'t save machine settings') + + return fRc + + def __testScenario_3(self, oSession, oMachine, sNewLoc): + """ + There are snapshots + """ + + # At moment, it's used only one snapshot due to the difficulty to get + # all attachments of the machine (i.e. not only attached at moment) + cSnap = 1 + + for counter in range(1,cSnap+1): + strSnapshot = 'Snapshot' + str(counter) + fRc = oSession.takeSnapshot(strSnapshot) + if fRc is False: + reporter.testFailure('3rd scenario: Can\'t take snapshot "%s"' % (strSnapshot,)) + + dsReferenceFiles = defaultdict(set) + + sController = self.dsKeys['StandardImage'] + aoMediumAttachments = oMachine.getMediumAttachmentsOfController(sController) + if fRc is True: + for oAttachment in aoMediumAttachments: + sRes = oAttachment.medium.location.rpartition(os.sep) + dsReferenceFiles['SnapshotFile'].add(os.path.normcase(sNewLoc + os.sep + oMachine.name + os.sep + + 'Snapshots' + os.sep + sRes[2])) + + sSettingFile = os.path.join(sNewLoc, os.path.join(oMachine.name, oMachine.name + '.vbox')) + dsReferenceFiles['SettingsFile'].add(os.path.normcase(sSettingFile)) + + fRc = self.moveVMToLocation(sNewLoc, oSession.o.machine) + + if fRc is True: + fRc = self.checkLocation(oSession.o.machine, dsReferenceFiles) + if fRc is False: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 3rd scenario: Check locations failed... !!!!!!!!!!!!!!!!!!') + else: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 3rd scenario: Move VM failed... !!!!!!!!!!!!!!!!!!') + + fRes = oSession.saveSettings() + if fRes is False: + reporter.log('3rd scenario: Couldn\'t save machine settings') + + return fRc + + def __testScenario_4(self, oMachine, sNewLoc): + """ + There are one or more save state files in the snapshots folder + and some files in the logs folder. + Here we run VM, next stop it in the "save" state. + And next move VM + """ + + # Run VM and get new Session object. + oSession = self.oTstDrv.startVm(oMachine); + if not oSession: + return False; + + # Some time interval should be here for not closing VM just after start. + self.oTstDrv.waitForTasks(1000); + + if oMachine.state != self.oTstDrv.oVBoxMgr.constants.MachineState_Running: + reporter.log("Machine '%s' is not Running" % (oMachine.name)) + fRc = False + + # Call Session::saveState(), already closes session unless it failed. + fRc = oSession.saveState() + if fRc is True: + reporter.log("Machine is in saved state") + + fRc = self.oTstDrv.terminateVmBySession(oSession) + + if fRc is True: + # Create a new Session object for moving VM. + oSession = self.oTstDrv.openSession(oMachine) + + # Always clear before each scenario. + dsReferenceFiles = defaultdict(set) + + asLogs = self.__getLogFiles(oMachine) + for sFile in asLogs: + sRes = sFile.rpartition(os.sep) + dsReferenceFiles['LogFile'].add(os.path.normcase(sNewLoc + os.sep + oMachine.name + os.sep + + 'Logs' + os.sep + sRes[2])) + + asStates = self.__getStatesFiles(oMachine) + for sFile in asStates: + sRes = sFile.rpartition(os.sep) + dsReferenceFiles['SavedStateFile'].add(os.path.normcase(sNewLoc + os.sep + oMachine.name + os.sep + + 'Snapshots' + os.sep + sRes[2])) + + fRc = self.moveVMToLocation(sNewLoc, oSession.o.machine) + + if fRc is True: + fRc = self.checkLocation(oSession.o.machine, dsReferenceFiles) + if fRc is False: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 4th scenario: Check locations failed... !!!!!!!!!!!!!!!!!!') + else: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 4th scenario: Move VM failed... !!!!!!!!!!!!!!!!!!') + + # cleaning up: get rid of saved state + fRes = oSession.discardSavedState(True) + if fRes is False: + reporter.log('4th scenario: Failed to discard the saved state of machine') + + fRes = oSession.close() + if fRes is False: + reporter.log('4th scenario: Couldn\'t close machine session') + else: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 4th scenario: Terminate machine by session failed... !!!!!!!!!!!!!!!!!!') + + return fRc + + def __testScenario_5(self, oMachine, sNewLoc, sOldLoc): + """ + There is an ISO image (.iso) attached to the VM. + Prerequisites - there is IDE Controller and there are no any images attached to it. + """ + + fRc = True + sISOImageName = 'tdMoveVM1.iso' + + # Always clear before each scenario. + dsReferenceFiles = defaultdict(set) + + # Create a new Session object. + oSession = self.oTstDrv.openSession(oMachine) + + sISOLoc = self.asRsrcs[0] # '5.3/isos/tdMoveVM1.iso' + reporter.log("sHost is '%s', sResourcePath is '%s'" % (self.oTstDrv.sHost, self.oTstDrv.sResourcePath)) + sISOLoc = self.oTstDrv.getFullResourceName(sISOLoc) + reporter.log("sISOLoc is '%s'" % (sISOLoc,)) + + if not os.path.exists(sISOLoc): + reporter.log('ISO file does not exist at "%s"' % (sISOLoc,)) + fRc = False + + # Copy ISO image from the common resource folder into machine folder. + shutil.copy(sISOLoc, sOldLoc) + + # Attach ISO image to the IDE controller. + if fRc is True: + # Set actual ISO location. + sISOLoc = sOldLoc + os.sep + sISOImageName + reporter.log("sISOLoc is '%s'" % (sISOLoc,)) + if not os.path.exists(sISOLoc): + reporter.log('ISO file does not exist at "%s"' % (sISOLoc,)) + fRc = False + + sController=self.dsKeys['ISOImage'] + aoMediumAttachments = oMachine.getMediumAttachmentsOfController(sController) + iPort = len(aoMediumAttachments) + fRc = oSession.attachDvd(sISOLoc, sController, iPort, iDevice = 0) + dsReferenceFiles['ISOImage'].add(os.path.normcase(os.path.join(os.path.join(sNewLoc, oMachine.name), sISOImageName))) + + if fRc is True: + fRc = self.moveVMToLocation(sNewLoc, oSession.o.machine) + if fRc is True: + fRc = self.checkLocation(oSession.o.machine, dsReferenceFiles) + if fRc is False: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 5th scenario: Check locations failed... !!!!!!!!!!!!!!!!!!') + else: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 5th scenario: Move VM failed... !!!!!!!!!!!!!!!!!!') + else: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 5th scenario: Attach ISO image failed... !!!!!!!!!!!!!!!!!!') + + # Detach ISO image. + fRes = oSession.detachHd(sController, iPort, 0) + if fRes is False: + reporter.log('5th scenario: Couldn\'t detach image from the controller %s ' + 'port %s device %s' % (sController, iPort, 0)) + + fRes = oSession.saveSettings() + if fRes is False: + reporter.log('5th scenario: Couldn\'t save machine settings') + + fRes = oSession.close() + if fRes is False: + reporter.log('5th scenario: Couldn\'t close machine session') + + return fRc + + def __testScenario_6(self, oMachine, sNewLoc, sOldLoc): + """ + There is a floppy image (.img) attached to the VM. + Prerequisites - there is Floppy Controller and there are no any images attached to it. + """ + + fRc = True + + # Always clear before each scenario. + dsReferenceFiles = defaultdict(set) + + # Create a new Session object. + oSession = self.oTstDrv.openSession(oMachine) + + sFloppyLoc = self.asRsrcs[1] # '5.3/floppy/tdMoveVM1.img' + sFloppyLoc = self.oTstDrv.getFullResourceName(sFloppyLoc) + + if not os.path.exists(sFloppyLoc): + reporter.log('Floppy disk does not exist at "%s"' % (sFloppyLoc,)) + fRc = False + + # Copy floppy image from the common resource folder into machine folder. + shutil.copy(sFloppyLoc, sOldLoc) + + # Attach floppy image. + if fRc is True: + # Set actual floppy location. + sFloppyImageName = 'tdMoveVM1.img' + sFloppyLoc = sOldLoc + os.sep + sFloppyImageName + sController=self.dsKeys['FloppyImage'] + fRc = fRc and oSession.attachFloppy(sFloppyLoc, sController, 0, 0) + dsReferenceFiles['FloppyImage'].add(os.path.normcase(os.path.join(os.path.join(sNewLoc, oMachine.name), + sFloppyImageName))) + + if fRc is True: + fRc = self.moveVMToLocation(sNewLoc, oSession.o.machine) + if fRc is True: + fRc = self.checkLocation(oSession.o.machine, dsReferenceFiles) + if fRc is False: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 6th scenario: Check locations failed... !!!!!!!!!!!!!!!!!!') + else: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 6th scenario: Move VM failed... !!!!!!!!!!!!!!!!!!') + else: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 6th scenario: Attach floppy image failed... !!!!!!!!!!!!!!!!!!') + + # Detach floppy image. + fRes = oSession.detachHd(sController, 0, 0) + if fRes is False: + reporter.log('6th scenario: Couldn\'t detach image from the controller %s port %s device %s' % (sController, 0, 0)) + + fRes = oSession.saveSettings() + if fRes is False: + reporter.testFailure('6th scenario: Couldn\'t save machine settings') + + fRes = oSession.close() + if fRes is False: + reporter.log('6th scenario: Couldn\'t close machine session') + return fRc + + + def testVMMove(self): + """ + Test machine moving. + """ + if not self.oTstDrv.importVBoxApi(): + return False + + fSupported = self.checkAPIVersion() + reporter.log('ValidationKit folder is "%s"' % (g_ksValidationKitDir,)) + + if fSupported is False: + reporter.log('API version %s is too old. Just skip this test.' % (self.oTstDrv.fpApiVer)) + return None; + reporter.log('API version is "%s".' % (self.oTstDrv.fpApiVer)) + + # Scenarios + # 1. All disks attached to VM are located outside the VM's folder. + # There are no any snapshots and logs. + # In this case only VM setting file should be moved (.vbox file) + # + # 2. All disks attached to VM are located inside the VM's folder. + # There are no any snapshots and logs. + # + # 3. There are snapshots. + # + # 4. There are one or more save state files in the snapshots folder + # and some files in the logs folder. + # + # 5. There is an ISO image (.iso) attached to the VM. + # + # 6. There is a floppy image (.img) attached to the VM. + # + # 7. There are shareable disk and immutable disk attached to the VM. + + try: ## @todo r=bird: Would be nice to use sub-tests here for each scenario, however + ## this try/catch as well as lots of return points makes that very hard. + ## Big try/catch stuff like this should be avoided. + # Create test machine. + oMachine = self.createTestMachine() + if oMachine is None: + reporter.error('Failed to create test machine') + + # Create temporary subdirectory in the current working directory. + sOrigLoc = self.oTstDrv.sScratchPath + sBaseLoc = os.path.join(sOrigLoc, 'moveFolder') + os.mkdir(sBaseLoc, 0o775) + + # lock machine + # get session machine + oSession = self.oTstDrv.openSession(oMachine) + fRc = True + + sNewLoc = sBaseLoc + os.sep + + dsReferenceFiles = defaultdict(set) + + # + # 1. case: + # + # All disks attached to VM are located outside the VM's folder. + # There are no any snapshots and logs. + # In this case only VM setting file should be moved (.vbox file) + # + reporter.log("Scenario #1:"); + for s in self.asImagesNames: + reporter.log('"%s"' % (s,)) + dsReferenceFiles['StandardImage'].add(os.path.normcase(os.path.join(sOrigLoc, s))) + + sSettingFile = os.path.normcase(os.path.join(sNewLoc, os.path.join(oMachine.name, oMachine.name + '.vbox'))) + dsReferenceFiles['SettingsFile'].add(sSettingFile) + + fRc = self.moveVMToLocation(sNewLoc, oSession.o.machine) + + if fRc is True: + fRc = self.checkLocation(oSession.o.machine, dsReferenceFiles) + if fRc is False: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 1st scenario: Check locations failed... !!!!!!!!!!!!!!!!!!') + return False; + else: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 1st scenario: Move VM failed... !!!!!!!!!!!!!!!!!!') + return False; + + fRc = oSession.saveSettings() + if fRc is False: + reporter.testFailure('1st scenario: Couldn\'t save machine settings') + + # + # 2. case: + # + # All disks attached to VM are located inside the VM's folder. + # There are no any snapshots and logs. + # + reporter.log("Scenario #2:"); + sOldLoc = sNewLoc + oMachine.name + os.sep + sNewLoc = os.path.join(sOrigLoc, 'moveFolder_2nd_scenario') + os.mkdir(sNewLoc, 0o775) + + fRc = self.__testScenario_2(oSession, oMachine, sNewLoc, sOldLoc) + if fRc is False: + return False; + + # + # 3. case: + # + # There are snapshots. + # + reporter.log("Scenario #3:"); + sOldLoc = sNewLoc + oMachine.name + os.sep + sNewLoc = os.path.join(sOrigLoc, 'moveFolder_3rd_scenario') + os.mkdir(sNewLoc, 0o775) + + fRc = self.__testScenario_3(oSession, oMachine, sNewLoc) + if fRc is False: + return False; + + # + # 4. case: + # + # There are one or more save state files in the snapshots folder + # and some files in the logs folder. + # Here we run VM, next stop it in the "save" state. + # And next move VM + # + reporter.log("Scenario #4:"); + sOldLoc = sNewLoc + oMachine.name + os.sep + sNewLoc = os.path.join(sOrigLoc, 'moveFolder_4th_scenario') + os.mkdir(sNewLoc, 0o775) + + # Close Session object because after starting VM we get new instance of session + fRc = oSession.close() and fRc + if fRc is False: + reporter.log('Couldn\'t close machine session') + + del oSession + + fRc = self.__testScenario_4(oMachine, sNewLoc) + if fRc is False: + return False; + + # + # 5. case: + # + # There is an ISO image (.iso) attached to the VM. + # Prerequisites - there is IDE Controller and there are no any images attached to it. + # + reporter.log("Scenario #5:"); + sOldLoc = sNewLoc + os.sep + oMachine.name + sNewLoc = os.path.join(sOrigLoc, 'moveFolder_5th_scenario') + os.mkdir(sNewLoc, 0o775) + fRc = self.__testScenario_5(oMachine, sNewLoc, sOldLoc) + if fRc is False: + return False; + + # + # 6. case: + # + # There is a floppy image (.img) attached to the VM. + # Prerequisites - there is Floppy Controller and there are no any images attached to it. + # + reporter.log("Scenario #6:"); + sOldLoc = sNewLoc + os.sep + oMachine.name + sNewLoc = os.path.join(sOrigLoc, 'moveFolder_6th_scenario') + os.mkdir(sNewLoc, 0o775) + fRc = self.__testScenario_6(oMachine, sNewLoc, sOldLoc) + if fRc is False: + return False; + +# # +# # 7. case: +# # +# # There are shareable disk and immutable disk attached to the VM. +# # +# reporter.log("Scenario #7:"); +# fRc = fRc and oSession.saveSettings() +# if fRc is False: +# reporter.log('Couldn\'t save machine settings') +# + + assert fRc is True + except: + reporter.errorXcpt() + + return fRc; + + +if __name__ == '__main__': + sys.path.append(os.path.dirname(os.path.abspath(__file__))) + from tdApi1 import tdApi1; # pylint: disable=relative-import + sys.exit(tdApi1([SubTstDrvMoveVm1]).main(sys.argv)) + diff --git a/src/VBox/ValidationKit/tests/api/tdPython1.py b/src/VBox/ValidationKit/tests/api/tdPython1.py new file mode 100755 index 00000000..6b0d18ef --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdPython1.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdPython1.py $ + +""" +VirtualBox Validation Kit - Python Bindings Test #1 +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard Python imports. +import os +import sys +import time +import threading + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testdriver import base; +from testdriver import reporter; + + +class SubTstDrvPython1(base.SubTestDriverBase): + """ + Sub-test driver for Python Bindings Test #1. + """ + + def __init__(self, oTstDrv): + base.SubTestDriverBase.__init__(self, oTstDrv, 'python-binding', 'Python bindings'); + + def testIt(self): + """ + Execute the sub-testcase. + """ + return self.testEventQueueWaiting() \ + and self.testEventQueueInterrupt(); + + # + # Test execution helpers. + # + + def testEventQueueWaitingThreadProc(self): + """ Thread procedure for checking that waitForEvents fails when not called by the main thread. """ + try: + rc2 = self.oTstDrv.oVBoxMgr.waitForEvents(0); + except: + return True; + reporter.error('waitForEvents() returned "%s" when called on a worker thread, expected exception.' % (rc2,)); + return False; + + def testEventQueueWaiting(self): + """ + Test event queue waiting. + """ + reporter.testStart('waitForEvents'); + + # Check return values and such. + for cMsTimeout in (0, 1, 2, 3, 256, 1000, 0): + iLoop = 0; + while True: + try: + rc = self.oTstDrv.oVBoxMgr.waitForEvents(cMsTimeout); + except: + reporter.errorXcpt(); + break; + if not isinstance(rc, int): + reporter.error('waitForEvents returns non-integer type'); + break; + if rc == 1: + break; + if rc != 0: + reporter.error('waitForEvents returns "%s", expected 0 or 1' % (rc,)); + break; + iLoop += 1; + if iLoop > 10240: + reporter.error('waitForEvents returns 0 (success) %u times. ' + 'Expected 1 (timeout/interrupt) after a call or two.' + % (iLoop,)); + break; + if reporter.testErrorCount() != 0: + break; + + # Check that we get an exception when trying to call the method from + # a different thread. + reporter.log('If running a debug build, you will see an ignored assertion now. Please ignore it.') + sVBoxAssertSaved = os.environ.get('VBOX_ASSERT', 'breakpoint'); + os.environ['VBOX_ASSERT'] = 'ignore'; + oThread = threading.Thread(target=self.testEventQueueWaitingThreadProc); + oThread.start(); + oThread.join(); + os.environ['VBOX_ASSERT'] = sVBoxAssertSaved; + + return reporter.testDone()[1] == 0; + + def interruptWaitEventsThreadProc(self): + """ Thread procedure that's used for waking up the main thread. """ + time.sleep(2); + try: + rc2 = self.oTstDrv.oVBoxMgr.interruptWaitEvents(); + except: + reporter.errorXcpt(); + else: + if rc2 is True: + return True; + reporter.error('interruptWaitEvents returned "%s" when called from other thread, expected True' % (rc2,)); + return False; + + def testEventQueueInterrupt(self): + """ + Test interrupting an event queue wait. + """ + reporter.testStart('interruptWait'); + + # interrupt ourselves first and check the return value. + for i in range(0, 10): + try: + rc = self.oTstDrv.oVBoxMgr.interruptWaitEvents(); + except: + reporter.errorXcpt(); + break; + if rc is not True: + reporter.error('interruptWaitEvents returned "%s" expected True' % (rc,)); + break + + if reporter.testErrorCount() == 0: + # + # Interrupt a waitForEvents call. + # + # This test ASSUMES that no other events are posted to the thread's + # event queue once we've drained it. Also ASSUMES the box is + # relatively fast and not too busy because we're timing sensitive. + # + for i in range(0, 4): + # Try quiesce the event queue. + for _ in range(1, 100): + self.oTstDrv.oVBoxMgr.waitForEvents(0); + + # Create a thread that will interrupt us in 2 seconds. + try: + oThread = threading.Thread(target=self.interruptWaitEventsThreadProc); + oThread.setDaemon(False); # pylint: disable=deprecated-method + except: + reporter.errorXcpt(); + break; + + cMsTimeout = 20000; + if i == 2: + cMsTimeout = -1; + elif i == 3: + cMsTimeout = -999999; + + # Do the wait. + oThread.start(); + msNow = base.timestampMilli(); + try: + rc = self.oTstDrv.oVBoxMgr.waitForEvents(cMsTimeout); + except: + reporter.errorXcpt(); + else: + msElapsed = base.timestampMilli() - msNow; + + # Check the return code and elapsed time. + if not isinstance(rc, int): + reporter.error('waitForEvents returns non-integer type after %u ms, expected 1' % (msElapsed,)); + elif rc != 1: + reporter.error('waitForEvents returned "%s" after %u ms, expected 1' % (rc, msElapsed)); + if msElapsed > 15000: + reporter.error('waitForEvents after %u ms, expected just above 2-3 seconds' % (msElapsed,)); + elif msElapsed < 100: + reporter.error('waitForEvents after %u ms, expected more than 100 ms.' % (msElapsed,)); + + oThread.join(); + oThread = None; + if reporter.testErrorCount() != 0: + break; + reporter.log('Iteration %u was successful...' % (i + 1,)); + return reporter.testDone()[1] == 0; + + +if __name__ == '__main__': + from tests.api.tdApi1 import tdApi1; + sys.exit(tdApi1([SubTstDrvPython1]).main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/api/tdTreeDepth1.py b/src/VBox/ValidationKit/tests/api/tdTreeDepth1.py new file mode 100755 index 00000000..85995f35 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdTreeDepth1.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdTreeDepth1.py $ + +""" +VirtualBox Validation Kit - Medium and Snapshot Tree Depth Test #1 +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os +import sys +import random + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0] +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(g_ksValidationKitDir) + +# Validation Kit imports. +from testdriver import base +from testdriver import reporter +from testdriver import vboxcon + + +class SubTstDrvTreeDepth1(base.SubTestDriverBase): + """ + Sub-test driver for Medium and Snapshot Tree Depth Test #1. + """ + + def __init__(self, oTstDrv): + base.SubTestDriverBase.__init__(self, oTstDrv, 'tree-depth', 'Media and Snapshot tree depths'); + + def testIt(self): + """ + Execute the sub-testcase. + """ + return self.testMediumTreeDepth() \ + and self.testSnapshotTreeDepth() + + # + # Test execution helpers. + # + + def testMediumTreeDepth(self): + """ + Test medium tree depth. + """ + reporter.testStart('mediumTreeDepth') + + try: + oVBox = self.oTstDrv.oVBoxMgr.getVirtualBox() + oVM = self.oTstDrv.createTestVM('test-medium', 1, None, 4) + assert oVM is not None + + # create chain with up to 64 disk images (medium tree depth limit) + fRc = True + oSession = self.oTstDrv.openSession(oVM) + cImages = random.randrange(1, 64); + reporter.log('Creating chain with %d disk images' % (cImages)) + for i in range(1, cImages + 1): + sHddPath = os.path.join(self.oTstDrv.sScratchPath, 'Test' + str(i) + '.vdi') + if i == 1: + oHd = oSession.createBaseHd(sHddPath, cb=1024*1024) + else: + oHd = oSession.createDiffHd(oHd, sHddPath) + if oHd is None: + fRc = False + break + + # modify the VM config, attach HDD + fRc = fRc and oSession.attachHd(sHddPath, sController='SATA Controller', fImmutable=False, fForceResource=False) + fRc = fRc and oSession.saveSettings() + fRc = oSession.close() and fRc + ## @todo r=klaus: count known hard disk images, should be cImages + + # unregister, making sure the images are closed + sSettingsFile = oVM.settingsFilePath + fDetachAll = random.choice([False, True]) + if fDetachAll: + reporter.log('unregistering VM, DetachAll style') + else: + reporter.log('unregistering VM, UnregisterOnly style') + self.oTstDrv.forgetTestMachine(oVM) + if fDetachAll: + aoHDs = oVM.unregister(vboxcon.CleanupMode_DetachAllReturnHardDisksOnly) + for oHD in aoHDs: + oHD.close() + aoHDs = None + else: + oVM.unregister(vboxcon.CleanupMode_UnregisterOnly) + oVM = None + + # If there is no base image (expected) then there are no leftover + # child images either. Can be changed later once the todos above + # and below are resolved. + cBaseImages = len(self.oTstDrv.oVBoxMgr.getArray(oVBox, 'hardDisks')) + reporter.log('API reports %i base images' % (cBaseImages)) + fRc = fRc and cBaseImages == 0 + if cBaseImages != 0: + reporter.error('Got %d initial base images, expected %d' % (cBaseImages, 0)); + + # re-register to test loading of settings + reporter.log('opening VM %s, testing config reading' % (sSettingsFile)) + if self.oTstDrv.fpApiVer >= 7.0: + # Needs a password parameter since 7.0. + oVM = oVBox.openMachine(sSettingsFile, "") + else: + oVM = oVBox.openMachine(sSettingsFile) + ## @todo r=klaus: count known hard disk images, should be cImages + + reporter.log('unregistering VM') + oVM.unregister(vboxcon.CleanupMode_UnregisterOnly) + oVM = None + + cBaseImages = len(self.oTstDrv.oVBoxMgr.getArray(oVBox, 'hardDisks')) + reporter.log('API reports %i base images' % (cBaseImages)) + fRc = fRc and cBaseImages == 0 + if cBaseImages != 0: + reporter.error('Got %d base images after unregistering, expected %d' % (cBaseImages, 0)); + + except: + reporter.errorXcpt() + + return reporter.testDone()[1] == 0 + + def testSnapshotTreeDepth(self): + """ + Test snapshot tree depth. + """ + reporter.testStart('snapshotTreeDepth') + + try: + oVBox = self.oTstDrv.oVBoxMgr.getVirtualBox() + oVM = self.oTstDrv.createTestVM('test-snap', 1, None, 4) + assert oVM is not None + + # modify the VM config, create and attach empty HDD + oSession = self.oTstDrv.openSession(oVM) + sHddPath = os.path.join(self.oTstDrv.sScratchPath, 'TestSnapEmpty.vdi') + fRc = True + fRc = fRc and oSession.createAndAttachHd(sHddPath, cb=1024*1024, sController='SATA Controller', fImmutable=False) + fRc = fRc and oSession.saveSettings() + + # take up to 200 snapshots (255 is the snapshot tree depth limit) + cSnapshots = random.randrange(1, 200); ## @todo r=andy BUGBUG When specifying 254 here, it fails with object 251. + reporter.log('Taking %d snapshots' % (cSnapshots)) + for i in range(1, cSnapshots + 1): + fRc = fRc and oSession.takeSnapshot('Snapshot ' + str(i)) + fRc = oSession.close() and fRc + oSession = None + reporter.log('API reports %i snapshots' % (oVM.snapshotCount)) + fRc = fRc and oVM.snapshotCount == cSnapshots + if oVM.snapshotCount != cSnapshots: + reporter.error('Got %d initial snapshots, expected %d' % (oVM.snapshotCount, cSnapshots)); + + # unregister, making sure the images are closed + sSettingsFile = oVM.settingsFilePath + fDetachAll = random.choice([False, True]) + if fDetachAll: + reporter.log('unregistering VM, DetachAll style') + else: + reporter.log('unregistering VM, UnregisterOnly style') + self.oTstDrv.forgetTestMachine(oVM) + if fDetachAll: + aoHDs = oVM.unregister(vboxcon.CleanupMode_DetachAllReturnHardDisksOnly) + for oHD in aoHDs: + oHD.close() + aoHDs = None + else: + oVM.unregister(vboxcon.CleanupMode_UnregisterOnly) + oVM = None + + # If there is no base image (expected) then there are no leftover + # child images either. Can be changed later once the todos above + # and below are resolved. + cBaseImages = len(self.oTstDrv.oVBoxMgr.getArray(oVBox, 'hardDisks')) + reporter.log('API reports %i base images' % (cBaseImages)) + fRc = fRc and cBaseImages == 0 + if cBaseImages != 0: + reporter.error('Got %d initial base images, expected %d' % (cBaseImages, 0)); + + # re-register to test loading of settings + reporter.log('opening VM %s, testing config reading' % (sSettingsFile)) + if self.oTstDrv.fpApiVer >= 7.0: + # Needs a password parameter since 7.0. + oVM = oVBox.openMachine(sSettingsFile, "") + else: + oVM = oVBox.openMachine(sSettingsFile) + reporter.log('API reports %i snapshots' % (oVM.snapshotCount)) + fRc = fRc and oVM.snapshotCount == cSnapshots + if oVM.snapshotCount != cSnapshots: + reporter.error('Got %d snapshots after re-registering, expected %d' % (oVM.snapshotCount, cSnapshots)); + + reporter.log('unregistering VM') + oVM.unregister(vboxcon.CleanupMode_UnregisterOnly) + oVM = None + + cBaseImages = len(self.oTstDrv.oVBoxMgr.getArray(oVBox, 'hardDisks')) + reporter.log('API reports %i base images' % (cBaseImages)) + fRc = fRc and cBaseImages == 0 + if cBaseImages != 0: + reporter.error('Got %d base images after unregistering, expected %d' % (cBaseImages, 0)); + except: + reporter.errorXcpt() + + return reporter.testDone()[1] == 0 + + +if __name__ == '__main__': + sys.path.append(os.path.dirname(os.path.abspath(__file__))) + from tdApi1 import tdApi1; # pylint: disable=relative-import + sys.exit(tdApi1([SubTstDrvTreeDepth1]).main(sys.argv)) diff --git a/src/VBox/ValidationKit/tests/audio/Makefile.kmk b/src/VBox/ValidationKit/tests/audio/Makefile.kmk new file mode 100644 index 00000000..38d56ffa --- /dev/null +++ b/src/VBox/ValidationKit/tests/audio/Makefile.kmk @@ -0,0 +1,50 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Audio tests. +# + +# +# Copyright (C) 2021-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +INSTALLS += ValidationKitTestsAudio +ValidationKitTestsAudio_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsAudio_INST = $(INST_VALIDATIONKIT)tests/audio/ +ValidationKitTestsAudio_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdAudioTest.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsAudio_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/ValidationKit/tests/audio/tdAudioTest.py b/src/VBox/ValidationKit/tests/audio/tdAudioTest.py new file mode 100755 index 00000000..e12dbfba --- /dev/null +++ b/src/VBox/ValidationKit/tests/audio/tdAudioTest.py @@ -0,0 +1,823 @@ +# -*- coding: utf-8 -*- +# $Id: tdAudioTest.py $ + +""" +AudioTest test driver which invokes the VKAT (Validation Kit Audio Test) +binary to perform the actual audio tests. + +The generated test set archive on the guest will be downloaded by TXS +to the host for later audio comparison / verification. +""" + +__copyright__ = \ +""" +Copyright (C) 2021-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Standard Python imports. +from datetime import datetime +import os +import sys +import subprocess +import time +import threading + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testdriver import reporter +from testdriver import base +from testdriver import vbox +from testdriver import vboxcon; +from testdriver import vboxtestvms +from common import utils; + +# pylint: disable=unnecessary-semicolon + +class tdAudioTest(vbox.TestDriver): + """ + Runs various audio tests. + """ + def __init__(self): + vbox.TestDriver.__init__(self); + self.oTestVmSet = self.oTestVmManager.getSmokeVmSet('nat'); + self.asGstVkatPaths = [ + # Debugging stuff (SCP'd over to the guest). + '/tmp/vkat', + '/tmp/VBoxAudioTest', + 'C:\\Temp\\vkat', + 'C:\\Temp\\VBoxAudioTest', + # Validation Kit .ISO. + '${CDROM}/vboxvalidationkit/${OS/ARCH}/vkat${EXESUFF}', + '${CDROM}/${OS/ARCH}/vkat${EXESUFF}', + # Test VMs. + '/opt/apps/vkat', + '/opt/apps/VBoxAudioTest', + '/apps/vkat', + '/apps/VBoxAudioTest', + 'C:\\Apps\\vkat${EXESUFF}', + 'C:\\Apps\\VBoxAudioTest${EXESUFF}', + ## @todo VBoxAudioTest on Guest Additions? + ]; + self.asTestsDef = [ + 'guest_tone_playback', 'guest_tone_recording' + ]; + self.asTests = self.asTestsDef; + + # Optional arguments passing to VKAT when doing the actual audio tests. + self.asVkatTestArgs = []; + # Optional arguments passing to VKAT when verifying audio test sets. + self.asVkatVerifyArgs = []; + + # Exit code of last host process execution, shared between exeuction thread and main thread. + # This ASSUMES that we only have one thread running at a time. Rather hacky, but does the job for now. + self.iThreadHstProcRc = 0; + + # Enable audio debug mode. + # + # This is needed in order to load and use the Validation Kit audio driver, + # which in turn is being used in conjunction with the guest side to record + # output (guest is playing back) and injecting input (guest is recording). + self.asOptExtraData = [ + 'VBoxInternal2/Audio/Debug/Enabled:true', + ]; + + # Name of the running VM to use for running the test driver. Optional, and None if not being used. + self.sRunningVmName = None; + + # Audio controller type to use. + # If set to None, the OS' recommended controller type will be used (defined by Main). + self.sAudioControllerType = None; + + def showUsage(self): + """ + Shows the audio test driver-specific command line options. + """ + fRc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdAudioTest Options:'); + reporter.log(' --runningvmname <vmname>'); + reporter.log(' --audio-tests <s1[:s2[:]]>'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestsDef))); + reporter.log(' --audio-controller-type <HDA|AC97|SB16>'); + reporter.log(' Default: recommended controller'); + reporter.log(' --audio-test-count <number>'); + reporter.log(' Default: 0 (means random)'); + reporter.log(' --audio-test-tone-duration <ms>'); + reporter.log(' Default: 0 (means random)'); + reporter.log(' --audio-verify-max-diff-count <number>'); + reporter.log(' Default: 0 (strict)'); + reporter.log(' --audio-verify-max-diff-percent <0-100>'); + reporter.log(' Default: 0 (strict)'); + reporter.log(' --audio-verify-max-size-percent <0-100>'); + reporter.log(' Default: 0 (strict)'); + return fRc; + + def parseOption(self, asArgs, iArg): + """ + Parses the audio test driver-specific command line options. + """ + if asArgs[iArg] == '--runningvmname': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--runningvmname" needs VM name'); + + self.sRunningVmName = asArgs[iArg]; + elif asArgs[iArg] == '--audio-tests': + iArg += 1; + if asArgs[iArg] == 'all': # Nice for debugging scripts. + self.asTests = self.asTestsDef; + else: + self.asTests = asArgs[iArg].split(':'); + for s in self.asTests: + if s not in self.asTestsDef: + raise base.InvalidOption('The "--audio-tests" value "%s" is not valid; valid values are: %s' + % (s, ' '.join(self.asTestsDef))); + elif asArgs[iArg] == '--audio-controller-type': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('Option "%s" needs a value' % (asArgs[iArg - 1])); + if asArgs[iArg] == 'HDA' \ + or asArgs[iArg] == 'AC97' \ + or asArgs[iArg] == 'SB16': + self.sAudioControllerType = asArgs[iArg]; + else: + raise base.InvalidOption('The "--audio-controller-type" value "%s" is not valid' % (asArgs[iArg])); + elif asArgs[iArg] == '--audio-test-count' \ + or asArgs[iArg] == '--audio-test-tone-duration': + # Strip the "--audio-test-" prefix and keep the options as defined in VKAT, + # e.g. "--audio-test-count" -> "--count". That way we don't + # need to do any special argument translation and whatnot. + self.asVkatTestArgs.extend(['--' + asArgs[iArg][len('--audio-test-'):]]); + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('Option "%s" needs a value' % (asArgs[iArg - 1])); + self.asVkatTestArgs.extend([asArgs[iArg]]); + elif asArgs[iArg] == '--audio-verify-max-diff-count' \ + or asArgs[iArg] == '--audio-verify-max-diff-percent' \ + or asArgs[iArg] == '--audio-verify-max-size-percent': + # Strip the "--audio-verify-" prefix and keep the options as defined in VKAT, + # e.g. "--audio-verify-max-diff-count" -> "--max-diff-count". That way we don't + # need to do any special argument translation and whatnot. + self.asVkatVerifyArgs.extend(['--' + asArgs[iArg][len('--audio-verify-'):]]); + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('Option "%s" needs a value' % (asArgs[iArg - 1])); + self.asVkatVerifyArgs.extend([asArgs[iArg]]); + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def actionVerify(self): + """ + Verifies the test driver before running. + """ + if self.sVBoxValidationKitIso is None or not os.path.isfile(self.sVBoxValidationKitIso): + reporter.error('Cannot find the VBoxValidationKit.iso! (%s)' + 'Please unzip a Validation Kit build in the current directory or in some parent one.' + % (self.sVBoxValidationKitIso,) ); + return False; + return vbox.TestDriver.actionVerify(self); + + def actionConfig(self): + """ + Configures the test driver before running. + """ + if not self.importVBoxApi(): # So we can use the constant below. + return False; + + # Make sure that the Validation Kit .ISO is mounted + # to find the VKAT (Validation Kit Audio Test) binary on it. + assert self.sVBoxValidationKitIso is not None; + return self.oTestVmSet.actionConfig(self, sDvdImage = self.sVBoxValidationKitIso); + + def actionExecute(self): + """ + Executes the test driver. + """ + + # Disable maximum logging line restrictions per group. + # This comes in handy when running this test driver in a (very) verbose mode, e.g. for debugging. + os.environ['VBOX_LOG_MAX_PER_GROUP'] = '0'; + os.environ['VBOX_RELEASE_LOG_MAX_PER_GROUP'] = '0'; + os.environ['VKAT_RELEASE_LOG_MAX_PER_GROUP'] = '0'; + + if self.sRunningVmName is None: + return self.oTestVmSet.actionExecute(self, self.testOneVmConfig); + return self.actionExecuteOnRunnigVM(); + + def actionExecuteOnRunnigVM(self): + """ + Executes the tests in an already configured + running VM. + """ + if not self.importVBoxApi(): + return False; + + fRc = True; + + oVM = None; + oVirtualBox = None; + + oVirtualBox = self.oVBoxMgr.getVirtualBox(); + try: + oVM = oVirtualBox.findMachine(self.sRunningVmName); + if oVM.state != self.oVBoxMgr.constants.MachineState_Running: + reporter.error("Machine '%s' is not in Running state (state is %d)" % (self.sRunningVmName, oVM.state)); + fRc = False; + except: + reporter.errorXcpt("Machine '%s' not found" % (self.sRunningVmName)); + fRc = False; + + if fRc: + oSession = self.openSession(oVM); + if oSession: + # Tweak this to your likings. + oTestVm = vboxtestvms.TestVm('runningvm', sKind = 'WindowsXP'); #sKind = 'WindowsXP' # sKind = 'Ubuntu_64' + (fRc, oTxsSession) = self.txsDoConnectViaTcp(oSession, 30 * 1000); + if fRc: + self.doTest(oTestVm, oSession, oTxsSession); + else: + reporter.error("Unable to open session for machine '%s'" % (self.sRunningVmName)); + fRc = False; + + if oVM: + del oVM; + if oVirtualBox: + del oVirtualBox; + return fRc; + + def getGstVkatLogFilePath(self, oTestVm): + """ + Returns the log file path of VKAT running on the guest (daemonized). + """ + return oTestVm.pathJoin(self.getGuestTempDir(oTestVm), 'vkat-guest.log'); + + def locateGstBinary(self, oSession, oTxsSession, asPaths): + """ + Locates a guest binary on the guest by checking the paths in \a asPaths. + """ + for sCurPath in asPaths: + reporter.log2('Checking for \"%s\" ...' % (sCurPath)); + if self.txsIsFile(oSession, oTxsSession, sCurPath, fIgnoreErrors = True): + return (True, sCurPath); + reporter.error('Unable to find guest binary in any of these places:\n%s' % ('\n'.join(asPaths),)); + return (False, ""); + + def executeHstLoop(self, sWhat, asArgs, asEnv = None, fAsAdmin = False): + """ + Inner loop which handles the execution of a host binary. + + Might be called synchronously in main thread or via the thread exeuction helper (asynchronous). + """ + fRc = False; + + asEnvTmp = os.environ.copy(); + if asEnv: + for sEnv in asEnv: + sKey, sValue = sEnv.split('='); + reporter.log2('Setting env var \"%s\" -> \"%s\"' % (sKey, sValue)); + os.environ[sKey] = sValue; # Also apply it to the current environment. + asEnvTmp[sKey] = sValue; + + try: + # Spawn process. + if fAsAdmin \ + and utils.getHostOs() != 'win': + oProcess = utils.sudoProcessStart(asArgs, env = asEnvTmp, stdout=subprocess.PIPE, stderr=subprocess.STDOUT); + else: + oProcess = utils.processStart(asArgs, env = asEnvTmp, stdout=subprocess.PIPE, stderr=subprocess.STDOUT); + + if not oProcess: + reporter.error('Starting process for "%s" failed!' % (sWhat)); + return False; + + iPid = oProcess.pid; + self.pidFileAdd(iPid, sWhat); + + iRc = 0; + + # For Python 3.x we provide "real-time" output. + if sys.version_info[0] >= 3: + while oProcess.stdout.readable(): # pylint: disable=no-member + sStdOut = oProcess.stdout.readline(); + if sStdOut: + sStdOut = sStdOut.strip(); + reporter.log('%s: %s' % (sWhat, sStdOut)); + iRc = oProcess.poll(); + if iRc is not None: + break; + else: + # For Python 2.x it's too much hassle to set the file descriptor options (O_NONBLOCK) and stuff, + # so just use communicate() here and dump everythiong all at once when finished. + sStdOut = oProcess.communicate(); + if sStdOut: + reporter.log('%s: %s' % (sWhat, sStdOut)); + iRc = oProcess.poll(); + + if iRc == 0: + reporter.log('*** %s: exit code %d' % (sWhat, iRc)); + fRc = True; + else: + reporter.log('!*! %s: exit code %d' % (sWhat, iRc)); + + self.pidFileRemove(iPid); + + # Save thread result code. + self.iThreadHstProcRc = iRc; + + except: + reporter.logXcpt('Executing "%s" failed!' % (sWhat)); + + return fRc; + + def executeHstThread(self, sWhat, asArgs, asEnv = None, fAsAdmin = False): + """ + Thread execution helper to run a process on the host. + """ + fRc = self.executeHstLoop(sWhat, asArgs, asEnv, fAsAdmin); + if fRc: + reporter.log('Executing \"%s\" on host done' % (sWhat,)); + else: + reporter.log('Executing \"%s\" on host failed' % (sWhat,)); + + def executeHst(self, sWhat, asArgs, asEnv = None, fAsAdmin = False): + """ + Runs a binary (image) with optional admin (root) rights on the host and + waits until it terminates. + + Windows currently is not supported yet running stuff as Administrator. + + Returns success status (exit code is 0). + """ + reporter.log('Executing \"%s\" on host (as admin = %s)' % (sWhat, fAsAdmin)); + + try: sys.stdout.flush(); + except: pass; + try: sys.stderr.flush(); + except: pass; + + # Initialize thread rc. + self.iThreadHstProcRc = -42; + + try: + oThread = threading.Thread(target = self.executeHstThread, args = [ sWhat, asArgs, asEnv, fAsAdmin ]); + oThread.start(); + while oThread.join(0.1): + if not oThread.is_alive(): + break; + self.processEvents(0); + reporter.log2('Thread returned exit code for "%s": %d' % (sWhat, self.iThreadHstProcRc)); + except: + reporter.logXcpt('Starting thread for "%s" failed' % (sWhat,)); + + return self.iThreadHstProcRc == 0; + + def getWinFirewallArgsDisable(self, sOsType): + """ + Returns the command line arguments for Windows OSes + to disable the built-in firewall (if any). + + If not supported, returns an empty array. + """ + if sOsType == 'vista': # pylint: disable=no-else-return + # Vista and up. + return (['netsh.exe', 'advfirewall', 'set', 'allprofiles', 'state', 'off']); + elif sOsType == 'xp': # Older stuff (XP / 2003). + return(['netsh.exe', 'firewall', 'set', 'opmode', 'mode=DISABLE']); + # Not supported / available. + return []; + + def disableGstFirewall(self, oTestVm, oTxsSession): + """ + Disables the firewall on a guest (if any). + + Needs elevated / admin / root privileges. + + Returns success status, not logged. + """ + fRc = False; + + asArgs = []; + sOsType = ''; + if oTestVm.isWindows(): + if oTestVm.sKind in ['WindowsNT4', 'WindowsNT3x']: + sOsType = 'nt3x'; # Not supported, but define it anyway. + elif oTestVm.sKind in ('Windows2000', 'WindowsXP', 'Windows2003'): + sOsType = 'xp'; + else: + sOsType = 'vista'; + asArgs = self.getWinFirewallArgsDisable(sOsType); + else: + sOsType = 'unsupported'; + + reporter.log('Disabling firewall on guest (type: %s) ...' % (sOsType,)); + + if asArgs: + fRc = self.txsRunTest(oTxsSession, 'Disabling guest firewall', 3 * 60 * 1000, \ + oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), asArgs[0]), asArgs); + if not fRc: + reporter.error('Disabling firewall on guest returned exit code error %d' % (self.getLastRcFromTxs(oTxsSession))); + else: + reporter.log('Firewall not available on guest, skipping'); + fRc = True; # Not available, just skip. + + return fRc; + + def disableHstFirewall(self): + """ + Disables the firewall on the host (if any). + + Needs elevated / admin / root privileges. + + Returns success status, not logged. + """ + fRc = False; + + asArgs = []; + sOsType = sys.platform; + + if sOsType == 'win32': + reporter.log('Disabling firewall on host (type: %s) ...' % (sOsType)); + + ## @todo For now we ASSUME that we don't run (and don't support even) on old(er) + # Windows hosts than Vista. + asArgs = self.getWinFirewallArgsDisable('vista'); + if asArgs: + fRc = self.executeHst('Disabling host firewall', asArgs, fAsAdmin = True); + else: + reporter.log('Firewall not available on host, skipping'); + fRc = True; # Not available, just skip. + + return fRc; + + def getLastRcFromTxs(self, oTxsSession): + """ + Extracts the last exit code reported by TXS from a run before. + Assumes that nothing else has been run on the same TXS session in the meantime. + """ + iRc = 0; + (_, sOpcode, abPayload) = oTxsSession.getLastReply(); + if sOpcode.startswith('PROC NOK '): # Extract process rc + iRc = abPayload[0]; # ASSUMES 8-bit rc for now. + return iRc; + + def startVkatOnGuest(self, oTestVm, oSession, oTxsSession, sTag): + """ + Starts VKAT on the guest (running in background). + """ + sPathTemp = self.getGuestTempDir(oTestVm); + sPathAudioOut = oTestVm.pathJoin(sPathTemp, 'vkat-guest-out'); + sPathAudioTemp = oTestVm.pathJoin(sPathTemp, 'vkat-guest-temp'); + + reporter.log('Guest audio test temp path is \"%s\"' % (sPathAudioOut)); + reporter.log('Guest audio test output path is \"%s\"' % (sPathAudioTemp)); + reporter.log('Guest audio test tag is \"%s\"' % (sTag)); + + fRc, sVkatExe = self.locateGstBinary(oSession, oTxsSession, self.asGstVkatPaths); + if fRc: + reporter.log('Using VKAT on guest at \"%s\"' % (sVkatExe)); + + sCmd = ''; + asArgs = []; + + asArgsVkat = [ sVkatExe, 'test', '--mode', 'guest', '--probe-backends', \ + '--tempdir', sPathAudioTemp, '--outdir', sPathAudioOut, \ + '--tag', sTag ]; + + asArgs.extend(asArgsVkat); + + for _ in range(1, reporter.getVerbosity()): # Verbosity always is initialized at 1. + asArgs.extend([ '-v' ]); + + # Needed for NATed VMs. + asArgs.extend(['--tcp-connect-addr', '10.0.2.2' ]); + + if oTestVm.sKind in 'Oracle_64': + # + # Some Linux distros have a bug / are configured (?) so that processes started by init system + # cannot access the PulseAudio server ("Connection refused"), for example OL 8.1. + # + # To work around this, we use the (hopefully) configured user "vbox" and run it under its behalf, + # as the Test Execution Service (TxS) currently does not implement impersonation yet. + # + asSU = [ '/bin/su', + '/usr/bin/su', + '/usr/local/bin/su' ]; + fRc, sCmd = self.locateGstBinary(oSession, oTxsSession, asSU); + if fRc: + sCmdArgs = ''; + for sArg in asArgs: + sCmdArgs += sArg + " "; + asArgs = [ sCmd, oTestVm.getTestUser(), '-c', sCmdArgs ]; + else: + reporter.log('Unable to find SU on guest, falling back to regular starting ...') + + if not sCmd: # Just start it with the same privileges as TxS. + sCmd = sVkatExe; + + reporter.log2('startVkatOnGuest: sCmd=%s' % (sCmd,)); + reporter.log2('startVkatOnGuest: asArgs=%s' % (asArgs,)); + + # + # Add own environment stuff. + # + asEnv = []; + + # Write the log file to some deterministic place so TxS can retrieve it later. + sVkatLogFile = 'VKAT_RELEASE_LOG_DEST=file=' + self.getGstVkatLogFilePath(oTestVm); + asEnv.extend([ sVkatLogFile ]); + + # + # Execute asynchronously on the guest. + # + fRc = oTxsSession.asyncExec(sCmd, asArgs, asEnv, cMsTimeout = 15 * 60 * 1000, sPrefix = '[VKAT Guest] '); + if fRc: + self.addTask(oTxsSession); + + if not fRc: + reporter.error('VKAT on guest returned exit code error %d' % (self.getLastRcFromTxs(oTxsSession))); + else: + reporter.error('VKAT on guest not found'); + + return fRc; + + def runTests(self, oTestVm, oSession, oTxsSession, sDesc, sTag, asTests): + """ + Runs one or more tests using VKAT on the host, which in turn will + communicate with VKAT running on the guest and the Validation Kit + audio driver ATS (Audio Testing Service). + """ + _ = oTestVm, oSession, oTxsSession; + + sPathTemp = self.sScratchPath; + sPathAudioOut = os.path.join(sPathTemp, 'vkat-host-out-%s' % (sTag)); + sPathAudioTemp = os.path.join(sPathTemp, 'vkat-host-temp-%s' % (sTag)); + + reporter.log('Host audio test temp path is \"%s\"' % (sPathAudioOut)); + reporter.log('Host audio test output path is \"%s\"' % (sPathAudioTemp)); + reporter.log('Host audio test tag is \"%s\"' % (sTag)); + + reporter.testStart(sDesc); + + sVkatExe = self.getBinTool('vkat'); + + reporter.log('Using VKAT on host at: \"%s\"' % (sVkatExe)); + + # Build the base command line, exclude all tests by default. + asArgs = [ sVkatExe, 'test', '--mode', 'host', '--probe-backends', + '--tempdir', sPathAudioTemp, '--outdir', sPathAudioOut, '-a', + '--tag', sTag, + '--no-audio-ok', # Enables running on hosts which do not have any audio hardware. + '--no-verify' ]; # We do the verification separately in the step below. + + for _ in range(1, reporter.getVerbosity()): # Verbosity always is initialized at 1. + asArgs.extend([ '-v' ]); + + if self.asVkatTestArgs: + asArgs += self.asVkatTestArgs; + + # ... and extend it with wanted tests. + asArgs.extend(asTests); + + # + # Let VKAT on the host run synchronously. + # + fRc = self.executeHst("VKAT Host", asArgs); + + reporter.testDone(); + + if fRc: + # + # When running the test(s) above were successful, do the verification step next. + # This gives us a bit more fine-grained test results in the test manager. + # + reporter.testStart('Verifying audio data'); + + sNameSetHst = '%s-host.tar.gz' % (sTag); + sPathSetHst = os.path.join(sPathAudioOut, sNameSetHst); + sNameSetGst = '%s-guest.tar.gz' % (sTag); + sPathSetGst = os.path.join(sPathAudioOut, sNameSetGst); + + asArgs = [ sVkatExe, 'verify', sPathSetHst, sPathSetGst ]; + + for _ in range(1, reporter.getVerbosity()): # Verbosity always is initialized at 1. + asArgs.extend([ '-v' ]); + + if self.asVkatVerifyArgs: + asArgs += self.asVkatVerifyArgs; + + fRc = self.executeHst("VKAT Host Verify", asArgs); + if fRc: + reporter.log("Verification audio data successful"); + else: + # + # Add the test sets to the test manager for later (manual) diagnosis. + # + reporter.addLogFile(sPathSetGst, 'misc/other', 'Guest audio test set'); + reporter.addLogFile(sPathSetHst, 'misc/other', 'Host audio test set'); + + reporter.error("Verification of audio data failed"); + + reporter.testDone(); + + return fRc; + + def doTest(self, oTestVm, oSession, oTxsSession): + """ + Executes the specified audio tests. + """ + + # Disable any OS-specific firewalls preventing VKAT / ATS to run. + fRc = self.disableHstFirewall(); + fRc = self.disableGstFirewall(oTestVm, oTxsSession) and fRc; + + if not fRc: + return False; + + reporter.log("Active tests: %s" % (self.asTests,)); + + # Define a tag for the whole run. + sTag = oTestVm.sVmName + "_" + datetime.now().strftime("%Y%m%d_%H%M%S"); + + fRc = self.startVkatOnGuest(oTestVm, oSession, oTxsSession, sTag); + if fRc: + # + # Execute the tests using VKAT on the guest side (in guest mode). + # + if "guest_tone_playback" in self.asTests: + fRc = self.runTests(oTestVm, oSession, oTxsSession, \ + 'Guest audio playback', sTag + "_test_playback", \ + asTests = [ '-i0' ]); + if "guest_tone_recording" in self.asTests: + fRc = fRc and self.runTests(oTestVm, oSession, oTxsSession, \ + 'Guest audio recording', sTag + "_test_recording", \ + asTests = [ '-i1' ]); + + # Cancel guest VKAT execution task summoned by startVkatOnGuest(). + oTxsSession.cancelTask(); + + # + # Retrieve log files for diagnosis. + # + self.txsDownloadFiles(oSession, oTxsSession, + [ ( self.getGstVkatLogFilePath(oTestVm), + 'vkat-guest-%s.log' % (oTestVm.sVmName,),), + ], + fIgnoreErrors = True); + + # A bit of diagnosis on error. + ## @todo Remove this later when stuff runs stable. + if not fRc: + reporter.log('Kernel messages:'); + sCmdDmesg = oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), 'dmesg'); + oTxsSession.syncExec(sCmdDmesg, (sCmdDmesg), fIgnoreErrors = True); + reporter.log('Loaded kernel modules:'); + sCmdLsMod = oTestVm.pathJoin(self.getGuestSystemAdminDir(oTestVm), 'lsmod'); + oTxsSession.syncExec(sCmdLsMod, (sCmdLsMod), fIgnoreErrors = True); + + return fRc; + + def testOneVmConfig(self, oVM, oTestVm): + """ + Runs tests using one specific VM config. + """ + + self.logVmInfo(oVM); + + reporter.testStart("Audio Testing"); + + fSkip = False; + + if oTestVm.isWindows() \ + and oTestVm.sKind in ('WindowsNT4', 'Windows2000'): # Too old for DirectSound and WASAPI backends. + reporter.log('Audio testing skipped, not implemented/available for that OS yet.'); + fSkip = True; + + if not fSkip \ + and self.fpApiVer < 7.0: + reporter.log('Audio testing for non-trunk builds skipped.'); + fSkip = True; + + if not fSkip: + sVkatExe = self.getBinTool('vkat'); + asArgs = [ sVkatExe, 'enum', '--probe-backends' ]; + for _ in range(1, reporter.getVerbosity()): # Verbosity always is initialized at 1. + asArgs.extend([ '-v' ]); + fRc = self.executeHst("VKAT Host Audio Probing", asArgs); + if not fRc: + # Not fatal, as VBox then should fall back to the NULL audio backend (also worth having as a test case). + reporter.log('Warning: Backend probing on host failed, no audio available (pure server installation?)'); + + if fSkip: + reporter.testDone(fSkipped = True); + return True; + + # Reconfigure the VM. + oSession = self.openSession(oVM); + if oSession is not None: + + cVerbosity = reporter.getVerbosity(); + if cVerbosity >= 2: # Explicitly set verbosity via extra-data when >= level 2. + self.asOptExtraData.extend([ 'VBoxInternal2/Audio/Debug/Level:' + str(cVerbosity) ]); + + # Set extra data. + for sExtraData in self.asOptExtraData: + sKey, sValue = sExtraData.split(':'); + reporter.log('Set extradata: %s => %s' % (sKey, sValue)); + fRc = oSession.setExtraData(sKey, sValue) and fRc; + + # Make sure that the VM's audio adapter is configured the way we need it to. + if self.fpApiVer >= 4.0: + enmAudioControllerType = None; + reporter.log('Configuring audio controller type ...'); + if self.sAudioControllerType is None: + oOsType = oSession.getOsType(); + enmAudioControllerType = oOsType.recommendedAudioController; + else: + if self.sAudioControllerType == 'HDA': + enmAudioControllerType = vboxcon.AudioControllerType_HDA; + elif self.sAudioControllerType == 'AC97': + enmAudioControllerType = vboxcon.AudioControllerType_AC97; + elif self.sAudioControllerType == 'SB16': + enmAudioControllerType = vboxcon.AudioControllerType_SB16; + assert enmAudioControllerType is not None; + + # For now we're encforcing to test the HDA emulation only, regardless of + # what the recommended audio controller type from above was. + ## @todo Make other emulations work as well. + fEncforceHDA = True; + + if fEncforceHDA: + enmAudioControllerType = vboxcon.AudioControllerType_HDA; + reporter.log('Enforcing audio controller type to HDA'); + + reporter.log('Setting user-defined audio controller type to %d' % (enmAudioControllerType)); + oSession.setupAudio(enmAudioControllerType, + fEnable = True, fEnableIn = True, fEnableOut = True); + + # Save the settings. + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc; + + reporter.testStart('Waiting for TXS'); + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, + fCdWait = True, + cMsTimeout = 3 * 60 * 1000, + sFileCdWait = '${OS/ARCH}/vkat${EXESUFF}'); + reporter.testDone(); + + reporter.log('Waiting for any OS startup sounds getting played (to skip those) ...'); + time.sleep(5); + + if oSession is not None: + self.addTask(oTxsSession); + + fRc = self.doTest(oTestVm, oSession, oTxsSession); + + # Cleanup. + self.removeTask(oTxsSession); + self.terminateVmBySession(oSession); + + reporter.testDone(); + return fRc; + + def onExit(self, iRc): + """ + Exit handler for this test driver. + """ + return vbox.TestDriver.onExit(self, iRc); + +if __name__ == '__main__': + sys.exit(tdAudioTest().main(sys.argv)) diff --git a/src/VBox/ValidationKit/tests/audio/tdGuestHostTimings.py b/src/VBox/ValidationKit/tests/audio/tdGuestHostTimings.py new file mode 100755 index 00000000..d9231317 --- /dev/null +++ b/src/VBox/ValidationKit/tests/audio/tdGuestHostTimings.py @@ -0,0 +1,240 @@ +# -*- coding: utf-8 -*- +# $Id: tdGuestHostTimings.py $ + +""" +???????? +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +import os +import sys +import time +import subprocess +import re +import time + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testdriver import reporter +from testdriver import base +from testdriver import vbox +from testdriver import vboxcon +from testdriver import vboxtestvms + +class tdGuestHostTimings(vbox.TestDriver): # pylint: disable=too-many-instance-attributes + + def __init__(self): + vbox.TestDriver.__init__(self); + self.sSessionTypeDef = 'gui'; + + self.oTestVmSet = self.oTestVmManager.getStandardVmSet('nat') ## ??? + + # Use the command line "--test-vms mw7x64 execute" to run the only "mw7x64" VM + oTestVm = vboxtestvms.TestVm('mw7x64', oSet = self.oTestVmSet, sHd = 'mw7x64.vdi', + sKind = 'Windows7', acCpusSup = range(1, 2), fIoApic = True, sFirmwareType = 'bios', + asParavirtModesSup = ['hyperv'], asVirtModesSup = ['hwvirt-np'], + sHddControllerType = 'SATA Controller'); + + self.oTestVmSet.aoTestVms.append(oTestVm); + + self.sVMname = None + + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdGuestHostTimings Options:'); + reporter.log(' --runningvmname <vmname>'); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--runningvmname': + iArg += 1 + if iArg >= len(asArgs): + raise base.InvalidOption('The "----runningvmname" needs VM name') + + self.sVMname = asArgs[iArg] + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg) + return iArg + 1 + + def actionConfig(self): + return True + + def actionExecute(self): + #self.sTempPathHost = os.environ.get("IPRT_TMPDIR") + self.sTempPathHost = os.path.normpath(os.environ.get("TEMP") + "/VBoxAudioValKit") + + if self.sVMname is None: + return self.oTestVmSet.actionExecute(self, self.testOneVmConfig) + else: + return self.actionExecuteOnRunnigVM() + + def doTest(self, oSession): + oConsole = oSession.console + oGuest = oConsole.guest + + sOSTypeId = oGuest.OSTypeId.lower() + if sOSTypeId.find("win") == -1 : + reporter.log("Only Windows guests are currently supported") + reporter.testDone() + return True + + oGuestSession = oGuest.createSession("Administrator", "password", "", "Audio Validation Kit") + guestSessionWaitResult = oGuestSession.waitFor(self.oVBoxMgr.constants.GuestSessionWaitResult_Start, 2000) + reporter.log("guestSessionWaitResult = %d" % guestSessionWaitResult) + + for duration in range(3, 6): + reporter.testStart("Checking for duration of " + str(duration) + " seconds") + sPathToPlayer = "D:\\win\\" + ("amd64" if (sOSTypeId.find('_64') >= 0) else "x86") + "\\ntPlayToneWaveX.exe" + oProcess = oGuestSession.processCreate(sPathToPlayer, ["xxx0", "--total-duration-in-secs", str(duration)], [], [], 0) + processWaitResult = oProcess.waitFor(self.oVBoxMgr.constants.ProcessWaitForFlag_Start, 1000) + reporter.log("Started: pid %d, waitResult %d" % (oProcess.PID, processWaitResult)) + + processWaitResult = oProcess.waitFor(self.oVBoxMgr.constants.ProcessWaitForFlag_Terminate, 2 * duration * 1000) + reporter.log("Terminated: pid %d, waitResult %d" % (oProcess.PID, processWaitResult)) + time.sleep(1) # Give audio backend sometime to save a stream to .wav file + + absFileName = self.seekLatestAudioFileName(oGuestSession, duration) + + if absFileName is None: + reporter.testFailure("Unable to find audio file") + continue + + reporter.log("Checking audio file '" + absFileName + "'") + + diff = self.checkGuestHostTimings(absFileName + ".timing") + if diff is not None: + if diff > 0.0: # Guest sends data quicker than a host can play + if diff > 0.01: # 1% is probably good threshold here + reporter.testFailure("Guest sends audio buffers too quickly") + else: + diff = -diff; # Much worse case: guest sends data very slow, host feels starvation + if diff > 0.005: # 0.5% is probably good threshold here + reporter.testFailure("Guest sends audio buffers too slowly") + + reporter.testDone() + else: + reporter.testFailure("Unable to parse a file with timings") + + oGuestSession.close() + + del oGuest + del oConsole + + return True + + def testOneVmConfig(self, oVM, oTestVm): + #self.logVmInfo(oVM) + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, + fCdWait = True, + cMsTimeout = 60 * 1000) + if oSession is not None and oTxsSession is not None: + # Wait until guest reported success + reporter.log('Guest started. Connection to TXS service established.') + self.doTest(oSessionWrapper.o) + + return True + + def actionExecuteOnRunnigVM(self): + if not self.importVBoxApi(): + return False; + + oVirtualBox = self.oVBoxMgr.getVirtualBox() + oMachine = oVirtualBox.findMachine(self.sVMname) + + if oMachine == None: + reporter.log("Machine '%s' is unknown" % (oMachine.name)) + return False + + if oMachine.state != self.oVBoxMgr.constants.MachineState_Running: + reporter.log("Machine '%s' is not Running" % (oMachine.name)) + return False + + oSession = self.oVBoxMgr.mgr.getSessionObject(oVirtualBox) + oMachine.lockMachine(oSession, self.oVBoxMgr.constants.LockType_Shared) + + self.doTest(oSession); + + oSession.unlockMachine() + + del oSession + del oMachine + del oVirtualBox + return True + + def seekLatestAudioFileName(self, guestSession, duration): + + listOfFiles = os.listdir(self.sTempPathHost) + # Assuming that .wav files are named like 2016-11-15T12_08_27.669573100Z.wav by VBOX audio backend + # So that sorting by name = sorting by creation date + listOfFiles.sort(reverse = True) + + for fileName in listOfFiles: + if not fileName.endswith(".wav"): + continue + + absFileName = os.path.join(self.sTempPathHost, fileName) + + # Ignore too small wav files (usually uncompleted audio streams) + statInfo = os.stat(absFileName) + if statInfo.st_size > 100: + return absFileName + + return + + def checkGuestHostTimings(self, absFileName): + with open(absFileName) as f: + for line_terminated in f: + line = line_terminated.rstrip('\n') + + reporter.log("Last line is: " + line) + matchObj = re.match( r'(\d+) (\d+)', line, re.I) + if matchObj: + hostTime = int(matchObj.group(1)) + guestTime = int(matchObj.group(2)) + + diff = float(guestTime - hostTime) / hostTime + return diff + + return + +if __name__ == '__main__': + sys.exit(tdGuestHostTimings().main(sys.argv)); diff --git a/src/VBox/ValidationKit/tests/autostart/Makefile.kmk b/src/VBox/ValidationKit/tests/autostart/Makefile.kmk new file mode 100644 index 00000000..e9667324 --- /dev/null +++ b/src/VBox/ValidationKit/tests/autostart/Makefile.kmk @@ -0,0 +1,51 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Autostart. +# + +# +# Copyright (C) 2013-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +INSTALLS += ValidationKitTestsAutostart +ValidationKitTestsAutostart_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsAutostart_INST = $(INST_VALIDATIONKIT)tests/autostart/ +ValidationKitTestsAutostart_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdAutostart1.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsAutostart_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/autostart/tdAutostart1.py b/src/VBox/ValidationKit/tests/autostart/tdAutostart1.py new file mode 100755 index 00000000..724d96e7 --- /dev/null +++ b/src/VBox/ValidationKit/tests/autostart/tdAutostart1.py @@ -0,0 +1,1443 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Autostart testcase using <please-tell-what-I-am-doing>. +""" + +__copyright__ = \ +""" +Copyright (C) 2013-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Id: tdAutostart1.py $" + +# Standard Python imports. +import os; +import sys; +import re; + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testdriver import reporter; +from testdriver import base; +from testdriver import vbox; +from testdriver import vboxcon; +from testdriver import vboxtestvms; +from testdriver import vboxwrappers; + +class VBoxManageStdOutWrapper(object): + """ Parser for VBoxManage list runningvms """ + + def __init__(self): + self.sVmRunning = ''; + + def __del__(self): + self.close(); + + def close(self): + """file.close""" + return; + + def read(self, cb): + """file.read""" + _ = cb; + return ""; + + def write(self, sText): + """VBoxManage stdout write""" + if sText is None: + return None; + try: sText = str(sText); # pylint: disable=redefined-variable-type + except: pass; + asLines = sText.splitlines(); + for sLine in asLines: + sLine = sLine.strip(); + reporter.log('Logging: ' + sLine); + # Extract the value + idxVmNameStart = sLine.find('"'); + if idxVmNameStart == -1: + raise Exception('VBoxManageStdOutWrapper: Invalid output'); + idxVmNameStart += 1; + idxVmNameEnd = idxVmNameStart; + while sLine[idxVmNameEnd] != '"': + idxVmNameEnd += 1; + self.sVmRunning = sLine[idxVmNameStart:idxVmNameEnd]; + reporter.log('Logging: ' + self.sVmRunning); + return None; + +class tdAutostartOs(vboxtestvms.BaseTestVm): + """ + Base autostart helper class to provide common methods. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None): + vboxtestvms.BaseTestVm.__init__(self, sVmName, oSet = oSet, sKind = sKind); + self.oTstDrv = oTstDrv; + self.sHdd = sHdd; + self.eNic0Type = eNic0Type; + self.cMbRam = cMbRam; + self.cCpus = cCpus; + self.fPae = fPae; + self.sGuestAdditionsIso = sGuestAdditionsIso; + self.asTestBuildDirs = oTstDrv.asTestBuildDirs; + self.sVBoxInstaller = ""; + self.asVirtModesSup = ['hwvirt-np',]; + self.asParavirtModesSup = ['default',]; + + def _findFile(self, sRegExp, asTestBuildDirs): + """ + Returns a filepath based on the given regex and paths to look into + or None if no matching file is found. + """ + oRegExp = re.compile(sRegExp); + for sTestBuildDir in asTestBuildDirs: + try: + #return most recent file if there are several ones matching the pattern + asFiles = [s for s in os.listdir(sTestBuildDir) + if os.path.isfile(os.path.join(sTestBuildDir, s))]; + asFiles = (s for s in asFiles + if oRegExp.match(os.path.basename(s)) + and os.path.exists(sTestBuildDir + '/' + s)); + asFiles = sorted(asFiles, reverse = True, + key = lambda s, sTstBuildDir = sTestBuildDir: os.path.getmtime(os.path.join(sTstBuildDir, s))); + if asFiles: + return sTestBuildDir + '/' + asFiles[0]; + except: + pass; + reporter.error('Failed to find a file matching "%s" in %s.' % (sRegExp, ','.join(asTestBuildDirs))); + return None; + + def _createAutostartCfg(self, sDefaultPolicy = 'allow', asUserAllow = (), asUserDeny = ()): + """ + Creates a autostart config for VirtualBox + """ + sVBoxCfg = 'default_policy=' + sDefaultPolicy + '\n'; + for sUserAllow in asUserAllow: + sVBoxCfg = sVBoxCfg + sUserAllow + ' = {\n allow = true\n }\n'; + for sUserDeny in asUserDeny: + sVBoxCfg = sVBoxCfg + sUserDeny + ' = {\n allow = false\n }\n'; + return sVBoxCfg; + + def _waitAdditionsIsRunning(self, oGuest, fWaitTrayControl): + """ + Check is the additions running + """ + cAttempt = 0; + fRc = False; + while cAttempt < 30: + fRc = oGuest.additionsRunLevel in [vboxcon.AdditionsRunLevelType_Userland, + vboxcon.AdditionsRunLevelType_Desktop]; + if fRc: + eServiceStatus, _ = oGuest.getFacilityStatus(vboxcon.AdditionsFacilityType_VBoxService); + fRc = eServiceStatus == vboxcon.AdditionsFacilityStatus_Active; + if fRc and not fWaitTrayControl: + break; + if fRc: + eServiceStatus, _ = oGuest.getFacilityStatus(vboxcon.AdditionsFacilityType_VBoxTrayClient); + fRc = eServiceStatus == vboxcon.AdditionsFacilityStatus_Active; + if fRc: + break; + self.oTstDrv.sleep(10); + cAttempt += 1; + return fRc; + + def createSession(self, oSession, sName, sUser, sPassword, cMsTimeout = 10 * 1000, fIsError = True): + """ + Creates (opens) a guest session. + Returns (True, IGuestSession) on success or (False, None) on failure. + """ + oGuest = oSession.o.console.guest; + if sName is None: + sName = "<untitled>"; + reporter.log('Creating session "%s" ...' % (sName,)); + try: + oGuestSession = oGuest.createSession(sUser, sPassword, '', sName); + except: + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErrXcpt(fIsError, 'Creating a guest session "%s" failed; sUser="%s", pw="%s"' + % (sName, sUser, sPassword)); + return (False, None); + reporter.log('Waiting for session "%s" to start within %dms...' % (sName, cMsTimeout)); + aeWaitFor = [ vboxcon.GuestSessionWaitForFlag_Start, ]; + try: + waitResult = oGuestSession.waitForArray(aeWaitFor, cMsTimeout); + # + # Be nice to Guest Additions < 4.3: They don't support session handling and + # therefore return WaitFlagNotSupported. + # + if waitResult not in (vboxcon.GuestSessionWaitResult_Start, vboxcon.GuestSessionWaitResult_WaitFlagNotSupported): + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErr(fIsError, 'Session did not start successfully, returned wait result: %d' % (waitResult,)); + return (False, None); + reporter.log('Session "%s" successfully started' % (sName,)); + except: + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErrXcpt(fIsError, 'Waiting for guest session "%s" (usr=%s;pw=%s) to start failed:' + % (sName, sUser, sPassword,)); + return (False, None); + return (True, oGuestSession); + + def closeSession(self, oGuestSession, fIsError = True): + """ + Closes the guest session. + """ + if oGuestSession is not None: + try: + sName = oGuestSession.name; + except: + return reporter.errorXcpt(); + reporter.log('Closing session "%s" ...' % (sName,)); + try: + oGuestSession.close(); + oGuestSession = None; + except: + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErrXcpt(fIsError, 'Closing guest session "%s" failed:' % (sName,)); + return False; + return True; + + def guestProcessExecute(self, oGuestSession, sTestName, cMsTimeout, sExecName, asArgs = (), + fGetStdOut = True, fIsError = True): + """ + Helper function to execute a program on a guest, specified in the current test. + Returns (True, ProcessStatus, ProcessExitCode, ProcessStdOutBuffer) on success or (False, 0, 0, None) on failure. + """ + _ = sTestName; + fRc = True; # Be optimistic. + reporter.log2('Using session user=%s, name=%s, timeout=%d' + % (oGuestSession.user, oGuestSession.name, oGuestSession.timeout,)); + # + # Start the process: + # + reporter.log2('Executing sCmd=%s, timeoutMS=%d, asArgs=%s' + % (sExecName, cMsTimeout, asArgs, )); + fTaskFlags = []; + if fGetStdOut: + fTaskFlags = [vboxcon.ProcessCreateFlag_WaitForStdOut, + vboxcon.ProcessCreateFlag_WaitForStdErr]; + try: + oProcess = oGuestSession.processCreate(sExecName, + asArgs if self.oTstDrv.fpApiVer >= 5.0 else asArgs[1:], + [], fTaskFlags, cMsTimeout); + except: + reporter.maybeErrXcpt(fIsError, 'asArgs=%s' % (asArgs,)); + return (False, 0, 0, None); + if oProcess is None: + return (reporter.error('oProcess is None! (%s)' % (asArgs,)), 0, 0, None); + #time.sleep(5); # try this if you want to see races here. + # Wait for the process to start properly: + reporter.log2('Process start requested, waiting for start (%dms) ...' % (cMsTimeout,)); + iPid = -1; + aeWaitFor = [ vboxcon.ProcessWaitForFlag_Start, ]; + aBuf = None; + try: + eWaitResult = oProcess.waitForArray(aeWaitFor, cMsTimeout); + except: + reporter.maybeErrXcpt(fIsError, 'waitforArray failed for asArgs=%s' % (asArgs,)); + fRc = False; + else: + try: + eStatus = oProcess.status; + iPid = oProcess.PID; + except: + fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + else: + reporter.log2('Wait result returned: %d, current process status is: %d' % (eWaitResult, eStatus,)); + # + # Wait for the process to run to completion if necessary. + # + # Note! The above eWaitResult return value can be ignored as it will + # (mostly) reflect the process status anyway. + # + if eStatus == vboxcon.ProcessStatus_Started: + # What to wait for: + aeWaitFor = [ vboxcon.ProcessWaitForFlag_Terminate, + vboxcon.ProcessWaitForFlag_StdOut, + vboxcon.ProcessWaitForFlag_StdErr]; + reporter.log2('Process (PID %d) started, waiting for termination (%dms), aeWaitFor=%s ...' + % (iPid, cMsTimeout, aeWaitFor)); + acbFdOut = [0,0,0]; + while True: + try: + eWaitResult = oProcess.waitForArray(aeWaitFor, cMsTimeout); + except KeyboardInterrupt: # Not sure how helpful this is, but whatever. + reporter.error('Process (PID %d) execution interrupted' % (iPid,)); + try: oProcess.close(); + except: pass; + break; + except: + fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + break; + reporter.log2('Wait returned: %d' % (eWaitResult,)); + # Process output: + for eFdResult, iFd, sFdNm in [ (vboxcon.ProcessWaitResult_StdOut, 1, 'stdout'), + (vboxcon.ProcessWaitResult_StdErr, 2, 'stderr'), ]: + if eWaitResult in (eFdResult, vboxcon.ProcessWaitResult_WaitFlagNotSupported): + reporter.log2('Reading %s ...' % (sFdNm,)); + try: + abBuf = oProcess.read(iFd, 64 * 1024, cMsTimeout); + except KeyboardInterrupt: # Not sure how helpful this is, but whatever. + reporter.error('Process (PID %d) execution interrupted' % (iPid,)); + try: oProcess.close(); + except: pass; + except: + pass; ## @todo test for timeouts and fail on anything else! + else: + if abBuf: + reporter.log2('Process (PID %d) got %d bytes of %s data' % (iPid, len(abBuf), sFdNm,)); + acbFdOut[iFd] += len(abBuf); + ## @todo Figure out how to uniform + append! + sBuf = ''; + if sys.version_info >= (2, 7) and isinstance(abBuf, memoryview): + abBuf = abBuf.tobytes(); + sBuf = abBuf.decode("utf-8"); + else: + sBuf = str(abBuf); + if aBuf: + aBuf += sBuf; + else: + aBuf = sBuf; + ## Process input (todo): + #if eWaitResult in (vboxcon.ProcessWaitResult_StdIn, vboxcon.ProcessWaitResult_WaitFlagNotSupported): + # reporter.log2('Process (PID %d) needs stdin data' % (iPid,)); + # Termination or error? + if eWaitResult in (vboxcon.ProcessWaitResult_Terminate, + vboxcon.ProcessWaitResult_Error, + vboxcon.ProcessWaitResult_Timeout,): + try: eStatus = oProcess.status; + except: fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + reporter.log2('Process (PID %d) reported terminate/error/timeout: %d, status: %d' + % (iPid, eWaitResult, eStatus,)); + break; + # End of the wait loop. + _, cbStdOut, cbStdErr = acbFdOut; + try: eStatus = oProcess.status; + except: fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + reporter.log2('Final process status (PID %d) is: %d' % (iPid, eStatus)); + reporter.log2('Process (PID %d) %d stdout, %d stderr' % (iPid, cbStdOut, cbStdErr)); + # + # Get the final status and exit code of the process. + # + try: + uExitStatus = oProcess.status; + iExitCode = oProcess.exitCode; + except: + fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + reporter.log2('Process (PID %d) has exit code: %d; status: %d ' % (iPid, iExitCode, uExitStatus)); + return (fRc, uExitStatus, iExitCode, aBuf); + + def uploadString(self, oGuestSession, sSrcString, sDst): + """ + Upload the string into guest. + """ + fRc = True; + try: + oFile = oGuestSession.fileOpenEx(sDst, vboxcon.FileAccessMode_ReadWrite, vboxcon.FileOpenAction_CreateOrReplace, + vboxcon.FileSharingMode_All, 0, []); + except: + fRc = reporter.errorXcpt('Upload string failed. Could not create and open the file %s' % sDst); + else: + try: + oFile.write(bytearray(sSrcString), 60*1000); + except: + fRc = reporter.errorXcpt('Upload string failed. Could not write the string into the file %s' % sDst); + try: + oFile.close(); + except: + fRc = reporter.errorXcpt('Upload string failed. Could not close the file %s' % sDst); + return fRc; + + def uploadFile(self, oGuestSession, sSrc, sDst): + """ + Upload the string into guest. + """ + fRc = True; + try: + if self.oTstDrv.fpApiVer >= 5.0: + oCurProgress = oGuestSession.fileCopyToGuest(sSrc, sDst, [0]); + else: + oCurProgress = oGuestSession.copyTo(sSrc, sDst, [0]); + except: + reporter.maybeErrXcpt(True, 'Upload file exception for sSrc="%s":' + % (self.sGuestAdditionsIso,)); + fRc = False; + else: + if oCurProgress is not None: + oWrapperProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, self.oTstDrv, "uploadFile"); + oWrapperProgress.wait(); + if not oWrapperProgress.isSuccess(): + oWrapperProgress.logResult(fIgnoreErrors = False); + fRc = False; + else: + fRc = reporter.error('No progress object returned'); + return fRc; + + def downloadFile(self, oGuestSession, sSrc, sDst, fIgnoreErrors = False): + """ + Get a file (sSrc) from the guest storing it on the host (sDst). + """ + fRc = True; + try: + if self.oTstDrv.fpApiVer >= 5.0: + oCurProgress = oGuestSession.fileCopyFromGuest(sSrc, sDst, [0]); + else: + oCurProgress = oGuestSession.copyFrom(sSrc, sDst, [0]); + except: + if not fIgnoreErrors: + reporter.errorXcpt('Download file exception for sSrc="%s":' % (sSrc,)); + else: + reporter.log('warning: Download file exception for sSrc="%s":' % (sSrc,)); + fRc = False; + else: + if oCurProgress is not None: + oWrapperProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, + self.oTstDrv, "downloadFile"); + oWrapperProgress.wait(); + if not oWrapperProgress.isSuccess(): + oWrapperProgress.logResult(fIgnoreErrors); + fRc = False; + else: + if not fIgnoreErrors: + reporter.error('No progress object returned'); + else: + reporter.log('warning: No progress object returned'); + fRc = False; + return fRc; + + def downloadFiles(self, oGuestSession, asFiles, fIgnoreErrors = False): + """ + Convenience function to get files from the guest and stores it + into the scratch directory for later (manual) review. + Returns True on success. + Returns False on failure, logged. + """ + fRc = True; + for sGstFile in asFiles: + ## @todo r=bird: You need to use the guest specific path functions here. + ## Best would be to add basenameEx to common/pathutils.py. See how joinEx + ## is used by BaseTestVm::pathJoin and such. + sTmpFile = os.path.join(self.oTstDrv.sScratchPath, 'tmp-' + os.path.basename(sGstFile)); + reporter.log2('Downloading file "%s" to "%s" ...' % (sGstFile, sTmpFile)); + # First try to remove (unlink) an existing temporary file, as we don't truncate the file. + try: os.unlink(sTmpFile); + except: pass; + ## @todo Check for already existing files on the host and create a new + # name for the current file to download. + fRc = self.downloadFile(oGuestSession, sGstFile, sTmpFile, fIgnoreErrors); + if fRc: + reporter.addLogFile(sTmpFile, 'misc/other', 'guest - ' + sGstFile); + else: + if fIgnoreErrors is not True: + reporter.error('error downloading file "%s" to "%s"' % (sGstFile, sTmpFile)); + return fRc; + reporter.log('warning: file "%s" was not downloaded, ignoring.' % (sGstFile,)); + return True; + + def _checkVmIsReady(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Start a guest process', + 30 * 1000, '/sbin/ifconfig', + ['ifconfig',], + False, False); + return fRc; + + def waitVmIsReady(self, oSession, fWaitTrayControl): + """ + Waits the VM is ready after start or reboot. + Returns result (true or false) and guest session obtained + """ + _ = fWaitTrayControl; + # Give the VM a time to reboot + self.oTstDrv.sleep(30); + # Waiting the VM is ready. + # To do it, one will try to open the guest session and start the guest process in loop + if not self._waitAdditionsIsRunning(oSession.o.console.guest, False): + return (False, None); + cAttempt = 0; + oGuestSession = None; + fRc = False; + while cAttempt < 30: + fRc, oGuestSession = self.createSession(oSession, 'Session for user: vbox', + 'vbox', 'password', 10 * 1000, False); + if fRc: + fRc = self._checkVmIsReady(oGuestSession); + if fRc: + break; + self.closeSession(oGuestSession, False); + self.oTstDrv.sleep(10); + cAttempt += 1; + return (fRc, oGuestSession); + + def _rebootVM(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Reboot the VM', + 30 * 1000, '/usr/bin/sudo', + ['sudo', 'reboot'], + False, True); + if not fRc: + reporter.error('Calling the reboot utility failed'); + return fRc; + + def rebootVMAndCheckReady(self, oSession, oGuestSession): + """ + Reboot the VM and wait the VM is ready. + Returns result and guest session obtained after reboot + """ + reporter.testStart('Reboot VM and wait for readiness'); + fRc = self._rebootVM(oGuestSession); + fRc = self.closeSession(oGuestSession, True) and fRc and True; # pychecker hack. + if fRc: + (fRc, oGuestSession) = self.waitVmIsReady(oSession, False); + if not fRc: + reporter.error('VM is not ready after reboot'); + reporter.testDone(); + return (fRc, oGuestSession); + + def _powerDownVM(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Power down the VM', + 30 * 1000, '/usr/bin/sudo', + ['sudo', 'poweroff'], + False, True); + if not fRc: + reporter.error('Calling the poweroff utility failed'); + return fRc; + + def powerDownVM(self, oGuestSession): + """ + Power down the VM by calling guest process without wating + the VM is really powered off. Also, closes the guest session. + It helps the terminateBySession to stop the VM without aborting. + """ + if oGuestSession is None: + return False; + reporter.testStart('Power down the VM'); + fRc = self._powerDownVM(oGuestSession); + fRc = self.closeSession(oGuestSession, True) and fRc and True; # pychecker hack. + if not fRc: + reporter.error('Power down the VM failed'); + reporter.testDone(); + return fRc; + + def installAdditions(self, oSession, oGuestSession, oVM): + """ + Installs the Windows guest additions using the test execution service. + """ + _ = oSession; + _ = oGuestSession; + _ = oVM; + reporter.error('Not implemented'); + return False; + + def installVirtualBox(self, oGuestSession): + """ + Install VirtualBox in the guest. + """ + _ = oGuestSession; + reporter.error('Not implemented'); + return False; + + def configureAutostart(self, oGuestSession, sDefaultPolicy = 'allow', asUserAllow = (), asUserDeny = ()): + """ + Configures the autostart feature in the guest. + """ + _ = oGuestSession; + _ = sDefaultPolicy; + _ = asUserAllow; # pylint: disable=redefined-variable-type + _ = asUserDeny; + reporter.error('Not implemented'); + return False; + + def createUser(self, oGuestSession, sUser): + """ + Create a new user with the given name + """ + _ = oGuestSession; + _ = sUser; + reporter.error('Not implemented'); + return False; + + def checkForRunningVM(self, oSession, oGuestSession, sUser, sVmName): + """ + Check for VM running in the guest after autostart. + Due to the sUser is created whithout password, + all calls will be perfomed using 'sudo -u sUser' + """ + _ = oSession; + _ = oGuestSession; + _ = sUser; + _ = sVmName; + reporter.error('Not implemented'); + return False; + + def getResourceSet(self): + asRet = []; + if not os.path.isabs(self.sHdd): + asRet.append(self.sHdd); + return asRet; + + def _createVmDoIt(self, oTestDrv, eNic0AttachType, sDvdImage): + """ + Creates the VM. + Returns Wrapped VM object on success, None on failure. + """ + _ = eNic0AttachType; + _ = sDvdImage; + return oTestDrv.createTestVM(self.sVmName, self.iGroup, self.sHdd, sKind = self.sKind, \ + fIoApic = True, eNic0AttachType = vboxcon.NetworkAttachmentType_NAT, \ + eNic0Type = self.eNic0Type, cMbRam = self.cMbRam, \ + sHddControllerType = "SATA Controller", fPae = self.fPae, \ + cCpus = self.cCpus, sDvdImage = self.sGuestAdditionsIso); + + def _createVmPost(self, oTestDrv, oVM, eNic0AttachType, sDvdImage): + _ = eNic0AttachType; + _ = sDvdImage; + fRc = True; + oSession = oTestDrv.openSession(oVM); + if oSession is not None: + fRc = fRc and oSession.enableVirtEx(True); + fRc = fRc and oSession.enableNestedPaging(True); + fRc = fRc and oSession.enableNestedHwVirt(True); + # disable 3D until the error is fixed. + fRc = fRc and oSession.setAccelerate3DEnabled(False); + fRc = fRc and oSession.setVRamSize(256); + fRc = fRc and oSession.setVideoControllerType(vboxcon.GraphicsControllerType_VBoxSVGA); + fRc = fRc and oSession.enableUsbOhci(True); + fRc = fRc and oSession.enableUsbHid(True); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + return oVM if fRc else None; + + def getReconfiguredVm(self, oTestDrv, cCpus, sVirtMode, sParavirtMode = None): + # + # Current test uses precofigured VMs. This override disables any changes in the machine. + # + _ = cCpus; + _ = sVirtMode; + _ = sParavirtMode; + oVM = oTestDrv.getVmByName(self.sVmName); + if oVM is None: + return (False, None); + return (True, oVM); + +class tdAutostartOsLinux(tdAutostartOs): + """ + Autostart support methods for Linux guests. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None): + tdAutostartOs.__init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type, cMbRam, \ + cCpus, fPae, sGuestAdditionsIso); + try: self.sVBoxInstaller = '^VirtualBox-.*\\.run$'; + except: pass; + return; + + def installAdditions(self, oSession, oGuestSession, oVM): + """ + Install guest additions in the guest. + """ + reporter.testStart('Install Guest Additions'); + fRc = False; + # Install Kernel headers, which are required for actually installing the Linux Additions. + if oVM.OSTypeId.startswith('Debian') \ + or oVM.OSTypeId.startswith('Ubuntu'): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing Kernel headers', + 5 * 60 *1000, '/usr/bin/apt-get', + ['/usr/bin/apt-get', 'install', '-y', + 'linux-headers-generic'], + False, True); + if not fRc: + reporter.error('Error installing Kernel headers'); + else: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing Guest Additions depdendencies', + 5 * 60 *1000, '/usr/bin/apt-get', + ['/usr/bin/apt-get', 'install', '-y', 'build-essential', + 'perl'], False, True); + if not fRc: + reporter.error('Error installing additional installer dependencies'); + elif oVM.OSTypeId.startswith('OL') \ + or oVM.OSTypeId.startswith('Oracle') \ + or oVM.OSTypeId.startswith('RHEL') \ + or oVM.OSTypeId.startswith('Redhat') \ + or oVM.OSTypeId.startswith('Cent'): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing Kernel headers', + 5 * 60 *1000, '/usr/bin/yum', + ['/usr/bin/yum', '-y', 'install', 'kernel-headers'], + False, True); + if not fRc: + reporter.error('Error installing Kernel headers'); + else: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing Guest Additions depdendencies', + 5 * 60 *1000, '/usr/bin/yum', + ['/usr/bin/yum', '-y', 'install', 'make', 'automake', 'gcc', + 'kernel-devel', 'dkms', 'bzip2', 'perl'], False, True); + if not fRc: + reporter.error('Error installing additional installer dependencies'); + else: + reporter.error('Installing Linux Additions for the "%s" is not supported yet' % oVM.OSTypeId); + fRc = False; + if fRc: + # + # The actual install. + # Also tell the installer to produce the appropriate log files. + # + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing guest additions', + 10 * 60 *1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/sh', + '/media/cdrom/VBoxLinuxAdditions.run'], + False, True); + if fRc: + # Due to the GA updates as separate process the above function returns before + # the actual installation finished. So just wait until the GA installed + fRc = self.closeSession(oGuestSession); + if fRc: + (fRc, oGuestSession) = self.waitVmIsReady(oSession, False); + # Download log files. + # Ignore errors as all files above might not be present for whatever reason. + # + if fRc: + asLogFile = []; + asLogFile.append('/var/log/vboxadd-install.log'); + self.downloadFiles(oGuestSession, asLogFile, fIgnoreErrors = True); + else: + reporter.error('Installing guest additions failed: Error occured during vbox installer execution') + if fRc: + (fRc, oGuestSession) = self.rebootVMAndCheckReady(oSession, oGuestSession); + if not fRc: + reporter.error('Reboot after installing GuestAdditions failed'); + reporter.testDone(); + return (fRc, oGuestSession); + + def installVirtualBox(self, oGuestSession): + """ + Install VirtualBox in the guest. + """ + reporter.testStart('Install Virtualbox into the guest VM'); + sTestBuild = self._findFile(self.sVBoxInstaller, self.asTestBuildDirs); + reporter.log("Virtualbox install file: %s" % os.path.basename(sTestBuild)); + fRc = sTestBuild is not None; + if fRc: + fRc = self.uploadFile(oGuestSession, sTestBuild, + '/tmp/' + os.path.basename(sTestBuild)); + else: + reporter.error("VirtualBox install package is not defined"); + + if not fRc: + reporter.error('Upload the vbox installer into guest VM failed'); + else: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, + 'Allowing execution for the vbox installer', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/chmod', '755', + '/tmp/' + os.path.basename(sTestBuild)], + False, True); + if not fRc: + reporter.error('Allowing execution for the vbox installer failed'); + if fRc: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing VBox', + 240 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', + '/tmp/' + os.path.basename(sTestBuild),], + False, True); + if not fRc: + reporter.error('Installing VBox failed'); + reporter.testDone(); + return fRc; + + def configureAutostart(self, oGuestSession, sDefaultPolicy = 'allow', asUserAllow = (), asUserDeny = ()): + """ + Configures the autostart feature in the guest. + """ + reporter.testStart('Configure autostart'); + # Create autostart database directory writeable for everyone + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Creating autostart database', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/mkdir', '-m', '1777', '/etc/vbox/autostart.d'], + False, True); + if not fRc: + reporter.error('Creating autostart database failed'); + # Create /etc/default/virtualbox + if fRc: + sVBoxCfg = 'VBOXAUTOSTART_CONFIG=/etc/vbox/autostart.cfg\n' \ + + 'VBOXAUTOSTART_DB=/etc/vbox/autostart.d\n'; + fRc = self.uploadString(oGuestSession, sVBoxCfg, '/tmp/virtualbox'); + if not fRc: + reporter.error('Upload to /tmp/virtualbox failed'); + if fRc: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Moving to destination', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/mv', '/tmp/virtualbox', + '/etc/default/virtualbox'], + False, True); + if not fRc: + reporter.error('Moving the /tmp/virtualbox to destination failed'); + if fRc: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Setting permissions', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/chmod', '644', + '/etc/default/virtualbox'], + False, True); + if not fRc: + reporter.error('Setting permissions for the virtualbox failed'); + if fRc: + sVBoxCfg = self._createAutostartCfg(sDefaultPolicy, asUserAllow, asUserDeny); + fRc = self.uploadString(oGuestSession, sVBoxCfg, '/tmp/autostart.cfg'); + if not fRc: + reporter.error('Upload to /tmp/autostart.cfg failed'); + if fRc: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Moving to destination', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/mv', '/tmp/autostart.cfg', + '/etc/vbox/autostart.cfg'], + False, True); + if not fRc: + reporter.error('Moving the /tmp/autostart.cfg to destination failed'); + if fRc: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Setting permissions', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/chmod', '644', + '/etc/vbox/autostart.cfg'], + False, True); + if not fRc: + reporter.error('Setting permissions for the autostart.cfg failed'); + reporter.testDone(); + return fRc; + + def createUser(self, oGuestSession, sUser): + """ + Create a new user with the given name + """ + reporter.testStart('Create user %s' % sUser); + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Creating new user', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/usr/sbin/useradd', '-m', '-U', + sUser], False, True); + if not fRc: + reporter.error('Create user %s failed' % sUser); + reporter.testDone(); + return fRc; + + # pylint: enable=too-many-arguments + def createTestVM(self, oSession, oGuestSession, sUser, sVmName): + """ + Create a test VM in the guest and enable autostart. + Due to the sUser is created whithout password, + all calls will be perfomed using 'sudo -u sUser' + """ + _ = oSession; + reporter.testStart('Create test VM for user %s' % sUser); + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Configuring autostart database', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '-u', sUser, '-H', '/opt/VirtualBox/VBoxManage', + 'setproperty', 'autostartdbpath', '/etc/vbox/autostart.d'], + False, True); + if not fRc: + reporter.error('Configuring autostart database failed'); + else: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Create VM ' + sVmName, + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '-u', sUser, '-H', + '/opt/VirtualBox/VBoxManage', 'createvm', + '--name', sVmName, '--register'], False, True); + if not fRc: + reporter.error('Create VM %s failed' % sVmName); + if fRc: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Enabling autostart for test VM', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '-u', sUser, '-H', + '/opt/VirtualBox/VBoxManage', 'modifyvm', + sVmName, '--autostart-enabled', 'on'], False, True); + if not fRc: + reporter.error('Enabling autostart for %s failed' % sVmName); + reporter.testDone(); + return fRc; + + def checkForRunningVM(self, oSession, oGuestSession, sUser, sVmName): + """ + Check for VM running in the guest after autostart. + Due to the sUser is created whithout password, + all calls will be perfomed using 'sudo -u sUser' + """ + self.oTstDrv.sleep(30); + _ = oSession; + reporter.testStart('Check the VM %s is running for user %s' % (sVmName, sUser)); + (fRc, _, _, aBuf) = self.guestProcessExecute(oGuestSession, 'Check for running VM', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '-u', sUser, '-H', + '/opt/VirtualBox/VBoxManage', + 'list', 'runningvms'], True, True); + if not fRc: + reporter.error('Checking the VM %s is running for user %s failed' % (sVmName, sUser)); + else: + bufWrapper = VBoxManageStdOutWrapper(); + bufWrapper.write(aBuf); + fRc = bufWrapper.sVmRunning == sVmName; + reporter.testDone(); + return fRc; + +class tdAutostartOsDarwin(tdAutostartOs): + """ + Autostart support methods for Darwin guests. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None): + tdAutostartOs.__init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type, cMbRam, \ + cCpus, fPae, sGuestAdditionsIso); + raise base.GenError('Testing the autostart functionality for Darwin is not implemented'); + +class tdAutostartOsSolaris(tdAutostartOs): + """ + Autostart support methods for Solaris guests. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None): + tdAutostartOs.__init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type, cMbRam, \ + cCpus, fPae, sGuestAdditionsIso); + raise base.GenError('Testing the autostart functionality for Solaris is not implemented'); + +class tdAutostartOsWin(tdAutostartOs): + """ + Autostart support methods for Windows guests. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None): + tdAutostartOs.__init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type, cMbRam, \ + cCpus, fPae, sGuestAdditionsIso); + try: self.sVBoxInstaller = '^VirtualBox-.*\\.(exe|msi)$'; + except: pass; + return; + + def _checkVmIsReady(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Start a guest process', + 30 * 1000, 'C:\\Windows\\System32\\ipconfig.exe', + ['C:\\Windows\\System32\\ipconfig.exe',], + False, False); + return fRc; + + def _rebootVM(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Reboot the VM', + 30 * 1000, 'C:\\Windows\\System32\\shutdown.exe', + ['C:\\Windows\\System32\\shutdown.exe', '/f', + '/r', '/t', '0'], + False, True); + if not fRc: + reporter.error('Calling the shutdown utility failed'); + return fRc; + + def _powerDownVM(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Power down the VM', + 30 * 1000, 'C:\\Windows\\System32\\shutdown.exe', + ['C:\\Windows\\System32\\shutdown.exe', '/f', + '/s', '/t', '0'], + False, True); + if not fRc: + reporter.error('Calling the shutdown utility failed'); + return fRc; + + def installAdditions(self, oSession, oGuestSession, oVM): + """ + Installs the Windows guest additions using the test execution service. + """ + _ = oVM; + reporter.testStart('Install Guest Additions'); + asLogFiles = []; + fRc = self.closeSession(oGuestSession, True); # pychecker hack. + try: + oCurProgress = oSession.o.console.guest.updateGuestAdditions(self.sGuestAdditionsIso, ['/l',], None); + except: + reporter.maybeErrXcpt(True, 'Updating Guest Additions exception for sSrc="%s":' + % (self.sGuestAdditionsIso,)); + fRc = False; + else: + if oCurProgress is not None: + oWrapperProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, + self.oTstDrv, "installAdditions"); + oWrapperProgress.wait(cMsTimeout = 10 * 60 * 1000); + if not oWrapperProgress.isSuccess(): + oWrapperProgress.logResult(fIgnoreErrors = False); + fRc = False; + else: + fRc = reporter.error('No progress object returned'); + #--------------------------------------- + # + ## + ## Install the public signing key. + ## + # + #self.oTstDrv.sleep(60 * 2); + # + #if oVM.OSTypeId not in ('WindowsNT4', 'Windows2000', 'WindowsXP', 'Windows2003'): + # (fRc, _, _, _) = \ + # self.guestProcessExecute(oGuestSession, 'Installing SHA1 certificate', + # 60 * 1000, 'D:\\cert\\VBoxCertUtil.exe', + # ['D:\\cert\\VBoxCertUtil.exe', 'add-trusted-publisher', + # 'D:\\cert\\vbox-sha1.cer'], + # False, True); + # if not fRc: + # reporter.error('Error installing SHA1 certificate'); + # else: + # (fRc, _, _, _) = \ + # self.guestProcessExecute(oGuestSession, 'Installing SHA1 certificate', + # 60 * 1000, 'D:\\cert\\VBoxCertUtil.exe', + # ['D:\\cert\\VBoxCertUtil.exe', 'add-trusted-publisher', + # 'D:\\cert\\vbox-sha256.cer'], + # False, True); + # if not fRc: + # reporter.error('Error installing SHA256 certificate'); + # + #(fRc, _, _, _) = \ + # self.guestProcessExecute(oGuestSession, 'Installing GA', + # 60 * 1000, 'D:\\VBoxWindowsAdditions.exe', + # ['D:\\VBoxWindowsAdditions.exe', '/S', '/l', + # '/no_vboxservice_exit'], + # False, True); + # + #if fRc: + # # Due to the GA updates as separate process the above function returns before + # # the actual installation finished. So just wait until the GA installed + # fRc = self.closeSession(oGuestSession, True); + # if fRc: + # (fRc, oGuestSession) = self.waitVmIsReady(oSession, False, False); + #--------------------------------------- + # Store the result and try download logs anyway. + fGaRc = fRc; + fRc, oGuestSession = self.createSession(oSession, 'Session for user: vbox', + 'vbox', 'password', 10 * 1000, True); + if fRc is True: + (fRc, oGuestSession) = self.rebootVMAndCheckReady(oSession, oGuestSession); + if fRc is True: + # Add the Windows Guest Additions installer files to the files we want to download + # from the guest. + sGuestAddsDir = 'C:/Program Files/Oracle/VirtualBox Guest Additions/'; + asLogFiles.append(sGuestAddsDir + 'install.log'); + # Note: There won't be a install_ui.log because of the silent installation. + asLogFiles.append(sGuestAddsDir + 'install_drivers.log'); + # Download log files. + # Ignore errors as all files above might not be present (or in different locations) + # on different Windows guests. + # + self.downloadFiles(oGuestSession, asLogFiles, fIgnoreErrors = True); + else: + reporter.error('Reboot after installing GuestAdditions failed'); + else: + reporter.error('Create session for user vbox after GA updating failed'); + reporter.testDone(); + return (fRc and fGaRc, oGuestSession); + + def installVirtualBox(self, oGuestSession): + """ + Install VirtualBox in the guest. + """ + reporter.testStart('Install Virtualbox into the guest VM'); + # Used windows image already contains the C:\Temp + sTestBuild = self._findFile(self.sVBoxInstaller, self.asTestBuildDirs); + reporter.log("Virtualbox install file: %s" % os.path.basename(sTestBuild)); + fRc = sTestBuild is not None; + if fRc: + fRc = self.uploadFile(oGuestSession, sTestBuild, + 'C:\\Temp\\' + os.path.basename(sTestBuild)); + else: + reporter.error("VirtualBox install package is not defined"); + + if not fRc: + reporter.error('Upload the installing into guest VM failed'); + else: + if sTestBuild.endswith('.msi'): + sLogFile = 'C:/Temp/VBoxInstallLog.txt'; + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing VBox', + 600 * 1000, 'C:\\Windows\\System32\\msiexec.exe', + ['msiexec', '/quiet', '/norestart', '/i', + 'C:\\Temp\\' + os.path.basename(sTestBuild), + '/lv', sLogFile], + False, True); + if not fRc: + reporter.error('Installing the VBox from msi installer failed'); + else: + sLogFile = 'C:/Temp/Virtualbox/VBoxInstallLog.txt'; + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing VBox', + 600 * 1000, 'C:\\Temp\\' + os.path.basename(sTestBuild), + ['C:\\Temp\\' + os.path.basename(sTestBuild), '-vvvv', + '--silent', '--logging', + '--msiparams', 'REBOOT=ReallySuppress'], + False, True); + if not fRc: + reporter.error('Installing the VBox failed'); + else: + (_, _, _, aBuf) = self.guestProcessExecute(oGuestSession, 'Check installation', + 240 * 1000, 'C:\\Windows\\System32\\cmd.exe', + ['c:\\Windows\\System32\\cmd.exe', '/c', + 'dir', 'C:\\Program Files\\Oracle\\VirtualBox\\*.*'], + True, True); + reporter.log('Content of VirtualBxox folder:'); + reporter.log(str(aBuf)); + asLogFiles = [sLogFile,]; + self.downloadFiles(oGuestSession, asLogFiles, fIgnoreErrors = True); + reporter.testDone(); + return fRc; + + def configureAutostart(self, oGuestSession, sDefaultPolicy = 'allow', asUserAllow = (), asUserDeny = ()): + """ + Configures the autostart feature in the guest. + """ + reporter.testStart('Configure autostart'); + # Create autostart database directory writeable for everyone + (fRc, _, _, _) = \ + self.guestProcessExecute(oGuestSession, 'Setting the autostart environment variable', + 30 * 1000, 'C:\\Windows\\System32\\reg.exe', + ['reg', 'add', + 'HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', + '/v', 'VBOXAUTOSTART_CONFIG', '/d', + 'C:\\ProgramData\\autostart.cfg', '/f'], + False, True); + if fRc: + sVBoxCfg = self._createAutostartCfg(sDefaultPolicy, asUserAllow, asUserDeny); + fRc = self.uploadString(oGuestSession, sVBoxCfg, 'C:\\ProgramData\\autostart.cfg'); + if not fRc: + reporter.error('Upload the autostart.cfg failed'); + else: + reporter.error('Setting the autostart environment variable failed'); + reporter.testDone(); + return fRc; + + def createTestVM(self, oSession, oGuestSession, sUser, sVmName): + """ + Create a test VM in the guest and enable autostart. + """ + _ = oGuestSession; + reporter.testStart('Create test VM for user %s' % sUser); + fRc, oGuestSession = self.createSession(oSession, 'Session for user: %s' % (sUser,), + sUser, 'password', 10 * 1000, True); + if not fRc: + reporter.error('Create session for user %s failed' % sUser); + else: + (fRc, _, _, _) = \ + self.guestProcessExecute(oGuestSession, 'Create VM ' + sVmName, + 30 * 1000, 'C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe', + ['C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe', 'createvm', + '--name', sVmName, '--register'], False, True); + if not fRc: + reporter.error('Create VM %s for user %s failed' % (sVmName, sUser)); + else: + (fRc, _, _, _) = \ + self.guestProcessExecute(oGuestSession, 'Enabling autostart for test VM', + 30 * 1000, 'C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe', + ['C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe', + 'modifyvm', sVmName, '--autostart-enabled', 'on'], False, True); + if not fRc: + reporter.error('Enabling autostart for VM %s for user %s failed' % (sVmName, sUser)); + if fRc: + fRc = self.uploadString(oGuestSession, 'password', 'C:\\ProgramData\\password.cfg'); + if not fRc: + reporter.error('Upload the password.cfg failed'); + if fRc: + (fRc, _, _, _) = \ + self.guestProcessExecute(oGuestSession, 'Install autostart service for the user', + 30 * 1000, 'C:\\Program Files\\Oracle\\VirtualBox\\VBoxAutostartSvc.exe', + ['C:\\Program Files\\Oracle\\VirtualBox\\VBoxAutostartSvc.exe', + 'install', '--user=' + sUser, + '--password-file=C:\\ProgramData\\password.cfg'], + False, True); + if not fRc: + reporter.error('Install autostart service for user %s failed' % sUser); + fRc1 = self.closeSession(oGuestSession, True); + if not fRc1: + reporter.error('Closing session for user %s failed' % sUser); + fRc = fRc1 and fRc and True; # pychecker hack. + reporter.testDone(); + return fRc; + + def checkForRunningVM(self, oSession, oGuestSession, sUser, sVmName): + """ + Check for VM running in the guest after autostart. + """ + self.oTstDrv.sleep(30); + _ = oGuestSession; + reporter.testStart('Check the VM %s is running for user %s' % (sVmName, sUser)); + fRc, oGuestSession = self.createSession(oSession, 'Session for user: %s' % (sUser,), + sUser, 'password', 10 * 1000, True); + if not fRc: + reporter.error('Create session for user %s failed' % sUser); + else: + (fRc, _, _, aBuf) = self.guestProcessExecute(oGuestSession, 'Check for running VM', + 60 * 1000, 'C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe', + [ 'C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe', + 'list', 'runningvms' ], True, True); + if not fRc: + reporter.error('Checking the VM %s is running for user %s failed' % (sVmName, sUser)); + else: + bufWrapper = VBoxManageStdOutWrapper(); + bufWrapper.write(aBuf); + fRc = bufWrapper.sVmRunning == sVmName; + fRc1 = self.closeSession(oGuestSession, True); + if not fRc1: + reporter.error('Closing session for user %s failed' % sUser); + fRc = fRc1 and fRc and True; # pychecker hack. + reporter.testDone(); + return fRc; + + def createUser(self, oGuestSession, sUser): + """ + Create a new user with the given name + """ + reporter.testStart('Create user %s' % sUser); + # Create user + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Creating user %s to run a VM' % sUser, + 30 * 1000, 'C:\\Windows\\System32\\net.exe', + ['net', 'user', sUser, 'password', '/add' ], False, True); + if not fRc: + reporter.error('Creating user %s to run a VM failed' % sUser); + # Add the user to Administrators group + else: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Adding the user %s to Administrators group' % sUser, + 30 * 1000, 'C:\\Windows\\System32\\net.exe', + ['net', 'localgroup', 'Administrators', sUser, '/add' ], False, True); + if not fRc: + reporter.error('Adding the user %s to Administrators group failed' % sUser); + #Allow the user to logon as service + if fRc: + sSecPolicyEditor = """ +$sUser = '%s' +$oUser = New-Object System.Security.Principal.NTAccount("$sUser") +$oSID = $oUser.Translate([System.Security.Principal.SecurityIdentifier]) +$sExportFile = 'C:\\Temp\\cfg.inf' +$sSecDb = 'C:\\Temp\\secedt.sdb' +$sImportFile = 'C:\\Temp\\newcfg.inf' +secedit /export /cfg $sExportFile +$sCurrServiceLogonRight = Get-Content -Path $sExportFile | + Where-Object {$_ -Match 'SeServiceLogonRight'} +$asFileContent = @' +[Unicode] +Unicode=yes +[System Access] +[Event Audit] +[Registry Values] +[Version] +signature="$CHICAGO$" +Revision=1 +[Profile Description] +Description=GrantLogOnAsAService security template +[Privilege Rights] +{0}*{1} +'@ -f $( + if($sCurrServiceLogonRight){"$sCurrServiceLogonRight,"} + else{'SeServiceLogonRight = '} + ), $oSid.Value +Set-Content -Path $sImportFile -Value $asFileContent +secedit /import /db $sSecDb /cfg $sImportFile +secedit /configure /db $sSecDb +Remove-Item -Path $sExportFile +Remove-Item -Path $sSecDb +Remove-Item -Path $sImportFile + """ % (sUser,); + fRc = self.uploadString(oGuestSession, sSecPolicyEditor, 'C:\\Temp\\adjustsec.ps1'); + if not fRc: + reporter.error('Upload the adjustsec.ps1 failed'); + if fRc: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, + 'Setting the "Logon as service" policy to the user %s' % sUser, + 300 * 1000, 'C:\\Windows\\System32\\cmd.exe', + ['cmd.exe', '/c', "type C:\\Temp\\adjustsec.ps1 | powershell -"], + False, True); + if not fRc: + reporter.error('Setting the "Logon as service" policy to the user %s failed' % sUser); + try: + oGuestSession.fsObjRemove('C:\\Temp\\adjustsec.ps1'); + except: + fRc = reporter.errorXcpt('Removing policy script failed'); + reporter.testDone(); + return fRc; + +class tdAutostart(vbox.TestDriver): # pylint: disable=too-many-instance-attributes + """ + Autostart testcase. + """ + ksOsLinux = 'tst-linux' + ksOsWindows = 'tst-win' + ksOsDarwin = 'tst-darwin' + ksOsSolaris = 'tst-solaris' + ksOsFreeBSD = 'tst-freebsd' + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.asTestVMsDef = [self.ksOsWindows, self.ksOsLinux]; #[self.ksOsLinux, self.ksOsWindows]; + self.asTestVMs = self.asTestVMsDef; + self.asSkipVMs = []; + ## @todo r=bird: The --test-build-dirs option as primary way to get the installation files to test + ## is not an acceptable test practice as we don't know wtf you're testing. See defect for more. + self.asTestBuildDirs = [os.path.join(self.sScratchPath, 'bin'),]; + self.sGuestAdditionsIso = None; #'D:/AlexD/TestBox/TestAdditionalFiles/VBoxGuestAdditions_6.1.2.iso'; + oSet = vboxtestvms.TestVmSet(self.oTestVmManager, acCpus = [2], asVirtModes = ['hwvirt-np',], fIgnoreSkippedVm = True); + # pylint: disable=line-too-long + self.asTestVmClasses = { + 'win' : tdAutostartOsWin(oSet, self, self.ksOsWindows, 'Windows7_64', \ + '6.0/windows7piglit/windows7piglit.vdi', eNic0Type = None, cMbRam = 2048, \ + cCpus = 2, fPae = True, sGuestAdditionsIso = self.getGuestAdditionsIso()), + 'linux' : tdAutostartOsLinux(oSet, self, self.ksOsLinux, 'Ubuntu_64', \ + '6.0/ub1804piglit/ub1804piglit.vdi', eNic0Type = None, \ + cMbRam = 2048, cCpus = 2, fPae = None, sGuestAdditionsIso = self.getGuestAdditionsIso()), + 'solaris' : None, #'tdAutostartOsSolaris', + 'darwin' : None #'tdAutostartOsDarwin' + }; + oSet.aoTestVms.extend([oTestVm for oTestVm in self.asTestVmClasses.values() if oTestVm is not None]); + sOs = self.getBuildOs(); + if sOs in self.asTestVmClasses: + for oTestVM in oSet.aoTestVms: + if oTestVM is not None: + oTestVM.fSkip = oTestVM != self.asTestVmClasses[sOs]; + + # pylint: enable=line-too-long + self.oTestVmSet = oSet; + + # + # Overridden methods. + # + + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdAutostart Options:'); + reporter.log(' --test-build-dirs <path1[,path2[,...]]>'); + reporter.log(' The list of directories with VirtualBox distros. Overrides default path.'); + reporter.log(' Default path is $TESTBOX_SCRATCH_PATH/bin.'); + reporter.log(' --vbox-<os>-build <path>'); + reporter.log(' The path to vbox build for the specified OS.'); + reporter.log(' The OS can be one of "win", "linux", "solaris" and "darwin".'); + reporter.log(' This option alse enables corresponding VM for testing.'); + reporter.log(' (Default behaviour is testing only VM having host-like OS.)'); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--test-build-dirs': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--test-build-dirs" takes a path argument'); + self.asTestBuildDirs = asArgs[iArg].split(','); + for oTestVm in self.oTestVmSet.aoTestVms: + oTestVm.asTestBuildDirs = self.asTestBuildDirs; + elif asArgs[iArg] in [ '--vbox-%s-build' % sKey for sKey in self.asTestVmClasses]: + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "%s" take a path argument' % (asArgs[iArg - 1],)); + oMatch = re.match("--vbox-([^-]+)-build", asArgs[iArg - 1]); + if oMatch is not None: + sOs = oMatch.group(1); + oTestVm = self.asTestVmClasses.get(sOs); + if oTestVm is not None: + oTestVm.sTestBuild = asArgs[iArg]; + oTestVm.fSkip = False; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def actionConfig(self): + if not self.importVBoxApi(): # So we can use the constant below. + return False; + return self.oTestVmSet.actionConfig(self); + + def actionExecute(self): + """ + Execute the testcase. + """ + return self.oTestVmSet.actionExecute(self, self.testAutostartOneCfg) + + # + # Test execution helpers. + # + def testAutostartOneCfg(self, oVM, oTestVm): + # Reconfigure the VM + fRc = True; + self.logVmInfo(oVM); + oSession = self.startVmByName(oTestVm.sVmName); + if oSession is not None: + sTestUserAllow = 'test1'; + sTestUserDeny = 'test2'; + sTestVmName = 'TestVM'; + #wait the VM is ready after starting + (fRc, oGuestSession) = oTestVm.waitVmIsReady(oSession, True); + #install fresh guest additions + if fRc: + (fRc, oGuestSession) = oTestVm.installAdditions(oSession, oGuestSession, oVM); + # Create two new users + fRc = fRc and oTestVm.createUser(oGuestSession, sTestUserAllow); + fRc = fRc and oTestVm.createUser(oGuestSession, sTestUserDeny); + if fRc is True: + # Install VBox first + fRc = oTestVm.installVirtualBox(oGuestSession); + if fRc is True: + fRc = oTestVm.configureAutostart(oGuestSession, 'allow', (sTestUserAllow,), (sTestUserDeny,)); + if fRc is True: + # Create a VM with autostart enabled in the guest for both users + fRc = oTestVm.createTestVM(oSession, oGuestSession, sTestUserAllow, sTestVmName); + fRc = fRc and oTestVm.createTestVM(oSession, oGuestSession, sTestUserDeny, sTestVmName); + if fRc is True: + # Reboot the guest + (fRc, oGuestSession) = oTestVm.rebootVMAndCheckReady(oSession, oGuestSession); + if fRc is True: + # Fudge factor - Allow the guest VMs to finish starting up. + self.sleep(60); + fRc = oTestVm.checkForRunningVM(oSession, oGuestSession, sTestUserAllow, sTestVmName); + if fRc is True: + fRc = oTestVm.checkForRunningVM(oSession, oGuestSession, sTestUserDeny, sTestVmName); + if fRc is True: + reporter.error('Test VM is running inside the guest for denied user'); + fRc = not fRc; + else: + reporter.error('Test VM is not running inside the guest for allowed user'); + else: + reporter.error('Rebooting the guest failed'); + else: + reporter.error('Creating test VM failed'); + else: + reporter.error('Configuring autostart in the guest failed'); + else: + reporter.error('Installing VirtualBox in the guest failed'); + else: + reporter.error('Creating test users failed'); + if oGuestSession is not None: + try: oTestVm.powerDownVM(oGuestSession); + except: pass; + try: self.terminateVmBySession(oSession); + except: pass; + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + return fRc; + +if __name__ == '__main__': + sys.exit(tdAutostart().main(sys.argv)); diff --git a/src/VBox/ValidationKit/tests/benchmarks/Makefile.kmk b/src/VBox/ValidationKit/tests/benchmarks/Makefile.kmk new file mode 100644 index 00000000..7d82731f --- /dev/null +++ b/src/VBox/ValidationKit/tests/benchmarks/Makefile.kmk @@ -0,0 +1,52 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Benchmarks. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +INSTALLS += ValidationKitTestsBenchmarks +ValidationKitTestsBenchmarks_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsBenchmarks_INST = $(INST_VALIDATIONKIT)tests/benchmarks/ +ValidationKitTestsBenchmarks_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdBenchmark1.py \ + $(PATH_SUB_CURRENT)/tdBenchmark2.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsBenchmarks_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/benchmarks/tdBenchmark1.py b/src/VBox/ValidationKit/tests/benchmarks/tdBenchmark1.py new file mode 100755 index 00000000..448d502b --- /dev/null +++ b/src/VBox/ValidationKit/tests/benchmarks/tdBenchmark1.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdBenchmark1.py $ + +""" +VirtualBox Validation Kit - Test that runs various benchmarks. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os; +import sys; + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testdriver import reporter; +from testdriver import vbox; +from testdriver import vboxtestvms; + + +class tdBenchmark1(vbox.TestDriver): + """ + Benchmark #1. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + oTestVm = vboxtestvms.BootSectorTestVm(self.oTestVmSet, 'tst-bs-test1', + os.path.join(self.sVBoxBootSectors, 'bootsector2-test1.img')); + self.oTestVmSet.aoTestVms.append(oTestVm); + + + # + # Overridden methods. + # + + + def actionConfig(self): + self._detectValidationKit(); + return self.oTestVmSet.actionConfig(self); + + def actionExecute(self): + return self.oTestVmSet.actionExecute(self, self.testOneCfg); + + + + # + # Test execution helpers. + # + + def testOneCfg(self, oVM, oTestVm): + """ + Runs the specified VM thru the tests. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + fRc = False; + + sXmlFile = self.prepareResultFile(); + asEnv = [ 'IPRT_TEST_FILE=' + sXmlFile]; + + self.logVmInfo(oVM); + oSession = self.startVm(oVM, sName = oTestVm.sVmName, asEnv = asEnv); + if oSession is not None: + cMsTimeout = 15*60*1000; + if not reporter.isLocal(): ## @todo need to figure a better way of handling timeouts on the testboxes ... + cMsTimeout = self.adjustTimeoutMs(180 * 60000); + + oRc = self.waitForTasks(cMsTimeout); + if oRc == oSession: + fRc = oSession.assertPoweredOff(); + else: + reporter.error('oRc=%s, expected %s' % (oRc, oSession)); + + reporter.addSubXmlFile(sXmlFile); + self.terminateVmBySession(oSession); + return fRc; + + + +if __name__ == '__main__': + sys.exit(tdBenchmark1().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/benchmarks/tdBenchmark2.py b/src/VBox/ValidationKit/tests/benchmarks/tdBenchmark2.py new file mode 100755 index 00000000..26ca308c --- /dev/null +++ b/src/VBox/ValidationKit/tests/benchmarks/tdBenchmark2.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdBenchmark2.py $ + +""" +VirtualBox Validation Kit - Test that runs various benchmarks. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os; +import sys; + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testdriver import reporter; +from testdriver import vbox; +from testdriver import vboxcon; +from testdriver import vboxtestvms; + + +class tdBenchmark2(vbox.TestDriver): + """ + Benchmark #2 - Memory. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + oTestVm = vboxtestvms.BootSectorTestVm(self.oTestVmSet, 'tst-bs-memalloc-1', + os.path.join(self.sVBoxBootSectors, 'bs3-memalloc-1.img')); + self.oTestVmSet.aoTestVms.append(oTestVm); + + + # + # Overridden methods. + # + + + def actionConfig(self): + self._detectValidationKit(); + return self.oTestVmSet.actionConfig(self); + + def actionExecute(self): + return self.oTestVmSet.actionExecute(self, self.testOneCfg); + + + + # + # Test execution helpers. + # + + def testOneCfg(self, oVM, oTestVm): + """ + Runs the specified VM thru the tests. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + fRc = False; + + # + # Determin the RAM configurations we want to test. + # + cMbMaxGuestRam = self.oVBox.systemProperties.maxGuestRAM; + cMbHostAvail = self.oVBox.host.memoryAvailable; + cMbHostTotal = self.oVBox.host.memorySize; + reporter.log('cMbMaxGuestRam=%s cMbHostAvail=%s cMbHostTotal=%s' % (cMbMaxGuestRam, cMbHostAvail, cMbHostTotal,)); + + cMbHostAvail -= cMbHostAvail // 7; # Rough 14% safety/overhead margin. + if cMbMaxGuestRam < cMbHostAvail: + # Currently: 2048 GiB, 1536 GiB, 1024 GiB, 512 GiB, 256 GiB, 128 GiB, 64 GiB, 32 GiB + acMbRam = [ cMbMaxGuestRam, cMbMaxGuestRam // 4 * 3, cMbMaxGuestRam // 2, cMbMaxGuestRam // 4, + cMbMaxGuestRam // 8, cMbMaxGuestRam // 16 ]; + if acMbRam[-1] > 64*1024: + acMbRam.append(64*1024); + if acMbRam[-1] > 32*1024: + acMbRam.append(32*1024); + elif cMbHostAvail > 8*1024: + # First entry is available memory rounded down to the nearest 8 GiB + cMbHostAvail = cMbHostAvail & ~(8 * 1024 - 1); + acMbRam = [ cMbHostAvail, ]; + + # The remaining entries are powers of two below that, up to 6 of these stopping at 16 GiB. + cMb = 8*1024; + while cMb < cMbHostAvail: + cMb *= 2; + while len(acMbRam) < 7 and cMb > 16 * 1024: + cMb //= 2; + acMbRam.append(cMb); + elif cMbHostAvail >= 16000 and cMbHostAvail > 7168: + # Desperate attempt at getting some darwin testruns too. We've got two + # with 16 GiB and they usually end up with just short of 8GiB of free RAM. + acMbRam = [7168,]; + else: + reporter.log("Less than 8GB of host RAM available for VMs, skipping test"); + return None; + reporter.log("RAM configurations: %s" % (acMbRam)); + + # Large pages only work with nested paging. + afLargePages = [False, ]; + try: + if oVM.getHWVirtExProperty(vboxcon.HWVirtExPropertyType_NestedPaging): + afLargePages = [True, False]; + except: + return reporter.errorXcpt("Failed to get HWVirtExPropertyType_NestedPaging"); + + # + # Test the RAM configurations. + # + for fLargePages in afLargePages: + sLargePages = 'large pages' if fLargePages is True else 'no large pages'; + for cMbRam in acMbRam: + reporter.testStart('%s MiB, %s' % (cMbRam, sLargePages)); + + # Reconfigure the VM: + fRc = False + oSession = self.openSession(oVM); + if oSession: + fRc = oSession.setRamSize(cMbRam); + fRc = oSession.setLargePages(fLargePages) and fRc; + if fRc: + fRc = oSession.saveSettings(); + if not fRc: + oSession.discardSettings(True); + oSession.close(); + if fRc: + # Set up the result file + sXmlFile = self.prepareResultFile(); + asEnv = [ 'IPRT_TEST_FILE=' + sXmlFile, ]; + + # Do the test: + self.logVmInfo(oVM); + oSession = self.startVm(oVM, sName = oTestVm.sVmName, asEnv = asEnv); + if oSession is not None: + cMsTimeout = 15 * 60000 + cMbRam // 168; + if not reporter.isLocal(): ## @todo need to figure a better way of handling timeouts on the testboxes ... + cMsTimeout = self.adjustTimeoutMs(180 * 60000 + cMbRam // 168); + + oRc = self.waitForTasks(cMsTimeout); + if oRc == oSession: + fRc = oSession.assertPoweredOff(); + else: + reporter.error('oRc=%s, expected %s' % (oRc, oSession)); + + reporter.addSubXmlFile(sXmlFile); + self.terminateVmBySession(oSession); + else: + reporter.errorXcpt("Failed to set memory size to %s MiB or setting largePages to %s" % (cMbRam, fLargePages)); + reporter.testDone(); + + return fRc; + + + +if __name__ == '__main__': + sys.exit(tdBenchmark2().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/cpu/Makefile.kmk b/src/VBox/ValidationKit/tests/cpu/Makefile.kmk new file mode 100644 index 00000000..be190df6 --- /dev/null +++ b/src/VBox/ValidationKit/tests/cpu/Makefile.kmk @@ -0,0 +1,51 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - CPU Tests. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +INSTALLS += ValidationKitTestsCpu +ValidationKitTestsCpu_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsCpu_INST = $(INST_VALIDATIONKIT)tests/cpu/ +ValidationKitTestsCpu_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdCpuPae1.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsCpu_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/cpu/tdCpuPae1.py b/src/VBox/ValidationKit/tests/cpu/tdCpuPae1.py new file mode 100755 index 00000000..6a1a1f89 --- /dev/null +++ b/src/VBox/ValidationKit/tests/cpu/tdCpuPae1.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdCpuPae1.py $ + +""" +VirtualBox Validation Kit - Catch PAE not enabled. + +Test that switching into PAE mode when it isn't enable, check that it produces +the right runtime error. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os; +import sys; + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testdriver import reporter; +from testdriver import base; +from testdriver import vbox; + + +class tdCpuPae1ConsoleCallbacks(vbox.ConsoleEventHandlerBase): + """ + For catching the PAE runtime error. + """ + def __init__(self, dArgs): + oTstDrv = dArgs['oTstDrv']; + oVBoxMgr = dArgs['oVBoxMgr']; _ = oVBoxMgr; + + vbox.ConsoleEventHandlerBase.__init__(self, dArgs, 'tdCpuPae1'); + self.oTstDrv = oTstDrv; + + def onRuntimeError(self, fFatal, sErrId, sMessage): + """ Verify the error. """ + reporter.log('onRuntimeError: fFatal=%s sErrId="%s" sMessage="%s"' % (fFatal, sErrId, sMessage)); + if sErrId != 'PAEmode': + reporter.testFailure('sErrId=%s, expected PAEmode' % (sErrId,)); + elif fFatal is not True: + reporter.testFailure('fFatal=%s, expected True' % (fFatal,)); + else: + self.oTstDrv.fCallbackSuccess = True; + self.oTstDrv.fCallbackFired = True; + self.oVBoxMgr.interruptWaitEvents(); + return None; + + +class tdCpuPae1(vbox.TestDriver): + """ + PAE Test #1. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asSkipTests = []; + self.asVirtModesDef = ['hwvirt', 'hwvirt-np', 'raw',] + self.asVirtModes = self.asVirtModesDef + self.acCpusDef = [1, 2,] + self.acCpus = self.acCpusDef; + self.fCallbackFired = False; + self.fCallbackSuccess = False; + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdCpuPae1 Options:'); + reporter.log(' --virt-modes <m1[:m2[:]]'); + reporter.log(' Default: %s' % (':'.join(self.asVirtModesDef))); + reporter.log(' --cpu-counts <c1[:c2[:]]'); + reporter.log(' Default: %s' % (':'.join(str(c) for c in self.acCpusDef))); + reporter.log(' --quick'); + reporter.log(' Shorthand for: --virt-modes raw --cpu-counts 1 32'); + return rc; + + def parseOption(self, asArgs, iArg): + if asArgs[iArg] == '--virt-modes': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--virt-modes" takes a colon separated list of modes'); + self.asVirtModes = asArgs[iArg].split(':'); + for s in self.asVirtModes: + if s not in self.asVirtModesDef: + raise base.InvalidOption('The "--virt-modes" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asVirtModesDef))); + elif asArgs[iArg] == '--cpu-counts': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--cpu-counts" takes a colon separated list of cpu counts'); + self.acCpus = []; + for s in asArgs[iArg].split(':'): + try: c = int(s); + except: raise base.InvalidOption('The "--cpu-counts" value "%s" is not an integer' % (s,)); + if c <= 0: raise base.InvalidOption('The "--cpu-counts" value "%s" is zero or negative' % (s,)); + self.acCpus.append(c); + elif asArgs[iArg] == '--quick': + self.asVirtModes = ['raw',]; + self.acCpus = [1,]; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def getResourceSet(self): + return []; + + def actionConfig(self): + # Make sure vboxapi has been imported so we can use the constants. + if not self.importVBoxApi(): + return False; + + # + # Configure a VM with the PAE bootsector as floppy image. + # + + oVM = self.createTestVM('tst-bs-pae', 2, sKind = 'Other', fVirtEx = False, fPae = False, \ + sFloppy = os.path.join(self.sVBoxBootSectors, 'bootsector-pae.img') ); + if oVM is None: + return False; + return True; + + def actionExecute(self): + """ + Execute the testcase. + """ + return self.test1(); + + + + # + # Test execution helpers. + # + + def test1OneCfg(self, oVM, cCpus, fHwVirt, fNestedPaging): + """ + Runs the specified VM thru test #1. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + + # Reconfigure the VM + fRc = True; + oSession = self.openSession(oVM); + if oSession is not None: + fRc = fRc and oSession.enableVirtEx(fHwVirt); + fRc = fRc and oSession.enableNestedPaging(fNestedPaging); + fRc = fRc and oSession.setCpuCount(cCpus); + fRc = fRc and oSession.setupBootLogo(True, 2500); # Race avoidance fudge. + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + + # Zap the state (used by the callback). + self.fCallbackFired = False; + self.fCallbackSuccess = False; + + # Start up. + if fRc is True: + self.logVmInfo(oVM); + oSession = self.startVm(oVM) + if oSession is not None: + # Set up a callback for catching the runtime error. !Races the guest bootup! + oConsoleCallbacks = oSession.registerDerivedEventHandler(tdCpuPae1ConsoleCallbacks, {'oTstDrv':self,}) + + fRc = False; + if oConsoleCallbacks is not None: + # Wait for 30 seconds for something to finish. + tsStart = base.timestampMilli(); + while base.timestampMilli() - tsStart < 30000: + oTask = self.waitForTasks(1000); + if oTask is not None: + break; + if self.fCallbackFired: + break; + if not self.fCallbackFired: + reporter.testFailure('the callback did not fire'); + fRc = self.fCallbackSuccess; + + # cleanup. + oConsoleCallbacks.unregister(); + self.terminateVmBySession(oSession) #, fRc); + else: + fRc = False; + return fRc; + + + def test1(self): + """ + Executes test #1 - Negative API testing. + + ASSUMES that the VMs are + """ + reporter.testStart('Test 1'); + oVM = self.getVmByName('tst-bs-pae'); + + for cCpus in self.acCpus: + if cCpus == 1: reporter.testStart('1 cpu'); + else: reporter.testStart('%u cpus' % (cCpus)); + + for sVirtMode in self.asVirtModes: + if sVirtMode == 'raw' and cCpus > 1: + continue; + + hsVirtModeDesc = {}; + hsVirtModeDesc['raw'] = 'Raw-mode'; + hsVirtModeDesc['hwvirt'] = 'HwVirt'; + hsVirtModeDesc['hwvirt-np'] = 'NestedPaging'; + reporter.testStart(hsVirtModeDesc[sVirtMode]); + + fHwVirt = sVirtMode != 'raw'; + fNestedPaging = sVirtMode == 'hwvirt-np'; + self.test1OneCfg(oVM, cCpus, fHwVirt, fNestedPaging); + + reporter.testDone(); + reporter.testDone(); + + return reporter.testDone()[1] == 0; + + + +if __name__ == '__main__': + sys.exit(tdCpuPae1().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/installation/Makefile.kmk b/src/VBox/ValidationKit/tests/installation/Makefile.kmk new file mode 100644 index 00000000..dcd0147b --- /dev/null +++ b/src/VBox/ValidationKit/tests/installation/Makefile.kmk @@ -0,0 +1,52 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Automatic guest OS installation tests. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +INSTALLS += ValidationKitInstallationTests +ValidationKitInstallationTests_TEMPLATE = VBoxValidationKitR3 +ValidationKitInstallationTests_INST = $(INST_VALIDATIONKIT)tests/installation/ +ValidationKitInstallationTests_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdGuestOsInstTest1.py \ + $(PATH_SUB_CURRENT)/tdGuestOsInstOs2.py \ + $(PATH_SUB_CURRENT)/tdGuestOsUnattendedInst1.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitInstallationTests_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/ValidationKit/tests/installation/tdGuestOsInstOs2.py b/src/VBox/ValidationKit/tests/installation/tdGuestOsInstOs2.py new file mode 100755 index 00000000..d1d5f22e --- /dev/null +++ b/src/VBox/ValidationKit/tests/installation/tdGuestOsInstOs2.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdGuestOsInstOs2.py $ + +""" +VirtualBox Validation Kit - OS/2 install tests. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard Python imports. +import os +import sys + + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0] +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(g_ksValidationKitDir) + +# Validation Kit imports. +from testdriver import vbox +from testdriver import base +from testdriver import reporter +from testdriver import vboxcon + + +class tdGuestOsInstOs2(vbox.TestDriver): + """ + OS/2 unattended installation. + + Scenario: + - Create new VM that corresponds specified installation ISO image + - Create HDD that corresponds to OS type that will be installed + - Set VM boot order: HDD, Floppy, ISO + - Start VM: sinse there is no OS installed on HDD, VM will booted from floppy + - After first reboot VM will continue installation from HDD automatically + - Wait for incomming TCP connection (guest should initiate such a + connection in case installation has been completed successfully) + """ + + ksSataController = 'SATA Controller' + ksIdeController = 'IDE Controller' + + # VM parameters required to run ISO image. + # Format: (cBytesHdd, sKind) + kaoVmParams = { + 'acp2-txs.iso': ( 2*1024*1024*1024, 'OS2', ksIdeController ), + 'mcp2-txs.iso': ( 2*1024*1024*1024, 'OS2', ksIdeController ), + } + + def __init__(self): + """ + Reinitialize child class instance. + """ + vbox.TestDriver.__init__(self) + + self.sVmName = 'TestVM' + self.sHddName = 'TestHdd.vdi' + self.sIso = None + self.sFloppy = None + self.sIsoPathBase = os.path.join(self.sResourcePath, '4.2', 'isos') + self.fEnableIOAPIC = True + self.cCpus = 1 + self.fEnableNestedPaging = True + self.fEnablePAE = False + self.asExtraData = [] + + # + # Overridden methods. + # + + def showUsage(self): + """ + Extend usage info + """ + rc = vbox.TestDriver.showUsage(self) + reporter.log(' --install-iso <ISO file name>') + reporter.log(' --cpus <# CPUs>') + reporter.log(' --no-ioapic') + reporter.log(' --no-nested-paging') + reporter.log(' --pae') + reporter.log(' --set-extradata <key>:value') + reporter.log(' Set VM extra data. This command line option might be used multiple times.') + return rc + + def parseOption(self, asArgs, iArg): + """ + Extend standard options set + """ + if asArgs[iArg] == '--install-iso': + iArg += 1 + if iArg >= len(asArgs): raise base.InvalidOption('The "--install-iso" option requires an argument') + self.sIso = asArgs[iArg] + elif asArgs[iArg] == '--cpus': + iArg += 1 + if iArg >= len(asArgs): raise base.InvalidOption('The "--cpus" option requires an argument') + self.cCpus = int(asArgs[iArg]) + elif asArgs[iArg] == '--no-ioapic': + self.fEnableIOAPIC = False + elif asArgs[iArg] == '--no-nested-paging': + self.fEnableNestedPaging = False + elif asArgs[iArg] == '--pae': + self.fEnablePAE = True + elif asArgs[iArg] == '--extra-mem': + self.fEnablePAE = True + elif asArgs[iArg] == '--set-extradata': + iArg = self.requireMoreArgs(1, asArgs, iArg) + self.asExtraData.append(asArgs[iArg]) + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg) + + return iArg + 1 + + def actionConfig(self): + """ + Configure pre-conditions. + """ + + if not self.importVBoxApi(): + return False + + assert self.sIso is not None + if self.sIso not in self.kaoVmParams: + reporter.log('Error: unknown ISO image specified: %s' % self.sIso) + return False + + # Get VM params specific to ISO image + cBytesHdd, sKind, sController = self.kaoVmParams[self.sIso] + + # Create VM itself + eNic0AttachType = vboxcon.NetworkAttachmentType_NAT + self.sIso = os.path.join(self.sIsoPathBase, self.sIso) + assert os.path.isfile(self.sIso) + + self.sFloppy = os.path.join(self.sIsoPathBase, os.path.splitext(self.sIso)[0] + '.img') + + oVM = self.createTestVM(self.sVmName, 1, sKind = sKind, sDvdImage = self.sIso, cCpus = self.cCpus, + sFloppy = self.sFloppy, eNic0AttachType = eNic0AttachType) + assert oVM is not None + + oSession = self.openSession(oVM) + + # Create HDD + sHddPath = os.path.join(self.sScratchPath, self.sHddName) + fRc = True + if sController == self.ksSataController: + fRc = oSession.setStorageControllerType(vboxcon.StorageControllerType_IntelAhci, sController) + + fRc = fRc and oSession.createAndAttachHd(sHddPath, cb = cBytesHdd, + sController = sController, iPort = 0, fImmutable=False) + if sController == self.ksSataController: + fRc = fRc and oSession.setStorageControllerPortCount(sController, 1) + + # Set proper boot order + fRc = fRc and oSession.setBootOrder(1, vboxcon.DeviceType_HardDisk) + fRc = fRc and oSession.setBootOrder(2, vboxcon.DeviceType_Floppy) + + # Enable HW virt + fRc = fRc and oSession.enableVirtEx(True) + + # Enable I/O APIC + fRc = fRc and oSession.enableIoApic(self.fEnableIOAPIC) + + # Enable Nested Paging + fRc = fRc and oSession.enableNestedPaging(self.fEnableNestedPaging) + + # Enable PAE + fRc = fRc and oSession.enablePae(self.fEnablePAE) + + # Remote desktop + oSession.setupVrdp(True) + + # Set extra data + if self.asExtraData: + for sExtraData in self.asExtraData: + try: + sKey, sValue = sExtraData.split(':') + except ValueError: + raise base.InvalidOption('Invalid extradata specified: %s' % sExtraData) + reporter.log('Set extradata: %s => %s' % (sKey, sValue)) + fRc = fRc and oSession.setExtraData(sKey, sValue) + + fRc = fRc and oSession.saveSettings() + fRc = oSession.close() + assert fRc is True + + return vbox.TestDriver.actionConfig(self) + + def actionExecute(self): + """ + Execute the testcase itself. + """ + if not self.importVBoxApi(): + return False + return self.testDoInstallGuestOs() + + # + # Test execution helpers. + # + + def testDoInstallGuestOs(self): + """ + Install guest OS and wait for result + """ + reporter.testStart('Installing %s' % (os.path.basename(self.sIso),)) + + cMsTimeout = 40*60000; + if not reporter.isLocal(): ## @todo need to figure a better way of handling timeouts on the testboxes ... + cMsTimeout = 180 * 60000; # will be adjusted down. + + oSession, _ = self.startVmAndConnectToTxsViaTcp(self.sVmName, fCdWait = False, cMsTimeout = cMsTimeout) + if oSession is not None: + # Wait until guest reported success + reporter.log('Guest reported success') + reporter.testDone() + fRc = self.terminateVmBySession(oSession) + return fRc is True + reporter.error('Installation of %s has failed' % (self.sIso,)) + reporter.testDone() + return False + +if __name__ == '__main__': + sys.exit(tdGuestOsInstOs2().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/installation/tdGuestOsInstTest1.py b/src/VBox/ValidationKit/tests/installation/tdGuestOsInstTest1.py new file mode 100755 index 00000000..c38a16d7 --- /dev/null +++ b/src/VBox/ValidationKit/tests/installation/tdGuestOsInstTest1.py @@ -0,0 +1,409 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdGuestOsInstTest1.py $ + +""" +VirtualBox Validation Kit - Guest OS installation tests. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os +import sys + + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0] +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(g_ksValidationKitDir) + +# Validation Kit imports. +from testdriver import vbox; +from testdriver import base; +from testdriver import reporter; +from testdriver import vboxcon; +from testdriver import vboxtestvms; + + +class InstallTestVm(vboxtestvms.TestVm): + """ Installation test VM. """ + + ## @name The primary controller, to which the disk will be attached. + ## @{ + ksScsiController = 'SCSI Controller' + ksSataController = 'SATA Controller' + ksIdeController = 'IDE Controller' + ## @} + + ## @name VM option flags (OR together). + ## @{ + kf32Bit = 0x01; + kf64Bit = 0x02; + # most likely for ancient Linux kernels assuming that AMD processors have always an I/O-APIC + kfReqIoApic = 0x10; + kfReqIoApicSmp = 0x20; + kfReqPae = 0x40; + kfIdeIrqDelay = 0x80; + kfUbuntuNewAmdBug = 0x100; + kfNoWin81Paravirt = 0x200; + ## @} + + ## IRQ delay extra data config for win2k VMs. + kasIdeIrqDelay = [ 'VBoxInternal/Devices/piix3ide/0/Config/IRQDelay:1', ]; + + ## Install ISO path relative to the testrsrc root. + ksIsoPathBase = os.path.join('4.2', 'isos'); + + + def __init__(self, oSet, sVmName, sKind, sInstallIso, sHdCtrlNm, cGbHdd, fFlags): + vboxtestvms.TestVm.__init__(self, sVmName, oSet = oSet, sKind = sKind, sHddControllerType = sHdCtrlNm, + fRandomPvPMode = (fFlags & self.kfNoWin81Paravirt) == 0); + self.sDvdImage = os.path.join(self.ksIsoPathBase, sInstallIso); + self.cGbHdd = cGbHdd; + self.fInstVmFlags = fFlags; + if fFlags & self.kfReqPae: + self.fPae = True; + if fFlags & (self.kfReqIoApic | self.kfReqIoApicSmp): + self.fIoApic = True; + + # Tweaks + self.iOptRamAdjust = 0; + self.asExtraData = []; + if fFlags & self.kfIdeIrqDelay: + self.asExtraData = self.kasIdeIrqDelay; + + def detatchAndDeleteHd(self, oTestDrv): + """ + Detaches and deletes the HD. + Returns success indicator, error info logged. + """ + fRc = False; + oVM = oTestDrv.getVmByName(self.sVmName); + if oVM is not None: + oSession = oTestDrv.openSession(oVM); + if oSession is not None: + (fRc, oHd) = oSession.detachHd(self.sHddControllerType, iPort = 0, iDevice = 0); + if fRc is True and oHd is not None: + fRc = oSession.saveSettings(); + fRc = fRc and oTestDrv.oVBox.deleteHdByMedium(oHd); + fRc = fRc and oSession.saveSettings(); # Necessary for media reg? + fRc = oSession.close() and fRc; + return fRc; + + def getReconfiguredVm(self, oTestDrv, cCpus, sVirtMode, sParavirtMode = None): + # + # Do the standard reconfig in the base class first, it'll figure out + # if we can run the VM as requested. + # + (fRc, oVM) = vboxtestvms.TestVm.getReconfiguredVm(self, oTestDrv, cCpus, sVirtMode, sParavirtMode); + + # + # Make sure there is no HD from the previous run attached nor taking + # up storage on the host. + # + if fRc is True: + fRc = self.detatchAndDeleteHd(oTestDrv); + + # + # Check for ubuntu installer vs. AMD host CPU. + # + if fRc is True and (self.fInstVmFlags & self.kfUbuntuNewAmdBug): + if self.isHostCpuAffectedByUbuntuNewAmdBug(oTestDrv): + return (None, None); # (skip) + + # + # Make adjustments to the default config, and adding a fresh HD. + # + if fRc is True: + oSession = oTestDrv.openSession(oVM); + if oSession is not None: + if self.sHddControllerType == self.ksSataController: + fRc = fRc and oSession.setStorageControllerType(vboxcon.StorageControllerType_IntelAhci, + self.sHddControllerType); + fRc = fRc and oSession.setStorageControllerPortCount(self.sHddControllerType, 1); + elif self.sHddControllerType == self.ksScsiController: + fRc = fRc and oSession.setStorageControllerType(vboxcon.StorageControllerType_LsiLogic, + self.sHddControllerType); + try: + sHddPath = os.path.join(os.path.dirname(oVM.settingsFilePath), + '%s-%s-%s.vdi' % (self.sVmName, sVirtMode, cCpus,)); + except: + reporter.errorXcpt(); + sHddPath = None; + fRc = False; + + fRc = fRc and oSession.createAndAttachHd(sHddPath, + cb = self.cGbHdd * 1024*1024*1024, + sController = self.sHddControllerType, + iPort = 0, + fImmutable = False); + + # Set proper boot order + fRc = fRc and oSession.setBootOrder(1, vboxcon.DeviceType_HardDisk) + fRc = fRc and oSession.setBootOrder(2, vboxcon.DeviceType_DVD) + + # Adjust memory if requested. + if self.iOptRamAdjust != 0: + fRc = fRc and oSession.setRamSize(oSession.o.machine.memorySize + self.iOptRamAdjust); + + # Set extra data + for sExtraData in self.asExtraData: + try: + sKey, sValue = sExtraData.split(':') + except ValueError: + raise base.InvalidOption('Invalid extradata specified: %s' % sExtraData) + reporter.log('Set extradata: %s => %s' % (sKey, sValue)) + fRc = fRc and oSession.setExtraData(sKey, sValue) + + # Other variations? + + # Save the settings. + fRc = fRc and oSession.saveSettings() + fRc = oSession.close() and fRc; + else: + fRc = False; + if fRc is not True: + oVM = None; + + # Done. + return (fRc, oVM) + + + + + +class tdGuestOsInstTest1(vbox.TestDriver): + """ + Guest OS installation tests. + + Scenario: + - Create new VM that corresponds specified installation ISO image. + - Create HDD that corresponds to OS type that will be installed. + - Boot VM from ISO image (i.e. install guest OS). + - Wait for incomming TCP connection (guest should initiate such a + connection in case installation has been completed successfully). + """ + + + def __init__(self): + """ + Reinitialize child class instance. + """ + vbox.TestDriver.__init__(self) + self.fLegacyOptions = False; + assert self.fEnableVrdp; # in parent driver. + + # + # Our install test VM set. + # + oSet = vboxtestvms.TestVmSet(self.oTestVmManager, fIgnoreSkippedVm = True); + oSet.aoTestVms.extend([ + # pylint: disable=line-too-long + InstallTestVm(oSet, 'tst-fedora4', 'Fedora', 'fedora4-txs.iso', InstallTestVm.ksIdeController, 8, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-fedora5', 'Fedora', 'fedora5-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit | InstallTestVm.kfReqPae | InstallTestVm.kfReqIoApicSmp), + InstallTestVm(oSet, 'tst-fedora6', 'Fedora', 'fedora6-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit | InstallTestVm.kfReqIoApic), + InstallTestVm(oSet, 'tst-fedora7', 'Fedora', 'fedora7-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit | InstallTestVm.kfUbuntuNewAmdBug | InstallTestVm.kfReqIoApic), + InstallTestVm(oSet, 'tst-fedora9', 'Fedora', 'fedora9-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-fedora18-64', 'Fedora_64', 'fedora18-x64-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf64Bit), + InstallTestVm(oSet, 'tst-fedora18', 'Fedora', 'fedora18-txs.iso', InstallTestVm.ksScsiController, 8, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-ols6', 'Oracle', 'ols6-i386-txs.iso', InstallTestVm.ksSataController, 12, InstallTestVm.kf32Bit | InstallTestVm.kfReqPae), + InstallTestVm(oSet, 'tst-ols6-64', 'Oracle_64', 'ols6-x86_64-txs.iso', InstallTestVm.ksSataController, 12, InstallTestVm.kf64Bit), + InstallTestVm(oSet, 'tst-rhel5', 'RedHat', 'rhel5-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit | InstallTestVm.kfReqPae | InstallTestVm.kfReqIoApic), + InstallTestVm(oSet, 'tst-suse102', 'OpenSUSE', 'opensuse102-txs.iso', InstallTestVm.ksIdeController, 8, InstallTestVm.kf32Bit | InstallTestVm.kfReqIoApic), + ## @todo InstallTestVm(oSet, 'tst-ubuntu606', 'Ubuntu', 'ubuntu606-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit), + ## @todo InstallTestVm(oSet, 'tst-ubuntu710', 'Ubuntu', 'ubuntu710-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-ubuntu804', 'Ubuntu', 'ubuntu804-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit | InstallTestVm.kfUbuntuNewAmdBug | InstallTestVm.kfReqPae | InstallTestVm.kfReqIoApic), + InstallTestVm(oSet, 'tst-ubuntu804-64', 'Ubuntu_64', 'ubuntu804-amd64-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf64Bit), + InstallTestVm(oSet, 'tst-ubuntu904', 'Ubuntu', 'ubuntu904-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit | InstallTestVm.kfUbuntuNewAmdBug | InstallTestVm.kfReqPae), + InstallTestVm(oSet, 'tst-ubuntu904-64', 'Ubuntu_64', 'ubuntu904-amd64-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf64Bit), + #InstallTestVm(oSet, 'tst-ubuntu1404', 'Ubuntu', 'ubuntu1404-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit | InstallTestVm.kfUbuntuNewAmdBug | InstallTestVm.kfReqPae), bird: Is 14.04 one of the 'older ones'? + InstallTestVm(oSet, 'tst-ubuntu1404', 'Ubuntu', 'ubuntu1404-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit | InstallTestVm.kfReqPae), + InstallTestVm(oSet, 'tst-ubuntu1404-64','Ubuntu_64', 'ubuntu1404-amd64-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf64Bit), + InstallTestVm(oSet, 'tst-debian7', 'Debian', 'debian-7.0.0-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-debian7-64', 'Debian_64', 'debian-7.0.0-x64-txs.iso', InstallTestVm.ksScsiController, 8, InstallTestVm.kf64Bit), + InstallTestVm(oSet, 'tst-vista-64', 'WindowsVista_64', 'vista-x64-txs.iso', InstallTestVm.ksSataController, 25, InstallTestVm.kf64Bit), + InstallTestVm(oSet, 'tst-vista-32', 'WindowsVista', 'vista-x86-txs.iso', InstallTestVm.ksSataController, 25, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-w7-64', 'Windows7_64', 'win7-x64-txs.iso', InstallTestVm.ksSataController, 25, InstallTestVm.kf64Bit), + InstallTestVm(oSet, 'tst-w7-32', 'Windows7', 'win7-x86-txs.iso', InstallTestVm.ksSataController, 25, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-w2k3', 'Windows2003', 'win2k3ent-txs.iso', InstallTestVm.ksIdeController, 25, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-w2k', 'Windows2000', 'win2ksp0-txs.iso', InstallTestVm.ksIdeController, 25, InstallTestVm.kf32Bit | InstallTestVm.kfIdeIrqDelay), + InstallTestVm(oSet, 'tst-w2ksp4', 'Windows2000', 'win2ksp4-txs.iso', InstallTestVm.ksIdeController, 25, InstallTestVm.kf32Bit | InstallTestVm.kfIdeIrqDelay), + InstallTestVm(oSet, 'tst-wxp', 'WindowsXP', 'winxppro-txs.iso', InstallTestVm.ksIdeController, 25, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-wxpsp2', 'WindowsXP', 'winxpsp2-txs.iso', InstallTestVm.ksIdeController, 25, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-wxp64', 'WindowsXP_64', 'winxp64-txs.iso', InstallTestVm.ksIdeController, 25, InstallTestVm.kf64Bit), + ## @todo disable paravirt for Windows 8.1 guests as long as it's not fixed in the code + InstallTestVm(oSet, 'tst-w81-32', 'Windows81', 'win81-x86-txs.iso', InstallTestVm.ksSataController, 25, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-w81-64', 'Windows81_64', 'win81-x64-txs.iso', InstallTestVm.ksSataController, 25, InstallTestVm.kf64Bit), + InstallTestVm(oSet, 'tst-w10-32', 'Windows10', 'win10-x86-txs.iso', InstallTestVm.ksSataController, 25, InstallTestVm.kf32Bit | InstallTestVm.kfReqPae), + InstallTestVm(oSet, 'tst-w10-64', 'Windows10_64', 'win10-x64-txs.iso', InstallTestVm.ksSataController, 25, InstallTestVm.kf64Bit), + # pylint: enable=line-too-long + ]); + self.oTestVmSet = oSet; + + + + # + # Overridden methods. + # + + def showUsage(self): + """ + Extend usage info + """ + rc = vbox.TestDriver.showUsage(self) + reporter.log(''); + reporter.log('tdGuestOsInstTest1 options:'); + reporter.log(' --ioapic, --no-ioapic'); + reporter.log(' Enable or disable the I/O apic.'); + reporter.log(' Default: --ioapic'); + reporter.log(' --pae, --no-pae'); + reporter.log(' Enable or disable PAE support for 32-bit guests.'); + reporter.log(' Default: Guest dependent.'); + reporter.log(' --ram-adjust <MBs>') + reporter.log(' Adjust the VM ram size by the given delta. Both negative and positive'); + reporter.log(' values are accepted.'); + reporter.log(' --set-extradata <key>:value') + reporter.log(' Set VM extra data. This command line option might be used multiple times.') + reporter.log('obsolete:'); + reporter.log(' --nested-paging, --no-nested-paging'); + reporter.log(' --raw-mode'); + reporter.log(' --cpus <# CPUs>'); + reporter.log(' --install-iso <ISO file name>'); + + return rc + + def parseOption(self, asArgs, iArg): + """ + Extend standard options set + """ + + if False is True: + pass; + elif asArgs[iArg] == '--ioapic': + for oTestVm in self.oTestVmSet.aoTestVms: + oTestVm.fIoApic = True; + elif asArgs[iArg] == '--no-ioapic': + for oTestVm in self.oTestVmSet.aoTestVms: + oTestVm.fIoApic = False; + elif asArgs[iArg] == '--pae': + for oTestVm in self.oTestVmSet.aoTestVms: + oTestVm.fPae = True; + elif asArgs[iArg] == '--no-pae': + for oTestVm in self.oTestVmSet.aoTestVms: + oTestVm.fPae = False; + elif asArgs[iArg] == '--ram-adjust': + iArg = self.requireMoreArgs(1, asArgs, iArg); + for oTestVm in self.oTestVmSet.aoTestVms: + oTestVm.iOptRamAdjust = int(asArgs[iArg]); + elif asArgs[iArg] == '--set-extradata': + iArg = self.requireMoreArgs(1, asArgs, iArg) + for oTestVm in self.oTestVmSet.aoTestVms: + oTestVm.asExtraData.append(asArgs[iArg]); + + # legacy, to be removed once TM is reconfigured. + elif asArgs[iArg] == '--install-iso': + self.legacyOptions(); + iArg = self.requireMoreArgs(1, asArgs, iArg); + for oTestVm in self.oTestVmSet.aoTestVms: + oTestVm.fSkip = os.path.basename(oTestVm.sDvdImage) != asArgs[iArg]; + elif asArgs[iArg] == '--cpus': + self.legacyOptions(); + iArg = self.requireMoreArgs(1, asArgs, iArg); + self.oTestVmSet.acCpus = [ int(asArgs[iArg]), ]; + elif asArgs[iArg] == '--raw-mode': + self.legacyOptions(); + self.oTestVmSet.asVirtModes = [ 'raw', ]; + elif asArgs[iArg] == '--nested-paging': + self.legacyOptions(); + self.oTestVmSet.asVirtModes = [ 'hwvirt-np', ]; + elif asArgs[iArg] == '--no-nested-paging': + self.legacyOptions(); + self.oTestVmSet.asVirtModes = [ 'hwvirt', ]; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg) + + return iArg + 1 + + def legacyOptions(self): + """ Enables legacy option mode. """ + if not self.fLegacyOptions: + self.fLegacyOptions = True; + self.oTestVmSet.asVirtModes = [ 'hwvirt', ]; + self.oTestVmSet.acCpus = [ 1, ]; + return True; + + def actionConfig(self): + if not self.importVBoxApi(): # So we can use the constant below. + return False; + return self.oTestVmSet.actionConfig(self, eNic0AttachType = vboxcon.NetworkAttachmentType_NAT); + + def actionExecute(self): + """ + Execute the testcase. + """ + return self.oTestVmSet.actionExecute(self, self.testOneVmConfig) + + def testOneVmConfig(self, oVM, oTestVm): + """ + Install guest OS and wait for result + """ + + self.logVmInfo(oVM) + reporter.testStart('Installing %s' % (oTestVm.sVmName,)) + + cMsTimeout = 40*60000; + if not reporter.isLocal(): ## @todo need to figure a better way of handling timeouts on the testboxes ... + cMsTimeout = 180 * 60000; # will be adjusted down. + + oSession, _ = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, fCdWait = False, cMsTimeout = cMsTimeout); + if oSession is not None: + # The guest has connected to TXS, so we're done (for now anyways). + reporter.log('Guest reported success') + ## @todo Do save + restore. + + reporter.testDone() + fRc = self.terminateVmBySession(oSession) + return fRc is True + + reporter.error('Installation of %s has failed' % (oTestVm.sVmName,)) + oTestVm.detatchAndDeleteHd(self); # Save space. + reporter.testDone() + return False + +if __name__ == '__main__': + sys.exit(tdGuestOsInstTest1().main(sys.argv)) + diff --git a/src/VBox/ValidationKit/tests/installation/tdGuestOsUnattendedInst1.py b/src/VBox/ValidationKit/tests/installation/tdGuestOsUnattendedInst1.py new file mode 100755 index 00000000..2341c4a0 --- /dev/null +++ b/src/VBox/ValidationKit/tests/installation/tdGuestOsUnattendedInst1.py @@ -0,0 +1,769 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdGuestOsUnattendedInst1.py $ + +""" +VirtualBox Validation Kit - Guest OS unattended installation tests. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + + +# Standard Python imports. +import copy; +import os; +import sys; + + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0] +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(g_ksValidationKitDir) + +# Validation Kit imports. +from testdriver import vbox; +from testdriver import base; +from testdriver import reporter; +from testdriver import vboxcon; +from testdriver import vboxtestvms; +from common import utils; + +# Sub-test driver imports. +sys.path.append(os.path.join(g_ksValidationKitDir, 'tests', 'additions')); +from tdAddGuestCtrl import SubTstDrvAddGuestCtrl; +from tdAddSharedFolders1 import SubTstDrvAddSharedFolders1; + + +class UnattendedVm(vboxtestvms.BaseTestVm): + """ Unattended Installation test VM. """ + + ## @name VM option flags (OR together). + ## @{ + kfUbuntuAvx2Crash = 0x0001; ##< Disables AVX2 as ubuntu 16.04 think it means AVX512 is available and compiz crashes. + kfNoGAs = 0x0002; ##< No guest additions installation possible. + kfKeyFile = 0x0004; ##< ISO requires a .key file containing the product key. + kfNeedCom1 = 0x0008; ##< Need serial port, typically for satifying the windows kernel debugger. + + kfIdeIrqDelay = 0x1000; + kfUbuntuNewAmdBug = 0x2000; + kfNoWin81Paravirt = 0x4000; + kfAvoidNetwork = 0x8000; + ## @} + + ## kfUbuntuAvx2Crash: Extra data that disables AVX2. + kasUbuntuAvx2Crash = [ '/CPUM/IsaExts/AVX2:0', ]; + + ## IRQ delay extra data config for win2k VMs. + kasIdeIrqDelay = [ 'VBoxInternal/Devices/piix3ide/0/Config/IRQDelay:1', ]; + + def __init__(self, oSet, sVmName, sKind, sInstallIso, fFlags = 0): + vboxtestvms.BaseTestVm.__init__(self, sVmName, oSet = oSet, sKind = sKind, + fRandomPvPModeCrap = (fFlags & self.kfNoWin81Paravirt) == 0); + self.sInstallIso = sInstallIso; + self.fInstVmFlags = fFlags; + + # Adjustments over the defaults. + self.iOptRamAdjust = 0; + self.fOptIoApic = None; + self.fOptPae = None; + self.fOptInstallAdditions = False; + self.asOptExtraData = []; + if fFlags & self.kfUbuntuAvx2Crash: + self.asOptExtraData += self.kasUbuntuAvx2Crash; + if fFlags & self.kfIdeIrqDelay: + self.asOptExtraData += self.kasIdeIrqDelay; + if fFlags & self.kfNeedCom1: + self.fCom1RawFile = True; + + def _unattendedConfigure(self, oIUnattended, oTestDrv): # type: (Any, vbox.TestDriver) -> bool + """ + Configures the unattended install object. + + The ISO attribute has been set and detectIsoOS has been done, the rest of the + setup is done here. + + Returns True on success, False w/ errors logged on failure. + """ + + # + # Make it install the TXS. + # + try: oIUnattended.installTestExecService = True; + except: return reporter.errorXcpt(); + try: oIUnattended.validationKitIsoPath = oTestDrv.sVBoxValidationKitIso; + except: return reporter.errorXcpt(); + oTestDrv.processPendingEvents(); + + # + # Avoid using network during unattended install (stalls Debian installs). + # + if self.fInstVmFlags & UnattendedVm.kfAvoidNetwork: + try: oIUnattended.avoidUpdatesOverNetwork = True; + except: return reporter.errorXcpt(); + + # + # Install GAs? + # + if self.fOptInstallAdditions: + if (self.fInstVmFlags & self.kfNoGAs) == 0: + try: oIUnattended.installGuestAdditions = True; + except: return reporter.errorXcpt(); + try: oIUnattended.additionsIsoPath = oTestDrv.getGuestAdditionsIso(); + except: return reporter.errorXcpt(); + oTestDrv.processPendingEvents(); + else: + reporter.log("Warning! Ignoring request to install Guest Additions as kfNoGAs is set!"); + + # + # Product key? + # + if self.fInstVmFlags & UnattendedVm.kfKeyFile: + sKeyFile = ''; + sKey = ''; + try: + sKeyFile = oIUnattended.isoPath + '.key'; + oFile = utils.openNoInherit(sKeyFile); + for sLine in oFile: + sLine = sLine.strip(); + if sLine and not sLine.startswith(';') and not sLine.startswith('#') and not sLine.startswith('//'): + sKey = sLine; + break; + oFile.close(); + except: + return reporter.errorXcpt('sKeyFile=%s' % (sKeyFile,)); + if not sKey: + return reporter.error('No key in keyfile (%s)!' % (sKeyFile,)); + try: oIUnattended.productKey = sKey; + except: return reporter.errorXcpt(); + + return True; + + def _unattendedDoIt(self, oIUnattended, oVM, oTestDrv): # type: (Any, Any, vbox.TestDriver) -> bool + """ + Does the unattended installation preparing, media construction and VM reconfiguration. + + Returns True on success, False w/ errors logged on failure. + """ + + # Associate oVM with the installer: + try: + oIUnattended.machine = oVM; + except: + return reporter.errorXcpt(); + oTestDrv.processPendingEvents(); + + # Prepare and log it: + try: + oIUnattended.prepare(); + except: + return reporter.errorXcpt("IUnattended.prepare failed"); + oTestDrv.processPendingEvents(); + + reporter.log('IUnattended attributes after prepare():'); + self._unattendedLogIt(oIUnattended, oTestDrv); + + # Create media: + try: + oIUnattended.constructMedia(); + except: + return reporter.errorXcpt("IUnattended.constructMedia failed"); + oTestDrv.processPendingEvents(); + + # Reconfigure the VM: + try: + oIUnattended.reconfigureVM(); + except: + return reporter.errorXcpt("IUnattended.reconfigureVM failed"); + oTestDrv.processPendingEvents(); + + return True; + + def _unattendedLogIt(self, oIUnattended, oTestDrv): + """ + Logs the attributes of the unattended installation object. + """ + fRc = True; + asAttribs = ( 'isoPath', 'user', 'password', 'fullUserName', 'productKey', 'additionsIsoPath', 'installGuestAdditions', + 'validationKitIsoPath', 'installTestExecService', 'timeZone', 'locale', 'language', 'country', 'proxy', + 'packageSelectionAdjustments', 'hostname', 'auxiliaryBasePath', 'imageIndex', 'machine', + 'scriptTemplatePath', 'postInstallScriptTemplatePath', 'postInstallCommand', + 'extraInstallKernelParameters', 'detectedOSTypeId', 'detectedOSVersion', 'detectedOSLanguages', + 'detectedOSFlavor', 'detectedOSHints', ); + for sAttrib in asAttribs: + try: + oValue = getattr(oIUnattended, sAttrib); + except: + fRc = reporter.errorXcpt('sAttrib=%s' % sAttrib); + else: + reporter.log('%s: %s' % (sAttrib.rjust(32), oValue,)); + oTestDrv.processPendingEvents(); + return fRc; + + + # + # Overriden methods. + # + + def getResourceSet(self): + asRet = []; + if not os.path.isabs(self.sInstallIso): + asRet.append(self.sInstallIso); + if self.fInstVmFlags & UnattendedVm.kfKeyFile: + asRet.append(self.sInstallIso + '.key'); + return asRet; + + def _createVmDoIt(self, oTestDrv, eNic0AttachType, sDvdImage): + # + # Use HostOnly networking for ubuntu and debian VMs to prevent them from + # downloading updates and doing database updates during installation. + # We want predicable results. + # + if eNic0AttachType is None: + if self.isLinux() \ + and ( 'ubuntu' in self.sKind.lower() + or 'debian' in self.sKind.lower()): + eNic0AttachType = vboxcon.NetworkAttachmentType_HostOnly; + + # Also use it for windows xp to prevent it from ever going online. + if self.sKind in ('WindowsXP','WindowsXP_64',): + eNic0AttachType = vboxcon.NetworkAttachmentType_HostOnly; + + # + # Use host-only networks instead of host-only adapters for trunk builds on Mac OS. + # + if eNic0AttachType == vboxcon.NetworkAttachmentType_HostOnly \ + and utils.getHostOs() == 'darwin' \ + and oTestDrv.fpApiVer >= 7.0: + eNic0AttachType = vboxcon.NetworkAttachmentType_HostOnlyNetwork; + + return vboxtestvms.BaseTestVm._createVmDoIt(self, oTestDrv, eNic0AttachType, sDvdImage); # pylint: disable=protected-access + + + def _createVmPost(self, oTestDrv, oVM, eNic0AttachType, sDvdImage): + # + # Adjust the ram, I/O APIC and stuff. + # + oSession = oTestDrv.openSession(oVM); + if oSession is None: + return None; + + fRc = True; + + ## Set proper boot order - IUnattended::reconfigureVM does this, doesn't it? + #fRc = fRc and oSession.setBootOrder(1, vboxcon.DeviceType_HardDisk) + #fRc = fRc and oSession.setBootOrder(2, vboxcon.DeviceType_DVD) + + # Adjust memory if requested. + if self.iOptRamAdjust != 0: + try: cMbRam = oSession.o.machine.memorySize; + except: fRc = reporter.errorXcpt(); + else: + fRc = oSession.setRamSize(cMbRam + self.iOptRamAdjust) and fRc; + + # I/O APIC: + if self.fOptIoApic is not None: + fRc = oSession.enableIoApic(self.fOptIoApic) and fRc; + + # I/O APIC: + if self.fOptPae is not None: + fRc = oSession.enablePae(self.fOptPae) and fRc; + + # Set extra data + for sExtraData in self.asOptExtraData: + sKey, sValue = sExtraData.split(':'); + reporter.log('Set extradata: %s => %s' % (sKey, sValue)) + fRc = oSession.setExtraData(sKey, sValue) and fRc; + + # Save the settings. + fRc = fRc and oSession.saveSettings() + fRc = oSession.close() and fRc; + + return oVM if fRc else None; + + def _skipVmTest(self, oTestDrv, oVM): + _ = oVM; + # + # Check for ubuntu installer vs. AMD host CPU. + # + if self.fInstVmFlags & self.kfUbuntuNewAmdBug: + if self.isHostCpuAffectedByUbuntuNewAmdBug(oTestDrv): + return True; + + return vboxtestvms.BaseTestVm._skipVmTest(self, oTestDrv, oVM); # pylint: disable=protected-access + + + def getReconfiguredVm(self, oTestDrv, cCpus, sVirtMode, sParavirtMode = None): + # + # Do the standard reconfig in the base class first, it'll figure out + # if we can run the VM as requested. + # + (fRc, oVM) = vboxtestvms.BaseTestVm.getReconfiguredVm(self, oTestDrv, cCpus, sVirtMode, sParavirtMode); + if fRc is True: + # + # Make sure there is no HD from the previous run attached nor taking + # up storage on the host. + # + fRc = self.recreateRecommendedHdd(oVM, oTestDrv); + if fRc is True: + # + # Set up unattended installation. + # + try: + oIUnattended = oTestDrv.oVBox.createUnattendedInstaller(); + except: + fRc = reporter.errorXcpt(); + if fRc is True: + fRc = self.unattendedDetectOs(oIUnattended, oTestDrv); + if fRc is True: + fRc = self._unattendedConfigure(oIUnattended, oTestDrv); + if fRc is True: + fRc = self._unattendedDoIt(oIUnattended, oVM, oTestDrv); + + # Done. + return (fRc, oVM) + + def isLoggedOntoDesktop(self): + # + # Normally all unattended installations should end up on the desktop. + # An exception is a minimal install, but we currently don't support that. + # + return True; + + def getTestUser(self): + # Default unattended installation user (parent knowns its password). + return 'vboxuser'; + + + # + # Our methods. + # + + def unattendedDetectOs(self, oIUnattended, oTestDrv): # type: (Any, vbox.TestDriver) -> bool + """ + Does the detectIsoOS operation and checks that the detect OSTypeId matches. + + Returns True on success, False w/ errors logged on failure. + """ + + # + # Point the installer at the ISO and do the detection. + # + sInstallIso = self.sInstallIso + if not os.path.isabs(sInstallIso): + sInstallIso = os.path.join(oTestDrv.sResourcePath, sInstallIso); + + try: + oIUnattended.isoPath = sInstallIso; + except: + return reporter.errorXcpt('sInstallIso=%s' % (sInstallIso,)); + + try: + oIUnattended.detectIsoOS(); + except: + if oTestDrv.oVBoxMgr.xcptIsNotEqual(None, oTestDrv.oVBoxMgr.statuses.E_NOTIMPL): + return reporter.errorXcpt('sInstallIso=%s' % (sInstallIso,)); + + # + # Get and log the result. + # + # Note! Current (6.0.97) fails with E_NOTIMPL even if it does some work. + # + try: + sDetectedOSTypeId = oIUnattended.detectedOSTypeId; + sDetectedOSVersion = oIUnattended.detectedOSVersion; + sDetectedOSFlavor = oIUnattended.detectedOSFlavor; + sDetectedOSLanguages = oIUnattended.detectedOSLanguages; + sDetectedOSHints = oIUnattended.detectedOSHints; + except: + return reporter.errorXcpt('sInstallIso=%s' % (sInstallIso,)); + + reporter.log('detectIsoOS result for "%s" (vm %s):' % (sInstallIso, self.sVmName)); + reporter.log(' DetectedOSTypeId: %s' % (sDetectedOSTypeId,)); + reporter.log(' DetectedOSVersion: %s' % (sDetectedOSVersion,)); + reporter.log(' DetectedOSFlavor: %s' % (sDetectedOSFlavor,)); + reporter.log(' DetectedOSLanguages: %s' % (sDetectedOSLanguages,)); + reporter.log(' DetectedOSHints: %s' % (sDetectedOSHints,)); + + # + # Check if the OS type matches. + # + if self.sKind != sDetectedOSTypeId: + return reporter.error('sInstallIso=%s: DetectedOSTypeId is %s, expected %s' + % (sInstallIso, sDetectedOSTypeId, self.sKind)); + + return True; + + +class tdGuestOsInstTest1(vbox.TestDriver): + """ + Unattended Guest OS installation tests using IUnattended. + + Scenario: + - Create a new VM with default settings using IMachine::applyDefaults. + - Setup unattended installation using IUnattended. + - Start the VM and do the installation. + - Wait for TXS to report for service. + - If installing GAs: + - Wait for GAs to report operational runlevel. + - Save & restore state. + - If installing GAs: + - Test guest properties (todo). + - Test guest controls. + - Test shared folders. + """ + + + def __init__(self): + """ + Reinitialize child class instance. + """ + vbox.TestDriver.__init__(self) + self.fLegacyOptions = False; + assert self.fEnableVrdp; # in parent driver. + + # + # Our install test VM set. + # + oSet = vboxtestvms.TestVmSet(self.oTestVmManager, fIgnoreSkippedVm = True); + # pylint: disable=line-too-long + oSet.aoTestVms.extend([ + # + # Windows. The older windows ISOs requires a keyfile (for xp sp3 + # pick a key from the PID.INF file on the ISO). + # + UnattendedVm(oSet, 'tst-xp-32', 'WindowsXP', '6.0/uaisos/en_winxp_pro_x86_build2600_iso.img', UnattendedVm.kfKeyFile), # >=2GiB + UnattendedVm(oSet, 'tst-xpsp2-32', 'WindowsXP', '6.0/uaisos/en_winxp_pro_with_sp2.iso', UnattendedVm.kfKeyFile), # >=2GiB + UnattendedVm(oSet, 'tst-xpsp3-32', 'WindowsXP', '6.0/uaisos/en_windows_xp_professional_with_service_pack_3_x86_cd_x14-80428.iso', UnattendedVm.kfKeyFile), # >=2GiB + UnattendedVm(oSet, 'tst-xp-64', 'WindowsXP_64', '6.0/uaisos/en_win_xp_pro_x64_vl.iso', UnattendedVm.kfKeyFile), # >=3GiB + UnattendedVm(oSet, 'tst-xpsp2-64', 'WindowsXP_64', '6.0/uaisos/en_win_xp_pro_x64_with_sp2_vl_x13-41611.iso', UnattendedVm.kfKeyFile), # >=3GiB + #fixme: UnattendedVm(oSet, 'tst-xpchk-64', 'WindowsXP_64', '6.0/uaisos/en_windows_xp_professional_x64_chk.iso', UnattendedVm.kfKeyFile | UnattendedVm.kfNeedCom1), # >=3GiB + # No key files needed: + UnattendedVm(oSet, 'tst-vista-32', 'WindowsVista', '6.0/uaisos/en_windows_vista_ee_x86_dvd_vl_x13-17271.iso'), # >=6GiB + UnattendedVm(oSet, 'tst-vista-64', 'WindowsVista_64', '6.0/uaisos/en_windows_vista_enterprise_x64_dvd_vl_x13-17316.iso'), # >=8GiB + UnattendedVm(oSet, 'tst-vistasp1-32', 'WindowsVista', '6.0/uaisos/en_windows_vista_enterprise_with_service_pack_1_x86_dvd_x14-55954.iso'), # >=6GiB + UnattendedVm(oSet, 'tst-vistasp1-64', 'WindowsVista_64', '6.0/uaisos/en_windows_vista_enterprise_with_service_pack_1_x64_dvd_x14-55934.iso'), # >=9GiB + UnattendedVm(oSet, 'tst-vistasp2-32', 'WindowsVista', '6.0/uaisos/en_windows_vista_enterprise_sp2_x86_dvd_342329.iso'), # >=7GiB + UnattendedVm(oSet, 'tst-vistasp2-64', 'WindowsVista_64', '6.0/uaisos/en_windows_vista_enterprise_sp2_x64_dvd_342332.iso'), # >=10GiB + UnattendedVm(oSet, 'tst-w7-32', 'Windows7', '6.0/uaisos/en_windows_7_enterprise_x86_dvd_x15-70745.iso'), # >=6GiB + UnattendedVm(oSet, 'tst-w7-64', 'Windows7_64', '6.0/uaisos/en_windows_7_enterprise_x64_dvd_x15-70749.iso'), # >=10GiB + UnattendedVm(oSet, 'tst-w7sp1-32', 'Windows7', '6.0/uaisos/en_windows_7_enterprise_with_sp1_x86_dvd_u_677710.iso'), # >=6GiB + UnattendedVm(oSet, 'tst-w7sp1-64', 'Windows7_64', '6.0/uaisos/en_windows_7_enterprise_with_sp1_x64_dvd_u_677651.iso'), # >=8GiB + UnattendedVm(oSet, 'tst-w8-32', 'Windows8', '6.0/uaisos/en_windows_8_enterprise_x86_dvd_917587.iso'), # >=6GiB + UnattendedVm(oSet, 'tst-w8-64', 'Windows8_64', '6.0/uaisos/en_windows_8_enterprise_x64_dvd_917522.iso'), # >=9GiB + UnattendedVm(oSet, 'tst-w81-32', 'Windows81', '6.0/uaisos/en_windows_8_1_enterprise_x86_dvd_2791510.iso'), # >=5GiB + UnattendedVm(oSet, 'tst-w81-64', 'Windows81_64', '6.0/uaisos/en_windows_8_1_enterprise_x64_dvd_2791088.iso'), # >=8GiB + UnattendedVm(oSet, 'tst-w10-1507-32', 'Windows10', '6.0/uaisos/en_windows_10_pro_10240_x86_dvd.iso'), # >=6GiB + UnattendedVm(oSet, 'tst-w10-1507-64', 'Windows10_64', '6.0/uaisos/en_windows_10_pro_10240_x64_dvd.iso'), # >=9GiB + UnattendedVm(oSet, 'tst-w10-1511-32', 'Windows10', '6.0/uaisos/en_windows_10_enterprise_version_1511_updated_feb_2016_x86_dvd_8378870.iso'), # >=7GiB + UnattendedVm(oSet, 'tst-w10-1511-64', 'Windows10_64', '6.0/uaisos/en_windows_10_enterprise_version_1511_x64_dvd_7224901.iso'), # >=9GiB + UnattendedVm(oSet, 'tst-w10-1607-32', 'Windows10', '6.0/uaisos/en_windows_10_enterprise_version_1607_updated_jul_2016_x86_dvd_9060097.iso'), # >=7GiB + UnattendedVm(oSet, 'tst-w10-1607-64', 'Windows10_64', '6.0/uaisos/en_windows_10_enterprise_version_1607_updated_jul_2016_x64_dvd_9054264.iso'), # >=9GiB + UnattendedVm(oSet, 'tst-w10-1703-32', 'Windows10', '6.0/uaisos/en_windows_10_enterprise_version_1703_updated_march_2017_x86_dvd_10188981.iso'), # >=7GiB + UnattendedVm(oSet, 'tst-w10-1703-64', 'Windows10_64', '6.0/uaisos/en_windows_10_enterprise_version_1703_updated_march_2017_x64_dvd_10189290.iso'), # >=10GiB + UnattendedVm(oSet, 'tst-w10-1709-32', 'Windows10', '6.0/uaisos/en_windows_10_multi-edition_vl_version_1709_updated_sept_2017_x86_dvd_100090759.iso'), # >=7GiB + UnattendedVm(oSet, 'tst-w10-1709-64', 'Windows10_64', '6.0/uaisos/en_windows_10_multi-edition_vl_version_1709_updated_sept_2017_x64_dvd_100090741.iso'), # >=10GiB + UnattendedVm(oSet, 'tst-w10-1803-32', 'Windows10', '6.0/uaisos/en_windows_10_business_editions_version_1803_updated_march_2018_x86_dvd_12063341.iso'), # >=7GiB + UnattendedVm(oSet, 'tst-w10-1803-64', 'Windows10_64', '6.0/uaisos/en_windows_10_business_editions_version_1803_updated_march_2018_x64_dvd_12063333.iso'), # >=10GiB + UnattendedVm(oSet, 'tst-w10-1809-32', 'Windows10', '6.0/uaisos/en_windows_10_business_edition_version_1809_updated_sept_2018_x86_dvd_2f92403b.iso'), # >=7GiB + UnattendedVm(oSet, 'tst-w10-1809-64', 'Windows10_64', '6.0/uaisos/en_windows_10_business_edition_version_1809_updated_sept_2018_x64_dvd_f0b7dc68.iso'), # >=10GiB + UnattendedVm(oSet, 'tst-w10-1903-32', 'Windows10', '6.0/uaisos/en_windows_10_business_editions_version_1903_x86_dvd_ca4f0f49.iso'), # >=7GiB + UnattendedVm(oSet, 'tst-w10-1903-64', 'Windows10_64', '6.0/uaisos/en_windows_10_business_editions_version_1903_x64_dvd_37200948.iso'), # >=10GiB + # + # Ubuntu + # + ## @todo 15.10 fails with grub install error. + #UnattendedVm(oSet, 'tst-ubuntu-15.10-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-15.10-desktop-amd64.iso'), + UnattendedVm(oSet, 'tst-ubuntu-16.04-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-16.04-desktop-amd64.iso', # ~5GiB + UnattendedVm.kfUbuntuAvx2Crash), + UnattendedVm(oSet, 'tst-ubuntu-16.04-32', 'Ubuntu', '6.0/uaisos/ubuntu-16.04-desktop-i386.iso'), # >=4.5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.1-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-16.04.1-desktop-amd64.iso'), # >=5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.1-32', 'Ubuntu', '6.0/uaisos/ubuntu-16.04.1-desktop-i386.iso'), # >=4.5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.2-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-16.04.2-desktop-amd64.iso'), # >=5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.2-32', 'Ubuntu', '6.0/uaisos/ubuntu-16.04.2-desktop-i386.iso'), # >=4.5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.3-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-16.04.3-desktop-amd64.iso'), # >=5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.3-32', 'Ubuntu', '6.0/uaisos/ubuntu-16.04.3-desktop-i386.iso'), # >=4.5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.4-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-16.04.4-desktop-amd64.iso'), # >=5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.4-32', 'Ubuntu', '6.0/uaisos/ubuntu-16.04.4-desktop-i386.iso'), # >=4.5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.5-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-16.04.5-desktop-amd64.iso'), # >=5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.5-32', 'Ubuntu', '6.0/uaisos/ubuntu-16.04.5-desktop-i386.iso'), # >=4.5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.6-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-16.04.6-desktop-amd64.iso'), # >=5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.6-32', 'Ubuntu', '6.0/uaisos/ubuntu-16.04.6-desktop-i386.iso'), # >=4.5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.10-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-16.10-desktop-amd64.iso'), # >=5.5GiB + ## @todo 16.10-32 doesn't ask for an IP, so it always fails. + #UnattendedVm(oSet, 'tst-ubuntu-16.10-32', 'Ubuntu', '6.0/uaisos/ubuntu-16.10-desktop-i386.iso'), # >=5.5GiB? + UnattendedVm(oSet, 'tst-ubuntu-17.04-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-17.04-desktop-amd64.iso'), # >=5GiB + UnattendedVm(oSet, 'tst-ubuntu-17.04-32', 'Ubuntu', '6.0/uaisos/ubuntu-17.04-desktop-i386.iso'), # >=4.5GiB + ## @todo ubuntu 17.10, 18.04 & 18.10 do not work. They misses all the the build tools (make, gcc, perl, ++) + ## and has signed kmods: + UnattendedVm(oSet, 'tst-ubuntu-17.10-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-17.10-desktop-amd64.iso', # >=4Gib + UnattendedVm.kfNoGAs), + UnattendedVm(oSet, 'tst-ubuntu-18.04-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-18.04-desktop-amd64.iso', # >=6GiB + UnattendedVm.kfNoGAs), + # 18.10 hangs reading install DVD during "starting partitioner..." + #UnattendedVm(oSet, 'tst-ubuntu-18.10-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-18.10-desktop-amd64.iso', + # UnattendedVm.kfNoGAs), + UnattendedVm(oSet, 'tst-ubuntu-19.04-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-19.04-desktop-amd64.iso', # >=6GiB + UnattendedVm.kfNoGAs), + UnattendedVm(oSet, 'tst-debian-9.3-64', 'Debian_64', '6.0/uaisos/debian-9.3.0-amd64-DVD-1.iso', # >=6GiB? + UnattendedVm.kfAvoidNetwork | UnattendedVm.kfNoGAs), + UnattendedVm(oSet, 'tst-debian-9.4-64', 'Debian_64', '6.0/uaisos/debian-9.4.0-amd64-DVD-1.iso', # >=6GiB? + UnattendedVm.kfAvoidNetwork | UnattendedVm.kfNoGAs), + UnattendedVm(oSet, 'tst-debian-10.0-64', 'Debian_64', '6.0/uaisos/debian-10.0.0-amd64-DVD-1.iso', # >=6GiB? + UnattendedVm.kfAvoidNetwork), + # + # OS/2. + # + UnattendedVm(oSet, 'tst-acp2', 'OS2Warp45', '7.0/uaisos/acp2_us_cd2.iso'), # ~400MiB + ## @todo mcp2 too? + ]); + # pylint: enable=line-too-long + self.oTestVmSet = oSet; + + # For option parsing: + self.aoSelectedVms = oSet.aoTestVms # type: list(UnattendedVm) + + # Number of VMs to test in parallel: + self.cInParallel = 1; + + # Whether to do the save-and-restore test. + self.fTestSaveAndRestore = True; + + # + # Sub-test drivers. + # + self.addSubTestDriver(SubTstDrvAddSharedFolders1(self)); + self.addSubTestDriver(SubTstDrvAddGuestCtrl(self)); + + + # + # Overridden methods. + # + + def showUsage(self): + """ + Extend usage info + """ + rc = vbox.TestDriver.showUsage(self) + reporter.log(''); + reporter.log('tdGuestOsUnattendedInst1 options:'); + reporter.log(' --parallel <num>'); + reporter.log(' Number of VMs to test in parallel.'); + reporter.log(' Default: 1'); + reporter.log(''); + reporter.log(' Options for working on selected test VMs:'); + reporter.log(' --select <vm1[:vm2[:..]]>'); + reporter.log(' Selects a test VM for the following configuration alterations.'); + reporter.log(' Default: All possible test VMs'); + reporter.log(' --copy <old-vm>=<new-vm>'); + reporter.log(' Creates and selects <new-vm> as a copy of <old-vm>.'); + reporter.log(' --guest-type <guest-os-type>'); + reporter.log(' Sets the guest-os type of the currently selected test VM.'); + reporter.log(' --install-iso <ISO file name>'); + reporter.log(' Sets ISO image to use for the selected test VM.'); + reporter.log(' --ram-adjust <MBs>'); + reporter.log(' Adjust the VM ram size by the given delta. Both negative and positive'); + reporter.log(' values are accepted.'); + reporter.log(' --max-cpus <# CPUs>'); + reporter.log(' Sets the maximum number of guest CPUs for the selected VM.'); + reporter.log(' --set-extradata <key>:value'); + reporter.log(' Set VM extra data for the selected VM. Can be repeated.'); + reporter.log(' --ioapic, --no-ioapic'); + reporter.log(' Enable or disable the I/O apic for the selected VM.'); + reporter.log(' --pae, --no-pae'); + reporter.log(' Enable or disable PAE support (32-bit guests only) for the selected VM.'); + return rc + + def parseOption(self, asArgs, iArg): + """ + Extend standard options set + """ + + if asArgs[iArg] == '--parallel': + iArg = self.requireMoreArgs(1, asArgs, iArg); + self.cInParallel = int(asArgs[iArg]); + if self.cInParallel <= 0: + self.cInParallel = 1; + elif asArgs[iArg] == '--select': + iArg = self.requireMoreArgs(1, asArgs, iArg); + self.aoSelectedVms = []; + for sTestVm in asArgs[iArg].split(':'): + oTestVm = self.oTestVmSet.findTestVmByName(sTestVm); + if not oTestVm: + raise base.InvalidOption('Unknown test VM: %s' % (sTestVm,)); + self.aoSelectedVms.append(oTestVm); + elif asArgs[iArg] == '--copy': + iArg = self.requireMoreArgs(1, asArgs, iArg); + asNames = asArgs[iArg].split('='); + if len(asNames) != 2 or not asNames[0] or not asNames[1]: + raise base.InvalidOption('The --copy option expects value on the form "old=new": %s' % (asArgs[iArg],)); + oOldTestVm = self.oTestVmSet.findTestVmByName(asNames[0]); + if not oOldTestVm: + raise base.InvalidOption('Unknown test VM: %s' % (asNames[0],)); + oNewTestVm = copy.deepcopy(oOldTestVm); + oNewTestVm.sVmName = asNames[1]; + self.oTestVmSet.aoTestVms.append(oNewTestVm); + self.aoSelectedVms = [oNewTestVm]; + elif asArgs[iArg] == '--guest-type': + iArg = self.requireMoreArgs(1, asArgs, iArg); + for oTestVm in self.aoSelectedVms: + oTestVm.sKind = asArgs[iArg]; + elif asArgs[iArg] == '--install-iso': + iArg = self.requireMoreArgs(1, asArgs, iArg); + for oTestVm in self.aoSelectedVms: + oTestVm.sInstallIso = asArgs[iArg]; + elif asArgs[iArg] == '--ram-adjust': + iArg = self.requireMoreArgs(1, asArgs, iArg); + for oTestVm in self.aoSelectedVms: + oTestVm.iOptRamAdjust = int(asArgs[iArg]); + elif asArgs[iArg] == '--max-cpus': + iArg = self.requireMoreArgs(1, asArgs, iArg); + for oTestVm in self.aoSelectedVms: + oTestVm.iOptMaxCpus = int(asArgs[iArg]); + elif asArgs[iArg] == '--set-extradata': + iArg = self.requireMoreArgs(1, asArgs, iArg) + sExtraData = asArgs[iArg]; + try: _, _ = sExtraData.split(':'); + except: raise base.InvalidOption('Invalid extradata specified: %s' % (sExtraData, )); + for oTestVm in self.aoSelectedVms: + oTestVm.asOptExtraData.append(sExtraData); + elif asArgs[iArg] == '--ioapic': + for oTestVm in self.aoSelectedVms: + oTestVm.fOptIoApic = True; + elif asArgs[iArg] == '--no-ioapic': + for oTestVm in self.aoSelectedVms: + oTestVm.fOptIoApic = False; + elif asArgs[iArg] == '--pae': + for oTestVm in self.aoSelectedVms: + oTestVm.fOptPae = True; + elif asArgs[iArg] == '--no-pae': + for oTestVm in self.aoSelectedVms: + oTestVm.fOptPae = False; + elif asArgs[iArg] == '--install-additions': + for oTestVm in self.aoSelectedVms: + oTestVm.fOptInstallAdditions = True; + elif asArgs[iArg] == '--no-install-additions': + for oTestVm in self.aoSelectedVms: + oTestVm.fOptInstallAdditions = False; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def actionConfig(self): + if not self.importVBoxApi(): # So we can use the constant below. + return False; + return self.oTestVmSet.actionConfig(self); + + def actionExecute(self): + """ + Execute the testcase. + """ + return self.oTestVmSet.actionExecute(self, self.testOneVmConfig) + + def testOneVmConfig(self, oVM, oTestVm): # type: (Any, UnattendedVm) -> bool + """ + Install guest OS and wait for result + """ + + self.logVmInfo(oVM) + reporter.testStart('Installing %s%s' % (oTestVm.sVmName, ' with GAs' if oTestVm.fOptInstallAdditions else '')) + + cMsTimeout = 40*60000; + if not reporter.isLocal(): ## @todo need to figure a better way of handling timeouts on the testboxes ... + cMsTimeout = 180 * 60000; # will be adjusted down. + + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, fCdWait = False, cMsTimeout = cMsTimeout); + #oSession = self.startVmByName(oTestVm.sVmName); # (for quickly testing waitForGAs) + if oSession is not None: + # The guest has connected to TXS. + reporter.log('Guest reported success via TXS.'); + reporter.testDone(); + + fRc = True; + # Kudge: GAs doesn't come up correctly, so we have to reboot the guest first: + # Looks like VBoxService isn't there. + if oTestVm.fOptInstallAdditions: + reporter.testStart('Rebooting'); + fRc, oTxsSession = self.txsRebootAndReconnectViaTcp(oSession, oTxsSession); + reporter.testDone(); + + # If we're installing GAs, wait for them to come online: + if oTestVm.fOptInstallAdditions and fRc is True: + reporter.testStart('Guest additions'); + aenmRunLevels = [vboxcon.AdditionsRunLevelType_Userland,]; + if oTestVm.isLoggedOntoDesktop(): + aenmRunLevels.append(vboxcon.AdditionsRunLevelType_Desktop); + fRc = self.waitForGAs(oSession, cMsTimeout = cMsTimeout / 2, aenmWaitForRunLevels = aenmRunLevels, + aenmWaitForActive = (vboxcon.AdditionsFacilityType_VBoxGuestDriver, + vboxcon.AdditionsFacilityType_VBoxService,)); + reporter.testDone(); + + # Now do a save & restore test: + if fRc is True and self.fTestSaveAndRestore: + fRc, oSession, oTxsSession = self.testSaveAndRestore(oSession, oTxsSession, oTestVm); + + # Test GAs if requested: + if oTestVm.fOptInstallAdditions and fRc is True: + for oSubTstDrv in self.aoSubTstDrvs: + if oSubTstDrv.fEnabled: + reporter.testStart(oSubTstDrv.sTestName); + fRc2, oTxsSession = oSubTstDrv.testIt(oTestVm, oSession, oTxsSession); + reporter.testDone(fRc2 is None); + if fRc2 is False: + fRc = False; + + if oSession is not None: + fRc = self.terminateVmBySession(oSession) and fRc; + return fRc is True + + reporter.error('Installation of %s has failed' % (oTestVm.sVmName,)) + #oTestVm.detatchAndDeleteHd(self); # Save space. + reporter.testDone() + return False + + def testSaveAndRestore(self, oSession, oTxsSession, oTestVm): + """ + Tests saving and restoring the VM. + """ + _ = oTestVm; + reporter.testStart('Save'); + ## @todo + reporter.testDone(); + reporter.testStart('Restore'); + ## @todo + reporter.testDone(); + return (True, oSession, oTxsSession); + +if __name__ == '__main__': + sys.exit(tdGuestOsInstTest1().main(sys.argv)) diff --git a/src/VBox/ValidationKit/tests/network/Makefile.kmk b/src/VBox/ValidationKit/tests/network/Makefile.kmk new file mode 100644 index 00000000..2baf57a3 --- /dev/null +++ b/src/VBox/ValidationKit/tests/network/Makefile.kmk @@ -0,0 +1,51 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Network Tests. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +INSTALLS += ValidationKitTestsNetwork +ValidationKitTestsNetwork_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsNetwork_INST = $(INST_VALIDATIONKIT)tests/network/ +ValidationKitTestsNetwork_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdNetBenchmark1.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsNetwork_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/network/tdNetBenchmark1.py b/src/VBox/ValidationKit/tests/network/tdNetBenchmark1.py new file mode 100755 index 00000000..dccaa15f --- /dev/null +++ b/src/VBox/ValidationKit/tests/network/tdNetBenchmark1.py @@ -0,0 +1,633 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdNetBenchmark1.py $ + +""" +VirtualBox Validation Kit - Networking benchmark #1. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os; +import socket +import sys; + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testdriver import reporter; +from testdriver import base; +from testdriver import vbox; +from testdriver import vboxcon; + + +class tdNetBenchmark1(vbox.TestDriver): # pylint: disable=too-many-instance-attributes + """ + Networking benchmark #1. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.sLocalName = socket.getfqdn(); + self.sLocalIP = None + self.sRemoteName = None; + self.sRemoteIP = None; + self.sGuestName = None; + self.sGuestIP = None; + self.oGuestToGuestVM = None; + self.oGuestToGuestSess = None; + self.oGuestToGuestTxs = None; + self.asTestVMsDef = ['tst-rhel5', 'tst-win2k3ent', 'tst-sol10']; + self.asTestVMs = self.asTestVMsDef; + self.asSkipVMs = []; + self.asVirtModesDef = ['hwvirt', 'hwvirt-np', 'raw',] + self.asVirtModes = self.asVirtModesDef + self.acCpusDef = [1, 2,] + self.acCpus = self.acCpusDef; + self.asNicTypesDef = ['E1000', 'PCNet', 'Virtio',]; + self.asNicTypes = self.asNicTypesDef; + self.sNicAttachmentDef = 'bridged'; + self.sNicAttachment = self.sNicAttachmentDef; + self.asSetupsDef = ['g2h', 'g2r', 'g2g',]; + self.asSetups = self.asSetupsDef; + self.cSecsRunDef = 30; + self.cSecsRun = self.cSecsRunDef; + self.asTestsDef = ['tcp-latency', 'tcp-throughput', 'udp-latency', 'udp-throughput', 'tbench']; + self.asTests = self.asTestsDef + self.acbLatencyPktsDef = [32, 1024, 4096, 8192, 65536,]; + self.acbLatencyPkts = self.acbLatencyPktsDef + self.acbThroughputPktsDef = [8192, 65536]; + self.acbThroughputPkts = self.acbThroughputPktsDef + + try: self.sLocalName = socket.gethostbyname(self.sLocalName); + except: pass; + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdNetBenchmark1 Options:'); + reporter.log(' --remote-host <hostname|address>'); + reporter.log(' --local-host <hostname|address>'); + reporter.log(' --guest-host <hostname|address>'); + reporter.log(' --virt-modes <m1[:m2[:]]'); + reporter.log(' Default: %s' % (':'.join(self.asVirtModesDef))); + reporter.log(' --cpu-counts <c1[:c2[:]]'); + reporter.log(' Default: %s' % (':'.join(str(c) for c in self.acCpusDef))); + reporter.log(' --nic-types <type1[:type2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asNicTypes))); + reporter.log(' --nic-attachment <bridged|nat>'); + reporter.log(' Default: %s' % (self.sNicAttachmentDef)); + reporter.log(' --setups <s1[:s2[:]]>'); + reporter.log(' Default: %s (all)' % (':'.join(self.asSetupsDef))); + reporter.log(' --secs-per-run <seconds>'); + reporter.log(' Default: %s' % (self.cSecsRunDef)); + reporter.log(' --tests <s1[:s2[:]]>'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestsDef))); + reporter.log(' --latency-sizes <size1[:size2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(str(cb) for cb in self.acbLatencyPktsDef))); # pychecker bug? + reporter.log(' --throughput-sizes <size1[:size2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(str(cb) for cb in self.acbThroughputPktsDef))); # pychecker bug? + reporter.log(' --test-vms <vm1[:vm2[:...]]>'); + reporter.log(' Test the specified VMs in the given order. Use this to change'); + reporter.log(' the execution order or limit the choice of VMs'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestVMsDef))); + reporter.log(' --skip-vms <vm1[:vm2[:...]]>'); + reporter.log(' Skip the specified VMs when testing.'); + reporter.log(' --quick'); + reporter.log(' Shorthand for: --virt-modes hwvirt --cpu-counts 1 --secs-per-run 5 --latency-sizes 32'); + reporter.log(' --throughput-sizes 8192 --test-vms tst-rhel5:tst-win2k3ent:tst-sol10'); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--remote-host': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--remote-host" takes an IP address or a hostname'); + self.sRemoteName = asArgs[iArg]; + elif asArgs[iArg] == '--local-host': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--local-host" takes an IP address or a hostname'); + self.sLocalName = asArgs[iArg]; + elif asArgs[iArg] == '--guest-host': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--guest-host" takes an IP address or a hostname'); + self.sGuestName = asArgs[iArg]; + elif asArgs[iArg] == '--virt-modes': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--virt-modes" takes a colon separated list of modes'); + self.asVirtModes = asArgs[iArg].split(':'); + for s in self.asVirtModes: + if s not in self.asVirtModesDef: + raise base.InvalidOption('The "--virt-modes" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asVirtModesDef))); + elif asArgs[iArg] == '--cpu-counts': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--cpu-counts" takes a colon separated list of cpu counts'); + self.acCpus = []; + for s in asArgs[iArg].split(':'): + try: c = int(s); + except: raise base.InvalidOption('The "--cpu-counts" value "%s" is not an integer' % (s,)); + if c <= 0: raise base.InvalidOption('The "--cpu-counts" value "%s" is zero or negative' % (s,)); + self.acCpus.append(c); + elif asArgs[iArg] == '--nic-types': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--nic-types" takes a colon separated list of NIC types'); + self.asNicTypes = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--nic-attachment': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--nic-attachment" takes an argument'); + self.sNicAttachment = asArgs[iArg]; + if self.sNicAttachment not in ('bridged', 'nat'): + raise base.InvalidOption('The "--nic-attachment" value "%s" is not supported. Valid values are: bridged, nat' \ + % (self.sNicAttachment)); + elif asArgs[iArg] == '--setups': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--setups" takes a colon separated list of setups'); + self.asSetups = asArgs[iArg].split(':'); + for s in self.asSetups: + if s not in self.asSetupsDef: + raise base.InvalidOption('The "--setups" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asSetupsDef))); + elif asArgs[iArg] == '--secs-per-run': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--secs-per-run" takes second count'); + try: self.cSecsRun = int(asArgs[iArg]); + except: raise base.InvalidOption('The "--secs-per-run" value "%s" is not an integer' % (self.cSecsRun,)); + if self.cSecsRun <= 0: + raise base.InvalidOption('The "--secs-per-run" value "%s" is zero or negative.' % (self.cSecsRun,)); + elif asArgs[iArg] == '--tests': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--tests" takes a colon separated list of tests'); + self.asTests = asArgs[iArg].split(':'); + for s in self.asTests: + if s not in self.asTestsDef: + raise base.InvalidOption('The "--tests" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asTestsDef))); + elif asArgs[iArg] == '--latency-sizes': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--latency-sizes" takes a colon separated list of sizes'); + self.acbLatencyPkts = []; + for s in asArgs[iArg].split(':'): + try: cb = int(s); + except: raise base.InvalidOption('The "--latency-sizes" value "%s" is not an integer' % (s,)); + if cb <= 0: raise base.InvalidOption('The "--latency-sizes" value "%s" is zero or negative' % (s,)); + self.acbLatencyPkts.append(cb); + elif asArgs[iArg] == '--throughput-sizes': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--throughput-sizes" takes a colon separated list of sizes'); + self.acbThroughputPkts = []; + for s in asArgs[iArg].split(':'): + try: cb = int(s); + except: raise base.InvalidOption('The "--throughput-sizes" value "%s" is not an integer' % (s,)); + if cb <= 0: raise base.InvalidOption('The "--throughput-sizes" value "%s" is zero or negative' % (s,)); + self.acbThroughputPkts.append(cb); + elif asArgs[iArg] == '--test-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--test-vms" takes colon separated list'); + self.asTestVMs = asArgs[iArg].split(':'); + for s in self.asTestVMs: + if s not in self.asTestVMsDef: + raise base.InvalidOption('The "--test-vms" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asTestVMsDef))); + elif asArgs[iArg] == '--skip-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--skip-vms" takes colon separated list'); + self.asSkipVMs = asArgs[iArg].split(':'); + for s in self.asSkipVMs: + if s not in self.asTestVMsDef: + reporter.log('warning: The "--test-vms" value "%s" does not specify any of our test VMs.' % (s)); + elif asArgs[iArg] == '--quick': + self.cSecsRun = 5; + self.asVirtModes = ['hwvirt',]; + self.acCpus = [1,]; + self.acbLatencyPkts = [32,]; + self.acbThroughputPkts = [8192,]; + self.asTestVMs = ['tst-rhel5', 'tst-win2k3ent', 'tst-sol10',]; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def completeOptions(self): + # Remove skipped VMs from the test list. + for sVM in self.asSkipVMs: + try: self.asTestVMs.remove(sVM); + except: pass; + + # Resolve any names we've been given. + self.sLocalIP = base.tryGetHostByName(self.sLocalName); + self.sRemoteIP = base.tryGetHostByName(self.sRemoteName); + self.sGuestIP = base.tryGetHostByName(self.sGuestName); + + reporter.log('Local IP : %s' % (self.sLocalIP)); + reporter.log('Remote IP: %s' % (self.sRemoteIP)); + if self.sGuestIP is None: + reporter.log('Guest IP : use tst-guest2guest'); + else: + reporter.log('Guest IP : %s' % (self.sGuestIP)); + + return vbox.TestDriver.completeOptions(self); + + def getResourceSet(self): + # Construct the resource list the first time it's queried. + if self.asRsrcs is None: + self.asRsrcs = []; + if 'tst-rhel5' in self.asTestVMs or 'g2g' in self.asSetups: + self.asRsrcs.append('3.0/tcp/rhel5.vdi'); + if 'tst-rhel5-64' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/rhel5-64.vdi'); + if 'tst-sles11' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/sles11.vdi'); + if 'tst-sles11-64' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/sles11-64.vdi'); + if 'tst-oel' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/oel.vdi'); + if 'tst-oel-64' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/oel-64.vdi'); + if 'tst-win2k3ent' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/win2k3ent-acpi.vdi'); + if 'tst-win2k3ent-64' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/win2k3ent-64.vdi'); + if 'tst-win2k8' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/win2k8.vdi'); + if 'tst-sol10' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/solaris10.vdi'); + if 'tst-sol11' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/solaris11.vdi'); + return self.asRsrcs; + + def actionConfig(self): + # Some stupid trickery to guess the location of the iso. ## fixme - testsuite unzip ++ + sVBoxValidationKit_iso = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../VBoxValidationKit.iso')); + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../VBoxTestSuite.iso')); + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/ramdisk/vbox/svn/trunk/validationkit/VBoxValidationKit.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/ramdisk/vbox/svn/trunk/testsuite/VBoxTestSuite.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sCur = os.getcwd(); + for i in range(0, 10): + sVBoxValidationKit_iso = os.path.join(sCur, 'validationkit/VBoxValidationKit.iso'); + if os.path.isfile(sVBoxValidationKit_iso): + break; + sVBoxValidationKit_iso = os.path.join(sCur, 'testsuite/VBoxTestSuite.iso'); + if os.path.isfile(sVBoxValidationKit_iso): + break; + sCur = os.path.abspath(os.path.join(sCur, '..')); + if i is None: pass; # shut up pychecker/pylint. + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/home/bird/validationkit/VBoxValidationKit.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/home/bird/testsuite/VBoxTestSuite.iso'; + + # Make sure vboxapi has been imported so we can use the constants. + if not self.importVBoxApi(): + return False; + + # Guest to Guest VM. + if self.sGuestName is None and 'g2g' in self.asSetups: + oVM = self.createTestVM('tst-guest2guest', 0, '3.0/tcp/rhel5.vdi', sKind = 'RedHat', fIoApic = True, \ + eNic0Type = vboxcon.NetworkAdapterType_I82545EM, \ + eNic0AttachType = vboxcon.NetworkAttachmentType_Bridged, \ + fVirtEx = True, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + self.oGuestToGuestVM = oVM; + + # + # Configure the VMs we're going to use. + # + eNic0AttachType = vboxcon.NetworkAttachmentType_Bridged; + if self.sNicAttachment == 'nat': + eNic0AttachType = vboxcon.NetworkAttachmentType_NAT; + + # Linux VMs + if 'tst-rhel5' in self.asTestVMs: + oVM = self.createTestVM('tst-rhel5', 1, '3.0/tcp/rhel5.vdi', sKind = 'RedHat', fIoApic = True, \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-rhel5-64' in self.asTestVMs: + oVM = self.createTestVM('tst-rhel5-64', 1, '3.0/tcp/rhel5-64.vdi', sKind = 'RedHat_64', \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-sles11' in self.asTestVMs: + oVM = self.createTestVM('tst-sles11', 1, '3.0/tcp/sles11.vdi', sKind = 'OpenSUSE', fIoApic = True, \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-sles11-64' in self.asTestVMs: + oVM = self.createTestVM('tst-sles11-64', 1, '3.0/tcp/sles11-64.vdi', sKind = 'OpenSUSE_64', \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-oel' in self.asTestVMs: + oVM = self.createTestVM('tst-oel', 1, '3.0/tcp/oel.vdi', sKind = 'Oracle', fIoApic = True, \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-oel-64' in self.asTestVMs: + oVM = self.createTestVM('tst-oel-64', 1, '3.0/tcp/oel-64.vdi', sKind = 'Oracle_64', \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + # Windows VMs + if 'tst-win2k3ent' in self.asTestVMs: + oVM = self.createTestVM('tst-win2k3ent', 1, '3.0/tcp/win2k3ent-acpi.vdi', sKind = 'Windows2003', \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-win2k3ent-64' in self.asTestVMs: + oVM = self.createTestVM('tst-win2k3ent-64', 1, '3.0/tcp/win2k3ent-64.vdi', sKind = 'Windows2003_64', \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-win2k8' in self.asTestVMs: + oVM = self.createTestVM('tst-win2k8', 1, '3.0/tcp/win2k8.vdi', sKind = 'Windows2008_64', \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + # Solaris VMs + if 'tst-sol10' in self.asTestVMs: + oVM = self.createTestVM('tst-sol10', 1, '3.0/tcp/solaris10.vdi', sKind = 'Solaris_64', \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-sol11' in self.asTestVMs: + oVM = self.createTestVM('tst-sol11', 1, '3.0/tcp/os2009-11.vdi', sKind = 'Solaris_64', \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + return True; + + def actionExecute(self): + """ + Execute the testcase. + """ + fRc = self.test1(); + return fRc; + + + # + # Test execution helpers. + # + + def test1RunTestProgs(self, oTxsSession, fRc, sTestName, sAddr): + """ + Runs all the test programs against one 'server' machine. + """ + reporter.testStart(sTestName); + + reporter.testStart('TCP latency'); + if fRc and 'tcp-latency' in self.asTests and sAddr is not None: + for cbPkt in self.acbLatencyPkts: + fRc = self.txsRunTest(oTxsSession, '%u bytes' % (cbPkt), self.cSecsRun * 1000 * 4, + '${CDROM}/${OS/ARCH}/NetPerf${EXESUFF}', + ('NetPerf', '--client', sAddr, '--interval', self.cSecsRun, '--len', cbPkt, + '--mode', 'latency')); + if not fRc: + break; + reporter.testDone(); + else: + reporter.testDone(fSkipped = True); + + reporter.testStart('TCP throughput'); + if fRc and 'tcp-throughput' in self.asTests and sAddr is not None: + for cbPkt in self.acbThroughputPkts: + fRc = self.txsRunTest(oTxsSession, '%u bytes' % (cbPkt), self.cSecsRun * 2 * 1000 * 4, + '${CDROM}/${OS/ARCH}/NetPerf${EXESUFF}', + ('NetPerf', '--client', sAddr, '--interval', self.cSecsRun, '--len', cbPkt, + '--mode', 'throughput')); + if not fRc: + break; + reporter.testDone(); + else: + reporter.testDone(fSkipped = True); + + reporter.testStart('UDP latency'); + if fRc and 'udp-latency' in self.asTests and sAddr is not None: + ## @todo Netperf w/UDP. + reporter.testDone(fSkipped = True); + else: + reporter.testDone(fSkipped = True); + + reporter.testStart('UDP throughput'); + if fRc and 'udp-throughput' in self.asTests and sAddr is not None: + ## @todo Netperf w/UDP. + reporter.testDone(fSkipped = True); + else: + reporter.testDone(fSkipped = True); + + reporter.testStart('tbench'); + if fRc and 'tbench' in self.asTests and sAddr is not None: + ## @todo tbench. + reporter.testDone(fSkipped = True); + else: + reporter.testDone(fSkipped = True); + + reporter.testDone(not fRc); + return fRc; + + def test1OneCfg(self, sVmName, eNicType, cCpus, fHwVirt, fNestedPaging): + """ + Runs the specified VM thru test #1. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + oVM = self.getVmByName(sVmName); + + # Reconfigure the VM + fRc = True; + oSession = self.openSession(oVM); + if oSession is not None: + fRc = fRc and oSession.setNicType(eNicType); + fRc = fRc and oSession.enableVirtEx(fHwVirt); + fRc = fRc and oSession.enableNestedPaging(fNestedPaging); + fRc = fRc and oSession.setCpuCount(cCpus); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + + # Start up. + if fRc is True: + self.logVmInfo(oVM); + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(sVmName, fCdWait = True); + if oSession is not None: + self.addTask(oTxsSession); + + # Fudge factor - Allow the guest to finish starting up. + self.sleep(5); + + # Benchmark #1 - guest <-> host. + if 'g2h' in self.asSetups: + self.test1RunTestProgs(oTxsSession, fRc, 'guest <-> host', self.sLocalIP); + + # Benchmark #2 - guest <-> host. + if 'g2r' in self.asSetups: + self.test1RunTestProgs(oTxsSession, fRc, 'guest <-> remote', self.sRemoteIP); + + # Benchmark #3 - guest <-> guest. + if 'g2g' in self.asSetups: + self.test1RunTestProgs(oTxsSession, fRc, 'guest <-> guest', self.sGuestIP); + + # cleanup. + self.removeTask(oTxsSession); + self.terminateVmBySession(oSession) + else: + fRc = False; + return fRc; + + def test1OneVM(self, sVmName, asSkipNicTypes = (), asSupVirtModes = None, rSupCpus = range(1, 256)): + """ + Runs one VM thru the various configurations. + """ + if asSupVirtModes is None: + asSupVirtModes = self.asVirtModes; + + reporter.testStart(sVmName); + fRc = True; + for sNicType in self.asNicTypes: + if sNicType in asSkipNicTypes: + continue; + reporter.testStart(sNicType); + + if sNicType == 'E1000': + eNicType = vboxcon.NetworkAdapterType_I82545EM; + elif sNicType == 'PCNet': + eNicType = vboxcon.NetworkAdapterType_Am79C973; + elif sNicType == 'Virtio': + eNicType = vboxcon.NetworkAdapterType_Virtio; + else: + eNicType = None; + + for cCpus in self.acCpus: + if cCpus == 1: reporter.testStart('1 cpu'); + else: reporter.testStart('%u cpus' % (cCpus)); + + for sVirtMode in self.asVirtModes: + if sVirtMode == 'raw' and cCpus > 1: + continue; + if cCpus not in rSupCpus: + continue; + if sVirtMode not in asSupVirtModes: + continue; + hsVirtModeDesc = {}; + hsVirtModeDesc['raw'] = 'Raw-mode'; + hsVirtModeDesc['hwvirt'] = 'HwVirt'; + hsVirtModeDesc['hwvirt-np'] = 'NestedPaging'; + reporter.testStart(hsVirtModeDesc[sVirtMode]); + + fHwVirt = sVirtMode != 'raw'; + fNestedPaging = sVirtMode == 'hwvirt-np'; + fRc = self.test1OneCfg(sVmName, eNicType, cCpus, fHwVirt, fNestedPaging) and fRc and True; # pychecker hack. + + reporter.testDone(); + reporter.testDone(); + reporter.testDone(); + reporter.testDone(); + return fRc; + + def test1(self): + """ + Executes test #1. + """ + + # Start the VM for the guest to guest testing, if required. + fRc = True; + if 'g2g' in self.asSetups and self.sGuestName is None: + self.oGuestToGuestSess, self.oGuestToGuestTxs = self.startVmAndConnectToTxsViaTcp('tst-guest2guest', fCdWait = True); + if self.oGuestToGuestSess is None: + return False; + self.sGuestIP = self.oGuestToGuestSess.getPrimaryIp(); + reporter.log('tst-guest2guest IP: %s' % (self.sGuestIP)); + + # Start the test servers on it. + fRc = self.oGuestToGuestTxs.syncExec('${CDROM}/${OS/ARCH}/NetPerf${EXESUFF}', + ('NetPerf', '--server', '--daemonize'), fWithTestPipe=False); + + # Loop thru the test VMs. + if fRc: + for sVM in self.asTestVMs: + # figure args. + asSkipNicTypes = []; + if sVM not in ('tst-sles11', 'tst-sles11-64'): + asSkipNicTypes.append('Virtio'); + if sVM in ('tst-sol11', 'tst-sol10'): + asSkipNicTypes.append('PCNet'); + asSupVirtModes = None; + if sVM in ('tst-sol11', 'tst-sol10'): # 64-bit only + asSupVirtModes = ('hwvirt', 'hwvirt-np',); + + # run test on the VM. + if not self.test1OneVM(sVM, asSkipNicTypes, asSupVirtModes): + fRc = False; + + # Kill the guest to guest VM and clean up the state. + if self.oGuestToGuestSess is not None: + self.terminateVmBySession(self.oGuestToGuestSess); + self.oGuestToGuestSess = None; + self.oGuestToGuestTxs = None; + self.sGuestIP = None; + + return fRc; + + + +if __name__ == '__main__': + sys.exit(tdNetBenchmark1().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/selftests/Makefile.kmk b/src/VBox/ValidationKit/tests/selftests/Makefile.kmk new file mode 100644 index 00000000..9361690a --- /dev/null +++ b/src/VBox/ValidationKit/tests/selftests/Makefile.kmk @@ -0,0 +1,54 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Testsuite & TestManager Tests. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +INSTALLS += ValidationKitTestsSelfTests +ValidationKitTestsSelfTests_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsSelfTests_INST = $(INST_VALIDATIONKIT)tests/selftests/ +ValidationKitTestsSelfTests_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdSelfTest1.py \ + $(PATH_SUB_CURRENT)/tdSelfTest2.py \ + $(PATH_SUB_CURRENT)/tdSelfTest3.py \ + $(PATH_SUB_CURRENT)/tdSelfTest4.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsSelfTests_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/selftests/tdSelfTest1.py b/src/VBox/ValidationKit/tests/selftests/tdSelfTest1.py new file mode 100755 index 00000000..851430c0 --- /dev/null +++ b/src/VBox/ValidationKit/tests/selftests/tdSelfTest1.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdSelfTest1.py $ + +""" +Test Manager Self Test - Dummy Test Driver. +""" + +from __future__ import print_function; + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +import sys; +import os; + +print('dummydriver.py: hello world!'); +print('dummydriver.py: args: %s' % (sys.argv,)); + +print('dummydriver.py: environment:'); +for sVar in sorted(os.environ.keys()): # pylint: disable=consider-iterating-dictionary + print('%s=%s' % (sVar, os.environ[sVar])); + +if sys.argv[-1] in [ 'all', 'execute' ]: + + import time; + + for i in range(10, 1, -1): + print('dummydriver.py: %u...', i); + sys.stdout.flush(); + time.sleep(1); + print('dummydriver.py: ...0! done'); + +sys.exit(0); + diff --git a/src/VBox/ValidationKit/tests/selftests/tdSelfTest2.py b/src/VBox/ValidationKit/tests/selftests/tdSelfTest2.py new file mode 100755 index 00000000..9fca04e6 --- /dev/null +++ b/src/VBox/ValidationKit/tests/selftests/tdSelfTest2.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdSelfTest2.py $ + +""" +Test Manager / Suite Self Test #2 - Everything should succeed. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os; +import sys; + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from common import utils; +from testdriver import reporter; +from testdriver.base import TestDriverBase; + + +class tdSelfTest2(TestDriverBase): + """ + Test Manager / Suite Self Test #2 - Everything should succeed. + """ + + def __init__(self): + TestDriverBase.__init__(self); + + + def actionExecute(self): + reporter.testStart('reporter.testXXXX API'); + reporter.testValue('value-name1', 123456789, 'ms'); + + reporter.testStart('subtest'); + reporter.testValue('value-name2', 11223344, 'times'); + reporter.testDone(); + + reporter.testStart('subtest2'); + reporter.testValue('value-name3', 39, 'sec'); + reporter.testValue('value-name4', 42, 'ns'); + reporter.testDone(); + + reporter.testStart('subtest3'); + reporter.testDone(fSkipped = True); + + # No spaces in XML. + reporter.testStart('subtest4'); + oSubXmlFile = reporter.FileWrapperTestPipe(); + oSubXmlFile.write('<?xml version="1.0" encoding="UTF-8" ?>'); + oSubXmlFile.write('<Test timestamp="%s" name="foobar1">' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write('<Test timestamp="%s" name="sub1">' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write('<Passed timestamp="%s"/>' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write('</Test>'); + oSubXmlFile.write('<End timestamp="%s" errors="0"/>' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write('</Test>'); + oSubXmlFile.close(); + oSubXmlFile = None; + reporter.testDone(); + + # Spaces + funny line endings. + reporter.testStart('subtest5'); + oSubXmlFile = reporter.FileWrapperTestPipe(); + oSubXmlFile.write('<?xml version="1.0" encoding="UTF-8" ?>\r\n'); + oSubXmlFile.write('<Test timestamp="%s" name="foobar2">\n\n\t\n\r\n' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write('<Test timestamp="%s" name="sub2">' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write(' <Passed timestamp="%s"/>\n' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write(' </Test>\n'); + oSubXmlFile.write(' <End timestamp="%s" errors="0"/>\r' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write('</Test>'); + oSubXmlFile.close(); + reporter.testDone(); + + # A few long log times for WUI log testing. + reporter.log('long line: asdfasdfljkasdlfkjasldkfjlaksdfjl asdlfkjasdlkfjalskdfjlaksdjfa falkjaldkjfalskdjflaksdjf ' \ + 'lajksdflkjasdlfkjalsdfj asldfkjlaskdjflaksdjflaksdjflkj asdlfkjalsdkfjalsdkjflaksdj fasdlfkj ' \ + 'asdlkfj aljkasdflkj alkjdsf lakjsdf'); + reporter.log('long line: asdfasdfljkasdlfkjasldkfjlaksdfjl asdlfkjasdlkfjalskdfjlaksdjfa falkjaldkjfalskdjflaksdjf ' \ + 'lajksdflkjasdlfkjalsdfj asldfkjlaskdjflaksdjflaksdjflkj asdlfkjalsdkfjalsdkjflaksdj fasdlfkj ' \ + 'asdlkfj aljkasdflkj alkjdsf lakjsdf'); + reporter.log('long line: asdfasdfljkasdlfkjasldkfjlaksdfjl asdlfkjasdlkfjalskdfjlaksdjfa falkjaldkjfalskdjflaksdjf ' \ + 'lajksdflkjasdlfkjalsdfj asldfkjlaskdjflaksdjflaksdjflkj asdlfkjalsdkfjalsdkjflaksdj fasdlfkj ' \ + 'asdlkfj aljkasdflkj alkjdsf lakjsdf'); + + # Upload a file. + reporter.addLogFile(__file__, sKind = 'log/release/vm'); + + reporter.testDone(); + return True; + + +if __name__ == '__main__': + sys.exit(tdSelfTest2().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/selftests/tdSelfTest3.py b/src/VBox/ValidationKit/tests/selftests/tdSelfTest3.py new file mode 100755 index 00000000..5f1273b1 --- /dev/null +++ b/src/VBox/ValidationKit/tests/selftests/tdSelfTest3.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdSelfTest3.py $ + +""" +Test Manager / Suite Self Test #3 - Bad XML input and other Failures. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os; +import sys; + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from common import utils; +from testdriver import reporter; +from testdriver.base import TestDriverBase; + + +class tdSelfTest3(TestDriverBase): + """ + Test Manager / Suite Self Test #3 - Bad XML input and other Failures. + """ + + def __init__(self): + TestDriverBase.__init__(self); + + + def actionExecute(self): + + # Testing PushHint/PopHint. + reporter.testStart('Negative XML #1'); + oSubXmlFile = reporter.FileWrapperTestPipe(); + oSubXmlFile.write('<Test timestamp="%s" name="foobar3">\n\n\t\n\r\n' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write('<Test timestamp="%s" name="sub3">' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write('<Test timestamp="%s" name="subsub1">' % (utils.getIsoTimestamp(),)); + oSubXmlFile.close(); + reporter.testDone(); + + # Missing end, like we had with IRPT at one time. + reporter.testStart('Negative XML #2 (IPRT)'); + oSubXmlFile = reporter.FileWrapperTestPipe(); + oSubXmlFile.write(""" +<?xml version="1.0" encoding="UTF-8" ?> +<Test timestamp="2013-05-29T08:59:05.930602000Z" name="tstRTGetOpt"> + <Test timestamp="2013-05-29T08:59:05.930656000Z" name="Basics"> + <Passed timestamp="2013-05-29T08:59:05.930756000Z"/> + </Test> + <Test timestamp="2013-05-29T08:59:05.930995000Z" name="RTGetOpt - IPv4"> + <Passed timestamp="2013-05-29T08:59:05.931036000Z"/> + </Test> + <Test timestamp="2013-05-29T08:59:05.931161000Z" name="RTGetOpt - MAC Address"> + <Passed timestamp="2013-05-29T08:59:05.931194000Z"/> + </Test> + <Test timestamp="2013-05-29T08:59:05.931313000Z" name="RTGetOpt - Option w/ Index"> + <Passed timestamp="2013-05-29T08:59:05.931357000Z"/> + </Test> + <Test timestamp="2013-05-29T08:59:05.931475000Z" name="RTGetOptFetchValue"> + <Passed timestamp="2013-05-29T08:59:05.931516000Z"/> + </Test> + <Test timestamp="2013-05-29T08:59:05.931640000Z" name="RTGetOpt - bool on/off"> + <Passed timestamp="2013-05-29T08:59:05.931687000Z"/> + </Test> + <Test timestamp="2013-05-29T08:59:05.931807000Z" name="Standard options"> + <Passed timestamp="2013-05-29T08:59:05.931843000Z"/> + </Test> + <Test timestamp="2013-05-29T08:59:05.931963000Z" name="Options first"> + <Passed timestamp="2013-05-29T08:59:05.932035000Z"/> + </Test> +"""); + oSubXmlFile.close(); + oSubXmlFile = None; + reporter.testDone(); + + # The use of testFailure. + reporter.testStart('Using testFailure()'); + reporter.testValue('value-name3', 12345678, 'times'); + reporter.testFailure('failure detail message'); + reporter.testDone(); + + return True; + + +if __name__ == '__main__': + sys.exit(tdSelfTest3().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/selftests/tdSelfTest4.py b/src/VBox/ValidationKit/tests/selftests/tdSelfTest4.py new file mode 100755 index 00000000..1aff45ef --- /dev/null +++ b/src/VBox/ValidationKit/tests/selftests/tdSelfTest4.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdSelfTest4.py $ + +""" +Test Manager / Suite Self Test #4 - Testing result overflow handling. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os; +import sys; + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testdriver import reporter; +from testdriver.base import TestDriverBase, InvalidOption; + + +class tdSelfTest4(TestDriverBase): + """ + Test Manager / Suite Self Test #4 - Testing result overflow handling. + """ + + ## Valid tests. + kasValidTests = [ 'immediate-sub-tests', 'total-sub-tests', 'immediate-values', 'total-values', 'immediate-messages']; + + def __init__(self): + TestDriverBase.__init__(self); + self.sOptWhich = 'immediate-sub-tests'; + + def parseOption(self, asArgs, iArg): + if asArgs[iArg] == '--test': + iArg = self.requireMoreArgs(1, asArgs, iArg); + if asArgs[iArg] not in self.kasValidTests: + raise InvalidOption('Invalid test name "%s". Must be one of: %s' + % (asArgs[iArg], ', '.join(self.kasValidTests),)); + self.sOptWhich = asArgs[iArg]; + else: + return TestDriverBase.parseOption(self, asArgs, iArg); + return iArg + 1; + + def actionExecute(self): + # Too many immediate sub-tests. + if self.sOptWhich == 'immediate-sub-tests': + reporter.testStart('Too many immediate sub-tests (negative)'); + for i in range(1024): + reporter.testStart('subsub%d' % i); + reporter.testDone(); + # Too many sub-tests in total. + elif self.sOptWhich == 'total-sub-tests': + reporter.testStart('Too many sub-tests (negative)'); + # 32 * 256 = 2^(5+8) = 2^13 = 8192. + for i in range(32): + reporter.testStart('subsub%d' % i); + for j in range(256): + reporter.testStart('subsubsub%d' % j); + reporter.testDone(); + reporter.testDone(); + # Too many immediate values. + elif self.sOptWhich == 'immediate-values': + reporter.testStart('Too many immediate values (negative)'); + for i in range(512): + reporter.testValue('value%d' % i, i, 'times'); + # Too many values in total. + elif self.sOptWhich == 'total-values': + reporter.testStart('Too many sub-tests (negative)'); + for i in range(256): + reporter.testStart('subsub%d' % i); + for j in range(64): + reporter.testValue('value%d' % j, i * 10000 + j, 'times'); + reporter.testDone(); + # Too many failure reasons (only immediate since the limit is extremely low). + elif self.sOptWhich == 'immediate-messages': + reporter.testStart('Too many immediate messages (negative)'); + for i in range(16): + reporter.testFailure('Detail %d' % i); + else: + reporter.testStart('Unknown test %s' % (self.sOptWhich,)); + reporter.error('Invalid test selected: %s' % (self.sOptWhich,)); + reporter.testDone(); + return True; + + +if __name__ == '__main__': + sys.exit(tdSelfTest4().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/serial/Makefile.kmk b/src/VBox/ValidationKit/tests/serial/Makefile.kmk new file mode 100644 index 00000000..ba457d1e --- /dev/null +++ b/src/VBox/ValidationKit/tests/serial/Makefile.kmk @@ -0,0 +1,52 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Serial port. +# + +# +# Copyright (C) 2018-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +INSTALLS += ValidationKitTestsSerial +ValidationKitTestsSerial_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsSerial_INST = $(INST_VALIDATIONKIT)tests/serial/ +ValidationKitTestsSerial_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdSerial1.py \ + $(PATH_SUB_CURRENT)/loopback.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsSerial_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/serial/loopback.py b/src/VBox/ValidationKit/tests/serial/loopback.py new file mode 100755 index 00000000..d4b55260 --- /dev/null +++ b/src/VBox/ValidationKit/tests/serial/loopback.py @@ -0,0 +1,255 @@ +# -*- coding: utf-8 -*- +# $Id: loopback.py $ + +""" +VirtualBox Validation Kit - Serial loopback module. +""" + +__copyright__ = \ +""" +Copyright (C) 2018-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Standard Python imports. +#import os; +import socket; +import threading; + + +g_ksLoopbackTcpServ = 'TcpServ'; +g_ksLoopbackTcpClient = 'TcpClient'; +g_ksLoopbackNamedPipeServ = 'NamedPipeServ'; +g_ksLoopbackNamedPipeClient = 'NamedPipeClient'; + +class SerialLoopbackTcpServ(object): + """ + Handler for a server TCP style connection. + """ + def __init__(self, sLocation, iTimeout): + sHost, sPort = sLocation.split(':'); + self.oConn = None; + self.oSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM); + self.oSock.settimeout(iTimeout); + self.oSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1); + self.oSock.bind((sHost, int(sPort))); + self.oSock.listen(1); + self.iTimeout = iTimeout; + + def __del__(self): + if self.oConn is not None: + self.oConn.close(); + if self.oSock is not None: + self.oSock.close(); + self.oSock = None; + + def shutdown(self): + if self.oConn is not None: + self.oConn.close(); + self.oConn = None; + self.oSock.close(); + self.oSock = None; + + def pumpIo(self): + """ + Main I/O pumping routine. + """ + try: + if self.oConn is None: + oConn, _ = self.oSock.accept(); + self.oConn = oConn; + else: + abData = self.oConn.recv(1024); # pylint: disable=no-member + if abData is not None: + self.oConn.send(abData); # pylint: disable=no-member + except: + pass; + +class SerialLoopbackTcpClient(object): + """ + Handler for a client TCP style connection. + """ + def __init__(self, sLocation, iTimeout): + sHost, sPort = sLocation.split(':'); + self.oConn = socket.socket(socket.AF_INET, socket.SOCK_STREAM); + self.oConn.connect((sHost, int(sPort))); + self.oConn.settimeout(iTimeout); + self.iTimeout = iTimeout; + + def __del__(self): + if self.oConn is not None: + self.oConn.close(); + + def shutdown(self): + if self.oConn is not None: + self.oConn.close(); + self.oConn = None; + + def pumpIo(self): + """ + Main I/O pumping routine. + """ + try: + abData = self.oConn.recv(1024); + if abData is not None: + self.oConn.send(abData); + except: + pass; + +class SerialLoopbackNamedPipeServ(object): + """ + Handler for a named pipe server style connection. + """ + def __init__(self, sLocation, iTimeout): + self.oConn = None; + self.oSock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM); # pylint: disable=no-member + self.oSock.settimeout(iTimeout); + self.oSock.bind(sLocation); + self.oSock.listen(1); + self.iTimeout = iTimeout; + + def __del__(self): + if self.oConn is not None: + self.oConn.close(); + if self.oSock is not None: + self.oSock.close(); + self.oSock = None; + + def shutdown(self): + if self.oConn is not None: + self.oConn.close(); + self.oConn = None; + self.oSock.close(); + self.oSock = None; + + def pumpIo(self): + """ + Main I/O pumping routine. + """ + try: + if self.oConn is None: + oConn, _ = self.oSock.accept(); + self.oConn = oConn; + else: + abData = self.oConn.recv(1024); # pylint: disable=no-member + if abData is not None: + self.oConn.send(abData); # pylint: disable=no-member + except: + pass; + +class SerialLoopbackNamedPipeClient(object): + """ + Handler for a named pipe client style connection. + """ + def __init__(self, sLocation, iTimeout): + self.oConn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM); # pylint: disable=no-member + self.oConn.connect(sLocation); + self.oConn.settimeout(iTimeout); + self.iTimeout = iTimeout; + + def __del__(self): + if self.oConn is not None: + self.oConn.close(); + + def shutdown(self): + if self.oConn is not None: + self.oConn.close(); + self.oConn = None; + + def pumpIo(self): + """ + Main I/O pumping routine. + """ + try: + abData = self.oConn.recv(1024); + if abData is not None: + self.oConn.send(abData); + except: + pass; + +class SerialLoopback(object): + """ + Serial port loopback module working with TCP and named pipes. + """ + + def __init__(self, sType, sLocation): + self.fShutdown = False; + self.sType = sType; + self.sLocation = sLocation; + self.oLock = threading.Lock(); + self.oThread = threading.Thread(target=self.threadWorker, args=(), name=('SerLoopback')); + + if sType == g_ksLoopbackTcpServ: + self.oIoPumper = SerialLoopbackTcpServ(sLocation, 0.5); + self.oThread.start(); + elif sType == g_ksLoopbackNamedPipeServ: + self.oIoPumper = SerialLoopbackNamedPipeServ(sLocation, 0.5); # pylint: disable=redefined-variable-type + self.oThread.start(); + + def connect(self): + """ + Connects to the server for a client type version. + """ + fRc = True; + try: + if self.sType == g_ksLoopbackTcpClient: + self.oIoPumper = SerialLoopbackTcpClient(self.sLocation, 0.5); + elif self.sType == g_ksLoopbackNamedPipeClient: + self.oIoPumper = SerialLoopbackNamedPipeClient(self.sLocation, 0.5); # pylint: disable=redefined-variable-type + except: + fRc = False; + else: + self.oThread.start(); + return fRc; + + def shutdown(self): + """ + Shutdown any connection and wait for it to become idle. + """ + with self.oLock: + self.fShutdown = True; + self.oIoPumper.shutdown(); + + def isShutdown(self): + """ + Returns whether the I/O pumping thread should shut down. + """ + with self.oLock: + fShutdown = self.fShutdown; + + return fShutdown; + + def threadWorker(self): + """ + The threaded worker. + """ + while not self.isShutdown(): + self.oIoPumper.pumpIo(); + diff --git a/src/VBox/ValidationKit/tests/serial/tdSerial1.py b/src/VBox/ValidationKit/tests/serial/tdSerial1.py new file mode 100755 index 00000000..a7166bbe --- /dev/null +++ b/src/VBox/ValidationKit/tests/serial/tdSerial1.py @@ -0,0 +1,345 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdSerial1.py $ + +""" +VirtualBox Validation Kit - Serial port testing #1. +""" + +__copyright__ = \ +""" +Copyright (C) 2018-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os; +import random; +import string; +import struct; +import sys; + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testdriver import base; +from testdriver import reporter; +from testdriver import vbox; +from testdriver import vboxcon; + +import loopback; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + xrange = range; # pylint: disable=redefined-builtin,invalid-name + + +class tdSerial1(vbox.TestDriver): + """ + VBox serial port testing #1. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.oTestVmSet = self.oTestVmManager.selectSet(self.oTestVmManager.kfGrpStdSmoke); + self.asSerialModesDef = ['RawFile', 'Tcp', 'TcpServ', 'NamedPipe', 'NamedPipeServ', 'HostDev']; + self.asSerialModes = self.asSerialModesDef; + self.asSerialTestsDef = ['Write', 'ReadWrite']; + self.asSerialTests = self.asSerialTestsDef; + self.asUartsDef = ['16450', '16550A', '16750']; + self.asUarts = self.asUartsDef; + self.oLoopback = None; + self.sLocation = None; + self.fVerboseTest = False; + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdSerial1 Options:'); + reporter.log(' --serial-modes <m1[:m2[:]]'); + reporter.log(' Default: %s' % (':'.join(self.asSerialModesDef))); + reporter.log(' --serial-tests <t1[:t2[:]]'); + reporter.log(' Default: %s' % (':'.join(self.asSerialTestsDef))); + reporter.log(' --uarts <u1[:u2[:]]'); + reporter.log(' Default: %s' % (':'.join(self.asUartsDef))); + reporter.log(' --verbose-test'); + reporter.log(' Whether to enable verbose output when running the'); + reporter.log(' test utility inside the VM'); + return rc; + + def parseOption(self, asArgs, iArg): + if asArgs[iArg] == '--serial-modes': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--serial-modes" takes a colon separated list of serial port modes to test'); + self.asSerialModes = asArgs[iArg].split(':'); + for s in self.asSerialModes: + if s not in self.asSerialModesDef: + reporter.log('warning: The "--serial-modes" value "%s" is not a valid serial port mode.' % (s)); + elif asArgs[iArg] == '--serial-tests': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--serial-tests" takes a colon separated list of serial port tests'); + self.asSerialTests = asArgs[iArg].split(':'); + for s in self.asSerialTests: + if s not in self.asSerialTestsDef: + reporter.log('warning: The "--serial-tests" value "%s" is not a valid serial port test.' % (s)); + elif asArgs[iArg] == '--aurts': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--uarts" takes a colon separated list of uarts to test'); + self.asUarts = asArgs[iArg].split(':'); + for s in self.asUarts: + if s not in self.asUartsDef: + reporter.log('warning: The "--uarts" value "%s" is not a valid uart.' % (s)); + elif asArgs[iArg] == '--verbose-test': + iArg += 1; + self.fVerboseTest = True; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + + return iArg + 1; + + def actionVerify(self): + if self.sVBoxValidationKitIso is None or not os.path.isfile(self.sVBoxValidationKitIso): + reporter.error('Cannot find the VBoxValidationKit.iso! (%s)' + 'Please unzip a Validation Kit build in the current directory or in some parent one.' + % (self.sVBoxValidationKitIso,) ); + return False; + return vbox.TestDriver.actionVerify(self); + + def actionConfig(self): + # Make sure vboxapi has been imported so we can use the constants. + if not self.importVBoxApi(): + return False; + + assert self.sVBoxValidationKitIso is not None; + return self.oTestVmSet.actionConfig(self, sDvdImage = self.sVBoxValidationKitIso); + + def actionExecute(self): + """ + Execute the testcase. + """ + return self.oTestVmSet.actionExecute(self, self.testOneVmConfig) + + + # + # Test execution helpers. + # + + def _generateRawPortFilename(self, oTestDrv, oTestVm, sInfix, sSuffix): + """ Generates a raw port filename. """ + random.seed(); + sRandom = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(10)); + return os.path.join(oTestDrv.sScratchPath, oTestVm.sVmName + sInfix + sRandom + sSuffix); + + def setupSerialMode(self, oSession, oTestVm, sMode): + """ + Sets up the serial mode. + """ + fRc = True; + fServer = False; + sLocation = None; + ePortMode = vboxcon.PortMode_Disconnected; + if sMode == 'RawFile': + sLocation = self._generateRawPortFilename(self, oTestVm, '-com1-', '.out'); + ePortMode = vboxcon.PortMode_RawFile; + elif sMode == 'Tcp': + sLocation = '127.0.0.1:1234'; + self.oLoopback = loopback.SerialLoopback(loopback.g_ksLoopbackTcpServ, sLocation); + ePortMode = vboxcon.PortMode_TCP; + elif sMode == 'TcpServ': + fServer = True; + sLocation = '1234'; + ePortMode = vboxcon.PortMode_TCP; + self.oLoopback = loopback.SerialLoopback(loopback.g_ksLoopbackTcpClient, '127.0.0.1:1234'); + elif sMode == 'NamedPipe': + sLocation = self._generateRawPortFilename(self, oTestVm, '-com1-', '.out'); + ePortMode = vboxcon.PortMode_HostPipe; + self.oLoopback = loopback.SerialLoopback(loopback.g_ksLoopbackNamedPipeServ, sLocation); + elif sMode == 'NamedPipeServ': + fServer = True; + sLocation = self._generateRawPortFilename(self, oTestVm, '-com1-', '.out'); + ePortMode = vboxcon.PortMode_HostPipe; + self.oLoopback = loopback.SerialLoopback(loopback.g_ksLoopbackNamedPipeClient, sLocation); + elif sMode == 'HostDev': + sLocation = '/dev/ttyUSB0'; + ePortMode = vboxcon.PortMode_HostDevice; + else: + reporter.log('warning, invalid mode %s given' % (sMode, )); + fRc = False; + + if fRc: + fRc = oSession.changeSerialPortAttachment(0, ePortMode, sLocation, fServer); + if fRc and sMode in ('TcpServ', 'NamedPipeServ',): + self.sleep(2); # Fudge to allow the TCP server to get started. + fRc = self.oLoopback.connect(); + if not fRc: + reporter.log('Failed to connect to %s' % (sLocation, )); + self.sLocation = sLocation; + + return fRc; + + def testWrite(self, oSession, oTxsSession, oTestVm, sMode): + """ + Does a simple write test verifying the output. + """ + _ = oSession; + + reporter.testStart('Write'); + tupCmdLine = ('SerialTest', '--tests', 'write', '--txbytes', '1048576'); + if self.fVerboseTest: + tupCmdLine += ('--verbose',); + if oTestVm.isWindows(): + tupCmdLine += ('--device', r'\\.\COM1',); + elif oTestVm.isLinux(): + tupCmdLine += ('--device', r'/dev/ttyS0',); + + fRc = self.txsRunTest(oTxsSession, 'SerialTest', 3600 * 1000, \ + '${CDROM}/${OS/ARCH}/SerialTest${EXESUFF}', tupCmdLine); + if not fRc: + reporter.testFailure('Running serial test utility failed'); + elif sMode == 'RawFile': + # Open serial port and verify + cLast = 0; + try: + with open(self.sLocation, 'rb') as oFile: + sFmt = '=I'; + cBytes = 4; + for i in xrange(1048576 // 4): + _ = i; + sData = oFile.read(cBytes); + tupUnpacked = struct.unpack(sFmt, sData); + cLast = cLast + 1; + if tupUnpacked[0] != cLast: + reporter.testFailure('Corruption detected, expected counter value %s, got %s' + % (cLast + 1, tupUnpacked[0],)); + break; + except: + reporter.logXcpt(); + reporter.testFailure('Verifying the written data failed'); + reporter.testDone(); + return fRc; + + def testReadWrite(self, oSession, oTxsSession, oTestVm): + """ + Does a simple write test verifying the output. + """ + _ = oSession; + + reporter.testStart('ReadWrite'); + tupCmdLine = ('SerialTest', '--tests', 'readwrite', '--txbytes', '1048576'); + if self.fVerboseTest: + tupCmdLine += ('--verbose',); + if oTestVm.isWindows(): + tupCmdLine += ('--device', r'\\.\COM1',); + elif oTestVm.isLinux(): + tupCmdLine += ('--device', r'/dev/ttyS0',); + + fRc = self.txsRunTest(oTxsSession, 'SerialTest', 600 * 1000, \ + '${CDROM}/${OS/ARCH}/SerialTest${EXESUFF}', tupCmdLine); + if not fRc: + reporter.testFailure('Running serial test utility failed'); + + reporter.testDone(); + return fRc; + + def isModeCompatibleWithTest(self, sMode, sTest): + """ + Returns whether the given port mode and test combination is + supported for testing. + """ + if sMode == 'RawFile' and sTest == 'ReadWrite': + return False; + if sMode != 'RawFile' and sTest == 'Write': + return False; + return True; + + def testOneVmConfig(self, oVM, oTestVm): + """ + Runs the specified VM thru test #1. + """ + + for sUart in self.asUarts: + reporter.testStart(sUart); + # Reconfigure the VM + fRc = True; + oSession = self.openSession(oVM); + if oSession is not None: + fRc = oSession.enableSerialPort(0); + + fRc = fRc and oSession.setExtraData("VBoxInternal/Devices/serial/0/Config/UartType", "string:" + sUart); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc; + oSession = None; + else: + fRc = False; + + if fRc is True: + self.logVmInfo(oVM); + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, fCdWait = True); + if oSession is not None: + self.addTask(oTxsSession); + + for sMode in self.asSerialModes: + reporter.testStart(sMode); + fRc = self.setupSerialMode(oSession, oTestVm, sMode); + if fRc: + for sTest in self.asSerialTests: + # Skip tests which don't work with the current mode. + if self.isModeCompatibleWithTest(sMode, sTest): + if sTest == 'Write': + fRc = self.testWrite(oSession, oTxsSession, oTestVm, sMode); + if sTest == 'ReadWrite': + fRc = self.testReadWrite(oSession, oTxsSession, oTestVm); + if self.oLoopback is not None: + self.oLoopback.shutdown(); + self.oLoopback = None; + + reporter.testDone(); + + self.removeTask(oTxsSession); + self.terminateVmBySession(oSession); + reporter.testDone(); + + return fRc; + +if __name__ == '__main__': + sys.exit(tdSerial1().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/shutdown/tdGuestOsShutdown1.py b/src/VBox/ValidationKit/tests/shutdown/tdGuestOsShutdown1.py new file mode 100755 index 00000000..27ac369a --- /dev/null +++ b/src/VBox/ValidationKit/tests/shutdown/tdGuestOsShutdown1.py @@ -0,0 +1,367 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +VMM Guest OS boot tests. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os +import sys +import time + + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0] +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(g_ksValidationKitDir) + +# Validation Kit imports. +from testdriver import vbox +from testdriver import base +from testdriver import reporter +from testdriver import vboxcon + + +class tdGuestOsBootTest1(vbox.TestDriver): + """ + VMM Unit Tests Set. + + Scenario: + - Create VM that corresponds to Guest OS pre-installed on selected HDD + - Start VM and wait for TXS server connection (which is started after Guest successfully booted) + """ + + ksSataController = 'SATA Controller' + ksIdeController = 'IDE Controller' + + # VM parameters required to run HDD image. + # Format: { HDD image filename: (sKind, HDD controller type) } + kaoVmParams = { + 't-win80.vdi': ( 'Windows 8 (64 bit)', ksSataController ), + } + + # List of platforms which are able to suspend and resume host automatically. + # In order to add new platform, self._SuspendResume() should be adapted. + kasSuspendAllowedPlatforms = ( 'darwin' ) + + kcMsVmStartLimit = 5 * 60000 + kcMsVmShutdownLimit = 1 * 60000 + + def __init__(self): + """ + Reinitialize child class instance. + """ + vbox.TestDriver.__init__(self) + + self.sVmName = 'TestVM' + self.sHddName = None + self.sHddPathBase = os.path.join(self.sResourcePath, '4.2', 'nat', 'win80') + self.oVM = None + + # TODO: that should be moved to some common place + self.fEnableIOAPIC = True + self.cCpus = 1 + self.fEnableNestedPaging = True + self.fEnablePAE = False + self.fSuspendHost = False + self.cSecSuspendTime = 60 + self.cShutdownIters = 1 + self.fExtraVm = False + self.sExtraVmName = "TestVM-Extra" + self.oExtraVM = None + self.fLocalCatch = False + + # + # Overridden methods. + # + + def showUsage(self): + """ + Extend usage info + """ + rc = vbox.TestDriver.showUsage(self) + reporter.log(' --boot-hdd <HDD image file name>') + + reporter.log(' --cpus <# CPUs>') + reporter.log(' --no-ioapic') + reporter.log(' --no-nested-paging') + reporter.log(' --pae') + reporter.log(' --suspend-host') + reporter.log(' --suspend-time <sec>') + reporter.log(' --shutdown-iters <# iters>') + reporter.log(' --extra-vm') + reporter.log(' --local-catch') + return rc + + def parseOption(self, asArgs, iArg): + """ + Extend standard options set + """ + if asArgs[iArg] == '--boot-hdd': + iArg += 1 + if iArg >= len(asArgs): raise base.InvalidOption('The "--boot-hdd" option requires an argument') + self.sHddName = asArgs[iArg] + + elif asArgs[iArg] == '--cpus': + iArg += 1 + if iArg >= len(asArgs): raise base.InvalidOption('The "--cpus" option requires an argument') + self.cCpus = int(asArgs[iArg]) + elif asArgs[iArg] == '--no-ioapic': + self.fEnableIOAPIC = False + elif asArgs[iArg] == '--no-nested-paging': + self.fEnableNestedPaging = False + elif asArgs[iArg] == '--pae': + self.fEnablePAE = True + elif asArgs[iArg] == '--suspend-host': + self.fSuspendHost = True + elif asArgs[iArg] == '--suspend-time': + iArg += 1 + if iArg >= len(asArgs): raise base.InvalidOption('The "--suspend-time" option requires an argument') + self.cSecSuspendTime = int(asArgs[iArg]) + elif asArgs[iArg] == '--shutdown-iters': + iArg += 1 + if iArg >= len(asArgs): raise base.InvalidOption('The "--shutdown-iters" option requires an argument') + self.cShutdownIters = int(asArgs[iArg]) + elif asArgs[iArg] == '--extra-vm': + self.fExtraVm = True + elif asArgs[iArg] == '--local-catch': + self.fLocalCatch = True + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg) + + return iArg + 1 + + def getResourceSet(self): + """ + Returns a set of file and/or directory names relative to + TESTBOX_PATH_RESOURCES. + """ + return [os.path.join(self.sHddPathBase, sRsrc) for sRsrc in self.kaoVmParams]; + + def _addVM(self, sVmName, sNicTraceFile=None): + """ + Create VM + """ + # Get VM params specific to HDD image + sKind, sController = self.kaoVmParams[self.sHddName] + + # Create VM itself + eNic0AttachType = vboxcon.NetworkAttachmentType_NAT + sHddPath = os.path.join(self.sHddPathBase, self.sHddName) + assert os.path.isfile(sHddPath) + + oVM = \ + self.createTestVM(sVmName, 1, sKind=sKind, cCpus=self.cCpus, + eNic0AttachType=eNic0AttachType, sDvdImage = self.sVBoxValidationKitIso) + assert oVM is not None + + oSession = self.openSession(oVM) + + # Attach an HDD + fRc = oSession.attachHd(sHddPath, sController, fImmutable=True) + + # Enable HW virt + fRc = fRc and oSession.enableVirtEx(True) + + # Enable I/O APIC + fRc = fRc and oSession.enableIoApic(self.fEnableIOAPIC) + + # Enable Nested Paging + fRc = fRc and oSession.enableNestedPaging(self.fEnableNestedPaging) + + # Enable PAE + fRc = fRc and oSession.enablePae(self.fEnablePAE) + + if (sNicTraceFile is not None): + fRc = fRc and oSession.setNicTraceEnabled(True, sNicTraceFile) + + # Remote desktop + oSession.setupVrdp(True) + + fRc = fRc and oSession.saveSettings() + fRc = fRc and oSession.close() + assert fRc is True + + return oVM + + def actionConfig(self): + """ + Configure pre-conditions. + """ + + if not self.importVBoxApi(): + return False + + # Save time: do not start VM if there is no way to suspend host + if (self.fSuspendHost is True and sys.platform not in self.kasSuspendAllowedPlatforms): + reporter.log('Platform [%s] is not in the list of supported platforms' % sys.platform) + return False + + assert self.sHddName is not None + if self.sHddName not in self.kaoVmParams: + reporter.log('Error: unknown HDD image specified: %s' % self.sHddName) + return False + + if (self.fExtraVm is True): + self.oExtraVM = self._addVM(self.sExtraVmName) + + self.oVM = self._addVM(self.sVmName) + + return vbox.TestDriver.actionConfig(self) + + def _SuspendResume(self, cSecTimeout): + """ + Put host into sleep and automatically resume it after specified timeout. + """ + fRc = False + + if (sys.platform == 'darwin'): + tsStart = time.time() + fRc = os.system("/usr/bin/pmset relative wake %d" % self.cSecSuspendTime) + fRc |= os.system("/usr/bin/pmset sleepnow") + # Wait for host to wake up + while ((time.time() - tsStart) < self.cSecSuspendTime): + self.sleep(0.1) + + return fRc == 0 + + def _waitKeyboardInterrupt(self): + """ + Idle loop until user press CTRL+C + """ + reporter.log('[LOCAL CATCH]: waiting for keyboard interrupt') + while (True): + try: + self.sleep(1) + except KeyboardInterrupt: + reporter.log('[LOCAL CATCH]: keyboard interrupt occurred') + break + + def actionExecute(self): + """ + Execute the testcase itself. + """ + #self.logVmInfo(self.oVM) + + reporter.testStart('SHUTDOWN GUEST') + + cIter = 0 + fRc = True + + if (self.fExtraVm is True): + oExtraSession, oExtraTxsSession = self.startVmAndConnectToTxsViaTcp(self.sExtraVmName, + fCdWait=False, + cMsTimeout=self.kcMsVmStartLimit) + if oExtraSession is None or oExtraTxsSession is None: + reporter.error('Unable to start extra VM.') + if (self.fLocalCatch is True): + self._waitKeyboardInterrupt() + reporter.testDone() + return False + + while (cIter < self.cShutdownIters): + + cIter += 1 + + reporter.log("Starting iteration #%d." % cIter) + + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(self.sVmName, + fCdWait=False, + cMsTimeout=self.kcMsVmStartLimit) + if oSession is not None and oTxsSession is not None: + # Wait until guest reported success + reporter.log('Guest started. Connection to TXS service established.') + + if (self.fSuspendHost is True): + reporter.log("Disconnect form TXS.") + fRc = fRc and self.txsDisconnect(oSession, oTxsSession) + if (fRc is not True): + reporter.log("Disconnect form TXS failed.") + else: + reporter.log('Put host to sleep and resume it automatically after %d seconds.' % self.cSecSuspendTime) + fRc = fRc and self._SuspendResume(self.cSecSuspendTime) + if (fRc is True): + reporter.log("Sleep/resume success.") + else: + reporter.log("Sleep/resume failed.") + reporter.log("Re-connect to TXS in 10 seconds.") + self.sleep(10) + (fRc, oTxsSession) = self.txsDoConnectViaTcp(oSession, 2 * 60 * 10000) + if (fRc is not True): + reporter.log("Re-connect to TXS failed.") + + if (fRc is True): + reporter.log('Attempt to shutdown guest.') + fRc = fRc and oTxsSession.syncShutdown(cMsTimeout=(4 * 60 * 1000)) + if (fRc is True): + reporter.log('Shutdown request issued successfully.') + self.waitOnDirectSessionClose(self.oVM, self.kcMsVmShutdownLimit) + reporter.log('Shutdown %s.' % ('success' if fRc is True else 'failed')) + else: + reporter.error('Shutdown request failed.') + + # Do not terminate failing VM in order to catch it. + if (fRc is not True and self.fLocalCatch is True): + self._waitKeyboardInterrupt() + break + + fRc = fRc and self.terminateVmBySession(oSession) + reporter.log('VM terminated.') + + else: + reporter.error('Guest did not start (iteration %d of %d)' % (cIter, self.cShutdownIters)) + fRc = False + + # Stop if fail + if (fRc is not True): + break + + # Local catch at the end. + if (self.fLocalCatch is True): + reporter.log("Test completed. Waiting for user to press CTRL+C.") + self._waitKeyboardInterrupt() + + if (self.fExtraVm is True): + fRc = fRc and self.terminateVmBySession(oExtraSession) + + reporter.testDone() + return fRc is True + +if __name__ == '__main__': + sys.exit(tdGuestOsBootTest1().main(sys.argv)) diff --git a/src/VBox/ValidationKit/tests/smoketests/Makefile.kmk b/src/VBox/ValidationKit/tests/smoketests/Makefile.kmk new file mode 100644 index 00000000..cab94afb --- /dev/null +++ b/src/VBox/ValidationKit/tests/smoketests/Makefile.kmk @@ -0,0 +1,52 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Smoke Tests. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +INSTALLS += ValidationKitTestsSmokeTests +ValidationKitTestsSmokeTests_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsSmokeTests_INST = $(INST_VALIDATIONKIT)tests/smoketests/ +ValidationKitTestsSmokeTests_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdSmokeTest1.py \ + $(PATH_SUB_CURRENT)/tdExoticOrAncient1.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsSmokeTests_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/smoketests/tdExoticOrAncient1.py b/src/VBox/ValidationKit/tests/smoketests/tdExoticOrAncient1.py new file mode 100755 index 00000000..937417e8 --- /dev/null +++ b/src/VBox/ValidationKit/tests/smoketests/tdExoticOrAncient1.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdExoticOrAncient1.py $ + +""" +VirtualBox Validation Kit - Exotic and/or ancient OSes #1. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os; +import sys; + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testdriver import reporter; +from testdriver import vbox; + + +class tdExoticOrAncient1(vbox.TestDriver): + """ + VBox exotic and/or ancient OSes #1. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.oTestVmSet = self.oTestVmManager.selectSet( self.oTestVmManager.kfGrpAncient + | self.oTestVmManager.kfGrpExotic); + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + return rc; + + def parseOption(self, asArgs, iArg): + return vbox.TestDriver.parseOption(self, asArgs, iArg); + + def actionVerify(self): + if self.sVBoxValidationKitIso is None or not os.path.isfile(self.sVBoxValidationKitIso): + reporter.error('Cannot find the VBoxValidationKit.iso! (%s)' + 'Please unzip a Validation Kit build in the current directory or in some parent one.' + % (self.sVBoxValidationKitIso,) ); + return False; + return vbox.TestDriver.actionVerify(self); + + def actionConfig(self): + # Make sure vboxapi has been imported so we can use the constants. + if not self.importVBoxApi(): + return False; + + assert self.sVBoxValidationKitIso is not None; + return self.oTestVmSet.actionConfig(self, sDvdImage = self.sVBoxValidationKitIso); + + def actionExecute(self): + """ + Execute the testcase. + """ + return self.oTestVmSet.actionExecute(self, self.testOneVmConfig) + + + # + # Test execution helpers. + # + + def testOneVmConfig(self, oVM, oTestVm): + """ + Runs the specified VM thru test #1. + """ + + # Simple test. + self.logVmInfo(oVM); + if oTestVm.fGrouping & self.oTestVmManager.kfGrpNoTxs: + sResult = self.runVmAndMonitorComRawFile(oTestVm.sVmName, oTestVm.sCom1RawFile); + ## @todo sResult = self.runVmAndMonitorComRawFile(oTestVm.sVmName, oTestVm.getCom1RawFile()); + return sResult == 'PASSED'; + oSession, _ = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, fCdWait = True); + if oSession is not None: + return self.terminateVmBySession(oSession); + return False; + +if __name__ == '__main__': + sys.exit(tdExoticOrAncient1().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/smoketests/tdSmokeTest1.py b/src/VBox/ValidationKit/tests/smoketests/tdSmokeTest1.py new file mode 100755 index 00000000..0c8cb5ee --- /dev/null +++ b/src/VBox/ValidationKit/tests/smoketests/tdSmokeTest1.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdSmokeTest1.py $ + +""" +VirtualBox Validation Kit - Smoke Test #1. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os; +import sys; + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testdriver import reporter; +from testdriver import base; +from testdriver import vbox; +from testdriver import vboxcon; + + +class tdSmokeTest1(vbox.TestDriver): + """ + VBox Smoke Test #1. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.oTestVmSet = self.oTestVmManager.getSmokeVmSet(); + self.sNicAttachmentDef = 'mixed'; + self.sNicAttachment = self.sNicAttachmentDef; + self.fQuick = False; + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('Smoke Test #1 options:'); + reporter.log(' --nic-attachment <bridged|nat|mixed>'); + reporter.log(' Default: %s' % (self.sNicAttachmentDef)); + reporter.log(' --quick'); + reporter.log(' Very selective testing.') + return rc; + + def parseOption(self, asArgs, iArg): + if asArgs[iArg] == '--nic-attachment': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--nic-attachment" takes an argument'); + self.sNicAttachment = asArgs[iArg]; + if self.sNicAttachment not in ('bridged', 'nat', 'mixed'): + raise base.InvalidOption('The "--nic-attachment" value "%s" is not supported. Valid values are: bridged, nat' \ + % (self.sNicAttachment)); + elif asArgs[iArg] == '--quick': + # Disable all but a few VMs and configurations. + for oTestVm in self.oTestVmSet.aoTestVms: + if oTestVm.sVmName == 'tst-win2k3ent': # 32-bit paging + oTestVm.asVirtModesSup = [ 'hwvirt' ]; + oTestVm.acCpusSup = range(1, 2); + elif oTestVm.sVmName == 'tst-rhel5': # 32-bit paging + oTestVm.asVirtModesSup = [ 'raw' ]; + oTestVm.acCpusSup = range(1, 2); + elif oTestVm.sVmName == 'tst-win2k8': # 64-bit + oTestVm.asVirtModesSup = [ 'hwvirt-np' ]; + oTestVm.acCpusSup = range(1, 2); + elif oTestVm.sVmName == 'tst-sol10-64': # SMP, 64-bit + oTestVm.asVirtModesSup = [ 'hwvirt' ]; + oTestVm.acCpusSup = range(2, 3); + elif oTestVm.sVmName == 'tst-sol10': # SMP, 32-bit + oTestVm.asVirtModesSup = [ 'hwvirt-np' ]; + oTestVm.acCpusSup = range(2, 3); + elif oTestVm.sVmName == 'tst-nsthwvirt-ubuntu-64': # Nested hw.virt, 64-bit + oTestVm.asVirtModesSup = [ 'hwvirt-np' ]; + oTestVm.acCpusSup = range(1, 2); + else: + oTestVm.fSkip = True; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def actionVerify(self): + if self.sVBoxValidationKitIso is None or not os.path.isfile(self.sVBoxValidationKitIso): + reporter.error('Cannot find the VBoxValidationKit.iso! (%s)' + 'Please unzip a Validation Kit build in the current directory or in some parent one.' + % (self.sVBoxValidationKitIso,) ); + return False; + return vbox.TestDriver.actionVerify(self); + + def actionConfig(self): + # Make sure vboxapi has been imported so we can use the constants. + if not self.importVBoxApi(): + return False; + + # Do the configuring. + if self.sNicAttachment == 'nat': eNic0AttachType = vboxcon.NetworkAttachmentType_NAT; + elif self.sNicAttachment == 'bridged': eNic0AttachType = vboxcon.NetworkAttachmentType_Bridged; + else: eNic0AttachType = None; + assert self.sVBoxValidationKitIso is not None; + return self.oTestVmSet.actionConfig(self, eNic0AttachType = eNic0AttachType, sDvdImage = self.sVBoxValidationKitIso); + + def actionExecute(self): + """ + Execute the testcase. + """ + return self.oTestVmSet.actionExecute(self, self.testOneVmConfig) + + + # + # Test execution helpers. + # + + def testOneVmConfig(self, oVM, oTestVm): + """ + Runs the specified VM thru test #1. + """ + + # Simple test. + self.logVmInfo(oVM); + # Try waiting for a bit longer (15 minutes) until the CD is available to avoid running into timeouts. + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, fCdWait = True, cMsCdWait = 15 * 60 * 1000); + if oSession is not None: + self.addTask(oTxsSession); + + ## @todo do some quick tests: save, restore, execute some test program, shut down the guest. + + # cleanup. + self.removeTask(oTxsSession); + self.terminateVmBySession(oSession) + return True; + return None; + +if __name__ == '__main__': + sys.exit(tdSmokeTest1().main(sys.argv)); diff --git a/src/VBox/ValidationKit/tests/storage/Makefile.kmk b/src/VBox/ValidationKit/tests/storage/Makefile.kmk new file mode 100644 index 00000000..6ed368e2 --- /dev/null +++ b/src/VBox/ValidationKit/tests/storage/Makefile.kmk @@ -0,0 +1,56 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Storage Tests. +# + +# +# Copyright (C) 2012-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +INSTALLS += ValidationKitTestsStorage +ValidationKitTestsStorage_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsStorage_INST = $(INST_VALIDATIONKIT)tests/storage/ +ValidationKitTestsStorage_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdStorageBenchmark1.py \ + $(PATH_SUB_CURRENT)/tdStorageSnapshotMerging1.py \ + $(PATH_SUB_CURRENT)/tdStorageStress1.py \ + $(PATH_SUB_CURRENT)/tdStorageRawDrive1.py \ + $(PATH_SUB_CURRENT)/remoteexecutor.py \ + $(PATH_SUB_CURRENT)/storagecfg.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsStorage_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/storage/remoteexecutor.py b/src/VBox/ValidationKit/tests/storage/remoteexecutor.py new file mode 100755 index 00000000..b451992e --- /dev/null +++ b/src/VBox/ValidationKit/tests/storage/remoteexecutor.py @@ -0,0 +1,314 @@ +# -*- coding: utf-8 -*- +# $Id: remoteexecutor.py $ + +""" +VirtualBox Validation Kit - Storage benchmark, test execution helpers. +""" + +__copyright__ = \ +""" +Copyright (C) 2016-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import array; +import os; +import shutil; +import sys; +if sys.version_info[0] >= 3: + from io import StringIO as StringIO; # pylint: disable=import-error,no-name-in-module,useless-import-alias +else: + from StringIO import StringIO as StringIO; # pylint: disable=import-error,no-name-in-module,useless-import-alias +import subprocess; + +# Validation Kit imports. +from common import utils; +from testdriver import reporter; + + + +class StdInOutBuffer(object): + """ Standard input output buffer """ + + def __init__(self, sInput = None): + self.sInput = StringIO(); + if sInput is not None: + self.sInput.write(self._toString(sInput)); + self.sInput.seek(0); + self.sOutput = ''; + + def _toString(self, sText): + """ + Converts any possible array to + a string. + """ + if isinstance(sText, array.array): + try: + if sys.version_info < (3, 9, 0): + # Removed since Python 3.9. + return str(sText.tostring()); # pylint: disable=no-member + return str(sText.tobytes()); + except: + pass; + elif isinstance(sText, bytes): + return sText.decode('utf-8'); + + return sText; + + def read(self, cb): + """file.read""" + return self.sInput.read(cb); + + def write(self, sText): + """file.write""" + self.sOutput += self._toString(sText); + return None; + + def getOutput(self): + """ + Returns the output of the buffer. + """ + return self.sOutput; + + def close(self): + """ file.close """ + return; + +class RemoteExecutor(object): + """ + Helper for executing tests remotely through TXS or locally + """ + + def __init__(self, oTxsSession = None, asBinaryPaths = None, sScratchPath = None): + self.oTxsSession = oTxsSession; + self.asPaths = asBinaryPaths; + self.sScratchPath = sScratchPath; + if self.asPaths is None: + self.asPaths = [ ]; + + def _getBinaryPath(self, sBinary): + """ + Returns the complete path of the given binary if found + from the configured search path or None if not found. + """ + for sPath in self.asPaths: + sFile = sPath + '/' + sBinary; + if self.isFile(sFile): + return sFile; + return sBinary; + + def _sudoExecuteSync(self, asArgs, sInput): + """ + Executes a sudo child process synchronously. + Returns a tuple [True, 0] if the process executed successfully + and returned 0, otherwise [False, rc] is returned. + """ + reporter.log('Executing [sudo]: %s' % (asArgs, )); + reporter.flushall(); + fRc = True; + sOutput = ''; + sError = ''; + try: + oProcess = utils.sudoProcessPopen(asArgs, stdout=subprocess.PIPE, stdin=subprocess.PIPE, + stderr=subprocess.PIPE, shell = False, close_fds = False); + + sOutput, sError = oProcess.communicate(sInput); + iExitCode = oProcess.poll(); + + if iExitCode != 0: + fRc = False; + except: + reporter.errorXcpt(); + fRc = False; + reporter.log('Exit code [sudo]: %s (%s)' % (fRc, asArgs)); + return (fRc, str(sOutput), str(sError)); + + def _execLocallyOrThroughTxs(self, sExec, asArgs, sInput, cMsTimeout): + """ + Executes the given program locally or through TXS based on the + current config. + """ + fRc = False; + sOutput = None; + if self.oTxsSession is not None: + reporter.log('Executing [remote]: %s %s %s' % (sExec, asArgs, sInput)); + reporter.flushall(); + oStdOut = StdInOutBuffer(); + oStdErr = StdInOutBuffer(); + oTestPipe = reporter.FileWrapperTestPipe(); + oStdIn = None; + if sInput is not None: + oStdIn = StdInOutBuffer(sInput); + else: + oStdIn = '/dev/null'; # pylint: disable=redefined-variable-type + fRc = self.oTxsSession.syncExecEx(sExec, (sExec,) + asArgs, + oStdIn = oStdIn, oStdOut = oStdOut, + oStdErr = oStdErr, oTestPipe = oTestPipe, + cMsTimeout = cMsTimeout); + sOutput = oStdOut.getOutput(); + sError = oStdErr.getOutput(); + if fRc is False: + reporter.log('Exit code [remote]: %s (stdout: %s stderr: %s)' % (fRc, sOutput, sError)); + else: + reporter.log('Exit code [remote]: %s' % (fRc,)); + else: + fRc, sOutput, sError = self._sudoExecuteSync([sExec, ] + list(asArgs), sInput); + return (fRc, sOutput, sError); + + def execBinary(self, sExec, asArgs, sInput = None, cMsTimeout = 3600000): + """ + Executes the given binary with the given arguments + providing some optional input through stdin and + returning whether the process exited successfully and the output + in a string. + """ + + fRc = True; + sOutput = None; + sError = None; + sBinary = self._getBinaryPath(sExec); + if sBinary is not None: + fRc, sOutput, sError = self._execLocallyOrThroughTxs(sBinary, asArgs, sInput, cMsTimeout); + else: + fRc = False; + return (fRc, sOutput, sError); + + def execBinaryNoStdOut(self, sExec, asArgs, sInput = None): + """ + Executes the given binary with the given arguments + providing some optional input through stdin and + returning whether the process exited successfully. + """ + fRc, _, _ = self.execBinary(sExec, asArgs, sInput); + return fRc; + + def copyFile(self, sLocalFile, sFilename, cMsTimeout = 30000): + """ + Copies the local file to the remote destination + if configured + + Returns a file ID which can be used as an input parameter + to execBinary() resolving to the real filepath on the remote side + or locally. + """ + sFileId = None; + if self.oTxsSession is not None: + sFileId = '${SCRATCH}/' + sFilename; + fRc = self.oTxsSession.syncUploadFile(sLocalFile, sFileId, cMsTimeout); + if not fRc: + sFileId = None; + else: + sFileId = self.sScratchPath + '/' + sFilename; + try: + shutil.copy(sLocalFile, sFileId); + except: + sFileId = None; + + return sFileId; + + def copyString(self, sContent, sFilename, cMsTimeout = 30000): + """ + Creates a file remotely or locally with the given content. + + Returns a file ID which can be used as an input parameter + to execBinary() resolving to the real filepath on the remote side + or locally. + """ + sFileId = None; + if self.oTxsSession is not None: + sFileId = '${SCRATCH}/' + sFilename; + fRc = self.oTxsSession.syncUploadString(sContent, sFileId, cMsTimeout); + if not fRc: + sFileId = None; + else: + sFileId = self.sScratchPath + '/' + sFilename; + try: + with open(sFileId, 'wb') as oFile: + oFile.write(sContent); + except: + sFileId = None; + + return sFileId; + + def mkDir(self, sDir, fMode = 0o700, cMsTimeout = 30000): + """ + Creates a new directory at the given location. + """ + fRc = True; + if self.oTxsSession is not None: + fRc = self.oTxsSession.syncMkDir(sDir, fMode, cMsTimeout); + elif not os.path.isdir(sDir): + fRc = os.mkdir(sDir, fMode); + + return fRc; + + def rmDir(self, sDir, cMsTimeout = 30000): + """ + Removes the given directory. + """ + fRc = True; + if self.oTxsSession is not None: + fRc = self.oTxsSession.syncRmDir(sDir, cMsTimeout); + else: + fRc = self.execBinaryNoStdOut('rmdir', (sDir,)); + + return fRc; + + def rmTree(self, sDir, cMsTimeout = 30000): + """ + Recursively removes all files and sub directories including the given directory. + """ + fRc = True; + if self.oTxsSession is not None: + fRc = self.oTxsSession.syncRmTree(sDir, cMsTimeout); + else: + try: + shutil.rmtree(sDir, ignore_errors=True); + except: + fRc = False; + + return fRc; + + def isFile(self, sPath, cMsTimeout = 30000): + """ + Checks that the given file exists. + """ + fRc = True; + if self.oTxsSession is not None: + fRc = self.oTxsSession.syncIsFile(sPath, cMsTimeout); + else: + try: + fRc = os.path.isfile(sPath); + except: + fRc = False; + + return fRc; diff --git a/src/VBox/ValidationKit/tests/storage/storagecfg.py b/src/VBox/ValidationKit/tests/storage/storagecfg.py new file mode 100755 index 00000000..786d5270 --- /dev/null +++ b/src/VBox/ValidationKit/tests/storage/storagecfg.py @@ -0,0 +1,681 @@ +# -*- coding: utf-8 -*- +# $Id: storagecfg.py $ + +""" +VirtualBox Validation Kit - Storage test configuration API. +""" + +__copyright__ = \ +""" +Copyright (C) 2016-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Standard Python imports. +import os; +import re; + + +class StorageDisk(object): + """ + Class representing a disk for testing. + """ + + def __init__(self, sPath, fRamDisk = False): + self.sPath = sPath; + self.fUsed = False; + self.fRamDisk = fRamDisk; + + def getPath(self): + """ + Return the disk path. + """ + return self.sPath; + + def isUsed(self): + """ + Returns whether the disk is currently in use. + """ + return self.fUsed; + + def isRamDisk(self): + """ + Returns whether the disk objecthas a RAM backing. + """ + return self.fRamDisk; + + def setUsed(self, fUsed): + """ + Sets the used flag for the disk. + """ + if fUsed: + if self.fUsed: + return False; + + self.fUsed = True; + else: + self.fUsed = fUsed; + + return True; + +class StorageConfigOs(object): + """ + Base class for a single hosts OS storage configuration. + """ + + def _getDisksMatchingRegExpWithPath(self, sPath, sRegExp): + """ + Adds new disks to the config matching the given regular expression. + """ + + lstDisks = []; + oRegExp = re.compile(sRegExp); + asFiles = os.listdir(sPath); + for sFile in asFiles: + if oRegExp.match(os.path.basename(sFile)) and os.path.exists(sPath + '/' + sFile): + lstDisks.append(StorageDisk(sPath + '/' + sFile)); + + return lstDisks; + +class StorageConfigOsSolaris(StorageConfigOs): + """ + Class implementing the Solaris specifics for a storage configuration. + """ + + def __init__(self): + StorageConfigOs.__init__(self); + self.idxRamDisk = 0; + + def _getActivePoolsStartingWith(self, oExec, sPoolIdStart): + """ + Returns a list of pools starting with the given ID or None on failure. + """ + lstPools = None; + fRc, sOutput, _ = oExec.execBinary('zpool', ('list', '-H')); + if fRc: + lstPools = []; + asPools = sOutput.splitlines(); + for sPool in asPools: + if sPool.startswith(sPoolIdStart): + # Extract the whole name and add it to the list. + asItems = sPool.split('\t'); + lstPools.append(asItems[0]); + return lstPools; + + def _getActiveVolumesInPoolStartingWith(self, oExec, sPool, sVolumeIdStart): + """ + Returns a list of active volumes for the given pool starting with the given + identifier or None on failure. + """ + lstVolumes = None; + fRc, sOutput, _ = oExec.execBinary('zfs', ('list', '-H')); + if fRc: + lstVolumes = []; + asVolumes = sOutput.splitlines(); + for sVolume in asVolumes: + if sVolume.startswith(sPool + '/' + sVolumeIdStart): + # Extract the whole name and add it to the list. + asItems = sVolume.split('\t'); + lstVolumes.append(asItems[0]); + return lstVolumes; + + def getDisksMatchingRegExp(self, sRegExp): + """ + Returns a list of disks matching the regular expression. + """ + return self._getDisksMatchingRegExpWithPath('/dev/dsk', sRegExp); + + def getMntBase(self): + """ + Returns the mountpoint base for the host. + """ + return '/pools'; + + def createStoragePool(self, oExec, sPool, asDisks, sRaidLvl): + """ + Creates a new storage pool with the given disks and the given RAID level. + """ + sZPoolRaid = None; + if len(asDisks) > 1 and (sRaidLvl == 'raid5' or sRaidLvl is None): + sZPoolRaid = 'raidz'; + + fRc = True; + if sZPoolRaid is not None: + fRc = oExec.execBinaryNoStdOut('zpool', ('create', '-f', sPool, sZPoolRaid,) + tuple(asDisks)); + else: + fRc = oExec.execBinaryNoStdOut('zpool', ('create', '-f', sPool,) + tuple(asDisks)); + + return fRc; + + def createVolume(self, oExec, sPool, sVol, sMountPoint, cbVol = None): + """ + Creates and mounts a filesystem at the given mountpoint using the + given pool and volume IDs. + """ + fRc = True; + if cbVol is not None: + fRc = oExec.execBinaryNoStdOut('zfs', ('create', '-o', 'mountpoint='+sMountPoint, '-V', cbVol, sPool + '/' + sVol)); + else: + fRc = oExec.execBinaryNoStdOut('zfs', ('create', '-o', 'mountpoint='+sMountPoint, sPool + '/' + sVol)); + + # @todo Add proper parameters to set proper owner:group ownership, the testcase broke in r133060 for Solaris + # because ceating directories is now done using the python mkdir API instead of calling 'sudo mkdir...'. + # No one noticed though because testboxstor1 went out of action before... + # Will get fixed as soon as I'm back home. + if fRc: + fRc = oExec.execBinaryNoStdOut('chmod', ('777', sMountPoint)); + + return fRc; + + def destroyVolume(self, oExec, sPool, sVol): + """ + Destroys the given volume. + """ + fRc = oExec.execBinaryNoStdOut('zfs', ('destroy', sPool + '/' + sVol)); + return fRc; + + def destroyPool(self, oExec, sPool): + """ + Destroys the given storage pool. + """ + fRc = oExec.execBinaryNoStdOut('zpool', ('destroy', sPool)); + return fRc; + + def cleanupPoolsAndVolumes(self, oExec, sPoolIdStart, sVolIdStart): + """ + Cleans up any pools and volumes starting with the name in the given + parameters. + """ + fRc = True; + lstPools = self._getActivePoolsStartingWith(oExec, sPoolIdStart); + if lstPools is not None: + for sPool in lstPools: + lstVolumes = self._getActiveVolumesInPoolStartingWith(oExec, sPool, sVolIdStart); + if lstVolumes is not None: + # Destroy all the volumes first + for sVolume in lstVolumes: + fRc2 = oExec.execBinaryNoStdOut('zfs', ('destroy', sVolume)); + if not fRc2: + fRc = fRc2; + + # Destroy the pool + fRc2 = self.destroyPool(oExec, sPool); + if not fRc2: + fRc = fRc2; + else: + fRc = False; + else: + fRc = False; + + return fRc; + + def createRamDisk(self, oExec, cbRamDisk): + """ + Creates a RAM backed disk with the given size. + """ + oDisk = None; + sRamDiskName = 'ramdisk%u' % (self.idxRamDisk,); + fRc, _ , _ = oExec.execBinary('ramdiskadm', ('-a', sRamDiskName, str(cbRamDisk))); + if fRc: + self.idxRamDisk += 1; + oDisk = StorageDisk('/dev/ramdisk/%s' % (sRamDiskName, ), True); + + return oDisk; + + def destroyRamDisk(self, oExec, oDisk): + """ + Destroys the given ramdisk object. + """ + sRamDiskName = os.path.basename(oDisk.getPath()); + return oExec.execBinaryNoStdOut('ramdiskadm', ('-d', sRamDiskName)); + +class StorageConfigOsLinux(StorageConfigOs): + """ + Class implementing the Linux specifics for a storage configuration. + """ + + def __init__(self): + StorageConfigOs.__init__(self); + self.dSimplePools = { }; # Simple storage pools which don't use lvm (just one partition) + self.dMounts = { }; # Pool/Volume to mountpoint mapping. + + def _getDmRaidLevelFromLvl(self, sRaidLvl): + """ + Converts our raid level indicators to something mdadm can understand. + """ + if sRaidLvl is None or sRaidLvl == 'raid0': + return 'stripe'; + if sRaidLvl == 'raid5': + return '5'; + if sRaidLvl == 'raid1': + return 'mirror'; + return 'stripe'; + + def getDisksMatchingRegExp(self, sRegExp): + """ + Returns a list of disks matching the regular expression. + """ + return self._getDisksMatchingRegExpWithPath('/dev/', sRegExp); + + def getMntBase(self): + """ + Returns the mountpoint base for the host. + """ + return '/mnt'; + + def createStoragePool(self, oExec, sPool, asDisks, sRaidLvl): + """ + Creates a new storage pool with the given disks and the given RAID level. + """ + fRc = True; + if len(asDisks) == 1 and sRaidLvl is None: + # Doesn't require LVM, put into the simple pools dictionary so we can + # use it when creating a volume later. + self.dSimplePools[sPool] = asDisks[0]; + else: + # If a RAID is required use dm-raid first to create one. + asLvmPvDisks = asDisks; + fRc = oExec.execBinaryNoStdOut('mdadm', ('--create', '/dev/md0', '--assume-clean', + '--level=' + self._getDmRaidLevelFromLvl(sRaidLvl), + '--raid-devices=' + str(len(asDisks))) + tuple(asDisks)); + if fRc: + # /dev/md0 is the only block device to use for our volume group. + asLvmPvDisks = [ '/dev/md0' ]; + + # Create a physical volume on every disk first. + for sLvmPvDisk in asLvmPvDisks: + fRc = oExec.execBinaryNoStdOut('pvcreate', (sLvmPvDisk, )); + if not fRc: + break; + + if fRc: + # Create volume group with all physical volumes included + fRc = oExec.execBinaryNoStdOut('vgcreate', (sPool, ) + tuple(asLvmPvDisks)); + return fRc; + + def createVolume(self, oExec, sPool, sVol, sMountPoint, cbVol = None): + """ + Creates and mounts a filesystem at the given mountpoint using the + given pool and volume IDs. + """ + fRc = True; + sBlkDev = None; + if sPool in self.dSimplePools: + sDiskPath = self.dSimplePools.get(sPool); + if sDiskPath.find('zram') != -1: + sBlkDev = sDiskPath; + else: + # Create a partition with the requested size + sFdiskScript = ';\n'; # Single partition filling everything + if cbVol is not None: + sFdiskScript = ',' + str(cbVol // 512) + '\n'; # Get number of sectors + fRc = oExec.execBinaryNoStdOut('sfdisk', ('--no-reread', '--wipe', 'always', '-q', '-f', sDiskPath), \ + sFdiskScript); + if fRc: + if sDiskPath.find('nvme') != -1: + sBlkDev = sDiskPath + 'p1'; + else: + sBlkDev = sDiskPath + '1'; + else: + if cbVol is None: + fRc = oExec.execBinaryNoStdOut('lvcreate', ('-l', '100%FREE', '-n', sVol, sPool)); + else: + fRc = oExec.execBinaryNoStdOut('lvcreate', ('-L', str(cbVol), '-n', sVol, sPool)); + if fRc: + sBlkDev = '/dev/mapper' + sPool + '-' + sVol; + + if fRc is True and sBlkDev is not None: + # Create a filesystem and mount it + fRc = oExec.execBinaryNoStdOut('mkfs.ext4', ('-F', '-F', sBlkDev,)); + fRc = fRc and oExec.mkDir(sMountPoint); + fRc = fRc and oExec.execBinaryNoStdOut('mount', (sBlkDev, sMountPoint)); + if fRc: + self.dMounts[sPool + '/' + sVol] = sMountPoint; + return fRc; + + def destroyVolume(self, oExec, sPool, sVol): + """ + Destroys the given volume. + """ + # Unmount first + sMountPoint = self.dMounts[sPool + '/' + sVol]; + fRc = oExec.execBinaryNoStdOut('umount', (sMountPoint,)); + self.dMounts.pop(sPool + '/' + sVol); + oExec.rmDir(sMountPoint); + if sPool in self.dSimplePools: + # Wipe partition table + sDiskPath = self.dSimplePools.get(sPool); + if sDiskPath.find('zram') == -1: + fRc = oExec.execBinaryNoStdOut('sfdisk', ('--no-reread', '--wipe', 'always', '-q', '-f', '--delete', \ + sDiskPath)); + else: + fRc = oExec.execBinaryNoStdOut('lvremove', (sPool + '/' + sVol,)); + return fRc; + + def destroyPool(self, oExec, sPool): + """ + Destroys the given storage pool. + """ + fRc = True; + if sPool in self.dSimplePools: + self.dSimplePools.pop(sPool); + else: + fRc = oExec.execBinaryNoStdOut('vgremove', (sPool,)); + return fRc; + + def cleanupPoolsAndVolumes(self, oExec, sPoolIdStart, sVolIdStart): + """ + Cleans up any pools and volumes starting with the name in the given + parameters. + """ + # @todo: Needs implementation, for LVM based configs a similar approach can be used + # as for Solaris. + _ = oExec; + _ = sPoolIdStart; + _ = sVolIdStart; + return True; + + def createRamDisk(self, oExec, cbRamDisk): + """ + Creates a RAM backed disk with the given size. + """ + # Make sure the ZRAM module is loaded. + oDisk = None; + fRc = oExec.execBinaryNoStdOut('modprobe', ('zram',)); + if fRc: + fRc, sOut, _ = oExec.execBinary('zramctl', ('--raw', '-f', '-s', str(cbRamDisk))); + if fRc: + oDisk = StorageDisk(sOut.rstrip(), True); + + return oDisk; + + def destroyRamDisk(self, oExec, oDisk): + """ + Destroys the given ramdisk object. + """ + return oExec.execBinaryNoStdOut('zramctl', ('-r', oDisk.getPath())); + +## @name Host disk config types. +## @{ +g_ksDiskCfgStatic = 'StaticDir'; +g_ksDiskCfgRegExp = 'RegExp'; +g_ksDiskCfgList = 'DiskList'; +## @} + +class DiskCfg(object): + """ + Host disk configuration. + """ + + def __init__(self, sTargetOs, sCfgType, oDisks): + self.sTargetOs = sTargetOs; + self.sCfgType = sCfgType; + self.oDisks = oDisks; + + def getTargetOs(self): + return self.sTargetOs; + + def getCfgType(self): + return self.sCfgType; + + def isCfgStaticDir(self): + return self.sCfgType == g_ksDiskCfgStatic; + + def isCfgRegExp(self): + return self.sCfgType == g_ksDiskCfgRegExp; + + def isCfgList(self): + return self.sCfgType == g_ksDiskCfgList; + + def getDisks(self): + return self.oDisks; + +class StorageCfg(object): + """ + Storage configuration helper class taking care of the different host OS. + """ + + def __init__(self, oExec, oDiskCfg): + self.oExec = oExec; + self.lstDisks = [ ]; # List of disks present in the system. + self.dPools = { }; # Dictionary of storage pools. + self.dVols = { }; # Dictionary of volumes. + self.iPoolId = 0; + self.iVolId = 0; + self.oDiskCfg = oDiskCfg; + + fRc = True; + oStorOs = None; + if oDiskCfg.getTargetOs() == 'solaris': + oStorOs = StorageConfigOsSolaris(); + elif oDiskCfg.getTargetOs() == 'linux': + oStorOs = StorageConfigOsLinux(); # pylint: disable=redefined-variable-type + elif not oDiskCfg.isCfgStaticDir(): + # For unknown hosts only allow a static testing directory we don't care about setting up + fRc = False; + + if fRc: + self.oStorOs = oStorOs; + if oDiskCfg.isCfgRegExp(): + self.lstDisks = oStorOs.getDisksMatchingRegExp(oDiskCfg.getDisks()); + elif oDiskCfg.isCfgList(): + # Assume a list of of disks and add. + for sDisk in oDiskCfg.getDisks(): + self.lstDisks.append(StorageDisk(sDisk)); + elif oDiskCfg.isCfgStaticDir(): + if not os.path.exists(oDiskCfg.getDisks()): + self.oExec.mkDir(oDiskCfg.getDisks(), 0o700); + + def __del__(self): + self.cleanup(); + self.oDiskCfg = None; + + def cleanup(self): + """ + Cleans up any created storage configs. + """ + + if not self.oDiskCfg.isCfgStaticDir(): + # Destroy all volumes first. + for sMountPoint in list(self.dVols.keys()): # pylint: disable=consider-iterating-dictionary + self.destroyVolume(sMountPoint); + + # Destroy all pools. + for sPool in list(self.dPools.keys()): # pylint: disable=consider-iterating-dictionary + self.destroyStoragePool(sPool); + + self.dVols.clear(); + self.dPools.clear(); + self.iPoolId = 0; + self.iVolId = 0; + + def getRawDisk(self): + """ + Returns a raw disk device from the list of free devices for use. + """ + + for oDisk in self.lstDisks: + if oDisk.isUsed() is False: + oDisk.setUsed(True); + return oDisk.getPath(); + + return None; + + def getUnusedDiskCount(self): + """ + Returns the number of unused disks. + """ + + cDisksUnused = 0; + for oDisk in self.lstDisks: + if not oDisk.isUsed(): + cDisksUnused += 1; + + return cDisksUnused; + + def createStoragePool(self, cDisks = 0, sRaidLvl = None, + cbPool = None, fRamDisk = False): + """ + Create a new storage pool + """ + lstDisks = [ ]; + fRc = True; + sPool = None; + + if not self.oDiskCfg.isCfgStaticDir(): + if fRamDisk: + oDisk = self.oStorOs.createRamDisk(self.oExec, cbPool); + if oDisk is not None: + lstDisks.append(oDisk); + cDisks = 1; + else: + if cDisks == 0: + cDisks = self.getUnusedDiskCount(); + + for oDisk in self.lstDisks: + if not oDisk.isUsed(): + oDisk.setUsed(True); + lstDisks.append(oDisk); + if len(lstDisks) == cDisks: + break; + + # Enough drives to satisfy the request? + if len(lstDisks) == cDisks: + # Create a list of all device paths + lstDiskPaths = [ ]; + for oDisk in lstDisks: + lstDiskPaths.append(oDisk.getPath()); + + # Find a name for the pool + sPool = 'pool' + str(self.iPoolId); + self.iPoolId += 1; + + fRc = self.oStorOs.createStoragePool(self.oExec, sPool, lstDiskPaths, sRaidLvl); + if fRc: + self.dPools[sPool] = lstDisks; + else: + self.iPoolId -= 1; + else: + fRc = False; + + # Cleanup in case of error. + if not fRc: + for oDisk in lstDisks: + oDisk.setUsed(False); + if oDisk.isRamDisk(): + self.oStorOs.destroyRamDisk(self.oExec, oDisk); + else: + sPool = 'StaticDummy'; + + return fRc, sPool; + + def destroyStoragePool(self, sPool): + """ + Destroys the storage pool with the given ID. + """ + + fRc = True; + + if not self.oDiskCfg.isCfgStaticDir(): + lstDisks = self.dPools.get(sPool); + if lstDisks is not None: + fRc = self.oStorOs.destroyPool(self.oExec, sPool); + if fRc: + # Mark disks as unused + self.dPools.pop(sPool); + for oDisk in lstDisks: + oDisk.setUsed(False); + if oDisk.isRamDisk(): + self.oStorOs.destroyRamDisk(self.oExec, oDisk); + else: + fRc = False; + + return fRc; + + def createVolume(self, sPool, cbVol = None): + """ + Creates a new volume from the given pool returning the mountpoint. + """ + + fRc = True; + sMountPoint = None; + if not self.oDiskCfg.isCfgStaticDir(): + if sPool in self.dPools: + sVol = 'vol' + str(self.iVolId); + sMountPoint = self.oStorOs.getMntBase() + '/' + sVol; + self.iVolId += 1; + fRc = self.oStorOs.createVolume(self.oExec, sPool, sVol, sMountPoint, cbVol); + if fRc: + self.dVols[sMountPoint] = (sVol, sPool); + else: + self.iVolId -= 1; + else: + fRc = False; + else: + sMountPoint = self.oDiskCfg.getDisks(); + + return fRc, sMountPoint; + + def destroyVolume(self, sMountPoint): + """ + Destroy the volume at the given mount point. + """ + + fRc = True; + if not self.oDiskCfg.isCfgStaticDir(): + sVol, sPool = self.dVols.get(sMountPoint); + if sVol is not None: + fRc = self.oStorOs.destroyVolume(self.oExec, sPool, sVol); + if fRc: + self.dVols.pop(sMountPoint); + else: + fRc = False; + + return fRc; + + def mkDirOnVolume(self, sMountPoint, sDir, fMode = 0o700): + """ + Creates a new directory on the volume pointed to by the given mount point. + """ + return self.oExec.mkDir(sMountPoint + '/' + sDir, fMode); + + def cleanupLeftovers(self): + """ + Tries to cleanup any leftover pools and volumes from a failed previous run. + """ + if not self.oDiskCfg.isCfgStaticDir(): + return self.oStorOs.cleanupPoolsAndVolumes(self.oExec, 'pool', 'vol'); + + fRc = True; + if os.path.exists(self.oDiskCfg.getDisks()): + for sEntry in os.listdir(self.oDiskCfg.getDisks()): + fRc = fRc and self.oExec.rmTree(os.path.join(self.oDiskCfg.getDisks(), sEntry)); + + return fRc; diff --git a/src/VBox/ValidationKit/tests/storage/tdStorageBenchmark1.py b/src/VBox/ValidationKit/tests/storage/tdStorageBenchmark1.py new file mode 100755 index 00000000..1e4cbbcc --- /dev/null +++ b/src/VBox/ValidationKit/tests/storage/tdStorageBenchmark1.py @@ -0,0 +1,1469 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdStorageBenchmark1.py $ + +""" +VirtualBox Validation Kit - Storage benchmark. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os; +import socket; +import sys; +if sys.version_info[0] >= 3: + from io import StringIO as StringIO; # pylint: disable=import-error,no-name-in-module,useless-import-alias +else: + from StringIO import StringIO as StringIO; # pylint: disable=import-error,no-name-in-module,useless-import-alias + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from common import constants; +from common import utils; +from testdriver import reporter; +from testdriver import base; +from testdriver import vbox; +from testdriver import vboxcon; +from testdriver import vboxwrappers; + +import remoteexecutor; +import storagecfg; + + +class FioTest(object): + """ + Flexible I/O tester testcase. + """ + + kdHostIoEngine = { + 'solaris': ('solarisaio', False), + 'linux': ('libaio', True) + }; + + def __init__(self, oExecutor, dCfg = None): + self.oExecutor = oExecutor; + self.sCfgFileId = None; + self.dCfg = dCfg; + self.sError = None; + self.sResult = None; + + def prepare(self, cMsTimeout = 30000): + """ Prepares the testcase """ + reporter.testStart('Fio'); + + sTargetOs = self.dCfg.get('TargetOs', 'linux'); + sIoEngine, fDirectIo = self.kdHostIoEngine.get(sTargetOs); + if sIoEngine is None: + return False; + + cfgBuf = StringIO(); + cfgBuf.write('[global]\n'); + cfgBuf.write('bs=' + str(self.dCfg.get('RecordSize', 4096)) + '\n'); + cfgBuf.write('ioengine=' + sIoEngine + '\n'); + cfgBuf.write('iodepth=' + str(self.dCfg.get('QueueDepth', 32)) + '\n'); + cfgBuf.write('size=' + str(self.dCfg.get('TestsetSize', 2147483648)) + '\n'); + if fDirectIo: + cfgBuf.write('direct=1\n'); + else: + cfgBuf.write('direct=0\n'); + cfgBuf.write('directory=' + self.dCfg.get('FilePath', '/mnt') + '\n'); + cfgBuf.write('filename=fio.test.file'); + + cfgBuf.write('[seq-write]\n'); + cfgBuf.write('rw=write\n'); + cfgBuf.write('stonewall\n'); + + cfgBuf.write('[rand-write]\n'); + cfgBuf.write('rw=randwrite\n'); + cfgBuf.write('stonewall\n'); + + cfgBuf.write('[seq-read]\n'); + cfgBuf.write('rw=read\n'); + cfgBuf.write('stonewall\n'); + + cfgBuf.write('[rand-read]\n'); + cfgBuf.write('rw=randread\n'); + cfgBuf.write('stonewall\n'); + + self.sCfgFileId = self.oExecutor.copyString(cfgBuf.getvalue(), 'aio-test', cMsTimeout); + return self.sCfgFileId is not None; + + def run(self, cMsTimeout = 30000): + """ Runs the testcase """ + fRc, sOutput, sError = self.oExecutor.execBinary('fio', (self.sCfgFileId,), cMsTimeout = cMsTimeout); + if fRc: + self.sResult = sOutput; + else: + self.sError = ('Binary: fio\n' + + '\nOutput:\n\n' + + sOutput + + '\nError:\n\n' + + sError); + return fRc; + + def cleanup(self): + """ Cleans up any leftovers from the testcase. """ + reporter.testDone(); + return True; + + def reportResult(self): + """ + Reports the test results to the test manager. + """ + return True; + + def getErrorReport(self): + """ + Returns the error report in case the testcase failed. + """ + return self.sError; + +class IozoneTest(object): + """ + I/O zone testcase. + """ + def __init__(self, oExecutor, dCfg = None): + self.oExecutor = oExecutor; + self.sResult = None; + self.sError = None; + self.lstTests = [ ('initial writers', 'FirstWrite'), + ('rewriters', 'Rewrite'), + ('re-readers', 'ReRead'), + ('stride readers', 'StrideRead'), + ('reverse readers', 'ReverseRead'), + ('random readers', 'RandomRead'), + ('mixed workload', 'MixedWorkload'), + ('random writers', 'RandomWrite'), + ('pwrite writers', 'PWrite'), + ('pread readers', 'PRead'), + ('fwriters', 'FWrite'), + ('freaders', 'FRead'), + ('readers', 'FirstRead')]; + self.sRecordSize = str(int(dCfg.get('RecordSize', 4096) / 1024)); + self.sTestsetSize = str(int(dCfg.get('TestsetSize', 2147483648) / 1024)); + self.sQueueDepth = str(int(dCfg.get('QueueDepth', 32))); + self.sFilePath = dCfg.get('FilePath', '/mnt/iozone'); + self.fDirectIo = True; + + sTargetOs = dCfg.get('TargetOs'); + if sTargetOs == 'solaris': + self.fDirectIo = False; + + def prepare(self, cMsTimeout = 30000): + """ Prepares the testcase """ + reporter.testStart('IoZone'); + _ = cMsTimeout; + return True; # Nothing to do. + + def run(self, cMsTimeout = 30000): + """ Runs the testcase """ + tupArgs = ('-r', self.sRecordSize, '-s', self.sTestsetSize, \ + '-t', '1', '-T', '-F', self.sFilePath + '/iozone.tmp'); + if self.fDirectIo: + tupArgs += ('-I',); + fRc, sOutput, sError = self.oExecutor.execBinary('iozone', tupArgs, cMsTimeout = cMsTimeout); + if fRc: + self.sResult = sOutput; + else: + self.sError = ('Binary: iozone\n' + + '\nOutput:\n\n' + + sOutput + + '\nError:\n\n' + + sError); + return fRc; + + def cleanup(self): + """ Cleans up any leftovers from the testcase. """ + reporter.testDone(); + return True; + + def reportResult(self): + """ + Reports the test results to the test manager. + """ + + fRc = True; + if self.sResult is not None: + try: + asLines = self.sResult.splitlines(); + for sLine in asLines: + sLine = sLine.strip(); + if sLine.startswith('Children') is True: + # Extract the value + idxValue = sLine.rfind('='); + if idxValue == -1: + raise Exception('IozoneTest: Invalid state'); + + idxValue += 1; + while sLine[idxValue] == ' ': + idxValue += 1; + + # Get the reported value, cut off after the decimal point + # it is not supported by the testmanager yet and is not really + # relevant anyway. + idxValueEnd = idxValue; + while sLine[idxValueEnd].isdigit(): + idxValueEnd += 1; + + for sNeedle, sTestVal in self.lstTests: + if sLine.rfind(sNeedle) != -1: + reporter.testValue(sTestVal, sLine[idxValue:idxValueEnd], + constants.valueunit.g_asNames[constants.valueunit.KILOBYTES_PER_SEC]); + break; + except: + fRc = False; + else: + fRc = False; + + return fRc; + + def getErrorReport(self): + """ + Returns the error report in case the testcase failed. + """ + return self.sError; + +class IoPerfTest(object): + """ + IoPerf testcase. + """ + def __init__(self, oExecutor, dCfg = None): + self.oExecutor = oExecutor; + self.sResult = None; + self.sError = None; + self.sRecordSize = str(dCfg.get('RecordSize', 4094)); + self.sTestsetSize = str(dCfg.get('TestsetSize', 2147483648)); + self.sQueueDepth = str(dCfg.get('QueueDepth', 32)); + self.sFilePath = dCfg.get('FilePath', '/mnt'); + self.fDirectIo = True; + self.asGstIoPerfPaths = [ + '${CDROM}/vboxvalidationkit/${OS/ARCH}/IoPerf${EXESUFF}', + '${CDROM}/${OS/ARCH}/IoPerf${EXESUFF}', + ]; + + sTargetOs = dCfg.get('TargetOs'); + if sTargetOs == 'solaris': + self.fDirectIo = False; + + def _locateGstIoPerf(self): + """ + Returns guest side path to FsPerf. + """ + for sIoPerfPath in self.asGstIoPerfPaths: + if self.oExecutor.isFile(sIoPerfPath): + return sIoPerfPath; + reporter.log('Unable to find guest FsPerf in any of these places: %s' % ('\n '.join(self.asGstIoPerfPaths),)); + return self.asGstIoPerfPaths[0]; + + def prepare(self, cMsTimeout = 30000): + """ Prepares the testcase """ + _ = cMsTimeout; + return True; # Nothing to do. + + def run(self, cMsTimeout = 30000): + """ Runs the testcase """ + tupArgs = ('--block-size', self.sRecordSize, '--test-set-size', self.sTestsetSize, \ + '--maximum-requests', self.sQueueDepth, '--dir', self.sFilePath + '/ioperfdir-1'); + if self.fDirectIo: + tupArgs += ('--use-cache', 'off'); + fRc, sOutput, sError = self.oExecutor.execBinary(self._locateGstIoPerf(), tupArgs, cMsTimeout = cMsTimeout); + if fRc: + self.sResult = sOutput; + else: + if sError is None: + sError = ''; + if sOutput is None: + sOutput = ''; + self.sError = ('Binary: IoPerf\n' + + '\nOutput:\n\n' + + sOutput + + '\nError:\n\n' + + sError); + return fRc; + + def cleanup(self): + """ Cleans up any leftovers from the testcase. """ + return True; + + def reportResult(self): + """ + Reports the test results to the test manager. + """ + # Should be done using the test pipe already. + return True; + + def getErrorReport(self): + """ + Returns the error report in case the testcase failed. + """ + return self.sError; + +class StorTestCfgMgr(object): + """ + Manages the different testcases. + """ + + def __init__(self, aasTestLvls, aasTestsBlacklist, fnIsCfgSupported = None): + self.aasTestsBlacklist = aasTestsBlacklist; + self.at4TestLvls = []; + self.iTestLvl = 0; + self.fnIsCfgSupported = fnIsCfgSupported; + for asTestLvl in aasTestLvls: + if isinstance(asTestLvl, tuple): + asTestLvl, fSubTestStartAuto, fnTestFmt = asTestLvl; + self.at4TestLvls.append((0, fSubTestStartAuto, fnTestFmt, asTestLvl)); + else: + self.at4TestLvls.append((0, True, None, asTestLvl)); + + self.at4TestLvls.reverse(); + + # Get the first non blacklisted test. + asTestCfg = self.getCurrentTestCfg(); + while asTestCfg and self.isTestCfgBlacklisted(asTestCfg): + asTestCfg = self.advanceTestCfg(); + + iLvl = 0; + for sCfg in asTestCfg: + sSubTest = self.getTestIdString(sCfg, iLvl); + if sSubTest is not None: + reporter.testStart('%s' % (sSubTest,)); + iLvl += 1; + + def __del__(self): + # Make sure the tests are marked as done. + while self.iTestLvl < len(self.at4TestLvls): + reporter.testDone(); + self.iTestLvl += 1; + + def getTestIdString(self, oCfg, iLvl): + """ + Returns a potentially formatted string for the test name. + """ + + # The order of the test levels is reversed so get the level starting + # from the end. + _, fSubTestStartAuto, fnTestFmt, _ = self.at4TestLvls[len(self.at4TestLvls) - 1 - iLvl]; + if not fSubTestStartAuto: + return None; + if fnTestFmt is not None: + return fnTestFmt(oCfg); + return oCfg; + + def isTestCfgBlacklisted(self, asTestCfg): + """ + Returns whether the given test config is black listed. + """ + fBlacklisted = False; + + for asTestBlacklist in self.aasTestsBlacklist: + iLvl = 0; + fBlacklisted = True; + while iLvl < len(asTestBlacklist) and iLvl < len(asTestCfg): + if asTestBlacklist[iLvl] != asTestCfg[iLvl] and asTestBlacklist[iLvl] != '*': + fBlacklisted = False; + break; + + iLvl += 1; + + if not fBlacklisted and self.fnIsCfgSupported is not None: + fBlacklisted = not self.fnIsCfgSupported(asTestCfg); + + return fBlacklisted; + + def advanceTestCfg(self): + """ + Advances to the next test config and returns it as an + array of strings or an empty config if there is no test left anymore. + """ + iTestCfg, fSubTestStartAuto, fnTestFmt, asTestCfg = self.at4TestLvls[self.iTestLvl]; + iTestCfg += 1; + self.at4TestLvls[self.iTestLvl] = (iTestCfg, fSubTestStartAuto, fnTestFmt, asTestCfg); + while iTestCfg == len(asTestCfg) and self.iTestLvl < len(self.at4TestLvls): + self.at4TestLvls[self.iTestLvl] = (0, fSubTestStartAuto, fnTestFmt, asTestCfg); + self.iTestLvl += 1; + if self.iTestLvl < len(self.at4TestLvls): + iTestCfg, fSubTestStartAuto, fnTestFmt, asTestCfg = self.at4TestLvls[self.iTestLvl]; + iTestCfg += 1; + self.at4TestLvls[self.iTestLvl] = (iTestCfg, fSubTestStartAuto, fnTestFmt, asTestCfg); + if iTestCfg < len(asTestCfg): + self.iTestLvl = 0; + break; + else: + break; # We reached the end of our tests. + + return self.getCurrentTestCfg(); + + def getCurrentTestCfg(self): + """ + Returns the current not black listed test config as an array of strings. + """ + asTestCfg = []; + + if self.iTestLvl < len(self.at4TestLvls): + for t4TestLvl in self.at4TestLvls: + iTestCfg, _, _, asTestLvl = t4TestLvl; + asTestCfg.append(asTestLvl[iTestCfg]); + + asTestCfg.reverse() + + return asTestCfg; + + def getNextTestCfg(self): + """ + Returns the next not blacklisted test config or an empty list if + there is no test left. + """ + asTestCfgCur = self.getCurrentTestCfg(); + + asTestCfg = self.advanceTestCfg(); + while asTestCfg and self.isTestCfgBlacklisted(asTestCfg): + asTestCfg = self.advanceTestCfg(); + + # Compare the current and next config and close the approriate test + # categories. + #reporter.testDone(fSkippedLast); + if asTestCfg: + idxSame = 0; + while asTestCfgCur[idxSame] == asTestCfg[idxSame]: + idxSame += 1; + + for i in range(idxSame, len(asTestCfg) - 1): + reporter.testDone(); + + for i in range(idxSame, len(asTestCfg)): + sSubTest = self.getTestIdString(asTestCfg[i], i); + if sSubTest is not None: + reporter.testStart('%s' % (sSubTest,)); + + else: + # No more tests, mark all tests as done + for i in range(0, len(asTestCfgCur) - 1): + reporter.testDone(); + + return asTestCfg; + +class tdStorageBenchmark(vbox.TestDriver): # pylint: disable=too-many-instance-attributes + """ + Storage benchmark. + """ + + # Global storage configs for the testbox + kdStorageCfgs = { + # Testbox configs (Flag whether to test raw mode on the testbox, disk configuration) + 'testboxstor1.de.oracle.com': (True, storagecfg.DiskCfg('solaris', storagecfg.g_ksDiskCfgRegExp, r'c[3-9]t\dd0\Z')), + # Windows testbox doesn't return testboxstor2.de.oracle.com from socket.getfqdn() + 'testboxstor2': (False, storagecfg.DiskCfg('win', storagecfg.g_ksDiskCfgStatic, 'D:\\StorageTest')), + + # Local test configs for the testcase developer + 'adaris': (True, storagecfg.DiskCfg('linux', storagecfg.g_ksDiskCfgStatic, \ + '/home/alexander/StorageScratch')), + 'daedalus': (True, storagecfg.DiskCfg('darwin', storagecfg.g_ksDiskCfgStatic, \ + '/Volumes/VirtualBox/Testsuite/StorageScratch')), + 'windows10': (True, storagecfg.DiskCfg('win', storagecfg.g_ksDiskCfgStatic, \ + 'L:\\Testsuite\\StorageTest')), + }; + + # Available test sets. + kdTestSets = { + # Mostly for developing and debugging the testcase. + 'Fast': { + 'RecordSize': 65536, + 'TestsetSize': 104857600, # 100 MiB + 'QueueDepth': 32, + 'DiskSizeGb': 2 + }, + # For quick functionality tests where benchmark results are not required. + 'Functionality': { + 'RecordSize': 65536, + 'TestsetSize': 2147483648, # 2 GiB + 'QueueDepth': 32, + 'DiskSizeGb': 10 + }, + # For benchmarking the I/O stack. + 'Benchmark': { + 'RecordSize': 65536, + 'TestsetSize': 21474836480, # 20 Gib + 'QueueDepth': 32, + 'DiskSizeGb': 30 + }, + # For stress testing which takes a lot of time. + 'Stress': { + 'RecordSize': 65536, + 'TestsetSize': 2199023255552, # 2 TiB + 'QueueDepth': 32, + 'DiskSizeGb': 10000 + }, + }; + + # Dictionary mapping the virtualization mode mnemonics to a little less cryptic + # strings used in test descriptions. + kdVirtModeDescs = { + 'raw' : 'Raw-mode', + 'hwvirt' : 'HwVirt', + 'hwvirt-np' : 'NestedPaging' + }; + + kdHostIoCacheDescs = { + 'default' : 'HostCacheDef', + 'hostiocache' : 'HostCacheOn', + 'no-hostiocache' : 'HostCacheOff' + }; + + # Password ID for encryption. + ksPwId = 'EncPwId'; + + # Array indexes for the test configs. + kiVmName = 0; + kiStorageCtrl = 1; + kiHostIoCache = 2; + kiDiskFmt = 3; + kiDiskVar = 4; + kiCpuCount = 5; + kiVirtMode = 6; + kiTestSet = 7; + kiIoTest = 8; + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.asTestVMsDef = ['tst-storage', 'tst-storage32']; + self.asTestVMs = self.asTestVMsDef; + self.asSkipVMs = []; + self.asVirtModesDef = ['hwvirt', 'hwvirt-np', 'raw',] + self.asVirtModes = self.asVirtModesDef; + self.acCpusDef = [1, 2]; + self.acCpus = self.acCpusDef; + self.asStorageCtrlsDef = ['AHCI', 'IDE', 'LsiLogicSAS', 'LsiLogic', 'BusLogic', 'NVMe', 'VirtIoScsi']; + self.asStorageCtrls = self.asStorageCtrlsDef; + self.asHostIoCacheDef = ['default', 'hostiocache', 'no-hostiocache']; + self.asHostIoCache = self.asHostIoCacheDef; + self.asDiskFormatsDef = ['VDI', 'VMDK', 'VHD', 'QED', 'Parallels', 'QCOW', 'iSCSI']; + self.asDiskFormats = self.asDiskFormatsDef; + self.asDiskVariantsDef = ['Dynamic', 'Fixed', 'DynamicSplit2G', 'FixedSplit2G', 'Network']; + self.asDiskVariants = self.asDiskVariantsDef; + self.asTestsDef = ['ioperf']; + self.asTests = self.asTestsDef; + self.asTestSetsDef = ['Fast', 'Functionality', 'Benchmark', 'Stress']; + self.asTestSets = self.asTestSetsDef; + self.asIscsiTargetsDef = [ ]; # @todo: Configure one target for basic iSCSI testing + self.asIscsiTargets = self.asIscsiTargetsDef; + self.cDiffLvlsDef = 0; + self.cDiffLvls = self.cDiffLvlsDef; + self.fTestHost = False; + self.fUseScratch = False; + self.fRecreateStorCfg = True; + self.fReportBenchmarkResults = True; + self.fTestRawMode = False; + self.oStorCfg = None; + self.sIoLogPathDef = self.sScratchPath; + self.sIoLogPath = self.sIoLogPathDef; + self.fIoLog = False; + self.fUseRamDiskDef = False; + self.fUseRamDisk = self.fUseRamDiskDef; + self.fEncryptDiskDef = False; + self.fEncryptDisk = self.fEncryptDiskDef; + self.sEncryptPwDef = 'TestTestTest'; + self.sEncryptPw = self.sEncryptPwDef; + self.sEncryptAlgoDef = 'AES-XTS256-PLAIN64'; + self.sEncryptAlgo = self.sEncryptAlgoDef; + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdStorageBenchmark1 Options:'); + reporter.log(' --virt-modes <m1[:m2[:]]'); + reporter.log(' Default: %s' % (':'.join(self.asVirtModesDef))); + reporter.log(' --cpu-counts <c1[:c2[:]]'); + reporter.log(' Default: %s' % (':'.join(str(c) for c in self.acCpusDef))); + reporter.log(' --storage-ctrls <type1[:type2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asStorageCtrlsDef))); + reporter.log(' --host-io-cache <setting1[:setting2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asHostIoCacheDef))); + reporter.log(' --disk-formats <type1[:type2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asDiskFormatsDef))); + reporter.log(' --disk-variants <variant1[:variant2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asDiskVariantsDef))); + reporter.log(' --iscsi-targets <target1[:target2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asIscsiTargetsDef))); + reporter.log(' --tests <test1[:test2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asTestsDef))); + reporter.log(' --test-sets <set1[:set2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asTestSetsDef))); + reporter.log(' --diff-levels <number of diffs>'); + reporter.log(' Default: %s' % (self.cDiffLvlsDef)); + reporter.log(' --test-vms <vm1[:vm2[:...]]>'); + reporter.log(' Test the specified VMs in the given order. Use this to change'); + reporter.log(' the execution order or limit the choice of VMs'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestVMsDef))); + reporter.log(' --skip-vms <vm1[:vm2[:...]]>'); + reporter.log(' Skip the specified VMs when testing.'); + reporter.log(' --test-host'); + reporter.log(' Do all configured tests on the host first and report the results'); + reporter.log(' to get a baseline'); + reporter.log(' --use-scratch'); + reporter.log(' Use the scratch directory for testing instead of setting up'); + reporter.log(' fresh volumes on dedicated disks (for development)'); + reporter.log(' --always-wipe-storage-cfg'); + reporter.log(' Recreate the host storage config before each test'); + reporter.log(' --dont-wipe-storage-cfg'); + reporter.log(' Don\'t recreate the host storage config before each test'); + reporter.log(' --report-benchmark-results'); + reporter.log(' Report all benchmark results'); + reporter.log(' --dont-report-benchmark-results'); + reporter.log(' Don\'t report any benchmark results'); + reporter.log(' --io-log-path <path>'); + reporter.log(' Default: %s' % (self.sIoLogPathDef)); + reporter.log(' --enable-io-log'); + reporter.log(' Whether to enable I/O logging for each test'); + reporter.log(' --use-ramdisk'); + reporter.log(' Default: %s' % (self.fUseRamDiskDef)); + reporter.log(' --encrypt-disk'); + reporter.log(' Default: %s' % (self.fEncryptDiskDef)); + reporter.log(' --encrypt-password'); + reporter.log(' Default: %s' % (self.sEncryptPwDef)); + reporter.log(' --encrypt-algorithm'); + reporter.log(' Default: %s' % (self.sEncryptAlgoDef)); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--virt-modes': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--virt-modes" takes a colon separated list of modes'); + self.asVirtModes = asArgs[iArg].split(':'); + for s in self.asVirtModes: + if s not in self.asVirtModesDef: + raise base.InvalidOption('The "--virt-modes" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asVirtModesDef))); + elif asArgs[iArg] == '--cpu-counts': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--cpu-counts" takes a colon separated list of cpu counts'); + self.acCpus = []; + for s in asArgs[iArg].split(':'): + try: c = int(s); + except: raise base.InvalidOption('The "--cpu-counts" value "%s" is not an integer' % (s,)); + if c <= 0: raise base.InvalidOption('The "--cpu-counts" value "%s" is zero or negative' % (s,)); + self.acCpus.append(c); + elif asArgs[iArg] == '--storage-ctrls': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--storage-ctrls" takes a colon separated list of Storage controller types'); + self.asStorageCtrls = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--host-io-cache': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--host-io-cache" takes a colon separated list of I/O cache settings'); + self.asHostIoCache = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--disk-formats': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--disk-formats" takes a colon separated list of disk formats'); + self.asDiskFormats = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--disk-variants': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--disk-variants" takes a colon separated list of disk variants'); + self.asDiskVariants = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--iscsi-targets': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--iscsi-targets" takes a colon separated list of iscsi targets'); + self.asIscsiTargets = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--tests': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--tests" takes a colon separated list of tests to run'); + self.asTests = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--test-sets': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--test-sets" takes a colon separated list of test sets'); + self.asTestSets = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--diff-levels': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--diff-levels" takes an integer'); + try: self.cDiffLvls = int(asArgs[iArg]); + except: raise base.InvalidOption('The "--diff-levels" value "%s" is not an integer' % (asArgs[iArg],)); + elif asArgs[iArg] == '--test-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--test-vms" takes colon separated list'); + self.asTestVMs = asArgs[iArg].split(':'); + for s in self.asTestVMs: + if s not in self.asTestVMsDef: + raise base.InvalidOption('The "--test-vms" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asTestVMsDef))); + elif asArgs[iArg] == '--skip-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--skip-vms" takes colon separated list'); + self.asSkipVMs = asArgs[iArg].split(':'); + for s in self.asSkipVMs: + if s not in self.asTestVMsDef: + reporter.log('warning: The "--test-vms" value "%s" does not specify any of our test VMs.' % (s)); + elif asArgs[iArg] == '--test-host': + self.fTestHost = True; + elif asArgs[iArg] == '--use-scratch': + self.fUseScratch = True; + elif asArgs[iArg] == '--always-wipe-storage-cfg': + self.fRecreateStorCfg = True; + elif asArgs[iArg] == '--dont-wipe-storage-cfg': + self.fRecreateStorCfg = False; + elif asArgs[iArg] == '--report-benchmark-results': + self.fReportBenchmarkResults = True; + elif asArgs[iArg] == '--dont-report-benchmark-results': + self.fReportBenchmarkResults = False; + elif asArgs[iArg] == '--io-log-path': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--io-log-path" takes a path argument'); + self.sIoLogPath = asArgs[iArg]; + elif asArgs[iArg] == '--enable-io-log': + self.fIoLog = True; + elif asArgs[iArg] == '--use-ramdisk': + self.fUseRamDisk = True; + elif asArgs[iArg] == '--encrypt-disk': + self.fEncryptDisk = True; + elif asArgs[iArg] == '--encrypt-password': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--encrypt-password" takes a string'); + self.sEncryptPw = asArgs[iArg]; + elif asArgs[iArg] == '--encrypt-algorithm': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--encrypt-algorithm" takes a string'); + self.sEncryptAlgo = asArgs[iArg]; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def completeOptions(self): + # Remove skipped VMs from the test list. + for sVM in self.asSkipVMs: + try: self.asTestVMs.remove(sVM); + except: pass; + + return vbox.TestDriver.completeOptions(self); + + def getResourceSet(self): + # Construct the resource list the first time it's queried. + if self.asRsrcs is None: + self.asRsrcs = []; + if 'tst-storage' in self.asTestVMs: + self.asRsrcs.append('5.0/storage/tst-storage.vdi'); + if 'tst-storage32' in self.asTestVMs: + self.asRsrcs.append('5.0/storage/tst-storage32.vdi'); + + return self.asRsrcs; + + def actionConfig(self): + + # Make sure vboxapi has been imported so we can use the constants. + if not self.importVBoxApi(): + return False; + + # + # Configure the VMs we're going to use. + # + + # Linux VMs + if 'tst-storage' in self.asTestVMs: + oVM = self.createTestVM('tst-storage', 1, '5.0/storage/tst-storage.vdi', sKind = 'ArchLinux_64', fIoApic = True, \ + eNic0AttachType = vboxcon.NetworkAttachmentType_NAT, \ + eNic0Type = vboxcon.NetworkAdapterType_Am79C973, \ + sDvdImage = self.sVBoxValidationKitIso); + if oVM is None: + return False; + + if 'tst-storage32' in self.asTestVMs: + oVM = self.createTestVM('tst-storage32', 1, '5.0/storage/tst-storage32.vdi', sKind = 'ArchLinux', fIoApic = True, \ + eNic0AttachType = vboxcon.NetworkAttachmentType_NAT, \ + eNic0Type = vboxcon.NetworkAdapterType_Am79C973, \ + sDvdImage = self.sVBoxValidationKitIso); + if oVM is None: + return False; + + return True; + + def actionExecute(self): + """ + Execute the testcase. + """ + fRc = self.test1(); + return fRc; + + + # + # Test execution helpers. + # + + def prepareStorage(self, oStorCfg, fRamDisk = False, cbPool = None): + """ + Prepares the host storage for disk images or direct testing on the host. + """ + # Create a basic pool with the default configuration. + sMountPoint = None; + fRc, sPoolId = oStorCfg.createStoragePool(cbPool = cbPool, fRamDisk = fRamDisk); + if fRc: + fRc, sMountPoint = oStorCfg.createVolume(sPoolId); + if not fRc: + sMountPoint = None; + oStorCfg.cleanup(); + + return sMountPoint; + + def cleanupStorage(self, oStorCfg): + """ + Cleans up any created storage space for a test. + """ + return oStorCfg.cleanup(); + + def getGuestDisk(self, oSession, oTxsSession, eStorageController): + """ + Gets the path of the disk in the guest to use for testing. + """ + lstDisks = None; + + # The naming scheme for NVMe is different and we don't have + # to query the guest for unformatted disks here because the disk with the OS + # is not attached to a NVMe controller. + if eStorageController == vboxcon.StorageControllerType_NVMe: + lstDisks = [ '/dev/nvme0n1' ]; + else: + # Find a unformatted disk (no partition). + # @todo: This is a hack because LIST and STAT are not yet implemented + # in TXS (get to this eventually) + lstBlkDev = [ '/dev/sda', '/dev/sdb' ]; + for sBlkDev in lstBlkDev: + fRc = oTxsSession.syncExec('/usr/bin/ls', ('ls', sBlkDev + '1')); + if not fRc: + lstDisks = [ sBlkDev ]; + break; + + _ = oSession; + return lstDisks; + + def mountValidationKitIso(self, oVmExec): + """ + Hack to get the vlaidation kit ISO mounted in the guest as it was left out + originally and I don't feel like respinning the disk image. + """ + fRc = oVmExec.mkDir('/media'); + if fRc: + fRc = oVmExec.mkDir('/media/cdrom'); + if fRc: + fRc = oVmExec.execBinaryNoStdOut('mount', ('/dev/sr0', '/media/cdrom')); + + return fRc; + + def getDiskFormatVariantsForTesting(self, sDiskFmt, asVariants): + """ + Returns a list of disk variants for testing supported by the given + disk format and selected for testing. + """ + lstDskFmts = self.oVBoxMgr.getArray(self.oVBox.systemProperties, 'mediumFormats'); + for oDskFmt in lstDskFmts: + if oDskFmt.id == sDiskFmt: + lstDskVariants = []; + lstCaps = self.oVBoxMgr.getArray(oDskFmt, 'capabilities'); + + if vboxcon.MediumFormatCapabilities_CreateDynamic in lstCaps \ + and 'Dynamic' in asVariants: + lstDskVariants.append('Dynamic'); + + if vboxcon.MediumFormatCapabilities_CreateFixed in lstCaps \ + and 'Fixed' in asVariants: + lstDskVariants.append('Fixed'); + + if vboxcon.MediumFormatCapabilities_CreateSplit2G in lstCaps \ + and vboxcon.MediumFormatCapabilities_CreateDynamic in lstCaps \ + and 'DynamicSplit2G' in asVariants: + lstDskVariants.append('DynamicSplit2G'); + + if vboxcon.MediumFormatCapabilities_CreateSplit2G in lstCaps \ + and vboxcon.MediumFormatCapabilities_CreateFixed in lstCaps \ + and 'FixedSplit2G' in asVariants: + lstDskVariants.append('FixedSplit2G'); + + if vboxcon.MediumFormatCapabilities_TcpNetworking in lstCaps \ + and 'Network' in asVariants: + lstDskVariants.append('Network'); # Solely for iSCSI to get a non empty list + + return lstDskVariants; + + return []; + + def convDiskToMediumVariant(self, sDiskVariant): + """ + Returns a tuple of medium variant flags matching the given disk variant. + """ + tMediumVariant = None; + if sDiskVariant == 'Dynamic': + tMediumVariant = (vboxcon.MediumVariant_Standard, ); + elif sDiskVariant == 'Fixed': + tMediumVariant = (vboxcon.MediumVariant_Fixed, ); + elif sDiskVariant == 'DynamicSplit2G': + tMediumVariant = (vboxcon.MediumVariant_Standard, vboxcon.MediumVariant_VmdkSplit2G); + elif sDiskVariant == 'FixedSplit2G': + tMediumVariant = (vboxcon.MediumVariant_Fixed, vboxcon.MediumVariant_VmdkSplit2G); + + return tMediumVariant; + + def getStorageCtrlFromName(self, sStorageCtrl): + """ + Resolves the storage controller string to the matching constant. + """ + eStorageCtrl = None; + + if sStorageCtrl == 'AHCI': + eStorageCtrl = vboxcon.StorageControllerType_IntelAhci; + elif sStorageCtrl == 'IDE': + eStorageCtrl = vboxcon.StorageControllerType_PIIX4; + elif sStorageCtrl == 'LsiLogicSAS': + eStorageCtrl = vboxcon.StorageControllerType_LsiLogicSas; + elif sStorageCtrl == 'LsiLogic': + eStorageCtrl = vboxcon.StorageControllerType_LsiLogic; + elif sStorageCtrl == 'BusLogic': + eStorageCtrl = vboxcon.StorageControllerType_BusLogic; + elif sStorageCtrl == 'NVMe': + eStorageCtrl = vboxcon.StorageControllerType_NVMe; + elif sStorageCtrl == 'VirtIoScsi': + eStorageCtrl = vboxcon.StorageControllerType_VirtioSCSI; + + return eStorageCtrl; + + def getStorageDriverFromEnum(self, eStorageCtrl, fHardDisk): + """ + Returns the appropriate driver name for the given storage controller + and a flag whether the driver has the generic SCSI driver attached. + """ + if eStorageCtrl == vboxcon.StorageControllerType_IntelAhci: + if fHardDisk: + return ('ahci', False); + return ('ahci', True); + if eStorageCtrl == vboxcon.StorageControllerType_PIIX4: + return ('piix3ide', False); + if eStorageCtrl == vboxcon.StorageControllerType_LsiLogicSas: + return ('lsilogicsas', True); + if eStorageCtrl == vboxcon.StorageControllerType_LsiLogic: + return ('lsilogicscsi', True); + if eStorageCtrl == vboxcon.StorageControllerType_BusLogic: + return ('buslogic', True); + if eStorageCtrl == vboxcon.StorageControllerType_NVMe: + return ('nvme', False); + if eStorageCtrl == vboxcon.StorageControllerType_VirtioSCSI: + return ('virtio-scsi', True); + + return ('<invalid>', False); + + def isTestCfgSupported(self, asTestCfg): + """ + Returns whether a specific test config is supported. + """ + + # Check whether the disk variant is supported by the selected format. + asVariants = self.getDiskFormatVariantsForTesting(asTestCfg[self.kiDiskFmt], [ asTestCfg[self.kiDiskVar] ]); + if not asVariants: + return False; + + # For iSCSI check whether we have targets configured. + if asTestCfg[self.kiDiskFmt] == 'iSCSI' and not self.asIscsiTargets: + return False; + + # Check for virt mode, CPU count and selected VM. + if asTestCfg[self.kiVirtMode] == 'raw' \ + and ( asTestCfg[self.kiCpuCount] > 1 \ + or asTestCfg[self.kiVmName] == 'tst-storage' \ + or not self.fTestRawMode): + return False; + + # IDE does not support the no host I/O cache setting + if asTestCfg[self.kiHostIoCache] == 'no-hostiocache' \ + and asTestCfg[self.kiStorageCtrl] == 'IDE': + return False; + + return True; + + def fnFormatCpuString(self, cCpus): + """ + Formats the CPU count to be readable. + """ + if cCpus == 1: + return '1 cpu'; + return '%u cpus' % (cCpus); + + def fnFormatVirtMode(self, sVirtMode): + """ + Formats the virtualization mode to be a little less cryptic for use in test + descriptions. + """ + return self.kdVirtModeDescs[sVirtMode]; + + def fnFormatHostIoCache(self, sHostIoCache): + """ + Formats the host I/O cache mode to be a little less cryptic for use in test + descriptions. + """ + return self.kdHostIoCacheDescs[sHostIoCache]; + + def testBenchmark(self, sTargetOs, sBenchmark, sMountpoint, oExecutor, dTestSet, \ + cMsTimeout = 3600000): + """ + Runs the given benchmark on the test host. + """ + + dTestSet['FilePath'] = sMountpoint; + dTestSet['TargetOs'] = sTargetOs; + + oTst = None; + if sBenchmark == 'iozone': + oTst = IozoneTest(oExecutor, dTestSet); + elif sBenchmark == 'fio': + oTst = FioTest(oExecutor, dTestSet); # pylint: disable=redefined-variable-type + elif sBenchmark == 'ioperf': + oTst = IoPerfTest(oExecutor, dTestSet); # pylint: disable=redefined-variable-type + + if oTst is not None: + fRc = oTst.prepare(); + if fRc: + fRc = oTst.run(cMsTimeout); + if fRc: + if self.fReportBenchmarkResults: + fRc = oTst.reportResult(); + else: + reporter.testFailure('Running the testcase failed'); + reporter.addLogString(oTst.getErrorReport(), sBenchmark + '.log', + 'log/release/client', 'Benchmark raw output'); + else: + reporter.testFailure('Preparing the testcase failed'); + + oTst.cleanup(); + + return fRc; + + def createHd(self, oSession, sDiskFormat, sDiskVariant, iDiffLvl, oHdParent, \ + sDiskPath, cbDisk): + """ + Creates a new disk with the given parameters returning the medium object + on success. + """ + + oHd = None; + if sDiskFormat == "iSCSI" and iDiffLvl == 0: + listNames = []; + listValues = []; + listValues = self.asIscsiTargets[0].split('|'); + listNames.append('TargetAddress'); + listNames.append('TargetName'); + listNames.append('LUN'); + + if self.fpApiVer >= 5.0: + oHd = oSession.oVBox.createMedium(sDiskFormat, sDiskPath, vboxcon.AccessMode_ReadWrite, \ + vboxcon.DeviceType_HardDisk); + else: + oHd = oSession.oVBox.createHardDisk(sDiskFormat, sDiskPath); + oHd.type = vboxcon.MediumType_Normal; + oHd.setProperties(listNames, listValues); + else: + if iDiffLvl == 0: + tMediumVariant = self.convDiskToMediumVariant(sDiskVariant); + oHd = oSession.createBaseHd(sDiskPath + '/base.img', sDiskFormat, cbDisk, \ + cMsTimeout = 3600 * 1000, tMediumVariant = tMediumVariant); + else: + sDiskPath = sDiskPath + '/diff_%u.img' % (iDiffLvl); + oHd = oSession.createDiffHd(oHdParent, sDiskPath, None); + + if oHd is not None and iDiffLvl == 0 and self.fEncryptDisk: + try: + oIProgress = oHd.changeEncryption('', self.sEncryptAlgo, self.sEncryptPw, self.ksPwId); + oProgress = vboxwrappers.ProgressWrapper(oIProgress, self.oVBoxMgr, self, 'Encrypting "%s"' % (sDiskPath,)); + oProgress.wait(60*60000); # Wait for up to one hour, fixed disks take longer to encrypt. + if oProgress.logResult() is False: + raise base.GenError('Encrypting disk "%s" failed' % (sDiskPath, )); + except: + reporter.errorXcpt('changeEncryption("%s","%s","%s") failed on "%s"' \ + % ('', self.sEncryptAlgo, self.sEncryptPw, oSession.sName) ); + self.oVBox.deleteHdByMedium(oHd); + oHd = None; + else: + reporter.log('Encrypted "%s"' % (sDiskPath,)); + + return oHd; + + def startVmAndConnect(self, sVmName): + """ + Our own implementation of startVmAndConnectToTxsViaTcp to make it possible + to add passwords to a running VM when encryption is used. + """ + oSession = self.startVmByName(sVmName); + if oSession is not None: + # Add password to the session in case encryption is used. + fRc = True; + if self.fEncryptDisk: + try: + if self.fpApiVer >= 7.0: + oSession.o.console.addEncryptionPassword(self.ksPwId, self.sEncryptPw, False); + else: + oSession.o.console.addDiskEncryptionPassword(self.ksPwId, self.sEncryptPw, False); + except: + reporter.logXcpt(); + fRc = False; + + # Connect to TXS. + if fRc: + reporter.log2('startVmAndConnect: Started(/prepared) "%s", connecting to TXS ...' % (sVmName,)); + (fRc, oTxsSession) = self.txsDoConnectViaTcp(oSession, 15*60000, fNatForwardingForTxs = True); + if fRc is True: + if fRc is True: + # Success! + return (oSession, oTxsSession); + else: + reporter.error('startVmAndConnect: txsDoConnectViaTcp failed'); + # If something went wrong while waiting for TXS to be started - take VM screenshot before terminate it + + self.terminateVmBySession(oSession); + + return (None, None); + + def testOneCfg(self, sVmName, eStorageController, sHostIoCache, sDiskFormat, # pylint: disable=too-many-arguments,too-many-locals,too-many-statements + sDiskVariant, sDiskPath, cCpus, sIoTest, sVirtMode, sTestSet): + """ + Runs the specified VM thru test #1. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + oVM = self.getVmByName(sVmName); + + dTestSet = self.kdTestSets.get(sTestSet); + cbDisk = dTestSet.get('DiskSizeGb') * 1024*1024*1024; + fHwVirt = sVirtMode != 'raw'; + fNestedPaging = sVirtMode == 'hwvirt-np'; + + fRc = True; + if sDiskFormat == 'iSCSI': + sDiskPath = self.asIscsiTargets[0]; + elif self.fUseScratch: + sDiskPath = self.sScratchPath; + else: + # If requested recreate the storage space to start with a clean config + # for benchmarks + if self.fRecreateStorCfg: + sMountPoint = self.prepareStorage(self.oStorCfg, self.fUseRamDisk, 2 * cbDisk); + if sMountPoint is not None: + # Create a directory where every normal user can write to. + self.oStorCfg.mkDirOnVolume(sMountPoint, 'test', 0o777); + sDiskPath = sMountPoint + '/test'; + else: + fRc = False; + reporter.testFailure('Failed to prepare storage for VM'); + + if not fRc: + return fRc; + + lstDisks = []; # List of disks we have to delete afterwards. + + for iDiffLvl in range(self.cDiffLvls + 1): + sIoLogFile = None; + + if iDiffLvl == 0: + reporter.testStart('Base'); + else: + reporter.testStart('Diff %u' % (iDiffLvl)); + + # Reconfigure the VM + oSession = self.openSession(oVM); + if oSession is not None: + # + # Disable audio controller which shares the interrupt line with the BusLogic controller and is suspected to cause + # rare test failures because the device initialization fails. + # + fRc = oSession.setupAudio(vboxcon.AudioControllerType_AC97, False); + # Attach HD + fRc = fRc and oSession.ensureControllerAttached(self.controllerTypeToName(eStorageController)); + fRc = fRc and oSession.setStorageControllerType(eStorageController, + self.controllerTypeToName(eStorageController)); + + if sHostIoCache == 'hostiocache': + fRc = fRc and oSession.setStorageControllerHostIoCache(self.controllerTypeToName(eStorageController), True); + elif sHostIoCache == 'no-hostiocache': + fRc = fRc and oSession.setStorageControllerHostIoCache(self.controllerTypeToName(eStorageController), False); + + iDevice = 0; + if eStorageController in (vboxcon.StorageControllerType_PIIX3, vboxcon.StorageControllerType_PIIX4,): + iDevice = 1; # Master is for the OS. + + oHdParent = None; + if iDiffLvl > 0: + oHdParent = lstDisks[0]; + oHd = self.createHd(oSession, sDiskFormat, sDiskVariant, iDiffLvl, oHdParent, sDiskPath, cbDisk); + if oHd is not None: + lstDisks.insert(0, oHd); + try: + if oSession.fpApiVer >= 4.0: + oSession.o.machine.attachDevice(self.controllerTypeToName(eStorageController), + 0, iDevice, vboxcon.DeviceType_HardDisk, oHd); + else: + oSession.o.machine.attachDevice(self.controllerTypeToName(eStorageController), + 0, iDevice, vboxcon.DeviceType_HardDisk, oHd.id); + except: + reporter.errorXcpt('attachDevice("%s",%s,%s,HardDisk,"%s") failed on "%s"' \ + % (self.controllerTypeToName(eStorageController), 1, 0, oHd.id, oSession.sName) ); + fRc = False; + else: + reporter.log('attached "%s" to %s' % (sDiskPath, oSession.sName)); + else: + fRc = False; + + # Set up the I/O logging config if enabled + if fRc and self.fIoLog: + try: + oSession.o.machine.setExtraData('VBoxInternal2/EnableDiskIntegrityDriver', '1'); + + iLun = 0; + if eStorageController in (vboxcon.StorageControllerType_PIIX3, vboxcon.StorageControllerType_PIIX4,): + iLun = 1 + sDrv, fDrvScsi = self.getStorageDriverFromEnum(eStorageController, True); + if fDrvScsi: + sCfgmPath = 'VBoxInternal/Devices/%s/0/LUN#%u/AttachedDriver/Config' % (sDrv, iLun); + else: + sCfgmPath = 'VBoxInternal/Devices/%s/0/LUN#%u/Config' % (sDrv, iLun); + + sIoLogFile = '%s/%s.iolog' % (self.sIoLogPath, sDrv); + print(sCfgmPath); + print(sIoLogFile); + oSession.o.machine.setExtraData('%s/IoLog' % (sCfgmPath,), sIoLogFile); + except: + reporter.logXcpt(); + + fRc = fRc and oSession.enableVirtEx(fHwVirt); + fRc = fRc and oSession.enableNestedPaging(fNestedPaging); + fRc = fRc and oSession.setCpuCount(cCpus); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + + # Start up. + if fRc is True: + self.logVmInfo(oVM); + oSession, oTxsSession = self.startVmAndConnect(sVmName); + if oSession is not None: + self.addTask(oTxsSession); + + # Fudge factor - Allow the guest to finish starting up. + self.sleep(5); + + # Prepare the storage on the guest + lstBinaryPaths = ['/bin', '/sbin', '/usr/bin', '/usr/sbin' ]; + oExecVm = remoteexecutor.RemoteExecutor(oTxsSession, lstBinaryPaths, '${SCRATCH}'); + fRc = self.mountValidationKitIso(oExecVm); + if fRc: + oGstDiskCfg = storagecfg.DiskCfg('linux', storagecfg.g_ksDiskCfgList, + self.getGuestDisk(oSession, oTxsSession, eStorageController)); + oStorCfgVm = storagecfg.StorageCfg(oExecVm, oGstDiskCfg); + + iTry = 0; + while iTry < 3: + sMountPoint = self.prepareStorage(oStorCfgVm); + if sMountPoint is not None: + reporter.log('Prepared storage on %s try' % (iTry + 1,)); + break; + iTry = iTry + 1; + self.sleep(5); + + if sMountPoint is not None: + # 3 hours max (Benchmark and QED takes a lot of time) + self.testBenchmark('linux', sIoTest, sMountPoint, oExecVm, dTestSet, cMsTimeout = 3 * 3600 * 1000); + self.cleanupStorage(oStorCfgVm); + else: + reporter.testFailure('Failed to prepare storage for the guest benchmark'); + + # cleanup. + self.removeTask(oTxsSession); + self.terminateVmBySession(oSession); + + # Add the I/O log if it exists and the test failed + if reporter.testErrorCount() > 0 \ + and sIoLogFile is not None \ + and os.path.exists(sIoLogFile): + reporter.addLogFile(sIoLogFile, 'misc/other', 'I/O log'); + os.remove(sIoLogFile); + else: + reporter.testFailure('Failed to mount validation kit ISO'); + + else: + fRc = False; + + # Remove disk + oSession = self.openSession(oVM); + if oSession is not None: + try: + oSession.o.machine.detachDevice(self.controllerTypeToName(eStorageController), 0, iDevice); + + # Remove storage controller if it is not an IDE controller. + if eStorageController not in (vboxcon.StorageControllerType_PIIX3, vboxcon.StorageControllerType_PIIX4,): + oSession.o.machine.removeStorageController(self.controllerTypeToName(eStorageController)); + + oSession.saveSettings(); + oSession.saveSettings(); + oSession.close(); + oSession = None; + except: + reporter.errorXcpt('failed to detach/delete disk %s from storage controller' % (sDiskPath)); + else: + fRc = False; + + reporter.testDone(); + + # Delete all disks + for oHd in lstDisks: + self.oVBox.deleteHdByMedium(oHd); + + # Cleanup storage area + if sDiskFormat != 'iSCSI' and not self.fUseScratch and self.fRecreateStorCfg: + self.cleanupStorage(self.oStorCfg); + + return fRc; + + def testStorage(self, sDiskPath = None): + """ + Runs the storage testcase through the selected configurations + """ + + aasTestCfgs = []; + aasTestCfgs.insert(self.kiVmName, self.asTestVMs); + aasTestCfgs.insert(self.kiStorageCtrl, self.asStorageCtrls); + aasTestCfgs.insert(self.kiHostIoCache, (self.asHostIoCache, True, self.fnFormatHostIoCache)); + aasTestCfgs.insert(self.kiDiskFmt, self.asDiskFormats); + aasTestCfgs.insert(self.kiDiskVar, self.asDiskVariants); + aasTestCfgs.insert(self.kiCpuCount, (self.acCpus, True, self.fnFormatCpuString)); + aasTestCfgs.insert(self.kiVirtMode, (self.asVirtModes, True, self.fnFormatVirtMode)); + aasTestCfgs.insert(self.kiTestSet, self.asTestSets); + aasTestCfgs.insert(self.kiIoTest, (self.asTests, False, None)); + + aasTestsBlacklist = []; + aasTestsBlacklist.append(['tst-storage', 'BusLogic']); # 64bit Linux is broken with BusLogic + + oTstCfgMgr = StorTestCfgMgr(aasTestCfgs, aasTestsBlacklist, self.isTestCfgSupported); + + fRc = True; + asTestCfg = oTstCfgMgr.getCurrentTestCfg(); + while asTestCfg: + fRc = self.testOneCfg(asTestCfg[self.kiVmName], self.getStorageCtrlFromName(asTestCfg[self.kiStorageCtrl]), \ + asTestCfg[self.kiHostIoCache], asTestCfg[self.kiDiskFmt], asTestCfg[self.kiDiskVar], + sDiskPath, asTestCfg[self.kiCpuCount], asTestCfg[self.kiIoTest], \ + asTestCfg[self.kiVirtMode], asTestCfg[self.kiTestSet]) and fRc and True; # pychecker hack. + + asTestCfg = oTstCfgMgr.getNextTestCfg(); + + return fRc; + + def test1(self): + """ + Executes test #1. + """ + + fRc = True; + tupTstCfg = self.kdStorageCfgs.get(socket.getfqdn().lower()); + if tupTstCfg is None: + tupTstCfg = self.kdStorageCfgs.get(socket.gethostname().lower()); + + # Test the host first if requested + if tupTstCfg is not None or self.fUseScratch: + self.fTestRawMode = tupTstCfg[0]; + oDiskCfg = tupTstCfg[1]; + lstBinaryPaths = ['/bin', '/sbin', '/usr/bin', '/usr/sbin', \ + '/opt/csw/bin', '/usr/ccs/bin', '/usr/sfw/bin']; + oExecutor = remoteexecutor.RemoteExecutor(None, lstBinaryPaths, self.sScratchPath); + if not self.fUseScratch: + self.oStorCfg = storagecfg.StorageCfg(oExecutor, oDiskCfg); + + # Try to cleanup any leftovers from a previous run first. + fRc = self.oStorCfg.cleanupLeftovers(); + if not fRc: + reporter.error('Failed to cleanup any leftovers from a previous run'); + + if self.fTestHost: + reporter.testStart('Host'); + if self.fUseScratch: + sMountPoint = self.sScratchPath; + else: + sMountPoint = self.prepareStorage(self.oStorCfg); + if sMountPoint is not None: + for sIoTest in self.asTests: + for sTestSet in self.asTestSets: + reporter.testStart(sTestSet); + dTestSet = self.kdTestSets.get(sTestSet); + self.testBenchmark(utils.getHostOs(), sIoTest, sMountPoint, oExecutor, dTestSet); + reporter.testDone(); + self.cleanupStorage(self.oStorCfg); + else: + reporter.testFailure('Failed to prepare host storage'); + fRc = False; + reporter.testDone(); + else: + # Create the storage space first if it is not done before every test. + sMountPoint = None; + if self.fUseScratch: + sMountPoint = self.sScratchPath; + elif not self.fRecreateStorCfg: + reporter.testStart('Create host storage'); + sMountPoint = self.prepareStorage(self.oStorCfg); + if sMountPoint is None: + reporter.testFailure('Failed to prepare host storage'); + fRc = False; + self.oStorCfg.mkDirOnVolume(sMountPoint, 'test', 0o777); + sMountPoint = sMountPoint + '/test'; + reporter.testDone(); + + if fRc: + # Run the storage tests. + if not self.testStorage(sMountPoint): + fRc = False; + + if not self.fRecreateStorCfg and not self.fUseScratch: + self.cleanupStorage(self.oStorCfg); + else: + reporter.testFailure('Could not get disk configuration for host: %s' % (socket.getfqdn().lower())); + fRc = False; + + return fRc; + +if __name__ == '__main__': + sys.exit(tdStorageBenchmark().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/storage/tdStorageRawDrive1.py b/src/VBox/ValidationKit/tests/storage/tdStorageRawDrive1.py new file mode 100755 index 00000000..1f667c2f --- /dev/null +++ b/src/VBox/ValidationKit/tests/storage/tdStorageRawDrive1.py @@ -0,0 +1,1692 @@ + +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +VirtualBox Validation Kit - VMDK raw disk tests. +""" + +__copyright__ = \ +""" +Copyright (C) 2013-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Id: tdStorageRawDrive1.py $" + +# Standard Python imports. +import os; +import re; +import sys; + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from common import utils; +from testdriver import reporter; +from testdriver import base; +from testdriver import vbox; +from testdriver import vboxcon; +from testdriver import vboxtestvms; +from testdriver import vboxwrappers; + + +class tdStorageRawDriveOs(vboxtestvms.BaseTestVm): + """ + Base autostart helper class to provide common methods. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None, sBootSector = None): + vboxtestvms.BaseTestVm.__init__(self, sVmName, oSet = oSet, sKind = sKind); + self.oTstDrv = oTstDrv; + self.sHdd = sHdd; + self.eNic0Type = eNic0Type; + self.cMbRam = cMbRam; + self.cCpus = cCpus; + self.fPae = fPae; + self.sGuestAdditionsIso = sGuestAdditionsIso; + self.asTestBuildDirs = oTstDrv.asTestBuildDirs; + self.sVBoxInstaller = ""; + self.sVMDKPath='/home/vbox/vmdk'; + self.asVirtModesSup = ['hwvirt-np',]; + self.asParavirtModesSup = ['default',]; + self.sBootSector = sBootSector; + self.sPathDelimiter = '/'; + + # Had to move it here from oTestDrv because the output is platform-dependent + self.asHdds = \ + { '6.1/storage/t-mbr.vdi' : + { + 'Header' : + { + #Drive: /dev/sdb + 'Model' : '"ATA VBOX HARDDISK"', + 'UUID' : '62d4f394-0000-0000-0000-000000000000', + 'Size' : '2.0GiB', + 'Sector Size' : '512 bytes', + 'Scheme' : 'MBR', + }, + 'Partitions' : + { + 'Partitions' : + [ + '$(1) 07 10.0MiB 1.0MiB 0/ 32/33 1/102/37 no IFS', + '$(2) 83 10.0MiB 11.0MiB 5/ 93/33 11/ 29/14 no Linux', + '$(3) 07 10.0MiB 21.0MiB 2/172/43 3/242/47 no IFS', + '$(4) 07 10.0MiB 32.0MiB 4/ 20/17 5/ 90/21 no IFS', + '$(5) 83 10.0MiB 43.0MiB 5/122/54 6/192/58 no Linux', + '$(6) 07 10.0MiB 54.0MiB 6/225/28 8/ 40/32 no IFS', + '$(7) 83 10.0MiB 65.0MiB 8/ 73/ 2 9/143/ 6 no Linux', + '$(8) 07 1.9GiB 76.0MiB 9/175/39 260/243/47 no IFS', + ], + 'PartitionNumbers' : [1, 2, 3, 5, 6, 7, 8, 9], + }, + } , + '6.1/storage/t-gpt.vdi' : + { + 'Header' : + { + #Drive: /dev/sdc + 'Model' : '"ATA VBOX HARDDISK"', + 'UUID' : '7b642ab1-9d44-b844-a860-ce71e0686274', + 'Size' : '2.0GiB', + 'Sector Size' : '512 bytes', + 'Scheme' : 'GPT', + }, + 'Partitions' : + { + 'Partitions' : + [ + '$(1) WindowsBasicData 560b261d-081f-fb4a-8df8-c64fffcb2bd1 10.0MiB 1.0MiB off', + '$(2) LinuxData 629f66be-0254-7c4f-a328-cc033e4de124 10.0MiB 11.0MiB off', + '$(3) WindowsBasicData d3f56c96-3b28-7f44-a53d-85b8bc93bd91 10.0MiB 21.0MiB off', + '$(4) LinuxData 27c0f5ad-74c8-d54f-835f-06e51b3f10ef 10.0MiB 31.0MiB off', + '$(5) WindowsBasicData 6cf1fdf0-b2ae-3849-9cfa-c056f9d8b722 10.0MiB 41.0MiB off', + '$(6) LinuxData 017bcbed-8b96-be4d-925a-2f872194fbe6 10.0MiB 51.0MiB off', + '$(7) WindowsBasicData af6c4f89-8fc3-5049-9d98-3e2e98061073 10.0MiB 61.0MiB off', + '$(8) LinuxData 9704d7cd-810f-4d44-ac78-432ebc16143f 10.0MiB 71.0MiB off', + '$(9) WindowsBasicData a05f8e09-f9e7-5b4e-bb4e-e9f8fde3110e 1.9GiB 81.0MiB off', + ], + 'PartitionNumbers' : [1, 2, 3, 4, 5, 6, 7, 8, 9], + }, + + } + }; + self.asActions = \ + [ + { + 'action' : 'whole drive', + 'options' : [], + 'data-crc' : {}, + 'createType' : 'fullDevice', + 'extents' : { '6.1/storage/t-mbr.vdi' : ['RW 0 FLAT "$(disk)" 0',], + '6.1/storage/t-gpt.vdi' : ['RW 0 FLAT "$(disk)" 0',], + }, + }, + { + 'action' : '1 partition', + 'options' : ['--property', 'Partitions=1'], + 'data-crc' : {'6.1/storage/t-mbr.vdi' : 2681429243, + '6.1/storage/t-gpt.vdi' : 1391394051, + }, + 'createType' : 'partitionedDevice', + 'extents' : { '6.1/storage/t-mbr.vdi' : + ['RW 2048 FLAT "vmdktest-pt.vmdk" 0', + 'RW 20480 FLAT "$(disk)" 2048', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 2048', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 4096', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 6144', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 8192', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 10240', + 'RW 4036608 ZERO', + 'RW 36028797014771712 ZERO', + ], + '6.1/storage/t-gpt.vdi' : + ['RW 1 FLAT "vmdktest-pt.vmdk" 0', + 'RW 2047 FLAT "vmdktest-pt.vmdk" 1', + 'RW 20480 FLAT "$(disk)" 2048', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 4026368 ZERO', + 'RW 36028797014771712 ZERO', + ], + }, + }, + { + 'action' : '2 partitions', + 'options' : ['--property', 'Partitions=1,$(4)'], + 'data-crc' : {'6.1/storage/t-mbr.vdi' : 2681429243, + '6.1/storage/t-gpt.vdi' : 1391394051, + }, + 'createType' : 'partitionedDevice', + 'extents' : { '6.1/storage/t-mbr.vdi' : + ['RW 2048 FLAT "vmdktest-pt.vmdk" 0', + 'RW 20480 FLAT "$(disk)" 2048', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 2048', + 'RW 20480 FLAT "$(disk)" 65536', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 4096', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 6144', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 8192', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 10240', + 'RW 4036608 ZERO', + 'RW 36028797014771712 ZERO', + ], + '6.1/storage/t-gpt.vdi' : + ['RW 1 FLAT "vmdktest-pt.vmdk" 0', + 'RW 2047 FLAT "vmdktest-pt.vmdk" 1', + 'RW 20480 FLAT "$(disk)" 2048', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 FLAT "$(disk)" 63488', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 4026368 ZERO', + 'RW 36028797014771712 ZERO', + ], + }, + }, + { + 'action' : '1 partition with boot sector', + 'options' : ['--property', 'Partitions=1', + '--property-file', 'BootSector=$(bootsector)'], + 'data-crc' : {'6.1/storage/t-mbr.vdi' : 3980784439, + '6.1/storage/t-gpt.vdi' : 1152317131, + }, + 'createType' : 'partitionedDevice', + 'extents' : { '6.1/storage/t-mbr.vdi' : + ['RW 2048 FLAT "vmdktest-pt.vmdk" 0', + 'RW 20480 FLAT "$(disk)" 2048', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 2048', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 4096', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 6144', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 8192', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 10240', + 'RW 4036608 ZERO', + 'RW 36028797014771712 ZERO', + ], + '6.1/storage/t-gpt.vdi' : + ['RW 1 FLAT "vmdktest-pt.vmdk" 0', + 'RW 2047 FLAT "vmdktest-pt.vmdk" 1', + 'RW 20480 FLAT "$(disk)" 2048', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 4026368 ZERO', + 'RW 36028797014771712 ZERO', + ], + }, + }, + { + 'action' : '2 partitions with boot sector', + 'options' : ['--property', 'Partitions=1,$(4)', + '--property-file', 'BootSector=$(bootsector)'], + 'data-crc' : {'6.1/storage/t-mbr.vdi' : 3980784439, + '6.1/storage/t-gpt.vdi' : 1152317131, + }, + 'createType' : 'partitionedDevice', + 'extents' : { '6.1/storage/t-mbr.vdi' : + ['RW 2048 FLAT "vmdktest-pt.vmdk" 0', + 'RW 20480 FLAT "$(disk)" 2048', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 2048', + 'RW 20480 FLAT "$(disk)" 65536', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 4096', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 6144', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 8192', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 10240', + 'RW 4036608 ZERO', + 'RW 36028797014771712 ZERO', + ], + '6.1/storage/t-gpt.vdi' : + ['RW 1 FLAT "vmdktest-pt.vmdk" 0', + 'RW 2047 FLAT "vmdktest-pt.vmdk" 1', + 'RW 20480 FLAT "$(disk)" 2048', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 FLAT "$(disk)" 63488', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 4026368 ZERO', + 'RW 36028797014771712 ZERO', + ], + }, + }, + { + 'action' : '1 partition with relative names', + 'options' : ['--property', 'Partitions=1', '--property', 'Relative=1'], + 'data-crc' : {'6.1/storage/t-mbr.vdi' : 2681429243, + '6.1/storage/t-gpt.vdi' : 1391394051, + }, + 'createType' : 'partitionedDevice', + 'extents' : { '6.1/storage/t-mbr.vdi' : + ['RW 2048 FLAT "vmdktest-pt.vmdk" 0', + 'RW 20480 FLAT "$(part)1" 0', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 2048', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 4096', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 6144', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 8192', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 10240', + 'RW 4036608 ZERO', + 'RW 36028797014771712 ZERO', + ], + '6.1/storage/t-gpt.vdi' : + ['RW 1 FLAT "vmdktest-pt.vmdk" 0', + 'RW 2047 FLAT "vmdktest-pt.vmdk" 1', + 'RW 20480 FLAT "$(part)1" 0', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 4026368 ZERO', + 'RW 36028797014771712 ZERO', + ], + }, + }, + { + 'action' : '2 partitions with relative names', + 'options' : ['--property', 'Partitions=1,$(4)', '--property', 'Relative=1'], + 'data-crc' : {'6.1/storage/t-mbr.vdi' : 2681429243, + '6.1/storage/t-gpt.vdi' : 1391394051, + }, + 'createType' : 'partitionedDevice', + 'extents' : { '6.1/storage/t-mbr.vdi' : + ['RW 2048 FLAT "vmdktest-pt.vmdk" 0', + 'RW 20480 FLAT "$(part)1" 0', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 2048', + 'RW 20480 FLAT "$(part)$(4)" 0', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 4096', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 6144', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 8192', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 10240', + 'RW 4036608 ZERO', + 'RW 36028797014771712 ZERO', + ], + '6.1/storage/t-gpt.vdi' : + ['RW 1 FLAT "vmdktest-pt.vmdk" 0', + 'RW 2047 FLAT "vmdktest-pt.vmdk" 1', + 'RW 20480 FLAT "$(part)1" 0', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 FLAT "$(part)$(4)" 0', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 4026368 ZERO', + 'RW 36028797014771712 ZERO', + ], + }, + }, + ]; + + + def _findFile(self, sRegExp, asTestBuildDirs): + """ + Returns a filepath based on the given regex and paths to look into + or None if no matching file is found. + """ + oRegExp = re.compile(sRegExp); + for sTestBuildDir in asTestBuildDirs: + try: + #return most recent file if there are several ones matching the pattern + asFiles = [s for s in os.listdir(sTestBuildDir) + if os.path.isfile(os.path.join(sTestBuildDir, s))]; + asFiles = (s for s in asFiles + if oRegExp.match(os.path.basename(s)) + and os.path.exists(sTestBuildDir + '/' + s)); + asFiles = sorted(asFiles, reverse = True, + key = lambda s, sTstBuildDir = sTestBuildDir: os.path.getmtime(os.path.join(sTstBuildDir, s))); + if asFiles: + return sTestBuildDir + '/' + asFiles[0]; + except: + pass; + reporter.error('Failed to find a file matching "%s" in %s.' % (sRegExp, ','.join(asTestBuildDirs))); + return None; + + def _waitAdditionsIsRunning(self, oGuest, fWaitTrayControl): + """ + Check is the additions running + """ + cAttempt = 0; + fRc = False; + while cAttempt < 30: + fRc = oGuest.additionsRunLevel in [vboxcon.AdditionsRunLevelType_Userland, + vboxcon.AdditionsRunLevelType_Desktop]; + if fRc: + eServiceStatus, _ = oGuest.getFacilityStatus(vboxcon.AdditionsFacilityType_VBoxService); + fRc = eServiceStatus == vboxcon.AdditionsFacilityStatus_Active; + if fRc and not fWaitTrayControl: + break; + if fRc: + eServiceStatus, _ = oGuest.getFacilityStatus(vboxcon.AdditionsFacilityType_VBoxTrayClient); + fRc = eServiceStatus == vboxcon.AdditionsFacilityStatus_Active; + if fRc: + break; + self.oTstDrv.sleep(10); + cAttempt += 1; + return fRc; + + def createSession(self, oSession, sName, sUser, sPassword, cMsTimeout = 10 * 1000, fIsError = True): + """ + Creates (opens) a guest session. + Returns (True, IGuestSession) on success or (False, None) on failure. + """ + oGuest = oSession.o.console.guest; + if sName is None: + sName = "<untitled>"; + reporter.log('Creating session "%s" ...' % (sName,)); + try: + oGuestSession = oGuest.createSession(sUser, sPassword, '', sName); + except: + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErrXcpt(fIsError, 'Creating a guest session "%s" failed; sUser="%s", pw="%s"' + % (sName, sUser, sPassword)); + return (False, None); + reporter.log('Waiting for session "%s" to start within %dms...' % (sName, cMsTimeout)); + aeWaitFor = [ vboxcon.GuestSessionWaitForFlag_Start, ]; + try: + waitResult = oGuestSession.waitForArray(aeWaitFor, cMsTimeout); + # + # Be nice to Guest Additions < 4.3: They don't support session handling and + # therefore return WaitFlagNotSupported. + # + if waitResult not in (vboxcon.GuestSessionWaitResult_Start, vboxcon.GuestSessionWaitResult_WaitFlagNotSupported): + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErr(fIsError, 'Session did not start successfully, returned wait result: %d' % (waitResult,)); + return (False, None); + reporter.log('Session "%s" successfully started' % (sName,)); + except: + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErrXcpt(fIsError, 'Waiting for guest session "%s" (usr=%s;pw=%s) to start failed:' + % (sName, sUser, sPassword,)); + return (False, None); + return (True, oGuestSession); + + def closeSession(self, oGuestSession, fIsError = True): + """ + Closes the guest session. + """ + if oGuestSession is not None: + try: + sName = oGuestSession.name; + except: + return reporter.errorXcpt(); + reporter.log('Closing session "%s" ...' % (sName,)); + try: + oGuestSession.close(); + oGuestSession = None; + except: + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErrXcpt(fIsError, 'Closing guest session "%s" failed:' % (sName,)); + return False; + return True; + + def guestProcessExecute(self, oGuestSession, sTestName, cMsTimeout, sExecName, asArgs = (), + fGetStdOut = True, fIsError = True): + """ + Helper function to execute a program on a guest, specified in the current test. + Returns (True, ProcessStatus, ProcessExitCode, ProcessStdOutBuffer) on success or (False, 0, 0, None) on failure. + """ + _ = sTestName; + fRc = True; # Be optimistic. + reporter.log2('Using session user=%s, name=%s, timeout=%d' + % (oGuestSession.user, oGuestSession.name, oGuestSession.timeout,)); + # + # Start the process: + # + reporter.log2('Executing sCmd=%s, timeoutMS=%d, asArgs=%s' + % (sExecName, cMsTimeout, asArgs, )); + fTaskFlags = []; + if fGetStdOut: + fTaskFlags = [vboxcon.ProcessCreateFlag_WaitForStdOut, + vboxcon.ProcessCreateFlag_WaitForStdErr]; + try: + oProcess = oGuestSession.processCreate(sExecName, + asArgs if self.oTstDrv.fpApiVer >= 5.0 else asArgs[1:], + [], fTaskFlags, cMsTimeout); + except: + reporter.maybeErrXcpt(fIsError, 'asArgs=%s' % (asArgs,)); + return (False, 0, 0, None); + if oProcess is None: + return (reporter.error('oProcess is None! (%s)' % (asArgs,)), 0, 0, None); + #time.sleep(5); # try this if you want to see races here. + # Wait for the process to start properly: + reporter.log2('Process start requested, waiting for start (%dms) ...' % (cMsTimeout,)); + iPid = -1; + aeWaitFor = [ vboxcon.ProcessWaitForFlag_Start, ]; + aBuf = None; + try: + eWaitResult = oProcess.waitForArray(aeWaitFor, cMsTimeout); + except: + reporter.maybeErrXcpt(fIsError, 'waitforArray failed for asArgs=%s' % (asArgs,)); + fRc = False; + else: + try: + eStatus = oProcess.status; + iPid = oProcess.PID; + except: + fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + else: + reporter.log2('Wait result returned: %d, current process status is: %d' % (eWaitResult, eStatus,)); + # + # Wait for the process to run to completion if necessary. + # + # Note! The above eWaitResult return value can be ignored as it will + # (mostly) reflect the process status anyway. + # + if eStatus == vboxcon.ProcessStatus_Started: + # What to wait for: + aeWaitFor = [ vboxcon.ProcessWaitForFlag_Terminate, + vboxcon.ProcessWaitForFlag_StdOut, + vboxcon.ProcessWaitForFlag_StdErr]; + reporter.log2('Process (PID %d) started, waiting for termination (%dms), aeWaitFor=%s ...' + % (iPid, cMsTimeout, aeWaitFor)); + acbFdOut = [0,0,0]; + while True: + try: + eWaitResult = oProcess.waitForArray(aeWaitFor, cMsTimeout); + except KeyboardInterrupt: # Not sure how helpful this is, but whatever. + reporter.error('Process (PID %d) execution interrupted' % (iPid,)); + try: oProcess.close(); + except: pass; + break; + except: + fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + break; + reporter.log2('Wait returned: %d' % (eWaitResult,)); + # Process output: + for eFdResult, iFd, sFdNm in [ (vboxcon.ProcessWaitResult_StdOut, 1, 'stdout'), + (vboxcon.ProcessWaitResult_StdErr, 2, 'stderr'), ]: + if eWaitResult in (eFdResult, vboxcon.ProcessWaitResult_WaitFlagNotSupported): + reporter.log2('Reading %s ...' % (sFdNm,)); + try: + abBuf = oProcess.read(iFd, 64 * 1024, cMsTimeout); + except KeyboardInterrupt: # Not sure how helpful this is, but whatever. + reporter.error('Process (PID %d) execution interrupted' % (iPid,)); + try: oProcess.close(); + except: pass; + except: + pass; ## @todo test for timeouts and fail on anything else! + else: + if abBuf: + reporter.log2('Process (PID %d) got %d bytes of %s data' % (iPid, len(abBuf), sFdNm,)); + acbFdOut[iFd] += len(abBuf); + ## @todo Figure out how to uniform + append! + sBuf = ''; + if sys.version_info >= (2, 7) and isinstance(abBuf, memoryview): + abBuf = abBuf.tobytes(); + sBuf = abBuf.decode("utf-8"); + else: + sBuf = str(abBuf); + if aBuf: + aBuf += sBuf; + else: + aBuf = sBuf; + ## Process input (todo): + #if eWaitResult in (vboxcon.ProcessWaitResult_StdIn, vboxcon.ProcessWaitResult_WaitFlagNotSupported): + # reporter.log2('Process (PID %d) needs stdin data' % (iPid,)); + # Termination or error? + if eWaitResult in (vboxcon.ProcessWaitResult_Terminate, + vboxcon.ProcessWaitResult_Error, + vboxcon.ProcessWaitResult_Timeout,): + try: eStatus = oProcess.status; + except: fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + reporter.log2('Process (PID %d) reported terminate/error/timeout: %d, status: %d' + % (iPid, eWaitResult, eStatus,)); + break; + # End of the wait loop. + _, cbStdOut, cbStdErr = acbFdOut; + try: eStatus = oProcess.status; + except: fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + reporter.log2('Final process status (PID %d) is: %d' % (iPid, eStatus)); + reporter.log2('Process (PID %d) %d stdout, %d stderr' % (iPid, cbStdOut, cbStdErr)); + # + # Get the final status and exit code of the process. + # + try: + uExitStatus = oProcess.status; + iExitCode = oProcess.exitCode; + except: + fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + reporter.log2('Process (PID %d) has exit code: %d; status: %d ' % (iPid, iExitCode, uExitStatus)); + return (fRc, uExitStatus, iExitCode, aBuf); + + def uploadString(self, oGuestSession, sSrcString, sDst): + """ + Upload the string into guest. + """ + fRc = True; + try: + oFile = oGuestSession.fileOpenEx(sDst, vboxcon.FileAccessMode_ReadWrite, vboxcon.FileOpenAction_CreateOrReplace, + vboxcon.FileSharingMode_All, 0, []); + except: + fRc = reporter.errorXcpt('Upload string failed. Could not create and open the file %s' % sDst); + else: + try: + oFile.write(bytearray(sSrcString), 60*1000); + except: + fRc = reporter.errorXcpt('Upload string failed. Could not write the string into the file %s' % sDst); + try: + oFile.close(); + except: + fRc = reporter.errorXcpt('Upload string failed. Could not close the file %s' % sDst); + return fRc; + + def uploadFile(self, oGuestSession, sSrc, sDst): + """ + Upload the string into guest. + """ + fRc = True; + try: + if self.oTstDrv.fpApiVer >= 5.0: + oCurProgress = oGuestSession.fileCopyToGuest(sSrc, sDst, [0]); + else: + oCurProgress = oGuestSession.copyTo(sSrc, sDst, [0]); + except: + reporter.maybeErrXcpt(True, 'Upload file exception for sSrc="%s":' + % (self.sGuestAdditionsIso,)); + fRc = False; + else: + if oCurProgress is not None: + oWrapperProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, self.oTstDrv, "uploadFile"); + oWrapperProgress.wait(); + if not oWrapperProgress.isSuccess(): + oWrapperProgress.logResult(fIgnoreErrors = False); + fRc = False; + else: + fRc = reporter.error('No progress object returned'); + return fRc; + + def downloadFile(self, oGuestSession, sSrc, sDst, fIgnoreErrors = False): + """ + Get a file (sSrc) from the guest storing it on the host (sDst). + """ + fRc = True; + try: + if self.oTstDrv.fpApiVer >= 5.0: + oCurProgress = oGuestSession.fileCopyFromGuest(sSrc, sDst, [0]); + else: + oCurProgress = oGuestSession.copyFrom(sSrc, sDst, [0]); + except: + if not fIgnoreErrors: + reporter.errorXcpt('Download file exception for sSrc="%s":' % (sSrc,)); + else: + reporter.log('warning: Download file exception for sSrc="%s":' % (sSrc,)); + fRc = False; + else: + if oCurProgress is not None: + oWrapperProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, + self.oTstDrv, "downloadFile"); + oWrapperProgress.wait(); + if not oWrapperProgress.isSuccess(): + oWrapperProgress.logResult(fIgnoreErrors); + fRc = False; + else: + if not fIgnoreErrors: + reporter.error('No progress object returned'); + else: + reporter.log('warning: No progress object returned'); + fRc = False; + return fRc; + + def downloadFiles(self, oGuestSession, asFiles, fIgnoreErrors = False): + """ + Convenience function to get files from the guest and stores it + into the scratch directory for later (manual) review. + Returns True on success. + Returns False on failure, logged. + """ + fRc = True; + for sGstFile in asFiles: + ## @todo r=bird: You need to use the guest specific path functions here. + ## Best would be to add basenameEx to common/pathutils.py. See how joinEx + ## is used by BaseTestVm::pathJoin and such. + sTmpFile = os.path.join(self.oTstDrv.sScratchPath, 'tmp-' + os.path.basename(sGstFile)); + reporter.log2('Downloading file "%s" to "%s" ...' % (sGstFile, sTmpFile)); + # First try to remove (unlink) an existing temporary file, as we don't truncate the file. + try: os.unlink(sTmpFile); + except: pass; + ## @todo Check for already existing files on the host and create a new + # name for the current file to download. + fRc = self.downloadFile(oGuestSession, sGstFile, sTmpFile, fIgnoreErrors); + if fRc: + reporter.addLogFile(sTmpFile, 'misc/other', 'guest - ' + sGstFile); + else: + if fIgnoreErrors is not True: + reporter.error('error downloading file "%s" to "%s"' % (sGstFile, sTmpFile)); + return fRc; + reporter.log('warning: file "%s" was not downloaded, ignoring.' % (sGstFile,)); + return True; + + def _checkVmIsReady(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Start a guest process', + 30 * 1000, '/sbin/ifconfig', + ['ifconfig',], + False, False); + return fRc; + + def waitVmIsReady(self, oSession, fWaitTrayControl): + """ + Waits the VM is ready after start or reboot. + Returns result (true or false) and guest session obtained + """ + _ = fWaitTrayControl; + # Give the VM a time to reboot + self.oTstDrv.sleep(30); + # Waiting the VM is ready. + # To do it, one will try to open the guest session and start the guest process in loop + if not self._waitAdditionsIsRunning(oSession.o.console.guest, False): + return (False, None); + cAttempt = 0; + oGuestSession = None; + fRc = False; + while cAttempt < 30: + fRc, oGuestSession = self.createSession(oSession, 'Session for user: vbox', + 'vbox', 'password', 10 * 1000, False); + if fRc: + fRc = self._checkVmIsReady(oGuestSession); + if fRc: + break; + self.closeSession(oGuestSession, False); + self.oTstDrv.sleep(10); + cAttempt += 1; + return (fRc, oGuestSession); + + def _rebootVM(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Reboot the VM', + 30 * 1000, '/usr/bin/sudo', + ['sudo', 'reboot'], + False, True); + if not fRc: + reporter.error('Calling the reboot utility failed'); + return fRc; + + def rebootVMAndCheckReady(self, oSession, oGuestSession): + """ + Reboot the VM and wait the VM is ready. + Returns result and guest session obtained after reboot + """ + reporter.testStart('Reboot VM and wait for readiness'); + fRc = self._rebootVM(oGuestSession); + fRc = self.closeSession(oGuestSession, True) and fRc and True; # pychecker hack. + if fRc: + (fRc, oGuestSession) = self.waitVmIsReady(oSession, False); + if not fRc: + reporter.error('VM is not ready after reboot'); + reporter.testDone(); + return (fRc, oGuestSession); + + def _powerDownVM(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Power down the VM', + 30 * 1000, '/usr/bin/sudo', + ['sudo', 'poweroff'], + False, True); + if not fRc: + reporter.error('Calling the poweroff utility failed'); + return fRc; + + def powerDownVM(self, oGuestSession): + """ + Power down the VM by calling guest process without wating + the VM is really powered off. Also, closes the guest session. + It helps the terminateBySession to stop the VM without aborting. + """ + if oGuestSession is None: + return False; + reporter.testStart('Power down the VM'); + fRc = self._powerDownVM(oGuestSession); + fRc = self.closeSession(oGuestSession, True) and fRc and True; # pychecker hack. + if not fRc: + reporter.error('Power down the VM failed'); + reporter.testDone(); + return fRc; + + def installAdditions(self, oSession, oGuestSession, oVM): + """ + Installs the Windows guest additions using the test execution service. + """ + _ = oSession; + _ = oGuestSession; + _ = oVM; + reporter.error('Not implemented'); + return False; + + def installVirtualBox(self, oGuestSession): + """ + Install VirtualBox in the guest. + """ + _ = oGuestSession; + reporter.error('Not implemented'); + return False; + + def getResourceSet(self): + asRet = []; + if not os.path.isabs(self.sHdd): + asRet.append(self.sHdd); + return asRet; + + def _createVmDoIt(self, oTestDrv, eNic0AttachType, sDvdImage): + """ + Creates the VM. + Returns Wrapped VM object on success, None on failure. + """ + _ = eNic0AttachType; + _ = sDvdImage; + return oTestDrv.createTestVM(self.sVmName, self.iGroup, self.sHdd, sKind = self.sKind, \ + fIoApic = True, eNic0AttachType = vboxcon.NetworkAttachmentType_NAT, \ + eNic0Type = self.eNic0Type, cMbRam = self.cMbRam, \ + sHddControllerType = "SATA Controller", fPae = self.fPae, \ + cCpus = self.cCpus, sDvdImage = self.sGuestAdditionsIso); + + def _createVmPost(self, oTestDrv, oVM, eNic0AttachType, sDvdImage): + _ = eNic0AttachType; + _ = sDvdImage; + fRc = True; + oSession = oTestDrv.openSession(oVM); + if oSession is not None: + fRc = fRc and oSession.enableVirtEx(True); + # nested paging doesn't need for the test + #fRc = fRc and oSession.enableNestedPaging(True); + #fRc = fRc and oSession.enableNestedHwVirt(True); + # disable 3D until the error is fixed. + fRc = fRc and oSession.setAccelerate3DEnabled(False); + fRc = fRc and oSession.setVRamSize(256); + fRc = fRc and oSession.setVideoControllerType(vboxcon.GraphicsControllerType_VBoxSVGA); + fRc = fRc and oSession.enableUsbOhci(True); + fRc = fRc and oSession.enableUsbHid(True); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + return oVM if fRc else None; + + def getReconfiguredVm(self, oTestDrv, cCpus, sVirtMode, sParavirtMode = None): + # + # Current test uses precofigured VMs. This override disables any changes in the machine. + # + _ = cCpus; + _ = sVirtMode; + _ = sParavirtMode; + oVM = oTestDrv.getVmByName(self.sVmName); + if oVM is None: + return (False, None); + return (True, oVM); + + def reattachHdd(self, oVM, sHdd, asHdds): + """ + Attach required hdd and remove all others from asHdds list. + """ + reporter.testStart("Reattach hdd"); + oSession = self.oTstDrv.openSession(oVM); + fRc = False; + if oSession is not None: + # for simplicity and because we are using VMs having "SATA controller" + # we will add the hdds to only "SATA controller" + iPortNew = 0; + fFound = False; + try: + aoAttachments = self.oTstDrv.oVBox.oVBoxMgr.getArray(oVM, 'mediumAttachments'); + except: + fRc = reporter.errorXcpt(); + else: + for oAtt in aoAttachments: + try: + sCtrl = oAtt.controller + iPort = oAtt.port; + iDev = oAtt.device; + eType = oAtt.type; + except: + fRc = reporter.errorXcpt(); + break; + + fDetached = False; + if eType == vboxcon.DeviceType_HardDisk: + oMedium = oVM.getMedium(sCtrl, iPort, iDev); + if oMedium.location.endswith(sHdd): + fRc = True; + fFound = True; + break; + for sHddVar in asHdds: + if oMedium.location.endswith(sHddVar) \ + or oMedium.parent is not None and oMedium.parent.location.endswith(sHddVar) : + (fRc, oOldHd) = oSession.detachHd(sCtrl, iPort, iDev); + if fRc and oOldHd is not None: + fRc = oSession.saveSettings(); + if oMedium.parent is not None: + fRc = fRc and self.oTstDrv.oVBox.deleteHdByMedium(oOldHd); + else: + fRc = fRc and oOldHd.close(); + fRc = fRc and oSession.saveSettings(); + fDetached = True; + if not fDetached and sCtrl == 'SATA Controller' and iPort + 1 > iPortNew: + iPortNew = iPort + 1; + if not fFound: + fRc = oSession.attachHd(sHdd, 'SATA Controller', iPortNew, 0); + if fRc: + fRc = oSession.saveSettings(); + else: + oSession.discadSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack + else: + reporter.error("Open session for '%s' failed" % self.sVmName); + fRc = False; + reporter.testDone(); + return fRc; + + def _callVBoxManage(self, oGuestSession, sTestName, cMsTimeout, asArgs = (), + fGetStdOut = True, fIsError = True): + return self.guestProcessExecute(oGuestSession, sTestName, + cMsTimeout, '/usr/bin/sudo', + ['/usr/bin/sudo', '/opt/VirtualBox/VBoxManage'] + asArgs, fGetStdOut, fIsError); + + def listHostDrives(self, oGuestSession, sHdd): + """ + Define path of the specified drive using 'VBoxManage list hostdrives'. + """ + reporter.testStart("List host drives"); + sDrive = None; + (fRc, _, _, aBuf) = self._callVBoxManage(oGuestSession, 'List host drives', 60 * 1000, + ['list', 'hostdrives'], True, True); + if not fRc: + reporter.error('List host drives in the VM %s failed' % (self.sVmName, )); + else: + if aBuf is None: + fRc = reporter.error('"List host drives" output is empty for the VM %s' % (self.sVmName, )); + else: + asHddData = self.asHdds[sHdd]; + + try: aBuf = str(aBuf); # pylint: disable=redefined-variable-type + except: pass; + asLines = aBuf.splitlines(); + oRegExp = re.compile(r'^\s*([^:]+)\s*:\s*(.+)\s*$'); + + # pylint: disable=no-init + class ParseState(object): + kiNothing = 0; + kiDrive = 1; + kiPartition = 2; + + iParseState = ParseState.kiNothing; + asKeysNotFound = asHddData['Header'].keys(); + idxPartition = 0; + for sLine in asLines: + if not sLine or sLine.startswith('#') or sLine.startswith("\n"): + continue; + oMatch = oRegExp.match(sLine); + if oMatch is not None: + sKey = oMatch.group(1); + sValue = oMatch.group(2); + if sKey is not None and sKey == 'Drive': + # we found required disk if we found all required disk info and partitions + if sDrive and not asKeysNotFound and idxPartition >= len(asHddData['Partitions']['Partitions']): + break; + sDrive = sValue; + iParseState = ParseState.kiDrive; + asKeysNotFound = asKeysNotFound = asHddData['Header'].keys(); + idxPartition = 0; + continue; + if iParseState == ParseState.kiDrive: + if sLine.strip().startswith('Partitions:'): + iParseState = ParseState.kiPartition; + continue; + if oMatch is None or sKey is None: + continue; + if sKey in asHddData['Header'].keys() and asHddData['Header'][sKey] == sValue: + asKeysNotFound.remove(sKey); + continue; + if iParseState == ParseState.kiPartition: + if idxPartition < len(asHddData['Partitions']['Partitions']): + sPart = asHddData['Partitions']['Partitions'][idxPartition]; + sPart = sPart.replace('$(' + str(idxPartition + 1) + ')', + str(asHddData['Partitions']['PartitionNumbers'][idxPartition])); + if sLine.strip() == sPart: + idxPartition += 1; + continue; + fRc = sDrive and not asKeysNotFound and idxPartition >= len(asHddData['Partitions']['Partitions']); + if fRc: + reporter.log("Path to the drive '%s' in the VM '%s': %s " % (sHdd, self.sVmName, sDrive)); + else: + reporter.error("Path to drive '%s' not found in the VM '%s'" % (sHdd, self.sVmName)); + reporter.testDone(); + return (fRc, sDrive); + + def convertDiskToPartitionPrefix(self, sDisk): + return sDisk; + + def checkVMDKDescriptor(self, asDescriptor, sHdd, sRawDrive, asAction): + """ + Check VMDK descriptor of the disk created + """ + if asDescriptor is None \ + or asDescriptor[0] != '# Disk DescriptorFile' \ + and asDescriptor[0] != '# Disk Descriptor File' \ + and asDescriptor[0] != '#Disk Descriptor File' \ + and asDescriptor[0] != '#Disk DescriptorFile': + return reporter.error("VMDK descriptor has invalid format"); + + # pylint: disable=no-init + class DescriptorParseState(object): + kiHeader = 1; + kiExtent = 2; + kiDatabase = 3; + + asHddData = self.asHdds[sHdd]; + iParseState = DescriptorParseState.kiHeader; + + asHeader = { 'version' : '1', + 'CID' : '*', + 'parentCID' : 'ffffffff', + 'createType' : '$' + }; + + asDatabase = { 'ddb.virtualHWVersion' : '4', + 'ddb.adapterType' : 'ide', + 'ddb.uuid.image' : '*', + 'ddb.uuid.parent' : '00000000-0000-0000-0000-000000000000', + 'ddb.uuid.modification' : '00000000-0000-0000-0000-000000000000', + 'ddb.uuid.parentmodification' : '00000000-0000-0000-0000-000000000000' + }; + + oRegExp = re.compile(r'^\s*([^=]+)\s*=\s*\"*([^\"]+)\"*\s*$'); + iExtentIdx = 0; + + for sLine in asDescriptor: + if not sLine or sLine.startswith('#') or sLine.startswith("\n"): + continue; + + if iParseState == DescriptorParseState.kiHeader: + if sLine.startswith('ddb.'): + return reporter.error("VMDK descriptor has invalid order of sections"); + if sLine.startswith("RW") \ + or sLine.startswith("RDONLY") \ + or sLine.startswith("NOACCESS"): + iParseState = DescriptorParseState.kiExtent; + else: + oMatch = oRegExp.match(sLine); + if oMatch is None: + return reporter.error("VMDK descriptor contains lines in invalid form"); + sKey = oMatch.group(1).strip(); + sValue = oMatch.group(2).strip(); + if sKey not in asHeader: + return reporter.error("VMDK descriptor has invalid format"); + sDictValue = asHeader[sKey]; + if sDictValue == '$': + sDictValue = asAction[sKey]; + if sDictValue not in ('*', sValue): + return reporter.error("VMDK descriptor has value which was not expected"); + continue; + + if iParseState == DescriptorParseState.kiExtent: + if sLine.startswith('ddb.'): + iParseState = DescriptorParseState.kiDatabase; + else: + if not sLine.startswith("RW") \ + and not sLine.startswith("RDONLY") \ + and not sLine.startswith("NOACCESS"): + return reporter.error("VMDK descriptor has invalid order of sections"); + sExtent = asAction['extents'][sHdd][iExtentIdx]; + sExtent = sExtent.replace('$(disk)', sRawDrive); + sExtent = sExtent.replace('$(part)', self.convertDiskToPartitionPrefix(sRawDrive)); + sExtent = re.sub(r'\$\((\d+)\)', + lambda oMatch: str(asHddData['Partitions']['PartitionNumbers'][int(oMatch.group(1)) - 1]), + sExtent); + if sExtent != sLine.strip(): + return reporter.error("VMDK descriptor has invalid order of sections"); + iExtentIdx += 1; + continue; + + if iParseState == DescriptorParseState.kiDatabase: + if not sLine.startswith('ddb.'): + return reporter.error("VMDK descriptor has invalid order of sections"); + oMatch = oRegExp.match(sLine); + if oMatch is None: + return reporter.error("VMDK descriptor contains lines in invalid form"); + sKey = oMatch.group(1).strip(); + sValue = oMatch.group(2).strip(); + if sKey not in asDatabase: + return reporter.error("VMDK descriptor has invalid format"); + sDictValue = asDatabase[sKey]; + if sDictValue not in ('*', sValue): + return reporter.error("VMDK descriptor has value which was not expected"); + continue; + return iParseState == DescriptorParseState.kiDatabase; + + def _setPermissionsToVmdkFiles(self, oGuestSession): + """ + Sets 0644 permissions to all files in the self.sVMDKPath allowing reading them by 'vbox' user. + """ + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, + 'Allowing reading of the VMDK content by vbox user', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/chmod', '644', + self.sVMDKPath + '/vmdktest.vmdk', self.sVMDKPath + '/vmdktest-pt.vmdk'], + False, True); + return fRc; + + def createDrives(self, oGuestSession, sHdd, sRawDrive): + """ + Creates VMDK Raw file and check correctness + """ + reporter.testStart("Create VMDK disks"); + asHddData = self.asHdds[sHdd]; + fRc = True; + try: oGuestSession.directoryCreate(self.sVMDKPath, 0o777, (vboxcon.DirectoryCreateFlag_Parents,)); + except: fRc = reporter.errorXcpt('Create directory for VMDK files failed in the VM %s' % (self.sVmName)); + if fRc: + sBootSectorGuestPath = self.sVMDKPath + self.sPathDelimiter + 't-bootsector.bin'; + try: fExists = oGuestSession.fileExists(sBootSectorGuestPath, False); + except: fExists = False; + if not fExists: + sBootSectorPath = self.oTstDrv.getFullResourceName(self.sBootSector); + fRc = self.uploadFile(oGuestSession, sBootSectorPath, sBootSectorGuestPath); + + for action in self.asActions: + reporter.testStart("Create VMDK disk: %s" % action["action"]); + asOptions = action['options']; + asOptions = [option.replace('$(bootsector)', sBootSectorGuestPath) for option in asOptions]; + asOptions = [re.sub(r'\$\((\d+)\)', + lambda oMatch: str(asHddData['Partitions']['PartitionNumbers'][int(oMatch.group(1)) - 1]), + option) + for option in asOptions]; + (fRc, _, _, _) = self._callVBoxManage(oGuestSession, 'Create VMDK disk', 60 * 1000, + ['createmedium', '--filename', + self.sVMDKPath + self.sPathDelimiter + 'vmdktest.vmdk', + '--format', 'VMDK', '--variant', 'RawDisk', + '--property', 'RawDrive=%s' % (sRawDrive,) ] + asOptions, + False, True); + if not fRc: + reporter.error('Create VMDK raw drive variant "%s" failed in the VM %s' % (action["action"], self.sVmName)); + else: + fRc = self._setPermissionsToVmdkFiles(oGuestSession); + if not fRc: + reporter.error('Setting permissions to VMDK files failed'); + else: + sSrcFile = self.sVMDKPath + self.sPathDelimiter + 'vmdktest.vmdk'; + sDstFile = os.path.join(self.oTstDrv.sScratchPath, 'guest-vmdktest.vmdk'); + reporter.log2('Downloading file "%s" to "%s" ...' % (sSrcFile, sDstFile)); + # First try to remove (unlink) an existing temporary file, as we don't truncate the file. + try: os.unlink(sDstFile); + except: pass; + fRc = self.downloadFile(oGuestSession, sSrcFile, sDstFile, False); + if not fRc: + reporter.error('Download vmdktest.vmdk from guest to host failed'); + else: + with open(sDstFile) as oFile: # pylint: disable=unspecified-encoding + asDescriptor = [row.strip() for row in oFile]; + if not asDescriptor: + fRc = reporter.error('Reading vmdktest.vmdk from guest filed'); + else: + fRc = self.checkVMDKDescriptor(asDescriptor, sHdd, sRawDrive, action); + if not fRc: + reporter.error('Cheking vmdktest.vmdk from guest filed'); + elif action['data-crc']: + sSrcFile = self.sVMDKPath + self.sPathDelimiter + 'vmdktest-pt.vmdk'; + sDstFile = os.path.join(self.oTstDrv.sScratchPath, 'guest-vmdktest-pt.vmdk'); + reporter.log2('Downloading file "%s" to "%s" ...' % (sSrcFile, sDstFile)); + # First try to remove (unlink) an existing temporary file, as we don't truncate the file. + try: os.unlink(sDstFile); + except: pass; + fRc = self.downloadFile(oGuestSession, sSrcFile, sDstFile, False); + if not fRc: + reporter.error('Download vmdktest-pt.vmdk from guest to host failed'); + else: + uResCrc32 = utils.calcCrc32OfFile(sDstFile); + if uResCrc32 != action['data-crc'][sHdd]: + fRc = reporter.error('vmdktest-pt.vmdk does not match what was expected'); + (fRc1, _, _, _) = self._callVBoxManage(oGuestSession, 'Delete VMDK disk', 60 * 1000, + ['closemedium', + self.sVMDKPath + self.sPathDelimiter + 'vmdktest.vmdk', + '--delete'], + False, True); + if not fRc1: + reporter.error('Delete VMDK raw drive variant "%s" failed in the VM %s' % + (action["action"], self.sVmName)); + fRc = fRc and fRc1; + reporter.testDone(); + if not fRc: + break; + else: + reporter.error('Create %s dir failed in the VM %s' % (self.sVMDKPath, self.sVmName)); + + reporter.testDone(); + return fRc; + + +class tdStorageRawDriveOsLinux(tdStorageRawDriveOs): + """ + Autostart support methods for Linux guests. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None, sBootSector = None): + tdStorageRawDriveOs.__init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type, cMbRam, \ + cCpus, fPae, sGuestAdditionsIso, sBootSector); + self.sVBoxInstaller = '^VirtualBox-.*\\.run$'; + return; + + def installAdditions(self, oSession, oGuestSession, oVM): + """ + Install guest additions in the guest. + """ + reporter.testStart('Install Guest Additions'); + fRc = False; + # Install Kernel headers, which are required for actually installing the Linux Additions. + if oVM.OSTypeId.startswith('Debian') \ + or oVM.OSTypeId.startswith('Ubuntu'): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing Kernel headers', + 5 * 60 *1000, '/usr/bin/apt-get', + ['/usr/bin/apt-get', 'install', '-y', + 'linux-headers-generic'], + False, True); + if not fRc: + reporter.error('Error installing Kernel headers'); + else: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing Guest Additions depdendencies', + 5 * 60 *1000, '/usr/bin/apt-get', + ['/usr/bin/apt-get', 'install', '-y', 'build-essential', + 'perl'], False, True); + if not fRc: + reporter.error('Error installing additional installer dependencies'); + elif oVM.OSTypeId.startswith('OL') \ + or oVM.OSTypeId.startswith('Oracle') \ + or oVM.OSTypeId.startswith('RHEL') \ + or oVM.OSTypeId.startswith('Redhat') \ + or oVM.OSTypeId.startswith('Cent'): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing Kernel headers', + 5 * 60 *1000, '/usr/bin/yum', + ['/usr/bin/yum', '-y', 'install', 'kernel-headers'], + False, True); + if not fRc: + reporter.error('Error installing Kernel headers'); + else: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing Guest Additions depdendencies', + 5 * 60 *1000, '/usr/bin/yum', + ['/usr/bin/yum', '-y', 'install', 'make', 'automake', 'gcc', + 'kernel-devel', 'dkms', 'bzip2', 'perl'], False, True); + if not fRc: + reporter.error('Error installing additional installer dependencies'); + else: + reporter.error('Installing Linux Additions for the "%s" is not supported yet' % oVM.OSTypeId); + fRc = False; + if fRc: + # + # The actual install. + # Also tell the installer to produce the appropriate log files. + # + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing guest additions', + 10 * 60 *1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/sh', + '/media/cdrom/VBoxLinuxAdditions.run'], + False, True); + if fRc: + # Due to the GA updates as separate process the above function returns before + # the actual installation finished. So just wait until the GA installed + fRc = self.closeSession(oGuestSession); + if fRc: + (fRc, oGuestSession) = self.waitVmIsReady(oSession, False); + # Download log files. + # Ignore errors as all files above might not be present for whatever reason. + # + if fRc: + asLogFile = []; + asLogFile.append('/var/log/vboxadd-install.log'); + self.downloadFiles(oGuestSession, asLogFile, fIgnoreErrors = True); + else: + reporter.error('Installing guest additions failed: Error occured during vbox installer execution') + if fRc: + (fRc, oGuestSession) = self.rebootVMAndCheckReady(oSession, oGuestSession); + if not fRc: + reporter.error('Reboot after installing GuestAdditions failed'); + reporter.testDone(); + return (fRc, oGuestSession); + + def installVirtualBox(self, oGuestSession): + """ + Install VirtualBox in the guest. + """ + reporter.testStart('Install Virtualbox into the guest VM'); + sTestBuild = self._findFile(self.sVBoxInstaller, self.asTestBuildDirs); + reporter.log("Virtualbox install file: %s" % os.path.basename(sTestBuild)); + fRc = sTestBuild is not None; + if fRc: + fRc = self.uploadFile(oGuestSession, sTestBuild, + '/tmp/' + os.path.basename(sTestBuild)); + else: + reporter.error("VirtualBox install package is not defined"); + + if not fRc: + reporter.error('Upload the vbox installer into guest VM failed'); + else: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, + 'Allowing execution for the vbox installer', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/chmod', '755', + '/tmp/' + os.path.basename(sTestBuild)], + False, True); + if not fRc: + reporter.error('Allowing execution for the vbox installer failed'); + if fRc: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing VBox', + 240 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', + '/tmp/' + os.path.basename(sTestBuild),], + False, True); + if not fRc: + reporter.error('Installing VBox failed'); + reporter.testDone(); + return fRc; + +class tdStorageRawDriveOsDarwin(tdStorageRawDriveOs): + """ + Autostart support methods for Darwin guests. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None, sBootSector = None): + tdStorageRawDriveOs.__init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type, cMbRam, \ + cCpus, fPae, sGuestAdditionsIso, sBootSector); + raise base.GenError('Testing the autostart functionality for Darwin is not implemented'); + +class tdStorageRawDriveOsSolaris(tdStorageRawDriveOs): + """ + Autostart support methods for Solaris guests. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None, sBootSector = None): + tdStorageRawDriveOs.__init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type, cMbRam, \ + cCpus, fPae, sGuestAdditionsIso, sBootSector); + raise base.GenError('Testing the autostart functionality for Solaris is not implemented'); + +class tdStorageRawDriveOsWin(tdStorageRawDriveOs): + """ + Autostart support methods for Windows guests. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None, sBootSector = None): + tdStorageRawDriveOs.__init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type, cMbRam, \ + cCpus, fPae, sGuestAdditionsIso, sBootSector); + self.sVBoxInstaller = r'^VirtualBox-.*\.(exe|msi)$'; + self.sVMDKPath=r'C:\Temp\vmdk'; + self.sPathDelimiter = '\\'; + self.asHdds['6.1/storage/t-mbr.vdi']['Header']['Model'] = '"VBOX HARDDISK"'; + self.asHdds['6.1/storage/t-gpt.vdi']['Header']['Model'] = '"VBOX HARDDISK"'; + self.asHdds['6.1/storage/t-mbr.vdi']['Partitions']['PartitionNumbers'] = [1, 2, 3, 4, 5, 6, 7, 8]; + return; + + def _checkVmIsReady(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Start a guest process', + 30 * 1000, 'C:\\Windows\\System32\\ipconfig.exe', + ['C:\\Windows\\System32\\ipconfig.exe',], + False, False); + return fRc; + + def _rebootVM(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Reboot the VM', + 30 * 1000, 'C:\\Windows\\System32\\shutdown.exe', + ['C:\\Windows\\System32\\shutdown.exe', '/f', + '/r', '/t', '0'], + False, True); + if not fRc: + reporter.error('Calling the shutdown utility failed'); + return fRc; + + def _powerDownVM(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Power down the VM', + 30 * 1000, 'C:\\Windows\\System32\\shutdown.exe', + ['C:\\Windows\\System32\\shutdown.exe', '/f', + '/s', '/t', '0'], + False, True); + if not fRc: + reporter.error('Calling the shutdown utility failed'); + return fRc; + + def _callVBoxManage(self, oGuestSession, sTestName, cMsTimeout, asArgs = (), + fGetStdOut = True, fIsError = True): + return self.guestProcessExecute(oGuestSession, sTestName, + cMsTimeout, r'C:\Program Files\Oracle\VirtualBox\VBoxManage.exe', + [r'C:\Program Files\Oracle\VirtualBox\VBoxManage.exe',] + asArgs, fGetStdOut, fIsError); + + def _setPermissionsToVmdkFiles(self, oGuestSession): + """ + Sets 0644 permissions to all files in the self.sVMDKPath allowing reading them by 'vbox' user. + """ + _ = oGuestSession; + # It is not required in case of Windows + return True; + + def installAdditions(self, oSession, oGuestSession, oVM): + """ + Installs the Windows guest additions using the test execution service. + """ + _ = oVM; + reporter.testStart('Install Guest Additions'); + asLogFiles = []; + fRc = self.closeSession(oGuestSession, True); # pychecker hack. + try: + oCurProgress = oSession.o.console.guest.updateGuestAdditions(self.sGuestAdditionsIso, ['/l',], None); + except: + reporter.maybeErrXcpt(True, 'Updating Guest Additions exception for sSrc="%s":' + % (self.sGuestAdditionsIso,)); + fRc = False; + else: + if oCurProgress is not None: + oWrapperProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, + self.oTstDrv, "installAdditions"); + oWrapperProgress.wait(cMsTimeout = 10 * 60 * 1000); + if not oWrapperProgress.isSuccess(): + oWrapperProgress.logResult(fIgnoreErrors = False); + fRc = False; + else: + fRc = reporter.error('No progress object returned'); + + # Store the result and try download logs anyway. + fGaRc = fRc; + fRc, oGuestSession = self.createSession(oSession, 'Session for user: vbox', + 'vbox', 'password', 10 * 1000, True); + if fRc is True: + (fRc, oGuestSession) = self.rebootVMAndCheckReady(oSession, oGuestSession); + if fRc is True: + # Add the Windows Guest Additions installer files to the files we want to download + # from the guest. + sGuestAddsDir = 'C:/Program Files/Oracle/VirtualBox Guest Additions/'; + asLogFiles.append(sGuestAddsDir + 'install.log'); + # Note: There won't be a install_ui.log because of the silent installation. + asLogFiles.append(sGuestAddsDir + 'install_drivers.log'); + # Download log files. + # Ignore errors as all files above might not be present (or in different locations) + # on different Windows guests. + # + self.downloadFiles(oGuestSession, asLogFiles, fIgnoreErrors = True); + else: + reporter.error('Reboot after installing GuestAdditions failed'); + else: + reporter.error('Create session for user vbox after GA updating failed'); + reporter.testDone(); + return (fRc and fGaRc, oGuestSession); + + def installVirtualBox(self, oGuestSession): + """ + Install VirtualBox in the guest. + """ + reporter.testStart('Install Virtualbox into the guest VM'); + # Used windows image already contains the C:\Temp + sTestBuild = self._findFile(self.sVBoxInstaller, self.asTestBuildDirs); + reporter.log("Virtualbox install file: %s" % os.path.basename(sTestBuild)); + fRc = sTestBuild is not None; + if fRc: + fRc = self.uploadFile(oGuestSession, sTestBuild, + 'C:\\Temp\\' + os.path.basename(sTestBuild)); + else: + reporter.error("VirtualBox install package is not defined"); + + if not fRc: + reporter.error('Upload the installing into guest VM failed'); + else: + if sTestBuild.endswith('.msi'): + sLogFile = 'C:/Temp/VBoxInstallLog.txt'; + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing VBox', + 600 * 1000, 'C:\\Windows\\System32\\msiexec.exe', + ['msiexec', '/quiet', '/norestart', '/i', + 'C:\\Temp\\' + os.path.basename(sTestBuild), + '/lv', sLogFile], + False, True); + if not fRc: + reporter.error('Installing the VBox from msi installer failed'); + else: + sLogFile = 'C:/Temp/Virtualbox/VBoxInstallLog.txt'; + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing VBox', + 600 * 1000, 'C:\\Temp\\' + os.path.basename(sTestBuild), + ['C:\\Temp\\' + os.path.basename(sTestBuild), '-vvvv', + '--silent', '--logging', + '--msiparams', 'REBOOT=ReallySuppress'], + False, True); + if not fRc: + reporter.error('Installing the VBox failed'); + else: + (_, _, _, aBuf) = self.guestProcessExecute(oGuestSession, 'Check installation', + 240 * 1000, 'C:\\Windows\\System32\\cmd.exe', + ['c:\\Windows\\System32\\cmd.exe', '/c', + 'dir', 'C:\\Program Files\\Oracle\\VirtualBox\\*.*'], + True, True); + reporter.log('Content of VirtualBxox folder:'); + reporter.log(str(aBuf)); + asLogFiles = [sLogFile,]; + self.downloadFiles(oGuestSession, asLogFiles, fIgnoreErrors = True); + reporter.testDone(); + return fRc; + + def convertDiskToPartitionPrefix(self, sDisk): + # Convert \\.\PhysicalDriveX into \\.\HarddiskXPartition + oMatch = re.match(r'^\\\\.\\PhysicalDrive(\d+)$', sDisk); + if oMatch is None: + return None; + return r'\\.\Harddisk' + oMatch.group(1) + 'Partition'; + +class tdStorageRawDrive(vbox.TestDriver): # pylint: disable=too-many-instance-attributes + """ + Autostart testcase. + """ + ksOsLinux = 'tst-linux'; + ksOsWindows = 'tst-win'; + ksOsDarwin = 'tst-darwin'; + ksOsSolaris = 'tst-solaris'; + ksOsFreeBSD = 'tst-freebsd'; + ksBootSectorPath = '6.1/storage/t-bootsector.bin'; + kasHdds = ['6.1/storage/t-gpt.vdi', '6.1/storage/t-mbr.vdi']; + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.asSkipVMs = []; + ## @todo r=bird: The --test-build-dirs option as primary way to get the installation files to test + ## is not an acceptable test practice as we don't know wtf you're testing. See defect for more. + self.asTestBuildDirs = [os.path.join(self.sScratchPath, 'bin'),]; + self.sGuestAdditionsIso = None; #'D:/AlexD/TestBox/TestAdditionalFiles/VBoxGuestAdditions_6.1.2.iso'; + oSet = vboxtestvms.TestVmSet(self.oTestVmManager, acCpus = [2], asVirtModes = ['hwvirt-np',], fIgnoreSkippedVm = True); + # pylint: disable=line-too-long + self.asTestVmClasses = { + 'win' : None, #tdStorageRawDriveOsWin(oSet, self, self.ksOsWindows, 'Windows7_64', \ + #'6.0/windows7piglit/windows7piglit.vdi', eNic0Type = None, cMbRam = 2048, \ + #cCpus = 2, fPae = True, sGuestAdditionsIso = self.getGuestAdditionsIso(), + #sBootSector = self.ksBootSectorPath), + 'linux' : tdStorageRawDriveOsLinux(oSet, self, self.ksOsLinux, 'Ubuntu_64', \ + '6.0/ub1804piglit/ub1804piglit.vdi', eNic0Type = None, \ + cMbRam = 2048, cCpus = 2, fPae = None, sGuestAdditionsIso = self.getGuestAdditionsIso(), + sBootSector = self.ksBootSectorPath), + 'solaris' : None, #'tdAutostartOsSolaris', + 'darwin' : None #'tdAutostartOsDarwin' + }; + oSet.aoTestVms.extend([oTestVm for oTestVm in self.asTestVmClasses.values() if oTestVm is not None]); + sOs = self.getBuildOs(); + if sOs in self.asTestVmClasses: + for oTestVM in oSet.aoTestVms: + if oTestVM is not None: + oTestVM.fSkip = oTestVM != self.asTestVmClasses[sOs]; + # pylint: enable=line-too-long + self.oTestVmSet = oSet; + + # + # Overridden methods. + # + + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdAutostart Options:'); + reporter.log(' --test-build-dirs <path1[,path2[,...]]>'); + reporter.log(' The list of directories with VirtualBox distros. Overrides default path.'); + reporter.log(' Default path is $TESTBOX_SCRATCH_PATH/bin.'); + reporter.log(' --vbox-<os>-build <path>'); + reporter.log(' The path to vbox build for the specified OS.'); + reporter.log(' The OS can be one of "win", "linux", "solaris" and "darwin".'); + reporter.log(' This option alse enables corresponding VM for testing.'); + reporter.log(' (Default behaviour is testing only VM having host-like OS.)'); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--test-build-dirs': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--test-build-dirs" takes a path argument'); + self.asTestBuildDirs = asArgs[iArg].split(','); + for oTestVm in self.oTestVmSet.aoTestVms: + oTestVm.asTestBuildDirs = self.asTestBuildDirs; + elif asArgs[iArg] in [ '--vbox-%s-build' % sKey for sKey in self.asTestVmClasses]: + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "%s" take a path argument' % (asArgs[iArg - 1],)); + oMatch = re.match("--vbox-([^-]+)-build", asArgs[iArg - 1]); + if oMatch is not None: + sOs = oMatch.group(1); + oTestVm = self.asTestVmClasses.get(sOs); + if oTestVm is not None: + oTestVm.sTestBuild = asArgs[iArg]; + oTestVm.fSkip = False; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def getResourceSet(self): + asRsrcs = self.kasHdds[:]; + asRsrcs.extend([self.ksBootSectorPath,]); + asRsrcs.extend(vbox.TestDriver.getResourceSet(self)); + return asRsrcs; + + def actionConfig(self): + if not self.importVBoxApi(): # So we can use the constant below. + return False; + return self.oTestVmSet.actionConfig(self); + + def actionExecute(self): + """ + Execute the testcase. + """ + return self.oTestVmSet.actionExecute(self, self.testAutostartOneVfg) + + # + # Test execution helpers. + # + def testAutostartOneVfg(self, oVM, oTestVm): + fRc = True; + self.logVmInfo(oVM); + + for sHdd in self.kasHdds: + reporter.testStart('%s with %s disk' % ( oTestVm.sVmName, sHdd)) + fRc = oTestVm.reattachHdd(oVM, sHdd, self.kasHdds); + if fRc: + oSession = self.startVmByName(oTestVm.sVmName); + if oSession is not None: + (fRc, oGuestSession) = oTestVm.waitVmIsReady(oSession, True); + if fRc: + if fRc: + (fRc, oGuestSession) = oTestVm.installAdditions(oSession, oGuestSession, oVM); + if fRc: + fRc = oTestVm.installVirtualBox(oGuestSession); + if fRc: + (fRc, sRawDrive) = oTestVm.listHostDrives(oGuestSession, sHdd); + if fRc: + fRc = oTestVm.createDrives(oGuestSession, sHdd, sRawDrive); + if not fRc: + reporter.error('Create VMDK raw drives failed'); + else: + reporter.error('List host drives failed'); + else: + reporter.error('Installing VirtualBox in the guest failed'); + else: + reporter.error('Creating Guest Additions failed'); + else: + reporter.error('Waiting for start VM failed'); + if oGuestSession is not None: + try: oTestVm.powerDownVM(oGuestSession); + except: pass; + try: self.terminateVmBySession(oSession); + except: pass; + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + else: + reporter.error('Attaching %s to %s failed' % (sHdd, oTestVm.sVmName)); + reporter.testDone(); + return fRc; + +if __name__ == '__main__': + sys.exit(tdStorageRawDrive().main(sys.argv)); diff --git a/src/VBox/ValidationKit/tests/storage/tdStorageSnapshotMerging1.py b/src/VBox/ValidationKit/tests/storage/tdStorageSnapshotMerging1.py new file mode 100755 index 00000000..aef13274 --- /dev/null +++ b/src/VBox/ValidationKit/tests/storage/tdStorageSnapshotMerging1.py @@ -0,0 +1,414 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdStorageSnapshotMerging1.py $ + +""" +VirtualBox Validation Kit - Storage snapshotting and merging testcase. +""" + +__copyright__ = \ +""" +Copyright (C) 2013-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os; +import sys; + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from common import utils; +from testdriver import reporter; +from testdriver import base; +from testdriver import vbox; +from testdriver import vboxcon; +from testdriver import vboxwrappers; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + + +class tdStorageSnapshot(vbox.TestDriver): # pylint: disable=too-many-instance-attributes + """ + Storage benchmark. + """ + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.oGuestToGuestVM = None; + self.oGuestToGuestSess = None; + self.oGuestToGuestTxs = None; + self.asStorageCtrlsDef = ['AHCI']; + self.asStorageCtrls = self.asStorageCtrlsDef; + #self.asDiskFormatsDef = ['VDI', 'VMDK', 'VHD', 'QED', 'Parallels', 'QCOW', 'iSCSI']; + self.asDiskFormatsDef = ['VDI', 'VMDK', 'VHD']; + self.asDiskFormats = self.asDiskFormatsDef; + self.sRndData = os.urandom(100*1024*1024); + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdStorageSnapshot1 Options:'); + reporter.log(' --storage-ctrls <type1[:type2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asStorageCtrls))); + reporter.log(' --disk-formats <type1[:type2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asDiskFormats))); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--storage-ctrls': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--storage-ctrls" takes a colon separated list of Storage controller types'); + self.asStorageCtrls = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--disk-formats': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--disk-formats" takes a colon separated list of disk formats'); + self.asDiskFormats = asArgs[iArg].split(':'); + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def getResourceSet(self): + # Construct the resource list the first time it's queried. + if self.asRsrcs is None: + self.asRsrcs = ['5.3/storage/mergeMedium/t-orig.vdi', + '5.3/storage/mergeMedium/t-fixed.vdi', + '5.3/storage/mergeMedium/t-resized.vdi']; + return self.asRsrcs; + + def actionExecute(self): + """ + Execute the testcase. + """ + fRc = self.test1(); + return fRc; + + def resizeMedium(self, oMedium, cbNewSize): + if oMedium.deviceType is not vboxcon.DeviceType_HardDisk: + return False; + + if oMedium.type is not vboxcon.MediumType_Normal: + return False; + + #currently only VDI can be resizable. Medium variant is not checked, because testcase creates disks itself + oMediumFormat = oMedium.mediumFormat; + if oMediumFormat.id != 'VDI': + return False; + + cbCurrSize = oMedium.logicalSize; + # currently reduce is not supported + if cbNewSize < cbCurrSize: + return False; + + try: + oProgressCom = oMedium.resize(cbNewSize); + except: + reporter.logXcpt('IMedium::resize failed on %s' % (oMedium.name)); + return False; + oProgress = vboxwrappers.ProgressWrapper(oProgressCom, self.oVBoxMgr, self.oVBox.oTstDrv, + 'Resize medium %s' % (oMedium.name)); + oProgress.wait(cMsTimeout = 15*60*1000); # 15 min + oProgress.logResult(); + return True; + + def getMedium(self, oVM, sController): + oMediumAttachments = oVM.getMediumAttachmentsOfController(sController); + + for oAttachment in oMediumAttachments: + oMedium = oAttachment.medium; + if oMedium.deviceType is not vboxcon.DeviceType_HardDisk: + continue; + if oMedium.type is not vboxcon.MediumType_Normal: + continue; + return oMedium; + + return None; + + def getSnapshotMedium(self, oSnapshot, sController): + oVM = oSnapshot.machine; + oMedium = self.getMedium(oVM, sController); + + aoMediumChildren = self.oVBoxMgr.getArray(oMedium, 'children') + if aoMediumChildren is None or not aoMediumChildren: + return None; + + for oChildMedium in aoMediumChildren: + for uSnapshotId in oChildMedium.getSnapshotIds(oVM.id): + if uSnapshotId == oVM.id: + return oChildMedium; + + return None; + + def openMedium(self, sHd, fImmutable = False): + """ + Opens medium in readonly mode. + Returns Medium object on success and None on failure. Error information is logged. + """ + sFullName = self.oVBox.oTstDrv.getFullResourceName(sHd); + try: + oHd = self.oVBox.findHardDisk(sFullName); + except: + try: + if self.fpApiVer >= 4.1: + oHd = self.oVBox.openMedium(sFullName, vboxcon.DeviceType_HardDisk, vboxcon.AccessMode_ReadOnly, False); + elif self.fpApiVer >= 4.0: + oHd = self.oVBox.openMedium(sFullName, vboxcon.DeviceType_HardDisk, vboxcon.AccessMode_ReadOnly); + else: + oHd = self.oVBox.openHardDisk(sFullName, vboxcon.AccessMode_ReadOnly, False, "", False, ""); + + except: + reporter.errorXcpt('failed to open hd "%s"' % (sFullName)); + return None; + + try: + if fImmutable: + oHd.type = vboxcon.MediumType_Immutable; + else: + oHd.type = vboxcon.MediumType_Normal; + + except: + if fImmutable: + reporter.errorXcpt('failed to set hd "%s" immutable' % (sHd)); + else: + reporter.errorXcpt('failed to set hd "%s" normal' % (sHd)); + + return None; + + return oHd; + + def cloneMedium(self, oSrcHd, oTgtHd): + """ + Clones medium into target medium. + """ + try: + oProgressCom = oSrcHd.cloneTo(oTgtHd, (vboxcon.MediumVariant_Standard, ), None); + except: + reporter.errorXcpt('failed to clone medium %s to %s' % (oSrcHd.name, oTgtHd.name)); + return False; + oProgress = vboxwrappers.ProgressWrapper(oProgressCom, self.oVBoxMgr, self.oVBox.oTstDrv, + 'clone base disk %s to %s' % (oSrcHd.name, oTgtHd.name)); + oProgress.wait(cMsTimeout = 15*60*1000); # 15 min + oProgress.logResult(); + return True; + + def deleteVM(self, oVM): + try: + oVM.unregister(vboxcon.CleanupMode_DetachAllReturnNone); + except: + reporter.logXcpt(); + + if self.fpApiVer >= 4.0: + try: + if self.fpApiVer >= 4.3: + oProgressCom = oVM.deleteConfig([]); + else: + oProgressCom = oVM.delete(None); + except: + reporter.logXcpt(); + else: + oProgress = vboxwrappers.ProgressWrapper(oProgressCom, self.oVBoxMgr, self.oVBox.oTstDrv, + 'Delete VM %s' % (oVM.name)); + oProgress.wait(cMsTimeout = 15*60*1000); # 15 min + oProgress.logResult(); + else: + try: oVM.deleteSettings(); + except: reporter.logXcpt(); + + return None; + + # + # Test execution helpers. + # + + def test1OneCfg(self, eStorageController, oDskFmt): + """ + Runs the specified VM thru test #1. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + + (asExts, aTypes) = oDskFmt.describeFileExtensions() + for i in range(0, len(asExts)): #pylint: disable=consider-using-enumerate + if aTypes[i] is vboxcon.DeviceType_HardDisk: + sExt = '.' + asExts[i] + break + + if sExt is None: + return False; + + oOrigBaseHd = self.openMedium('5.3/storage/mergeMedium/t-orig.vdi'); + if oOrigBaseHd is None: + return False; + + #currently only VDI can be resizable. Medium variant is not checked, because testcase creates disks itself + fFmtDynamic = oDskFmt.id == 'VDI'; + sOrigWithDiffHd = '5.3/storage/mergeMedium/t-fixed.vdi' + uOrigCrc = long(0x7a417cbb); + + if fFmtDynamic: + sOrigWithDiffHd = '5.3/storage/mergeMedium/t-resized.vdi'; + uOrigCrc = long(0xa8f5daa3); + + oOrigWithDiffHd = self.openMedium(sOrigWithDiffHd); + if oOrigWithDiffHd is None: + return False; + + oVM = self.createTestVM('testvm', 1, None); + if oVM is None: + return False; + + sController = self.controllerTypeToName(eStorageController); + + # Reconfigure the VM + oSession = self.openSession(oVM); + if oSession is None: + return False; + # Attach HD + + fRc = True; + sFile = 't-base' + sExt; + sHddPath = os.path.join(self.oVBox.oTstDrv.sScratchPath, sFile); + oHd = oSession.createBaseHd(sHddPath, sFmt=oDskFmt.id, cb=oOrigBaseHd.logicalSize, + cMsTimeout = 15 * 60 * 1000); # 15 min + #if oSession.createBaseHd can't create disk because it exists, oHd will point to some stub object anyway + fRc = fRc and oHd is not None and (oHd.logicalSize == oOrigBaseHd.logicalSize); + fRc = fRc and self.cloneMedium(oOrigBaseHd, oHd); + + fRc = fRc and oSession.ensureControllerAttached(sController); + fRc = fRc and oSession.setStorageControllerType(eStorageController, sController); + fRc = fRc and oSession.saveSettings(); + fRc = fRc and oSession.attachHd(sHddPath, sController, iPort = 0, fImmutable=False, fForceResource=False) + + if fRc: + oSession.takeSnapshot('Base snapshot'); + oSnapshot = oSession.findSnapshot('Base snapshot'); + + if oSnapshot is not None: + oSnapshotMedium = self.getSnapshotMedium(oSnapshot, sController); + fRc = oSnapshotMedium is not None; + + if fFmtDynamic: + fRc = fRc and self.resizeMedium(oSnapshotMedium, oOrigWithDiffHd.logicalSize); + fRc = fRc and self.cloneMedium(oOrigWithDiffHd, oSnapshotMedium); + fRc = fRc and oSession.deleteSnapshot(oSnapshot.id, cMsTimeout = 120 * 1000); + + if fRc: + # disk for result test by checksum + sResFilePath = os.path.join(self.oVBox.oTstDrv.sScratchPath, 't_res.vmdk'); + sResFilePathRaw = os.path.join(self.oVBox.oTstDrv.sScratchPath, 't_res-flat.vmdk'); + oResHd = oSession.createBaseHd(sResFilePath, sFmt='VMDK', cb=oOrigWithDiffHd.logicalSize, + tMediumVariant = (vboxcon.MediumVariant_Fixed, ), + cMsTimeout = 15 * 60 * 1000); # 15 min + fRc = oResHd is not None; + fRc = fRc and self.cloneMedium(oHd, oResHd); + + uResCrc32 = long(0); + if fRc: + uResCrc32 = long(utils.calcCrc32OfFile(sResFilePathRaw)); + if uResCrc32 == uOrigCrc: + reporter.log('Snapshot merged successfully. Crc32 is correct'); + fRc = True; + else: + reporter.error('Snapshot merging failed. Crc32 is invalid'); + fRc = False; + + self.oVBox.deleteHdByMedium(oResHd); + + if oSession is not None: + if oHd is not None: + oSession.detachHd(sController, iPort = 0, iDevice = 0); + + oSession.saveSettings(fClose = True); + if oHd is not None: + self.oVBox.deleteHdByMedium(oHd); + + self.deleteVM(oVM); + return fRc; + + def test1(self): + """ + Executes test #1 thru the various configurations. + """ + if not self.importVBoxApi(): + return False; + + sVmName = 'testvm'; + reporter.testStart(sVmName); + + aoDskFmts = self.oVBoxMgr.getArray(self.oVBox.systemProperties, 'mediumFormats') + if aoDskFmts is None or not aoDskFmts: + return False; + + fRc = True; + for sStorageCtrl in self.asStorageCtrls: + reporter.testStart(sStorageCtrl); + if sStorageCtrl == 'AHCI': + eStorageCtrl = vboxcon.StorageControllerType_IntelAhci; + elif sStorageCtrl == 'IDE': + eStorageCtrl = vboxcon.StorageControllerType_PIIX4; + elif sStorageCtrl == 'LsiLogicSAS': + eStorageCtrl = vboxcon.StorageControllerType_LsiLogicSas; + elif sStorageCtrl == 'LsiLogic': + eStorageCtrl = vboxcon.StorageControllerType_LsiLogic; + elif sStorageCtrl == 'BusLogic': + eStorageCtrl = vboxcon.StorageControllerType_BusLogic; + else: + eStorageCtrl = None; + + for oDskFmt in aoDskFmts: + if oDskFmt.id in self.asDiskFormats: + reporter.testStart('%s' % (oDskFmt.id)); + fRc = self.test1OneCfg(eStorageCtrl, oDskFmt); + reporter.testDone(); + if not fRc: + break; + + reporter.testDone(); + if not fRc: + break; + + reporter.testDone(); + return fRc; + +if __name__ == '__main__': + sys.exit(tdStorageSnapshot().main(sys.argv)); diff --git a/src/VBox/ValidationKit/tests/storage/tdStorageStress1.py b/src/VBox/ValidationKit/tests/storage/tdStorageStress1.py new file mode 100755 index 00000000..7d9d6c68 --- /dev/null +++ b/src/VBox/ValidationKit/tests/storage/tdStorageStress1.py @@ -0,0 +1,513 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Storage testcase using xfstests. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Id: tdStorageStress1.py $" + + +# Standard Python imports. +import os; +import sys; + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testdriver import reporter; +from testdriver import base; +from testdriver import vbox; +from testdriver import vboxcon; + + +class tdStorageStress(vbox.TestDriver): # pylint: disable=too-many-instance-attributes + """ + Storage testcase. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.oGuestToGuestVM = None; + self.oGuestToGuestSess = None; + self.oGuestToGuestTxs = None; + self.asTestVMsDef = ['tst-debian']; + self.asTestVMs = self.asTestVMsDef; + self.asSkipVMs = []; + self.asVirtModesDef = ['hwvirt', 'hwvirt-np', 'raw',] + self.asVirtModes = self.asVirtModesDef + self.acCpusDef = [1, 2,] + self.acCpus = self.acCpusDef; + self.asStorageCtrlsDef = ['AHCI', 'IDE', 'LsiLogicSAS', 'LsiLogic', 'BusLogic']; + self.asStorageCtrls = self.asStorageCtrlsDef; + self.asDiskFormatsDef = ['VDI', 'VMDK', 'VHD', 'QED', 'Parallels', 'QCOW']; + self.asDiskFormats = self.asDiskFormatsDef; + self.asTestsDef = ['xfstests']; + self.asTests = self.asTestsDef; + self.asGuestFs = ['xfs', 'ext4', 'btrfs']; + self.asGuestFsDef = self.asGuestFs; + self.asIscsiTargetsDef = ['aurora|iqn.2011-03.home.aurora:aurora.storagebench|1']; + self.asIscsiTargets = self.asIscsiTargetsDef; + self.asDirsDef = ['/run/media/alexander/OWCSSD/alexander', \ + '/run/media/alexander/CrucialSSD/alexander', \ + '/run/media/alexander/HardDisk/alexander', \ + '/home/alexander']; + self.asDirs = self.asDirsDef; + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdStorageBenchmark1 Options:'); + reporter.log(' --virt-modes <m1[:m2[:]]'); + reporter.log(' Default: %s' % (':'.join(self.asVirtModesDef))); + reporter.log(' --cpu-counts <c1[:c2[:]]'); + reporter.log(' Default: %s' % (':'.join(str(c) for c in self.acCpusDef))); + reporter.log(' --storage-ctrls <type1[:type2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asStorageCtrls))); + reporter.log(' --disk-formats <type1[:type2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asDiskFormats))); + reporter.log(' --disk-dirs <path1[:path2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asDirs))); + reporter.log(' --iscsi-targets <target1[:target2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asIscsiTargets))); + reporter.log(' --tests <test1[:test2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asTests))); + reporter.log(' --guest-fs <fs1[:fs2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asGuestFs))); + reporter.log(' --test-vms <vm1[:vm2[:...]]>'); + reporter.log(' Test the specified VMs in the given order. Use this to change'); + reporter.log(' the execution order or limit the choice of VMs'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestVMsDef))); + reporter.log(' --skip-vms <vm1[:vm2[:...]]>'); + reporter.log(' Skip the specified VMs when testing.'); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--virt-modes': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--virt-modes" takes a colon separated list of modes'); + self.asVirtModes = asArgs[iArg].split(':'); + for s in self.asVirtModes: + if s not in self.asVirtModesDef: + raise base.InvalidOption('The "--virt-modes" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asVirtModesDef))); + elif asArgs[iArg] == '--cpu-counts': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--cpu-counts" takes a colon separated list of cpu counts'); + self.acCpus = []; + for s in asArgs[iArg].split(':'): + try: c = int(s); + except: raise base.InvalidOption('The "--cpu-counts" value "%s" is not an integer' % (s,)); + if c <= 0: raise base.InvalidOption('The "--cpu-counts" value "%s" is zero or negative' % (s,)); + self.acCpus.append(c); + elif asArgs[iArg] == '--storage-ctrls': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--storage-ctrls" takes a colon separated list of Storage controller types'); + self.asStorageCtrls = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--disk-formats': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--disk-formats" takes a colon separated list of disk formats'); + self.asDiskFormats = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--disk-dirs': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--disk-dirs" takes a colon separated list of directories'); + self.asDirs = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--iscsi-targets': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--iscsi-targets" takes a colon separated list of iscsi targets'); + self.asIscsiTargets = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--tests': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--tests" takes a colon separated list of disk formats'); + self.asTests = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--guest-fs': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--guest-fs" takes a colon separated list of filesystem identifiers'); + self.asGuestFs = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--test-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--test-vms" takes colon separated list'); + self.asTestVMs = asArgs[iArg].split(':'); + for s in self.asTestVMs: + if s not in self.asTestVMsDef: + raise base.InvalidOption('The "--test-vms" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asTestVMsDef))); + elif asArgs[iArg] == '--skip-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--skip-vms" takes colon separated list'); + self.asSkipVMs = asArgs[iArg].split(':'); + for s in self.asSkipVMs: + if s not in self.asTestVMsDef: + reporter.log('warning: The "--test-vms" value "%s" does not specify any of our test VMs.' % (s)); + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def completeOptions(self): + # Remove skipped VMs from the test list. + for sVM in self.asSkipVMs: + try: self.asTestVMs.remove(sVM); + except: pass; + + return vbox.TestDriver.completeOptions(self); + + def getResourceSet(self): + # Construct the resource list the first time it's queried. + if self.asRsrcs is None: + self.asRsrcs = []; + if 'tst-debian' in self.asTestVMs: + self.asRsrcs.append('4.2/storage/debian.vdi'); + + return self.asRsrcs; + + def actionConfig(self): + # Some stupid trickery to guess the location of the iso. ## fixme - testsuite unzip ++ + sVBoxValidationKit_iso = os.path.abspath(os.path.join(os.path.dirname(__file__), + '../../VBoxValidationKitStorIo.iso')); + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = os.path.abspath(os.path.join(os.path.dirname(__file__), + '../../VBoxValidationKitStorIo.iso')); + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/ramdisk/vbox/svn/trunk/validationkit/VBoxValidationKitStorIo.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/ramdisk/vbox/svn/trunk/testsuite/VBoxTestSuiteStorIo.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sCur = os.getcwd(); + for i in range(0, 10): + sVBoxValidationKit_iso = os.path.join(sCur, 'validationkit/VBoxValidationKitStorIo.iso'); + if os.path.isfile(sVBoxValidationKit_iso): + break; + sVBoxValidationKit_iso = os.path.join(sCur, 'testsuite/VBoxTestSuiteStorIo.iso'); + if os.path.isfile(sVBoxValidationKit_iso): + break; + sCur = os.path.abspath(os.path.join(sCur, '..')); + if i is None: pass; # shut up pychecker/pylint. + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/VirtualBox/VBoxValidationKitStorIo.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/VirtualBox/VBoxTestSuiteStorIo.iso'; + + + + # Make sure vboxapi has been imported so we can use the constants. + if not self.importVBoxApi(): + return False; + + # + # Configure the VMs we're going to use. + # + + # Linux VMs + if 'tst-debian' in self.asTestVMs: + oVM = self.createTestVM('tst-debian', 1, '4.2/storage/debian.vdi', sKind = 'Debian_64', fIoApic = True, \ + eNic0AttachType = vboxcon.NetworkAttachmentType_NAT, \ + eNic0Type = vboxcon.NetworkAdapterType_Am79C973, \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + return True; + + def actionExecute(self): + """ + Execute the testcase. + """ + fRc = self.test1(); + return fRc; + + + # + # Test execution helpers. + # + + def test1RunTestProgs(self, oSession, oTxsSession, fRc, sTestName, sGuestFs): + """ + Runs all the test programs on the test machine. + """ + _ = oSession; + + reporter.testStart(sTestName); + + sMkfsCmd = 'mkfs.' + sGuestFs; + + # Prepare test disks, just create filesystem without partition + reporter.testStart('Preparation'); + fRc = fRc and self.txsRunTest(oTxsSession, 'Create FS 1', 60000, \ + '/sbin/' + sMkfsCmd, + (sMkfsCmd, '/dev/sdb')); + + fRc = fRc and self.txsRunTest(oTxsSession, 'Create FS 2', 60000, \ + '/sbin/' + sMkfsCmd, + (sMkfsCmd, '/dev/sdc')); + + # Create test and scratch directory + fRc = fRc and self.txsRunTest(oTxsSession, 'Create /mnt/test', 10000, \ + '/bin/mkdir', + ('mkdir', '/mnt/test')); + + fRc = fRc and self.txsRunTest(oTxsSession, 'Create /mnt/scratch', 10000, \ + '/bin/mkdir', + ('mkdir', '/mnt/scratch')); + + # Mount test and scratch directory. + fRc = fRc and self.txsRunTest(oTxsSession, 'Mount /mnt/test', 10000, \ + '/bin/mount', + ('mount', '/dev/sdb','/mnt/test')); + + fRc = fRc and self.txsRunTest(oTxsSession, 'Mount /mnt/scratch', 10000, \ + '/bin/mount', + ('mount', '/dev/sdc','/mnt/scratch')); + + fRc = fRc and self.txsRunTest(oTxsSession, 'Copying xfstests', 10000, \ + '/bin/cp', + ('cp', '-r','${CDROM}/${OS.ARCH}/xfstests', '/tmp')); + + reporter.testDone(); + + # Run xfstests (this sh + cd crap is required because the cwd for the script must be in the root + # of the xfstests directory...) + reporter.testStart('xfstests'); + if fRc and 'xfstests' in self.asTests: + fRc = self.txsRunTest(oTxsSession, 'xfstests', 3600000, + '/bin/sh', + ('sh', '-c', '(cd /tmp/xfstests && ./check -g auto)'), + ('TEST_DIR=/mnt/test', 'TEST_DEV=/dev/sdb', 'SCRATCH_MNT=/mnt/scratch', 'SCRATCH_DEV=/dev/sdc', + 'FSTYP=' + sGuestFs)); + reporter.testDone(); + else: + reporter.testDone(fSkipped = True); + + reporter.testDone(not fRc); + return fRc; + + # pylint: disable=too-many-arguments + + def test1OneCfg(self, sVmName, eStorageController, sDiskFormat, sDiskPath1, sDiskPath2, \ + sGuestFs, cCpus, fHwVirt, fNestedPaging): + """ + Runs the specified VM thru test #1. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + oVM = self.getVmByName(sVmName); + + # Reconfigure the VM + fRc = True; + oSession = self.openSession(oVM); + if oSession is not None: + # Attach HD + fRc = oSession.ensureControllerAttached(self.controllerTypeToName(eStorageController)); + fRc = fRc and oSession.setStorageControllerType(eStorageController, self.controllerTypeToName(eStorageController)); + + if sDiskFormat == "iSCSI": + listNames = []; + listValues = []; + listValues = sDiskPath1.split('|'); + listNames.append('TargetAddress'); + listNames.append('TargetName'); + listNames.append('LUN'); + + if self.fpApiVer >= 5.0: + oHd = oSession.oVBox.createMedium(sDiskFormat, sDiskPath1, vboxcon.AccessMode_ReadWrite, \ + vboxcon.DeviceType_HardDisk); + else: + oHd = oSession.oVBox.createHardDisk(sDiskFormat, sDiskPath1); + oHd.type = vboxcon.MediumType_Normal; + oHd.setProperties(listNames, listValues); + + # Attach it. + if fRc is True: + try: + if oSession.fpApiVer >= 4.0: + oSession.o.machine.attachDevice(self.controllerTypeToName(eStorageController), + 1, 0, vboxcon.DeviceType_HardDisk, oHd); + else: + oSession.o.machine.attachDevice(self.controllerTypeToName(eStorageController), + 1, 0, vboxcon.DeviceType_HardDisk, oHd.id); + except: + reporter.errorXcpt('attachDevice("%s",%s,%s,HardDisk,"%s") failed on "%s"' \ + % (self.controllerTypeToName(eStorageController), 1, 0, oHd.id, oSession.sName) ); + fRc = False; + else: + reporter.log('attached "%s" to %s' % (sDiskPath1, oSession.sName)); + else: + fRc = fRc and oSession.createAndAttachHd(sDiskPath1, sDiskFormat, self.controllerTypeToName(eStorageController), + cb = 10*1024*1024*1024, iPort = 1, fImmutable = False); + fRc = fRc and oSession.createAndAttachHd(sDiskPath2, sDiskFormat, self.controllerTypeToName(eStorageController), + cb = 10*1024*1024*1024, iPort = 2, fImmutable = False); + fRc = fRc and oSession.enableVirtEx(fHwVirt); + fRc = fRc and oSession.enableNestedPaging(fNestedPaging); + fRc = fRc and oSession.setCpuCount(cCpus); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + + # Start up. + if fRc is True: + self.logVmInfo(oVM); + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(sVmName, fCdWait = False, fNatForwardingForTxs = True); + if oSession is not None: + self.addTask(oTxsSession); + + # Fudge factor - Allow the guest to finish starting up. + self.sleep(5); + + fRc = self.test1RunTestProgs(oSession, oTxsSession, fRc, 'stress testing', sGuestFs); + + # cleanup. + self.removeTask(oTxsSession); + self.terminateVmBySession(oSession) + + # Remove disk + oSession = self.openSession(oVM); + if oSession is not None: + try: + oSession.o.machine.detachDevice(self.controllerTypeToName(eStorageController), 1, 0); + oSession.o.machine.detachDevice(self.controllerTypeToName(eStorageController), 2, 0); + + # Remove storage controller if it is not an IDE controller. + if eStorageController not in (vboxcon.StorageControllerType_PIIX3, vboxcon.StorageControllerType_PIIX4,): + oSession.o.machine.removeStorageController(self.controllerTypeToName(eStorageController)); + + oSession.saveSettings(); + oSession.oVBox.deleteHdByLocation(sDiskPath1); + oSession.oVBox.deleteHdByLocation(sDiskPath2); + oSession.saveSettings(); + oSession.close(); + oSession = None; + except: + reporter.errorXcpt('failed to detach/delete disks %s and %s from storage controller' % \ + (sDiskPath1, sDiskPath2)); + else: + fRc = False; + else: + fRc = False; + return fRc; + + def test1OneVM(self, sVmName): + """ + Runs one VM thru the various configurations. + """ + reporter.testStart(sVmName); + fRc = True; + for sStorageCtrl in self.asStorageCtrls: + reporter.testStart(sStorageCtrl); + + if sStorageCtrl == 'AHCI': + eStorageCtrl = vboxcon.StorageControllerType_IntelAhci; + elif sStorageCtrl == 'IDE': + eStorageCtrl = vboxcon.StorageControllerType_PIIX4; + elif sStorageCtrl == 'LsiLogicSAS': + eStorageCtrl = vboxcon.StorageControllerType_LsiLogicSas; + elif sStorageCtrl == 'LsiLogic': + eStorageCtrl = vboxcon.StorageControllerType_LsiLogic; + elif sStorageCtrl == 'BusLogic': + eStorageCtrl = vboxcon.StorageControllerType_BusLogic; + else: + eStorageCtrl = None; + + for sDiskFormat in self.asDiskFormats: + reporter.testStart('%s' % (sDiskFormat,)); + + asPaths = self.asDirs; + + for sDir in asPaths: + reporter.testStart('%s' % (sDir,)); + + sPathDisk1 = sDir + "/disk1.disk"; + sPathDisk2 = sDir + "/disk2.disk"; + + for sGuestFs in self.asGuestFs: + reporter.testStart('%s' % (sGuestFs,)); + + for cCpus in self.acCpus: + if cCpus == 1: reporter.testStart('1 cpu'); + else: reporter.testStart('%u cpus' % (cCpus,)); + + for sVirtMode in self.asVirtModes: + if sVirtMode == 'raw' and cCpus > 1: + continue; + hsVirtModeDesc = {}; + hsVirtModeDesc['raw'] = 'Raw-mode'; + hsVirtModeDesc['hwvirt'] = 'HwVirt'; + hsVirtModeDesc['hwvirt-np'] = 'NestedPaging'; + reporter.testStart(hsVirtModeDesc[sVirtMode]); + + fHwVirt = sVirtMode != 'raw'; + fNestedPaging = sVirtMode == 'hwvirt-np'; + fRc = self.test1OneCfg(sVmName, eStorageCtrl, sDiskFormat, sPathDisk1, sPathDisk2, \ + sGuestFs, cCpus, fHwVirt, fNestedPaging) and fRc and True; + reporter.testDone(); + reporter.testDone(); + reporter.testDone(); + reporter.testDone(); + reporter.testDone(); + reporter.testDone(); + reporter.testDone(); + return fRc; + + def test1(self): + """ + Executes test #1. + """ + + # Loop thru the test VMs. + for sVM in self.asTestVMs: + # run test on the VM. + if not self.test1OneVM(sVM): + fRc = False; + else: + fRc = True; + + return fRc; + + + +if __name__ == '__main__': + sys.exit(tdStorageStress().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/teleportation/Makefile.kmk b/src/VBox/ValidationKit/tests/teleportation/Makefile.kmk new file mode 100644 index 00000000..49d61d08 --- /dev/null +++ b/src/VBox/ValidationKit/tests/teleportation/Makefile.kmk @@ -0,0 +1,51 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Teleportation. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +INSTALLS += ValidationKitTestsTeleportation +ValidationKitTestsTeleportation_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsTeleportation_INST = $(INST_VALIDATIONKIT)testcases/cpu/ +ValidationKitTestsTeleportation_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdTeleportLocal1.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsTeleportation_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/teleportation/tdTeleportLocal1.py b/src/VBox/ValidationKit/tests/teleportation/tdTeleportLocal1.py new file mode 100755 index 00000000..b734908c --- /dev/null +++ b/src/VBox/ValidationKit/tests/teleportation/tdTeleportLocal1.py @@ -0,0 +1,963 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdTeleportLocal1.py $ + +""" +VirtualBox Validation Kit - Local teleportation testdriver. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os; +import sys; + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testdriver import reporter; +from testdriver import base; +from testdriver import vbox; + + +class tdTeleportLocal1(vbox.TestDriver): + """ + Local Teleportation Test #1. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + + self.asTestsDef = ['test1', 'test2']; + self.asTests = ['test1', 'test2']; + self.asSkipTests = []; + self.asTestVMsDef = ['tst-rhel5', 'tst-win2k3ent', 'tst-sol10']; + self.asTestVMs = self.asTestVMsDef; + self.asSkipVMs = []; + self.asVirtModesDef = ['hwvirt', 'hwvirt-np', 'raw',] + self.asVirtModes = self.asVirtModesDef + self.acCpusDef = [1, 2,] + self.acCpus = self.acCpusDef; + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdTeleportLocal1 Options:'); + reporter.log(' --virt-modes <m1[:m2[:]]'); + reporter.log(' Default: %s' % (':'.join(self.asVirtModesDef))); + reporter.log(' --cpu-counts <c1[:c2[:]]'); + reporter.log(' Default: %s' % (':'.join(str(c) for c in self.acCpusDef))); + reporter.log(' --test-vms <vm1[:vm2[:...]]>'); + reporter.log(' Test the specified VMs in the given order. Use this to change'); + reporter.log(' the execution order or limit the choice of VMs'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestVMsDef))); + reporter.log(' --skip-vms <vm1[:vm2[:...]]>'); + reporter.log(' Skip the specified VMs when testing.'); + reporter.log(' --tests <test1[:test2[:...]]>'); + reporter.log(' Run the specified tests.'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestsDef))); + reporter.log(' --skip-tests <test1[:test2[:...]]>'); + reporter.log(' Skip the specified VMs when testing.'); + reporter.log(' --quick'); + reporter.log(' Shorthand for: --virt-modes hwvirt --cpu-counts 1'); + reporter.log(' --test-vms tst-rhel5:tst-win2k3ent:tst-sol10'); + return rc; + + def parseOption(self, asArgs, iArg): + if asArgs[iArg] == '--virt-modes': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--virt-modes" takes a colon separated list of modes'); + self.asVirtModes = asArgs[iArg].split(':'); + for s in self.asVirtModes: + if s not in self.asVirtModesDef: + raise base.InvalidOption('The "--virt-modes" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asVirtModesDef))); + elif asArgs[iArg] == '--cpu-counts': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--cpu-counts" takes a colon separated list of cpu counts'); + self.acCpus = []; + for s in asArgs[iArg].split(':'): + try: c = int(s); + except: raise base.InvalidOption('The "--cpu-counts" value "%s" is not an integer' % (s,)); + if c <= 0: raise base.InvalidOption('The "--cpu-counts" value "%s" is zero or negative' % (s,)); + self.acCpus.append(c); + elif asArgs[iArg] == '--test-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--test-vms" takes colon separated list'); + if asArgs[iArg]: + self.asTestVMs = asArgs[iArg].split(':'); + for s in self.asTestVMs: + if s not in self.asTestVMsDef: + raise base.InvalidOption('The "--test-vms" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asTestVMsDef))); + else: + self.asTestVMs = []; + elif asArgs[iArg] == '--skip-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--skip-vms" takes colon separated list'); + self.asSkipVMs = asArgs[iArg].split(':'); + for s in self.asSkipVMs: + if s not in self.asTestVMsDef: + reporter.log('warning: The "--skip-vms" value "%s" does not specify any of our test VMs.' % (s)); + elif asArgs[iArg] == '--tests': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--tests" takes colon separated list'); + self.asTests = asArgs[iArg].split(':'); + for s in self.asTests: + if s not in self.asTestsDef: + reporter.log('warning: The "--tests" value "%s" does not specify any of our tests.' % (s)); + elif asArgs[iArg] == '--skip-tests': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--skip-tests" takes colon separated list'); + self.asSkipVMs = asArgs[iArg].split(':'); + for s in self.asSkipTests: + if s not in self.asTestsDef: + reporter.log('warning: The "--skip-tests" value "%s" does not specify any of our tests.' % (s)); + elif asArgs[iArg] == '--quick': + self.asVirtModes = ['hwvirt',]; + self.acCpus = [1,]; + #self.asTestVMs = ['tst-rhel5', 'tst-win2k3ent', 'tst-sol10',]; + self.asTestVMs = ['tst-rhel5', ]; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def completeOptions(self): + # Remove skipped VMs from the test VM list. + for sVM in self.asSkipVMs: + try: self.asTestVMs.remove(sVM); + except: pass; + + # Remove skipped tests from the test list. + for sTest in self.asSkipTests: + try: self.asTests.remove(sTest); + except: pass; + + # If no test2, then no test VMs. + if 'test2' not in self.asTests: + self.asTestVMs = []; + + return vbox.TestDriver.completeOptions(self); + + def getResourceSet(self): + # Construct the resource list the first time it's queried. + if self.asRsrcs is None: + self.asRsrcs = []; + if 'tst-rhel5' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/rhel5.vdi'); + if 'tst-rhel5-64' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/rhel5-64.vdi'); + if 'tst-sles11' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/sles11.vdi'); + if 'tst-sles11-64' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/sles11-64.vdi'); + if 'tst-oel' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/oel.vdi'); + if 'tst-oel-64' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/oel-64.vdi'); + if 'tst-win2k3ent' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/win2k3ent-acpi.vdi'); + if 'tst-win2k3ent-64' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/win2k3ent-64.vdi'); + if 'tst-win2k8' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/win2k8.vdi'); + if 'tst-sol10' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/solaris10.vdi'); + if 'tst-sol11' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/solaris11.vdi'); + return self.asRsrcs; + + def actionConfig(self): + ## @todo actionConfig() and getResourceSet() are working on the same + # set of VMs as tdNetBenchmark1, creating common base class would be + # a good idea. + + # Some stupid trickery to guess the location of the iso. ## fixme - testsuite unzip ++ + sVBoxValidationKit_iso = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../VBoxValidationKit.iso')); + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../VBoxTestSuite.iso')); + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/ramdisk/vbox/svn/trunk/validationkit/VBoxValidationKit.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/ramdisk/vbox/svn/trunk/testsuite/VBoxTestSuite.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sCur = os.getcwd(); + for i in range(0, 10): + sVBoxValidationKit_iso = os.path.join(sCur, 'validationkit/VBoxValidationKit.iso'); + if os.path.isfile(sVBoxValidationKit_iso): + break; + sVBoxValidationKit_iso = os.path.join(sCur, 'testsuite/VBoxTestSuite.iso'); + if os.path.isfile(sVBoxValidationKit_iso): + break; + sCur = os.path.abspath(os.path.join(sCur, '..')); + if i is not None: pass; # shut up pychecker/pylint. + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/home/bird/validationkit/VBoxValidationKit.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/home/bird/testsuite/VBoxTestSuite.iso'; + + # Make sure vboxapi has been imported so we can use the constants. + if not self.importVBoxApi(): + return False; + + # + # Configure the empty VMs we're going to use for the first tests. + # + + if 'test1' in self.asTests: + #oVM = self.createTestVMs('tst-empty-hwvirt', 0, sKind = 'Other', fVirtEx = True); + #if oVM is None: + # return False; + + oVM = self.createTestVMs('tst-empty-raw', 2, sKind = 'Other', fVirtEx = False); + if oVM is None: + return False; + + # + # Configure the VMs we're going to use for the last test. + # + + # Linux VMs + if 'tst-rhel5' in self.asTestVMs: + oVM = self.createTestVMs('tst-rhel5', 1, '3.0/tcp/rhel5.vdi', sKind = 'RedHat', fIoApic = True, \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-rhel5-64' in self.asTestVMs: + oVM = self.createTestVMs('tst-rhel5-64', 1, '3.0/tcp/rhel5-64.vdi', sKind = 'RedHat_64', \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-sles11' in self.asTestVMs: + oVM = self.createTestVMs('tst-sles11', 1, '3.0/tcp/sles11.vdi', sKind = 'OpenSUSE', fIoApic = True, \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-sles11-64' in self.asTestVMs: + oVM = self.createTestVMs('tst-sles11-64', 1, '3.0/tcp/sles11-64.vdi', sKind = 'OpenSUSE_64', \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-oel' in self.asTestVMs: + oVM = self.createTestVMs('tst-oel', 1, '3.0/tcp/oel.vdi', sKind = 'Oracle', fIoApic = True, \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-oel-64' in self.asTestVMs: + oVM = self.createTestVMs('tst-oel-64', 1, '3.0/tcp/oel-64.vdi', sKind = 'Oracle_64', \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + # Windows VMs + if 'tst-win2k3ent' in self.asTestVMs: + oVM = self.createTestVMs('tst-win2k3ent', 1, '3.0/tcp/win2k3ent-acpi.vdi', sKind = 'Windows2003', \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-win2k3ent-64' in self.asTestVMs: + oVM = self.createTestVMs('tst-win2k3ent-64', 1, '3.0/tcp/win2k3ent-64.vdi', sKind = 'Windows2003_64', \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-win2k8' in self.asTestVMs: + oVM = self.createTestVMs('tst-win2k8', 1, '3.0/tcp/win2k8.vdi', sKind = 'Windows2008_64', \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + # Solaris VMs + if 'tst-sol10' in self.asTestVMs: + oVM = self.createTestVMs('tst-sol10', 1, '3.0/tcp/solaris10.vdi', sKind = 'Solaris_64', \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-sol11' in self.asTestVMs: + oVM = self.createTestVMs('tst-sol11', 1, '3.0/tcp/os2009-11.vdi', sKind = 'Solaris_64', \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + return True; + + def actionExecute(self): + """ + Execute the testcase. + """ + fRc = 'test1' not in self.asTests or self.test1(); + if fRc: fRc = 'test2' not in self.asTests or self.test2(); + return fRc; + + + # + # Test config helpers. + # + + def createTestVMs(self, sName, iGroup, *tArgs, **dKeywordArgs): + """ + Wrapper around vbox.createTestVM for creating two VMs, the source + (sName-1) and target (sName-2). + + Returns the 2nd VM object on success, None on failure. + """ + sName1 = sName + '-1'; + oVM = self.createTestVM(sName1, iGroup * 2, *tArgs, **dKeywordArgs); + if oVM is not None: + sName2 = sName + '-2'; + oVM = self.createTestVM(sName2, iGroup * 2 + 1, *tArgs, **dKeywordArgs); + return oVM; + + + # + # Test execution helpers. + # + + def test2Teleport(self, oVmSrc, oSessionSrc, oVmDst): + """ + Attempts a teleportation. + + Returns the input parameters for the next test2Teleport call (source + and destiation are switched around). The input session is closed and + removed from the task list, while the return session is in the list. + """ + + # Enable the teleporter of the VM. + oSession = self.openSession(oVmDst); + fRc = oSession is not None + if fRc: + fRc = oSession.enableTeleporter(); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + if fRc: + # Start the destination VM. + oSessionDst, oProgressDst = self.startVmEx(oVmDst, fWait = False); + if oSessionDst is not None: + if oProgressDst.waitForOperation(iOperation = -3) == 0: + + # Do the teleportation. + try: + uDstPort = oVmDst.teleporterPort; + except: + reporter.logXcpt(); + uDstPort = 6502; + oProgressSrc = oSessionSrc.teleport('localhost', uDstPort, 'password', 1024); + if oProgressSrc is not None: + oProgressSrc.wait(); + if oProgressSrc.isSuccess(): + oProgressDst.wait(); + if oProgressSrc.isSuccess() and oProgressDst.isSuccess(): + + # Terminate the source VM. + self.terminateVmBySession(oSessionSrc, oProgressSrc); + + # Return with the source and destination swapped. + return oVmDst, oSessionDst, oVmSrc; + + # Failure / bail out. + oProgressSrc.logResult(); + oProgressDst.logResult(); + self.terminateVmBySession(oSessionDst, oProgressDst); + return oVmSrc, oSessionSrc, oVmDst; + + def test2OneCfg(self, sVmBaseName, cCpus, fHwVirt, fNestedPaging): + """ + Runs the specified VM thru test #1. + """ + + # Reconfigure the source VM. + oVmSrc = self.getVmByName(sVmBaseName + '-1'); + fRc = True; + oSession = self.openSession(oVmSrc); + if oSession is not None: + fRc = fRc and oSession.enableVirtEx(fHwVirt); + fRc = fRc and oSession.enableNestedPaging(fNestedPaging); + fRc = fRc and oSession.setCpuCount(cCpus); + fRc = fRc and oSession.setupTeleporter(False, uPort=6501, sPassword='password'); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + + # Reconfigure the destination VM. + oVmDst = self.getVmByName(sVmBaseName + '-2'); + oSession = self.openSession(oVmDst); + if oSession is not None: + fRc = fRc and oSession.enableVirtEx(fHwVirt); + fRc = fRc and oSession.enableNestedPaging(fNestedPaging); + fRc = fRc and oSession.setCpuCount(cCpus); + fRc = fRc and oSession.setupTeleporter(True, uPort=6502, sPassword='password'); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + + # Simple test. + if fRc is True: + self.logVmInfo(oVmSrc); + self.logVmInfo(oVmDst); + + # Start the source VM. + oSessionSrc = self.startVm(oVmSrc); + if oSessionSrc is not None: + # Simple back and forth to test the ice... + cTeleportations = 0; + oVmSrc, oSessionSrc, oVmDst = self.test2Teleport(oVmSrc, oSessionSrc, oVmDst); + if reporter.testErrorCount() == 0: + cTeleportations += 1; + oVmSrc, oSessionSrc, oVmDst = self.test2Teleport(oVmSrc, oSessionSrc, oVmDst); + + # Teleport back and forth for a while. + msStart = base.timestampMilli(); + while reporter.testErrorCount() == 0: + cTeleportations += 1; + if oSessionSrc.txsTryConnectViaTcp(2500, 'localhost') is True: + break; + oVmSrc, oSessionSrc, oVmDst = self.test2Teleport(oVmSrc, oSessionSrc, oVmDst); + cMsElapsed = base.timestampMilli() - msStart; + if cMsElapsed > 5*60000: + reporter.testFailure('TXS did not show up after %u min of teleporting (%u)...' \ + % (cMsElapsed / 60000.0, cTeleportations)); + break; + + # Clean up the source VM. + self.terminateVmBySession(oSessionSrc) + return None; + + def test2OneVM(self, sVmBaseName, asSupVirtModes = None, rSupCpus = range(1, 256)): + """ + Runs one VM (a pair really) thru the various configurations. + """ + if asSupVirtModes is None: + asSupVirtModes = self.asVirtModes; + + reporter.testStart(sVmBaseName); + for cCpus in self.acCpus: + if cCpus == 1: reporter.testStart('1 cpu'); + else: reporter.testStart('%u cpus' % (cCpus)); + + for sVirtMode in self.asVirtModes: + if sVirtMode == 'raw' and cCpus > 1: + continue; + if cCpus not in rSupCpus: + continue; + if sVirtMode not in asSupVirtModes: + continue; + hsVirtModeDesc = {}; + hsVirtModeDesc['raw'] = 'Raw-mode'; + hsVirtModeDesc['hwvirt'] = 'HwVirt'; + hsVirtModeDesc['hwvirt-np'] = 'NestedPaging'; + reporter.testStart(hsVirtModeDesc[sVirtMode]); + + fHwVirt = sVirtMode != 'raw'; + fNestedPaging = sVirtMode == 'hwvirt-np'; + self.test2OneCfg(sVmBaseName, cCpus, fHwVirt, fNestedPaging); + + reporter.testDone(); + reporter.testDone(); + return reporter.testDone()[1] == 0; + + def test2(self): + """ + Executes test #2. + """ + + # Loop thru the test VMs. + fRc = True; + for sVM in self.asTestVMs: + # figure args. + asSupVirtModes = None; + if sVM in ('tst-sol11', 'tst-sol10'): # 64-bit only + asSupVirtModes = ['hwvirt', 'hwvirt-np',]; + + # run test on the VM. + if not self.test2OneVM(sVM, asSupVirtModes): + fRc = False; + + return fRc; + # + # Test #1 + # + + def test1ResetVmConfig(self, oVM, fTeleporterEnabled = False): + """ + Resets the teleportation config for the specified VM. + Returns True on success, False on failure. + """ + oSession = self.openSession(oVM); + if oSession is not None: + fRc = oSession.setupTeleporter(fTeleporterEnabled, uPort=6502, sPassword='password'); + fRc = fRc and oSession.saveSettings(); + if not oSession.close(): fRc = False; + oSession = None; + else: + fRc = False; + return fRc; + + def test1Sub7(self, oVmSrc, oVmDst): + """ + Test the password check. + """ + reporter.testStart('Bad password'); + if self.test1ResetVmConfig(oVmSrc, fTeleporterEnabled = False) \ + and self.test1ResetVmConfig(oVmDst, fTeleporterEnabled = True): + # Start the target VM. + oSessionDst, oProgressDst = self.startVmEx(oVmDst, fWait = False); + if oSessionDst is not None: + if oProgressDst.waitForOperation(iOperation = -3) == 0: + # Start the source VM. + oSessionSrc = self.startVm(oVmSrc); + if oSessionSrc is not None: + tsPasswords = ('password-bad', 'passwor', 'pass', 'p', '', 'Password', ); + for sPassword in tsPasswords: + reporter.testStart(sPassword); + oProgressSrc = oSessionSrc.teleport('localhost', 6502, sPassword); + if oProgressSrc: + oProgressSrc.wait(); + reporter.log('src: %s' % oProgressSrc.stringifyResult()); + if oProgressSrc.isSuccess(): + reporter.testFailure('IConsole::teleport succeeded with bad password "%s"' % sPassword); + elif oProgressSrc.getErrInfoResultCode() != vbox.ComError.E_FAIL: + reporter.testFailure('IConsole::teleport returns %s instead of E_FAIL' \ + % (vbox.ComError.toString(oProgressSrc.getErrInfoResultCode()),)); + elif oProgressSrc.getErrInfoText() != 'Invalid password': + reporter.testFailure('IConsole::teleport returns "%s" instead of "Invalid password"' \ + % (oProgressSrc.getErrInfoText(),)); + elif oProgressDst.isCompleted(): + reporter.testFailure('Destination completed unexpectedly after bad password "%s"' \ + % sPassword); + else: + reporter.testFailure('IConsole::teleport failed with password "%s"' % sPassword); + if reporter.testDone()[1] != 0: + break; + self.terminateVmBySession(oSessionSrc, oProgressSrc); + self.terminateVmBySession(oSessionDst, oProgressDst); + else: + reporter.testFailure('reconfig failed'); + return reporter.testDone()[1] == 0; + + def test1Sub6(self, oVmSrc, oVmDst): + """ + Misconfigure the target VM and check that teleportation fails with the + same status and message on both ends. + xTracker: #4813 + """ + reporter.testStart('Misconfiguration & error message'); + if self.test1ResetVmConfig(oVmSrc, fTeleporterEnabled = False) \ + and self.test1ResetVmConfig(oVmDst, fTeleporterEnabled = True): + # Give the source a bit more RAM. + oSession = self.openSession(oVmSrc); + if oSession is not None: + try: cbMB = oVmSrc.memorySize + 4; + except: cbMB = 1; fRc = False; + fRc = oSession.setRamSize(cbMB); + if not oSession.saveSettings(): fRc = False; + if not oSession.close(): fRc = False; + oSession = None; + else: + fRc = False; + if fRc: + # Start the target VM. + oSessionDst, oProgressDst = self.startVmEx(oVmDst, fWait = False); + if oSessionDst is not None: + if oProgressDst.waitForOperation(iOperation = -3) == 0: + # Start the source VM. + oSessionSrc = self.startVm(oVmSrc); + if oSessionSrc is not None: + # Try teleport. + oProgressSrc = oSessionSrc.teleport('localhost', 6502, 'password'); + if oProgressSrc: + oProgressSrc.wait(); + oProgressDst.wait(); + + reporter.log('src: %s' % oProgressSrc.stringifyResult()); + reporter.log('dst: %s' % oProgressDst.stringifyResult()); + + # Make sure it failed. + if oProgressSrc.isSuccess() and oProgressDst.isSuccess(): + reporter.testFailure('The teleporation did not fail as expected'); + + # Compare the results. + if oProgressSrc.getResult() != oProgressDst.getResult(): + reporter.testFailure('Result differs - src=%s dst=%s' \ + % (vbox.ComError.toString(oProgressSrc.getResult()),\ + vbox.ComError.toString(oProgressDst.getResult()))); + elif oProgressSrc.getErrInfoResultCode() != oProgressDst.getErrInfoResultCode(): + reporter.testFailure('ErrorInfo::resultCode differs - src=%s dst=%s' \ + % (vbox.ComError.toString(oProgressSrc.getErrInfoResultCode()),\ + vbox.ComError.toString(oProgressDst.getErrInfoResultCode()))); + elif oProgressSrc.getErrInfoText() != oProgressDst.getErrInfoText(): + reporter.testFailure('ErrorInfo::text differs - src="%s" dst="%s"' \ + % (oProgressSrc.getErrInfoText(), oProgressDst.getErrInfoText())); + + self.terminateVmBySession(oSessionSrc, oProgressSrc); + self.terminateVmBySession(oSessionDst, oProgressDst); + self.test1ResetVmConfig(oVmSrc, fTeleporterEnabled = False) + self.test1ResetVmConfig(oVmDst, fTeleporterEnabled = True); + else: + reporter.testFailure('reconfig #2 failed'); + else: + reporter.testFailure('reconfig #1 failed'); + return reporter.testDone()[1] == 0; + + def test1Sub5(self, oVmSrc, oVmDst): + """ + Test that basic teleporting works. + xTracker: #4749 + """ + reporter.testStart('Simple teleportation'); + for cSecsX2 in range(0, 10): + if self.test1ResetVmConfig(oVmSrc, fTeleporterEnabled = False) \ + and self.test1ResetVmConfig(oVmDst, fTeleporterEnabled = True): + # Start the target VM. + oSessionDst, oProgressDst = self.startVmEx(oVmDst, fWait = False); + if oSessionDst is not None: + if oProgressDst.waitForOperation(iOperation = -3) == 0: + # Start the source VM. + oSessionSrc = self.startVm(oVmSrc); + if oSessionSrc is not None: + self.sleep(cSecsX2 / 2); + # Try teleport. + oProgressSrc = oSessionSrc.teleport('localhost', 6502, 'password'); + if oProgressSrc: + oProgressSrc.wait(); + oProgressDst.wait(); + + self.terminateVmBySession(oSessionSrc, oProgressSrc); + self.terminateVmBySession(oSessionDst, oProgressDst); + else: + reporter.testFailure('reconfig failed'); + return reporter.testDone()[1] == 0; + + def test1Sub4(self, oVM): + """ + Test that we can start and cancel a teleportation target. + (No source VM trying to connect here.) + xTracker: #4965 + """ + reporter.testStart('openRemoteSession cancel'); + for cSecsX2 in range(0, 10): + if self.test1ResetVmConfig(oVM, fTeleporterEnabled = True): + oSession, oProgress = self.startVmEx(oVM, fWait = False); + if oSession is not None: + self.sleep(cSecsX2 / 2); + oProgress.cancel(); + oProgress.wait(); + self.terminateVmBySession(oSession, oProgress); + else: + reporter.testFailure('reconfig failed'); + return reporter.testDone()[1] == 0; + + def test1Sub3(self, oVM): + """ + Test that starting a teleportation target VM will fail if we give it + a bad address to bind to. + """ + reporter.testStart('bad IMachine::teleporterAddress'); + + # re-configure it with a bad bind-to address. + fRc = False; + oSession = self.openSession(oVM); + if oSession is not None: + fRc = oSession.setupTeleporter(True, uPort=6502, sAddress='no.such.hostname.should.ever.exist.duh'); + if not oSession.saveSettings(fClose=True): fRc = False; + oSession = None; + if fRc: + # Try start it. + oSession, oProgress = self.startVmEx(oVM, fWait = False); + if oSession is not None: + oProgress.wait(); + ## TODO: exact error code and look for the IPRT right string. + if not oProgress.isCompleted() or oProgress.getResult() >= 0: + reporter.testFailure('%s' % (oProgress.stringifyResult(),)); + self.terminateVmBySession(oSession, oProgress); + + # put back the old teleporter setup. + self.test1ResetVmConfig(oVM, fTeleporterEnabled = True); + else: + reporter.testFailure('reconfig #1 failed'); + return reporter.testDone()[1] == 0; + + # test1Sub2 - start + + def test1Sub2SetEnabled(self, oSession, fEnabled): + """ This should never fail.""" + try: + oSession.o.machine.teleporterEnabled = fEnabled; + except: + reporter.testFailureXcpt('machine.teleporterEnabled=%s' % (fEnabled,)); + return False; + try: + fNew = oSession.o.machine.teleporterEnabled; + except: + reporter.testFailureXcpt(); + return False; + if fNew != fEnabled: + reporter.testFailure('machine.teleporterEnabled=%s but afterwards it is actually %s' % (fEnabled, fNew)); + return False; + return True; + + def test1Sub2SetPassword(self, oSession, sPassword): + """ This should never fail.""" + try: + oSession.o.machine.teleporterPassword = sPassword; + except: + reporter.testFailureXcpt('machine.teleporterPassword=%s' % (sPassword,)); + return False; + try: + sNew = oSession.o.machine.teleporterPassword; + except: + reporter.testFailureXcpt(); + return False; + if sNew != sPassword: + reporter.testFailure('machine.teleporterPassword="%s" but afterwards it is actually "%s"' % (sPassword, sNew)); + return False; + return True; + + def test1Sub2SetPort(self, oSession, uPort, fInvalid = False): + """ This can fail, thus fInvalid.""" + if not fInvalid: + uOld = uPort; + else: + try: uOld = oSession.o.machine.teleporterPort; + except: return reporter.testFailureXcpt(); + + try: + oSession.o.machine.teleporterPort = uPort; + except Exception as oXcpt: + if not fInvalid or vbox.ComError.notEqual(oXcpt, vbox.ComError.E_INVALIDARG): + return reporter.testFailureXcpt('machine.teleporterPort=%u' % (uPort,)); + else: + if fInvalid: + return reporter.testFailureXcpt('machine.teleporterPort=%u succeeded unexpectedly' % (uPort,)); + + try: uNew = oSession.o.machine.teleporterPort; + except: return reporter.testFailureXcpt(); + if uNew != uOld: + if not fInvalid: + reporter.testFailure('machine.teleporterPort=%u but afterwards it is actually %u' % (uPort, uNew)); + else: + reporter.testFailure('machine.teleporterPort is %u after failure, expected %u' % (uNew, uOld)); + return False; + return True; + + def test1Sub2SetAddress(self, oSession, sAddress): + """ This should never fail.""" + try: + oSession.o.machine.teleporterAddress = sAddress; + except: + reporter.testFailureXcpt('machine.teleporterAddress=%s' % (sAddress,)); + return False; + try: + sNew = oSession.o.machine.teleporterAddress; + except: + reporter.testFailureXcpt(); + return False; + if sNew != sAddress: + reporter.testFailure('machine.teleporterAddress="%s" but afterwards it is actually "%s"' % (sAddress, sNew)); + return False; + return True; + + def test1Sub2(self, oVM): + """ + Test the attributes, making sure that we get exceptions on bad values. + """ + reporter.testStart('IMachine::teleport*'); + + # Save the original teleporter attributes for the discard test. + try: + sOrgAddress = oVM.teleporterAddress; + uOrgPort = oVM.teleporterPort; + sOrgPassword = oVM.teleporterPassword; + fOrgEnabled = oVM.teleporterEnabled; + except: + reporter.testFailureXcpt(); + else: + # Open a session and start messing with the properties. + oSession = self.openSession(oVM); + if oSession is not None: + # Anything goes for the address. + reporter.testStart('teleporterAddress'); + self.test1Sub2SetAddress(oSession, ''); + self.test1Sub2SetAddress(oSession, '1'); + self.test1Sub2SetAddress(oSession, 'Anything goes! ^&$@!$%^'); + reporter.testDone(); + + # The port is restricted to {0..65535}. + reporter.testStart('teleporterPort'); + for uPort in range(0, 1000) + range(16000, 17000) + range(32000, 33000) + range(65000, 65536): + if not self.test1Sub2SetPort(oSession, uPort): + break; + self.processPendingEvents(); + reporter.testDone(); + + reporter.testStart('teleporterPort negative'); + self.test1Sub2SetPort(oSession, 65536, True); + self.test1Sub2SetPort(oSession, 999999, True); + reporter.testDone(); + + # Anything goes for the password. + reporter.testStart('teleporterPassword'); + self.test1Sub2SetPassword(oSession, 'password'); + self.test1Sub2SetPassword(oSession, ''); + self.test1Sub2SetPassword(oSession, '1'); + self.test1Sub2SetPassword(oSession, 'Anything goes! ^&$@!$%^'); + reporter.testDone(); + + # Just test that it works. + reporter.testStart('teleporterEnabled'); + self.test1Sub2SetEnabled(oSession, True); + self.test1Sub2SetEnabled(oSession, True); + self.test1Sub2SetEnabled(oSession, False); + self.test1Sub2SetEnabled(oSession, False); + reporter.testDone(); + + # Finally, discard the changes, close the session and check + # that we're back to the originals. + if not oSession.discardSettings(True): + reporter.testFailure('Failed to discard settings & close the session') + else: + reporter.testFailure('Failed to open VM session') + try: + if oVM.teleporterAddress != sOrgAddress: reporter.testFailure('Rollback failed for teleporterAddress'); + if oVM.teleporterPort != uOrgPort: reporter.testFailure('Rollback failed for teleporterPort'); + if oVM.teleporterPassword != sOrgPassword: reporter.testFailure('Rollback failed for teleporterPassword'); + if oVM.teleporterEnabled != fOrgEnabled: reporter.testFailure('Rollback failed for teleporterEnabled'); + except: + reporter.testFailureXcpt(); + return reporter.testDone()[1] != 0; + + # test1Sub1 - start + + def test1Sub1DoTeleport(self, oSession, sHostname, uPort, sPassword, cMsMaxDowntime, hrcExpected, sTestName): + """ Do a bad IConsole::teleport call and check the result.""" + reporter.testStart(sTestName); + fRc = False; + try: + oProgress = oSession.o.console.teleport(sHostname, uPort, sPassword, cMsMaxDowntime); + except vbox.ComException as oXcpt: + if vbox.ComError.equal(oXcpt, hrcExpected): + fRc = True; + else: + reporter.testFailure('hresult %s, expected %s' \ + % (vbox.ComError.toString(oXcpt.hresult), + vbox.ComError.toString(hrcExpected))); + except Exception as oXcpt: + reporter.testFailure('Unexpected exception %s' % (oXcpt)); + else: + reporter.testFailure('Unpexected success'); + oProgress.cancel(); + oProgress.wait(); + reporter.testDone(); + return fRc; + + def test1Sub1(self, oVM): + """ Test simple IConsole::teleport() failure paths. """ + reporter.testStart('IConsole::teleport'); + oSession = self.startVm(oVM); + if oSession: + self.test1Sub1DoTeleport(oSession, 'localhost', 65536, 'password', 10000, + vbox.ComError.E_INVALIDARG, 'Bad port value 65536'); + self.test1Sub1DoTeleport(oSession, 'localhost', 0, 'password', 10000, + vbox.ComError.E_INVALIDARG, 'Bad port value 0'); + self.test1Sub1DoTeleport(oSession, 'localhost', 5000, 'password', 0, + vbox.ComError.E_INVALIDARG, 'Bad max downtime'); + self.test1Sub1DoTeleport(oSession, '', 5000, 'password', 10000, + vbox.ComError.E_INVALIDARG, 'No hostname'); + self.test1Sub1DoTeleport(oSession, 'no.such.hostname.should.ever.exist.duh', 5000, 'password', 0, + vbox.ComError.E_INVALIDARG, 'Non-existing host'); + + self.terminateVmBySession(oSession) + else: + reporter.testFailure('startVm'); + return reporter.testDone()[1] == 0; + + + def test1(self): + """ + Executes test #1 - Negative API testing. + + ASSUMES that the VMs are + """ + reporter.testStart('Test 1'); + + # Get the VMs. + #oVmHwVirt1 = self.getVmByName('tst-empty-hwvirt-1'); + #oVmHwVirt2 = self.getVmByName('tst-empty-hwvirt-2'); + oVmRaw1 = self.getVmByName('tst-empty-raw-1'); + oVmRaw2 = self.getVmByName('tst-empty-raw-2'); + + # Reset their teleportation related configuration. + fRc = True; + #for oVM in (oVmHwVirt1, oVmHwVirt2, oVmRaw1, oVmRaw2): + for oVM in (oVmRaw1, oVmRaw2): + if not self.test1ResetVmConfig(oVM): fRc = False; + + # Do the testing (don't both with fRc on the subtests). + if fRc: + self.test1Sub1(oVmRaw1); + self.test1Sub2(oVmRaw2); + self.test1Sub3(oVmRaw2); + self.test1Sub4(oVmRaw2); + self.processPendingEvents(); + self.test1Sub5(oVmRaw1, oVmRaw2); + self.test1Sub6(oVmRaw1, oVmRaw2); + self.test1Sub7(oVmRaw1, oVmRaw2); + else: + reporter.testFailure('Failed to reset the VM configs') + return reporter.testDone()[1] == 0; + + + +if __name__ == '__main__': + sys.exit(tdTeleportLocal1().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/unittests/Makefile.kmk b/src/VBox/ValidationKit/tests/unittests/Makefile.kmk new file mode 100644 index 00000000..af34c5fa --- /dev/null +++ b/src/VBox/ValidationKit/tests/unittests/Makefile.kmk @@ -0,0 +1,51 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Unit Tests. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +INSTALLS += ValidationKitTestsUnitTests +ValidationKitTestsUnitTests_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsUnitTests_INST = $(INST_VALIDATIONKIT)tests/unittests/ +ValidationKitTestsUnitTests_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdUnitTest1.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsUnitTests_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/unittests/tdUnitTest1.py b/src/VBox/ValidationKit/tests/unittests/tdUnitTest1.py new file mode 100755 index 00000000..49c507a2 --- /dev/null +++ b/src/VBox/ValidationKit/tests/unittests/tdUnitTest1.py @@ -0,0 +1,1257 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdUnitTest1.py $ + +""" +VirtualBox Validation Kit - Unit Tests. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2023 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 155137 $" + + +# Standard Python imports. +import os +import sys +import re + + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(g_ksValidationKitDir) + +# Validation Kit imports. +from common import utils; +from testdriver import base; +from testdriver import reporter; +from testdriver import vbox; +from testdriver import vboxcon; + + +class tdUnitTest1(vbox.TestDriver): + """ + Unit Tests. + """ + + ## The temporary exclude list. + ## @note This shall be empty before we release 4.3! + kdTestCasesBuggyPerOs = { + 'darwin': { + 'testcase/tstX86-1': '', # 'FSTP M32R, ST0' fails; no idea why. + }, + 'linux': { + 'testcase/tstRTFileAio': '', # See xTracker #8035. + }, + 'linux.amd64': { + 'testcase/tstLdr-4': '', # failed: Failed to get bits for '/home/vbox/test/tmp/bin/testcase/tstLdrObjR0.r0'/0, + # rc=VERR_SYMBOL_VALUE_TOO_BIG. aborting test + }, + 'solaris': { + 'testcase/tstIntNet-1': '', # Fails opening rge0, probably a generic issue figuring which nic to use. + 'testcase/tstIprtList': '', # Crashes in the multithreaded test, I think. + 'testcase/tstRTCritSect': '', # Fairness/whatever issue here. + 'testcase/tstRTR0MemUserKernelDriver': '', # Failes when kernel to kernel buffers. + 'testcase/tstRTSemRW': '', # line 338: RTSemRWReleaseRead(hSemRW): got VERR_ACCESS_DENIED + 'testcase/tstRTStrAlloc': '', # VERR_NO_STR_MEMORY! + 'testcase/tstRTFileQuerySize-1': '', # VERR_DEV_IO_ERROR on /dev/null! + }, + 'solaris.amd64': { + 'testcase/tstLdr-4': '', # failed: Failed to get bits for '/home/vbox/test/tmp/bin/testcase/tstLdrObjR0.r0'/0, + # rc=VERR_SYMBOL_VALUE_TOO_BIG. aborting test + }, + 'win': { + 'testcase/tstFile': '', # ?? + 'testcase/tstIntNet-1': '', # possibly same issue as solaris. + 'testcase/tstMouseImpl': '', # STATUS_ACCESS_VIOLATION + 'testcase/tstRTR0ThreadPreemptionDriver': '', # ?? + 'testcase/tstRTPath': '<4.3.51r89894', + 'testcase/tstRTPipe': '', # ?? + 'testcase/tstRTR0MemUserKernelDriver': '', # ?? + 'testcase/tstRTR0SemMutexDriver': '', # ?? + 'testcase/tstRTStrAlloc': '', # ?? + 'testcase/tstRTStrFormat': '', # ?? + 'testcase/tstRTSystemQueryOsInfo': '', # ?? + 'testcase/tstRTTemp': '', # ?? + 'testcase/tstRTTime': '', # ?? + 'testcase/tstTime-2': '', # Total time differs too much! ... delta=-10859859 + 'testcase/tstTime-4': '', # Needs to be converted to DLL; ditto for tstTime-2. + 'testcase/tstUtf8': '', # ?? + 'testcase/tstVMMR0CallHost-2': '', # STATUS_STACK_OVERFLOW + 'testcase/tstX86-1': '', # Fails on win.x86. + 'tscpasswd': '', # ?? + 'tstVMREQ': '', # ?? Same as darwin.x86? + }, + 'win.x86': { + 'testcase/tstRTR0TimerDriver': '', # See xTracker #8041. + } + }; + + kdTestCasesBuggy = { + 'testcase/tstGuestPropSvc': '', # GET_NOTIFICATION fails on testboxlin5.de.oracle.com and others. + 'testcase/tstRTProcCreateEx': '', # Seen failing on wei01-b6ka-9.de.oracle.com. + 'testcase/tstTimer': '', # Sometimes fails on linux, not important atm. + 'testcase/tstGIP-2': '', # 2015-09-10: Fails regularly. E.g. TestSetID 2744205 (testboxsh2), + # 2743961 (wei01-b6kc-6). The responsible engineer should reenable + # it once it has been fixed. + }; + + ## The permanent exclude list. + # @note Stripped of extensions! + kdTestCasesBlackList = { + 'testcase/tstClipboardX11Smoke': '', # (Old naming, deprecated) Needs X, not available on all test boxes. + 'testcase/tstClipboardGH-X11Smoke': '', # (New name) Ditto. + 'testcase/tstClipboardMockHGCM': '', # Ditto. + 'tstClipboardQt': '', # Is interactive and needs Qt, needed for Qt clipboard bugfixing. + 'testcase/tstClipboardQt': '', # In case it moves here. + 'tstDragAndDropQt': '', # Is interactive and needs Qt, needed for Qt drag'n drop bugfixing. + 'testcase/tstDragAndDropQt': '', # In case it moves here. + 'testcase/tstFileLock': '', + 'testcase/tstDisasm-2': '', # without parameters it will disassembler 1GB starting from 0 + 'testcase/tstFileAppendWin-1': '', + 'testcase/tstDir': '', # useless without parameters + 'testcase/tstDir-2': '', # useless without parameters + 'testcase/tstGlobalConfig': '', + 'testcase/tstHostHardwareLinux': '', # must be killed with CTRL-C + 'testcase/tstHttp': '', # Talks to outside servers. + 'testcase/tstRTHttp': '', # parameters required + 'testcase/tstLdr-2': '', # parameters required + 'testcase/tstLdr-3': '', # parameters required + 'testcase/tstLdr': '', # parameters required + 'testcase/tstLdrLoad': '', # parameters required + 'testcase/tstMove': '', # parameters required + 'testcase/tstRTR0Timer': '', # loads 'tstRTR0Timer.r0' + 'testcase/tstRTR0ThreadDriver': '', # loads 'tstRTR0Thread.r0' + 'testcase/tstRunTestcases': '', # that's a script like this one + 'testcase/tstRTReqPool': '', # fails sometimes, testcase buggy + 'testcase/tstRTS3': '', # parameters required + 'testcase/tstSDL': '', # graphics test + 'testcase/tstSupLoadModule': '', # Needs parameters and vboxdrv access. Covered elsewhere. + 'testcase/tstSeamlessX11': '', # graphics test + 'testcase/tstTime-3': '', # parameters required + 'testcase/tstVBoxControl': '', # works only inside a guest + 'testcase/tstVDCopy': '', # parameters required + 'testcase/tstVDFill': '', # parameters required + 'tstAnimate': '', # parameters required + 'testcase/tstAPI': '', # user interaction required + 'tstCollector': '', # takes forever + 'testcase/tstHeadless': '', # parameters required + 'tstHeadless': '', # parameters required + 'tstMicroRC': '', # required by tstMicro + 'tstVBoxDbg': '', # interactive test + 'testcase/tstTestServMgr': '', # some strange xpcom18a4 test, does not work + 'tstTestServMgr': '', # some strange xpcom18a4 test, does not work + 'tstPDMAsyncCompletion': '', # parameters required + 'testcase/tstXptDump': '', # parameters required + 'tstXptDump': '', # parameters required + 'testcase/tstnsIFileEnumerator': '', # some strange xpcom18a4 test, does not work + 'tstnsIFileEnumerator': '', # some strange xpcom18a4 test, does not work + 'testcase/tstSimpleTypeLib': '', # parameters required + 'tstSimpleTypeLib': '', # parameters required + 'testcase/tstTestAtoms': '', # additional test file (words.txt) required + 'tstTestAtoms': '', # additional test file (words.txt) required + 'testcase/tstXptLink': '', # parameters required + 'tstXptLink': '', # parameters required + 'tstXPCOMCGlue': '', # user interaction required + 'testcase/tstXPCOMCGlue': '', # user interaction required + 'testcase/tstCAPIGlue': '', # user interaction required + 'testcase/tstTestCallTemplates': '', # some strange xpcom18a4 test, segfaults + 'tstTestCallTemplates': '', # some strange xpcom18a4 test, segfaults + 'testcase/tstRTFilesystem': '', # parameters required + 'testcase/tstRTDvm': '', # parameters required + 'tstSSLCertDownloads': '', # Obsolete. + # later + 'testcase/tstIntNetR0': '', # RTSPINLOCK_FLAGS_INTERRUPT_SAFE == RTSPINLOCK_FLAGS_INTERRUPT_UNSAFE + # slow stuff + 'testcase/tstAvl': '', # SLOW! + 'testcase/tstRTAvl': '', # SLOW! (new name) + 'testcase/tstVD': '', # 8GB fixed-sized vmdk + # failed or hang + 'testcase/tstCryptoPkcs7Verify': '', # hang + 'tstOVF': '', # hang (only ancient version, now in new place) + 'testcase/tstRTLockValidator': '', # Lock validation is not enabled for critical sections + 'testcase/tstGuestControlSvc': '', # failed: line 288: testHost(&svcTable): + # expected VINF_SUCCESS, got VERR_NOT_FOUND + 'testcase/tstRTMemEf': '', # failed w/o error message + 'testcase/tstSupSem': '', # failed: SRE Timeout Accuracy (ms) : FAILED (1 errors) + 'testcase/tstCryptoPkcs7Sign': '', # failed: 29330: + # error:02001002:lib(2):func(1):reason(2):NA:0:fopen('server.pem': '','r') + 'testcase/tstCompressionBenchmark': '', # failed: error: RTZipBlockCompress failed + # for 'RTZipBlock/LZJB' (#4): VERR_NOT_SUPPORTED + 'tstPDMAsyncCompletionStress': '', # VERR_INVALID_PARAMETER (cbSize = 0) + 'tstMicro': '', # doesn't work on solaris, fix later if we care. + 'tstVMM-HwAccm': '', # failed: Only checked AMD-V on linux + 'tstVMM-HM': '', # failed: Only checked AMD-V on linux + 'tstVMMFork': '', # failed: xtracker 6171 + 'tstTestFactory': '', # some strange xpcom18a4 test, does not work + 'testcase/tstRTSemXRoads': '', # sporadically failed: Traffic - 8 threads per direction, 10 sec : + # FAILED (8 errors) + 'tstVBoxAPILinux': '', # creates VirtualBox directories for root user because of sudo + # (should be in vbox) + 'testcase/tstVMStructDTrace': '', # This is a D-script generator. + 'tstVMStructRC': '', # This is a C-code generator. + 'tstDeviceStructSizeRC': '', # This is a C-code generator. + 'testcase/tstTSC': '', # Doesn't test anything and might fail with HT or/and too many cores. + 'testcase/tstOpenUSBDev': '', # Not a useful testcase. + 'testcase/tstX86-1': '', # Really more guest side. + 'testcase/tstX86-FpuSaveRestore': '', # Experiments, could be useful for the guest not the host. + 'tstAsmStructsRC': '', # Testcase run during build time (fails to find libstdc++.so.6 on some + # Solaris testboxes). + }; + + # Suffix exclude list. + kasSuffixBlackList = [ + '.r0', + '.gc', + '.debug', + '.rel', + '.sys', + '.ko', + '.o', + '.obj', + '.lib', + '.a', + '.so', + '.dll', + '.dylib', + '.tmp', + '.log', + '.py', + '.pyc', + '.pyo', + '.pdb', + '.dSYM', + '.sym', + '.template', + '.expected', + '.expect', + ]; + + # White list, which contains tests considered to be safe to execute, + # even on remote targets (guests). + # + # When --only-whitelist is specified, this is the only list being checked for. + kdTestCasesWhiteList = { + 'testcase/tstFile': '', + 'testcase/tstFileLock': '', + 'testcase/tstClipboardMockHGCM': '', # Requires X on Linux OSes. Execute on remote targets only (guests). + 'testcase/tstRTLocalIpc': '', + 'testcase/tstRTPathQueryInfo': '', + 'testcase/tstRTPipe': '', + 'testcase/tstRTProcCreateEx': '', + 'testcase/tstRTProcCreatePrf': '', + 'testcase/tstRTProcIsRunningByName': '', + 'testcase/tstRTProcQueryUsername': '', + 'testcase/tstRTProcWait': '', + 'testcase/tstTime-2': '', + 'testcase/tstTime-3': '', + 'testcase/tstTime-4': '', + 'testcase/tstTimer': '', + 'testcase/tstThread-1': '', + 'testcase/tstUtf8': '' + }; + + # Test dependency list -- libraries. + # Needed in order to execute testcases on remote targets which don't have a VBox installation present. + kdTestCaseDepsLibs = [ + "VBoxRT" + ]; + + ## The exclude list. + # @note Stripped extensions! + kasHardened = [ + "testcase/tstIntNet-1", + "testcase/tstR0ThreadPreemptionDriver", # VBox 4.3 + "testcase/tstRTR0ThreadPreemptionDriver", + "testcase/tstRTR0MemUserKernelDriver", + "testcase/tstRTR0SemMutexDriver", + "testcase/tstRTR0TimerDriver", + "testcase/tstRTR0ThreadDriver", + 'testcase/tstRTR0DbgKrnlInfoDriver', + "tstInt", + "tstPDMQueue", # Comment in testcase says its driverless, but it needs driver access. + "tstVMM", + "tstVMMFork", + "tstVMREQ", + 'testcase/tstCFGM', + 'testcase/tstContiguous', + 'testcase/tstGetPagingMode', + 'testcase/tstGIP-2', + 'testcase/tstInit', + 'testcase/tstLow', + 'testcase/tstMMHyperHeap', + 'testcase/tstPage', + 'testcase/tstPin', + 'testcase/tstRTTime', 'testcase/tstTime', # GIP test case. + 'testcase/tstRTTime-2', 'testcase/tstTime-2', # GIP test case. + 'testcase/tstRTTime-4', 'testcase/tstTime-4', # GIP test case. + 'testcase/tstSSM', + 'testcase/tstSupSem-Zombie', + ] + + ## Argument lists + kdArguments = { + 'testcase/tstbntest': [ '-out', os.devnull, ], # Very noisy. + }; + + + ## Status code translations. + ## @{ + kdExitCodeNames = { + 0: 'RTEXITCODE_SUCCESS', + 1: 'RTEXITCODE_FAILURE', + 2: 'RTEXITCODE_SYNTAX', + 3: 'RTEXITCODE_INIT', + 4: 'RTEXITCODE_SKIPPED', + }; + kdExitCodeNamesWin = { + -1073741515: 'STATUS_DLL_NOT_FOUND', + -1073741512: 'STATUS_ORDINAL_NOT_FOUND', + -1073741511: 'STATUS_ENTRYPOINT_NOT_FOUND', + -1073741502: 'STATUS_DLL_INIT_FAILED', + -1073741500: 'STATUS_UNHANDLED_EXCEPTION', + -1073741499: 'STATUS_APP_INIT_FAILURE', + -1073741819: 'STATUS_ACCESS_VIOLATION', + -1073741571: 'STATUS_STACK_OVERFLOW', + }; + ## @} + + def __init__(self): + """ + Reinitialize child class instance. + """ + vbox.TestDriver.__init__(self); + + # We need to set a default test VM set here -- otherwise the test + # driver base class won't let us use the "--test-vms" switch. + # + # See the "--local" switch in self.parseOption(). + self.oTestVmSet = self.oTestVmManager.getSmokeVmSet('nat'); + + # Selected NIC attachment. + self.sNicAttachment = ''; + + # Session handling stuff. + # Only needed for remote tests executed by TxS. + self.oSession = None; + self.oTxsSession = None; + + self.sVBoxInstallRoot = None; + + ## Testing mode being used: + # "local": Execute unit tests locally (same host, default). + # "remote-copy": Copies unit tests from host to the remote, then executing it. + # "remote-exec": Executes unit tests right on the remote from a given source. + self.sMode = 'local'; + + self.cSkipped = 0; + self.cPassed = 0; + self.cFailed = 0; + + # The source directory where our unit tests live. + # This most likely is our out/ or some staging directory and + # also acts the source for copying over the testcases to a remote target. + self.sUnitTestsPathSrc = None; + + # Array of environment variables with NAME=VAL entries + # to be applied for testcases. + # + # This is also needed for testcases which are being executed remotely. + self.asEnv = []; + + # The destination directory our unit tests live when being + # copied over to a remote target (via TxS). + self.sUnitTestsPathDst = None; + + # Will be determined before executing the actual unit tests. + self.sExeSuff = ''; + + self.aiVBoxVer = (4, 3, 0, 0); + + # For testing testcase logic. + self.fDryRun = False; + self.fOnlyWhiteList = False; + + @staticmethod + def _sanitizePath(sPath): + """ + Does a little bit of sanitizing a given path by removing quoting, if any. + + This is needed because handed-in paths via command line arguments can contain variables like "${CDROM}" + which might need to get processed by TXS on the guest side first. + + Returns the sanitized path. + """ + if sPath is None: # Keep uninitialized strings as-is. + return None; + return sPath.strip('\"').strip('\''); + + def _detectPaths(self): + """ + Internal worker for actionVerify and actionExecute that detects paths. + + This sets sVBoxInstallRoot and sUnitTestsPathBase and returns True/False. + """ + + reporter.log2('Detecting paths ...'); + + # + # Do some sanity checking first. + # + if self.sMode == 'remote-exec' and not self.sUnitTestsPathSrc: # There is no way we can figure this out automatically. + reporter.error('Unit tests source must be specified explicitly for selected mode!'); + return False; + + # + # We need a VBox install (/ build) to test. + # + if False is True: ## @todo r=andy ?? + if not self.importVBoxApi(): + reporter.error('Unabled to import the VBox Python API.'); + return False; + else: + self._detectBuild(); + if self.oBuild is None: + reporter.error('Unabled to detect the VBox build.'); + return False; + + # + # Where are the files installed? + # Solaris requires special handling because of it's multi arch subdirs. + # + if not self.sVBoxInstallRoot: + self.sVBoxInstallRoot = self.oBuild.sInstallPath; + if not self.oBuild.isDevBuild() and utils.getHostOs() == 'solaris': + sArchDir = utils.getHostArch(); + if sArchDir == 'x86': sArchDir = 'i386'; + self.sVBoxInstallRoot = os.path.join(self.sVBoxInstallRoot, sArchDir); + + ## @todo r=andy Make sure the install root really exists and is accessible. + + # Add the installation root to the PATH on windows so we can get DLLs from it. + if utils.getHostOs() == 'win': + sPathName = 'PATH'; + if not sPathName in os.environ: + sPathName = 'Path'; + sPath = os.environ.get(sPathName, '.'); + if sPath and sPath[-1] != ';': + sPath += ';'; + os.environ[sPathName] = sPath + self.sVBoxInstallRoot + ';'; + else: + reporter.log2('VBox installation root already set to "%s"' % (self.sVBoxInstallRoot)); + + self.sVBoxInstallRoot = self._sanitizePath(self.sVBoxInstallRoot); + + # + # The unittests are generally not installed, so look for them. + # + if not self.sUnitTestsPathSrc: + sBinOrDist = 'dist' if utils.getHostOs() in [ 'darwin', ] else 'bin'; + asCandidates = [ + self.oBuild.sInstallPath, + os.path.join(self.sScratchPath, utils.getHostOsDotArch(), self.oBuild.sType, sBinOrDist), + os.path.join(self.sScratchPath, utils.getHostOsDotArch(), 'release', sBinOrDist), + os.path.join(self.sScratchPath, utils.getHostOsDotArch(), 'debug', sBinOrDist), + os.path.join(self.sScratchPath, utils.getHostOsDotArch(), 'strict', sBinOrDist), + os.path.join(self.sScratchPath, utils.getHostOsDotArch(), 'dbgopt', sBinOrDist), + os.path.join(self.sScratchPath, utils.getHostOsDotArch(), 'profile', sBinOrDist), + os.path.join(self.sScratchPath, sBinOrDist + '.' + utils.getHostArch()), + os.path.join(self.sScratchPath, sBinOrDist, utils.getHostArch()), + os.path.join(self.sScratchPath, sBinOrDist), + ]; + if utils.getHostOs() == 'darwin': + for i in range(1, len(asCandidates)): + asCandidates[i] = os.path.join(asCandidates[i], 'VirtualBox.app', 'Contents', 'MacOS'); + + for sCandidat in asCandidates: + if os.path.exists(os.path.join(sCandidat, 'testcase', 'tstVMStructSize' + self.sExeSuff)): + self.sUnitTestsPathSrc = sCandidat; + break + + if self.sUnitTestsPathSrc: + reporter.log('Unit test source dir path: ', self.sUnitTestsPathSrc) + else: + reporter.error('Unable to find unit test source dir. Candidates: %s' % (asCandidates,)); + if reporter.getVerbosity() >= 2: + reporter.log('Contents of "%s"' % self.sScratchPath); + for paths, dirs, files in os.walk(self.sScratchPath): + reporter.log('{} {} {}'.format(repr(paths), repr(dirs), repr(files))); + return False + + else: + reporter.log2('Unit test source dir already set to "%s"' % (self.sUnitTestsPathSrc)) + reporter.log('Unit test source dir path: ', self.sUnitTestsPathSrc) + + self.sUnitTestsPathSrc = self._sanitizePath(self.sUnitTestsPathSrc); + + return True; + + # + # Overridden methods. + # + + def showUsage(self): + """ + Shows the testdriver usage. + """ + fRc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('Unit Test #1 options:'); + reporter.log(' --dryrun'); + reporter.log(' Performs a dryrun (no tests being executed).'); + reporter.log(' --mode <local|remote-copy|remote-exec>'); + reporter.log(' Specifies the test execution mode:'); + reporter.log(' local: Locally on the same machine.'); + reporter.log(' remote-copy: On remote (guest) by copying them from the local source.'); + reporter.log(' remote-exec: On remote (guest) directly (needs unit test source).'); + reporter.log(' --only-whitelist'); + reporter.log(' Only processes the white list.'); + reporter.log(' --quick'); + reporter.log(' Very selective testing.'); + reporter.log(' --unittest-source <dir>'); + reporter.log(' Sets the unit test source to <dir>.'); + reporter.log(' Also used for remote execution.'); + reporter.log(' --vbox-install-root <dir>'); + reporter.log(' Sets the VBox install root to <dir>.'); + reporter.log(' Also used for remote execution.'); + return fRc; + + def parseOption(self, asArgs, iArg): + """ + Parses the testdriver arguments from the command line. + """ + if asArgs[iArg] == '--dryrun': + self.fDryRun = True; + elif asArgs[iArg] == '--mode': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('Option "%s" needs a value' % (asArgs[iArg - 1])); + if asArgs[iArg] in ('local', 'remote-copy', 'remote-exec',): + self.sMode = asArgs[iArg]; + else: + raise base.InvalidOption('Argument "%s" invalid' % (asArgs[iArg])); + elif asArgs[iArg] == '--unittest-source': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('Option "%s" needs a value' % (asArgs[iArg - 1])); + self.sUnitTestsPathSrc = asArgs[iArg]; + elif asArgs[iArg] == '--only-whitelist': + self.fOnlyWhiteList = True; + elif asArgs[iArg] == '--quick': + self.fOnlyWhiteList = True; + elif asArgs[iArg] == '--vbox-install-root': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('Option "%s" needs a value' % (asArgs[iArg - 1])); + self.sVBoxInstallRoot = asArgs[iArg]; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def actionVerify(self): + if not self._detectPaths(): + return False; + + if self.oTestVmSet: + return vbox.TestDriver.actionVerify(self); + + return True; + + def actionConfig(self): + # Make sure vboxapi has been imported so we can use the constants. + if not self.importVBoxApi(): + return False; + + # Do the configuring. + if self.isRemoteMode(): + if self.sNicAttachment == 'nat': eNic0AttachType = vboxcon.NetworkAttachmentType_NAT; + elif self.sNicAttachment == 'bridged': eNic0AttachType = vboxcon.NetworkAttachmentType_Bridged; + else: eNic0AttachType = None; + + # Make sure to mount the Validation Kit .ISO so that TxS has the chance + # to update itself. + # + # This is necessary as a lot of our test VMs nowadays have a very old TxS + # installed which don't understand commands like uploading files to the guest. + # Uploading files is needed for this test driver, however. + # + ## @todo Get rid of this as soon as we create test VMs in a descriptive (automated) manner. + return self.oTestVmSet.actionConfig(self, eNic0AttachType = eNic0AttachType, + sDvdImage = self.sVBoxValidationKitIso); + + return True; + + def actionExecute(self): + # Make sure vboxapi has been imported so we can execute the driver without going thru + # a former configuring step. + if not self.importVBoxApi(): + return False; + if not self._detectPaths(): + return False; + reporter.log2('Unit test source path is "%s"\n' % self.sUnitTestsPathSrc); + + if not self.sUnitTestsPathDst: + self.sUnitTestsPathDst = self.sScratchPath; + reporter.log2('Unit test destination path is "%s"\n' % self.sUnitTestsPathDst); + + if self.isRemoteMode(): # Run on a test VM (guest). + if self.fpApiVer < 7.0: ## @todo Needs Validation Kit .ISO tweaking (including the unit tests) first. + reporter.log('Remote unit tests for non-trunk builds skipped.'); + fRc = True; + else: + assert self.oTestVmSet is not None; + fRc = self.oTestVmSet.actionExecute(self, self.testOneVmConfig); + else: # Run locally (host). + self._figureVersion(); + self._makeEnvironmentChanges(); + + # If this is an ASAN build and we're on linux, make sure we've got + # libasan.so.N in the LD_LIBRARY_PATH or stuff w/o a RPATH entry + # pointing to /opt/VirtualBox will fail (like tstAsmStructs). + if self.getBuildType() == 'asan' and utils.getHostOs() in ('linux',): + sLdLibraryPath = ''; + if 'LD_LIBRARY_PATH' in os.environ: + sLdLibraryPath = os.environ['LD_LIBRARY_PATH'] + ':'; + sLdLibraryPath += self.oBuild.sInstallPath; + os.environ['LD_LIBRARY_PATH'] = sLdLibraryPath; + + fRc = self._testRunUnitTests(None); + + return fRc; + + # + # Misc. + # + def isRemoteMode(self): + """ Predicate method for checking if in any remote mode. """ + return self.sMode.startswith('remote'); + + # + # Test execution helpers. + # + + def _testRunUnitTests(self, oTestVm): + """ + Main function to execute all unit tests. + """ + + # Determine executable suffix based on selected execution mode. + if self.isRemoteMode(): # Run on a test VM (guest). + if oTestVm.isWindows(): + self.sExeSuff = '.exe'; + else: + self.sExeSuff = ''; + else: + self.sExeSuff = base.exeSuff(); + + self._testRunUnitTestsSet(oTestVm, r'^tst*', 'testcase'); + self._testRunUnitTestsSet(oTestVm, r'^tst*', '.'); + + fRc = self.cFailed == 0; + + reporter.log(''); + if self.fDryRun: + reporter.log('*********************************************************'); + reporter.log('DRY RUN - DRY RUN - DRY RUN - DRY RUN - DRY RUN - DRY RUN'); + reporter.log('*********************************************************'); + reporter.log('*********************************************************'); + reporter.log(' Target: %s' % (oTestVm.sVmName if oTestVm else 'local',)); + reporter.log(' Mode: %s' % (self.sMode,)); + reporter.log(' Exe suffix: %s' % (self.sExeSuff,)); + reporter.log('Unit tests source: %s %s' + % (self.sUnitTestsPathSrc, '(on remote)' if self.sMode == 'remote-exec' else '',)); + reporter.log('VBox install root: %s %s' + % (self.sVBoxInstallRoot, '(on remote)' if self.sMode == 'remote-exec' else '',)); + reporter.log('*********************************************************'); + reporter.log('*** PASSED: %d' % (self.cPassed,)); + reporter.log('*** FAILED: %d' % (self.cFailed,)); + reporter.log('*** SKIPPED: %d' % (self.cSkipped,)); + reporter.log('*** TOTAL: %d' % (self.cPassed + self.cFailed + self.cSkipped,)); + + return fRc; + + + def testOneVmConfig(self, oVM, oTestVm): + """ + Runs the specified VM thru test #1. + """ + + # Simple test. + self.logVmInfo(oVM); + + if not self.fDryRun: + # Try waiting for a bit longer (5 minutes) until the CD is available to avoid running into timeouts. + self.oSession, self.oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, + fCdWait = not self.fDryRun, + cMsCdWait = 5 * 60 * 1000); + if self.oSession is None: + return False; + + self.addTask(self.oTxsSession); + + # Determine the unit tests destination path. + self.sUnitTestsPathDst = oTestVm.pathJoin(self.getGuestTempDir(oTestVm), 'testUnitTests'); + + # Run the unit tests. + self._testRunUnitTests(oTestVm); + + # Cleanup. + if self.oSession is not None: + self.removeTask(self.oTxsSession); + self.terminateVmBySession(self.oSession); + return True; + + # + # Test execution helpers. + # + + def _figureVersion(self): + """ Tries to figure which VBox version this is, setting self.aiVBoxVer. """ + try: + sVer = utils.processOutputChecked(['VBoxManage', '--version']) + + sVer = sVer.strip(); + sVer = re.sub(r'_BETA.*r', '.', sVer); + sVer = re.sub(r'_ALPHA.*r', '.', sVer); + sVer = re.sub(r'_RC.*r', '.', sVer); + sVer = re.sub('_SPB', '', sVer) + sVer = sVer.replace('r', '.'); + + self.aiVBoxVer = [int(sComp) for sComp in sVer.split('.')]; + + reporter.log('VBox version: %s' % (self.aiVBoxVer,)); + except: + reporter.logXcpt(); + return False; + return True; + + def _compareVersion(self, aiVer): + """ + Compares the give version string with the vbox version string, + returning a result similar to C strcmp(). aiVer is on the right side. + """ + cComponents = min(len(self.aiVBoxVer), len(aiVer)); + for i in range(cComponents): + if self.aiVBoxVer[i] < aiVer[i]: + return -1; + if self.aiVBoxVer[i] > aiVer[i]: + return 1; + return len(self.aiVBoxVer) - len(aiVer); + + def _isExcluded(self, sTest, dExclList): + """ Checks if the testcase is excluded or not. """ + if sTest in dExclList: + sFullExpr = dExclList[sTest].replace(' ', '').strip(); + if sFullExpr == '': + return True; + + # Consider each exclusion expression. These are generally ranges, + # either open ended or closed: "<4.3.51r12345", ">=4.3.0 && <=4.3.4". + asExprs = sFullExpr.split(';'); + for sExpr in asExprs: + + # Split it on the and operator and process each sub expression. + fResult = True; + for sSubExpr in sExpr.split('&&'): + # Split out the comparison operator and the version value. + if sSubExpr.startswith('<=') or sSubExpr.startswith('>='): + sOp = sSubExpr[:2]; + sValue = sSubExpr[2:]; + elif sSubExpr.startswith('<') or sSubExpr.startswith('>') or sSubExpr.startswith('='): + sOp = sSubExpr[:1]; + sValue = sSubExpr[1:]; + else: + sOp = sValue = ''; + + # Convert the version value, making sure we've got a valid one. + try: aiValue = [int(sComp) for sComp in sValue.replace('r', '.').split('.')]; + except: aiValue = (); + if not aiValue or len(aiValue) > 4: + reporter.error('Invalid exclusion expression for %s: "%s" [%s]' % (sTest, sSubExpr, dExclList[sTest])); + return True; + + # Do the compare. + iCmp = self._compareVersion(aiValue); + if sOp == '>=' and iCmp < 0: + fResult = False; + elif sOp == '>' and iCmp <= 0: + fResult = False; + elif sOp == '<' and iCmp >= 0: + fResult = False; + elif sOp == '>=' and iCmp < 0: + fResult = False; + reporter.log2('iCmp=%s; %s %s %s -> %s' % (iCmp, self.aiVBoxVer, sOp, aiValue, fResult)); + + # Did the expression match? + if fResult: + return True; + + return False; + + def _sudoExecuteSync(self, asArgs): + """ + Executes a sudo child process synchronously. + Returns True if the process executed successfully and returned 0, + otherwise False is returned. + """ + reporter.log2('Executing [sudo]: %s' % (asArgs, )); + if self.isRemoteMode(): + iRc = -1; ## @todo Not used remotely yet. + else: + try: + iRc = utils.sudoProcessCall(asArgs, shell = False, close_fds = False); + except: + reporter.errorXcpt(); + return False; + reporter.log('Exit code [sudo]: %s (%s)' % (iRc, asArgs)); + return iRc == 0; + + + def _logExpandString(self, sString, cVerbosity = 2): + """ + Expands a given string by asking TxS on the guest side and logs it. + Uses log level 2 by default. + + No-op if no TxS involved. + """ + if reporter.getVerbosity() < cVerbosity or self.oTxsSession is None: + return; + sStringExp = self.oTxsSession.syncExpandString(sString); + if not sStringExp: + return; + reporter.log2('_logExpandString: "%s" -> "%s"' % (sString, sStringExp)); + + def _wrapPathExists(self, sPath): + """ + Creates the directory specified sPath (including parents). + """ + reporter.log2('_wrapPathExists: %s' % (sPath,)); + if self.fDryRun: + return True; + fRc = False; + if self.isRemoteMode(): + self._logExpandString(sPath); + fRc = self.oTxsSession.syncIsDir(sPath, fIgnoreErrors = True); + if not fRc: + fRc = self.oTxsSession.syncIsFile(sPath, fIgnoreErrors = True); + else: + fRc = os.path.exists(sPath); + return fRc; + + def _wrapMkDir(self, sPath): + """ + Creates the directory specified sPath (including parents). + """ + reporter.log2('_wrapMkDir: %s' % (sPath,)); + if self.fDryRun: + return True; + fRc = True; + if self.isRemoteMode(): + fRc = self.oTxsSession.syncMkDirPath(sPath, fMode = 0o755); + else: + if utils.getHostOs() in [ 'win', 'os2' ]: + os.makedirs(sPath, 0o755); + else: + fRc = self._sudoExecuteSync(['/bin/mkdir', '-p', '-m', '0755', sPath]); + if not fRc: + reporter.log('Failed to create dir "%s".' % (sPath,)); + return fRc; + + def _wrapCopyFile(self, sSrc, sDst, iMode): + """ + Copies a file. + """ + reporter.log2('_wrapCopyFile: %s -> %s (mode: %o)' % (sSrc, sDst, iMode,)); + if self.fDryRun: + return True; + fRc = True; + if self.isRemoteMode(): + self._logExpandString(sSrc); + self._logExpandString(sDst); + if self.sMode == 'remote-exec': + self.oTxsSession.syncCopyFile(sSrc, sDst, iMode); + else: + fRc = self.oTxsSession.syncUploadFile(sSrc, sDst); + if fRc: + fRc = self.oTxsSession.syncChMod(sDst, iMode); + else: + if utils.getHostOs() in [ 'win', 'os2' ]: + utils.copyFileSimple(sSrc, sDst); + os.chmod(sDst, iMode); + else: + fRc = self._sudoExecuteSync(['/bin/cp', sSrc, sDst]); + if fRc: + fRc = self._sudoExecuteSync(['/bin/chmod', '%o' % (iMode,), sDst]); + if fRc is not True: + raise Exception('Failed to chmod "%s".' % (sDst,)); + if not fRc: + reporter.log('Failed to copy "%s" to "%s".' % (sSrc, sDst,)); + return fRc; + + def _wrapDeleteFile(self, sPath): + """ + Deletes a file. + """ + reporter.log2('_wrapDeleteFile: %s' % (sPath,)); + if self.fDryRun: + return True; + fRc = True; + if self.isRemoteMode(): + if self.oTxsSession.syncIsFile(sPath): + fRc = self.oTxsSession.syncRmFile(sPath, fIgnoreErrors = True); + else: + if os.path.exists(sPath): + if utils.getHostOs() in [ 'win', 'os2' ]: + os.remove(sPath); + else: + fRc = self._sudoExecuteSync(['/bin/rm', sPath]); + if not fRc: + reporter.log('Failed to remove "%s".' % (sPath,)); + return fRc; + + def _wrapRemoveDir(self, sPath): + """ + Removes a directory. + """ + reporter.log2('_wrapRemoveDir: %s' % (sPath,)); + if self.fDryRun: + return True; + fRc = True; + if self.isRemoteMode(): + if self.oTxsSession.syncIsDir(sPath): + fRc = self.oTxsSession.syncRmDir(sPath, fIgnoreErrors = True); + else: + if os.path.exists(sPath): + if utils.getHostOs() in [ 'win', 'os2' ]: + os.rmdir(sPath); + else: + fRc = self._sudoExecuteSync(['/bin/rmdir', sPath]); + if not fRc: + reporter.log('Failed to remove "%s".' % (sPath,)); + return fRc; + + def _envSet(self, sName, sValue): + if self.isRemoteMode(): + # For remote execution we cache the environment block and pass it + # right when the process execution happens. + self.asEnv.append([ sName, sValue ]); + else: + os.environ[sName] = sValue; + return True; + + def _executeTestCase(self, oTestVm, sName, sFullPath, sTestCaseSubDir, oDevNull): # pylint: disable=too-many-locals,too-many-statements + """ + Executes a test case. + """ + + fSkipped = False; + + # + # If hardening is enabled, some test cases and their dependencies + # needs to be copied to and execute from the source + # directory in order to work. They also have to be executed as + # root, i.e. via sudo. + # + fHardened = sName in self.kasHardened and self.sUnitTestsPathSrc != self.sVBoxInstallRoot; + fCopyToRemote = self.isRemoteMode(); + fCopyDeps = self.isRemoteMode(); + asFilesToRemove = []; # Stuff to clean up. + asDirsToRemove = []; # Ditto. + + if fHardened or fCopyToRemote: + if fCopyToRemote: + sDstDir = os.path.join(self.sUnitTestsPathDst, sTestCaseSubDir); + else: + sDstDir = os.path.join(self.sVBoxInstallRoot, sTestCaseSubDir); + if not self._wrapPathExists(sDstDir): + self._wrapMkDir(sDstDir); + asDirsToRemove.append(sDstDir); + + sSrc = sFullPath + self.sExeSuff; + # If the testcase source does not exist for whatever reason, just mark it as skipped + # instead of reporting an error. + if not self._wrapPathExists(sSrc): + self.cSkipped += 1; + fSkipped = True; + return fSkipped; + + sDst = os.path.join(sDstDir, os.path.basename(sFullPath) + self.sExeSuff); + fModeExe = 0; + fModeDeps = 0; + if not oTestVm or (oTestVm and not oTestVm.isWindows()): ## @todo NT4 does not like the chmod. Investigate this! + fModeExe = 0o755; + fModeDeps = 0o644; + self._wrapCopyFile(sSrc, sDst, fModeExe); + asFilesToRemove.append(sDst); + + # Copy required dependencies to destination. + if fCopyDeps: + for sLib in self.kdTestCaseDepsLibs: + for sSuff in [ '.dll', '.so', '.dylib' ]: + assert self.sVBoxInstallRoot is not None; + sSrc = os.path.join(self.sVBoxInstallRoot, sLib + sSuff); + if self._wrapPathExists(sSrc): + sDst = os.path.join(sDstDir, os.path.basename(sSrc)); + self._wrapCopyFile(sSrc, sDst, fModeDeps); + asFilesToRemove.append(sDst); + + # Copy any associated .dll/.so/.dylib. + for sSuff in [ '.dll', '.so', '.dylib' ]: + sSrc = os.path.splitext(sFullPath)[0] + sSuff; + if os.path.exists(sSrc): + sDst = os.path.join(sDstDir, os.path.basename(sSrc)); + self._wrapCopyFile(sSrc, sDst, fModeDeps); + asFilesToRemove.append(sDst); + + # Copy any associated .r0, .rc and .gc modules. + offDriver = sFullPath.rfind('Driver') + if offDriver > 0: + for sSuff in [ '.r0', 'RC.rc', 'RC.gc' ]: + sSrc = sFullPath[:offDriver] + sSuff; + if os.path.exists(sSrc): + sDst = os.path.join(sDstDir, os.path.basename(sSrc)); + self._wrapCopyFile(sSrc, sDst, fModeDeps); + asFilesToRemove.append(sDst); + + sFullPath = os.path.join(sDstDir, os.path.basename(sFullPath)); + + # + # Set up arguments and environment. + # + asArgs = [sFullPath + self.sExeSuff,] + if sName in self.kdArguments: + asArgs.extend(self.kdArguments[sName]); + + sXmlFile = os.path.join(self.sUnitTestsPathDst, 'result.xml'); + + self._envSet('IPRT_TEST_OMIT_TOP_TEST', '1'); + self._envSet('IPRT_TEST_FILE', sXmlFile); + + if self._wrapPathExists(sXmlFile): + try: os.unlink(sXmlFile); + except: self._wrapDeleteFile(sXmlFile); + + # + # Execute the test case. + # + # Windows is confusing output. Trying a few things to get rid of this. + # First, flush both stderr and stdout before running the child. Second, + # assign the child stderr to stdout. If this doesn't help, we'll have + # to capture the child output. + # + reporter.log('*** Executing %s%s...' % (asArgs, ' [hardened]' if fHardened else '')); + try: sys.stdout.flush(); + except: pass; + try: sys.stderr.flush(); + except: pass; + + iRc = 0; + + if not self.fDryRun: + if fCopyToRemote: + fRc = self.txsRunTest(self.oTxsSession, sName, cMsTimeout = 30 * 60 * 1000, sExecName = asArgs[0], + asArgs = asArgs, asAddEnv = self.asEnv, fCheckSessionStatus = True); + if fRc: + iRc = 0; + else: + (_, sOpcode, abPayload) = self.oTxsSession.getLastReply(); + if sOpcode.startswith('PROC NOK '): # Extract process rc. + iRc = abPayload[0]; # ASSUMES 8-bit rc for now. + if iRc == 0: # Might happen if the testcase misses some dependencies. Set it to -42 then. + iRc = -42; + else: + iRc = -1; ## @todo + else: + oChild = None; + try: + if fHardened: + oChild = utils.sudoProcessPopen(asArgs, stdin = oDevNull, stdout = sys.stdout, stderr = sys.stdout); + else: + oChild = utils.processPopenSafe(asArgs, stdin = oDevNull, stdout = sys.stdout, stderr = sys.stdout); + except: + if sName in [ 'tstAsmStructsRC', # 32-bit, may fail to start on 64-bit linux. Just ignore. + ]: + reporter.logXcpt(); + fSkipped = True; + else: + reporter.errorXcpt(); + iRc = 1023; + oChild = None; + + if oChild is not None: + self.pidFileAdd(oChild.pid, sName, fSudo = fHardened); + iRc = oChild.wait(); + self.pidFileRemove(oChild.pid); + # + # Clean up + # + for sPath in asFilesToRemove: + self._wrapDeleteFile(sPath); + for sPath in asDirsToRemove: + self._wrapRemoveDir(sPath); + + # + # Report. + # + if os.path.exists(sXmlFile): + reporter.addSubXmlFile(sXmlFile); + if fHardened: + self._wrapDeleteFile(sXmlFile); + else: + os.unlink(sXmlFile); + + if iRc == 0: + reporter.log('*** %s: exit code %d' % (sFullPath, iRc)); + self.cPassed += 1; + + elif iRc == 4: # RTEXITCODE_SKIPPED + reporter.log('*** %s: exit code %d (RTEXITCODE_SKIPPED)' % (sFullPath, iRc)); + fSkipped = True; + self.cSkipped += 1; + + elif fSkipped: + reporter.log('*** %s: exit code %d (Skipped)' % (sFullPath, iRc)); + self.cSkipped += 1; + + else: + sName = self.kdExitCodeNames.get(iRc, ''); + if iRc in self.kdExitCodeNamesWin and utils.getHostOs() == 'win': + sName = self.kdExitCodeNamesWin[iRc]; + if sName != '': + sName = ' (%s)' % (sName); + + if iRc != 1: + reporter.testFailure('Exit status: %d%s' % (iRc, sName)); + reporter.log( '!*! %s: exit code %d%s' % (sFullPath, iRc, sName)); + else: + reporter.error('!*! %s: exit code %d%s' % (sFullPath, iRc, sName)); + self.cFailed += 1; + + return fSkipped; + + def _testRunUnitTestsSet(self, oTestVm, sTestCasePattern, sTestCaseSubDir): + """ + Run subset of the unit tests set. + """ + + # Open /dev/null for use as stdin further down. + try: + oDevNull = open(os.path.devnull, 'w+'); # pylint: disable=consider-using-with,unspecified-encoding + except: + oDevNull = None; + + # Determin the host OS specific exclusion lists. + dTestCasesBuggyForHostOs = self.kdTestCasesBuggyPerOs.get(utils.getHostOs(), []); + dTestCasesBuggyForHostOs.update(self.kdTestCasesBuggyPerOs.get(utils.getHostOsDotArch(), [])); + + ## @todo Add filtering for more specific OSes (like OL server, doesn't have X installed) by adding a separate + # black list + using utils.getHostOsVersion(). + + # + # Process the file list and run everything looking like a testcase. + # + if not self.fOnlyWhiteList: + if self.sMode in ('local', 'remote-copy'): + asFiles = sorted(os.listdir(os.path.join(self.sUnitTestsPathSrc, sTestCaseSubDir))); + else: # 'remote-exec' + ## @todo Implement remote file enumeration / directory listing. + reporter.error('Sorry, no remote file enumeration implemented yet!\nUse --only-whitelist instead.'); + return; + else: + # Transform our dict into a list, where the keys are the list elements. + asFiles = list(self.kdTestCasesWhiteList.keys()); + # Make sure to only keep the list item's base name so that the iteration down below works + # with our white list without any additional modification. + asFiles = [os.path.basename(s) for s in asFiles]; + + for sFilename in asFiles: + # Separate base and suffix and morph the base into something we + # can use for reporting and array lookups. + sBaseName = os.path.basename(sFilename); + sName, sSuffix = os.path.splitext(sBaseName); + if sTestCaseSubDir != '.': + sName = sTestCaseSubDir + '/' + sName; + + reporter.log2('sTestCasePattern=%s, sBaseName=%s, sName=%s, sSuffix=%s, sFileName=%s' + % (sTestCasePattern, sBaseName, sName, sSuffix, sFilename,)); + + # Process white list first, if set. + if self.fOnlyWhiteList and not self._isExcluded(sName, self.kdTestCasesWhiteList): + # (No testStart/Done or accounting here!) + reporter.log('%s: SKIPPED (not in white list)' % (sName,)); + continue; + + # Basic exclusion. + if not re.match(sTestCasePattern, sBaseName) or sSuffix in self.kasSuffixBlackList: + reporter.log2('"%s" is not a test case.' % (sName,)); + continue; + + # When not only processing the white list, do some more checking first. + if not self.fOnlyWhiteList: + # Check if the testcase is black listed or buggy before executing it. + if self._isExcluded(sName, self.kdTestCasesBlackList): + # (No testStart/Done or accounting here!) + reporter.log('%s: SKIPPED (blacklisted)' % (sName,)); + continue; + + if self._isExcluded(sName, self.kdTestCasesBuggy): + reporter.testStart(sName); + reporter.log('%s: Skipping, buggy in general.' % (sName,)); + reporter.testDone(fSkipped = True); + self.cSkipped += 1; + continue; + + if self._isExcluded(sName, dTestCasesBuggyForHostOs): + reporter.testStart(sName); + reporter.log('%s: Skipping, buggy on %s.' % (sName, utils.getHostOs(),)); + reporter.testDone(fSkipped = True); + self.cSkipped += 1; + continue; + else: + # Passed the white list check already above. + pass; + + sFullPath = os.path.normpath(os.path.join(self.sUnitTestsPathSrc, os.path.join(sTestCaseSubDir, sFilename))); + reporter.testStart(sName); + try: + fSkipped = self._executeTestCase(oTestVm, sName, sFullPath, sTestCaseSubDir, oDevNull); + except: + reporter.errorXcpt('!*!'); + self.cFailed += 1; + fSkipped = False; + reporter.testDone(fSkipped); + + +if __name__ == '__main__': + sys.exit(tdUnitTest1().main(sys.argv)) diff --git a/src/VBox/ValidationKit/tests/usb/Makefile.kmk b/src/VBox/ValidationKit/tests/usb/Makefile.kmk new file mode 100644 index 00000000..0b25f015 --- /dev/null +++ b/src/VBox/ValidationKit/tests/usb/Makefile.kmk @@ -0,0 +1,53 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - USB. +# + +# +# Copyright (C) 2014-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +INSTALLS += ValidationKitTestsUsb +ValidationKitTestsUsb_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsUsb_INST = $(INST_VALIDATIONKIT)tests/usb/ +ValidationKitTestsUsb_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdUsb1.py \ + $(PATH_SUB_CURRENT)/usbgadget.py \ + $(PATH_SUB_CURRENT)/tst-utsgadget.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsUsb_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/usb/tdUsb1.py b/src/VBox/ValidationKit/tests/usb/tdUsb1.py new file mode 100755 index 00000000..994916ab --- /dev/null +++ b/src/VBox/ValidationKit/tests/usb/tdUsb1.py @@ -0,0 +1,590 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdUsb1.py $ + +""" +VirtualBox Validation Kit - USB testcase and benchmark. +""" + +__copyright__ = \ +""" +Copyright (C) 2014-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + + +# Standard Python imports. +import os; +import sys; +import socket; + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))); +sys.path.append(g_ksValidationKitDir); + +# Validation Kit imports. +from testdriver import reporter; +from testdriver import base; +from testdriver import vbox; +from testdriver import vboxcon; + +# USB gadget control import +import usbgadget; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + xrange = range; # pylint: disable=redefined-builtin,invalid-name + + +class tdUsbBenchmark(vbox.TestDriver): # pylint: disable=too-many-instance-attributes + """ + USB benchmark. + """ + + # The available test devices + # + # The first key is the hostname of the host the test is running on. + # It contains a new dictionary with the attached gadgets based on the + # USB speed we want to test (Low, Full, High, Super). + # The parameters consist of the hostname of the gadget in the network + # and the hardware type. + kdGadgetParams = { + 'adaris': { + 'Low': ('usbtest.de.oracle.com', None), + 'Full': ('usbtest.de.oracle.com', None), + 'High': ('usbtest.de.oracle.com', None), + 'Super': ('usbtest.de.oracle.com', None) + }, + }; + + # Mappings of USB controllers to supported USB device speeds. + kdUsbSpeedMappings = { + 'OHCI': ['Low', 'Full'], + 'EHCI': ['High'], + 'XHCI': ['Low', 'Full', 'High', 'Super'] + }; + + # Tests currently disabled because they fail, need investigation. + kdUsbTestsDisabled = { + 'Low': [24], + 'Full': [24], + 'High': [24], + 'Super': [24] + }; + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.asTestVMsDef = ['tst-arch']; + self.asTestVMs = self.asTestVMsDef; + self.asSkipVMs = []; + self.asVirtModesDef = ['hwvirt', 'hwvirt-np', 'raw']; + self.asVirtModes = self.asVirtModesDef; + self.acCpusDef = [1, 2,]; + self.acCpus = self.acCpusDef; + self.asUsbCtrlsDef = ['OHCI', 'EHCI', 'XHCI']; + self.asUsbCtrls = self.asUsbCtrlsDef; + self.asUsbSpeedDef = ['Low', 'Full', 'High', 'Super']; + self.asUsbSpeed = self.asUsbSpeedDef; + self.asUsbTestsDef = ['Compliance', 'Reattach']; + self.asUsbTests = self.asUsbTestsDef; + self.cUsbReattachCyclesDef = 100; + self.cUsbReattachCycles = self.cUsbReattachCyclesDef; + self.sHostname = socket.gethostname().lower(); + self.sGadgetHostnameDef = 'usbtest.de.oracle.com'; + self.uGadgetPortDef = None; + self.sUsbCapturePathDef = self.sScratchPath; + self.sUsbCapturePath = self.sUsbCapturePathDef; + self.fUsbCapture = False; + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdUsb1 Options:'); + reporter.log(' --virt-modes <m1[:m2[:]]'); + reporter.log(' Default: %s' % (':'.join(self.asVirtModesDef))); + reporter.log(' --cpu-counts <c1[:c2[:]]'); + reporter.log(' Default: %s' % (':'.join(str(c) for c in self.acCpusDef))); + reporter.log(' --test-vms <vm1[:vm2[:...]]>'); + reporter.log(' Test the specified VMs in the given order. Use this to change'); + reporter.log(' the execution order or limit the choice of VMs'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestVMsDef))); + reporter.log(' --skip-vms <vm1[:vm2[:...]]>'); + reporter.log(' Skip the specified VMs when testing.'); + reporter.log(' --usb-ctrls <u1[:u2[:]]'); + reporter.log(' Default: %s' % (':'.join(str(c) for c in self.asUsbCtrlsDef))); + reporter.log(' --usb-speed <s1[:s2[:]]'); + reporter.log(' Default: %s' % (':'.join(str(c) for c in self.asUsbSpeedDef))); + reporter.log(' --usb-tests <s1[:s2[:]]'); + reporter.log(' Default: %s' % (':'.join(str(c) for c in self.asUsbTestsDef))); + reporter.log(' --usb-reattach-cycles <cycles>'); + reporter.log(' Default: %s' % (self.cUsbReattachCyclesDef)); + reporter.log(' --hostname: <hostname>'); + reporter.log(' Default: %s' % (self.sHostname)); + reporter.log(' --default-gadget-host <hostname>'); + reporter.log(' Default: %s' % (self.sGadgetHostnameDef)); + reporter.log(' --default-gadget-port <port>'); + reporter.log(' Default: %s' % (6042)); + reporter.log(' --usb-capture-path <path>'); + reporter.log(' Default: %s' % (self.sUsbCapturePathDef)); + reporter.log(' --usb-capture'); + reporter.log(' Whether to capture the USB traffic for each test'); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--virt-modes': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--virt-modes" takes a colon separated list of modes'); + self.asVirtModes = asArgs[iArg].split(':'); + for s in self.asVirtModes: + if s not in self.asVirtModesDef: + raise base.InvalidOption('The "--virt-modes" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asVirtModesDef))); + elif asArgs[iArg] == '--cpu-counts': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--cpu-counts" takes a colon separated list of cpu counts'); + self.acCpus = []; + for s in asArgs[iArg].split(':'): + try: c = int(s); + except: raise base.InvalidOption('The "--cpu-counts" value "%s" is not an integer' % (s,)); + if c <= 0: raise base.InvalidOption('The "--cpu-counts" value "%s" is zero or negative' % (s,)); + self.acCpus.append(c); + elif asArgs[iArg] == '--test-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--test-vms" takes colon separated list'); + self.asTestVMs = asArgs[iArg].split(':'); + for s in self.asTestVMs: + if s not in self.asTestVMsDef: + raise base.InvalidOption('The "--test-vms" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asTestVMsDef))); + elif asArgs[iArg] == '--skip-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--skip-vms" takes colon separated list'); + self.asSkipVMs = asArgs[iArg].split(':'); + for s in self.asSkipVMs: + if s not in self.asTestVMsDef: + reporter.log('warning: The "--test-vms" value "%s" does not specify any of our test VMs.' % (s)); + elif asArgs[iArg] == '--usb-ctrls': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--usb-ctrls" takes a colon separated list of USB controllers'); + self.asUsbCtrls = asArgs[iArg].split(':'); + for s in self.asUsbCtrls: + if s not in self.asUsbCtrlsDef: + reporter.log('warning: The "--usb-ctrls" value "%s" is not a valid USB controller.' % (s)); + elif asArgs[iArg] == '--usb-speed': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--usb-speed" takes a colon separated list of USB speeds'); + self.asUsbSpeed = asArgs[iArg].split(':'); + for s in self.asUsbSpeed: + if s not in self.asUsbSpeedDef: + reporter.log('warning: The "--usb-speed" value "%s" is not a valid USB speed.' % (s)); + elif asArgs[iArg] == '--usb-tests': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--usb-tests" takes a colon separated list of USB tests'); + self.asUsbTests = asArgs[iArg].split(':'); + for s in self.asUsbTests: + if s not in self.asUsbTestsDef: + reporter.log('warning: The "--usb-tests" value "%s" is not a valid USB test.' % (s)); + elif asArgs[iArg] == '--usb-reattach-cycles': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--usb-reattach-cycles" takes cycle count'); + try: self.cUsbReattachCycles = int(asArgs[iArg]); + except: raise base.InvalidOption('The "--usb-reattach-cycles" value "%s" is not an integer' \ + % (asArgs[iArg],)); + if self.cUsbReattachCycles <= 0: + raise base.InvalidOption('The "--usb-reattach-cycles" value "%s" is zero or negative.' \ + % (self.cUsbReattachCycles,)); + elif asArgs[iArg] == '--hostname': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--hostname" takes a hostname'); + self.sHostname = asArgs[iArg]; + elif asArgs[iArg] == '--default-gadget-host': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--default-gadget-host" takes a hostname'); + self.sGadgetHostnameDef = asArgs[iArg]; + elif asArgs[iArg] == '--default-gadget-port': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--default-gadget-port" takes port number'); + try: self.uGadgetPortDef = int(asArgs[iArg]); + except: raise base.InvalidOption('The "--default-gadget-port" value "%s" is not an integer' \ + % (asArgs[iArg],)); + if self.uGadgetPortDef <= 0: + raise base.InvalidOption('The "--default-gadget-port" value "%s" is zero or negative.' \ + % (self.uGadgetPortDef,)); + elif asArgs[iArg] == '--usb-capture-path': + if iArg >= len(asArgs): raise base.InvalidOption('The "--usb-capture-path" takes a path argument'); + self.sUsbCapturePath = asArgs[iArg]; + elif asArgs[iArg] == '--usb-capture': + self.fUsbCapture = True; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def completeOptions(self): + # Remove skipped VMs from the test list. + for sVM in self.asSkipVMs: + try: self.asTestVMs.remove(sVM); + except: pass; + + return vbox.TestDriver.completeOptions(self); + + def getResourceSet(self): + # Construct the resource list the first time it's queried. + if self.asRsrcs is None: + self.asRsrcs = []; + + if 'tst-arch' in self.asTestVMs: + self.asRsrcs.append('4.2/usb/tst-arch.vdi'); + + return self.asRsrcs; + + def actionConfig(self): + + # Some stupid trickery to guess the location of the iso. ## fixme - testsuite unzip ++ + sVBoxValidationKit_iso = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../VBoxValidationKit.iso')); + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../VBoxTestSuite.iso')); + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/ramdisk/vbox/svn/trunk/validationkit/VBoxValidationKit.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/ramdisk/vbox/svn/trunk/testsuite/VBoxTestSuite.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sCur = os.getcwd(); + for i in range(0, 10): + sVBoxValidationKit_iso = os.path.join(sCur, 'validationkit/VBoxValidationKit.iso'); + if os.path.isfile(sVBoxValidationKit_iso): + break; + sVBoxValidationKit_iso = os.path.join(sCur, 'testsuite/VBoxTestSuite.iso'); + if os.path.isfile(sVBoxValidationKit_iso): + break; + sCur = os.path.abspath(os.path.join(sCur, '..')); + if i is None: pass; # shut up pychecker/pylint. + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/home/bird/validationkit/VBoxValidationKit.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/home/bird/testsuite/VBoxTestSuite.iso'; + + # Make sure vboxapi has been imported so we can use the constants. + if not self.importVBoxApi(): + return False; + + # + # Configure the VMs we're going to use. + # + + # Linux VMs + if 'tst-arch' in self.asTestVMs: + oVM = self.createTestVM('tst-arch', 1, '4.2/usb/tst-arch.vdi', sKind = 'ArchLinux_64', fIoApic = True, \ + eNic0AttachType = vboxcon.NetworkAttachmentType_NAT, \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + return True; + + def actionExecute(self): + """ + Execute the testcase. + """ + fRc = self.testUsb(); + return fRc; + + def getGadgetParams(self, sHostname, sSpeed): + """ + Returns the gadget hostname and port from the + given hostname the test is running on and device speed we want to test. + """ + kdGadgetsConfigured = self.kdGadgetParams.get(sHostname); + if kdGadgetsConfigured is not None: + return kdGadgetsConfigured.get(sSpeed); + + return (self.sGadgetHostnameDef, self.uGadgetPortDef); + + def getCaptureFilePath(self, sUsbCtrl, sSpeed): + """ + Returns capture filename from the given data. + """ + + return '%s%s%s-%s.pcap' % (self.sUsbCapturePath, os.sep, sUsbCtrl, sSpeed); + + def attachUsbDeviceToVm(self, oSession, sVendorId, sProductId, iBusId, + sCaptureFile = None): + """ + Attaches the given USB device to the VM either via a filter + or directly if capturing the USB traffic is enabled. + + Returns True on success, False on failure. + """ + fRc = False; + if sCaptureFile is None: + fRc = oSession.addUsbDeviceFilter('Compliance device', sVendorId = sVendorId, sProductId = sProductId, \ + sPort = format(iBusId, 'x')); + else: + # Search for the correct device in the USB device list waiting for some time + # to let it appear. + iVendorId = int(sVendorId, 16); + iProductId = int(sProductId, 16); + + # Try a few times to give VBoxSVC a chance to detect the new device. + for _ in xrange(5): + fFound = False; + aoUsbDevs = self.oVBoxMgr.getArray(self.oVBox.host, 'USBDevices'); + for oUsbDev in aoUsbDevs: + if oUsbDev.vendorId == iVendorId \ + and oUsbDev.productId == iProductId \ + and oUsbDev.port == iBusId: + fFound = True; + fRc = oSession.attachUsbDevice(oUsbDev.id, sCaptureFile); + break; + + if fFound: + break; + + # Wait a moment until the next try. + self.sleep(1); + + if fRc: + # Wait a moment to let the USB device appear + self.sleep(9); + + return fRc; + + # + # Test execution helpers. + # + def testUsbCompliance(self, oSession, oTxsSession, sUsbCtrl, sSpeed, sCaptureFile = None): + """ + Test VirtualBoxs USB stack in a VM. + """ + # Get configured USB test devices from hostname we are running on + sGadgetHost, uGadgetPort = self.getGadgetParams(self.sHostname, sSpeed); + + oUsbGadget = usbgadget.UsbGadget(); + reporter.log('Connecting to UTS: ' + sGadgetHost); + fRc = oUsbGadget.connectTo(30 * 1000, sGadgetHost, uPort = uGadgetPort, fTryConnect = True); + if fRc is True: + reporter.log('Connect succeeded'); + self.oVBox.host.addUSBDeviceSource('USBIP', sGadgetHost, sGadgetHost + (':%s' % oUsbGadget.getUsbIpPort()), [], []); + + fSuperSpeed = False; + if sSpeed == 'Super': + fSuperSpeed = True; + + # Create test device gadget and a filter to attach the device automatically. + fRc = oUsbGadget.impersonate(usbgadget.g_ksGadgetImpersonationTest, fSuperSpeed); + if fRc is True: + iBusId, _ = oUsbGadget.getGadgetBusAndDevId(); + fRc = self.attachUsbDeviceToVm(oSession, '0525', 'a4a0', iBusId, sCaptureFile); + if fRc is True: + tupCmdLine = ('UsbTest', ); + # Exclude a few tests which hang and cause a timeout, need investigation. + lstTestsExclude = self.kdUsbTestsDisabled.get(sSpeed); + for iTestExclude in lstTestsExclude: + tupCmdLine = tupCmdLine + ('--exclude', str(iTestExclude)); + + fRc = self.txsRunTest(oTxsSession, 'UsbTest', 3600 * 1000, \ + '${CDROM}/${OS/ARCH}/UsbTest${EXESUFF}', tupCmdLine); + if not fRc: + reporter.testFailure('Running USB test utility failed'); + else: + reporter.testFailure('Failed to attach USB device to VM'); + oUsbGadget.disconnectFrom(); + else: + reporter.testFailure('Failed to impersonate test device'); + + self.oVBox.host.removeUSBDeviceSource(sGadgetHost); + else: + reporter.log('warning: Failed to connect to USB gadget'); + fRc = None + + _ = sUsbCtrl; + return fRc; + + def testUsbReattach(self, oSession, oTxsSession, sUsbCtrl, sSpeed, sCaptureFile = None): # pylint: disable=unused-argument + """ + Tests that rapid connect/disconnect cycles work. + """ + # Get configured USB test devices from hostname we are running on + sGadgetHost, uGadgetPort = self.getGadgetParams(self.sHostname, sSpeed); + + oUsbGadget = usbgadget.UsbGadget(); + reporter.log('Connecting to UTS: ' + sGadgetHost); + fRc = oUsbGadget.connectTo(30 * 1000, sGadgetHost, uPort = uGadgetPort, fTryConnect = True); + if fRc is True: + self.oVBox.host.addUSBDeviceSource('USBIP', sGadgetHost, sGadgetHost + (':%s' % oUsbGadget.getUsbIpPort()), [], []); + + fSuperSpeed = False; + if sSpeed == 'Super': + fSuperSpeed = True; + + # Create test device gadget and a filter to attach the device automatically. + fRc = oUsbGadget.impersonate(usbgadget.g_ksGadgetImpersonationTest, fSuperSpeed); + if fRc is True: + iBusId, _ = oUsbGadget.getGadgetBusAndDevId(); + fRc = self.attachUsbDeviceToVm(oSession, '0525', 'a4a0', iBusId, sCaptureFile); + if fRc is True: + + # Wait a moment to let the USB device appear + self.sleep(3); + + # Do a rapid disconnect reconnect cycle. Wait a second before disconnecting + # again or it will happen so fast that the VM can't attach the new device. + # @todo: Get rid of the constant wait and use an event to get notified when + # the device was attached. + for iCycle in xrange (0, self.cUsbReattachCycles): + fRc = oUsbGadget.disconnectUsb(); + fRc = fRc and oUsbGadget.connectUsb(); + if not fRc: + reporter.testFailure('Reattach cycle %s failed on the gadget device' % (iCycle)); + break; + self.sleep(1); + + else: + reporter.testFailure('Failed to create USB device filter'); + + oUsbGadget.disconnectFrom(); + else: + reporter.testFailure('Failed to impersonate test device'); + else: + reporter.log('warning: Failed to connect to USB gadget'); + fRc = None + + return fRc; + + def testUsbOneCfg(self, sVmName, sUsbCtrl, sSpeed, sUsbTest): + """ + Runs the specified VM thru one specified test. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + oVM = self.getVmByName(sVmName); + + # Reconfigure the VM + fRc = True; + oSession = self.openSession(oVM); + if oSession is not None: + fRc = fRc and oSession.enableVirtEx(True); + fRc = fRc and oSession.enableNestedPaging(True); + + # Make sure controllers are disabled initially. + fRc = fRc and oSession.enableUsbOhci(False); + fRc = fRc and oSession.enableUsbEhci(False); + fRc = fRc and oSession.enableUsbXhci(False); + + if sUsbCtrl == 'OHCI': + fRc = fRc and oSession.enableUsbOhci(True); + elif sUsbCtrl == 'EHCI': + fRc = fRc and oSession.enableUsbEhci(True); + elif sUsbCtrl == 'XHCI': + fRc = fRc and oSession.enableUsbXhci(True); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + + # Start up. + if fRc is True: + self.logVmInfo(oVM); + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(sVmName, fCdWait = False, fNatForwardingForTxs = False); + if oSession is not None: + self.addTask(oTxsSession); + + # Fudge factor - Allow the guest to finish starting up. + self.sleep(5); + + sCaptureFile = None; + if self.fUsbCapture: + sCaptureFile = self.getCaptureFilePath(sUsbCtrl, sSpeed); + + if sUsbTest == 'Compliance': + fRc = self.testUsbCompliance(oSession, oTxsSession, sUsbCtrl, sSpeed, sCaptureFile); + elif sUsbTest == 'Reattach': + fRc = self.testUsbReattach(oSession, oTxsSession, sUsbCtrl, sSpeed, sCaptureFile); + + # cleanup. + self.removeTask(oTxsSession); + self.terminateVmBySession(oSession) + + # Add the traffic dump if it exists and the test failed + if reporter.testErrorCount() > 0 \ + and sCaptureFile is not None \ + and os.path.exists(sCaptureFile): + reporter.addLogFile(sCaptureFile, 'misc/other', 'USB traffic dump'); + else: + fRc = False; + return fRc; + + def testUsbForOneVM(self, sVmName): + """ + Runs one VM thru the various configurations. + """ + fRc = False; + reporter.testStart(sVmName); + for sUsbCtrl in self.asUsbCtrls: + reporter.testStart(sUsbCtrl) + for sUsbSpeed in self.asUsbSpeed: + asSupportedSpeeds = self.kdUsbSpeedMappings.get(sUsbCtrl); + if sUsbSpeed in asSupportedSpeeds: + reporter.testStart(sUsbSpeed) + for sUsbTest in self.asUsbTests: + reporter.testStart(sUsbTest) + fRc = self.testUsbOneCfg(sVmName, sUsbCtrl, sUsbSpeed, sUsbTest); + reporter.testDone(); + reporter.testDone(); + reporter.testDone(); + reporter.testDone(); + return fRc; + + def testUsb(self): + """ + Executes USB test. + """ + + reporter.log("Running on host: " + self.sHostname); + + # Loop thru the test VMs. + for sVM in self.asTestVMs: + # run test on the VM. + fRc = self.testUsbForOneVM(sVM); + + return fRc; + + + +if __name__ == '__main__': + sys.exit(tdUsbBenchmark().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/usb/tst-utsgadget.py b/src/VBox/ValidationKit/tests/usb/tst-utsgadget.py new file mode 100755 index 00000000..1f8220a8 --- /dev/null +++ b/src/VBox/ValidationKit/tests/usb/tst-utsgadget.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +# $Id: tst-utsgadget.py $ + +""" +Simple testcase for usbgadget2.py. +""" + +__copyright__ = \ +""" +Copyright (C) 2016-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 153224 $" + +# Standard python imports. +import sys + +# Validation Kit imports. +sys.path.insert(0, '.'); +sys.path.insert(0, '..'); +sys.path.insert(0, '../..'); +from common import utils; +from testdriver import reporter; +import usbgadget; + + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + + +g_cTests = 0; +g_cFailures = 0 + +def boolRes(rc, fExpect = True): + """Checks a boolean result.""" + global g_cTests, g_cFailures; + g_cTests = g_cTests + 1; + if isinstance(rc, bool): + if rc == fExpect: + return 'PASSED'; + g_cFailures = g_cFailures + 1; + return 'FAILED'; + +def stringRes(rc, sExpect): + """Checks a string result.""" + global g_cTests, g_cFailures; + g_cTests = g_cTests + 1; + if utils.isString(rc): + if rc == sExpect: + return 'PASSED'; + g_cFailures = g_cFailures + 1; + return 'FAILED'; + +def main(asArgs): # pylint: disable=missing-docstring,too-many-locals,too-many-statements + cMsTimeout = long(30*1000); + sAddress = 'localhost'; + uPort = None; + fStdTests = True; + + i = 1; + while i < len(asArgs): + if asArgs[i] == '--hostname': + sAddress = asArgs[i + 1]; + i = i + 2; + elif asArgs[i] == '--port': + uPort = int(asArgs[i + 1]); + i = i + 2; + elif asArgs[i] == '--timeout': + cMsTimeout = long(asArgs[i + 1]); + i = i + 2; + elif asArgs[i] == '--help': + print('tst-utsgadget.py [--hostname <addr|name>] [--port <num>] [--timeout <cMS>]'); + return 0; + else: + print('Unknown argument: %s' % (asArgs[i],)); + return 2; + + oGadget = usbgadget.UsbGadget(); + if uPort is None: + rc = oGadget.connectTo(cMsTimeout, sAddress); + else: + rc = oGadget.connectTo(cMsTimeout, sAddress, uPort = uPort); + if rc is False: + print('connectTo failed'); + return 1; + + if fStdTests: + rc = oGadget.getUsbIpPort() is not None; + print('%s: getUsbIpPort() -> %s' % (boolRes(rc), oGadget.getUsbIpPort(),)); + + rc = oGadget.impersonate(usbgadget.g_ksGadgetImpersonationTest); + print('%s: impersonate()' % (boolRes(rc),)); + + rc = oGadget.disconnectUsb(); + print('%s: disconnectUsb()' % (boolRes(rc),)); + + rc = oGadget.connectUsb(); + print('%s: connectUsb()' % (boolRes(rc),)); + + rc = oGadget.clearImpersonation(); + print('%s: clearImpersonation()' % (boolRes(rc),)); + + # Test super speed (and therefore passing configuration items) + rc = oGadget.impersonate(usbgadget.g_ksGadgetImpersonationTest, True); + print('%s: impersonate(, True)' % (boolRes(rc),)); + + rc = oGadget.clearImpersonation(); + print('%s: clearImpersonation()' % (boolRes(rc),)); + + # Done + rc = oGadget.disconnectFrom(); + print('%s: disconnectFrom() -> %s' % (boolRes(rc), rc,)); + + if g_cFailures != 0: + print('tst-utsgadget.py: %u out of %u test failed' % (g_cFailures, g_cTests,)); + return 1; + print('tst-utsgadget.py: all %u tests passed!' % (g_cTests,)); + return 0; + + +if __name__ == '__main__': + reporter.incVerbosity(); + reporter.incVerbosity(); + reporter.incVerbosity(); + reporter.incVerbosity(); + sys.exit(main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/usb/usbgadget.py b/src/VBox/ValidationKit/tests/usb/usbgadget.py new file mode 100755 index 00000000..e00ef0d6 --- /dev/null +++ b/src/VBox/ValidationKit/tests/usb/usbgadget.py @@ -0,0 +1,1478 @@ +# -*- coding: utf-8 -*- +# $Id: usbgadget.py $ +# pylint: disable=too-many-lines + +""" +UTS (USB Test Service) client. +""" +__copyright__ = \ +""" +Copyright (C) 2010-2022 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 154728 $" + +# Standard Python imports. +import array +import errno +import select +import socket +import sys; +import threading +import time +import zlib + +# Validation Kit imports. +from common import utils; +from testdriver import base; +from testdriver import reporter; +from testdriver.base import TdTaskBase; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + + +## @name USB gadget impersonation string constants. +## @{ +g_ksGadgetImpersonationInvalid = 'Invalid'; +g_ksGadgetImpersonationTest = 'Test'; +g_ksGadgetImpersonationMsd = 'Msd'; +g_ksGadgetImpersonationWebcam = 'Webcam'; +g_ksGadgetImpersonationEther = 'Ether'; +## @} + +## @name USB gadget type used in the UTS protocol. +## @{ +g_kiGadgetTypeTest = 1; +## @} + +## @name USB gadget access methods used in the UTS protocol. +## @{ +g_kiGadgetAccessUsbIp = 1; +## @} + +## @name USB gadget config types. +## @{ +g_kiGadgetCfgTypeBool = 1; +g_kiGadgetCfgTypeString = 2; +g_kiGadgetCfgTypeUInt8 = 3; +g_kiGadgetCfgTypeUInt16 = 4; +g_kiGadgetCfgTypeUInt32 = 5; +g_kiGadgetCfgTypeUInt64 = 6; +g_kiGadgetCfgTypeInt8 = 7; +g_kiGadgetCfgTypeInt16 = 8; +g_kiGadgetCfgTypeInt32 = 9; +g_kiGadgetCfgTypeInt64 = 10; +## @} + +# +# Helpers for decoding data received from the UTS. +# These are used both the Session and Transport classes. +# + +def getU64(abData, off): + """Get a U64 field.""" + return abData[off] \ + + abData[off + 1] * 256 \ + + abData[off + 2] * 65536 \ + + abData[off + 3] * 16777216 \ + + abData[off + 4] * 4294967296 \ + + abData[off + 5] * 1099511627776 \ + + abData[off + 6] * 281474976710656 \ + + abData[off + 7] * 72057594037927936; + +def getU32(abData, off): + """Get a U32 field.""" + return abData[off] \ + + abData[off + 1] * 256 \ + + abData[off + 2] * 65536 \ + + abData[off + 3] * 16777216; + +def getU16(abData, off): + """Get a U16 field.""" + return abData[off] \ + + abData[off + 1] * 256; + +def getU8(abData, off): + """Get a U8 field.""" + return abData[off]; + +def getSZ(abData, off, sDefault = None): + """ + Get a zero-terminated string field. + Returns sDefault if the string is invalid. + """ + cchStr = getSZLen(abData, off); + if cchStr >= 0: + abStr = abData[off:(off + cchStr)]; + try: + return abStr.tostring().decode('utf_8'); + except: + reporter.errorXcpt('getSZ(,%u)' % (off)); + return sDefault; + +def getSZLen(abData, off): + """ + Get the length of a zero-terminated string field, in bytes. + Returns -1 if off is beyond the data packet or not properly terminated. + """ + cbData = len(abData); + if off >= cbData: + return -1; + + offCur = off; + while abData[offCur] != 0: + offCur = offCur + 1; + if offCur >= cbData: + return -1; + + return offCur - off; + +def isValidOpcodeEncoding(sOpcode): + """ + Checks if the specified opcode is valid or not. + Returns True on success. + Returns False if it is invalid, details in the log. + """ + sSet1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + sSet2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_ "; + if len(sOpcode) != 8: + reporter.error("invalid opcode length: %s" % (len(sOpcode))); + return False; + for i in range(0, 1): + if sSet1.find(sOpcode[i]) < 0: + reporter.error("invalid opcode char #%u: %s" % (i, sOpcode)); + return False; + for i in range(2, 7): + if sSet2.find(sOpcode[i]) < 0: + reporter.error("invalid opcode char #%u: %s" % (i, sOpcode)); + return False; + return True; + +# +# Helper for encoding data sent to the UTS. +# + +def u32ToByteArray(u32): + """Encodes the u32 value as a little endian byte (B) array.""" + return array.array('B', \ + ( u32 % 256, \ + (u32 // 256) % 256, \ + (u32 // 65536) % 256, \ + (u32 // 16777216) % 256) ); + +def u16ToByteArray(u16): + """Encodes the u16 value as a little endian byte (B) array.""" + return array.array('B', \ + ( u16 % 256, \ + (u16 // 256) % 256) ); + +def u8ToByteArray(uint8): + """Encodes the u8 value as a little endian byte (B) array.""" + return array.array('B', (uint8 % 256)); + +def zeroByteArray(cb): + """Returns an array with the given size containing 0.""" + abArray = array.array('B', (0, )); + cb = cb - 1; + for i in range(cb): # pylint: disable=unused-variable + abArray.append(0); + return abArray; + +def strToByteArry(sStr): + """Encodes the string as a little endian byte (B) array including the terminator.""" + abArray = array.array('B'); + sUtf8 = sStr.encode('utf_8'); + for ch in sUtf8: + abArray.append(ord(ch)); + abArray.append(0); + return abArray; + +def cfgListToByteArray(lst): + """Encodes the given config list as a little endian byte (B) array.""" + abArray = array.array('B'); + if lst is not None: + for t3Item in lst: + # Encode they key size + abArray.extend(u32ToByteArray(len(t3Item[0]) + 1)); # Include terminator + abArray.extend(u32ToByteArray(t3Item[1])) # Config type + abArray.extend(u32ToByteArray(len(t3Item[2]) + 1)); # Value size including temrinator. + abArray.extend(u32ToByteArray(0)); # Reserved field. + + abArray.extend(strToByteArry(t3Item[0])); + abArray.extend(strToByteArry(t3Item[2])); + + return abArray; + +class TransportBase(object): + """ + Base class for the transport layer. + """ + + def __init__(self, sCaller): + self.sDbgCreated = '%s: %s' % (utils.getTimePrefix(), sCaller); + self.fDummy = 0; + self.abReadAheadHdr = array.array('B'); + + def toString(self): + """ + Stringify the instance for logging and debugging. + """ + return '<%s: abReadAheadHdr=%s, sDbgCreated=%s>' % (type(self).__name__, self.abReadAheadHdr, self.sDbgCreated); + + def __str__(self): + return self.toString(); + + def cancelConnect(self): + """ + Cancels any pending connect() call. + Returns None; + """ + return None; + + def connect(self, cMsTimeout): + """ + Quietly attempts to connect to the UTS. + + Returns True on success. + Returns False on retryable errors (no logging). + Returns None on fatal errors with details in the log. + + Override this method, don't call super. + """ + _ = cMsTimeout; + return False; + + def disconnect(self, fQuiet = False): + """ + Disconnect from the UTS. + + Returns True. + + Override this method, don't call super. + """ + _ = fQuiet; + return True; + + def sendBytes(self, abBuf, cMsTimeout): + """ + Sends the bytes in the buffer abBuf to the UTS. + + Returns True on success. + Returns False on failure and error details in the log. + + Override this method, don't call super. + + Remarks: len(abBuf) is always a multiple of 16. + """ + _ = abBuf; _ = cMsTimeout; + return False; + + def recvBytes(self, cb, cMsTimeout, fNoDataOk): + """ + Receive cb number of bytes from the UTS. + + Returns the bytes (array('B')) on success. + Returns None on failure and error details in the log. + + Override this method, don't call super. + + Remarks: cb is always a multiple of 16. + """ + _ = cb; _ = cMsTimeout; _ = fNoDataOk; + return None; + + def isConnectionOk(self): + """ + Checks if the connection is OK. + + Returns True if it is. + Returns False if it isn't (caller should call diconnect). + + Override this method, don't call super. + """ + return True; + + def isRecvPending(self, cMsTimeout = 0): + """ + Checks if there is incoming bytes, optionally waiting cMsTimeout + milliseconds for something to arrive. + + Returns True if there is, False if there isn't. + + Override this method, don't call super. + """ + _ = cMsTimeout; + return False; + + def sendMsgInt(self, sOpcode, cMsTimeout, abPayload = array.array('B')): + """ + Sends a message (opcode + encoded payload). + + Returns True on success. + Returns False on failure and error details in the log. + """ + # Fix + check the opcode. + if len(sOpcode) < 2: + reporter.fatal('sendMsgInt: invalid opcode length: %d (\"%s\")' % (len(sOpcode), sOpcode)); + return False; + sOpcode = sOpcode.ljust(8); + if not isValidOpcodeEncoding(sOpcode): + reporter.fatal('sendMsgInt: invalid opcode encoding: \"%s\"' % (sOpcode)); + return False; + + # Start construct the message. + cbMsg = 16 + len(abPayload); + abMsg = array.array('B'); + abMsg.extend(u32ToByteArray(cbMsg)); + abMsg.extend((0, 0, 0, 0)); # uCrc32 + try: + abMsg.extend(array.array('B', \ + ( ord(sOpcode[0]), \ + ord(sOpcode[1]), \ + ord(sOpcode[2]), \ + ord(sOpcode[3]), \ + ord(sOpcode[4]), \ + ord(sOpcode[5]), \ + ord(sOpcode[6]), \ + ord(sOpcode[7]) ) ) ); + if abPayload: + abMsg.extend(abPayload); + except: + reporter.fatalXcpt('sendMsgInt: packing problem...'); + return False; + + # checksum it, padd it and send it off. + uCrc32 = zlib.crc32(abMsg[8:]); + abMsg[4:8] = u32ToByteArray(uCrc32); + + while len(abMsg) % 16: + abMsg.append(0); + + reporter.log2('sendMsgInt: op=%s len=%d to=%d' % (sOpcode, len(abMsg), cMsTimeout)); + return self.sendBytes(abMsg, cMsTimeout); + + def recvMsg(self, cMsTimeout, fNoDataOk = False): + """ + Receives a message from the UTS. + + Returns the message three-tuple: length, opcode, payload. + Returns (None, None, None) on failure and error details in the log. + """ + + # Read the header. + if self.abReadAheadHdr: + assert(len(self.abReadAheadHdr) == 16); + abHdr = self.abReadAheadHdr; + self.abReadAheadHdr = array.array('B'); + else: + abHdr = self.recvBytes(16, cMsTimeout, fNoDataOk); # (virtual method) # pylint: disable=assignment-from-none + if abHdr is None: + return (None, None, None); + if len(abHdr) != 16: + reporter.fatal('recvBytes(16) returns %d bytes!' % (len(abHdr))); + return (None, None, None); + + # Unpack and validate the header. + cbMsg = getU32(abHdr, 0); + uCrc32 = getU32(abHdr, 4); + sOpcode = abHdr[8:16].tostring().decode('ascii'); + + if cbMsg < 16: + reporter.fatal('recvMsg: message length is out of range: %s (min 16 bytes)' % (cbMsg)); + return (None, None, None); + if cbMsg > 1024*1024: + reporter.fatal('recvMsg: message length is out of range: %s (max 1MB)' % (cbMsg)); + return (None, None, None); + if not isValidOpcodeEncoding(sOpcode): + reporter.fatal('recvMsg: invalid opcode \"%s\"' % (sOpcode)); + return (None, None, None); + + # Get the payload (if any), dropping the padding. + abPayload = array.array('B'); + if cbMsg > 16: + if cbMsg % 16: + cbPadding = 16 - (cbMsg % 16); + else: + cbPadding = 0; + abPayload = self.recvBytes(cbMsg - 16 + cbPadding, cMsTimeout, False); # pylint: disable=assignment-from-none + if abPayload is None: + self.abReadAheadHdr = abHdr; + if not fNoDataOk : + reporter.log('recvMsg: failed to recv payload bytes!'); + return (None, None, None); + + while cbPadding > 0: + abPayload.pop(); + cbPadding = cbPadding - 1; + + # Check the CRC-32. + if uCrc32 != 0: + uActualCrc32 = zlib.crc32(abHdr[8:]); + if cbMsg > 16: + uActualCrc32 = zlib.crc32(abPayload, uActualCrc32); + uActualCrc32 = uActualCrc32 & 0xffffffff; + if uCrc32 != uActualCrc32: + reporter.fatal('recvMsg: crc error: expected %s, got %s' % (hex(uCrc32), hex(uActualCrc32))); + return (None, None, None); + + reporter.log2('recvMsg: op=%s len=%d' % (sOpcode, len(abPayload))); + return (cbMsg, sOpcode, abPayload); + + def sendMsg(self, sOpcode, cMsTimeout, aoPayload = ()): + """ + Sends a message (opcode + payload tuple). + + Returns True on success. + Returns False on failure and error details in the log. + Returns None if you pass the incorrectly typed parameters. + """ + # Encode the payload. + abPayload = array.array('B'); + for o in aoPayload: + try: + if utils.isString(o): + # the primitive approach... + sUtf8 = o.encode('utf_8'); + for ch in sUtf8: + abPayload.append(ord(ch)) + abPayload.append(0); + elif isinstance(o, long): + if o < 0 or o > 0xffffffff: + reporter.fatal('sendMsg: uint32_t payload is out of range: %s' % (hex(o))); + return None; + abPayload.extend(u32ToByteArray(o)); + elif isinstance(o, int): + if o < 0 or o > 0xffffffff: + reporter.fatal('sendMsg: uint32_t payload is out of range: %s' % (hex(o))); + return None; + abPayload.extend(u32ToByteArray(o)); + elif isinstance(o, array.array): + abPayload.extend(o); + else: + reporter.fatal('sendMsg: unexpected payload type: %s (%s) (aoPayload=%s)' % (type(o), o, aoPayload)); + return None; + except: + reporter.fatalXcpt('sendMsg: screwed up the encoding code...'); + return None; + return self.sendMsgInt(sOpcode, cMsTimeout, abPayload); + + +class Session(TdTaskBase): + """ + A USB Test Service (UTS) client session. + """ + + def __init__(self, oTransport, cMsTimeout, cMsIdleFudge, fTryConnect = False): + """ + Construct a UTS session. + + This starts by connecting to the UTS and will enter the signalled state + when connected or the timeout has been reached. + """ + TdTaskBase.__init__(self, utils.getCallerName()); + self.oTransport = oTransport; + self.sStatus = ""; + self.cMsTimeout = 0; + self.fErr = True; # Whether to report errors as error. + self.msStart = 0; + self.oThread = None; + self.fnTask = self.taskDummy; + self.aTaskArgs = None; + self.oTaskRc = None; + self.t3oReply = (None, None, None); + self.fScrewedUpMsgState = False; + self.fTryConnect = fTryConnect; + + if not self.startTask(cMsTimeout, False, "connecting", self.taskConnect, (cMsIdleFudge,)): + raise base.GenError("startTask failed"); + + def __del__(self): + """Make sure to cancel the task when deleted.""" + self.cancelTask(); + + def toString(self): + return '<%s fnTask=%s, aTaskArgs=%s, sStatus=%s, oTaskRc=%s, cMsTimeout=%s,' \ + ' msStart=%s, fTryConnect=%s, fErr=%s, fScrewedUpMsgState=%s, t3oReply=%s oTransport=%s, oThread=%s>' \ + % (TdTaskBase.toString(self), self.fnTask, self.aTaskArgs, self.sStatus, self.oTaskRc, self.cMsTimeout, + self.msStart, self.fTryConnect, self.fErr, self.fScrewedUpMsgState, self.t3oReply, self.oTransport, self.oThread); + + def taskDummy(self): + """Place holder to catch broken state handling.""" + raise Exception(); + + def startTask(self, cMsTimeout, fIgnoreErrors, sStatus, fnTask, aArgs = ()): + """ + Kicks of a new task. + + cMsTimeout: The task timeout in milliseconds. Values less than + 500 ms will be adjusted to 500 ms. This means it is + OK to use negative value. + sStatus: The task status. + fnTask: The method that'll execute the task. + aArgs: Arguments to pass to fnTask. + + Returns True on success, False + error in log on failure. + """ + if not self.cancelTask(): + reporter.maybeErr(not fIgnoreErrors, 'utsclient.Session.startTask: failed to cancel previous task.'); + return False; + + # Change status and make sure we're the + self.lockTask(); + if self.sStatus != "": + self.unlockTask(); + reporter.maybeErr(not fIgnoreErrors, 'utsclient.Session.startTask: race.'); + return False; + self.sStatus = "setup"; + self.oTaskRc = None; + self.t3oReply = (None, None, None); + self.resetTaskLocked(); + self.unlockTask(); + + self.cMsTimeout = max(cMsTimeout, 500); + self.fErr = not fIgnoreErrors; + self.fnTask = fnTask; + self.aTaskArgs = aArgs; + self.oThread = threading.Thread(target=self.taskThread, args=(), name=('UTS-%s' % (sStatus))); + self.oThread.setDaemon(True); # pylint: disable=deprecated-method + self.msStart = base.timestampMilli(); + + self.lockTask(); + self.sStatus = sStatus; + self.unlockTask(); + self.oThread.start(); + + return True; + + def cancelTask(self, fSync = True): + """ + Attempts to cancel any pending tasks. + Returns success indicator (True/False). + """ + self.lockTask(); + + if self.sStatus == "": + self.unlockTask(); + return True; + if self.sStatus == "setup": + self.unlockTask(); + return False; + if self.sStatus == "cancelled": + self.unlockTask(); + return False; + + reporter.log('utsclient: cancelling "%s"...' % (self.sStatus)); + if self.sStatus == 'connecting': + self.oTransport.cancelConnect(); + + self.sStatus = "cancelled"; + oThread = self.oThread; + self.unlockTask(); + + if not fSync: + return False; + + oThread.join(61.0); + + if sys.version_info < (3, 9, 0): + # Removed since Python 3.9. + return oThread.isAlive(); # pylint: disable=no-member + return oThread.is_alive(); + + def taskThread(self): + """ + The task thread function. + This does some housekeeping activities around the real task method call. + """ + if not self.isCancelled(): + try: + fnTask = self.fnTask; + oTaskRc = fnTask(*self.aTaskArgs); + except: + reporter.fatalXcpt('taskThread', 15); + oTaskRc = None; + else: + reporter.log('taskThread: cancelled already'); + + self.lockTask(); + + reporter.log('taskThread: signalling task with status "%s", oTaskRc=%s' % (self.sStatus, oTaskRc)); + self.oTaskRc = oTaskRc; + self.oThread = None; + self.sStatus = ''; + self.signalTaskLocked(); + + self.unlockTask(); + return None; + + def isCancelled(self): + """Internal method for checking if the task has been cancelled.""" + self.lockTask(); + sStatus = self.sStatus; + self.unlockTask(); + if sStatus == "cancelled": + return True; + return False; + + def hasTimedOut(self): + """Internal method for checking if the task has timed out or not.""" + cMsLeft = self.getMsLeft(); + if cMsLeft <= 0: + return True; + return False; + + def getMsLeft(self, cMsMin = 0, cMsMax = -1): + """Gets the time left until the timeout.""" + cMsElapsed = base.timestampMilli() - self.msStart; + if cMsElapsed < 0: + return cMsMin; + cMsLeft = self.cMsTimeout - cMsElapsed; + if cMsLeft <= cMsMin: + return cMsMin; + if cMsLeft > cMsMax > 0: + return cMsMax + return cMsLeft; + + def recvReply(self, cMsTimeout = None, fNoDataOk = False): + """ + Wrapper for TransportBase.recvMsg that stashes the response away + so the client can inspect it later on. + """ + if cMsTimeout is None: + cMsTimeout = self.getMsLeft(500); + cbMsg, sOpcode, abPayload = self.oTransport.recvMsg(cMsTimeout, fNoDataOk); + self.lockTask(); + self.t3oReply = (cbMsg, sOpcode, abPayload); + self.unlockTask(); + return (cbMsg, sOpcode, abPayload); + + def recvAck(self, fNoDataOk = False): + """ + Receives an ACK or error response from the UTS. + + Returns True on success. + Returns False on timeout or transport error. + Returns (sOpcode, sDetails) tuple on failure. The opcode is stripped + and there are always details of some sort or another. + """ + cbMsg, sOpcode, abPayload = self.recvReply(None, fNoDataOk); + if cbMsg is None: + return False; + sOpcode = sOpcode.strip() + if sOpcode == "ACK": + return True; + return (sOpcode, getSZ(abPayload, 16, sOpcode)); + + def recvAckLogged(self, sCommand, fNoDataOk = False): + """ + Wrapper for recvAck and logging. + Returns True on success (ACK). + Returns False on time, transport error and errors signalled by UTS. + """ + rc = self.recvAck(fNoDataOk); + if rc is not True and not fNoDataOk: + if rc is False: + reporter.maybeErr(self.fErr, 'recvAckLogged: %s transport error' % (sCommand)); + else: + reporter.maybeErr(self.fErr, 'recvAckLogged: %s response was %s: %s' % (sCommand, rc[0], rc[1])); + rc = False; + return rc; + + def recvTrueFalse(self, sCommand): + """ + Receives a TRUE/FALSE response from the UTS. + Returns True on TRUE, False on FALSE and None on error/other (logged). + """ + cbMsg, sOpcode, abPayload = self.recvReply(); + if cbMsg is None: + reporter.maybeErr(self.fErr, 'recvAckLogged: %s transport error' % (sCommand)); + return None; + + sOpcode = sOpcode.strip() + if sOpcode == "TRUE": + return True; + if sOpcode == "FALSE": + return False; + reporter.maybeErr(self.fErr, 'recvAckLogged: %s response was %s: %s' % \ + (sCommand, sOpcode, getSZ(abPayload, 16, sOpcode))); + return None; + + def sendMsg(self, sOpcode, aoPayload = (), cMsTimeout = None): + """ + Wrapper for TransportBase.sendMsg that inserts the correct timeout. + """ + if cMsTimeout is None: + cMsTimeout = self.getMsLeft(500); + return self.oTransport.sendMsg(sOpcode, cMsTimeout, aoPayload); + + def asyncToSync(self, fnAsync, *aArgs): + """ + Wraps an asynchronous task into a synchronous operation. + + Returns False on failure, task return status on success. + """ + rc = fnAsync(*aArgs); + if rc is False: + reporter.log2('asyncToSync(%s): returns False (#1)' % (fnAsync)); + return rc; + + rc = self.waitForTask(self.cMsTimeout + 5000); + if rc is False: + reporter.maybeErrXcpt(self.fErr, 'asyncToSync: waitForTask failed...'); + self.cancelTask(); + #reporter.log2('asyncToSync(%s): returns False (#2)' % (fnAsync, rc)); + return False; + + rc = self.getResult(); + #reporter.log2('asyncToSync(%s): returns %s' % (fnAsync, rc)); + return rc; + + # + # Connection tasks. + # + + def taskConnect(self, cMsIdleFudge): + """Tries to connect to the UTS""" + while not self.isCancelled(): + reporter.log2('taskConnect: connecting ...'); + rc = self.oTransport.connect(self.getMsLeft(500)); + if rc is True: + reporter.log('taskConnect: succeeded'); + return self.taskGreet(cMsIdleFudge); + if rc is None: + reporter.log2('taskConnect: unable to connect'); + return None; + if self.hasTimedOut(): + reporter.log2('taskConnect: timed out'); + if not self.fTryConnect: + reporter.maybeErr(self.fErr, 'taskConnect: timed out'); + return False; + time.sleep(self.getMsLeft(1, 1000) / 1000.0); + if not self.fTryConnect: + reporter.maybeErr(self.fErr, 'taskConnect: cancelled'); + return False; + + def taskGreet(self, cMsIdleFudge): + """Greets the UTS""" + sHostname = socket.gethostname().lower(); + cbFill = 68 - len(sHostname) - 1; + rc = self.sendMsg("HOWDY", ((1 << 16) | 0, 0x1, len(sHostname), sHostname, zeroByteArray(cbFill))); + if rc is True: + rc = self.recvAckLogged("HOWDY", self.fTryConnect); + if rc is True: + while cMsIdleFudge > 0: + cMsIdleFudge -= 1000; + time.sleep(1); + else: + self.oTransport.disconnect(self.fTryConnect); + return rc; + + def taskBye(self): + """Says goodbye to the UTS""" + rc = self.sendMsg("BYE"); + if rc is True: + rc = self.recvAckLogged("BYE"); + self.oTransport.disconnect(); + return rc; + + # + # Gadget tasks. + # + + def taskGadgetCreate(self, iGadgetType, iGadgetAccess, lstCfg = None): + """Creates a new gadget on UTS""" + cCfgItems = 0; + if lstCfg is not None: + cCfgItems = len(lstCfg); + fRc = self.sendMsg("GDGTCRT", (iGadgetType, iGadgetAccess, cCfgItems, 0, cfgListToByteArray(lstCfg))); + if fRc is True: + fRc = self.recvAckLogged("GDGTCRT"); + return fRc; + + def taskGadgetDestroy(self, iGadgetId): + """Destroys the given gadget handle on UTS""" + fRc = self.sendMsg("GDGTDTOR", (iGadgetId, zeroByteArray(12))); + if fRc is True: + fRc = self.recvAckLogged("GDGTDTOR"); + return fRc; + + def taskGadgetConnect(self, iGadgetId): + """Connects the given gadget handle on UTS""" + fRc = self.sendMsg("GDGTCNCT", (iGadgetId, zeroByteArray(12))); + if fRc is True: + fRc = self.recvAckLogged("GDGTCNCT"); + return fRc; + + def taskGadgetDisconnect(self, iGadgetId): + """Disconnects the given gadget handle from UTS""" + fRc = self.sendMsg("GDGTDCNT", (iGadgetId, zeroByteArray(12))); + if fRc is True: + fRc = self.recvAckLogged("GDGTDCNT"); + return fRc; + + # + # Public methods - generic task queries + # + + def isSuccess(self): + """Returns True if the task completed successfully, otherwise False.""" + self.lockTask(); + sStatus = self.sStatus; + oTaskRc = self.oTaskRc; + self.unlockTask(); + if sStatus != "": + return False; + if oTaskRc is False or oTaskRc is None: + return False; + return True; + + def getResult(self): + """ + Returns the result of a completed task. + Returns None if not completed yet or no previous task. + """ + self.lockTask(); + sStatus = self.sStatus; + oTaskRc = self.oTaskRc; + self.unlockTask(); + if sStatus != "": + return None; + return oTaskRc; + + def getLastReply(self): + """ + Returns the last reply three-tuple: cbMsg, sOpcode, abPayload. + Returns a None, None, None three-tuple if there was no last reply. + """ + self.lockTask(); + t3oReply = self.t3oReply; + self.unlockTask(); + return t3oReply; + + # + # Public methods - connection. + # + + def asyncDisconnect(self, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a disconnect task. + + Returns True on success, False on failure (logged). + + The task returns True on success and False on failure. + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "bye", self.taskBye); + + def syncDisconnect(self, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncDisconnect, cMsTimeout, fIgnoreErrors); + + # + # Public methods - gadget API + # + + def asyncGadgetCreate(self, iGadgetType, iGadgetAccess, lstCfg = None, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a gadget create task. + + Returns True on success, False on failure (logged). + + The task returns True on success and False on failure. + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "GadgetCreate", self.taskGadgetCreate, \ + (iGadgetType, iGadgetAccess, lstCfg)); + + def syncGadgetCreate(self, iGadgetType, iGadgetAccess, lstCfg = None, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncGadgetCreate, iGadgetType, iGadgetAccess, lstCfg, cMsTimeout, fIgnoreErrors); + + def asyncGadgetDestroy(self, iGadgetId, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a gadget destroy task. + + Returns True on success, False on failure (logged). + + The task returns True on success and False on failure. + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "GadgetDestroy", self.taskGadgetDestroy, \ + (iGadgetId, )); + + def syncGadgetDestroy(self, iGadgetId, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncGadgetDestroy, iGadgetId, cMsTimeout, fIgnoreErrors); + + def asyncGadgetConnect(self, iGadgetId, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a gadget connect task. + + Returns True on success, False on failure (logged). + + The task returns True on success and False on failure. + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "GadgetConnect", self.taskGadgetConnect, \ + (iGadgetId, )); + + def syncGadgetConnect(self, iGadgetId, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncGadgetConnect, iGadgetId, cMsTimeout, fIgnoreErrors); + + def asyncGadgetDisconnect(self, iGadgetId, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a gadget disconnect task. + + Returns True on success, False on failure (logged). + + The task returns True on success and False on failure. + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "GadgetDisconnect", self.taskGadgetDisconnect, \ + (iGadgetId, )); + + def syncGadgetDisconnect(self, iGadgetId, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncGadgetDisconnect, iGadgetId, cMsTimeout, fIgnoreErrors); + + +class TransportTcp(TransportBase): + """ + TCP transport layer for the UTS client session class. + """ + + def __init__(self, sHostname, uPort): + """ + Save the parameters. The session will call us back to make the + connection later on its worker thread. + """ + TransportBase.__init__(self, utils.getCallerName()); + self.sHostname = sHostname; + self.uPort = uPort if uPort is not None else 6042; + self.oSocket = None; + self.oWakeupW = None; + self.oWakeupR = None; + self.fConnectCanceled = False; + self.fIsConnecting = False; + self.oCv = threading.Condition(); + self.abReadAhead = array.array('B'); + + def toString(self): + return '<%s sHostname=%s, uPort=%s, oSocket=%s,'\ + ' fConnectCanceled=%s, fIsConnecting=%s, oCv=%s, abReadAhead=%s>' \ + % (TransportBase.toString(self), self.sHostname, self.uPort, self.oSocket, + self.fConnectCanceled, self.fIsConnecting, self.oCv, self.abReadAhead); + + def __isInProgressXcpt(self, oXcpt): + """ In progress exception? """ + try: + if isinstance(oXcpt, socket.error): + try: + if oXcpt[0] == errno.EINPROGRESS: + return True; + except: pass; + try: + if oXcpt[0] == errno.EWOULDBLOCK: + return True; + if utils.getHostOs() == 'win' and oXcpt[0] == errno.WSAEWOULDBLOCK: # pylint: disable=no-member + return True; + except: pass; + except: + pass; + return False; + + def __isWouldBlockXcpt(self, oXcpt): + """ Would block exception? """ + try: + if isinstance(oXcpt, socket.error): + try: + if oXcpt[0] == errno.EWOULDBLOCK: + return True; + except: pass; + try: + if oXcpt[0] == errno.EAGAIN: + return True; + except: pass; + except: + pass; + return False; + + def __isConnectionReset(self, oXcpt): + """ Connection reset by Peer or others. """ + try: + if isinstance(oXcpt, socket.error): + try: + if oXcpt[0] == errno.ECONNRESET: + return True; + except: pass; + try: + if oXcpt[0] == errno.ENETRESET: + return True; + except: pass; + except: + pass; + return False; + + def _closeWakeupSockets(self): + """ Closes the wakup sockets. Caller should own the CV. """ + oWakeupR = self.oWakeupR; + self.oWakeupR = None; + if oWakeupR is not None: + oWakeupR.close(); + + oWakeupW = self.oWakeupW; + self.oWakeupW = None; + if oWakeupW is not None: + oWakeupW.close(); + + return None; + + def cancelConnect(self): + # This is bad stuff. + self.oCv.acquire(); + reporter.log2('TransportTcp::cancelConnect: fIsConnecting=%s oSocket=%s' % (self.fIsConnecting, self.oSocket)); + self.fConnectCanceled = True; + if self.fIsConnecting: + oSocket = self.oSocket; + self.oSocket = None; + if oSocket is not None: + reporter.log2('TransportTcp::cancelConnect: closing the socket'); + oSocket.close(); + + oWakeupW = self.oWakeupW; + self.oWakeupW = None; + if oWakeupW is not None: + reporter.log2('TransportTcp::cancelConnect: wakeup call'); + try: oWakeupW.send('cancelled!\n'); + except: reporter.logXcpt(); + try: oWakeupW.shutdown(socket.SHUT_WR); + except: reporter.logXcpt(); + oWakeupW.close(); + self.oCv.release(); + + def _connectAsClient(self, oSocket, oWakeupR, cMsTimeout): + """ Connects to the UTS server as client. """ + + # Connect w/ timeouts. + rc = None; + try: + oSocket.connect((self.sHostname, self.uPort)); + rc = True; + except socket.error as oXcpt: + iRc = oXcpt.errno; + if self.__isInProgressXcpt(oXcpt): + # Do the actual waiting. + reporter.log2('TransportTcp::connect: operation in progress (%s)...' % (oXcpt,)); + try: + ttRc = select.select([oWakeupR], [oSocket], [oSocket, oWakeupR], cMsTimeout / 1000.0); + if len(ttRc[1]) + len(ttRc[2]) == 0: + raise socket.error(errno.ETIMEDOUT, 'select timed out'); + iRc = oSocket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR); + rc = iRc == 0; + except socket.error as oXcpt2: + iRc = oXcpt2.errno; + except: + iRc = -42; + reporter.fatalXcpt('socket.select() on connect failed'); + + if rc is True: + pass; + elif iRc in (errno.ECONNREFUSED, errno.EHOSTUNREACH, errno.EINTR, errno.ENETDOWN, errno.ENETUNREACH, errno.ETIMEDOUT): + rc = False; # try again. + else: + if iRc != errno.EBADF or not self.fConnectCanceled: + reporter.fatalXcpt('socket.connect((%s,%s)) failed; iRc=%s' % (self.sHostname, self.uPort, iRc)); + reporter.log2('TransportTcp::connect: rc=%s iRc=%s' % (rc, iRc)); + except: + reporter.fatalXcpt('socket.connect((%s,%s)) failed' % (self.sHostname, self.uPort)); + return rc; + + + def connect(self, cMsTimeout): + # Create a non-blocking socket. + reporter.log2('TransportTcp::connect: cMsTimeout=%s sHostname=%s uPort=%s' % (cMsTimeout, self.sHostname, self.uPort)); + try: + oSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0); + except: + reporter.fatalXcpt('socket.socket() failed'); + return None; + try: + oSocket.setblocking(0); + except: + oSocket.close(); + reporter.fatalXcpt('socket.socket() failed'); + return None; + + # Create wakeup socket pair for unix (select doesn't wake up on socket close on Linux). + oWakeupR = None; + oWakeupW = None; + if hasattr(socket, 'socketpair'): + try: (oWakeupR, oWakeupW) = socket.socketpair(); # pylint: disable=no-member + except: reporter.logXcpt('socket.socketpair() failed'); + + # Update the state. + self.oCv.acquire(); + rc = None; + if not self.fConnectCanceled: + self.oSocket = oSocket; + self.oWakeupW = oWakeupW; + self.oWakeupR = oWakeupR; + self.fIsConnecting = True; + self.oCv.release(); + + # Try connect. + if oWakeupR is None: + oWakeupR = oSocket; # Avoid select failure. + rc = self._connectAsClient(oSocket, oWakeupR, cMsTimeout); + oSocket = None; + + # Update the state and cleanup on failure/cancel. + self.oCv.acquire(); + if rc is True and self.fConnectCanceled: + rc = False; + self.fIsConnecting = False; + + if rc is not True: + if self.oSocket is not None: + self.oSocket.close(); + self.oSocket = None; + self._closeWakeupSockets(); + self.oCv.release(); + + reporter.log2('TransportTcp::connect: returning %s' % (rc,)); + return rc; + + def disconnect(self, fQuiet = False): + if self.oSocket is not None: + self.abReadAhead = array.array('B'); + + # Try a shutting down the socket gracefully (draining it). + try: + self.oSocket.shutdown(socket.SHUT_WR); + except: + if not fQuiet: + reporter.error('shutdown(SHUT_WR)'); + try: + self.oSocket.setblocking(0); # just in case it's not set. + sData = "1"; + while sData: + sData = self.oSocket.recv(16384); + except: + pass; + + # Close it. + self.oCv.acquire(); + try: self.oSocket.setblocking(1); + except: pass; + self.oSocket.close(); + self.oSocket = None; + else: + self.oCv.acquire(); + self._closeWakeupSockets(); + self.oCv.release(); + + def sendBytes(self, abBuf, cMsTimeout): + if self.oSocket is None: + reporter.error('TransportTcp.sendBytes: No connection.'); + return False; + + # Try send it all. + try: + cbSent = self.oSocket.send(abBuf); + if cbSent == len(abBuf): + return True; + except Exception as oXcpt: + if not self.__isWouldBlockXcpt(oXcpt): + reporter.errorXcpt('TranportTcp.sendBytes: %s bytes' % (len(abBuf))); + return False; + cbSent = 0; + + # Do a timed send. + msStart = base.timestampMilli(); + while True: + cMsElapsed = base.timestampMilli() - msStart; + if cMsElapsed > cMsTimeout: + reporter.error('TranportTcp.sendBytes: %s bytes timed out (1)' % (len(abBuf))); + break; + + # wait. + try: + ttRc = select.select([], [self.oSocket], [self.oSocket], (cMsTimeout - cMsElapsed) / 1000.0); + if ttRc[2] and not ttRc[1]: + reporter.error('TranportTcp.sendBytes: select returned with exception'); + break; + if not ttRc[1]: + reporter.error('TranportTcp.sendBytes: %s bytes timed out (2)' % (len(abBuf))); + break; + except: + reporter.errorXcpt('TranportTcp.sendBytes: select failed'); + break; + + # Try send more. + try: + cbSent += self.oSocket.send(abBuf[cbSent:]); + if cbSent == len(abBuf): + return True; + except Exception as oXcpt: + if not self.__isWouldBlockXcpt(oXcpt): + reporter.errorXcpt('TranportTcp.sendBytes: %s bytes' % (len(abBuf))); + break; + + return False; + + def __returnReadAheadBytes(self, cb): + """ Internal worker for recvBytes. """ + assert(len(self.abReadAhead) >= cb); + abRet = self.abReadAhead[:cb]; + self.abReadAhead = self.abReadAhead[cb:]; + return abRet; + + def recvBytes(self, cb, cMsTimeout, fNoDataOk): + if self.oSocket is None: + reporter.error('TransportTcp.recvBytes(%s,%s): No connection.' % (cb, cMsTimeout)); + return None; + + # Try read in some more data without bothering with timeout handling first. + if len(self.abReadAhead) < cb: + try: + abBuf = self.oSocket.recv(cb - len(self.abReadAhead)); + if abBuf: + self.abReadAhead.extend(array.array('B', abBuf)); + except Exception as oXcpt: + if not self.__isWouldBlockXcpt(oXcpt): + reporter.errorXcpt('TranportTcp.recvBytes: 0/%s bytes' % (cb,)); + return None; + + if len(self.abReadAhead) >= cb: + return self.__returnReadAheadBytes(cb); + + # Timeout loop. + msStart = base.timestampMilli(); + while True: + cMsElapsed = base.timestampMilli() - msStart; + if cMsElapsed > cMsTimeout: + if not fNoDataOk or self.abReadAhead: + reporter.error('TranportTcp.recvBytes: %s/%s bytes timed out (1)' % (len(self.abReadAhead), cb)); + break; + + # Wait. + try: + ttRc = select.select([self.oSocket], [], [self.oSocket], (cMsTimeout - cMsElapsed) / 1000.0); + if ttRc[2] and not ttRc[0]: + reporter.error('TranportTcp.recvBytes: select returned with exception'); + break; + if not ttRc[0]: + if not fNoDataOk or self.abReadAhead: + reporter.error('TranportTcp.recvBytes: %s/%s bytes timed out (2) fNoDataOk=%s' + % (len(self.abReadAhead), cb, fNoDataOk)); + break; + except: + reporter.errorXcpt('TranportTcp.recvBytes: select failed'); + break; + + # Try read more. + try: + abBuf = self.oSocket.recv(cb - len(self.abReadAhead)); + if not abBuf: + reporter.error('TranportTcp.recvBytes: %s/%s bytes (%s) - connection has been shut down' + % (len(self.abReadAhead), cb, fNoDataOk)); + self.disconnect(); + return None; + + self.abReadAhead.extend(array.array('B', abBuf)); + + except Exception as oXcpt: + reporter.log('recv => exception %s' % (oXcpt,)); + if not self.__isWouldBlockXcpt(oXcpt): + if not fNoDataOk or not self.__isConnectionReset(oXcpt) or self.abReadAhead: + reporter.errorXcpt('TranportTcp.recvBytes: %s/%s bytes (%s)' % (len(self.abReadAhead), cb, fNoDataOk)); + break; + + # Done? + if len(self.abReadAhead) >= cb: + return self.__returnReadAheadBytes(cb); + + #reporter.log('recv => None len(self.abReadAhead) -> %d' % (len(self.abReadAhead), )); + return None; + + def isConnectionOk(self): + if self.oSocket is None: + return False; + try: + ttRc = select.select([], [], [self.oSocket], 0.0); + if ttRc[2]: + return False; + + self.oSocket.send(array.array('B')); # send zero bytes. + except: + return False; + return True; + + def isRecvPending(self, cMsTimeout = 0): + try: + ttRc = select.select([self.oSocket], [], [], cMsTimeout / 1000.0); + if not ttRc[0]: + return False; + except: + pass; + return True; + + +class UsbGadget(object): + """ + USB Gadget control class using the USBT Test Service to talk to the external + board behaving like a USB device. + """ + + def __init__(self): + self.oUtsSession = None; + self.sImpersonation = g_ksGadgetImpersonationInvalid; + self.idGadget = None; + self.iBusId = None; + self.iDevId = None; + self.iUsbIpPort = None; + + def clearImpersonation(self): + """ + Removes the current impersonation of the gadget. + """ + fRc = True; + + if self.idGadget is not None: + fRc = self.oUtsSession.syncGadgetDestroy(self.idGadget); + self.idGadget = None; + self.iBusId = None; + self.iDevId = None; + + return fRc; + + def disconnectUsb(self): + """ + Disconnects the USB gadget from the host. (USB connection not network + connection used for control) + """ + return self.oUtsSession.syncGadgetDisconnect(self.idGadget); + + def connectUsb(self): + """ + Connect the USB gadget to the host. + """ + return self.oUtsSession.syncGadgetConnect(self.idGadget); + + def impersonate(self, sImpersonation, fSuperSpeed = False): + """ + Impersonate a given device. + """ + + # Clear any previous impersonation + self.clearImpersonation(); + self.sImpersonation = sImpersonation; + + fRc = False; + if sImpersonation == g_ksGadgetImpersonationTest: + lstCfg = []; + if fSuperSpeed is True: + lstCfg.append( ('Gadget/SuperSpeed', g_kiGadgetCfgTypeBool, 'true') ); + fDone = self.oUtsSession.syncGadgetCreate(g_kiGadgetTypeTest, g_kiGadgetAccessUsbIp, lstCfg); + if fDone is True and self.oUtsSession.isSuccess(): + # Get the gadget ID. + _, _, abPayload = self.oUtsSession.getLastReply(); + + fRc = True; + self.idGadget = getU32(abPayload, 16); + self.iBusId = getU32(abPayload, 20); + self.iDevId = getU32(abPayload, 24); + else: + reporter.log('Invalid or unsupported impersonation'); + + return fRc; + + def getUsbIpPort(self): + """ + Returns the port the USB/IP server is listening on if requested, + None if USB/IP is not supported. + """ + return self.iUsbIpPort; + + def getGadgetBusAndDevId(self): + """ + Returns the bus ad device ID of the gadget as a tuple. + """ + return (self.iBusId, self.iDevId); + + def connectTo(self, cMsTimeout, sHostname, uPort = None, fUsbIpSupport = True, cMsIdleFudge = 0, fTryConnect = False): + """ + Connects to the specified target device. + Returns True on Success. + Returns False otherwise. + """ + fRc = True; + + # @todo + if fUsbIpSupport is False: + return False; + + reporter.log2('openTcpSession(%s, %s, %s, %s)' % \ + (cMsTimeout, sHostname, uPort, cMsIdleFudge)); + try: + oTransport = TransportTcp(sHostname, uPort); + self.oUtsSession = Session(oTransport, cMsTimeout, cMsIdleFudge, fTryConnect); + + if self.oUtsSession is not None: + fDone = self.oUtsSession.waitForTask(30*1000); + reporter.log('connect: waitForTask -> %s, result %s' % (fDone, self.oUtsSession.getResult())); + if fDone is True and self.oUtsSession.isSuccess(): + # Parse the reply. + _, _, abPayload = self.oUtsSession.getLastReply(); + + if getU32(abPayload, 20) is g_kiGadgetAccessUsbIp: + fRc = True; + self.iUsbIpPort = getU32(abPayload, 24); + else: + reporter.log('Gadget doesn\'t support access over USB/IP despite being requested'); + fRc = False; + else: + fRc = False; + else: + fRc = False; + except: + reporter.errorXcpt(None, 15); + return False; + + return fRc; + + def disconnectFrom(self): + """ + Disconnects from the target device. + """ + fRc = True; + + self.clearImpersonation(); + if self.oUtsSession is not None: + fRc = self.oUtsSession.syncDisconnect(); + + return fRc; diff --git a/src/VBox/ValidationKit/utils/Makefile.kmk b/src/VBox/ValidationKit/utils/Makefile.kmk new file mode 100644 index 00000000..522c99da --- /dev/null +++ b/src/VBox/ValidationKit/utils/Makefile.kmk @@ -0,0 +1,78 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Utilities. +# + +# +# Copyright (C) 2010-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Include sub-makefiles. +# +include $(PATH_SUB_CURRENT)/TestExecServ/Makefile.kmk +include $(PATH_SUB_CURRENT)/audio/Makefile.kmk +ifeq ($(KBUILD_TARGET),win) + include $(PATH_SUB_CURRENT)/clipboard/Makefile.kmk +endif +include $(PATH_SUB_CURRENT)/cpu/Makefile.kmk +include $(PATH_SUB_CURRENT)/fs/Makefile.kmk +include $(PATH_SUB_CURRENT)/misc/Makefile.kmk +include $(PATH_SUB_CURRENT)/network/Makefile.kmk +ifeq ($(KBUILD_TARGET),win) + include $(PATH_SUB_CURRENT)/nt/Makefile.kmk +endif +include $(PATH_SUB_CURRENT)/serial/Makefile.kmk +include $(PATH_SUB_CURRENT)/storage/Makefile.kmk +ifeq ($(KBUILD_TARGET),linux) + include $(PATH_SUB_CURRENT)/usb/Makefile.kmk +endif + +# +# On OS/2 the binaries requires the libc DLLs +# (no official static linking support). +# +INSTALLS.os2 += ValidationKitOs2LibC +ValidationKitOs2LibC_TEMPLATE = VBoxValidationKitR3 +ValidationKitOs2LibC_SOURCES = \ + $(KBUILD_BIN_PATH)/libc06.dll \ + $(KBUILD_BIN_PATH)/libc061.dll \ + $(KBUILD_BIN_PATH)/libc062.dll \ + $(KBUILD_BIN_PATH)/libc063.dll \ + $(KBUILD_BIN_PATH)/libc064.dll \ + $(KBUILD_BIN_PATH)/libc065.dll \ + $(KBUILD_BIN_PATH)/libc066.dll + + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/Makefile.kmk b/src/VBox/ValidationKit/utils/TestExecServ/Makefile.kmk new file mode 100644 index 00000000..b1f119a4 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/Makefile.kmk @@ -0,0 +1,87 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - The Basic Remote Execution Service. +# + +# +# Copyright (C) 2010-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +PROGRAMS += TestExecService +TestExecService_TEMPLATE = VBoxValidationKitR3 +ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING +TestExecService_DEFS = \ + KBUILD_TARGET="$(KBUILD_TARGET)" \ + KBUILD_TARGET_ARCH="$(KBUILD_TARGET_ARCH)" +else +TestExecService_DEFS = \ + KBUILD_TARGET=\"$(KBUILD_TARGET)\" \ + KBUILD_TARGET_ARCH=\"$(KBUILD_TARGET_ARCH)\" +endif +TestExecService_SOURCES = \ + TestExecService.cpp \ + TestExecServiceTcp.cpp + +ifn1of ($(KBUILD_TARGET), os2) +TestExecService_SOURCES += \ + TestExecServiceSerial.cpp +endif + +INSTALLS += TestExecServiceFiles +TestExecServiceFiles_TEMPLATE = VBoxValidationKitR3 +TestExecServiceFiles_INST = $(INST_VALIDATIONKIT) +TestExecServiceFiles_SOURCES := \ + vboxtxs-readme.txt + +TestExecServiceFiles_EXEC_SOURCES.linux := \ + $(PATH_SUB_CURRENT)/linux/vboxtxs.sh=>linux/vboxtxs \ + $(PATH_SUB_CURRENT)/linux/vboxtxs.service=>linux/vboxtxs.service \ + $(PATH_SUB_CURRENT)/linux/vboxtxs-nat.sh=>linux/vboxtxs-nat + +TestExecServiceFiles_SOURCES.solaris := \ + $(PATH_SUB_CURRENT)/solaris/vboxtxs.xml=>solaris/vboxtxs.xml \ + $(PATH_SUB_CURRENT)/solaris/vboxtxs-sol10.xml=>solaris/vboxtxs-sol10.xml +TestExecServiceFiles_EXEC_SOURCES.solaris := \ + $(PATH_SUB_CURRENT)/solaris/vboxtxs.sh=>solaris/vboxtxs.sh + +TestExecServiceFiles_SOURCES.win := \ + $(PATH_SUB_CURRENT)/win/vboxtxs.reg=>win/vboxtxs.reg \ + $(PATH_SUB_CURRENT)/win/vboxtxs-nat.reg=>win/vboxtxs-nat.reg +TestExecServiceFiles_EXEC_SOURCES.win := \ + $(PATH_SUB_CURRENT)/win/vboxtxs.cmd=>win/vboxtxs.cmd \ + $(PATH_SUB_CURRENT)/win/vboxtxs-nat.cmd=>win/vboxtxs-nat.cmd + + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/TestExecService.cpp b/src/VBox/ValidationKit/utils/TestExecServ/TestExecService.cpp new file mode 100644 index 00000000..6910d937 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/TestExecService.cpp @@ -0,0 +1,4037 @@ +/* $Id: TestExecService.cpp $ */ +/** @file + * TestExecServ - Basic Remote Execution Service. + */ + +/* + * Copyright (C) 2010-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/alloca.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/buildconfig.h> +#include <iprt/cdrom.h> +#include <iprt/critsect.h> +#include <iprt/crc.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/handle.h> +#include <iprt/initterm.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/pipe.h> +#include <iprt/poll.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/uuid.h> +#include <iprt/zip.h> + +#include <package-generated.h> +#include "product-generated.h" + +#include <VBox/version.h> +#include <VBox/log.h> + +#include "product-generated.h" +#include "TestExecServiceInternal.h" + + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Handle IDs used by txsDoExec for the poll set. + */ +typedef enum TXSEXECHNDID +{ + TXSEXECHNDID_STDIN = 0, + TXSEXECHNDID_STDOUT, + TXSEXECHNDID_STDERR, + TXSEXECHNDID_TESTPIPE, + TXSEXECHNDID_STDIN_WRITABLE, + TXSEXECHNDID_TRANSPORT, + TXSEXECHNDID_THREAD +} TXSEXECHNDID; + + +/** + * For buffering process input supplied by the client. + */ +typedef struct TXSEXECSTDINBUF +{ + /** The mount of buffered data. */ + size_t cb; + /** The current data offset. */ + size_t off; + /** The data buffer. */ + char *pch; + /** The amount of allocated buffer space. */ + size_t cbAllocated; + /** Send further input into the bit bucket (stdin is dead). */ + bool fBitBucket; + /** The CRC-32 for standard input (received part). */ + uint32_t uCrc32; +} TXSEXECSTDINBUF; +/** Pointer to a standard input buffer. */ +typedef TXSEXECSTDINBUF *PTXSEXECSTDINBUF; + +/** + * TXS child process info. + */ +typedef struct TXSEXEC +{ + PCTXSPKTHDR pPktHdr; + RTMSINTERVAL cMsTimeout; + int rcReplySend; + + RTPOLLSET hPollSet; + RTPIPE hStdInW; + RTPIPE hStdOutR; + RTPIPE hStdErrR; + RTPIPE hTestPipeR; + RTPIPE hWakeUpPipeR; + RTTHREAD hThreadWaiter; + + /** @name For the setup phase + * @{ */ + struct StdPipe + { + RTHANDLE hChild; + PRTHANDLE phChild; + } StdIn, + StdOut, + StdErr; + RTPIPE hTestPipeW; + RTENV hEnv; + /** @} */ + + /** For serializating some access. */ + RTCRITSECT CritSect; + /** @name Members protected by the critical section. + * @{ */ + RTPROCESS hProcess; + /** The process status. Only valid when fProcessAlive is cleared. */ + RTPROCSTATUS ProcessStatus; + /** Set when the process is alive, clear when dead. */ + bool volatile fProcessAlive; + /** The end of the pipe that hThreadWaiter writes to. */ + RTPIPE hWakeUpPipeW; + /** @} */ +} TXSEXEC; +/** Pointer to a the TXS child process info. */ +typedef TXSEXEC *PTXSEXEC; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Transport layers. + */ +static const PCTXSTRANSPORT g_apTransports[] = +{ + &g_TcpTransport, +#ifndef RT_OS_OS2 + &g_SerialTransport, +#endif + //&g_FileSysTransport, + //&g_GuestPropTransport, + //&g_TestDevTransport, +}; + +/** The release logger. */ +static PRTLOGGER g_pRelLogger; +/** The select transport layer. */ +static PCTXSTRANSPORT g_pTransport; +/** The scratch path. */ +static char g_szScratchPath[RTPATH_MAX]; +/** The default scratch path. */ +static char g_szDefScratchPath[RTPATH_MAX]; +/** The CD/DVD-ROM path. */ +static char g_szCdRomPath[RTPATH_MAX]; +/** The default CD/DVD-ROM path. */ +static char g_szDefCdRomPath[RTPATH_MAX]; +/** The directory containing the TXS executable. */ +static char g_szTxsDir[RTPATH_MAX]; +/** The current working directory for TXS (doesn't change). */ +static char g_szCwd[RTPATH_MAX]; +/** The operating system short name. */ +static char g_szOsShortName[16]; +/** The CPU architecture short name. */ +static char g_szArchShortName[16]; +/** The combined "OS.arch" name. */ +static char g_szOsDotArchShortName[32]; +/** The combined "OS/arch" name. */ +static char g_szOsSlashArchShortName[32]; +/** The executable suffix. */ +static char g_szExeSuff[8]; +/** The shell script suffix. */ +static char g_szScriptSuff[8]; +/** UUID identifying this TXS instance. This can be used to see if TXS + * has been restarted or not. */ +static RTUUID g_InstanceUuid; +/** Whether to display the output of the child process or not. */ +static bool g_fDisplayOutput = true; +/** Whether to terminate or not. + * @todo implement signals and stuff. */ +static bool volatile g_fTerminate = false; +/** Verbosity level. */ +uint32_t g_cVerbose = 1; + + +/** + * Calculates the checksum value, zero any padding space and send the packet. + * + * @returns IPRT status code. + * @param pPkt The packet to send. Must point to a correctly + * aligned buffer. + */ +static int txsSendPkt(PTXSPKTHDR pPkt) +{ + Assert(pPkt->cb >= sizeof(*pPkt)); + pPkt->uCrc32 = RTCrc32(pPkt->achOpcode, pPkt->cb - RT_UOFFSETOF(TXSPKTHDR, achOpcode)); + if (pPkt->cb != RT_ALIGN_32(pPkt->cb, TXSPKT_ALIGNMENT)) + memset((uint8_t *)pPkt + pPkt->cb, '\0', RT_ALIGN_32(pPkt->cb, TXSPKT_ALIGNMENT) - pPkt->cb); + + Log(("txsSendPkt: cb=%#x opcode=%.8s\n", pPkt->cb, pPkt->achOpcode)); + Log2(("%.*Rhxd\n", RT_MIN(pPkt->cb, 256), pPkt)); + int rc = g_pTransport->pfnSendPkt(pPkt); + while (RT_UNLIKELY(rc == VERR_INTERRUPTED) && !g_fTerminate) + rc = g_pTransport->pfnSendPkt(pPkt); + if (RT_FAILURE(rc)) + Log(("txsSendPkt: rc=%Rrc\n", rc)); + + return rc; +} + +/** + * Sends a babble reply and disconnects the client (if applicable). + * + * @param pszOpcode The BABBLE opcode. + */ +static void txsReplyBabble(const char *pszOpcode) +{ + TXSPKTHDR Reply; + Reply.cb = sizeof(Reply); + Reply.uCrc32 = 0; + memcpy(Reply.achOpcode, pszOpcode, sizeof(Reply.achOpcode)); + + g_pTransport->pfnBabble(&Reply, 20*1000); +} + +/** + * Receive and validate a packet. + * + * Will send bable responses to malformed packets that results in a error status + * code. + * + * @returns IPRT status code. + * @param ppPktHdr Where to return the packet on success. Free + * with RTMemFree. + * @param fAutoRetryOnFailure Whether to retry on error. + */ +static int txsRecvPkt(PPTXSPKTHDR ppPktHdr, bool fAutoRetryOnFailure) +{ + for (;;) + { + PTXSPKTHDR pPktHdr; + int rc = g_pTransport->pfnRecvPkt(&pPktHdr); + if (RT_SUCCESS(rc)) + { + /* validate the packet. */ + if ( pPktHdr->cb >= sizeof(TXSPKTHDR) + && pPktHdr->cb < TXSPKT_MAX_SIZE) + { + Log2(("txsRecvPkt: pPktHdr=%p cb=%#x crc32=%#x opcode=%.8s\n" + "%.*Rhxd\n", + pPktHdr, pPktHdr->cb, pPktHdr->uCrc32, pPktHdr->achOpcode, RT_MIN(pPktHdr->cb, 256), pPktHdr)); + uint32_t uCrc32Calc = pPktHdr->uCrc32 != 0 + ? RTCrc32(&pPktHdr->achOpcode[0], pPktHdr->cb - RT_UOFFSETOF(TXSPKTHDR, achOpcode)) + : 0; + if (pPktHdr->uCrc32 == uCrc32Calc) + { + AssertCompileMemberSize(TXSPKTHDR, achOpcode, 8); + if ( RT_C_IS_UPPER(pPktHdr->achOpcode[0]) + && RT_C_IS_UPPER(pPktHdr->achOpcode[1]) + && (RT_C_IS_UPPER(pPktHdr->achOpcode[2]) || pPktHdr->achOpcode[2] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[3]) || pPktHdr->achOpcode[3] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[4]) || pPktHdr->achOpcode[4] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[5]) || pPktHdr->achOpcode[5] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[6]) || pPktHdr->achOpcode[6] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[7]) || pPktHdr->achOpcode[7] == ' ') + ) + { + Log(("txsRecvPkt: cb=%#x opcode=%.8s\n", pPktHdr->cb, pPktHdr->achOpcode)); + *ppPktHdr = pPktHdr; + return rc; + } + + rc = VERR_IO_BAD_COMMAND; + } + else + { + Log(("txsRecvPkt: cb=%#x opcode=%.8s crc32=%#x actual=%#x\n", + pPktHdr->cb, pPktHdr->achOpcode, pPktHdr->uCrc32, uCrc32Calc)); + rc = VERR_IO_CRC; + } + } + else + rc = VERR_IO_BAD_LENGTH; + + /* Send babble reply and disconnect the client if the transport is + connection oriented. */ + if (rc == VERR_IO_BAD_LENGTH) + txsReplyBabble("BABBLE L"); + else if (rc == VERR_IO_CRC) + txsReplyBabble("BABBLE C"); + else if (rc == VERR_IO_BAD_COMMAND) + txsReplyBabble("BABBLE O"); + else + txsReplyBabble("BABBLE "); + RTMemFree(pPktHdr); + } + + /* Try again or return failure? */ + if ( g_fTerminate + || rc != VERR_INTERRUPTED + || !fAutoRetryOnFailure + ) + { + Log(("txsRecvPkt: rc=%Rrc\n", rc)); + return rc; + } + } +} + +/** + * Make a simple reply, only status opcode. + * + * @returns IPRT status code of the send. + * @param pReply The reply packet. + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + * @param cbExtra Bytes in addition to the header. + */ +static int txsReplyInternal(PTXSPKTHDR pReply, const char *pszOpcode, size_t cbExtra) +{ + /* copy the opcode, don't be too strict in case of a padding screw up. */ + size_t cchOpcode = strlen(pszOpcode); + if (RT_LIKELY(cchOpcode == sizeof(pReply->achOpcode))) + memcpy(pReply->achOpcode, pszOpcode, sizeof(pReply->achOpcode)); + else + { + Assert(cchOpcode == sizeof(pReply->achOpcode)); + while (cchOpcode > 0 && pszOpcode[cchOpcode - 1] == ' ') + cchOpcode--; + AssertMsgReturn(cchOpcode < sizeof(pReply->achOpcode), ("%d/'%.8s'\n", cchOpcode, pszOpcode), VERR_INTERNAL_ERROR_4); + memcpy(pReply->achOpcode, pszOpcode, cchOpcode); + memset(&pReply->achOpcode[cchOpcode], ' ', sizeof(pReply->achOpcode) - cchOpcode); + } + + pReply->cb = (uint32_t)sizeof(TXSPKTHDR) + (uint32_t)cbExtra; + pReply->uCrc32 = 0; /* (txsSendPkt sets it) */ + + return txsSendPkt(pReply); +} + +/** + * Make a simple reply, only status opcode. + * + * @returns IPRT status code of the send. + * @param pPktHdr The original packet (for future use). + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + */ +static int txsReplySimple(PCTXSPKTHDR pPktHdr, const char *pszOpcode) +{ + TXSPKTHDR Pkt; + NOREF(pPktHdr); + return txsReplyInternal(&Pkt, pszOpcode, 0); +} + +/** + * Acknowledges a packet with success. + * + * @returns IPRT status code of the send. + * @param pPktHdr The original packet (for future use). + */ +static int txsReplyAck(PCTXSPKTHDR pPktHdr) +{ + return txsReplySimple(pPktHdr, "ACK "); +} + +/** + * Replies with a failure. + * + * @returns IPRT status code of the send. + * @param pPktHdr The original packet (for future use). + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + * @param pszDetailFmt Longer description of the problem (format + * string). + * @param va Format arguments. + */ +static int txsReplyFailureV(PCTXSPKTHDR pPktHdr, const char *pszOpcode, const char *pszDetailFmt, va_list va) +{ + NOREF(pPktHdr); + union + { + TXSPKTHDR Hdr; + char ach[256]; + } uPkt; + + size_t cchDetail = RTStrPrintfV(&uPkt.ach[sizeof(TXSPKTHDR)], + sizeof(uPkt) - sizeof(TXSPKTHDR), + pszDetailFmt, va); + return txsReplyInternal(&uPkt.Hdr, pszOpcode, cchDetail + 1); +} + +/** + * Replies with a failure. + * + * @returns IPRT status code of the send. + * @param pPktHdr The original packet (for future use). + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + * @param pszDetailFmt Longer description of the problem (format + * string). + * @param ... Format arguments. + */ +static int txsReplyFailure(PCTXSPKTHDR pPktHdr, const char *pszOpcode, const char *pszDetailFmt, ...) +{ + va_list va; + va_start(va, pszDetailFmt); + int rc = txsReplyFailureV(pPktHdr, pszOpcode, pszDetailFmt, va); + va_end(va); + return rc; +} + +/** + * Replies according to the return code. + * + * @returns IPRT status code of the send. + * @param pPktHdr The packet to reply to. + * @param rcOperation The status code to report. + * @param pszOperationFmt The operation that failed. Typically giving the + * function call with important arguments. + * @param ... Arguments to the format string. + */ +static int txsReplyRC(PCTXSPKTHDR pPktHdr, int rcOperation, const char *pszOperationFmt, ...) +{ + if (RT_SUCCESS(rcOperation)) + return txsReplyAck(pPktHdr); + + char szOperation[128]; + va_list va; + va_start(va, pszOperationFmt); + RTStrPrintfV(szOperation, sizeof(szOperation), pszOperationFmt, va); + va_end(va); + + return txsReplyFailure(pPktHdr, "FAILED ", "%s failed with rc=%Rrc (opcode '%.8s')", + szOperation, rcOperation, pPktHdr->achOpcode); +} + +/** + * Signal a bad packet minum size. + * + * @returns IPRT status code of the send. + * @param pPktHdr The packet to reply to. + * @param cbMin The minimum size. + */ +static int txsReplyBadMinSize(PCTXSPKTHDR pPktHdr, size_t cbMin) +{ + return txsReplyFailure(pPktHdr, "BAD SIZE", "Expected at least %zu bytes, got %u (opcode '%.8s')", + cbMin, pPktHdr->cb, pPktHdr->achOpcode); +} + +/** + * Signal a bad packet exact size. + * + * @returns IPRT status code of the send. + * @param pPktHdr The packet to reply to. + * @param cb The wanted size. + */ +static int txsReplyBadSize(PCTXSPKTHDR pPktHdr, size_t cb) +{ + return txsReplyFailure(pPktHdr, "BAD SIZE", "Expected at %zu bytes, got %u (opcode '%.8s')", + cb, pPktHdr->cb, pPktHdr->achOpcode); +} + +/** + * Deals with a command that isn't implemented yet. + * @returns IPRT status code of the send. + * @param pPktHdr The packet which opcode isn't implemented. + */ +static int txsReplyNotImplemented(PCTXSPKTHDR pPktHdr) +{ + return txsReplyFailure(pPktHdr, "NOT IMPL", "Opcode '%.8s' is not implemented", pPktHdr->achOpcode); +} + +/** + * Deals with a unknown command. + * @returns IPRT status code of the send. + * @param pPktHdr The packet to reply to. + */ +static int txsReplyUnknown(PCTXSPKTHDR pPktHdr) +{ + return txsReplyFailure(pPktHdr, "UNKNOWN ", "Opcode '%.8s' is not known", pPktHdr->achOpcode); +} + +/** + * Replaces a variable with its value. + * + * @returns VINF_SUCCESS or VERR_NO_STR_MEMORY. + * @param ppszNew In/Out. + * @param pcchNew In/Out. (Messed up on failure.) + * @param offVar Variable offset. + * @param cchVar Variable length. + * @param pszValue The value. + * @param cchValue Value length. + */ +static int txsReplaceStringVariable(char **ppszNew, size_t *pcchNew, size_t offVar, size_t cchVar, + const char *pszValue, size_t cchValue) +{ + size_t const cchAfter = *pcchNew - offVar - cchVar; + if (cchVar < cchValue) + { + *pcchNew += cchValue - cchVar; + int rc = RTStrRealloc(ppszNew, *pcchNew + 1); + if (RT_FAILURE(rc)) + return rc; + } + + char *pszNew = *ppszNew; + memmove(&pszNew[offVar + cchValue], &pszNew[offVar + cchVar], cchAfter + 1); + memcpy(&pszNew[offVar], pszValue, cchValue); + return VINF_SUCCESS; +} + +/** + * Replace the variables found in the source string, returning a new string that + * lives on the string heap. + * + * @returns Boolean success indicator. Will reply to the client with all the + * gory detail on failure. + * @param pPktHdr The packet the string relates to. For replying + * on error. + * @param pszSrc The source string. + * @param ppszNew Where to return the new string. + * @param prcSend Where to return the status code of the send on + * failure. + */ +static int txsReplaceStringVariables(PCTXSPKTHDR pPktHdr, const char *pszSrc, char **ppszNew, int *prcSend) +{ + /* Lazy approach that employs memmove. */ + size_t cchNew = strlen(pszSrc); + char *pszNew = RTStrDup(pszSrc); + char *pszDollar = pszNew; + while (pszDollar && (pszDollar = strchr(pszDollar, '$')) != NULL) + { + if (pszDollar[1] == '{') + { + char *pszEnd = strchr(&pszDollar[2], '}'); + if (pszEnd) + { +#define IF_VARIABLE_DO(pszDollar, szVarExpr, pszValue) \ + if ( cchVar == sizeof(szVarExpr) - 1 \ + && !memcmp(pszDollar, szVarExpr, sizeof(szVarExpr) - 1) ) \ + { \ + size_t const cchValue = strlen(pszValue); \ + rc = txsReplaceStringVariable(&pszNew, &cchNew, offDollar, \ + sizeof(szVarExpr) - 1, pszValue, cchValue); \ + offDollar += cchValue; \ + } + int rc; + size_t const cchVar = pszEnd - pszDollar + 1; /* includes "${}" */ + size_t offDollar = pszDollar - pszNew; + IF_VARIABLE_DO(pszDollar, "${CDROM}", g_szCdRomPath) + else IF_VARIABLE_DO(pszDollar, "${SCRATCH}", g_szScratchPath) + else IF_VARIABLE_DO(pszDollar, "${ARCH}", g_szArchShortName) + else IF_VARIABLE_DO(pszDollar, "${OS}", g_szOsShortName) + else IF_VARIABLE_DO(pszDollar, "${OS.ARCH}", g_szOsDotArchShortName) + else IF_VARIABLE_DO(pszDollar, "${OS/ARCH}", g_szOsSlashArchShortName) + else IF_VARIABLE_DO(pszDollar, "${EXESUFF}", g_szExeSuff) + else IF_VARIABLE_DO(pszDollar, "${SCRIPTSUFF}", g_szScriptSuff) + else IF_VARIABLE_DO(pszDollar, "${TXSDIR}", g_szTxsDir) + else IF_VARIABLE_DO(pszDollar, "${CWD}", g_szCwd) + else if ( cchVar >= sizeof("${env.") + 1 + && memcmp(pszDollar, RT_STR_TUPLE("${env.")) == 0) + { + const char *pszEnvVar = pszDollar + 6; + size_t cchValue = 0; + char szValue[RTPATH_MAX]; + *pszEnd = '\0'; + rc = RTEnvGetEx(RTENV_DEFAULT, pszEnvVar, szValue, sizeof(szValue), &cchValue); + if (RT_SUCCESS(rc)) + { + *pszEnd = '}'; + rc = txsReplaceStringVariable(&pszNew, &cchNew, offDollar, cchVar, szValue, cchValue); + offDollar += cchValue; + } + else + { + if (rc == VERR_ENV_VAR_NOT_FOUND) + *prcSend = txsReplyFailure(pPktHdr, "UNKN VAR", "Environment variable '%s' encountered in '%s'", + pszEnvVar, pszSrc); + else + *prcSend = txsReplyFailure(pPktHdr, "FAILDENV", + "RTEnvGetEx(,'%s',,,) failed with %Rrc (opcode '%.8s')", + pszEnvVar, rc, pPktHdr->achOpcode); + RTStrFree(pszNew); + *ppszNew = NULL; + return false; + } + } + else + { + RTStrFree(pszNew); + *prcSend = txsReplyFailure(pPktHdr, "UNKN VAR", "Unknown variable '%.*s' encountered in '%s'", + cchVar, pszDollar, pszSrc); + *ppszNew = NULL; + return false; + } + pszDollar = &pszNew[offDollar]; + + if (RT_FAILURE(rc)) + { + RTStrFree(pszNew); + *prcSend = txsReplyRC(pPktHdr, rc, "RTStrRealloc"); + *ppszNew = NULL; + return false; + } +#undef IF_VARIABLE_DO + } + } + /* Undo dollar escape sequences: $$ -> $ */ + else if (pszDollar[1] == '$') + { + size_t cchLeft = cchNew - (&pszDollar[1] - pszNew); + memmove(pszDollar, &pszDollar[1], cchLeft); + pszDollar[cchLeft] = '\0'; + cchNew -= 1; + } + else /* No match, move to next char to avoid endless looping. */ + pszDollar++; + } + + *ppszNew = pszNew; + *prcSend = VINF_SUCCESS; + return true; +} + +/** + * Checks if the string is valid and returns the expanded version. + * + * @returns true if valid, false if invalid. + * @param pPktHdr The packet being unpacked. + * @param pszArgName The argument name. + * @param psz Pointer to the string within pPktHdr. + * @param ppszExp Where to return the expanded string. Must be + * freed by calling RTStrFree(). + * @param ppszNext Where to return the pointer to the next field. + * If NULL, then we assume this string is at the + * end of the packet and will make sure it has the + * advertised length. + * @param prcSend Where to return the status code of the send on + * failure. + */ +static bool txsIsStringValid(PCTXSPKTHDR pPktHdr, const char *pszArgName, const char *psz, + char **ppszExp, const char **ppszNext, int *prcSend) +{ + *ppszExp = NULL; + if (ppszNext) + *ppszNext = NULL; + + size_t const off = psz - (const char *)pPktHdr; + if (pPktHdr->cb <= off) + { + *prcSend = txsReplyFailure(pPktHdr, "STR MISS", "Missing string argument '%s' in '%.8s'", + pszArgName, pPktHdr->achOpcode); + return false; + } + + size_t const cchMax = pPktHdr->cb - off; + const char *pszEnd = RTStrEnd(psz, cchMax); + if (!pszEnd) + { + *prcSend = txsReplyFailure(pPktHdr, "STR TERM", "The string argument '%s' in '%.8s' is unterminated", + pszArgName, pPktHdr->achOpcode); + return false; + } + + if (!ppszNext && (size_t)(pszEnd - psz) != cchMax - 1) + { + *prcSend = txsReplyFailure(pPktHdr, "STR SHRT", "The string argument '%s' in '%.8s' is shorter than advertised", + pszArgName, pPktHdr->achOpcode); + return false; + } + + if (!txsReplaceStringVariables(pPktHdr, psz, ppszExp, prcSend)) + return false; + if (ppszNext) + *ppszNext = pszEnd + 1; + return true; +} + +/** + * Validates a packet with a single string after the header. + * + * @returns true if valid, false if invalid. + * @param pPktHdr The packet. + * @param pszArgName The argument name. + * @param ppszExp Where to return the string pointer. Variables + * will be replaced and it must therefore be freed + * by calling RTStrFree(). + * @param prcSend Where to return the status code of the send on + * failure. + */ +static bool txsIsStringPktValid(PCTXSPKTHDR pPktHdr, const char *pszArgName, char **ppszExp, int *prcSend) +{ + if (pPktHdr->cb < sizeof(TXSPKTHDR) + 2) + { + *ppszExp = NULL; + *prcSend = txsReplyBadMinSize(pPktHdr, sizeof(TXSPKTHDR) + 2); + return false; + } + + return txsIsStringValid(pPktHdr, pszArgName, (const char *)(pPktHdr + 1), ppszExp, NULL, prcSend); +} + +/** + * Checks if the two opcodes match. + * + * @returns true on match, false on mismatch. + * @param pPktHdr The packet header. + * @param pszOpcode2 The opcode we're comparing with. Does not have + * to be the whole 8 chars long. + */ +DECLINLINE(bool) txsIsSameOpcode(PCTXSPKTHDR pPktHdr, const char *pszOpcode2) +{ + if (pPktHdr->achOpcode[0] != pszOpcode2[0]) + return false; + if (pPktHdr->achOpcode[1] != pszOpcode2[1]) + return false; + + unsigned i = 2; + while ( i < RT_SIZEOFMEMB(TXSPKTHDR, achOpcode) + && pszOpcode2[i] != '\0') + { + if (pPktHdr->achOpcode[i] != pszOpcode2[i]) + break; + i++; + } + + if ( i < RT_SIZEOFMEMB(TXSPKTHDR, achOpcode) + && pszOpcode2[i] == '\0') + { + while ( i < RT_SIZEOFMEMB(TXSPKTHDR, achOpcode) + && pPktHdr->achOpcode[i] == ' ') + i++; + } + + return i == RT_SIZEOFMEMB(TXSPKTHDR, achOpcode); +} + +/** + * Used by txsDoGetFile to wait for a reply ACK from the client. + * + * @returns VINF_SUCCESS on ACK, VERR_GENERAL_FAILURE on NACK, + * VERR_NET_NOT_CONNECTED on unknown response (sending a bable reply), + * or whatever txsRecvPkt returns. + * @param pPktHdr The original packet (for future use). + */ +static int txsWaitForAck(PCTXSPKTHDR pPktHdr) +{ + NOREF(pPktHdr); + /** @todo timeout? */ + PTXSPKTHDR pReply; + int rc = txsRecvPkt(&pReply, false /*fAutoRetryOnFailure*/); + if (RT_SUCCESS(rc)) + { + if (txsIsSameOpcode(pReply, "ACK")) + rc = VINF_SUCCESS; + else if (txsIsSameOpcode(pReply, "NACK")) + rc = VERR_GENERAL_FAILURE; + else + { + txsReplyBabble("BABBLE "); + rc = VERR_NET_NOT_CONNECTED; + } + RTMemFree(pReply); + } + return rc; +} + +/** + * Expands the variables in the string and sends it back to the host. + * + * @returns IPRT status code from send. + * @param pPktHdr The expand string packet. + */ +static int txsDoExpandString(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszExpanded; + if (!txsIsStringPktValid(pPktHdr, "string", &pszExpanded, &rc)) + return rc; + + struct + { + TXSPKTHDR Hdr; + char szString[_64K]; + char abPadding[TXSPKT_ALIGNMENT]; + } Pkt; + + size_t const cbExpanded = strlen(pszExpanded) + 1; + if (cbExpanded <= sizeof(Pkt.szString)) + { + memcpy(Pkt.szString, pszExpanded, cbExpanded); + rc = txsReplyInternal(&Pkt.Hdr, "STRING ", cbExpanded); + } + else + { + memcpy(Pkt.szString, pszExpanded, sizeof(Pkt.szString)); + Pkt.szString[0] = '\0'; + rc = txsReplyInternal(&Pkt.Hdr, "SHORTSTR", sizeof(Pkt.szString)); + } + + RTStrFree(pszExpanded); + return rc; +} + +/** + * Packs a tar file / directory. + * + * @returns IPRT status code from send. + * @param pPktHdr The pack file packet. + */ +static int txsDoPackFile(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszFile = NULL; + char *pszSource = NULL; + + /* Packet cursor. */ + const char *pch = (const char *)(pPktHdr + 1); + + if (txsIsStringValid(pPktHdr, "file", pch, &pszFile, &pch, &rc)) + { + if (txsIsStringValid(pPktHdr, "source", pch, &pszSource, &pch, &rc)) + { + char *pszSuff = RTPathSuffix(pszFile); + + const char *apszArgs[7]; + unsigned cArgs = 0; + + apszArgs[cArgs++] = "RTTar"; + apszArgs[cArgs++] = "--create"; + + apszArgs[cArgs++] = "--file"; + apszArgs[cArgs++] = pszFile; + + if ( pszSuff + && ( !RTStrICmp(pszSuff, ".gz") + || !RTStrICmp(pszSuff, ".tgz"))) + apszArgs[cArgs++] = "--gzip"; + + apszArgs[cArgs++] = pszSource; + + RTEXITCODE rcExit = RTZipTarCmd(cArgs, (char **)apszArgs); + if (rcExit != RTEXITCODE_SUCCESS) + rc = VERR_GENERAL_FAILURE; /** @todo proper return code. */ + else + rc = VINF_SUCCESS; + + rc = txsReplyRC(pPktHdr, rc, "RTZipTarCmd(\"%s\",\"%s\")", + pszFile, pszSource); + + RTStrFree(pszSource); + } + RTStrFree(pszFile); + } + + return rc; +} + +/** + * Unpacks a tar file. + * + * @returns IPRT status code from send. + * @param pPktHdr The unpack file packet. + */ +static int txsDoUnpackFile(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszFile = NULL; + char *pszDirectory = NULL; + + /* Packet cursor. */ + const char *pch = (const char *)(pPktHdr + 1); + + if (txsIsStringValid(pPktHdr, "file", pch, &pszFile, &pch, &rc)) + { + if (txsIsStringValid(pPktHdr, "directory", pch, &pszDirectory, &pch, &rc)) + { + char *pszSuff = RTPathSuffix(pszFile); + + const char *apszArgs[7]; + unsigned cArgs = 0; + + apszArgs[cArgs++] = "RTTar"; + apszArgs[cArgs++] = "--extract"; + + apszArgs[cArgs++] = "--file"; + apszArgs[cArgs++] = pszFile; + + apszArgs[cArgs++] = "--directory"; + apszArgs[cArgs++] = pszDirectory; + + if ( pszSuff + && ( !RTStrICmp(pszSuff, ".gz") + || !RTStrICmp(pszSuff, ".tgz"))) + apszArgs[cArgs++] = "--gunzip"; + + RTEXITCODE rcExit = RTZipTarCmd(cArgs, (char **)apszArgs); + if (rcExit != RTEXITCODE_SUCCESS) + rc = VERR_GENERAL_FAILURE; /** @todo proper return code. */ + else + rc = VINF_SUCCESS; + + rc = txsReplyRC(pPktHdr, rc, "RTZipTarCmd(\"%s\",\"%s\")", + pszFile, pszDirectory); + + RTStrFree(pszDirectory); + } + RTStrFree(pszFile); + } + + return rc; +} + +/** + * Downloads a file to the client. + * + * The transfer sends a stream of DATA packets (0 or more) and ends it all with + * a ACK packet. If an error occurs, a FAILURE packet is sent and the transfer + * aborted. + * + * @returns IPRT status code from send. + * @param pPktHdr The get file packet. + */ +static int txsDoGetFile(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "file", &pszPath, &rc)) + return rc; + + RTFILE hFile; + rc = RTFileOpen(&hFile, pszPath, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN); + if (RT_SUCCESS(rc)) + { + uint32_t uMyCrc32 = RTCrc32Start(); + for (;;) + { + struct + { + TXSPKTHDR Hdr; + uint32_t uCrc32; + char ab[_64K]; + char abPadding[TXSPKT_ALIGNMENT]; + } Pkt; + size_t cbRead; + rc = RTFileRead(hFile, &Pkt.ab[0], _64K, &cbRead); + if (RT_FAILURE(rc) || cbRead == 0) + { + if (rc == VERR_EOF || (RT_SUCCESS(rc) && cbRead == 0)) + { + Pkt.uCrc32 = RTCrc32Finish(uMyCrc32); + rc = txsReplyInternal(&Pkt.Hdr, "DATA EOF", sizeof(uint32_t)); + if (RT_SUCCESS(rc)) + rc = txsWaitForAck(&Pkt.Hdr); + } + else + rc = txsReplyRC(pPktHdr, rc, "RTFileRead"); + break; + } + + uMyCrc32 = RTCrc32Process(uMyCrc32, &Pkt.ab[0], cbRead); + Pkt.uCrc32 = RTCrc32Finish(uMyCrc32); + rc = txsReplyInternal(&Pkt.Hdr, "DATA ", cbRead + sizeof(uint32_t)); + if (RT_FAILURE(rc)) + break; + rc = txsWaitForAck(&Pkt.Hdr); + if (RT_FAILURE(rc)) + break; + } + + RTFileClose(hFile); + } + else + rc = txsReplyRC(pPktHdr, rc, "RTFileOpen(,\"%s\",)", pszPath); + + RTStrFree(pszPath); + return rc; +} + +/** + * Copies a file from the source to the destination locally. + * + * @returns IPRT status code from send. + * @param pPktHdr The copy file packet. + */ +static int txsDoCopyFile(PCTXSPKTHDR pPktHdr) +{ + /* After the packet header follows a 32-bit file mode, + * the remainder of the packet are two zero terminated paths. */ + size_t const cbMin = sizeof(TXSPKTHDR) + sizeof(RTFMODE) + 2; + if (pPktHdr->cb < cbMin) + return txsReplyBadMinSize(pPktHdr, cbMin); + + /* Packet cursor. */ + const char *pch = (const char *)(pPktHdr + 1); + + int rc; + + RTFMODE const fMode = *(RTFMODE const *)pch; + + char *pszSrc; + if (txsIsStringValid(pPktHdr, "source", (const char *)pch + sizeof(RTFMODE), &pszSrc, &pch, &rc)) + { + char *pszDst; + if (txsIsStringValid(pPktHdr, "dest", pch, &pszDst, NULL /* Check for string termination */, &rc)) + { + rc = RTFileCopy(pszSrc, pszDst); + if (RT_SUCCESS(rc)) + { + if (fMode) /* Do we need to set the file mode? */ + { + rc = RTPathSetMode(pszDst, fMode); + if (RT_FAILURE(rc)) + rc = txsReplyRC(pPktHdr, rc, "RTPathSetMode(\"%s\", %#x)", pszDst, fMode); + } + + if (RT_SUCCESS(rc)) + rc = txsReplyAck(pPktHdr); + } + else + rc = txsReplyRC(pPktHdr, rc, "RTFileCopy"); + RTStrFree(pszDst); + } + + RTStrFree(pszSrc); + } + + return rc; +} + +/** + * Uploads a file from the client. + * + * The transfer sends a stream of DATA packets (0 or more) and ends it all with + * a DATA EOF packet. We ACK each of these, so that if a write error occurs we + * can abort the transfer straight away. + * + * @returns IPRT status code from send. + * @param pPktHdr The put file packet. + * @param fHasMode Set if the packet starts with a mode field. + */ +static int txsDoPutFile(PCTXSPKTHDR pPktHdr, bool fHasMode) +{ + int rc; + RTFMODE fMode = 0; + char *pszPath; + if (!fHasMode) + { + if (!txsIsStringPktValid(pPktHdr, "file", &pszPath, &rc)) + return rc; + } + else + { + /* After the packet header follows a mode mask and the remainder of + the packet is the zero terminated file name. */ + size_t const cbMin = sizeof(TXSPKTHDR) + sizeof(RTFMODE) + 2; + if (pPktHdr->cb < cbMin) + return txsReplyBadMinSize(pPktHdr, cbMin); + if (!txsIsStringValid(pPktHdr, "file", (const char *)(pPktHdr + 1) + sizeof(RTFMODE), &pszPath, NULL, &rc)) + return rc; + fMode = *(RTFMODE const *)(pPktHdr + 1); + fMode <<= RTFILE_O_CREATE_MODE_SHIFT; + fMode &= RTFILE_O_CREATE_MODE_MASK; + } + + RTFILE hFile; + rc = RTFileOpen(&hFile, pszPath, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE | fMode); + if (RT_SUCCESS(rc)) + { + bool fSuccess = false; + rc = txsReplyAck(pPktHdr); + if (RT_SUCCESS(rc)) + { + if (fMode) + RTFileSetMode(hFile, fMode); + + /* + * Read client command packets and process them. + */ + uint32_t uMyCrc32 = RTCrc32Start(); + for (;;) + { + PTXSPKTHDR pDataPktHdr; + rc = txsRecvPkt(&pDataPktHdr, false /*fAutoRetryOnFailure*/); + if (RT_FAILURE(rc)) + break; + + if (txsIsSameOpcode(pDataPktHdr, "DATA")) + { + size_t const cbMin = sizeof(TXSPKTHDR) + sizeof(uint32_t); + if (pDataPktHdr->cb >= cbMin) + { + size_t cbData = pDataPktHdr->cb - cbMin; + const void *pvData = (const char *)pDataPktHdr + cbMin; + uint32_t uCrc32 = *(uint32_t const *)(pDataPktHdr + 1); + + uMyCrc32 = RTCrc32Process(uMyCrc32, pvData, cbData); + if (RTCrc32Finish(uMyCrc32) == uCrc32) + { + rc = RTFileWrite(hFile, pvData, cbData, NULL); + if (RT_SUCCESS(rc)) + { + rc = txsReplyAck(pDataPktHdr); + RTMemFree(pDataPktHdr); + continue; + } + + rc = txsReplyRC(pDataPktHdr, rc, "RTFileWrite"); + } + else + rc = txsReplyFailure(pDataPktHdr, "BAD DCRC", "mycrc=%#x your=%#x", uMyCrc32, uCrc32); + } + else + rc = txsReplyBadMinSize(pPktHdr, cbMin); + } + else if (txsIsSameOpcode(pDataPktHdr, "DATA EOF")) + { + if (pDataPktHdr->cb == sizeof(TXSPKTHDR) + sizeof(uint32_t)) + { + uint32_t uCrc32 = *(uint32_t const *)(pDataPktHdr + 1); + if (RTCrc32Finish(uMyCrc32) == uCrc32) + { + rc = txsReplyAck(pDataPktHdr); + fSuccess = RT_SUCCESS(rc); + } + else + rc = txsReplyFailure(pDataPktHdr, "BAD DCRC", "mycrc=%#x your=%#x", uMyCrc32, uCrc32); + } + else + rc = txsReplyAck(pDataPktHdr); + } + else if (txsIsSameOpcode(pDataPktHdr, "ABORT")) + rc = txsReplyAck(pDataPktHdr); + else + rc = txsReplyFailure(pDataPktHdr, "UNKNOWN ", "Opcode '%.8s' is not known or not recognized during PUT FILE", pDataPktHdr->achOpcode); + RTMemFree(pDataPktHdr); + break; + } + } + + RTFileClose(hFile); + + /* + * Delete the file on failure. + */ + if (!fSuccess) + RTFileDelete(pszPath); + } + else + rc = txsReplyRC(pPktHdr, rc, "RTFileOpen(,\"%s\",)", pszPath); + + RTStrFree(pszPath); + return rc; +} + +/** + * List the entries in the specified directory. + * + * @returns IPRT status code from send. + * @param pPktHdr The list packet. + */ +static int txsDoList(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "dir", &pszPath, &rc)) + return rc; + + rc = txsReplyNotImplemented(pPktHdr); + + RTStrFree(pszPath); + return rc; +} + +/** + * Worker for STAT and LSTAT for packing down the file info reply. + * + * @returns IPRT status code from send. + * @param pInfo The info to pack down. + */ +static int txsReplyObjInfo(PCRTFSOBJINFO pInfo) +{ + struct + { + TXSPKTHDR Hdr; + int64_t cbObject; + int64_t cbAllocated; + int64_t nsAccessTime; + int64_t nsModificationTime; + int64_t nsChangeTime; + int64_t nsBirthTime; + uint32_t fMode; + uint32_t uid; + uint32_t gid; + uint32_t cHardLinks; + uint64_t INodeIdDevice; + uint64_t INodeId; + uint64_t Device; + char abPadding[TXSPKT_ALIGNMENT]; + } Pkt; + + Pkt.cbObject = pInfo->cbObject; + Pkt.cbAllocated = pInfo->cbAllocated; + Pkt.nsAccessTime = RTTimeSpecGetNano(&pInfo->AccessTime); + Pkt.nsModificationTime = RTTimeSpecGetNano(&pInfo->ModificationTime); + Pkt.nsChangeTime = RTTimeSpecGetNano(&pInfo->ChangeTime); + Pkt.nsBirthTime = RTTimeSpecGetNano(&pInfo->BirthTime); + Pkt.fMode = pInfo->Attr.fMode; + Pkt.uid = pInfo->Attr.u.Unix.uid; + Pkt.gid = pInfo->Attr.u.Unix.gid; + Pkt.cHardLinks = pInfo->Attr.u.Unix.cHardlinks; + Pkt.INodeIdDevice = pInfo->Attr.u.Unix.INodeIdDevice; + Pkt.INodeId = pInfo->Attr.u.Unix.INodeId; + Pkt.Device = pInfo->Attr.u.Unix.Device; + + return txsReplyInternal(&Pkt.Hdr, "FILEINFO", sizeof(Pkt) - TXSPKT_ALIGNMENT - sizeof(TXSPKTHDR)); +} + +/** + * Get info about a file system object, following all but the symbolic links + * except in the final path component. + * + * @returns IPRT status code from send. + * @param pPktHdr The lstat packet. + */ +static int txsDoLStat(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "path", &pszPath, &rc)) + return rc; + + RTFSOBJINFO Info; + rc = RTPathQueryInfoEx(pszPath, &Info, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc)) + rc = txsReplyObjInfo(&Info); + else + rc = txsReplyRC(pPktHdr, rc, "RTPathQueryInfoEx(\"%s\",,UNIX,ON_LINK)", pszPath); + + RTStrFree(pszPath); + return rc; +} + +/** + * Get info about a file system object, following all symbolic links. + * + * @returns IPRT status code from send. + * @param pPktHdr The stat packet. + */ +static int txsDoStat(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "path", &pszPath, &rc)) + return rc; + + RTFSOBJINFO Info; + rc = RTPathQueryInfoEx(pszPath, &Info, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK); + if (RT_SUCCESS(rc)) + rc = txsReplyObjInfo(&Info); + else + rc = txsReplyRC(pPktHdr, rc, "RTPathQueryInfoEx(\"%s\",,UNIX,FOLLOW_LINK)", pszPath); + + RTStrFree(pszPath); + return rc; +} + +/** + * Checks if the specified path is a symbolic link. + * + * @returns IPRT status code from send. + * @param pPktHdr The issymlnk packet. + */ +static int txsDoIsSymlnk(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "symlink", &pszPath, &rc)) + return rc; + + RTFSOBJINFO Info; + rc = RTPathQueryInfoEx(pszPath, &Info, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc) && RTFS_IS_SYMLINK(Info.Attr.fMode)) + rc = txsReplySimple(pPktHdr, "TRUE "); + else + rc = txsReplySimple(pPktHdr, "FALSE "); + + RTStrFree(pszPath); + return rc; +} + +/** + * Checks if the specified path is a file or not. + * + * If the final path element is a symbolic link to a file, we'll return + * FALSE. + * + * @returns IPRT status code from send. + * @param pPktHdr The isfile packet. + */ +static int txsDoIsFile(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "dir", &pszPath, &rc)) + return rc; + + RTFSOBJINFO Info; + rc = RTPathQueryInfoEx(pszPath, &Info, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc) && RTFS_IS_FILE(Info.Attr.fMode)) + rc = txsReplySimple(pPktHdr, "TRUE "); + else + rc = txsReplySimple(pPktHdr, "FALSE "); + + RTStrFree(pszPath); + return rc; +} + +/** + * Checks if the specified path is a directory or not. + * + * If the final path element is a symbolic link to a directory, we'll return + * FALSE. + * + * @returns IPRT status code from send. + * @param pPktHdr The isdir packet. + */ +static int txsDoIsDir(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "dir", &pszPath, &rc)) + return rc; + + RTFSOBJINFO Info; + rc = RTPathQueryInfoEx(pszPath, &Info, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc) && RTFS_IS_DIRECTORY(Info.Attr.fMode)) + rc = txsReplySimple(pPktHdr, "TRUE "); + else + rc = txsReplySimple(pPktHdr, "FALSE "); + + RTStrFree(pszPath); + return rc; +} + +/** + * Changes the owner of a file, directory or symbolic link. + * + * @returns IPRT status code from send. + * @param pPktHdr The chmod packet. + */ +static int txsDoChOwn(PCTXSPKTHDR pPktHdr) +{ +#ifdef RT_OS_WINDOWS + return txsReplyNotImplemented(pPktHdr); +#else + /* After the packet header follows a 32-bit UID and 32-bit GID, while the + remainder of the packet is the zero terminated path. */ + size_t const cbMin = sizeof(TXSPKTHDR) + sizeof(RTFMODE) + 2; + if (pPktHdr->cb < cbMin) + return txsReplyBadMinSize(pPktHdr, cbMin); + + int rc; + char *pszPath; + if (!txsIsStringValid(pPktHdr, "path", (const char *)(pPktHdr + 1) + sizeof(uint32_t) * 2, &pszPath, NULL, &rc)) + return rc; + + uint32_t uid = ((uint32_t const *)(pPktHdr + 1))[0]; + uint32_t gid = ((uint32_t const *)(pPktHdr + 1))[1]; + + rc = RTPathSetOwnerEx(pszPath, uid, gid, RTPATH_F_ON_LINK); + + rc = txsReplyRC(pPktHdr, rc, "RTPathSetOwnerEx(\"%s\", %u, %u)", pszPath, uid, gid); + RTStrFree(pszPath); + return rc; +#endif +} + +/** + * Changes the mode of a file or directory. + * + * @returns IPRT status code from send. + * @param pPktHdr The chmod packet. + */ +static int txsDoChMod(PCTXSPKTHDR pPktHdr) +{ + /* After the packet header follows a mode mask and the remainder of + the packet is the zero terminated file name. */ + size_t const cbMin = sizeof(TXSPKTHDR) + sizeof(RTFMODE) + 2; + if (pPktHdr->cb < cbMin) + return txsReplyBadMinSize(pPktHdr, cbMin); + + int rc; + char *pszPath; + if (!txsIsStringValid(pPktHdr, "path", (const char *)(pPktHdr + 1) + sizeof(RTFMODE), &pszPath, NULL, &rc)) + return rc; + + RTFMODE fMode = *(RTFMODE const *)(pPktHdr + 1); + + rc = RTPathSetMode(pszPath, fMode); + + rc = txsReplyRC(pPktHdr, rc, "RTPathSetMode(\"%s\", %o)", pszPath, fMode); + RTStrFree(pszPath); + return rc; +} + +/** + * Removes a directory tree. + * + * @returns IPRT status code from send. + * @param pPktHdr The rmtree packet. + */ +static int txsDoRmTree(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "dir", &pszPath, &rc)) + return rc; + + rc = RTDirRemoveRecursive(pszPath, 0 /*fFlags*/); + + rc = txsReplyRC(pPktHdr, rc, "RTDirRemoveRecusive(\"%s\",0)", pszPath); + RTStrFree(pszPath); + return rc; +} + +/** + * Removes a symbolic link. + * + * @returns IPRT status code from send. + * @param pPktHdr The rmsymlink packet. + */ +static int txsDoRmSymlnk(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "symlink", &pszPath, &rc)) + return rc; + + rc = RTSymlinkDelete(pszPath, 0); + + rc = txsReplyRC(pPktHdr, rc, "RTSymlinkDelete(\"%s\")", pszPath); + RTStrFree(pszPath); + return rc; +} + +/** + * Removes a file. + * + * @returns IPRT status code from send. + * @param pPktHdr The rmfile packet. + */ +static int txsDoRmFile(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "file", &pszPath, &rc)) + return rc; + + rc = RTFileDelete(pszPath); + + rc = txsReplyRC(pPktHdr, rc, "RTFileDelete(\"%s\")", pszPath); + RTStrFree(pszPath); + return rc; +} + +/** + * Removes a directory. + * + * @returns IPRT status code from send. + * @param pPktHdr The rmdir packet. + */ +static int txsDoRmDir(PCTXSPKTHDR pPktHdr) +{ + int rc; + char *pszPath; + if (!txsIsStringPktValid(pPktHdr, "dir", &pszPath, &rc)) + return rc; + + rc = RTDirRemove(pszPath); + + rc = txsReplyRC(pPktHdr, rc, "RTDirRemove(\"%s\")", pszPath); + RTStrFree(pszPath); + return rc; +} + +/** + * Creates a symbolic link. + * + * @returns IPRT status code from send. + * @param pPktHdr The mksymlnk packet. + */ +static int txsDoMkSymlnk(PCTXSPKTHDR pPktHdr) +{ + return txsReplyNotImplemented(pPktHdr); +} + +/** + * Creates a directory and all its parents. + * + * @returns IPRT status code from send. + * @param pPktHdr The mkdir -p packet. + */ +static int txsDoMkDrPath(PCTXSPKTHDR pPktHdr) +{ + /* The same format as the MKDIR command. */ + if (pPktHdr->cb < sizeof(TXSPKTHDR) + sizeof(RTFMODE) + 2) + return txsReplyBadMinSize(pPktHdr, sizeof(TXSPKTHDR) + sizeof(RTFMODE) + 2); + + int rc; + char *pszPath; + if (!txsIsStringValid(pPktHdr, "dir", (const char *)(pPktHdr + 1) + sizeof(RTFMODE), &pszPath, NULL, &rc)) + return rc; + + RTFMODE fMode = *(RTFMODE const *)(pPktHdr + 1); + + rc = RTDirCreateFullPathEx(pszPath, fMode, RTDIRCREATE_FLAGS_IGNORE_UMASK); + + rc = txsReplyRC(pPktHdr, rc, "RTDirCreateFullPath(\"%s\", %#x)", pszPath, fMode); + RTStrFree(pszPath); + return rc; +} + +/** + * Creates a directory. + * + * @returns IPRT status code from send. + * @param pPktHdr The mkdir packet. + */ +static int txsDoMkDir(PCTXSPKTHDR pPktHdr) +{ + /* After the packet header follows a mode mask and the remainder of + the packet is the zero terminated directory name. */ + size_t const cbMin = sizeof(TXSPKTHDR) + sizeof(RTFMODE) + 2; + if (pPktHdr->cb < cbMin) + return txsReplyBadMinSize(pPktHdr, cbMin); + + int rc; + char *pszPath; + if (!txsIsStringValid(pPktHdr, "dir", (const char *)(pPktHdr + 1) + sizeof(RTFMODE), &pszPath, NULL, &rc)) + return rc; + + RTFMODE fMode = *(RTFMODE const *)(pPktHdr + 1); + rc = RTDirCreate(pszPath, fMode, RTDIRCREATE_FLAGS_IGNORE_UMASK); + + rc = txsReplyRC(pPktHdr, rc, "RTDirCreate(\"%s\", %#x)", pszPath, fMode); + RTStrFree(pszPath); + return rc; +} + +/** + * Cleans up the scratch area. + * + * @returns IPRT status code from send. + * @param pPktHdr The shutdown packet. + */ +static int txsDoCleanup(PCTXSPKTHDR pPktHdr) +{ + int rc = RTDirRemoveRecursive(g_szScratchPath, RTDIRRMREC_F_CONTENT_ONLY); + return txsReplyRC(pPktHdr, rc, "RTDirRemoveRecursive(\"%s\", CONTENT_ONLY)", g_szScratchPath); +} + +/** + * Ejects the specified DVD/CD drive. + * + * @returns IPRT status code from send. + * @param pPktHdr The eject packet. + */ +static int txsDoCdEject(PCTXSPKTHDR pPktHdr) +{ + /* After the packet header follows a uint32_t ordinal. */ + size_t const cbExpected = sizeof(TXSPKTHDR) + sizeof(uint32_t); + if (pPktHdr->cb != cbExpected) + return txsReplyBadSize(pPktHdr, cbExpected); + uint32_t iOrdinal = *(uint32_t const *)(pPktHdr + 1); + + RTCDROM hCdrom; + int rc = RTCdromOpenByOrdinal(iOrdinal, RTCDROM_O_CONTROL, &hCdrom); + if (RT_FAILURE(rc)) + return txsReplyRC(pPktHdr, rc, "RTCdromOpenByOrdinal(%u, RTCDROM_O_CONTROL, )", iOrdinal); + rc = RTCdromEject(hCdrom, true /*fForce*/); + RTCdromRelease(hCdrom); + + return txsReplyRC(pPktHdr, rc, "RTCdromEject(ord=%u, fForce=true)", iOrdinal); +} + +/** + * Common worker for txsDoShutdown and txsDoReboot. + * + * @returns IPRT status code from send. + * @param pPktHdr The reboot packet. + * @param fAction Which action to take. + */ +static int txsCommonShutdownReboot(PCTXSPKTHDR pPktHdr, uint32_t fAction) +{ + /* + * We ACK the reboot & shutdown before actually performing them, then we + * terminate the transport layer. + * + * This is to make sure the client isn't stuck with a dead connection. The + * transport layer termination also make sure we won't accept new + * connections in case the client is too eager to reconnect to a rebooted + * test victim. On the down side, we cannot easily report RTSystemShutdown + * failures failures this way. But the client can kind of figure it out by + * reconnecting and seeing that our UUID was unchanged. + */ + int rc; + if (pPktHdr->cb != sizeof(TXSPKTHDR)) + return txsReplyBadSize(pPktHdr, sizeof(TXSPKTHDR)); + g_pTransport->pfnNotifyReboot(); + rc = txsReplyAck(pPktHdr); + RTThreadSleep(2560); /* fudge factor */ + g_pTransport->pfnTerm(); + + /* + * Do the job, if it fails we'll restart the transport layer. + */ +#if 0 + rc = VINF_SUCCESS; +#else + rc = RTSystemShutdown(0 /*cMsDelay*/, + fAction | RTSYSTEM_SHUTDOWN_PLANNED | RTSYSTEM_SHUTDOWN_FORCE, + "Test Execution Service"); +#endif + if (RT_SUCCESS(rc)) + { + RTMsgInfo(fAction == RTSYSTEM_SHUTDOWN_REBOOT ? "Rebooting...\n" : "Shutting down...\n"); + g_fTerminate = true; + } + else + { + RTMsgError("RTSystemShutdown w/ fAction=%#x failed: %Rrc", fAction, rc); + + int rc2 = g_pTransport->pfnInit(); + if (RT_FAILURE(rc2)) + { + g_fTerminate = true; + rc = rc2; + } + } + return rc; +} + +/** + * Shuts down the machine, powering it off if possible. + * + * @returns IPRT status code from send. + * @param pPktHdr The shutdown packet. + */ +static int txsDoShutdown(PCTXSPKTHDR pPktHdr) +{ + return txsCommonShutdownReboot(pPktHdr, RTSYSTEM_SHUTDOWN_POWER_OFF_HALT); +} + +/** + * Reboots the machine. + * + * @returns IPRT status code from send. + * @param pPktHdr The reboot packet. + */ +static int txsDoReboot(PCTXSPKTHDR pPktHdr) +{ + return txsCommonShutdownReboot(pPktHdr, RTSYSTEM_SHUTDOWN_REBOOT); +} + +/** + * Verifies and acknowledges a "UUID" request. + * + * @returns IPRT status code. + * @param pPktHdr The UUID packet. + */ +static int txsDoUuid(PCTXSPKTHDR pPktHdr) +{ + if (pPktHdr->cb != sizeof(TXSPKTHDR)) + return txsReplyBadSize(pPktHdr, sizeof(TXSPKTHDR)); + + struct + { + TXSPKTHDR Hdr; + char szUuid[RTUUID_STR_LENGTH]; + char abPadding[TXSPKT_ALIGNMENT]; + } Pkt; + + int rc = RTUuidToStr(&g_InstanceUuid, Pkt.szUuid, sizeof(Pkt.szUuid)); + if (RT_FAILURE(rc)) + return txsReplyRC(pPktHdr, rc, "RTUuidToStr"); + return txsReplyInternal(&Pkt.Hdr, "ACK UUID", strlen(Pkt.szUuid) + 1); +} + +/** + * Verifies and acknowledges a "BYE" request. + * + * @returns IPRT status code. + * @param pPktHdr The bye packet. + */ +static int txsDoBye(PCTXSPKTHDR pPktHdr) +{ + int rc; + if (pPktHdr->cb == sizeof(TXSPKTHDR)) + rc = txsReplyAck(pPktHdr); + else + rc = txsReplyBadSize(pPktHdr, sizeof(TXSPKTHDR)); + g_pTransport->pfnNotifyBye(); + return rc; +} + +/** + * Verifies and acknowledges a "VER" request. + * + * @returns IPRT status code. + * @param pPktHdr The version packet. + */ +static int txsDoVer(PCTXSPKTHDR pPktHdr) +{ + if (pPktHdr->cb != sizeof(TXSPKTHDR)) + return txsReplyBadSize(pPktHdr, sizeof(TXSPKTHDR)); + + struct + { + TXSPKTHDR Hdr; + char szVer[96]; + char abPadding[TXSPKT_ALIGNMENT]; + } Pkt; + + if (RTStrPrintf2(Pkt.szVer, sizeof(Pkt.szVer), "%s r%s %s.%s (%s %s)", + RTBldCfgVersion(), RTBldCfgRevisionStr(), KBUILD_TARGET, KBUILD_TARGET_ARCH, __DATE__, __TIME__) > 0) + { + return txsReplyInternal(&Pkt.Hdr, "ACK VER ", strlen(Pkt.szVer) + 1); + } + + return txsReplyRC(pPktHdr, VERR_BUFFER_OVERFLOW, "RTStrPrintf2"); +} + +/** + * Verifies and acknowledges a "HOWDY" request. + * + * @returns IPRT status code. + * @param pPktHdr The howdy packet. + */ +static int txsDoHowdy(PCTXSPKTHDR pPktHdr) +{ + if (pPktHdr->cb != sizeof(TXSPKTHDR)) + return txsReplyBadSize(pPktHdr, sizeof(TXSPKTHDR)); + int rc = txsReplyAck(pPktHdr); + if (RT_SUCCESS(rc)) + { + g_pTransport->pfnNotifyHowdy(); + RTDirRemoveRecursive(g_szScratchPath, RTDIRRMREC_F_CONTENT_ONLY); + } + return rc; +} + +/** + * Replies according to the return code. + * + * @returns rcOperation and pTxsExec->rcReplySend. + * @param pTxsExec The TXSEXEC instance. + * @param rcOperation The status code to report. + * @param pszOperationFmt The operation that failed. Typically giving the + * function call with important arguments. + * @param ... Arguments to the format string. + */ +static int txsExecReplyRC(PTXSEXEC pTxsExec, int rcOperation, const char *pszOperationFmt, ...) +{ + AssertStmt(RT_FAILURE_NP(rcOperation), rcOperation = VERR_IPE_UNEXPECTED_INFO_STATUS); + + char szOperation[128]; + va_list va; + va_start(va, pszOperationFmt); + RTStrPrintfV(szOperation, sizeof(szOperation), pszOperationFmt, va); + va_end(va); + + pTxsExec->rcReplySend = txsReplyFailure(pTxsExec->pPktHdr, "FAILED ", + "%s failed with rc=%Rrc (opcode '%.8s')", + szOperation, rcOperation, pTxsExec->pPktHdr->achOpcode); + return rcOperation; +} + + +/** + * Sends the process exit status reply to the TXS client. + * + * @returns IPRT status code of the send. + * @param pTxsExec The TXSEXEC instance. + * @param fProcessAlive Whether the process is still alive (against our + * will). + * @param fProcessTimedOut Whether the process timed out. + * @param MsProcessKilled When the process was killed, UINT64_MAX if not. + */ +static int txsExecSendExitStatus(PTXSEXEC pTxsExec, bool fProcessAlive, bool fProcessTimedOut, uint64_t MsProcessKilled) +{ + int rc; + if ( fProcessTimedOut && !fProcessAlive && MsProcessKilled != UINT64_MAX) + { + rc = txsReplySimple(pTxsExec->pPktHdr, "PROC TOK"); + if (g_fDisplayOutput) + RTPrintf("txs: Process timed out and was killed\n"); + } + else if (fProcessTimedOut && fProcessAlive && MsProcessKilled != UINT64_MAX) + { + rc = txsReplySimple(pTxsExec->pPktHdr, "PROC TOA"); + if (g_fDisplayOutput) + RTPrintf("txs: Process timed out and was not killed successfully\n"); + } + else if (g_fTerminate && (fProcessAlive || MsProcessKilled != UINT64_MAX)) + rc = txsReplySimple(pTxsExec->pPktHdr, "PROC DWN"); + else if (fProcessAlive) + { + rc = txsReplyFailure(pTxsExec->pPktHdr, "PROC DOO", "Doofus! process is alive when it should not"); + AssertFailed(); + } + else if (MsProcessKilled != UINT64_MAX) + { + rc = txsReplyFailure(pTxsExec->pPktHdr, "PROC DOO", "Doofus! process has been killed when it should not"); + AssertFailed(); + } + else if ( pTxsExec->ProcessStatus.enmReason == RTPROCEXITREASON_NORMAL + && pTxsExec->ProcessStatus.iStatus == 0) + { + rc = txsReplySimple(pTxsExec->pPktHdr, "PROC OK "); + if (g_fDisplayOutput) + RTPrintf("txs: Process exited with status: 0\n"); + } + else if (pTxsExec->ProcessStatus.enmReason == RTPROCEXITREASON_NORMAL) + { + rc = txsReplyFailure(pTxsExec->pPktHdr, "PROC NOK", "%d", pTxsExec->ProcessStatus.iStatus); + if (g_fDisplayOutput) + RTPrintf("txs: Process exited with status: %d\n", pTxsExec->ProcessStatus.iStatus); + } + else if (pTxsExec->ProcessStatus.enmReason == RTPROCEXITREASON_SIGNAL) + { + rc = txsReplyFailure(pTxsExec->pPktHdr, "PROC SIG", "%d", pTxsExec->ProcessStatus.iStatus); + if (g_fDisplayOutput) + RTPrintf("txs: Process exited with status: signal %d\n", pTxsExec->ProcessStatus.iStatus); + } + else if (pTxsExec->ProcessStatus.enmReason == RTPROCEXITREASON_ABEND) + { + rc = txsReplyFailure(pTxsExec->pPktHdr, "PROC ABD", ""); + if (g_fDisplayOutput) + RTPrintf("txs: Process exited with status: abend\n"); + } + else + { + rc = txsReplyFailure(pTxsExec->pPktHdr, "PROC DOO", "enmReason=%d iStatus=%d", + pTxsExec->ProcessStatus.enmReason, pTxsExec->ProcessStatus.iStatus); + AssertMsgFailed(("enmReason=%d iStatus=%d", pTxsExec->ProcessStatus.enmReason, pTxsExec->ProcessStatus.iStatus)); + } + return rc; +} + +/** + * Handle pending output data or error on standard out, standard error or the + * test pipe. + * + * @returns IPRT status code from client send. + * @param hPollSet The polling set. + * @param fPollEvt The event mask returned by RTPollNoResume. + * @param phPipeR The pipe handle. + * @param puCrc32 The current CRC-32 of the stream. (In/Out) + * @param enmHndId The handle ID. + * @param pszOpcode The opcode for the data upload. + * + * @todo Put the last 4 parameters into a struct! + */ +static int txsDoExecHlpHandleOutputEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, PRTPIPE phPipeR, + uint32_t *puCrc32, TXSEXECHNDID enmHndId, const char *pszOpcode) +{ + Log(("txsDoExecHlpHandleOutputEvent: %s fPollEvt=%#x\n", pszOpcode, fPollEvt)); + + /* + * Try drain the pipe before acting on any errors. + */ + int rc = VINF_SUCCESS; + struct + { + TXSPKTHDR Hdr; + uint32_t uCrc32; + char abBuf[_64K]; + char abPadding[TXSPKT_ALIGNMENT]; + } Pkt; + size_t cbRead; + int rc2 = RTPipeRead(*phPipeR, Pkt.abBuf, sizeof(Pkt.abBuf), &cbRead); + if (RT_SUCCESS(rc2) && cbRead) + { + Log(("Crc32=%#x ", *puCrc32)); + *puCrc32 = RTCrc32Process(*puCrc32, Pkt.abBuf, cbRead); + Log(("cbRead=%#x Crc32=%#x \n", cbRead, *puCrc32)); + Pkt.uCrc32 = RTCrc32Finish(*puCrc32); + if (g_fDisplayOutput) + { + if (enmHndId == TXSEXECHNDID_STDOUT) + RTStrmPrintf(g_pStdErr, "%.*s", cbRead, Pkt.abBuf); + else if (enmHndId == TXSEXECHNDID_STDERR) + RTStrmPrintf(g_pStdErr, "%.*s", cbRead, Pkt.abBuf); + } + + rc = txsReplyInternal(&Pkt.Hdr, pszOpcode, cbRead + sizeof(uint32_t)); + + /* Make sure we go another poll round in case there was too much data + for the buffer to hold. */ + fPollEvt &= RTPOLL_EVT_ERROR; + } + else if (RT_FAILURE(rc2)) + { + fPollEvt |= RTPOLL_EVT_ERROR; + AssertMsg(rc2 == VERR_BROKEN_PIPE, ("%Rrc\n", rc)); + } + + /* + * If an error was raised signalled, + */ + if (fPollEvt & RTPOLL_EVT_ERROR) + { + rc2 = RTPollSetRemove(hPollSet, enmHndId); + AssertRC(rc2); + + rc2 = RTPipeClose(*phPipeR); + AssertRC(rc2); + *phPipeR = NIL_RTPIPE; + } + return rc; +} + +/** + * Try write some more data to the standard input of the child. + * + * @returns IPRT status code. + * @param pStdInBuf The standard input buffer. + * @param hStdInW The standard input pipe. + */ +static int txsDoExecHlpWriteStdIn(PTXSEXECSTDINBUF pStdInBuf, RTPIPE hStdInW) +{ + size_t cbToWrite = pStdInBuf->cb - pStdInBuf->off; + size_t cbWritten; + int rc = RTPipeWrite(hStdInW, &pStdInBuf->pch[pStdInBuf->off], cbToWrite, &cbWritten); + if (RT_SUCCESS(rc)) + { + Assert(cbWritten == cbToWrite); + pStdInBuf->off += cbWritten; + } + return rc; +} + +/** + * Handle an error event on standard input. + * + * @param hPollSet The polling set. + * @param fPollEvt The event mask returned by RTPollNoResume. + * @param phStdInW The standard input pipe handle. + * @param pStdInBuf The standard input buffer. + */ +static void txsDoExecHlpHandleStdInErrorEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, PRTPIPE phStdInW, + PTXSEXECSTDINBUF pStdInBuf) +{ + NOREF(fPollEvt); + int rc2; + if (pStdInBuf->off < pStdInBuf->cb) + { + rc2 = RTPollSetRemove(hPollSet, TXSEXECHNDID_STDIN_WRITABLE); + AssertRC(rc2); + } + + rc2 = RTPollSetRemove(hPollSet, TXSEXECHNDID_STDIN); + AssertRC(rc2); + + rc2 = RTPipeClose(*phStdInW); + AssertRC(rc2); + *phStdInW = NIL_RTPIPE; + + RTMemFree(pStdInBuf->pch); + pStdInBuf->pch = NULL; + pStdInBuf->off = 0; + pStdInBuf->cb = 0; + pStdInBuf->cbAllocated = 0; + pStdInBuf->fBitBucket = true; +} + +/** + * Handle an event indicating we can write to the standard input pipe of the + * child process. + * + * @param hPollSet The polling set. + * @param fPollEvt The event mask returned by RTPollNoResume. + * @param phStdInW The standard input pipe. + * @param pStdInBuf The standard input buffer. + */ +static void txsDoExecHlpHandleStdInWritableEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, PRTPIPE phStdInW, + PTXSEXECSTDINBUF pStdInBuf) +{ + int rc; + if (!(fPollEvt & RTPOLL_EVT_ERROR)) + { + rc = txsDoExecHlpWriteStdIn(pStdInBuf, *phStdInW); + if (RT_FAILURE(rc) && rc != VERR_BAD_PIPE) + { + /** @todo do we need to do something about this error condition? */ + AssertRC(rc); + } + + if (pStdInBuf->off < pStdInBuf->cb) + { + rc = RTPollSetRemove(hPollSet, TXSEXECHNDID_STDIN_WRITABLE); + AssertRC(rc); + } + } + else + txsDoExecHlpHandleStdInErrorEvent(hPollSet, fPollEvt, phStdInW, pStdInBuf); +} + +/** + * Handle a transport event or successful pfnPollIn() call. + * + * @returns IPRT status code from client send. + * @retval VINF_EOF indicates ABORT command. + * + * @param hPollSet The polling set. + * @param fPollEvt The event mask returned by RTPollNoResume. + * @param idPollHnd The handle ID. + * @param phStdInW The standard input pipe. + * @param pStdInBuf The standard input buffer. + */ +static int txsDoExecHlpHandleTransportEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, uint32_t idPollHnd, + PRTPIPE phStdInW, PTXSEXECSTDINBUF pStdInBuf) +{ + /* ASSUMES the transport layer will detect or clear any error condition. */ + NOREF(fPollEvt); NOREF(idPollHnd); + Log(("txsDoExecHlpHandleTransportEvent\n")); + /** @todo Use a callback for this case? */ + + /* + * Read client command packet and process it. + */ + /** @todo Sometimes this hangs on windows because there isn't any data pending. + * We probably get woken up at the wrong time or in the wrong way, i.e. RTPoll() + * is busted for sockets. + * + * Temporary workaround: Poll for input before trying to read it. */ + if (!g_pTransport->pfnPollIn()) + { + Log(("Bad transport event\n")); + RTThreadYield(); + return VINF_SUCCESS; + } + PTXSPKTHDR pPktHdr; + int rc = txsRecvPkt(&pPktHdr, false /*fAutoRetryOnFailure*/); + if (RT_FAILURE(rc)) + return rc; + Log(("Bad transport event\n")); + + /* + * The most common thing here would be a STDIN request with data + * for the child process. + */ + if (txsIsSameOpcode(pPktHdr, "STDIN")) + { + if ( !pStdInBuf->fBitBucket + && pPktHdr->cb >= sizeof(TXSPKTHDR) + sizeof(uint32_t)) + { + uint32_t uCrc32 = *(uint32_t *)(pPktHdr + 1); + const char *pch = (const char *)(pPktHdr + 1) + sizeof(uint32_t); + size_t cb = pPktHdr->cb - sizeof(TXSPKTHDR) - sizeof(uint32_t); + + /* Check the CRC */ + pStdInBuf->uCrc32 = RTCrc32Process(pStdInBuf->uCrc32, pch, cb); + if (RTCrc32Finish(pStdInBuf->uCrc32) == uCrc32) + { + + /* Rewind the buffer if it's empty. */ + size_t cbInBuf = pStdInBuf->cb - pStdInBuf->off; + bool const fAddToSet = cbInBuf == 0; + if (fAddToSet) + pStdInBuf->cb = pStdInBuf->off = 0; + + /* Try and see if we can simply append the data. */ + if (cb + pStdInBuf->cb <= pStdInBuf->cbAllocated) + { + memcpy(&pStdInBuf->pch[pStdInBuf->cb], pch, cb); + pStdInBuf->cb += cb; + rc = txsReplyAck(pPktHdr); + } + else + { + /* Try write a bit or two before we move+realloc the buffer. */ + if (cbInBuf > 0) + txsDoExecHlpWriteStdIn(pStdInBuf, *phStdInW); + + /* Move any buffered data to the front. */ + cbInBuf = pStdInBuf->cb - pStdInBuf->off; + if (cbInBuf == 0) + pStdInBuf->cb = pStdInBuf->off = 0; + else + { + memmove(pStdInBuf->pch, &pStdInBuf->pch[pStdInBuf->off], cbInBuf); + pStdInBuf->cb = cbInBuf; + pStdInBuf->off = 0; + } + + /* Do we need to grow the buffer? */ + if (cb + pStdInBuf->cb > pStdInBuf->cbAllocated) + { + size_t cbAlloc = pStdInBuf->cb + cb; + cbAlloc = RT_ALIGN_Z(cbAlloc, _64K); + void *pvNew = RTMemRealloc(pStdInBuf->pch, cbAlloc); + if (pvNew) + { + pStdInBuf->pch = (char *)pvNew; + pStdInBuf->cbAllocated = cbAlloc; + } + } + + /* Finally, copy the data. */ + if (cb + pStdInBuf->cb <= pStdInBuf->cbAllocated) + { + memcpy(&pStdInBuf->pch[pStdInBuf->cb], pch, cb); + pStdInBuf->cb += cb; + rc = txsReplyAck(pPktHdr); + } + else + rc = txsReplySimple(pPktHdr, "STDINMEM"); + } + + /* + * Flush the buffered data and add/remove the standard input + * handle from the set. + */ + txsDoExecHlpWriteStdIn(pStdInBuf, *phStdInW); + if (fAddToSet && pStdInBuf->off < pStdInBuf->cb) + { + int rc2 = RTPollSetAddPipe(hPollSet, *phStdInW, RTPOLL_EVT_WRITE, TXSEXECHNDID_STDIN_WRITABLE); + AssertRC(rc2); + } + else if (!fAddToSet && pStdInBuf->off >= pStdInBuf->cb) + { + int rc2 = RTPollSetRemove(hPollSet, TXSEXECHNDID_STDIN_WRITABLE); + AssertRC(rc2); + } + } + else + rc = txsReplyFailure(pPktHdr, "STDINCRC", "Invalid CRC checksum expected %#x got %#x", + pStdInBuf->uCrc32, uCrc32); + } + else if (pPktHdr->cb < sizeof(TXSPKTHDR) + sizeof(uint32_t)) + rc = txsReplySimple(pPktHdr, "STDINBAD"); + else + rc = txsReplySimple(pPktHdr, "STDINIGN"); + } + /* + * Marks the end of the stream for stdin. + */ + else if (txsIsSameOpcode(pPktHdr, "STDINEOS")) + { + if (RT_LIKELY(pPktHdr->cb == sizeof(TXSPKTHDR))) + { + /* Close the pipe. */ + txsDoExecHlpHandleStdInErrorEvent(hPollSet, fPollEvt, phStdInW, pStdInBuf); + rc = txsReplyAck(pPktHdr); + } + else + rc = txsReplySimple(pPktHdr, "STDINBAD"); + } + /* + * The only other two requests are connection oriented and we return a error + * code so that we unwind the whole EXEC shebang and start afresh. + */ + else if (txsIsSameOpcode(pPktHdr, "BYE")) + { + rc = txsDoBye(pPktHdr); + if (RT_SUCCESS(rc)) + rc = VERR_NET_NOT_CONNECTED; + } + else if (txsIsSameOpcode(pPktHdr, "HOWDY")) + { + rc = txsDoHowdy(pPktHdr); + if (RT_SUCCESS(rc)) + rc = VERR_NET_NOT_CONNECTED; + } + else if (txsIsSameOpcode(pPktHdr, "ABORT")) + { + rc = txsReplyAck(pPktHdr); + if (RT_SUCCESS(rc)) + rc = VINF_EOF; /* this is but ugly! */ + } + else + rc = txsReplyFailure(pPktHdr, "UNKNOWN ", "Opcode '%.8s' is not known or not recognized during EXEC", pPktHdr->achOpcode); + + RTMemFree(pPktHdr); + return rc; +} + +/** + * Handles the output and input of the process, waits for it finish up. + * + * @returns IPRT status code from reply send. + * @param pTxsExec The TXSEXEC instance. + */ +static int txsDoExecHlp2(PTXSEXEC pTxsExec) +{ + int rc; /* client send. */ + int rc2; + TXSEXECSTDINBUF StdInBuf = { 0, 0, NULL, 0, pTxsExec->hStdInW == NIL_RTPIPE, RTCrc32Start() }; + uint32_t uStdOutCrc32 = RTCrc32Start(); + uint32_t uStdErrCrc32 = uStdOutCrc32; + uint32_t uTestPipeCrc32 = uStdOutCrc32; + uint64_t const MsStart = RTTimeMilliTS(); + bool fProcessTimedOut = false; + uint64_t MsProcessKilled = UINT64_MAX; + RTMSINTERVAL const cMsPollBase = g_pTransport->pfnPollSetAdd || pTxsExec->hStdInW == NIL_RTPIPE + ? RT_MS_5SEC : 100; + RTMSINTERVAL cMsPollCur = 0; + + /* + * Before entering the loop, tell the client that we've started the guest + * and that it's now OK to send input to the process. (This is not the + * final ACK, so the packet header is NULL ... kind of bogus.) + */ + rc = txsReplyAck(NULL); + + /* + * Process input, output, the test pipe and client requests. + */ + while ( RT_SUCCESS(rc) + && RT_UNLIKELY(!g_fTerminate)) + { + /* + * Wait/Process all pending events. + */ + uint32_t idPollHnd; + uint32_t fPollEvt; + Log3(("Calling RTPollNoResume(,%u,)...\n", cMsPollCur)); + rc2 = RTPollNoResume(pTxsExec->hPollSet, cMsPollCur, &fPollEvt, &idPollHnd); + Log3(("RTPollNoResume -> fPollEvt=%#x idPollHnd=%u\n", fPollEvt, idPollHnd)); + if (g_fTerminate) + continue; + cMsPollCur = 0; /* no rest until we've checked everything. */ + + if (RT_SUCCESS(rc2)) + { + switch (idPollHnd) + { + case TXSEXECHNDID_STDOUT: + rc = txsDoExecHlpHandleOutputEvent(pTxsExec->hPollSet, fPollEvt, &pTxsExec->hStdOutR, &uStdOutCrc32, + TXSEXECHNDID_STDOUT, "STDOUT "); + break; + + case TXSEXECHNDID_STDERR: + rc = txsDoExecHlpHandleOutputEvent(pTxsExec->hPollSet, fPollEvt, &pTxsExec->hStdErrR, &uStdErrCrc32, + TXSEXECHNDID_STDERR, "STDERR "); + break; + + case TXSEXECHNDID_TESTPIPE: + rc = txsDoExecHlpHandleOutputEvent(pTxsExec->hPollSet, fPollEvt, &pTxsExec->hTestPipeR, &uTestPipeCrc32, + TXSEXECHNDID_TESTPIPE, "TESTPIPE"); + break; + + case TXSEXECHNDID_STDIN: + txsDoExecHlpHandleStdInErrorEvent(pTxsExec->hPollSet, fPollEvt, &pTxsExec->hStdInW, &StdInBuf); + break; + + case TXSEXECHNDID_STDIN_WRITABLE: + txsDoExecHlpHandleStdInWritableEvent(pTxsExec->hPollSet, fPollEvt, &pTxsExec->hStdInW, &StdInBuf); + break; + + case TXSEXECHNDID_THREAD: + rc2 = RTPollSetRemove(pTxsExec->hPollSet, TXSEXECHNDID_THREAD); AssertRC(rc2); + break; + + default: + rc = txsDoExecHlpHandleTransportEvent(pTxsExec->hPollSet, fPollEvt, idPollHnd, &pTxsExec->hStdInW, + &StdInBuf); + break; + } + if (RT_FAILURE(rc) || rc == VINF_EOF) + break; /* abort command, or client dead or something */ + continue; + } + + /* + * Check for incoming data. + */ + if (g_pTransport->pfnPollIn()) + { + rc = txsDoExecHlpHandleTransportEvent(pTxsExec->hPollSet, 0, UINT32_MAX, &pTxsExec->hStdInW, &StdInBuf); + if (RT_FAILURE(rc) || rc == VINF_EOF) + break; /* abort command, or client dead or something */ + continue; + } + + /* + * If the process has terminated, we're should head out. + */ + if (!ASMAtomicReadBool(&pTxsExec->fProcessAlive)) + break; + + /* + * Check for timed out, killing the process. + */ + uint32_t cMilliesLeft = RT_INDEFINITE_WAIT; + if (pTxsExec->cMsTimeout != RT_INDEFINITE_WAIT) + { + uint64_t u64Now = RTTimeMilliTS(); + uint64_t cMsElapsed = u64Now - MsStart; + if (cMsElapsed >= pTxsExec->cMsTimeout) + { + fProcessTimedOut = true; + if ( MsProcessKilled == UINT64_MAX + || u64Now - MsProcessKilled > RT_MS_1SEC) + { + if ( MsProcessKilled != UINT64_MAX + && u64Now - MsProcessKilled > 20*RT_MS_1MIN) + break; /* give up after 20 mins */ + RTCritSectEnter(&pTxsExec->CritSect); + if (pTxsExec->fProcessAlive) + RTProcTerminate(pTxsExec->hProcess); + RTCritSectLeave(&pTxsExec->CritSect); + MsProcessKilled = u64Now; + continue; + } + cMilliesLeft = RT_MS_10SEC; + } + else + cMilliesLeft = pTxsExec->cMsTimeout - (uint32_t)cMsElapsed; + } + + /* Reset the polling interval since we've done all pending work. */ + cMsPollCur = cMilliesLeft >= cMsPollBase ? cMsPollBase : cMilliesLeft; + } + + /* + * At this point we should hopefully only have to wait 0 ms on the thread + * to release the handle... But if for instance the process refuses to die, + * we'll have to try kill it again. Bothersome. + */ + for (size_t i = 0; i < 22; i++) + { + rc2 = RTThreadWait(pTxsExec->hThreadWaiter, RT_MS_1SEC / 2, NULL); + if (RT_SUCCESS(rc)) + { + pTxsExec->hThreadWaiter = NIL_RTTHREAD; + Assert(!pTxsExec->fProcessAlive); + break; + } + if (i == 0 || i == 10 || i == 15 || i == 18 || i > 20) + { + RTCritSectEnter(&pTxsExec->CritSect); + if (pTxsExec->fProcessAlive) + RTProcTerminate(pTxsExec->hProcess); + RTCritSectLeave(&pTxsExec->CritSect); + } + } + + /* + * If we don't have a client problem (RT_FAILURE(rc) we'll reply to the + * clients exec packet now. + */ + if (RT_SUCCESS(rc)) + rc = txsExecSendExitStatus(pTxsExec, pTxsExec->fProcessAlive, fProcessTimedOut, MsProcessKilled); + + RTMemFree(StdInBuf.pch); + return rc; +} + +/** + * Creates a poll set for the pipes and let the transport layer add stuff to it + * as well. + * + * @returns IPRT status code, reply to client made on error. + * @param pTxsExec The TXSEXEC instance. + */ +static int txsExecSetupPollSet(PTXSEXEC pTxsExec) +{ + int rc = RTPollSetCreate(&pTxsExec->hPollSet); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTPollSetCreate"); + + rc = RTPollSetAddPipe(pTxsExec->hPollSet, pTxsExec->hStdInW, RTPOLL_EVT_ERROR, TXSEXECHNDID_STDIN); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTPollSetAddPipe/stdin"); + + rc = RTPollSetAddPipe(pTxsExec->hPollSet, pTxsExec->hStdOutR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, + TXSEXECHNDID_STDOUT); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTPollSetAddPipe/stdout"); + + rc = RTPollSetAddPipe(pTxsExec->hPollSet, pTxsExec->hStdErrR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, + TXSEXECHNDID_STDERR); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTPollSetAddPipe/stderr"); + + rc = RTPollSetAddPipe(pTxsExec->hPollSet, pTxsExec->hTestPipeR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, + TXSEXECHNDID_TESTPIPE); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTPollSetAddPipe/test"); + + rc = RTPollSetAddPipe(pTxsExec->hPollSet, pTxsExec->hWakeUpPipeR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, + TXSEXECHNDID_THREAD); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTPollSetAddPipe/wakeup"); + + if (g_pTransport->pfnPollSetAdd) + { + rc = g_pTransport->pfnPollSetAdd(pTxsExec->hPollSet, TXSEXECHNDID_TRANSPORT); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "%s->pfnPollSetAdd/stdin", g_pTransport->szName); + } + + return VINF_SUCCESS; +} + +/** + * Thread that calls RTProcWait and signals the main thread when it returns. + * + * The thread is created before the process is started and is waiting for a user + * signal from the main thread before it calls RTProcWait. + * + * @returns VINF_SUCCESS (ignored). + * @param hThreadSelf The thread handle. + * @param pvUser The TXEEXEC structure. + */ +static DECLCALLBACK(int) txsExecWaitThreadProc(RTTHREAD hThreadSelf, void *pvUser) +{ + PTXSEXEC pTxsExec = (PTXSEXEC)pvUser; + + /* Wait for the go ahead... */ + int rc = RTThreadUserWait(hThreadSelf, RT_INDEFINITE_WAIT); AssertRC(rc); + + RTCritSectEnter(&pTxsExec->CritSect); + for (;;) + { + RTCritSectLeave(&pTxsExec->CritSect); + rc = RTProcWaitNoResume(pTxsExec->hProcess, RTPROCWAIT_FLAGS_BLOCK, &pTxsExec->ProcessStatus); + RTCritSectEnter(&pTxsExec->CritSect); + + /* If the pipe is NIL, the destructor wants us to get lost ASAP. */ + if (pTxsExec->hWakeUpPipeW == NIL_RTPIPE) + break; + + if (RT_FAILURE(rc)) + { + rc = RTProcWait(pTxsExec->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &pTxsExec->ProcessStatus); + if (rc == VERR_PROCESS_RUNNING) + continue; + + if (RT_FAILURE(rc)) + { + AssertRC(rc); + pTxsExec->ProcessStatus.iStatus = rc; + pTxsExec->ProcessStatus.enmReason = RTPROCEXITREASON_ABEND; + } + } + + /* The process finished, signal the main thread over the pipe. */ + ASMAtomicWriteBool(&pTxsExec->fProcessAlive, false); + size_t cbIgnored; + RTPipeWrite(pTxsExec->hWakeUpPipeW, "done", 4, &cbIgnored); + RTPipeClose(pTxsExec->hWakeUpPipeW); + pTxsExec->hWakeUpPipeW = NIL_RTPIPE; + break; + } + RTCritSectLeave(&pTxsExec->CritSect); + + return VINF_SUCCESS; +} + +/** + * Sets up the thread that waits for the process to complete. + * + * @returns IPRT status code, reply to client made on error. + * @param pTxsExec The TXSEXEC instance. + */ +static int txsExecSetupThread(PTXSEXEC pTxsExec) +{ + int rc = RTPipeCreate(&pTxsExec->hWakeUpPipeR, &pTxsExec->hWakeUpPipeW, 0 /*fFlags*/); + if (RT_FAILURE(rc)) + { + pTxsExec->hWakeUpPipeR = pTxsExec->hWakeUpPipeW = NIL_RTPIPE; + return txsExecReplyRC(pTxsExec, rc, "RTPipeCreate/wait"); + } + + rc = RTThreadCreate(&pTxsExec->hThreadWaiter, txsExecWaitThreadProc, + pTxsExec, 0 /*cbStack */, RTTHREADTYPE_DEFAULT, + RTTHREADFLAGS_WAITABLE, "TxsProcW"); + if (RT_FAILURE(rc)) + { + pTxsExec->hThreadWaiter = NIL_RTTHREAD; + return txsExecReplyRC(pTxsExec, rc, "RTThreadCreate"); + } + + return VINF_SUCCESS; +} + +/** + * Sets up the test pipe. + * + * @returns IPRT status code, reply to client made on error. + * @param pTxsExec The TXSEXEC instance. + * @param pszTestPipe How to set up the test pipe. + */ +static int txsExecSetupTestPipe(PTXSEXEC pTxsExec, const char *pszTestPipe) +{ + if (strcmp(pszTestPipe, "|")) + return VINF_SUCCESS; + + int rc = RTPipeCreate(&pTxsExec->hTestPipeR, &pTxsExec->hTestPipeW, RTPIPE_C_INHERIT_WRITE); + if (RT_FAILURE(rc)) + { + pTxsExec->hTestPipeR = pTxsExec->hTestPipeW = NIL_RTPIPE; + return txsExecReplyRC(pTxsExec, rc, "RTPipeCreate/test/%s", pszTestPipe); + } + + char szVal[64]; + RTStrPrintf(szVal, sizeof(szVal), "%#llx", (uint64_t)RTPipeToNative(pTxsExec->hTestPipeW)); + rc = RTEnvSetEx(pTxsExec->hEnv, "IPRT_TEST_PIPE", szVal); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTEnvSetEx/test/%s", pszTestPipe); + + return VINF_SUCCESS; +} + +/** + * Sets up the redirection / pipe / nothing for one of the standard handles. + * + * @returns IPRT status code, reply to client made on error. + * @param pTxsExec The TXSEXEC instance. + * @param pszHowTo How to set up this standard handle. + * @param pszStdWhat For what to setup redirection (stdin/stdout/stderr). + * @param fd Which standard handle it is (0 == stdin, 1 == + * stdout, 2 == stderr). + * @param ph The generic handle that @a pph may be set + * pointing to. Always set. + * @param pph Pointer to the RTProcCreateExec argument. + * Always set. + * @param phPipe Where to return the end of the pipe that we + * should service. Always set. + */ +static int txsExecSetupRedir(PTXSEXEC pTxsExec, const char *pszHowTo, const char *pszStdWhat, int fd, PRTHANDLE ph, PRTHANDLE *pph, PRTPIPE phPipe) +{ + ph->enmType = RTHANDLETYPE_PIPE; + ph->u.hPipe = NIL_RTPIPE; + *pph = NULL; + *phPipe = NIL_RTPIPE; + + int rc; + if (!strcmp(pszHowTo, "|")) + { + /* + * Setup a pipe for forwarding to/from the client. + */ + if (fd == 0) + rc = RTPipeCreate(&ph->u.hPipe, phPipe, RTPIPE_C_INHERIT_READ); + else + rc = RTPipeCreate(phPipe, &ph->u.hPipe, RTPIPE_C_INHERIT_WRITE); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTPipeCreate/%s/%s", pszStdWhat, pszHowTo); + ph->enmType = RTHANDLETYPE_PIPE; + *pph = ph; + } + else if (!strcmp(pszHowTo, "/dev/null")) + { + /* + * Redirect to/from /dev/null. + */ + RTFILE hFile; + rc = RTFileOpenBitBucket(&hFile, fd == 0 ? RTFILE_O_READ : RTFILE_O_WRITE); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTFileOpenBitBucket/%s/%s", pszStdWhat, pszHowTo); + + ph->enmType = RTHANDLETYPE_FILE; + ph->u.hFile = hFile; + *pph = ph; + } + else if (*pszHowTo) + { + /* + * Redirect to/from file. + */ + uint32_t fFlags; + if (fd == 0) + fFlags = RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN; + else + { + if (pszHowTo[0] != '>' || pszHowTo[1] != '>') + fFlags = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE; + else + { + /* append */ + pszHowTo += 2; + fFlags = RTFILE_O_WRITE | RTFILE_O_DENY_NONE | RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND; + } + } + + RTFILE hFile; + rc = RTFileOpen(&hFile, pszHowTo, fFlags); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTFileOpen/%s/%s", pszStdWhat, pszHowTo); + + ph->enmType = RTHANDLETYPE_FILE; + ph->u.hFile = hFile; + *pph = ph; + } + else + /* same as parent (us) */ + rc = VINF_SUCCESS; + return rc; +} + +/** + * Create the environment. + * + * @returns IPRT status code, reply to client made on error. + * @param pTxsExec The TXSEXEC instance. + * @param cEnvVars The number of environment variables. + * @param papszEnv The environment variables (var=value). + */ +static int txsExecSetupEnv(PTXSEXEC pTxsExec, uint32_t cEnvVars, const char * const *papszEnv) +{ + /* + * Create the environment. + */ + int rc = RTEnvClone(&pTxsExec->hEnv, RTENV_DEFAULT); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTEnvClone"); + + for (size_t i = 0; i < cEnvVars; i++) + { + rc = RTEnvPutEx(pTxsExec->hEnv, papszEnv[i]); + if (RT_FAILURE(rc)) + return txsExecReplyRC(pTxsExec, rc, "RTEnvPutEx(,'%s')", papszEnv[i]); + } + return VINF_SUCCESS; +} + +/** + * Deletes the TXSEXEC structure and frees the memory backing it. + * + * @param pTxsExec The structure to destroy. + */ +static void txsExecDestroy(PTXSEXEC pTxsExec) +{ + int rc2; + + rc2 = RTEnvDestroy(pTxsExec->hEnv); AssertRC(rc2); + pTxsExec->hEnv = NIL_RTENV; + rc2 = RTPipeClose(pTxsExec->hTestPipeW); AssertRC(rc2); + pTxsExec->hTestPipeW = NIL_RTPIPE; + rc2 = RTHandleClose(pTxsExec->StdErr.phChild); AssertRC(rc2); + pTxsExec->StdErr.phChild = NULL; + rc2 = RTHandleClose(pTxsExec->StdOut.phChild); AssertRC(rc2); + pTxsExec->StdOut.phChild = NULL; + rc2 = RTHandleClose(pTxsExec->StdIn.phChild); AssertRC(rc2); + pTxsExec->StdIn.phChild = NULL; + + rc2 = RTPipeClose(pTxsExec->hTestPipeR); AssertRC(rc2); + pTxsExec->hTestPipeR = NIL_RTPIPE; + rc2 = RTPipeClose(pTxsExec->hStdErrR); AssertRC(rc2); + pTxsExec->hStdErrR = NIL_RTPIPE; + rc2 = RTPipeClose(pTxsExec->hStdOutR); AssertRC(rc2); + pTxsExec->hStdOutR = NIL_RTPIPE; + rc2 = RTPipeClose(pTxsExec->hStdInW); AssertRC(rc2); + pTxsExec->hStdInW = NIL_RTPIPE; + + RTPollSetDestroy(pTxsExec->hPollSet); + pTxsExec->hPollSet = NIL_RTPOLLSET; + + /* + * If the process is still running we're in a bit of a fix... Try kill it, + * although that's potentially racing process termination and reusage of + * the pid. + */ + RTCritSectEnter(&pTxsExec->CritSect); + + RTPipeClose(pTxsExec->hWakeUpPipeW); + pTxsExec->hWakeUpPipeW = NIL_RTPIPE; + RTPipeClose(pTxsExec->hWakeUpPipeR); + pTxsExec->hWakeUpPipeR = NIL_RTPIPE; + + if ( pTxsExec->hProcess != NIL_RTPROCESS + && pTxsExec->fProcessAlive) + RTProcTerminate(pTxsExec->hProcess); + + RTCritSectLeave(&pTxsExec->CritSect); + + int rcThread = VINF_SUCCESS; + if (pTxsExec->hThreadWaiter != NIL_RTTHREAD) + rcThread = RTThreadWait(pTxsExec->hThreadWaiter, 5000, NULL); + if (RT_SUCCESS(rcThread)) + { + pTxsExec->hThreadWaiter = NIL_RTTHREAD; + RTCritSectDelete(&pTxsExec->CritSect); + RTMemFree(pTxsExec); + } + /* else: leak it or RTThreadWait may cause heap corruption later. */ +} + +/** + * Initializes the TXSEXEC structure. + * + * @returns VINF_SUCCESS and non-NULL *ppTxsExec on success, reply send status + * and *ppTxsExec set to NULL on failure. + * @param pPktHdr The exec packet. + * @param cMsTimeout The time parameter. + * @param ppTxsExec Where to return the structure. + */ +static int txsExecCreate(PCTXSPKTHDR pPktHdr, RTMSINTERVAL cMsTimeout, PTXSEXEC *ppTxsExec) +{ + *ppTxsExec = NULL; + + /* + * Allocate the basic resources. + */ + PTXSEXEC pTxsExec = (PTXSEXEC)RTMemAlloc(sizeof(*pTxsExec)); + if (!pTxsExec) + return txsReplyRC(pPktHdr, VERR_NO_MEMORY, "RTMemAlloc(%zu)", sizeof(*pTxsExec)); + int rc = RTCritSectInit(&pTxsExec->CritSect); + if (RT_FAILURE(rc)) + { + RTMemFree(pTxsExec); + return txsReplyRC(pPktHdr, rc, "RTCritSectInit"); + } + + /* + * Initialize the member to NIL values. + */ + pTxsExec->pPktHdr = pPktHdr; + pTxsExec->cMsTimeout = cMsTimeout; + pTxsExec->rcReplySend = VINF_SUCCESS; + + pTxsExec->hPollSet = NIL_RTPOLLSET; + pTxsExec->hStdInW = NIL_RTPIPE; + pTxsExec->hStdOutR = NIL_RTPIPE; + pTxsExec->hStdErrR = NIL_RTPIPE; + pTxsExec->hTestPipeR = NIL_RTPIPE; + pTxsExec->hWakeUpPipeR = NIL_RTPIPE; + pTxsExec->hThreadWaiter = NIL_RTTHREAD; + + pTxsExec->StdIn.phChild = NULL; + pTxsExec->StdOut.phChild = NULL; + pTxsExec->StdErr.phChild = NULL; + pTxsExec->hTestPipeW = NIL_RTPIPE; + pTxsExec->hEnv = NIL_RTENV; + + pTxsExec->hProcess = NIL_RTPROCESS; + pTxsExec->ProcessStatus.iStatus = 254; + pTxsExec->ProcessStatus.enmReason = RTPROCEXITREASON_ABEND; + pTxsExec->fProcessAlive = false; + pTxsExec->hWakeUpPipeW = NIL_RTPIPE; + + *ppTxsExec = pTxsExec; + return VINF_SUCCESS; +} + +/** + * txsDoExec helper that takes over when txsDoExec has expanded the packet. + * + * @returns IPRT status code from send. + * @param pPktHdr The exec packet. + * @param fFlags Flags, reserved for future use. + * @param pszExecName The executable name. + * @param cArgs The argument count. + * @param papszArgs The arguments. + * @param cEnvVars The environment variable count. + * @param papszEnv The environment variables. + * @param pszStdIn How to deal with standard in. + * @param pszStdOut How to deal with standard out. + * @param pszStdErr How to deal with standard err. + * @param pszTestPipe How to deal with the test pipe. + * @param pszUsername The user to run the program as. + * @param cMillies The process time limit in milliseconds. + */ +static int txsDoExecHlp(PCTXSPKTHDR pPktHdr, uint32_t fFlags, const char *pszExecName, + uint32_t cArgs, const char * const *papszArgs, + uint32_t cEnvVars, const char * const *papszEnv, + const char *pszStdIn, const char *pszStdOut, const char *pszStdErr, const char *pszTestPipe, + const char *pszUsername, RTMSINTERVAL cMillies) +{ + int rc2; + RT_NOREF_PV(fFlags); + + /* + * Input validation, filter out things we don't yet support.. + */ + Assert(!fFlags); + if (!*pszExecName) + return txsReplyFailure(pPktHdr, "STR ZERO", "Executable name is empty"); + if (!*pszStdIn) + return txsReplyFailure(pPktHdr, "STR ZERO", "The stdin howto is empty"); + if (!*pszStdOut) + return txsReplyFailure(pPktHdr, "STR ZERO", "The stdout howto is empty"); + if (!*pszStdErr) + return txsReplyFailure(pPktHdr, "STR ZERO", "The stderr howto is empty"); + if (!*pszTestPipe) + return txsReplyFailure(pPktHdr, "STR ZERO", "The testpipe howto is empty"); + if (strcmp(pszTestPipe, "|") && strcmp(pszTestPipe, "/dev/null")) + return txsReplyFailure(pPktHdr, "BAD TSTP", "Only \"|\" and \"/dev/null\" are allowed as testpipe howtos ('%s')", + pszTestPipe); + if (*pszUsername) + return txsReplyFailure(pPktHdr, "NOT IMPL", "Executing as a specific user is not implemented ('%s')", pszUsername); + + /* + * Prepare for process launch. + */ + PTXSEXEC pTxsExec; + int rc = txsExecCreate(pPktHdr, cMillies, &pTxsExec); + if (pTxsExec == NULL) + return rc; + rc = txsExecSetupEnv(pTxsExec, cEnvVars, papszEnv); + if (RT_SUCCESS(rc)) + rc = txsExecSetupRedir(pTxsExec, pszStdIn, "StdIn", 0, &pTxsExec->StdIn.hChild, &pTxsExec->StdIn.phChild, &pTxsExec->hStdInW); + if (RT_SUCCESS(rc)) + rc = txsExecSetupRedir(pTxsExec, pszStdOut, "StdOut", 1, &pTxsExec->StdOut.hChild, &pTxsExec->StdOut.phChild, &pTxsExec->hStdOutR); + if (RT_SUCCESS(rc)) + rc = txsExecSetupRedir(pTxsExec, pszStdErr, "StdErr", 2, &pTxsExec->StdErr.hChild, &pTxsExec->StdErr.phChild, &pTxsExec->hStdErrR); + if (RT_SUCCESS(rc)) + rc = txsExecSetupTestPipe(pTxsExec, pszTestPipe); + if (RT_SUCCESS(rc)) + rc = txsExecSetupThread(pTxsExec); + if (RT_SUCCESS(rc)) + rc = txsExecSetupPollSet(pTxsExec); + if (RT_SUCCESS(rc)) + { + char szPathResolved[RTPATH_MAX + 1]; + rc = RTPathReal(pszExecName, szPathResolved, sizeof(szPathResolved)); + if (RT_SUCCESS(rc)) + { + /* + * Create the process. + */ + if (g_fDisplayOutput) + { + RTPrintf("txs: Executing \"%s\" -> \"%s\": ", pszExecName, szPathResolved); + for (uint32_t i = 0; i < cArgs; i++) + RTPrintf(" \"%s\"", papszArgs[i]); + RTPrintf("\n"); + } + + rc = RTProcCreateEx(szPathResolved, papszArgs, pTxsExec->hEnv, 0 /*fFlags*/, + pTxsExec->StdIn.phChild, pTxsExec->StdOut.phChild, pTxsExec->StdErr.phChild, + *pszUsername ? pszUsername : NULL, NULL, NULL, + &pTxsExec->hProcess); + if (RT_SUCCESS(rc)) + { + ASMAtomicWriteBool(&pTxsExec->fProcessAlive, true); + rc2 = RTThreadUserSignal(pTxsExec->hThreadWaiter); AssertRC(rc2); + + /* + * Close the child ends of any pipes and redirected files. + */ + rc2 = RTHandleClose(pTxsExec->StdIn.phChild); AssertRC(rc2); + pTxsExec->StdIn.phChild = NULL; + rc2 = RTHandleClose(pTxsExec->StdOut.phChild); AssertRC(rc2); + pTxsExec->StdOut.phChild = NULL; + rc2 = RTHandleClose(pTxsExec->StdErr.phChild); AssertRC(rc2); + pTxsExec->StdErr.phChild = NULL; + rc2 = RTPipeClose(pTxsExec->hTestPipeW); AssertRC(rc2); + pTxsExec->hTestPipeW = NIL_RTPIPE; + + /* + * Let another worker function funnel output and input to the + * client as well as the process exit code. + */ + rc = txsDoExecHlp2(pTxsExec); + } + } + + if (RT_FAILURE(rc)) + rc = txsReplyFailure(pPktHdr, "FAILED ", "Executing process \"%s\" failed with %Rrc", + pszExecName, rc); + } + else + rc = pTxsExec->rcReplySend; + txsExecDestroy(pTxsExec); + return rc; +} + +/** + * Execute a program. + * + * @returns IPRT status code from send. + * @param pPktHdr The exec packet. + */ +static int txsDoExec(PCTXSPKTHDR pPktHdr) +{ + /* + * This packet has a lot of parameters, most of which are zero terminated + * strings. The strings used in items 7 thru 10 are either file names, + * "/dev/null" or a pipe char (|). + * + * Packet content: + * 1. Flags reserved for future use (32-bit unsigned). + * 2. The executable name (string). + * 3. The argument count given as a 32-bit unsigned integer. + * 4. The arguments strings. + * 5. The number of environment strings (32-bit unsigned). + * 6. The environment strings (var=val) to apply the environment. + * 7. What to do about standard in (string). + * 8. What to do about standard out (string). + * 9. What to do about standard err (string). + * 10. What to do about the test pipe (string). + * 11. The name of the user to run the program as (string). Empty string + * means running it as the current user. + * 12. Process time limit in milliseconds (32-bit unsigned). Max == no limit. + */ + size_t const cbMin = sizeof(TXSPKTHDR) + + sizeof(uint32_t) /* flags */ + 2 + + sizeof(uint32_t) /* argc */ + 2 /* argv */ + + sizeof(uint32_t) + 0 /* environ */ + + 4 * 1 + + sizeof(uint32_t) /* timeout */; + if (pPktHdr->cb < cbMin) + return txsReplyBadMinSize(pPktHdr, cbMin); + + /* unpack the packet */ + char const *pchEnd = (char const *)pPktHdr + pPktHdr->cb; + char const *pch = (char const *)(pPktHdr + 1); /* cursor */ + + /* 1. flags */ + uint32_t const fFlags = *(uint32_t const *)pch; + pch += sizeof(uint32_t); + if (fFlags != 0) + return txsReplyFailure(pPktHdr, "BAD FLAG", "Invalid EXEC flags %#x, expected 0", fFlags); + + /* 2. exec name */ + int rc; + char *pszExecName = NULL; + if (!txsIsStringValid(pPktHdr, "execname", pch, &pszExecName, &pch, &rc)) + return rc; + + /* 3. argc */ + uint32_t const cArgs = (size_t)(pchEnd - pch) > sizeof(uint32_t) ? *(uint32_t const *)pch : 0xff; + pch += sizeof(uint32_t); + if (cArgs * 1 >= (size_t)(pchEnd - pch)) + rc = txsReplyFailure(pPktHdr, "BAD ARGC", "Bad or missing argument count (%#x)", cArgs); + else if (cArgs > 128) + rc = txsReplyFailure(pPktHdr, "BAD ARGC", "Too many arguments (%#x)", cArgs); + else + { + char **papszArgs = (char **)RTMemTmpAllocZ(sizeof(char *) * (cArgs + 1)); + if (papszArgs) + { + /* 4. argv */ + bool fOk = true; + for (size_t i = 0; i < cArgs && fOk; i++) + { + fOk = txsIsStringValid(pPktHdr, "argvN", pch, &papszArgs[i], &pch, &rc); + if (!fOk) + break; + } + if (fOk) + { + /* 5. cEnvVars */ + uint32_t const cEnvVars = (size_t)(pchEnd - pch) > sizeof(uint32_t) ? *(uint32_t const *)pch : 0xfff; + pch += sizeof(uint32_t); + if (cEnvVars * 1 >= (size_t)(pchEnd - pch)) + rc = txsReplyFailure(pPktHdr, "BAD ENVC", "Bad or missing environment variable count (%#x)", cEnvVars); + else if (cEnvVars > 256) + rc = txsReplyFailure(pPktHdr, "BAD ENVC", "Too many environment variables (%#x)", cEnvVars); + else + { + char **papszEnv = (char **)RTMemTmpAllocZ(sizeof(char *) * (cEnvVars + 1)); + if (papszEnv) + { + /* 6. environ */ + for (size_t i = 0; i < cEnvVars && fOk; i++) + { + fOk = txsIsStringValid(pPktHdr, "envN", pch, &papszEnv[i], &pch, &rc); + if (!fOk) /* Bail out on error. */ + break; + } + if (fOk) + { + /* 7. stdout */ + char *pszStdIn; + if (txsIsStringValid(pPktHdr, "stdin", pch, &pszStdIn, &pch, &rc)) + { + /* 8. stdout */ + char *pszStdOut; + if (txsIsStringValid(pPktHdr, "stdout", pch, &pszStdOut, &pch, &rc)) + { + /* 9. stderr */ + char *pszStdErr; + if (txsIsStringValid(pPktHdr, "stderr", pch, &pszStdErr, &pch, &rc)) + { + /* 10. testpipe */ + char *pszTestPipe; + if (txsIsStringValid(pPktHdr, "testpipe", pch, &pszTestPipe, &pch, &rc)) + { + /* 11. username */ + char *pszUsername; + if (txsIsStringValid(pPktHdr, "username", pch, &pszUsername, &pch, &rc)) + { + /** @todo No password value? */ + + /* 12. time limit */ + uint32_t const cMillies = (size_t)(pchEnd - pch) >= sizeof(uint32_t) + ? *(uint32_t const *)pch + : 0; + if ((size_t)(pchEnd - pch) > sizeof(uint32_t)) + rc = txsReplyFailure(pPktHdr, "BAD END ", "Timeout argument not at end of packet (%#x)", (size_t)(pchEnd - pch)); + else if ((size_t)(pchEnd - pch) < sizeof(uint32_t)) + rc = txsReplyFailure(pPktHdr, "BAD NOTO", "No timeout argument"); + else if (cMillies < 1000) + rc = txsReplyFailure(pPktHdr, "BAD TO ", "Timeout is less than a second (%#x)", cMillies); + else + { + pch += sizeof(uint32_t); + + /* + * Time to employ a helper here before we go way beyond + * the right margin... + */ + rc = txsDoExecHlp(pPktHdr, fFlags, pszExecName, + cArgs, papszArgs, + cEnvVars, papszEnv, + pszStdIn, pszStdOut, pszStdErr, pszTestPipe, + pszUsername, + cMillies == UINT32_MAX ? RT_INDEFINITE_WAIT : cMillies); + } + RTStrFree(pszUsername); + } + RTStrFree(pszTestPipe); + } + RTStrFree(pszStdErr); + } + RTStrFree(pszStdOut); + } + RTStrFree(pszStdIn); + } + } + for (size_t i = 0; i < cEnvVars; i++) + RTStrFree(papszEnv[i]); + RTMemTmpFree(papszEnv); + } + else + rc = txsReplyFailure(pPktHdr, "NO MEM ", "Failed to allocate %zu bytes environ", sizeof(char *) * (cEnvVars + 1)); + } + } + for (size_t i = 0; i < cArgs; i++) + RTStrFree(papszArgs[i]); + RTMemTmpFree(papszArgs); + } + else + rc = txsReplyFailure(pPktHdr, "NO MEM ", "Failed to allocate %zu bytes for argv", sizeof(char *) * (cArgs + 1)); + } + RTStrFree(pszExecName); + + return rc; +} + +/** + * The main loop. + * + * @returns exit code. + */ +static RTEXITCODE txsMainLoop(void) +{ + if (g_cVerbose > 0) + RTMsgInfo("txsMainLoop: start...\n"); + RTEXITCODE enmExitCode = RTEXITCODE_SUCCESS; + while (!g_fTerminate) + { + /* + * Read client command packet and process it. + */ + PTXSPKTHDR pPktHdr; + int rc = txsRecvPkt(&pPktHdr, true /*fAutoRetryOnFailure*/); + if (RT_FAILURE(rc)) + continue; + if (g_cVerbose > 0) + RTMsgInfo("txsMainLoop: CMD: %.8s...", pPktHdr->achOpcode); + + /* + * Do a string switch on the opcode bit. + */ + /* Connection: */ + if ( txsIsSameOpcode(pPktHdr, "HOWDY ")) + rc = txsDoHowdy(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "BYE ")) + rc = txsDoBye(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "VER ")) + rc = txsDoVer(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "UUID ")) + rc = txsDoUuid(pPktHdr); + /* Process: */ + else if (txsIsSameOpcode(pPktHdr, "EXEC ")) + rc = txsDoExec(pPktHdr); + /* Admin: */ + else if (txsIsSameOpcode(pPktHdr, "REBOOT ")) + rc = txsDoReboot(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "SHUTDOWN")) + rc = txsDoShutdown(pPktHdr); + /* CD/DVD control: */ + else if (txsIsSameOpcode(pPktHdr, "CD EJECT")) + rc = txsDoCdEject(pPktHdr); + /* File system: */ + else if (txsIsSameOpcode(pPktHdr, "CLEANUP ")) + rc = txsDoCleanup(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "MKDIR ")) + rc = txsDoMkDir(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "MKDRPATH")) + rc = txsDoMkDrPath(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "MKSYMLNK")) + rc = txsDoMkSymlnk(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "RMDIR ")) + rc = txsDoRmDir(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "RMFILE ")) + rc = txsDoRmFile(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "RMSYMLNK")) + rc = txsDoRmSymlnk(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "RMTREE ")) + rc = txsDoRmTree(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "CHMOD ")) + rc = txsDoChMod(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "CHOWN ")) + rc = txsDoChOwn(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "ISDIR ")) + rc = txsDoIsDir(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "ISFILE ")) + rc = txsDoIsFile(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "ISSYMLNK")) + rc = txsDoIsSymlnk(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "STAT ")) + rc = txsDoStat(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "LSTAT ")) + rc = txsDoLStat(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "LIST ")) + rc = txsDoList(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "CPFILE ")) + rc = txsDoCopyFile(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "PUT FILE")) + rc = txsDoPutFile(pPktHdr, false /*fHasMode*/); + else if (txsIsSameOpcode(pPktHdr, "PUT2FILE")) + rc = txsDoPutFile(pPktHdr, true /*fHasMode*/); + else if (txsIsSameOpcode(pPktHdr, "GET FILE")) + rc = txsDoGetFile(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "PKFILE ")) + rc = txsDoPackFile(pPktHdr); + else if (txsIsSameOpcode(pPktHdr, "UNPKFILE")) + rc = txsDoUnpackFile(pPktHdr); + /* Misc: */ + else if (txsIsSameOpcode(pPktHdr, "EXP STR ")) + rc = txsDoExpandString(pPktHdr); + else + rc = txsReplyUnknown(pPktHdr); + + if (g_cVerbose > 0) + RTMsgInfo("txsMainLoop: CMD: %.8s -> %Rrc", pPktHdr->achOpcode, rc); + RTMemFree(pPktHdr); + } + + if (g_cVerbose > 0) + RTMsgInfo("txsMainLoop: end\n"); + return enmExitCode; +} + + +/** + * Finalizes the scratch directory, making sure it exists. + * + * @returns exit code. + */ +static RTEXITCODE txsFinalizeScratch(void) +{ + RTPathStripTrailingSlash(g_szScratchPath); + char *pszFilename = RTPathFilename(g_szScratchPath); + if (!pszFilename) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "cannot use root for scratch (%s)\n", g_szScratchPath); + + int rc; + if (strchr(pszFilename, 'X')) + { + char ch = *pszFilename; + rc = RTDirCreateFullPath(g_szScratchPath, 0700); + *pszFilename = ch; + if (RT_SUCCESS(rc)) + rc = RTDirCreateTemp(g_szScratchPath, 0700); + } + else + { + if (RTDirExists(g_szScratchPath)) + rc = VINF_SUCCESS; + else + rc = RTDirCreateFullPath(g_szScratchPath, 0700); + } + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to create scratch directory: %Rrc (%s)\n", rc, g_szScratchPath); + return RTEXITCODE_SUCCESS; +} + +/** + * Attempts to complete an upgrade by updating the original and relaunching + * ourselves from there again. + * + * On failure, we'll continue running as the temporary copy. + * + * @returns Exit code. Exit if this is non-zero or @a *pfExit is set. + * @param argc The number of arguments. + * @param argv The argument vector. + * @param pfExit For indicating exit when the exit code is zero. + * @param pszUpgrading The upgraded image path. + */ +static RTEXITCODE txsAutoUpdateStage2(int argc, char **argv, bool *pfExit, const char *pszUpgrading) +{ + if (g_cVerbose > 0) + RTMsgInfo("Auto update stage 2..."); + + /* + * Copy the current executable onto the original. + * Note that we're racing the original program on some platforms, thus the + * 60 sec sleep mess. + */ + char szUpgradePath[RTPATH_MAX]; + if (!RTProcGetExecutablePath(szUpgradePath, sizeof(szUpgradePath))) + { + RTMsgError("RTProcGetExecutablePath failed (step 2)\n"); + return RTEXITCODE_SUCCESS; + } + void *pvUpgrade; + size_t cbUpgrade; + int rc = RTFileReadAll(szUpgradePath, &pvUpgrade, &cbUpgrade); + if (RT_FAILURE(rc)) + { + RTMsgError("RTFileReadAllEx(\"%s\"): %Rrc (step 2)\n", szUpgradePath, rc); + return RTEXITCODE_SUCCESS; + } + + uint64_t StartMilliTS = RTTimeMilliTS(); + RTFILE hFile; + rc = RTFileOpen(&hFile, pszUpgrading, + RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_TRUNCATE + | (0755 << RTFILE_O_CREATE_MODE_SHIFT)); + while ( RT_FAILURE(rc) + && RTTimeMilliTS() - StartMilliTS < 60000) + { + RTThreadSleep(1000); + rc = RTFileOpen(&hFile, pszUpgrading, + RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_TRUNCATE + | (0755 << RTFILE_O_CREATE_MODE_SHIFT)); + } + if (RT_SUCCESS(rc)) + { + rc = RTFileWrite(hFile, pvUpgrade, cbUpgrade, NULL); + RTFileClose(hFile); + if (RT_SUCCESS(rc)) + { + /* + * Relaunch the service with the original name, foricbly barring + * further upgrade cycles in case of bugs (and simplifying the code). + */ + const char **papszArgs = (const char **)RTMemAlloc((argc + 1 + 1) * sizeof(char **)); + if (papszArgs) + { + papszArgs[0] = pszUpgrading; + for (int i = 1; i < argc; i++) + papszArgs[i] = argv[i]; + papszArgs[argc] = "--no-auto-upgrade"; + papszArgs[argc + 1] = NULL; + + RTMsgInfo("Launching upgraded image: \"%s\"\n", pszUpgrading); + RTPROCESS hProc; + rc = RTProcCreate(pszUpgrading, papszArgs, RTENV_DEFAULT, 0 /*fFlags*/, &hProc); + if (RT_SUCCESS(rc)) + *pfExit = true; + else + RTMsgError("RTProcCreate(\"%s\"): %Rrc (upgrade stage 2)\n", pszUpgrading, rc); + RTMemFree(papszArgs); + } + else + RTMsgError("RTMemAlloc failed during upgrade attempt (stage 2)\n"); + } + else + RTMsgError("RTFileWrite(%s,,%zu): %Rrc (step 2) - BAD\n", pszUpgrading, cbUpgrade, rc); + } + else + RTMsgError("RTFileOpen(,%s,): %Rrc\n", pszUpgrading, rc); + RTFileReadAllFree(pvUpgrade, cbUpgrade); + return RTEXITCODE_SUCCESS; +} + +/** + * Checks for an upgrade and respawns if there is. + * + * @returns Exit code. Exit if this is non-zero or @a *pfExit is set. + * @param argc The number of arguments. + * @param argv The argument vector. + * @param cSecsCdWait Number of seconds to wait on the CD. + * @param pfExit For indicating exit when the exit code is zero. + */ +static RTEXITCODE txsAutoUpdateStage1(int argc, char **argv, uint32_t cSecsCdWait, bool *pfExit) +{ + if (g_cVerbose > 1) + RTMsgInfo("Auto update stage 1..."); + + /* + * Figure names of the current service image and the potential upgrade. + */ + char szOrgPath[RTPATH_MAX]; + if (!RTProcGetExecutablePath(szOrgPath, sizeof(szOrgPath))) + { + RTMsgError("RTProcGetExecutablePath failed\n"); + return RTEXITCODE_SUCCESS; + } + + char szUpgradePath[RTPATH_MAX]; + int rc = RTPathJoin(szUpgradePath, sizeof(szUpgradePath), g_szCdRomPath, g_szOsSlashArchShortName); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(szUpgradePath, sizeof(szUpgradePath), RTPathFilename(szOrgPath)); + if (RT_FAILURE(rc)) + { + RTMsgError("Failed to construct path to potential service upgrade: %Rrc\n", rc); + return RTEXITCODE_SUCCESS; + } + + /* + * Query information about the two images and read the entire potential source file. + * Because the CD may take a little time to be mounted when the system boots, we + * need to do some fudging here. + */ + uint64_t nsStart = RTTimeNanoTS(); + RTFSOBJINFO UpgradeInfo; + for (;;) + { + rc = RTPathQueryInfo(szUpgradePath, &UpgradeInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc)) + break; + if ( rc != VERR_FILE_NOT_FOUND + && rc != VERR_PATH_NOT_FOUND + && rc != VERR_MEDIA_NOT_PRESENT + && rc != VERR_MEDIA_NOT_RECOGNIZED) + { + RTMsgError("RTPathQueryInfo(\"%s\"): %Rrc (upgrade)\n", szUpgradePath, rc); + return RTEXITCODE_SUCCESS; + } + uint64_t cNsElapsed = RTTimeNanoTS() - nsStart; + if (cNsElapsed >= cSecsCdWait * RT_NS_1SEC_64) + { + if (g_cVerbose > 0) + RTMsgInfo("Auto update: Giving up waiting for media."); + return RTEXITCODE_SUCCESS; + } + RTThreadSleep(500); + } + + RTFSOBJINFO OrgInfo; + rc = RTPathQueryInfo(szOrgPath, &OrgInfo, RTFSOBJATTRADD_NOTHING); + if (RT_FAILURE(rc)) + { + RTMsgError("RTPathQueryInfo(\"%s\"): %Rrc (old)\n", szOrgPath, rc); + return RTEXITCODE_SUCCESS; + } + + void *pvUpgrade; + size_t cbUpgrade; + rc = RTFileReadAllEx(szUpgradePath, 0, UpgradeInfo.cbObject, RTFILE_RDALL_O_DENY_NONE, &pvUpgrade, &cbUpgrade); + if (RT_FAILURE(rc)) + { + RTMsgError("RTPathQueryInfo(\"%s\"): %Rrc (old)\n", szOrgPath, rc); + return RTEXITCODE_SUCCESS; + } + + /* + * Compare and see if we've got a different service image or not. + */ + if (OrgInfo.cbObject == UpgradeInfo.cbObject) + { + /* must compare bytes. */ + void *pvOrg; + size_t cbOrg; + rc = RTFileReadAllEx(szOrgPath, 0, OrgInfo.cbObject, RTFILE_RDALL_O_DENY_NONE, &pvOrg, &cbOrg); + if (RT_FAILURE(rc)) + { + RTMsgError("RTFileReadAllEx(\"%s\"): %Rrc\n", szOrgPath, rc); + RTFileReadAllFree(pvUpgrade, cbUpgrade); + return RTEXITCODE_SUCCESS; + } + bool fSame = !memcmp(pvUpgrade, pvOrg, OrgInfo.cbObject); + RTFileReadAllFree(pvOrg, cbOrg); + if (fSame) + { + RTFileReadAllFree(pvUpgrade, cbUpgrade); + if (g_cVerbose > 0) + RTMsgInfo("Auto update: Not necessary."); + return RTEXITCODE_SUCCESS; + } + } + + /* + * Should upgrade. Start by creating an executable copy of the update + * image in the scratch area. + */ + RTEXITCODE rcExit = txsFinalizeScratch(); + if (rcExit == RTEXITCODE_SUCCESS) + { + char szTmpPath[RTPATH_MAX]; + rc = RTPathJoin(szTmpPath, sizeof(szTmpPath), g_szScratchPath, RTPathFilename(szOrgPath)); + if (RT_SUCCESS(rc)) + { + RTFileDelete(szTmpPath); /* shouldn't hurt. */ + + RTFILE hFile; + rc = RTFileOpen(&hFile, szTmpPath, + RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE + | (0755 << RTFILE_O_CREATE_MODE_SHIFT)); + if (RT_SUCCESS(rc)) + { + rc = RTFileWrite(hFile, pvUpgrade, UpgradeInfo.cbObject, NULL); + RTFileClose(hFile); + if (RT_SUCCESS(rc)) + { + /* + * Try execute the new image and quit if it works. + */ + const char **papszArgs = (const char **)RTMemAlloc((argc + 2 + 1) * sizeof(char **)); + if (papszArgs) + { + papszArgs[0] = szTmpPath; + for (int i = 1; i < argc; i++) + papszArgs[i] = argv[i]; + papszArgs[argc] = "--upgrading"; + papszArgs[argc + 1] = szOrgPath; + papszArgs[argc + 2] = NULL; + + RTMsgInfo("Launching intermediate automatic upgrade stage: \"%s\"\n", szTmpPath); + RTPROCESS hProc; + rc = RTProcCreate(szTmpPath, papszArgs, RTENV_DEFAULT, 0 /*fFlags*/, &hProc); + if (RT_SUCCESS(rc)) + *pfExit = true; + else + RTMsgError("RTProcCreate(\"%s\"): %Rrc (upgrade stage 1)\n", szTmpPath, rc); + RTMemFree(papszArgs); + } + else + RTMsgError("RTMemAlloc failed during upgrade attempt (stage)\n"); + } + else + RTMsgError("RTFileWrite(%s,,%zu): %Rrc\n", szTmpPath, UpgradeInfo.cbObject, rc); + } + else + RTMsgError("RTFileOpen(,%s,): %Rrc\n", szTmpPath, rc); + } + else + RTMsgError("Failed to construct path to temporary upgrade image: %Rrc\n", rc); + } + + RTFileReadAllFree(pvUpgrade, cbUpgrade); + return rcExit; +} + +/** + * Determines the default configuration. + */ +static void txsSetDefaults(void) +{ + /* + * OS and ARCH. + */ + AssertCompile(sizeof(KBUILD_TARGET) <= sizeof(g_szOsShortName)); + strcpy(g_szOsShortName, KBUILD_TARGET); + + AssertCompile(sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szArchShortName)); + strcpy(g_szArchShortName, KBUILD_TARGET_ARCH); + + AssertCompile(sizeof(KBUILD_TARGET) + sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szOsDotArchShortName)); + strcpy(g_szOsDotArchShortName, KBUILD_TARGET); + g_szOsDotArchShortName[sizeof(KBUILD_TARGET) - 1] = '.'; + strcpy(&g_szOsDotArchShortName[sizeof(KBUILD_TARGET)], KBUILD_TARGET_ARCH); + + AssertCompile(sizeof(KBUILD_TARGET) + sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szOsSlashArchShortName)); + strcpy(g_szOsSlashArchShortName, KBUILD_TARGET); + g_szOsSlashArchShortName[sizeof(KBUILD_TARGET) - 1] = '/'; + strcpy(&g_szOsSlashArchShortName[sizeof(KBUILD_TARGET)], KBUILD_TARGET_ARCH); + +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + strcpy(g_szExeSuff, ".exe"); + strcpy(g_szScriptSuff, ".cmd"); +#else + strcpy(g_szExeSuff, ""); + strcpy(g_szScriptSuff, ".sh"); +#endif + + int rc = RTPathGetCurrent(g_szCwd, sizeof(g_szCwd)); + if (RT_FAILURE(rc)) + RTMsgError("RTPathGetCurrent failed: %Rrc\n", rc); + g_szCwd[sizeof(g_szCwd) - 1] = '\0'; + + if (!RTProcGetExecutablePath(g_szTxsDir, sizeof(g_szTxsDir))) + RTMsgError("RTProcGetExecutablePath failed!\n"); + g_szTxsDir[sizeof(g_szTxsDir) - 1] = '\0'; + RTPathStripFilename(g_szTxsDir); + RTPathStripTrailingSlash(g_szTxsDir); + + /* + * The CD/DVD-ROM location. + */ + /** @todo do a better job here :-) */ +#ifdef RT_OS_WINDOWS + strcpy(g_szDefCdRomPath, "D:/"); +#elif defined(RT_OS_OS2) + strcpy(g_szDefCdRomPath, "D:/"); +#else + if (RTDirExists("/media")) + strcpy(g_szDefCdRomPath, "/media/cdrom"); + else + strcpy(g_szDefCdRomPath, "/mnt/cdrom"); +#endif + strcpy(g_szCdRomPath, g_szDefCdRomPath); + + /* + * Temporary directory. + */ + rc = RTPathTemp(g_szDefScratchPath, sizeof(g_szDefScratchPath)); + if (RT_SUCCESS(rc)) +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) || defined(RT_OS_DOS) + rc = RTPathAppend(g_szDefScratchPath, sizeof(g_szDefScratchPath), "txs-XXXX.tmp"); +#else + rc = RTPathAppend(g_szDefScratchPath, sizeof(g_szDefScratchPath), "txs-XXXXXXXXX.tmp"); +#endif + if (RT_FAILURE(rc)) + { + RTMsgError("RTPathTemp/Append failed when constructing scratch path: %Rrc\n", rc); + strcpy(g_szDefScratchPath, "/tmp/txs-XXXX.tmp"); + } + strcpy(g_szScratchPath, g_szDefScratchPath); + + /* + * The default transporter is the first one. + */ + g_pTransport = g_apTransports[0]; +} + +/** + * Prints the usage. + * + * @param pStrm Where to print it. + * @param pszArgv0 The program name (argv[0]). + */ +static void txsUsage(PRTSTREAM pStrm, const char *pszArgv0) +{ + RTStrmPrintf(pStrm, + "Usage: %Rbn [options]\n" + "\n" + "Options:\n" + " --cdrom <path>\n" + " Where the CD/DVD-ROM will be mounted.\n" + " Default: %s\n" + " --scratch <path>\n" + " Where to put scratch files.\n" + " Default: %s \n" + , + pszArgv0, + g_szDefCdRomPath, + g_szDefScratchPath); + RTStrmPrintf(pStrm, + " --transport <name>\n" + " Use the specified transport layer, one of the following:\n"); + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + RTStrmPrintf(pStrm, " %s - %s\n", g_apTransports[i]->szName, g_apTransports[i]->pszDesc); + RTStrmPrintf(pStrm, " Default: %s\n", g_pTransport->szName); + RTStrmPrintf(pStrm, + " --auto-upgrade, --no-auto-upgrade\n" + " To enable or disable the automatic upgrade mechanism where any different\n" + " version found on the CD-ROM on startup will replace the initial copy.\n" + " Default: --auto-upgrade\n" + " --wait-cdrom <secs>\n" + " Number of seconds to wait for the CD-ROM to be mounted before giving up\n" + " on automatic upgrading.\n" + " Default: --wait-cdrom 1; solaris: --wait-cdrom 8\n" + " --upgrading <org-path>\n" + " Internal use only.\n"); + RTStrmPrintf(pStrm, + " --display-output, --no-display-output\n" + " Display the output and the result of all child processes.\n"); + RTStrmPrintf(pStrm, + " --foreground\n" + " Don't daemonize, run in the foreground.\n"); + RTStrmPrintf(pStrm, + " --verbose, -v\n" + " Increases the verbosity level. Can be specified multiple times.\n"); + RTStrmPrintf(pStrm, + " --quiet, -q\n" + " Mutes any logging output.\n"); + RTStrmPrintf(pStrm, + " --help, -h, -?\n" + " Display this message and exit.\n" + " --version, -V\n" + " Display the version and exit.\n"); + + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + if (g_apTransports[i]->cOpts) + { + RTStrmPrintf(pStrm, + "\n" + "Options for %s:\n", g_apTransports[i]->szName); + g_apTransports[i]->pfnUsage(g_pStdOut); + } +} + +/** + * Parses the arguments. + * + * @returns Exit code. Exit if this is non-zero or @a *pfExit is set. + * @param argc The number of arguments. + * @param argv The argument vector. + * @param pfExit For indicating exit when the exit code is zero. + */ +static RTEXITCODE txsParseArgv(int argc, char **argv, bool *pfExit) +{ + *pfExit = false; + + /* + * Storage for locally handled options. + */ + bool fAutoUpgrade = true; + bool fDaemonize = true; + bool fDaemonized = false; + const char *pszUpgrading = NULL; +#ifdef RT_OS_SOLARIS + uint32_t cSecsCdWait = 8; +#else + uint32_t cSecsCdWait = 1; +#endif + + /* + * Combine the base and transport layer option arrays. + */ + static const RTGETOPTDEF s_aBaseOptions[] = + { + { "--transport", 't', RTGETOPT_REQ_STRING }, + { "--cdrom", 'c', RTGETOPT_REQ_STRING }, + { "--wait-cdrom", 'w', RTGETOPT_REQ_UINT32 }, + { "--scratch", 's', RTGETOPT_REQ_STRING }, + { "--auto-upgrade", 'a', RTGETOPT_REQ_NOTHING }, + { "--no-auto-upgrade", 'A', RTGETOPT_REQ_NOTHING }, + { "--upgrading", 'U', RTGETOPT_REQ_STRING }, + { "--display-output", 'd', RTGETOPT_REQ_NOTHING }, + { "--no-display-output",'D', RTGETOPT_REQ_NOTHING }, + { "--foreground", 'f', RTGETOPT_REQ_NOTHING }, + { "--daemonized", 'Z', RTGETOPT_REQ_NOTHING }, + { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + }; + + size_t cOptions = RT_ELEMENTS(s_aBaseOptions); + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + cOptions += g_apTransports[i]->cOpts; + + PRTGETOPTDEF paOptions = (PRTGETOPTDEF)alloca(cOptions * sizeof(RTGETOPTDEF)); + if (!paOptions) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "alloca failed\n"); + + memcpy(paOptions, s_aBaseOptions, sizeof(s_aBaseOptions)); + cOptions = RT_ELEMENTS(s_aBaseOptions); + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + { + memcpy(&paOptions[cOptions], g_apTransports[i]->paOpts, g_apTransports[i]->cOpts * sizeof(RTGETOPTDEF)); + cOptions += g_apTransports[i]->cOpts; + } + + /* + * Parse the arguments. + */ + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, argc, argv, paOptions, cOptions, 1, 0 /* fFlags */); + AssertRC(rc); + + int ch; + RTGETOPTUNION Val; + while ((ch = RTGetOpt(&GetState, &Val))) + { + switch (ch) + { + case 'a': + fAutoUpgrade = true; + break; + + case 'A': + fAutoUpgrade = false; + break; + + case 'c': + rc = RTStrCopy(g_szCdRomPath, sizeof(g_szCdRomPath), Val.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "CD/DVD-ROM is path too long (%Rrc)\n", rc); + break; + + case 'd': + g_fDisplayOutput = true; + break; + + case 'D': + g_fDisplayOutput = false; + break; + + case 'f': + fDaemonize = false; + break; + + case 'h': + txsUsage(g_pStdOut, argv[0]); + *pfExit = true; + return RTEXITCODE_SUCCESS; + + case 's': + rc = RTStrCopy(g_szScratchPath, sizeof(g_szScratchPath), Val.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "scratch path is too long (%Rrc)\n", rc); + break; + + case 't': + { + PCTXSTRANSPORT pTransport = NULL; + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + if (!strcmp(g_apTransports[i]->szName, Val.psz)) + { + pTransport = g_apTransports[i]; + break; + } + if (!pTransport) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown transport layer name '%s'\n", Val.psz); + g_pTransport = pTransport; + break; + } + + case 'U': + pszUpgrading = Val.psz; + break; + + case 'w': + cSecsCdWait = Val.u32; + break; + + case 'q': + g_cVerbose = 0; + break; + + case 'v': + g_cVerbose++; + break; + + case 'V': + RTPrintf("$Revision: 153224 $\n"); + *pfExit = true; + return RTEXITCODE_SUCCESS; + + case 'Z': + fDaemonized = true; + fDaemonize = false; + break; + + default: + { + rc = VERR_TRY_AGAIN; + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + if (g_apTransports[i]->cOpts) + { + rc = g_apTransports[i]->pfnOption(ch, &Val); + if (RT_SUCCESS(rc)) + break; + if (rc != VERR_TRY_AGAIN) + { + *pfExit = true; + return RTEXITCODE_SYNTAX; + } + } + if (rc == VERR_TRY_AGAIN) + { + *pfExit = true; + return RTGetOptPrintError(ch, &Val); + } + break; + } + } + } + + /* + * Handle automatic upgrading of the service. + */ + if (fAutoUpgrade && !*pfExit) + { + RTEXITCODE rcExit; + if (pszUpgrading) + rcExit = txsAutoUpdateStage2(argc, argv, pfExit, pszUpgrading); + else + rcExit = txsAutoUpdateStage1(argc, argv, cSecsCdWait, pfExit); + if ( *pfExit + || rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + + /* + * Daemonize ourselves if asked to. + */ + if (fDaemonize && !*pfExit) + { + if (g_cVerbose > 0) + RTMsgInfo("Daemonizing..."); + rc = RTProcDaemonize(argv, "--daemonized"); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcDaemonize: %Rrc\n", rc); + *pfExit = true; + } + + return RTEXITCODE_SUCCESS; +} + +/** + * @callback_method_impl{FNRTLOGPHASE, Release logger callback} + */ +static DECLCALLBACK(void) logHeaderFooter(PRTLOGGER pLoggerRelease, RTLOGPHASE enmPhase, PFNRTLOGPHASEMSG pfnLog) +{ + /* Some introductory information. */ + static RTTIMESPEC s_TimeSpec; + char szTmp[256]; + if (enmPhase == RTLOGPHASE_BEGIN) + RTTimeNow(&s_TimeSpec); + RTTimeSpecToString(&s_TimeSpec, szTmp, sizeof(szTmp)); + + switch (enmPhase) + { + case RTLOGPHASE_BEGIN: + { + pfnLog(pLoggerRelease, + "TestExecService (Validation Kit TxS) %s r%s (verbosity: %u) %s %s (%s %s) release log\n" + "Copyright (C) " VBOX_C_YEAR " " VBOX_VENDOR "\n\n" + "Log opened %s\n", + RTBldCfgVersion(), RTBldCfgRevisionStr(), g_cVerbose, + KBUILD_TARGET, KBUILD_TARGET_ARCH, + __DATE__, __TIME__, szTmp); + + int vrc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) + pfnLog(pLoggerRelease, "OS Product: %s\n", szTmp); + vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) + pfnLog(pLoggerRelease, "OS Release: %s\n", szTmp); + vrc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) + pfnLog(pLoggerRelease, "OS Version: %s\n", szTmp); + vrc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) + pfnLog(pLoggerRelease, "OS Service Pack: %s\n", szTmp); + + /* the package type is interesting for Linux distributions */ + char szExecName[RTPATH_MAX]; + char *pszExecName = RTProcGetExecutablePath(szExecName, sizeof(szExecName)); + pfnLog(pLoggerRelease, + "Executable: %s\n" + "Process ID: %u\n" + "Package type: %s" +#ifdef VBOX_OSE + " (OSE)" +#endif + "\n", + pszExecName ? pszExecName : "unknown", + RTProcSelf(), + VBOX_PACKAGE_STRING); + break; + } + + case RTLOGPHASE_PREROTATE: + pfnLog(pLoggerRelease, "Log rotated - Log started %s\n", szTmp); + break; + + case RTLOGPHASE_POSTROTATE: + pfnLog(pLoggerRelease, "Log continuation - Log started %s\n", szTmp); + break; + + case RTLOGPHASE_END: + pfnLog(pLoggerRelease, "End of log file - Log started %s\n", szTmp); + break; + + default: + /* nothing */ + break; + } +} + +int main(int argc, char **argv) +{ + /* + * Initialize the runtime. + */ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Determine defaults and parse the arguments. + */ + txsSetDefaults(); + bool fExit; + RTEXITCODE rcExit = txsParseArgv(argc, argv, &fExit); + if (rcExit != RTEXITCODE_SUCCESS || fExit) + return rcExit; + + /* + * Enable (release) TxS logging to stdout + file. This is independent from the actual test cases being run. + * + * Keep the log file path + naming predictable (the OS' temp dir) so that we later can retrieve it + * from the host side without guessing much. + * + * If enabling logging fails for some reason, just tell but don't bail out to not make tests fail. + */ + char szLogFile[RTPATH_MAX]; + rc = RTPathTemp(szLogFile, sizeof(szLogFile)); + if (RT_SUCCESS(rc)) + { + rc = RTPathAppend(szLogFile, sizeof(szLogFile), "vbox-txs-release.log"); + if (RT_FAILURE(rc)) + RTMsgError("RTPathAppend failed when constructing log file path: %Rrc\n", rc); + } + else + RTMsgError("RTPathTemp failed when constructing log file path: %Rrc\n", rc); + + if (RT_SUCCESS(rc)) + { + RTUINT fFlags = RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG; +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + fFlags |= RTLOGFLAGS_USECRLF; +#endif + static const char * const s_apszLogGroups[] = VBOX_LOGGROUP_NAMES; + rc = RTLogCreateEx(&g_pRelLogger, "VBOX_TXS_RELEASE_LOG", fFlags, "all", + RT_ELEMENTS(s_apszLogGroups), s_apszLogGroups, UINT32_MAX /* cMaxEntriesPerGroup */, + 0 /*cBufDescs*/, NULL /* paBufDescs */, RTLOGDEST_STDOUT | RTLOGDEST_FILE, + logHeaderFooter /* pfnPhase */ , + 10 /* cHistory */, 100 * _1M /* cbHistoryFileMax */, RT_SEC_1DAY /* cSecsHistoryTimeSlot */, + NULL /*pOutputIf*/, NULL /*pvOutputIfUser*/, + NULL /* pErrInfo */, "%s", szLogFile); + if (RT_SUCCESS(rc)) + { + RTLogRelSetDefaultInstance(g_pRelLogger); + if (g_cVerbose) + { + RTMsgInfo("Setting verbosity logging to level %u\n", g_cVerbose); + switch (g_cVerbose) /* Not very elegant, but has to do it for now. */ + { + case 1: + rc = RTLogGroupSettings(g_pRelLogger, "all.e.l.l2"); + break; + + case 2: + rc = RTLogGroupSettings(g_pRelLogger, "all.e.l.l2.l3"); + break; + + case 3: + rc = RTLogGroupSettings(g_pRelLogger, "all.e.l.l2.l3.l4"); + break; + + case 4: + RT_FALL_THROUGH(); + default: + rc = RTLogGroupSettings(g_pRelLogger, "all.e.l.l2.l3.l4.f"); + break; + } + if (RT_FAILURE(rc)) + RTMsgError("Setting logging groups failed, rc=%Rrc\n", rc); + } + } + else + RTMsgError("Failed to create release logger: %Rrc", rc); + + if (RT_SUCCESS(rc)) + RTMsgInfo("Log file written to '%s'\n", szLogFile); + } + + /* + * Generate a UUID for this TXS instance. + */ + rc = RTUuidCreate(&g_InstanceUuid); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTUuidCreate failed: %Rrc", rc); + if (g_cVerbose > 0) + RTMsgInfo("Instance UUID: %RTuuid", &g_InstanceUuid); + + /* + * Finalize the scratch directory and initialize the transport layer. + */ + rcExit = txsFinalizeScratch(); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + rc = g_pTransport->pfnInit(); + if (RT_FAILURE(rc)) + return RTEXITCODE_FAILURE; + + /* + * Ok, start working + */ + rcExit = txsMainLoop(); + + /* + * Cleanup. + */ + g_pTransport->pfnTerm(); + + return rcExit; +} diff --git a/src/VBox/ValidationKit/utils/TestExecServ/TestExecServiceInternal.h b/src/VBox/ValidationKit/utils/TestExecServ/TestExecServiceInternal.h new file mode 100644 index 00000000..86a30893 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/TestExecServiceInternal.h @@ -0,0 +1,232 @@ +/* $Id: TestExecServiceInternal.h $ */ +/** @file + * TestExecServ - Basic Remote Execution Service, Internal Header. + */ + +/* + * Copyright (C) 2010-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_TestExecServ_TestExecServiceInternal_h +#define VBOX_INCLUDED_SRC_TestExecServ_TestExecServiceInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/getopt.h> +#include <iprt/stream.h> + +RT_C_DECLS_BEGIN + +/** + * Packet header. + */ +typedef struct TXSPKTHDR +{ + /** The unpadded packet length. This include this header. */ + uint32_t cb; + /** The CRC-32 for the packet starting from the opcode field. 0 if the packet + * hasn't been CRCed. */ + uint32_t uCrc32; + /** Packet opcode, an unterminated ASCII string. */ + uint8_t achOpcode[8]; +} TXSPKTHDR; +AssertCompileSize(TXSPKTHDR, 16); +/** Pointer to a packet header. */ +typedef TXSPKTHDR *PTXSPKTHDR; +/** Pointer to a packet header. */ +typedef TXSPKTHDR const *PCTXSPKTHDR; +/** Pointer to a packet header pointer. */ +typedef PTXSPKTHDR *PPTXSPKTHDR; + +/** Packet alignment. */ +#define TXSPKT_ALIGNMENT 16 +/** Max packet size. */ +#define TXSPKT_MAX_SIZE _256K + + +/** + * Transport layer descriptor. + */ +typedef struct TXSTRANSPORT +{ + /** The name. */ + char szName[16]; + /** The description. */ + const char *pszDesc; + /** Pointer to an array of options. */ + PCRTGETOPTDEF paOpts; + /** The number of options in the array. */ + size_t cOpts; + + /** + * Print the usage information for this transport layer. + * + * @param pStream The stream to print the usage info to. + * + * @remarks This is only required if TXSTRANSPORT::cOpts is greater than 0. + */ + DECLR3CALLBACKMEMBER(void, pfnUsage,(PRTSTREAM pStream)); + + /** + * Handle an option. + * + * When encountering an options that is not part of the base options, we'll call + * this method for each transport layer until one handles it. + * + * @retval VINF_SUCCESS if handled. + * @retval VERR_TRY_AGAIN if not handled. + * @retval VERR_INVALID_PARAMETER if we should exit with a non-zero status. + * + * @param ch The short option value. + * @param pVal Pointer to the value union. + * + * @remarks This is only required if TXSTRANSPORT::cOpts is greater than 0. + */ + DECLR3CALLBACKMEMBER(int, pfnOption,(int ch, PCRTGETOPTUNION pVal)); + + /** + * Initializes the transport layer. + * + * @returns IPRT status code. On errors, the transport layer shall call + * RTMsgError to display the error details to the user. + */ + DECLR3CALLBACKMEMBER(int, pfnInit,(void)); + + /** + * Terminate the transport layer, closing and freeing resources. + * + * On errors, the transport layer shall call RTMsgError to display the error + * details to the user. + */ + DECLR3CALLBACKMEMBER(void, pfnTerm,(void)); + + /** + * Polls for incoming packets. + * + * @returns true if there are pending packets, false if there isn't. + */ + DECLR3CALLBACKMEMBER(bool, pfnPollIn,(void)); + + /** + * Adds any pollable handles to the poll set. + * + * This is optional and layers that doesn't have anything that can be polled + * shall set this method pointer to NULL to indicate that pfnPollIn must be used + * instead. + * + * @returns IPRT status code. + * @param hPollSet The poll set to add them to. + * @param idStart The handle ID to start at. + */ + DECLR3CALLBACKMEMBER(int, pfnPollSetAdd,(RTPOLLSET hPollSet, uint32_t idStart)); + + /** + * Receives an incoming packet. + * + * This will block until the data becomes available or we're interrupted by a + * signal or something. + * + * @returns IPRT status code. On error conditions other than VERR_INTERRUPTED, + * the current operation will be aborted when applicable. When + * interrupted, the transport layer will store the data until the next + * receive call. + * + * @param ppPktHdr Where to return the pointer to the packet we've + * read. This is allocated from the heap using + * RTMemAlloc (w/ TXSPKT_ALIGNMENT) and must be + * free by calling RTMemFree. + */ + DECLR3CALLBACKMEMBER(int, pfnRecvPkt,(PPTXSPKTHDR ppPktHdr)); + + /** + * Sends an outgoing packet. + * + * This will block until the data has been written. + * + * @returns IPRT status code. + * @retval VERR_INTERRUPTED if interrupted before anything was sent. + * + * @param pPktHdr The packet to send. The size is given by + * aligning the size in the header by + * TXSPKT_ALIGNMENT. + */ + DECLR3CALLBACKMEMBER(int, pfnSendPkt,(PCTXSPKTHDR pPktHdr)); + + /** + * Sends a babble packet and disconnects the client (if applicable). + * + * @param pPktHdr The packet to send. The size is given by + * aligning the size in the header by + * TXSPKT_ALIGNMENT. + * @param cMsSendTimeout The send timeout measured in milliseconds. + */ + DECLR3CALLBACKMEMBER(void, pfnBabble,(PCTXSPKTHDR pPktHdr, RTMSINTERVAL cMsSendTimeout)); + + /** + * Notification about a client HOWDY. + */ + DECLR3CALLBACKMEMBER(void, pfnNotifyHowdy,(void)); + + /** + * Notification about a client BYE. + * + * For connection oriented transport layers, it would be good to disconnect the + * client at this point. + */ + DECLR3CALLBACKMEMBER(void, pfnNotifyBye,(void)); + + /** + * Notification about a REBOOT or SHUTDOWN. + * + * For connection oriented transport layers, stop listening for and + * accepting at this point. + */ + DECLR3CALLBACKMEMBER(void, pfnNotifyReboot,(void)); + + /** Non-zero end marker. */ + uint32_t u32EndMarker; +} TXSTRANSPORT; +/** Pointer to a const transport layer descriptor. */ +typedef const struct TXSTRANSPORT *PCTXSTRANSPORT; + + +extern TXSTRANSPORT const g_TcpTransport; +extern TXSTRANSPORT const g_SerialTransport; +extern TXSTRANSPORT const g_FileSysTransport; +extern TXSTRANSPORT const g_GuestPropTransport; +extern TXSTRANSPORT const g_TestDevTransport; + +extern uint32_t g_cVerbose; + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_TestExecServ_TestExecServiceInternal_h */ + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/TestExecServiceSerial.cpp b/src/VBox/ValidationKit/utils/TestExecServ/TestExecServiceSerial.cpp new file mode 100644 index 00000000..be58af6f --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/TestExecServiceSerial.cpp @@ -0,0 +1,417 @@ +/* $Id: TestExecServiceSerial.cpp $ */ +/** @file + * TestExecServ - Basic Remote Execution Service, Serial port Transport Layer. + */ + +/* + * Copyright (C) 2018-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/string.h> +#include <iprt/serialport.h> +#include <iprt/thread.h> +#include <iprt/time.h> + +#include "TestExecServiceInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The default baud rate port. */ +#define TXS_SERIAL_DEF_BAUDRATE 115200 +/** The default serial device to use. */ +#if defined(RT_OS_LINUX) +# define TXS_SERIAL_DEF_DEVICE "/dev/ttyS0" +#elif defined(RT_OS_WINDOWS) +# define TXS_SERIAL_DEF_DEVICE "COM1" +#elif defined(RT_OS_SOLARIS) +# define TXS_SERIAL_DEF_DEVICE "<todo>" +#elif defined(RT_OS_FREEBSD) +# define TXS_SERIAL_DEF_DEVICE "<todo>" +#elif defined(RT_OS_DARWIN) +# define TXS_SERIAL_DEF_DEVICE "<todo>" +#else +# error "Port me" +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** @name Serial Parameters + * @{ */ +/** The addresses to bind to. Empty string means any. */ +static uint32_t g_uSerialBaudRate = TXS_SERIAL_DEF_BAUDRATE; +/** The serial port device to use. */ +static char g_szSerialDevice[256] = TXS_SERIAL_DEF_DEVICE; +/** @} */ + +/** The serial port handle. */ +static RTSERIALPORT g_hSerialPort = NIL_RTSERIALPORT; +/** The size of the stashed data. */ +static size_t g_cbSerialStashed = 0; +/** The size of the stashed data allocation. */ +static size_t g_cbSerialStashedAlloced = 0; +/** The stashed data. */ +static uint8_t *g_pbSerialStashed = NULL; + + + +/** + * @interface_method_impl{TXSTRANSPORT,pfnNotifyReboot} + */ +static DECLCALLBACK(void) txsSerialNotifyReboot(void) +{ + /* nothing to do here */ +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnNotifyBye} + */ +static DECLCALLBACK(void) txsSerialNotifyBye(void) +{ + /* nothing to do here */ +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnNotifyHowdy} + */ +static DECLCALLBACK(void) txsSerialNotifyHowdy(void) +{ + /* nothing to do here */ +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnBabble} + */ +static DECLCALLBACK(void) txsSerialBabble(PCTXSPKTHDR pPktHdr, RTMSINTERVAL cMsSendTimeout) +{ + Assert(g_hSerialPort != NIL_RTSERIALPORT); + + /* + * Try send the babble reply. + */ + NOREF(cMsSendTimeout); /** @todo implement the timeout here; non-blocking write + select-on-write. */ + int rc; + size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, TXSPKT_ALIGNMENT); + do rc = RTSerialPortWrite(g_hSerialPort, pPktHdr, cbToSend, NULL); + while (rc == VERR_INTERRUPTED); + + /* + * Disconnect the client. + */ + Log(("txsSerialBabble: RTSerialPortWrite rc=%Rrc\n", rc)); +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnSendPkt} + */ +static DECLCALLBACK(int) txsSerialSendPkt(PCTXSPKTHDR pPktHdr) +{ + Assert(g_hSerialPort != NIL_RTSERIALPORT); + Assert(pPktHdr->cb >= sizeof(TXSPKTHDR)); + + /* + * Write it. + */ + size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, TXSPKT_ALIGNMENT); + int rc = RTSerialPortWrite(g_hSerialPort, pPktHdr, cbToSend, NULL); + if ( RT_FAILURE(rc) + && rc != VERR_INTERRUPTED) + { + /* assume fatal connection error. */ + Log(("RTSerialPortWrite -> %Rrc\n", rc)); + } + + return rc; +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnRecvPkt} + */ +static DECLCALLBACK(int) txsSerialRecvPkt(PPTXSPKTHDR ppPktHdr) +{ + Assert(g_hSerialPort != NIL_RTSERIALPORT); + + int rc = VINF_SUCCESS; + *ppPktHdr = NULL; + + /* + * Read state. + */ + size_t offData = 0; + size_t cbData = 0; + size_t cbDataAlloced; + uint8_t *pbData = NULL; + + /* + * Any stashed data? + */ + if (g_cbSerialStashedAlloced) + { + offData = g_cbSerialStashed; + cbDataAlloced = g_cbSerialStashedAlloced; + pbData = g_pbSerialStashed; + + g_cbSerialStashed = 0; + g_cbSerialStashedAlloced = 0; + g_pbSerialStashed = NULL; + } + else + { + cbDataAlloced = RT_ALIGN_Z(64, TXSPKT_ALIGNMENT); + pbData = (uint8_t *)RTMemAlloc(cbDataAlloced); + if (!pbData) + return VERR_NO_MEMORY; + } + + /* + * Read and valid the length. + */ + while (offData < sizeof(uint32_t)) + { + size_t cbRead = sizeof(uint32_t) - offData; + rc = RTSerialPortRead(g_hSerialPort, pbData + offData, cbRead, NULL); + if (RT_FAILURE(rc)) + break; + offData += cbRead; + } + if (RT_SUCCESS(rc)) + { + ASMCompilerBarrier(); /* paranoia^3 */ + cbData = *(uint32_t volatile *)pbData; + if (cbData >= sizeof(TXSPKTHDR) && cbData <= TXSPKT_MAX_SIZE) + { + /* + * Align the length and reallocate the return packet it necessary. + */ + cbData = RT_ALIGN_Z(cbData, TXSPKT_ALIGNMENT); + if (cbData > cbDataAlloced) + { + void *pvNew = RTMemRealloc(pbData, cbData); + if (pvNew) + { + pbData = (uint8_t *)pvNew; + cbDataAlloced = cbData; + } + else + rc = VERR_NO_MEMORY; + } + if (RT_SUCCESS(rc)) + { + /* + * Read the remainder of the data. + */ + while (offData < cbData) + { + size_t cbRead = cbData - offData; + rc = RTSerialPortRead(g_hSerialPort, pbData + offData, cbRead, NULL); + if (RT_FAILURE(rc)) + break; + offData += cbRead; + } + } + } + else + rc = VERR_NET_PROTOCOL_ERROR; + } + if (RT_SUCCESS(rc)) + *ppPktHdr = (PTXSPKTHDR)pbData; + else + { + /* + * Deal with errors. + */ + if (rc == VERR_INTERRUPTED) + { + /* stash it away for the next call. */ + g_cbSerialStashed = cbData; + g_cbSerialStashedAlloced = cbDataAlloced; + g_pbSerialStashed = pbData; + } + else + { + RTMemFree(pbData); + + /* assume fatal connection error. */ + Log(("txsSerialRecvPkt: RTSerialPortRead -> %Rrc\n", rc)); + } + } + + return rc; +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnPollIn} + */ +static DECLCALLBACK(bool) txsSerialPollIn(void) +{ + Assert(g_hSerialPort != NIL_RTSERIALPORT); + + uint32_t fEvtsRecv = 0; + int rc = RTSerialPortEvtPoll(g_hSerialPort, RTSERIALPORT_EVT_F_DATA_RX, + &fEvtsRecv, 0/*cMillies*/); + return RT_SUCCESS(rc); +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnTerm} + */ +static DECLCALLBACK(void) txsSerialTerm(void) +{ + if (g_hSerialPort != NIL_RTSERIALPORT) + RTSerialPortClose(g_hSerialPort); + + /* Clean up stashing. */ + if (g_pbSerialStashed) + RTMemFree(g_pbSerialStashed); + g_pbSerialStashed = NULL; + g_cbSerialStashed = 0; + g_cbSerialStashedAlloced = 0; + + Log(("txsSerialTerm: done\n")); +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnInit} + */ +static DECLCALLBACK(int) txsSerialInit(void) +{ + uint32_t fOpenFlags = RTSERIALPORT_OPEN_F_READ | RTSERIALPORT_OPEN_F_WRITE; + int rc = RTSerialPortOpen(&g_hSerialPort, &g_szSerialDevice[0], fOpenFlags); + if (RT_SUCCESS(rc)) + { + RTSERIALPORTCFG SerPortCfg; + + SerPortCfg.uBaudRate = g_uSerialBaudRate; + SerPortCfg.enmParity = RTSERIALPORTPARITY_NONE; + SerPortCfg.enmDataBitCount = RTSERIALPORTDATABITS_8BITS; + SerPortCfg.enmStopBitCount = RTSERIALPORTSTOPBITS_ONE; + rc = RTSerialPortCfgSet(g_hSerialPort, &SerPortCfg, NULL); + if (RT_FAILURE(rc)) + { + RTMsgError("RTSerialPortCfgSet() failed: %Rrc\n", rc); + RTSerialPortClose(g_hSerialPort); + g_hSerialPort = NIL_RTSERIALPORT; + } + } + else + RTMsgError("RTSerialPortOpen(, %s, %#x) failed: %Rrc\n", + g_szSerialDevice, fOpenFlags, rc); + + return rc; +} + +/** Options */ +enum TXSSERIALOPT +{ + TXSSERIALOPT_BAUDRATE = 1000, + TXSSERIALOPT_DEVICE +}; + +/** + * @interface_method_impl{TXSTRANSPORT,pfnOption} + */ +static DECLCALLBACK(int) txsSerialOption(int ch, PCRTGETOPTUNION pVal) +{ + int rc; + + switch (ch) + { + case TXSSERIALOPT_DEVICE: + rc = RTStrCopy(g_szSerialDevice, sizeof(g_szSerialDevice), pVal->psz); + if (RT_FAILURE(rc)) + return RTMsgErrorRc(VERR_INVALID_PARAMETER, "Serial port device path is too long (%Rrc)", rc); + if (!g_szSerialDevice[0]) + strcpy(g_szSerialDevice, TXS_SERIAL_DEF_DEVICE); + return VINF_SUCCESS; + case TXSSERIALOPT_BAUDRATE: + g_uSerialBaudRate = pVal->u32 == 0 ? TXS_SERIAL_DEF_BAUDRATE : pVal->u32; + return VINF_SUCCESS; + } + return VERR_TRY_AGAIN; +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnUsage} + */ +DECLCALLBACK(void) txsSerialUsage(PRTSTREAM pStream) +{ + RTStrmPrintf(pStream, + " --serial-device <device>\n" + " Selects the serial port to use.\n" + " Default: %s\n" + " --serial-baudrate <baudrate>\n" + " Selects the baudrate to set the serial port to.\n" + " Default: %u\n" + , TXS_SERIAL_DEF_DEVICE, TXS_SERIAL_DEF_BAUDRATE); +} + +/** Command line options for the serial transport layer. */ +static const RTGETOPTDEF g_SerialOpts[] = +{ + { "--serial-device", TXSSERIALOPT_DEVICE, RTGETOPT_REQ_STRING }, + { "--serial-baudrate", TXSSERIALOPT_BAUDRATE, RTGETOPT_REQ_UINT32 } +}; + +/** Serial port transport layer. */ +const TXSTRANSPORT g_SerialTransport = +{ + /* .szName = */ "serial", + /* .pszDesc = */ "Serial", + /* .cOpts = */ &g_SerialOpts[0], + /* .paOpts = */ RT_ELEMENTS(g_SerialOpts), + /* .pfnUsage = */ txsSerialUsage, + /* .pfnOption = */ txsSerialOption, + /* .pfnInit = */ txsSerialInit, + /* .pfnTerm = */ txsSerialTerm, + /* .pfnPollIn = */ txsSerialPollIn, + /* .pfnPollSetAdd = */ NULL, + /* .pfnRecvPkt = */ txsSerialRecvPkt, + /* .pfnSendPkt = */ txsSerialSendPkt, + /* .pfnBabble = */ txsSerialBabble, + /* .pfnNotifyHowdy = */ txsSerialNotifyHowdy, + /* .pfnNotifyBye = */ txsSerialNotifyBye, + /* .pfnNotifyReboot = */ txsSerialNotifyReboot, + /* .u32EndMarker = */ UINT32_C(0x12345678) +}; + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/TestExecServiceTcp.cpp b/src/VBox/ValidationKit/utils/TestExecServ/TestExecServiceTcp.cpp new file mode 100644 index 00000000..4158806c --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/TestExecServiceTcp.cpp @@ -0,0 +1,842 @@ +/* $Id: TestExecServiceTcp.cpp $ */ +/** @file + * TestExecServ - Basic Remote Execution Service, TCP/IP Transport Layer. + */ + +/* + * Copyright (C) 2010-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/poll.h> +#include <iprt/string.h> +#include <iprt/tcp.h> +#include <iprt/thread.h> +#include <iprt/time.h> + +#include "TestExecServiceInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The default server port. */ +#define TXS_TCP_DEF_BIND_PORT 5042 +/** The default client port. */ +#define TXS_TCP_DEF_CONNECT_PORT 5048 + +/** The default server bind address. */ +#define TXS_TCP_DEF_BIND_ADDRESS "" +/** The default client connect address (i.e. of the host server). */ +#define TXS_TCP_DEF_CONNECT_ADDRESS "10.0.2.2" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** @name TCP Parameters + * @{ */ +static enum { TXSTCPMODE_BOTH, TXSTCPMODE_CLIENT, TXSTCPMODE_SERVER } + g_enmTcpMode = TXSTCPMODE_BOTH; + +/** The addresses to bind to. Empty string means any. */ +static char g_szTcpBindAddr[256] = TXS_TCP_DEF_BIND_ADDRESS; +/** The TCP port to listen to. */ +static uint32_t g_uTcpBindPort = TXS_TCP_DEF_BIND_PORT; +/** The addresses to connect to if fRevesedSetupMode is @c true. */ +static char g_szTcpConnectAddr[256] = TXS_TCP_DEF_CONNECT_ADDRESS; +/** The TCP port to listen to. */ +static uint32_t g_uTcpConnectPort = TXS_TCP_DEF_CONNECT_PORT; +/** @} */ + +/** Critical section for serializing access to the next few variables. */ +static RTCRITSECT g_TcpCritSect; +/** Pointer to the TCP server instance. */ +static PRTTCPSERVER g_pTcpServer = NULL; +/** Thread calling RTTcpServerListen2. */ +static RTTHREAD g_hThreadTcpServer = NIL_RTTHREAD; +/** Thread calling RTTcpClientConnect. */ +static RTTHREAD g_hThreadTcpConnect = NIL_RTTHREAD; +/** The main thread handle (for signalling). */ +static RTTHREAD g_hThreadMain = NIL_RTTHREAD; +/** Stop connecting attempts when set. */ +static bool g_fTcpStopConnecting = false; +/** Connect cancel cookie. */ +static PRTTCPCLIENTCONNECTCANCEL volatile g_pTcpConnectCancelCookie = NULL; + +/** Socket of the current client. */ +static RTSOCKET g_hTcpClient = NIL_RTSOCKET; +/** Indicates whether g_hTcpClient comes from the server or from a client + * connect (relevant when closing it). */ +static bool g_fTcpClientFromServer = false; +/** The size of the stashed data. */ +static size_t g_cbTcpStashed = 0; +/** The size of the stashed data allocation. */ +static size_t g_cbTcpStashedAlloced = 0; +/** The stashed data. */ +static uint8_t *g_pbTcpStashed = NULL; + + + +/** + * Disconnects the current client. + */ +static void txsTcpDisconnectClient(void) +{ + int rc; + if (g_fTcpClientFromServer) + rc = RTTcpServerDisconnectClient2(g_hTcpClient); + else + rc = RTTcpClientClose(g_hTcpClient); + AssertRCSuccess(rc); + g_hTcpClient = NIL_RTSOCKET; +} + +/** + * Sets the current client socket in a safe manner. + * + * @returns NIL_RTSOCKET if consumed, other wise hTcpClient. + * @param hTcpClient The client socket. + */ +static RTSOCKET txsTcpSetClient(RTSOCKET hTcpClient) +{ + RTCritSectEnter(&g_TcpCritSect); + if ( g_hTcpClient == NIL_RTSOCKET + && !g_fTcpStopConnecting + && g_hThreadMain != NIL_RTTHREAD + ) + { + g_fTcpClientFromServer = true; + g_hTcpClient = hTcpClient; + int rc = RTThreadUserSignal(g_hThreadMain); AssertRC(rc); + hTcpClient = NIL_RTSOCKET; + } + RTCritSectLeave(&g_TcpCritSect); + return hTcpClient; +} + +/** + * Server mode connection thread. + * + * @returns iprt status code. + * @param hSelf Thread handle. Ignored. + * @param pvUser Ignored. + */ +static DECLCALLBACK(int) txsTcpServerConnectThread(RTTHREAD hSelf, void *pvUser) +{ + RTSOCKET hTcpClient; + int rc = RTTcpServerListen2(g_pTcpServer, &hTcpClient); + Log(("txsTcpConnectServerThread: RTTcpServerListen2 -> %Rrc\n", rc)); + if (RT_SUCCESS(rc)) + { + hTcpClient = txsTcpSetClient(hTcpClient); + RTTcpServerDisconnectClient2(hTcpClient); + } + + RT_NOREF2(hSelf, pvUser); + return rc; +} + +/** + * Checks if it's a fatal RTTcpClientConnect return code. + * + * @returns true / false. + * @param rc The IPRT status code. + */ +static bool txsTcpIsFatalClientConnectStatus(int rc) +{ + return rc != VERR_NET_UNREACHABLE + && rc != VERR_NET_HOST_DOWN + && rc != VERR_NET_HOST_UNREACHABLE + && rc != VERR_NET_CONNECTION_REFUSED + && rc != VERR_TIMEOUT + && rc != VERR_NET_CONNECTION_TIMED_OUT; +} + +/** + * Client mode connection thread. + * + * @returns iprt status code. + * @param hSelf Thread handle. Use to sleep on. The main thread will + * signal it to speed up thread shutdown. + * @param pvUser Ignored. + */ +static DECLCALLBACK(int) txsTcpClientConnectThread(RTTHREAD hSelf, void *pvUser) +{ + RT_NOREF1(pvUser); + + for (;;) + { + /* Stop? */ + RTCritSectEnter(&g_TcpCritSect); + bool fStop = g_fTcpStopConnecting; + RTCritSectLeave(&g_TcpCritSect); + if (fStop) + return VINF_SUCCESS; + + /* Try connect. */ /** @todo make cancelable! */ + RTSOCKET hTcpClient; + Log2(("Calling RTTcpClientConnect(%s, %u,)...\n", g_szTcpConnectAddr, g_uTcpConnectPort)); + int rc = RTTcpClientConnectEx(g_szTcpConnectAddr, g_uTcpConnectPort, &hTcpClient, + RT_SOCKETCONNECT_DEFAULT_WAIT, &g_pTcpConnectCancelCookie); + Log(("txsTcpRecvPkt: RTTcpClientConnect -> %Rrc\n", rc)); + if (RT_SUCCESS(rc)) + { + hTcpClient = txsTcpSetClient(hTcpClient); + RTTcpClientCloseEx(hTcpClient, true /* fGracefulShutdown*/); + break; + } + + if (txsTcpIsFatalClientConnectStatus(rc)) + return rc; + + /* Delay a wee bit before retrying. */ + RTThreadUserWait(hSelf, 1536); + } + return VINF_SUCCESS; +} + +/** + * Wait on the threads to complete. + * + * @returns Thread status (if collected), otherwise VINF_SUCCESS. + * @param cMillies The period to wait on each thread. + */ +static int txsTcpConnectWaitOnThreads(RTMSINTERVAL cMillies) +{ + int rcRet = VINF_SUCCESS; + + if (g_hThreadTcpConnect != NIL_RTTHREAD) + { + int rcThread; + int rc2 = RTThreadWait(g_hThreadTcpConnect, cMillies, &rcThread); + if (RT_SUCCESS(rc2)) + { + g_hThreadTcpConnect = NIL_RTTHREAD; + rcRet = rcThread; + } + } + + if (g_hThreadTcpServer != NIL_RTTHREAD) + { + int rcThread; + int rc2 = RTThreadWait(g_hThreadTcpServer, cMillies, &rcThread); + if (RT_SUCCESS(rc2)) + { + g_hThreadTcpServer = NIL_RTTHREAD; + if (RT_SUCCESS(rc2)) + rcRet = rcThread; + } + } + return rcRet; +} + +/** + * Connects to the peer. + * + * @returns VBox status code. Updates g_hTcpClient and g_fTcpClientFromServer on + * success + */ +static int txsTcpConnect(void) +{ + int rc; + if (g_enmTcpMode == TXSTCPMODE_SERVER) + { + g_fTcpClientFromServer = true; + rc = RTTcpServerListen2(g_pTcpServer, &g_hTcpClient); + Log(("txsTcpRecvPkt: RTTcpServerListen2 -> %Rrc\n", rc)); + } + else if (g_enmTcpMode == TXSTCPMODE_CLIENT) + { + g_fTcpClientFromServer = false; + for (;;) + { + Log2(("Calling RTTcpClientConnect(%s, %u,)...\n", g_szTcpConnectAddr, g_uTcpConnectPort)); + rc = RTTcpClientConnect(g_szTcpConnectAddr, g_uTcpConnectPort, &g_hTcpClient); + Log(("txsTcpRecvPkt: RTTcpClientConnect -> %Rrc\n", rc)); + if (RT_SUCCESS(rc) || txsTcpIsFatalClientConnectStatus(rc)) + break; + + /* Delay a wee bit before retrying. */ + RTThreadSleep(1536); + } + } + else + { + Assert(g_enmTcpMode == TXSTCPMODE_BOTH); + RTTHREAD hSelf = RTThreadSelf(); + + /* + * Create client threads. + */ + RTCritSectEnter(&g_TcpCritSect); + RTThreadUserReset(hSelf); + g_hThreadMain = hSelf; + g_fTcpStopConnecting = false; + RTCritSectLeave(&g_TcpCritSect); + + txsTcpConnectWaitOnThreads(32); + + rc = VINF_SUCCESS; + if (g_hThreadTcpConnect == NIL_RTTHREAD) + { + g_pTcpConnectCancelCookie = NULL; + rc = RTThreadCreate(&g_hThreadTcpConnect, txsTcpClientConnectThread, NULL, 0, RTTHREADTYPE_DEFAULT, + RTTHREADFLAGS_WAITABLE, "tcpconn"); + } + if (g_hThreadTcpServer == NIL_RTTHREAD && RT_SUCCESS(rc)) + rc = RTThreadCreate(&g_hThreadTcpServer, txsTcpServerConnectThread, NULL, 0, RTTHREADTYPE_DEFAULT, + RTTHREADFLAGS_WAITABLE, "tcpserv"); + + RTCritSectEnter(&g_TcpCritSect); + + /* + * Wait for connection to be established. + */ + while ( RT_SUCCESS(rc) + && g_hTcpClient == NIL_RTSOCKET) + { + RTCritSectLeave(&g_TcpCritSect); + RTThreadUserWait(hSelf, 1536); + rc = txsTcpConnectWaitOnThreads(0); + RTCritSectEnter(&g_TcpCritSect); + } + + /* + * Cancel the threads. + */ + g_hThreadMain = NIL_RTTHREAD; + g_fTcpStopConnecting = true; + + RTCritSectLeave(&g_TcpCritSect); + RTTcpClientCancelConnect(&g_pTcpConnectCancelCookie); + } + + AssertMsg(RT_SUCCESS(rc) ? g_hTcpClient != NIL_RTSOCKET : g_hTcpClient == NIL_RTSOCKET, ("%Rrc %p\n", rc, g_hTcpClient)); + g_cbTcpStashed = 0; + return rc; +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnNotifyReboot} + */ +static DECLCALLBACK(void) txsTcpNotifyReboot(void) +{ + Log(("txsTcpNotifyReboot: RTTcpServerDestroy(%p)\n", g_pTcpServer)); + if (g_pTcpServer) + { + int rc = RTTcpServerDestroy(g_pTcpServer); + if (RT_FAILURE(rc)) + RTMsgInfo("RTTcpServerDestroy failed in txsTcpNotifyReboot: %Rrc", rc); + g_pTcpServer = NULL; + } +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnNotifyBye} + */ +static DECLCALLBACK(void) txsTcpNotifyBye(void) +{ + Log(("txsTcpNotifyBye: txsTcpDisconnectClient %RTsock\n", g_hTcpClient)); + txsTcpDisconnectClient(); +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnNotifyHowdy} + */ +static DECLCALLBACK(void) txsTcpNotifyHowdy(void) +{ + /* nothing to do here */ +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnBabble} + */ +static DECLCALLBACK(void) txsTcpBabble(PCTXSPKTHDR pPktHdr, RTMSINTERVAL cMsSendTimeout) +{ + /* + * Quietly ignore already disconnected client. + */ + RTSOCKET hTcpClient = g_hTcpClient; + if (hTcpClient == NIL_RTSOCKET) + return; + + /* + * Try send the babble reply. + */ + NOREF(cMsSendTimeout); /** @todo implement the timeout here; non-blocking write + select-on-write. */ + int rc; + size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, TXSPKT_ALIGNMENT); + do rc = RTTcpWrite(hTcpClient, pPktHdr, cbToSend); + while (rc == VERR_INTERRUPTED); + + /* + * Disconnect the client. + */ + Log(("txsTcpBabble: txsTcpDisconnectClient(%RTsock) (RTTcpWrite rc=%Rrc)\n", g_hTcpClient, rc)); + txsTcpDisconnectClient(); +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnSendPkt} + */ +static DECLCALLBACK(int) txsTcpSendPkt(PCTXSPKTHDR pPktHdr) +{ + Assert(pPktHdr->cb >= sizeof(TXSPKTHDR)); + + /* + * Fail if no client connection. + */ + RTSOCKET hTcpClient = g_hTcpClient; + if (hTcpClient == NIL_RTSOCKET) + return VERR_NET_NOT_CONNECTED; + + /* + * Write it. + */ + size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, TXSPKT_ALIGNMENT); + int rc = RTTcpWrite(hTcpClient, pPktHdr, cbToSend); + if ( RT_FAILURE(rc) + && rc != VERR_INTERRUPTED) + { + /* assume fatal connection error. */ + Log(("RTTcpWrite -> %Rrc -> txsTcpDisconnectClient(%RTsock)\n", rc, g_hTcpClient)); + txsTcpDisconnectClient(); + } + + return rc; +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnRecvPkt} + */ +static DECLCALLBACK(int) txsTcpRecvPkt(PPTXSPKTHDR ppPktHdr) +{ + int rc = VINF_SUCCESS; + *ppPktHdr = NULL; + + /* + * Do we have to wait for a client to connect? + */ + RTSOCKET hTcpClient = g_hTcpClient; + if (hTcpClient == NIL_RTSOCKET) + { + rc = txsTcpConnect(); + if (RT_FAILURE(rc)) + return rc; + hTcpClient = g_hTcpClient; Assert(hTcpClient != NIL_RTSOCKET); + } + + /* + * Read state. + */ + size_t offData = 0; + size_t cbData = 0; + size_t cbDataAlloced; + uint8_t *pbData = NULL; + + /* + * Any stashed data? + */ + if (g_cbTcpStashedAlloced) + { + offData = g_cbTcpStashed; + cbDataAlloced = g_cbTcpStashedAlloced; + pbData = g_pbTcpStashed; + + g_cbTcpStashed = 0; + g_cbTcpStashedAlloced = 0; + g_pbTcpStashed = NULL; + } + else + { + cbDataAlloced = RT_ALIGN_Z(64, TXSPKT_ALIGNMENT); + pbData = (uint8_t *)RTMemAlloc(cbDataAlloced); + if (!pbData) + return VERR_NO_MEMORY; + } + + /* + * Read and valid the length. + */ + while (offData < sizeof(uint32_t)) + { + size_t cbRead; + rc = RTTcpRead(hTcpClient, pbData + offData, sizeof(uint32_t) - offData, &cbRead); + if (RT_FAILURE(rc)) + break; + if (cbRead == 0) + { + Log(("txsTcpRecvPkt: RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#1)\n", rc)); + rc = VERR_NET_NOT_CONNECTED; + break; + } + offData += cbRead; + } + if (RT_SUCCESS(rc)) + { + ASMCompilerBarrier(); /* paranoia^3 */ + cbData = *(uint32_t volatile *)pbData; + if (cbData >= sizeof(TXSPKTHDR) && cbData <= TXSPKT_MAX_SIZE) + { + /* + * Align the length and reallocate the return packet it necessary. + */ + cbData = RT_ALIGN_Z(cbData, TXSPKT_ALIGNMENT); + if (cbData > cbDataAlloced) + { + void *pvNew = RTMemRealloc(pbData, cbData); + if (pvNew) + { + pbData = (uint8_t *)pvNew; + cbDataAlloced = cbData; + } + else + rc = VERR_NO_MEMORY; + } + if (RT_SUCCESS(rc)) + { + /* + * Read the remainder of the data. + */ + while (offData < cbData) + { + size_t cbRead; + rc = RTTcpRead(hTcpClient, pbData + offData, cbData - offData, &cbRead); + if (RT_FAILURE(rc)) + break; + if (cbRead == 0) + { + Log(("txsTcpRecvPkt: RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#2)\n", rc)); + rc = VERR_NET_NOT_CONNECTED; + break; + } + offData += cbRead; + } + } + } + else + rc = VERR_NET_PROTOCOL_ERROR; + } + if (RT_SUCCESS(rc)) + *ppPktHdr = (PTXSPKTHDR)pbData; + else + { + /* + * Deal with errors. + */ + if (rc == VERR_INTERRUPTED) + { + /* stash it away for the next call. */ + g_cbTcpStashed = cbData; + g_cbTcpStashedAlloced = cbDataAlloced; + g_pbTcpStashed = pbData; + } + else + { + RTMemFree(pbData); + + /* assume fatal connection error. */ + Log(("txsTcpRecvPkt: RTTcpRead -> %Rrc -> txsTcpDisconnectClient(%RTsock)\n", rc, g_hTcpClient)); + txsTcpDisconnectClient(); + } + } + + return rc; +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnPollSetAdd} + */ +static DECLCALLBACK(int) txsTcpPollSetAdd(RTPOLLSET hPollSet, uint32_t idStart) +{ + return RTPollSetAddSocket(hPollSet, g_hTcpClient, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, idStart); +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnPollIn} + */ +static DECLCALLBACK(bool) txsTcpPollIn(void) +{ + RTSOCKET hTcpClient = g_hTcpClient; + if (hTcpClient == NIL_RTSOCKET) + return false; + int rc = RTTcpSelectOne(hTcpClient, 0/*cMillies*/); + return RT_SUCCESS(rc); +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnTerm} + */ +static DECLCALLBACK(void) txsTcpTerm(void) +{ + /* Signal thread */ + if (RTCritSectIsInitialized(&g_TcpCritSect)) + { + RTCritSectEnter(&g_TcpCritSect); + g_fTcpStopConnecting = true; + RTCritSectLeave(&g_TcpCritSect); + } + + if (g_hThreadTcpConnect != NIL_RTTHREAD) + { + RTThreadUserSignal(g_hThreadTcpConnect); + RTTcpClientCancelConnect(&g_pTcpConnectCancelCookie); + } + + /* Shut down the server (will wake up thread). */ + if (g_pTcpServer) + { + Log(("txsTcpTerm: Destroying server...\n")); + int rc = RTTcpServerDestroy(g_pTcpServer); + if (RT_FAILURE(rc)) + RTMsgInfo("RTTcpServerDestroy failed in txsTcpTerm: %Rrc", rc); + g_pTcpServer = NULL; + } + + /* Shut down client */ + if (g_hTcpClient != NIL_RTSOCKET) + { + if (g_fTcpClientFromServer) + { + Log(("txsTcpTerm: Disconnecting client...\n")); + int rc = RTTcpServerDisconnectClient2(g_hTcpClient); + if (RT_FAILURE(rc)) + RTMsgInfo("RTTcpServerDisconnectClient2(%RTsock) failed in txsTcpTerm: %Rrc", g_hTcpClient, rc); + } + else + { + int rc = RTTcpClientClose(g_hTcpClient); + if (RT_FAILURE(rc)) + RTMsgInfo("RTTcpClientClose(%RTsock) failed in txsTcpTerm: %Rrc", g_hTcpClient, rc); + } + g_hTcpClient = NIL_RTSOCKET; + } + + /* Clean up stashing. */ + RTMemFree(g_pbTcpStashed); + g_pbTcpStashed = NULL; + g_cbTcpStashed = 0; + g_cbTcpStashedAlloced = 0; + + /* Wait for the thread (they should've had some time to quit by now). */ + txsTcpConnectWaitOnThreads(15000); + + /* Finally, clean up the critical section. */ + if (RTCritSectIsInitialized(&g_TcpCritSect)) + RTCritSectDelete(&g_TcpCritSect); + + Log(("txsTcpTerm: done\n")); +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnInit} + */ +static DECLCALLBACK(int) txsTcpInit(void) +{ + int rc = RTCritSectInit(&g_TcpCritSect); + if (RT_SUCCESS(rc) && g_enmTcpMode != TXSTCPMODE_CLIENT) + { + rc = RTTcpServerCreateEx(g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, &g_pTcpServer); + if (RT_FAILURE(rc)) + { + if (rc == VERR_NET_DOWN) + { + RTMsgInfo("RTTcpServerCreateEx(%s, %u,) failed: %Rrc, retrying for 20 seconds...\n", + g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, rc); + uint64_t StartMs = RTTimeMilliTS(); + do + { + RTThreadSleep(1000); + rc = RTTcpServerCreateEx(g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, &g_pTcpServer); + } while ( rc == VERR_NET_DOWN + && RTTimeMilliTS() - StartMs < 20000); + if (RT_SUCCESS(rc)) + RTMsgInfo("RTTcpServerCreateEx succceeded.\n"); + } + if (RT_FAILURE(rc)) + { + g_pTcpServer = NULL; + RTCritSectDelete(&g_TcpCritSect); + RTMsgError("RTTcpServerCreateEx(%s, %u,) failed: %Rrc\n", + g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, rc); + } + } + } + + return rc; +} + +/** Options */ +enum TXSTCPOPT +{ + TXSTCPOPT_MODE = 1000, + TXSTCPOPT_BIND_ADDRESS, + TXSTCPOPT_BIND_PORT, + TXSTCPOPT_CONNECT_ADDRESS, + TXSTCPOPT_CONNECT_PORT, + + /* legacy: */ + TXSTCPOPT_LEGACY_PORT, + TXSTCPOPT_LEGACY_CONNECT +}; + +/** + * @interface_method_impl{TXSTRANSPORT,pfnOption} + */ +static DECLCALLBACK(int) txsTcpOption(int ch, PCRTGETOPTUNION pVal) +{ + int rc; + + switch (ch) + { + case TXSTCPOPT_MODE: + if (!strcmp(pVal->psz, "both")) + g_enmTcpMode = TXSTCPMODE_BOTH; + else if (!strcmp(pVal->psz, "client")) + g_enmTcpMode = TXSTCPMODE_CLIENT; + else if (!strcmp(pVal->psz, "server")) + g_enmTcpMode = TXSTCPMODE_SERVER; + else + return RTMsgErrorRc(VERR_INVALID_PARAMETER, "Invalid TCP mode: '%s'\n", pVal->psz); + return VINF_SUCCESS; + + case TXSTCPOPT_BIND_ADDRESS: + rc = RTStrCopy(g_szTcpBindAddr, sizeof(g_szTcpBindAddr), pVal->psz); + if (RT_FAILURE(rc)) + return RTMsgErrorRc(VERR_INVALID_PARAMETER, "TCP bind address is too long (%Rrc)", rc); + return VINF_SUCCESS; + + case TXSTCPOPT_BIND_PORT: + g_uTcpBindPort = pVal->u16 == 0 ? TXS_TCP_DEF_BIND_PORT : pVal->u16; + return VINF_SUCCESS; + + case TXSTCPOPT_LEGACY_CONNECT: + g_enmTcpMode = TXSTCPMODE_CLIENT; + RT_FALL_THRU(); + case TXSTCPOPT_CONNECT_ADDRESS: + rc = RTStrCopy(g_szTcpConnectAddr, sizeof(g_szTcpConnectAddr), pVal->psz); + if (RT_FAILURE(rc)) + return RTMsgErrorRc(VERR_INVALID_PARAMETER, "TCP connect address is too long (%Rrc)", rc); + if (!g_szTcpConnectAddr[0]) + strcpy(g_szTcpConnectAddr, TXS_TCP_DEF_CONNECT_ADDRESS); + return VINF_SUCCESS; + + case TXSTCPOPT_CONNECT_PORT: + g_uTcpConnectPort = pVal->u16 == 0 ? TXS_TCP_DEF_CONNECT_PORT : pVal->u16; + return VINF_SUCCESS; + + case TXSTCPOPT_LEGACY_PORT: + if (pVal->u16 == 0) + { + g_uTcpBindPort = TXS_TCP_DEF_BIND_PORT; + g_uTcpConnectPort = TXS_TCP_DEF_CONNECT_PORT; + } + else + { + g_uTcpBindPort = pVal->u16; + g_uTcpConnectPort = pVal->u16; + } + return VINF_SUCCESS; + } + return VERR_TRY_AGAIN; +} + +/** + * @interface_method_impl{TXSTRANSPORT,pfnUsage} + */ +DECLCALLBACK(void) txsTcpUsage(PRTSTREAM pStream) +{ + RTStrmPrintf(pStream, + " --tcp-mode <both|client|server>\n" + " Selects the mode of operation.\n" + " Default: both\n" + " --tcp-bind-address <address>\n" + " The address(es) to listen to TCP connection on. Empty string\n" + " means any address, this is the default.\n" + " --tcp-bind-port <port>\n" + " The port to listen to TCP connections on.\n" + " Default: %u\n" + " --tcp-connect-address <address>\n" + " The address of the server to try connect to in client mode.\n" + " Default: " TXS_TCP_DEF_CONNECT_ADDRESS "\n" + " --tcp-connect-port <port>\n" + " The port on the server to connect to in client mode.\n" + " Default: %u\n" + , TXS_TCP_DEF_BIND_PORT, TXS_TCP_DEF_CONNECT_PORT); +} + +/** Command line options for the TCP/IP transport layer. */ +static const RTGETOPTDEF g_TcpOpts[] = +{ + { "--tcp-mode", TXSTCPOPT_MODE, RTGETOPT_REQ_STRING }, + { "--tcp-bind-address", TXSTCPOPT_BIND_ADDRESS, RTGETOPT_REQ_STRING }, + { "--tcp-bind-port", TXSTCPOPT_BIND_PORT, RTGETOPT_REQ_UINT16 }, + { "--tcp-connect-address", TXSTCPOPT_CONNECT_ADDRESS, RTGETOPT_REQ_STRING }, + { "--tcp-connect-port", TXSTCPOPT_CONNECT_PORT, RTGETOPT_REQ_UINT16 }, + + /* legacy */ + { "--tcp-port", TXSTCPOPT_LEGACY_PORT, RTGETOPT_REQ_UINT16 }, + { "--tcp-connect", TXSTCPOPT_LEGACY_CONNECT, RTGETOPT_REQ_STRING }, +}; + +/** TCP/IP transport layer. */ +const TXSTRANSPORT g_TcpTransport = +{ + /* .szName = */ "tcp", + /* .pszDesc = */ "TCP/IP", + /* .cOpts = */ &g_TcpOpts[0], + /* .paOpts = */ RT_ELEMENTS(g_TcpOpts), + /* .pfnUsage = */ txsTcpUsage, + /* .pfnOption = */ txsTcpOption, + /* .pfnInit = */ txsTcpInit, + /* .pfnTerm = */ txsTcpTerm, + /* .pfnPollIn = */ txsTcpPollIn, + /* .pfnPollSetAdd = */ txsTcpPollSetAdd, + /* .pfnRecvPkt = */ txsTcpRecvPkt, + /* .pfnSendPkt = */ txsTcpSendPkt, + /* .pfnBabble = */ txsTcpBabble, + /* .pfnNotifyHowdy = */ txsTcpNotifyHowdy, + /* .pfnNotifyBye = */ txsTcpNotifyBye, + /* .pfnNotifyReboot = */ txsTcpNotifyReboot, + /* .u32EndMarker = */ UINT32_C(0x12345678) +}; + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs-nat.sh b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs-nat.sh new file mode 100755 index 00000000..c1ffc915 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs-nat.sh @@ -0,0 +1,174 @@ +#!/bin/sh +## @file +# VirtualBox Test Execution Service Init Script for NATted setups. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +# chkconfig: 35 35 65 +# description: VirtualBox Test Execution Service +# +### BEGIN INIT INFO +# Provides: vboxtxs +# Required-Start: $network +# Required-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Description: VirtualBox Test Execution Service +### END INIT INFO + +PATH=$PATH:/bin:/sbin:/usr/sbin +SCRIPTNAME=vboxtxs-nat.sh + +CDROM_PATH=/media/cdrom +SCRATCH_PATH=/tmp/vboxtxs-scratch + +PIDFILE="/var/run/vboxtxs" + +# Preamble for Gentoo +if [ "`which $0`" = "/sbin/rc" ]; then + shift +fi + +begin_msg() +{ + test -n "${2}" && echo "${SCRIPTNAME}: ${1}." + logger -t "${SCRIPTNAME}" "${1}." +} + +succ_msg() +{ + logger -t "${SCRIPTNAME}" "${1}." +} + +fail_msg() +{ + echo "${SCRIPTNAME}: failed: ${1}." >&2 + logger -t "${SCRIPTNAME}" "failed: ${1}." +} + +killproc() { + kp_binary="${1##*/}" + pkill "${kp_binary}" || return 0 + sleep 1 + pkill "${kp_binary}" || return 0 + sleep 1 + pkill -9 "${kp_binary}" + return 0 +} + +case "`uname -m`" in + AMD64|amd64|X86_64|x86_64) + binary=/opt/validationkit/linux/amd64/TestExecService + ;; + + i386|x86|i486|i586|i686|i787) + binary=/opt/validationkit/linux/x86/TestExecService + ;; + + *) + binary=/opt/validationkit/linux/x86/TestExecService + ;; +esac + +fixAndTestBinary() { + chmod a+x "$binary" 2> /dev/null > /dev/null + test -x "$binary" || { + echo "Cannot run $binary" + exit 1 + } +} + +start() { + if ! test -f $PIDFILE; then + begin_msg "Starting VirtualBox Test Execution Service" console + fixAndTestBinary + mount /dev/cdrom "${CDROM_PATH}" 2> /dev/null > /dev/null + $binary --auto-upgrade --scratch="${SCRATCH_PATH}" --cdrom="${CDROM_PATH}" \ + --no-display-output --tcp-connect 10.0.2.2 > /dev/null + RETVAL=$? + test $RETVAL -eq 0 && sleep 2 && echo `pidof TestExecService` > $PIDFILE + if ! test -s "${PIDFILE}"; then + RETVAL=5 + fi + if test $RETVAL -eq 0; then + succ_msg "VirtualBox Test Execution service started" + else + fail_msg "VirtualBox Test Execution service failed to start" + fi + fi + return $RETVAL +} + +stop() { + if test -f $PIDFILE; then + begin_msg "Stopping VirtualBox Test Execution Service" console + killproc $binary + fi +} + +restart() { + stop && start +} + +status() { + echo -n "Checking for vboxtxs" + if [ -f $PIDFILE ]; then + echo " ...running" + else + echo " ...not running" + fi +} + +case "$1" in +start) + start + ;; +stop) + stop + ;; +restart) + restart + ;; +status) + status + ;; +setup) + ;; +cleanup) + ;; +*) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 +esac + +exit $RETVAL diff --git a/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs-runvm.desktop b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs-runvm.desktop new file mode 100644 index 00000000..95e7ddcb --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs-runvm.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=Application +Encoding=UTF-8 +Name=vboxtxs +Exec=sudo sh -c "/opt/validationkit/linux/vboxtxs-runvm start" + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs-runvm.sh b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs-runvm.sh new file mode 100755 index 00000000..59ecb2a0 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs-runvm.sh @@ -0,0 +1,208 @@ +#!/bin/sh +## @file +# VirtualBox Test Execution Service Init Script. +# + +# +# Copyright (C) 2018-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +# chkconfig: 35 35 65 +# description: VirtualBox Test Execution Service +# +### BEGIN INIT INFO +# Provides: vboxtxs-runvm +# Required-Start: $ALL +# Required-Stop: +# Default-Start: 5 +# Default-Stop: 0 1 6 +# Description: VirtualBox Test Execution Service +### END INIT INFO + +PATH=$PATH:/bin:/sbin:/usr/sbin +SCRIPTNAME=vboxtxs-runvm.sh + +CDROM_PATH=/media/cdrom +SCRATCH_PATH=/tmp/vboxtxs-scratch +SMOKEOUTPUT_PATH=/tmp/vboxtxs-smoketestoutput +DEVKMSG_PATH=/dev/kmsg + +PIDFILE="/var/run/vboxtxs" + +export TESTBOX_PATH_RESOURCES="/home/vbox/testrsrc" +SMOKETEST_SCRIPT="/opt/validationkit/tests/smoketests/tdSmokeTest1.py" +PYTHON_BINARY="python" + +# Preamble for Gentoo +if [ "`which $0`" = "/sbin/rc" ]; then + shift +fi + +kernlog_msg() { + test -n "$2" && echo "${SCRIPTNAME}: ${1}" + echo "${SCRIPTNAME}: ${1}" > $DEVKMSG_PATH +} + +dumpfile_to_kernlog() { + if test -f "$1"; then + kernlog_msg "---------------------- DUMP BEGIN ----------------------" + cat "$1" | while read LINE + do + kernlog_msg "${LINE}" + done + kernlog_msg "---------------------- DUMP END ------------------------" + rm -f "$1" + else + kernlog_msg "${1}: file not found" console + fi +} + +killproc() +{ + kp_binary="${1##*/}" + pkill "${kp_binary}" || return 0 + sleep 1 + pkill "${kp_binary}" || return 0 + sleep 1 + pkill -9 "${kp_binary}" + return 0 +} + +case "`uname -m`" in + AMD64|amd64|X86_64|x86_64) + binary=/opt/validationkit/linux/amd64/TestExecService + ;; + + i386|x86|i486|i586|i686|i787) + binary=/opt/validationkit/linux/x86/TestExecService + ;; + + *) + binary=/opt/validationkit/linux/x86/TestExecService + ;; +esac + +fixAndTestBinary() { + chmod a+x "$binary" 2> /dev/null > /dev/null + test -x "$binary" || { + echo "Cannot run $binary" + exit 1 + } +} + +testRsrcPath() { + test -d "$TESTBOX_PATH_RESOURCES" || { + echo "TESTBOX_PATH_RESOURCES directory not found" + exit 1 + } +} + +start() { + if ! test -f $PIDFILE; then + kernlog_msg "Starting Nested Smoke Test" console + fixAndTestBinary + testRsrcPath + $PYTHON_BINARY $SMOKETEST_SCRIPT -v -v -d --vbox-session-type gui --nic-attachment nat --quick all 1> "${SMOKEOUTPUT_PATH}" 2>&1 + RETVAL=$? + dumpfile_to_kernlog "${SMOKEOUTPUT_PATH}" + sync + sleep 15 + if test $RETVAL -eq 0; then + kernlog_msg "Nested Smoke Test done; Starting Test Execution service" console + mkdir -p "${CDROM_PATH}" + mount -o ro /dev/cdrom "${CDROM_PATH}" 2> /dev/null > /dev/null + $binary --auto-upgrade --scratch="${SCRATCH_PATH}" --cdrom="${CDROM_PATH}" --no-display-output > /dev/null + RETVAL=$? + test $RETVAL -eq 0 && sleep 3 && echo `pidof TestExecService` > $PIDFILE + if ! test -s "${PIDFILE}"; then + RETVAL=5 + fi + if test $RETVAL -eq 0; then + kernlog_msg "Test Execution service started" console + else + kernlog_msg "Test Execution service failed to start" console + RETVAL=6 + fi + else + kernlog_msg "Smoke Test failed! error code ${RETVAL}" console + RETVAL=7 + fi + else + kernlog_msg "Starting Nested Smoke Test failed! Pidfile ${PIDFILE} exists" console + RETVAL=9 + fi + return $RETVAL +} + +stop() { + if test -f $PIDFILE; then + kernlog_msg "Stopping Test Execution Service" + killproc $binary + rm -f $PIDFILE + fi +} + +restart() { + stop && start +} + +status() { + echo -n "Checking for vboxtxs" + if [ -f $PIDFILE ]; then + echo " ...running" + else + echo " ...not running" + fi +} + +case "$1" in +start) + start + ;; +stop) + stop + ;; +restart) + restart + ;; +status) + status + ;; +setup) + ;; +cleanup) + ;; +*) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 +esac + +exit $RETVAL + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs.service b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs.service new file mode 100644 index 00000000..b61426e9 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs.service @@ -0,0 +1,18 @@ +[Unit] +Description=VirtualBox Test Execution Service +SourcePath=/opt/validationkit/linux/vboxtxs + +[Service] +Type=forking +Restart=no +TimeoutSec=5min +IgnoreSIGPIPE=no +KillMode=process +GuessMainPID=no +RemainAfterExit=yes +ExecStart=/opt/validationkit/linux/vboxtxs start +ExecStop=/opt/validationkit/linux/vboxtxs stop + +[Install] +WantedBy=multi-user.target + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs.sh b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs.sh new file mode 100755 index 00000000..d895f34a --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/linux/vboxtxs.sh @@ -0,0 +1,173 @@ +#!/bin/sh +## @file +# VirtualBox Test Execution Service Init Script. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +# chkconfig: 35 35 65 +# description: VirtualBox Test Execution Service +# +### BEGIN INIT INFO +# Provides: vboxtxs +# Required-Start: $network +# Required-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Description: VirtualBox Test Execution Service +### END INIT INFO + +PATH=$PATH:/bin:/sbin:/usr/sbin +SCRIPTNAME=vboxtxs.sh + +CDROM_PATH=/media/cdrom +SCRATCH_PATH=/tmp/vboxtxs-scratch + +PIDFILE="/var/run/vboxtxs" + +# Preamble for Gentoo +if [ "`which $0`" = "/sbin/rc" ]; then + shift +fi + +begin_msg() +{ + test -n "${2}" && echo "${SCRIPTNAME}: ${1}." + logger -t "${SCRIPTNAME}" "${1}." +} + +succ_msg() +{ + logger -t "${SCRIPTNAME}" "${1}." +} + +fail_msg() +{ + echo "${SCRIPTNAME}: failed: ${1}." >&2 + logger -t "${SCRIPTNAME}" "failed: ${1}." +} + +killproc() { + kp_binary="${1##*/}" + pkill "${kp_binary}" || return 0 + sleep 1 + pkill "${kp_binary}" || return 0 + sleep 1 + pkill -9 "${kp_binary}" + return 0 +} + +case "`uname -m`" in + AMD64|amd64|X86_64|x86_64) + binary=/opt/validationkit/linux/amd64/TestExecService + ;; + + i386|x86|i486|i586|i686|i787) + binary=/opt/validationkit/linux/x86/TestExecService + ;; + + *) + binary=/opt/validationkit/linux/x86/TestExecService + ;; +esac + +fixAndTestBinary() { + chmod a+x "$binary" 2> /dev/null > /dev/null + test -x "$binary" || { + echo "Cannot run $binary" + exit 1 + } +} + +start() { + if ! test -f $PIDFILE; then + begin_msg "Starting VirtualBox Test Execution Service" console + fixAndTestBinary + mount /dev/cdrom "${CDROM_PATH}" 2> /dev/null > /dev/null + $binary --auto-upgrade --scratch="${SCRATCH_PATH}" --cdrom="${CDROM_PATH}" --no-display-output > /dev/null + RETVAL=$? + test $RETVAL -eq 0 && sleep 2 && echo `pidof TestExecService` > $PIDFILE + if ! test -s "${PIDFILE}"; then + RETVAL=5 + fi + if test $RETVAL -eq 0; then + succ_msg "VirtualBox Test Execution service started" + else + fail_msg "VirtualBox Test Execution service failed to start" + fi + fi + return $RETVAL +} + +stop() { + if test -f $PIDFILE; then + begin_msg "Stopping VirtualBox Test Execution Service" console + killproc $binary + fi +} + +restart() { + stop && start +} + +status() { + echo -n "Checking for vboxtxs" + if [ -f $PIDFILE ]; then + echo " ...running" + else + echo " ...not running" + fi +} + +case "$1" in +start) + start + ;; +stop) + stop + ;; +restart) + restart + ;; +status) + status + ;; +setup) + ;; +cleanup) + ;; +*) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 +esac + +exit $RETVAL diff --git a/src/VBox/ValidationKit/utils/TestExecServ/solaris/vboxtxs-sol10.xml b/src/VBox/ValidationKit/utils/TestExecServ/solaris/vboxtxs-sol10.xml new file mode 100644 index 00000000..5e7bbf8b --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/solaris/vboxtxs-sol10.xml @@ -0,0 +1,84 @@ +<?xml version='1.0'?> +<!-- + Solaris SMF service manifest for the VirtualBox Test eXecution Service. + $Id: vboxtxs-sol10.xml $ +--> +<!-- + Copyright (C) 2010-2022 Oracle and/or its affiliates. + + This file is part of VirtualBox base platform packages, as + available from https://www.virtualbox.org. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation, in version 3 of the + License. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <https://www.gnu.org/licenses>. + + The contents of this file may alternatively be used under the terms + of the Common Development and Distribution License Version 1.0 + (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + in the VirtualBox distribution, in which case the provisions of the + CDDL are applicable instead of those of the GPL. + + You may elect to license modified versions of this file under the + terms and conditions of either the GPL or the CDDL or both. + + SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +--> +<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1"> + +<service_bundle type='manifest' name='export'> +<service name='system/virtualbox/vboxtxs' type='service' version='1'> + + <create_default_instance enabled='false' /> + <single_instance/> + + <!-- Wait for the network to start up --> + <dependency name='milestone-network' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/network:default' /> + </dependency> + + <!-- Wait for devices to be initialized as we depend on vboxguest (PCI) --> + <dependency name='milestone-devices' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/devices:default' /> + </dependency> + + <!-- We wish to be started as late as possible... so go crazy with deps. --> + <dependency name='multi-user' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/multi-user:default' /> + </dependency> + <dependency name='multi-user-server' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/multi-user-server:default' /> + </dependency> + <dependency name='filesystem-local' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/system/filesystem/local:default' /> + </dependency> + <dependency name='filesystem-autofs' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/system/filesystem/autofs:default' /> + </dependency> + <dependency name='filesystem-volfs' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/system/filesystem/volfs:default' /> + </dependency> + + <!-- start + stop methods --> + <exec_method type='method' name='start' exec='/opt/VBoxTest/testsuite/solaris/vboxtxs.sh' timeout_seconds='30' /> + <exec_method type='method' name='stop' exec=':kill' timeout_seconds='60' /> + + <!-- Description --> + <template> + <common_name> + <loctext xml:lang='C'>VirtualBox Test eXecution Service</loctext> + </common_name> + </template> +</service> + +</service_bundle> + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/solaris/vboxtxs.sh b/src/VBox/ValidationKit/utils/TestExecServ/solaris/vboxtxs.sh new file mode 100755 index 00000000..2d8ae7b5 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/solaris/vboxtxs.sh @@ -0,0 +1,64 @@ +#!/bin/sh +## @file +# VirtualBox Test Execution Service Architecture Wrapper for Solaris. +# + +# +# Copyright (C) 2010-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +# 1. Change directory to the script directory (usually /opt/VBoxTest/). +set -x +MY_DIR=`dirname "$0"` +cd "${MY_DIR}" + +# 2. Determine the architecture. +MY_ARCH=`isainfo -k` +case "${MY_ARCH}" in + amd64) + MY_ARCH=amd64 + ;; + i386) + MY_ARCH=x86 + ;; + *) + echo "vboxtxs.sh: Unsupported architecture '${MY_ARCH}' returned by isainfo -k." >&2 + exit 2; + ;; +esac + +# 3. Exec the service. +exec "./${MY_ARCH}/TestExecService" \ + --cdrom="/cdrom/cdrom0/" \ + --scratch="/var/tmp/VBoxTest/" \ + --no-display-output \ + $* +exit 3; + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/solaris/vboxtxs.xml b/src/VBox/ValidationKit/utils/TestExecServ/solaris/vboxtxs.xml new file mode 100644 index 00000000..99cb75a7 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/solaris/vboxtxs.xml @@ -0,0 +1,84 @@ +<?xml version='1.0'?> +<!-- + Solaris SMF service manifest for the VirtualBox Test eXecution Service. + $Id: vboxtxs.xml $ +--> +<!-- + Copyright (C) 2010-2022 Oracle and/or its affiliates. + + This file is part of VirtualBox base platform packages, as + available from https://www.virtualbox.org. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation, in version 3 of the + License. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <https://www.gnu.org/licenses>. + + The contents of this file may alternatively be used under the terms + of the Common Development and Distribution License Version 1.0 + (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + in the VirtualBox distribution, in which case the provisions of the + CDDL are applicable instead of those of the GPL. + + You may elect to license modified versions of this file under the + terms and conditions of either the GPL or the CDDL or both. + + SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +--> +<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1"> + +<service_bundle type='manifest' name='export'> +<service name='system/virtualbox/vboxtxs' type='service' version='1'> + + <create_default_instance enabled='false' /> + <single_instance/> + + <!-- Wait for the network to start up --> + <dependency name='milestone-network' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/network:default' /> + </dependency> + + <!-- Wait for devices to be initialized as we depend on vboxguest (PCI) --> + <dependency name='milestone-devices' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/devices:default' /> + </dependency> + + <!-- We wish to be started as late as possible... so go crazy with deps. --> + <dependency name='multi-user' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/multi-user:default' /> + </dependency> + <dependency name='multi-user-server' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/multi-user-server:default' /> + </dependency> + <dependency name='filesystem-local' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/system/filesystem/local:default' /> + </dependency> + <dependency name='filesystem-autofs' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/system/filesystem/autofs:default' /> + </dependency> + <dependency name='filesystem-rmvolmgr' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/system/filesystem/rmvolmgr:default' /> + </dependency> + + <!-- start + stop methods --> + <exec_method type='method' name='start' exec='/opt/VBoxTest/testsuite/solaris/vboxtxs.sh' timeout_seconds='30' /> + <exec_method type='method' name='stop' exec=':kill' timeout_seconds='60' /> + + <!-- Description --> + <template> + <common_name> + <loctext xml:lang='C'>VirtualBox Test eXecution Service</loctext> + </common_name> + </template> +</service> + +</service_bundle> + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/vboxtxs-readme.txt b/src/VBox/ValidationKit/utils/TestExecServ/vboxtxs-readme.txt new file mode 100644 index 00000000..70de141f --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/vboxtxs-readme.txt @@ -0,0 +1,145 @@ +$Id: vboxtxs-readme.txt $ + + +VirtualBox Test eXecution Service +================================= + +This readme briefly describes how to install the Test eXecution Service (TXS) +on the various systems. + +There are currently two transport options for the TXS: + + - The default is to use it in TCP server mode, i.e. the test script needs + to know the guest's IP and therefore requires guest additions to be + installed as well. (Please use the latest stable additions compatible with + the VBox host versions you intend to test.) + + - The alternative is for NATted setups where TXS will act like a TCP client + and try connect to the test script on the host. Since this require that + TXS knows which IP to connect to, it's only really possible in a NATted + setup where we know the host IP is 10.0.2.2. + +Since r85596 TXS operates in both modes by default so the nat version of +the init scripts is not required anymore. Instead the other type can be installed +for both cases. + +Linux Installation +------------------ + +1. mkdir -p /opt/validationkit +2. scp/download VBoxValidationKit*.zip there. +3. unzip VBoxValidationKit*.zip +4. chmod -R u+w,a+x /opt/validationkit/ && chown -R root.root /opt/ +5. cd /etc/init.d/ + +6 a) For init.rc distros: + Link up the right init script (see connection type above): + nat) ln -s ../../opt/validationkit/linux/vboxtxs-nat ./vboxtxs + other) ln -s ../../opt/validationkit/linux/vboxtxs ./vboxtxs +6 b) Add vboxtxs to runlevels 2, 3, 5 and any other that makes sense + on the distro. There is usually some command for doing this, e.g. + ```update-rc.d vboxtxs defaults && update-rc.d vboxtxs enable``` (Debian-based) + or + ```chkconfig --add vboxtxs``` (OL/RHEL) + + ... or ... + +7 a) For systemd distros: Link/copy up the vboxtxs.system to [/usr]/lib/systemd/, e.g. + cp /opt/validationkit/linux/vboxtxs.service /etc/systemd/system + b) Enable the vboxtxs service via: + systemctl enable vboxtxs + + For all distros again: + +8a. Check the CD-ROM location (--cdrom <path>) in vboxtxs and fix it so it's correct, make sure + to update in svn as well. +8b. Optional: If no suitable CD-ROM location is available on the guest yet, do a: + mkdir -p /media/cdrom; vi /etc/fstab + and enter this in /etc/fstab: + /dev/sr0<tab>/media/cdrom<tab>udf,iso9660<tab>user,noauto,exec,utf8<tab>0<tab>0 +8c. Optional: If SELinux denies execution of TXS, make sure to allow this, based on + how the distribution handles SELinux exceptions. Often there even is a GUI for that + (e.g. Oracle Linux 8+). +9. Make sure that the package sources are still valid and up to date (apt / yum / ++) +10. reboot / done. +11. Do test. + + +OS/2 Installation +-------------------- + +1. Start an "OS/2 Window" ("OS/2 System" -> "Command Prompts") +2. md C:\Apps +3. cd C:\Apps +4. Mount the validationkit iso. +5. copy D:\os2\x86\* C:\Apps +5. copy D:\os2\x86\libc*.dll C:\OS2\DLL\ +6. Open C:\startup.cmd in an editor (tedit.exe for instance or e.exe). +7. Add the line "start /C C:\Apps\TestExecService.exe --foreground" at the top of the file. +8. reboot / done +9. Do test. + + +Solaris Installation +-------------------- + +1. Start the guest and open a root console. +2. mkdir -p /opt/VBoxTest +3. cd /opt/VBoxTest +4. scp/download VBoxValidationKit*.zip there. +5. unzip VBoxValidationKit*.zip +6. chmod -R u+w,a+x /opt/VBoxTest/ +7. Import the right service setup depending on the Solaris version: + <= 10u9) /usr/sbin/svccfg import /opt/VBoxTest/validationkit/solaris/vboxtxs-sol10.xml + >= 11.0) /usr/sbin/svccfg import /opt/VBoxTest/validationkit/solaris/vboxtxs.xml +8. /usr/sbin/svcadm enable svc:/system/virtualbox/vboxtxs +9. reboot / done. + +To remove the service before repeating steps 7 & 8: +1. /usr/sbin/svcadm disable -s svc:/system/virtualbox/vboxtxs:default +2. /usr/sbin/svccfg delete svc:/system/virtualbox/vboxtxs:default + +Note. To configure dhcp for more a new interface the files +/etc/hostname.<if#X> and /etc/dhcp.<ifnm#> have to exist. If you want the VM +to work with any network card you throw at it, create /etc/*.pcn[01] and +/etc/*.e1000g[012] as Solaris will remember it has seen the other variants +before and use a different instance number (or something to that effect). + + +Windows Installation +-------------------- + +1. Log on as Administrator. +2. Make sure you have set a secure password, which you'll need in step 9. +3. Start CMD.EXE or equivalent. +4. md C:\Apps +5. cd C:\Apps +6. Mount the validationkit iso. +7. copy D:\win\* C:\Apps +8. copy D:\win\<x86 or amd64>\* C:\Apps +9. Put the password from step 2 into the right service setup (see connection + type above) and import it into the registry: + nat) start C:\Apps\vboxtxs-nat.reg + other) start C:\Apps\vboxtxs.reg +10. Make sure that the CD-ROM location is assigned to D: (via "Disk Management"). +11. reboot / done +12. Do test. + +NT 3.1 and 3.x tricks: +- Make sure the file system is NTFS. Observed issues converting 2GB partitions, + more success with smaller. +- For NT3.1 PCNET drivers can be found on the net. No DHCP, so NAT only with + IP 10.0.2.15, 10.0.2.2 as gateway, and 10.0.2.3 as DNS with --natdnsproxy1 on. +- On NT3.1 you need to add SystemDrive=C: to the environment. +- Need to perform registry edits manually. +- Use startup folder instead of non-exising Windows/Run key. + + +Testing the setup +----------------- + +1. Make sure the validationkit.iso is inserted. +2. Boot / reboot the guest. +3. Depending on the TXS transport options: + nat) python testdriver/tst-txsclient.py --reversed-setup + other) python testdriver/tst-txsclient.py --hostname <guest-ip> diff --git a/src/VBox/ValidationKit/utils/TestExecServ/vboxtxs-runvm-readme.txt b/src/VBox/ValidationKit/utils/TestExecServ/vboxtxs-runvm-readme.txt new file mode 100644 index 00000000..f8745391 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/vboxtxs-runvm-readme.txt @@ -0,0 +1,54 @@ +$Id: vboxtxs-runvm-readme.txt $ + + +VirtualBox Test eXecution Service +================================= + +This readme briefly describes how to install the Test eXecution Service (TXS) +for nested hardware-virtualization smoke testing on the various systems. + +The basic idea is to execute one smoke test within the VM and then launch +the regular TXS service in the VM to report success or failure to the host. + +Linux Installation +------------------ + +1. scp/download latest release build of VirtualBox and install it in the VM. +2. scp/download the required smoke test VDI from remote test-resource to + /home/vbox/testrsrc/3.0/tcp/win2k3ent-acpi.vdi +3. cd /root +3. scp/download VBoxValidationKit*.zip there. +5. unzip VBoxValidationKit*.zip +6. chmod -R u+w,a+x /opt/validationkit/ +7a. Gnome: Copy /opt/validationkit/linux/vboxtxs-runvm.desktop to /etc/xdg/autostart +7b. KDE/Others: TODO: Document other desktop managers +8. Add the vbox user to sudo group using: + sudo usermod -a -G sudo vbox +9. Ensure no password is required for vbox when using sudo: + sudo echo "vbox ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/username +10. Check the cdrom location and /dev/kmsg equivalent of your linux distro + in /opt/validationkit/linux/vboxtxs-runvm and fix it so it's correct +11. Reboot / done. + +TODO: Document other OSes as we add them. + +Note: vboxtxs-runvm uses a GUI session to launch the nested-VM for better +visibility when troubleshooting the nested smoke test. + +If this causes problems try troubleshooting the XAUTHORITY and DISPLAY +environment variables in vboxtxs-runvm.service. It might differ depending +on the display manager of the particular linux distro. + + + +Testing the setup +----------------- + +1. Make sure the validationkit.iso is inserted. +2. Boot / reboot the guest. +3. To test the connection - Depending on the TXS transport options: + nat) python testdriver/tst-txsclient.py --reversed-setup + other) python testdriver/tst-txsclient.py --hostname <guest-ip> +4. To test the smoke test: + python tests/smoketests/tdSmokeTest1.py -v -v -d --vbox-session-type gui --test-vms <guest-name> + diff --git a/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs-nat.cmd b/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs-nat.cmd new file mode 100644 index 00000000..d1a6cbf3 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs-nat.cmd @@ -0,0 +1,41 @@ +@REM REM @file
+@REM VirtualBox Test Execution Service Init Script for NATted VMs.
+@REM
+
+@REM
+REM
+REM Copyright (C) 2006-2022 Oracle and/or its affiliates.
+REM
+REM This file is part of VirtualBox base platform packages, as
+REM available from https://www.virtualbox.org.
+REM
+REM This program is free software; you can redistribute it and/or
+REM modify it under the terms of the GNU General Public License
+REM as published by the Free Software Foundation, in version 3 of the
+REM License.
+REM
+REM This program is distributed in the hope that it will be useful, but
+REM WITHOUT ANY WARRANTY; without even the implied warranty of
+REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+REM General Public License for more details.
+REM
+REM You should have received a copy of the GNU General Public License
+REM along with this program; if not, see <https://www.gnu.org/licenses>.
+REM
+REM The contents of this file may alternatively be used under the terms
+REM of the Common Development and Distribution License Version 1.0
+REM (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+REM in the VirtualBox distribution, in which case the provisions of the
+REM CDDL are applicable instead of those of the GPL.
+REM
+REM You may elect to license modified versions of this file under the
+REM terms and conditions of either the GPL or the CDDL or both.
+REM
+REM SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+REM
+
+%SystemDrive%\Apps\TestExecService.exe --foreground --display-output ^
+--cdrom D:\ --scratch C:\Temp\vboxtest --auto-upgrade ^
+--tcp-connect 10.0.2.2
+pause
+
diff --git a/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs-nat.reg b/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs-nat.reg new file mode 100644 index 00000000..20ab995b --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs-nat.reg @@ -0,0 +1,13 @@ +REGEDIT4
+
+[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon]
+"PowerdownAfterShutdown"="1"
+"AutoAdminLogon"="1"
+"ForceAutoLogon"="1"
+"DefaultUserName"="Administrator"
+; Placeholder password for test VM, see TestExecServ/vboxtxs-readme.txt
+"DefaultPassword"="Please_put_your_secure_password_here_see_TestExecServ/vboxtxs-readme.txt"
+
+[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run]
+"NTConfiguration"="c:\\Apps\\vboxtxs-nat.cmd"
+
diff --git a/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs.cmd b/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs.cmd new file mode 100644 index 00000000..7bec8d29 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs.cmd @@ -0,0 +1,40 @@ +@REM REM @file
+@REM VirtualBox Test Execution Service Init Script.
+@REM
+
+@REM
+REM
+REM Copyright (C) 2006-2022 Oracle and/or its affiliates.
+REM
+REM This file is part of VirtualBox base platform packages, as
+REM available from https://www.virtualbox.org.
+REM
+REM This program is free software; you can redistribute it and/or
+REM modify it under the terms of the GNU General Public License
+REM as published by the Free Software Foundation, in version 3 of the
+REM License.
+REM
+REM This program is distributed in the hope that it will be useful, but
+REM WITHOUT ANY WARRANTY; without even the implied warranty of
+REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+REM General Public License for more details.
+REM
+REM You should have received a copy of the GNU General Public License
+REM along with this program; if not, see <https://www.gnu.org/licenses>.
+REM
+REM The contents of this file may alternatively be used under the terms
+REM of the Common Development and Distribution License Version 1.0
+REM (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+REM in the VirtualBox distribution, in which case the provisions of the
+REM CDDL are applicable instead of those of the GPL.
+REM
+REM You may elect to license modified versions of this file under the
+REM terms and conditions of either the GPL or the CDDL or both.
+REM
+REM SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+REM
+
+%SystemDrive%\Apps\TestExecService.exe --foreground --display-output ^
+--cdrom D:\ --scratch C:\Temp\vboxtest --auto-upgrade
+pause
+
diff --git a/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs.reg b/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs.reg new file mode 100644 index 00000000..037ac425 --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs.reg @@ -0,0 +1,13 @@ +REGEDIT4
+
+[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon]
+"PowerdownAfterShutdown"="1"
+"AutoAdminLogon"="1"
+"ForceAutoLogon"="1"
+"DefaultUserName"="Administrator"
+; Sample password for test VM, see TestExecServ/vboxtxs-readme.txt
+"DefaultPassword"="Please_put_your_secure_password_here_see_TestExecServ/vboxtxs-readme.txt"
+
+[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run]
+"NTConfiguration"="c:\\Apps\\vboxtxs.cmd"
+
diff --git a/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs.xml b/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs.xml new file mode 100644 index 00000000..f67211bb --- /dev/null +++ b/src/VBox/ValidationKit/utils/TestExecServ/win/vboxtxs.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-16"?> +<!-- + Copyright (C) 2019-2022 Oracle and/or its affiliates. + + This file is part of VirtualBox base platform packages, as + available from https://www.virtualbox.org. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation, in version 3 of the + License. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <https://www.gnu.org/licenses>. + + The contents of this file may alternatively be used under the terms + of the Common Development and Distribution License Version 1.0 + (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + in the VirtualBox distribution, in which case the provisions of the + CDDL are applicable instead of those of the GPL. + + You may elect to license modified versions of this file under the + terms and conditions of either the GPL or the CDDL or both. + + SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +--> +<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"> + <RegistrationInfo> + <Date>2019-05-27T18:41:34.3048528</Date> + <Author>Administrator</Author> + </RegistrationInfo> + <Triggers> + <LogonTrigger> + <Enabled>true</Enabled> + <UserId>Administrator</UserId> + </LogonTrigger> + </Triggers> + <Principals> + <Principal id="Author"> + <UserId>Administrator</UserId> + <LogonType>InteractiveToken</LogonType> + <RunLevel>HighestAvailable</RunLevel> + </Principal> + </Principals> + <Settings> + <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy> + <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries> + <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries> + <AllowHardTerminate>true</AllowHardTerminate> + <StartWhenAvailable>false</StartWhenAvailable> + <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable> + <IdleSettings> + <StopOnIdleEnd>false</StopOnIdleEnd> + <RestartOnIdle>false</RestartOnIdle> + </IdleSettings> + <AllowStartOnDemand>true</AllowStartOnDemand> + <Enabled>true</Enabled> + <Hidden>false</Hidden> + <RunOnlyIfIdle>false</RunOnlyIfIdle> + <WakeToRun>false</WakeToRun> + <ExecutionTimeLimit>PT0S</ExecutionTimeLimit> + <Priority>7</Priority> + </Settings> + <Actions Context="Author"> + <Exec> + <Command>C:\Apps\vboxtxs.cmd</Command> + </Exec> + </Actions> +</Task> + diff --git a/src/VBox/ValidationKit/utils/audio/Makefile.kmk b/src/VBox/ValidationKit/utils/audio/Makefile.kmk new file mode 100644 index 00000000..d85587bf --- /dev/null +++ b/src/VBox/ValidationKit/utils/audio/Makefile.kmk @@ -0,0 +1,213 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Audio Utilities. +# + +# +# Copyright (C) 2010-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Make sure the ValKit config file is included when the additions build +# is including just this makefile. +# +ifndef VBOX_VALIDATIONKIT_CONFIG_KMK_INCLUDED + include $(PATH_ROOT)/src/VBox/ValidationKit/Config.kmk +endif + + +# +# Append what we build here to PROGRAMS (at the top because it's a bit messy). +# +ifn1of ($(KBUILD_TARGET), os2 freebsd netbsd openbsd) + if defined(VBOX_ONLY_VALIDATIONKIT) || !defined(VBOX_ONLY_BUILD) + PROGRAMS += vkat + endif + ifdef VBOX_WITH_ADDITIONS_SHIPPING_AUDIO_TEST + if defined(VBOX_ONLY_ADDITIONS) || !defined(VBOX_ONLY_BUILD) + ifndef VBOX_WITH_ADDITIONS_FROM_BUILD_SERVER + ifdef VBOX_WITH_ADDITIONS + PROGRAMS += vkatadd + endif + endif + endif + endif +endif + + +# +# Utility to play sine wave to Default Audio Device. +# +if 0 # Disabled for now; does not work without WinMM.dll import validator files. + PROGRAMS.win += ntPlayToneWaveX + ntPlayToneWaveX_TEMPLATE = VBoxValidationKitR3 + ntPlayToneWaveX_SOURCES = ntPlayToneWaveX.cpp + ntPlayToneWaveX_LIBS += \ + WinMM.Lib +endif + + +# +# The Validation Kit Audio Test (VKAT) utility. +# +VKAT_PATH_AUDIO = $(PATH_ROOT)/src/VBox/Devices/Audio +vkat_TEMPLATE = VBoxValidationKitR3 +vkat_VBOX_IMPORT_CHECKER.win.x86 = nt4 +vkat_DEFS = VBOX_AUDIO_VKAT IN_VMM_R3 IN_VMM_STATIC +vkat_INCS = \ + $(PATH_ROOT)/src/VBox/Devices/build \ + $(PATH_ROOT)/src/VBox/Devices \ + $(PATH_ROOT)/src/VBox/Devices/Audio +vkat_SOURCES = \ + vkat.cpp \ + vkatCommon.cpp \ + vkatCmdGeneric.cpp \ + vkatDriverStack.cpp \ + $(VKAT_PATH_AUDIO)/AudioTest.cpp \ + $(VKAT_PATH_AUDIO)/DrvAudio.cpp \ + $(VKAT_PATH_AUDIO)/DrvHostAudioNull.cpp \ + $(VKAT_PATH_AUDIO)/AudioMixer.cpp \ + $(VKAT_PATH_AUDIO)/AudioMixBuffer.cpp \ + $(VKAT_PATH_AUDIO)/AudioHlp.cpp + +# Debug stuff. +ifdef VBOX_WITH_AUDIO_DEBUG + vkat_DEFS += VBOX_WITH_AUDIO_DEBUG + vkat_SOURCES += \ + $(VKAT_PATH_AUDIO)/DrvHostAudioDebug.cpp +endif + +# Self-test stuff. +vkat_DEFS += VBOX_WITH_AUDIO_VALIDATIONKIT +vkat_SOURCES += \ + vkatCmdSelfTest.cpp \ + $(VKAT_PATH_AUDIO)/DrvHostAudioValidationKit.cpp \ + $(VKAT_PATH_AUDIO)/AudioTestService.cpp \ + $(VKAT_PATH_AUDIO)/AudioTestServiceClient.cpp \ + $(VKAT_PATH_AUDIO)/AudioTestServiceProtocol.cpp \ + $(VKAT_PATH_AUDIO)/AudioTestServiceTcp.cpp + +ifdef VBOX_WITH_AUDIO_PULSE + vkat_DEFS += VBOX_WITH_AUDIO_PULSE + vkat_SOURCES += \ + $(VKAT_PATH_AUDIO)/DrvHostAudioPulseAudioStubs.cpp \ + $(VKAT_PATH_AUDIO)/DrvHostAudioPulseAudio.cpp +endif + +ifdef VBOX_WITH_AUDIO_ALSA + vkat_DEFS += VBOX_WITH_AUDIO_ALSA + vkat_SOURCES += \ + $(VKAT_PATH_AUDIO)/DrvHostAudioAlsa.cpp \ + $(VKAT_PATH_AUDIO)/DrvHostAudioAlsaStubs.cpp +endif + +ifdef VBOX_WITH_AUDIO_OSS + vkat_DEFS += VBOX_WITH_AUDIO_OSS + vkat_SOURCES += \ + $(VKAT_PATH_AUDIO)/DrvHostAudioOss.cpp +endif + +vkat_SOURCES.win += \ + $(VKAT_PATH_AUDIO)/DrvHostAudioDSound.cpp \ + $(VKAT_PATH_AUDIO)/DrvHostAudioWasApi.cpp +ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT + vkat_DEFS.win += VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT + vkat_SOURCES.win += \ + $(VKAT_PATH_AUDIO)/DrvHostAudioDSoundMMNotifClient.cpp +endif + +vkat_SOURCES.darwin = \ + $(VKAT_PATH_AUDIO)/DrvHostAudioCoreAudio.cpp \ + $(VKAT_PATH_AUDIO)/DrvHostAudioCoreAudioAuth.mm +vkat_LDFLAGS.darwin = \ + -framework CoreAudio \ + -framework AudioUnit \ + -framework AudioToolbox \ + -framework Foundation +ifn1of ($(VBOX_DEF_MACOSX_VERSION_MIN), 10.4 10.5 10.6) + vkat_LDFLAGS.darwin += \ + -framework AVFoundation +endif + + +# +# The additions variant of the audio test utility. +# +# We name it VBoxAudioTest though, to not clutter up Guest Additions +# installations with cryptic binaries not sporting 'VBox' as prefix. +# +vkatadd_TEMPLATE = VBoxGuestR3Exe +vkatadd_EXTENDS = vkat +vkatadd_EXTENDS_BY = appending +vkatadd_NAME = VBoxAudioTest +vkatadd_SDKS = VBOX_ZLIB_STATIC +vkatadd_LDFLAGS.darwin = -framework IOKit +vkatadd_LIBS.solaris = m + + +# +# Copy the valkit vkat to bin so it can be shipped with the host installer too. +# +# We use the same name as for the GAs, even if the binaries is the same as the +# validation kit. +# +ifdef VBOX_WITH_HOST_SHIPPING_AUDIO_TEST + ifn1of ($(KBUILD_TARGET), os2 freebsd netbsd openbsd) + ifndef VBOX_ONLY_ADDITIONS + INSTALLS += vkathost + vkathost_TEMPLATE = VBOXR3EXE + vkathost_SOURCES = $(vkat_1_TARGET)=>VBoxAudioTest$(HOSTSUFF_EXE) + vkathost_MODE = a+rx,u+w + endif + endif +endif + +if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK) \ + && 0 ## @todo r=bird: Disabled because nobody really wants or needs to run this during build other than Andy. + ## And more importantly, it breaks the build (os2, bsd*). + + PROGRAMS += tstVkatHostSelftest + tstVkatHostSelftest_EXTENDS = vkat + tstVkatHostSelftest_EXTENDS_BY = appending + tstVkatHostSelftest_INST = $(INST_TESTCASE) + tstVkatHostSelftest_DEFS.debug = VBOX_WITH_EF_WRAPS + + TESTING += $(tstVkatHostSelftest_0_OUTDIR)/tstVkatHostSelftest.run + $$(tstVkatHostSelftest_0_OUTDIR)/tstVkatHostSelftest.run: $$(tstVkatHostSelftest_1_TARGET) + export VKAT_RELEASE_LOG=-all; $(tstVkatHostSelftest_1_TARGET) selftest + $(QUIET)$(APPEND) -t "$@" "done" + +endif + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/audio/ntPlayToneWaveX.cpp b/src/VBox/ValidationKit/utils/audio/ntPlayToneWaveX.cpp new file mode 100644 index 00000000..78d05415 --- /dev/null +++ b/src/VBox/ValidationKit/utils/audio/ntPlayToneWaveX.cpp @@ -0,0 +1,226 @@ +/* $Id: ntPlayToneWaveX.cpp $ */ +/** @file + * ???? + */ + +/* + * Copyright (C) 2012-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/win/windows.h> + +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/stream.h> +#include <iprt/errcore.h> + +#define _USE_MATH_DEFINES +#include <math.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +uint32_t g_cSamplesPerSec = 44100; +uint32_t g_cSamplesPerPeriod = 100; // 441.0Hz for 44.1kHz +uint32_t g_cSamplesInBuffer = 4096; +double g_rdSecDuration = 5.0; + +uint32_t g_cbSample; // assuming 16-bit stereo (for now) + +HWAVEOUT g_hWaveOut; +HANDLE g_hWavEvent; + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + static const RTGETOPTDEF s_aOptions[] = + { + { "--samples-per-sec", 's', RTGETOPT_REQ_UINT32 }, + { "--period-in-samples", 'p', RTGETOPT_REQ_UINT32 }, + { "--bufsize-in-samples", 'b', RTGETOPT_REQ_UINT32 }, + { "--total-duration-in-secs", 'd', RTGETOPT_REQ_UINT32 } + }; + + RTGETOPTSTATE State; + RTGetOptInit(&State, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0); + RTGETOPTUNION ValueUnion; + int chOpt; + while ((chOpt = RTGetOpt(&State, &ValueUnion)) != 0) + { + switch (chOpt) + { + case 's': g_cSamplesPerSec = ValueUnion.u32; break; + case 'p': g_cSamplesPerPeriod = ValueUnion.u32; break; + case 'b': g_cSamplesInBuffer = ValueUnion.u32; break; + case 'd': g_rdSecDuration = ValueUnion.u32; break; + case 'h': + RTPrintf("usage: ntPlayToneWaveX.exe\n" + "[-s|--samples-per-sec]\n" + "[-p|--period-in-samples]\n" + "[-b|--bufsize-in-samples]\n" + "[-d|--total-duration-in-secs]\n" + "\n" + "Plays sine tone using ancient waveX API\n"); + return 0; + + default: + return RTGetOptPrintError(chOpt, &ValueUnion); + } + } + + + WAVEFORMATEX waveFormatEx = { 0 }; + MMRESULT mmresult; + + waveFormatEx.wFormatTag = WAVE_FORMAT_PCM; + waveFormatEx.nChannels = 2; + waveFormatEx.nSamplesPerSec = g_cSamplesPerSec; + waveFormatEx.wBitsPerSample = 16; + waveFormatEx.nBlockAlign = g_cbSample = waveFormatEx.nChannels * waveFormatEx.wBitsPerSample / 8; + waveFormatEx.nAvgBytesPerSec = waveFormatEx.nBlockAlign * waveFormatEx.nSamplesPerSec; + waveFormatEx.cbSize = 0; + + g_hWavEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + mmresult = waveOutOpen(&g_hWaveOut, WAVE_MAPPER, &waveFormatEx, (DWORD_PTR)g_hWavEvent, NULL, CALLBACK_EVENT); + + if (mmresult != MMSYSERR_NOERROR) + { + RTMsgError("waveOutOpen failed with 0x%X\n", mmresult); + return -1; + } + + + uint32_t ui32SamplesToPlayTotal = (uint32_t)(g_rdSecDuration * g_cSamplesPerSec); + uint32_t ui32SamplesToPlay = ui32SamplesToPlayTotal; + uint32_t ui32SamplesPlayed = 0; + uint32_t ui32SamplesForWavBuf; + + WAVEHDR waveHdr1 = {0}, waveHdr2 = {0}, *pWaveHdr, *pWaveHdrPlaying, *pWaveHdrWaiting; + uint32_t i, k; + DWORD res; + + int16_t *i16Samples1 = (int16_t *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, g_cSamplesInBuffer * g_cbSample); + int16_t *i16Samples2 = (int16_t *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, g_cSamplesInBuffer * g_cbSample); + + k = 0; // This is discrete time really!!! + + for (i = 0; i < g_cSamplesInBuffer; i++, k++) + { + i16Samples1[2 * i] = (uint16_t)(10000.0 * sin(2.0 * M_PI * k / g_cSamplesPerPeriod)); + i16Samples1[2 * i + 1] = i16Samples1[2 * i]; + } + + ui32SamplesForWavBuf = min(ui32SamplesToPlay, g_cSamplesInBuffer); + + waveHdr1.lpData = (LPSTR)i16Samples1; + waveHdr1.dwBufferLength = ui32SamplesForWavBuf * g_cbSample; + waveHdr1.dwFlags = 0; + waveHdr1.dwLoops = 0; + + ui32SamplesToPlay -= ui32SamplesForWavBuf; + ui32SamplesPlayed += ui32SamplesForWavBuf; + + pWaveHdrPlaying = &waveHdr1; + + mmresult = waveOutPrepareHeader(g_hWaveOut, pWaveHdrPlaying, sizeof(WAVEHDR)); + mmresult = waveOutWrite(g_hWaveOut, pWaveHdrPlaying, sizeof(WAVEHDR)); + //RTMsgInfo("waveOutWrite completes with %d\n", mmresult); + + res = WaitForSingleObject(g_hWavEvent, INFINITE); + //RTMsgInfo("WaitForSingleObject completes with %d\n\n", res); + + waveHdr2.lpData = (LPSTR)i16Samples2; + waveHdr2.dwBufferLength = 0; + waveHdr2.dwFlags = 0; + waveHdr2.dwLoops = 0; + + pWaveHdrWaiting = &waveHdr2; + + while (ui32SamplesToPlay > 0) + { + int16_t *i16Samples = (int16_t *)pWaveHdrWaiting->lpData; + + for (i = 0; i < g_cSamplesInBuffer; i++, k++) + { + i16Samples[2 * i] = (uint16_t)(10000.0 * sin(2.0 * M_PI * k / g_cSamplesPerPeriod)); + i16Samples[2 * i + 1] = i16Samples[2 * i]; + } + + ui32SamplesForWavBuf = min(ui32SamplesToPlay, g_cSamplesInBuffer); + + pWaveHdrWaiting->dwBufferLength = ui32SamplesForWavBuf * g_cbSample; + pWaveHdrWaiting->dwFlags = 0; + pWaveHdrWaiting->dwLoops = 0; + + + ui32SamplesToPlay -= ui32SamplesForWavBuf; + ui32SamplesPlayed += ui32SamplesForWavBuf; + + mmresult = waveOutPrepareHeader(g_hWaveOut, pWaveHdrWaiting, sizeof(WAVEHDR)); + mmresult = waveOutWrite(g_hWaveOut, pWaveHdrWaiting, sizeof(WAVEHDR)); + //RTMsgInfo("waveOutWrite completes with %d\n", mmresult); + + res = WaitForSingleObject(g_hWavEvent, INFINITE); + //RTMsgInfo("WaitForSingleObject completes with %d\n\n", res); + + mmresult = waveOutUnprepareHeader(g_hWaveOut, pWaveHdrPlaying, sizeof(WAVEHDR)); + //RTMsgInfo("waveOutUnprepareHeader completes with %d\n", mmresult); + + pWaveHdr = pWaveHdrWaiting; + pWaveHdrWaiting = pWaveHdrPlaying; + pWaveHdrPlaying = pWaveHdr; + } + + while (mmresult = waveOutUnprepareHeader(g_hWaveOut, pWaveHdrPlaying, sizeof(WAVEHDR))) + { + //Expecting WAVERR_STILLPLAYING + //RTMsgInfo("waveOutUnprepareHeader failed with 0x%X\n", mmresult); + Sleep(100); + } + + if (mmresult == MMSYSERR_NOERROR) + { + waveOutClose(g_hWaveOut); + } + + HeapFree(GetProcessHeap(), 0, i16Samples1); + HeapFree(GetProcessHeap(), 0, i16Samples2); +} + diff --git a/src/VBox/ValidationKit/utils/audio/readme.txt b/src/VBox/ValidationKit/utils/audio/readme.txt new file mode 100644 index 00000000..ba894b5a --- /dev/null +++ b/src/VBox/ValidationKit/utils/audio/readme.txt @@ -0,0 +1,2 @@ + +See docs/VBoxAudioValidationKitReadMe.txt or docs/VBoxAudioValidationKitReadMe.html. diff --git a/src/VBox/ValidationKit/utils/audio/vkat.cpp b/src/VBox/ValidationKit/utils/audio/vkat.cpp new file mode 100644 index 00000000..6ce54b3e --- /dev/null +++ b/src/VBox/ValidationKit/utils/audio/vkat.cpp @@ -0,0 +1,1638 @@ +/* $Id: vkat.cpp $ */ +/** @file + * Validation Kit Audio Test (VKAT) utility for testing and validating the audio stack. + */ + +/* + * Copyright (C) 2021-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_AUDIO_TEST + +#include <iprt/buildconfig.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/errcore.h> +#include <iprt/file.h> +#include <iprt/initterm.h> +#include <iprt/getopt.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/rand.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/test.h> + +#include <package-generated.h> +#include "product-generated.h" + +#include <VBox/version.h> +#include <VBox/log.h> + +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> /* for CoInitializeEx and SetConsoleCtrlHandler */ +#else +# include <signal.h> +#endif + +#include "vkatInternal.h" + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int audioVerifyOne(const char *pszPathSetA, const char *pszPathSetB, PAUDIOTESTVERIFYOPTS pOpts); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Backends description table. + * + * @note The first backend in the array is the default one for the platform. + */ +AUDIOTESTBACKENDDESC const g_aBackends[] = +{ +#ifdef VBOX_WITH_AUDIO_PULSE + { &g_DrvHostPulseAudio, "pulseaudio" }, + { &g_DrvHostPulseAudio, "pulse" }, + { &g_DrvHostPulseAudio, "pa" }, +#endif +/* + * Note: ALSA has to come second so that PulseAudio above always is the default on Linux-y OSes + * -- most distros are using an ALSA plugin for PulseAudio nowadays. + * However, some of these configurations do not seem to work by default (can't create audio streams). + * + * If PulseAudio is not available, the (optional) probing ("--probe-backends") will choose the "pure" ALSA stack instead then. + */ +#if defined(VBOX_WITH_AUDIO_ALSA) && defined(RT_OS_LINUX) + { &g_DrvHostALSAAudio, "alsa" }, +#endif +#ifdef VBOX_WITH_AUDIO_OSS + { &g_DrvHostOSSAudio, "oss" }, +#endif +#if defined(RT_OS_DARWIN) + { &g_DrvHostCoreAudio, "coreaudio" }, + { &g_DrvHostCoreAudio, "core" }, + { &g_DrvHostCoreAudio, "ca" }, +#endif +#if defined(RT_OS_WINDOWS) + { &g_DrvHostAudioWas, "wasapi" }, + { &g_DrvHostAudioWas, "was" }, + { &g_DrvHostDSound, "directsound" }, + { &g_DrvHostDSound, "dsound" }, + { &g_DrvHostDSound, "ds" }, +#endif +#ifdef VBOX_WITH_AUDIO_DEBUG + { &g_DrvHostDebugAudio, "debug" }, +#endif + { &g_DrvHostValidationKitAudio, "valkit" } +}; +AssertCompile(sizeof(g_aBackends) > 0 /* port me */); +/** Number of backends defined. */ +unsigned g_cBackends = RT_ELEMENTS(g_aBackends); + +/** + * Long option values for the 'test' command. + */ +enum +{ + VKAT_TEST_OPT_COUNT = 900, + VKAT_TEST_OPT_DEV, + VKAT_TEST_OPT_GUEST_ATS_ADDR, + VKAT_TEST_OPT_GUEST_ATS_PORT, + VKAT_TEST_OPT_HOST_ATS_ADDR, + VKAT_TEST_OPT_HOST_ATS_PORT, + VKAT_TEST_OPT_MODE, + VKAT_TEST_OPT_NO_AUDIO_OK, + VKAT_TEST_OPT_NO_VERIFY, + VKAT_TEST_OPT_OUTDIR, + VKAT_TEST_OPT_PAUSE, + VKAT_TEST_OPT_PCM_HZ, + VKAT_TEST_OPT_PCM_BIT, + VKAT_TEST_OPT_PCM_CHAN, + VKAT_TEST_OPT_PCM_SIGNED, + VKAT_TEST_OPT_PROBE_BACKENDS, + VKAT_TEST_OPT_TAG, + VKAT_TEST_OPT_TEMPDIR, + VKAT_TEST_OPT_VOL, + VKAT_TEST_OPT_TCP_BIND_ADDRESS, + VKAT_TEST_OPT_TCP_BIND_PORT, + VKAT_TEST_OPT_TCP_CONNECT_ADDRESS, + VKAT_TEST_OPT_TCP_CONNECT_PORT, + VKAT_TEST_OPT_TONE_DURATION_MS, + VKAT_TEST_OPT_TONE_VOL_PERCENT +}; + +/** + * Long option values for the 'verify' command. + */ +enum +{ + VKAT_VERIFY_OPT_MAX_DIFF_COUNT = 900, + VKAT_VERIFY_OPT_MAX_DIFF_PERCENT, + VKAT_VERIFY_OPT_MAX_SIZE_PERCENT, + VKAT_VERIFY_OPT_NORMALIZE +}; + +/** + * Common command line parameters. + */ +static const RTGETOPTDEF g_aCmdCommonOptions[] = +{ + { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + { "--daemonize", AUDIO_TEST_OPT_CMN_DAEMONIZE, RTGETOPT_REQ_NOTHING }, + { "--daemonized", AUDIO_TEST_OPT_CMN_DAEMONIZED, RTGETOPT_REQ_NOTHING }, + { "--debug-audio", AUDIO_TEST_OPT_CMN_DEBUG_AUDIO_ENABLE, RTGETOPT_REQ_NOTHING }, + { "--debug-audio-path", AUDIO_TEST_OPT_CMN_DEBUG_AUDIO_PATH, RTGETOPT_REQ_STRING }, +}; + +/** + * Command line parameters for test mode. + */ +static const RTGETOPTDEF g_aCmdTestOptions[] = +{ + { "--backend", 'b', RTGETOPT_REQ_STRING }, + { "--drvaudio", 'd', RTGETOPT_REQ_NOTHING }, + { "--exclude", 'e', RTGETOPT_REQ_UINT32 }, + { "--exclude-all", 'a', RTGETOPT_REQ_NOTHING }, + { "--guest-ats-addr", VKAT_TEST_OPT_GUEST_ATS_ADDR, RTGETOPT_REQ_STRING }, + { "--guest-ats-port", VKAT_TEST_OPT_GUEST_ATS_PORT, RTGETOPT_REQ_UINT32 }, + { "--host-ats-address", VKAT_TEST_OPT_HOST_ATS_ADDR, RTGETOPT_REQ_STRING }, + { "--host-ats-port", VKAT_TEST_OPT_HOST_ATS_PORT, RTGETOPT_REQ_UINT32 }, + { "--include", 'i', RTGETOPT_REQ_UINT32 }, + { "--outdir", VKAT_TEST_OPT_OUTDIR, RTGETOPT_REQ_STRING }, + { "--count", VKAT_TEST_OPT_COUNT, RTGETOPT_REQ_UINT32 }, + { "--device", VKAT_TEST_OPT_DEV, RTGETOPT_REQ_STRING }, + { "--pause", VKAT_TEST_OPT_PAUSE, RTGETOPT_REQ_UINT32 }, + { "--pcm-bit", VKAT_TEST_OPT_PCM_BIT, RTGETOPT_REQ_UINT8 }, + { "--pcm-chan", VKAT_TEST_OPT_PCM_CHAN, RTGETOPT_REQ_UINT8 }, + { "--pcm-hz", VKAT_TEST_OPT_PCM_HZ, RTGETOPT_REQ_UINT16 }, + { "--pcm-signed", VKAT_TEST_OPT_PCM_SIGNED, RTGETOPT_REQ_BOOL }, + { "--probe-backends", VKAT_TEST_OPT_PROBE_BACKENDS, RTGETOPT_REQ_NOTHING }, + { "--mode", VKAT_TEST_OPT_MODE, RTGETOPT_REQ_STRING }, + { "--no-audio-ok", VKAT_TEST_OPT_NO_AUDIO_OK, RTGETOPT_REQ_NOTHING }, + { "--no-verify", VKAT_TEST_OPT_NO_VERIFY, RTGETOPT_REQ_NOTHING }, + { "--tag", VKAT_TEST_OPT_TAG, RTGETOPT_REQ_STRING }, + { "--tempdir", VKAT_TEST_OPT_TEMPDIR, RTGETOPT_REQ_STRING }, + { "--vol", VKAT_TEST_OPT_VOL, RTGETOPT_REQ_UINT8 }, + { "--tcp-bind-addr", VKAT_TEST_OPT_TCP_BIND_ADDRESS, RTGETOPT_REQ_STRING }, + { "--tcp-bind-port", VKAT_TEST_OPT_TCP_BIND_PORT, RTGETOPT_REQ_UINT16 }, + { "--tcp-connect-addr", VKAT_TEST_OPT_TCP_CONNECT_ADDRESS, RTGETOPT_REQ_STRING }, + { "--tcp-connect-port", VKAT_TEST_OPT_TCP_CONNECT_PORT, RTGETOPT_REQ_UINT16 }, + { "--tone-duration", VKAT_TEST_OPT_TONE_DURATION_MS, RTGETOPT_REQ_UINT32 }, + { "--tone-vol", VKAT_TEST_OPT_TONE_VOL_PERCENT, RTGETOPT_REQ_UINT8 } +}; + +/** + * Command line parameters for verification mode. + */ +static const RTGETOPTDEF g_aCmdVerifyOptions[] = +{ + { "--max-diff-count", VKAT_VERIFY_OPT_MAX_DIFF_COUNT, RTGETOPT_REQ_UINT32 }, + { "--max-diff-percent", VKAT_VERIFY_OPT_MAX_DIFF_PERCENT, RTGETOPT_REQ_UINT8 }, + { "--max-size-percent", VKAT_VERIFY_OPT_MAX_SIZE_PERCENT, RTGETOPT_REQ_UINT8 }, + { "--normalize", VKAT_VERIFY_OPT_NORMALIZE, RTGETOPT_REQ_BOOL } +}; + +/** Terminate ASAP if set. Set on Ctrl-C. */ +bool volatile g_fTerminate = false; +/** The release logger. */ +PRTLOGGER g_pRelLogger = NULL; +/** The test handle. */ +RTTEST g_hTest; +/** The current verbosity level. */ +unsigned g_uVerbosity = 0; +/** DrvAudio: Enable debug (or not). */ +bool g_fDrvAudioDebug = false; +/** DrvAudio: The debug output path. */ +const char *g_pszDrvAudioDebug = NULL; + + +/** + * Get default backend. + */ +PCPDMDRVREG AudioTestGetDefaultBackend(void) +{ + return g_aBackends[0].pDrvReg; +} + + +/** + * Helper for handling --backend options. + * + * @returns Pointer to the specified backend, NULL if not found (error + * displayed). + * @param pszBackend The backend option value. + */ +PCPDMDRVREG AudioTestFindBackendOpt(const char *pszBackend) +{ + for (uintptr_t i = 0; i < RT_ELEMENTS(g_aBackends); i++) + if ( strcmp(pszBackend, g_aBackends[i].pszName) == 0 + || strcmp(pszBackend, g_aBackends[i].pDrvReg->szName) == 0) + return g_aBackends[i].pDrvReg; + RTMsgError("Unknown backend: '%s'", pszBackend); + return NULL; +} + + +/********************************************************************************************************************************* +* Test callbacks * +*********************************************************************************************************************************/ + +/** + * @copydoc FNAUDIOTESTSETUP + */ +static DECLCALLBACK(int) audioTestPlayToneSetup(PAUDIOTESTENV pTstEnv, PAUDIOTESTDESC pTstDesc, PAUDIOTESTPARMS pTstParmsAcq, void **ppvCtx) +{ + RT_NOREF(pTstDesc, ppvCtx); + + int rc = VINF_SUCCESS; + + if (strlen(pTstEnv->szDev)) + { + rc = audioTestDriverStackSetDevice(pTstEnv->pDrvStack, PDMAUDIODIR_OUT, pTstEnv->szDev); + if (RT_FAILURE(rc)) + return rc; + } + + pTstParmsAcq->enmType = AUDIOTESTTYPE_TESTTONE_PLAY; + pTstParmsAcq->enmDir = PDMAUDIODIR_OUT; + + pTstParmsAcq->TestTone = pTstEnv->ToneParms; + + pTstParmsAcq->TestTone.Hdr.idxTest = pTstEnv->idxTest; /* Assign unique test ID. */ + + return rc; +} + +/** + * @copydoc FNAUDIOTESTEXEC + */ +static DECLCALLBACK(int) audioTestPlayToneExec(PAUDIOTESTENV pTstEnv, void *pvCtx, PAUDIOTESTPARMS pTstParms) +{ + RT_NOREF(pvCtx); + + int rc = VINF_SUCCESS; + + PAUDIOTESTTONEPARMS const pToneParms = &pTstParms->TestTone; + + uint32_t const idxTest = pToneParms->Hdr.idxTest; + + RTTIMESPEC NowTimeSpec; + RTTimeExplode(&pToneParms->Hdr.tsCreated, RTTimeNow(&NowTimeSpec)); + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Playing test tone (%RU16Hz, %RU32ms)\n", + idxTest, (uint16_t)pToneParms->dbFreqHz, pToneParms->msDuration); + + /* + * 1. Arm the (host) ValKit ATS with the recording parameters. + */ + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "Test #%RU32: Telling ValKit audio driver on host to record new tone ...\n", idxTest); + + rc = AudioTestSvcClientToneRecord(&pTstEnv->u.Host.AtsClValKit, pToneParms); + if (RT_SUCCESS(rc)) + { + /* Give the Validaiton Kit audio driver on the host a bit of time to register / arming the new test. */ + RTThreadSleep(5000); /* Fudge factor. */ + + /* + * 2. Tell VKAT on guest to start playback. + */ + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Telling VKAT on guest to play tone ...\n", idxTest); + + rc = AudioTestSvcClientTonePlay(&pTstEnv->u.Host.AtsClGuest, pToneParms); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Test #%RU32: AudioTestSvcClientTonePlay() failed with %Rrc\n", idxTest, rc); + } + else + RTTestFailed(g_hTest, "Test #%RU32: AudioTestSvcClientToneRecord() failed with %Rrc\n", idxTest, rc); + + if (RT_SUCCESS(rc)) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Playing tone done\n", idxTest); + + /* Give the audio stack a random amount of time for draining data before the next iteration. */ + if (pTstEnv->cIterations > 1) + RTThreadSleep(RTRandU32Ex(2000, 5000)); /** @todo Implement some dedicated ATS command for this? */ + } + + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Test #%RU32: Playing test tone failed with %Rrc\n", idxTest, rc); + + return rc; +} + +/** + * @copydoc FNAUDIOTESTDESTROY + */ +static DECLCALLBACK(int) audioTestPlayToneDestroy(PAUDIOTESTENV pTstEnv, void *pvCtx) +{ + RT_NOREF(pTstEnv, pvCtx); + + return VINF_SUCCESS; +} + +/** + * @copydoc FNAUDIOTESTSETUP + */ +static DECLCALLBACK(int) audioTestRecordToneSetup(PAUDIOTESTENV pTstEnv, PAUDIOTESTDESC pTstDesc, PAUDIOTESTPARMS pTstParmsAcq, void **ppvCtx) +{ + RT_NOREF(pTstDesc, ppvCtx); + + int rc = VINF_SUCCESS; + + if (strlen(pTstEnv->szDev)) + { + rc = audioTestDriverStackSetDevice(pTstEnv->pDrvStack, PDMAUDIODIR_IN, pTstEnv->szDev); + if (RT_FAILURE(rc)) + return rc; + } + + pTstParmsAcq->enmType = AUDIOTESTTYPE_TESTTONE_RECORD; + pTstParmsAcq->enmDir = PDMAUDIODIR_IN; + + pTstParmsAcq->TestTone = pTstEnv->ToneParms; + + pTstParmsAcq->TestTone.Hdr.idxTest = pTstEnv->idxTest; /* Assign unique test ID. */ + + return rc; +} + +/** + * @copydoc FNAUDIOTESTEXEC + */ +static DECLCALLBACK(int) audioTestRecordToneExec(PAUDIOTESTENV pTstEnv, void *pvCtx, PAUDIOTESTPARMS pTstParms) +{ + RT_NOREF(pvCtx); + + int rc = VINF_SUCCESS; + + PAUDIOTESTTONEPARMS const pToneParms = &pTstParms->TestTone; + + uint32_t const idxTest = pToneParms->Hdr.idxTest; + + RTTIMESPEC NowTimeSpec; + RTTimeExplode(&pToneParms->Hdr.tsCreated, RTTimeNow(&NowTimeSpec)); + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Recording test tone (%RU16Hz, %RU32ms)\n", + idxTest, (uint16_t)pToneParms->dbFreqHz, pToneParms->msDuration); + + /* + * 1. Arm the (host) ValKit ATS with the playback parameters. + */ + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "Test #%RU32: Telling ValKit audio driver on host to inject recording data ...\n", idxTest); + + rc = AudioTestSvcClientTonePlay(&pTstEnv->u.Host.AtsClValKit, &pTstParms->TestTone); + if (RT_SUCCESS(rc)) + { + /* + * 2. Tell the guest ATS to start recording. + */ + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Telling VKAT on guest to record audio ...\n", idxTest); + + rc = AudioTestSvcClientToneRecord(&pTstEnv->u.Host.AtsClGuest, &pTstParms->TestTone); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Test #%RU32: AudioTestSvcClientToneRecord() failed with %Rrc\n", idxTest, rc); + } + else + RTTestFailed(g_hTest, "Test #%RU32: AudioTestSvcClientTonePlay() failed with %Rrc\n", idxTest, rc); + + if (RT_SUCCESS(rc)) + { + /* Wait a bit to let the left over audio bits being processed. */ + if (pTstEnv->cIterations > 1) + RTThreadSleep(RTRandU32Ex(2000, 5000)); /** @todo Implement some dedicated ATS command for this? */ + } + + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Test #%RU32: Recording test tone failed with %Rrc\n", idxTest, rc); + + return rc; +} + +/** + * @copydoc FNAUDIOTESTDESTROY + */ +static DECLCALLBACK(int) audioTestRecordToneDestroy(PAUDIOTESTENV pTstEnv, void *pvCtx) +{ + RT_NOREF(pTstEnv, pvCtx); + + return VINF_SUCCESS; +} + + +/********************************************************************************************************************************* +* Test execution * +*********************************************************************************************************************************/ + +/** Test definition table. */ +AUDIOTESTDESC g_aTests[] = +{ + /* pszTest fExcluded pfnSetup */ + { "PlayTone", false, audioTestPlayToneSetup, audioTestPlayToneExec, audioTestPlayToneDestroy }, + { "RecordTone", false, audioTestRecordToneSetup, audioTestRecordToneExec, audioTestRecordToneDestroy } +}; +/** Number of tests defined. */ +unsigned g_cTests = RT_ELEMENTS(g_aTests); + +/** + * Runs one specific audio test. + * + * @returns VBox status code. + * @param pTstEnv Test environment to use for running the test. + * @param pTstDesc Test to run. + */ +static int audioTestOne(PAUDIOTESTENV pTstEnv, PAUDIOTESTDESC pTstDesc) +{ + int rc = VINF_SUCCESS; + + AUDIOTESTPARMS TstParms; + audioTestParmsInit(&TstParms); + + RTTestSub(g_hTest, pTstDesc->pszName); + + if (pTstDesc->fExcluded) + { + RTTestSkipped(g_hTest, "Test #%RU32 is excluded from list, skipping", pTstEnv->idxTest); + return VINF_SUCCESS; + } + + pTstEnv->cIterations = pTstEnv->cIterations == 0 ? RTRandU32Ex(1, 10) : pTstEnv->cIterations; + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32 (%RU32 iterations total)\n", pTstEnv->idxTest, pTstEnv->cIterations); + + void *pvCtx = NULL; /* Test-specific opaque context. Optional and can be NULL. */ + + AssertPtr(pTstDesc->pfnExec); + for (uint32_t i = 0; i < pTstEnv->cIterations; i++) + { + int rc2; + + if (pTstDesc->pfnSetup) + { + rc2 = pTstDesc->pfnSetup(pTstEnv, pTstDesc, &TstParms, &pvCtx); + if (RT_FAILURE(rc2)) + RTTestFailed(g_hTest, "Test #%RU32 setup failed with %Rrc\n", pTstEnv->idxTest, rc2); + } + else + rc2 = VINF_SUCCESS; + + if (RT_SUCCESS(rc2)) + { + AssertPtrBreakStmt(pTstDesc->pfnExec, VERR_INVALID_POINTER); + rc2 = pTstDesc->pfnExec(pTstEnv, pvCtx, &TstParms); + if (RT_FAILURE(rc2)) + RTTestFailed(g_hTest, "Test #%RU32 execution failed with %Rrc\n", pTstEnv->idxTest, rc2); + } + + if (pTstDesc->pfnDestroy) + { + rc2 = pTstDesc->pfnDestroy(pTstEnv, pvCtx); + if (RT_FAILURE(rc2)) + RTTestFailed(g_hTest, "Test #%RU32 destruction failed with %Rrc\n", pTstEnv->idxTest, rc2); + } + + if (RT_SUCCESS(rc)) + rc = rc2; + + /* Keep going. */ + pTstEnv->idxTest++; + } + + RTTestSubDone(g_hTest); + + audioTestParmsDestroy(&TstParms); + + return rc; +} + +/** + * Runs all specified tests in a row. + * + * @returns VBox status code. + * @param pTstEnv Test environment to use for running all tests. + */ +int audioTestWorker(PAUDIOTESTENV pTstEnv) +{ + int rc = VINF_SUCCESS; + + if (pTstEnv->enmMode == AUDIOTESTMODE_GUEST) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Guest ATS running\n"); + + while (!g_fTerminate) + RTThreadSleep(100); + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Shutting down guest ATS ...\n"); + + int rc2 = AudioTestSvcStop(pTstEnv->pSrv); + if (RT_SUCCESS(rc)) + rc = rc2; + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Guest ATS shutdown complete\n"); + } + else if (pTstEnv->enmMode == AUDIOTESTMODE_HOST) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Using tag '%s'\n", pTstEnv->szTag); + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Telling ValKit audio driver on host to begin a new test set ...\n"); + rc = AudioTestSvcClientTestSetBegin(&pTstEnv->u.Host.AtsClValKit, pTstEnv->szTag); + if (RT_SUCCESS(rc)) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Telling VKAT on guest to begin a new test set ...\n"); + rc = AudioTestSvcClientTestSetBegin(&pTstEnv->u.Host.AtsClGuest, pTstEnv->szTag); + if (RT_FAILURE(rc)) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "Beginning test set on guest failed with %Rrc\n", rc); + } + else + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "Beginning test set on host (Validation Kit audio driver) failed with %Rrc\n", rc); + + if (RT_SUCCESS(rc)) + { + for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++) + { + int rc2 = audioTestOne(pTstEnv, &g_aTests[i]); + if (RT_SUCCESS(rc)) + rc = rc2; + + if (g_fTerminate) + break; + } + + if (RT_SUCCESS(rc)) + { + /** @todo Fudge! */ + RTMSINTERVAL const msWait = RTRandU32Ex(RT_MS_1SEC, RT_MS_5SEC); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "Waiting %RU32ms to let guest and the audio stack process remaining data ...\n", msWait); + RTThreadSleep(msWait); + } + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Ending test set on guest ...\n"); + int rc2 = AudioTestSvcClientTestSetEnd(&pTstEnv->u.Host.AtsClGuest, pTstEnv->szTag); + if (RT_FAILURE(rc2)) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Ending test set on guest failed with %Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Ending test set on host (Validation Kit audio driver) ...\n"); + rc2 = AudioTestSvcClientTestSetEnd(&pTstEnv->u.Host.AtsClValKit, pTstEnv->szTag); + if (RT_FAILURE(rc2)) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "Ending test set on host (Validation Kit audio driver) failed with %Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + if ( !g_fTerminate + && RT_SUCCESS(rc)) + { + /* + * Download guest + Validation Kit audio driver test sets to our output directory. + */ + char szFileName[RTPATH_MAX]; + if (RTStrPrintf2(szFileName, sizeof(szFileName), "%s-guest.tar.gz", pTstEnv->szTag)) + { + rc = RTPathJoin(pTstEnv->u.Host.szPathTestSetGuest, sizeof(pTstEnv->u.Host.szPathTestSetGuest), + pTstEnv->szPathOut, szFileName); + if (RT_SUCCESS(rc)) + { + if (RTStrPrintf2(szFileName, sizeof(szFileName), "%s-host.tar.gz", pTstEnv->szTag)) + { + rc = RTPathJoin(pTstEnv->u.Host.szPathTestSetValKit, sizeof(pTstEnv->u.Host.szPathTestSetValKit), + pTstEnv->szPathOut, szFileName); + } + else + rc = VERR_BUFFER_OVERFLOW; + } + else + rc = VERR_BUFFER_OVERFLOW; + + if (RT_SUCCESS(rc)) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Downloading guest test set to '%s'\n", + pTstEnv->u.Host.szPathTestSetGuest); + rc = AudioTestSvcClientTestSetDownload(&pTstEnv->u.Host.AtsClGuest, + pTstEnv->szTag, pTstEnv->u.Host.szPathTestSetGuest); + } + + if (RT_SUCCESS(rc)) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Downloading host test set to '%s'\n", + pTstEnv->u.Host.szPathTestSetValKit); + rc = AudioTestSvcClientTestSetDownload(&pTstEnv->u.Host.AtsClValKit, + pTstEnv->szTag, pTstEnv->u.Host.szPathTestSetValKit); + } + } + else + rc = VERR_BUFFER_OVERFLOW; + + if ( RT_SUCCESS(rc) + && !pTstEnv->fSkipVerify) + { + rc = audioVerifyOne(pTstEnv->u.Host.szPathTestSetGuest, pTstEnv->u.Host.szPathTestSetValKit, NULL /* pOpts */); + } + else + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Verification skipped\n"); + + if (!pTstEnv->fSkipVerify) + { + RTFileDelete(pTstEnv->u.Host.szPathTestSetGuest); + RTFileDelete(pTstEnv->u.Host.szPathTestSetValKit); + } + else + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Leaving test set files behind\n"); + } + } + } + else + rc = VERR_NOT_IMPLEMENTED; + + /* Clean up. */ + RTDirRemove(pTstEnv->szPathTemp); + RTDirRemove(pTstEnv->szPathOut); + + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Test worker failed with %Rrc", rc); + + return rc; +} + +/** Option help for the 'test' command. */ +static DECLCALLBACK(const char *) audioTestCmdTestHelp(PCRTGETOPTDEF pOpt) +{ + switch (pOpt->iShort) + { + case 'a': return "Exclude all tests from the list (useful to enable single tests later with --include)"; + case 'b': return "The audio backend to use"; + case 'd': return "Go via DrvAudio instead of directly interfacing with the backend"; + case 'e': return "Exclude the given test id from the list"; + case 'i': return "Include the given test id in the list"; + case VKAT_TEST_OPT_COUNT: return "Number of test iterations to perform for selected tests\n" + " Default: random number"; + case VKAT_TEST_OPT_DEV: return "Name of the input/output device to use\n" + " Default: default device"; + case VKAT_TEST_OPT_TONE_DURATION_MS: return "Test tone duration to play / record (ms)\n" + " Default: random duration"; + case VKAT_TEST_OPT_TONE_VOL_PERCENT: return "Test tone volume (percent)\n" + " Default: 100"; + case VKAT_TEST_OPT_GUEST_ATS_ADDR: return "Address of guest ATS to connect to\n" + " Default: " ATS_TCP_DEF_CONNECT_GUEST_STR; + case VKAT_TEST_OPT_GUEST_ATS_PORT: return "Port of guest ATS to connect to (needs NAT port forwarding)\n" + " Default: 6042"; /* ATS_TCP_DEF_CONNECT_PORT_GUEST */ + case VKAT_TEST_OPT_HOST_ATS_ADDR: return "Address of host ATS to connect to\n" + " Default: " ATS_TCP_DEF_CONNECT_HOST_ADDR_STR; + case VKAT_TEST_OPT_HOST_ATS_PORT: return "Port of host ATS to connect to\n" + " Default: 6052"; /* ATS_TCP_DEF_BIND_PORT_VALKIT */ + case VKAT_TEST_OPT_MODE: return "Test mode to use when running the tests"; + case VKAT_TEST_OPT_NO_AUDIO_OK: return "Enables running without any found audio hardware (e.g. servers)"; + case VKAT_TEST_OPT_NO_VERIFY: return "Skips the verification step"; + case VKAT_TEST_OPT_OUTDIR: return "Output directory to use"; + case VKAT_TEST_OPT_PAUSE: return "Not yet implemented"; + case VKAT_TEST_OPT_PCM_HZ: return "PCM Hertz (Hz) rate to use\n" + " Default: 44100"; + case VKAT_TEST_OPT_PCM_BIT: return "PCM sample bits (i.e. 16) to use\n" + " Default: 16"; + case VKAT_TEST_OPT_PCM_CHAN: return "PCM channels to use\n" + " Default: 2"; + case VKAT_TEST_OPT_PCM_SIGNED: return "PCM samples to use (signed = true, unsigned = false)\n" + " Default: true"; + case VKAT_TEST_OPT_PROBE_BACKENDS: return "Probes all (available) backends until a working one is found"; + case VKAT_TEST_OPT_TAG: return "Test set tag to use"; + case VKAT_TEST_OPT_TEMPDIR: return "Temporary directory to use"; + case VKAT_TEST_OPT_VOL: return "Audio volume (percent) to use"; + case VKAT_TEST_OPT_TCP_BIND_ADDRESS: return "TCP address listening to (server mode)"; + case VKAT_TEST_OPT_TCP_BIND_PORT: return "TCP port listening to (server mode)"; + case VKAT_TEST_OPT_TCP_CONNECT_ADDRESS: return "TCP address to connect to (client mode)"; + case VKAT_TEST_OPT_TCP_CONNECT_PORT: return "TCP port to connect to (client mode)"; + default: + break; + } + return NULL; +} + +/** + * Main (entry) function for the testing functionality of VKAT. + * + * @returns Program exit code. + * @param pGetState RTGetOpt state. + */ +static DECLCALLBACK(RTEXITCODE) audioTestMain(PRTGETOPTSTATE pGetState) +{ + AUDIOTESTENV TstEnv; + audioTestEnvInit(&TstEnv); + + int rc; + + PCPDMDRVREG pDrvReg = AudioTestGetDefaultBackend(); + uint8_t cPcmSampleBit = 0; + uint8_t cPcmChannels = 0; + uint32_t uPcmHz = 0; + bool fPcmSigned = true; + bool fProbeBackends = false; + bool fNoAudioOk = false; + + const char *pszGuestTcpAddr = NULL; + uint16_t uGuestTcpPort = ATS_TCP_DEF_BIND_PORT_GUEST; + const char *pszValKitTcpAddr = NULL; + uint16_t uValKitTcpPort = ATS_TCP_DEF_BIND_PORT_VALKIT; + + int ch; + RTGETOPTUNION ValueUnion; + while ((ch = RTGetOpt(pGetState, &ValueUnion))) + { + switch (ch) + { + case 'a': + for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++) + g_aTests[i].fExcluded = true; + break; + + case 'b': + pDrvReg = AudioTestFindBackendOpt(ValueUnion.psz); + if (pDrvReg == NULL) + return RTEXITCODE_SYNTAX; + break; + + case 'd': + TstEnv.IoOpts.fWithDrvAudio = true; + break; + + case 'e': + if (ValueUnion.u32 >= RT_ELEMENTS(g_aTests)) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid test number %u passed to --exclude", ValueUnion.u32); + g_aTests[ValueUnion.u32].fExcluded = true; + break; + + case VKAT_TEST_OPT_GUEST_ATS_ADDR: + pszGuestTcpAddr = ValueUnion.psz; + break; + + case VKAT_TEST_OPT_GUEST_ATS_PORT: + uGuestTcpPort = ValueUnion.u32; + break; + + case VKAT_TEST_OPT_HOST_ATS_ADDR: + pszValKitTcpAddr = ValueUnion.psz; + break; + + case VKAT_TEST_OPT_HOST_ATS_PORT: + uValKitTcpPort = ValueUnion.u32; + break; + + case VKAT_TEST_OPT_MODE: + if (TstEnv.enmMode != AUDIOTESTMODE_UNKNOWN) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Test mode (guest / host) already specified"); + TstEnv.enmMode = RTStrICmp(ValueUnion.psz, "guest") == 0 ? AUDIOTESTMODE_GUEST : AUDIOTESTMODE_HOST; + break; + + case VKAT_TEST_OPT_NO_AUDIO_OK: + fNoAudioOk = true; + break; + + case VKAT_TEST_OPT_NO_VERIFY: + TstEnv.fSkipVerify = true; + break; + + case 'i': + if (ValueUnion.u32 >= RT_ELEMENTS(g_aTests)) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid test number %u passed to --include", ValueUnion.u32); + g_aTests[ValueUnion.u32].fExcluded = false; + break; + + case VKAT_TEST_OPT_COUNT: + TstEnv.cIterations = ValueUnion.u32; + break; + + case VKAT_TEST_OPT_DEV: + rc = RTStrCopy(TstEnv.szDev, sizeof(TstEnv.szDev), ValueUnion.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Failed to copy out device: %Rrc", rc); + break; + + case VKAT_TEST_OPT_TONE_DURATION_MS: + TstEnv.ToneParms.msDuration = ValueUnion.u32; + break; + + case VKAT_TEST_OPT_TONE_VOL_PERCENT: + TstEnv.ToneParms.uVolumePercent = ValueUnion.u8; + break; + + case VKAT_TEST_OPT_PAUSE: + return RTMsgErrorExitFailure("Not yet implemented!"); + + case VKAT_TEST_OPT_OUTDIR: + rc = RTStrCopy(TstEnv.szPathOut, sizeof(TstEnv.szPathOut), ValueUnion.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Failed to copy out directory: %Rrc", rc); + break; + + case VKAT_TEST_OPT_PCM_BIT: + cPcmSampleBit = ValueUnion.u8; + break; + + case VKAT_TEST_OPT_PCM_CHAN: + cPcmChannels = ValueUnion.u8; + break; + + case VKAT_TEST_OPT_PCM_HZ: + uPcmHz = ValueUnion.u32; + break; + + case VKAT_TEST_OPT_PCM_SIGNED: + fPcmSigned = ValueUnion.f; + break; + + case VKAT_TEST_OPT_PROBE_BACKENDS: + fProbeBackends = true; + break; + + case VKAT_TEST_OPT_TAG: + rc = RTStrCopy(TstEnv.szTag, sizeof(TstEnv.szTag), ValueUnion.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Tag invalid, rc=%Rrc", rc); + break; + + case VKAT_TEST_OPT_TEMPDIR: + rc = RTStrCopy(TstEnv.szPathTemp, sizeof(TstEnv.szPathTemp), ValueUnion.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Temp dir invalid, rc=%Rrc", rc); + break; + + case VKAT_TEST_OPT_VOL: + TstEnv.IoOpts.uVolumePercent = ValueUnion.u8; + break; + + case VKAT_TEST_OPT_TCP_BIND_ADDRESS: + rc = RTStrCopy(TstEnv.TcpOpts.szBindAddr, sizeof(TstEnv.TcpOpts.szBindAddr), ValueUnion.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Bind address invalid, rc=%Rrc", rc); + break; + + case VKAT_TEST_OPT_TCP_BIND_PORT: + TstEnv.TcpOpts.uBindPort = ValueUnion.u16; + break; + + case VKAT_TEST_OPT_TCP_CONNECT_ADDRESS: + rc = RTStrCopy(TstEnv.TcpOpts.szConnectAddr, sizeof(TstEnv.TcpOpts.szConnectAddr), ValueUnion.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Connect address invalid, rc=%Rrc", rc); + break; + + case VKAT_TEST_OPT_TCP_CONNECT_PORT: + TstEnv.TcpOpts.uConnectPort = ValueUnion.u16; + break; + + AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion, &g_CmdTest); + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + /* + * Start testing. + */ + RTTestBanner(g_hTest); + + if (TstEnv.enmMode == AUDIOTESTMODE_UNKNOWN) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No test mode (--mode) specified!\n"); + + /* Validate TCP options. */ + if ( TstEnv.TcpOpts.szBindAddr[0] + && TstEnv.TcpOpts.szConnectAddr[0]) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Only one TCP connection mode (connect as client *or* bind as server) can be specified) at a time!\n"); + + /* Set new (override standard) I/O PCM properties if set by the user. */ + if ( cPcmSampleBit + || cPcmChannels + || uPcmHz) + { + PDMAudioPropsInit(&TstEnv.IoOpts.Props, + cPcmSampleBit ? cPcmSampleBit / 2 : 2 /* 16-bit */, fPcmSigned /* fSigned */, + cPcmChannels ? cPcmChannels : 2 /* Stereo */, uPcmHz ? uPcmHz : 44100); + } + + /* Do this first before everything else below. */ + rc = AudioTestDriverStackPerformSelftest(); + if (RT_FAILURE(rc)) + { + if (!fNoAudioOk) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Testing driver stack failed: %Rrc\n", rc); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "Warning: Testing driver stack not possible (%Rrc), but --no-audio-ok was specified. Running on a server without audio hardware?\n", rc); + } + + AUDIOTESTDRVSTACK DrvStack; + if (fProbeBackends) + rc = audioTestDriverStackProbe(&DrvStack, pDrvReg, + true /* fEnabledIn */, true /* fEnabledOut */, TstEnv.IoOpts.fWithDrvAudio); /** @todo Make in/out configurable, too. */ + else + rc = audioTestDriverStackInitEx(&DrvStack, pDrvReg, + true /* fEnabledIn */, true /* fEnabledOut */, TstEnv.IoOpts.fWithDrvAudio); /** @todo Make in/out configurable, too. */ + if (RT_FAILURE(rc)) + { + if (!fNoAudioOk) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unable to init driver stack: %Rrc\n", rc); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "Warning: Initializing driver stack not possible (%Rrc), but --no-audio-ok was specified. Running on a server without audio hardware?\n", rc); + } + + PPDMAUDIOHOSTDEV pDev; + rc = audioTestDevicesEnumerateAndCheck(&DrvStack, TstEnv.szDev, &pDev); + if (RT_FAILURE(rc)) + { + if (!fNoAudioOk) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Enumerating device(s) failed: %Rrc\n", rc); + } + + /* For now all tests have the same test environment and driver stack. */ + rc = audioTestEnvCreate(&TstEnv, &DrvStack); + if (RT_SUCCESS(rc)) + rc = audioTestWorker(&TstEnv); + + audioTestEnvDestroy(&TstEnv); + audioTestDriverStackDelete(&DrvStack); + + if (RT_FAILURE(rc)) /* Let us know that something went wrong in case we forgot to mention it. */ + RTTestFailed(g_hTest, "Testing failed with %Rrc\n", rc); + + /* + * Print summary and exit. + */ + return RTTestSummaryAndDestroy(g_hTest); +} + + +const VKATCMD g_CmdTest = +{ + "test", + audioTestMain, + "Runs audio tests and creates an audio test set.", + g_aCmdTestOptions, + RT_ELEMENTS(g_aCmdTestOptions), + audioTestCmdTestHelp, + true /* fNeedsTransport */ +}; + + +/********************************************************************************************************************************* +* Command: verify * +*********************************************************************************************************************************/ + +static int audioVerifyOpenTestSet(const char *pszPathSet, PAUDIOTESTSET pSet) +{ + int rc; + + char szPathExtracted[RTPATH_MAX]; + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Opening test set '%s'\n", pszPathSet); + + const bool fPacked = AudioTestSetIsPacked(pszPathSet); + + if (fPacked) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test set is an archive and needs to be unpacked\n"); + + if (!RTFileExists(pszPathSet)) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test set '%s' does not exist\n", pszPathSet); + rc = VERR_FILE_NOT_FOUND; + } + else + rc = VINF_SUCCESS; + + if (RT_SUCCESS(rc)) + { + char szPathTemp[RTPATH_MAX]; + rc = RTPathTemp(szPathTemp, sizeof(szPathTemp)); + if (RT_SUCCESS(rc)) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Using temporary directory '%s'\n", szPathTemp); + + rc = RTPathJoin(szPathExtracted, sizeof(szPathExtracted), szPathTemp, "vkat-testset-XXXX"); + if (RT_SUCCESS(rc)) + { + rc = RTDirCreateTemp(szPathExtracted, 0755); + if (RT_SUCCESS(rc)) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Unpacking archive to '%s'\n", szPathExtracted); + rc = AudioTestSetUnpack(pszPathSet, szPathExtracted); + if (RT_SUCCESS(rc)) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Archive successfully unpacked\n"); + } + } + } + } + } + else + rc = VINF_SUCCESS; + + if (RT_SUCCESS(rc)) + rc = AudioTestSetOpen(pSet, fPacked ? szPathExtracted : pszPathSet); + + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Unable to open / unpack test set archive: %Rrc", rc); + + return rc; +} + +/** + * Verifies one test set pair. + * + * @returns VBox status code. + * @param pszPathSetA Absolute path to test set A. + * @param pszPathSetB Absolute path to test set B. + * @param pOpts Verification options to use. Optional. + * When NULL, the (very strict) defaults will be used. + */ +static int audioVerifyOne(const char *pszPathSetA, const char *pszPathSetB, PAUDIOTESTVERIFYOPTS pOpts) +{ + RTTestSubF(g_hTest, "Verifying"); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Verifying test set '%s' with test set '%s'\n", pszPathSetA, pszPathSetB); + + AUDIOTESTSET SetA, SetB; + int rc = audioVerifyOpenTestSet(pszPathSetA, &SetA); + if (RT_SUCCESS(rc)) + { + rc = audioVerifyOpenTestSet(pszPathSetB, &SetB); + if (RT_SUCCESS(rc)) + { + AUDIOTESTERRORDESC errDesc; + if (pOpts) + rc = AudioTestSetVerifyEx(&SetA, &SetB, pOpts, &errDesc); + else + rc = AudioTestSetVerify(&SetA, &SetB, &errDesc); + if (RT_SUCCESS(rc)) + { + uint32_t const cErr = AudioTestErrorDescCount(&errDesc); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "%RU32 errors occurred while verifying\n", cErr); + + /** @todo Use some AudioTestErrorXXX API for enumeration here later. */ + PAUDIOTESTERRORENTRY pErrEntry; + RTListForEach(&errDesc.List, pErrEntry, AUDIOTESTERRORENTRY, Node) + { + if (RT_FAILURE(pErrEntry->rc)) + RTTestFailed(g_hTest, "%s\n", pErrEntry->szDesc); + else + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "%s\n", pErrEntry->szDesc); + } + + if (cErr == 0) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Verification successful\n"); + + AudioTestErrorDescDestroy(&errDesc); + } + else + RTTestFailed(g_hTest, "Verification failed with %Rrc", rc); + +#ifdef DEBUG + if (g_fDrvAudioDebug) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "\n" + "Use the following command line to re-run verification in the debugger:\n" + "gdb --args ./VBoxAudioTest -vvvv --debug-audio verify \"%s\" \"%s\"\n", + SetA.szPathAbs, SetB.szPathAbs); +#endif + if (!g_fDrvAudioDebug) /* Don't wipe stuff when debugging. Can be useful for introspecting data. */ + AudioTestSetWipe(&SetB); + AudioTestSetClose(&SetB); + } + + if (!g_fDrvAudioDebug) /* Ditto. */ + AudioTestSetWipe(&SetA); + AudioTestSetClose(&SetA); + } + + RTTestSubDone(g_hTest); + + return rc; +} + +/** Option help for the 'verify' command. */ +static DECLCALLBACK(const char *) audioTestCmdVerifyHelp(PCRTGETOPTDEF pOpt) +{ + switch (pOpt->iShort) + { + case VKAT_VERIFY_OPT_MAX_DIFF_COUNT: return "Specifies the maximum number of differences\n" + " Default: 0 (strict)"; + case VKAT_VERIFY_OPT_MAX_DIFF_PERCENT: return "Specifies the maximum difference (percent)\n" + " Default: 0 (strict)"; + case VKAT_VERIFY_OPT_MAX_SIZE_PERCENT: return "Specifies the maximum size difference (percent)\n" + " Default: 1 (strict)"; + case VKAT_VERIFY_OPT_NORMALIZE: return "Enables / disables audio data normalization\n" + " Default: false"; + default: + break; + } + return NULL; +} + +/** + * Main (entry) function for the verification functionality of VKAT. + * + * @returns Program exit code. + * @param pGetState RTGetOpt state. + */ +static DECLCALLBACK(RTEXITCODE) audioVerifyMain(PRTGETOPTSTATE pGetState) +{ + /* + * Parse options and process arguments. + */ + const char *apszSets[2] = { NULL, NULL }; + unsigned iTestSet = 0; + + AUDIOTESTVERIFYOPTS Opts; + AudioTestSetVerifyOptsInit(&Opts); + + int ch; + RTGETOPTUNION ValueUnion; + while ((ch = RTGetOpt(pGetState, &ValueUnion))) + { + switch (ch) + { + case VKAT_VERIFY_OPT_MAX_DIFF_COUNT: + Opts.cMaxDiff = ValueUnion.u32; + break; + + case VKAT_VERIFY_OPT_MAX_DIFF_PERCENT: + Opts.uMaxDiffPercent = ValueUnion.u8; + break; + + case VKAT_VERIFY_OPT_MAX_SIZE_PERCENT: + Opts.uMaxSizePercent = ValueUnion.u8; + break; + + case VKAT_VERIFY_OPT_NORMALIZE: + Opts.fNormalize = ValueUnion.f; + break; + + case VINF_GETOPT_NOT_OPTION: + if (iTestSet == 0) + RTTestBanner(g_hTest); + if (iTestSet >= RT_ELEMENTS(apszSets)) + return RTMsgErrorExitFailure("Only two test sets can be verified at one time"); + apszSets[iTestSet++] = ValueUnion.psz; + break; + + AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion, &g_CmdVerify); + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + if (!iTestSet) + return RTMsgErrorExitFailure("At least one test set must be specified"); + + int rc = VINF_SUCCESS; + + /* + * If only test set A is given, default to the current directory + * for test set B. + */ + char szDirCur[RTPATH_MAX]; + if (iTestSet == 1) + { + rc = RTPathGetCurrent(szDirCur, sizeof(szDirCur)); + if (RT_SUCCESS(rc)) + apszSets[1] = szDirCur; + else + RTTestFailed(g_hTest, "Failed to retrieve current directory: %Rrc", rc); + } + + if (RT_SUCCESS(rc)) + audioVerifyOne(apszSets[0], apszSets[1], &Opts); + + /* + * Print summary and exit. + */ + return RTTestSummaryAndDestroy(g_hTest); +} + + +const VKATCMD g_CmdVerify = +{ + "verify", + audioVerifyMain, + "Verifies a formerly created audio test set.", + g_aCmdVerifyOptions, + RT_ELEMENTS(g_aCmdVerifyOptions), + audioTestCmdVerifyHelp, + false /* fNeedsTransport */ +}; + + +/********************************************************************************************************************************* +* Main * +*********************************************************************************************************************************/ + +/** + * Ctrl-C signal handler. + * + * This just sets g_fTerminate and hope it will be noticed soon. + * + * On non-Windows it restores the SIGINT action to default, so that a second + * Ctrl-C will have the normal effect (just in case the code doesn't respond to + * g_fTerminate). + */ +#ifdef RT_OS_WINDOWS +static BOOL CALLBACK audioTestConsoleCtrlHandler(DWORD dwCtrlType) RT_NOEXCEPT +{ + if (dwCtrlType != CTRL_C_EVENT && dwCtrlType != CTRL_BREAK_EVENT) + return false; + RTPrintf(dwCtrlType == CTRL_C_EVENT ? "Ctrl-C!\n" : "Ctrl-Break!\n"); + + ASMAtomicWriteBool(&g_fTerminate, true); + + return true; +} +#else +static void audioTestSignalHandler(int iSig) RT_NOEXCEPT +{ + Assert(iSig == SIGINT); RT_NOREF(iSig); + RTPrintf("Ctrl-C!\n"); + + ASMAtomicWriteBool(&g_fTerminate, true); + + signal(SIGINT, SIG_DFL); +} +#endif + +/** + * Commands. + */ +static const VKATCMD * const g_apCommands[] = +{ + &g_CmdTest, + &g_CmdVerify, + &g_CmdBackends, + &g_CmdEnum, + &g_CmdPlay, + &g_CmdRec, + &g_CmdSelfTest +}; + +/** + * Shows tool usage text. + */ +RTEXITCODE audioTestUsage(PRTSTREAM pStrm, PCVKATCMD pOnlyCmd) +{ + RTStrmPrintf(pStrm, "usage: %s [global options] <command> [command-options]\n", RTProcShortName()); + RTStrmPrintf(pStrm, + "\n" + "Global Options:\n" + " --debug-audio\n" + " Enables (DrvAudio) debugging\n" + " --debug-audio-path=<path>\n" + " Tells DrvAudio where to put its debug output (wav-files)\n" + " -q, --quiet\n" + " Sets verbosity to zero\n" + " -v, --verbose\n" + " Increase verbosity\n" + " -V, --version\n" + " Displays version\n" + " -h, -?, --help\n" + " Displays help\n" + ); + + for (uintptr_t iCmd = 0; iCmd < RT_ELEMENTS(g_apCommands); iCmd++) + { + PCVKATCMD const pCmd = g_apCommands[iCmd]; + if (!pOnlyCmd || pCmd == pOnlyCmd) + { + RTStrmPrintf(pStrm, + "\n" + "Command '%s':\n" + " %s\n" + "Options for '%s':\n", + pCmd->pszCommand, pCmd->pszDesc, pCmd->pszCommand); + PCRTGETOPTDEF const paOptions = pCmd->paOptions; + for (unsigned i = 0; i < pCmd->cOptions; i++) + { + if (RT_C_IS_PRINT(paOptions[i].iShort)) + RTStrmPrintf(pStrm, " -%c, %s\n", paOptions[i].iShort, paOptions[i].pszLong); + else + RTStrmPrintf(pStrm, " %s\n", paOptions[i].pszLong); + + const char *pszHelp = NULL; + if (pCmd->pfnOptionHelp) + pszHelp = pCmd->pfnOptionHelp(&paOptions[i]); + if (pszHelp) + RTStrmPrintf(pStrm, " %s\n", pszHelp); + } + + if (pCmd->fNeedsTransport) + for (uintptr_t iTx = 0; iTx < g_cTransports; iTx++) + g_apTransports[iTx]->pfnUsage(pStrm); + } + } + + return RTEXITCODE_SUCCESS; +} + +/** + * Lists the commands and their descriptions. + */ +static RTEXITCODE audioTestListCommands(PRTSTREAM pStrm) +{ + RTStrmPrintf(pStrm, "Commands:\n"); + for (uintptr_t iCmd = 0; iCmd < RT_ELEMENTS(g_apCommands); iCmd++) + RTStrmPrintf(pStrm, "%8s - %s\n", g_apCommands[iCmd]->pszCommand, g_apCommands[iCmd]->pszDesc); + return RTEXITCODE_SUCCESS; +} + +/** + * Shows tool version. + */ +RTEXITCODE audioTestVersion(void) +{ + RTPrintf("%s\n", RTBldCfgRevisionStr()); + return RTEXITCODE_SUCCESS; +} + +/** + * Shows the logo. + * + * @param pStream Output stream to show logo on. + */ +void audioTestShowLogo(PRTSTREAM pStream) +{ + RTStrmPrintf(pStream, VBOX_PRODUCT " VKAT (Validation Kit Audio Test) Version " VBOX_VERSION_STRING " - r%s\n" + "Copyright (C) " VBOX_C_YEAR " " VBOX_VENDOR "\n\n", RTBldCfgRevisionStr()); +} + +int main(int argc, char **argv) +{ + /* + * Init IPRT. + */ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Handle special command line options which need parsing before + * everything else. + */ + /** @todo r=bird: this isn't at all syntactically sane, because you don't know + * how to parse past the command (can almost be done safely thought, since + * you've got the option definitions for every command at hand). So, if someone + * wants to play a file named "-v.wav", you'll incorrectly take that as two 'v' + * options. The parsing has to stop when you get to the command, i.e. first + * VINF_GETOPT_NOT_OPTION or anything that isn't a common option. Daemonizing + * when for instance encountering an invalid command, is not correct. + * + * Btw. you MUST however process the 'q' option in parallel to 'v' here, they + * are oposites. For instance '-vqvvv' is supposed to give you level 3 logging, + * not quiet! So, either you process both 'v' and 'q' here, or you pospone them + * (better option). + */ + /** @todo r=bird: Is the daemonizing needed? The testcase doesn't seem to use + * it... If you don't need it, drop it as it make the parsing complex + * and illogical. The --daemonized / --damonize options should be + * required to before the command, then okay. */ + bool fDaemonize = false; + bool fDaemonized = false; + + RTGETOPTSTATE GetState; + rc = RTGetOptInit(&GetState, argc, argv, g_aCmdCommonOptions, + RT_ELEMENTS(g_aCmdCommonOptions), 1 /*idxFirst*/, 0 /*fFlags - must not sort! */); + AssertRCReturn(rc, RTEXITCODE_INIT); + + int ch; + RTGETOPTUNION ValueUnion; + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (ch) + { + case AUDIO_TEST_OPT_CMN_DAEMONIZE: + fDaemonize = true; + break; + + case AUDIO_TEST_OPT_CMN_DAEMONIZED: + fDaemonized = true; + break; + + /* Has to be defined here and not in AUDIO_TEST_COMMON_OPTION_CASES, to get the logger + * configured before the specific command handlers down below come into play. */ + case 'v': + g_uVerbosity++; + break; + + default: + break; + } + } + + /** @todo add something to suppress this stuff. */ + audioTestShowLogo(g_pStdOut); + + if (fDaemonize) + { + if (!fDaemonized) + { + rc = RTProcDaemonize(argv, "--daemonized"); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcDaemonize() failed with %Rrc\n", rc); + + RTMsgInfo("Starting in background (daemonizing) ..."); + return RTEXITCODE_SUCCESS; + } + /* else continue running in background. */ + } + + /* + * Init test and globals. + * Note: Needs to be done *after* daemonizing, otherwise the child will fail! + */ + rc = RTTestCreate("AudioTest", &g_hTest); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTTestCreate() failed with %Rrc\n", rc); + +#ifdef RT_OS_WINDOWS + HRESULT hrc = CoInitializeEx(NULL /*pReserved*/, COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY | COINIT_DISABLE_OLE1DDE); + if (FAILED(hrc)) + RTMsgWarning("CoInitializeEx failed: %#x", hrc); +#endif + + /* + * Configure release logging to go to stdout. + */ + RTUINT fFlags = RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG; +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + fFlags |= RTLOGFLAGS_USECRLF; +#endif + static const char * const s_apszLogGroups[] = VBOX_LOGGROUP_NAMES; + rc = RTLogCreate(&g_pRelLogger, fFlags, "all.e.l", "VKAT_RELEASE_LOG", + RT_ELEMENTS(s_apszLogGroups), s_apszLogGroups, RTLOGDEST_STDOUT, NULL /*"vkat-release.log"*/); + if (RT_SUCCESS(rc)) + { + RTLogRelSetDefaultInstance(g_pRelLogger); + if (g_uVerbosity) + { + RTMsgInfo("Setting verbosity logging to level %u\n", g_uVerbosity); + switch (g_uVerbosity) /* Not very elegant, but has to do it for now. */ + { + case 1: + rc = RTLogGroupSettings(g_pRelLogger, + "drv_audio.e.l+drv_host_audio.e.l+" + "audio_mixer.e.l+audio_test.e.l"); + break; + + case 2: + rc = RTLogGroupSettings(g_pRelLogger, + "drv_audio.e.l.l2+drv_host_audio.e.l.l2+" + "audio_mixer.e.l.l2+audio_test.e.l.l2"); + break; + + case 3: + rc = RTLogGroupSettings(g_pRelLogger, + "drv_audio.e.l.l2.l3+drv_host_audio.e.l.l2.l3+" + "audio_mixer.e.l.l2.l3+audio_test.e.l.l2.l3"); + break; + + case 4: + RT_FALL_THROUGH(); + default: + rc = RTLogGroupSettings(g_pRelLogger, + "drv_audio.e.l.l2.l3.l4.f+drv_host_audio.e.l.l2.l3.l4.f+" + "audio_mixer.e.l.l2.l3.l4.f+audio_test.e.l.l2.l3.l4.f"); + break; + } + if (RT_FAILURE(rc)) + RTMsgError("Setting debug logging failed, rc=%Rrc\n", rc); + } + } + else + RTMsgWarning("Failed to create release logger: %Rrc", rc); + + /* + * Install a Ctrl-C signal handler. + */ +#ifdef RT_OS_WINDOWS + SetConsoleCtrlHandler(audioTestConsoleCtrlHandler, TRUE); +#else + struct sigaction sa; + RT_ZERO(sa); + sa.sa_handler = audioTestSignalHandler; + sigaction(SIGINT, &sa, NULL); +#endif + + /* + * Process common options. + */ + RT_ZERO(GetState); + rc = RTGetOptInit(&GetState, argc, argv, g_aCmdCommonOptions, + RT_ELEMENTS(g_aCmdCommonOptions), 1 /*idxFirst*/, 0 /*fFlags - must not sort! */); + AssertRCReturn(rc, RTEXITCODE_INIT); + + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (ch) + { + AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion, NULL); + + case VINF_GETOPT_NOT_OPTION: + { + for (uintptr_t iCmd = 0; iCmd < RT_ELEMENTS(g_apCommands); iCmd++) + { + PCVKATCMD const pCmd = g_apCommands[iCmd]; + if (strcmp(ValueUnion.psz, pCmd->pszCommand) == 0) + { + /* Count the combined option definitions: */ + size_t cCombinedOptions = pCmd->cOptions + RT_ELEMENTS(g_aCmdCommonOptions); + if (pCmd->fNeedsTransport) + for (uintptr_t iTx = 0; iTx < g_cTransports; iTx++) + cCombinedOptions += g_apTransports[iTx]->cOpts; + + /* Combine the option definitions: */ + PRTGETOPTDEF paCombinedOptions = (PRTGETOPTDEF)RTMemAlloc(cCombinedOptions * sizeof(RTGETOPTDEF)); + if (paCombinedOptions) + { + uint32_t idxOpts = 0; + memcpy(paCombinedOptions, g_aCmdCommonOptions, sizeof(g_aCmdCommonOptions)); + idxOpts += RT_ELEMENTS(g_aCmdCommonOptions); + + memcpy(&paCombinedOptions[idxOpts], pCmd->paOptions, pCmd->cOptions * sizeof(RTGETOPTDEF)); + idxOpts += (uint32_t)pCmd->cOptions; + + if (pCmd->fNeedsTransport) + for (uintptr_t iTx = 0; iTx < g_cTransports; iTx++) + { + memcpy(&paCombinedOptions[idxOpts], + g_apTransports[iTx]->paOpts, g_apTransports[iTx]->cOpts * sizeof(RTGETOPTDEF)); + idxOpts += (uint32_t)g_apTransports[iTx]->cOpts; + } + + /* Re-initialize the option getter state and pass it to the command handler. */ + rc = RTGetOptInit(&GetState, argc, argv, paCombinedOptions, cCombinedOptions, + GetState.iNext /*idxFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST); + if (RT_SUCCESS(rc)) + { + RTEXITCODE rcExit = pCmd->pfnHandler(&GetState); + RTMemFree(paCombinedOptions); + return rcExit; + } + RTMemFree(paCombinedOptions); + return RTMsgErrorExitFailure("RTGetOptInit failed for '%s': %Rrc", ValueUnion.psz, rc); + } + return RTMsgErrorExitFailure("Out of memory!"); + } + } + RTMsgError("Unknown command '%s'!\n", ValueUnion.psz); + audioTestListCommands(g_pStdErr); + return RTEXITCODE_SYNTAX; + } + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + RTMsgError("No command specified!\n"); + audioTestListCommands(g_pStdErr); + return RTEXITCODE_SYNTAX; +} diff --git a/src/VBox/ValidationKit/utils/audio/vkatCmdGeneric.cpp b/src/VBox/ValidationKit/utils/audio/vkatCmdGeneric.cpp new file mode 100644 index 00000000..c6cde693 --- /dev/null +++ b/src/VBox/ValidationKit/utils/audio/vkatCmdGeneric.cpp @@ -0,0 +1,1169 @@ +/* $Id: vkatCmdGeneric.cpp $ */ +/** @file + * Validation Kit Audio Test (VKAT) utility for testing and validating the audio stack. + */ + +/* + * Copyright (C) 2021-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/errcore.h> +#include <iprt/message.h> +#include <iprt/rand.h> +#include <iprt/test.h> + +#include "vkatInternal.h" + + +/********************************************************************************************************************************* +* Command: backends * +*********************************************************************************************************************************/ + +/** + * Options for 'backends'. + */ +static const RTGETOPTDEF g_aCmdBackendsOptions[] = +{ + { "--dummy", 'd', RTGETOPT_REQ_NOTHING }, /* just a placeholder */ +}; + + +/** The 'backends' command option help. */ +static DECLCALLBACK(const char *) audioTestCmdBackendsHelp(PCRTGETOPTDEF pOpt) +{ + RT_NOREF(pOpt); + return NULL; +} + +/** + * The 'backends' command handler. + * + * @returns Program exit code. + * @param pGetState RTGetOpt state. + */ +static DECLCALLBACK(RTEXITCODE) audioTestCmdBackendsHandler(PRTGETOPTSTATE pGetState) +{ + /* + * Parse options. + */ + int ch; + RTGETOPTUNION ValueUnion; + while ((ch = RTGetOpt(pGetState, &ValueUnion)) != 0) + { + switch (ch) + { + AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion, &g_CmdBackends); + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + /* + * List the backends. + */ + RTPrintf("Backends (%u):\n", g_cBackends); + for (size_t i = 0; i < g_cBackends; i++) + RTPrintf(" %12s - %s\n", g_aBackends[i].pszName, g_aBackends[i].pDrvReg->pszDescription); + + return RTEXITCODE_SUCCESS; +} + + +/** + * Command table entry for 'backends'. + */ +const VKATCMD g_CmdBackends = +{ + /* .pszCommand = */ "backends", + /* .pfnHandler = */ audioTestCmdBackendsHandler, + /* .pszDesc = */ "Lists the compiled in audio backends.", + /* .paOptions = */ g_aCmdBackendsOptions, + /* .cOptions = */ 0 /*RT_ELEMENTS(g_aCmdBackendsOptions)*/, + /* .pfnOptionHelp = */ audioTestCmdBackendsHelp, + /* .fNeedsTransport = */ false +}; + + +/********************************************************************************************************************************* +* Command: enum * +*********************************************************************************************************************************/ + + + +/** + * Long option values for the 'enum' command. + */ +enum +{ + VKAT_ENUM_OPT_PROBE_BACKENDS = 900 +}; + +/** + * Options for 'enum'. + */ +static const RTGETOPTDEF g_aCmdEnumOptions[] = +{ + { "--backend", 'b', RTGETOPT_REQ_STRING }, + { "--probe-backends", VKAT_ENUM_OPT_PROBE_BACKENDS, RTGETOPT_REQ_NOTHING } +}; + + +/** The 'enum' command option help. */ +static DECLCALLBACK(const char *) audioTestCmdEnumHelp(PCRTGETOPTDEF pOpt) +{ + switch (pOpt->iShort) + { + case 'b': return "The audio backend to use"; + case VKAT_ENUM_OPT_PROBE_BACKENDS: return "Probes all (available) backends until a working one is found"; + default: return NULL; + } +} + +/** + * The 'enum' command handler. + * + * @returns Program exit code. + * @param pGetState RTGetOpt state. + */ +static DECLCALLBACK(RTEXITCODE) audioTestCmdEnumHandler(PRTGETOPTSTATE pGetState) +{ + /* + * Parse options. + */ + /* Option values: */ + PCPDMDRVREG pDrvReg = AudioTestGetDefaultBackend(); + bool fProbeBackends = false; + + /* Argument processing loop: */ + int ch; + RTGETOPTUNION ValueUnion; + while ((ch = RTGetOpt(pGetState, &ValueUnion)) != 0) + { + switch (ch) + { + case 'b': + pDrvReg = AudioTestFindBackendOpt(ValueUnion.psz); + if (pDrvReg == NULL) + return RTEXITCODE_SYNTAX; + break; + + case VKAT_ENUM_OPT_PROBE_BACKENDS: + fProbeBackends = true; + break; + + AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion, &g_CmdEnum); + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + int rc; + + AUDIOTESTDRVSTACK DrvStack; + if (fProbeBackends) + rc = audioTestDriverStackProbe(&DrvStack, pDrvReg, + true /* fEnabledIn */, true /* fEnabledOut */, false /* fWithDrvAudio */); + else + rc = audioTestDriverStackInitEx(&DrvStack, pDrvReg, + true /* fEnabledIn */, true /* fEnabledOut */, false /* fWithDrvAudio */); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unable to init driver stack: %Rrc\n", rc); + + /* + * Do the enumeration. + */ + RTEXITCODE rcExit = RTEXITCODE_FAILURE; + + if (DrvStack.pIHostAudio->pfnGetDevices) + { + PDMAUDIOHOSTENUM Enum; + rc = DrvStack.pIHostAudio->pfnGetDevices(DrvStack.pIHostAudio, &Enum); + if (RT_SUCCESS(rc)) + { + RTPrintf("Found %u device%s\n", Enum.cDevices, Enum.cDevices != 1 ? "s" : ""); + + PPDMAUDIOHOSTDEV pHostDev; + RTListForEach(&Enum.LstDevices, pHostDev, PDMAUDIOHOSTDEV, ListEntry) + { + RTPrintf("\nDevice \"%s\":\n", pHostDev->pszName); + + char szFlags[PDMAUDIOHOSTDEV_MAX_FLAGS_STRING_LEN]; + if (pHostDev->cMaxInputChannels && !pHostDev->cMaxOutputChannels && pHostDev->enmUsage == PDMAUDIODIR_IN) + RTPrintf(" Input: max %u channels (%s)\n", + pHostDev->cMaxInputChannels, PDMAudioHostDevFlagsToString(szFlags, pHostDev->fFlags)); + else if (!pHostDev->cMaxInputChannels && pHostDev->cMaxOutputChannels && pHostDev->enmUsage == PDMAUDIODIR_OUT) + RTPrintf(" Output: max %u channels (%s)\n", + pHostDev->cMaxOutputChannels, PDMAudioHostDevFlagsToString(szFlags, pHostDev->fFlags)); + else + RTPrintf(" %s: max %u output channels, max %u input channels (%s)\n", + PDMAudioDirGetName(pHostDev->enmUsage), pHostDev->cMaxOutputChannels, + pHostDev->cMaxInputChannels, PDMAudioHostDevFlagsToString(szFlags, pHostDev->fFlags)); + + if (pHostDev->pszId && *pHostDev->pszId) + RTPrintf(" ID: \"%s\"\n", pHostDev->pszId); + } + + PDMAudioHostEnumDelete(&Enum); + } + else + rcExit = RTMsgErrorExitFailure("Enumeration failed: %Rrc\n", rc); + } + else + rcExit = RTMsgErrorExitFailure("Enumeration not supported by backend '%s'\n", pDrvReg->szName); + audioTestDriverStackDelete(&DrvStack); + + return RTEXITCODE_SUCCESS; +} + + +/** + * Command table entry for 'enum'. + */ +const VKATCMD g_CmdEnum = +{ + "enum", + audioTestCmdEnumHandler, + "Enumerates audio devices.", + g_aCmdEnumOptions, + RT_ELEMENTS(g_aCmdEnumOptions), + audioTestCmdEnumHelp, + false /* fNeedsTransport */ +}; + + + + +/********************************************************************************************************************************* +* Command: play * +*********************************************************************************************************************************/ + +/** + * Worker for audioTestPlayOne implementing the play loop. + */ +static RTEXITCODE audioTestPlayOneInner(PAUDIOTESTDRVMIXSTREAM pMix, PAUDIOTESTWAVEFILE pWaveFile, + PCPDMAUDIOSTREAMCFG pCfgAcq, const char *pszFile) +{ + uint32_t const cbPreBuffer = PDMAudioPropsFramesToBytes(pMix->pProps, pCfgAcq->Backend.cFramesPreBuffering); + uint64_t const nsStarted = RTTimeNanoTS(); + uint64_t nsDonePreBuffering = 0; + + /* + * Transfer data as quickly as we're allowed. + */ + uint8_t abSamples[16384]; + uint32_t const cbSamplesAligned = PDMAudioPropsFloorBytesToFrame(pMix->pProps, sizeof(abSamples)); + uint64_t offStream = 0; + while (!g_fTerminate) + { + /* Read a chunk from the wave file. */ + size_t cbSamples = 0; + int rc = AudioTestWaveFileRead(pWaveFile, abSamples, cbSamplesAligned, &cbSamples); + if (RT_SUCCESS(rc) && cbSamples > 0) + { + /* Pace ourselves a little. */ + if (offStream >= cbPreBuffer) + { + if (!nsDonePreBuffering) + nsDonePreBuffering = RTTimeNanoTS(); + uint64_t const cNsWritten = PDMAudioPropsBytesToNano64(pMix->pProps, offStream - cbPreBuffer); + uint64_t const cNsElapsed = RTTimeNanoTS() - nsStarted; + if (cNsWritten > cNsElapsed + RT_NS_10MS) + RTThreadSleep((cNsWritten - cNsElapsed - RT_NS_10MS / 2) / RT_NS_1MS); + } + + /* Transfer the data to the audio stream. */ + for (uint32_t offSamples = 0; offSamples < cbSamples;) + { + uint32_t const cbCanWrite = AudioTestMixStreamGetWritable(pMix); + if (cbCanWrite > 0) + { + uint32_t const cbToPlay = RT_MIN(cbCanWrite, (uint32_t)cbSamples - offSamples); + uint32_t cbPlayed = 0; + rc = AudioTestMixStreamPlay(pMix, &abSamples[offSamples], cbToPlay, &cbPlayed); + if (RT_SUCCESS(rc)) + { + if (cbPlayed) + { + offSamples += cbPlayed; + offStream += cbPlayed; + } + else + return RTMsgErrorExitFailure("Played zero bytes - %#x bytes reported playable!\n", cbCanWrite); + } + else + return RTMsgErrorExitFailure("Failed to play %#x bytes: %Rrc\n", cbToPlay, rc); + } + else if (AudioTestMixStreamIsOkay(pMix)) + RTThreadSleep(RT_MIN(RT_MAX(1, pCfgAcq->Device.cMsSchedulingHint), 256)); + else + return RTMsgErrorExitFailure("Stream is not okay!\n"); + } + } + else if (RT_SUCCESS(rc) && cbSamples == 0) + break; + else + return RTMsgErrorExitFailure("Error reading wav file '%s': %Rrc", pszFile, rc); + } + + /* + * Drain the stream. + */ + if (g_uVerbosity > 0) + RTMsgInfo("%'RU64 ns: Draining...\n", RTTimeNanoTS() - nsStarted); + int rc = AudioTestMixStreamDrain(pMix, true /*fSync*/); + if (RT_SUCCESS(rc)) + { + if (g_uVerbosity > 0) + RTMsgInfo("%'RU64 ns: Done\n", RTTimeNanoTS() - nsStarted); + } + else + return RTMsgErrorExitFailure("Draining failed: %Rrc", rc); + + return RTEXITCODE_SUCCESS; +} + +/** + * Worker for audioTestCmdPlayHandler that plays one file. + */ +static RTEXITCODE audioTestPlayOne(const char *pszFile, PCPDMDRVREG pDrvReg, const char *pszDevId, + PAUDIOTESTIOOPTS pIoOpts) +{ + char szTmp[128]; + + /* + * First we must open the file and determin the format. + */ + RTERRINFOSTATIC ErrInfo; + AUDIOTESTWAVEFILE WaveFile; + int rc = AudioTestWaveFileOpen(pszFile, &WaveFile, RTErrInfoInitStatic(&ErrInfo)); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Failed to open '%s': %Rrc%#RTeim", pszFile, rc, &ErrInfo.Core); + + if (g_uVerbosity > 0) + { + RTMsgInfo("Opened '%s' for playing\n", pszFile); + RTMsgInfo("Format: %s\n", PDMAudioPropsToString(&WaveFile.Props, szTmp, sizeof(szTmp))); + RTMsgInfo("Size: %'RU32 bytes / %#RX32 / %'RU32 frames / %'RU64 ns\n", + WaveFile.cbSamples, WaveFile.cbSamples, + PDMAudioPropsBytesToFrames(&WaveFile.Props, WaveFile.cbSamples), + PDMAudioPropsBytesToNano(&WaveFile.Props, WaveFile.cbSamples)); + } + + /* + * Construct the driver stack. + */ + RTEXITCODE rcExit = RTEXITCODE_FAILURE; + AUDIOTESTDRVSTACK DrvStack; + rc = audioTestDriverStackInit(&DrvStack, pDrvReg, pIoOpts->fWithDrvAudio); + if (RT_SUCCESS(rc)) + { + /* + * Set the output device if one is specified. + */ + rc = audioTestDriverStackSetDevice(&DrvStack, PDMAUDIODIR_OUT, pszDevId); + if (RT_SUCCESS(rc)) + { + /* + * Open a stream for the output. + */ + uint8_t const cChannels = PDMAudioPropsChannels(&pIoOpts->Props); + + PDMAUDIOPCMPROPS ReqProps = WaveFile.Props; + if (cChannels != 0 && PDMAudioPropsChannels(&ReqProps) != cChannels) + PDMAudioPropsSetChannels(&ReqProps, cChannels); + + uint8_t const cbSample = PDMAudioPropsSampleSize(&pIoOpts->Props); + if (cbSample != 0) + PDMAudioPropsSetSampleSize(&ReqProps, cbSample); + + uint32_t const uHz = PDMAudioPropsHz(&pIoOpts->Props); + if (uHz != 0) + ReqProps.uHz = uHz; + + PDMAUDIOSTREAMCFG CfgAcq; + PPDMAUDIOSTREAM pStream = NULL; + rc = audioTestDriverStackStreamCreateOutput(&DrvStack, &ReqProps, pIoOpts->cMsBufferSize, + pIoOpts->cMsPreBuffer, pIoOpts->cMsSchedulingHint, &pStream, &CfgAcq); + if (RT_SUCCESS(rc)) + { + /* + * Automatically enable the mixer if the wave file and the + * output parameters doesn't match. + */ + if ( !pIoOpts->fWithMixer + && ( !PDMAudioPropsAreEqual(&WaveFile.Props, &pStream->Cfg.Props) + || pIoOpts->uVolumePercent != 100) + ) + { + RTMsgInfo("Enabling the mixer buffer.\n"); + pIoOpts->fWithMixer = true; + } + + /* + * Create a mixer wrapper. This is just a thin wrapper if fWithMixer + * is false, otherwise it's doing mixing, resampling and recoding. + */ + AUDIOTESTDRVMIXSTREAM Mix; + rc = AudioTestMixStreamInit(&Mix, &DrvStack, pStream, pIoOpts->fWithMixer ? &WaveFile.Props : NULL, 100 /*ms*/); + if (RT_SUCCESS(rc)) + { + if (g_uVerbosity > 0) + RTMsgInfo("Stream: %s cbBackend=%#RX32%s\n", + PDMAudioPropsToString(&pStream->Cfg.Props, szTmp, sizeof(szTmp)), + pStream->cbBackend, pIoOpts->fWithMixer ? " mixed" : ""); + + if (pIoOpts->fWithMixer) + AudioTestMixStreamSetVolume(&Mix, pIoOpts->uVolumePercent); + + /* + * Enable the stream and start playing. + */ + rc = AudioTestMixStreamEnable(&Mix); + if (RT_SUCCESS(rc)) + rcExit = audioTestPlayOneInner(&Mix, &WaveFile, &CfgAcq, pszFile); + else + rcExit = RTMsgErrorExitFailure("Enabling the output stream failed: %Rrc", rc); + + /* + * Clean up. + */ + AudioTestMixStreamTerm(&Mix); + } + audioTestDriverStackStreamDestroy(&DrvStack, pStream); + pStream = NULL; + } + else + rcExit = RTMsgErrorExitFailure("Creating output stream failed: %Rrc", rc); + } + else + rcExit = RTMsgErrorExitFailure("Failed to set output device to '%s': %Rrc", pszDevId, rc); + audioTestDriverStackDelete(&DrvStack); + } + else + rcExit = RTMsgErrorExitFailure("Driver stack construction failed: %Rrc", rc); + AudioTestWaveFileClose(&WaveFile); + return rcExit; +} + +/** + * Worker for audioTestCmdPlayHandler that plays one test tone. + */ +static RTEXITCODE audioTestPlayTestToneOne(PAUDIOTESTTONEPARMS pToneParms, + PCPDMDRVREG pDrvReg, const char *pszDevId, + PAUDIOTESTIOOPTS pIoOpts) +{ + char szTmp[128]; + + AUDIOTESTSTREAM TstStream; + RT_ZERO(TstStream); + + /* + * Construct the driver stack. + */ + RTEXITCODE rcExit = RTEXITCODE_FAILURE; + AUDIOTESTDRVSTACK DrvStack; + int rc = audioTestDriverStackInit(&DrvStack, pDrvReg, pIoOpts->fWithDrvAudio); + if (RT_SUCCESS(rc)) + { + /* + * Set the output device if one is specified. + */ + rc = audioTestDriverStackSetDevice(&DrvStack, PDMAUDIODIR_OUT, pszDevId); + if (RT_SUCCESS(rc)) + { + /* + * Open a stream for the output. + */ + uint8_t const cChannels = PDMAudioPropsChannels(&pIoOpts->Props); + + PDMAUDIOPCMPROPS ReqProps = pToneParms->Props; + if (cChannels != 0 && PDMAudioPropsChannels(&ReqProps) != cChannels) + PDMAudioPropsSetChannels(&ReqProps, cChannels); + + uint8_t const cbSample = PDMAudioPropsSampleSize(&pIoOpts->Props); + if (cbSample != 0) + PDMAudioPropsSetSampleSize(&ReqProps, cbSample); + + uint32_t const uHz = PDMAudioPropsHz(&pIoOpts->Props); + if (uHz != 0) + ReqProps.uHz = uHz; + + rc = audioTestDriverStackStreamCreateOutput(&DrvStack, &ReqProps, pIoOpts->cMsBufferSize, + pIoOpts->cMsPreBuffer, pIoOpts->cMsSchedulingHint, &TstStream.pStream, &TstStream.Cfg); + if (RT_SUCCESS(rc)) + { + /* + * Automatically enable the mixer if the wave file and the + * output parameters doesn't match. + */ + if ( !pIoOpts->fWithMixer + && ( !PDMAudioPropsAreEqual(&pToneParms->Props, &TstStream.pStream->Cfg.Props) + || pToneParms->uVolumePercent != 100) + ) + { + RTMsgInfo("Enabling the mixer buffer.\n"); + pIoOpts->fWithMixer = true; + } + + /* + * Create a mixer wrapper. This is just a thin wrapper if fWithMixer + * is false, otherwise it's doing mixing, resampling and recoding. + */ + rc = AudioTestMixStreamInit(&TstStream.Mix, &DrvStack, TstStream.pStream, + pIoOpts->fWithMixer ? &pToneParms->Props : NULL, 100 /*ms*/); + if (RT_SUCCESS(rc)) + { + if (g_uVerbosity > 0) + RTMsgInfo("Stream: %s cbBackend=%#RX32%s\n", + PDMAudioPropsToString(&TstStream.pStream->Cfg.Props, szTmp, sizeof(szTmp)), + TstStream.pStream->cbBackend, pIoOpts->fWithMixer ? " mixed" : ""); + + /* + * Enable the stream and start playing. + */ + rc = AudioTestMixStreamEnable(&TstStream.Mix); + if (RT_SUCCESS(rc)) + { + if (pIoOpts->fWithMixer) + AudioTestMixStreamSetVolume(&TstStream.Mix, pToneParms->uVolumePercent); + + rc = audioTestPlayTone(pIoOpts, NULL /* pTstEnv */, &TstStream, pToneParms); + if (RT_SUCCESS(rc)) + rcExit = RTEXITCODE_SUCCESS; + } + else + rcExit = RTMsgErrorExitFailure("Enabling the output stream failed: %Rrc", rc); + + /* + * Clean up. + */ + AudioTestMixStreamTerm(&TstStream.Mix); + } + audioTestDriverStackStreamDestroy(&DrvStack, TstStream.pStream); + TstStream.pStream = NULL; + } + else + rcExit = RTMsgErrorExitFailure("Creating output stream failed: %Rrc", rc); + } + else + rcExit = RTMsgErrorExitFailure("Failed to set output device to '%s': %Rrc", pszDevId, rc); + audioTestDriverStackDelete(&DrvStack); + } + else + rcExit = RTMsgErrorExitFailure("Driver stack construction failed: %Rrc", rc); + return rcExit; +} + + +/** + * Long option values for the 'play' command. + */ +enum +{ + VKAT_PLAY_OPT_TONE_DUR = 900, + VKAT_PLAY_OPT_TONE_FREQ, + VKAT_PLAY_OPT_TONE_VOL, + VKAT_PLAY_OPT_VOL +}; + + +/** + * Options for 'play'. + */ +static const RTGETOPTDEF g_aCmdPlayOptions[] = +{ + { "--backend", 'b', RTGETOPT_REQ_STRING }, + { "--channels", 'c', RTGETOPT_REQ_UINT8 }, + { "--hz", 'f', RTGETOPT_REQ_UINT32 }, + { "--frequency", 'f', RTGETOPT_REQ_UINT32 }, + { "--sample-size", 'z', RTGETOPT_REQ_UINT8 }, + { "--test-tone", 't', RTGETOPT_REQ_NOTHING }, + { "--tone-dur", VKAT_PLAY_OPT_TONE_DUR, RTGETOPT_REQ_UINT32 }, + { "--tone-freq", VKAT_PLAY_OPT_TONE_FREQ, RTGETOPT_REQ_UINT32 }, + { "--tone-vol", VKAT_PLAY_OPT_TONE_VOL, RTGETOPT_REQ_UINT32 }, + { "--output-device", 'o', RTGETOPT_REQ_STRING }, + { "--with-drv-audio", 'd', RTGETOPT_REQ_NOTHING }, + { "--with-mixer", 'm', RTGETOPT_REQ_NOTHING }, + { "--vol", VKAT_PLAY_OPT_VOL, RTGETOPT_REQ_UINT8 } +}; + + +/** The 'play' command option help. */ +static DECLCALLBACK(const char *) audioTestCmdPlayHelp(PCRTGETOPTDEF pOpt) +{ + switch (pOpt->iShort) + { + case 'b': return "The audio backend to use"; + case 'c': return "Number of backend output channels"; + case 'd': return "Go via DrvAudio instead of directly interfacing with the backend"; + case 'f': return "Output frequency (Hz)"; + case 'z': return "Output sample size (bits)"; + case 't': return "Plays a test tone. Can be specified multiple times"; + case 'm': return "Go via the mixer"; + case 'o': return "The ID of the output device to use"; + case VKAT_PLAY_OPT_TONE_DUR: return "Test tone duration (ms)"; + case VKAT_PLAY_OPT_TONE_FREQ: return "Test tone frequency (Hz)"; + case VKAT_PLAY_OPT_TONE_VOL: return "Test tone volume (percent)"; + case VKAT_PLAY_OPT_VOL: return "Playback volume (percent)"; + default: return NULL; + } +} + + +/** + * The 'play' command handler. + * + * @returns Program exit code. + * @param pGetState RTGetOpt state. + */ +static DECLCALLBACK(RTEXITCODE) audioTestCmdPlayHandler(PRTGETOPTSTATE pGetState) +{ + /* Option values: */ + PCPDMDRVREG pDrvReg = AudioTestGetDefaultBackend(); + const char *pszDevId = NULL; + uint32_t cTestTones = 0; + uint8_t cbSample = 0; + uint8_t cChannels = 0; + uint32_t uHz = 0; + + AUDIOTESTIOOPTS IoOpts; + audioTestIoOptsInitDefaults(&IoOpts); + + AUDIOTESTTONEPARMS ToneParms; + audioTestToneParmsInit(&ToneParms); + + /* Argument processing loop: */ + int ch; + RTGETOPTUNION ValueUnion; + while ((ch = RTGetOpt(pGetState, &ValueUnion)) != 0) + { + switch (ch) + { + case 'b': + pDrvReg = AudioTestFindBackendOpt(ValueUnion.psz); + if (pDrvReg == NULL) + return RTEXITCODE_SYNTAX; + break; + + case 'c': + cChannels = ValueUnion.u8; + break; + + case 'd': + IoOpts.fWithDrvAudio = true; + break; + + case 'f': + uHz = ValueUnion.u32; + break; + + case 'm': + IoOpts.fWithMixer = true; + break; + + case 'o': + pszDevId = ValueUnion.psz; + break; + + case 't': + cTestTones++; + break; + + case 'z': + cbSample = ValueUnion.u8 / 8; + break; + + case VKAT_PLAY_OPT_TONE_DUR: + ToneParms.msDuration = ValueUnion.u32; + break; + + case VKAT_PLAY_OPT_TONE_FREQ: + ToneParms.dbFreqHz = ValueUnion.u32; + break; + + case VKAT_PLAY_OPT_TONE_VOL: + ToneParms.uVolumePercent = ValueUnion.u8; + if (ToneParms.uVolumePercent > 100) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid tonevolume (0-100)"); + break; + + case VKAT_PLAY_OPT_VOL: + IoOpts.uVolumePercent = ValueUnion.u8; + if (IoOpts.uVolumePercent > 100) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid playback volume (0-100)"); + break; + + case VINF_GETOPT_NOT_OPTION: + { + if (cTestTones) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Playing test tones (-t) cannot be combined with playing files"); + + /* Set new (override standard) I/O PCM properties if set by the user. */ + PDMAudioPropsInit(&IoOpts.Props, + cbSample ? cbSample : 2 /* 16-bit */, true /* fSigned */, + cChannels ? cChannels : 2 /* Stereo */, uHz ? uHz : 44100); + + RTEXITCODE rcExit = audioTestPlayOne(ValueUnion.psz, pDrvReg, pszDevId, &IoOpts); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + break; + } + + AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion, &g_CmdPlay); + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + while (cTestTones--) + { + /* Use some sane defaults if no PCM props are set by the user. */ + PDMAudioPropsInit(&ToneParms.Props, + cbSample ? cbSample : 2 /* 16-bit */, true /* fSigned */, + cChannels ? cChannels : 2 /* Stereo */, uHz ? uHz : 44100); + + RTEXITCODE rcExit = audioTestPlayTestToneOne(&ToneParms, pDrvReg, pszDevId, &IoOpts); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Command table entry for 'play'. + */ +const VKATCMD g_CmdPlay = +{ + "play", + audioTestCmdPlayHandler, + "Plays one or more wave files.", + g_aCmdPlayOptions, + RT_ELEMENTS(g_aCmdPlayOptions), + audioTestCmdPlayHelp, + false /* fNeedsTransport */ +}; + + +/********************************************************************************************************************************* +* Command: rec * +*********************************************************************************************************************************/ + +/** + * Worker for audioTestRecOne implementing the recording loop. + */ +static RTEXITCODE audioTestRecOneInner(PAUDIOTESTDRVMIXSTREAM pMix, PAUDIOTESTWAVEFILE pWaveFile, + PCPDMAUDIOSTREAMCFG pCfgAcq, uint64_t cMaxFrames, const char *pszFile) +{ + int rc; + uint64_t const nsStarted = RTTimeNanoTS(); + + /* + * Transfer data as quickly as we're allowed. + */ + uint8_t abSamples[16384]; + uint32_t const cbSamplesAligned = PDMAudioPropsFloorBytesToFrame(pMix->pProps, sizeof(abSamples)); + uint64_t cFramesCapturedTotal = 0; + while (!g_fTerminate && cFramesCapturedTotal < cMaxFrames) + { + /* + * Anything we can read? + */ + uint32_t const cbCanRead = AudioTestMixStreamGetReadable(pMix); + if (cbCanRead) + { + uint32_t const cbToRead = RT_MIN(cbCanRead, cbSamplesAligned); + uint32_t cbCaptured = 0; + rc = AudioTestMixStreamCapture(pMix, abSamples, cbToRead, &cbCaptured); + if (RT_SUCCESS(rc)) + { + if (cbCaptured) + { + uint32_t cFramesCaptured = PDMAudioPropsBytesToFrames(pMix->pProps, cbCaptured); + if (cFramesCaptured + cFramesCaptured < cMaxFrames) + { /* likely */ } + else + { + cFramesCaptured = cMaxFrames - cFramesCaptured; + cbCaptured = PDMAudioPropsFramesToBytes(pMix->pProps, cFramesCaptured); + } + + rc = AudioTestWaveFileWrite(pWaveFile, abSamples, cbCaptured); + if (RT_SUCCESS(rc)) + cFramesCapturedTotal += cFramesCaptured; + else + return RTMsgErrorExitFailure("Error writing to '%s': %Rrc", pszFile, rc); + } + else + return RTMsgErrorExitFailure("Captured zero bytes - %#x bytes reported readable!\n", cbCanRead); + } + else + return RTMsgErrorExitFailure("Failed to capture %#x bytes: %Rrc (%#x available)\n", cbToRead, rc, cbCanRead); + } + else if (AudioTestMixStreamIsOkay(pMix)) + RTThreadSleep(RT_MIN(RT_MAX(1, pCfgAcq->Device.cMsSchedulingHint), 256)); + else + return RTMsgErrorExitFailure("Stream is not okay!\n"); + } + + /* + * Disable the stream. + */ + rc = AudioTestMixStreamDisable(pMix); + if (RT_SUCCESS(rc) && g_uVerbosity > 0) + RTMsgInfo("%'RU64 ns: Stopped after recording %RU64 frames%s\n", RTTimeNanoTS() - nsStarted, cFramesCapturedTotal, + g_fTerminate ? " - Ctrl-C" : "."); + else if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Disabling stream failed: %Rrc", rc); + + return RTEXITCODE_SUCCESS; +} + + +/** + * Worker for audioTestCmdRecHandler that recs one file. + */ +static RTEXITCODE audioTestRecOne(const char *pszFile, uint8_t cWaveChannels, uint8_t cbWaveSample, uint32_t uWaveHz, + PCPDMDRVREG pDrvReg, const char *pszDevId, PAUDIOTESTIOOPTS pIoOpts, + uint64_t cMaxFrames, uint64_t cNsMaxDuration) +{ + /* + * Construct the driver stack. + */ + RTEXITCODE rcExit = RTEXITCODE_FAILURE; + AUDIOTESTDRVSTACK DrvStack; + int rc = audioTestDriverStackInit(&DrvStack, pDrvReg, pIoOpts->fWithDrvAudio); + if (RT_SUCCESS(rc)) + { + /* + * Set the input device if one is specified. + */ + rc = audioTestDriverStackSetDevice(&DrvStack, PDMAUDIODIR_IN, pszDevId); + if (RT_SUCCESS(rc)) + { + /* + * Create an input stream. + */ + PDMAUDIOPCMPROPS ReqProps; + PDMAudioPropsInit(&ReqProps, + pIoOpts->Props.cbSampleX ? pIoOpts->Props.cbSampleX : cbWaveSample ? cbWaveSample : 2, + pIoOpts->Props.fSigned, + pIoOpts->Props.cChannelsX ? pIoOpts->Props.cChannelsX : cWaveChannels ? cWaveChannels : 2, + pIoOpts->Props.uHz ? pIoOpts->Props.uHz : uWaveHz ? uWaveHz : 44100); + + PDMAUDIOSTREAMCFG CfgAcq; + PPDMAUDIOSTREAM pStream = NULL; + rc = audioTestDriverStackStreamCreateInput(&DrvStack, &ReqProps, pIoOpts->cMsBufferSize, + pIoOpts->cMsPreBuffer, pIoOpts->cMsSchedulingHint, &pStream, &CfgAcq); + if (RT_SUCCESS(rc)) + { + /* + * Determine the wave file properties. If it differs from the stream + * properties, make sure the mixer is enabled. + */ + PDMAUDIOPCMPROPS WaveProps; + PDMAudioPropsInit(&WaveProps, + cbWaveSample ? cbWaveSample : PDMAudioPropsSampleSize(&CfgAcq.Props), + true /*fSigned*/, + cWaveChannels ? cWaveChannels : PDMAudioPropsChannels(&CfgAcq.Props), + uWaveHz ? uWaveHz : PDMAudioPropsHz(&CfgAcq.Props)); + if (!pIoOpts->fWithMixer && !PDMAudioPropsAreEqual(&WaveProps, &CfgAcq.Props)) + { + RTMsgInfo("Enabling the mixer buffer.\n"); + pIoOpts->fWithMixer = true; + } + + /* Console the max duration into frames now that we've got the wave file format. */ + if (cMaxFrames != UINT64_MAX && cNsMaxDuration != UINT64_MAX) + { + uint64_t cMaxFrames2 = PDMAudioPropsNanoToBytes64(&WaveProps, cNsMaxDuration); + cMaxFrames = RT_MAX(cMaxFrames, cMaxFrames2); + } + else if (cNsMaxDuration != UINT64_MAX) + cMaxFrames = PDMAudioPropsNanoToBytes64(&WaveProps, cNsMaxDuration); + + /* + * Create a mixer wrapper. This is just a thin wrapper if fWithMixer + * is false, otherwise it's doing mixing, resampling and recoding. + */ + AUDIOTESTDRVMIXSTREAM Mix; + rc = AudioTestMixStreamInit(&Mix, &DrvStack, pStream, pIoOpts->fWithMixer ? &WaveProps : NULL, 100 /*ms*/); + if (RT_SUCCESS(rc)) + { + char szTmp[128]; + if (g_uVerbosity > 0) + RTMsgInfo("Stream: %s cbBackend=%#RX32%s\n", + PDMAudioPropsToString(&pStream->Cfg.Props, szTmp, sizeof(szTmp)), + pStream->cbBackend, pIoOpts->fWithMixer ? " mixed" : ""); + + /* + * Open the wave output file. + */ + AUDIOTESTWAVEFILE WaveFile; + RTERRINFOSTATIC ErrInfo; + rc = AudioTestWaveFileCreate(pszFile, &WaveProps, &WaveFile, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + { + if (g_uVerbosity > 0) + { + RTMsgInfo("Opened '%s' for playing\n", pszFile); + RTMsgInfo("Format: %s\n", PDMAudioPropsToString(&WaveFile.Props, szTmp, sizeof(szTmp))); + } + + /* + * Enable the stream and start recording. + */ + rc = AudioTestMixStreamEnable(&Mix); + if (RT_SUCCESS(rc)) + rcExit = audioTestRecOneInner(&Mix, &WaveFile, &CfgAcq, cMaxFrames, pszFile); + else + rcExit = RTMsgErrorExitFailure("Enabling the input stream failed: %Rrc", rc); + if (rcExit != RTEXITCODE_SUCCESS) + AudioTestMixStreamDisable(&Mix); + + /* + * Clean up. + */ + rc = AudioTestWaveFileClose(&WaveFile); + if (RT_FAILURE(rc)) + rcExit = RTMsgErrorExitFailure("Error closing '%s': %Rrc", pszFile, rc); + } + else + rcExit = RTMsgErrorExitFailure("Failed to open '%s': %Rrc%#RTeim", pszFile, rc, &ErrInfo.Core.pszMsg); + + AudioTestMixStreamTerm(&Mix); + } + audioTestDriverStackStreamDestroy(&DrvStack, pStream); + pStream = NULL; + } + else + rcExit = RTMsgErrorExitFailure("Creating output stream failed: %Rrc", rc); + } + else + rcExit = RTMsgErrorExitFailure("Failed to set output device to '%s': %Rrc", pszDevId, rc); + audioTestDriverStackDelete(&DrvStack); + } + else + rcExit = RTMsgErrorExitFailure("Driver stack construction failed: %Rrc", rc); + return rcExit; +} + + +/** + * Options for 'rec'. + */ +static const RTGETOPTDEF g_aCmdRecOptions[] = +{ + { "--backend", 'b', RTGETOPT_REQ_STRING }, + { "--channels", 'c', RTGETOPT_REQ_UINT8 }, + { "--hz", 'f', RTGETOPT_REQ_UINT32 }, + { "--frequency", 'f', RTGETOPT_REQ_UINT32 }, + { "--sample-size", 'z', RTGETOPT_REQ_UINT8 }, + { "--input-device", 'i', RTGETOPT_REQ_STRING }, + { "--wav-channels", 'C', RTGETOPT_REQ_UINT8 }, + { "--wav-hz", 'F', RTGETOPT_REQ_UINT32 }, + { "--wav-frequency", 'F', RTGETOPT_REQ_UINT32 }, + { "--wav-sample-size", 'Z', RTGETOPT_REQ_UINT8 }, + { "--with-drv-audio", 'd', RTGETOPT_REQ_NOTHING }, + { "--with-mixer", 'm', RTGETOPT_REQ_NOTHING }, + { "--max-frames", 'r', RTGETOPT_REQ_UINT64 }, + { "--max-sec", 's', RTGETOPT_REQ_UINT64 }, + { "--max-seconds", 's', RTGETOPT_REQ_UINT64 }, + { "--max-ms", 't', RTGETOPT_REQ_UINT64 }, + { "--max-milliseconds", 't', RTGETOPT_REQ_UINT64 }, + { "--max-ns", 'T', RTGETOPT_REQ_UINT64 }, + { "--max-nanoseconds", 'T', RTGETOPT_REQ_UINT64 }, +}; + + +/** The 'rec' command option help. */ +static DECLCALLBACK(const char *) audioTestCmdRecHelp(PCRTGETOPTDEF pOpt) +{ + switch (pOpt->iShort) + { + case 'b': return "The audio backend to use."; + case 'c': return "Number of backend input channels"; + case 'C': return "Number of wave-file channels"; + case 'd': return "Go via DrvAudio instead of directly interfacing with the backend."; + case 'f': return "Input frequency (Hz)"; + case 'F': return "Wave-file frequency (Hz)"; + case 'z': return "Input sample size (bits)"; + case 'Z': return "Wave-file sample size (bits)"; + case 'm': return "Go via the mixer."; + case 'i': return "The ID of the input device to use."; + case 'r': return "Max recording duration in frames."; + case 's': return "Max recording duration in seconds."; + case 't': return "Max recording duration in milliseconds."; + case 'T': return "Max recording duration in nanoseconds."; + default: return NULL; + } +} + + +/** + * The 'rec' command handler. + * + * @returns Program exit code. + * @param pGetState RTGetOpt state. + */ +static DECLCALLBACK(RTEXITCODE) audioTestCmdRecHandler(PRTGETOPTSTATE pGetState) +{ + /* Option values: */ + PCPDMDRVREG pDrvReg = AudioTestGetDefaultBackend(); + const char *pszDevId = NULL; + uint8_t cbSample = 0; + uint8_t cChannels = 0; + uint32_t uHz = 0; + uint8_t cbWaveSample = 0; + uint8_t cWaveChannels = 0; + uint32_t uWaveHz = 0; + uint64_t cMaxFrames = UINT64_MAX; + uint64_t cNsMaxDuration = UINT64_MAX; + + AUDIOTESTIOOPTS IoOpts; + audioTestIoOptsInitDefaults(&IoOpts); + + /* Argument processing loop: */ + int ch; + RTGETOPTUNION ValueUnion; + while ((ch = RTGetOpt(pGetState, &ValueUnion)) != 0) + { + switch (ch) + { + case 'b': + pDrvReg = AudioTestFindBackendOpt(ValueUnion.psz); + if (pDrvReg == NULL) + return RTEXITCODE_SYNTAX; + break; + + case 'c': + cChannels = ValueUnion.u8; + break; + + case 'C': + cWaveChannels = ValueUnion.u8; + break; + + case 'd': + IoOpts.fWithDrvAudio = true; + break; + + case 'f': + uHz = ValueUnion.u32; + break; + + case 'F': + uWaveHz = ValueUnion.u32; + break; + + case 'i': + pszDevId = ValueUnion.psz; + break; + + case 'm': + IoOpts.fWithMixer = true; + break; + + case 'r': + cMaxFrames = ValueUnion.u64; + break; + + case 's': + cNsMaxDuration = ValueUnion.u64 >= UINT64_MAX / RT_NS_1SEC ? UINT64_MAX : ValueUnion.u64 * RT_NS_1SEC; + break; + + case 't': + cNsMaxDuration = ValueUnion.u64 >= UINT64_MAX / RT_NS_1MS ? UINT64_MAX : ValueUnion.u64 * RT_NS_1MS; + break; + + case 'T': + cNsMaxDuration = ValueUnion.u64; + break; + + case 'z': + cbSample = ValueUnion.u8 / 8; + break; + + case 'Z': + cbWaveSample = ValueUnion.u8 / 8; + break; + + case VINF_GETOPT_NOT_OPTION: + { + if ( cbSample + || cChannels + || uHz) + { + /* Set new (override standard) I/O PCM properties if set by the user. */ + PDMAudioPropsInit(&IoOpts.Props, + cbSample ? cbSample : 2 /* 16-bit */, true /* fSigned */, + cChannels ? cChannels : 2 /* Stereo */, uHz ? uHz : 44100); + } + + RTEXITCODE rcExit = audioTestRecOne(ValueUnion.psz, cWaveChannels, cbWaveSample, uWaveHz, + pDrvReg, pszDevId, &IoOpts, + cMaxFrames, cNsMaxDuration); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + break; + } + + AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion, &g_CmdRec); + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + return RTEXITCODE_SUCCESS; +} + + +/** + * Command table entry for 'rec'. + */ +const VKATCMD g_CmdRec = +{ + "rec", + audioTestCmdRecHandler, + "Records audio to a wave file.", + g_aCmdRecOptions, + RT_ELEMENTS(g_aCmdRecOptions), + audioTestCmdRecHelp, + false /* fNeedsTransport */ +}; + diff --git a/src/VBox/ValidationKit/utils/audio/vkatCmdSelfTest.cpp b/src/VBox/ValidationKit/utils/audio/vkatCmdSelfTest.cpp new file mode 100644 index 00000000..134dea1b --- /dev/null +++ b/src/VBox/ValidationKit/utils/audio/vkatCmdSelfTest.cpp @@ -0,0 +1,481 @@ +/* $Id: vkatCmdSelfTest.cpp $ */ +/** @file + * Validation Kit Audio Test (VKAT) - Self test. + * + * Self-test which does a complete audio testing framework run without the need + * of a VM or other infrastructure, i.e. all required parts are running locally + * on the same machine. + * + * This self-test does the following: + * - 1. Creates a separate thread for the guest side VKAT and connects to the ATS instance on + * the host side at port 6052 (ATS_TCP_DEF_BIND_PORT_HOST). + * - 2. Uses the Validation Kit audio backend, which in turn creates an ATS instance + * listening at port 6062 (ATS_TCP_DEF_BIND_PORT_VALKIT). + * - 3. Uses the host test environment which creates an ATS instance + * listening at port 6052 (ATS_TCP_DEF_BIND_PORT_HOST). + * - 4. Executes a complete test run locally (e.g. without any guest (VM) involved). + */ + +/* + * Copyright (C) 2021-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ + +#include <iprt/ctype.h> +#include <iprt/errcore.h> +#include <iprt/getopt.h> +#include <iprt/message.h> +#include <iprt/rand.h> +#include <iprt/test.h> + +#include "Audio/AudioHlp.h" +#include "Audio/AudioTest.h" +#include "Audio/AudioTestService.h" +#include "Audio/AudioTestServiceClient.h" + +#include "vkatInternal.h" + + +/********************************************************************************************************************************* +* Internal structures * +*********************************************************************************************************************************/ + +/** + * Structure for keeping a VKAT self test context. + */ +typedef struct SELFTESTCTX +{ + /** Common tag for guest and host side. */ + char szTag[AUDIOTEST_TAG_MAX]; + /** The driver stack in use. */ + AUDIOTESTDRVSTACK DrvStack; + /** Audio driver to use. + * Defaults to the platform's default driver. */ + PCPDMDRVREG pDrvReg; + struct + { + AUDIOTESTENV TstEnv; + /** Where to bind the address of the guest ATS instance to. + * Defaults to localhost (127.0.0.1) if empty. */ + char szAtsAddr[64]; + /** Port of the guest ATS instance. + * Defaults to ATS_ALT_PORT if not set. */ + uint32_t uAtsPort; + } Guest; + struct + { + AUDIOTESTENV TstEnv; + /** Address of the guest ATS instance. + * Defaults to localhost (127.0.0.1) if not set. */ + char szGuestAtsAddr[64]; + /** Port of the guest ATS instance. + * Defaults to ATS_DEFAULT_PORT if not set. */ + uint32_t uGuestAtsPort; + /** Address of the Validation Kit audio driver ATS instance. + * Defaults to localhost (127.0.0.1) if not set. */ + char szValKitAtsAddr[64]; + /** Port of the Validation Kit audio driver ATS instance. + * Defaults to ATS_ALT_PORT if not set. */ + uint32_t uValKitAtsPort; + } Host; +} SELFTESTCTX; +/** Pointer to a VKAT self test context. */ +typedef SELFTESTCTX *PSELFTESTCTX; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** The global self-text context. */ +static SELFTESTCTX g_Ctx; + + +/********************************************************************************************************************************* +* Driver stack self-test implementation * +*********************************************************************************************************************************/ + +/** + * Performs a (quick) audio driver stack self test. + * + * Local only, no guest/host communication involved. + * + * @returns VBox status code. + */ +int AudioTestDriverStackPerformSelftest(void) +{ + PCPDMDRVREG pDrvReg = AudioTestGetDefaultBackend(); + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Testing driver stack started\n"); + + AUDIOTESTDRVSTACK DrvStack; + int rc = audioTestDriverStackProbe(&DrvStack, pDrvReg, + true /* fEnabledIn */, true /* fEnabledOut */, false /* fWithDrvAudio */); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, rc); + + AUDIOTESTIOOPTS IoOpts; + audioTestIoOptsInitDefaults(&IoOpts); + + PPDMAUDIOSTREAM pStream; + PDMAUDIOSTREAMCFG CfgAcq; + rc = audioTestDriverStackStreamCreateOutput(&DrvStack, &IoOpts.Props, + IoOpts.cMsBufferSize, IoOpts.cMsPreBuffer, IoOpts.cMsSchedulingHint, + &pStream, &CfgAcq); + AssertRCReturn(rc, rc); + + rc = audioTestDriverStackStreamEnable(&DrvStack, pStream); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, rc); + + RTTEST_CHECK_RET(g_hTest, audioTestDriverStackStreamIsOkay(&DrvStack, pStream), VERR_AUDIO_STREAM_NOT_READY); + + uint8_t abBuf[_4K]; + memset(abBuf, 0x42, sizeof(abBuf)); + + uint32_t cbWritten; + rc = audioTestDriverStackStreamPlay(&DrvStack, pStream, abBuf, sizeof(abBuf), &cbWritten); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, rc); + RTTEST_CHECK_RET(g_hTest, cbWritten == sizeof(abBuf), VERR_AUDIO_STREAM_NOT_READY); + + audioTestDriverStackStreamDrain(&DrvStack, pStream, true /* fSync */); + audioTestDriverStackStreamDestroy(&DrvStack, pStream); + + audioTestDriverStackDelete(&DrvStack); + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Testing driver stack ended with %Rrc\n", rc); + return rc; +} + + +/********************************************************************************************************************************* +* Self-test implementation * +*********************************************************************************************************************************/ + +/** + * Thread callback for mocking the guest (VM) side of things. + * + * @returns VBox status code. + * @param hThread Thread handle. + * @param pvUser Pointer to user-supplied data. + */ +static DECLCALLBACK(int) audioTestSelftestGuestAtsThread(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF(hThread); + PSELFTESTCTX pCtx = (PSELFTESTCTX)pvUser; + + PAUDIOTESTENV pTstEnvGst = &pCtx->Guest.TstEnv; + + audioTestEnvInit(pTstEnvGst); + + /* Flag the environment for self test mode. */ + pTstEnvGst->fSelftest = true; + + /* Tweak the address the guest ATS is trying to connect to the host if anything else is specified. + * Note: The host also runs on the same host (this self-test is completely self-contained and does not need a VM). */ + if (!pTstEnvGst->TcpOpts.szConnectAddr[0]) + RTStrCopy(pTstEnvGst->TcpOpts.szConnectAddr, sizeof(pTstEnvGst->TcpOpts.szConnectAddr), "127.0.0.1"); + + /* Generate tag for guest side. */ + int rc = RTStrCopy(pTstEnvGst->szTag, sizeof(pTstEnvGst->szTag), pCtx->szTag); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, rc); + + rc = AudioTestPathCreateTemp(pTstEnvGst->szPathTemp, sizeof(pTstEnvGst->szPathTemp), "selftest-guest"); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, rc); + + rc = AudioTestPathCreateTemp(pTstEnvGst->szPathOut, sizeof(pTstEnvGst->szPathOut), "selftest-out"); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, rc); + + pTstEnvGst->enmMode = AUDIOTESTMODE_GUEST; + + rc = audioTestEnvCreate(pTstEnvGst, &pCtx->DrvStack); + if (RT_SUCCESS(rc)) + { + RTThreadUserSignal(hThread); + + rc = audioTestWorker(pTstEnvGst); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, rc); + + audioTestEnvDestroy(pTstEnvGst); + } + + return rc; +} + +/** + * Main function for performing the self test. + * + * @returns RTEXITCODE + * @param pCtx Self test context to use. + */ +RTEXITCODE audioTestDoSelftest(PSELFTESTCTX pCtx) +{ + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Running self test ...\n"); + + /* Generate a common tag for guest and host side. */ + int rc = AudioTestGenTag(pCtx->szTag, sizeof(pCtx->szTag)); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, RTEXITCODE_FAILURE); + + PAUDIOTESTENV pTstEnvHst = &pCtx->Host.TstEnv; + + audioTestEnvInit(pTstEnvHst); + + /* Flag the environment for self test mode. */ + pTstEnvHst->fSelftest = true; + + /* One test iteration with a 5s maximum test tone is enough for a (quick) self test. */ + pTstEnvHst->cIterations = 1; + pTstEnvHst->ToneParms.msDuration = RTRandU32Ex(500, RT_MS_5SEC); + + /* Generate tag for host side. */ + rc = RTStrCopy(pTstEnvHst->szTag, sizeof(pTstEnvHst->szTag), pCtx->szTag); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, RTEXITCODE_FAILURE); + + rc = AudioTestPathCreateTemp(pTstEnvHst->szPathTemp, sizeof(pTstEnvHst->szPathTemp), "selftest-tmp"); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, RTEXITCODE_FAILURE); + + rc = AudioTestPathCreateTemp(pTstEnvHst->szPathOut, sizeof(pTstEnvHst->szPathOut), "selftest-out"); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, RTEXITCODE_FAILURE); + + /* + * Step 1. + */ + RTTHREAD hThreadGstAts = NIL_RTTHREAD; + + bool const fStartGuestAts = RTStrNLen(pCtx->Host.szGuestAtsAddr, sizeof(pCtx->Host.szGuestAtsAddr)) == 0; + if (fStartGuestAts) + { + /* Step 1b. */ + rc = RTThreadCreate(&hThreadGstAts, audioTestSelftestGuestAtsThread, pCtx, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, + "VKATGstAts"); + if (RT_SUCCESS(rc)) + rc = RTThreadUserWait(hThreadGstAts, RT_MS_30SEC); + } + + RTThreadSleep(2000); /* Fudge: Wait until guest ATS is up. 2 seconds should be enough (tm). */ + + if (RT_SUCCESS(rc)) + { + /* + * Steps 2 + 3. + */ + pTstEnvHst->enmMode = AUDIOTESTMODE_HOST; + + rc = audioTestEnvCreate(pTstEnvHst, &pCtx->DrvStack); + if (RT_SUCCESS(rc)) + { + /* + * Step 4. + */ + rc = audioTestWorker(pTstEnvHst); + RTTEST_CHECK_RC_OK(g_hTest, rc); + + audioTestEnvDestroy(pTstEnvHst); + } + } + + /* + * Shutting down. + */ + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Shutting down self test\n"); + + /* If we started the guest ATS ourselves, wait for it to terminate properly. */ + if (fStartGuestAts) + { + int rcThread; + int rc2 = RTThreadWait(hThreadGstAts, RT_MS_30SEC, &rcThread); + if (RT_SUCCESS(rc2)) + rc2 = rcThread; + if (RT_FAILURE(rc2)) + RTTestFailed(g_hTest, "Shutting down guest ATS failed with %Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Self test failed with %Rrc\n", rc); + + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/********************************************************************************************************************************* +* Command: selftest * +*********************************************************************************************************************************/ + +/** + * Command line parameters for self-test mode. + */ +static const RTGETOPTDEF s_aCmdSelftestOptions[] = +{ + { "--exclude-all", 'a', RTGETOPT_REQ_NOTHING }, + { "--backend", 'b', RTGETOPT_REQ_STRING }, + { "--with-drv-audio", 'd', RTGETOPT_REQ_NOTHING }, + { "--with-mixer", 'm', RTGETOPT_REQ_NOTHING }, + { "--exclude", 'e', RTGETOPT_REQ_UINT32 }, + { "--include", 'i', RTGETOPT_REQ_UINT32 } +}; + +/** the 'selftest' command option help. */ +static DECLCALLBACK(const char *) audioTestCmdSelftestHelp(PCRTGETOPTDEF pOpt) +{ + switch (pOpt->iShort) + { + case 'a': return "Exclude all tests from the list (useful to enable single tests later with --include)"; + case 'b': return "The audio backend to use"; + case 'd': return "Go via DrvAudio instead of directly interfacing with the backend"; + case 'e': return "Exclude the given test id from the list"; + case 'i': return "Include the given test id in the list"; + case 'm': return "Use the internal mixing engine explicitly"; + default: return NULL; + } +} + +/** + * The 'selftest' command handler. + * + * @returns Program exit code. + * @param pGetState RTGetOpt state. + */ +DECLCALLBACK(RTEXITCODE) audioTestCmdSelftestHandler(PRTGETOPTSTATE pGetState) +{ + RT_ZERO(g_Ctx); + + audioTestEnvInit(&g_Ctx.Guest.TstEnv); + audioTestEnvInit(&g_Ctx.Host.TstEnv); + + AUDIOTESTIOOPTS IoOpts; + audioTestIoOptsInitDefaults(&IoOpts); + + /* Argument processing loop: */ + int rc; + RTGETOPTUNION ValueUnion; + while ((rc = RTGetOpt(pGetState, &ValueUnion)) != 0) + { + switch (rc) + { + case 'a': + for (unsigned i = 0; i < g_cTests; i++) + g_aTests[i].fExcluded = true; + break; + + case 'b': + g_Ctx.pDrvReg = AudioTestFindBackendOpt(ValueUnion.psz); + if (g_Ctx.pDrvReg == NULL) + return RTEXITCODE_SYNTAX; + break; + + case 'd': + IoOpts.fWithDrvAudio = true; + break; + + case 'e': + if (ValueUnion.u32 >= g_cTests) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid test number %u passed to --exclude", ValueUnion.u32); + g_aTests[ValueUnion.u32].fExcluded = true; + break; + + case 'i': + if (ValueUnion.u32 >= g_cTests) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid test number %u passed to --include", ValueUnion.u32); + g_aTests[ValueUnion.u32].fExcluded = false; + break; + + case 'm': + IoOpts.fWithMixer = true; + break; + + AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion, &g_CmdSelfTest); + + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + /* For simplicity both test environments, guest and host, will have the same I/O options. + ** @todo Make this indepedent by a prefix, "--[guest|host]-<option>" -> e.g. "--guest-with-drv-audio". */ + memcpy(&g_Ctx.Guest.TstEnv.IoOpts, &IoOpts, sizeof(AUDIOTESTIOOPTS)); + memcpy(&g_Ctx.Host.TstEnv.IoOpts, &IoOpts, sizeof(AUDIOTESTIOOPTS)); + + rc = AudioTestDriverStackPerformSelftest(); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Testing driver stack failed: %Rrc\n", rc); + + /* Go with the Validation Kit audio backend if nothing else is specified. */ + if (g_Ctx.pDrvReg == NULL) + g_Ctx.pDrvReg = AudioTestFindBackendOpt("valkit"); + + /* + * In self-test mode the guest and the host side have to share the same driver stack, + * as we don't have any device emulation between the two sides. + * + * This is necessary to actually get the played/recorded audio to from/to the guest + * and host respectively. + * + * Choosing any other backend than the Validation Kit above *will* break this self-test! + */ + rc = audioTestDriverStackInitEx(&g_Ctx.DrvStack, g_Ctx.pDrvReg, + true /* fEnabledIn */, true /* fEnabledOut */, g_Ctx.Host.TstEnv.IoOpts.fWithDrvAudio); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unable to init driver stack: %Rrc\n", rc); + + /* + * Start testing. + */ + RTTestBanner(g_hTest); + + int rc2 = audioTestDoSelftest(&g_Ctx); + if (RT_FAILURE(rc2)) + RTTestFailed(g_hTest, "Self test failed with rc=%Rrc", rc2); + + audioTestDriverStackDelete(&g_Ctx.DrvStack); + + /* + * Print summary and exit. + */ + return RTTestSummaryAndDestroy(g_hTest); +} + +/** + * Command table entry for 'selftest'. + */ +const VKATCMD g_CmdSelfTest = +{ + "selftest", + audioTestCmdSelftestHandler, + "Performs self-tests.", + s_aCmdSelftestOptions, + RT_ELEMENTS(s_aCmdSelftestOptions), + audioTestCmdSelftestHelp, + true /* fNeedsTransport */ +}; + diff --git a/src/VBox/ValidationKit/utils/audio/vkatCommon.cpp b/src/VBox/ValidationKit/utils/audio/vkatCommon.cpp new file mode 100644 index 00000000..2eccc8c7 --- /dev/null +++ b/src/VBox/ValidationKit/utils/audio/vkatCommon.cpp @@ -0,0 +1,1750 @@ +/* $Id: vkatCommon.cpp $ */ +/** @file + * Validation Kit Audio Test (VKAT) - Self test code. + */ + +/* + * Copyright (C) 2021-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_AUDIO_TEST +#include <iprt/log.h> + +#ifdef VBOX_WITH_AUDIO_ALSA +# include "DrvHostAudioAlsaStubsMangling.h" +# include <alsa/asoundlib.h> +# include <alsa/control.h> /* For device enumeration. */ +# include <alsa/version.h> +# include "DrvHostAudioAlsaStubs.h" +#endif +#ifdef VBOX_WITH_AUDIO_OSS +# include <errno.h> +# include <fcntl.h> +# include <sys/ioctl.h> +# include <sys/mman.h> +# include <sys/soundcard.h> +# include <unistd.h> +#endif +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +# include <iprt/win/audioclient.h> +# include <endpointvolume.h> /* For IAudioEndpointVolume. */ +# include <audiopolicy.h> /* For IAudioSessionManager. */ +# include <AudioSessionTypes.h> +# include <Mmdeviceapi.h> +#endif + +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/errcore.h> +#include <iprt/getopt.h> +#include <iprt/message.h> +#include <iprt/rand.h> +#include <iprt/test.h> + +#include "Audio/AudioHlp.h" +#include "Audio/AudioTest.h" +#include "Audio/AudioTestService.h" +#include "Audio/AudioTestServiceClient.h" + +#include "vkatInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int audioTestStreamInit(PAUDIOTESTDRVSTACK pDrvStack, PAUDIOTESTSTREAM pStream, PDMAUDIODIR enmDir, PAUDIOTESTIOOPTS pPlayOpt); +static int audioTestStreamDestroy(PAUDIOTESTDRVSTACK pDrvStack, PAUDIOTESTSTREAM pStream); + + +/********************************************************************************************************************************* +* Volume handling. * +*********************************************************************************************************************************/ + +#ifdef VBOX_WITH_AUDIO_ALSA +/** + * Sets the system's master volume via ALSA, if available. + * + * @returns VBox status code. + * @param uVolPercent Volume (in percent) to set. + */ +static int audioTestSetMasterVolumeALSA(unsigned uVolPercent) +{ + int rc = audioLoadAlsaLib(); + if (RT_FAILURE(rc)) + return rc; + + int err; + snd_mixer_t *handle; + +# define ALSA_CHECK_RET(a_Exp, a_Text) \ + if (!(a_Exp)) \ + { \ + AssertLogRelMsg(a_Exp, a_Text); \ + if (handle) \ + snd_mixer_close(handle); \ + return VERR_GENERAL_FAILURE; \ + } + +# define ALSA_CHECK_ERR_RET(a_Text) \ + ALSA_CHECK_RET(err >= 0, a_Text) + + err = snd_mixer_open(&handle, 0 /* Index */); + ALSA_CHECK_ERR_RET(("ALSA: Failed to open mixer: %s\n", snd_strerror(err))); + err = snd_mixer_attach(handle, "default"); + ALSA_CHECK_ERR_RET(("ALSA: Failed to attach to default sink: %s\n", snd_strerror(err))); + err = snd_mixer_selem_register(handle, NULL, NULL); + ALSA_CHECK_ERR_RET(("ALSA: Failed to attach to default sink: %s\n", snd_strerror(err))); + err = snd_mixer_load(handle); + ALSA_CHECK_ERR_RET(("ALSA: Failed to load mixer: %s\n", snd_strerror(err))); + + snd_mixer_selem_id_t *sid = NULL; + snd_mixer_selem_id_alloca(&sid); + + snd_mixer_selem_id_set_index(sid, 0 /* Index */); + snd_mixer_selem_id_set_name(sid, "Master"); + + snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid); + ALSA_CHECK_RET(elem != NULL, ("ALSA: Failed to find mixer element: %s\n", snd_strerror(err))); + + long uVolMin, uVolMax; + snd_mixer_selem_get_playback_volume_range(elem, &uVolMin, &uVolMax); + ALSA_CHECK_ERR_RET(("ALSA: Failed to get playback volume range: %s\n", snd_strerror(err))); + + long const uVol = RT_MIN(uVolPercent, 100) * uVolMax / 100; + + err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, uVol); + ALSA_CHECK_ERR_RET(("ALSA: Failed to set playback volume left: %s\n", snd_strerror(err))); + err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, uVol); + ALSA_CHECK_ERR_RET(("ALSA: Failed to set playback volume right: %s\n", snd_strerror(err))); + + snd_mixer_close(handle); + + return VINF_SUCCESS; + +# undef ALSA_CHECK_RET +# undef ALSA_CHECK_ERR_RET +} +#endif /* VBOX_WITH_AUDIO_ALSA */ + +#ifdef VBOX_WITH_AUDIO_OSS +/** + * Sets the system's master volume via OSS, if available. + * + * @returns VBox status code. + * @param uVolPercent Volume (in percent) to set. + */ +static int audioTestSetMasterVolumeOSS(unsigned uVolPercent) +{ + int hFile = open("/dev/dsp", O_WRONLY | O_NONBLOCK, 0); + if (hFile == -1) + { + /* Try opening the mixing device instead. */ + hFile = open("/dev/mixer", O_RDONLY | O_NONBLOCK, 0); + } + + if (hFile != -1) + { + /* OSS maps 0 (muted) - 100 (max), so just use uVolPercent unmodified here. */ + uint16_t uVol = RT_MAKE_U16(uVolPercent, uVolPercent); + AssertLogRelMsgReturnStmt(ioctl(hFile, SOUND_MIXER_PCM /* SNDCTL_DSP_SETPLAYVOL */, &uVol) >= 0, + ("OSS: Failed to set DSP playback volume: %s (%d)\n", + strerror(errno), errno), close(hFile), RTErrConvertFromErrno(errno)); + return VINF_SUCCESS; + } + + return VERR_NOT_SUPPORTED; +} +#endif /* VBOX_WITH_AUDIO_OSS */ + +#ifdef RT_OS_WINDOWS +static int audioTestSetMasterVolumeWASAPI(unsigned uVolPercent) +{ + HRESULT hr; + +# define WASAPI_CHECK_HR_RET(a_Text) \ + if (FAILED(hr)) \ + { \ + AssertLogRelMsgFailed(a_Text); \ + return VERR_GENERAL_FAILURE; \ + } + + hr = CoInitialize(NULL); + WASAPI_CHECK_HR_RET(("CoInitialize() failed, hr=%Rhrc", hr)); + IMMDeviceEnumerator* pIEnumerator = NULL; + hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void **)&pIEnumerator); + WASAPI_CHECK_HR_RET(("WASAPI: Unable to create IMMDeviceEnumerator, hr=%Rhrc", hr)); + + IMMDevice *pIMMDevice = NULL; + hr = pIEnumerator->GetDefaultAudioEndpoint(EDataFlow::eRender, ERole::eConsole, &pIMMDevice); + WASAPI_CHECK_HR_RET(("WASAPI: Unable to get audio endpoint, hr=%Rhrc", hr)); + pIEnumerator->Release(); + + IAudioEndpointVolume *pIAudioEndpointVolume = NULL; + hr = pIMMDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL, (void **)&pIAudioEndpointVolume); + WASAPI_CHECK_HR_RET(("WASAPI: Unable to activate audio endpoint volume, hr=%Rhrc", hr)); + pIMMDevice->Release(); + + float dbMin, dbMax, dbInc; + hr = pIAudioEndpointVolume->GetVolumeRange(&dbMin, &dbMax, &dbInc); + WASAPI_CHECK_HR_RET(("WASAPI: Unable to get volume range, hr=%Rhrc", hr)); + + float const dbSteps = (dbMax - dbMin) / dbInc; + float const dbStepsPerPercent = (dbSteps * dbInc) / 100; + float const dbVol = dbMin + (dbStepsPerPercent * (float(RT_MIN(uVolPercent, 100.0)))); + + hr = pIAudioEndpointVolume->SetMasterVolumeLevel(dbVol, NULL); + WASAPI_CHECK_HR_RET(("WASAPI: Unable to set master volume level, hr=%Rhrc", hr)); + pIAudioEndpointVolume->Release(); + + return VINF_SUCCESS; + +# undef WASAPI_CHECK_HR_RET +} +#endif /* RT_OS_WINDOWS */ + +/** + * Sets the system's master volume, if available. + * + * @returns VBox status code. VERR_NOT_SUPPORTED if not supported. + * @param uVolPercent Volume (in percent) to set. + */ +int audioTestSetMasterVolume(unsigned uVolPercent) +{ + int rc = VINF_SUCCESS; + +#ifdef VBOX_WITH_AUDIO_ALSA + rc = audioTestSetMasterVolumeALSA(uVolPercent); + if (RT_SUCCESS(rc)) + return rc; + /* else try OSS (if available) below. */ +#endif /* VBOX_WITH_AUDIO_ALSA */ + +#ifdef VBOX_WITH_AUDIO_OSS + rc = audioTestSetMasterVolumeOSS(uVolPercent); + if (RT_SUCCESS(rc)) + return rc; +#endif /* VBOX_WITH_AUDIO_OSS */ + +#ifdef RT_OS_WINDOWS + rc = audioTestSetMasterVolumeWASAPI(uVolPercent); + if (RT_SUCCESS(rc)) + return rc; +#endif + + RT_NOREF(rc, uVolPercent); + /** @todo Port other platforms. */ + return VERR_NOT_SUPPORTED; +} + + +/********************************************************************************************************************************* +* Device enumeration + handling. * +*********************************************************************************************************************************/ + +/** + * Enumerates audio devices and optionally searches for a specific device. + * + * @returns VBox status code. + * @param pDrvStack Driver stack to use for enumeration. + * @param pszDev Device name to search for. Can be NULL if the default device shall be used. + * @param ppDev Where to return the pointer of the device enumeration of \a pTstEnv when a + * specific device was found. + */ +int audioTestDevicesEnumerateAndCheck(PAUDIOTESTDRVSTACK pDrvStack, const char *pszDev, PPDMAUDIOHOSTDEV *ppDev) +{ + RTTestSubF(g_hTest, "Enumerating audio devices and checking for device '%s'", pszDev && *pszDev ? pszDev : "[Default]"); + + if (!pDrvStack->pIHostAudio->pfnGetDevices) + { + RTTestSkipped(g_hTest, "Backend does not support device enumeration, skipping"); + return VINF_NOT_SUPPORTED; + } + + Assert(pszDev == NULL || ppDev); + + if (ppDev) + *ppDev = NULL; + + int rc = pDrvStack->pIHostAudio->pfnGetDevices(pDrvStack->pIHostAudio, &pDrvStack->DevEnum); + if (RT_SUCCESS(rc)) + { + PPDMAUDIOHOSTDEV pDev; + RTListForEach(&pDrvStack->DevEnum.LstDevices, pDev, PDMAUDIOHOSTDEV, ListEntry) + { + char szFlags[PDMAUDIOHOSTDEV_MAX_FLAGS_STRING_LEN]; + if (pDev->pszId) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Device '%s' (ID '%s'):\n", pDev->pszName, pDev->pszId); + else + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Device '%s':\n", pDev->pszName); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Usage = %s\n", PDMAudioDirGetName(pDev->enmUsage)); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Flags = %s\n", PDMAudioHostDevFlagsToString(szFlags, pDev->fFlags)); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Input channels = %RU8\n", pDev->cMaxInputChannels); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enum: Output channels = %RU8\n", pDev->cMaxOutputChannels); + + if ( (pszDev && *pszDev) + && !RTStrCmp(pDev->pszName, pszDev)) + { + *ppDev = pDev; + } + } + } + else + RTTestFailed(g_hTest, "Enumerating audio devices failed with %Rrc", rc); + + if (RT_SUCCESS(rc)) + { + if ( (pszDev && *pszDev) + && *ppDev == NULL) + { + RTTestFailed(g_hTest, "Audio device '%s' not found", pszDev); + rc = VERR_NOT_FOUND; + } + } + + RTTestSubDone(g_hTest); + return rc; +} + +static int audioTestStreamInit(PAUDIOTESTDRVSTACK pDrvStack, PAUDIOTESTSTREAM pStream, + PDMAUDIODIR enmDir, PAUDIOTESTIOOPTS pIoOpts) +{ + int rc; + + if (enmDir == PDMAUDIODIR_IN) + rc = audioTestDriverStackStreamCreateInput(pDrvStack, &pIoOpts->Props, pIoOpts->cMsBufferSize, + pIoOpts->cMsPreBuffer, pIoOpts->cMsSchedulingHint, &pStream->pStream, &pStream->Cfg); + else if (enmDir == PDMAUDIODIR_OUT) + rc = audioTestDriverStackStreamCreateOutput(pDrvStack, &pIoOpts->Props, pIoOpts->cMsBufferSize, + pIoOpts->cMsPreBuffer, pIoOpts->cMsSchedulingHint, &pStream->pStream, &pStream->Cfg); + else + rc = VERR_NOT_SUPPORTED; + + if (RT_SUCCESS(rc)) + { + if (!pDrvStack->pIAudioConnector) + { + pStream->pBackend = &((PAUDIOTESTDRVSTACKSTREAM)pStream->pStream)->Backend; + } + else + pStream->pBackend = NULL; + + /* + * Automatically enable the mixer if the PCM properties don't match. + */ + if ( !pIoOpts->fWithMixer + && !PDMAudioPropsAreEqual(&pIoOpts->Props, &pStream->Cfg.Props)) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Enabling stream mixer\n"); + pIoOpts->fWithMixer = true; + } + + rc = AudioTestMixStreamInit(&pStream->Mix, pDrvStack, pStream->pStream, + pIoOpts->fWithMixer ? &pIoOpts->Props : NULL, 100 /* ms */); /** @todo Configure mixer buffer? */ + } + + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Initializing %s stream failed with %Rrc", enmDir == PDMAUDIODIR_IN ? "input" : "output", rc); + + return rc; +} + +/** + * Destroys an audio test stream. + * + * @returns VBox status code. + * @param pDrvStack Driver stack the stream belongs to. + * @param pStream Audio stream to destroy. + */ +static int audioTestStreamDestroy(PAUDIOTESTDRVSTACK pDrvStack, PAUDIOTESTSTREAM pStream) +{ + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + if (pStream->pStream) + { + /** @todo Anything else to do here, e.g. test if there are left over samples or some such? */ + + audioTestDriverStackStreamDestroy(pDrvStack, pStream->pStream); + pStream->pStream = NULL; + pStream->pBackend = NULL; + } + + AudioTestMixStreamTerm(&pStream->Mix); + + return VINF_SUCCESS; +} + + +/********************************************************************************************************************************* +* Test Primitives * +*********************************************************************************************************************************/ + +/** + * Initializes test tone parameters (partly with random values). + + * @param pToneParms Test tone parameters to initialize. + */ +void audioTestToneParmsInit(PAUDIOTESTTONEPARMS pToneParms) +{ + RT_BZERO(pToneParms, sizeof(AUDIOTESTTONEPARMS)); + + /** + * Set default (randomized) test tone parameters if not set explicitly. + */ + pToneParms->dbFreqHz = AudioTestToneGetRandomFreq(); + pToneParms->msDuration = RTRandU32Ex(200, RT_MS_30SEC); + pToneParms->uVolumePercent = 100; /* We always go with maximum volume for now. */ + + PDMAudioPropsInit(&pToneParms->Props, + 2 /* 16-bit */, true /* fPcmSigned */, 2 /* cPcmChannels */, 44100 /* uPcmHz */); +} + +/** + * Initializes I/O options with some sane default values. + * + * @param pIoOpts I/O options to initialize. + */ +void audioTestIoOptsInitDefaults(PAUDIOTESTIOOPTS pIoOpts) +{ + RT_BZERO(pIoOpts, sizeof(AUDIOTESTIOOPTS)); + + /* Initialize the PCM properties to some sane values. */ + PDMAudioPropsInit(&pIoOpts->Props, + 2 /* 16-bit */, true /* fPcmSigned */, 2 /* cPcmChannels */, 44100 /* uPcmHz */); + + pIoOpts->cMsBufferSize = UINT32_MAX; + pIoOpts->cMsPreBuffer = UINT32_MAX; + pIoOpts->cMsSchedulingHint = UINT32_MAX; + pIoOpts->uVolumePercent = 100; /* Use maximum volume by default. */ +} + +#if 0 /* Unused */ +/** + * Returns a random scheduling hint (in ms). + */ +DECLINLINE(uint32_t) audioTestEnvGetRandomSchedulingHint(void) +{ + static const unsigned s_aSchedulingHintsMs[] = + { + 10, + 25, + 50, + 100, + 200, + 250 + }; + + return s_aSchedulingHintsMs[RTRandU32Ex(0, RT_ELEMENTS(s_aSchedulingHintsMs) - 1)]; +} +#endif + +/** + * Plays a test tone on a specific audio test stream. + * + * @returns VBox status code. + * @param pIoOpts I/O options to use. + * @param pTstEnv Test environment to use for running the test. + * Optional and can be NULL (for simple playback only). + * @param pStream Stream to use for playing the tone. + * @param pParms Tone parameters to use. + * + * @note Blocking function. + */ +int audioTestPlayTone(PAUDIOTESTIOOPTS pIoOpts, PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms) +{ + uint32_t const idxTest = (uint8_t)pParms->Hdr.idxTest; + + AUDIOTESTTONE TstTone; + AudioTestToneInit(&TstTone, &pStream->Cfg.Props, pParms->dbFreqHz); + + char const *pcszPathOut = NULL; + if (pTstEnv) + pcszPathOut = pTstEnv->Set.szPathAbs; + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Playing test tone (tone frequency is %RU16Hz, %RU32ms, %RU8%% volume)\n", + idxTest, (uint16_t)pParms->dbFreqHz, pParms->msDuration, pParms->uVolumePercent); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Using %RU32ms stream scheduling hint\n", + idxTest, pStream->Cfg.Device.cMsSchedulingHint); + if (pcszPathOut) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Writing to '%s'\n", idxTest, pcszPathOut); + + int rc; + + /** @todo Use .WAV here? */ + AUDIOTESTOBJ Obj; + RT_ZERO(Obj); /* Shut up MSVC. */ + if (pTstEnv) + { + rc = AudioTestSetObjCreateAndRegister(&pTstEnv->Set, "guest-tone-play.pcm", &Obj); + AssertRCReturn(rc, rc); + } + + uint8_t const uVolPercent = pIoOpts->uVolumePercent; + int rc2 = audioTestSetMasterVolume(uVolPercent); + if (RT_FAILURE(rc2)) + { + if (rc2 == VERR_NOT_SUPPORTED) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Setting system's master volume is not supported on this platform, skipping\n"); + else + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Setting system's master volume failed with %Rrc\n", rc2); + } + else + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Set system's master volume to %RU8%%\n", uVolPercent); + + rc = AudioTestMixStreamEnable(&pStream->Mix); + if ( RT_SUCCESS(rc) + && AudioTestMixStreamIsOkay(&pStream->Mix)) + { + uint32_t cbToWriteTotal = PDMAudioPropsMilliToBytes(&pStream->Cfg.Props, pParms->msDuration); + AssertStmt(cbToWriteTotal, rc = VERR_INVALID_PARAMETER); + uint32_t cbWrittenTotal = 0; + + /* We play a pre + post beacon before + after the actual test tone. + * We always start with the pre beacon. */ + AUDIOTESTTONEBEACON Beacon; + AudioTestBeaconInit(&Beacon, (uint8_t)pParms->Hdr.idxTest, AUDIOTESTTONEBEACONTYPE_PLAY_PRE, &pStream->Cfg.Props); + + uint32_t const cbBeacon = AudioTestBeaconGetSize(&Beacon); + if (cbBeacon) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Playing 2 x %RU32 bytes pre/post beacons\n", + idxTest, cbBeacon); + + if (g_uVerbosity >= 2) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Playing %s beacon ...\n", + idxTest, AudioTestBeaconTypeGetName(Beacon.enmType)); + } + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Playing %RU32 bytes total\n", idxTest, cbToWriteTotal); + + AudioTestObjAddMetadataStr(Obj, "test_id=%04RU32\n", pParms->Hdr.idxTest); + AudioTestObjAddMetadataStr(Obj, "beacon_type=%RU32\n", (uint32_t)AudioTestBeaconGetType(&Beacon)); + AudioTestObjAddMetadataStr(Obj, "beacon_pre_bytes=%RU32\n", cbBeacon); + AudioTestObjAddMetadataStr(Obj, "beacon_post_bytes=%RU32\n", cbBeacon); + AudioTestObjAddMetadataStr(Obj, "stream_to_write_total_bytes=%RU32\n", cbToWriteTotal); + AudioTestObjAddMetadataStr(Obj, "stream_period_size_frames=%RU32\n", pStream->Cfg.Backend.cFramesPeriod); + AudioTestObjAddMetadataStr(Obj, "stream_buffer_size_frames=%RU32\n", pStream->Cfg.Backend.cFramesBufferSize); + AudioTestObjAddMetadataStr(Obj, "stream_prebuf_size_frames=%RU32\n", pStream->Cfg.Backend.cFramesPreBuffering); + /* Note: This mostly is provided by backend (e.g. PulseAudio / ALSA / ++) and + * has nothing to do with the device emulation scheduling hint. */ + AudioTestObjAddMetadataStr(Obj, "device_scheduling_hint_ms=%RU32\n", pStream->Cfg.Device.cMsSchedulingHint); + + PAUDIOTESTDRVMIXSTREAM pMix = &pStream->Mix; + + uint32_t const cbPreBuffer = PDMAudioPropsFramesToBytes(pMix->pProps, pStream->Cfg.Backend.cFramesPreBuffering); + uint64_t const nsStarted = RTTimeNanoTS(); + uint64_t nsDonePreBuffering = 0; + + uint64_t offStream = 0; + uint64_t nsTimeout = RT_MS_5MIN_64 * RT_NS_1MS; + uint64_t nsLastMsgCantWrite = 0; /* Timestamp (in ns) when the last message of an unwritable stream was shown. */ + uint64_t nsLastWrite = 0; + + AUDIOTESTSTATE enmState = AUDIOTESTSTATE_PRE; + uint8_t abBuf[_16K]; + + for (;;) + { + uint64_t const nsNow = RTTimeNanoTS(); + if (!nsLastWrite) + nsLastWrite = nsNow; + + /* Pace ourselves a little. */ + if (offStream >= cbPreBuffer) + { + if (!nsDonePreBuffering) + nsDonePreBuffering = nsNow; + uint64_t const cNsWritten = PDMAudioPropsBytesToNano64(pMix->pProps, offStream - cbPreBuffer); + uint64_t const cNsElapsed = nsNow - nsStarted; + if (cNsWritten > cNsElapsed + RT_NS_10MS) + RTThreadSleep((cNsWritten - cNsElapsed - RT_NS_10MS / 2) / RT_NS_1MS); + } + + uint32_t cbWritten = 0; + uint32_t const cbCanWrite = AudioTestMixStreamGetWritable(&pStream->Mix); + if (cbCanWrite) + { + if (g_uVerbosity >= 4) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Stream is writable with %RU64ms (%RU32 bytes)\n", + idxTest, PDMAudioPropsBytesToMilli(pMix->pProps, cbCanWrite), cbCanWrite); + + switch (enmState) + { + case AUDIOTESTSTATE_PRE: + RT_FALL_THROUGH(); + case AUDIOTESTSTATE_POST: + { + if (g_uVerbosity >= 4) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: %RU32 bytes (%RU64ms) beacon data remaining\n", + idxTest, AudioTestBeaconGetRemaining(&Beacon), + PDMAudioPropsBytesToMilli(&pStream->pStream->Cfg.Props, AudioTestBeaconGetRemaining(&Beacon))); + + bool fGoToNextStage = false; + + if ( AudioTestBeaconGetSize(&Beacon) + && !AudioTestBeaconIsComplete(&Beacon)) + { + bool const fStarted = AudioTestBeaconGetRemaining(&Beacon) == AudioTestBeaconGetSize(&Beacon); + + uint32_t const cbBeaconRemaining = AudioTestBeaconGetRemaining(&Beacon); + AssertBreakStmt(cbBeaconRemaining, VERR_WRONG_ORDER); + + /* Limit to exactly one beacon (pre or post). */ + uint32_t const cbToWrite = RT_MIN(sizeof(abBuf), RT_MIN(cbCanWrite, cbBeaconRemaining)); + + rc = AudioTestBeaconWrite(&Beacon, abBuf, cbToWrite); + if (RT_SUCCESS(rc)) + { + rc = AudioTestMixStreamPlay(&pStream->Mix, abBuf, cbToWrite, &cbWritten); + if ( RT_SUCCESS(rc) + && pTstEnv) + { + /* Also write the beacon data to the test object. + * Note: We use cbPlayed here instead of cbToPlay to know if the data actually was + * reported as being played by the audio stack. */ + rc = AudioTestObjWrite(Obj, abBuf, cbWritten); + } + } + + if ( fStarted + && g_uVerbosity >= 2) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Writing %s beacon begin\n", + idxTest, AudioTestBeaconTypeGetName(Beacon.enmType)); + if (AudioTestBeaconIsComplete(&Beacon)) + { + if (g_uVerbosity >= 2) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Writing %s beacon end\n", + idxTest, AudioTestBeaconTypeGetName(Beacon.enmType)); + fGoToNextStage = true; + } + } + else + fGoToNextStage = true; + + if (fGoToNextStage) + { + if (enmState == AUDIOTESTSTATE_PRE) + enmState = AUDIOTESTSTATE_RUN; + else if (enmState == AUDIOTESTSTATE_POST) + enmState = AUDIOTESTSTATE_DONE; + } + break; + } + + case AUDIOTESTSTATE_RUN: + { + uint32_t cbToWrite = RT_MIN(sizeof(abBuf), cbCanWrite); + cbToWrite = RT_MIN(cbToWrite, cbToWriteTotal - cbWrittenTotal); + if (cbToWrite) + { + rc = AudioTestToneGenerate(&TstTone, abBuf, cbToWrite, &cbToWrite); + if (RT_SUCCESS(rc)) + { + if (pTstEnv) + { + /* Write stuff to disk before trying to play it. Help analysis later. */ + rc = AudioTestObjWrite(Obj, abBuf, cbToWrite); + } + + if (RT_SUCCESS(rc)) + { + rc = AudioTestMixStreamPlay(&pStream->Mix, abBuf, cbToWrite, &cbWritten); + if (RT_SUCCESS(rc)) + { + AssertBreakStmt(cbWritten <= cbToWrite, rc = VERR_TOO_MUCH_DATA); + + offStream += cbWritten; + + if (cbWritten != cbToWrite) + RTTestFailed(g_hTest, "Test #%RU32: Only played %RU32/%RU32 bytes", + idxTest, cbWritten, cbToWrite); + + if (cbWritten) + nsLastWrite = nsNow; + + if (g_uVerbosity >= 4) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "Test #%RU32: Played back %RU32 bytes\n", idxTest, cbWritten); + + cbWrittenTotal += cbWritten; + } + } + } + } + + const bool fComplete = cbWrittenTotal >= cbToWriteTotal; + if (fComplete) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Playing back audio data ended\n", idxTest); + + enmState = AUDIOTESTSTATE_POST; + + /* Re-use the beacon object, but this time it's the post beacon. */ + AudioTestBeaconInit(&Beacon, (uint8_t)idxTest, AUDIOTESTTONEBEACONTYPE_PLAY_POST, + &pStream->Cfg.Props); + } + break; + } + + case AUDIOTESTSTATE_DONE: + { + /* Handled below. */ + break; + } + + default: + AssertFailed(); + break; + } + + if (RT_FAILURE(rc)) + break; + + if (enmState == AUDIOTESTSTATE_DONE) + break; + + nsLastMsgCantWrite = 0; + } + else if (AudioTestMixStreamIsOkay(&pStream->Mix)) + { + RTMSINTERVAL const msSleep = RT_MIN(RT_MAX(1, pStream->Cfg.Device.cMsSchedulingHint), 256); + + if ( g_uVerbosity >= 3 + && ( !nsLastMsgCantWrite + || (nsNow - nsLastMsgCantWrite) > RT_NS_10SEC)) /* Don't spam the output too much. */ + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Waiting %RU32ms for stream to be writable again (last write %RU64ns ago) ...\n", + idxTest, msSleep, nsNow - nsLastWrite); + nsLastMsgCantWrite = nsNow; + } + + RTThreadSleep(msSleep); + } + else + AssertFailedBreakStmt(rc = VERR_AUDIO_STREAM_NOT_READY); + + /* Fail-safe in case something screwed up while playing back. */ + uint64_t const cNsElapsed = nsNow - nsStarted; + if (cNsElapsed > nsTimeout) + { + RTTestFailed(g_hTest, "Test #%RU32: Playback took too long (running %RU64 vs. timeout %RU64), aborting\n", + idxTest, cNsElapsed, nsTimeout); + rc = VERR_TIMEOUT; + } + + if (RT_FAILURE(rc)) + break; + } /* for */ + + if (cbWrittenTotal != cbToWriteTotal) + RTTestFailed(g_hTest, "Test #%RU32: Playback ended unexpectedly (%RU32/%RU32 played)\n", + idxTest, cbWrittenTotal, cbToWriteTotal); + + if (RT_SUCCESS(rc)) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Draining stream ...\n", idxTest); + rc = AudioTestMixStreamDrain(&pStream->Mix, true /*fSync*/); + } + } + else + rc = VERR_AUDIO_STREAM_NOT_READY; + + if (pTstEnv) + { + rc2 = AudioTestObjClose(Obj); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Test #%RU32: Playing tone failed with %Rrc\n", idxTest, rc); + + return rc; +} + +/** + * Records a test tone from a specific audio test stream. + * + * @returns VBox status code. + * @param pIoOpts I/O options to use. + * @param pTstEnv Test environment to use for running the test. + * @param pStream Stream to use for recording the tone. + * @param pParms Tone parameters to use. + * + * @note Blocking function. + */ +static int audioTestRecordTone(PAUDIOTESTIOOPTS pIoOpts, PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms) +{ + uint32_t const idxTest = (uint8_t)pParms->Hdr.idxTest; + + const char *pcszPathOut = pTstEnv->Set.szPathAbs; + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Recording test tone (tone frequency is %RU16Hz, %RU32ms)\n", + idxTest, (uint16_t)pParms->dbFreqHz, pParms->msDuration); + RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Test #%RU32: Writing to '%s'\n", idxTest, pcszPathOut); + + /** @todo Use .WAV here? */ + AUDIOTESTOBJ Obj; + int rc = AudioTestSetObjCreateAndRegister(&pTstEnv->Set, "guest-tone-rec.pcm", &Obj); + AssertRCReturn(rc, rc); + + PAUDIOTESTDRVMIXSTREAM pMix = &pStream->Mix; + + rc = AudioTestMixStreamEnable(pMix); + if (RT_SUCCESS(rc)) + { + uint32_t cbRecTotal = 0; /* Counts everything, including silence / whatever. */ + uint32_t cbTestToRec = PDMAudioPropsMilliToBytes(&pStream->Cfg.Props, pParms->msDuration); + uint32_t cbTestRec = 0; + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Recording %RU32 bytes total\n", idxTest, cbTestToRec); + + /* We expect a pre + post beacon before + after the actual test tone. + * We always start with the pre beacon. */ + AUDIOTESTTONEBEACON Beacon; + AudioTestBeaconInit(&Beacon, (uint8_t)pParms->Hdr.idxTest, AUDIOTESTTONEBEACONTYPE_PLAY_PRE, &pStream->Cfg.Props); + + uint32_t const cbBeacon = AudioTestBeaconGetSize(&Beacon); + if (cbBeacon) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Expecting 2 x %RU32 bytes pre/post beacons\n", + idxTest, cbBeacon); + if (g_uVerbosity >= 2) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Waiting for %s beacon ...\n", + idxTest, AudioTestBeaconTypeGetName(Beacon.enmType)); + } + + AudioTestObjAddMetadataStr(Obj, "test_id=%04RU32\n", pParms->Hdr.idxTest); + AudioTestObjAddMetadataStr(Obj, "beacon_type=%RU32\n", (uint32_t)AudioTestBeaconGetType(&Beacon)); + AudioTestObjAddMetadataStr(Obj, "beacon_pre_bytes=%RU32\n", cbBeacon); + AudioTestObjAddMetadataStr(Obj, "beacon_post_bytes=%RU32\n", cbBeacon); + AudioTestObjAddMetadataStr(Obj, "stream_to_record_bytes=%RU32\n", cbTestToRec); + AudioTestObjAddMetadataStr(Obj, "stream_buffer_size_ms=%RU32\n", pIoOpts->cMsBufferSize); + AudioTestObjAddMetadataStr(Obj, "stream_prebuf_size_ms=%RU32\n", pIoOpts->cMsPreBuffer); + /* Note: This mostly is provided by backend (e.g. PulseAudio / ALSA / ++) and + * has nothing to do with the device emulation scheduling hint. */ + AudioTestObjAddMetadataStr(Obj, "device_scheduling_hint_ms=%RU32\n", pIoOpts->cMsSchedulingHint); + + uint8_t abSamples[16384]; + uint32_t const cbSamplesAligned = PDMAudioPropsFloorBytesToFrame(pMix->pProps, sizeof(abSamples)); + + uint64_t const nsStarted = RTTimeNanoTS(); + + uint64_t nsTimeout = RT_MS_5MIN_64 * RT_NS_1MS; + uint64_t nsLastMsgCantRead = 0; /* Timestamp (in ns) when the last message of an unreadable stream was shown. */ + + AUDIOTESTSTATE enmState = AUDIOTESTSTATE_PRE; + + while (!g_fTerminate) + { + uint64_t const nsNow = RTTimeNanoTS(); + + /* + * Anything we can read? + */ + uint32_t const cbCanRead = AudioTestMixStreamGetReadable(pMix); + if (cbCanRead) + { + if (g_uVerbosity >= 3) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Stream is readable with %RU64ms (%RU32 bytes)\n", + idxTest, PDMAudioPropsBytesToMilli(pMix->pProps, cbCanRead), cbCanRead); + + uint32_t const cbToRead = RT_MIN(cbCanRead, cbSamplesAligned); + uint32_t cbRecorded = 0; + rc = AudioTestMixStreamCapture(pMix, abSamples, cbToRead, &cbRecorded); + if (RT_SUCCESS(rc)) + { + /* Flag indicating whether the whole block we're going to play is silence or not. */ + bool const fIsAllSilence = PDMAudioPropsIsBufferSilence(&pStream->pStream->Cfg.Props, abSamples, cbRecorded); + + cbRecTotal += cbRecorded; /* Do a bit of accounting. */ + + switch (enmState) + { + case AUDIOTESTSTATE_PRE: + RT_FALL_THROUGH(); + case AUDIOTESTSTATE_POST: + { + bool fGoToNextStage = false; + + if ( AudioTestBeaconGetSize(&Beacon) + && !AudioTestBeaconIsComplete(&Beacon)) + { + bool const fStarted = AudioTestBeaconGetRemaining(&Beacon) == AudioTestBeaconGetSize(&Beacon); + + size_t uOff; + rc = AudioTestBeaconAddConsecutive(&Beacon, abSamples, cbRecorded, &uOff); + if (RT_SUCCESS(rc)) + { + /* + * When being in the AUDIOTESTSTATE_PRE state, we might get more audio data + * than we need for the pre-beacon to complete. In other words, that "more data" + * needs to be counted to the actual recorded test tone data then. + */ + if (enmState == AUDIOTESTSTATE_PRE) + cbTestRec += cbRecorded - (uint32_t)uOff; + } + + if ( fStarted + && g_uVerbosity >= 3) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "Test #%RU32: Detection of %s beacon started (%RU32ms recorded so far)\n", + idxTest, AudioTestBeaconTypeGetName(Beacon.enmType), + PDMAudioPropsBytesToMilli(&pStream->pStream->Cfg.Props, cbRecTotal)); + + if (AudioTestBeaconIsComplete(&Beacon)) + { + if (g_uVerbosity >= 2) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Detected %s beacon\n", + idxTest, AudioTestBeaconTypeGetName(Beacon.enmType)); + fGoToNextStage = true; + } + } + else + fGoToNextStage = true; + + if (fGoToNextStage) + { + if (enmState == AUDIOTESTSTATE_PRE) + enmState = AUDIOTESTSTATE_RUN; + else if (enmState == AUDIOTESTSTATE_POST) + enmState = AUDIOTESTSTATE_DONE; + } + break; + } + + case AUDIOTESTSTATE_RUN: + { + /* Whether we count all silence as recorded data or not. + * Currently we don't, as otherwise consequtively played tones will be cut off in the end. */ + if (!fIsAllSilence) + { + uint32_t const cbToAddMax = cbTestToRec - cbTestRec; + + /* Don't read more than we're told to. + * After the actual test tone data there might come a post beacon which also + * needs to be handled in the AUDIOTESTSTATE_POST state then. */ + if (cbRecorded > cbToAddMax) + cbRecorded = cbToAddMax; + + cbTestRec += cbRecorded; + } + + if (cbTestToRec - cbTestRec == 0) /* Done recording the test tone? */ + { + enmState = AUDIOTESTSTATE_POST; + + if (g_uVerbosity >= 2) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Recording tone data done", idxTest); + + if (AudioTestBeaconGetSize(&Beacon)) + { + /* Re-use the beacon object, but this time it's the post beacon. */ + AudioTestBeaconInit(&Beacon, (uint8_t)pParms->Hdr.idxTest, AUDIOTESTTONEBEACONTYPE_PLAY_POST, + &pStream->Cfg.Props); + if (g_uVerbosity >= 2) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "Test #%RU32: Waiting for %s beacon ...", + idxTest, AudioTestBeaconTypeGetName(Beacon.enmType)); + } + } + break; + } + + case AUDIOTESTSTATE_DONE: + { + /* Nothing to do here. */ + break; + } + + default: + AssertFailed(); + break; + } + } + + if (cbRecorded) + { + /* Always write (record) everything, no matter if the current audio contains complete silence or not. + * Might be also become handy later if we want to have a look at start/stop timings and so on. */ + rc = AudioTestObjWrite(Obj, abSamples, cbRecorded); + AssertRCBreak(rc); + } + + if (enmState == AUDIOTESTSTATE_DONE) /* Bail out when in state "done". */ + break; + } + else if (AudioTestMixStreamIsOkay(pMix)) + { + RTMSINTERVAL const msSleep = RT_MIN(RT_MAX(1, pStream->Cfg.Device.cMsSchedulingHint), 256); + + if ( g_uVerbosity >= 3 + && ( !nsLastMsgCantRead + || (nsNow - nsLastMsgCantRead) > RT_NS_10SEC)) /* Don't spam the output too much. */ + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Waiting %RU32ms for stream to be readable again ...\n", + idxTest, msSleep); + nsLastMsgCantRead = nsNow; + } + + RTThreadSleep(msSleep); + } + + /* Fail-safe in case something screwed up while playing back. */ + uint64_t const cNsElapsed = nsNow - nsStarted; + if (cNsElapsed > nsTimeout) + { + RTTestFailed(g_hTest, "Test #%RU32: Recording took too long (running %RU64 vs. timeout %RU64), aborting\n", + idxTest, cNsElapsed, nsTimeout); + rc = VERR_TIMEOUT; + } + + if (RT_FAILURE(rc)) + break; + } + + if (g_uVerbosity >= 2) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test #%RU32: Recorded %RU32 bytes total\n", idxTest, cbRecTotal); + if (cbTestRec != cbTestToRec) + { + RTTestFailed(g_hTest, "Test #%RU32: Recording ended unexpectedly (%RU32/%RU32 recorded)\n", + idxTest, cbTestRec, cbTestToRec); + rc = VERR_WRONG_ORDER; /** @todo Find a better rc. */ + } + + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Test #%RU32: Recording failed (state is '%s')\n", idxTest, AudioTestStateToStr(enmState)); + + int rc2 = AudioTestMixStreamDisable(pMix); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + int rc2 = AudioTestObjClose(Obj); + if (RT_SUCCESS(rc)) + rc = rc2; + + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Test #%RU32: Recording tone done failed with %Rrc\n", idxTest, rc); + + return rc; +} + + +/********************************************************************************************************************************* +* ATS Callback Implementations * +*********************************************************************************************************************************/ + +/** @copydoc ATSCALLBACKS::pfnHowdy + * + * @note Runs as part of the guest ATS. + */ +static DECLCALLBACK(int) audioTestGstAtsHowdyCallback(void const *pvUser) +{ + PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser; + + AssertReturn(pCtx->cClients <= UINT8_MAX - 1, VERR_BUFFER_OVERFLOW); + + pCtx->cClients++; + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "New client connected, now %RU8 total\n", pCtx->cClients); + + return VINF_SUCCESS; +} + +/** @copydoc ATSCALLBACKS::pfnBye + * + * @note Runs as part of the guest ATS. + */ +static DECLCALLBACK(int) audioTestGstAtsByeCallback(void const *pvUser) +{ + PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser; + + AssertReturn(pCtx->cClients, VERR_WRONG_ORDER); + pCtx->cClients--; + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Client wants to disconnect, %RU8 remaining\n", pCtx->cClients); + + if (0 == pCtx->cClients) /* All clients disconnected? Tear things down. */ + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Last client disconnected, terminating server ...\n"); + ASMAtomicWriteBool(&g_fTerminate, true); + } + + return VINF_SUCCESS; +} + +/** @copydoc ATSCALLBACKS::pfnTestSetBegin + * + * @note Runs as part of the guest ATS. + */ +static DECLCALLBACK(int) audioTestGstAtsTestSetBeginCallback(void const *pvUser, const char *pszTag) +{ + PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser; + PAUDIOTESTENV pTstEnv = pCtx->pTstEnv; + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Got request for beginning test set '%s' in '%s'\n", pszTag, pTstEnv->szPathTemp); + + return AudioTestSetCreate(&pTstEnv->Set, pTstEnv->szPathTemp, pszTag); +} + +/** @copydoc ATSCALLBACKS::pfnTestSetEnd + * + * @note Runs as part of the guest ATS. + */ +static DECLCALLBACK(int) audioTestGstAtsTestSetEndCallback(void const *pvUser, const char *pszTag) +{ + PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser; + PAUDIOTESTENV pTstEnv = pCtx->pTstEnv; + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Got request for ending test set '%s'\n", pszTag); + + /* Pack up everything to be ready for transmission. */ + return audioTestEnvPrologue(pTstEnv, true /* fPack */, pCtx->szTestSetArchive, sizeof(pCtx->szTestSetArchive)); +} + +/** @copydoc ATSCALLBACKS::pfnTonePlay + * + * @note Runs as part of the guest ATS. + */ +static DECLCALLBACK(int) audioTestGstAtsTonePlayCallback(void const *pvUser, PAUDIOTESTTONEPARMS pToneParms) +{ + PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser; + PAUDIOTESTENV pTstEnv = pCtx->pTstEnv; + PAUDIOTESTIOOPTS pIoOpts = &pTstEnv->IoOpts; + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Got request for playing test tone #%RU32 (%RU16Hz, %RU32ms) ...\n", + pToneParms->Hdr.idxTest, (uint16_t)pToneParms->dbFreqHz, pToneParms->msDuration); + + char szTimeCreated[RTTIME_STR_LEN]; + RTTimeToString(&pToneParms->Hdr.tsCreated, szTimeCreated, sizeof(szTimeCreated)); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test created (caller UTC): %s\n", szTimeCreated); + + const PAUDIOTESTSTREAM pTstStream = &pTstEnv->aStreams[0]; /** @todo Make this dynamic. */ + + int rc = audioTestStreamInit(pTstEnv->pDrvStack, pTstStream, PDMAUDIODIR_OUT, pIoOpts); + if (RT_SUCCESS(rc)) + { + AUDIOTESTPARMS TstParms; + RT_ZERO(TstParms); + TstParms.enmType = AUDIOTESTTYPE_TESTTONE_PLAY; + TstParms.enmDir = PDMAUDIODIR_OUT; + TstParms.TestTone = *pToneParms; + + PAUDIOTESTENTRY pTst; + rc = AudioTestSetTestBegin(&pTstEnv->Set, "Playing test tone", &TstParms, &pTst); + if (RT_SUCCESS(rc)) + { + rc = audioTestPlayTone(&pTstEnv->IoOpts, pTstEnv, pTstStream, pToneParms); + if (RT_SUCCESS(rc)) + { + AudioTestSetTestDone(pTst); + } + else + AudioTestSetTestFailed(pTst, rc, "Playing tone failed"); + } + + int rc2 = audioTestStreamDestroy(pTstEnv->pDrvStack, pTstStream); + if (RT_SUCCESS(rc)) + rc = rc2; + } + else + RTTestFailed(g_hTest, "Error creating output stream, rc=%Rrc\n", rc); + + return rc; +} + +/** @copydoc ATSCALLBACKS::pfnToneRecord */ +static DECLCALLBACK(int) audioTestGstAtsToneRecordCallback(void const *pvUser, PAUDIOTESTTONEPARMS pToneParms) +{ + PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser; + PAUDIOTESTENV pTstEnv = pCtx->pTstEnv; + PAUDIOTESTIOOPTS pIoOpts = &pTstEnv->IoOpts; + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Got request for recording test tone #%RU32 (%RU32ms) ...\n", + pToneParms->Hdr.idxTest, pToneParms->msDuration); + + char szTimeCreated[RTTIME_STR_LEN]; + RTTimeToString(&pToneParms->Hdr.tsCreated, szTimeCreated, sizeof(szTimeCreated)); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test created (caller UTC): %s\n", szTimeCreated); + + const PAUDIOTESTSTREAM pTstStream = &pTstEnv->aStreams[0]; /** @todo Make this dynamic. */ + + int rc = audioTestStreamInit(pTstEnv->pDrvStack, pTstStream, PDMAUDIODIR_IN, pIoOpts); + if (RT_SUCCESS(rc)) + { + AUDIOTESTPARMS TstParms; + RT_ZERO(TstParms); + TstParms.enmType = AUDIOTESTTYPE_TESTTONE_RECORD; + TstParms.enmDir = PDMAUDIODIR_IN; + TstParms.TestTone = *pToneParms; + + PAUDIOTESTENTRY pTst; + rc = AudioTestSetTestBegin(&pTstEnv->Set, "Recording test tone from host", &TstParms, &pTst); + if (RT_SUCCESS(rc)) + { + rc = audioTestRecordTone(pIoOpts, pTstEnv, pTstStream, pToneParms); + if (RT_SUCCESS(rc)) + { + AudioTestSetTestDone(pTst); + } + else + AudioTestSetTestFailed(pTst, rc, "Recording tone failed"); + } + + int rc2 = audioTestStreamDestroy(pTstEnv->pDrvStack, pTstStream); + if (RT_SUCCESS(rc)) + rc = rc2; + } + else + RTTestFailed(g_hTest, "Error creating input stream, rc=%Rrc\n", rc); + + return rc; +} + +/** @copydoc ATSCALLBACKS::pfnTestSetSendBegin */ +static DECLCALLBACK(int) audioTestGstAtsTestSetSendBeginCallback(void const *pvUser, const char *pszTag) +{ + RT_NOREF(pszTag); + + PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser; + + if (!RTFileExists(pCtx->szTestSetArchive)) /* Has the archive successfully been created yet? */ + return VERR_WRONG_ORDER; + + int rc = RTFileOpen(&pCtx->hTestSetArchive, pCtx->szTestSetArchive, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_SUCCESS(rc)) + { + uint64_t uSize; + rc = RTFileQuerySize(pCtx->hTestSetArchive, &uSize); + if (RT_SUCCESS(rc)) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Sending test set '%s' (%zu bytes)\n", pCtx->szTestSetArchive, uSize); + } + + return rc; +} + +/** @copydoc ATSCALLBACKS::pfnTestSetSendRead */ +static DECLCALLBACK(int) audioTestGstAtsTestSetSendReadCallback(void const *pvUser, + const char *pszTag, void *pvBuf, size_t cbBuf, size_t *pcbRead) +{ + RT_NOREF(pszTag); + + PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser; + + return RTFileRead(pCtx->hTestSetArchive, pvBuf, cbBuf, pcbRead); +} + +/** @copydoc ATSCALLBACKS::pfnTestSetSendEnd */ +static DECLCALLBACK(int) audioTestGstAtsTestSetSendEndCallback(void const *pvUser, const char *pszTag) +{ + RT_NOREF(pszTag); + + PATSCALLBACKCTX pCtx = (PATSCALLBACKCTX)pvUser; + + int rc = RTFileClose(pCtx->hTestSetArchive); + if (RT_SUCCESS(rc)) + { + pCtx->hTestSetArchive = NIL_RTFILE; + } + + return rc; +} + + +/********************************************************************************************************************************* +* Implementation of audio test environment handling * +*********************************************************************************************************************************/ + +/** + * Connects an ATS client via TCP/IP to a peer. + * + * @returns VBox status code. + * @param pTstEnv Test environment to use. + * @param pClient Client to connect. + * @param pszWhat Hint of what to connect to where. + * @param pTcpOpts Pointer to TCP options to use. + */ +int audioTestEnvConnectViaTcp(PAUDIOTESTENV pTstEnv, PATSCLIENT pClient, const char *pszWhat, PAUDIOTESTENVTCPOPTS pTcpOpts) +{ + RT_NOREF(pTstEnv); + + RTGETOPTUNION Val; + RT_ZERO(Val); + + Val.u32 = pTcpOpts->enmConnMode; + int rc = AudioTestSvcClientHandleOption(pClient, ATSTCPOPT_CONN_MODE, &Val); + AssertRCReturn(rc, rc); + + if ( pTcpOpts->enmConnMode == ATSCONNMODE_BOTH + || pTcpOpts->enmConnMode == ATSCONNMODE_SERVER) + { + Assert(pTcpOpts->uBindPort); /* Always set by the caller. */ + Val.u16 = pTcpOpts->uBindPort; + rc = AudioTestSvcClientHandleOption(pClient, ATSTCPOPT_BIND_PORT, &Val); + AssertRCReturn(rc, rc); + + if (pTcpOpts->szBindAddr[0]) + { + Val.psz = pTcpOpts->szBindAddr; + rc = AudioTestSvcClientHandleOption(pClient, ATSTCPOPT_BIND_ADDRESS, &Val); + AssertRCReturn(rc, rc); + } + else + { + RTTestFailed(g_hTest, "No bind address specified!\n"); + return VERR_INVALID_PARAMETER; + } + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Connecting %s by listening as server at %s:%RU32 ...\n", + pszWhat, pTcpOpts->szBindAddr, pTcpOpts->uBindPort); + } + + + if ( pTcpOpts->enmConnMode == ATSCONNMODE_BOTH + || pTcpOpts->enmConnMode == ATSCONNMODE_CLIENT) + { + Assert(pTcpOpts->uConnectPort); /* Always set by the caller. */ + Val.u16 = pTcpOpts->uConnectPort; + rc = AudioTestSvcClientHandleOption(pClient, ATSTCPOPT_CONNECT_PORT, &Val); + AssertRCReturn(rc, rc); + + if (pTcpOpts->szConnectAddr[0]) + { + Val.psz = pTcpOpts->szConnectAddr; + rc = AudioTestSvcClientHandleOption(pClient, ATSTCPOPT_CONNECT_ADDRESS, &Val); + AssertRCReturn(rc, rc); + } + else + { + RTTestFailed(g_hTest, "No connect address specified!\n"); + return VERR_INVALID_PARAMETER; + } + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Connecting %s by connecting as client to %s:%RU32 ...\n", + pszWhat, pTcpOpts->szConnectAddr, pTcpOpts->uConnectPort); + } + + rc = AudioTestSvcClientConnect(pClient); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Connecting %s failed with %Rrc\n", pszWhat, rc); + return rc; + } + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Successfully connected %s\n", pszWhat); + return rc; +} + +/** + * Configures and starts an ATS TCP/IP server. + * + * @returns VBox status code. + * @param pSrv ATS server instance to configure and start. + * @param pCallbacks ATS callback table to use. + * @param pszDesc Hint of server type which is being started. + * @param pTcpOpts TCP options to use. + */ +int audioTestEnvConfigureAndStartTcpServer(PATSSERVER pSrv, PCATSCALLBACKS pCallbacks, const char *pszDesc, + PAUDIOTESTENVTCPOPTS pTcpOpts) +{ + RTGETOPTUNION Val; + RT_ZERO(Val); + + int rc = AudioTestSvcInit(pSrv, pCallbacks); + if (RT_FAILURE(rc)) + return rc; + + Val.u32 = pTcpOpts->enmConnMode; + rc = AudioTestSvcHandleOption(pSrv, ATSTCPOPT_CONN_MODE, &Val); + AssertRCReturn(rc, rc); + + if ( pTcpOpts->enmConnMode == ATSCONNMODE_BOTH + || pTcpOpts->enmConnMode == ATSCONNMODE_SERVER) + { + Assert(pTcpOpts->uBindPort); /* Always set by the caller. */ + Val.u16 = pTcpOpts->uBindPort; + rc = AudioTestSvcHandleOption(pSrv, ATSTCPOPT_BIND_PORT, &Val); + AssertRCReturn(rc, rc); + + if (pTcpOpts->szBindAddr[0]) + { + Val.psz = pTcpOpts->szBindAddr; + rc = AudioTestSvcHandleOption(pSrv, ATSTCPOPT_BIND_ADDRESS, &Val); + AssertRCReturn(rc, rc); + } + else + { + RTTestFailed(g_hTest, "No bind address specified!\n"); + return VERR_INVALID_PARAMETER; + } + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Starting server for %s at %s:%RU32 ...\n", + pszDesc, pTcpOpts->szBindAddr, pTcpOpts->uBindPort); + } + + + if ( pTcpOpts->enmConnMode == ATSCONNMODE_BOTH + || pTcpOpts->enmConnMode == ATSCONNMODE_CLIENT) + { + Assert(pTcpOpts->uConnectPort); /* Always set by the caller. */ + Val.u16 = pTcpOpts->uConnectPort; + rc = AudioTestSvcHandleOption(pSrv, ATSTCPOPT_CONNECT_PORT, &Val); + AssertRCReturn(rc, rc); + + if (pTcpOpts->szConnectAddr[0]) + { + Val.psz = pTcpOpts->szConnectAddr; + rc = AudioTestSvcHandleOption(pSrv, ATSTCPOPT_CONNECT_ADDRESS, &Val); + AssertRCReturn(rc, rc); + } + else + { + RTTestFailed(g_hTest, "No connect address specified!\n"); + return VERR_INVALID_PARAMETER; + } + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Starting server for %s by connecting as client to %s:%RU32 ...\n", + pszDesc, pTcpOpts->szConnectAddr, pTcpOpts->uConnectPort); + } + + if (RT_SUCCESS(rc)) + { + rc = AudioTestSvcStart(pSrv); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Starting server for %s failed with %Rrc\n", pszDesc, rc); + } + + return rc; +} + +/** + * Initializes an audio test environment. + * + * @param pTstEnv Audio test environment to initialize. + */ +void audioTestEnvInit(PAUDIOTESTENV pTstEnv) +{ + RT_BZERO(pTstEnv, sizeof(AUDIOTESTENV)); + + audioTestIoOptsInitDefaults(&pTstEnv->IoOpts); + audioTestToneParmsInit(&pTstEnv->ToneParms); +} + +/** + * Creates an audio test environment. + * + * @returns VBox status code. + * @param pTstEnv Audio test environment to create. + * @param pDrvStack Driver stack to use. + */ +int audioTestEnvCreate(PAUDIOTESTENV pTstEnv, PAUDIOTESTDRVSTACK pDrvStack) +{ + AssertReturn(PDMAudioPropsAreValid(&pTstEnv->IoOpts.Props), VERR_WRONG_ORDER); + + int rc = VINF_SUCCESS; + + pTstEnv->pDrvStack = pDrvStack; + + /* + * Set sane defaults if not already set. + */ + if (!RTStrNLen(pTstEnv->szTag, sizeof(pTstEnv->szTag))) + { + rc = AudioTestGenTag(pTstEnv->szTag, sizeof(pTstEnv->szTag)); + AssertRCReturn(rc, rc); + } + + if (!RTStrNLen(pTstEnv->szPathTemp, sizeof(pTstEnv->szPathTemp))) + { + rc = AudioTestPathGetTemp(pTstEnv->szPathTemp, sizeof(pTstEnv->szPathTemp)); + AssertRCReturn(rc, rc); + } + + if (!RTStrNLen(pTstEnv->szPathOut, sizeof(pTstEnv->szPathOut))) + { + rc = RTPathJoin(pTstEnv->szPathOut, sizeof(pTstEnv->szPathOut), pTstEnv->szPathTemp, "vkat-temp"); + AssertRCReturn(rc, rc); + } + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Initializing environment for mode '%s'\n", pTstEnv->enmMode == AUDIOTESTMODE_HOST ? "host" : "guest"); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Using tag '%s'\n", pTstEnv->szTag); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Output directory is '%s'\n", pTstEnv->szPathOut); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Temp directory is '%s'\n", pTstEnv->szPathTemp); + + char szPathTemp[RTPATH_MAX]; + if ( !strlen(pTstEnv->szPathTemp) + || !strlen(pTstEnv->szPathOut)) + rc = RTPathTemp(szPathTemp, sizeof(szPathTemp)); + + if ( RT_SUCCESS(rc) + && !strlen(pTstEnv->szPathTemp)) + rc = RTPathJoin(pTstEnv->szPathTemp, sizeof(pTstEnv->szPathTemp), szPathTemp, "vkat-temp"); + + if (RT_SUCCESS(rc)) + { + rc = RTDirCreate(pTstEnv->szPathTemp, RTFS_UNIX_IRWXU, 0 /* fFlags */); + if (rc == VERR_ALREADY_EXISTS) + rc = VINF_SUCCESS; + } + + if ( RT_SUCCESS(rc) + && !strlen(pTstEnv->szPathOut)) + rc = RTPathJoin(pTstEnv->szPathOut, sizeof(pTstEnv->szPathOut), szPathTemp, "vkat"); + + if (RT_SUCCESS(rc)) + { + rc = RTDirCreate(pTstEnv->szPathOut, RTFS_UNIX_IRWXU, 0 /* fFlags */); + if (rc == VERR_ALREADY_EXISTS) + rc = VINF_SUCCESS; + } + + if (RT_FAILURE(rc)) + return rc; + + /** + * For NAT'ed VMs we use (default): + * - client mode (uConnectAddr / uConnectPort) on the guest. + * - server mode (uBindAddr / uBindPort) on the host. + */ + if ( !pTstEnv->TcpOpts.szConnectAddr[0] + && !pTstEnv->TcpOpts.szBindAddr[0]) + RTStrCopy(pTstEnv->TcpOpts.szBindAddr, sizeof(pTstEnv->TcpOpts.szBindAddr), "0.0.0.0"); + + /* + * Determine connection mode based on set variables. + */ + if ( pTstEnv->TcpOpts.szBindAddr[0] + && pTstEnv->TcpOpts.szConnectAddr[0]) + { + pTstEnv->TcpOpts.enmConnMode = ATSCONNMODE_BOTH; + } + else if (pTstEnv->TcpOpts.szBindAddr[0]) + pTstEnv->TcpOpts.enmConnMode = ATSCONNMODE_SERVER; + else /* "Reversed mode", i.e. used for NATed VMs. */ + pTstEnv->TcpOpts.enmConnMode = ATSCONNMODE_CLIENT; + + /* Set a back reference to the test environment for the callback context. */ + pTstEnv->CallbackCtx.pTstEnv = pTstEnv; + + ATSCALLBACKS Callbacks; + RT_ZERO(Callbacks); + Callbacks.pvUser = &pTstEnv->CallbackCtx; + + if (pTstEnv->enmMode == AUDIOTESTMODE_GUEST) + { + Callbacks.pfnHowdy = audioTestGstAtsHowdyCallback; + Callbacks.pfnBye = audioTestGstAtsByeCallback; + Callbacks.pfnTestSetBegin = audioTestGstAtsTestSetBeginCallback; + Callbacks.pfnTestSetEnd = audioTestGstAtsTestSetEndCallback; + Callbacks.pfnTonePlay = audioTestGstAtsTonePlayCallback; + Callbacks.pfnToneRecord = audioTestGstAtsToneRecordCallback; + Callbacks.pfnTestSetSendBegin = audioTestGstAtsTestSetSendBeginCallback; + Callbacks.pfnTestSetSendRead = audioTestGstAtsTestSetSendReadCallback; + Callbacks.pfnTestSetSendEnd = audioTestGstAtsTestSetSendEndCallback; + + if (!pTstEnv->TcpOpts.uBindPort) + pTstEnv->TcpOpts.uBindPort = ATS_TCP_DEF_BIND_PORT_GUEST; + + if (!pTstEnv->TcpOpts.uConnectPort) + pTstEnv->TcpOpts.uConnectPort = ATS_TCP_DEF_CONNECT_PORT_GUEST; + + pTstEnv->pSrv = (PATSSERVER)RTMemAlloc(sizeof(ATSSERVER)); + AssertPtrReturn(pTstEnv->pSrv, VERR_NO_MEMORY); + + /* + * Start the ATS (Audio Test Service) on the guest side. + * That service then will perform playback and recording operations on the guest, triggered from the host. + * + * When running this in self-test mode, that service also can be run on the host if nothing else is specified. + * Note that we have to bind to "0.0.0.0" by default so that the host can connect to it. + */ + rc = audioTestEnvConfigureAndStartTcpServer(pTstEnv->pSrv, &Callbacks, "guest", &pTstEnv->TcpOpts); + } + else /* Host mode */ + { + if (!pTstEnv->TcpOpts.uBindPort) + pTstEnv->TcpOpts.uBindPort = ATS_TCP_DEF_BIND_PORT_HOST; + + if (!pTstEnv->TcpOpts.uConnectPort) + pTstEnv->TcpOpts.uConnectPort = ATS_TCP_DEF_CONNECT_PORT_HOST_PORT_FWD; + + /** + * Note: Don't set pTstEnv->TcpOpts.szTcpConnectAddr by default here, as this specifies what connection mode + * (client / server / both) we use on the host. + */ + + /* We need to start a server on the host so that VMs configured with NAT networking + * can connect to it as well. */ + rc = AudioTestSvcClientCreate(&pTstEnv->u.Host.AtsClGuest); + if (RT_SUCCESS(rc)) + rc = audioTestEnvConnectViaTcp(pTstEnv, &pTstEnv->u.Host.AtsClGuest, + "host -> guest", &pTstEnv->TcpOpts); + if (RT_SUCCESS(rc)) + { + AUDIOTESTENVTCPOPTS ValKitTcpOpts; + RT_ZERO(ValKitTcpOpts); + + /* We only connect as client to the Validation Kit audio driver ATS. */ + ValKitTcpOpts.enmConnMode = ATSCONNMODE_CLIENT; + + /* For now we ASSUME that the Validation Kit audio driver ATS runs on the same host as VKAT (this binary) runs on. */ + ValKitTcpOpts.uConnectPort = ATS_TCP_DEF_CONNECT_PORT_VALKIT; /** @todo Make this dynamic. */ + RTStrCopy(ValKitTcpOpts.szConnectAddr, sizeof(ValKitTcpOpts.szConnectAddr), ATS_TCP_DEF_CONNECT_HOST_ADDR_STR); /** @todo Ditto. */ + + rc = AudioTestSvcClientCreate(&pTstEnv->u.Host.AtsClValKit); + if (RT_SUCCESS(rc)) + { + rc = audioTestEnvConnectViaTcp(pTstEnv, &pTstEnv->u.Host.AtsClValKit, + "host -> valkit", &ValKitTcpOpts); + if (RT_FAILURE(rc)) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Unable to connect to the Validation Kit audio driver!\n" + "There could be multiple reasons:\n\n" + " - Wrong host being used\n" + " - VirtualBox host version is too old\n" + " - Audio debug mode is not enabled\n" + " - Support for Validation Kit audio driver is not included\n" + " - Firewall / network configuration problem\n"); + } + } + } + + return rc; +} + +/** + * Destroys an audio test environment. + * + * @param pTstEnv Audio test environment to destroy. + */ +void audioTestEnvDestroy(PAUDIOTESTENV pTstEnv) +{ + if (!pTstEnv) + return; + + /* When in host mode, we need to destroy our ATS clients in order to also let + * the ATS server(s) know we're going to quit. */ + if (pTstEnv->enmMode == AUDIOTESTMODE_HOST) + { + AudioTestSvcClientDestroy(&pTstEnv->u.Host.AtsClValKit); + AudioTestSvcClientDestroy(&pTstEnv->u.Host.AtsClGuest); + } + + if (pTstEnv->pSrv) + { + int rc2 = AudioTestSvcDestroy(pTstEnv->pSrv); + AssertRC(rc2); + + RTMemFree(pTstEnv->pSrv); + pTstEnv->pSrv = NULL; + } + + for (unsigned i = 0; i < RT_ELEMENTS(pTstEnv->aStreams); i++) + { + int rc2 = audioTestStreamDestroy(pTstEnv->pDrvStack, &pTstEnv->aStreams[i]); + if (RT_FAILURE(rc2)) + RTTestFailed(g_hTest, "Stream destruction for stream #%u failed with %Rrc\n", i, rc2); + } + + /* Try cleaning up a bit. */ + RTDirRemove(pTstEnv->szPathTemp); + RTDirRemove(pTstEnv->szPathOut); + + pTstEnv->pDrvStack = NULL; +} + +/** + * Closes, packs up and destroys a test environment. + * + * @returns VBox status code. + * @param pTstEnv Test environment to handle. + * @param fPack Whether to pack the test set up before destroying / wiping it. + * @param pszPackFile Where to store the packed test set file on success. Can be NULL if \a fPack is \c false. + * @param cbPackFile Size (in bytes) of \a pszPackFile. Can be 0 if \a fPack is \c false. + */ +int audioTestEnvPrologue(PAUDIOTESTENV pTstEnv, bool fPack, char *pszPackFile, size_t cbPackFile) +{ + /* Close the test set first. */ + AudioTestSetClose(&pTstEnv->Set); + + int rc = VINF_SUCCESS; + + if (fPack) + { + /* Before destroying the test environment, pack up the test set so + * that it's ready for transmission. */ + rc = AudioTestSetPack(&pTstEnv->Set, pTstEnv->szPathOut, pszPackFile, cbPackFile); + if (RT_SUCCESS(rc)) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test set packed up to '%s'\n", pszPackFile); + } + + if (!g_fDrvAudioDebug) /* Don't wipe stuff when debugging. Can be useful for introspecting data. */ + /* ignore rc */ AudioTestSetWipe(&pTstEnv->Set); + + AudioTestSetDestroy(&pTstEnv->Set); + + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Test set prologue failed with %Rrc\n", rc); + + return rc; +} + +/** + * Initializes an audio test parameters set. + * + * @param pTstParms Test parameters set to initialize. + */ +void audioTestParmsInit(PAUDIOTESTPARMS pTstParms) +{ + RT_ZERO(*pTstParms); +} + +/** + * Destroys an audio test parameters set. + * + * @param pTstParms Test parameters set to destroy. + */ +void audioTestParmsDestroy(PAUDIOTESTPARMS pTstParms) +{ + if (!pTstParms) + return; + + return; +} + diff --git a/src/VBox/ValidationKit/utils/audio/vkatDriverStack.cpp b/src/VBox/ValidationKit/utils/audio/vkatDriverStack.cpp new file mode 100644 index 00000000..9b54467f --- /dev/null +++ b/src/VBox/ValidationKit/utils/audio/vkatDriverStack.cpp @@ -0,0 +1,1621 @@ +/* $Id: vkatDriverStack.cpp $ */ +/** @file + * Validation Kit Audio Test (VKAT) - Driver stack code. + */ + +/* + * Copyright (C) 2021-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_AUDIO_TEST +#include <iprt/log.h> + +#include <iprt/errcore.h> +#include <iprt/message.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/uuid.h> +#include <iprt/test.h> + + +/** + * Internal driver instance data + * @note This must be put here as it's needed before pdmdrv.h is included. + */ +typedef struct PDMDRVINSINT +{ + /** The stack the drive belongs to. */ + struct AUDIOTESTDRVSTACK *pStack; +} PDMDRVINSINT; +#define PDMDRVINSINT_DECLARED + +#include "vkatInternal.h" +#include "VBoxDD.h" + + + +/********************************************************************************************************************************* +* Fake PDM Driver Handling. * +*********************************************************************************************************************************/ + +/** @name Driver Fakes/Stubs + * + * @{ */ + +VMMR3DECL(PCFGMNODE) audioTestDrvHlp_CFGMR3GetChild(PCFGMNODE pNode, const char *pszPath) +{ + RT_NOREF(pNode, pszPath); + return NULL; +} + + +VMMR3DECL(int) audioTestDrvHlp_CFGMR3QueryString(PCFGMNODE pNode, const char *pszName, char *pszString, size_t cchString) +{ + if (pNode != NULL) + { + PCPDMDRVREG pDrvReg = (PCPDMDRVREG)pNode; + if (g_uVerbosity > 2) + RTPrintf("debug: CFGMR3QueryString([%s], %s, %p, %#x)\n", pDrvReg->szName, pszName, pszString, cchString); + + if ( ( strcmp(pDrvReg->szName, "PulseAudio") == 0 + || strcmp(pDrvReg->szName, "HostAudioWas") == 0) + && strcmp(pszName, "VmName") == 0) + return RTStrCopy(pszString, cchString, "vkat"); + + if ( strcmp(pDrvReg->szName, "HostAudioWas") == 0 + && strcmp(pszName, "VmUuid") == 0) + return RTStrCopy(pszString, cchString, "794c9192-d045-4f28-91ed-46253ac9998e"); + } + else if (g_uVerbosity > 2) + RTPrintf("debug: CFGMR3QueryString(%p, %s, %p, %#x)\n", pNode, pszName, pszString, cchString); + + return VERR_CFGM_VALUE_NOT_FOUND; +} + + +VMMR3DECL(int) audioTestDrvHlp_CFGMR3QueryStringAlloc(PCFGMNODE pNode, const char *pszName, char **ppszString) +{ + char szStr[128]; + int rc = audioTestDrvHlp_CFGMR3QueryString(pNode, pszName, szStr, sizeof(szStr)); + if (RT_SUCCESS(rc)) + *ppszString = RTStrDup(szStr); + + return rc; +} + + +VMMR3DECL(void) audioTestDrvHlp_MMR3HeapFree(PPDMDRVINS pDrvIns, void *pv) +{ + RT_NOREF(pDrvIns); + + /* counterpart to CFGMR3QueryStringAlloc */ + RTStrFree((char *)pv); +} + + +VMMR3DECL(int) audioTestDrvHlp_CFGMR3QueryStringDef(PCFGMNODE pNode, const char *pszName, char *pszString, size_t cchString, const char *pszDef) +{ + PCPDMDRVREG pDrvReg = (PCPDMDRVREG)pNode; + if (RT_VALID_PTR(pDrvReg)) + { + const char *pszRet = pszDef; + if ( g_pszDrvAudioDebug + && strcmp(pDrvReg->szName, "AUDIO") == 0 + && strcmp(pszName, "DebugPathOut") == 0) + pszRet = g_pszDrvAudioDebug; + + int rc = RTStrCopy(pszString, cchString, pszRet); + + if (g_uVerbosity > 2) + RTPrintf("debug: CFGMR3QueryStringDef([%s], %s, %p, %#x, %s) -> '%s' + %Rrc\n", + pDrvReg->szName, pszName, pszString, cchString, pszDef, pszRet, rc); + return rc; + } + + if (g_uVerbosity > 2) + RTPrintf("debug: CFGMR3QueryStringDef(%p, %s, %p, %#x, %s)\n", pNode, pszName, pszString, cchString, pszDef); + return RTStrCopy(pszString, cchString, pszDef); +} + + +VMMR3DECL(int) audioTestDrvHlp_CFGMR3QueryBoolDef(PCFGMNODE pNode, const char *pszName, bool *pf, bool fDef) +{ + PCPDMDRVREG pDrvReg = (PCPDMDRVREG)pNode; + if (RT_VALID_PTR(pDrvReg)) + { + *pf = fDef; + if ( strcmp(pDrvReg->szName, "AUDIO") == 0 + && strcmp(pszName, "DebugEnabled") == 0) + *pf = g_fDrvAudioDebug; + + if (g_uVerbosity > 2) + RTPrintf("debug: CFGMR3QueryBoolDef([%s], %s, %p, %RTbool) -> %RTbool\n", pDrvReg->szName, pszName, pf, fDef, *pf); + return VINF_SUCCESS; + } + *pf = fDef; + return VINF_SUCCESS; +} + + +VMMR3DECL(int) audioTestDrvHlp_CFGMR3QueryU8(PCFGMNODE pNode, const char *pszName, uint8_t *pu8) +{ + RT_NOREF(pNode, pszName, pu8); + return VERR_CFGM_VALUE_NOT_FOUND; +} + + +VMMR3DECL(int) audioTestDrvHlp_CFGMR3QueryU32(PCFGMNODE pNode, const char *pszName, uint32_t *pu32) +{ + RT_NOREF(pNode, pszName, pu32); + return VERR_CFGM_VALUE_NOT_FOUND; +} + + +VMMR3DECL(int) audioTestDrvHlp_CFGMR3ValidateConfig(PCFGMNODE pNode, const char *pszNode, + const char *pszValidValues, const char *pszValidNodes, + const char *pszWho, uint32_t uInstance) +{ + RT_NOREF(pNode, pszNode, pszValidValues, pszValidNodes, pszWho, uInstance); + return VINF_SUCCESS; +} + +/** @} */ + +/** @name Driver Helper Fakes + * @{ */ + +static DECLCALLBACK(int) audioTestDrvHlp_Attach(PPDMDRVINS pDrvIns, uint32_t fFlags, PPDMIBASE *ppBaseInterface) +{ + /* DrvAudio must be allowed to attach the backend driver (paranoid + backend drivers may call us to check that nothing is attached). */ + if (strcmp(pDrvIns->pReg->szName, "AUDIO") == 0) + { + PAUDIOTESTDRVSTACK pDrvStack = pDrvIns->Internal.s.pStack; + AssertReturn(pDrvStack->pDrvBackendIns == NULL, VERR_PDM_DRIVER_ALREADY_ATTACHED); + + if (g_uVerbosity > 1) + RTMsgInfo("Attaching backend '%s' to DrvAudio...\n", pDrvStack->pDrvReg->szName); + int rc = audioTestDrvConstruct(pDrvStack, pDrvStack->pDrvReg, pDrvIns, &pDrvStack->pDrvBackendIns); + if (RT_SUCCESS(rc)) + { + if (ppBaseInterface) + *ppBaseInterface = &pDrvStack->pDrvBackendIns->IBase; + } + else + RTMsgError("Failed to attach backend: %Rrc", rc); + return rc; + } + RT_NOREF(fFlags); + return VERR_PDM_NO_ATTACHED_DRIVER; +} + + +static DECLCALLBACK(void) audioTestDrvHlp_STAMRegister(PPDMDRVINS pDrvIns, void *pvSample, STAMTYPE enmType, const char *pszName, + STAMUNIT enmUnit, const char *pszDesc) +{ + RT_NOREF(pDrvIns, pvSample, enmType, pszName, enmUnit, pszDesc); +} + + +static DECLCALLBACK(void) audioTestDrvHlp_STAMRegisterF(PPDMDRVINS pDrvIns, void *pvSample, STAMTYPE enmType, + STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, const char *pszDesc, + const char *pszName, ...) +{ + RT_NOREF(pDrvIns, pvSample, enmType, enmVisibility, enmUnit, pszDesc, pszName); +} + + +static DECLCALLBACK(void) audioTestDrvHlp_STAMRegisterV(PPDMDRVINS pDrvIns, void *pvSample, STAMTYPE enmType, + STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, const char *pszDesc, + const char *pszName, va_list args) +{ + RT_NOREF(pDrvIns, pvSample, enmType, enmVisibility, enmUnit, pszDesc, pszName, args); +} + + +static DECLCALLBACK(int) audioTestDrvHlp_STAMDeregister(PPDMDRVINS pDrvIns, void *pvSample) +{ + RT_NOREF(pDrvIns, pvSample); + return VINF_SUCCESS; +} + + +static DECLCALLBACK(int) audioTestDrvHlp_STAMDeregisterByPrefix(PPDMDRVINS pDrvIns, const char *pszPrefix) +{ + RT_NOREF(pDrvIns, pszPrefix); + return VINF_SUCCESS; +} + +/** + * Get the driver helpers. + */ +static const PDMDRVHLPR3 *audioTestFakeGetDrvHlp(void) +{ + /* + * Note! No initializer for s_DrvHlp (also why it's not a file global). + * We do not want to have to update this code every time PDMDRVHLPR3 + * grows new entries or are otherwise modified. Only when the + * entries used by the audio driver changes do we want to change + * our code. + */ + static PDMDRVHLPR3 s_DrvHlp; + if (s_DrvHlp.u32Version != PDM_DRVHLPR3_VERSION) + { + s_DrvHlp.u32Version = PDM_DRVHLPR3_VERSION; + s_DrvHlp.u32TheEnd = PDM_DRVHLPR3_VERSION; + s_DrvHlp.pfnAttach = audioTestDrvHlp_Attach; + s_DrvHlp.pfnSTAMRegister = audioTestDrvHlp_STAMRegister; + s_DrvHlp.pfnSTAMRegisterF = audioTestDrvHlp_STAMRegisterF; + s_DrvHlp.pfnSTAMRegisterV = audioTestDrvHlp_STAMRegisterV; + s_DrvHlp.pfnSTAMDeregister = audioTestDrvHlp_STAMDeregister; + s_DrvHlp.pfnSTAMDeregisterByPrefix = audioTestDrvHlp_STAMDeregisterByPrefix; + s_DrvHlp.pfnCFGMGetChild = audioTestDrvHlp_CFGMR3GetChild; + s_DrvHlp.pfnCFGMQueryString = audioTestDrvHlp_CFGMR3QueryString; + s_DrvHlp.pfnCFGMQueryStringAlloc = audioTestDrvHlp_CFGMR3QueryStringAlloc; + s_DrvHlp.pfnMMHeapFree = audioTestDrvHlp_MMR3HeapFree; + s_DrvHlp.pfnCFGMQueryStringDef = audioTestDrvHlp_CFGMR3QueryStringDef; + s_DrvHlp.pfnCFGMQueryBoolDef = audioTestDrvHlp_CFGMR3QueryBoolDef; + s_DrvHlp.pfnCFGMQueryU8 = audioTestDrvHlp_CFGMR3QueryU8; + s_DrvHlp.pfnCFGMQueryU32 = audioTestDrvHlp_CFGMR3QueryU32; + s_DrvHlp.pfnCFGMValidateConfig = audioTestDrvHlp_CFGMR3ValidateConfig; + } + return &s_DrvHlp; +} + +/** @} */ + + +/** + * Implementation of PDMIBASE::pfnQueryInterface for a fake device above + * DrvAudio. + */ +static DECLCALLBACK(void *) audioTestFakeDeviceIBaseQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, pInterface); + RTMsgWarning("audioTestFakeDeviceIBaseQueryInterface: Unknown interface: %s\n", pszIID); + return NULL; +} + +/** IBase interface for a fake device above DrvAudio. */ +static PDMIBASE g_AudioTestFakeDeviceIBase = { audioTestFakeDeviceIBaseQueryInterface }; + + +static DECLCALLBACK(int) audioTestIHostAudioPort_DoOnWorkerThread(PPDMIHOSTAUDIOPORT pInterface, PPDMAUDIOBACKENDSTREAM pStream, + uintptr_t uUser, void *pvUser) +{ + RT_NOREF(pInterface, pStream, uUser, pvUser); + RTMsgWarning("audioTestIHostAudioPort_DoOnWorkerThread was called\n"); + return VERR_NOT_IMPLEMENTED; +} + + +DECLCALLBACK(void) audioTestIHostAudioPort_NotifyDeviceChanged(PPDMIHOSTAUDIOPORT pInterface, PDMAUDIODIR enmDir, void *pvUser) +{ + RT_NOREF(pInterface, enmDir, pvUser); + RTMsgWarning("audioTestIHostAudioPort_NotifyDeviceChanged was called\n"); +} + + +static DECLCALLBACK(void) audioTestIHostAudioPort_StreamNotifyPreparingDeviceSwitch(PPDMIHOSTAUDIOPORT pInterface, + PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + RTMsgWarning("audioTestIHostAudioPort_StreamNotifyPreparingDeviceSwitch was called\n"); +} + + +static DECLCALLBACK(void) audioTestIHostAudioPort_StreamNotifyDeviceChanged(PPDMIHOSTAUDIOPORT pInterface, + PPDMAUDIOBACKENDSTREAM pStream, bool fReInit) +{ + RT_NOREF(pInterface, pStream, fReInit); + RTMsgWarning("audioTestIHostAudioPort_StreamNotifyDeviceChanged was called\n"); +} + + +static DECLCALLBACK(void) audioTestIHostAudioPort_NotifyDevicesChanged(PPDMIHOSTAUDIOPORT pInterface) +{ + RT_NOREF(pInterface); + RTMsgWarning("audioTestIHostAudioPort_NotifyDevicesChanged was called\n"); +} + + +static PDMIHOSTAUDIOPORT g_AudioTestIHostAudioPort = +{ + audioTestIHostAudioPort_DoOnWorkerThread, + audioTestIHostAudioPort_NotifyDeviceChanged, + audioTestIHostAudioPort_StreamNotifyPreparingDeviceSwitch, + audioTestIHostAudioPort_StreamNotifyDeviceChanged, + audioTestIHostAudioPort_NotifyDevicesChanged, +}; + + +/** + * Implementation of PDMIBASE::pfnQueryInterface for a fake DrvAudio above a + * backend. + */ +static DECLCALLBACK(void *) audioTestFakeDrvAudioIBaseQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, pInterface); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIOPORT, &g_AudioTestIHostAudioPort); + RTMsgWarning("audioTestFakeDrvAudioIBaseQueryInterface: Unknown interface: %s\n", pszIID); + return NULL; +} + + +/** IBase interface for a fake DrvAudio above a lonesome backend. */ +static PDMIBASE g_AudioTestFakeDrvAudioIBase = { audioTestFakeDrvAudioIBaseQueryInterface }; + + + +/** + * Constructs a PDM audio driver instance. + * + * @returns VBox status code. + * @param pDrvStack The stack this is associated with. + * @param pDrvReg PDM driver registration record to use for construction. + * @param pParentDrvIns The parent driver (if any). + * @param ppDrvIns Where to return the driver instance structure. + */ +int audioTestDrvConstruct(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, PPDMDRVINS pParentDrvIns, + PPPDMDRVINS ppDrvIns) +{ + /* The destruct function must have valid data to work with. */ + *ppDrvIns = NULL; + + /* + * Check registration structure validation (doesn't need to be too + * thorough, PDM check it in detail on every VM startup). + */ + AssertPtrReturn(pDrvReg, VERR_INVALID_POINTER); + RTMsgInfo("Initializing backend '%s' ...\n", pDrvReg->szName); + AssertPtrReturn(pDrvReg->pfnConstruct, VERR_INVALID_PARAMETER); + + /* + * Create the instance data structure. + */ + PPDMDRVINS pDrvIns = (PPDMDRVINS)RTMemAllocZVar(RT_UOFFSETOF_DYN(PDMDRVINS, achInstanceData[pDrvReg->cbInstance])); + RTTEST_CHECK_RET(g_hTest, pDrvIns, VERR_NO_MEMORY); + + pDrvIns->u32Version = PDM_DRVINS_VERSION; + pDrvIns->iInstance = 0; + pDrvIns->pHlpR3 = audioTestFakeGetDrvHlp(); + pDrvIns->pvInstanceDataR3 = &pDrvIns->achInstanceData[0]; + pDrvIns->pReg = pDrvReg; + pDrvIns->pCfg = (PCFGMNODE)pDrvReg; + pDrvIns->Internal.s.pStack = pDrvStack; + pDrvIns->pUpBase = NULL; + pDrvIns->pDownBase = NULL; + if (pParentDrvIns) + { + Assert(pParentDrvIns->pDownBase == NULL); + pParentDrvIns->pDownBase = &pDrvIns->IBase; + pDrvIns->pUpBase = &pParentDrvIns->IBase; + } + else if (strcmp(pDrvReg->szName, "AUDIO") == 0) + pDrvIns->pUpBase = &g_AudioTestFakeDeviceIBase; + else + pDrvIns->pUpBase = &g_AudioTestFakeDrvAudioIBase; + + /* + * Invoke the constructor. + */ + int rc = pDrvReg->pfnConstruct(pDrvIns, pDrvIns->pCfg, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + *ppDrvIns = pDrvIns; + return VINF_SUCCESS; + } + + if (pDrvReg->pfnDestruct) + pDrvReg->pfnDestruct(pDrvIns); + RTMemFree(pDrvIns); + return rc; +} + + +/** + * Destructs a PDM audio driver instance. + * + * @param pDrvIns Driver instance to destruct. + */ +static void audioTestDrvDestruct(PPDMDRVINS pDrvIns) +{ + if (pDrvIns) + { + Assert(pDrvIns->u32Version == PDM_DRVINS_VERSION); + + if (pDrvIns->pReg->pfnDestruct) + pDrvIns->pReg->pfnDestruct(pDrvIns); + + pDrvIns->u32Version = 0; + pDrvIns->pReg = NULL; + RTMemFree(pDrvIns); + } +} + + +/** + * Sends the PDM driver a power off notification. + * + * @param pDrvIns Driver instance to notify. + */ +static void audioTestDrvNotifyPowerOff(PPDMDRVINS pDrvIns) +{ + if (pDrvIns) + { + Assert(pDrvIns->u32Version == PDM_DRVINS_VERSION); + if (pDrvIns->pReg->pfnPowerOff) + pDrvIns->pReg->pfnPowerOff(pDrvIns); + } +} + + +/** + * Deletes a driver stack. + * + * This will power off and destroy the drivers. + */ +void audioTestDriverStackDelete(PAUDIOTESTDRVSTACK pDrvStack) +{ + /* + * Do power off notifications (top to bottom). + */ + audioTestDrvNotifyPowerOff(pDrvStack->pDrvAudioIns); + audioTestDrvNotifyPowerOff(pDrvStack->pDrvBackendIns); + + /* + * Drivers are destroyed from bottom to top (closest to the device). + */ + audioTestDrvDestruct(pDrvStack->pDrvBackendIns); + pDrvStack->pDrvBackendIns = NULL; + pDrvStack->pIHostAudio = NULL; + + audioTestDrvDestruct(pDrvStack->pDrvAudioIns); + pDrvStack->pDrvAudioIns = NULL; + pDrvStack->pIAudioConnector = NULL; + + PDMAudioHostEnumDelete(&pDrvStack->DevEnum); +} + + +/** + * Initializes a driver stack, extended version. + * + * @returns VBox status code. + * @param pDrvStack The driver stack to initialize. + * @param pDrvReg The backend driver to use. + * @param fEnabledIn Whether input is enabled or not on creation time. + * @param fEnabledOut Whether output is enabled or not on creation time. + * @param fWithDrvAudio Whether to include DrvAudio in the stack or not. + */ +int audioTestDriverStackInitEx(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, bool fEnabledIn, bool fEnabledOut, bool fWithDrvAudio) +{ + int rc; + + RT_ZERO(*pDrvStack); + pDrvStack->pDrvReg = pDrvReg; + + PDMAudioHostEnumInit(&pDrvStack->DevEnum); + + if (!fWithDrvAudio) + rc = audioTestDrvConstruct(pDrvStack, pDrvReg, NULL /*pParentDrvIns*/, &pDrvStack->pDrvBackendIns); + else + { + rc = audioTestDrvConstruct(pDrvStack, &g_DrvAUDIO, NULL /*pParentDrvIns*/, &pDrvStack->pDrvAudioIns); + if (RT_SUCCESS(rc)) + { + Assert(pDrvStack->pDrvAudioIns); + PPDMIBASE const pIBase = &pDrvStack->pDrvAudioIns->IBase; + pDrvStack->pIAudioConnector = (PPDMIAUDIOCONNECTOR)pIBase->pfnQueryInterface(pIBase, PDMIAUDIOCONNECTOR_IID); + if (pDrvStack->pIAudioConnector) + { + /* Both input and output is disabled by default. */ + if (fEnabledIn) + rc = pDrvStack->pIAudioConnector->pfnEnable(pDrvStack->pIAudioConnector, PDMAUDIODIR_IN, true); + + if (RT_SUCCESS(rc)) + { + if (fEnabledOut) + rc = pDrvStack->pIAudioConnector->pfnEnable(pDrvStack->pIAudioConnector, PDMAUDIODIR_OUT, true); + } + + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Failed to enabled input and output: %Rrc", rc); + audioTestDriverStackDelete(pDrvStack); + } + } + else + { + RTTestFailed(g_hTest, "Failed to query PDMIAUDIOCONNECTOR"); + audioTestDriverStackDelete(pDrvStack); + rc = VERR_PDM_MISSING_INTERFACE; + } + } + } + + /* + * Get the IHostAudio interface and check that the host driver is working. + */ + if (RT_SUCCESS(rc)) + { + PPDMIBASE const pIBase = &pDrvStack->pDrvBackendIns->IBase; + pDrvStack->pIHostAudio = (PPDMIHOSTAUDIO)pIBase->pfnQueryInterface(pIBase, PDMIHOSTAUDIO_IID); + if (pDrvStack->pIHostAudio) + { + PDMAUDIOBACKENDSTS enmStatus = pDrvStack->pIHostAudio->pfnGetStatus(pDrvStack->pIHostAudio, PDMAUDIODIR_OUT); + if (enmStatus == PDMAUDIOBACKENDSTS_RUNNING) + return VINF_SUCCESS; + + RTTestFailed(g_hTest, "Expected backend status RUNNING, got %d instead", enmStatus); + } + else + RTTestFailed(g_hTest, "Failed to query PDMIHOSTAUDIO for '%s'", pDrvReg->szName); + audioTestDriverStackDelete(pDrvStack); + } + + return rc; +} + + +/** + * Initializes a driver stack. + * + * @returns VBox status code. + * @param pDrvStack The driver stack to initialize. + * @param pDrvReg The backend driver to use. + * @param fEnabledIn Whether input is enabled or not on creation time. + * @param fEnabledOut Whether output is enabled or not on creation time. + * @param fWithDrvAudio Whether to include DrvAudio in the stack or not. + */ +int audioTestDriverStackInit(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, bool fWithDrvAudio) +{ + return audioTestDriverStackInitEx(pDrvStack, pDrvReg, true /* fEnabledIn */, true /* fEnabledOut */, fWithDrvAudio); +} + +/** + * Initializes a driver stack by probing all backends in the order of appearance + * in the backends description table. + * + * @returns VBox status code. + * @param pDrvStack The driver stack to initialize. + * @param pDrvReg The backend driver to use. + * @param fEnabledIn Whether input is enabled or not on creation time. + * @param fEnabledOut Whether output is enabled or not on creation time. + * @param fWithDrvAudio Whether to include DrvAudio in the stack or not. + */ +int audioTestDriverStackProbe(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, bool fEnabledIn, bool fEnabledOut, bool fWithDrvAudio) +{ + int rc = VERR_IPE_UNINITIALIZED_STATUS; /* Shut up MSVC. */ + + for (size_t i = 0; i < g_cBackends; i++) + { + pDrvReg = g_aBackends[i].pDrvReg; + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Probing for backend '%s' ...\n", g_aBackends[i].pszName); + + rc = audioTestDriverStackInitEx(pDrvStack, pDrvReg, fEnabledIn, fEnabledOut, fWithDrvAudio); /** @todo Make in/out configurable, too. */ + if (RT_SUCCESS(rc)) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Probing backend '%s' successful\n", g_aBackends[i].pszName); + return rc; + } + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Probing backend '%s' failed with %Rrc, trying next one\n", + g_aBackends[i].pszName, rc); + continue; + } + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Probing all backends failed\n"); + return rc; +} + +/** + * Wrapper around PDMIHOSTAUDIO::pfnSetDevice. + */ +int audioTestDriverStackSetDevice(PAUDIOTESTDRVSTACK pDrvStack, PDMAUDIODIR enmDir, const char *pszDevId) +{ + int rc; + if ( pDrvStack->pIHostAudio + && pDrvStack->pIHostAudio->pfnSetDevice) + rc = pDrvStack->pIHostAudio->pfnSetDevice(pDrvStack->pIHostAudio, enmDir, pszDevId); + else if (!pszDevId || *pszDevId) + rc = VINF_SUCCESS; + else + rc = VERR_INVALID_FUNCTION; + return rc; +} + + +/** + * Common stream creation code. + * + * @returns VBox status code. + * @param pDrvStack The audio driver stack to create it via. + * @param pCfgReq The requested config. + * @param ppStream Where to return the stream pointer on success. + * @param pCfgAcq Where to return the actual (well, not necessarily when + * using DrvAudio, but probably the same) stream config on + * success (not used as input). + */ +static int audioTestDriverStackStreamCreate(PAUDIOTESTDRVSTACK pDrvStack, PCPDMAUDIOSTREAMCFG pCfgReq, + PPDMAUDIOSTREAM *ppStream, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX + 16]; + int rc; + *ppStream = NULL; + + if (pDrvStack->pIAudioConnector) + { + /* + * DrvAudio does most of the work here. + */ + rc = pDrvStack->pIAudioConnector->pfnStreamCreate(pDrvStack->pIAudioConnector, 0 /*fFlags*/, pCfgReq, ppStream); + if (RT_SUCCESS(rc)) + { + *pCfgAcq = (*ppStream)->Cfg; + RTMsgInfo("Created backend stream: %s\n", PDMAudioStrmCfgToString(pCfgReq, szTmp, sizeof(szTmp))); + return rc; + } + /* else: Don't set RTTestFailed(...) here, as test boxes (servers) don't have any audio hardware. + * Caller has check the rc then. */ + } + else + { + /* + * Get the config so we can see how big the PDMAUDIOBACKENDSTREAM + * structure actually is for this backend. + */ + PDMAUDIOBACKENDCFG BackendCfg; + rc = pDrvStack->pIHostAudio->pfnGetConfig(pDrvStack->pIHostAudio, &BackendCfg); + if (RT_SUCCESS(rc)) + { + if (BackendCfg.cbStream >= sizeof(PDMAUDIOBACKENDSTREAM)) + { + /* + * Allocate and initialize the stream. + */ + uint32_t const cbStream = sizeof(AUDIOTESTDRVSTACKSTREAM) - sizeof(PDMAUDIOBACKENDSTREAM) + BackendCfg.cbStream; + PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)RTMemAllocZVar(cbStream); + if (pStreamAt) + { + pStreamAt->Core.uMagic = PDMAUDIOSTREAM_MAGIC; + pStreamAt->Core.Cfg = *pCfgReq; + pStreamAt->Core.cbBackend = cbStream; + + pStreamAt->Backend.uMagic = PDMAUDIOBACKENDSTREAM_MAGIC; + pStreamAt->Backend.pStream = &pStreamAt->Core; + + /* + * Call the backend to create the stream. + */ + rc = pDrvStack->pIHostAudio->pfnStreamCreate(pDrvStack->pIHostAudio, &pStreamAt->Backend, + pCfgReq, &pStreamAt->Core.Cfg); + if (RT_SUCCESS(rc)) + { + if (g_uVerbosity > 1) + RTMsgInfo("Created backend stream: %s\n", + PDMAudioStrmCfgToString(&pStreamAt->Core.Cfg, szTmp, sizeof(szTmp))); + + /* Return if stream is ready: */ + if (rc == VINF_SUCCESS) + { + *ppStream = &pStreamAt->Core; + *pCfgAcq = pStreamAt->Core.Cfg; + return VINF_SUCCESS; + } + if (rc == VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED) + { + /* + * Do async init right here and now. + */ + rc = pDrvStack->pIHostAudio->pfnStreamInitAsync(pDrvStack->pIHostAudio, &pStreamAt->Backend, + false /*fDestroyed*/); + if (RT_SUCCESS(rc)) + { + *ppStream = &pStreamAt->Core; + *pCfgAcq = pStreamAt->Core.Cfg; + return VINF_SUCCESS; + } + + RTTestFailed(g_hTest, "pfnStreamInitAsync failed: %Rrc\n", rc); + } + else + { + RTTestFailed(g_hTest, "pfnStreamCreate returned unexpected info status: %Rrc", rc); + rc = VERR_IPE_UNEXPECTED_INFO_STATUS; + } + pDrvStack->pIHostAudio->pfnStreamDestroy(pDrvStack->pIHostAudio, &pStreamAt->Backend, true /*fImmediate*/); + } + /* else: Don't set RTTestFailed(...) here, as test boxes (servers) don't have any audio hardware. + * Caller has check the rc then. */ + } + else + { + RTTestFailed(g_hTest, "Out of memory!\n"); + rc = VERR_NO_MEMORY; + } + RTMemFree(pStreamAt); + } + else + { + RTTestFailed(g_hTest, "cbStream=%#x is too small, min %#zx!\n", BackendCfg.cbStream, sizeof(PDMAUDIOBACKENDSTREAM)); + rc = VERR_OUT_OF_RANGE; + } + } + else + RTTestFailed(g_hTest, "pfnGetConfig failed: %Rrc\n", rc); + } + return rc; +} + + +/** + * Creates an output stream. + * + * @returns VBox status code. + * @param pDrvStack The audio driver stack to create it via. + * @param pProps The audio properties to use. + * @param cMsBufferSize The buffer size in milliseconds. + * @param cMsPreBuffer The pre-buffering amount in milliseconds. + * @param cMsSchedulingHint The scheduling hint in milliseconds. + * @param ppStream Where to return the stream pointer on success. + * @param pCfgAcq Where to return the actual (well, not + * necessarily when using DrvAudio, but probably + * the same) stream config on success (not used as + * input). + */ +int audioTestDriverStackStreamCreateOutput(PAUDIOTESTDRVSTACK pDrvStack, PCPDMAUDIOPCMPROPS pProps, + uint32_t cMsBufferSize, uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint, + PPDMAUDIOSTREAM *ppStream, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + /* + * Calculate the stream config. + */ + PDMAUDIOSTREAMCFG CfgReq; + int rc = PDMAudioStrmCfgInitWithProps(&CfgReq, pProps); + AssertRC(rc); + CfgReq.enmDir = PDMAUDIODIR_OUT; + CfgReq.enmPath = PDMAUDIOPATH_OUT_FRONT; + CfgReq.Device.cMsSchedulingHint = cMsSchedulingHint == UINT32_MAX || cMsSchedulingHint == 0 + ? 10 : cMsSchedulingHint; + if (pDrvStack->pIAudioConnector && (cMsBufferSize == UINT32_MAX || cMsBufferSize == 0)) + CfgReq.Backend.cFramesBufferSize = 0; /* DrvAudio picks the default */ + else + CfgReq.Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(pProps, + cMsBufferSize == UINT32_MAX || cMsBufferSize == 0 + ? 300 : cMsBufferSize); + if (cMsPreBuffer == UINT32_MAX) + CfgReq.Backend.cFramesPreBuffering = pDrvStack->pIAudioConnector ? UINT32_MAX /*DrvAudo picks the default */ + : CfgReq.Backend.cFramesBufferSize * 2 / 3; + else + CfgReq.Backend.cFramesPreBuffering = PDMAudioPropsMilliToFrames(pProps, cMsPreBuffer); + if ( CfgReq.Backend.cFramesPreBuffering >= CfgReq.Backend.cFramesBufferSize + 16 + && !pDrvStack->pIAudioConnector /*DrvAudio deals with it*/ ) + { + RTMsgWarning("Cannot pre-buffer %#x frames with only %#x frames of buffer!", + CfgReq.Backend.cFramesPreBuffering, CfgReq.Backend.cFramesBufferSize); + CfgReq.Backend.cFramesPreBuffering = CfgReq.Backend.cFramesBufferSize > 16 + ? CfgReq.Backend.cFramesBufferSize - 16 : 0; + } + + static uint32_t s_idxStream = 0; + uint32_t const idxStream = s_idxStream++; + RTStrPrintf(CfgReq.szName, sizeof(CfgReq.szName), "out-%u", idxStream); + + /* + * Call common code to do the actual work. + */ + return audioTestDriverStackStreamCreate(pDrvStack, &CfgReq, ppStream, pCfgAcq); +} + + +/** + * Creates an input stream. + * + * @returns VBox status code. + * @param pDrvStack The audio driver stack to create it via. + * @param pProps The audio properties to use. + * @param cMsBufferSize The buffer size in milliseconds. + * @param cMsPreBuffer The pre-buffering amount in milliseconds. + * @param cMsSchedulingHint The scheduling hint in milliseconds. + * @param ppStream Where to return the stream pointer on success. + * @param pCfgAcq Where to return the actual (well, not + * necessarily when using DrvAudio, but probably + * the same) stream config on success (not used as + * input). + */ +int audioTestDriverStackStreamCreateInput(PAUDIOTESTDRVSTACK pDrvStack, PCPDMAUDIOPCMPROPS pProps, + uint32_t cMsBufferSize, uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint, + PPDMAUDIOSTREAM *ppStream, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + /* + * Calculate the stream config. + */ + PDMAUDIOSTREAMCFG CfgReq; + int rc = PDMAudioStrmCfgInitWithProps(&CfgReq, pProps); + AssertRC(rc); + CfgReq.enmDir = PDMAUDIODIR_IN; + CfgReq.enmPath = PDMAUDIOPATH_IN_LINE; + CfgReq.Device.cMsSchedulingHint = cMsSchedulingHint == UINT32_MAX || cMsSchedulingHint == 0 + ? 10 : cMsSchedulingHint; + if (pDrvStack->pIAudioConnector && (cMsBufferSize == UINT32_MAX || cMsBufferSize == 0)) + CfgReq.Backend.cFramesBufferSize = 0; /* DrvAudio picks the default */ + else + CfgReq.Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(pProps, + cMsBufferSize == UINT32_MAX || cMsBufferSize == 0 + ? 300 : cMsBufferSize); + if (cMsPreBuffer == UINT32_MAX) + CfgReq.Backend.cFramesPreBuffering = pDrvStack->pIAudioConnector ? UINT32_MAX /*DrvAudio picks the default */ + : CfgReq.Backend.cFramesBufferSize / 2; + else + CfgReq.Backend.cFramesPreBuffering = PDMAudioPropsMilliToFrames(pProps, cMsPreBuffer); + if ( CfgReq.Backend.cFramesPreBuffering >= CfgReq.Backend.cFramesBufferSize + 16 /** @todo way to little */ + && !pDrvStack->pIAudioConnector /*DrvAudio deals with it*/ ) + { + RTMsgWarning("Cannot pre-buffer %#x frames with only %#x frames of buffer!", + CfgReq.Backend.cFramesPreBuffering, CfgReq.Backend.cFramesBufferSize); + CfgReq.Backend.cFramesPreBuffering = CfgReq.Backend.cFramesBufferSize > 16 + ? CfgReq.Backend.cFramesBufferSize - 16 : 0; + } + + static uint32_t s_idxStream = 0; + uint32_t const idxStream = s_idxStream++; + RTStrPrintf(CfgReq.szName, sizeof(CfgReq.szName), "in-%u", idxStream); + + /* + * Call common code to do the actual work. + */ + return audioTestDriverStackStreamCreate(pDrvStack, &CfgReq, ppStream, pCfgAcq); +} + + +/** + * Destroys a stream. + * + * @param pDrvStack Driver stack the stream to destroy is assigned to. + * @param pStream Stream to destroy. Pointer will be NULL (invalid) after successful return. + */ +void audioTestDriverStackStreamDestroy(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream) +{ + if (!pStream) + return; + + if (pDrvStack->pIAudioConnector) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Destroying stream '%s' (IAudioConnector) ...\n", pStream->Cfg.szName); + int rc = pDrvStack->pIAudioConnector->pfnStreamDestroy(pDrvStack->pIAudioConnector, pStream, true /*fImmediate*/); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "pfnStreamDestroy failed: %Rrc", rc); + } + else + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Destroying stream '%s' (IHostAudio) ...\n", pStream->Cfg.szName); + PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream; + int rc = pDrvStack->pIHostAudio->pfnStreamDestroy(pDrvStack->pIHostAudio, &pStreamAt->Backend, true /*fImmediate*/); + if (RT_SUCCESS(rc)) + { + pStreamAt->Core.uMagic = ~PDMAUDIOSTREAM_MAGIC; + pStreamAt->Backend.uMagic = ~PDMAUDIOBACKENDSTREAM_MAGIC; + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Destroying stream '%s' done\n", pStream->Cfg.szName); + + RTMemFree(pStreamAt); + + pStreamAt = NULL; + pStream = NULL; + } + else + RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamDestroy failed: %Rrc", rc); + } +} + + +/** + * Enables a stream. + */ +int audioTestDriverStackStreamEnable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream) +{ + int rc; + if (pDrvStack->pIAudioConnector) + { + rc = pDrvStack->pIAudioConnector->pfnStreamControl(pDrvStack->pIAudioConnector, pStream, PDMAUDIOSTREAMCMD_ENABLE); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "pfnStreamControl/ENABLE failed: %Rrc", rc); + } + else + { + PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream; + rc = pDrvStack->pIHostAudio->pfnStreamEnable(pDrvStack->pIHostAudio, &pStreamAt->Backend); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamEnable failed: %Rrc", rc); + } + return rc; +} + + +/** + * Disables a stream. + */ +int AudioTestDriverStackStreamDisable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream) +{ + int rc; + if (pDrvStack->pIAudioConnector) + { + rc = pDrvStack->pIAudioConnector->pfnStreamControl(pDrvStack->pIAudioConnector, pStream, PDMAUDIOSTREAMCMD_DISABLE); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "pfnStreamControl/DISABLE failed: %Rrc", rc); + } + else + { + PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream; + rc = pDrvStack->pIHostAudio->pfnStreamDisable(pDrvStack->pIHostAudio, &pStreamAt->Backend); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamDisable failed: %Rrc", rc); + } + return rc; +} + + +/** + * Drains an output stream. + */ +int audioTestDriverStackStreamDrain(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream, bool fSync) +{ + int rc; + if (pDrvStack->pIAudioConnector) + { + /* + * Issue the drain request. + */ + rc = pDrvStack->pIAudioConnector->pfnStreamControl(pDrvStack->pIAudioConnector, pStream, PDMAUDIOSTREAMCMD_DRAIN); + if (RT_SUCCESS(rc) && fSync) + { + /* + * This is a synchronous drain, so wait for the driver to change state to inactive. + */ + PDMAUDIOSTREAMSTATE enmState; + while ( (enmState = pDrvStack->pIAudioConnector->pfnStreamGetState(pDrvStack->pIAudioConnector, pStream)) + >= PDMAUDIOSTREAMSTATE_ENABLED) + { + RTThreadSleep(2); + rc = pDrvStack->pIAudioConnector->pfnStreamIterate(pDrvStack->pIAudioConnector, pStream); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "pfnStreamIterate/DRAIN failed: %Rrc", rc); + break; + } + } + if (enmState != PDMAUDIOSTREAMSTATE_INACTIVE) + { + RTTestFailed(g_hTest, "Stream state not INACTIVE after draining: %s", PDMAudioStreamStateGetName(enmState)); + rc = VERR_AUDIO_STREAM_NOT_READY; + } + } + else if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "pfnStreamControl/ENABLE failed: %Rrc", rc); + } + else + { + /* + * Issue the drain request. + */ + PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream; + rc = pDrvStack->pIHostAudio->pfnStreamDrain(pDrvStack->pIHostAudio, &pStreamAt->Backend); + if (RT_SUCCESS(rc) && fSync) + { + RTMSINTERVAL const msTimeout = RT_MS_5MIN; /* 5 minutes should be really enough for draining our stuff. */ + uint64_t const tsStart = RTTimeMilliTS(); + + /* + * This is a synchronous drain, so wait for the driver to change state to inactive. + */ + PDMHOSTAUDIOSTREAMSTATE enmHostState; + while ( (enmHostState = pDrvStack->pIHostAudio->pfnStreamGetState(pDrvStack->pIHostAudio, &pStreamAt->Backend)) + == PDMHOSTAUDIOSTREAMSTATE_DRAINING) + { + RTThreadSleep(2); + uint32_t cbWritten = UINT32_MAX; + rc = pDrvStack->pIHostAudio->pfnStreamPlay(pDrvStack->pIHostAudio, &pStreamAt->Backend, + NULL /*pvBuf*/, 0 /*cbBuf*/, &cbWritten); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "pfnStreamPlay/DRAIN failed: %Rrc", rc); + break; + } + if (cbWritten != 0) + { + RTTestFailed(g_hTest, "pfnStreamPlay/DRAIN did not set cbWritten to zero: %#x", cbWritten); + rc = VERR_MISSING; + break; + } + + /* Fail-safe for audio stacks and/or implementations which mess up draining. + * + * Note: On some testboxes draining never seems to finish and thus is getting aborted, no clue why. + * The test result in the end still could be correct, although the actual draining problem + * needs to be investigated further. + * + * So don't make this (and the stream state check below) an error for now and just warn about it. + * + ** @todo Investigate draining issues on testboxes. + */ + if (RTTimeMilliTS() - tsStart > msTimeout) + { + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "Warning: Draining stream took too long (timeout is %RU32ms), giving up", msTimeout); + break; + } + } + if (enmHostState != PDMHOSTAUDIOSTREAMSTATE_OKAY) + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "Warning: Stream state not OKAY after draining: %s", PDMHostAudioStreamStateGetName(enmHostState)); + } + else if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamControl/ENABLE failed: %Rrc", rc); + } + return rc; +} + + +/** + * Checks if the stream is okay. + * @returns true if okay, false if not. + */ +bool audioTestDriverStackStreamIsOkay(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream) +{ + /* + * Get the stream status and check if it means is okay or not. + */ + bool fRc = false; + if (pDrvStack->pIAudioConnector) + { + PDMAUDIOSTREAMSTATE enmState = pDrvStack->pIAudioConnector->pfnStreamGetState(pDrvStack->pIAudioConnector, pStream); + switch (enmState) + { + case PDMAUDIOSTREAMSTATE_NOT_WORKING: + case PDMAUDIOSTREAMSTATE_NEED_REINIT: + break; + case PDMAUDIOSTREAMSTATE_INACTIVE: + case PDMAUDIOSTREAMSTATE_ENABLED: + case PDMAUDIOSTREAMSTATE_ENABLED_READABLE: + case PDMAUDIOSTREAMSTATE_ENABLED_WRITABLE: + fRc = true; + break; + /* no default */ + case PDMAUDIOSTREAMSTATE_INVALID: + case PDMAUDIOSTREAMSTATE_END: + case PDMAUDIOSTREAMSTATE_32BIT_HACK: + break; + } + } + else + { + PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream; + PDMHOSTAUDIOSTREAMSTATE enmHostState = pDrvStack->pIHostAudio->pfnStreamGetState(pDrvStack->pIHostAudio, + &pStreamAt->Backend); + switch (enmHostState) + { + case PDMHOSTAUDIOSTREAMSTATE_INITIALIZING: + case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING: + break; + case PDMHOSTAUDIOSTREAMSTATE_OKAY: + case PDMHOSTAUDIOSTREAMSTATE_DRAINING: + case PDMHOSTAUDIOSTREAMSTATE_INACTIVE: + fRc = true; + break; + /* no default */ + case PDMHOSTAUDIOSTREAMSTATE_INVALID: + case PDMHOSTAUDIOSTREAMSTATE_END: + case PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK: + break; + } + } + return fRc; +} + + +/** + * Gets the number of bytes it's currently possible to write to the stream. + */ +uint32_t audioTestDriverStackStreamGetWritable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream) +{ + uint32_t cbWritable; + if (pDrvStack->pIAudioConnector) + cbWritable = pDrvStack->pIAudioConnector->pfnStreamGetWritable(pDrvStack->pIAudioConnector, pStream); + else + { + PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream; + cbWritable = pDrvStack->pIHostAudio->pfnStreamGetWritable(pDrvStack->pIHostAudio, &pStreamAt->Backend); + } + return cbWritable; +} + + +/** + * Tries to play the @a cbBuf bytes of samples in @a pvBuf. + */ +int audioTestDriverStackStreamPlay(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream, + void const *pvBuf, uint32_t cbBuf, uint32_t *pcbPlayed) +{ + int rc; + if (pDrvStack->pIAudioConnector) + { + rc = pDrvStack->pIAudioConnector->pfnStreamPlay(pDrvStack->pIAudioConnector, pStream, pvBuf, cbBuf, pcbPlayed); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "pfnStreamPlay(,,,%#x,) failed: %Rrc", cbBuf, rc); + } + else + { + PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream; + rc = pDrvStack->pIHostAudio->pfnStreamPlay(pDrvStack->pIHostAudio, &pStreamAt->Backend, pvBuf, cbBuf, pcbPlayed); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamPlay(,,,%#x,) failed: %Rrc", cbBuf, rc); + } + return rc; +} + + +/** + * Gets the number of bytes it's currently possible to write to the stream. + */ +uint32_t audioTestDriverStackStreamGetReadable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream) +{ + uint32_t cbReadable; + if (pDrvStack->pIAudioConnector) + cbReadable = pDrvStack->pIAudioConnector->pfnStreamGetReadable(pDrvStack->pIAudioConnector, pStream); + else + { + PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream; + cbReadable = pDrvStack->pIHostAudio->pfnStreamGetReadable(pDrvStack->pIHostAudio, &pStreamAt->Backend); + } + return cbReadable; +} + + +/** + * Tries to capture @a cbBuf bytes of samples in @a pvBuf. + */ +int audioTestDriverStackStreamCapture(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream, + void *pvBuf, uint32_t cbBuf, uint32_t *pcbCaptured) +{ + int rc; + if (pDrvStack->pIAudioConnector) + { + rc = pDrvStack->pIAudioConnector->pfnStreamCapture(pDrvStack->pIAudioConnector, pStream, pvBuf, cbBuf, pcbCaptured); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "pfnStreamCapture(,,,%#x,) failed: %Rrc", cbBuf, rc); + } + else + { + PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream; + rc = pDrvStack->pIHostAudio->pfnStreamCapture(pDrvStack->pIHostAudio, &pStreamAt->Backend, pvBuf, cbBuf, pcbCaptured); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamCapture(,,,%#x,) failed: %Rrc", cbBuf, rc); + } + return rc; +} + + +/********************************************************************************************************************************* +* Mixed streams * +*********************************************************************************************************************************/ + +/** + * Initializing mixing for a stream. + * + * This can be used as a do-nothing wrapper for the stack. + * + * @returns VBox status code. + * @param pMix The mixing state. + * @param pStream The stream to mix to/from. + * @param pProps The mixer properties. Pass NULL for no mixing, just + * wrap the driver stack functionality. + * @param cMsBuffer The buffer size. + */ +int AudioTestMixStreamInit(PAUDIOTESTDRVMIXSTREAM pMix, PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream, + PCPDMAUDIOPCMPROPS pProps, uint32_t cMsBuffer) +{ + RT_ZERO(*pMix); + + AssertReturn(pDrvStack, VERR_INVALID_PARAMETER); + AssertReturn(pStream, VERR_INVALID_PARAMETER); + + pMix->pDrvStack = pDrvStack; + pMix->pStream = pStream; + if (!pProps) + { + pMix->pProps = &pStream->Cfg.Props; + return VINF_SUCCESS; + } + + /* + * Okay, we're doing mixing so we need to set up the mixer buffer + * and associated states. + */ + pMix->fDoMixing = true; + int rc = AudioMixBufInit(&pMix->MixBuf, "mixer", pProps, PDMAudioPropsMilliToFrames(pProps, cMsBuffer)); + if (RT_SUCCESS(rc)) + { + pMix->pProps = &pMix->MixBuf.Props; + + if (pStream->Cfg.enmDir == PDMAUDIODIR_IN) + { + rc = AudioMixBufInitPeekState(&pMix->MixBuf, &pMix->PeekState, &pMix->MixBuf.Props); + if (RT_SUCCESS(rc)) + { + rc = AudioMixBufInitWriteState(&pMix->MixBuf, &pMix->WriteState, &pStream->Cfg.Props); + if (RT_SUCCESS(rc)) + return rc; + } + } + else if (pStream->Cfg.enmDir == PDMAUDIODIR_OUT) + { + rc = AudioMixBufInitWriteState(&pMix->MixBuf, &pMix->WriteState, &pMix->MixBuf.Props); + if (RT_SUCCESS(rc)) + { + rc = AudioMixBufInitPeekState(&pMix->MixBuf, &pMix->PeekState, &pStream->Cfg.Props); + if (RT_SUCCESS(rc)) + return rc; + } + } + else + { + RTTestFailed(g_hTest, "Bogus stream direction!"); + rc = VERR_INVALID_STATE; + } + } + else + RTTestFailed(g_hTest, "AudioMixBufInit failed: %Rrc", rc); + RT_ZERO(*pMix); + return rc; +} + + +/** + * Terminate mixing (leaves the stream untouched). + * + * @param pMix The mixing state. + */ +void AudioTestMixStreamTerm(PAUDIOTESTDRVMIXSTREAM pMix) +{ + if (pMix->fDoMixing) + { + AudioMixBufTerm(&pMix->MixBuf); + pMix->pStream = NULL; + } + RT_ZERO(*pMix); +} + + +/** + * Worker that transports data between the mixer buffer and the drivers. + * + * @returns VBox status code. + * @param pMix The mixer stream setup to do transfers for. + */ +static int audioTestMixStreamTransfer(PAUDIOTESTDRVMIXSTREAM pMix) +{ + uint8_t abBuf[16384]; + if (pMix->pStream->Cfg.enmDir == PDMAUDIODIR_IN) + { + /* + * Try fill up the mixer buffer as much as possible. + * + * Slight fun part is that we have to calculate conversion + * ratio and be rather pessimistic about it. + */ + uint32_t const cbBuf = PDMAudioPropsFloorBytesToFrame(&pMix->pStream->Cfg.Props, sizeof(abBuf)); + for (;;) + { + /* + * Figure out how much we can move in this iteration. + */ + uint32_t cDstFrames = AudioMixBufFree(&pMix->MixBuf); + if (!cDstFrames) + break; + + uint32_t cbReadable = audioTestDriverStackStreamGetReadable(pMix->pDrvStack, pMix->pStream); + if (!cbReadable) + break; + + uint32_t cbToRead; + if (PDMAudioPropsHz(&pMix->pStream->Cfg.Props) == PDMAudioPropsHz(&pMix->MixBuf.Props)) + cbToRead = PDMAudioPropsFramesToBytes(&pMix->pStream->Cfg.Props, cDstFrames); + else + cbToRead = PDMAudioPropsFramesToBytes(&pMix->pStream->Cfg.Props, + (uint64_t)cDstFrames * PDMAudioPropsHz(&pMix->pStream->Cfg.Props) + / PDMAudioPropsHz(&pMix->MixBuf.Props)); + cbToRead = RT_MIN(cbToRead, RT_MIN(cbReadable, cbBuf)); + if (!cbToRead) + break; + + /* + * Get the data. + */ + uint32_t cbCaptured = 0; + int rc = audioTestDriverStackStreamCapture(pMix->pDrvStack, pMix->pStream, abBuf, cbToRead, &cbCaptured); + if (RT_FAILURE(rc)) + return rc; + Assert(cbCaptured == cbToRead); + AssertBreak(cbCaptured > 0); + + /* + * Feed it to the mixer. + */ + uint32_t cDstFramesWritten = 0; + if ((abBuf[0] >> 4) & 1) /* some cheap random */ + AudioMixBufWrite(&pMix->MixBuf, &pMix->WriteState, abBuf, cbCaptured, + 0 /*offDstFrame*/, cDstFrames, &cDstFramesWritten); + else + { + AudioMixBufSilence(&pMix->MixBuf, &pMix->WriteState, 0 /*offFrame*/, cDstFrames); + AudioMixBufBlend(&pMix->MixBuf, &pMix->WriteState, abBuf, cbCaptured, + 0 /*offDstFrame*/, cDstFrames, &cDstFramesWritten); + } + AudioMixBufCommit(&pMix->MixBuf, cDstFramesWritten); + } + } + else + { + /* + * The goal here is to empty the mixer buffer by transfering all + * the data to the drivers. + */ + uint32_t const cbBuf = PDMAudioPropsFloorBytesToFrame(&pMix->MixBuf.Props, sizeof(abBuf)); + for (;;) + { + uint32_t cFrames = AudioMixBufUsed(&pMix->MixBuf); + if (!cFrames) + break; + + uint32_t cbWritable = audioTestDriverStackStreamGetWritable(pMix->pDrvStack, pMix->pStream); + if (!cbWritable) + break; + + uint32_t cSrcFramesPeeked; + uint32_t cbDstPeeked; + AudioMixBufPeek(&pMix->MixBuf, 0 /*offSrcFrame*/, cFrames, &cSrcFramesPeeked, + &pMix->PeekState, abBuf, RT_MIN(cbBuf, cbWritable), &cbDstPeeked); + AudioMixBufAdvance(&pMix->MixBuf, cSrcFramesPeeked); + + if (!cbDstPeeked) + break; + + uint32_t offBuf = 0; + while (offBuf < cbDstPeeked) + { + uint32_t cbPlayed = 0; + int rc = audioTestDriverStackStreamPlay(pMix->pDrvStack, pMix->pStream, + &abBuf[offBuf], cbDstPeeked - offBuf, &cbPlayed); + if (RT_FAILURE(rc)) + return rc; + if (!cbPlayed) + RTThreadSleep(1); + offBuf += cbPlayed; + } + } + } + return VINF_SUCCESS; +} + + +/** + * Same as audioTestDriverStackStreamEnable. + */ +int AudioTestMixStreamEnable(PAUDIOTESTDRVMIXSTREAM pMix) +{ + return audioTestDriverStackStreamEnable(pMix->pDrvStack, pMix->pStream); +} + + +/** + * Same as audioTestDriverStackStreamDrain. + */ +int AudioTestMixStreamDrain(PAUDIOTESTDRVMIXSTREAM pMix, bool fSync) +{ + /* + * If we're mixing, we must first make sure the buffer is empty. + */ + if (pMix->fDoMixing) + { + audioTestMixStreamTransfer(pMix); + while (AudioMixBufUsed(&pMix->MixBuf) > 0) + { + RTThreadSleep(1); + audioTestMixStreamTransfer(pMix); + } + } + + /* + * Then we do the regular work. + */ + return audioTestDriverStackStreamDrain(pMix->pDrvStack, pMix->pStream, fSync); +} + +/** + * Same as audioTestDriverStackStreamDisable. + */ +int AudioTestMixStreamDisable(PAUDIOTESTDRVMIXSTREAM pMix) +{ + return AudioTestDriverStackStreamDisable(pMix->pDrvStack, pMix->pStream); +} + + +/** + * Same as audioTestDriverStackStreamIsOkay. + */ +bool AudioTestMixStreamIsOkay(PAUDIOTESTDRVMIXSTREAM pMix) +{ + return audioTestDriverStackStreamIsOkay(pMix->pDrvStack, pMix->pStream); +} + + +/** + * Same as audioTestDriverStackStreamGetWritable + */ +uint32_t AudioTestMixStreamGetWritable(PAUDIOTESTDRVMIXSTREAM pMix) +{ + if (!pMix->fDoMixing) + return audioTestDriverStackStreamGetWritable(pMix->pDrvStack, pMix->pStream); + uint32_t cbRet = AudioMixBufFreeBytes(&pMix->MixBuf); + if (!cbRet) + { + audioTestMixStreamTransfer(pMix); + cbRet = AudioMixBufFreeBytes(&pMix->MixBuf); + } + return cbRet; +} + + + + +/** + * Same as audioTestDriverStackStreamPlay. + */ +int AudioTestMixStreamPlay(PAUDIOTESTDRVMIXSTREAM pMix, void const *pvBuf, uint32_t cbBuf, uint32_t *pcbPlayed) +{ + if (!pMix->fDoMixing) + return audioTestDriverStackStreamPlay(pMix->pDrvStack, pMix->pStream, pvBuf, cbBuf, pcbPlayed); + + *pcbPlayed = 0; + + int rc = audioTestMixStreamTransfer(pMix); + if (RT_FAILURE(rc)) + return rc; + + uint32_t const cbFrame = PDMAudioPropsFrameSize(&pMix->MixBuf.Props); + while (cbBuf >= cbFrame) + { + uint32_t const cFrames = AudioMixBufFree(&pMix->MixBuf); + if (!cFrames) + break; + uint32_t cbToWrite = PDMAudioPropsFramesToBytes(&pMix->MixBuf.Props, cFrames); + cbToWrite = RT_MIN(cbToWrite, cbBuf); + cbToWrite = PDMAudioPropsFloorBytesToFrame(&pMix->MixBuf.Props, cbToWrite); + + uint32_t cFramesWritten = 0; + AudioMixBufWrite(&pMix->MixBuf, &pMix->WriteState, pvBuf, cbToWrite, 0 /*offDstFrame*/, cFrames, &cFramesWritten); + Assert(cFramesWritten == PDMAudioPropsBytesToFrames(&pMix->MixBuf.Props, cbToWrite)); + AudioMixBufCommit(&pMix->MixBuf, cFramesWritten); + + *pcbPlayed += cbToWrite; + cbBuf -= cbToWrite; + pvBuf = (uint8_t const *)pvBuf + cbToWrite; + + rc = audioTestMixStreamTransfer(pMix); + if (RT_FAILURE(rc)) + return *pcbPlayed ? VINF_SUCCESS : rc; + } + + return VINF_SUCCESS; +} + + +/** + * Same as audioTestDriverStackStreamGetReadable + */ +uint32_t AudioTestMixStreamGetReadable(PAUDIOTESTDRVMIXSTREAM pMix) +{ + if (!pMix->fDoMixing) + return audioTestDriverStackStreamGetReadable(pMix->pDrvStack, pMix->pStream); + + audioTestMixStreamTransfer(pMix); + uint32_t cbRet = AudioMixBufUsedBytes(&pMix->MixBuf); + return cbRet; +} + + + + +/** + * Same as audioTestDriverStackStreamCapture. + */ +int AudioTestMixStreamCapture(PAUDIOTESTDRVMIXSTREAM pMix, void *pvBuf, uint32_t cbBuf, uint32_t *pcbCaptured) +{ + if (!pMix->fDoMixing) + return audioTestDriverStackStreamCapture(pMix->pDrvStack, pMix->pStream, pvBuf, cbBuf, pcbCaptured); + + *pcbCaptured = 0; + + int rc = audioTestMixStreamTransfer(pMix); + if (RT_FAILURE(rc)) + return rc; + + uint32_t const cbFrame = PDMAudioPropsFrameSize(&pMix->MixBuf.Props); + while (cbBuf >= cbFrame) + { + uint32_t const cFrames = AudioMixBufUsed(&pMix->MixBuf); + if (!cFrames) + break; + uint32_t cbToRead = PDMAudioPropsFramesToBytes(&pMix->MixBuf.Props, cFrames); + cbToRead = RT_MIN(cbToRead, cbBuf); + cbToRead = PDMAudioPropsFloorBytesToFrame(&pMix->MixBuf.Props, cbToRead); + + uint32_t cFramesPeeked = 0; + uint32_t cbPeeked = 0; + AudioMixBufPeek(&pMix->MixBuf, 0 /*offSrcFrame*/, cFrames, &cFramesPeeked, &pMix->PeekState, pvBuf, cbToRead, &cbPeeked); + Assert(cFramesPeeked == PDMAudioPropsBytesToFrames(&pMix->MixBuf.Props, cbPeeked)); + AudioMixBufAdvance(&pMix->MixBuf, cFramesPeeked); + + *pcbCaptured += cbToRead; + cbBuf -= cbToRead; + pvBuf = (uint8_t *)pvBuf + cbToRead; + + rc = audioTestMixStreamTransfer(pMix); + if (RT_FAILURE(rc)) + return *pcbCaptured ? VINF_SUCCESS : rc; + } + + return VINF_SUCCESS; +} + +/** + * Sets the volume of a mixing stream. + * + * @param pMix Mixing stream to set volume for. + * @param uVolumePercent Volume to set (in percent, 0-100). + */ +void AudioTestMixStreamSetVolume(PAUDIOTESTDRVMIXSTREAM pMix, uint8_t uVolumePercent) +{ + AssertReturnVoid(pMix->fDoMixing); + + uint8_t const uVol = (PDMAUDIO_VOLUME_MAX / 100) * uVolumePercent; + + PDMAUDIOVOLUME Vol; + RT_ZERO(Vol); + for (size_t i = 0; i < RT_ELEMENTS(Vol.auChannels); i++) + Vol.auChannels[i] = uVol; + AudioMixBufSetVolume(&pMix->MixBuf, &Vol); +} + diff --git a/src/VBox/ValidationKit/utils/audio/vkatInternal.h b/src/VBox/ValidationKit/utils/audio/vkatInternal.h new file mode 100644 index 00000000..ab1bfeb8 --- /dev/null +++ b/src/VBox/ValidationKit/utils/audio/vkatInternal.h @@ -0,0 +1,547 @@ +/* $Id: vkatInternal.h $ */ +/** @file + * VKAT - Internal header file for common definitions + structs. + */ + +/* + * Copyright (C) 2021-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_audio_vkatInternal_h +#define VBOX_INCLUDED_SRC_audio_vkatInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/getopt.h> + +#include <VBox/vmm/pdmdrv.h> +#include <VBox/vmm/pdmaudioinline.h> +#include <VBox/vmm/pdmaudiohostenuminline.h> + +#include "Audio/AudioMixBuffer.h" +#include "Audio/AudioTest.h" +#include "Audio/AudioTestService.h" +#include "Audio/AudioTestServiceClient.h" + +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Audio driver stack. + * + * This can be just be backend driver alone or DrvAudio with a backend. + * @todo add automatic resampling via mixer so we can test more of the audio + * stack used by the device emulations. + */ +typedef struct AUDIOTESTDRVSTACK +{ + /** The device registration record for the backend. */ + PCPDMDRVREG pDrvReg; + /** The backend driver instance. */ + PPDMDRVINS pDrvBackendIns; + /** The backend's audio interface. */ + PPDMIHOSTAUDIO pIHostAudio; + + /** The DrvAudio instance. */ + PPDMDRVINS pDrvAudioIns; + /** This is NULL if we don't use DrvAudio. */ + PPDMIAUDIOCONNECTOR pIAudioConnector; + + /** The current (last) audio device enumeration to use. */ + PDMAUDIOHOSTENUM DevEnum; +} AUDIOTESTDRVSTACK; +/** Pointer to an audio driver stack. */ +typedef AUDIOTESTDRVSTACK *PAUDIOTESTDRVSTACK; + +/** + * Backend-only stream structure. + */ +typedef struct AUDIOTESTDRVSTACKSTREAM +{ + /** The public stream data. */ + PDMAUDIOSTREAM Core; + /** The backend data (variable size). */ + PDMAUDIOBACKENDSTREAM Backend; +} AUDIOTESTDRVSTACKSTREAM; +/** Pointer to a backend-only stream structure. */ +typedef AUDIOTESTDRVSTACKSTREAM *PAUDIOTESTDRVSTACKSTREAM; + +/** + * Mixer setup for a stream. + */ +typedef struct AUDIOTESTDRVMIXSTREAM +{ + /** Pointer to the driver stack. */ + PAUDIOTESTDRVSTACK pDrvStack; + /** Pointer to the stream. */ + PPDMAUDIOSTREAM pStream; + /** Properties to use. */ + PCPDMAUDIOPCMPROPS pProps; + /** Set if we're mixing or just passing thru to the driver stack. */ + bool fDoMixing; + /** Mixer buffer. */ + AUDIOMIXBUF MixBuf; + /** Write state. */ + AUDIOMIXBUFWRITESTATE WriteState; + /** Peek state. */ + AUDIOMIXBUFPEEKSTATE PeekState; +} AUDIOTESTDRVMIXSTREAM; +/** Pointer to mixer setup for a stream. */ +typedef AUDIOTESTDRVMIXSTREAM *PAUDIOTESTDRVMIXSTREAM; + +/** + * Enumeration specifying the current audio test mode. + */ +typedef enum AUDIOTESTMODE +{ + /** Unknown mode. */ + AUDIOTESTMODE_UNKNOWN = 0, + /** VKAT is running on the guest side. */ + AUDIOTESTMODE_GUEST, + /** VKAT is running on the host side. */ + AUDIOTESTMODE_HOST +} AUDIOTESTMODE; + +struct AUDIOTESTENV; +/** Pointer a audio test environment. */ +typedef AUDIOTESTENV *PAUDIOTESTENV; + +struct AUDIOTESTDESC; +/** Pointer a audio test descriptor. */ +typedef AUDIOTESTDESC *PAUDIOTESTDESC; + +/** + * Callback to set up the test parameters for a specific test. + * + * @returns IPRT status code. + * @retval VINF_SUCCESS if setting the parameters up succeeded. Any other error code + * otherwise indicating the kind of error. + * @param pszTest Test name. + * @param pTstParmsAcq The audio test parameters to set up. + */ +typedef DECLCALLBACKTYPE(int, FNAUDIOTESTSETUP,(PAUDIOTESTENV pTstEnv, PAUDIOTESTDESC pTstDesc, PAUDIOTESTPARMS pTstParmsAcq, void **ppvCtx)); +/** Pointer to an audio test setup callback. */ +typedef FNAUDIOTESTSETUP *PFNAUDIOTESTSETUP; + +typedef DECLCALLBACKTYPE(int, FNAUDIOTESTEXEC,(PAUDIOTESTENV pTstEnv, void *pvCtx, PAUDIOTESTPARMS pTstParms)); +/** Pointer to an audio test exec callback. */ +typedef FNAUDIOTESTEXEC *PFNAUDIOTESTEXEC; + +typedef DECLCALLBACKTYPE(int, FNAUDIOTESTDESTROY,(PAUDIOTESTENV pTstEnv, void *pvCtx)); +/** Pointer to an audio test destroy callback. */ +typedef FNAUDIOTESTDESTROY *PFNAUDIOTESTDESTROY; + +/** + * Structure for keeping an audio test audio stream. + */ +typedef struct AUDIOTESTSTREAM +{ + /** The PDM stream. */ + PPDMAUDIOSTREAM pStream; + /** The backend stream. */ + PPDMAUDIOBACKENDSTREAM pBackend; + /** The stream config. */ + PDMAUDIOSTREAMCFG Cfg; + /** Associated mixing stream. Optional. */ + AUDIOTESTDRVMIXSTREAM Mix; +} AUDIOTESTSTREAM; +/** Pointer to audio test stream. */ +typedef AUDIOTESTSTREAM *PAUDIOTESTSTREAM; + +/** Maximum audio streams a test environment can handle. */ +#define AUDIOTESTENV_MAX_STREAMS 8 + +/** + * Structure for keeping TCP/IP-specific options. + */ +typedef struct AUDIOTESTENVTCPOPTS +{ + /** Connection mode(s) to use. */ + ATSCONNMODE enmConnMode; + /** Bind address (server mode). When empty, "0.0.0.0" (any host) will be used. */ + char szBindAddr[128]; + /** Bind port (server mode). */ + uint16_t uBindPort; + /** Connection address (client mode). */ + char szConnectAddr[128]; + /** Connection port (client mode). */ + uint16_t uConnectPort; +} AUDIOTESTENVTCPOPTS; +/** Pointer to audio test TCP options. */ +typedef AUDIOTESTENVTCPOPTS *PAUDIOTESTENVTCPOPTS; + +/** + * Structure holding additional I/O options. + */ +typedef struct AUDIOTESTIOOPTS +{ + /** Whether to use the audio connector or not. */ + bool fWithDrvAudio; + /** Whether to use a mixing buffer or not. */ + bool fWithMixer; + /** Buffer size (in ms). */ + uint32_t cMsBufferSize; + /** Pre-buffering size (in ms). */ + uint32_t cMsPreBuffer; + /** Scheduling (in ms). */ + uint32_t cMsSchedulingHint; + /** Audio vlume to use (in percent). */ + uint8_t uVolumePercent; + /** PCM audio properties to use. */ + PDMAUDIOPCMPROPS Props; +} AUDIOTESTIOOPTS; +/** Pointer to additional playback options. */ +typedef AUDIOTESTIOOPTS *PAUDIOTESTIOOPTS; + +/** + * Structure for keeping a user context for the test service callbacks. + */ +typedef struct ATSCALLBACKCTX +{ + /** The test environment bound to this context. */ + PAUDIOTESTENV pTstEnv; + /** Absolute path to the packed up test set archive. + * Keep it simple for now and only support one (open) archive at a time. */ + char szTestSetArchive[RTPATH_MAX]; + /** File handle to the (opened) test set archive for reading. */ + RTFILE hTestSetArchive; + /** Number of currently connected clients. */ + uint8_t cClients; +} ATSCALLBACKCTX; +typedef ATSCALLBACKCTX *PATSCALLBACKCTX; + +/** + * Audio test environment parameters. + * + * This is global to all tests defined. + */ +typedef struct AUDIOTESTENV +{ + /** Audio testing mode. */ + AUDIOTESTMODE enmMode; + /** Whether self test mode is active or not. */ + bool fSelftest; + /** Whether skip the actual verification or not. */ + bool fSkipVerify; + /** Name of the audio device to use. + * If empty the default audio device will be used. */ + char szDev[128]; + /** Zero-based index of current test (will be increased for every run test). */ + uint32_t idxTest; + /** Number of iterations for *all* tests specified. + * When set to 0 (default), a random value (see specific test) will be chosen. */ + uint32_t cIterations; + /** I/O options to use. */ + AUDIOTESTIOOPTS IoOpts; + /** Test tone parameters to use. */ + AUDIOTESTTONEPARMS ToneParms; + /** Output path for storing the test environment's final test files. */ + char szTag[AUDIOTEST_TAG_MAX]; + /** Output path for storing the test environment's final test files. */ + char szPathOut[RTPATH_MAX]; + /** Temporary path for this test environment. */ + char szPathTemp[RTPATH_MAX]; + /** Pointer to audio test driver stack to use. */ + PAUDIOTESTDRVSTACK pDrvStack; + /** Audio stream. */ + AUDIOTESTSTREAM aStreams[AUDIOTESTENV_MAX_STREAMS]; + /** The audio test set to use. */ + AUDIOTESTSET Set; + /** TCP options to use for ATS. */ + AUDIOTESTENVTCPOPTS TcpOpts; + /** ATS server instance to use. + * NULL if not in use. */ + PATSSERVER pSrv; + /** ATS callback context to use. */ + ATSCALLBACKCTX CallbackCtx; + union + { + struct + { + /** Client connected to the ATS on the guest side. */ + ATSCLIENT AtsClGuest; + /** Path to the guest's test set downloaded to the host. */ + char szPathTestSetGuest[RTPATH_MAX]; + /** Client connected to the Validation Kit audio driver ATS. */ + ATSCLIENT AtsClValKit; + /** Path to the Validation Kit audio driver's test set downloaded to the host. */ + char szPathTestSetValKit[RTPATH_MAX]; + } Host; + } u; +} AUDIOTESTENV; + +/** + * Audio test descriptor. + */ +typedef struct AUDIOTESTDESC +{ + /** (Sort of) Descriptive test name. */ + const char *pszName; + /** Flag whether the test is excluded. */ + bool fExcluded; + /** The setup callback. */ + PFNAUDIOTESTSETUP pfnSetup; + /** The exec callback. */ + PFNAUDIOTESTEXEC pfnExec; + /** The destruction callback. */ + PFNAUDIOTESTDESTROY pfnDestroy; +} AUDIOTESTDESC; + +/** + * Backend description. + */ +typedef struct AUDIOTESTBACKENDDESC +{ + /** The driver registration structure. */ + PCPDMDRVREG pDrvReg; + /** The backend name. + * Aliases are implemented by having multiple entries for the same backend. */ + const char *pszName; +} AUDIOTESTBACKENDDESC; + +/** + * VKAT command table entry. + */ +typedef struct VKATCMD +{ + /** The command name. */ + const char *pszCommand; + /** The command handler. */ + DECLCALLBACKMEMBER(RTEXITCODE, pfnHandler,(PRTGETOPTSTATE pGetState)); + + /** Command description. */ + const char *pszDesc; + /** Options array. */ + PCRTGETOPTDEF paOptions; + /** Number of options in the option array. */ + size_t cOptions; + /** Gets help for an option. */ + DECLCALLBACKMEMBER(const char *, pfnOptionHelp,(PCRTGETOPTDEF pOpt)); + /** Flag indicating if the command needs the ATS transport layer. + * Needed for command line parsing. */ + bool fNeedsTransport; +} VKATCMD; +/** Pointer to a const VKAT command entry. */ +typedef VKATCMD const *PCVKATCMD; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Terminate ASAP if set. Set on Ctrl-C. */ +extern bool volatile g_fTerminate; +/** The release logger. */ +extern PRTLOGGER g_pRelLogger; + +/** The test handle. */ +extern RTTEST g_hTest; +/** The current verbosity level. */ +extern unsigned g_uVerbosity; +/** DrvAudio: Enable debug (or not). */ +extern bool g_fDrvAudioDebug; +/** DrvAudio: The debug output path. */ +extern const char *g_pszDrvAudioDebug; + +extern const VKATCMD g_CmdTest; +extern const VKATCMD g_CmdVerify; +extern const VKATCMD g_CmdBackends; +extern const VKATCMD g_CmdEnum; +extern const VKATCMD g_CmdPlay; +extern const VKATCMD g_CmdRec; +extern const VKATCMD g_CmdSelfTest; + + +extern AUDIOTESTDESC g_aTests[]; +extern unsigned g_cTests; + +extern AUDIOTESTBACKENDDESC const g_aBackends[]; +extern unsigned g_cBackends; + + +/********************************************************************************************************************************* +* Prototypes * +*********************************************************************************************************************************/ + +/** @name Command line handlers + * @{ */ +RTEXITCODE audioTestUsage(PRTSTREAM pStrm, PCVKATCMD pOnlyCmd); +RTEXITCODE audioTestVersion(void); +void audioTestShowLogo(PRTSTREAM pStream); +/** @} */ + +/** @name Driver stack + * @{ */ +int AudioTestDriverStackPerformSelftest(void); + +void audioTestDriverStackDelete(PAUDIOTESTDRVSTACK pDrvStack); +int audioTestDriverStackInitEx(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, bool fEnabledIn, bool fEnabledOut, bool fWithDrvAudio); +int audioTestDriverStackInit(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, bool fWithDrvAudio); +int audioTestDriverStackProbe(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, bool fEnabledIn, bool fEnabledOut, bool fWithDrvAudio); +int audioTestDriverStackSetDevice(PAUDIOTESTDRVSTACK pDrvStack, PDMAUDIODIR enmDir, const char *pszDevId); +/** @} */ + +/** @name Driver + * @{ */ +int audioTestDrvConstruct(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, PPDMDRVINS pParentDrvIns, PPPDMDRVINS ppDrvIns); +/** @} */ + +/** @name Driver stack stream + * @{ */ +int audioTestDriverStackStreamCreateInput(PAUDIOTESTDRVSTACK pDrvStack, PCPDMAUDIOPCMPROPS pProps, + uint32_t cMsBufferSize, uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint, + PPDMAUDIOSTREAM *ppStream, PPDMAUDIOSTREAMCFG pCfgAcq); +int audioTestDriverStackStreamCreateOutput(PAUDIOTESTDRVSTACK pDrvStack, PCPDMAUDIOPCMPROPS pProps, + uint32_t cMsBufferSize, uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint, + PPDMAUDIOSTREAM *ppStream, PPDMAUDIOSTREAMCFG pCfgAcq); +void audioTestDriverStackStreamDestroy(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream); +int audioTestDriverStackStreamDrain(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream, bool fSync); +int audioTestDriverStackStreamEnable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream); +int AudioTestDriverStackStreamDisable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream); +bool audioTestDriverStackStreamIsOkay(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream); +uint32_t audioTestDriverStackStreamGetWritable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream); +int audioTestDriverStackStreamPlay(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream, void const *pvBuf, + uint32_t cbBuf, uint32_t *pcbPlayed); +uint32_t audioTestDriverStackStreamGetReadable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream); +int audioTestDriverStackStreamCapture(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream, + void *pvBuf, uint32_t cbBuf, uint32_t *pcbCaptured); +/** @} */ + +/** @name Backend handling + * @{ */ +PCPDMDRVREG AudioTestGetDefaultBackend(void); +PCPDMDRVREG AudioTestFindBackendOpt(const char *pszBackend); +/** @} */ + +/** @name Mixing stream + * @{ */ +int AudioTestMixStreamInit(PAUDIOTESTDRVMIXSTREAM pMix, PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream, + PCPDMAUDIOPCMPROPS pProps, uint32_t cMsBuffer); +void AudioTestMixStreamTerm(PAUDIOTESTDRVMIXSTREAM pMix); +int AudioTestMixStreamEnable(PAUDIOTESTDRVMIXSTREAM pMix); +int AudioTestMixStreamDrain(PAUDIOTESTDRVMIXSTREAM pMix, bool fSync); +int AudioTestMixStreamDisable(PAUDIOTESTDRVMIXSTREAM pMix); +bool AudioTestMixStreamIsOkay(PAUDIOTESTDRVMIXSTREAM pMix); +uint32_t AudioTestMixStreamGetWritable(PAUDIOTESTDRVMIXSTREAM pMix); +int AudioTestMixStreamPlay(PAUDIOTESTDRVMIXSTREAM pMix, void const *pvBuf, uint32_t cbBuf, uint32_t *pcbPlayed); +uint32_t AudioTestMixStreamGetReadable(PAUDIOTESTDRVMIXSTREAM pMix); +int AudioTestMixStreamCapture(PAUDIOTESTDRVMIXSTREAM pMix, void *pvBuf, uint32_t cbBuf, uint32_t *pcbCaptured); +void AudioTestMixStreamSetVolume(PAUDIOTESTDRVMIXSTREAM pMix, uint8_t uVolumePercent); +/** @} */ + +/** @name Device handling + * @{ */ +int audioTestDeviceOpen(PPDMAUDIOHOSTDEV pDev); +int audioTestDeviceClose(PPDMAUDIOHOSTDEV pDev); + +int audioTestDevicesEnumerateAndCheck(PAUDIOTESTDRVSTACK pDrvStack, const char *pszDev, PPDMAUDIOHOSTDEV *ppDev); +/** @} */ + +/** @name ATS routines + * @{ */ +int audioTestEnvConnectToValKitAts(PAUDIOTESTENV pTstEnv, + const char *pszHostTcpAddr, uint32_t uHostTcpPort); +/** @} */ + +/** @name Test environment handling + * @{ */ +void audioTestEnvInit(PAUDIOTESTENV pTstEnv); +int audioTestEnvCreate(PAUDIOTESTENV pTstEnv, PAUDIOTESTDRVSTACK pDrvStack); +void audioTestEnvDestroy(PAUDIOTESTENV pTstEnv); +int audioTestEnvPrologue(PAUDIOTESTENV pTstEnv, bool fPack, char *pszPackFile, size_t cbPackFile); + +void audioTestParmsInit(PAUDIOTESTPARMS pTstParms); +void audioTestParmsDestroy(PAUDIOTESTPARMS pTstParms); +/** @} */ + +int audioTestWorker(PAUDIOTESTENV pTstEnv); + +/** @todo Test tone handling */ +int audioTestPlayTone(PAUDIOTESTIOOPTS pIoOpts, PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms); +void audioTestToneParmsInit(PAUDIOTESTTONEPARMS pToneParms); +/** @} */ + +void audioTestIoOptsInitDefaults(PAUDIOTESTIOOPTS pIoOpts); + + +/********************************************************************************************************************************* +* Common command line stuff * +*********************************************************************************************************************************/ + +/** + * Common long options values. + */ +enum +{ + AUDIO_TEST_OPT_CMN_DAEMONIZE = 256, + AUDIO_TEST_OPT_CMN_DAEMONIZED, + AUDIO_TEST_OPT_CMN_DEBUG_AUDIO_ENABLE, + AUDIO_TEST_OPT_CMN_DEBUG_AUDIO_PATH +}; + +/** For use in the option switch to handle common options. */ +#define AUDIO_TEST_COMMON_OPTION_CASES(a_ValueUnion, a_pCmd) \ + case 'q': \ + g_uVerbosity = 0; \ + if (g_pRelLogger) \ + RTLogGroupSettings(g_pRelLogger, "all=0 all.e"); \ + break; \ + \ + case 'v': \ + /* No-op here, has been handled by main() already. */ /** @todo r-bird: -q works, so -v must too! */ \ + break; \ + \ + case 'V': \ + return audioTestVersion(); \ + \ + case 'h': \ + return audioTestUsage(g_pStdOut, a_pCmd); \ + \ + case AUDIO_TEST_OPT_CMN_DEBUG_AUDIO_ENABLE: \ + g_fDrvAudioDebug = true; \ + break; \ + \ + case AUDIO_TEST_OPT_CMN_DEBUG_AUDIO_PATH: \ + g_pszDrvAudioDebug = (a_ValueUnion).psz; \ + break; \ + case AUDIO_TEST_OPT_CMN_DAEMONIZE: \ + break; \ + case AUDIO_TEST_OPT_CMN_DAEMONIZED: \ + break; + +#endif /* !VBOX_INCLUDED_SRC_audio_vkatInternal_h */ + diff --git a/src/VBox/ValidationKit/utils/clipboard/ClipUtil.cpp b/src/VBox/ValidationKit/utils/clipboard/ClipUtil.cpp new file mode 100644 index 00000000..b05bbea9 --- /dev/null +++ b/src/VBox/ValidationKit/utils/clipboard/ClipUtil.cpp @@ -0,0 +1,1793 @@ +/* $Id: ClipUtil.cpp $ */ +/** @file + * ClipUtil - Clipboard Utility + */ + +/* + * Copyright (C) 2021-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef RT_OS_OS2 +# define INCL_BASE +# define INCL_PM +# define INCL_ERRORS +# include <os2.h> +# undef RT_MAX +#endif + +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/process.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/utf16.h> +#include <iprt/zero.h> + +#ifdef RT_OS_DARWIN +/** @todo */ +#elif defined(RT_OS_WINDOWS) +# include <iprt/nt/nt-and-windows.h> +#elif !defined(RT_OS_OS2) +# include <X11/Xlib.h> +# include <X11/Xatom.h> +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) || defined(RT_OS_DARWIN) +# undef MULTI_TARGET_CLIPBOARD +# undef CU_X11 +#else +# define MULTI_TARGET_CLIPBOARD +# define CU_X11 +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Clipboard format descriptor. + */ +typedef struct CLIPUTILFORMAT +{ + /** Format name. */ + const char *pszName; + +#if defined(RT_OS_WINDOWS) + /** Windows integer format (CF_XXXX). */ + UINT fFormat; + /** Windows string format name. */ + const WCHAR *pwszFormat; + +#elif defined(RT_OS_OS2) + /** OS/2 integer format. */ + ULONG fFormat; + /** OS/2 string format name. */ + const char *pszFormat; + +#elif defined(RT_OS_DARWIN) + /** Native format (flavor). */ + CFStringRef *hStrFormat; +#else + /** The X11 atom for the format. */ + Atom uAtom; + /** The X11 atom name if uAtom must be termined dynamically. */ + const char *pszAtomName; + /** @todo X11 */ +#endif + + /** Description. */ + const char *pszDesc; + /** CLIPUTILFORMAT_F_XXX. */ + uint32_t fFlags; +} CLIPUTILFORMAT; +/** Pointer to a clipobard format descriptor. */ +typedef CLIPUTILFORMAT const *PCCLIPUTILFORMAT; + +/** Convert to/from UTF-8. */ +#define CLIPUTILFORMAT_F_CONVERT_UTF8 RT_BIT_32(0) +/** Ad hoc entry. */ +#define CLIPUTILFORMAT_F_AD_HOC RT_BIT_32(1) + + +#ifdef MULTI_TARGET_CLIPBOARD +/** + * Clipboard target descriptor. + */ +typedef struct CLIPUTILTARGET +{ + /** Target name. */ + const char *pszName; + /** The X11 atom for the target. */ + Atom uAtom; + /** The X11 atom name if uAtom must be termined dynamically. */ + const char *pszAtomName; + /** Description. */ + const char *pszDesc; +} CLIPUTILTARGET; +/** Pointer to clipboard target descriptor. */ +typedef CLIPUTILTARGET const *PCCLIPUTILTARGET; +#endif /* MULTI_TARGET_CLIPBOARD */ + + +#ifdef RT_OS_OS2 +/** Header for Odin32 specific clipboard entries. + * (Used to get the correct size of the data.) + */ +typedef struct _Odin32ClipboardHeader +{ + /** Magic (CLIPHEADER_MAGIC) */ + char achMagic[8]; + /** Size of the following data. + * (The interpretation depends on the type.) */ + unsigned cbData; + /** Odin32 format number. */ + unsigned uFormat; +} CLIPHEADER, *PCLIPHEADER; + +#define CLIPHEADER_MAGIC "Odin\1\0\1" +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Command line parameters */ +static const RTGETOPTDEF g_aCmdOptions[] = +{ + { "--list", 'l', RTGETOPT_REQ_NOTHING }, + { "--get", 'g', RTGETOPT_REQ_STRING }, + { "--get-file", 'G', RTGETOPT_REQ_STRING }, + { "--put", 'p', RTGETOPT_REQ_STRING }, + { "--put-file", 'P', RTGETOPT_REQ_STRING }, + { "--check", 'c', RTGETOPT_REQ_STRING }, + { "--check-file", 'C', RTGETOPT_REQ_STRING }, + { "--check-not", 'n', RTGETOPT_REQ_STRING }, + { "--zap", 'z', RTGETOPT_REQ_NOTHING }, +#ifdef MULTI_TARGET_CLIPBOARD + { "--target", 't', RTGETOPT_REQ_STRING }, +#endif +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) + { "--close", 'k', RTGETOPT_REQ_NOTHING }, +#endif + { "--wait", 'w', RTGETOPT_REQ_UINT32 }, + { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + { "--version", 'V', RTGETOPT_REQ_NOTHING }, + { "--help", 'h', RTGETOPT_REQ_NOTHING }, /* for Usage() */ +}; + +/** Format descriptors. */ +static CLIPUTILFORMAT g_aFormats[] = +{ +#if defined(RT_OS_WINDOWS) + { "text/ansi", CF_TEXT, NULL, "ANSI text", 0 }, + { "text/utf-16", CF_UNICODETEXT, NULL, "UTF-16 text", 0 }, + { "text/utf-8", CF_UNICODETEXT, NULL, "UTF-8 text", CLIPUTILFORMAT_F_CONVERT_UTF8 }, + /* https://docs.microsoft.com/en-us/windows/desktop/dataxchg/html-clipboard-format */ + { "text/html", 0, L"HTML Format", "HTML text", 0 }, + { "bitmap", CF_DIB, NULL, "Bitmap (DIB)", 0 }, + { "bitmap/v5", CF_DIBV5, NULL, "Bitmap version 5 (DIBv5)", 0 }, +#elif defined(RT_OS_OS2) + { "text/ascii", CF_TEXT, NULL, "ASCII text", 0 }, + { "text/utf-8", CF_TEXT, NULL, "UTF-8 text", CLIPUTILFORMAT_F_CONVERT_UTF8 }, + { "text/utf-16", 0, "Odin32 UnicodeText", "UTF-16 text", 0}, +#elif defined(RT_OS_DARWIN) + { "text/utf-8", kUTTypeUTF8PlainText, "UTF-8 text", 0 }, + { "text/utf-16", kUTTypeUTF16PlainText, "UTF-16 text", 0 }, +#else + /** @todo X11 */ + { "text/utf-8", None, "UTF8_STRING", "UTF-8 text", 0 }, +#endif +}; + +#ifdef MULTI_TARGET_CLIPBOARD +/** Target descriptors. */ +static CLIPUTILTARGET g_aTargets[] = +{ + { "clipboard", 0, "CLIPBOARD", "XA_CLIPBOARD: The clipboard (default)" }, + { "primary", XA_PRIMARY, NULL, "XA_PRIMARY: Primary selected text (middle mouse button)" }, + { "secondary", XA_SECONDARY, NULL, "XA_SECONDARY: Secondary selected text (with ctrl)" }, +}; + +/** The current clipboard target. */ +static CLIPUTILTARGET *g_pTarget = &g_aTargets[0]; +#endif /* MULTI_TARGET_CLIPBOARD */ + +/** The -v/-q state. */ +static unsigned g_uVerbosity = 1; + +#ifdef RT_OS_DARWIN + +#elif defined(RT_OS_OS2) +/** Anchorblock handle. */ +static HAB g_hOs2Ab = NULLHANDLE; +/** The message queue handle. */ +static HMQ g_hOs2MsgQueue = NULLHANDLE; +/** Windows that becomes clipboard owner when setting data. */ +static HWND g_hOs2Wnd = NULLHANDLE; +/** Set if we've opened the clipboard. */ +static bool g_fOs2OpenedClipboard = false; +/** Set if we're the clipboard owner. */ +static bool g_fOs2ClipboardOwner = false; +/** Set when we receive a WM_TIMER message during DoWait(). */ +static bool volatile g_fOs2TimerTicked = false; + +#elif defined(RT_OS_WINDOWS) +/** Set if we've opened the clipboard. */ +static bool g_fWinOpenedClipboard = false; +/** Set when we receive a WM_TIMER message during DoWait(). */ +static bool volatile g_fWinTimerTicked = false; +/** Window that becomes clipboard owner when setting data. */ +static HWND g_hWinWnd = NULL; + +#else +/** Number of errors (incremented by error handle callback). */ +static uint32_t volatile g_cX11Errors; +/** The X11 display. */ +static Display *g_pX11Display = NULL; +/** The X11 dummy window. */ +static Window g_hX11Window = 0; +/** TARGETS */ +static Atom g_uX11AtomTargets; +/** MULTIPLE */ +static Atom g_uX11AtomMultiple; + +#endif + + +/** + * Gets a format descriptor, complaining if invalid format. + * + * @returns Pointer to the descriptor if found, NULL + msg if not. + * @param pszFormat The format to get. + */ +static PCCLIPUTILFORMAT GetFormatDesc(const char *pszFormat) +{ + for (size_t i = 0; i < RT_ELEMENTS(g_aFormats); i++) + if (strcmp(pszFormat, g_aFormats[i].pszName) == 0) + { +#if defined(RT_OS_DARWIN) + /** @todo */ + +#elif defined(RT_OS_OS2) + if (g_aFormats[i].pszFormat && g_aFormats[i].fFormat == 0) + { + g_aFormats[i].fFormat = WinAddAtom(WinQuerySystemAtomTable(), g_aFormats[i].pszFormat); + if (g_aFormats[i].fFormat == 0) + RTMsgError("WinAddAtom(,%s) failed: %#x", g_aFormats[i].pszFormat, WinGetLastError(g_hOs2Ab)); + } + +#elif defined(RT_OS_WINDOWS) + if (g_aFormats[i].pwszFormat && g_aFormats[i].fFormat == 0) + { + g_aFormats[i].fFormat = RegisterClipboardFormatW(g_aFormats[i].pwszFormat); + if (g_aFormats[i].fFormat == 0) + RTMsgError("RegisterClipboardFormatW(%ls) failed: %u (%#x)", + g_aFormats[i].pwszFormat, GetLastError(), GetLastError()); + } +#elif defined(CU_X11) + if (g_aFormats[i].pszAtomName && g_aFormats[i].uAtom == 0) + g_aFormats[i].uAtom = XInternAtom(g_pX11Display, g_aFormats[i].pszAtomName, False); +#endif + return &g_aFormats[i]; + } + + /* + * Try register the format. + */ + static CLIPUTILFORMAT AdHoc; + AdHoc.pszName = pszFormat; + AdHoc.pszDesc = pszFormat; + AdHoc.fFlags = CLIPUTILFORMAT_F_AD_HOC; +#ifdef RT_OS_DARWIN +/** @todo */ + +#elif defined(RT_OS_OS2) + AdHoc.pszFormat = pszFormat; + AdHoc.fFormat = WinAddAtom(WinQuerySystemAtomTable(), pszFormat); + if (AdHoc.fFormat == 0) + { + RTMsgError("Invalid format '%s' (%#x)", pszFormat, WinGetLastError(g_hOs2Ab)); + return NULL; + } + +#elif defined(RT_OS_WINDOWS) + AdHoc.pwszFormat = NULL; + AdHoc.fFormat = RegisterClipboardFormatA(pszFormat); + if (AdHoc.fFormat == 0) + { + RTMsgError("RegisterClipboardFormatA(%s) failed: %u (%#x)", pszFormat, GetLastError(), GetLastError()); + return NULL; + } + +#else + AdHoc.pszAtomName = pszFormat; + AdHoc.uAtom = XInternAtom(g_pX11Display, pszFormat, False); + if (AdHoc.uAtom == None) + { + RTMsgError("Invalid format '%s' or out of memory for X11 atoms", pszFormat); + return NULL; + } + +#endif + return &AdHoc; +} + + +#ifdef RT_OS_DARWIN + +/** @todo */ + + +#elif defined(RT_OS_OS2) + +/** + * The window procedure for the object window. + * + * @returns Message result. + * + * @param hwnd The window handle. + * @param msg The message. + * @param mp1 Message parameter 1. + * @param mp2 Message parameter 2. + */ +static MRESULT EXPENTRY CuOs2WinProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2) +{ + if (g_uVerbosity > 2) + RTMsgInfo("CuOs2WinProc: hwnd=%#lx msg=%#lx mp1=%#lx mp2=%#lx\n", hwnd, msg, mp1, mp2); + + switch (msg) + { + case WM_CREATE: + return NULL; /* FALSE(/NULL) == Continue*/ + case WM_DESTROY: + break; + + /* + * Clipboard viewer message - the content has been changed. + * This is sent *after* releasing the clipboard sem + * and during the WinSetClipbrdViewer call. + */ + case WM_DRAWCLIPBOARD: + break; + + /* + * Clipboard owner message - the content was replaced. + * This is sent by someone with an open clipboard, so don't try open it now. + */ + case WM_DESTROYCLIPBOARD: + break; + + /* + * Clipboard owner message - somebody is requesting us to render a format. + * This is called by someone which owns the clipboard, but that's fine. + */ + case WM_RENDERFMT: + break; + + /* + * Clipboard owner message - we're about to quit and should render all formats. + */ + case WM_RENDERALLFMTS: + break; + + /* + * Clipboard owner messages dealing with owner drawn content. + * We shouldn't be seeing any of these. + */ + case WM_PAINTCLIPBOARD: + case WM_SIZECLIPBOARD: + case WM_HSCROLLCLIPBOARD: + case WM_VSCROLLCLIPBOARD: + AssertMsgFailed(("msg=%lx (%ld)\n", msg, msg)); + break; + + /* + * We shouldn't be seeing any other messages according to the docs. + * But for whatever reason, PM sends us a WM_ADJUSTWINDOWPOS message + * during WinCreateWindow. So, ignore that and assert on anything else. + */ + default: + AssertMsgFailed(("msg=%lx (%ld)\n", msg, msg)); + case WM_ADJUSTWINDOWPOS: + break; + + /* + * We use this window fielding WM_TIMER during DoWait. + */ + case WM_TIMER: + if (SHORT1FROMMP(mp1) == 1) + g_fOs2TimerTicked = true; + break; + } + return NULL; +} + + +/** + * Initialize the OS/2 bits. + */ +static RTEXITCODE CuOs2Init(void) +{ + g_hOs2Ab = WinInitialize(0); + if (g_hOs2Ab == NULLHANDLE) + return RTMsgErrorExitFailure("WinInitialize failed!"); + + g_hOs2MsgQueue = WinCreateMsgQueue(g_hOs2Ab, 10); + if (g_hOs2MsgQueue == NULLHANDLE) + return RTMsgErrorExitFailure("WinCreateMsgQueue failed: %#x", WinGetLastError(g_hOs2Ab)); + + static char s_szClass[] = "VBox-ClipUtilClipboardClass"; + if (!WinRegisterClass(g_hOs2Wnd, (PCSZ)s_szClass, CuOs2WinProc, 0, 0)) + return RTMsgErrorExitFailure("WinRegisterClass failed: %#x", WinGetLastError(g_hOs2Ab)); + + g_hOs2Wnd = WinCreateWindow(HWND_OBJECT, /* hwndParent */ + (PCSZ)s_szClass, /* pszClass */ + (PCSZ)"VirtualBox Clipboard Utility", /* pszName */ + 0, /* flStyle */ + 0, 0, 0, 0, /* x, y, cx, cy */ + NULLHANDLE, /* hwndOwner */ + HWND_BOTTOM, /* hwndInsertBehind */ + 42, /* id */ + NULL, /* pCtlData */ + NULL); /* pPresParams */ + if (g_hOs2Wnd == NULLHANDLE) + return RTMsgErrorExitFailure("WinCreateWindow failed: %#x", WinGetLastError(g_hOs2Ab)); + + return RTEXITCODE_SUCCESS; +} + + +/** + * Terminates the OS/2 bits. + */ +static RTEXITCODE CuOs2Term(void) +{ + if (g_fOs2OpenedClipboard) + { + if (!WinCloseClipbrd(g_hOs2Ab)) + return RTMsgErrorExitFailure("WinCloseClipbrd failed: %#x", WinGetLastError(g_hOs2Ab)); + g_fOs2OpenedClipboard = false; + } + + WinDestroyWindow(g_hOs2Wnd); + g_hOs2Wnd = NULLHANDLE; + + WinDestroyMsgQueue(g_hOs2MsgQueue); + g_hOs2MsgQueue = NULLHANDLE; + + WinTerminate(g_hOs2Ab); + g_hOs2Ab = NULLHANDLE; + + return RTEXITCODE_SUCCESS; +} + + +/** + * Opens the OS/2 clipboard. + */ +static RTEXITCODE CuOs2OpenClipboardIfNecessary(void) +{ + if (g_fOs2OpenedClipboard) + return RTEXITCODE_SUCCESS; + if (WinOpenClipbrd(g_hOs2Ab)) + { + if (g_uVerbosity > 0) + RTMsgInfo("Opened the clipboard\n"); + g_fOs2OpenedClipboard = true; + return RTEXITCODE_SUCCESS; + } + return RTMsgErrorExitFailure("WinOpenClipbrd failed: %#x", WinGetLastError(g_hOs2Ab)); +} + + +#elif defined(RT_OS_WINDOWS) + +/** + * Window procedure for the clipboard owner window on Windows. + */ +static LRESULT CALLBACK CuWinWndProc(HWND hWnd, UINT idMsg, WPARAM wParam, LPARAM lParam) RT_NOTHROW_DEF +{ + if (g_uVerbosity > 2) + RTMsgInfo("CuWinWndProc: hWnd=%p idMsg=%#05x wParam=%#zx lParam=%#zx\n", hWnd, idMsg, wParam, lParam); + + switch (idMsg) + { + case WM_TIMER: + if (wParam == 1) + g_fWinTimerTicked = true; + break; + } + return DefWindowProc(hWnd, idMsg, wParam, lParam); +} + + +/** + * Initialize the Windows bits. + */ +static RTEXITCODE CuWinInit(void) +{ + /* Register the window class: */ + static wchar_t s_wszClass[] = L"VBox-ClipUtilClipboardClass"; + WNDCLASSW WndCls = {0}; + WndCls.style = CS_NOCLOSE; + WndCls.lpfnWndProc = CuWinWndProc; + WndCls.cbClsExtra = 0; + WndCls.cbWndExtra = 0; + WndCls.hInstance = (HINSTANCE)GetModuleHandle(NULL); + WndCls.hIcon = NULL; + WndCls.hCursor = NULL; + WndCls.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1); + WndCls.lpszMenuName = NULL; + WndCls.lpszClassName = s_wszClass; + + ATOM uAtomWndClass = RegisterClassW(&WndCls); + if (!uAtomWndClass) + return RTMsgErrorExitFailure("RegisterClassW failed: %u (%#x)", GetLastError(), GetLastError()); + + /* Create the clipboard owner window: */ + g_hWinWnd = CreateWindowExW(WS_EX_TRANSPARENT, /* fExStyle */ + s_wszClass, /* pwszClass */ + L"VirtualBox Clipboard Utility", /* pwszName */ + 0, /* fStyle */ + 0, 0, 0, 0, /* x, y, cx, cy */ + HWND_MESSAGE, /* hWndParent */ + NULL, /* hMenu */ + (HINSTANCE)GetModuleHandle(NULL), /* hinstance */ + NULL); /* pParam */ + if (g_hWinWnd == NULL) + return RTMsgErrorExitFailure("CreateWindowExW failed: %u (%#x)", GetLastError(), GetLastError()); + + return RTEXITCODE_SUCCESS; +} + +/** + * Terminates the Windows bits. + */ +static RTEXITCODE CuWinTerm(void) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + if (g_fWinOpenedClipboard) + { + if (CloseClipboard()) + g_fWinOpenedClipboard = false; + else + rcExit = RTMsgErrorExitFailure("CloseClipboard failed: %u (%#x)", GetLastError(), GetLastError()); + } + + if (g_hWinWnd != NULL) + { + if (!DestroyWindow(g_hWinWnd)) + rcExit = RTMsgErrorExitFailure("DestroyWindow failed: %u (%#x)", GetLastError(), GetLastError()); + g_hWinWnd = NULL; + } + + return rcExit; +} + + +/** + * Opens the window clipboard. + */ +static RTEXITCODE WinOpenClipboardIfNecessary(void) +{ + if (g_fWinOpenedClipboard) + return RTEXITCODE_SUCCESS; + if (OpenClipboard(g_hWinWnd)) + { + if (g_uVerbosity > 0) + RTMsgInfo("Opened the clipboard\n"); + g_fWinOpenedClipboard = true; + return RTEXITCODE_SUCCESS; + } + return RTMsgErrorExitFailure("OpenClipboard failed: %u (%#x)", GetLastError(), GetLastError()); +} + + +#else /* X11: */ + +/** + * Error handler callback. + */ +static int CuX11ErrorCallback(Display *pX11Display, XErrorEvent *pErrEvt) +{ + g_cX11Errors++; + char szErr[2048]; + XGetErrorText(pX11Display, pErrEvt->error_code, szErr, sizeof(szErr)); + RTMsgError("An X Window protocol error occurred: %s\n" + " Request code: %u\n" + " Minor code: %u\n" + " Serial number of the failed request: %u\n", + szErr, pErrEvt->request_code, pErrEvt->minor_code, pErrEvt->serial); + return 0; +} + + +/** + * Initialize the X11 bits. + */ +static RTEXITCODE CuX11Init(void) +{ + /* + * Open the X11 display and create a little dummy window. + */ + XSetErrorHandler(CuX11ErrorCallback); + g_pX11Display = XOpenDisplay(NULL); + if (!g_pX11Display) + return RTMsgErrorExitFailure("XOpenDisplay failed"); + + int const iDefaultScreen = DefaultScreen(g_pX11Display); + g_hX11Window = XCreateSimpleWindow(g_pX11Display, + RootWindow(g_pX11Display, iDefaultScreen), + 0 /*x*/, 0 /*y*/, + 1 /*cx*/, 1 /*cy*/, + 0 /*cPxlBorder*/, + BlackPixel(g_pX11Display, iDefaultScreen) /*Border*/, + WhitePixel(g_pX11Display, iDefaultScreen) /*Background*/); + + /* + * Resolve any unknown atom values we might need later. + */ + for (size_t i = 0; i < RT_ELEMENTS(g_aTargets); i++) + if (g_aTargets[i].pszAtomName) + { + g_aTargets[i].uAtom = XInternAtom(g_pX11Display, g_aTargets[i].pszAtomName, False); + if (g_uVerbosity > 2) + RTPrintf("target %s atom=%#x\n", g_aTargets[i].pszName, g_aTargets[i].uAtom); + } + + g_uX11AtomTargets = XInternAtom(g_pX11Display, "TARGETS", False); + g_uX11AtomMultiple = XInternAtom(g_pX11Display, "MULTIPLE", False); + + return RTEXITCODE_SUCCESS; +} + +#endif /* X11 */ + + +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) +/** + * Closes the clipboard if open. + */ +static RTEXITCODE CuCloseClipboard(void) +{ +# if defined(RT_OS_OS2) + if (g_fOs2OpenedClipboard) + { + if (!WinCloseClipbrd(g_hOs2Ab)) + return RTMsgErrorExitFailure("WinCloseClipbrd failed: %#x", WinGetLastError(g_hOs2Ab)); + g_fOs2OpenedClipboard = false; + if (g_uVerbosity > 0) + RTMsgInfo("Closed the clipboard.\n"); + } +# else + if (g_fWinOpenedClipboard) + { + if (!CloseClipboard()) + return RTMsgErrorExitFailure("CloseClipboard failed: %u (%#x)", GetLastError(), GetLastError()); + g_fWinOpenedClipboard = false; + if (g_uVerbosity > 0) + RTMsgInfo("Closed the clipboard.\n"); + } +# endif + else if (g_uVerbosity > 0) + RTMsgInfo("No need to close clipboard, not opened.\n"); + + return RTEXITCODE_SUCCESS; +} +#endif /* RT_OS_OS2 || RT_OS_WINDOWS */ + + +/** + * Lists the clipboard format. + */ +static RTEXITCODE ListClipboardContent(void) +{ +#if defined(RT_OS_OS2) + RTEXITCODE rcExit = CuOs2OpenClipboardIfNecessary(); + if (rcExit == RTEXITCODE_SUCCESS) + { + HATOMTBL const hAtomTbl = WinQuerySystemAtomTable(); + uint32_t idx = 0; + ULONG fFormat = 0; + while ((fFormat = WinEnumClipbrdFmts(g_hOs2Ab)) != 0) + { + char szName[256] = {0}; + ULONG cchRet = WinQueryAtomName(hAtomTbl, fFormat, szName, sizeof(szName)); + if (cchRet != 0) + RTPrintf("#%02u: %#06x - %s\n", idx, fFormat, szName); + else + { + const char *pszName = NULL; + switch (fFormat) + { + case CF_TEXT: pszName = "CF_TEXT"; break; + case CF_BITMAP: pszName = "CF_BITMAP"; break; + case CF_DSPTEXT: pszName = "CF_DSPTEXT"; break; + case CF_DSPBITMAP: pszName = "CF_DSPBITMAP"; break; + case CF_METAFILE: pszName = "CF_METAFILE"; break; + case CF_DSPMETAFILE: pszName = "CF_DSPMETAFILE"; break; + case CF_PALETTE: pszName = "CF_PALETTE"; break; + default: + break; + } + if (pszName) + RTPrintf("#%02u: %#06x - %s\n", idx, fFormat, pszName); + else + RTPrintf("#%02u: %#06x\n", idx, fFormat); + } + + idx++; + } + } + + return rcExit; + +#elif defined(RT_OS_WINDOWS) + RTEXITCODE rcExit = WinOpenClipboardIfNecessary(); + if (rcExit == RTEXITCODE_SUCCESS) + { + SetLastError(0); + uint32_t idx = 0; + UINT fFormat = 0; + while ((fFormat = EnumClipboardFormats(fFormat)) != 0) + { + WCHAR wszName[256]; + int cchName = GetClipboardFormatNameW(fFormat, wszName, RT_ELEMENTS(wszName)); + if (cchName > 0) + RTPrintf("#%02u: %#06x - %ls\n", idx, fFormat, wszName); + else + { + const char *pszName = NULL; + switch (fFormat) + { + case CF_TEXT: pszName = "CF_TEXT"; break; + case CF_BITMAP: pszName = "CF_BITMAP"; break; + case CF_METAFILEPICT: pszName = "CF_METAFILEPICT"; break; + case CF_SYLK: pszName = "CF_SYLK"; break; + case CF_DIF: pszName = "CF_DIF"; break; + case CF_TIFF: pszName = "CF_TIFF"; break; + case CF_OEMTEXT: pszName = "CF_OEMTEXT"; break; + case CF_DIB: pszName = "CF_DIB"; break; + case CF_PALETTE: pszName = "CF_PALETTE"; break; + case CF_PENDATA: pszName = "CF_PENDATA"; break; + case CF_RIFF: pszName = "CF_RIFF"; break; + case CF_WAVE: pszName = "CF_WAVE"; break; + case CF_UNICODETEXT: pszName = "CF_UNICODETEXT"; break; + case CF_ENHMETAFILE: pszName = "CF_ENHMETAFILE"; break; + case CF_HDROP: pszName = "CF_HDROP"; break; + case CF_LOCALE: pszName = "CF_LOCALE"; break; + case CF_DIBV5: pszName = "CF_DIBV5"; break; + default: + break; + } + if (pszName) + RTPrintf("#%02u: %#06x - %s\n", idx, fFormat, pszName); + else + RTPrintf("#%02u: %#06x\n", idx, fFormat); + } + + idx++; + } + if (idx == 0) + RTPrintf("Empty\n"); + } + return rcExit; + +#elif defined(CU_X11) + /* Request the TARGETS property: */ + Atom uAtomDst = g_uX11AtomTargets; + int rc = XConvertSelection(g_pX11Display, g_pTarget->uAtom, g_uX11AtomTargets, uAtomDst, g_hX11Window, CurrentTime); + if (g_uVerbosity > 1) + RTPrintf("XConvertSelection -> %d\n", rc); + + /* Wait for the reply: */ + for (;;) + { + XEvent Evt = {0}; + rc = XNextEvent(g_pX11Display, &Evt); + if (Evt.type == SelectionNotify) + { + if (g_uVerbosity > 1) + RTPrintf("XNextEvent -> %d; type=SelectionNotify\n", rc); + if (Evt.xselection.selection == g_pTarget->uAtom) + { + if (Evt.xselection.property == None) + return RTMsgErrorExitFailure("XConvertSelection(,%s,TARGETS,) failed", g_pTarget->pszName); + + /* Get the TARGETS property data: */ + Atom uAtomRetType = 0; + int iActualFmt = 0; + unsigned long cbLeftToRead = 0; + unsigned long cItems = 0; + unsigned char *pbData = NULL; + rc = XGetWindowProperty(g_pX11Display, g_hX11Window, uAtomDst, + 0 /*offset*/, sizeof(Atom) * 4096 /* should be enough */, True /*fDelete*/, XA_ATOM, + &uAtomRetType, &iActualFmt, &cItems, &cbLeftToRead, &pbData); + if (g_uVerbosity > 1) + RTPrintf("XConvertSelection -> %d; uAtomRetType=%u iActualFmt=%d cItems=%lu cbLeftToRead=%lu pbData=%p\n", + rc, uAtomRetType, iActualFmt, cItems, cbLeftToRead, pbData); + if (pbData && cItems > 0) + { + /* Display the TARGETS: */ + Atom const *paTargets = (Atom const *)pbData; + for (unsigned long i = 0; i < cItems; i++) + { + const char *pszName = XGetAtomName(g_pX11Display, paTargets[i]); + if (pszName) + RTPrintf("#%02u: %#06x - %s\n", i, paTargets[i], pszName); + else + RTPrintf("#%02u: %#06x\n", i, paTargets[i]); + } + } + else + RTMsgInfo("Empty"); + if (pbData) + XFree(pbData); + return RTEXITCODE_SUCCESS; + } + } + else if (g_uVerbosity > 1) + RTPrintf("XNextEvent -> %d; type=%d\n", rc, Evt.type); + } + +#else + return RTMsgErrorExitFailure("ListClipboardContent is not implemented"); +#endif +} + + +/** + * Reads the given clipboard format and stores it in on the heap. + * + * @returns Success indicator. + * @param pFmtDesc The format to get. + * @param ppvData Where to return the pointer to the data. Free using + * RTMemFree when done. + * @param pcbData Where to return the amount of data returned. + */ +static RTEXITCODE ReadClipboardData(PCCLIPUTILFORMAT pFmtDesc, void **ppvData, size_t *pcbData) +{ + *ppvData = NULL; + *pcbData = 0; + +#if defined(RT_OS_OS2) + RTEXITCODE rcExit = CuOs2OpenClipboardIfNecessary(); + if (rcExit == RTEXITCODE_SUCCESS) + { + ULONG fFmtInfo = 0; + if (WinQueryClipbrdFmtInfo(g_hOs2Ab, pFmtDesc->fFormat, &fFmtInfo)) + { + ULONG uData = WinQueryClipbrdData(g_hOs2Ab, pFmtDesc->fFormat); + if (fFmtInfo & CFI_POINTER) + { + PCLIPHEADER pOdinHdr = (PCLIPHEADER)uData; + if (pFmtDesc->fFormat == CF_TEXT) + { + if (pFmtDesc->fFlags & CLIPUTILFORMAT_F_CONVERT_UTF8) + { + char *pszUtf8 = NULL; + int rc = RTStrCurrentCPToUtf8(&pszUtf8, (const char *)uData); + if (RT_SUCCESS(rc)) + { + *pcbData = strlen(pszUtf8) + 1; + *ppvData = RTMemDup(pszUtf8, *pcbData); + RTStrFree(pszUtf8); + } + else + return RTMsgErrorExitFailure("RTStrCurrentCPToUtf8 failed: %Rrc", rc); + } + else + { + *pcbData = strlen((const char *)uData) + 1; + *ppvData = RTMemDup((const char *)uData, *pcbData); + } + } + else if ( strcmp(pFmtDesc->pszFormat, "Odin32 UnicodeText") == 0 + && memcmp(pOdinHdr->achMagic, CLIPHEADER_MAGIC, sizeof(pOdinHdr->achMagic)) == 0) + { + *pcbData = pOdinHdr->cbData; + *ppvData = RTMemDup(pOdinHdr + 1, pOdinHdr->cbData); + } + else + { + /* We could use DosQueryMem here to figure out the size of the allocation... */ + *pcbData = PAGE_SIZE - (uData & PAGE_OFFSET_MASK); + *ppvData = RTMemDup((void const *)uData, *pcbData); + } + } + else + { + *pcbData = sizeof(uData); + *ppvData = RTMemDup(&uData, sizeof(uData)); + } + if (!*ppvData) + rcExit = RTMsgErrorExitFailure("Out of memory allocating %#zx bytes.", *pcbData); + } + else + rcExit = RTMsgErrorExitFailure("WinQueryClipbrdFmtInfo(,%s,) failed: %#x\n", + pFmtDesc->pszName, WinGetLastError(g_hOs2Ab)); + } + return rcExit; + +#elif defined(RT_OS_WINDOWS) + RTEXITCODE rcExit = WinOpenClipboardIfNecessary(); + if (rcExit == RTEXITCODE_SUCCESS) + { + HANDLE hData = GetClipboardData(pFmtDesc->fFormat); + if (hData != NULL) + { + SIZE_T const cbData = GlobalSize(hData); + PVOID const pvData = GlobalLock(hData); + if (pvData != NULL) + { + *pcbData = cbData; + if (cbData != 0) + { + if (pFmtDesc->fFlags & CLIPUTILFORMAT_F_CONVERT_UTF8) + { + char *pszUtf8 = NULL; + size_t cchUtf8 = 0; + int rc = RTUtf16ToUtf8Ex((PCRTUTF16)pvData, cbData / sizeof(RTUTF16), &pszUtf8, 0, &cchUtf8); + if (RT_SUCCESS(rc)) + { + *pcbData = cchUtf8 + 1; + *ppvData = RTMemDup(pszUtf8, cchUtf8 + 1); + RTStrFree(pszUtf8); + if (!*ppvData) + rcExit = RTMsgErrorExitFailure("Out of memory allocating %#zx bytes.", cbData); + } + else + rcExit = RTMsgErrorExitFailure("RTUtf16ToUtf8Ex failed: %Rrc", rc); + } + else + { + *ppvData = RTMemDup(pvData, cbData); + if (!*ppvData) + rcExit = RTMsgErrorExitFailure("Out of memory allocating %#zx bytes.", cbData); + } + } + GlobalUnlock(hData); + } + else + rcExit = RTMsgErrorExitFailure("GetClipboardData(%s) failed: %u (%#x)\n", + pFmtDesc->pszName, GetLastError(), GetLastError()); + } + else + rcExit = RTMsgErrorExitFailure("GetClipboardData(%s) failed: %u (%#x)\n", + pFmtDesc->pszName, GetLastError(), GetLastError()); + } + return rcExit; + +#elif defined(CU_X11) + + /* Request the data: */ + Atom const uAtomDst = pFmtDesc->uAtom; + int rc = XConvertSelection(g_pX11Display, g_pTarget->uAtom, pFmtDesc->uAtom, uAtomDst, g_hX11Window, CurrentTime); + if (g_uVerbosity > 1) + RTPrintf("XConvertSelection -> %d\n", rc); + + /* Wait for the reply: */ + for (;;) + { + XEvent Evt = {0}; + rc = XNextEvent(g_pX11Display, &Evt); + if (Evt.type == SelectionNotify) + { + if (g_uVerbosity > 1) + RTPrintf("XNextEvent -> %d; type=SelectionNotify\n", rc); + if (Evt.xselection.selection == g_pTarget->uAtom) + { + if (Evt.xselection.property == None) + return RTMsgErrorExitFailure("XConvertSelection(,%s,%s,) failed", g_pTarget->pszName, pFmtDesc->pszName); + + /* + * Retrieve the data. + */ + Atom uAtomRetType = 0; + int cBitsActualFmt = 0; + unsigned long cbLeftToRead = 0; + unsigned long cItems = 0; + unsigned char *pbData = NULL; + rc = XGetWindowProperty(g_pX11Display, g_hX11Window, uAtomDst, + 0 /*offset*/, _64M, False/*fDelete*/, AnyPropertyType, + &uAtomRetType, &cBitsActualFmt, &cItems, &cbLeftToRead, &pbData); + if (g_uVerbosity > 1) + RTPrintf("XConvertSelection -> %d; uAtomRetType=%u cBitsActualFmt=%d cItems=%lu cbLeftToRead=%lu pbData=%p\n", + rc, uAtomRetType, cBitsActualFmt, cItems, cbLeftToRead, pbData); + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + if (pbData && cItems > 0) + { + *pcbData = cItems * (cBitsActualFmt / 8); + *ppvData = RTMemDup(pbData, *pcbData); + if (!*ppvData) + rcExit = RTMsgErrorExitFailure("Out of memory allocating %#zx bytes.", *pcbData); + } + if (pbData) + XFree(pbData); + XDeleteProperty(g_pX11Display, g_hX11Window, uAtomDst); + return rcExit; + } + } + else if (g_uVerbosity > 1) + RTPrintf("XNextEvent -> %d; type=%d\n", rc, Evt.type); + } + +#else + RT_NOREF(pFmtDesc); + return RTMsgErrorExitFailure("ReadClipboardData is not implemented\n"); +#endif +} + + +/** + * Puts the given data and format on the clipboard. + * + * @returns Success indicator. + * @param pFmtDesc The format. + * @param pvData The data. + * @param cbData The amount of data in bytes. + */ +static RTEXITCODE WriteClipboardData(PCCLIPUTILFORMAT pFmtDesc, void const *pvData, size_t cbData) +{ +#if defined(RT_OS_OS2) + RTEXITCODE rcExit = CuOs2OpenClipboardIfNecessary(); + if (rcExit == RTEXITCODE_SUCCESS) + { + /** @todo do we need to become owner? */ + + /* Convert to local code page if needed: */ + char *pszLocale = NULL; + if (pFmtDesc->fFlags & CLIPUTILFORMAT_F_CONVERT_UTF8) + { + int rc = RTStrUtf8ToCurrentCPEx(&pszLocale, (char *)pvData, cbData); + if (RT_SUCCESS(rc)) + { + pvData = pszLocale; + cbData = strlen(pszLocale) + 1; + } + else + return RTMsgErrorExitFailure("RTStrUtf8ToCurrentCPEx failed: %Rrc\n", rc); + } + + /* Allocate a bunch of shared memory for the object. */ + PVOID pvShared = NULL; + APIRET orc = DosAllocSharedMem(&pvShared, NULL, cbData, + OBJ_GIVEABLE | OBJ_GETTABLE | OBJ_TILE | PAG_READ | PAG_WRITE | PAG_COMMIT); + if (orc == NO_ERROR) + { + memcpy(pvShared, pvData, cbData); + + if (WinSetClipbrdData(g_hOs2Ab, (uintptr_t)pvShared, pFmtDesc->fFormat, CFI_POINTER)) + { + if (g_uVerbosity > 0) + RTMsgInfo("Put '%s' on the clipboard: %p LB %zu\n", pFmtDesc->pszName, pvShared, cbData); + rcExit = RTEXITCODE_SUCCESS; + } + else + { + rcExit = RTMsgErrorExitFailure("WinSetClipbrdData(,%p LB %#x,%s,) failed: %#x\n", + pvShared, cbData, pFmtDesc->pszName, WinGetLastError(g_hOs2Ab)); + DosFreeMem(pvShared); + } + } + else + rcExit = RTMsgErrorExitFailure("DosAllocSharedMem(,, %#x,) -> %u", cbData, orc); + RTStrFree(pszLocale); + } + return rcExit; + + +#elif defined(RT_OS_WINDOWS) + RTEXITCODE rcExit = WinOpenClipboardIfNecessary(); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Do input data conversion. + */ + PRTUTF16 pwszFree = NULL; + if (pFmtDesc->fFlags & CLIPUTILFORMAT_F_CONVERT_UTF8) + { + size_t cwcConv = 0; + int rc = RTStrToUtf16Ex((char const *)pvData, cbData, &pwszFree, 0, &cwcConv); + if (RT_SUCCESS(rc)) + { + pvData = pwszFree; + cbData = cwcConv * sizeof(RTUTF16); + } + else + return RTMsgErrorExitFailure("RTStrToTUtf16Ex failed: %Rrc\n", rc); + } + + /* + * Text formats generally include the zero terminator. + */ + uint32_t cbZeroPadding = 0; + if (pFmtDesc->fFormat == CF_UNICODETEXT) + cbZeroPadding = sizeof(WCHAR); + else if (pFmtDesc->fFormat == CF_TEXT) + cbZeroPadding = sizeof(char); + + HANDLE hDstData = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, cbData + cbZeroPadding); + if (hDstData) + { + if (cbData) + { + PVOID pvDstData = GlobalLock(hDstData); + if (pvDstData) + memcpy(pvDstData, pvData, cbData); + else + rcExit = RTMsgErrorExitFailure("GlobalLock failed: %u (%#x)\n", GetLastError(), GetLastError()); + } + if (rcExit == RTEXITCODE_SUCCESS) + { + if (SetClipboardData(pFmtDesc->fFormat, hDstData)) + { + if (g_uVerbosity > 0) + RTMsgInfo("Put '%s' on the clipboard: %p LB %zu\n", pFmtDesc->pszName, hDstData, cbData + cbZeroPadding); + } + else + { + rcExit = RTMsgErrorExitFailure("SetClipboardData(%s) failed: %u (%#x)\n", + pFmtDesc->pszName, GetLastError(), GetLastError()); + GlobalFree(hDstData); + } + } + else + GlobalFree(hDstData); + } + else + rcExit = RTMsgErrorExitFailure("GlobalAlloc(,%#zx) failed: %u (%#x)\n", + cbData + cbZeroPadding, GetLastError(), GetLastError()); + } + return rcExit; + +#else + RT_NOREF(pFmtDesc, pvData, cbData); + return RTMsgErrorExitFailure("WriteClipboardData is not implemented\n"); +#endif +} + + +/** + * Check if the given data + format matches what's actually on the clipboard. + * + * @returns Success indicator. + * @param pFmtDesc The format to compare. + * @param pvExpect The expected clipboard data. + * @param cbExpect The size of the expected clipboard data. + */ +static RTEXITCODE CompareDataWithClipboard(PCCLIPUTILFORMAT pFmtDesc, void const *pvExpect, size_t cbExpect) +{ + void *pvData = NULL; + size_t cbData = 0; + RTEXITCODE rcExit = ReadClipboardData(pFmtDesc, &pvData, &cbData); + if (rcExit == RTEXITCODE_SUCCESS) + { + if ( cbData == cbExpect + && memcmp(pvData, pvExpect, cbData) == 0) + rcExit = RTEXITCODE_SUCCESS; + else + rcExit = RTMsgErrorExitFailure("Mismatch for '%s' (cbData=%#zx cbExpect=%#zx)\n", + pFmtDesc->pszName, cbData, cbExpect); + RTMemFree(pvData); + } + return rcExit; +} + + +/** + * Gets the given clipboard format. + * + * @returns Success indicator. + * @param pFmtDesc The format to get. + * @param pStrmOut Where to output the data. + * @param fIsStdOut Set if @a pStrmOut is standard output, clear if not. + */ +static RTEXITCODE ClipboardContentToStdOut(PCCLIPUTILFORMAT pFmtDesc) +{ + void *pvData = NULL; + size_t cbData = 0; + RTEXITCODE rcExit = ReadClipboardData(pFmtDesc, &pvData, &cbData); + if (rcExit == RTEXITCODE_SUCCESS) + { + int rc = RTStrmWrite(g_pStdOut, pvData, cbData); + RTMemFree(pvData); + if (RT_FAILURE(rc)) + rcExit = RTMsgErrorExitFailure("Error writing %#zx bytes to standard output: %Rrc", cbData, rc); + } + return rcExit; +} + + +/** + * Gets the given clipboard format. + * + * @returns Success indicator. + * @param pFmtDesc The format to get. + * @param pszFilename The output filename. + */ +static RTEXITCODE ClipboardContentToFile(PCCLIPUTILFORMAT pFmtDesc, const char *pszFilename) +{ + void *pvData = NULL; + size_t cbData = 0; + RTEXITCODE rcExit = ReadClipboardData(pFmtDesc, &pvData, &cbData); + if (rcExit == RTEXITCODE_SUCCESS) + { + RTFILE hFile = NIL_RTFILE; + int rc = RTFileOpen(&hFile, pszFilename, + RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE + | (0770 << RTFILE_O_CREATE_MODE_SHIFT)); + if (RT_SUCCESS(rc)) + { + rc = RTFileWrite(hFile, pvData, cbData, NULL); + int const rc2 = RTFileClose(hFile); + if (RT_FAILURE(rc) || RT_FAILURE(rc2)) + { + if (RT_FAILURE_NP(rc)) + RTMsgError("Writing %#z bytes to '%s' failed: %Rrc", cbData, pszFilename, rc); + else + RTMsgError("Closing '%s' failed: %Rrc", pszFilename, rc2); + RTMsgInfo("Deleting '%s'.", pszFilename); + RTFileDelete(pszFilename); + rcExit = RTEXITCODE_FAILURE; + } + } + else + rcExit = RTMsgErrorExitFailure("Failed to open '%s' for writing: %Rrc", pszFilename, rc); + RTMemFree(pvData); + } + return rcExit; +} + + +/** + * Puts the given format + data onto the clipboard. + * + * @returns Success indicator. + * @param pFmtDesc The format to put. + * @param pszData The string data. + */ +static RTEXITCODE PutStringOnClipboard(PCCLIPUTILFORMAT pFmtDesc, const char *pszData) +{ + return WriteClipboardData(pFmtDesc, pszData, strlen(pszData)); +} + + +/** + * Puts a format + file content onto the clipboard. + * + * @returns Success indicator. + * @param pFmtDesc The format to put. + * @param pszFilename The filename. + */ +static RTEXITCODE PutFileOnClipboard(PCCLIPUTILFORMAT pFmtDesc, const char *pszFilename) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + void *pvData = NULL; + size_t cbData = 0; + int rc = RTFileReadAll(pszFilename, &pvData, &cbData); + if (RT_SUCCESS(rc)) + { + rcExit = WriteClipboardData(pFmtDesc, pvData, cbData); + RTFileReadAllFree(pvData, cbData); + } + else + rcExit = RTMsgErrorExitFailure("Failed to open and read '%s' into memory: %Rrc", pszFilename, rc); + return rcExit; +} + + +/** + * Checks if the given format + data matches what's on the clipboard. + * + * @returns Success indicator. + * @param pFmtDesc The format to check. + * @param pszData The string data. + */ +static RTEXITCODE CheckStringAgainstClipboard(PCCLIPUTILFORMAT pFmtDesc, const char *pszData) +{ + return CompareDataWithClipboard(pFmtDesc, pszData, strlen(pszData)); +} + + +/** + * Check if the given format + file content matches what's on the clipboard. + * + * @returns Success indicator. + * @param pFmtDesc The format to check. + * @param pszFilename The filename. + */ +static RTEXITCODE CheckFileAgainstClipboard(PCCLIPUTILFORMAT pFmtDesc, const char *pszFilename) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + void *pvData = NULL; + size_t cbData = 0; + int rc = RTFileReadAll(pszFilename, &pvData, &cbData); + if (RT_SUCCESS(rc)) + { + rcExit = CompareDataWithClipboard(pFmtDesc, pvData, cbData); + RTFileReadAllFree(pvData, cbData); + } + else + rcExit = RTMsgErrorExitFailure("Failed to open and read '%s' into memory: %Rrc", pszFilename, rc); + return rcExit; +} + + +/** + * Check that the given format is not on the clipboard. + * + * @returns Success indicator. + * @param pFmtDesc The format to check. + */ +static RTEXITCODE CheckFormatNotOnClipboard(PCCLIPUTILFORMAT pFmtDesc) +{ +#if defined(RT_OS_OS2) + RTEXITCODE rcExit = CuOs2OpenClipboardIfNecessary(); + if (rcExit == RTEXITCODE_SUCCESS) + { + ULONG fFmtInfo = 0; + if (WinQueryClipbrdFmtInfo(g_hOs2Ab, pFmtDesc->fFormat, &fFmtInfo)) + rcExit = RTMsgErrorExitFailure("Format '%s' is present"); + } + return rcExit; + +#elif defined(RT_OS_WINDOWS) + RTEXITCODE rcExit = WinOpenClipboardIfNecessary(); + if (rcExit == RTEXITCODE_SUCCESS) + { + if (IsClipboardFormatAvailable(pFmtDesc->fFormat)) + rcExit = RTMsgErrorExitFailure("Format '%s' is present"); + } + return rcExit; + +#else + RT_NOREF(pFmtDesc); + return RTMsgErrorExitFailure("CheckFormatNotOnClipboard is not implemented"); +#endif +} + + +/** + * Empties the clipboard. + * + * @returns Success indicator. + */ +static RTEXITCODE ZapAllClipboardData(void) +{ +#if defined(RT_OS_OS2) + RTEXITCODE rcExit = CuOs2OpenClipboardIfNecessary(); + if (rcExit == RTEXITCODE_SUCCESS) + { + ULONG fFmtInfo = 0; + if (WinEmptyClipbrd(g_hOs2Ab)) + { + WinSetClipbrdOwner(g_hOs2Ab, g_hOs2Wnd); /* Probably unnecessary? */ + WinSetClipbrdOwner(g_hOs2Ab, NULLHANDLE); + g_fOs2ClipboardOwner = false; + } + else + rcExit = RTMsgErrorExitFailure("WinEmptyClipbrd() failed: %#x\n", WinGetLastError(g_hOs2Ab)); + } + return rcExit; + +#elif defined(RT_OS_WINDOWS) + RTEXITCODE rcExit = WinOpenClipboardIfNecessary(); + if (rcExit == RTEXITCODE_SUCCESS) + { + if (!EmptyClipboard()) + rcExit = RTMsgErrorExitFailure("EmptyClipboard() failed: %u (%#x)\n", GetLastError(), GetLastError()); + } + return rcExit; + +#else + return RTMsgErrorExitFailure("ZapAllClipboardData is not implemented"); +#endif +} + + +/** + * Waits/delays at least @a cMsWait milliseconds. + * + * @returns Success indicator. + * @param cMsWait Minimum wait/delay time in milliseconds. + */ +static RTEXITCODE DoWait(uint32_t cMsWait) +{ + uint64_t const msStart = RTTimeMilliTS(); + if (g_uVerbosity > 1) + RTMsgInfo("Waiting %u ms...\n", cMsWait); + +#if defined(RT_OS_OS2) + /* + * Arm a timer which will timeout after the desired period and + * quit when we've dispatched it. + */ + g_fOs2TimerTicked = false; + if (WinStartTimer(g_hOs2Ab, g_hOs2Wnd, 1 /*idEvent*/, cMsWait + 1) != 0) + { + QMSG Msg; + while (WinGetMsg(g_hOs2Ab, &Msg, NULL, 0, 0)) + { + WinDispatchMsg(g_hOs2Ab, &Msg); + if (g_fOs2TimerTicked || RTTimeMilliTS() - msStart >= cMsWait) + break; + } + + if (!WinStopTimer(g_hOs2Ab, g_hOs2Wnd, 1 /*idEvent*/)) + RTMsgWarning("WinStopTimer failed: %#x", WinGetLastError(g_hOs2Ab)); + } + else + return RTMsgErrorExitFailure("WinStartTimer(,,,%u ms) failed: %#x", cMsWait + 1, WinGetLastError(g_hOs2Ab)); + +#elif defined(RT_OS_WINDOWS) + /* + * Arm a timer which will timeout after the desired period and + * quit when we've dispatched it. + */ + g_fWinTimerTicked = false; + if (SetTimer(g_hWinWnd, 1 /*idEvent*/, cMsWait + 1, NULL /*pfnTimerProc*/) != 0) + { + MSG Msg; + while (GetMessageW(&Msg, NULL, 0, 0)) + { + TranslateMessage(&Msg); + DispatchMessageW(&Msg); + if (g_fWinTimerTicked || RTTimeMilliTS() - msStart >= cMsWait) + break; + } + + if (!KillTimer(g_hWinWnd, 1 /*idEvent*/)) + RTMsgWarning("KillTimer failed: %u (%#x)", GetLastError(), GetLastError()); + } + else + return RTMsgErrorExitFailure("SetTimer(,,%u ms,) failed: %u (%#x)", cMsWait + 1, GetLastError(), GetLastError()); + +#else +/** @todo X11 needs to run it's message queue too, because if we're offering + * things on the "clipboard" we must reply to requests for them. */ + /* + * Just a plain simple RTThreadSleep option (will probably not be used in the end): + */ + for (;;) + { + uint64_t cMsElapsed = RTTimeMilliTS() - msStart; + if (cMsElapsed >= cMsWait) + break; + RTThreadSleep(cMsWait - cMsElapsed); + } +#endif + + if (g_uVerbosity > 2) + RTMsgInfo("Done waiting after %u ms.\n", RTTimeMilliTS() - msStart); + return RTEXITCODE_SUCCESS; +} + + +/** + * Display the usage to @a pStrm. + */ +static void Usage(PRTSTREAM pStrm) +{ + RTStrmPrintf(pStrm, + "usage: %s [--get <fmt> [--get ...]] [--get-file <fmt> <file> [--get-file ...]]\n" + " %s [--zap] [--put <fmt> <content> [--put ...]] [--put-file <fmt> <file> [--put-file ...]] [--wait <ms>]\n" + " %s [--check <fmt> <expected> [--check ...]] [--check-file <fmt> <file> [--check-file ...]]\n" + " [--check-no <fmt> [--check-no ...]]\n" + , RTProcShortName(), RTProcShortName(), RTProcShortName()); + RTStrmPrintf(pStrm, "\n"); + RTStrmPrintf(pStrm, "Actions/Options:\n"); + + for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++) + { + const char *pszHelp; + switch (g_aCmdOptions[i].iShort) + { + case 'l': pszHelp = "List the clipboard content."; break; + case 'g': pszHelp = "Get given clipboard format and writes it to standard output."; break; + case 'G': pszHelp = "Get given clipboard format and writes it to the specified file."; break; + case 'p': pszHelp = "Puts given format and content on the clipboard."; break; + case 'P': pszHelp = "Puts given format and file content on the clipboard."; break; + case 'c': pszHelp = "Checks that the given format and content matches the clipboard."; break; + case 'C': pszHelp = "Checks that the given format and file content matches the clipboard."; break; + case 'n': pszHelp = "Checks that the given format is not on the clipboard."; break; + case 'z': pszHelp = "Zaps the clipboard content."; break; +#ifdef MULTI_TARGET_CLIPBOARD + case 't': pszHelp = "Selects the target clipboard."; break; +#endif +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) + case 'k': pszHelp = "Closes the clipboard if open (win,os2)."; break; +#endif + case 'w': pszHelp = "Waits a given number of milliseconds before continuing."; break; + case 'v': pszHelp = "More verbose execution."; break; + case 'q': pszHelp = "Quiet execution."; break; + case 'h': pszHelp = "Displays this help and exit"; break; + case 'V': pszHelp = "Displays the program revision"; break; + + default: + pszHelp = "Option undocumented"; + break; + } + if ((unsigned)g_aCmdOptions[i].iShort < 127U) + { + char szOpt[64]; + RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort); + RTStrmPrintf(pStrm, " %-19s %s\n", szOpt, pszHelp); + } + else + RTStrmPrintf(pStrm, " %-19s %s\n", g_aCmdOptions[i].pszLong, pszHelp); + } + RTStrmPrintf(pStrm, + "\n" + "Note! Options are processed in the order they are given.\n"); + + RTStrmPrintf(pStrm, "\nFormats:\n"); + for (size_t i = 0; i < RT_ELEMENTS(g_aFormats); i++) + RTStrmPrintf(pStrm, " %-12s: %s\n", g_aFormats[i].pszName, g_aFormats[i].pszDesc); + +#ifdef MULTI_TARGET_CLIPBOARD + RTStrmPrintf(pStrm, "\nTarget:\n"); + for (size_t i = 0; i < RT_ELEMENTS(g_aTargets); i++) + RTStrmPrintf(pStrm, " %-12s: %s\n", g_aTargets[i].pszName, g_aTargets[i].pszDesc); +#endif +} + + +int main(int argc, char *argv[]) +{ + /* + * Init IPRT. + */ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Host specific init. + */ +#ifdef RT_OS_DARWIN + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; +#elif defined(RT_OS_OS2) + RTEXITCODE rcExit = CuOs2Init(); +#elif defined(RT_OS_WINDOWS) + RTEXITCODE rcExit = CuWinInit(); +#else + RTEXITCODE rcExit = CuX11Init(); +#endif + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + /* + * Process options (in order). + */ + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */); + while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + RTEXITCODE rcExit2 = RTEXITCODE_SUCCESS; + switch (rc) + { +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) + case 'k': + rcExit2 = CuCloseClipboard(); + break; +#endif + + case 'l': + rcExit2 = ListClipboardContent(); + break; + + case 'g': + { + PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz); + if (pFmtDesc) + rcExit2 = ClipboardContentToStdOut(pFmtDesc); + else + rcExit2 = RTEXITCODE_FAILURE; + break; + } + + case 'G': + { + PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz); + if (pFmtDesc) + { + rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_STRING); + if (RT_SUCCESS(rc)) + rcExit2 = ClipboardContentToFile(pFmtDesc, ValueUnion.psz); + else + return RTMsgErrorExitFailure("No filename given with --get-file"); + } + else + rcExit2 = RTEXITCODE_FAILURE; + break; + } + + case 'p': + { + PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz); + if (pFmtDesc) + { + rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_STRING); + if (RT_SUCCESS(rc)) + rcExit2 = PutStringOnClipboard(pFmtDesc, ValueUnion.psz); + else + return RTMsgErrorExitFailure("No data string given with --put"); + } + else + rcExit2 = RTEXITCODE_FAILURE; + break; + } + + case 'P': + { + PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz); + if (pFmtDesc) + { + rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_STRING); + if (RT_SUCCESS(rc)) + rcExit2 = PutFileOnClipboard(pFmtDesc, ValueUnion.psz); + else + return RTMsgErrorExitFailure("No filename given with --put-file"); + } + else + rcExit2 = RTEXITCODE_FAILURE; + break; + } + + case 'c': + { + PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz); + if (pFmtDesc) + { + rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_STRING); + if (RT_SUCCESS(rc)) + rcExit2 = CheckStringAgainstClipboard(pFmtDesc, ValueUnion.psz); + else + return RTMsgErrorExitFailure("No data string given with --check"); + } + else + rcExit2 = RTEXITCODE_FAILURE; + break; + } + + case 'C': + { + PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz); + if (pFmtDesc) + { + rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_STRING); + if (RT_SUCCESS(rc)) + rcExit2 = CheckFileAgainstClipboard(pFmtDesc, ValueUnion.psz); + else + return RTMsgErrorExitFailure("No filename given with --check-file"); + } + else + rcExit2 = RTEXITCODE_FAILURE; + break; + } + + case 'n': + { + PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz); + if (pFmtDesc) + rcExit2 = CheckFormatNotOnClipboard(pFmtDesc); + else + rcExit2 = RTEXITCODE_FAILURE; + break; + } + + + case 'z': + rcExit2 = ZapAllClipboardData(); + break; + +#ifdef MULTI_TARGET_CLIPBOARD + case 't': + { + CLIPUTILTARGET *pNewTarget = NULL; + for (size_t i = 0; i < RT_ELEMENTS(g_aTargets); i++) + if (strcmp(ValueUnion.psz, g_aTargets[i].pszName) == 0) + { + pNewTarget = &g_aTargets[i]; + break; + } + if (!pNewTarget) + return RTMsgErrorExitFailure("Unknown target '%s'", ValueUnion.psz); + if (pNewTarget != g_pTarget && g_uVerbosity > 0) + RTMsgInfo("Switching from '%s' to '%s'\n", g_pTarget->pszName, pNewTarget->pszName); + g_pTarget = pNewTarget; + break; + } +#endif + + case 'w': + rcExit2 = DoWait(ValueUnion.u32); + break; + + case 'q': + g_uVerbosity = 0; + break; + + case 'v': + g_uVerbosity++; + break; + + case 'h': + Usage(g_pStdOut); + return RTEXITCODE_SUCCESS; + + case 'V': + { + char szRev[] = "$Revision: 153224 $"; + szRev[RT_ELEMENTS(szRev) - 2] = '\0'; + RTPrintf(RTStrStrip(strchr(szRev, ':') + 1)); + return RTEXITCODE_SUCCESS; + } + + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + + if (rcExit2 != RTEXITCODE_SUCCESS && rcExit == RTEXITCODE_SUCCESS) + rcExit = rcExit2; + } + + /* + * Host specific cleanup. + */ +#if defined(RT_OS_OS2) + RTEXITCODE rcExit2 = CuOs2Term(); +#elif defined(RT_OS_WINDOWS) + RTEXITCODE rcExit2 = CuWinTerm(); +#else + RTEXITCODE rcExit2 = RTEXITCODE_SUCCESS; +#endif + if (rcExit2 != RTEXITCODE_SUCCESS && rcExit != RTEXITCODE_SUCCESS) + rcExit = rcExit2; + + return rcExit; +} + diff --git a/src/VBox/ValidationKit/utils/clipboard/Makefile.kmk b/src/VBox/ValidationKit/utils/clipboard/Makefile.kmk new file mode 100644 index 00000000..795f748f --- /dev/null +++ b/src/VBox/ValidationKit/utils/clipboard/Makefile.kmk @@ -0,0 +1,53 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Clipboard tests. +# + +# +# Copyright (C) 2021-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Clipboard Utility. +# +PROGRAMS += ClipUtil +ClipUtil_TEMPLATE = VBoxValidationKitR3 +ClipUtil_SOURCES = ClipUtil.cpp +ifn1of ($(KBUILD_TARGET), darwin os2 win) +ClipUtil_LIBPATH = $(VBOX_LIBPATH_X11) +ClipUtil_LIBS = X11 Xmu +endif + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/cpu/Makefile.kmk b/src/VBox/ValidationKit/utils/cpu/Makefile.kmk new file mode 100644 index 00000000..01b2a927 --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/Makefile.kmk @@ -0,0 +1,84 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - CPU Test Utilities. +# + +# +# Copyright (C) 2009-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +if1of ($(KBUILD_TARGET_ARCH), x86 amd64) + PROGRAMS += xmmsaving + xmmsaving_TEMPLATE = VBoxValidationKitR3 + xmmsaving_SOURCES = xmmsaving.cpp xmmsaving-asm.asm +endif + +if1of ($(KBUILD_TARGET_ARCH), x86 amd64) + PROGRAMS += exceptionsR3 + exceptionsR3_TEMPLATE = VBoxValidationKitR3 + exceptionsR3_SOURCES = exceptionsR3.cpp exceptionsR3-asm.asm +endif + +PROGRAMS += cpu-numa +cpu-numa_TEMPLATE = VBoxValidationKitR3 +cpu-numa_SOURCES = cpu-numa.cpp + +PROGRAMS += cpu-alloc-all-mem +cpu-alloc-all-mem_TEMPLATE = VBoxValidationKitR3 +cpu-alloc-all-mem_SOURCES = cpu-alloc-all-mem.cpp + +if1of ($(KBUILD_TARGET_ARCH), x86 amd64) + ifneq ($(KBUILD_HOST),os2) + PROGRAMS += cidet-app + endif + cidet-app_TEMPLATE = VBoxValidationKitR3 + cidet-app_SOURCES = \ + cidet-app.cpp \ + cidet-appA.asm \ + cidet-core.cpp \ + cidet-instr-1.cpp + cidet-app_DEFS = IN_DIS + cidet-app_DEFS.linux = CIDET_REDUCED_CTX + cidet-app_LIBS = $(PATH_STAGE_LIB)/DisasmR3Static$(VBOX_SUFF_LIB) + cidet-app_VBOX_IMPORT_CHECKER.win.x86 = $(NO_SUCH_VARIABLE) # doesn't work on NT4 yet. +endif + +if1of ($(KBUILD_TARGET_ARCH), x86 amd64) + PROGRAMS += rdtsc + rdtsc_TEMPLATE = VBoxValidationKitR3 + rdtsc_SOURCES = rdtsc.cpp rdtsc-asm.asm +endif + + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/cpu/cidet-app.cpp b/src/VBox/ValidationKit/utils/cpu/cidet-app.cpp new file mode 100644 index 00000000..32d16624 --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/cidet-app.cpp @@ -0,0 +1,1376 @@ +/* $Id: cidet-app.cpp $ */ +/** @file + * CPU Instruction Decoding & Execution Tests - Ring-3 Driver Application. + */ + +/* + * Copyright (C) 2014-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "cidet.h" + +#include <iprt/asm-amd64-x86.h> +#include <iprt/buildconfig.h> +#include <iprt/err.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/param.h> +#include <iprt/rand.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/test.h> + +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +#else +# define USE_SIGNALS +# include <signal.h> +# include <unistd.h> +# include <sys/ucontext.h> +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @def CIDET_LEAVE_GS_ALONE + * Leave GS alone on 64-bit darwin (gs is 0, no ldt or gdt entry to load that'll + * restore the lower 32-bits of the base when saving and restoring the register). + */ +#if (defined(RT_OS_DARWIN) && defined(RT_ARCH_AMD64)) || defined(DOXYGEN_RUNNING) +# define CIDET_LEAVE_GS_ALONE +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * CIDET driver app buffer. + */ +typedef struct CIDETAPPBUF +{ + /** The buffer size. */ + size_t cb; + /** The normal allocation. + * There is a fence page before this as well as at pbNormal+cb. */ + uint8_t *pbNormal; + /** The low memory allocation (32-bit addressable if 64-bit host, 16-bit + * addressable if 32-bit host). */ + uint8_t *pbLow; + /** Set if we're using the normal buffer, clear if it's the low one. */ + bool fUsingNormal : 1; + /** Set if the buffer is armed, clear if mostly accessible. */ + bool fArmed : 1; + /** Set if this is a code buffer. */ + bool fIsCode : 1; + /** The memory protection for the pages (RTMEM_PROT_XXX). */ + uint8_t fDefaultProt : 3; + /** The memory protection for the last page (RTMEM_PROT_XXX). */ + uint8_t fLastPageProt : 3; + /** The buffer index. */ + uint16_t idxCfg; +} CIDETAPPBUF; +/** Pointer to a CIDET driver app buffer. */ +typedef CIDETAPPBUF *PCIDETAPPBUF; + +/** Number of code buffers. */ +#define CIDETAPP_CODE_BUF_COUNT 1 +/** Number of data buffers. */ +#define CIDETAPP_DATA_BUF_COUNT 1 + + +/** + * CIDET driver app instance. + */ +typedef struct CIDETAPP +{ + /** The core structure. */ + CIDETCORE Core; + /** The execute return context. */ + CIDETCPUCTX ExecuteCtx; + /** Code buffers (runs parallel to g_aCodeBufCfgs). */ + CIDETAPPBUF aCodeBuffers[CIDETAPP_CODE_BUF_COUNT]; + /** Data buffers (runs parallel to g_aDataBufCfgs). */ + CIDETAPPBUF aDataBuffers[CIDETAPP_DATA_BUF_COUNT]; + + /** The lowest stack address. */ + uint8_t *pbStackLow; + /** The end of the stack allocation (highest address). */ + uint8_t *pbStackEnd; + /** Stack size (= pbStackEnd - pbStackLow). */ + uint32_t cbStack; + /** Whether we're currently using the 'lock int3' to deal with tricky stack. */ + bool fUsingLockedInt3; +} CIDETAPP; +/** Pointer to a CIDET driver app instance. */ +typedef CIDETAPP *PCIDETAPP; +/** Pointer to a pointer to a CIDET driver app instance. */ +typedef PCIDETAPP *PPCIDETAPP; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The test instance handle. */ +static RTTEST g_hTest; +/** Points to the instance data while executing, NULL if not executing or if + * we've already handled the first exception while executing. */ +static PCIDETAPP volatile g_pExecutingThis; +#ifdef USE_SIGNALS +/** The default signal mask. */ +static sigset_t g_ProcSigMask; +/** The alternative signal stack. */ +static stack_t g_AltStack; +#endif + + +/** Code buffer configurations (parallel to CIDETAPP::aCodeBuffers). */ +static CIDETBUFCFG g_aCodeBufCfgs[CIDETAPP_CODE_BUF_COUNT] = +{ + { + "Normal", + CIDETBUF_PROT_RWX | CIDETBUF_DPL_3 | CIDETBUF_DPL_SAME | CIDETBUF_SEG_ER | CIDETBUF_KIND_CODE, + }, +}; + +/** Data buffer configurations (parallel to CIDETAPP::aDataBuffers). */ +static CIDETBUFCFG g_aDataBufCfgs[CIDETAPP_DATA_BUF_COUNT] = +{ + { + "Normal", + CIDETBUF_PROT_RWX | CIDETBUF_DPL_3 | CIDETBUF_DPL_SAME | CIDETBUF_SEG_RW | CIDETBUF_KIND_DATA, + }, +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +DECLASM(void) CidetAppSaveAndRestoreCtx(void); +DECLASM(void) CidetAppRestoreCtx(PCCIDETCPUCTX pRestoreCtx); +DECLASM(void) CidetAppExecute(PCIDETCPUCTX pSaveCtx, PCCIDETCPUCTX pRestoreCtx); + + +/* + * + * + * Exception and signal handling. + * Exception and signal handling. + * Exception and signal handling. + * + * + */ + +#ifdef RT_OS_WINDOWS +static int CidetAppXcptFilter(EXCEPTION_POINTERS *pXcptPtrs) RT_NOTHROW_DEF +{ + /* + * Grab the this point. We expect at most one signal. + */ + PCIDETAPP pThis = g_pExecutingThis; + g_pExecutingThis = NULL; + if (pThis == NULL) + { + /* we're up the infamous creek... */ + for (;;) ExitProcess(2); + } + + /* + * Gather CPU state information from the context structure. + */ + CONTEXT *pSrcCtx = pXcptPtrs->ContextRecord; +# ifdef RT_ARCH_AMD64 + if ( (pSrcCtx->ContextFlags & (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS)) + != (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS)) + __debugbreak(); + pThis->Core.ActualCtx.rip = pSrcCtx->Rip; + pThis->Core.ActualCtx.rfl = pSrcCtx->EFlags; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xAX] = pSrcCtx->Rax; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xCX] = pSrcCtx->Rcx; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xDX] = pSrcCtx->Rdx; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xBX] = pSrcCtx->Rbx; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xSP] = pSrcCtx->Rsp; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xBP] = pSrcCtx->Rbp; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xSI] = pSrcCtx->Rsi; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xDI] = pSrcCtx->Rdi; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x8] = pSrcCtx->R8; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x9] = pSrcCtx->R9; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x10] = pSrcCtx->R10; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x11] = pSrcCtx->R11; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x12] = pSrcCtx->R12; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x13] = pSrcCtx->R13; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x14] = pSrcCtx->R14; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x15] = pSrcCtx->R15; + pThis->Core.ActualCtx.aSRegs[X86_SREG_ES] = pSrcCtx->SegEs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_CS] = pSrcCtx->SegCs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_SS] = pSrcCtx->SegSs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_DS] = pSrcCtx->SegDs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_FS] = pSrcCtx->SegFs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_GS] = pSrcCtx->SegGs; + if (pSrcCtx->ContextFlags & CONTEXT_FLOATING_POINT) + { + /* ... */ + } + if (pSrcCtx->ContextFlags & CONTEXT_DEBUG_REGISTERS) + { + /* ... */ + } + +# elif defined(RT_ARCH_X86) + if ( (pSrcCtx->ContextFlags & (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS)) + != (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS)) + __debugbreak(); + pThis->Core.ActualCtx.rip = pSrcCtx->Eip; + pThis->Core.ActualCtx.rfl = pSrcCtx->EFlags; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xAX] = pSrcCtx->Eax; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xCX] = pSrcCtx->Ecx; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xDX] = pSrcCtx->Edx; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xBX] = pSrcCtx->Ebx; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xSP] = pSrcCtx->Esp; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xBP] = pSrcCtx->Ebp; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xSI] = pSrcCtx->Esi; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xDI] = pSrcCtx->Edi; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x8] = 0; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x9] = 0; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x10] = 0; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x11] = 0; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x12] = 0; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x13] = 0; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x14] = 0; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x15] = 0; + pThis->Core.ActualCtx.aSRegs[X86_SREG_ES] = pSrcCtx->SegEs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_CS] = pSrcCtx->SegCs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_SS] = pSrcCtx->SegSs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_DS] = pSrcCtx->SegDs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_FS] = pSrcCtx->SegFs; + pThis->Core.ActualCtx.aSRegs[X86_SREG_GS] = pSrcCtx->SegGs; + if (pSrcCtx->ContextFlags & CONTEXT_FLOATING_POINT) + { + /* ... */ + } + if (pSrcCtx->ContextFlags & CONTEXT_EXTENDED_REGISTERS) + { + /* ... */ + } + if (pSrcCtx->ContextFlags & CONTEXT_DEBUG_REGISTERS) + { + /* ... */ + } +# else +# error "Not supported" +# endif + + /* + * Add/Adjust CPU state information according to the exception code. + */ + pThis->Core.ActualCtx.uErr = UINT64_MAX; + switch (pXcptPtrs->ExceptionRecord->ExceptionCode) + { + case EXCEPTION_INT_DIVIDE_BY_ZERO: + pThis->Core.ActualCtx.uXcpt = X86_XCPT_DE; + break; + case EXCEPTION_SINGLE_STEP: + pThis->Core.ActualCtx.uXcpt = X86_XCPT_DB; + break; + case EXCEPTION_BREAKPOINT: + pThis->Core.ActualCtx.uXcpt = X86_XCPT_BP; + break; + case EXCEPTION_INT_OVERFLOW: + pThis->Core.ActualCtx.uXcpt = X86_XCPT_OF; + break; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + pThis->Core.ActualCtx.uXcpt = X86_XCPT_BR; + break; + case EXCEPTION_ILLEGAL_INSTRUCTION: + pThis->Core.ActualCtx.uXcpt = X86_XCPT_UD; + break; + + case EXCEPTION_PRIV_INSTRUCTION: + pThis->Core.ActualCtx.uXcpt = X86_XCPT_GP; + pThis->Core.ActualCtx.uErr = 0; + break; + + case EXCEPTION_ACCESS_VIOLATION: + { + pThis->Core.ActualCtx.uXcpt = X86_XCPT_PF; + pThis->Core.ActualCtx.cr2 = pXcptPtrs->ExceptionRecord->ExceptionInformation[1]; + pThis->Core.ActualCtx.uErr = 0; + if (pXcptPtrs->ExceptionRecord->ExceptionInformation[0] == EXCEPTION_WRITE_FAULT) + pThis->Core.ActualCtx.uErr = X86_TRAP_PF_RW; + else if (pXcptPtrs->ExceptionRecord->ExceptionInformation[0] == EXCEPTION_EXECUTE_FAULT) + pThis->Core.ActualCtx.uErr = X86_TRAP_PF_ID; + else if (pXcptPtrs->ExceptionRecord->ExceptionInformation[0] != EXCEPTION_READ_FAULT) + AssertFatalFailed(); + + MEMORY_BASIC_INFORMATION MemInfo = {0}; + if (VirtualQuery((PVOID)pXcptPtrs->ExceptionRecord->ExceptionInformation[1], &MemInfo, sizeof(MemInfo)) > 0) + switch (MemInfo.Protect & 0xff) + { + case PAGE_NOACCESS: + break; + case PAGE_READONLY: + case PAGE_READWRITE: + case PAGE_WRITECOPY: + case PAGE_EXECUTE: + case PAGE_EXECUTE_READ: + case PAGE_EXECUTE_READWRITE: + case PAGE_EXECUTE_WRITECOPY: + pThis->Core.ActualCtx.uErr |= X86_TRAP_PF_P; + break; + default: + AssertFatalFailed(); + } + break; + } + + case EXCEPTION_FLT_DENORMAL_OPERAND: + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + case EXCEPTION_FLT_INEXACT_RESULT: + case EXCEPTION_FLT_INVALID_OPERATION: + case EXCEPTION_FLT_OVERFLOW: + case EXCEPTION_FLT_STACK_CHECK: + case EXCEPTION_FLT_UNDERFLOW: + pThis->Core.ActualCtx.uXcpt = X86_XCPT_MF; + break; + + case EXCEPTION_DATATYPE_MISALIGNMENT: + pThis->Core.ActualCtx.uXcpt = X86_XCPT_AC; + break; + + default: + pThis->Core.ActualCtx.uXcpt = pXcptPtrs->ExceptionRecord->ExceptionCode; + break; + } + + /* + * Our own personal long jump implementation. + */ + CidetAppRestoreCtx(&pThis->ExecuteCtx); + + /* Won't return...*/ + return EXCEPTION_EXECUTE_HANDLER; +} + + +/** + * Vectored exception handler. + * + * @returns Long jumps or terminates the process. + * @param pXcptPtrs The exception record. + */ +static LONG CALLBACK CidetAppVectoredXcptHandler(EXCEPTION_POINTERS *pXcptPtrs) RT_NOTHROW_DEF +{ + RTStrmPrintf(g_pStdErr, "CidetAppVectoredXcptHandler!\n"); + CidetAppXcptFilter(pXcptPtrs); + + /* won't get here. */ + return EXCEPTION_CONTINUE_SEARCH; +} + + +/** + * Unhandled exception filter. + * + * @returns Long jumps or terminates the process. + * @param pXcptPtrs The exception record. + */ +static LONG CALLBACK CidetAppUnhandledXcptFilter(EXCEPTION_POINTERS *pXcptPtrs) RT_NOTHROW_DEF +{ + RTStrmPrintf(g_pStdErr, "CidetAppUnhandledXcptFilter!\n"); + CidetAppXcptFilter(pXcptPtrs); + + /* won't get here. */ + return EXCEPTION_CONTINUE_SEARCH; +} + + +#elif defined(USE_SIGNALS) +/** + * Signal handler. + */ +static void CidetAppSigHandler(int iSignal, siginfo_t *pSigInfo, void *pvCtx) +{ +# if 1 + if ( !g_pExecutingThis + || !g_pExecutingThis->fUsingLockedInt3 + || iSignal != SIGILL) + { + RTStrmPrintf(g_pStdErr, "signal %d pSigInfo=%p pvCtx=%p", iSignal, pSigInfo, pvCtx); + if (pSigInfo) + RTStrmPrintf(g_pStdErr, " si_addr=%p si_code=%#x sival_ptr=%p sival_int=%d", + pSigInfo->si_addr, pSigInfo->si_code, pSigInfo->si_value.sival_ptr, pSigInfo->si_value.sival_int); + RTStrmPrintf(g_pStdErr, "\n"); + } +# endif + + /* + * Grab the this point. We expect at most one signal. + */ + PCIDETAPP pThis = g_pExecutingThis; + g_pExecutingThis = NULL; + if (pThis == NULL) + { + /* we're up the infamous creek... */ + RTStrmPrintf(g_pStdErr, "Creek time!\n"); + for (;;) _exit(2); + } + + /* + * Gather all the CPU state information available. + */ +# ifdef RT_OS_LINUX + ucontext_t const *pCtx = (ucontext_t const *)pvCtx; +# ifdef RT_ARCH_AMD64 + + pThis->Core.ActualCtx.aGRegs[X86_GREG_xAX] = pCtx->uc_mcontext.gregs[REG_RAX]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xCX] = pCtx->uc_mcontext.gregs[REG_RCX]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xDX] = pCtx->uc_mcontext.gregs[REG_RDX]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xBX] = pCtx->uc_mcontext.gregs[REG_RBX]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xSP] = pCtx->uc_mcontext.gregs[REG_RSP]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xBP] = pCtx->uc_mcontext.gregs[REG_RBP]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xSI] = pCtx->uc_mcontext.gregs[REG_RSI]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xDI] = pCtx->uc_mcontext.gregs[REG_RDI]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x8 ] = pCtx->uc_mcontext.gregs[REG_R8]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x9 ] = pCtx->uc_mcontext.gregs[REG_R9]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x10] = pCtx->uc_mcontext.gregs[REG_R10]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x11] = pCtx->uc_mcontext.gregs[REG_R11]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x12] = pCtx->uc_mcontext.gregs[REG_R12]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x13] = pCtx->uc_mcontext.gregs[REG_R13]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x14] = pCtx->uc_mcontext.gregs[REG_R14]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_x15] = pCtx->uc_mcontext.gregs[REG_R15]; + pThis->Core.ActualCtx.aSRegs[X86_SREG_CS] = RT_LO_U16((uint32_t)pCtx->uc_mcontext.gregs[REG_CSGSFS]); + pThis->Core.ActualCtx.aSRegs[X86_SREG_GS] = RT_HI_U16((uint32_t)pCtx->uc_mcontext.gregs[REG_CSGSFS]); + pThis->Core.ActualCtx.aSRegs[X86_SREG_FS] = (uint16_t)RT_HI_U32(pCtx->uc_mcontext.gregs[REG_CSGSFS]); + pThis->Core.ActualCtx.aSRegs[X86_SREG_DS] = ASMGetDS(); + pThis->Core.ActualCtx.aSRegs[X86_SREG_ES] = ASMGetES(); + pThis->Core.ActualCtx.aSRegs[X86_SREG_SS] = ASMGetSS(); + pThis->Core.ActualCtx.rip = pCtx->uc_mcontext.gregs[REG_RIP]; + pThis->Core.ActualCtx.rfl = pCtx->uc_mcontext.gregs[REG_EFL]; + pThis->Core.ActualCtx.cr2 = pCtx->uc_mcontext.gregs[REG_CR2]; + pThis->Core.ActualCtx.uXcpt = pCtx->uc_mcontext.gregs[REG_TRAPNO]; + pThis->Core.ActualCtx.uErr = pCtx->uc_mcontext.gregs[REG_ERR]; + + /* Fudge the FS and GS registers as setup_sigcontext returns 0. */ + if (pThis->Core.ActualCtx.aSRegs[X86_SREG_FS] == 0) + pThis->Core.ActualCtx.aSRegs[X86_SREG_FS] = pThis->Core.ExpectedCtx.aSRegs[X86_SREG_FS]; + if (pThis->Core.ActualCtx.aSRegs[X86_SREG_GS] == 0) + pThis->Core.ActualCtx.aSRegs[X86_SREG_GS] = pThis->Core.ExpectedCtx.aSRegs[X86_SREG_GS]; + +# elif defined(RT_ARCH_X86) + pThis->Core.ActualCtx.aGRegs[X86_GREG_xAX] = pCtx->uc_mcontext.gregs[REG_EAX]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xCX] = pCtx->uc_mcontext.gregs[REG_ECX]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xDX] = pCtx->uc_mcontext.gregs[REG_EDX]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xBX] = pCtx->uc_mcontext.gregs[REG_EBX]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xSP] = pCtx->uc_mcontext.gregs[REG_ESP]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xBP] = pCtx->uc_mcontext.gregs[REG_EBP]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xSI] = pCtx->uc_mcontext.gregs[REG_ESI]; + pThis->Core.ActualCtx.aGRegs[X86_GREG_xDI] = pCtx->uc_mcontext.gregs[REG_EDI]; + pThis->Core.ActualCtx.aSRegs[X86_SREG_CS] = pCtx->uc_mcontext.gregs[REG_CS]; + pThis->Core.ActualCtx.aSRegs[X86_SREG_DS] = pCtx->uc_mcontext.gregs[REG_DS]; + pThis->Core.ActualCtx.aSRegs[X86_SREG_ES] = pCtx->uc_mcontext.gregs[REG_ES]; + pThis->Core.ActualCtx.aSRegs[X86_SREG_FS] = pCtx->uc_mcontext.gregs[REG_FS]; + pThis->Core.ActualCtx.aSRegs[X86_SREG_GS] = pCtx->uc_mcontext.gregs[REG_GS]; + pThis->Core.ActualCtx.aSRegs[X86_SREG_SS] = pCtx->uc_mcontext.gregs[REG_SS]; + pThis->Core.ActualCtx.rip = pCtx->uc_mcontext.gregs[REG_EIP]; + pThis->Core.ActualCtx.rfl = pCtx->uc_mcontext.gregs[REG_EFL]; + pThis->Core.ActualCtx.cr2 = pCtx->uc_mcontext.cr2; + pThis->Core.ActualCtx.uXcpt = pCtx->uc_mcontext.gregs[REG_TRAPNO]; + pThis->Core.ActualCtx.uErr = pCtx->uc_mcontext.gregs[REG_ERR]; + +# else +# error "Unsupported arch." +# endif + + /* Adjust uErr. */ + switch (pThis->Core.ActualCtx.uXcpt) + { + case X86_XCPT_TS: + case X86_XCPT_NP: + case X86_XCPT_SS: + case X86_XCPT_GP: + case X86_XCPT_PF: + case X86_XCPT_AC: + case X86_XCPT_DF: + break; + default: + pThis->Core.ActualCtx.uErr = UINT64_MAX; + break; + } + +# if 0 + /* Fudge the resume flag (it's probably always set here). */ + if ( (pThis->Core.ActualCtx.rfl & X86_EFL_RF) + && !(pThis->Core.ExpectedCtx.rfl & X86_EFL_RF)) + pThis->Core.ActualCtx.rfl &= ~X86_EFL_RF; +# endif + +# else + /** @todo */ +# endif + + + /* + * Check for the 'lock int3' instruction used for tricky stacks. + */ + if ( pThis->fUsingLockedInt3 + && pThis->Core.ActualCtx.uXcpt == X86_XCPT_UD + && pThis->Core.ActualCtx.rip == pThis->Core.CodeBuf.uEffBufAddr - pThis->Core.CodeBuf.offSegBase + + pThis->Core.CodeBuf.offActive + pThis->Core.CodeBuf.cbActive ) + { + pThis->Core.ActualCtx.uXcpt = UINT32_MAX; + Assert(pThis->Core.ActualCtx.uErr == UINT64_MAX); + pThis->Core.ActualCtx.rfl &= ~X86_EFL_RF; + } + + /* + * Jump back to CidetAppCbExecute. + */ + CidetAppRestoreCtx(&pThis->ExecuteCtx); +} +#endif + + + +/* + * + * Buffer handling + * Buffer handling + * Buffer handling + * + * + */ + +static int cidetAppAllocateAndConfigureOneBuffer(PCIDETAPP pThis, PCIDETAPPBUF pBuf, uint16_t idxBuf, bool fIsCode, + uint32_t fFlags) +{ + RT_NOREF_PV(pThis); + static uint8_t const s_afBufProtToDefaultMemProt[] = + { + /* [0] = */ RTMEM_PROT_NONE, + /* [1] = */ RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC, + /* [2] = */ RTMEM_PROT_READ | RTMEM_PROT_WRITE, + /* [3] = */ RTMEM_PROT_READ | RTMEM_PROT_EXEC, + /* [4] = */ RTMEM_PROT_READ, + /* [5] = */ RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC, + /* [6] = */ RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC, + /* [7] = */ RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC, + /* [8] = */ RTMEM_PROT_NONE, + /* [9] = */ RTMEM_PROT_NONE, + /* [10] = */ RTMEM_PROT_NONE, + /* [11] = */ RTMEM_PROT_NONE, + /* [12] = */ RTMEM_PROT_NONE, + /* [13] = */ RTMEM_PROT_NONE, + /* [14] = */ RTMEM_PROT_NONE, + /* [15] = */ RTMEM_PROT_NONE, + }; + static uint8_t const s_afBufProtToLastPageMemProt[] = + { + /* [0] = */ RTMEM_PROT_NONE, + /* [1] = */ RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC, + /* [2] = */ RTMEM_PROT_READ | RTMEM_PROT_WRITE, + /* [3] = */ RTMEM_PROT_READ | RTMEM_PROT_EXEC, + /* [4] = */ RTMEM_PROT_READ, + /* [5] = */ RTMEM_PROT_NONE, + /* [6] = */ RTMEM_PROT_READ | RTMEM_PROT_WRITE, + /* [7] = */ RTMEM_PROT_READ, + /* [8] = */ RTMEM_PROT_NONE, + /* [9] = */ RTMEM_PROT_NONE, + /* [10] = */ RTMEM_PROT_NONE, + /* [11] = */ RTMEM_PROT_NONE, + /* [12] = */ RTMEM_PROT_NONE, + /* [13] = */ RTMEM_PROT_NONE, + /* [14] = */ RTMEM_PROT_NONE, + /* [15] = */ RTMEM_PROT_NONE, + }; + + int rc; + Assert(CIDETBUF_IS_CODE(fFlags) == fIsCode); + pBuf->fIsCode = fIsCode; + pBuf->idxCfg = idxBuf; + pBuf->fUsingNormal = true; + pBuf->fDefaultProt = s_afBufProtToDefaultMemProt[fFlags & CIDETBUF_PROT_MASK]; + pBuf->fLastPageProt = s_afBufProtToLastPageMemProt[fFlags & CIDETBUF_PROT_MASK]; + if (pBuf->fDefaultProt != RTMEM_PROT_NONE) + { + /* + * Allocate a 3 page buffer plus two fence pages. + */ + pBuf->cb = fIsCode ? CIDET_CODE_BUF_SIZE : CIDET_DATA_BUF_SIZE; + pBuf->pbNormal = (uint8_t *)RTMemPageAlloc(PAGE_SIZE + pBuf->cb + PAGE_SIZE); + if (pBuf->pbNormal) + { + memset(pBuf->pbNormal, 0x55, PAGE_SIZE); + memset(pBuf->pbNormal + PAGE_SIZE, 0xcc, pBuf->cb); + memset(pBuf->pbNormal + PAGE_SIZE + pBuf->cb, 0x77, PAGE_SIZE); + + /* Set up fence pages. */ + rc = RTMemProtect(pBuf->pbNormal, PAGE_SIZE, RTMEM_PROT_NONE); /* fence */ + if (RT_SUCCESS(rc)) + rc = RTMemProtect(pBuf->pbNormal + PAGE_SIZE + pBuf->cb, PAGE_SIZE, RTMEM_PROT_NONE); /* fence */ + pBuf->pbNormal += PAGE_SIZE; + + /* Default protection + read + write. */ + if (RT_SUCCESS(rc)) + rc = RTMemProtect(pBuf->pbNormal, pBuf->cb, pBuf->fDefaultProt | RTMEM_PROT_READ | RTMEM_PROT_WRITE); + + /* + * Allocate a low memory buffer or LDT if necessary. + */ + if ( RT_SUCCESS(rc) + && (uintptr_t)pBuf->pbNormal + pBuf->cb > RT_BIT_64(sizeof(uintptr_t) / 2 * 8)) + { + /** @todo Buffers for the other addressing mode. */ + pBuf->pbLow = NULL; + } + else + pBuf->pbLow = pBuf->pbNormal; + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + } + else + rc = RTTestIFailedRc(VERR_NO_PAGE_MEMORY, "Error allocating three pages."); + } + else + rc = RTTestIFailedRc(VERR_NO_PAGE_MEMORY, "Unsupported buffer config: fFlags=%#x, idxBuf=%u", fFlags, idxBuf); + return rc; +} + + +static void CidetAppDeleteBuffer(PCIDETAPPBUF pBuf) +{ + RTMemProtect(pBuf->pbNormal - PAGE_SIZE, PAGE_SIZE + pBuf->cb + PAGE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + RTMemPageFree(pBuf->pbNormal - PAGE_SIZE, PAGE_SIZE + pBuf->cb + PAGE_SIZE); + if (pBuf->pbLow != pBuf->pbNormal && pBuf->pbLow) + { + RTMemProtect(pBuf->pbLow, pBuf->cb, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + RTMemFreeEx(pBuf->pbLow, pBuf->cb); + } +} + + +static bool CidetAppArmBuf(PCIDETAPP pThis, PCIDETAPPBUF pAppBuf) +{ + RT_NOREF_PV(pThis); + uint8_t *pbUsingBuf = (pAppBuf->fUsingNormal ? pAppBuf->pbNormal : pAppBuf->pbLow); + if (pAppBuf->fLastPageProt == pAppBuf->fDefaultProt) + { + if ((pAppBuf->fDefaultProt & (RTMEM_PROT_READ | RTMEM_PROT_WRITE)) != (RTMEM_PROT_READ | RTMEM_PROT_WRITE)) + RTTESTI_CHECK_RC_RET(RTMemProtect(pbUsingBuf, pAppBuf->cb, pAppBuf->fDefaultProt), VINF_SUCCESS, false); + } + else + { + if ((pAppBuf->fDefaultProt & (RTMEM_PROT_READ | RTMEM_PROT_WRITE)) != (RTMEM_PROT_READ | RTMEM_PROT_WRITE)) + RTTESTI_CHECK_RC_RET(RTMemProtect(pbUsingBuf, pAppBuf->cb - PAGE_SIZE, pAppBuf->fDefaultProt), VINF_SUCCESS, false); + RTTESTI_CHECK_RC_RET(RTMemProtect(pbUsingBuf + pAppBuf->cb - PAGE_SIZE, PAGE_SIZE, pAppBuf->fLastPageProt), + VINF_SUCCESS, false); + } + pAppBuf->fArmed = true; + return true; +} + + +static bool CidetAppDearmBuf(PCIDETAPP pThis, PCIDETAPPBUF pAppBuf) +{ + RT_NOREF_PV(pThis); + uint8_t *pbUsingBuf = (pAppBuf->fUsingNormal ? pAppBuf->pbNormal : pAppBuf->pbLow); + int rc = RTMemProtect(pbUsingBuf, pAppBuf->cb, pAppBuf->fDefaultProt | RTMEM_PROT_READ | RTMEM_PROT_WRITE); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTMemProtect failed on %s buf #%u: %Rrc", pAppBuf->fIsCode ? "code" : "data", pAppBuf->idxCfg, rc); + return false; + } + pAppBuf->fArmed = false; + return true; +} + + +/** + * @interface_method_impl{CIDETCORE,pfnReInitDataBuf} + */ +static DECLCALLBACK(bool) CidetAppCbReInitDataBuf(PCIDETCORE pThis, PCIDETBUF pBuf) +{ + PCIDETAPP pThisApp = (PCIDETAPP)pThis; + PCIDETAPPBUF pAppBuf = &pThisApp->aDataBuffers[pBuf->idxCfg]; + Assert(CIDETBUF_IS_DATA(pBuf->pCfg->fFlags)); + + /* + * De-arm the buffer. + */ + if (pAppBuf->fArmed) + if (RT_UNLIKELY(!CidetAppDearmBuf(pThisApp, pAppBuf))) + return false; + + /* + * Check the allocation requirements. + */ + if (RT_UNLIKELY((size_t)pBuf->off + pBuf->cb > pAppBuf->cb)) + { + RTTestIFailed("Buffer too small; off=%#x cb=%#x pAppBuf->cb=%#x (%s)", + pBuf->off, pBuf->cb, pAppBuf->cb, pBuf->pCfg->pszName); + return false; + } + + /* + * Do we need to use the low buffer? Check that we have one, if we need it. + */ + bool fUseNormal = pThis->cbAddrMode == ARCH_BITS / 8; + if (!fUseNormal && !pAppBuf->pbLow) + return false; + + /* + * Update the state. + */ + pAppBuf->fUsingNormal = fUseNormal; + + pBuf->offActive = pBuf->off; + pBuf->cbActive = pBuf->cb; + pBuf->cbPrologue = 0; + pBuf->cbEpilogue = 0; + pBuf->uSeg = UINT32_MAX; + pBuf->cbActiveSegLimit = UINT64_MAX; + pBuf->uSegBase = 0; + if (fUseNormal) + pBuf->uEffBufAddr = (uintptr_t)pAppBuf->pbNormal; + else + pBuf->uEffBufAddr = (uintptr_t)pAppBuf->pbLow; + + return true; +} + + +/** + * @interface_method_impl{CIDETCORE,pfnSetupDataBuf} + */ +static DECLCALLBACK(bool) CidetAppCbSetupDataBuf(PCIDETCORE pThis, PCIDETBUF pBuf, void const *pvSrc) +{ + PCIDETAPP pThisApp = (PCIDETAPP)pThis; + PCIDETAPPBUF pAppBuf = &pThisApp->aDataBuffers[pBuf->idxCfg]; + Assert(CIDETBUF_IS_DATA(pBuf->pCfg->fFlags)); + Assert(!pAppBuf->fArmed); + + + /* + * Copy over the data. + */ + uint8_t *pbUsingBuf = (pAppBuf->fUsingNormal ? pAppBuf->pbNormal : pAppBuf->pbLow); + memcpy(pbUsingBuf + pBuf->offActive, pvSrc, pBuf->cbActive); + + /* + * Arm the buffer. + */ + return CidetAppArmBuf(pThisApp, pAppBuf); +} + + +/** + * @interface_method_impl{CIDETCORE,pfnIsBufEqual} + */ +static DECLCALLBACK(bool) CidetAppCbIsBufEqual(PCIDETCORE pThis, struct CIDETBUF *pBuf, void const *pvExpected) +{ + PCIDETAPP pThisApp = (PCIDETAPP)pThis; + PCIDETAPPBUF pAppBuf = CIDETBUF_IS_CODE(pBuf->pCfg->fFlags) + ? &pThisApp->aCodeBuffers[pBuf->idxCfg] + : &pThisApp->aDataBuffers[pBuf->idxCfg]; + + /* + * Disarm the buffer if we can't read it all. + */ + if ( pAppBuf->fArmed + && ( !(pAppBuf->fLastPageProt & RTMEM_PROT_READ) + || !(pAppBuf->fDefaultProt & RTMEM_PROT_READ)) ) + if (RT_UNLIKELY(!CidetAppDearmBuf(pThisApp, pAppBuf))) + return false; + + /* + * Do the comparing. + */ + uint8_t *pbUsingBuf = (pAppBuf->fUsingNormal ? pAppBuf->pbNormal : pAppBuf->pbLow); + if (memcmp(pbUsingBuf + pBuf->offActive, pvExpected, pBuf->cbActive) != 0) + { + /** @todo RTMEM_PROT_NONE may kill content on some hosts... */ + return false; + } + + /** @todo check padding. */ + return true; +} + + +/* + * + * Code buffer, prologue, epilogue, and execution. + * Code buffer, prologue, epilogue, and execution. + * Code buffer, prologue, epilogue, and execution. + * + * + */ + + +/** + * @interface_method_impl{CIDETCORE,pfnReInitCodeBuf} + */ +static DECLCALLBACK(bool) CidetAppCbReInitCodeBuf(PCIDETCORE pThis, PCIDETBUF pBuf) +{ + PCIDETAPP pThisApp = (PCIDETAPP)pThis; + PCIDETAPPBUF pAppBuf = &pThisApp->aCodeBuffers[pBuf->idxCfg]; + Assert(CIDETBUF_IS_CODE(pBuf->pCfg->fFlags)); + Assert(pAppBuf->fUsingNormal); + + /* + * De-arm the buffer. + */ + if (pAppBuf->fArmed) + if (RT_UNLIKELY(!CidetAppDearmBuf(pThisApp, pAppBuf))) + return false; + + /* + * Determin the prologue and epilogue sizes. + */ + uint16_t cbPrologue = 0; + uint16_t cbEpilogue = ARCH_BITS == 64 ? 0x56 : 0x4e; + if (pThis->InCtx.fTrickyStack) + cbEpilogue = 16; + + /* + * Check the allocation requirements. + */ + if (RT_UNLIKELY( cbPrologue > pBuf->off + || (size_t)pBuf->off + pBuf->cb + cbEpilogue > pAppBuf->cb)) + { + RTTestIFailed("Buffer too small; off=%#x cb=%#x cbPro=%#x cbEpi=%#x pAppBuf->cb=%#x (%s)", + pBuf->off, pBuf->cb, cbPrologue, cbEpilogue, pAppBuf->cb, pBuf->pCfg->pszName); + return false; + } + + /* + * Update the state. + */ + pAppBuf->fUsingNormal = true; + + pBuf->cbActive = pBuf->cb; + pBuf->offActive = pBuf->off; + pBuf->cbPrologue = cbPrologue; + pBuf->cbEpilogue = cbEpilogue; + pBuf->uSeg = UINT32_MAX; + pBuf->cbActiveSegLimit = UINT64_MAX; + pBuf->uSegBase = 0; + pBuf->uEffBufAddr = (uintptr_t)pAppBuf->pbNormal; + + return true; +} + + +/** + * @interface_method_impl{CIDETCORE,pfnSetupCodeBuf} + */ +static DECLCALLBACK(bool) CidetAppCbSetupCodeBuf(PCIDETCORE pThis, PCIDETBUF pBuf, void const *pvInstr) +{ + PCIDETAPP pThisApp = (PCIDETAPP)pThis; + PCIDETAPPBUF pAppBuf =&pThisApp->aCodeBuffers[pBuf->idxCfg]; + Assert(CIDETBUF_IS_CODE(pBuf->pCfg->fFlags)); + Assert(pAppBuf->fUsingNormal); + Assert(!pAppBuf->fArmed); + + /* + * Emit prologue code. + */ + uint8_t *pbDst = pAppBuf->pbNormal + pBuf->offActive - pBuf->cbPrologue; + + /* + * Copy over the code. + */ + Assert(pbDst == &pAppBuf->pbNormal[pBuf->offActive]); + memcpy(pbDst, pvInstr, pBuf->cbActive); + pbDst += pBuf->cbActive; + + /* + * Emit epilogue code. + */ + if (!pThis->InCtx.fTrickyStack) + { + /* + * The stack is reasonably good, do minimal work. + * + * Note! Ideally, we would just fill in 16 int3s here and check that + * we hit the first right one. However, if we wish to run this + * code with IEM, we better skip unnecessary trips to ring-0. + */ + uint8_t * const pbStartEpilogue = pbDst; + + /* jmp $+6 */ + *pbDst++ = 0xeb; + *pbDst++ = 0x06; /* This is a push es, so if the decoder is one off, we'll hit the int 3 below. */ + + /* Six int3s for trapping incorrectly decoded instructions. */ + *pbDst++ = 0xcc; + *pbDst++ = 0xcc; + *pbDst++ = 0xcc; + *pbDst++ = 0xcc; + *pbDst++ = 0xcc; + *pbDst++ = 0xcc; + + /* push rip / call $+0 */ + *pbDst++ = 0xe8; + *pbDst++ = 0x00; + *pbDst++ = 0x00; + *pbDst++ = 0x00; + *pbDst++ = 0x00; + uint8_t offRipAdjust = (uint8_t)(uintptr_t)(pbStartEpilogue - pbDst); + + /* push xCX */ + *pbDst++ = 0x51; + + /* mov xCX, [xSP + xCB] */ + *pbDst++ = 0x48; + *pbDst++ = 0x8b; + *pbDst++ = 0x4c; + *pbDst++ = 0x24; + *pbDst++ = sizeof(uintptr_t); + + /* lea xCX, [xCX - 24] */ + *pbDst++ = 0x48; + *pbDst++ = 0x8d; + *pbDst++ = 0x49; + *pbDst++ = offRipAdjust; + + /* mov xCX, [xSP + xCB] */ + *pbDst++ = 0x48; + *pbDst++ = 0x89; + *pbDst++ = 0x4c; + *pbDst++ = 0x24; + *pbDst++ = sizeof(uintptr_t); + + /* mov xCX, &pThis->ActualCtx */ +#ifdef RT_ARCH_AMD64 + *pbDst++ = 0x48; +#endif + *pbDst++ = 0xb9; + *(uintptr_t *)pbDst = (uintptr_t)&pThisApp->Core.ActualCtx; + pbDst += sizeof(uintptr_t); + + /* pop [ss:rcx + ActualCtx.aGRegs[X86_GREG_xCX]] */ + *pbDst++ = 0x36; + *pbDst++ = 0x8f; + *pbDst++ = 0x41; + *pbDst++ = RT_UOFFSETOF(CIDETCPUCTX, aGRegs[X86_GREG_xCX]); + Assert(RT_UOFFSETOF(CIDETCPUCTX, aGRegs[X86_GREG_xCX]) < 0x7f); + + /* mov [ss:rcx + ActualCtx.aGRegs[X86_GREG_xDX]], rdx */ + *pbDst++ = 0x36; +#ifdef RT_ARCH_AMD64 + *pbDst++ = 0x48; +#endif + *pbDst++ = 0x89; + *pbDst++ = 0x51; + *pbDst++ = RT_UOFFSETOF(CIDETCPUCTX, aGRegs[X86_GREG_xDX]); + Assert(RT_UOFFSETOF(CIDETCPUCTX, aGRegs[X86_GREG_xDX]) < 0x7f); + + /* mov [ss:rcx + ActualCtx.aSRegs[X86_GREG_DS]], ds */ + *pbDst++ = 0x36; + *pbDst++ = 0x8c; + *pbDst++ = 0x99; + *(uint32_t *)pbDst = RT_UOFFSETOF(CIDETCPUCTX, aSRegs[X86_SREG_DS]); + pbDst += sizeof(uint32_t); + + /* mov edx, 0XXYYh */ + *pbDst++ = 0xba; + *(uint32_t *)pbDst = pThisApp->Core.InTemplateCtx.aSRegs[X86_SREG_DS]; + pbDst += sizeof(uint32_t); + + /* mov ds, dx */ + *pbDst++ = 0x8e; + *pbDst++ = 0xda; + + /* mov xDX, &pThisApp->ExecuteCtx */ +#ifdef RT_ARCH_AMD64 + *pbDst++ = 0x48; +#endif + *pbDst++ = 0xba; + *(uintptr_t *)pbDst = (uintptr_t)&pThisApp->ExecuteCtx; + pbDst += sizeof(uintptr_t); + +#ifdef RT_ARCH_AMD64 + /* jmp [cs:$ wrt rip] */ + *pbDst++ = 0xff; + *pbDst++ = 0x25; + *(uint32_t *)pbDst = 0; + pbDst += sizeof(uint32_t); +#else + /* jmp NAME(CidetAppSaveAndRestoreCtx) */ + *pbDst++ = 0xb9; +#endif + *(uintptr_t *)pbDst = (uintptr_t)CidetAppSaveAndRestoreCtx; + pbDst += sizeof(uintptr_t); + + /* int3 */ + *pbDst++ = 0xcc; + + pThisApp->fUsingLockedInt3 = false; + + } + else + { + /* + * Tricky stack, so just make it raise #UD after a successful run. + */ + *pbDst++ = 0xf0; /* lock prefix */ + memset(pbDst, 0xcc, 15); /* int3 */ + pbDst += 15; + + pThisApp->fUsingLockedInt3 = true; + } + + AssertMsg(pbDst == &pAppBuf->pbNormal[pBuf->offActive + pBuf->cb + pBuf->cbEpilogue], + ("cbEpilogue=%#x, actual %#x\n", pBuf->cbEpilogue, pbDst - &pAppBuf->pbNormal[pBuf->offActive + pBuf->cb])); + + /* + * Arm the buffer. + */ + return CidetAppArmBuf(pThisApp, pAppBuf); +} + + +/** + * @interface_method_impl{CIDETCORE,pfnExecute} + */ +static DECLCALLBACK(bool) CidetAppCbExecute(PCIDETCORE pThis) +{ +#if defined(RT_OS_WINDOWS) || defined(RT_OS_DARWIN) + /* Skip tricky stack because windows cannot dispatch exception if RSP/ESP is bad. */ + if (pThis->InCtx.fTrickyStack) + return false; +#endif + + g_pExecutingThis = (PCIDETAPP)pThis; +#ifdef RT_OS_WINDOWS + __try + { + CidetAppExecute(&((PCIDETAPP)pThis)->ExecuteCtx, &pThis->InCtx); + } + __except (CidetAppXcptFilter(GetExceptionInformation())) + { + /* Won't end up here... */ + } + g_pExecutingThis = NULL; +#else + CidetAppExecute(&((PCIDETAPP)pThis)->ExecuteCtx, &pThis->InCtx); + if (g_pExecutingThis) + g_pExecutingThis = NULL; + else + { + RTTESTI_CHECK_RC(sigprocmask(SIG_SETMASK, &g_ProcSigMask, NULL), 0); + RTTESTI_CHECK_RC(sigaltstack(&g_AltStack, NULL), 0); + } +#endif + + return true; +} + + + + +/* + * + * + * CIDET Application. + * CIDET Application. + * CIDET Application. + * + * + */ + + +/** + * @interface_method_impl{CIDETCORE,pfnFailure} + */ +static DECLCALLBACK(void) CidetAppCbFailureV(PCIDETCORE pThis, const char *pszFormat, va_list va) +{ + RT_NOREF_PV(pThis); + RTTestIFailedV(pszFormat, va); +} + + +static int cidetAppAllocateAndConfigureBuffers(PCIDETAPP pThis) +{ + /* + * Code buffers. + */ + for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCodeBuffers); i++) + { + int rc = cidetAppAllocateAndConfigureOneBuffer(pThis, &pThis->aCodeBuffers[i], i, true /*fCode*/, + g_aCodeBufCfgs[i].fFlags); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Data buffers. + */ + for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aDataBuffers); i++) + { + int rc = cidetAppAllocateAndConfigureOneBuffer(pThis, &pThis->aDataBuffers[i], i, false /*fCode*/, + g_aDataBufCfgs[i].fFlags); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Stack. + */ + pThis->cbStack = _32K; + pThis->pbStackLow = (uint8_t *)RTMemPageAlloc(pThis->cbStack); + if (!pThis->pbStackLow) + { + RTTestIFailed("Failed to allocate %u bytes for stack\n", pThis->cbStack); + return false; + } + pThis->pbStackEnd = pThis->pbStackLow + pThis->cbStack; + + return true; +} + + +static int CidetAppCreate(PPCIDETAPP ppThis) +{ + *ppThis = NULL; + + PCIDETAPP pThis = (PCIDETAPP)RTMemAlloc(sizeof(*pThis)); + if (!pThis) + return RTTestIFailedRc(VERR_NO_MEMORY, "Error allocating %zu bytes.", sizeof(*pThis)); + + /* Create a random source. */ + RTRAND hRand; + int rc = RTRandAdvCreateParkMiller(&hRand); + if (RT_SUCCESS(rc)) + { + uint64_t uSeed = ASMReadTSC(); + rc = RTRandAdvSeed(hRand, uSeed); + if (RT_SUCCESS(rc)) + RTTestIPrintf(RTTESTLVL_ALWAYS, "Random seed %#llx\n", uSeed); + + /* Initialize the CIDET structure. */ + rc = CidetCoreInit(&pThis->Core, hRand); + if (RT_SUCCESS(rc)) + { + pThis->Core.pfnReInitDataBuf = CidetAppCbReInitDataBuf; + pThis->Core.pfnSetupDataBuf = CidetAppCbSetupDataBuf; + pThis->Core.pfnIsBufEqual = CidetAppCbIsBufEqual; + pThis->Core.pfnReInitCodeBuf = CidetAppCbReInitCodeBuf; + pThis->Core.pfnSetupCodeBuf = CidetAppCbSetupCodeBuf; + pThis->Core.pfnExecute = CidetAppCbExecute; + pThis->Core.pfnFailure = CidetAppCbFailureV; + + pThis->Core.paCodeBufConfigs = g_aCodeBufCfgs; + pThis->Core.cCodeBufConfigs = CIDETAPP_CODE_BUF_COUNT; + pThis->Core.paDataBufConfigs = g_aDataBufCfgs; + pThis->Core.cDataBufConfigs = CIDETAPP_DATA_BUF_COUNT; + + rc = cidetAppAllocateAndConfigureBuffers(pThis); + if (RT_SUCCESS(rc)) + { + rc = CidetCoreSetTargetMode(&pThis->Core, ARCH_BITS == 32 ? CIDETMODE_PP_32 : CIDETMODE_LM_64); + if (RT_SUCCESS(rc)) + { + pThis->Core.InTemplateCtx.aSRegs[X86_SREG_CS] = ASMGetCS(); + pThis->Core.InTemplateCtx.aSRegs[X86_SREG_DS] = ASMGetDS(); + pThis->Core.InTemplateCtx.aSRegs[X86_SREG_ES] = ASMGetES(); + pThis->Core.InTemplateCtx.aSRegs[X86_SREG_FS] = ASMGetFS(); + pThis->Core.InTemplateCtx.aSRegs[X86_SREG_GS] = ASMGetGS(); + pThis->Core.InTemplateCtx.aSRegs[X86_SREG_SS] = ASMGetSS(); + pThis->Core.InTemplateCtx.aGRegs[X86_GREG_xSP] = (uintptr_t)pThis->pbStackEnd - 64; + + pThis->Core.fTestCfg |= CIDET_TESTCFG_SEG_PRF_CS; + pThis->Core.fTestCfg |= CIDET_TESTCFG_SEG_PRF_DS; + pThis->Core.fTestCfg |= CIDET_TESTCFG_SEG_PRF_ES; +#if !defined(RT_OS_WINDOWS) + pThis->Core.fTestCfg |= CIDET_TESTCFG_SEG_PRF_FS; +#endif +#if !defined(CIDET_LEAVE_GS_ALONE) + pThis->Core.fTestCfg |= CIDET_TESTCFG_SEG_PRF_GS; +#endif + + *ppThis = pThis; + return VINF_SUCCESS; + } + rc = RTTestIFailedRc(rc, "Error setting target mode: %Rrc", rc); + } + CidetCoreDelete(&pThis->Core); + } + else + { + rc = RTTestIFailedRc(rc, "CidetCoreInit failed: %Rrc", rc); + RTRandAdvDestroy(hRand); + } + } + else + rc = RTTestIFailedRc(rc, "RTRandAdvCreate failed: %Rrc", rc); + RTMemFree(pThis); + return rc; +} + + +static void CidetAppDestroy(PCIDETAPP pThis) +{ + CidetCoreDelete(&pThis->Core); + + for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCodeBuffers); i++) + CidetAppDeleteBuffer(&pThis->aCodeBuffers[i]); + for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aDataBuffers); i++) + CidetAppDeleteBuffer(&pThis->aDataBuffers[i]); + RTMemPageFree(pThis->pbStackLow, pThis->cbStack); + + RTMemFree(pThis); +} + + +static void CidetAppTestBunch(PCIDETAPP pThis, PCCIDETINSTR paInstructions, uint32_t cInstructions, const char *pszBunchName) +{ + for (uint32_t iInstr = 0; iInstr < cInstructions; iInstr++) + { + RTTestSubF(g_hTest, "%s - %s", pszBunchName, paInstructions[iInstr].pszMnemonic); + CidetCoreTestInstruction(&pThis->Core, &paInstructions[iInstr]); + } +} + + +int main(int argc, char **argv) +{ + /* + * Initialize the runtime. + */ + RTEXITCODE rcExit = RTTestInitExAndCreate(argc, &argv, 0, "cidet-app", &g_hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + /* + * Parse arguments. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--noop", 'n', RTGETOPT_REQ_NOTHING }, + }; + + int chOpt; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0); + while ((chOpt = RTGetOpt(&GetState, &ValueUnion))) + { + switch (chOpt) + { + case 'n': + break; + + case 'h': + RTPrintf("usage: %s\n", argv[0]); + return RTEXITCODE_SUCCESS; + + case 'V': + RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision()); + return RTEXITCODE_SUCCESS; + + default: + return RTGetOptPrintError(chOpt, &ValueUnion); + } + } + +#ifdef USE_SIGNALS + /* + * Set up signal handlers with alternate stack. + */ + /* Get the default signal mask. */ + RTTESTI_CHECK_RC_RET(sigprocmask(SIG_BLOCK, NULL, &g_ProcSigMask), 0, RTEXITCODE_FAILURE); + + /* Alternative stack so we can play with esp/rsp. */ + RT_ZERO(g_AltStack); + g_AltStack.ss_flags = 0; +# ifdef SIGSTKSZ + g_AltStack.ss_size = RT_MAX(SIGSTKSZ, _128K); +# else + g_AltStack.ss_size = _128K; +# endif +#ifdef RT_OS_FREEBSD + g_AltStack.ss_sp = (char *)RTMemPageAlloc(g_AltStack.ss_size); +#else + g_AltStack.ss_sp = RTMemPageAlloc(g_AltStack.ss_size); +#endif + RTTESTI_CHECK_RET(g_AltStack.ss_sp != NULL, RTEXITCODE_FAILURE); + RTTESTI_CHECK_RC_RET(sigaltstack(&g_AltStack, NULL), 0, RTEXITCODE_FAILURE); + + /* Default signal action config. */ + struct sigaction Act; + RT_ZERO(Act); + Act.sa_sigaction = CidetAppSigHandler; + Act.sa_flags = SA_SIGINFO | SA_ONSTACK; + sigfillset(&Act.sa_mask); + + /* Hook the signals we might need. */ + sigaction(SIGILL, &Act, NULL); + sigaction(SIGTRAP, &Act, NULL); +# ifdef SIGEMT + sigaction(SIGEMT, &Act, NULL); +# endif + sigaction(SIGFPE, &Act, NULL); + sigaction(SIGBUS, &Act, NULL); + sigaction(SIGSEGV, &Act, NULL); + +#elif defined(RT_OS_WINDOWS) + /* + * Register vectored exception handler and override the default unhandled + * exception filter, just to be on the safe side... + */ + RTTESTI_CHECK(AddVectoredExceptionHandler(1 /* first */, CidetAppVectoredXcptHandler) != NULL); + SetUnhandledExceptionFilter(CidetAppUnhandledXcptFilter); +#endif + + /* + * Do the work. + */ + RTTestBanner(g_hTest); + + PCIDETAPP pThis; + int rc = CidetAppCreate(&pThis); + if (RT_SUCCESS(rc)) + { + CidetAppTestBunch(pThis, g_aCidetInstructions1, g_cCidetInstructions1, "First Bunch"); + + CidetAppDestroy(pThis); + } + + return RTTestSummaryAndDestroy(g_hTest); +} + diff --git a/src/VBox/ValidationKit/utils/cpu/cidet-appA.asm b/src/VBox/ValidationKit/utils/cpu/cidet-appA.asm new file mode 100644 index 00000000..0cea0284 --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/cidet-appA.asm @@ -0,0 +1,319 @@ +; $Id: cidet-appA.asm $ +;; @file +; CPU Instruction Decoding & Execution Tests - Ring-3 Driver Application, Assembly Code. +; + +; +; Copyright (C) 2009-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" +%include "cidet.mac" + + +;******************************************************************************* +;* Global Variables * +;******************************************************************************* +%ifdef RT_ARCH_X86 +;; Used by CidetAppSaveAndRestoreCtx when we have a tricky target stack. +g_uTargetEip dd 0 +g_uTargetCs dw 0 +%endif + + +;; +; Leave GS alone on 64-bit darwin (gs is 0, no ldt or gdt entry to load that'll +; restore the lower 32-bits of the base when saving and restoring the register). +%ifdef RT_OS_DARWIN + %ifdef RT_ARCH_AMD64 + %define CIDET_LEAVE_GS_ALONE + %endif +%endif + + + +BEGINCODE + +;; +; ASSUMES that it's called and the EIP/RIP is found on the stack. +; +; @param pSaveCtx ds:xCX The context to save; DS, xDX and xCX have +; already been saved by the caller. +; @param pRestoreCtx ds:xDX The context to restore. +; +BEGINPROC CidetAppSaveAndRestoreCtx + ; + ; Save the stack pointer and program counter first so we can later + ; bypass this step if we need to. + ; + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xAX * 8], xAX ; need scratch register. + lea xAX, [xSP + xCB] + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xSP * 8], xAX + mov word [xCX + CIDETCPUCTX.aSRegs + X86_SREG_SS * 2], ss + mov word [xCX + CIDETCPUCTX.aSRegs + X86_SREG_CS * 2], cs + mov xAX, [xSP] + mov [xCX + CIDETCPUCTX.rip], xAX + jmp CidetAppSaveAndRestoreCtx_1 + +GLOBALNAME CidetAppSaveAndRestoreCtx_NoSsSpCsIp + mov [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xAX * 8], xAX +CidetAppSaveAndRestoreCtx_1: + + ; Flags. +%ifdef RT_ARCH_AMD64 + pushfq +%else + pushfd +%endif + pop xAX + mov [xCX + CIDETCPUCTX.rfl], xAX + + ; Segment registers. + mov word [xCX + CIDETCPUCTX.aSRegs + X86_SREG_ES * 2], es + mov word [xCX + CIDETCPUCTX.aSRegs + X86_SREG_FS * 2], fs + mov word [xCX + CIDETCPUCTX.aSRegs + X86_SREG_GS * 2], gs + + ; Remaining GPRs. + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xBX * 8], xBX + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xBP * 8], xBP + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xSI * 8], xSI + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xDI * 8], xDI +%ifdef RT_ARCH_AMD64 + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x8 * 8], r8 + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x9 * 8], r9 + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x10 * 8], r10 + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x11 * 8], r11 + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x12 * 8], r12 + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x13 * 8], r13 + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x14 * 8], r14 + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x15 * 8], r15 + xor eax, eax + mov [xCX + CIDETCPUCTX.cr2], rax + %ifndef CIDET_REDUCED_CTX + mov [xCX + CIDETCPUCTX.cr0], rax + mov [xCX + CIDETCPUCTX.cr3], rax + mov [xCX + CIDETCPUCTX.cr4], rax + mov [xCX + CIDETCPUCTX.cr8], rax + mov [xCX + CIDETCPUCTX.dr0], rax + mov [xCX + CIDETCPUCTX.dr1], rax + mov [xCX + CIDETCPUCTX.dr2], rax + mov [xCX + CIDETCPUCTX.dr3], rax + mov [xCX + CIDETCPUCTX.dr6], rax + mov [xCX + CIDETCPUCTX.dr7], rax + mov [xCX + CIDETCPUCTX.tr], ax + mov [xCX + CIDETCPUCTX.ldtr], ax + %endif +%else + xor eax, eax + mov [xCX + CIDETCPUCTX.rfl + 4], eax + mov [xCX + CIDETCPUCTX.rip + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xAX * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xCX * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xDX * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xBX * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xSP * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xBP * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xSI * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xDI * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x8 * 8 ], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x8 * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x9 * 8 ], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x9 * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x10 * 8 ], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x10 * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x11 * 8 ], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x11 * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x12 * 8 ], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x12 * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x13 * 8 ], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x13 * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x14 * 8 ], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x14 * 8 + 4], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x15 * 8 ], eax + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_x15 * 8 + 4], eax + mov [xCX + CIDETCPUCTX.cr2 ], eax + mov [xCX + CIDETCPUCTX.cr2 + 4], eax + %ifndef CIDET_REDUCED_CTX + mov [xCX + CIDETCPUCTX.cr0 ], eax + mov [xCX + CIDETCPUCTX.cr0 + 4], eax + mov [xCX + CIDETCPUCTX.cr3 ], eax + mov [xCX + CIDETCPUCTX.cr3 + 4], eax + mov [xCX + CIDETCPUCTX.cr4 ], eax + mov [xCX + CIDETCPUCTX.cr4 + 4], eax + mov [xCX + CIDETCPUCTX.cr8 ], eax + mov [xCX + CIDETCPUCTX.cr8 + 4], eax + mov [xCX + CIDETCPUCTX.dr0 ], eax + mov [xCX + CIDETCPUCTX.dr0 + 4], eax + mov [xCX + CIDETCPUCTX.dr1 ], eax + mov [xCX + CIDETCPUCTX.dr1 + 4], eax + mov [xCX + CIDETCPUCTX.dr2 ], eax + mov [xCX + CIDETCPUCTX.dr2 + 4], eax + mov [xCX + CIDETCPUCTX.dr3 ], eax + mov [xCX + CIDETCPUCTX.dr3 + 4], eax + mov [xCX + CIDETCPUCTX.dr6 ], eax + mov [xCX + CIDETCPUCTX.dr6 + 4], eax + mov [xCX + CIDETCPUCTX.dr7 ], eax + mov [xCX + CIDETCPUCTX.dr7 + 4], eax + mov [xCX + CIDETCPUCTX.tr], ax + mov [xCX + CIDETCPUCTX.ldtr], ax + %endif +%endif + dec xAX + mov [xCX + CIDETCPUCTX.uErr], xAX +%ifdef RT_ARCH_X86 + mov [xCX + CIDETCPUCTX.uErr + 4], eax +%endif + mov [xCX + CIDETCPUCTX.uXcpt], eax + + ; + ; Restore the other state (pointer in xDX). + ; +NAME(CidetAppSaveAndRestoreCtx_Restore): + + ; Restore ES, FS, and GS. + mov es, [xDX + CIDETCPUCTX.aSRegs + X86_SREG_ES * 2] + mov fs, [xDX + CIDETCPUCTX.aSRegs + X86_SREG_FS * 2] +%ifndef CIDET_LEAVE_GS_ALONE + mov gs, [xDX + CIDETCPUCTX.aSRegs + X86_SREG_GS * 2] +%endif + + ; Restore most GPRs (except xCX, xAX and xSP). + mov xCX, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xCX * 8] + mov xBX, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xBX * 8] + mov xBP, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xBP * 8] + mov xSI, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xSI * 8] + mov xDI, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xDI * 8] +%ifdef RT_ARCH_AMD64 + mov r8, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_x8 * 8] + mov r9, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_x9 * 8] + mov r10, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_x10 * 8] + mov r11, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_x11 * 8] + mov r12, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_x12 * 8] + mov r13, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_x13 * 8] + mov r14, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_x14 * 8] + mov r15, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_x15 * 8] +%endif + +%ifdef RT_ARCH_AMD64 + ; Create an iret frame which restores SS:RSP, RFLAGS, and CS:RIP. + movzx eax, word [xDX + CIDETCPUCTX.aSRegs + X86_SREG_SS * 2] + push xAX + push qword [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xSP * 8] + push qword [xDX + CIDETCPUCTX.rfl] + movzx eax, word [xDX + CIDETCPUCTX.aSRegs + X86_SREG_CS * 2] + push xAX + push qword [xDX + CIDETCPUCTX.rip] + + ; Restore DS, xAX and xDX then do the iret. + mov ds, [xDX + CIDETCPUCTX.aSRegs + X86_SREG_DS * 2] + mov xAX, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xAX * 8] + mov xDX, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xDX * 8] + iretq +%else + ; In 32-bit mode iret doesn't restore CS:ESP for us, so we have to + ; make a choice whether the SS:ESP is more important than EFLAGS. + cmp byte [xDX + CIDETCPUCTX.fTrickyStack], 0 + jne .tricky_stack + + mov ss, [xDX + CIDETCPUCTX.aSRegs + X86_SREG_SS * 2] + mov xSP, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xSP * 8] + + push dword [xDX + CIDETCPUCTX.rfl] ; iret frame + movzx eax, word [xDX + CIDETCPUCTX.aSRegs + X86_SREG_CS * 2] ; iret frame + push xAX ; iret frame + push dword [xDX + CIDETCPUCTX.rip] ; iret frame + + mov xAX, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xAX * 8] + mov ds, [xDX + CIDETCPUCTX.aSRegs + X86_SREG_DS * 2] + mov xDX, [cs:xDX + CIDETCPUCTX.aGRegs + X86_GREG_xDX * 8] + iretd + +.tricky_stack: + mov xAX, [xDX + CIDETCPUCTX.rip] + mov [g_uTargetEip], xAX + mov ax, [xDX + CIDETCPUCTX.aSRegs + X86_SREG_CS * 2] + mov [g_uTargetCs], ax + push dword [xDX + CIDETCPUCTX.rfl] + popfd + mov ss, [xDX + CIDETCPUCTX.aSRegs + X86_SREG_SS * 2] + mov xSP, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xSP * 8] + mov xAX, [xDX + CIDETCPUCTX.aGRegs + X86_GREG_xAX * 8] + mov ds, [xDX + CIDETCPUCTX.aSRegs + X86_SREG_DS * 2] + mov xDX, [cs:xDX + CIDETCPUCTX.aGRegs + X86_GREG_xDX * 8] + jmp far [cs:g_uTargetEip] +%endif +ENDPROC CidetAppSaveAndRestoreCtx + + +;; +; C callable version of CidetAppSaveAndRestoreCtx more or less. +; +; @param pSaveCtx x86:esp+4 gcc:rdi msc:rcx +; @param pRestoreCtx x86:esp+8 gcc:rsi msc:rdx +BEGINPROC CidetAppExecute +%ifdef RT_ARCH_X86 + mov ecx, [esp + 4] + mov edx, [esp + 8] +%elifdef ASM_CALL64_GCC + mov rcx, rdi + mov rdx, rsi +%elifndef ASM_CALL64_MSC + %error "unsupport arch." +%endif + mov word [xCX + CIDETCPUCTX.aSRegs + X86_SREG_DS * 2], ds + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xDX * 8], xDX + mov [xCX + CIDETCPUCTX.aGRegs + X86_GREG_xCX * 8], xCX + jmp NAME(CidetAppSaveAndRestoreCtx) +ENDPROC CidetAppExecute + + +;; +; C callable restore function. +; +; @param pRestoreCtx x86:esp+4 gcc:rdi msc:rcx +BEGINPROC CidetAppRestoreCtx +%ifdef RT_ARCH_X86 + mov edx, [esp + 4] +%elifdef ASM_CALL64_GCC + mov rdx, rdi +%elifdef ASM_CALL64_MSC + mov rdx, rcx +%else + %error "unsupport arch." +%endif + mov ds, [cs:xDX + CIDETCPUCTX.aSRegs + X86_SREG_DS * 2] + jmp NAME(CidetAppSaveAndRestoreCtx_Restore) +ENDPROC CidetAppRestoreCtx + diff --git a/src/VBox/ValidationKit/utils/cpu/cidet-core.cpp b/src/VBox/ValidationKit/utils/cpu/cidet-core.cpp new file mode 100644 index 00000000..59d4faf8 --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/cidet-core.cpp @@ -0,0 +1,2368 @@ +/* $Id: cidet-core.cpp $ */ +/** @file + * CPU Instruction Decoding & Execution Tests - Simple Instructions. + */ + +/* + * Copyright (C) 2014-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define CIDET_INSTR_TEST_OP_FLAG(a_pInstr, a_fFlag) \ + ( ((a_pInstr)->afOperands[0] & (a_fFlag)) \ + || ((a_pInstr)->afOperands[1] & (a_fFlag)) \ + || ( (a_pInstr)->cOperands > 2 \ + && ( ((a_pInstr)->afOperands[2] & (a_fFlag)) \ + || ((a_pInstr)->afOperands[3] & (a_fFlag)) ) ) ) + +#define CIDET_INSTR_TEST_OP_MASK_VALUE(a_pInstr, a_fMask, a_fValue) \ + ( ((a_pInstr)->afOperands[0] & (a_fMask)) == (a_fValue) \ + || ((a_pInstr)->afOperands[1] & (a_fMask)) == (a_fValue) \ + || ( (a_pInstr)->cOperands > 2 \ + && ( ((a_pInstr)->afOperands[2] & (a_fMask)) == (a_fValue) \ + || ((a_pInstr)->afOperands[3] & (a_fMask)) == (a_fValue) ) ) ) + +/** @def CIDET_DPRINTF + * Debug printf. */ +#if 1 //def DEBUG_bird +# define CIDET_DPRINTF(a) do { RTPrintf a; } while (0) +# define CIDET_DPRINTF_ENABLED +#else +# define CIDET_DPRINTF(a) do { } while (0) +#endif + +/** @def CIDET_DEBUG_DISAS + * Enables instruction disassembly. */ +#if defined(DOXYGEN_RUNNING) +# define CIDET_DEBUG_DISAS 1 +#endif + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "cidet.h" + +#include <iprt/assert.h> +#include <iprt/rand.h> +#include <iprt/param.h> +#include <iprt/string.h> +#include <iprt/errcore.h> +#if defined(CIDET_DPRINTF_ENABLED) || defined(CIDET_DEBUG_DISAS) +# include <VBox/dis.h> +# include <iprt/stream.h> +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** For translating CIDET_OF_Z_XXX values (after shifting). */ +uint16_t const g_acbCidetOfSizes[] = +{ + /* [CIDET_OF_Z_NONE] = */ 0, + /* [CIDET_OF_Z_BYTE] = */ 1, + /* [CIDET_OF_Z_WORD] = */ 2, + /* [CIDET_OF_Z_DWORD] = */ 4, + /* [CIDET_OF_Z_QWORD] = */ 8, + /* [CIDET_OF_Z_TBYTE] = */ 10, + /* [CIDET_OF_Z_OWORD] = */ 16, + /* [CIDET_OF_Z_YWORD] = */ 32, + /* [CIDET_OF_Z_ZWORD] = */ 64, + /* [CIDET_OF_Z_VAR_WDQ] = */ UINT16_MAX, + /* [0xa] = */ 0, + /* [0xb] = */ 0, + /* [0xc] = */ 0, + /* [0xd] = */ 0, + /* [0xe] = */ 0, + /* [CIDET_OF_Z_SPECIAL] = */ UINT16_MAX - 1, +}; + + +/** Converts operand sizes in bytes to 64-bit masks. */ +static const uint64_t g_au64ByteSizeToMask[] = +{ + UINT64_C(0x0000000000000000), + UINT64_C(0x00000000000000ff), + UINT64_C(0x000000000000ffff), + UINT64_C(0x0000000000ffffff), + UINT64_C(0x00000000ffffffff), + UINT64_C(0x000000ffffffffff), + UINT64_C(0x0000ffffffffffff), + UINT64_C(0x00ffffffffffffff), + UINT64_C(0xffffffffffffffff), +}; + +/** Converts operand sizes in bytes to 64-bit signed max values. */ +static const int64_t g_ai64ByteSizeToMax[] = +{ + INT64_C(0x0000000000000000), + INT64_C(0x000000000000007f), + INT64_C(0x0000000000007fff), + INT64_C(0x00000000007fffff), + INT64_C(0x000000007fffffff), + INT64_C(0x0000007fffffffff), + INT64_C(0x00007fffffffffff), + INT64_C(0x007fffffffffffff), + INT64_C(0x7fffffffffffffff), +}; + + +bool CidetInstrHasMrmMemOperand(PCCIDETINSTR pInstr) +{ + return CIDET_INSTR_TEST_OP_FLAG(pInstr, CIDET_OF_M_RM_ONLY_M); +} + + +bool CidetInstrHasMrmRegOperand(PCCIDETINSTR pInstr) +{ + return CIDET_INSTR_TEST_OP_FLAG(pInstr, CIDET_OF_M_RM_ONLY_R); +} + + +bool CidetInstrRespondsToOperandSizePrefixes(PCCIDETINSTR pInstr) +{ + return CIDET_INSTR_TEST_OP_MASK_VALUE(pInstr, CIDET_OF_Z_MASK, CIDET_OF_Z_VAR_WDQ); +} + + + + +int CidetCoreInit(PCIDETCORE pThis, RTRAND hRand) +{ + AssertPtr(pThis); + AssertPtr(hRand); + + RT_ZERO(*pThis); + pThis->u32Magic = CIDETCORE_MAGIC; + pThis->hRand = hRand; + return VINF_SUCCESS; +} + + +void CidetCoreDelete(PCIDETCORE pThis) +{ + AssertPtr(pThis); Assert(pThis->u32Magic == CIDETCORE_MAGIC); + + RTRandAdvDestroy(pThis->hRand); + RT_ZERO(*pThis); +} + + +/** + * Report a test failure via CIDET::pfnFailure + * + * @returns false + * @param pThis Pointer to the core structure. + * @param pszFormat Format string containing failure details. + * @param va Arguments referenced in @a pszFormat. + */ +int CidetCoreSetErrorV(PCIDETCORE pThis, const char *pszFormat, va_list va) +{ + pThis->pfnFailure(pThis, pszFormat, va); + return false; +} + + +/** + * Report a test failure via CIDET::pfnFailure + * + * @returns false + * @param pThis Pointer to the core structure. + * @param pszFormat Format string containing failure details. + * @param ... Arguments referenced in @a pszFormat. + */ +bool CidetCoreSetError(PCIDETCORE pThis, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + CidetCoreSetErrorV(pThis, pszFormat, va); + va_end(va); + return false; +} + + +/** + * Get a signed random number, with a given number of significant bytes. + * + * @returns Random number. + * @param pThis Pointer to the core structure. + * @param cbSignificant The number of significant bytes. + */ +int64_t CidetCoreGetRandS64(PCIDETCORE pThis, uint8_t cbSignificant) +{ + int64_t iVal = RTRandAdvS64(pThis->hRand); + switch (cbSignificant) + { + case 1: return (int8_t)iVal; + case 2: return (int16_t)iVal; + case 4: return (int32_t)iVal; + case 8: return iVal; + default: + AssertReleaseFailed(); + return iVal; + } +} + + +/** + * Get an unsigned random number, with a given number of significant bytes. + * + * @returns Random number. + * @param pThis Pointer to the core structure. + * @param cbSignificant The number of significant bytes. + */ +uint64_t CidetCoreGetRandU64(PCIDETCORE pThis, uint8_t cbSignificant) +{ + Assert(cbSignificant == 1 || cbSignificant == 2 || cbSignificant == 4 || cbSignificant == 8); + + uint64_t uVal = RTRandAdvU64(pThis->hRand); + uVal &= g_au64ByteSizeToMask[cbSignificant]; + + return uVal; +} + + + +void CidetCoreInitializeCtxTemplate(PCIDETCORE pThis) +{ + pThis->InTemplateCtx.rip = UINT64_MAX; + pThis->InTemplateCtx.rfl = X86_EFL_1 | X86_EFL_ID | X86_EFL_IF; + + unsigned i = RT_ELEMENTS(pThis->InTemplateCtx.aGRegs); + if (CIDETMODE_IS_LM(pThis->bMode)) + while (i-- > 0) + pThis->InTemplateCtx.aGRegs[i] = UINT64_C(0x3fefcc00daba005d) + | ((uint64_t)i << 32) + | ((uint32_t)i << 8); + else + while (i-- > 0) + pThis->InTemplateCtx.aGRegs[i] = UINT64_C(0xfada009b) + | ((uint32_t)i << 12) + | ((uint32_t)i << 8); + i = RT_ELEMENTS(pThis->InTemplateCtx.aSRegs); + while (i-- > 0) + pThis->InTemplateCtx.aSRegs[i] = 0; /* Front end sets these afterwards. */ + pThis->InTemplateCtx.cr2 = 0; +#ifndef CIDET_REDUCED_CTX + pThis->InTemplateCtx.tr = 0; + pThis->InTemplateCtx.ldtr = 0; + pThis->InTemplateCtx.cr0 = 0; + pThis->InTemplateCtx.cr3 = 0; + pThis->InTemplateCtx.cr4 = 0; + pThis->InTemplateCtx.cr8 = 0; +#endif + pThis->InTemplateCtx.fIgnoredRFlags = 0; + pThis->InTemplateCtx.uXcpt = UINT32_MAX; + pThis->InTemplateCtx.uErr = UINT64_MAX; + pThis->InTemplateCtx.fTrickyStack = false; +} + + +/** + * Sets the target mode. + * + * Caller must set up default selector values after calling this function. + * + * @returns VBox status code. + * @param pThis Pointer to the core structure. + * @param bMode The new mode. + */ +int CidetCoreSetTargetMode(PCIDETCORE pThis, uint8_t bMode) +{ + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); AssertReturn(pThis->u32Magic == CIDETCORE_MAGIC, VERR_INVALID_HANDLE); + switch (bMode) + { + //case CIDETMODE_RM: + //case CIDETMODE_PE_16: + //case CIDETMODE_PE_32: + //case CIDETMODE_PE_V86: + //case CIDETMODE_PP_16: + case CIDETMODE_PP_32: + //case CIDETMODE_PP_V86: + //case CIDETMODE_PAE_16: + case CIDETMODE_PAE_32: + //case CIDETMODE_PAE_V86: + //case CIDETMODE_LM_S16: + //case CIDETMODE_LM_32: + case CIDETMODE_LM_64: + break; + default: + return VERR_NOT_IMPLEMENTED; + } + pThis->bMode = bMode; + CidetCoreInitializeCtxTemplate(pThis); + return VINF_SUCCESS; +} + + +bool CidetCoreIsEncodingCompatibleWithInstruction(PCIDETCORE pThis) +{ + RT_NOREF_PV(pThis); + return true; +} + + +/** + * Selects the next address size mode. + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + */ +static bool cidetCoreSetupNextBaseEncoding_AddressSize(PCIDETCORE pThis) +{ + if (pThis->fAddrSizePrf) + { + /* + * Reset to default. + */ + pThis->cbAddrMode = CIDETMODE_GET_BYTE_COUNT(pThis->bMode); + pThis->fAddrSizePrf = false; + } + else + { + /* + * The other addressing size. + */ + if (CIDETMODE_IS_64BIT(pThis->bMode)) + pThis->cbAddrMode = 4; + else if (CIDETMODE_IS_32BIT(pThis->bMode)) + pThis->cbAddrMode = 2; + else + { + AssertRelease(CIDETMODE_IS_16BIT(pThis->bMode)); + pThis->cbAddrMode = 2; + } + pThis->fAddrSizePrf = true; + } + return pThis->fAddrSizePrf; +} + + +/** + * Selects the first REG encoding. + * + * @param pThis The core state structure. + */ +static void cidetCoreSetupFirstBaseEncoding_MrmReg(PCIDETCORE pThis) +{ + pThis->aOperands[pThis->idxMrmRegOp].iReg = 0; + pThis->aOperands[pThis->idxMrmRegOp].fIsMem = false; + pThis->aOperands[pThis->idxMrmRegOp].fIsRipRelative = false; + pThis->aOperands[pThis->idxMrmRegOp].fIsHighByteRegister = false; + pThis->aOperands[pThis->idxMrmRegOp].cbMemDisp = 0; + pThis->aOperands[pThis->idxMrmRegOp].iMemBaseReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRegOp].iMemIndexReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRegOp].uMemScale = 1; + pThis->aOperands[pThis->idxMrmRegOp].iEffSeg = UINT8_MAX; + pThis->bModRm &= ~X86_MODRM_REG_MASK; + pThis->fRexR = false; +} + + +/** + * Selects the next REG (ModR/M) encoding. + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + * @param iReg The value of MODRM.REG /w REX.R applied. + */ +static bool cidetCoreSetupNextBaseEncoding_MrmReg(PCIDETCORE pThis, uint8_t iReg) +{ + Assert(pThis->idxMrmRegOp < RT_ELEMENTS(pThis->aOperands) && !pThis->aOperands[pThis->idxMrmRegOp].fIsMem); + Assert(iReg < 16); + + /* + * Clear the collision flags here because of the byte register kludge. + */ + pThis->fHasRegCollisionDirect = false; + pThis->fHasRegCollisionMemBase = false; + pThis->fHasRegCollisionMemIndex = false; + pThis->fHasRegCollisionMem = false; + + /* + * Clear the REX prefix and high byte register tracking too. ASSUMES MrmReg is after MrmRmMod. + */ + Assert(!pThis->fNoRexPrefixMrmRm); + Assert(!pThis->fHasHighByteRegInMrmRm); + pThis->fNoRexPrefixMrmReg = false; + pThis->fNoRexPrefix = false; + pThis->fHasHighByteRegInMrmReg = false; + pThis->aOperands[pThis->idxMrmRegOp].fIsHighByteRegister = false; + + /* + * Special kludge for ah, ch, dh, bh, spl, bpl, sil, and dil. + * Needs extra care in 64-bit mode and special collision detection code. + */ + CIDET_DPRINTF(("aOperands[%u].cb=%u fGpr=%u iReg=%d fRex=%d fRexW=%u fRexX=%u fRexB=%u fRexR=%d\n", + pThis->idxMrmRegOp, pThis->aOperands[pThis->idxMrmRegOp].cb, CIDET_OF_K_IS_GPR(pThis->fMrmRegOp), iReg, + pThis->fRex, pThis->fRexW, pThis->fRexX, pThis->fRexB, pThis->fRexR)); + if ( pThis->aOperands[pThis->idxMrmRegOp].cb == 1 + && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp) + && iReg >= 3 + && ( iReg <= 6 + || (CIDETMODE_IS_64BIT(pThis->bMode) && iReg == 7 && !pThis->fRex)) ) + + { + if (!pThis->fRex && iReg >= 4 && CIDETMODE_IS_64BIT(pThis->bMode) && !pThis->fNoRexPrefix) + { + /* The AMD64 low variants: spl, bpl, sil and dil. */ + pThis->fRex = true; + pThis->fHasStackRegInMrmReg = iReg == X86_GREG_xSP; + + /* Check for collisions. */ + if (pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands)) + { + Assert(!pThis->fHasHighByteRegInMrmRm); + if (!pThis->aOperands[pThis->idxMrmRmOp].fIsMem) + pThis->fHasRegCollisionDirect = CIDET_OF_K_IS_GPR(pThis->fMrmRmOp) + && iReg == pThis->aOperands[pThis->idxMrmRmOp].iReg; + else + { + Assert(!pThis->fUsesVexIndexRegs || pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg == UINT8_MAX); + + pThis->fHasRegCollisionMemBase = iReg == pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg; + pThis->fHasRegCollisionMemIndex = iReg == pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg; + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase || pThis->fHasRegCollisionMemIndex; + } + } + } + else + { + /* Next register: ah, ch, dh and bh. */ + iReg++; + pThis->aOperands[pThis->idxMrmRegOp].iReg = iReg; + pThis->bModRm &= ~X86_MODRM_REG_MASK; + pThis->bModRm |= (iReg & X86_MODRM_REG_SMASK) << X86_MODRM_REG_SHIFT; + pThis->fRex = false; + pThis->fRexR = false; + pThis->fNoRexPrefixMrmReg = true; + pThis->fNoRexPrefix = true; + pThis->fHasHighByteRegInMrmReg = true; + pThis->fHasStackRegInMrmReg = false; + pThis->aOperands[pThis->idxMrmRegOp].fIsHighByteRegister = true; + Assert(!pThis->fRexW); Assert(!pThis->fRexX); Assert(!pThis->fRexB); + + /* Check for collisions. */ + if (pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands)) + { + if (!pThis->aOperands[pThis->idxMrmRmOp].fIsMem) + pThis->fHasRegCollisionDirect = CIDET_OF_K_IS_GPR(pThis->fMrmRmOp) + && ( ( pThis->aOperands[pThis->idxMrmRmOp].cb == 1 + && iReg == pThis->aOperands[pThis->idxMrmRmOp].iReg + && pThis->fHasHighByteRegInMrmRm) + || ( pThis->aOperands[pThis->idxMrmRmOp].cb > 1 + && iReg - 4 == pThis->aOperands[pThis->idxMrmRmOp].iReg)); + else + { + Assert(!pThis->fUsesVexIndexRegs || pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg == UINT8_MAX); + + pThis->fHasRegCollisionMemBase = iReg - 4 == pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg; + pThis->fHasRegCollisionMemIndex = iReg - 4 == pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg; + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase || pThis->fHasRegCollisionMemIndex; + } + } + } + return true; + } + + Assert(!pThis->fRex || (iReg == 7 && CIDETMODE_IS_64BIT(pThis->bMode))); + pThis->fRex = false; + + /* + * Next register. + */ + iReg = (iReg + 1) & (CIDETMODE_IS_64BIT(pThis->bMode) ? 15 : 7); + + pThis->aOperands[pThis->idxMrmRegOp].iReg = iReg; + pThis->bModRm &= ~X86_MODRM_REG_MASK; + pThis->bModRm |= (iReg & X86_MODRM_REG_SMASK) << X86_MODRM_REG_SHIFT; + pThis->fRexR = iReg >= 8; + pThis->fHasStackRegInMrmReg = iReg == X86_GREG_xSP && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp); + + /* + * Register collision detection. + */ + if (pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands)) + { + if (!pThis->aOperands[pThis->idxMrmRmOp].fIsMem) + pThis->fHasRegCollisionDirect = iReg == pThis->aOperands[pThis->idxMrmRmOp].iReg + && CIDET_OF_K_IS_SAME(pThis->fMrmRmOp, pThis->fMrmRegOp); + else if (CIDET_OF_K_IS_GPR(pThis->fMrmRegOp)) + { + Assert(!pThis->fUsesVexIndexRegs || pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg == UINT8_MAX); + pThis->fHasRegCollisionMemBase = iReg == pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg; + pThis->fHasRegCollisionMemIndex = iReg == pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg; + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase || pThis->fHasRegCollisionMemIndex; + } + } + Assert(!pThis->fSib); + + return iReg != 0; +} + + +/** + * Selects the next MOD & R/M encoding, 16-bit addressing variant. + * + * @param pThis The core state structure. + * @param iReg The value of MODRM.REG /w REX.R applied. + */ +static void cidetCoreSetupFirstBaseEncoding_MrmRmMod_16bit(PCIDETCORE pThis, uint8_t iReg) +{ + if (CidetInstrHasMrmRegOperand(pThis->pCurInstr)) + { + pThis->aOperands[pThis->idxMrmRmOp].iReg = 0; + pThis->aOperands[pThis->idxMrmRmOp].fIsMem = false; + pThis->aOperands[pThis->idxMrmRmOp].fIsRipRelative = false; + pThis->aOperands[pThis->idxMrmRmOp].fIsHighByteRegister = false; + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 0; + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 1; + pThis->aOperands[pThis->idxMrmRmOp].iEffSeg = UINT8_MAX; + pThis->bModRm &= ~(X86_MODRM_RM_MASK | X86_MODRM_MOD_MASK); + pThis->bModRm |= 3 << X86_MODRM_MOD_SHIFT; + pThis->fRexB = false; + pThis->fRexX = false; + pThis->fHasMemoryOperand = false; + pThis->fHasRegCollisionDirect = iReg == 0 + && CIDET_OF_K_IS_SAME(pThis->fMrmRmOp, pThis->fMrmRegOp); + pThis->fHasRegCollisionMem = false; + pThis->fHasRegCollisionMemBase = false; + pThis->fHasRegCollisionMemIndex = false; + pThis->fHasStackRegInMrmRmBase = false; + } + else + { + Assert(CidetInstrHasMrmMemOperand(pThis->pCurInstr)); + pThis->aOperands[pThis->idxMrmRmOp].iReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].fIsMem = true; + pThis->aOperands[pThis->idxMrmRmOp].fIsRipRelative = false; + pThis->aOperands[pThis->idxMrmRmOp].fIsHighByteRegister = false; + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 0; + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = X86_GREG_xBX; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = X86_GREG_xSI; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 1; + pThis->aOperands[pThis->idxMrmRmOp].iEffSeg = UINT8_MAX; + pThis->bModRm &= ~(X86_MODRM_RM_MASK | X86_MODRM_MOD_MASK); + pThis->fRexB = false; + pThis->fRexX = false; + pThis->fHasMemoryOperand = true; + pThis->fHasRegCollisionDirect = false; + iReg -= pThis->fHasHighByteRegInMrmReg * 4; + pThis->fHasRegCollisionMemBase = iReg == X86_GREG_xBX && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp); + pThis->fHasRegCollisionMemIndex = iReg == X86_GREG_xSI && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp); + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase || pThis->fHasRegCollisionMemIndex; + pThis->fHasStackRegInMrmRmBase = false; + } +} + + +/** + * Selects the next MOD & R/M encoding, 16-bit addressing variant. + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + * @param iReg The value of MODRM.REG /w REX.R applied. + */ +static bool cidetCoreSetupNextBaseEncoding_MrmRmMod_16bit(PCIDETCORE pThis, uint8_t iReg) +{ + AssertRelease(!pThis->fRexB); + AssertRelease(!pThis->fRexX); + uint8_t iRm = pThis->bModRm & X86_MODRM_RM_MASK; + uint8_t iMod = (pThis->bModRm >> X86_MODRM_MOD_SHIFT) & X86_MODRM_MOD_SMASK; + if (iMod == 3) + { + /* + * Register access mode. + */ + Assert(pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands) && !pThis->aOperands[pThis->idxMrmRmOp].fIsMem); + Assert(!pThis->fHasMemoryOperand); + Assert(!pThis->fHasRegCollisionMem); + Assert(!pThis->fHasRegCollisionMemBase); + Assert(!pThis->fHasRegCollisionMemIndex); + if (iRm < 7) + { + iRm++; + pThis->aOperands[pThis->idxMrmRmOp].iReg = iRm; + pThis->bModRm &= ~X86_MODRM_RM_MASK; + pThis->bModRm |= iRm; + pThis->fHasRegCollisionDirect = iRm == iReg + && CIDET_OF_K_IS_SAME(pThis->fMrmRmOp, pThis->fMrmRegOp); + pThis->fHasStackRegInMrmRmBase = iRm == X86_GREG_xSP && CIDET_OF_K_IS_GPR(pThis->fMrmRmOp); + return true; + } + + /* If no memory modes, we're done. */ + if (!CidetInstrHasMrmMemOperand(pThis->pCurInstr)) + { + cidetCoreSetupFirstBaseEncoding_MrmRmMod_16bit(pThis, iReg); + return false; + } + + /* Next mode: 16-bit memory addressing without displacement. */ + pThis->aOperands[pThis->idxMrmRmOp].fIsMem = true; + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 0; + iMod = 0; + } + else + { + /* + * Memory access mode. + */ + Assert(pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands) && pThis->aOperands[pThis->idxMrmRmOp].fIsMem); + Assert(pThis->fHasMemoryOperand); + if (iRm < 7) + { + iRm++; + switch (iRm) + { + case 1: + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = X86_GREG_xBX; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = X86_GREG_xDI; + break; + case 2: + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = X86_GREG_xBP; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = X86_GREG_xSI; + break; + case 3: + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = X86_GREG_xBP; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = X86_GREG_xDI; + break; + case 4: + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = X86_GREG_xSI; + break; + case 5: + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = X86_GREG_xDI; + break; + case 6: + if (iMod == 0) + { + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 2; + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = UINT8_MAX; + } + else + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = X86_GREG_xBP; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = UINT8_MAX; + break; + case 7: + if (iMod == 0) + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 0; + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = X86_GREG_xBX; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = UINT8_MAX; + break; + default: AssertReleaseFailed(); + } + pThis->bModRm &= ~X86_MODRM_RM_MASK; + pThis->bModRm |= iRm; + if (CIDET_OF_K_IS_GPR(pThis->fMrmRegOp)) + { + iReg -= pThis->fHasHighByteRegInMrmReg * 4; + pThis->fHasRegCollisionMemBase = iReg == pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg; + pThis->fHasRegCollisionMemIndex = iReg == pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg; + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase || pThis->fHasRegCollisionMemIndex; + } + return true; + } + + /* Last mode? */ + if (iMod >= 2) + { + cidetCoreSetupFirstBaseEncoding_MrmRmMod_16bit(pThis, iReg); + return false; + } + + /* Next memory addressing mode (if any). */ + iMod++; + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp++; + } + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = X86_GREG_xBX; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = X86_GREG_xSI; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 1; + pThis->bModRm &= ~(X86_MODRM_RM_MASK | X86_MODRM_MOD_MASK); + pThis->bModRm |= iMod << X86_MODRM_MOD_SHIFT; + pThis->fHasMemoryOperand = true; + pThis->fHasRegCollisionDirect = false; + pThis->fHasStackRegInMrmRmBase = false; + if (CIDET_OF_K_IS_GPR(pThis->fMrmRmOp)) + { + iReg -= pThis->fHasHighByteRegInMrmReg * 4; + pThis->fHasRegCollisionMemBase = iReg == X86_GREG_xBX; + pThis->fHasRegCollisionMemIndex = iReg == X86_GREG_xSI; + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase || pThis->fHasRegCollisionMemIndex; + } + return true; +} + + +/** + * Selects the first MOD & R/M encoding, 32-bit and 64-bit addressing variant. + * + * @param pThis The core state structure. + * @param iReg The value of MODRM.REG /w REX.R applied. + * @param f64Bit Set if 64-bit, clear if 32-bit. + */ +static void cidetCoreSetupFirstBaseEncoding_MrmRmMod_32bit64bit(PCIDETCORE pThis, uint8_t iReg, bool f64Bit) +{ + RT_NOREF_PV(f64Bit); + if (CidetInstrHasMrmRegOperand(pThis->pCurInstr)) + { + pThis->aOperands[pThis->idxMrmRmOp].iReg = 0; + pThis->aOperands[pThis->idxMrmRmOp].fIsMem = false; + pThis->aOperands[pThis->idxMrmRmOp].fIsRipRelative = false; + pThis->aOperands[pThis->idxMrmRmOp].fIsHighByteRegister = false; + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 0; + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 1; + pThis->aOperands[pThis->idxMrmRmOp].iEffSeg = UINT8_MAX; + pThis->bModRm &= ~(X86_MODRM_RM_MASK | X86_MODRM_MOD_MASK); + pThis->bModRm |= 3 << X86_MODRM_MOD_SHIFT; + pThis->fRexB = false; + pThis->fRexX = false; + pThis->fHasMemoryOperand = false; + pThis->fHasRegCollisionDirect = iReg == 0 + && CIDET_OF_K_IS_SAME(pThis->fMrmRmOp, pThis->fMrmRegOp); + pThis->fHasRegCollisionMem = false; + pThis->fHasRegCollisionMemBase = false; + pThis->fHasRegCollisionMemIndex = false; + pThis->fHasStackRegInMrmRmBase = false; + } + else + { + Assert(CidetInstrHasMrmMemOperand(pThis->pCurInstr)); + pThis->aOperands[pThis->idxMrmRmOp].iReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].fIsMem = true; + pThis->aOperands[pThis->idxMrmRmOp].fIsRipRelative = false; + pThis->aOperands[pThis->idxMrmRmOp].fIsHighByteRegister = false; + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 0; + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = 0; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 1; + pThis->aOperands[pThis->idxMrmRmOp].iEffSeg = UINT8_MAX; + pThis->bModRm &= ~(X86_MODRM_RM_MASK | X86_MODRM_MOD_MASK); + pThis->fRexB = false; + pThis->fRexX = false; + pThis->fHasMemoryOperand = true; + pThis->fHasRegCollisionDirect = false; + pThis->fHasRegCollisionMemIndex = false; + pThis->fHasRegCollisionMemBase = iReg == pThis->fHasHighByteRegInMrmReg * 4 && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp); + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase; + pThis->fHasStackRegInMrmRmBase = false; + } +} + + +/** + * Selects the next MOD & R/M encoding, 32-bit and 64-bit addressing variant. + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + * @param iReg The value of MODRM.REG /w REX.R applied. + * @param f64Bit Set if 64-bit, clear if 32-bit. + */ +static bool cidetCoreSetupNextBaseEncoding_MrmRmMod_32bit64bit(PCIDETCORE pThis, uint8_t iReg, bool f64Bit) +{ + AssertRelease(!pThis->fRexX || CIDETMODE_IS_64BIT(pThis->bMode)); + AssertRelease(!pThis->fRexB || CIDETMODE_IS_64BIT(pThis->bMode)); + uint8_t iRm = (pThis->bModRm & X86_MODRM_RM_MASK) + pThis->fRexB * 8; + uint8_t iMod = (pThis->bModRm >> X86_MODRM_MOD_SHIFT) & X86_MODRM_MOD_SMASK; + if (iMod == 3) + { + /* + * Register access mode. + */ + Assert(pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands) && !pThis->aOperands[pThis->idxMrmRmOp].fIsMem); + Assert(!pThis->fHasMemoryOperand); + Assert(!pThis->fHasRegCollisionMem); + Assert(!pThis->fHasRegCollisionMemBase); + Assert(!pThis->fHasRegCollisionMemIndex); + + if (CIDETMODE_IS_64BIT(pThis->bMode) && !pThis->fRexX && !pThis->fNoRexPrefix) /* should be ignored. */ + { + pThis->fRexX = true; + return true; + } + + /* Reset the byte register kludges variables. */ + pThis->aOperands[pThis->idxMrmRmOp].fIsHighByteRegister = false; + pThis->fHasHighByteRegInMrmRm = false; + pThis->fNoRexPrefixMrmRm = false; + pThis->fNoRexPrefix = pThis->fNoRexPrefixMrmReg; + + if (iRm < (CIDETMODE_IS_64BIT(pThis->bMode) && !pThis->fNoRexPrefix ? 15 : 7)) + { + /* + * Byte register kludge. + */ + if ( pThis->aOperands[pThis->idxMrmRmOp].cb == 1 + && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp) + && iRm >= 3 + && ( iRm <= 6 + || (iRm == 7 && CIDETMODE_IS_64BIT(pThis->bMode) && !pThis->fRexX) ) ) + { + if (!pThis->fRexX && iRm >= 4 && CIDETMODE_IS_64BIT(pThis->bMode) && !pThis->fNoRexPrefix) + { + /* The AMD64 low variants: spl, bpl, sil and dil. (Using fRexX here as REG covers fRex.) */ + pThis->fRexX = true; + pThis->fHasRegCollisionDirect = CIDET_OF_K_IS_GPR(pThis->fMrmRegOp) + && iRm == iReg - pThis->fHasHighByteRegInMrmReg * 4; + pThis->fHasStackRegInMrmRmBase = iRm == X86_GREG_xSP && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp); + } + else + { + /* Next register: ah, ch, dh and bh. */ + iRm++; + pThis->aOperands[pThis->idxMrmRmOp].iReg = iRm; + pThis->bModRm &= ~X86_MODRM_RM_MASK; + pThis->bModRm |= iRm & X86_MODRM_RM_MASK; + pThis->fRexB = false; + pThis->fRexX = false; + if (!pThis->fRexR && !pThis->fRexW && !pThis->fRex) + { + pThis->fNoRexPrefixMrmRm = true; + pThis->fNoRexPrefix = true; + pThis->fHasHighByteRegInMrmRm = true; + pThis->aOperands[pThis->idxMrmRmOp].fIsHighByteRegister = true; + pThis->fHasRegCollisionDirect = CIDET_OF_K_IS_GPR(pThis->fMrmRegOp) + && iRm - 4 == iReg - pThis->fHasHighByteRegInMrmReg * 4; + pThis->fHasStackRegInMrmRmBase = false; + + } + else + { + /* Can't do the high stuff, so do the spl, bpl, sil and dil variation instead. + Note! We don't set the RexX yet since the base register or operand width holds it down. */ + pThis->fHasRegCollisionDirect = CIDET_OF_K_IS_GPR(pThis->fMrmRegOp) + && iRm == iReg - pThis->fHasHighByteRegInMrmReg * 4; + pThis->fHasStackRegInMrmRmBase = iRm == X86_GREG_xSP && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp); + } + } + } + /* + * Normal register. + */ + else + { + iRm++; + pThis->aOperands[pThis->idxMrmRmOp].iReg = iRm; + pThis->bModRm &= ~X86_MODRM_RM_MASK; + pThis->bModRm |= iRm & X86_MODRM_RM_MASK; + pThis->fRexB = iRm >= 8; + pThis->fRexX = false; + pThis->fHasRegCollisionDirect = iRm == iReg && CIDET_OF_K_IS_SAME(pThis->fMrmRmOp, pThis->fMrmRegOp); + pThis->fHasStackRegInMrmRmBase = iRm == X86_GREG_xSP && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp); + } + return true; + } + + /* If no memory modes, we're done. */ + if (!CidetInstrHasMrmMemOperand(pThis->pCurInstr)) + { + cidetCoreSetupFirstBaseEncoding_MrmRmMod_32bit64bit(pThis, iReg, f64Bit); + return false; + } + + /* Next mode: 32-bit/64-bit memory addressing without displacement. */ + pThis->aOperands[pThis->idxMrmRmOp].fIsMem = true; + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 0; + iMod = 0; + } + else + { + /* + * Memory access mode. + */ + Assert(pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands) && pThis->aOperands[pThis->idxMrmRmOp].fIsMem); + Assert(pThis->fHasMemoryOperand); + Assert(!pThis->fHasStackRegInMrmRmBase); + if (iRm < (CIDETMODE_IS_64BIT(pThis->bMode) && !pThis->fNoRexPrefix ? 15 : 7)) + { + iRm++; + if (iRm == 12) + iRm++; /* Leave REX.B=1 to the next-sib-base function. */ + if (iRm == 4) + { + /* SIB */ + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = 0; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = 0; + pThis->fSib = true; + pThis->bSib = 0; + } + else if ((iRm & 7) == 5 && iMod == 0) + { + /* Absolute or wrt rip addressing. */ + pThis->aOperands[pThis->idxMrmRmOp].fIsRipRelative = CIDETMODE_IS_64BIT(pThis->bMode); + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 4; + } + else + { + if ((iRm & 7) == 6 && iMod == 0) + { + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 0; + pThis->aOperands[pThis->idxMrmRmOp].fIsRipRelative = false; + } + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = iRm; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = UINT8_MAX; + } + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 1; + pThis->bModRm &= ~X86_MODRM_RM_MASK; + pThis->bModRm |= iRm & X86_MODRM_RM_MASK; + pThis->fRexB = iRm >= 8; + pThis->fRexX = false; + if (CIDET_OF_K_IS_GPR(pThis->fMrmRegOp)) + { + iReg -= pThis->fHasHighByteRegInMrmReg * 4; + pThis->fHasRegCollisionMemBase = iReg == pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg; + pThis->fHasRegCollisionMemIndex = iReg == pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg; + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase || pThis->fHasRegCollisionMemIndex; + } + return true; + } + + /* Last mode? */ + if (iMod >= 2) + { + cidetCoreSetupFirstBaseEncoding_MrmRmMod_32bit64bit(pThis, iReg, f64Bit); + return false; + } + + /* Next memory addressing mode (if any). */ + iMod++; + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = iMod == 1 ? 1 : 4; + } + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = 0; + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = UINT8_MAX; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 1; + pThis->bModRm &= ~(X86_MODRM_RM_MASK | X86_MODRM_MOD_MASK); + pThis->bModRm |= iMod << X86_MODRM_MOD_SHIFT; + pThis->fRexB = false; + pThis->fRexX = false; + pThis->fHasMemoryOperand = true; + pThis->fHasRegCollisionDirect = false; + pThis->fHasRegCollisionMemIndex = false; + pThis->fHasRegCollisionMemBase = iReg == pThis->fHasHighByteRegInMrmReg * 4 + && CIDET_OF_K_IS_GPR(pThis->fMrmRmOp); + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase; + pThis->fHasStackRegInMrmRmBase = false; + return true; +} + + +/** + * Selects the next MOD & R/M encoding. + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + * @param iReg The value of MODRM.REG /w REX.R applied. + */ +static bool cidetCoreSetupNextBaseEncoding_MrmRmMod(PCIDETCORE pThis, uint8_t iReg) +{ + if (pThis->cbAddrMode == 2) + return cidetCoreSetupNextBaseEncoding_MrmRmMod_16bit(pThis, iReg); + if (pThis->cbAddrMode == 4) + return cidetCoreSetupNextBaseEncoding_MrmRmMod_32bit64bit(pThis, iReg, false); + if (pThis->cbAddrMode == 8) + return cidetCoreSetupNextBaseEncoding_MrmRmMod_32bit64bit(pThis, iReg, true); + AssertReleaseFailedReturn(false); +} + + + +/** + * Selects the next SIB base register (/ encoding). + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + * @param iReg The value of MODRM.REG /w REX.R applied. + */ +static bool cidetCoreSetupNextBaseEncoding_SibBase(PCIDETCORE pThis, uint8_t iReg) +{ + AssertRelease(!pThis->fRexB || CIDETMODE_IS_64BIT(pThis->bMode)); + + uint8_t iBase = (pThis->bSib & X86_SIB_BASE_MASK) + pThis->fRexB * 8; + iBase = (iBase + 1) & (CIDETMODE_IS_64BIT(pThis->bMode) && !pThis->fNoRexPrefix ? 15 : 7); + + if ((iBase & 7) == 5 && (pThis->bModRm & X86_MODRM_MOD_MASK) == 0) + { + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 4; + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = UINT8_MAX; + } + else + { + if ((iBase & 7) == 6 && (pThis->bModRm & X86_MODRM_MOD_MASK) == 0) + pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp = 0; + pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg = iBase; + } + pThis->bSib &= ~X86_SIB_BASE_MASK; + pThis->bSib |= iBase & X86_SIB_BASE_MASK; + pThis->fRexB = iBase >= 8; + pThis->fHasRegCollisionMemBase = pThis->aOperands[pThis->idxMrmRmOp].iMemBaseReg + == iReg - pThis->fHasHighByteRegInMrmReg * 4 + && CIDET_OF_K_IS_GPR(pThis->fMrmRegOp); + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase || pThis->fHasRegCollisionMemIndex; + pThis->fHasStackRegInMrmRmBase = iBase == X86_GREG_xSP; + + return iBase != 0; +} + + +/** + * Selects the next SIB index register (/ encoding). + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + * @param iReg The value of MODRM.REG /w REX.R applied. + */ +static bool cidetCoreSetupNextBaseEncoding_SibIndex(PCIDETCORE pThis, uint8_t iReg) +{ + AssertRelease(!pThis->fRexX || CIDETMODE_IS_64BIT(pThis->bMode)); + Assert(pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands) && pThis->aOperands[pThis->idxMrmRmOp].fIsMem); + + uint8_t iIndex = ((pThis->bSib >> X86_SIB_INDEX_SHIFT) & X86_SIB_INDEX_SMASK) + pThis->fRexX * 8; + iIndex = (iIndex + 1) & (CIDETMODE_IS_64BIT(pThis->bMode) && !pThis->fNoRexPrefix ? 15 : 7); + + if (iIndex == 4 && !pThis->fUsesVexIndexRegs) + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = UINT8_MAX; + else + pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg = iIndex; + pThis->bSib &= ~X86_SIB_INDEX_MASK; + pThis->bSib |= (iIndex & X86_SIB_INDEX_SMASK) << X86_SIB_INDEX_SHIFT; + pThis->fRexX = iIndex >= 8; + pThis->fHasRegCollisionMemIndex = pThis->aOperands[pThis->idxMrmRmOp].iMemIndexReg + == iReg - pThis->fHasHighByteRegInMrmReg * 4 + && ( !pThis->fUsesVexIndexRegs + ? CIDET_OF_K_IS_GPR(pThis->fMrmRegOp) : CIDET_OF_K_IS_VRX(pThis->fMrmRegOp) ); + pThis->fHasRegCollisionMem = pThis->fHasRegCollisionMemBase || pThis->fHasRegCollisionMemIndex; + + return iIndex != 0; +} + + +/** + * Selects the next SIB scale. + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + * @param iReg The value of MODRM.REG /w REX.R applied. + */ +static bool cidetCoreSetupNextBaseEncoding_SibScale(PCIDETCORE pThis, uint8_t iReg) +{ + RT_NOREF_PV(iReg); + switch ((pThis->bSib >> X86_SIB_SCALE_SHIFT) & X86_SIB_SCALE_SMASK) + { + case 0: + pThis->bSib |= 1 << X86_SIB_SCALE_SHIFT; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 2; + return true; + case 1: + pThis->bSib &= ~X86_SIB_SCALE_MASK; + pThis->bSib |= 2 << X86_SIB_SCALE_SHIFT; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 4; + return true; + case 2: + pThis->bSib |= 3 << X86_SIB_SCALE_SHIFT; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 8; + return true; + case 3: + pThis->bSib &= ~X86_SIB_SCALE_MASK; + pThis->aOperands[pThis->idxMrmRmOp].uMemScale = 1; + return false; + + default: AssertReleaseFailedReturn(false); + } +} + + +/** + * Selects the next segment prefix. + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + */ +static bool cidetCoreSetupNextBaseEncoding_SegmentPrefix(PCIDETCORE pThis) +{ + if ( pThis->fHasMemoryOperand + && (pThis->fTestCfg & CIDET_TESTCFG_SEG_PRF_MASK)) + { + switch (pThis->uSegPrf) + { + case X86_SREG_COUNT: + pThis->uSegPrf = X86_SREG_ES; + if (pThis->fTestCfg & CIDET_TESTCFG_SEG_PRF_ES) + return true; + RT_FALL_THRU(); + case X86_SREG_ES: + pThis->uSegPrf = X86_SREG_CS; + if (pThis->fTestCfg & CIDET_TESTCFG_SEG_PRF_CS) + return true; + RT_FALL_THRU(); + case X86_SREG_CS: + pThis->uSegPrf = X86_SREG_SS; + if (pThis->fTestCfg & CIDET_TESTCFG_SEG_PRF_SS) + return true; + RT_FALL_THRU(); + case X86_SREG_SS: + pThis->uSegPrf = X86_SREG_DS; + if (pThis->fTestCfg & CIDET_TESTCFG_SEG_PRF_DS) + return true; + RT_FALL_THRU(); + case X86_SREG_DS: + pThis->uSegPrf = X86_SREG_FS; + if (pThis->fTestCfg & CIDET_TESTCFG_SEG_PRF_FS) + return true; + RT_FALL_THRU(); + case X86_SREG_FS: + pThis->uSegPrf = X86_SREG_GS; + if (pThis->fTestCfg & CIDET_TESTCFG_SEG_PRF_GS) + return true; + RT_FALL_THRU(); + case X86_SREG_GS: + break; + default: AssertReleaseFailedBreak(); + } + pThis->uSegPrf = X86_SREG_COUNT; + } + return false; +} + + +/** + * Updates the variable sized operands. + * + * @param pThis The core state structure. + */ +static void cidetCoreUpdateOperandSizes(PCIDETCORE pThis) +{ + uint8_t iOp = pThis->cOperands; + while (iOp-- > 0) + pThis->aOperands[iOp].cb = (uint8_t)CidetCoreGetOperandSize(pThis, iOp); +} + + +/** + * Selects the next operand size. + * + * @returns @c true if done, @c false if the next wheel needs to be moved. + * @param pThis The core state structure. + */ +static bool cidetCoreSetupNextBaseEncoding_OperandSize(PCIDETCORE pThis) +{ + if (CidetInstrRespondsToOperandSizePrefixes(pThis->pCurInstr)) + { + if (CIDETMODE_IS_64BIT(pThis->bMode)) + { + switch (pThis->fOpSizePrf + pThis->fRexW * 2) + { + case 0: + pThis->fOpSizePrf = true; + cidetCoreUpdateOperandSizes(pThis); + return true; + case 1: + pThis->fOpSizePrf = false; + if (pThis->fNoRexPrefix) + break; + pThis->fRexW = true; + cidetCoreUpdateOperandSizes(pThis); + return true; + case 2: + pThis->fOpSizePrf = true; /* check that it's ignored. */ + cidetCoreUpdateOperandSizes(pThis); + return true; + default: AssertReleaseFailed(); + case 3: + break; + } + } + else + { + if (!pThis->fOpSizePrf) + { + pThis->fOpSizePrf = true; + cidetCoreUpdateOperandSizes(pThis); + return true; + } + } + pThis->fRexW = false; + pThis->fOpSizePrf = false; + cidetCoreUpdateOperandSizes(pThis); + } + return false; +} + + +bool CidetCoreSetupNextBaseEncoding(PCIDETCORE pThis) +{ + if (pThis->fUsesModRm) + { + /* + * The wheels are lined up as follows: + * 1. Address size prefix. + * 2. MODRM.MOD + * 3. MODRM.REG + REX.R + * 4. MODRM.R/M + REX.B + * 5. SIB - MODRM.R/M == 4 && MODRM.MOD != 3: + * 5a) SIB.BASE + REX.B + * 5b) SIB.INDEX + REX.X + * 5c) SIB.SCALE + * 6. Segment prefix overrides if applicable and supported (memory). + * 7. Operand size prefix and REX.W if applicable. + */ + if (cidetCoreSetupNextBaseEncoding_OperandSize(pThis)) + return true; + if (cidetCoreSetupNextBaseEncoding_SegmentPrefix(pThis)) + return true; + + /* The ModR/M register value for collision detection. */ + uint8_t iReg = ((pThis->bModRm >> X86_MODRM_REG_SHIFT) & X86_MODRM_REG_SMASK) + pThis->fRexR * 8; + + if (pThis->fSib) + { + AssertRelease(pThis->fHasMemoryOperand); + if (cidetCoreSetupNextBaseEncoding_SibScale(pThis, iReg)) + return true; + if (cidetCoreSetupNextBaseEncoding_SibIndex(pThis, iReg)) + return true; + if (cidetCoreSetupNextBaseEncoding_SibBase(pThis, iReg)) + return true; + Assert(pThis->bSib == 0); + pThis->fSib = false; + } + + if (cidetCoreSetupNextBaseEncoding_MrmRmMod(pThis, iReg)) + return true; + if (cidetCoreSetupNextBaseEncoding_MrmReg(pThis, iReg)) + return true; + if (cidetCoreSetupNextBaseEncoding_AddressSize(pThis)) + return true; + } + else + AssertFailedReturn(false); + return false; +} + + +bool CidetCoreSetupFirstBaseEncoding(PCIDETCORE pThis) +{ + /* + * Reset all the knobs and wheels. + */ + pThis->fSib = false; + pThis->uSegPrf = X86_SREG_COUNT; + pThis->fAddrSizePrf = false; + pThis->fOpSizePrf = false; + pThis->fRexW = false; + pThis->fRexR = false; + pThis->fRexX = false; + pThis->fRexB = false; + pThis->fRex = false; + pThis->bModRm = 0; + pThis->bSib = 0; + + /* Indicators. */ + pThis->cbAddrMode = CIDETMODE_GET_BYTE_COUNT(pThis->bMode); + pThis->fHasMemoryOperand = false; + pThis->fHasRegCollisionMem = false; + pThis->fHasRegCollisionMemBase = false; + pThis->fHasRegCollisionMemIndex = false; + pThis->fHasStackRegInMrmRmBase = false; + + /* + * Now, drill down on the instruction encoding. + */ + if (pThis->pCurInstr->fFlags & CIDET_IF_MODRM) + { + Assert(pThis->fUsesModRm == true); + cidetCoreSetupFirstBaseEncoding_MrmReg(pThis); + if (pThis->cbAddrMode == 2) + cidetCoreSetupFirstBaseEncoding_MrmRmMod_16bit(pThis, 0); + else if (pThis->cbAddrMode == 4) + cidetCoreSetupFirstBaseEncoding_MrmRmMod_32bit64bit(pThis, 0, false); + else if (pThis->cbAddrMode == 8) + cidetCoreSetupFirstBaseEncoding_MrmRmMod_32bit64bit(pThis, 0, true); + else + AssertReleaseFailedReturn(false); + } + else + AssertFailedReturn(false); + return true; +} + + +/** + * The next memory operand configuration. + * + * @returns true if new one to test, false if we've reached end already. + * @param pThis The core state structure. + */ +bool CidetCoreSetupNextMemoryOperandConfig(PCIDETCORE pThis) +{ + RT_NOREF_PV(pThis); + return false; +} + + +/** + * Sets up the first memory operand configuration and counts memory operands. + * + * @returns true on success, false if no data buffers configured or failure. + * @param pThis The core state structure. + */ +bool CidetCoreSetupFirstMemoryOperandConfig(PCIDETCORE pThis) +{ + pThis->cMemoryOperands = 0; + PCIDETBUF pDataBuf = &pThis->DataBuf; + uint8_t idxOp = pThis->cOperands; + while (idxOp-- > 0) + if (!pThis->aOperands[idxOp].fIsMem) + pThis->aOperands[idxOp].pDataBuf = NULL; + else + { + if (RT_UNLIKELY(!pThis->cDataBufConfigs)) + return false; + + pDataBuf->idxCfg = 0; + pDataBuf->pCfg = &pThis->paDataBufConfigs[0]; + pDataBuf->off = 0; + pDataBuf->cb = pThis->aOperands[idxOp].cb; + pDataBuf->cbSegLimit = UINT16_MAX; + pDataBuf->offSegBase = 0; + pDataBuf->fActive = false; + pDataBuf->idxOp = idxOp; + pDataBuf->fXcptAfterInstruction = false; + pDataBuf->enmExpectXcpt = kCidetExpectXcpt_None; + pThis->aOperands[idxOp].pDataBuf = pDataBuf; + pThis->cMemoryOperands++; + pDataBuf++; + } + + /** @todo implement more than one memory operand. */ + AssertReleaseReturn(pThis->cMemoryOperands <= 1, false); + return true; +} + + +/** + * The next code buffer configuration. + * + * @returns true if new one to test, false if we've reached end already. + * @param pThis The core state structure. + */ +bool CidetCoreSetupNextCodeBufferConfig(PCIDETCORE pThis) +{ + RT_NOREF_PV(pThis); + return false; +} + + +/** + * Sets up the first code buffer configuration. + * + * @returns true on success, false if no data buffers configured or failure. + * @param pThis The core state structure. + */ +bool CidetCoreSetupFirstCodeBufferConfig(PCIDETCORE pThis) +{ + Assert(pThis->cCodeBufConfigs > 0); + Assert(CIDETBUF_IS_CODE(pThis->paCodeBufConfigs[0].fFlags)); + pThis->CodeBuf.idxCfg = 0; + pThis->CodeBuf.pCfg = &pThis->paCodeBufConfigs[0]; + pThis->CodeBuf.off = 0; + pThis->CodeBuf.cb = 0x1000; + pThis->CodeBuf.cbSegLimit = UINT16_MAX; + pThis->CodeBuf.offSegBase = 0; + pThis->CodeBuf.fActive = true; + pThis->CodeBuf.idxOp = 7; + pThis->CodeBuf.fXcptAfterInstruction = false; + pThis->CodeBuf.enmExpectXcpt = kCidetExpectXcpt_None; + return true; +} + + +/** + * Gets the (encoded) size of the given operand in the current context. + * + * @returns Size in bytes. + * @param pThis The core state structure (for context). + * @param iOp The operand index. + */ +uint32_t CidetCoreGetOperandSize(PCIDETCORE pThis, uint8_t iOp) +{ + Assert(iOp < RT_ELEMENTS(pThis->aOperands)); + uint32_t cbOp = g_acbCidetOfSizes[(pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) >> CIDET_OF_Z_SHIFT]; + if (cbOp == UINT16_MAX) + { + Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_VAR_WDQ); + if (CIDETMODE_IS_64BIT(pThis->bMode)) + { + if (pThis->fRexW) + cbOp = 8; + else if (!pThis->fOpSizePrf) + cbOp = 4; + else + cbOp = 2; + } + else if (CIDETMODE_IS_32BIT(pThis->bMode)) + cbOp = !pThis->fOpSizePrf ? 4 : 2; + else + { + Assert(CIDETMODE_IS_16BIT(pThis->bMode)); + cbOp = !pThis->fOpSizePrf ? 2 : 4; + } + return cbOp; + } + + if (cbOp == UINT16_MAX - 1) + { + Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_SPECIAL); + AssertReleaseFailedReturn(0); + } + + if (cbOp) + { +#ifdef VBOX_STRICT + switch (cbOp) + { + case 1: Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_BYTE); break; + case 2: Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_WORD); break; + case 4: Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_DWORD); break; + case 8: Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_QWORD); break; + case 10: Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_TBYTE); break; + case 16: Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_OWORD); break; + case 32: Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_YWORD); break; + case 64: Assert((pThis->aOperands[iOp].fFlags & CIDET_OF_Z_MASK) == CIDET_OF_Z_ZWORD); break; + default: AssertFailed(); + } +#endif + return cbOp; + } + AssertReleaseFailedReturn(0); +} + + +bool CideCoreSetInstruction(PCIDETCORE pThis, PCCIDETINSTR pInstr) +{ + AssertReleaseMsgReturn(RT_VALID_PTR(pInstr), ("%p\n", pInstr), false); + + pThis->pCurInstr = pInstr; + + /* + * Extract info from the instruction descriptor. + */ + pThis->fUsesModRm = false; + pThis->fUsesVexIndexRegs = false; + pThis->idxMrmRegOp = 7; + pThis->idxMrmRmOp = 7; + pThis->fMrmRegOp = 0; + pThis->fMrmRmOp = 0; + pThis->fInstrFlags = pInstr->fFlags; + pThis->cOperands = pInstr->cOperands; + if (pInstr->fFlags & CIDET_IF_MODRM) + { + pThis->fUsesModRm = true; + for (uint8_t iOp = 0; iOp < pInstr->cOperands; iOp++) + if (pInstr->afOperands[iOp] & CIDET_OF_M_REG) + { + pThis->idxMrmRegOp = iOp; + pThis->fMrmRegOp = pInstr->afOperands[iOp]; + } + else if (pInstr->afOperands[iOp] & CIDET_OF_M_RM) + { + pThis->idxMrmRmOp = iOp; + pThis->fMrmRmOp = pInstr->afOperands[iOp]; + } + } + else + AssertFailedReturn(false); + + uint8_t iOp; + for (iOp = 0; iOp < pInstr->cOperands; iOp++) + { + pThis->aOperands[iOp].fFlags = pInstr->afOperands[iOp]; + pThis->aOperands[iOp].iReg = UINT8_MAX; + pThis->aOperands[iOp].cb = (uint8_t)CidetCoreGetOperandSize(pThis, iOp); + pThis->aOperands[iOp].fIsImmediate = (pInstr->afOperands[iOp] & CIDET_OF_K_MASK) == CIDET_OF_K_IMM; + pThis->aOperands[iOp].fIsMem = (pInstr->afOperands[iOp] & CIDET_OF_K_MASK) == CIDET_OF_K_MEM; + pThis->aOperands[iOp].fIsRipRelative = false; + pThis->aOperands[iOp].cbMemDisp = 0; + pThis->aOperands[iOp].iMemBaseReg = UINT8_MAX; + pThis->aOperands[iOp].iMemIndexReg = UINT8_MAX; + pThis->aOperands[iOp].uMemScale = 1; + pThis->aOperands[iOp].iEffSeg = UINT8_MAX; + pThis->aOperands[iOp].offSeg = UINT64_MAX; + pThis->aOperands[iOp].uEffAddr = UINT64_MAX; + pThis->aOperands[iOp].uImmDispValue = UINT64_MAX; + pThis->aOperands[iOp].uMemBaseRegValue = UINT64_MAX; + pThis->aOperands[iOp].uMemIndexRegValue = UINT64_MAX; + pThis->aOperands[iOp].In.pv = NULL; + pThis->aOperands[iOp].Expected.pv = NULL; + pThis->aOperands[iOp].pDataBuf = NULL; + } + + for (; iOp < RT_ELEMENTS(pThis->aOperands); iOp++) + { + pThis->aOperands[iOp].fFlags = 0; + pThis->aOperands[iOp].iReg = UINT8_MAX; + pThis->aOperands[iOp].cb = 0; + pThis->aOperands[iOp].fIsImmediate = false; + pThis->aOperands[iOp].fIsMem = false; + pThis->aOperands[iOp].fIsRipRelative = false; + pThis->aOperands[iOp].cbMemDisp = 0; + pThis->aOperands[iOp].iMemBaseReg = UINT8_MAX; + pThis->aOperands[iOp].iMemIndexReg = UINT8_MAX; + pThis->aOperands[iOp].uMemScale = 1; + pThis->aOperands[iOp].iEffSeg = UINT8_MAX; + pThis->aOperands[iOp].offSeg = UINT64_MAX; + pThis->aOperands[iOp].uEffAddr = UINT64_MAX; + pThis->aOperands[iOp].uImmDispValue = UINT64_MAX; + pThis->aOperands[iOp].uMemBaseRegValue = UINT64_MAX; + pThis->aOperands[iOp].uMemIndexRegValue = UINT64_MAX; + pThis->aOperands[iOp].In.pv = NULL; + pThis->aOperands[iOp].Expected.pv = NULL; + pThis->aOperands[iOp].pDataBuf = NULL; + } + + /* + * Reset various things. + */ + for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aiInOut); i++) + pThis->aiInOut[i] = 0; + + return true; +} + + +bool CidetCoreSetupInOut(PCIDETCORE pThis) +{ + /* + * Enumerate the operands. + */ + uint8_t *pbBuf = &pThis->abBuf[0]; + pbBuf = RT_ALIGN_PT(pbBuf, 16, uint8_t *); + + uint8_t idxOp = pThis->cOperands; + while (idxOp-- > 0) + { + if (pThis->aOperands[idxOp].fIsMem) + { + /* + * Memory operand. + */ + Assert(pThis->aOperands[idxOp].fIsMem); + + /* Set the In & Expected members to point to temporary buffer space. */ + pThis->aOperands[idxOp].Expected.pu8 = pbBuf; + pbBuf += pThis->aOperands[idxOp].cb; + pbBuf = RT_ALIGN_PT(pbBuf, 16, uint8_t *); + + pThis->aOperands[idxOp].In.pu8 = pbBuf; + pbBuf += pThis->aOperands[idxOp].cb; + pbBuf = RT_ALIGN_PT(pbBuf, 16, uint8_t *); + + /* Initialize the buffer we're gonna use. */ + pThis->aOperands[idxOp].iEffSeg = pThis->uSegPrf != X86_SREG_COUNT + ? pThis->uSegPrf + : !(pThis->aOperands[idxOp].fFlags & CIDET_OF_ALWAYS_SEG_ES) ? X86_SREG_DS + : X86_SREG_ES; + + PCIDETBUF pDataBuf = pThis->aOperands[idxOp].pDataBuf; + AssertReleaseReturn(pDataBuf, false); + Assert(pDataBuf->cb == pThis->aOperands[idxOp].cb); + Assert(pDataBuf->idxOp == idxOp); + if (!pThis->pfnReInitDataBuf(pThis, pDataBuf)) + { + pThis->cSkippedReInitDataBuf++; + return false; + } + pDataBuf->fActive = true; + + /* Calc buffer related operand members. */ + pThis->aOperands[idxOp].uEffAddr = pDataBuf->uEffBufAddr + pDataBuf->off; + uint64_t offSeg = pThis->aOperands[idxOp].uEffAddr - pDataBuf->uSegBase; + pThis->aOperands[idxOp].offSeg = offSeg; + AssertRelease(offSeg <= g_au64ByteSizeToMask[pThis->cbAddrMode]); + + /* + * Select register and displacement values for the buffer addressing (works on offSeg). + */ + uint8_t const iMemIndexReg = pThis->aOperands[idxOp].iMemIndexReg; + uint8_t const iMemBaseReg = pThis->aOperands[idxOp].iMemBaseReg; + if (pThis->aOperands[idxOp].fIsRipRelative) + { + /* rip relative. */ + pThis->aOperands[idxOp].uImmDispValue = offSeg - (pThis->InCtx.rip + pThis->cbInstr); + Assert(pThis->aOperands[idxOp].cbMemDisp == 4); + if ( (int64_t)pThis->aOperands[idxOp].uImmDispValue > INT32_MAX + || (int64_t)pThis->aOperands[idxOp].uImmDispValue < INT32_MIN) + { + pThis->cSkippedDataBufWrtRip++; + return false; + } + } + else if (iMemBaseReg != UINT8_MAX) + { + if ( iMemBaseReg != iMemIndexReg + || pThis->fUsesVexIndexRegs) + { + /* [base] or [base + disp] or [base + index * scale] or [base + index * scale + disp] */ + if (pThis->aOperands[idxOp].cbMemDisp > 0) + { + pThis->aOperands[idxOp].uImmDispValue = CidetCoreGetRandS64(pThis, pThis->aOperands[idxOp].cbMemDisp); + offSeg -= (int64_t)pThis->aOperands[idxOp].uImmDispValue; + } + + if (iMemIndexReg != UINT8_MAX) + { + pThis->aOperands[idxOp].uMemIndexRegValue = CidetCoreGetRandU64(pThis, pThis->cbAddrMode); + offSeg -= pThis->aOperands[idxOp].uMemIndexRegValue * pThis->aOperands[idxOp].uMemScale; + } + + pThis->aOperands[idxOp].uMemBaseRegValue = offSeg & g_au64ByteSizeToMask[pThis->cbAddrMode]; + } + else + { + /* base == index; [base + index * scale] or [base * (scale + 1)]. */ + uint8_t const uEffScale = pThis->aOperands[idxOp].uMemScale + 1; + if (pThis->aOperands[idxOp].cbMemDisp > 0) + { + pThis->aOperands[idxOp].uImmDispValue = CidetCoreGetRandS64(pThis, pThis->aOperands[idxOp].cbMemDisp); + offSeg -= (int64_t)pThis->aOperands[idxOp].uImmDispValue; + offSeg &= g_au64ByteSizeToMask[pThis->cbAddrMode]; + uint8_t uRemainder = offSeg % uEffScale; + if (uRemainder != 0) + { + Assert(pThis->aOperands[idxOp].cbMemDisp < 8); + Assert( (int64_t)pThis->aOperands[idxOp].uImmDispValue + <= g_ai64ByteSizeToMax[pThis->aOperands[idxOp].cbMemDisp]); + pThis->aOperands[idxOp].uImmDispValue = (int64_t)pThis->aOperands[idxOp].uImmDispValue + + uRemainder; + offSeg -= uRemainder; + if ( (int64_t)pThis->aOperands[idxOp].uImmDispValue + > g_ai64ByteSizeToMax[pThis->aOperands[idxOp].cbMemDisp]) + { + pThis->aOperands[idxOp].uImmDispValue -= uEffScale; + offSeg += uEffScale; + } + Assert(offSeg % uEffScale == 0); + } + } + else + { + offSeg &= g_au64ByteSizeToMask[pThis->cbAddrMode]; + if (offSeg % uEffScale != 0) + { + pThis->cSkippedSameBaseIndexRemainder++; + return false; + } + } + offSeg /= uEffScale; + pThis->aOperands[idxOp].uMemBaseRegValue = pThis->aOperands[idxOp].uMemIndexRegValue = offSeg; + } + } + else if (iMemIndexReg != UINT8_MAX) + { + /* [index * scale] or [index * scale + disp] */ + if (pThis->aOperands[idxOp].cbMemDisp > 0) + { + pThis->aOperands[idxOp].uImmDispValue = CidetCoreGetRandS64(pThis, pThis->aOperands[idxOp].cbMemDisp); + offSeg -= (int64_t)pThis->aOperands[idxOp].uImmDispValue; + pThis->aOperands[idxOp].uImmDispValue += offSeg & (RT_BIT_64(pThis->aOperands[idxOp].uMemScale) - 1); + offSeg &= ~(RT_BIT_64(pThis->aOperands[idxOp].uMemScale) - 1); + } + else if (offSeg & (RT_BIT_64(pThis->aOperands[idxOp].uMemScale) - 1)) + { + pThis->cSkippedOnlyIndexRemainder++; + return false; + } + + pThis->aOperands[idxOp].uMemIndexRegValue = offSeg / pThis->aOperands[idxOp].uMemScale; + Assert((offSeg % pThis->aOperands[idxOp].uMemScale) == 0); + AssertRelease(!pThis->fUsesVexIndexRegs); /** @todo implement VEX indexing */ + } + else + { + /* [disp] */ + Assert( pThis->aOperands[idxOp].cbMemDisp == 8 + || pThis->aOperands[idxOp].cbMemDisp == 4 + || pThis->aOperands[idxOp].cbMemDisp == 2 + || pThis->aOperands[idxOp].cbMemDisp == 1); + if ( pThis->aOperands[idxOp].cbMemDisp == 4 + ? (int64_t)offSeg != (int32_t)offSeg + : pThis->aOperands[idxOp].cbMemDisp == 2 + ? (int64_t)offSeg != (int16_t)offSeg + : pThis->aOperands[idxOp].cbMemDisp == 1 + ? (int64_t)offSeg != (int8_t)offSeg + : false /* 8 */) + { + pThis->cSkippedDirectAddressingOverflow++; + return false; + } + pThis->aOperands[idxOp].uImmDispValue = offSeg; + } + + /* + * Modify the input and expected output contexts with the base and + * index register values. To simplify verification and the work + * here, we update the uMemBaseRegValue and uMemIndexRegValue + * members to reflect the whole register. + */ + if (iMemBaseReg != UINT8_MAX) + { + if (pThis->cbAddrMode == 4) + { + pThis->aOperands[idxOp].uMemBaseRegValue &= UINT32_MAX; + pThis->aOperands[idxOp].uMemBaseRegValue |= pThis->InCtx.aGRegs[iMemBaseReg] & UINT64_C(0xffffffff00000000); + } + else if (pThis->cbAddrMode == 2) + { + pThis->aOperands[idxOp].uMemBaseRegValue &= UINT16_MAX; + pThis->aOperands[idxOp].uMemBaseRegValue |= pThis->InCtx.aGRegs[iMemBaseReg] & UINT64_C(0xffffffffffff0000); + } + pThis->InCtx.aGRegs[iMemBaseReg] = pThis->aOperands[idxOp].uMemBaseRegValue; + pThis->ExpectedCtx.aGRegs[iMemBaseReg] = pThis->aOperands[idxOp].uMemBaseRegValue; + } + + if (iMemIndexReg != UINT8_MAX) + { + if (pThis->cbAddrMode == 4) + { + pThis->aOperands[idxOp].uMemIndexRegValue &= UINT32_MAX; + pThis->aOperands[idxOp].uMemIndexRegValue |= pThis->InCtx.aGRegs[iMemIndexReg] & UINT64_C(0xffffffff00000000); + } + else if (pThis->cbAddrMode == 2) + { + pThis->aOperands[idxOp].uMemIndexRegValue &= UINT16_MAX; + pThis->aOperands[idxOp].uMemIndexRegValue |= pThis->InCtx.aGRegs[iMemIndexReg] & UINT64_C(0xffffffffffff0000); + } + pThis->InCtx.aGRegs[iMemIndexReg] = pThis->aOperands[idxOp].uMemIndexRegValue; + pThis->ExpectedCtx.aGRegs[iMemIndexReg] = pThis->aOperands[idxOp].uMemIndexRegValue; + } + } + else + { + /* + * Non-memory, so clear the memory related members. + */ + Assert(!pThis->aOperands[idxOp].fIsMem); + pThis->aOperands[idxOp].iEffSeg = UINT8_MAX; + pThis->aOperands[idxOp].offSeg = UINT64_MAX; + pThis->aOperands[idxOp].uEffAddr = UINT64_MAX; + pThis->aOperands[idxOp].pDataBuf = NULL; + + switch (pThis->aOperands[idxOp].fFlags & CIDET_OF_K_MASK) + { + case CIDET_OF_K_GPR: + if (!pThis->aOperands[idxOp].fIsHighByteRegister) + { + pThis->aOperands[idxOp].In.pv = &pThis->InCtx.aGRegs[pThis->aOperands[idxOp].iReg]; + pThis->aOperands[idxOp].Expected.pv = &pThis->ExpectedCtx.aGRegs[pThis->aOperands[idxOp].iReg]; + } + else + { + pThis->aOperands[idxOp].In.pv = &pThis->InCtx.aGRegs[pThis->aOperands[idxOp].iReg - 4]; + pThis->aOperands[idxOp].In.pu8++; + pThis->aOperands[idxOp].Expected.pv = &pThis->ExpectedCtx.aGRegs[pThis->aOperands[idxOp].iReg - 4]; + pThis->aOperands[idxOp].Expected.pu8++; + } + break; + + case CIDET_OF_K_IMM: + pThis->aOperands[idxOp].In.pv = NULL; + pThis->aOperands[idxOp].Expected.pv = NULL; + break; + + case CIDET_OF_K_SREG: + if (pThis->aOperands[idxOp].iReg < RT_ELEMENTS(pThis->InCtx.aSRegs)) + { + pThis->aOperands[idxOp].In.pv = &pThis->InCtx.aSRegs[pThis->aOperands[idxOp].iReg]; + pThis->aOperands[idxOp].Expected.pv = &pThis->ExpectedCtx.aSRegs[pThis->aOperands[idxOp].iReg]; + } + else + { + pThis->aOperands[idxOp].In.pv = NULL; + pThis->aOperands[idxOp].Expected.pv = NULL; + } + break; + + case CIDET_OF_K_CR: + case CIDET_OF_K_SSE: + case CIDET_OF_K_AVX: + case CIDET_OF_K_AVX512: + case CIDET_OF_K_FPU: + case CIDET_OF_K_MMX: + case CIDET_OF_K_AVXFUTURE: + case CIDET_OF_K_SPECIAL: + case CIDET_OF_K_TEST: + /** @todo Implement testing these registers. */ + case CIDET_OF_K_NONE: + default: + AssertReleaseFailedReturn(false); + } + } + } + AssertRelease((uintptr_t)pbBuf - (uintptr_t)&pThis->abBuf[0] <= sizeof(pThis->abBuf)); + + /* + * Call instruction specific setup function (for operand values and flags). + */ + int rc = pThis->pCurInstr->pfnSetupInOut(pThis, false /*fInvalid*/); + if (RT_FAILURE(rc)) + { + pThis->cSkippedSetupInOut++; + return false; + } + + /* + * Do the 2nd set of the memory operand preparations. + */ + if (pThis->fHasMemoryOperand) + { + idxOp = pThis->cOperands; + while (idxOp-- > 0) + if (pThis->aOperands[idxOp].fIsMem) + { + Assert(pThis->aOperands[idxOp].pDataBuf); + if (!pThis->pfnSetupDataBuf(pThis, pThis->aOperands[idxOp].pDataBuf, pThis->aOperands[idxOp].In.pv)) + { + pThis->cSkippedSetupDataBuf++; + return false; + } + + Assert( pThis->aOperands[idxOp].iMemBaseReg == UINT8_MAX + || pThis->InCtx.aGRegs[pThis->aOperands[idxOp].iMemBaseReg] == pThis->aOperands[idxOp].uMemBaseRegValue); + Assert( pThis->aOperands[idxOp].iMemIndexReg == UINT8_MAX + || ( !pThis->fUsesVexIndexRegs + ? pThis->InCtx.aGRegs[pThis->aOperands[idxOp].iMemIndexReg] + == pThis->aOperands[idxOp].uMemIndexRegValue + : false /** @todo VEX indexing */)); + } + } + + return true; +} + + +/** + * Figures the instruction length. + * + * This is a duplicate of CidetCoreAssemble() with the buffer updates removed. + * + * @returns true and pThis->cbInstr on success, false on failure. + * @param pThis The core state structure (for context). + */ +bool CidetCoreAssembleLength(PCIDETCORE pThis) +{ + uint8_t off = 0; + + /* + * Prefixes. + */ + if (1) + { + if (pThis->fAddrSizePrf) + off++; + if (pThis->fOpSizePrf) + off++; + } + else + { + /** @todo prefix list. */ + } + + /* + * Prefixes that must come right before the opcode. + */ + /** @todo VEX and EVEX. */ + if (pThis->fVex) + { + /** @todo VEX and EVEX. */ + } + else if (pThis->fEvex) + { + /** @todo VEX and EVEX. */ + } + else + { + if (pThis->fRexB || pThis->fRexX || pThis->fRexR || pThis->fRexW || pThis->fRex) + off++; + } + + /* + * The opcode. + */ + //uint8_t const *pbOpcode = pThis->pCurInstr->abOpcode; + switch (pThis->pCurInstr->cbOpcode) + { + case 3: off++; RT_FALL_THRU(); + case 2: off++; RT_FALL_THRU(); + case 1: off++; + break; + default: + AssertReleaseFailedReturn(false); + } + + /* + * Mod R/M + */ + if (pThis->fUsesModRm) + { + off++; + if (pThis->fSib) + off++; + if (pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands)) + { + //uint64_t uDispValue = pThis->aOperands[pThis->idxMrmRmOp].uImmDispValue; + switch (pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp) + { + case 0: break; + case 8: + case 7: + case 6: + case 5: + case 4: + case 3: + case 2: + case 1: + break; + default: AssertReleaseFailedReturn(false); + } + off += pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp; + } + } + + /* + * Immediates. + */ + uint8_t iOp = pThis->cOperands; + while (iOp-- > 0) + if ((pThis->aOperands[iOp].fFlags & CIDET_OF_K_MASK) == CIDET_OF_K_IMM) + { + //uint64_t uImmValue = pThis->aOperands[iOp].uImmDispValue; + switch (pThis->aOperands[iOp].cb) + { + case 8: + case 7: + case 6: + case 5: + case 4: + case 3: + case 2: + case 1: + break; + default: AssertReleaseFailedReturn(false); + } + off += pThis->aOperands[iOp].cb; + } + + pThis->cbInstr = off; + return true; +} + + +/** + * Assembles the instruction. + * + * This is a duplicate of CidetCoreAssembleLength() with buffer writes. + * + * @returns true and pThis->cbInstr and pThis->abInstr on success, false on + * failure. + * @param pThis The core state structure (for context). + */ +bool CidetCoreAssemble(PCIDETCORE pThis) +{ + uint8_t off = 0; + + /* + * Prefixes. + */ + if (1) + { + if (pThis->fAddrSizePrf) + pThis->abInstr[off++] = 0x67; + if (pThis->fOpSizePrf) + pThis->abInstr[off++] = 0x66; + } + else + { + /** @todo prefix list. */ + } + + /* + * Prefixes that must come right before the opcode. + */ + /** @todo VEX and EVEX. */ + if (pThis->fVex) + { + /** @todo VEX and EVEX. */ + } + else if (pThis->fEvex) + { + /** @todo VEX and EVEX. */ + } + else + { + if (pThis->fRexB || pThis->fRexX || pThis->fRexR || pThis->fRexW || pThis->fRex) + pThis->abInstr[off++] = 0x40 | (pThis->fRexB * 1) | (pThis->fRexX * 2) | (pThis->fRexR * 4) | (pThis->fRexW * 8); + } + + /* + * The opcode. + */ + uint8_t const *pbOpcode = pThis->pCurInstr->abOpcode; + switch (pThis->pCurInstr->cbOpcode) + { + case 3: pThis->abInstr[off++] = *pbOpcode++; RT_FALL_THRU(); + case 2: pThis->abInstr[off++] = *pbOpcode++; RT_FALL_THRU(); + case 1: pThis->abInstr[off++] = *pbOpcode++; + break; + default: + AssertReleaseFailedReturn(false); + } + + /* + * Mod R/M + */ + if (pThis->fUsesModRm) + { + pThis->abInstr[off++] = pThis->bModRm; + if (pThis->fSib) + pThis->abInstr[off++] = pThis->bSib; + if (pThis->idxMrmRmOp < RT_ELEMENTS(pThis->aOperands)) + { + uint64_t uDispValue = pThis->aOperands[pThis->idxMrmRmOp].uImmDispValue; + switch (pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp) + { + case 0: break; + case 8: pThis->abInstr[off + 3] = (uDispValue >> 56) & UINT8_C(0xff); RT_FALL_THRU(); + case 7: pThis->abInstr[off + 3] = (uDispValue >> 48) & UINT8_C(0xff); RT_FALL_THRU(); + case 6: pThis->abInstr[off + 3] = (uDispValue >> 40) & UINT8_C(0xff); RT_FALL_THRU(); + case 5: pThis->abInstr[off + 3] = (uDispValue >> 32) & UINT8_C(0xff); RT_FALL_THRU(); + case 4: pThis->abInstr[off + 3] = (uDispValue >> 24) & UINT8_C(0xff); RT_FALL_THRU(); + case 3: pThis->abInstr[off + 2] = (uDispValue >> 16) & UINT8_C(0xff); RT_FALL_THRU(); + case 2: pThis->abInstr[off + 1] = (uDispValue >> 8) & UINT8_C(0xff); RT_FALL_THRU(); + case 1: pThis->abInstr[off] = uDispValue & UINT8_C(0xff); + break; + default: AssertReleaseFailedReturn(false); + } + off += pThis->aOperands[pThis->idxMrmRmOp].cbMemDisp; + } + } + + /* + * Immediates. + */ + uint8_t iOp = pThis->cOperands; + while (iOp-- > 0) + if ((pThis->aOperands[iOp].fFlags & CIDET_OF_K_MASK) == CIDET_OF_K_IMM) + { + uint64_t uImmValue = pThis->aOperands[iOp].uImmDispValue; + switch (pThis->aOperands[iOp].cb) + { + case 8: pThis->abInstr[off + 3] = (uImmValue >> 56) & UINT8_C(0xff); RT_FALL_THRU(); + case 7: pThis->abInstr[off + 3] = (uImmValue >> 48) & UINT8_C(0xff); RT_FALL_THRU(); + case 6: pThis->abInstr[off + 3] = (uImmValue >> 40) & UINT8_C(0xff); RT_FALL_THRU(); + case 5: pThis->abInstr[off + 3] = (uImmValue >> 32) & UINT8_C(0xff); RT_FALL_THRU(); + case 4: pThis->abInstr[off + 3] = (uImmValue >> 24) & UINT8_C(0xff); RT_FALL_THRU(); + case 3: pThis->abInstr[off + 2] = (uImmValue >> 16) & UINT8_C(0xff); RT_FALL_THRU(); + case 2: pThis->abInstr[off + 1] = (uImmValue >> 8) & UINT8_C(0xff); RT_FALL_THRU(); + case 1: pThis->abInstr[off] = uImmValue & UINT8_C(0xff); + break; + default: AssertReleaseFailedReturn(false); + } + off += pThis->aOperands[iOp].cb; + } + + pThis->cbInstr = off; + return true; +} + + +bool CidetCoreReInitCodeBuf(PCIDETCORE pThis) +{ + /* + * Re-initialize the buffer. Requires instruction length and positioning. + */ + if (CidetCoreAssembleLength(pThis)) + { + pThis->CodeBuf.cb = pThis->cbInstr; + pThis->CodeBuf.off = CIDET_CODE_BUF_SIZE - PAGE_SIZE - pThis->cbInstr; + if (pThis->pfnReInitCodeBuf(pThis, &pThis->CodeBuf)) + { + pThis->CodeBuf.fActive = true; + + /* + * Update the RIP and CS values in the input and expected contexts. + */ + pThis->InCtx.rip = pThis->CodeBuf.uEffBufAddr + pThis->CodeBuf.offActive - pThis->CodeBuf.uSegBase; + pThis->ExpectedCtx.rip = pThis->InCtx.rip + pThis->cbInstr; /** @todo account for expected traps. */ + if (pThis->CodeBuf.uSeg != UINT32_MAX) + { + pThis->InCtx.aSRegs[X86_SREG_CS] = pThis->CodeBuf.uSeg; + pThis->ExpectedCtx.aSRegs[X86_SREG_CS] = pThis->CodeBuf.uSeg; + } + return true; + } + else + pThis->cSkippedReInitCodeBuf++; + } + else + pThis->cSkippedAssemble++; + return false; +} + + +#ifdef CIDET_DEBUG_DISAS +/** + * @callback_method_impl{FNDISREADBYTES} + */ +static DECLCALLBACK(int) cidetCoreDisReadBytes(PDISSTATE pDis, uint8_t offInstr, uint8_t cbMinRead, uint8_t cbMaxRead) +{ + PCIDETCORE pThis = (PCIDETCORE)pDis->pvUser; + memcpy(&pDis->abInstr[offInstr], &pThis->abInstr[offInstr], cbMaxRead); + pDis->cbCachedInstr = offInstr + cbMaxRead; + return VINF_SUCCESS; +} +#endif + + +bool CidetCoreSetupCodeBuf(PCIDETCORE pThis, unsigned iSubTest) +{ + if (CidetCoreAssemble(pThis)) + { + //CIDET_DPRINTF(("%04u: %.*Rhxs\n", i, pThis->cbInstr, pThis->abInstr)); +#ifdef CIDET_DEBUG_DISAS + DISCPUSTATE Dis; + char szInstr[80] = {0}; + uint32_t cbInstr; + int rcDis = DISInstrToStrEx(pThis->InCtx.rip, + CIDETMODE_IS_64BIT(pThis->bMode) ? DISCPUMODE_64BIT + : CIDETMODE_IS_32BIT(pThis->bMode) ? DISCPUMODE_32BIT : DISCPUMODE_16BIT, + cidetCoreDisReadBytes, + pThis, + DISOPTYPE_ALL, + &Dis, + &cbInstr, + szInstr, sizeof(szInstr)); + CIDET_DPRINTF(("%04u: %s", iSubTest, szInstr)); + Assert(cbInstr == pThis->cbInstr); +#else + RT_NOREF_PV(iSubTest); +#endif + if (pThis->pfnSetupCodeBuf(pThis, &pThis->CodeBuf, pThis->abInstr)) + { + return true; + } + pThis->cSkippedSetupCodeBuf++; + } + else + pThis->cSkippedAssemble++; + return false; +} + + +/** + * Compares the output with the output expectations. + * + * @returns true if ok, false if not (calls pfnFailure too). + * @param pThis The core state structure. + */ +bool CidetCoreCheckResults(PCIDETCORE pThis) +{ + if (memcmp(&pThis->ActualCtx, &pThis->ExpectedCtx, CIDETCPUCTX_COMPARE_SIZE) == 0) + return true; + + unsigned cDiffs = 0; +#define IF_FIELD_DIFFERS_SET_ERROR(a_Field, a_Fmt) \ + if (pThis->ActualCtx.a_Field != pThis->ExpectedCtx.a_Field) \ + { \ + CidetCoreSetError(pThis, #a_Field " differs: got %#llx expected %#llx", \ + pThis->ActualCtx.a_Field, pThis->ExpectedCtx.a_Field); \ + cDiffs++; \ + } else do { } while (0) + + IF_FIELD_DIFFERS_SET_ERROR(rip, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(rfl, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_xAX], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_xBX], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_xCX], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_xDX], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_xSP], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_xBP], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_xSI], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_xDI], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_x8], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_x9], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_x9], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_x10], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_x11], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_x12], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_x13], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_x14], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aGRegs[X86_GREG_x15], "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(aSRegs[X86_SREG_CS], "%#06x"); + IF_FIELD_DIFFERS_SET_ERROR(aSRegs[X86_SREG_SS], "%#06x"); + IF_FIELD_DIFFERS_SET_ERROR(aSRegs[X86_SREG_DS], "%#06x"); + IF_FIELD_DIFFERS_SET_ERROR(aSRegs[X86_SREG_ES], "%#06x"); + IF_FIELD_DIFFERS_SET_ERROR(aSRegs[X86_SREG_FS], "%#06x"); + IF_FIELD_DIFFERS_SET_ERROR(aSRegs[X86_SREG_GS], "%#06x"); + IF_FIELD_DIFFERS_SET_ERROR(uXcpt, "%#04x"); + IF_FIELD_DIFFERS_SET_ERROR(uErr, "%#04llx"); + IF_FIELD_DIFFERS_SET_ERROR(cr2, "%#010llx"); +#ifndef CIDET_REDUCED_CTX + IF_FIELD_DIFFERS_SET_ERROR(tr, "%#06x"); + IF_FIELD_DIFFERS_SET_ERROR(ldtr, "%#06x"); + IF_FIELD_DIFFERS_SET_ERROR(cr0, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(cr3, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(cr4, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(cr8, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(dr0, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(dr1, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(dr2, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(dr3, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(dr6, "%#010llx"); + IF_FIELD_DIFFERS_SET_ERROR(dr7, "%#010llx"); +#endif + +AssertMsgFailed(("cDiffs=%d\n", cDiffs)); + Assert(cDiffs > 0); + return cDiffs == 0; +} + + +bool CidetCoreTest_Basic(PCIDETCORE pThis) +{ + /* + * Iterate all encodings. + */ + if (!CidetCoreSetupFirstBaseEncoding(pThis)) + return CidetCoreSetError(pThis, "CidetCoreSetupFirstBaseEncoding failed"); + unsigned cExecuted = 0; + unsigned cSkipped = 0; + do + { + /* + * Iterate data buffer configurations (one iteration if none). + */ + if (CidetCoreSetupFirstMemoryOperandConfig(pThis)) + { + do + { + /* + * Iterate code buffer configurations. + */ + if (!CidetCoreSetupFirstCodeBufferConfig(pThis)) + return CidetCoreSetError(pThis, "CidetCoreSetupFirstMemoryOperandConfig failed"); + do + { + /* + * Set up inputs and expected outputs, then emit the test code. + */ + pThis->InCtx = pThis->InTemplateCtx; + pThis->InCtx.fTrickyStack = pThis->fHasStackRegInMrmRmBase || pThis->fHasStackRegInMrmReg; + pThis->ExpectedCtx = pThis->InCtx; + if ( CidetCoreReInitCodeBuf(pThis) + && CidetCoreSetupInOut(pThis) + && CidetCoreSetupCodeBuf(pThis, cSkipped + cExecuted) + ) + { + if (pThis->pfnExecute(pThis)) + { + cExecuted++; + + /* + * Check the result against our expectations. + */ + CidetCoreCheckResults(pThis); + /** @todo check result. */ + + } + else + cSkipped++; + } + else + cSkipped++; + } while (CidetCoreSetupNextCodeBufferConfig(pThis)); + } while (CidetCoreSetupNextMemoryOperandConfig(pThis)); + } + else + cSkipped++; + } while (CidetCoreSetupNextBaseEncoding(pThis)); + + CIDET_DPRINTF(("CidetCoreTest_Basic: cExecuted=%u cSkipped=%u\n" + " cSkippedSetupInOut =%u\n" + " cSkippedReInitDataBuf =%u\n" + " cSkippedSetupDataBuf =%u\n" + " cSkippedDataBufWrtRip =%u\n" + " cSkippedAssemble =%u\n" + " cSkippedReInitCodeBuf =%u\n" + " cSkippedSetupCodeBuf =%u\n" + " cSkippedSameBaseIndexRemainder =%u\n" + " cSkippedOnlyIndexRemainder =%u\n" + " cSkippedDirectAddressingOverflow =%u\n" + , + cExecuted, cSkipped, + pThis->cSkippedSetupInOut, + pThis->cSkippedReInitDataBuf, + pThis->cSkippedSetupDataBuf, + pThis->cSkippedDataBufWrtRip, + pThis->cSkippedAssemble, + pThis->cSkippedReInitCodeBuf, + pThis->cSkippedSetupCodeBuf, + pThis->cSkippedSameBaseIndexRemainder, + pThis->cSkippedOnlyIndexRemainder, + pThis->cSkippedDirectAddressingOverflow + )); + + return true; +} + + +bool CidetCoreTestInstruction(PCIDETCORE pThis, PCCIDETINSTR pInstr) +{ + AssertReleaseMsgReturn(RT_VALID_PTR(pThis), ("%p\n", pThis), false); + AssertReleaseReturn(pThis->u32Magic == CIDETCORE_MAGIC, false); + AssertReleaseReturn(pThis->cCodeBufConfigs > 0, false); + + if (!CideCoreSetInstruction(pThis, pInstr)) + return CidetCoreSetError(pThis, "CideCoreSetInstruction failed"); + + bool fResult = CidetCoreTest_Basic(pThis); + + return fResult; +} + diff --git a/src/VBox/ValidationKit/utils/cpu/cidet-instr-1.cpp b/src/VBox/ValidationKit/utils/cpu/cidet-instr-1.cpp new file mode 100644 index 00000000..06694c8c --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/cidet-instr-1.cpp @@ -0,0 +1,297 @@ +/* $Id: cidet-instr-1.cpp $ */ +/** @file + * CPU Instruction Decoding & Execution Tests - First bunch of instructions. + */ + +/* + * Copyright (C) 2014-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "cidet.h" +#include <VBox/err.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/* + * Shorter defines for the EFLAGS to save table space. + */ +#undef CF +#undef PF +#undef AF +#undef ZF +#undef SF +#undef OF + +#define CF X86_EFL_CF +#define PF X86_EFL_PF +#define AF X86_EFL_AF +#define ZF X86_EFL_ZF +#define SF X86_EFL_SF +#define OF X86_EFL_OF + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct CIDET2IN1OUTWITHFLAGSU8ENTRY +{ + uint8_t uIn1; + uint8_t uIn2; + uint16_t fEFlagsIn; + uint8_t uOut; + uint16_t fEFlagsOut; +} CIDET2IN1OUTWITHFLAGSU8ENTRY; +typedef CIDET2IN1OUTWITHFLAGSU8ENTRY const *PCCIDET2IN1OUTWITHFLAGSU8ENTRY; + +typedef struct CIDET2IN1OUTWITHFLAGSU16ENTRY +{ + uint16_t uIn1; + uint16_t uIn2; + uint16_t fEFlagsIn; + uint16_t uOut; + uint16_t fEFlagsOut; +} CIDET2IN1OUTWITHFLAGSU16ENTRY; +typedef CIDET2IN1OUTWITHFLAGSU16ENTRY const *PCCIDET2IN1OUTWITHFLAGSU16ENTRY; + +typedef struct CIDET2IN1OUTWITHFLAGSU32ENTRY +{ + uint32_t uIn1; + uint32_t uIn2; + uint16_t fEFlagsIn; + uint32_t uOut; + uint16_t fEFlagsOut; +} CIDET2IN1OUTWITHFLAGSU32ENTRY; +typedef CIDET2IN1OUTWITHFLAGSU32ENTRY const *PCCIDET2IN1OUTWITHFLAGSU32ENTRY; + +typedef struct CIDET2IN1OUTWITHFLAGSU64ENTRY +{ + uint64_t uIn1; + uint64_t uIn2; + uint16_t fEFlagsIn; + uint64_t uOut; + uint16_t fEFlagsOut; +} CIDET2IN1OUTWITHFLAGSU64ENTRY; +typedef CIDET2IN1OUTWITHFLAGSU64ENTRY const *PCCIDET2IN1OUTWITHFLAGSU64ENTRY; + +typedef struct CIDET2IN1OUTWITHFLAGS +{ + PCCIDET2IN1OUTWITHFLAGSU8ENTRY pa8Entries; + PCCIDET2IN1OUTWITHFLAGSU16ENTRY pa16Entries; + PCCIDET2IN1OUTWITHFLAGSU32ENTRY pa32Entries; + PCCIDET2IN1OUTWITHFLAGSU64ENTRY pa64Entries; + uint16_t c8Entries; + uint16_t c16Entries; + uint16_t c32Entries; + uint16_t c64Entries; + uint32_t fRelevantEFlags; +} CIDET2IN1OUTWITHFLAGS; + +#define CIDET2IN1OUTWITHFLAGS_INITIALIZER(a_fRelevantEFlags) \ + { \ + &s_a8Results[0], &s_a16Results[0], &s_a32Results[0], &s_a64Results[0], \ + RT_ELEMENTS(s_a8Results), RT_ELEMENTS(s_a16Results), RT_ELEMENTS(s_a32Results), RT_ELEMENTS(s_a64Results), \ + (a_fRelevantEFlags) \ + } + + +/** + * Generic worker for a FNCIDETSETUPINOUT function with two GPR/MEM registers, + * storing result in the first and flags. + * + * @returns See FNCIDETSETUPINOUT. + * @param pThis The core CIDET state structure. The InCtx + * and ExpectedCtx members will be modified. + * @param fInvalid When set, get the next invalid operands that will + * cause exceptions/faults. + * @param pResults The result collection. + */ +static int CidetGenericIn2Out1WithFlags(PCIDETCORE pThis, bool fInvalid, CIDET2IN1OUTWITHFLAGS const *pResults) +{ + int rc; + + Assert(pThis->idxMrmRegOp < 2); + Assert(pThis->idxMrmRmOp < 2); + Assert(pThis->idxMrmRmOp != pThis->idxMrmRegOp); + AssertCompile(RT_ELEMENTS(pThis->aiInOut) >= 4); + + if (!fInvalid) + { + if ( !pThis->fHasRegCollisionDirect + && !pThis->fHasRegCollisionMem) + { + pThis->InCtx.rfl &= ~(uint64_t)pResults->fRelevantEFlags; + pThis->ExpectedCtx.rfl &= ~(uint64_t)pResults->fRelevantEFlags; + switch (pThis->aOperands[0].cb) + { + case 1: + { + uint16_t idx = ++pThis->aiInOut[0] % pResults->c8Entries; + PCCIDET2IN1OUTWITHFLAGSU8ENTRY pEntry = &pResults->pa8Entries[idx]; + rc = idx ? VINF_SUCCESS : VINF_EOF; + + *pThis->aOperands[0].In.pu8 = pEntry->uIn1; + *pThis->aOperands[1].In.pu8 = pEntry->uIn2; + pThis->InCtx.rfl |= pEntry->fEFlagsIn; + + *pThis->aOperands[0].Expected.pu8 = pEntry->uOut; + *pThis->aOperands[1].Expected.pu8 = pEntry->uIn2; + pThis->ExpectedCtx.rfl |= pEntry->fEFlagsOut; + break; + } + + case 2: + { + uint16_t idx = ++pThis->aiInOut[1] % pResults->c16Entries; + PCCIDET2IN1OUTWITHFLAGSU16ENTRY pEntry = &pResults->pa16Entries[idx]; + rc = idx ? VINF_SUCCESS : VINF_EOF; + + *pThis->aOperands[0].In.pu16 = pEntry->uIn1; + *pThis->aOperands[1].In.pu16 = pEntry->uIn2; + pThis->InCtx.rfl |= pEntry->fEFlagsIn; + + *pThis->aOperands[0].Expected.pu16 = pEntry->uOut; + *pThis->aOperands[1].Expected.pu16 = pEntry->uIn2; + pThis->ExpectedCtx.rfl |= pEntry->fEFlagsOut; + break; + } + + case 4: + { + uint16_t idx = ++pThis->aiInOut[2] % pResults->c32Entries; + PCCIDET2IN1OUTWITHFLAGSU32ENTRY pEntry = &pResults->pa32Entries[idx]; + rc = idx ? VINF_SUCCESS : VINF_EOF; + + *pThis->aOperands[0].In.pu32 = pEntry->uIn1; + *pThis->aOperands[1].In.pu32 = pEntry->uIn2; + pThis->InCtx.rfl |= pEntry->fEFlagsIn; + + *pThis->aOperands[0].Expected.pu32 = pEntry->uOut; + if (!pThis->aOperands[0].fIsMem) + pThis->aOperands[0].Expected.pu32[1] = 0; + *pThis->aOperands[1].Expected.pu32 = pEntry->uIn2; + pThis->ExpectedCtx.rfl |= pEntry->fEFlagsOut; + break; + } + + case 8: + { + uint16_t idx = ++pThis->aiInOut[3] % pResults->c64Entries; + PCCIDET2IN1OUTWITHFLAGSU64ENTRY pEntry = &pResults->pa64Entries[idx]; + rc = idx ? VINF_SUCCESS : VINF_EOF; + + *pThis->aOperands[0].In.pu64 = pEntry->uIn1; + *pThis->aOperands[1].In.pu64 = pEntry->uIn2; + pThis->InCtx.rfl |= pEntry->fEFlagsIn; + + *pThis->aOperands[0].Expected.pu64 = pEntry->uOut; + *pThis->aOperands[1].Expected.pu64 = pEntry->uIn2; + pThis->ExpectedCtx.rfl |= pEntry->fEFlagsOut; + break; + } + + default: + AssertFailed(); + rc = VERR_INTERNAL_ERROR_3; + } + } + else + rc = VERR_NOT_SUPPORTED; + } + else + rc = VERR_NO_DATA; + return rc; +} + + +static DECLCALLBACK(int) cidetInOutAdd(PCIDETCORE pThis, bool fInvalid) +{ + static const CIDET2IN1OUTWITHFLAGSU8ENTRY s_a8Results[] = + { + { UINT8_C(0x00), UINT8_C(0x00), 0, UINT8_C(0x00), ZF | PF }, + { UINT8_C(0xff), UINT8_C(0x01), 0, UINT8_C(0x00), CF | ZF | AF | PF }, + { UINT8_C(0x7f), UINT8_C(0x80), 0, UINT8_C(0xff), SF | PF }, + { UINT8_C(0x01), UINT8_C(0x01), 0, UINT8_C(0x02), 0 }, + }; + static const CIDET2IN1OUTWITHFLAGSU16ENTRY s_a16Results[] = + { + { UINT16_C(0x0000), UINT16_C(0x0000), 0, UINT16_C(0x0000), ZF | PF }, + { UINT16_C(0xfefd), UINT16_C(0x0103), 0, UINT16_C(0x0000), CF | ZF | AF | PF }, + { UINT16_C(0x8e7d), UINT16_C(0x7182), 0, UINT16_C(0xffff), SF | PF }, + { UINT16_C(0x0001), UINT16_C(0x0001), 0, UINT16_C(0x0002), 0 }, + }; + static const CIDET2IN1OUTWITHFLAGSU32ENTRY s_a32Results[] = + { + { UINT32_C(0x00000000), UINT32_C(0x00000000), 0, UINT32_C(0x00000000), ZF | PF }, + { UINT32_C(0xfefdfcfb), UINT32_C(0x01020305), 0, UINT32_C(0x00000000), CF | ZF | AF | PF }, + { UINT32_C(0x8efdfcfb), UINT32_C(0x71020304), 0, UINT32_C(0xffffffff), SF | PF }, + { UINT32_C(0x00000001), UINT32_C(0x00000001), 0, UINT32_C(0x00000002), 0 }, + }; + static const CIDET2IN1OUTWITHFLAGSU64ENTRY s_a64Results[] = + { + { UINT64_C(0x0000000000000000), UINT64_C(0x0000000000000000), 0, UINT64_C(0x0000000000000000), ZF | PF }, + { UINT64_C(0xfefdfcfbfaf9f8f7), UINT64_C(0x0102030405060709), 0, UINT64_C(0x0000000000000000), CF | ZF | AF | PF }, + { UINT64_C(0x7efdfcfbfaf9f8f7), UINT64_C(0x8102030405060708), 0, UINT64_C(0xffffffffffffffff), SF | PF }, + { UINT64_C(0x0000000000000001), UINT64_C(0x0000000000000001), 0, UINT64_C(0x0000000000000002), 0 }, + }; + static const CIDET2IN1OUTWITHFLAGS s_Results = CIDET2IN1OUTWITHFLAGS_INITIALIZER(CF | PF | AF | SF | OF); + return CidetGenericIn2Out1WithFlags(pThis, fInvalid, &s_Results); +} + + +/** First bunch of instructions. */ +const CIDETINSTR g_aCidetInstructions1[] = +{ +#if 1 + { + "add Eb,Gb", cidetInOutAdd, 1, {0x00, 0, 0}, 0, 2, + { CIDET_OF_K_GPR | CIDET_OF_Z_BYTE | CIDET_OF_M_RM | CIDET_OF_A_RW, + CIDET_OF_K_GPR | CIDET_OF_Z_BYTE | CIDET_OF_M_REG | CIDET_OF_A_R, + 0, 0 }, CIDET_IF_MODRM + }, +#endif +#if 1 + { + "add Ev,Gv", cidetInOutAdd, 1, {0x01, 0, 0}, 0, 2, + { CIDET_OF_K_GPR | CIDET_OF_Z_VAR_WDQ | CIDET_OF_M_RM | CIDET_OF_A_RW, + CIDET_OF_K_GPR | CIDET_OF_Z_VAR_WDQ | CIDET_OF_M_REG | CIDET_OF_A_R, + 0, 0 }, CIDET_IF_MODRM + }, +#endif +}; +/** Number of instruction in the g_aInstructions1 array. */ +const uint32_t g_cCidetInstructions1 = RT_ELEMENTS(g_aCidetInstructions1); + diff --git a/src/VBox/ValidationKit/utils/cpu/cidet.h b/src/VBox/ValidationKit/utils/cpu/cidet.h new file mode 100644 index 00000000..8c4e6693 --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/cidet.h @@ -0,0 +1,1092 @@ +/* $Id: cidet.h $ */ +/** @file + * CPU Instruction Decoding & Execution Tests - C/C++ Header. + */ + +/* + * Copyright (C) 2014-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_cpu_cidet_h +#define VBOX_INCLUDED_SRC_cpu_cidet_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/types.h> +#include <iprt/x86.h> + + +/** @name CIDET - Operand flags. + * @{ */ +#define CIDET_OF_FIXED_MASK UINT32_C(0x0000001f) /**< Fixed register/whatever mask. */ + +#define CIDET_OF_Z_SHIFT 8 /**< Size shift. */ +#define CIDET_OF_Z_MASK UINT32_C(0x00000f00) /**< Size mask. */ +#define CIDET_OF_Z_NONE UINT32_C(0x00000000) /**< Unused zero value. */ +#define CIDET_OF_Z_BYTE UINT32_C(0x00000100) /**< Byte size. */ +#define CIDET_OF_Z_WORD UINT32_C(0x00000200) /**< Word (2 bytes) size. */ +#define CIDET_OF_Z_DWORD UINT32_C(0x00000300) /**< Double word (4 bytes) size. */ +#define CIDET_OF_Z_QWORD UINT32_C(0x00000400) /**< Quad word (8 bytes) size. */ +#define CIDET_OF_Z_TBYTE UINT32_C(0x00000500) /**< Ten byte (10 bytes) size - aka TWORD. */ +#define CIDET_OF_Z_OWORD UINT32_C(0x00000600) /**< Octa word (16 bytes) size - aka DQWORD. */ +#define CIDET_OF_Z_YWORD UINT32_C(0x00000700) /**< Yxx sized, i.e. 32 bytes. */ +#define CIDET_OF_Z_ZWORD UINT32_C(0x00000800) /**< Zxx sized, i.e. 64 bytes. */ +#define CIDET_OF_Z_VAR_WDQ UINT32_C(0x00000900) /**< Variable size depending on size prefix (2, 4, or 8 bytes). */ +#define CIDET_OF_Z_SPECIAL UINT32_C(0x00000f00) /**< Special size, see instruction flags or smth. */ + +#define CIDET_OF_K_MASK UINT32_C(0x0000f000) /**< Kind of operand. */ +#define CIDET_OF_K_NONE UINT32_C(0x00000000) /**< Unused zero value. */ +#define CIDET_OF_K_GPR UINT32_C(0x00001000) /**< General purpose register. Includes memory when used with CIDET_OF_M_RM. */ +#define CIDET_OF_K_SREG UINT32_C(0x00002000) /**< Segment register. */ +#define CIDET_OF_K_CR UINT32_C(0x00003000) /**< Control register. */ +#define CIDET_OF_K_SSE UINT32_C(0x00004000) /**< SSE register. */ +#define CIDET_OF_K_AVX UINT32_C(0x00005000) /**< AVX register. */ +#define CIDET_OF_K_AVX512 UINT32_C(0x00006000) /**< AVX-512 register. */ +#define CIDET_OF_K_AVXFUTURE UINT32_C(0x00007000) /**< Reserved for future AVX register set. */ +#define CIDET_OF_K_VRX_TST_MASK UINT32_C(0x0000c000) /**< Used for testing for VRX register kind, see CIDET_OF_K_IS_VRX. */ +#define CIDET_OF_K_VRX_TST_RES UINT32_C(0x00004000) /**< Used for testing for VRX register kind, see CIDET_OF_K_IS_VRX. */ +#define CIDET_OF_K_FPU UINT32_C(0x00008000) /**< FPU register. */ +#define CIDET_OF_K_MMX UINT32_C(0x00009000) /**< MMX register. */ +#define CIDET_OF_K_TEST UINT32_C(0x0000a000) /**< Test register. */ +#define CIDET_OF_K_IMM UINT32_C(0x0000d000) /**< Immediate. */ +#define CIDET_OF_K_MEM UINT32_C(0x0000e000) /**< Memory. */ +#define CIDET_OF_K_SPECIAL UINT32_C(0x0000f000) /**< Special. */ +/** Check if @a a_fOp is a general purpose register. */ +#define CIDET_OF_K_IS_GPR(a_fOp) ( ((a_fOp) & CIDET_OF_K_MASK) == CIDET_OF_K_GPR ) +/** Check if @a a_fOp is a XMM (SSE), YMM (AVX), ZMM (AVX-512) or similar register. */ +#define CIDET_OF_K_IS_VRX(a_fOp) ( ((a_fOp) & CIDET_OF_K_VRX_TST_MASK) == CIDET_OF_K_VRX_TST_RES ) +/** Check if @a a_fOp1 and @a a_fOp2 specify the same kind of register, + * treating SSE, AVX, AVX-512 and AVX-future as the same kind and ignoring the + * special register kind. */ +#define CIDET_OF_K_IS_SAME(a_fOp1, a_fOp2) \ + ( ((a_fOp1) & CIDET_OF_K_MASK) == ((a_fOp2) & CIDET_OF_K_MASK) \ + ? ((a_fOp1) & CIDET_OF_K_MASK) != CIDET_OF_K_SPECIAL \ + : (CIDET_OF_K_IS_VRX(a_fOp1) && CIDET_OF_K_IS_VRX(a_fOp2)) ) + +#define CIDET_OF_M_RM_ONLY_R UINT32_C(0x00010000) +#define CIDET_OF_M_RM_ONLY_M UINT32_C(0x00020000) +#define CIDET_OF_M_RM (CIDET_OF_M_RM_ONLY_R | CIDET_OF_M_RM_ONLY_M) +#define CIDET_OF_M_REG UINT32_C(0x00040000) + +#define CIDET_OF_A_R UINT32_C(0x00080000) /**< Read access. */ +#define CIDET_OF_A_W UINT32_C(0x00100000) /**< Write access. */ +#define CIDET_OF_A_RW UINT32_C(0x00180000) /**< Read & write access. */ + +/** The operand defaults to 64-bit width in 64-bit mode, making 32-bit width + * inaccessible. */ +#define CIDET_OF_DEFAULT_64BIT UINT32_C(0x40000000) +/** Operand always uses the ES segment for memory accesses. */ +#define CIDET_OF_ALWAYS_SEG_ES UINT32_C(0x80000000) +/** @} */ + + +/** @name CIDET - Instruction flags. + * @{ */ +#define CIDET_IF_MODRM RT_BIT_64(0) /**< ModR/M encoded. */ +#define CIDET_IF_PRIVILEGED RT_BIT_64(1) /**< Privileged. */ +/** @} */ + + +/** + * Callback function for setting up the input and expected output CPU contexts. + * + * @returns VBox status code. + * @retval VINF_EOF when static test data wraps (first entry is returned). + * @retval VERR_NO_DATA if @a fInvalid is set and there are no invalid operand + * values for this instruction. + * @retval VERR_NOT_SUPPORTED if something in the setup prevents us from + * comming up with working set of inputs and outputs. + * + * @param pThis The core CIDET state structure. The InCtx + * and ExpectedCtx members will be modified. + * @param fInvalid When set, get the next invalid operands that will + * cause exceptions/faults. + */ +typedef DECLCALLBACKTYPE(int, FNCIDETSETUPINOUT,(struct CIDETCORE *pThis, bool fInvalid)); +/** Pointer to a FNCIDETSETUPINOUT function. */ +typedef FNCIDETSETUPINOUT *PFNCIDETSETUPINOUT; + + +/** + * Instruction test descriptor. + */ +typedef struct CIDETINSTR +{ + /** The mnemonic (kind of). */ + const char *pszMnemonic; + /** Setup input and outputs. */ + PFNCIDETSETUPINOUT pfnSetupInOut; + /** Number of opcode bytes. */ + uint8_t cbOpcode; + /** Opcode byte(s). */ + uint8_t abOpcode[3]; + /** Mandatory prefix (zero if not applicable). */ + uint8_t bMandatoryPrefix; + /** Number of operands. */ + uint8_t cOperands; + /** Operand flags. */ + uint32_t afOperands[4]; + /** Flags. */ + uint64_t fFlags; +} CIDETINSTR; +/** Pointer to an instruction test descriptor. */ +typedef CIDETINSTR const *PCCIDETINSTR; + + +/** + * CPU Context with a few extra bits for expectations and results. + */ +typedef struct CIDETCPUCTX +{ + uint64_t rip; + uint64_t rfl; + uint64_t aGRegs[16]; + uint16_t aSRegs[6]; + +#ifndef CIDET_REDUCED_CTX + uint16_t tr; + uint16_t ldtr; + uint64_t cr0; +#else + uint16_t au16Padding[2]; +#endif + uint64_t cr2; +#ifndef CIDET_REDUCED_CTX + uint64_t cr3; + uint64_t cr4; + uint64_t cr8; + uint64_t dr0; + uint64_t dr1; + uint64_t dr2; + uint64_t dr3; + uint64_t dr6; + uint64_t dr7; +#endif + + uint64_t uErr; /**< Exception error code. UINT64_MAX if not applicable. (Not for input context.) */ + uint32_t uXcpt; /**< Exception number. UINT32_MAX if no exception. (Not for input context.) */ + + uint32_t fIgnoredRFlags; /**< Only for expected result. */ + bool fTrickyStack; /**< Set if the stack might be bad. May come at the cost of accurate flags (32-bit). */ +} CIDETCPUCTX; +typedef CIDETCPUCTX *PCIDETCPUCTX; +typedef CIDETCPUCTX const *PCCIDETCPUCTX; + +/** Number of bytes of CIDETCPUCTX that can be compared quickly using memcmp. + * Anything following these bytes are not relevant to the compare. */ +#define CIDETCPUCTX_COMPARE_SIZE RT_UOFFSETOF(CIDETCPUCTX, fIgnoredRFlags) + + +/** @name CPU mode + bits + environment. + * @{ */ +#define CIDETMODE_BIT_MASK UINT8_C(0x0e) /**< The instruction bit count. Results in byte size when masked. */ +#define CIDETMODE_BIT_16 UINT8_C(0x02) /**< 16-bit instructions. */ +#define CIDETMODE_BIT_32 UINT8_C(0x04) /**< 32-bit instructions. */ +#define CIDETMODE_BIT_64 UINT8_C(0x08) /**< 64-bit instructions. */ +#define CIDETMODE_MODE_MASK UINT8_C(0x70) /**< CPU mode mask. */ +#define CIDETMODE_MODE_RM UINT8_C(0x00) /**< Real mode. */ +#define CIDETMODE_MODE_PE UINT8_C(0x10) /**< Protected mode without paging. */ +#define CIDETMODE_MODE_PP UINT8_C(0x20) /**< Paged protected mode. */ +#define CIDETMODE_MODE_PAE UINT8_C(0x30) /**< PAE protected mode (paged). */ +#define CIDETMODE_MODE_LM UINT8_C(0x40) /**< Long mode (paged). */ +#define CIDETMODE_ENV_MASK UINT8_C(0x81) /**< Execution environment. */ +#define CIDETMODE_ENV_NORMAL UINT8_C(0x01) /**< Normal environment. */ +#define CIDETMODE_ENV_V86 UINT8_C(0x80) /**< V8086 environment. */ +#define CIDETMODE_RM (CIDETMODE_MODE_RM | CIDETMODE_BIT_16 | CIDETMODE_ENV_NORMAL) +#define CIDETMODE_PE_16 (CIDETMODE_MODE_PE | CIDETMODE_BIT_16 | CIDETMODE_ENV_NORMAL) +#define CIDETMODE_PE_32 (CIDETMODE_MODE_PE | CIDETMODE_BIT_32 | CIDETMODE_ENV_NORMAL) +#define CIDETMODE_PE_V86 (CIDETMODE_MODE_PE | CIDETMODE_BIT_16 | CIDETMODE_ENV_V86) +#define CIDETMODE_PP_16 (CIDETMODE_MODE_PP | CIDETMODE_BIT_16 | CIDETMODE_ENV_NORMAL) +#define CIDETMODE_PP_32 (CIDETMODE_MODE_PP | CIDETMODE_BIT_32 | CIDETMODE_ENV_NORMAL) +#define CIDETMODE_PP_V86 (CIDETMODE_MODE_PP | CIDETMODE_BIT_16 | CIDETMODE_ENV_V86) +#define CIDETMODE_PAE_16 (CIDETMODE_MODE_PAE | CIDETMODE_BIT_16 | CIDETMODE_ENV_NORMAL) +#define CIDETMODE_PAE_32 (CIDETMODE_MODE_PAE | CIDETMODE_BIT_32 | CIDETMODE_ENV_NORMAL) +#define CIDETMODE_PAE_V86 (CIDETMODE_MODE_PAE | CIDETMODE_BIT_16 | CIDETMODE_ENV_V86) +#define CIDETMODE_LM_16 (CIDETMODE_MODE_LM | CIDETMODE_BIT_16 | CIDETMODE_ENV_NORMAL) +#define CIDETMODE_LM_32 (CIDETMODE_MODE_LM | CIDETMODE_BIT_32 | CIDETMODE_ENV_NORMAL) +#define CIDETMODE_LM_64 (CIDETMODE_MODE_LM | CIDETMODE_BIT_64 | CIDETMODE_ENV_NORMAL) +/** Test if @a a_bMode is a 16-bit mode. */ +#define CIDETMODE_IS_16BIT(a_bMode) ( ((a_bMode) & CIDETMODE_BIT_MASK) == CIDETMODE_BIT_16 ) +/** Test if @a a_bMode is a 32-bit mode. */ +#define CIDETMODE_IS_32BIT(a_bMode) ( ((a_bMode) & CIDETMODE_BIT_MASK) == CIDETMODE_BIT_32 ) +/** Test if @a a_bMode is a 64-bit mode. */ +#define CIDETMODE_IS_64BIT(a_bMode) ( ((a_bMode) & CIDETMODE_BIT_MASK) == CIDETMODE_BIT_64 ) +/** Get the instruction bit count. */ +#define CIDETMODE_GET_BIT_COUNT(a_bMode) ( CIDETMODE_GET_BYTE_COUNT(a_bMode) * 8 ) +/** Get the instruction byte count. */ +#define CIDETMODE_GET_BYTE_COUNT(a_bMode) ( (a_bMode) & CIDETMODE_BIT_MASK ) +/** Test if @a a_bMode long mode. */ +#define CIDETMODE_IS_LM(a_bMode) ( ((a_bMode) & CIDETMODE_MODE_MASK) == CIDETMODE_MODE_LM ) +/** Test if @a a_bMode some kind of protected mode. */ +#define CIDETMODE_IS_PROT(a_bMode) ( ((a_bMode) & CIDETMODE_MODE_MASK) >= CIDETMODE_MODE_PE ) + +/** @} */ + + +/** @name Test Configuration Flags. + * @{ */ +#define CIDET_TESTCFG_SEG_PRF_CS UINT64_C(0x0000000000000001) +#define CIDET_TESTCFG_SEG_PRF_SS UINT64_C(0x0000000000000002) +#define CIDET_TESTCFG_SEG_PRF_DS UINT64_C(0x0000000000000004) +#define CIDET_TESTCFG_SEG_PRF_ES UINT64_C(0x0000000000000008) +#define CIDET_TESTCFG_SEG_PRF_FS UINT64_C(0x0000000000000010) +#define CIDET_TESTCFG_SEG_PRF_GS UINT64_C(0x0000000000000020) +#define CIDET_TESTCFG_SEG_PRF_MASK UINT64_C(0x000000000000003f) +/** @} */ + +/** */ +typedef enum CIDETREG +{ + kCidetReg_Gpr_Invalid = 0, + + kCidetReg_Gpr_al, + kCidetReg_Gpr_cl, + kCidetReg_Gpr_dl, + kCidetReg_Gpr_bl, + kCidetReg_Gpr_spl, + kCidetReg_Gpr_bpl, + kCidetReg_Gpr_sil, + kCidetReg_Gpr_dil, + kCidetReg_Gpr_r8b, + kCidetReg_Gpr_r9b, + kCidetReg_Gpr_r10b, + kCidetReg_Gpr_r11b, + kCidetReg_Gpr_r12b, + kCidetReg_Gpr_r13b, + kCidetReg_Gpr_r14b, + kCidetReg_Gpr_r15b, + kCidetReg_Gpr_ah, + kCidetReg_Gpr_ch, + kCidetReg_Gpr_dh, + kCidetReg_Gpr_bh, +#define kCidetReg_Gpr_Byte_First kCidetReg_Gpr_al +#define kCidetReg_Gpr_Byte_First_Upper kCidetReg_Gpr_ah +#define kCidetReg_Gpr_Byte_Last kCidetReg_Gpr_bh + + kCidetReg_Gpr_ax, + kCidetReg_Gpr_cx, + kCidetReg_Gpr_dx, + kCidetReg_Gpr_bx, + kCidetReg_Gpr_sp, + kCidetReg_Gpr_bp, + kCidetReg_Gpr_si, + kCidetReg_Gpr_di, + kCidetReg_Gpr_r8w, + kCidetReg_Gpr_r9w, + kCidetReg_Gpr_r10w, + kCidetReg_Gpr_r11w, + kCidetReg_Gpr_r12w, + kCidetReg_Gpr_r13w, + kCidetReg_Gpr_r14w, + kCidetReg_Gpr_r15w, +#define kCidetReg_Gpr_Word_First kCidetReg_Gpr_ax +#define kCidetReg_Gpr_Word_Last kCidetReg_Gpr_r15w + + kCidetReg_Gpr_eax, + kCidetReg_Gpr_ecx, + kCidetReg_Gpr_edx, + kCidetReg_Gpr_ebx, + kCidetReg_Gpr_esp, + kCidetReg_Gpr_ebp, + kCidetReg_Gpr_esi, + kCidetReg_Gpr_edi, + kCidetReg_Gpr_r8d, + kCidetReg_Gpr_r9d, + kCidetReg_Gpr_r10d, + kCidetReg_Gpr_r11d, + kCidetReg_Gpr_r12d, + kCidetReg_Gpr_r13d, + kCidetReg_Gpr_r14d, + kCidetReg_Gpr_r15d, +#define kCidetReg_Gpr_DWord_First kCidetReg_Gpr_eax +#define kCidetReg_Gpr_DWord_Last kCidetReg_Gpr_r15d + + kCidetReg_Gpr_rax, + kCidetReg_Gpr_rcx, + kCidetReg_Gpr_rdx, + kCidetReg_Gpr_rbx, + kCidetReg_Gpr_rsp, + kCidetReg_Gpr_rbp, + kCidetReg_Gpr_rsi, + kCidetReg_Gpr_rdi, + kCidetReg_Gpr_r8, + kCidetReg_Gpr_r9, + kCidetReg_Gpr_r10, + kCidetReg_Gpr_r11, + kCidetReg_Gpr_r12, + kCidetReg_Gpr_r13, + kCidetReg_Gpr_r14, + kCidetReg_Gpr_r15, +#define kCidetReg_Gpr_QWord_First kCidetReg_Gpr_rax +#define kCidetReg_Gpr_QWord_Last kCidetReg_Gpr_r15 + + kCidetReg_Seg_es, + kCidetReg_Seg_cs, + kCidetReg_Seg_ss, + kCidetReg_Seg_ds, + kCidetReg_Seg_fs, + kCidetReg_Seg_gs, + kCidetReg_Seg_Inv6, + kCidetReg_Seg_Inv7, +#define kCidetReg_Seg_First kCidetReg_Seg_es +#define kCidetReg_Seg_Last kCidetReg_Seg_gs +#define kCidetReg_Seg_Last_Inv kCidetReg_Seg_Inv7 + + kCidetReg_Misc_ip, + kCidetReg_Misc_eip, + kCidetReg_Misc_rip, + kCidetReg_Misc_flags, + kCidetReg_Misc_eflags, + kCidetReg_Misc_rflags, + kCidetReg_Misc_tr, + kCidetReg_Misc_ldtr, + kCidetReg_Misc_gdtr, + kCidetReg_Misc_idtr, + + kCidetReg_Ctrl_cr0, + kCidetReg_Ctrl_cr1, + kCidetReg_Ctrl_cr2, + kCidetReg_Ctrl_cr3, + kCidetReg_Ctrl_cr4, + kCidetReg_Ctrl_cr5, + kCidetReg_Ctrl_cr6, + kCidetReg_Ctrl_cr7, + kCidetReg_Ctrl_cr8, + kCidetReg_Ctrl_cr9, + kCidetReg_Ctrl_cr10, + kCidetReg_Ctrl_cr11, + kCidetReg_Ctrl_cr12, + kCidetReg_Ctrl_cr13, + kCidetReg_Ctrl_cr14, + kCidetReg_Ctrl_cr15, +#define kCidetReg_Ctrl_First kCidetReg_Ctrl_cr0 +#define kCidetReg_Ctrl_Last kCidetReg_Ctrl_cr15 +#define CIDETREG_CTRL_IS_VALID(a_iReg) ( (a_iReg) == kCidetReg_Ctrl_cr0 \ + && (a_iReg) == kCidetReg_Ctrl_cr2 \ + && (a_iReg) == kCidetReg_Ctrl_cr3 \ + && (a_iReg) == kCidetReg_Ctrl_cr4 \ + && (a_iReg) == kCidetReg_Ctrl_cr8 ) + + kCidetReg_Dbg_dr0, + kCidetReg_Dbg_dr1, + kCidetReg_Dbg_dr2, + kCidetReg_Dbg_dr3, + kCidetReg_Dbg_dr4, + kCidetReg_Dbg_dr5, + kCidetReg_Dbg_dr6, + kCidetReg_Dbg_dr7, + kCidetReg_Dbg_dr8, + kCidetReg_Dbg_dr9, + kCidetReg_Dbg_dr10, + kCidetReg_Dbg_dr11, + kCidetReg_Dbg_dr12, + kCidetReg_Dbg_dr13, + kCidetReg_Dbg_dr14, + kCidetReg_Dbg_dr15, +#define kCidetReg_Dbg_First kCidetReg_Dbg_dr0 +#define kCidetReg_Dbg_Last kCidetReg_Dbg_dr15 +#define CIDETREG_DBG_IS_VALID(a_iReg) ((a_iReg) < kCidetReg_Dbg_dr8 && (a_iReg) >= kCidetReg_Dbg_First) + + kCidetReg_Test_tr0, + kCidetReg_Test_tr1, + kCidetReg_Test_tr2, + kCidetReg_Test_tr3, + kCidetReg_Test_tr4, + kCidetReg_Test_tr5, + kCidetReg_Test_tr6, + kCidetReg_Test_tr7, + kCidetReg_Test_tr8, + kCidetReg_Test_tr9, + kCidetReg_Test_tr10, + kCidetReg_Test_tr11, + kCidetReg_Test_tr12, + kCidetReg_Test_tr13, + kCidetReg_Test_tr14, + kCidetReg_Test_tr15, +#define kCidetReg_Test_First kCidetReg_Test_tr0 +#define kCidetReg_Test_Last kCidetReg_Test_tr15 + + kCidetReg_Fpu_st0, + kCidetReg_Fpu_st1, + kCidetReg_Fpu_st2, + kCidetReg_Fpu_st3, + kCidetReg_Fpu_st4, + kCidetReg_Fpu_st5, + kCidetReg_Fpu_st6, + kCidetReg_Fpu_st7, +#define kCidetReg_Fpu_First kCidetReg_Mmx_st0 +#define kCidetReg_Fpu_Last kCidetReg_Mmx_st7 + + kCidetReg_FpuMisc_cs, + kCidetReg_FpuMisc_ip, + kCidetReg_FpuMisc_ds, + kCidetReg_FpuMisc_dp, + kCidetReg_FpuMisc_fop, + kCidetReg_FpuMisc_ftw, + kCidetReg_FpuMisc_fsw, + kCidetReg_FpuMisc_fcw, + kCidetReg_FpuMisc_mxcsr_mask, + kCidetReg_FpuMisc_mxcsr, + + kCidetReg_Mmx_mm0, + kCidetReg_Mmx_mm1, + kCidetReg_Mmx_mm2, + kCidetReg_Mmx_mm3, + kCidetReg_Mmx_mm4, + kCidetReg_Mmx_mm5, + kCidetReg_Mmx_mm6, + kCidetReg_Mmx_mm7, +#define kCidetReg_Mmx_First kCidetReg_Mmx_mm0 +#define kCidetReg_Mmx_Last kCidetReg_Mmx_mm7 + + kCidetReg_Sse_xmm0, + kCidetReg_Sse_xmm1, + kCidetReg_Sse_xmm2, + kCidetReg_Sse_xmm3, + kCidetReg_Sse_xmm4, + kCidetReg_Sse_xmm5, + kCidetReg_Sse_xmm6, + kCidetReg_Sse_xmm7, + kCidetReg_Sse_xmm8, + kCidetReg_Sse_xmm9, + kCidetReg_Sse_xmm10, + kCidetReg_Sse_xmm11, + kCidetReg_Sse_xmm12, + kCidetReg_Sse_xmm13, + kCidetReg_Sse_xmm14, + kCidetReg_Sse_xmm15, + kCidetReg_Sse_xmm16, + kCidetReg_Sse_xmm17, + kCidetReg_Sse_xmm18, + kCidetReg_Sse_xmm19, + kCidetReg_Sse_xmm20, + kCidetReg_Sse_xmm21, + kCidetReg_Sse_xmm22, + kCidetReg_Sse_xmm23, + kCidetReg_Sse_xmm24, + kCidetReg_Sse_xmm25, + kCidetReg_Sse_xmm26, + kCidetReg_Sse_xmm27, + kCidetReg_Sse_xmm28, + kCidetReg_Sse_xmm29, + kCidetReg_Sse_xmm30, + kCidetReg_Sse_xmm31, +#define kCidetReg_Sse_First kCidetReg_Mmx_Xmm0 +#define kCidetReg_Sse_Last kCidetReg_Mmx_Xmm15 +#define kCidetReg_Sse_Last_Avx512 kCidetReg_Mmx_Xmm31 + + kCidetReg_Avx_Ymm0, + kCidetReg_Avx_Ymm1, + kCidetReg_Avx_Ymm2, + kCidetReg_Avx_Ymm3, + kCidetReg_Avx_Ymm4, + kCidetReg_Avx_Ymm5, + kCidetReg_Avx_Ymm6, + kCidetReg_Avx_Ymm7, + kCidetReg_Avx_Ymm8, + kCidetReg_Avx_Ymm9, + kCidetReg_Avx_Ymm10, + kCidetReg_Avx_Ymm11, + kCidetReg_Avx_Ymm12, + kCidetReg_Avx_Ymm13, + kCidetReg_Avx_Ymm14, + kCidetReg_Avx_Ymm15, + kCidetReg_Avx_Ymm16, + kCidetReg_Avx_Ymm17, + kCidetReg_Avx_Ymm18, + kCidetReg_Avx_Ymm19, + kCidetReg_Avx_Ymm20, + kCidetReg_Avx_Ymm21, + kCidetReg_Avx_Ymm22, + kCidetReg_Avx_Ymm23, + kCidetReg_Avx_Ymm24, + kCidetReg_Avx_Ymm25, + kCidetReg_Avx_Ymm26, + kCidetReg_Avx_Ymm27, + kCidetReg_Avx_Ymm28, + kCidetReg_Avx_Ymm29, + kCidetReg_Avx_Ymm30, + kCidetReg_Avx_Ymm31, +#define kCidetReg_Avx_First kCidetReg_Avx_Ymm0 +#define kCidetReg_Avx_Last kCidetReg_Avx_Ymm15 +#define kCidetReg_Avx_Last_Avx512 kCidetReg_Avx_Ymm31 + + kCidetReg_Avx512_Zmm0, + kCidetReg_Avx512_Zmm1, + kCidetReg_Avx512_Zmm2, + kCidetReg_Avx512_Zmm3, + kCidetReg_Avx512_Zmm4, + kCidetReg_Avx512_Zmm5, + kCidetReg_Avx512_Zmm6, + kCidetReg_Avx512_Zmm7, + kCidetReg_Avx512_Zmm8, + kCidetReg_Avx512_Zmm9, + kCidetReg_Avx512_Zmm10, + kCidetReg_Avx512_Zmm11, + kCidetReg_Avx512_Zmm12, + kCidetReg_Avx512_Zmm13, + kCidetReg_Avx512_Zmm14, + kCidetReg_Avx512_Zmm15, + kCidetReg_Avx512_Zmm16, + kCidetReg_Avx512_Zmm17, + kCidetReg_Avx512_Zmm18, + kCidetReg_Avx512_Zmm19, + kCidetReg_Avx512_Zmm20, + kCidetReg_Avx512_Zmm21, + kCidetReg_Avx512_Zmm22, + kCidetReg_Avx512_Zmm23, + kCidetReg_Avx512_Zmm24, + kCidetReg_Avx512_Zmm25, + kCidetReg_Avx512_Zmm26, + kCidetReg_Avx512_Zmm27, + kCidetReg_Avx512_Zmm28, + kCidetReg_Avx512_Zmm29, + kCidetReg_Avx512_Zmm30, + kCidetReg_Avx512_Zmm31, +#define kCidetReg_Avx512_First kCidetReg_Avx512_Zmm0 +#define kCidetReg_Avx512_Last kCidetReg_Avx512_Zmm31 + + kCidetReg_End +} CIDETREG; + + +/** @name CIDETBUF_XXX - buffer flags. + * @{ */ +#define CIDETBUF_PROT_MASK UINT32_C(0x0000000f) /**< Page protection mask. */ +#define CIDETBUF_PROT_RWX UINT32_C(0x00000001) /**< Read + write + execute. */ +#define CIDETBUF_PROT_RWNX UINT32_C(0x00000002) /**< Read + write + no execute. */ +#define CIDETBUF_PROT_RX UINT32_C(0x00000003) /**< Read + execute. */ +#define CIDETBUF_PROT_RNX UINT32_C(0x00000004) /**< Read + no execute. */ +#define CIDETBUF_PROT_RWX_1NP UINT32_C(0x00000005) /**< Read + write + execute; 1 page not present. */ +#define CIDETBUF_PROT_RWX_1RWNX UINT32_C(0x00000006) /**< Read + write + execute; 1 page read + write + no execute. */ +#define CIDETBUF_PROT_RWX_1RNX UINT32_C(0x00000007) /**< Read + write + execute; 1 page read + no execute. */ +#define CIDETBUF_PROT_RWX_1RWXS UINT32_C(0x00000008) /**< Read + write + execute; 1 page read + execute + supervisor. */ + +#define CIDETBUF_LOC_MASK UINT32_C(0x000000f0) /**< Location mask. */ +/** Buffer located at top and start of the 32-bit address space. */ +#define CIDETBUF_LOC_32BIT_WRAP UINT32_C(0x00000010) +/** Buffer located at the low canonical boundrary (AMD64). */ +#define CIDETBUF_LOC_CANON_LO UINT32_C(0x00000020) +/** Buffer located at the high canonical boundrary (AMD64). */ +#define CIDETBUF_LOC_CANON_HI UINT32_C(0x00000030) + +/** Segment protection mask. */ +#define CIDETBUF_SEG_MASK UINT32_C(0x00000f00) +#define CIDETBUF_SEG_EO UINT32_C(0x00000100) /**< Execute only */ +#define CIDETBUF_SEG_ER UINT32_C(0x00000200) /**< Execute + read */ +#define CIDETBUF_SEG_EO_CONF UINT32_C(0x00000300) /**< Execute only + conforming. */ +#define CIDETBUF_SEG_ER_CONF UINT32_C(0x00000400) /**< Execute + read + conforming. */ +#define CIDETBUF_SEG_RO UINT32_C(0x00000500) /**< Read only. */ +#define CIDETBUF_SEG_RW UINT32_C(0x00000600) /**< Read + write. */ +#define CIDETBUF_SEG_RO_DOWN UINT32_C(0x00000700) /**< Read only + expand down. */ +#define CIDETBUF_SEG_RW_DOWN UINT32_C(0x00000800) /**< Read + write + expand down. */ + +#define CIDETBUF_DPL_MASK UINT32_C(0x00003000) /**< DPL mask. */ +#define CIDETBUF_DPL_0 UINT32_C(0x00000000) /**< DPL=0. */ +#define CIDETBUF_DPL_1 UINT32_C(0x00001000) /**< DPL=1. */ +#define CIDETBUF_DPL_2 UINT32_C(0x00002000) /**< DPL=2. */ +#define CIDETBUF_DPL_3 UINT32_C(0x00003000) /**< DPL=3. */ +#define CIDETBUF_DPL_SAME UINT32_C(0x00004000) /**< Same DPL as the execution environment. */ + +#define CIDETBUF_SEG_LIMIT_BASE_CAP UINT32_C(0x00008000) /**< Capability to change segment limit and base. */ + +#define CIDETBUF_KIND_DATA UINT32_C(0x00000000) /**< Data buffer. */ +#define CIDETBUF_KIND_CODE UINT32_C(0x80000000) /**< Code buffer. */ +/** Checks if @a a_fFlags describes a code buffer. */ +#define CIDETBUF_IS_CODE(a_fFlags) (((a_fFlags) & CIDETBUF_KIND_CODE) != 0) +/** Checks if @a a_fFlags describes a data buffer. */ +#define CIDETBUF_IS_DATA(a_fFlags) (((a_fFlags) & CIDETBUF_KIND_CODE) == 0) +/** @} */ + +/** Code buffer size. (At least two pages.) */ +#define CIDET_CODE_BUF_SIZE (PAGE_SIZE * 2) +/** Data buffer size. (At least two pages.) */ +#define CIDET_DATA_BUF_SIZE (PAGE_SIZE * 3) + + +/** + * Detailed expected exception. + * + * This is used to internally in the core to calculate the expected exception + * considering all the things that may cause exceptions. + */ +typedef enum CIDETEXPECTXCPT +{ + kCidetExpectXcpt_Invalid = 0, + /** No exception expected. */ + kCidetExpectXcpt_None, + + /** Page not present. */ + kCidetExpectXcpt_PageNotPresent, + /** Write access to a non-writable page. */ + kCidetExpectXcpt_PageNotWritable, + /** Executable access to a non-executable page. */ + kCidetExpectXcpt_PageNotExecutable, + /** Access to supervisor page from user mode code. */ + kCidetExpectXcpt_PagePrivileged, +#define kCidetExpectXcpt_First_PageFault kCidetExpectXcpt_PageNotPresent +#define kCidetExpectXcpt_Last_PageFault kCidetExpectXcpt_PagePrivileged + + /** Read or write access to an execute only segment. */ + kCidetExpectXcpt_SegExecuteOnly, + /** Write to a read only or execute+read segment. */ + kCidetExpectXcpt_SegNotWritable, + /** Exceeded the limit of a non-stack access. */ + kCidetExpectXcpt_SegExceededLimit, + /** Non-canonical address via any segment other than the stack. */ + kCidetExpectXcpt_AddrNotCanonical, + /** Misaligned 16 or 32 byte SSE or AVX operand. */ + kCidetExpectXcpt_MisalignedSseAvx, + /** Privileged instruction. */ + kCidetExpectXcpt_PrivilegedInstruction, +#define kCidetExpectXcpt_First_GeneralProtectionFault kCidetExpectXcpt_SegExecuteOnly +#define kCidetExpectXcpt_Last_GeneralProtectionFault kCidetExpectXcpt_PrivilegedInstruction + + /** Exceeded the limit of a stack access. */ + kCidetExpectXcpt_StackExceededLimit, + /** Non-canonical stack address. */ + kCidetExpectXcpt_StackAddrNotCanonical, +#define kCidetExpectXcpt_First_StackFault kCidetExpectXcpt_StackExceededLimit +#define kCidetExpectXcpt_Last_StackFault kCidetExpectXcpt_StackAddrNotCanonical + + /** Misaligned memory operand (and alignment checking is in effect) if AC is + * enabled (executing in ring-3). */ + kCidetExpectXcpt_MisalignedIfAcEnabled, + /** Misaligned 16 byte memory operand resulting in \#AC if ring-3 and + * enable, otherwise \#GP(0). */ + kCidetExpectXcpt_Misaligned16ByteAcEnabledOrGp, +#define kCidetExpectXcpt_First_AlignmentCheckFault kCidetExpectXcpt_MisalignedIfAcEnabled +#define kCidetExpectXcpt_Last_AlignmentCheckFault kCidetExpectXcpt_Misaligned16ByteAcEnabledOrGp + + kCidetExpectXcpt_End +} CIDETEXPECTXCPT; + + +/** + * Buffer configuration. + */ +typedef struct CIDETBUFCFG +{ + /** The name of this buffer configuration. */ + const char *pszName; + /** The buffer flags (CIDETBUF_XXX) */ + uint32_t fFlags; +} CIDETBUFCFG; +/** Pointer to a constant buffer configuration. */ +typedef CIDETBUFCFG const *PCCIDETBUFCFG; + + +/** + * CIDET buffer for code or data. + * + * ASSUMES page aligned buffers. + */ +typedef struct CIDETBUF +{ + /** @name Owned & modified by the front end. + * @{ */ + /** Effective buffer address. */ + uint64_t uEffBufAddr; + /** The segment base address. */ + uint64_t uSegBase; + /** The active segment limit (see also cbSegLimit). UINT64_MAX if flat. */ + uint64_t cbActiveSegLimit; + /** This specifies the selector to use if a non-flat segment limit or special + * segment flags was requested via pfnSetupBuf. UINT32_MAX if any segment is + * selector works. */ + uint32_t uSeg; + /** The off value at the last pfnReinitBuf call. */ + uint16_t offActive; + /** The cb value at the last pfnReinitBuf call. */ + uint16_t cbActive; + /** Prologue (or front fence) size. */ + uint16_t cbPrologue; + /** Epilogue (or tail fence) size. */ + uint16_t cbEpilogue; + /** @} */ + + /** @name Set by the core before pfnReinitBuf call. + * @{ */ + /** Pointer to the buffer config. */ + PCCIDETBUFCFG pCfg; + /** The configuration index. */ + uint32_t idxCfg; + /** The offset into the buffer of the data / code. */ + uint16_t off; + /** The number of bytes of data / code. */ + uint16_t cb; + /** The segment limit relative to the start of the buffer (last byte included + * in count). UINT16_MAX if maximum segment size should be used. */ + uint16_t cbSegLimit; + /** Desired segment base offset. + * This is for checking where the alignment checks are performed. */ + uint8_t offSegBase; + + /** Set if this buffer is actively being used. */ + bool fActive : 1; + /** The operand index (if data), 7 if not active. */ + uint8_t idxOp : 3; + /** Code: Set if the expected exception is supposed to occur on the + * following insturction, not the instruction unter test. */ + bool fXcptAfterInstruction : 1; + /** Set if the instruction will read from the buffer. */ + bool fRead : 1; + /** Set if the instruction will write to the buffer. */ + bool fWrite : 1; + /** The expected exception. */ + CIDETEXPECTXCPT enmExpectXcpt; + /** @} */ +} CIDETBUF; +/** Pointer to a CIDET buffer for code or data. */ +typedef CIDETBUF *PCIDETBUF; + + +/** + * CPU Instruction Decoding & Execution Testing (CIDET) state. + */ +typedef struct CIDETCORE +{ + /** Magic number (CIDETCORE_MAGIC). */ + uint32_t u32Magic; + + /** The target CPU mode / environment. */ + uint8_t bMode; + /** The target ring. */ + uint8_t iRing; + /** Unused padding bytes. */ + uint8_t abPadding1[2]; + + /** Test configuration. */ + uint64_t fTestCfg; + + /** Code buffer configurations to test. + * The first buffer must be a normal buffer that does not cause any problems. */ + PCCIDETBUFCFG paCodeBufConfigs; + /** The number of code buffer configurations to test (pafCodeBufConfigs). */ + uint32_t cCodeBufConfigs; + /** The number of data buffer configurations to test (pafDataBufConfigs). */ + uint32_t cDataBufConfigs; + /** Data buffer configurations to test. + * The first buffer must be a normal buffer that does not cause any problems. */ + PCCIDETBUFCFG paDataBufConfigs; + + /** The instruction currently under testing. */ + PCCIDETINSTR pCurInstr; + + /** Primary data buffer. */ + CIDETBUF DataBuf; + /** Secondary data buffer. */ + CIDETBUF DataBuf2; + + /** Handle to the random number source. */ + RTRAND hRand; + + /** + * Re-initializes one of the data buffers. + * + * @returns true on succes, false if the request cannot be satisfied. + * @param pThis The core state. + * @param pBuf Pointer to the buffer structure. + */ + DECLCALLBACKMEMBER(bool, pfnReInitDataBuf,(struct CIDETCORE *pThis, PCIDETBUF pBuf)); + + /** + * Copies bytes into the data buffer and sets it up for execution. + * + * @returns true on succes, false if the request cannot be satisfied. + * @param pThis The core state. + * @param pBuf Pointer to the buffer structure. + * @param pvSrc The source bytes (size and destination offset + * given in pfnReinitBuf call). + */ + DECLCALLBACKMEMBER(bool, pfnSetupDataBuf,(struct CIDETCORE *pThis, PCIDETBUF pBuf, void const *pvSrc)); + + /** + * Compares buffer content after test execution. + * + * This also checks any fill bytes in the buffer that the front end may + * have put up. The front end will double buffer the content of supposedly + * inaccessible pages as well as non-existing pages to simplify things for + * the core code. + * + * @returns true if equal, false if not. + * @param pThis The core state. + * @param pBuf Pointer to the buffer structure. + * @param pvExpected Pointer to the expected source bytes (size and + * buffer offset given in pfnReinitBuf call). + */ + DECLCALLBACKMEMBER(bool, pfnIsBufEqual,(struct CIDETCORE *pThis, struct CIDETBUF *pBuf, void const *pvExpected)); + + /** + * Re-initializes the code buffer. + * + * @returns true on succes, false if the request cannot be satisfied. + * @param pThis The core state. + * @param pBuf Pointer to the CodeBuf member. The off and cb + * members represent what the core wants to + * execute. + */ + DECLCALLBACKMEMBER(bool, pfnReInitCodeBuf,(struct CIDETCORE *pThis, PCIDETBUF pBuf)); + + /** + * Emit code into the code buffer, making everything ready for pfnExecute. + * + * @returns VBox status code. + * @param pThis Pointer to the core structure. + * @param pBuf Pointer to the CodeBuf member. + * @param pvInstr Pointer to the encoded instruction bytes. + */ + DECLCALLBACKMEMBER(bool, pfnSetupCodeBuf,(struct CIDETCORE *pThis, PCIDETBUF pBuf, void const *pvInstr)); + + /** + * Executes the code indicated by InCtx, returning the result in ActualCtx. + * + * @returns true if execute, false if skipped. + * @param pThis Pointer to the core structure. + */ + DECLCALLBACKMEMBER(bool, pfnExecute,(struct CIDETCORE *pThis)); + + /** + * Report a test failure. + * + * @param pThis Pointer to the core structure. + * @param pszFormat Format string containing failure details. + * @param va Arguments referenced in @a pszFormat. + */ + DECLCALLBACKMEMBER(void, pfnFailure,(struct CIDETCORE *pThis, const char *pszFormat, va_list va)); + + /** Array of indexes for use by FNCIDETSETUPINOUT. + * Reset when changing instruction or switching between valid and invalid + * inputs. */ + uint32_t aiInOut[4]; + + /** @name Copyied and extracted instruction information. + * @{ */ + /** The flags (CIDET_OF_XXX) for the MODRM.REG operand, 0 if not applicable. */ + uint32_t fMrmRegOp; + /** The flags (CIDET_OF_XXX) for the MODRM.RM operand, 0 if not applicable. */ + uint32_t fMrmRmOp; + /** Instruction flags (CIDETINSTR::fFlags). */ + uint64_t fInstrFlags; + /** Number of operands (CIDETINSTR::cOperands). */ + uint8_t cOperands; + /** Number of memory operands (set by CidetCoreSetupFirstMemoryOperandConfig). */ + uint8_t cMemoryOperands : 3; + /** Set if we're working on a MOD R/M byte. */ + bool fUsesModRm : 1; + /** The index of the MODRM.REG operand, 7 if not applicable. */ + uint8_t idxMrmRegOp : 3; + /** The index of the MODRM.RM operand, 7 if not applicable. */ + uint8_t idxMrmRmOp : 3; + /** Set if the SIB byte uses VEX registers for indexing. */ + bool fUsesVexIndexRegs : 1; + /** @} */ + + /** @name Basic encoding knobs, wheels and indicators. + * @{ */ + /** Set if we're working on a SIB byte. */ + bool fSib : 1; + /** Required segment prefix (X86_SREG_XXX), X86_SREG_COUNT if not. */ + uint8_t uSegPrf : 3; + /** The address size prefix. */ + bool fAddrSizePrf : 1; + /** The operand size prefix. */ + bool fOpSizePrf : 1; + /** The REX.W prefix value. */ + bool fRexW : 1; + /** The REX.R prefix value. */ + bool fRexR : 1; + /** The REX.X prefix value. */ + bool fRexX : 1; + /** The REX.B prefix value. */ + bool fRexB : 1; + /** Set if a REX prefix is required with or without flags (for byte regs). */ + bool fRex : 1; + /** Use VEX encoding. */ + bool fVex : 1; + /** Use EVEX encoding. */ + bool fEvex : 1; + /** Indicator: Effective addressing mode in bytes (2, 4, 8). */ + uint8_t cbAddrMode : 4; + /** Indicator: Set if there is an operand accessing memory. */ + bool fHasMemoryOperand : 1; + /** Indicator: Set if a register is used in two or more operands, and one of + * them being for addressing. */ + bool fHasRegCollisionMem : 1; + /** Indicator: Helper indicator for tracking SIB.BASE collision. */ + bool fHasRegCollisionMemBase : 1; + /** Indicator: Helper indicator for tracking SIB.INDEX collision. */ + bool fHasRegCollisionMemIndex : 1; + /** Indicator: Set if a register is used directly in more than one operand. */ + bool fHasRegCollisionDirect : 1; + + /** Indicator: Set if MODRM.REG is the stack register. */ + bool fHasStackRegInMrmReg : 1; + /** Indicator: Set if MODRM.RM or SIB.BASE is the stack register. */ + bool fHasStackRegInMrmRmBase: 1; + + /** Indicator: High byte-register specified by MODRM.REG. */ + bool fHasHighByteRegInMrmReg : 1; + /** Indicator: High byte-register specified by MODRM.RM. */ + bool fHasHighByteRegInMrmRm : 1; + /** Indicator: Set if REX prefixes are incompatible with the byte-register + * specified by MODRM.REG. */ + bool fNoRexPrefixMrmReg : 1; + /** Indicator: Set if REX prefixes are incompatible with the byte-register + * specified by MODRM.RM. */ + bool fNoRexPrefixMrmRm : 1; + /** Indicator: fNoRexPrefixMrmReg || fNoRexPrefixMrmMr. */ + bool fNoRexPrefix : 1; + /** The MOD R/M byte we're working on (if fUsesModRm is set). */ + uint8_t bModRm; + /** The SIB/VSIB byte we're working on (if fSib is set). */ + uint8_t bSib; + /** @} */ + + /** The effective instruction address. (See InCtx.rip and InCtx.cs for the + * rest of the instruction addressing stuff.) */ + uint64_t uInstrEffAddr; + + /** Operand information, mainly for the FNCIDETSETUPINOUT and similar. */ + struct + { + /** The operand flags copied from (CIDETINSTR::afOperands). */ + uint32_t fFlags; + /** The encoded register number, if register, UINT8_MAX if not. */ + uint8_t iReg; + /** The actual operand size (encoded). */ + uint8_t cb; + /** Set if immediate value. */ + bool fIsImmediate : 1; + /** Set if memory access. */ + bool fIsMem : 1; + /** Set if addressing is relative to RIP. */ + bool fIsRipRelative : 1; + /** Set if it's a high byte register. */ + bool fIsHighByteRegister : 1; + /** Size of the disposition, 0 if none. */ + uint8_t cbMemDisp; + /** Base register, UINT8_MAX if not applicable. */ + uint8_t iMemBaseReg; + /** Index register, UINT8_MAX if not applicable. */ + uint8_t iMemIndexReg; + /** Index register, 1 if not applicable. */ + uint8_t uMemScale; + /** Effective segment register, UINT8_MAX if not memory access. */ + uint8_t iEffSeg; + /** Segment offset if memory access. Undefined if not memory access. */ + uint64_t offSeg; + /** The effective address if memory access. */ + uint64_t uEffAddr; + /** Immediate or displacement value. */ + uint64_t uImmDispValue; + /** Base register value, undefined if irrelevant. */ + uint64_t uMemBaseRegValue; + /** Index register value, undefined if irrelevant. */ + uint64_t uMemIndexRegValue; + /** Points to where the input data for this operand should be placed, + * when possible. In the fIsMem = true case, it either points directly + * to the input buffer or to a temporary one. While in the other case, + * it'll point into InCtx when possible. */ + RTPTRUNION In; + /** Points to where the expected output data for this operand should be + * stored, when possible. In the fIsMem = false case, it'll point into + * ExpectedCtx when possible. */ + RTPTRUNION Expected; + /** Pointer to the data buffer for this operand. */ + PCIDETBUF pDataBuf; + } aOperands[4]; + + /** Buffer where we assemble the instruction. */ + uint8_t abInstr[45]; + /** The size of the instruction in abInstr. */ + uint8_t cbInstr; + /** Offset of the instruction into the buffer. */ + uint16_t offInstr; + /** Current code buffer. */ + CIDETBUF CodeBuf; + + /** The input context. Initalized by driver and FNCIDETSETUPINOUT. */ + CIDETCPUCTX InCtx; + /** The expected output context. */ + CIDETCPUCTX ExpectedCtx; + /** The actual output context. */ + CIDETCPUCTX ActualCtx; + /** Template input context, initialized when setting the mode. */ + CIDETCPUCTX InTemplateCtx; + + /** Input and expected output temporary memory buffers. */ + uint8_t abBuf[0x2000]; + + + /** Number of skipped tests because of pfnSetupInOut failures. */ + uint32_t cSkippedSetupInOut; + /** Number of skipped tests because of pfnReInitDataBuf failures. */ + uint32_t cSkippedReInitDataBuf; + /** Number of skipped tests because of pfnSetupDataBuf failures. */ + uint32_t cSkippedSetupDataBuf; + /** Number of skipped tests because RIP relative addressing constraints. */ + uint32_t cSkippedDataBufWrtRip; + /** Number of skipped tests because of assemble failures. */ + uint32_t cSkippedAssemble; + /** Number of skipped tests because of pfnReInitCodeBuf failures. */ + uint32_t cSkippedReInitCodeBuf; + /** Number of skipped tests because of pfnSetupCodeBuf failures. */ + uint32_t cSkippedSetupCodeBuf; + /** Number of skipped tests because the base and index registers are the same + * one and there was a remainder when trying to point to the data buffer. */ + uint32_t cSkippedSameBaseIndexRemainder; + /** Number of skipped tests because index-only addressing left a remainder. */ + uint32_t cSkippedOnlyIndexRemainder; + /** Number of skipped tests because of direct addressing overflowed. */ + uint32_t cSkippedDirectAddressingOverflow; + + +} CIDETCORE; +/** Pointer to the CIDET core state. */ +typedef CIDETCORE *PCIDETCORE; + +/** Magic number for CIDETCORE (Lee Konitz). */ +#define CIDETCORE_MAGIC UINT32_C(0x19271013) + + +int CidetCoreInit(PCIDETCORE pThis, RTRAND hRand); +void CidetCoreDelete(PCIDETCORE pThis); +int CidetCoreSetTargetMode(PCIDETCORE pThis, uint8_t bMode); +uint32_t CidetCoreGetOperandSize(PCIDETCORE pThis, uint8_t iOp); +bool CidetCoreTestInstruction(PCIDETCORE pThis, PCCIDETINSTR pInstr); + + +extern const CIDETINSTR g_aCidetInstructions1[]; +extern const uint32_t g_cCidetInstructions1; + +#endif /* !VBOX_INCLUDED_SRC_cpu_cidet_h */ + diff --git a/src/VBox/ValidationKit/utils/cpu/cidet.mac b/src/VBox/ValidationKit/utils/cpu/cidet.mac new file mode 100644 index 00000000..d3e6f188 --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/cidet.mac @@ -0,0 +1,75 @@ +; $Id: cidet.mac $ ; +;; @file +; CPU Instruction Decoding & Execution Tests - Assembly Header. +; + +; +; Copyright (C) 2014-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%ifndef ___cidet_mac___ +%define ___cidet_mac___ + +struc CIDETCPUCTX + .rip resq 1 + .rfl resq 1 + .aGRegs resq 16 + .aSRegs resw 6 + +%ifndef CIDET_REDUCED_CTX + .tr resw 1 + .ldtr resw 1 + .cr0 resq 1 +%else + .au16Padding resw 2 +%endif + .cr2 resq 1 +%ifndef CIDET_REDUCED_CTX + .cr3 resq 1 + .cr4 resq 1 + .cr8 resq 1 + .dr0 resq 1 + .dr1 resq 1 + .dr2 resq 1 + .dr3 resq 1 + .dr6 resq 1 + .dr7 resq 1 +%endif + + .uErr resq 1 + .uXcpt resd 1 + + .fIgnoredRFlags resd 1 + .fTrickyStack resb 1 +endstruc + +%endif + diff --git a/src/VBox/ValidationKit/utils/cpu/cpu-alloc-all-mem.cpp b/src/VBox/ValidationKit/utils/cpu/cpu-alloc-all-mem.cpp new file mode 100644 index 00000000..d853acdf --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/cpu-alloc-all-mem.cpp @@ -0,0 +1,223 @@ +/* $Id: cpu-alloc-all-mem.cpp $ */ +/** @file + * Allocate all memory we can get and then quit. + */ + +/* + * Copyright (C) 2011-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/test.h> + +#include <iprt/asm.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/param.h> +#include <iprt/string.h> +#include <iprt/time.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct TSTALLOC +{ + /** The page sequence number. */ + size_t iPageSeq; + /** The allocation sequence number. */ + size_t iAllocSeq; + /** The allocation size. */ + size_t cb; + /** Pointer to the ourselves (paranoid). */ + void *pv; + /** Linked list node. */ + RTLISTNODE Node; + +} TSTALLOC; +typedef TSTALLOC *PTSTALLOC; + + +static bool checkList(PRTLISTNODE pHead) +{ + size_t iPageSeq = 0; + size_t iAllocSeq = 0; + PTSTALLOC pCur; + RTListForEach(pHead, pCur, TSTALLOC, Node) + { + RTTESTI_CHECK_RET(pCur->iAllocSeq == iAllocSeq, false); + RTTESTI_CHECK_RET(pCur->pv == pCur, false); + + size_t const *pu = (size_t const *)pCur; + size_t const *puEnd = pu + pCur->cb / sizeof(size_t); + while (pu != puEnd) + { + RTTESTI_CHECK_RET(*pu == iPageSeq, false); + iPageSeq++; + pu += PAGE_SIZE / sizeof(size_t); + } + iAllocSeq++; + } + return true; +} + + +static void doTest(RTTEST hTest) +{ + RTTestSub(hTest, "Allocate all memory"); + + RTLISTANCHOR AllocHead; + PTSTALLOC pCur; + uint64_t cNsElapsed = 0; + size_t cbPrint = 0; + uint64_t uPrintTS = 0; + size_t cbTotal = 0; +#if ARCH_BITS == 64 + size_t const cbOneStart = 64 * _1M; + size_t const cbOneMin = 4 * _1M; +#else + size_t const cbOneStart = 16 * _1M; + size_t const cbOneMin = 4 * _1M; +#endif + size_t cbOne = cbOneStart; + size_t cAllocs = 0; + uint32_t iPageSeq = 0; + RTListInit(&AllocHead); + + for (;;) + { + /* + * Allocate a chunk and make sure all the pages are there. + */ + uint64_t const uStartTS = RTTimeNanoTS(); + pCur = (PTSTALLOC)RTMemPageAlloc(cbOne); + if (pCur) + { + size_t *pu = (size_t *)pCur; + size_t *puEnd = pu + cbOne / sizeof(size_t); + while (pu != puEnd) + { + *pu = iPageSeq++; + pu += PAGE_SIZE / sizeof(size_t); + } + uint64_t const uEndTS = RTTimeNanoTS(); + uint64_t const cNsThis = uEndTS - uStartTS; + + /* + * Update the statistics. + */ + cNsElapsed += cNsThis; + cbTotal += cbOne; + cAllocs++; + + /* + * Link the allocation. + */ + pCur->iAllocSeq = cAllocs - 1; + pCur->pv = pCur; + pCur->cb = cbOne; + RTListAppend(&AllocHead, &pCur->Node); + + /* + * Print progress info? + */ + if ( uEndTS - uPrintTS >= RT_NS_1SEC_64*10 +#if ARCH_BITS == 64 + || cbTotal - cbPrint >= _4G +#else + || cbTotal - cbPrint >= _2G +#endif + ) + { + cbPrint = cbTotal; + uPrintTS = uEndTS; + + uint32_t cMBPerSec = (uint32_t)((long double)cbTotal / ((long double)cNsElapsed / RT_NS_1SEC) / _1M); + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "%'zu bytes in %'llu ns - %'u MB/s\n", + cbTotal, cNsElapsed, cMBPerSec); + RTTESTI_CHECK_RETV(checkList(&AllocHead)); + } + } + else + { + /* + * Try again with a smaller request. + */ + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Failed to allocate %'zu bytes (after %'zu bytes)\n", cbOne, cbTotal); + if (cbOne <= cbOneMin) + break; + cbOne = cbOneMin; + } + } + + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Verifying...\n"); + RTTESTI_CHECK_RETV(checkList(&AllocHead)); + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "... detected no corruption.\n"); + + /* + * Free up some memory before displaying the results. + */ + size_t i = 0; + PTSTALLOC pPrev; + RTListForEachReverseSafe(&AllocHead, pCur, pPrev, TSTALLOC, Node) + { + RTMemPageFree(pCur->pv, pCur->cb); + if (++i > 32) + break; + } + + RTTestValue(hTest, "amount", cbTotal, RTTESTUNIT_BYTES); + RTTestValue(hTest, "time", cNsElapsed, RTTESTUNIT_NS); + uint32_t cMBPerSec = (uint32_t)((long double)cbTotal / ((long double)cNsElapsed / RT_NS_1SEC) / _1M); + RTTestValue(hTest, "speed", cMBPerSec, RTTESTUNIT_MEGABYTES_PER_SEC); + RTTestSubDone(hTest); +} + + +int main(int argc, char **argv) +{ + RTTEST hTest; + RTEXITCODE rcExit = RTTestInitAndCreate("memallocall", &hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(hTest); + + NOREF(argv); + if (argc == 1) + doTest(hTest); + else + RTTestFailed(hTest, "This test takes no arguments!"); + + return RTTestSummaryAndDestroy(hTest); +} + diff --git a/src/VBox/ValidationKit/utils/cpu/cpu-numa.cpp b/src/VBox/ValidationKit/utils/cpu/cpu-numa.cpp new file mode 100644 index 00000000..6726589d --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/cpu-numa.cpp @@ -0,0 +1,205 @@ +/* $Id: cpu-numa.cpp $ */ +/** @file + * numa - NUMA / memory benchmark. + */ + +/* + * Copyright (C) 2011-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/test.h> + +#include <iprt/asm.h> +//#if defined(RT_ARCH_X86) || defined(RT_ARCH_AMD64) +//# include <iprt/asm-amd64-x86.h> +//#endif +#include <iprt/mem.h> +#include <iprt/mp.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/time.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The number of threads to skip when testing. */ +static uint32_t g_cThreadsToSkip = 1; + +/** + * Gets the next online CPU. + * + * @returns Next CPU index or RTCPUSET_MAX_CPUS. + * @param iCurCpu The current CPU (index). + */ +static int getNextCpu(unsigned iCurCpu) +{ + /* Skip to the next chip. */ + iCurCpu = (iCurCpu / g_cThreadsToSkip) * g_cThreadsToSkip; + iCurCpu += g_cThreadsToSkip; + + /* Skip offline cpus. */ + while ( iCurCpu < RTCPUSET_MAX_CPUS + && !RTMpIsCpuOnline(iCurCpu) ) + iCurCpu++; + + /* Make sure we're within bounds (in case of bad input). */ + if (iCurCpu > RTCPUSET_MAX_CPUS) + iCurCpu = RTCPUSET_MAX_CPUS; + return iCurCpu; +} + + +static void doTest(RTTEST hTest) +{ + NOREF(hTest); + uint32_t iAllocCpu = 0; + while (iAllocCpu < RTCPUSET_MAX_CPUS) + { + const uint32_t cbTestSet = _1M * 32; + const uint32_t cIterations = 384; + + /* + * Change CPU and allocate a chunk of memory. + */ + RTTESTI_CHECK_RC_OK_RETV(RTThreadSetAffinityToCpu(RTMpCpuIdFromSetIndex(iAllocCpu))); + + void *pvTest = RTMemPageAlloc(cbTestSet); /* may be leaked, who cares */ + RTTESTI_CHECK_RETV(pvTest != NULL); + memset(pvTest, 0xef, cbTestSet); + + /* + * Do the tests. + */ + uint32_t iAccessCpu = 0; + while (iAccessCpu < RTCPUSET_MAX_CPUS) + { + RTTESTI_CHECK_RC_OK_RETV(RTThreadSetAffinityToCpu(RTMpCpuIdFromSetIndex(iAccessCpu))); + + /* + * The write test. + */ + RTTimeNanoTS(); RTThreadYield(); + uint64_t u64StartTS = RTTimeNanoTS(); + for (uint32_t i = 0; i < cIterations; i++) + { + ASMCompilerBarrier(); /* paranoia */ + memset(pvTest, i, cbTestSet); + } + uint64_t const cNsElapsedWrite = RTTimeNanoTS() - u64StartTS; + uint64_t cMBPerSec = (uint64_t)( ((uint64_t)cIterations * cbTestSet) /* bytes */ + / ((long double)cNsElapsedWrite / RT_NS_1SEC_64) /* seconds */ + / _1M /* MB */ ); + RTTestIValueF(cMBPerSec, RTTESTUNIT_MEGABYTES_PER_SEC, "cpu%02u-mem%02u-write", iAllocCpu, iAccessCpu); + + /* + * The read test. + */ + memset(pvTest, 0, cbTestSet); + RTTimeNanoTS(); RTThreadYield(); + u64StartTS = RTTimeNanoTS(); + for (uint32_t i = 0; i < cIterations; i++) + { +#if 1 + size_t u = 0; + size_t volatile *puCur = (size_t volatile *)pvTest; + size_t volatile *puEnd = puCur + cbTestSet / sizeof(size_t); + while (puCur != puEnd) + u += *puCur++; +#else + ASMCompilerBarrier(); /* paranoia */ + void *pvFound = memchr(pvTest, (i & 127) + 1, cbTestSet); + RTTESTI_CHECK(pvFound == NULL); +#endif + } + uint64_t const cNsElapsedRead = RTTimeNanoTS() - u64StartTS; + cMBPerSec = (uint64_t)( ((uint64_t)cIterations * cbTestSet) /* bytes */ + / ((long double)cNsElapsedRead / RT_NS_1SEC_64) /* seconds */ + / _1M /* MB */ ); + RTTestIValueF(cMBPerSec, RTTESTUNIT_MEGABYTES_PER_SEC, "cpu%02u-mem%02u-read", iAllocCpu, iAccessCpu); + + /* + * The read/write test. + */ + RTTimeNanoTS(); RTThreadYield(); + u64StartTS = RTTimeNanoTS(); + for (uint32_t i = 0; i < cIterations; i++) + { + ASMCompilerBarrier(); /* paranoia */ + memcpy(pvTest, (uint8_t *)pvTest + cbTestSet / 2, cbTestSet / 2); + } + uint64_t const cNsElapsedRW = RTTimeNanoTS() - u64StartTS; + cMBPerSec = (uint64_t)( ((uint64_t)cIterations * cbTestSet) /* bytes */ + / ((long double)cNsElapsedRW / RT_NS_1SEC_64) /* seconds */ + / _1M /* MB */ ); + RTTestIValueF(cMBPerSec, RTTESTUNIT_MEGABYTES_PER_SEC, "cpu%02u-mem%02u-read-write", iAllocCpu, iAccessCpu); + + /* + * Total time. + */ + RTTestIValueF(cNsElapsedRead + cNsElapsedWrite + cNsElapsedRW, RTTESTUNIT_NS, + "cpu%02u-mem%02u-time", iAllocCpu, iAccessCpu); + + /* advance */ + iAccessCpu = getNextCpu(iAccessCpu); + } + + /* + * Clean up and advance to the next CPU. + */ + RTMemPageFree(pvTest, cbTestSet); + iAllocCpu = getNextCpu(iAllocCpu); + } +} + + +int main(int argc, char **argv) +{ + RTTEST hTest; + RTEXITCODE rcExit = RTTestInitAndCreate("numa-1", &hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(hTest); + +#if defined(RT_ARCH_X86) || defined(RT_ARCH_AMD64) + /** @todo figure basic topology. */ +#endif + if (argc == 2) + g_cThreadsToSkip = RTStrToUInt8(argv[1]); + + doTest(hTest); + + return RTTestSummaryAndDestroy(hTest); +} + diff --git a/src/VBox/ValidationKit/utils/cpu/exceptionsR3-asm.asm b/src/VBox/ValidationKit/utils/cpu/exceptionsR3-asm.asm new file mode 100644 index 00000000..9b41aa04 --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/exceptionsR3-asm.asm @@ -0,0 +1,160 @@ +; $Id: exceptionsR3-asm.asm $ +;; @file +; exceptionsR3-asm.asm - assembly helpers. +; + +; +; Copyright (C) 2009-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "iprt/asmdefs.mac" + + +;******************************************************************************* +;* Defined Constants And Macros * +;******************************************************************************* +%ifdef RT_ARCH_AMD64 + %define TST_XCPT_MAGIC 0123456789abcdef0h +%else + %define TST_XCPT_MAGIC 012345678h +%endif + +%macro tstXcptAsmProlog 0 + push xBP + push xDI + push xSI + push xBX + %ifdef RT_ARCH_X86 + push gs + push fs + push es + push ds + %endif + %ifdef RT_ARCH_AMD64 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + %endif + + mov xAX, TST_XCPT_MAGIC + mov xBX, xAX + mov xCX, xAX + mov xDX, xAX + mov xDI, xAX + mov xSI, xAX + mov xBP, xAX + %ifdef RT_ARCH_AMD64 + mov r8, xAX + mov r9, xAX + mov r10, xAX + mov r11, xAX + mov r12, xAX + mov r13, xAX + mov r14, xAX + mov r15, xAX + %endif +%endmacro + +%macro tstXcptAsmEpilog 0 + %ifdef RT_ARCH_AMD64 + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + %endif + %ifdef RT_ARCH_X86 + pop ds + pop es + pop fs + pop gs + %endif + pop xBX + pop xSI + pop xDI + pop xBP +%endmacro + + +BEGINCODE + +;; +BEGINPROC tstXcptAsmNullPtrRead +; tstXcptAsmProlog + xor eax, eax +GLOBALNAME tstXcptAsmNullPtrRead_PC + mov al, [xAX] +; tstXcptAsmEpilog + ret +ENDPROC tstXcptAsmNullPtrRead + + +;; +BEGINPROC tstXcptAsmNullPtrWrite + tstXcptAsmProlog + xor eax, eax +GLOBALNAME tstXcptAsmNullPtrWrite_PC + mov [xAX], al + tstXcptAsmEpilog + ret +ENDPROC tstXcptAsmNullPtrWrite + + +;; +BEGINPROC tstXcptAsmSysCall + tstXcptAsmProlog +GLOBALNAME tstXcptAsmSysCall_PC + syscall + tstXcptAsmEpilog + ret +ENDPROC tstXcptAsmSysCall + + +;; +BEGINPROC tstXcptAsmSysEnter + tstXcptAsmProlog +GLOBALNAME tstXcptAsmSysEnter_PC +%ifdef RT_ARCH_AMD64 + db 00fh, 034h ; test this on 64-bit, yasm complains... +%else + sysenter +%endif + tstXcptAsmEpilog + ret +ENDPROC tstXcptAsmSysEnter + diff --git a/src/VBox/ValidationKit/utils/cpu/exceptionsR3.cpp b/src/VBox/ValidationKit/utils/cpu/exceptionsR3.cpp new file mode 100644 index 00000000..b8fde0d2 --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/exceptionsR3.cpp @@ -0,0 +1,272 @@ +/* $Id: exceptionsR3.cpp $ */ +/** @file + * exceptionsR3 - Tests various ring-3 CPU exceptions. + */ + +/* + * Copyright (C) 2009-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/cdefs.h> +#include <iprt/ctype.h> +#include <iprt/getopt.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/test.h> +#include <iprt/x86.h> + +#include <setjmp.h> + +#ifndef RT_OS_WINDOWS +# define USE_SIGNALS +# include <signal.h> +# include <stdlib.h> +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Executes a simple test. */ +#define TST_XCPT(Trapper, iTrap, uErr) \ + do \ + { \ + RTTestISub(#Trapper); \ + tstXcptReset(); \ + if (!setjmp(g_JmpBuf)) \ + { \ + tstXcptAsm##Trapper(); \ + RTTestIFailed("%s didn't trap (line no %u)", #Trapper, __LINE__); \ + } \ + else if ( (iTrap) != tstXcptCurTrap() \ + || (uErr) != tstXcptCurErr() ) \ + RTTestIFailed("%s trapped with %#x/%#x, expected %#x/%#x (line no %u)", \ + #Trapper, tstXcptCurTrap(), tstXcptCurErr(), (iTrap), (uErr), __LINE__); \ + else \ + RTTestISubDone(); \ + } while (0) + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Where to longjmp to when getting a signal/exception. */ +jmp_buf g_JmpBuf; +#ifdef USE_SIGNALS +/** Pending signal. + * -1 if no signal is pending. */ +int32_t volatile g_iSignal; +/** Pending signal info. */ +siginfo_t volatile g_SigInfo; +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +DECLASM(void) tstXcptAsmNullPtrRead(void); +DECLASM(void) tstXcptAsmNullPtrWrite(void); +DECLASM(void) tstXcptAsmSysEnter(void); +DECLASM(void) tstXcptAsmSysCall(void); + + + +#ifdef USE_SIGNALS +/** + * Generic signal handler. + */ +static void tstXcptSigHandler(int iSignal, siginfo_t *pSigInfo, void *pvCtx) +{ +#if 1 + RTStrmPrintf(g_pStdErr, "signal %d pSigInfo=%p pvCtx=%p", iSignal, pSigInfo, pvCtx); + if (pSigInfo) + RTStrmPrintf(g_pStdErr, " si_addr=%p si_code=%#x sival_ptr=%p sival_int=%d", + pSigInfo->si_addr, pSigInfo->si_code, pSigInfo->si_value.sival_ptr, pSigInfo->si_value.sival_int); + RTStrmPrintf(g_pStdErr, "\n"); +#endif + if (g_iSignal == -1) + { + g_iSignal = iSignal; + if (pSigInfo) + memcpy((void *)&g_SigInfo, pSigInfo, sizeof(g_SigInfo)); + longjmp(g_JmpBuf, 1); + } + else + { + /* we're up the infamous creek... */ + _Exit(2); + } +} + +#elif defined(RT_OS_WINDOWS) +/** @todo */ +//# error "PORTME" + +#else +# error "PORTME" +#endif + + +/** Reset the current exception state and get ready for a new trap. */ +static void tstXcptReset(void) +{ +#ifdef USE_SIGNALS + g_iSignal = -1; + memset((void *)&g_SigInfo, 0, sizeof(g_SigInfo)); +#endif +} + + + +/** Get the current intel trap number. Returns -1 if none. */ +static int tstXcptCurTrap(void) +{ +#ifdef USE_SIGNALS + /** @todo this is just a quick sketch. */ + switch (g_iSignal) + { + case SIGBUS: +# ifdef RT_OS_DARWIN + if (g_SigInfo.si_code == 2 /*KERN_PROTECTION_FAILURE*/) + return X86_XCPT_PF; +# endif + return X86_XCPT_GP; + + case SIGSEGV: + return X86_XCPT_GP; + } +#endif + return -1; +} + + +/** Get the exception error code if applicable. */ +static uint32_t tstXcptCurErr(void) +{ +#ifdef USE_SIGNALS + /** @todo this is just a quick sketch. */ + switch (g_iSignal) + { + case SIGBUS: +# ifdef RT_OS_DARWIN + if (g_SigInfo.si_code == 2 /*KERN_PROTECTION_FAILURE*/) + return 0; +# endif + break; + + case SIGSEGV: + break; + } +#endif + return UINT32_MAX; +} + + +int main(int argc, char **argv) +{ + /* + * Prolog. + */ + RTTEST hTest; + int rc = RTTestInitAndCreate("exceptionsR3", &hTest); + if (rc) + return rc; + + /* + * Parse options. + */ + bool volatile fRawMode = false; + static const RTGETOPTDEF s_aOptions[] = + { + { "--raw-mode", 'r', RTGETOPT_REQ_NOTHING }, + }; + + RTGETOPTUNION ValUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0); + while ((rc = RTGetOpt(&GetState, &ValUnion))) + { + switch (rc) + { + case 'r': + fRawMode = true; + break; + + default: + return RTGetOptPrintError(rc, &ValUnion); + } + } + + /* + * Test setup. + */ +#ifdef USE_SIGNALS + struct sigaction Act; + RT_ZERO(Act); + Act.sa_sigaction = tstXcptSigHandler; + Act.sa_flags = SA_SIGINFO; + sigfillset(&Act.sa_mask); + + sigaction(SIGILL, &Act, NULL); + sigaction(SIGTRAP, &Act, NULL); +# ifdef SIGEMT + sigaction(SIGEMT, &Act, NULL); +# endif + sigaction(SIGFPE, &Act, NULL); + sigaction(SIGBUS, &Act, NULL); + sigaction(SIGSEGV, &Act, NULL); + +#else + /** @todo Implement this using structured exception handling on Windows and + * OS/2. */ +#endif + + /* + * The tests. + */ + RTTestBanner(hTest); + TST_XCPT(NullPtrRead, X86_XCPT_PF, 0); + TST_XCPT(NullPtrWrite, X86_XCPT_PF, 0); + if (fRawMode) + { + TST_XCPT(SysEnter, X86_XCPT_GP, 0); + TST_XCPT(SysCall, X86_XCPT_UD, 0); + } + + /* + * Epilog. + */ + return RTTestSummaryAndDestroy(hTest); +} + diff --git a/src/VBox/ValidationKit/utils/cpu/rdtsc-asm.asm b/src/VBox/ValidationKit/utils/cpu/rdtsc-asm.asm new file mode 100644 index 00000000..a18528b8 --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/rdtsc-asm.asm @@ -0,0 +1,162 @@ +; $Id: rdtsc-asm.asm $ +;; @file +; RDTSC test, assembly code +; + +; +; Copyright (C) 2009-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;********************************************************************************************************************************* +;* Header Files * +;********************************************************************************************************************************* +%include "iprt/asmdefs.mac" +%include "iprt/x86.mac" + + +;********************************************************************************************************************************* +;* Global Variables * +;********************************************************************************************************************************* +BEGINDATA +;; +; Where DoTscReads() returns the rdtsc values. +; +; @note The results are 32-bit value pairs in x86 mode and 64-bit pairs in +; AMD64 mode. +GLOBALNAME g_aRdTscResults +%ifdef RT_ARCH_AMD64 + dq 0, 0 + dq 0, 0 ; first value stored + dq 0, 0 + dq 0, 0 + dq 0, 0 + dq 0, 0 + dq 0, 0 +%else + dq 0, 0 + dd 0, 0 ; first value stored + dd 0, 0 + dd 0, 0 +%endif + + +BEGINCODE + +;; Takes no arguments, returns number of values read into g_aRdTscResults. +BEGINPROC DoTscReads + push xBP + mov xBP, xSP +%ifdef RT_ARCH_AMD64 + mov rax, 0feedfacecafebabeh + mov rdx, 0cafebabefeedfaceh + mov r8, 0deadbeefdeadbeefh + mov r9, 0deadbeefdeadbeefh + mov r10, 0deadbeefdeadbeefh + mov r11, 0deadbeefdeadbeefh + push rbx + push r12 + push r13 + push r14 + push r15 + + ; Read 6x TSC into registers. + rdtsc + mov r8, rax + mov r9, rdx + rdtsc + mov r10, rax + mov r11, rdx + rdtsc + mov r12, rax + mov r13, rdx + rdtsc + mov r14, rax + mov r15, rdx + rdtsc + mov rbx, rax + mov rcx, rdx + rdtsc + + ; Store the values (64-bit). + mov [NAME(g_aRdTscResults) + 10h xWrtRIP], r8 + mov [NAME(g_aRdTscResults) + 18h xWrtRIP], r9 + mov [NAME(g_aRdTscResults) + 20h xWrtRIP], r10 + mov [NAME(g_aRdTscResults) + 28h xWrtRIP], r11 + mov [NAME(g_aRdTscResults) + 30h xWrtRIP], r12 + mov [NAME(g_aRdTscResults) + 38h xWrtRIP], r13 + mov [NAME(g_aRdTscResults) + 40h xWrtRIP], r14 + mov [NAME(g_aRdTscResults) + 48h xWrtRIP], r15 + mov [NAME(g_aRdTscResults) + 50h xWrtRIP], rbx + mov [NAME(g_aRdTscResults) + 58h xWrtRIP], rcx + mov [NAME(g_aRdTscResults) + 60h xWrtRIP], rax + mov [NAME(g_aRdTscResults) + 68h xWrtRIP], rdx + + pop r15 + pop r14 + pop r13 + pop r12 + pop rbx + + mov eax, 6 +%else + mov eax, 0feedfaceh + mov edx, 0cafebabeh + push esi + push edi + push ebx + + ; Read 3x TSC into registers. + rdtsc + mov ebx, eax + mov ecx, edx + rdtsc + mov esi, eax + mov edi, edx + rdtsc + + ; Store values. + mov [NAME(g_aRdTscResults) + 08h], ebx + mov [NAME(g_aRdTscResults) + 0ch], ecx + mov [NAME(g_aRdTscResults) + 10h], esi + mov [NAME(g_aRdTscResults) + 14h], edi + mov [NAME(g_aRdTscResults) + 18h], eax + mov [NAME(g_aRdTscResults) + 1ch], edx + + pop ebx + pop edi + pop esi + + mov eax, 3 +%endif + leave + ret +ENDPROC DoTscReads + diff --git a/src/VBox/ValidationKit/utils/cpu/rdtsc.cpp b/src/VBox/ValidationKit/utils/cpu/rdtsc.cpp new file mode 100644 index 00000000..f9d5564e --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/rdtsc.cpp @@ -0,0 +1,294 @@ +/* $Id: rdtsc.cpp $ */ +/** @file + * rdtsc - Test if three consecutive rdtsc instructions return different values. + */ + +/* + * Copyright (C) 2009-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/time.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct RDTSCRESULT +{ + RTCCUINTREG uLow, uHigh; +} RDTSCRESULT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +extern "C" RDTSCRESULT g_aRdTscResults[]; /* rdtsc-asm.asm */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +/** + * Does 3 (32-bit) or 6 (64-bit) fast TSC reads and stores the result + * in g_aRdTscResults, starting with the 2nd entry. + * + * Starting the result storing at g_aRdTscResults[1] make it easy to do the + * comparisons in a loop. + * + * @returns Number of results read into g_aRdTscResults[1] and onwards. + */ +DECLASM(uint32_t) DoTscReads(void); + + + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Tunables. + */ + uint64_t offJumpThreshold = _4G * 2; + unsigned cMaxLoops = 10000000; + unsigned cStatusEvery = 2000000; + unsigned cMinSeconds = 0; + + for (int i = 1; i < argc; i++) + { + const char *psz = argv[i]; + if (*psz == '-') + { + psz++; + char chOpt; + while ((chOpt = *psz++) != '\0') + { + /* Option value. */ + const char *pszValue = NULL; + uint64_t uValue = 0; + switch (chOpt) + { + case 'l': + case 's': + case 'm': + if (*psz == '\0') + { + if (i + 1 >= argc) + return RTMsgSyntax("The %c option requires a value", chOpt); + pszValue = argv[++i]; + } + else + pszValue = psz + (*psz == ':' || *psz == '='); + switch (chOpt) + { + case 'l': + case 's': + case 'm': + { + char *pszNext = NULL; + rc = RTStrToUInt64Ex(pszValue, &pszNext, 0, &uValue); + if (RT_FAILURE(rc)) + return RTMsgSyntax("Bad number: %s (%Rrc)", pszValue, rc); + if (pszNext && *pszNext != '\0') + { + if (*pszNext == 'M'&& pszNext[1] == '\0') + uValue *= _1M; + else if (*pszNext == 'K' && pszNext[1] == '\0') + uValue *= _1K; + else if (*pszNext == 'G' && pszNext[1] == '\0') + uValue *= _1G; + else + return RTMsgSyntax("Bad value format for option %c: %s", chOpt, pszValue); + } + break; + } + } + break; + } + + /* handle the option. */ + switch (chOpt) + { + case 'l': + cMaxLoops = uValue; + break; + + case 'm': + cMinSeconds = uValue; + break; + + case 's': + cStatusEvery = uValue; + break; + + case 'h': + case '?': + RTPrintf("usage: rdtsc [-l <loops>] [-s <loops-between-status>]\n" + " [-m <minimum-seconds-to-run>]\n"); + return RTEXITCODE_SUCCESS; + + default: + return RTMsgSyntax("Unknown option %c (argument %d)\n", chOpt, i); + } + } + } + else + return RTMsgSyntax("argument %d (%s): not an option\n", i, psz); + } + + /* + * Do the job. + */ + uint64_t const nsTsStart = RTTimeNanoTS(); + unsigned cOuterLoops = 0; + unsigned cLoopsToNextStatus = cStatusEvery; + unsigned cRdTscInstructions = 0; + unsigned cBackwards = 0; + unsigned cSame = 0; + unsigned cBadValues = 0; + unsigned cJumps = 0; + uint64_t offMaxJump = 0; + uint64_t offMinIncr = UINT64_MAX; + uint64_t offMaxIncr = 0; + + g_aRdTscResults[0] = g_aRdTscResults[DoTscReads() - 1]; + + for (;;) + { + for (unsigned iLoop = 0; iLoop < cMaxLoops; iLoop++) + { + uint32_t const cResults = DoTscReads(); + cRdTscInstructions += cResults; + + for (uint32_t i = 0; i < cResults; i++) + { + uint64_t uPrev = RT_MAKE_U64((uint32_t)g_aRdTscResults[i ].uLow, (uint32_t)g_aRdTscResults[i ].uHigh); + uint64_t uCur = RT_MAKE_U64((uint32_t)g_aRdTscResults[i + 1].uLow, (uint32_t)g_aRdTscResults[i + 1].uHigh); + if (RT_LIKELY(uCur != uPrev)) + { + int64_t offDelta = uCur - uPrev; + if (RT_LIKELY(offDelta >= 0)) + { + if (RT_LIKELY((uint64_t)offDelta < offJumpThreshold)) + { + if ((uint64_t)offDelta < offMinIncr) + offMinIncr = offDelta; + if ((uint64_t)offDelta > offMaxIncr && i != 0) + offMaxIncr = offDelta; + } + else + { + cJumps++; + if ((uint64_t)offDelta > offMaxJump) + offMaxJump = offDelta; + RTPrintf("%u/%u: Jump: %#010x`%08x -> %#010x`%08x\n", cOuterLoops, iLoop, + (unsigned)g_aRdTscResults[i].uHigh, (unsigned)g_aRdTscResults[i].uLow, + (unsigned)g_aRdTscResults[i + 1].uHigh, (unsigned)g_aRdTscResults[i + 1].uLow); + } + } + else + { + cBackwards++; + RTPrintf("%u/%u: Back: %#010x`%08x -> %#010x`%08x\n", cOuterLoops, iLoop, + (unsigned)g_aRdTscResults[i].uHigh, (unsigned)g_aRdTscResults[i].uLow, + (unsigned)g_aRdTscResults[i + 1].uHigh, (unsigned)g_aRdTscResults[i + 1].uLow); + } + } + else + { + cSame++; + RTPrintf("%u/%u: Same: %#010x`%08x -> %#010x`%08x\n", cOuterLoops, iLoop, + (unsigned)g_aRdTscResults[i].uHigh, (unsigned)g_aRdTscResults[i].uLow, + (unsigned)g_aRdTscResults[i + 1].uHigh, (unsigned)g_aRdTscResults[i + 1].uLow); + } +#if ARCH_BITS == 64 + if ((g_aRdTscResults[i + 1].uLow >> 32) || (g_aRdTscResults[i + 1].uHigh >> 32)) + cBadValues++; +#endif + } + + /* Copy the last value for the next iteration. */ + g_aRdTscResults[0] = g_aRdTscResults[cResults]; + + /* Display status. */ + if (RT_LIKELY(--cLoopsToNextStatus > 0)) + { /* likely */ } + else + { + cLoopsToNextStatus = cStatusEvery; + RTPrintf("%u/%u: %#010x`%08x\n", cOuterLoops, iLoop, + (unsigned)g_aRdTscResults[cResults].uHigh, (unsigned)g_aRdTscResults[cResults].uLow); + } + } + + /* + * Check minimum number of seconds. + */ + cOuterLoops++; + if (!cMinSeconds) + break; + uint64_t nsElapsed = RTTimeNanoTS() - nsTsStart; + if (nsElapsed >= cMinSeconds * RT_NS_1SEC_64) + break; + } + + /* + * Summary. + */ + if (cBackwards == 0 && cSame == 0 && cJumps == 0 && cBadValues == 0) + { + RTPrintf("rdtsc: Success (%u RDTSC over %u*%u loops, deltas: %#x`%08x..%#x`%08x)\n", + cRdTscInstructions, cOuterLoops, cMaxLoops, + (unsigned)(offMinIncr >> 32), (unsigned)offMinIncr, (unsigned)(offMaxIncr >> 32), (unsigned)offMaxIncr); + return RTEXITCODE_SUCCESS; + } + RTPrintf("RDTSC instructions: %u\n", cRdTscInstructions); + RTPrintf("Loops: %u * %u => %u\n", cMaxLoops, cOuterLoops, cOuterLoops * cMaxLoops); + RTPrintf("Backwards: %u\n", cBackwards); + RTPrintf("Jumps: %u\n", cJumps); + RTPrintf("Max jumps: %#010x`%08x\n", (unsigned)(offMaxJump >> 32), (unsigned)offMaxJump); + RTPrintf("Same value: %u\n", cSame); + RTPrintf("Bad values: %u\n", cBadValues); + RTPrintf("Min increment: %#010x`%08x\n", (unsigned)(offMinIncr >> 32), (unsigned)offMinIncr); + RTPrintf("Max increment: %#010x`%08x\n", (unsigned)(offMaxIncr >> 32), (unsigned)offMaxIncr); + return RTEXITCODE_FAILURE; +} + diff --git a/src/VBox/ValidationKit/utils/cpu/xmmsaving-asm.asm b/src/VBox/ValidationKit/utils/cpu/xmmsaving-asm.asm new file mode 100644 index 00000000..fff6865d --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/xmmsaving-asm.asm @@ -0,0 +1,162 @@ +; $Id: xmmsaving-asm.asm $ +;; @file +; xmmsaving - assembly helpers. +; + +; +; Copyright (C) 2009-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +%include "iprt/asmdefs.mac" +%include "VBox/vmm/stam.mac" + + +BEGINCODE + + +;; +; DECLASM(int) XmmSavingTestLoadSet(const MYXMMREGSET *pSet, const MYXMMREGSET *pPrevSet, PRTUINT128U pBadVal); +; +; @returns 0 on success, 1-based register number on failure. +; @param pSet The new set. +; @param pPrevSet The previous set. Can be NULL. +; @param pBadVal Where to store the actual register value on failure. +; +BEGINPROC XmmSavingTestLoadSet + push xBP + mov xBP, xSP + sub xSP, 32 ; Space for storing an XMM register (in TEST_REG). + and xSP, ~31 ; Align it. + + ; Unify register/arguments. +%ifdef ASM_CALL64_GCC + mov r8, rdx ; pBadVal + mov xCX, rdi ; pSet + mov xDX, rsi ; pPrevSet +%endif +%ifdef RT_ARCH_X86 + mov xCX, [ebp + 8] ; pSet + mov xDX, [ebp + 12] ; pPrevSet +%endif + + test xDX, xDX + jz near .just_load + + ; Check that the old set is still correct. +%macro TEST_REG 1, + movdqa [xSP], xmm %+ %1 + mov xAX, [xDX + %1 * 8] + cmp [xSP], xAX + jne %%bad + mov xAX, [xDX + %1 * 8 + xCB] + cmp [xSP + xCB], xAX +%ifdef RT_ARCH_X86 + jne %%bad + mov xAX, [xDX + %1 * 8 + xCB*2] + cmp [xSP + xCB*2], xAX + jne %%bad + mov xAX, [xDX + %1 * 8 + xCB*3] + cmp [xSP + xCB*3], xAX +%endif + je %%next +%%bad: + mov eax, %1 + 1 + jmp .return_copy_badval +%%next: +%endmacro + + TEST_REG 0 + TEST_REG 1 + TEST_REG 2 + TEST_REG 3 + TEST_REG 4 + TEST_REG 5 + TEST_REG 6 + TEST_REG 7 +%ifdef RT_ARCH_AMD64 + TEST_REG 8 + TEST_REG 9 + TEST_REG 10 + TEST_REG 11 + TEST_REG 12 + TEST_REG 13 + TEST_REG 14 + TEST_REG 15 +%endif + + ; Load the new state. +.just_load: + movdqu xmm0, [xCX + 0*8] + movdqu xmm1, [xCX + 1*8] + movdqu xmm2, [xCX + 2*8] + movdqu xmm3, [xCX + 3*8] + movdqu xmm4, [xCX + 4*8] + movdqu xmm5, [xCX + 5*8] + movdqu xmm6, [xCX + 6*8] + movdqu xmm7, [xCX + 7*8] +%ifdef RT_ARCH_AMD64 + movdqu xmm8, [xCX + 8*8] + movdqu xmm9, [xCX + 9*8] + movdqu xmm10, [xCX + 10*8] + movdqu xmm11, [xCX + 11*8] + movdqu xmm12, [xCX + 12*8] + movdqu xmm13, [xCX + 13*8] + movdqu xmm14, [xCX + 14*8] + movdqu xmm15, [xCX + 15*8] +%endif + xor eax, eax + jmp .return + +.return_copy_badval: + ; don't touch eax here. +%ifdef RT_ARCH_X86 + mov edx, [ebp + 16] + mov ecx, [esp] + mov [edx ], ecx + mov ecx, [esp + 4] + mov [edx + 4], ecx + mov ecx, [esp + 8] + mov [edx + 8], ecx + mov ecx, [esp + 12] + mov [edx + 12], ecx +%else + mov rdx, [rsp] + mov rcx, [rsp + 8] + mov [r8], rdx + mov [r8 + 8], rcx +%endif + jmp .return + +.return: + leave + ret +ENDPROC XmmSavingTestLoadSet + diff --git a/src/VBox/ValidationKit/utils/cpu/xmmsaving.cpp b/src/VBox/ValidationKit/utils/cpu/xmmsaving.cpp new file mode 100644 index 00000000..20c3573c --- /dev/null +++ b/src/VBox/ValidationKit/utils/cpu/xmmsaving.cpp @@ -0,0 +1,130 @@ +/* $Id: xmmsaving.cpp $ */ +/** @file + * xmmsaving - Test that all XMM register state is handled correctly and + * not corrupted the VMM. + */ + +/* + * Copyright (C) 2009-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/test.h> +#include <iprt/x86.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct MYXMMREGSET +{ + RTUINT128U aRegs[16]; +} MYXMMREGSET; + + +DECLASM(int) XmmSavingTestLoadSet(const MYXMMREGSET *pSet, const MYXMMREGSET *pPrevSet, PRTUINT128U pBadVal); + + +static void XmmSavingTest(void) +{ + RTTestISub("xmm saving and restoring"); + + /* Create the test sets. */ + static MYXMMREGSET s_aSets[256]; + for (unsigned s = 0; s < RT_ELEMENTS(s_aSets); s++) + { + for (unsigned r = 0; r < RT_ELEMENTS(s_aSets[s].aRegs); r++) + { + unsigned x = (s << 4) | r; + s_aSets[s].aRegs[r].au32[0] = x | UINT32_C(0x12345000); + s_aSets[s].aRegs[r].au32[1] = (x << 8) | UINT32_C(0x88700011); + s_aSets[s].aRegs[r].au32[2] = (x << 16) | UINT32_C(0xe000dcba); + s_aSets[s].aRegs[r].au32[3] = (x << 20) | UINT32_C(0x00087654); + } + } + + /* Do the actual testing. */ + const MYXMMREGSET *pPrev2 = NULL; + const MYXMMREGSET *pPrev = NULL; + for (int i = 0; i < 1000000; i++) + { + if ((i % 50000) == 0) + { + RTTestIPrintf(RTTESTLVL_ALWAYS, "."); + pPrev = pPrev2 = NULL; /* May be trashed by the above call. */ + } + for (unsigned s = 0; s < RT_ELEMENTS(s_aSets); s++) + { + RTUINT128U BadVal; + const MYXMMREGSET *pSet = &s_aSets[s]; + int r = XmmSavingTestLoadSet(pSet, pPrev, &BadVal); + if (r-- != 0) + { + RTTestIFailed("i=%d s=%d r=%d", i, s, r); + RTTestIFailureDetails("XMM%-2d = %08x,%08x,%08x,%08x\n", + r, + BadVal.au32[0], + BadVal.au32[1], + BadVal.au32[2], + BadVal.au32[3]); + RTTestIFailureDetails("Expected %08x,%08x,%08x,%08x\n", + pPrev->aRegs[r].au32[0], + pPrev->aRegs[r].au32[1], + pPrev->aRegs[r].au32[2], + pPrev->aRegs[r].au32[3]); + if (pPrev2) + RTTestIFailureDetails("PrevPrev %08x,%08x,%08x,%08x\n", + pPrev2->aRegs[r].au32[0], + pPrev2->aRegs[r].au32[1], + pPrev2->aRegs[r].au32[2], + pPrev2->aRegs[r].au32[3]); + return; + } + pPrev2 = pPrev; + pPrev = pSet; + } + } + RTTestISubDone(); +} + + +int main() +{ + RTTEST hTest; + int rc = RTTestInitAndCreate("xmmsaving", &hTest); + if (rc) + return rc; + XmmSavingTest(); + return RTTestSummaryAndDestroy(hTest); +} + diff --git a/src/VBox/ValidationKit/utils/dos/DosSleep.c b/src/VBox/ValidationKit/utils/dos/DosSleep.c new file mode 100644 index 00000000..b4ba1815 --- /dev/null +++ b/src/VBox/ValidationKit/utils/dos/DosSleep.c @@ -0,0 +1,67 @@ +/* $Id: DosSleep.c $ */ +/** @file + * 16-bit DOS sleep program. + * + * Build: wcl -I%WATCOM%\h\win -l=dos -k4096 -fm -W4 DosSleep.c + */ + +/* + * Copyright (C) 2018-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + + + +int main(int argc, char **argv) +{ + int iExit; + + if (argc == 2) + { + int cSeconds = atoi(argv[1]); + sleep(cSeconds); + iExit = 0; + } + else + { + fprintf(stderr, "syntax error: only expected a number of seconds\n"); + iExit = 4; + } + + return iExit; +} + diff --git a/src/VBox/ValidationKit/utils/dos/DosVmOff.asm b/src/VBox/ValidationKit/utils/dos/DosVmOff.asm new file mode 100644 index 00000000..7fc48987 --- /dev/null +++ b/src/VBox/ValidationKit/utils/dos/DosVmOff.asm @@ -0,0 +1,79 @@ +; $Id: DosVmOff.asm $ +;; @file +; 16-bit DOS COM program that powers off the VM. +; +; Build: yasm -f bin -i../../../../../include/ DosVmOff.asm -o DosVmOff.com +; + +; +; Copyright (C) 2018-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + + +%include "VBox/bios.mac" + + org 100h + +segment text +main: +%if 0 + ; Setup stack. + mov ax, stack + mov ss, ax + mov sp, top_of_stack +%endif + + ; Do the shutdown thing. + mov ax, cs + mov ds, ax + + mov bl, 64 + mov dx, VBOX_BIOS_SHUTDOWN_PORT + mov ax, VBOX_BIOS_OLD_SHUTDOWN_PORT +.retry: + mov cx, 8 + mov si, .s_szShutdown + rep outsb + xchg ax, dx ; alternate between the new (VBox) and old (Bochs) ports. + dec bl + jnz .retry + + + ; Probably not a VBox VM, exit the program with errorlevel 1. +.whatever: + mov ax, 04c01h + int 021h + hlt + jmp .whatever + +.s_szShutdown: + db 'Shutdown', 0 + diff --git a/src/VBox/ValidationKit/utils/dos/WinExit.asm b/src/VBox/ValidationKit/utils/dos/WinExit.asm new file mode 100644 index 00000000..c43cfd62 --- /dev/null +++ b/src/VBox/ValidationKit/utils/dos/WinExit.asm @@ -0,0 +1,95 @@ +; $Id: WinExit.asm $ +;; @file +; 16-bit windows program that exits windows. +; +; Build: wcl -I%WATCOM%\h\win -l=windows -k4096 -fm WinExit.asm +; + +; +; Copyright (C) 2018-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + + +;.stack 4096 +STACK segment para stack 'STACK' +STACK ends + + +extrn INITTASK:FAR +extrn INITAPP:FAR +extrn EXITWINDOWS:FAR +extrn WAITEVENT:FAR + +_TEXT segment word public 'CODE' +start: + push bp + mov bp, sp + + ; + ; Initialize the windows app. + ; + call INITTASK + + xor ax, ax + push ax + call WAITEVENT + + push di ; hInstance + push di + call INITAPP + + ; + ; Do what we're here for, exitting windows. + ; + xor ax, ax + xor cx, cx + xor dx, dx + push ax + push ax + push ax + push ax + call EXITWINDOWS + + ; + ; Exit via DOS interrupt. + ; + xor al, al + mov ah,04cH + int 021h + + mov sp, bp + pop bp + ret + +_TEXT ends + +end start + diff --git a/src/VBox/ValidationKit/utils/fs/FsPerf.cpp b/src/VBox/ValidationKit/utils/fs/FsPerf.cpp new file mode 100644 index 00000000..e032e18c --- /dev/null +++ b/src/VBox/ValidationKit/utils/fs/FsPerf.cpp @@ -0,0 +1,6834 @@ +/* $Id: FsPerf.cpp $ */ +/** @file + * FsPerf - File System (Shared Folders) Performance Benchmark. + */ + +/* + * Copyright (C) 2019-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef RT_OS_OS2 +# define INCL_BASE +# include <os2.h> +# undef RT_MAX +#endif +#include <iprt/alloca.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/param.h> +#include <iprt/path.h> +#ifdef RT_OS_LINUX +# include <iprt/pipe.h> +#endif +#include <iprt/process.h> +#include <iprt/rand.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/system.h> +#include <iprt/tcp.h> +#include <iprt/test.h> +#include <iprt/time.h> +#include <iprt/thread.h> +#include <iprt/zero.h> + +#ifdef RT_OS_WINDOWS +# include <iprt/nt/nt-and-windows.h> +#else +# include <errno.h> +# include <unistd.h> +# include <limits.h> +# include <sys/types.h> +# include <sys/fcntl.h> +# ifndef RT_OS_OS2 +# include <sys/mman.h> +# include <sys/uio.h> +# endif +# include <sys/socket.h> +# include <signal.h> +# ifdef RT_OS_LINUX +# include <sys/sendfile.h> +# include <sys/syscall.h> +# endif +# ifdef RT_OS_DARWIN +# include <sys/uio.h> +# endif +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Used for cutting the the -d parameter value short and avoid a number of buffer overflow checks. */ +#define FSPERF_MAX_NEEDED_PATH 224 +/** The max path used by this code. + * It greatly exceeds the RTPATH_MAX so we can push the limits on windows. */ +#define FSPERF_MAX_PATH (_32K) + +/** EOF marker character used by the master/slave comms. */ +#define FSPERF_EOF 0x1a +/** EOF marker character used by the master/slave comms, string version. */ +#define FSPERF_EOF_STR "\x1a" + +/** @def FSPERF_TEST_SENDFILE + * Whether to enable the sendfile() tests. */ +#if defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) +# define FSPERF_TEST_SENDFILE +#endif + +/** + * Macro for profiling @a a_fnCall (typically forced inline) for about @a a_cNsTarget ns. + * + * Always does an even number of iterations. + */ +#define PROFILE_FN(a_fnCall, a_cNsTarget, a_szDesc) \ + do { \ + /* Estimate how many iterations we need to fill up the given timeslot: */ \ + fsPerfYield(); \ + uint64_t nsStart = RTTimeNanoTS(); \ + uint64_t nsPrf; \ + do \ + nsPrf = RTTimeNanoTS(); \ + while (nsPrf == nsStart); \ + nsStart = nsPrf; \ + \ + uint64_t iIteration = 0; \ + do \ + { \ + RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \ + iIteration++; \ + nsPrf = RTTimeNanoTS() - nsStart; \ + } while (nsPrf < RT_NS_10MS || (iIteration & 1)); \ + nsPrf /= iIteration; \ + if (nsPrf > g_nsPerNanoTSCall + 32) \ + nsPrf -= g_nsPerNanoTSCall; \ + \ + uint64_t cIterations = (a_cNsTarget) / nsPrf; \ + if (cIterations <= 1) \ + cIterations = 2; \ + else if (cIterations & 1) \ + cIterations++; \ + \ + /* Do the actual profiling: */ \ + fsPerfYield(); \ + iIteration = 0; \ + nsStart = RTTimeNanoTS(); \ + for (; iIteration < cIterations; iIteration++) \ + RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \ + nsPrf = RTTimeNanoTS() - nsStart; \ + RTTestIValue(a_szDesc, nsPrf / cIterations, RTTESTUNIT_NS_PER_OCCURRENCE); \ + if (g_fShowDuration) \ + RTTestIValueF(nsPrf, RTTESTUNIT_NS, "%s duration", a_szDesc); \ + if (g_fShowIterations) \ + RTTestIValueF(iIteration, RTTESTUNIT_OCCURRENCES, "%s iterations", a_szDesc); \ + } while (0) + + +/** + * Macro for profiling an operation on each file in the manytree directory tree. + * + * Always does an even number of tree iterations. + */ +#define PROFILE_MANYTREE_FN(a_szPath, a_fnCall, a_cEstimationIterations, a_cNsTarget, a_szDesc) \ + do { \ + if (!g_fManyFiles) \ + break; \ + \ + /* Estimate how many iterations we need to fill up the given timeslot: */ \ + fsPerfYield(); \ + uint64_t nsStart = RTTimeNanoTS(); \ + uint64_t ns; \ + do \ + ns = RTTimeNanoTS(); \ + while (ns == nsStart); \ + nsStart = ns; \ + \ + PFSPERFNAMEENTRY pCur; \ + uint64_t iIteration = 0; \ + do \ + { \ + RTListForEach(&g_ManyTreeHead, pCur, FSPERFNAMEENTRY, Entry) \ + { \ + memcpy(a_szPath, pCur->szName, pCur->cchName); \ + for (uint32_t i = 0; i < g_cManyTreeFilesPerDir; i++) \ + { \ + RTStrFormatU32(&a_szPath[pCur->cchName], sizeof(a_szPath) - pCur->cchName, i, 10, 5, 5, RTSTR_F_ZEROPAD); \ + RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \ + } \ + } \ + iIteration++; \ + ns = RTTimeNanoTS() - nsStart; \ + } while (ns < RT_NS_10MS || (iIteration & 1)); \ + ns /= iIteration; \ + if (ns > g_nsPerNanoTSCall + 32) \ + ns -= g_nsPerNanoTSCall; \ + \ + uint32_t cIterations = (a_cNsTarget) / ns; \ + if (cIterations <= 1) \ + cIterations = 2; \ + else if (cIterations & 1) \ + cIterations++; \ + \ + /* Do the actual profiling: */ \ + fsPerfYield(); \ + uint32_t cCalls = 0; \ + nsStart = RTTimeNanoTS(); \ + for (iIteration = 0; iIteration < cIterations; iIteration++) \ + { \ + RTListForEach(&g_ManyTreeHead, pCur, FSPERFNAMEENTRY, Entry) \ + { \ + memcpy(a_szPath, pCur->szName, pCur->cchName); \ + for (uint32_t i = 0; i < g_cManyTreeFilesPerDir; i++) \ + { \ + RTStrFormatU32(&a_szPath[pCur->cchName], sizeof(a_szPath) - pCur->cchName, i, 10, 5, 5, RTSTR_F_ZEROPAD); \ + RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \ + cCalls++; \ + } \ + } \ + } \ + ns = RTTimeNanoTS() - nsStart; \ + RTTestIValueF(ns / cCalls, RTTESTUNIT_NS_PER_OCCURRENCE, a_szDesc); \ + if (g_fShowDuration) \ + RTTestIValueF(ns, RTTESTUNIT_NS, "%s duration", a_szDesc); \ + if (g_fShowIterations) \ + RTTestIValueF(iIteration, RTTESTUNIT_OCCURRENCES, "%s iterations", a_szDesc); \ + } while (0) + + +/** + * Execute a_fnCall for each file in the manytree. + */ +#define DO_MANYTREE_FN(a_szPath, a_fnCall) \ + do { \ + PFSPERFNAMEENTRY pCur; \ + RTListForEach(&g_ManyTreeHead, pCur, FSPERFNAMEENTRY, Entry) \ + { \ + memcpy(a_szPath, pCur->szName, pCur->cchName); \ + for (uint32_t i = 0; i < g_cManyTreeFilesPerDir; i++) \ + { \ + RTStrFormatU32(&a_szPath[pCur->cchName], sizeof(a_szPath) - pCur->cchName, i, 10, 5, 5, RTSTR_F_ZEROPAD); \ + a_fnCall; \ + } \ + } \ + } while (0) + + +/** @def FSPERF_VERR_PATH_NOT_FOUND + * Hides the fact that we only get VERR_PATH_NOT_FOUND on non-unix systems. */ +#if defined(RT_OS_WINDOWS) //|| defined(RT_OS_OS2) - using posix APIs IIRC, so lost in translation. +# define FSPERF_VERR_PATH_NOT_FOUND VERR_PATH_NOT_FOUND +#else +# define FSPERF_VERR_PATH_NOT_FOUND VERR_FILE_NOT_FOUND +#endif + +#ifdef RT_OS_WINDOWS +/** @def CHECK_WINAPI + * Checks a windows API call, reporting the last error on failure. */ +# define CHECK_WINAPI_CALL(a_CallAndTestExpr) \ + if (!(a_CallAndTestExpr)) { \ + RTTestIFailed("line %u: %s failed - last error %u, last status %#x", \ + __LINE__, #a_CallAndTestExpr, GetLastError(), RTNtLastStatusValue()); \ + } else do {} while (0) +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct FSPERFNAMEENTRY +{ + RTLISTNODE Entry; + uint16_t cchName; + RT_FLEXIBLE_ARRAY_EXTENSION + char szName[RT_FLEXIBLE_ARRAY]; +} FSPERFNAMEENTRY; +typedef FSPERFNAMEENTRY *PFSPERFNAMEENTRY; + + +enum +{ + kCmdOpt_First = 128, + + kCmdOpt_ManyFiles = kCmdOpt_First, + kCmdOpt_NoManyFiles, + kCmdOpt_Open, + kCmdOpt_NoOpen, + kCmdOpt_FStat, + kCmdOpt_NoFStat, +#ifdef RT_OS_WINDOWS + kCmdOpt_NtQueryInfoFile, + kCmdOpt_NoNtQueryInfoFile, + kCmdOpt_NtQueryVolInfoFile, + kCmdOpt_NoNtQueryVolInfoFile, +#endif + kCmdOpt_FChMod, + kCmdOpt_NoFChMod, + kCmdOpt_FUtimes, + kCmdOpt_NoFUtimes, + kCmdOpt_Stat, + kCmdOpt_NoStat, + kCmdOpt_ChMod, + kCmdOpt_NoChMod, + kCmdOpt_Utimes, + kCmdOpt_NoUtimes, + kCmdOpt_Rename, + kCmdOpt_NoRename, + kCmdOpt_DirOpen, + kCmdOpt_NoDirOpen, + kCmdOpt_DirEnum, + kCmdOpt_NoDirEnum, + kCmdOpt_MkRmDir, + kCmdOpt_NoMkRmDir, + kCmdOpt_StatVfs, + kCmdOpt_NoStatVfs, + kCmdOpt_Rm, + kCmdOpt_NoRm, + kCmdOpt_ChSize, + kCmdOpt_NoChSize, + kCmdOpt_ReadPerf, + kCmdOpt_NoReadPerf, + kCmdOpt_ReadTests, + kCmdOpt_NoReadTests, +#ifdef FSPERF_TEST_SENDFILE + kCmdOpt_SendFile, + kCmdOpt_NoSendFile, +#endif +#ifdef RT_OS_LINUX + kCmdOpt_Splice, + kCmdOpt_NoSplice, +#endif + kCmdOpt_WritePerf, + kCmdOpt_NoWritePerf, + kCmdOpt_WriteTests, + kCmdOpt_NoWriteTests, + kCmdOpt_Seek, + kCmdOpt_NoSeek, + kCmdOpt_FSync, + kCmdOpt_NoFSync, + kCmdOpt_MMap, + kCmdOpt_NoMMap, + kCmdOpt_MMapCoherency, + kCmdOpt_NoMMapCoherency, + kCmdOpt_MMapPlacement, + kCmdOpt_IgnoreNoCache, + kCmdOpt_NoIgnoreNoCache, + kCmdOpt_IoFileSize, + kCmdOpt_SetBlockSize, + kCmdOpt_AddBlockSize, + kCmdOpt_Copy, + kCmdOpt_NoCopy, + kCmdOpt_Remote, + kCmdOpt_NoRemote, + + kCmdOpt_ShowDuration, + kCmdOpt_NoShowDuration, + kCmdOpt_ShowIterations, + kCmdOpt_NoShowIterations, + + kCmdOpt_ManyTreeFilesPerDir, + kCmdOpt_ManyTreeSubdirsPerDir, + kCmdOpt_ManyTreeDepth, + + kCmdOpt_MaxBufferSize, + + kCmdOpt_End +}; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Command line parameters */ +static const RTGETOPTDEF g_aCmdOptions[] = +{ + { "--dir", 'd', RTGETOPT_REQ_STRING }, + { "--relative-dir", 'r', RTGETOPT_REQ_NOTHING }, + { "--comms-dir", 'c', RTGETOPT_REQ_STRING }, + { "--comms-slave", 'C', RTGETOPT_REQ_NOTHING }, + { "--seconds", 's', RTGETOPT_REQ_UINT32 }, + { "--milliseconds", 'm', RTGETOPT_REQ_UINT64 }, + + { "--enable-all", 'e', RTGETOPT_REQ_NOTHING }, + { "--disable-all", 'z', RTGETOPT_REQ_NOTHING }, + + { "--many-files", kCmdOpt_ManyFiles, RTGETOPT_REQ_UINT32 }, + { "--no-many-files", kCmdOpt_NoManyFiles, RTGETOPT_REQ_NOTHING }, + { "--files-per-dir", kCmdOpt_ManyTreeFilesPerDir, RTGETOPT_REQ_UINT32 }, + { "--subdirs-per-dir", kCmdOpt_ManyTreeSubdirsPerDir, RTGETOPT_REQ_UINT32 }, + { "--tree-depth", kCmdOpt_ManyTreeDepth, RTGETOPT_REQ_UINT32 }, + { "--max-buffer-size", kCmdOpt_MaxBufferSize, RTGETOPT_REQ_UINT32 }, + { "--mmap-placement", kCmdOpt_MMapPlacement, RTGETOPT_REQ_STRING }, + /// @todo { "--timestamp-style", kCmdOpt_TimestampStyle, RTGETOPT_REQ_STRING }, + + { "--open", kCmdOpt_Open, RTGETOPT_REQ_NOTHING }, + { "--no-open", kCmdOpt_NoOpen, RTGETOPT_REQ_NOTHING }, + { "--fstat", kCmdOpt_FStat, RTGETOPT_REQ_NOTHING }, + { "--no-fstat", kCmdOpt_NoFStat, RTGETOPT_REQ_NOTHING }, +#ifdef RT_OS_WINDOWS + { "--nt-query-info-file", kCmdOpt_NtQueryInfoFile, RTGETOPT_REQ_NOTHING }, + { "--no-nt-query-info-file", kCmdOpt_NoNtQueryInfoFile, RTGETOPT_REQ_NOTHING }, + { "--nt-query-vol-info-file", kCmdOpt_NtQueryVolInfoFile, RTGETOPT_REQ_NOTHING }, + { "--no-nt-query-vol-info-file",kCmdOpt_NoNtQueryVolInfoFile, RTGETOPT_REQ_NOTHING }, +#endif + { "--fchmod", kCmdOpt_FChMod, RTGETOPT_REQ_NOTHING }, + { "--no-fchmod", kCmdOpt_NoFChMod, RTGETOPT_REQ_NOTHING }, + { "--futimes", kCmdOpt_FUtimes, RTGETOPT_REQ_NOTHING }, + { "--no-futimes", kCmdOpt_NoFUtimes, RTGETOPT_REQ_NOTHING }, + { "--stat", kCmdOpt_Stat, RTGETOPT_REQ_NOTHING }, + { "--no-stat", kCmdOpt_NoStat, RTGETOPT_REQ_NOTHING }, + { "--chmod", kCmdOpt_ChMod, RTGETOPT_REQ_NOTHING }, + { "--no-chmod", kCmdOpt_NoChMod, RTGETOPT_REQ_NOTHING }, + { "--utimes", kCmdOpt_Utimes, RTGETOPT_REQ_NOTHING }, + { "--no-utimes", kCmdOpt_NoUtimes, RTGETOPT_REQ_NOTHING }, + { "--rename", kCmdOpt_Rename, RTGETOPT_REQ_NOTHING }, + { "--no-rename", kCmdOpt_NoRename, RTGETOPT_REQ_NOTHING }, + { "--dir-open", kCmdOpt_DirOpen, RTGETOPT_REQ_NOTHING }, + { "--no-dir-open", kCmdOpt_NoDirOpen, RTGETOPT_REQ_NOTHING }, + { "--dir-enum", kCmdOpt_DirEnum, RTGETOPT_REQ_NOTHING }, + { "--no-dir-enum", kCmdOpt_NoDirEnum, RTGETOPT_REQ_NOTHING }, + { "--mk-rm-dir", kCmdOpt_MkRmDir, RTGETOPT_REQ_NOTHING }, + { "--no-mk-rm-dir", kCmdOpt_NoMkRmDir, RTGETOPT_REQ_NOTHING }, + { "--stat-vfs", kCmdOpt_StatVfs, RTGETOPT_REQ_NOTHING }, + { "--no-stat-vfs", kCmdOpt_NoStatVfs, RTGETOPT_REQ_NOTHING }, + { "--rm", kCmdOpt_Rm, RTGETOPT_REQ_NOTHING }, + { "--no-rm", kCmdOpt_NoRm, RTGETOPT_REQ_NOTHING }, + { "--chsize", kCmdOpt_ChSize, RTGETOPT_REQ_NOTHING }, + { "--no-chsize", kCmdOpt_NoChSize, RTGETOPT_REQ_NOTHING }, + { "--read-tests", kCmdOpt_ReadTests, RTGETOPT_REQ_NOTHING }, + { "--no-read-tests", kCmdOpt_NoReadTests, RTGETOPT_REQ_NOTHING }, + { "--read-perf", kCmdOpt_ReadPerf, RTGETOPT_REQ_NOTHING }, + { "--no-read-perf", kCmdOpt_NoReadPerf, RTGETOPT_REQ_NOTHING }, +#ifdef FSPERF_TEST_SENDFILE + { "--sendfile", kCmdOpt_SendFile, RTGETOPT_REQ_NOTHING }, + { "--no-sendfile", kCmdOpt_NoSendFile, RTGETOPT_REQ_NOTHING }, +#endif +#ifdef RT_OS_LINUX + { "--splice", kCmdOpt_Splice, RTGETOPT_REQ_NOTHING }, + { "--no-splice", kCmdOpt_NoSplice, RTGETOPT_REQ_NOTHING }, +#endif + { "--write-tests", kCmdOpt_WriteTests, RTGETOPT_REQ_NOTHING }, + { "--no-write-tests", kCmdOpt_NoWriteTests, RTGETOPT_REQ_NOTHING }, + { "--write-perf", kCmdOpt_WritePerf, RTGETOPT_REQ_NOTHING }, + { "--no-write-perf", kCmdOpt_NoWritePerf, RTGETOPT_REQ_NOTHING }, + { "--seek", kCmdOpt_Seek, RTGETOPT_REQ_NOTHING }, + { "--no-seek", kCmdOpt_NoSeek, RTGETOPT_REQ_NOTHING }, + { "--fsync", kCmdOpt_FSync, RTGETOPT_REQ_NOTHING }, + { "--no-fsync", kCmdOpt_NoFSync, RTGETOPT_REQ_NOTHING }, + { "--mmap", kCmdOpt_MMap, RTGETOPT_REQ_NOTHING }, + { "--no-mmap", kCmdOpt_NoMMap, RTGETOPT_REQ_NOTHING }, + { "--mmap-coherency", kCmdOpt_MMapCoherency, RTGETOPT_REQ_NOTHING }, + { "--no-mmap-coherency", kCmdOpt_NoMMapCoherency, RTGETOPT_REQ_NOTHING }, + { "--ignore-no-cache", kCmdOpt_IgnoreNoCache, RTGETOPT_REQ_NOTHING }, + { "--no-ignore-no-cache", kCmdOpt_NoIgnoreNoCache, RTGETOPT_REQ_NOTHING }, + { "--io-file-size", kCmdOpt_IoFileSize, RTGETOPT_REQ_UINT64 }, + { "--set-block-size", kCmdOpt_SetBlockSize, RTGETOPT_REQ_UINT32 }, + { "--add-block-size", kCmdOpt_AddBlockSize, RTGETOPT_REQ_UINT32 }, + { "--copy", kCmdOpt_Copy, RTGETOPT_REQ_NOTHING }, + { "--no-copy", kCmdOpt_NoCopy, RTGETOPT_REQ_NOTHING }, + { "--remote", kCmdOpt_Remote, RTGETOPT_REQ_NOTHING }, + { "--no-remote", kCmdOpt_NoRemote, RTGETOPT_REQ_NOTHING }, + + { "--show-duration", kCmdOpt_ShowDuration, RTGETOPT_REQ_NOTHING }, + { "--no-show-duration", kCmdOpt_NoShowDuration, RTGETOPT_REQ_NOTHING }, + { "--show-iterations", kCmdOpt_ShowIterations, RTGETOPT_REQ_NOTHING }, + { "--no-show-iterations", kCmdOpt_NoShowIterations, RTGETOPT_REQ_NOTHING }, + + { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + { "--version", 'V', RTGETOPT_REQ_NOTHING }, + { "--help", 'h', RTGETOPT_REQ_NOTHING } /* for Usage() */ +}; + +/** The test handle. */ +static RTTEST g_hTest; +/** The number of nanoseconds a RTTimeNanoTS call takes. + * This is used for adjusting loop count estimates. */ +static uint64_t g_nsPerNanoTSCall = 1; +/** Whether or not to display the duration of each profile run. + * This is chiefly for verify the estimate phase. */ +static bool g_fShowDuration = false; +/** Whether or not to display the iteration count for each profile run. + * This is chiefly for verify the estimate phase. */ +static bool g_fShowIterations = false; +/** Verbosity level. */ +static uint32_t g_uVerbosity = 0; +/** Max buffer size, UINT32_MAX for unlimited. + * This is for making sure we don't run into the MDL limit on windows, which + * a bit less than 64 MiB. */ +#if defined(RT_OS_WINDOWS) +static uint32_t g_cbMaxBuffer = _32M; +#else +static uint32_t g_cbMaxBuffer = UINT32_MAX; +#endif +/** When to place the mmap test. */ +static int g_iMMapPlacement = 0; + +/** @name Selected subtest + * @{ */ +static bool g_fManyFiles = true; +static bool g_fOpen = true; +static bool g_fFStat = true; +#ifdef RT_OS_WINDOWS +static bool g_fNtQueryInfoFile = true; +static bool g_fNtQueryVolInfoFile = true; +#endif +static bool g_fFChMod = true; +static bool g_fFUtimes = true; +static bool g_fStat = true; +static bool g_fChMod = true; +static bool g_fUtimes = true; +static bool g_fRename = true; +static bool g_fDirOpen = true; +static bool g_fDirEnum = true; +static bool g_fMkRmDir = true; +static bool g_fStatVfs = true; +static bool g_fRm = true; +static bool g_fChSize = true; +static bool g_fReadTests = true; +static bool g_fReadPerf = true; +#ifdef FSPERF_TEST_SENDFILE +static bool g_fSendFile = true; +#endif +#ifdef RT_OS_LINUX +static bool g_fSplice = true; +#endif +static bool g_fWriteTests = true; +static bool g_fWritePerf = true; +static bool g_fSeek = true; +static bool g_fFSync = true; +static bool g_fMMap = true; +static bool g_fMMapCoherency = true; +static bool g_fCopy = true; +static bool g_fRemote = true; +/** @} */ + +/** The length of each test run. */ +static uint64_t g_nsTestRun = RT_NS_1SEC_64 * 10; + +/** For the 'manyfiles' subdir. */ +static uint32_t g_cManyFiles = 10000; + +/** Number of files in the 'manytree' directory tree. */ +static uint32_t g_cManyTreeFiles = 640 + 16*640 /*10880*/; +/** Number of files per directory in the 'manytree' construct. */ +static uint32_t g_cManyTreeFilesPerDir = 640; +/** Number of subdirs per directory in the 'manytree' construct. */ +static uint32_t g_cManyTreeSubdirsPerDir = 16; +/** The depth of the 'manytree' directory tree. */ +static uint32_t g_cManyTreeDepth = 1; +/** List of directories in the many tree, creation order. */ +static RTLISTANCHOR g_ManyTreeHead; + +/** Number of configured I/O block sizes. */ +static uint32_t g_cIoBlocks = 8; +/** Configured I/O block sizes. */ +static uint32_t g_acbIoBlocks[16] = { 1, 512, 4096, 16384, 65536, _1M, _32M, _128M }; +/** The desired size of the test file we use for I/O. */ +static uint64_t g_cbIoFile = _512M; +/** Whether to be less strict with non-cache file handle. */ +static bool g_fIgnoreNoCache = false; + +/** Set if g_szDir and friends are path relative to CWD rather than absolute. */ +static bool g_fRelativeDir = false; +/** The length of g_szDir. */ +static size_t g_cchDir; +/** The length of g_szEmptyDir. */ +static size_t g_cchEmptyDir; +/** The length of g_szDeepDir. */ +static size_t g_cchDeepDir; + +/** The length of g_szCommsDir. */ +static size_t g_cchCommsDir; +/** The length of g_szCommsSubDir. */ +static size_t g_cchCommsSubDir; + +/** The test directory (absolute). This will always have a trailing slash. */ +static char g_szDir[FSPERF_MAX_PATH]; +/** The test directory (absolute), 2nd copy for use with InDir2(). */ +static char g_szDir2[FSPERF_MAX_PATH]; +/** The empty test directory (absolute). This will always have a trailing slash. */ +static char g_szEmptyDir[FSPERF_MAX_PATH]; +/** The deep test directory (absolute). This will always have a trailing slash. */ +static char g_szDeepDir[FSPERF_MAX_PATH + _1K]; + +/** The communcations directory. This will always have a trailing slash. */ +static char g_szCommsDir[FSPERF_MAX_PATH]; +/** The communcations subdirectory used for the actual communication. This will + * always have a trailing slash. */ +static char g_szCommsSubDir[FSPERF_MAX_PATH]; + +/** + * Yield the CPU and stuff before starting a test run. + */ +DECLINLINE(void) fsPerfYield(void) +{ + RTThreadYield(); + RTThreadYield(); +} + + +/** + * Profiles the RTTimeNanoTS call, setting g_nsPerNanoTSCall. + */ +static void fsPerfNanoTS(void) +{ + fsPerfYield(); + + /* Make sure we start off on a changing timestamp on platforms will low time resoultion. */ + uint64_t nsStart = RTTimeNanoTS(); + uint64_t ns; + do + ns = RTTimeNanoTS(); + while (ns == nsStart); + nsStart = ns; + + /* Call it for 10 ms. */ + uint32_t i = 0; + do + { + i++; + ns = RTTimeNanoTS(); + } + while (ns - nsStart < RT_NS_10MS); + + g_nsPerNanoTSCall = (ns - nsStart) / i; +} + + +/** + * Construct a path relative to the base test directory. + * + * @returns g_szDir. + * @param pszAppend What to append. + * @param cchAppend How much to append. + */ +DECLINLINE(char *) InDir(const char *pszAppend, size_t cchAppend) +{ + Assert(g_szDir[g_cchDir - 1] == RTPATH_SLASH); + memcpy(&g_szDir[g_cchDir], pszAppend, cchAppend); + g_szDir[g_cchDir + cchAppend] = '\0'; + return &g_szDir[0]; +} + + +/** + * Construct a path relative to the base test directory, 2nd copy. + * + * @returns g_szDir2. + * @param pszAppend What to append. + * @param cchAppend How much to append. + */ +DECLINLINE(char *) InDir2(const char *pszAppend, size_t cchAppend) +{ + Assert(g_szDir[g_cchDir - 1] == RTPATH_SLASH); + memcpy(g_szDir2, g_szDir, g_cchDir); + memcpy(&g_szDir2[g_cchDir], pszAppend, cchAppend); + g_szDir2[g_cchDir + cchAppend] = '\0'; + return &g_szDir2[0]; +} + + +/** + * Construct a path relative to the empty directory. + * + * @returns g_szEmptyDir. + * @param pszAppend What to append. + * @param cchAppend How much to append. + */ +DECLINLINE(char *) InEmptyDir(const char *pszAppend, size_t cchAppend) +{ + Assert(g_szEmptyDir[g_cchEmptyDir - 1] == RTPATH_SLASH); + memcpy(&g_szEmptyDir[g_cchEmptyDir], pszAppend, cchAppend); + g_szEmptyDir[g_cchEmptyDir + cchAppend] = '\0'; + return &g_szEmptyDir[0]; +} + + +/** + * Construct a path relative to the deep test directory. + * + * @returns g_szDeepDir. + * @param pszAppend What to append. + * @param cchAppend How much to append. + */ +DECLINLINE(char *) InDeepDir(const char *pszAppend, size_t cchAppend) +{ + Assert(g_szDeepDir[g_cchDeepDir - 1] == RTPATH_SLASH); + memcpy(&g_szDeepDir[g_cchDeepDir], pszAppend, cchAppend); + g_szDeepDir[g_cchDeepDir + cchAppend] = '\0'; + return &g_szDeepDir[0]; +} + + + +/********************************************************************************************************************************* +* Slave FsPerf Instance Interaction. * +*********************************************************************************************************************************/ + +/** + * Construct a path relative to the comms directory. + * + * @returns g_szCommsDir. + * @param pszAppend What to append. + * @param cchAppend How much to append. + */ +DECLINLINE(char *) InCommsDir(const char *pszAppend, size_t cchAppend) +{ + Assert(g_szCommsDir[g_cchCommsDir - 1] == RTPATH_SLASH); + memcpy(&g_szCommsDir[g_cchCommsDir], pszAppend, cchAppend); + g_szCommsDir[g_cchCommsDir + cchAppend] = '\0'; + return &g_szCommsDir[0]; +} + + +/** + * Construct a path relative to the comms sub-directory. + * + * @returns g_szCommsSubDir. + * @param pszAppend What to append. + * @param cchAppend How much to append. + */ +DECLINLINE(char *) InCommsSubDir(const char *pszAppend, size_t cchAppend) +{ + Assert(g_szCommsSubDir[g_cchCommsSubDir - 1] == RTPATH_SLASH); + memcpy(&g_szCommsSubDir[g_cchCommsSubDir], pszAppend, cchAppend); + g_szCommsSubDir[g_cchCommsSubDir + cchAppend] = '\0'; + return &g_szCommsSubDir[0]; +} + + +/** + * Creates a file under g_szCommsDir with the given content. + * + * Will modify g_szCommsDir to contain the given filename. + * + * @returns IPRT status code (fully bitched). + * @param pszFilename The filename. + * @param cchFilename The length of the filename. + * @param pszContent The file content. + * @param cchContent The length of the file content. + */ +static int FsPerfCommsWriteFile(const char *pszFilename, size_t cchFilename, const char *pszContent, size_t cchContent) +{ + RTFILE hFile; + int rc = RTFileOpen(&hFile, InCommsDir(pszFilename, cchFilename), + RTFILE_O_WRITE | RTFILE_O_DENY_NONE | RTFILE_O_CREATE_REPLACE); + if (RT_SUCCESS(rc)) + { + rc = RTFileWrite(hFile, pszContent, cchContent, NULL); + if (RT_FAILURE(rc)) + RTMsgError("Error writing %#zx bytes to '%s': %Rrc", cchContent, g_szCommsDir, rc); + + int rc2 = RTFileClose(hFile); + if (RT_FAILURE(rc2)) + { + RTMsgError("Error closing to '%s': %Rrc", g_szCommsDir, rc); + rc = rc2; + } + if (RT_SUCCESS(rc) && g_uVerbosity >= 3) + RTMsgInfo("comms: wrote '%s'\n", g_szCommsDir); + if (RT_FAILURE(rc)) + RTFileDelete(g_szCommsDir); + } + else + RTMsgError("Failed to create '%s': %Rrc", g_szCommsDir, rc); + return rc; +} + + +/** + * Creates a file under g_szCommsDir with the given content, then renames it + * into g_szCommsSubDir. + * + * Will modify g_szCommsSubDir to contain the final filename and g_szCommsDir to + * hold the temporary one. + * + * @returns IPRT status code (fully bitched). + * @param pszFilename The filename. + * @param cchFilename The length of the filename. + * @param pszContent The file content. + * @param cchContent The length of the file content. + */ +static int FsPerfCommsWriteFileAndRename(const char *pszFilename, size_t cchFilename, const char *pszContent, size_t cchContent) +{ + int rc = FsPerfCommsWriteFile(pszFilename, cchFilename, pszContent, cchContent); + if (RT_SUCCESS(rc)) + { + rc = RTFileRename(g_szCommsDir, InCommsSubDir(pszFilename, cchFilename), RTPATHRENAME_FLAGS_REPLACE); + if (RT_SUCCESS(rc) && g_uVerbosity >= 3) + RTMsgInfo("comms: placed '%s'\n", g_szCommsSubDir); + if (RT_FAILURE(rc)) + { + RTMsgError("Error renaming '%s' to '%s': %Rrc", g_szCommsDir, g_szCommsSubDir, rc); + RTFileDelete(g_szCommsDir); + } + } + return rc; +} + + +/** + * Reads the given file from the comms subdir, ensuring that it is terminated by + * an EOF (0x1a) character. + * + * @returns IPRT status code. + * @retval VERR_TRY_AGAIN if the file is incomplete. + * @retval VERR_FILE_TOO_BIG if the file is considered too big. + * @retval VERR_FILE_NOT_FOUND if not found. + * + * @param iSeqNo The sequence number. + * @param pszSuffix The filename suffix. + * @param ppszContent Where to return the content. + */ +static int FsPerfCommsReadFile(uint32_t iSeqNo, const char *pszSuffix, char **ppszContent) +{ + *ppszContent = NULL; + + RTStrPrintf(&g_szCommsSubDir[g_cchCommsSubDir], sizeof(g_szCommsSubDir) - g_cchCommsSubDir, "%u%s", iSeqNo, pszSuffix); + RTFILE hFile; + int rc = RTFileOpen(&hFile, g_szCommsSubDir, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN); + if (RT_SUCCESS(rc)) + { + size_t cbUsed = 0; + size_t cbAlloc = 1024; + char *pszBuf = (char *)RTMemAllocZ(cbAlloc); + for (;;) + { + /* Do buffer resizing. */ + size_t cbMaxRead = cbAlloc - cbUsed - 1; + if (cbMaxRead < 8) + { + if (cbAlloc < _1M) + { + cbAlloc *= 2; + void *pvRealloced = RTMemRealloc(pszBuf, cbAlloc); + if (!pvRealloced) + { + rc = VERR_NO_MEMORY; + break; + } + pszBuf = (char *)pvRealloced; + RT_BZERO(&pszBuf[cbAlloc / 2], cbAlloc); + cbMaxRead = cbAlloc - cbUsed - 1; + } + else + { + RTMsgError("File '%s' is too big - giving up at 1MB", g_szCommsSubDir); + rc = VERR_FILE_TOO_BIG; + break; + } + } + + /* Do the reading. */ + size_t cbActual = 0; + rc = RTFileRead(hFile, &pszBuf[cbUsed], cbMaxRead, &cbActual); + if (RT_SUCCESS(rc)) + cbUsed += cbActual; + else + { + RTMsgError("Failed to read '%s': %Rrc", g_szCommsSubDir, rc); + break; + } + + /* EOF? */ + if (cbActual < cbMaxRead) + break; + } + + RTFileClose(hFile); + + /* + * Check if the file ends with the EOF marker. + */ + if ( RT_SUCCESS(rc) + && ( cbUsed == 0 + || pszBuf[cbUsed - 1] != FSPERF_EOF)) + rc = VERR_TRY_AGAIN; + + /* + * Return or free the content we've read. + */ + if (RT_SUCCESS(rc)) + *ppszContent = pszBuf; + else + RTMemFree(pszBuf); + } + else if (rc != VERR_FILE_NOT_FOUND && rc != VERR_SHARING_VIOLATION) + RTMsgError("Failed to open '%s': %Rrc", g_szCommsSubDir, rc); + return rc; +} + + +/** + * FsPerfCommsReadFile + renaming from the comms subdir to the comms dir. + * + * g_szCommsSubDir holds the original filename and g_szCommsDir the final + * filename on success. + */ +static int FsPerfCommsReadFileAndRename(uint32_t iSeqNo, const char *pszSuffix, const char *pszRenameSuffix, char **ppszContent) +{ + RTStrPrintf(&g_szCommsDir[g_cchCommsDir], sizeof(g_szCommsDir) - g_cchCommsDir, "%u%s", iSeqNo, pszRenameSuffix); + int rc = FsPerfCommsReadFile(iSeqNo, pszSuffix, ppszContent); + if (RT_SUCCESS(rc)) + { + rc = RTFileRename(g_szCommsSubDir, g_szCommsDir, RTPATHRENAME_FLAGS_REPLACE); + if (RT_FAILURE(rc)) + { + RTMsgError("Error renaming '%s' to '%s': %Rrc", g_szCommsSubDir, g_szCommsDir, rc); + RTMemFree(*ppszContent); + *ppszContent = NULL; + } + } + return rc; +} + + +/** The comms master sequence number. */ +static uint32_t g_iSeqNoMaster = 0; + + +/** + * Sends a script to the remote comms slave. + * + * @returns IPRT status code giving the scripts execution status. + * @param pszScript The script. + */ +static int FsPerfCommsSend(const char *pszScript) +{ + /* + * Make sure the script is correctly terminated with an EOF control character. + */ + size_t const cchScript = strlen(pszScript); + AssertReturn(cchScript > 0 && pszScript[cchScript - 1] == FSPERF_EOF, VERR_INVALID_PARAMETER); + + /* + * Make sure the comms slave is running. + */ + if (!RTFileExists(InCommsDir(RT_STR_TUPLE("slave.pid")))) + return VERR_PIPE_NOT_CONNECTED; + + /* + * Format all the names we might want to check for. + */ + char szSendNm[32]; + size_t const cchSendNm = RTStrPrintf(szSendNm, sizeof(szSendNm), "%u-order.send", g_iSeqNoMaster); + + char szAckNm[64]; + size_t const cchAckNm = RTStrPrintf(szAckNm, sizeof(szAckNm), "%u-order.ack", g_iSeqNoMaster); + + /* + * Produce the script file and submit it. + */ + int rc = FsPerfCommsWriteFileAndRename(szSendNm, cchSendNm, pszScript, cchScript); + if (RT_SUCCESS(rc)) + { + g_iSeqNoMaster++; + + /* + * Wait for the result. + */ + uint64_t const msTimeout = RT_MS_1MIN / 2; + uint64_t msStart = RTTimeMilliTS(); + uint32_t msSleepX4 = 4; + for (;;) + { + /* Try read the result file: */ + char *pszContent = NULL; + rc = FsPerfCommsReadFile(g_iSeqNoMaster - 1, "-order.done", &pszContent); + if (RT_SUCCESS(rc)) + { + /* Split the result content into status code and error text: */ + char *pszErrorText = strchr(pszContent, '\n'); + if (pszErrorText) + { + *pszErrorText = '\0'; + pszErrorText++; + } + else + { + char *pszEnd = strchr(pszContent, '\0'); + Assert(pszEnd[-1] == FSPERF_EOF); + pszEnd[-1] = '\0'; + } + + /* Parse the status code: */ + int32_t rcRemote = VERR_GENERAL_FAILURE; + rc = RTStrToInt32Full(pszContent, 0, &rcRemote); + if (rc != VINF_SUCCESS) + { + RTTestIFailed("FsPerfCommsSend: Failed to convert status code '%s'", pszContent); + rcRemote = VERR_GENERAL_FAILURE; + } + + /* Display or return the text? */ + if (RT_SUCCESS(rc) && g_uVerbosity >= 2) + RTMsgInfo("comms: order #%u: %Rrc%s%s\n", + g_iSeqNoMaster - 1, rcRemote, *pszErrorText ? " - " : "", pszErrorText); + + RTMemFree(pszContent); + return rcRemote; + } + + if (rc == VERR_TRY_AGAIN) + msSleepX4 = 4; + + /* Check for timeout. */ + if (RTTimeMilliTS() - msStart > msTimeout) + { + if (RT_SUCCESS(rc) && g_uVerbosity >= 2) + RTMsgInfo("comms: timed out waiting for order #%u'\n", g_iSeqNoMaster - 1); + + rc = RTFileDelete(InCommsSubDir(szSendNm, cchSendNm)); + if (RT_SUCCESS(rc)) + { + g_iSeqNoMaster--; + rc = VERR_TIMEOUT; + } + else if (RTFileExists(InCommsDir(szAckNm, cchAckNm))) + rc = VERR_PIPE_BUSY; + else + rc = VERR_PIPE_IO_ERROR; + break; + } + + /* Sleep a little while. */ + msSleepX4++; + RTThreadSleep(msSleepX4 / 4); + } + } + return rc; +} + + +/** + * Shuts down the comms slave if it exists. + */ +static void FsPerfCommsShutdownSlave(void) +{ + static bool s_fAlreadyShutdown = false; + if (g_szCommsDir[0] != '\0' && !s_fAlreadyShutdown) + { + s_fAlreadyShutdown = true; + FsPerfCommsSend("exit" FSPERF_EOF_STR); + + g_szCommsDir[g_cchCommsDir] = '\0'; + int rc = RTDirRemoveRecursive(g_szCommsDir, RTDIRRMREC_F_CONTENT_AND_DIR | (g_fRelativeDir ? RTDIRRMREC_F_NO_ABS_PATH : 0)); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "RTDirRemoveRecursive(%s,) -> %Rrc\n", g_szCommsDir, rc); + } +} + + + +/********************************************************************************************************************************* +* Comms Slave * +*********************************************************************************************************************************/ + +typedef struct FSPERFCOMMSSLAVESTATE +{ + uint32_t iSeqNo; + bool fTerminate; + RTEXITCODE rcExit; + RTFILE ahFiles[8]; + char *apszFilenames[8]; + + /** The current command. */ + const char *pszCommand; + /** The current line number. */ + uint32_t iLineNo; + /** The current line content. */ + const char *pszLine; + /** Where to return extra error info text. */ + RTERRINFOSTATIC ErrInfo; +} FSPERFCOMMSSLAVESTATE; + + +static void FsPerfSlaveStateInit(FSPERFCOMMSSLAVESTATE *pState) +{ + pState->iSeqNo = 0; + pState->fTerminate = false; + pState->rcExit = RTEXITCODE_SUCCESS; + unsigned i = RT_ELEMENTS(pState->ahFiles); + while (i-- > 0) + { + pState->ahFiles[i] = NIL_RTFILE; + pState->apszFilenames[i] = NULL; + } + RTErrInfoInitStatic(&pState->ErrInfo); +} + + +static void FsPerfSlaveStateCleanup(FSPERFCOMMSSLAVESTATE *pState) +{ + unsigned i = RT_ELEMENTS(pState->ahFiles); + while (i-- > 0) + { + if (pState->ahFiles[i] != NIL_RTFILE) + { + RTFileClose(pState->ahFiles[i]); + pState->ahFiles[i] = NIL_RTFILE; + } + if (pState->apszFilenames[i] != NULL) + { + RTStrFree(pState->apszFilenames[i]); + pState->apszFilenames[i] = NULL; + } + } +} + + +/** Helper reporting a error. */ +static int FsPerfSlaveError(FSPERFCOMMSSLAVESTATE *pState, int rc, const char *pszError, ...) +{ + va_list va; + va_start(va, pszError); + RTErrInfoSetF(&pState->ErrInfo.Core, VERR_PARSE_ERROR, "line %u: %s: error: %N", + pState->iLineNo, pState->pszCommand, pszError, &va); + va_end(va); + return rc; +} + + +/** Helper reporting a syntax error. */ +static int FsPerfSlaveSyntax(FSPERFCOMMSSLAVESTATE *pState, const char *pszError, ...) +{ + va_list va; + va_start(va, pszError); + RTErrInfoSetF(&pState->ErrInfo.Core, VERR_PARSE_ERROR, "line %u: %s: syntax error: %N", + pState->iLineNo, pState->pszCommand, pszError, &va); + va_end(va); + return VERR_PARSE_ERROR; +} + + +/** Helper for parsing an unsigned 64-bit integer argument. */ +static int FsPerfSlaveParseU64(FSPERFCOMMSSLAVESTATE *pState, const char *pszArg, const char *pszName, + unsigned uBase, uint64_t uMin, uint64_t uLast, uint64_t *puValue) +{ + *puValue = uMin; + uint64_t uValue; + int rc = RTStrToUInt64Full(pszArg, uBase, &uValue); + if (RT_FAILURE(rc)) + return FsPerfSlaveSyntax(pState, "invalid %s: %s (RTStrToUInt64Full -> %Rrc)", pszName, pszArg, rc); + if (uValue < uMin || uValue > uLast) + return FsPerfSlaveSyntax(pState, "%s is out of range: %u, valid range %u..%u", pszName, uValue, uMin, uLast); + *puValue = uValue; + return VINF_SUCCESS; +} + + +/** Helper for parsing an unsigned 32-bit integer argument. */ +static int FsPerfSlaveParseU32(FSPERFCOMMSSLAVESTATE *pState, const char *pszArg, const char *pszName, + unsigned uBase, uint32_t uMin, uint32_t uLast, uint32_t *puValue) +{ + *puValue = uMin; + uint32_t uValue; + int rc = RTStrToUInt32Full(pszArg, uBase, &uValue); + if (RT_FAILURE(rc)) + return FsPerfSlaveSyntax(pState, "invalid %s: %s (RTStrToUInt32Full -> %Rrc)", pszName, pszArg, rc); + if (uValue < uMin || uValue > uLast) + return FsPerfSlaveSyntax(pState, "%s is out of range: %u, valid range %u..%u", pszName, uValue, uMin, uLast); + *puValue = uValue; + return VINF_SUCCESS; +} + + +/** Helper for parsing a file handle index argument. */ +static int FsPerfSlaveParseFileIdx(FSPERFCOMMSSLAVESTATE *pState, const char *pszArg, uint32_t *pidxFile) +{ + return FsPerfSlaveParseU32(pState, pszArg, "file index", 0, 0, RT_ELEMENTS(pState->ahFiles) - 1, pidxFile); +} + + +/** + * 'open {idxFile} {filename} {access} {disposition} [sharing] [mode]' + */ +static int FsPerfSlaveHandleOpen(FSPERFCOMMSSLAVESTATE *pState, char **papszArgs, int cArgs) +{ + /* + * Parse parameters. + */ + if (cArgs > 1 + 6 || cArgs < 1 + 4) + return FsPerfSlaveSyntax(pState, "takes four to six arguments, not %u", cArgs); + + uint32_t idxFile; + int rc = FsPerfSlaveParseFileIdx(pState, papszArgs[1], &idxFile); + if (RT_FAILURE(rc)) + return rc; + + const char *pszFilename = papszArgs[2]; + + uint64_t fOpen = 0; + rc = RTFileModeToFlagsEx(papszArgs[3], papszArgs[4], papszArgs[5], &fOpen); + if (RT_FAILURE(rc)) + return FsPerfSlaveSyntax(pState, "failed to parse access (%s), disposition (%s) and sharing (%s): %Rrc", + papszArgs[3], papszArgs[4], papszArgs[5] ? papszArgs[5] : "", rc); + + uint32_t uMode = 0660; + if (cArgs >= 1 + 6) + { + rc = FsPerfSlaveParseU32(pState, papszArgs[6], "mode", 8, 0, 0777, &uMode); + if (RT_FAILURE(rc)) + return rc; + fOpen |= uMode << RTFILE_O_CREATE_MODE_SHIFT; + } + + /* + * Is there already a file assigned to the file handle index? + */ + if (pState->ahFiles[idxFile] != NIL_RTFILE) + return FsPerfSlaveError(pState, VERR_RESOURCE_BUSY, "handle #%u is already in use for '%s'", + idxFile, pState->apszFilenames[idxFile]); + + /* + * Check the filename length. + */ + size_t const cchFilename = strlen(pszFilename); + if (g_cchDir + cchFilename >= sizeof(g_szDir)) + return FsPerfSlaveError(pState, VERR_FILENAME_TOO_LONG, "'%.*s%s'", g_cchDir, g_szDir, pszFilename); + + /* + * Duplicate the name and execute the command. + */ + char *pszDup = RTStrDup(pszFilename); + if (!pszDup) + return FsPerfSlaveError(pState, VERR_NO_STR_MEMORY, "out of memory"); + + RTFILE hFile = NIL_RTFILE; + rc = RTFileOpen(&hFile, InDir(pszFilename, cchFilename), fOpen); + if (RT_SUCCESS(rc)) + { + pState->ahFiles[idxFile] = hFile; + pState->apszFilenames[idxFile] = pszDup; + } + else + { + RTStrFree(pszDup); + rc = FsPerfSlaveError(pState, rc, "%s: %Rrc", pszFilename, rc); + } + return rc; +} + + +/** + * 'close {idxFile}' + */ +static int FsPerfSlaveHandleClose(FSPERFCOMMSSLAVESTATE *pState, char **papszArgs, int cArgs) +{ + /* + * Parse parameters. + */ + if (cArgs > 1 + 1) + return FsPerfSlaveSyntax(pState, "takes exactly one argument, not %u", cArgs); + + uint32_t idxFile; + int rc = FsPerfSlaveParseFileIdx(pState, papszArgs[1], &idxFile); + if (RT_SUCCESS(rc)) + { + /* + * Do it. + */ + rc = RTFileClose(pState->ahFiles[idxFile]); + if (RT_SUCCESS(rc)) + { + pState->ahFiles[idxFile] = NIL_RTFILE; + RTStrFree(pState->apszFilenames[idxFile]); + pState->apszFilenames[idxFile] = NULL; + } + } + return rc; +} + +/** @name Patterns for 'writepattern' + * @{ */ +static uint8_t const g_abPattern0[] = { 0xf0 }; +static uint8_t const g_abPattern1[] = { 0xf1 }; +static uint8_t const g_abPattern2[] = { 0xf2 }; +static uint8_t const g_abPattern3[] = { 0xf3 }; +static uint8_t const g_abPattern4[] = { 0xf4 }; +static uint8_t const g_abPattern5[] = { 0xf5 }; +static uint8_t const g_abPattern6[] = { 0xf6 }; +static uint8_t const g_abPattern7[] = { 0xf7 }; +static uint8_t const g_abPattern8[] = { 0xf8 }; +static uint8_t const g_abPattern9[] = { 0xf9 }; +static uint8_t const g_abPattern10[] = { 0x1f, 0x4e, 0x99, 0xec, 0x71, 0x71, 0x48, 0x0f, 0xa7, 0x5c, 0xb4, 0x5a, 0x1f, 0xc7, 0xd0, 0x93 }; +static struct +{ + uint8_t const *pb; + uint32_t cb; +} const g_aPatterns[] = +{ + { g_abPattern0, sizeof(g_abPattern0) }, + { g_abPattern1, sizeof(g_abPattern1) }, + { g_abPattern2, sizeof(g_abPattern2) }, + { g_abPattern3, sizeof(g_abPattern3) }, + { g_abPattern4, sizeof(g_abPattern4) }, + { g_abPattern5, sizeof(g_abPattern5) }, + { g_abPattern6, sizeof(g_abPattern6) }, + { g_abPattern7, sizeof(g_abPattern7) }, + { g_abPattern8, sizeof(g_abPattern8) }, + { g_abPattern9, sizeof(g_abPattern9) }, + { g_abPattern10, sizeof(g_abPattern10) }, +}; +/** @} */ + +/** + * 'writepattern {idxFile} {offFile} {idxPattern} {cbToWrite}' + */ +static int FsPerfSlaveHandleWritePattern(FSPERFCOMMSSLAVESTATE *pState, char **papszArgs, int cArgs) +{ + /* + * Parse parameters. + */ + if (cArgs > 1 + 4) + return FsPerfSlaveSyntax(pState, "takes exactly four arguments, not %u", cArgs); + + uint32_t idxFile; + int rc = FsPerfSlaveParseFileIdx(pState, papszArgs[1], &idxFile); + if (RT_FAILURE(rc)) + return rc; + + uint64_t offFile; + rc = FsPerfSlaveParseU64(pState, papszArgs[2], "file offset", 0, 0, UINT64_MAX / 4, &offFile); + if (RT_FAILURE(rc)) + return rc; + + uint32_t idxPattern; + rc = FsPerfSlaveParseU32(pState, papszArgs[3], "pattern index", 0, 0, RT_ELEMENTS(g_aPatterns) - 1, &idxPattern); + if (RT_FAILURE(rc)) + return rc; + + uint64_t cbToWrite; + rc = FsPerfSlaveParseU64(pState, papszArgs[4], "number of bytes to write", 0, 0, _1G, &cbToWrite); + if (RT_FAILURE(rc)) + return rc; + + if (pState->ahFiles[idxFile] == NIL_RTFILE) + return FsPerfSlaveError(pState, VERR_INVALID_HANDLE, "no open file at index #%u", idxFile); + + /* + * Allocate a suitable buffer. + */ + size_t cbMaxBuf = RT_MIN(_2M, g_cbMaxBuffer); + size_t cbBuf = cbToWrite >= cbMaxBuf ? cbMaxBuf : RT_ALIGN_Z((size_t)cbToWrite, 512); + uint8_t *pbBuf = (uint8_t *)RTMemTmpAlloc(cbBuf); + if (!pbBuf) + { + cbBuf = _4K; + pbBuf = (uint8_t *)RTMemTmpAlloc(cbBuf); + if (!pbBuf) + return FsPerfSlaveError(pState, VERR_NO_TMP_MEMORY, "failed to allocate 4KB for buffers"); + } + + /* + * Fill 1 byte patterns before we start looping. + */ + if (g_aPatterns[idxPattern].cb == 1) + memset(pbBuf, g_aPatterns[idxPattern].pb[0], cbBuf); + + /* + * The write loop. + */ + uint32_t offPattern = 0; + while (cbToWrite > 0) + { + /* + * Fill the buffer if multi-byte pattern (single byte patterns are handled before the loop): + */ + if (g_aPatterns[idxPattern].cb > 1) + { + uint32_t const cbSrc = g_aPatterns[idxPattern].cb; + uint8_t const * const pbSrc = g_aPatterns[idxPattern].pb; + size_t cbDst = cbBuf; + uint8_t *pbDst = pbBuf; + + /* first iteration, potential partial pattern. */ + if (offPattern >= cbSrc) + offPattern = 0; + size_t cbThis1 = RT_MIN(g_aPatterns[idxPattern].cb - offPattern, cbToWrite); + memcpy(pbDst, &pbSrc[offPattern], cbThis1); + cbDst -= cbThis1; + if (cbDst > 0) + { + pbDst += cbThis1; + offPattern = 0; + + /* full patterns */ + while (cbDst >= cbSrc) + { + memcpy(pbDst, pbSrc, cbSrc); + pbDst += cbSrc; + cbDst -= cbSrc; + } + + /* partial final copy */ + if (cbDst > 0) + { + memcpy(pbDst, pbSrc, cbDst); + offPattern = (uint32_t)cbDst; + } + } + } + + /* + * Write. + */ + size_t const cbThisWrite = (size_t)RT_MIN(cbToWrite, cbBuf); + rc = RTFileWriteAt(pState->ahFiles[idxFile], offFile, pbBuf, cbThisWrite, NULL); + if (RT_FAILURE(rc)) + { + FsPerfSlaveError(pState, rc, "error writing %#zx bytes at %#RX64: %Rrc (file: %s)", + cbThisWrite, offFile, rc, pState->apszFilenames[idxFile]); + break; + } + + offFile += cbThisWrite; + cbToWrite -= cbThisWrite; + } + + RTMemTmpFree(pbBuf); + return rc; +} + + +/** + * 'truncate {idxFile} {cbFile}' + */ +static int FsPerfSlaveHandleTruncate(FSPERFCOMMSSLAVESTATE *pState, char **papszArgs, int cArgs) +{ + /* + * Parse parameters. + */ + if (cArgs != 1 + 2) + return FsPerfSlaveSyntax(pState, "takes exactly two arguments, not %u", cArgs); + + uint32_t idxFile; + int rc = FsPerfSlaveParseFileIdx(pState, papszArgs[1], &idxFile); + if (RT_FAILURE(rc)) + return rc; + + uint64_t cbFile; + rc = FsPerfSlaveParseU64(pState, papszArgs[2], "new file size", 0, 0, UINT64_MAX / 4, &cbFile); + if (RT_FAILURE(rc)) + return rc; + + if (pState->ahFiles[idxFile] == NIL_RTFILE) + return FsPerfSlaveError(pState, VERR_INVALID_HANDLE, "no open file at index #%u", idxFile); + + /* + * Execute. + */ + rc = RTFileSetSize(pState->ahFiles[idxFile], cbFile); + if (RT_FAILURE(rc)) + return FsPerfSlaveError(pState, rc, "failed to set file size to %#RX64: %Rrc (file: %s)", + cbFile, rc, pState->apszFilenames[idxFile]); + return VINF_SUCCESS; +} + + +/** + * 'futimes {idxFile} {modified|0} [access|0] [change|0] [birth|0]' + */ +static int FsPerfSlaveHandleFUTimes(FSPERFCOMMSSLAVESTATE *pState, char **papszArgs, int cArgs) +{ + /* + * Parse parameters. + */ + if (cArgs < 1 + 2 || cArgs > 1 + 5) + return FsPerfSlaveSyntax(pState, "takes between two and five arguments, not %u", cArgs); + + uint32_t idxFile; + int rc = FsPerfSlaveParseFileIdx(pState, papszArgs[1], &idxFile); + if (RT_FAILURE(rc)) + return rc; + + uint64_t nsModifiedTime; + rc = FsPerfSlaveParseU64(pState, papszArgs[2], "modified time", 0, 0, UINT64_MAX, &nsModifiedTime); + if (RT_FAILURE(rc)) + return rc; + + uint64_t nsAccessTime = 0; + if (cArgs >= 1 + 3) + { + rc = FsPerfSlaveParseU64(pState, papszArgs[3], "access time", 0, 0, UINT64_MAX, &nsAccessTime); + if (RT_FAILURE(rc)) + return rc; + } + + uint64_t nsChangeTime = 0; + if (cArgs >= 1 + 4) + { + rc = FsPerfSlaveParseU64(pState, papszArgs[4], "change time", 0, 0, UINT64_MAX, &nsChangeTime); + if (RT_FAILURE(rc)) + return rc; + } + + uint64_t nsBirthTime = 0; + if (cArgs >= 1 + 5) + { + rc = FsPerfSlaveParseU64(pState, papszArgs[4], "birth time", 0, 0, UINT64_MAX, &nsBirthTime); + if (RT_FAILURE(rc)) + return rc; + } + + if (pState->ahFiles[idxFile] == NIL_RTFILE) + return FsPerfSlaveError(pState, VERR_INVALID_HANDLE, "no open file at index #%u", idxFile); + + /* + * Execute. + */ + RTTIMESPEC ModifiedTime; + RTTIMESPEC AccessTime; + RTTIMESPEC ChangeTime; + RTTIMESPEC BirthTime; + rc = RTFileSetTimes(pState->ahFiles[idxFile], + nsAccessTime ? RTTimeSpecSetNano(&AccessTime, nsAccessTime) : NULL, + nsModifiedTime ? RTTimeSpecSetNano(&ModifiedTime, nsModifiedTime) : NULL, + nsChangeTime ? RTTimeSpecSetNano(&ChangeTime, nsChangeTime) : NULL, + nsBirthTime ? RTTimeSpecSetNano(&BirthTime, nsBirthTime) : NULL); + if (RT_FAILURE(rc)) + return FsPerfSlaveError(pState, rc, "failed to set file times to %RI64, %RI64, %RI64, %RI64: %Rrc (file: %s)", + nsModifiedTime, nsAccessTime, nsChangeTime, nsBirthTime, rc, pState->apszFilenames[idxFile]); + return VINF_SUCCESS; +} + + +/** + * 'fchmod {idxFile} {cbFile}' + */ +static int FsPerfSlaveHandleFChMod(FSPERFCOMMSSLAVESTATE *pState, char **papszArgs, int cArgs) +{ + /* + * Parse parameters. + */ + if (cArgs != 1 + 2) + return FsPerfSlaveSyntax(pState, "takes exactly two arguments, not %u", cArgs); + + uint32_t idxFile; + int rc = FsPerfSlaveParseFileIdx(pState, papszArgs[1], &idxFile); + if (RT_FAILURE(rc)) + return rc; + + uint32_t fAttribs; + rc = FsPerfSlaveParseU32(pState, papszArgs[2], "new file attributes", 0, 0, UINT32_MAX, &fAttribs); + if (RT_FAILURE(rc)) + return rc; + + if (pState->ahFiles[idxFile] == NIL_RTFILE) + return FsPerfSlaveError(pState, VERR_INVALID_HANDLE, "no open file at index #%u", idxFile); + + /* + * Execute. + */ + rc = RTFileSetMode(pState->ahFiles[idxFile], fAttribs); + if (RT_FAILURE(rc)) + return FsPerfSlaveError(pState, rc, "failed to set file mode to %#RX32: %Rrc (file: %s)", + fAttribs, rc, pState->apszFilenames[idxFile]); + return VINF_SUCCESS; +} + + +/** + * 'reset' + */ +static int FsPerfSlaveHandleReset(FSPERFCOMMSSLAVESTATE *pState, char **papszArgs, int cArgs) +{ + /* + * Parse parameters. + */ + if (cArgs > 1) + return FsPerfSlaveSyntax(pState, "takes zero arguments, not %u", cArgs); + RT_NOREF(papszArgs); + + /* + * Execute the command. + */ + FsPerfSlaveStateCleanup(pState); + return VINF_SUCCESS; +} + + +/** + * 'exit [exitcode]' + */ +static int FsPerfSlaveHandleExit(FSPERFCOMMSSLAVESTATE *pState, char **papszArgs, int cArgs) +{ + /* + * Parse parameters. + */ + if (cArgs > 1 + 1) + return FsPerfSlaveSyntax(pState, "takes zero or one argument, not %u", cArgs); + + if (cArgs >= 1 + 1) + { + uint32_t uExitCode; + int rc = FsPerfSlaveParseU32(pState, papszArgs[1], "exit code", 0, 0, 127, &uExitCode); + if (RT_FAILURE(rc)) + return rc; + + /* + * Execute the command. + */ + pState->rcExit = (RTEXITCODE)uExitCode; + } + pState->fTerminate = true; + return VINF_SUCCESS; +} + + +/** + * Executes a script line. + */ +static int FsPerfSlaveExecuteLine(FSPERFCOMMSSLAVESTATE *pState, char *pszLine) +{ + /* + * Parse the command line using bourne shell quoting style. + */ + char **papszArgs; + int cArgs; + int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH, NULL); + if (RT_FAILURE(rc)) + return RTErrInfoSetF(&pState->ErrInfo.Core, rc, "Failed to parse line %u: %s", pState->iLineNo, pszLine); + if (cArgs <= 0) + { + RTGetOptArgvFree(papszArgs); + return RTErrInfoSetF(&pState->ErrInfo.Core, rc, "No command found on line %u: %s", pState->iLineNo, pszLine); + } + + /* + * Execute the command. + */ + static const struct + { + const char *pszCmd; + size_t cchCmd; + int (*pfnHandler)(FSPERFCOMMSSLAVESTATE *pState, char **papszArgs, int cArgs); + } s_aHandlers[] = + { + { RT_STR_TUPLE("open"), FsPerfSlaveHandleOpen }, + { RT_STR_TUPLE("close"), FsPerfSlaveHandleClose }, + { RT_STR_TUPLE("writepattern"), FsPerfSlaveHandleWritePattern }, + { RT_STR_TUPLE("truncate"), FsPerfSlaveHandleTruncate }, + { RT_STR_TUPLE("futimes"), FsPerfSlaveHandleFUTimes}, + { RT_STR_TUPLE("fchmod"), FsPerfSlaveHandleFChMod }, + { RT_STR_TUPLE("reset"), FsPerfSlaveHandleReset }, + { RT_STR_TUPLE("exit"), FsPerfSlaveHandleExit }, + }; + const char * const pszCmd = papszArgs[0]; + size_t const cchCmd = strlen(pszCmd); + for (size_t i = 0; i < RT_ELEMENTS(s_aHandlers); i++) + if ( s_aHandlers[i].cchCmd == cchCmd + && memcmp(pszCmd, s_aHandlers[i].pszCmd, cchCmd) == 0) + { + pState->pszCommand = s_aHandlers[i].pszCmd; + rc = s_aHandlers[i].pfnHandler(pState, papszArgs, cArgs); + RTGetOptArgvFree(papszArgs); + return rc; + } + + rc = RTErrInfoSetF(&pState->ErrInfo.Core, VERR_NOT_FOUND, "Command on line %u not found: %s", pState->iLineNo, pszLine); + RTGetOptArgvFree(papszArgs); + return rc; +} + + +/** + * Executes a script. + */ +static int FsPerfSlaveExecuteScript(FSPERFCOMMSSLAVESTATE *pState, char *pszContent) +{ + /* + * Validate the encoding. + */ + int rc = RTStrValidateEncoding(pszContent); + if (RT_FAILURE(rc)) + return RTErrInfoSetF(&pState->ErrInfo.Core, rc, "Invalid UTF-8 encoding"); + + /* + * Work the script content line by line. + */ + pState->iLineNo = 0; + while (*pszContent != FSPERF_EOF && *pszContent != '\0') + { + pState->iLineNo++; + + /* Figure the current line and move pszContent ahead: */ + char *pszLine = RTStrStripL(pszContent); + char *pszEol = strchr(pszLine, '\n'); + if (pszEol) + pszContent = pszEol + 1; + else + { + pszEol = strchr(pszLine, FSPERF_EOF); + AssertStmt(pszEol, pszEol = strchr(pszLine, '\0')); + pszContent = pszEol; + } + + /* Terminate and strip it: */ + *pszEol = '\0'; + pszLine = RTStrStrip(pszLine); + + /* Skip empty lines and comment lines: */ + if (*pszLine == '\0' || *pszLine == '#') + continue; + + /* Execute the line: */ + pState->pszLine = pszLine; + rc = FsPerfSlaveExecuteLine(pState, pszLine); + if (RT_FAILURE(rc)) + break; + } + return rc; +} + + +/** + * Communication slave. + * + * @returns exit code. + */ +static int FsPerfCommsSlave(void) +{ + /* + * Make sure we've got a directory and create it and it's subdir. + */ + if (g_cchCommsDir == 0) + return RTMsgError("no communcation directory was specified (-C)"); + + int rc = RTDirCreateFullPath(g_szCommsSubDir, 0775); + if (RT_FAILURE(rc)) + return RTMsgError("Failed to create '%s': %Rrc", g_szCommsSubDir, rc); + + /* + * Signal that we're here. + */ + char szTmp[_4K]; + rc = FsPerfCommsWriteFile(RT_STR_TUPLE("slave.pid"), szTmp, RTStrPrintf(szTmp, sizeof(szTmp), + "%u" FSPERF_EOF_STR, RTProcSelf())); + if (RT_FAILURE(rc)) + return RTEXITCODE_FAILURE; + + /* + * Processing loop. + */ + FSPERFCOMMSSLAVESTATE State; + FsPerfSlaveStateInit(&State); + uint32_t msSleep = 1; + while (!State.fTerminate) + { + /* + * Try read the next command script. + */ + char *pszContent = NULL; + rc = FsPerfCommsReadFileAndRename(State.iSeqNo, "-order.send", "-order.ack", &pszContent); + if (RT_SUCCESS(rc)) + { + /* + * Execute it. + */ + RTErrInfoInitStatic(&State.ErrInfo); + rc = FsPerfSlaveExecuteScript(&State, pszContent); + + /* + * Write the result. + */ + char szResult[64]; + size_t cchResult = RTStrPrintf(szResult, sizeof(szResult), "%u-order.done", State.iSeqNo); + size_t cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "%d\n%s" FSPERF_EOF_STR, + rc, RTErrInfoIsSet(&State.ErrInfo.Core) ? State.ErrInfo.Core.pszMsg : ""); + FsPerfCommsWriteFileAndRename(szResult, cchResult, szTmp, cchTmp); + State.iSeqNo++; + + msSleep = 1; + } + + /* + * Wait a little and check again. + */ + RTThreadSleep(msSleep); + if (msSleep < 128) + msSleep++; + } + + /* + * Remove the we're here indicator and quit. + */ + RTFileDelete(InCommsDir(RT_STR_TUPLE("slave.pid"))); + FsPerfSlaveStateCleanup(&State); + return State.rcExit; +} + + + +/********************************************************************************************************************************* +* Tests * +*********************************************************************************************************************************/ + +/** + * Prepares the test area. + * @returns VBox status code. + */ +static int fsPrepTestArea(void) +{ + /* The empty subdir and associated globals: */ + static char s_szEmpty[] = "empty"; + memcpy(g_szEmptyDir, g_szDir, g_cchDir); + memcpy(&g_szEmptyDir[g_cchDir], s_szEmpty, sizeof(s_szEmpty)); + g_cchEmptyDir = g_cchDir + sizeof(s_szEmpty) - 1; + RTTESTI_CHECK_RC_RET(RTDirCreate(g_szEmptyDir, 0755, 0), VINF_SUCCESS, rcCheck); + g_szEmptyDir[g_cchEmptyDir++] = RTPATH_SLASH; + g_szEmptyDir[g_cchEmptyDir] = '\0'; + RTTestIPrintf(RTTESTLVL_ALWAYS, "Empty dir: %s\n", g_szEmptyDir); + + /* Deep directory: */ + memcpy(g_szDeepDir, g_szDir, g_cchDir); + g_cchDeepDir = g_cchDir; + do + { + static char const s_szSub[] = "d" RTPATH_SLASH_STR; + memcpy(&g_szDeepDir[g_cchDeepDir], s_szSub, sizeof(s_szSub)); + g_cchDeepDir += sizeof(s_szSub) - 1; + int rc = RTDirCreate(g_szDeepDir, 0755, 0); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTDirCreate(g_szDeepDir=%s) -> %Rrc\n", g_szDeepDir, rc); + return rc; + } + } while (g_cchDeepDir < 176); + RTTestIPrintf(RTTESTLVL_ALWAYS, "Deep dir: %s\n", g_szDeepDir); + + /* Create known file in both deep and shallow dirs: */ + RTFILE hKnownFile; + RTTESTI_CHECK_RC_RET(RTFileOpen(&hKnownFile, InDir(RT_STR_TUPLE("known-file")), + RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), + VINF_SUCCESS, rcCheck); + RTTESTI_CHECK_RC_RET(RTFileClose(hKnownFile), VINF_SUCCESS, rcCheck); + + RTTESTI_CHECK_RC_RET(RTFileOpen(&hKnownFile, InDeepDir(RT_STR_TUPLE("known-file")), + RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), + VINF_SUCCESS, rcCheck); + RTTESTI_CHECK_RC_RET(RTFileClose(hKnownFile), VINF_SUCCESS, rcCheck); + + return VINF_SUCCESS; +} + + +/** + * Create a name list entry. + * @returns Pointer to the entry, NULL if out of memory. + * @param pchName The name. + * @param cchName The name length. + */ +PFSPERFNAMEENTRY fsPerfCreateNameEntry(const char *pchName, size_t cchName) +{ + PFSPERFNAMEENTRY pEntry = (PFSPERFNAMEENTRY)RTMemAllocVar(RT_UOFFSETOF_DYN(FSPERFNAMEENTRY, szName[cchName + 1])); + if (pEntry) + { + RTListInit(&pEntry->Entry); + pEntry->cchName = (uint16_t)cchName; + memcpy(pEntry->szName, pchName, cchName); + pEntry->szName[cchName] = '\0'; + } + return pEntry; +} + + +static int fsPerfManyTreeRecursiveDirCreator(size_t cchDir, uint32_t iDepth) +{ + PFSPERFNAMEENTRY pEntry = fsPerfCreateNameEntry(g_szDir, cchDir); + RTTESTI_CHECK_RET(pEntry, VERR_NO_MEMORY); + RTListAppend(&g_ManyTreeHead, &pEntry->Entry); + + RTTESTI_CHECK_RC_RET(RTDirCreate(g_szDir, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL), + VINF_SUCCESS, rcCheck); + + if (iDepth < g_cManyTreeDepth) + for (uint32_t i = 0; i < g_cManyTreeSubdirsPerDir; i++) + { + size_t cchSubDir = RTStrPrintf(&g_szDir[cchDir], sizeof(g_szDir) - cchDir, "d%02u" RTPATH_SLASH_STR, i); + RTTESTI_CHECK_RC_RET(fsPerfManyTreeRecursiveDirCreator(cchDir + cchSubDir, iDepth + 1), VINF_SUCCESS, rcCheck); + } + + return VINF_SUCCESS; +} + + +void fsPerfManyFiles(void) +{ + RTTestISub("manyfiles"); + + /* + * Create a sub-directory with like 10000 files in it. + * + * This does push the directory organization of the underlying file system, + * which is something we might not want to profile with shared folders. It + * is however useful for directory enumeration. + */ + RTTESTI_CHECK_RC_RETV(RTDirCreate(InDir(RT_STR_TUPLE("manyfiles")), 0755, + RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL), + VINF_SUCCESS); + + size_t offFilename = strlen(g_szDir); + g_szDir[offFilename++] = RTPATH_SLASH; + + fsPerfYield(); + RTFILE hFile; + uint64_t const nsStart = RTTimeNanoTS(); + for (uint32_t i = 0; i < g_cManyFiles; i++) + { + RTStrFormatU32(&g_szDir[offFilename], sizeof(g_szDir) - offFilename, i, 10, 5, 5, RTSTR_F_ZEROPAD); + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile, g_szDir, RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS); + } + uint64_t const cNsElapsed = RTTimeNanoTS() - nsStart; + RTTestIValueF(cNsElapsed, RTTESTUNIT_NS, "Creating %u empty files in single directory", g_cManyFiles); + RTTestIValueF(cNsElapsed / g_cManyFiles, RTTESTUNIT_NS_PER_OCCURRENCE, "Create empty file (single dir)"); + + /* + * Create a bunch of directories with exacly 32 files in each, hoping to + * avoid any directory organization artifacts. + */ + /* Create the directories first, building a list of them for simplifying iteration: */ + RTListInit(&g_ManyTreeHead); + InDir(RT_STR_TUPLE("manytree" RTPATH_SLASH_STR)); + RTTESTI_CHECK_RC_RETV(fsPerfManyTreeRecursiveDirCreator(strlen(g_szDir), 0), VINF_SUCCESS); + + /* Create the zero byte files: */ + fsPerfYield(); + uint64_t const nsStart2 = RTTimeNanoTS(); + uint32_t cFiles = 0; + PFSPERFNAMEENTRY pCur; + RTListForEach(&g_ManyTreeHead, pCur, FSPERFNAMEENTRY, Entry) + { + char szPath[FSPERF_MAX_PATH]; + memcpy(szPath, pCur->szName, pCur->cchName); + for (uint32_t i = 0; i < g_cManyTreeFilesPerDir; i++) + { + RTStrFormatU32(&szPath[pCur->cchName], sizeof(szPath) - pCur->cchName, i, 10, 5, 5, RTSTR_F_ZEROPAD); + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile, szPath, RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS); + cFiles++; + } + } + uint64_t const cNsElapsed2 = RTTimeNanoTS() - nsStart2; + RTTestIValueF(cNsElapsed2, RTTESTUNIT_NS, "Creating %u empty files in tree", cFiles); + RTTestIValueF(cNsElapsed2 / cFiles, RTTESTUNIT_NS_PER_OCCURRENCE, "Create empty file (tree)"); + RTTESTI_CHECK(g_cManyTreeFiles == cFiles); +} + + +DECL_FORCE_INLINE(int) fsPerfOpenExistingOnceReadonly(const char *pszFile) +{ + RTFILE hFile; + RTTESTI_CHECK_RC_RET(RTFileOpen(&hFile, pszFile, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS, rcCheck); + RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS); + return VINF_SUCCESS; +} + + +DECL_FORCE_INLINE(int) fsPerfOpenExistingOnceWriteonly(const char *pszFile) +{ + RTFILE hFile; + RTTESTI_CHECK_RC_RET(RTFileOpen(&hFile, pszFile, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS, rcCheck); + RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS); + return VINF_SUCCESS; +} + + +/** @note tstRTFileOpenEx-1.cpp has a copy of this code. */ +static void tstOpenExTest(unsigned uLine, int cbExist, int cbNext, const char *pszFilename, uint64_t fAction, + int rcExpect, RTFILEACTION enmActionExpected) +{ + uint64_t const fCreateMode = (0644 << RTFILE_O_CREATE_MODE_SHIFT); + RTFILE hFile; + int rc; + + /* + * File existence and size. + */ + bool fOkay = false; + RTFSOBJINFO ObjInfo; + rc = RTPathQueryInfoEx(pszFilename, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc)) + fOkay = cbExist == (int64_t)ObjInfo.cbObject; + else + fOkay = rc == VERR_FILE_NOT_FOUND && cbExist < 0; + if (!fOkay) + { + if (cbExist >= 0) + { + rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | fCreateMode); + if (RT_SUCCESS(rc)) + { + while (cbExist > 0) + { + int cbToWrite = (int)strlen(pszFilename); + if (cbToWrite > cbExist) + cbToWrite = cbExist; + rc = RTFileWrite(hFile, pszFilename, cbToWrite, NULL); + if (RT_FAILURE(rc)) + { + RTTestIFailed("%u: RTFileWrite(%s,%#x) -> %Rrc\n", uLine, pszFilename, cbToWrite, rc); + break; + } + cbExist -= cbToWrite; + } + + RTTESTI_CHECK_RC(RTFileClose(hFile), VINF_SUCCESS); + } + else + RTTestIFailed("%u: RTFileDelete(%s) -> %Rrc\n", uLine, pszFilename, rc); + + } + else + { + rc = RTFileDelete(pszFilename); + if (rc != VINF_SUCCESS && rc != VERR_FILE_NOT_FOUND) + RTTestIFailed("%u: RTFileDelete(%s) -> %Rrc\n", uLine, pszFilename, rc); + } + } + + /* + * The actual test. + */ + RTFILEACTION enmActuallyTaken = RTFILEACTION_END; + hFile = NIL_RTFILE; + rc = RTFileOpenEx(pszFilename, fAction | RTFILE_O_READWRITE | RTFILE_O_DENY_NONE | fCreateMode, &hFile, &enmActuallyTaken); + if ( rc != rcExpect + || enmActuallyTaken != enmActionExpected + || (RT_SUCCESS(rc) ? hFile == NIL_RTFILE : hFile != NIL_RTFILE)) + RTTestIFailed("%u: RTFileOpenEx(%s, %#llx) -> %Rrc + %d (hFile=%p), expected %Rrc + %d\n", + uLine, pszFilename, fAction, rc, enmActuallyTaken, hFile, rcExpect, enmActionExpected); + if (RT_SUCCESS(rc)) + { + if ( enmActionExpected == RTFILEACTION_REPLACED + || enmActionExpected == RTFILEACTION_TRUNCATED) + { + uint8_t abBuf[16]; + rc = RTFileRead(hFile, abBuf, 1, NULL); + if (rc != VERR_EOF) + RTTestIFailed("%u: RTFileRead(%s,,1,) -> %Rrc, expected VERR_EOF\n", uLine, pszFilename, rc); + } + + while (cbNext > 0) + { + int cbToWrite = (int)strlen(pszFilename); + if (cbToWrite > cbNext) + cbToWrite = cbNext; + rc = RTFileWrite(hFile, pszFilename, cbToWrite, NULL); + if (RT_FAILURE(rc)) + { + RTTestIFailed("%u: RTFileWrite(%s,%#x) -> %Rrc\n", uLine, pszFilename, cbToWrite, rc); + break; + } + cbNext -= cbToWrite; + } + + rc = RTFileClose(hFile); + if (RT_FAILURE(rc)) + RTTestIFailed("%u: RTFileClose(%p) -> %Rrc\n", uLine, hFile, rc); + } +} + + +void fsPerfOpen(void) +{ + RTTestISub("open"); + + /* Opening non-existing files. */ + RTFILE hFile; + RTTESTI_CHECK_RC(RTFileOpen(&hFile, InEmptyDir(RT_STR_TUPLE("no-such-file")), + RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VERR_FILE_NOT_FOUND); + RTTESTI_CHECK_RC(RTFileOpen(&hFile, InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")), + RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTFileOpen(&hFile, InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")), + RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VERR_PATH_NOT_FOUND); + + /* + * The following is copied from tstRTFileOpenEx-1.cpp: + */ + InDir(RT_STR_TUPLE("file1")); + tstOpenExTest(__LINE__, -1, -1, g_szDir, RTFILE_O_OPEN, VERR_FILE_NOT_FOUND, RTFILEACTION_INVALID); + tstOpenExTest(__LINE__, -1, -1, g_szDir, RTFILE_O_OPEN_CREATE, VINF_SUCCESS, RTFILEACTION_CREATED); + tstOpenExTest(__LINE__, 0, 0, g_szDir, RTFILE_O_OPEN_CREATE, VINF_SUCCESS, RTFILEACTION_OPENED); + tstOpenExTest(__LINE__, 0, 0, g_szDir, RTFILE_O_OPEN, VINF_SUCCESS, RTFILEACTION_OPENED); + + tstOpenExTest(__LINE__, 0, 0, g_szDir, RTFILE_O_OPEN | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_TRUNCATED); + tstOpenExTest(__LINE__, 0, 10, g_szDir, RTFILE_O_OPEN_CREATE | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_TRUNCATED); + tstOpenExTest(__LINE__, 10, 10, g_szDir, RTFILE_O_OPEN_CREATE | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_TRUNCATED); + tstOpenExTest(__LINE__, 10, -1, g_szDir, RTFILE_O_OPEN | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_TRUNCATED); + tstOpenExTest(__LINE__, -1, -1, g_szDir, RTFILE_O_OPEN | RTFILE_O_TRUNCATE, VERR_FILE_NOT_FOUND, RTFILEACTION_INVALID); + tstOpenExTest(__LINE__, -1, 0, g_szDir, RTFILE_O_OPEN_CREATE | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_CREATED); + + tstOpenExTest(__LINE__, 0, -1, g_szDir, RTFILE_O_CREATE_REPLACE, VINF_SUCCESS, RTFILEACTION_REPLACED); + tstOpenExTest(__LINE__, -1, 0, g_szDir, RTFILE_O_CREATE_REPLACE, VINF_SUCCESS, RTFILEACTION_CREATED); + tstOpenExTest(__LINE__, 0, -1, g_szDir, RTFILE_O_CREATE, VERR_ALREADY_EXISTS, RTFILEACTION_ALREADY_EXISTS); + tstOpenExTest(__LINE__, -1, -1, g_szDir, RTFILE_O_CREATE, VINF_SUCCESS, RTFILEACTION_CREATED); + + tstOpenExTest(__LINE__, -1, 10, g_szDir, RTFILE_O_CREATE | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_CREATED); + tstOpenExTest(__LINE__, 10, 10, g_szDir, RTFILE_O_CREATE | RTFILE_O_TRUNCATE, VERR_ALREADY_EXISTS, RTFILEACTION_ALREADY_EXISTS); + tstOpenExTest(__LINE__, 10, -1, g_szDir, RTFILE_O_CREATE_REPLACE | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_REPLACED); + tstOpenExTest(__LINE__, -1, -1, g_szDir, RTFILE_O_CREATE_REPLACE | RTFILE_O_TRUNCATE, VINF_SUCCESS, RTFILEACTION_CREATED); + + RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VINF_SUCCESS); + + /* + * Create file1 and then try exclusivly creating it again. + * Then profile opening it for reading. + */ + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file1")), + RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileOpen(&hFile, g_szDir, RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VERR_ALREADY_EXISTS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + PROFILE_FN(fsPerfOpenExistingOnceReadonly(g_szDir), g_nsTestRun, "RTFileOpen/Close/Readonly"); + PROFILE_FN(fsPerfOpenExistingOnceWriteonly(g_szDir), g_nsTestRun, "RTFileOpen/Close/Writeonly"); + + /* + * Profile opening in the deep directory too. + */ + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file1")), + RTFILE_O_CREATE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + PROFILE_FN(fsPerfOpenExistingOnceReadonly(g_szDeepDir), g_nsTestRun, "RTFileOpen/Close/deep/readonly"); + PROFILE_FN(fsPerfOpenExistingOnceWriteonly(g_szDeepDir), g_nsTestRun, "RTFileOpen/Close/deep/writeonly"); + + /* Manytree: */ + char szPath[FSPERF_MAX_PATH]; + PROFILE_MANYTREE_FN(szPath, fsPerfOpenExistingOnceReadonly(szPath), 1, g_nsTestRun, "RTFileOpen/Close/manytree/readonly"); +} + + +void fsPerfFStat(void) +{ + RTTestISub("fstat"); + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file2")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTFSOBJINFO ObjInfo = {0}; + PROFILE_FN(RTFileQueryInfo(hFile1, &ObjInfo, RTFSOBJATTRADD_NOTHING), g_nsTestRun, "RTFileQueryInfo/NOTHING"); + PROFILE_FN(RTFileQueryInfo(hFile1, &ObjInfo, RTFSOBJATTRADD_UNIX), g_nsTestRun, "RTFileQueryInfo/UNIX"); + + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); +} + +#ifdef RT_OS_WINDOWS +/** + * Nt(Query|Set|QueryDir)Information(File|) information class info. + */ +static const struct +{ + const char *pszName; + int enmValue; + bool fQuery; + bool fSet; + bool fQueryDir; + uint8_t cbMin; +} g_aNtQueryInfoFileClasses[] = +{ +#define E(a_enmValue, a_fQuery, a_fSet, a_fQueryDir, a_cbMin) \ + { #a_enmValue, a_enmValue, a_fQuery, a_fSet, a_fQueryDir, a_cbMin } + { "invalid0", 0, false, false, false, 0 }, + E(FileDirectoryInformation, false, false, true, sizeof(FILE_DIRECTORY_INFORMATION)), // 0x00, 0x00, 0x48 + E(FileFullDirectoryInformation, false, false, true, sizeof(FILE_FULL_DIR_INFORMATION)), // 0x00, 0x00, 0x48 + E(FileBothDirectoryInformation, false, false, true, sizeof(FILE_BOTH_DIR_INFORMATION)), // 0x00, 0x00, 0x60 + E(FileBasicInformation, true, true, false, sizeof(FILE_BASIC_INFORMATION)), + E(FileStandardInformation, true, false, false, sizeof(FILE_STANDARD_INFORMATION)), + E(FileInternalInformation, true, false, false, sizeof(FILE_INTERNAL_INFORMATION)), + E(FileEaInformation, true, false, false, sizeof(FILE_EA_INFORMATION)), + E(FileAccessInformation, true, false, false, sizeof(FILE_ACCESS_INFORMATION)), + E(FileNameInformation, true, false, false, sizeof(FILE_NAME_INFORMATION)), + E(FileRenameInformation, false, true, false, sizeof(FILE_RENAME_INFORMATION)), + E(FileLinkInformation, false, true, false, sizeof(FILE_LINK_INFORMATION)), + E(FileNamesInformation, false, false, true, sizeof(FILE_NAMES_INFORMATION)), // 0x00, 0x00, 0x10 + E(FileDispositionInformation, false, true, false, sizeof(FILE_DISPOSITION_INFORMATION)), // 0x00, 0x01, + E(FilePositionInformation, true, true, false, sizeof(FILE_POSITION_INFORMATION)), // 0x08, 0x08, + E(FileFullEaInformation, false, false, false, sizeof(FILE_FULL_EA_INFORMATION)), // 0x00, 0x00, + E(FileModeInformation, true, true, false, sizeof(FILE_MODE_INFORMATION)), // 0x04, 0x04, + E(FileAlignmentInformation, true, false, false, sizeof(FILE_ALIGNMENT_INFORMATION)), // 0x04, 0x00, + E(FileAllInformation, true, false, false, sizeof(FILE_ALL_INFORMATION)), // 0x68, 0x00, + E(FileAllocationInformation, false, true, false, sizeof(FILE_ALLOCATION_INFORMATION)), // 0x00, 0x08, + E(FileEndOfFileInformation, false, true, false, sizeof(FILE_END_OF_FILE_INFORMATION)), // 0x00, 0x08, + E(FileAlternateNameInformation, true, false, false, sizeof(FILE_NAME_INFORMATION)), // 0x08, 0x00, + E(FileStreamInformation, true, false, false, sizeof(FILE_STREAM_INFORMATION)), // 0x20, 0x00, + E(FilePipeInformation, true, true, false, sizeof(FILE_PIPE_INFORMATION)), // 0x08, 0x08, + E(FilePipeLocalInformation, true, false, false, sizeof(FILE_PIPE_LOCAL_INFORMATION)), // 0x28, 0x00, + E(FilePipeRemoteInformation, true, true, false, sizeof(FILE_PIPE_REMOTE_INFORMATION)), // 0x10, 0x10, + E(FileMailslotQueryInformation, true, false, false, sizeof(FILE_MAILSLOT_QUERY_INFORMATION)), // 0x18, 0x00, + E(FileMailslotSetInformation, false, true, false, sizeof(FILE_MAILSLOT_SET_INFORMATION)), // 0x00, 0x08, + E(FileCompressionInformation, true, false, false, sizeof(FILE_COMPRESSION_INFORMATION)), // 0x10, 0x00, + E(FileObjectIdInformation, true, true, true, sizeof(FILE_OBJECTID_INFORMATION)), // 0x48, 0x48, + E(FileCompletionInformation, false, true, false, sizeof(FILE_COMPLETION_INFORMATION)), // 0x00, 0x10, + E(FileMoveClusterInformation, false, true, false, sizeof(FILE_MOVE_CLUSTER_INFORMATION)), // 0x00, 0x18, + E(FileQuotaInformation, true, true, true, sizeof(FILE_QUOTA_INFORMATION)), // 0x38, 0x38, 0x38 + E(FileReparsePointInformation, true, false, true, sizeof(FILE_REPARSE_POINT_INFORMATION)), // 0x10, 0x00, 0x10 + E(FileNetworkOpenInformation, true, false, false, sizeof(FILE_NETWORK_OPEN_INFORMATION)), // 0x38, 0x00, + E(FileAttributeTagInformation, true, false, false, sizeof(FILE_ATTRIBUTE_TAG_INFORMATION)), // 0x08, 0x00, + E(FileTrackingInformation, false, true, false, sizeof(FILE_TRACKING_INFORMATION)), // 0x00, 0x10, + E(FileIdBothDirectoryInformation, false, false, true, sizeof(FILE_ID_BOTH_DIR_INFORMATION)), // 0x00, 0x00, 0x70 + E(FileIdFullDirectoryInformation, false, false, true, sizeof(FILE_ID_FULL_DIR_INFORMATION)), // 0x00, 0x00, 0x58 + E(FileValidDataLengthInformation, false, true, false, sizeof(FILE_VALID_DATA_LENGTH_INFORMATION)), // 0x00, 0x08, + E(FileShortNameInformation, false, true, false, sizeof(FILE_NAME_INFORMATION)), // 0x00, 0x08, + E(FileIoCompletionNotificationInformation, true, true, false, sizeof(FILE_IO_COMPLETION_NOTIFICATION_INFORMATION)), // 0x04, 0x04, + E(FileIoStatusBlockRangeInformation, false, true, false, sizeof(IO_STATUS_BLOCK) /*?*/), // 0x00, 0x10, + E(FileIoPriorityHintInformation, true, true, false, sizeof(FILE_IO_PRIORITY_HINT_INFORMATION)), // 0x04, 0x04, + E(FileSfioReserveInformation, true, true, false, sizeof(FILE_SFIO_RESERVE_INFORMATION)), // 0x14, 0x14, + E(FileSfioVolumeInformation, true, false, false, sizeof(FILE_SFIO_VOLUME_INFORMATION)), // 0x0C, 0x00, + E(FileHardLinkInformation, true, false, false, sizeof(FILE_LINKS_INFORMATION)), // 0x20, 0x00, + E(FileProcessIdsUsingFileInformation, true, false, false, sizeof(FILE_PROCESS_IDS_USING_FILE_INFORMATION)), // 0x10, 0x00, + E(FileNormalizedNameInformation, true, false, false, sizeof(FILE_NAME_INFORMATION)), // 0x08, 0x00, + E(FileNetworkPhysicalNameInformation, true, false, false, sizeof(FILE_NETWORK_PHYSICAL_NAME_INFORMATION)), // 0x08, 0x00, + E(FileIdGlobalTxDirectoryInformation, false, false, true, sizeof(FILE_ID_GLOBAL_TX_DIR_INFORMATION)), // 0x00, 0x00, 0x60 + E(FileIsRemoteDeviceInformation, true, false, false, sizeof(FILE_IS_REMOTE_DEVICE_INFORMATION)), // 0x01, 0x00, + E(FileUnusedInformation, false, false, false, 0), // 0x00, 0x00, + E(FileNumaNodeInformation, true, false, false, sizeof(FILE_NUMA_NODE_INFORMATION)), // 0x02, 0x00, + E(FileStandardLinkInformation, true, false, false, sizeof(FILE_STANDARD_LINK_INFORMATION)), // 0x0C, 0x00, + E(FileRemoteProtocolInformation, true, false, false, sizeof(FILE_REMOTE_PROTOCOL_INFORMATION)), // 0x74, 0x00, + E(FileRenameInformationBypassAccessCheck, false, false, false, 0 /*kernel mode only*/), // 0x00, 0x00, + E(FileLinkInformationBypassAccessCheck, false, false, false, 0 /*kernel mode only*/), // 0x00, 0x00, + E(FileVolumeNameInformation, true, false, false, sizeof(FILE_VOLUME_NAME_INFORMATION)), // 0x08, 0x00, + E(FileIdInformation, true, false, false, sizeof(FILE_ID_INFORMATION)), // 0x18, 0x00, + E(FileIdExtdDirectoryInformation, false, false, true, sizeof(FILE_ID_EXTD_DIR_INFORMATION)), // 0x00, 0x00, 0x60 + E(FileReplaceCompletionInformation, false, true, false, sizeof(FILE_COMPLETION_INFORMATION)), // 0x00, 0x10, + E(FileHardLinkFullIdInformation, true, false, false, sizeof(FILE_LINK_ENTRY_FULL_ID_INFORMATION)), // 0x24, 0x00, + E(FileIdExtdBothDirectoryInformation, false, false, true, sizeof(FILE_ID_EXTD_BOTH_DIR_INFORMATION)), // 0x00, 0x00, 0x78 + E(FileDispositionInformationEx, false, true, false, sizeof(FILE_DISPOSITION_INFORMATION_EX)), // 0x00, 0x04, + E(FileRenameInformationEx, false, true, false, sizeof(FILE_RENAME_INFORMATION)), // 0x00, 0x18, + E(FileRenameInformationExBypassAccessCheck, false, false, false, 0 /*kernel mode only*/), // 0x00, 0x00, + E(FileDesiredStorageClassInformation, true, true, false, sizeof(FILE_DESIRED_STORAGE_CLASS_INFORMATION)), // 0x08, 0x08, + E(FileStatInformation, true, false, false, sizeof(FILE_STAT_INFORMATION)), // 0x48, 0x00, + E(FileMemoryPartitionInformation, false, true, false, 0x10), // 0x00, 0x10, + E(FileStatLxInformation, true, false, false, sizeof(FILE_STAT_LX_INFORMATION)), // 0x60, 0x00, + E(FileCaseSensitiveInformation, true, true, false, sizeof(FILE_CASE_SENSITIVE_INFORMATION)), // 0x04, 0x04, + E(FileLinkInformationEx, false, true, false, sizeof(FILE_LINK_INFORMATION)), // 0x00, 0x18, + E(FileLinkInformationExBypassAccessCheck, false, false, false, 0 /*kernel mode only*/), // 0x00, 0x00, + E(FileStorageReserveIdInformation, true, true, false, 0x04), // 0x04, 0x04, + E(FileCaseSensitiveInformationForceAccessCheck, true, true, false, sizeof(FILE_CASE_SENSITIVE_INFORMATION)), // 0x04, 0x04, +#undef E +}; + +void fsPerfNtQueryInfoFileWorker(HANDLE hNtFile1, uint32_t fType) +{ + char const chType = fType == RTFS_TYPE_DIRECTORY ? 'd' : 'r'; + + /** @todo may run out of buffer for really long paths? */ + union + { + uint8_t ab[4096]; + FILE_ACCESS_INFORMATION Access; + FILE_ALIGNMENT_INFORMATION Align; + FILE_ALL_INFORMATION All; + FILE_ALLOCATION_INFORMATION Alloc; + FILE_ATTRIBUTE_TAG_INFORMATION AttribTag; + FILE_BASIC_INFORMATION Basic; + FILE_BOTH_DIR_INFORMATION BothDir; + FILE_CASE_SENSITIVE_INFORMATION CaseSensitivity; + FILE_COMPLETION_INFORMATION Completion; + FILE_COMPRESSION_INFORMATION Compression; + FILE_DESIRED_STORAGE_CLASS_INFORMATION StorageClass; + FILE_DIRECTORY_INFORMATION Dir; + FILE_DISPOSITION_INFORMATION Disp; + FILE_DISPOSITION_INFORMATION_EX DispEx; + FILE_EA_INFORMATION Ea; + FILE_END_OF_FILE_INFORMATION EndOfFile; + FILE_FULL_DIR_INFORMATION FullDir; + FILE_FULL_EA_INFORMATION FullEa; + FILE_ID_BOTH_DIR_INFORMATION IdBothDir; + FILE_ID_EXTD_BOTH_DIR_INFORMATION ExtIdBothDir; + FILE_ID_EXTD_DIR_INFORMATION ExtIdDir; + FILE_ID_FULL_DIR_INFORMATION IdFullDir; + FILE_ID_GLOBAL_TX_DIR_INFORMATION IdGlobalTx; + FILE_ID_INFORMATION IdInfo; + FILE_INTERNAL_INFORMATION Internal; + FILE_IO_COMPLETION_NOTIFICATION_INFORMATION IoCompletion; + FILE_IO_PRIORITY_HINT_INFORMATION IoPrioHint; + FILE_IS_REMOTE_DEVICE_INFORMATION IsRemoteDev; + FILE_LINK_ENTRY_FULL_ID_INFORMATION LinkFullId; + FILE_LINK_INFORMATION Link; + FILE_MAILSLOT_QUERY_INFORMATION MailslotQuery; + FILE_MAILSLOT_SET_INFORMATION MailslotSet; + FILE_MODE_INFORMATION Mode; + FILE_MOVE_CLUSTER_INFORMATION MoveCluster; + FILE_NAME_INFORMATION Name; + FILE_NAMES_INFORMATION Names; + FILE_NETWORK_OPEN_INFORMATION NetOpen; + FILE_NUMA_NODE_INFORMATION Numa; + FILE_OBJECTID_INFORMATION ObjId; + FILE_PIPE_INFORMATION Pipe; + FILE_PIPE_LOCAL_INFORMATION PipeLocal; + FILE_PIPE_REMOTE_INFORMATION PipeRemote; + FILE_POSITION_INFORMATION Pos; + FILE_PROCESS_IDS_USING_FILE_INFORMATION Pids; + FILE_QUOTA_INFORMATION Quota; + FILE_REMOTE_PROTOCOL_INFORMATION RemoteProt; + FILE_RENAME_INFORMATION Rename; + FILE_REPARSE_POINT_INFORMATION Reparse; + FILE_SFIO_RESERVE_INFORMATION SfiRes; + FILE_SFIO_VOLUME_INFORMATION SfioVol; + FILE_STANDARD_INFORMATION Std; + FILE_STANDARD_LINK_INFORMATION StdLink; + FILE_STAT_INFORMATION Stat; + FILE_STAT_LX_INFORMATION StatLx; + FILE_STREAM_INFORMATION Stream; + FILE_TRACKING_INFORMATION Tracking; + FILE_VALID_DATA_LENGTH_INFORMATION ValidDataLen; + FILE_VOLUME_NAME_INFORMATION VolName; + } uBuf; + + IO_STATUS_BLOCK const VirginIos = RTNT_IO_STATUS_BLOCK_INITIALIZER; + for (unsigned i = 0; i < RT_ELEMENTS(g_aNtQueryInfoFileClasses); i++) + { + FILE_INFORMATION_CLASS const enmClass = (FILE_INFORMATION_CLASS)g_aNtQueryInfoFileClasses[i].enmValue; + const char * const pszClass = g_aNtQueryInfoFileClasses[i].pszName; + + memset(&uBuf, 0xff, sizeof(uBuf)); + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + ULONG cbBuf = sizeof(uBuf); + NTSTATUS rcNt = NtQueryInformationFile(hNtFile1, &Ios, &uBuf, cbBuf, enmClass); + if (NT_SUCCESS(rcNt)) + { + if (Ios.Status == VirginIos.Status || Ios.Information == VirginIos.Information) + RTTestIFailed("%s/%#x: I/O status block was not modified: %#x %#zx", pszClass, cbBuf, Ios.Status, Ios.Information); + else if (!g_aNtQueryInfoFileClasses[i].fQuery) + RTTestIFailed("%s/%#x: This isn't supposed to be queriable! (rcNt=%#x)", pszClass, cbBuf, rcNt); + else + { + ULONG const cbActualMin = enmClass != FileStorageReserveIdInformation ? Ios.Information : 4; /* weird */ + + switch (enmClass) + { + case FileNameInformation: + case FileAlternateNameInformation: + case FileShortNameInformation: + case FileNormalizedNameInformation: + case FileNetworkPhysicalNameInformation: + if ( RT_UOFFSETOF_DYN(FILE_NAME_INFORMATION, FileName[uBuf.Name.FileNameLength / sizeof(WCHAR)]) + != cbActualMin) + RTTestIFailed("%s/%#x: Wrong FileNameLength=%#x vs cbActual=%#x", + pszClass, cbActualMin, uBuf.Name.FileNameLength, cbActualMin); + if (uBuf.Name.FileName[uBuf.Name.FileNameLength / sizeof(WCHAR) - 1] == '\0') + RTTestIFailed("%s/%#x: Zero terminated name!", pszClass, cbActualMin); + if (g_uVerbosity > 1) + RTTestIPrintf(RTTESTLVL_ALWAYS, "%+34s/%#x: FileNameLength=%#x FileName='%.*ls'\n", + pszClass, cbActualMin, uBuf.Name.FileNameLength, + uBuf.Name.FileNameLength / sizeof(WCHAR), uBuf.Name.FileName); + break; + + case FileVolumeNameInformation: + if (RT_UOFFSETOF_DYN(FILE_VOLUME_NAME_INFORMATION, + DeviceName[uBuf.VolName.DeviceNameLength / sizeof(WCHAR)]) != cbActualMin) + RTTestIFailed("%s/%#x: Wrong DeviceNameLength=%#x vs cbActual=%#x", + pszClass, cbActualMin, uBuf.VolName.DeviceNameLength, cbActualMin); + if (uBuf.VolName.DeviceName[uBuf.VolName.DeviceNameLength / sizeof(WCHAR) - 1] == '\0') + RTTestIFailed("%s/%#x: Zero terminated name!", pszClass, cbActualMin); + if (g_uVerbosity > 1) + RTTestIPrintf(RTTESTLVL_ALWAYS, "%+34s/%#x: DeviceNameLength=%#x DeviceName='%.*ls'\n", + pszClass, cbActualMin, uBuf.VolName.DeviceNameLength, + uBuf.VolName.DeviceNameLength / sizeof(WCHAR), uBuf.VolName.DeviceName); + break; + default: + break; + } + + ULONG const cbMin = g_aNtQueryInfoFileClasses[i].cbMin; + ULONG const cbMax = RT_MIN(cbActualMin + 64, sizeof(uBuf)); + for (cbBuf = 0; cbBuf < cbMax; cbBuf++) + { + memset(&uBuf, 0xfe, sizeof(uBuf)); + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + rcNt = NtQueryInformationFile(hNtFile1, &Ios, &uBuf, cbBuf, enmClass); + if (!ASMMemIsAllU8(&uBuf.ab[cbBuf], sizeof(uBuf) - cbBuf, 0xfe)) + RTTestIFailed("%s/%#x: Touched memory beyond end of buffer (rcNt=%#x)", pszClass, cbBuf, rcNt); + if (cbBuf < cbMin) + { + if (rcNt != STATUS_INFO_LENGTH_MISMATCH) + RTTestIFailed("%s/%#x: %#x, expected STATUS_INFO_LENGTH_MISMATCH", pszClass, cbBuf, rcNt); + if (Ios.Status != VirginIos.Status || Ios.Information != VirginIos.Information) + RTTestIFailed("%s/%#x: I/O status block was modified (STATUS_INFO_LENGTH_MISMATCH): %#x %#zx", + pszClass, cbBuf, Ios.Status, Ios.Information); + } + else if (cbBuf < cbActualMin) + { + if ( rcNt != STATUS_BUFFER_OVERFLOW + /* RDR2/w10 returns success if the buffer can hold exactly the share name: */ + && !( rcNt == STATUS_SUCCESS + && enmClass == FileNetworkPhysicalNameInformation) + ) + RTTestIFailed("%s/%#x: %#x, expected STATUS_BUFFER_OVERFLOW", pszClass, cbBuf, rcNt); + /** @todo check name and length fields */ + } + else + { + if ( !ASMMemIsAllU8(&uBuf.ab[cbActualMin], sizeof(uBuf) - cbActualMin, 0xfe) + && enmClass != FileStorageReserveIdInformation /* NTFS bug? */ ) + RTTestIFailed("%s/%#x: Touched memory beyond returned length (cbActualMin=%#x, rcNt=%#x)", + pszClass, cbBuf, cbActualMin, rcNt); + + } + } + } + } + else + { + if (!g_aNtQueryInfoFileClasses[i].fQuery) + { + if ( rcNt != STATUS_INVALID_INFO_CLASS + && ( rcNt != STATUS_INVALID_PARAMETER /* w7rtm-32 result */ + || enmClass != FileUnusedInformation)) + RTTestIFailed("%s/%#x/%c: %#x, expected STATUS_INVALID_INFO_CLASS", pszClass, cbBuf, chType, rcNt); + } + else if ( rcNt != STATUS_INVALID_INFO_CLASS + && rcNt != STATUS_INVALID_PARAMETER + && !(rcNt == STATUS_OBJECT_NAME_NOT_FOUND && enmClass == FileAlternateNameInformation) + && !( rcNt == STATUS_ACCESS_DENIED + && ( enmClass == FileIoPriorityHintInformation + || enmClass == FileSfioReserveInformation + || enmClass == FileStatLxInformation)) + && !(rcNt == STATUS_NO_SUCH_DEVICE && enmClass == FileNumaNodeInformation) + && !( rcNt == STATUS_NOT_SUPPORTED /* RDR2/W10-17763 */ + && ( enmClass == FileMailslotQueryInformation + || enmClass == FileObjectIdInformation + || enmClass == FileReparsePointInformation + || enmClass == FileSfioVolumeInformation + || enmClass == FileHardLinkInformation + || enmClass == FileStandardLinkInformation + || enmClass == FileHardLinkFullIdInformation + || enmClass == FileDesiredStorageClassInformation + || enmClass == FileStatInformation + || enmClass == FileCaseSensitiveInformation + || enmClass == FileStorageReserveIdInformation + || enmClass == FileCaseSensitiveInformationForceAccessCheck) + || ( fType == RTFS_TYPE_DIRECTORY + && (enmClass == FileSfioReserveInformation || enmClass == FileStatLxInformation))) + && !(rcNt == STATUS_INVALID_DEVICE_REQUEST && fType == RTFS_TYPE_FILE) + ) + RTTestIFailed("%s/%#x/%c: %#x", pszClass, cbBuf, chType, rcNt); + if ( (Ios.Status != VirginIos.Status || Ios.Information != VirginIos.Information) + && !(fType == RTFS_TYPE_DIRECTORY && Ios.Status == rcNt && Ios.Information == 0) /* NTFS/W10-17763 */ + && !( enmClass == FileUnusedInformation + && Ios.Status == rcNt && Ios.Information == sizeof(uBuf)) /* NTFS/VBoxSF/w7rtm */ ) + RTTestIFailed("%s/%#x/%c: I/O status block was modified: %#x %#zx", + pszClass, cbBuf, chType, Ios.Status, Ios.Information); + if (!ASMMemIsAllU8(&uBuf, sizeof(uBuf), 0xff)) + RTTestIFailed("%s/%#x/%c: Buffer was touched in failure case!", pszClass, cbBuf, chType); + } + } +} + +void fsPerfNtQueryInfoFile(void) +{ + RTTestISub("NtQueryInformationFile"); + + /* On a regular file: */ + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file2qif")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE), VINF_SUCCESS); + fsPerfNtQueryInfoFileWorker((HANDLE)RTFileToNative(hFile1), RTFS_TYPE_FILE); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + /* On a directory: */ + HANDLE hDir1 = INVALID_HANDLE_VALUE; + RTTESTI_CHECK_RC_RETV(RTNtPathOpenDir(InDir(RT_STR_TUPLE("")), GENERIC_READ | SYNCHRONIZE | FILE_SYNCHRONOUS_IO_NONALERT, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, 0, &hDir1, NULL), VINF_SUCCESS); + fsPerfNtQueryInfoFileWorker(hDir1, RTFS_TYPE_DIRECTORY); + RTTESTI_CHECK(CloseHandle(hDir1) == TRUE); +} + + +/** + * Nt(Query|Set)VolumeInformationFile) information class info. + */ +static const struct +{ + const char *pszName; + int enmValue; + bool fQuery; + bool fSet; + uint8_t cbMin; +} g_aNtQueryVolInfoFileClasses[] = +{ +#define E(a_enmValue, a_fQuery, a_fSet, a_cbMin) \ + { #a_enmValue, a_enmValue, a_fQuery, a_fSet, a_cbMin } + { "invalid0", 0, false, false, 0 }, + E(FileFsVolumeInformation, 1, 0, sizeof(FILE_FS_VOLUME_INFORMATION)), + E(FileFsLabelInformation, 0, 1, sizeof(FILE_FS_LABEL_INFORMATION)), + E(FileFsSizeInformation, 1, 0, sizeof(FILE_FS_SIZE_INFORMATION)), + E(FileFsDeviceInformation, 1, 0, sizeof(FILE_FS_DEVICE_INFORMATION)), + E(FileFsAttributeInformation, 1, 0, sizeof(FILE_FS_ATTRIBUTE_INFORMATION)), + E(FileFsControlInformation, 1, 1, sizeof(FILE_FS_CONTROL_INFORMATION)), + E(FileFsFullSizeInformation, 1, 0, sizeof(FILE_FS_FULL_SIZE_INFORMATION)), + E(FileFsObjectIdInformation, 1, 1, sizeof(FILE_FS_OBJECTID_INFORMATION)), + E(FileFsDriverPathInformation, 1, 0, sizeof(FILE_FS_DRIVER_PATH_INFORMATION)), + E(FileFsVolumeFlagsInformation, 1, 1, sizeof(FILE_FS_VOLUME_FLAGS_INFORMATION)), + E(FileFsSectorSizeInformation, 1, 0, sizeof(FILE_FS_SECTOR_SIZE_INFORMATION)), + E(FileFsDataCopyInformation, 1, 0, sizeof(FILE_FS_DATA_COPY_INFORMATION)), + E(FileFsMetadataSizeInformation, 1, 0, sizeof(FILE_FS_METADATA_SIZE_INFORMATION)), + E(FileFsFullSizeInformationEx, 1, 0, sizeof(FILE_FS_FULL_SIZE_INFORMATION_EX)), +#undef E +}; + +void fsPerfNtQueryVolInfoFileWorker(HANDLE hNtFile1, uint32_t fType) +{ + char const chType = fType == RTFS_TYPE_DIRECTORY ? 'd' : 'r'; + union + { + uint8_t ab[4096]; + FILE_FS_VOLUME_INFORMATION Vol; + FILE_FS_LABEL_INFORMATION Label; + FILE_FS_SIZE_INFORMATION Size; + FILE_FS_DEVICE_INFORMATION Dev; + FILE_FS_ATTRIBUTE_INFORMATION Attrib; + FILE_FS_CONTROL_INFORMATION Ctrl; + FILE_FS_FULL_SIZE_INFORMATION FullSize; + FILE_FS_OBJECTID_INFORMATION ObjId; + FILE_FS_DRIVER_PATH_INFORMATION DrvPath; + FILE_FS_VOLUME_FLAGS_INFORMATION VolFlags; + FILE_FS_SECTOR_SIZE_INFORMATION SectorSize; + FILE_FS_DATA_COPY_INFORMATION DataCopy; + FILE_FS_METADATA_SIZE_INFORMATION Metadata; + FILE_FS_FULL_SIZE_INFORMATION_EX FullSizeEx; + } uBuf; + + IO_STATUS_BLOCK const VirginIos = RTNT_IO_STATUS_BLOCK_INITIALIZER; + for (unsigned i = 0; i < RT_ELEMENTS(g_aNtQueryVolInfoFileClasses); i++) + { + FS_INFORMATION_CLASS const enmClass = (FS_INFORMATION_CLASS)g_aNtQueryVolInfoFileClasses[i].enmValue; + const char * const pszClass = g_aNtQueryVolInfoFileClasses[i].pszName; + + memset(&uBuf, 0xff, sizeof(uBuf)); + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + ULONG cbBuf = sizeof(uBuf); + NTSTATUS rcNt = NtQueryVolumeInformationFile(hNtFile1, &Ios, &uBuf, cbBuf, enmClass); + if (g_uVerbosity > 3) + RTTestIPrintf(RTTESTLVL_ALWAYS, "%+34s/%#04x/%c: rcNt=%#x Ios.Status=%#x Info=%#zx\n", + pszClass, cbBuf, chType, rcNt, Ios.Status, Ios.Information); + if (NT_SUCCESS(rcNt)) + { + if (Ios.Status == VirginIos.Status || Ios.Information == VirginIos.Information) + RTTestIFailed("%s/%#x/%c: I/O status block was not modified: %#x %#zx", + pszClass, cbBuf, chType, Ios.Status, Ios.Information); + else if (!g_aNtQueryVolInfoFileClasses[i].fQuery) + RTTestIFailed("%s/%#x/%c: This isn't supposed to be queriable! (rcNt=%#x)", pszClass, cbBuf, chType, rcNt); + else + { + ULONG const cbActualMin = Ios.Information; + ULONG *pcbName = NULL; + ULONG offName = 0; + + switch (enmClass) + { + case FileFsVolumeInformation: + pcbName = &uBuf.Vol.VolumeLabelLength; + offName = RT_UOFFSETOF(FILE_FS_VOLUME_INFORMATION, VolumeLabel); + if (RT_UOFFSETOF_DYN(FILE_FS_VOLUME_INFORMATION, + VolumeLabel[uBuf.Vol.VolumeLabelLength / sizeof(WCHAR)]) != cbActualMin) + RTTestIFailed("%s/%#x/%c: Wrong VolumeLabelLength=%#x vs cbActual=%#x", + pszClass, cbActualMin, chType, uBuf.Vol.VolumeLabelLength, cbActualMin); + if (uBuf.Vol.VolumeLabel[uBuf.Vol.VolumeLabelLength / sizeof(WCHAR) - 1] == '\0') + RTTestIFailed("%s/%#x/%c: Zero terminated name!", pszClass, cbActualMin, chType); + if (g_uVerbosity > 1) + RTTestIPrintf(RTTESTLVL_ALWAYS, "%+34s/%#04x/%c: VolumeLabelLength=%#x VolumeLabel='%.*ls'\n", + pszClass, cbActualMin, chType, uBuf.Vol.VolumeLabelLength, + uBuf.Vol.VolumeLabelLength / sizeof(WCHAR), uBuf.Vol.VolumeLabel); + break; + + case FileFsAttributeInformation: + pcbName = &uBuf.Attrib.FileSystemNameLength; + offName = RT_UOFFSETOF(FILE_FS_ATTRIBUTE_INFORMATION, FileSystemName); + if (RT_UOFFSETOF_DYN(FILE_FS_ATTRIBUTE_INFORMATION, + FileSystemName[uBuf.Attrib.FileSystemNameLength / sizeof(WCHAR)]) != cbActualMin) + RTTestIFailed("%s/%#x/%c: Wrong FileSystemNameLength=%#x vs cbActual=%#x", + pszClass, cbActualMin, chType, uBuf.Attrib.FileSystemNameLength, cbActualMin); + if (uBuf.Attrib.FileSystemName[uBuf.Attrib.FileSystemNameLength / sizeof(WCHAR) - 1] == '\0') + RTTestIFailed("%s/%#x/%c: Zero terminated name!", pszClass, cbActualMin, chType); + if (g_uVerbosity > 1) + RTTestIPrintf(RTTESTLVL_ALWAYS, "%+34s/%#04x/%c: FileSystemNameLength=%#x FileSystemName='%.*ls' Attribs=%#x MaxCompName=%#x\n", + pszClass, cbActualMin, chType, uBuf.Attrib.FileSystemNameLength, + uBuf.Attrib.FileSystemNameLength / sizeof(WCHAR), uBuf.Attrib.FileSystemName, + uBuf.Attrib.FileSystemAttributes, uBuf.Attrib.MaximumComponentNameLength); + break; + + case FileFsDriverPathInformation: + pcbName = &uBuf.DrvPath.DriverNameLength; + offName = RT_UOFFSETOF(FILE_FS_DRIVER_PATH_INFORMATION, DriverName); + if (RT_UOFFSETOF_DYN(FILE_FS_DRIVER_PATH_INFORMATION, + DriverName[uBuf.DrvPath.DriverNameLength / sizeof(WCHAR)]) != cbActualMin) + RTTestIFailed("%s/%#x/%c: Wrong DriverNameLength=%#x vs cbActual=%#x", + pszClass, cbActualMin, chType, uBuf.DrvPath.DriverNameLength, cbActualMin); + if (uBuf.DrvPath.DriverName[uBuf.DrvPath.DriverNameLength / sizeof(WCHAR) - 1] == '\0') + RTTestIFailed("%s/%#x/%c: Zero terminated name!", pszClass, cbActualMin, chType); + if (g_uVerbosity > 1) + RTTestIPrintf(RTTESTLVL_ALWAYS, "%+34s/%#04x/%c: DriverNameLength=%#x DriverName='%.*ls'\n", + pszClass, cbActualMin, chType, uBuf.DrvPath.DriverNameLength, + uBuf.DrvPath.DriverNameLength / sizeof(WCHAR), uBuf.DrvPath.DriverName); + break; + + case FileFsSectorSizeInformation: + if (g_uVerbosity > 1) + RTTestIPrintf(RTTESTLVL_ALWAYS, "%+34s/%#04x/%c: Flags=%#x log=%#x atomic=%#x perf=%#x eff=%#x offSec=%#x offPart=%#x\n", + pszClass, cbActualMin, chType, uBuf.SectorSize.Flags, + uBuf.SectorSize.LogicalBytesPerSector, + uBuf.SectorSize.PhysicalBytesPerSectorForAtomicity, + uBuf.SectorSize.PhysicalBytesPerSectorForPerformance, + uBuf.SectorSize.FileSystemEffectivePhysicalBytesPerSectorForAtomicity, + uBuf.SectorSize.ByteOffsetForSectorAlignment, + uBuf.SectorSize.ByteOffsetForPartitionAlignment); + break; + + default: + if (g_uVerbosity > 2) + RTTestIPrintf(RTTESTLVL_ALWAYS, "%+34s/%#04x/%c:\n", pszClass, cbActualMin, chType); + break; + } + ULONG const cbName = pcbName ? *pcbName : 0; + uint8_t abNameCopy[4096]; + RT_ZERO(abNameCopy); + if (pcbName) + memcpy(abNameCopy, &uBuf.ab[offName], cbName); + + ULONG const cbMin = g_aNtQueryVolInfoFileClasses[i].cbMin; + ULONG const cbMax = RT_MIN(cbActualMin + 64, sizeof(uBuf)); + for (cbBuf = 0; cbBuf < cbMax; cbBuf++) + { + memset(&uBuf, 0xfe, sizeof(uBuf)); + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + rcNt = NtQueryVolumeInformationFile(hNtFile1, &Ios, &uBuf, cbBuf, enmClass); + if (!ASMMemIsAllU8(&uBuf.ab[cbBuf], sizeof(uBuf) - cbBuf, 0xfe)) + RTTestIFailed("%s/%#x/%c: Touched memory beyond end of buffer (rcNt=%#x)", pszClass, cbBuf, chType, rcNt); + if (cbBuf < cbMin) + { + if (rcNt != STATUS_INFO_LENGTH_MISMATCH) + RTTestIFailed("%s/%#x/%c: %#x, expected STATUS_INFO_LENGTH_MISMATCH", pszClass, cbBuf, chType, rcNt); + if (Ios.Status != VirginIos.Status || Ios.Information != VirginIos.Information) + RTTestIFailed("%s/%#x/%c: I/O status block was modified (STATUS_INFO_LENGTH_MISMATCH): %#x %#zx", + pszClass, cbBuf, chType, Ios.Status, Ios.Information); + } + else if (cbBuf < cbActualMin) + { + if (rcNt != STATUS_BUFFER_OVERFLOW) + RTTestIFailed("%s/%#x/%c: %#x, expected STATUS_BUFFER_OVERFLOW", pszClass, cbBuf, chType, rcNt); + if (pcbName) + { + size_t const cbNameAlt = offName < cbBuf ? cbBuf - offName : 0; + if ( *pcbName != cbName + && !( *pcbName == cbNameAlt + && (enmClass == FileFsAttributeInformation /*NTFS,FAT*/))) + RTTestIFailed("%s/%#x/%c: Wrong name length: %#x, expected %#x (or %#x)", + pszClass, cbBuf, chType, *pcbName, cbName, cbNameAlt); + if (memcmp(abNameCopy, &uBuf.ab[offName], cbNameAlt) != 0) + RTTestIFailed("%s/%#x/%c: Wrong partial name: %.*Rhxs", + pszClass, cbBuf, chType, cbNameAlt, &uBuf.ab[offName]); + } + if (Ios.Information != cbBuf) + RTTestIFailed("%s/%#x/%c: Ios.Information = %#x, expected %#x", + pszClass, cbBuf, chType, Ios.Information, cbBuf); + } + else + { + if ( !ASMMemIsAllU8(&uBuf.ab[cbActualMin], sizeof(uBuf) - cbActualMin, 0xfe) + && enmClass != FileStorageReserveIdInformation /* NTFS bug? */ ) + RTTestIFailed("%s/%#x/%c: Touched memory beyond returned length (cbActualMin=%#x, rcNt=%#x)", + pszClass, cbBuf, chType, cbActualMin, rcNt); + if (pcbName && *pcbName != cbName) + RTTestIFailed("%s/%#x/%c: Wrong name length: %#x, expected %#x", + pszClass, cbBuf, chType, *pcbName, cbName); + if (pcbName && memcmp(abNameCopy, &uBuf.ab[offName], cbName) != 0) + RTTestIFailed("%s/%#x/%c: Wrong name: %.*Rhxs", + pszClass, cbBuf, chType, cbName, &uBuf.ab[offName]); + } + } + } + } + else + { + if (!g_aNtQueryVolInfoFileClasses[i].fQuery) + { + if (rcNt != STATUS_INVALID_INFO_CLASS) + RTTestIFailed("%s/%#x/%c: %#x, expected STATUS_INVALID_INFO_CLASS", pszClass, cbBuf, chType, rcNt); + } + else if ( rcNt != STATUS_INVALID_INFO_CLASS + && rcNt != STATUS_INVALID_PARAMETER + && !(rcNt == STATUS_ACCESS_DENIED && enmClass == FileFsControlInformation /* RDR2/W10 */) + && !(rcNt == STATUS_OBJECT_NAME_NOT_FOUND && enmClass == FileFsObjectIdInformation /* RDR2/W10 */) + ) + RTTestIFailed("%s/%#x/%c: %#x", pszClass, cbBuf, chType, rcNt); + if ( (Ios.Status != VirginIos.Status || Ios.Information != VirginIos.Information) + && !( Ios.Status == 0 && Ios.Information == 0 + && fType == RTFS_TYPE_DIRECTORY + && ( enmClass == FileFsObjectIdInformation /* RDR2+NTFS on W10 */ + || enmClass == FileFsControlInformation /* RDR2 on W10 */ + || enmClass == FileFsVolumeFlagsInformation /* RDR2+NTFS on W10 */ + || enmClass == FileFsDataCopyInformation /* RDR2 on W10 */ + || enmClass == FileFsMetadataSizeInformation /* RDR2+NTFS on W10 */ + || enmClass == FileFsFullSizeInformationEx /* RDR2 on W10 */ + ) ) + ) + RTTestIFailed("%s/%#x/%c: I/O status block was modified: %#x %#zx (rcNt=%#x)", + pszClass, cbBuf, chType, Ios.Status, Ios.Information, rcNt); + if (!ASMMemIsAllU8(&uBuf, sizeof(uBuf), 0xff)) + RTTestIFailed("%s/%#x/%c: Buffer was touched in failure case!", pszClass, cbBuf, chType); + } + } + RT_NOREF(fType); +} + +void fsPerfNtQueryVolInfoFile(void) +{ + RTTestISub("NtQueryVolumeInformationFile"); + + /* On a regular file: */ + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file2qvif")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE), VINF_SUCCESS); + fsPerfNtQueryVolInfoFileWorker((HANDLE)RTFileToNative(hFile1), RTFS_TYPE_FILE); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + /* On a directory: */ + HANDLE hDir1 = INVALID_HANDLE_VALUE; + RTTESTI_CHECK_RC_RETV(RTNtPathOpenDir(InDir(RT_STR_TUPLE("")), GENERIC_READ | SYNCHRONIZE | FILE_SYNCHRONOUS_IO_NONALERT, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, 0, &hDir1, NULL), VINF_SUCCESS); + fsPerfNtQueryVolInfoFileWorker(hDir1, RTFS_TYPE_DIRECTORY); + RTTESTI_CHECK(CloseHandle(hDir1) == TRUE); + + /* On a regular file opened for reading: */ + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file2qvif")), + RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS); + fsPerfNtQueryVolInfoFileWorker((HANDLE)RTFileToNative(hFile1), RTFS_TYPE_FILE); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); +} + +#endif /* RT_OS_WINDOWS */ + +void fsPerfFChMod(void) +{ + RTTestISub("fchmod"); + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file4")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTFSOBJINFO ObjInfo = {0}; + RTTESTI_CHECK_RC(RTFileQueryInfo(hFile1, &ObjInfo, RTFSOBJATTRADD_NOTHING), VINF_SUCCESS); + RTFMODE const fEvenMode = (ObjInfo.Attr.fMode & ~RTFS_UNIX_ALL_ACCESS_PERMS) | RTFS_DOS_READONLY | 0400; + RTFMODE const fOddMode = (ObjInfo.Attr.fMode & ~(RTFS_UNIX_ALL_ACCESS_PERMS | RTFS_DOS_READONLY)) | 0640; + PROFILE_FN(RTFileSetMode(hFile1, iIteration & 1 ? fOddMode : fEvenMode), g_nsTestRun, "RTFileSetMode"); + + RTFileSetMode(hFile1, ObjInfo.Attr.fMode); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); +} + + +void fsPerfFUtimes(void) +{ + RTTestISub("futimes"); + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file5")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTIMESPEC Time1; + RTTimeNow(&Time1); + RTTIMESPEC Time2 = Time1; + RTTimeSpecSubSeconds(&Time2, 3636); + + RTFSOBJINFO ObjInfo0 = {0}; + RTTESTI_CHECK_RC(RTFileQueryInfo(hFile1, &ObjInfo0, RTFSOBJATTRADD_NOTHING), VINF_SUCCESS); + + /* Modify modification time: */ + RTTESTI_CHECK_RC(RTFileSetTimes(hFile1, NULL, &Time2, NULL, NULL), VINF_SUCCESS); + RTFSOBJINFO ObjInfo1 = {0}; + RTTESTI_CHECK_RC(RTFileQueryInfo(hFile1, &ObjInfo1, RTFSOBJATTRADD_NOTHING), VINF_SUCCESS); + RTTESTI_CHECK((RTTimeSpecGetSeconds(&ObjInfo1.ModificationTime) >> 2) == (RTTimeSpecGetSeconds(&Time2) >> 2)); + char sz1[RTTIME_STR_LEN], sz2[RTTIME_STR_LEN]; /* Div by 1000 here for posix impl. using timeval. */ + RTTESTI_CHECK_MSG(RTTimeSpecGetNano(&ObjInfo1.AccessTime) / 1000 == RTTimeSpecGetNano(&ObjInfo0.AccessTime) / 1000, + ("%s, expected %s", RTTimeSpecToString(&ObjInfo1.AccessTime, sz1, sizeof(sz1)), + RTTimeSpecToString(&ObjInfo0.AccessTime, sz2, sizeof(sz2)))); + + /* Modify access time: */ + RTTESTI_CHECK_RC(RTFileSetTimes(hFile1, &Time1, NULL, NULL, NULL), VINF_SUCCESS); + RTFSOBJINFO ObjInfo2 = {0}; + RTTESTI_CHECK_RC(RTFileQueryInfo(hFile1, &ObjInfo2, RTFSOBJATTRADD_NOTHING), VINF_SUCCESS); + RTTESTI_CHECK((RTTimeSpecGetSeconds(&ObjInfo2.AccessTime) >> 2) == (RTTimeSpecGetSeconds(&Time1) >> 2)); + RTTESTI_CHECK(RTTimeSpecGetNano(&ObjInfo2.ModificationTime) / 1000 == RTTimeSpecGetNano(&ObjInfo1.ModificationTime) / 1000); + + /* Benchmark it: */ + PROFILE_FN(RTFileSetTimes(hFile1, NULL, iIteration & 1 ? &Time1 : &Time2, NULL, NULL), g_nsTestRun, "RTFileSetTimes"); + + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); +} + + +void fsPerfStat(void) +{ + RTTestISub("stat"); + RTFSOBJINFO ObjInfo; + + /* Non-existing files. */ + RTTESTI_CHECK_RC(RTPathQueryInfoEx(InEmptyDir(RT_STR_TUPLE("no-such-file")), + &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VERR_FILE_NOT_FOUND); + RTTESTI_CHECK_RC(RTPathQueryInfoEx(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")), + &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTPathQueryInfoEx(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")), + &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VERR_PATH_NOT_FOUND); + + /* Shallow: */ + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file3")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + PROFILE_FN(RTPathQueryInfoEx(g_szDir, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), g_nsTestRun, + "RTPathQueryInfoEx/NOTHING"); + PROFILE_FN(RTPathQueryInfoEx(g_szDir, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK), g_nsTestRun, + "RTPathQueryInfoEx/UNIX"); + + + /* Deep: */ + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file3")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + PROFILE_FN(RTPathQueryInfoEx(g_szDeepDir, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), g_nsTestRun, + "RTPathQueryInfoEx/deep/NOTHING"); + PROFILE_FN(RTPathQueryInfoEx(g_szDeepDir, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK), g_nsTestRun, + "RTPathQueryInfoEx/deep/UNIX"); + + /* Manytree: */ + char szPath[FSPERF_MAX_PATH]; + PROFILE_MANYTREE_FN(szPath, RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), + 1, g_nsTestRun, "RTPathQueryInfoEx/manytree/NOTHING"); + PROFILE_MANYTREE_FN(szPath, RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK), + 1, g_nsTestRun, "RTPathQueryInfoEx/manytree/UNIX"); +} + + +void fsPerfChmod(void) +{ + RTTestISub("chmod"); + + /* Non-existing files. */ + RTTESTI_CHECK_RC(RTPathSetMode(InEmptyDir(RT_STR_TUPLE("no-such-file")), 0665), + VERR_FILE_NOT_FOUND); + RTTESTI_CHECK_RC(RTPathSetMode(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")), 0665), + FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTPathSetMode(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")), 0665), VERR_PATH_NOT_FOUND); + + /* Shallow: */ + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file14")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + RTFSOBJINFO ObjInfo; + RTTESTI_CHECK_RC(RTPathQueryInfoEx(g_szDir, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VINF_SUCCESS); + RTFMODE const fEvenMode = (ObjInfo.Attr.fMode & ~RTFS_UNIX_ALL_ACCESS_PERMS) | RTFS_DOS_READONLY | 0400; + RTFMODE const fOddMode = (ObjInfo.Attr.fMode & ~(RTFS_UNIX_ALL_ACCESS_PERMS | RTFS_DOS_READONLY)) | 0640; + PROFILE_FN(RTPathSetMode(g_szDir, iIteration & 1 ? fOddMode : fEvenMode), g_nsTestRun, "RTPathSetMode"); + RTPathSetMode(g_szDir, ObjInfo.Attr.fMode); + + /* Deep: */ + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file14")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + PROFILE_FN(RTPathSetMode(g_szDeepDir, iIteration & 1 ? fOddMode : fEvenMode), g_nsTestRun, "RTPathSetMode/deep"); + RTPathSetMode(g_szDeepDir, ObjInfo.Attr.fMode); + + /* Manytree: */ + char szPath[FSPERF_MAX_PATH]; + PROFILE_MANYTREE_FN(szPath, RTPathSetMode(szPath, iIteration & 1 ? fOddMode : fEvenMode), 1, g_nsTestRun, + "RTPathSetMode/manytree"); + DO_MANYTREE_FN(szPath, RTPathSetMode(szPath, ObjInfo.Attr.fMode)); +} + + +void fsPerfUtimes(void) +{ + RTTestISub("utimes"); + + RTTIMESPEC Time1; + RTTimeNow(&Time1); + RTTIMESPEC Time2 = Time1; + RTTimeSpecSubSeconds(&Time2, 3636); + + /* Non-existing files. */ + RTTESTI_CHECK_RC(RTPathSetTimesEx(InEmptyDir(RT_STR_TUPLE("no-such-file")), NULL, &Time1, NULL, NULL, RTPATH_F_ON_LINK), + VERR_FILE_NOT_FOUND); + RTTESTI_CHECK_RC(RTPathSetTimesEx(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")), + NULL, &Time1, NULL, NULL, RTPATH_F_ON_LINK), + FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTPathSetTimesEx(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")), + NULL, &Time1, NULL, NULL, RTPATH_F_ON_LINK), + VERR_PATH_NOT_FOUND); + + /* Shallow: */ + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file15")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + RTFSOBJINFO ObjInfo0 = {0}; + RTTESTI_CHECK_RC(RTPathQueryInfoEx(g_szDir, &ObjInfo0, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VINF_SUCCESS); + + /* Modify modification time: */ + RTTESTI_CHECK_RC(RTPathSetTimesEx(g_szDir, NULL, &Time2, NULL, NULL, RTPATH_F_ON_LINK), VINF_SUCCESS); + RTFSOBJINFO ObjInfo1; + RTTESTI_CHECK_RC(RTPathQueryInfoEx(g_szDir, &ObjInfo1, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VINF_SUCCESS); + RTTESTI_CHECK((RTTimeSpecGetSeconds(&ObjInfo1.ModificationTime) >> 2) == (RTTimeSpecGetSeconds(&Time2) >> 2)); + RTTESTI_CHECK(RTTimeSpecGetNano(&ObjInfo1.AccessTime) / 1000 == RTTimeSpecGetNano(&ObjInfo0.AccessTime) / 1000 /* posix timeval */); + + /* Modify access time: */ + RTTESTI_CHECK_RC(RTPathSetTimesEx(g_szDir, &Time1, NULL, NULL, NULL, RTPATH_F_ON_LINK), VINF_SUCCESS); + RTFSOBJINFO ObjInfo2 = {0}; + RTTESTI_CHECK_RC(RTPathQueryInfoEx(g_szDir, &ObjInfo2, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK), VINF_SUCCESS); + RTTESTI_CHECK((RTTimeSpecGetSeconds(&ObjInfo2.AccessTime) >> 2) == (RTTimeSpecGetSeconds(&Time1) >> 2)); + RTTESTI_CHECK(RTTimeSpecGetNano(&ObjInfo2.ModificationTime) / 1000 == RTTimeSpecGetNano(&ObjInfo1.ModificationTime) / 1000 /* posix timeval */); + + /* Profile shallow: */ + PROFILE_FN(RTPathSetTimesEx(g_szDir, iIteration & 1 ? &Time1 : &Time2, iIteration & 1 ? &Time2 : &Time1, + NULL, NULL, RTPATH_F_ON_LINK), + g_nsTestRun, "RTPathSetTimesEx"); + + /* Deep: */ + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file15")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + PROFILE_FN(RTPathSetTimesEx(g_szDeepDir, iIteration & 1 ? &Time1 : &Time2, iIteration & 1 ? &Time2 : &Time1, + NULL, NULL, RTPATH_F_ON_LINK), + g_nsTestRun, "RTPathSetTimesEx/deep"); + + /* Manytree: */ + char szPath[FSPERF_MAX_PATH]; + PROFILE_MANYTREE_FN(szPath, RTPathSetTimesEx(szPath, iIteration & 1 ? &Time1 : &Time2, iIteration & 1 ? &Time2 : &Time1, + NULL, NULL, RTPATH_F_ON_LINK), + 1, g_nsTestRun, "RTPathSetTimesEx/manytree"); +} + + +DECL_FORCE_INLINE(int) fsPerfRenameMany(const char *pszFile, uint32_t iIteration) +{ + char szRenamed[FSPERF_MAX_PATH]; + strcat(strcpy(szRenamed, pszFile), "-renamed"); + if (!(iIteration & 1)) + return RTPathRename(pszFile, szRenamed, 0); + return RTPathRename(szRenamed, pszFile, 0); +} + + +void fsPerfRename(void) +{ + RTTestISub("rename"); + char szPath[FSPERF_MAX_PATH]; + +/** @todo rename directories too! */ +/** @todo check overwriting files and directoris (empty ones should work on + * unix). */ + + /* Non-existing files. */ + strcpy(szPath, InEmptyDir(RT_STR_TUPLE("other-no-such-file"))); + RTTESTI_CHECK_RC(RTPathRename(InEmptyDir(RT_STR_TUPLE("no-such-file")), szPath, 0), VERR_FILE_NOT_FOUND); + strcpy(szPath, InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "other-no-such-file"))); + RTTESTI_CHECK_RC(RTPathRename(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")), szPath, 0), + FSPERF_VERR_PATH_NOT_FOUND); + strcpy(szPath, InEmptyDir(RT_STR_TUPLE("other-no-such-file"))); + RTTESTI_CHECK_RC(RTPathRename(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")), szPath, 0), VERR_PATH_NOT_FOUND); + + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file16")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + strcat(strcpy(szPath, g_szDir), "-no-such-dir" RTPATH_SLASH_STR "file16"); + RTTESTI_CHECK_RC(RTPathRename(szPath, g_szDir, 0), FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTPathRename(g_szDir, szPath, 0), FSPERF_VERR_PATH_NOT_FOUND); + + /* Shallow: */ + strcat(strcpy(szPath, g_szDir), "-other"); + PROFILE_FN(RTPathRename(iIteration & 1 ? szPath : g_szDir, iIteration & 1 ? g_szDir : szPath, 0), g_nsTestRun, "RTPathRename"); + + /* Deep: */ + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDeepDir(RT_STR_TUPLE("file15")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + strcat(strcpy(szPath, g_szDeepDir), "-other"); + PROFILE_FN(RTPathRename(iIteration & 1 ? szPath : g_szDeepDir, iIteration & 1 ? g_szDeepDir : szPath, 0), + g_nsTestRun, "RTPathRename/deep"); + + /* Manytree: */ + PROFILE_MANYTREE_FN(szPath, fsPerfRenameMany(szPath, iIteration), 2, g_nsTestRun, "RTPathRename/manytree"); +} + + +/** + * Wrapper around RTDirOpen/RTDirOpenFiltered which takes g_fRelativeDir into + * account. + */ +DECL_FORCE_INLINE(int) fsPerfOpenDirWrap(PRTDIR phDir, const char *pszPath) +{ + if (!g_fRelativeDir) + return RTDirOpen(phDir, pszPath); + return RTDirOpenFiltered(phDir, pszPath, RTDIRFILTER_NONE, RTDIR_F_NO_ABS_PATH); +} + + +DECL_FORCE_INLINE(int) fsPerfOpenClose(const char *pszDir) +{ + RTDIR hDir; + RTTESTI_CHECK_RC_RET(fsPerfOpenDirWrap(&hDir, pszDir), VINF_SUCCESS, rcCheck); + RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS); + return VINF_SUCCESS; +} + + +void vsPerfDirOpen(void) +{ + RTTestISub("dir open"); + RTDIR hDir; + + /* + * Non-existing files. + */ + RTTESTI_CHECK_RC(fsPerfOpenDirWrap(&hDir, InEmptyDir(RT_STR_TUPLE("no-such-file"))), VERR_FILE_NOT_FOUND); + RTTESTI_CHECK_RC(fsPerfOpenDirWrap(&hDir, InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file"))), FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(fsPerfOpenDirWrap(&hDir, InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file"))), VERR_PATH_NOT_FOUND); + + /* + * Check that open + close works. + */ + g_szEmptyDir[g_cchEmptyDir] = '\0'; + RTTESTI_CHECK_RC_RETV(fsPerfOpenDirWrap(&hDir, g_szEmptyDir), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS); + + + /* + * Profile empty dir and dir with many files. + */ + g_szEmptyDir[g_cchEmptyDir] = '\0'; + PROFILE_FN(fsPerfOpenClose(g_szEmptyDir), g_nsTestRun, "RTDirOpen/Close empty"); + if (g_fManyFiles) + { + InDir(RT_STR_TUPLE("manyfiles")); + PROFILE_FN(fsPerfOpenClose(g_szDir), g_nsTestRun, "RTDirOpen/Close manyfiles"); + } +} + + +DECL_FORCE_INLINE(int) fsPerfEnumEmpty(void) +{ + RTDIR hDir; + g_szEmptyDir[g_cchEmptyDir] = '\0'; + RTTESTI_CHECK_RC_RET(fsPerfOpenDirWrap(&hDir, g_szEmptyDir), VINF_SUCCESS, rcCheck); + + RTDIRENTRY Entry; + RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VERR_NO_MORE_FILES); + + RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS); + return VINF_SUCCESS; +} + + +DECL_FORCE_INLINE(int) fsPerfEnumManyFiles(void) +{ + RTDIR hDir; + RTTESTI_CHECK_RC_RET(fsPerfOpenDirWrap(&hDir, InDir(RT_STR_TUPLE("manyfiles"))), VINF_SUCCESS, rcCheck); + uint32_t cLeft = g_cManyFiles + 2; + for (;;) + { + RTDIRENTRY Entry; + if (cLeft > 0) + RTTESTI_CHECK_RC_BREAK(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS); + else + { + RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VERR_NO_MORE_FILES); + break; + } + cLeft--; + } + RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS); + return VINF_SUCCESS; +} + + +void vsPerfDirEnum(void) +{ + RTTestISub("dir enum"); + RTDIR hDir; + + /* + * The empty directory. + */ + g_szEmptyDir[g_cchEmptyDir] = '\0'; + RTTESTI_CHECK_RC_RETV(fsPerfOpenDirWrap(&hDir, g_szEmptyDir), VINF_SUCCESS); + + uint32_t fDots = 0; + RTDIRENTRY Entry; + RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS); + RTTESTI_CHECK(RTDirEntryIsStdDotLink(&Entry)); + fDots |= RT_BIT_32(Entry.cbName - 1); + + RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VINF_SUCCESS); + RTTESTI_CHECK(RTDirEntryIsStdDotLink(&Entry)); + fDots |= RT_BIT_32(Entry.cbName - 1); + RTTESTI_CHECK(fDots == 3); + + RTTESTI_CHECK_RC(RTDirRead(hDir, &Entry, NULL), VERR_NO_MORE_FILES); + + RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS); + + /* + * The directory with many files in it. + */ + if (g_fManyFiles) + { + fDots = 0; + uint32_t const cBitmap = RT_ALIGN_32(g_cManyFiles, 64); + void *pvBitmap = alloca(cBitmap / 8); + RT_BZERO(pvBitmap, cBitmap / 8); + for (uint32_t i = g_cManyFiles; i < cBitmap; i++) + ASMBitSet(pvBitmap, i); + + uint32_t cFiles = 0; + RTTESTI_CHECK_RC_RETV(fsPerfOpenDirWrap(&hDir, InDir(RT_STR_TUPLE("manyfiles"))), VINF_SUCCESS); + for (;;) + { + int rc = RTDirRead(hDir, &Entry, NULL); + if (rc == VINF_SUCCESS) + { + if (Entry.szName[0] == '.') + { + if (Entry.szName[1] == '.') + { + RTTESTI_CHECK(!(fDots & 2)); + fDots |= 2; + } + else + { + RTTESTI_CHECK(Entry.szName[1] == '\0'); + RTTESTI_CHECK(!(fDots & 1)); + fDots |= 1; + } + } + else + { + uint32_t iFile = UINT32_MAX; + RTTESTI_CHECK_RC(RTStrToUInt32Full(Entry.szName, 10, &iFile), VINF_SUCCESS); + if ( iFile < g_cManyFiles + && !ASMBitTest(pvBitmap, iFile)) + { + ASMBitSet(pvBitmap, iFile); + cFiles++; + } + else + RTTestFailed(g_hTest, "line %u: iFile=%u g_cManyFiles=%u\n", __LINE__, iFile, g_cManyFiles); + } + } + else if (rc == VERR_NO_MORE_FILES) + break; + else + { + RTTestFailed(g_hTest, "RTDirRead failed enumerating manyfiles: %Rrc\n", rc); + RTDirClose(hDir); + return; + } + } + RTTESTI_CHECK_RC(RTDirClose(hDir), VINF_SUCCESS); + RTTESTI_CHECK(fDots == 3); + RTTESTI_CHECK(cFiles == g_cManyFiles); + RTTESTI_CHECK(ASMMemIsAllU8(pvBitmap, cBitmap / 8, 0xff)); + } + + /* + * Profile. + */ + PROFILE_FN(fsPerfEnumEmpty(),g_nsTestRun, "RTDirOpen/Read/Close empty"); + if (g_fManyFiles) + PROFILE_FN(fsPerfEnumManyFiles(), g_nsTestRun, "RTDirOpen/Read/Close manyfiles"); +} + + +void fsPerfMkRmDir(void) +{ + RTTestISub("mkdir/rmdir"); + + /* Non-existing directories: */ + RTTESTI_CHECK_RC(RTDirRemove(InEmptyDir(RT_STR_TUPLE("no-such-dir"))), VERR_FILE_NOT_FOUND); + RTTESTI_CHECK_RC(RTDirRemove(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR))), VERR_FILE_NOT_FOUND); + RTTESTI_CHECK_RC(RTDirRemove(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file"))), FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTDirRemove(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file" RTPATH_SLASH_STR))), FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file"))), VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file" RTPATH_SLASH_STR))), VERR_PATH_NOT_FOUND); + + RTTESTI_CHECK_RC(RTDirCreate(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")), 0755, 0), FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTDirCreate(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")), 0755, 0), VERR_PATH_NOT_FOUND); + + /* Already existing directories and files: */ + RTTESTI_CHECK_RC(RTDirCreate(InEmptyDir(RT_STR_TUPLE(".")), 0755, 0), VERR_ALREADY_EXISTS); + RTTESTI_CHECK_RC(RTDirCreate(InEmptyDir(RT_STR_TUPLE("..")), 0755, 0), VERR_ALREADY_EXISTS); + + RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE("known-file"))), VERR_NOT_A_DIRECTORY); + RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR))), VERR_NOT_A_DIRECTORY); + + /* Remove directory with subdirectories: */ +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE("."))), VERR_DIR_NOT_EMPTY); +#else + RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE("."))), VERR_INVALID_PARAMETER); /* EINVAL for '.' */ +#endif +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + int rc = RTDirRemove(InDir(RT_STR_TUPLE(".."))); +# ifdef RT_OS_WINDOWS + if (rc != VERR_DIR_NOT_EMPTY /*ntfs root*/ && rc != VERR_SHARING_VIOLATION /*ntfs weird*/ && rc != VERR_ACCESS_DENIED /*fat32 root*/) + RTTestIFailed("RTDirRemove(%s) -> %Rrc, expected VERR_DIR_NOT_EMPTY, VERR_SHARING_VIOLATION or VERR_ACCESS_DENIED", g_szDir, rc); +# else + if (rc != VERR_DIR_NOT_EMPTY && rc != VERR_RESOURCE_BUSY /*IPRT/kLIBC fun*/) + RTTestIFailed("RTDirRemove(%s) -> %Rrc, expected VERR_DIR_NOT_EMPTY or VERR_RESOURCE_BUSY", g_szDir, rc); + + APIRET orc; + RTTESTI_CHECK_MSG((orc = DosDelete((PCSZ)InEmptyDir(RT_STR_TUPLE(".")))) == ERROR_ACCESS_DENIED, + ("DosDelete(%s) -> %u, expected %u\n", g_szEmptyDir, orc, ERROR_ACCESS_DENIED)); + RTTESTI_CHECK_MSG((orc = DosDelete((PCSZ)InEmptyDir(RT_STR_TUPLE("..")))) == ERROR_ACCESS_DENIED, + ("DosDelete(%s) -> %u, expected %u\n", g_szEmptyDir, orc, ERROR_ACCESS_DENIED)); + RTTESTI_CHECK_MSG((orc = DosDelete((PCSZ)InEmptyDir(RT_STR_TUPLE("")))) == ERROR_PATH_NOT_FOUND, /* a little weird (fsrouter) */ + ("DosDelete(%s) -> %u, expected %u\n", g_szEmptyDir, orc, ERROR_PATH_NOT_FOUND)); + +# endif +#else + RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE(".."))), VERR_DIR_NOT_EMPTY); +#endif + RTTESTI_CHECK_RC(RTDirRemove(InDir(RT_STR_TUPLE(""))), VERR_DIR_NOT_EMPTY); + + /* Create a directory and remove it: */ + RTTESTI_CHECK_RC(RTDirCreate(InDir(RT_STR_TUPLE("subdir-1")), 0755, 0), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTDirRemove(g_szDir), VINF_SUCCESS); + + /* Create a file and try remove it or create a directory with the same name: */ + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file18")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTDirRemove(g_szDir), VERR_NOT_A_DIRECTORY); + RTTESTI_CHECK_RC(RTDirCreate(g_szDir, 0755, 0), VERR_ALREADY_EXISTS); + RTTESTI_CHECK_RC(RTDirCreate(InDir(RT_STR_TUPLE("file18" RTPATH_SLASH_STR "subdir")), 0755, 0), VERR_PATH_NOT_FOUND); + + /* + * Profile alternately creating and removing a bunch of directories. + */ + RTTESTI_CHECK_RC_RETV(RTDirCreate(InDir(RT_STR_TUPLE("subdir-2")), 0755, 0), VINF_SUCCESS); + size_t cchDir = strlen(g_szDir); + g_szDir[cchDir++] = RTPATH_SLASH; + g_szDir[cchDir++] = 's'; + + uint32_t cCreated = 0; + uint64_t nsCreate = 0; + uint64_t nsRemove = 0; + for (;;) + { + /* Create a bunch: */ + uint64_t nsStart = RTTimeNanoTS(); + for (uint32_t i = 0; i < 998; i++) + { + RTStrFormatU32(&g_szDir[cchDir], sizeof(g_szDir) - cchDir, i, 10, 3, 3, RTSTR_F_ZEROPAD); + RTTESTI_CHECK_RC_RETV(RTDirCreate(g_szDir, 0755, 0), VINF_SUCCESS); + } + nsCreate += RTTimeNanoTS() - nsStart; + cCreated += 998; + + /* Remove the bunch: */ + nsStart = RTTimeNanoTS(); + for (uint32_t i = 0; i < 998; i++) + { + RTStrFormatU32(&g_szDir[cchDir], sizeof(g_szDir) - cchDir, i, 10, 3, 3, RTSTR_F_ZEROPAD); + RTTESTI_CHECK_RC_RETV(RTDirRemove(g_szDir), VINF_SUCCESS); + } + nsRemove = RTTimeNanoTS() - nsStart; + + /* Check if we got time for another round: */ + if ( ( nsRemove >= g_nsTestRun + && nsCreate >= g_nsTestRun) + || nsCreate + nsRemove >= g_nsTestRun * 3) + break; + } + RTTestIValue("RTDirCreate", nsCreate / cCreated, RTTESTUNIT_NS_PER_OCCURRENCE); + RTTestIValue("RTDirRemove", nsRemove / cCreated, RTTESTUNIT_NS_PER_OCCURRENCE); +} + + +void fsPerfStatVfs(void) +{ + RTTestISub("statvfs"); + + g_szEmptyDir[g_cchEmptyDir] = '\0'; + RTFOFF cbTotal; + RTFOFF cbFree; + uint32_t cbBlock; + uint32_t cbSector; + RTTESTI_CHECK_RC(RTFsQuerySizes(g_szEmptyDir, &cbTotal, &cbFree, &cbBlock, &cbSector), VINF_SUCCESS); + + uint32_t uSerial; + RTTESTI_CHECK_RC(RTFsQuerySerial(g_szEmptyDir, &uSerial), VINF_SUCCESS); + + RTFSPROPERTIES Props; + RTTESTI_CHECK_RC(RTFsQueryProperties(g_szEmptyDir, &Props), VINF_SUCCESS); + + RTFSTYPE enmType; + RTTESTI_CHECK_RC(RTFsQueryType(g_szEmptyDir, &enmType), VINF_SUCCESS); + + g_szDeepDir[g_cchDeepDir] = '\0'; + PROFILE_FN(RTFsQuerySizes(g_szEmptyDir, &cbTotal, &cbFree, &cbBlock, &cbSector), g_nsTestRun, "RTFsQuerySize/empty"); + PROFILE_FN(RTFsQuerySizes(g_szDeepDir, &cbTotal, &cbFree, &cbBlock, &cbSector), g_nsTestRun, "RTFsQuerySize/deep"); +} + + +void fsPerfRm(void) +{ + RTTestISub("rm"); + + /* Non-existing files. */ + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("no-such-file"))), VERR_FILE_NOT_FOUND); + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("no-such-file" RTPATH_SLASH_STR))), VERR_FILE_NOT_FOUND); + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file"))), FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file" RTPATH_SLASH_STR))), FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTFileDelete(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file"))), VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTFileDelete(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file" RTPATH_SLASH_STR))), VERR_PATH_NOT_FOUND); + + /* Existing file but specified as if it was a directory: */ +#if defined(RT_OS_WINDOWS) + RTTESTI_CHECK_RC(RTFileDelete(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR ))), VERR_INVALID_NAME); +#else + RTTESTI_CHECK_RC(RTFileDelete(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR))), VERR_PATH_NOT_FOUND); +#endif + + /* Directories: */ +#if defined(RT_OS_WINDOWS) + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("."))), VERR_ACCESS_DENIED); + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(".."))), VERR_ACCESS_DENIED); + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(""))), VERR_ACCESS_DENIED); +#elif defined(RT_OS_DARWIN) /* unlink() on xnu 16.7.0 is behaviour totally werid: */ + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("."))), VERR_INVALID_PARAMETER); + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(".."))), VINF_SUCCESS /*WTF?!?*/); + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(""))), VERR_ACCESS_DENIED); +#elif defined(RT_OS_OS2) /* OS/2 has a busted unlink, it think it should remove directories too. */ + RTTESTI_CHECK_RC(RTFileDelete(InDir(RT_STR_TUPLE("."))), VERR_DIR_NOT_EMPTY); + int rc = RTFileDelete(InDir(RT_STR_TUPLE(".."))); + if (rc != VERR_DIR_NOT_EMPTY && rc != VERR_FILE_NOT_FOUND && rc != VERR_RESOURCE_BUSY) + RTTestIFailed("RTFileDelete(%s) -> %Rrc, expected VERR_DIR_NOT_EMPTY or VERR_FILE_NOT_FOUND or VERR_RESOURCE_BUSY", g_szDir, rc); + RTTESTI_CHECK_RC(RTFileDelete(InDir(RT_STR_TUPLE(""))), VERR_DIR_NOT_EMPTY); + APIRET orc; + RTTESTI_CHECK_MSG((orc = DosDelete((PCSZ)InEmptyDir(RT_STR_TUPLE(".")))) == ERROR_ACCESS_DENIED, + ("DosDelete(%s) -> %u, expected %u\n", g_szEmptyDir, orc, ERROR_ACCESS_DENIED)); + RTTESTI_CHECK_MSG((orc = DosDelete((PCSZ)InEmptyDir(RT_STR_TUPLE("..")))) == ERROR_ACCESS_DENIED, + ("DosDelete(%s) -> %u, expected %u\n", g_szEmptyDir, orc, ERROR_ACCESS_DENIED)); + RTTESTI_CHECK_MSG((orc = DosDelete((PCSZ)InEmptyDir(RT_STR_TUPLE("")))) == ERROR_PATH_NOT_FOUND, + ("DosDelete(%s) -> %u, expected %u\n", g_szEmptyDir, orc, ERROR_PATH_NOT_FOUND)); /* hpfs+jfs; weird. */ + +#else + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE("."))), VERR_IS_A_DIRECTORY); + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(".."))), VERR_IS_A_DIRECTORY); + RTTESTI_CHECK_RC(RTFileDelete(InEmptyDir(RT_STR_TUPLE(""))), VERR_IS_A_DIRECTORY); +#endif + + /* Shallow: */ + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file19")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VERR_FILE_NOT_FOUND); + + if (g_fManyFiles) + { + /* + * Profile the deletion of the manyfiles content. + */ + { + InDir(RT_STR_TUPLE("manyfiles" RTPATH_SLASH_STR)); + size_t const offFilename = strlen(g_szDir); + fsPerfYield(); + uint64_t const nsStart = RTTimeNanoTS(); + for (uint32_t i = 0; i < g_cManyFiles; i++) + { + RTStrFormatU32(&g_szDir[offFilename], sizeof(g_szDir) - offFilename, i, 10, 5, 5, RTSTR_F_ZEROPAD); + RTTESTI_CHECK_RC_RETV(RTFileDelete(g_szDir), VINF_SUCCESS); + } + uint64_t const cNsElapsed = RTTimeNanoTS() - nsStart; + RTTestIValueF(cNsElapsed, RTTESTUNIT_NS, "Deleted %u empty files from a single directory", g_cManyFiles); + RTTestIValueF(cNsElapsed / g_cManyFiles, RTTESTUNIT_NS_PER_OCCURRENCE, "Delete file (single dir)"); + } + + /* + * Ditto for the manytree. + */ + { + char szPath[FSPERF_MAX_PATH]; + uint64_t const nsStart = RTTimeNanoTS(); + DO_MANYTREE_FN(szPath, RTTESTI_CHECK_RC_RETV(RTFileDelete(szPath), VINF_SUCCESS)); + uint64_t const cNsElapsed = RTTimeNanoTS() - nsStart; + RTTestIValueF(cNsElapsed, RTTESTUNIT_NS, "Deleted %u empty files in tree", g_cManyTreeFiles); + RTTestIValueF(cNsElapsed / g_cManyTreeFiles, RTTESTUNIT_NS_PER_OCCURRENCE, "Delete file (tree)"); + } + } +} + + +void fsPerfChSize(void) +{ + RTTestISub("chsize"); + + /* + * We need some free space to perform this test. + */ + g_szDir[g_cchDir] = '\0'; + RTFOFF cbFree = 0; + RTTESTI_CHECK_RC_RETV(RTFsQuerySizes(g_szDir, NULL, &cbFree, NULL, NULL), VINF_SUCCESS); + if (cbFree < _1M) + { + RTTestSkipped(g_hTest, "Insufficent free space: %'RU64 bytes, requires >= 1MB", cbFree); + return; + } + + /* + * Create a file and play around with it's size. + * We let the current file position follow the end position as we make changes. + */ + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file20")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE), VINF_SUCCESS); + uint64_t cbFile = UINT64_MAX; + RTTESTI_CHECK_RC(RTFileQuerySize(hFile1, &cbFile), VINF_SUCCESS); + RTTESTI_CHECK(cbFile == 0); + + uint8_t abBuf[4096]; + static uint64_t const s_acbChanges[] = + { + 1023, 1024, 1024, 1025, 8192, 11111, _1M, _8M, _8M, + _4M, _2M + 1, _1M - 1, 65537, 65536, 32768, 8000, 7999, 7998, 1024, 1, 0 + }; + uint64_t cbOld = 0; + for (unsigned i = 0; i < RT_ELEMENTS(s_acbChanges); i++) + { + uint64_t cbNew = s_acbChanges[i]; + if (cbNew + _64K >= (uint64_t)cbFree) + continue; + + RTTESTI_CHECK_RC(RTFileSetSize(hFile1, cbNew), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileQuerySize(hFile1, &cbFile), VINF_SUCCESS); + RTTESTI_CHECK_MSG(cbFile == cbNew, ("cbFile=%#RX64 cbNew=%#RX64\n", cbFile, cbNew)); + + if (cbNew > cbOld) + { + /* Check that the extension is all zeroed: */ + uint64_t cbLeft = cbNew - cbOld; + while (cbLeft > 0) + { + memset(abBuf, 0xff, sizeof(abBuf)); + size_t cbToRead = sizeof(abBuf); + if (cbToRead > cbLeft) + cbToRead = (size_t)cbLeft; + RTTESTI_CHECK_RC(RTFileRead(hFile1, abBuf, cbToRead, NULL), VINF_SUCCESS); + RTTESTI_CHECK(ASMMemIsZero(abBuf, cbToRead)); + cbLeft -= cbToRead; + } + } + else + { + /* Check that reading fails with EOF because current position is now beyond the end: */ + RTTESTI_CHECK_RC(RTFileRead(hFile1, abBuf, 1, NULL), VERR_EOF); + + /* Keep current position at the end of the file: */ + RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbNew, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + } + cbOld = cbNew; + } + + /* + * Profile just the file setting operation itself, keeping the changes within + * an allocation unit to avoid needing to adjust the actual (host) FS allocation. + * ASSUMES allocation unit >= 512 and power of two. + */ + RTTESTI_CHECK_RC(RTFileSetSize(hFile1, _64K), VINF_SUCCESS); + PROFILE_FN(RTFileSetSize(hFile1, _64K - (iIteration & 255) - 128), g_nsTestRun, "RTFileSetSize/noalloc"); + + RTTESTI_CHECK_RC(RTFileSetSize(hFile1, 0), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VINF_SUCCESS); +} + + +int fsPerfIoPrepFileWorker(RTFILE hFile1, uint64_t cbFile, uint8_t *pbBuf, size_t cbBuf) +{ + /* + * Fill the file with 0xf6 and insert offset markers with 1KB intervals. + */ + RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck); + memset(pbBuf, 0xf6, cbBuf); + uint64_t cbLeft = cbFile; + uint64_t off = 0; + while (cbLeft > 0) + { + Assert(!(off & (_1K - 1))); + Assert(!(cbBuf & (_1K - 1))); + for (size_t offBuf = 0; offBuf < cbBuf; offBuf += _1K, off += _1K) + *(uint64_t *)&pbBuf[offBuf] = off; + + size_t cbToWrite = cbBuf; + if (cbToWrite > cbLeft) + cbToWrite = (size_t)cbLeft; + + RTTESTI_CHECK_RC_RET(RTFileWrite(hFile1, pbBuf, cbToWrite, NULL), VINF_SUCCESS, rcCheck); + cbLeft -= cbToWrite; + } + return VINF_SUCCESS; +} + +int fsPerfIoPrepFile(RTFILE hFile1, uint64_t cbFile, uint8_t **ppbFree) +{ + /* + * Seek to the end - 4K and write the last 4K. + * This should have the effect of filling the whole file with zeros. + */ + RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, cbFile - _4K, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck); + RTTESTI_CHECK_RC_RET(RTFileWrite(hFile1, g_abRTZero4K, _4K, NULL), VINF_SUCCESS, rcCheck); + + /* + * Check that the space we searched across actually is zero filled. + */ + RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck); + size_t cbBuf = RT_MIN(_1M, g_cbMaxBuffer); + uint8_t *pbBuf = *ppbFree = (uint8_t *)RTMemAlloc(cbBuf); + RTTESTI_CHECK_RET(pbBuf != NULL, VERR_NO_MEMORY); + uint64_t cbLeft = cbFile; + while (cbLeft > 0) + { + size_t cbToRead = cbBuf; + if (cbToRead > cbLeft) + cbToRead = (size_t)cbLeft; + pbBuf[cbToRead - 1] = 0xff; + + RTTESTI_CHECK_RC_RET(RTFileRead(hFile1, pbBuf, cbToRead, NULL), VINF_SUCCESS, rcCheck); + RTTESTI_CHECK_RET(ASMMemIsZero(pbBuf, cbToRead), VERR_MISMATCH); + + cbLeft -= cbToRead; + } + + /* + * Fill the file with 0xf6 and insert offset markers with 1KB intervals. + */ + return fsPerfIoPrepFileWorker(hFile1, cbFile, pbBuf, cbBuf); +} + +/** + * Used in relation to the mmap test when in non-default position. + */ +int fsPerfReinitFile(RTFILE hFile1, uint64_t cbFile) +{ + size_t cbBuf = RT_MIN(_1M, g_cbMaxBuffer); + uint8_t *pbBuf = (uint8_t *)RTMemAlloc(cbBuf); + RTTESTI_CHECK_RET(pbBuf != NULL, VERR_NO_MEMORY); + + int rc = fsPerfIoPrepFileWorker(hFile1, cbFile, pbBuf, cbBuf); + + RTMemFree(pbBuf); + return rc; +} + +/** + * Checks the content read from the file fsPerfIoPrepFile() prepared. + */ +bool fsPerfCheckReadBuf(unsigned uLineNo, uint64_t off, uint8_t const *pbBuf, size_t cbBuf, uint8_t bFiller = 0xf6) +{ + uint32_t cMismatches = 0; + size_t offBuf = 0; + uint32_t offBlock = (uint32_t)(off & (_1K - 1)); + while (offBuf < cbBuf) + { + /* + * Check the offset marker: + */ + if (offBlock < sizeof(uint64_t)) + { + RTUINT64U uMarker; + uMarker.u = off + offBuf - offBlock; + unsigned offMarker = offBlock & (sizeof(uint64_t) - 1); + while (offMarker < sizeof(uint64_t) && offBuf < cbBuf) + { + if (uMarker.au8[offMarker] != pbBuf[offBuf]) + { + RTTestIFailed("%u: Mismatch at buffer/file offset %#zx/%#RX64: %#x, expected %#x", + uLineNo, offBuf, off + offBuf, pbBuf[offBuf], uMarker.au8[offMarker]); + if (cMismatches++ > 32) + return false; + } + offMarker++; + offBuf++; + } + offBlock = sizeof(uint64_t); + } + + /* + * Check the filling: + */ + size_t cbFilling = RT_MIN(_1K - offBlock, cbBuf - offBuf); + if ( cbFilling == 0 + || ASMMemIsAllU8(&pbBuf[offBuf], cbFilling, bFiller)) + offBuf += cbFilling; + else + { + /* Some mismatch, locate it/them: */ + while (cbFilling > 0 && offBuf < cbBuf) + { + if (pbBuf[offBuf] != bFiller) + { + RTTestIFailed("%u: Mismatch at buffer/file offset %#zx/%#RX64: %#x, expected %#04x", + uLineNo, offBuf, off + offBuf, pbBuf[offBuf], bFiller); + if (cMismatches++ > 32) + return false; + } + offBuf++; + cbFilling--; + } + } + offBlock = 0; + } + return cMismatches == 0; +} + + +/** + * Sets up write buffer with offset markers and fillers. + */ +void fsPerfFillWriteBuf(uint64_t off, uint8_t *pbBuf, size_t cbBuf, uint8_t bFiller = 0xf6) +{ + uint32_t offBlock = (uint32_t)(off & (_1K - 1)); + while (cbBuf > 0) + { + /* The marker. */ + if (offBlock < sizeof(uint64_t)) + { + RTUINT64U uMarker; + uMarker.u = off + offBlock; + if (cbBuf > sizeof(uMarker) - offBlock) + { + memcpy(pbBuf, &uMarker.au8[offBlock], sizeof(uMarker) - offBlock); + pbBuf += sizeof(uMarker) - offBlock; + cbBuf -= sizeof(uMarker) - offBlock; + off += sizeof(uMarker) - offBlock; + } + else + { + memcpy(pbBuf, &uMarker.au8[offBlock], cbBuf); + return; + } + offBlock = sizeof(uint64_t); + } + + /* Do the filling. */ + size_t cbFilling = RT_MIN(_1K - offBlock, cbBuf); + memset(pbBuf, bFiller, cbFilling); + pbBuf += cbFilling; + cbBuf -= cbFilling; + off += cbFilling; + + offBlock = 0; + } +} + + + +void fsPerfIoSeek(RTFILE hFile1, uint64_t cbFile) +{ + /* + * Do a bunch of search tests, most which are random. + */ + struct + { + int rc; + uint32_t uMethod; + int64_t offSeek; + uint64_t offActual; + + } aSeeks[9 + 64] = + { + { VINF_SUCCESS, RTFILE_SEEK_BEGIN, 0, 0 }, + { VINF_SUCCESS, RTFILE_SEEK_CURRENT, 0, 0 }, + { VINF_SUCCESS, RTFILE_SEEK_END, 0, cbFile }, + { VINF_SUCCESS, RTFILE_SEEK_CURRENT, -4096, cbFile - 4096 }, + { VINF_SUCCESS, RTFILE_SEEK_CURRENT, 4096 - (int64_t)cbFile, 0 }, + { VINF_SUCCESS, RTFILE_SEEK_END, -(int64_t)cbFile/2, cbFile / 2 + (cbFile & 1) }, + { VINF_SUCCESS, RTFILE_SEEK_CURRENT, -(int64_t)cbFile/2, 0 }, +#if defined(RT_OS_WINDOWS) + { VERR_NEGATIVE_SEEK, RTFILE_SEEK_CURRENT, -1, 0 }, +#else + { VERR_INVALID_PARAMETER, RTFILE_SEEK_CURRENT, -1, 0 }, +#endif + { VINF_SUCCESS, RTFILE_SEEK_CURRENT, 0, 0 }, + }; + + uint64_t offActual = 0; + for (unsigned i = 9; i < RT_ELEMENTS(aSeeks); i++) + { + switch (RTRandU32Ex(RTFILE_SEEK_BEGIN, RTFILE_SEEK_END)) + { + default: AssertFailedBreak(); + case RTFILE_SEEK_BEGIN: + aSeeks[i].uMethod = RTFILE_SEEK_BEGIN; + aSeeks[i].rc = VINF_SUCCESS; + aSeeks[i].offSeek = RTRandU64Ex(0, cbFile + cbFile / 8); + aSeeks[i].offActual = offActual = aSeeks[i].offSeek; + break; + + case RTFILE_SEEK_CURRENT: + aSeeks[i].uMethod = RTFILE_SEEK_CURRENT; + aSeeks[i].rc = VINF_SUCCESS; + aSeeks[i].offSeek = (int64_t)RTRandU64Ex(0, cbFile + cbFile / 8) - (int64_t)offActual; + aSeeks[i].offActual = offActual += aSeeks[i].offSeek; + break; + + case RTFILE_SEEK_END: + aSeeks[i].uMethod = RTFILE_SEEK_END; + aSeeks[i].rc = VINF_SUCCESS; + aSeeks[i].offSeek = -(int64_t)RTRandU64Ex(0, cbFile); + aSeeks[i].offActual = offActual = cbFile + aSeeks[i].offSeek; + break; + } + } + + for (unsigned iDoReadCheck = 0; iDoReadCheck < 2; iDoReadCheck++) + { + for (uint32_t i = 0; i < RT_ELEMENTS(aSeeks); i++) + { + offActual = UINT64_MAX; + int rc = RTFileSeek(hFile1, aSeeks[i].offSeek, aSeeks[i].uMethod, &offActual); + if (rc != aSeeks[i].rc) + RTTestIFailed("Seek #%u: Expected %Rrc, got %Rrc", i, aSeeks[i].rc, rc); + if (RT_SUCCESS(rc) && offActual != aSeeks[i].offActual) + RTTestIFailed("Seek #%u: offActual %#RX64, expected %#RX64", i, offActual, aSeeks[i].offActual); + if (RT_SUCCESS(rc)) + { + uint64_t offTell = RTFileTell(hFile1); + if (offTell != offActual) + RTTestIFailed("Seek #%u: offActual %#RX64, RTFileTell %#RX64", i, offActual, offTell); + } + + if (RT_SUCCESS(rc) && offActual + _2K <= cbFile && iDoReadCheck) + { + uint8_t abBuf[_2K]; + RTTESTI_CHECK_RC(rc = RTFileRead(hFile1, abBuf, sizeof(abBuf), NULL), VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + size_t offMarker = (size_t)(RT_ALIGN_64(offActual, _1K) - offActual); + uint64_t uMarker = *(uint64_t *)&abBuf[offMarker]; /** @todo potentially unaligned access */ + if (uMarker != offActual + offMarker) + RTTestIFailed("Seek #%u: Invalid marker value (@ %#RX64): %#RX64, expected %#RX64", + i, offActual, uMarker, offActual + offMarker); + + RTTESTI_CHECK_RC(RTFileSeek(hFile1, -(int64_t)sizeof(abBuf), RTFILE_SEEK_CURRENT, NULL), VINF_SUCCESS); + } + } + } + } + + + /* + * Profile seeking relative to the beginning of the file and relative + * to the end. The latter might be more expensive in a SF context. + */ + PROFILE_FN(RTFileSeek(hFile1, iIteration < cbFile ? iIteration : iIteration % cbFile, RTFILE_SEEK_BEGIN, NULL), + g_nsTestRun, "RTFileSeek/BEGIN"); + PROFILE_FN(RTFileSeek(hFile1, iIteration < cbFile ? -(int64_t)iIteration : -(int64_t)(iIteration % cbFile), RTFILE_SEEK_END, NULL), + g_nsTestRun, "RTFileSeek/END"); + +} + +#ifdef FSPERF_TEST_SENDFILE + +/** + * Send file thread arguments. + */ +typedef struct FSPERFSENDFILEARGS +{ + uint64_t offFile; + size_t cbSend; + uint64_t cbSent; + size_t cbBuf; + uint8_t *pbBuf; + uint8_t bFiller; + bool fCheckBuf; + RTSOCKET hSocket; + uint64_t volatile tsThreadDone; +} FSPERFSENDFILEARGS; + +/** Thread receiving the bytes from a sendfile() call. */ +static DECLCALLBACK(int) fsPerfSendFileThread(RTTHREAD hSelf, void *pvUser) +{ + FSPERFSENDFILEARGS *pArgs = (FSPERFSENDFILEARGS *)pvUser; + int rc = VINF_SUCCESS; + + if (pArgs->fCheckBuf) + RTTestSetDefault(g_hTest, NULL); + + uint64_t cbReceived = 0; + while (cbReceived < pArgs->cbSent) + { + size_t const cbToRead = RT_MIN(pArgs->cbBuf, pArgs->cbSent - cbReceived); + size_t cbActual = 0; + RTTEST_CHECK_RC_BREAK(g_hTest, rc = RTTcpRead(pArgs->hSocket, pArgs->pbBuf, cbToRead, &cbActual), VINF_SUCCESS); + RTTEST_CHECK_BREAK(g_hTest, cbActual != 0); + RTTEST_CHECK(g_hTest, cbActual <= cbToRead); + if (pArgs->fCheckBuf) + fsPerfCheckReadBuf(__LINE__, pArgs->offFile + cbReceived, pArgs->pbBuf, cbActual, pArgs->bFiller); + cbReceived += cbActual; + } + + pArgs->tsThreadDone = RTTimeNanoTS(); + + if (cbReceived == pArgs->cbSent && RT_SUCCESS(rc)) + { + size_t cbActual = 0; + rc = RTSocketReadNB(pArgs->hSocket, pArgs->pbBuf, 1, &cbActual); + if (rc != VINF_SUCCESS && rc != VINF_TRY_AGAIN) + RTTestFailed(g_hTest, "RTSocketReadNB(sendfile client socket) -> %Rrc; expected VINF_SUCCESS or VINF_TRY_AGAIN\n", rc); + else if (cbActual != 0) + RTTestFailed(g_hTest, "sendfile client socket still contains data when done!\n"); + } + + RTTEST_CHECK_RC(g_hTest, RTSocketClose(pArgs->hSocket), VINF_SUCCESS); + pArgs->hSocket = NIL_RTSOCKET; + + RT_NOREF(hSelf); + return rc; +} + + +static uint64_t fsPerfSendFileOne(FSPERFSENDFILEARGS *pArgs, RTFILE hFile1, uint64_t offFile, + size_t cbSend, uint64_t cbSent, uint8_t bFiller, bool fCheckBuf, unsigned iLine) +{ + /* Copy parameters to the argument structure: */ + pArgs->offFile = offFile; + pArgs->cbSend = cbSend; + pArgs->cbSent = cbSent; + pArgs->bFiller = bFiller; + pArgs->fCheckBuf = fCheckBuf; + + /* Create a socket pair. */ + pArgs->hSocket = NIL_RTSOCKET; + RTSOCKET hServer = NIL_RTSOCKET; + RTTESTI_CHECK_RC_RET(RTTcpCreatePair(&hServer, &pArgs->hSocket, 0), VINF_SUCCESS, 0); + + /* Create the receiving thread: */ + int rc; + RTTHREAD hThread = NIL_RTTHREAD; + RTTESTI_CHECK_RC(rc = RTThreadCreate(&hThread, fsPerfSendFileThread, pArgs, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "sendfile"), VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + uint64_t const tsStart = RTTimeNanoTS(); + +# if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) + /* SystemV sendfile: */ + loff_t offFileSf = pArgs->offFile; + ssize_t cbActual = sendfile((int)RTSocketToNative(hServer), (int)RTFileToNative(hFile1), &offFileSf, pArgs->cbSend); + int const iErr = errno; + if (cbActual < 0) + RTTestIFailed("%u: sendfile(socket, file, &%#X64, %#zx) failed (%zd): %d (%Rrc), offFileSf=%#RX64\n", + iLine, pArgs->offFile, pArgs->cbSend, cbActual, iErr, RTErrConvertFromErrno(iErr), (uint64_t)offFileSf); + else if ((uint64_t)cbActual != pArgs->cbSent) + RTTestIFailed("%u: sendfile(socket, file, &%#RX64, %#zx): %#zx, expected %#RX64 (offFileSf=%#RX64)\n", + iLine, pArgs->offFile, pArgs->cbSend, cbActual, pArgs->cbSent, (uint64_t)offFileSf); + else if ((uint64_t)offFileSf != pArgs->offFile + pArgs->cbSent) + RTTestIFailed("%u: sendfile(socket, file, &%#RX64, %#zx): %#zx; offFileSf=%#RX64, expected %#RX64\n", + iLine, pArgs->offFile, pArgs->cbSend, cbActual, (uint64_t)offFileSf, pArgs->offFile + pArgs->cbSent); +#else + /* BSD sendfile: */ +# ifdef SF_SYNC + int fSfFlags = SF_SYNC; +# else + int fSfFlags = 0; +# endif + off_t cbActual = pArgs->cbSend; + rc = sendfile((int)RTFileToNative(hFile1), (int)RTSocketToNative(hServer), +# ifdef RT_OS_DARWIN + pArgs->offFile, &cbActual, NULL, fSfFlags); +# else + pArgs->offFile, cbActual, NULL, &cbActual, fSfFlags); +# endif + int const iErr = errno; + if (rc != 0) + RTTestIFailed("%u: sendfile(file, socket, %#RX64, %#zx, NULL,, %#x) failed (%d): %d (%Rrc), cbActual=%#RX64\n", + iLine, pArgs->offFile, (size_t)pArgs->cbSend, rc, iErr, RTErrConvertFromErrno(iErr), (uint64_t)cbActual); + if ((uint64_t)cbActual != pArgs->cbSent) + RTTestIFailed("%u: sendfile(file, socket, %#RX64, %#zx, NULL,, %#x): cbActual=%#RX64, expected %#RX64 (rc=%d, errno=%d)\n", + iLine, pArgs->offFile, (size_t)pArgs->cbSend, (uint64_t)cbActual, pArgs->cbSent, rc, iErr); +# endif + RTTESTI_CHECK_RC(RTSocketClose(hServer), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTThreadWait(hThread, 30 * RT_NS_1SEC, NULL), VINF_SUCCESS); + + if (pArgs->tsThreadDone >= tsStart) + return RT_MAX(pArgs->tsThreadDone - tsStart, 1); + } + return 0; +} + + +static void fsPerfSendFile(RTFILE hFile1, uint64_t cbFile) +{ + RTTestISub("sendfile"); +# ifdef RT_OS_LINUX + uint64_t const cbFileMax = RT_MIN(cbFile, UINT32_MAX - PAGE_OFFSET_MASK); +# else + uint64_t const cbFileMax = RT_MIN(cbFile, SSIZE_MAX - PAGE_OFFSET_MASK); +# endif + signal(SIGPIPE, SIG_IGN); + + /* + * Allocate a buffer. + */ + FSPERFSENDFILEARGS Args; + Args.cbBuf = RT_MIN(RT_MIN(cbFileMax, _16M), g_cbMaxBuffer); + Args.pbBuf = (uint8_t *)RTMemAlloc(Args.cbBuf); + while (!Args.pbBuf) + { + Args.cbBuf /= 8; + RTTESTI_CHECK_RETV(Args.cbBuf >= _64K); + Args.pbBuf = (uint8_t *)RTMemAlloc(Args.cbBuf); + } + + /* + * First iteration with default buffer content. + */ + fsPerfSendFileOne(&Args, hFile1, 0, cbFileMax, cbFileMax, 0xf6, true /*fCheckBuf*/, __LINE__); + if (cbFileMax == cbFile) + fsPerfSendFileOne(&Args, hFile1, 63, cbFileMax, cbFileMax - 63, 0xf6, true /*fCheckBuf*/, __LINE__); + else + fsPerfSendFileOne(&Args, hFile1, 63, cbFileMax - 63, cbFileMax - 63, 0xf6, true /*fCheckBuf*/, __LINE__); + + /* + * Write a block using the regular API and then send it, checking that + * the any caching that sendfile does is correctly updated. + */ + uint8_t bFiller = 0xf6; + size_t cbToSend = RT_MIN(cbFileMax, Args.cbBuf); + do + { + fsPerfSendFileOne(&Args, hFile1, 0, cbToSend, cbToSend, bFiller, true /*fCheckBuf*/, __LINE__); /* prime cache */ + + bFiller += 1; + fsPerfFillWriteBuf(0, Args.pbBuf, cbToSend, bFiller); + RTTESTI_CHECK_RC(RTFileWriteAt(hFile1, 0, Args.pbBuf, cbToSend, NULL), VINF_SUCCESS); + + fsPerfSendFileOne(&Args, hFile1, 0, cbToSend, cbToSend, bFiller, true /*fCheckBuf*/, __LINE__); + + cbToSend /= 2; + } while (cbToSend >= PAGE_SIZE && ((unsigned)bFiller - 0xf7U) < 64); + + /* + * Restore buffer content + */ + bFiller = 0xf6; + fsPerfFillWriteBuf(0, Args.pbBuf, Args.cbBuf, bFiller); + RTTESTI_CHECK_RC(RTFileWriteAt(hFile1, 0, Args.pbBuf, Args.cbBuf, NULL), VINF_SUCCESS); + + /* + * Do 128 random sends. + */ + uint64_t const cbSmall = RT_MIN(_256K, cbFileMax / 16); + for (uint32_t iTest = 0; iTest < 128; iTest++) + { + cbToSend = (size_t)RTRandU64Ex(1, iTest < 64 ? cbSmall : cbFileMax); + uint64_t const offToSendFrom = RTRandU64Ex(0, cbFile - 1); + uint64_t const cbSent = offToSendFrom + cbToSend <= cbFile ? cbToSend : cbFile - offToSendFrom; + + fsPerfSendFileOne(&Args, hFile1, offToSendFrom, cbToSend, cbSent, bFiller, true /*fCheckBuf*/, __LINE__); + } + + /* + * Benchmark it. + */ + uint32_t cIterations = 0; + uint64_t nsElapsed = 0; + for (;;) + { + uint64_t cNsThis = fsPerfSendFileOne(&Args, hFile1, 0, cbFileMax, cbFileMax, 0xf6, false /*fCheckBuf*/, __LINE__); + nsElapsed += cNsThis; + cIterations++; + if (!cNsThis || nsElapsed >= g_nsTestRun) + break; + } + uint64_t cbTotal = cbFileMax * cIterations; + RTTestIValue("latency", nsElapsed / cIterations, RTTESTUNIT_NS_PER_CALL); + RTTestIValue("throughput", (uint64_t)(cbTotal / ((double)nsElapsed / RT_NS_1SEC)), RTTESTUNIT_BYTES_PER_SEC); + RTTestIValue("calls", cIterations, RTTESTUNIT_CALLS); + RTTestIValue("bytes", cbTotal, RTTESTUNIT_BYTES); + if (g_fShowDuration) + RTTestIValue("duration", nsElapsed, RTTESTUNIT_NS); + + /* + * Cleanup. + */ + RTMemFree(Args.pbBuf); +} + +#endif /* FSPERF_TEST_SENDFILE */ +#ifdef RT_OS_LINUX + +#ifndef __NR_splice +# if defined(RT_ARCH_AMD64) +# define __NR_splice 275 +# elif defined(RT_ARCH_X86) +# define __NR_splice 313 +# else +# error "fix me" +# endif +#endif + +/** FsPerf is built against ancient glibc, so make the splice syscall ourselves. */ +DECLINLINE(ssize_t) syscall_splice(int fdIn, loff_t *poffIn, int fdOut, loff_t *poffOut, size_t cbChunk, unsigned fFlags) +{ + return syscall(__NR_splice, fdIn, poffIn, fdOut, poffOut, cbChunk, fFlags); +} + + +/** + * Send file thread arguments. + */ +typedef struct FSPERFSPLICEARGS +{ + uint64_t offFile; + size_t cbSend; + uint64_t cbSent; + size_t cbBuf; + uint8_t *pbBuf; + uint8_t bFiller; + bool fCheckBuf; + uint32_t cCalls; + RTPIPE hPipe; + uint64_t volatile tsThreadDone; +} FSPERFSPLICEARGS; + + +/** Thread receiving the bytes from a splice() call. */ +static DECLCALLBACK(int) fsPerfSpliceToPipeThread(RTTHREAD hSelf, void *pvUser) +{ + FSPERFSPLICEARGS *pArgs = (FSPERFSPLICEARGS *)pvUser; + int rc = VINF_SUCCESS; + + if (pArgs->fCheckBuf) + RTTestSetDefault(g_hTest, NULL); + + uint64_t cbReceived = 0; + while (cbReceived < pArgs->cbSent) + { + size_t const cbToRead = RT_MIN(pArgs->cbBuf, pArgs->cbSent - cbReceived); + size_t cbActual = 0; + RTTEST_CHECK_RC_BREAK(g_hTest, rc = RTPipeReadBlocking(pArgs->hPipe, pArgs->pbBuf, cbToRead, &cbActual), VINF_SUCCESS); + RTTEST_CHECK_BREAK(g_hTest, cbActual != 0); + RTTEST_CHECK(g_hTest, cbActual <= cbToRead); + if (pArgs->fCheckBuf) + fsPerfCheckReadBuf(__LINE__, pArgs->offFile + cbReceived, pArgs->pbBuf, cbActual, pArgs->bFiller); + cbReceived += cbActual; + } + + pArgs->tsThreadDone = RTTimeNanoTS(); + + if (cbReceived == pArgs->cbSent && RT_SUCCESS(rc)) + { + size_t cbActual = 0; + rc = RTPipeRead(pArgs->hPipe, pArgs->pbBuf, 1, &cbActual); + if (rc != VINF_SUCCESS && rc != VINF_TRY_AGAIN && rc != VERR_BROKEN_PIPE) + RTTestFailed(g_hTest, "RTPipeReadBlocking() -> %Rrc; expected VINF_SUCCESS or VINF_TRY_AGAIN\n", rc); + else if (cbActual != 0) + RTTestFailed(g_hTest, "splice read pipe still contains data when done!\n"); + } + + RTTEST_CHECK_RC(g_hTest, RTPipeClose(pArgs->hPipe), VINF_SUCCESS); + pArgs->hPipe = NIL_RTPIPE; + + RT_NOREF(hSelf); + return rc; +} + + +/** Sends hFile1 to a pipe via the Linux-specific splice() syscall. */ +static uint64_t fsPerfSpliceToPipeOne(FSPERFSPLICEARGS *pArgs, RTFILE hFile1, uint64_t offFile, + size_t cbSend, uint64_t cbSent, uint8_t bFiller, bool fCheckBuf, unsigned iLine) +{ + /* Copy parameters to the argument structure: */ + pArgs->offFile = offFile; + pArgs->cbSend = cbSend; + pArgs->cbSent = cbSent; + pArgs->bFiller = bFiller; + pArgs->fCheckBuf = fCheckBuf; + + /* Create a socket pair. */ + pArgs->hPipe = NIL_RTPIPE; + RTPIPE hPipeW = NIL_RTPIPE; + RTTESTI_CHECK_RC_RET(RTPipeCreate(&pArgs->hPipe, &hPipeW, 0 /*fFlags*/), VINF_SUCCESS, 0); + + /* Create the receiving thread: */ + int rc; + RTTHREAD hThread = NIL_RTTHREAD; + RTTESTI_CHECK_RC(rc = RTThreadCreate(&hThread, fsPerfSpliceToPipeThread, pArgs, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "splicerecv"), VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + uint64_t const tsStart = RTTimeNanoTS(); + size_t cbLeft = cbSend; + size_t cbTotal = 0; + do + { + loff_t offFileIn = offFile; + ssize_t cbActual = syscall_splice((int)RTFileToNative(hFile1), &offFileIn, (int)RTPipeToNative(hPipeW), NULL, + cbLeft, 0 /*fFlags*/); + int const iErr = errno; + if (RT_UNLIKELY(cbActual < 0)) + { + if (iErr == EPIPE && cbTotal == pArgs->cbSent) + break; + RTTestIFailed("%u: splice(file, &%#RX64, pipe, NULL, %#zx, 0) failed (%zd): %d (%Rrc), offFileIn=%#RX64\n", + iLine, offFile, cbLeft, cbActual, iErr, RTErrConvertFromErrno(iErr), (uint64_t)offFileIn); + break; + } + RTTESTI_CHECK_BREAK((uint64_t)cbActual <= cbLeft); + if ((uint64_t)offFileIn != offFile + (uint64_t)cbActual) + { + RTTestIFailed("%u: splice(file, &%#RX64, pipe, NULL, %#zx, 0): %#zx; offFileIn=%#RX64, expected %#RX64\n", + iLine, offFile, cbLeft, cbActual, (uint64_t)offFileIn, offFile + (uint64_t)cbActual); + break; + } + if (cbActual > 0) + { + pArgs->cCalls++; + offFile += (size_t)cbActual; + cbTotal += (size_t)cbActual; + cbLeft -= (size_t)cbActual; + } + else + break; + } while (cbLeft > 0); + + if (cbTotal != pArgs->cbSent) + RTTestIFailed("%u: spliced a total of %#zx bytes, expected %#zx!\n", iLine, cbTotal, pArgs->cbSent); + + RTTESTI_CHECK_RC(RTPipeClose(hPipeW), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTThreadWait(hThread, 30 * RT_NS_1SEC, NULL), VINF_SUCCESS); + + if (pArgs->tsThreadDone >= tsStart) + return RT_MAX(pArgs->tsThreadDone - tsStart, 1); + } + return 0; +} + + +static void fsPerfSpliceToPipe(RTFILE hFile1, uint64_t cbFile) +{ + RTTestISub("splice/to-pipe"); + + /* + * splice was introduced in 2.6.17 according to the man-page. + */ + char szRelease[64]; + RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szRelease, sizeof(szRelease)); + if (RTStrVersionCompare(szRelease, "2.6.17") < 0) + { + RTTestPassed(g_hTest, "too old kernel (%s)", szRelease); + return; + } + + uint64_t const cbFileMax = RT_MIN(cbFile, UINT32_MAX - PAGE_OFFSET_MASK); + signal(SIGPIPE, SIG_IGN); + + /* + * Allocate a buffer. + */ + FSPERFSPLICEARGS Args; + Args.cbBuf = RT_MIN(RT_MIN(cbFileMax, _16M), g_cbMaxBuffer); + Args.pbBuf = (uint8_t *)RTMemAlloc(Args.cbBuf); + while (!Args.pbBuf) + { + Args.cbBuf /= 8; + RTTESTI_CHECK_RETV(Args.cbBuf >= _64K); + Args.pbBuf = (uint8_t *)RTMemAlloc(Args.cbBuf); + } + + /* + * First iteration with default buffer content. + */ + fsPerfSpliceToPipeOne(&Args, hFile1, 0, cbFileMax, cbFileMax, 0xf6, true /*fCheckBuf*/, __LINE__); + if (cbFileMax == cbFile) + fsPerfSpliceToPipeOne(&Args, hFile1, 63, cbFileMax, cbFileMax - 63, 0xf6, true /*fCheckBuf*/, __LINE__); + else + fsPerfSpliceToPipeOne(&Args, hFile1, 63, cbFileMax - 63, cbFileMax - 63, 0xf6, true /*fCheckBuf*/, __LINE__); + + /* + * Write a block using the regular API and then send it, checking that + * the any caching that sendfile does is correctly updated. + */ + uint8_t bFiller = 0xf6; + size_t cbToSend = RT_MIN(cbFileMax, Args.cbBuf); + do + { + fsPerfSpliceToPipeOne(&Args, hFile1, 0, cbToSend, cbToSend, bFiller, true /*fCheckBuf*/, __LINE__); /* prime cache */ + + bFiller += 1; + fsPerfFillWriteBuf(0, Args.pbBuf, cbToSend, bFiller); + RTTESTI_CHECK_RC(RTFileWriteAt(hFile1, 0, Args.pbBuf, cbToSend, NULL), VINF_SUCCESS); + + fsPerfSpliceToPipeOne(&Args, hFile1, 0, cbToSend, cbToSend, bFiller, true /*fCheckBuf*/, __LINE__); + + cbToSend /= 2; + } while (cbToSend >= PAGE_SIZE && ((unsigned)bFiller - 0xf7U) < 64); + + /* + * Restore buffer content + */ + bFiller = 0xf6; + fsPerfFillWriteBuf(0, Args.pbBuf, Args.cbBuf, bFiller); + RTTESTI_CHECK_RC(RTFileWriteAt(hFile1, 0, Args.pbBuf, Args.cbBuf, NULL), VINF_SUCCESS); + + /* + * Do 128 random sends. + */ + uint64_t const cbSmall = RT_MIN(_256K, cbFileMax / 16); + for (uint32_t iTest = 0; iTest < 128; iTest++) + { + cbToSend = (size_t)RTRandU64Ex(1, iTest < 64 ? cbSmall : cbFileMax); + uint64_t const offToSendFrom = RTRandU64Ex(0, cbFile - 1); + uint64_t const cbSent = offToSendFrom + cbToSend <= cbFile ? cbToSend : cbFile - offToSendFrom; + + fsPerfSpliceToPipeOne(&Args, hFile1, offToSendFrom, cbToSend, cbSent, bFiller, true /*fCheckBuf*/, __LINE__); + } + + /* + * Benchmark it. + */ + Args.cCalls = 0; + uint32_t cIterations = 0; + uint64_t nsElapsed = 0; + for (;;) + { + uint64_t cNsThis = fsPerfSpliceToPipeOne(&Args, hFile1, 0, cbFileMax, cbFileMax, 0xf6, false /*fCheckBuf*/, __LINE__); + nsElapsed += cNsThis; + cIterations++; + if (!cNsThis || nsElapsed >= g_nsTestRun) + break; + } + uint64_t cbTotal = cbFileMax * cIterations; + RTTestIValue("latency", nsElapsed / Args.cCalls, RTTESTUNIT_NS_PER_CALL); + RTTestIValue("throughput", (uint64_t)(cbTotal / ((double)nsElapsed / RT_NS_1SEC)), RTTESTUNIT_BYTES_PER_SEC); + RTTestIValue("calls", Args.cCalls, RTTESTUNIT_CALLS); + RTTestIValue("bytes/call", cbTotal / Args.cCalls, RTTESTUNIT_BYTES); + RTTestIValue("iterations", cIterations, RTTESTUNIT_NONE); + RTTestIValue("bytes", cbTotal, RTTESTUNIT_BYTES); + if (g_fShowDuration) + RTTestIValue("duration", nsElapsed, RTTESTUNIT_NS); + + /* + * Cleanup. + */ + RTMemFree(Args.pbBuf); +} + + +/** Thread sending the bytes to a splice() call. */ +static DECLCALLBACK(int) fsPerfSpliceToFileThread(RTTHREAD hSelf, void *pvUser) +{ + FSPERFSPLICEARGS *pArgs = (FSPERFSPLICEARGS *)pvUser; + int rc = VINF_SUCCESS; + + uint64_t offFile = pArgs->offFile; + uint64_t cbTotalSent = 0; + while (cbTotalSent < pArgs->cbSent) + { + size_t const cbToSend = RT_MIN(pArgs->cbBuf, pArgs->cbSent - cbTotalSent); + fsPerfFillWriteBuf(offFile, pArgs->pbBuf, cbToSend, pArgs->bFiller); + RTTEST_CHECK_RC_BREAK(g_hTest, rc = RTPipeWriteBlocking(pArgs->hPipe, pArgs->pbBuf, cbToSend, NULL), VINF_SUCCESS); + offFile += cbToSend; + cbTotalSent += cbToSend; + } + + pArgs->tsThreadDone = RTTimeNanoTS(); + + RTTEST_CHECK_RC(g_hTest, RTPipeClose(pArgs->hPipe), VINF_SUCCESS); + pArgs->hPipe = NIL_RTPIPE; + + RT_NOREF(hSelf); + return rc; +} + + +/** Fill hFile1 via a pipe and the Linux-specific splice() syscall. */ +static uint64_t fsPerfSpliceToFileOne(FSPERFSPLICEARGS *pArgs, RTFILE hFile1, uint64_t offFile, + size_t cbSend, uint64_t cbSent, uint8_t bFiller, bool fCheckFile, unsigned iLine) +{ + /* Copy parameters to the argument structure: */ + pArgs->offFile = offFile; + pArgs->cbSend = cbSend; + pArgs->cbSent = cbSent; + pArgs->bFiller = bFiller; + pArgs->fCheckBuf = false; + + /* Create a socket pair. */ + pArgs->hPipe = NIL_RTPIPE; + RTPIPE hPipeR = NIL_RTPIPE; + RTTESTI_CHECK_RC_RET(RTPipeCreate(&hPipeR, &pArgs->hPipe, 0 /*fFlags*/), VINF_SUCCESS, 0); + + /* Create the receiving thread: */ + int rc; + RTTHREAD hThread = NIL_RTTHREAD; + RTTESTI_CHECK_RC(rc = RTThreadCreate(&hThread, fsPerfSpliceToFileThread, pArgs, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "splicerecv"), VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + /* + * Do the splicing. + */ + uint64_t const tsStart = RTTimeNanoTS(); + size_t cbLeft = cbSend; + size_t cbTotal = 0; + do + { + loff_t offFileOut = offFile; + ssize_t cbActual = syscall_splice((int)RTPipeToNative(hPipeR), NULL, (int)RTFileToNative(hFile1), &offFileOut, + cbLeft, 0 /*fFlags*/); + int const iErr = errno; + if (RT_UNLIKELY(cbActual < 0)) + { + RTTestIFailed("%u: splice(pipe, NULL, file, &%#RX64, %#zx, 0) failed (%zd): %d (%Rrc), offFileOut=%#RX64\n", + iLine, offFile, cbLeft, cbActual, iErr, RTErrConvertFromErrno(iErr), (uint64_t)offFileOut); + break; + } + RTTESTI_CHECK_BREAK((uint64_t)cbActual <= cbLeft); + if ((uint64_t)offFileOut != offFile + (uint64_t)cbActual) + { + RTTestIFailed("%u: splice(pipe, NULL, file, &%#RX64, %#zx, 0): %#zx; offFileOut=%#RX64, expected %#RX64\n", + iLine, offFile, cbLeft, cbActual, (uint64_t)offFileOut, offFile + (uint64_t)cbActual); + break; + } + if (cbActual > 0) + { + pArgs->cCalls++; + offFile += (size_t)cbActual; + cbTotal += (size_t)cbActual; + cbLeft -= (size_t)cbActual; + } + else + break; + } while (cbLeft > 0); + uint64_t const nsElapsed = RTTimeNanoTS() - tsStart; + + if (cbTotal != pArgs->cbSent) + RTTestIFailed("%u: spliced a total of %#zx bytes, expected %#zx!\n", iLine, cbTotal, pArgs->cbSent); + + RTTESTI_CHECK_RC(RTPipeClose(hPipeR), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTThreadWait(hThread, 30 * RT_NS_1SEC, NULL), VINF_SUCCESS); + + /* Check the file content. */ + if (fCheckFile && cbTotal == pArgs->cbSent) + { + offFile = pArgs->offFile; + cbLeft = cbSent; + while (cbLeft > 0) + { + size_t cbToRead = RT_MIN(cbLeft, pArgs->cbBuf); + RTTESTI_CHECK_RC_BREAK(RTFileReadAt(hFile1, offFile, pArgs->pbBuf, cbToRead, NULL), VINF_SUCCESS); + if (!fsPerfCheckReadBuf(iLine, offFile, pArgs->pbBuf, cbToRead, pArgs->bFiller)) + break; + offFile += cbToRead; + cbLeft -= cbToRead; + } + } + return nsElapsed; + } + return 0; +} + + +static void fsPerfSpliceToFile(RTFILE hFile1, uint64_t cbFile) +{ + RTTestISub("splice/to-file"); + + /* + * splice was introduced in 2.6.17 according to the man-page. + */ + char szRelease[64]; + RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szRelease, sizeof(szRelease)); + if (RTStrVersionCompare(szRelease, "2.6.17") < 0) + { + RTTestPassed(g_hTest, "too old kernel (%s)", szRelease); + return; + } + + uint64_t const cbFileMax = RT_MIN(cbFile, UINT32_MAX - PAGE_OFFSET_MASK); + signal(SIGPIPE, SIG_IGN); + + /* + * Allocate a buffer. + */ + FSPERFSPLICEARGS Args; + Args.cbBuf = RT_MIN(RT_MIN(cbFileMax, _16M), g_cbMaxBuffer); + Args.pbBuf = (uint8_t *)RTMemAlloc(Args.cbBuf); + while (!Args.pbBuf) + { + Args.cbBuf /= 8; + RTTESTI_CHECK_RETV(Args.cbBuf >= _64K); + Args.pbBuf = (uint8_t *)RTMemAlloc(Args.cbBuf); + } + + /* + * Do the whole file. + */ + uint8_t bFiller = 0x76; + fsPerfSpliceToFileOne(&Args, hFile1, 0, cbFileMax, cbFileMax, bFiller, true /*fCheckFile*/, __LINE__); + + /* + * Do 64 random chunks (this is slower). + */ + uint64_t const cbSmall = RT_MIN(_256K, cbFileMax / 16); + for (uint32_t iTest = 0; iTest < 64; iTest++) + { + size_t const cbToWrite = (size_t)RTRandU64Ex(1, iTest < 24 ? cbSmall : cbFileMax); + uint64_t const offToWriteAt = RTRandU64Ex(0, cbFile - cbToWrite); + uint64_t const cbTryRead = cbToWrite + (iTest & 1 ? RTRandU32Ex(0, _64K) : 0); + + bFiller++; + fsPerfSpliceToFileOne(&Args, hFile1, offToWriteAt, cbTryRead, cbToWrite, bFiller, true /*fCheckFile*/, __LINE__); + } + + /* + * Benchmark it. + */ + Args.cCalls = 0; + uint32_t cIterations = 0; + uint64_t nsElapsed = 0; + for (;;) + { + uint64_t cNsThis = fsPerfSpliceToFileOne(&Args, hFile1, 0, cbFileMax, cbFileMax, 0xf6, false /*fCheckBuf*/, __LINE__); + nsElapsed += cNsThis; + cIterations++; + if (!cNsThis || nsElapsed >= g_nsTestRun) + break; + } + uint64_t cbTotal = cbFileMax * cIterations; + RTTestIValue("latency", nsElapsed / Args.cCalls, RTTESTUNIT_NS_PER_CALL); + RTTestIValue("throughput", (uint64_t)(cbTotal / ((double)nsElapsed / RT_NS_1SEC)), RTTESTUNIT_BYTES_PER_SEC); + RTTestIValue("calls", Args.cCalls, RTTESTUNIT_CALLS); + RTTestIValue("bytes/call", cbTotal / Args.cCalls, RTTESTUNIT_BYTES); + RTTestIValue("iterations", cIterations, RTTESTUNIT_NONE); + RTTestIValue("bytes", cbTotal, RTTESTUNIT_BYTES); + if (g_fShowDuration) + RTTestIValue("duration", nsElapsed, RTTESTUNIT_NS); + + /* + * Cleanup. + */ + RTMemFree(Args.pbBuf); +} + +#endif /* RT_OS_LINUX */ + +/** For fsPerfIoRead and fsPerfIoWrite. */ +#define PROFILE_IO_FN(a_szOperation, a_fnCall) \ + do \ + { \ + RTTESTI_CHECK_RC_RETV(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); \ + uint64_t offActual = 0; \ + uint32_t cSeeks = 0; \ + \ + /* Estimate how many iterations we need to fill up the given timeslot: */ \ + fsPerfYield(); \ + uint64_t nsStart = RTTimeNanoTS(); \ + uint64_t ns; \ + do \ + ns = RTTimeNanoTS(); \ + while (ns == nsStart); \ + nsStart = ns; \ + \ + uint64_t iIteration = 0; \ + do \ + { \ + RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \ + iIteration++; \ + ns = RTTimeNanoTS() - nsStart; \ + } while (ns < RT_NS_10MS); \ + ns /= iIteration; \ + if (ns > g_nsPerNanoTSCall + 32) \ + ns -= g_nsPerNanoTSCall; \ + uint64_t cIterations = g_nsTestRun / ns; \ + if (cIterations < 2) \ + cIterations = 2; \ + else if (cIterations & 1) \ + cIterations++; \ + \ + /* Do the actual profiling: */ \ + cSeeks = 0; \ + iIteration = 0; \ + fsPerfYield(); \ + nsStart = RTTimeNanoTS(); \ + for (uint32_t iAdjust = 0; iAdjust < 4; iAdjust++) \ + { \ + for (; iIteration < cIterations; iIteration++)\ + RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \ + ns = RTTimeNanoTS() - nsStart;\ + if (ns >= g_nsTestRun - (g_nsTestRun / 10)) \ + break; \ + cIterations += cIterations / 4; \ + if (cIterations & 1) \ + cIterations++; \ + nsStart += g_nsPerNanoTSCall; \ + } \ + RTTestIValueF(ns / iIteration, \ + RTTESTUNIT_NS_PER_OCCURRENCE, a_szOperation "/seq/%RU32 latency", cbBlock); \ + RTTestIValueF((uint64_t)((double)(iIteration * cbBlock) / ((double)ns / RT_NS_1SEC)), \ + RTTESTUNIT_BYTES_PER_SEC, a_szOperation "/seq/%RU32 throughput", cbBlock); \ + RTTestIValueF(iIteration, \ + RTTESTUNIT_CALLS, a_szOperation "/seq/%RU32 calls", cbBlock); \ + RTTestIValueF((uint64_t)iIteration * cbBlock, \ + RTTESTUNIT_BYTES, a_szOperation "/seq/%RU32 bytes", cbBlock); \ + RTTestIValueF(cSeeks, \ + RTTESTUNIT_OCCURRENCES, a_szOperation "/seq/%RU32 seeks", cbBlock); \ + if (g_fShowDuration) \ + RTTestIValueF(ns, RTTESTUNIT_NS, a_szOperation "/seq/%RU32 duration", cbBlock); \ + } while (0) + + +/** + * One RTFileRead profiling iteration. + */ +DECL_FORCE_INLINE(int) fsPerfIoReadWorker(RTFILE hFile1, uint64_t cbFile, uint32_t cbBlock, uint8_t *pbBlock, + uint64_t *poffActual, uint32_t *pcSeeks) +{ + /* Do we need to seek back to the start? */ + if (*poffActual + cbBlock <= cbFile) + { /* likely */ } + else + { + RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck); + *pcSeeks += 1; + *poffActual = 0; + } + + size_t cbActuallyRead = 0; + RTTESTI_CHECK_RC_RET(RTFileRead(hFile1, pbBlock, cbBlock, &cbActuallyRead), VINF_SUCCESS, rcCheck); + if (cbActuallyRead == cbBlock) + { + *poffActual += cbActuallyRead; + return VINF_SUCCESS; + } + RTTestIFailed("RTFileRead at %#RX64 returned just %#x bytes, expected %#x", *poffActual, cbActuallyRead, cbBlock); + *poffActual += cbActuallyRead; + return VERR_READ_ERROR; +} + + +void fsPerfIoReadBlockSize(RTFILE hFile1, uint64_t cbFile, uint32_t cbBlock) +{ + RTTestISubF("IO - Sequential read %RU32", cbBlock); + if (cbBlock <= cbFile) + { + + uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBlock); + if (pbBuf) + { + memset(pbBuf, 0xf7, cbBlock); + PROFILE_IO_FN("RTFileRead", fsPerfIoReadWorker(hFile1, cbFile, cbBlock, pbBuf, &offActual, &cSeeks)); + RTMemPageFree(pbBuf, cbBlock); + } + else + RTTestSkipped(g_hTest, "insufficient (virtual) memory available"); + } + else + RTTestSkipped(g_hTest, "test file too small"); +} + + +/** preadv is too new to be useful, so we use the readv api via this wrapper. */ +DECLINLINE(int) myFileSgReadAt(RTFILE hFile, RTFOFF off, PRTSGBUF pSgBuf, size_t cbToRead, size_t *pcbRead) +{ + int rc = RTFileSeek(hFile, off, RTFILE_SEEK_BEGIN, NULL); + if (RT_SUCCESS(rc)) + rc = RTFileSgRead(hFile, pSgBuf, cbToRead, pcbRead); + return rc; +} + + +void fsPerfRead(RTFILE hFile1, RTFILE hFileNoCache, uint64_t cbFile) +{ + RTTestISubF("IO - RTFileRead"); + + /* + * Allocate a big buffer we can play around with. Min size is 1MB. + */ + size_t cbMaxBuf = RT_MIN(_64M, g_cbMaxBuffer); + size_t cbBuf = cbFile < cbMaxBuf ? (size_t)cbFile : cbMaxBuf; + uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf); + while (!pbBuf) + { + cbBuf /= 2; + RTTESTI_CHECK_RETV(cbBuf >= _1M); + pbBuf = (uint8_t *)RTMemPageAlloc(_32M); + } + +#if 1 + /* + * Start at the beginning and read the full buffer in random small chunks, thereby + * checking that unaligned buffer addresses, size and file offsets work fine. + */ + struct + { + uint64_t offFile; + uint32_t cbMax; + } aRuns[] = { { 0, 127 }, { cbFile - cbBuf, UINT32_MAX }, { 0, UINT32_MAX -1 }}; + for (uint32_t i = 0; i < RT_ELEMENTS(aRuns); i++) + { + memset(pbBuf, 0x55, cbBuf); + RTTESTI_CHECK_RC(RTFileSeek(hFile1, aRuns[i].offFile, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + for (size_t offBuf = 0; offBuf < cbBuf; ) + { + uint32_t const cbLeft = (uint32_t)(cbBuf - offBuf); + uint32_t const cbToRead = aRuns[i].cbMax < UINT32_MAX / 2 ? RTRandU32Ex(1, RT_MIN(aRuns[i].cbMax, cbLeft)) + : aRuns[i].cbMax == UINT32_MAX ? RTRandU32Ex(RT_MAX(cbLeft / 4, 1), cbLeft) + : RTRandU32Ex(cbLeft >= _8K ? _8K : 1, RT_MIN(_1M, cbLeft)); + size_t cbActual = 0; + RTTESTI_CHECK_RC(RTFileRead(hFile1, &pbBuf[offBuf], cbToRead, &cbActual), VINF_SUCCESS); + if (cbActual == cbToRead) + { + offBuf += cbActual; + RTTESTI_CHECK_MSG(RTFileTell(hFile1) == aRuns[i].offFile + offBuf, + ("%#RX64, expected %#RX64\n", RTFileTell(hFile1), aRuns[i].offFile + offBuf)); + } + else + { + RTTestIFailed("Attempting to read %#x bytes at %#zx, only got %#x bytes back! (cbLeft=%#x cbBuf=%#zx)\n", + cbToRead, offBuf, cbActual, cbLeft, cbBuf); + if (cbActual) + offBuf += cbActual; + else + pbBuf[offBuf++] = 0x11; + } + } + fsPerfCheckReadBuf(__LINE__, aRuns[i].offFile, pbBuf, cbBuf); + } + + /* + * Test reading beyond the end of the file. + */ + size_t const acbMax[] = { cbBuf, _64K, _16K, _4K, 256 }; + uint32_t const aoffFromEos[] = + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 32, 63, 64, 127, 128, 255, 254, 256, 1023, 1024, 2048, + 4092, 4093, 4094, 4095, 4096, 4097, 4098, 4099, 4100, 8192, 16384, 32767, 32768, 32769, 65535, 65536, _1M - 1 + }; + for (unsigned iMax = 0; iMax < RT_ELEMENTS(acbMax); iMax++) + { + size_t const cbMaxRead = acbMax[iMax]; + for (uint32_t iOffFromEos = 0; iOffFromEos < RT_ELEMENTS(aoffFromEos); iOffFromEos++) + { + uint32_t off = aoffFromEos[iOffFromEos]; + if (off >= cbMaxRead) + continue; + RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbFile - off, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + size_t cbActual = ~(size_t)0; + RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, cbMaxRead, &cbActual), VINF_SUCCESS); + RTTESTI_CHECK(cbActual == off); + + RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbFile - off, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + cbActual = ~(size_t)0; + RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, off, &cbActual), VINF_SUCCESS); + RTTESTI_CHECK_MSG(cbActual == off, ("%#zx vs %#zx\n", cbActual, off)); + + cbActual = ~(size_t)0; + RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, 1, &cbActual), VINF_SUCCESS); + RTTESTI_CHECK_MSG(cbActual == 0, ("cbActual=%zu\n", cbActual)); + + RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, cbMaxRead, NULL), VERR_EOF); + + /* Repeat using native APIs in case IPRT or other layers hide status codes: */ +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) + RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbFile - off, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); +# ifdef RT_OS_OS2 + ULONG cbActual2 = ~(ULONG)0; + APIRET orc = DosRead((HFILE)RTFileToNative(hFile1), pbBuf, cbMaxRead, &cbActual2); + RTTESTI_CHECK_MSG(orc == NO_ERROR, ("orc=%u, expected 0\n", orc)); + RTTESTI_CHECK_MSG(cbActual2 == off, ("%#x vs %#x\n", cbActual2, off)); +# else + IO_STATUS_BLOCK const IosVirgin = RTNT_IO_STATUS_BLOCK_INITIALIZER; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/, + &Ios, pbBuf, (ULONG)cbMaxRead, NULL /*poffFile*/, NULL /*Key*/); + if (off == 0) + { + RTTESTI_CHECK_MSG(rcNt == STATUS_END_OF_FILE, ("rcNt=%#x, expected %#x\n", rcNt, STATUS_END_OF_FILE)); + RTTESTI_CHECK_MSG(Ios.Status == IosVirgin.Status /*slow?*/ || Ios.Status == STATUS_END_OF_FILE /*fastio?*/, + ("%#x vs %x/%#x; off=%#x\n", Ios.Status, IosVirgin.Status, STATUS_END_OF_FILE, off)); + RTTESTI_CHECK_MSG(Ios.Information == IosVirgin.Information /*slow*/ || Ios.Information == 0 /*fastio?*/, + ("%#zx vs %zx/0; off=%#x\n", Ios.Information, IosVirgin.Information, off)); + } + else + { + RTTESTI_CHECK_MSG(rcNt == STATUS_SUCCESS, ("rcNt=%#x, expected 0 (off=%#x cbMaxRead=%#zx)\n", rcNt, off, cbMaxRead)); + RTTESTI_CHECK_MSG(Ios.Status == STATUS_SUCCESS, ("%#x; off=%#x\n", Ios.Status, off)); + RTTESTI_CHECK_MSG(Ios.Information == off, ("%#zx vs %#x\n", Ios.Information, off)); + } +# endif + +# ifdef RT_OS_OS2 + cbActual2 = ~(ULONG)0; + orc = DosRead((HFILE)RTFileToNative(hFile1), pbBuf, 1, &cbActual2); + RTTESTI_CHECK_MSG(orc == NO_ERROR, ("orc=%u, expected 0\n", orc)); + RTTESTI_CHECK_MSG(cbActual2 == 0, ("cbActual2=%u\n", cbActual2)); +# else + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/, + &Ios, pbBuf, 1, NULL /*poffFile*/, NULL /*Key*/); + RTTESTI_CHECK_MSG(rcNt == STATUS_END_OF_FILE, ("rcNt=%#x, expected %#x\n", rcNt, STATUS_END_OF_FILE)); +# endif + +#endif + } + } + + /* + * Test reading beyond end of the file. + */ + for (unsigned iMax = 0; iMax < RT_ELEMENTS(acbMax); iMax++) + { + size_t const cbMaxRead = acbMax[iMax]; + for (uint32_t off = 0; off < 256; off++) + { + RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbFile + off, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + size_t cbActual = ~(size_t)0; + RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, cbMaxRead, &cbActual), VINF_SUCCESS); + RTTESTI_CHECK(cbActual == 0); + + RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, cbMaxRead, NULL), VERR_EOF); + + /* Repeat using native APIs in case IPRT or other layers hid status codes: */ +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) + RTTESTI_CHECK_RC(RTFileSeek(hFile1, cbFile + off, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); +# ifdef RT_OS_OS2 + ULONG cbActual2 = ~(ULONG)0; + APIRET orc = DosRead((HFILE)RTFileToNative(hFile1), pbBuf, cbMaxRead, &cbActual2); + RTTESTI_CHECK_MSG(orc == NO_ERROR, ("orc=%u, expected 0\n", orc)); + RTTESTI_CHECK_MSG(cbActual2 == 0, ("%#x vs %#x\n", cbActual2, off)); +# else + IO_STATUS_BLOCK const IosVirgin = RTNT_IO_STATUS_BLOCK_INITIALIZER; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/, + &Ios, pbBuf, (ULONG)cbMaxRead, NULL /*poffFile*/, NULL /*Key*/); + RTTESTI_CHECK_MSG(rcNt == STATUS_END_OF_FILE, ("rcNt=%#x, expected %#x\n", rcNt, STATUS_END_OF_FILE)); + RTTESTI_CHECK_MSG(Ios.Status == IosVirgin.Status /*slow?*/ || Ios.Status == STATUS_END_OF_FILE /*fastio?*/, + ("%#x vs %x/%#x; off=%#x\n", Ios.Status, IosVirgin.Status, STATUS_END_OF_FILE, off)); + RTTESTI_CHECK_MSG(Ios.Information == IosVirgin.Information /*slow*/ || Ios.Information == 0 /*fastio?*/, + ("%#zx vs %zx/0; off=%#x\n", Ios.Information, IosVirgin.Information, off)); + + /* Need to work with sector size on uncached, but might be worth it for non-fastio path. */ + uint32_t cbSector = 0x1000; + uint32_t off2 = off * cbSector + (cbFile & (cbSector - 1) ? cbSector - (cbFile & (cbSector - 1)) : 0); + RTTESTI_CHECK_RC(RTFileSeek(hFileNoCache, cbFile + off2, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + size_t const cbMaxRead2 = RT_ALIGN_Z(cbMaxRead, cbSector); + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + rcNt = NtReadFile((HANDLE)RTFileToNative(hFileNoCache), NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/, + &Ios, pbBuf, (ULONG)cbMaxRead2, NULL /*poffFile*/, NULL /*Key*/); + RTTESTI_CHECK_MSG(rcNt == STATUS_END_OF_FILE, + ("rcNt=%#x, expected %#x; off2=%x cbMaxRead2=%#x\n", rcNt, STATUS_END_OF_FILE, off2, cbMaxRead2)); + RTTESTI_CHECK_MSG(Ios.Status == IosVirgin.Status /*slow?*/, + ("%#x vs %x; off2=%#x cbMaxRead2=%#x\n", Ios.Status, IosVirgin.Status, off2, cbMaxRead2)); + RTTESTI_CHECK_MSG(Ios.Information == IosVirgin.Information /*slow*/, + ("%#zx vs %zx; off2=%#x cbMaxRead2=%#x\n", Ios.Information, IosVirgin.Information, off2, cbMaxRead2)); +# endif +#endif + } + } + + /* + * Do uncached access, must be page aligned. + */ + uint32_t cbPage = PAGE_SIZE; + memset(pbBuf, 0x66, cbBuf); + if (!g_fIgnoreNoCache || hFileNoCache != NIL_RTFILE) + { + RTTESTI_CHECK_RC(RTFileSeek(hFileNoCache, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + for (size_t offBuf = 0; offBuf < cbBuf; ) + { + uint32_t const cPagesLeft = (uint32_t)((cbBuf - offBuf) / cbPage); + uint32_t const cPagesToRead = RTRandU32Ex(1, cPagesLeft); + size_t const cbToRead = cPagesToRead * (size_t)cbPage; + size_t cbActual = 0; + RTTESTI_CHECK_RC(RTFileRead(hFileNoCache, &pbBuf[offBuf], cbToRead, &cbActual), VINF_SUCCESS); + if (cbActual == cbToRead) + offBuf += cbActual; + else + { + RTTestIFailed("Attempting to read %#zx bytes at %#zx, only got %#x bytes back!\n", cbToRead, offBuf, cbActual); + if (cbActual) + offBuf += cbActual; + else + { + memset(&pbBuf[offBuf], 0x11, cbPage); + offBuf += cbPage; + } + } + } + fsPerfCheckReadBuf(__LINE__, 0, pbBuf, cbBuf); + } + + /* + * Check reading zero bytes at the end of the file. + * Requires native call because RTFileWrite doesn't call kernel on zero byte reads. + */ + RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_END, NULL), VINF_SUCCESS); +# ifdef RT_OS_WINDOWS + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, 0, NULL, NULL); + RTTESTI_CHECK_MSG(rcNt == STATUS_SUCCESS, ("rcNt=%#x", rcNt)); + RTTESTI_CHECK(Ios.Status == STATUS_SUCCESS); + RTTESTI_CHECK(Ios.Information == 0); + + IO_STATUS_BLOCK const IosVirgin = RTNT_IO_STATUS_BLOCK_INITIALIZER; + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, 1, NULL, NULL); + RTTESTI_CHECK_MSG(rcNt == STATUS_END_OF_FILE, ("rcNt=%#x", rcNt)); + RTTESTI_CHECK_MSG(Ios.Status == IosVirgin.Status /*slow?*/ || Ios.Status == STATUS_END_OF_FILE /*fastio?*/, + ("%#x vs %x/%#x\n", Ios.Status, IosVirgin.Status, STATUS_END_OF_FILE)); + RTTESTI_CHECK_MSG(Ios.Information == IosVirgin.Information /*slow*/ || Ios.Information == 0 /*fastio?*/, + ("%#zx vs %zx/0\n", Ios.Information, IosVirgin.Information)); +# else + ssize_t cbRead = read((int)RTFileToNative(hFile1), pbBuf, 0); + RTTESTI_CHECK(cbRead == 0); +# endif + +#else + RT_NOREF(hFileNoCache); +#endif + + /* + * Scatter read function operation. + */ +#ifdef RT_OS_WINDOWS + /** @todo RTFileSgReadAt is just a RTFileReadAt loop for windows NT. Need + * to use ReadFileScatter (nocache + page aligned). */ +#elif !defined(RT_OS_OS2) /** @todo implement RTFileSg using list i/o */ + +# ifdef UIO_MAXIOV + RTSGSEG aSegs[UIO_MAXIOV]; +# else + RTSGSEG aSegs[512]; +# endif + RTSGBUF SgBuf; + uint32_t cIncr = 1; + for (uint32_t cSegs = 1; cSegs <= RT_ELEMENTS(aSegs); cSegs += cIncr) + { + size_t const cbSeg = cbBuf / cSegs; + size_t const cbToRead = cbSeg * cSegs; + for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++) + { + aSegs[iSeg].cbSeg = cbSeg; + aSegs[iSeg].pvSeg = &pbBuf[cbToRead - (iSeg + 1) * cbSeg]; + } + RTSgBufInit(&SgBuf, &aSegs[0], cSegs); + int rc = myFileSgReadAt(hFile1, 0, &SgBuf, cbToRead, NULL); + if (RT_SUCCESS(rc)) + for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++) + { + if (!fsPerfCheckReadBuf(__LINE__, iSeg * cbSeg, &pbBuf[cbToRead - (iSeg + 1) * cbSeg], cbSeg)) + { + cSegs = RT_ELEMENTS(aSegs); + break; + } + } + else + { + RTTestIFailed("myFileSgReadAt failed: %Rrc - cSegs=%u cbSegs=%#zx cbToRead=%#zx", rc, cSegs, cbSeg, cbToRead); + break; + } + if (cSegs == 16) + cIncr = 7; + else if (cSegs == 16 * 7 + 16 /*= 128*/) + cIncr = 64; + } + + for (uint32_t iTest = 0; iTest < 128; iTest++) + { + uint32_t cSegs = RTRandU32Ex(1, RT_ELEMENTS(aSegs)); + uint32_t iZeroSeg = cSegs > 10 ? RTRandU32Ex(0, cSegs - 1) : UINT32_MAX / 2; + uint32_t cZeroSegs = cSegs > 10 ? RTRandU32Ex(1, RT_MIN(cSegs - iZeroSeg, 25)) : 0; + size_t cbToRead = 0; + size_t cbLeft = cbBuf; + uint8_t *pbCur = &pbBuf[cbBuf]; + for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++) + { + uint32_t iAlign = RTRandU32Ex(0, 3); + if (iAlign & 2) /* end is page aligned */ + { + cbLeft -= (uintptr_t)pbCur & PAGE_OFFSET_MASK; + pbCur -= (uintptr_t)pbCur & PAGE_OFFSET_MASK; + } + + size_t cbSegOthers = (cSegs - iSeg) * _8K; + size_t cbSegMax = cbLeft > cbSegOthers ? cbLeft - cbSegOthers + : cbLeft > cSegs ? cbLeft - cSegs + : cbLeft; + size_t cbSeg = cbLeft != 0 ? RTRandU32Ex(0, cbSegMax) : 0; + if (iAlign & 1) /* start is page aligned */ + cbSeg += ((uintptr_t)pbCur - cbSeg) & PAGE_OFFSET_MASK; + + if (iSeg - iZeroSeg < cZeroSegs) + cbSeg = 0; + + cbToRead += cbSeg; + cbLeft -= cbSeg; + pbCur -= cbSeg; + aSegs[iSeg].cbSeg = cbSeg; + aSegs[iSeg].pvSeg = pbCur; + } + + uint64_t offFile = cbToRead < cbFile ? RTRandU64Ex(0, cbFile - cbToRead) : 0; + RTSgBufInit(&SgBuf, &aSegs[0], cSegs); + int rc = myFileSgReadAt(hFile1, offFile, &SgBuf, cbToRead, NULL); + if (RT_SUCCESS(rc)) + for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++) + { + if (!fsPerfCheckReadBuf(__LINE__, offFile, (uint8_t *)aSegs[iSeg].pvSeg, aSegs[iSeg].cbSeg)) + { + RTTestIFailureDetails("iSeg=%#x cSegs=%#x cbSeg=%#zx cbToRead=%#zx\n", iSeg, cSegs, aSegs[iSeg].cbSeg, cbToRead); + iTest = _16K; + break; + } + offFile += aSegs[iSeg].cbSeg; + } + else + { + RTTestIFailed("myFileSgReadAt failed: %Rrc - cSegs=%#x cbToRead=%#zx", rc, cSegs, cbToRead); + for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++) + RTTestIFailureDetails("aSeg[%u] = %p LB %#zx (last %p)\n", iSeg, aSegs[iSeg].pvSeg, aSegs[iSeg].cbSeg, + (uint8_t *)aSegs[iSeg].pvSeg + aSegs[iSeg].cbSeg - 1); + break; + } + } + + /* reading beyond the end of the file */ + for (uint32_t cSegs = 1; cSegs < 6; cSegs++) + for (uint32_t iTest = 0; iTest < 128; iTest++) + { + uint32_t const cbToRead = RTRandU32Ex(0, cbBuf); + uint32_t const cbBeyond = cbToRead ? RTRandU32Ex(0, cbToRead) : 0; + uint32_t const cbSeg = cbToRead / cSegs; + uint32_t cbLeft = cbToRead; + uint8_t *pbCur = &pbBuf[cbToRead]; + for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++) + { + aSegs[iSeg].cbSeg = iSeg + 1 < cSegs ? cbSeg : cbLeft; + aSegs[iSeg].pvSeg = pbCur -= aSegs[iSeg].cbSeg; + cbLeft -= aSegs[iSeg].cbSeg; + } + Assert(pbCur == pbBuf); + + uint64_t offFile = cbFile + cbBeyond - cbToRead; + RTSgBufInit(&SgBuf, &aSegs[0], cSegs); + int rcExpect = cbBeyond == 0 || cbToRead == 0 ? VINF_SUCCESS : VERR_EOF; + int rc = myFileSgReadAt(hFile1, offFile, &SgBuf, cbToRead, NULL); + if (rc != rcExpect) + { + RTTestIFailed("myFileSgReadAt failed: %Rrc - cSegs=%#x cbToRead=%#zx cbBeyond=%#zx\n", rc, cSegs, cbToRead, cbBeyond); + for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++) + RTTestIFailureDetails("aSeg[%u] = %p LB %#zx (last %p)\n", iSeg, aSegs[iSeg].pvSeg, aSegs[iSeg].cbSeg, + (uint8_t *)aSegs[iSeg].pvSeg + aSegs[iSeg].cbSeg - 1); + } + + RTSgBufInit(&SgBuf, &aSegs[0], cSegs); + size_t cbActual = 0; + rc = myFileSgReadAt(hFile1, offFile, &SgBuf, cbToRead, &cbActual); + if (rc != VINF_SUCCESS || cbActual != cbToRead - cbBeyond) + RTTestIFailed("myFileSgReadAt failed: %Rrc cbActual=%#zu - cSegs=%#x cbToRead=%#zx cbBeyond=%#zx expected %#zx\n", + rc, cbActual, cSegs, cbToRead, cbBeyond, cbToRead - cbBeyond); + if (RT_SUCCESS(rc) && cbActual > 0) + for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++) + { + if (!fsPerfCheckReadBuf(__LINE__, offFile, (uint8_t *)aSegs[iSeg].pvSeg, RT_MIN(cbActual, aSegs[iSeg].cbSeg))) + { + RTTestIFailureDetails("iSeg=%#x cSegs=%#x cbSeg=%#zx cbActual%#zx cbToRead=%#zx cbBeyond=%#zx\n", + iSeg, cSegs, aSegs[iSeg].cbSeg, cbActual, cbToRead, cbBeyond); + iTest = _16K; + break; + } + if (cbActual <= aSegs[iSeg].cbSeg) + break; + cbActual -= aSegs[iSeg].cbSeg; + offFile += aSegs[iSeg].cbSeg; + } + } + +#endif + + /* + * Other OS specific stuff. + */ +#ifdef RT_OS_WINDOWS + /* Check that reading at an offset modifies the position: */ + RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_END, NULL), VINF_SUCCESS); + RTTESTI_CHECK(RTFileTell(hFile1) == cbFile); + + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + LARGE_INTEGER offNt; + offNt.QuadPart = cbFile / 2; + rcNt = NtReadFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, _4K, &offNt, NULL); + RTTESTI_CHECK_MSG(rcNt == STATUS_SUCCESS, ("rcNt=%#x", rcNt)); + RTTESTI_CHECK(Ios.Status == STATUS_SUCCESS); + RTTESTI_CHECK(Ios.Information == _4K); + RTTESTI_CHECK(RTFileTell(hFile1) == cbFile / 2 + _4K); + fsPerfCheckReadBuf(__LINE__, cbFile / 2, pbBuf, _4K); +#endif + + + RTMemPageFree(pbBuf, cbBuf); +} + + +/** + * One RTFileWrite profiling iteration. + */ +DECL_FORCE_INLINE(int) fsPerfIoWriteWorker(RTFILE hFile1, uint64_t cbFile, uint32_t cbBlock, uint8_t *pbBlock, + uint64_t *poffActual, uint32_t *pcSeeks) +{ + /* Do we need to seek back to the start? */ + if (*poffActual + cbBlock <= cbFile) + { /* likely */ } + else + { + RTTESTI_CHECK_RC_RET(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck); + *pcSeeks += 1; + *poffActual = 0; + } + + size_t cbActuallyWritten = 0; + RTTESTI_CHECK_RC_RET(RTFileWrite(hFile1, pbBlock, cbBlock, &cbActuallyWritten), VINF_SUCCESS, rcCheck); + if (cbActuallyWritten == cbBlock) + { + *poffActual += cbActuallyWritten; + return VINF_SUCCESS; + } + RTTestIFailed("RTFileWrite at %#RX64 returned just %#x bytes, expected %#x", *poffActual, cbActuallyWritten, cbBlock); + *poffActual += cbActuallyWritten; + return VERR_WRITE_ERROR; +} + + +void fsPerfIoWriteBlockSize(RTFILE hFile1, uint64_t cbFile, uint32_t cbBlock) +{ + RTTestISubF("IO - Sequential write %RU32", cbBlock); + + if (cbBlock <= cbFile) + { + uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBlock); + if (pbBuf) + { + memset(pbBuf, 0xf7, cbBlock); + PROFILE_IO_FN("RTFileWrite", fsPerfIoWriteWorker(hFile1, cbFile, cbBlock, pbBuf, &offActual, &cSeeks)); + RTMemPageFree(pbBuf, cbBlock); + } + else + RTTestSkipped(g_hTest, "insufficient (virtual) memory available"); + } + else + RTTestSkipped(g_hTest, "test file too small"); +} + + +/** pwritev is too new to be useful, so we use the writev api via this wrapper. */ +DECLINLINE(int) myFileSgWriteAt(RTFILE hFile, RTFOFF off, PRTSGBUF pSgBuf, size_t cbToWrite, size_t *pcbWritten) +{ + int rc = RTFileSeek(hFile, off, RTFILE_SEEK_BEGIN, NULL); + if (RT_SUCCESS(rc)) + rc = RTFileSgWrite(hFile, pSgBuf, cbToWrite, pcbWritten); + return rc; +} + + +void fsPerfWrite(RTFILE hFile1, RTFILE hFileNoCache, RTFILE hFileWriteThru, uint64_t cbFile) +{ + RTTestISubF("IO - RTFileWrite"); + + /* + * Allocate a big buffer we can play around with. Min size is 1MB. + */ + size_t cbMaxBuf = RT_MIN(_64M, g_cbMaxBuffer); + size_t cbBuf = cbFile < cbMaxBuf ? (size_t)cbFile : cbMaxBuf; + uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf); + while (!pbBuf) + { + cbBuf /= 2; + RTTESTI_CHECK_RETV(cbBuf >= _1M); + pbBuf = (uint8_t *)RTMemPageAlloc(_32M); + } + + uint8_t bFiller = 0x88; + +#if 1 + /* + * Start at the beginning and write out the full buffer in random small chunks, thereby + * checking that unaligned buffer addresses, size and file offsets work fine. + */ + struct + { + uint64_t offFile; + uint32_t cbMax; + } aRuns[] = { { 0, 127 }, { cbFile - cbBuf, UINT32_MAX }, { 0, UINT32_MAX -1 }}; + for (uint32_t i = 0; i < RT_ELEMENTS(aRuns); i++, bFiller) + { + fsPerfFillWriteBuf(aRuns[i].offFile, pbBuf, cbBuf, bFiller); + fsPerfCheckReadBuf(__LINE__, aRuns[i].offFile, pbBuf, cbBuf, bFiller); + + RTTESTI_CHECK_RC(RTFileSeek(hFile1, aRuns[i].offFile, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + for (size_t offBuf = 0; offBuf < cbBuf; ) + { + uint32_t const cbLeft = (uint32_t)(cbBuf - offBuf); + uint32_t const cbToWrite = aRuns[i].cbMax < UINT32_MAX / 2 ? RTRandU32Ex(1, RT_MIN(aRuns[i].cbMax, cbLeft)) + : aRuns[i].cbMax == UINT32_MAX ? RTRandU32Ex(RT_MAX(cbLeft / 4, 1), cbLeft) + : RTRandU32Ex(cbLeft >= _8K ? _8K : 1, RT_MIN(_1M, cbLeft)); + size_t cbActual = 0; + RTTESTI_CHECK_RC(RTFileWrite(hFile1, &pbBuf[offBuf], cbToWrite, &cbActual), VINF_SUCCESS); + if (cbActual == cbToWrite) + { + offBuf += cbActual; + RTTESTI_CHECK_MSG(RTFileTell(hFile1) == aRuns[i].offFile + offBuf, + ("%#RX64, expected %#RX64\n", RTFileTell(hFile1), aRuns[i].offFile + offBuf)); + } + else + { + RTTestIFailed("Attempting to write %#x bytes at %#zx (%#x left), only got %#x written!\n", + cbToWrite, offBuf, cbLeft, cbActual); + if (cbActual) + offBuf += cbActual; + else + pbBuf[offBuf++] = 0x11; + } + } + + RTTESTI_CHECK_RC(RTFileReadAt(hFile1, aRuns[i].offFile, pbBuf, cbBuf, NULL), VINF_SUCCESS); + fsPerfCheckReadBuf(__LINE__, aRuns[i].offFile, pbBuf, cbBuf, bFiller); + } + + + /* + * Do uncached and write-thru accesses, must be page aligned. + */ + RTFILE ahFiles[2] = { hFileWriteThru, hFileNoCache }; + for (unsigned iFile = 0; iFile < RT_ELEMENTS(ahFiles); iFile++, bFiller++) + { + if (g_fIgnoreNoCache && ahFiles[iFile] == NIL_RTFILE) + continue; + + fsPerfFillWriteBuf(0, pbBuf, cbBuf, bFiller); + fsPerfCheckReadBuf(__LINE__, 0, pbBuf, cbBuf, bFiller); + RTTESTI_CHECK_RC(RTFileSeek(ahFiles[iFile], 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + + uint32_t cbPage = PAGE_SIZE; + for (size_t offBuf = 0; offBuf < cbBuf; ) + { + uint32_t const cPagesLeft = (uint32_t)((cbBuf - offBuf) / cbPage); + uint32_t const cPagesToWrite = RTRandU32Ex(1, cPagesLeft); + size_t const cbToWrite = cPagesToWrite * (size_t)cbPage; + size_t cbActual = 0; + RTTESTI_CHECK_RC(RTFileWrite(ahFiles[iFile], &pbBuf[offBuf], cbToWrite, &cbActual), VINF_SUCCESS); + if (cbActual == cbToWrite) + { + RTTESTI_CHECK_RC(RTFileReadAt(hFile1, offBuf, pbBuf, cbToWrite, NULL), VINF_SUCCESS); + fsPerfCheckReadBuf(__LINE__, offBuf, pbBuf, cbToWrite, bFiller); + offBuf += cbActual; + } + else + { + RTTestIFailed("Attempting to read %#zx bytes at %#zx, only got %#x written!\n", cbToWrite, offBuf, cbActual); + if (cbActual) + offBuf += cbActual; + else + { + memset(&pbBuf[offBuf], 0x11, cbPage); + offBuf += cbPage; + } + } + } + + RTTESTI_CHECK_RC(RTFileReadAt(ahFiles[iFile], 0, pbBuf, cbBuf, NULL), VINF_SUCCESS); + fsPerfCheckReadBuf(__LINE__, 0, pbBuf, cbBuf, bFiller); + } + + /* + * Check the behavior of writing zero bytes to the file _4K from the end + * using native API. In the olden days zero sized write have been known + * to be used to truncate a file. + */ + RTTESTI_CHECK_RC(RTFileSeek(hFile1, -_4K, RTFILE_SEEK_END, NULL), VINF_SUCCESS); +# ifdef RT_OS_WINDOWS + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtWriteFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, 0, NULL, NULL); + RTTESTI_CHECK_MSG(rcNt == STATUS_SUCCESS, ("rcNt=%#x", rcNt)); + RTTESTI_CHECK(Ios.Status == STATUS_SUCCESS); + RTTESTI_CHECK(Ios.Information == 0); +# else + ssize_t cbWritten = write((int)RTFileToNative(hFile1), pbBuf, 0); + RTTESTI_CHECK(cbWritten == 0); +# endif + RTTESTI_CHECK_RC(RTFileRead(hFile1, pbBuf, _4K, NULL), VINF_SUCCESS); + fsPerfCheckReadBuf(__LINE__, cbFile - _4K, pbBuf, _4K, pbBuf[0x8]); + +#else + RT_NOREF(hFileNoCache, hFileWriteThru); +#endif + + /* + * Gather write function operation. + */ +#ifdef RT_OS_WINDOWS + /** @todo RTFileSgWriteAt is just a RTFileWriteAt loop for windows NT. Need + * to use WriteFileGather (nocache + page aligned). */ +#elif !defined(RT_OS_OS2) /** @todo implement RTFileSg using list i/o */ + +# ifdef UIO_MAXIOV + RTSGSEG aSegs[UIO_MAXIOV]; +# else + RTSGSEG aSegs[512]; +# endif + RTSGBUF SgBuf; + uint32_t cIncr = 1; + for (uint32_t cSegs = 1; cSegs <= RT_ELEMENTS(aSegs); cSegs += cIncr, bFiller++) + { + size_t const cbSeg = cbBuf / cSegs; + size_t const cbToWrite = cbSeg * cSegs; + for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++) + { + aSegs[iSeg].cbSeg = cbSeg; + aSegs[iSeg].pvSeg = &pbBuf[cbToWrite - (iSeg + 1) * cbSeg]; + fsPerfFillWriteBuf(iSeg * cbSeg, (uint8_t *)aSegs[iSeg].pvSeg, cbSeg, bFiller); + } + RTSgBufInit(&SgBuf, &aSegs[0], cSegs); + int rc = myFileSgWriteAt(hFile1, 0, &SgBuf, cbToWrite, NULL); + if (RT_SUCCESS(rc)) + { + RTTESTI_CHECK_RC(RTFileReadAt(hFile1, 0, pbBuf, cbToWrite, NULL), VINF_SUCCESS); + fsPerfCheckReadBuf(__LINE__, 0, pbBuf, cbToWrite, bFiller); + } + else + { + RTTestIFailed("myFileSgWriteAt failed: %Rrc - cSegs=%u cbSegs=%#zx cbToWrite=%#zx", rc, cSegs, cbSeg, cbToWrite); + break; + } + if (cSegs == 16) + cIncr = 7; + else if (cSegs == 16 * 7 + 16 /*= 128*/) + cIncr = 64; + } + + /* random stuff, including zero segments. */ + for (uint32_t iTest = 0; iTest < 128; iTest++, bFiller++) + { + uint32_t cSegs = RTRandU32Ex(1, RT_ELEMENTS(aSegs)); + uint32_t iZeroSeg = cSegs > 10 ? RTRandU32Ex(0, cSegs - 1) : UINT32_MAX / 2; + uint32_t cZeroSegs = cSegs > 10 ? RTRandU32Ex(1, RT_MIN(cSegs - iZeroSeg, 25)) : 0; + size_t cbToWrite = 0; + size_t cbLeft = cbBuf; + uint8_t *pbCur = &pbBuf[cbBuf]; + for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++) + { + uint32_t iAlign = RTRandU32Ex(0, 3); + if (iAlign & 2) /* end is page aligned */ + { + cbLeft -= (uintptr_t)pbCur & PAGE_OFFSET_MASK; + pbCur -= (uintptr_t)pbCur & PAGE_OFFSET_MASK; + } + + size_t cbSegOthers = (cSegs - iSeg) * _8K; + size_t cbSegMax = cbLeft > cbSegOthers ? cbLeft - cbSegOthers + : cbLeft > cSegs ? cbLeft - cSegs + : cbLeft; + size_t cbSeg = cbLeft != 0 ? RTRandU32Ex(0, cbSegMax) : 0; + if (iAlign & 1) /* start is page aligned */ + cbSeg += ((uintptr_t)pbCur - cbSeg) & PAGE_OFFSET_MASK; + + if (iSeg - iZeroSeg < cZeroSegs) + cbSeg = 0; + + cbToWrite += cbSeg; + cbLeft -= cbSeg; + pbCur -= cbSeg; + aSegs[iSeg].cbSeg = cbSeg; + aSegs[iSeg].pvSeg = pbCur; + } + + uint64_t const offFile = cbToWrite < cbFile ? RTRandU64Ex(0, cbFile - cbToWrite) : 0; + uint64_t offFill = offFile; + for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++) + if (aSegs[iSeg].cbSeg) + { + fsPerfFillWriteBuf(offFill, (uint8_t *)aSegs[iSeg].pvSeg, aSegs[iSeg].cbSeg, bFiller); + offFill += aSegs[iSeg].cbSeg; + } + + RTSgBufInit(&SgBuf, &aSegs[0], cSegs); + int rc = myFileSgWriteAt(hFile1, offFile, &SgBuf, cbToWrite, NULL); + if (RT_SUCCESS(rc)) + { + RTTESTI_CHECK_RC(RTFileReadAt(hFile1, offFile, pbBuf, cbToWrite, NULL), VINF_SUCCESS); + fsPerfCheckReadBuf(__LINE__, offFile, pbBuf, cbToWrite, bFiller); + } + else + { + RTTestIFailed("myFileSgWriteAt failed: %Rrc - cSegs=%#x cbToWrite=%#zx", rc, cSegs, cbToWrite); + break; + } + } + +#endif + + /* + * Other OS specific stuff. + */ +#ifdef RT_OS_WINDOWS + /* Check that reading at an offset modifies the position: */ + RTTESTI_CHECK_RC(RTFileReadAt(hFile1, cbFile / 2, pbBuf, _4K, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_END, NULL), VINF_SUCCESS); + RTTESTI_CHECK(RTFileTell(hFile1) == cbFile); + + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + LARGE_INTEGER offNt; + offNt.QuadPart = cbFile / 2; + rcNt = NtWriteFile((HANDLE)RTFileToNative(hFile1), NULL, NULL, NULL, &Ios, pbBuf, _4K, &offNt, NULL); + RTTESTI_CHECK_MSG(rcNt == STATUS_SUCCESS, ("rcNt=%#x", rcNt)); + RTTESTI_CHECK(Ios.Status == STATUS_SUCCESS); + RTTESTI_CHECK(Ios.Information == _4K); + RTTESTI_CHECK(RTFileTell(hFile1) == cbFile / 2 + _4K); +#endif + + RTMemPageFree(pbBuf, cbBuf); +} + + +/** + * Worker for testing RTFileFlush. + */ +DECL_FORCE_INLINE(int) fsPerfFSyncWorker(RTFILE hFile1, uint64_t cbFile, uint8_t *pbBuf, size_t cbBuf, uint64_t *poffFile) +{ + if (*poffFile + cbBuf <= cbFile) + { /* likely */ } + else + { + RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + *poffFile = 0; + } + + RTTESTI_CHECK_RC_RET(RTFileWrite(hFile1, pbBuf, cbBuf, NULL), VINF_SUCCESS, rcCheck); + RTTESTI_CHECK_RC_RET(RTFileFlush(hFile1), VINF_SUCCESS, rcCheck); + + *poffFile += cbBuf; + return VINF_SUCCESS; +} + + +void fsPerfFSync(RTFILE hFile1, uint64_t cbFile) +{ + RTTestISub("fsync"); + + RTTESTI_CHECK_RC(RTFileFlush(hFile1), VINF_SUCCESS); + + PROFILE_FN(RTFileFlush(hFile1), g_nsTestRun, "RTFileFlush"); + + size_t cbBuf = PAGE_SIZE; + uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf); + RTTESTI_CHECK_RETV(pbBuf != NULL); + memset(pbBuf, 0xf4, cbBuf); + + RTTESTI_CHECK_RC(RTFileSeek(hFile1, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + uint64_t offFile = 0; + PROFILE_FN(fsPerfFSyncWorker(hFile1, cbFile, pbBuf, cbBuf, &offFile), g_nsTestRun, "RTFileWrite[Page]/RTFileFlush"); + + RTMemPageFree(pbBuf, cbBuf); +} + + +#ifndef RT_OS_OS2 +/** + * Worker for profiling msync. + */ +DECL_FORCE_INLINE(int) fsPerfMSyncWorker(uint8_t *pbMapping, size_t offMapping, size_t cbFlush, size_t *pcbFlushed) +{ + uint8_t *pbCur = &pbMapping[offMapping]; + for (size_t offFlush = 0; offFlush < cbFlush; offFlush += PAGE_SIZE) + *(size_t volatile *)&pbCur[offFlush + 8] = cbFlush; +# ifdef RT_OS_WINDOWS + CHECK_WINAPI_CALL(FlushViewOfFile(pbCur, cbFlush) == TRUE); +# else + RTTESTI_CHECK(msync(pbCur, cbFlush, MS_SYNC) == 0); +# endif + if (*pcbFlushed < offMapping + cbFlush) + *pcbFlushed = offMapping + cbFlush; + return VINF_SUCCESS; +} +#endif /* !RT_OS_OS2 */ + + +void fsPerfMMap(RTFILE hFile1, RTFILE hFileNoCache, uint64_t cbFile) +{ + RTTestISub("mmap"); +#if !defined(RT_OS_OS2) + static const char * const s_apszStates[] = { "readonly", "writecopy", "readwrite" }; + enum { kMMap_ReadOnly = 0, kMMap_WriteCopy, kMMap_ReadWrite, kMMap_End }; + for (int enmState = kMMap_ReadOnly; enmState < kMMap_End; enmState++) + { + /* + * Do the mapping. + */ + size_t cbMapping = (size_t)cbFile; + if (cbMapping != cbFile) + cbMapping = _256M; + uint8_t *pbMapping; + +# ifdef RT_OS_WINDOWS + HANDLE hSection; + pbMapping = NULL; + for (;; cbMapping /= 2) + { + hSection = CreateFileMapping((HANDLE)RTFileToNative(hFile1), NULL, + enmState == kMMap_ReadOnly ? PAGE_READONLY + : enmState == kMMap_WriteCopy ? PAGE_WRITECOPY : PAGE_READWRITE, + (uint32_t)((uint64_t)cbMapping >> 32), (uint32_t)cbMapping, NULL); + DWORD dwErr1 = GetLastError(); + DWORD dwErr2 = 0; + if (hSection != NULL) + { + pbMapping = (uint8_t *)MapViewOfFile(hSection, + enmState == kMMap_ReadOnly ? FILE_MAP_READ + : enmState == kMMap_WriteCopy ? FILE_MAP_COPY + : FILE_MAP_WRITE, + 0, 0, cbMapping); + if (pbMapping) + break; + dwErr2 = GetLastError(); + CHECK_WINAPI_CALL(CloseHandle(hSection) == TRUE); + } + if (cbMapping <= _2M) + { + RTTestIFailed("%u/%s: CreateFileMapping or MapViewOfFile failed: %u, %u", + enmState, s_apszStates[enmState], dwErr1, dwErr2); + break; + } + } +# else + for (;; cbMapping /= 2) + { + pbMapping = (uint8_t *)mmap(NULL, cbMapping, + enmState == kMMap_ReadOnly ? PROT_READ : PROT_READ | PROT_WRITE, + enmState == kMMap_WriteCopy ? MAP_PRIVATE : MAP_SHARED, + (int)RTFileToNative(hFile1), 0); + if ((void *)pbMapping != MAP_FAILED) + break; + if (cbMapping <= _2M) + { + RTTestIFailed("%u/%s: mmap failed: %s (%u)", enmState, s_apszStates[enmState], strerror(errno), errno); + break; + } + } +# endif + if (cbMapping <= _2M) + continue; + + /* + * Time page-ins just for fun. + */ + size_t const cPages = cbMapping >> PAGE_SHIFT; + size_t uDummy = 0; + uint64_t ns = RTTimeNanoTS(); + for (size_t iPage = 0; iPage < cPages; iPage++) + uDummy += ASMAtomicReadU8(&pbMapping[iPage << PAGE_SHIFT]); + ns = RTTimeNanoTS() - ns; + RTTestIValueF(ns / cPages, RTTESTUNIT_NS_PER_OCCURRENCE, "page-in %s", s_apszStates[enmState]); + + /* Check the content. */ + fsPerfCheckReadBuf(__LINE__, 0, pbMapping, cbMapping); + + if (enmState != kMMap_ReadOnly) + { + /* Write stuff to the first two megabytes. In the COW case, we'll detect + corruption of shared data during content checking of the RW iterations. */ + fsPerfFillWriteBuf(0, pbMapping, _2M, 0xf7); + if (enmState == kMMap_ReadWrite && g_fMMapCoherency) + { + /* For RW we can try read back from the file handle and check if we get + a match there first. */ + uint8_t abBuf[_4K]; + for (uint32_t off = 0; off < _2M; off += sizeof(abBuf)) + { + RTTESTI_CHECK_RC(RTFileReadAt(hFile1, off, abBuf, sizeof(abBuf), NULL), VINF_SUCCESS); + fsPerfCheckReadBuf(__LINE__, off, abBuf, sizeof(abBuf), 0xf7); + } +# ifdef RT_OS_WINDOWS + CHECK_WINAPI_CALL(FlushViewOfFile(pbMapping, _2M) == TRUE); +# else + RTTESTI_CHECK(msync(pbMapping, _2M, MS_SYNC) == 0); +# endif + } + + /* + * Time modifying and flushing a few different number of pages. + */ + if (enmState == kMMap_ReadWrite) + { + static size_t const s_acbFlush[] = { PAGE_SIZE, PAGE_SIZE * 2, PAGE_SIZE * 3, PAGE_SIZE * 8, PAGE_SIZE * 16, _2M }; + for (unsigned iFlushSize = 0 ; iFlushSize < RT_ELEMENTS(s_acbFlush); iFlushSize++) + { + size_t const cbFlush = s_acbFlush[iFlushSize]; + if (cbFlush > cbMapping) + continue; + + char szDesc[80]; + RTStrPrintf(szDesc, sizeof(szDesc), "touch/flush/%zu", cbFlush); + size_t const cFlushes = cbMapping / cbFlush; + size_t const cbMappingUsed = cFlushes * cbFlush; + size_t cbFlushed = 0; + PROFILE_FN(fsPerfMSyncWorker(pbMapping, (iIteration * cbFlush) % cbMappingUsed, cbFlush, &cbFlushed), + g_nsTestRun, szDesc); + + /* + * Check that all the changes made it thru to the file: + */ + if (!g_fIgnoreNoCache || hFileNoCache != NIL_RTFILE) + { + size_t cbBuf = RT_MIN(_2M, g_cbMaxBuffer); + uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf); + if (!pbBuf) + { + cbBuf = _4K; + pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf); + } + RTTESTI_CHECK(pbBuf != NULL); + if (pbBuf) + { + RTTESTI_CHECK_RC(RTFileSeek(hFileNoCache, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + size_t const cbToCheck = RT_MIN(cFlushes * cbFlush, cbFlushed); + unsigned cErrors = 0; + for (size_t offBuf = 0; cErrors < 32 && offBuf < cbToCheck; offBuf += cbBuf) + { + size_t cbToRead = RT_MIN(cbBuf, cbToCheck - offBuf); + RTTESTI_CHECK_RC(RTFileRead(hFileNoCache, pbBuf, cbToRead, NULL), VINF_SUCCESS); + + for (size_t offFlush = 0; offFlush < cbToRead; offFlush += PAGE_SIZE) + if (*(size_t volatile *)&pbBuf[offFlush + 8] != cbFlush) + { + RTTestIFailed("Flush issue at offset #%zx: %#zx, expected %#zx (cbFlush=%#zx, %#RX64)", + offBuf + offFlush + 8, *(size_t volatile *)&pbBuf[offFlush + 8], + cbFlush, cbFlush, *(uint64_t volatile *)&pbBuf[offFlush]); + if (++cErrors > 32) + break; + } + } + RTMemPageFree(pbBuf, cbBuf); + } + } + } + +# if 0 /* not needed, very very slow */ + /* + * Restore the file to 0xf6 state for the next test. + */ + RTTestIPrintf(RTTESTLVL_ALWAYS, "Restoring content...\n"); + fsPerfFillWriteBuf(0, pbMapping, cbMapping, 0xf6); +# ifdef RT_OS_WINDOWS + CHECK_WINAPI_CALL(FlushViewOfFile(pbMapping, cbMapping) == TRUE); +# else + RTTESTI_CHECK(msync(pbMapping, cbMapping, MS_SYNC) == 0); +# endif + RTTestIPrintf(RTTESTLVL_ALWAYS, "... done\n"); +# endif + } + } + + /* + * Observe how regular writes affects a read-only or readwrite mapping. + * These should ideally be immediately visible in the mapping, at least + * when not performed thru an no-cache handle. + */ + if ( (enmState == kMMap_ReadOnly || enmState == kMMap_ReadWrite) + && g_fMMapCoherency) + { + size_t cbBuf = RT_MIN(RT_MIN(_2M, cbMapping / 2), g_cbMaxBuffer); + uint8_t *pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf); + if (!pbBuf) + { + cbBuf = _4K; + pbBuf = (uint8_t *)RTMemPageAlloc(cbBuf); + } + RTTESTI_CHECK(pbBuf != NULL); + if (pbBuf) + { + /* Do a number of random writes to the file (using hFile1). + Immediately undoing them. */ + for (uint32_t i = 0; i < 128; i++) + { + /* Generate a randomly sized write at a random location, making + sure it differs from whatever is there already before writing. */ + uint32_t const cbToWrite = RTRandU32Ex(1, (uint32_t)cbBuf); + uint64_t const offToWrite = RTRandU64Ex(0, cbMapping - cbToWrite); + + fsPerfFillWriteBuf(offToWrite, pbBuf, cbToWrite, 0xf8); + pbBuf[0] = ~pbBuf[0]; + if (cbToWrite > 1) + pbBuf[cbToWrite - 1] = ~pbBuf[cbToWrite - 1]; + RTTESTI_CHECK_RC(RTFileWriteAt(hFile1, offToWrite, pbBuf, cbToWrite, NULL), VINF_SUCCESS); + + /* Check the mapping. */ + if (memcmp(&pbMapping[(size_t)offToWrite], pbBuf, cbToWrite) != 0) + { + RTTestIFailed("Write #%u @ %#RX64 LB %#x was not reflected in the mapping!\n", i, offToWrite, cbToWrite); + } + + /* Restore */ + fsPerfFillWriteBuf(offToWrite, pbBuf, cbToWrite, 0xf6); + RTTESTI_CHECK_RC(RTFileWriteAt(hFile1, offToWrite, pbBuf, cbToWrite, NULL), VINF_SUCCESS); + } + + RTMemPageFree(pbBuf, cbBuf); + } + } + + /* + * Unmap it. + */ +# ifdef RT_OS_WINDOWS + CHECK_WINAPI_CALL(UnmapViewOfFile(pbMapping) == TRUE); + CHECK_WINAPI_CALL(CloseHandle(hSection) == TRUE); +# else + RTTESTI_CHECK(munmap(pbMapping, cbMapping) == 0); +# endif + } + + /* + * Memory mappings without open handles (pretty common). + */ + for (uint32_t i = 0; i < 32; i++) + { + /* Create a new file, 256 KB in size, and fill it with random bytes. + Try uncached access if we can to force the page-in to do actual reads. */ + char szFile2[FSPERF_MAX_PATH + 32]; + memcpy(szFile2, g_szDir, g_cchDir); + RTStrPrintf(&szFile2[g_cchDir], sizeof(szFile2) - g_cchDir, "mmap-%u.noh", i); + RTFILE hFile2 = NIL_RTFILE; + int rc = (i & 3) == 3 ? VERR_TRY_AGAIN + : RTFileOpen(&hFile2, szFile2, RTFILE_O_READWRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_NO_CACHE); + if (RT_FAILURE(rc)) + { + RTTESTI_CHECK_RC_BREAK(RTFileOpen(&hFile2, szFile2, RTFILE_O_READWRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE), + VINF_SUCCESS); + } + + static char s_abContentUnaligned[256*1024 + PAGE_SIZE - 1]; + char * const pbContent = &s_abContentUnaligned[PAGE_SIZE - ((uintptr_t)&s_abContentUnaligned[0] & PAGE_OFFSET_MASK)]; + size_t const cbContent = 256*1024; + RTRandBytes(pbContent, cbContent); + RTTESTI_CHECK_RC(rc = RTFileWrite(hFile2, pbContent, cbContent, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + /* Reopen the file with normal caching. Every second time, we also + does a read-only open of it to confuse matters. */ + RTFILE hFile3 = NIL_RTFILE; + if ((i & 3) == 3) + RTTESTI_CHECK_RC(RTFileOpen(&hFile3, szFile2, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE), VINF_SUCCESS); + hFile2 = NIL_RTFILE; + RTTESTI_CHECK_RC_BREAK(RTFileOpen(&hFile2, szFile2, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE), + VINF_SUCCESS); + if ((i & 3) == 1) + RTTESTI_CHECK_RC(RTFileOpen(&hFile3, szFile2, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE), VINF_SUCCESS); + + /* Memory map it read-write (no COW). */ +#ifdef RT_OS_WINDOWS + HANDLE hSection = CreateFileMapping((HANDLE)RTFileToNative(hFile2), NULL, PAGE_READWRITE, 0, cbContent, NULL); + CHECK_WINAPI_CALL(hSection != NULL); + uint8_t *pbMapping = (uint8_t *)MapViewOfFile(hSection, FILE_MAP_WRITE, 0, 0, cbContent); + CHECK_WINAPI_CALL(pbMapping != NULL); + CHECK_WINAPI_CALL(CloseHandle(hSection) == TRUE); +# else + uint8_t *pbMapping = (uint8_t *)mmap(NULL, cbContent, PROT_READ | PROT_WRITE, MAP_SHARED, + (int)RTFileToNative(hFile2), 0); + if ((void *)pbMapping == MAP_FAILED) + pbMapping = NULL; + RTTESTI_CHECK_MSG(pbMapping != NULL, ("errno=%s (%d)\n", strerror(errno), errno)); +# endif + + /* Close the file handles. */ + if ((i & 7) == 7) + { + RTTESTI_CHECK_RC(RTFileClose(hFile3), VINF_SUCCESS); + hFile3 = NIL_RTFILE; + } + RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS); + if ((i & 7) == 5) + { + RTTESTI_CHECK_RC(RTFileClose(hFile3), VINF_SUCCESS); + hFile3 = NIL_RTFILE; + } + if (pbMapping) + { + RTThreadSleep(2); /* fudge for cleanup/whatever */ + + /* Page in the mapping by comparing with the content we wrote above. */ + RTTESTI_CHECK(memcmp(pbMapping, pbContent, cbContent) == 0); + + /* Now dirty everything by inverting everything. */ + size_t *puCur = (size_t *)pbMapping; + size_t cLeft = cbContent / sizeof(*puCur); + while (cLeft-- > 0) + { + *puCur = ~*puCur; + puCur++; + } + + /* Sync it all. */ +# ifdef RT_OS_WINDOWS + //CHECK_WINAPI_CALL(FlushViewOfFile(pbMapping, cbContent) == TRUE); + SetLastError(0); + if (FlushViewOfFile(pbMapping, cbContent) != TRUE) + RTTestIFailed("line %u, i=%u: FlushViewOfFile(%p, %#zx) failed: %u / %#x", __LINE__, i, + pbMapping, cbContent, GetLastError(), RTNtLastStatusValue()); +# else + RTTESTI_CHECK(msync(pbMapping, cbContent, MS_SYNC) == 0); +# endif + + /* Unmap it. */ +# ifdef RT_OS_WINDOWS + CHECK_WINAPI_CALL(UnmapViewOfFile(pbMapping) == TRUE); +# else + RTTESTI_CHECK(munmap(pbMapping, cbContent) == 0); +# endif + } + + if (hFile3 != NIL_RTFILE) + RTTESTI_CHECK_RC(RTFileClose(hFile3), VINF_SUCCESS); + } + RTTESTI_CHECK_RC(RTFileDelete(szFile2), VINF_SUCCESS); + } + + +#else + RTTestSkipped(g_hTest, "not supported/implemented"); + RT_NOREF(hFile1, hFileNoCache, cbFile); +#endif +} + + +/** + * This does the read, write and seek tests. + */ +void fsPerfIo(void) +{ + RTTestISub("I/O"); + + /* + * Determin the size of the test file. + */ + g_szDir[g_cchDir] = '\0'; + RTFOFF cbFree = 0; + RTTESTI_CHECK_RC_RETV(RTFsQuerySizes(g_szDir, NULL, &cbFree, NULL, NULL), VINF_SUCCESS); + uint64_t cbFile = g_cbIoFile; + if (cbFile + _16M < (uint64_t)cbFree) + cbFile = RT_ALIGN_64(cbFile, _64K); + else if (cbFree < _32M) + { + RTTestSkipped(g_hTest, "Insufficent free space: %'RU64 bytes, requires >= 32MB", cbFree); + return; + } + else + { + cbFile = cbFree - (cbFree > _128M ? _64M : _16M); + cbFile = RT_ALIGN_64(cbFile, _64K); + RTTestIPrintf(RTTESTLVL_ALWAYS, "Adjusted file size to %'RU64 bytes, due to %'RU64 bytes free.\n", cbFile, cbFree); + } + if (cbFile < _64K) + { + RTTestSkipped(g_hTest, "Specified test file size too small: %'RU64 bytes, requires >= 64KB", cbFile); + return; + } + + /* + * Create a cbFile sized test file. + */ + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file21")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE), VINF_SUCCESS); + RTFILE hFileNoCache; + if (!g_fIgnoreNoCache) + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFileNoCache, g_szDir, + RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE | RTFILE_O_NO_CACHE), + VINF_SUCCESS); + else + { + int rc = RTFileOpen(&hFileNoCache, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE | RTFILE_O_NO_CACHE); + if (RT_FAILURE(rc)) + { + RTTestIPrintf(RTTESTLVL_ALWAYS, "Unable to open I/O file with non-cache flag (%Rrc), skipping related tests.\n", rc); + hFileNoCache = NIL_RTFILE; + } + } + RTFILE hFileWriteThru; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFileWriteThru, g_szDir, + RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE | RTFILE_O_WRITE_THROUGH), + VINF_SUCCESS); + + uint8_t *pbFree = NULL; + int rc = fsPerfIoPrepFile(hFile1, cbFile, &pbFree); + RTMemFree(pbFree); + if (RT_SUCCESS(rc)) + { + /* + * Do the testing & profiling. + */ + if (g_fSeek) + fsPerfIoSeek(hFile1, cbFile); + + if (g_fMMap && g_iMMapPlacement < 0) + { + fsPerfMMap(hFile1, hFileNoCache, cbFile); + fsPerfReinitFile(hFile1, cbFile); + } + + if (g_fReadTests) + fsPerfRead(hFile1, hFileNoCache, cbFile); + if (g_fReadPerf) + for (unsigned i = 0; i < g_cIoBlocks; i++) + fsPerfIoReadBlockSize(hFile1, cbFile, g_acbIoBlocks[i]); +#ifdef FSPERF_TEST_SENDFILE + if (g_fSendFile) + fsPerfSendFile(hFile1, cbFile); +#endif +#ifdef RT_OS_LINUX + if (g_fSplice) + fsPerfSpliceToPipe(hFile1, cbFile); +#endif + if (g_fMMap && g_iMMapPlacement == 0) + fsPerfMMap(hFile1, hFileNoCache, cbFile); + + /* This is destructive to the file content. */ + if (g_fWriteTests) + fsPerfWrite(hFile1, hFileNoCache, hFileWriteThru, cbFile); + if (g_fWritePerf) + for (unsigned i = 0; i < g_cIoBlocks; i++) + fsPerfIoWriteBlockSize(hFile1, cbFile, g_acbIoBlocks[i]); +#ifdef RT_OS_LINUX + if (g_fSplice) + fsPerfSpliceToFile(hFile1, cbFile); +#endif + if (g_fFSync) + fsPerfFSync(hFile1, cbFile); + + if (g_fMMap && g_iMMapPlacement > 0) + { + fsPerfReinitFile(hFile1, cbFile); + fsPerfMMap(hFile1, hFileNoCache, cbFile); + } + } + + RTTESTI_CHECK_RC(RTFileSetSize(hFile1, 0), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + if (hFileNoCache != NIL_RTFILE || !g_fIgnoreNoCache) + RTTESTI_CHECK_RC(RTFileClose(hFileNoCache), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFileWriteThru), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VINF_SUCCESS); +} + + +DECL_FORCE_INLINE(int) fsPerfCopyWorker1(const char *pszSrc, const char *pszDst) +{ + RTFileDelete(pszDst); + return RTFileCopy(pszSrc, pszDst); +} + + +#ifdef RT_OS_LINUX +DECL_FORCE_INLINE(int) fsPerfCopyWorkerSendFile(RTFILE hFile1, RTFILE hFile2, size_t cbFile) +{ + RTTESTI_CHECK_RC_RET(RTFileSeek(hFile2, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS, rcCheck); + + loff_t off = 0; + ssize_t cbSent = sendfile((int)RTFileToNative(hFile2), (int)RTFileToNative(hFile1), &off, cbFile); + if (cbSent > 0 && (size_t)cbSent == cbFile) + return 0; + + int rc = VERR_GENERAL_FAILURE; + if (cbSent < 0) + { + rc = RTErrConvertFromErrno(errno); + RTTestIFailed("sendfile(file,file,NULL,%#zx) failed (%zd): %d (%Rrc)", cbFile, cbSent, errno, rc); + } + else + RTTestIFailed("sendfile(file,file,NULL,%#zx) returned %#zx, expected %#zx (diff %zd)", + cbFile, cbSent, cbFile, cbSent - cbFile); + return rc; +} +#endif /* RT_OS_LINUX */ + + +static void fsPerfCopy(void) +{ + RTTestISub("copy"); + + /* + * Non-existing files. + */ + RTTESTI_CHECK_RC(RTFileCopy(InEmptyDir(RT_STR_TUPLE("no-such-file")), + InDir2(RT_STR_TUPLE("whatever"))), VERR_FILE_NOT_FOUND); + RTTESTI_CHECK_RC(RTFileCopy(InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file")), + InDir2(RT_STR_TUPLE("no-such-file"))), FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTFileCopy(InDir(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file")), + InDir2(RT_STR_TUPLE("whatever"))), VERR_PATH_NOT_FOUND); + + RTTESTI_CHECK_RC(RTFileCopy(InDir(RT_STR_TUPLE("known-file")), + InEmptyDir(RT_STR_TUPLE("no-such-dir" RTPATH_SLASH_STR "no-such-file"))), FSPERF_VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC(RTFileCopy(InDir(RT_STR_TUPLE("known-file")), + InDir2(RT_STR_TUPLE("known-file" RTPATH_SLASH_STR "no-such-file"))), VERR_PATH_NOT_FOUND); + + /* + * Determin the size of the test file. + * We want to be able to make 1 copy of it. + */ + g_szDir[g_cchDir] = '\0'; + RTFOFF cbFree = 0; + RTTESTI_CHECK_RC_RETV(RTFsQuerySizes(g_szDir, NULL, &cbFree, NULL, NULL), VINF_SUCCESS); + uint64_t cbFile = g_cbIoFile; + if (cbFile + _16M < (uint64_t)cbFree) + cbFile = RT_ALIGN_64(cbFile, _64K); + else if (cbFree < _32M) + { + RTTestSkipped(g_hTest, "Insufficent free space: %'RU64 bytes, requires >= 32MB", cbFree); + return; + } + else + { + cbFile = cbFree - (cbFree > _128M ? _64M : _16M); + cbFile = RT_ALIGN_64(cbFile, _64K); + RTTestIPrintf(RTTESTLVL_ALWAYS, "Adjusted file size to %'RU64 bytes, due to %'RU64 bytes free.\n", cbFile, cbFree); + } + if (cbFile < _512K * 2) + { + RTTestSkipped(g_hTest, "Specified test file size too small: %'RU64 bytes, requires >= 1MB", cbFile); + return; + } + cbFile /= 2; + + /* + * Create a cbFile sized test file. + */ + RTFILE hFile1; + RTTESTI_CHECK_RC_RETV(RTFileOpen(&hFile1, InDir(RT_STR_TUPLE("file22")), + RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE), VINF_SUCCESS); + uint8_t *pbFree = NULL; + int rc = fsPerfIoPrepFile(hFile1, cbFile, &pbFree); + RTMemFree(pbFree); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + /* + * Make copies. + */ + /* plain */ + RTFileDelete(InDir2(RT_STR_TUPLE("file23"))); + RTTESTI_CHECK_RC(RTFileCopy(g_szDir, g_szDir2), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileCopy(g_szDir, g_szDir2), VERR_ALREADY_EXISTS); + RTTESTI_CHECK_RC(RTFileCompare(g_szDir, g_szDir2), VINF_SUCCESS); + + /* by handle */ + hFile1 = NIL_RTFILE; + RTTESTI_CHECK_RC(RTFileOpen(&hFile1, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS); + RTFILE hFile2 = NIL_RTFILE; + RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileCopyByHandles(hFile1, hFile2), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileCompare(g_szDir, g_szDir2), VINF_SUCCESS); + + /* copy part */ + hFile1 = NIL_RTFILE; + RTTESTI_CHECK_RC(RTFileOpen(&hFile1, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS); + hFile2 = NIL_RTFILE; + RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileCopyPart(hFile1, 0, hFile2, 0, cbFile / 2, 0, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileCopyPart(hFile1, cbFile / 2, hFile2, cbFile / 2, cbFile - cbFile / 2, 0, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileCompare(g_szDir, g_szDir2), VINF_SUCCESS); + +#ifdef RT_OS_LINUX + /* + * On linux we can also use sendfile between two files, except for 2.5.x to 2.6.33. + */ + uint64_t const cbFileMax = RT_MIN(cbFile, UINT32_C(0x7ffff000)); + char szRelease[64]; + RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szRelease, sizeof(szRelease)); + bool const fSendFileBetweenFiles = RTStrVersionCompare(szRelease, "2.5.0") < 0 + || RTStrVersionCompare(szRelease, "2.6.33") >= 0; + if (fSendFileBetweenFiles) + { + /* Copy the whole file: */ + hFile1 = NIL_RTFILE; + RTTESTI_CHECK_RC(RTFileOpen(&hFile1, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS); + RTFileDelete(g_szDir2); + hFile2 = NIL_RTFILE; + RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + ssize_t cbSent = sendfile((int)RTFileToNative(hFile2), (int)RTFileToNative(hFile1), NULL, cbFile); + if (cbSent < 0) + RTTestIFailed("sendfile(file,file,NULL,%#zx) failed (%zd): %d (%Rrc)", + cbFile, cbSent, errno, RTErrConvertFromErrno(errno)); + else if ((size_t)cbSent != cbFileMax) + RTTestIFailed("sendfile(file,file,NULL,%#zx) returned %#zx, expected %#zx (diff %zd)", + cbFile, cbSent, cbFileMax, cbSent - cbFileMax); + RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileCompare(g_szDir, g_szDir2), VINF_SUCCESS); + + /* Try copy a little bit too much: */ + if (cbFile == cbFileMax) + { + hFile1 = NIL_RTFILE; + RTTESTI_CHECK_RC(RTFileOpen(&hFile1, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS); + RTFileDelete(g_szDir2); + hFile2 = NIL_RTFILE; + RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + size_t cbToCopy = cbFile + RTRandU32Ex(1, _64M); + cbSent = sendfile((int)RTFileToNative(hFile2), (int)RTFileToNative(hFile1), NULL, cbToCopy); + if (cbSent < 0) + RTTestIFailed("sendfile(file,file,NULL,%#zx) failed (%zd): %d (%Rrc)", + cbToCopy, cbSent, errno, RTErrConvertFromErrno(errno)); + else if ((size_t)cbSent != cbFile) + RTTestIFailed("sendfile(file,file,NULL,%#zx) returned %#zx, expected %#zx (diff %zd)", + cbToCopy, cbSent, cbFile, cbSent - cbFile); + RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileCompare(g_szDir, g_szDir2), VINF_SUCCESS); + } + + /* Do partial copy: */ + hFile2 = NIL_RTFILE; + RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + for (uint32_t i = 0; i < 64; i++) + { + size_t cbToCopy = RTRandU32Ex(0, cbFileMax - 1); + uint32_t const offFile = RTRandU32Ex(1, (uint64_t)RT_MIN(cbFileMax - cbToCopy, UINT32_MAX)); + RTTESTI_CHECK_RC_BREAK(RTFileSeek(hFile2, offFile, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + loff_t offFile2 = offFile; + cbSent = sendfile((int)RTFileToNative(hFile2), (int)RTFileToNative(hFile1), &offFile2, cbToCopy); + if (cbSent < 0) + RTTestIFailed("sendfile(file,file,%#x,%#zx) failed (%zd): %d (%Rrc)", + offFile, cbToCopy, cbSent, errno, RTErrConvertFromErrno(errno)); + else if ((size_t)cbSent != cbToCopy) + RTTestIFailed("sendfile(file,file,%#x,%#zx) returned %#zx, expected %#zx (diff %zd)", + offFile, cbToCopy, cbSent, cbToCopy, cbSent - cbToCopy); + else if (offFile2 != (loff_t)(offFile + cbToCopy)) + RTTestIFailed("sendfile(file,file,%#x,%#zx) returned %#zx + off=%#RX64, expected off %#x", + offFile, cbToCopy, cbSent, offFile2, offFile + cbToCopy); + } + RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileCompare(g_szDir, g_szDir2), VINF_SUCCESS); + } +#endif + + /* + * Do some benchmarking. + */ +#define PROFILE_COPY_FN(a_szOperation, a_fnCall) \ + do \ + { \ + /* Estimate how many iterations we need to fill up the given timeslot: */ \ + fsPerfYield(); \ + uint64_t nsStart = RTTimeNanoTS(); \ + uint64_t ns; \ + do \ + ns = RTTimeNanoTS(); \ + while (ns == nsStart); \ + nsStart = ns; \ + \ + uint64_t iIteration = 0; \ + do \ + { \ + RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \ + iIteration++; \ + ns = RTTimeNanoTS() - nsStart; \ + } while (ns < RT_NS_10MS); \ + ns /= iIteration; \ + if (ns > g_nsPerNanoTSCall + 32) \ + ns -= g_nsPerNanoTSCall; \ + uint64_t cIterations = g_nsTestRun / ns; \ + if (cIterations < 2) \ + cIterations = 2; \ + else if (cIterations & 1) \ + cIterations++; \ + \ + /* Do the actual profiling: */ \ + iIteration = 0; \ + fsPerfYield(); \ + nsStart = RTTimeNanoTS(); \ + for (uint32_t iAdjust = 0; iAdjust < 4; iAdjust++) \ + { \ + for (; iIteration < cIterations; iIteration++)\ + RTTESTI_CHECK_RC(a_fnCall, VINF_SUCCESS); \ + ns = RTTimeNanoTS() - nsStart;\ + if (ns >= g_nsTestRun - (g_nsTestRun / 10)) \ + break; \ + cIterations += cIterations / 4; \ + if (cIterations & 1) \ + cIterations++; \ + nsStart += g_nsPerNanoTSCall; \ + } \ + RTTestIValueF(ns / iIteration, \ + RTTESTUNIT_NS_PER_OCCURRENCE, a_szOperation " latency"); \ + RTTestIValueF((uint64_t)((double)(iIteration * cbFile) / ((double)ns / RT_NS_1SEC)), \ + RTTESTUNIT_BYTES_PER_SEC, a_szOperation " throughput"); \ + RTTestIValueF((uint64_t)iIteration * cbFile, \ + RTTESTUNIT_BYTES, a_szOperation " bytes"); \ + RTTestIValueF(iIteration, \ + RTTESTUNIT_OCCURRENCES, a_szOperation " iterations"); \ + if (g_fShowDuration) \ + RTTestIValueF(ns, RTTESTUNIT_NS, a_szOperation " duration"); \ + } while (0) + + PROFILE_COPY_FN("RTFileCopy/Replace", fsPerfCopyWorker1(g_szDir, g_szDir2)); + + hFile1 = NIL_RTFILE; + RTTESTI_CHECK_RC(RTFileOpen(&hFile1, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS); + RTFileDelete(g_szDir2); + hFile2 = NIL_RTFILE; + RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + PROFILE_COPY_FN("RTFileCopyByHandles/Overwrite", RTFileCopyByHandles(hFile1, hFile2)); + RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + /* We could benchmark RTFileCopyPart with various block sizes and whatnot... + But it's currently well covered by the two previous operations. */ + +#ifdef RT_OS_LINUX + if (fSendFileBetweenFiles) + { + hFile1 = NIL_RTFILE; + RTTESTI_CHECK_RC(RTFileOpen(&hFile1, g_szDir, RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_READ), VINF_SUCCESS); + RTFileDelete(g_szDir2); + hFile2 = NIL_RTFILE; + RTTESTI_CHECK_RC(RTFileOpen(&hFile2, g_szDir2, RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_WRITE), VINF_SUCCESS); + PROFILE_COPY_FN("sendfile/overwrite", fsPerfCopyWorkerSendFile(hFile1, hFile2, cbFileMax)); + RTTESTI_CHECK_RC(RTFileClose(hFile2), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + } +#endif + } + + /* + * Clean up. + */ + RTFileDelete(InDir2(RT_STR_TUPLE("file22c1"))); + RTFileDelete(InDir2(RT_STR_TUPLE("file22c2"))); + RTFileDelete(InDir2(RT_STR_TUPLE("file22c3"))); + RTTESTI_CHECK_RC(RTFileDelete(g_szDir), VINF_SUCCESS); +} + + +static void fsPerfRemote(void) +{ + RTTestISub("remote"); + uint8_t abBuf[16384]; + + + /* + * Create a file on the remote end and check that we can immediately see it. + */ + RTTESTI_CHECK_RC_RETV(FsPerfCommsSend("reset\n" + "open 0 'file30' 'w' 'ca'\n" + "writepattern 0 0 0 4096" FSPERF_EOF_STR), VINF_SUCCESS); + + RTFILEACTION enmActuallyTaken = RTFILEACTION_END; + RTFILE hFile0 = NIL_RTFILE; + RTTESTI_CHECK_RC(RTFileOpenEx(InDir(RT_STR_TUPLE("file30")), RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, + &hFile0, &enmActuallyTaken), VINF_SUCCESS); + RTTESTI_CHECK(enmActuallyTaken == RTFILEACTION_OPENED); + RTTESTI_CHECK_RC(RTFileRead(hFile0, abBuf, 4096, NULL), VINF_SUCCESS); + AssertCompile(RT_ELEMENTS(g_abPattern0) == 1); + RTTESTI_CHECK(ASMMemIsAllU8(abBuf, 4096, g_abPattern0[0])); + RTTESTI_CHECK_RC(RTFileRead(hFile0, abBuf, 1, NULL), VERR_EOF); + + /* + * Append a little to it on the host and see that we can read it. + */ + RTTESTI_CHECK_RC(FsPerfCommsSend("writepattern 0 4096 1 1024" FSPERF_EOF_STR), VINF_SUCCESS); + AssertCompile(RT_ELEMENTS(g_abPattern1) == 1); + RTTESTI_CHECK_RC(RTFileRead(hFile0, abBuf, 1024, NULL), VINF_SUCCESS); + RTTESTI_CHECK(ASMMemIsAllU8(abBuf, 1024, g_abPattern1[0])); + RTTESTI_CHECK_RC(RTFileRead(hFile0, abBuf, 1, NULL), VERR_EOF); + + /* + * Have the host truncate the file. + */ + RTTESTI_CHECK_RC(FsPerfCommsSend("truncate 0 1024" FSPERF_EOF_STR), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileRead(hFile0, abBuf, 1, NULL), VERR_EOF); + RTTESTI_CHECK_RC(RTFileSeek(hFile0, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileRead(hFile0, abBuf, 1024, NULL), VINF_SUCCESS); + AssertCompile(RT_ELEMENTS(g_abPattern0) == 1); + RTTESTI_CHECK(ASMMemIsAllU8(abBuf, 4096, g_abPattern0[0])); + RTTESTI_CHECK_RC(RTFileRead(hFile0, abBuf, 1, NULL), VERR_EOF); + + /* + * Write a bunch of stuff to the file here, then truncate it to a given size, + * then have the host add more, finally test that we can successfully chop off + * what the host added by reissuing the same truncate call as before (issue of + * RDBSS using cached size to noop out set-eof-to-same-size). + */ + memset(abBuf, 0xe9, sizeof(abBuf)); + RTTESTI_CHECK_RC(RTFileSeek(hFile0, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileWrite(hFile0, abBuf, 16384, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileSetSize(hFile0, 8000), VINF_SUCCESS); + RTTESTI_CHECK_RC(FsPerfCommsSend("writepattern 0 8000 0 1000" FSPERF_EOF_STR), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileSetSize(hFile0, 8000), VINF_SUCCESS); + uint64_t cbFile = 0; + RTTESTI_CHECK_RC(RTFileQuerySize(hFile0, &cbFile), VINF_SUCCESS); + RTTESTI_CHECK_MSG(cbFile == 8000, ("cbFile=%u\n", cbFile)); + RTTESTI_CHECK_RC(RTFileRead(hFile0, abBuf, 1, NULL), VERR_EOF); + + /* Same, but using RTFileRead to find out and RTFileWrite to define the size. */ + RTTESTI_CHECK_RC(RTFileSeek(hFile0, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileSetSize(hFile0, 0), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileWrite(hFile0, abBuf, 5000, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(FsPerfCommsSend("writepattern 0 5000 0 1000" FSPERF_EOF_STR), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileSetSize(hFile0, 5000), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileRead(hFile0, abBuf, 1, NULL), VERR_EOF); + RTTESTI_CHECK_RC(RTFileQuerySize(hFile0, &cbFile), VINF_SUCCESS); + RTTESTI_CHECK_MSG(cbFile == 5000, ("cbFile=%u\n", cbFile)); + + /* Same, but host truncates rather than adding stuff. */ + RTTESTI_CHECK_RC(RTFileSeek(hFile0, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileWrite(hFile0, abBuf, 16384, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileSetSize(hFile0, 10000), VINF_SUCCESS); + RTTESTI_CHECK_RC(FsPerfCommsSend("truncate 0 4000" FSPERF_EOF_STR), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileQuerySize(hFile0, &cbFile), VINF_SUCCESS); + RTTESTI_CHECK_MSG(cbFile == 4000, ("cbFile=%u\n", cbFile)); + RTTESTI_CHECK_RC(RTFileRead(hFile0, abBuf, 1, NULL), VERR_EOF); + + /* + * Test noticing remote size changes when opening a file. Need to keep hFile0 + * open here so we're sure to have an inode/FCB for the file in question. + */ + memset(abBuf, 0xe7, sizeof(abBuf)); + RTTESTI_CHECK_RC(RTFileSeek(hFile0, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileSetSize(hFile0, 0), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileWrite(hFile0, abBuf, 12288, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileSetSize(hFile0, 12288), VINF_SUCCESS); + + RTTESTI_CHECK_RC(FsPerfCommsSend("writepattern 0 12288 2 4096" FSPERF_EOF_STR), VINF_SUCCESS); + + enmActuallyTaken = RTFILEACTION_END; + RTFILE hFile1 = NIL_RTFILE; + RTTESTI_CHECK_RC(RTFileOpenEx(InDir(RT_STR_TUPLE("file30")), RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, + &hFile1, &enmActuallyTaken), VINF_SUCCESS); + RTTESTI_CHECK(enmActuallyTaken == RTFILEACTION_OPENED); + AssertCompile(sizeof(abBuf) >= 16384); + RTTESTI_CHECK_RC(RTFileRead(hFile1, abBuf, 16384, NULL), VINF_SUCCESS); + RTTESTI_CHECK(ASMMemIsAllU8(abBuf, 12288, 0xe7)); + AssertCompile(RT_ELEMENTS(g_abPattern2) == 1); + RTTESTI_CHECK(ASMMemIsAllU8(&abBuf[12288], 4096, g_abPattern2[0])); + RTTESTI_CHECK_RC(RTFileRead(hFile1, abBuf, 1, NULL), VERR_EOF); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + /* Same, but remote end truncates the file: */ + memset(abBuf, 0xe6, sizeof(abBuf)); + RTTESTI_CHECK_RC(RTFileSeek(hFile0, 0, RTFILE_SEEK_BEGIN, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileSetSize(hFile0, 0), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileWrite(hFile0, abBuf, 12288, NULL), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTFileSetSize(hFile0, 12288), VINF_SUCCESS); + + RTTESTI_CHECK_RC(FsPerfCommsSend("truncate 0 7500" FSPERF_EOF_STR), VINF_SUCCESS); + + enmActuallyTaken = RTFILEACTION_END; + hFile1 = NIL_RTFILE; + RTTESTI_CHECK_RC(RTFileOpenEx(InDir(RT_STR_TUPLE("file30")), RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, + &hFile1, &enmActuallyTaken), VINF_SUCCESS); + RTTESTI_CHECK(enmActuallyTaken == RTFILEACTION_OPENED); + RTTESTI_CHECK_RC(RTFileRead(hFile1, abBuf, 7500, NULL), VINF_SUCCESS); + RTTESTI_CHECK(ASMMemIsAllU8(abBuf, 7500, 0xe6)); + RTTESTI_CHECK_RC(RTFileRead(hFile1, abBuf, 1, NULL), VERR_EOF); + RTTESTI_CHECK_RC(RTFileClose(hFile1), VINF_SUCCESS); + + RTTESTI_CHECK_RC(RTFileClose(hFile0), VINF_SUCCESS); +} + + + +/** + * Display the usage to @a pStrm. + */ +static void Usage(PRTSTREAM pStrm) +{ + char szExec[FSPERF_MAX_PATH]; + RTStrmPrintf(pStrm, "usage: %s <-d <testdir>> [options]\n", + RTPathFilename(RTProcGetExecutablePath(szExec, sizeof(szExec)))); + RTStrmPrintf(pStrm, "\n"); + RTStrmPrintf(pStrm, "options: \n"); + + for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++) + { + char szHelp[80]; + const char *pszHelp; + switch (g_aCmdOptions[i].iShort) + { + case 'd': pszHelp = "The directory to use for testing. default: CWD/fstestdir"; break; + case 'r': pszHelp = "Don't abspath test dir (good for deep dirs). default: disabled"; break; + case 'e': pszHelp = "Enables all tests. default: -e"; break; + case 'z': pszHelp = "Disables all tests. default: -e"; break; + case 's': pszHelp = "Set benchmark duration in seconds. default: 10 sec"; break; + case 'm': pszHelp = "Set benchmark duration in milliseconds. default: 10000 ms"; break; + case 'v': pszHelp = "More verbose execution."; break; + case 'q': pszHelp = "Quiet execution."; break; + case 'h': pszHelp = "Displays this help and exit"; break; + case 'V': pszHelp = "Displays the program revision"; break; + case kCmdOpt_ShowDuration: pszHelp = "Show duration of profile runs. default: --no-show-duration"; break; + case kCmdOpt_NoShowDuration: pszHelp = "Hide duration of profile runs. default: --no-show-duration"; break; + case kCmdOpt_ShowIterations: pszHelp = "Show iteration count for profile runs. default: --no-show-iterations"; break; + case kCmdOpt_NoShowIterations: pszHelp = "Hide iteration count for profile runs. default: --no-show-iterations"; break; + case kCmdOpt_ManyFiles: pszHelp = "Count of files in big test dir. default: --many-files 10000"; break; + case kCmdOpt_NoManyFiles: pszHelp = "Skip big test dir with many files. default: --many-files 10000"; break; + case kCmdOpt_ManyTreeFilesPerDir: pszHelp = "Count of files per directory in test tree. default: 640"; break; + case kCmdOpt_ManyTreeSubdirsPerDir: pszHelp = "Count of subdirs per directory in test tree. default: 16"; break; + case kCmdOpt_ManyTreeDepth: pszHelp = "Depth of test tree (not counting root). default: 1"; break; +#if defined(RT_OS_WINDOWS) + case kCmdOpt_MaxBufferSize: pszHelp = "For avoiding the MDL limit on windows. default: 32MiB"; break; +#else + case kCmdOpt_MaxBufferSize: pszHelp = "For avoiding the MDL limit on windows. default: 0"; break; +#endif + case kCmdOpt_MMapPlacement: pszHelp = "When to do mmap testing (caching effects): first, between (default), last "; break; + case kCmdOpt_IgnoreNoCache: pszHelp = "Ignore error wrt no-cache handle. default: --no-ignore-no-cache"; break; + case kCmdOpt_NoIgnoreNoCache: pszHelp = "Do not ignore error wrt no-cache handle. default: --no-ignore-no-cache"; break; + case kCmdOpt_IoFileSize: pszHelp = "Size of file used for I/O tests. default: 512 MB"; break; + case kCmdOpt_SetBlockSize: pszHelp = "Sets single I/O block size (in bytes)."; break; + case kCmdOpt_AddBlockSize: pszHelp = "Adds an I/O block size (in bytes)."; break; + default: + if (g_aCmdOptions[i].iShort >= kCmdOpt_First) + { + if (RTStrStartsWith(g_aCmdOptions[i].pszLong, "--no-")) + RTStrPrintf(szHelp, sizeof(szHelp), "Disables the '%s' test.", g_aCmdOptions[i].pszLong + 5); + else + RTStrPrintf(szHelp, sizeof(szHelp), "Enables the '%s' test.", g_aCmdOptions[i].pszLong + 2); + pszHelp = szHelp; + } + else + pszHelp = "Option undocumented"; + break; + } + if ((unsigned)g_aCmdOptions[i].iShort < 127U) + { + char szOpt[64]; + RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort); + RTStrmPrintf(pStrm, " %-19s %s\n", szOpt, pszHelp); + } + else + RTStrmPrintf(pStrm, " %-19s %s\n", g_aCmdOptions[i].pszLong, pszHelp); + } +} + + +static uint32_t fsPerfCalcManyTreeFiles(void) +{ + uint32_t cDirs = 1; + for (uint32_t i = 0, cDirsAtLevel = 1; i < g_cManyTreeDepth; i++) + { + cDirs += cDirsAtLevel * g_cManyTreeSubdirsPerDir; + cDirsAtLevel *= g_cManyTreeSubdirsPerDir; + } + return g_cManyTreeFilesPerDir * cDirs; +} + + +int main(int argc, char *argv[]) +{ + /* + * Init IPRT and globals. + */ + int rc = RTTestInitAndCreate("FsPerf", &g_hTest); + if (rc) + return rc; + RTListInit(&g_ManyTreeHead); + + /* + * Default values. + */ + char szDefaultDir[RTPATH_MAX]; + const char *pszDir = szDefaultDir; + + /* As default retrieve the system's temporary directory and create a test directory beneath it, + * as this binary might get executed from a read-only medium such as ${CDROM}. */ + rc = RTPathTemp(szDefaultDir, sizeof(szDefaultDir)); + if (RT_SUCCESS(rc)) + { + char szDirName[32]; + RTStrPrintf2(szDirName, sizeof(szDirName), "fstestdir-%u" RTPATH_SLASH_STR, RTProcSelf()); + rc = RTPathAppend(szDefaultDir, sizeof(szDefaultDir), szDirName); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Unable to append dir name in temp dir, rc=%Rrc\n", rc); + return RTTestSummaryAndDestroy(g_hTest); + } + } + else + { + RTTestFailed(g_hTest, "Unable to retrieve temp dir, rc=%Rrc\n", rc); + return RTTestSummaryAndDestroy(g_hTest); + } + + RTTestIPrintf(RTTESTLVL_INFO, "Default directory is: %s\n", szDefaultDir); + + bool fCommsSlave = false; + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */); + while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (rc) + { + case 'c': + if (!g_fRelativeDir) + rc = RTPathAbs(ValueUnion.psz, g_szCommsDir, sizeof(g_szCommsDir) - 128); + else + rc = RTStrCopy(g_szCommsDir, sizeof(g_szCommsDir) - 128, ValueUnion.psz); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "%s(%s) failed: %Rrc\n", g_fRelativeDir ? "RTStrCopy" : "RTAbsPath", pszDir, rc); + return RTTestSummaryAndDestroy(g_hTest); + } + RTPathEnsureTrailingSeparator(g_szCommsDir, sizeof(g_szCommsDir)); + g_cchCommsDir = strlen(g_szCommsDir); + + rc = RTPathJoin(g_szCommsSubDir, sizeof(g_szCommsSubDir) - 128, g_szCommsDir, "comms" RTPATH_SLASH_STR); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "RTPathJoin(%s,,'comms/') failed: %Rrc\n", g_szCommsDir, rc); + return RTTestSummaryAndDestroy(g_hTest); + } + g_cchCommsSubDir = strlen(g_szCommsSubDir); + break; + + case 'C': + fCommsSlave = true; + break; + + case 'd': + pszDir = ValueUnion.psz; + break; + + case 'r': + g_fRelativeDir = true; + break; + + case 's': + if (ValueUnion.u32 == 0) + g_nsTestRun = RT_NS_1SEC_64 * 10; + else + g_nsTestRun = ValueUnion.u32 * RT_NS_1SEC_64; + break; + + case 'm': + if (ValueUnion.u64 == 0) + g_nsTestRun = RT_NS_1SEC_64 * 10; + else + g_nsTestRun = ValueUnion.u64 * RT_NS_1MS; + break; + + case 'e': + g_fManyFiles = true; + g_fOpen = true; + g_fFStat = true; +#ifdef RT_OS_WINDOWS + g_fNtQueryInfoFile = true; + g_fNtQueryVolInfoFile = true; +#endif + g_fFChMod = true; + g_fFUtimes = true; + g_fStat = true; + g_fChMod = true; + g_fUtimes = true; + g_fRename = true; + g_fDirOpen = true; + g_fDirEnum = true; + g_fMkRmDir = true; + g_fStatVfs = true; + g_fRm = true; + g_fChSize = true; + g_fReadTests = true; + g_fReadPerf = true; +#ifdef FSPERF_TEST_SENDFILE + g_fSendFile = true; +#endif +#ifdef RT_OS_LINUX + g_fSplice = true; +#endif + g_fWriteTests = true; + g_fWritePerf = true; + g_fSeek = true; + g_fFSync = true; + g_fMMap = true; + g_fMMapCoherency = true; + g_fCopy = true; + g_fRemote = true; + break; + + case 'z': + g_fManyFiles = false; + g_fOpen = false; + g_fFStat = false; +#ifdef RT_OS_WINDOWS + g_fNtQueryInfoFile = false; + g_fNtQueryVolInfoFile = false; +#endif + g_fFChMod = false; + g_fFUtimes = false; + g_fStat = false; + g_fChMod = false; + g_fUtimes = false; + g_fRename = false; + g_fDirOpen = false; + g_fDirEnum = false; + g_fMkRmDir = false; + g_fStatVfs = false; + g_fRm = false; + g_fChSize = false; + g_fReadTests = false; + g_fReadPerf = false; +#ifdef FSPERF_TEST_SENDFILE + g_fSendFile = false; +#endif +#ifdef RT_OS_LINUX + g_fSplice = false; +#endif + g_fWriteTests = false; + g_fWritePerf = false; + g_fSeek = false; + g_fFSync = false; + g_fMMap = false; + g_fMMapCoherency = false; + g_fCopy = false; + g_fRemote = false; + break; + +#define CASE_OPT(a_Stem) \ + case RT_CONCAT(kCmdOpt_,a_Stem): RT_CONCAT(g_f,a_Stem) = true; break; \ + case RT_CONCAT(kCmdOpt_No,a_Stem): RT_CONCAT(g_f,a_Stem) = false; break + CASE_OPT(Open); + CASE_OPT(FStat); +#ifdef RT_OS_WINDOWS + CASE_OPT(NtQueryInfoFile); + CASE_OPT(NtQueryVolInfoFile); +#endif + CASE_OPT(FChMod); + CASE_OPT(FUtimes); + CASE_OPT(Stat); + CASE_OPT(ChMod); + CASE_OPT(Utimes); + CASE_OPT(Rename); + CASE_OPT(DirOpen); + CASE_OPT(DirEnum); + CASE_OPT(MkRmDir); + CASE_OPT(StatVfs); + CASE_OPT(Rm); + CASE_OPT(ChSize); + CASE_OPT(ReadTests); + CASE_OPT(ReadPerf); +#ifdef FSPERF_TEST_SENDFILE + CASE_OPT(SendFile); +#endif +#ifdef RT_OS_LINUX + CASE_OPT(Splice); +#endif + CASE_OPT(WriteTests); + CASE_OPT(WritePerf); + CASE_OPT(Seek); + CASE_OPT(FSync); + CASE_OPT(MMap); + CASE_OPT(MMapCoherency); + CASE_OPT(IgnoreNoCache); + CASE_OPT(Copy); + CASE_OPT(Remote); + + CASE_OPT(ShowDuration); + CASE_OPT(ShowIterations); +#undef CASE_OPT + + case kCmdOpt_ManyFiles: + g_fManyFiles = ValueUnion.u32 > 0; + g_cManyFiles = ValueUnion.u32; + break; + + case kCmdOpt_NoManyFiles: + g_fManyFiles = false; + break; + + case kCmdOpt_ManyTreeFilesPerDir: + if (ValueUnion.u32 > 0 && ValueUnion.u32 <= _64M) + { + g_cManyTreeFilesPerDir = ValueUnion.u32; + g_cManyTreeFiles = fsPerfCalcManyTreeFiles(); + break; + } + RTTestFailed(g_hTest, "Out of range --files-per-dir value: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32); + return RTTestSummaryAndDestroy(g_hTest); + + case kCmdOpt_ManyTreeSubdirsPerDir: + if (ValueUnion.u32 > 0 && ValueUnion.u32 <= 1024) + { + g_cManyTreeSubdirsPerDir = ValueUnion.u32; + g_cManyTreeFiles = fsPerfCalcManyTreeFiles(); + break; + } + RTTestFailed(g_hTest, "Out of range --subdirs-per-dir value: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32); + return RTTestSummaryAndDestroy(g_hTest); + + case kCmdOpt_ManyTreeDepth: + if (ValueUnion.u32 <= 8) + { + g_cManyTreeDepth = ValueUnion.u32; + g_cManyTreeFiles = fsPerfCalcManyTreeFiles(); + break; + } + RTTestFailed(g_hTest, "Out of range --tree-depth value: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32); + return RTTestSummaryAndDestroy(g_hTest); + + case kCmdOpt_MaxBufferSize: + if (ValueUnion.u32 >= 4096) + g_cbMaxBuffer = ValueUnion.u32; + else if (ValueUnion.u32 == 0) + g_cbMaxBuffer = UINT32_MAX; + else + { + RTTestFailed(g_hTest, "max buffer size is less than 4KB: %#x\n", ValueUnion.u32); + return RTTestSummaryAndDestroy(g_hTest); + } + break; + + case kCmdOpt_IoFileSize: + if (ValueUnion.u64 == 0) + g_cbIoFile = _512M; + else + g_cbIoFile = ValueUnion.u64; + break; + + case kCmdOpt_SetBlockSize: + if (ValueUnion.u32 > 0) + { + g_cIoBlocks = 1; + g_acbIoBlocks[0] = ValueUnion.u32; + } + else + { + RTTestFailed(g_hTest, "Invalid I/O block size: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32); + return RTTestSummaryAndDestroy(g_hTest); + } + break; + + case kCmdOpt_AddBlockSize: + if (g_cIoBlocks >= RT_ELEMENTS(g_acbIoBlocks)) + RTTestFailed(g_hTest, "Too many I/O block sizes: max %u\n", RT_ELEMENTS(g_acbIoBlocks)); + else if (ValueUnion.u32 == 0) + RTTestFailed(g_hTest, "Invalid I/O block size: %u (%#x)\n", ValueUnion.u32, ValueUnion.u32); + else + { + g_acbIoBlocks[g_cIoBlocks++] = ValueUnion.u32; + break; + } + return RTTestSummaryAndDestroy(g_hTest); + + case kCmdOpt_MMapPlacement: + if (strcmp(ValueUnion.psz, "first") == 0) + g_iMMapPlacement = -1; + else if ( strcmp(ValueUnion.psz, "between") == 0 + || strcmp(ValueUnion.psz, "default") == 0) + g_iMMapPlacement = 0; + else if (strcmp(ValueUnion.psz, "last") == 0) + g_iMMapPlacement = 1; + else + { + RTTestFailed(g_hTest, + "Invalid --mmap-placment directive '%s'! Expected 'first', 'last', 'between' or 'default'.\n", + ValueUnion.psz); + return RTTestSummaryAndDestroy(g_hTest); + } + break; + + case 'q': + g_uVerbosity = 0; + break; + + case 'v': + g_uVerbosity++; + break; + + case 'h': + Usage(g_pStdOut); + return RTEXITCODE_SUCCESS; + + case 'V': + { + char szRev[] = "$Revision: 153224 $"; + szRev[RT_ELEMENTS(szRev) - 2] = '\0'; + RTPrintf(RTStrStrip(strchr(szRev, ':') + 1)); + return RTEXITCODE_SUCCESS; + } + + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + /* + * Populate g_szDir. + */ + if (!g_fRelativeDir) + rc = RTPathAbs(pszDir, g_szDir, sizeof(g_szDir) - FSPERF_MAX_NEEDED_PATH); + else + rc = RTStrCopy(g_szDir, sizeof(g_szDir) - FSPERF_MAX_NEEDED_PATH, pszDir); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "%s(%s) failed: %Rrc\n", g_fRelativeDir ? "RTStrCopy" : "RTAbsPath", pszDir, rc); + return RTTestSummaryAndDestroy(g_hTest); + } + RTPathEnsureTrailingSeparator(g_szDir, sizeof(g_szDir)); + g_cchDir = strlen(g_szDir); + + /* + * If communication slave, go do that and be done. + */ + if (fCommsSlave) + { + if (pszDir == szDefaultDir) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The slave must have a working directory specified (-d)!"); + return FsPerfCommsSlave(); + } + + /* + * Create the test directory with an 'empty' subdirectory under it, + * execute the tests, and remove directory when done. + */ + RTTestBanner(g_hTest); + if (!RTPathExists(g_szDir)) + { + /* The base dir: */ + rc = RTDirCreate(g_szDir, 0755, + RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL); + if (RT_SUCCESS(rc)) + { + RTTestIPrintf(RTTESTLVL_ALWAYS, "Test dir: %s\n", g_szDir); + rc = fsPrepTestArea(); + if (RT_SUCCESS(rc)) + { + /* Profile RTTimeNanoTS(). */ + fsPerfNanoTS(); + + /* Do tests: */ + if (g_fManyFiles) + fsPerfManyFiles(); + if (g_fOpen) + fsPerfOpen(); + if (g_fFStat) + fsPerfFStat(); +#ifdef RT_OS_WINDOWS + if (g_fNtQueryInfoFile) + fsPerfNtQueryInfoFile(); + if (g_fNtQueryVolInfoFile) + fsPerfNtQueryVolInfoFile(); +#endif + if (g_fFChMod) + fsPerfFChMod(); + if (g_fFUtimes) + fsPerfFUtimes(); + if (g_fStat) + fsPerfStat(); + if (g_fChMod) + fsPerfChmod(); + if (g_fUtimes) + fsPerfUtimes(); + if (g_fRename) + fsPerfRename(); + if (g_fDirOpen) + vsPerfDirOpen(); + if (g_fDirEnum) + vsPerfDirEnum(); + if (g_fMkRmDir) + fsPerfMkRmDir(); + if (g_fStatVfs) + fsPerfStatVfs(); + if (g_fRm || g_fManyFiles) + fsPerfRm(); /* deletes manyfiles and manytree */ + if (g_fChSize) + fsPerfChSize(); + if ( g_fReadPerf || g_fReadTests || g_fWritePerf || g_fWriteTests +#ifdef FSPERF_TEST_SENDFILE + || g_fSendFile +#endif +#ifdef RT_OS_LINUX + || g_fSplice +#endif + || g_fSeek || g_fFSync || g_fMMap) + fsPerfIo(); + if (g_fCopy) + fsPerfCopy(); + if (g_fRemote && g_szCommsDir[0] != '\0') + fsPerfRemote(); + } + + /* + * Cleanup: + */ + FsPerfCommsShutdownSlave(); + + g_szDir[g_cchDir] = '\0'; + rc = RTDirRemoveRecursive(g_szDir, RTDIRRMREC_F_CONTENT_AND_DIR | (g_fRelativeDir ? RTDIRRMREC_F_NO_ABS_PATH : 0)); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "RTDirRemoveRecursive(%s,) -> %Rrc\n", g_szDir, rc); + } + else + RTTestFailed(g_hTest, "RTDirCreate(%s) -> %Rrc\n", g_szDir, rc); + } + else + RTTestFailed(g_hTest, "Test directory already exists: %s\n", g_szDir); + + FsPerfCommsShutdownSlave(); + + return RTTestSummaryAndDestroy(g_hTest); +} + diff --git a/src/VBox/ValidationKit/utils/fs/Makefile.kmk b/src/VBox/ValidationKit/utils/fs/Makefile.kmk new file mode 100644 index 00000000..6105b419 --- /dev/null +++ b/src/VBox/ValidationKit/utils/fs/Makefile.kmk @@ -0,0 +1,49 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - File system tests. +# + +# +# Copyright (C) 2010-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# File System Performance (think shared folders). +# +PROGRAMS += FsPerf +FsPerf_TEMPLATE = VBoxValidationKitR3 +FsPerf_SOURCES = FsPerf.cpp + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/misc/Makefile.kmk b/src/VBox/ValidationKit/utils/misc/Makefile.kmk new file mode 100644 index 00000000..200a33aa --- /dev/null +++ b/src/VBox/ValidationKit/utils/misc/Makefile.kmk @@ -0,0 +1,84 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Miscellaneous Utilites. +# + +# +# Copyright (C) 2010-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Load generator, optionally with an ring-0 IPI generator. +# +PROGRAMS += LoadGenerator +if1of ($(KBUILD_TARGET_ARCH),amd64) + ifdef VBOX_WITH_R0_MODULES + ifdef VBOX_WITH_VBOXR0_AS_DLL +DLLS += loadgeneratorR0 + else +SYSMODS += loadgeneratorR0 + endif +loadgeneratorR0_TEMPLATE := VBoxValidationKitR0 +loadgeneratorR0_SOURCES := loadgeneratorR0.cpp + endif +LoadGenerator_TEMPLATE := VBoxValidationKitR3SupDrv +LoadGenerator_DEFS := WITH_IPI_LOAD_GEN +else +LoadGenerator_TEMPLATE := VBoxValidationKitR3 +endif +LoadGenerator_SOURCES := loadgenerator.cpp + +# +# vts_rm - Test cleanup utility similar to unix rm. +# +PROGRAMS += vts_rm +vts_rm_TEMPLATE = VBoxValidationKitR3 +vts_rm_SOURCES = vts_rm.cpp + +# +# vts_tar - Tar for untarring and tarring test related stuff. +# +PROGRAMS += vts_tar +vts_tar_TEMPLATE = VBoxValidationKitR3 +vts_tar_SDKS = VBOX_ZLIB_STATIC +vts_tar_SOURCES = vts_tar.cpp + +# +# vts_shutdown - initiates a guest or host shut down / reboot. +# +PROGRAMS += vts_shutdown +vts_shutdown_TEMPLATE = VBoxValidationKitR3 +vts_shutdown_SOURCES = ../../../Runtime/tools/RTShutdown.cpp + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/misc/loadgenerator.cpp b/src/VBox/ValidationKit/utils/misc/loadgenerator.cpp new file mode 100644 index 00000000..8decf470 --- /dev/null +++ b/src/VBox/ValidationKit/utils/misc/loadgenerator.cpp @@ -0,0 +1,366 @@ +/* $Id: loadgenerator.cpp $ */ +/** @file + * Load Generator. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/errcore.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/mp.h> +#include <iprt/asm.h> +#include <iprt/getopt.h> +#ifdef WITH_IPI_LOAD_GEN +# include <VBox/sup.h> +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Whether the threads should quit or not. */ +static bool volatile g_fQuit = false; + + +static void LoadGenSpin(uint64_t cNanoSeconds) +{ + const uint64_t u64StartTS = RTTimeNanoTS(); + do + { + for (uint32_t volatile i = 0; i < 10240 && !g_fQuit; i++) + i++; + } while (RTTimeNanoTS() - u64StartTS < cNanoSeconds && !g_fQuit); +} + + +static DECLCALLBACK(int) LoadGenSpinThreadFunction(RTTHREAD hThreadSelf, void *pvUser) +{ + NOREF(hThreadSelf); + LoadGenSpin(*(uint64_t *)pvUser); + return VINF_SUCCESS; +} + +#ifdef WITH_IPI_LOAD_GEN + +static int LoadGenIpiInit(void) +{ + /* + * Try make sure the support library is initialized... + */ + SUPR3Init(NULL); + + /* + * Load the module. + */ + char szPath[RTPATH_MAX]; + int rc = RTPathAppPrivateArchTop(szPath, sizeof(szPath) - sizeof("/loadgenerator.r0")); + if (RT_SUCCESS(rc)) + { + strcat(szPath, "/loadgeneratorR0.r0"); + void *pvImageBase; + rc = SUPR3LoadServiceModule(szPath, "loadgeneratorR0", "LoadGenR0ServiceReqHandler", &pvImageBase); + if (RT_SUCCESS(rc)) + { + /* done */ + } + else + RTMsgError("SUPR3LoadServiceModule(%s): %Rrc", szPath, rc); + } + else + RTMsgError("RTPathAppPrivateArch: %Rrc", rc); + return rc; +} + + +static void LoadGenIpi(uint64_t cNanoSeconds) +{ + const uint64_t u64StartTS = RTTimeNanoTS(); + do + { + int rc = SUPR3CallR0Service("loadgeneratorR0", sizeof("loadgeneratorR0") - 1, + 0 /* uOperation */, 1 /* cIpis */, NULL /* pReqHdr */); + if (RT_FAILURE(rc)) + { + RTMsgError("SUPR3CallR0Service: %Rrc", rc); + break; + } + } while (RTTimeNanoTS() - u64StartTS < cNanoSeconds && !g_fQuit); +} + + +static DECLCALLBACK(int) LoadGenIpiThreadFunction(RTTHREAD hThreadSelf, void *pvUser) +{ + LoadGenIpi(*(uint64_t *)pvUser); + NOREF(hThreadSelf); + return VINF_SUCCESS; +} + +#endif + + +int main(int argc, char **argv) +{ + static const struct LOADGENTYPE + { + const char *pszName; + int (*pfnInit)(void); + PFNRTTHREAD pfnThread; + } s_aLoadTypes[] = + { + { "spin", NULL, LoadGenSpinThreadFunction }, +#ifdef WITH_IPI_LOAD_GEN + { "ipi", LoadGenIpiInit, LoadGenIpiThreadFunction }, +#endif + }; + unsigned iLoadType = 0; + static RTTHREAD s_aThreads[256]; + int rc; + uint32_t cThreads = 1; + bool fScaleByCpus = false; + RTTHREADTYPE enmThreadType = RTTHREADTYPE_DEFAULT; + RTPROCPRIORITY enmProcPriority = RTPROCPRIORITY_DEFAULT; + uint64_t cNanoSeconds = UINT64_MAX; + + RTR3InitExe(argc, &argv, 0); + + /* + * Parse arguments. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--number-of-threads", 'n', RTGETOPT_REQ_UINT32 }, + { "--timeout", 't', RTGETOPT_REQ_STRING }, + { "--thread-type", 'p', RTGETOPT_REQ_STRING }, + { "--scale-by-cpus", 'c', RTGETOPT_REQ_NOTHING }, + { "--load", 'l', RTGETOPT_REQ_STRING }, + }; + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /* fFlags */); + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) + { + case 'n': + cThreads = ValueUnion.u64; + if (cThreads == 0 || cThreads > RT_ELEMENTS(s_aThreads)) + return RTMsgSyntax("Requested number of threads, %RU32, is out of range (1..%d).", + cThreads, RT_ELEMENTS(s_aThreads) - 1); + break; + + case 't': + { + char *psz; + rc = RTStrToUInt64Ex(ValueUnion.psz, &psz, 0, &cNanoSeconds); + if (RT_FAILURE(rc)) + return RTMsgSyntax("Failed reading the alleged timeout number '%s' (rc=%Rrc).", + ValueUnion.psz, rc); + while (*psz == ' ' || *psz == '\t') + psz++; + if (*psz) + { + uint64_t u64Factor = 1; + if (!strcmp(psz, "ns")) + u64Factor = 1; + else if (!strcmp(psz, "ms")) + u64Factor = 1000; + else if (!strcmp(psz, "s")) + u64Factor = 1000000000; + else if (!strcmp(psz, "m")) + u64Factor = UINT64_C(60000000000); + else if (!strcmp(psz, "h")) + u64Factor = UINT64_C(3600000000000); + else + return RTMsgSyntax("Unknown time suffix '%s'", psz); + uint64_t u64 = cNanoSeconds * u64Factor; + if (u64 < cNanoSeconds || (u64 < u64Factor && u64)) + return RTMsgSyntax("Time representation overflowed! (%RU64 * %RU64)", + cNanoSeconds, u64Factor); + cNanoSeconds = u64; + } + break; + } + + case 'p': + { + enmProcPriority = RTPROCPRIORITY_NORMAL; + + uint32_t u32 = RTTHREADTYPE_INVALID; + char *psz; + rc = RTStrToUInt32Ex(ValueUnion.psz, &psz, 0, &u32); + if (RT_FAILURE(rc) || *psz) + { + if (!strcmp(ValueUnion.psz, "default")) + { + enmProcPriority = RTPROCPRIORITY_DEFAULT; + enmThreadType = RTTHREADTYPE_DEFAULT; + } + else if (!strcmp(ValueUnion.psz, "idle")) + { + enmProcPriority = RTPROCPRIORITY_LOW; + enmThreadType = RTTHREADTYPE_INFREQUENT_POLLER; + } + else if (!strcmp(ValueUnion.psz, "high")) + { + enmProcPriority = RTPROCPRIORITY_HIGH; + enmThreadType = RTTHREADTYPE_IO; + } + else + return RTMsgSyntax("can't grok thread type '%s'", + ValueUnion.psz); + } + else + { + enmThreadType = (RTTHREADTYPE)u32; + if (enmThreadType <= RTTHREADTYPE_INVALID || enmThreadType >= RTTHREADTYPE_END) + return RTMsgSyntax("thread type '%d' is out of range (%d..%d)", + ValueUnion.psz, RTTHREADTYPE_INVALID + 1, RTTHREADTYPE_END - 1); + } + break; + } + + case 'c': + fScaleByCpus = true; + break; + + case 'l': + { + for (unsigned i = 0; i < RT_ELEMENTS(s_aLoadTypes); i++) + if (!strcmp(s_aLoadTypes[i].pszName, ValueUnion.psz)) + { + ValueUnion.psz = NULL; + iLoadType = i; + break; + } + if (ValueUnion.psz) + return RTMsgSyntax("Unknown load type '%s'.", ValueUnion.psz); + break; + } + + case 'h': + RTPrintf("Usage: %s [-p|--thread-type <type>] [-t|--timeout <sec|xxx[h|m|s|ms|ns]>] \\\n" + " %*s [-n|--number-of-threads <threads>] [-l|--load <loadtype>]\n" + "\n" + "Load types: " + , RTProcShortName(), strlen(RTProcShortName()), ""); + for (size_t i = 0; i < RT_ELEMENTS(s_aLoadTypes); i++) + RTPrintf(i == 0 ? "%s (default)" : ", %s", s_aLoadTypes[i].pszName); + RTPrintf("\n"); + return 1; + + case 'V': + RTPrintf("$Revision: 153461 $\n"); + return 0; + + case VINF_GETOPT_NOT_OPTION: + return RTMsgSyntax("Unknown argument #%d: '%s'", GetState.iNext-1, ValueUnion.psz); + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + /* + * Scale thread count by host cpu count. + */ + if (fScaleByCpus) + { + const unsigned cCpus = RTMpGetOnlineCount(); + if (cCpus * cThreads > RT_ELEMENTS(s_aThreads)) + return RTMsgSyntax("Requested number of threads, %RU32, is out of range (1..%d) when scaled by %d.", + cThreads, RT_ELEMENTS(s_aThreads) - 1, cCpus); + cThreads *= cCpus; + } + + /* + * Modify process and thread priority? (ignore failure) + */ + if (enmProcPriority != RTPROCPRIORITY_DEFAULT) + RTProcSetPriority(enmProcPriority); + if (enmThreadType != RTTHREADTYPE_DEFAULT) + RTThreadSetType(RTThreadSelf(), enmThreadType); + + /* + * Load type specific init. + */ + if (s_aLoadTypes[iLoadType].pfnInit) + { + rc = s_aLoadTypes[iLoadType].pfnInit(); + if (RT_FAILURE(rc)) + return 1; + } + + + /* + * Start threads. + */ + for (unsigned i = 1; i < cThreads; i++) + { + s_aThreads[i] = NIL_RTTHREAD; + rc = RTThreadCreate(&s_aThreads[i], s_aLoadTypes[iLoadType].pfnThread, + &cNanoSeconds, 128*1024, enmThreadType, RTTHREADFLAGS_WAITABLE, "spinner"); + if (RT_FAILURE(rc)) + { + ASMAtomicXchgBool(&g_fQuit, true); + RTMsgError("failed to create thread #%d: %Rrc", i, rc); + while (i-- > 1) + RTThreadWait(s_aThreads[i], 1500, NULL); + return 1; + } + } + + /* our selves */ + s_aLoadTypes[iLoadType].pfnThread(RTThreadSelf(), &cNanoSeconds); + + /* + * Wait for threads. + */ + ASMAtomicXchgBool(&g_fQuit, true); + for (unsigned i = 1; i < cThreads; i++) + RTThreadWait(s_aThreads[i], 1500, NULL); + + return 0; +} + diff --git a/src/VBox/ValidationKit/utils/misc/loadgeneratorR0.cpp b/src/VBox/ValidationKit/utils/misc/loadgeneratorR0.cpp new file mode 100644 index 00000000..b93230d9 --- /dev/null +++ b/src/VBox/ValidationKit/utils/misc/loadgeneratorR0.cpp @@ -0,0 +1,101 @@ +/* $Id: loadgeneratorR0.cpp $ */ +/** @file + * Load Generator, Ring-0 Service. + */ + +/* + * Copyright (C) 2008-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/mp.h> +#include <VBox/sup.h> +#include <iprt/errcore.h> + + + + +/** + * Worker for loadgenR0Ipi. + */ +static DECLCALLBACK(void) loadgenR0IpiWorker(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + NOREF(idCpu); + NOREF(pvUser1); + NOREF(pvUser2); +} + + +/** + * Generate broadcast inter processor interrupts (IPI), aka cross calls. + * + * @returns VBox status code. + * @param cIpis The number of IPIs to do. + */ +static int loadgenR0Ipi(uint64_t cIpis) +{ + if (cIpis > _1G || !cIpis) + return VERR_INVALID_PARAMETER; + + while (cIpis-- > 0) + { + int rc = RTMpOnAll(loadgenR0IpiWorker, NULL, NULL); + if (RT_FAILURE(rc)) + return rc; + } + return VINF_SUCCESS; +} + + +/** + * Service request handler entry point. + * + * @copydoc FNSUPR0SERVICEREQHANDLER + */ +extern "C" DECLEXPORT(int) LoadGenR0ServiceReqHandler(PSUPDRVSESSION pSession, uint32_t uOperation, + uint64_t u64Arg, PSUPR0SERVICEREQHDR pReqHdr) + +{ + switch (uOperation) + { + case 0: + if (pReqHdr) + return VERR_INVALID_PARAMETER; + return loadgenR0Ipi(u64Arg); + + default: + NOREF(pSession); + return VERR_NOT_SUPPORTED; + } +} + diff --git a/src/VBox/ValidationKit/utils/misc/vts_rm.cpp b/src/VBox/ValidationKit/utils/misc/vts_rm.cpp new file mode 100644 index 00000000..63d962e4 --- /dev/null +++ b/src/VBox/ValidationKit/utils/misc/vts_rm.cpp @@ -0,0 +1,54 @@ +/* $Id: vts_rm.cpp $ */ +/** @file + * VirtualBox Validation Kit - rm like utility. + */ + +/* + * Copyright (C) 2013-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/path.h> +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <iprt/message.h> + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + return RTPathRmCmd(argc, argv); +} + diff --git a/src/VBox/ValidationKit/utils/misc/vts_tar.cpp b/src/VBox/ValidationKit/utils/misc/vts_tar.cpp new file mode 100644 index 00000000..bd659f21 --- /dev/null +++ b/src/VBox/ValidationKit/utils/misc/vts_tar.cpp @@ -0,0 +1,54 @@ +/* $Id: vts_tar.cpp $ */ +/** @file + * VirtualBox Validation Kit - tar like utility. + */ + +/* + * Copyright (C) 2013-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/zip.h> +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <iprt/message.h> + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + return RTZipTarCmd(argc, argv); +} + diff --git a/src/VBox/ValidationKit/utils/network/Makefile.kmk b/src/VBox/ValidationKit/utils/network/Makefile.kmk new file mode 100644 index 00000000..f3c9bf17 --- /dev/null +++ b/src/VBox/ValidationKit/utils/network/Makefile.kmk @@ -0,0 +1,49 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Network tests. +# + +# +# Copyright (C) 2010-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Network Performance Benchmark. +# +PROGRAMS += NetPerf +NetPerf_TEMPLATE = VBoxValidationKitR3 +NetPerf_SOURCES = NetPerf.cpp + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/network/NetPerf.cpp b/src/VBox/ValidationKit/utils/network/NetPerf.cpp new file mode 100644 index 00000000..1783155d --- /dev/null +++ b/src/VBox/ValidationKit/utils/network/NetPerf.cpp @@ -0,0 +1,2008 @@ +/* $Id: NetPerf.cpp $ */ +/** @file + * NetPerf - Network Performance Benchmark. + */ + +/* + * Copyright (C) 2010-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/param.h> +#include <iprt/process.h> +#include <iprt/rand.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/tcp.h> +#include <iprt/thread.h> +#include <iprt/test.h> +#include <iprt/time.h> +#include <iprt/timer.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Default TCP port (update help text if you change this) */ +#define NETPERF_DEFAULT_PORT 5002 + +/** Default TCP packet size (bytes) */ +#define NETPERF_DEFAULT_PKT_SIZE_THROUGHPUT 8192 +/** Default TCP packet size (bytes) */ +#define NETPERF_DEFAULT_PKT_SIZE_LATENCY 1024 +/** Maximum packet size possible (bytes). */ +#define NETPERF_MAX_PKT_SIZE _1M +/** Minimum packet size possible (bytes). */ +#define NETPERF_MIN_PKT_SIZE sizeof(NETPERFHDR) + +/** Default timeout in (seconds) */ +#define NETPERF_DEFAULT_TIMEOUT 10 +/** Maximum timeout possible (seconds). */ +#define NETPERF_MAX_TIMEOUT 3600 /* 1h */ +/** Minimum timeout possible (seconds). */ +#define NETPERF_MIN_TIMEOUT 1 + +/** The default warmup time (ms). */ +#define NETPERF_DEFAULT_WARMUP 1000 /* 1s */ +/** The maxium warmup time (ms). */ +#define NETPERF_MAX_WARMUP 60000 /* 60s */ +/** The minimum warmup time (ms). */ +#define NETPERF_MIN_WARMUP 1000 /* 1s */ + +/** The default cool down time (ms). */ +#define NETPERF_DEFAULT_COOL_DOWN 1000 /* 1s */ +/** The maxium cool down time (ms). */ +#define NETPERF_MAX_COOL_DOWN 60000 /* 60s */ +/** The minimum cool down time (ms). */ +#define NETPERF_MIN_COOL_DOWN 1000 /* 1s */ + +/** Maximum socket buffer size possible (bytes). */ +#define NETPERF_MAX_BUF_SIZE _128M +/** Minimum socket buffer size possible (bytes). */ +#define NETPERF_MIN_BUF_SIZE 256 + +/** The length of the length prefix used when submitting parameters and + * results. */ +#define NETPERF_LEN_PREFIX 4 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef enum NETPERFPROTO +{ + NETPERFPROTO_INVALID = 0, + NETPERFPROTO_TCP + //NETPERFPROTO_UDP +} NETPERFPROTO; + +/** + * What kind of test we're performing. + */ +typedef enum NETPERFMODE +{ + NETPERFMODE_INVALID = 0, + /** Latency of a symmetric packet exchange. */ + NETPERFMODE_LATENCY, + /** Separate throughput measurements for each direction. */ + NETPERFMODE_THROUGHPUT, + /** Transmit throughput. */ + NETPERFMODE_THROUGHPUT_XMIT, + /** Transmit throughput. */ + NETPERFMODE_THROUGHPUT_RECV +} NETPERFMODE; + +/** + * Statistics. + */ +typedef struct NETPERFSTATS +{ + uint64_t cTx; + uint64_t cRx; + uint64_t cEchos; + uint64_t cErrors; + uint64_t cNsElapsed; +} NETPERFSTATS; + +/** + * Settings & a little bit of state. + */ +typedef struct NETPERFPARAMS +{ + /** @name Static settings + * @{ */ + /** The TCP port number. */ + uint32_t uPort; + /** Client: Use server statistcs. */ + bool fServerStats; + /** Server: Quit after the first client. */ + bool fSingleClient; + /** Send and receive buffer sizes for TCP sockets, zero if to use defaults. */ + uint32_t cbBufferSize; + /** @} */ + + /** @name Dynamic settings + * @{ */ + /** Disable send packet coalescing. */ + bool fNoDelay; + /** Detect broken payloads. */ + bool fCheckData; + /** The test mode. */ + NETPERFMODE enmMode; + /** The number of seconds to run each of the test steps. */ + uint32_t cSecTimeout; + /** Number of millisecond to spend warning up before testing. */ + uint32_t cMsWarmup; + /** Number of millisecond to spend cooling down after the testing. */ + uint32_t cMsCoolDown; + /** The packet size. */ + uint32_t cbPacket; + /** @} */ + + /** @name State + * @{ */ + RTSOCKET hSocket; + /** @} */ +} NETPERFPARAMS; + +/** + * Packet header used in tests. + * + * Need to indicate when we've timed out and it's time to reverse the roles or + * stop testing. + */ +typedef struct NETPERFHDR +{ + /** Magic value (little endian). */ + uint32_t u32Magic; + /** State value. */ + uint32_t u32State; + /** Sequence number (little endian). */ + uint32_t u32Seq; + /** Reserved, must be zero. */ + uint32_t u32Reserved; +} NETPERFHDR; + +/** Magic value for NETPERFHDR::u32Magic. */ +#define NETPERFHDR_MAGIC UINT32_C(0xfeedf00d) + +/** @name Packet State (NETPERF::u32Magic) + * @{ */ +/** Warm up. */ +#define NETPERFHDR_WARMUP UINT32_C(0x0c0ffe01) +/** The clock is running. */ +#define NETPERFHDR_TESTING UINT32_C(0x0c0ffe02) +/** Stop the clock but continue the package flow. */ +#define NETPERFHDR_COOL_DOWN UINT32_C(0x0c0ffe03) +/** Done, stop the clock if not done already and reply with results. */ +#define NETPERFHDR_DONE UINT32_C(0x0c0ffe04) +/** @} */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Connection start/identifier to make sure other end is NetPerf. */ +static const char g_ConnectStart[] = "yo! waaazzzzzaaaaup dude?"; +/** Start of parameters proposal made by the client. */ +static const char g_szStartParams[] = "deal?"; +/** All okay to start test */ +static const char g_szAck[] = "okay!"; +/** Negative. */ +static const char g_szNegative[] = "nope!"; +AssertCompile(sizeof(g_szAck) == sizeof(g_szNegative)); +/** Start of statistics. */ +static const char g_szStartStats[] = "dude, stats"; + +/** Command line parameters */ +static const RTGETOPTDEF g_aCmdOptions[] = +{ + { "--server", 's', RTGETOPT_REQ_NOTHING }, + { "--client", 'c', RTGETOPT_REQ_STRING }, + { "--interval", 'i', RTGETOPT_REQ_UINT32 }, + { "--port", 'p', RTGETOPT_REQ_UINT32 }, + { "--len", 'l', RTGETOPT_REQ_UINT32 }, + { "--nodelay", 'N', RTGETOPT_REQ_NOTHING }, + { "--mode", 'm', RTGETOPT_REQ_STRING }, + { "--warmup", 'w', RTGETOPT_REQ_UINT32 }, + { "--cool-down", 'W', RTGETOPT_REQ_UINT32 }, + { "--server-stats", 'S', RTGETOPT_REQ_NOTHING }, + { "--single-client", '1', RTGETOPT_REQ_NOTHING }, + { "--daemonize", 'd', RTGETOPT_REQ_NOTHING }, + { "--daemonized", 'D', RTGETOPT_REQ_NOTHING }, + { "--check-data", 'C', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + { "--buffer-size", 'b', RTGETOPT_REQ_UINT32 }, + { "--help", 'h', RTGETOPT_REQ_NOTHING } /* for Usage() */ +}; + +/** The test handle. */ +static RTTEST g_hTest; +/** Verbosity level. */ +static uint32_t g_uVerbosity = 0; + + + +static void Usage(PRTSTREAM pStrm) +{ + char szExec[RTPATH_MAX]; + RTStrmPrintf(pStrm, "usage: %s <-s|-c <host>> [options]\n", + RTPathFilename(RTProcGetExecutablePath(szExec, sizeof(szExec)))); + RTStrmPrintf(pStrm, "\n"); + RTStrmPrintf(pStrm, "options: \n"); + + + for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++) + { + const char *pszHelp; + switch (g_aCmdOptions[i].iShort) + { + case 'h': + pszHelp = "Displays this help and exit"; + break; + case 's': + pszHelp = "Run in server mode, waiting for clients (default)"; + break; + case 'c': + pszHelp = "Run in client mode, connecting to <host>"; + break; + case 'i': + pszHelp = "Interval in seconds to run the test (default " RT_XSTR(NETPERF_DEFAULT_TIMEOUT) " s)"; + break; + case 'p': + pszHelp = "Server port to listen/connect to (default " RT_XSTR(NETPERF_DEFAULT_PORT) ")"; + break; + case 'l': + pszHelp = "Packet size in bytes (defaults to " RT_XSTR(NETPERF_DEFAULT_PKT_SIZE_LATENCY) + " for latency and " RT_XSTR(NETPERF_DEFAULT_PKT_SIZE_THROUGHPUT) " for throughput)"; + break; + case 'm': + pszHelp = "Test mode: latency (default), throughput, throughput-xmit or throughput-recv"; + break; + case 'N': + pszHelp = "Set TCP no delay, disabling Nagle's algorithm"; + break; + case 'S': + pszHelp = "Report server stats, ignored if server"; + break; + case '1': + pszHelp = "Stop the server after the first client"; + break; + case 'd': + pszHelp = "Daemonize if server, ignored if client"; + break; + case 'D': + continue; /* internal */ + case 'w': + pszHelp = "Warmup time, in milliseconds (default " RT_XSTR(NETPERF_DEFAULT_WARMUP) " ms)"; + break; + case 'W': + pszHelp = "Cool down time, in milliseconds (default " RT_XSTR(NETPERF_DEFAULT_COOL_DOWN) " ms)"; + break; + case 'C': + pszHelp = "Check payload data at the receiving end"; + break; + case 'b': + pszHelp = "Send and receive buffer sizes for TCP"; + break; + case 'v': + pszHelp = "Verbose execution."; + break; + default: + pszHelp = "Option undocumented"; + break; + } + char szOpt[256]; + RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort); + RTStrmPrintf(pStrm, " %-20s%s\n", szOpt, pszHelp); + } +} + +/** + * Timer callback employed to set the stop indicator. + * + * This is used both by the client and server side. + * + * @param hTimer The timer, ignored. + * @param pvUser Pointer to the stop variable. + * @param iTick The tick, ignored. + */ +static DECLCALLBACK(void) netperfStopTimerCallback(RTTIMERLR hTimer, void *pvUser, uint64_t iTick) +{ + bool volatile *pfStop = (bool volatile *)pvUser; + if (g_uVerbosity > 0) + RTPrintf("Time's Up!\n"); + ASMAtomicWriteBool(pfStop, true); + NOREF(hTimer); NOREF(iTick); +} + +/** + * Sends a statistics packet to our peer. + * + * @returns IPRT status code. + * @param pStats The stats to send. + * @param hSocket The TCP socket to send them to. + */ +static int netperfSendStats(NETPERFSTATS const *pStats, RTSOCKET hSocket) +{ + char szBuf[256 + NETPERF_LEN_PREFIX]; + size_t cch = RTStrPrintf(&szBuf[NETPERF_LEN_PREFIX], sizeof(szBuf) - NETPERF_LEN_PREFIX, + "%s:%llu:%llu:%llu:%llu:%llu", + g_szStartStats, + pStats->cTx, + pStats->cRx, + pStats->cEchos, + pStats->cErrors, + pStats->cNsElapsed); + + RTStrPrintf(szBuf, NETPERF_LEN_PREFIX + 1, "%0*u", NETPERF_LEN_PREFIX, cch); + szBuf[NETPERF_LEN_PREFIX] = g_szStartStats[0]; + Assert(strlen(szBuf) == cch + NETPERF_LEN_PREFIX); + + int rc = RTTcpWrite(hSocket, szBuf, cch + NETPERF_LEN_PREFIX); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "stats: Failed to send stats: %Rrc\n", rc); + + /* + * Wait for ACK. + */ + rc = RTTcpRead(hSocket, szBuf, sizeof(g_szAck) - 1, NULL); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "stats: failed to write stats: %Rrc\n", rc); + szBuf[sizeof(g_szAck) - 1] = '\0'; + if (!strcmp(szBuf, g_szNegative)) + return RTTestIFailedRc(rc, "stats: client failed to parse them\n"); + if (strcmp(szBuf, g_szAck)) + return RTTestIFailedRc(rc, "stats: got '%s' in instead of ack/nack\n", szBuf); + + return VINF_SUCCESS; +} + +/** + * Receives a statistics packet from our peer. + * + * @returns IPRT status code. Error signalled. + * @param pStats Where to receive the stats. + * @param hSocket The TCP socket to recevie them from. + */ +static int netperfRecvStats(NETPERFSTATS *pStats, RTSOCKET hSocket) +{ + /* + * Read the stats message. + */ + /* the length prefix */ + char szBuf[256 + NETPERF_LEN_PREFIX]; + int rc = RTTcpRead(hSocket, szBuf, NETPERF_LEN_PREFIX, NULL); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "stats: failed to read stats prefix: %Rrc\n", rc); + + szBuf[NETPERF_LEN_PREFIX] = '\0'; + uint32_t cch; + rc = RTStrToUInt32Full(szBuf, 10, &cch); + if (rc != VINF_SUCCESS) + return RTTestIFailedRc(RT_SUCCESS(rc) ? -rc : rc, "stats: bad stat length prefix: '%s' - %Rrc\n", szBuf, rc); + if (cch >= sizeof(szBuf)) + return RTTestIFailedRc(VERR_BUFFER_OVERFLOW, "stats: too large: %u bytes\n", cch); + + /* the actual message */ + rc = RTTcpRead(hSocket, szBuf, cch, NULL); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "failed to read stats: %Rrc\n", rc); + szBuf[cch] = '\0'; + + /* + * Validate the message header. + */ + if ( strncmp(szBuf, g_szStartStats, sizeof(g_szStartStats) - 1) + || szBuf[sizeof(g_szStartStats) - 1] != ':') + return RTTestIFailedRc(VERR_NET_PROTOCOL_ERROR, "stats: invalid packet start: '%s'\n", szBuf); + char *pszCur = &szBuf[sizeof(g_szStartStats)]; + + /* + * Parse it. + */ + static const char * const s_apszNames[] = + { + "cTx", "cRx", "cEchos", "cErrors", "cNsElapsed" + }; + uint64_t *apu64[RT_ELEMENTS(s_apszNames)] = + { + &pStats->cTx, + &pStats->cRx, + &pStats->cEchos, + &pStats->cErrors, + &pStats->cNsElapsed + }; + + for (unsigned i = 0; i < RT_ELEMENTS(apu64); i++) + { + if (!pszCur) + return RTTestIFailedRc(VERR_PARSE_ERROR, "stats: missing %s\n", s_apszNames[i]); + + char *pszNext = strchr(pszCur, ':'); + if (pszNext) + *pszNext++ = '\0'; + rc = RTStrToUInt64Full(pszCur, 10, apu64[i]); + if (rc != VINF_SUCCESS) + return RTTestIFailedRc(RT_SUCCESS(rc) ? -rc : rc, "stats: bad value for %s: '%s' - %Rrc\n", + s_apszNames[i], pszCur, rc); + + pszCur = pszNext; + } + + if (pszCur) + return RTTestIFailedRc(VERR_PARSE_ERROR, "stats: Unparsed data: '%s'\n", pszCur); + + /* + * Send ACK. + */ + rc = RTTcpWrite(hSocket, g_szAck, sizeof(g_szAck) - 1); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "stats: failed to write ack: %Rrc\n", rc); + + return VINF_SUCCESS; +} + +/** + * TCP Throughput: Print the statistics. + * + * @param pSendStats Send stats. + * @param pRecvStats Receive stats. + * @param cbPacket Packet size. + */ +static void netperfPrintThroughputStats(NETPERFSTATS const *pSendStats, NETPERFSTATS const *pRecvStats, uint32_t cbPacket) +{ + RTTestIValue("Packet size", cbPacket, RTTESTUNIT_BYTES); + + if (pSendStats) + { + double rdSecElapsed = (double)pSendStats->cNsElapsed / 1000000000.0; + RTTestIValue("Sends", pSendStats->cTx, RTTESTUNIT_PACKETS); + RTTestIValue("Send Interval", pSendStats->cNsElapsed, RTTESTUNIT_NS); + RTTestIValue("Send Throughput", (uint64_t)((double)(cbPacket * pSendStats->cTx) / rdSecElapsed), RTTESTUNIT_BYTES_PER_SEC); + RTTestIValue("Send Rate", (uint64_t)((double)pSendStats->cTx / rdSecElapsed), RTTESTUNIT_PACKETS_PER_SEC); + RTTestIValue("Send Latency", (uint64_t)(rdSecElapsed / (double)pSendStats->cTx * 1000000000.0), RTTESTUNIT_NS_PER_PACKET); + } + + if (pRecvStats) + { + double rdSecElapsed = (double)pRecvStats->cNsElapsed / 1000000000.0; + RTTestIValue("Receives", pRecvStats->cRx, RTTESTUNIT_PACKETS); + RTTestIValue("Receive Interval", pRecvStats->cNsElapsed, RTTESTUNIT_NS); + RTTestIValue("Receive Throughput", (uint64_t)((double)(cbPacket * pRecvStats->cRx) / rdSecElapsed), RTTESTUNIT_BYTES_PER_SEC); + RTTestIValue("Receive Rate", (uint64_t)((double)pRecvStats->cRx / rdSecElapsed), RTTESTUNIT_PACKETS_PER_SEC); + RTTestIValue("Receive Latency", (uint64_t)(rdSecElapsed / (double)pRecvStats->cRx * 1000000000.0), RTTESTUNIT_NS_PER_PACKET); + } +} + +/** + * TCP Throughput: Send data to the other party. + * + * @returns IPRT status code. + * @param pParams The TCP parameters block. + * @param pBuf The buffer we're using when sending. + * @param pSendStats Where to return the statistics. + */ +static int netperfTCPThroughputSend(NETPERFPARAMS const *pParams, NETPERFHDR *pBuf, NETPERFSTATS *pSendStats) +{ + RT_ZERO(*pSendStats); + + /* + * Create the timer + */ + RTTIMERLR hTimer; + bool volatile fStop = false; + int rc = RTTimerLRCreateEx(&hTimer, 0 /* nsec */, RTTIMER_FLAGS_CPU_ANY, netperfStopTimerCallback, (void *)&fStop); + if (RT_SUCCESS(rc)) + { + uint32_t u32Seq = 0; + + RT_BZERO(pBuf, pParams->cbPacket); + pBuf->u32Magic = RT_H2LE_U32_C(NETPERFHDR_MAGIC); + pBuf->u32State = 0; + pBuf->u32Seq = 0; + pBuf->u32Reserved = 0; + + /* + * Warm up. + */ + if (g_uVerbosity > 0) + RTPrintf("Warmup...\n"); + pBuf->u32State = RT_H2LE_U32_C(NETPERFHDR_WARMUP); + rc = RTTimerLRStart(hTimer, pParams->cMsWarmup * UINT64_C(1000000) /* nsec */); + if (RT_SUCCESS(rc)) + { + while (!fStop) + { + u32Seq++; + pBuf->u32Seq = RT_H2LE_U32(u32Seq); + rc = RTTcpWrite(pParams->hSocket, pBuf, pParams->cbPacket); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTTcpWrite/warmup: %Rrc\n", rc); + break; + } + } + } + else + RTTestIFailed("RTTimerLRStart/warmup: %Rrc\n", rc); + + /* + * The real thing. + */ + if (RT_SUCCESS(rc)) + { + if (g_uVerbosity > 0) + RTPrintf("The real thing...\n"); + pBuf->u32State = RT_H2LE_U32_C(NETPERFHDR_TESTING); + fStop = false; + rc = RTTimerLRStart(hTimer, pParams->cSecTimeout * UINT64_C(1000000000) /* nsec */); + if (RT_SUCCESS(rc)) + { + uint64_t u64StartTS = RTTimeNanoTS(); + while (!fStop) + { + u32Seq++; + pBuf->u32Seq = RT_H2LE_U32(u32Seq); + rc = RTTcpWrite(pParams->hSocket, pBuf, pParams->cbPacket); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTTcpWrite/testing: %Rrc\n", rc); + break; + } + pSendStats->cTx++; + } + pSendStats->cNsElapsed = RTTimeNanoTS() - u64StartTS; + } + else + RTTestIFailed("RTTimerLRStart/testing: %Rrc\n", rc); + } + + /* + * Cool down. + */ + if (RT_SUCCESS(rc)) + { + if (g_uVerbosity > 0) + RTPrintf("Cool down...\n"); + pBuf->u32State = RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN); + fStop = false; + rc = RTTimerLRStart(hTimer, pParams->cMsCoolDown * UINT64_C(1000000) /* nsec */); + if (RT_SUCCESS(rc)) + { + while (!fStop) + { + u32Seq++; + pBuf->u32Seq = RT_H2LE_U32(u32Seq); + rc = RTTcpWrite(pParams->hSocket, pBuf, pParams->cbPacket); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTTcpWrite/cool down: %Rrc\n", rc); + break; + } + } + } + else + RTTestIFailed("RTTimerLRStart/testing: %Rrc\n", rc); + } + + /* + * Send DONE packet. + */ + if (g_uVerbosity > 0) + RTPrintf("Done\n"); + if (RT_SUCCESS(rc)) + { + u32Seq++; + pBuf->u32Seq = RT_H2LE_U32(u32Seq); + pBuf->u32State = RT_H2LE_U32_C(NETPERFHDR_DONE); + rc = RTTcpWrite(pParams->hSocket, pBuf, pParams->cbPacket); + if (RT_FAILURE(rc)) + RTTestIFailed("RTTcpWrite/done: %Rrc\n", rc); + } + + RTTimerLRDestroy(hTimer); + } + else + RTTestIFailed("Failed to create timer object: %Rrc\n", rc); + return rc; +} + + +/** + * TCP Throughput: Receive data from the other party. + * + * @returns IPRT status code. + * @param pParams The TCP parameters block. + * @param pBuf The buffer we're using when sending. + * @param pStats Where to return the statistics. + */ +static int netperfTCPThroughputRecv(NETPERFPARAMS const *pParams, NETPERFHDR *pBuf, NETPERFSTATS *pStats) +{ + RT_ZERO(*pStats); + + int rc; + uint32_t u32Seq = 0; + uint64_t cRx = 0; + uint64_t u64StartTS = 0; + uint32_t uState = RT_H2LE_U32_C(NETPERFHDR_WARMUP); + + for (;;) + { + rc = RTTcpRead(pParams->hSocket, pBuf, pParams->cbPacket, NULL); + if (RT_FAILURE(rc)) + { + pStats->cErrors++; + RTTestIFailed("RTTcpRead failed: %Rrc\n", rc); + break; + } + if (RT_UNLIKELY( pBuf->u32Magic != RT_H2LE_U32_C(NETPERFHDR_MAGIC) + || pBuf->u32Reserved != 0)) + { + pStats->cErrors++; + RTTestIFailed("Invalid magic or reserved field value: %#x %#x\n", RT_H2LE_U32(pBuf->u32Magic), RT_H2LE_U32(pBuf->u32Reserved)); + rc = VERR_INVALID_MAGIC; + break; + } + + u32Seq += 1; + if (RT_UNLIKELY(pBuf->u32Seq != RT_H2LE_U32(u32Seq))) + { + pStats->cErrors++; + RTTestIFailed("Out of sequence: got %#x, expected %#x\n", RT_H2LE_U32(pBuf->u32Seq), u32Seq); + rc = VERR_WRONG_ORDER; + break; + } + + if (pParams->fCheckData && uState == RT_H2LE_U32_C(NETPERFHDR_TESTING)) + { + unsigned i = sizeof(NETPERFHDR); + for (;i < pParams->cbPacket; ++i) + if (((unsigned char *)pBuf)[i]) + break; + if (i != pParams->cbPacket) + { + pStats->cErrors++; + RTTestIFailed("Broken payload: at %#x got %#x, expected %#x\n", i, ((unsigned char *)pBuf)[i], 0); + rc = VERR_NOT_EQUAL; + break; + } + } + if (RT_LIKELY(pBuf->u32State == uState)) + cRx++; + /* + * Validate and act on switch state. + */ + else if ( uState == RT_H2LE_U32_C(NETPERFHDR_WARMUP) + && pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_TESTING)) + { + cRx = 0; + u64StartTS = RTTimeNanoTS(); + uState = pBuf->u32State; + } + else if ( uState == RT_H2LE_U32_C(NETPERFHDR_TESTING) + && ( pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN) + || pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_DONE)) ) + { + pStats->cNsElapsed = RTTimeNanoTS() - u64StartTS; + pStats->cRx = cRx + 1; + uState = pBuf->u32State; + if (uState == RT_H2LE_U32_C(NETPERFHDR_DONE)) + break; + } + else if ( uState == RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN) + && pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_DONE)) + { + uState = pBuf->u32State; + break; + } + else + { + pStats->cErrors++; + RTTestIFailed("Protocol error: invalid state transition %#x -> %#x\n", + RT_LE2H_U32(uState), RT_LE2H_U32(pBuf->u32State)); + rc = VERR_INVALID_MAGIC; + break; + } + } + + AssertReturn(uState == RT_H2LE_U32_C(NETPERFHDR_DONE) || RT_FAILURE(rc), VERR_INVALID_STATE); + return rc; +} + + +/** + * Prints the statistics for the latency test. + * + * @param pStats The statistics. + * @param cbPacket The packet size in bytes. + */ +static void netperfPrintLatencyStats(NETPERFSTATS const *pStats, uint32_t cbPacket) +{ + double rdSecElapsed = (double)pStats->cNsElapsed / 1000000000.0; + RTTestIValue("Transmitted", pStats->cTx, RTTESTUNIT_PACKETS); + RTTestIValue("Successful echos", pStats->cEchos, RTTESTUNIT_PACKETS); + RTTestIValue("Errors", pStats->cErrors, RTTESTUNIT_PACKETS); + RTTestIValue("Interval", pStats->cNsElapsed, RTTESTUNIT_NS); + RTTestIValue("Packet size", cbPacket, RTTESTUNIT_BYTES); + RTTestIValue("Average rate", (uint64_t)((double)pStats->cEchos / rdSecElapsed), RTTESTUNIT_PACKETS_PER_SEC); + RTTestIValue("Average throughput", (uint64_t)((double)(cbPacket * pStats->cEchos) / rdSecElapsed), RTTESTUNIT_BYTES_PER_SEC); + RTTestIValue("Average latency", (uint64_t)(rdSecElapsed / (double)pStats->cEchos * 1000000000.0), RTTESTUNIT_NS_PER_ROUND_TRIP); + RTTestISubDone(); +} + + +/** + * NETPERFMODE -> string. + * + * @returns readonly string. + * @param enmMode The mode. + */ +static const char *netperfModeToString(NETPERFMODE enmMode) +{ + switch (enmMode) + { + case NETPERFMODE_LATENCY: return "latency"; + case NETPERFMODE_THROUGHPUT: return "throughput"; + case NETPERFMODE_THROUGHPUT_XMIT: return "throughput-xmit"; + case NETPERFMODE_THROUGHPUT_RECV: return "throughput-recv"; + default: AssertFailed(); return "internal-error"; + } +} + +/** + * String -> NETPERFMODE. + * + * @returns The corresponding NETPERFMODE, NETPERFMODE_INVALID on failure. + * @param pszMode The mode string. + */ +static NETPERFMODE netperfModeFromString(const char *pszMode) +{ + if (!strcmp(pszMode, "latency")) + return NETPERFMODE_LATENCY; + if ( !strcmp(pszMode, "throughput") + || !strcmp(pszMode, "thruput") ) + return NETPERFMODE_THROUGHPUT; + if ( !strcmp(pszMode, "throughput-xmit") + || !strcmp(pszMode, "thruput-xmit") + || !strcmp(pszMode, "xmit") ) + return NETPERFMODE_THROUGHPUT_XMIT; + if ( !strcmp(pszMode, "throughput-recv") + || !strcmp(pszMode, "thruput-recv") + || !strcmp(pszMode, "recv") ) + return NETPERFMODE_THROUGHPUT_RECV; + return NETPERFMODE_INVALID; +} + + + + + +/** + * TCP Server: Throughput test. + * + * @returns IPRT status code. + * @param pParams The parameters to use for this test. + */ +static int netperfTCPServerDoThroughput(NETPERFPARAMS const *pParams) +{ + /* + * Allocate the buffer. + */ + NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket); + if (!pBuf) + return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory"); + + /* + * Receive first, then Send. The reverse of the client. + */ + NETPERFSTATS RecvStats; + int rc = netperfTCPThroughputRecv(pParams, pBuf, &RecvStats); + if (RT_SUCCESS(rc)) + { + rc = netperfSendStats(&RecvStats, pParams->hSocket); + if (RT_SUCCESS(rc)) + { + NETPERFSTATS SendStats; + rc = netperfTCPThroughputSend(pParams, pBuf, &SendStats); + if (RT_SUCCESS(rc)) + { + rc = netperfSendStats(&SendStats, pParams->hSocket); + netperfPrintThroughputStats(&SendStats, &RecvStats, pParams->cbPacket); + } + } + } + + return rc; +} + +/** + * TCP Server: Throughput xmit test (receive from client). + * + * @returns IPRT status code. + * @param pParams The parameters to use for this test. + */ +static int netperfTCPServerDoThroughputXmit(NETPERFPARAMS const *pParams) +{ + /* + * Allocate the buffer. + */ + NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket); + if (!pBuf) + return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory"); + + /* + * Receive the transmitted data (reverse of client). + */ + NETPERFSTATS RecvStats; + int rc = netperfTCPThroughputRecv(pParams, pBuf, &RecvStats); + if (RT_SUCCESS(rc)) + { + rc = netperfSendStats(&RecvStats, pParams->hSocket); + if (RT_SUCCESS(rc)) + netperfPrintThroughputStats(NULL, &RecvStats, pParams->cbPacket); + } + + return rc; +} + +/** + * TCP Server: Throughput recv test (transmit to client). + * + * @returns IPRT status code. + * @param pParams The parameters to use for this test. + */ +static int netperfTCPServerDoThroughputRecv(NETPERFPARAMS const *pParams) +{ + /* + * Allocate the buffer. + */ + NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket); + if (!pBuf) + return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory"); + + /* + * Send data to the client (reverse of client). + */ + NETPERFSTATS SendStats; + int rc = netperfTCPThroughputSend(pParams, pBuf, &SendStats); + if (RT_SUCCESS(rc)) + { + rc = netperfSendStats(&SendStats, pParams->hSocket); + if (RT_SUCCESS(rc)) + netperfPrintThroughputStats(&SendStats, NULL, pParams->cbPacket); + } + + return rc; +} + +/** + * TCP Server: Latency test. + * + * @returns IPRT status code. + * @param pParams The parameters to use for this test. + */ +static int netperfTCPServerDoLatency(NETPERFPARAMS const *pParams) +{ + NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket); + if (!pBuf) + return RTTestIFailedRc(VERR_NO_MEMORY, "Failed to allocated packet buffer of %u bytes.\n", pParams->cbPacket); + + /* + * Ping pong with client. + */ + int rc; + uint32_t uState = RT_H2LE_U32_C(NETPERFHDR_WARMUP); + uint32_t u32Seq = 0; + uint64_t cTx = 0; + uint64_t cRx = 0; + uint64_t u64StartTS = 0; + NETPERFSTATS Stats; + RT_ZERO(Stats); + for (;;) + { + rc = RTTcpRead(pParams->hSocket, pBuf, pParams->cbPacket, NULL); + if (RT_FAILURE(rc)) + { + RTTestIFailed("Failed to read data from client: %Rrc\n", rc); + break; + } + + /* + * Validate the packet + */ + if (RT_UNLIKELY( pBuf->u32Magic != RT_H2LE_U32_C(NETPERFHDR_MAGIC) + || pBuf->u32Reserved != 0)) + { + RTTestIFailed("Invalid magic or reserved field value: %#x %#x\n", RT_H2LE_U32(pBuf->u32Magic), RT_H2LE_U32(pBuf->u32Reserved)); + rc = VERR_INVALID_MAGIC; + break; + } + + u32Seq += 1; + if (RT_UNLIKELY(pBuf->u32Seq != RT_H2LE_U32(u32Seq))) + { + RTTestIFailed("Out of sequence: got %#x, expected %#x\n", RT_H2LE_U32(pBuf->u32Seq), u32Seq); + rc = VERR_WRONG_ORDER; + break; + } + + /* + * Count the packet if the state remains unchanged. + */ + if (RT_LIKELY(pBuf->u32State == uState)) + cRx++; + /* + * Validate and act on the state transition. + */ + else if ( uState == RT_H2LE_U32_C(NETPERFHDR_WARMUP) + && pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_TESTING)) + { + cRx = cTx = 0; + u64StartTS = RTTimeNanoTS(); + uState = pBuf->u32State; + } + else if ( uState == RT_H2LE_U32_C(NETPERFHDR_TESTING) + && ( pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN) + || pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_DONE)) ) + { + Stats.cNsElapsed = RTTimeNanoTS() - u64StartTS; + Stats.cEchos = cTx; + Stats.cTx = cTx; + Stats.cRx = cRx; + uState = pBuf->u32State; + if (uState == RT_H2LE_U32_C(NETPERFHDR_DONE)) + break; + } + else if ( uState == RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN) + && pBuf->u32State == RT_H2LE_U32_C(NETPERFHDR_DONE)) + { + uState = pBuf->u32State; + break; + } + else + { + RTTestIFailed("Protocol error: invalid state transition %#x -> %#x\n", + RT_LE2H_U32(uState), RT_LE2H_U32(pBuf->u32State)); + break; + } + + /* + * Write same data back to client. + */ + rc = RTTcpWrite(pParams->hSocket, pBuf, pParams->cbPacket); + if (RT_FAILURE(rc)) + { + RTTestIFailed("Failed to write data to client: %Rrc\n", rc); + break; + } + + cTx++; + } + + /* + * Send stats to client and print them. + */ + if (uState == RT_H2LE_U32_C(NETPERFHDR_DONE)) + netperfSendStats(&Stats, pParams->hSocket); + + if ( uState == RT_H2LE_U32_C(NETPERFHDR_DONE) + || uState == RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN)) + netperfPrintLatencyStats(&Stats, pParams->cbPacket); + + RTMemFree(pBuf); + return rc; +} + +/** + * Parses the parameters the client has sent us. + * + * @returns IPRT status code. Message has been shown on failure. + * @param pParams The parameter structure to store the parameters + * in. + * @param pszParams The parameter string sent by the client. + */ +static int netperfTCPServerParseParams(NETPERFPARAMS *pParams, char *pszParams) +{ + /* + * Set defaults for the dynamic settings. + */ + pParams->fNoDelay = false; + pParams->enmMode = NETPERFMODE_LATENCY; + pParams->cSecTimeout = NETPERF_DEFAULT_WARMUP; + pParams->cMsCoolDown = NETPERF_DEFAULT_COOL_DOWN; + pParams->cMsWarmup = NETPERF_DEFAULT_WARMUP; + pParams->cbPacket = NETPERF_DEFAULT_PKT_SIZE_LATENCY; + + /* + * Parse the client parameters. + */ + /* first arg: transport type. [mandatory] */ + char *pszCur = strchr(pszParams, ':'); + if (!pszCur) + return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: No colon\n"); + char *pszNext = strchr(++pszCur, ':'); + if (pszNext) + *pszNext++ = '\0'; + if (strcmp(pszCur, "TCP")) + return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: Invalid transport type: \"%s\"\n", pszCur); + pszCur = pszNext; + + /* second arg: mode. [mandatory] */ + if (!pszCur) + return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: Missing test mode\n"); + pszNext = strchr(pszCur, ':'); + if (pszNext) + *pszNext++ = '\0'; + pParams->enmMode = netperfModeFromString(pszCur); + if (pParams->enmMode == NETPERFMODE_INVALID) + return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: Invalid test mode: \"%s\"\n", pszCur); + pszCur = pszNext; + + /* + * The remainder are uint32_t or bool. + */ + struct + { + bool fBool; + bool fMandatory; + void *pvValue; + uint32_t uMin; + uint32_t uMax; + const char *pszName; + } aElements[] = + { + { false, true, &pParams->cSecTimeout, NETPERF_MIN_TIMEOUT, NETPERF_MAX_TIMEOUT, "timeout" }, + { false, true, &pParams->cbPacket, NETPERF_MIN_PKT_SIZE, NETPERF_MAX_PKT_SIZE, "packet size" }, + { false, true, &pParams->cMsWarmup, NETPERF_MIN_WARMUP, NETPERF_MAX_WARMUP, "warmup period" }, + { false, true, &pParams->cMsCoolDown, NETPERF_MIN_COOL_DOWN, NETPERF_MAX_COOL_DOWN, "cool down period" }, + { true, true, &pParams->fNoDelay, false, true, "no delay" }, + }; + + for (unsigned i = 0; i < RT_ELEMENTS(aElements); i++) + { + if (!pszCur) + return aElements[i].fMandatory + ? RTTestIFailedRc(VERR_PARSE_ERROR, "client params: missing %s\n", aElements[i].pszName) + : VINF_SUCCESS; + + pszNext = strchr(pszCur, ':'); + if (pszNext) + *pszNext++ = '\0'; + uint32_t u32; + int rc = RTStrToUInt32Full(pszCur, 10, &u32); + if (rc != VINF_SUCCESS) + return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: bad %s value \"%s\": %Rrc\n", + aElements[i].pszName, pszCur, rc); + + if ( u32 < aElements[i].uMin + || u32 > aElements[i].uMax) + return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: %s %u s is out of range (%u..%u)\n", + aElements[i].pszName, u32, aElements[i].uMin, aElements[i].uMax); + if (aElements[i].fBool) + *(bool *)aElements[i].pvValue = u32 ? true : false; + else + *(uint32_t *)aElements[i].pvValue = u32; + + pszCur = pszNext; + } + + /* Fail if too many elements. */ + if (pszCur) + return RTTestIFailedRc(VERR_PARSE_ERROR, "client params: too many elements: \"%s\"\n", + pszCur); + return VINF_SUCCESS; +} + + +/** + * TCP server callback that handles one client connection. + * + * @returns IPRT status code. VERR_TCP_SERVER_STOP is special. + * @param hSocket The client socket. + * @param pvUser Our parameters. + */ +static DECLCALLBACK(int) netperfTCPServerWorker(RTSOCKET hSocket, void *pvUser) +{ + NETPERFPARAMS *pParams = (NETPERFPARAMS *)pvUser; + AssertReturn(pParams, VERR_INVALID_POINTER); + + pParams->hSocket = hSocket; + + RTNETADDR Addr; + int rc = RTTcpGetPeerAddress(hSocket, &Addr); + if (RT_SUCCESS(rc)) + RTTestIPrintf(RTTESTLVL_ALWAYS, "Client connected from %RTnaddr\n", &Addr); + else + { + RTTestIPrintf(RTTESTLVL_ALWAYS, "Failed to get client details: %Rrc\n", rc); + Addr.enmType = RTNETADDRTYPE_INVALID; + } + + /* + * Adjust send and receive buffer sizes if necessary. + */ + if (pParams->cbBufferSize) + { + rc = RTTcpSetBufferSize(hSocket, pParams->cbBufferSize); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to set socket buffer sizes to %#x: %Rrc\n", pParams->cbBufferSize, rc); + } + + /* + * Greet the other dude. + */ + rc = RTTcpWrite(hSocket, g_ConnectStart, sizeof(g_ConnectStart) - 1); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to send connection start Id: %Rrc\n", rc); + + /* + * Read connection parameters. + */ + char szBuf[256]; + rc = RTTcpRead(hSocket, szBuf, NETPERF_LEN_PREFIX, NULL); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to read connection parameters: %Rrc\n", rc); + szBuf[NETPERF_LEN_PREFIX] = '\0'; + uint32_t cchParams; + rc = RTStrToUInt32Full(szBuf, 10, &cchParams); + if (rc != VINF_SUCCESS) + return RTTestIFailedRc(RT_SUCCESS(rc) ? VERR_INTERNAL_ERROR : rc, + "Failed to read connection parameters: %Rrc\n", rc); + if (cchParams >= sizeof(szBuf)) + return RTTestIFailedRc(VERR_TOO_MUCH_DATA, "parameter packet is too big (%u bytes)\n", cchParams); + rc = RTTcpRead(hSocket, szBuf, cchParams, NULL); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to read connection parameters: %Rrc\n", rc); + szBuf[cchParams] = '\0'; + + if (strncmp(szBuf, g_szStartParams, sizeof(g_szStartParams) - 1)) + return RTTestIFailedRc(VERR_NET_PROTOCOL_ERROR, "Invalid connection parameters '%s'\n", szBuf); + + /* + * Parse the parameters and signal whether we've got a deal or not. + */ + rc = netperfTCPServerParseParams(pParams, szBuf); + if (RT_FAILURE(rc)) + { + int rc2 = RTTcpWrite(hSocket, g_szNegative, sizeof(g_szNegative) - 1); + if (RT_FAILURE(rc2)) + RTTestIFailed("Failed to send negative ack: %Rrc\n", rc2); + return rc; + } + + if (Addr.enmType != RTNETADDRTYPE_INVALID) + RTTestISubF("%RTnaddr - %s, %u s, %u bytes", &Addr, + netperfModeToString(pParams->enmMode), pParams->cSecTimeout, pParams->cbPacket); + else + RTTestISubF("Unknown - %s, %u s, %u bytes", + netperfModeToString(pParams->enmMode), pParams->cSecTimeout, pParams->cbPacket); + + rc = RTTcpSetSendCoalescing(hSocket, !pParams->fNoDelay); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to apply no-delay option (%RTbool): %Rrc\n", pParams->fNoDelay, rc); + + rc = RTTcpWrite(hSocket, g_szAck, sizeof(g_szAck) - 1); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to send start test commend to client: %Rrc\n", rc); + + /* + * Take action according to our mode. + */ + switch (pParams->enmMode) + { + case NETPERFMODE_LATENCY: + rc = netperfTCPServerDoLatency(pParams); + break; + + case NETPERFMODE_THROUGHPUT: + rc = netperfTCPServerDoThroughput(pParams); + break; + + case NETPERFMODE_THROUGHPUT_XMIT: + rc = netperfTCPServerDoThroughputXmit(pParams); + break; + + case NETPERFMODE_THROUGHPUT_RECV: + rc = netperfTCPServerDoThroughputRecv(pParams); + break; + + case NETPERFMODE_INVALID: + rc = VERR_INTERNAL_ERROR; + break; + + /* no default! */ + } + if (rc == VERR_NO_MEMORY) + return VERR_TCP_SERVER_STOP; + + /* + * Wait for other clients or quit. + */ + if (pParams->fSingleClient) + return VERR_TCP_SERVER_STOP; + return VINF_SUCCESS; +} + + +/** + * TCP server. + * + * @returns IPRT status code. + * @param pParams The TCP parameter block. + */ +static int netperfTCPServer(NETPERFPARAMS *pParams) +{ + /* + * Spawn the TCP server thread & listen. + */ + PRTTCPSERVER pServer; + int rc = RTTcpServerCreateEx(NULL, pParams->uPort, &pServer); + if (RT_SUCCESS(rc)) + { + RTPrintf("Server listening on TCP port %d\n", pParams->uPort); + rc = RTTcpServerListen(pServer, netperfTCPServerWorker, pParams); + RTTcpServerDestroy(pServer); + } + else + RTPrintf("Failed to create TCP server thread: %Rrc\n", rc); + + return rc; +} + +/** + * The server part. + * + * @returns Exit code. + * @param enmProto The protocol. + * @param pParams The parameter block. + */ +static RTEXITCODE netperfServer(NETPERFPROTO enmProto, NETPERFPARAMS *pParams) +{ + + switch (enmProto) + { + case NETPERFPROTO_TCP: + { + int rc = netperfTCPServer(pParams); + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; + } + + default: + RTTestIFailed("Protocol not supported.\n"); + return RTEXITCODE_FAILURE; + } +} + + + + + +/** + * TCP client: Do the throughput test. + * + * @returns IPRT status code + * @param pParams The parameters. + */ +static int netperfTCPClientDoThroughput(NETPERFPARAMS *pParams) +{ + /* + * Allocate the buffer. + */ + NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket); + if (!pBuf) + return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory"); + + /* + * Send first, then Receive. + */ + NETPERFSTATS SendStats; + int rc = netperfTCPThroughputSend(pParams, pBuf, &SendStats); + if (RT_SUCCESS(rc)) + { + NETPERFSTATS SrvSendStats; + rc = netperfRecvStats(&SrvSendStats, pParams->hSocket); + if (RT_SUCCESS(rc)) + { + NETPERFSTATS RecvStats; + rc = netperfTCPThroughputRecv(pParams, pBuf, &RecvStats); + if (RT_SUCCESS(rc)) + { + NETPERFSTATS SrvRecvStats; + rc = netperfRecvStats(&SrvRecvStats, pParams->hSocket); + if (RT_SUCCESS(rc)) + { + if (pParams->fServerStats) + netperfPrintThroughputStats(&SrvSendStats, &SrvRecvStats, pParams->cbPacket); + else + netperfPrintThroughputStats(&SendStats, &RecvStats, pParams->cbPacket); + } + } + } + } + + RTTestISubDone(); + return rc; +} + +/** + * TCP client: Do the throughput xmit test. + * + * @returns IPRT status code + * @param pParams The parameters. + */ +static int netperfTCPClientDoThroughputXmit(NETPERFPARAMS *pParams) +{ + /* + * Allocate the buffer. + */ + NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket); + if (!pBuf) + return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory"); + + /* + * Do the job. + */ + NETPERFSTATS SendStats; + int rc = netperfTCPThroughputSend(pParams, pBuf, &SendStats); + if (RT_SUCCESS(rc)) + { + NETPERFSTATS SrvSendStats; + rc = netperfRecvStats(&SrvSendStats, pParams->hSocket); + if (RT_SUCCESS(rc)) + { + if (pParams->fServerStats) + netperfPrintThroughputStats(&SrvSendStats, NULL, pParams->cbPacket); + else + netperfPrintThroughputStats(&SendStats, NULL, pParams->cbPacket); + } + } + + RTTestISubDone(); + return rc; +} + +/** + * TCP client: Do the throughput recv test. + * + * @returns IPRT status code + * @param pParams The parameters. + */ +static int netperfTCPClientDoThroughputRecv(NETPERFPARAMS *pParams) +{ + /* + * Allocate the buffer. + */ + NETPERFHDR *pBuf = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket); + if (!pBuf) + return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory"); + + /* + * Do the job. + */ + NETPERFSTATS RecvStats; + int rc = netperfTCPThroughputRecv(pParams, pBuf, &RecvStats); + if (RT_SUCCESS(rc)) + { + NETPERFSTATS SrvRecvStats; + rc = netperfRecvStats(&SrvRecvStats, pParams->hSocket); + if (RT_SUCCESS(rc)) + { + if (pParams->fServerStats) + netperfPrintThroughputStats(NULL, &SrvRecvStats, pParams->cbPacket); + else + netperfPrintThroughputStats(NULL, &RecvStats, pParams->cbPacket); + } + } + + RTTestISubDone(); + return rc; +} + +/** + * TCP client: Do the latency test. + * + * @returns IPRT status code + * @param pParams The parameters. + */ +static int netperfTCPClientDoLatency(NETPERFPARAMS *pParams) +{ + /* + * Generate a selection of packages before we start, after all we're not + * benchmarking the random number generator, are we. :-) + */ + void *pvReadBuf = RTMemAllocZ(pParams->cbPacket); + if (!pvReadBuf) + return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory"); + + size_t i; + NETPERFHDR *apPackets[256]; + for (i = 0; i < RT_ELEMENTS(apPackets); i++) + { + apPackets[i] = (NETPERFHDR *)RTMemAllocZ(pParams->cbPacket); + if (!apPackets[i]) + { + while (i-- > 0) + RTMemFree(apPackets[i]); + RTMemFree(pvReadBuf); + return RTTestIFailedRc(VERR_NO_MEMORY, "Out of memory"); + } + RTRandBytes(apPackets[i], pParams->cbPacket); + apPackets[i]->u32Magic = RT_H2LE_U32_C(NETPERFHDR_MAGIC); + apPackets[i]->u32State = 0; + apPackets[i]->u32Seq = 0; + apPackets[i]->u32Reserved = 0; + } + + /* + * Create & start a timer to eventually disconnect. + */ + bool volatile fStop = false; + RTTIMERLR hTimer; + int rc = RTTimerLRCreateEx(&hTimer, 0 /* nsec */, RTTIMER_FLAGS_CPU_ANY, netperfStopTimerCallback, (void *)&fStop); + if (RT_SUCCESS(rc)) + { + uint32_t u32Seq = 0; + NETPERFSTATS Stats; + RT_ZERO(Stats); + + /* + * Warm up. + */ + if (g_uVerbosity > 0) + RTPrintf("Warmup...\n"); + rc = RTTimerLRStart(hTimer, pParams->cMsWarmup * UINT64_C(1000000) /* nsec */); + if (RT_SUCCESS(rc)) + { + while (!fStop) + { + NETPERFHDR *pPacket = apPackets[u32Seq % RT_ELEMENTS(apPackets)]; + u32Seq++; + pPacket->u32Seq = RT_H2LE_U32(u32Seq); + pPacket->u32State = RT_H2LE_U32_C(NETPERFHDR_WARMUP); + rc = RTTcpWrite(pParams->hSocket, pPacket, pParams->cbPacket); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTTcpWrite/warmup: %Rrc\n", rc); + break; + } + rc = RTTcpRead(pParams->hSocket, pvReadBuf, pParams->cbPacket, NULL); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTTcpRead/warmup: %Rrc\n", rc); + break; + } + } + } + else + RTTestIFailed("RTTimerLRStart/warmup: %Rrc\n", rc); + + /* + * The real thing. + */ + if (RT_SUCCESS(rc)) + { + if (g_uVerbosity > 0) + RTPrintf("The real thing...\n"); + fStop = false; + rc = RTTimerLRStart(hTimer, pParams->cSecTimeout * UINT64_C(1000000000) /* nsec */); + if (RT_SUCCESS(rc)) + { + uint64_t u64StartTS = RTTimeNanoTS(); + while (!fStop) + { + NETPERFHDR *pPacket = apPackets[u32Seq % RT_ELEMENTS(apPackets)]; + u32Seq++; + pPacket->u32Seq = RT_H2LE_U32(u32Seq); + pPacket->u32State = RT_H2LE_U32_C(NETPERFHDR_TESTING); + rc = RTTcpWrite(pParams->hSocket, pPacket, pParams->cbPacket); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTTcpWrite/testing: %Rrc\n", rc); + break; + } + Stats.cTx++; + + rc = RTTcpRead(pParams->hSocket, pvReadBuf, pParams->cbPacket, NULL); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTTcpRead/testing: %Rrc\n", rc); + break; + } + Stats.cRx++; + + if (!memcmp(pvReadBuf, pPacket, pParams->cbPacket)) + Stats.cEchos++; + else + Stats.cErrors++; + } + Stats.cNsElapsed = RTTimeNanoTS() - u64StartTS; + } + else + RTTestIFailed("RTTimerLRStart/testing: %Rrc\n", rc); + } + + /* + * Cool down. + */ + if (RT_SUCCESS(rc)) + { + if (g_uVerbosity > 0) + RTPrintf("Cool down...\n"); + fStop = false; + rc = RTTimerLRStart(hTimer, pParams->cMsCoolDown * UINT64_C(1000000) /* nsec */); + if (RT_SUCCESS(rc)) + { + while (!fStop) + { + NETPERFHDR *pPacket = apPackets[u32Seq % RT_ELEMENTS(apPackets)]; + u32Seq++; + pPacket->u32Seq = RT_H2LE_U32(u32Seq); + pPacket->u32State = RT_H2LE_U32_C(NETPERFHDR_COOL_DOWN); + rc = RTTcpWrite(pParams->hSocket, pPacket, pParams->cbPacket); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTTcpWrite/warmup: %Rrc\n", rc); + break; + } + rc = RTTcpRead(pParams->hSocket, pvReadBuf, pParams->cbPacket, NULL); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTTcpRead/warmup: %Rrc\n", rc); + break; + } + } + } + else + RTTestIFailed("RTTimerLRStart/testing: %Rrc\n", rc); + } + + /* + * Send DONE packet. + */ + if (g_uVerbosity > 0) + RTPrintf("Done\n"); + if (RT_SUCCESS(rc)) + { + u32Seq++; + NETPERFHDR *pPacket = apPackets[u32Seq % RT_ELEMENTS(apPackets)]; + pPacket->u32Seq = RT_H2LE_U32(u32Seq); + pPacket->u32State = RT_H2LE_U32_C(NETPERFHDR_DONE); + rc = RTTcpWrite(pParams->hSocket, pPacket, pParams->cbPacket); + if (RT_FAILURE(rc)) + RTTestIFailed("RTTcpWrite/done: %Rrc\n", rc); + } + + + /* + * Get and print stats. + */ + NETPERFSTATS SrvStats; + if (RT_SUCCESS(rc)) + { + rc = netperfRecvStats(&SrvStats, pParams->hSocket); + if (RT_SUCCESS(rc) && pParams->fServerStats) + netperfPrintLatencyStats(&SrvStats, pParams->cbPacket); + else if (!pParams->fServerStats) + netperfPrintLatencyStats(&Stats, pParams->cbPacket); + } + + /* clean up*/ + RTTimerLRDestroy(hTimer); + } + else + RTTestIFailed("Failed to create timer object: %Rrc\n", rc); + for (i = 0; i < RT_ELEMENTS(apPackets); i++) + RTMemFree(apPackets[i]); + + RTMemFree(pvReadBuf); + + return rc; +} + +/** + * TCP client test driver. + * + * @returns IPRT status code + * @param pszServer The server name. + * @param pParams The parameter structure. + */ +static int netperfTCPClient(const char *pszServer, NETPERFPARAMS *pParams) +{ + AssertReturn(pParams, VERR_INVALID_POINTER); + RTTestISubF("TCP - %u s, %u bytes%s", pParams->cSecTimeout, + pParams->cbPacket, pParams->fNoDelay ? ", no delay" : ""); + + RTSOCKET hSocket = NIL_RTSOCKET; + int rc = RTTcpClientConnect(pszServer, pParams->uPort, &hSocket); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to connect to %s on port %u: %Rrc\n", pszServer, pParams->uPort, rc); + pParams->hSocket = hSocket; + + /* + * Disable send coalescing (no-delay). + */ + if (pParams->fNoDelay) + { + rc = RTTcpSetSendCoalescing(hSocket, false /*fEnable*/); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to set no-delay option: %Rrc\n", rc); + } + + /* + * Adjust send and receive buffer sizes if necessary. + */ + if (pParams->cbBufferSize) + { + rc = RTTcpSetBufferSize(hSocket, pParams->cbBufferSize); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to set socket buffer sizes to %#x: %Rrc\n", pParams->cbBufferSize, rc); + } + + /* + * Verify the super secret Start Connect Id to start the connection. + */ + char szBuf[256 + NETPERF_LEN_PREFIX]; + RT_ZERO(szBuf); + rc = RTTcpRead(hSocket, szBuf, sizeof(g_ConnectStart) - 1, NULL); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to read connection initializer: %Rrc\n", rc); + + if (strcmp(szBuf, g_ConnectStart)) + return RTTestIFailedRc(VERR_INVALID_MAGIC, "Invalid connection initializer '%s'\n", szBuf); + + /* + * Send all the dynamic parameters to the server. + * (If the server is newer than the client, it will select default for any + * missing parameters.) + */ + size_t cchParams = RTStrPrintf(&szBuf[NETPERF_LEN_PREFIX], sizeof(szBuf) - NETPERF_LEN_PREFIX, + "%s:%s:%s:%u:%u:%u:%u:%u", + g_szStartParams, + "TCP", + netperfModeToString(pParams->enmMode), + pParams->cSecTimeout, + pParams->cbPacket, + pParams->cMsWarmup, + pParams->cMsCoolDown, + pParams->fNoDelay); + RTStrPrintf(szBuf, NETPERF_LEN_PREFIX + 1, "%0*u", NETPERF_LEN_PREFIX, cchParams); + szBuf[NETPERF_LEN_PREFIX] = g_szStartParams[0]; + Assert(strlen(szBuf) == NETPERF_LEN_PREFIX + cchParams); + rc = RTTcpWrite(hSocket, szBuf, NETPERF_LEN_PREFIX + cchParams); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to send connection parameters: %Rrc\n", rc); + + /* + * Wait for acknowledgment. + */ + rc = RTTcpRead(hSocket, szBuf, sizeof(g_szAck) - 1, NULL); + if (RT_FAILURE(rc)) + return RTTestIFailedRc(rc, "Failed to send parameters: %Rrc\n", rc); + szBuf[sizeof(g_szAck) - 1] = '\0'; + + if (!strcmp(szBuf, g_szNegative)) + return RTTestIFailedRc(VERR_NET_PROTOCOL_ERROR, "Server failed to accept packet size of %u bytes.\n", pParams->cbPacket); + if (strcmp(szBuf, g_szAck)) + return RTTestIFailedRc(VERR_NET_PROTOCOL_ERROR, "Invalid response from server '%s'\n", szBuf); + + /* + * Take action according to our mode. + */ + switch (pParams->enmMode) + { + case NETPERFMODE_LATENCY: + RTTestIPrintf(RTTESTLVL_ALWAYS, "Connected to %s port %u, running the latency test for %u seconds.\n", + pszServer, pParams->uPort, pParams->cSecTimeout); + rc = netperfTCPClientDoLatency(pParams); + break; + + case NETPERFMODE_THROUGHPUT: + RTTestIPrintf(RTTESTLVL_ALWAYS, "Connected to %s port %u, running the throughput test for %u seconds in each direction.\n", + pszServer, pParams->uPort, pParams->cSecTimeout); + rc = netperfTCPClientDoThroughput(pParams); + break; + + case NETPERFMODE_THROUGHPUT_XMIT: + RTTestIPrintf(RTTESTLVL_ALWAYS, "Connected to %s port %u, running the throughput-xmit test for %u seconds.\n", + pszServer, pParams->uPort, pParams->cSecTimeout); + rc = netperfTCPClientDoThroughputXmit(pParams); + break; + + case NETPERFMODE_THROUGHPUT_RECV: + RTTestIPrintf(RTTESTLVL_ALWAYS, "Connected to %s port %u, running the throughput-recv test for %u seconds.\n", + pszServer, pParams->uPort, pParams->cSecTimeout); + rc = netperfTCPClientDoThroughputRecv(pParams); + break; + + case NETPERFMODE_INVALID: + rc = VERR_INTERNAL_ERROR; + break; + + /* no default! */ + } + return rc; +} + +/** + * The client part. + * + * @returns Exit code. + * @param enmProto The protocol. + * @param pszServer The server name. + * @param pvUser The parameter block as opaque user data. + */ +static RTEXITCODE netperfClient(NETPERFPROTO enmProto, const char *pszServer, void *pvUser) +{ + switch (enmProto) + { + case NETPERFPROTO_TCP: + { + NETPERFPARAMS *pParams = (NETPERFPARAMS *)pvUser; + int rc = netperfTCPClient(pszServer, pParams); + if (pParams->hSocket != NIL_RTSOCKET) + { + RTTcpClientClose(pParams->hSocket); + pParams->hSocket = NIL_RTSOCKET; + } + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; + } + + default: + RTTestIFailed("Protocol not supported.\n"); + return RTEXITCODE_FAILURE; + } +} + + +int main(int argc, char *argv[]) +{ + /* + * Init IPRT and globals. + */ + int rc = RTTestInitAndCreate("NetPerf", &g_hTest); + if (rc) + return rc; + + /* + * Special case. + */ + if (argc < 2) + { + RTTestFailed(g_hTest, "No arguments given."); + return RTTestSummaryAndDestroy(g_hTest); + } + + /* + * Default values. + */ + NETPERFPROTO enmProtocol = NETPERFPROTO_TCP; + bool fServer = true; + bool fDaemonize = false; + bool fDaemonized = false; + bool fPacketSizeSet = false; + const char *pszServerAddress= NULL; + + NETPERFPARAMS Params; + Params.uPort = NETPERF_DEFAULT_PORT; + Params.fServerStats = false; + Params.fSingleClient = false; + + Params.fNoDelay = false; + Params.fCheckData = false; + Params.enmMode = NETPERFMODE_LATENCY; + Params.cSecTimeout = NETPERF_DEFAULT_TIMEOUT; + Params.cMsWarmup = NETPERF_DEFAULT_WARMUP; + Params.cMsCoolDown = NETPERF_DEFAULT_COOL_DOWN; + Params.cbPacket = NETPERF_DEFAULT_PKT_SIZE_LATENCY; + Params.cbBufferSize = 0; + + Params.hSocket = NIL_RTSOCKET; + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */); + while ((rc = RTGetOpt(&GetState, &ValueUnion))) + { + switch (rc) + { + case 's': + fServer = true; + break; + + case 'c': + fServer = false; + pszServerAddress = ValueUnion.psz; + break; + + case 'd': + fDaemonize = true; + break; + + case 'D': + fDaemonized = true; + break; + + case 'i': + Params.cSecTimeout = ValueUnion.u32; + if ( Params.cSecTimeout < NETPERF_MIN_TIMEOUT + || Params.cSecTimeout > NETPERF_MAX_TIMEOUT) + { + RTTestFailed(g_hTest, "Invalid interval %u s, valid range: %u-%u\n", + Params.cbPacket, NETPERF_MIN_TIMEOUT, NETPERF_MAX_TIMEOUT); + return RTTestSummaryAndDestroy(g_hTest); + } + break; + + case 'l': + Params.cbPacket = ValueUnion.u32; + if ( Params.cbPacket < NETPERF_MIN_PKT_SIZE + || Params.cbPacket > NETPERF_MAX_PKT_SIZE) + { + RTTestFailed(g_hTest, "Invalid packet size %u bytes, valid range: %u-%u\n", + Params.cbPacket, NETPERF_MIN_PKT_SIZE, NETPERF_MAX_PKT_SIZE); + return RTTestSummaryAndDestroy(g_hTest); + } + fPacketSizeSet = true; + break; + + case 'm': + Params.enmMode = netperfModeFromString(ValueUnion.psz); + if (Params.enmMode == NETPERFMODE_INVALID) + { + RTTestFailed(g_hTest, "Invalid test mode: \"%s\"\n", ValueUnion.psz); + return RTTestSummaryAndDestroy(g_hTest); + } + if (!fPacketSizeSet) + switch (Params.enmMode) + { + case NETPERFMODE_LATENCY: + Params.cbPacket = NETPERF_DEFAULT_PKT_SIZE_LATENCY; + break; + case NETPERFMODE_THROUGHPUT: + case NETPERFMODE_THROUGHPUT_XMIT: + case NETPERFMODE_THROUGHPUT_RECV: + Params.cbPacket = NETPERF_DEFAULT_PKT_SIZE_THROUGHPUT; + break; + case NETPERFMODE_INVALID: + break; + /* no default! */ + } + break; + + case 'p': + Params.uPort = ValueUnion.u32; + break; + + case 'N': + Params.fNoDelay = true; + break; + + case 'S': + Params.fServerStats = true; + break; + + case '1': + Params.fSingleClient = true; + break; + + case 'v': + g_uVerbosity++; + break; + + case 'h': + Usage(g_pStdOut); + return RTEXITCODE_SUCCESS; + + case 'V': + RTPrintf("$Revision: 153224 $\n"); + return RTEXITCODE_SUCCESS; + + case 'w': + Params.cMsWarmup = ValueUnion.u32; + if ( Params.cMsWarmup < NETPERF_MIN_WARMUP + || Params.cMsWarmup > NETPERF_MAX_WARMUP) + { + RTTestFailed(g_hTest, "invalid warmup time %u ms, valid range: %u-%u\n", + Params.cMsWarmup, NETPERF_MIN_WARMUP, NETPERF_MAX_WARMUP); + return RTTestSummaryAndDestroy(g_hTest); + } + break; + + case 'W': + Params.cMsCoolDown = ValueUnion.u32; + if ( Params.cMsCoolDown < NETPERF_MIN_COOL_DOWN + || Params.cMsCoolDown > NETPERF_MAX_COOL_DOWN) + { + RTTestFailed(g_hTest, "invalid cool down time %u ms, valid range: %u-%u\n", + Params.cMsCoolDown, NETPERF_MIN_COOL_DOWN, NETPERF_MAX_COOL_DOWN); + return RTTestSummaryAndDestroy(g_hTest); + } + break; + + case 'C': + Params.fCheckData = true; + break; + + case 'b': + Params.cbBufferSize = ValueUnion.u32; + if ( ( Params.cbBufferSize < NETPERF_MIN_BUF_SIZE + || Params.cbBufferSize > NETPERF_MAX_BUF_SIZE) + && Params.cbBufferSize != 0) + { + RTTestFailed(g_hTest, "Invalid packet size %u bytes, valid range: %u-%u or 0\n", + Params.cbBufferSize, NETPERF_MIN_BUF_SIZE, NETPERF_MAX_BUF_SIZE); + return RTTestSummaryAndDestroy(g_hTest); + } + break; + + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + /* + * Handle the server process daemoniziation. + */ + if (fDaemonize && !fDaemonized && fServer) + { + rc = RTProcDaemonize(argv, "--daemonized"); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcDaemonize failed: %Rrc\n", rc); + return RTEXITCODE_SUCCESS; + } + + /* + * Get down to business. + */ + RTTestBanner(g_hTest); + if (fServer) + rc = netperfServer(enmProtocol, &Params); + else if (pszServerAddress) + rc = netperfClient(enmProtocol, pszServerAddress, &Params); + else + RTTestFailed(g_hTest, "missing server address to connect to\n"); + + RTEXITCODE rc2 = RTTestSummaryAndDestroy(g_hTest); + return rc2 != RTEXITCODE_FAILURE ? (RTEXITCODE)rc2 : rc; +} + diff --git a/src/VBox/ValidationKit/utils/nt/Makefile.kmk b/src/VBox/ValidationKit/utils/nt/Makefile.kmk new file mode 100644 index 00000000..cd751ce0 --- /dev/null +++ b/src/VBox/ValidationKit/utils/nt/Makefile.kmk @@ -0,0 +1,63 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Windows NT Specific Utilities. +# + +# +# Copyright (C) 2010-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Set Clock Frequency Utility. +# +PROGRAMS.win += ntSetFreq +ntSetFreq_TEMPLATE = VBoxValidationKitR3 +ntSetFreq_SOURCES = ntsetfreq.cpp +ntSetFreq_VBOX_IMPORT_CHECKER.win.x86 = nt350 + +# +# Test coherency among NT time sources. +# +PROGRAMS.win += ntTimeSources +ntTimeSources_TEMPLATE = VBoxValidationKitR3 +ntTimeSources_SOURCES = nttimesources.cpp + +# +# Test NtFlushVirtualMemory. +# +PROGRAMS.win += ntFlushVirtualMemory +ntFlushVirtualMemory_TEMPLATE = VBoxValidationKitR3 +ntFlushVirtualMemory_SOURCES = ntFlushVirtualMemory.cpp + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/nt/ntFlushVirtualMemory.cpp b/src/VBox/ValidationKit/utils/nt/ntFlushVirtualMemory.cpp new file mode 100644 index 00000000..fe30fb6f --- /dev/null +++ b/src/VBox/ValidationKit/utils/nt/ntFlushVirtualMemory.cpp @@ -0,0 +1,498 @@ +/* $Id: ntFlushVirtualMemory.cpp $ */ +/** @file + * Memory mapped files testcase - NT. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/nt/nt.h> + +#include <iprt/alloca.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/err.h> +#include <iprt/x86.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Create page signature. */ +#define MAKE_PAGE_SIGNATURE(a_iPage) ((a_iPage) | UINT32_C(0x42000000) ) + +/** Number history entries on the page. */ +#define NUM_ROUND_HISTORY 16 + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** How chatty we should be. */ +static uint32_t g_cVerbosity = 0; + + +/** + * Checks if the on-disk file matches our expectations. + * + * @returns IPRT status code, fully bitched. + * @param pszFilename The name of the file. + * @param pu32BufChk Buffer to read the file into + * @param pu32BufOrg Expected file content. + * @param cbBuf The buffer size. + * @param iRound The update round. + */ +static int CheckFile(const char *pszFilename, uint32_t *pu32BufChk, uint32_t const *pu32BufOrg, size_t cbBuf, uint32_t iRound) +{ + /* + * Open and read the file into memory. + */ + HANDLE hFile; + int rc = RTNtPathOpen(pszFilename, + GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_NO_INTERMEDIATE_BUFFERING, + OBJ_CASE_INSENSITIVE, + &hFile, + NULL); + if (RT_SUCCESS(rc)) + { + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtReadFile(hFile, NULL /*hEvent*/, NULL /*pfnApc*/, NULL /*pvApcCtx*/, + &Ios, pu32BufChk, (ULONG)cbBuf, NULL /*poffFile*/, NULL /*pvKey*/); + if (NT_SUCCESS(rcNt) || Ios.Information != cbBuf) + { + /* + * See if the content of the file matches our expectations. + */ + if (memcmp(pu32BufChk, pu32BufOrg, cbBuf) == 0) + { /* matches - likely */ } + else + { + RTMsgError("Round %u: Buffer mismatch!\n", iRound); + + /* Try figure where the differences are. */ + size_t const cPages = cbBuf / X86_PAGE_SIZE; + size_t const cItemsPerPage = X86_PAGE_SIZE / sizeof(pu32BufOrg[0]); + for (uint32_t iPage = 0; iPage < cPages; iPage++) + for (uint32_t iItem = 0; iItem < cItemsPerPage; iItem++) + { + uint32_t uValue = pu32BufChk[iPage * cItemsPerPage + iItem]; + uint32_t uExpected = pu32BufOrg[iPage * cItemsPerPage + iItem]; + if (uValue != uExpected) + RTMsgError("Round %u: page #%u, index #%u: %#x, expected %#x\n", + iRound, iPage, iItem, uValue, uExpected); + } + + rc = VERR_MISMATCH; + } + } + else if (NT_SUCCESS(rcNt)) + { + RTMsgError("Round %u: NtReadFile return %zu bytes intead of %zu!\n", iRound, Ios.Information, cbBuf); + rc = VERR_READ_ERROR; + } + else + { + RTMsgError("Round %u: NtReadFile(%#x) failed: %#x (%#x)\n", iRound, cbBuf, rcNt, Ios.Status); + rc = RTErrConvertFromNtStatus(rcNt); + } + + /* + * Close the file and return. + */ + rcNt = NtClose(hFile); + if (!NT_SUCCESS(rcNt)) + { + RTMsgError("Round %u: NtCloseFile() failed: %#x\n", iRound, rcNt); + rc = RTErrConvertFromNtStatus(rcNt); + } + } + else + RTMsgError("Round %u: RTNtPathOpen() failed: %Rrc\n", iRound, rc); + return rc; +} + + +/** + * Manually checks whether the buffer matches up to our expectations. + * + * @returns IPRT status code, fully bitched. + * @param pu32Buf The buffer/mapping to check. + * @param cbBuf The buffer size. + * @param iRound The update round. + * @param cFlushesLeft Number of flushes left in the round. + */ +static int CheckBuffer(uint32_t const *pu32Buf, size_t cbBuf, uint32_t iRound, uint32_t cFlushesLeft) +{ + size_t const cPages = cbBuf / X86_PAGE_SIZE; + size_t const cItemsPerPage = X86_PAGE_SIZE / sizeof(pu32Buf[0]); + size_t const offPage = iRound & (NUM_ROUND_HISTORY - 1); + uint32_t const uValue = iRound | (cFlushesLeft << 20); +//RTPrintf("debug: CheckBuffer: %p %u/%u\n", pu32Buf, iRound, cFlushesLeft); + + for (uint32_t iPage = 0; iPage < cPages; iPage++) + { + uint32_t uActual = pu32Buf[iPage * cItemsPerPage + offPage]; + if (uActual != uValue) + { + RTMsgError("Round %u/%u: page #%u: last entry is corrupted: %#x, expected %#x\n", + iRound, cFlushesLeft, iPage, uActual, uValue); + return VERR_MISMATCH; + } + + uActual = pu32Buf[iPage * cItemsPerPage + cItemsPerPage - 1]; + if (uActual != MAKE_PAGE_SIGNATURE(iPage)) + { + RTMsgError("Round %u/%u: page #%u magic corrupted: %#x, expected %#x\n", + iRound, cFlushesLeft, iPage, uActual, MAKE_PAGE_SIGNATURE(iPage)); + return VERR_INVALID_MAGIC; + } + } + + /* + * Check previous rounds. + */ + for (uint32_t cRoundsAgo = 1; cRoundsAgo < NUM_ROUND_HISTORY - 1 && cRoundsAgo <= iRound; cRoundsAgo++) + { + uint32_t iOldRound = iRound - cRoundsAgo; + size_t const offOldPage = iOldRound & (NUM_ROUND_HISTORY - 1); + for (uint32_t iPage = 0; iPage < cPages; iPage++) + { + uint32_t uActual = pu32Buf[iPage * cItemsPerPage + offOldPage]; + if (uActual != iOldRound) + { + RTMsgError("Round %u/%u: page #%u: entry from %u rounds ago is corrupted: %#x, expected %#x\n", + iRound, cFlushesLeft, iPage, cRoundsAgo, uActual, uValue); + return VERR_MISMATCH; + } + } + } + + return VINF_SUCCESS; +} + + +/** + * Updates the buffer. + * + * @param pu32Buf The buffer/mapping to update. + * @param cbBuf The buffer size. + * @param iRound The update round. + * @param cFlushesLeft Number of flushes left in this round. + */ +static void UpdateBuffer(uint32_t *pu32Buf, size_t cbBuf, uint32_t iRound, uint32_t cFlushesLeft) +{ + size_t const cPages = cbBuf / X86_PAGE_SIZE; + size_t const cItemsPerPage = X86_PAGE_SIZE / sizeof(pu32Buf[0]); + size_t const offPage = iRound & (NUM_ROUND_HISTORY - 1); + uint32_t const uValue = iRound | (cFlushesLeft << 20); +//RTPrintf("debug: UpdateBuffer: %p %u/%u\n", pu32Buf, iRound, cFlushesLeft); + + for (uint32_t iPage = 0; iPage < cPages; iPage++) + pu32Buf[iPage * cItemsPerPage + offPage] = uValue; +} + + + +/** + * Modifies the file via memory mapping. + * + * @returns IPRT status code, fully bitched. + * @param pszFilename The file we're using as a test bed. + * @param pu32BufOrg The sane copy of the file that gets updated in + * parallel. + * @param cbBuf The size of the file and bufer. + * @param iRound The current round number. + * @param fCheckFirst Whether to read from the mapping the mapping + * before dirtying it the first time around. + * @param fCheckAfterFlush Whether to read from the mapping the mapping + * before dirtying it after a flush. + * @param cFlushes How many times we modify the mapping and flush + * it before one final modification and unmapping. + * @param fLargePages Whether to use large pages. + */ +static int MakeModifications(const char *pszFilename, uint32_t *pu32BufOrg, size_t cbBuf, uint32_t iRound, + bool fCheckFirst, bool fCheckAfterFlush, uint32_t cFlushes, bool fLargePages) +{ + + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + int rc = RTNtPathOpen(pszFilename, + GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_NO_INTERMEDIATE_BUFFERING, + OBJ_CASE_INSENSITIVE, + &hFile, + NULL); + if (RT_SUCCESS(rc)) + { + HANDLE hSection; + NTSTATUS rcNt = NtCreateSection(&hSection, + SECTION_ALL_ACCESS, + NULL, /*pObjAttrs*/ + NULL, /*pcbMax*/ + PAGE_READWRITE, + SEC_COMMIT, + hFile); + NtClose(hFile); + if (NT_SUCCESS(rcNt)) + { + PVOID pvMapping = NULL; + SIZE_T cbMapping = 0; + rcNt = NtMapViewOfSection(hSection, NtCurrentProcess(), + &pvMapping, + 0, /* ZeroBits */ + 0, /* CommitSize */ + NULL, /* SectionOffset */ + &cbMapping, + ViewUnmap, + fLargePages ? MEM_LARGE_PAGES : 0, + PAGE_READWRITE); + if (NT_SUCCESS(rcNt)) + { + /* + * Make the modifications. + */ + if (g_cVerbosity >= 2) + RTPrintf("debug: pvMapping=%p LB %#x\n", pvMapping, cbBuf); + + for (uint32_t iInner = 0;; iInner++) + { + if (iInner ? fCheckAfterFlush : fCheckFirst) + { + if (iInner == 0) + rc = CheckBuffer((uint32_t *)pvMapping, cbBuf, iRound - 1, 0); + else + rc = CheckBuffer((uint32_t *)pvMapping, cbBuf, iRound, cFlushes - iInner + 1); + if (RT_FAILURE(rc)) + { + RTMsgError("Round %u/%u: NtUnmapViewOfSection failed: %#x\n", iRound, rcNt); + break; + } + } + + UpdateBuffer((uint32_t *)pvMapping, cbBuf, iRound, cFlushes - iInner); + UpdateBuffer(pu32BufOrg, cbBuf, iRound, cFlushes - iInner); + + if (iInner >= cFlushes) + break; + + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + SIZE_T cbBuf2 = cbBuf; + PVOID pvMapping2 = pvMapping; + rcNt = NtFlushVirtualMemory(NtCurrentProcess(), &pvMapping2, &cbBuf2, &Ios); + if (!NT_SUCCESS(rcNt)) + { + RTMsgError("Round %u: NtFlushVirtualMemory failed: %#x\n", iRound, rcNt); + rc = RTErrConvertFromNtStatus(rcNt); + break; + } + } + + /* + * Cleanup. + */ + rcNt = NtUnmapViewOfSection(NtCurrentProcess(), pvMapping); + if (!NT_SUCCESS(rcNt)) + { + RTMsgError("Round %u: NtUnmapViewOfSection failed: %#x\n", iRound, rcNt); + rc = RTErrConvertFromNtStatus(rcNt); + } + } + else + { + RTMsgError("Round %u: NtMapViewOfSection failed: %#x\n", iRound, rcNt); + rc = RTErrConvertFromNtStatus(rcNt); + } + + rcNt = NtClose(hSection); + if (!NT_SUCCESS(rcNt)) + { + RTMsgError("Round %u: NtClose(hSection) failed: %#x\n", iRound, rcNt); + rc = RTErrConvertFromNtStatus(rcNt); + } + } + else + { + RTMsgError("Round %u: NtCreateSection failed: %#x\n", iRound, rcNt); + rc = RTErrConvertFromNtStatus(rcNt); + } + } + else + RTMsgError("Round %u: Error opening file '%s' for memory mapping: %Rrc\n", iRound, pszFilename, rc); + return rc; +} + + +int main(int argc, char **argv) +{ + /* + * Init IPRT. + */ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Parse arguments. + */ + const char *pszFilename = NULL; + uint32_t cRounds = 4096; + uint32_t cPages = 128; + bool fLargePages = false; + + static const RTGETOPTDEF s_aOptions[] = + { + { "--rounds", 'r', RTGETOPT_REQ_UINT32 }, + { "--pages", 'p', RTGETOPT_REQ_UINT32 }, + { "--filename", 'f', RTGETOPT_REQ_STRING }, + { "--large-pages", 'l', RTGETOPT_REQ_NOTHING }, + { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + }; + + RTGETOPTSTATE State; + RTGetOptInit(&State, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0); + RTGETOPTUNION ValueUnion; + int chOpt; + while ((chOpt = RTGetOpt(&State, &ValueUnion)) != 0) + { + switch (chOpt) + { + case 'r': cRounds = ValueUnion.u32; break; + case 'p': cPages = ValueUnion.u32; break; + case 'f': pszFilename = ValueUnion.psz; break; + case 'l': fLargePages = true; break; + case 'q': g_cVerbosity = 0; break; + case 'v': g_cVerbosity += 1; break; + case 'h': + RTPrintf("usage: ntFlushVirtualMemory [-c <times>] [-p <pages>] [-l|--large-pages] [-f <filename>]\n" + "\n" + "Aims at testing memory mapped files on NT w/ NtFlushVirtualMemory / FlushViewOfFile.\n"); + return 0; + + default: + return RTGetOptPrintError(chOpt, &ValueUnion); + } + } + + /* + * Allocate buffers and initialize the original with page numbers. + * + * We keep a original copy that gets updated in parallel to the memory + * mapping, allowing for simple file initialization and memcpy checking. + * + * The second buffer is for reading the file from disk and check. + */ + size_t const cbBuf = cPages * X86_PAGE_SIZE; + size_t const cItemsPerPage = X86_PAGE_SIZE / sizeof(uint32_t); + uint32_t *pu32BufOrg = (uint32_t *)RTMemPageAllocZ(cbBuf); + uint32_t *pu32BufChk = (uint32_t *)RTMemPageAllocZ(cbBuf); + if (pu32BufOrg == NULL || pu32BufChk == NULL) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to allocate two %zu sized buffers!\n", cbBuf); + + for (uint32_t iPage = 0; iPage < cPages; iPage++) + pu32BufOrg[iPage * cItemsPerPage + cItemsPerPage - 1] = MAKE_PAGE_SIGNATURE(iPage); + + rc = CheckBuffer(pu32BufOrg, cbBuf, 0, 0); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Internal error: CheckBuffer failed on virgin buffer: %Rrc\n", rc); + + /* + * Open the file and write out the orignal one. + */ + RTFILE hFile; + if (!pszFilename) + { + char *pszBuf = (char *)alloca(RTPATH_MAX); + rc = RTFileOpenTemp(&hFile, pszBuf, RTPATH_MAX, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create temporary file: %Rrc\n", rc); + pszFilename = pszBuf; + } + else + { + rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_READWRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open '%s': %Rrc\n", rc); + } + + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + + rc = RTFileWrite(hFile, pu32BufOrg, cbBuf, NULL); + if (RT_SUCCESS(rc)) + { + RTFileClose(hFile); + + /* + * Do the rounds. We count from 1 here to make verifying the previous round simpler. + */ + for (uint32_t iRound = 1; iRound <= cRounds; iRound++) + { + rc = MakeModifications(pszFilename, pu32BufOrg, cbBuf, iRound, + ((iRound >> 5) & 1) == 1, ((iRound >> 5) & 3) == 3, (iRound >> 3) & 31, fLargePages); + if (RT_SUCCESS(rc)) + { + rc = CheckBuffer(pu32BufOrg, cbBuf, iRound, 0); + if (RT_SUCCESS(rc)) + { + rc = CheckFile(pszFilename, pu32BufChk, pu32BufOrg, cbBuf, iRound); + if (RT_SUCCESS(rc)) + continue; + } + } + break; + } + } + else + { + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Error writing initial %zu bytes to '%s': %Rrc\n", cbBuf, rc); + RTFileClose(hFile); + } + RTFileDelete(pszFilename); + return rcExit; +} + diff --git a/src/VBox/ValidationKit/utils/nt/ntsetfreq.cpp b/src/VBox/ValidationKit/utils/nt/ntsetfreq.cpp new file mode 100644 index 00000000..88481487 --- /dev/null +++ b/src/VBox/ValidationKit/utils/nt/ntsetfreq.cpp @@ -0,0 +1,161 @@ +/* $Id: ntsetfreq.cpp $ */ +/** @file + * Set the NT timer frequency. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/nt/nt.h> + +#include <iprt/initterm.h> +#include <iprt/getopt.h> +#include <iprt/message.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/errcore.h> + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Parse arguments. + */ + bool fVerbose = true; + uint32_t u32NewRes = 0; + uint32_t cSecsSleep = UINT32_MAX; + + static const RTGETOPTDEF s_aOptions[] = + { + { "--resolution", 'r', RTGETOPT_REQ_UINT32 }, + { "--sleep", 's', RTGETOPT_REQ_UINT32 }, + { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + }; + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0); + while ((rc = RTGetOpt(&GetState, &ValueUnion))) + { + switch (rc) + { + case 'r': + u32NewRes = ValueUnion.u32; + if (u32NewRes > 16*10000 /* 16 ms */ || u32NewRes < 1000 /* 100 microsec */) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, + "syntax error: the new timer resolution (%RU32) is out of range\n", + u32NewRes); + break; + + case 's': + cSecsSleep = ValueUnion.u32; + break; + + case 'q': + fVerbose = false; + break; + + case 'v': + fVerbose = true; + break; + + case 'h': + RTPrintf("Usage: ntsetfreq [-q|--quiet] [-v|--verbose] [-r|--resolution <100ns>] [-s|--sleep <1s>]\n"); + return RTEXITCODE_SUCCESS; + + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + + /* + * Query the current resolution. + */ + ULONG Cur = UINT32_MAX; + ULONG Min = UINT32_MAX; + ULONG Max = UINT32_MAX; + NTSTATUS rcNt = STATUS_SUCCESS; + if (fVerbose || !u32NewRes) + { + rcNt = NtQueryTimerResolution(&Min, &Max, &Cur); + if (NT_SUCCESS(rcNt)) + RTMsgInfo("cur: %u (%u.%02u Hz) min: %u (%u.%02u Hz) max: %u (%u.%02u Hz)\n", + Cur, 10000000 / Cur, (10000000 / (Cur * 100)) % 100, + Min, 10000000 / Min, (10000000 / (Min * 100)) % 100, + Max, 10000000 / Max, (10000000 / (Max * 100)) % 100); + else + RTMsgError("NTQueryTimerResolution failed with status %#x\n", rcNt); + } + + if (u32NewRes) + { + rcNt = NtSetTimerResolution(u32NewRes, TRUE, &Cur); + if (!NT_SUCCESS(rcNt)) + RTMsgError("NTSetTimerResolution(%RU32,,) failed with status %#x\n", u32NewRes, rcNt); + else if (fVerbose) + { + Cur = Min = Max = UINT32_MAX; + rcNt = NtQueryTimerResolution(&Min, &Max, &Cur); + if (NT_SUCCESS(rcNt)) + RTMsgInfo("new: %u (%u.%02u Hz) requested %RU32 (%u.%02u Hz)\n", + Cur, 10000000 / Cur, (10000000 / (Cur * 100)) % 100, + u32NewRes, 10000000 / u32NewRes, (10000000 / (u32NewRes * 100)) % 100); + else + RTMsgError("NTSetTimerResolution succeeded but the NTQueryTimerResolution call failed with status %#x (ignored)\n", + rcNt); + rcNt = STATUS_SUCCESS; + } + } + + if (u32NewRes && NT_SUCCESS(rcNt)) + { + if (cSecsSleep == UINT32_MAX) + for (;;) + RTThreadSleep(RT_INDEFINITE_WAIT); + else + while (cSecsSleep-- > 0) + RTThreadSleep(1000); + } + + return NT_SUCCESS(rcNt) ? 0 : 1; +} + diff --git a/src/VBox/ValidationKit/utils/nt/nttimesources.cpp b/src/VBox/ValidationKit/utils/nt/nttimesources.cpp new file mode 100644 index 00000000..77267cfc --- /dev/null +++ b/src/VBox/ValidationKit/utils/nt/nttimesources.cpp @@ -0,0 +1,246 @@ +/* $Id: nttimesources.cpp $ */ +/** @file + * Check the various time sources on Windows NT. + */ + +/* + * Copyright (C) 2009-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/win/windows.h> + +#include <iprt/asm.h> +#include <iprt/asm-amd64-x86.h> +#include <iprt/errcore.h> +#include <iprt/string.h> +#include <iprt/test.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct _MY_KSYSTEM_TIME +{ + ULONG LowPart; + LONG High1Time; + LONG High2Time; +} MY_KSYSTEM_TIME; + +typedef struct _MY_KUSER_SHARED_DATA +{ + ULONG TickCountLowDeprecated; + ULONG TickCountMultiplier; + volatile MY_KSYSTEM_TIME InterruptTime; + volatile MY_KSYSTEM_TIME SystemTime; + volatile MY_KSYSTEM_TIME TimeZoneBias; + /* The rest is not relevant. */ +} MY_KUSER_SHARED_DATA; + +/** The fixed pointer to the user shared data. */ +#define MY_USER_SHARED_DATA ((MY_KUSER_SHARED_DATA *)0x7ffe0000) + +/** Spins until GetTickCount() changes. */ +static void SpinUntilTick(void) +{ + /* spin till GetTickCount changes. */ + DWORD dwMsTick = GetTickCount(); + while (GetTickCount() == dwMsTick) + /* nothing */; +} + +/** Delay function that tries to return right after GetTickCount changed. */ +static void DelayMillies(DWORD dwMsStart, DWORD cMillies) +{ + /* Delay cMillies - 1. */ + Sleep(cMillies - 1); + while (GetTickCount() - dwMsStart < cMillies - 1U) + Sleep(1); + + SpinUntilTick(); +} + + +int main(int argc, char **argv) +{ + RT_NOREF1(argv); + + /* + * Init, create a test instance and "parse" arguments. + */ + RTTEST hTest; + int rc = RTTestInitAndCreate("nttimesources", &hTest); + if (rc) + return rc; + if (argc > 1) + { + RTTestFailed(hTest, "Syntax error! no arguments expected"); + return RTTestSummaryAndDestroy(hTest); + } + + /* + * Guess MHz using GetTickCount. + */ + RTTestSub(hTest, "Guess MHz"); + DWORD dwTickStart, dwTickEnd, cMsTicks; + uint64_t u64TscStart, u64TscEnd, cTscTicks; + + /* get a good start time. */ + SpinUntilTick(); + do + { + dwTickStart = GetTickCount(); + ASMCompilerBarrier(); + ASMSerializeInstruction(); + u64TscStart = ASMReadTSC(); + ASMCompilerBarrier(); + } while (GetTickCount() != dwTickStart); + + /* delay a good while. */ + DelayMillies(dwTickStart, 256); + + /* get a good end time. */ + do + { + dwTickEnd = GetTickCount(); + ASMCompilerBarrier(); + ASMSerializeInstruction(); + u64TscEnd = ASMReadTSC(); + ASMCompilerBarrier(); + } while (GetTickCount() != dwTickEnd); + cMsTicks = dwTickEnd - dwTickStart; + cTscTicks = u64TscEnd - u64TscStart; + + /* Calc an approximate TSC frequency: + cTscTicks / uTscHz = cMsTicks / 1000 + 1 / uTscHz = (cMsTicks / 1000) / cTscTicks + uTscHz = cTscTicks / (cMsTicks / 1000) */ + uint64_t u64TscHz = (long double)cTscTicks / ((long double)cMsTicks / 1000.0); + if ( u64TscHz > _1M*3 + && u64TscHz < _1T) + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "u64TscHz=%'llu", u64TscHz); + else + { + RTTestFailed(hTest, "u64TscHz=%'llu - out of range", u64TscHz); + u64TscHz = 0; + } + + + /* + * Pit GetTickCount, InterruptTime, Performance Counters and TSC against each other. + */ + LARGE_INTEGER PrfHz; + LARGE_INTEGER PrfStart, PrfEnd, cPrfTicks; + LARGE_INTEGER IntStart, IntEnd, cIntTicks; + for (uint32_t i = 0; i < 7; i++) + { + RTTestSubF(hTest, "The whole bunch - pass #%u", i + 1); + + if (!QueryPerformanceFrequency(&PrfHz)) + { + RTTestFailed(hTest, "QueryPerformanceFrequency failed (%u)", GetLastError()); + return RTTestSummaryAndDestroy(hTest); + } + + /* get a good start time. */ + SpinUntilTick(); + do + { + IntStart.HighPart = MY_USER_SHARED_DATA->InterruptTime.High1Time; + IntStart.LowPart = MY_USER_SHARED_DATA->InterruptTime.LowPart; + dwTickStart = GetTickCount(); + if (!QueryPerformanceCounter(&PrfStart)) + { + RTTestFailed(hTest, "QueryPerformanceCounter failed (%u)", GetLastError()); + return RTTestSummaryAndDestroy(hTest); + } + ASMCompilerBarrier(); + ASMSerializeInstruction(); + u64TscStart = ASMReadTSC(); + ASMCompilerBarrier(); + } while ( MY_USER_SHARED_DATA->InterruptTime.High2Time != IntStart.HighPart + || MY_USER_SHARED_DATA->InterruptTime.LowPart != IntStart.LowPart + || GetTickCount() != dwTickStart); + + /* delay a good while. */ + DelayMillies(dwTickStart, 256); + + /* get a good end time. */ + do + { + IntEnd.HighPart = MY_USER_SHARED_DATA->InterruptTime.High1Time; + IntEnd.LowPart = MY_USER_SHARED_DATA->InterruptTime.LowPart; + dwTickEnd = GetTickCount(); + if (!QueryPerformanceCounter(&PrfEnd)) + { + RTTestFailed(hTest, "QueryPerformanceCounter failed (%u)", GetLastError()); + return RTTestSummaryAndDestroy(hTest); + } + ASMCompilerBarrier(); + ASMSerializeInstruction(); + u64TscEnd = ASMReadTSC(); + ASMCompilerBarrier(); + } while ( MY_USER_SHARED_DATA->InterruptTime.High2Time != IntEnd.HighPart + || MY_USER_SHARED_DATA->InterruptTime.LowPart != IntEnd.LowPart + || GetTickCount() != dwTickEnd); + + cMsTicks = dwTickEnd - dwTickStart; + cTscTicks = u64TscEnd - u64TscStart; + cIntTicks.QuadPart = IntEnd.QuadPart - IntStart.QuadPart; + cPrfTicks.QuadPart = PrfEnd.QuadPart - PrfStart.QuadPart; + + /* Recalc to micro seconds. */ + uint64_t u64MicroSecMs = (uint64_t)cMsTicks * 1000; + uint64_t u64MicroSecTsc = u64TscHz ? (long double)cTscTicks / (long double)u64TscHz * 1000000 : u64MicroSecMs; + uint64_t u64MicroSecPrf = (long double)cPrfTicks.QuadPart / (long double)PrfHz.QuadPart * 1000000; + uint64_t u64MicroSecInt = cIntTicks.QuadPart / 10; /* 100ns units*/ + + /* check how much they differ using the millisecond tick count as the standard candle. */ + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, " %9llu / %7lld us - GetTickCount\n", u64MicroSecMs, 0); + + int64_t off = u64MicroSecTsc - u64MicroSecMs; + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, " %9llu / %7lld us - TSC\n", u64MicroSecTsc, off); + RTTEST_CHECK(hTest, RT_ABS(off) < 50000 /*us*/); /* some extra uncertainty with TSC. */ + + off = u64MicroSecInt - u64MicroSecMs; + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, " %9llu / %7lld us - InterruptTime\n", u64MicroSecInt, off); + RTTEST_CHECK(hTest, RT_ABS(off) < 25000 /*us*/); + + off = u64MicroSecPrf - u64MicroSecMs; + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, " %9llu / %7lld us - QueryPerformanceCounter\n", u64MicroSecPrf, off); + RTTEST_CHECK(hTest, RT_ABS(off) < 25000 /*us*/); + } + + return RTTestSummaryAndDestroy(hTest); +} + diff --git a/src/VBox/ValidationKit/utils/serial/Makefile.kmk b/src/VBox/ValidationKit/utils/serial/Makefile.kmk new file mode 100644 index 00000000..0ef934e2 --- /dev/null +++ b/src/VBox/ValidationKit/utils/serial/Makefile.kmk @@ -0,0 +1,49 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Serial port tests. +# + +# +# Copyright (C) 2017-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Serial port testing utility. +# +PROGRAMS += SerialTest +SerialTest_TEMPLATE = VBoxValidationKitR3 +SerialTest_SOURCES = SerialTest.cpp + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/serial/SerialTest.cpp b/src/VBox/ValidationKit/utils/serial/SerialTest.cpp new file mode 100644 index 00000000..03d88800 --- /dev/null +++ b/src/VBox/ValidationKit/utils/serial/SerialTest.cpp @@ -0,0 +1,1118 @@ +/* $Id: SerialTest.cpp $ */ +/** @file + * SerialTest - Serial port testing utility. + */ + +/* + * Copyright (C) 2017-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/errcore.h> +#include <iprt/getopt.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/param.h> +#include <iprt/process.h> +#include <iprt/rand.h> +#include <iprt/serialport.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/test.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** Number of times to toggle the status lines during the test. */ +#define SERIALTEST_STS_LINE_TOGGLE_COUNT 100 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + + +/** + * Serial test mode. + */ +typedef enum SERIALTESTMODE +{ + /** Invalid mode. */ + SERIALTESTMODE_INVALID = 0, + /** Serial port is looped back to itself */ + SERIALTESTMODE_LOOPBACK, + /** A secondary serial port is used with a null modem cable in between. */ + SERIALTESTMODE_SECONDARY, + /** The serial port is connected externally over which we have no control. */ + SERIALTESTMODE_EXTERNAL, + /** Usual 32bit hack. */ + SERIALTESTMODE_32BIT_HACK = 0x7fffffff +} SERIALTESTMODE; +/** Pointer to a serial test mode. */ +typedef SERIALTESTMODE *PSERIALTESTMDOE; + +/** Pointer to the serial test data instance. */ +typedef struct SERIALTEST *PSERIALTEST; + +/** + * Test callback function. + * + * @returns IPRT status code. + * @param pSerialTest The serial test instance data. + */ +typedef DECLCALLBACKTYPE(int, FNSERIALTESTRUN,(PSERIALTEST pSerialTest)); +/** Pointer to the serial test callback. */ +typedef FNSERIALTESTRUN *PFNSERIALTESTRUN; + + +/** + * The serial test instance data. + */ +typedef struct SERIALTEST +{ + /** The assigned test handle. */ + RTTEST hTest; + /** The assigned serial port. */ + RTSERIALPORT hSerialPort; + /** The currently active config. */ + PCRTSERIALPORTCFG pSerialCfg; +} SERIALTEST; + + +/** + * Test descriptor. + */ +typedef struct SERIALTESTDESC +{ + /** Test ID. */ + const char *pszId; + /** Test description. */ + const char *pszDesc; + /** Test run callback. */ + PFNSERIALTESTRUN pfnRun; +} SERIALTESTDESC; +/** Pointer to a test descriptor. */ +typedef SERIALTESTDESC *PSERIALTESTDESC; +/** Pointer to a constant test descriptor. */ +typedef const SERIALTESTDESC *PCSERIALTESTDESC; + + +/** + * TX/RX buffer containing a simple counter. + */ +typedef struct SERIALTESTTXRXBUFCNT +{ + /** The current counter value. */ + uint32_t iCnt; + /** Number of bytes left to receive/transmit. */ + size_t cbTxRxLeft; + /** The offset into the buffer to receive to/send from. */ + size_t offBuf; + /** Maximum size to send/receive before processing is needed again. */ + size_t cbTxRxMax; + /** The data buffer. */ + uint8_t abBuf[_1K]; +} SERIALTESTTXRXBUFCNT; +/** Pointer to a TX/RX buffer. */ +typedef SERIALTESTTXRXBUFCNT *PSERIALTESTTXRXBUFCNT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + + +/** Command line parameters */ +static const RTGETOPTDEF g_aCmdOptions[] = +{ + {"--device", 'd', RTGETOPT_REQ_STRING }, + {"--baudrate", 'b', RTGETOPT_REQ_UINT32 }, + {"--parity", 'p', RTGETOPT_REQ_STRING }, + {"--databits", 'c', RTGETOPT_REQ_UINT32 }, + {"--stopbits", 's', RTGETOPT_REQ_STRING }, + {"--mode", 'm', RTGETOPT_REQ_STRING }, + {"--secondarydevice", 'l', RTGETOPT_REQ_STRING }, + {"--tests", 't', RTGETOPT_REQ_STRING }, + {"--txbytes", 'x', RTGETOPT_REQ_UINT32 }, + {"--abort-on-error", 'a', RTGETOPT_REQ_NOTHING}, + {"--verbose", 'v', RTGETOPT_REQ_NOTHING}, + {"--help", 'h', RTGETOPT_REQ_NOTHING} +}; + + +static DECLCALLBACK(int) serialTestRunReadWrite(PSERIALTEST pSerialTest); +static DECLCALLBACK(int) serialTestRunWrite(PSERIALTEST pSerialTest); +static DECLCALLBACK(int) serialTestRunReadVerify(PSERIALTEST pSerialTest); +static DECLCALLBACK(int) serialTestRunStsLines(PSERIALTEST pSerialTest); +static DECLCALLBACK(int) serialTestRunEcho(PSERIALTEST pSerialTest); + +/** Implemented tests. */ +static const SERIALTESTDESC g_aSerialTests[] = +{ + {"readwrite", "Simple Read/Write test on the same serial port", serialTestRunReadWrite }, + {"write", "Simple write test (verification done somewhere else)", serialTestRunWrite }, + {"readverify", "Counterpart to write test (reads and verifies data)", serialTestRunReadVerify }, + {"stslines", "Testing the status line setting and receiving", serialTestRunStsLines }, + {"echo", "Echoes received data back to the sender (not real test)", serialTestRunEcho }, +}; + +/** Verbosity value. */ +static unsigned g_cVerbosity = 0; +/** The test handle. */ +static RTTEST g_hTest = NIL_RTTEST; +/** The serial test mode. */ +static SERIALTESTMODE g_enmMode = SERIALTESTMODE_LOOPBACK; +/** Random number generator. */ +static RTRAND g_hRand = NIL_RTRAND; +/** The serial port handle. */ +static RTSERIALPORT g_hSerialPort = NIL_RTSERIALPORT; +/** The loopback serial port handle if configured. */ +static RTSERIALPORT g_hSerialPortSecondary = NIL_RTSERIALPORT; +/** Number of bytes to transmit for read/write tests. */ +static size_t g_cbTx = _1M; +/** Flag whether to abort the tool when encountering the first error. */ +static bool g_fAbortOnError = false; +/** The config used. */ +static RTSERIALPORTCFG g_SerialPortCfg = +{ + /* uBaudRate */ + 115200, + /* enmParity */ + RTSERIALPORTPARITY_NONE, + /* enmDataBitCount */ + RTSERIALPORTDATABITS_8BITS, + /* enmStopBitCount */ + RTSERIALPORTSTOPBITS_ONE +}; + + +/** + * RTTestFailed() wrapper which aborts the program if the option is set. + */ +static void serialTestFailed(RTTEST hTest, const char *pszFmt, ...) +{ + va_list va; + va_start(va, pszFmt); + RTTestFailedV(hTest, pszFmt, va); + va_end(va); + if (g_fAbortOnError) + RT_BREAKPOINT(); +} + + +/** + * Initializes a TX buffer. + * + * @returns nothing. + * @param pSerBuf The serial buffer to initialize. + * @param cbTx Maximum number of bytes to transmit. + */ +static void serialTestTxBufInit(PSERIALTESTTXRXBUFCNT pSerBuf, size_t cbTx) +{ + pSerBuf->iCnt = 0; + pSerBuf->offBuf = 0; + pSerBuf->cbTxRxMax = 0; + pSerBuf->cbTxRxLeft = cbTx; + RT_ZERO(pSerBuf->abBuf); +} + + +/** + * Initializes a RX buffer. + * + * @returns nothing. + * @param pSerBuf The serial buffer to initialize. + * @param cbRx Maximum number of bytes to receive. + */ +static void serialTestRxBufInit(PSERIALTESTTXRXBUFCNT pSerBuf, size_t cbRx) +{ + pSerBuf->iCnt = 0; + pSerBuf->offBuf = 0; + pSerBuf->cbTxRxMax = sizeof(pSerBuf->abBuf); + pSerBuf->cbTxRxLeft = cbRx; + RT_ZERO(pSerBuf->abBuf); +} + + +/** + * Prepares the given TX buffer with data for sending it out. + * + * @returns nothing. + * @param pSerBuf The TX buffer pointer. + */ +static void serialTestTxBufPrepare(PSERIALTESTTXRXBUFCNT pSerBuf) +{ + /* Move the data to the front to make room at the end to fill. */ + if (pSerBuf->offBuf) + { + memmove(&pSerBuf->abBuf[0], &pSerBuf->abBuf[pSerBuf->offBuf], sizeof(pSerBuf->abBuf) - pSerBuf->offBuf); + pSerBuf->offBuf = 0; + } + + /* Fill up with data. */ + uint32_t offData = 0; + while (pSerBuf->cbTxRxMax + sizeof(uint32_t) <= sizeof(pSerBuf->abBuf)) + { + pSerBuf->iCnt++; + *(uint32_t *)&pSerBuf->abBuf[pSerBuf->offBuf + offData] = pSerBuf->iCnt; + pSerBuf->cbTxRxMax += sizeof(uint32_t); + offData += sizeof(uint32_t); + } +} + + +/** + * Sends a new batch of data from the TX buffer preapring new data if required. + * + * @returns IPRT status code. + * @param hSerialPort The serial port handle to send the data to. + * @param pSerBuf The TX buffer pointer. + */ +static int serialTestTxBufSend(RTSERIALPORT hSerialPort, PSERIALTESTTXRXBUFCNT pSerBuf) +{ + int rc = VINF_SUCCESS; + + if (pSerBuf->cbTxRxLeft) + { + if (!pSerBuf->cbTxRxMax) + serialTestTxBufPrepare(pSerBuf); + + size_t cbToWrite = RT_MIN(pSerBuf->cbTxRxMax, pSerBuf->cbTxRxLeft); + size_t cbWritten = 0; + rc = RTSerialPortWriteNB(hSerialPort, &pSerBuf->abBuf[pSerBuf->offBuf], cbToWrite, &cbWritten); + if (RT_SUCCESS(rc)) + { + pSerBuf->cbTxRxMax -= cbWritten; + pSerBuf->offBuf += cbWritten; + pSerBuf->cbTxRxLeft -= cbWritten; + } + } + + return rc; +} + + +/** + * Receives dat from the given serial port into the supplied RX buffer and does some validity checking. + * + * @returns IPRT status code. + * @param hSerialPort The serial port handle to receive data from. + * @param pSerBuf The RX buffer pointer. + */ +static int serialTestRxBufRecv(RTSERIALPORT hSerialPort, PSERIALTESTTXRXBUFCNT pSerBuf) +{ + int rc = VINF_SUCCESS; + + if (pSerBuf->cbTxRxLeft) + { + size_t cbToRead = RT_MIN(pSerBuf->cbTxRxMax, pSerBuf->cbTxRxLeft); + size_t cbRead = 0; + rc = RTSerialPortReadNB(hSerialPort, &pSerBuf->abBuf[pSerBuf->offBuf], cbToRead, &cbRead); + if (RT_SUCCESS(rc)) + { + pSerBuf->offBuf += cbRead; + pSerBuf->cbTxRxMax -= cbRead; + pSerBuf->cbTxRxLeft -= cbRead; + } + } + + return rc; +} + + +/** + * Verifies the data in the given RX buffer for correct transmission. + * + * @returns Flag whether verification failed. + * @param hTest The test handle to report errors to. + * @param pSerBuf The RX buffer pointer. + * @param iCntTx The current TX counter value the RX buffer should never get ahead of, + * UINT32_MAX disables this check. + */ +static bool serialTestRxBufVerify(RTTEST hTest, PSERIALTESTTXRXBUFCNT pSerBuf, uint32_t iCntTx) +{ + uint32_t offRx = 0; + bool fFailed = false; + + while (offRx + sizeof(uint32_t) < pSerBuf->offBuf) + { + uint32_t u32Val = *(uint32_t *)&pSerBuf->abBuf[offRx]; + offRx += sizeof(uint32_t); + + if (RT_UNLIKELY(u32Val != ++pSerBuf->iCnt)) + { + fFailed = true; + if (g_cVerbosity > 0) + serialTestFailed(hTest, "Data corruption/loss detected, expected counter value %u got %u\n", + pSerBuf->iCnt, u32Val); + } + } + + if (RT_UNLIKELY(pSerBuf->iCnt > iCntTx)) + { + fFailed = true; + serialTestFailed(hTest, "Overtook the send buffer, expected maximum counter value %u got %u\n", + iCntTx, pSerBuf->iCnt); + } + + /* Remove processed data from the buffer and move the rest to the front. */ + if (offRx) + { + memmove(&pSerBuf->abBuf[0], &pSerBuf->abBuf[offRx], sizeof(pSerBuf->abBuf) - offRx); + pSerBuf->offBuf -= offRx; + pSerBuf->cbTxRxMax += offRx; + } + + return fFailed; +} + + +DECLINLINE(bool) serialTestRndTrue(void) +{ + return RTRandAdvU32Ex(g_hRand, 0, 1) == 1; +} + + +/** + * Runs a simple read/write test. + * + * @returns IPRT status code. + * @param pSerialTest The serial test configuration. + */ +static DECLCALLBACK(int) serialTestRunReadWrite(PSERIALTEST pSerialTest) +{ + uint64_t tsStart = RTTimeNanoTS(); + bool fFailed = false; + SERIALTESTTXRXBUFCNT SerBufTx; + SERIALTESTTXRXBUFCNT SerBufRx; + + serialTestTxBufInit(&SerBufTx, g_cbTx); + serialTestRxBufInit(&SerBufRx, g_cbTx); + + int rc = serialTestTxBufSend(pSerialTest->hSerialPort, &SerBufTx); + while ( RT_SUCCESS(rc) + && ( SerBufTx.cbTxRxLeft + || SerBufRx.cbTxRxLeft)) + { + uint32_t fEvts = 0; + uint32_t fEvtsQuery = 0; + if (SerBufTx.cbTxRxLeft) + fEvtsQuery |= RTSERIALPORT_EVT_F_DATA_TX; + if (SerBufRx.cbTxRxLeft) + fEvtsQuery |= RTSERIALPORT_EVT_F_DATA_RX; + + rc = RTSerialPortEvtPoll(pSerialTest->hSerialPort, fEvtsQuery, &fEvts, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc)) + break; + + if (fEvts & RTSERIALPORT_EVT_F_DATA_RX) + { + rc = serialTestRxBufRecv(pSerialTest->hSerialPort, &SerBufRx); + if (RT_FAILURE(rc)) + break; + + bool fRes = serialTestRxBufVerify(pSerialTest->hTest, &SerBufRx, SerBufTx.iCnt); + if (fRes && !fFailed) + { + fFailed = true; + serialTestFailed(pSerialTest->hTest, "Data corruption/loss detected\n"); + } + } + if ( RT_SUCCESS(rc) + && (fEvts & RTSERIALPORT_EVT_F_DATA_TX)) + rc = serialTestTxBufSend(pSerialTest->hSerialPort, &SerBufTx); + } + + uint64_t tsRuntime = RTTimeNanoTS() - tsStart; + size_t cNsPerByte = tsRuntime / g_cbTx; + uint64_t cbBytesPerSec = RT_NS_1SEC / cNsPerByte; + RTTestValue(pSerialTest->hTest, "Throughput", cbBytesPerSec, RTTESTUNIT_BYTES_PER_SEC); + + return rc; +} + + +/** + * Runs a simple write test without doing any verification. + * + * @returns IPRT status code. + * @param pSerialTest The serial test configuration. + */ +static DECLCALLBACK(int) serialTestRunWrite(PSERIALTEST pSerialTest) +{ + uint64_t tsStart = RTTimeNanoTS(); + SERIALTESTTXRXBUFCNT SerBufTx; + + serialTestTxBufInit(&SerBufTx, g_cbTx); + + int rc = serialTestTxBufSend(pSerialTest->hSerialPort, &SerBufTx); + while ( RT_SUCCESS(rc) + && SerBufTx.cbTxRxLeft) + { + uint32_t fEvts = 0; + + rc = RTSerialPortEvtPoll(pSerialTest->hSerialPort, RTSERIALPORT_EVT_F_DATA_TX, &fEvts, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc)) + break; + + if (fEvts & RTSERIALPORT_EVT_F_DATA_TX) + rc = serialTestTxBufSend(pSerialTest->hSerialPort, &SerBufTx); + } + + uint64_t tsRuntime = RTTimeNanoTS() - tsStart; + size_t cNsPerByte = tsRuntime / g_cbTx; + uint64_t cbBytesPerSec = RT_NS_1SEC / cNsPerByte; + RTTestValue(pSerialTest->hTest, "Throughput", cbBytesPerSec, RTTESTUNIT_BYTES_PER_SEC); + + return rc; +} + + +/** + * Runs the counterpart to the write test, reading and verifying data. + * + * @returns IPRT status code. + * @param pSerialTest The serial test configuration. + */ +static DECLCALLBACK(int) serialTestRunReadVerify(PSERIALTEST pSerialTest) +{ + int rc = VINF_SUCCESS; + uint64_t tsStart = RTTimeNanoTS(); + bool fFailed = false; + SERIALTESTTXRXBUFCNT SerBufRx; + + serialTestRxBufInit(&SerBufRx, g_cbTx); + + while ( RT_SUCCESS(rc) + && SerBufRx.cbTxRxLeft) + { + uint32_t fEvts = 0; + uint32_t fEvtsQuery = RTSERIALPORT_EVT_F_DATA_RX; + + rc = RTSerialPortEvtPoll(pSerialTest->hSerialPort, fEvtsQuery, &fEvts, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc)) + break; + + if (fEvts & RTSERIALPORT_EVT_F_DATA_RX) + { + rc = serialTestRxBufRecv(pSerialTest->hSerialPort, &SerBufRx); + if (RT_FAILURE(rc)) + break; + + bool fRes = serialTestRxBufVerify(pSerialTest->hTest, &SerBufRx, UINT32_MAX); + if (fRes && !fFailed) + { + fFailed = true; + serialTestFailed(pSerialTest->hTest, "Data corruption/loss detected\n"); + } + } + } + + uint64_t tsRuntime = RTTimeNanoTS() - tsStart; + size_t cNsPerByte = tsRuntime / g_cbTx; + uint64_t cbBytesPerSec = RT_NS_1SEC / cNsPerByte; + RTTestValue(pSerialTest->hTest, "Throughput", cbBytesPerSec, RTTESTUNIT_BYTES_PER_SEC); + + return rc; +} + + +/** + * Tests setting status lines and getting notified about status line changes. + * + * @returns IPRT status code. + * @param pSerialTest The serial test configuration. + */ +static DECLCALLBACK(int) serialTestRunStsLines(PSERIALTEST pSerialTest) +{ + int rc = VINF_SUCCESS; + + if (g_enmMode == SERIALTESTMODE_LOOPBACK) + { + uint32_t fStsLinesQueriedOld = 0; + + rc = RTSerialPortChgStatusLines(pSerialTest->hSerialPort, + RTSERIALPORT_CHG_STS_LINES_F_RTS | RTSERIALPORT_CHG_STS_LINES_F_DTR, + 0); + if (RT_SUCCESS(rc)) + { + rc = RTSerialPortQueryStatusLines(pSerialTest->hSerialPort, &fStsLinesQueriedOld); + if (RT_SUCCESS(rc)) + { + /* Everything should be clear at this stage. */ + if (!fStsLinesQueriedOld) + { + uint32_t fStsLinesSetOld = 0; + + for (uint32_t i = 0; i < SERIALTEST_STS_LINE_TOGGLE_COUNT; i++) + { + uint32_t fStsLinesSet = 0; + uint32_t fStsLinesClear = 0; + + /* Change RTS? */ + if (serialTestRndTrue()) + { + /* Clear, if set previously otherwise set it. */ + if (fStsLinesSetOld & RTSERIALPORT_CHG_STS_LINES_F_RTS) + fStsLinesClear |= RTSERIALPORT_CHG_STS_LINES_F_RTS; + else + fStsLinesSet |= RTSERIALPORT_CHG_STS_LINES_F_RTS; + } + + /* Change DTR? */ + if (serialTestRndTrue()) + { + /* Clear, if set previously otherwise set it. */ + if (fStsLinesSetOld & RTSERIALPORT_CHG_STS_LINES_F_DTR) + fStsLinesClear |= RTSERIALPORT_CHG_STS_LINES_F_DTR; + else + fStsLinesSet |= RTSERIALPORT_CHG_STS_LINES_F_DTR; + } + + rc = RTSerialPortChgStatusLines(pSerialTest->hSerialPort, fStsLinesClear, fStsLinesSet); + if (RT_FAILURE(rc)) + { + serialTestFailed(g_hTest, "Changing status lines failed with %Rrc on iteration %u (fSet=%#x fClear=%#x)\n", + rc, i, fStsLinesSet, fStsLinesClear); + break; + } + + /* Wait for status line monitor event. */ + uint32_t fEvtsRecv = 0; + rc = RTSerialPortEvtPoll(pSerialTest->hSerialPort, RTSERIALPORT_EVT_F_STATUS_LINE_CHANGED, + &fEvtsRecv, RT_MS_1SEC); + if ( RT_FAILURE(rc) + && (rc != VERR_TIMEOUT && !fStsLinesSet && !fStsLinesClear)) + { + serialTestFailed(g_hTest, "Waiting for status line change failed with %Rrc on iteration %u\n", + rc, i); + break; + } + + uint32_t fStsLinesQueried = 0; + rc = RTSerialPortQueryStatusLines(pSerialTest->hSerialPort, &fStsLinesQueried); + if (RT_FAILURE(rc)) + { + serialTestFailed(g_hTest, "Querying status lines failed with %Rrc on iteration %u\n", + rc, i); + break; + } + + /* Compare expected and real result. */ + if ( (fStsLinesQueried & RTSERIALPORT_STS_LINE_DSR) + != (fStsLinesQueriedOld & RTSERIALPORT_STS_LINE_DSR)) + { + if ( (fStsLinesQueried & RTSERIALPORT_STS_LINE_DSR) + && !(fStsLinesSet & RTSERIALPORT_CHG_STS_LINES_F_DTR)) + serialTestFailed(g_hTest, "DSR line got set when it shouldn't be on iteration %u\n", i); + else if ( !(fStsLinesQueried & RTSERIALPORT_STS_LINE_DSR) + && !(fStsLinesClear & RTSERIALPORT_CHG_STS_LINES_F_DTR)) + serialTestFailed(g_hTest, "DSR line got cleared when it shouldn't be on iteration %u\n", i); + } + else if ( (fStsLinesSet & RTSERIALPORT_CHG_STS_LINES_F_DTR) + || (fStsLinesClear & RTSERIALPORT_CHG_STS_LINES_F_DTR)) + serialTestFailed(g_hTest, "DSR line didn't change when it should have on iteration %u\n", i); + + if ( (fStsLinesQueried & RTSERIALPORT_STS_LINE_DCD) + != (fStsLinesQueriedOld & RTSERIALPORT_STS_LINE_DCD)) + { + if ( (fStsLinesQueried & RTSERIALPORT_STS_LINE_DCD) + && !(fStsLinesSet & RTSERIALPORT_CHG_STS_LINES_F_DTR)) + serialTestFailed(g_hTest, "DCD line got set when it shouldn't be on iteration %u\n", i); + else if ( !(fStsLinesQueried & RTSERIALPORT_STS_LINE_DCD) + && !(fStsLinesClear & RTSERIALPORT_CHG_STS_LINES_F_DTR)) + serialTestFailed(g_hTest, "DCD line got cleared when it shouldn't be on iteration %u\n", i); + } + else if ( (fStsLinesSet & RTSERIALPORT_CHG_STS_LINES_F_DTR) + || (fStsLinesClear & RTSERIALPORT_CHG_STS_LINES_F_DTR)) + serialTestFailed(g_hTest, "DCD line didn't change when it should have on iteration %u\n", i); + + if ( (fStsLinesQueried & RTSERIALPORT_STS_LINE_CTS) + != (fStsLinesQueriedOld & RTSERIALPORT_STS_LINE_CTS)) + { + if ( (fStsLinesQueried & RTSERIALPORT_STS_LINE_CTS) + && !(fStsLinesSet & RTSERIALPORT_CHG_STS_LINES_F_RTS)) + serialTestFailed(g_hTest, "CTS line got set when it shouldn't be on iteration %u\n", i); + else if ( !(fStsLinesQueried & RTSERIALPORT_STS_LINE_CTS) + && !(fStsLinesClear & RTSERIALPORT_CHG_STS_LINES_F_RTS)) + serialTestFailed(g_hTest, "CTS line got cleared when it shouldn't be on iteration %u\n", i); + } + else if ( (fStsLinesSet & RTSERIALPORT_CHG_STS_LINES_F_RTS) + || (fStsLinesClear & RTSERIALPORT_CHG_STS_LINES_F_RTS)) + serialTestFailed(g_hTest, "CTS line didn't change when it should have on iteration %u\n", i); + + if (RTTestErrorCount(g_hTest) > 0) + break; + + fStsLinesSetOld |= fStsLinesSet; + fStsLinesSetOld &= ~fStsLinesClear; + fStsLinesQueriedOld = fStsLinesQueried; + } + } + else + serialTestFailed(g_hTest, "Status lines active which should be clear (%#x, but expected %#x)\n", + fStsLinesQueriedOld, 0); + } + else + serialTestFailed(g_hTest, "Querying status lines failed with %Rrc\n", rc); + } + else + serialTestFailed(g_hTest, "Clearing status lines failed with %Rrc\n", rc); + } + else + rc = VERR_NOT_IMPLEMENTED; + + return rc; +} + + +/** + * Runs a simple echo service (not a real test on its own). + * + * @returns IPRT status code. + * @param pSerialTest The serial test configuration. + */ +static DECLCALLBACK(int) serialTestRunEcho(PSERIALTEST pSerialTest) +{ + int rc = VINF_SUCCESS; + uint64_t tsStart = RTTimeNanoTS(); + uint8_t abBuf[_1K]; + size_t cbLeft = g_cbTx; + size_t cbInBuf = 0; + + while ( RT_SUCCESS(rc) + && ( cbLeft + || cbInBuf)) + { + uint32_t fEvts = 0; + uint32_t fEvtsQuery = 0; + if (cbInBuf) + fEvtsQuery |= RTSERIALPORT_EVT_F_DATA_TX; + if (cbLeft && cbInBuf < sizeof(abBuf)) + fEvtsQuery |= RTSERIALPORT_EVT_F_DATA_RX; + + rc = RTSerialPortEvtPoll(pSerialTest->hSerialPort, fEvtsQuery, &fEvts, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc)) + break; + + if (fEvts & RTSERIALPORT_EVT_F_DATA_RX) + { + size_t cbThisRead = RT_MIN(cbLeft, sizeof(abBuf) - cbInBuf); + size_t cbRead = 0; + rc = RTSerialPortReadNB(pSerialTest->hSerialPort, &abBuf[cbInBuf], cbThisRead, &cbRead); + if (RT_SUCCESS(rc)) + { + cbInBuf += cbRead; + cbLeft -= cbRead; + } + else if (RT_FAILURE(rc)) + break; + } + + if (fEvts & RTSERIALPORT_EVT_F_DATA_TX) + { + size_t cbWritten = 0; + rc = RTSerialPortWriteNB(pSerialTest->hSerialPort, &abBuf[0], cbInBuf, &cbWritten); + if (RT_SUCCESS(rc)) + { + memmove(&abBuf[0], &abBuf[cbWritten], cbInBuf - cbWritten); + cbInBuf -= cbWritten; + } + } + } + + uint64_t tsRuntime = RTTimeNanoTS() - tsStart; + size_t cNsPerByte = tsRuntime / g_cbTx; + uint64_t cbBytesPerSec = RT_NS_1SEC / cNsPerByte; + RTTestValue(pSerialTest->hTest, "Throughput", cbBytesPerSec, RTTESTUNIT_BYTES_PER_SEC); + + return rc; +} + + +/** + * Returns an array of test descriptors get from the given string. + * + * @returns Pointer to the array of test descriptors. + * @param pszTests The string containing the tests separated with ':'. + */ +static PSERIALTESTDESC serialTestSelectFromCmdLine(const char *pszTests) +{ + size_t cTests = 1; + + const char *pszNext = strchr(pszTests, ':'); + while (pszNext) + { + pszNext++; + cTests++; + pszNext = strchr(pszNext, ':'); + } + + PSERIALTESTDESC paTests = (PSERIALTESTDESC)RTMemAllocZ((cTests + 1) * sizeof(SERIALTESTDESC)); + if (RT_LIKELY(paTests)) + { + uint32_t iTest = 0; + + pszNext = strchr(pszTests, ':'); + while (pszNext) + { + bool fFound = false; + + pszNext++; /* Skip : character. */ + + for (unsigned i = 0; i < RT_ELEMENTS(g_aSerialTests); i++) + { + if (!RTStrNICmp(pszTests, g_aSerialTests[i].pszId, pszNext - pszTests - 1)) + { + memcpy(&paTests[iTest], &g_aSerialTests[i], sizeof(SERIALTESTDESC)); + fFound = true; + break; + } + } + + if (RT_UNLIKELY(!fFound)) + { + RTPrintf("Testcase \"%.*s\" not known\n", pszNext - pszTests - 1, pszTests); + RTMemFree(paTests); + return NULL; + } + + pszTests = pszNext; + pszNext = strchr(pszTests, ':'); + } + + /* Fill last descriptor. */ + bool fFound = false; + for (unsigned i = 0; i < RT_ELEMENTS(g_aSerialTests); i++) + { + if (!RTStrICmp(pszTests, g_aSerialTests[i].pszId)) + { + memcpy(&paTests[iTest], &g_aSerialTests[i], sizeof(SERIALTESTDESC)); + fFound = true; + break; + } + } + + if (RT_UNLIKELY(!fFound)) + { + RTPrintf("Testcase \"%s\" not known\n", pszTests); + RTMemFree(paTests); + paTests = NULL; + } + } + else + RTPrintf("Failed to allocate test descriptors for %u selected tests\n", cTests); + + return paTests; +} + + +/** + * Shows tool usage text. + */ +static void serialTestUsage(PRTSTREAM pStrm) +{ + char szExec[RTPATH_MAX]; + RTStrmPrintf(pStrm, "usage: %s [options]\n", + RTPathFilename(RTProcGetExecutablePath(szExec, sizeof(szExec)))); + RTStrmPrintf(pStrm, "\n"); + RTStrmPrintf(pStrm, "options: \n"); + + + for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++) + { + const char *pszHelp; + switch (g_aCmdOptions[i].iShort) + { + case 'h': + pszHelp = "Displays this help and exit"; + break; + case 'd': + pszHelp = "Use the specified serial port device"; + break; + case 'b': + pszHelp = "Use the given baudrate"; + break; + case 'p': + pszHelp = "Use the given parity, valid modes are: none, even, odd, mark, space"; + break; + case 'c': + pszHelp = "Use the given data bitcount, valid are: 5, 6, 7, 8"; + break; + case 's': + pszHelp = "Use the given stop bitcount, valid are: 1, 1.5, 2"; + break; + case 'm': + pszHelp = "Mode of the serial port, valid are: loopback, secondary, external"; + break; + case 'l': + pszHelp = "Use the given serial port device as the secondary device"; + break; + case 't': + pszHelp = "The tests to run separated by ':'"; + break; + case 'x': + pszHelp = "Number of bytes to transmit during read/write tests"; + break; + default: + pszHelp = "Option undocumented"; + break; + } + char szOpt[256]; + RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort); + RTStrmPrintf(pStrm, " %-30s%s\n", szOpt, pszHelp); + } +} + + +int main(int argc, char *argv[]) +{ + /* + * Init IPRT and globals. + */ + int rc = RTTestInitAndCreate("SerialTest", &g_hTest); + if (rc) + return rc; + + /* + * Default values. + */ + const char *pszDevice = NULL; + const char *pszDeviceSecondary = NULL; + PSERIALTESTDESC paTests = NULL; + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */); + while ((rc = RTGetOpt(&GetState, &ValueUnion))) + { + switch (rc) + { + case 'h': + serialTestUsage(g_pStdOut); + return RTEXITCODE_SUCCESS; + case 'v': + g_cVerbosity++; + break; + case 'd': + pszDevice = ValueUnion.psz; + break; + case 'l': + pszDeviceSecondary = ValueUnion.psz; + break; + case 'b': + g_SerialPortCfg.uBaudRate = ValueUnion.u32; + break; + case 'p': + if (!RTStrICmp(ValueUnion.psz, "none")) + g_SerialPortCfg.enmParity = RTSERIALPORTPARITY_NONE; + else if (!RTStrICmp(ValueUnion.psz, "even")) + g_SerialPortCfg.enmParity = RTSERIALPORTPARITY_EVEN; + else if (!RTStrICmp(ValueUnion.psz, "odd")) + g_SerialPortCfg.enmParity = RTSERIALPORTPARITY_ODD; + else if (!RTStrICmp(ValueUnion.psz, "mark")) + g_SerialPortCfg.enmParity = RTSERIALPORTPARITY_MARK; + else if (!RTStrICmp(ValueUnion.psz, "space")) + g_SerialPortCfg.enmParity = RTSERIALPORTPARITY_SPACE; + else + { + RTPrintf("Unknown parity \"%s\" given\n", ValueUnion.psz); + return RTEXITCODE_FAILURE; + } + break; + case 'c': + if (ValueUnion.u32 == 5) + g_SerialPortCfg.enmDataBitCount = RTSERIALPORTDATABITS_5BITS; + else if (ValueUnion.u32 == 6) + g_SerialPortCfg.enmDataBitCount = RTSERIALPORTDATABITS_6BITS; + else if (ValueUnion.u32 == 7) + g_SerialPortCfg.enmDataBitCount = RTSERIALPORTDATABITS_7BITS; + else if (ValueUnion.u32 == 8) + g_SerialPortCfg.enmDataBitCount = RTSERIALPORTDATABITS_8BITS; + else + { + RTPrintf("Unknown data bitcount \"%u\" given\n", ValueUnion.u32); + return RTEXITCODE_FAILURE; + } + break; + case 's': + if (!RTStrICmp(ValueUnion.psz, "1")) + g_SerialPortCfg.enmStopBitCount = RTSERIALPORTSTOPBITS_ONE; + else if (!RTStrICmp(ValueUnion.psz, "1.5")) + g_SerialPortCfg.enmStopBitCount = RTSERIALPORTSTOPBITS_ONEPOINTFIVE; + else if (!RTStrICmp(ValueUnion.psz, "2")) + g_SerialPortCfg.enmStopBitCount = RTSERIALPORTSTOPBITS_TWO; + else + { + RTPrintf("Unknown stop bitcount \"%s\" given\n", ValueUnion.psz); + return RTEXITCODE_FAILURE; + } + break; + case 'm': + if (!RTStrICmp(ValueUnion.psz, "loopback")) + g_enmMode = SERIALTESTMODE_LOOPBACK; + else if (!RTStrICmp(ValueUnion.psz, "secondary")) + g_enmMode = SERIALTESTMODE_SECONDARY; + else if (!RTStrICmp(ValueUnion.psz, "external")) + g_enmMode = SERIALTESTMODE_EXTERNAL; + else + { + RTPrintf("Unknown serial test mode \"%s\" given\n", ValueUnion.psz); + return RTEXITCODE_FAILURE; + } + break; + case 't': + paTests = serialTestSelectFromCmdLine(ValueUnion.psz); + if (!paTests) + return RTEXITCODE_FAILURE; + break; + case 'x': + g_cbTx = ValueUnion.u32; + break; + case 'a': + g_fAbortOnError = true; + break; + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + if (g_enmMode == SERIALTESTMODE_SECONDARY && !pszDeviceSecondary) + { + RTPrintf("Mode set to secondary device but no secondary device given\n"); + return RTEXITCODE_FAILURE; + } + + if (!paTests) + { + /* Select all. */ + paTests = (PSERIALTESTDESC)RTMemAllocZ((RT_ELEMENTS(g_aSerialTests) + 1) * sizeof(SERIALTESTDESC)); + if (RT_UNLIKELY(!paTests)) + { + RTPrintf("Failed to allocate memory for test descriptors\n"); + return RTEXITCODE_FAILURE; + } + memcpy(paTests, &g_aSerialTests[0], RT_ELEMENTS(g_aSerialTests) * sizeof(SERIALTESTDESC)); + } + + rc = RTRandAdvCreateParkMiller(&g_hRand); + if (RT_FAILURE(rc)) + { + RTPrintf("Failed to create random number generator: %Rrc\n", rc); + return RTEXITCODE_FAILURE; + } + + rc = RTRandAdvSeed(g_hRand, UINT64_C(0x123456789abcdef)); + AssertRC(rc); + + /* + * Start testing. + */ + RTTestBanner(g_hTest); + + if (pszDevice) + { + uint32_t fFlags = RTSERIALPORT_OPEN_F_READ + | RTSERIALPORT_OPEN_F_WRITE + | RTSERIALPORT_OPEN_F_SUPPORT_STATUS_LINE_MONITORING; + + RTTestSub(g_hTest, "Opening device"); + rc = RTSerialPortOpen(&g_hSerialPort, pszDevice, fFlags); + if (RT_SUCCESS(rc)) + { + if (g_enmMode == SERIALTESTMODE_SECONDARY) + { + RTTestSub(g_hTest, "Opening secondary device"); + rc = RTSerialPortOpen(&g_hSerialPortSecondary, pszDeviceSecondary, fFlags); + if (RT_FAILURE(rc)) + serialTestFailed(g_hTest, "Opening secondary device \"%s\" failed with %Rrc\n", pszDevice, rc); + } + + if (RT_SUCCESS(rc)) + { + RTTestSub(g_hTest, "Setting serial port configuration"); + + rc = RTSerialPortCfgSet(g_hSerialPort, &g_SerialPortCfg ,NULL); + if (RT_SUCCESS(rc)) + { + if (g_enmMode == SERIALTESTMODE_SECONDARY) + { + RTTestSub(g_hTest, "Setting serial port configuration for secondary device"); + rc = RTSerialPortCfgSet(g_hSerialPortSecondary, &g_SerialPortCfg, NULL); + if (RT_FAILURE(rc)) + serialTestFailed(g_hTest, "Setting configuration of secondary device \"%s\" failed with %Rrc\n", pszDevice, rc); + } + + if (RT_SUCCESS(rc)) + { + SERIALTEST Test; + PSERIALTESTDESC pTest = &paTests[0]; + + Test.hTest = g_hTest; + Test.hSerialPort = g_hSerialPort; + Test.pSerialCfg = &g_SerialPortCfg; + + while (pTest->pszId) + { + RTTestSub(g_hTest, pTest->pszDesc); + rc = pTest->pfnRun(&Test); + if ( RT_FAILURE(rc) + || RTTestErrorCount(g_hTest) > 0) + serialTestFailed(g_hTest, "Running test \"%s\" failed (%Rrc, cErrors=%u)\n", + pTest->pszId, rc, RTTestErrorCount(g_hTest)); + + RTTestSubDone(g_hTest); + pTest++; + } + } + } + else + serialTestFailed(g_hTest, "Setting configuration of device \"%s\" failed with %Rrc\n", pszDevice, rc); + + RTSerialPortClose(g_hSerialPort); + } + } + else + serialTestFailed(g_hTest, "Opening device \"%s\" failed with %Rrc\n", pszDevice, rc); + } + else + serialTestFailed(g_hTest, "No device given on command line\n"); + + RTRandAdvDestroy(g_hRand); + RTMemFree(paTests); + RTEXITCODE rcExit = RTTestSummaryAndDestroy(g_hTest); + return rcExit; +} + diff --git a/src/VBox/ValidationKit/utils/storage/IoPerf.cpp b/src/VBox/ValidationKit/utils/storage/IoPerf.cpp new file mode 100644 index 00000000..5fed6ccb --- /dev/null +++ b/src/VBox/ValidationKit/utils/storage/IoPerf.cpp @@ -0,0 +1,1408 @@ +/* $Id: IoPerf.cpp $ */ +/** @file + * IoPerf - Storage I/O Performance Benchmark. + */ + +/* + * Copyright (C) 2019-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/ioqueue.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/rand.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/system.h> +#include <iprt/test.h> +#include <iprt/time.h> +#include <iprt/thread.h> +#include <iprt/zero.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** Size multiplier for the random data buffer to seek around. */ +#define IOPERF_RAND_DATA_BUF_FACTOR 3 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** Forward declaration of the master. */ +typedef struct IOPERFMASTER *PIOPERFMASTER; + +/** + * I/O perf supported tests. + */ +typedef enum IOPERFTEST +{ + /** Invalid test handle. */ + IOPERFTEST_INVALID = 0, + /** The test was disabled. */ + IOPERFTEST_DISABLED, + IOPERFTEST_FIRST_WRITE, + IOPERFTEST_SEQ_READ, + IOPERFTEST_SEQ_WRITE, + IOPERFTEST_REV_READ, + IOPERFTEST_REV_WRITE, + IOPERFTEST_RND_READ, + IOPERFTEST_RND_WRITE, + IOPERFTEST_SEQ_READWRITE, + IOPERFTEST_RND_READWRITE, + /** Special shutdown test which lets the workers exit, must be LAST. */ + IOPERFTEST_SHUTDOWN, + IOPERFTEST_32BIT_HACK = 0x7fffffff +} IOPERFTEST; + + +/** + * I/O perf test set preparation method. + */ +typedef enum IOPERFTESTSETPREP +{ + IOPERFTESTSETPREP_INVALID = 0, + /** Just create the file and don't set any sizes. */ + IOPERFTESTSETPREP_JUST_CREATE, + /** Standard RTFileSetSize() call which might create a sparse file. */ + IOPERFTESTSETPREP_SET_SZ, + /** Uses RTFileSetAllocationSize() to ensure storage is allocated for the file. */ + IOPERFTESTSETPREP_SET_ALLOC_SZ, + /** 32bit hack. */ + IOPERFTESTSETPREP_32BIT_HACK = 0x7fffffff +} IOPERFTESTSETPREP; + + +/** + * Statistics values for a single request kept around until the + * test completed for statistics collection. + */ +typedef struct IOPERFREQSTAT +{ + /** Start timestamp for the request. */ + uint64_t tsStart; + /** Completion timestamp for the request. */ + uint64_t tsComplete; +} IOPERFREQSTAT; +/** Pointer to a request statistics record. */ +typedef IOPERFREQSTAT *PIOPERFREQSTAT; + + +/** + * I/O perf request. + */ +typedef struct IOPERFREQ +{ + /** Request operation code. */ + RTIOQUEUEOP enmOp; + /** Start offset. */ + uint64_t offXfer; + /** Transfer size for the request. */ + size_t cbXfer; + /** The buffer used for the transfer. */ + void *pvXfer; + /** This is the statically assigned destination buffer for read requests for this request. */ + void *pvXferRead; + /** Size of the read buffer. */ + size_t cbXferRead; + /** Pointer to statistics record. */ + PIOPERFREQSTAT pStats; +} IOPERFREQ; +/** Pointer to an I/O perf request. */ +typedef IOPERFREQ *PIOPERFREQ; +/** Pointer to a constant I/O perf request. */ +typedef const IOPERFREQ *PCIOPERFREQ; + + +/** + * I/O perf job data. + */ +typedef struct IOPERFJOB +{ + /** Pointer to the master if multiple jobs are running. */ + PIOPERFMASTER pMaster; + /** Job ID. */ + uint32_t idJob; + /** The test this job is executing. */ + volatile IOPERFTEST enmTest; + /** The thread executing the job. */ + RTTHREAD hThread; + /** The I/O queue for the job. */ + RTIOQUEUE hIoQueue; + /** The file path used. */ + char *pszFilename; + /** The handle to use for the I/O queue. */ + RTHANDLE Hnd; + /** Multi event semaphore to synchronise with other jobs. */ + RTSEMEVENTMULTI hSemEvtMultiRendezvous; + /** The test set size. */ + uint64_t cbTestSet; + /** Size of one I/O block. */ + size_t cbIoBlock; + /** Maximum number of requests to queue. */ + uint32_t cReqsMax; + /** Pointer to the array of request specific data. */ + PIOPERFREQ paIoReqs; + /** Page aligned chunk of memory assigned as read buffers for the individual requests. */ + void *pvIoReqReadBuf; + /** Size of the read memory buffer. */ + size_t cbIoReqReadBuf; + /** Random number generator used. */ + RTRAND hRand; + /** The random data buffer used for writes. */ + uint8_t *pbRandWrite; + /** Size of the random write buffer in 512 byte blocks. */ + uint32_t cRandWriteBlocks512B; + /** Chance in percent to get a write. */ + unsigned uWriteChance; + /** Flag whether to verify read data. */ + bool fVerifyReads; + /** Start timestamp. */ + uint64_t tsStart; + /** End timestamp. for the job. */ + uint64_t tsFinish; + /** Number of request statistic records. */ + uint32_t cReqStats; + /** Index of the next free statistics record to use. */ + uint32_t idxReqStatNext; + /** Array of request statistic records for the whole test. */ + PIOPERFREQSTAT paReqStats; + /** Test dependent data. */ + union + { + /** Sequential read write. */ + uint64_t offNextSeq; + /** Data for random acess. */ + struct + { + /** Number of valid entries in the bitmap. */ + uint32_t cBlocks; + /** Pointer to the bitmap marking accessed blocks. */ + uint8_t *pbMapAccessed; + /** Number of unaccessed blocks. */ + uint32_t cBlocksLeft; + } Rnd; + } Tst; +} IOPERFJOB; +/** Pointer to an I/O Perf job. */ +typedef IOPERFJOB *PIOPERFJOB; + + +/** + * I/O perf master instance coordinating the job execution. + */ +typedef struct IOPERFMASTER +{ + /** Event semaphore. */ + /** Number of jobs. */ + uint32_t cJobs; + /** Job instances, variable in size. */ + IOPERFJOB aJobs[1]; +} IOPERFMASTER; + + +enum +{ + kCmdOpt_First = 128, + + kCmdOpt_FirstWrite = kCmdOpt_First, + kCmdOpt_NoFirstWrite, + kCmdOpt_SeqRead, + kCmdOpt_NoSeqRead, + kCmdOpt_SeqWrite, + kCmdOpt_NoSeqWrite, + kCmdOpt_RndRead, + kCmdOpt_NoRndRead, + kCmdOpt_RndWrite, + kCmdOpt_NoRndWrite, + kCmdOpt_RevRead, + kCmdOpt_NoRevRead, + kCmdOpt_RevWrite, + kCmdOpt_NoRevWrite, + kCmdOpt_SeqReadWrite, + kCmdOpt_NoSeqReadWrite, + kCmdOpt_RndReadWrite, + kCmdOpt_NoRndReadWrite, + + kCmdOpt_End +}; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Command line parameters */ +static const RTGETOPTDEF g_aCmdOptions[] = +{ + { "--dir", 'd', RTGETOPT_REQ_STRING }, + { "--relative-dir", 'r', RTGETOPT_REQ_NOTHING }, + + { "--jobs", 'j', RTGETOPT_REQ_UINT32 }, + { "--io-engine", 'i', RTGETOPT_REQ_STRING }, + { "--test-set-size", 's', RTGETOPT_REQ_UINT64 }, + { "--block-size", 'b', RTGETOPT_REQ_UINT32 }, + { "--maximum-requests", 'm', RTGETOPT_REQ_UINT32 }, + { "--verify-reads", 'y', RTGETOPT_REQ_BOOL }, + { "--use-cache", 'c', RTGETOPT_REQ_BOOL }, + + { "--first-write", kCmdOpt_FirstWrite, RTGETOPT_REQ_NOTHING }, + { "--no-first-write", kCmdOpt_NoFirstWrite, RTGETOPT_REQ_NOTHING }, + { "--seq-read", kCmdOpt_SeqRead, RTGETOPT_REQ_NOTHING }, + { "--no-seq-read", kCmdOpt_NoSeqRead, RTGETOPT_REQ_NOTHING }, + { "--seq-write", kCmdOpt_SeqWrite, RTGETOPT_REQ_NOTHING }, + { "--no-seq-write", kCmdOpt_NoSeqWrite, RTGETOPT_REQ_NOTHING }, + { "--rnd-read", kCmdOpt_RndRead, RTGETOPT_REQ_NOTHING }, + { "--no-rnd-read", kCmdOpt_NoRndRead, RTGETOPT_REQ_NOTHING }, + { "--rnd-write", kCmdOpt_RndWrite, RTGETOPT_REQ_NOTHING }, + { "--no-rnd-write", kCmdOpt_NoRndWrite, RTGETOPT_REQ_NOTHING }, + { "--rev-read", kCmdOpt_RevRead, RTGETOPT_REQ_NOTHING }, + { "--no-rev-read", kCmdOpt_NoRevRead, RTGETOPT_REQ_NOTHING }, + { "--rev-write", kCmdOpt_RevWrite, RTGETOPT_REQ_NOTHING }, + { "--no-rev-write", kCmdOpt_NoRevWrite, RTGETOPT_REQ_NOTHING }, + { "--seq-read-write", kCmdOpt_SeqReadWrite, RTGETOPT_REQ_NOTHING }, + { "--no-seq-read-write", kCmdOpt_NoSeqReadWrite, RTGETOPT_REQ_NOTHING }, + { "--rnd-read-write", kCmdOpt_RndReadWrite, RTGETOPT_REQ_NOTHING }, + { "--no-rnd-read-write", kCmdOpt_NoRndReadWrite, RTGETOPT_REQ_NOTHING }, + + { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + { "--version", 'V', RTGETOPT_REQ_NOTHING }, + { "--help", 'h', RTGETOPT_REQ_NOTHING } /* for Usage() */ +}; + +/** The test handle. */ +static RTTEST g_hTest; +/** Verbosity level. */ +static uint32_t g_uVerbosity = 0; +/** Selected I/O engine for the tests, NULL means pick best default one. */ +static const char *g_pszIoEngine = NULL; +/** Number of jobs to run concurrently. */ +static uint32_t g_cJobs = 1; +/** Size of each test set (file) in bytes. */ +static uint64_t g_cbTestSet = _2G; +/** Block size for each request. */ +static size_t g_cbIoBlock = _4K; +/** Maximum number of concurrent requests for each job. */ +static uint32_t g_cReqsMax = 16; +/** Flag whether to open the file without caching enabled. */ +static bool g_fNoCache = true; +/** Write chance for mixed read/write tests. */ +static unsigned g_uWriteChance = 50; +/** Flag whether to verify read data. */ +static bool g_fVerifyReads = true; + +/** @name Configured tests, this must match the IOPERFTEST order. + * @{ */ +static IOPERFTEST g_aenmTests[] = +{ + IOPERFTEST_DISABLED, /** @< The invalid test value is disabled of course. */ + IOPERFTEST_DISABLED, + IOPERFTEST_FIRST_WRITE, + IOPERFTEST_SEQ_READ, + IOPERFTEST_SEQ_WRITE, + IOPERFTEST_REV_READ, + IOPERFTEST_REV_WRITE, + IOPERFTEST_RND_READ, + IOPERFTEST_RND_WRITE, + IOPERFTEST_SEQ_READWRITE, + IOPERFTEST_RND_READWRITE, + IOPERFTEST_SHUTDOWN +}; +/** The test index being selected next. */ +static uint32_t g_idxTest = 2; +/** @} */ + +/** Set if g_szDir and friends are path relative to CWD rather than absolute. */ +static bool g_fRelativeDir = false; +/** The length of g_szDir. */ +static size_t g_cchDir; + +/** The test directory (absolute). This will always have a trailing slash. */ +static char g_szDir[RTPATH_BIG_MAX]; + + +/********************************************************************************************************************************* +* Tests * +*********************************************************************************************************************************/ + + +/** + * Selects the next test to run. + * + * @return Next test to run. + */ +static IOPERFTEST ioPerfJobTestSelectNext() +{ + AssertReturn(g_idxTest < RT_ELEMENTS(g_aenmTests), IOPERFTEST_SHUTDOWN); + + while ( g_idxTest < RT_ELEMENTS(g_aenmTests) + && g_aenmTests[g_idxTest] == IOPERFTEST_DISABLED) + g_idxTest++; + + AssertReturn(g_idxTest < RT_ELEMENTS(g_aenmTests), IOPERFTEST_SHUTDOWN); + + return g_aenmTests[g_idxTest++]; +} + + +/** + * Returns the I/O queue operation for the next request. + * + * @returns I/O queue operation enum. + * @param pJob The job data for the current worker. + */ +static RTIOQUEUEOP ioPerfJobTestGetIoQOp(PIOPERFJOB pJob) +{ + switch (pJob->enmTest) + { + case IOPERFTEST_FIRST_WRITE: + case IOPERFTEST_SEQ_WRITE: + case IOPERFTEST_REV_WRITE: + case IOPERFTEST_RND_WRITE: + return RTIOQUEUEOP_WRITE; + + case IOPERFTEST_SEQ_READ: + case IOPERFTEST_RND_READ: + case IOPERFTEST_REV_READ: + return RTIOQUEUEOP_READ; + + case IOPERFTEST_SEQ_READWRITE: + case IOPERFTEST_RND_READWRITE: + { + uint32_t uRnd = RTRandAdvU32Ex(pJob->hRand, 0, 100); + return (uRnd < pJob->uWriteChance) ? RTIOQUEUEOP_WRITE : RTIOQUEUEOP_READ; + } + + default: + AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest)); + break; + } + + return RTIOQUEUEOP_INVALID; +} + + +/** + * Returns the offset to use for the next request. + * + * @returns Offset to use. + * @param pJob The job data for the current worker. + */ +static uint64_t ioPerfJobTestGetOffsetNext(PIOPERFJOB pJob) +{ + uint64_t offNext = 0; + + switch (pJob->enmTest) + { + case IOPERFTEST_FIRST_WRITE: + case IOPERFTEST_SEQ_WRITE: + case IOPERFTEST_SEQ_READ: + case IOPERFTEST_SEQ_READWRITE: + offNext = pJob->Tst.offNextSeq; + pJob->Tst.offNextSeq += pJob->cbIoBlock; + break; + case IOPERFTEST_REV_WRITE: + case IOPERFTEST_REV_READ: + offNext = pJob->Tst.offNextSeq; + if (pJob->Tst.offNextSeq == 0) + pJob->Tst.offNextSeq = pJob->cbTestSet; + else + pJob->Tst.offNextSeq -= pJob->cbIoBlock; + break; + case IOPERFTEST_RND_WRITE: + case IOPERFTEST_RND_READ: + case IOPERFTEST_RND_READWRITE: + { + int idx = -1; + + idx = ASMBitFirstClear(pJob->Tst.Rnd.pbMapAccessed, pJob->Tst.Rnd.cBlocks); + + /* In case this is the last request we don't need to search further. */ + if (pJob->Tst.Rnd.cBlocksLeft > 1) + { + int idxIo; + idxIo = RTRandAdvU32Ex(pJob->hRand, idx, pJob->Tst.Rnd.cBlocks - 1); + + /* + * If the bit is marked free use it, otherwise search for the next free bit + * and if that doesn't work use the first free bit. + */ + if (ASMBitTest(pJob->Tst.Rnd.pbMapAccessed, idxIo)) + { + idxIo = ASMBitNextClear(pJob->Tst.Rnd.pbMapAccessed, pJob->Tst.Rnd.cBlocks, idxIo); + if (idxIo != -1) + idx = idxIo; + } + else + idx = idxIo; + } + + Assert(idx != -1); + offNext = (uint64_t)idx * pJob->cbIoBlock; + pJob->Tst.Rnd.cBlocksLeft--; + ASMBitSet(pJob->Tst.Rnd.pbMapAccessed, idx); + break; + } + default: + AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest)); + break; + } + + return offNext; +} + + +/** + * Returns a pointer to the write buffer with random data for the given offset which + * is predictable for data verification. + * + * @returns Pointer to I/O block sized data buffer with random data. + * @param pJob The job data for the current worker. + * @param off The offset to get the buffer for. + */ +static void *ioPerfJobTestGetWriteBufForOffset(PIOPERFJOB pJob, uint64_t off) +{ + /* + * Dividing the file into 512 byte blocks so buffer pointers are at least + * 512 byte aligned to work with async I/O on some platforms (Linux and O_DIRECT for example). + */ + uint64_t uBlock = off / 512; + uint32_t idxBuf = uBlock % pJob->cRandWriteBlocks512B; + return pJob->pbRandWrite + idxBuf * 512; +} + + +/** + * Initialize the given request for submission. + * + * @returns nothing. + * @param pJob The job data for the current worker. + * @param pIoReq The request to initialize. + */ +static void ioPerfJobTestReqInit(PIOPERFJOB pJob, PIOPERFREQ pIoReq) +{ + pIoReq->enmOp = ioPerfJobTestGetIoQOp(pJob); + pIoReq->offXfer = ioPerfJobTestGetOffsetNext(pJob); + pIoReq->cbXfer = pJob->cbIoBlock; + if (pIoReq->enmOp == RTIOQUEUEOP_READ) + pIoReq->pvXfer = pIoReq->pvXferRead; + else if (pIoReq->enmOp == RTIOQUEUEOP_WRITE) + pIoReq->pvXfer = ioPerfJobTestGetWriteBufForOffset(pJob, pIoReq->offXfer); + else /* Flush */ + pIoReq->pvXfer = NULL; + + Assert(pJob->idxReqStatNext < pJob->cReqStats); + if (RT_LIKELY(pJob->idxReqStatNext < pJob->cReqStats)) + { + pIoReq->pStats = &pJob->paReqStats[pJob->idxReqStatNext++]; + pIoReq->pStats->tsStart = RTTimeNanoTS(); + } + else + pIoReq->pStats = NULL; +} + + +/** + * Returns a stringified version of the test given. + * + * @returns Pointer to string representation of the test. + * @param enmTest The test to stringify. + */ +static const char *ioPerfJobTestStringify(IOPERFTEST enmTest) +{ + switch (enmTest) + { + case IOPERFTEST_FIRST_WRITE: + return "FirstWrite"; + case IOPERFTEST_SEQ_WRITE: + return "SequentialWrite"; + case IOPERFTEST_SEQ_READ: + return "SequentialRead"; + case IOPERFTEST_REV_WRITE: + return "ReverseWrite"; + case IOPERFTEST_REV_READ: + return "ReverseRead"; + case IOPERFTEST_RND_WRITE: + return "RandomWrite"; + case IOPERFTEST_RND_READ: + return "RandomRead"; + case IOPERFTEST_SEQ_READWRITE: + return "SequentialReadWrite"; + case IOPERFTEST_RND_READWRITE: + return "RandomReadWrite"; + default: + AssertMsgFailed(("Invalid/unknown test selected: %d\n", enmTest)); + break; + } + + return "INVALID_TEST"; +} + + +/** + * Initializes the test state for the current test. + * + * @returns IPRT status code. + * @param pJob The job data for the current worker. + */ +static int ioPerfJobTestInit(PIOPERFJOB pJob) +{ + int rc = VINF_SUCCESS; + + pJob->idxReqStatNext = 0; + + switch (pJob->enmTest) + { + case IOPERFTEST_FIRST_WRITE: + case IOPERFTEST_SEQ_WRITE: + case IOPERFTEST_SEQ_READ: + case IOPERFTEST_SEQ_READWRITE: + pJob->Tst.offNextSeq = 0; + break; + case IOPERFTEST_REV_WRITE: + case IOPERFTEST_REV_READ: + pJob->Tst.offNextSeq = pJob->cbTestSet - pJob->cbIoBlock; + break; + case IOPERFTEST_RND_WRITE: + case IOPERFTEST_RND_READ: + case IOPERFTEST_RND_READWRITE: + { + pJob->Tst.Rnd.cBlocks = (uint32_t)( pJob->cbTestSet / pJob->cbIoBlock + + (pJob->cbTestSet % pJob->cbIoBlock ? 1 : 0)); + pJob->Tst.Rnd.cBlocksLeft = pJob->Tst.Rnd.cBlocks; + pJob->Tst.Rnd.pbMapAccessed = (uint8_t *)RTMemAllocZ( pJob->Tst.Rnd.cBlocks / 8 + + ((pJob->Tst.Rnd.cBlocks % 8) + ? 1 + : 0)); + if (!pJob->Tst.Rnd.pbMapAccessed) + rc = VERR_NO_MEMORY; + break; + } + default: + AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest)); + break; + } + + pJob->tsStart = RTTimeNanoTS(); + return rc; +} + + +/** + * Frees allocated resources specific for the current test. + * + * @returns nothing. + * @param pJob The job data for the current worker. + */ +static void ioPerfJobTestFinish(PIOPERFJOB pJob) +{ + pJob->tsFinish = RTTimeNanoTS(); + + switch (pJob->enmTest) + { + case IOPERFTEST_FIRST_WRITE: + case IOPERFTEST_SEQ_WRITE: + case IOPERFTEST_SEQ_READ: + case IOPERFTEST_REV_WRITE: + case IOPERFTEST_REV_READ: + case IOPERFTEST_SEQ_READWRITE: + break; /* Nothing to do. */ + + case IOPERFTEST_RND_WRITE: + case IOPERFTEST_RND_READ: + case IOPERFTEST_RND_READWRITE: + RTMemFree(pJob->Tst.Rnd.pbMapAccessed); + break; + default: + AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest)); + break; + } +} + + +/** + * Returns whether the current test is done with submitting new requests (reached test set size). + * + * @returns True when the test has submitted all required requests, false if there are still requests required + */ +static bool ioPerfJobTestIsDone(PIOPERFJOB pJob) +{ + switch (pJob->enmTest) + { + case IOPERFTEST_FIRST_WRITE: + case IOPERFTEST_SEQ_WRITE: + case IOPERFTEST_SEQ_READ: + case IOPERFTEST_REV_WRITE: + case IOPERFTEST_REV_READ: + case IOPERFTEST_SEQ_READWRITE: + return pJob->Tst.offNextSeq == pJob->cbTestSet; + case IOPERFTEST_RND_WRITE: + case IOPERFTEST_RND_READ: + case IOPERFTEST_RND_READWRITE: + return pJob->Tst.Rnd.cBlocksLeft == 0; + break; + default: + AssertMsgFailed(("Invalid/unknown test selected: %d\n", pJob->enmTest)); + break; + } + + return true; +} + + +/** + * The test I/O loop pumping I/O. + * + * @returns IPRT status code. + * @param pJob The job data for the current worker. + */ +static int ioPerfJobTestIoLoop(PIOPERFJOB pJob) +{ + int rc = ioPerfJobTestInit(pJob); + if (RT_SUCCESS(rc)) + { + /* Allocate the completion event array. */ + uint32_t cReqsQueued = 0; + PRTIOQUEUECEVT paIoQCEvt = (PRTIOQUEUECEVT)RTMemAllocZ(pJob->cReqsMax * sizeof(RTIOQUEUECEVT)); + if (RT_LIKELY(paIoQCEvt)) + { + /* Queue requests up to the maximum. */ + while ( (cReqsQueued < pJob->cReqsMax) + && !ioPerfJobTestIsDone(pJob) + && RT_SUCCESS(rc)) + { + PIOPERFREQ pReq = &pJob->paIoReqs[cReqsQueued]; + ioPerfJobTestReqInit(pJob, pReq); + RTTESTI_CHECK_RC(RTIoQueueRequestPrepare(pJob->hIoQueue, &pJob->Hnd, pReq->enmOp, + pReq->offXfer, pReq->pvXfer, pReq->cbXfer, 0 /*fReqFlags*/, + pReq), VINF_SUCCESS); + cReqsQueued++; + } + + /* Commit the prepared requests. */ + if ( RT_SUCCESS(rc) + && cReqsQueued) + { + RTTESTI_CHECK_RC(RTIoQueueCommit(pJob->hIoQueue), VINF_SUCCESS); + } + + /* Enter wait loop and process completed requests. */ + while ( RT_SUCCESS(rc) + && cReqsQueued) + { + uint32_t cCEvtCompleted = 0; + + RTTESTI_CHECK_RC(RTIoQueueEvtWait(pJob->hIoQueue, paIoQCEvt, pJob->cReqsMax, 1 /*cMinWait*/, + &cCEvtCompleted, 0 /*fFlags*/), VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + uint32_t cReqsThisQueued = 0; + + /* Process any completed event and continue to fill the queue as long as there is stuff to do. */ + for (uint32_t i = 0; i < cCEvtCompleted; i++) + { + PIOPERFREQ pReq = (PIOPERFREQ)paIoQCEvt[i].pvUser; + + if (RT_SUCCESS(paIoQCEvt[i].rcReq)) + { + Assert(paIoQCEvt[i].cbXfered == pReq->cbXfer); + + if (pReq->pStats) + pReq->pStats->tsComplete = RTTimeNanoTS(); + + if ( pJob->fVerifyReads + && pReq->enmOp == RTIOQUEUEOP_READ) + { + const void *pvBuf = ioPerfJobTestGetWriteBufForOffset(pJob, pReq->offXfer); + if (memcmp(pReq->pvXferRead, pvBuf, pReq->cbXfer)) + { + if (g_uVerbosity > 1) + RTTestIFailed("IoPerf: Corrupted data detected by read at offset %#llu (sz: %zu)", pReq->offXfer, pReq->cbXfer); + else + RTTestIErrorInc(); + } + } + + if (!ioPerfJobTestIsDone(pJob)) + { + ioPerfJobTestReqInit(pJob, pReq); + RTTESTI_CHECK_RC(RTIoQueueRequestPrepare(pJob->hIoQueue, &pJob->Hnd, pReq->enmOp, + pReq->offXfer, pReq->pvXfer, pReq->cbXfer, 0 /*fReqFlags*/, + pReq), VINF_SUCCESS); + cReqsThisQueued++; + } + else + cReqsQueued--; + } + else + RTTestIErrorInc(); + } + + if ( cReqsThisQueued + && RT_SUCCESS(rc)) + { + RTTESTI_CHECK_RC(RTIoQueueCommit(pJob->hIoQueue), VINF_SUCCESS); + } + } + } + + RTMemFree(paIoQCEvt); + } + + ioPerfJobTestFinish(pJob); + } + + return rc; +} + + +/** + * Calculates the statistic values for the given job after a + * test finished. + * + * @returns nothing. + * @param pJob The job data. + */ +static void ioPerfJobStats(PIOPERFJOB pJob) +{ + const char *pszTest = ioPerfJobTestStringify(pJob->enmTest); + uint64_t nsJobRuntime = pJob->tsFinish - pJob->tsStart; + RTTestIValueF(nsJobRuntime, RTTESTUNIT_NS, "%s/Job/%RU32/Runtime", pszTest, pJob->idJob); + + uint64_t *paReqRuntimeNs = (uint64_t *)RTMemAllocZ(pJob->cReqStats * sizeof(uint64_t)); + if (RT_LIKELY(paReqRuntimeNs)) + { + /* Calculate runtimes for each request first. */ + for (uint32_t i = 0; i < pJob->cReqStats; i++) + { + PIOPERFREQSTAT pStat = &pJob->paReqStats[i]; + paReqRuntimeNs[i] = pStat->tsComplete - pStat->tsStart; + } + + /* Get average bandwidth for the job. */ + RTTestIValueF((uint64_t)((double)pJob->cbTestSet / ((double)nsJobRuntime / RT_NS_1SEC)), + RTTESTUNIT_BYTES_PER_SEC, "%s/Job/%RU32/AvgBandwidth", pszTest, pJob->idJob); + + RTTestIValueF((uint64_t)(pJob->cReqStats / ((double)nsJobRuntime / RT_NS_1SEC)), + RTTESTUNIT_OCCURRENCES_PER_SEC, "%s/Job/%RU32/AvgIops", pszTest, pJob->idJob); + + /* Calculate the average latency for the requests. */ + uint64_t uLatency = 0; + for (uint32_t i = 0; i < pJob->cReqStats; i++) + uLatency += paReqRuntimeNs[i]; + RTTestIValueF(uLatency / pJob->cReqStats, RTTESTUNIT_NS, "%s/Job/%RU32/AvgLatency", pszTest, pJob->idJob); + + RTMemFree(paReqRuntimeNs); + } + else + RTTestIErrorInc(); +} + + +/** + * Synchronizes with the other jobs and waits for the current test to execute. + * + * @returns IPRT status. + * @param pJob The job data for the current worker. + */ +static int ioPerfJobSync(PIOPERFJOB pJob) +{ + if (pJob->pMaster) + { + /* Enter the rendezvous semaphore. */ + int rc = VINF_SUCCESS; + + return rc; + } + + /* Single threaded run, collect the results from our current test and select the next test. */ + /** @todo Results and statistics collection. */ + pJob->enmTest = ioPerfJobTestSelectNext(); + return VINF_SUCCESS; +} + + +/** + * I/O perf job main work loop. + * + * @returns IPRT status code. + * @param pJob The job data for the current worker. + */ +static int ioPerfJobWorkLoop(PIOPERFJOB pJob) +{ + int rc = VINF_SUCCESS; + + for (;;) + { + /* Synchronize with the other jobs and the master. */ + rc = ioPerfJobSync(pJob); + if (RT_FAILURE(rc)) + break; + + if (pJob->enmTest == IOPERFTEST_SHUTDOWN) + break; + + rc = ioPerfJobTestIoLoop(pJob); + if (RT_FAILURE(rc)) + break; + + /* + * Do the statistics here for a single job run, + * the master will do this for each job and combined statistics + * otherwise. + */ + if (!pJob->pMaster) + ioPerfJobStats(pJob); + } + + return rc; +} + + +/** + * Job thread entry point. + */ +static DECLCALLBACK(int) ioPerfJobThread(RTTHREAD hThrdSelf, void *pvUser) +{ + RT_NOREF(hThrdSelf); + + PIOPERFJOB pJob = (PIOPERFJOB)pvUser; + return ioPerfJobWorkLoop(pJob); +} + + +/** + * Prepares the test set by laying out the files and filling them with data. + * + * @returns IPRT status code. + * @param pJob The job to initialize. + */ +static int ioPerfJobTestSetPrep(PIOPERFJOB pJob) +{ + int rc = RTRandAdvCreateParkMiller(&pJob->hRand); + if (RT_SUCCESS(rc)) + { + rc = RTRandAdvSeed(pJob->hRand, RTTimeNanoTS()); + if (RT_SUCCESS(rc)) + { + /* + * Create a random data buffer for writes, we'll use multiple of the I/O block size to + * be able to seek in the buffer quite a bit to make the file content as random as possible + * to avoid mechanisms like compression or deduplication for now which can influence storage + * benchmarking unpredictably. + */ + pJob->cRandWriteBlocks512B = (uint32_t)(((IOPERF_RAND_DATA_BUF_FACTOR - 1) * pJob->cbIoBlock) / 512); + pJob->pbRandWrite = (uint8_t *)RTMemPageAllocZ(IOPERF_RAND_DATA_BUF_FACTOR * pJob->cbIoBlock); + if (RT_LIKELY(pJob->pbRandWrite)) + { + RTRandAdvBytes(pJob->hRand, pJob->pbRandWrite, IOPERF_RAND_DATA_BUF_FACTOR * pJob->cbIoBlock); + + /* Write the content here if the first write test is disabled. */ + if (g_aenmTests[IOPERFTEST_FIRST_WRITE] == IOPERFTEST_DISABLED) + { + for (uint64_t off = 0; off < pJob->cbTestSet && RT_SUCCESS(rc); off += pJob->cbIoBlock) + { + void *pvWrite = ioPerfJobTestGetWriteBufForOffset(pJob, off); + rc = RTFileWriteAt(pJob->Hnd.u.hFile, off, pvWrite, pJob->cbIoBlock, NULL); + } + } + + if (RT_SUCCESS(rc)) + return rc; + + RTMemPageFree(pJob->pbRandWrite, IOPERF_RAND_DATA_BUF_FACTOR * pJob->cbIoBlock); + } + } + RTRandAdvDestroy(pJob->hRand); + } + + return rc; +} + + +/** + * Initializes the given job instance. + * + * @returns IPRT status code. + * @param pJob The job to initialize. + * @param pMaster The coordination master if any. + * @param idJob ID of the job. + * @param pszIoEngine I/O queue engine for this job, NULL for best default. + * @param pszTestDir The test directory to create the file in - requires a slash a the end. + * @param enmPrepMethod Test set preparation method to use. + * @param cbTestSet Size of the test set ofr this job. + * @param cbIoBlock I/O block size for the given job. + * @param cReqsMax Maximum number of concurrent requests for this job. + * @param uWriteChance The write chance for mixed read/write tests. + * @param fVerifyReads Flag whether to verify read data. + */ +static int ioPerfJobInit(PIOPERFJOB pJob, PIOPERFMASTER pMaster, uint32_t idJob, + const char *pszIoEngine, const char *pszTestDir, + IOPERFTESTSETPREP enmPrepMethod, + uint64_t cbTestSet, size_t cbIoBlock, uint32_t cReqsMax, + unsigned uWriteChance, bool fVerifyReads) +{ + pJob->pMaster = pMaster; + pJob->idJob = idJob; + pJob->enmTest = IOPERFTEST_INVALID; + pJob->hThread = NIL_RTTHREAD; + pJob->Hnd.enmType = RTHANDLETYPE_FILE; + pJob->cbTestSet = cbTestSet; + pJob->cbIoBlock = cbIoBlock; + pJob->cReqsMax = cReqsMax; + pJob->cbIoReqReadBuf = cReqsMax * cbIoBlock; + pJob->uWriteChance = uWriteChance; + pJob->fVerifyReads = fVerifyReads; + pJob->cReqStats = (uint32_t)(pJob->cbTestSet / pJob->cbIoBlock + ((pJob->cbTestSet % pJob->cbIoBlock) ? 1 : 0)); + pJob->idxReqStatNext = 0; + + int rc = VINF_SUCCESS; + pJob->paIoReqs = (PIOPERFREQ)RTMemAllocZ(cReqsMax * sizeof(IOPERFREQ)); + if (RT_LIKELY(pJob->paIoReqs)) + { + pJob->paReqStats = (PIOPERFREQSTAT)RTMemAllocZ(pJob->cReqStats * sizeof(IOPERFREQSTAT)); + if (RT_LIKELY(pJob->paReqStats)) + { + pJob->pvIoReqReadBuf = RTMemPageAlloc(pJob->cbIoReqReadBuf); + if (RT_LIKELY(pJob->pvIoReqReadBuf)) + { + uint8_t *pbReadBuf = (uint8_t *)pJob->pvIoReqReadBuf; + + for (uint32_t i = 0; i < cReqsMax; i++) + { + pJob->paIoReqs[i].pvXferRead = pbReadBuf; + pJob->paIoReqs[i].cbXferRead = cbIoBlock; + pbReadBuf += cbIoBlock; + } + + /* Create the file. */ + pJob->pszFilename = RTStrAPrintf2("%sioperf-%u.file", pszTestDir, idJob); + if (RT_LIKELY(pJob->pszFilename)) + { + uint32_t fOpen = RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE | RTFILE_O_READWRITE | RTFILE_O_ASYNC_IO; + if (g_fNoCache) + fOpen |= RTFILE_O_NO_CACHE; + rc = RTFileOpen(&pJob->Hnd.u.hFile, pJob->pszFilename, fOpen); + if (RT_SUCCESS(rc)) + { + switch (enmPrepMethod) + { + case IOPERFTESTSETPREP_JUST_CREATE: + break; + case IOPERFTESTSETPREP_SET_SZ: + rc = RTFileSetSize(pJob->Hnd.u.hFile, pJob->cbTestSet); + break; + case IOPERFTESTSETPREP_SET_ALLOC_SZ: + rc = RTFileSetAllocationSize(pJob->Hnd.u.hFile, pJob->cbTestSet, RTFILE_ALLOC_SIZE_F_DEFAULT); + break; + default: + AssertMsgFailed(("Invalid file preparation method: %d\n", enmPrepMethod)); + } + + if (RT_SUCCESS(rc)) + { + rc = ioPerfJobTestSetPrep(pJob); + if (RT_SUCCESS(rc)) + { + /* Create I/O queue. */ + PCRTIOQUEUEPROVVTABLE pIoQProv = NULL; + if (!pszIoEngine) + pIoQProv = RTIoQueueProviderGetBestForHndType(RTHANDLETYPE_FILE); + else + pIoQProv = RTIoQueueProviderGetById(pszIoEngine); + + if (RT_LIKELY(pIoQProv)) + { + rc = RTIoQueueCreate(&pJob->hIoQueue, pIoQProv, 0 /*fFlags*/, cReqsMax, cReqsMax); + if (RT_SUCCESS(rc)) + { + rc = RTIoQueueHandleRegister(pJob->hIoQueue, &pJob->Hnd); + if (RT_SUCCESS(rc)) + { + /* Spin up the worker thread. */ + if (pMaster) + rc = RTThreadCreateF(&pJob->hThread, ioPerfJobThread, pJob, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "ioperf-%u", idJob); + + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + } + } + } + else + rc = VERR_NOT_SUPPORTED; + } + + RTRandAdvDestroy(pJob->hRand); + } + + RTFileClose(pJob->Hnd.u.hFile); + RTFileDelete(pJob->pszFilename); + } + + RTStrFree(pJob->pszFilename); + } + else + rc = VERR_NO_STR_MEMORY; + + RTMemPageFree(pJob->pvIoReqReadBuf, pJob->cbIoReqReadBuf); + } + else + rc = VERR_NO_MEMORY; + + RTMemFree(pJob->paReqStats); + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +/** + * Teardown a job instance and free all associated resources. + * + * @returns IPRT status code. + * @param pJob The job to teardown. + */ +static int ioPerfJobTeardown(PIOPERFJOB pJob) +{ + if (pJob->pMaster) + { + int rc = RTThreadWait(pJob->hThread, RT_INDEFINITE_WAIT, NULL); + AssertRC(rc); RT_NOREF(rc); + } + + RTIoQueueHandleDeregister(pJob->hIoQueue, &pJob->Hnd); + RTIoQueueDestroy(pJob->hIoQueue); + RTRandAdvDestroy(pJob->hRand); + RTMemPageFree(pJob->pbRandWrite, IOPERF_RAND_DATA_BUF_FACTOR * pJob->cbIoBlock); + RTFileClose(pJob->Hnd.u.hFile); + RTFileDelete(pJob->pszFilename); + RTStrFree(pJob->pszFilename); + RTMemPageFree(pJob->pvIoReqReadBuf, pJob->cbIoReqReadBuf); + RTMemFree(pJob->paIoReqs); + RTMemFree(pJob->paReqStats); + return VINF_SUCCESS; +} + + +/** + * Single job testing entry point. + * + * @returns IPRT status code. + */ +static int ioPerfDoTestSingle(void) +{ + IOPERFJOB Job; + + int rc = ioPerfJobInit(&Job, NULL, 0, g_pszIoEngine, + g_szDir, IOPERFTESTSETPREP_SET_SZ, + g_cbTestSet, g_cbIoBlock, g_cReqsMax, + g_uWriteChance, g_fVerifyReads); + if (RT_SUCCESS(rc)) + { + rc = ioPerfJobWorkLoop(&Job); + if (RT_SUCCESS(rc)) + { + rc = ioPerfJobTeardown(&Job); + AssertRC(rc); RT_NOREF(rc); + } + } + + return rc; +} + + +/** + * Multi job testing entry point. + * + * @returns IPRT status code. + */ +static int ioPerfDoTestMulti(void) +{ + return VERR_NOT_IMPLEMENTED; +} + + +/** + * Display the usage to @a pStrm. + */ +static void Usage(PRTSTREAM pStrm) +{ + char szExec[RTPATH_MAX]; + RTStrmPrintf(pStrm, "usage: %s <-d <testdir>> [options]\n", + RTPathFilename(RTProcGetExecutablePath(szExec, sizeof(szExec)))); + RTStrmPrintf(pStrm, "\n"); + RTStrmPrintf(pStrm, "options: \n"); + + for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++) + { + char szHelp[80]; + const char *pszHelp; + switch (g_aCmdOptions[i].iShort) + { + case 'd': pszHelp = "The directory to use for testing. default: CWD/fstestdir"; break; + case 'r': pszHelp = "Don't abspath test dir (good for deep dirs). default: disabled"; break; + case 'y': pszHelp = "Flag whether to verify read data. default: enabled"; break; + case 'c': pszHelp = "Flag whether to use the filesystem cache. default: disabled"; break; + case 'v': pszHelp = "More verbose execution."; break; + case 'q': pszHelp = "Quiet execution."; break; + case 'h': pszHelp = "Displays this help and exit"; break; + case 'V': pszHelp = "Displays the program revision"; break; + default: + if (g_aCmdOptions[i].iShort >= kCmdOpt_First) + { + if (RTStrStartsWith(g_aCmdOptions[i].pszLong, "--no-")) + RTStrPrintf(szHelp, sizeof(szHelp), "Disables the '%s' test.", g_aCmdOptions[i].pszLong + 5); + else + RTStrPrintf(szHelp, sizeof(szHelp), "Enables the '%s' test.", g_aCmdOptions[i].pszLong + 2); + pszHelp = szHelp; + } + else + pszHelp = "Option undocumented"; + break; + } + if ((unsigned)g_aCmdOptions[i].iShort < 127U) + { + char szOpt[64]; + RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort); + RTStrmPrintf(pStrm, " %-19s %s\n", szOpt, pszHelp); + } + else + RTStrmPrintf(pStrm, " %-19s %s\n", g_aCmdOptions[i].pszLong, pszHelp); + } +} + + +int main(int argc, char *argv[]) +{ + /* + * Init IPRT and globals. + */ + int rc = RTTestInitAndCreate("IoPerf", &g_hTest); + if (rc) + return rc; + + /* + * Default values. + */ + char szDefaultDir[32]; + const char *pszDir = szDefaultDir; + RTStrPrintf(szDefaultDir, sizeof(szDefaultDir), "ioperfdir-%u" RTPATH_SLASH_STR, RTProcSelf()); + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */); + while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (rc) + { + case 'd': + pszDir = ValueUnion.psz; + break; + + case 'r': + g_fRelativeDir = true; + break; + + case 'i': + g_pszIoEngine = ValueUnion.psz; + break; + + case 's': + g_cbTestSet = ValueUnion.u64; + break; + + case 'b': + g_cbIoBlock = ValueUnion.u32; + break; + + case 'm': + g_cReqsMax = ValueUnion.u32; + break; + + case 'y': + g_fVerifyReads = ValueUnion.f; + break; + + case 'c': + g_fNoCache = !ValueUnion.f; + break; + + case kCmdOpt_FirstWrite: + g_aenmTests[IOPERFTEST_FIRST_WRITE] = IOPERFTEST_FIRST_WRITE; + break; + case kCmdOpt_NoFirstWrite: + g_aenmTests[IOPERFTEST_FIRST_WRITE] = IOPERFTEST_DISABLED; + break; + case kCmdOpt_SeqRead: + g_aenmTests[IOPERFTEST_SEQ_READ] = IOPERFTEST_SEQ_READ; + break; + case kCmdOpt_NoSeqRead: + g_aenmTests[IOPERFTEST_SEQ_READ] = IOPERFTEST_DISABLED; + break; + case kCmdOpt_SeqWrite: + g_aenmTests[IOPERFTEST_SEQ_WRITE] = IOPERFTEST_SEQ_WRITE; + break; + case kCmdOpt_NoSeqWrite: + g_aenmTests[IOPERFTEST_SEQ_WRITE] = IOPERFTEST_DISABLED; + break; + case kCmdOpt_RndRead: + g_aenmTests[IOPERFTEST_RND_READ] = IOPERFTEST_RND_READ; + break; + case kCmdOpt_NoRndRead: + g_aenmTests[IOPERFTEST_RND_READ] = IOPERFTEST_DISABLED; + break; + case kCmdOpt_RndWrite: + g_aenmTests[IOPERFTEST_RND_WRITE] = IOPERFTEST_RND_WRITE; + break; + case kCmdOpt_NoRndWrite: + g_aenmTests[IOPERFTEST_RND_WRITE] = IOPERFTEST_DISABLED; + break; + case kCmdOpt_RevRead: + g_aenmTests[IOPERFTEST_REV_READ] = IOPERFTEST_REV_READ; + break; + case kCmdOpt_NoRevRead: + g_aenmTests[IOPERFTEST_REV_READ] = IOPERFTEST_DISABLED; + break; + case kCmdOpt_RevWrite: + g_aenmTests[IOPERFTEST_REV_WRITE] = IOPERFTEST_REV_WRITE; + break; + case kCmdOpt_NoRevWrite: + g_aenmTests[IOPERFTEST_REV_WRITE] = IOPERFTEST_DISABLED; + break; + case kCmdOpt_SeqReadWrite: + g_aenmTests[IOPERFTEST_SEQ_READWRITE] = IOPERFTEST_SEQ_READWRITE; + break; + case kCmdOpt_NoSeqReadWrite: + g_aenmTests[IOPERFTEST_SEQ_READWRITE] = IOPERFTEST_DISABLED; + break; + case kCmdOpt_RndReadWrite: + g_aenmTests[IOPERFTEST_RND_READWRITE] = IOPERFTEST_RND_READWRITE; + break; + case kCmdOpt_NoRndReadWrite: + g_aenmTests[IOPERFTEST_RND_READWRITE] = IOPERFTEST_DISABLED; + break; + + case 'q': + g_uVerbosity = 0; + break; + + case 'v': + g_uVerbosity++; + break; + + case 'h': + Usage(g_pStdOut); + return RTEXITCODE_SUCCESS; + + case 'V': + { + char szRev[] = "$Revision: 153224 $"; + szRev[RT_ELEMENTS(szRev) - 2] = '\0'; + RTPrintf(RTStrStrip(strchr(szRev, ':') + 1)); + return RTEXITCODE_SUCCESS; + } + + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + /* + * Populate g_szDir. + */ + if (!g_fRelativeDir) + rc = RTPathAbs(pszDir, g_szDir, sizeof(g_szDir)); + else + rc = RTStrCopy(g_szDir, sizeof(g_szDir), pszDir); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "%s(%s) failed: %Rrc\n", g_fRelativeDir ? "RTStrCopy" : "RTAbsPath", pszDir, rc); + return RTTestSummaryAndDestroy(g_hTest); + } + RTPathEnsureTrailingSeparator(g_szDir, sizeof(g_szDir)); + g_cchDir = strlen(g_szDir); + + /* + * Create the test directory with an 'empty' subdirectory under it, + * execute the tests, and remove directory when done. + */ + RTTestBanner(g_hTest); + if (!RTPathExists(g_szDir)) + { + /* The base dir: */ + rc = RTDirCreate(g_szDir, 0755, + RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL); + if (RT_SUCCESS(rc)) + { + RTTestIPrintf(RTTESTLVL_ALWAYS, "Test dir: %s\n", g_szDir); + + if (g_cJobs == 1) + rc = ioPerfDoTestSingle(); + else + rc = ioPerfDoTestMulti(); + + g_szDir[g_cchDir] = '\0'; + rc = RTDirRemoveRecursive(g_szDir, RTDIRRMREC_F_CONTENT_AND_DIR | (g_fRelativeDir ? RTDIRRMREC_F_NO_ABS_PATH : 0)); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "RTDirRemoveRecursive(%s,) -> %Rrc\n", g_szDir, rc); + } + else + RTTestFailed(g_hTest, "RTDirCreate(%s) -> %Rrc\n", g_szDir, rc); + } + else + RTTestFailed(g_hTest, "Test directory already exists: %s\n", g_szDir); + + return RTTestSummaryAndDestroy(g_hTest); +} + diff --git a/src/VBox/ValidationKit/utils/storage/Makefile.kmk b/src/VBox/ValidationKit/utils/storage/Makefile.kmk new file mode 100644 index 00000000..e17ab163 --- /dev/null +++ b/src/VBox/ValidationKit/utils/storage/Makefile.kmk @@ -0,0 +1,49 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Storage I/O performance tests. +# + +# +# Copyright (C) 2019-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Storage I/O Performance. +# +PROGRAMS += IoPerf +IoPerf_TEMPLATE = VBoxValidationKitR3 +IoPerf_SOURCES = IoPerf.cpp + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/usb/Makefile.kmk b/src/VBox/ValidationKit/utils/usb/Makefile.kmk new file mode 100644 index 00000000..4c6bd005 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/Makefile.kmk @@ -0,0 +1,74 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - USB test helpers. +# + +# +# Copyright (C) 2014-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# USB Linux test frontend. +# +ifeq ($(KBUILD_TARGET),linux) + PROGRAMS += UsbTest + UsbTest_TEMPLATE = VBoxValidationKitR3 + UsbTest_SOURCES = UsbTest.cpp +endif + +PROGRAMS += UsbTestService +UsbTestService_TEMPLATE = VBoxValidationKitR3 +ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING + UsbTestService_DEFS = \ + KBUILD_TARGET="$(KBUILD_TARGET)" \ + KBUILD_TARGET_ARCH="$(KBUILD_TARGET_ARCH)" +else + UsbTestService_DEFS = \ + KBUILD_TARGET=\"$(KBUILD_TARGET)\" \ + KBUILD_TARGET_ARCH=\"$(KBUILD_TARGET_ARCH)\" +endif +UsbTestService_SOURCES = \ + UsbTestService.cpp \ + UsbTestServiceGadgetCfg.cpp \ + UsbTestServiceGadgetClassTest.cpp \ + UsbTestServiceGadgetHost.cpp \ + UsbTestServiceGadgetHostUsbIp.cpp \ + UsbTestServiceGadget.cpp \ + UsbTestServiceProtocol.cpp \ + UsbTestServiceTcp.cpp +UsbTestService_SOURCES.linux = \ + UsbTestServicePlatform-linux.cpp + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTest.cpp b/src/VBox/ValidationKit/utils/usb/UsbTest.cpp new file mode 100644 index 00000000..b43e0756 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTest.cpp @@ -0,0 +1,668 @@ +/* $Id: UsbTest.cpp $ */ +/** @file + * UsbTest - User frontend for the Linux usbtest USB test and benchmarking module. + * Integrates with our test framework for nice outputs. + */ + +/* + * Copyright (C) 2014-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/dir.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/path.h> +#include <iprt/param.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/test.h> + +#include <iprt/linux/sysfs.h> + +#include <unistd.h> +#include <errno.h> +#include <limits.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <sys/ioctl.h> +#include <linux/usbdevice_fs.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * USB test request data. + * There is no public header with this information so we define it ourself here. + */ +typedef struct USBTESTPARMS +{ + /** Specifies the test to run. */ + uint32_t idxTest; + /** How many iterations the test should be executed. */ + uint32_t cIterations; + /** Size of the data packets. */ + uint32_t cbData; + /** Size of */ + uint32_t cbVariation; + /** Length of the S/G list for the test. */ + uint32_t cSgLength; + /** Returned time data after completing the test. */ + struct timeval TimeTest; +} USBTESTPARAMS; +/** Pointer to a test parameter structure. */ +typedef USBTESTPARAMS *PUSBTESTPARAMS; + +/** + * USB device descriptor. Used to search for the test device based + * on the vendor and product id. + */ +#pragma pack(1) +typedef struct USBDEVDESC +{ + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; +} USBDEVDESC; +#pragma pack() + +#define USBTEST_REQUEST _IOWR('U', 100, USBTESTPARMS) + +/** + * Callback to set up the test parameters for a specific test. + * + * @returns IPRT status code. + * @retval VINF_SUCCESS if setting the parameters up succeeded. Any other error code + * otherwise indicating the kind of error. + * @param idxTest The test index. + * @param pszTest Test name. + * @param pParams The USB test parameters to set up. + */ +typedef DECLCALLBACKTYPE(int, FNUSBTESTPARAMSSETUP,(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams)); +/** Pointer to a USB test parameters setup callback. */ +typedef FNUSBTESTPARAMSSETUP *PFNUSBTESTPARAMSSETUP; + +/** + * USB test descriptor. + */ +typedef struct USBTESTDESC +{ + /** (Sort of) Descriptive test name. */ + const char *pszName; + /** Flag whether the test is excluded. */ + bool fExcluded; + /** The parameter setup callback. */ + PFNUSBTESTPARAMSSETUP pfnParamsSetup; +} USBTESTDESC; +/** Pointer a USB test descriptor. */ +typedef USBTESTDESC *PUSBTESTDESC; + +/** + * USB speed values. + */ +typedef enum USBTESTSPEED +{ + USBTESTSPEED_ANY = 0, + USBTESTSPEED_UNKNOWN, + USBTESTSPEED_LOW, + USBTESTSPEED_FULL, + USBTESTSPEED_HIGH, + USBTESTSPEED_SUPER +} USBTESTSPEED; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** Some forward method declarations. */ +static DECLCALLBACK(int) usbTestParamsSetupReadWrite(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams); +static DECLCALLBACK(int) usbTestParamsSetupControlWrites(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams); + +/** Command line parameters */ +static const RTGETOPTDEF g_aCmdOptions[] = +{ + {"--device", 'd', RTGETOPT_REQ_STRING }, + {"--help", 'h', RTGETOPT_REQ_NOTHING}, + {"--exclude", 'e', RTGETOPT_REQ_UINT32}, + {"--exclude-all", 'a', RTGETOPT_REQ_NOTHING}, + {"--include", 'i', RTGETOPT_REQ_UINT32}, + {"--expected-speed", 's', RTGETOPT_REQ_STRING } +}; + +static USBTESTDESC g_aTests[] = +{ + /* pszTest fExcluded pfnParamsSetup */ + {"NOP", false, usbTestParamsSetupReadWrite}, + {"Non-queued Bulk write", false, usbTestParamsSetupReadWrite}, + {"Non-queued Bulk read", false, usbTestParamsSetupReadWrite}, + {"Non-queued Bulk write variabe size", false, usbTestParamsSetupReadWrite}, + {"Non-queued Bulk read variabe size", false, usbTestParamsSetupReadWrite}, + {"Queued Bulk write", false, usbTestParamsSetupReadWrite}, + {"Queued Bulk read", false, usbTestParamsSetupReadWrite}, + {"Queued Bulk write variabe size", false, usbTestParamsSetupReadWrite}, + {"Queued Bulk read variabe size", false, usbTestParamsSetupReadWrite}, + {"Chapter 9 Control Test", false, usbTestParamsSetupReadWrite}, + {"Queued control messaging", false, usbTestParamsSetupReadWrite}, + {"Unlink reads", false, usbTestParamsSetupReadWrite}, + {"Unlink writes", false, usbTestParamsSetupReadWrite}, + {"Set/Clear halts", false, usbTestParamsSetupReadWrite}, + {"Control writes", false, usbTestParamsSetupControlWrites}, + {"Isochronous write", false, usbTestParamsSetupReadWrite}, + {"Isochronous read", false, usbTestParamsSetupReadWrite}, + {"Bulk write unaligned (DMA)", false, usbTestParamsSetupReadWrite}, + {"Bulk read unaligned (DMA)", false, usbTestParamsSetupReadWrite}, + {"Bulk write unaligned (no DMA)", false, usbTestParamsSetupReadWrite}, + {"Bulk read unaligned (no DMA)", false, usbTestParamsSetupReadWrite}, + {"Control writes unaligned", false, usbTestParamsSetupControlWrites}, + {"Isochronous write unaligned", false, usbTestParamsSetupReadWrite}, + {"Isochronous read unaligned", false, usbTestParamsSetupReadWrite}, + {"Unlink queued Bulk", false, usbTestParamsSetupReadWrite} +}; + +/** The test handle. */ +static RTTEST g_hTest; +/** The expected device speed. */ +static USBTESTSPEED g_enmSpeed = USBTESTSPEED_ANY; + +/** + * Setup callback for basic read/write (bulk, isochronous) tests. + * + * @copydoc FNUSBTESTPARAMSSETUP + */ +static DECLCALLBACK(int) usbTestParamsSetupReadWrite(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams) +{ + NOREF(idxTest); + NOREF(pszTest); + + pParams->cIterations = 1000; + pParams->cbData = 512; + pParams->cbVariation = 512; + pParams->cSgLength = 32; + + return VINF_SUCCESS; +} + +/** + * Setup callback for the control writes test. + * + * @copydoc FNUSBTESTPARAMSSETUP + */ +static DECLCALLBACK(int) usbTestParamsSetupControlWrites(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams) +{ + NOREF(idxTest); + NOREF(pszTest); + + pParams->cIterations = 1000; + pParams->cbData = 512; + /* + * Must be smaller than cbData or the parameter check in the usbtest module fails, + * no idea yet why it must be this. + */ + pParams->cbVariation = 256; + pParams->cSgLength = 32; + + return VINF_SUCCESS; +} + +/** + * Shows tool usage text. + */ +static void usbTestUsage(PRTSTREAM pStrm) +{ + char szExec[RTPATH_MAX]; + RTStrmPrintf(pStrm, "usage: %s [options]\n", + RTPathFilename(RTProcGetExecutablePath(szExec, sizeof(szExec)))); + RTStrmPrintf(pStrm, "\n"); + RTStrmPrintf(pStrm, "options: \n"); + + + for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++) + { + const char *pszHelp; + switch (g_aCmdOptions[i].iShort) + { + case 'h': + pszHelp = "Displays this help and exit"; + break; + case 'd': + pszHelp = "Use the specified test device"; + break; + case 'e': + pszHelp = "Exclude the given test id from the list"; + break; + case 'a': + pszHelp = "Exclude all tests from the list (useful to enable single tests later with --include)"; + break; + case 'i': + pszHelp = "Include the given test id in the list"; + break; + case 's': + pszHelp = "The device speed to expect"; + break; + default: + pszHelp = "Option undocumented"; + break; + } + char szOpt[256]; + RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort); + RTStrmPrintf(pStrm, " %-30s%s\n", szOpt, pszHelp); + } +} + +/** + * Searches for a USB test device and returns the bus and device ID and the device speed. + */ +static int usbTestDeviceQueryBusAndDevId(uint16_t *pu16BusId, uint16_t *pu16DevId, USBTESTSPEED *penmSpeed) +{ + bool fFound = false; + +#define USBTEST_USB_DEV_SYSFS "/sys/bus/usb/devices/" + + RTDIR hDirUsb = NULL; + int rc = RTDirOpen(&hDirUsb, USBTEST_USB_DEV_SYSFS); + if (RT_SUCCESS(rc)) + { + do + { + RTDIRENTRY DirUsbBus; + rc = RTDirRead(hDirUsb, &DirUsbBus, NULL); + if ( RT_SUCCESS(rc) + && RTStrNCmp(DirUsbBus.szName, "usb", 3) + && RTLinuxSysFsExists(USBTEST_USB_DEV_SYSFS "%s/idVendor", DirUsbBus.szName)) + { + int64_t idVendor = 0; + int64_t idProduct = 0; + int64_t iBusId = 0; + int64_t iDevId = 0; + char aszSpeed[20]; + + rc = RTLinuxSysFsReadIntFile(16, &idVendor, USBTEST_USB_DEV_SYSFS "%s/idVendor", DirUsbBus.szName); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsReadIntFile(16, &idProduct, USBTEST_USB_DEV_SYSFS "%s/idProduct", DirUsbBus.szName); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsReadIntFile(16, &iBusId, USBTEST_USB_DEV_SYSFS "%s/busnum", DirUsbBus.szName); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsReadIntFile(16, &iDevId, USBTEST_USB_DEV_SYSFS "%s/devnum", DirUsbBus.szName); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsReadStrFile(&aszSpeed[0], sizeof(aszSpeed), NULL, USBTEST_USB_DEV_SYSFS "%s/speed", DirUsbBus.szName); + + if ( RT_SUCCESS(rc) + && idVendor == 0x0525 + && idProduct == 0xa4a0) + { + if (penmSpeed) + { + /* Parse the speed. */ + if (!RTStrCmp(&aszSpeed[0], "1.5")) + *penmSpeed = USBTESTSPEED_LOW; + else if (!RTStrCmp(&aszSpeed[0], "12")) + *penmSpeed = USBTESTSPEED_FULL; + else if (!RTStrCmp(&aszSpeed[0], "480")) + *penmSpeed = USBTESTSPEED_HIGH; + else if ( !RTStrCmp(&aszSpeed[0], "5000") + || !RTStrCmp(&aszSpeed[0], "10000")) + *penmSpeed = USBTESTSPEED_SUPER; + else + *penmSpeed = USBTESTSPEED_UNKNOWN; + } + + if (pu16BusId) + *pu16BusId = (uint16_t)iBusId; + if (pu16DevId) + *pu16DevId = (uint16_t)iDevId; + fFound = true; + break; + } + } + else if (rc != VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + + } while ( RT_SUCCESS(rc) + && !fFound); + + if (rc == VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + + RTDirClose(hDirUsb); + } + + if (RT_SUCCESS(rc) && !fFound) + rc = VERR_NOT_FOUND; + + return rc; +} + +/** + * Search for a USB test device and return the device path. + * + * @returns Path to the USB test device or NULL if none was found. + */ +static char *usbTestFindDevice(void) +{ + /* + * Very crude and quick way to search for the correct test device. + * Assumption is that the path looks like /dev/bus/usb/%3d/%3d. + */ + char *pszDevPath = NULL; + + RTDIR hDirUsb = NULL; + int rc = RTDirOpen(&hDirUsb, "/dev/bus/usb"); + if (RT_SUCCESS(rc)) + { + do + { + RTDIRENTRY DirUsbBus; + rc = RTDirRead(hDirUsb, &DirUsbBus, NULL); + if (RT_SUCCESS(rc)) + { + char aszPath[RTPATH_MAX + 1]; + RTStrPrintf(&aszPath[0], RT_ELEMENTS(aszPath), "/dev/bus/usb/%s", DirUsbBus.szName); + + RTDIR hDirUsbBus = NULL; + rc = RTDirOpen(&hDirUsbBus, &aszPath[0]); + if (RT_SUCCESS(rc)) + { + do + { + RTDIRENTRY DirUsbDev; + rc = RTDirRead(hDirUsbBus, &DirUsbDev, NULL); + if (RT_SUCCESS(rc)) + { + char aszPathDev[RTPATH_MAX + 1]; + RTStrPrintf(&aszPathDev[0], RT_ELEMENTS(aszPathDev), "/dev/bus/usb/%s/%s", + DirUsbBus.szName, DirUsbDev.szName); + + RTFILE hFileDev; + rc = RTFileOpen(&hFileDev, aszPathDev, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + USBDEVDESC DevDesc; + + rc = RTFileRead(hFileDev, &DevDesc, sizeof(DevDesc), NULL); + RTFileClose(hFileDev); + + if ( RT_SUCCESS(rc) + && DevDesc.idVendor == 0x0525 + && DevDesc.idProduct == 0xa4a0) + pszDevPath = RTStrDup(aszPathDev); + } + + rc = VINF_SUCCESS; + } + else if (rc != VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + + } while ( RT_SUCCESS(rc) + && !pszDevPath); + + rc = VINF_SUCCESS; + RTDirClose(hDirUsbBus); + } + } + else if (rc != VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + } while ( RT_SUCCESS(rc) + && !pszDevPath); + + RTDirClose(hDirUsb); + } + + return pszDevPath; +} + +static int usbTestIoctl(int iDevFd, int iInterface, PUSBTESTPARAMS pParams) +{ + struct usbdevfs_ioctl IoCtlData; + + IoCtlData.ifno = iInterface; + IoCtlData.ioctl_code = (int)USBTEST_REQUEST; + IoCtlData.data = pParams; + return ioctl(iDevFd, USBDEVFS_IOCTL, &IoCtlData); +} + +/** + * Test execution worker. + * + * @returns nothing. + * @param pszDevice The device to use for testing. + */ +static void usbTestExec(const char *pszDevice) +{ + int iDevFd; + + RTTestSub(g_hTest, "Opening device"); + iDevFd = open(pszDevice, O_RDWR); + if (iDevFd != -1) + { + USBTESTPARAMS Params; + + RTTestPassed(g_hTest, "Opening device successful\n"); + + for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++) + { + RTTestSub(g_hTest, g_aTests[i].pszName); + + if (g_aTests[i].fExcluded) + { + RTTestSkipped(g_hTest, "Excluded from list"); + continue; + } + + int rc = g_aTests[i].pfnParamsSetup(i, g_aTests[i].pszName, &Params); + if (RT_SUCCESS(rc)) + { + Params.idxTest = i; + + /* Assume the test interface has the number 0 for now. */ + int rcPosix = usbTestIoctl(iDevFd, 0, &Params); + if (rcPosix < 0 && errno == EOPNOTSUPP) + { + RTTestSkipped(g_hTest, "Not supported"); + continue; + } + + if (rcPosix < 0) + { + /* + * The error status code of the unlink testcase is + * offset by 2000 for the sync and 1000 for the sync code path + * (see drivers/usb/misc/usbtest.c in the Linux kernel sources). + * + * Adjust to the actual status code so converting doesn't assert. + */ + int iTmpErrno = errno; + if (iTmpErrno >= 2000) + iTmpErrno -= 2000; + else if (iTmpErrno >= 1000) + iTmpErrno -= 1000; + RTTestFailed(g_hTest, "Test failed with %Rrc\n", RTErrConvertFromErrno(iTmpErrno)); + } + else + { + uint64_t u64Ns = Params.TimeTest.tv_sec * RT_NS_1SEC + Params.TimeTest.tv_usec * RT_NS_1US; + RTTestValue(g_hTest, "Runtime", u64Ns, RTTESTUNIT_NS); + } + } + else + RTTestFailed(g_hTest, "Setting up test parameters failed with %Rrc\n", rc); + RTTestSubDone(g_hTest); + } + + close(iDevFd); + } + else + RTTestFailed(g_hTest, "Opening device failed with %Rrc\n", RTErrConvertFromErrno(errno)); + +} + +int main(int argc, char *argv[]) +{ + /* + * Init IPRT and globals. + */ + int rc = RTTestInitAndCreate("UsbTest", &g_hTest); + if (rc) + return rc; + + /* + * Default values. + */ + const char *pszDevice = NULL; + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */); + while ((rc = RTGetOpt(&GetState, &ValueUnion))) + { + switch (rc) + { + case 'h': + usbTestUsage(g_pStdOut); + return RTEXITCODE_SUCCESS; + case 'd': + pszDevice = ValueUnion.psz; + break; + case 's': + if (!RTStrICmp(ValueUnion.psz, "Low")) + g_enmSpeed = USBTESTSPEED_LOW; + else if (!RTStrICmp(ValueUnion.psz, "Full")) + g_enmSpeed = USBTESTSPEED_FULL; + else if (!RTStrICmp(ValueUnion.psz, "High")) + g_enmSpeed = USBTESTSPEED_HIGH; + else if (!RTStrICmp(ValueUnion.psz, "Super")) + g_enmSpeed = USBTESTSPEED_SUPER; + else + { + RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, "Invalid speed passed to --expected-speed\n"); + RTTestErrorInc(g_hTest); + return RTGetOptPrintError(VERR_INVALID_PARAMETER, &ValueUnion); + } + break; + case 'e': + if (ValueUnion.u32 < RT_ELEMENTS(g_aTests)) + g_aTests[ValueUnion.u32].fExcluded = true; + else + { + RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, "Invalid test number passed to --exclude\n"); + RTTestErrorInc(g_hTest); + return RTGetOptPrintError(VERR_INVALID_PARAMETER, &ValueUnion); + } + break; + case 'a': + for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++) + g_aTests[i].fExcluded = true; + break; + case 'i': + if (ValueUnion.u32 < RT_ELEMENTS(g_aTests)) + g_aTests[ValueUnion.u32].fExcluded = false; + else + { + RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, "Invalid test number passed to --include\n"); + RTTestErrorInc(g_hTest); + return RTGetOptPrintError(VERR_INVALID_PARAMETER, &ValueUnion); + } + break; + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + /* + * Start testing. + */ + RTTestBanner(g_hTest); + + /* Find the first test device if none was given. */ + if (!pszDevice) + { + RTTestSub(g_hTest, "Detecting device"); + pszDevice = usbTestFindDevice(); + if (!pszDevice) + RTTestFailed(g_hTest, "Failed to find suitable device\n"); + + RTTestSubDone(g_hTest); + } + + if (pszDevice) + { + /* First check that the requested speed matches. */ + if (g_enmSpeed != USBTESTSPEED_ANY) + { + RTTestSub(g_hTest, "Checking correct device speed"); + + USBTESTSPEED enmSpeed = USBTESTSPEED_UNKNOWN; + rc = usbTestDeviceQueryBusAndDevId(NULL, NULL, &enmSpeed); + if (RT_SUCCESS(rc)) + { + if (enmSpeed == g_enmSpeed) + RTTestPassed(g_hTest, "Reported device speed matches requested speed\n"); + else + RTTestFailed(g_hTest, "Reported device speed doesn'match requested speed (%u vs %u)\n", + enmSpeed, g_enmSpeed); + } + else + RTTestFailed(g_hTest, "Failed to query device speed with rc=%Rrc\n", rc); + + RTTestSubDone(g_hTest); + } + usbTestExec(pszDevice); + } + + RTEXITCODE rcExit = RTTestSummaryAndDestroy(g_hTest); + return rcExit; +} + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp new file mode 100644 index 00000000..0f559ec6 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp @@ -0,0 +1,1659 @@ +/* $Id: UsbTestService.cpp $ */ +/** @file + * UsbTestService - Remote USB test configuration and execution server. + */ + +/* + * Copyright (C) 2010-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/alloca.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/crc.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/getopt.h> +#include <iprt/handle.h> +#include <iprt/initterm.h> +#include <iprt/json.h> +#include <iprt/list.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/pipe.h> +#include <iprt/poll.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/thread.h> + +#include "UsbTestServiceInternal.h" +#include "UsbTestServiceGadget.h" +#include "UsbTestServicePlatform.h" + + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +#define UTS_USBIP_PORT_FIRST 3240 +#define UTS_USBIP_PORT_LAST 3340 + +/** + * UTS client state. + */ +typedef enum UTSCLIENTSTATE +{ + /** Invalid client state. */ + UTSCLIENTSTATE_INVALID = 0, + /** Client is initialising, only the HOWDY and BYE packets are allowed. */ + UTSCLIENTSTATE_INITIALISING, + /** Client is in fully cuntional state and ready to process all requests. */ + UTSCLIENTSTATE_READY, + /** Client is destroying. */ + UTSCLIENTSTATE_DESTROYING, + /** 32bit hack. */ + UTSCLIENTSTATE_32BIT_HACK = 0x7fffffff +} UTSCLIENTSTATE; + +/** + * UTS client instance. + */ +typedef struct UTSCLIENT +{ + /** List node for new clients. */ + RTLISTNODE NdLst; + /** The current client state. */ + UTSCLIENTSTATE enmState; + /** Transport backend specific data. */ + PUTSTRANSPORTCLIENT pTransportClient; + /** Client hostname. */ + char *pszHostname; + /** Gadget host handle. */ + UTSGADGETHOST hGadgetHost; + /** Handle fo the current configured gadget. */ + UTSGADGET hGadget; +} UTSCLIENT; +/** Pointer to a UTS client instance. */ +typedef UTSCLIENT *PUTSCLIENT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Transport layers. + */ +static const PCUTSTRANSPORT g_apTransports[] = +{ + &g_TcpTransport, + //&g_SerialTransport, + //&g_FileSysTransport, + //&g_GuestPropTransport, + //&g_TestDevTransport, +}; + +/** The select transport layer. */ +static PCUTSTRANSPORT g_pTransport; +/** The config path. */ +static char g_szCfgPath[RTPATH_MAX]; +/** The scratch path. */ +static char g_szScratchPath[RTPATH_MAX]; +/** The default scratch path. */ +static char g_szDefScratchPath[RTPATH_MAX]; +/** The CD/DVD-ROM path. */ +static char g_szCdRomPath[RTPATH_MAX]; +/** The default CD/DVD-ROM path. */ +static char g_szDefCdRomPath[RTPATH_MAX]; +/** The operating system short name. */ +static char g_szOsShortName[16]; +/** The CPU architecture short name. */ +static char g_szArchShortName[16]; +/** The combined "OS.arch" name. */ +static char g_szOsDotArchShortName[32]; +/** The combined "OS/arch" name. */ +static char g_szOsSlashArchShortName[32]; +/** The executable suffix. */ +static char g_szExeSuff[8]; +/** The shell script suffix. */ +static char g_szScriptSuff[8]; +/** Whether to display the output of the child process or not. */ +static bool g_fDisplayOutput = true; +/** Whether to terminate or not. + * @todo implement signals and stuff. */ +static bool volatile g_fTerminate = false; +/** Configuration AST. */ +static RTJSONVAL g_hCfgJson = NIL_RTJSONVAL; +/** Pipe for communicating with the serving thread about new clients. - read end */ +static RTPIPE g_hPipeR; +/** Pipe for communicating with the serving thread about new clients. - write end */ +static RTPIPE g_hPipeW; +/** Thread serving connected clients. */ +static RTTHREAD g_hThreadServing; +/** Critical section protecting the list of new clients. */ +static RTCRITSECT g_CritSectClients; +/** List of new clients waiting to be picked up by the client worker thread. */ +static RTLISTANCHOR g_LstClientsNew; +/** First USB/IP port we can use. */ +static uint16_t g_uUsbIpPortFirst = UTS_USBIP_PORT_FIRST; +/** Last USB/IP port we can use. */ +static uint16_t g_uUsbIpPortLast = UTS_USBIP_PORT_LAST; +/** Next free port. */ +static uint16_t g_uUsbIpPortNext = UTS_USBIP_PORT_FIRST; + + + +/** + * Returns the string represenation of the given state. + */ +static const char *utsClientStateStringify(UTSCLIENTSTATE enmState) +{ + switch (enmState) + { + case UTSCLIENTSTATE_INVALID: + return "INVALID"; + case UTSCLIENTSTATE_INITIALISING: + return "INITIALISING"; + case UTSCLIENTSTATE_READY: + return "READY"; + case UTSCLIENTSTATE_DESTROYING: + return "DESTROYING"; + case UTSCLIENTSTATE_32BIT_HACK: + default: + break; + } + + AssertMsgFailed(("Unknown state %#x\n", enmState)); + return "UNKNOWN"; +} + +/** + * Calculates the checksum value, zero any padding space and send the packet. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPkt The packet to send. Must point to a correctly + * aligned buffer. + */ +static int utsSendPkt(PUTSCLIENT pClient, PUTSPKTHDR pPkt) +{ + Assert(pPkt->cb >= sizeof(*pPkt)); + pPkt->uCrc32 = RTCrc32(pPkt->achOpcode, pPkt->cb - RT_UOFFSETOF(UTSPKTHDR, achOpcode)); + if (pPkt->cb != RT_ALIGN_32(pPkt->cb, UTSPKT_ALIGNMENT)) + memset((uint8_t *)pPkt + pPkt->cb, '\0', RT_ALIGN_32(pPkt->cb, UTSPKT_ALIGNMENT) - pPkt->cb); + + Log(("utsSendPkt: cb=%#x opcode=%.8s\n", pPkt->cb, pPkt->achOpcode)); + Log2(("%.*Rhxd\n", RT_MIN(pPkt->cb, 256), pPkt)); + int rc = g_pTransport->pfnSendPkt(pClient->pTransportClient, pPkt); + while (RT_UNLIKELY(rc == VERR_INTERRUPTED) && !g_fTerminate) + rc = g_pTransport->pfnSendPkt(pClient->pTransportClient, pPkt); + if (RT_FAILURE(rc)) + Log(("utsSendPkt: rc=%Rrc\n", rc)); + + return rc; +} + +/** + * Sends a babble reply and disconnects the client (if applicable). + * + * @param pClient The UTS client structure. + * @param pszOpcode The BABBLE opcode. + */ +static void utsReplyBabble(PUTSCLIENT pClient, const char *pszOpcode) +{ + UTSPKTHDR Reply; + Reply.cb = sizeof(Reply); + Reply.uCrc32 = 0; + memcpy(Reply.achOpcode, pszOpcode, sizeof(Reply.achOpcode)); + + g_pTransport->pfnBabble(pClient->pTransportClient, &Reply, 20*1000); +} + +/** + * Receive and validate a packet. + * + * Will send bable responses to malformed packets that results in a error status + * code. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param ppPktHdr Where to return the packet on success. Free + * with RTMemFree. + * @param fAutoRetryOnFailure Whether to retry on error. + */ +static int utsRecvPkt(PUTSCLIENT pClient, PPUTSPKTHDR ppPktHdr, bool fAutoRetryOnFailure) +{ + for (;;) + { + PUTSPKTHDR pPktHdr; + int rc = g_pTransport->pfnRecvPkt(pClient->pTransportClient, &pPktHdr); + if (RT_SUCCESS(rc)) + { + /* validate the packet. */ + if ( pPktHdr->cb >= sizeof(UTSPKTHDR) + && pPktHdr->cb < UTSPKT_MAX_SIZE) + { + Log2(("utsRecvPkt: pPktHdr=%p cb=%#x crc32=%#x opcode=%.8s\n" + "%.*Rhxd\n", + pPktHdr, pPktHdr->cb, pPktHdr->uCrc32, pPktHdr->achOpcode, RT_MIN(pPktHdr->cb, 256), pPktHdr)); + uint32_t uCrc32Calc = pPktHdr->uCrc32 != 0 + ? RTCrc32(&pPktHdr->achOpcode[0], pPktHdr->cb - RT_UOFFSETOF(UTSPKTHDR, achOpcode)) + : 0; + if (pPktHdr->uCrc32 == uCrc32Calc) + { + AssertCompileMemberSize(UTSPKTHDR, achOpcode, 8); + if ( RT_C_IS_UPPER(pPktHdr->achOpcode[0]) + && RT_C_IS_UPPER(pPktHdr->achOpcode[1]) + && (RT_C_IS_UPPER(pPktHdr->achOpcode[2]) || pPktHdr->achOpcode[2] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[3]) || pPktHdr->achOpcode[3] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[4]) || pPktHdr->achOpcode[4] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[5]) || pPktHdr->achOpcode[5] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[6]) || pPktHdr->achOpcode[6] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[7]) || pPktHdr->achOpcode[7] == ' ') + ) + { + Log(("utsRecvPkt: cb=%#x opcode=%.8s\n", pPktHdr->cb, pPktHdr->achOpcode)); + *ppPktHdr = pPktHdr; + return rc; + } + + rc = VERR_IO_BAD_COMMAND; + } + else + { + Log(("utsRecvPkt: cb=%#x opcode=%.8s crc32=%#x actual=%#x\n", + pPktHdr->cb, pPktHdr->achOpcode, pPktHdr->uCrc32, uCrc32Calc)); + rc = VERR_IO_CRC; + } + } + else + rc = VERR_IO_BAD_LENGTH; + + /* Send babble reply and disconnect the client if the transport is + connection oriented. */ + if (rc == VERR_IO_BAD_LENGTH) + utsReplyBabble(pClient, "BABBLE L"); + else if (rc == VERR_IO_CRC) + utsReplyBabble(pClient, "BABBLE C"); + else if (rc == VERR_IO_BAD_COMMAND) + utsReplyBabble(pClient, "BABBLE O"); + else + utsReplyBabble(pClient, "BABBLE "); + RTMemFree(pPktHdr); + } + + /* Try again or return failure? */ + if ( g_fTerminate + || rc != VERR_INTERRUPTED + || !fAutoRetryOnFailure + ) + { + Log(("utsRecvPkt: rc=%Rrc\n", rc)); + return rc; + } + } +} + +/** + * Make a simple reply, only status opcode. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pReply The reply packet. + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + * @param cbExtra Bytes in addition to the header. + */ +static int utsReplyInternal(PUTSCLIENT pClient, PUTSPKTSTS pReply, const char *pszOpcode, size_t cbExtra) +{ + /* copy the opcode, don't be too strict in case of a padding screw up. */ + size_t cchOpcode = strlen(pszOpcode); + if (RT_LIKELY(cchOpcode == sizeof(pReply->Hdr.achOpcode))) + memcpy(pReply->Hdr.achOpcode, pszOpcode, sizeof(pReply->Hdr.achOpcode)); + else + { + Assert(cchOpcode == sizeof(pReply->Hdr.achOpcode)); + while (cchOpcode > 0 && pszOpcode[cchOpcode - 1] == ' ') + cchOpcode--; + AssertMsgReturn(cchOpcode < sizeof(pReply->Hdr.achOpcode), ("%d/'%.8s'\n", cchOpcode, pszOpcode), VERR_INTERNAL_ERROR_4); + memcpy(pReply->Hdr.achOpcode, pszOpcode, cchOpcode); + memset(&pReply->Hdr.achOpcode[cchOpcode], ' ', sizeof(pReply->Hdr.achOpcode) - cchOpcode); + } + + pReply->Hdr.cb = (uint32_t)sizeof(UTSPKTSTS) + (uint32_t)cbExtra; + pReply->Hdr.uCrc32 = 0; + + return utsSendPkt(pClient, &pReply->Hdr); +} + +/** + * Make a simple reply, only status opcode. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The original packet (for future use). + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + */ +static int utsReplySimple(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, const char *pszOpcode) +{ + UTSPKTSTS Pkt; + + RT_ZERO(Pkt); + Pkt.rcReq = VINF_SUCCESS; + Pkt.cchStsMsg = 0; + NOREF(pPktHdr); + return utsReplyInternal(pClient, &Pkt, pszOpcode, 0); +} + +/** + * Acknowledges a packet with success. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The original packet (for future use). + */ +static int utsReplyAck(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + return utsReplySimple(pClient, pPktHdr, "ACK "); +} + +/** + * Replies with a failure. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The original packet (for future use). + * @param rcReq Status code. + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + * @param rcReq The status code of the request. + * @param pszDetailFmt Longer description of the problem (format string). + * @param va Format arguments. + */ +static int utsReplyFailureV(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, const char *pszOpcode, int rcReq, const char *pszDetailFmt, va_list va) +{ + NOREF(pPktHdr); + union + { + UTSPKTSTS Hdr; + char ach[256]; + } uPkt; + + RT_ZERO(uPkt); + size_t cchDetail = RTStrPrintfV(&uPkt.ach[sizeof(UTSPKTSTS)], + sizeof(uPkt) - sizeof(UTSPKTSTS), + pszDetailFmt, va); + uPkt.Hdr.rcReq = rcReq; + uPkt.Hdr.cchStsMsg = cchDetail; + return utsReplyInternal(pClient, &uPkt.Hdr, pszOpcode, cchDetail + 1); +} + +/** + * Replies with a failure. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The original packet (for future use). + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + * @param rcReq Status code. + * @param pszDetailFmt Longer description of the problem (format string). + * @param ... Format arguments. + */ +static int utsReplyFailure(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, const char *pszOpcode, int rcReq, const char *pszDetailFmt, ...) +{ + va_list va; + va_start(va, pszDetailFmt); + int rc = utsReplyFailureV(pClient, pPktHdr, pszOpcode, rcReq, pszDetailFmt, va); + va_end(va); + return rc; +} + +/** + * Replies according to the return code. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet to reply to. + * @param rcOperation The status code to report. + * @param pszOperationFmt The operation that failed. Typically giving the + * function call with important arguments. + * @param ... Arguments to the format string. + */ +static int utsReplyRC(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, int rcOperation, const char *pszOperationFmt, ...) +{ + if (RT_SUCCESS(rcOperation)) + return utsReplyAck(pClient, pPktHdr); + + char szOperation[128]; + va_list va; + va_start(va, pszOperationFmt); + RTStrPrintfV(szOperation, sizeof(szOperation), pszOperationFmt, va); + va_end(va); + + return utsReplyFailure(pClient, pPktHdr, "FAILED ", rcOperation, "%s failed with rc=%Rrc (opcode '%.8s')", + szOperation, rcOperation, pPktHdr->achOpcode); +} + +#if 0 /* unused */ +/** + * Signal a bad packet minum size. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet to reply to. + * @param cbMin The minimum size. + */ +static int utsReplyBadMinSize(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, size_t cbMin) +{ + return utsReplyFailure(pClient, pPktHdr, "BAD SIZE", VERR_INVALID_PARAMETER, "Expected at least %zu bytes, got %u (opcode '%.8s')", + cbMin, pPktHdr->cb, pPktHdr->achOpcode); +} +#endif + +/** + * Signal a bad packet exact size. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet to reply to. + * @param cb The wanted size. + */ +static int utsReplyBadSize(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, size_t cb) +{ + return utsReplyFailure(pClient, pPktHdr, "BAD SIZE", VERR_INVALID_PARAMETER, "Expected at %zu bytes, got %u (opcode '%.8s')", + cb, pPktHdr->cb, pPktHdr->achOpcode); +} + +#if 0 /* unused */ +/** + * Deals with a command that isn't implemented yet. + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet which opcode isn't implemented. + */ +static int utsReplyNotImplemented(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + return utsReplyFailure(pClient, pPktHdr, "NOT IMPL", VERR_NOT_IMPLEMENTED, "Opcode '%.8s' is not implemented", pPktHdr->achOpcode); +} +#endif + +/** + * Deals with a unknown command. + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet to reply to. + */ +static int utsReplyUnknown(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + return utsReplyFailure(pClient, pPktHdr, "UNKNOWN ", VERR_NOT_FOUND, "Opcode '%.8s' is not known", pPktHdr->achOpcode); +} + +/** + * Deals with a command which contains an unterminated string. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet containing the unterminated string. + */ +static int utsReplyBadStrTermination(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + return utsReplyFailure(pClient, pPktHdr, "BAD TERM", VERR_INVALID_PARAMETER, "Opcode '%.8s' contains an unterminated string", pPktHdr->achOpcode); +} + +/** + * Deals with a command sent in an invalid client state. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet containing the unterminated string. + */ +static int utsReplyInvalidState(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + return utsReplyFailure(pClient, pPktHdr, "INVSTATE", VERR_INVALID_STATE, "Opcode '%.8s' is not supported at client state '%s", + pPktHdr->achOpcode, utsClientStateStringify(pClient->enmState)); +} + +/** + * Parses an unsigned integer from the given value string. + * + * @returns IPRT status code. + * @retval VERR_OUT_OF_RANGE if the parsed value exceeds the given maximum. + * @param pszVal The value string. + * @param uMax The maximum value. + * @param pu64 Where to store the parsed number on success. + */ +static int utsDoGadgetCreateCfgParseUInt(const char *pszVal, uint64_t uMax, uint64_t *pu64) +{ + int rc = RTStrToUInt64Ex(pszVal, NULL, 0, pu64); + if (RT_SUCCESS(rc)) + { + if (*pu64 > uMax) + rc = VERR_OUT_OF_RANGE; + } + + return rc; +} + +/** + * Parses a signed integer from the given value string. + * + * @returns IPRT status code. + * @retval VERR_OUT_OF_RANGE if the parsed value exceeds the given range. + * @param pszVal The value string. + * @param iMin The minimum value. + * @param iMax The maximum value. + * @param pi64 Where to store the parsed number on success. + */ +static int utsDoGadgetCreateCfgParseInt(const char *pszVal, int64_t iMin, int64_t iMax, int64_t *pi64) +{ + int rc = RTStrToInt64Ex(pszVal, NULL, 0, pi64); + if (RT_SUCCESS(rc)) + { + if ( *pi64 < iMin + || *pi64 > iMax) + rc = VERR_OUT_OF_RANGE; + } + + return rc; +} + +/** + * Parses the given config item and fills in the value according to the given type. + * + * @returns IPRT status code. + * @param pCfgItem The config item to parse. + * @param u32Type The config type. + * @param pszVal The value encoded as a string. + */ +static int utsDoGadgetCreateCfgParseItem(PUTSGADGETCFGITEM pCfgItem, uint32_t u32Type, + const char *pszVal) +{ + int rc = VINF_SUCCESS; + + switch (u32Type) + { + case UTSPKT_GDGT_CFG_ITEM_TYPE_BOOLEAN: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_BOOLEAN; + if ( RTStrICmp(pszVal, "enabled") + || RTStrICmp(pszVal, "1") + || RTStrICmp(pszVal, "true")) + pCfgItem->Val.u.f = true; + else if ( RTStrICmp(pszVal, "disabled") + || RTStrICmp(pszVal, "0") + || RTStrICmp(pszVal, "false")) + pCfgItem->Val.u.f = false; + else + rc = VERR_INVALID_PARAMETER; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_STRING: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_STRING; + pCfgItem->Val.u.psz = RTStrDup(pszVal); + if (!pCfgItem->Val.u.psz) + rc = VERR_NO_STR_MEMORY; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT8: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT8; + + uint64_t u64; + rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT8_MAX, &u64); + if (RT_SUCCESS(rc)) + pCfgItem->Val.u.u8 = (uint8_t)u64; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT16: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT16; + + uint64_t u64; + rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT16_MAX, &u64); + if (RT_SUCCESS(rc)) + pCfgItem->Val.u.u16 = (uint16_t)u64; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT32: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT32; + + uint64_t u64; + rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT32_MAX, &u64); + if (RT_SUCCESS(rc)) + pCfgItem->Val.u.u32 = (uint32_t)u64; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT64: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT64; + rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT64_MAX, &pCfgItem->Val.u.u64); + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_INT8: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT8; + + int64_t i64; + rc = utsDoGadgetCreateCfgParseInt(pszVal, INT8_MIN, INT8_MAX, &i64); + if (RT_SUCCESS(rc)) + pCfgItem->Val.u.i8 = (int8_t)i64; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_INT16: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT16; + + int64_t i64; + rc = utsDoGadgetCreateCfgParseInt(pszVal, INT16_MIN, INT16_MAX, &i64); + if (RT_SUCCESS(rc)) + pCfgItem->Val.u.i16 = (int16_t)i64; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_INT32: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT32; + + int64_t i64; + rc = utsDoGadgetCreateCfgParseInt(pszVal, INT32_MIN, INT32_MAX, &i64); + if (RT_SUCCESS(rc)) + pCfgItem->Val.u.i32 = (int32_t)i64; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_INT64: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT64; + rc = utsDoGadgetCreateCfgParseInt(pszVal, INT64_MIN, INT64_MAX, &pCfgItem->Val.u.i64); + break; + } + default: + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + +/** + * Creates the configuration from the given GADGET CREATE packet. + * + * @returns IPRT status code. + * @param pCfgItem The first config item header in the request packet. + * @param cCfgItems Number of config items in the packet to parse. + * @param cbPkt Number of bytes left in the packet for the config data. + * @param paCfg The array of configuration items to fill. + */ +static int utsDoGadgetCreateFillCfg(PUTSPKTREQGDGTCTORCFGITEM pCfgItem, unsigned cCfgItems, + size_t cbPkt, PUTSGADGETCFGITEM paCfg) +{ + int rc = VINF_SUCCESS; + unsigned idxCfg = 0; + + while ( RT_SUCCESS(rc) + && cCfgItems + && cbPkt) + { + if (cbPkt >= sizeof(UTSPKTREQGDGTCTORCFGITEM)) + { + cbPkt -= sizeof(UTSPKTREQGDGTCTORCFGITEM); + if (pCfgItem->u32KeySize + pCfgItem->u32ValSize >= cbPkt) + { + const char *pszKey = (const char *)(pCfgItem + 1); + const char *pszVal = pszKey + pCfgItem->u32KeySize; + + /* Validate termination. */ + if ( *(pszKey + pCfgItem->u32KeySize - 1) != '\0' + || *(pszVal + pCfgItem->u32ValSize - 1) != '\0') + rc = VERR_INVALID_PARAMETER; + else + { + paCfg[idxCfg].pszKey = RTStrDup(pszKey); + + rc = utsDoGadgetCreateCfgParseItem(&paCfg[idxCfg], pCfgItem->u32Type, pszVal); + if (RT_SUCCESS(rc)) + { + cbPkt -= pCfgItem->u32KeySize + pCfgItem->u32ValSize; + cCfgItems--; + idxCfg++; + pCfgItem = (PUTSPKTREQGDGTCTORCFGITEM)(pszVal + pCfgItem->u32ValSize); + } + } + } + else + rc = VERR_INVALID_PARAMETER; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + +/** + * Verifies and acknowledges a "BYE" request. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPktHdr The howdy packet. + */ +static int utsDoBye(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + int rc; + if (pPktHdr->cb == sizeof(UTSPKTHDR)) + rc = utsReplyAck(pClient, pPktHdr); + else + rc = utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTHDR)); + return rc; +} + +/** + * Verifies and acknowledges a "HOWDY" request. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPktHdr The howdy packet. + */ +static int utsDoHowdy(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + int rc = VINF_SUCCESS; + + if (pPktHdr->cb != sizeof(UTSPKTREQHOWDY)) + return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQHOWDY)); + + if (pClient->enmState != UTSCLIENTSTATE_INITIALISING) + return utsReplyInvalidState(pClient, pPktHdr); + + PUTSPKTREQHOWDY pReq = (PUTSPKTREQHOWDY)pPktHdr; + + if (pReq->uVersion != UTS_PROTOCOL_VS) + return utsReplyRC(pClient, pPktHdr, VERR_VERSION_MISMATCH, "The given version %#x is not supported", pReq->uVersion); + + /* Verify hostname string. */ + if (pReq->cchHostname >= sizeof(pReq->achHostname)) + return utsReplyBadSize(pClient, pPktHdr, sizeof(pReq->achHostname) - 1); + + if (pReq->achHostname[pReq->cchHostname] != '\0') + return utsReplyBadStrTermination(pClient, pPktHdr); + + /* Extract string. */ + pClient->pszHostname = RTStrDup(&pReq->achHostname[0]); + if (!pClient->pszHostname) + return utsReplyRC(pClient, pPktHdr, VERR_NO_MEMORY, "Failed to allocate memory for the hostname string"); + + if (pReq->fUsbConn & UTSPKT_HOWDY_CONN_F_PHYSICAL) + return utsReplyRC(pClient, pPktHdr, VERR_NOT_SUPPORTED, "Physical connections are not yet supported"); + + if (pReq->fUsbConn & UTSPKT_HOWDY_CONN_F_USBIP) + { + /* Set up the USB/IP server, find an unused port we can start the server on. */ + UTSGADGETCFGITEM aCfg[2]; + + uint16_t uPort = g_uUsbIpPortNext; + + if (g_uUsbIpPortNext == g_uUsbIpPortLast) + g_uUsbIpPortNext = g_uUsbIpPortFirst; + else + g_uUsbIpPortNext++; + + aCfg[0].pszKey = "UsbIp/Port"; + aCfg[0].Val.enmType = UTSGADGETCFGTYPE_UINT16; + aCfg[0].Val.u.u16 = uPort; + aCfg[1].pszKey = NULL; + + rc = utsGadgetHostCreate(UTSGADGETHOSTTYPE_USBIP, &aCfg[0], &pClient->hGadgetHost); + if (RT_SUCCESS(rc)) + { + /* Send the reply with the configured USB/IP port. */ + UTSPKTREPHOWDY Rep; + + RT_ZERO(Rep); + + Rep.uVersion = UTS_PROTOCOL_VS; + Rep.fUsbConn = UTSPKT_HOWDY_CONN_F_USBIP; + Rep.uUsbIpPort = uPort; + Rep.cUsbIpDevices = 1; + Rep.cPhysicalDevices = 0; + + rc = utsReplyInternal(pClient, &Rep.Sts, "ACK ", sizeof(Rep) - sizeof(UTSPKTSTS)); + if (RT_SUCCESS(rc)) + { + g_pTransport->pfnNotifyHowdy(pClient->pTransportClient); + pClient->enmState = UTSCLIENTSTATE_READY; + RTDirRemoveRecursive(g_szScratchPath, RTDIRRMREC_F_CONTENT_ONLY); + } + } + else + return utsReplyRC(pClient, pPktHdr, rc, "Creating the USB/IP gadget host failed"); + } + else + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_PARAMETER, "No access method requested"); + + return rc; +} + +/** + * Verifies and processes a "GADGET CREATE" request. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPktHdr The gadget create packet. + */ +static int utsDoGadgetCreate(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + int rc = VINF_SUCCESS; + + if (pPktHdr->cb < sizeof(UTSPKTREQGDGTCTOR)) + return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQGDGTCTOR)); + + if ( pClient->enmState != UTSCLIENTSTATE_READY + || pClient->hGadgetHost == NIL_UTSGADGETHOST) + return utsReplyInvalidState(pClient, pPktHdr); + + PUTSPKTREQGDGTCTOR pReq = (PUTSPKTREQGDGTCTOR)pPktHdr; + + if (pReq->u32GdgtType != UTSPKT_GDGT_CREATE_TYPE_TEST) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_PARAMETER, "The given gadget type is not supported"); + + if (pReq->u32GdgtAccess != UTSPKT_GDGT_CREATE_ACCESS_USBIP) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_PARAMETER, "The given gadget access method is not supported"); + + PUTSGADGETCFGITEM paCfg = NULL; + if (pReq->u32CfgItems > 0) + { + paCfg = (PUTSGADGETCFGITEM)RTMemAllocZ((pReq->u32CfgItems + 1) * sizeof(UTSGADGETCFGITEM)); + if (RT_UNLIKELY(!paCfg)) + return utsReplyRC(pClient, pPktHdr, VERR_NO_MEMORY, "Failed to allocate memory for configration items"); + + rc = utsDoGadgetCreateFillCfg((PUTSPKTREQGDGTCTORCFGITEM)(pReq + 1), pReq->u32CfgItems, + pPktHdr->cb - sizeof(UTSPKTREQGDGTCTOR), paCfg); + if (RT_FAILURE(rc)) + { + RTMemFree(paCfg); + return utsReplyRC(pClient, pPktHdr, rc, "Failed to parse configuration"); + } + } + + rc = utsGadgetCreate(pClient->hGadgetHost, UTSGADGETCLASS_TEST, paCfg, &pClient->hGadget); + if (RT_SUCCESS(rc)) + { + UTSPKTREPGDGTCTOR Rep; + RT_ZERO(Rep); + + Rep.idGadget = 0; + Rep.u32BusId = utsGadgetGetBusId(pClient->hGadget); + Rep.u32DevId = utsGadgetGetDevId(pClient->hGadget); + rc = utsReplyInternal(pClient, &Rep.Sts, "ACK ", sizeof(Rep) - sizeof(UTSPKTSTS)); + } + else + rc = utsReplyRC(pClient, pPktHdr, rc, "Failed to create gadget with %Rrc\n", rc); + + return rc; +} + +/** + * Verifies and processes a "GADGET DESTROY" request. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPktHdr The gadget destroy packet. + */ +static int utsDoGadgetDestroy(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + if (pPktHdr->cb != sizeof(UTSPKTREQGDGTDTOR)) + return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQGDGTDTOR)); + + if ( pClient->enmState != UTSCLIENTSTATE_READY + || pClient->hGadgetHost == NIL_UTSGADGETHOST) + return utsReplyInvalidState(pClient, pPktHdr); + + PUTSPKTREQGDGTDTOR pReq = (PUTSPKTREQGDGTDTOR)pPktHdr; + + if (pReq->idGadget != 0) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_HANDLE, "The given gadget handle is invalid"); + if (pClient->hGadget == NIL_UTSGADGET) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_STATE, "The gadget is not set up"); + + utsGadgetRelease(pClient->hGadget); + pClient->hGadget = NIL_UTSGADGET; + + return utsReplyAck(pClient, pPktHdr); +} + +/** + * Verifies and processes a "GADGET CONNECT" request. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPktHdr The gadget connect packet. + */ +static int utsDoGadgetConnect(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + if (pPktHdr->cb != sizeof(UTSPKTREQGDGTCNCT)) + return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQGDGTCNCT)); + + if ( pClient->enmState != UTSCLIENTSTATE_READY + || pClient->hGadgetHost == NIL_UTSGADGETHOST) + return utsReplyInvalidState(pClient, pPktHdr); + + PUTSPKTREQGDGTCNCT pReq = (PUTSPKTREQGDGTCNCT)pPktHdr; + + if (pReq->idGadget != 0) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_HANDLE, "The given gadget handle is invalid"); + if (pClient->hGadget == NIL_UTSGADGET) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_STATE, "The gadget is not set up"); + + int rc = utsGadgetConnect(pClient->hGadget); + if (RT_SUCCESS(rc)) + rc = utsReplyAck(pClient, pPktHdr); + else + rc = utsReplyRC(pClient, pPktHdr, rc, "Failed to connect the gadget"); + + return rc; +} + +/** + * Verifies and processes a "GADGET DISCONNECT" request. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPktHdr The gadget disconnect packet. + */ +static int utsDoGadgetDisconnect(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + if (pPktHdr->cb != sizeof(UTSPKTREQGDGTDCNT)) + return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQGDGTDCNT)); + + if ( pClient->enmState != UTSCLIENTSTATE_READY + || pClient->hGadgetHost == NIL_UTSGADGETHOST) + return utsReplyInvalidState(pClient, pPktHdr); + + PUTSPKTREQGDGTDCNT pReq = (PUTSPKTREQGDGTDCNT)pPktHdr; + + if (pReq->idGadget != 0) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_HANDLE, "The given gadget handle is invalid"); + if (pClient->hGadget == NIL_UTSGADGET) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_STATE, "The gadget is not set up"); + + int rc = utsGadgetDisconnect(pClient->hGadget); + if (RT_SUCCESS(rc)) + rc = utsReplyAck(pClient, pPktHdr); + else + rc = utsReplyRC(pClient, pPktHdr, rc, "Failed to disconnect the gadget"); + + return rc; +} + +/** + * Main request processing routine for each client. + * + * @returns IPRT status code. + * @param pClient The UTS client structure sending the request. + */ +static int utsClientReqProcess(PUTSCLIENT pClient) +{ + /* + * Read client command packet and process it. + */ + PUTSPKTHDR pPktHdr = NULL; + int rc = utsRecvPkt(pClient, &pPktHdr, true /*fAutoRetryOnFailure*/); + if (RT_FAILURE(rc)) + return rc; + + /* + * Do a string switch on the opcode bit. + */ + /* Connection: */ + if ( utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_HOWDY)) + rc = utsDoHowdy(pClient, pPktHdr); + else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_BYE)) + rc = utsDoBye(pClient, pPktHdr); + /* Gadget API. */ + else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_GADGET_CREATE)) + rc = utsDoGadgetCreate(pClient, pPktHdr); + else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_GADGET_DESTROY)) + rc = utsDoGadgetDestroy(pClient, pPktHdr); + else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_GADGET_CONNECT)) + rc = utsDoGadgetConnect(pClient, pPktHdr); + else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_GADGET_DISCONNECT)) + rc = utsDoGadgetDisconnect(pClient, pPktHdr); + /* Misc: */ + else + rc = utsReplyUnknown(pClient, pPktHdr); + + RTMemFree(pPktHdr); + + return rc; +} + +/** + * Destroys a client instance. + * + * @returns nothing. + * @param pClient The UTS client structure. + */ +static void utsClientDestroy(PUTSCLIENT pClient) +{ + if (pClient->pszHostname) + RTStrFree(pClient->pszHostname); + if (pClient->hGadget != NIL_UTSGADGET) + utsGadgetRelease(pClient->hGadget); + if (pClient->hGadgetHost != NIL_UTSGADGETHOST) + utsGadgetHostRelease(pClient->hGadgetHost); + RTMemFree(pClient); +} + +/** + * The main thread worker serving the clients. + */ +static DECLCALLBACK(int) utsClientWorker(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF2(hThread, pvUser); + unsigned cClientsMax = 0; + unsigned cClientsCur = 0; + PUTSCLIENT *papClients = NULL; + RTPOLLSET hPollSet; + + int rc = RTPollSetCreate(&hPollSet); + if (RT_FAILURE(rc)) + return rc; + + /* Add the pipe to the poll set. */ + rc = RTPollSetAddPipe(hPollSet, g_hPipeR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, 0); + if (RT_SUCCESS(rc)) + { + while (!g_fTerminate) + { + uint32_t fEvts; + uint32_t uId; + rc = RTPoll(hPollSet, RT_INDEFINITE_WAIT, &fEvts, &uId); + if (RT_SUCCESS(rc)) + { + if (uId == 0) + { + if (fEvts & RTPOLL_EVT_ERROR) + break; + + /* We got woken up because of a new client. */ + Assert(fEvts & RTPOLL_EVT_READ); + + uint8_t bRead; + size_t cbRead = 0; + rc = RTPipeRead(g_hPipeR, &bRead, 1, &cbRead); + AssertRC(rc); + + RTCritSectEnter(&g_CritSectClients); + /* Walk the list and add all new clients. */ + PUTSCLIENT pIt, pItNext; + RTListForEachSafe(&g_LstClientsNew, pIt, pItNext, UTSCLIENT, NdLst) + { + RTListNodeRemove(&pIt->NdLst); + Assert(cClientsCur <= cClientsMax); + if (cClientsCur == cClientsMax) + { + /* Realloc to accommodate for the new clients. */ + PUTSCLIENT *papClientsNew = (PUTSCLIENT *)RTMemReallocZ(papClients, cClientsMax * sizeof(PUTSCLIENT), (cClientsMax + 10) * sizeof(PUTSCLIENT)); + if (RT_LIKELY(papClientsNew)) + { + cClientsMax += 10; + papClients = papClientsNew; + } + } + + if (cClientsCur < cClientsMax) + { + /* Find a free slot in the client array. */ + unsigned idxSlt = 0; + while ( idxSlt < cClientsMax + && papClients[idxSlt] != NULL) + idxSlt++; + + rc = g_pTransport->pfnPollSetAdd(hPollSet, pIt->pTransportClient, idxSlt + 1); + if (RT_SUCCESS(rc)) + { + cClientsCur++; + papClients[idxSlt] = pIt; + } + else + { + g_pTransport->pfnNotifyBye(pIt->pTransportClient); + utsClientDestroy(pIt); + } + } + else + { + g_pTransport->pfnNotifyBye(pIt->pTransportClient); + utsClientDestroy(pIt); + } + } + RTCritSectLeave(&g_CritSectClients); + } + else + { + /* Client sends a request, pick the right client and process it. */ + PUTSCLIENT pClient = papClients[uId - 1]; + AssertPtr(pClient); + if (fEvts & RTPOLL_EVT_READ) + rc = utsClientReqProcess(pClient); + + if ( (fEvts & RTPOLL_EVT_ERROR) + || RT_FAILURE(rc)) + { + /* Close connection and remove client from array. */ + rc = g_pTransport->pfnPollSetRemove(hPollSet, pClient->pTransportClient, uId); + AssertRC(rc); + + g_pTransport->pfnNotifyBye(pClient->pTransportClient); + papClients[uId - 1] = NULL; + cClientsCur--; + utsClientDestroy(pClient); + } + } + } + } + } + + RTPollSetDestroy(hPollSet); + + return rc; +} + +/** + * The main loop. + * + * @returns exit code. + */ +static RTEXITCODE utsMainLoop(void) +{ + RTEXITCODE enmExitCode = RTEXITCODE_SUCCESS; + while (!g_fTerminate) + { + /* + * Wait for new connection and spin off a new thread + * for every new client. + */ + PUTSTRANSPORTCLIENT pTransportClient; + int rc = g_pTransport->pfnWaitForConnect(&pTransportClient); + if (RT_FAILURE(rc)) + continue; + + /* + * New connection, create new client structure and spin of + * the request handling thread. + */ + PUTSCLIENT pClient = (PUTSCLIENT)RTMemAllocZ(sizeof(UTSCLIENT)); + if (RT_LIKELY(pClient)) + { + pClient->enmState = UTSCLIENTSTATE_INITIALISING; + pClient->pTransportClient = pTransportClient; + pClient->pszHostname = NULL; + pClient->hGadgetHost = NIL_UTSGADGETHOST; + pClient->hGadget = NIL_UTSGADGET; + + /* Add client to the new list and inform the worker thread. */ + RTCritSectEnter(&g_CritSectClients); + RTListAppend(&g_LstClientsNew, &pClient->NdLst); + RTCritSectLeave(&g_CritSectClients); + + size_t cbWritten = 0; + rc = RTPipeWrite(g_hPipeW, "", 1, &cbWritten); + if (RT_FAILURE(rc)) + RTMsgError("Failed to inform worker thread of a new client"); + } + else + { + RTMsgError("Creating new client structure failed with out of memory error\n"); + g_pTransport->pfnNotifyBye(pTransportClient); + } + + + } + + return enmExitCode; +} + +/** + * Initializes the global UTS state. + * + * @returns IPRT status code. + */ +static int utsInit(void) +{ + int rc = VINF_SUCCESS; + PRTERRINFO pErrInfo = NULL; + + RTListInit(&g_LstClientsNew); + + rc = RTJsonParseFromFile(&g_hCfgJson, g_szCfgPath, pErrInfo); + if (RT_SUCCESS(rc)) + { + rc = utsPlatformInit(); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectInit(&g_CritSectClients); + if (RT_SUCCESS(rc)) + { + rc = RTPipeCreate(&g_hPipeR, &g_hPipeW, 0); + if (RT_SUCCESS(rc)) + { + /* Spin off the thread serving connections. */ + rc = RTThreadCreate(&g_hThreadServing, utsClientWorker, NULL, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, + "USBTSTSRV"); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + else + RTMsgError("Creating the client worker thread failed with %Rrc\n", rc); + + RTPipeClose(g_hPipeR); + RTPipeClose(g_hPipeW); + } + else + RTMsgError("Creating communications pipe failed with %Rrc\n", rc); + + RTCritSectDelete(&g_CritSectClients); + } + else + RTMsgError("Creating global critical section failed with %Rrc\n", rc); + + RTJsonValueRelease(g_hCfgJson); + } + else + RTMsgError("Initializing the platform failed with %Rrc\n", rc); + } + else + { + if (RTErrInfoIsSet(pErrInfo)) + { + RTMsgError("Failed to parse config with detailed error: %s (%Rrc)\n", + pErrInfo->pszMsg, pErrInfo->rc); + RTErrInfoFree(pErrInfo); + } + else + RTMsgError("Failed to parse config with unknown error (%Rrc)\n", rc); + } + + return rc; +} + +/** + * Determines the default configuration. + */ +static void utsSetDefaults(void) +{ + /* + * OS and ARCH. + */ + AssertCompile(sizeof(KBUILD_TARGET) <= sizeof(g_szOsShortName)); + strcpy(g_szOsShortName, KBUILD_TARGET); + + AssertCompile(sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szArchShortName)); + strcpy(g_szArchShortName, KBUILD_TARGET_ARCH); + + AssertCompile(sizeof(KBUILD_TARGET) + sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szOsDotArchShortName)); + strcpy(g_szOsDotArchShortName, KBUILD_TARGET); + g_szOsDotArchShortName[sizeof(KBUILD_TARGET) - 1] = '.'; + strcpy(&g_szOsDotArchShortName[sizeof(KBUILD_TARGET)], KBUILD_TARGET_ARCH); + + AssertCompile(sizeof(KBUILD_TARGET) + sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szOsSlashArchShortName)); + strcpy(g_szOsSlashArchShortName, KBUILD_TARGET); + g_szOsSlashArchShortName[sizeof(KBUILD_TARGET) - 1] = '/'; + strcpy(&g_szOsSlashArchShortName[sizeof(KBUILD_TARGET)], KBUILD_TARGET_ARCH); + +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + strcpy(g_szExeSuff, ".exe"); + strcpy(g_szScriptSuff, ".cmd"); +#else + strcpy(g_szExeSuff, ""); + strcpy(g_szScriptSuff, ".sh"); +#endif + + /* + * The CD/DVD-ROM location. + */ + /** @todo do a better job here :-) */ +#ifdef RT_OS_WINDOWS + strcpy(g_szDefCdRomPath, "D:/"); +#elif defined(RT_OS_OS2) + strcpy(g_szDefCdRomPath, "D:/"); +#else + if (RTDirExists("/media")) + strcpy(g_szDefCdRomPath, "/media/cdrom"); + else + strcpy(g_szDefCdRomPath, "/mnt/cdrom"); +#endif + strcpy(g_szCdRomPath, g_szDefCdRomPath); + + /* + * Temporary directory. + */ + int rc = RTPathTemp(g_szDefScratchPath, sizeof(g_szDefScratchPath)); + if (RT_SUCCESS(rc)) +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) || defined(RT_OS_DOS) + rc = RTPathAppend(g_szDefScratchPath, sizeof(g_szDefScratchPath), "uts-XXXX.tmp"); +#else + rc = RTPathAppend(g_szDefScratchPath, sizeof(g_szDefScratchPath), "uts-XXXXXXXXX.tmp"); +#endif + if (RT_FAILURE(rc)) + { + RTMsgError("RTPathTemp/Append failed when constructing scratch path: %Rrc\n", rc); + strcpy(g_szDefScratchPath, "/tmp/uts-XXXX.tmp"); + } + strcpy(g_szScratchPath, g_szDefScratchPath); + + /* + * Config file location. + */ + /** @todo Improve */ +#if !defined(RT_OS_WINDOWS) + strcpy(g_szCfgPath, "/etc/uts.conf"); +#else + strcpy(g_szCfgPath, ""); +#endif + + /* + * The default transporter is the first one. + */ + g_pTransport = g_apTransports[0]; +} + +/** + * Prints the usage. + * + * @param pStrm Where to print it. + * @param pszArgv0 The program name (argv[0]). + */ +static void utsUsage(PRTSTREAM pStrm, const char *pszArgv0) +{ + RTStrmPrintf(pStrm, + "Usage: %Rbn [options]\n" + "\n" + "Options:\n" + " --config <path>\n" + " Where to load the config from\n" + " --cdrom <path>\n" + " Where the CD/DVD-ROM will be mounted.\n" + " Default: %s\n" + " --scratch <path>\n" + " Where to put scratch files.\n" + " Default: %s \n" + , + pszArgv0, + g_szDefCdRomPath, + g_szDefScratchPath); + RTStrmPrintf(pStrm, + " --transport <name>\n" + " Use the specified transport layer, one of the following:\n"); + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + RTStrmPrintf(pStrm, " %s - %s\n", g_apTransports[i]->szName, g_apTransports[i]->pszDesc); + RTStrmPrintf(pStrm, " Default: %s\n", g_pTransport->szName); + RTStrmPrintf(pStrm, + " --display-output, --no-display-output\n" + " Display the output and the result of all child processes.\n"); + RTStrmPrintf(pStrm, + " --foreground\n" + " Don't daemonize, run in the foreground.\n"); + RTStrmPrintf(pStrm, + " --help, -h, -?\n" + " Display this message and exit.\n" + " --version, -V\n" + " Display the version and exit.\n"); + + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + if (g_apTransports[i]->cOpts) + { + RTStrmPrintf(pStrm, + "\n" + "Options for %s:\n", g_apTransports[i]->szName); + g_apTransports[i]->pfnUsage(g_pStdOut); + } +} + +/** + * Parses the arguments. + * + * @returns Exit code. Exit if this is non-zero or @a *pfExit is set. + * @param argc The number of arguments. + * @param argv The argument vector. + * @param pfExit For indicating exit when the exit code is zero. + */ +static RTEXITCODE utsParseArgv(int argc, char **argv, bool *pfExit) +{ + *pfExit = false; + + /* + * Storage for locally handled options. + */ + bool fDaemonize = true; + bool fDaemonized = false; + + /* + * Combine the base and transport layer option arrays. + */ + static const RTGETOPTDEF s_aBaseOptions[] = + { + { "--config", 'C', RTGETOPT_REQ_STRING }, + { "--transport", 't', RTGETOPT_REQ_STRING }, + { "--cdrom", 'c', RTGETOPT_REQ_STRING }, + { "--scratch", 's', RTGETOPT_REQ_STRING }, + { "--display-output", 'd', RTGETOPT_REQ_NOTHING }, + { "--no-display-output",'D', RTGETOPT_REQ_NOTHING }, + { "--foreground", 'f', RTGETOPT_REQ_NOTHING }, + { "--daemonized", 'Z', RTGETOPT_REQ_NOTHING }, + }; + + size_t cOptions = RT_ELEMENTS(s_aBaseOptions); + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + cOptions += g_apTransports[i]->cOpts; + + PRTGETOPTDEF paOptions = (PRTGETOPTDEF)alloca(cOptions * sizeof(RTGETOPTDEF)); + if (!paOptions) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "alloca failed\n"); + + memcpy(paOptions, s_aBaseOptions, sizeof(s_aBaseOptions)); + cOptions = RT_ELEMENTS(s_aBaseOptions); + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + { + memcpy(&paOptions[cOptions], g_apTransports[i]->paOpts, g_apTransports[i]->cOpts * sizeof(RTGETOPTDEF)); + cOptions += g_apTransports[i]->cOpts; + } + + /* + * Parse the arguments. + */ + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, argc, argv, paOptions, cOptions, 1, 0 /* fFlags */); + AssertRC(rc); + + int ch; + RTGETOPTUNION Val; + while ((ch = RTGetOpt(&GetState, &Val))) + { + switch (ch) + { + case 'C': + rc = RTStrCopy(g_szCfgPath, sizeof(g_szCfgPath), Val.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Config file path is path too long (%Rrc)\n", rc); + break; + + case 'c': + rc = RTStrCopy(g_szCdRomPath, sizeof(g_szCdRomPath), Val.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "CD/DVD-ROM is path too long (%Rrc)\n", rc); + break; + + case 'd': + g_fDisplayOutput = true; + break; + + case 'D': + g_fDisplayOutput = false; + break; + + case 'f': + fDaemonize = false; + break; + + case 'h': + utsUsage(g_pStdOut, argv[0]); + *pfExit = true; + return RTEXITCODE_SUCCESS; + + case 's': + rc = RTStrCopy(g_szScratchPath, sizeof(g_szScratchPath), Val.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "scratch path is too long (%Rrc)\n", rc); + break; + + case 't': + { + PCUTSTRANSPORT pTransport = NULL; + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + if (!strcmp(g_apTransports[i]->szName, Val.psz)) + { + pTransport = g_apTransports[i]; + break; + } + if (!pTransport) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown transport layer name '%s'\n", Val.psz); + g_pTransport = pTransport; + break; + } + + case 'V': + RTPrintf("$Revision: 153224 $\n"); + *pfExit = true; + return RTEXITCODE_SUCCESS; + + case 'Z': + fDaemonized = true; + fDaemonize = false; + break; + + default: + { + rc = VERR_TRY_AGAIN; + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + if (g_apTransports[i]->cOpts) + { + rc = g_apTransports[i]->pfnOption(ch, &Val); + if (RT_SUCCESS(rc)) + break; + if (rc != VERR_TRY_AGAIN) + { + *pfExit = true; + return RTEXITCODE_SYNTAX; + } + } + if (rc == VERR_TRY_AGAIN) + { + *pfExit = true; + return RTGetOptPrintError(ch, &Val); + } + break; + } + } + } + + /* + * Daemonize ourselves if asked to. + */ + if (fDaemonize && !*pfExit) + { + rc = RTProcDaemonize(argv, "--daemonized"); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcDaemonize: %Rrc\n", rc); + *pfExit = true; + } + + return RTEXITCODE_SUCCESS; +} + + +int main(int argc, char **argv) +{ + /* + * Initialize the runtime. + */ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Determine defaults and parse the arguments. + */ + utsSetDefaults(); + bool fExit; + RTEXITCODE rcExit = utsParseArgv(argc, argv, &fExit); + if (rcExit != RTEXITCODE_SUCCESS || fExit) + return rcExit; + + /* + * Initialize global state. + */ + rc = utsInit(); + if (RT_FAILURE(rc)) + return RTEXITCODE_FAILURE; + + /* + * Initialize the transport layer. + */ + rc = g_pTransport->pfnInit(); + if (RT_FAILURE(rc)) + return RTEXITCODE_FAILURE; + + /* + * Ok, start working + */ + rcExit = utsMainLoop(); + + /* + * Cleanup. + */ + g_pTransport->pfnTerm(); + + utsPlatformTerm(); + + return rcExit; +} + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.cpp new file mode 100644 index 00000000..1cb29fa6 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.cpp @@ -0,0 +1,212 @@ +/* $Id: UsbTestServiceGadget.cpp $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, USB gadget host API. + */ + +/* + * Copyright (C) 2016-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include "UsbTestServiceGadgetInternal.h" + + +/********************************************************************************************************************************* +* Constants And Macros, Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Internal UTS gadget host instance data. + */ +typedef struct UTSGADGETINT +{ + /** Reference counter. */ + volatile uint32_t cRefs; + /** Pointer to the gadget class callback table. */ + PCUTSGADGETCLASSIF pClassIf; + /** The gadget host handle. */ + UTSGADGETHOST hGadgetHost; + /** Class specific instance data - variable in size. */ + uint8_t abClassInst[1]; +} UTSGADGETINT; +/** Pointer to the internal gadget host instance data. */ +typedef UTSGADGETINT *PUTSGADGETINT; + + +/********************************************************************************************************************************* +* Global variables * +*********************************************************************************************************************************/ + +/** Known gadget host interfaces. */ +static const PCUTSGADGETCLASSIF g_apUtsGadgetClass[] = +{ + &g_UtsGadgetClassTest +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +/** + * Destroys a gadget instance. + * + * @returns nothing. + * @param pThis The gadget instance. + */ +static void utsGadgetDestroy(PUTSGADGETINT pThis) +{ + pThis->pClassIf->pfnTerm((PUTSGADGETCLASSINT)&pThis->abClassInst[0]); + RTMemFree(pThis); +} + + +DECLHIDDEN(int) utsGadgetCreate(UTSGADGETHOST hGadgetHost, UTSGADGETCLASS enmClass, + PCUTSGADGETCFGITEM paCfg, PUTSGADET phGadget) +{ + int rc = VINF_SUCCESS; + PCUTSGADGETCLASSIF pClassIf = NULL; + + /* Get the interface. */ + for (unsigned i = 0; i < RT_ELEMENTS(g_apUtsGadgetClass); i++) + { + if (g_apUtsGadgetClass[i]->enmClass == enmClass) + { + pClassIf = g_apUtsGadgetClass[i]; + break; + } + } + + if (RT_LIKELY(pClassIf)) + { + PUTSGADGETINT pThis = (PUTSGADGETINT)RTMemAllocZ(RT_UOFFSETOF_DYN(UTSGADGETINT, abClassInst[pClassIf->cbClass])); + if (RT_LIKELY(pThis)) + { + pThis->cRefs = 1; + pThis->hGadgetHost = hGadgetHost; + pThis->pClassIf = pClassIf; + rc = pClassIf->pfnInit((PUTSGADGETCLASSINT)&pThis->abClassInst[0], paCfg); + if (RT_SUCCESS(rc)) + { + /* Connect the gadget to the host. */ + rc = utsGadgetHostGadgetConnect(pThis->hGadgetHost, pThis); + if (RT_SUCCESS(rc)) + *phGadget = pThis; + } + else + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_INVALID_PARAMETER; + + return rc; +} + + +DECLHIDDEN(uint32_t) utsGadgetRetain(UTSGADGET hGadget) +{ + PUTSGADGETINT pThis = hGadget; + + AssertPtrReturn(pThis, 0); + + return ASMAtomicIncU32(&pThis->cRefs); +} + + +DECLHIDDEN(uint32_t) utsGadgetRelease(UTSGADGET hGadget) +{ + PUTSGADGETINT pThis = hGadget; + + AssertPtrReturn(pThis, 0); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + if (!cRefs) + utsGadgetDestroy(pThis); + + return cRefs; +} + + +DECLHIDDEN(uint32_t) utsGadgetGetBusId(UTSGADGET hGadget) +{ + PUTSGADGETINT pThis = hGadget; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + return pThis->pClassIf->pfnGetBusId((PUTSGADGETCLASSINT)&pThis->abClassInst[0]); +} + + +DECLHIDDEN(uint32_t) utsGadgetGetDevId(UTSGADGET hGadget) +{ + PUTSGADGETINT pThis = hGadget; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + return 1; /** @todo Current assumption which is true on Linux with dummy_hcd. */ +} + + +DECLHIDDEN(int) utsGadgetConnect(UTSGADGET hGadget) +{ + PUTSGADGETINT pThis = hGadget; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + int rc = pThis->pClassIf->pfnConnect((PUTSGADGETCLASSINT)&pThis->abClassInst[0]); + if (RT_SUCCESS(rc)) + rc = utsGadgetHostGadgetConnect(pThis->hGadgetHost, hGadget); + + return rc; +} + + +DECLHIDDEN(int) utsGadgetDisconnect(UTSGADGET hGadget) +{ + PUTSGADGETINT pThis = hGadget; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + int rc = utsGadgetHostGadgetDisconnect(pThis->hGadgetHost, hGadget); + if (RT_SUCCESS(rc)) + rc = pThis->pClassIf->pfnDisconnect((PUTSGADGETCLASSINT)&pThis->abClassInst[0]); + + return rc; +} + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.h new file mode 100644 index 00000000..142d5db6 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.h @@ -0,0 +1,546 @@ +/* $Id: UsbTestServiceGadget.h $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Gadget API. + */ + +/* + * Copyright (C) 2016-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceGadget_h +#define VBOX_INCLUDED_SRC_usb_UsbTestServiceGadget_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> +#include <iprt/types.h> + +RT_C_DECLS_BEGIN + +/** Opaque gadget host handle. */ +typedef struct UTSGADGETHOSTINT *UTSGADGETHOST; +/** Pointer to a gadget host handle. */ +typedef UTSGADGETHOST *PUTSGADGETHOST; + +/** NIL gadget host handle. */ +#define NIL_UTSGADGETHOST ((UTSGADGETHOST)0) + +/** Opaque USB gadget handle. */ +typedef struct UTSGADGETINT *UTSGADGET; +/** Pointer to a USB gadget handle. */ +typedef UTSGADGET *PUTSGADET; + +/** NIL gadget handle. */ +#define NIL_UTSGADGET ((UTSGADGET)0) + +/** + * Gadget/Gadget host configuration item type. + */ +typedef enum UTSGADGETCFGTYPE +{ + /** Don't use! */ + UTSGADGETCFGTYPE_INVALID = 0, + /** Boolean type. */ + UTSGADGETCFGTYPE_BOOLEAN, + /** UTF-8 string. */ + UTSGADGETCFGTYPE_STRING, + /** Unsigned 8bit integer. */ + UTSGADGETCFGTYPE_UINT8, + /** Unsigned 16bit integer. */ + UTSGADGETCFGTYPE_UINT16, + /** Unsigned 32bit integer. */ + UTSGADGETCFGTYPE_UINT32, + /** Unsigned 64bit integer. */ + UTSGADGETCFGTYPE_UINT64, + /** Signed 8bit integer. */ + UTSGADGETCFGTYPE_INT8, + /** Signed 16bit integer. */ + UTSGADGETCFGTYPE_INT16, + /** Signed 32bit integer. */ + UTSGADGETCFGTYPE_INT32, + /** Signed 64bit integer. */ + UTSGADGETCFGTYPE_INT64, + /** 32bit hack. */ + UTSGADGETCFGTYPE_32BIT_HACK = 0x7fffffff +} UTSGADGETCFGTYPE; + +/** + * Gadget configuration value. + */ +typedef struct UTSGADGETCFGVAL +{ + /** Value type */ + UTSGADGETCFGTYPE enmType; + /** Value based on the type. */ + union + { + bool f; + const char *psz; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + int8_t i8; + int16_t i16; + int32_t i32; + int64_t i64; + } u; +} UTSGADGETCFGVAL; +/** Pointer to a gadget configuration value. */ +typedef UTSGADGETCFGVAL *PUTSGADGETCFGVAL; +/** Pointer to a const gadget configuration value. */ +typedef const UTSGADGETCFGVAL *PCUTSGADGETCFGVAL; + +/** + * Gadget configuration item. + */ +typedef struct UTSGADGETCFGITEM +{ + /** Item key. */ + const char *pszKey; + /** Item value. */ + UTSGADGETCFGVAL Val; +} UTSGADGETCFGITEM; +/** Pointer to a gadget configuration item. */ +typedef UTSGADGETCFGITEM *PUTSGADGETCFGITEM; +/** Pointer to a const gadget configuration item. */ +typedef const UTSGADGETCFGITEM *PCUTSGADGETCFGITEM; + +/** + * Type for the gadget host. + */ +typedef enum UTSGADGETHOSTTYPE +{ + /** Invalid type, don't use. */ + UTSGADGETHOSTTYPE_INVALID = 0, + /** USB/IP host, gadgets are exported using a USB/IP server. */ + UTSGADGETHOSTTYPE_USBIP, + /** Physical connection using a device or OTG port. */ + UTSGADGETHOSTTYPE_PHYSICAL, + /** 32bit hack. */ + UTSGADGETHOSTTYPE_32BIT_HACK = 0x7fffffff +} UTSGADGETHOSTTYPE; + +/** + * USB gadget class. + */ +typedef enum UTSGADGETCLASS +{ + /** Invalid class, don't use. */ + UTSGADGETCLASS_INVALID = 0, + /** Special test device class. */ + UTSGADGETCLASS_TEST, + /** MSD device. */ + UTSGADGETCLASS_MSD, + /** 32bit hack. */ + UTSGADGETCLASS_32BIT_HACK = 0x7fffffff +} UTSGADGETCLASS; + +/** + * Queries the value of a given boolean key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pf Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryBool(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + bool *pf); + +/** + * Queries the value of a given boolean key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pf Where to store the value on success. + * @param fDef The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryBoolDef(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + bool *pf, bool fDef); + +/** + * Queries the string value of a given key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param ppszVal Where to store the pointer to the string on success, + * must be freed with RTStrFree(). + */ +DECLHIDDEN(int) utsGadgetCfgQueryString(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + char **ppszVal); + +/** + * Queries the string value of a given key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param ppszVal Where to store the pointer to the string on success, + * must be freed with RTStrFree(). + * @param pszDef The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryStringDef(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + char **ppszVal, const char *pszDef); + +/** + * Queries the value of a given uint8_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu8 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU8(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint8_t *pu8); + +/** + * Queries the value of a given uint8_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu8 Where to store the value on success. + * @param u8Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU8Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint8_t *pu8, uint8_t u8Def); + +/** + * Queries the value of a given uint16_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu16 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU16(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pu16); + +/** + * Queries the value of a given uint16_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu16 Where to store the value on success. + * @param u16Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU16Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pu16, uint16_t u16Def); + +/** + * Queries the value of a given uint32_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu32 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU32(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pu32); + +/** + * Queries the value of a given uint32_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu32 Where to store the value on success. + * @param u32Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU32Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pu32, uint32_t u32Def); + +/** + * Queries the value of a given uint64_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu64 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU64(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pu64); + +/** + * Queries the value of a given uint64_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu64 Where to store the value on success. + * @param u64Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU64Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pu64, uint64_t u64Def); + +/** + * Queries the value of a given int8_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi8 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS8(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + int8_t *pi8); + +/** + * Queries the value of a given int8_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi8 Where to store the value on success. + * @param i8Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS8Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + int8_t *pi8, uint8_t i8Def); + +/** + * Queries the value of a given int16_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi16 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS16(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pi16); + +/** + * Queries the value of a given int16_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi16 Where to store the value on success. + * @param i16Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS16Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pi16, uint16_t i16Def); + +/** + * Queries the value of a given int32_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi32 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS32(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pi32); + +/** + * Queries the value of a given int32_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi32 Where to store the value on success. + * @param i32Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS32Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pi32, uint32_t i32Def); + +/** + * Queries the value of a given int64_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi64 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS64(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pi64); + +/** + * Queries the value of a given int64_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi64 Where to store the value on success. + * @param i64Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS64Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pi64, uint64_t i64Def); + +/** + * Creates a new USB gadget host. + * + * @returns IPRT status code. + * @param enmType The host type. + * @param paCfg Additional configuration parameters - optional. + * The array must be terminated with a NULL entry. + * @param phGadgetHost Where to store the handle to the gadget host on success. + */ +DECLHIDDEN(int) utsGadgetHostCreate(UTSGADGETHOSTTYPE enmType, PCUTSGADGETCFGITEM paCfg, + PUTSGADGETHOST phGadgetHost); + +/** + * Retains the given gadget host handle. + * + * @returns New reference count. + * @param hGadgetHost The gadget host handle to retain. + */ +DECLHIDDEN(uint32_t) utsGadgetHostRetain(UTSGADGETHOST hGadgetHost); + +/** + * Releases the given gadget host handle, destroying it if the reference + * count reaches 0. + * + * @returns New reference count. + * @param hGadgetHost The gadget host handle to release. + */ +DECLHIDDEN(uint32_t) utsGadgetHostRelease(UTSGADGETHOST hGadgetHost); + +/** + * Returns the current config of the given gadget host. + * + * @returns Pointer to a constant array of configuration items for the given gadget host. + * @param hGadgetHost The gadget host handle. + */ +DECLHIDDEN(PCUTSGADGETCFGITEM) utsGadgetHostGetCfg(UTSGADGETHOST hGadgetHost); + +/** + * Connects the given gadget to the host. + * + * @returns IPRT status code. + * @param hGadgetHost The gadget host handle. + * @param hGadget The gadget handle. + */ +DECLHIDDEN(int) utsGadgetHostGadgetConnect(UTSGADGETHOST hGadgetHost, UTSGADGET hGadget); + +/** + * Disconnects the given gadget from the host. + * + * @returns IPRT status code. + * @param hGadgetHost The gadget host handle. + * @param hGadget The gadget handle. + */ +DECLHIDDEN(int) utsGadgetHostGadgetDisconnect(UTSGADGETHOST hGadgetHost, UTSGADGET hGadget); + +/** + * Creates a new USB gadget based the class. + * + * @returns IPRT status code. + * @param hGadgetHost The gadget host the gadget is part of. + * @param enmClass The gadget class. + * @param paCfg Array of optional configuration items for the gadget. + * @param phGadget Where to store the gadget handle on success. + */ +DECLHIDDEN(int) utsGadgetCreate(UTSGADGETHOST hGadgetHost, UTSGADGETCLASS enmClass, + PCUTSGADGETCFGITEM paCfg, PUTSGADET phGadget); + +/** + * Retains the given gadget handle. + * + * @returns New reference count. + * @param hGadget The gadget handle to retain. + */ +DECLHIDDEN(uint32_t) utsGadgetRetain(UTSGADGET hGadget); + +/** + * Releases the given gadget handle, destroying it if the reference + * count reaches 0. + * + * @returns New reference count. + * @param hGadget The gadget handle to destroy. + */ +DECLHIDDEN(uint32_t) utsGadgetRelease(UTSGADGET hGadget); + +/** + * Returns the current config of the given gadget. + * + * @returns Pointer to a constant array of configuration items for the given gadget. + * @param hGadget The gadget handle. + */ +DECLHIDDEN(PCUTSGADGETCFGITEM) utsGadgetGetCfg(UTSGADGET hGadget); + +/** + * Returns the path of the given gadget from which it can be accessed. + * + * @returns Access path. + * @param hGadget The gadget handle. + */ +DECLHIDDEN(const char *) utsGadgetGetAccessPath(UTSGADGET hGadget); + +/** + * Returns the bus ID the gadget is on. + * + * @returns Bus ID of the gadget. + * @param hGadget The gadget handle. + */ +DECLHIDDEN(uint32_t) utsGadgetGetBusId(UTSGADGET hGadget); + +/** + * Returns the device ID of the gagdet. + * + * @returns Device ID of the gadget. + * @param hGadget The gadget handle. + */ +DECLHIDDEN(uint32_t) utsGadgetGetDevId(UTSGADGET hGadget); + +/** + * Mark the gadget as connected to the host. Depending + * on the host type it will be appear as physically attached + * or will appear in the exported USB device list. + * + * @returns IPRT status code. + * @param hGadget The gadget handle to connect. + */ +DECLHIDDEN(int) utsGadgetConnect(UTSGADGET hGadget); + +/** + * Mark the gadget as disconnected from the host. + * + * @returns IPRT status code. + * @param hGadget The gadget handle to disconnect. + */ +DECLHIDDEN(int) utsGadgetDisconnect(UTSGADGET hGadget); + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceGadget_h */ + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetCfg.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetCfg.cpp new file mode 100644 index 00000000..50be8aab --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetCfg.cpp @@ -0,0 +1,462 @@ +/* $Id: UsbTestServiceGadgetCfg.cpp $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, USB gadget Cfg API. + */ + +/* + * Copyright (C) 2016-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/cdefs.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include "UsbTestServiceGadget.h" + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** + * Returns the gadget configuration item matching the given key. + * + * @returns Pointer to the configuration item on success or NULL if not found. + * @param paCfg The configuration item array. + * @param pszKey The key to look for. + */ +static PCUTSGADGETCFGITEM utsGadgetCfgGetItemFromKey(PCUTSGADGETCFGITEM paCfg, const char *pszKey) +{ + while ( paCfg + && paCfg->pszKey) + { + if (!RTStrCmp(paCfg->pszKey, pszKey)) + return paCfg; + + paCfg++; + } + return NULL; +} + + + +DECLHIDDEN(int) utsGadgetCfgQueryBool(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + bool *pf) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_BOOLEAN) + { + *pf = pCfgItem->Val.u.f; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryBoolDef(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + bool *pf, bool fDef) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_BOOLEAN) + { + *pf = pCfgItem ? pCfgItem->Val.u.f : fDef; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryString(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + char **ppszVal) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_STRING) + { + *ppszVal = RTStrDup(pCfgItem->Val.u.psz); + if (*ppszVal) + rc = VINF_SUCCESS; + else + rc = VERR_NO_STR_MEMORY; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryStringDef(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + char **ppszVal, const char *pszDef) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_STRING) + { + *ppszVal = RTStrDup(pCfgItem ? pCfgItem->Val.u.psz : pszDef); + if (*ppszVal) + rc = VINF_SUCCESS; + else + rc = VERR_NO_STR_MEMORY; + } + else + rc = VERR_INVALID_PARAMETER; + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU8(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint8_t *pu8) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT8) + { + *pu8 = pCfgItem->Val.u.u8; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU8Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint8_t *pu8, uint8_t u8Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT8) + { + *pu8 = pCfgItem ? pCfgItem->Val.u.u8 : u8Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU16(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pu16) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT16) + { + *pu16 = pCfgItem->Val.u.u16; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU16Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pu16, uint16_t u16Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT16) + { + *pu16 = pCfgItem ? pCfgItem->Val.u.u16 : u16Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU32(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pu32) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT32) + { + *pu32 = pCfgItem->Val.u.u32; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU32Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pu32, uint32_t u32Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT32) + { + *pu32 = pCfgItem ? pCfgItem->Val.u.u32 : u32Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU64(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pu64) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT64) + { + *pu64 = pCfgItem->Val.u.u64; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU64Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pu64, uint64_t u64Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT64) + { + *pu64 = pCfgItem ? pCfgItem->Val.u.u64 : u64Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS8(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + int8_t *pi8) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT8) + { + *pi8 = pCfgItem->Val.u.i8; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS8Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + int8_t *pi8, uint8_t i8Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT8) + { + *pi8 = pCfgItem ? pCfgItem->Val.u.i8 : i8Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS16(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pi16) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT16) + { + *pi16 = pCfgItem->Val.u.i16; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS16Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pi16, uint16_t i16Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT16) + { + *pi16 = pCfgItem ? pCfgItem->Val.u.i16 : i16Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS32(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pi32) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT32) + { + *pi32 = pCfgItem->Val.u.i32; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS32Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pi32, uint32_t i32Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT32) + { + *pi32 = pCfgItem ? pCfgItem->Val.u.i32 : i32Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS64(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pi64) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT64) + { + *pi64 = pCfgItem->Val.u.i64; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS64Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pi64, uint64_t i64Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT64) + { + *pi64 = pCfgItem ? pCfgItem->Val.u.i64 : i64Def; + rc = VINF_SUCCESS; + } + + return rc; +} + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetClassTest.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetClassTest.cpp new file mode 100644 index 00000000..eb7c769a --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetClassTest.cpp @@ -0,0 +1,471 @@ +/* $Id: UsbTestServiceGadgetClassTest.cpp $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, USB gadget class + * for the test device. + */ + +/* + * Copyright (C) 2016-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/err.h> +#include <iprt/env.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/string.h> +#include <iprt/symlink.h> +#include <iprt/thread.h> + +#include <iprt/linux/sysfs.h> + +#include "UsbTestServiceGadgetInternal.h" +#include "UsbTestServicePlatform.h" + + +/********************************************************************************************************************************* +* Constants And Macros, Structures and Typedefs * +*********************************************************************************************************************************/ + +/** Default configfs mount point. */ +#define UTS_GADGET_CLASS_CONFIGFS_MNT_DEF "/sys/kernel/config/usb_gadget" +/** Gadget template name */ +#define UTS_GADGET_TEMPLATE_NAME "gadget_test" + +/** Default vendor ID which is recognized by the usbtest driver. */ +#define UTS_GADGET_TEST_VENDOR_ID_DEF UINT16_C(0x0525) +/** Default product ID which is recognized by the usbtest driver. */ +#define UTS_GADGET_TEST_PRODUCT_ID_DEF UINT16_C(0xa4a0) +/** Default device class. */ +#define UTS_GADGET_TEST_DEVICE_CLASS_DEF UINT8_C(0xff) +/** Default serial number string. */ +#define UTS_GADGET_TEST_SERIALNUMBER_DEF "0123456789" +/** Default manufacturer string. */ +#define UTS_GADGET_TEST_MANUFACTURER_DEF "Oracle Inc." +/** Default product string. */ +#define UTS_GADGET_TEST_PRODUCT_DEF "USB test device" + +/** + * Internal UTS gadget host instance data. + */ +typedef struct UTSGADGETCLASSINT +{ + /** Gadget template path. */ + char *pszGadgetPath; + /** The UDC this gadget is connected to. */ + char *pszUdc; + /** Bus identifier for the used UDC. */ + uint32_t uBusId; + /** Device identifier. */ + uint32_t uDevId; +} UTSGADGETCLASSINT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** Number of already created gadgets, used for the template name. */ +static volatile uint32_t g_cGadgets = 0; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** + * Creates a new directory pointed to by the given format string. + * + * @returns IPRT status code. + * @param pszFormat The format string. + * @param va The arguments. + */ +static int utsGadgetClassTestDirCreateV(const char *pszFormat, va_list va) +{ + int rc = VINF_SUCCESS; + char aszPath[RTPATH_MAX + 1]; + + size_t cbStr = RTStrPrintfV(&aszPath[0], sizeof(aszPath), pszFormat, va); + if (cbStr <= sizeof(aszPath) - 1) + rc = RTDirCreateFullPath(aszPath, 0700); + else + rc = VERR_BUFFER_OVERFLOW; + + return rc; +} + + +/** + * Creates a new directory pointed to by the given format string. + * + * @returns IPRT status code. + * @param pszFormat The format string. + * @param ... The arguments. + */ +static int utsGadgetClassTestDirCreate(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = utsGadgetClassTestDirCreateV(pszFormat, va); + va_end(va); + return rc; +} + + +/** + * Removes a directory pointed to by the given format string. + * + * @returns IPRT status code. + * @param pszFormat The format string. + * @param va The arguments. + */ +static int utsGadgetClassTestDirRemoveV(const char *pszFormat, va_list va) +{ + int rc = VINF_SUCCESS; + char aszPath[RTPATH_MAX + 1]; + + size_t cbStr = RTStrPrintfV(&aszPath[0], sizeof(aszPath), pszFormat, va); + if (cbStr <= sizeof(aszPath) - 1) + rc = RTDirRemove(aszPath); + else + rc = VERR_BUFFER_OVERFLOW; + + return rc; +} + + +/** + * Removes a directory pointed to by the given format string. + * + * @returns IPRT status code. + * @param pszFormat The format string. + * @param ... The arguments. + */ +static int utsGadgetClassTestDirRemove(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = utsGadgetClassTestDirRemoveV(pszFormat, va); + va_end(va); + return rc; +} + + +/** + * Links the given function to the given config. + * + * @returns IPRT status code. + * @param pClass The gadget class instance data. + * @param pszFunc The function to link. + * @param pszCfg The configuration which the function will be part of. + */ +static int utsGadgetClassTestLinkFuncToCfg(PUTSGADGETCLASSINT pClass, const char *pszFunc, const char *pszCfg) +{ + int rc = VINF_SUCCESS; + char aszPathFunc[RTPATH_MAX + 1]; + char aszPathCfg[RTPATH_MAX + 1]; + + size_t cbStr = RTStrPrintf(&aszPathFunc[0], sizeof(aszPathFunc), "%s/functions/%s", + pClass->pszGadgetPath, pszFunc); + if (cbStr <= sizeof(aszPathFunc) - 1) + { + cbStr = RTStrPrintf(&aszPathCfg[0], sizeof(aszPathCfg), "%s/configs/%s/%s", + pClass->pszGadgetPath, pszCfg, pszFunc); + if (cbStr <= sizeof(aszPathCfg) - 1) + rc = RTSymlinkCreate(&aszPathCfg[0], &aszPathFunc[0], RTSYMLINKTYPE_DIR, 0); + else + rc = VERR_BUFFER_OVERFLOW; + } + else + rc = VERR_BUFFER_OVERFLOW; + + return rc; +} + + +/** + * Unlinks the given function from the given configuration. + * + * @returns IPRT status code. + * @param pClass The gadget class instance data. + * @param pszFunc The function to unlink. + * @param pszCfg The configuration which the function is currently part of. + */ +static int utsGadgetClassTestUnlinkFuncFromCfg(PUTSGADGETCLASSINT pClass, const char *pszFunc, const char *pszCfg) +{ + int rc = VINF_SUCCESS; + char aszPath[RTPATH_MAX + 1]; + size_t cbStr = RTStrPrintf(&aszPath[0], sizeof(aszPath), "%s/configs/%s/%s", + pClass->pszGadgetPath, pszCfg, pszFunc); + if (cbStr <= sizeof(aszPath) - 1) + rc = RTSymlinkDelete(&aszPath[0], 0); + else + rc = VERR_BUFFER_OVERFLOW; + + return rc; +} + + +/** + * Cleans up any leftover configurations from the gadget class. + * + * @returns nothing. + * @param pClass The gadget class instance data. + */ +static void utsGadgetClassTestCleanup(PUTSGADGETCLASSINT pClass) +{ + /* Unbind the gadget from the currently assigned UDC first. */ + int rc = RTLinuxSysFsWriteStrFile("", 0, NULL, "%s/UDC", pClass->pszGadgetPath); + AssertRC(rc); + + /* Delete the symlinks, ignore any errors. */ + utsGadgetClassTestUnlinkFuncFromCfg(pClass, "Loopback.0", "c.2"); + utsGadgetClassTestUnlinkFuncFromCfg(pClass, "SourceSink.0", "c.1"); + + /* Delete configuration strings and then the configuration directories. */ + utsGadgetClassTestDirRemove("%s/configs/c.2/strings/0x409", pClass->pszGadgetPath); + utsGadgetClassTestDirRemove("%s/configs/c.1/strings/0x409", pClass->pszGadgetPath); + + utsGadgetClassTestDirRemove("%s/configs/c.2", pClass->pszGadgetPath); + utsGadgetClassTestDirRemove("%s/configs/c.1", pClass->pszGadgetPath); + + /* Delete the functions. */ + utsGadgetClassTestDirRemove("%s/functions/Loopback.0", pClass->pszGadgetPath); + utsGadgetClassTestDirRemove("%s/functions/SourceSink.0", pClass->pszGadgetPath); + + /* Delete the english strings. */ + utsGadgetClassTestDirRemove("%s/strings/0x409", pClass->pszGadgetPath); + + /* Finally delete the gadget template. */ + utsGadgetClassTestDirRemove(pClass->pszGadgetPath); + + /* Release the UDC. */ + if (pClass->pszUdc) + { + rc = utsPlatformLnxReleaseUDC(pClass->pszUdc); + AssertRC(rc); + RTStrFree(pClass->pszUdc); + } +} + +/** + * @interface_method_impl{UTSGADGETCLASSIF,pfnInit} + */ +static DECLCALLBACK(int) utsGadgetClassTestInit(PUTSGADGETCLASSINT pClass, PCUTSGADGETCFGITEM paCfg) +{ + int rc = VINF_SUCCESS; + + if (RTLinuxSysFsExists(UTS_GADGET_CLASS_CONFIGFS_MNT_DEF)) + { + /* Create the gadget template */ + unsigned idx = ASMAtomicIncU32(&g_cGadgets); + + int rcStr = RTStrAPrintf(&pClass->pszGadgetPath, "%s/%s%u", UTS_GADGET_CLASS_CONFIGFS_MNT_DEF, + UTS_GADGET_TEMPLATE_NAME, idx); + if (rcStr == -1) + return VERR_NO_STR_MEMORY; + + rc = utsGadgetClassTestDirCreate(pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + { + uint16_t idVendor = 0; + uint16_t idProduct = 0; + uint8_t bDeviceClass = 0; + char *pszSerial = NULL; + char *pszManufacturer = NULL; + char *pszProduct = NULL; + bool fSuperSpeed = false; + + /* Get basic device config. */ + rc = utsGadgetCfgQueryU16Def(paCfg, "Gadget/idVendor", &idVendor, UTS_GADGET_TEST_VENDOR_ID_DEF); + if (RT_SUCCESS(rc)) + rc = utsGadgetCfgQueryU16Def(paCfg, "Gadget/idProduct", &idProduct, UTS_GADGET_TEST_PRODUCT_ID_DEF); + if (RT_SUCCESS(rc)) + rc = utsGadgetCfgQueryU8Def(paCfg, "Gadget/bDeviceClass", &bDeviceClass, UTS_GADGET_TEST_DEVICE_CLASS_DEF); + if (RT_SUCCESS(rc)) + rc = utsGadgetCfgQueryStringDef(paCfg, "Gadget/SerialNumber", &pszSerial, UTS_GADGET_TEST_SERIALNUMBER_DEF); + if (RT_SUCCESS(rc)) + rc = utsGadgetCfgQueryStringDef(paCfg, "Gadget/Manufacturer", &pszManufacturer, UTS_GADGET_TEST_MANUFACTURER_DEF); + if (RT_SUCCESS(rc)) + rc = utsGadgetCfgQueryStringDef(paCfg, "Gadget/Product", &pszProduct, UTS_GADGET_TEST_PRODUCT_DEF); + if (RT_SUCCESS(rc)) + rc = utsGadgetCfgQueryBoolDef(paCfg, "Gadget/SuperSpeed", &fSuperSpeed, false); + + if (RT_SUCCESS(rc)) + { + /* Write basic attributes. */ + rc = RTLinuxSysFsWriteU16File(16, idVendor, "%s/idVendor", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteU16File(16, idProduct, "%s/idProduct", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteU16File(16, bDeviceClass, "%s/bDeviceClass", pClass->pszGadgetPath); + + /* Create english language strings. */ + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/strings/0x409", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteStrFile(pszSerial, 0, NULL, "%s/strings/0x409/serialnumber", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteStrFile(pszManufacturer, 0, NULL, "%s/strings/0x409/manufacturer", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteStrFile(pszProduct, 0, NULL, "%s/strings/0x409/product", pClass->pszGadgetPath); + + /* Create the gadget functions. */ + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/functions/SourceSink.0", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/functions/Loopback.0", pClass->pszGadgetPath); + + /* Create the device configs. */ + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/configs/c.1", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/configs/c.2", pClass->pszGadgetPath); + + /* Write configuration strings. */ + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/configs/c.1/strings/0x409", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/configs/c.2/strings/0x409", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteStrFile("source and sink data", 0, NULL, "%s/configs/c.1/strings/0x409/configuration", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteStrFile("loop input to output", 0, NULL, "%s/configs/c.2/strings/0x409/configuration", pClass->pszGadgetPath); + + /* Link the functions into the configurations. */ + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestLinkFuncToCfg(pClass, "SourceSink.0", "c.1"); + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestLinkFuncToCfg(pClass, "Loopback.0", "c.2"); + + /* Finally enable the gadget by attaching it to a UDC. */ + if (RT_SUCCESS(rc)) + { + pClass->pszUdc = NULL; + + rc = utsPlatformLnxAcquireUDC(fSuperSpeed, &pClass->pszUdc, &pClass->uBusId); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteStrFile(pClass->pszUdc, 0, NULL, "%s/UDC", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + RTThreadSleep(500); /* Fudge: Sleep a bit to give the device a chance to appear on the host so binding succeeds. */ + } + } + + if (pszSerial) + RTStrFree(pszSerial); + if (pszManufacturer) + RTStrFree(pszManufacturer); + if (pszProduct) + RTStrFree(pszProduct); + } + } + else + rc = VERR_NOT_FOUND; + + if (RT_FAILURE(rc)) + utsGadgetClassTestCleanup(pClass); + + return rc; +} + + +/** + * @interface_method_impl{UTSGADGETCLASSIF,pfnTerm} + */ +static DECLCALLBACK(void) utsGadgetClassTestTerm(PUTSGADGETCLASSINT pClass) +{ + utsGadgetClassTestCleanup(pClass); + + if (pClass->pszGadgetPath) + RTStrFree(pClass->pszGadgetPath); +} + + +/** + * @interface_method_impl{UTSGADGETCLASSIF,pfnGetBusId} + */ +static DECLCALLBACK(uint32_t) utsGadgetClassTestGetBusId(PUTSGADGETCLASSINT pClass) +{ + return pClass->uBusId; +} + + +/** + * @interface_method_impl{UTSGADGETCLASSIF,pfnConnect} + */ +static DECLCALLBACK(int) utsGadgetClassTestConnect(PUTSGADGETCLASSINT pClass) +{ + int rc = RTLinuxSysFsWriteStrFile("connect", 0, NULL, "/sys/class/udc/%s/soft_connect", pClass->pszUdc); + if (RT_SUCCESS(rc)) + RTThreadSleep(500); /* Fudge: Sleep a bit to give the device a chance to appear on the host so binding succeeds. */ + + return rc; +} + + +/** + * @interface_method_impl{UTSGADGETCLASSIF,pfnDisconnect} + */ +static DECLCALLBACK(int) utsGadgetClassTestDisconnect(PUTSGADGETCLASSINT pClass) +{ + return RTLinuxSysFsWriteStrFile("disconnect", 0, NULL, "/sys/class/udc/%s/soft_connect", pClass->pszUdc);} + + + +/** + * The gadget host interface callback table. + */ +const UTSGADGETCLASSIF g_UtsGadgetClassTest = +{ + /** enmType */ + UTSGADGETCLASS_TEST, + /** pszDesc */ + "UTS test device gadget class", + /** cbIf */ + sizeof(UTSGADGETCLASSINT), + /** pfnInit */ + utsGadgetClassTestInit, + /** pfnTerm */ + utsGadgetClassTestTerm, + /** pfnGetBusId */ + utsGadgetClassTestGetBusId, + /** pfnConnect */ + utsGadgetClassTestConnect, + /** pfnDisconnect. */ + utsGadgetClassTestDisconnect +}; + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHost.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHost.cpp new file mode 100644 index 00000000..0984e5ce --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHost.cpp @@ -0,0 +1,180 @@ +/* $Id: UsbTestServiceGadgetHost.cpp $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, USB gadget host API. + */ + +/* + * Copyright (C) 2016-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include "UsbTestServiceGadget.h" +#include "UsbTestServiceGadgetHostInternal.h" + + +/********************************************************************************************************************************* +* Constants And Macros, Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Internal UTS gadget host instance data. + */ +typedef struct UTSGADGETHOSTINT +{ + /** Reference counter. */ + volatile uint32_t cRefs; + /** Pointer to the gadget host callback table. */ + PCUTSGADGETHOSTIF pHstIf; + /** Interface specific instance data - variable in size. */ + uint8_t abIfInst[1]; +} UTSGADGETHOSTINT; +/** Pointer to the internal gadget host instance data. */ +typedef UTSGADGETHOSTINT *PUTSGADGETHOSTINT; + + +/********************************************************************************************************************************* +* Global variables * +*********************************************************************************************************************************/ + +/** Known gadget host interfaces. */ +static const PCUTSGADGETHOSTIF g_apUtsGadgetHostIf[] = +{ + &g_UtsGadgetHostIfUsbIp, +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +/** + * Destroys a gadget host instance. + * + * @returns nothing. + * @param pThis The gadget host instance. + */ +static void utsGadgetHostDestroy(PUTSGADGETHOSTINT pThis) +{ + /** @todo Remove all gadgets. */ + pThis->pHstIf->pfnTerm((PUTSGADGETHOSTTYPEINT)&pThis->abIfInst[0]); + RTMemFree(pThis); +} + + +DECLHIDDEN(int) utsGadgetHostCreate(UTSGADGETHOSTTYPE enmType, PCUTSGADGETCFGITEM paCfg, + PUTSGADGETHOST phGadgetHost) +{ + int rc = VINF_SUCCESS; + PCUTSGADGETHOSTIF pIf = NULL; + + /* Get the interface. */ + for (unsigned i = 0; i < RT_ELEMENTS(g_apUtsGadgetHostIf); i++) + { + if (g_apUtsGadgetHostIf[i]->enmType == enmType) + { + pIf = g_apUtsGadgetHostIf[i]; + break; + } + } + + if (RT_LIKELY(pIf)) + { + PUTSGADGETHOSTINT pThis = (PUTSGADGETHOSTINT)RTMemAllocZ(RT_UOFFSETOF_DYN(UTSGADGETHOSTINT, abIfInst[pIf->cbIf])); + if (RT_LIKELY(pThis)) + { + pThis->cRefs = 1; + pThis->pHstIf = pIf; + rc = pIf->pfnInit((PUTSGADGETHOSTTYPEINT)&pThis->abIfInst[0], paCfg); + if (RT_SUCCESS(rc)) + *phGadgetHost = pThis; + else + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_INVALID_PARAMETER; + + return rc; +} + + +DECLHIDDEN(uint32_t) utsGadgetHostRetain(UTSGADGETHOST hGadgetHost) +{ + PUTSGADGETHOSTINT pThis = hGadgetHost; + + AssertPtrReturn(pThis, 0); + + return ASMAtomicIncU32(&pThis->cRefs); +} + + +DECLHIDDEN(uint32_t) utsGadgetHostRelease(UTSGADGETHOST hGadgetHost) +{ + PUTSGADGETHOSTINT pThis = hGadgetHost; + + AssertPtrReturn(pThis, 0); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + if (!cRefs) + utsGadgetHostDestroy(pThis); + + return cRefs; +} + + +DECLHIDDEN(int) utsGadgetHostGadgetConnect(UTSGADGETHOST hGadgetHost, UTSGADGET hGadget) +{ + PUTSGADGETHOSTINT pThis = hGadgetHost; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + return pThis->pHstIf->pfnGadgetConnect((PUTSGADGETHOSTTYPEINT)&pThis->abIfInst[0], hGadget); +} + + +DECLHIDDEN(int) utsGadgetHostGadgetDisconnect(UTSGADGETHOST hGadgetHost, UTSGADGET hGadget) +{ + PUTSGADGETHOSTINT pThis = hGadgetHost; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + return pThis->pHstIf->pfnGadgetDisconnect((PUTSGADGETHOSTTYPEINT)&pThis->abIfInst[0], hGadget); +} + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostInternal.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostInternal.h new file mode 100644 index 00000000..440ab50e --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostInternal.h @@ -0,0 +1,133 @@ +/* $Id: UsbTestServiceGadgetHostInternal.h $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Gadget API. + */ + +/* + * Copyright (C) 2016-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetHostInternal_h +#define VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetHostInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> +#include <iprt/types.h> + +#include "UsbTestServiceGadget.h" + +RT_C_DECLS_BEGIN + +/** Pointer to an opaque type dependent gadget host instance data. */ +typedef struct UTSGADGETHOSTTYPEINT *PUTSGADGETHOSTTYPEINT; +/** Pointer to a gadget host instance pointer. */ +typedef PUTSGADGETHOSTTYPEINT *PPUTSGADETHOSTTYPEINT; + +/** + * Gadget host interface. + */ +typedef struct UTSGADGETHOSTIF +{ + /** The gadget host type implemented. */ + UTSGADGETHOSTTYPE enmType; + /** Description. */ + const char *pszDesc; + /** Size of the interface specific instance data. */ + size_t cbIf; + + /** + * Initializes the gadget host interface. + * + * @returns IPRT status code. + * @param pIf The interface specific instance data. + * @param paCfg The configuration of the interface. + */ + DECLR3CALLBACKMEMBER(int, pfnInit, (PUTSGADGETHOSTTYPEINT pIf, PCUTSGADGETCFGITEM paCfg)); + + /** + * Terminates the gadget host interface. + * + * @returns nothing. + * @param pIf The interface specific instance data. + */ + DECLR3CALLBACKMEMBER(void, pfnTerm, (PUTSGADGETHOSTTYPEINT pIf)); + + /** + * Adds the given gadget to the host interface. + * + * @returns IPRT status code. + * @param pIf The interface specific instance data. + * @param hGadget The gadget handle to add. + */ + DECLR3CALLBACKMEMBER(int, pfnGadgetAdd, (PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget)); + + /** + * Removes the given gadget from the host interface. + * + * @returns IPRT status code. + * @param pIf The interface specific instance data. + * @param hGadget The gadget handle to remove. + */ + DECLR3CALLBACKMEMBER(int, pfnGadgetRemove, (PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget)); + + /** + * Connects the given gadget to the host interface so it appears as connected to the client + * of the gadget host. + * + * @returns IPRT status code. + * @param pIf The interface specific instance data. + * @param hGadget The gadget handle to add. + */ + DECLR3CALLBACKMEMBER(int, pfnGadgetConnect, (PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget)); + + /** + * Disconnects the given gadget from the host interface so it appears as disconnected to the client + * of the gadget host. + * + * @returns IPRT status code. + * @param pIf The interface specific instance data. + * @param hGadget The gadget handle to add. + */ + DECLR3CALLBACKMEMBER(int, pfnGadgetDisconnect, (PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget)); + +} UTSGADGETHOSTIF; +/** Pointer to a gadget host callback table. */ +typedef UTSGADGETHOSTIF *PUTSGADGETHOSTIF; +/** Pointer to a const gadget host callback table. */ +typedef const struct UTSGADGETHOSTIF *PCUTSGADGETHOSTIF; + +extern UTSGADGETHOSTIF const g_UtsGadgetHostIfUsbIp; + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetHostInternal_h */ + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostUsbIp.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostUsbIp.cpp new file mode 100644 index 00000000..b9934cf8 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostUsbIp.cpp @@ -0,0 +1,263 @@ +/* $Id: UsbTestServiceGadgetHostUsbIp.cpp $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, USB gadget host interface + * for USB/IP. + */ + +/* + * Copyright (C) 2016-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/env.h> +#include <iprt/mem.h> +#include <iprt/process.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/time.h> + +#include "UsbTestServiceGadgetHostInternal.h" +#include "UsbTestServicePlatform.h" + + +/********************************************************************************************************************************* +* Constants And Macros, Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Internal UTS gadget host instance data. + */ +typedef struct UTSGADGETHOSTTYPEINT +{ + /** Handle to the USB/IP daemon process. */ + RTPROCESS hProcUsbIp; +} UTSGADGETHOSTTYPEINT; + +/** Default port of the USB/IP server. */ +#define UTS_GADGET_HOST_USBIP_PORT_DEF 3240 + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +/** + * Worker for binding/unbinding the given gadget from the USB/IP server. + * + * @returns IPRT status code. + * @param pThis The gadget host instance. + * @param hGadget The gadget handle. + * @param fBind Flag whether to do a bind or unbind. + */ +static int usbGadgetHostUsbIpBindUnbind(PUTSGADGETHOSTTYPEINT pThis, UTSGADGET hGadget, bool fBind) +{ + RT_NOREF1(pThis); + uint32_t uBusId, uDevId; + char aszBus[32]; + + uBusId = utsGadgetGetBusId(hGadget); + uDevId = utsGadgetGetDevId(hGadget); + + /* Create the busid argument string. */ + size_t cbRet = RTStrPrintf(&aszBus[0], RT_ELEMENTS(aszBus), "%u-%u", uBusId, uDevId); + if (cbRet == RT_ELEMENTS(aszBus)) + return VERR_BUFFER_OVERFLOW; + + /* Bind to the USB/IP server. */ + RTPROCESS hProcUsbIp = NIL_RTPROCESS; + const char *apszArgv[5]; + + apszArgv[0] = "usbip"; + apszArgv[1] = fBind ? "bind" : "unbind"; + apszArgv[2] = "-b"; + apszArgv[3] = &aszBus[0]; + apszArgv[4] = NULL; + + int rc = RTProcCreate("usbip", apszArgv, RTENV_DEFAULT, RTPROC_FLAGS_SEARCH_PATH, &hProcUsbIp); + if (RT_SUCCESS(rc)) + { + RTPROCSTATUS ProcSts; + rc = RTProcWait(hProcUsbIp, RTPROCWAIT_FLAGS_BLOCK, &ProcSts); + if (RT_SUCCESS(rc)) + { + /* Evaluate the process status. */ + if ( ProcSts.enmReason != RTPROCEXITREASON_NORMAL + || ProcSts.iStatus != 0) + rc = VERR_UNRESOLVED_ERROR; /** @todo Log and give finer grained status code. */ + } + } + + return rc; +} + +/** + * @interface_method_impl{UTSGADGETHOSTIF,pfnInit} + */ +static DECLCALLBACK(int) utsGadgetHostUsbIpInit(PUTSGADGETHOSTTYPEINT pIf, PCUTSGADGETCFGITEM paCfg) +{ + int rc = VINF_SUCCESS; + uint16_t uPort = 0; + + pIf->hProcUsbIp = NIL_RTPROCESS; + + rc = utsGadgetCfgQueryU16Def(paCfg, "UsbIp/Port", &uPort, UTS_GADGET_HOST_USBIP_PORT_DEF); + if (RT_SUCCESS(rc)) + { + /* Make sure the kernel drivers are loaded. */ + rc = utsPlatformModuleLoad("usbip-core", NULL, 0); + if (RT_SUCCESS(rc)) + { + rc = utsPlatformModuleLoad("usbip-host", NULL, 0); + if (RT_SUCCESS(rc)) + { + char aszPort[10]; + char aszPidFile[64]; + const char *apszArgv[6]; + + RTStrPrintf(aszPort, RT_ELEMENTS(aszPort), "%u", uPort); + RTStrPrintf(aszPidFile, RT_ELEMENTS(aszPidFile), "/var/run/usbipd-%u.pid", uPort); + /* Start the USB/IP server process. */ + apszArgv[0] = "usbipd"; + apszArgv[1] = "--tcp-port"; + apszArgv[2] = aszPort; + apszArgv[3] = "--pid"; + apszArgv[4] = aszPidFile; + apszArgv[5] = NULL; + rc = RTProcCreate("usbipd", apszArgv, RTENV_DEFAULT, RTPROC_FLAGS_SEARCH_PATH, &pIf->hProcUsbIp); + if (RT_SUCCESS(rc)) + { + /* Wait for a bit to make sure the server started up successfully. */ + uint64_t tsStart = RTTimeMilliTS(); + do + { + RTPROCSTATUS ProcSts; + rc = RTProcWait(pIf->hProcUsbIp, RTPROCWAIT_FLAGS_NOBLOCK, &ProcSts); + if (rc != VERR_PROCESS_RUNNING) + { + rc = VERR_INVALID_HANDLE; + break; + } + RTThreadSleep(1); + rc = VINF_SUCCESS; + } while (RTTimeMilliTS() - tsStart < 2 * 1000); /* 2 seconds. */ + } + } + } + } + + return rc; +} + + +/** + * @interface_method_impl{UTSGADGETHOSTIF,pfnTerm} + */ +static DECLCALLBACK(void) utsGadgetHostUsbIpTerm(PUTSGADGETHOSTTYPEINT pIf) +{ + /* Kill the process and wait for it to terminate. */ + RTProcTerminate(pIf->hProcUsbIp); + + RTPROCSTATUS ProcSts; + RTProcWait(pIf->hProcUsbIp, RTPROCWAIT_FLAGS_BLOCK, &ProcSts); +} + + +/** + * @interface_method_impl{UTSGADGETHOSTIF,pfnGadgetAdd} + */ +static DECLCALLBACK(int) utsGadgetHostUsbIpGadgetAdd(PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget) +{ + /* Nothing to do so far. */ + RT_NOREF2(pIf, hGadget); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{UTSGADGETHOSTIF,pfnGadgetRemove} + */ +static DECLCALLBACK(int) utsGadgetHostUsbIpGadgetRemove(PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget) +{ + /* Nothing to do so far. */ + RT_NOREF2(pIf, hGadget); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{UTSGADGETHOSTIF,pfnGadgetConnect} + */ +static DECLCALLBACK(int) utsGadgetHostUsbIpGadgetConnect(PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget) +{ + return usbGadgetHostUsbIpBindUnbind(pIf, hGadget, true /* fBind */); +} + + +/** + * @interface_method_impl{UTSGADGETHOSTIF,pfnGadgetDisconnect} + */ +static DECLCALLBACK(int) utsGadgetHostUsbIpGadgetDisconnect(PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget) +{ + return usbGadgetHostUsbIpBindUnbind(pIf, hGadget, false /* fBind */); +} + + + +/** + * The gadget host interface callback table. + */ +const UTSGADGETHOSTIF g_UtsGadgetHostIfUsbIp = +{ + /** enmType */ + UTSGADGETHOSTTYPE_USBIP, + /** pszDesc */ + "UTS USB/IP gadget host", + /** cbIf */ + sizeof(UTSGADGETHOSTTYPEINT), + /** pfnInit */ + utsGadgetHostUsbIpInit, + /** pfnTerm */ + utsGadgetHostUsbIpTerm, + /** pfnGadgetAdd */ + utsGadgetHostUsbIpGadgetAdd, + /** pfnGadgetRemove */ + utsGadgetHostUsbIpGadgetRemove, + /** pfnGadgetConnect */ + utsGadgetHostUsbIpGadgetConnect, + /** pfnGadgetDisconnect */ + utsGadgetHostUsbIpGadgetDisconnect +}; diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetInternal.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetInternal.h new file mode 100644 index 00000000..7131307e --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetInternal.h @@ -0,0 +1,119 @@ +/* $Id: UsbTestServiceGadgetInternal.h $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Interal gadget interfaces. + */ + +/* + * Copyright (C) 2016-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetInternal_h +#define VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> +#include <iprt/types.h> + +#include "UsbTestServiceGadget.h" + +RT_C_DECLS_BEGIN + +/** Pointer to an opaque type dependent gadget host instance data. */ +typedef struct UTSGADGETCLASSINT *PUTSGADGETCLASSINT; +/** Pointer to a gadget host instance pointer. */ +typedef PUTSGADGETCLASSINT *PPUTSGADGETCLASSINT; + +/** + * Gadget class interface. + */ +typedef struct UTSGADGETCLASSIF +{ + /** The gadget class type implemented. */ + UTSGADGETCLASS enmClass; + /** Description. */ + const char *pszDesc; + /** Size of the class specific instance data. */ + size_t cbClass; + + /** + * Initializes the gadget class instance. + * + * @returns IPRT status code. + * @param pClass The interface specific instance data. + * @param paCfg The configuration of the interface. + */ + DECLR3CALLBACKMEMBER(int, pfnInit, (PUTSGADGETCLASSINT pClass, PCUTSGADGETCFGITEM paCfg)); + + /** + * Terminates the gadget class instance. + * + * @returns nothing. + * @param pClass The interface specific instance data. + */ + DECLR3CALLBACKMEMBER(void, pfnTerm, (PUTSGADGETCLASSINT pClass)); + + /** + * Returns the bus ID of the class instance. + * + * @returns Bus ID. + * @param pClass The interface specific instance data. + */ + DECLR3CALLBACKMEMBER(uint32_t, pfnGetBusId, (PUTSGADGETCLASSINT pClass)); + + /** + * Connects the gadget. + * + * @returns IPRT status code. + * @param pClass The interface specific instance data. + */ + DECLR3CALLBACKMEMBER(int, pfnConnect, (PUTSGADGETCLASSINT pClass)); + + /** + * Disconnect the gadget. + * + * @returns IPRT status code. + * @param pClass The interface specific instance data. + */ + DECLR3CALLBACKMEMBER(int, pfnDisconnect, (PUTSGADGETCLASSINT pClass)); + +} UTSGADGETCLASSIF; +/** Pointer to a gadget class callback table. */ +typedef UTSGADGETCLASSIF *PUTSGADGETCLASSIF; +/** Pointer to a const gadget host callback table. */ +typedef const struct UTSGADGETCLASSIF *PCUTSGADGETCLASSIF; + +extern UTSGADGETCLASSIF const g_UtsGadgetClassTest; + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetInternal_h */ + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceInternal.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceInternal.h new file mode 100644 index 00000000..55dc0442 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceInternal.h @@ -0,0 +1,226 @@ +/* $Id: UsbTestServiceInternal.h $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Internal Header. + */ + +/* + * Copyright (C) 2016-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceInternal_h +#define VBOX_INCLUDED_SRC_usb_UsbTestServiceInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/getopt.h> +#include <iprt/stream.h> + +#include "UsbTestServiceProtocol.h" + +RT_C_DECLS_BEGIN + +/** Opaque UTS transport layer specific client data. */ +typedef struct UTSTRANSPORTCLIENT *PUTSTRANSPORTCLIENT; +typedef PUTSTRANSPORTCLIENT *PPUTSTRANSPORTCLIENT; + +/** + * Transport layer descriptor. + */ +typedef struct UTSTRANSPORT +{ + /** The name. */ + char szName[16]; + /** The description. */ + const char *pszDesc; + /** Pointer to an array of options. */ + PCRTGETOPTDEF paOpts; + /** The number of options in the array. */ + size_t cOpts; + + /** + * Print the usage information for this transport layer. + * + * @param pStream The stream to print the usage info to. + * + * @remarks This is only required if TXSTRANSPORT::cOpts is greater than 0. + */ + DECLR3CALLBACKMEMBER(void, pfnUsage, (PRTSTREAM pStream)); + + /** + * Handle an option. + * + * When encountering an options that is not part of the base options, we'll call + * this method for each transport layer until one handles it. + * + * @retval VINF_SUCCESS if handled. + * @retval VERR_TRY_AGAIN if not handled. + * @retval VERR_INVALID_PARAMETER if we should exit with a non-zero status. + * + * @param ch The short option value. + * @param pVal Pointer to the value union. + * + * @remarks This is only required if TXSTRANSPORT::cOpts is greater than 0. + */ + DECLR3CALLBACKMEMBER(int, pfnOption, (int ch, PCRTGETOPTUNION pVal)); + + /** + * Initializes the transport layer. + * + * @returns IPRT status code. On errors, the transport layer shall call + * RTMsgError to display the error details to the user. + */ + DECLR3CALLBACKMEMBER(int, pfnInit, (void)); + + /** + * Terminate the transport layer, closing and freeing resources. + * + * On errors, the transport layer shall call RTMsgError to display the error + * details to the user. + */ + DECLR3CALLBACKMEMBER(void, pfnTerm, (void)); + + /** + * Waits for a new client to connect and returns the client specific data on + * success. + */ + DECLR3CALLBACKMEMBER(int, pfnWaitForConnect, (PPUTSTRANSPORTCLIENT ppClientNew)); + + /** + * Polls for incoming packets. + * + * @returns true if there are pending packets, false if there isn't. + * @param pClient The client to poll for data. + */ + DECLR3CALLBACKMEMBER(bool, pfnPollIn, (PUTSTRANSPORTCLIENT pClient)); + + /** + * Adds any pollable handles to the poll set. + * + * @returns IPRT status code. + * @param hPollSet The poll set to add them to. + * @param pClient The transport client structure. + * @param idStart The handle ID to start at. + */ + DECLR3CALLBACKMEMBER(int, pfnPollSetAdd, (RTPOLLSET hPollSet, PUTSTRANSPORTCLIENT pClient, uint32_t idStart)); + + /** + * Removes the given client frmo the given pollset. + * + * @returns IPRT status code. + * @param hPollSet The poll set to remove from. + * @param pClient The transport client structure. + * @param idStart The handle ID to remove. + */ + DECLR3CALLBACKMEMBER(int, pfnPollSetRemove, (RTPOLLSET hPollSet, PUTSTRANSPORTCLIENT pClient, uint32_t idStart)); + + /** + * Receives an incoming packet. + * + * This will block until the data becomes available or we're interrupted by a + * signal or something. + * + * @returns IPRT status code. On error conditions other than VERR_INTERRUPTED, + * the current operation will be aborted when applicable. When + * interrupted, the transport layer will store the data until the next + * receive call. + * + * @param pClient The transport client structure. + * @param ppPktHdr Where to return the pointer to the packet we've + * read. This is allocated from the heap using + * RTMemAlloc (w/ UTSPKT_ALIGNMENT) and must be + * free by calling RTMemFree. + */ + DECLR3CALLBACKMEMBER(int, pfnRecvPkt, (PUTSTRANSPORTCLIENT pClient, PPUTSPKTHDR ppPktHdr)); + + /** + * Sends an outgoing packet. + * + * This will block until the data has been written. + * + * @returns IPRT status code. + * @retval VERR_INTERRUPTED if interrupted before anything was sent. + * + * @param pClient The transport client structure. + * @param pPktHdr The packet to send. The size is given by + * aligning the size in the header by + * UTSPKT_ALIGNMENT. + */ + DECLR3CALLBACKMEMBER(int, pfnSendPkt, (PUTSTRANSPORTCLIENT pClient, PCUTSPKTHDR pPktHdr)); + + /** + * Sends a babble packet and disconnects the client (if applicable). + * + * @param pClient The transport client structure. + * @param pPktHdr The packet to send. The size is given by + * aligning the size in the header by + * UTSPKT_ALIGNMENT. + * @param cMsSendTimeout The send timeout measured in milliseconds. + */ + DECLR3CALLBACKMEMBER(void, pfnBabble, (PUTSTRANSPORTCLIENT pClient, PCUTSPKTHDR pPktHdr, RTMSINTERVAL cMsSendTimeout)); + + /** + * Notification about a client HOWDY. + * + * @param pClient The transport client structure. + */ + DECLR3CALLBACKMEMBER(void, pfnNotifyHowdy, (PUTSTRANSPORTCLIENT pClient)); + + /** + * Notification about a client BYE. + * + * For connection oriented transport layers, it would be good to disconnect the + * client at this point. + * + * @param pClient The transport client structure. + */ + DECLR3CALLBACKMEMBER(void, pfnNotifyBye, (PUTSTRANSPORTCLIENT pClient)); + + /** + * Notification about a REBOOT or SHUTDOWN. + * + * For connection oriented transport layers, stop listening for and + * accepting at this point. + */ + DECLR3CALLBACKMEMBER(void, pfnNotifyReboot, (void)); + + /** Non-zero end marker. */ + uint32_t u32EndMarker; +} UTSTRANSPORT; +/** Pointer to a const transport layer descriptor. */ +typedef const struct UTSTRANSPORT *PCUTSTRANSPORT; + + +extern UTSTRANSPORT const g_TcpTransport; + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceInternal_h */ + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform-linux.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform-linux.cpp new file mode 100644 index 00000000..4eafc3b0 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform-linux.cpp @@ -0,0 +1,448 @@ +/* $Id: UsbTestServicePlatform-linux.cpp $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Platform + * specific helpers - Linux version. + */ + +/* + * Copyright (C) 2016-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/string.h> + +#include <iprt/linux/sysfs.h> + +#include "UsbTestServicePlatform.h" + + +/********************************************************************************************************************************* +* Constants And Macros, Structures and Typedefs * +*********************************************************************************************************************************/ + +/** Where the dummy_hcd.* and dummy_udc.* entries are stored. */ +#define UTS_PLATFORM_LNX_DUMMY_HCD_PATH "/sys/devices/platform" + +/** + * A USB bus provided by the dummy HCD. + */ +typedef struct UTSPLATFORMLNXDUMMYHCDBUS +{ + /** The bus ID on the host the dummy HCD is serving. */ + uint32_t uBusId; + /** Flag whether this is a super speed bus. */ + bool fSuperSpeed; +} UTSPLATFORMLNXDUMMYHCDBUS; +/** Pointer to a Dummy HCD bus. */ +typedef UTSPLATFORMLNXDUMMYHCDBUS *PUTSPLATFORMLNXDUMMYHCDBUS; + +/** + * A dummy UDC descriptor. + */ +typedef struct UTSPLATFORMLNXDUMMYHCD +{ + /** Index of the dummy hcd entry. */ + uint32_t idxDummyHcd; + /** Name for the dummy HCD. */ + const char *pszHcdName; + /** Name for the accompanying dummy HCD. */ + const char *pszUdcName; + /** Flag whether this HCD is free for use. */ + bool fAvailable; + /** Flag whether this HCD contains a super speed capable bus. */ + bool fSuperSpeed; + /** Number of busses this HCD instance serves. */ + unsigned cBusses; + /** Bus structures the HCD serves.*/ + PUTSPLATFORMLNXDUMMYHCDBUS paBusses; +} UTSPLATFORMLNXDUMMYHCD; +/** Pointer to a dummy HCD entry. */ +typedef UTSPLATFORMLNXDUMMYHCD *PUTSPLATFORMLNXDUMMYHCD; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** Array of dummy HCD entries. */ +static PUTSPLATFORMLNXDUMMYHCD g_paDummyHcd = NULL; +/** Number of Dummy hCD entries in the array. */ +static unsigned g_cDummyHcd = 0; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +/** + * Queries the assigned busses for the given dummy HCD instance. + * + * @returns IPRT status code. + * @param pHcd The dummy HCD bus instance. + * @param pszHcdName The base HCD name. + */ +static int utsPlatformLnxDummyHcdQueryBusses(PUTSPLATFORMLNXDUMMYHCD pHcd, const char *pszHcdName) +{ + int rc = VINF_SUCCESS; + char aszPath[RTPATH_MAX + 1]; + unsigned idxBusCur = 0; + unsigned idxBusMax = 0; + + size_t cchPath = RTStrPrintf(&aszPath[0], RT_ELEMENTS(aszPath), UTS_PLATFORM_LNX_DUMMY_HCD_PATH "/%s.%u/usb*", + pszHcdName, pHcd->idxDummyHcd); + if (cchPath == RT_ELEMENTS(aszPath)) + return VERR_BUFFER_OVERFLOW; + + RTDIR hDir = NULL; + rc = RTDirOpenFiltered(&hDir, aszPath, RTDIRFILTER_WINNT, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + do + { + RTDIRENTRY DirFolderContent; + rc = RTDirRead(hDir, &DirFolderContent, NULL); + if (RT_SUCCESS(rc)) + { + uint32_t uBusId = 0; + + /* Extract the bus number - it is after "usb", i.e. "usb9" indicates a bus ID of 9. */ + rc = RTStrToUInt32Ex(&DirFolderContent.szName[3], NULL, 10, &uBusId); + if (RT_SUCCESS(rc)) + { + /* Check whether this is a super speed bus. */ + int64_t iSpeed = 0; + bool fSuperSpeed = false; + rc = RTLinuxSysFsReadIntFile(10, &iSpeed, UTS_PLATFORM_LNX_DUMMY_HCD_PATH "/%s.%u/%s/speed", + pszHcdName, pHcd->idxDummyHcd, DirFolderContent.szName); + if ( RT_SUCCESS(rc) + && (iSpeed == 5000 || iSpeed == 10000)) + { + fSuperSpeed = true; + pHcd->fSuperSpeed = true; + } + + /* Add to array of available busses for this HCD. */ + if (idxBusCur == idxBusMax) + { + size_t cbNew = (idxBusMax + 10) * sizeof(UTSPLATFORMLNXDUMMYHCDBUS); + PUTSPLATFORMLNXDUMMYHCDBUS pNew = (PUTSPLATFORMLNXDUMMYHCDBUS)RTMemRealloc(pHcd->paBusses, cbNew); + if (pNew) + { + idxBusMax += 10; + pHcd->paBusses = pNew; + } + } + + if (idxBusCur < idxBusMax) + { + pHcd->paBusses[idxBusCur].uBusId = uBusId; + pHcd->paBusses[idxBusCur].fSuperSpeed = fSuperSpeed; + idxBusCur++; + } + else + rc = VERR_NO_MEMORY; + } + } + } while (RT_SUCCESS(rc)); + + pHcd->cBusses = idxBusCur; + + if (rc == VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + + RTDirClose(hDir); + } + + return rc; +} + + +/** + * Scans all available HCDs with the given name. + * + * @returns IPRT status code. + * @param pszHcdName The base HCD name. + * @param pszUdcName The base UDC name. + */ +static int utsPlatformLnxHcdScanByName(const char *pszHcdName, const char *pszUdcName) +{ + char aszPath[RTPATH_MAX + 1]; + size_t cchPath = RTStrPrintf(&aszPath[0], RT_ELEMENTS(aszPath), + UTS_PLATFORM_LNX_DUMMY_HCD_PATH "/%s.*", pszHcdName); + if (cchPath == RT_ELEMENTS(aszPath)) + return VERR_BUFFER_OVERFLOW; + + /* Enumerate the available HCD and their bus numbers. */ + RTDIR hDir = NULL; + int rc = RTDirOpenFiltered(&hDir, aszPath, RTDIRFILTER_WINNT, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + unsigned idxHcdCur = g_cDummyHcd; + unsigned idxHcdMax = g_cDummyHcd; + + do + { + RTDIRENTRY DirFolderContent; + rc = RTDirRead(hDir, &DirFolderContent, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Get the HCD index and assigned bus number form the sysfs entries, + * Any error here is silently ignored and results in the HCD not being + * added to the list of available controllers. + */ + const char *pszIdx = RTStrStr(DirFolderContent.szName, "."); + if (pszIdx) + { + /* Skip the separator and convert number to index. */ + pszIdx++; + + uint32_t idxHcd = 0; + rc = RTStrToUInt32Ex(pszIdx, NULL, 10, &idxHcd); + if (RT_SUCCESS(rc)) + { + /* Add to array of available HCDs. */ + if (idxHcdCur == idxHcdMax) + { + size_t cbNew = (idxHcdMax + 10) * sizeof(UTSPLATFORMLNXDUMMYHCD); + PUTSPLATFORMLNXDUMMYHCD pNew = (PUTSPLATFORMLNXDUMMYHCD)RTMemRealloc(g_paDummyHcd, cbNew); + if (pNew) + { + idxHcdMax += 10; + g_paDummyHcd = pNew; + } + } + + if (idxHcdCur < idxHcdMax) + { + g_paDummyHcd[idxHcdCur].idxDummyHcd = idxHcd; + g_paDummyHcd[idxHcdCur].pszHcdName = pszHcdName; + g_paDummyHcd[idxHcdCur].pszUdcName = pszUdcName; + g_paDummyHcd[idxHcdCur].fAvailable = true; + g_paDummyHcd[idxHcdCur].fSuperSpeed = false; + g_paDummyHcd[idxHcdCur].cBusses = 0; + g_paDummyHcd[idxHcdCur].paBusses = NULL; + rc = utsPlatformLnxDummyHcdQueryBusses(&g_paDummyHcd[idxHcdCur], pszHcdName); + if (RT_SUCCESS(rc)) + idxHcdCur++; + } + else + rc = VERR_NO_MEMORY; + } + } + } + } while (RT_SUCCESS(rc)); + + g_cDummyHcd = idxHcdCur; + + if (rc == VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + + RTDirClose(hDir); + } + + return rc; +} + +DECLHIDDEN(int) utsPlatformInit(void) +{ + /* Load the modules required for setting up USB/IP testing. */ + int rc = utsPlatformModuleLoad("libcomposite", NULL, 0); + if (RT_SUCCESS(rc)) + { + const char *apszArg[] = { "num=20" }; /** @todo Make configurable from config. */ + rc = utsPlatformModuleLoad("dummy_hcd", &apszArg[0], RT_ELEMENTS(apszArg)); + if (RT_SUCCESS(rc)) + rc = utsPlatformModuleLoad("dummy_hcd_ss", &apszArg[0], RT_ELEMENTS(apszArg)); + if (RT_SUCCESS(rc)) + rc = utsPlatformLnxHcdScanByName("dummy_hcd", "dummy_udc"); + if (RT_SUCCESS(rc)) + rc = utsPlatformLnxHcdScanByName("dummy_hcd_ss", "dummy_udc_ss"); + } + + return rc; +} + + +DECLHIDDEN(void) utsPlatformTerm(void) +{ + /* Unload dummy HCD. */ + utsPlatformModuleUnload("dummy_hcd"); + utsPlatformModuleUnload("dummy_hcd_ss"); + + RTMemFree(g_paDummyHcd); +} + + +DECLHIDDEN(int) utsPlatformModuleLoad(const char *pszModule, const char **papszArgv, + unsigned cArgv) +{ + RTPROCESS hProcModprobe = NIL_RTPROCESS; + const char **papszArgs = (const char **)RTMemAllocZ((3 + cArgv) * sizeof(const char *)); + if (RT_UNLIKELY(!papszArgs)) + return VERR_NO_MEMORY; + + papszArgs[0] = "modprobe"; + papszArgs[1] = pszModule; + + unsigned idx; + for (idx = 0; idx < cArgv; idx++) + papszArgs[2+idx] = papszArgv[idx]; + papszArgs[2+idx] = NULL; + + int rc = RTProcCreate("modprobe", papszArgs, RTENV_DEFAULT, RTPROC_FLAGS_SEARCH_PATH, &hProcModprobe); + if (RT_SUCCESS(rc)) + { + RTPROCSTATUS ProcSts; + rc = RTProcWait(hProcModprobe, RTPROCWAIT_FLAGS_BLOCK, &ProcSts); + if (RT_SUCCESS(rc)) + { + /* Evaluate the process status. */ + if ( ProcSts.enmReason != RTPROCEXITREASON_NORMAL + || ProcSts.iStatus != 0) + rc = VERR_UNRESOLVED_ERROR; /** @todo Log and give finer grained status code. */ + } + } + + RTMemFree(papszArgs); + return rc; +} + + +DECLHIDDEN(int) utsPlatformModuleUnload(const char *pszModule) +{ + RTPROCESS hProcModprobe = NIL_RTPROCESS; + const char *apszArgv[3]; + + apszArgv[0] = "rmmod"; + apszArgv[1] = pszModule; + apszArgv[2] = NULL; + + int rc = RTProcCreate("rmmod", apszArgv, RTENV_DEFAULT, RTPROC_FLAGS_SEARCH_PATH, &hProcModprobe); + if (RT_SUCCESS(rc)) + { + RTPROCSTATUS ProcSts; + rc = RTProcWait(hProcModprobe, RTPROCWAIT_FLAGS_BLOCK, &ProcSts); + if (RT_SUCCESS(rc)) + { + /* Evaluate the process status. */ + if ( ProcSts.enmReason != RTPROCEXITREASON_NORMAL + || ProcSts.iStatus != 0) + rc = VERR_UNRESOLVED_ERROR; /** @todo Log and give finer grained status code. */ + } + } + + return rc; +} + + +DECLHIDDEN(int) utsPlatformLnxAcquireUDC(bool fSuperSpeed, char **ppszUdc, uint32_t *puBusId) +{ + int rc = VERR_NOT_FOUND; + + for (unsigned i = 0; i < g_cDummyHcd; i++) + { + PUTSPLATFORMLNXDUMMYHCD pHcd = &g_paDummyHcd[i]; + + /* + * We can't use a super speed capable UDC for gadgets with lower speeds + * because they hardcode the maximum speed to SuperSpeed most of the time + * which will make it unusable for lower speeds. + */ + if ( pHcd->fAvailable + && pHcd->fSuperSpeed == fSuperSpeed) + { + /* Check all assigned busses for a speed match. */ + for (unsigned idxBus = 0; idxBus < pHcd->cBusses; idxBus++) + { + if (pHcd->paBusses[idxBus].fSuperSpeed == fSuperSpeed) + { + rc = VINF_SUCCESS; + int cbRet = RTStrAPrintf(ppszUdc, "%s.%u", pHcd->pszUdcName, pHcd->idxDummyHcd); + if (cbRet == -1) + rc = VERR_NO_STR_MEMORY; + *puBusId = pHcd->paBusses[idxBus].uBusId; + pHcd->fAvailable = false; + break; + } + } + + if (rc != VERR_NOT_FOUND) + break; + } + } + + return rc; +} + + +DECLHIDDEN(int) utsPlatformLnxReleaseUDC(const char *pszUdc) +{ + int rc = VERR_INVALID_PARAMETER; + const char *pszIdx = RTStrStr(pszUdc, "."); + if (pszIdx) + { + size_t cchUdcName = pszIdx - pszUdc; + pszIdx++; + uint32_t idxHcd = 0; + rc = RTStrToUInt32Ex(pszIdx, NULL, 10, &idxHcd); + if (RT_SUCCESS(rc)) + { + rc = VERR_NOT_FOUND; + + for (unsigned i = 0; i < g_cDummyHcd; i++) + { + if ( g_paDummyHcd[i].idxDummyHcd == idxHcd + && !RTStrNCmp(g_paDummyHcd[i].pszUdcName, pszUdc, cchUdcName)) + { + AssertReturn(!g_paDummyHcd[i].fAvailable, VERR_INVALID_PARAMETER); + g_paDummyHcd[i].fAvailable = true; + rc = VINF_SUCCESS; + break; + } + } + } + } + + return rc; +} + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform.h b/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform.h new file mode 100644 index 00000000..5c976adc --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform.h @@ -0,0 +1,105 @@ +/* $Id: UsbTestServicePlatform.h $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Platform specific helpers. + */ + +/* + * Copyright (C) 2016-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServicePlatform_h +#define VBOX_INCLUDED_SRC_usb_UsbTestServicePlatform_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> +#include <iprt/types.h> + +RT_C_DECLS_BEGIN + +/** + * Initializes the platform specific structures for UTS. + * + * @returns IPRT status code. + */ +DECLHIDDEN(int) utsPlatformInit(void); + +/** + * Frees all platform specific structures for UTS. + */ +DECLHIDDEN(void) utsPlatformTerm(void); + +/** + * Loads the specified kernel module on the platform. + * + * @returns IPRT status code. + * @param pszModule The module to load. + * @param papszArgv Array of arguments to pass to the module. + * @param cArgv Number of argument array entries. + */ +DECLHIDDEN(int) utsPlatformModuleLoad(const char *pszModule, const char **papszArgv, + unsigned cArgv); + +/** + * Unloads the specified kernel module on the platform. + * + * @returns IPRT status code. + * @param pszModule The module to unload. + */ +DECLHIDDEN(int) utsPlatformModuleUnload(const char *pszModule); + +#ifdef RT_OS_LINUX + +/** + * Acquires a free UDC to attach a gadget to. + * + * @returns IPRT status code. + * @param fSuperSpeed Flag whether a super speed bus is required. + * @param ppszUdc Where to store the pointer to the name of the UDC on success. + * Free with RTStrFree(). + * @param puBusId Where to store the bus ID the UDC is attached to on the host side. + */ +DECLHIDDEN(int) utsPlatformLnxAcquireUDC(bool fSuperSpeed, char **ppszUdc, uint32_t *puBusId); + +/** + * Releases the given UDC for other use. + * + * @returns IPRT status code. + * @param pszUdc The UDC to release. + */ +DECLHIDDEN(int) utsPlatformLnxReleaseUDC(const char *pszUdc); + +#endif + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServicePlatform_h */ + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.cpp new file mode 100644 index 00000000..a4b9b981 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.cpp @@ -0,0 +1,124 @@ +/* $Id: UsbTestServiceProtocol.cpp $ */ +/** @file + * UsbTestService - Remote USB test configuration and execution server, Protocol helpers. + */ + +/* + * Copyright (C) 2016-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/asm.h> +#include <iprt/cdefs.h> + +#include "UsbTestServiceProtocol.h" + + + +/** + * Converts a UTS packet header from host to network byte order. + * + * @returns nothing. + * @param pPktHdr The packet header to convert. + */ +DECLINLINE(void) utsProtocolPktHdrH2N(PUTSPKTHDR pPktHdr) +{ + pPktHdr->cb = RT_H2N_U32(pPktHdr->cb); + pPktHdr->uCrc32 = RT_H2N_U32(pPktHdr->uCrc32); +} + + +/** + * Converts a UTS packet header from network to host byte order. + * + * @returns nothing. + * @param pPktHdr The packet header to convert. + */ +DECLINLINE(void) utsProtocolPktHdrN2H(PUTSPKTHDR pPktHdr) +{ + pPktHdr->cb = RT_N2H_U32(pPktHdr->cb); + pPktHdr->uCrc32 = RT_N2H_U32(pPktHdr->uCrc32); +} + + +/** + * Converts a UTS status header from host to network byte order. + * + * @returns nothing. + * @param pPktHdr The packet header to convert. + */ +DECLINLINE(void) utsProtocolStsHdrH2N(PUTSPKTSTS pPktHdr) +{ + utsProtocolPktHdrH2N(&pPktHdr->Hdr); + pPktHdr->rcReq = RT_H2N_U32(pPktHdr->rcReq); + pPktHdr->cchStsMsg = RT_H2N_U32(pPktHdr->cchStsMsg); +} + + +/** + * Converts a UTS status header from network to host byte order. + * + * @returns nothing. + * @param pPktHdr The packet header to convert. + */ +DECLINLINE(void) utsProtocolStsHdrN2H(PUTSPKTSTS pPktHdr) +{ + utsProtocolPktHdrN2H(&pPktHdr->Hdr); + pPktHdr->rcReq = RT_N2H_U32(pPktHdr->rcReq); + pPktHdr->cchStsMsg = RT_N2H_U32(pPktHdr->cchStsMsg); +} + + +DECLHIDDEN(void) utsProtocolReqH2N(PUTSPKTHDR pPktHdr) +{ + utsProtocolPktHdrH2N(pPktHdr); +} + + +DECLHIDDEN(void) utsProtocolReqN2H(PUTSPKTHDR pPktHdr) +{ + RT_NOREF1(pPktHdr); +} + + +DECLHIDDEN(void) utsProtocolRepH2N(PUTSPKTSTS pPktHdr) +{ + RT_NOREF1(pPktHdr); +} + + +DECLHIDDEN(void) utsProtocolRepN2H(PUTSPKTSTS pPktHdr) +{ + RT_NOREF1(pPktHdr); +} diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.h new file mode 100644 index 00000000..95b6d499 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.h @@ -0,0 +1,377 @@ +/* $Id: UsbTestServiceProtocol.h $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Protocol Header. + */ + +/* + * Copyright (C) 2016-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceProtocol_h +#define VBOX_INCLUDED_SRC_usb_UsbTestServiceProtocol_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> + +RT_C_DECLS_BEGIN + +/** + * Common Packet header (for requests and replies). + */ +typedef struct UTSPKTHDR +{ + /** The unpadded packet length. This include this header. */ + uint32_t cb; + /** The CRC-32 for the packet starting from the opcode field. 0 if the packet + * hasn't been CRCed. */ + uint32_t uCrc32; + /** Packet opcode, an unterminated ASCII string. */ + uint8_t achOpcode[8]; +} UTSPKTHDR; +AssertCompileSize(UTSPKTHDR, 16); +/** Pointer to a packet header. */ +typedef UTSPKTHDR *PUTSPKTHDR; +/** Pointer to a packet header. */ +typedef UTSPKTHDR const *PCUTSPKTHDR; +/** Pointer to a packet header pointer. */ +typedef PUTSPKTHDR *PPUTSPKTHDR; + +/** Packet alignment. */ +#define UTSPKT_ALIGNMENT 16 +/** Max packet size. */ +#define UTSPKT_MAX_SIZE _256K + +/** + * Status packet. + */ +typedef struct UTSPKTSTS +{ + /** Embedded common packet header. */ + UTSPKTHDR Hdr; + /** The IPRT status code of the request. */ + int32_t rcReq; + /** Size of the optional status message following this structure - + * only for errors. */ + uint32_t cchStsMsg; + /** Padding - reserved. */ + uint8_t au8Padding[8]; +} UTSPKTSTS; +AssertCompileSizeAlignment(UTSPKTSTS, UTSPKT_ALIGNMENT); +/** Pointer to a status packet header. */ +typedef UTSPKTSTS *PUTSPKTSTS; + +#define UTSPKT_OPCODE_HOWDY "HOWDY " + +/** 32bit protocol version consisting of a 16bit major and 16bit minor part. */ +#define UTS_PROTOCOL_VS (UTS_PROTOCOL_VS_MAJOR | UTS_PROTOCOL_VS_MINOR) +/** The major version part of the protocol version. */ +#define UTS_PROTOCOL_VS_MAJOR (1 << 16) +/** The minor version part of the protocol version. */ +#define UTS_PROTOCOL_VS_MINOR (0) + +/** + * The HOWDY request structure. + */ +typedef struct UTSPKTREQHOWDY +{ + /** Embedded packet header. */ + UTSPKTHDR Hdr; + /** Version of the protocol the client wants to use. */ + uint32_t uVersion; + /** Mask of USB device connections the client wants to use. */ + uint32_t fUsbConn; + /** The number of characters for the hostname. */ + uint32_t cchHostname; + /** The client host name as terminated ASCII string. */ + char achHostname[68]; +} UTSPKTREQHOWDY; +AssertCompileSizeAlignment(UTSPKTREQHOWDY, UTSPKT_ALIGNMENT); +/** Pointer to a HOWDY request structure. */ +typedef UTSPKTREQHOWDY *PUTSPKTREQHOWDY; + +/** + * The HOWDY reply structure. + */ +typedef struct UTSPKTREPHOWDY +{ + /** Status packet. */ + UTSPKTSTS Sts; + /** Version to use for the established connection. */ + uint32_t uVersion; + /** Mask of supported USB device connections for this connection. */ + uint32_t fUsbConn; + /** Port number the USB/IP server is listening on if + * the client requested USB/IP support and the server can + * deliver it. */ + uint32_t uUsbIpPort; + /** Maximum number of devices supported over USB/IP + * at the same time. */ + uint32_t cUsbIpDevices; + /** Maximum number of physical devices supported for this client + * if a physical connection is present. */ + uint32_t cPhysicalDevices; + /** Padding - reserved. */ + uint8_t au8Padding[12]; +} UTSPKTREPHOWDY; +AssertCompileSizeAlignment(UTSPKTREPHOWDY, UTSPKT_ALIGNMENT); +/** Pointer to a HOWDY reply structure. */ +typedef UTSPKTREPHOWDY *PUTSPKTREPHOWDY; + +/** Connections over USB/IP are supported. */ +#define UTSPKT_HOWDY_CONN_F_USBIP RT_BIT_32(0) +/** The server has a physical connection available to the client + * which can be used for testing. */ +#define UTSPKT_HOWDY_CONN_F_PHYSICAL RT_BIT_32(1) + + +#define UTSPKT_OPCODE_BYE "BYE " + +/* No additional structures for BYE. */ + +#define UTSPKT_OPCODE_GADGET_CREATE "GDGTCRT " + +/** + * The GADGET CREATE request structure. + */ +typedef struct UTSPKTREQGDGTCTOR +{ + /** Embedded packet header. */ + UTSPKTHDR Hdr; + /** Gadget type. */ + uint32_t u32GdgtType; + /** Access methods. */ + uint32_t u32GdgtAccess; + /** Number of config items - following this structure. */ + uint32_t u32CfgItems; + /** Reserved. */ + uint32_t u32Rsvd0; +} UTSPKTREQGDGTCTOR; +AssertCompileSizeAlignment(UTSPKTREQGDGTCTOR, UTSPKT_ALIGNMENT); +/** Pointer to a GADGET CREATE structure. */ +typedef UTSPKTREQGDGTCTOR *PUTSPKTREQGDGTCTOR; + +/** Gadget type - Test device. */ +#define UTSPKT_GDGT_CREATE_TYPE_TEST UINT32_C(0x1) + +/** Gadget acess method - USB/IP. */ +#define UTSPKT_GDGT_CREATE_ACCESS_USBIP UINT32_C(0x1) + +/** + * Configuration item. + */ +typedef struct UTSPKTREQGDGTCTORCFGITEM +{ + /** Size of the key incuding termination in bytes. */ + uint32_t u32KeySize; + /** Item type. */ + uint32_t u32Type; + /** Size of the value string including termination in bytes. */ + uint32_t u32ValSize; + /** Reserved. */ + uint32_t u32Rsvd0; +} UTSPKTREQGDGTCTORCFGITEM; +AssertCompileSizeAlignment(UTSPKTREQGDGTCTORCFGITEM, UTSPKT_ALIGNMENT); +/** Pointer to a configuration item. */ +typedef UTSPKTREQGDGTCTORCFGITEM *PUTSPKTREQGDGTCTORCFGITEM; + +/** Boolean configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_BOOLEAN UINT32_C(1) +/** String configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_STRING UINT32_C(2) +/** Unsigned 8-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_UINT8 UINT32_C(3) +/** Unsigned 16-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_UINT16 UINT32_C(4) +/** Unsigned 32-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_UINT32 UINT32_C(5) +/** Unsigned 64-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_UINT64 UINT32_C(6) +/** Signed 8-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_INT8 UINT32_C(7) +/** Signed 16-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_INT16 UINT32_C(8) +/** Signed 32-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_INT32 UINT32_C(9) +/** Signed 64-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_INT64 UINT32_C(10) + +/** + * The GADGET CREATE reply structure. + */ +typedef struct UTSPKTREPGDGTCTOR +{ + /** Status packet. */ + UTSPKTSTS Sts; + /** The gadget ID on success. */ + uint32_t idGadget; + /** Bus ID the gadget is attached to */ + uint32_t u32BusId; + /** Device ID of the gadget on the bus. */ + uint32_t u32DevId; + /** Padding - reserved. */ + uint8_t au8Padding[4]; +} UTSPKTREPGDGTCTOR; +AssertCompileSizeAlignment(UTSPKTREPGDGTCTOR, UTSPKT_ALIGNMENT); +/** Pointer to a GADGET CREATE structure. */ +typedef UTSPKTREPGDGTCTOR *PUTSPKTREPGDGTCTOR; + + +#define UTSPKT_OPCODE_GADGET_DESTROY "GDGTDTOR" + +/** + * The GADGET DESTROY request structure. + */ +typedef struct UTSPKTREQGDGTDTOR +{ + /** Embedded packet header. */ + UTSPKTHDR Hdr; + /** Gadget ID as returned from the GADGET CREATE request on success. */ + uint32_t idGadget; + /** Padding - reserved. */ + uint8_t au8Padding[12]; +} UTSPKTREQGDGTDTOR; +AssertCompileSizeAlignment(UTSPKTREQGDGTDTOR, UTSPKT_ALIGNMENT); +/** Pointer to a GADGET DESTROY structure. */ +typedef UTSPKTREQGDGTDTOR *PUTSPKTREQGDGTDTOR; + +/* No additional structure for the reply (just standard STATUS packet). */ + +#define UTSPKT_OPCODE_GADGET_CONNECT "GDGTCNCT" + +/** + * The GADGET CONNECT request structure. + */ +typedef struct UTSPKTREQGDGTCNCT +{ + /** Embedded packet header. */ + UTSPKTHDR Hdr; + /** Gadget ID as returned from the GADGET CREATE request on success. */ + uint32_t idGadget; + /** Padding - reserved. */ + uint8_t au8Padding[12]; +} UTSPKTREQGDGTCNCT; +AssertCompileSizeAlignment(UTSPKTREQGDGTCNCT, UTSPKT_ALIGNMENT); +/** Pointer to a GADGET CONNECT request structure. */ +typedef UTSPKTREQGDGTCNCT *PUTSPKTREQGDGTCNCT; + +/* No additional structure for the reply (just standard STATUS packet). */ + +#define UTSPKT_OPCODE_GADGET_DISCONNECT "GDGTDCNT" + +/** + * The GADGET DISCONNECT request structure. + */ +typedef struct UTSPKTREQGDGTDCNT +{ + /** Embedded packet header. */ + UTSPKTHDR Hdr; + /** Gadget ID as returned from the GADGET CREATE request on success. */ + uint32_t idGadget; + /** Padding - reserved. */ + uint8_t au8Padding[12]; +} UTSPKTREQGDGTDCNT; +AssertCompileSizeAlignment(UTSPKTREQGDGTDCNT, UTSPKT_ALIGNMENT); +/** Pointer to a GADGET CONNECT request structure. */ +typedef UTSPKTREQGDGTDCNT *PUTSPKTREQGDGTDCNT; + +/* No additional structure for the reply (just standard STATUS packet). */ + +/** + * Checks if the two opcodes match. + * + * @returns true on match, false on mismatch. + * @param pPktHdr The packet header. + * @param pszOpcode2 The opcode we're comparing with. Does not have + * to be the whole 8 chars long. + */ +DECLINLINE(bool) utsIsSameOpcode(PCUTSPKTHDR pPktHdr, const char *pszOpcode2) +{ + if (pPktHdr->achOpcode[0] != pszOpcode2[0]) + return false; + if (pPktHdr->achOpcode[1] != pszOpcode2[1]) + return false; + + unsigned i = 2; + while ( i < RT_SIZEOFMEMB(UTSPKTHDR, achOpcode) + && pszOpcode2[i] != '\0') + { + if (pPktHdr->achOpcode[i] != pszOpcode2[i]) + break; + i++; + } + + if ( i < RT_SIZEOFMEMB(UTSPKTHDR, achOpcode) + && pszOpcode2[i] == '\0') + { + while ( i < RT_SIZEOFMEMB(UTSPKTHDR, achOpcode) + && pPktHdr->achOpcode[i] == ' ') + i++; + } + + return i == RT_SIZEOFMEMB(UTSPKTHDR, achOpcode); +} + +/** + * Converts a UTS request packet from host to network byte ordering. + * + * @returns nothing. + * @param pPktHdr The packet to convert. + */ +DECLHIDDEN(void) utsProtocolReqH2N(PUTSPKTHDR pPktHdr); + +/** + * Converts a UTS request packet from network to host byte ordering. + * + * @returns nothing. + * @param pPktHdr The packet to convert. + */ +DECLHIDDEN(void) utsProtocolReqN2H(PUTSPKTHDR pPktHdr); + +/** + * Converts a UTS reply packet from host to network byte ordering. + * + * @returns nothing. + * @param pPktHdr The packet to convert. + */ +DECLHIDDEN(void) utsProtocolRepH2N(PUTSPKTHDR pPktHdr); + +/** + * Converts a UTS reply packet from network to host byte ordering. + * + * @returns nothing. + * @param pPktHdr The packet to convert. + */ +DECLHIDDEN(void) utsProtocolRepN2H(PUTSPKTHDR pPktHdr); + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceProtocol_h */ diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceTcp.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceTcp.cpp new file mode 100644 index 00000000..7a43ca6c --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceTcp.cpp @@ -0,0 +1,512 @@ +/* $Id: UsbTestServiceTcp.cpp $ */ +/** @file + * UsbTestService - Remote USB test configuration and execution server, TCP/IP Transport Layer. + */ + +/* + * Copyright (C) 2010-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/poll.h> +#include <iprt/string.h> +#include <iprt/tcp.h> +#include <iprt/thread.h> +#include <iprt/time.h> + +#include "UsbTestServiceInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The default server port. */ +#define UTS_TCP_DEF_BIND_PORT 6042 +/** The default server bind address. */ +#define UTS_TCP_DEF_BIND_ADDRESS "" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * TCP specific client data. + */ +typedef struct UTSTRANSPORTCLIENT +{ + /** Socket of the current client. */ + RTSOCKET hTcpClient; + /** The size of the stashed data. */ + size_t cbTcpStashed; + /** The size of the stashed data allocation. */ + size_t cbTcpStashedAlloced; + /** The stashed data. */ + uint8_t *pbTcpStashed; +} UTSTRANSPORTCLIENT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** @name TCP Parameters + * @{ */ +/** The addresses to bind to. Empty string means any. */ +static char g_szTcpBindAddr[256] = UTS_TCP_DEF_BIND_ADDRESS; +/** The TCP port to listen to. */ +static uint32_t g_uTcpBindPort = UTS_TCP_DEF_BIND_PORT; +/** @} */ + +/** Pointer to the TCP server instance. */ +static PRTTCPSERVER g_pTcpServer = NULL; +#if 0 /* unused */ +/** Stop connecting attempts when set. */ +static bool g_fTcpStopConnecting = false; +#endif + + + +/** + * Disconnects the current client and frees all stashed data. + */ +static void utsTcpDisconnectClient(PUTSTRANSPORTCLIENT pClient) +{ + if (pClient->hTcpClient != NIL_RTSOCKET) + { + int rc = RTTcpServerDisconnectClient2(pClient->hTcpClient); + pClient->hTcpClient = NIL_RTSOCKET; + AssertRCSuccess(rc); + } + + if (pClient->pbTcpStashed) + { + RTMemFree(pClient->pbTcpStashed); + pClient->pbTcpStashed = NULL; + } +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnWaitForConnect} + */ +static DECLCALLBACK(int) utsTcpWaitForConnect(PPUTSTRANSPORTCLIENT ppClientNew) +{ + int rc; + RTSOCKET hClientNew; + + rc = RTTcpServerListen2(g_pTcpServer, &hClientNew); + Log(("utsTcpWaitForConnect: RTTcpServerListen2 -> %Rrc\n", rc)); + + if (RT_SUCCESS(rc)) + { + PUTSTRANSPORTCLIENT pClient = (PUTSTRANSPORTCLIENT)RTMemAllocZ(sizeof(UTSTRANSPORTCLIENT)); + if (RT_LIKELY(pClient)) + { + pClient->hTcpClient = hClientNew; + pClient->cbTcpStashed = 0; + pClient->cbTcpStashedAlloced = 0; + pClient->pbTcpStashed = NULL; + *ppClientNew = pClient; + } + else + { + RTTcpServerDisconnectClient2(hClientNew); + rc = VERR_NO_MEMORY; + } + } + + return rc; +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnNotifyReboot} + */ +static DECLCALLBACK(void) utsTcpNotifyReboot(void) +{ + Log(("utsTcpNotifyReboot: RTTcpServerDestroy(%p)\n", g_pTcpServer)); + if (g_pTcpServer) + { + int rc = RTTcpServerDestroy(g_pTcpServer); + if (RT_FAILURE(rc)) + RTMsgInfo("RTTcpServerDestroy failed in utsTcpNotifyReboot: %Rrc", rc); + g_pTcpServer = NULL; + } +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnNotifyBye} + */ +static DECLCALLBACK(void) utsTcpNotifyBye(PUTSTRANSPORTCLIENT pClient) +{ + Log(("utsTcpNotifyBye: utsTcpDisconnectClient %RTsock\n", pClient->hTcpClient)); + utsTcpDisconnectClient(pClient); + RTMemFree(pClient); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnNotifyHowdy} + */ +static DECLCALLBACK(void) utsTcpNotifyHowdy(PUTSTRANSPORTCLIENT pClient) +{ + /* nothing to do here */ + RT_NOREF1(pClient); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnBabble} + */ +static DECLCALLBACK(void) utsTcpBabble(PUTSTRANSPORTCLIENT pClient, PCUTSPKTHDR pPktHdr, RTMSINTERVAL cMsSendTimeout) +{ + /* + * Try send the babble reply. + */ + NOREF(cMsSendTimeout); /** @todo implement the timeout here; non-blocking write + select-on-write. */ + int rc; + size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, UTSPKT_ALIGNMENT); + do rc = RTTcpWrite(pClient->hTcpClient, pPktHdr, cbToSend); + while (rc == VERR_INTERRUPTED); + + /* + * Disconnect the client. + */ + Log(("utsTcpBabble: utsTcpDisconnectClient(%RTsock) (RTTcpWrite rc=%Rrc)\n", pClient->hTcpClient, rc)); + utsTcpDisconnectClient(pClient); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnSendPkt} + */ +static DECLCALLBACK(int) utsTcpSendPkt(PUTSTRANSPORTCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + Assert(pPktHdr->cb >= sizeof(UTSPKTHDR)); + + /* + * Write it. + */ + size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, UTSPKT_ALIGNMENT); + int rc = RTTcpWrite(pClient->hTcpClient, pPktHdr, cbToSend); + if ( RT_FAILURE(rc) + && rc != VERR_INTERRUPTED) + { + /* assume fatal connection error. */ + Log(("RTTcpWrite -> %Rrc -> utsTcpDisconnectClient(%RTsock)\n", rc, pClient->hTcpClient)); + utsTcpDisconnectClient(pClient); + } + + return rc; +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnRecvPkt} + */ +static DECLCALLBACK(int) utsTcpRecvPkt(PUTSTRANSPORTCLIENT pClient, PPUTSPKTHDR ppPktHdr) +{ + int rc = VINF_SUCCESS; + *ppPktHdr = NULL; + + /* + * Read state. + */ + size_t offData = 0; + size_t cbData = 0; + size_t cbDataAlloced; + uint8_t *pbData = NULL; + + /* + * Any stashed data? + */ + if (pClient->cbTcpStashedAlloced) + { + offData = pClient->cbTcpStashed; + cbDataAlloced = pClient->cbTcpStashedAlloced; + pbData = pClient->pbTcpStashed; + + pClient->cbTcpStashed = 0; + pClient->cbTcpStashedAlloced = 0; + pClient->pbTcpStashed = NULL; + } + else + { + cbDataAlloced = RT_ALIGN_Z(64, UTSPKT_ALIGNMENT); + pbData = (uint8_t *)RTMemAlloc(cbDataAlloced); + if (!pbData) + return VERR_NO_MEMORY; + } + + /* + * Read and valid the length. + */ + while (offData < sizeof(uint32_t)) + { + size_t cbRead; + rc = RTTcpRead(pClient->hTcpClient, pbData + offData, sizeof(uint32_t) - offData, &cbRead); + if (RT_FAILURE(rc)) + break; + if (cbRead == 0) + { + Log(("utsTcpRecvPkt: RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#1)\n", rc)); + rc = VERR_NET_NOT_CONNECTED; + break; + } + offData += cbRead; + } + if (RT_SUCCESS(rc)) + { + ASMCompilerBarrier(); /* paranoia^3 */ + cbData = *(uint32_t volatile *)pbData; + if (cbData >= sizeof(UTSPKTHDR) && cbData <= UTSPKT_MAX_SIZE) + { + /* + * Align the length and reallocate the return packet it necessary. + */ + cbData = RT_ALIGN_Z(cbData, UTSPKT_ALIGNMENT); + if (cbData > cbDataAlloced) + { + void *pvNew = RTMemRealloc(pbData, cbData); + if (pvNew) + { + pbData = (uint8_t *)pvNew; + cbDataAlloced = cbData; + } + else + rc = VERR_NO_MEMORY; + } + if (RT_SUCCESS(rc)) + { + /* + * Read the remainder of the data. + */ + while (offData < cbData) + { + size_t cbRead; + rc = RTTcpRead(pClient->hTcpClient, pbData + offData, cbData - offData, &cbRead); + if (RT_FAILURE(rc)) + break; + if (cbRead == 0) + { + Log(("utsTcpRecvPkt: RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#2)\n", rc)); + rc = VERR_NET_NOT_CONNECTED; + break; + } + offData += cbRead; + } + } + } + else + rc = VERR_NET_PROTOCOL_ERROR; + } + if (RT_SUCCESS(rc)) + *ppPktHdr = (PUTSPKTHDR)pbData; + else + { + /* + * Deal with errors. + */ + if (rc == VERR_INTERRUPTED) + { + /* stash it away for the next call. */ + pClient->cbTcpStashed = cbData; + pClient->cbTcpStashedAlloced = cbDataAlloced; + pClient->pbTcpStashed = pbData; + } + else + { + RTMemFree(pbData); + + /* assume fatal connection error. */ + Log(("utsTcpRecvPkt: RTTcpRead -> %Rrc -> utsTcpDisconnectClient(%RTsock)\n", rc, pClient->hTcpClient)); + utsTcpDisconnectClient(pClient); + } + } + + return rc; +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnPollSetAdd} + */ +static DECLCALLBACK(int) utsTcpPollSetAdd(RTPOLLSET hPollSet, PUTSTRANSPORTCLIENT pClient, uint32_t idStart) +{ + return RTPollSetAddSocket(hPollSet, pClient->hTcpClient, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, idStart); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnPollSetRemove} + */ +static DECLCALLBACK(int) utsTcpPollSetRemove(RTPOLLSET hPollSet, PUTSTRANSPORTCLIENT pClient, uint32_t idStart) +{ + RT_NOREF1(pClient); + return RTPollSetRemove(hPollSet, idStart); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnPollIn} + */ +static DECLCALLBACK(bool) utsTcpPollIn(PUTSTRANSPORTCLIENT pClient) +{ + int rc = RTTcpSelectOne(pClient->hTcpClient, 0/*cMillies*/); + return RT_SUCCESS(rc); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnTerm} + */ +static DECLCALLBACK(void) utsTcpTerm(void) +{ + /* Shut down the server (will wake up thread). */ + if (g_pTcpServer) + { + Log(("utsTcpTerm: Destroying server...\n")); + int rc = RTTcpServerDestroy(g_pTcpServer); + if (RT_FAILURE(rc)) + RTMsgInfo("RTTcpServerDestroy failed in utsTcpTerm: %Rrc", rc); + g_pTcpServer = NULL; + } + + Log(("utsTcpTerm: done\n")); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnInit} + */ +static DECLCALLBACK(int) utsTcpInit(void) +{ + int rc = RTTcpServerCreateEx(g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, &g_pTcpServer); + if (RT_FAILURE(rc)) + { + if (rc == VERR_NET_DOWN) + { + RTMsgInfo("RTTcpServerCreateEx(%s, %u,) failed: %Rrc, retrying for 20 seconds...\n", + g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, rc); + uint64_t StartMs = RTTimeMilliTS(); + do + { + RTThreadSleep(1000); + rc = RTTcpServerCreateEx(g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, &g_pTcpServer); + } while ( rc == VERR_NET_DOWN + && RTTimeMilliTS() - StartMs < 20000); + if (RT_SUCCESS(rc)) + RTMsgInfo("RTTcpServerCreateEx succceeded.\n"); + } + if (RT_FAILURE(rc)) + { + g_pTcpServer = NULL; + RTMsgError("RTTcpServerCreateEx(%s, %u,) failed: %Rrc\n", + g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, rc); + } + } + + return rc; +} + +/** Options */ +enum UTSTCPOPT +{ + UTSTCPOPT_BIND_ADDRESS = 1000, + UTSTCPOPT_BIND_PORT +}; + +/** + * @interface_method_impl{UTSTRANSPORT,pfnOption} + */ +static DECLCALLBACK(int) utsTcpOption(int ch, PCRTGETOPTUNION pVal) +{ + int rc; + + switch (ch) + { + case UTSTCPOPT_BIND_ADDRESS: + rc = RTStrCopy(g_szTcpBindAddr, sizeof(g_szTcpBindAddr), pVal->psz); + if (RT_FAILURE(rc)) + return RTMsgErrorRc(VERR_INVALID_PARAMETER, "TCP bind address is too long (%Rrc)", rc); + return VINF_SUCCESS; + + case UTSTCPOPT_BIND_PORT: + g_uTcpBindPort = pVal->u16 == 0 ? UTS_TCP_DEF_BIND_PORT : pVal->u16; + return VINF_SUCCESS; + } + return VERR_TRY_AGAIN; +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnUsage} + */ +DECLCALLBACK(void) utsTcpUsage(PRTSTREAM pStream) +{ + RTStrmPrintf(pStream, + " --tcp-bind-address <address>\n" + " The address(es) to listen to TCP connection on. Empty string\n" + " means any address, this is the default.\n" + " --tcp-bind-port <port>\n" + " The port to listen to TCP connections on.\n" + " Default: %u\n" + , UTS_TCP_DEF_BIND_PORT); +} + +/** Command line options for the TCP/IP transport layer. */ +static const RTGETOPTDEF g_TcpOpts[] = +{ + { "--tcp-bind-address", UTSTCPOPT_BIND_ADDRESS, RTGETOPT_REQ_STRING }, + { "--tcp-bind-port", UTSTCPOPT_BIND_PORT, RTGETOPT_REQ_UINT16 } +}; + +/** TCP/IP transport layer. */ +const UTSTRANSPORT g_TcpTransport = +{ + /* .szName = */ "tcp", + /* .pszDesc = */ "TCP/IP", + /* .cOpts = */ &g_TcpOpts[0], + /* .paOpts = */ RT_ELEMENTS(g_TcpOpts), + /* .pfnUsage = */ utsTcpUsage, + /* .pfnOption = */ utsTcpOption, + /* .pfnInit = */ utsTcpInit, + /* .pfnTerm = */ utsTcpTerm, + /* .pfnWaitForConnect = */ utsTcpWaitForConnect, + /* .pfnPollIn = */ utsTcpPollIn, + /* .pfnPollSetAdd = */ utsTcpPollSetAdd, + /* .pfnPollSetRemove = */ utsTcpPollSetRemove, + /* .pfnRecvPkt = */ utsTcpRecvPkt, + /* .pfnSendPkt = */ utsTcpSendPkt, + /* .pfnBabble = */ utsTcpBabble, + /* .pfnNotifyHowdy = */ utsTcpNotifyHowdy, + /* .pfnNotifyBye = */ utsTcpNotifyBye, + /* .pfnNotifyReboot = */ utsTcpNotifyReboot, + /* .u32EndMarker = */ UINT32_C(0x12345678) +}; diff --git a/src/VBox/ValidationKit/vms/t-dos20.txt b/src/VBox/ValidationKit/vms/t-dos20.txt new file mode 100644 index 00000000..f251f1f8 --- /dev/null +++ b/src/VBox/ValidationKit/vms/t-dos20.txt @@ -0,0 +1,27 @@ + +Test VM - t-dos20 - PC-DOS 2.0 on a harddisk +============================================ + +Setup: + - Create a default DOS VM 't-dos20', but restrict the disk size to 31MB. + - Partition the disk with a single partition. + - format C: /s + - Install (copy?) the DOS files to C:\DOS + - Copy DosSleep.exe and DosVmOff.com onto the disk. + - Create config.sys if needed. + - autoexec.bat (cannot test from test.bat, so all in one file): + PATH C:\DOS;C:\ + ECHO ON + ECHO TESTING chkdsk C: >COM1 + dossleep 1 + C:\DOS\CHKDSK C: + ECHO PASSED>COM1 + ECHO Powering off VM in 5 seconds... + DosSleep 1 + DosSleep 1 + DosSleep 1 + DosSleep 1 + DosSleep 1 + - More tests can be added, if desired. If test failure can be detected, end + with echoing 'FAILED' to COM1 instead of 'PASSED'. + diff --git a/src/VBox/ValidationKit/vms/t-dos401-emm386-win30me.txt b/src/VBox/ValidationKit/vms/t-dos401-emm386-win30me.txt new file mode 100644 index 00000000..38dc7a75 --- /dev/null +++ b/src/VBox/ValidationKit/vms/t-dos401-emm386-win30me.txt @@ -0,0 +1,9 @@ + +Test VM - t-dos401-emm386-win30me - DOS 4.01 /w patch and Windows 3.0 Multimedia Edition +======================================================================================== + +1. Set up t-dos401-win30me first. +2. Clone it into 't-dos401-emm386-win30me', make sure the .vdi is also renamed. +3. Edit C:\config.sys, adding "DEVICE=c:\DOS\EMM386.SYS 8192" after the HIMEM line. +4. Edit C:\test.bat to jump over the 386 mode and standard mode windows tests. + diff --git a/src/VBox/ValidationKit/vms/t-dos401-win30me.txt b/src/VBox/ValidationKit/vms/t-dos401-win30me.txt new file mode 100644 index 00000000..a68d197b --- /dev/null +++ b/src/VBox/ValidationKit/vms/t-dos401-win30me.txt @@ -0,0 +1,88 @@ + +Test VM - t-dos401-win30me - DOS 4.01 /w patch and Windows 3.0 Multimedia Edition +================================================================================= + +Setup: + - Create a default DOS VM 't-dos401-win30me', but restrict the memory to 4MB. + - Install DOS (single partition, etc). + - Install CDROM driver (e.g. OAK). + - Install Window 3.0 multimedia edition into C:\MWINDOWS. + - Select tandy soundblaster driver, make sure to configure + them correctly (port 220, irq 5, dma 5/7) or they will hang + the system. Check that it works. + - Disable the tandy welcome screen. + - Copy DosSleep.exe, DosVmOff.com, and WinExit.exe onto the disk. + - Start the macro recorder (recorder.exe) in windows and record two different + macro files, both which are associated with Shift-F7 and play in normal time. + First C:\WinExit.Rec: + - [Anything that might be useful+easy to test in read mode] + - File->Run in the program manager using keyboard (Alt-F, R) + - Type in "C:\WinExit.exe" and enter (no mouse). + - Save macro. + Second (hit File->New first) C:\TestMM.Rec: + - File->Run in the program manager using keyboard (Alt-F, R) + - Type in "welcome.exe" and enter (no mouse). + - Wait for the tandy welcome animation to finish. + - [Anything else that might be useful+easy to test] + - File->Run in program manager again. + - Type in "C:\WinExit.exe" and enter (no mouse). + - Save macro. + - Edit win.ini changing the REC file association to: + rec=RECORDER.EXE -H +F7 ^.REC + This allows us to load .REC with Shift-F7 macros inside them at startup and + execute them. (Windows 3.0 didn't have a startup folder.) + - Make a copy of win.ini called win-mm.ini changing the 'load=scrnsvr.exe' + statement to 'load=c:\testmm.rec'. + - Make a copy of win.ini called win-exit.ini changing the 'load=scrnsvr.exe' + statement to 'load=c:\winexit.rec'. + - Create c:\test.bat with the following content: + :dos-stuff + echo TESTING chkdsk C: >COM1 + dossleep 1 + chkdsk c: + if not errorlevel 0 goto fail + + echo TESTING c:\mwindows\msd.exe /f nul >COM1 + dossleep 1 + c:\mwindows\msd.exe /f nul + if not errorlevel 0 goto fail + + :386mode + echo TESTING win /3 >COM1 + dossleep 1 + copy c:\mwindows\win-mm.ini c:\mwindows\win.ini + win /3 + if not errorlevel 0 goto fail + + :standardmode-max-4mb + echo TESTING win /s >COM1 + dossleep 1 + copy c:\mwindows\win-mm.ini c:\mwindows\win.ini + win /s + if not errorlevel 0 goto fail + + :realmode + echo TESTING win /r >COM1 + dossleep 1 + copy c:\mwindows\win-exit.ini c:\mwindows\win.ini + win /r + if not errorlevel 0 goto fail + + :success + echo PASSED>COM1 + goto done + + :fail + echo ERRORLEVEL=%ERRORLEVEL% + echo FAILED>COM1 + + :done + echo powering off the vm in 5 seconds... + dossleep 1 + dossleep 1 + dossleep 1 + dossleep 1 + dossleep 1 + dosvmoff + - Edit c:\autoexec.bat appending "echo on" and "call c:\test.bat". + diff --git a/src/VBox/ValidationKit/vms/t-dos50-emm386-win31.txt b/src/VBox/ValidationKit/vms/t-dos50-emm386-win31.txt new file mode 100644 index 00000000..d0d2a872 --- /dev/null +++ b/src/VBox/ValidationKit/vms/t-dos50-emm386-win31.txt @@ -0,0 +1,9 @@ + +Test VM - t-dos50-emm386-win31 - DOS 5.0 and Windows 3.1, using EMM386 +====================================================================== + +1. Set up t-dos50-win31 first. +2. Clone it into 't-dos50-emm386-win31', make sure the .vdi is also renamed. +3. Edit C:\config.sys, adding "DEVICE=c:\WINDOWS\EMM386.SYS" after the HIMEM line. + + diff --git a/src/VBox/ValidationKit/vms/t-dos50-win31.txt b/src/VBox/ValidationKit/vms/t-dos50-win31.txt new file mode 100644 index 00000000..a64432bf --- /dev/null +++ b/src/VBox/ValidationKit/vms/t-dos50-win31.txt @@ -0,0 +1,78 @@ + +Test VM - t-dos50-win31 - DOS 5.0 and Windows 3.1 +================================================= + +Setup: + - Create a default DOS VM 't-dos50-win31'. + - Install DOS (single partition, etc). + - Install CDROM driver (e.g. OAK). + - Install Window 3.1 into C:\WINDOWS. + - Open the control panel and install sound blaster 1.5 drivers (port 220, irq 5, + dma 5/7). + - Open the control panel and make sure all system sounds have .WAV files associated + with them. Test that you can hear them. + - Copy DosSleep.exe, DosVmOff.com, and WinExit.exe onto the disk (C:\). + - Start the macro recorder (recorder.exe) in windows and record a macro file + that can be run automatically, make sure to associate it with Shift-F7 and + that it plays in normal time. + First C:\AbtExit.rec: + - Help->About in the program manager using keyboard (Alt-H, A). + - Dismiss using the enter key after a beat (no mouse). + - [Anything that might be useful+easy to test] + - File->Run in the program manager using keyboard (Alt-F, R) + - Type in "C:\WinExit.exe" and enter (no mouse). + - Save macro. + - Open the startup folder and create a program item with the following command line: + C:\WINDOWS\RECORDER.EXE -H +F7 C:\ABTREXIT.REC + - Create c:\test.bat with the following content: + :dos-stuff + echo TESTING CHKDSK C: >COM1 + dossleep 1 + C:\DOS\CHKDSK.EXE C: + if not errorlevel 0 goto fail + + echo TESTING msd.exe /P NUL >COM1 + dossleep 1 + C:\WINDOWS\MSD.EXE /P NUL + if not errorlevel 0 goto fail + + echo TESTING qbasic.exe /RUN C:\HELLO.BAS >COM1 + dossleep 1 + C:\DOS\QBASIC.EXE /RUN C:\HELLO.BAS + if not errorlevel 0 goto fail + + :386mode + echo TESTING win /3 >COM1 + dossleep 1 + win /3 + if not errorlevel 0 goto fail + + :standardmode + echo TESTING win /s >COM1 + dossleep 1 + win /s + if not errorlevel 0 goto fail + + :success + echo PASSED>COM1 + goto done + + :failed + echo FAILED>COM1 + goto done + + :done + echo powering off the VM in 5 seconds... + dossleep 1 + dossleep 1 + dossleep 1 + dossleep 1 + dossleep 1 + dosvmoff + - Create C:\HELLO.BAS with the following content: + PRINT "Hello World!" + SYSTEM + END + - Edit c:\autoexec.bat appending "echo on" and "call c:\test.bat". + - Check that C:\config.sys contains himem.sys from windows and no emm386. + diff --git a/src/VBox/ValidationKit/vms/t-dos622-emm386.txt b/src/VBox/ValidationKit/vms/t-dos622-emm386.txt new file mode 100644 index 00000000..30291cc6 --- /dev/null +++ b/src/VBox/ValidationKit/vms/t-dos622-emm386.txt @@ -0,0 +1,8 @@ + +Test VM - t-dos622-emm386 - DOS 6.22 using EMM386 +================================================= + +1. Set up t-dos622 first. +2. Clone it into 't-dos622-emm386', make sure the .vdi is also renamed. +3. Edit C:\config.sys, adding "DEVICE=c:\DOS\EMM386.SYS /V" after the HIMEM line. + diff --git a/src/VBox/ValidationKit/vms/t-dos622.txt b/src/VBox/ValidationKit/vms/t-dos622.txt new file mode 100644 index 00000000..6fcd888f --- /dev/null +++ b/src/VBox/ValidationKit/vms/t-dos622.txt @@ -0,0 +1,50 @@ + +Test VM - t-dos622 - DOS 6.22 +============================= + +Setup: + - Create a default DOS VM 't-dos622'. + - Install DOS (single partition, etc). + - Install CDROM driver (e.g. OAK). + - Copy DosSleep.exe and DosVmOff.com onto the disk (C:\). + - Create c:\test.bat with the following content: + echo on + + echo TESTING chkdsk.exe C: >COM1 + dossleep 1 + C:\DOS\CHKDSK.EXE C: + IF NOT ERRORLEVEL 0 goto fail + + echo TESTING msd.exe /P NUL >COM1 + dossleep 1 + C:\DOS\MSD.EXE /P NUL + + echo TESTING qbasic.exe /RUN C:\HELLO.BAS >COM1 + dossleep 1 + C:\DOS\QBASIC.EXE /RUN C:\HELLO.BAS + IF NOT ERRORLEVEL 0 goto fail + + REM Done + echo PASSED>COM1 + goto done + + :fail + echo FAILED>COM1 + goto done + + :done + echo Powering off the VM in 5 seconds... + dossleep 1 + dossleep 1 + dossleep 1 + dossleep 1 + dossleep 1 + dosvmoff + :exit + - Create C:\HELLO.BAS with the following content: + PRINT "Hello World!" + SYSTEM + END + - Edit c:\autoexec.bat appending "echo on" and "call c:\test.bat". + - Check that C:\config.sys contains himem.sys from windows and no emm386. + diff --git a/src/VBox/ValidationKit/vms/t-dos71.txt b/src/VBox/ValidationKit/vms/t-dos71.txt new file mode 100644 index 00000000..37cdb1be --- /dev/null +++ b/src/VBox/ValidationKit/vms/t-dos71.txt @@ -0,0 +1,34 @@ + +Test VM - t-dos71 - PC-DOS 7.1 (or 7.0) +======================================= + +Setup: + - Create a default DOS VM 't-dos71'. + - Install DOS (single partition, etc). Make sure to install the IBM anti virus. + - Make sure POWER.EXE is loaded by Config.sys and that emm386 isn't used. + - Add /V to HIMEM. + - Install CDROM driver (e.g. OAK). + - Copy DosSleep.exe and DosVmOff.com onto the disk (C:\). + - Create c:\test.bat with the following content: + echo TESTING: chkdsk C: >COM1 + C:\DOS\CHKDSK.COM C: + @IF NOT ERRORLEVEL 0 GOTO fail + @ + echo TESTING: IBM anti virus scan >COM1 + c:\dos\ibmavsp.exe C: -ALL -NLOG + @IF NOT ERRORLEVEL 0 GOTO fail + @ + echo PASSED>COM1 + goto done + + :fail + echo FAILED>COM1 + goto done + + :done + @echo Powering off in 5 seconds... + dossleep 5 + dosvmoff + - Edit c:\autoexec.bat appending "echo on" and "call c:\test.bat". + - Check that C:\config.sys contains himem.sys from windows and no emm386. + diff --git a/src/VBox/ValidationKit/vms/t-nsthwvirt-ubuntu-64.txt b/src/VBox/ValidationKit/vms/t-nsthwvirt-ubuntu-64.txt new file mode 100644 index 00000000..8a090a2e --- /dev/null +++ b/src/VBox/ValidationKit/vms/t-nsthwvirt-ubuntu-64.txt @@ -0,0 +1,26 @@ + +Test VM - nsthwvirt-ubuntu - Nested hardware-virtualization Ubuntu +================================================================== + +Setup: + - Configure a VM tst-nsthwvirt-ubuntu-64 with default Ubuntu 64-bit setting, + with a 6 GB or larger disk. + - Configure the VM with 4 GB of RAM. + - Make sure networking is NAT. + - Disable audio for the VM. + - Install Ubuntu 17.04 amd64. + - Disable screen-blanking, and suspend: + - Click 'System Settings' from the Unity bar on the left side or open + a terminal and run 'unity-control-center'. + - From 'Power': + - Switch 'Suspend when inactive for' to 'Dont suspend'. + - From 'Brightness & Lock': + - Switch 'Turn screen off when inactive' to 'Never' + - Switch 'Lock' to 'off'. + - Close the system settings window. + - Disable auto-mounting of the CDROM: + gsettings set org.gnome.desktop.media-handling automount false + - Disable VirtualBox Guest Additions that ship with Ubuntu 17.04 by default: + sudo sh -c "echo 'blacklist vboxguest' > /etc/modprobe.d/blacklist.conf" + sudo sh -c "echo 'blacklist vboxvideo' > /etc/modprobe.d/blacklist.conf" + - Proceed as shown in readme_first.txt diff --git a/src/VBox/ValidationKit/vms/t-nt310.txt b/src/VBox/ValidationKit/vms/t-nt310.txt new file mode 100644 index 00000000..32db4621 --- /dev/null +++ b/src/VBox/ValidationKit/vms/t-nt310.txt @@ -0,0 +1,27 @@ + +Test VM - t-nt310 - Windows NT 3.1 no service pack +================================================== + +Setup: + - Configure a VM t-nt310 with using the Windows NT 3.x settings, but with only + 64 MiB RAM (can't detect more) and 512 MiB disk (2GiB fails NTFS conversion). + - Enable COM1. + - Make sure networking is NAT and PCNET. + - Make sure you're using BusLogic for both HD and DVD-ROM. + - Enable DNS proxying: + VBoxManage modifyvm t-nt310 --natdnsproxy1 on. + - Optionally set 486 as the CPU profile (may avoid install problems): + VBoxManage modifyvm t-nt310 --cpu-profile "Intel 80486" + - Install the OS. + - PCNet are not included on install media, so get them of the + net and install via floppy when it fails to detect NICs. + - Use 'password' as Administrator password. + - Open the system settings and add an environment variable "SystemDrive" with the + value "C:" (NT 3.50 and later sets this automagically). + - While in the system settings, reduce the boot wait to 1 second. + - Open the network settings and add the TCPIP protocol. Configure it with IP + 10.0.2.15/24, gateway 10.0.2.2, and DNS 10.0.2.3 (via "Connections..." button). + - Reboot and check that you can ping stuff. + - Insert the ValidationKit ISO and install the test execution service, see + vboxtxs-readme.txt for details. Needs the NT 3.x bits. + - Proceed as shown in readme_first.txt diff --git a/src/VBox/ValidationKit/vms/t-nt350.txt b/src/VBox/ValidationKit/vms/t-nt350.txt new file mode 100644 index 00000000..3553edf7 --- /dev/null +++ b/src/VBox/ValidationKit/vms/t-nt350.txt @@ -0,0 +1,23 @@ + +Test VM - t-nt350 - Windows NT 3.5(0) no service pack +===================================================== + +Setup: + - Configure a VM t-nt350 with using the Windows NT 3.x settings, up + the memory to 128MiB. + - Enable COM1. + - Make sure networking is NAT and PCNET. + - Make sure you're using BusLogic for both HD and DVD-ROM. + - Optionally set 486 as the CPU profile (may avoid install problems): + VBoxManage modifyvm t-nt350 --cpu-profile "Intel 80486" + - Install the OS. + - You probably have to select the AMD PCNET Family Ethernet Adapter + manually if detection fails. + - Make sure to configure DHCP and DNS. + - In the system settings: + - Reduce the boot wait to 1 second. + - Disable automatic reboot in recovery settings. + - Check that you can ping stuff. + - Insert the ValidationKit ISO and install the test execution service, see + vboxtxs-readme.txt for details. Needs the NT 3.x bits. + - Proceed as shown in readme_first.txt diff --git a/src/VBox/ValidationKit/vms/t-nt4sp1.txt b/src/VBox/ValidationKit/vms/t-nt4sp1.txt new file mode 100644 index 00000000..9f1eac9c --- /dev/null +++ b/src/VBox/ValidationKit/vms/t-nt4sp1.txt @@ -0,0 +1,21 @@ + +Test VM - nt4sp1 - Windows NT 4 Service Pack 1 +============================================== + +Setup: + - Configure a VM t-nt4sp1 with default Windows NT 4 settings. Make sure to + configure the disk to 8 GB or higher capacity (2 GB suffices). + - Make sure networking is NAT. + - Install Windows NT 4 SP 1. + - Disable CD autorun: + - Start regedit. + - Set the value HKLM/System/CurrentControlSet/Services/Cdrom to 0. + - Shorten boot menu wait, disable automatic reboot on STOP, make it write a + dump: + - Right click on "My Computer", select "Properties" and go to the + "Startup/Shutdown" tab. + - Change the "Show list for" entry field to "1" second. + - Uncheck "Automatically reboot". + - Check "Write debugging information to". + - Check "Overwrite any existing file". + - Proceed as shown in readme_first.txt diff --git a/src/VBox/ValidationKit/vms/t-sol11u1.txt b/src/VBox/ValidationKit/vms/t-sol11u1.txt new file mode 100644 index 00000000..b57fc640 --- /dev/null +++ b/src/VBox/ValidationKit/vms/t-sol11u1.txt @@ -0,0 +1,16 @@ +Test VM - sol11u1 - Solaris 11 update 1 +======================================= + +Setup: + - Configure a VM t-sol11u1 with the default Solaris 11 settings. + - Make sure networking is NAT. + - Install Solaris 11 update 1, default user 'test'. + - Change the default password policy in /etc/default/passwd: + - MINUPPER=0 + - MINLOWER=0 + - MAXREPEATS=0 + - MINSPECIAL=0 + - MINDIGIT=0 + - Adjust the grub timeout to 1 second. + - ?More? + - Proceed as shown in readme_first.txt diff --git a/src/VBox/ValidationKit/vms/t-ubuntu-15_10-64-efi.txt b/src/VBox/ValidationKit/vms/t-ubuntu-15_10-64-efi.txt new file mode 100644 index 00000000..7e2315bb --- /dev/null +++ b/src/VBox/ValidationKit/vms/t-ubuntu-15_10-64-efi.txt @@ -0,0 +1,12 @@ + +Test VM - tst-ubuntu-15_10-64-efi - Ubuntu smoke test +===================================================== + +Setup: + - Configure a VM tst-ubuntu-15_10-64-efi with default Ubuntu 64-bit setting, + with a 6 GB or larger disk. + - Make sure that EFI under "System / Extended Features" is checked. + - Configure the VM with 4 GB of RAM. + - Make sure networking is NAT. + - Install Ubuntu 15.10 amd64. + - Proceed as shown in readme_first.txt diff --git a/src/VBox/ValidationKit/vms/t-ubuntu-20_04-64.txt b/src/VBox/ValidationKit/vms/t-ubuntu-20_04-64.txt new file mode 100644 index 00000000..fb06acf7 --- /dev/null +++ b/src/VBox/ValidationKit/vms/t-ubuntu-20_04-64.txt @@ -0,0 +1,36 @@ +Test VM - ubuntu-20-04_64 - Ubuntu 20.04 64-bit (for IOMMU testing) +=================================================================== + +Setup: + - Configure a VM tst-ubuntu-20_04-64 with default Ubuntu 64-bit setting, + with a default 10 GB or larger disk. + - Configure the VM with 2 GB of RAM, ICH9 chipset, I/O APIC and IOMMU (AMD or Intel). + - Make sure networking is NAT. + - Disable audio for the VM. + - Install Ubuntu 20.04 amd64. + Disallow "Install Updates During Installation" + - After installation, open "Settings" and configure as follows: + - Power: + - Set "Blank Screen" to "Never" + - Set "Automatic Suspend" to "Off" + - Displays: + - Set screen resolution to 1280x960 (4:3) + - Privacy -> Screen lock: + - Set "Automatic Screen Lock" to "Off" + - Set "Lock Screen On Suspend" to "Off" + - Set "Show notifications on Lock screen" to "Off" + - Disable VirtualBox Guest Additions that ship with Ubuntu by default: + - sudo sh -c "echo 'blacklist vboxguest' >> /etc/modprobe.d/blacklist.conf" + - sudo sh -c "echo 'blacklist vboxvideo' >> /etc/modprobe.d/blacklist.conf" + - Disable auto-mounting of the CDROM: + gsettings set org.gnome.desktop.media-handling automount false + sudo mkdir -p /media/cdrom + Add /dev/sr0 to /etc/fstab for mounting CD (see readme_first.txt for details). + - Enable address translation for Intel IOMMU: + - sudo nano /etc/default/grub + - Change GRUB_CMDLINE_LINUX_DEFAULT="intel_iommu=on" + i.e. remove "quiet splash" and replaces with "intel_iommu=on" instead. + This setting has no effect when AMD IOMMU is configured so always configure this so we can switch testing + AMD or Intel IOMMUs. + - Proceed as shown in readme_first.txt + diff --git a/src/VBox/ValidationKit/vms/t-win7-32-1.txt b/src/VBox/ValidationKit/vms/t-win7-32-1.txt new file mode 100644 index 00000000..2a35719a --- /dev/null +++ b/src/VBox/ValidationKit/vms/t-win7-32-1.txt @@ -0,0 +1,8 @@ + +Test VM - tst-win7-32-1 +======================= + +Based on tst-win7-32, but solves the following problems: + +- Activation nag screen should be gone, which should make TxS start reliably. +- Power management settings altered to not turn off the display after 5 / 10 minutes (blank screen). diff --git a/src/VBox/ValidationKit/vms/t-win7-32.txt b/src/VBox/ValidationKit/vms/t-win7-32.txt new file mode 100644 index 00000000..d1ce7ebd --- /dev/null +++ b/src/VBox/ValidationKit/vms/t-win7-32.txt @@ -0,0 +1,14 @@ + +Test VM - tst-win7-32 +===================== + +Setup: + - Configure a VM t-win7-32 with default windows 7 32-bit settings and disk size. + - VBoxManage unattended install t-win7-32 --iso i:\Windows\7\rtm\en_windows_7_enterprise_x86_dvd_x15-70745.iso \ + --hostname=t-win7-32.smoketest --user=Administrator --password=password + - Start VM and perform unattended installation. + - Eject DVD and floppy. + - Create a user 'test' without a password. + - Create a user 'test2' with 'test2' as password. + - Enable the guest user. + - Proceed as shown in readme_first.txt diff --git a/src/VBox/ValidationKit/vms/t-xppro.txt b/src/VBox/ValidationKit/vms/t-xppro.txt new file mode 100644 index 00000000..50dbb471 --- /dev/null +++ b/src/VBox/ValidationKit/vms/t-xppro.txt @@ -0,0 +1,44 @@ + +Test VM - xppro - Windows XP Professional +========================================= + +Setup: + - Configure a VM t-xppro1 with default Windows XP settings. 2 GB or more disk. + - Make sure PAE is disabled. + - Make sure networking is NAT. + - Install Windows XP Pro. + - Create a user 'test' without a password. + - Disable non-windows-logo signed driver popups: + - Start menu -> Right-click on "My Computer" -> Select Properties. + - Go to the Hardware tab and press the Driver Signing button. + - Select the "Ignore - Install the software anyway and don't ask for my + approval" radio button. + - Make sure "Make this action the system default" is checked. + - Ok both dialogs. + - Redo from start to verify that it actually changed. + - Complete disable windows update: + - Start menu -> Right-click on "My Computer" -> Select Properties. + - Go to the Automatic Updates tab. + - Select the "Turn off automatic updating" radio button. + - Ok the dialog. + - Disable automatic reboot on STOP, make it write a + dump: + - Right click on "My Computer", select "Properties" and go to the + "Advanced" tab and press the "Startupp and Recovery" button. + - Make sure the "Time to display list of operating systems" is 1. + - Uncheck "Automatically restart". + - Select "Kernel memory dump". + - Check "Overwrite any existing file". + - Ok both dialogs. + - Redo from start to verify correctness. + - Disable CD autorun. Follow these instructions: + http://support.microsoft.com/kb/967715 + - Disable driver search on Windows Update (will fail bitching about + unsigned/unknown publisher of iuident.cab, because we're getting + redirected when doing v4 update accesses): + - Run/start gpedit.msc. + - Navigate to User Configuration -> Administrative Templates -> System. + - Change "Configure driver search locations" to "Enabled" and check + the "Don't search Windows Update". + - Change "Windows Automatic Updates" to "Disabled". + - Proceed as shown in readme_first.txt |